From ad8abef74cebf75db6ce7f2ca3274b354a8a5b10 Mon Sep 17 00:00:00 2001 From: JFonS Date: Sat, 19 Dec 2020 14:42:31 +0100 Subject: [PATCH 1/7] Add OpenImageDenoise thirdparty library --- modules/denoise/SCsub | 118 + modules/denoise/config.py | 15 + modules/denoise/denoise_wrapper.cpp | 69 + modules/denoise/denoise_wrapper.h | 38 + modules/denoise/lightmap_denoiser.cpp | 65 + modules/denoise/lightmap_denoiser.h | 56 + modules/denoise/register_types.cpp | 40 + modules/denoise/register_types.h | 37 + modules/denoise/resource_to_cpp.py | 68 + thirdparty/README.md | 31 + thirdparty/oidn/LICENSE.txt | 202 + thirdparty/oidn/common/barrier.h | 52 + thirdparty/oidn/common/exception.h | 45 + thirdparty/oidn/common/platform.cpp | 114 + thirdparty/oidn/common/platform.h | 131 + thirdparty/oidn/common/ref.h | 163 + thirdparty/oidn/common/tensor.cpp | 83 + thirdparty/oidn/common/tensor.h | 66 + thirdparty/oidn/common/thread.cpp | 297 ++ thirdparty/oidn/common/thread.h | 202 + thirdparty/oidn/common/timer.h | 49 + thirdparty/oidn/core/api.cpp | 408 ++ thirdparty/oidn/core/autoencoder.cpp | 535 ++ thirdparty/oidn/core/autoencoder.h | 120 + thirdparty/oidn/core/buffer.h | 75 + thirdparty/oidn/core/common.h | 136 + thirdparty/oidn/core/device.cpp | 238 + thirdparty/oidn/core/device.h | 102 + thirdparty/oidn/core/filter.cpp | 27 + thirdparty/oidn/core/filter.h | 52 + thirdparty/oidn/core/image.h | 111 + thirdparty/oidn/core/input_reorder.h | 232 + thirdparty/oidn/core/math.h | 78 + thirdparty/oidn/core/network.cpp | 436 ++ thirdparty/oidn/core/network.h | 112 + thirdparty/oidn/core/node.h | 142 + thirdparty/oidn/core/output_reorder.h | 126 + thirdparty/oidn/core/transfer_function.cpp | 103 + thirdparty/oidn/core/transfer_function.h | 201 + thirdparty/oidn/core/upsample.h | 92 + thirdparty/oidn/core/weights_reorder.h | 99 + .../oidn/include/OpenImageDenoise/oidn.h | 214 + .../oidn/include/OpenImageDenoise/oidn.hpp | 468 ++ .../oidn/include/OpenImageDenoise/version.h | 23 + thirdparty/oidn/mkl-dnn/LICENSE | 214 + thirdparty/oidn/mkl-dnn/include/mkldnn.h | 1771 +++++++ thirdparty/oidn/mkl-dnn/include/mkldnn.hpp | 2615 ++++++++++ .../oidn/mkl-dnn/include/mkldnn_debug.h | 98 + .../oidn/mkl-dnn/include/mkldnn_types.h | 1415 +++++ .../oidn/mkl-dnn/include/mkldnn_version.h | 32 + .../oidn/mkl-dnn/include/mkldnn_version.h.in | 32 + .../src/common/batch_normalization.cpp | 104 + .../src/common/batch_normalization_pd.hpp | 240 + .../oidn/mkl-dnn/src/common/c_types_map.hpp | 550 ++ thirdparty/oidn/mkl-dnn/src/common/concat.cpp | 86 + .../oidn/mkl-dnn/src/common/concat_pd.hpp | 211 + .../oidn/mkl-dnn/src/common/convolution.cpp | 200 + .../mkl-dnn/src/common/convolution_pd.cpp | 56 + .../mkl-dnn/src/common/convolution_pd.hpp | 348 ++ .../oidn/mkl-dnn/src/common/deconvolution.cpp | 188 + .../mkl-dnn/src/common/deconvolution_pd.hpp | 293 ++ .../oidn/mkl-dnn/src/common/eltwise.cpp | 84 + .../oidn/mkl-dnn/src/common/eltwise_pd.hpp | 161 + thirdparty/oidn/mkl-dnn/src/common/engine.cpp | 75 + thirdparty/oidn/mkl-dnn/src/common/engine.hpp | 119 + .../oidn/mkl-dnn/src/common/inner_product.cpp | 106 + .../mkl-dnn/src/common/inner_product_pd.cpp | 56 + .../mkl-dnn/src/common/inner_product_pd.hpp | 321 ++ thirdparty/oidn/mkl-dnn/src/common/lrn.cpp | 91 + thirdparty/oidn/mkl-dnn/src/common/lrn_pd.hpp | 170 + .../oidn/mkl-dnn/src/common/math_utils.hpp | 280 + thirdparty/oidn/mkl-dnn/src/common/memory.cpp | 238 + thirdparty/oidn/mkl-dnn/src/common/memory.hpp | 63 + .../src/common/memory_desc_wrapper.cpp | 212 + .../src/common/memory_desc_wrapper.hpp | 400 ++ .../mkl-dnn/src/common/memory_tracking.hpp | 295 ++ .../oidn/mkl-dnn/src/common/mkldnn_debug.cpp | 131 + .../src/common/mkldnn_debug_autogenerated.cpp | 365 ++ .../oidn/mkl-dnn/src/common/mkldnn_thread.hpp | 115 + .../src/common/mkldnn_thread_parallel_nd.hpp | 277 + .../oidn/mkl-dnn/src/common/mkldnn_traits.hpp | 77 + thirdparty/oidn/mkl-dnn/src/common/nstl.hpp | 193 + .../oidn/mkl-dnn/src/common/pooling.cpp | 114 + .../oidn/mkl-dnn/src/common/pooling_pd.hpp | 238 + .../oidn/mkl-dnn/src/common/primitive.cpp | 103 + .../oidn/mkl-dnn/src/common/primitive.hpp | 76 + .../mkl-dnn/src/common/primitive_attr.cpp | 290 ++ .../mkl-dnn/src/common/primitive_attr.hpp | 183 + .../mkl-dnn/src/common/primitive_desc.cpp | 78 + .../mkl-dnn/src/common/primitive_desc.hpp | 174 + .../src/common/primitive_exec_types.cpp | 90 + .../src/common/primitive_exec_types.hpp | 68 + .../mkl-dnn/src/common/primitive_iterator.cpp | 89 + .../mkl-dnn/src/common/primitive_iterator.hpp | 79 + thirdparty/oidn/mkl-dnn/src/common/query.cpp | 59 + .../oidn/mkl-dnn/src/common/reorder.cpp | 68 + .../oidn/mkl-dnn/src/common/reorder_pd.hpp | 85 + thirdparty/oidn/mkl-dnn/src/common/rnn.cpp | 400 ++ thirdparty/oidn/mkl-dnn/src/common/rnn_pd.hpp | 280 + .../oidn/mkl-dnn/src/common/scratchpad.cpp | 112 + .../oidn/mkl-dnn/src/common/scratchpad.hpp | 36 + .../oidn/mkl-dnn/src/common/shuffle.cpp | 72 + .../oidn/mkl-dnn/src/common/shuffle_pd.hpp | 121 + .../oidn/mkl-dnn/src/common/softmax.cpp | 68 + .../oidn/mkl-dnn/src/common/softmax_pd.hpp | 161 + thirdparty/oidn/mkl-dnn/src/common/stream.cpp | 46 + thirdparty/oidn/mkl-dnn/src/common/stream.hpp | 44 + thirdparty/oidn/mkl-dnn/src/common/sum.cpp | 79 + thirdparty/oidn/mkl-dnn/src/common/sum_pd.hpp | 143 + .../oidn/mkl-dnn/src/common/tag_traits.hpp | 200 + .../oidn/mkl-dnn/src/common/type_helpers.hpp | 348 ++ thirdparty/oidn/mkl-dnn/src/common/utils.cpp | 135 + thirdparty/oidn/mkl-dnn/src/common/utils.hpp | 370 ++ .../oidn/mkl-dnn/src/common/verbose.cpp | 665 +++ .../oidn/mkl-dnn/src/common/verbose.hpp | 62 + .../oidn/mkl-dnn/src/common/z_magic.hpp | 46 + .../oidn/mkl-dnn/src/cpu/cpu_barrier.cpp | 112 + .../oidn/mkl-dnn/src/cpu/cpu_barrier.hpp | 60 + .../src/cpu/cpu_batch_normalization_pd.hpp | 40 + .../src/cpu/cpu_batch_normalization_utils.cpp | 140 + .../src/cpu/cpu_batch_normalization_utils.hpp | 43 + .../oidn/mkl-dnn/src/cpu/cpu_concat.cpp | 51 + .../oidn/mkl-dnn/src/cpu/cpu_concat_pd.hpp | 41 + .../mkl-dnn/src/cpu/cpu_convolution_pd.hpp | 74 + .../mkl-dnn/src/cpu/cpu_deconvolution_pd.hpp | 46 + .../oidn/mkl-dnn/src/cpu/cpu_eltwise_pd.hpp | 45 + .../oidn/mkl-dnn/src/cpu/cpu_engine.cpp | 324 ++ .../oidn/mkl-dnn/src/cpu/cpu_engine.hpp | 70 + .../mkl-dnn/src/cpu/cpu_inner_product_pd.hpp | 84 + .../oidn/mkl-dnn/src/cpu/cpu_isa_traits.hpp | 151 + .../oidn/mkl-dnn/src/cpu/cpu_lrn_pd.hpp | 42 + .../oidn/mkl-dnn/src/cpu/cpu_memory.cpp | 277 + .../oidn/mkl-dnn/src/cpu/cpu_memory.hpp | 89 + .../oidn/mkl-dnn/src/cpu/cpu_pooling_pd.hpp | 40 + .../oidn/mkl-dnn/src/cpu/cpu_primitive.hpp | 83 + .../oidn/mkl-dnn/src/cpu/cpu_reducer.cpp | 544 ++ .../oidn/mkl-dnn/src/cpu/cpu_reducer.hpp | 334 ++ .../oidn/mkl-dnn/src/cpu/cpu_reorder.cpp | 262 + .../oidn/mkl-dnn/src/cpu/cpu_reorder_pd.hpp | 48 + .../oidn/mkl-dnn/src/cpu/cpu_shuffle_pd.hpp | 41 + .../oidn/mkl-dnn/src/cpu/cpu_softmax_pd.hpp | 45 + thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum.cpp | 48 + .../oidn/mkl-dnn/src/cpu/cpu_sum_pd.hpp | 39 + .../src/cpu/gemm/f32/gemm_utils_f32.cpp | 372 ++ .../src/cpu/gemm/f32/gemm_utils_f32.hpp | 72 + .../gemm/f32/jit_avx512_common_gemm_f32.cpp | 2131 ++++++++ .../gemm/f32/jit_avx512_common_gemm_f32.hpp | 36 + .../src/cpu/gemm/f32/jit_avx_gemm_f32.cpp | 2705 ++++++++++ .../src/cpu/gemm/f32/jit_avx_gemm_f32.hpp | 37 + .../mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.cpp | 346 ++ .../mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.hpp | 36 + thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.cpp | 280 + thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.hpp | 58 + .../oidn/mkl-dnn/src/cpu/gemm/os_blas.hpp | 86 + .../mkl-dnn/src/cpu/gemm/s8x8s32/common.hpp | 206 + .../mkl-dnn/src/cpu/gemm/s8x8s32/gemv.hpp | 28 + .../s8x8s32/jit_avx512_core_gemm_s8u8s32.cpp | 1409 +++++ .../s8x8s32/jit_avx512_core_gemm_s8u8s32.hpp | 38 + .../jit_avx512_core_gemm_s8u8s32_kern.cpp | 539 ++ .../jit_avx512_core_gemm_s8u8s32_kern.hpp | 101 + .../s8x8s32/jit_avx512_core_gemv_s8u8s32.cpp | 290 ++ ...t_avx512_core_kernel_gemv_s8u8s32_kern.cpp | 411 ++ ...t_avx512_core_kernel_gemv_s8u8s32_kern.hpp | 64 + .../jit_avx512_core_u8_copy_an_kern.cpp | 819 +++ .../jit_avx512_core_u8_copy_at_kern.cpp | 2209 ++++++++ .../jit_avx512_core_u8_copy_bn_kern.cpp | 564 ++ .../jit_avx512_core_u8_copy_bt_kern.cpp | 501 ++ .../jit_avx512_core_u8_copy_sum_an_kern.cpp | 1283 +++++ .../jit_avx512_core_u8_copy_sum_at_kern.cpp | 3163 ++++++++++++ .../jit_avx512_core_u8_copy_sum_bn_kern.cpp | 821 +++ .../jit_avx512_core_u8_copy_sum_bt_kern.cpp | 647 +++ .../src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.cpp | 116 + .../src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.hpp | 38 + .../cpu/gemm/s8x8s32/simple_gemm_s8s8s32.cpp | 180 + .../cpu/gemm/s8x8s32/simple_gemm_s8s8s32.hpp | 37 + .../oidn/mkl-dnn/src/cpu/gemm_convolution.cpp | 307 ++ .../oidn/mkl-dnn/src/cpu/gemm_convolution.hpp | 250 + .../src/cpu/gemm_convolution_utils.cpp | 771 +++ .../src/cpu/gemm_convolution_utils.hpp | 66 + .../mkl-dnn/src/cpu/gemm_inner_product.cpp | 156 + .../mkl-dnn/src/cpu/gemm_inner_product.hpp | 157 + .../src/cpu/gemm_x8s8s32x_convolution.cpp | 740 +++ .../src/cpu/gemm_x8s8s32x_convolution.hpp | 266 + .../src/cpu/gemm_x8s8s32x_inner_product.cpp | 453 ++ .../src/cpu/gemm_x8s8s32x_inner_product.hpp | 166 + .../src/cpu/jit_avx2_1x1_conv_kernel_f32.cpp | 674 +++ .../src/cpu/jit_avx2_1x1_conv_kernel_f32.hpp | 110 + .../src/cpu/jit_avx2_1x1_convolution.cpp | 545 ++ .../src/cpu/jit_avx2_1x1_convolution.hpp | 344 ++ .../src/cpu/jit_avx2_conv_kernel_f32.cpp | 1501 ++++++ .../src/cpu/jit_avx2_conv_kernel_f32.hpp | 225 + .../mkl-dnn/src/cpu/jit_avx2_convolution.cpp | 410 ++ .../mkl-dnn/src/cpu/jit_avx2_convolution.hpp | 302 ++ .../cpu/jit_avx512_common_1x1_conv_kernel.cpp | 1255 +++++ .../cpu/jit_avx512_common_1x1_conv_kernel.hpp | 108 + .../cpu/jit_avx512_common_1x1_convolution.cpp | 816 +++ .../cpu/jit_avx512_common_1x1_convolution.hpp | 344 ++ .../src/cpu/jit_avx512_common_conv_kernel.cpp | 4539 +++++++++++++++++ .../src/cpu/jit_avx512_common_conv_kernel.hpp | 423 ++ ...avx512_common_conv_winograd_kernel_f32.cpp | 1163 +++++ ...avx512_common_conv_winograd_kernel_f32.hpp | 179 + .../src/cpu/jit_avx512_common_convolution.cpp | 1526 ++++++ .../src/cpu/jit_avx512_common_convolution.hpp | 302 ++ ...jit_avx512_common_convolution_winograd.cpp | 1215 +++++ ...jit_avx512_common_convolution_winograd.hpp | 318 ++ .../mkl-dnn/src/cpu/jit_avx512_common_lrn.cpp | 853 ++++ .../mkl-dnn/src/cpu/jit_avx512_common_lrn.hpp | 96 + .../jit_avx512_core_fp32_wino_conv_2x3.cpp | 1103 ++++ .../jit_avx512_core_fp32_wino_conv_2x3.hpp | 144 + .../jit_avx512_core_fp32_wino_conv_4x3.cpp | 1020 ++++ .../jit_avx512_core_fp32_wino_conv_4x3.hpp | 386 ++ ..._avx512_core_fp32_wino_conv_4x3_kernel.cpp | 2596 ++++++++++ ..._avx512_core_fp32_wino_conv_4x3_kernel.hpp | 291 ++ ..._avx512_core_u8s8s32x_wino_convolution.cpp | 1284 +++++ ..._avx512_core_u8s8s32x_wino_convolution.hpp | 128 + ...t_avx512_core_x8s8s32x_1x1_conv_kernel.cpp | 820 +++ ...t_avx512_core_x8s8s32x_1x1_conv_kernel.hpp | 131 + ...t_avx512_core_x8s8s32x_1x1_convolution.cpp | 292 ++ ...t_avx512_core_x8s8s32x_1x1_convolution.hpp | 159 + ...avx512_core_x8s8s32x_1x1_deconvolution.hpp | 140 + .../jit_avx512_core_x8s8s32x_conv_kernel.cpp | 1182 +++++ .../jit_avx512_core_x8s8s32x_conv_kernel.hpp | 239 + .../jit_avx512_core_x8s8s32x_convolution.cpp | 423 ++ .../jit_avx512_core_x8s8s32x_convolution.hpp | 115 + ...jit_avx512_core_x8s8s32x_deconvolution.cpp | 1034 ++++ ...jit_avx512_core_x8s8s32x_deconvolution.hpp | 237 + .../oidn/mkl-dnn/src/cpu/jit_generator.hpp | 773 +++ .../mkl-dnn/src/cpu/jit_primitive_conf.hpp | 481 ++ .../src/cpu/jit_sse42_1x1_conv_kernel_f32.cpp | 677 +++ .../src/cpu/jit_sse42_1x1_conv_kernel_f32.hpp | 104 + .../src/cpu/jit_sse42_1x1_convolution.cpp | 134 + .../src/cpu/jit_sse42_1x1_convolution.hpp | 96 + .../src/cpu/jit_sse42_conv_kernel_f32.cpp | 497 ++ .../src/cpu/jit_sse42_conv_kernel_f32.hpp | 93 + .../mkl-dnn/src/cpu/jit_sse42_convolution.cpp | 136 + .../mkl-dnn/src/cpu/jit_sse42_convolution.hpp | 103 + .../src/cpu/jit_transpose_src_utils.cpp | 1192 +++++ .../src/cpu/jit_transpose_src_utils.hpp | 145 + .../src/cpu/jit_uni_1x1_conv_utils.hpp | 327 ++ .../src/cpu/jit_uni_batch_normalization.cpp | 1407 +++++ .../src/cpu/jit_uni_batch_normalization.hpp | 100 + .../src/cpu/jit_uni_dw_conv_kernel_f32.cpp | 1302 +++++ .../src/cpu/jit_uni_dw_conv_kernel_f32.hpp | 253 + .../src/cpu/jit_uni_dw_convolution.cpp | 427 ++ .../src/cpu/jit_uni_dw_convolution.hpp | 266 + .../oidn/mkl-dnn/src/cpu/jit_uni_eltwise.cpp | 1142 +++++ .../oidn/mkl-dnn/src/cpu/jit_uni_eltwise.hpp | 193 + .../mkl-dnn/src/cpu/jit_uni_i8i8_pooling.cpp | 949 ++++ .../mkl-dnn/src/cpu/jit_uni_i8i8_pooling.hpp | 89 + .../oidn/mkl-dnn/src/cpu/jit_uni_lrn.cpp | 305 ++ .../oidn/mkl-dnn/src/cpu/jit_uni_lrn.hpp | 103 + .../src/cpu/jit_uni_lrn_kernel_f32.cpp | 1487 ++++++ .../src/cpu/jit_uni_lrn_kernel_f32.hpp | 183 + .../src/cpu/jit_uni_pool_kernel_f32.cpp | 699 +++ .../src/cpu/jit_uni_pool_kernel_f32.hpp | 192 + .../oidn/mkl-dnn/src/cpu/jit_uni_pooling.cpp | 264 + .../oidn/mkl-dnn/src/cpu/jit_uni_pooling.hpp | 182 + .../oidn/mkl-dnn/src/cpu/jit_uni_reorder.cpp | 1006 ++++ .../oidn/mkl-dnn/src/cpu/jit_uni_reorder.hpp | 127 + .../mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp | 313 ++ .../mkl-dnn/src/cpu/jit_utils/jit_utils.cpp | 115 + .../mkl-dnn/src/cpu/jit_utils/jit_utils.hpp | 32 + .../cpu/jit_utils/jitprofiling/LICENSE.BSD | 27 + .../src/cpu/jit_utils/jitprofiling/README.md | 1 + .../jit_utils/jitprofiling/ittnotify_config.h | 595 +++ .../jit_utils/jitprofiling/ittnotify_types.h | 94 + .../cpu/jit_utils/jitprofiling/jitprofiling.c | 293 ++ .../cpu/jit_utils/jitprofiling/jitprofiling.h | 673 +++ .../oidn/mkl-dnn/src/cpu/nchw_pooling.cpp | 317 ++ .../oidn/mkl-dnn/src/cpu/nchw_pooling.hpp | 147 + .../src/cpu/ncsp_batch_normalization.cpp | 382 ++ .../src/cpu/ncsp_batch_normalization.hpp | 160 + .../oidn/mkl-dnn/src/cpu/nhwc_pooling.cpp | 392 ++ .../oidn/mkl-dnn/src/cpu/nhwc_pooling.hpp | 210 + .../src/cpu/nspc_batch_normalization.cpp | 288 ++ .../src/cpu/nspc_batch_normalization.hpp | 169 + .../src/cpu/ref_batch_normalization.cpp | 265 + .../src/cpu/ref_batch_normalization.hpp | 127 + .../oidn/mkl-dnn/src/cpu/ref_concat.hpp | 97 + .../oidn/mkl-dnn/src/cpu/ref_convolution.cpp | 395 ++ .../oidn/mkl-dnn/src/cpu/ref_convolution.hpp | 194 + .../mkl-dnn/src/cpu/ref_deconvolution.cpp | 199 + .../mkl-dnn/src/cpu/ref_deconvolution.hpp | 502 ++ .../oidn/mkl-dnn/src/cpu/ref_eltwise.cpp | 297 ++ .../oidn/mkl-dnn/src/cpu/ref_eltwise.hpp | 168 + .../mkl-dnn/src/cpu/ref_inner_product.cpp | 285 ++ .../mkl-dnn/src/cpu/ref_inner_product.hpp | 159 + thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.cpp | 252 + thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.hpp | 136 + .../oidn/mkl-dnn/src/cpu/ref_pooling.cpp | 381 ++ .../oidn/mkl-dnn/src/cpu/ref_pooling.hpp | 119 + .../oidn/mkl-dnn/src/cpu/ref_shuffle.cpp | 153 + .../oidn/mkl-dnn/src/cpu/ref_shuffle.hpp | 111 + .../oidn/mkl-dnn/src/cpu/ref_softmax.cpp | 264 + .../oidn/mkl-dnn/src/cpu/ref_softmax.hpp | 186 + thirdparty/oidn/mkl-dnn/src/cpu/ref_sum.hpp | 101 + .../oidn/mkl-dnn/src/cpu/rnn/cell_common.cpp | 90 + .../oidn/mkl-dnn/src/cpu/rnn/cell_gru.cpp | 180 + .../oidn/mkl-dnn/src/cpu/rnn/cell_gru_lbr.cpp | 170 + .../oidn/mkl-dnn/src/cpu/rnn/cell_lstm.cpp | 143 + .../oidn/mkl-dnn/src/cpu/rnn/cell_rnn.cpp | 113 + .../oidn/mkl-dnn/src/cpu/rnn/cpu_rnn_pd.hpp | 191 + .../src/cpu/rnn/jit_uni_rnn_postgemm.hpp | 401 ++ .../oidn/mkl-dnn/src/cpu/rnn/ref_rnn.cpp | 788 +++ .../oidn/mkl-dnn/src/cpu/rnn/ref_rnn.hpp | 328 ++ .../oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp | 380 ++ .../oidn/mkl-dnn/src/cpu/rnn/rnn_utils.cpp | 426 ++ .../oidn/mkl-dnn/src/cpu/rnn/rnn_utils.hpp | 225 + .../oidn/mkl-dnn/src/cpu/simple_concat.cpp | 126 + .../oidn/mkl-dnn/src/cpu/simple_concat.hpp | 155 + .../oidn/mkl-dnn/src/cpu/simple_q10n.hpp | 98 + .../oidn/mkl-dnn/src/cpu/simple_reorder.hpp | 1022 ++++ .../oidn/mkl-dnn/src/cpu/simple_sum.cpp | 91 + .../oidn/mkl-dnn/src/cpu/simple_sum.hpp | 74 + .../oidn/mkl-dnn/src/cpu/wino_reorder.hpp | 376 ++ .../oidn/mkl-dnn/src/cpu/xbyak/COPYRIGHT | 47 + thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak.h | 2658 ++++++++++ .../mkl-dnn/src/cpu/xbyak/xbyak_bin2hex.h | 303 ++ .../mkl-dnn/src/cpu/xbyak/xbyak_mnemonic.h | 2017 ++++++++ .../oidn/mkl-dnn/src/cpu/xbyak/xbyak_util.h | 772 +++ .../oidn/patches/godot-changes-c58c5216.patch | 337 ++ .../patches/mkl-dnn-fix-vs2017-build.patch | 45 + thirdparty/oidn/weights/LICENSE.txt | 202 + thirdparty/oidn/weights/rtlightmap_hdr.tza | Bin 0 -> 5660131 bytes 324 files changed, 114426 insertions(+) create mode 100644 modules/denoise/SCsub create mode 100644 modules/denoise/config.py create mode 100644 modules/denoise/denoise_wrapper.cpp create mode 100644 modules/denoise/denoise_wrapper.h create mode 100644 modules/denoise/lightmap_denoiser.cpp create mode 100644 modules/denoise/lightmap_denoiser.h create mode 100644 modules/denoise/register_types.cpp create mode 100644 modules/denoise/register_types.h create mode 100644 modules/denoise/resource_to_cpp.py create mode 100644 thirdparty/oidn/LICENSE.txt create mode 100644 thirdparty/oidn/common/barrier.h create mode 100644 thirdparty/oidn/common/exception.h create mode 100644 thirdparty/oidn/common/platform.cpp create mode 100644 thirdparty/oidn/common/platform.h create mode 100644 thirdparty/oidn/common/ref.h create mode 100644 thirdparty/oidn/common/tensor.cpp create mode 100644 thirdparty/oidn/common/tensor.h create mode 100644 thirdparty/oidn/common/thread.cpp create mode 100644 thirdparty/oidn/common/thread.h create mode 100644 thirdparty/oidn/common/timer.h create mode 100644 thirdparty/oidn/core/api.cpp create mode 100644 thirdparty/oidn/core/autoencoder.cpp create mode 100644 thirdparty/oidn/core/autoencoder.h create mode 100644 thirdparty/oidn/core/buffer.h create mode 100644 thirdparty/oidn/core/common.h create mode 100644 thirdparty/oidn/core/device.cpp create mode 100644 thirdparty/oidn/core/device.h create mode 100644 thirdparty/oidn/core/filter.cpp create mode 100644 thirdparty/oidn/core/filter.h create mode 100644 thirdparty/oidn/core/image.h create mode 100644 thirdparty/oidn/core/input_reorder.h create mode 100644 thirdparty/oidn/core/math.h create mode 100644 thirdparty/oidn/core/network.cpp create mode 100644 thirdparty/oidn/core/network.h create mode 100644 thirdparty/oidn/core/node.h create mode 100644 thirdparty/oidn/core/output_reorder.h create mode 100644 thirdparty/oidn/core/transfer_function.cpp create mode 100644 thirdparty/oidn/core/transfer_function.h create mode 100644 thirdparty/oidn/core/upsample.h create mode 100644 thirdparty/oidn/core/weights_reorder.h create mode 100644 thirdparty/oidn/include/OpenImageDenoise/oidn.h create mode 100644 thirdparty/oidn/include/OpenImageDenoise/oidn.hpp create mode 100644 thirdparty/oidn/include/OpenImageDenoise/version.h create mode 100644 thirdparty/oidn/mkl-dnn/LICENSE create mode 100644 thirdparty/oidn/mkl-dnn/include/mkldnn.h create mode 100644 thirdparty/oidn/mkl-dnn/include/mkldnn.hpp create mode 100644 thirdparty/oidn/mkl-dnn/include/mkldnn_debug.h create mode 100644 thirdparty/oidn/mkl-dnn/include/mkldnn_types.h create mode 100644 thirdparty/oidn/mkl-dnn/include/mkldnn_version.h create mode 100644 thirdparty/oidn/mkl-dnn/include/mkldnn_version.h.in create mode 100644 thirdparty/oidn/mkl-dnn/src/common/batch_normalization.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/batch_normalization_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/c_types_map.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/concat.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/concat_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/convolution_pd.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/convolution_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/deconvolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/deconvolution_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/eltwise.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/eltwise_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/engine.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/engine.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/inner_product.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/lrn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/lrn_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/math_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/memory.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/memory.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/memory_tracking.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread_parallel_nd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/mkldnn_traits.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/nstl.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/pooling.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/pooling_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_attr.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_attr.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_desc.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_desc.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/query.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/reorder.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/reorder_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/rnn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/rnn_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/scratchpad.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/scratchpad.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/shuffle.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/shuffle_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/softmax.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/softmax_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/stream.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/stream.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/sum.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/sum_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/tag_traits.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/type_helpers.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/verbose.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/verbose.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/common/z_magic.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_convolution_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_deconvolution_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_eltwise_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_inner_product_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_isa_traits.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_lrn_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_pooling_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_primitive.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_shuffle_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_softmax_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/os_blas.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/common.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/gemv.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemv_s8u8s32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_an_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_at_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bn_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bt_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_an_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_at_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bn_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bt_kern.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_deconvolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_generator.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_primitive_conf.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_1x1_conv_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/LICENSE.BSD create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/README.md create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_config.h create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_types.h create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.h create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_concat.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/ref_sum.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_common.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru_lbr.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_lstm.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_rnn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/cpu_rnn_pd.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/jit_uni_rnn_postgemm.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/simple_q10n.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/simple_reorder.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.cpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/wino_reorder.hpp create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/xbyak/COPYRIGHT create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak.h create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_bin2hex.h create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_mnemonic.h create mode 100644 thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_util.h create mode 100644 thirdparty/oidn/patches/godot-changes-c58c5216.patch create mode 100644 thirdparty/oidn/patches/mkl-dnn-fix-vs2017-build.patch create mode 100644 thirdparty/oidn/weights/LICENSE.txt create mode 100644 thirdparty/oidn/weights/rtlightmap_hdr.tza diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub new file mode 100644 index 000000000000..bf3bd7d0739e --- /dev/null +++ b/modules/denoise/SCsub @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +import resource_to_cpp + +Import("env") +Import("env_modules") + +env_oidn = env_modules.Clone() + +# Thirdparty source files +thirdparty_dir = "#thirdparty/oidn/" +thirdparty_sources = [ + "core/api.cpp", + "core/device.cpp", + "core/filter.cpp", + "core/network.cpp", + "core/autoencoder.cpp", + "core/transfer_function.cpp", + "weights/rtlightmap_hdr.gen.cpp", + "mkl-dnn/src/common/batch_normalization.cpp", + "mkl-dnn/src/common/concat.cpp", + "mkl-dnn/src/common/convolution.cpp", + "mkl-dnn/src/common/convolution_pd.cpp", + "mkl-dnn/src/common/deconvolution.cpp", + "mkl-dnn/src/common/eltwise.cpp", + "mkl-dnn/src/common/engine.cpp", + "mkl-dnn/src/common/inner_product.cpp", + "mkl-dnn/src/common/inner_product_pd.cpp", + "mkl-dnn/src/common/lrn.cpp", + "mkl-dnn/src/common/memory.cpp", + "mkl-dnn/src/common/memory_desc_wrapper.cpp", + "mkl-dnn/src/common/mkldnn_debug.cpp", + "mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp", + "mkl-dnn/src/common/pooling.cpp", + "mkl-dnn/src/common/primitive.cpp", + "mkl-dnn/src/common/primitive_attr.cpp", + "mkl-dnn/src/common/primitive_desc.cpp", + "mkl-dnn/src/common/primitive_exec_types.cpp", + "mkl-dnn/src/common/primitive_iterator.cpp", + "mkl-dnn/src/common/query.cpp", + "mkl-dnn/src/common/reorder.cpp", + "mkl-dnn/src/common/rnn.cpp", + "mkl-dnn/src/common/scratchpad.cpp", + "mkl-dnn/src/common/shuffle.cpp", + "mkl-dnn/src/common/softmax.cpp", + "mkl-dnn/src/common/stream.cpp", + "mkl-dnn/src/common/sum.cpp", + "mkl-dnn/src/common/utils.cpp", + "mkl-dnn/src/common/verbose.cpp", + "mkl-dnn/src/cpu/cpu_barrier.cpp", + "mkl-dnn/src/cpu/cpu_concat.cpp", + "mkl-dnn/src/cpu/cpu_engine.cpp", + "mkl-dnn/src/cpu/cpu_memory.cpp", + "mkl-dnn/src/cpu/cpu_reducer.cpp", + "mkl-dnn/src/cpu/cpu_reorder.cpp", + "mkl-dnn/src/cpu/cpu_sum.cpp", + "mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_avx2_convolution.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp", + "mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp", + "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp", + "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp", + "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp", + "mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_sse42_convolution.cpp", + "mkl-dnn/src/cpu/jit_transpose_src_utils.cpp", + "mkl-dnn/src/cpu/jit_uni_eltwise.cpp", + "mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp", + "mkl-dnn/src/cpu/jit_uni_pooling.cpp", + "mkl-dnn/src/cpu/jit_uni_reorder.cpp", + "mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp", + "mkl-dnn/src/cpu/jit_utils/jit_utils.cpp", + "mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c", + "common/platform.cpp", + "common/thread.cpp", + "common/tensor.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +thirdparty_include_dirs = [ + "", + "include", + "mkl-dnn/include", + "mkl-dnn/src", + "mkl-dnn/src/common", + "mkl-dnn/src/cpu/xbyak", + "mkl-dnn/src/cpu", +] +thirdparty_include_dirs = [thirdparty_dir + file for file in thirdparty_include_dirs] + + +env_oidn.Prepend(CPPPATH=thirdparty_include_dirs) +env_oidn.Append( + CPPDEFINES=[ + "MKLDNN_THR=MKLDNN_THR_SEQ", + "OIDN_STATIC_LIB", + "__STDC_CONSTANT_MACROS", + "__STDC_LIMIT_MACROS", + "DISABLE_VERBOSE", + "MKLDNN_ENABLE_CONCURRENT_EXEC", + "NDEBUG", + ] +) + +env_thirdparty = env_oidn.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza" +weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" + +env_thirdparty.Depends(weights_out_path, weights_in_path) +env_thirdparty.CommandNoCache(weights_out_path, weights_in_path, resource_to_cpp.tza_to_cpp) + +env_oidn.add_source_files(env.modules_sources, "denoise_wrapper.cpp") +env_modules.add_source_files(env.modules_sources, ["register_types.cpp", "lightmap_denoiser.cpp"]) diff --git a/modules/denoise/config.py b/modules/denoise/config.py new file mode 100644 index 000000000000..211b201fec7c --- /dev/null +++ b/modules/denoise/config.py @@ -0,0 +1,15 @@ +def can_build(env, platform): + # Thirdparty dependency OpenImage Denoise includes oneDNN library + # which only supports 64-bit architectures. + # It's also only relevant for tools build and desktop platforms, + # as doing lightmap generation and denoising on Android or HTML5 + # would be a bit far-fetched. + # Note: oneDNN doesn't support ARM64, OIDN needs updating to the latest version + supported_platform = platform in ["x11", "osx", "windows", "server"] + supported_bits = env["bits"] == "64" + supported_arch = env["arch"] != "arm64" + return env["tools"] and supported_platform and supported_bits and supported_arch + + +def configure(env): + pass diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/denoise/denoise_wrapper.cpp new file mode 100644 index 000000000000..a52ac735a62b --- /dev/null +++ b/modules/denoise/denoise_wrapper.cpp @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* denoise_wrapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "denoise_wrapper.h" +#include "core/os/copymem.h" +#include "core/os/memory.h" +#include "thirdparty/oidn/include/OpenImageDenoise/oidn.h" +#include + +void *oidn_denoiser_init() { + OIDNDeviceImpl *device = oidnNewDevice(OIDN_DEVICE_TYPE_CPU); + oidnCommitDevice(device); + return device; +} + +bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) { + OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr; + OIDNFilter filter = oidnNewFilter(device, "RTLightmap"); + void *input_buffer = memalloc(p_width * p_height * 3 * sizeof(float)); + copymem(input_buffer, p_floats, p_width * p_height * 3 * sizeof(float)); + oidnSetSharedFilterImage(filter, "color", input_buffer, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); + oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); + oidnSetFilter1b(filter, "hdr", true); + //oidnSetFilter1f(filter, "hdrScale", 1.0f); + //oidnSetFilter1i(filter, "verbose", 4); + oidnCommitFilter(filter); + oidnExecuteFilter(filter); + + const char *msg; + bool success = true; + if (oidnGetDeviceError(device, &msg) != OIDN_ERROR_NONE) { + printf("LightmapDenoiser: %s\n", msg); + success = false; + } + + oidnReleaseFilter(filter); + return success; +} + +void oidn_denoiser_finish(void *device) { + oidnReleaseDevice((OIDNDeviceImpl *)device); +} diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h new file mode 100644 index 000000000000..2107df09c174 --- /dev/null +++ b/modules/denoise/denoise_wrapper.h @@ -0,0 +1,38 @@ +/*************************************************************************/ +/* denoise_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DENOISE_WRAPPER_H +#define DENOISE_WRAPPER_H + +void *oidn_denoiser_init(); +bool oidn_denoise(void *device, float *p_floats, int p_width, int p_height); +void oidn_denoiser_finish(void *device); + +#endif // DENOISE_WRAPPER_H diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/denoise/lightmap_denoiser.cpp new file mode 100644 index 000000000000..7726e0dc065f --- /dev/null +++ b/modules/denoise/lightmap_denoiser.cpp @@ -0,0 +1,65 @@ +/*************************************************************************/ +/* lightmap_denoiser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "lightmap_denoiser.h" +#include "denoise_wrapper.h" + +LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() { + return memnew(LightmapDenoiserOIDN); +} + +void LightmapDenoiserOIDN::make_default_denoiser() { + create_function = create_oidn_denoiser; +} + +Ref LightmapDenoiserOIDN::denoise_image(const Ref &p_image) { + Ref img = p_image->duplicate(); + + img->convert(Image::FORMAT_RGBF); + + PoolByteArray data = img->get_data(); + { + PoolByteArray::Write w = data.write(); + if (!oidn_denoise(device, (float *)w.ptr(), img->get_width(), img->get_height())) { + return p_image; + } + } + + img->create(img->get_width(), img->get_height(), false, img->get_format(), data); + return img; +} + +LightmapDenoiserOIDN::LightmapDenoiserOIDN() { + device = oidn_denoiser_init(); +} + +LightmapDenoiserOIDN::~LightmapDenoiserOIDN() { + oidn_denoiser_finish(device); +} diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h new file mode 100644 index 000000000000..74a9d8af8669 --- /dev/null +++ b/modules/denoise/lightmap_denoiser.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* lightmap_denoiser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LIGHTMAP_DENOISER_H +#define LIGHTMAP_DENOISER_H + +#include "core/class_db.h" +#include "scene/3d/lightmapper.h" + +struct OIDNDeviceImpl; + +class LightmapDenoiserOIDN : public LightmapDenoiser { + GDCLASS(LightmapDenoiserOIDN, LightmapDenoiser); + +protected: + void *device = nullptr; + +public: + static LightmapDenoiser *create_oidn_denoiser(); + + Ref denoise_image(const Ref &p_image) override; + + static void make_default_denoiser(); + + LightmapDenoiserOIDN(); + ~LightmapDenoiserOIDN(); +}; + +#endif // LIGHTMAP_DENOISER_H diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp new file mode 100644 index 000000000000..b78734a5318c --- /dev/null +++ b/modules/denoise/register_types.cpp @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" +#include "core/engine.h" +#include "lightmap_denoiser.h" + +void register_denoise_types() { + LightmapDenoiserOIDN::make_default_denoiser(); +} + +void unregister_denoise_types() { +} diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h new file mode 100644 index 000000000000..f0f1f44bfec2 --- /dev/null +++ b/modules/denoise/register_types.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DENOISE_REGISTER_TYPES_H +#define DENOISE_REGISTER_TYPES_H + +void register_denoise_types(); +void unregister_denoise_types(); + +#endif // DENOISE_REGISTER_TYPES_H diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py new file mode 100644 index 000000000000..6c8327735599 --- /dev/null +++ b/modules/denoise/resource_to_cpp.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +## ======================================================================== ## +## Copyright 2009-2019 Intel Corporation ## +## ## +## Licensed under the Apache License, Version 2.0 (the "License"); ## +## you may not use this file except in compliance with the License. ## +## You may obtain a copy of the License at ## +## ## +## http://www.apache.org/licenses/LICENSE-2.0 ## +## ## +## Unless required by applicable law or agreed to in writing, software ## +## distributed under the License is distributed on an "AS IS" BASIS, ## +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## +## See the License for the specific language governing permissions and ## +## limitations under the License. ## +## ======================================================================== ## + +import os +from array import array + +# Generates a C++ file from the specified binary resource file +def generate(in_path, out_path): + + namespace = "oidn::weights" + scopes = namespace.split("::") + + file_name = os.path.basename(in_path) + var_name = os.path.splitext(file_name)[0] + + with open(in_path, "rb") as in_file, open(out_path, "w") as out_file: + # Header + out_file.write("// Generated from: %s\n" % file_name) + out_file.write("#include \n\n") + + # Open the namespaces + for s in scopes: + out_file.write("namespace %s {\n" % s) + if scopes: + out_file.write("\n") + + # Read the file + in_data = array("B", in_file.read()) + + # Write the size + out_file.write("//const size_t %s_size = %d;\n\n" % (var_name, len(in_data))) + + # Write the data + out_file.write("unsigned char %s[] = {" % var_name) + for i in range(len(in_data)): + c = in_data[i] + if i > 0: + out_file.write(",") + if (i + 1) % 20 == 1: + out_file.write("\n") + out_file.write("%d" % c) + out_file.write("\n};\n") + + # Close the namespaces + if scopes: + out_file.write("\n") + for scope in reversed(scopes): + out_file.write("} // namespace %s\n" % scope) + + +def tza_to_cpp(target, source, env): + for x in zip(source, target): + generate(str(x[0]), str(x[1])) diff --git a/thirdparty/README.md b/thirdparty/README.md index 418c5cbeb5b7..3a7100f732a5 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -360,6 +360,37 @@ Files extracted from the upstream source: - LICENSE.txt +## oidn + +- Upstream: https://github.com/OpenImageDenoise/oidn +- Version: 1.1.0 (c58c5216db05ceef4cde5a096862f2eeffd14c06, 2019) +- License: Apache 2.0 + +Files extracted from upstream source: + +common/* (except tasking.* and CMakeLists.txt) +core/* +include/OpenImageDenoise/* (except version.h.in) +LICENSE.txt +mkl-dnn/include/* +mkl-dnn/src/* (except CMakeLists.txt) +weights/rtlightmap_hdr.tza +scripts/resource_to_cpp.py + +Modified files: +Modifications are marked with `// -- GODOT start --` and `// -- GODOT end --`. +Patch files are provided in `oidn/patches/`. + +core/autoencoder.cpp +core/autoencoder.h +core/common.h +core/device.cpp +core/device.h +core/transfer_function.cpp + +scripts/resource_to_cpp.py (used in modules/denoise/resource_to_cpp.py) + + ## opus - Upstream: https://opus-codec.org diff --git a/thirdparty/oidn/LICENSE.txt b/thirdparty/oidn/LICENSE.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/thirdparty/oidn/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/thirdparty/oidn/common/barrier.h b/thirdparty/oidn/common/barrier.h new file mode 100644 index 000000000000..b20f670053b1 --- /dev/null +++ b/thirdparty/oidn/common/barrier.h @@ -0,0 +1,52 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "platform.h" +#include +#include + +namespace oidn { + + class Barrier + { + private: + std::mutex m; + std::condition_variable cv; + volatile int count; + + public: + Barrier(int count) : count(count) {} + + void wait() + { + std::unique_lock lk(m); + count--; + + if (count == 0) + { + lk.unlock(); + cv.notify_all(); + } + else + { + cv.wait(lk, [&]{ return count == 0; }); + } + } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/common/exception.h b/thirdparty/oidn/common/exception.h new file mode 100644 index 000000000000..18069c6a7dcf --- /dev/null +++ b/thirdparty/oidn/common/exception.h @@ -0,0 +1,45 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include +#include "platform.h" + +namespace oidn { + + class Exception : public std::exception + { + private: + Error error; + const char* message; + + public: + Exception(Error error, const char* message) + : error(error), message(message) {} + + Error code() const noexcept + { + return error; + } + + const char* what() const noexcept override + { + return message; + } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/common/platform.cpp b/thirdparty/oidn/common/platform.cpp new file mode 100644 index 000000000000..59a14ff47c1f --- /dev/null +++ b/thirdparty/oidn/common/platform.cpp @@ -0,0 +1,114 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "platform.h" + +namespace oidn { + + // ---------------------------------------------------------------------------- + // Common functions + // ---------------------------------------------------------------------------- + + void* alignedMalloc(size_t size, size_t alignment) + { + if (size == 0) + return nullptr; + + assert((alignment & (alignment-1)) == 0); + void* ptr = _mm_malloc(size, alignment); + + if (ptr == nullptr) + throw std::bad_alloc(); + + return ptr; + } + + void alignedFree(void* ptr) + { + if (ptr) + _mm_free(ptr); + } + + // ---------------------------------------------------------------------------- + // System information + // ---------------------------------------------------------------------------- + + std::string getPlatformName() + { + std::string name; + + #if defined(__linux__) + name = "Linux"; + #elif defined(__FreeBSD__) + name = "FreeBSD"; + #elif defined(__CYGWIN__) + name = "Cygwin"; + #elif defined(_WIN32) + name = "Windows"; + #elif defined(__APPLE__) + name = "macOS"; + #elif defined(__unix__) + name = "Unix"; + #else + return "Unknown"; + #endif + + #if defined(__x86_64__) || defined(_M_X64) || defined(__ia64__) || defined(__aarch64__) + name += " (64-bit)"; + #else + name += " (32-bit)"; + #endif + + return name; + } + + std::string getCompilerName() + { + #if defined(__INTEL_COMPILER) + int mayor = __INTEL_COMPILER / 100 % 100; + int minor = __INTEL_COMPILER % 100; + std::string version = "Intel Compiler "; + version += toString(mayor); + version += "." + toString(minor); + #if defined(__INTEL_COMPILER_UPDATE) + version += "." + toString(__INTEL_COMPILER_UPDATE); + #endif + return version; + #elif defined(__clang__) + return "Clang " __clang_version__; + #elif defined(__GNUC__) + return "GCC " __VERSION__; + #elif defined(_MSC_VER) + std::string version = toString(_MSC_FULL_VER); + version.insert(4, "."); + version.insert(9, "."); + version.insert(2, "."); + return "Visual C++ Compiler " + version; + #else + return "Unknown"; + #endif + } + + std::string getBuildName() + { + #if defined(NDEBUG) + return "Release"; + #else + return "Debug"; + #endif + } + +} // namespace oidn diff --git a/thirdparty/oidn/common/platform.h b/thirdparty/oidn/common/platform.h new file mode 100644 index 000000000000..9373b617b57c --- /dev/null +++ b/thirdparty/oidn/common/platform.h @@ -0,0 +1,131 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#if defined(_WIN32) + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include +#elif defined(__APPLE__) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/OpenImageDenoise/oidn.hpp" + +namespace oidn { + + // ---------------------------------------------------------------------------- + // Macros + // ---------------------------------------------------------------------------- + + #if defined(_WIN32) + // Windows + #if !defined(__noinline) + #define __noinline __declspec(noinline) + #endif + #else + // Unix + #if !defined(__forceinline) + #define __forceinline inline __attribute__((always_inline)) + #endif + #if !defined(__noinline) + #define __noinline __attribute__((noinline)) + #endif + #endif + + #ifndef UNUSED + #define UNUSED(x) ((void)x) + #endif + #ifndef MAYBE_UNUSED + #define MAYBE_UNUSED(x) UNUSED(x) + #endif + + // ---------------------------------------------------------------------------- + // Error handling and debugging + // ---------------------------------------------------------------------------- + + struct Verbose + { + int verbose; + + Verbose(int v = 0) : verbose(v) {} + __forceinline bool isVerbose(int v = 1) const { return v <= verbose; } + }; + + #define OIDN_WARNING(message) { if (isVerbose()) std::cerr << "Warning: " << message << std::endl; } + #define OIDN_FATAL(message) throw std::runtime_error(message); + + // ---------------------------------------------------------------------------- + // Common functions + // ---------------------------------------------------------------------------- + + using std::min; + using std::max; + + template + __forceinline T clamp(const T& value, const T& minValue, const T& maxValue) + { + return min(max(value, minValue), maxValue); + } + + void* alignedMalloc(size_t size, size_t alignment); + void alignedFree(void* ptr); + + template + inline std::string toString(const T& a) + { + std::stringstream sm; + sm << a; + return sm.str(); + } + +#if defined(__APPLE__) + template + bool getSysctl(const char* name, T& value) + { + int64_t result = 0; + size_t size = sizeof(result); + + if (sysctlbyname(name, &result, &size, nullptr, 0) != 0) + return false; + + value = T(result); + return true; + } +#endif + + // ---------------------------------------------------------------------------- + // System information + // ---------------------------------------------------------------------------- + + std::string getPlatformName(); + std::string getCompilerName(); + std::string getBuildName(); + +} // namespace oidn diff --git a/thirdparty/oidn/common/ref.h b/thirdparty/oidn/common/ref.h new file mode 100644 index 000000000000..de44603af205 --- /dev/null +++ b/thirdparty/oidn/common/ref.h @@ -0,0 +1,163 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "platform.h" + +namespace oidn { + + class RefCount + { + private: + std::atomic count; + + public: + __forceinline RefCount(int count = 0) noexcept : count(count) {} + + __forceinline size_t incRef() noexcept + { + return count.fetch_add(1) + 1; + } + + __forceinline size_t decRef() + { + const size_t newCount = decRefKeep(); + if (newCount == 0) + destroy(); + return newCount; + } + + __forceinline size_t decRefKeep() noexcept + { + return count.fetch_add(-1) - 1; + } + + __forceinline void destroy() + { + delete this; + } + + protected: + // Disable copying + RefCount(const RefCount&) = delete; + RefCount& operator =(const RefCount&) = delete; + + virtual ~RefCount() noexcept = default; + }; + + template + class Ref + { + private: + T* ptr; + + public: + __forceinline Ref() noexcept : ptr(nullptr) {} + __forceinline Ref(std::nullptr_t) noexcept : ptr(nullptr) {} + __forceinline Ref(const Ref& other) noexcept : ptr(other.ptr) { if (ptr) ptr->incRef(); } + __forceinline Ref(Ref&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; } + __forceinline Ref(T* ptr) noexcept : ptr(ptr) { if (ptr) ptr->incRef(); } + + template + __forceinline Ref(const Ref& other) noexcept : ptr(other.get()) { if (ptr) ptr->incRef(); } + + template + __forceinline explicit Ref(Y* ptr) noexcept : ptr(ptr) { if (ptr) ptr->incRef(); } + + __forceinline ~Ref() { if (ptr) ptr->decRef(); } + + __forceinline Ref& operator =(const Ref& other) + { + if (other.ptr) + other.ptr->incRef(); + if (ptr) + ptr->decRef(); + ptr = other.ptr; + return *this; + } + + __forceinline Ref& operator =(Ref&& other) + { + if (ptr) + ptr->decRef(); + ptr = other.ptr; + other.ptr = nullptr; + return *this; + } + + __forceinline Ref& operator =(T* other) + { + if (other) + other->incRef(); + if (ptr) + ptr->decRef(); + ptr = other; + return *this; + } + + __forceinline Ref& operator =(std::nullptr_t) + { + if (ptr) + ptr->decRef(); + ptr = nullptr; + return *this; + } + + __forceinline operator bool() const noexcept { return ptr != nullptr; } + + __forceinline T& operator *() const noexcept { return *ptr; } + __forceinline T* operator ->() const noexcept { return ptr; } + + __forceinline T* get() const noexcept { return ptr; } + + __forceinline T* detach() noexcept + { + T* res = ptr; + ptr = nullptr; + return res; + } + }; + + template __forceinline bool operator < (const Ref& a, const Ref& b) noexcept { return a.ptr < b.ptr; } + + template __forceinline bool operator ==(const Ref& a, std::nullptr_t) noexcept { return a.ptr == nullptr; } + template __forceinline bool operator ==(std::nullptr_t, const Ref& b) noexcept { return nullptr == b.ptr; } + template __forceinline bool operator ==(const Ref& a, const Ref& b) noexcept { return a.ptr == b.ptr; } + + template __forceinline bool operator !=(const Ref& a, std::nullptr_t) noexcept { return a.ptr != nullptr; } + template __forceinline bool operator !=(std::nullptr_t, const Ref& b) noexcept { return nullptr != b.ptr; } + template __forceinline bool operator !=(const Ref& a, const Ref& b) noexcept { return a.ptr != b.ptr; } + + template + __forceinline Ref makeRef(Args&&... args) + { + return Ref(new T(std::forward(args)...)); + } + + template + __forceinline Ref staticRefCast(const Ref& a) + { + return Ref(static_cast(a.get())); + } + + template + __forceinline Ref dynamicRefCast(const Ref& a) + { + return Ref(dynamic_cast(a.get())); + } + +} // namespace oidn diff --git a/thirdparty/oidn/common/tensor.cpp b/thirdparty/oidn/common/tensor.cpp new file mode 100644 index 000000000000..0249f2e14193 --- /dev/null +++ b/thirdparty/oidn/common/tensor.cpp @@ -0,0 +1,83 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "exception.h" +#include "tensor.h" + +namespace oidn { + + std::map parseTensors(void* buffer) + { + char* input = (char*)buffer; + + // Parse the magic value + const int magic = *(unsigned short*)input; + if (magic != 0x41D7) + throw Exception(Error::InvalidOperation, "invalid tensor archive"); + input += sizeof(unsigned short); + + // Parse the version + const int majorVersion = *(unsigned char*)input++; + const int minorVersion = *(unsigned char*)input++; + UNUSED(minorVersion); + if (majorVersion > 1) + throw Exception(Error::InvalidOperation, "unsupported tensor archive version"); + + // Parse the number of tensors + const int numTensors = *(int*)input; + input += sizeof(int); + + // Parse the tensors + std::map tensorMap; + for (int i = 0; i < numTensors; ++i) + { + Tensor tensor; + + // Parse the name + const int nameLen = *(unsigned char*)input++; + std::string name(input, nameLen); + input += nameLen; + + // Parse the number of dimensions + const int ndims = *(unsigned char*)input++; + + // Parse the shape of the tensor + tensor.dims.resize(ndims); + for (int i = 0; i < ndims; ++i) + tensor.dims[i] = ((int*)input)[i]; + input += ndims * sizeof(int); + + // Parse the format of the tensor + tensor.format = std::string(input, input + ndims); + input += ndims; + + // Parse the data type of the tensor + const char type = *(unsigned char*)input++; + if (type != 'f') // only float32 is supported + throw Exception(Error::InvalidOperation, "unsupported tensor data type"); + + // Skip the data + tensor.data = (float*)input; + input += tensor.size() * sizeof(float); + + // Add the tensor to the map + tensorMap.emplace(name, std::move(tensor)); + } + + return tensorMap; + } + +} // namespace oidn diff --git a/thirdparty/oidn/common/tensor.h b/thirdparty/oidn/common/tensor.h new file mode 100644 index 000000000000..48e7d1123d10 --- /dev/null +++ b/thirdparty/oidn/common/tensor.h @@ -0,0 +1,66 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "platform.h" +#include +#include + +namespace oidn { + + template + using shared_vector = std::shared_ptr>; + + // Generic tensor + struct Tensor + { + float* data; + std::vector dims; + std::string format; + shared_vector buffer; // optional, only for reference counting + + __forceinline Tensor() : data(nullptr) {} + + __forceinline Tensor(const std::vector& dims, const std::string& format) + : dims(dims), + format(format) + { + buffer = std::make_shared>(size() * sizeof(float)); + data = (float*)buffer->data(); + } + + __forceinline operator bool() const { return data != nullptr; } + + __forceinline int ndims() const { return (int)dims.size(); } + + // Returns the number of values + __forceinline size_t size() const + { + size_t size = 1; + for (int i = 0; i < ndims(); ++i) + size *= dims[i]; + return size; + } + + __forceinline float& operator [](size_t i) { return data[i]; } + __forceinline const float& operator [](size_t i) const { return data[i]; } + }; + + // Parses tensors from a buffer + std::map parseTensors(void* buffer); + +} // namespace oidn diff --git a/thirdparty/oidn/common/thread.cpp b/thirdparty/oidn/common/thread.cpp new file mode 100644 index 000000000000..48c489c57b61 --- /dev/null +++ b/thirdparty/oidn/common/thread.cpp @@ -0,0 +1,297 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#if defined(_MSC_VER) + #pragma warning (disable : 4146) // unary minus operator applied to unsigned type, result still unsigned +#endif + +#if defined(__APPLE__) + #include + #include +#endif + +#include "thread.h" +#include + +namespace oidn { + +#if defined(_WIN32) + + // -------------------------------------------------------------------------- + // ThreadAffinity - Windows + // -------------------------------------------------------------------------- + + ThreadAffinity::ThreadAffinity(int numThreadsPerCore, int verbose) + : Verbose(verbose) + { + HMODULE hLib = GetModuleHandle(TEXT("kernel32")); + pGetLogicalProcessorInformationEx = (GetLogicalProcessorInformationExFunc)GetProcAddress(hLib, "GetLogicalProcessorInformationEx"); + pSetThreadGroupAffinity = (SetThreadGroupAffinityFunc)GetProcAddress(hLib, "SetThreadGroupAffinity"); + + if (pGetLogicalProcessorInformationEx && pSetThreadGroupAffinity) + { + // Get logical processor information + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer = nullptr; + DWORD bufferSize = 0; + + // First call the function with an empty buffer to get the required buffer size + BOOL result = pGetLogicalProcessorInformationEx(RelationProcessorCore, buffer, &bufferSize); + if (result || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + OIDN_WARNING("GetLogicalProcessorInformationEx failed"); + return; + } + + // Allocate the buffer + buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)malloc(bufferSize); + if (!buffer) + { + OIDN_WARNING("SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX allocation failed"); + return; + } + + // Call again the function but now with the properly sized buffer + result = pGetLogicalProcessorInformationEx(RelationProcessorCore, buffer, &bufferSize); + if (!result) + { + OIDN_WARNING("GetLogicalProcessorInformationEx failed"); + free(buffer); + return; + } + + // Iterate over the logical processor information structures + // There should be one structure for each physical core + char* ptr = (char*)buffer; + while (ptr < (char*)buffer + bufferSize) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX item = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)ptr; + if (item->Relationship == RelationProcessorCore && item->Processor.GroupCount > 0) + { + // Iterate over the groups + int numThreads = 0; + for (int group = 0; (group < item->Processor.GroupCount) && (numThreads < numThreadsPerCore); ++group) + { + GROUP_AFFINITY coreAffinity = item->Processor.GroupMask[group]; + while ((coreAffinity.Mask != 0) && (numThreads < numThreadsPerCore)) + { + // Extract the next set bit/thread from the mask + GROUP_AFFINITY threadAffinity = coreAffinity; + threadAffinity.Mask = threadAffinity.Mask & -threadAffinity.Mask; + + // Push the affinity for this thread + affinities.push_back(threadAffinity); + oldAffinities.push_back(threadAffinity); + numThreads++; + + // Remove this bit/thread from the mask + coreAffinity.Mask ^= threadAffinity.Mask; + } + } + } + + // Next structure + ptr += item->Size; + } + + // Free the buffer + free(buffer); + } + } + + void ThreadAffinity::set(int threadIndex) + { + if (threadIndex >= (int)affinities.size()) + return; + + // Save the current affinity and set the new one + const HANDLE thread = GetCurrentThread(); + if (!pSetThreadGroupAffinity(thread, &affinities[threadIndex], &oldAffinities[threadIndex])) + OIDN_WARNING("SetThreadGroupAffinity failed"); + } + + void ThreadAffinity::restore(int threadIndex) + { + if (threadIndex >= (int)affinities.size()) + return; + + // Restore the original affinity + const HANDLE thread = GetCurrentThread(); + if (!pSetThreadGroupAffinity(thread, &oldAffinities[threadIndex], nullptr)) + OIDN_WARNING("SetThreadGroupAffinity failed"); + } + +#elif defined(__linux__) + + // -------------------------------------------------------------------------- + // ThreadAffinity - Linux + // -------------------------------------------------------------------------- + + ThreadAffinity::ThreadAffinity(int numThreadsPerCore, int verbose) + : Verbose(verbose) + { + std::vector threadIds; + + // Parse the thread/CPU topology + for (int cpuId = 0; ; cpuId++) + { + std::fstream fs; + std::string cpu = std::string("/sys/devices/system/cpu/cpu") + std::to_string(cpuId) + std::string("/topology/thread_siblings_list"); + fs.open(cpu.c_str(), std::fstream::in); + if (fs.fail()) break; + + int i; + int j = 0; + while ((j < numThreadsPerCore) && (fs >> i)) + { + if (std::none_of(threadIds.begin(), threadIds.end(), [&](int id) { return id == i; })) + threadIds.push_back(i); + + if (fs.peek() == ',') + fs.ignore(); + j++; + } + + fs.close(); + } + + #if 0 + for (size_t i = 0; i < thread_ids.size(); ++i) + std::cout << "thread " << i << " -> " << thread_ids[i] << std::endl; + #endif + + // Create the affinity structures + affinities.resize(threadIds.size()); + oldAffinities.resize(threadIds.size()); + + for (size_t i = 0; i < threadIds.size(); ++i) + { + cpu_set_t affinity; + CPU_ZERO(&affinity); + CPU_SET(threadIds[i], &affinity); + + affinities[i] = affinity; + oldAffinities[i] = affinity; + } + } + + void ThreadAffinity::set(int threadIndex) + { + if (threadIndex >= (int)affinities.size()) + return; + + const pthread_t thread = pthread_self(); + + // Save the current affinity + if (pthread_getaffinity_np(thread, sizeof(cpu_set_t), &oldAffinities[threadIndex]) != 0) + { + OIDN_WARNING("pthread_getaffinity_np failed"); + oldAffinities[threadIndex] = affinities[threadIndex]; + return; + } + + // Set the new affinity + if (pthread_setaffinity_np(thread, sizeof(cpu_set_t), &affinities[threadIndex]) != 0) + OIDN_WARNING("pthread_setaffinity_np failed"); + } + + void ThreadAffinity::restore(int threadIndex) + { + if (threadIndex >= (int)affinities.size()) + return; + + const pthread_t thread = pthread_self(); + + // Restore the original affinity + if (pthread_setaffinity_np(thread, sizeof(cpu_set_t), &oldAffinities[threadIndex]) != 0) + OIDN_WARNING("pthread_setaffinity_np failed"); + } + +#elif defined(__APPLE__) + + // -------------------------------------------------------------------------- + // ThreadAffinity - macOS + // -------------------------------------------------------------------------- + + ThreadAffinity::ThreadAffinity(int numThreadsPerCore, int verbose) + : Verbose(verbose) + { + // Query the thread/CPU topology + int numPhysicalCpus; + int numLogicalCpus; + + if (!getSysctl("hw.physicalcpu", numPhysicalCpus) || !getSysctl("hw.logicalcpu", numLogicalCpus)) + { + OIDN_WARNING("sysctlbyname failed"); + return; + } + + if ((numLogicalCpus % numPhysicalCpus != 0) && (numThreadsPerCore > 1)) + return; // this shouldn't happen + const int maxThreadsPerCore = numLogicalCpus / numPhysicalCpus; + + // Create the affinity structures + // macOS doesn't support binding a thread to a specific core, but we can at least group threads which + // should be on the same core together + for (int core = 1; core <= numPhysicalCpus; ++core) // tags start from 1! + { + thread_affinity_policy affinity; + affinity.affinity_tag = core; + + for (int thread = 0; thread < min(numThreadsPerCore, maxThreadsPerCore); ++thread) + { + affinities.push_back(affinity); + oldAffinities.push_back(affinity); + } + } + } + + void ThreadAffinity::set(int threadIndex) + { + if (threadIndex >= (int)affinities.size()) + return; + + const auto thread = mach_thread_self(); + + // Save the current affinity + mach_msg_type_number_t policyCount = THREAD_AFFINITY_POLICY_COUNT; + boolean_t getDefault = FALSE; + if (thread_policy_get(thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&oldAffinities[threadIndex], &policyCount, &getDefault) != KERN_SUCCESS) + { + OIDN_WARNING("thread_policy_get failed"); + oldAffinities[threadIndex] = affinities[threadIndex]; + return; + } + + // Set the new affinity + if (thread_policy_set(thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&affinities[threadIndex], THREAD_AFFINITY_POLICY_COUNT) != KERN_SUCCESS) + OIDN_WARNING("thread_policy_set failed"); + } + + void ThreadAffinity::restore(int threadIndex) + { + if (threadIndex >= (int)affinities.size()) + return; + + const auto thread = mach_thread_self(); + + // Restore the original affinity + if (thread_policy_set(thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&oldAffinities[threadIndex], THREAD_AFFINITY_POLICY_COUNT) != KERN_SUCCESS) + OIDN_WARNING("thread_policy_set failed"); + } + +#endif + +} // namespace oidn diff --git a/thirdparty/oidn/common/thread.h b/thirdparty/oidn/common/thread.h new file mode 100644 index 000000000000..2c731367da3b --- /dev/null +++ b/thirdparty/oidn/common/thread.h @@ -0,0 +1,202 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "platform.h" + +#if !defined(_WIN32) + #include + #include + #if defined(__APPLE__) + #include + #endif +#endif + +#include +#include + +namespace oidn { + + // -------------------------------------------------------------------------- + // ThreadLocal + // -------------------------------------------------------------------------- + + // Wrapper which makes any variable thread-local + template + class ThreadLocal : public Verbose + { + private: + #if defined(_WIN32) + DWORD key; + #else + pthread_key_t key; + #endif + + std::vector instances; + std::mutex mutex; + + public: + ThreadLocal(int verbose = 0) + : Verbose(verbose) + { + #if defined(_WIN32) + key = TlsAlloc(); + if (key == TLS_OUT_OF_INDEXES) + OIDN_FATAL("TlsAlloc failed"); + #else + if (pthread_key_create(&key, nullptr) != 0) + OIDN_FATAL("pthread_key_create failed"); + #endif + } + + ~ThreadLocal() + { + std::lock_guard lock(mutex); + for (T* ptr : instances) + delete ptr; + + #if defined(_WIN32) + if (!TlsFree(key)) + OIDN_WARNING("TlsFree failed"); + #else + if (pthread_key_delete(key) != 0) + OIDN_WARNING("pthread_key_delete failed"); + #endif + } + + T& get() + { + #if defined(_WIN32) + T* ptr = (T*)TlsGetValue(key); + #else + T* ptr = (T*)pthread_getspecific(key); + #endif + + if (ptr) + return *ptr; + + ptr = new T; + std::lock_guard lock(mutex); + instances.push_back(ptr); + + #if defined(_WIN32) + if (!TlsSetValue(key, ptr)) + OIDN_FATAL("TlsSetValue failed"); + #else + if (pthread_setspecific(key, ptr) != 0) + OIDN_FATAL("pthread_setspecific failed"); + #endif + + return *ptr; + } + }; + +#if defined(_WIN32) + + // -------------------------------------------------------------------------- + // ThreadAffinity - Windows + // -------------------------------------------------------------------------- + + class ThreadAffinity : public Verbose + { + private: + typedef BOOL (WINAPI *GetLogicalProcessorInformationExFunc)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, + PDWORD); + + typedef BOOL (WINAPI *SetThreadGroupAffinityFunc)(HANDLE, + CONST GROUP_AFFINITY*, + PGROUP_AFFINITY); + + GetLogicalProcessorInformationExFunc pGetLogicalProcessorInformationEx = nullptr; + SetThreadGroupAffinityFunc pSetThreadGroupAffinity = nullptr; + + std::vector affinities; // thread affinities + std::vector oldAffinities; // original thread affinities + + public: + ThreadAffinity(int numThreadsPerCore = INT_MAX, int verbose = 0); + + int getNumThreads() const + { + return (int)affinities.size(); + } + + // Sets the affinity (0..numThreads-1) of the thread after saving the current affinity + void set(int threadIndex); + + // Restores the affinity of the thread + void restore(int threadIndex); + }; + +#elif defined(__linux__) + + // -------------------------------------------------------------------------- + // ThreadAffinity - Linux + // -------------------------------------------------------------------------- + + class ThreadAffinity : public Verbose + { + private: + std::vector affinities; // thread affinities + std::vector oldAffinities; // original thread affinities + + public: + ThreadAffinity(int numThreadsPerCore = INT_MAX, int verbose = 0); + + int getNumThreads() const + { + return (int)affinities.size(); + } + + // Sets the affinity (0..numThreads-1) of the thread after saving the current affinity + void set(int threadIndex); + + // Restores the affinity of the thread + void restore(int threadIndex); + }; + +#elif defined(__APPLE__) + + // -------------------------------------------------------------------------- + // ThreadAffinity - macOS + // -------------------------------------------------------------------------- + + class ThreadAffinity : public Verbose + { + private: + std::vector affinities; // thread affinities + std::vector oldAffinities; // original thread affinities + + public: + ThreadAffinity(int numThreadsPerCore = INT_MAX, int verbose = 0); + + int getNumThreads() const + { + return (int)affinities.size(); + } + + // Sets the affinity (0..numThreads-1) of the thread after saving the current affinity + void set(int threadIndex); + + // Restores the affinity of the thread + void restore(int threadIndex); + }; + +#endif + +} // namespace oidn diff --git a/thirdparty/oidn/common/timer.h b/thirdparty/oidn/common/timer.h new file mode 100644 index 000000000000..62aaaa1c33fc --- /dev/null +++ b/thirdparty/oidn/common/timer.h @@ -0,0 +1,49 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "platform.h" +#include + +namespace oidn { + + class Timer + { + private: + using clock = std::chrono::high_resolution_clock; + + std::chrono::time_point start; + + public: + Timer() + { + reset(); + } + + void reset() + { + start = clock::now(); + } + + double query() const + { + auto end = clock::now(); + return std::chrono::duration_cast>(end - start).count(); + } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/api.cpp b/thirdparty/oidn/core/api.cpp new file mode 100644 index 000000000000..7353fe4e2528 --- /dev/null +++ b/thirdparty/oidn/core/api.cpp @@ -0,0 +1,408 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifdef _WIN32 +# define OIDN_API extern "C" __declspec(dllexport) +#else +# define OIDN_API extern "C" __attribute__ ((visibility ("default"))) +#endif + +// Locks the device that owns the specified object +// Use *only* inside OIDN_TRY/CATCH! +#define OIDN_LOCK(obj) \ + std::lock_guard lock(obj->getDevice()->getMutex()); + +// Try/catch for converting exceptions to errors +#define OIDN_TRY \ + try { + +#define OIDN_CATCH(obj) \ + } catch (Exception& e) { \ + Device::setError(obj ? obj->getDevice() : nullptr, e.code(), e.what()); \ + } catch (std::bad_alloc&) { \ + Device::setError(obj ? obj->getDevice() : nullptr, Error::OutOfMemory, "out of memory"); \ + } catch (mkldnn::error& e) { \ + if (e.status == mkldnn_out_of_memory) \ + Device::setError(obj ? obj->getDevice() : nullptr, Error::OutOfMemory, "out of memory"); \ + else \ + Device::setError(obj ? obj->getDevice() : nullptr, Error::Unknown, e.message); \ + } catch (std::exception& e) { \ + Device::setError(obj ? obj->getDevice() : nullptr, Error::Unknown, e.what()); \ + } catch (...) { \ + Device::setError(obj ? obj->getDevice() : nullptr, Error::Unknown, "unknown exception caught"); \ + } + +#include "device.h" +#include "filter.h" +#include + +namespace oidn { + + namespace + { + __forceinline void checkHandle(void* handle) + { + if (handle == nullptr) + throw Exception(Error::InvalidArgument, "invalid handle"); + } + + template + __forceinline void retainObject(T* obj) + { + if (obj) + { + obj->incRef(); + } + else + { + OIDN_TRY + checkHandle(obj); + OIDN_CATCH(obj) + } + } + + template + __forceinline void releaseObject(T* obj) + { + if (obj == nullptr || obj->decRefKeep() == 0) + { + OIDN_TRY + checkHandle(obj); + OIDN_LOCK(obj); + obj->destroy(); + OIDN_CATCH(obj) + } + } + + template<> + __forceinline void releaseObject(Device* obj) + { + if (obj == nullptr || obj->decRefKeep() == 0) + { + OIDN_TRY + checkHandle(obj); + // Do NOT lock the device because it owns the mutex + obj->destroy(); + OIDN_CATCH(obj) + } + } + } + + OIDN_API OIDNDevice oidnNewDevice(OIDNDeviceType type) + { + Ref device = nullptr; + OIDN_TRY + if (type == OIDN_DEVICE_TYPE_CPU || type == OIDN_DEVICE_TYPE_DEFAULT) + device = makeRef(); + else + throw Exception(Error::InvalidArgument, "invalid device type"); + OIDN_CATCH(device) + return (OIDNDevice)device.detach(); + } + + OIDN_API void oidnRetainDevice(OIDNDevice hDevice) + { + Device* device = (Device*)hDevice; + retainObject(device); + } + + OIDN_API void oidnReleaseDevice(OIDNDevice hDevice) + { + Device* device = (Device*)hDevice; + releaseObject(device); + } + + OIDN_API void oidnSetDevice1b(OIDNDevice hDevice, const char* name, bool value) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + device->set1i(name, value); + OIDN_CATCH(device) + } + + OIDN_API void oidnSetDevice1i(OIDNDevice hDevice, const char* name, int value) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + device->set1i(name, value); + OIDN_CATCH(device) + } + + OIDN_API bool oidnGetDevice1b(OIDNDevice hDevice, const char* name) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + return device->get1i(name); + OIDN_CATCH(device) + return false; + } + + OIDN_API int oidnGetDevice1i(OIDNDevice hDevice, const char* name) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + return device->get1i(name); + OIDN_CATCH(device) + return 0; + } + + OIDN_API void oidnSetDeviceErrorFunction(OIDNDevice hDevice, OIDNErrorFunction func, void* userPtr) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + device->setErrorFunction((ErrorFunction)func, userPtr); + OIDN_CATCH(device) + } + + OIDN_API OIDNError oidnGetDeviceError(OIDNDevice hDevice, const char** outMessage) + { + Device* device = (Device*)hDevice; + OIDN_TRY + return (OIDNError)Device::getError(device, outMessage); + OIDN_CATCH(device) + if (outMessage) *outMessage = ""; + return OIDN_ERROR_UNKNOWN; + } + + OIDN_API void oidnCommitDevice(OIDNDevice hDevice) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + device->commit(); + OIDN_CATCH(device) + } + + OIDN_API OIDNBuffer oidnNewBuffer(OIDNDevice hDevice, size_t byteSize) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + Ref buffer = device->newBuffer(byteSize); + return (OIDNBuffer)buffer.detach(); + OIDN_CATCH(device) + return nullptr; + } + + OIDN_API OIDNBuffer oidnNewSharedBuffer(OIDNDevice hDevice, void* ptr, size_t byteSize) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + Ref buffer = device->newBuffer(ptr, byteSize); + return (OIDNBuffer)buffer.detach(); + OIDN_CATCH(device) + return nullptr; + } + + OIDN_API void oidnRetainBuffer(OIDNBuffer hBuffer) + { + Buffer* buffer = (Buffer*)hBuffer; + retainObject(buffer); + } + + OIDN_API void oidnReleaseBuffer(OIDNBuffer hBuffer) + { + Buffer* buffer = (Buffer*)hBuffer; + releaseObject(buffer); + } + + OIDN_API void* oidnMapBuffer(OIDNBuffer hBuffer, OIDNAccess access, size_t byteOffset, size_t byteSize) + { + Buffer* buffer = (Buffer*)hBuffer; + OIDN_TRY + checkHandle(hBuffer); + OIDN_LOCK(buffer); + return buffer->map(byteOffset, byteSize); + OIDN_CATCH(buffer) + return nullptr; + } + + OIDN_API void oidnUnmapBuffer(OIDNBuffer hBuffer, void* mappedPtr) + { + Buffer* buffer = (Buffer*)hBuffer; + OIDN_TRY + checkHandle(hBuffer); + OIDN_LOCK(buffer); + return buffer->unmap(mappedPtr); + OIDN_CATCH(buffer) + } + + OIDN_API OIDNFilter oidnNewFilter(OIDNDevice hDevice, const char* type) + { + Device* device = (Device*)hDevice; + OIDN_TRY + checkHandle(hDevice); + OIDN_LOCK(device); + Ref filter = device->newFilter(type); + return (OIDNFilter)filter.detach(); + OIDN_CATCH(device) + return nullptr; + } + + OIDN_API void oidnRetainFilter(OIDNFilter hFilter) + { + Filter* filter = (Filter*)hFilter; + retainObject(filter); + } + + OIDN_API void oidnReleaseFilter(OIDNFilter hFilter) + { + Filter* filter = (Filter*)hFilter; + releaseObject(filter); + } + + OIDN_API void oidnSetFilterImage(OIDNFilter hFilter, const char* name, + OIDNBuffer hBuffer, OIDNFormat format, + size_t width, size_t height, + size_t byteOffset, + size_t bytePixelStride, size_t byteRowStride) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + checkHandle(hBuffer); + OIDN_LOCK(filter); + Ref buffer = (Buffer*)hBuffer; + if (buffer->getDevice() != filter->getDevice()) + throw Exception(Error::InvalidArgument, "the specified objects are bound to different devices"); + Image data(buffer, (Format)format, (int)width, (int)height, byteOffset, bytePixelStride, byteRowStride); + filter->setImage(name, data); + OIDN_CATCH(filter) + } + + OIDN_API void oidnSetSharedFilterImage(OIDNFilter hFilter, const char* name, + void* ptr, OIDNFormat format, + size_t width, size_t height, + size_t byteOffset, + size_t bytePixelStride, size_t byteRowStride) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + Image data(ptr, (Format)format, (int)width, (int)height, byteOffset, bytePixelStride, byteRowStride); + filter->setImage(name, data); + OIDN_CATCH(filter) + } + + OIDN_API void oidnSetFilter1b(OIDNFilter hFilter, const char* name, bool value) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + filter->set1i(name, int(value)); + OIDN_CATCH(filter) + } + + OIDN_API bool oidnGetFilter1b(OIDNFilter hFilter, const char* name) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + return filter->get1i(name); + OIDN_CATCH(filter) + return false; + } + + OIDN_API void oidnSetFilter1i(OIDNFilter hFilter, const char* name, int value) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + filter->set1i(name, value); + OIDN_CATCH(filter) + } + + OIDN_API int oidnGetFilter1i(OIDNFilter hFilter, const char* name) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + return filter->get1i(name); + OIDN_CATCH(filter) + return 0; + } + + OIDN_API void oidnSetFilter1f(OIDNFilter hFilter, const char* name, float value) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + filter->set1f(name, value); + OIDN_CATCH(filter) + } + + OIDN_API float oidnGetFilter1f(OIDNFilter hFilter, const char* name) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + return filter->get1f(name); + OIDN_CATCH(filter) + return 0; + } + + OIDN_API void oidnSetFilterProgressMonitorFunction(OIDNFilter hFilter, OIDNProgressMonitorFunction func, void* userPtr) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + filter->setProgressMonitorFunction(func, userPtr); + OIDN_CATCH(filter) + } + + OIDN_API void oidnCommitFilter(OIDNFilter hFilter) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + filter->commit(); + OIDN_CATCH(filter) + } + + OIDN_API void oidnExecuteFilter(OIDNFilter hFilter) + { + Filter* filter = (Filter*)hFilter; + OIDN_TRY + checkHandle(hFilter); + OIDN_LOCK(filter); + filter->execute(); + OIDN_CATCH(filter) + } + +} // namespace oidn diff --git a/thirdparty/oidn/core/autoencoder.cpp b/thirdparty/oidn/core/autoencoder.cpp new file mode 100644 index 000000000000..d8da684cb894 --- /dev/null +++ b/thirdparty/oidn/core/autoencoder.cpp @@ -0,0 +1,535 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "autoencoder.h" + +namespace oidn { + + // -------------------------------------------------------------------------- + // AutoencoderFilter + // -------------------------------------------------------------------------- + + AutoencoderFilter::AutoencoderFilter(const Ref& device) + : Filter(device) + { + } + + void AutoencoderFilter::setImage(const std::string& name, const Image& data) + { + if (name == "color") + color = data; + else if (name == "albedo") + albedo = data; + else if (name == "normal") + normal = data; + else if (name == "output") + output = data; + + dirty = true; + } + + void AutoencoderFilter::set1i(const std::string& name, int value) + { + if (name == "hdr") + hdr = value; + else if (name == "srgb") + srgb = value; + else if (name == "maxMemoryMB") + maxMemoryMB = value; + + dirty = true; + } + + int AutoencoderFilter::get1i(const std::string& name) + { + if (name == "hdr") + return hdr; + else if (name == "srgb") + return srgb; + else if (name == "maxMemoryMB") + return maxMemoryMB; + else if (name == "alignment") + return alignment; + else if (name == "overlap") + return overlap; + else + throw Exception(Error::InvalidArgument, "invalid parameter"); + } + + void AutoencoderFilter::set1f(const std::string& name, float value) + { + if (name == "hdrScale") + hdrScale = value; + + dirty = true; + } + + float AutoencoderFilter::get1f(const std::string& name) + { + if (name == "hdrScale") + return hdrScale; + else + throw Exception(Error::InvalidArgument, "invalid parameter"); + } + + void AutoencoderFilter::commit() + { + if (!dirty) + return; + + // -- GODOT start -- + //device->executeTask([&]() + //{ + // GODOT end -- + + if (mayiuse(avx512_common)) + net = buildNet<16>(); + else + net = buildNet<8>(); + + // GODOT start -- + //}); + // GODOT end -- + + dirty = false; + } + + void AutoencoderFilter::execute() + { + if (dirty) + throw Exception(Error::InvalidOperation, "changes to the filter are not committed"); + + if (!net) + return; + // -- GODOT start -- + //device->executeTask([&]() + //{ + // -- GODOT end -- + Progress progress; + progress.func = progressFunc; + progress.userPtr = progressUserPtr; + progress.taskCount = tileCountH * tileCountW; + + // Iterate over the tiles + int tileIndex = 0; + + for (int i = 0; i < tileCountH; ++i) + { + const int h = i * (tileH - 2*overlap); // input tile position (including overlap) + const int overlapBeginH = i > 0 ? overlap : 0; // overlap on the top + const int overlapEndH = i < tileCountH-1 ? overlap : 0; // overlap on the bottom + const int tileH1 = min(H - h, tileH); // input tile size (including overlap) + const int tileH2 = tileH1 - overlapBeginH - overlapEndH; // output tile size + const int alignOffsetH = tileH - roundUp(tileH1, alignment); // align to the bottom in the tile buffer + + for (int j = 0; j < tileCountW; ++j) + { + const int w = j * (tileW - 2*overlap); // input tile position (including overlap) + const int overlapBeginW = j > 0 ? overlap : 0; // overlap on the left + const int overlapEndW = j < tileCountW-1 ? overlap : 0; // overlap on the right + const int tileW1 = min(W - w, tileW); // input tile size (including overlap) + const int tileW2 = tileW1 - overlapBeginW - overlapEndW; // output tile size + const int alignOffsetW = tileW - roundUp(tileW1, alignment); // align to the right in the tile buffer + + // Set the input tile + inputReorder->setTile(h, w, + alignOffsetH, alignOffsetW, + tileH1, tileW1); + + // Set the output tile + outputReorder->setTile(alignOffsetH + overlapBeginH, alignOffsetW + overlapBeginW, + h + overlapBeginH, w + overlapBeginW, + tileH2, tileW2); + + //printf("Tile: %d %d -> %d %d\n", w+overlapBeginW, h+overlapBeginH, w+overlapBeginW+tileW2, h+overlapBeginH+tileH2); + + // Denoise the tile + net->execute(progress, tileIndex); + + // Next tile + tileIndex++; + } + } + // -- GODOT start -- + //}); + // -- GODOT end -- + } + + void AutoencoderFilter::computeTileSize() + { + const int minTileSize = 3*overlap; + const int estimatedBytesPerPixel = mayiuse(avx512_common) ? estimatedBytesPerPixel16 : estimatedBytesPerPixel8; + const int64_t maxTilePixels = (int64_t(maxMemoryMB)*1024*1024 - estimatedBytesBase) / estimatedBytesPerPixel; + + tileCountH = 1; + tileCountW = 1; + tileH = roundUp(H, alignment); + tileW = roundUp(W, alignment); + + // Divide the image into tiles until the tile size gets below the threshold + while (int64_t(tileH) * tileW > maxTilePixels) + { + if (tileH > minTileSize && tileH > tileW) + { + tileCountH++; + tileH = max(roundUp(ceilDiv(H - 2*overlap, tileCountH), alignment) + 2*overlap, minTileSize); + } + else if (tileW > minTileSize) + { + tileCountW++; + tileW = max(roundUp(ceilDiv(W - 2*overlap, tileCountW), alignment) + 2*overlap, minTileSize); + } + else + break; + } + + // Compute the final number of tiles + tileCountH = (H > tileH) ? ceilDiv(H - 2*overlap, tileH - 2*overlap) : 1; + tileCountW = (W > tileW) ? ceilDiv(W - 2*overlap, tileW - 2*overlap) : 1; + + if (device->isVerbose(2)) + { + std::cout << "Tile size : " << tileW << "x" << tileH << std::endl; + std::cout << "Tile count: " << tileCountW << "x" << tileCountH << std::endl; + } + } + + template + std::shared_ptr AutoencoderFilter::buildNet() + { + H = color.height; + W = color.width; + + // Configure the network + int inputC; + void* weightPtr; + + if (srgb && hdr) + throw Exception(Error::InvalidOperation, "srgb and hdr modes cannot be enabled at the same time"); + + if (color && !albedo && !normal && weightData.hdr) + { + inputC = 3; + weightPtr = hdr ? weightData.hdr : weightData.ldr; + } + else if (color && albedo && !normal && weightData.hdr_alb) + { + inputC = 6; + weightPtr = hdr ? weightData.hdr_alb : weightData.ldr_alb; + } + else if (color && albedo && normal && weightData.hdr_alb_nrm) + { + inputC = 9; + weightPtr = hdr ? weightData.hdr_alb_nrm : weightData.ldr_alb_nrm; + } + else + { + throw Exception(Error::InvalidOperation, "unsupported combination of input features"); + } + + if (!output) + throw Exception(Error::InvalidOperation, "output image not specified"); + + if ((color.format != Format::Float3) + || (albedo && albedo.format != Format::Float3) + || (normal && normal.format != Format::Float3) + || (output.format != Format::Float3)) + throw Exception(Error::InvalidOperation, "unsupported image format"); + + if ((albedo && (albedo.width != W || albedo.height != H)) + || (normal && (normal.width != W || normal.height != H)) + || (output.width != W || output.height != H)) + throw Exception(Error::InvalidOperation, "image size mismatch"); + + // Compute the tile size + computeTileSize(); + + // If the image size is zero, there is nothing else to do + if (H <= 0 || W <= 0) + return nullptr; + + // Parse the weights + const auto weightMap = parseTensors(weightPtr); + + // Create the network + std::shared_ptr> net = std::make_shared>(device, weightMap); + + // Compute the tensor sizes + const auto inputDims = memory::dims({1, inputC, tileH, tileW}); + const auto inputReorderDims = net->getInputReorderDims(inputDims, alignment); //-> concat0 + + const auto conv1Dims = net->getConvDims("conv1", inputReorderDims); //-> temp0 + const auto conv1bDims = net->getConvDims("conv1b", conv1Dims); //-> temp1 + const auto pool1Dims = net->getPoolDims(conv1bDims); //-> concat1 + const auto conv2Dims = net->getConvDims("conv2", pool1Dims); //-> temp0 + const auto pool2Dims = net->getPoolDims(conv2Dims); //-> concat2 + const auto conv3Dims = net->getConvDims("conv3", pool2Dims); //-> temp0 + const auto pool3Dims = net->getPoolDims(conv3Dims); //-> concat3 + const auto conv4Dims = net->getConvDims("conv4", pool3Dims); //-> temp0 + const auto pool4Dims = net->getPoolDims(conv4Dims); //-> concat4 + const auto conv5Dims = net->getConvDims("conv5", pool4Dims); //-> temp0 + const auto pool5Dims = net->getPoolDims(conv5Dims); //-> temp1 + const auto upsample4Dims = net->getUpsampleDims(pool5Dims); //-> concat4 + const auto concat4Dims = net->getConcatDims(upsample4Dims, pool4Dims); + const auto conv6Dims = net->getConvDims("conv6", concat4Dims); //-> temp0 + const auto conv6bDims = net->getConvDims("conv6b", conv6Dims); //-> temp1 + const auto upsample3Dims = net->getUpsampleDims(conv6bDims); //-> concat3 + const auto concat3Dims = net->getConcatDims(upsample3Dims, pool3Dims); + const auto conv7Dims = net->getConvDims("conv7", concat3Dims); //-> temp0 + const auto conv7bDims = net->getConvDims("conv7b", conv7Dims); //-> temp1 + const auto upsample2Dims = net->getUpsampleDims(conv7bDims); //-> concat2 + const auto concat2Dims = net->getConcatDims(upsample2Dims, pool2Dims); + const auto conv8Dims = net->getConvDims("conv8", concat2Dims); //-> temp0 + const auto conv8bDims = net->getConvDims("conv8b", conv8Dims); //-> temp1 + const auto upsample1Dims = net->getUpsampleDims(conv8bDims); //-> concat1 + const auto concat1Dims = net->getConcatDims(upsample1Dims, pool1Dims); + const auto conv9Dims = net->getConvDims("conv9", concat1Dims); //-> temp0 + const auto conv9bDims = net->getConvDims("conv9b", conv9Dims); //-> temp1 + const auto upsample0Dims = net->getUpsampleDims(conv9bDims); //-> concat0 + const auto concat0Dims = net->getConcatDims(upsample0Dims, inputReorderDims); + const auto conv10Dims = net->getConvDims("conv10", concat0Dims); //-> temp0 + const auto conv10bDims = net->getConvDims("conv10b", conv10Dims); //-> temp1 + const auto conv11Dims = net->getConvDims("conv11", conv10bDims); //-> temp0 + + const auto outputDims = memory::dims({1, 3, tileH, tileW}); + + // Allocate two temporary ping-pong buffers to decrease memory usage + const auto temp0Dims = getMaxTensorDims({ + conv1Dims, + conv2Dims, + conv3Dims, + conv4Dims, + conv5Dims, + conv6Dims, + conv7Dims, + conv8Dims, + conv9Dims, + conv10Dims, + conv11Dims + }); + + const auto temp1Dims = getMaxTensorDims({ + conv1bDims, + pool5Dims, + conv6bDims, + conv7bDims, + conv8bDims, + conv9bDims, + conv10bDims, + }); + + auto temp0 = net->allocTensor(temp0Dims); + auto temp1 = net->allocTensor(temp1Dims); + + // Allocate enough memory to hold the concat outputs. Then use the first + // half to hold the previous conv output and the second half to hold the + // pool/orig image output. This works because everything is C dimension + // outermost, padded to K floats, and all the concats are on the C dimension. + auto concat0Dst = net->allocTensor(concat0Dims); + auto concat1Dst = net->allocTensor(concat1Dims); + auto concat2Dst = net->allocTensor(concat2Dims); + auto concat3Dst = net->allocTensor(concat3Dims); + auto concat4Dst = net->allocTensor(concat4Dims); + + // Transfer function + std::shared_ptr transferFunc = makeTransferFunc(); + + // Autoexposure + if (auto tf = std::dynamic_pointer_cast(transferFunc)) + { + if (isnan(hdrScale)) + net->addAutoexposure(color, tf); + else + tf->setExposure(hdrScale); + } + + // Input reorder + auto inputReorderDst = net->castTensor(inputReorderDims, concat0Dst, upsample0Dims); + inputReorder = net->addInputReorder(color, albedo, normal, + transferFunc, + alignment, inputReorderDst); + + // conv1 + auto conv1 = net->addConv("conv1", inputReorder->getDst(), temp0); + + // conv1b + auto conv1b = net->addConv("conv1b", conv1->getDst(), temp1); + + // pool1 + // Adjust pointer for pool1 to eliminate concat1 + auto pool1Dst = net->castTensor(pool1Dims, concat1Dst, upsample1Dims); + auto pool1 = net->addPool(conv1b->getDst(), pool1Dst); + + // conv2 + auto conv2 = net->addConv("conv2", pool1->getDst(), temp0); + + // pool2 + // Adjust pointer for pool2 to eliminate concat2 + auto pool2Dst = net->castTensor(pool2Dims, concat2Dst, upsample2Dims); + auto pool2 = net->addPool(conv2->getDst(), pool2Dst); + + // conv3 + auto conv3 = net->addConv("conv3", pool2->getDst(), temp0); + + // pool3 + // Adjust pointer for pool3 to eliminate concat3 + auto pool3Dst = net->castTensor(pool3Dims, concat3Dst, upsample3Dims); + auto pool3 = net->addPool(conv3->getDst(), pool3Dst); + + // conv4 + auto conv4 = net->addConv("conv4", pool3->getDst(), temp0); + + // pool4 + // Adjust pointer for pool4 to eliminate concat4 + auto pool4Dst = net->castTensor(pool4Dims, concat4Dst, upsample4Dims); + auto pool4 = net->addPool(conv4->getDst(), pool4Dst); + + // conv5 + auto conv5 = net->addConv("conv5", pool4->getDst(), temp0); + + // pool5 + auto pool5 = net->addPool(conv5->getDst(), temp1); + + // upsample4 + auto upsample4Dst = net->castTensor(upsample4Dims, concat4Dst); + auto upsample4 = net->addUpsample(pool5->getDst(), upsample4Dst); + + // conv6 + auto conv6 = net->addConv("conv6", concat4Dst, temp0); + + // conv6b + auto conv6b = net->addConv("conv6b", conv6->getDst(), temp1); + + // upsample3 + auto upsample3Dst = net->castTensor(upsample3Dims, concat3Dst); + auto upsample3 = net->addUpsample(conv6b->getDst(), upsample3Dst); + + // conv7 + auto conv7 = net->addConv("conv7", concat3Dst, temp0); + + // conv7b + auto conv7b = net->addConv("conv7b", conv7->getDst(), temp1); + + // upsample2 + auto upsample2Dst = net->castTensor(upsample2Dims, concat2Dst); + auto upsample2 = net->addUpsample(conv7b->getDst(), upsample2Dst); + + // conv8 + auto conv8 = net->addConv("conv8", concat2Dst, temp0); + + // conv8b + auto conv8b = net->addConv("conv8b", conv8->getDst(), temp1); + + // upsample1 + auto upsample1Dst = net->castTensor(upsample1Dims, concat1Dst); + auto upsample1 = net->addUpsample(conv8b->getDst(), upsample1Dst); + + // conv9 + auto conv9 = net->addConv("conv9", concat1Dst, temp0); + + // conv9b + auto conv9b = net->addConv("conv9b", conv9->getDst(), temp1); + + // upsample0 + auto upsample0Dst = net->castTensor(upsample0Dims, concat0Dst); + auto upsample0 = net->addUpsample(conv9b->getDst(), upsample0Dst); + + // conv10 + auto conv10 = net->addConv("conv10", concat0Dst, temp0); + + // conv10b + auto conv10b = net->addConv("conv10b", conv10->getDst(), temp1); + + // conv11 + auto conv11 = net->addConv("conv11", conv10b->getDst(), temp0, false /* no relu */); + + // Output reorder + outputReorder = net->addOutputReorder(conv11->getDst(), transferFunc, output); + + net->finalize(); + return net; + } + + std::shared_ptr AutoencoderFilter::makeTransferFunc() + { + if (hdr) + return std::make_shared(); + else if (srgb) + return std::make_shared(); + else + return std::make_shared(); + } + +// -- GODOT start -- +// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. +#if 0 +// -- GODOT end -- + + // -------------------------------------------------------------------------- + // RTFilter + // -------------------------------------------------------------------------- + + namespace weights + { + // LDR + extern unsigned char rt_ldr[]; // color + extern unsigned char rt_ldr_alb[]; // color, albedo + extern unsigned char rt_ldr_alb_nrm[]; // color, albedo, normal + + // HDR + extern unsigned char rt_hdr[]; // color + extern unsigned char rt_hdr_alb[]; // color, albedo + extern unsigned char rt_hdr_alb_nrm[]; // color, albedo, normal + } + + RTFilter::RTFilter(const Ref& device) + : AutoencoderFilter(device) + { + weightData.ldr = weights::rt_ldr; + weightData.ldr_alb = weights::rt_ldr_alb; + weightData.ldr_alb_nrm = weights::rt_ldr_alb_nrm; + weightData.hdr = weights::rt_hdr; + weightData.hdr_alb = weights::rt_hdr_alb; + weightData.hdr_alb_nrm = weights::rt_hdr_alb_nrm; + } +// -- GODOT start -- +#endif +// -- GODOT end -- + + // -------------------------------------------------------------------------- + // RTLightmapFilter + // -------------------------------------------------------------------------- + + namespace weights + { + // HDR + extern unsigned char rtlightmap_hdr[]; // color + } + + RTLightmapFilter::RTLightmapFilter(const Ref& device) + : AutoencoderFilter(device) + { + weightData.hdr = weights::rtlightmap_hdr; + + hdr = true; + } + + std::shared_ptr RTLightmapFilter::makeTransferFunc() + { + return std::make_shared(); + } + +} // namespace oidn diff --git a/thirdparty/oidn/core/autoencoder.h b/thirdparty/oidn/core/autoencoder.h new file mode 100644 index 000000000000..98b610844ed7 --- /dev/null +++ b/thirdparty/oidn/core/autoencoder.h @@ -0,0 +1,120 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "filter.h" +#include "network.h" +#include "transfer_function.h" + +namespace oidn { + + // -------------------------------------------------------------------------- + // AutoencoderFilter - Direct-predicting autoencoder + // -------------------------------------------------------------------------- + + class AutoencoderFilter : public Filter + { + protected: + static constexpr int alignment = 32; // required spatial alignment in pixels (padding may be necessary) + static constexpr int receptiveField = 222; // receptive field in pixels + static constexpr int overlap = roundUp(receptiveField / 2, alignment); // required spatial overlap between tiles in pixels + + static constexpr int estimatedBytesBase = 16*1024*1024; // estimated base memory usage + static constexpr int estimatedBytesPerPixel8 = 889; // estimated memory usage per pixel for K=8 + static constexpr int estimatedBytesPerPixel16 = 2185; // estimated memory usage per pixel for K=16 + + Image color; + Image albedo; + Image normal; + Image output; + bool hdr = false; + float hdrScale = std::numeric_limits::quiet_NaN(); + bool srgb = false; + int maxMemoryMB = 6000; // approximate maximum memory usage in MBs + + int H = 0; // image height + int W = 0; // image width + int tileH = 0; // tile height + int tileW = 0; // tile width + int tileCountH = 1; // number of tiles in H dimension + int tileCountW = 1; // number of tiles in W dimension + + std::shared_ptr net; + std::shared_ptr inputReorder; + std::shared_ptr outputReorder; + + struct + { + void* ldr = nullptr; + void* ldr_alb = nullptr; + void* ldr_alb_nrm = nullptr; + void* hdr = nullptr; + void* hdr_alb = nullptr; + void* hdr_alb_nrm = nullptr; + } weightData; + + explicit AutoencoderFilter(const Ref& device); + virtual std::shared_ptr makeTransferFunc(); + + public: + void setImage(const std::string& name, const Image& data) override; + void set1i(const std::string& name, int value) override; + int get1i(const std::string& name) override; + void set1f(const std::string& name, float value) override; + float get1f(const std::string& name) override; + + void commit() override; + void execute() override; + + private: + void computeTileSize(); + + template + std::shared_ptr buildNet(); + + bool isCommitted() const { return bool(net); } + }; + + // -------------------------------------------------------------------------- + // RTFilter - Generic ray tracing denoiser + // -------------------------------------------------------------------------- + +// -- GODOT start -- +// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. +#if 0 +// -- GODOT end -- + class RTFilter : public AutoencoderFilter + { + public: + explicit RTFilter(const Ref& device); + }; +// -- GODOT start -- +#endif +// -- GODOT end -- + + // -------------------------------------------------------------------------- + // RTLightmapFilter - Ray traced lightmap denoiser + // -------------------------------------------------------------------------- + + class RTLightmapFilter : public AutoencoderFilter + { + public: + explicit RTLightmapFilter(const Ref& device); + std::shared_ptr makeTransferFunc() override; + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/buffer.h b/thirdparty/oidn/core/buffer.h new file mode 100644 index 000000000000..b95109152e72 --- /dev/null +++ b/thirdparty/oidn/core/buffer.h @@ -0,0 +1,75 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common.h" +#include "device.h" + +namespace oidn { + + class Device; + + // Buffer which may or may not own its data + class Buffer : public RefCount + { + private: + char* ptr; + size_t byteSize; + bool shared; + Ref device; + + public: + __forceinline Buffer(const Ref& device, size_t size) + : ptr((char*)alignedMalloc(size, 64)), + byteSize(size), + shared(false), + device(device) {} + + __forceinline Buffer(const Ref& device, void* data, size_t size) + : ptr((char*)data), + byteSize(size), + shared(true), + device(device) + { + if (data == nullptr) + throw Exception(Error::InvalidArgument, "buffer pointer null"); + } + + __forceinline ~Buffer() + { + if (!shared) + alignedFree(ptr); + } + + __forceinline char* data() { return ptr; } + __forceinline const char* data() const { return ptr; } + __forceinline size_t size() const { return byteSize; } + + void* map(size_t offset, size_t size) + { + if (offset + size > byteSize) + throw Exception(Error::InvalidArgument, "buffer region out of range"); + + return ptr + offset; + } + + void unmap(void* mappedPtr) {} + + Device* getDevice() { return device.get(); } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/common.h b/thirdparty/oidn/core/common.h new file mode 100644 index 000000000000..a35dd908b46c --- /dev/null +++ b/thirdparty/oidn/core/common.h @@ -0,0 +1,136 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common/platform.h" + +#include "mkl-dnn/include/mkldnn.hpp" +#include "mkl-dnn/include/mkldnn_debug.h" +#include "mkl-dnn/src/common/mkldnn_thread.hpp" +#include "mkl-dnn/src/common/type_helpers.hpp" +#include "mkl-dnn/src/cpu/jit_generator.hpp" + +#include "common/ref.h" +#include "common/exception.h" +#include "common/thread.h" +// -- GODOT start -- +//#include "common/tasking.h" +// -- GODOT end -- +#include "math.h" + +namespace oidn { + + using namespace mkldnn; + using namespace mkldnn::impl::cpu; + using mkldnn::impl::parallel_nd; + using mkldnn::impl::memory_desc_matches_tag; + + + inline size_t getFormatBytes(Format format) + { + switch (format) + { + case Format::Undefined: return 1; + case Format::Float: return sizeof(float); + case Format::Float2: return sizeof(float)*2; + case Format::Float3: return sizeof(float)*3; + case Format::Float4: return sizeof(float)*4; + } + assert(0); + return 0; + } + + + inline memory::dims getTensorDims(const std::shared_ptr& mem) + { + const mkldnn_memory_desc_t& desc = mem->get_desc().data; + return memory::dims(&desc.dims[0], &desc.dims[desc.ndims]); + } + + inline memory::data_type getTensorType(const std::shared_ptr& mem) + { + const mkldnn_memory_desc_t& desc = mem->get_desc().data; + return memory::data_type(desc.data_type); + } + + // Returns the number of values in a tensor + inline size_t getTensorSize(const memory::dims& dims) + { + size_t res = 1; + for (int i = 0; i < (int)dims.size(); ++i) + res *= dims[i]; + return res; + } + + inline memory::dims getMaxTensorDims(const std::vector& dims) + { + memory::dims result; + size_t maxSize = 0; + + for (const auto& d : dims) + { + const size_t size = getTensorSize(d); + if (size > maxSize) + { + result = d; + maxSize = size; + } + } + + return result; + } + + inline size_t getTensorSize(const std::shared_ptr& mem) + { + return getTensorSize(getTensorDims(mem)); + } + + + template + inline int getPadded(int dim) + { + return (dim + (K-1)) & ~(K-1); + } + + template + inline memory::dims getPadded_nchw(const memory::dims& dims) + { + assert(dims.size() == 4); + memory::dims padDims = dims; + padDims[1] = getPadded(dims[1]); // pad C + return padDims; + } + + + template + struct BlockedFormat; + + template<> + struct BlockedFormat<8> + { + static constexpr memory::format_tag nChwKc = memory::format_tag::nChw8c; + static constexpr memory::format_tag OIhwKiKo = memory::format_tag::OIhw8i8o; + }; + + template<> + struct BlockedFormat<16> + { + static constexpr memory::format_tag nChwKc = memory::format_tag::nChw16c; + static constexpr memory::format_tag OIhwKiKo = memory::format_tag::OIhw16i16o; + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/device.cpp b/thirdparty/oidn/core/device.cpp new file mode 100644 index 000000000000..3cd658b9c808 --- /dev/null +++ b/thirdparty/oidn/core/device.cpp @@ -0,0 +1,238 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "device.h" +#include "autoencoder.h" + +namespace oidn { + + thread_local Device::ErrorState Device::globalError; + + Device::Device() + { + if (!mayiuse(sse41)) + throw Exception(Error::UnsupportedHardware, "SSE4.1 support is required at minimum"); + } + + Device::~Device() + { + // -- GODOT start -- + //observer.reset(); + // -- GODOT end -- + } + + void Device::setError(Device* device, Error code, const std::string& message) + { + // Update the stored error only if the previous error was queried + if (device) + { + ErrorState& curError = device->error.get(); + + if (curError.code == Error::None) + { + curError.code = code; + curError.message = message; + } + + // Print the error message in verbose mode + if (device->isVerbose()) + std::cerr << "Error: " << message << std::endl; + + // Call the error callback function + ErrorFunction errorFunc; + void* errorUserPtr; + + { + std::lock_guard lock(device->mutex); + errorFunc = device->errorFunc; + errorUserPtr = device->errorUserPtr; + } + + if (errorFunc) + errorFunc(errorUserPtr, code, (code == Error::None) ? nullptr : message.c_str()); + } + else + { + if (globalError.code == Error::None) + { + globalError.code = code; + globalError.message = message; + } + } + } + + Error Device::getError(Device* device, const char** outMessage) + { + // Return and clear the stored error code, but keep the error message so pointers to it will + // remain valid until the next getError call + if (device) + { + ErrorState& curError = device->error.get(); + const Error code = curError.code; + if (outMessage) + *outMessage = (code == Error::None) ? nullptr : curError.message.c_str(); + curError.code = Error::None; + return code; + } + else + { + const Error code = globalError.code; + if (outMessage) + *outMessage = (code == Error::None) ? nullptr : globalError.message.c_str(); + globalError.code = Error::None; + return code; + } + } + + void Device::setErrorFunction(ErrorFunction func, void* userPtr) + { + errorFunc = func; + errorUserPtr = userPtr; + } + + int Device::get1i(const std::string& name) + { + if (name == "numThreads") + return numThreads; + else if (name == "setAffinity") + return setAffinity; + else if (name == "verbose") + return verbose; + else if (name == "version") + return OIDN_VERSION; + else if (name == "versionMajor") + return OIDN_VERSION_MAJOR; + else if (name == "versionMinor") + return OIDN_VERSION_MINOR; + else if (name == "versionPatch") + return OIDN_VERSION_PATCH; + else + throw Exception(Error::InvalidArgument, "invalid parameter"); + } + + void Device::set1i(const std::string& name, int value) + { + if (name == "numThreads") + numThreads = value; + else if (name == "setAffinity") + setAffinity = value; + else if (name == "verbose") + { + verbose = value; + error.verbose = value; + } + + dirty = true; + } + + void Device::commit() + { + if (isCommitted()) + throw Exception(Error::InvalidOperation, "device can be committed only once"); + + // -- GODOT start -- + #if 0 + // -- GODOT end -- + // Get the optimal thread affinities + if (setAffinity) + { + affinity = std::make_shared(1, verbose); // one thread per core + if (affinity->getNumThreads() == 0) + affinity.reset(); + } + + // Create the task arena + const int maxNumThreads = affinity ? affinity->getNumThreads() : tbb::this_task_arena::max_concurrency(); + numThreads = (numThreads > 0) ? min(numThreads, maxNumThreads) : maxNumThreads; + arena = std::make_shared(numThreads); + + // Automatically set the thread affinities + if (affinity) + observer = std::make_shared(affinity, *arena); + // -- GODOT start -- + #endif + numThreads = 1; + // -- GODOT end -- + dirty = false; + + if (isVerbose()) + print(); + } + + void Device::checkCommitted() + { + if (dirty) + throw Exception(Error::InvalidOperation, "changes to the device are not committed"); + } + + Ref Device::newBuffer(size_t byteSize) + { + checkCommitted(); + return makeRef(Ref(this), byteSize); + } + + Ref Device::newBuffer(void* ptr, size_t byteSize) + { + checkCommitted(); + return makeRef(Ref(this), ptr, byteSize); + } + + Ref Device::newFilter(const std::string& type) + { + checkCommitted(); + + if (isVerbose()) + std::cout << "Filter: " << type << std::endl; + + Ref filter; + +// -- GODOT start -- +// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. +#if 0 +// -- GODOT end -- + if (type == "RT") + filter = makeRef(Ref(this)); +// -- GODOT start -- +// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. +#endif + if (type == "RTLightmap") +// -- GODOT end -- + filter = makeRef(Ref(this)); + else + throw Exception(Error::InvalidArgument, "unknown filter type"); + + return filter; + } + + void Device::print() + { + std::cout << std::endl; + + std::cout << "Intel(R) Open Image Denoise " << OIDN_VERSION_STRING << std::endl; + std::cout << " Compiler: " << getCompilerName() << std::endl; + std::cout << " Build : " << getBuildName() << std::endl; + std::cout << " Platform: " << getPlatformName() << std::endl; + +// -- GODOT start -- +// std::cout << " Tasking :"; +// std::cout << " TBB" << TBB_VERSION_MAJOR << "." << TBB_VERSION_MINOR; +// std::cout << " TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << tbb::TBB_runtime_interface_version(); +// std::cout << std::endl; +// -- GODOT end -- + std::cout << std::endl; + } + +} // namespace oidn diff --git a/thirdparty/oidn/core/device.h b/thirdparty/oidn/core/device.h new file mode 100644 index 000000000000..d9cfd8541abf --- /dev/null +++ b/thirdparty/oidn/core/device.h @@ -0,0 +1,102 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common.h" + +namespace oidn { + + class Buffer; + class Filter; + + class Device : public RefCount, public Verbose + { + private: + // Thread-safety + std::mutex mutex; + + // Error handling + struct ErrorState + { + Error code = Error::None; + std::string message; + }; + + static thread_local ErrorState globalError; + ThreadLocal error; + ErrorFunction errorFunc = nullptr; + void* errorUserPtr = nullptr; + +// -- GODOT start -- +// // Tasking +// std::shared_ptr arena; +// std::shared_ptr observer; +// std::shared_ptr affinity; +// -- GODOT end -- + + // Parameters + int numThreads = 0; // autodetect by default + bool setAffinity = true; + + bool dirty = true; + + public: + Device(); + ~Device(); + + static void setError(Device* device, Error code, const std::string& message); + static Error getError(Device* device, const char** outMessage); + + void setErrorFunction(ErrorFunction func, void* userPtr); + + int get1i(const std::string& name); + void set1i(const std::string& name, int value); + + void commit(); + +// -- GODOT start -- +// template +// void executeTask(F& f) +// { +// arena->execute(f); +// } + +// template +// void executeTask(const F& f) +// { +// arena->execute(f); +// } +// -- GODOT end -- + + Ref newBuffer(size_t byteSize); + Ref newBuffer(void* ptr, size_t byteSize); + Ref newFilter(const std::string& type); + + __forceinline Device* getDevice() { return this; } + __forceinline std::mutex& getMutex() { return mutex; } + + private: +// -- GODOT start -- + //bool isCommitted() const { return bool(arena); } + bool isCommitted() const { return false; } +// -- GODOT end -- + void checkCommitted(); + + void print(); + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/filter.cpp b/thirdparty/oidn/core/filter.cpp new file mode 100644 index 000000000000..ec1f10af871f --- /dev/null +++ b/thirdparty/oidn/core/filter.cpp @@ -0,0 +1,27 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "filter.h" + +namespace oidn { + + void Filter::setProgressMonitorFunction(ProgressMonitorFunction func, void* userPtr) + { + progressFunc = func; + progressUserPtr = userPtr; + } + +} // namespace oidn diff --git a/thirdparty/oidn/core/filter.h b/thirdparty/oidn/core/filter.h new file mode 100644 index 000000000000..935fa202f45c --- /dev/null +++ b/thirdparty/oidn/core/filter.h @@ -0,0 +1,52 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common.h" +#include "device.h" +#include "image.h" + +namespace oidn { + + class Filter : public RefCount + { + protected: + Ref device; + + ProgressMonitorFunction progressFunc = nullptr; + void* progressUserPtr = nullptr; + + bool dirty = true; + + public: + explicit Filter(const Ref& device) : device(device) {} + + virtual void setImage(const std::string& name, const Image& data) = 0; + virtual void set1i(const std::string& name, int value) = 0; + virtual int get1i(const std::string& name) = 0; + virtual void set1f(const std::string& name, float value) = 0; + virtual float get1f(const std::string& name) = 0; + + void setProgressMonitorFunction(ProgressMonitorFunction func, void* userPtr); + + virtual void commit() = 0; + virtual void execute() = 0; + + Device* getDevice() { return device.get(); } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/image.h b/thirdparty/oidn/core/image.h new file mode 100644 index 000000000000..748f49c4e5c4 --- /dev/null +++ b/thirdparty/oidn/core/image.h @@ -0,0 +1,111 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common.h" +#include "buffer.h" + +namespace oidn { + + struct Image + { + static constexpr int maxSize = 65536; + + char* ptr; // pointer to the first pixel + int width; // width in number of pixels + int height; // height in number of pixels + size_t bytePixelStride; // pixel stride in number of *bytes* + size_t rowStride; // row stride in number of *pixel strides* + Format format; // pixel format + Ref buffer; // buffer containing the image data + + Image() : ptr(nullptr), width(0), height(0), bytePixelStride(0), rowStride(0), format(Format::Undefined) {} + + Image(void* ptr, Format format, int width, int height, size_t byteOffset, size_t inBytePixelStride, size_t inByteRowStride) + { + if (ptr == nullptr) + throw Exception(Error::InvalidArgument, "buffer pointer null"); + + init((char*)ptr + byteOffset, format, width, height, inBytePixelStride, inByteRowStride); + } + + Image(const Ref& buffer, Format format, int width, int height, size_t byteOffset, size_t inBytePixelStride, size_t inByteRowStride) + { + init(buffer->data() + byteOffset, format, width, height, inBytePixelStride, inByteRowStride); + + if (byteOffset + height * rowStride * bytePixelStride > buffer->size()) + throw Exception(Error::InvalidArgument, "buffer region out of range"); + } + + void init(char* ptr, Format format, int width, int height, size_t inBytePixelStride, size_t inByteRowStride) + { + assert(width >= 0); + assert(height >= 0); + if (width > maxSize || height > maxSize) + throw Exception(Error::InvalidArgument, "image size too large"); + + this->ptr = ptr; + this->width = width; + this->height = height; + + const size_t pixelSize = getFormatBytes(format); + if (inBytePixelStride != 0) + { + if (inBytePixelStride < pixelSize) + throw Exception(Error::InvalidArgument, "pixel stride smaller than pixel size"); + + this->bytePixelStride = inBytePixelStride; + } + else + { + this->bytePixelStride = pixelSize; + } + + if (inByteRowStride != 0) + { + if (inByteRowStride < width * this->bytePixelStride) + throw Exception(Error::InvalidArgument, "row stride smaller than width * pixel stride"); + if (inByteRowStride % this->bytePixelStride != 0) + throw Exception(Error::InvalidArgument, "row stride not integer multiple of pixel stride"); + + this->rowStride = inByteRowStride / this->bytePixelStride; + } + else + { + this->rowStride = width; + } + + this->format = format; + } + + __forceinline char* get(int y, int x) + { + return ptr + ((size_t(y) * rowStride + size_t(x)) * bytePixelStride); + } + + __forceinline const char* get(int y, int x) const + { + return ptr + ((size_t(y) * rowStride + size_t(x)) * bytePixelStride); + } + + operator bool() const + { + return ptr != nullptr; + } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/input_reorder.h b/thirdparty/oidn/core/input_reorder.h new file mode 100644 index 000000000000..966856afe9e5 --- /dev/null +++ b/thirdparty/oidn/core/input_reorder.h @@ -0,0 +1,232 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "node.h" +#include "image.h" + +namespace oidn { + + // Input reorder node + template + class InputReorderNode : public Node + { + private: + // Source + Image color; + Image albedo; + Image normal; + + // Destination + std::shared_ptr dst; + float* dstPtr; + int C2; + int H2; + int W2; + + // Tile + int h1Begin; + int w1Begin; + int h2Begin; + int w2Begin; + int H; + int W; + + std::shared_ptr transferFunc; + + public: + InputReorderNode(const Image& color, + const Image& albedo, + const Image& normal, + const std::shared_ptr& dst, + const std::shared_ptr& transferFunc) + : color(color), albedo(albedo), normal(normal), + dst(dst), + h1Begin(0), w1Begin(0), + H(color.height), W(color.width), + transferFunc(transferFunc) + { + const mkldnn_memory_desc_t& dstDesc = dst->get_desc().data; + assert(memory_desc_matches_tag(dstDesc, mkldnn_format_tag_t(BlockedFormat::nChwKc))); + assert(dstDesc.ndims == 4); + assert(dstDesc.data_type == memory::data_type::f32); + assert(dstDesc.dims[0] == 1); + //assert(dstDesc.dims[1] >= getPadded(C1)); + + dstPtr = (float*)dst->get_data_handle(); + C2 = dstDesc.dims[1]; + H2 = dstDesc.dims[2]; + W2 = dstDesc.dims[3]; + } + + void setTile(int h1, int w1, int h2, int w2, int H, int W) override + { + h1Begin = h1; + w1Begin = w1; + h2Begin = h2; + w2Begin = w2; + this->H = H; + this->W = W; + } + + void execute(stream& sm) override + { + assert(H + h1Begin <= color.height); + assert(W + w1Begin <= color.width); + assert(H + h2Begin <= H2); + assert(W + w2Begin <= W2); + + parallel_nd(H2, [&](int h2) + { + const int h = h2 - h2Begin; + + if (h >= 0 && h < H) + { + const int h1 = h + h1Begin; + + // Zero pad + for (int w2 = 0; w2 < w2Begin; ++w2) + { + int c = 0; + while (c < C2) + store(h2, w2, c, 0.f); + } + + // Reorder + for (int w = 0; w < W; ++w) + { + const int w1 = w + w1Begin; + const int w2 = w + w2Begin; + + int c = 0; + storeColor(h2, w2, c, (float*)color.get(h1, w1)); + if (albedo) + storeAlbedo(h2, w2, c, (float*)albedo.get(h1, w1)); + if (normal) + storeNormal(h2, w2, c, (float*)normal.get(h1, w1)); + while (c < C2) + store(h2, w2, c, 0.f); + } + + // Zero pad + for (int w2 = W + w2Begin; w2 < W2; ++w2) + { + int c = 0; + while (c < C2) + store(h2, w2, c, 0.f); + } + } + else + { + // Zero pad + for (int w2 = 0; w2 < W2; ++w2) + { + int c = 0; + while (c < C2) + store(h2, w2, c, 0.f); + } + } + }); + } + + std::shared_ptr getDst() const override { return dst; } + + private: + // Stores a single value + __forceinline void store(int h, int w, int& c, float value) + { + // Destination is in nChwKc format + float* dst_c = dstPtr + (H2*W2*K*(c/K)) + h*W2*K + w*K + (c%K); + *dst_c = value; + c++; + } + + // Stores a color + __forceinline void storeColor(int h, int w, int& c, const float* values) + { + #pragma unroll + for (int i = 0; i < 3; ++i) + { + // Load the value + float x = values[i]; + + // Sanitize the value + x = maxSafe(x, 0.f); + + // Apply the transfer function + x = transferFunc->forward(x); + + // Store the value + store(h, w, c, x); + } + } + + // Stores an albedo + __forceinline void storeAlbedo(int h, int w, int& c, const float* values) + { + #pragma unroll + for (int i = 0; i < 3; ++i) + { + // Load the value + float x = values[i]; + + // Sanitize the value + x = clampSafe(x, 0.f, 1.f); + + // Store the value + store(h, w, c, x); + } + } + + // Stores a normal + __forceinline void storeNormal(int h, int w, int& c, const float* values) + { + // Load the normal + float x = values[0]; + float y = values[1]; + float z = values[2]; + + // Compute the length of the normal + const float lengthSqr = sqr(x) + sqr(y) + sqr(z); + + // Normalize the normal and transform it to [0..1] + if (isfinite(lengthSqr)) + { + const float invLength = (lengthSqr > minVectorLengthSqr) ? rsqrt(lengthSqr) : 1.f; + + const float scale = invLength * 0.5f; + const float offset = 0.5f; + + x = x * scale + offset; + y = y * scale + offset; + z = z * scale + offset; + } + else + { + x = 0.f; + y = 0.f; + z = 0.f; + } + + // Store the normal + store(h, w, c, x); + store(h, w, c, y); + store(h, w, c, z); + } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/math.h b/thirdparty/oidn/core/math.h new file mode 100644 index 000000000000..a844ef0d1dc0 --- /dev/null +++ b/thirdparty/oidn/core/math.h @@ -0,0 +1,78 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common/platform.h" + +namespace oidn { + + constexpr float minVectorLength = 1e-10f; + constexpr float minVectorLengthSqr = minVectorLength * minVectorLength; + + using std::log; + using std::log2; + using std::exp; + using std::exp2; + using std::pow; + using std::isfinite; + using std::isnan; + + __forceinline float sqr(float x) + { + return x * x; + } + + __forceinline float rcp(float x) + { + __m128 r = _mm_rcp_ss(_mm_set_ss(x)); + return _mm_cvtss_f32(_mm_sub_ss(_mm_add_ss(r, r), _mm_mul_ss(_mm_mul_ss(r, r), _mm_set_ss(x)))); + } + + __forceinline float rsqrt(float x) + { + __m128 r = _mm_rsqrt_ss(_mm_set_ss(x)); + return _mm_cvtss_f32(_mm_add_ss(_mm_mul_ss(_mm_set_ss(1.5f), r), + _mm_mul_ss(_mm_mul_ss(_mm_mul_ss(_mm_set_ss(x), _mm_set_ss(-0.5f)), r), _mm_mul_ss(r, r)))); + } + + __forceinline float maxSafe(float value, float minValue) + { + return isfinite(value) ? max(value, minValue) : minValue; + } + + __forceinline float clampSafe(float value, float minValue, float maxValue) + { + return isfinite(value) ? clamp(value, minValue, maxValue) : minValue; + } + + // Returns ceil(a / b) for non-negative integers + template + __forceinline constexpr Int ceilDiv(Int a, Int b) + { + //assert(a >= 0); + //assert(b > 0); + return (a + b - 1) / b; + } + + // Returns a rounded up to multiple of b + template + __forceinline constexpr Int roundUp(Int a, Int b) + { + return ceilDiv(a, b) * b; + } + +} // namespace oidn diff --git a/thirdparty/oidn/core/network.cpp b/thirdparty/oidn/core/network.cpp new file mode 100644 index 000000000000..ed8328c954dc --- /dev/null +++ b/thirdparty/oidn/core/network.cpp @@ -0,0 +1,436 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "upsample.h" +#include "weights_reorder.h" +#include "network.h" +// -- GODOT start -- +#include +// -- GODOT end -- + +namespace oidn { + + template + Network::Network(const Ref& device, const std::map& weightMap) + : device(device), + eng(engine::cpu, 0), + sm(eng), + weightMap(weightMap) + { + } + + template + void Network::execute(const Progress& progress, int taskIndex) + { + if (progress.func) + { + const double value = double(taskIndex) / double(progress.taskCount); + if (!progress.func(progress.userPtr, value)) + throw Exception(Error::Cancelled, "execution was cancelled"); + } + + for (size_t i = 0; i < nodes.size(); ++i) + { + nodes[i]->execute(sm); + + if (progress.func) + { + const double value = (double(taskIndex) + double(i+1) / double(nodes.size())) / double(progress.taskCount); + if (!progress.func(progress.userPtr, value)) + throw Exception(Error::Cancelled, "execution was cancelled"); + } + } + } + + template + std::shared_ptr Network::allocTensor(const memory::dims& dims, + memory::format_tag format, + void* data) + { + if (format == memory::format_tag::any) + { + if (dims.size() == 4) + format = BlockedFormat::nChwKc; + else if (dims.size() == 1) + format = memory::format_tag::x; + else + assert(0); + } + memory::desc desc(dims, memory::data_type::f32, format); + if (data == nullptr) + { + const size_t bytes = getTensorSize(dims) * sizeof(float); + if (format == BlockedFormat::nChwKc) + activationAllocBytes += bytes; + totalAllocBytes += bytes; + + return std::make_shared(desc, eng); + } + else + { + return std::make_shared(desc, eng, data); + } + } + + template + std::shared_ptr Network::castTensor(const memory::dims& dims, + const std::shared_ptr& src, + size_t srcOffset, + memory::format_tag format) + { + const mkldnn_memory_desc_t& srcDesc = src->get_desc().data; + MAYBE_UNUSED(srcDesc); + assert(srcDesc.data_type == memory::data_type::f32); + assert(getTensorSize(src) >= srcOffset + getTensorSize(dims)); + + if (format == memory::format_tag::any) + { + if (dims.size() == 4) + format = BlockedFormat::nChwKc; + else if (dims.size() == 1) + format = memory::format_tag::x; + else + assert(0); + } + memory::desc desc(dims, memory::data_type::f32, format); + float* srcPtr = (float*)src->get_data_handle() + srcOffset; + return std::make_shared(desc, eng, srcPtr); + } + + template + std::shared_ptr Network::castTensor(const memory::dims& dims, + const std::shared_ptr& src, + const memory::dims& srcOffset) + { + return castTensor(dims, src, getTensorSize(srcOffset)); + } + + template + void Network::zeroTensor(const std::shared_ptr& dst) + { + assert(getTensorType(dst) == memory::data_type::f32); + memset(dst->get_data_handle(), 0, getTensorSize(dst)*sizeof(float)); + } + + template + memory::dims Network::getInputReorderDims(const memory::dims& srcDims, int alignment) + { + memory::dims dstDims = srcDims; + dstDims[1] = getPadded(srcDims[1]); // round up C + dstDims[2] = roundUp(srcDims[2], memory::dim(alignment)); // round up H + dstDims[3] = roundUp(srcDims[3], memory::dim(alignment)); // round up W + return dstDims; + } + + template + std::shared_ptr Network::addInputReorder(const Image& color, + const Image& albedo, + const Image& normal, + const std::shared_ptr& transferFunc, + int alignment, + const std::shared_ptr& userDst) + { + assert(color); + int inputC = 3; + if (albedo) inputC += 3; + if (normal) inputC += 3; + + memory::dims srcDims = {1, inputC, color.height, color.width}; + memory::dims dstDims = getInputReorderDims(srcDims, alignment); + + // Allocate padded memory + auto dst = userDst; + if (!dst) + dst = allocTensor(dstDims); + + // Push node + std::shared_ptr node; + + if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(color, albedo, normal, dst, tf); + else if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(color, albedo, normal, dst, tf); + else if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(color, albedo, normal, dst, tf); + else if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(color, albedo, normal, dst, tf); + else + assert(0); + + nodes.push_back(node); + return node; + } + + template + std::shared_ptr Network::addOutputReorder(const std::shared_ptr& src, + const std::shared_ptr& transferFunc, + const Image& output) + { + memory::dims srcDims = getTensorDims(src); + assert(srcDims[1] == K); + + // Push node + std::shared_ptr node; + + if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(src, output, tf); + else if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(src, output, tf); + else if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(src, output, tf); + else if (auto tf = std::dynamic_pointer_cast(transferFunc)) + node = std::make_shared>(src, output, tf); + else + assert(0); + + nodes.push_back(node); + return node; + } + + template + memory::dims Network::getConvDims(const std::string& name, const memory::dims& srcDims) + { + auto b = weightMap[name + "/b"]; + memory::dims dstDims = srcDims; + dstDims[1] = getPadded(b.dims[0]); // dstDims[C] = getPadded(OC) + return dstDims; + } + + template + std::shared_ptr Network::addConv(const std::string& name, + const std::shared_ptr& src, + const std::shared_ptr& userDst, + bool relu) + { + const memory::dims strides = {1, 1}; + const memory::dims padding = {1, 1}; + + memory::dims srcDims = getTensorDims(src); + + // Get the weights + const auto& W = weightMap[name + "/W"]; + if (W.ndims() != 4 || W.format != "oihw") + throw Exception(Error::InvalidOperation, "invalid convolution weights"); + memory::dims weightsDims = W.dims; + auto userWeights = allocTensor(weightsDims, memory::format_tag::oihw, W.data); + + // Pad the weights + memory::dims weightsPadDims = weightsDims; + weightsPadDims[1] = getPadded(weightsDims[1]); // IC + weightsPadDims[0] = getPadded(weightsDims[0]); // OC + assert(srcDims[1] == weightsPadDims[1]); // srcDims[C] == weightsPadDims[IC] + auto weightsPad = allocTensor(weightsPadDims, memory::format_tag::oihw); + WeightsReorderNode(userWeights, weightsPad).execute(sm); + + // Get the biases + const auto& b = weightMap[name + "/b"]; + if (b.ndims() != 1) + throw Exception(Error::InvalidOperation, "invalid convolution biases"); + memory::dims biasDims = b.dims; + + // Copy/pad the biases + memory::dims biasPadDims = {getPadded(biasDims[0])}; + auto bias = allocTensor(biasPadDims); + if (biasDims[0] != biasPadDims[0]) + memset(bias->get_data_handle(), 0, biasPadDims[0]*sizeof(float)); + memcpy(bias->get_data_handle(), b.data, biasDims[0]*sizeof(float)); + + // Allocate memory for destination + memory::dims dstDims = srcDims; + dstDims[1] = weightsPadDims[0]; // dstDims[C] = weightsPadDims[OC] + + std::shared_ptr dst; + if (!userDst) + dst = allocTensor(dstDims); + else if (getTensorDims(userDst) == dstDims) + dst = userDst; + else + dst = castTensor(dstDims, userDst); + + // Create a convolution + // Let the convolution primitive choose the weights format + auto weightsDesc = memory::desc({ weightsPadDims }, memory::data_type::f32, memory::format_tag::any); + + auto convAlgo = (K == 16) ? convolution_winograd : convolution_direct; + auto convDesc = convolution_forward::desc( + prop_kind::forward_inference, convAlgo, + src->get_desc(), + weightsDesc, + bias->get_desc(), + dst->get_desc(), + strides, padding, padding, padding_kind::zero); + + // Incorporate relu + mkldnn::primitive_attr convAttr; + if (relu) + { + mkldnn::post_ops ops; + ops.append_eltwise( + 1.f, // scale factor, not used + algorithm::eltwise_relu, + 0.f, // max with + 0.f // unused + ); + convAttr.set_post_ops(ops); + } + convAttr.set_scratchpad_mode(scratchpad_mode_user); + + auto convPrimDesc = convolution_forward::primitive_desc(convDesc, convAttr, eng); + + // Reorder the weights to the final format, if necessary + auto weights = weightsPad; + if (convPrimDesc.weights_desc() != weightsPad->get_desc()) + { + weights = std::make_shared(convPrimDesc.weights_desc(), eng); + ReorderNode(weightsPad, weights).execute(sm); + } + + // Create convolution node and add it to the net + auto node = std::make_shared(convPrimDesc, src, weights, bias, dst); + nodes.push_back(node); + return node; + } + + template + memory::dims Network::getPoolDims(const memory::dims& srcDims) + { + memory::dims dstDims = srcDims; + dstDims[2] /= 2; // H/2 + dstDims[3] /= 2; // W/2 + return dstDims; + } + + template + std::shared_ptr Network::addPool(const std::shared_ptr& src, + const std::shared_ptr& userDst) + { + const memory::dims kernel = {2, 2}; + const memory::dims strides = {2, 2}; + const memory::dims padding = {0, 0}; + + memory::dims srcDims = getTensorDims(src); + memory::dims dstDims = getPoolDims(srcDims); + + std::shared_ptr dst; + if (!userDst) + dst = allocTensor(dstDims); + else if (getTensorDims(userDst) == dstDims) + dst = userDst; + else + dst = castTensor(dstDims, userDst); + + auto poolDesc = pooling_forward::desc( + prop_kind::forward_inference, pooling_max, + src->get_desc(), + dst->get_desc(), + strides, kernel, padding, padding, padding_kind::zero); + + mkldnn::primitive_attr poolAttr; + poolAttr.set_scratchpad_mode(scratchpad_mode_user); + + auto poolPrimDesc = pooling_forward::primitive_desc(poolDesc, poolAttr, eng); + + auto node = std::make_shared(poolPrimDesc, src, dst); + nodes.push_back(node); + return node; + } + + template + memory::dims Network::getUpsampleDims(const memory::dims& srcDims) + { + memory::dims dstDims = srcDims; + dstDims[2] *= 2; // H*2 + dstDims[3] *= 2; // W*2 + return dstDims; + } + + template + std::shared_ptr Network::addUpsample(const std::shared_ptr& src, + const std::shared_ptr& userDst) + { + memory::dims srcDims = getTensorDims(src); + memory::dims dstDims = getUpsampleDims(srcDims); + + std::shared_ptr dst; + if (!userDst) + dst = allocTensor(dstDims); + else if (getTensorDims(userDst) == dstDims) + dst = userDst; + else + dst = castTensor(dstDims, userDst); + + // Create upsampling node and add it to net + auto node = std::make_shared>(src, dst); + nodes.push_back(node); + return node; + } + + template + memory::dims Network::getConcatDims(const memory::dims& src1Dims, const memory::dims& src2Dims) + { + assert(src1Dims[0] == src2Dims[0]); // N + assert(src1Dims[2] == src2Dims[2]); // H + assert(src1Dims[3] == src2Dims[3]); // W + + memory::dims dstDims = src1Dims; + dstDims[1] += src2Dims[1]; // C + return dstDims; + } + + template + std::shared_ptr Network::addAutoexposure(const Image& color, + const std::shared_ptr& transferFunc) + { + auto node = std::make_shared(color, transferFunc); + nodes.push_back(node); + return node; + } + + template + void Network::finalize() + { + // Compute the size of the scratchpad + size_t scratchpadSize = 0; + for (const auto& node : nodes) + scratchpadSize = max(scratchpadSize, node->getScratchpadSize()); + + // Allocate the scratchpad + memory::dims scratchpadDims = { memory::dim(scratchpadSize) }; + memory::desc scratchpadDesc(scratchpadDims, memory::data_type::u8, memory::format_tag::x); + auto scratchpad = std::make_shared(scratchpadDesc, eng); + activationAllocBytes += scratchpadSize; + totalAllocBytes += scratchpadSize; + + // Set the scratchpad for the nodes + for (auto& node : nodes) + node->setScratchpad(scratchpad); + + // Free the weights + weightMap.clear(); + + // Print statistics + if (device->isVerbose(2)) + { + std::cout << "Activation bytes: " << activationAllocBytes << std::endl; + std::cout << "Scratchpad bytes: " << scratchpadSize << std::endl; + std::cout << "Total bytes : " << totalAllocBytes << std::endl; + } + } + + template class Network<8>; + template class Network<16>; + +} // namespace oidn diff --git a/thirdparty/oidn/core/network.h b/thirdparty/oidn/core/network.h new file mode 100644 index 000000000000..7a696fd3550d --- /dev/null +++ b/thirdparty/oidn/core/network.h @@ -0,0 +1,112 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "common/tensor.h" +#include "image.h" +#include "node.h" +#include "input_reorder.h" +#include "output_reorder.h" +#include "transfer_function.h" + +#pragma once + +namespace oidn { + + // Progress state + struct Progress + { + ProgressMonitorFunction func; + void* userPtr; + int taskCount; + }; + + class Executable + { + public: + virtual ~Executable() {} + virtual void execute(const Progress& progress, int taskIndex) = 0; + }; + + template + class Network : public Executable + { + public: + Network(const Ref& device, const std::map& weightMap); + + void execute(const Progress& progress, int taskIndex) override; + + std::shared_ptr allocTensor(const memory::dims& dims, + memory::format_tag format = memory::format_tag::any, + void* data = nullptr); + + std::shared_ptr castTensor(const memory::dims& dims, + const std::shared_ptr& src, + size_t srcOffset = 0, + memory::format_tag format = memory::format_tag::any); + + std::shared_ptr castTensor(const memory::dims& dims, + const std::shared_ptr& src, + const memory::dims& srcOffset); + + void zeroTensor(const std::shared_ptr& dst); + + memory::dims getInputReorderDims(const memory::dims& srcDims, int alignment); + + std::shared_ptr addInputReorder(const Image& color, + const Image& albedo, + const Image& normal, + const std::shared_ptr& transferFunc, + int alignment, + const std::shared_ptr& userDst = nullptr); + + std::shared_ptr addOutputReorder(const std::shared_ptr& src, + const std::shared_ptr& transferFunc, + const Image& output); + + memory::dims getConvDims(const std::string& name, const memory::dims& srcDims); + std::shared_ptr addConv(const std::string& name, + const std::shared_ptr& src, + const std::shared_ptr& userDst = nullptr, + bool relu = true); + + memory::dims getPoolDims(const memory::dims& srcDims); + std::shared_ptr addPool(const std::shared_ptr& src, + const std::shared_ptr& userDst = nullptr); + + memory::dims getUpsampleDims(const memory::dims& srcDims); + std::shared_ptr addUpsample(const std::shared_ptr& src, + const std::shared_ptr& userDst = nullptr); + + memory::dims getConcatDims(const memory::dims& src1Dims, const memory::dims& src2Dims); + + std::shared_ptr addAutoexposure(const Image& color, + const std::shared_ptr& transferFunc); + + void finalize(); + + private: + Ref device; + engine eng; + stream sm; + std::vector> nodes; + std::map weightMap; + + // Memory allocation statistics + size_t activationAllocBytes = 0; // number of allocated activation bytes + size_t totalAllocBytes = 0; // total number of allocated bytes + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/node.h b/thirdparty/oidn/core/node.h new file mode 100644 index 000000000000..b9ffe906dff5 --- /dev/null +++ b/thirdparty/oidn/core/node.h @@ -0,0 +1,142 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "common.h" +#include + +namespace oidn { + + class Node + { + public: + virtual ~Node() = default; + + virtual void execute(stream& sm) = 0; + + virtual std::shared_ptr getDst() const { return nullptr; } + + virtual size_t getScratchpadSize() const { return 0; } + virtual void setScratchpad(const std::shared_ptr& mem) {} + + virtual void setTile(int h1, int w1, int h2, int w2, int H, int W) + { + assert(0); // not supported + } + }; + + // Node wrapping an MKL-DNN primitive + class MklNode : public Node + { + private: + primitive prim; + std::unordered_map args; + std::shared_ptr scratchpad; + + public: + MklNode(const primitive& prim, const std::unordered_map& args) + : prim(prim), + args(args) + {} + + size_t getScratchpadSize() const override + { + const auto primDesc = prim.get_primitive_desc(); + const mkldnn_memory_desc_t* scratchpadDesc = mkldnn_primitive_desc_query_md(primDesc, mkldnn_query_scratchpad_md, 0); + if (scratchpadDesc == nullptr) + return 0; + return mkldnn_memory_desc_get_size(scratchpadDesc); + } + + void setScratchpad(const std::shared_ptr& mem) override + { + scratchpad = mem; + args.insert(std::make_pair(MKLDNN_ARG_SCRATCHPAD, *scratchpad)); + } + + void execute(stream& sm) override + { + prim.execute(sm, args); + } + }; + + // Convolution node + class ConvNode : public MklNode + { + private: + std::shared_ptr src; + std::shared_ptr weights; + std::shared_ptr bias; + std::shared_ptr dst; + + public: + ConvNode(const convolution_forward::primitive_desc& desc, + const std::shared_ptr& src, + const std::shared_ptr& weights, + const std::shared_ptr& bias, + const std::shared_ptr& dst) + : MklNode(convolution_forward(desc), + { { MKLDNN_ARG_SRC, *src }, + { MKLDNN_ARG_WEIGHTS, *weights }, + { MKLDNN_ARG_BIAS, *bias }, + { MKLDNN_ARG_DST, *dst } }), + src(src), weights(weights), bias(bias), dst(dst) + {} + + std::shared_ptr getDst() const override { return dst; } + }; + + // Pooling node + class PoolNode : public MklNode + { + private: + std::shared_ptr src; + std::shared_ptr dst; + + public: + PoolNode(const pooling_forward::primitive_desc& desc, + const std::shared_ptr& src, + const std::shared_ptr& dst) + : MklNode(pooling_forward(desc), + { { MKLDNN_ARG_SRC, *src }, + { MKLDNN_ARG_DST, *dst } }), + src(src), dst(dst) + {} + + std::shared_ptr getDst() const override { return dst; } + }; + + // Reorder node + class ReorderNode : public MklNode + { + private: + std::shared_ptr src; + std::shared_ptr dst; + + public: + ReorderNode(const std::shared_ptr& src, + const std::shared_ptr& dst) + : MklNode(reorder(reorder::primitive_desc(*src, *dst)), + { { MKLDNN_ARG_SRC, *src }, + { MKLDNN_ARG_DST, *dst } }), + src(src), dst(dst) + {} + + std::shared_ptr getDst() const override { return dst; } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/output_reorder.h b/thirdparty/oidn/core/output_reorder.h new file mode 100644 index 000000000000..7918d48e1508 --- /dev/null +++ b/thirdparty/oidn/core/output_reorder.h @@ -0,0 +1,126 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "node.h" +#include "image.h" + +namespace oidn { + + // Output reorder node + template + class OutputReorderNode : public Node + { + private: + // Source + std::shared_ptr src; + const float* srcPtr; + int H1; + int W1; + + // Destination + Image output; + + // Tile + int h1Begin; + int w1Begin; + int h2Begin; + int w2Begin; + int H; + int W; + + std::shared_ptr transferFunc; + + public: + OutputReorderNode(const std::shared_ptr& src, + const Image& output, + const std::shared_ptr& transferFunc) + : src(src), + output(output), + h1Begin(0), w1Begin(0), + h2Begin(0), w2Begin(0), + H(output.height), W(output.width), + transferFunc(transferFunc) + { + const mkldnn_memory_desc_t& srcDesc = src->get_desc().data; + MAYBE_UNUSED(srcDesc); + assert(memory_desc_matches_tag(srcDesc, mkldnn_format_tag_t(BlockedFormat::nChwKc))); + assert(srcDesc.ndims == 4); + assert(srcDesc.data_type == memory::data_type::f32); + assert(srcDesc.dims[0] == 1); + // We assume output data is <= K OC + assert(srcDesc.dims[1] == K); + + srcPtr = (float*)src->get_data_handle(); + H1 = srcDesc.dims[2]; + W1 = srcDesc.dims[3]; + } + + void setTile(int h1, int w1, int h2, int w2, int H, int W) override + { + h1Begin = h1; + w1Begin = w1; + h2Begin = h2; + w2Begin = w2; + this->H = H; + this->W = W; + } + + void execute(stream& sm) override + { + assert(h1Begin + H <= H1); + assert(w1Begin + W <= W1); + assert(h2Begin + H <= output.height); + assert(w2Begin + W <= output.width); + + const int C1 = K; + + parallel_nd(H, [&](int h) + { + const int h1 = h + h1Begin; + const int h2 = h + h2Begin; + + for (int w = 0; w < W; ++w) + { + const int w1 = w + w1Begin; + const int w2 = w + w2Begin; + float* dstPtr_C = (float*)output.get(h2, w2); + + // Source is in nChwKc format. In this case C is 1 so this is really nhwc + const float* srcPtr_C = srcPtr + h1*W1*C1 + w1*C1; + + #pragma unroll + for (int i = 0; i < 3; ++i) + { + // Load the value + float x = srcPtr_C[i]; + + // The CNN output may contain negative values or even NaNs, so it must be sanitized + x = maxSafe(x, 0.f); + + // Apply the inverse transfer function + x = transferFunc->inverse(x); + + // Sanitize and store the final value + dstPtr_C[i] = max(x, 0.f); + } + } + }); + } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/transfer_function.cpp b/thirdparty/oidn/core/transfer_function.cpp new file mode 100644 index 000000000000..ce5deca56bc7 --- /dev/null +++ b/thirdparty/oidn/core/transfer_function.cpp @@ -0,0 +1,103 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#include "transfer_function.h" + +namespace oidn { + + const float LogTransferFunction::xScale = 1.f / log(LogTransferFunction::yMax + 1.f); + const float PQXTransferFunction::xScale = 1.f / PQXTransferFunction::pqxForward(PQXTransferFunction::yMax * PQXTransferFunction::yScale); + + float AutoexposureNode::autoexposure(const Image& color) + { + assert(color.format == Format::Float3); + + constexpr float key = 0.18f; + constexpr float eps = 1e-8f; + constexpr int K = 16; // downsampling amount + + // Downsample the image to minimize sensitivity to noise + const int H = color.height; // original height + const int W = color.width; // original width + const int HK = (H + K/2) / K; // downsampled height + const int WK = (W + K/2) / K; // downsampled width + + // Compute the average log luminance of the downsampled image + using Sum = std::pair; + + // -- GODOT start -- + // Sum sum = + // tbb::parallel_reduce( + // tbb::blocked_range2d(0, HK, 0, WK), + // Sum(0.f, 0), + // [&](const tbb::blocked_range2d& r, Sum sum) -> Sum + // { + // // Iterate over blocks + // for (int i = r.rows().begin(); i != r.rows().end(); ++i) + // { + // for (int j = r.cols().begin(); j != r.cols().end(); ++j) + // { + + Sum sum = Sum(0.0f, 0); + + for (int i = 0; i != HK; ++i) + { + for (int j = 0; j != WK; ++j) + { + // Compute the average luminance in the current block + const int beginH = int(ptrdiff_t(i) * H / HK); + const int beginW = int(ptrdiff_t(j) * W / WK); + const int endH = int(ptrdiff_t(i+1) * H / HK); + const int endW = int(ptrdiff_t(j+1) * W / WK); + + float L = 0.f; + + for (int h = beginH; h < endH; ++h) + { + for (int w = beginW; w < endW; ++w) + { + const float* rgb = (const float*)color.get(h, w); + + const float r = maxSafe(rgb[0], 0.f); + const float g = maxSafe(rgb[1], 0.f); + const float b = maxSafe(rgb[2], 0.f); + + L += luminance(r, g, b); + } + } + + L /= (endH - beginH) * (endW - beginW); + + // Accumulate the log luminance + if (L > eps) + { + sum.first += log2(L); + sum.second++; + } + } + } + + // return sum; + // }, + // [](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); }, + // tbb::static_partitioner() + // ); + // -- GODOT end -- + + return (sum.second > 0) ? (key / exp2(sum.first / float(sum.second))) : 1.f; + } + +} // namespace oidn diff --git a/thirdparty/oidn/core/transfer_function.h b/thirdparty/oidn/core/transfer_function.h new file mode 100644 index 000000000000..35f28330925c --- /dev/null +++ b/thirdparty/oidn/core/transfer_function.h @@ -0,0 +1,201 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "image.h" +#include "node.h" + +namespace oidn { + + __forceinline float luminance(float r, float g, float b) + { + return 0.212671f * r + 0.715160f * g + 0.072169f * b; + } + + // Color transfer function base class + class TransferFunction + { + public: + virtual ~TransferFunction() = default; + + virtual float forward(float y) const = 0; + virtual float inverse(float x) const = 0; + }; + + // HDR transfer function base class + class HDRTransferFunction : public TransferFunction + { + protected: + static constexpr float yMax = 65504.f; + + float exposure; + float rcpExposure; + + public: + HDRTransferFunction(float exposure = 1.f) + { + setExposure(exposure); + } + + void setExposure(float exposure) + { + this->exposure = exposure; + this->rcpExposure = (exposure != 0.f) ? (1.f / exposure) : 0.f; + } + }; + + // Linear transfer function (LDR) + class LinearTransferFunction : public TransferFunction + { + public: + __forceinline float forward(float y) const override + { + return min(y, 1.f); + } + + __forceinline float inverse(float x) const override + { + return min(x, 1.f); + } + }; + + // 2.2 gamma transfer function (LDR) + class GammaTransferFunction : public TransferFunction + { + public: + __forceinline float forward(float y) const override + { + return min(pow(y, 1.f/2.2f), 1.f); + } + + __forceinline float inverse(float x) const override + { + return min(pow(x, 2.2f), 1.f); + } + }; + + // Logarithmic transfer function (HDR) + // Compresses [0..65504] to [0..1] + class LogTransferFunction : public HDRTransferFunction + { + private: + static const float xScale; + + public: + LogTransferFunction(float exposure = 1.f) + : HDRTransferFunction(exposure) + { + } + + __forceinline float forward(float y) const override + { + return log(y * exposure + 1.f) * xScale; + } + + __forceinline float inverse(float x) const override + { + return (exp(x * (1.f/xScale)) - 1.f) * rcpExposure; + } + }; + + // PQX transfer function (HDR) + // Compresses [0..65504] to [0..1] + class PQXTransferFunction : public HDRTransferFunction + { + private: + static constexpr float m1 = 2610.f / 4096.f / 4.f; + static constexpr float m2 = 2523.f / 4096.f * 128.f; + static constexpr float c1 = 3424.f / 4096.f; + static constexpr float c2 = 2413.f / 4096.f * 32.f; + static constexpr float c3 = 2392.f / 4096.f * 32.f; + static constexpr float a = 3711.f / 4096.f / 8.f; + + static constexpr float yScale = 100.f / 10000.f; + static const float xScale; + + public: + PQXTransferFunction(float exposure = 1.f) + : HDRTransferFunction(exposure) + { + } + + __forceinline float forward(float y) const override + { + return pqxForward(y * exposure * yScale) * xScale; + } + + __forceinline float inverse(float x) const override + { + return pqxInverse(x * (1.f/xScale)) * (1.f/yScale) * rcpExposure; + } + + private: + static __forceinline float pqForward(float y) + { + const float yp = pow(y, m1); + return pow((c1 + c2 * yp) * rcp(1.f + c3 * yp), m2); + } + + static __forceinline float pqxForward(float y) + { + if (y <= 1.f) + return pqForward(y); + else + return a * log(y) + 1.f; + } + + static __forceinline float pqInverse(float x) + { + const float xp = pow(x, 1.f/m2); + return pow(max((xp - c1) * rcp(c2 - c3 * xp), 0.f), 1.f/m1); + } + + static __forceinline float pqxInverse(float x) + { + if (x <= 1.f) + return pqInverse(x); + else + return exp((x - 1.f) * (1.f/a)); + } + }; + + // Autoexposure node + class AutoexposureNode : public Node + { + private: + Image color; + std::shared_ptr transferFunc; + + public: + AutoexposureNode(const Image& color, + const std::shared_ptr& transferFunc) + : color(color), + transferFunc(transferFunc) + {} + + void execute(stream& sm) override + { + const float exposure = autoexposure(color); + //printf("exposure = %f\n", exposure); + transferFunc->setExposure(exposure); + } + + private: + static float autoexposure(const Image& color); + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/upsample.h b/thirdparty/oidn/core/upsample.h new file mode 100644 index 000000000000..f6cace44cd2d --- /dev/null +++ b/thirdparty/oidn/core/upsample.h @@ -0,0 +1,92 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "node.h" + +namespace oidn { + + // 2x2 nearest-neighbor upsampling node + template + class UpsampleNode : public Node + { + private: + std::shared_ptr src; + std::shared_ptr dst; + + public: + UpsampleNode(const std::shared_ptr& src, + const std::shared_ptr& dst) + : src(src), + dst(dst) + { + const mkldnn_memory_desc_t& srcDesc = src->get_desc().data; + const mkldnn_memory_desc_t& dstDesc = dst->get_desc().data; + MAYBE_UNUSED(srcDesc); + MAYBE_UNUSED(dstDesc); + assert(memory_desc_matches_tag(srcDesc, mkldnn_format_tag_t(BlockedFormat::nChwKc))); + assert(memory_desc_matches_tag(dstDesc, mkldnn_format_tag_t(BlockedFormat::nChwKc))); + assert(srcDesc.ndims == 4); + assert(dstDesc.ndims == 4); + assert(srcDesc.data_type == memory::data_type::f32); + assert(dstDesc.data_type == memory::data_type::f32); + assert(srcDesc.dims[0] == 1); + assert(dstDesc.dims[0] == 1); + // 2x2 upsampling + assert(dstDesc.dims[2] == srcDesc.dims[2] * 2); + assert(dstDesc.dims[3] == srcDesc.dims[3] * 2); + } + + void execute(stream& sm) override + { + const mkldnn_memory_desc_t& srcDesc = src->get_desc().data; + + const float* srcPtr = (float*)src->get_data_handle(); + float* dstPtr = (float*)dst->get_data_handle(); + + const int C = srcDesc.dims[1]; + const int H = srcDesc.dims[2]; + const int W = srcDesc.dims[3]; + const int CK = C / K; + + parallel_nd(CK, H, [&](int ck, int h) + { + const size_t offset = ck*H*W*K + h*W*K; + const float* srcPtr_line = srcPtr + offset; + float* dstPtr_line0 = dstPtr + offset * 4; + float* dstPtr_line1 = dstPtr_line0 + W*2*K; // next line + + for (int w = 0; w < W; ++w) + { + #pragma unroll + for (int k = 0; k < K; k += 4) + { + const __m128 m = _mm_load_ps(&srcPtr_line[w*K + k]); + + _mm_stream_ps(&dstPtr_line0[w*2*K + k], m); + _mm_stream_ps(&dstPtr_line0[w*2*K+K + k], m); + _mm_stream_ps(&dstPtr_line1[w*2*K + k], m); + _mm_stream_ps(&dstPtr_line1[w*2*K+K + k], m); + } + } + }); + } + + std::shared_ptr getDst() const override { return dst; } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/core/weights_reorder.h b/thirdparty/oidn/core/weights_reorder.h new file mode 100644 index 000000000000..6c5dacb8aaa2 --- /dev/null +++ b/thirdparty/oidn/core/weights_reorder.h @@ -0,0 +1,99 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "node.h" + +namespace oidn { + + // Reorders weights from oihw to padded oihw format + template + class WeightsReorderNode : public Node + { + private: + std::shared_ptr src; + std::shared_ptr dst; + + public: + WeightsReorderNode(const std::shared_ptr& src, + const std::shared_ptr& dst) + : src(src), + dst(dst) + { + const mkldnn_memory_desc_t& srcDesc = src->get_desc().data; + const mkldnn_memory_desc_t& dstDesc = dst->get_desc().data; + MAYBE_UNUSED(srcDesc); + MAYBE_UNUSED(dstDesc); + assert(memory_desc_matches_tag(srcDesc, mkldnn_format_tag_t(memory::format_tag::oihw))); + assert(memory_desc_matches_tag(dstDesc, mkldnn_format_tag_t(memory::format_tag::oihw))); + assert(srcDesc.ndims == 4); + assert(dstDesc.ndims == 4); + assert(srcDesc.data_type == memory::data_type::f32); + assert(dstDesc.data_type == memory::data_type::f32); + assert(getPadded(srcDesc.dims[0]) == dstDesc.dims[0]); // OC + assert(getPadded(srcDesc.dims[1]) == dstDesc.dims[1]); // IC + assert(srcDesc.dims[2] == dstDesc.dims[2]); + assert(srcDesc.dims[3] == dstDesc.dims[3]); + } + + void execute(stream& sm) override + { + const mkldnn_memory_desc_t& srcDesc = src->get_desc().data; + const mkldnn_memory_desc_t& dstDesc = dst->get_desc().data; + + const float* srcPtr = (float*)src->get_data_handle(); + float* dstPtr = (float*)dst->get_data_handle(); + + const int OC1 = srcDesc.dims[0]; + const int OC2 = dstDesc.dims[0]; + const int IC1 = srcDesc.dims[1]; + const int IC2 = dstDesc.dims[1]; + const int H = dstDesc.dims[2]; + const int W = dstDesc.dims[3]; + + for (int oc = 0; oc < OC2; ++oc) + { + for (int ic = 0; ic < IC2; ++ic) + { + for (int h = 0; h < H; ++h) + { + for (int w = 0; w < W; ++w) + { + // Output is in oihw format + float* dstPtr_c = dstPtr + oc*IC2*H*W + ic*H*W + h*W + w; + + if (oc < OC1 && ic < IC1) + { + // Input is in oihw format + const float* srcPtr_c = srcPtr + oc*IC1*H*W + ic*H*W + h*W + w; + *dstPtr_c = *srcPtr_c; + } + else + { + // padding + *dstPtr_c = 0; + } + } + } + } + } + } + + std::shared_ptr getDst() const override { return dst; } + }; + +} // namespace oidn diff --git a/thirdparty/oidn/include/OpenImageDenoise/oidn.h b/thirdparty/oidn/include/OpenImageDenoise/oidn.h new file mode 100644 index 000000000000..57ba6baa213e --- /dev/null +++ b/thirdparty/oidn/include/OpenImageDenoise/oidn.h @@ -0,0 +1,214 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include +#include +#include + +#include "version.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef OIDN_API +#if defined(_WIN32) && !defined(OIDN_STATIC_LIB) +# define OIDN_API __declspec(dllimport) +#else +# define OIDN_API +#endif +#endif + +// ---------------------------------------------------------------------------- +// Device +// ---------------------------------------------------------------------------- + +// Device types +typedef enum +{ + OIDN_DEVICE_TYPE_DEFAULT = 0, // select device automatically + + OIDN_DEVICE_TYPE_CPU = 1, // CPU device +} OIDNDeviceType; + +// Error codes +typedef enum +{ + OIDN_ERROR_NONE = 0, // no error occurred + OIDN_ERROR_UNKNOWN = 1, // an unknown error occurred + OIDN_ERROR_INVALID_ARGUMENT = 2, // an invalid argument was specified + OIDN_ERROR_INVALID_OPERATION = 3, // the operation is not allowed + OIDN_ERROR_OUT_OF_MEMORY = 4, // not enough memory to execute the operation + OIDN_ERROR_UNSUPPORTED_HARDWARE = 5, // the hardware (e.g. CPU) is not supported + OIDN_ERROR_CANCELLED = 6, // the operation was cancelled by the user +} OIDNError; + +// Error callback function +typedef void (*OIDNErrorFunction)(void* userPtr, OIDNError code, const char* message); + +// Device handle +typedef struct OIDNDeviceImpl* OIDNDevice; + +// Creates a new device. +OIDN_API OIDNDevice oidnNewDevice(OIDNDeviceType type); + +// Retains the device (increments the reference count). +OIDN_API void oidnRetainDevice(OIDNDevice device); + +// Releases the device (decrements the reference count). +OIDN_API void oidnReleaseDevice(OIDNDevice device); + +// Sets a boolean parameter of the device. +OIDN_API void oidnSetDevice1b(OIDNDevice device, const char* name, bool value); + +// Sets an integer parameter of the device. +OIDN_API void oidnSetDevice1i(OIDNDevice device, const char* name, int value); + +// Gets a boolean parameter of the device. +OIDN_API bool oidnGetDevice1b(OIDNDevice device, const char* name); + +// Gets an integer parameter of the device (e.g. "version"). +OIDN_API int oidnGetDevice1i(OIDNDevice device, const char* name); + +// Sets the error callback function of the device. +OIDN_API void oidnSetDeviceErrorFunction(OIDNDevice device, OIDNErrorFunction func, void* userPtr); + +// Returns the first unqueried error code stored in the device for the current +// thread, optionally also returning a string message (if not NULL), and clears +// the stored error. Can be called with a NULL device as well to check why a +// device creation failed. +OIDN_API OIDNError oidnGetDeviceError(OIDNDevice device, const char** outMessage); + +// Commits all previous changes to the device. +// Must be called before first using the device (e.g. creating filters). +OIDN_API void oidnCommitDevice(OIDNDevice device); + +// ---------------------------------------------------------------------------- +// Buffer +// ---------------------------------------------------------------------------- + +// Formats for images and other data stored in buffers +typedef enum +{ + OIDN_FORMAT_UNDEFINED = 0, + + // 32-bit single-precision floating point scalar and vector formats + OIDN_FORMAT_FLOAT = 1, + OIDN_FORMAT_FLOAT2 = 2, + OIDN_FORMAT_FLOAT3 = 3, + OIDN_FORMAT_FLOAT4 = 4, +} OIDNFormat; + +// Access modes for mapping buffers +typedef enum +{ + OIDN_ACCESS_READ = 0, // read-only access + OIDN_ACCESS_WRITE = 1, // write-only access + OIDN_ACCESS_READ_WRITE = 2, // read and write access + OIDN_ACCESS_WRITE_DISCARD = 3, // write-only access, previous contents discarded +} OIDNAccess; + +// Buffer handle +typedef struct OIDNBufferImpl* OIDNBuffer; + +// Creates a new buffer (data allocated and owned by the device). +OIDN_API OIDNBuffer oidnNewBuffer(OIDNDevice device, size_t byteSize); + +// Creates a new shared buffer (data allocated and owned by the user). +OIDN_API OIDNBuffer oidnNewSharedBuffer(OIDNDevice device, void* ptr, size_t byteSize); + +// Maps a region of the buffer to host memory. +// If byteSize is 0, the maximum available amount of memory will be mapped. +OIDN_API void* oidnMapBuffer(OIDNBuffer buffer, OIDNAccess access, size_t byteOffset, size_t byteSize); + +// Unmaps a region of the buffer. +// mappedPtr must be a pointer returned by a previous call to oidnMapBuffer. +OIDN_API void oidnUnmapBuffer(OIDNBuffer buffer, void* mappedPtr); + +// Retains the buffer (increments the reference count). +OIDN_API void oidnRetainBuffer(OIDNBuffer buffer); + +// Releases the buffer (decrements the reference count). +OIDN_API void oidnReleaseBuffer(OIDNBuffer buffer); + +// ---------------------------------------------------------------------------- +// Filter +// ---------------------------------------------------------------------------- + +// Progress monitor callback function +typedef bool (*OIDNProgressMonitorFunction)(void* userPtr, double n); + +// Filter handle +typedef struct OIDNFilterImpl* OIDNFilter; + +// Creates a new filter of the specified type (e.g. "RT"). +OIDN_API OIDNFilter oidnNewFilter(OIDNDevice device, const char* type); + +// Retains the filter (increments the reference count). +OIDN_API void oidnRetainFilter(OIDNFilter filter); + +// Releases the filter (decrements the reference count). +OIDN_API void oidnReleaseFilter(OIDNFilter filter); + +// Sets an image parameter of the filter (stored in a buffer). +// If bytePixelStride and/or byteRowStride are zero, these will be computed automatically. +OIDN_API void oidnSetFilterImage(OIDNFilter filter, const char* name, + OIDNBuffer buffer, OIDNFormat format, + size_t width, size_t height, + size_t byteOffset, + size_t bytePixelStride, size_t byteRowStride); + +// Sets an image parameter of the filter (owned by the user). +// If bytePixelStride and/or byteRowStride are zero, these will be computed automatically. +OIDN_API void oidnSetSharedFilterImage(OIDNFilter filter, const char* name, + void* ptr, OIDNFormat format, + size_t width, size_t height, + size_t byteOffset, + size_t bytePixelStride, size_t byteRowStride); + +// Sets a boolean parameter of the filter. +OIDN_API void oidnSetFilter1b(OIDNFilter filter, const char* name, bool value); + +// Gets a boolean parameter of the filter. +OIDN_API bool oidnGetFilter1b(OIDNFilter filter, const char* name); + +// Sets an integer parameter of the filter. +OIDN_API void oidnSetFilter1i(OIDNFilter filter, const char* name, int value); + +// Gets an integer parameter of the filter. +OIDN_API int oidnGetFilter1i(OIDNFilter filter, const char* name); + +// Sets a float parameter of the filter. +OIDN_API void oidnSetFilter1f(OIDNFilter filter, const char* name, float value); + +// Gets a float parameter of the filter. +OIDN_API float oidnGetFilter1f(OIDNFilter filter, const char* name); + +// Sets the progress monitor callback function of the filter. +OIDN_API void oidnSetFilterProgressMonitorFunction(OIDNFilter filter, OIDNProgressMonitorFunction func, void* userPtr); + +// Commits all previous changes to the filter. +// Must be called before first executing the filter. +OIDN_API void oidnCommitFilter(OIDNFilter filter); + +// Executes the filter. +OIDN_API void oidnExecuteFilter(OIDNFilter filter); + +#if defined(__cplusplus) +} +#endif diff --git a/thirdparty/oidn/include/OpenImageDenoise/oidn.hpp b/thirdparty/oidn/include/OpenImageDenoise/oidn.hpp new file mode 100644 index 000000000000..9f95a56fe17f --- /dev/null +++ b/thirdparty/oidn/include/OpenImageDenoise/oidn.hpp @@ -0,0 +1,468 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include +#include "oidn.h" + +namespace oidn { + + // -------------------------------------------------------------------------- + // Buffer + // -------------------------------------------------------------------------- + + // Formats for images and other data stored in buffers + enum class Format + { + Undefined = OIDN_FORMAT_UNDEFINED, + + // 32-bit single-precision floating point scalar and vector formats + Float = OIDN_FORMAT_FLOAT, + Float2 = OIDN_FORMAT_FLOAT2, + Float3 = OIDN_FORMAT_FLOAT3, + Float4 = OIDN_FORMAT_FLOAT4, + }; + + // Access modes for mapping buffers + enum class Access + { + Read = OIDN_ACCESS_READ, // read-only access + Write = OIDN_ACCESS_WRITE, // write-only access + ReadWrite = OIDN_ACCESS_READ_WRITE, // read and write access + WriteDiscard = OIDN_ACCESS_WRITE_DISCARD, // write-only access, previous contents discarded + }; + + // Buffer object with automatic reference counting + class BufferRef + { + private: + OIDNBuffer handle; + + public: + BufferRef() : handle(nullptr) {} + BufferRef(OIDNBuffer handle) : handle(handle) {} + + BufferRef(const BufferRef& other) : handle(other.handle) + { + if (handle) + oidnRetainBuffer(handle); + } + + BufferRef(BufferRef&& other) : handle(other.handle) + { + other.handle = nullptr; + } + + BufferRef& operator =(const BufferRef& other) + { + if (&other != this) + { + if (other.handle) + oidnRetainBuffer(other.handle); + if (handle) + oidnReleaseBuffer(handle); + handle = other.handle; + } + return *this; + } + + BufferRef& operator =(BufferRef&& other) + { + std::swap(handle, other.handle); + return *this; + } + + BufferRef& operator =(OIDNBuffer other) + { + if (other) + oidnRetainBuffer(other); + if (handle) + oidnReleaseBuffer(handle); + handle = other; + return *this; + } + + ~BufferRef() + { + if (handle) + oidnReleaseBuffer(handle); + } + + OIDNBuffer getHandle() const + { + return handle; + } + + operator bool() const + { + return handle != nullptr; + } + + // Maps a region of the buffer to host memory. + // If byteSize is 0, the maximum available amount of memory will be mapped. + void* map(Access access = Access::ReadWrite, size_t byteOffset = 0, size_t byteSize = 0) + { + return oidnMapBuffer(handle, (OIDNAccess)access, byteOffset, byteSize); + } + + // Unmaps a region of the buffer. + // mappedPtr must be a pointer returned by a previous call to map. + void unmap(void* mappedPtr) + { + oidnUnmapBuffer(handle, mappedPtr); + } + }; + + // -------------------------------------------------------------------------- + // Filter + // -------------------------------------------------------------------------- + + // Progress monitor callback function + typedef bool (*ProgressMonitorFunction)(void* userPtr, double n); + + // Filter object with automatic reference counting + class FilterRef + { + private: + OIDNFilter handle; + + public: + FilterRef() : handle(nullptr) {} + FilterRef(OIDNFilter handle) : handle(handle) {} + + FilterRef(const FilterRef& other) : handle(other.handle) + { + if (handle) + oidnRetainFilter(handle); + } + + FilterRef(FilterRef&& other) : handle(other.handle) + { + other.handle = nullptr; + } + + FilterRef& operator =(const FilterRef& other) + { + if (&other != this) + { + if (other.handle) + oidnRetainFilter(other.handle); + if (handle) + oidnReleaseFilter(handle); + handle = other.handle; + } + return *this; + } + + FilterRef& operator =(FilterRef&& other) + { + std::swap(handle, other.handle); + return *this; + } + + FilterRef& operator =(OIDNFilter other) + { + if (other) + oidnRetainFilter(other); + if (handle) + oidnReleaseFilter(handle); + handle = other; + return *this; + } + + ~FilterRef() + { + if (handle) + oidnReleaseFilter(handle); + } + + OIDNFilter getHandle() const + { + return handle; + } + + operator bool() const + { + return handle != nullptr; + } + + // Sets an image parameter of the filter (stored in a buffer). + void setImage(const char* name, + const BufferRef& buffer, Format format, + size_t width, size_t height, + size_t byteOffset = 0, + size_t bytePixelStride = 0, size_t byteRowStride = 0) + { + oidnSetFilterImage(handle, name, + buffer.getHandle(), (OIDNFormat)format, + width, height, + byteOffset, + bytePixelStride, byteRowStride); + } + + // Sets an image parameter of the filter (owned by the user). + void setImage(const char* name, + void* ptr, Format format, + size_t width, size_t height, + size_t byteOffset = 0, + size_t bytePixelStride = 0, size_t byteRowStride = 0) + { + oidnSetSharedFilterImage(handle, name, + ptr, (OIDNFormat)format, + width, height, + byteOffset, + bytePixelStride, byteRowStride); + } + + // Sets a boolean parameter of the filter. + void set(const char* name, bool value) + { + oidnSetFilter1b(handle, name, value); + } + + // Sets an integer parameter of the filter. + void set(const char* name, int value) + { + oidnSetFilter1i(handle, name, value); + } + + // Sets a float parameter of the filter. + void set(const char* name, float value) + { + oidnSetFilter1f(handle, name, value); + } + + // Gets a parameter of the filter. + template + T get(const char* name); + + // Sets the progress monitor callback function of the filter. + void setProgressMonitorFunction(ProgressMonitorFunction func, void* userPtr = nullptr) + { + oidnSetFilterProgressMonitorFunction(handle, (OIDNProgressMonitorFunction)func, userPtr); + } + + // Commits all previous changes to the filter. + void commit() + { + oidnCommitFilter(handle); + } + + // Executes the filter. + void execute() + { + oidnExecuteFilter(handle); + } + }; + + // Gets a boolean parameter of the filter. + template<> + inline bool FilterRef::get(const char* name) + { + return oidnGetFilter1b(handle, name); + } + + // Gets an integer parameter of the filter. + template<> + inline int FilterRef::get(const char* name) + { + return oidnGetFilter1i(handle, name); + } + + // Gets a float parameter of the filter. + template<> + inline float FilterRef::get(const char* name) + { + return oidnGetFilter1f(handle, name); + } + + // -------------------------------------------------------------------------- + // Device + // -------------------------------------------------------------------------- + + // Device types + enum class DeviceType + { + Default = OIDN_DEVICE_TYPE_DEFAULT, // select device automatically + + CPU = OIDN_DEVICE_TYPE_CPU, // CPU device + }; + + // Error codes + enum class Error + { + None = OIDN_ERROR_NONE, // no error occurred + Unknown = OIDN_ERROR_UNKNOWN, // an unknown error occurred + InvalidArgument = OIDN_ERROR_INVALID_ARGUMENT, // an invalid argument was specified + InvalidOperation = OIDN_ERROR_INVALID_OPERATION, // the operation is not allowed + OutOfMemory = OIDN_ERROR_OUT_OF_MEMORY, // not enough memory to execute the operation + UnsupportedHardware = OIDN_ERROR_UNSUPPORTED_HARDWARE, // the hardware (e.g. CPU) is not supported + Cancelled = OIDN_ERROR_CANCELLED, // the operation was cancelled by the user + }; + + // Error callback function + typedef void (*ErrorFunction)(void* userPtr, Error code, const char* message); + + // Device object with automatic reference counting + class DeviceRef + { + private: + OIDNDevice handle; + + public: + DeviceRef() : handle(nullptr) {} + DeviceRef(OIDNDevice handle) : handle(handle) {} + + DeviceRef(const DeviceRef& other) : handle(other.handle) + { + if (handle) + oidnRetainDevice(handle); + } + + DeviceRef(DeviceRef&& other) : handle(other.handle) + { + other.handle = nullptr; + } + + DeviceRef& operator =(const DeviceRef& other) + { + if (&other != this) + { + if (other.handle) + oidnRetainDevice(other.handle); + if (handle) + oidnReleaseDevice(handle); + handle = other.handle; + } + return *this; + } + + DeviceRef& operator =(DeviceRef&& other) + { + std::swap(handle, other.handle); + return *this; + } + + DeviceRef& operator =(OIDNDevice other) + { + if (other) + oidnRetainDevice(other); + if (handle) + oidnReleaseDevice(handle); + handle = other; + return *this; + } + + ~DeviceRef() + { + if (handle) + oidnReleaseDevice(handle); + } + + OIDNDevice getHandle() const + { + return handle; + } + + operator bool() const + { + return handle != nullptr; + } + + // Sets a boolean parameter of the device. + void set(const char* name, bool value) + { + oidnSetDevice1b(handle, name, value); + } + + // Sets an integer parameter of the device. + void set(const char* name, int value) + { + oidnSetDevice1i(handle, name, value); + } + + // Gets a parameter of the device. + template + T get(const char* name); + + // Sets the error callback function of the device. + void setErrorFunction(ErrorFunction func, void* userPtr = nullptr) + { + oidnSetDeviceErrorFunction(handle, (OIDNErrorFunction)func, userPtr); + } + + // Returns the first unqueried error code and clears the stored error. + // Can be called for a null device as well to check why a device creation failed. + Error getError() + { + return (Error)oidnGetDeviceError(handle, nullptr); + } + + // Returns the first unqueried error code and string message, and clears the stored error. + // Can be called for a null device as well to check why a device creation failed. + Error getError(const char*& outMessage) + { + return (Error)oidnGetDeviceError(handle, &outMessage); + } + + // Commits all previous changes to the device. + // Must be called before first using the device (e.g. creating filters). + void commit() + { + oidnCommitDevice(handle); + } + + // Creates a new buffer (data allocated and owned by the device). + BufferRef newBuffer(size_t byteSize) + { + return oidnNewBuffer(handle, byteSize); + } + + // Creates a new shared buffer (data allocated and owned by the user). + BufferRef newBuffer(void* ptr, size_t byteSize) + { + return oidnNewSharedBuffer(handle, ptr, byteSize); + } + + // Creates a new filter of the specified type (e.g. "RT"). + FilterRef newFilter(const char* type) + { + return oidnNewFilter(handle, type); + } + }; + + // Gets a boolean parameter of the device. + template<> + inline bool DeviceRef::get(const char* name) + { + return oidnGetDevice1b(handle, name); + } + + // Gets an integer parameter of the device (e.g. "version"). + template<> + inline int DeviceRef::get(const char* name) + { + return oidnGetDevice1i(handle, name); + } + + // Creates a new device. + inline DeviceRef newDevice(DeviceType type = DeviceType::Default) + { + return DeviceRef(oidnNewDevice((OIDNDeviceType)type)); + } + +} // namespace oidn diff --git a/thirdparty/oidn/include/OpenImageDenoise/version.h b/thirdparty/oidn/include/OpenImageDenoise/version.h new file mode 100644 index 000000000000..66b347c992b7 --- /dev/null +++ b/thirdparty/oidn/include/OpenImageDenoise/version.h @@ -0,0 +1,23 @@ +// ======================================================================== // +// Copyright 2009-2019 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#define OIDN_VERSION_MAJOR 1 +#define OIDN_VERSION_MINOR 1 +#define OIDN_VERSION_PATCH 0 +#define OIDN_VERSION 10100 +#define OIDN_VERSION_STRING "1.1.0" diff --git a/thirdparty/oidn/mkl-dnn/LICENSE b/thirdparty/oidn/mkl-dnn/LICENSE new file mode 100644 index 000000000000..d13f7b7ca06d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/LICENSE @@ -0,0 +1,214 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + ============================================================================ + + Intel MKL-DNN includes components with separate copyright + notices and license terms. + + XByak, 3-clause BSD license + Copyright (c) 2007 MITSUNARI Shigeo + See full copyright notice and license text in src/cpu/xbyak/COPYRIGHT + + gtest, 3-clause BSD license + Copyright 2008, Google Inc. + See full copyright notice and license text in tests/gtests/gtest/LICENSE diff --git a/thirdparty/oidn/mkl-dnn/include/mkldnn.h b/thirdparty/oidn/mkl-dnn/include/mkldnn.h new file mode 100644 index 000000000000..9b649949223e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/include/mkldnn.h @@ -0,0 +1,1771 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_H +#define MKLDNN_H + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/* All symbols shall be internal unless marked as MKLDNN_API */ +#if defined _WIN32 || defined __CYGWIN__ +# define MKLDNN_HELPER_DLL_IMPORT __declspec(dllimport) +# define MKLDNN_HELPER_DLL_EXPORT __declspec(dllexport) +#else +# if __GNUC__ >= 4 +# define MKLDNN_HELPER_DLL_IMPORT __attribute__ ((visibility ("default"))) +# define MKLDNN_HELPER_DLL_EXPORT __attribute__ ((visibility ("default"))) +# else +# define MKLDNN_HELPER_DLL_IMPORT +# define MKLDNN_HELPER_DLL_EXPORT +# endif +#endif + +#ifdef MKLDNN_DLL +# ifdef MKLDNN_DLL_EXPORTS +# define MKLDNN_API MKLDNN_HELPER_DLL_EXPORT +# else +# define MKLDNN_API MKLDNN_HELPER_DLL_IMPORT +# endif +#else +# define MKLDNN_API +#endif + +#if defined (__GNUC__) +# define MKLDNN_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define MKLDNN_DEPRECATED __declspec(deprecated) +#else +# define MKLDNN_DEPRECATED +#endif + +#include "mkldnn_types.h" +#include "mkldnn_version.h" +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup c_api C API + * @{ */ + +/** @addtogroup c_api_primitive Primitive operations + * @{ */ + +/** @addtogroup c_api_primitive_common Common primitive operations + * @{ */ + +/** Creates a primitive descriptor @p iterator for given @p op_desc, @p attr, + * @p engine, and optionally a hint primitive descriptor from forward + * propagation (required for backward propagation). Pass @c NULL for forward + * propagation. + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_iterator_create( + mkldnn_primitive_desc_iterator_t *iterator, + const_mkldnn_op_desc_t op_desc, const_mkldnn_primitive_attr_t attr, + mkldnn_engine_t engine, + const_mkldnn_primitive_desc_t hint_forward_primitive_desc); + +/** Iterates over primitive descriptors. Returns #mkldnn_iterator_ends if no + * more primitive descriptors are available. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_iterator_next( + mkldnn_primitive_desc_iterator_t iterator); + +/** Fetches the current primitive descriptor. + * + * @note + * The user should delete the fetched primitive descriptor using + * mkldnn_primitive_desc_destroy() once it is no longer needed. */ +mkldnn_primitive_desc_t MKLDNN_API mkldnn_primitive_desc_iterator_fetch( + const_mkldnn_primitive_desc_iterator_t iterator); + +/** Deletes a primitive descriptor @p iterator */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_iterator_destroy( + mkldnn_primitive_desc_iterator_t iterator); + +/** Creates a @p primitive_desc using @p op_desc, @p attr, @p engine, and + * optionally a hint primitive descriptor from forward propagation. The call is + * equivalent to creating a primitive descriptor iterator, immediately fetching + * a primitive descriptor, and then destroying the iterator. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_create( + mkldnn_primitive_desc_t *primitive_desc, + const_mkldnn_op_desc_t op_desc, const_mkldnn_primitive_attr_t attr, + mkldnn_engine_t engine, + const_mkldnn_primitive_desc_t hint_forward_primitive_desc); + +/** Makes a copy of a @p primitive_desc. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_clone( + mkldnn_primitive_desc_t *primitive_desc, + const_mkldnn_primitive_desc_t existing_primitive_desc); + +/** Returns a constant reference to the attribute of a @p primitive_desc. + * + * @warning + * The user should not destroy the obtained @p attr. + * + * @warning + * The lifetime of an @p attr is the same as that of a @p primitive_desc, + * so it is illegal to use the @p attr once @p primitive_desc has been + * destroyed. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_get_attr( + const_mkldnn_primitive_desc_t primitive_desc, + const_mkldnn_primitive_attr_t *attr); + +/** Deletes a @p primitive_desc. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_destroy( + mkldnn_primitive_desc_t primitive_desc); + +/** Queries primitive descriptor + * + * One of the most typical use cases is to query a convolution primitive + * descriptor created with source, weights, and destination formats equal + * to #mkldnn_format_tag_any about the corresponding memory descriptors + * (@p what equals #mkldnn_query_src_md, #mkldnn_query_weights_md, and + * #mkldnn_query_dst_md respectively) to be able to prepare memory and + * create reorders if required. + * + * Another quite typical use case is to query an operation primitive + * descriptor for a workspace (@p what equals #mkldnn_query_workspace_md). + * The returned status #mkldnn_not_required indicates that a workspace is + * not required. + * + * A few other possibilities: + * - query an operation primitive descriptor for the underlying operation + * descriptor (#mkldnn_query_convolution_d, #mkldnn_query_eltwise_d, + * #mkldnn_query_rnn_d, etc.) + * - query an operation primitive descriptor for the implementation + * information string (#mkldnn_query_impl_info_str) + * - query an operation primitive descriptor for the number of inputs and + * outputs (#mkldnn_query_num_of_inputs_s32 and + * #mkldnn_query_num_of_outputs_s32 respectively) + * + * @sa mkldnn_query_t for more options + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_desc_query( + const_mkldnn_primitive_desc_t primitive_desc, mkldnn_query_t what, + int index, void *result); + +/** Queries primitive descriptor for memory descriptor + * + * @returns NULL in case of any error. + * + * This is just a specialized version of mkldnn_primitive_desc_query + * used for convenience. + */ +const mkldnn_memory_desc_t MKLDNN_API *mkldnn_primitive_desc_query_md( + const_mkldnn_primitive_desc_t primitive_desc, mkldnn_query_t what, + int index); + +/** Queries primitive descriptor for signed 32bit int + * + * @returns 0 in case of any error (in particular if the queried entity is + * not of type int32_t). Note that 0 might also be the actual returned + * value. + * + * This is just a specialized version of mkldnn_primitive_desc_query + * used for convenience. + */ +int MKLDNN_API mkldnn_primitive_desc_query_s32( + const_mkldnn_primitive_desc_t primitive_desc, mkldnn_query_t what, + int index); + +/** Creates a @p primitive using a @p primitive_desc descriptor. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_create( + mkldnn_primitive_t *primitive, + const_mkldnn_primitive_desc_t primitive_desc); + +/** Executes a @p primitive using a @p stream, and @p nargs arguments + * @p args. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_execute( + const_mkldnn_primitive_t primitive, mkldnn_stream_t stream, + int nargs, const mkldnn_exec_arg_t *args); + +/** Retrieves a reference to the @p primitive_desc descriptor of given @p + * primitive. + * + * @warning + * The returned object must not be destroyed by the user. The @c const + * qualifier of the returned object prevents such attempts. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_get_primitive_desc( + const_mkldnn_primitive_t primitive, + const_mkldnn_primitive_desc_t *primitive_desc); + +/** Deletes a @p primitive. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_destroy( + mkldnn_primitive_t primitive); + +/** @} */ + +/** @addtogroup c_api_attributes Attributes + * An extension for controlling primitive behavior. + * @{ */ + +/** Creates an empty (default) @p attr attribute. All the parameters are set to + * default values. + * + * An empty attribute is used in primitive descriptor creation whenever it + * is not passed explicitly, e.g. in mkldnn_primitive_desc_create. + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_create( + mkldnn_primitive_attr_t *attr); + +/** Makes a copy of an @p existing_attr. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_clone( + mkldnn_primitive_attr_t *attr, + const_mkldnn_primitive_attr_t existing_attr); + +/** Deletes an @p attr. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_destroy( + mkldnn_primitive_attr_t attr); + +/** Returns the scratchpad @p mode set in the attribute @p attr */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_get_scratchpad_mode( + const_mkldnn_primitive_attr_t attr, mkldnn_scratchpad_mode_t *mode); + +/** Sets scratchpad @p mode. + * + * The possible values are: #mkldnn_scratchpad_mode_library (default) and + * #mkldnn_scratchpad_mode_user. */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_set_scratchpad_mode( + mkldnn_primitive_attr_t attr, mkldnn_scratchpad_mode_t mode); + +/** Returns @p count, correspondence scale @p mask, and a pointer to a constant + * floating point array of output @p scales for given @p attr, previously set + * by mkldnn_primitive_attr_set_output_scales. + * + * @warning + * The @p scales array points to the internal @p attr field, so the user + * should not modify or destroy @p scales. + * + * @warning + * The lifetime of @p scales is the same as that of the @p attr to which it + * belongs, so it is illegal to use @p scales after @p attr is destroyed. + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_get_output_scales( + const_mkldnn_primitive_attr_t attr, mkldnn_dim_t *count, int *mask, + const float **scales); + +/** Sets output @p scales for primitive operations. The number of elements @p + * count and correspondence scale @p mask are stored for future use. + * + * The @p mask argument defines the correspondence between the output tensor + * dimensions and the @p scales array. Set the i-th bit of @p mask to 1 to use a + * dedicated scaling factor for each slice of the output tensor over the i-th + * dimension. Set @p mask to 0 to use a common scaling factor for the whole + * output tensor. + * + * @note + * The dimension order is always native and does not depend on the actual + * layout used. Examples: + * - 2D dimensional data the order of dimensions is always: (n, c) + * - 4D dimensional data the order is always: (n, c, h, w) + * - 5D dimensional weights the order is always: (g, oc, ic, kh, kw) + * + * Example usage: + * @code + * int mb = 32, oc = 32, oh = 14, ow = 14; // convolution output params + * float scales[oc] = { ... }; // unique output scales per output channel + * int oc_dim = 1; // mb_dim = 0, channel_dim = 1, height_dim = 2, ... + * + * mkldnn_convolution_desc_t cd; // create & configure convolution op_desc + * + * mkldnn_primitive_attr_t attr; + * mkldnn_primitive_attr_create(&attr); // create default attributes + * mkldnn_primitive_attr_set_output_scales(attr, oc, 1 << oc_dim, scales); + * + * mkldnn_primitive_desc_t cpd; + * mkldnn_primitive_desc_create(&cpd, &cd, attr, NULL); + * @endcode + * + * @note + * There is no way to check that @p count corresponds to @p mask until an + * actual primitive descriptor is created, so it is the user's + * responsibility to set proper values. The following formula must hold: + * + * \f[count = \prod\limits_{d \in mask} output.dims[d]\f] + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_set_output_scales( + mkldnn_primitive_attr_t attr, mkldnn_dim_t count, int mask, + const float *scales); + +/** Returns @p post_ops for given @p attr. + * + * @warning + * @p post_ops points to the internal @p attr field, so the user should not + * modify or destroy @p post_ops. Also, the lifetime of @p post_ops is the + * same as that of the @p attr it belongs to, so it is illegal to use @p + * post_ops after @p attr has been destroyed. + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_get_post_ops( + const_mkldnn_primitive_attr_t attr, const_mkldnn_post_ops_t *post_ops); + +/** Sets configured @p post_ops to an attribute @p attr for future use (when + * primitive descriptor is being created). + * + * @note + * At this point in time, there is no way to check whether the primitive + * descriptor does or does not support a given sequence of post operations. + * Therefore the user should handle an error that might occur at the + * mkldnn_primitive_desc_create call. + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_set_post_ops( + mkldnn_primitive_attr_t attr, const_mkldnn_post_ops_t post_ops); + +/** @addtogroup c_api_attributes_post_ops Sequence of post operations + * An extension for performing extra operations after a base operation. + * @{ */ + +/** Creates an empty sequence of post operations @p post_ops. */ +mkldnn_status_t MKLDNN_API mkldnn_post_ops_create(mkldnn_post_ops_t *post_ops); + +/** Deletes a @p post_ops sequence. */ +mkldnn_status_t MKLDNN_API mkldnn_post_ops_destroy(mkldnn_post_ops_t post_ops); + +/** Returns the @p length of post operations for given @p post_ops. */ +int MKLDNN_API mkldnn_post_ops_len(const_mkldnn_post_ops_t post_ops); + +/** Returns the type of post operation with index @p index in given + * @p post_ops. In case of error, returns #mkldnn_undefined_primitive. */ +mkldnn_primitive_kind_t MKLDNN_API mkldnn_post_ops_get_kind( + const_mkldnn_post_ops_t post_ops, int index); + +/** Appends accumulation (sum) post operation to the @p post_ops. Prior to + * accumulating the result, the previous value would be multiplied by @p scale. + * + * The kind of this post operation is #mkldnn_sum. + * + * This feature might improve performance for cases like residual learning + * blocks, where the result of convolution is accumulated to the previously + * computed activations. The parameter @p scale might be extreme for the + * integer-based computations when the result and previous activations have + * different logical scaling factors. + * + * In the simplest case when the accumulation is the only post operation, the + * computations would be: + * dst[] <- scale * dst[] + op(...) // instead of dst[] <- op(...) + * + * @note + * This post operation (as well as all the others) disregards the original + * layout of the destination; that is, the layout of the original + * destination is expected to be the same as the layout of the stored + * destination. + */ +mkldnn_status_t MKLDNN_API mkldnn_post_ops_append_sum( + mkldnn_post_ops_t post_ops, float scale); + +/** Gets the parameters of the accumulation (sum) post operation with index + * @p index in the sequence of @p post_ops. + * + * @note + * If index @p index would not correspond to the accumulation post + * operation, the function returns #mkldnn_invalid_arguments. + */ +mkldnn_status_t MKLDNN_API mkldnn_post_ops_get_params_sum( + const_mkldnn_post_ops_t post_ops, int index, float *scale); + +/** Appends eltwise post operation to the @p post_ops with given parameters + * @p kind, @p alpha, and @p beta (@sa mkldnn_eltwise_forward_desc_init and + * mkldnn_eltwise_desc_t). + * + * The kind of this post operation is #mkldnn_eltwise. + * + * In the simplest case when the eltwise is the only post operation, the + * computations would be: + * dst[] <- scale * eltwise_op ( op(...) ) // instead of dst[] <- op(...) + * where eltwise_op is configured with the given parameters. + */ +mkldnn_status_t MKLDNN_API mkldnn_post_ops_append_eltwise( + mkldnn_post_ops_t post_ops, float scale, mkldnn_alg_kind_t alg, + float alpha, float beta); + +/** Gets the eltwise parameters of the post operation with index @p index in + * the sequence of @p post_ops. + */ +mkldnn_status_t MKLDNN_API mkldnn_post_ops_get_params_eltwise( + const_mkldnn_post_ops_t post_ops, int index, float *scale, + mkldnn_alg_kind_t *alg, float *alpha, float *beta); + +/** @} */ + +/** @} */ + +/** @addtogroup c_api_memory Memory + * A primitive to describe and store data. + * + * The library supports various data types and formats. Memory hierarchy + * consists of three levels of abstraction: + * 1. **Memory descriptor** -- engine agnostic logical description of data + * (number of dimensions, dimensions themselves, and data type), and + * optionally the format/layout that describes the physical representation + * of data in memory. If the format is not known yet, one can pass + * #mkldnn_format_tag_any. This approach is used to allow compute-intensive + * primitives to specify the most appropriate format on their own with + * users required to reorder the data if the incoming format doesn't match + * the primitive's selection. Memory descriptor can be initialized with + * mkldnn_memory_desc_init_by_tag() or mkldnn_memory_desc_init_by_strides() + * functions, or by directly filling the mkldnn_memory_desc_t structure. + * The latter requires deep knowledge of how the physical data + * representation is mapped to the structure. + * The @ref understanding_memory_formats topic should shed some light on + * that. + * For the fully defined memory descriptors (i.e. where the format kind is + * not equal to #mkldnn_format_kind_any) a user can the size, using the + * mkldnn_memory_desc_get_size() function. As described in + * @ref understanding_memory_formats, the size of data sometimes cannot + * be computed as the product of dimensions times the size of the data + * type. So users are encouraged to use this function for better code + * portability. + * Two memory descriptors can be compared with mkldnn_memory_desc_equal(). + * The comparison is especially useful when checking whether a primitive + * requires reorder from the user's data format to the primitive's format. + * 2. **Memory** -- an engine-specific object that handles the data and its + * description (a memory descriptor). For CPU enigne, the data handle is + * simply a pointer to @c void. The data handle can be queried using + * mkldnn_memory_get_data_handle() and set using + * mkldnn_memory_set_data_handle(). The latter function always sets the + * memory in the padding region to zero, which is the invariant maintained + * by all the primitives in Intel MKL-DNN. + * See @ref understanding_memory_formats for more details. + * A memory can be created using mkldnn_memory_create() function. + * A memory can also be queried for the underlying memory descriptor and + * engine using mkldnn_memory_get_memory_desc() and + * mkldnn_memory_get_engine() functions. + * + * Along with ordinary memory with all dimensions being positive, Intel + * MKL-DNN supports *zero-volume* memory with one or more dimensions set to + * zero. This is to support the NumPy\* convention. + * If a *zero-volume* memory is passed to a primitive, the primitive does + * not perform any computations on this memory. For example: + * - Convolution with `(0 batch, 3 input channels, 13 height, 13 width)` + * source and `(16 output channels, 3 inputs, channel, 3 height, 3 width)` + * weights would produce `(0 batch, 16 output channels, 11 height, 11 width)` + * destination (assuming strides are `1` and paddings are zero) and perform + * zero multiply-add operations. + * - Concatenation of three memories of shapes `(3, 4, 13, 13)`, + * `(3, 0, 13, 13)`, and `(3, 1, 13, 13)` along the second axis would produce + * the output of the shape `(3, 5, 13, 13)`, effectively ignoring the second + * input (however, if the user created a concatenation primitive descriptor + * with three inputs they should also provide all three memories to the + * concatenation primitive, including the one with zero second dimension). + * - However, Intel MKL-DNN would return an error when attempting to create a + * convolution with *zero-volume* memory passed for weights because such a + * convolution is not well-defined: + * ~~~ + * dst(1, 16, 11, 11) <-- src(1, 0, 13, 13) (*) wei(16, 0, 3, 3) + * ~~~ + * Should the values in the destination be zeroes or just not accessed at + * all? Moreover, backward pass w.r.t. weights in such cases is also not + * well-defined. + * + * Data handle of *zero-volume* memory is never accessed and hence can be + * unset (NULL in case of CPU engine). + * + * @sa @ref understanding_memory_formats + * @{ */ + +/** Initializes a @p memory_desc memory descriptor using @p ndims, @p dims, @p + * data_type, and @p strides. + * + * The @p strides might be NULL, which means the order of physical dimensions + * is the same as the order of logical ones. + * + * @note The logical order of dimensions is defined by a primitive that + * consumes the memory. + */ +mkldnn_status_t MKLDNN_API mkldnn_memory_desc_init_by_strides( + mkldnn_memory_desc_t *memory_desc, int ndims, const mkldnn_dims_t dims, + mkldnn_data_type_t data_type, const mkldnn_dims_t strides); + +/** Initializes a @p memory_desc memory descriptor using @p ndims, @p dims, @p + * data_type, and format @p tag. + * + * @p tag can be #mkldnn_format_tag_any, which allows a primitive to define + * the appropriate memory format. In this case, the @p format_kind would be set + * to #mkldnn_format_kind_any */ +mkldnn_status_t MKLDNN_API mkldnn_memory_desc_init_by_tag( + mkldnn_memory_desc_t *memory_desc, int ndims, const mkldnn_dims_t dims, + mkldnn_data_type_t data_type, mkldnn_format_tag_t tag); + +/** Initializes a @p memory_desc for a given @p parent_memory_desc, with + * @p dims sizes and @p offsets. May fail if layout used does not allow + * obtain desired submemory. In this case consider using `extract` or `insert` + * primitive */ +mkldnn_status_t MKLDNN_API mkldnn_memory_desc_init_submemory( + mkldnn_memory_desc_t *memory_desc, + const mkldnn_memory_desc_t *parent_memory_desc, + const mkldnn_dims_t dims, const mkldnn_dims_t offsets); + +/** Compares two memory descriptors. + * @return 1 if the descriptors are the same. + * @return 0 if the descriptors are different. + * + * Use this function to identify whether a reorder is required between the + * two memories */ +int MKLDNN_API mkldnn_memory_desc_equal( + const mkldnn_memory_desc_t *lhs, + const mkldnn_memory_desc_t *rhs); + +/** Returns the size (in bytes) that is required for given @p memory_desc */ +size_t MKLDNN_API mkldnn_memory_desc_get_size( + const mkldnn_memory_desc_t *memory_desc); + +/** Creates a memory for given @p memory_desc and @p engine. Also sets handle + * to @p native_handle. + * The @p native_handle can: + * - point to the user allocated memory, i.e. valid handle. In this case the + * library doesn't own allocated memory. + * - be MKLDNN_NATIVE_HANDLE_ALLOCATE to ask the library to allocate and + * attach memory. In this case the library owns allocated memory. + * - be MKLDNN_NATIVE_HANDLE_NONE to create mkldnn_memory w/o attached memory. + */ +mkldnn_status_t MKLDNN_API mkldnn_memory_create(mkldnn_memory_t *memory, + const mkldnn_memory_desc_t *memory_desc, mkldnn_engine_t engine, + void *native_handle); + +/** Returns a @p memory_desc associated with @p memory. */ +mkldnn_status_t MKLDNN_API mkldnn_memory_get_memory_desc( + const_mkldnn_memory_t memory, + const mkldnn_memory_desc_t **memory_desc); + +/** Returns an @p engine associated with @p memory. */ +mkldnn_status_t MKLDNN_API mkldnn_memory_get_engine( + const_mkldnn_memory_t memory, mkldnn_engine_t *engine); + +/** For a @p memory, returns the data @p handle. + * + * For the CPU engine, the data handle is a pointer to the actual data. */ +mkldnn_status_t MKLDNN_API mkldnn_memory_get_data_handle( + const_mkldnn_memory_t memory, void **handle); + +/** For a @p memory, sets the data @p handle. */ +mkldnn_status_t MKLDNN_API mkldnn_memory_set_data_handle( + mkldnn_memory_t memory, void *handle); + +/** Deletes a @p memory. */ +mkldnn_status_t MKLDNN_API mkldnn_memory_destroy(mkldnn_memory_t memory); + +/** @} */ + +/** @addtogroup c_api_reorder Reorder + * A primitive to copy data between memory formats. + * @{ */ + +/** Initializes a @p reorder_primitive_desc using the description of the source + * (@p src_engine and @p src_md) and destination (@p dst_engine and @p dst_md) + * memory, and an @p attr attribute. + * + * Inputs: + * - input (#mkldnn_query_src_md, 0) + * + * Outputs: + * - output (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_reorder_primitive_desc_create( + mkldnn_primitive_desc_t *reorder_primitive_desc, + mkldnn_engine_t src_engine, const mkldnn_memory_desc_t *src_md, + mkldnn_engine_t dst_engine, const mkldnn_memory_desc_t *dst_md, + const_mkldnn_primitive_attr_t attr); + +/** @} */ + +/** @addtogroup c_api_concat Concat + * A primitive to concatenate data by arbitrary dimension. + * @{ */ + +/** Creates out-of-place @p concat_primitive_desc for concatenation of @p n + * inputs by @p concat_dimension with resulting @p output_desc memory + * descriptor. @p output_desc can be NULL or specified with the + * #mkldnn_format_kind_any format kind -- in this case, the appropriate memory + * format would be chosen automatically. + * + * Inputs: + * - input 0 (#mkldnn_query_src_md, 0) + * - input 1 (#mkldnn_query_src_md, 1) + * - ... + * - input @p n - 1 (#mkldnn_query_src_md, @p n - 1) + * + * Outputs: + * - output (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_concat_primitive_desc_create( + mkldnn_primitive_desc_t *concat_primitive_desc, + const mkldnn_memory_desc_t *dst_md, + int n, int concat_dimension, + const mkldnn_memory_desc_t *src_mds, + const_mkldnn_primitive_attr_t attr, + mkldnn_engine_t engine); + +/** @} */ + +/** @addtogroup c_api_sum Sum + * A primitive to sum data. + * @{ */ + +/** Creates out-of-place @p sum_primitive_desc for sum of @p n + * inputs multiplied by scale with resulting @p output_desc memory + * descriptor. @p output_desc can be NULL or specified with the + * #mkldnn_format_kind_any format kind -- in this case, the appropriate memory + * format would be chosen automatically. + * + * Inputs: + * - src 0 (#mkldnn_query_src_md, 0) + * - src 1 (#mkldnn_query_src_md, 1) + * - ... + * - src @p n - 1 (#mkldnn_query_src_md, @p n - 1) + * + * Outputs: + * - output (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_sum_primitive_desc_create( + mkldnn_primitive_desc_t *sum_primitive_desc, + const mkldnn_memory_desc_t *dst_mds, + int n, const float *scales, + const mkldnn_memory_desc_t *src_mds, + const_mkldnn_primitive_attr_t attr, + mkldnn_engine_t engine); + +/** @} */ + +/** @addtogroup c_api_convolution Convolution + * A primitive to compute convolution using different algorithms. + * + * \f[dst[n][oc][oh][ow] = + * \sum_{kw=0}^{KW}\sum_{kh=0}^{KH}\sum_{ic=0}^{IC} + * src[n][ic][oh \cdot s_h - p_l[0] + kh][ow \cdot s_w - p_r[1] + kw] + * \cdot weights[g][oc][ic][kh][kw] + * + bias[g][oc],\f] + * + * where size of output spatial domain is given by + * \f$ OH = \left\lfloor{\frac{IH - KH + p_l[0] + p_r[0]}{s_h}} + * \right\rfloor + 1\f$, + * \f$ OW = \left\lfloor{\frac{IW - KW + p_l[1] + p_r[1]}{s_w}} + * \right\rfloor + 1\f$, + * + * and summation is carried over input channels \f$ic\f$ in + * group \f$g\f$, and \f$s_h, s_w\f$ are @p strides and + * \f$p_l, p_r\f$ are @p padding_l and @p padding_r. + * @{ */ + +/** Initializes a convolution descriptor @p conv_desc for forward propagation + * using @p prop_kind (possible values are #mkldnn_forward_training and + * #mkldnn_forward_inference), @p alg_kind, memory descriptors, @p strides, @p + * padding_l, @p padding_r, and @p padding_kind. In order to create a + * convolution without bias, @p bias_desc should either be @c NULL or point to + * a descriptor with memory format kind equal to #mkldnn_format_kind_undef. + * + * @note If @p padding_r is @c NULL, the padding is supposed to be symmetric. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * - bias (#mkldnn_query_weights_md, 1), if created with bias + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_convolution_forward_desc_init( + mkldnn_convolution_desc_t *conv_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t padding_l, const mkldnn_dims_t padding_r, + mkldnn_padding_kind_t padding_kind); + +/** Initializes a dilated convolution descriptor @p conv_desc for forward + * propagation using @p prop_kind (possible values are #mkldnn_forward_training + * and #mkldnn_forward_inference), @p alg_kind, memory descriptors, @p strides, + * @p dilates, @p padding_l, @p padding_r, and @p padding_kind. + * In order to create a dilated convolution without bias, @p bias_desc + * should either be @c NULL or point to a descriptor with memory format kind + * equals #mkldnn_format_kind_undef. + * + * @note If @p padding_r is @c NULL, the padding is supposed to be symmetric. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * - bias (#mkldnn_query_weights_md, 1), if created with bias + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_dilated_convolution_forward_desc_init( + mkldnn_convolution_desc_t *conv_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t dilates, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** Initializes a convolution descriptor @p conv_desc for backward propagation + * with respect to data using @p alg_kind, memory descriptors, @p strides, @p + * padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_convolution_backward_data_desc_init( + mkldnn_convolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t padding_l, const mkldnn_dims_t padding_r, + mkldnn_padding_kind_t padding_kind); + +/** Initializes a dilated convolution descriptor @p conv_desc for backward + * propagation with respect to data using @p alg_kind, memory descriptors, @p + * strides, @p dilates @p padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_dilated_convolution_backward_data_desc_init( + mkldnn_convolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t dilates, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** Initializes a convolution descriptor @p conv_desc for backward propagation + * with respect to weights using @p alg_kind, memory descriptors, @p strides, + * @p padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_weights (#mkldnn_query_diff_weights_md, 0) + * - diff_bias (#mkldnn_query_diff_weights_md, 1), if created with bias + */ +mkldnn_status_t MKLDNN_API mkldnn_convolution_backward_weights_desc_init( + mkldnn_convolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *diff_weights_desc, + const mkldnn_memory_desc_t *diff_bias_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t padding_l, const mkldnn_dims_t padding_r, + mkldnn_padding_kind_t padding_kind); + +/** Initializes a convolution descriptor @p conv_desc for backward propagation + * with respect to weights using @p alg_kind, memory descriptors, @p strides, + * @p dilates @p padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_weights (#mkldnn_query_diff_weights_md, 0) + * - diff_bias (#mkldnn_query_diff_weights_md, 1), if created with bias + */ +mkldnn_status_t MKLDNN_API +mkldnn_dilated_convolution_backward_weights_desc_init( + mkldnn_convolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *diff_weights_desc, + const mkldnn_memory_desc_t *diff_bias_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t dilates, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** @} */ + +/** @addtogroup c_api_deconvolution Deconvolution + * A primitive to compute deconvolution using different algorithms. + * + * @{ */ + + +/** Initializes a deconvolution descriptor @p deconv_desc for forward + * propagation using @p prop_kind (possible values are #mkldnn_forward_training + * and #mkldnn_forward_inference), @p alg_kind, memory descriptors, @p strides, + * @p padding_l, @p padding_r, and @p padding_kind. In order to create a + * deconvolution without bias, @p bias_desc should either be @c NULL or point to + * a descriptor with memory format kind equals #mkldnn_format_kind_undef. + * + * @note If @p padding_r is @c NULL, the padding is supposed to be symmetric. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * - bias (#mkldnn_query_weights_md, 1), if created with bias + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_deconvolution_forward_desc_init( + mkldnn_deconvolution_desc_t *conv_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t padding_l, const mkldnn_dims_t padding_r, + mkldnn_padding_kind_t padding_kind); + +/** Initializes a dilated deconvolution descriptor @p deconv_desc for forward + * propagation using @p prop_kind (possible values are #mkldnn_forward_training + * and #mkldnn_forward_inference), @p alg_kind, memory descriptors, @p strides, + * @p dilates, @p padding_l, @p padding_r, and @p padding_kind. In order to + * create a dilated deconvolution without bias, @p bias_desc should either be + * @c NULL or point to a descriptor with memory format kind equal + * #mkldnn_format_kind_undef. + * + * @note If @p padding_r is @c NULL, the padding is supposed to be symmetric. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * - bias (#mkldnn_query_weights_md, 1), if created with bias + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_dilated_deconvolution_forward_desc_init( + mkldnn_deconvolution_desc_t *conv_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t dilates, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** Initializes a deconvolution descriptor @p conv_desc for backward propagation + * with respect to data using @p alg_kind, memory descriptors, @p strides, @p + * padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_deconvolution_backward_data_desc_init( + mkldnn_deconvolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t padding_l, const mkldnn_dims_t padding_r, + mkldnn_padding_kind_t padding_kind); + +/** Initializes a dilated deconvolution descriptor @p conv_desc for backward + * propagation with respect to data using @p alg_kind, memory descriptors, @p + * strides, @p dilates, @p padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_dilated_deconvolution_backward_data_desc_init( + mkldnn_deconvolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t dilates, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** Initializes a deconvolution descriptor @p conv_desc for backward propagation + * with respect to weights using @p alg_kind, memory descriptors, @p strides, + * @p padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_weights (#mkldnn_query_diff_weights_md, 0) + * - diff_bias (#mkldnn_query_diff_weights_md, 1), if created with bias + */ +mkldnn_status_t MKLDNN_API mkldnn_deconvolution_backward_weights_desc_init( + mkldnn_deconvolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *diff_weights_desc, + const mkldnn_memory_desc_t *diff_bias_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t padding_l, const mkldnn_dims_t padding_r, + mkldnn_padding_kind_t padding_kind); + +/** Initializes a dilated deconvolution descriptor @p conv_desc for backward + * propagation with respect to weights using @p alg_kind, memory descriptors, + * @p strides, @p dilates, @p padding_l, @p padding_r, and @p padding_kind. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_weights (#mkldnn_query_diff_weights_md, 0) + * - diff_bias (#mkldnn_query_diff_weights_md, 1), if created with bias + */ +mkldnn_status_t MKLDNN_API mkldnn_dilated_deconvolution_backward_weights_desc_init( + mkldnn_deconvolution_desc_t *conv_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *diff_weights_desc, + const mkldnn_memory_desc_t *diff_bias_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t dilates, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** @} */ + +/** @addtogroup c_api_shuffle Shuffle + * A primitive to shuffle data along the axis. + * @{ */ + +/** Initializes a @p shuffle_desc for forward propagation using @p prop_kind, + * memory descriptor @p data_desc, @p axis, and @p group_size. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + * + */ +mkldnn_status_t MKLDNN_API mkldnn_shuffle_forward_desc_init( + mkldnn_shuffle_desc_t *shuffle_desc, mkldnn_prop_kind_t prop_kind, + const mkldnn_memory_desc_t *data_desc, int axis, + mkldnn_dim_t group_size); + +/** Initializes a @p shuffle_desc for backward propagation using memory + * descriptor @p diff_data_desc, @p axis, and @p group_size. + * + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + * + */ +mkldnn_status_t MKLDNN_API mkldnn_shuffle_backward_desc_init( + mkldnn_shuffle_desc_t *shuffle_desc, + const mkldnn_memory_desc_t *diff_data_desc, int axis, + mkldnn_dim_t group_size); + +/** @} */ + +/** @addtogroup c_api_eltwise Eltwise + * A primitive to compute element-wise operations like parametric rectifier + * linear unit (ReLU). + * + * Both forward and backward passes support in-place operation; that is, src + * and dst point to the same memory for forward pass, and diff_dst and diff_src + * point to the same memory for backward pass. + * + * @warning Because the original src is required for backward pass, in-place + * forward pass in general cannot be applied during training. However, for some + * kinds of element-wise operations (namely ReLU with alpha parameter equals 0), + * dst and src can be interchangeable for the backward pass, which enables + * performing in-place forward even for training. + * + * @{ */ + +/** Initializes an @p eltwise_desc for forward propagation using @p prop_kind + * (possible values are #mkldnn_forward_training and #mkldnn_forward_inference), + * @p alg_kind algorithm, memory descriptor @p data_desc, @p alpha, and + * @p beta parameters. + * + * @sa mkldnn_eltwise_desc_t for details. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_eltwise_forward_desc_init( + mkldnn_eltwise_desc_t *eltwise_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *data_desc, + float alpha, float beta); + +/** Initializes an @p eltwise_desc for backward propagation using @p alg_kind + * algorithm memory descriptors @p diff_data_desc and @p data_desc, and the + * @p alpha and @p beta parameters. + * + * @sa mkldnn_eltwise_desc_t for details. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_eltwise_backward_desc_init( + mkldnn_eltwise_desc_t *eltwise_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_data_desc, + const mkldnn_memory_desc_t *data_desc, float alpha, float beta); + +/** @} */ + +/** @addtogroup c_api_softmax Softmax + * A primitive to perform softmax. + * + * \f[dst[u][c][in] = + * \frac{\exp(src[ou][c][in]) - \max\limits_{c}(src[ou][c][in])} + * {\sum\limits_{c}\{\exp(src[ou][c][in]) + * - \max\limits_{c}(src[ou][c][in])\}},\f] + * + * where \f$ou, iu\f$ are outer and inner sizes repectively, defined + * by @p data_desc.dims and @p softmax_axis. + * @{ */ + +/** Initializes a @p softmax_desc for forward propagation using @p prop_kind + * (possible values are #mkldnn_forward_training and #mkldnn_forward_inference) + * and memory descriptor @p data_desc. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_softmax_forward_desc_init( + mkldnn_softmax_desc_t *softmax_desc, mkldnn_prop_kind_t prop_kind, + const mkldnn_memory_desc_t *data_desc, int softmax_axis); + +/** Initializes a @p softmax_desc for backward propagation using memory + * descriptors @p diff_desc and @p data_desc. + * + * Inputs: + * - dst (#mkldnn_query_dst_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_softmax_backward_desc_init( + mkldnn_softmax_desc_t *softmax_desc, + const mkldnn_memory_desc_t *diff_desc, + const mkldnn_memory_desc_t *data_desc, int softmax_axis); + +/** @} */ + +/** @addtogroup c_api_pooling Pooling + * A primitive to perform max or average pooling. + * + * Max pooling: + * \f[dst[n][oc][oh][ow] = + * \max\limits_{kw,kh} + * (src[n][ic][oh \cdot s_h - p_l[0] + kh][ow \cdot s_w - p_r[1] + kw]),\f] + * + * Average pooling: + * \f[dst[n][oc][oh][ow] = + * \frac{1}{KW \cdot KH}\sum\limits_{kw,kh} + * src[n][ic][oh \cdot s_h - p_l[0] + kh][ow \cdot s_w - p_r[1] + kw],\f] + * + * where \f$p_l, p_r\f$ are @p padding_l and @p padding_r respectively, and + * output spatial dimensions are calculated similarly to how they are done in + * convolution. + * + * During training, max pooling requires a workspace on forward + * (#mkldnn_forward_training) and backward (#mkldnn_backward) passes to + * save indices where maximum was found. The workspace layout is opaque, and + * the indices cannot be restored from it. However, one can use backward + * pooling to perform up-sampling (used in some detection topologies). + * + * @{ */ + +/** Initializes a pooling descriptor @p pool_desc for forward propagation using + * @p prop_kind (possible values are #mkldnn_forward_training and + * #mkldnn_forward_inference), @p alg_kind, memory descriptors, and pooling + * parameters in the spatial domain: @p strides, @p kernel sizes, @p padding_l, + * @p padding_r, and @p padding_kind. + * + * @note If @p padding_r is @c NULL, the padding is supposed to be symmetric. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + * - workspace (#mkldnn_query_workspace_md, 0), + * if @p alg_kind = #mkldnn_pooling_max and + * @p prop_kind = #mkldnn_forward_training + */ +mkldnn_status_t MKLDNN_API mkldnn_pooling_forward_desc_init( + mkldnn_pooling_desc_t *pool_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t kernel, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** Initializes a pooling descriptor @p pool_desc for backward propagation + * using @p alg_kind, memory descriptors, and pooling parameters in the spatial + * domain: @p strides, @p kernel sizes, @p padding_l, @p padding_r, and @p + * padding_kind. + * + * @note If @p padding_r is @c NULL, the padding is supposed to be symmetric. + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - workspace (#mkldnn_query_workspace_md, 0), + * if @p alg_kind = #mkldnn_pooling_max + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_pooling_backward_desc_init( + mkldnn_pooling_desc_t *pool_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_src_desc, + const mkldnn_memory_desc_t *diff_dst_desc, const mkldnn_dims_t strides, + const mkldnn_dims_t kernel, const mkldnn_dims_t padding_l, + const mkldnn_dims_t padding_r, mkldnn_padding_kind_t padding_kind); + +/** @} */ + +/** @addtogroup c_api_lrn LRN + * A primitive to perform local response normalization (LRN) across or within + * channels. + * + * LRN accross channels: + * \f[dst[n][c][h][w] = \left\{k + \frac{\alpha}{n_{l}} + * \sum\limits_{i=-(n_{l}-1)/2}^{(n_{l}+1)/2} + * (src[n][c+i][h][w])^2\right\}^{-\beta} + * src[n][c][h][w],\f] + * + * LRN within channels: + * \f[dst[n][c][h][w] = \left\{k + \frac{\alpha}{n_{l}} + * \sum\limits_{i=-(n_{l}-1)/2}^{(n_{l}+1)/2} + * (src[n][c][h+i][w+i])^2\right\}^{-\beta} + * src[n][c][h][w],\f] + * + * where \f$n_{l}\f$ is the @p local_size. + * + * During training, LRN might or might not require a workspace on forward + * (#mkldnn_forward_training) and backward (#mkldnn_backward) passes. The + * behavior is implementation specific. Optimized implementations typically + * require a workspace and use it to save some intermediate results from the + * forward pass that accelerate computations on the backward pass. + * + * To check whether a workspace is required, query the LRN primitive descriptor + * for the workspace (#mkldnn_query_workspace_md). Success indicates that the + * workspace is required and its description will be returned. + * @sa mkldnn_primitive_desc_query and mkldnn_primitive_desc_query_pd + * + * @{ */ + +/** Initializes an @p lrn_desc for forward propagation using @p prop_kind + * (possible values are #mkldnn_forward_training and #mkldnn_forward_inference), + * @p alg_kind, memory descriptor @p data_desc, and regularization + * parameters @p local_size, @p alpha, @p beta, and @p k. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + * - workspace (#mkldnn_query_workspace_md, 0), + * if the underlying implementation requires + */ +mkldnn_status_t MKLDNN_API mkldnn_lrn_forward_desc_init( + mkldnn_lrn_desc_t *lrn_desc, mkldnn_prop_kind_t prop_kind, + mkldnn_alg_kind_t alg_kind, const mkldnn_memory_desc_t *data_desc, + mkldnn_dim_t local_size, float alpha, float beta, float k); + +/** Initializes an @p lrn_desc for backward propagation using @p alg_kind, + * memory descriptors @p data_desc and @p diff_data_desc, and regularization + * parameters @p local_size, @p alpha, @p beta, and @p k. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - workspace (#mkldnn_query_workspace_md, 0), + * if the underlying implementation requires + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_lrn_backward_desc_init( + mkldnn_lrn_desc_t *lrn_desc, mkldnn_alg_kind_t alg_kind, + const mkldnn_memory_desc_t *diff_data_desc, + const mkldnn_memory_desc_t *data_desc, mkldnn_dim_t local_size, + float alpha, float beta, float k); + +/** @} */ + +/** @addtogroup c_api_batch_normalization Batch Normalization + * A primitive to perform batch normalization. + * + * \f[dst[n][c][h][w] = \gamma[c] \frac{src[n][c][h][w] - \mu[c]} + * {\sqrt{\sigma[c] + eps}} + \beta[c],\f] + * + * where \f$\gamma[c], \beta[c]\f$ are weights and bias for a channel and, + * + * \f$\mu[c] = \frac{1}{NHW} \sum\limits_{whn} src[n][c][h][w]\f$, + * \f$\sigma[c] = \frac{1}{NHW} \sum\limits_{whn} + * (src[n][c][h][w] - \mu[c])^2\f$, + * + * and @c eps is a constant to improve numerical stability. + * + * Both forward and backward passes support in-place operation; that is, src + * and dst point to the same memory for forward pass, and diff_dst and diff_src + * point to the same memory for backward pass. + * + * Batch normalization supports different flavors controlled by + * mkldnn_batch_normalization_desc_t. For example, batch normalization can + * compute the mean and variance on its own or take them as inputs. It can + * either perform scaling and shifting using gamma and beta parameters or not. + * Optionally it can also perform a fused ReLU, which in case of training would + * also require a workspace. + * + * @sa mkldnn_batch_normalization_desc_t + * @{ */ + +/** Initializes a batch normalization descriptor @p bnrm_desc for forward + * propagation using @p prop_kind (possible values are + * #mkldnn_forward_training and #mkldnn_forward_inference), memory descriptor + * @p data_desc, normalization parameter @p epsilon, and @p flags set using bit + * flags of type mkldnn_batch_normalization_desc_t. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - mean (#mkldnn_query_src_md, 1), + * if #mkldnn_use_global_stats bit-flags is set in @p flags + * - variance (#mkldnn_query_src_md, 2), + * if #mkldnn_use_global_stats bit-flags is set in @p flags + * - scale_and_shift (#mkldnn_query_weights_md, 0), + * if #mkldnn_use_scaleshift bit-flags is set in @p flags + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + * - mean (#mkldnn_query_dst_md, 1), + * if #mkldnn_use_global_stats bit-flags is not set in @p flags + * @p prop_kind = #mkldnn_forward_training + * - variance (#mkldnn_query_dst_md, 2), + * if #mkldnn_use_global_stats bit-flags is not set in @p flags + * and @p prop_kind = #mkldnn_forward_training + * - workspace (#mkldnn_query_workspace_md, 0), + * if #mkldnn_fuse_bn_relu bit-flags is set in @p flags + * and @p prop_kind = #mkldnn_forward_training + * + * @note In-place operation is supported; that is, dst points to the same memory + * as src. + * + * @sa mkldnn_batch_normalization_desc_t + */ +mkldnn_status_t MKLDNN_API mkldnn_batch_normalization_forward_desc_init( + mkldnn_batch_normalization_desc_t *bnrm_desc, + mkldnn_prop_kind_t prop_kind, const mkldnn_memory_desc_t *data_desc, + float epsilon, unsigned flags); + +/** Initializes a batch normalization descriptor @p bnrm_desc for backward + * propagation with respect to data and scale-shift parameters using memory + * descriptors @p data_desc and @p diff_data_desc, normalization parameter + * @p epsilon, and @p flags set using bit flags of type + * mkldnn_batch_normalization_desc_t. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - mean (#mkldnn_query_src_md, 1) + * - variance (#mkldnn_query_src_md, 2) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - scale_and_shift (#mkldnn_query_weights_md, 0), + * if #mkldnn_use_scaleshift bit-flags is set in @p flags + * - workspace (#mkldnn_query_workspace_md, 0), + * if #mkldnn_fuse_bn_relu bit-flags is set in @p flags + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + * - diff_scale_and_shift (#mkldnn_query_diff_weights_md, 0), + * if #mkldnn_use_scaleshift bit-flags is set in @p flags + * and @p prop_kind = #mkldnn_backward + * + * @note in-place operation is supported, + * i.e. diff_src points to the same memory as diff_dst. + * + * @sa mkldnn_batch_normalization_desc_t + */ +mkldnn_status_t MKLDNN_API mkldnn_batch_normalization_backward_desc_init( + mkldnn_batch_normalization_desc_t *bnrm_desc, + mkldnn_prop_kind_t prop_kind, + const mkldnn_memory_desc_t *diff_data_desc, + const mkldnn_memory_desc_t *data_desc, + float epsilon, unsigned flags); + +/** @} */ + +/** @addtogroup c_api_inner_product Inner product + * A primitive to compute an inner product. + * + * Inner product layer is also known as fully connected layer. + * With spatial dimension: + * + * \f[dst[n][oc] = \sum\limits_{ic, kh, kw} + * src[n][ic][kh][kw] \cdot weights[oc][ic][kh][kw] + * + bias[oc]\f] + * @{ */ + +/** Initializes an inner product descriptor @p ip_desc for forward propagation + * using @p prop_kind (possible values are #mkldnn_forward_training and + * #mkldnn_forward_inference) and memory descriptors. In order to create an + * inner product without bias, @p bias_desc should be either @c NULL or a + * pointer to a descriptor with memory format kind equals + * #mkldnn_format_kind_undef. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * - bias (#mkldnn_query_weights_md, 1), if created with bias + * + * Outputs: + * - dst (#mkldnn_query_dst_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_inner_product_forward_desc_init( + mkldnn_inner_product_desc_t *ip_desc, mkldnn_prop_kind_t prop_kind, + const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_desc); + +/** Initializes an inner product descriptor @p ip_desc for backward propagation + * with respect to data using memory descriptors. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * - weights (#mkldnn_query_weights_md, 0) + * + * Outputs: + * - diff_src (#mkldnn_query_diff_src_md, 0) + */ +mkldnn_status_t MKLDNN_API mkldnn_inner_product_backward_data_desc_init( + mkldnn_inner_product_desc_t *ip_desc, + const mkldnn_memory_desc_t *diff_src_desc, + const mkldnn_memory_desc_t *weights_desc, + const mkldnn_memory_desc_t *diff_dst_desc); + +/** Initializes an inner product descriptor @p ip_desc for backward propagation + * with respect to weights using memory descriptors. + * + * @note Memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src (#mkldnn_query_src_md, 0) + * - diff_dst (#mkldnn_query_diff_dst_md, 0) + * + * Outputs: + * - diff_weights (#mkldnn_query_diff_weights_md, 0) + * - diff_bias (#mkldnn_query_diff_weights_md, 1), if created with bias + */ +mkldnn_status_t MKLDNN_API mkldnn_inner_product_backward_weights_desc_init( + mkldnn_inner_product_desc_t *ip_desc, + const mkldnn_memory_desc_t *src_desc, + const mkldnn_memory_desc_t *diff_weights_desc, + const mkldnn_memory_desc_t *diff_bias_desc, + const mkldnn_memory_desc_t *diff_dst_desc); + +/** @} */ + +/** @addtogroup c_api_rnn RNN + * A primitive to compute the common recurrent layer. + * @todo add additional description for the group + * @{ */ + +/** + * Initializes a recurrent cell descriptor @p rnn_cell_desc + * using @p rnn_cell_desc, @p kind (possible values are + * #mkldnn_vanilla_rnn, #mkldnn_vanilla_lstm, #mkldnn_vanilla_gru, and + * #mkldnn_gru_linear_before_reset), + * @p f (possible values are #mkldnn_eltwise_relu and + * #mkldnn_eltwise_tanh), @p flags, @p alpha, and @p clipping. + */ +mkldnn_status_t MKLDNN_API mkldnn_rnn_cell_desc_init( + mkldnn_rnn_cell_desc_t *rnn_cell_desc, + mkldnn_alg_kind_t kind, mkldnn_alg_kind_t f, + unsigned int flags, float alpha, float clipping); + +/** Returns the number of gates of a particular @p rnn_cell_desc. */ +int MKLDNN_API mkldnn_rnn_cell_get_gates_count( + const mkldnn_rnn_cell_desc_t *rnn_cell_desc); + +/** Returns the number of states of a particular @p rnn_cell_desc. */ +int MKLDNN_API mkldnn_rnn_cell_get_states_count( + const mkldnn_rnn_cell_desc_t *rnn_cell_desc); + +/** Sets quantization @p scale and @p shift for RNN data tensors. + * For performance reasons, low precision configuration of RNN primitive + * expects input activations to have unsigned int8 data type. Scale and shift + * used to quantize floating point data to unsigned integer must be passed to + * RNN primitive using attributes. + * Example usage: + * @code + * // rnn parameters + * int l = 2, t = 2, mb = 32, sic = 32, slc = 32, dic = 32, dlc = 32; + * // activations quantization parameters + * float scale = ..., shift = ..; + * + * mkldnn_primitive_attr_t rnn_attr; + * // create default attributes + * mkldnn_primitive_attr_create(&rnn_attr); + * + * // set scale and shift for int8 quantization of activation + * mkldnn_primitive_attr_set_rnn_data_qparams(rnn_attr, scale, shift); + * + * // create & configure rnn op_desc + * mkldnn_rnn_desc_t rnn_d; + * mkldnn_primitive_desc_t rnn_pd; + * mkldnn_primitive_desc_create(&rnn_pd, &rnn_d, attr, engine, NULL); + * @endcode + * @note + * Quantization scale and shift are common for src_layer, src_iter, + * dst_iter and dst_layer. + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_set_rnn_data_qparams( + mkldnn_primitive_attr_t attr, const float scale, const float shift); + +/** Sets quantization scales @p weights_scales for RNN weights tensors. + * Low precision configuration of RNN primitive expects input weights to have + * signed int8 data type. Scales used to quantize floating point data + * to signed integer must be passed to RNN primitive using attributes. + * The @p mask argument defines correspondence between output tensor dimensions + * and the @p weights_scales array. Set i-th bit of @p mask to 1 to use + * dedicated scaling factor for each slice of the output tensor over i-th + * dimension. Set @p mask to 0 to use common scaling factor for the whole output + * tensor. Example usage: + * @code + * // rnn parameters + * int l = 2, t = 2, mb = 32, sic = 32, slc = 32, dic = 32, dlc = 32; + * // unique output scales per output channel + * float weights_scales[dic * n_gates] = { ... }; + * // mask that specifies last two dimensions of ldigo format + * int mask = 0x3; + * + * mkldnn_primitive_attr_t attr; + * // create default attributes + * mkldnn_primitive_attr_create(&attr); + * + * // set output channel-wise weights scales + * mkldnn_primitive_attr_set_rnn_weights_qparams(attr, dic * n_gates, mask, + * weights_scales); + * + * // create & configure rnn op_desc + * mkldnn_rnn_desc_t rnn_d; + * mkldnn_primitive_desc_t rnn_pd; + * mkldnn_primitive_desc_create(&rnn_pd, &rnn_d, attr, engine, NULL); + * @endcode + * @note + * The dimension order is always native and does not depend on the actual + * layout used. For example, 5 dimensional weights always have + * (l, d, i, g, o) logical dimension ordering. + * @note + * Quantization sales are common for weights_layer and weights_iteration + * @note + * There is no way to check that @p count corresponds to @p mask until an + * actual primitive descriptor is created, so it is user's responsibility + * to set proper values. The following formula must be held: + * + * \f[count = \prod\limits_{d \in mask} output.dims[d]\f] + */ +mkldnn_status_t MKLDNN_API mkldnn_primitive_attr_set_rnn_weights_qparams ( + mkldnn_primitive_attr_t attr, mkldnn_dim_t count, int mask, + const float *weights_scales); + +/** Initializes a rnn descriptor @p rnn_desc for forward propagation + * using @p prop_kind, @p rnn_cell_desc, @p direction, and memory descriptors. + * @note If @p prop_kind equals #mkldnn_forward_training, you must query a + * workspace memory descriptor before creating the primitive. + * + * @p src_iter_desc, @p bias_desc, and @p dst_iter_desc are allowed to either be + * @c NULL or point to a zero memory descriptor, which would indicate that the + * RNN primitive should not use them. + * + * @note All memory descriptors except @p src_iter_desc are allowed to be + * initialized with #mkldnn_format_kind_any value of @p format_kind. + * + * Inputs: + * - src_layer (#mkldnn_query_src_md, 0) + * - src_iter (#mkldnn_query_src_md, 1), if used + * - weights_layer (#mkldnn_query_weights_md, 0) + * - weights_iter (#mkldnn_query_weights_md, 1) + * - bias (#mkldnn_query_weights_md, 2), if used + * + * Outputs: + * - dst_layer (#mkldnn_query_dst_md, 0) + * - dst_iter (#mkldnn_query_dst_md, 1), if used + * - workspace (#mkldnn_query_workspace_md, 0), + * if @p prop_kind equals #mkldnn_forward_training + */ +mkldnn_status_t MKLDNN_API mkldnn_rnn_forward_desc_init( + mkldnn_rnn_desc_t *rnn_desc, mkldnn_prop_kind_t prop_kind, + const mkldnn_rnn_cell_desc_t *rnn_cell_desc, + const mkldnn_rnn_direction_t direction, + const mkldnn_memory_desc_t *src_layer_desc, + const mkldnn_memory_desc_t *src_iter_desc, + const mkldnn_memory_desc_t *weights_layer_desc, + const mkldnn_memory_desc_t *weights_iter_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_layer_desc, + const mkldnn_memory_desc_t *dst_iter_desc); + +/** Initializes a rnn descriptor @p rnn_desc for backward propagation + * using @p prop_kind, @p rnn_cell_desc, @p direction, and memory descriptors. + * + * @note All memory descriptors are allowed to be initialized with + * #mkldnn_format_kind_any value of @p format_kind. + * + * @p src_iter_desc (simultaneously with @p diff_src_iter_desc), + * @p bias_desc (simultaneously with @p diff_bias_desc), and + * @p dst_iter_desc (simultaneously with @p diff_src_iter_desc) are allowed to + * either be @c NULL or point to a zero memory descriptor, which would indicate + * that the RNN primitive should not use them. + * + * Inputs: + * - src_layer (#mkldnn_query_src_md, 0) + * - src_iter (#mkldnn_query_src_md, 1), if used + * - weights_layer (#mkldnn_query_weights_md, 0) + * - weights_iter (#mkldnn_query_weights_md, 1) + * - bias (#mkldnn_query_weights_md, 2), if used + * - dst_layer (#mkldnn_query_dst_md, 0) + * - dst_iter (#mkldnn_query_dst_md, 1), if used + * - diff_dst_layer (#mkldnn_query_diff_dst_md, 0) + * - diff_dst_iter (#mkldnn_query_diff_dst_md, 1), if used + * - workspace (#mkldnn_query_workspace_md, 0) + * + * Outputs: + * - diff_src_layer (#mkldnn_query_diff_src_md, 0) + * - diff_src_iter (#mkldnn_query_diff_src_md, 1), if used + * - diff_weights_layer (#mkldnn_query_diff_weights_md, 0) + * - diff_weights_iter (#mkldnn_query_diff_weights_md, 1) + * - diff_bias (#mkldnn_query_diff_weights_md, 2), if used + */ +mkldnn_status_t MKLDNN_API mkldnn_rnn_backward_desc_init( + mkldnn_rnn_desc_t *rnn_desc, mkldnn_prop_kind_t prop_kind, + const mkldnn_rnn_cell_desc_t *rnn_cell_desc, + const mkldnn_rnn_direction_t direction, + const mkldnn_memory_desc_t *src_layer_desc, + const mkldnn_memory_desc_t *src_iter_desc, + const mkldnn_memory_desc_t *weights_layer_desc, + const mkldnn_memory_desc_t *weights_iter_desc, + const mkldnn_memory_desc_t *bias_desc, + const mkldnn_memory_desc_t *dst_layer_desc, + const mkldnn_memory_desc_t *dst_iter_desc, + const mkldnn_memory_desc_t *diff_src_layer_desc, + const mkldnn_memory_desc_t *diff_src_iter_desc, + const mkldnn_memory_desc_t *diff_weights_layer_desc, + const mkldnn_memory_desc_t *diff_weights_iter_desc, + const mkldnn_memory_desc_t *diff_bias_desc, + const mkldnn_memory_desc_t *diff_dst_layer, + const mkldnn_memory_desc_t *diff_dst_iter_desc); + +/** @} */ + +/** @} */ + +/** @addtogroup c_api_engine Engine operations + * @{ */ + +/** Returns the number of engines of a particular @p kind. */ +size_t MKLDNN_API mkldnn_engine_get_count(mkldnn_engine_kind_t kind); + +/** Creates an @p engine of particular @p kind and @p index. */ +mkldnn_status_t MKLDNN_API mkldnn_engine_create(mkldnn_engine_t *engine, + mkldnn_engine_kind_t kind, size_t index); + +/** Returns the kind of an @p engine. */ +mkldnn_status_t MKLDNN_API mkldnn_engine_get_kind(mkldnn_engine_t engine, + mkldnn_engine_kind_t *kind); + +/** Destroys an @p engine. */ +mkldnn_status_t MKLDNN_API mkldnn_engine_destroy(mkldnn_engine_t engine); + +/** @} */ + +/** @addtogroup c_api_stream Execution stream operations + * @{ */ + +/** Creates an execution @p stream for @p engine and with @p flags. */ +mkldnn_status_t MKLDNN_API mkldnn_stream_create(mkldnn_stream_t *stream, + mkldnn_engine_t engine, unsigned flags); + +/** Destroys an execution @p stream. */ +mkldnn_status_t MKLDNN_API mkldnn_stream_destroy(mkldnn_stream_t stream); + +/** @} */ + +/** @addtogroup c_api_service Service functions + * @{ */ + +/** Sets verbosity level (print information to stdout). + * Possible levels are: + * - 0 -- no verbose output (default) + * - 1 -- primitive information at execution + * - 2 -- primitive information at creation and execution + * + * @note + * Dumping information might affect performance. + * This setting overrides the MKLDNN_VERBOSE environment variable. */ +mkldnn_status_t MKLDNN_API mkldnn_set_verbose(int level); + +/** Enables or disables dumping of JIT-generated code. + * The enable parameter can be: + * - 0 -- disable + * - any other value -- enable + * + * @note + * This setting overrides the MKLDNN_JIT_DUMP environment variable. */ +mkldnn_status_t MKLDNN_API mkldnn_set_jit_dump(int enable); + +/** Gets library version information. + * Version information includes: + * - major -- major version number + * - minor -- minor version number + * - patch -- patch release number + * - hash -- git commit hash */ +const mkldnn_version_t MKLDNN_API *mkldnn_version(); + +/** @} */ + +/** @addtogroup c_api_blas BLAS functions + * A subset of Basic Linear ALgebra (BLAS) functions to perform + * matrix-matrix multiplication. + * @{ */ + +/** SGEMM performs a matrix-matrix multiplication operation defined as + * + * C := alpha*op( A )*op( B ) + beta*C + * + * where + * - op( X ) is one of op( X ) = X or op( X ) = X**T, + * - alpha and beta are scalars, + * - A, B and C are matrices, with op( A ) an m by k matrix, op( B ) a k by n matrix + * and C an m by n matrix. + * + * The matrices are assumed to be stored in column-major order (the elements + * in a matrix columns are contiguous in memory). + * + * @note + * The API is different from the standard BLAS routine + * because it returns mkldnn_status_t for error handling. + * XERBLA is not supported: no error message will be printed + * in case of incorrect parameters. */ +mkldnn_status_t MKLDNN_API mkldnn_sgemm( + const char *transa, const char *transb, + const mkldnn_dim_t *M, const mkldnn_dim_t *N, const mkldnn_dim_t *K, + const float *alpha, const float *A, const mkldnn_dim_t *lda, + const float *B, const mkldnn_dim_t *ldb, + const float *beta, float *C, const mkldnn_dim_t *ldc); + +/** gemm_s8u8s32 and gemm_s8s8s32 perform a matrix-matrix multiplication + * operation and add the result to a scalar-matrix product. For the final + * result, a vector is added to each row or column of the output matrix. + * The operation is defined as: + * + * C := alpha*(op(A) + A_offset) * (op(B) + B_offset) + beta*C + C_offset + * + * where + * - op( X ) = X or op( X ) = X**T, + * - A_offset is an m-by-k matrix with every element equal to the value oa, + * - B_offset is an k-by-n matrix with every element equal to the value ob, + * - C_offset is an m-by-n matrix defined by the oc array, size len: + * - if offsetc = F: len must be at least 1 + * - if offsetc = C: len must be at least max(1, m) + * - if offsetc = R: len must be at least max(1, n) + * - alpha and beta are scalars, and A, B and C are matrices, with op( A ) + * an m-by-k matrix, op( B ) a k-by-n matrix and C an m-by-n matrix. + * + * The matrices are assumed to be stored in column-major order (the elements + * in a matrix columns are contiguous in memory). + * + * @note + * The API is different compared with the standard BLAS routine + * because it returns mkldnn_status_t for error handling. + * XERBLA is not supported: no error message will be printed + * in case of incorrect parameters. */ +mkldnn_status_t MKLDNN_API mkldnn_gemm_s8u8s32( + const char *transa, const char *transb, const char *offsetc, + const mkldnn_dim_t *M, const mkldnn_dim_t *N, const mkldnn_dim_t *K, + const float *alpha, + const int8_t *A, const mkldnn_dim_t *lda, const int8_t *ao, + const uint8_t *B, const mkldnn_dim_t *ldb, const int8_t *bo, + const float *beta, + int32_t *c, const mkldnn_dim_t *ldc, const int32_t *co); + +mkldnn_status_t MKLDNN_API mkldnn_gemm_s8s8s32( + const char *transa, const char *transb, const char *offsetc, + const mkldnn_dim_t *M, const mkldnn_dim_t *N, const mkldnn_dim_t *K, + const float *alpha, + const int8_t *A, const mkldnn_dim_t *lda, const int8_t *ao, + const int8_t *B, const mkldnn_dim_t *ldb, const int8_t *bo, + const float *beta, + int32_t *c, const mkldnn_dim_t *ldc, const int32_t *co); +/** @} */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/oidn/mkl-dnn/include/mkldnn.hpp b/thirdparty/oidn/mkl-dnn/include/mkldnn.hpp new file mode 100644 index 000000000000..581400a0139f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/include/mkldnn.hpp @@ -0,0 +1,2615 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_HPP +#define MKLDNN_HPP + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#include +#include +#include +#include +#include +#include + +#include "mkldnn.h" +#endif + +namespace mkldnn { + +/// @addtogroup cpp_api C++ API +/// @{ + +/// @addtogroup cpp_api_utils Utils +/// @{ + +/// A class that provides the destructor for an Intel(R) MKL-DNN C handle +template class handle_traits {}; + +/// A class for wrapping an Intel(R) MKL-DNN handle. It is used as the base +/// class for primitive (#mkldnn_primitive_t), engine (#mkldnn_engine_t), and +/// stream (#mkldnn_stream_t) handles. An object of the #mkldnn::handle class +/// can be passed by value. This class enables wrapping: +/// - Newly constructed handles. +/// @n In this case, the constructed handle uses reference counting provided +/// by @p std::shared_ptr with a proper deleter function specified through +/// the @p handle_traits class. +/// - Pre-existing handles returned by the Intel(R) MKL-DNN C API (for +/// example, through mkldnn_primitive_get_primitive_desc()). +/// @n In this case, an Intel(R) MKL-DNN C API handle is wrapped without a +/// deleter because it is assumed that the handle wrapper for the original +/// object deletes the handle (this model is similar to @p std::weak_ptr). +template > class handle { +private: + std::shared_ptr::type> _data; + handle(const handle &&) = delete; + handle &operator=(const handle &&other) = delete; +protected: + bool operator==(const T other) const { return other == _data.get(); } + bool operator!=(const T other) const { return !(*this == other); } +public: + /// Constructs a C handle wrapper. + /// @param t The C handle to wrap. + /// @param weak A flag to specify whether to construct a weak wrapper. + handle(T t = 0, bool weak = false): _data(0) { + reset(t, weak); + } + + handle(const handle &other): _data(other._data) {} + handle &operator=(const handle &other) { + _data = other._data; + return *this; + } + /// Resets the value of a C handle. + /// @param t The new value of the C handle. + /// @param weak A flag to specify whether the wrapper should be weak. + void reset(T t, bool weak = false) { + auto dummy_destructor = [](T) { return decltype(traits::destructor(0))(0); }; + _data.reset(t, weak ? dummy_destructor : traits::destructor); + } + + /// Returns the value of the underlying C handle. + T get() const { return _data.get(); } + + bool operator==(const handle &other) const { return other._data.get() == _data.get(); } + bool operator!=(const handle &other) const { return !(*this == other); } +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_memory_destroy; +}; + +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_primitive_desc_destroy; +}; + +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_primitive_destroy; +}; + +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_primitive_desc_iterator_destroy; +}; +#endif + +struct memory; +struct primitive_desc; + +/// Base class for all computational primitives. +class primitive: public handle { + friend struct error; + friend struct stream; + using handle::handle; +public: + /// A proxy to C primitive kind enum + enum class kind { + undefined_primitive = mkldnn_undefined_primitive, + reorder = mkldnn_reorder, + concat = mkldnn_concat, + sum = mkldnn_sum, + convolution = mkldnn_convolution, + deconvolution = mkldnn_deconvolution, + shuffle = mkldnn_shuffle, + eltwise = mkldnn_eltwise, + softmax = mkldnn_softmax, + pooling = mkldnn_pooling, + lrn = mkldnn_lrn, + batch_normalization = mkldnn_batch_normalization, + inner_product = mkldnn_inner_product, + rnn = mkldnn_rnn, + }; + + primitive(const_mkldnn_primitive_desc_t c_pd); + primitive(const primitive_desc &pd); + + /// Returns the descriptor of the underlying C API primitive. + inline const_mkldnn_primitive_desc_t get_primitive_desc() const; + // TODO: use the C++ API wrapper structure. + + void execute(struct stream &astream, + const std::unordered_map &args) const; +}; + +inline mkldnn_primitive_kind_t convert_to_c(primitive::kind akind) { + return static_cast(akind); +} +/// Intel(R) MKL-DNN exception class. +/// +/// This class captures the status returned by the failed C API function, error +/// message, and, optionally, handle of the primitive that caused the error. +struct error: public std::exception { + mkldnn_status_t status; + const char *message; + + /// Constructs an error instance. + /// + /// @param astatus The error status returned by the C API. + /// @param amessage The error message. + error(mkldnn_status_t astatus, const char *amessage) + : status(astatus), message(amessage) {} + + /// A convenience function for wrapping calls to the C API. Checks the + /// return status and throws an #error in case of failure. + /// + /// @param status The error status returned by the C API. + /// @param message The error message. + static void wrap_c_api(mkldnn_status_t status, const char *message) { + if (status != mkldnn_success) + throw error(status, message); + } +}; + +const_mkldnn_primitive_desc_t primitive::get_primitive_desc() const { + const_mkldnn_primitive_desc_t pd; + error::wrap_c_api(mkldnn_primitive_get_primitive_desc(get(), &pd), + "could not get primitive descriptor by primitive"); + return pd; +} +/// @} + +/// @addtogroup cpp_api_enums Common data types and enumerations +/// A proxy to @ref c_api_types in @ref c_api. +/// +/// @{ + +enum scratchpad_mode { + scratchpad_mode_library = mkldnn_scratchpad_mode_library, + scratchpad_mode_user = mkldnn_scratchpad_mode_user, +}; + +inline mkldnn_scratchpad_mode_t convert_to_c(scratchpad_mode mode) { + return static_cast(mode); +} + +enum padding_kind { + zero = mkldnn_padding_zero +}; + +inline mkldnn_padding_kind_t convert_to_c(padding_kind kind) { + return static_cast(kind); +} + +enum prop_kind { + forward_training = mkldnn_forward_training, + forward_scoring = mkldnn_forward_scoring, + forward_inference = mkldnn_forward_inference, + forward = mkldnn_forward, + backward = mkldnn_backward, + backward_data = mkldnn_backward_data, + backward_weights = mkldnn_backward_weights, + backward_bias = mkldnn_backward_bias +}; + +inline mkldnn_prop_kind_t convert_to_c(prop_kind kind) { + return static_cast(kind); +} + +enum algorithm { + algorithm_undef = mkldnn_alg_kind_undef, + convolution_auto = mkldnn_convolution_auto, + convolution_direct = mkldnn_convolution_direct, + convolution_winograd = mkldnn_convolution_winograd, + deconvolution_direct = mkldnn_deconvolution_direct, + deconvolution_winograd = mkldnn_deconvolution_winograd, + eltwise_relu = mkldnn_eltwise_relu, + eltwise_tanh = mkldnn_eltwise_tanh, + eltwise_elu = mkldnn_eltwise_elu, + eltwise_square = mkldnn_eltwise_square, + eltwise_abs = mkldnn_eltwise_abs, + eltwise_sqrt = mkldnn_eltwise_sqrt, + eltwise_linear = mkldnn_eltwise_linear, + eltwise_bounded_relu = mkldnn_eltwise_bounded_relu, + eltwise_soft_relu = mkldnn_eltwise_soft_relu, + eltwise_logistic = mkldnn_eltwise_logistic, + lrn_across_channels = mkldnn_lrn_across_channels, + lrn_within_channel = mkldnn_lrn_within_channel, + pooling_max = mkldnn_pooling_max, + pooling_avg = mkldnn_pooling_avg, + pooling_avg_include_padding = mkldnn_pooling_avg_include_padding, + pooling_avg_exclude_padding = mkldnn_pooling_avg_exclude_padding, + vanilla_rnn = mkldnn_vanilla_rnn, + vanilla_lstm = mkldnn_vanilla_lstm, + vanilla_gru = mkldnn_vanilla_gru, + gru_linear_before_reset = mkldnn_gru_linear_before_reset +}; + +inline mkldnn_alg_kind_t convert_to_c(algorithm aalgorithm) { + return static_cast(aalgorithm); +} + +enum batch_normalization_flag { + use_global_stats = mkldnn_use_global_stats, + use_scale_shift = mkldnn_use_scaleshift, + fuse_bn_relu = mkldnn_fuse_bn_relu +}; + +inline mkldnn_batch_normalization_flag_t convert_to_c( + batch_normalization_flag aflag) { + return static_cast(aflag); +} + +enum rnn_direction { + unidirectional_left2right = mkldnn_unidirectional_left2right, + unidirectional_right2left = mkldnn_unidirectional_right2left, + unidirectional = mkldnn_unidirectional, + bidirectional_concat = mkldnn_bidirectional_concat, + bidirectional_sum = mkldnn_bidirectional_sum, +}; + +inline mkldnn_rnn_direction_t convert_to_c(rnn_direction adir) { + return static_cast(adir); +} + +enum query { + undef = mkldnn_query_undef, + + query_engine = mkldnn_query_engine, + primitive_kind = mkldnn_query_primitive_kind, + + num_of_inputs_s32 = mkldnn_query_num_of_inputs_s32, + num_of_outputs_s32 = mkldnn_query_num_of_outputs_s32, + + time_estimate_f64 = mkldnn_query_time_estimate_f64, + memory_consumption_s64 = mkldnn_query_memory_consumption_s64, + + query_scratchpad_engine = mkldnn_query_scratchpad_engine, + + impl_info_str = mkldnn_query_impl_info_str, + + op_d = mkldnn_query_op_d, + convolution_d = mkldnn_query_convolution_d, + deconvolution_d = mkldnn_query_deconvolution_d, + shuffle_d = mkldnn_query_shuffle_d, + eltwise_d = mkldnn_query_eltwise_d, + softmax_d = mkldnn_query_softmax_d, + pooling_d = mkldnn_query_pooling_d, + lrn_d = mkldnn_query_lrn_d, + batch_normalization_d = mkldnn_query_batch_normalization_d, + inner_product_d = mkldnn_query_inner_product_d, + rnn_d = mkldnn_query_rnn_d, + + src_md = mkldnn_query_src_md, + diff_src_md = mkldnn_query_diff_src_md, + weights_md = mkldnn_query_weights_md, + diff_weights_md = mkldnn_query_diff_weights_md, + dst_md = mkldnn_query_dst_md, + diff_dst_md = mkldnn_query_diff_dst_md, + workspace_md = mkldnn_query_workspace_md, + scratchpad_md = mkldnn_query_scratchpad_md, +}; + +inline mkldnn_query_t convert_to_c(query aquery) { + return static_cast(aquery); +} + +/// @} + +/// @addtogroup cpp_api_attr Attributes +/// An extension for controlling primitive behavior. +/// +/// @sa @ref c_api_attributes in @ref c_api +/// @{ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_post_ops_destroy; +}; +#endif + +struct post_ops: public handle { + post_ops() { + mkldnn_post_ops_t result; + error::wrap_c_api(mkldnn_post_ops_create(&result), + "could not create post operation sequence"); + reset(result); + } + + int len() const { return mkldnn_post_ops_len(get()); } + + primitive::kind kind(int index) const { + error::wrap_c_api( + index < len() ? mkldnn_success : mkldnn_invalid_arguments, + "post_ops index is out of range"); + return static_cast(mkldnn_post_ops_get_kind(get(), + index)); + } + + void append_sum(float scale = 1.) { + error::wrap_c_api(mkldnn_post_ops_append_sum(get(), scale), + "could not append sum"); + } + + void get_params_sum(int index, float &scale) const { + error::wrap_c_api(mkldnn_post_ops_get_params_sum(get(), index, &scale), + "could not get sum params"); + } + + void append_eltwise(float scale, algorithm alg, float alpha, + float beta) { + error::wrap_c_api(mkldnn_post_ops_append_eltwise(get(), scale, + convert_to_c(alg), alpha, beta), + "could not append eltwise"); + } + + void get_params_eltwise(int index, float &scale, algorithm &alg, + float &alpha, float &beta) const { + mkldnn_alg_kind_t c_alg; + error::wrap_c_api(mkldnn_post_ops_get_params_eltwise(get(), index, + &scale, &c_alg, &alpha, &beta), + "could not get eltwise params"); + alg = static_cast(c_alg); + } +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_primitive_attr_destroy; +}; +#endif + +struct primitive_attr: public handle { + primitive_attr() { + mkldnn_primitive_attr_t result; + error::wrap_c_api(mkldnn_primitive_attr_create(&result), + "could not create a primitive attr"); + reset(result); + } + + scratchpad_mode get_scratchpad_mode() const { + mkldnn_scratchpad_mode_t result; + error::wrap_c_api(mkldnn_primitive_attr_get_scratchpad_mode( + get(), &result), "could not get scratchpad mode"); + return scratchpad_mode(result); + } + + void set_scratchpad_mode(scratchpad_mode mode) { + error::wrap_c_api(mkldnn_primitive_attr_set_scratchpad_mode( + get(), mkldnn::convert_to_c(mode)), + "could not set scratchpad mode"); + } + + void get_output_scales(int &mask, std::vector &scales) const + { + mkldnn_dim_t count; + int c_mask; + const float *c_scales; + error::wrap_c_api(mkldnn_primitive_attr_get_output_scales(get(), + &count, &c_mask, &c_scales), + "could not get int output scales"); + scales.resize(count); + + mask = c_mask; + for (mkldnn_dim_t c = 0; c < count; ++c) + scales[c] = c_scales[c]; + } + + void set_output_scales(int mask, const std::vector &scales) + { + error::wrap_c_api(mkldnn_primitive_attr_set_output_scales(get(), + (mkldnn_dim_t)scales.size(), mask, &scales[0]), + "could not set int output scales"); + } + + const post_ops get_post_ops() const { + post_ops result; + const_mkldnn_post_ops_t c_result; + error::wrap_c_api(mkldnn_primitive_attr_get_post_ops(get(), &c_result), + "could not get post operation sequence"); + result.reset(const_cast(c_result), true); + return result; + } + + void set_post_ops(post_ops ops) { + error::wrap_c_api(mkldnn_primitive_attr_set_post_ops(get(), ops.get()), + "could not set post operation sequence"); + } + + void set_rnn_data_qparams(const float scale, const float shift) + { + error::wrap_c_api(mkldnn_primitive_attr_set_rnn_data_qparams(get(), + scale, shift), "could not set rnn data int scale/shift"); + } + + void set_rnn_weights_qparams(int mask, const std::vector &scales) + { + error::wrap_c_api(mkldnn_primitive_attr_set_rnn_weights_qparams(get(), + (int)scales.size(), mask, &scales[0]), + "could not set rnn weights int scales"); + } +}; + +/// @} + +/// @addtogroup cpp_api_engine Engine +/// Engine operations. +/// +/// @sa @ref c_api_engine in @ref c_api +/// @{ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_engine_destroy; +}; +#endif + +/// An execution engine. +struct engine: public handle { + friend class primitive; + // gcc bug??? using handle::handle; + + /// Kinds of engines. + enum kind { + /// An unspecified engine + any = mkldnn_any_engine, + /// CPU engine + cpu = mkldnn_cpu, + }; + + /// Returns the number of engines of a certain kind. + /// + /// @param akind The kind of engines to count. + + static size_t get_count(kind akind) { + return mkldnn_engine_get_count(convert_to_c(akind)); + } + + /// Constructs an engine. + /// + /// @param akind The kind of engine to construct. + /// @param index The index of the engine. Must be less than the value + /// returned by #get_count() for this particular kind of engine. + + engine(kind akind, size_t index) { + mkldnn_engine_t aengine; + error::wrap_c_api( + mkldnn_engine_create(&aengine, + convert_to_c(akind), index), + "could not create an engine"); + reset(aengine); + } + + explicit engine(const mkldnn_engine_t& aengine) + : handle(aengine, true) {} + + engine(const handle &pd) { + mkldnn_engine_t engine_q; + error::wrap_c_api( + mkldnn_primitive_desc_query(pd.get(), + mkldnn::convert_to_c(query_engine), 0, &engine_q), + "could not get engine from primitive_desc"); + reset(engine_q, true); + } + + template + static engine query(const primitive_desc &pd) { + mkldnn_engine_t engine_q; + error::wrap_c_api( + mkldnn_primitive_desc_query(pd.get(), + mkldnn::convert_to_c(query_engine), 0, &engine_q), + "could not get engine from primitive_desc"); + + return engine(engine_q); + } + +private: + static mkldnn_engine_kind_t convert_to_c(kind akind) { + return static_cast(akind); + } +}; + +/// @} + +/// @addtogroup cpp_api_stream Stream +/// Execution stream operations +/// +/// @sa @ref c_api_stream in @ref c_api +/// @{ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> struct handle_traits { + static constexpr auto destructor = &mkldnn_stream_destroy; +}; +#endif + +struct stream: public handle { + using handle::handle; + + enum: unsigned { + default_flags = mkldnn_stream_default_flags, + }; + + /// Constructs a stream. + stream(const engine &aengine, + unsigned flags = static_cast(default_flags)) { + mkldnn_stream_t astream; + error::wrap_c_api(mkldnn_stream_create(&astream, aengine.get(), flags), + "could not create a stream"); + reset(astream); + } +}; + +/// @} + +/// @addtogroup cpp_api_memory_related Memory and memory related operations +/// @{ + +/// @addtogroup cpp_api_memory Memory +/// A primitive to describe and store data. +/// +/// For more information, refer to @ref c_api_memory in @ref c_api. +/// @{ + +/// Memory that describes the data. +struct memory: public handle { + public: + typedef mkldnn_dim_t dim; + typedef std::vector dims; + + template static void validate_dims(const std::vector &v) { + if (v.size() > MKLDNN_MAX_NDIMS) + throw error(mkldnn_invalid_arguments, "invalid dimensions"); + } + + /// Data type specification. See #mkldnn_data_type_t for a detailed + /// description. + enum data_type { + data_undef = mkldnn_data_type_undef, + f32 = mkldnn_f32, + s32 = mkldnn_s32, + s8 = mkldnn_s8, + u8 = mkldnn_u8, + }; + + /// Memory format tag specification. See #mkldnn_format_tag_t + /// for a detailed description. + enum format_tag { + format_tag_undef = mkldnn_format_tag_undef, + any = mkldnn_format_tag_any, + a = mkldnn_a, + ab = mkldnn_ab, + abc = mkldnn_abc, + abcd = mkldnn_abcd, + abcde = mkldnn_abcde, + abcdef = mkldnn_abcdef, + abdec = mkldnn_abdec, + acb = mkldnn_acb, + acbde = mkldnn_acbde, + acdb = mkldnn_acdb, + acdeb = mkldnn_acdeb, + ba = mkldnn_ba, + bac = mkldnn_bac, + bacd = mkldnn_bacd, + bcda = mkldnn_bcda, + cba = mkldnn_cba, + cdba = mkldnn_cdba, + cdeba = mkldnn_cdeba, + decab = mkldnn_decab, + Abc16a = mkldnn_Abc16a, + ABc16a16b = mkldnn_ABc16a16b, + aBc16b = mkldnn_aBc16b, + ABc16b16a = mkldnn_ABc16b16a, + Abc4a = mkldnn_Abc4a, + aBc4b = mkldnn_aBc4b, + ABc4b16a4b = mkldnn_ABc4b16a4b, + ABc4b4a = mkldnn_ABc4b4a, + ABc8a16b2a = mkldnn_ABc8a16b2a, + ABc8a8b = mkldnn_ABc8a8b, + aBc8b = mkldnn_aBc8b, + ABc8b16a2b = mkldnn_ABc8b16a2b, + ABc8b8a = mkldnn_ABc8b8a, + Abcd16a = mkldnn_Abcd16a, + ABcd16a16b = mkldnn_ABcd16a16b, + aBcd16b = mkldnn_aBcd16b, + ABcd16b16a = mkldnn_ABcd16b16a, + aBCd16b16c = mkldnn_aBCd16b16c, + aBCd16c16b = mkldnn_aBCd16c16b, + Abcd4a = mkldnn_Abcd4a, + aBcd4b = mkldnn_aBcd4b, + ABcd4b16a4b = mkldnn_ABcd4b16a4b, + ABcd4b4a = mkldnn_ABcd4b4a, + aBCd4c16b4c = mkldnn_aBCd4c16b4c, + aBCd4c4b = mkldnn_aBCd4c4b, + ABcd8a16b2a = mkldnn_ABcd8a16b2a, + ABcd8a8b = mkldnn_ABcd8a8b, + aBcd8b = mkldnn_aBcd8b, + ABcd8b16a2b = mkldnn_ABcd8b16a2b, + aBCd8b16c2b = mkldnn_aBCd8b16c2b, + ABcd8b8a = mkldnn_ABcd8b8a, + aBCd8b8c = mkldnn_aBCd8b8c, + aBCd8c16b2c = mkldnn_aBCd8c16b2c, + aBCd8c8b = mkldnn_aBCd8c8b, + Abcde16a = mkldnn_Abcde16a, + ABcde16a16b = mkldnn_ABcde16a16b, + aBcde16b = mkldnn_aBcde16b, + ABcde16b16a = mkldnn_ABcde16b16a, + aBCde16b16c = mkldnn_aBCde16b16c, + aBCde16c16b = mkldnn_aBCde16c16b, + aBCde2c8b4c = mkldnn_aBCde2c8b4c, + Abcde4a = mkldnn_Abcde4a, + aBcde4b = mkldnn_aBcde4b, + ABcde4b4a = mkldnn_ABcde4b4a, + aBCde4b4c = mkldnn_aBCde4b4c, + aBCde4c16b4c = mkldnn_aBCde4c16b4c, + aBCde4c4b = mkldnn_aBCde4c4b, + Abcde8a = mkldnn_Abcde8a, + ABcde8a8b = mkldnn_ABcde8a8b, + aBcde8b = mkldnn_aBcde8b, + ABcde8b16a2b = mkldnn_ABcde8b16a2b, + aBCde8b16c2b = mkldnn_aBCde8b16c2b, + ABcde8b8a = mkldnn_ABcde8b8a, + aBCde8b8c = mkldnn_aBCde8b8c, + aBCde8c16b2c = mkldnn_aBCde8c16b2c, + aBCde8c8b = mkldnn_aBCde8c8b, + aBcdef16b = mkldnn_aBcdef16b, + aBCdef16b16c = mkldnn_aBCdef16b16c, + aBCdef16c16b = mkldnn_aBCdef16c16b, + aBcdef4b = mkldnn_aBcdef4b, + aBCdef4c4b = mkldnn_aBCdef4c4b, + aBCdef8b8c = mkldnn_aBCdef8b8c, + aBCdef8c16b2c = mkldnn_aBCdef8c16b2c, + aBCdef8c8b = mkldnn_aBCdef8c8b, + aBdc16b = mkldnn_aBdc16b, + aBdc4b = mkldnn_aBdc4b, + aBdc8b = mkldnn_aBdc8b, + aBdec16b = mkldnn_aBdec16b, + aBdec4b = mkldnn_aBdec4b, + aBdec8b = mkldnn_aBdec8b, + aBdefc16b = mkldnn_aBdefc16b, + aBdefc4b = mkldnn_aBdefc4b, + aBdefc8b = mkldnn_aBdefc8b, + Acb16a = mkldnn_Acb16a, + Acb4a = mkldnn_Acb4a, + Acb8a = mkldnn_Acb8a, + aCBd16b16c = mkldnn_aCBd16b16c, + aCBde16b16c = mkldnn_aCBde16b16c, + Acdb16a = mkldnn_Acdb16a, + Acdb4a = mkldnn_Acdb4a, + Acdb8a = mkldnn_Acdb8a, + Acdeb16a = mkldnn_Acdeb16a, + Acdeb4a = mkldnn_Acdeb4a, + Acdeb8a = mkldnn_Acdeb8a, + BAc16a16b = mkldnn_BAc16a16b, + BAcd16a16b = mkldnn_BAcd16a16b, + format_tag_last = mkldnn_format_tag_last, + + x = mkldnn_x, + nc = mkldnn_nc, + cn = mkldnn_cn, + ncw = mkldnn_ncw, + nwc = mkldnn_nwc, + nchw = mkldnn_nchw, + nhwc = mkldnn_nhwc, + chwn = mkldnn_chwn, + ncdhw = mkldnn_ncdhw, + ndhwc = mkldnn_ndhwc, + oi = mkldnn_oi, + io = mkldnn_io, + oiw = mkldnn_oiw, + wio = mkldnn_wio, + oihw = mkldnn_oihw, + hwio = mkldnn_hwio, + ihwo = mkldnn_ihwo, + iohw = mkldnn_iohw, + oidhw = mkldnn_oidhw, + dhwio = mkldnn_dhwio, + goiw = mkldnn_goiw, + goihw = mkldnn_goihw, + hwigo = mkldnn_hwigo, + giohw = mkldnn_giohw, + goidhw = mkldnn_goidhw, + tnc = mkldnn_tnc, + ntc = mkldnn_ntc, + ldsnc = mkldnn_ldsnc, + ldigo = mkldnn_ldigo, + ldgoi = mkldnn_ldgoi, + ldgo = mkldnn_ldgo, + nCdhw16c = mkldnn_nCdhw16c, + nCdhw4c = mkldnn_nCdhw4c, + nCdhw8c = mkldnn_nCdhw8c, + nChw16c = mkldnn_nChw16c, + nChw4c = mkldnn_nChw4c, + nChw8c = mkldnn_nChw8c, + nCw16c = mkldnn_nCw16c, + nCw4c = mkldnn_nCw4c, + nCw8c = mkldnn_nCw8c, + IOw16o16i = mkldnn_IOw16o16i, + OIw16i16o = mkldnn_OIw16i16o, + OIw16o16i = mkldnn_OIw16o16i, + Oiw16o = mkldnn_Oiw16o, + OIw4i16o4i = mkldnn_OIw4i16o4i, + OIw4i4o = mkldnn_OIw4i4o, + Oiw4o = mkldnn_Oiw4o, + OIw8i16o2i = mkldnn_OIw8i16o2i, + OIw8i8o = mkldnn_OIw8i8o, + OIw8o16i2o = mkldnn_OIw8o16i2o, + OIw8o8i = mkldnn_OIw8o8i, + Owi16o = mkldnn_Owi16o, + Owi4o = mkldnn_Owi4o, + Owi8o = mkldnn_Owi8o, + IOhw16o16i = mkldnn_IOhw16o16i, + Ohwi16o = mkldnn_Ohwi16o, + Ohwi4o = mkldnn_Ohwi4o, + Ohwi8o = mkldnn_Ohwi8o, + OIhw16i16o = mkldnn_OIhw16i16o, + OIhw16o16i = mkldnn_OIhw16o16i, + Oihw16o = mkldnn_Oihw16o, + OIhw4i16o4i = mkldnn_OIhw4i16o4i, + OIhw4i4o = mkldnn_OIhw4i4o, + Oihw4o = mkldnn_Oihw4o, + OIhw8i16o2i = mkldnn_OIhw8i16o2i, + OIhw8i8o = mkldnn_OIhw8i8o, + OIhw8o16i2o = mkldnn_OIhw8o16i2o, + OIhw8o8i = mkldnn_OIhw8o8i, + Odhwi16o = mkldnn_Odhwi16o, + Odhwi4o = mkldnn_Odhwi4o, + Odhwi8o = mkldnn_Odhwi8o, + OIdhw16i16o = mkldnn_OIdhw16i16o, + OIdhw16o16i = mkldnn_OIdhw16o16i, + Oidhw16o = mkldnn_Oidhw16o, + OIdhw4i4o = mkldnn_OIdhw4i4o, + Oidhw4o = mkldnn_Oidhw4o, + OIdhw8i16o2i = mkldnn_OIdhw8i16o2i, + OIdhw8i8o = mkldnn_OIdhw8i8o, + OIdhw8o8i = mkldnn_OIdhw8o8i, + gIOw16o16i = mkldnn_gIOw16o16i, + gOIw16i16o = mkldnn_gOIw16i16o, + gOIw16o16i = mkldnn_gOIw16o16i, + gOiw16o = mkldnn_gOiw16o, + gOIw4i16o4i = mkldnn_gOIw4i16o4i, + gOIw4i4o = mkldnn_gOIw4i4o, + gOiw4o = mkldnn_gOiw4o, + gOIw8i16o2i = mkldnn_gOIw8i16o2i, + gOIw8i8o = mkldnn_gOIw8i8o, + gOIw8o16i2o = mkldnn_gOIw8o16i2o, + gOIw8o8i = mkldnn_gOIw8o8i, + gOwi16o = mkldnn_gOwi16o, + gOwi4o = mkldnn_gOwi4o, + gOwi8o = mkldnn_gOwi8o, + gIOhw16o16i = mkldnn_gIOhw16o16i, + gOhwi16o = mkldnn_gOhwi16o, + gOhwi4o = mkldnn_gOhwi4o, + gOhwi8o = mkldnn_gOhwi8o, + Goihw16g = mkldnn_Goihw16g, + gOIhw16i16o = mkldnn_gOIhw16i16o, + gOIhw16o16i = mkldnn_gOIhw16o16i, + gOihw16o = mkldnn_gOihw16o, + gOIhw2i8o4i = mkldnn_gOIhw2i8o4i, + gOIhw4i16o4i = mkldnn_gOIhw4i16o4i, + gOIhw4i4o = mkldnn_gOIhw4i4o, + gOIhw4o4i = mkldnn_gOIhw4o4i, + gOihw4o = mkldnn_gOihw4o, + Goihw8g = mkldnn_Goihw8g, + gOIhw8i16o2i = mkldnn_gOIhw8i16o2i, + gOIhw8i8o = mkldnn_gOIhw8i8o, + gOIhw8o16i2o = mkldnn_gOIhw8o16i2o, + gOIhw8o8i = mkldnn_gOIhw8o8i, + gOdhwi16o = mkldnn_gOdhwi16o, + gOdhwi4o = mkldnn_gOdhwi4o, + gOdhwi8o = mkldnn_gOdhwi8o, + gOIdhw16i16o = mkldnn_gOIdhw16i16o, + gOIdhw16o16i = mkldnn_gOIdhw16o16i, + gOidhw16o = mkldnn_gOidhw16o, + gOIdhw4i4o = mkldnn_gOIdhw4i4o, + gOidhw4o = mkldnn_gOidhw4o, + gOIdhw8i16o2i = mkldnn_gOIdhw8i16o2i, + gOIdhw8i8o = mkldnn_gOIdhw8i8o, + gOIdhw8o8i = mkldnn_gOIdhw8o8i, + }; + + /// A memory descriptor. + struct desc { + friend struct memory; + /// The underlying C API data structure. + mkldnn_memory_desc_t data; + + /// Constructs a zero memory descriptor + desc(): data() {} + + /// Constructs a memory descriptor. + /// + /// @param adims Data dimensions + /// @param adata_type Data precision/type. + /// @param aformat Data layout format tag. + desc(const dims &adims, data_type adata_type, + format_tag aformat) { + validate_dims(adims); + error::wrap_c_api(mkldnn_memory_desc_init_by_tag(&data, (int)adims.size(), + adims.size() == 0 ? nullptr : &adims[0], + convert_to_c(adata_type), convert_to_c(aformat)), + "could not initialize a memory descriptor"); + } + + /// Constructs a memory descriptor from a C API data structure. + /// + /// @param adata A C API #mkldnn_memory_desc_t structure. + desc(const mkldnn_memory_desc_t &adata): data(adata) {} + + /// Constructs a sub-memory descriptor + // + /// @param adims Sizes of a sub-memory + /// @param offsets Offsets of a sub-memory + desc submemory_desc(const dims &adims, const dims &offsets) { + mkldnn_memory_desc_t sub_md; + error::wrap_c_api(mkldnn_memory_desc_init_submemory(&sub_md, + &data, &adims[0], &offsets[0]), + "could not initialize a sub-memory"); + return desc(sub_md); + } + + /// Returns the number of bytes required to allocate the memory described + /// including the padding area. + size_t get_size() const { return mkldnn_memory_desc_get_size(&data); } + + bool operator==(const desc &other) const { + return mkldnn_memory_desc_equal(&data, &other.data) != 0; + } + + bool operator!=(const desc &other) const { return !operator==(other); } + }; + + /// Constructs a memory. + /// + /// @param md Memory descriptor. + /// @param aengine Engine. + /// @param ahandle Native handle. + memory(const desc &md, const engine &aengine, void *ahandle) { + mkldnn_memory_t result; + error::wrap_c_api(mkldnn_memory_create(&result, &md.data, + aengine.get(), ahandle), "could not create a memory"); + reset(result); + } + + /// Constructs a memory. + /// + /// @param md Memory descriptor. + /// @param aengine Engine. + memory(const desc &md, const engine &aengine) + : memory(md, aengine, MKLDNN_NATIVE_HANDLE_ALLOCATE) {} + + /// Returns the descriptor of the memory. + desc get_desc() const { + const mkldnn_memory_desc_t *cdesc; + error::wrap_c_api(mkldnn_memory_get_memory_desc(get(), &cdesc), + "could not get memory descriptor from a memory"); + return desc(*cdesc); + } + + /// Returns the engine of the memory. + engine get_engine() const { + mkldnn_engine_t engine_q; + error::wrap_c_api(mkldnn_memory_get_engine(get(), &engine_q), + "could not get engine from a memory"); + return engine(engine_q); + } + + /// Returns a handle of the data contained in the memory. + /// + /// On the CPU engine, this is a pointer to the allocated memory. + void *get_data_handle() const { + void *handle; + error::wrap_c_api(mkldnn_memory_get_data_handle(get(), &handle), + "could not get native handle"); + return handle; + } + + void set_data_handle(void *handle) const { + error::wrap_c_api(mkldnn_memory_set_data_handle(get(), handle), + "could not set native handle"); + } + + // Must go away or be private: + static mkldnn_data_type_t convert_to_c(data_type adata_type) { + return static_cast(adata_type); + } + static mkldnn_format_tag_t convert_to_c(format_tag aformat) { + return static_cast(aformat); + } +}; + +inline bool operator==(mkldnn_data_type_t a, memory::data_type b) { + return a == memory::convert_to_c(b); +} +inline bool operator!=(mkldnn_data_type_t a, memory::data_type b) { + return !(a == b); +} +inline bool operator==(memory::data_type a, mkldnn_data_type_t b) { + return b == a; +} +inline bool operator!=(memory::data_type a, mkldnn_data_type_t b) { + return !(a == b); +} + +inline bool operator==(mkldnn_format_tag_t a, memory::format_tag b) { + return a == memory::convert_to_c(b); +} +inline bool operator!=(mkldnn_format_tag_t a, memory::format_tag b) { + return !(a == b); +} +inline bool operator==(memory::format_tag a, mkldnn_format_tag_t b) { + return b == a; +} +inline bool operator!=(memory::format_tag a, mkldnn_format_tag_t b) { + return !(a == b); +} + +/// @} + +/// @addtogroup cpp_api_reorder Reorder +/// A primitive to copy data between memory formats. +/// +/// @sa @ref c_api_reorder in @ref c_api +/// @{ + +struct reorder : public primitive { + struct primitive_desc : public handle { + primitive_desc(const engine &src_engine, const memory::desc &src_md, + const engine &dst_engine, const memory::desc &dst_md, + const primitive_attr &aattr) { + mkldnn_primitive_desc_t result; + error::wrap_c_api(mkldnn_reorder_primitive_desc_create(&result, + src_engine.get(), &src_md.data, + dst_engine.get(), &dst_md.data, aattr.get()), + "could not create a reorder primitive descriptor"); + reset(result); + } + + primitive_desc(const engine &src_engine, const memory::desc &src_md, + const engine &dst_engine, const memory::desc &dst_md) { + mkldnn_primitive_desc_t result; + error::wrap_c_api(mkldnn_reorder_primitive_desc_create(&result, + src_engine.get(), &src_md.data, + dst_engine.get(), &dst_md.data, nullptr), + "could not create a reorder primitive descriptor"); + reset(result); + } + + primitive_desc(const memory &src, const memory &dst, + const primitive_attr &aattr) { + mkldnn_primitive_desc_t result; + auto src_md = src.get_desc(); + auto dst_md = dst.get_desc(); + error::wrap_c_api(mkldnn_reorder_primitive_desc_create(&result, + src.get_engine().get(), &src_md.data, + dst.get_engine().get(), &dst_md.data, aattr.get()), + "could not create a reorder primitive descriptor"); + reset(result); + } + + primitive_desc(const memory &src, const memory &dst) { + mkldnn_primitive_desc_t result; + auto src_md = src.get_desc(); + auto dst_md = dst.get_desc(); + error::wrap_c_api(mkldnn_reorder_primitive_desc_create(&result, + src.get_engine().get(), &src_md.data, + dst.get_engine().get(), &dst_md.data, nullptr), + "could not create a reorder primitive descriptor"); + reset(result); + } + + memory::desc scratchpad_desc() const { + const mkldnn_memory_desc_t *cdesc = mkldnn_primitive_desc_query_md( + get(), mkldnn::convert_to_c(scratchpad_md), 0); + if (cdesc == nullptr) + return memory::desc(); + return memory::desc(*cdesc); + } + + engine scratchpad_engine() { + mkldnn_engine_t engine_q; + error::wrap_c_api( + mkldnn_primitive_desc_query(get(), + mkldnn::convert_to_c(query_scratchpad_engine), 0, &engine_q), + "could not get scratchpad engine from reorder primitive_desc"); + + return engine(engine_q); + } + + engine get_engine() { return engine::query(*this); } + }; + + reorder(const primitive_desc &pd): primitive(pd.get()) {} + + reorder(const memory &src, const memory &dst): + primitive(primitive_desc(src, dst).get()) {} + + void execute(stream astream, memory &src, memory &dst) { + primitive::execute(astream, + {{MKLDNN_ARG_FROM, src}, {MKLDNN_ARG_TO, dst}}); + } +}; + +/// @} + +/// @addtogroup cpp_api_concat Concat +/// A primitive to concatenate data by arbitrary dimension. +/// +/// @sa @ref c_api_concat in @ref c_api +/// @{ + +struct concat : public primitive { + struct primitive_desc : public handle { + std::vector cpp_to_c( + const std::vector &srcs) { + std::vector c_api_srcs; + c_api_srcs.reserve(srcs.size()); + for (const auto &s : srcs) c_api_srcs.push_back(s.data); + return c_api_srcs; + } + + primitive_desc(const memory::desc &dst, int concat_dimension, + const std::vector &srcs, const engine &aengine) { + auto c_api_srcs = cpp_to_c(srcs); + + mkldnn_primitive_desc_t result; + error::wrap_c_api(mkldnn_concat_primitive_desc_create( + &result, &dst.data, (int)c_api_srcs.size(), + concat_dimension, &c_api_srcs[0], nullptr, aengine.get()), + "could not create a concat primitive descriptor"); + reset(result); + } + + primitive_desc(int concat_dimension, + const std::vector &srcs, const engine &aengine) { + auto c_api_srcs = cpp_to_c(srcs); + + mkldnn_primitive_desc_t result; + error::wrap_c_api(mkldnn_concat_primitive_desc_create( + &result, nullptr, (int)c_api_srcs.size(), + concat_dimension, &c_api_srcs[0], nullptr, aengine.get()), + "could not create a concat primitive descriptor"); + reset(result); + } + + memory::desc dst_desc() const { + const mkldnn_memory_desc_t *cdesc = mkldnn_primitive_desc_query_md( + get(), mkldnn::convert_to_c(dst_md), 0); + error::wrap_c_api( + cdesc == nullptr ? mkldnn_runtime_error : mkldnn_success, + "could not get a dst memory descriptor"); + return memory::desc(*cdesc); + } + + memory::desc scratchpad_desc() const { + const mkldnn_memory_desc_t *cdesc = mkldnn_primitive_desc_query_md( + get(), mkldnn::convert_to_c(scratchpad_md), 0); + if (cdesc == nullptr) + return memory::desc(); + return memory::desc(*cdesc); + } + + engine get_engine() { return engine::query(*this); } + }; + + concat(const primitive_desc &pd): primitive(pd.get()) {} +}; + +/// @} + +/// @addtogroup cpp_api_sum Sum +/// A primitive to sum data. +/// +/// @sa @ref c_api_sum in @ref c_api +/// @{ + +struct sum : public primitive { + struct primitive_desc : public handle { + std::vector cpp_to_c( + const std::vector &srcs) { + std::vector c_api_srcs; + c_api_srcs.reserve(srcs.size()); + for (const auto &s : srcs) c_api_srcs.push_back(s.data); + return c_api_srcs; + } + + primitive_desc(const memory::desc &dst, + const std::vector &scales, + const std::vector &srcs, const engine &aengine) { + error::wrap_c_api(scales.size() == srcs.size() + ? mkldnn_success : mkldnn_invalid_arguments, + "number of scales not equal to number of srcs"); + + auto c_api_srcs = cpp_to_c(srcs); + + mkldnn_primitive_desc_t result; + error::wrap_c_api(mkldnn_sum_primitive_desc_create( + &result, &dst.data, (int)c_api_srcs.size(), + &scales[0], &c_api_srcs[0], nullptr, aengine.get()), + "could not create a sum primitive descriptor"); + reset(result); + } + + primitive_desc(const std::vector &scales, + const std::vector &srcs, const engine &aengine) { + error::wrap_c_api(scales.size() == srcs.size() + ? mkldnn_success : mkldnn_invalid_arguments, + "number of scales not equal to number of srcs"); + + auto c_api_srcs = cpp_to_c(srcs); + mkldnn_primitive_desc_t result; + error::wrap_c_api(mkldnn_sum_primitive_desc_create(&result, + nullptr, (int)c_api_srcs.size(), &scales[0], + &c_api_srcs[0], nullptr, aengine.get()), + "could not create a sum primitive descriptor"); + reset(result); + } + + memory::desc dst_desc() const { + const mkldnn_memory_desc_t *cdesc = mkldnn_primitive_desc_query_md( + get(), mkldnn::convert_to_c(dst_md), 0); + error::wrap_c_api( + cdesc == nullptr ? mkldnn_runtime_error : mkldnn_success, + "could not get a dst memory descriptor"); + return memory::desc(*cdesc); + } + + memory::desc scratchpad_desc() const { + const mkldnn_memory_desc_t *cdesc = mkldnn_primitive_desc_query_md( + get(), mkldnn::convert_to_c(scratchpad_md), 0); + if (cdesc == nullptr) + return memory::desc(); + return memory::desc(*cdesc); + } + + engine get_engine() { return engine::query(*this); } + }; + + sum(const primitive_desc &pd): primitive(pd.get()) {} +}; + +/// @} + +/// @} + +/// @addtogroup cpp_api_primitives Primitives +/// @{ + +/// @addtogroup cpp_api_primitive_descriptors Primitive descriptors +/// @{ + +/// A base class for all primitive descriptors. +struct primitive_desc : public handle { + primitive_desc(const_mkldnn_op_desc_t desc, const primitive_attr *attr, + const engine &e, const_mkldnn_primitive_desc_t hint_fwd_pd) { + mkldnn_primitive_desc_iterator_t iterator = nullptr; + mkldnn_status_t status = mkldnn_primitive_desc_iterator_create( + &iterator, desc, attr ? attr->get() : nullptr, e.get(), + hint_fwd_pd); + error::wrap_c_api(status, + "could not create a primitive descriptor iterator"); + pd_iterator.reset(iterator); + fetch_impl(); + } + + engine get_engine() { return engine::query(*this); } + + primitive_attr get_primitive_attr() const { + const_mkldnn_primitive_attr_t const_cattr; + error::wrap_c_api(mkldnn_primitive_desc_get_attr(get(), &const_cattr), + "could not get attributes"); + mkldnn_primitive_attr_t cattr; + error::wrap_c_api(mkldnn_primitive_attr_clone(&cattr, const_cattr), + "could not clone attributes"); + + primitive_attr attr; + attr.reset(cattr); + return attr; + } + + /// Returns implementation name + const char *impl_info_str() const { + const char *res; + error::wrap_c_api(mkldnn_primitive_desc_query(get(), + mkldnn_query_impl_info_str, 0, &res), + "could not query implementation info string"); + return res; + } + + /// Queries the memory::dim value (same as int64_t) + memory::dim query_s64(query q) const { + memory::dim res; + mkldnn_status_t status = mkldnn_primitive_desc_query(get(), + mkldnn::convert_to_c(q), 0, &res); + return status == mkldnn_success ? res : 0; + } + + /// Advances the next implementation for the given op descriptor. + /// + /// Returns: + /// - @c true on success + /// - @c false if the last implementation reached, and + /// the primitive descriptor itself is kept unchanged + bool next_impl() { + mkldnn_status_t status = mkldnn_primitive_desc_iterator_next( + pd_iterator.get()); + if (status == mkldnn_iterator_ends) return false; + error::wrap_c_api(status, "primitive descriptor iterator next failed"); + + fetch_impl(); + return true; + } + + /// Queries and returns requested memory descriptor. + memory::desc query_md(query what, int idx = 0) const { + std::vector valid_q{src_md, diff_src_md, weights_md, + diff_weights_md, dst_md, diff_dst_md, workspace_md, scratchpad_md}; + if (!std::any_of(valid_q.cbegin(), valid_q.cend(), + [=](query q) { return what == q; })) + throw error(mkldnn_invalid_arguments, "invalid memory query"); + + const mkldnn_memory_desc_t *cdesc = mkldnn_primitive_desc_query_md( + get(), mkldnn::convert_to_c(what), idx); + if (cdesc == nullptr) return memory::desc(); + + return memory::desc(*cdesc); + } + + // register specialized queries, e.g. src_desc() +# define REG_QUERY_MD(name, what, idx) \ + memory::desc name ## _desc() const { return query_md(what ## _md, idx); } + + private: + handle pd_iterator; + void fetch_impl() { + mkldnn_primitive_desc_t pd = mkldnn_primitive_desc_iterator_fetch( + pd_iterator.get()); + error::wrap_c_api(pd != nullptr ? mkldnn_success : mkldnn_runtime_error, + "could not fetch a primitive descriptor from the iterator"); + reset(pd); + } +}; + +/// @} + +/// @addtogroup cpp_api_convolution Convolution +/// A primitive to compute convolution using different algorithms. +/// +/// @sa @ref c_api_convolution in @ref c_api +/// @{ + +struct convolution_forward: public primitive { + struct desc { + mkldnn_convolution_desc_t data; + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &bias_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_convolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, &bias_desc.data, + &dst_desc.data, &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution forward descriptor"); + } + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_convolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, nullptr, + &dst_desc.data, &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution forward descriptor"); + } + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &bias_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api( + mkldnn_dilated_convolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, &bias_desc.data, + &dst_desc.data, &strides[0], &dilates[0], + &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated convolution forward descriptor"); + } + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api( + mkldnn_dilated_convolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, nullptr, + &dst_desc.data, &strides[0], &dilates[0], + &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated convolution forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(bias, weights, 1); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + convolution_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct convolution_backward_data : public primitive { + struct desc { + mkldnn_convolution_desc_t data; + desc(algorithm aalgorithm, + const memory::desc &diff_src_desc, + const memory::desc &weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_convolution_backward_data_desc_init( + &data, convert_to_c(aalgorithm), &diff_src_desc.data, + &weights_desc.data, &diff_dst_desc.data, + &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution backward data descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &diff_src_desc, + const memory::desc &weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api( + mkldnn_dilated_convolution_backward_data_desc_init( + &data, convert_to_c(aalgorithm), &diff_src_desc.data, + &weights_desc.data, &diff_dst_desc.data, + &strides[0], &dilates[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution backward data descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const convolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const convolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + convolution_backward_data(const primitive_desc &pd): primitive(pd) {} +}; + +struct convolution_backward_weights : public primitive { + struct desc { + mkldnn_convolution_desc_t data; + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_bias_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_convolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, &diff_bias_desc.data, + &diff_dst_desc.data, + &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution backward weights descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_convolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, nullptr, &diff_dst_desc.data, + &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution backward weights descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_bias_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_convolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, &diff_bias_desc.data, + &diff_dst_desc.data, + &strides[0], &dilates[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution backward weights descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_convolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, nullptr, &diff_dst_desc.data, + &strides[0], &dilates[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a convolution backward weights descriptor"); + } + + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const convolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const convolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(diff_weights, diff_weights, 0); + REG_QUERY_MD(diff_bias, diff_weights, 1); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + convolution_backward_weights(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} +// +/// @addtogroup cpp_api_deconvolution Deconvolution +/// A primitive to compute deconvolution using different algorithms. +/// +/// @sa @ref c_api_deconvolution in @ref c_api +/// @{ + +struct deconvolution_forward: public primitive { + struct desc { + mkldnn_deconvolution_desc_t data; + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &bias_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_deconvolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, &bias_desc.data, + &dst_desc.data, &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a deconvolution forward descriptor"); + } + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_deconvolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, nullptr, + &dst_desc.data, &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a deconvolution forward descriptor"); + } + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &bias_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_deconvolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, &bias_desc.data, + &dst_desc.data, &strides[0], &dilates[0], &padding_l[0], + &padding_r[0], mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated deconvolution forward descriptor"); + } + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_deconvolution_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, &weights_desc.data, nullptr, + &dst_desc.data, &strides[0], &dilates[0], &padding_l[0], + &padding_r[0], mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated deconvolution forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(bias, weights, 1); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + deconvolution_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct deconvolution_backward_data : public primitive { + struct desc { + mkldnn_deconvolution_desc_t data; + desc(algorithm aalgorithm, + const memory::desc &diff_src_desc, + const memory::desc &weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_deconvolution_backward_data_desc_init( + &data, convert_to_c(aalgorithm), &diff_src_desc.data, + &weights_desc.data, &diff_dst_desc.data, + &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a deconvolution backward data descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &diff_src_desc, + const memory::desc &weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_deconvolution_backward_data_desc_init( + &data, convert_to_c(aalgorithm), &diff_src_desc.data, + &weights_desc.data, &diff_dst_desc.data, + &strides[0], &dilates[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated deconvolution backward data descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const deconvolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const deconvolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + deconvolution_backward_data(const primitive_desc &pd): primitive(pd) {} +}; + +struct deconvolution_backward_weights : public primitive { + struct desc { + mkldnn_deconvolution_desc_t data; + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_bias_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_deconvolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, &diff_bias_desc.data, + &diff_dst_desc.data, + &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a deconvolution backward weights descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_deconvolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, nullptr, &diff_dst_desc.data, + &strides[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a deconvolution backward weights descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_bias_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_deconvolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, &diff_bias_desc.data, + &diff_dst_desc.data, + &strides[0], &dilates[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated deconvolution backward weights descriptor"); + } + desc(algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_dst_desc, + const memory::dims strides, + const memory::dims dilates, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(dilates); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_dilated_deconvolution_backward_weights_desc_init( + &data, convert_to_c(aalgorithm), &src_desc.data, + &diff_weights_desc.data, nullptr, &diff_dst_desc.data, + &strides[0], &dilates[0], &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not create a dilated deconvolution backward weights descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const deconvolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const deconvolution_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(diff_weights, diff_weights, 0); + REG_QUERY_MD(diff_bias, diff_weights, 1); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + deconvolution_backward_weights(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_lrn LRN +/// A primitive to perform local response normalization (LRN) across or within +/// channels. +/// +/// @sa @ref c_api_lrn in @ref c_api +/// @{ + +struct lrn_forward : public primitive { + struct desc { + mkldnn_lrn_desc_t data; + + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, memory::dim local_size, + float alpha, float beta, float k = 1.f) { + error::wrap_c_api(mkldnn_lrn_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), convert_to_c(aalgorithm), + &src_desc.data, local_size, alpha, beta, k), + "could not create a lrn forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + lrn_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct lrn_backward : public primitive { + struct desc { + mkldnn_lrn_desc_t data; + + desc(algorithm aalgorithm, const memory::desc &data_desc, + const memory::desc &diff_data_desc, memory::dim local_size, + float alpha, float beta, float k = 1.f) { + error::wrap_c_api(mkldnn_lrn_backward_desc_init(&data, + convert_to_c(aalgorithm), &diff_data_desc.data, + &data_desc.data, local_size, alpha, beta, k), + "could not create a lrn backward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const lrn_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const lrn_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + lrn_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_pooling Pooling +/// A primitive to perform max or average pooling. +/// +/// @sa @ref c_api_pooling in @ref c_api +/// @{ + +struct pooling_forward : public primitive { + struct desc { + mkldnn_pooling_desc_t data; + desc(prop_kind aprop_kind, algorithm aalgorithm, + const memory::desc &src_desc, + const memory::desc &dst_desc, + const memory::dims strides, + const memory::dims kernel, + const memory::dims padding_l, + const memory::dims padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(kernel); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_pooling_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), + convert_to_c(aalgorithm), + &src_desc.data, &dst_desc.data, + &strides[0], &kernel[0], + &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not init a forward pooling descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + pooling_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct pooling_backward : public primitive { + struct desc { + mkldnn_pooling_desc_t data; + desc(algorithm aalgorithm, + const memory::desc &diff_src_desc, + const memory::desc &diff_dst_desc, + const memory::dims &strides, + const memory::dims &kernel, + const memory::dims &padding_l, + const memory::dims &padding_r, + const padding_kind apadding_kind) { + memory::validate_dims(strides); + memory::validate_dims(kernel); + memory::validate_dims(padding_l); + memory::validate_dims(padding_r); + error::wrap_c_api(mkldnn_pooling_backward_desc_init(&data, + convert_to_c(aalgorithm), + &diff_src_desc.data, &diff_dst_desc.data, + &strides[0], &kernel[0], + &padding_l[0], &padding_r[0], + mkldnn::convert_to_c(apadding_kind)), + "could not init a backward pooling descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const pooling_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const pooling_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + pooling_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_eltwise Eltwise +/// A primitive to compute element-wise operations like parametric rectifier +/// linear unit (ReLU). +/// +/// @sa @ref c_api_eltwise in @ref c_api +/// @{ + +struct eltwise_forward : public primitive { + struct desc { + mkldnn_eltwise_desc_t data; + template + desc(prop_kind aprop_kind, algorithm alg_kind, + const memory::desc &src_desc, T alpha = 0, T beta = 0) { + error::wrap_c_api(mkldnn_eltwise_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), + mkldnn::convert_to_c(alg_kind), &src_desc.data, + static_cast(alpha), static_cast(beta)), + "could not create a eltwise forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + eltwise_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct eltwise_backward : public primitive { + struct desc { + mkldnn_eltwise_desc_t data; + + template + desc(algorithm alg_kind, const memory::desc &diff_data_desc, + const memory::desc &data_desc, T alpha = 0, T beta = 0) { + error::wrap_c_api(mkldnn_eltwise_backward_desc_init(&data, + mkldnn::convert_to_c(alg_kind), &diff_data_desc.data, + &data_desc.data, static_cast(alpha), + static_cast(beta)), + "could not create a eltwise backward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const eltwise_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const eltwise_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + eltwise_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_softmax Softmax +/// A primitive to perform softmax. +/// +/// @sa @ref c_api_softmax in @ref c_api +/// @{ + +struct softmax_forward : public primitive { + struct desc { + mkldnn_softmax_desc_t data; + desc(prop_kind aprop_kind, const memory::desc &data_desc, + int softmax_axis) { + error::wrap_c_api(mkldnn_softmax_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), &data_desc.data, + softmax_axis), + "could not create a softmax forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + softmax_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct softmax_backward : public primitive { + struct desc { + mkldnn_softmax_desc_t data; + desc(const memory::desc &diff_desc, const memory::desc &data_desc, + int softmax_axis) { + error::wrap_c_api(mkldnn_softmax_backward_desc_init(&data, + &diff_desc.data, &data_desc.data, softmax_axis), + "could not init a backward softmax descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const softmax_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const softmax_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + softmax_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_batch_norm Batch normalization +/// A primitive to perform batch normalization. +/// +/// @sa @ref c_api_batch_normalization in @ref c_api +/// @{ + +struct batch_normalization_forward : public primitive { + struct desc { + mkldnn_batch_normalization_desc_t data; + template + desc(prop_kind aprop_kind, const memory::desc &src_desc, T epsilon, + unsigned flags) { + error::wrap_c_api( + mkldnn_batch_normalization_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), &src_desc.data, + static_cast(epsilon), flags), + "could not create a batch normalization forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + + memory::desc mean_desc() const { return stat_desc(mean); } + memory::desc variance_desc() const { return stat_desc(var); } + + private: + enum { mean = 1, var = 2, }; + memory::desc stat_desc(int kind) const { + mkldnn_batch_normalization_desc_t *p; + error::wrap_c_api(mkldnn_primitive_desc_query( + get(), mkldnn::convert_to_c(batch_normalization_d), 0, &p), + "could not get a batch-normalization descriptor"); + return query_md(p->flags & use_global_stats ? src_md : dst_md, kind); + } + }; + + batch_normalization_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct batch_normalization_backward : public primitive { + struct desc { + mkldnn_batch_normalization_desc_t data; + template + desc(prop_kind aprop_kind, const memory::desc &diff_data_desc, + const memory::desc &data_desc, T epsilon, unsigned flags) { + error::wrap_c_api( + mkldnn_batch_normalization_backward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), + &diff_data_desc.data, &data_desc.data, + static_cast(epsilon), flags), + "could not create a batch normalization backward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const batch_normalization_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const batch_normalization_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(mean, src, 1); + REG_QUERY_MD(variance, src, 2); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(workspace, workspace, 0); + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(diff_weights, diff_weights, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + batch_normalization_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_inner_product Inner Product +/// A primitive to compute an inner product. +/// +/// @sa @ref c_api_inner_product in @ref c_api +/// @{ + +struct inner_product_forward: public primitive { + struct desc { + mkldnn_inner_product_desc_t data; + desc(prop_kind aprop_kind, const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &bias_desc, + const memory::desc &dst_desc) { + error::wrap_c_api( + mkldnn_inner_product_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), &src_desc.data, + &weights_desc.data, &bias_desc.data, &dst_desc.data), + "could not create a inner product forward descriptor"); + } + + desc(prop_kind aprop_kind, const memory::desc &src_desc, + const memory::desc &weights_desc, + const memory::desc &dst_desc) { + error::wrap_c_api( + mkldnn_inner_product_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), &src_desc.data, + &weights_desc.data, nullptr, &dst_desc.data), + "could not create a inner product forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(bias, weights, 1); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + inner_product_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct inner_product_backward_data: public primitive { + struct desc { + mkldnn_inner_product_desc_t data; + desc(const memory::desc &diff_src_desc, + const memory::desc &weights_desc, + const memory::desc &diff_dst_desc) { + error::wrap_c_api( + mkldnn_inner_product_backward_data_desc_init(&data, + &diff_src_desc.data, &weights_desc.data, + &diff_dst_desc.data), + "could not create a inner product backward data descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const inner_product_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const inner_product_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(weights, weights, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + inner_product_backward_data(const primitive_desc &pd): primitive(pd) {} +}; + +struct inner_product_backward_weights: public primitive { + struct desc { + mkldnn_inner_product_desc_t data; + desc(const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_bias_desc, + const memory::desc &diff_dst_desc) { + error::wrap_c_api( + mkldnn_inner_product_backward_weights_desc_init( + &data, &src_desc.data, &diff_weights_desc.data, + &diff_bias_desc.data, &diff_dst_desc.data), + "could not create a inner product backward weights descriptor"); + } + desc(const memory::desc &src_desc, + const memory::desc &diff_weights_desc, + const memory::desc &diff_dst_desc) { + error::wrap_c_api( + mkldnn_inner_product_backward_weights_desc_init( + &data, &src_desc.data, &diff_weights_desc.data, + nullptr, &diff_dst_desc.data), + "could not create a inner product backward weights descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const inner_product_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const inner_product_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(diff_weights, diff_weights, 0); + REG_QUERY_MD(diff_bias, diff_weights, 1); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + inner_product_backward_weights(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_rnn RNN +/// A primitive to compute common recurrent layer. +/// +/// @sa @ref c_api_rnn in @ref c_api +/// @{ + +struct rnn_cell { + struct desc { + mkldnn_rnn_cell_desc_t c_rnn_cell_; + + desc(algorithm kind, algorithm activation_f) { + error::wrap_c_api(mkldnn_rnn_cell_desc_init(&c_rnn_cell_, + mkldnn::convert_to_c(kind), + mkldnn::convert_to_c(activation_f), 0U, 0, 0), + "could not init an rnn cell descriptor"); + } + desc(algorithm kind): desc(kind, algorithm::algorithm_undef) {} + + operator const mkldnn_rnn_cell_desc_t*() const { return &c_rnn_cell_; } + + algorithm get_cell_kind() const + { return algorithm(c_rnn_cell_.cell_kind); } + algorithm get_activation() const + { return algorithm(c_rnn_cell_.activation_kind); } + + float get_alpha() const { return c_rnn_cell_.alpha; } + void set_alpha(float alpha) { + c_rnn_cell_.flags |= mkldnn_rnn_cell_with_relu; + c_rnn_cell_.alpha = alpha; + } + + float get_clipping() const { return c_rnn_cell_.clipping; } + void set_clipping(float clipping) { + c_rnn_cell_.flags |= mkldnn_rnn_cell_with_clipping; + c_rnn_cell_.clipping = clipping; + } + + int get_gates_count() const { + return mkldnn_rnn_cell_get_gates_count(&c_rnn_cell_); + } + int get_state_count() const { + return mkldnn_rnn_cell_get_states_count(&c_rnn_cell_); + } + }; +}; + +struct rnn_forward : public primitive { + struct desc { + mkldnn_rnn_desc_t data; + desc(prop_kind aprop_kind, rnn_cell::desc cell, + const rnn_direction direction, + const memory::desc &src_layer_desc, + const memory::desc &src_iter_desc, + const memory::desc &weights_layer_desc, + const memory::desc &weights_iter_desc, + const memory::desc &bias_desc, + const memory::desc &dst_layer_desc, + const memory::desc &dst_iter_desc + ) { + error::wrap_c_api(mkldnn_rnn_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), cell, + mkldnn::convert_to_c(direction), + &src_layer_desc.data, &src_iter_desc.data, + &weights_layer_desc.data, &weights_iter_desc.data, + &bias_desc.data, + &dst_layer_desc.data, &dst_iter_desc.data), + "could not create an RNN forward descriptor"); + } + + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e) + : mkldnn::primitive_desc(&desc.data, &attr, e, nullptr) {} + + REG_QUERY_MD(src_layer, src, 0); + REG_QUERY_MD(src_iter, src, 1); + REG_QUERY_MD(weights_layer, weights, 0); + REG_QUERY_MD(weights_iter, weights, 1); + REG_QUERY_MD(bias, weights, 2); + REG_QUERY_MD(dst_layer, dst, 0); + REG_QUERY_MD(dst_iter, dst, 1); + REG_QUERY_MD(workspace, workspace, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + rnn_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct rnn_backward : public primitive { + struct desc { + mkldnn_rnn_desc_t data; + desc(prop_kind aprop_kind, rnn_cell::desc cell, + const rnn_direction direction, + const memory::desc &src_layer_desc, + const memory::desc &src_iter_desc, + const memory::desc &weights_layer_desc, + const memory::desc &weights_iter_desc, + const memory::desc &bias_desc, + const memory::desc &dst_layer_desc, + const memory::desc &dst_iter_desc, + const memory::desc &diff_src_layer_desc, + const memory::desc &diff_src_iter_desc, + const memory::desc &diff_weights_layer_desc, + const memory::desc &diff_weights_iter_desc, + const memory::desc &diff_bias_desc, + const memory::desc &diff_dst_layer_desc, + const memory::desc &diff_dst_iter_desc) { + error::wrap_c_api(mkldnn_rnn_backward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), cell, + mkldnn::convert_to_c(direction), + &src_layer_desc.data, &src_iter_desc.data, + &weights_layer_desc.data, &weights_iter_desc.data, + &bias_desc.data, + &dst_layer_desc.data, &dst_iter_desc.data, + &diff_src_layer_desc.data, &diff_src_iter_desc.data, + &diff_weights_layer_desc.data, + &diff_weights_iter_desc.data, &diff_bias_desc.data, + &diff_dst_layer_desc.data, &diff_dst_iter_desc.data), + "could not create an RNN backward descriptor"); + } + + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const rnn_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + primitive_desc(const desc &desc, const primitive_attr &attr, const engine &e, + const rnn_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, &attr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(src_layer, src, 0); + REG_QUERY_MD(src_iter, src, 1); + REG_QUERY_MD(weights_layer, weights, 0); + REG_QUERY_MD(weights_iter, weights, 1); + REG_QUERY_MD(bias, weights, 2); + REG_QUERY_MD(dst_layer, dst, 0); + REG_QUERY_MD(dst_iter, dst, 1); + REG_QUERY_MD(workspace, workspace, 0); + + REG_QUERY_MD(diff_src_layer, diff_src, 0); + REG_QUERY_MD(diff_src_iter, diff_src, 1); + REG_QUERY_MD(diff_weights_layer, diff_weights, 0); + REG_QUERY_MD(diff_weights_iter, diff_weights, 1); + REG_QUERY_MD(diff_bias, diff_weights, 2); + REG_QUERY_MD(diff_dst_layer, diff_dst, 0); + REG_QUERY_MD(diff_dst_iter, diff_dst, 1); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + // With last iteration (with and without input src_iter) + rnn_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @addtogroup cpp_api_shuffle Shuffle +/// A primitive to shuffle data along the axis. +/// +/// @sa @ref c_api_shuffle in @ref c_api +/// @{ + +struct shuffle_forward : public primitive { + struct desc { + mkldnn_shuffle_desc_t data; + desc(prop_kind aprop_kind, const memory::desc &data_desc, + int axis, int group_size) { + error::wrap_c_api(mkldnn_shuffle_forward_desc_init(&data, + mkldnn::convert_to_c(aprop_kind), &data_desc.data, + axis, group_size), + "could not create a shuffle forward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e) + : mkldnn::primitive_desc(&desc.data, nullptr, e, nullptr) {} + + REG_QUERY_MD(src, src, 0); + REG_QUERY_MD(dst, dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + shuffle_forward(const primitive_desc &pd): primitive(pd) {} +}; + +struct shuffle_backward : public primitive { + struct desc { + mkldnn_shuffle_desc_t data; + desc(const memory::desc &diff_data_desc, int axis, int group_size) { + error::wrap_c_api(mkldnn_shuffle_backward_desc_init(&data, + &diff_data_desc.data, axis, group_size), + "could not create a shuffle backward descriptor"); + } + }; + + struct primitive_desc : public mkldnn::primitive_desc { + primitive_desc(const desc &desc, const engine &e, + const shuffle_forward::primitive_desc &hint_fwd_pd) + : mkldnn::primitive_desc(&desc.data, nullptr, e, hint_fwd_pd.get()) {} + + REG_QUERY_MD(diff_src, diff_src, 0); + REG_QUERY_MD(diff_dst, diff_dst, 0); + REG_QUERY_MD(scratchpad, scratchpad, 0); + }; + + shuffle_backward(const primitive_desc &pd): primitive(pd) {} +}; + +/// @} + +/// @} Primitives + +/// @} C++ API + +#undef REG_QUERY_MD + +// implementation section +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +inline primitive::primitive(const_mkldnn_primitive_desc_t c_pd) { + mkldnn_primitive_t result; + error::wrap_c_api(mkldnn_primitive_create(&result, c_pd), + "could not create a primitive"); + reset(result); +} + +inline primitive::primitive(const primitive_desc &pd): primitive(pd.get()) {} + +inline void primitive::execute(stream &astream, + const std::unordered_map &args) const { + std::vector c_args; + c_args.reserve(args.size()); + for (const auto &a: args) + c_args.push_back({a.first, a.second.get()}); + + error::wrap_c_api(mkldnn_primitive_execute(get(), astream.get(), + (int)c_args.size(), c_args.data()), + "primitive execution fail"); +} +#endif // DOXYGEN_SHOULD_SKIP_THIS + +} // namespace mkldnn + +#endif diff --git a/thirdparty/oidn/mkl-dnn/include/mkldnn_debug.h b/thirdparty/oidn/mkl-dnn/include/mkldnn_debug.h new file mode 100644 index 000000000000..f4dc2fdfa6ee --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/include/mkldnn_debug.h @@ -0,0 +1,98 @@ +/******************************************************************************* +* Copyright 2018-2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* DO NOT EDIT, AUTO-GENERATED */ + +#ifndef MKLDNN_DEBUG_H +#define MKLDNN_DEBUG_H + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/* All symbols shall be internal unless marked as MKLDNN_API */ +#if defined _WIN32 || defined __CYGWIN__ +# define MKLDNN_HELPER_DLL_IMPORT __declspec(dllimport) +# define MKLDNN_HELPER_DLL_EXPORT __declspec(dllexport) +#else +# if __GNUC__ >= 4 +# define MKLDNN_HELPER_DLL_IMPORT __attribute__ ((visibility ("default"))) +# define MKLDNN_HELPER_DLL_EXPORT __attribute__ ((visibility ("default"))) +# else +# define MKLDNN_HELPER_DLL_IMPORT +# define MKLDNN_HELPER_DLL_EXPORT +# endif +#endif + +#ifdef MKLDNN_DLL +# ifdef MKLDNN_DLL_EXPORTS +# define MKLDNN_API MKLDNN_HELPER_DLL_EXPORT +# else +# define MKLDNN_API MKLDNN_HELPER_DLL_IMPORT +# endif +#else +# define MKLDNN_API +#endif + +#if defined (__GNUC__) +# define MKLDNN_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define MKLDNN_DEPRECATED __declspec(deprecated) +#else +# define MKLDNN_DEPRECATED +#endif + +#include "mkldnn_types.h" +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +#ifdef __cplusplus +extern "C" { +#endif + +const char MKLDNN_API *mkldnn_status2str(mkldnn_status_t v); +const char MKLDNN_API *mkldnn_dt2str(mkldnn_data_type_t v); +const char MKLDNN_API *mkldnn_fmt_kind2str(mkldnn_format_kind_t v); +const char MKLDNN_API *mkldnn_fmt_tag2str(mkldnn_format_tag_t v); +const char MKLDNN_API *mkldnn_prop_kind2str(mkldnn_prop_kind_t v); +const char MKLDNN_API *mkldnn_prim_kind2str(mkldnn_primitive_kind_t v); +const char MKLDNN_API *mkldnn_alg_kind2str(mkldnn_alg_kind_t v); +const char MKLDNN_API *mkldnn_rnn_direction2str(mkldnn_rnn_direction_t v); + +/** Forms a format string for a given memory descriptor. + * + * The format is defined as: 'dt:[p|o|0]:fmt_kind:fmt:extra'. + * Here: + * - dt -- data type + * - p -- indicates there is non-trivial padding + * - o -- indicates there is non-trivial padding offset + * - 0 -- indicates there is non-trivial offset0 + * - fmt_kind -- format kind (blocked, wino, etc...) + * - fmt -- extended format string (format_kind specific) + * - extra -- shows extra fields (underspecified) + */ +int MKLDNN_API mkldnn_md2fmt_str(char *fmt_str, size_t fmt_str_len, + const mkldnn_memory_desc_t *md); + +/** Forms a dimension string for a given memory descriptor. + * + * The format is defined as: 'dim0xdim1x...xdimN + */ +int MKLDNN_API mkldnn_md2dim_str(char *dim_str, size_t dim_str_len, + const mkldnn_memory_desc_t *md); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/oidn/mkl-dnn/include/mkldnn_types.h b/thirdparty/oidn/mkl-dnn/include/mkldnn_types.h new file mode 100644 index 000000000000..1b6c35698205 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/include/mkldnn_types.h @@ -0,0 +1,1415 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_TYPES_H +#define MKLDNN_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#include +#include +#endif + +/** @addtogroup c_api C API + * @{ + * + * @addtogroup c_api_types Types + * @{ + * + * @addtogroup c_api_types_generic Generic + * @{ */ + +/** Intel(R) MKL-DNN Version type */ +typedef struct { + int major; + int minor; + int patch; + const char *hash; +} mkldnn_version_t; + +/** Status values returned by Intel(R) MKL-DNN functions. */ +typedef enum { + /** The operation was successful */ + mkldnn_success = 0, + /** The operation failed due to an out-of-memory condition */ + mkldnn_out_of_memory = 1, + /** The operation failed and should be retried */ + mkldnn_try_again = 2, + /** The operation failed because of incorrect function arguments */ + mkldnn_invalid_arguments = 3, + /** The operation failed because a primitive was not ready for execution */ + mkldnn_not_ready = 4, + /** The operation failed because requested functionality is not implemented + */ + mkldnn_unimplemented = 5, + /** Primitive iterator passed over last primitive descriptor */ + mkldnn_iterator_ends = 6, + /** Primitive or engine failed on execution */ + mkldnn_runtime_error = 7, + /** Queried element is not required for given primitive */ + mkldnn_not_required = 8, +} mkldnn_status_t; + +/** Data type specification */ +typedef enum { + /** Undefined data type, used for empty memory descriptors. */ + mkldnn_data_type_undef = 0, + /** 32-bit/single-precision floating point. */ + mkldnn_f32 = 1, + /** 32-bit signed integer. */ + mkldnn_s32 = 2, + /** 8-bit signed integer. */ + mkldnn_s8 = 3, + /** 8-bit unsigned integer. */ + mkldnn_u8 = 4, +} mkldnn_data_type_t; + +/** Memory format kind */ +typedef enum { + /** Undefined memory format, used for empty memory descriptors. */ + mkldnn_format_kind_undef = 0, + /** Unspecified format. The primitive selects a format automatically. */ + mkldnn_format_kind_any, + /** A tensor in a generic format described by the stride and blocking + * values in each dimension. See #mkldnn_blocking_desc_t for more + * information. */ + mkldnn_blocked, + /** Weights format used in 8bit Winograd convolution */ + mkldnn_format_kind_wino, + /** Packed weights format used in RNN */ + mkldnn_format_kind_rnn_packed, +} mkldnn_format_kind_t; + +/** Memory format tag specification. + * + * Intel MKL-DNN formats describe physical data layout. The physical layout + * is described as a sequence of the dimensions as they are laid out in the + * memory (from the outer-most to the inner-most). Note that this order + * doesn't affect the logical order of the dimensions that is kept in the + * `dims` field of the mkldnn_memory_desc_t structure. The logical order of the + * dimensions is specified by the type of tensor. + * + * For example, CNN 5D tensor always has its logical dimensions in the order + * `(batch, channels, depth, height, width)`, while the physical layout might be + * #mkldnn_ncdhw or #mkldnn_ndhwc: + * + * ~~~cpp + * int batch = 2, channels = 16, depth = 13, height = 13, width = 13; + * + * int ndims = 5; // 5D tensor + * mkldnn_dims_t dims = {batch, channels, depth, height, width}; + * mkldnn_memory_desc_t data_in_ncdhw; + * mkldnn_memory_desc_init_by_tag( + * &data_in_ncdhw, 5, dims, mkldnn_f32, mkldnn_ncdhw); + * + * // note that in both cases dims passed are the same + * mkldnn_memory_desc_t data_in_ndhwc; + * mkldnn_memory_desc_init_by_tag( + * &data_in_ndhwc, 5, dims, mkldnn_f32, mkldnn_ndhwc); + * ~~~ + * + * The following notation applies to memory format names: + * - @c 'n' denotes the mini-batch dimension + * - @c 'c' denotes a channels dimension + * - When there are multiple channel dimensions (for example, in convolution + * weights tensor), @c 'i' and @c 'o' denote dimensions of input and output + * channels + * - @c 'd', @c 'h', and @c 'w' denote spatial depth, height, and width + * respectively + * - Upper-case letters indicate that the data is laid out in blocks + * for a particular dimension. In such cases, the format name contains both + * upper- and lower-case letters for that dimension with a lower-case letter + * preceded by the block size. For example: @c 'mkldnn_nChw8c' describes a + * format where the outermost dimension is mini-batch, followed by the + * channel block number, followed by the spatial height and width, and + * finally followed by 8-element channel blocks. + * + * @note + * Channel designations can be different. For example, both the @c + * 'mkldnn_nc' and @c 'mkldnn_io' formats can be used to describe a 2D + * tensor. + * + * @sa @ref understanding_memory_formats + */ +typedef enum { + /** Undefined memory format tag */ + mkldnn_format_tag_undef = 0, + /** Undefined memory format tag. + * The primitive selects a format automatically. */ + mkldnn_format_tag_any, + + /* Semantic agnostic section */ + /* The physical order of dimensions is defined by the permutation of the + * characters, assuming that ab..z defines the natural order. + */ + + /* Plain formats */ + + mkldnn_a, + mkldnn_ab, + mkldnn_abc, + mkldnn_abcd, + mkldnn_abcde, + mkldnn_abcdef, + mkldnn_abdec, + mkldnn_acb, + mkldnn_acbde, + mkldnn_acdb, + mkldnn_acdeb, + mkldnn_ba, + mkldnn_bac, + mkldnn_bacd, + mkldnn_bcda, + mkldnn_cba, + mkldnn_cdba, + mkldnn_cdeba, + mkldnn_decab, + + /* Opaque blocked formats */ + + mkldnn_Abc16a, + mkldnn_ABc16a16b, + mkldnn_aBc16b, + mkldnn_ABc16b16a, + mkldnn_Abc4a, + mkldnn_aBc4b, + mkldnn_ABc4b16a4b, + mkldnn_ABc4b4a, + mkldnn_ABc8a16b2a, + mkldnn_ABc8a8b, + mkldnn_aBc8b, + mkldnn_ABc8b16a2b, + mkldnn_ABc8b8a, + mkldnn_Abcd16a, + mkldnn_ABcd16a16b, + mkldnn_aBcd16b, + mkldnn_ABcd16b16a, + mkldnn_aBCd16b16c, + mkldnn_aBCd16c16b, + mkldnn_Abcd4a, + mkldnn_aBcd4b, + mkldnn_ABcd4b16a4b, + mkldnn_ABcd4b4a, + mkldnn_aBCd4c16b4c, + mkldnn_aBCd4c4b, + mkldnn_ABcd8a16b2a, + mkldnn_ABcd8a8b, + mkldnn_aBcd8b, + mkldnn_ABcd8b16a2b, + mkldnn_aBCd8b16c2b, + mkldnn_ABcd8b8a, + mkldnn_aBCd8b8c, + mkldnn_aBCd8c16b2c, + mkldnn_aBCd8c8b, + mkldnn_Abcde16a, + mkldnn_ABcde16a16b, + mkldnn_aBcde16b, + mkldnn_ABcde16b16a, + mkldnn_aBCde16b16c, + mkldnn_aBCde16c16b, + mkldnn_aBCde2c8b4c, + mkldnn_Abcde4a, + mkldnn_aBcde4b, + mkldnn_ABcde4b4a, + mkldnn_aBCde4b4c, + mkldnn_aBCde4c16b4c, + mkldnn_aBCde4c4b, + mkldnn_Abcde8a, + mkldnn_ABcde8a8b, + mkldnn_aBcde8b, + mkldnn_ABcde8b16a2b, + mkldnn_aBCde8b16c2b, + mkldnn_ABcde8b8a, + mkldnn_aBCde8b8c, + mkldnn_aBCde8c16b2c, + mkldnn_aBCde8c8b, + mkldnn_aBcdef16b, + mkldnn_aBCdef16b16c, + mkldnn_aBCdef16c16b, + mkldnn_aBcdef4b, + mkldnn_aBCdef4c4b, + mkldnn_aBCdef8b8c, + mkldnn_aBCdef8c16b2c, + mkldnn_aBCdef8c8b, + mkldnn_aBdc16b, + mkldnn_aBdc4b, + mkldnn_aBdc8b, + mkldnn_aBdec16b, + mkldnn_aBdec4b, + mkldnn_aBdec8b, + mkldnn_aBdefc16b, + mkldnn_aBdefc4b, + mkldnn_aBdefc8b, + mkldnn_Acb16a, + mkldnn_Acb4a, + mkldnn_Acb8a, + mkldnn_aCBd16b16c, + mkldnn_aCBde16b16c, + mkldnn_Acdb16a, + mkldnn_Acdb4a, + mkldnn_Acdb8a, + mkldnn_Acdeb16a, + mkldnn_Acdeb4a, + mkldnn_Acdeb8a, + mkldnn_BAc16a16b, + mkldnn_BAcd16a16b, + + /** Just a sentinel, not real memory format tag. Must be changed after new + * format tag is added. */ + mkldnn_format_tag_last, + + /* Aliases */ + + mkldnn_x = mkldnn_a, + mkldnn_nc = mkldnn_ab, + mkldnn_cn = mkldnn_ba, + mkldnn_ncw = mkldnn_abc, + mkldnn_nwc = mkldnn_acb, + mkldnn_nchw = mkldnn_abcd, + mkldnn_nhwc = mkldnn_acdb, + mkldnn_chwn = mkldnn_bcda, + mkldnn_ncdhw = mkldnn_abcde, + mkldnn_ndhwc = mkldnn_acdeb, + + mkldnn_oi = mkldnn_ab, + mkldnn_io = mkldnn_ba, + mkldnn_oiw = mkldnn_abc, + mkldnn_wio = mkldnn_cba, + mkldnn_oihw = mkldnn_abcd, + mkldnn_hwio = mkldnn_cdba, + mkldnn_ihwo = mkldnn_bcda, + mkldnn_iohw = mkldnn_bacd, + mkldnn_oidhw = mkldnn_abcde, + mkldnn_dhwio = mkldnn_cdeba, + mkldnn_goiw = mkldnn_abcd, + mkldnn_goihw = mkldnn_abcde, + mkldnn_hwigo = mkldnn_decab, + mkldnn_giohw = mkldnn_acbde, + mkldnn_goidhw = mkldnn_abcdef, + + /** 3D RNN data tensor in the format (seq_length, batch, input channels). */ + mkldnn_tnc = mkldnn_abc, + /** 3D RNN data tensor in the format (batch, seq_length, input channels). */ + mkldnn_ntc = mkldnn_bac, + /** 5D RNN states tensor in the format (num_layers, num_directions, + * num_states, batch, state channels). */ + mkldnn_ldsnc = mkldnn_abcde, + /** 5D RNN weights tensor in the format (num_layers, num_directions, + * input_channels, num_gates, output_channels). + * + * - For LSTM cells, the gates order is input, forget, candidate + * and output gate. + * - For GRU cells, the gates order is update, reset and output gate. */ + mkldnn_ldigo = mkldnn_abcde, + /** 5D RNN weights tensor in the format (num_layers, num_directions, + * num_gates, output_channels, input_channels). + * + * - For LSTM cells, the gates order is input, forget, candidate + * and output gate. + * - For GRU cells, the gates order is update, reset and output gate. */ + mkldnn_ldgoi = mkldnn_abdec, + /** 4D RNN bias tensor in the format (num_layers, num_directions, + * num_gates, output_channels). + * + * - For LSTM cells, the gates order is input, forget, candidate + * and output gate. + * - For GRU cells, the gates order is update, reset and output gate. */ + mkldnn_ldgo = mkldnn_abcd, + + /* Opaque data types, are not to be used explicitly */ + + /* data */ + mkldnn_nCdhw16c = mkldnn_aBcde16b, + mkldnn_nCdhw4c = mkldnn_aBcde4b, + mkldnn_nCdhw8c = mkldnn_aBcde8b, + mkldnn_nChw16c = mkldnn_aBcd16b, + mkldnn_nChw4c = mkldnn_aBcd4b, + mkldnn_nChw8c = mkldnn_aBcd8b, + mkldnn_nCw16c = mkldnn_aBc16b, + mkldnn_nCw4c = mkldnn_aBc4b, + mkldnn_nCw8c = mkldnn_aBc8b, + + /* weights, 3D */ + mkldnn_IOw16o16i = mkldnn_BAc16a16b, + mkldnn_OIw16i16o = mkldnn_ABc16b16a, + mkldnn_OIw16o16i = mkldnn_ABc16a16b, + mkldnn_Oiw16o = mkldnn_Abc16a, + mkldnn_OIw4i16o4i = mkldnn_ABc4b16a4b, + mkldnn_OIw4i4o = mkldnn_ABc4b4a, + mkldnn_Oiw4o = mkldnn_Abc4a, + mkldnn_OIw8i16o2i = mkldnn_ABc8b16a2b, + mkldnn_OIw8i8o = mkldnn_ABc8b8a, + mkldnn_OIw8o16i2o = mkldnn_ABc8a16b2a, + mkldnn_OIw8o8i = mkldnn_ABc8a8b, + mkldnn_Owi16o = mkldnn_Acb16a, + mkldnn_Owi4o = mkldnn_Acb4a, + mkldnn_Owi8o = mkldnn_Acb8a, + + /* weights, 4D */ + mkldnn_IOhw16o16i = mkldnn_BAcd16a16b, + mkldnn_Ohwi16o = mkldnn_Acdb16a, + mkldnn_Ohwi4o = mkldnn_Acdb4a, + mkldnn_Ohwi8o = mkldnn_Acdb8a, + mkldnn_OIhw16i16o = mkldnn_ABcd16b16a, + mkldnn_OIhw16o16i = mkldnn_ABcd16a16b, + mkldnn_Oihw16o = mkldnn_Abcd16a, + mkldnn_OIhw4i16o4i = mkldnn_ABcd4b16a4b, + mkldnn_OIhw4i4o = mkldnn_ABcd4b4a, + mkldnn_Oihw4o = mkldnn_Abcd4a, + mkldnn_OIhw8i16o2i = mkldnn_ABcd8b16a2b, + mkldnn_OIhw8i8o = mkldnn_ABcd8b8a, + mkldnn_OIhw8o16i2o = mkldnn_ABcd8a16b2a, + mkldnn_OIhw8o8i = mkldnn_ABcd8a8b, + + /* weights, 5D */ + mkldnn_Odhwi16o = mkldnn_Acdeb16a, + mkldnn_Odhwi4o = mkldnn_Acdeb4a, + mkldnn_Odhwi8o = mkldnn_Acdeb8a, + mkldnn_OIdhw16i16o = mkldnn_ABcde16b16a, + mkldnn_OIdhw16o16i = mkldnn_ABcde16a16b, + mkldnn_Oidhw16o = mkldnn_Abcde16a, + mkldnn_OIdhw4i4o = mkldnn_ABcde4b4a, + mkldnn_Oidhw4o = mkldnn_Abcde4a, + mkldnn_OIdhw8i16o2i = mkldnn_ABcde8b16a2b, + mkldnn_OIdhw8i8o = mkldnn_ABcde8b8a, + mkldnn_OIdhw8o8i = mkldnn_ABcde8a8b, + + /* weights w/ groups, 3D */ + mkldnn_Goiw16g = mkldnn_Abcd16a, + mkldnn_gIOw16o16i = mkldnn_aCBd16b16c, + mkldnn_gOIw16i16o = mkldnn_aBCd16c16b, + mkldnn_gOIw16o16i = mkldnn_aBCd16b16c, + mkldnn_gOiw16o = mkldnn_aBcd16b, + mkldnn_gOIw4i16o4i = mkldnn_aBCd4c16b4c, + mkldnn_gOIw4i4o = mkldnn_aBCd4c4b, + mkldnn_gOiw4o = mkldnn_aBcd4b, + mkldnn_gOIw8i16o2i = mkldnn_aBCd8c16b2c, + mkldnn_gOIw8i8o = mkldnn_aBCd8c8b, + mkldnn_gOIw8o16i2o = mkldnn_aBCd8b16c2b, + mkldnn_gOIw8o8i = mkldnn_aBCd8b8c, + mkldnn_gOwi16o = mkldnn_aBdc16b, + mkldnn_gOwi4o = mkldnn_aBdc4b, + mkldnn_gOwi8o = mkldnn_aBdc8b, + + /* weights w/ groups, 4D */ + mkldnn_gIOhw16o16i = mkldnn_aCBde16b16c, + mkldnn_gOhwi16o = mkldnn_aBdec16b, + mkldnn_gOhwi4o = mkldnn_aBdec4b, + mkldnn_gOhwi8o = mkldnn_aBdec8b, + mkldnn_Goihw16g = mkldnn_Abcde16a, + mkldnn_gOIhw16i16o = mkldnn_aBCde16c16b, + mkldnn_gOIhw16o16i = mkldnn_aBCde16b16c, + mkldnn_gOihw16o = mkldnn_aBcde16b, + mkldnn_gOIhw2i8o4i = mkldnn_aBCde2c8b4c, + mkldnn_gOIhw4i16o4i = mkldnn_aBCde4c16b4c, + mkldnn_gOIhw4i4o = mkldnn_aBCde4c4b, + mkldnn_gOIhw4o4i = mkldnn_aBCde4b4c, + mkldnn_gOihw4o = mkldnn_aBcde4b, + mkldnn_Goihw8g = mkldnn_Abcde8a, + mkldnn_gOIhw8i16o2i = mkldnn_aBCde8c16b2c, + mkldnn_gOIhw8i8o = mkldnn_aBCde8c8b, + mkldnn_gOIhw8o16i2o = mkldnn_aBCde8b16c2b, + mkldnn_gOIhw8o8i = mkldnn_aBCde8b8c, + + /* weights w/ groups, 6D */ + mkldnn_gOdhwi16o = mkldnn_aBdefc16b, + mkldnn_gOdhwi4o = mkldnn_aBdefc4b, + mkldnn_gOdhwi8o = mkldnn_aBdefc8b, + mkldnn_gOIdhw16i16o = mkldnn_aBCdef16c16b, + mkldnn_gOIdhw16o16i = mkldnn_aBCdef16b16c, + mkldnn_gOidhw16o = mkldnn_aBcdef16b, + mkldnn_gOIdhw4i4o = mkldnn_aBCdef4c4b, + mkldnn_gOidhw4o = mkldnn_aBcdef4b, + mkldnn_gOIdhw8i16o2i = mkldnn_aBCdef8c16b2c, + mkldnn_gOIdhw8i8o = mkldnn_aBCdef8c8b, + mkldnn_gOIdhw8o8i = mkldnn_aBCdef8b8c, +} mkldnn_format_tag_t; + +/** Kinds of padding. Define how to interpret the data in padding regions. */ +typedef enum { + /** The data in padding regions is zero. */ + mkldnn_padding_zero, +} mkldnn_padding_kind_t; + +/** Kinds of propagation. */ +typedef enum { + /* TODO: suggest renames */ + /** Undefined propagation type. */ + mkldnn_prop_kind_undef = 0, + /** Forward data propagation (training mode). In this mode primitives + * perform computations necessary for subsequent backward propagation. */ + mkldnn_forward_training = 64, + /** Forward data propagation (inference mode). In this mode primitives + * perform only computations that are necessary for inference and omit + * computations that are necessary only for backward propagation. */ + mkldnn_forward_inference = 96, + /** Forward data propagation (alias for @c mkldnn_forward_inference) */ + mkldnn_forward_scoring = mkldnn_forward_inference, + /** Forward data propagation (alias for @c mkldnn_forward_training) */ + mkldnn_forward = mkldnn_forward_training, + /** Backward propagation (with respect to all parameters */ + mkldnn_backward = 128, + /** Backward data propagation */ + mkldnn_backward_data = 160, + /** Backward weights propagation */ + mkldnn_backward_weights = 192, + /** Backward bias propagation */ + mkldnn_backward_bias = 193, +} mkldnn_prop_kind_t; + +/** Kinds of primitives. Used to implement a way to extend the library with new + * primitives without changing the ABI. */ +typedef enum { + /** Undefined primitive (XXX: why do we have it?). */ + mkldnn_undefined_primitive, + /** A reorder primitive.*/ + mkldnn_reorder, + /** A shuffle primitive.*/ + mkldnn_shuffle, + /** A (out-of-place) concat primitive. */ + mkldnn_concat, + /** A sum primitive. */ + mkldnn_sum, + /** A convolution primitive. */ + mkldnn_convolution, + /** A deconvolution primitive. */ + mkldnn_deconvolution, + /** An element-wise primitive. */ + mkldnn_eltwise, + /** A Softmax primitive. */ + mkldnn_softmax, + /** A pooling primitive. */ + mkldnn_pooling, + /** An LRN primitive. */ + mkldnn_lrn, + /** An batch normalization primitive. */ + mkldnn_batch_normalization, + /** An inner product primitive. */ + mkldnn_inner_product, + /** A rnn primitive. */ + mkldnn_rnn, +} mkldnn_primitive_kind_t; + +/** Kinds of algorithms. */ +typedef enum { + mkldnn_alg_kind_undef, + /** Direct convolution */ + mkldnn_convolution_direct = 0x1, + /** Winograd convolution */ + mkldnn_convolution_winograd = 0x2, + /** Convolution algorithm(either direct or Winograd) is chosen just in time **/ + mkldnn_convolution_auto = 0x3, + /** Direct deconvolution */ + mkldnn_deconvolution_direct = 0xa, + /** Winograd deconvolution */ + mkldnn_deconvolution_winograd = 0xb, + /** Eltwise: ReLU */ + mkldnn_eltwise_relu = 0x1f, + /** Eltwise: hyperbolic tangent non-linearity (tanh) */ + mkldnn_eltwise_tanh = 0x2f, + /** Eltwise: parametric exponential linear unit (elu) */ + mkldnn_eltwise_elu = 0x3f, + /** Eltwise: square */ + mkldnn_eltwise_square = 0x4f, + /** Eltwise: abs */ + mkldnn_eltwise_abs = 0x5f, + /** Eltwise: square root */ + mkldnn_eltwise_sqrt = 0x6f, + /** Eltwise: linear */ + mkldnn_eltwise_linear = 0x7f, + /** Eltwise: bounded_relu */ + mkldnn_eltwise_bounded_relu = 0x8f, + /** Eltwise: soft_relu */ + mkldnn_eltwise_soft_relu = 0x9f, + /** Eltwise: logistic */ + mkldnn_eltwise_logistic = 0xaf, + /** Max pooling */ + mkldnn_pooling_max = 0x1ff, + /** Average pooling include padding */ + mkldnn_pooling_avg_include_padding = 0x2ff, + /** Average pooling exclude padding */ + mkldnn_pooling_avg_exclude_padding = 0x3ff, + mkldnn_pooling_avg = mkldnn_pooling_avg_exclude_padding, + /** Local response normalization (LRN) across multiple channels */ + mkldnn_lrn_across_channels = 0xaff, + /** LRN within a single channel */ + mkldnn_lrn_within_channel = 0xbff, + /** RNN cell */ + mkldnn_vanilla_rnn = 0x1fff, + /** LSTM cell */ + mkldnn_vanilla_lstm = 0x2fff, + /** GRU cell */ + mkldnn_vanilla_gru = 0x3fff, + /** GRU cell with linear before reset + * + * Modification of original GRU cell. Differs from #mkldnn_vanilla_gru + * in how the new memory gate is calculated: + * \f[ c_t = tanh(W_c*x_t + b_{c_x} + r_t*(U_c*h_{t-1}+b_{c_h})) \f] + * Primitive expects 4 biases on input: + * \f$[b_{u}, b_{r}, b_{c_x}, b_{c_h}]\f$ + * */ + mkldnn_gru_linear_before_reset = 0x4fff, +} mkldnn_alg_kind_t; + +/** Flags for batch-normalization primititve. */ +typedef enum { + /** Use global statistics + * + * If specified + * - on forward propagation use mean and variance provided by user (input) + * - on backward propagation reduces the amount of computations, since + * mean and variance are considered as constants + * + * If not specified: + * - on forward propagation mean and variance are computed and stored in + * output + * - on backward propagation compute full derivative wrt to data + */ + mkldnn_use_global_stats = 0x1U, + /** Use scale and shift parameters + * + * If specified: + * - on forward propagation use scale and shift (aka scale and bias) for + * the batch normalization results + * - on backward propagation (for prop_kind == #mkldnn_backward) compute + * diff wrt to scale and shift (hence one extra output used) + * + * If no specified: + * - on backward propagation prop_kind == #mkldnn_backward_data has the + * same behavior as prop_kind == #mkldnn_backward + */ + mkldnn_use_scaleshift = 0x2U, + /** Fuse with ReLU + * + * If specified: + * - on inference this option behaves the same as if the primitive were + * fused with ReLU via post ops API + * - on training primitive requires workspace (required to be able to + * perform backward pass) + */ + mkldnn_fuse_bn_relu = 0x4U, +} mkldnn_batch_normalization_flag_t; + +/** @} */ + +/** @addtogroup c_api_types_memory Memory + * @{ */ + +/** Maximum number of dimensions a tensor can have. Only restricts the amount + * of space used for the tensor description. Individual computational + * primitives may support only tensors of certain dimensions. */ +#define MKLDNN_MAX_NDIMS 12 + +/** A type to describe tensor dimension. */ +typedef int64_t mkldnn_dim_t; + +/** A type to describe tensor dimensions. */ +typedef mkldnn_dim_t mkldnn_dims_t[MKLDNN_MAX_NDIMS]; + +/** A type to describe strides within a tensor. */ +typedef mkldnn_dim_t mkldnn_strides_t[MKLDNN_MAX_NDIMS]; + +/** Generic description of blocked data layout for most memory formats. + * + * @sa @ref understanding_memory_formats */ +typedef struct { + /** The strides between the outermost blocks. + * In case of plain (non-blocked) formats the strides between dimensions. */ + mkldnn_dims_t strides; + /* Innermost section + * ASSUMPTION: the innermost blocks are always dense */ + /** The number of innermost blocks, e.g. 3 in case of `OIhw_4i16o4i_` */ + int inner_nblks; + /** The size of the blocks, e.g. `{4, 16, 4}` in case of `OIhw_4i16o4i` */ + mkldnn_dims_t inner_blks; + /** The logical indices of the blocks, e.g. `{1, 0, 1}` in case of + * `4i16o4i`, because `i` is the 1st dim and `o` is the 0st dim */ + mkldnn_dims_t inner_idxs; +} mkldnn_blocking_desc_t; + +typedef enum { + /** Undefined memory format, used for empty memory descriptors. */ + mkldnn_wino_undef = 0, + /** Tensors of weights for 2x3 winograd convolutions. */ + mkldnn_wino_wei_aaOIoi, + mkldnn_wino_wei_aaOio, + mkldnn_wino_wei_aaOBiOo, + /** Tensor of weights for 4x3 convolution. */ + mkldnn_wino_wei_OBaaIBOIio +} mkldnn_wino_memory_format_t; + +/** Description of tensor of weights for winograd 2x3 convolution. */ +typedef struct { + mkldnn_wino_memory_format_t wino_format; + int r; + int alpha; + int ic; + int oc; + int ic_block; + int oc_block; + int ic2_block; + int oc2_block; + float adj_scale; + size_t size; +} mkldnn_wino_desc_t; + +typedef enum { + mkldnn_packed_format_undef = 0, + mkldnn_ldigo_p, + mkldnn_ldgoi_p +} mkldnn_rnn_packed_memory_format_t; + +/* Maximum number of parts of RNN weights tensor that require separate + * computation. */ +#define MKLDNN_RNN_MAX_N_PARTS 4 + +/** Description of tensor of packed weights for rnn. */ +typedef struct { + mkldnn_rnn_packed_memory_format_t format; + int n_parts; + int n; + int parts[MKLDNN_RNN_MAX_N_PARTS]; + size_t part_pack_size[MKLDNN_RNN_MAX_N_PARTS]; + size_t offset_compensation; + size_t size; +} mkldnn_rnn_packed_desc_t; + +typedef enum { + mkldnn_memory_extra_flag_none = 0x0U, + /** Indicates the weights have an additional buffer, that depends on the + * @p compensation_mask. + * + * For instance, in 4D case with the compensation mask equals (1 << 0) + * the additional buffer would consist of OC values: + * O[oc : 0,OC] = + * -128 * SUM(ic : 0,IC; kh : 0,KH; kw : 0,KW){ weights(oc, ic, kh, kw) } + */ + mkldnn_memory_extra_flag_compensation_conv_s8s8 = 0x1U, + mkldnn_memory_extra_flag_scale_adjust = 0x2U, +} mkldnn_memory_extra_flags_t; + +/** Description of extra information stored in memory */ +typedef struct { + /** The flags contain arbitrary extra information, such as compensation. + * @sa mkldnn_memory_extra_flags_t */ + uint64_t flags; + /** Compensation mask */ + int compensation_mask; + /** Scale applied to the data */ + float scale_adjust; + /** For future backwards compatibility */ + char reserved[64]; +} mkldnn_memory_extra_desc_t; + +/** Memory descriptor. The description is based on a number of dimensions, + * dimensions themselves, plus information about elements type and memory + * format. Additionally, contains format-specific descriptions of the data + * layout. */ +typedef struct { + /** Number of dimensions */ + int ndims; + /** Dimensions in the following order: + * - CNN data tensors: mini-batch, channel, spatial + * ({N, C, [[D,] H,] W}) + * - CNN weight tensors: group (optional), output channel, input channel, + * spatial ({[G,] O, I, [[D,] H,] W}) + * - RNN data tensors: time, mini-batch, channels ({T, N, C}) + * or layers, directions, states, mini-batch, channels ({L, D, S, N, C}) + * - RNN weight tensor: layers, directions, input channel, gates, output channels + * ({L, D, I, G, O}). + * + * @note + * The order of dimensions does not depend on the memory format, so + * whether the data is laid out in #mkldnn_nchw or #mkldnn_nhwc + * the dims for 4D CN data tensor would be {N, C, H, W}. + */ + mkldnn_dims_t dims; + /** Data type of the tensor elements. */ + mkldnn_data_type_t data_type; + + /** Size of the data including padding in each dimension. */ + mkldnn_dims_t padded_dims; + /** Per-dimension offset from the padding to actual data, the top-level + * tensor with offsets applied must lie within the padding area. */ + mkldnn_dims_t padded_offsets; + + /** Offset from memory origin to the current block, non-zero only in + * a description of a memory sub-block. */ + mkldnn_dim_t offset0; + + /** Memory format kind. */ + mkldnn_format_kind_t format_kind; + union { + /** Description of the data layout for memory formats that use + * blocking. */ + mkldnn_blocking_desc_t blocking; + /** Tensor of weights for integer 8bit winograd convolution. */ + mkldnn_wino_desc_t wino_desc; + /** Tensor of packed weights for RNN. */ + mkldnn_rnn_packed_desc_t rnn_packed_desc; + /* ... other descriptions possible */ + } format_desc; + + mkldnn_memory_extra_desc_t extra; +} mkldnn_memory_desc_t; + +/** @struct mkldnn_memory + * An opaque structure to describe a memory. */ +struct mkldnn_memory; + +/** A memory handle. */ +typedef struct mkldnn_memory *mkldnn_memory_t; + +/** A constant memory handle. */ +typedef const struct mkldnn_memory *const_mkldnn_memory_t; + +#define MKLDNN_NATIVE_HANDLE_NONE (NULL) +#define MKLDNN_NATIVE_HANDLE_ALLOCATE ((void *)(size_t)-1) + +/** @} */ + +/** @addtogroup c_api_types_op_descs Operation descriptors + * @{*/ + +/** A pointer to any of the operation descriptors. */ +typedef void *mkldnn_op_desc_t; +/** A pointer to any of the operation descriptors (constant variant). */ +typedef const void *const_mkldnn_op_desc_t; + +/** A descriptor of a convolution operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_convolution. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, #mkldnn_backward_data, + * #mkldnn_backward_weights, and #mkldnn_backward_bias. */ + mkldnn_prop_kind_t prop_kind; + /** The kind of the convolution algorithm. Possible values: + * #mkldnn_convolution_direct. */ + mkldnn_alg_kind_t alg_kind; + /** Source memory descriptor. */ + mkldnn_memory_desc_t src_desc; + /** Source gradient memory descriptor. */ + mkldnn_memory_desc_t diff_src_desc; + /** Weights memory descriptor. */ + mkldnn_memory_desc_t weights_desc; + /** Weights gradient memory descriptor. */ + mkldnn_memory_desc_t diff_weights_desc; + /** Bias memory descriptor. */ + mkldnn_memory_desc_t bias_desc; + /** Bias gradient memory descriptor. */ + mkldnn_memory_desc_t diff_bias_desc; + /** Destination memory descriptor. */ + mkldnn_memory_desc_t dst_desc; + /** Destination gradient memory descriptor. */ + mkldnn_memory_desc_t diff_dst_desc; + /** Convolution strides in each spatial dimension. */ + mkldnn_dims_t strides; + /** Convolution dilates in each spatial dimension. */ + mkldnn_dims_t dilates; + /** Padding in each spatial dimension. padding[0] is a padding in the + * beginning (@p padding_l), padding[1] is a padding in the end (@p + * padding_r). */ + mkldnn_dims_t padding[2]; + /** The kind of padding to use. */ + mkldnn_padding_kind_t padding_kind; + /** The accumulator data type. Initialized automatically. */ + mkldnn_data_type_t accum_data_type; +} mkldnn_convolution_desc_t; + +/** A descriptor of a deconvolution operation. */ +typedef mkldnn_convolution_desc_t mkldnn_deconvolution_desc_t; + +/** A descriptor of a shuffle operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_convolution. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, and #mkldnn_backward_data. */ + mkldnn_prop_kind_t prop_kind; + /** Source and destination memory descriptor, + * and source and destination gradient memory descriptor. */ + mkldnn_memory_desc_t data_desc; + /** axis for shuffling. */ + int axis; + /** number of groups in group convolution */ + mkldnn_dim_t group_size; +} mkldnn_shuffle_desc_t; + +/** A descriptor of a element-wise operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_eltwise. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, #mkldnn_backward, and #mkldnn_backward_data. + */ + mkldnn_prop_kind_t prop_kind; + /** The kind of eltwise algorithm. Possible values: #mkldnn_eltwise_relu, + * #mkldnn_eltwise_tanh, #mkldnn_eltwise_elu, #mkldnn_eltwise_square, + * #mkldnn_eltwise_abs, #mkldnn_eltwise_sqrt, #mkldnn_eltwise_linear, + * #mkldnn_eltwise_bounded_relu, #mkldnn_eltwise_soft_relu, and + * #mkldnn_eltwise_logistic. */ + mkldnn_alg_kind_t alg_kind; + /** Source and destination memory descriptor. */ + mkldnn_memory_desc_t data_desc; + /** Source and destination gradient memory descriptor. */ + mkldnn_memory_desc_t diff_data_desc; + /** Algorithm specific parameter. + * Accordance table: + * - #mkldnn_eltwise_relu: @p alpha -- negative slope, @p beta ignored + * - #mkldnn_eltwise_tanh: @p alpha and @p beta ignored + * - #mkldnn_eltwise_elu: @p alpha -- negative slope, @p beta ignored + * - #mkldnn_eltwise_square: @p alpha and @p beta ignored + * - #mkldnn_eltwise_abs: @p alpha and @p beta ignored + * - #mkldnn_eltwise_sqrt: @p alpha and @p beta ignored + * - #mkldnn_eltwise_linear: @p alpha -- scale, @p beta -- shift + * - #mkldnn_eltwise_bounded_relu: @p alpha -- upper bound, @p beta ignored + * - #mkldnn_eltwise_soft_relu: @p alpha and @p beta ignored + * - #mkldnn_eltwise_logistic: @p alpha and @p beta ignored + */ + float alpha, beta; +} mkldnn_eltwise_desc_t; + +/** A descriptor of a Softmax operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_softmax. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training and + * #mkldnn_forward_inference. */ + mkldnn_prop_kind_t prop_kind; + /** Source and destination memory descriptor. */ + mkldnn_memory_desc_t data_desc; + /** Source and Destination of gradient memory descriptor. */ + mkldnn_memory_desc_t diff_desc; + /** The axis along which to perform the softmax. */ + int softmax_axis; +} mkldnn_softmax_desc_t; + +/** A descriptor of a pooling operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_pooling. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, #mkldnn_backward, and #mkldnn_backward_data. + */ + mkldnn_prop_kind_t prop_kind; + /** The kind of pooling algorithm. Possible values: #mkldnn_pooling_max and + * #mkldnn_pooling_avg. */ + mkldnn_alg_kind_t alg_kind; + /** Source memory descriptor. */ + mkldnn_memory_desc_t src_desc; + /** Source gradient memory descriptor. */ + mkldnn_memory_desc_t diff_src_desc; + /** Destination memory descriptor. */ + mkldnn_memory_desc_t dst_desc; + /** Destination gradient memory descriptor. */ + mkldnn_memory_desc_t diff_dst_desc; + /** Pooling kernel strides for spatial dimensions. */ + mkldnn_dims_t strides; + /** Pooling kernel spatial dimensions. */ + mkldnn_dims_t kernel; + /** Padding in each spatial dimension. padding[0] is a padding in the + * beginning (@p padding_l), padding[1] is a padding in the end (@p + * padding_r). */ + mkldnn_dims_t padding[2]; + /** The kind of padding to use. */ + mkldnn_padding_kind_t padding_kind; + /** The accumulator data type. Initialized automatically. */ + mkldnn_data_type_t accum_data_type; +} mkldnn_pooling_desc_t; + +/** A descriptor of a Local Response Normalization (LRN) operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_lrn. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, #mkldnn_backward, and #mkldnn_backward_data. + */ + mkldnn_prop_kind_t prop_kind; + /** LRN algorithm. Possible values: #mkldnn_lrn_within_channel and + * #mkldnn_lrn_across_channels. */ + mkldnn_alg_kind_t alg_kind; + /** Source and destination memory descriptor. */ + mkldnn_memory_desc_t data_desc; + /** Source and destination gradient memory descriptor. */ + mkldnn_memory_desc_t diff_data_desc; + /** The number of channels to sum over (for cross-channel LRN) or the side + * length of the square region to sum over (for within-channel LRN). */ + mkldnn_dim_t local_size; + /** LRN alpha parameter. */ + float lrn_alpha; + /** LRN beta parameter. */ + float lrn_beta; + /** LRN k parameter. */ + float lrn_k; +} mkldnn_lrn_desc_t; + +/** A descriptor of a Batch Normalization operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_batch_normalization. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, #mkldnn_backward, and #mkldnn_backward_data. + */ + mkldnn_prop_kind_t prop_kind; + /** Source and destination memory descriptor. */ + mkldnn_memory_desc_t data_desc; + /** Source and destination gradient memory descriptor. */ + mkldnn_memory_desc_t diff_data_desc; + /** Scale and shift data and gradient memory descriptors. + * + * Scaleshift memory descriptor uses 2D #mkldnn_nc format[2,Channels]. 1-st + * dimension contains gamma parameter, 2-nd dimension contains beta + * parameter. */ + mkldnn_memory_desc_t data_scaleshift_desc; + mkldnn_memory_desc_t diff_data_scaleshift_desc; + /** Mean and variance data memory descriptors. + * + * Mean and variance memory descriptors use 1D #mkldnn_x format[Channels]. + */ + mkldnn_memory_desc_t mean_desc; + mkldnn_memory_desc_t variance_desc; + /** Batch normalization epsilon parameter. */ + float batch_norm_epsilon; + unsigned flags; +} mkldnn_batch_normalization_desc_t; + +/** A descriptor of an inner product operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_inner_product. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, #mkldnn_backward_data, + * #mkldnn_backward_weights, and #mkldnn_backward_bias. */ + mkldnn_prop_kind_t prop_kind; + /** Source memory descriptor. */ + mkldnn_memory_desc_t src_desc; + /** Source gradient memory descriptor. */ + mkldnn_memory_desc_t diff_src_desc; + /** Weights memory descriptor. */ + mkldnn_memory_desc_t weights_desc; + /** Weights gradient memory descriptor. */ + mkldnn_memory_desc_t diff_weights_desc; + /** Bias memory descriptor. */ + mkldnn_memory_desc_t bias_desc; + /** Bias gradient memory descriptor. */ + mkldnn_memory_desc_t diff_bias_desc; + /** Destination memory descriptor. */ + mkldnn_memory_desc_t dst_desc; + /** Destination gradient memory descriptor. */ + mkldnn_memory_desc_t diff_dst_desc; + /** The accumulator data type. Initialized automatically. */ + mkldnn_data_type_t accum_data_type; +} mkldnn_inner_product_desc_t; + +/** Flags for RNN cell. */ +typedef enum { + mkldnn_rnn_cell_with_relu = 0x1U, + mkldnn_rnn_cell_with_clipping = 0x2U, +} mkldnn_rnn_cell_flags_t; + +typedef struct { + /** RNN cell kind. Must be one of #mkldnn_vanilla_rnn, + * #mkldnn_vanilla_lstm, #mkldnn_vanilla_gru, + * or #mkldnn_gru_linear_before_reset. */ + mkldnn_alg_kind_t cell_kind; + /** Activation function used. Must be either #mkldnn_eltwise_relu or + * #mkldnn_eltwise_tanh. */ + mkldnn_alg_kind_t activation_kind; + /** RNN cell flags */ + unsigned int flags; + /** @c alpha is a negative slope parameter (used only if + * `(flags & #mkldnn_rnn_cell_with_relu) != 0`) */ + float alpha; + /** clipping parameter (used only if + * `(flags & #mkldnn_rnn_cell_with_clipping) != 0`) */ + float clipping; +} mkldnn_rnn_cell_desc_t; + +/** A direction of RNN primitive execution. */ +typedef enum { + /* Unidirectional execution of RNN primitive from left to right. */ + mkldnn_unidirectional_left2right, + /* Unidirectional execution of RNN primitive from right to left. */ + mkldnn_unidirectional_right2left, + /* Bidirectional execution of RNN primitive with concatenation of the + * results. */ + mkldnn_bidirectional_concat, + /* Bidirectional execution of RNN primitive with summation of the + * results. */ + mkldnn_bidirectional_sum, + mkldnn_unidirectional = mkldnn_unidirectional_left2right, +} mkldnn_rnn_direction_t; + +/** A descriptor for an RNN operation. */ +typedef struct { + /** The kind of primitive. Used for self-identifying the primitive + * descriptor. Must be #mkldnn_rnn. */ + mkldnn_primitive_kind_t primitive_kind; + /** The kind of propagation. Possible values: #mkldnn_forward_training, + * #mkldnn_forward_inference, and #mkldnn_backward. */ + mkldnn_prop_kind_t prop_kind; + /** The RNN cell desc. */ + mkldnn_rnn_cell_desc_t cell_desc; + /** The direction of RNN primitive execution. */ + mkldnn_rnn_direction_t direction; + /** Source layer memory descriptor. */ + mkldnn_memory_desc_t src_layer_desc; + /** Source iteration memory descriptor. */ + mkldnn_memory_desc_t src_iter_desc; + /** Weights layer memory descriptor. */ + mkldnn_memory_desc_t weights_layer_desc; + /** Weights iteration memory descriptor. */ + mkldnn_memory_desc_t weights_iter_desc; + /** Bias memory descriptor. */ + mkldnn_memory_desc_t bias_desc; + /** Destination layer memory descriptor. */ + mkldnn_memory_desc_t dst_layer_desc; + /** Destination iter memory descriptor. */ + mkldnn_memory_desc_t dst_iter_desc; + /** Source gradient layer memory descriptor. */ + mkldnn_memory_desc_t diff_src_layer_desc; + /** Source gradient iter memory descriptor. */ + mkldnn_memory_desc_t diff_src_iter_desc; + /** Weights gradient layer memory descriptor. */ + mkldnn_memory_desc_t diff_weights_layer_desc; + /** Weights gradient iter memory descriptor. */ + mkldnn_memory_desc_t diff_weights_iter_desc; + /** Bias gradient memory descriptor. */ + mkldnn_memory_desc_t diff_bias_desc; + /** Destination gradient layer memory descriptor. */ + mkldnn_memory_desc_t diff_dst_layer_desc; + /** Destination gradient iteration memory descriptor. */ + mkldnn_memory_desc_t diff_dst_iter_desc; +} mkldnn_rnn_desc_t; + +/** @} */ + +/** @addtogroup c_api_engine_types Engine + * @{ */ + +/** @brief Kinds of engines. */ +typedef enum { + /** An unspecified engine. */ + mkldnn_any_engine, + /** CPU engine. */ + mkldnn_cpu, +} mkldnn_engine_kind_t; + +/** @struct mkldnn_engine + * @brief An opaque structure to describe an engine. */ +struct mkldnn_engine; +/** @brief An engine handle. */ +typedef struct mkldnn_engine *mkldnn_engine_t; +#if 0 +/* FIXME: looks like this never happens */ +/** @brief A constant engine handle. */ +typedef const struct mkldnn_engine *const_mkldnn_engine_t; +#endif + +/** @} */ + +/** @addtogroup c_api_primitive_desc_iterators Primitive descriptor iterators + * @{ */ + +/** @struct mkldnn_primitive_desc_iterator + * @brief An opaque structure to describe a primitive descriptor iterator. */ +struct mkldnn_primitive_desc_iterator; + +/** @brief A primitive descriptor iterator handle. */ +typedef struct mkldnn_primitive_desc_iterator + *mkldnn_primitive_desc_iterator_t; + +/** @brief A constant primitive descriptor iterator handle. */ +typedef const struct mkldnn_primitive_desc_iterator + *const_mkldnn_primitive_desc_iterator_t; + +/** @} */ + +/** @addtogroup c_api_primitive_descs Primitive descriptors + * @{ */ + +/** @struct mkldnn_primitive_desc + * @brief An opaque structure to describe a primitive descriptor. */ +struct mkldnn_primitive_desc; + +/** @brief A primitive descriptor handle. */ +typedef struct mkldnn_primitive_desc *mkldnn_primitive_desc_t; + +/** @brief A constant primitive descriptor handle. */ +typedef const struct mkldnn_primitive_desc *const_mkldnn_primitive_desc_t; + +/** @} */ + +/** @addtogroup c_api_primitive_attr Primitive descriptor attributes + * @{ */ + +/** Scratchpad mode */ +typedef enum { + /** The library manages scratchpad (default) */ + mkldnn_scratchpad_mode_library, + /** A user shall query and provide the scratchpad memory to primitives */ + mkldnn_scratchpad_mode_user, +} mkldnn_scratchpad_mode_t; + +/** @struct mkldnn_primitive_attr + * @brief An opaque structure for primitive descriptor attributes. + * + * Attributes may contain: + * - output scales (to scale the result prior to storing it to the memory) + */ +struct mkldnn_primitive_attr; + +/** @brief A primitive descriptor attributes handle that controls primitive + * behavior. */ +typedef struct mkldnn_primitive_attr *mkldnn_primitive_attr_t; + +/** @brief A constant primitive descriptor attributes handle. */ +typedef const struct mkldnn_primitive_attr *const_mkldnn_primitive_attr_t; + +/** @struct mkldnn_post_ops + * @brief An opaque structure for a chain of post operations. + * + * mkldnn_post_ops can be used to perform some (trivial) operations like + * accumulation or eltwise after certain primitives like convolution. + * + * Post operations might be combined together, making a chain of post + * operations. For instance one can configure convolution followed by + * accumulation followed by eltwise. This might be especially beneficial + * for residual learning blocks. + * + * @warning + * Of course not all combinations are supported, so the user should handle + * errors accordingly. + * + * Supported post operations: + * - accumulation (base primitive: convolution) + * - eltwise (base primitive: convolution) + */ +struct mkldnn_post_ops; + +/** @brief A post operation chain handle. */ +typedef struct mkldnn_post_ops *mkldnn_post_ops_t; + +/** @brief A constant post operation chain handle. */ +typedef const struct mkldnn_post_ops *const_mkldnn_post_ops_t; + +/** @} */ + +/** @addtogroup c_api_types_primitive Primitive + * @{ */ + +/** @struct mkldnn_primitive + * An opaque structure to describe a primitive. */ +struct mkldnn_primitive; +/** A primitive handle. */ +typedef struct mkldnn_primitive *mkldnn_primitive_t; +/** A constant primitive handle. */ +typedef const struct mkldnn_primitive *const_mkldnn_primitive_t; + +/** @addtogroup c_api_types_arguments Argument indices + * @{ */ + +#define MKLDNN_ARG_SRC_0 1 +#define MKLDNN_ARG_SRC MKLDNN_ARG_SRC_0 +#define MKLDNN_ARG_SRC_LAYER MKLDNN_ARG_SRC_0 +#define MKLDNN_ARG_FROM MKLDNN_ARG_SRC_0 + +#define MKLDNN_ARG_SRC_1 2 +#define MKLDNN_ARG_SRC_ITER MKLDNN_ARG_SRC_1 + +#define MKLDNN_ARG_DST_0 17 +#define MKLDNN_ARG_DST MKLDNN_ARG_DST_0 +#define MKLDNN_ARG_TO MKLDNN_ARG_DST_0 +#define MKLDNN_ARG_DST_LAYER MKLDNN_ARG_DST_0 + +#define MKLDNN_ARG_DST_1 18 +#define MKLDNN_ARG_DST_ITER MKLDNN_ARG_DST_1 + +#define MKLDNN_ARG_WEIGHTS_0 33 +#define MKLDNN_ARG_WEIGHTS MKLDNN_ARG_WEIGHTS_0 +#define MKLDNN_ARG_SCALE_SHIFT MKLDNN_ARG_WEIGHTS_0 +#define MKLDNN_ARG_WEIGHTS_LAYER MKLDNN_ARG_WEIGHTS_0 + +#define MKLDNN_ARG_WEIGHTS_1 34 +#define MKLDNN_ARG_WEIGHTS_ITER MKLDNN_ARG_WEIGHTS_1 + +#define MKLDNN_ARG_BIAS 41 + +#define MKLDNN_ARG_MEAN 49 +#define MKLDNN_ARG_VARIANCE 50 + +#define MKLDNN_ARG_WORKSPACE 64 +#define MKLDNN_ARG_SCRATCHPAD 80 + +#define MKLDNN_ARG_DIFF_SRC_0 129 +#define MKLDNN_ARG_DIFF_SRC MKLDNN_ARG_DIFF_SRC_0 +#define MKLDNN_ARG_DIFF_SRC_LAYER MKLDNN_ARG_DIFF_SRC_0 + +#define MKLDNN_ARG_DIFF_SRC_1 130 +#define MKLDNN_ARG_DIFF_SRC_ITER MKLDNN_ARG_DIFF_SRC_1 + +#define MKLDNN_ARG_DIFF_DST_0 145 +#define MKLDNN_ARG_DIFF_DST MKLDNN_ARG_DIFF_DST_0 +#define MKLDNN_ARG_DIFF_DST_LAYER MKLDNN_ARG_DIFF_DST_0 + +#define MKLDNN_ARG_DIFF_DST_1 146 +#define MKLDNN_ARG_DIFF_DST_ITER MKLDNN_ARG_DIFF_DST_1 + +#define MKLDNN_ARG_DIFF_WEIGHTS_0 161 +#define MKLDNN_ARG_DIFF_WEIGHTS MKLDNN_ARG_DIFF_WEIGHTS_0 +#define MKLDNN_ARG_DIFF_SCALE_SHIFT MKLDNN_ARG_DIFF_WEIGHTS_0 +#define MKLDNN_ARG_DIFF_WEIGHTS_LAYER MKLDNN_ARG_DIFF_WEIGHTS_0 + +#define MKLDNN_ARG_DIFF_WEIGHTS_1 162 +#define MKLDNN_ARG_DIFF_WEIGHTS_ITER MKLDNN_ARG_DIFF_WEIGHTS_1 + +#define MKLDNN_ARG_DIFF_BIAS 169 + +#define MKLDNN_ARG_MULTIPLE_SRC 1024 +#define MKLDNN_ARG_MULTIPLE_DST 2048 + +/** @} */ + +/** An auxiliary structure to specify primitive's inputs/outputs at execution + * + * @warning + * With this API it's impossible to preserve constness of memory, so all + * memories are passed w/o const qualifier. However only memories with + * output semantics might be changed during the execution */ +typedef struct { + int arg; /**< An argument index, e.g. MKLDNN_ARG_SRC */ + mkldnn_memory_t memory; /**< Input/output memory */ +} mkldnn_exec_arg_t; + +/** @} */ + +/** @addtogroup c_api_types_query Queries + * @{ */ + +/** Primitive descriptor query specification + * + * For generic function mkldnn_primitive_desc_query(), the type of result must + * agree with the queried argument. The correspondence table: + * Query | type of result + * -------------------------------------------------------------- + * #mkldnn_query_engine | mkldnn_engine_t * + * #mkldnn_query_scratchpad_engine | mkldnn_engine_t * + * #mkldnn_query_primitive_kind | mkldnn_primitive_kind_t * + * *_s32 | int * + * *_s64 | mkldnn_dim_t * (same as int64_t *) + * *_f64 | double * + * *_str | const char ** + * #mkldnn_query_op_d | const_mkldnn_op_desc_t * + * *_md | const mkldnn_memory_desc_t ** + * *_${op}_d | const mkldnn_${op}_desc_t ** + * *_pd | const_mkldnn_primitive_desc_t * + * + * @note + * Rule of thumb: all opaque types and structures are returned by + * reference. All numbers are returned by value. + * + * @warning + * All returned references point to constant objects and are valid only + * during the lifetime of the queried primitive descriptor. Returned objects + * must not be destroyed by the user. If you need to keep the object longer + * than the lifetime of the queried primitive descriptor, use + * mkldnn_primitive_desc_clone() to make a copy. */ +typedef enum { + mkldnn_query_undef = 0, /**< no query */ + + mkldnn_query_engine, /**< execution engine */ + mkldnn_query_primitive_kind, /**< primitive kind */ + + mkldnn_query_num_of_inputs_s32, /**< number of inputs expected */ + mkldnn_query_num_of_outputs_s32, /**< number of outputs expected */ + + mkldnn_query_time_estimate_f64, /**< runtime estimation (seconds) */ + mkldnn_query_memory_consumption_s64, /**< memory consumption -- extra + (scratch) memory, additional to all + inputs and outputs memory (bytes) */ + + mkldnn_query_scratchpad_engine, /**< scratchpad engine -- engine to be used + for creating scratchpad memory */ + + mkldnn_query_impl_info_str, /**< implementation name */ + + /* memory and op descriptor section */ + mkldnn_query_some_d = 64, /**< stub */ + mkldnn_query_op_d, /**< op descriptor */ + mkldnn_query_convolution_d, /**< convolution descriptor */ + mkldnn_query_deconvolution_d, /**< deconvolution descriptor */ + mkldnn_query_shuffle_d, /**< shuffle descriptor */ + mkldnn_query_eltwise_d, /**< eltwise descriptor */ + mkldnn_query_softmax_d, /**< softmax descriptor */ + mkldnn_query_pooling_d, /**< pooling descriptor */ + mkldnn_query_lrn_d, /**< lrn descriptor */ + mkldnn_query_batch_normalization_d, /**< batch normalization descriptor */ + mkldnn_query_inner_product_d, /**< inner product descriptor */ + mkldnn_query_rnn_d, /**< rnn descriptor */ + + /* memory descriptor section */ + mkldnn_query_some_md = 128, /**< stub */ + mkldnn_query_src_md, /**< source memory desc */ + mkldnn_query_diff_src_md, /**< source gradient memory desc */ + mkldnn_query_weights_md, /**< weights memory descriptor desc */ + mkldnn_query_diff_weights_md, /**< weights grad. memory desc */ + mkldnn_query_dst_md, /**< destination memory desc */ + mkldnn_query_diff_dst_md, /**< destination grad. memory desc */ + mkldnn_query_workspace_md, /**< workspace memory desc */ + mkldnn_query_scratchpad_md, /**< scratchpad memory desc */ +} mkldnn_query_t; + +/** @} */ + +/** @addtogroup c_api_types_stream Execution stream + * @{ */ + +/** @brief Stream flags. */ +typedef enum { + /** A default stream configuration. */ + mkldnn_stream_default_flags = 0x0U, +} mkldnn_stream_flags_t; + +/** @struct mkldnn_stream + * An opaque structure to describe an execution stream. */ +struct mkldnn_stream; +/** An execution stream handle. */ +typedef struct mkldnn_stream *mkldnn_stream_t; +/** A constant execution stream handle. */ +typedef const struct mkldnn_stream *const_mkldnn_stream_t; + +/** @} */ +/** @} */ +/** @} */ + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/thirdparty/oidn/mkl-dnn/include/mkldnn_version.h b/thirdparty/oidn/mkl-dnn/include/mkldnn_version.h new file mode 100644 index 000000000000..a2713deccb3c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/include/mkldnn_version.h @@ -0,0 +1,32 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_VERSION_H +#define MKLDNN_VERSION_H + +/* Major version of MKL-DNN */ +#define MKLDNN_VERSION_MAJOR 0 + +/* Minor version of MKL-DNN */ +#define MKLDNN_VERSION_MINOR 90 + +/* Patch version of MKL-DNN */ +#define MKLDNN_VERSION_PATCH 0 + +/* Git Commit Hash of MKL-DNN */ +#define MKLDNN_VERSION_HASH "096bda1ca23324879f2df5a129e610e4405f775c" + +#endif diff --git a/thirdparty/oidn/mkl-dnn/include/mkldnn_version.h.in b/thirdparty/oidn/mkl-dnn/include/mkldnn_version.h.in new file mode 100644 index 000000000000..5ee0126188ba --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/include/mkldnn_version.h.in @@ -0,0 +1,32 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_VERSION_H +#define MKLDNN_VERSION_H + +/* Major version of MKL-DNN */ +#define MKLDNN_VERSION_MAJOR @MKLDNN_VERSION_MAJOR@ + +/* Minor version of MKL-DNN */ +#define MKLDNN_VERSION_MINOR @MKLDNN_VERSION_MINOR@ + +/* Patch version of MKL-DNN */ +#define MKLDNN_VERSION_PATCH @MKLDNN_VERSION_PATCH@ + +/* Git Commit Hash of MKL-DNN */ +#define MKLDNN_VERSION_HASH "@MKLDNN_VERSION_HASH@" + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/batch_normalization.cpp b/thirdparty/oidn/mkl-dnn/src/common/batch_normalization.cpp new file mode 100644 index 000000000000..1a51d8562bfa --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/batch_normalization.cpp @@ -0,0 +1,104 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t bnrm_desc_init(batch_normalization_desc_t *bnrm_desc, + prop_kind_t prop_kind, const memory_desc_t *data_desc, + const memory_desc_t *diff_data_desc, float epsilon, unsigned flags) { + bool args_ok = true + && !any_null(bnrm_desc, data_desc) + && one_of(prop_kind, forward_training, forward_inference, + backward_data, backward) + && IMPLICATION(prop_kind & backward, diff_data_desc != nullptr); + if (!args_ok) return invalid_arguments; + + auto bd = batch_normalization_desc_t(); + bd.primitive_kind = primitive_kind::batch_normalization; + bd.prop_kind = prop_kind; + + bd.data_desc = *data_desc; + bd.diff_data_desc = zero_md(); + if ( one_of(bd.prop_kind,backward_data, backward) ) + bd.diff_data_desc = *diff_data_desc; + + dims_t scaleshift_dims = { 2, data_desc->dims[1] }; + mkldnn_memory_desc_init_by_tag(&bd.data_scaleshift_desc, 2, + scaleshift_dims, data_type::f32, mkldnn_nc); + bd.diff_data_scaleshift_desc = zero_md(); + if (bd.prop_kind == backward) { + bd.diff_data_scaleshift_desc = bd.data_scaleshift_desc; + } + + dims_t stats_dims = { data_desc->dims[1] }; + mkldnn_memory_desc_init_by_tag(&bd.mean_desc, 1, stats_dims, + data_type::f32, mkldnn_x); + bd.variance_desc = bd.mean_desc; + bd.batch_norm_epsilon = epsilon; + + unsigned bnorm_flags = + mkldnn_use_global_stats | mkldnn_use_scaleshift | mkldnn_fuse_bn_relu; + if ((~bnorm_flags & flags) != 0) return invalid_arguments; + + bd.flags = flags; + + bool consistency = true + && utils::one_of(bd.data_desc.ndims, 2, 4, 5); + if (bd.prop_kind == backward_data) + consistency = consistency + && utils::one_of(bd.diff_data_desc.ndims, 2, 4, 5) + && array_cmp(bd.diff_data_desc.dims, bd.data_desc.dims, + bd.diff_data_desc.ndims); + if (!consistency) return invalid_arguments; + + *bnrm_desc = bd; + return success; +} +} + +status_t mkldnn_batch_normalization_forward_desc_init( + batch_normalization_desc_t *bnrm_desc, prop_kind_t prop_kind, + const memory_desc_t *data_desc, float epsilon, unsigned flags) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return bnrm_desc_init(bnrm_desc, prop_kind, data_desc, nullptr, + epsilon, flags); +} + +status_t mkldnn_batch_normalization_backward_desc_init( + batch_normalization_desc_t *bnrm_desc, prop_kind_t prop_kind, + const memory_desc_t *diff_data_desc, const memory_desc_t *data_desc, + float epsilon, unsigned flags) { + if (!one_of(prop_kind, backward, backward_data)) + return invalid_arguments; + return bnrm_desc_init(bnrm_desc, prop_kind, data_desc, diff_data_desc, + epsilon, flags); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/batch_normalization_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/batch_normalization_pd.hpp new file mode 100644 index 000000000000..f61410b33c01 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/batch_normalization_pd.hpp @@ -0,0 +1,240 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef BATCH_NORMALIZATION_PD_HPP +#define BATCH_NORMALIZATION_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct batch_normalization_fwd_pd_t; + +struct batch_normalization_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::batch_normalization; + + batch_normalization_pd_t(engine_t *engine, + const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , data_md_(desc_.data_desc) + , stat_md_(desc_.mean_desc) + , scaleshift_md_(desc_.data_scaleshift_desc) + , ws_md_() + {} + + const batch_normalization_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::batch_normalization_d: + *(const batch_normalization_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common batch_normalization aux functions */ + + dim_t MB() const { return data_desc().dims[0]; } + dim_t C() const { return data_desc().dims[1]; } + dim_t D() const { return ndims() >= 5 ? data_desc().dims[ndims() - 3] : 1; } + dim_t H() const { return ndims() >= 4 ? data_desc().dims[ndims() - 2] : 1; } + dim_t W() const { return ndims() >= 3 ? data_desc().dims[ndims() - 1] : 1; } + + int ndims() const { return desc_.data_desc.ndims; } + + bool stats_is_src() const { return desc_.flags & mkldnn_use_global_stats; } + bool use_scaleshift() const { return desc_.flags & mkldnn_use_scaleshift; } + bool use_global_stats() const + { return desc_.flags & mkldnn_use_global_stats; } + bool fuse_bn_relu() const { return desc_.flags & mkldnn_fuse_bn_relu; } + bool with_relu_post_op() const { + const auto &p = this->attr()->post_ops_; + return p.len_ == 1 && p.entry_[0].is_relu(true, true); + } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + bool is_bwd() const { return !this->is_fwd(); } + bool is_training() const + { return desc_.prop_kind == prop_kind::forward_training; } + + bool has_zero_dim_memory() const + { return memory_desc_wrapper(desc_.data_desc).has_zero_dim(); } + +protected: + batch_normalization_desc_t desc_; + const batch_normalization_fwd_pd_t *hint_fwd_pd_; + + memory_desc_t data_md_; + memory_desc_t stat_md_; + memory_desc_t scaleshift_md_; + + memory_desc_t ws_md_; + + void init_default_ws(size_t bits_per_element) { + const auto data_mdw = memory_desc_wrapper(data_md_); + + const dim_t data_nelems = data_mdw.nelems(true); + const dim_t bits_per_byte = 8; + const dims_t ws_sz = { (dim_t)utils::div_up( + data_nelems * bits_per_element, bits_per_byte) }; + mkldnn_memory_desc_init_by_tag(&ws_md_, 1, ws_sz, impl::data_type::u8, + format_tag::x); + } + +private: + const memory_desc_t &data_desc() const { return desc_.data_desc; } +}; + +struct batch_normalization_fwd_pd_t: public batch_normalization_pd_t { + typedef batch_normalization_fwd_pd_t base_class; + typedef batch_normalization_fwd_pd_t hint_class; + + batch_normalization_fwd_pd_t(engine_t *engine, + const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : batch_normalization_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_SRC) return arg_usage_t::input; + if (arg == MKLDNN_ARG_DST) return arg_usage_t::output; + + if (utils::one_of(arg, MKLDNN_ARG_MEAN, MKLDNN_ARG_VARIANCE)) { + if (stats_is_src()) return arg_usage_t::input; + if (!stats_is_src() && is_training()) return arg_usage_t::output; + return arg_usage_t::unused; + } + + if (arg == MKLDNN_ARG_SCALE_SHIFT && use_scaleshift()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_WORKSPACE && is_training() && fuse_bn_relu()) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override { + if (index == 0) return &data_md_; + if (stats_is_src() && (index == 1 || index == 2)) return &stat_md_; + return nullptr; + } + + virtual const memory_desc_t *dst_md(int index = 0) const override { + if (index == 0) return &data_md_; + if (!stats_is_src() && is_training() && (index == 1 || index == 2)) + return &stat_md_; + return nullptr; + } + + virtual const memory_desc_t *weights_md(int index = 0) const override + { return index == 0 ? &scaleshift_md_ : nullptr; } + + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && is_training() && fuse_bn_relu() ? &ws_md_ : nullptr; } + + const memory_desc_t *stat_md() const + { return stats_is_src() ? src_md(1) : dst_md(1); } + + virtual int n_inputs() const override + { return 1 + 2 * stats_is_src() + use_scaleshift(); } + virtual int n_outputs() const override + { return 1 + (fuse_bn_relu() + 2 * (!stats_is_src())) * is_training(); } +}; + +struct batch_normalization_bwd_pd_t: public batch_normalization_pd_t { + typedef batch_normalization_bwd_pd_t base_class; + typedef batch_normalization_fwd_pd_t hint_class; + + batch_normalization_bwd_pd_t(engine_t *engine, + const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : batch_normalization_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_data_md_(desc_.diff_data_desc) + , diff_scaleshift_md_(desc_.diff_data_scaleshift_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_MEAN, + MKLDNN_ARG_VARIANCE, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_SCALE_SHIFT && use_scaleshift()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_WORKSPACE && fuse_bn_relu()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_DIFF_SCALE_SHIFT && use_scaleshift()) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &data_md_ : index <= 2 ? &stat_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + + virtual const memory_desc_t *weights_md(int index = 0) const override + { return index == 0 ? &scaleshift_md_ : nullptr; } + virtual const memory_desc_t *diff_weights_md(int index = 0) const override + { return index == 0 ? &diff_scaleshift_md_ : nullptr; } + + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && fuse_bn_relu() ? &ws_md_ : nullptr; } + + const memory_desc_t *stat_md() const { return src_md(1); } + + virtual int n_inputs() const override + { return 4 + use_scaleshift() + fuse_bn_relu(); } + virtual int n_outputs() const override + { return 1 + (desc_.prop_kind == prop_kind::backward); } + +protected: + memory_desc_t diff_data_md_; + memory_desc_t diff_scaleshift_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/c_types_map.hpp b/thirdparty/oidn/mkl-dnn/src/common/c_types_map.hpp new file mode 100644 index 000000000000..3d43a0fbee74 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/c_types_map.hpp @@ -0,0 +1,550 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef TYPE_MAPPING_HPP +#define TYPE_MAPPING_HPP + +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { + +// TODO: autogenerate this + +using dim_t = mkldnn_dim_t; +using dims_t = mkldnn_dims_t; +using stride_t = mkldnn_dim_t; +using strides_t = mkldnn_strides_t; + +using status_t = mkldnn_status_t; +namespace status { + const status_t success = mkldnn_success; + const status_t out_of_memory = mkldnn_out_of_memory; + const status_t try_again = mkldnn_try_again; + const status_t invalid_arguments = mkldnn_invalid_arguments; + const status_t not_ready = mkldnn_not_ready; + const status_t unimplemented = mkldnn_unimplemented; + const status_t iterator_ends = mkldnn_iterator_ends; + const status_t runtime_error = mkldnn_runtime_error; + const status_t not_required = mkldnn_not_required; +} + +using prop_kind_t = mkldnn_prop_kind_t; +namespace prop_kind { + const prop_kind_t undef = mkldnn_prop_kind_undef; + const prop_kind_t forward_training = mkldnn_forward_training; + const prop_kind_t forward_inference = mkldnn_forward_inference; + const prop_kind_t forward_scoring = mkldnn_forward_scoring; + const prop_kind_t forward = mkldnn_forward; + const prop_kind_t backward = mkldnn_backward; + const prop_kind_t backward_data = mkldnn_backward_data; + const prop_kind_t backward_weights = mkldnn_backward_weights; + const prop_kind_t backward_bias = mkldnn_backward_bias; +} + +using alg_kind_t = mkldnn_alg_kind_t; +namespace alg_kind { + const alg_kind_t undef = mkldnn_alg_kind_undef; + const alg_kind_t convolution_auto = mkldnn_convolution_auto; + const alg_kind_t convolution_direct = mkldnn_convolution_direct; + const alg_kind_t convolution_winograd = mkldnn_convolution_winograd; + const alg_kind_t deconvolution_direct = mkldnn_deconvolution_direct; + const alg_kind_t deconvolution_winograd = mkldnn_deconvolution_winograd; + const alg_kind_t eltwise_relu = mkldnn_eltwise_relu; + const alg_kind_t eltwise_tanh = mkldnn_eltwise_tanh; + const alg_kind_t eltwise_elu = mkldnn_eltwise_elu; + const alg_kind_t eltwise_square = mkldnn_eltwise_square; + const alg_kind_t eltwise_abs = mkldnn_eltwise_abs; + const alg_kind_t eltwise_sqrt = mkldnn_eltwise_sqrt; + const alg_kind_t eltwise_linear = mkldnn_eltwise_linear; + const alg_kind_t eltwise_bounded_relu = mkldnn_eltwise_bounded_relu; + const alg_kind_t eltwise_soft_relu = mkldnn_eltwise_soft_relu; + const alg_kind_t eltwise_logistic = mkldnn_eltwise_logistic; + const alg_kind_t pooling_max = mkldnn_pooling_max; + const alg_kind_t pooling_avg = mkldnn_pooling_avg; + const alg_kind_t pooling_avg_include_padding = mkldnn_pooling_avg_include_padding; + const alg_kind_t pooling_avg_exclude_padding = mkldnn_pooling_avg_exclude_padding; + const alg_kind_t lrn_across_channels = mkldnn_lrn_across_channels; + const alg_kind_t lrn_within_channel = mkldnn_lrn_within_channel; + const alg_kind_t vanilla_rnn = mkldnn_vanilla_rnn; + const alg_kind_t vanilla_lstm = mkldnn_vanilla_lstm; + const alg_kind_t vanilla_gru = mkldnn_vanilla_gru; + const alg_kind_t gru_linear_before_reset = mkldnn_gru_linear_before_reset; +} + +using data_type_t = mkldnn_data_type_t; +namespace data_type { + const data_type_t undef = mkldnn_data_type_undef; + const data_type_t f32 = mkldnn_f32; + const data_type_t s32 = mkldnn_s32; + const data_type_t s8 = mkldnn_s8; + const data_type_t u8 = mkldnn_u8; +} + +using scratchpad_mode_t = mkldnn_scratchpad_mode_t; +namespace scratchpad_mode { + const scratchpad_mode_t library = mkldnn_scratchpad_mode_library; + const scratchpad_mode_t user = mkldnn_scratchpad_mode_user; +} + +using rnn_packed_format_t = mkldnn_rnn_packed_memory_format_t; +namespace rnn_packed_format { + const rnn_packed_format_t undef = mkldnn_packed_format_undef; + const rnn_packed_format_t ldigo_p = mkldnn_ldigo_p; + const rnn_packed_format_t ldgoi_p = mkldnn_ldgoi_p; +} + +using format_kind_t = mkldnn_format_kind_t; +namespace format_kind { + const format_kind_t undef = mkldnn_format_kind_undef; + const format_kind_t any = mkldnn_format_kind_any; + const format_kind_t blocked = mkldnn_blocked; + const format_kind_t wino = mkldnn_format_kind_wino; + const format_kind_t rnn_packed = mkldnn_format_kind_rnn_packed; +} + +using format_tag_t = mkldnn_format_tag_t; +namespace format_tag { + const format_tag_t undef = mkldnn_format_tag_undef; + const format_tag_t any = mkldnn_format_tag_any; + const format_tag_t a = mkldnn_a; + const format_tag_t ab = mkldnn_ab; + const format_tag_t abc = mkldnn_abc; + const format_tag_t abcd = mkldnn_abcd; + const format_tag_t abcde = mkldnn_abcde; + const format_tag_t abcdef = mkldnn_abcdef; + const format_tag_t abdec = mkldnn_abdec; + const format_tag_t acb = mkldnn_acb; + const format_tag_t acbde = mkldnn_acbde; + const format_tag_t acdb = mkldnn_acdb; + const format_tag_t acdeb = mkldnn_acdeb; + const format_tag_t ba = mkldnn_ba; + const format_tag_t bac = mkldnn_bac; + const format_tag_t bacd = mkldnn_bacd; + const format_tag_t bcda = mkldnn_bcda; + const format_tag_t cba = mkldnn_cba; + const format_tag_t cdba = mkldnn_cdba; + const format_tag_t cdeba = mkldnn_cdeba; + const format_tag_t decab = mkldnn_decab; + const format_tag_t Abc16a = mkldnn_Abc16a; + const format_tag_t ABc16a16b = mkldnn_ABc16a16b; + const format_tag_t aBc16b = mkldnn_aBc16b; + const format_tag_t ABc16b16a = mkldnn_ABc16b16a; + const format_tag_t Abc4a = mkldnn_Abc4a; + const format_tag_t aBc4b = mkldnn_aBc4b; + const format_tag_t ABc4b16a4b = mkldnn_ABc4b16a4b; + const format_tag_t ABc4b4a = mkldnn_ABc4b4a; + const format_tag_t ABc8a16b2a = mkldnn_ABc8a16b2a; + const format_tag_t ABc8a8b = mkldnn_ABc8a8b; + const format_tag_t aBc8b = mkldnn_aBc8b; + const format_tag_t ABc8b16a2b = mkldnn_ABc8b16a2b; + const format_tag_t ABc8b8a = mkldnn_ABc8b8a; + const format_tag_t Abcd16a = mkldnn_Abcd16a; + const format_tag_t ABcd16a16b = mkldnn_ABcd16a16b; + const format_tag_t aBcd16b = mkldnn_aBcd16b; + const format_tag_t ABcd16b16a = mkldnn_ABcd16b16a; + const format_tag_t aBCd16b16c = mkldnn_aBCd16b16c; + const format_tag_t aBCd16c16b = mkldnn_aBCd16c16b; + const format_tag_t Abcd4a = mkldnn_Abcd4a; + const format_tag_t aBcd4b = mkldnn_aBcd4b; + const format_tag_t ABcd4b16a4b = mkldnn_ABcd4b16a4b; + const format_tag_t ABcd4b4a = mkldnn_ABcd4b4a; + const format_tag_t aBCd4c16b4c = mkldnn_aBCd4c16b4c; + const format_tag_t aBCd4c4b = mkldnn_aBCd4c4b; + const format_tag_t ABcd8a16b2a = mkldnn_ABcd8a16b2a; + const format_tag_t ABcd8a8b = mkldnn_ABcd8a8b; + const format_tag_t aBcd8b = mkldnn_aBcd8b; + const format_tag_t ABcd8b16a2b = mkldnn_ABcd8b16a2b; + const format_tag_t aBCd8b16c2b = mkldnn_aBCd8b16c2b; + const format_tag_t ABcd8b8a = mkldnn_ABcd8b8a; + const format_tag_t aBCd8b8c = mkldnn_aBCd8b8c; + const format_tag_t aBCd8c16b2c = mkldnn_aBCd8c16b2c; + const format_tag_t aBCd8c8b = mkldnn_aBCd8c8b; + const format_tag_t Abcde16a = mkldnn_Abcde16a; + const format_tag_t ABcde16a16b = mkldnn_ABcde16a16b; + const format_tag_t aBcde16b = mkldnn_aBcde16b; + const format_tag_t ABcde16b16a = mkldnn_ABcde16b16a; + const format_tag_t aBCde16b16c = mkldnn_aBCde16b16c; + const format_tag_t aBCde16c16b = mkldnn_aBCde16c16b; + const format_tag_t aBCde2c8b4c = mkldnn_aBCde2c8b4c; + const format_tag_t Abcde4a = mkldnn_Abcde4a; + const format_tag_t aBcde4b = mkldnn_aBcde4b; + const format_tag_t ABcde4b4a = mkldnn_ABcde4b4a; + const format_tag_t aBCde4b4c = mkldnn_aBCde4b4c; + const format_tag_t aBCde4c16b4c = mkldnn_aBCde4c16b4c; + const format_tag_t aBCde4c4b = mkldnn_aBCde4c4b; + const format_tag_t Abcde8a = mkldnn_Abcde8a; + const format_tag_t ABcde8a8b = mkldnn_ABcde8a8b; + const format_tag_t aBcde8b = mkldnn_aBcde8b; + const format_tag_t ABcde8b16a2b = mkldnn_ABcde8b16a2b; + const format_tag_t aBCde8b16c2b = mkldnn_aBCde8b16c2b; + const format_tag_t ABcde8b8a = mkldnn_ABcde8b8a; + const format_tag_t aBCde8b8c = mkldnn_aBCde8b8c; + const format_tag_t aBCde8c16b2c = mkldnn_aBCde8c16b2c; + const format_tag_t aBCde8c8b = mkldnn_aBCde8c8b; + const format_tag_t aBcdef16b = mkldnn_aBcdef16b; + const format_tag_t aBCdef16b16c = mkldnn_aBCdef16b16c; + const format_tag_t aBCdef16c16b = mkldnn_aBCdef16c16b; + const format_tag_t aBcdef4b = mkldnn_aBcdef4b; + const format_tag_t aBCdef4c4b = mkldnn_aBCdef4c4b; + const format_tag_t aBCdef8b8c = mkldnn_aBCdef8b8c; + const format_tag_t aBCdef8c16b2c = mkldnn_aBCdef8c16b2c; + const format_tag_t aBCdef8c8b = mkldnn_aBCdef8c8b; + const format_tag_t aBdc16b = mkldnn_aBdc16b; + const format_tag_t aBdc4b = mkldnn_aBdc4b; + const format_tag_t aBdc8b = mkldnn_aBdc8b; + const format_tag_t aBdec16b = mkldnn_aBdec16b; + const format_tag_t aBdec4b = mkldnn_aBdec4b; + const format_tag_t aBdec8b = mkldnn_aBdec8b; + const format_tag_t aBdefc16b = mkldnn_aBdefc16b; + const format_tag_t aBdefc4b = mkldnn_aBdefc4b; + const format_tag_t aBdefc8b = mkldnn_aBdefc8b; + const format_tag_t Acb16a = mkldnn_Acb16a; + const format_tag_t Acb4a = mkldnn_Acb4a; + const format_tag_t Acb8a = mkldnn_Acb8a; + const format_tag_t aCBd16b16c = mkldnn_aCBd16b16c; + const format_tag_t aCBde16b16c = mkldnn_aCBde16b16c; + const format_tag_t Acdb16a = mkldnn_Acdb16a; + const format_tag_t Acdb4a = mkldnn_Acdb4a; + const format_tag_t Acdb8a = mkldnn_Acdb8a; + const format_tag_t Acdeb16a = mkldnn_Acdeb16a; + const format_tag_t Acdeb4a = mkldnn_Acdeb4a; + const format_tag_t Acdeb8a = mkldnn_Acdeb8a; + const format_tag_t BAc16a16b = mkldnn_BAc16a16b; + const format_tag_t BAcd16a16b = mkldnn_BAcd16a16b; + const format_tag_t last = mkldnn_format_tag_last; + + const format_tag_t x = mkldnn_x; + const format_tag_t nc = mkldnn_nc; + const format_tag_t cn = mkldnn_cn; + const format_tag_t ncw = mkldnn_ncw; + const format_tag_t nwc = mkldnn_nwc; + const format_tag_t nchw = mkldnn_nchw; + const format_tag_t nhwc = mkldnn_nhwc; + const format_tag_t chwn = mkldnn_chwn; + const format_tag_t ncdhw = mkldnn_ncdhw; + const format_tag_t ndhwc = mkldnn_ndhwc; + const format_tag_t oi = mkldnn_oi; + const format_tag_t io = mkldnn_io; + const format_tag_t oiw = mkldnn_oiw; + const format_tag_t wio = mkldnn_wio; + const format_tag_t oihw = mkldnn_oihw; + const format_tag_t hwio = mkldnn_hwio; + const format_tag_t ihwo = mkldnn_ihwo; + const format_tag_t iohw = mkldnn_iohw; + const format_tag_t oidhw = mkldnn_oidhw; + const format_tag_t dhwio = mkldnn_dhwio; + const format_tag_t goiw = mkldnn_goiw; + const format_tag_t goihw = mkldnn_goihw; + const format_tag_t hwigo = mkldnn_hwigo; + const format_tag_t giohw = mkldnn_giohw; + const format_tag_t goidhw = mkldnn_goidhw; + const format_tag_t tnc = mkldnn_tnc; + const format_tag_t ntc = mkldnn_ntc; + const format_tag_t ldsnc = mkldnn_ldsnc; + const format_tag_t ldigo = mkldnn_ldigo; + const format_tag_t ldgoi = mkldnn_ldgoi; + const format_tag_t ldgo = mkldnn_ldgo; + const format_tag_t nCdhw16c = mkldnn_nCdhw16c; + const format_tag_t nCdhw4c = mkldnn_nCdhw4c; + const format_tag_t nCdhw8c = mkldnn_nCdhw8c; + const format_tag_t nChw16c = mkldnn_nChw16c; + const format_tag_t nChw4c = mkldnn_nChw4c; + const format_tag_t nChw8c = mkldnn_nChw8c; + const format_tag_t nCw16c = mkldnn_nCw16c; + const format_tag_t nCw4c = mkldnn_nCw4c; + const format_tag_t nCw8c = mkldnn_nCw8c; + const format_tag_t IOw16o16i = mkldnn_IOw16o16i; + const format_tag_t OIw16i16o = mkldnn_OIw16i16o; + const format_tag_t OIw16o16i = mkldnn_OIw16o16i; + const format_tag_t Oiw16o = mkldnn_Oiw16o; + const format_tag_t OIw4i16o4i = mkldnn_OIw4i16o4i; + const format_tag_t OIw4i4o = mkldnn_OIw4i4o; + const format_tag_t Oiw4o = mkldnn_Oiw4o; + const format_tag_t OIw8i16o2i = mkldnn_OIw8i16o2i; + const format_tag_t OIw8i8o = mkldnn_OIw8i8o; + const format_tag_t OIw8o16i2o = mkldnn_OIw8o16i2o; + const format_tag_t OIw8o8i = mkldnn_OIw8o8i; + const format_tag_t Owi16o = mkldnn_Owi16o; + const format_tag_t Owi4o = mkldnn_Owi4o; + const format_tag_t Owi8o = mkldnn_Owi8o; + const format_tag_t IOhw16o16i = mkldnn_IOhw16o16i; + const format_tag_t Ohwi16o = mkldnn_Ohwi16o; + const format_tag_t Ohwi4o = mkldnn_Ohwi4o; + const format_tag_t Ohwi8o = mkldnn_Ohwi8o; + const format_tag_t OIhw16i16o = mkldnn_OIhw16i16o; + const format_tag_t OIhw16o16i = mkldnn_OIhw16o16i; + const format_tag_t Oihw16o = mkldnn_Oihw16o; + const format_tag_t OIhw4i16o4i = mkldnn_OIhw4i16o4i; + const format_tag_t OIhw4i4o = mkldnn_OIhw4i4o; + const format_tag_t Oihw4o = mkldnn_Oihw4o; + const format_tag_t OIhw8i16o2i = mkldnn_OIhw8i16o2i; + const format_tag_t OIhw8i8o = mkldnn_OIhw8i8o; + const format_tag_t OIhw8o16i2o = mkldnn_OIhw8o16i2o; + const format_tag_t OIhw8o8i = mkldnn_OIhw8o8i; + const format_tag_t Odhwi16o = mkldnn_Odhwi16o; + const format_tag_t Odhwi4o = mkldnn_Odhwi4o; + const format_tag_t Odhwi8o = mkldnn_Odhwi8o; + const format_tag_t OIdhw16i16o = mkldnn_OIdhw16i16o; + const format_tag_t OIdhw16o16i = mkldnn_OIdhw16o16i; + const format_tag_t Oidhw16o = mkldnn_Oidhw16o; + const format_tag_t OIdhw4i4o = mkldnn_OIdhw4i4o; + const format_tag_t Oidhw4o = mkldnn_Oidhw4o; + const format_tag_t OIdhw8i16o2i = mkldnn_OIdhw8i16o2i; + const format_tag_t OIdhw8i8o = mkldnn_OIdhw8i8o; + const format_tag_t OIdhw8o8i = mkldnn_OIdhw8o8i; + const format_tag_t gIOw16o16i = mkldnn_gIOw16o16i; + const format_tag_t Goiw16g = mkldnn_Goiw16g; + const format_tag_t gOIw16i16o = mkldnn_gOIw16i16o; + const format_tag_t gOIw16o16i = mkldnn_gOIw16o16i; + const format_tag_t gOiw16o = mkldnn_gOiw16o; + const format_tag_t gOIw4i16o4i = mkldnn_gOIw4i16o4i; + const format_tag_t gOIw4i4o = mkldnn_gOIw4i4o; + const format_tag_t gOiw4o = mkldnn_gOiw4o; + const format_tag_t gOIw8i16o2i = mkldnn_gOIw8i16o2i; + const format_tag_t gOIw8i8o = mkldnn_gOIw8i8o; + const format_tag_t gOIw8o16i2o = mkldnn_gOIw8o16i2o; + const format_tag_t gOIw8o8i = mkldnn_gOIw8o8i; + const format_tag_t gOwi16o = mkldnn_gOwi16o; + const format_tag_t gOwi4o = mkldnn_gOwi4o; + const format_tag_t gOwi8o = mkldnn_gOwi8o; + const format_tag_t gIOhw16o16i = mkldnn_gIOhw16o16i; + const format_tag_t gOhwi16o = mkldnn_gOhwi16o; + const format_tag_t gOhwi4o = mkldnn_gOhwi4o; + const format_tag_t gOhwi8o = mkldnn_gOhwi8o; + const format_tag_t Goihw16g = mkldnn_Goihw16g; + const format_tag_t gOIhw16i16o = mkldnn_gOIhw16i16o; + const format_tag_t gOIhw16o16i = mkldnn_gOIhw16o16i; + const format_tag_t gOihw16o = mkldnn_gOihw16o; + const format_tag_t gOIhw2i8o4i = mkldnn_gOIhw2i8o4i; + const format_tag_t gOIhw4i16o4i = mkldnn_gOIhw4i16o4i; + const format_tag_t gOIhw4i4o = mkldnn_gOIhw4i4o; + const format_tag_t gOIhw4o4i = mkldnn_gOIhw4o4i; + const format_tag_t gOihw4o = mkldnn_gOihw4o; + const format_tag_t Goihw8g = mkldnn_Goihw8g; + const format_tag_t gOIhw8i16o2i = mkldnn_gOIhw8i16o2i; + const format_tag_t gOIhw8i8o = mkldnn_gOIhw8i8o; + const format_tag_t gOIhw8o16i2o = mkldnn_gOIhw8o16i2o; + const format_tag_t gOIhw8o8i = mkldnn_gOIhw8o8i; + const format_tag_t gOdhwi16o = mkldnn_gOdhwi16o; + const format_tag_t gOdhwi4o = mkldnn_gOdhwi4o; + const format_tag_t gOdhwi8o = mkldnn_gOdhwi8o; + const format_tag_t gOIdhw16i16o = mkldnn_gOIdhw16i16o; + const format_tag_t gOIdhw16o16i = mkldnn_gOIdhw16o16i; + const format_tag_t gOidhw16o = mkldnn_gOidhw16o; + const format_tag_t gOIdhw4i4o = mkldnn_gOIdhw4i4o; + const format_tag_t gOidhw4o = mkldnn_gOidhw4o; + const format_tag_t gOIdhw8i16o2i = mkldnn_gOIdhw8i16o2i; + const format_tag_t gOIdhw8i8o = mkldnn_gOIdhw8i8o; + const format_tag_t gOIdhw8o8i = mkldnn_gOIdhw8o8i; +} + +using memory_extra_flags_t = mkldnn_memory_extra_flags_t; +namespace memory_extra_flags { + const memory_extra_flags_t none = mkldnn_memory_extra_flag_none; + const memory_extra_flags_t compensation_conv_s8s8 = mkldnn_memory_extra_flag_compensation_conv_s8s8; + const memory_extra_flags_t scale_adjust = mkldnn_memory_extra_flag_scale_adjust; +} + +using padding_kind_t = mkldnn_padding_kind_t; +namespace padding_kind { + const padding_kind_t padding_zero = mkldnn_padding_zero; +} + +using engine_kind_t = mkldnn_engine_kind_t; +namespace engine_kind { + const engine_kind_t any_engine = mkldnn_any_engine; + const engine_kind_t cpu = mkldnn_cpu; +} + +using primitive_kind_t = mkldnn_primitive_kind_t; +namespace primitive_kind { + const primitive_kind_t undefined = mkldnn_undefined_primitive; + const primitive_kind_t reorder = mkldnn_reorder; + const primitive_kind_t concat = mkldnn_concat; + const primitive_kind_t sum = mkldnn_sum; + const primitive_kind_t convolution = mkldnn_convolution; + const primitive_kind_t deconvolution = mkldnn_deconvolution; + const primitive_kind_t shuffle = mkldnn_shuffle; + const primitive_kind_t eltwise = mkldnn_eltwise; + const primitive_kind_t softmax = mkldnn_softmax; + const primitive_kind_t pooling = mkldnn_pooling; + const primitive_kind_t lrn = mkldnn_lrn; + const primitive_kind_t batch_normalization = mkldnn_batch_normalization; + const primitive_kind_t inner_product = mkldnn_inner_product; + const primitive_kind_t rnn = mkldnn_rnn; +} + +using query_t = mkldnn_query_t; +namespace query { + const query_t undef = mkldnn_query_undef; + + const query_t engine = mkldnn_query_engine; + const query_t primitive_kind = mkldnn_query_primitive_kind; + + const query_t num_of_inputs_s32 = mkldnn_query_num_of_inputs_s32; + const query_t num_of_outputs_s32 = mkldnn_query_num_of_outputs_s32; + + const query_t time_estimate_f64 = mkldnn_query_time_estimate_f64; + const query_t memory_consumption_s64 = mkldnn_query_memory_consumption_s64; + + const query_t scratchpad_engine = mkldnn_query_scratchpad_engine; + + const query_t impl_info_str = mkldnn_query_impl_info_str; + + const query_t some_d = mkldnn_query_some_d; + const query_t op_d = mkldnn_query_op_d; + const query_t convolution_d = mkldnn_query_convolution_d; + const query_t deconvolution_d = mkldnn_query_deconvolution_d; + const query_t shuffle_d = mkldnn_query_shuffle_d; + const query_t eltwise_d = mkldnn_query_eltwise_d; + const query_t softmax_d = mkldnn_query_softmax_d; + const query_t pooling_d = mkldnn_query_pooling_d; + const query_t lrn_d = mkldnn_query_lrn_d; + const query_t batch_normalization_d = mkldnn_query_batch_normalization_d; + const query_t inner_product_d = mkldnn_query_inner_product_d; + const query_t rnn_d = mkldnn_query_rnn_d; + + const query_t some_md = mkldnn_query_some_md; + const query_t src_md = mkldnn_query_src_md; + const query_t diff_src_md = mkldnn_query_diff_src_md; + const query_t weights_md = mkldnn_query_weights_md; + const query_t diff_weights_md = mkldnn_query_diff_weights_md; + const query_t dst_md = mkldnn_query_dst_md; + const query_t diff_dst_md = mkldnn_query_diff_dst_md; + + const query_t workspace_md = mkldnn_query_workspace_md; + const query_t scratchpad_md = mkldnn_query_scratchpad_md; +} + +using blocking_desc_t = mkldnn_blocking_desc_t; +using rnn_packed_desc_t = mkldnn_rnn_packed_desc_t; +using wino_desc_t = mkldnn_wino_desc_t; +using memory_extra_desc_t = mkldnn_memory_extra_desc_t; +using memory_desc_t = mkldnn_memory_desc_t; +using convolution_desc_t = mkldnn_convolution_desc_t; +using deconvolution_desc_t = mkldnn_deconvolution_desc_t; +using shuffle_desc_t = mkldnn_shuffle_desc_t; +using pooling_desc_t = mkldnn_pooling_desc_t; +using eltwise_desc_t = mkldnn_eltwise_desc_t; +using softmax_desc_t = mkldnn_softmax_desc_t; +using lrn_desc_t = mkldnn_lrn_desc_t; +using batch_normalization_desc_t = mkldnn_batch_normalization_desc_t; +using inner_product_desc_t = mkldnn_inner_product_desc_t; + +using rnn_direction_t = mkldnn_rnn_direction_t; +using rnn_cell_desc_t = mkldnn_rnn_cell_desc_t; +using rnn_desc_t = mkldnn_rnn_desc_t; + +/* C op_desc_t, which eventually are just (void*) */ +using c_op_desc_t = mkldnn_op_desc_t; +using const_c_op_desc_t = const_mkldnn_op_desc_t; + +struct op_desc_t { + union { + primitive_kind_t kind; + convolution_desc_t convolution; + deconvolution_desc_t deconvolution; + shuffle_desc_t shuffle; + pooling_desc_t pooling; + eltwise_desc_t eltwise; + softmax_desc_t softmax; + lrn_desc_t lrn; + batch_normalization_desc_t batch_normalization; + inner_product_desc_t inner_product; + rnn_desc_t rnn; + }; + + op_desc_t(const primitive_kind_t &_): kind(_) {} + +# define DECL_CTOR_AND_CONVERTERS(c_type, name) \ + op_desc_t(const c_type &_): name(_) {} \ + static op_desc_t *convert_from_c(c_type *_) \ + { return reinterpret_cast(_); } \ + static const op_desc_t *convert_from_c(const c_type *_) \ + { return reinterpret_cast(_); } + + DECL_CTOR_AND_CONVERTERS(convolution_desc_t, convolution); + DECL_CTOR_AND_CONVERTERS(shuffle_desc_t, shuffle); + DECL_CTOR_AND_CONVERTERS(pooling_desc_t, pooling); + DECL_CTOR_AND_CONVERTERS(eltwise_desc_t, eltwise); + DECL_CTOR_AND_CONVERTERS(softmax_desc_t, softmax); + DECL_CTOR_AND_CONVERTERS(lrn_desc_t, lrn); + DECL_CTOR_AND_CONVERTERS(batch_normalization_desc_t, batch_normalization); + DECL_CTOR_AND_CONVERTERS(inner_product_desc_t, inner_product); + DECL_CTOR_AND_CONVERTERS(rnn_desc_t, rnn); + +# undef DECL_CTOR_AND_CONVERTERS +}; + +using engine_t = mkldnn_engine; +using primitive_desc_iterator_t = mkldnn_primitive_desc_iterator; +using primitive_desc_t = mkldnn_primitive_desc; +using primitive_attr_t = mkldnn_primitive_attr; +using post_ops_t = mkldnn_post_ops; +using memory_t = mkldnn_memory; +using primitive_t = mkldnn_primitive; + +using primitive_arg_index_t = int; + +using stream_flags_t = mkldnn_stream_flags_t; +namespace stream_flags { + const stream_flags_t default_flags = mkldnn_stream_default_flags; +} +using stream_t = mkldnn_stream; + +/* forward declaration of the internal primitive_desc types */ +struct batch_normalization_bwd_pd_t; +struct batch_normalization_fwd_pd_t; +struct batch_normalization_pd_t; +struct concat_pd_t; +struct convolution_bwd_data_pd_t; +struct convolution_bwd_weights_pd_t; +struct convolution_fwd_pd_t; +struct convolution_pd_t; +struct deconvolution_bwd_data_pd_t; +struct deconvolution_bwd_weights_pd_t; +struct deconvolution_fwd_pd_t; +struct deconvolution_pd_t; +struct eltwise_bwd_pd_t; +struct eltwise_fwd_pd_t; +struct eltwise_pd_t; +struct inner_product_bwd_data_pd_t; +struct inner_product_bwd_weights_pd_t; +struct inner_product_fwd_pd_t; +struct inner_product_pd_t; +struct lrn_bwd_pd_t; +struct lrn_fwd_pd_t; +struct lrn_pd_t; +struct pooling_bwd_pd_t; +struct pooling_fwd_pd_t; +struct pooling_pd_t; +struct reorder_pd_t; +struct rnn_bwd_pd_t; +struct rnn_fwd_pd_t; +struct rnn_pd_t; +struct shuffle_pd_t; +struct softmax_bwd_pd_t; +struct softmax_fwd_pd_t; +struct softmax_pd_t; +struct sum_pd_t; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/concat.cpp b/thirdparty/oidn/mkl-dnn/src/common/concat.cpp new file mode 100644 index 000000000000..ed4c35c6e912 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/concat.cpp @@ -0,0 +1,86 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "concat_pd.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; + +status_t mkldnn_concat_primitive_desc_create(primitive_desc_t **concat_pd, + const memory_desc_t *dst_md, int n, int concat_dim, + const memory_desc_t *src_mds, + const primitive_attr_t *attr, + engine_t *engine) { + bool args_ok = !any_null(concat_pd, src_mds) && n > 0; + if (!args_ok) return invalid_arguments; + + const primitive_attr_t dummy_attr; + if (attr == NULL) + attr = &dummy_attr; + + const int ndims = src_mds[0].ndims; + const dims_t &dims = src_mds[0].dims; + const data_type_t dt = src_mds[0].data_type; + + int concat_dim_sz = dims[concat_dim]; + for (int i = 1; i < n; ++i) { + if (src_mds[i].ndims != ndims) return invalid_arguments; + for (int d = 0; d < ndims; ++d) { + if (d == concat_dim) continue; + if (src_mds[i].dims[d] != dims[d]) + return invalid_arguments; + } + if (src_mds[i].data_type != dt) return invalid_arguments; + concat_dim_sz += src_mds[i].dims[concat_dim]; + } + + memory_desc_t dummy_dst_md; + if (dst_md) { + if (dst_md->ndims != ndims) return invalid_arguments; + for (int d = 0; d < ndims; ++d) { + if (dst_md->dims[d] != + (d == concat_dim ? concat_dim_sz : dims[d])) + return invalid_arguments; + } + } else { + dummy_dst_md = src_mds[0]; + dummy_dst_md.dims[concat_dim] = concat_dim_sz; + dummy_dst_md.format_kind = format_kind::any; + dst_md = &dummy_dst_md; + } + + auto c_pd = reinterpret_cast(concat_pd); + + for (auto c = engine->get_concat_implementation_list(); *c; ++c) { + if ((*c)(c_pd, engine, attr, dst_md, n, concat_dim, src_mds) + == success) { + (*c_pd)->init_info(); + (*c_pd)->init_scratchpad_md(); + return success; + } + } + return unimplemented; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/concat_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/concat_pd.hpp new file mode 100644 index 000000000000..29311927e2f3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/concat_pd.hpp @@ -0,0 +1,211 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CONCAT_PD_HPP +#define CONCAT_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "primitive_desc.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct concat_pd_t: public primitive_desc_t { + concat_pd_t(engine_t *engine, const primitive_attr_t *attr, + const memory_desc_t *dst_md, int n, int concat_dim, + const memory_desc_t *src_mds) + : primitive_desc_t(engine, attr, primitive_kind::concat) + , n_(n), concat_dim_(concat_dim), dst_md_(*dst_md) + { + src_mds_.reserve(n_); + for (int i = 0; i < n_; ++i) src_mds_.push_back(src_mds[i]); + } + + concat_pd_t(const concat_pd_t &rhs) = default; + + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg >= MKLDNN_ARG_MULTIPLE_SRC + && arg < MKLDNN_ARG_MULTIPLE_SRC + n_inputs()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index < n_inputs() ? &src_mds_[index] : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + + virtual int n_inputs() const override { return n_; } + virtual int n_outputs() const override { return 1; } + + int concat_dim() const { return concat_dim_; } + + const memory_desc_t *src_image_md(int index = 0) const + { return index < n_inputs() ? &src_image_mds_[index] : nullptr; } + +protected: + int n_, concat_dim_; + memory_desc_t dst_md_; + nstl::vector src_mds_; + + /* contains images of srcs in the dst memory (if possible) + * Lives here to simplify some implementations. An implementation might + * use this auxiliary array iff init() returned success */ + nstl::vector src_image_mds_; + +protected: + /* inits src_image_mds_ and dst_md_ in simple cases. The call may fail */ + status_t init() { + bool ok = true + && set_default_params() == status::success + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + for (int i = 0; i < n_; ++i) { + const memory_desc_wrapper i_d(&src_mds_[i]); + if (!i_d.is_blocking_desc() || i_d.is_additional_buffer()) + return status::unimplemented; + } + + const int ndims = dst_md_.ndims; + int current_concat_dim_offset = 0; + for (int i = 0; i < n_; ++i) { + const int dim = src_mds_[i].dims[concat_dim_]; + dims_t dims, offsets = {}; + utils::array_copy(dims, dst_md_.dims, ndims); + dims[concat_dim_] = dim; + offsets[concat_dim_] = current_concat_dim_offset; + + memory_desc_t src_img_d; + status_t status = mkldnn_memory_desc_init_submemory(&src_img_d, + &dst_md_, dims, offsets); + if (status != status::success) return status; + src_image_mds_.push_back(src_img_d); + current_concat_dim_offset += dim; + } + + return status::success; + } + + status_t set_default_params() { + if (dst_md_.format_kind != format_kind::any) + return status::success; + + const int ndims = dst_md_.ndims; + + /* The stupidest ever heuristics (but not the same as we had before): + * - Pick the first non-plain format; + * - If all formats are plain or it is not possible to create a + * blocked format for the output, pick the format of the plain input + * - If this fails as well, use plain layout (abcd...) + */ + status_t status = status::unimplemented; + for (int i = 0; i < n_; ++i) { + const memory_desc_wrapper src_d(src_mds_[i]); + if (src_d.is_blocking_desc() && !src_d.is_plain()) { + status = memory_desc_init_by_blocking_desc(dst_md_, + src_d.blocking_desc()); + if (status == status::success) break; + } + } + + if (status == status::success) { + /* check if we can create a sub-memory for the dst */ + bool desired_format_ok = true; + int current_concat_dim_offset = 0; + for (int i = 0; i < n_; ++i) { + const int dim = src_mds_[i].dims[concat_dim_]; + dims_t dims, offsets = {}; + utils::array_copy(dims, dst_md_.dims, ndims); + dims[concat_dim_] = dim; + offsets[concat_dim_] = current_concat_dim_offset; + + memory_desc_t src_img_d; + status_t status = mkldnn_memory_desc_init_submemory(&src_img_d, + &dst_md_, dims, offsets); + if (status != status::success) { + desired_format_ok = false; + break; + } + current_concat_dim_offset += dim; + } + + if (!desired_format_ok) + status = status::unimplemented; + } + + /* if no success so far, try using the format of the first plain input */ + if (status != status::success) { + for (int i = 0; i < n_; ++i) { + const memory_desc_wrapper src_d(src_mds_[i]); + if (src_d.is_blocking_desc() && src_d.is_plain()) { + status = memory_desc_init_by_blocking_desc(dst_md_, + memory_desc_wrapper(src_mds_[0]).blocking_desc()); + if (status == status::success) return status; + } + } + } + + /* the last line of defense: use plain abcd... format */ + if (status != status::success) + status = memory_desc_init_by_strides(dst_md_, nullptr); + + return status; + } +}; + +#define DECLARE_CONCAT_PD_t(impl_name, ...) \ + static status_t create(concat_pd_t **concat_pd, \ + engine_t *engine, const primitive_attr_t *attr, \ + const memory_desc_t *dst_md, int n, int concat_dim, \ + const memory_desc_t *src_mds) { \ + using namespace status; \ + auto _pd = new pd_t(engine, attr, dst_md, n, concat_dim, src_mds); \ + if (_pd == nullptr) return out_of_memory; \ + if (_pd->init() != success) { delete _pd; return unimplemented; } \ + return safe_ptr_assign(*concat_pd, _pd); \ + } \ + virtual status_t create_primitive(primitive_t **p) const override { \ + double ms = get_msec(); \ + auto ret = safe_ptr_assign(*p, new (__VA_ARGS__)(this)); \ + ms = get_msec() - ms; \ + if (mkldnn_verbose()->level >= 2) { \ + printf("mkldnn_verbose,create,%s,%g\n", this->info(), ms); \ + fflush(0); \ + } \ + return ret; \ + } \ + virtual pd_t *clone() const override { return new pd_t(*this); } \ + virtual const char *name() const override { return impl_name; } \ + +#define DECLARE_CONCAT_PD_T(impl_name, ...) \ + DECLARE_CONCAT_PD_t(impl_name, __VA_ARGS__) + +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/convolution.cpp b/thirdparty/oidn/mkl-dnn/src/common/convolution.cpp new file mode 100644 index 000000000000..0c5c02bcd1cf --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/convolution.cpp @@ -0,0 +1,200 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace mkldnn { +namespace impl { +status_t conv_desc_init(convolution_desc_t *conv_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *bias_desc, const memory_desc_t *dst_desc, + const dims_t strides, const dims_t dilates, + const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + bool args_ok = true + && !any_null(conv_desc, src_desc, weights_desc, dst_desc, strides, + padding_l) + && one_of(alg_kind, convolution_auto, convolution_direct, convolution_winograd) + && one_of(padding_kind, padding_kind::padding_zero); + if (!args_ok) return invalid_arguments; + + if (padding_r == nullptr) padding_r = padding_l; + + auto cd = convolution_desc_t(); + cd.primitive_kind = primitive_kind::convolution; + cd.prop_kind = prop_kind; + cd.alg_kind = alg_kind; + + cd.diff_src_desc = cd.src_desc = zero_md(); + cd.diff_dst_desc = cd.dst_desc = zero_md(); + cd.diff_weights_desc = cd.weights_desc = zero_md(); + cd.diff_bias_desc = cd.bias_desc = zero_md(); + + const bool is_fwd = one_of(prop_kind, forward_training, forward_inference); + const bool with_bias = + bias_desc && bias_desc->format_kind != format_kind::undef; + const bool with_groups = weights_desc->ndims == src_desc->ndims + 1; + + (prop_kind == backward_data ? cd.diff_src_desc : cd.src_desc) = *src_desc; + (is_fwd ? cd.dst_desc : cd.diff_dst_desc) = *dst_desc; + (prop_kind == backward_weights ? cd.diff_weights_desc : cd.weights_desc) = + *weights_desc; + if (with_bias) + (prop_kind == backward_weights ? cd.diff_bias_desc : cd.bias_desc) = + *bias_desc; + + int sp_dims = src_desc->ndims - 2; + utils::array_copy(cd.strides, strides, sp_dims); + utils::array_copy(cd.padding[0], padding_l, sp_dims); + utils::array_copy(cd.padding[1], padding_r, sp_dims); + if (dilates) + utils::array_copy(cd.dilates, dilates, sp_dims); + else + utils::array_set(cd.dilates, 0, sp_dims); + + cd.padding_kind = padding_kind; + cd.accum_data_type = types::default_accum_data_type(src_desc->data_type, + weights_desc->data_type, dst_desc->data_type, prop_kind); + + const int g = with_groups ? weights_desc->dims[0] : 1; + const int bias_dim = prop_kind == backward_data + ? src_desc->dims[1] + : dst_desc->dims[1]; + + bool consistency = true + && memory_desc_wrapper(weights_desc).nelems() + && src_desc->ndims == dst_desc->ndims + && utils::one_of(src_desc->ndims, 3, 4, 5) + && utils::one_of(weights_desc->ndims, src_desc->ndims, + src_desc->ndims + 1) + && (with_bias ? bias_desc->ndims == 1 : true) + && (with_bias ? bias_desc->dims[0] == bias_dim : true) + && src_desc->dims[0] == dst_desc->dims[0] + && src_desc->dims[1] == g * weights_desc->dims[with_groups + 1] + && dst_desc->dims[1] == g * weights_desc->dims[with_groups + 0]; + for (int i = 2; i < src_desc->ndims; ++i) + { + int src = src_desc->dims[i]; + int ker = weights_desc->dims[with_groups + i]; + int dil = cd.dilates[i - 2]; + int pad_l = padding_l[i - 2]; + int pad_r = padding_r[i - 2]; + int str = strides[i - 2]; + int dst = dst_desc->dims[i]; + int ker_range = 1 + (ker - 1) * (dil + 1); + + if (str < 1) return invalid_arguments; + consistency = consistency + && dil >= 0 + && pad_l >= 0 + && pad_r + str > 0 + && (src - ker_range + pad_l + pad_r) / str + 1 == dst; + } + if (!consistency) return invalid_arguments; + + *conv_desc = cd; + return success; +} +} +} + +status_t mkldnn_convolution_forward_desc_init(convolution_desc_t *conv_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *bias_desc, const memory_desc_t *dst_desc, + const dims_t strides, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return mkldnn::impl::conv_desc_init(conv_desc, prop_kind, alg_kind, src_desc, + weights_desc, bias_desc, dst_desc, strides, nullptr, + padding_l, padding_r, padding_kind); +} + +status_t mkldnn_dilated_convolution_forward_desc_init( + convolution_desc_t *conv_desc, prop_kind_t prop_kind, + alg_kind_t alg_kind, const memory_desc_t *src_desc, + const memory_desc_t *weights_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_desc, const dims_t strides, + const dims_t dilates, const dims_t padding_l, + const dims_t padding_r, padding_kind_t padding_kind) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return mkldnn::impl::conv_desc_init(conv_desc, prop_kind, alg_kind, src_desc, + weights_desc, bias_desc, dst_desc, strides, dilates, + padding_l, padding_r, padding_kind); +} + +status_t mkldnn_convolution_backward_data_desc_init( + convolution_desc_t *conv_desc, alg_kind_t alg_kind, + const memory_desc_t *diff_src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return mkldnn::impl::conv_desc_init(conv_desc, backward_data, alg_kind, diff_src_desc, + weights_desc, nullptr, diff_dst_desc, strides, nullptr, + padding_l, padding_r, padding_kind); +} + +status_t mkldnn_dilated_convolution_backward_data_desc_init( + convolution_desc_t *conv_desc, alg_kind_t alg_kind, + const memory_desc_t *diff_src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t dilates, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return mkldnn::impl::conv_desc_init(conv_desc, backward_data, alg_kind, diff_src_desc, + weights_desc, nullptr, diff_dst_desc, strides, dilates, + padding_l, padding_r, padding_kind); +} + +status_t mkldnn_convolution_backward_weights_desc_init( + convolution_desc_t *conv_desc, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *diff_weights_desc, + const memory_desc_t *diff_bias_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return mkldnn::impl::conv_desc_init(conv_desc, backward_weights, alg_kind, src_desc, + diff_weights_desc, diff_bias_desc, diff_dst_desc, strides, + nullptr, padding_l, padding_r, padding_kind); +} + +status_t mkldnn_dilated_convolution_backward_weights_desc_init( + convolution_desc_t *conv_desc, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *diff_weights_desc, + const memory_desc_t *diff_bias_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t dilates, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return mkldnn::impl::conv_desc_init(conv_desc, backward_weights, alg_kind, src_desc, + diff_weights_desc, diff_bias_desc, diff_dst_desc, strides, + dilates, padding_l, padding_r, padding_kind); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/convolution_pd.cpp b/thirdparty/oidn/mkl-dnn/src/common/convolution_pd.cpp new file mode 100644 index 000000000000..9604e0acf5ac --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/convolution_pd.cpp @@ -0,0 +1,56 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "utils.hpp" + +#include "convolution_pd.hpp" + +namespace mkldnn { +namespace impl { + +using namespace prop_kind; + +memory_desc_t *conv_prop_invariant_src_d(convolution_desc_t *desc) { + return desc->prop_kind == backward_data + ? &desc->diff_src_desc : &desc->src_desc; +} + +memory_desc_t *conv_prop_invariant_wei_d(convolution_desc_t *desc) { + return desc->prop_kind == backward_weights + ? &desc->diff_weights_desc : &desc->weights_desc; +} + +memory_desc_t *conv_prop_invariant_bia_d(convolution_desc_t *desc) { + return desc->prop_kind == backward_weights + ? &desc->diff_bias_desc : &desc->bias_desc; +} + +memory_desc_t *conv_prop_invariant_dst_d(convolution_desc_t *desc) { + return utils::one_of(desc->prop_kind, forward_inference, forward_training) + ? &desc->dst_desc : &desc->diff_dst_desc; +} + +const memory_desc_t *conv_prop_invariant_src_d(const convolution_desc_t *desc) +{ return conv_prop_invariant_src_d(const_cast(desc)); } +const memory_desc_t *conv_prop_invariant_wei_d(const convolution_desc_t *desc) +{ return conv_prop_invariant_wei_d(const_cast(desc)); } +const memory_desc_t *conv_prop_invariant_bia_d(const convolution_desc_t *desc) +{ return conv_prop_invariant_bia_d(const_cast(desc)); } +const memory_desc_t *conv_prop_invariant_dst_d(const convolution_desc_t *desc) +{ return conv_prop_invariant_dst_d(const_cast(desc)); } + +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/convolution_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/convolution_pd.hpp new file mode 100644 index 000000000000..b10c36db4909 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/convolution_pd.hpp @@ -0,0 +1,348 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CONVOLUTION_PD_HPP +#define CONVOLUTION_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +status_t conv_desc_init(convolution_desc_t *conv_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *bias_desc, const memory_desc_t *dst_desc, + const dims_t strides, const dims_t dilates, + const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind); + +memory_desc_t *conv_prop_invariant_src_d(convolution_desc_t *desc); +memory_desc_t *conv_prop_invariant_wei_d(convolution_desc_t *desc); +memory_desc_t *conv_prop_invariant_bia_d(convolution_desc_t *desc); +memory_desc_t *conv_prop_invariant_dst_d(convolution_desc_t *desc); +const memory_desc_t *conv_prop_invariant_src_d(const convolution_desc_t *desc); +const memory_desc_t *conv_prop_invariant_wei_d(const convolution_desc_t *desc); +const memory_desc_t *conv_prop_invariant_bia_d(const convolution_desc_t *desc); +const memory_desc_t *conv_prop_invariant_dst_d(const convolution_desc_t *desc); + +struct convolution_fwd_pd_t; + +struct convolution_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::convolution; + + convolution_pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + {} + + const convolution_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case pkind_traits::query_d: + *(const convolution_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common conv aux functions */ + + dim_t MB() const { return _src_md()->dims[0]; } + + dim_t IC() const { return _src_md()->dims[1]; } + dim_t OC() const { return _dst_md()->dims[1]; } + dim_t G() const { return with_groups() ? _wei_md()->dims[0] : 1; } + + dim_t ID() const { return ndims() >= 5 ? _src_md()->dims[ndims() - 3] : 1; } + dim_t IH() const { return ndims() >= 4 ? _src_md()->dims[ndims() - 2] : 1; } + dim_t IW() const { return _src_md()->dims[ndims() - 1]; } + + dim_t OD() const { return ndims() >= 5 ? _dst_md()->dims[ndims() - 3] : 1; } + dim_t OH() const { return ndims() >= 4 ? _dst_md()->dims[ndims() - 2] : 1; } + dim_t OW() const { return _dst_md()->dims[ndims() - 1]; } + + dim_t KD() const { return ndims() >= 5 ? _wei_md()->dims[ndims() + with_groups() - 3] : 1; } + dim_t KH() const { return ndims() >= 4 ? _wei_md()->dims[ndims() + with_groups() - 2] : 1; } + dim_t KW() const { return _wei_md()->dims[ndims() + with_groups() - 1]; } + + dim_t KSD() const { return ndims() >= 5 ? desc_.strides[ndims() - 5] : 1; } + dim_t KSH() const { return ndims() >= 4 ? desc_.strides[ndims() - 4] : 1; } + dim_t KSW() const { return desc_.strides[ndims() - 3]; } + + dim_t KDD() const { return ndims() >= 5 ? desc_.dilates[ndims() - 5] : 0; } + dim_t KDH() const { return ndims() >= 4 ? desc_.dilates[ndims() - 4] : 1; } + dim_t KDW() const { return desc_.dilates[ndims() - 3]; } + + dim_t padFront() const { return ndims() >= 5 ? desc_.padding[0][ndims() - 5] : 0; } + dim_t padBack() const { return ndims() >= 5 ? desc_.padding[1][ndims() - 5] : 0; } + dim_t padT() const { return ndims() >= 4 ? desc_.padding[0][ndims() - 4] : 0; } + dim_t padB() const { return ndims() >= 4 ? desc_.padding[1][ndims() - 4] : 0; } + dim_t padL() const { return desc_.padding[0][ndims() - 3]; } + dim_t padR() const { return desc_.padding[1][ndims() - 3]; } + + int ndims() const { return _src_md()->ndims; } + + bool with_bias() const { return !memory_desc_wrapper(*_bia_md()).is_zero(); } + bool with_groups() const { return _wei_md()->ndims == ndims() + 1; } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + + bool has_zero_dim_memory() const { + const auto s_d = memory_desc_wrapper(*_src_md()); + const auto d_d = memory_desc_wrapper(*_dst_md()); + return s_d.has_zero_dim() || d_d.has_zero_dim(); + } + +protected: + convolution_desc_t desc_; + const convolution_fwd_pd_t *hint_fwd_pd_; + + bool set_default_formats_common_template( + memory_desc_t &src_md, format_tag_t src_tag, + memory_desc_t &wei_md, format_tag_t wei_tag, + memory_desc_t &dst_md, format_tag_t dst_tag, + memory_desc_t &bia_md) { + using namespace format_tag; + +# define IS_OK(f) \ + do { if ((f) != status::success) return false; } while(0) + if (src_md.format_kind == format_kind::any + && !utils::one_of(src_tag, any, undef)) + IS_OK(memory_desc_init_by_tag(src_md, src_tag)); + if (dst_md.format_kind == format_kind::any + && !utils::one_of(dst_tag, any, undef)) + IS_OK(memory_desc_init_by_tag(dst_md, dst_tag)); + if (wei_md.format_kind == format_kind::any + && !utils::one_of(wei_tag, any, undef)) + IS_OK(memory_desc_init_by_tag(wei_md, wei_tag)); + if (with_bias() && bia_md.format_kind == format_kind::any) + IS_OK(memory_desc_init_by_tag(bia_md, x)); +# undef IS_OK + + return true; + } + + bool set_default_alg_kind(alg_kind_t alg_kind) { + assert(utils::one_of(alg_kind, alg_kind::convolution_direct, + alg_kind::convolution_winograd)); + if (desc_.alg_kind == alg_kind::convolution_auto) + desc_.alg_kind = alg_kind; + return desc_.alg_kind == alg_kind; + } + + bool expect_data_types(data_type_t src_dt, data_type_t wei_dt, + data_type_t bia_dt, data_type_t dst_dt, data_type_t acc_dt) const { + bool ok = true + && (src_dt == data_type::undef || _src_md()->data_type == src_dt) + && (wei_dt == data_type::undef || _wei_md()->data_type == wei_dt) + && (dst_dt == data_type::undef || _dst_md()->data_type == dst_dt) + && (acc_dt == data_type::undef || desc_.accum_data_type == acc_dt); + if (with_bias() && bia_dt != data_type::undef) + ok = ok && _bia_md()->data_type == bia_dt; + return ok; + } + +private: + const memory_desc_t *_src_md() const { return conv_prop_invariant_src_d(&desc_); } + const memory_desc_t *_wei_md() const { return conv_prop_invariant_wei_d(&desc_); } + const memory_desc_t *_bia_md() const { return conv_prop_invariant_bia_d(&desc_); } + const memory_desc_t *_dst_md() const { return conv_prop_invariant_dst_d(&desc_); } +}; + +struct convolution_fwd_pd_t: public convolution_pd_t { + typedef convolution_fwd_pd_t base_class; + typedef convolution_fwd_pd_t hint_class; + + convolution_fwd_pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : convolution_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , weights_md_(desc_.weights_desc) + , bias_md_(desc_.bias_desc) + , dst_md_(desc_.dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_WEIGHTS)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_BIAS && with_bias()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + virtual const memory_desc_t *weights_md(int index = 0) const override { + if (index == 0) return &weights_md_; + if (index == 1 && with_bias()) return &bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2 + with_bias(); } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t src_md_; + memory_desc_t weights_md_; + memory_desc_t bias_md_; + memory_desc_t dst_md_; + + bool set_default_formats_common(format_tag_t src_tag, + format_tag_t wei_tag, format_tag_t dst_tag) { + return set_default_formats_common_template(src_md_, src_tag, + weights_md_, wei_tag, dst_md_, dst_tag, bias_md_); + } +}; + +struct convolution_bwd_data_pd_t: public convolution_pd_t { + typedef convolution_bwd_data_pd_t base_class; + typedef convolution_fwd_pd_t hint_class; + + convolution_bwd_data_pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : convolution_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_src_md_(desc_.diff_src_desc) + , weights_md_(desc_.weights_desc) + , bias_md_(desc_.bias_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_WEIGHTS, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *weights_md(int index = 0) const override { + if (index == 0) return &weights_md_; + if (index == 1 && with_bias()) return &bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2 + with_bias(); } + virtual int n_outputs() const override { return 1; } + + virtual bool support_bias() const { return false; } + +protected: + memory_desc_t diff_src_md_; + memory_desc_t weights_md_; + memory_desc_t bias_md_; + memory_desc_t diff_dst_md_; + + bool set_default_formats_common(format_tag_t diff_src_tag, + format_tag_t wei_tag, format_tag_t diff_dst_tag) { + return set_default_formats_common_template(diff_src_md_, diff_src_tag, + weights_md_, wei_tag, diff_dst_md_, diff_dst_tag, bias_md_); + } +}; + +struct convolution_bwd_weights_pd_t: public convolution_pd_t { + typedef convolution_bwd_weights_pd_t base_class; + typedef convolution_fwd_pd_t hint_class; + + convolution_bwd_weights_pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : convolution_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , diff_weights_md_(desc_.diff_weights_desc) + , diff_bias_md_(desc_.diff_bias_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_WEIGHTS) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_DIFF_BIAS && with_bias()) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *diff_weights_md(int index = 0) const override { + if (index == 0) return &diff_weights_md_; + if (index == 1 && with_bias()) return &diff_bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2; } + virtual int n_outputs() const override { return 1 + with_bias(); } + +protected: + memory_desc_t src_md_; + memory_desc_t diff_weights_md_; + memory_desc_t diff_bias_md_; + memory_desc_t diff_dst_md_; + + bool set_default_formats_common(format_tag_t src_tag, + format_tag_t diff_wei_tag, format_tag_t diff_dst_tag) { + return set_default_formats_common_template(src_md_, src_tag, + diff_weights_md_, diff_wei_tag, diff_dst_md_, diff_dst_tag, + diff_bias_md_); + } +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/deconvolution.cpp b/thirdparty/oidn/mkl-dnn/src/common/deconvolution.cpp new file mode 100644 index 000000000000..98063c1c37ef --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/deconvolution.cpp @@ -0,0 +1,188 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn.h" +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t deconv_desc_init(deconvolution_desc_t *deconv_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *bias_desc, const memory_desc_t *dst_desc, + const dims_t strides, const dims_t dilates, const dims_t padding_l, + const dims_t padding_r, padding_kind_t padding_kind) { + bool args_ok = true + && !any_null(deconv_desc, src_desc, weights_desc, dst_desc, strides, + padding_l) + && one_of(alg_kind, deconvolution_direct, deconvolution_winograd) + && one_of(padding_kind, padding_kind::padding_zero); + if (!args_ok) + return invalid_arguments; + + if (padding_r == nullptr) + padding_r = padding_l; + + auto dd = deconvolution_desc_t(); + dd.primitive_kind = primitive_kind::deconvolution; + dd.prop_kind = prop_kind; + dd.alg_kind = alg_kind; + + dd.diff_src_desc = dd.src_desc = zero_md(); + dd.diff_dst_desc = dd.dst_desc = zero_md(); + dd.diff_weights_desc = dd.weights_desc = zero_md(); + dd.diff_bias_desc = dd.bias_desc = zero_md(); + + const bool is_fwd = one_of(prop_kind, forward_training, forward_inference); + const bool with_bias + = bias_desc && bias_desc->format_kind != format_kind::undef; + const bool with_groups = weights_desc->ndims == src_desc->ndims + 1; + + (prop_kind == backward_data ? dd.diff_src_desc : dd.src_desc) = *src_desc; + (is_fwd ? dd.dst_desc : dd.diff_dst_desc) = *dst_desc; + (prop_kind == backward_weights ? dd.diff_weights_desc : dd.weights_desc) + = *weights_desc; + if (with_bias) + (prop_kind == backward_weights ? dd.diff_bias_desc : dd.bias_desc) + = *bias_desc; + + int sp_dims = src_desc->ndims - 2; + utils::array_copy(dd.strides, strides, sp_dims); + utils::array_copy(dd.padding[0], padding_l, sp_dims); + utils::array_copy(dd.padding[1], padding_r, sp_dims); + if (dilates) + utils::array_copy(dd.dilates, dilates, sp_dims); + else + utils::array_set(dd.dilates, 0, sp_dims); + + dd.padding_kind = padding_kind; + dd.accum_data_type = types::default_accum_data_type(src_desc->data_type, + weights_desc->data_type, dst_desc->data_type, prop_kind); + + const int g = with_groups ? weights_desc->dims[0] : 1; + bool consistency = true + && src_desc->ndims == dst_desc->ndims + && utils::one_of(src_desc->ndims, 3, 4, 5) + && utils::one_of(weights_desc->ndims, src_desc->ndims, + src_desc->ndims + 1) + && (with_bias ? bias_desc->ndims == 1 : true) + && (with_bias ? bias_desc->dims[0] == dst_desc->dims[1] : true) + && src_desc->dims[0] == dst_desc->dims[0] + && src_desc->dims[1] == g * weights_desc->dims[with_groups + 1] + && dst_desc->dims[1] == g * weights_desc->dims[with_groups + 0]; + for (int i = 2; i < src_desc->ndims; ++i) { + int src = src_desc->dims[i]; + int ker = weights_desc->dims[with_groups + i]; + int dil = dd.dilates[i - 2]; + int pad = padding_l[i - 2] + padding_r[i - 2]; + int str = strides[i - 2]; + int dst = dst_desc->dims[i]; + int ker_range = 1 + (ker - 1) * (dil + 1); + + consistency + = consistency && (dst - ker_range + pad) / str + 1 == src; + } + if (!consistency) + return invalid_arguments; + + *deconv_desc = dd; + return success; +} +} + +status_t mkldnn_deconvolution_forward_desc_init( + deconvolution_desc_t *deconv_desc, prop_kind_t prop_kind, + alg_kind_t alg_kind, const memory_desc_t *src_desc, + const memory_desc_t *weights_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_desc, const dims_t strides, + const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return deconv_desc_init(deconv_desc, prop_kind, alg_kind, src_desc, + weights_desc, bias_desc, dst_desc, strides, nullptr, padding_l, + padding_r, padding_kind); +} + +status_t mkldnn_dilated_deconvolution_forward_desc_init( + deconvolution_desc_t *deconv_desc, prop_kind_t prop_kind, + alg_kind_t alg_kind, const memory_desc_t *src_desc, + const memory_desc_t *weights_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_desc, const dims_t strides, + const dims_t dilates, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return deconv_desc_init(deconv_desc, prop_kind, alg_kind, src_desc, + weights_desc, bias_desc, dst_desc, strides, dilates, padding_l, + padding_r, padding_kind); +} + +status_t mkldnn_deconvolution_backward_data_desc_init( + deconvolution_desc_t *deconv_desc, alg_kind_t alg_kind, + const memory_desc_t *diff_src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return deconv_desc_init(deconv_desc, backward_data, alg_kind, diff_src_desc, + weights_desc, nullptr, diff_dst_desc, strides, nullptr, padding_l, + padding_r, padding_kind); +} + +status_t mkldnn_dilated_deconvolution_backward_data_desc_init( + deconvolution_desc_t *deconv_desc, alg_kind_t alg_kind, + const memory_desc_t *diff_src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t dilates, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return deconv_desc_init(deconv_desc, backward_data, alg_kind, diff_src_desc, + weights_desc, nullptr, diff_dst_desc, strides,dilates, padding_l, + padding_r, padding_kind); +} + +status_t mkldnn_deconvolution_backward_weights_desc_init( + deconvolution_desc_t *deconv_desc, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *diff_weights_desc, + const memory_desc_t *diff_bias_desc, const memory_desc_t *diff_dst_desc, + const dims_t strides, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return deconv_desc_init(deconv_desc, backward_weights, alg_kind, src_desc, + diff_weights_desc, diff_bias_desc, diff_dst_desc, strides, nullptr, + padding_l, padding_r, padding_kind); +} + +status_t mkldnn_dilated_deconvolution_backward_weights_desc_init( + deconvolution_desc_t *deconv_desc, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *diff_weights_desc, + const memory_desc_t *diff_bias_desc, const memory_desc_t *diff_dst_desc, + const dims_t strides, const dims_t dilates, const dims_t padding_l, + const dims_t padding_r, padding_kind_t padding_kind) { + return deconv_desc_init(deconv_desc, backward_weights, alg_kind, src_desc, + diff_weights_desc, diff_bias_desc, diff_dst_desc, strides, dilates, + padding_l, padding_r, padding_kind); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/deconvolution_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/deconvolution_pd.hpp new file mode 100644 index 000000000000..539e44bd9bb1 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/deconvolution_pd.hpp @@ -0,0 +1,293 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef DECONVOLUTION_PD_HPP +#define DECONVOLUTION_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "convolution_pd.hpp" +#include "primitive_desc.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct deconvolution_fwd_pd_t; + +struct deconvolution_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::deconvolution; + + deconvolution_pd_t(engine_t *engine, + const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + {} + + const deconvolution_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case pkind_traits::query_d: + *(const deconvolution_desc_t **)result = desc(); + break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common deconv aux functions (note that conv_desc_t == deconv_desc_t) */ + + dim_t MB() const { return conv_prop_invariant_src_d(&desc_)->dims[0]; } + + dim_t IC() const { return conv_prop_invariant_src_d(&desc_)->dims[1]; } + dim_t OC() const { return conv_prop_invariant_dst_d(&desc_)->dims[1]; } + dim_t G() const + { return with_groups() ? conv_prop_invariant_wei_d(&desc_)->dims[0] : 1; } + + dim_t ID() const { + return ndims() >= 5 + ? conv_prop_invariant_src_d(&desc_)->dims[ndims() - 3] : 1; + } + dim_t IH() const { + return ndims() >= 4 + ? conv_prop_invariant_src_d(&desc_)->dims[ndims() - 2] : 1; + } + dim_t IW() const { + return conv_prop_invariant_src_d(&desc_)->dims[ndims() - 1]; + } + + dim_t OD() const { + return ndims() >= 5 + ? conv_prop_invariant_dst_d(&desc_)->dims[ndims() - 3] : 1; + } + dim_t OH() const { + return ndims() >= 4 + ? conv_prop_invariant_dst_d(&desc_)->dims[ndims() - 2] : 1; + } + dim_t OW() const { + return conv_prop_invariant_dst_d(&desc_)->dims[ndims() - 1]; + } + + dim_t KD() const { + const int w_ndims = ndims() + with_groups(); + return ndims() >= 5 + ? conv_prop_invariant_wei_d(&desc_)->dims[w_ndims - 3] : 1; + } + dim_t KH() const { + const int w_ndims = ndims() + with_groups(); + return ndims() >= 4 + ? conv_prop_invariant_wei_d(&desc_)->dims[w_ndims - 2] : 1; + } + dim_t KW() const { + const int w_ndims = ndims() + with_groups(); + return conv_prop_invariant_wei_d(&desc_)->dims[w_ndims - 1]; + } + + dim_t KSD() const { return ndims() >= 5 ? desc_.strides[ndims() - 5] : 1; } + dim_t KSH() const { return ndims() >= 4 ? desc_.strides[ndims() - 4] : 1; } + dim_t KSW() const { return desc_.strides[ndims() - 3]; } + + dim_t KDD() const { return ndims() >= 5 ? desc_.dilates[ndims() - 5] : 0; } + dim_t KDH() const { return ndims() >= 4 ? desc_.dilates[ndims() - 4] : 1; } + dim_t KDW() const { return desc_.dilates[ndims() - 3]; } + + dim_t padFront() const + { return ndims() >= 5 ? desc_.padding[0][ndims() - 5] : 0; } + dim_t padBack() const + { return ndims() >= 5 ? desc_.padding[1][ndims() - 5] : 0; } + dim_t padT() const + { return ndims() >= 4 ? desc_.padding[0][ndims() - 4] : 0; } + dim_t padB() const + { return ndims() >= 4 ? desc_.padding[1][ndims() - 4] : 0; } + dim_t padL() const { return desc_.padding[0][ndims() - 3]; } + dim_t padR() const { return desc_.padding[1][ndims() - 3]; } + + bool with_bias() const { + return + !memory_desc_wrapper(*conv_prop_invariant_bia_d(&desc_)).is_zero(); + } + + bool with_groups() const + { return conv_prop_invariant_wei_d(&desc_)->ndims == ndims() + 1; } + + int ndims() const { return conv_prop_invariant_src_d(&desc_)->ndims; } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + + bool has_zero_dim_memory() const { + const auto s_d = memory_desc_wrapper(*conv_prop_invariant_src_d(&desc_)); + const auto d_d = memory_desc_wrapper(*conv_prop_invariant_dst_d(&desc_)); + return s_d.has_zero_dim() || d_d.has_zero_dim(); + } + +protected: + deconvolution_desc_t desc_; + const deconvolution_fwd_pd_t *hint_fwd_pd_; +}; + +struct deconvolution_fwd_pd_t: public deconvolution_pd_t { + typedef deconvolution_fwd_pd_t base_class; + typedef deconvolution_fwd_pd_t hint_class; + + deconvolution_fwd_pd_t(engine_t *engine, + const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : deconvolution_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , weights_md_(desc_.weights_desc) + , bias_md_(desc_.bias_desc) + , dst_md_(desc_.dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_WEIGHTS)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_BIAS && with_bias()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + virtual const memory_desc_t *weights_md(int index = 0) const override { + if (index == 0) return &weights_md_; + if (index == 1 && with_bias()) return &bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2 + with_bias(); } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t src_md_; + memory_desc_t weights_md_; + memory_desc_t bias_md_; + memory_desc_t dst_md_; +}; + +struct deconvolution_bwd_data_pd_t: public deconvolution_pd_t { + typedef deconvolution_bwd_data_pd_t base_class; + typedef deconvolution_fwd_pd_t hint_class; + + deconvolution_bwd_data_pd_t(engine_t *engine, + const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : deconvolution_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_src_md_(desc_.diff_src_desc) + , weights_md_(desc_.weights_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_WEIGHTS, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *weights_md(int index = 0) const override + { return index == 0 ? &weights_md_ : nullptr; } + + virtual int n_inputs() const override { return 2; } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t diff_src_md_; + memory_desc_t weights_md_; + memory_desc_t diff_dst_md_; +}; + +struct deconvolution_bwd_weights_pd_t: public deconvolution_pd_t { + typedef deconvolution_bwd_weights_pd_t base_class; + typedef deconvolution_fwd_pd_t hint_class; + + deconvolution_bwd_weights_pd_t(engine_t *engine, + const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : deconvolution_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , diff_weights_md_(desc_.diff_weights_desc) + , diff_bias_md_(desc_.diff_bias_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_WEIGHTS) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_DIFF_BIAS && with_bias()) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *diff_weights_md(int index = 0) const override { + if (index == 0) return &diff_weights_md_; + if (index == 1 && with_bias()) return &diff_bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2; } + virtual int n_outputs() const override { return 1 + with_bias(); } + +protected: + memory_desc_t src_md_; + memory_desc_t diff_weights_md_; + memory_desc_t diff_bias_md_; + memory_desc_t diff_dst_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/eltwise.cpp b/thirdparty/oidn/mkl-dnn/src/common/eltwise.cpp new file mode 100644 index 000000000000..f1708fca5226 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/eltwise.cpp @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t eltwise_desc_init(eltwise_desc_t *eltwise_desc, prop_kind_t prop_kind, + alg_kind_t alg_kind, const memory_desc_t *data_desc, + const memory_desc_t *diff_data_desc, float alpha, float beta) { + bool args_ok = true + && !any_null(eltwise_desc, data_desc) + && one_of(prop_kind, forward_training, forward_inference, + backward_data) + && one_of(alg_kind, eltwise_relu, eltwise_tanh, eltwise_elu, + eltwise_square, eltwise_abs, eltwise_sqrt, eltwise_linear, + eltwise_bounded_relu, eltwise_soft_relu, eltwise_logistic) + && IMPLICATION(prop_kind == backward_data, diff_data_desc != nullptr); + if (!args_ok) return invalid_arguments; + + auto ed = eltwise_desc_t(); + ed.primitive_kind = primitive_kind::eltwise; + ed.prop_kind = prop_kind; + ed.alg_kind = alg_kind; + + ed.data_desc = *data_desc; + ed.diff_data_desc = + (ed.prop_kind == backward_data) ? *diff_data_desc : zero_md(); + + ed.alpha = alpha; + ed.beta = beta; + + bool consistency = true + && IMPLICATION(ed.prop_kind == backward_data, + array_cmp(ed.diff_data_desc.dims, ed.data_desc.dims, + ed.diff_data_desc.ndims)); + if (!consistency) return invalid_arguments; + + *eltwise_desc = ed; + return success; +} +} + +status_t mkldnn_eltwise_forward_desc_init(eltwise_desc_t *eltwise_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *data_desc, float alpha, float beta) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return eltwise_desc_init(eltwise_desc, prop_kind, alg_kind, data_desc, + nullptr, alpha, beta); +} + +status_t mkldnn_eltwise_backward_desc_init(eltwise_desc_t *eltwise_desc, + alg_kind_t alg_kind, const memory_desc_t *diff_data_desc, + const memory_desc_t *data_desc, float alpha, float beta) { + return eltwise_desc_init(eltwise_desc, backward_data, alg_kind, data_desc, + diff_data_desc, alpha, beta); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/eltwise_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/eltwise_pd.hpp new file mode 100644 index 000000000000..9fd260fceea4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/eltwise_pd.hpp @@ -0,0 +1,161 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef ELTWISE_PD_HPP +#define ELTWISE_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" + +namespace mkldnn { +namespace impl { + +struct eltwise_fwd_pd_t; + +struct eltwise_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::eltwise; + + eltwise_pd_t(mkldnn::impl::engine_t *engine, + const eltwise_desc_t *adesc, + const primitive_attr_t *attr, + const eltwise_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , data_md_(desc_.data_desc) + {} + + const eltwise_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::eltwise_d: + *(const eltwise_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common eltwise aux functions */ + + dim_t MB() const { return data_desc().dims[0]; } + dim_t C() const { return data_desc().dims[1]; } + dim_t D() const { return ndims() >= 5 ? data_desc().dims[ndims() - 3] : 1; } + dim_t H() const { return ndims() >= 4 ? data_desc().dims[ndims() - 2] : 1; } + dim_t W() const { return ndims() >= 3 ? data_desc().dims[ndims() - 1] : 1; } + + int ndims() const { return data_desc().ndims; } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + + bool has_zero_dim_memory() const + { return memory_desc_wrapper(desc_.data_desc).has_zero_dim(); } + +protected: + eltwise_desc_t desc_; + const eltwise_fwd_pd_t *hint_fwd_pd_; + + memory_desc_t data_md_; + +private: + const memory_desc_t &data_desc() const { return desc_.data_desc; } +}; + +struct eltwise_fwd_pd_t: public eltwise_pd_t { + typedef eltwise_fwd_pd_t base_class; + typedef eltwise_fwd_pd_t hint_class; + + eltwise_fwd_pd_t(mkldnn::impl::engine_t *engine, + const eltwise_desc_t *adesc, + const primitive_attr_t *attr, + const eltwise_fwd_pd_t *hint_fwd_pd) + : eltwise_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_SRC) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + + virtual int n_inputs() const override { return 1; } + virtual int n_outputs() const override { return 1; } + + bool is_zero_preserved() const + { return math::eltwise_fwd_preserves_zero(desc_.alg_kind); } +}; + +struct eltwise_bwd_pd_t: public eltwise_pd_t { + typedef eltwise_bwd_pd_t base_class; + typedef eltwise_fwd_pd_t hint_class; + + eltwise_bwd_pd_t(engine_t *engine, + const eltwise_desc_t *adesc, + const primitive_attr_t *attr, + const eltwise_fwd_pd_t *hint_fwd_pd) + : eltwise_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_data_md_(desc_.diff_data_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + + virtual int n_inputs() const override { return 2; } + virtual int n_outputs() const override { return 1; } + + bool is_zero_preserved() const { return true; } + +protected: + memory_desc_t diff_data_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/engine.cpp b/thirdparty/oidn/mkl-dnn/src/common/engine.cpp new file mode 100644 index 000000000000..3b3e25456d87 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/engine.cpp @@ -0,0 +1,75 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn.h" +#include "engine.hpp" +#include "nstl.hpp" + +#include "c_types_map.hpp" +#include "../cpu/cpu_engine.hpp" + +namespace mkldnn { +namespace impl { + +engine_factory_t *engine_factories[] = { + &cpu::engine_factory, + nullptr, +}; + +static inline engine_factory_t *get_engine_factory(engine_kind_t kind) { + for (engine_factory_t **ef = engine_factories; *ef; ef++) + if ((*ef)->kind() == kind) + return *ef; + return nullptr; +} + +} +} + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; + +size_t mkldnn_engine_get_count(engine_kind_t kind) { + engine_factory_t *ef = get_engine_factory(kind); + return ef != nullptr ? ef->count() : 0; +} + +status_t mkldnn_engine_create(engine_t **engine, + engine_kind_t kind, size_t index) { + if (engine == nullptr) + return invalid_arguments; + + engine_factory_t *ef = get_engine_factory(kind); + if (ef == nullptr || index >= ef->count()) + return invalid_arguments; + + return ef->engine_create(engine, index); +} + +status_t mkldnn_engine_get_kind(engine_t *engine, engine_kind_t *kind) { + if (engine == nullptr) + return invalid_arguments; + *kind = engine->kind(); + return success; +} + +status_t mkldnn_engine_destroy(engine_t *engine) { + /* TODO: engine->dec_ref_count(); */ + delete engine; + return success; +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/engine.hpp b/thirdparty/oidn/mkl-dnn/src/common/engine.hpp new file mode 100644 index 000000000000..8ac8a29de56e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/engine.hpp @@ -0,0 +1,119 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef ENGINE_HPP +#define ENGINE_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive.hpp" +#include "utils.hpp" + +/** \brief An abstraction of an execution unit with shared resources + * + * Responsibilities: + * - Provide engine specific memory allocation + * - Provide engine specific primitive_desc_t creators + */ +struct mkldnn_engine: public mkldnn::impl::c_compatible { + mkldnn_engine(mkldnn::impl::engine_kind_t kind) + : kind_(kind) + {} + virtual ~mkldnn_engine() {} + + /** get kind of the current engine */ + virtual mkldnn::impl::engine_kind_t kind() const { return kind_; } + + /** allocate memory */ + virtual mkldnn::impl::status_t memory_create( + mkldnn::impl::memory_t **memory, + const mkldnn::impl::memory_desc_t *md, + void *handle) = 0; + + /** implementation section (typedefs) */ + + // TODO: remove engine? + typedef mkldnn::impl::status_t (*reorder_primitive_desc_create_f)( + mkldnn::impl::reorder_pd_t **reorder_pd, + mkldnn::impl::engine_t *engine, + const mkldnn::impl::primitive_attr_t *attr, + mkldnn::impl::engine_t *src_engine, + const mkldnn::impl::memory_desc_t *src_md, + mkldnn::impl::engine_t *dst_engine, + const mkldnn::impl::memory_desc_t *dst_md); + + typedef mkldnn::impl::status_t (*concat_primitive_desc_create_f)( + mkldnn::impl::concat_pd_t **concat_pd, + mkldnn::impl::engine_t *engine, + const mkldnn::impl::primitive_attr_t *attr, + const mkldnn::impl::memory_desc_t *dst_md, + int n, int concat_dim, + const mkldnn::impl::memory_desc_t *src_mds); + + typedef mkldnn::impl::status_t (*sum_primitive_desc_create_f)( + mkldnn::impl::sum_pd_t **sum_pd, + mkldnn::impl::engine_t *engine, + const mkldnn::impl::primitive_attr_t *attr, + const mkldnn::impl::memory_desc_t *dst_md, + int n, const float *scales, + const mkldnn::impl::memory_desc_t *src_mds); + + typedef mkldnn::impl::status_t (*primitive_desc_create_f)( + mkldnn::impl::primitive_desc_t **, const mkldnn::impl::op_desc_t *, + const mkldnn::impl::primitive_attr_t *attr, + mkldnn::impl::engine_t *, const mkldnn::impl::primitive_desc_t *); + + /* implementation section */ + + /** return the list of reorder implementations. engine guarantees to return + * a NULL-terminated list */ + virtual const reorder_primitive_desc_create_f* + get_reorder_implementation_list() const = 0; + + /** return the list of concat implementations. engine guarantees to return + * a NULL-terminated list */ + virtual const concat_primitive_desc_create_f* + get_concat_implementation_list() const = 0; + + /** return the list of sum implementations. engine guarantees to return + * a NULL-terminated list */ + virtual const sum_primitive_desc_create_f* + get_sum_implementation_list() const = 0; + + /** return the list of implementations. engine guarantees to return a + * NULL-terminated list */ + virtual const primitive_desc_create_f* get_implementation_list() const = 0; + +protected: + mkldnn::impl::engine_kind_t kind_; +}; + +namespace mkldnn { +namespace impl { + +struct engine_factory_t: public c_compatible { + virtual size_t count() const = 0; + virtual engine_kind_t kind() const = 0; + virtual status_t engine_create(engine_t **engine, size_t index) const = 0; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/inner_product.cpp b/thirdparty/oidn/mkl-dnn/src/common/inner_product.cpp new file mode 100644 index 000000000000..5a9f58cb1e85 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/inner_product.cpp @@ -0,0 +1,106 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t ip_desc_init(inner_product_desc_t *ip_desc, prop_kind_t prop_kind, + const memory_desc_t *src_desc, const memory_desc_t *weights_desc, + const memory_desc_t *bias_desc, const memory_desc_t *dst_desc) { + bool args_ok = !any_null(ip_desc, src_desc, weights_desc, dst_desc); + if (!args_ok) return invalid_arguments; + + auto id = inner_product_desc_t(); + id.primitive_kind = primitive_kind::inner_product; + id.prop_kind = prop_kind; + + id.diff_src_desc = id.src_desc = zero_md(); + id.diff_dst_desc = id.dst_desc = zero_md(); + id.diff_weights_desc = id.weights_desc = zero_md(); + id.diff_bias_desc = id.bias_desc = zero_md(); + + const bool is_fwd = one_of(prop_kind, forward_training, forward_inference); + const bool with_bias = + bias_desc && bias_desc->format_kind != format_kind::undef; + + (prop_kind == backward_data ? id.diff_src_desc : id.src_desc) = *src_desc; + (is_fwd ? id.dst_desc : id.diff_dst_desc) = *dst_desc; + (prop_kind == backward_weights ? id.diff_weights_desc : id.weights_desc) = + *weights_desc; + if (with_bias) + (prop_kind == backward_weights ? id.diff_bias_desc : id.bias_desc) = + *bias_desc; + + id.accum_data_type = types::default_accum_data_type(src_desc->data_type, + weights_desc->data_type, dst_desc->data_type, prop_kind); + + bool consistency = true + && memory_desc_wrapper(weights_desc).nelems() + && one_of(src_desc->ndims, 2, 3, 4, 5) + && dst_desc->ndims == 2 + && weights_desc->ndims == src_desc->ndims + && (with_bias ? bias_desc->ndims == 1 : true) + && (with_bias ? bias_desc->dims[0] == dst_desc->dims[1] : true) + && src_desc->dims[0] == dst_desc->dims[0] + && array_cmp(&src_desc->dims[1], &weights_desc->dims[1], + src_desc->ndims - 1) + && dst_desc->dims[1] == weights_desc->dims[0]; + if (!consistency) return invalid_arguments; + + *ip_desc = id; + return success; +} +} + +status_t mkldnn_inner_product_forward_desc_init(inner_product_desc_t *ip_desc, + prop_kind_t prop_kind, const memory_desc_t *src_desc, + const memory_desc_t *weights_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_desc) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return ip_desc_init(ip_desc, prop_kind, src_desc, weights_desc, bias_desc, + dst_desc); +} + +status_t mkldnn_inner_product_backward_data_desc_init( + inner_product_desc_t *ip_desc, const memory_desc_t *diff_src_desc, + const memory_desc_t *weights_desc, const memory_desc_t *diff_dst_desc) +{ + return ip_desc_init(ip_desc, backward_data, diff_src_desc, weights_desc, + nullptr, diff_dst_desc); +} + +status_t mkldnn_inner_product_backward_weights_desc_init( + inner_product_desc_t *ip_desc, const memory_desc_t *src_desc, + const memory_desc_t *diff_weights_desc, + const memory_desc_t *diff_bias_desc, + const memory_desc_t *diff_dst_desc) { + return ip_desc_init(ip_desc, backward_weights, src_desc, diff_weights_desc, + diff_bias_desc, diff_dst_desc); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.cpp b/thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.cpp new file mode 100644 index 000000000000..091cf0f5d6c3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.cpp @@ -0,0 +1,56 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "utils.hpp" + +#include "inner_product_pd.hpp" + +namespace mkldnn { +namespace impl { + +using namespace prop_kind; + +memory_desc_t *ip_prop_invariant_src_d(inner_product_desc_t *desc) { + return desc->prop_kind == backward_data + ? &desc->diff_src_desc : &desc->src_desc; +} + +memory_desc_t *ip_prop_invariant_wei_d(inner_product_desc_t *desc) { + return desc->prop_kind == backward_weights + ? &desc->diff_weights_desc : &desc->weights_desc; +} + +memory_desc_t *ip_prop_invariant_bia_d(inner_product_desc_t *desc) { + return desc->prop_kind == backward_weights + ? &desc->diff_bias_desc : &desc->bias_desc; +} + +memory_desc_t *ip_prop_invariant_dst_d(inner_product_desc_t *desc) { + return utils::one_of(desc->prop_kind, forward_inference, forward_training) + ? &desc->dst_desc : &desc->diff_dst_desc; +} + +const memory_desc_t *ip_prop_invariant_src_d(const inner_product_desc_t *desc) +{ return ip_prop_invariant_src_d(const_cast(desc)); } +const memory_desc_t *ip_prop_invariant_wei_d(const inner_product_desc_t *desc) +{ return ip_prop_invariant_wei_d(const_cast(desc)); } +const memory_desc_t *ip_prop_invariant_bia_d(const inner_product_desc_t *desc) +{ return ip_prop_invariant_bia_d(const_cast(desc)); } +const memory_desc_t *ip_prop_invariant_dst_d(const inner_product_desc_t *desc) +{ return ip_prop_invariant_dst_d(const_cast(desc)); } + +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.hpp new file mode 100644 index 000000000000..c426de632cab --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/inner_product_pd.hpp @@ -0,0 +1,321 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef INNER_PRODUCT_PD_HPP +#define INNER_PRODUCT_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +memory_desc_t *ip_prop_invariant_src_d(inner_product_desc_t *desc); +memory_desc_t *ip_prop_invariant_wei_d(inner_product_desc_t *desc); +memory_desc_t *ip_prop_invariant_bia_d(inner_product_desc_t *desc); +memory_desc_t *ip_prop_invariant_dst_d(inner_product_desc_t *desc); +const memory_desc_t *ip_prop_invariant_src_d(const inner_product_desc_t *desc); +const memory_desc_t *ip_prop_invariant_wei_d(const inner_product_desc_t *desc); +const memory_desc_t *ip_prop_invariant_bia_d(const inner_product_desc_t *desc); +const memory_desc_t *ip_prop_invariant_dst_d(const inner_product_desc_t *desc); + +struct inner_product_fwd_pd_t; + +struct inner_product_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::inner_product; + + inner_product_pd_t(engine_t *engine, + const inner_product_desc_t *adesc, + const primitive_attr_t *attr, + const inner_product_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + {} + + const inner_product_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::inner_product_d: + *(const inner_product_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common inner_product aux functions */ + + dim_t MB() const { return ip_prop_invariant_src_d(&desc_)->dims[0]; } + dim_t IC() const { return ip_prop_invariant_src_d(&desc_)->dims[1]; } + dim_t OC() const { return ip_prop_invariant_dst_d(&desc_)->dims[1]; } + + dim_t ID() const { + return ndims() >= 5 + ? ip_prop_invariant_src_d(&desc_)->dims[ndims() - 3] : 1; + } + dim_t IH() const { + return ndims() >= 4 + ? ip_prop_invariant_src_d(&desc_)->dims[ndims() - 2] : 1; + } + dim_t IW() const { + return ndims() >= 3 + ? ip_prop_invariant_src_d(&desc_)->dims[ndims() - 1] : 1; + } + + dim_t OD() const { + return ndims() >= 5 + ? ip_prop_invariant_dst_d(&desc_)->dims[ndims() - 3] : 1; + } + dim_t OH() const { + return ndims() >= 4 + ? ip_prop_invariant_dst_d(&desc_)->dims[ndims() - 2] : 1; + } + dim_t OW() const { + return ndims() >= 3 + ? ip_prop_invariant_dst_d(&desc_)->dims[ndims() - 1] : 1; + } + + dim_t KD() const { + return ndims() >= 5 + ? ip_prop_invariant_wei_d(&desc_)->dims[ndims() - 3] : 1; + } + dim_t KH() const { + return ndims() >= 4 + ? ip_prop_invariant_wei_d(&desc_)->dims[ndims() - 2] : 1; + } + dim_t KW() const { + return ndims() >= 3 + ? ip_prop_invariant_wei_d(&desc_)->dims[ndims() - 1] : 1; + } + + dim_t IC_total() const { + return utils::array_product(&ip_prop_invariant_src_d(&desc_)->dims[1], + ndims() - 1); + } + + dim_t IC_total_padded() const { + auto src_d = desc()->prop_kind == prop_kind::backward_data + ? memory_desc_wrapper(diff_src_md()) + : memory_desc_wrapper(src_md()); + assert(src_d.is_blocking_desc()); + if (!src_d.is_blocking_desc()) return -1; + return utils::array_product(src_d.padded_dims() + 1, ndims() - 1); + } + + int ndims() const { return ip_prop_invariant_src_d(&desc_)->ndims; } + + bool with_bias() const + { return !memory_desc_wrapper(*ip_prop_invariant_bia_d(&desc_)).is_zero(); } + + bool has_zero_dim_memory() const { + const auto s_d = memory_desc_wrapper(*ip_prop_invariant_src_d(&desc_)); + const auto d_d = memory_desc_wrapper(*ip_prop_invariant_dst_d(&desc_)); + return s_d.has_zero_dim() || d_d.has_zero_dim(); + } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + +protected: + inner_product_desc_t desc_; + const inner_product_fwd_pd_t *hint_fwd_pd_; + + status_t template_set_default_params(memory_desc_t &src_md, + memory_desc_t &weights_md, memory_desc_t &dst_md, + memory_desc_t *bias_md) { + using namespace format_tag; + if (src_md.format_kind == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md, + utils::pick(ndims() - 2, nc, ncw, nchw, ncdhw))); + } + if (dst_md.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(dst_md, nc)); + if (weights_md.format_kind == format_kind::any) { + CHECK(memory_desc_init_by_tag(weights_md, + utils::pick(ndims() - 2, oi, oiw, oihw, oidhw))); + } + if (bias_md && bias_md->format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(*bias_md, x)); + return status::success; + } +}; + +struct inner_product_fwd_pd_t: public inner_product_pd_t { + typedef inner_product_fwd_pd_t base_class; + typedef inner_product_fwd_pd_t hint_class; + + inner_product_fwd_pd_t(engine_t *engine, + const inner_product_desc_t *adesc, + const primitive_attr_t *attr, + const inner_product_fwd_pd_t *hint_fwd_pd) + : inner_product_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , weights_md_(desc_.weights_desc) + , bias_md_(desc_.bias_desc) + , dst_md_(desc_.dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_WEIGHTS)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_BIAS && with_bias()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + virtual const memory_desc_t *weights_md(int index = 0) const override { + if (index == 0) return &weights_md_; + if (index == 1 && with_bias()) return &bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2 + with_bias(); } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t src_md_; + memory_desc_t weights_md_; + memory_desc_t bias_md_; + memory_desc_t dst_md_; + + status_t set_default_params() { + return template_set_default_params(src_md_, weights_md_, dst_md_, + &bias_md_); + } +}; + +struct inner_product_bwd_data_pd_t: public inner_product_pd_t { + typedef inner_product_bwd_data_pd_t base_class; + typedef inner_product_fwd_pd_t hint_class; + + inner_product_bwd_data_pd_t(engine_t *engine, + const inner_product_desc_t *adesc, + const primitive_attr_t *attr, + const inner_product_fwd_pd_t *hint_fwd_pd) + : inner_product_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_src_md_(desc_.diff_src_desc) + , weights_md_(desc_.weights_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_WEIGHTS, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *weights_md(int index = 0) const override + { return index == 0 ? &weights_md_ : nullptr; } + + virtual int n_inputs() const override { return 2; } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t diff_src_md_; + memory_desc_t weights_md_; + memory_desc_t diff_dst_md_; + + status_t set_default_params() { + return template_set_default_params(diff_src_md_, weights_md_, + diff_dst_md_, nullptr); + } +}; + +struct inner_product_bwd_weights_pd_t: public inner_product_pd_t { + typedef inner_product_bwd_weights_pd_t base_class; + typedef inner_product_fwd_pd_t hint_class; + + inner_product_bwd_weights_pd_t(engine_t *engine, + const inner_product_desc_t *adesc, + const primitive_attr_t *attr, + const inner_product_fwd_pd_t *hint_fwd_pd) + : inner_product_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , diff_weights_md_(desc_.diff_weights_desc) + , diff_bias_md_(desc_.diff_bias_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_WEIGHTS) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_DIFF_BIAS && with_bias()) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *diff_weights_md(int index = 0) const override { + if (index == 0) return &diff_weights_md_; + if (index == 1 && with_bias()) return &diff_bias_md_; + return nullptr; + } + + virtual int n_inputs() const override { return 2; } + virtual int n_outputs() const override { return 1 + with_bias(); } + +protected: + memory_desc_t src_md_; + memory_desc_t diff_weights_md_; + memory_desc_t diff_bias_md_; + memory_desc_t diff_dst_md_; + + status_t set_default_params() { + return template_set_default_params(src_md_, diff_weights_md_, + diff_dst_md_, &diff_bias_md_); + } +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/lrn.cpp b/thirdparty/oidn/mkl-dnn/src/common/lrn.cpp new file mode 100644 index 000000000000..fcf18b556f48 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/lrn.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t lrn_desc_init(lrn_desc_t *lrn_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *data_desc, const memory_desc_t *diff_data_desc, + dim_t local_size, float alpha, float beta, float k) { + bool args_ok = true + && !any_null(lrn_desc, data_desc) + && one_of(alg_kind, lrn_within_channel, lrn_across_channels) + && one_of(prop_kind, forward_training, forward_inference, backward_data) + && IMPLICATION(prop_kind == backward_data, diff_data_desc != nullptr); + if (!args_ok) return invalid_arguments; + + auto ld = lrn_desc_t(); + ld.primitive_kind = primitive_kind::lrn; + ld.prop_kind = prop_kind; + ld.alg_kind = alg_kind; + + const bool is_fwd = one_of(prop_kind, forward_training, forward_inference); + + ld.data_desc = *data_desc; + if (!is_fwd) + ld.diff_data_desc = *diff_data_desc; + else + ld.diff_data_desc = zero_md(); + ld.local_size = local_size; + ld.lrn_alpha = alpha; + ld.lrn_beta = beta; + ld.lrn_k = k; + + bool consistency = true + && ld.data_desc.ndims == 4; + if (ld.prop_kind == backward_data) + consistency = consistency + && ld.diff_data_desc.ndims == 4 + && array_cmp(ld.diff_data_desc.dims, ld.data_desc.dims, 4); + if (!consistency) return invalid_arguments; + + *lrn_desc = ld; + return success; +} +} + +status_t mkldnn_lrn_forward_desc_init(lrn_desc_t *lrn_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *data_desc, dim_t local_size, float alpha, + float beta, float k) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return lrn_desc_init(lrn_desc, prop_kind, alg_kind, data_desc, nullptr, + local_size, alpha, beta, k); +} + +status_t mkldnn_lrn_backward_desc_init(lrn_desc_t *lrn_desc, + alg_kind_t alg_kind, const memory_desc_t *data_desc, + const memory_desc_t *diff_data_desc, dim_t local_size, float alpha, + float beta, float k) { + return lrn_desc_init(lrn_desc, backward_data, alg_kind, data_desc, + diff_data_desc, local_size, alpha, beta, k); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/lrn_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/lrn_pd.hpp new file mode 100644 index 000000000000..90886e96562a --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/lrn_pd.hpp @@ -0,0 +1,170 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef LRN_PD_HPP +#define LRN_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" + +namespace mkldnn { +namespace impl { + +struct lrn_fwd_pd_t; + +struct lrn_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::lrn; + + lrn_pd_t(engine_t *engine, + const lrn_desc_t *adesc, + const primitive_attr_t *attr, + const lrn_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , data_md_(desc_.data_desc) + , ws_md_() + {} + + const lrn_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::lrn_d: + *(const lrn_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common lrn aux functions */ + + dim_t MB() const { return data_desc().dims[0]; } + dim_t C() const { return data_desc().dims[1]; } + dim_t D() const { return ndims() >= 5 ? data_desc().dims[ndims() - 3] : 1; } + dim_t H() const { return ndims() >= 4 ? data_desc().dims[ndims() - 2] : 1; } + dim_t W() const { return ndims() >= 3 ? data_desc().dims[ndims() - 1] : 1; } + + int ndims() const { return data_desc().ndims; } + + bool has_zero_dim_memory() const + { return memory_desc_wrapper(desc_.data_desc).has_zero_dim(); } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + +protected: + lrn_desc_t desc_; + const lrn_fwd_pd_t *hint_fwd_pd_; + + memory_desc_t data_md_; + memory_desc_t ws_md_; + +private: + const memory_desc_t &data_desc() const { return desc_.data_desc; } +}; + +struct lrn_fwd_pd_t: public lrn_pd_t { + typedef lrn_fwd_pd_t base_class; + typedef lrn_fwd_pd_t hint_class; + + lrn_fwd_pd_t(engine_t *engine, + const lrn_desc_t *adesc, + const primitive_attr_t *attr, + const lrn_fwd_pd_t *hint_fwd_pd) + : lrn_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_SRC) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && (workspace_md() != nullptr)) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && !types::is_zero_md(&ws_md_) ? &ws_md_ : nullptr; } + + virtual int n_inputs() const override { return 1; } + virtual int n_outputs() const override + { return 1 + (workspace_md() != nullptr); } +}; + +struct lrn_bwd_pd_t: public lrn_pd_t { + typedef lrn_bwd_pd_t base_class; + typedef lrn_fwd_pd_t hint_class; + + lrn_bwd_pd_t(engine_t *engine, + const lrn_desc_t *adesc, + const primitive_attr_t *attr, + const lrn_fwd_pd_t *hint_fwd_pd) + : lrn_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_data_md_(desc_.diff_data_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && (workspace_md() != nullptr)) + return arg_usage_t::input; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && !types::is_zero_md(&ws_md_) ? &ws_md_ : nullptr; } + + virtual int n_inputs() const override + { return 2 + (workspace_md() != nullptr); } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t diff_data_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/math_utils.hpp b/thirdparty/oidn/mkl-dnn/src/common/math_utils.hpp new file mode 100644 index 000000000000..3fddc0bd4565 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/math_utils.hpp @@ -0,0 +1,280 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MATH_UTILS_HPP +#define MATH_UTILS_HPP + +#include +#include + +#include "utils.hpp" +#include "nstl.hpp" +#include "mkldnn_traits.hpp" + +#if defined(MKLDNN_X86_64) +#include "immintrin.h" +#endif + +namespace mkldnn { +namespace impl { +namespace math { + +/** rounds @p f to an integer according to the mxcsr register */ +inline int mxcsr_round(float f) { +#if defined(MKLDNN_X86_64) + return _mm_cvtss_si32(_mm_load_ss(&f)); +#else + return (int)nearbyintf(f); // optimism +#endif +} + +template +inline typename utils::enable_if::value, + typename utils::remove_reference::type>::type +saturate(const acc_t &x) { + return (typename utils::remove_reference::type)x; +} + +template +inline typename utils::enable_if::value, + typename utils::remove_reference::type>::type +saturate(const acc_t &x) { + acc_t v = x; + if (v < (acc_t)nstl::numeric_limits::lowest()) + v = (acc_t)nstl::numeric_limits::lowest(); + if (v > (acc_t)nstl::numeric_limits::max()) + v = (acc_t)nstl::numeric_limits::max(); + return (typename utils::remove_reference::type)v; +} + +template +double saturate(const double &x) { + double v = x; + if (v < (double)nstl::numeric_limits::lowest()) + v = (double)nstl::numeric_limits::lowest(); + if (v > (double)nstl::numeric_limits::max()) + v = (double)nstl::numeric_limits::max(); + return v; +} + +template <> inline int8_t saturate(const uint8_t &x) { + return x <= 127u ? x : 127; +} + +template <> inline uint8_t saturate(const int8_t &x) { + return x >= 0 ? x : 0; +} + +template +typename utils::enable_if::value, out_t>::type +out_round(float v) { return (out_t)mxcsr_round(v); } + +template +typename utils::enable_if::value, out_t>::type +out_round(double v) { return (out_t)mxcsr_round((float)v); } + +template +typename utils::enable_if::value, out_t>::type +out_round(float v) { return v; } + +inline int gcd(int a, int b) { + a = impl::nstl::abs(a); + b = impl::nstl::abs(b); + if (a < b) { int x = a; a = b; b = x; } + + if (b == 0) return a; + + int r; + while ((r = a % b) != 0) { a = b; b = r; } + + return b; +} + +template +inline bool is_pow2(const T& v) { return (v & (v - 1)) == 0; } + +/** returns floor(log2(v)), aka the position of the leftmost non-0 bit */ +inline int ilog2q(size_t v) { + if (v == 0) + return -1; + + int p = 0; +# define CP(pw) do { if (v >= (1ull << pw)) { v >>= pw; p += pw; } } while(0) + CP(32); CP(16); CP(8); CP(4); CP(2); CP(1); +# undef CP + return p; +} + +template ::type> +inline U one_m_square(T x) { + return (U)(1 - x) * (1 + x); +} + +template ::type> +inline U x_m_square(T x) { + return (U)(1 - x) * x; +} + +/* activation */ +template ::type> +inline U relu_fwd(T s, A alpha) { + return s > 0 ? s : (U)(s * alpha); +} +template ::type> +inline U relu_bwd(T dd, T s, A alpha) { + return s > 0 ? dd : (U)(dd * alpha); +} + +template ::type> +inline U tanh_fwd(T s) { + const float e = tanhf((float) s); + return (U)e; +} + +template ::type> +inline U tanh_bwd(T dd, T s) { + const float e = tanh_fwd((float) s); + return (U)(dd * (1 - e) * (1 + e)); +} + +template ::type> +inline U elu_fwd(T s, A alpha) { + return s > 0 ? s : (U)(alpha * (::expm1f((float)s))); +} +template ::type> + inline U elu_bwd(T dd, T s, A alpha) { + return (U)(dd * (s > 0 ? 1 : alpha * ::expf((float)s))); +} + +template ::type> +inline U square_fwd(T s) { + return s * s; +} + +template ::type> +inline U square_bwd(T dd, T s) { + return dd * 2 * s; +} + +template ::type> +inline U abs_fwd(T s) { + return s > 0 ? s : -s; +} + +template ::type> +inline U abs_bwd(T dd, T s) { + return s > 0 ? dd : s < 0 ? -dd : 0; +} + +template ::type> +inline U sqrt_fwd(T s) { + return s > 0 ? (U)(::sqrtf((float)(s))) : 0; +} + +template ::type> +inline U sqrt_bwd(T dd, T s) { + return s > 0 + ? (U)(dd / (2 * ::sqrtf((float)(s)))) + : 0; +} + +template ::type> +inline U linear_fwd(T s, A alpha, A beta) { + return (U)(alpha * s + beta); +} + +template ::type> +inline U linear_bwd(T dd, T s, A alpha, A beta) { + (void) s; + (void) beta; + return (U)(dd * alpha); +} + +template ::type> +inline U bounded_relu_fwd(T s, A alpha) { + s = s > 0 ? s : 0; + return s > alpha ? (U)(alpha) : s; +} + +template ::type> +inline U bounded_relu_bwd(T dd, T s, A alpha) { + return dd * (0 < s && s < alpha ? 1 : 0); +} + +template ::type> +inline U soft_relu_fwd(T s) { + float max_logf = 8.872284e+01; //::logf(FLT_MAX) + return s < max_logf ? (U)(::log1pf(::expf((float)s))) : s; +} + +template ::type> +inline U soft_relu_bwd(T dd, T s) { + return (U)(dd / (1 + ::expf((float)(-s)))); +} + +template ::type> +inline U logistic_fwd(T s) { + U v = (U)(::expf((float) -s)); + return 1 / (1 + v); +} + +template ::type> +inline U logistic_bwd(T dd, T s) { + U v = logistic_fwd(s); + return dd * v * (1 - v); +} + +inline bool eltwise_fwd_preserves_zero(alg_kind_t alg, bool jit_impl = false) { + using namespace alg_kind; + using namespace utils; + const bool preserves_zero = true + && !one_of(alg, eltwise_linear, eltwise_soft_relu, eltwise_logistic) + && IMPLICATION(jit_impl, !one_of(alg, eltwise_elu, eltwise_tanh)); + return preserves_zero; +} + +inline float get_bias(const char *bias, size_t offset, data_type_t data_type) +{ + if (!bias) + return 0.0f; + +#define CASE(dt) \ + case dt: return (float)((const prec_traits
::type *)bias)[offset] + + switch (data_type) { + CASE(data_type::s8); + CASE(data_type::u8); + CASE(data_type::s32); + CASE(data_type::f32); + default: assert(!"unimplemented"); + } + return 0; // never happens (should probably be a NaN) +#undef CASE +} + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/memory.cpp b/thirdparty/oidn/mkl-dnn/src/common/memory.cpp new file mode 100644 index 000000000000..cea849c96e1f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/memory.cpp @@ -0,0 +1,238 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::data_type; + +namespace { +bool memory_desc_sanity_check(int ndims,const dims_t dims, + data_type_t data_type, format_kind_t format_kind) { + if (ndims == 0) return true; + + bool ok = true + && dims != nullptr + && 0 < ndims && ndims <= MKLDNN_MAX_NDIMS + && one_of(data_type, f32, s32, s8, u8) + && format_kind != format_kind::undef; + if (!ok) return false; + for (int d = 0; d < ndims; ++d) + if (dims[d] < 0) return false; + + return true; +} + +bool memory_desc_sanity_check(const memory_desc_t *md) { + if (md == nullptr) return false; + return memory_desc_sanity_check(md->ndims, md->dims, md->data_type, + format_kind::any); +} +} + +status_t mkldnn_memory_desc_init_by_tag(memory_desc_t *memory_desc, int ndims, + const dims_t dims, data_type_t data_type, format_tag_t tag) { + if (any_null(memory_desc)) return invalid_arguments; + if (ndims == 0 || tag == format_tag::undef) { + *memory_desc = types::zero_md(); + return success; + } + + format_kind_t format_kind = types::format_tag_to_kind(tag); + + /* memory_desc != 0 */ + bool args_ok = !any_null(memory_desc) + && memory_desc_sanity_check(ndims, dims, data_type, format_kind); + if (!args_ok) return invalid_arguments; + + auto md = memory_desc_t(); + md.ndims = ndims; + array_copy(md.dims, dims, ndims); + md.data_type = data_type; + array_copy(md.padded_dims, dims, ndims); + md.format_kind = format_kind; + + status_t status = success; + if (tag == format_tag::undef) { + status = invalid_arguments; + } else if (tag == format_tag::any) { + // nop + } else if (format_kind == format_kind::blocked) { + status = memory_desc_wrapper::compute_blocking(md, tag); + } else { + assert(!"unreachable"); + status = invalid_arguments; + } + + if (status == success) + *memory_desc = md; + + return status; +} + +status_t mkldnn_memory_desc_init_by_strides(memory_desc_t *memory_desc, + int ndims, const dims_t dims, data_type_t data_type, + const dims_t strides) { + if (any_null(memory_desc)) return invalid_arguments; + if (ndims == 0) { + *memory_desc = types::zero_md(); + return success; + } + + /* memory_desc != 0 */ + bool args_ok = !any_null(memory_desc) + && memory_desc_sanity_check(ndims, dims, data_type, format_kind::any); + if (!args_ok) return invalid_arguments; + + auto md = memory_desc_t(); + md.ndims = ndims; + array_copy(md.dims, dims, ndims); + md.data_type = data_type; + array_copy(md.padded_dims, dims, ndims); + md.format_kind = format_kind::blocked; + + dims_t default_strides = {0}; + if (strides == nullptr) { + default_strides[md.ndims - 1] = 1; + for (int d = md.ndims - 2; d >= 0; --d) + default_strides[d] = default_strides[d + 1] * md.padded_dims[d + 1]; + strides = default_strides; + } else { + /* TODO: add sanity check for the provided strides */ + } + + array_copy(md.format_desc.blocking.strides, strides, md.ndims); + + *memory_desc = md; + + return status::success; +} + +status_t mkldnn_memory_desc_init_submemory(memory_desc_t *md, + const memory_desc_t *parent_md, const dims_t dims, + const dims_t offsets) { + if (any_null(md, parent_md) || !memory_desc_sanity_check(parent_md)) + return invalid_arguments; + + const memory_desc_wrapper src_d(parent_md); + + for (int d = 0; d < src_d.ndims(); ++d) { + if (dims[d] < 0 || offsets[d] < 0 + || (offsets[d] + dims[d] > src_d.dims()[d])) + return invalid_arguments; + } + + if (src_d.format_kind() != format_kind::blocked) + return unimplemented; + + dims_t blocks; + src_d.compute_blocks(blocks); + + memory_desc_t dst_d = *parent_md; + auto &dst_d_blk = dst_d.format_desc.blocking; + + /* TODO: put this into memory_desc_wrapper */ + for (int d = 0; d < src_d.ndims(); ++d) { + /* very limited functionality for now */ + const bool ok = true + && offsets[d] % blocks[d] == 0 /* [r1] */ + && src_d.padded_offsets()[d] == 0 + && (false + || dims[d] % blocks[d] == 0 + || dims[d] < blocks[d]); + if (!ok) + return unimplemented; + + const bool is_right_border = offsets[d] + dims[d] == src_d.dims()[d]; + + dst_d.dims[d] = dims[d]; + dst_d.padded_dims[d] = is_right_border + ? src_d.padded_dims()[d] - offsets[d] : dst_d.dims[d]; + dst_d.padded_offsets[d] = src_d.padded_offsets()[d]; + dst_d.offset0 += /* [r1] */ + offsets[d] / blocks[d] * dst_d_blk.strides[d]; + } + + *md = dst_d; + + return success; +} + +int mkldnn_memory_desc_equal(const memory_desc_t *lhs, + const memory_desc_t *rhs) { + if (lhs == rhs) return 1; + if (any_null(lhs, rhs)) return 0; + return memory_desc_wrapper(*lhs) == memory_desc_wrapper(*rhs); +} + +size_t mkldnn_memory_desc_get_size(const memory_desc_t *md) { + if (md == nullptr) return 0; + return memory_desc_wrapper(*md).size(); +} + +status_t mkldnn_memory_create(memory_t **memory, const memory_desc_t *md, + engine_t *engine, void *handle) { + if (any_null(memory, engine)) return invalid_arguments; + memory_desc_t z_md = types::zero_md(); + return engine->memory_create(memory, md ? md : &z_md, handle); +} + +status_t mkldnn_memory_get_memory_desc(const memory_t *memory, + const memory_desc_t **md) { + if (any_null(memory, md)) return invalid_arguments; + *md = memory->md(); + return success; +} + +status_t mkldnn_memory_get_engine(const memory_t *memory, engine_t **engine) { + if (any_null(memory, engine)) return invalid_arguments; + *engine = memory->engine(); + return success; +} + +status_t mkldnn_memory_get_data_handle(const memory_t *memory, + void **handle) { + if (any_null(handle)) + return invalid_arguments; + if (memory == nullptr) { + *handle = nullptr; + return success; + } + return memory->get_data_handle(handle); +} + +status_t mkldnn_memory_set_data_handle(memory_t *memory, void *handle) { + if (any_null(memory)) return invalid_arguments; + return memory->set_data_handle(handle); +} + +status_t mkldnn_memory_destroy(memory_t *memory) { + delete memory; + return success; +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/memory.hpp b/thirdparty/oidn/mkl-dnn/src/common/memory.hpp new file mode 100644 index 000000000000..03dfee01ff72 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/memory.hpp @@ -0,0 +1,63 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MEMORY_HPP +#define MEMORY_HPP + +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "nstl.hpp" + +struct mkldnn_memory: public mkldnn::impl::c_compatible { + mkldnn_memory(mkldnn::impl::engine_t *engine, + const mkldnn::impl::memory_desc_t *md) + : engine_(engine), md_(*md) {} + virtual ~mkldnn_memory() {} + + /** allocates/initializes memory */ + virtual mkldnn::impl::status_t init() = 0; + + /** returns memory's engine */ + mkldnn::impl::engine_t *engine() const { return engine_; } + /** returns memory's description */ + const mkldnn::impl::memory_desc_t *md() const { return &md_; } + + /** returns data handle */ + virtual mkldnn::impl::status_t get_data_handle(void **handle) const = 0; + + /** sets data handle */ + virtual mkldnn::impl::status_t set_data_handle(void *handle) = 0; + + /** zeros padding */ + virtual mkldnn::impl::status_t zero_pad() const + { return mkldnn::impl::status::success; } + +protected: + mkldnn::impl::engine_t *engine_; + const mkldnn::impl::memory_desc_t md_; + +private: + mkldnn_memory() = delete; + mkldnn_memory(const mkldnn_memory &) = delete; + mkldnn_memory(mkldnn_memory &&) = delete; + mkldnn_memory &operator=(const mkldnn_memory &) = delete; + mkldnn_memory &operator=(mkldnn_memory &&) = delete; +}; + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.cpp b/thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.cpp new file mode 100644 index 000000000000..8a99be33f3ee --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.cpp @@ -0,0 +1,212 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include + +#include "c_types_map.hpp" +#include "memory_desc_wrapper.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +status_t fill_blocked(memory_desc_t &md, + std::initializer_list perm, + std::initializer_list inner_blks, + std::initializer_list inner_idxs) { + const bool ok = true + && perm.size() == (size_t)md.ndims + && inner_blks.size() == inner_idxs.size(); + if (!ok) return status::invalid_arguments; + + md.offset0 = 0; + + blocking_desc_t &blk = md.format_desc.blocking; + + dim_t block_size = 1; + dims_t blocks = {0}; + utils::array_set(blocks, 1, md.ndims); + + blk.inner_nblks = (int)inner_blks.size(); + + int iblk = 0; + for (const auto &b: inner_idxs) + blk.inner_idxs[iblk++] = b; + + iblk = 0; + for (const auto &b: inner_blks) { + int dim = blk.inner_idxs[iblk]; + block_size *= b; + blocks[dim] *= b; + blk.inner_blks[iblk++] = b; + } + + utils::array_set(md.padded_offsets, 0, md.ndims); + for (int d = 0; d < md.ndims; ++d) + md.padded_dims[d] = utils::rnd_up(md.dims[d], blocks[d]); + + dim_t stride = block_size; + // if only we use C++14, the initializer_list would have rbegin()/rend()... + for (int d = 0; d < md.ndims; ++d) + stride *= md.padded_dims[d] == 0 ? 1 : md.padded_dims[d] / blocks[d]; + + for (const auto &d: perm) { + if (md.padded_dims[d] == 0) { + blk.strides[d] = 1; + continue; + } + stride /= md.padded_dims[d] / blocks[d]; + blk.strides[d] = stride; + } + + assert(stride == block_size); + + return status::success; +} + +status_t memory_desc_wrapper::compute_blocking(memory_desc_t &memory_desc, + format_tag_t tag) +{ + using namespace format_tag; + + if (memory_desc.ndims == 0) return status::invalid_arguments; + +# define C(tag, ... /* perm, inner_blks, inner_idxs */) \ + case tag: return fill_blocked(memory_desc, __VA_ARGS__) + + switch (tag) { + C(a, {0}, {}, {}); + C(ab, {0, 1}, {}, {}); + C(abc, {0, 1, 2}, {}, {}); + C(abcd, {0, 1, 2, 3}, {}, {}); + C(abcde, {0, 1, 2, 3, 4}, {}, {}); + C(abcdef, {0, 1, 2, 3, 4, 5}, {}, {}); + C(abdec, {0, 1, 3, 4, 2}, {}, {}); + C(acb, {0, 2, 1}, {}, {}); + C(acbde, {0, 2, 1, 3, 4}, {}, {}); + C(acdb, {0, 2, 3, 1}, {}, {}); + C(acdeb, {0, 2, 3, 4, 1}, {}, {}); + C(ba, {1, 0}, {}, {}); + C(bac, {1, 0, 2}, {}, {}); + C(bacd, {1, 0, 2, 3}, {}, {}); + C(bcda, {1, 2, 3, 0}, {}, {}); + C(cba, {2, 1, 0}, {}, {}); + C(cdba, {2, 3, 1, 0}, {}, {}); + C(cdeba, {2, 3, 4, 1, 0}, {}, {}); + C(decab, {3, 4, 2, 0, 1}, {}, {}); + + C(Abc4a, {0, 1, 2}, {4}, {0}); + C(aBc4b, {0, 1, 2}, {4}, {1}); + C(ABc4b16a4b, {0, 1, 2}, {4, 16, 4}, {1, 0, 1}); + C(ABc4b4a, {0, 1, 2}, {4, 4}, {1, 0}); + C(Abcd4a, {0, 1, 2, 3}, {4}, {0}); + C(aBcd4b, {0, 1, 2, 3}, {4}, {1}); + C(ABcd4b4a, {0, 1, 2, 3}, {4, 4}, {1, 0}); + C(aBCd4c16b4c, {0, 1, 2, 3}, {4, 16, 4}, {2, 1, 2}); + C(aBCd4c4b, {0, 1, 2, 3, 4}, {4, 4}, {2, 1}); + C(Abcde4a, {0, 1, 2, 3, 4}, {4}, {0}); + C(aBcde4b, {0, 1, 2, 3, 4}, {4}, {1}); + C(ABcde4b4a, {0, 1, 2, 3, 4}, {4, 4}, {1, 0}); + C(aBCde4c4b, {0, 1, 2, 3, 4}, {4, 4}, {2, 1}); + C(aBcdef4b, {0, 1, 2, 3, 4, 5}, {4}, {1}); + C(aBCdef4c4b, {0, 1, 2, 3, 4, 5}, {4, 4}, {2, 1}); + C(aBdc4b, {0, 1, 3, 2}, {4}, {1}); + C(aBdec4b, {0, 1, 3, 4, 2}, {4}, {1}); + C(aBdefc4b, {0, 1, 3, 4, 5, 2}, {4}, {1}); + C(Acb4a, {0, 2, 1}, {4}, {0}); + C(Acdb4a, {0, 2, 3, 1}, {4}, {0}); + C(Acdeb4a, {0, 2, 3, 4, 1}, {4}, {0}); + + C(Abc16a, {0, 1, 2}, {16}, {0}); + C(ABc16a16b, {0, 1, 2}, {16, 16}, {0, 1}); + C(aBc16b, {0, 1, 2}, {16}, {1}); + C(ABc16b16a, {0, 1, 2}, {16, 16}, {1, 0}); + C(ABc8a16b2a, {0, 1, 2}, {8, 16, 2}, {0, 1, 0}); + C(ABc8a8b, {0, 1, 2}, {8, 8}, {0, 1}); + C(aBc8b, {0, 1, 2}, {8}, {1}); + C(ABc8b16a2b, {0, 1, 2}, {8, 16, 2}, {1, 0, 1}); + C(ABc8b8a, {0, 1, 2}, {8, 8}, {1, 0}); + C(Abcd16a, {0, 1, 2, 3}, {16}, {0}); + C(ABcd16a16b, {0, 1, 2, 3}, {16, 16}, {0, 1}); + C(aBcd16b, {0, 1, 2, 3}, {16}, {1}); + C(ABcd16b16a, {0, 1, 2, 3}, {16, 16}, {1, 0}); + C(aBCd16b16c, {0, 1, 2, 3}, {16, 16}, {1, 2}); + C(aBCd16c16b, {0, 1, 2, 3}, {16, 16}, {2, 1}); + C(ABcd4b16a4b, {0, 1, 2, 3}, {4, 16, 4}, {1, 0, 1}); + C(ABcd8a16b2a, {0, 1, 2, 3}, {8, 16, 2}, {0, 1, 0}); + C(ABcd8a8b, {0, 1, 2, 3}, {8, 8}, {0, 1}); + C(aBcd8b, {0, 1, 2, 3}, {8}, {1}); + C(ABcd8b16a2b, {0, 1, 2, 3}, {8, 16, 2}, {1, 0, 1}); + C(aBCd8b16c2b, {0, 1, 2, 3}, {8, 16, 2}, {1, 2, 1}); + C(ABcd8b8a, {0, 1, 2, 3}, {8, 8}, {1, 0}); + C(aBCd8b8c, {0, 1, 2, 3}, {8, 8}, {1, 2}); + C(aBCd8c16b2c, {0, 1, 2, 3}, {8, 16, 2}, {2, 1, 2}); + C(aBCd8c8b, {0, 1, 2, 3}, {8, 8}, {2, 1}); + C(Abcde16a, {0, 1, 2, 3, 4}, {16}, {0}); + C(ABcde16a16b, {0, 1, 2, 3, 4}, {16, 16}, {0, 1}); + C(aBcde16b, {0, 1, 2, 3, 4}, {16}, {1}); + C(ABcde16b16a, {0, 1, 2, 3, 4}, {16, 16}, {1, 0}); + C(aBCde16b16c, {0, 1, 2, 3, 4}, {16, 16}, {1, 2}); + C(aBCde16c16b, {0, 1, 2, 3, 4}, {16, 16}, {2, 1}); + C(aBCde2c8b4c, {0, 1, 2, 3, 4}, {2, 8, 4}, {2, 1, 2}); + C(aBCde4b4c, {0, 1, 2, 3, 4}, {4, 4}, {1, 2}); + C(aBCde4c16b4c, {0, 1, 2, 3, 4}, {4, 16, 4}, {2, 1, 2}); + C(Abcde8a, {0, 1, 2, 3, 4}, {8}, {0}); + C(ABcde8a8b, {0, 1, 2, 3, 4}, {8, 8}, {0, 1}); + C(aBcde8b, {0, 1, 2, 3, 4}, {8}, {1}); + C(ABcde8b16a2b, {0, 1, 2, 3, 4}, {8, 16, 2}, {1, 0, 1}); + C(aBCde8b16c2b, {0, 1, 2, 3, 4}, {8, 16, 2}, {1, 2, 1}); + C(ABcde8b8a, {0, 1, 2, 3, 4}, {8, 8}, {1, 0}); + C(aBCde8b8c, {0, 1, 2, 3, 4}, {8, 8}, {1, 2}); + C(aBCde8c16b2c, {0, 1, 2, 3, 4}, {8, 16, 2}, {2, 1, 2}); + C(aBCde8c8b, {0, 1, 2, 3, 4}, {8, 8}, {2, 1}); + C(aBcdef16b, {0, 1, 2, 3, 4, 5}, {16}, {1}); + C(aBCdef16b16c, {0, 1, 2, 3, 4, 5}, {16, 16}, {1, 2}); + C(aBCdef16c16b, {0, 1, 2, 3, 4, 5}, {16, 16}, {2, 1}); + C(aBCdef8b8c, {0, 1, 2, 3, 4, 5}, {8, 8}, {1, 2}); + C(aBCdef8c16b2c, {0, 1, 2, 3, 4, 5}, {8, 16, 2}, {2, 1, 2}); + C(aBCdef8c8b, {0, 1, 2, 3, 4, 5}, {8, 8}, {2, 1}); + C(aBdc16b, {0, 1, 3, 2}, {16}, {1}); + C(aBdc8b, {0, 1, 3, 2}, {8}, {1}); + C(aBdec16b, {0, 1, 3, 4, 2}, {16}, {1}); + C(aBdec8b, {0, 1, 3, 4, 2}, {8}, {1}); + C(aBdefc16b, {0, 1, 3, 4, 5, 2}, {16}, {1}); + C(aBdefc8b, {0, 1, 3, 4, 5, 2}, {8}, {1}); + C(Acb16a, {0, 2, 1}, {16}, {0}); + C(Acb8a, {0, 2, 1}, {8}, {0}); + C(aCBd16b16c, {0, 2, 1, 3}, {16, 16}, {1, 2}); + C(aCBde16b16c, {0, 2, 1, 3, 4}, {16, 16}, {1, 2}); + C(Acdb16a, {0, 2, 3, 1}, {16}, {0}); + C(Acdb8a, {0, 2, 3, 1}, {8}, {0}); + C(Acdeb16a, {0, 2, 3, 4, 1}, {16}, {0}); + C(Acdeb8a, {0, 2, 3, 4, 1}, {8}, {0}); + C(BAc16a16b, {1, 0, 2}, {16, 16}, {0, 1}); + C(BAcd16a16b, {1, 0, 2, 3}, {16, 16}, {0, 1}); + default: break; + } + +#undef C + + return status::invalid_arguments; +} + +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.hpp b/thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.hpp new file mode 100644 index 000000000000..1758f9078a40 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/memory_desc_wrapper.hpp @@ -0,0 +1,400 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MEMORY_DESC_WRAPPER_HPP +#define MEMORY_DESC_WRAPPER_HPP + +#include + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "utils.hpp" + +#include "type_helpers.hpp" + +namespace mkldnn { +namespace impl { + +/** thin wrapper class over \struct memory_desc_t which allows easy + * manipulations with underlying C structure, which is taken by reference */ +struct memory_desc_wrapper: public c_compatible { + const memory_desc_t *md_; + + /** constructor which takes a reference to a constant underlying C memory + * descriptor \param md */ + memory_desc_wrapper(const memory_desc_t *md): md_(md) {} + memory_desc_wrapper(const memory_desc_t &md): memory_desc_wrapper(&md) {} + + /* implementing attributes */ + int ndims() const { return md_->ndims; } + const dims_t &dims() const { return md_->dims; } + data_type_t data_type() const { return md_->data_type; } + + const dims_t &padded_dims() const { return md_->padded_dims; } + const dims_t &padded_offsets() const { return md_->padded_offsets; } + dim_t offset0() const { return md_->offset0; } + + format_kind_t format_kind() const { return md_->format_kind; } + + bool is_blocking_desc() const + { return format_kind() == format_kind::blocked; } + bool is_wino_desc() const + { return format_kind() == format_kind::wino; } + bool is_rnn_packed_desc() const + { return format_kind() == format_kind::rnn_packed; } + + const blocking_desc_t &blocking_desc() const { + assert(is_blocking_desc()); + return md_->format_desc.blocking; + } + const wino_desc_t &wino_desc() const { + assert(is_wino_desc()); + return md_->format_desc.wino_desc; + } + const rnn_packed_desc_t &rnn_packed_desc() const { + assert(is_rnn_packed_desc()); + return md_->format_desc.rnn_packed_desc; + } + + const memory_extra_desc_t &extra() const { return md_->extra; } + + /* some useful function */ + + /** returns the number of elements including padding if \param with_padding + * is true, and the number of data elements otherwise */ + dim_t nelems(bool with_padding = false) const { + if (is_zero()) return 0; + return utils::array_product( + with_padding ? padded_dims() : dims(), ndims()); + } + + /** returns true if memory descriptor is zero */ + bool is_zero() const { return ndims() == 0; } + + /** returns true if memory descriptor contains zero as one of its dim */ + bool has_zero_dim() const { return nelems() == 0; } + + /** return the size of data type (a shortcut) */ + size_t data_type_size() const + { return types::data_type_size(data_type()); } + + /** return the size of data type of additional buffer */ + size_t additional_buffer_data_size() const { + if (extra().flags & memory_extra_flags::compensation_conv_s8s8) + return sizeof(int32_t); + return 0; + } + + /** return true if memory format has additional buffer */ + bool is_additional_buffer() const { + return (extra().flags & memory_extra_flags::compensation_conv_s8s8); + } + + /** returns the size of additional buffer */ + size_t additional_buffer_size() const { + if (extra().flags & memory_extra_flags::compensation_conv_s8s8) { + int cmask = extra().compensation_mask; + assert(cmask == 1 || cmask == 3); + dim_t prod = 1; + for (int d = 0; d < ndims(); ++d) + if (cmask & (1<(max_size, + padded_dims()[d] / blocks[d] * bd.strides[d]); + + if (max_size == 1 && bd.inner_nblks != 0) { + max_size = utils::array_product(bd.inner_blks, bd.inner_nblks); + } + + return max_size * data_type_size() + additional_buffer_size(); + } + } + + /** returns true if data is dense in memory */ + bool is_dense(bool with_padding = false) const { + if (utils::one_of(format_kind(), format_kind::undef, format_kind::any)) + return false; + return nelems(with_padding) * data_type_size() == size(); + } + + /** returns true if memory desc is fully defined */ + bool is_defined() const { return format_kind() != format_kind::any; } + + /** returns true if the only (potentially) padded dim is \param dim */ + bool only_padded_dim(int dim) const { + for (int d = 0; d < ndims(); ++d) + if (d != dim && dims()[d] != padded_dims()[d]) + return false; + return true; + } + + /** returns true if memory desc has blocked layout and block dims are 1s */ + bool is_plain() const { + if (!is_blocking_desc()) return false; + return blocking_desc().inner_nblks == 0; + } + + /** returns overall block sizes */ + void compute_blocks(dims_t blocks) const { + if (!is_blocking_desc()) { + utils::array_set(blocks, 0, ndims()); + return; + } + + utils::array_set(blocks, 1, ndims()); + + const auto &bd = blocking_desc(); + for (int iblk = 0; iblk < bd.inner_nblks; ++iblk) + blocks[bd.inner_idxs[iblk]] *= bd.inner_blks[iblk]; + } + + /* comparison section */ + + bool operator==(const memory_desc_wrapper &rhs) const + { return *this->md_ == *rhs.md_; } + bool operator!=(const memory_desc_wrapper &rhs) const + { return !operator==(rhs); } + bool operator==(const memory_desc_t &rhs) const + { return operator==(memory_desc_wrapper(rhs)); } + bool operator!=(const memory_desc_t &rhs) const + { return !operator==(rhs); } + + /** returns true if data (w/o padding if with_padding == false and w/ + * padding otherwise) have the same physical structure, i.e. dimensions, + * strides, and blocked structure. Depending on with_data_type flag + * data_type is taken or not taken into account. dim_start allows to check + * similarity for the logical part of data [dim_start .. ndims()]. + * CAUTION: format kind any and undef are not similar to whatever, hence the + * following statement might be true: lhs == rhs && !lhs.similar_to(rhs) */ + /* TODO: revise */ + bool similar_to(const memory_desc_wrapper &rhs, + bool with_padding = true, bool with_data_type = true, + int dim_start = 0) const; + + /** returns true if one memory can be reordered to another */ + bool consistent_with(const memory_desc_wrapper &rhs) const; + + /** returns true if the memory desc corresponds to the given format tag and + * strides. + * @sa memory_desc_matches_tag */ + bool matches_tag(format_tag_t tag, const dims_t strides = nullptr) const { + return memory_desc_matches_tag(*md_, tag, strides); + } + + /** returns matching tag (or undef if match is not found) + * XXX: This is a workaround that eventually should go away! */ + template + format_tag_t matches_one_of_tag(Tags ...tags) const { + for (const auto tag: {tags...}) { + if (memory_desc_matches_tag(*md_, tag)) + return tag; + } + return format_tag::undef; + } + + /* offset section */ + + /** returns physical offset by logical one. logical offset is represented by + * an array \param pos. if \param is_pos_padded is true \param pos + * represents the position in already padded area */ + dim_t off_v(const dims_t pos, bool is_pos_padded = false) const { + assert(is_blocking_desc()); + const blocking_desc_t &blk = blocking_desc(); + + dims_t pos_copy = {0}; + for (int d = 0; d < ndims(); ++d) + pos_copy[d] = pos[d] + (is_pos_padded ? 0 : padded_offsets()[d]); + + dim_t phys_offset = offset0(); + + if (blk.inner_nblks > 0) { + dim_t blk_stride = 1; + for (int iblk = blk.inner_nblks - 1; iblk >= 0; --iblk) { + const int d = blk.inner_idxs[iblk]; + const dim_t p = pos_copy[d] % blk.inner_blks[iblk]; + + phys_offset += p * blk_stride; + + pos_copy[d] /= blk.inner_blks[iblk]; + + blk_stride *= blk.inner_blks[iblk]; + } + } + + for (int d = 0; d < ndims(); ++d) { + const dim_t p = pos_copy[d]; + phys_offset += p * blk.strides[d]; + } + + return phys_offset; + } + + /** returns physical offset by logical one. logical offset is represented by + * a scalar \param l_offset. if \param is_pos_padded is true, \param + * l_offset represents logical offset in already padded area */ + dim_t off_l(dim_t l_offset, bool is_pos_padded = false) const { + assert(is_blocking_desc()); + dims_t pos; + for (int rd = 0; rd < ndims(); ++rd) { + const int d = ndims() - 1 - rd; + const dim_t cur_dim = is_pos_padded ? padded_dims()[d] : dims()[d]; + pos[d] = l_offset % cur_dim; + l_offset /= cur_dim; + } + return off_v(pos, is_pos_padded); + } + + /** returns physical offset by logical one. logical offset is represented by + * a tuple of indices (\param xn, ..., \param x1, \param x0) */ + template + dim_t off(Args... args) const { + assert(sizeof...(args) == ndims()); + dims_t pos = { args... }; + return off_v(pos, false); + } + + /** returns physical offset by logical one. logical offset is represented by + * a tuple of indices (\param xn, ..., \param x1, \param x0) in already + * padded area */ + template + dim_t off_padding(Args... args) const { + assert(sizeof...(args) == ndims()); + dims_t pos = { args... }; + return off_v(pos, true); + } + + /** returns physical offset by logical one. Logical offset is represented by + * a tuple of block indices (\param bn, ..., \param b1, \param b0). It is a + * user responsibility to adjust the result to get offset within blocks */ + template + dim_t blk_off(Args... args) const { + return _blk_off(args...); + } + + template + dim_t blk_off(T xn, Args... args) const { + return skip_first + ? blk_off(args...) + : blk_off(xn, args...); + } + + /* static functions section */ + /* TODO: replace with non-static, once md_ becomes non-const ref */ + + static status_t compute_blocking(memory_desc_t &memory_desc, + format_tag_t tag); + +private: + /* TODO: put logical_offset in utils */ + template + dim_t logical_offset(T x0) const { return x0; } + + template + dim_t logical_offset(T xn, Args... args) const { + const size_t n_args = sizeof...(args); + return xn * utils::array_product( + &dims()[ndims() - n_args]) + logical_offset(args...); + } + + template + dim_t _blk_off() const { return offset0(); } + + template + dim_t _blk_off(T xc, Args ...args) const { + assert(is_blocking_desc()); + constexpr int dc = ORIG_LEN - sizeof...(args) - 1; + return xc * blocking_desc().strides[dc] + + _blk_off(args...); + } +}; + +inline bool memory_desc_wrapper::similar_to(const memory_desc_wrapper &rhs, + bool with_padding, bool with_data_type, int dim_start) const { + using namespace utils; + + if (one_of(format_kind(), format_kind::undef, format_kind::any)) + return false; + if (is_wino_desc() || is_rnn_packed_desc()) + return false; + + const int ds = dim_start; + const auto &blk = blocking_desc(); + const auto &r_blk = rhs.blocking_desc(); + + return ndims() == rhs.ndims() + && dim_start <= ndims() /* guard */ + && format_kind() == rhs.format_kind() + && IMPLICATION(with_data_type, data_type() == rhs.data_type()) + && array_cmp(dims() + ds, rhs.dims() + ds, ndims() - ds) + && array_cmp(blk.strides + ds, r_blk.strides + ds, ndims() - ds) + && blk.inner_nblks == r_blk.inner_nblks + && array_cmp(blk.inner_blks, r_blk.inner_blks, blk.inner_nblks) + && array_cmp(blk.inner_idxs, r_blk.inner_idxs, blk.inner_nblks) + && IMPLICATION(with_padding, true + && array_cmp(padded_dims() + ds, rhs.padded_dims() + ds, + ndims() - ds) + && array_cmp(padded_offsets() + ds, rhs.padded_offsets() + ds, + ndims() - ds)); +} + +inline bool memory_desc_wrapper::consistent_with( + const memory_desc_wrapper &rhs) const { + if (ndims() == rhs.ndims()) { + for (int d = 0; d < ndims(); ++d) { + if (dims()[d] != rhs.dims()[d]) return false; + } + return true; + } else { + /* TODO: revise. + * is the following possible? + * [1, a, b] <--reorder--> [a, b] + * [a, 1, b] <--reorder--> [a, b] + * not, at least for now */ + return false; + } +} + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/memory_tracking.hpp b/thirdparty/oidn/mkl-dnn/src/common/memory_tracking.hpp new file mode 100644 index 000000000000..ec077b308c65 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/memory_tracking.hpp @@ -0,0 +1,295 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MEMORY_TRACKING_HPP +#define MEMORY_TRACKING_HPP + +#include +#include + +#include "nstl.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace memory_tracking { + +/* Memory tracking capabilities + * + * The main purpose of this header file is to provide uniform way to register + * required memory for a scratchpad at a primitive descriptor creation time + * and then easily access it having only the base address of the scratchpad. + * + * Primitives might contain multiple disjoint parts that require temporary + * buffers (known as scratchpad) during their execution. A primitive descriptor + * should summarize all the needs into one single number -- the buffer size + * that would be requested from a user. At execution time, the corresponding + * primitive will receive a base pointer to a scratchpad. It then needs to + * provide each part of algorithm the corresponding piece of memory. Three main + * challenges here are: + * 1. Track correct offset (from the base scratchpad address) for each piece + * 2. Algorithm might require that different memory pieces to be aligned, so + * the scratchpad size is no more just a sum of size of the corresponding + * subparts. + * 3. While a primitive is responsible for its scratchpad, the implementation + * might use some other basic blocks (e.g. cpu_reducer) that also require + * scratchpad memory. So there should be a simple way of passing the + * information back and force between the main algorithm (a primitive) and + * auxiliary stuff that lives completely separately from it (e.g. reducer). + * + * To address these challenges this header file provides 3 structures: + * 1. registry_t -- the class the stores the information about requested + * memory. The information includes required size and desired + * alignment for each piece. This class is also responsible + * for computing the right offset to a given piece using the + * base pointer. + * This class is basically a ledger with all entries. + * Lives in primitive descriptors. + * + * 2. registrar_t -- the interface to a registry_t to book memory. Used at + * primitive descriptor creation time only. Contains a + * reference to the corresponding *mutable* registry. + * Always modifiable. + * Allows chaining (using prefixes). + * + * 3. grantor_t -- the interface to a registry_t to access memory. Used at + * primitive execution time only. Contains a reference to + * the corresponding *constant* registry and base pointer. + * Always constant. + * Allows chaining (using prefixes). + * + * Both registrar_t and grantor_t allow chaining with extra prefix provided. + * The feature is useful when a primitive offload a part of computations to + * some other primitives which require their own scratchpad space + * (e.g. reducer). Prefixes are used to avoid key collision in cases when + * multiple sub-primitive (e.g. multiple reducers) are used. + * + * A short example below demonstrates how to use aforementioned classes. In it + * the main primitive is convolution that uses scratchpad for keeping padded + * bias. It also needs a reducer, that needs its own space as well. + * + * ``` c++ + * struct reducer_t { + * static void init(registrar_t &scratchpad) { + * // preserve space for the reduction (one page aligned) + * scratchpad.book(key_space, sizeof(float) * 980 * 1024, 4096); + * } + * + * void exec(const grantor_t &scratchpad) { + * // get the pointer to preserved space. scratchpad came from + * // upper primitive (convolution in this example) + * auto space = scratchpad.get(key_reducer_space); + * + * space[:] += ...; + * } + * }; + * + * struct conv_t { + * struct pd_t { + * void init() { + * registrar_t scratchpad(scratchpad_registry_); + * + * // preserve a space for padded bias (using default alignment) + * scratchpad.book(key_conv_padded_bias, 128); + * + * // create a proxy registrar for the reducer All entries made + * // by reducer would live in convolution's registry, but would + * // have their own `prefix`, so no interference with conv's + * // buffers. + * registrar_t reducer_scratchpad(scratchpad, prefix_reducer); + * + * reducer_t::init(reducer_scratchpad); + * } + * + * registry_t scratchpad_registry_; + * } + * + * void exec() { + * // get the base pointer to a scratchpad memory from a user + * void *scratchpad_ptr = this->input(MKLDNN_MEM_SCRATCHPAD); + * + * // create a grantor to the scratchpad (and provide the base + * // pointer). + * grantor_t scratchpad(pd()->scratchpad_registry_, scratchpad_ptr); + * + * // access the padded_bias (need only key name and the grantor) + * auto padded_bias = scratchpad.get(key_conv_padded_bias); + * + * // to give the `right` grantor to reducer we need to add the + * // corresponding prefix, so that reducer would be able to access + * // its keys. The call is very similar to the one in pd_t::init + * // with only difference in types: grantor_t vs registrar_t. + * grantor_t reducer_scratchpad(scratchpad, prefix_reducer); + * reducer->exec(reducer_scratchpad); + * } + * }; + * ``` + */ + + +/* namespace with common keys and prefixes */ +namespace names { +enum { + key_none = 0, + key_bnorm_tmp_mean, + key_bnorm_tmp_var, + key_bnorm_tmp_diff_ss, + key_bnorm_tmp_stats, + key_bnorm_reduction, + key_concat_iptrs, + key_concat_istrides, + key_concat_nelems, + key_concat_optrs, + key_conv_adjusted_scales, + key_conv_bia_reduction, + key_conv_gemm_col, + key_conv_gemm_imtr, + key_conv_int_dat_in_acc_dt, + key_conv_padded_bias, + key_conv_rtus_space, + key_conv_tr_diff_dst, + key_conv_tr_diff_dst_bctx, + key_conv_tr_src, + key_conv_tr_src_bctx, + key_conv_wei_reduction, + key_conv_wei_bia_reduction, + key_conv_wei_bia_reduction_bctx, + key_iprod_int_dat_in_acc_dt, + key_reducer_space, + key_reducer_space_bctx, + key_reorder_wino_plain, + key_reorder_wino_transform_space, + key_reorder_rnn_weights_quantization, + key_reorder_rnn_weights_reduction, + key_rnn_space, + key_rnn_ptrs_bia, + key_rnn_ptrs_wei_layer, + key_rnn_ptrs_wei_iter, + key_softmax_reduction, + key_wino_U, + key_wino_V, + key_wino_M, + key_barrier, +}; + +enum { + prefix_none = 0, + prefix_reducer_bia, + prefix_reducer_wei, +}; +} + +// level 0: 00 00 00 xxx +// level 1: 00 00 aa xxx +// level 2: 00 aa bb xxx +// level 3: aa bb cc xxx +// max # of levels: 3 + 1 (base_level) +// here: +// xxx : [1 .. MAX_KEY) : key +// aa, bb, cc : [1 .. MAX_PREFIX) : prefixes for levels 1, 2, and 3 + +using key_t = uint32_t; +enum { MAX_KEY = (1u << 10), MAX_PREFIX = (1u << 7), }; + +/// generates global key based on a prefix and a local key +inline key_t make_key(key_t prefix, key_t key) { return prefix + key; } + +/// generates global prefix based on the global parent and the local ones +inline key_t make_prefix(key_t parent_prefix, key_t prefix) +{ return MAX_PREFIX * parent_prefix + MAX_KEY * prefix; } + +struct registrar_t; +struct grantor_t; + +struct registry_t { + void book(const key_t &key, size_t size, size_t alignment) { + if (size == 0) return; + assert(offset_map_.count(key) == 0); + + size = utils::rnd_up(size, minimal_alignment); + alignment = nstl::max(alignment, minimal_alignment); + offset_map_[key] = entry_t{size_, size, alignment}; + + size_ += size + alignment - minimal_alignment; + } + + void *get(const key_t &key, void *base_ptr) const { + if (base_ptr == nullptr) { assert(size() == 0); return nullptr; } + if (offset_map_.count(key) != 1) return nullptr; + + const auto &e = offset_map_.at(key); + base_ptr = utils::align_ptr(base_ptr, minimal_alignment); + char *ptr = (char *)base_ptr + e.offset; + return utils::align_ptr(ptr, e.alignment); + } + + size_t size() const + { return size_ > 0 ? size_ + minimal_alignment - 1 : 0; } + + registrar_t registrar(); + grantor_t grantor(void *base_ptr) const; + +protected: + enum { minimal_alignment = 64 }; + struct entry_t { size_t offset, size, alignment; }; + + std::unordered_map offset_map_; + size_t size_ = 0; +}; + +struct registrar_t { + enum { default_alignment = 64 }; + + registrar_t(registry_t ®istry): registry_(registry), prefix_(0) {} + registrar_t(registrar_t &parent, const key_t &prefix) + : registry_(parent.registry_) + , prefix_(make_prefix(parent.prefix_, prefix)) {} + + void book(const key_t &key, size_t size, + size_t alignment = default_alignment) + { registry_.book(make_key(prefix_, key), size, alignment); } + +protected: + registry_t ®istry_; + const key_t prefix_; +}; + +struct grantor_t { + grantor_t(const registry_t ®istry, void *base_ptr) + : registry_(registry), prefix_(0), base_ptr_(base_ptr) {} + grantor_t(const grantor_t &parent, const key_t &prefix) + : registry_(parent.registry_) + , prefix_(make_prefix(parent.prefix_, prefix)) + , base_ptr_(parent.base_ptr_) {} + + template T *get(const key_t &key) const + { return (T *)registry_.get(make_key(prefix_, key), base_ptr_); } + +protected: + const registry_t ®istry_; + const key_t prefix_; + void *base_ptr_; +}; + +inline registrar_t registry_t::registrar() { return registrar_t(*this); } +inline grantor_t registry_t::grantor(void *base_ptr) const +{ return grantor_t(*this, base_ptr); } + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug.cpp b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug.cpp new file mode 100644 index 000000000000..2ef4a8fddca9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug.cpp @@ -0,0 +1,131 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include +#include + +#include "mkldnn_debug.h" +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#define DPRINT(...) do { \ + int l = snprintf(str + written_len, str_len, __VA_ARGS__); \ + if (l < 0) return l; \ + if ((size_t)l >= str_len) return -1; \ + written_len += l; str_len -= l; \ +} while(0) + +int mkldnn_md2fmt_str(char *str, size_t str_len, + const mkldnn_memory_desc_t *mdesc) { + using namespace mkldnn::impl; + + if (str == nullptr || str_len <= 1u) + return -1; + + int written_len = 0; + + if (mdesc == nullptr) { + DPRINT("%s::%s::", + mkldnn_dt2str(data_type::undef), + mkldnn_fmt_kind2str(format_kind::undef)); + return written_len; + } + + memory_desc_wrapper md(mdesc); + + DPRINT("%s:", mkldnn_dt2str(md.data_type())); + + bool padded_dims = false, padded_offsets = false; + for (int d = 0; d < md.ndims(); ++d) { + if (md.dims()[d] != md.padded_dims()[d]) padded_dims = true; + if (md.padded_offsets()[d] != 0) padded_offsets = true; + } + bool offset0 = md.offset0(); + DPRINT("%s%s%s:", + padded_dims ? "p" : "", + padded_offsets ? "o" : "", + offset0 ? "0" : ""); + + DPRINT("%s:", mkldnn_fmt_kind2str(md.format_kind())); + + if (!md.is_blocking_desc()) { + /* TODO: extend */ + DPRINT("%s:", ""); + } else { + const auto &blk = md.blocking_desc(); + + dims_t blocks; + md.compute_blocks(blocks); + + char dim_chars[MKLDNN_MAX_NDIMS + 1]; + + bool plain = true; + for (int d = 0; d < md.ndims(); ++d) { + dim_chars[d] = (blocks[d] == 1 ? 'a' : 'A') + (char)d; + if (blocks[d] != 1) plain = false; + } + + dims_t strides; + utils::array_copy(strides, blk.strides, md.ndims()); + utils::simultaneous_sort(strides, dim_chars, md.ndims(), + [](dim_t a, dim_t b) { return b - a; }); + + dim_chars[md.ndims()] = '\0'; + DPRINT("%s", dim_chars); + + if (!plain) { + for (int iblk = 0; iblk < blk.inner_nblks; ++iblk) { + DPRINT("%d%c", (int)blk.inner_blks[iblk], + 'a' + (char)blk.inner_idxs[iblk]); + } + } + + DPRINT("%s", ":"); + } + + DPRINT("f%lx", (long)md.extra().flags); + + return written_len; +} + +int mkldnn_md2dim_str(char *str, size_t str_len, + const mkldnn_memory_desc_t *mdesc) { + using namespace mkldnn::impl; + + if (str == nullptr || str_len <= 1) + return -1; + + int written_len = 0; + + if (mdesc == nullptr || mdesc->ndims == 0) { + DPRINT("%s", ""); + return written_len; + } + + memory_desc_wrapper md(mdesc); + + for (int d = 0; d < md.ndims() - 1; ++d) + DPRINT("%" PRId64 "x", md.dims()[d]); + DPRINT("%" PRId64, md.dims()[md.ndims() - 1]); + + return written_len; +} + +#undef DPRINT diff --git a/thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp new file mode 100644 index 000000000000..16a8f7ea5e66 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp @@ -0,0 +1,365 @@ +/******************************************************************************* +* Copyright 2018-2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* DO NOT EDIT, AUTO-GENERATED */ + +#include + +#include "mkldnn_debug.h" +#include "mkldnn_types.h" + +const char *mkldnn_status2str(mkldnn_status_t v) { + if (v == mkldnn_success) return "success"; + if (v == mkldnn_out_of_memory) return "out_of_memory"; + if (v == mkldnn_try_again) return "try_again"; + if (v == mkldnn_invalid_arguments) return "invalid_arguments"; + if (v == mkldnn_not_ready) return "not_ready"; + if (v == mkldnn_unimplemented) return "unimplemented"; + if (v == mkldnn_iterator_ends) return "iterator_ends"; + if (v == mkldnn_runtime_error) return "runtime_error"; + if (v == mkldnn_not_required) return "not_required"; + assert(!"unknown status"); + return "unknown status"; +} + +const char *mkldnn_dt2str(mkldnn_data_type_t v) { + if (v == mkldnn_data_type_undef) return "undef"; + if (v == mkldnn_f32) return "f32"; + if (v == mkldnn_s32) return "s32"; + if (v == mkldnn_s8) return "s8"; + if (v == mkldnn_u8) return "u8"; + assert(!"unknown dt"); + return "unknown dt"; +} + +const char *mkldnn_fmt_kind2str(mkldnn_format_kind_t v) { + if (v == mkldnn_format_kind_undef) return "undef"; + if (v == mkldnn_format_kind_any) return "any"; + if (v == mkldnn_blocked) return "blocked"; + if (v == mkldnn_format_kind_wino) return "wino"; + if (v == mkldnn_format_kind_rnn_packed) return "rnn_packed"; + assert(!"unknown fmt_kind"); + return "unknown fmt_kind"; +} + +const char *mkldnn_fmt_tag2str(mkldnn_format_tag_t v) { + if (v == mkldnn_format_tag_undef) return "undef"; + if (v == mkldnn_format_tag_any) return "format_tag_any"; + if (v == mkldnn_a) return "a"; + if (v == mkldnn_ab) return "ab"; + if (v == mkldnn_abc) return "abc"; + if (v == mkldnn_abcd) return "abcd"; + if (v == mkldnn_abcde) return "abcde"; + if (v == mkldnn_abcdef) return "abcdef"; + if (v == mkldnn_abdec) return "abdec"; + if (v == mkldnn_acb) return "acb"; + if (v == mkldnn_acbde) return "acbde"; + if (v == mkldnn_acdb) return "acdb"; + if (v == mkldnn_acdeb) return "acdeb"; + if (v == mkldnn_ba) return "ba"; + if (v == mkldnn_bac) return "bac"; + if (v == mkldnn_bacd) return "bacd"; + if (v == mkldnn_bcda) return "bcda"; + if (v == mkldnn_cba) return "cba"; + if (v == mkldnn_cdba) return "cdba"; + if (v == mkldnn_cdeba) return "cdeba"; + if (v == mkldnn_decab) return "decab"; + if (v == mkldnn_Abc16a) return "Abc16a"; + if (v == mkldnn_ABc16a16b) return "ABc16a16b"; + if (v == mkldnn_aBc16b) return "aBc16b"; + if (v == mkldnn_ABc16b16a) return "ABc16b16a"; + if (v == mkldnn_Abc4a) return "Abc4a"; + if (v == mkldnn_aBc4b) return "aBc4b"; + if (v == mkldnn_ABc4b16a4b) return "ABc4b16a4b"; + if (v == mkldnn_ABc4b4a) return "ABc4b4a"; + if (v == mkldnn_ABc8a16b2a) return "ABc8a16b2a"; + if (v == mkldnn_ABc8a8b) return "ABc8a8b"; + if (v == mkldnn_aBc8b) return "aBc8b"; + if (v == mkldnn_ABc8b16a2b) return "ABc8b16a2b"; + if (v == mkldnn_ABc8b8a) return "ABc8b8a"; + if (v == mkldnn_Abcd16a) return "Abcd16a"; + if (v == mkldnn_ABcd16a16b) return "ABcd16a16b"; + if (v == mkldnn_aBcd16b) return "aBcd16b"; + if (v == mkldnn_ABcd16b16a) return "ABcd16b16a"; + if (v == mkldnn_aBCd16b16c) return "aBCd16b16c"; + if (v == mkldnn_aBCd16c16b) return "aBCd16c16b"; + if (v == mkldnn_Abcd4a) return "Abcd4a"; + if (v == mkldnn_aBcd4b) return "aBcd4b"; + if (v == mkldnn_ABcd4b16a4b) return "ABcd4b16a4b"; + if (v == mkldnn_ABcd4b4a) return "ABcd4b4a"; + if (v == mkldnn_aBCd4c16b4c) return "aBCd4c16b4c"; + if (v == mkldnn_aBCd4c4b) return "aBCd4c4b"; + if (v == mkldnn_ABcd8a16b2a) return "ABcd8a16b2a"; + if (v == mkldnn_ABcd8a8b) return "ABcd8a8b"; + if (v == mkldnn_aBcd8b) return "aBcd8b"; + if (v == mkldnn_ABcd8b16a2b) return "ABcd8b16a2b"; + if (v == mkldnn_aBCd8b16c2b) return "aBCd8b16c2b"; + if (v == mkldnn_ABcd8b8a) return "ABcd8b8a"; + if (v == mkldnn_aBCd8b8c) return "aBCd8b8c"; + if (v == mkldnn_aBCd8c16b2c) return "aBCd8c16b2c"; + if (v == mkldnn_aBCd8c8b) return "aBCd8c8b"; + if (v == mkldnn_Abcde16a) return "Abcde16a"; + if (v == mkldnn_ABcde16a16b) return "ABcde16a16b"; + if (v == mkldnn_aBcde16b) return "aBcde16b"; + if (v == mkldnn_ABcde16b16a) return "ABcde16b16a"; + if (v == mkldnn_aBCde16b16c) return "aBCde16b16c"; + if (v == mkldnn_aBCde16c16b) return "aBCde16c16b"; + if (v == mkldnn_aBCde2c8b4c) return "aBCde2c8b4c"; + if (v == mkldnn_Abcde4a) return "Abcde4a"; + if (v == mkldnn_aBcde4b) return "aBcde4b"; + if (v == mkldnn_ABcde4b4a) return "ABcde4b4a"; + if (v == mkldnn_aBCde4b4c) return "aBCde4b4c"; + if (v == mkldnn_aBCde4c16b4c) return "aBCde4c16b4c"; + if (v == mkldnn_aBCde4c4b) return "aBCde4c4b"; + if (v == mkldnn_Abcde8a) return "Abcde8a"; + if (v == mkldnn_ABcde8a8b) return "ABcde8a8b"; + if (v == mkldnn_ABcde8b16a2b) return "ABcde8b16a2b"; + if (v == mkldnn_aBCde8b16c2b) return "aBCde8b16c2b"; + if (v == mkldnn_ABcde8b8a) return "ABcde8b8a"; + if (v == mkldnn_aBCde8b8c) return "aBCde8b8c"; + if (v == mkldnn_aBCde8c16b2c) return "aBCde8c16b2c"; + if (v == mkldnn_aBCde8c8b) return "aBCde8c8b"; + if (v == mkldnn_aBcdef16b) return "aBcdef16b"; + if (v == mkldnn_aBCdef16b16c) return "aBCdef16b16c"; + if (v == mkldnn_aBCdef16c16b) return "aBCdef16c16b"; + if (v == mkldnn_aBcdef4b) return "aBcdef4b"; + if (v == mkldnn_aBCdef4c4b) return "aBCdef4c4b"; + if (v == mkldnn_aBCdef8b8c) return "aBCdef8b8c"; + if (v == mkldnn_aBCdef8c16b2c) return "aBCdef8c16b2c"; + if (v == mkldnn_aBCdef8c8b) return "aBCdef8c8b"; + if (v == mkldnn_aBdc16b) return "aBdc16b"; + if (v == mkldnn_aBdc4b) return "aBdc4b"; + if (v == mkldnn_aBdc8b) return "aBdc8b"; + if (v == mkldnn_aBdec16b) return "aBdec16b"; + if (v == mkldnn_aBdec4b) return "aBdec4b"; + if (v == mkldnn_aBdec8b) return "aBdec8b"; + if (v == mkldnn_aBdefc16b) return "aBdefc16b"; + if (v == mkldnn_aBdefc4b) return "aBdefc4b"; + if (v == mkldnn_aBdefc8b) return "aBdefc8b"; + if (v == mkldnn_Acb16a) return "Acb16a"; + if (v == mkldnn_Acb4a) return "Acb4a"; + if (v == mkldnn_Acb8a) return "Acb8a"; + if (v == mkldnn_aCBd16b16c) return "aCBd16b16c"; + if (v == mkldnn_aCBde16b16c) return "aCBde16b16c"; + if (v == mkldnn_Acdb16a) return "Acdb16a"; + if (v == mkldnn_Acdb4a) return "Acdb4a"; + if (v == mkldnn_Acdb8a) return "Acdb8a"; + if (v == mkldnn_Acdeb16a) return "Acdeb16a"; + if (v == mkldnn_Acdeb4a) return "Acdeb4a"; + if (v == mkldnn_Acdeb8a) return "Acdeb8a"; + if (v == mkldnn_BAc16a16b) return "BAc16a16b"; + if (v == mkldnn_BAcd16a16b) return "BAcd16a16b"; + if (v == mkldnn_format_tag_last) return "format_tag_last"; + if (v == mkldnn_x) return "x"; + if (v == mkldnn_nc) return "nc"; + if (v == mkldnn_cn) return "cn"; + if (v == mkldnn_ncw) return "ncw"; + if (v == mkldnn_nwc) return "nwc"; + if (v == mkldnn_nchw) return "nchw"; + if (v == mkldnn_nhwc) return "nhwc"; + if (v == mkldnn_chwn) return "chwn"; + if (v == mkldnn_ncdhw) return "ncdhw"; + if (v == mkldnn_ndhwc) return "ndhwc"; + if (v == mkldnn_oi) return "oi"; + if (v == mkldnn_io) return "io"; + if (v == mkldnn_oiw) return "oiw"; + if (v == mkldnn_wio) return "wio"; + if (v == mkldnn_oihw) return "oihw"; + if (v == mkldnn_hwio) return "hwio"; + if (v == mkldnn_ihwo) return "ihwo"; + if (v == mkldnn_iohw) return "iohw"; + if (v == mkldnn_oidhw) return "oidhw"; + if (v == mkldnn_dhwio) return "dhwio"; + if (v == mkldnn_goiw) return "goiw"; + if (v == mkldnn_goihw) return "goihw"; + if (v == mkldnn_hwigo) return "hwigo"; + if (v == mkldnn_giohw) return "giohw"; + if (v == mkldnn_goidhw) return "goidhw"; + if (v == mkldnn_tnc) return "tnc"; + if (v == mkldnn_ntc) return "ntc"; + if (v == mkldnn_ldsnc) return "ldsnc"; + if (v == mkldnn_ldigo) return "ldigo"; + if (v == mkldnn_ldgoi) return "ldgoi"; + if (v == mkldnn_ldgo) return "ldgo"; + if (v == mkldnn_nCdhw16c) return "nCdhw16c"; + if (v == mkldnn_nCdhw4c) return "nCdhw4c"; + if (v == mkldnn_nCdhw8c) return "nCdhw8c"; + if (v == mkldnn_nChw16c) return "nChw16c"; + if (v == mkldnn_nChw4c) return "nChw4c"; + if (v == mkldnn_nChw8c) return "nChw8c"; + if (v == mkldnn_nCw16c) return "nCw16c"; + if (v == mkldnn_nCw4c) return "nCw4c"; + if (v == mkldnn_nCw8c) return "nCw8c"; + if (v == mkldnn_IOw16o16i) return "IOw16o16i"; + if (v == mkldnn_OIw16i16o) return "OIw16i16o"; + if (v == mkldnn_OIw16o16i) return "OIw16o16i"; + if (v == mkldnn_Oiw16o) return "Oiw16o"; + if (v == mkldnn_OIw4i16o4i) return "OIw4i16o4i"; + if (v == mkldnn_OIw4i4o) return "OIw4i4o"; + if (v == mkldnn_Oiw4o) return "Oiw4o"; + if (v == mkldnn_OIw8i16o2i) return "OIw8i16o2i"; + if (v == mkldnn_OIw8i8o) return "OIw8i8o"; + if (v == mkldnn_OIw8o16i2o) return "OIw8o16i2o"; + if (v == mkldnn_OIw8o8i) return "OIw8o8i"; + if (v == mkldnn_Owi16o) return "Owi16o"; + if (v == mkldnn_Owi4o) return "Owi4o"; + if (v == mkldnn_Owi8o) return "Owi8o"; + if (v == mkldnn_IOhw16o16i) return "IOhw16o16i"; + if (v == mkldnn_Ohwi16o) return "Ohwi16o"; + if (v == mkldnn_Ohwi4o) return "Ohwi4o"; + if (v == mkldnn_Ohwi8o) return "Ohwi8o"; + if (v == mkldnn_OIhw16i16o) return "OIhw16i16o"; + if (v == mkldnn_OIhw16o16i) return "OIhw16o16i"; + if (v == mkldnn_Oihw16o) return "Oihw16o"; + if (v == mkldnn_OIhw4i16o4i) return "OIhw4i16o4i"; + if (v == mkldnn_OIhw4i4o) return "OIhw4i4o"; + if (v == mkldnn_Oihw4o) return "Oihw4o"; + if (v == mkldnn_OIhw8i16o2i) return "OIhw8i16o2i"; + if (v == mkldnn_OIhw8i8o) return "OIhw8i8o"; + if (v == mkldnn_OIhw8o16i2o) return "OIhw8o16i2o"; + if (v == mkldnn_OIhw8o8i) return "OIhw8o8i"; + if (v == mkldnn_Odhwi16o) return "Odhwi16o"; + if (v == mkldnn_Odhwi4o) return "Odhwi4o"; + if (v == mkldnn_Odhwi8o) return "Odhwi8o"; + if (v == mkldnn_OIdhw16i16o) return "OIdhw16i16o"; + if (v == mkldnn_OIdhw16o16i) return "OIdhw16o16i"; + if (v == mkldnn_Oidhw16o) return "Oidhw16o"; + if (v == mkldnn_OIdhw4i4o) return "OIdhw4i4o"; + if (v == mkldnn_Oidhw4o) return "Oidhw4o"; + if (v == mkldnn_OIdhw8i16o2i) return "OIdhw8i16o2i"; + if (v == mkldnn_OIdhw8i8o) return "OIdhw8i8o"; + if (v == mkldnn_OIdhw8o8i) return "OIdhw8o8i"; + if (v == mkldnn_Goiw16g) return "Goiw16g"; + if (v == mkldnn_gIOw16o16i) return "gIOw16o16i"; + if (v == mkldnn_gOIw16i16o) return "gOIw16i16o"; + if (v == mkldnn_gOIw16o16i) return "gOIw16o16i"; + if (v == mkldnn_gOiw16o) return "gOiw16o"; + if (v == mkldnn_gOIw4i16o4i) return "gOIw4i16o4i"; + if (v == mkldnn_gOIw4i4o) return "gOIw4i4o"; + if (v == mkldnn_gOiw4o) return "gOiw4o"; + if (v == mkldnn_gOIw8i16o2i) return "gOIw8i16o2i"; + if (v == mkldnn_gOIw8i8o) return "gOIw8i8o"; + if (v == mkldnn_gOIw8o16i2o) return "gOIw8o16i2o"; + if (v == mkldnn_gOIw8o8i) return "gOIw8o8i"; + if (v == mkldnn_gOwi16o) return "gOwi16o"; + if (v == mkldnn_gOwi4o) return "gOwi4o"; + if (v == mkldnn_gOwi8o) return "gOwi8o"; + if (v == mkldnn_gIOhw16o16i) return "gIOhw16o16i"; + if (v == mkldnn_gOhwi16o) return "gOhwi16o"; + if (v == mkldnn_gOhwi4o) return "gOhwi4o"; + if (v == mkldnn_gOhwi8o) return "gOhwi8o"; + if (v == mkldnn_Goihw16g) return "Goihw16g"; + if (v == mkldnn_gOIhw16i16o) return "gOIhw16i16o"; + if (v == mkldnn_gOIhw16o16i) return "gOIhw16o16i"; + if (v == mkldnn_gOihw16o) return "gOihw16o"; + if (v == mkldnn_gOIhw2i8o4i) return "gOIhw2i8o4i"; + if (v == mkldnn_gOIhw4i16o4i) return "gOIhw4i16o4i"; + if (v == mkldnn_gOIhw4i4o) return "gOIhw4i4o"; + if (v == mkldnn_gOIhw4o4i) return "gOIhw4o4i"; + if (v == mkldnn_gOihw4o) return "gOihw4o"; + if (v == mkldnn_Goihw8g) return "Goihw8g"; + if (v == mkldnn_gOIhw8i16o2i) return "gOIhw8i16o2i"; + if (v == mkldnn_gOIhw8i8o) return "gOIhw8i8o"; + if (v == mkldnn_gOIhw8o16i2o) return "gOIhw8o16i2o"; + if (v == mkldnn_gOIhw8o8i) return "gOIhw8o8i"; + if (v == mkldnn_gOdhwi16o) return "gOdhwi16o"; + if (v == mkldnn_gOdhwi4o) return "gOdhwi4o"; + if (v == mkldnn_gOdhwi8o) return "gOdhwi8o"; + if (v == mkldnn_gOIdhw16i16o) return "gOIdhw16i16o"; + if (v == mkldnn_gOIdhw16o16i) return "gOIdhw16o16i"; + if (v == mkldnn_gOidhw16o) return "gOidhw16o"; + if (v == mkldnn_gOIdhw4i4o) return "gOIdhw4i4o"; + if (v == mkldnn_gOidhw4o) return "gOidhw4o"; + if (v == mkldnn_gOIdhw8i16o2i) return "gOIdhw8i16o2i"; + if (v == mkldnn_gOIdhw8i8o) return "gOIdhw8i8o"; + if (v == mkldnn_gOIdhw8o8i) return "gOIdhw8o8i"; + assert(!"unknown fmt_tag"); + return "unknown fmt_tag"; +} + +const char *mkldnn_prop_kind2str(mkldnn_prop_kind_t v) { + if (v == mkldnn_prop_kind_undef) return "undef"; + if (v == mkldnn_forward_training) return "forward_training"; + if (v == mkldnn_forward_inference) return "forward_inference"; + if (v == mkldnn_forward_scoring) return "forward_scoring"; + if (v == mkldnn_forward) return "forward"; + if (v == mkldnn_backward) return "backward"; + if (v == mkldnn_backward_data) return "backward_data"; + if (v == mkldnn_backward_weights) return "backward_weights"; + if (v == mkldnn_backward_bias) return "backward_bias"; + assert(!"unknown prop_kind"); + return "unknown prop_kind"; +} + +const char *mkldnn_prim_kind2str(mkldnn_primitive_kind_t v) { + if (v == mkldnn_undefined_primitive) return "undef"; + if (v == mkldnn_reorder) return "reorder"; + if (v == mkldnn_shuffle) return "shuffle"; + if (v == mkldnn_concat) return "concat"; + if (v == mkldnn_sum) return "sum"; + if (v == mkldnn_convolution) return "convolution"; + if (v == mkldnn_deconvolution) return "deconvolution"; + if (v == mkldnn_eltwise) return "eltwise"; + if (v == mkldnn_softmax) return "softmax"; + if (v == mkldnn_pooling) return "pooling"; + if (v == mkldnn_lrn) return "lrn"; + if (v == mkldnn_batch_normalization) return "batch_normalization"; + if (v == mkldnn_inner_product) return "inner_product"; + if (v == mkldnn_rnn) return "rnn"; + assert(!"unknown prim_kind"); + return "unknown prim_kind"; +} + +const char *mkldnn_alg_kind2str(mkldnn_alg_kind_t v) { + if (v == mkldnn_alg_kind_undef) return "undef"; + if (v == mkldnn_convolution_direct) return "convolution_direct"; + if (v == mkldnn_convolution_winograd) return "convolution_winograd"; + if (v == mkldnn_convolution_auto) return "convolution_auto"; + if (v == mkldnn_deconvolution_direct) return "deconvolution_direct"; + if (v == mkldnn_deconvolution_winograd) return "deconvolution_winograd"; + if (v == mkldnn_eltwise_relu) return "eltwise_relu"; + if (v == mkldnn_eltwise_tanh) return "eltwise_tanh"; + if (v == mkldnn_eltwise_elu) return "eltwise_elu"; + if (v == mkldnn_eltwise_square) return "eltwise_square"; + if (v == mkldnn_eltwise_abs) return "eltwise_abs"; + if (v == mkldnn_eltwise_sqrt) return "eltwise_sqrt"; + if (v == mkldnn_eltwise_linear) return "eltwise_linear"; + if (v == mkldnn_eltwise_bounded_relu) return "eltwise_bounded_relu"; + if (v == mkldnn_eltwise_soft_relu) return "eltwise_soft_relu"; + if (v == mkldnn_eltwise_logistic) return "eltwise_logistic"; + if (v == mkldnn_pooling_max) return "pooling_max"; + if (v == mkldnn_pooling_avg_include_padding) return "pooling_avg_include_padding"; + if (v == mkldnn_pooling_avg_exclude_padding) return "pooling_avg_exclude_padding"; + if (v == mkldnn_pooling_avg) return "pooling_avg"; + if (v == mkldnn_lrn_across_channels) return "lrn_across_channels"; + if (v == mkldnn_lrn_within_channel) return "lrn_within_channel"; + if (v == mkldnn_vanilla_rnn) return "vanilla_rnn"; + if (v == mkldnn_vanilla_lstm) return "vanilla_lstm"; + if (v == mkldnn_vanilla_gru) return "vanilla_gru"; + if (v == mkldnn_gru_linear_before_reset) return "gru_linear_before_reset"; + assert(!"unknown alg_kind"); + return "unknown alg_kind"; +} + +const char *mkldnn_rnn_direction2str(mkldnn_rnn_direction_t v) { + if (v == mkldnn_unidirectional_left2right) return "unidirectional_left2right"; + if (v == mkldnn_unidirectional_right2left) return "unidirectional_right2left"; + if (v == mkldnn_bidirectional_concat) return "bidirectional_concat"; + if (v == mkldnn_bidirectional_sum) return "bidirectional_sum"; + if (v == mkldnn_unidirectional) return "unidirectional"; + assert(!"unknown rnn_direction"); + return "unknown rnn_direction"; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread.hpp b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread.hpp new file mode 100644 index 000000000000..7e5789e2c355 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread.hpp @@ -0,0 +1,115 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_THREAD_HPP +#define MKLDNN_THREAD_HPP + +#include "utils.hpp" +#include "z_magic.hpp" + +#define MKLDNN_THR_SEQ 0 +#define MKLDNN_THR_OMP 1 +#define MKLDNN_THR_TBB 2 + +/* Ideally this condition below should never happen (if the library is built + * using regular cmake). For the 3rd-party projects that build the library + * from the sources on their own try to guess the right threading... */ +#if !defined(MKLDNN_THR) +# define MKLDNN_THR MKLDNN_THR_TBB +#endif + +#if MKLDNN_THR == MKLDNN_THR_SEQ +#define MKLDNN_THR_SYNC 1 +inline int mkldnn_get_max_threads() { return 1; } +inline int mkldnn_get_num_threads() { return 1; } +inline int mkldnn_get_thread_num() { return 0; } +inline int mkldnn_in_parallel() { return 0; } +inline void mkldnn_thr_barrier() {} + +#define PRAGMA_OMP(...) + +#elif MKLDNN_THR == MKLDNN_THR_OMP +#include +#define MKLDNN_THR_SYNC 1 + +inline int mkldnn_get_max_threads() { return omp_get_max_threads(); } +inline int mkldnn_get_num_threads() { return omp_get_num_threads(); } +inline int mkldnn_get_thread_num() { return omp_get_thread_num(); } +inline int mkldnn_in_parallel() { return omp_in_parallel(); } +inline void mkldnn_thr_barrier() { +# pragma omp barrier +} + +#define PRAGMA_OMP(...) PRAGMA_MACRO(CHAIN2(omp, __VA_ARGS__)) + +#elif MKLDNN_THR == MKLDNN_THR_TBB +#include "tbb/task_arena.h" +#include "tbb/parallel_for.h" +#define MKLDNN_THR_SYNC 0 + +inline int mkldnn_get_max_threads() +{ return tbb::this_task_arena::max_concurrency(); } +inline int mkldnn_get_num_threads() { return mkldnn_get_max_threads(); } +inline int mkldnn_get_thread_num() +{ return tbb::this_task_arena::current_thread_index(); } +inline int mkldnn_in_parallel() { return 0; } +inline void mkldnn_thr_barrier() { assert(!"no barrier in TBB"); } + +#define PRAGMA_OMP(...) + +#endif + +/* MSVC still supports omp 2.0 only */ +#if defined(_MSC_VER) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define collapse(x) +# define PRAGMA_OMP_SIMD(...) +#else +# define PRAGMA_OMP_SIMD(...) PRAGMA_MACRO(CHAIN2(omp, simd __VA_ARGS__)) +#endif // defined(_MSC_VER) && !defined(__INTEL_COMPILER) + +namespace mkldnn { +namespace impl { + +inline bool mkldnn_thr_syncable() { return MKLDNN_THR_SYNC == 1; } + +template +inline void balance211(T n, U team, U tid, T &n_start, T &n_end) { + T n_min = 1; + T &n_my = n_end; + if (team <= 1 || n == 0) { + n_start = 0; + n_my = n; + } else if (n_min == 1) { + // team = T1 + T2 + // n = T1*n1 + T2*n2 (n1 - n2 = 1) + T n1 = utils::div_up(n, (T)team); + T n2 = n1 - 1; + T T1 = n - n2 * (T)team; + n_my = (T)tid < T1 ? n1 : n2; + n_start = (T)tid <= T1 ? tid * n1 : T1 * n1 + ((T)tid - T1) * n2; + } + + n_end += n_start; +} + +} // namespace impl +} // namespace mkldnn + +#include "mkldnn_thread_parallel_nd.hpp" + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread_parallel_nd.hpp b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread_parallel_nd.hpp new file mode 100644 index 000000000000..50f9b29622eb --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_thread_parallel_nd.hpp @@ -0,0 +1,277 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_THREAD_PARALLEL_ND_HPP +#define MKLDNN_THREAD_PARALLEL_ND_HPP + +/* This header must be included by mkldnn_thread.hpp only */ + +/* Functions: + * - parallel(nthr, f) - executes f in parallel using at most + * nthr threads. If nthr equals 0 + * mkldnn_get_max_threads() threads is + * used + * - for_nd(ithr, nthr, dims..., f) - multidimensional for loop for already + * created threads + * - parallel_nd(dims..., f) - creates a parallel section and then + * calls for_nd + * - parallel_nd_in_omp(dims..., f) - queries current nthr and ithr and then + * calls for_nd (mostly for convenience) + */ + +namespace mkldnn { +namespace impl { + +/* general parallelization */ +template +void parallel(int nthr, F f) { + if (nthr == 0) nthr = mkldnn_get_max_threads(); +#if MKLDNN_THR == MKLDNN_THR_SEQ + assert(nthr == 1); + f(0, 1); +#elif MKLDNN_THR == MKLDNN_THR_OMP + if (nthr == 1) { f(0, 1); return; } +# pragma omp parallel num_threads(nthr) + f(mkldnn_get_thread_num(), mkldnn_get_num_threads()); +#elif MKLDNN_THR == MKLDNN_THR_TBB + if (nthr == 1) { f(0, 1); return; } + tbb::parallel_for(0, nthr, [&](int ithr) { f(ithr, nthr); }, tbb::static_partitioner()); +#endif +} + +/* for_nd section */ + +template +void for_nd(const int ithr, const int nthr, const T0 &D0, F f) { + T0 start{0}, end{0}; + balance211(D0, nthr, ithr, start, end); + for (T0 d0 = start; d0 < end; ++d0) f(d0); +} + +template +void for_nd(const int ithr, const int nthr, const T0 &D0, const T1 &D1, F f) { + const size_t work_amount = (size_t)D0 * D1; + if (work_amount == 0) return; + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + T0 d0{0}; T1 d1{0}; + utils::nd_iterator_init(start, d0, D0, d1, D1); + for (size_t iwork = start; iwork < end; ++iwork) { + f(d0, d1); + utils::nd_iterator_step(d0, D0, d1, D1); + } +} + +template +void for_nd(const int ithr, const int nthr, const T0 &D0, const T1 &D1, + const T2 &D2, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2; + if (work_amount == 0) return; + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + T0 d0{0}; T1 d1{0}; T2 d2{0}; + utils::nd_iterator_init(start, d0, D0, d1, D1, d2, D2); + for (size_t iwork = start; iwork < end; ++iwork) { + f(d0, d1, d2); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2); + } +} + +template +void for_nd(const int ithr, const int nthr, const T0 &D0, const T1 &D1, + const T2 &D2, const T3 &D3, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2 * D3; + if (work_amount == 0) return; + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + T0 d0{0}; T1 d1{0}; T2 d2{0}; T3 d3{0}; + utils::nd_iterator_init(start, d0, D0, d1, D1, d2, D2, d3, D3); + for (size_t iwork = start; iwork < end; ++iwork) { + f(d0, d1, d2, d3); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2, d3, D3); + } +} + +template +void for_nd(const int ithr, const int nthr, const T0 &D0, const T1 &D1, + const T2 &D2, const T3 &D3, const T4 &D4, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2 * D3 * D4; + if (work_amount == 0) return; + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + T0 d0{0}; T1 d1{0}; T2 d2{0}; T3 d3{0}; T4 d4{0}; + utils::nd_iterator_init(start, d0, D0, d1, D1, d2, D2, d3, D3, d4, D4); + for (size_t iwork = start; iwork < end; ++iwork) { + f(d0, d1, d2, d3, d4); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2, d3, D3, d4, D4); + } +} + +template +void for_nd(const int ithr, const int nthr, const T0 &D0, const T1 &D1, + const T2 &D2, const T3 &D3, const T4 &D4, const T5 &D5, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2 * D3 * D4 * D5; + if (work_amount == 0) return; + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + T0 d0{0}; T1 d1{0}; T2 d2{0}; T3 d3{0}; T4 d4{0}; T5 d5{0}; + utils::nd_iterator_init(start, d0, D0, d1, D1, d2, D2, d3, D3, d4, D4, + d5, D5); + for (size_t iwork = start; iwork < end; ++iwork) { + f(d0, d1, d2, d3, d4, d5); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2, d3, D3, d4, D4, d5, D5); + } +} + +// Skip a lambda function in the parameter pack. +template +constexpr size_t get_work_amount(const T &v) { return 1; } +template +constexpr size_t get_work_amount(const T &v, Args &&...args) +{ return (size_t)v * get_work_amount(utils::forward(args)...); } + +/* parallel_nd and parallel_nd_in_omp section */ + +#if MKLDNN_THR != MKLDNN_THR_TBB +template +void parallel_nd(Args &&...args) { +#if MKLDNN_THR == MKLDNN_THR_SEQ + for_nd(0, 1, utils::forward(args)...); +#elif MKLDNN_THR == MKLDNN_THR_OMP + const bool do_parallel = get_work_amount(utils::forward(args)...) > 1; +# pragma omp parallel if (do_parallel) + { + const int nthr = !do_parallel ? 1 : mkldnn_get_num_threads(); + const int ithr = !do_parallel ? 0 : mkldnn_get_thread_num(); + for_nd(ithr, nthr, utils::forward(args)...); + } +#endif +} +#else // MKLDNN_THR != MKLDNN_THR_TBB + +// gcc 4.8 has a bug with passing parameter pack to lambdas. +// So have to explicitly instantiate all the cases. + +template +void parallel_nd(const T0 &D0, F f) { + const size_t work_amount = (size_t)D0; + if (work_amount == 0) return; + tbb::parallel_for(tbb::blocked_range(0, work_amount), [&](const tbb::blocked_range& r) { + for (size_t iwork = r.begin(); iwork != r.end(); ++iwork) { + f(T0(iwork)); + } + }, tbb::static_partitioner()); +} + +template +void parallel_nd(const T0 &D0, const T1 &D1, F f) { + const size_t work_amount = (size_t)D0 * D1; + if (work_amount == 0) return; + tbb::parallel_for(tbb::blocked_range(0, work_amount), [&](const tbb::blocked_range& r) { + T0 d0{0}; T1 d1{0}; + utils::nd_iterator_init(r.begin(), d0, D0, d1, D1); + for (size_t iwork = r.begin(); iwork != r.end(); ++iwork) { + f(d0, d1); + utils::nd_iterator_step(d0, D0, d1, D1); + } + }, tbb::static_partitioner()); +} + +template +void parallel_nd(const T0 &D0, const T1 &D1, const T2 &D2, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2; + if (work_amount == 0) return; + tbb::parallel_for(tbb::blocked_range(0, work_amount), [&](const tbb::blocked_range& r) { + T0 d0{0}; T1 d1{0}; T2 d2{0}; + utils::nd_iterator_init(r.begin(), d0, D0, d1, D1, d2, D2); + for (size_t iwork = r.begin(); iwork != r.end(); ++iwork) { + f(d0, d1, d2); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2); + } + }, tbb::static_partitioner()); +} + +template +void parallel_nd(const T0 &D0, const T1 &D1, const T2 &D2, const T3 &D3, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2 * D3; + if (work_amount == 0) return; + tbb::parallel_for(tbb::blocked_range(0, work_amount), [&](const tbb::blocked_range& r) { + T0 d0{0}; T1 d1{0}; T2 d2{0}; T3 d3{0}; + utils::nd_iterator_init(r.begin(), d0, D0, d1, D1, d2, D2, d3, D3); + for (size_t iwork = r.begin(); iwork != r.end(); ++iwork) { + f(d0, d1, d2, d3); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2, d3, D3); + } + }, tbb::static_partitioner()); +} + +template +void parallel_nd(const T0 &D0, const T1 &D1, const T2 &D2, const T3 &D3, + const T4 &D4, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2 * D3 * D4; + if (work_amount == 0) return; + tbb::parallel_for(tbb::blocked_range(0, work_amount), [&](const tbb::blocked_range& r) { + T0 d0{0}; T1 d1{0}; T2 d2{0}; T3 d3{0}; T4 d4{0}; + utils::nd_iterator_init(r.begin(), d0, D0, d1, D1, d2, D2, d3, D3, d4, D4); + for (size_t iwork = r.begin(); iwork != r.end(); ++iwork) { + f(d0, d1, d2, d3, d4); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2, d3, D3, d4, D4); + } + }, tbb::static_partitioner()); +} + +template +void parallel_nd(const T0 &D0, const T1 &D1, const T2 &D2, const T3 &D3, + const T4 &D4, const T5 &D5, F f) { + const size_t work_amount = (size_t)D0 * D1 * D2 * D3 * D4 * D5; + if (work_amount == 0) return; + tbb::parallel_for(tbb::blocked_range(0, work_amount), [&](const tbb::blocked_range& r) { + T0 d0{0}; T1 d1{0}; T2 d2{0}; T3 d3{0}; T4 d4{0}; T5 d5{0}; + utils::nd_iterator_init(r.begin(), d0, D0, d1, D1, d2, D2, d3, D3, d4, D4, + d5, D5); + for (size_t iwork = r.begin(); iwork != r.end(); ++iwork) { + f(d0, d1, d2, d3, d4, d5); + utils::nd_iterator_step(d0, D0, d1, D1, d2, D2, d3, D3, d4, D4, d5, D5); + } + }, tbb::static_partitioner()); +} +#endif + +template +void parallel_nd_in_omp(Args &&...args) { +#if MKLDNN_THR == MKLDNN_THR_SEQ + for_nd(0, 1, utils::forward(args)...); +#elif MKLDNN_THR == MKLDNN_THR_OMP + for_nd(mkldnn_get_thread_num(), mkldnn_get_num_threads(), + utils::forward(args)...); +#elif MKLDNN_THR == MKLDNN_THR_TBB + assert(!"unsupported parallel_nd_in_omp()"); +#endif +} + +} // namespace impl +} // namespace mkldnn + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/mkldnn_traits.hpp b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_traits.hpp new file mode 100644 index 000000000000..aa671a0b6e1d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/mkldnn_traits.hpp @@ -0,0 +1,77 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef MKLDNN_TRAITS_HPP +#define MKLDNN_TRAITS_HPP + +#include +#include + +#include "mkldnn.h" +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "utils.hpp" +#include "z_magic.hpp" + +namespace mkldnn { +namespace impl { + +template struct prec_traits {}; /* ::type -> float */ +template struct data_traits {}; /* ::data_type -> f32 */ +template struct typesize_traits {}; /* ::data_type_size -> f32 */ +template struct pkind_traits {}; /* ::desc_type, ::query_d */ + +template <> struct prec_traits { typedef float type; }; +template <> struct prec_traits { typedef int32_t type; }; +template <> struct prec_traits { typedef int8_t type; }; +template <> struct prec_traits { typedef uint8_t type; }; + +template <> struct data_traits +{ static constexpr data_type_t data_type = data_type::f32; }; +template <> struct data_traits +{ static constexpr data_type_t data_type = data_type::s32; }; +template <> struct data_traits +{ static constexpr data_type_t data_type = data_type::s8; }; +template <> struct data_traits +{ static constexpr data_type_t data_type = data_type::u8; }; + +template <> struct typesize_traits<4> { typedef float type; }; +template <> struct typesize_traits<2> { typedef int16_t type; }; +template <> struct typesize_traits<1> { typedef uint8_t type; }; + +#define PKIND_TRAITS_INST(op) \ +template <> struct pkind_traits { \ + typedef CONCAT2(op, _desc_t) desc_type; \ + static constexpr query_t query_d = query::CONCAT2(op, _d); \ +} +PKIND_TRAITS_INST(convolution); +PKIND_TRAITS_INST(deconvolution); +PKIND_TRAITS_INST(shuffle); +PKIND_TRAITS_INST(eltwise); +PKIND_TRAITS_INST(softmax); +PKIND_TRAITS_INST(pooling); +PKIND_TRAITS_INST(lrn); +PKIND_TRAITS_INST(batch_normalization); +PKIND_TRAITS_INST(inner_product); +PKIND_TRAITS_INST(rnn); +#undef PKIND_TRAITS_INST + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/nstl.hpp b/thirdparty/oidn/mkl-dnn/src/common/nstl.hpp new file mode 100644 index 000000000000..f89ea999e264 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/nstl.hpp @@ -0,0 +1,193 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef NSTL_HPP +#define NSTL_HPP + +#include +#include +#include + +#include +#include + +#include "z_magic.hpp" + +namespace mkldnn { +namespace impl { + +void *malloc(size_t size, int alignment); +void free(void *p); + +struct c_compatible { + enum { default_alignment = 64 }; + static void *operator new(size_t sz) { + return malloc(sz, default_alignment); + } + static void *operator new(size_t sz, void *p) { UNUSED(sz); return p; } + static void *operator new[](size_t sz) { + return malloc(sz, default_alignment); + } + static void operator delete(void *p) { free(p); } + static void operator delete[](void *p) { free(p); } +}; + +namespace nstl { + +template +inline const T abs(const T& a) { + return a >= 0 ? a : -a; +} + +template +inline const T& max(const T& a, const T& b) { + return a > b ? a : b; +} + +template +inline const T& min(const T& a, const T& b) { + return a < b ? a : b; +} + +template void swap(T& t1, T& t2) { + T tmp(t1); + t1 = t2; + t2 = tmp; +} + +// Rationale: MKL-DNN needs numeric limits implementation that does not +// generate dependencies on C++ run-time libraries. + +template struct numeric_limits; + +template<> struct numeric_limits { + static constexpr float lowest() { return -FLT_MAX; } + static constexpr float max() { return FLT_MAX; } +}; + +template<> struct numeric_limits { + static constexpr int lowest() { return INT32_MIN; } + static constexpr int max() { return INT32_MAX; } +}; + +template<> struct numeric_limits { + static constexpr int16_t lowest() { return INT16_MIN; } + static constexpr int16_t max() { return INT16_MAX; } +}; + +template<> struct numeric_limits { + static constexpr int8_t lowest() { return INT8_MIN; } + static constexpr int8_t max() { return INT8_MAX; } +}; + +template<> struct numeric_limits { + static constexpr uint8_t lowest() { return 0; } + static constexpr uint8_t max() { return UINT8_MAX; } +}; + +template struct is_integral +{ static constexpr bool value = false; }; +template<> struct is_integral { static constexpr bool value = true; }; +template<> struct is_integral { static constexpr bool value = true; }; +template<> struct is_integral { static constexpr bool value = true; }; +template<> struct is_integral { static constexpr bool value = true; }; + +template struct is_same +{ static constexpr bool value = false; }; +template struct is_same +{ static constexpr bool value = true; }; + +// Rationale: MKL-DNN needs container implementations that do not generate +// dependencies on C++ run-time libraries. +// +// Implementation philosophy: caller is responsible to check if the operation +// is valid. The only functions that have to return status are those that +// depend on memory allocation or similar operations. +// +// This means that e.g. an operator [] does not have to check for boundaries. +// The caller should have checked the boundaries. If it did not we crash and +// burn: this is a bug in MKL-DNN and throwing an exception would not have been +// recoverable. +// +// On the other hand, insert() or resize() or a similar operation needs to +// return a status because the outcome depends on factors external to the +// caller. The situation is probably also not recoverable also, but MKL-DNN +// needs to be nice and report "out of memory" to the users. + +enum nstl_status_t { + success = 0, + out_of_memory +}; + +template class vector: public c_compatible { +private: + std::vector _impl; +public: + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + typedef typename std::vector::size_type size_type; + vector() {} + vector(size_type n): _impl(n) {} + vector(size_type n, const T &value): _impl(n, value) {} + template + vector(input_iterator first, input_iterator last): _impl(first, last) {} + ~vector() {} + size_type size() const { return _impl.size(); } + T& operator[] (size_type i) { return _impl[i]; } + const T& operator[] (size_type i) const { return _impl[i]; } + iterator begin() { return _impl.begin(); } + const_iterator begin() const { return _impl.begin(); } + iterator end() { return _impl.end(); } + const_iterator end() const { return _impl.end(); } + template + nstl_status_t insert(iterator pos, input_iterator begin, input_iterator end) + { + _impl.insert(pos, begin, end); + return success; + } + void clear() { _impl.clear(); } + void push_back(const T& t) { _impl.push_back(t); } + void resize(size_type count) { _impl.resize(count); } + void reserve(size_type count) { _impl.reserve(count); } +}; + +template class map: public c_compatible { +private: + std::map _impl; +public: + typedef typename std::map::iterator iterator; + typedef typename std::map::const_iterator const_iterator; + typedef typename std::map::size_type size_type; + map() {} + ~map() {} + size_type size() const { return _impl.size(); } + T& operator[](const Key &k) { return _impl[k]; } + const T& operator[](const Key &k) const { return _impl[k]; } + iterator begin() { return _impl.begin(); } + const_iterator begin() const { return _impl.begin(); } + iterator end() { return _impl.end(); } + const_iterator end() const { return _impl.end(); } + template + void clear() { _impl.clear(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/pooling.cpp b/thirdparty/oidn/mkl-dnn/src/common/pooling.cpp new file mode 100644 index 000000000000..be96e654ff80 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/pooling.cpp @@ -0,0 +1,114 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t pooling_desc_init(pooling_desc_t *pool_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *dst_desc, + const dims_t strides, const dims_t kernel, const dims_t padding_l, + const dims_t padding_r, padding_kind_t padding_kind) { + bool args_ok = true + && !any_null(pool_desc, src_desc, dst_desc, strides, kernel, padding_l) + && one_of(alg_kind, pooling_max, + pooling_avg_include_padding, + pooling_avg_exclude_padding) + && one_of(padding_kind, padding_kind::padding_zero); + if (!args_ok) return invalid_arguments; + + if (padding_r == nullptr) padding_r = padding_l; + + auto pd = pooling_desc_t(); + pd.primitive_kind = primitive_kind::pooling; + pd.prop_kind = prop_kind; + pd.alg_kind = alg_kind; + pd.src_desc.ndims = src_desc->ndims; + + const bool is_fwd = one_of(prop_kind, forward_training, forward_inference); + + pd.diff_src_desc = pd.src_desc = zero_md(); + pd.diff_dst_desc = pd.dst_desc = zero_md(); + + (is_fwd ? pd.src_desc : pd.diff_src_desc) = *src_desc; + (is_fwd ? pd.dst_desc : pd.diff_dst_desc) = *dst_desc; + + int sp_dims = src_desc->ndims - 2; + utils::array_copy(pd.strides, strides, sp_dims); + utils::array_copy(pd.kernel, kernel, sp_dims); + utils::array_copy(pd.padding[0], padding_l, sp_dims); + utils::array_copy(pd.padding[1], padding_r, sp_dims); + + pd.padding_kind = padding_kind; + if (one_of(alg_kind, pooling_max, pooling_avg_include_padding, + pooling_avg_exclude_padding)) { + pd.accum_data_type = types::default_accum_data_type( + src_desc->data_type, dst_desc->data_type); + } else { + pd.accum_data_type = dst_desc->data_type; + } + + bool consistency = true + && utils::one_of(src_desc->ndims, 4, 5) + && utils::one_of(dst_desc->ndims, 4, 5) + && src_desc->dims[0] == dst_desc->dims[0] + && src_desc->dims[1] == dst_desc->dims[1]; + for (int i = 2; i < src_desc->ndims; ++i) + consistency = consistency && ( + (src_desc->dims[i] - kernel[i - 2] + padding_l[i - 2] + + padding_r[i - 2]) / strides[i - 2] + 1 + == dst_desc->dims[i]); + if (!consistency) return invalid_arguments; + + *pool_desc = pd; + return success; +} +} + +status_t mkldnn_pooling_forward_desc_init(pooling_desc_t *pool_desc, + prop_kind_t prop_kind, alg_kind_t alg_kind, + const memory_desc_t *src_desc, const memory_desc_t *dst_desc, + const dims_t strides, const dims_t kernel, const dims_t padding_l, + const dims_t padding_r, padding_kind_t padding_kind) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return pooling_desc_init(pool_desc, prop_kind, alg_kind, src_desc, + dst_desc, strides, kernel, padding_l, padding_r, padding_kind); +} + +status_t mkldnn_pooling_backward_desc_init(pooling_desc_t *pool_desc, + alg_kind_t alg_kind, const memory_desc_t *diff_src_desc, + const memory_desc_t *diff_dst_desc, const dims_t strides, + const dims_t kernel, const dims_t padding_l, const dims_t padding_r, + padding_kind_t padding_kind) { + return pooling_desc_init(pool_desc, prop_kind::backward_data, alg_kind, + diff_src_desc, diff_dst_desc, strides, kernel, padding_l, + padding_r, padding_kind); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/pooling_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/pooling_pd.hpp new file mode 100644 index 000000000000..4c9c009412cc --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/pooling_pd.hpp @@ -0,0 +1,238 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef POOLING_PD_HPP +#define POOLING_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" +#include "type_helpers.hpp" + +namespace mkldnn { +namespace impl { + +struct pooling_fwd_pd_t; + +struct pooling_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::pooling; + + pooling_pd_t(engine_t *engine, + const pooling_desc_t *adesc, + const primitive_attr_t *attr, + const pooling_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , ws_md_() + {} + + const pooling_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::pooling_d: + *(const pooling_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common pooling aux functions */ + + dim_t MB() const { return src_desc().dims[0]; } + dim_t C() const { return src_desc().dims[1]; } + + dim_t ID() const { return ndims() >= 5 ? src_desc().dims[ndims() - 3] : 1; } + dim_t IH() const { return ndims() >= 4 ? src_desc().dims[ndims() - 2] : 1; } + dim_t IW() const { return src_desc().dims[ndims() - 1]; } + + dim_t OD() const { return ndims() >= 5 ? dst_desc().dims[ndims() - 3] : 1; } + dim_t OH() const { return ndims() >= 4 ? dst_desc().dims[ndims() - 2] : 1; } + dim_t OW() const { return dst_desc().dims[ndims() - 1]; } + + dim_t KD() const { return ndims() >= 5 ? desc_.kernel[ndims() - 5] : 1; } + dim_t KH() const { return ndims() >= 4 ? desc_.kernel[ndims() - 4] : 1; } + dim_t KW() const { return desc_.kernel[ndims() - 3]; } + + dim_t KSD() const { return ndims() >= 5 ? desc_.strides[ndims() - 5] : 1; } + dim_t KSH() const { return ndims() >= 4 ? desc_.strides[ndims() - 4] : 1; } + dim_t KSW() const { return desc_.strides[ndims() - 3]; } + + dim_t padFront() const + { return ndims() >= 5 ? desc_.padding[0][ndims() - 5] : 0; } + dim_t padBack() const + { return ndims() >= 5 ? desc_.padding[1][ndims() - 5] : 0; } + dim_t padT() const + { return ndims() >= 4 ? desc_.padding[0][ndims() - 4] : 0; } + dim_t padB() const + { return ndims() >= 4 ? desc_.padding[1][ndims() - 4] : 0; } + dim_t padL() const { return desc_.padding[0][ndims() - 3]; } + dim_t padR() const { return desc_.padding[1][ndims() - 3]; } + + int ndims() const { return src_desc().ndims; } + bool is_3d() const { return ndims() == 5; } + + bool has_zero_dim_memory() const + { return memory_desc_wrapper(src_desc()).has_zero_dim(); } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + +protected: + pooling_desc_t desc_; + const pooling_fwd_pd_t *hint_fwd_pd_; + + memory_desc_t ws_md_; + + void init_default_ws() { + ws_md_ = is_fwd() ? *dst_md() : *diff_dst_md(); + ws_md_.data_type = indices_data_type(); + } + + data_type_t indices_data_type() const { + /* the simplest way to express 256... */ + const int u8_max = nstl::numeric_limits< + typename prec_traits::type>::max(); + return utils::array_product(desc()->kernel, ndims()) <= u8_max + ? data_type::u8 : data_type::s32; + } + +private: + const memory_desc_t &src_desc() const + { return is_fwd() ? desc_.src_desc : desc_.diff_src_desc; } + const memory_desc_t &dst_desc() const + { return is_fwd() ? desc_.dst_desc : desc_.diff_dst_desc; } +}; + +struct pooling_fwd_pd_t: public pooling_pd_t { + typedef pooling_fwd_pd_t base_class; + typedef pooling_fwd_pd_t hint_class; + + pooling_fwd_pd_t(engine_t *engine, + const pooling_desc_t *adesc, + const primitive_attr_t *attr, + const pooling_fwd_pd_t *hint_fwd_pd) + : pooling_pd_t(engine, adesc, attr, hint_fwd_pd) + , src_md_(desc_.src_desc) + , dst_md_(desc_.dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_SRC) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && (workspace_md() != nullptr)) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && !types::is_zero_md(&ws_md_) ? &ws_md_ : nullptr; } + + virtual int n_inputs() const override { return 1; } + virtual int n_outputs() const override + { return 1 + (workspace_md() != nullptr); } + +protected: + memory_desc_t src_md_; + memory_desc_t dst_md_; + + virtual status_t set_default_params() { + if (dst_md()->format_kind != format_kind::any) + return status::success; + + if (src_md()->format_kind != format_kind::blocked) + return status::unimplemented; + + return memory_desc_init_by_blocking_desc(dst_md_, + src_md_.format_desc.blocking); + } +}; + +struct pooling_bwd_pd_t: public pooling_pd_t { + typedef pooling_bwd_pd_t base_class; + typedef pooling_fwd_pd_t hint_class; + + pooling_bwd_pd_t(engine_t *engine, + const pooling_desc_t *adesc, + const primitive_attr_t *attr, + const pooling_fwd_pd_t *hint_fwd_pd) + : pooling_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_src_md_(desc_.diff_src_desc) + , diff_dst_md_(desc_.diff_dst_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_DIFF_DST) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && (workspace_md() != nullptr)) + return arg_usage_t::input; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_src_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_dst_md_ : nullptr; } + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && !types::is_zero_md(&ws_md_) ? &ws_md_ : nullptr; } + + virtual int n_inputs() const override + { return 1 + (workspace_md() != nullptr); } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t diff_src_md_; + memory_desc_t diff_dst_md_; + + virtual status_t set_default_params() { + if (diff_src_md()->format_kind != format_kind::any) + return status::success; + + if (diff_dst_md()->format_kind != format_kind::blocked) + return status::unimplemented; + + return memory_desc_init_by_blocking_desc(diff_src_md_, + diff_dst_md_.format_desc.blocking); + } +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive.cpp b/thirdparty/oidn/mkl-dnn/src/common/primitive.cpp new file mode 100644 index 000000000000..fdf6522f6214 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive.cpp @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "primitive_desc.hpp" +#include "primitive.hpp" +#include "type_helpers.hpp" +#include "stream.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::primitive_kind; + +namespace { +// XXX: this is a huge hammer. This disables all and any msan checks on +// primitives outputs. +// +// A proper approach would be an implementation-specific unpoisoning. +void unpoison_outputs(const exec_args_t &args) { + for(const auto &arg: args) { + if (arg.second.is_const) continue; + auto *mem = arg.second.mem; + void *p; + mem->get_data_handle(&p); + size_t s = memory_desc_wrapper(*mem->md()).size(); + msan_unpoison(p, s); + } +} +} + +status_t mkldnn_primitive_desc_destroy(primitive_desc_t *primitive_desc) { + if (primitive_desc) delete primitive_desc; + return success; +} + +status_t mkldnn_primitive_create(primitive_t **primitive, + const primitive_desc_t *primitive_desc) { + if (utils::any_null(primitive, primitive_desc)) + return invalid_arguments; + return primitive_desc->create_primitive(primitive); +} + +status_t mkldnn_primitive_execute(const primitive_t *primitive, + stream_t *stream, int nargs, const mkldnn_exec_arg_t *c_args) { + bool ok = true + && !utils::any_null(primitive, stream) + && primitive->engine() == stream->engine() + && IMPLICATION(nargs > 0, c_args != nullptr); + if (!ok) return invalid_arguments; + + exec_args_t args; + status_t status = cvt_primtive_args(primitive->pd(), nargs, c_args, args); + if (status != status::success) return status; + + exec_ctx_t ctx(stream, std::move(args)); + + if (mkldnn_verbose()->level) { + double ms = get_msec(); + status = primitive->execute(ctx); + ms = get_msec() - ms; + printf("mkldnn_verbose,exec,%s,%g\n", primitive->pd()->info(), ms); + fflush(0); + } else { + status = primitive->execute(ctx); + } + + if (msan_enabled) unpoison_outputs(ctx.args()); + + return status; +} + +status_t mkldnn_primitive_get_primitive_desc(const primitive_t *primitive, + const primitive_desc_t **primitive_desc) { + if (utils::any_null(primitive, primitive_desc)) + return invalid_arguments; + return safe_ptr_assign(*primitive_desc, + primitive->pd()); +} + +status_t mkldnn_primitive_destroy(primitive_t *primitive) { + if (primitive != nullptr) + delete primitive; + return success; +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive.hpp b/thirdparty/oidn/mkl-dnn/src/common/primitive.hpp new file mode 100644 index 000000000000..3b506d6d1f62 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive.hpp @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef PRIMITIVE_HPP +#define PRIMITIVE_HPP + +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "primitive_desc.hpp" +#include "primitive_exec_types.hpp" + +/** \brief A pure virtual primitive class + * + * Primitive contains links to its inputs & outputs, though it does not track + * their readiness on execution step. + * + * @remark @b Rational. + * Dependencies are essential through-out the whole MKL-DNN library, so it + * makes sense to include them on the very low level. On the other hand, + * tracking them should be a task for corresponding essence, like scheduler, + * stream or whatever. Primitive itself should know nothing about the + * environment it is running in. + * + * @note + * To make user experience better we should provide API which allows + * achieving the best (or good enough) performance when creating primitives + * in natural order: i.e. from bottom to top for forward pass and from top to + * bottom for backward pass. Please consider restriction [1] in Level 0. + */ +struct mkldnn_primitive: public mkldnn::impl::c_compatible { + mkldnn_primitive(const mkldnn::impl::primitive_desc_t *pd) + : pd_(pd->clone()) {} + virtual ~mkldnn_primitive() { delete pd_; } + + /** returns primitive's engine */ + mkldnn::impl::engine_t *engine() const { return pd_->engine(); } + /** returns primitive's inputs */ + const mkldnn::impl::primitive_desc_t *pd() const { return pd_; } + /** returns primitive's kind */ + mkldnn::impl::primitive_kind_t kind() const { return pd_->kind(); } + + /** executes primitive with execution context @p ctx */ + virtual mkldnn::impl::status_t execute(const mkldnn::impl::exec_ctx_t &ctx) + const = 0; + +protected: + const mkldnn::impl::primitive_desc_t *pd_; + +private: + mkldnn_primitive() = delete; + mkldnn_primitive(const mkldnn_primitive &) = delete; + mkldnn_primitive(mkldnn_primitive &&) = delete; + mkldnn_primitive &operator=(const mkldnn_primitive &) = delete; + mkldnn_primitive &operator=(mkldnn_primitive &&) = delete; +}; + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_attr.cpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_attr.cpp new file mode 100644 index 000000000000..9fd638842cd5 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_attr.cpp @@ -0,0 +1,290 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_attr.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; + +namespace mkldnn { +namespace impl { + +status_t scales_t::set(dim_t count, int mask, const float *scales) { + cleanup(); + + count_ = count; + mask_ = mask; + + if (count_ == 1) { + scales_ = scales_buf_; + utils::array_set(scales_, scales[0], scales_buf_size); + } else { + scales_ = (float *)impl::malloc(count_ * sizeof(*scales_), 64); + if (scales_ == nullptr) + return status::out_of_memory; + + for (dim_t c = 0; c < count_; ++c) + scales_[c] = scales[c]; + } + + return status::success; +} + +} +} + +status_t post_ops_t::append_sum(float scale) { + if (len_ == capacity) + return out_of_memory; + + entry_[len_].kind = primitive_kind::sum; + entry_[len_].sum.scale = scale; + + len_++; + + return success; +} + +status_t post_ops_t::append_eltwise(float scale, alg_kind_t alg, float alpha, + float beta) { + using namespace mkldnn::impl::alg_kind; + bool known_alg = one_of(alg, eltwise_relu, eltwise_tanh, eltwise_elu, + eltwise_square, eltwise_abs, eltwise_sqrt, eltwise_linear, + eltwise_bounded_relu, eltwise_soft_relu, eltwise_logistic); + if (!known_alg) + return invalid_arguments; + + if (len_ == capacity) + return out_of_memory; + + entry_[len_].kind = primitive_kind::eltwise; + entry_[len_].eltwise.scale = scale; + entry_[len_].eltwise.alg = alg; + entry_[len_].eltwise.alpha = alpha; + entry_[len_].eltwise.beta = beta; + + len_++; + + return success; +} + +status_t primitive_attr_t::set_scratchpad_mode( + scratchpad_mode_t scratchpad_mode) { + using namespace mkldnn::impl::scratchpad_mode; + + const bool ok = one_of(scratchpad_mode, library, user); + if (!ok) + return invalid_arguments; + + scratchpad_mode_ = scratchpad_mode; + return success; +} + +status_t primitive_attr_t::set_post_ops(const post_ops_t &post_ops) { + this->post_ops_ = post_ops; + return success; +} + +/* Public C API */ + +status_t mkldnn_primitive_attr_create(primitive_attr_t **attr) { + if (attr == nullptr) + return invalid_arguments; + + return safe_ptr_assign(*attr, + new mkldnn_primitive_attr); +} + +status_t mkldnn_primitive_attr_clone(primitive_attr_t **attr, + const primitive_attr_t *existing_attr) { + if (any_null(attr, existing_attr)) + return invalid_arguments; + + return safe_ptr_assign(*attr, + existing_attr->clone()); +} + +status_t mkldnn_primitive_attr_destroy(primitive_attr_t *attr) { + if (attr) + delete attr; + + return success; +} + +status_t mkldnn_primitive_attr_get_scratchpad_mode( + const primitive_attr_t *attr, scratchpad_mode_t *scratchpad_mode) { + if (any_null(attr, scratchpad_mode)) + return invalid_arguments; + + *scratchpad_mode = attr->scratchpad_mode_; + + return success; +} + +status_t mkldnn_primitive_attr_set_scratchpad_mode( + primitive_attr_t *attr, scratchpad_mode_t scratchpad_mode) { + if (any_null(attr)) + return invalid_arguments; + + return attr->set_scratchpad_mode(scratchpad_mode); +} + +status_t mkldnn_primitive_attr_get_output_scales(const primitive_attr_t *attr, + dim_t *count, int *mask, const float **scales) { + if (any_null(attr, count, mask, scales)) + return invalid_arguments; + + *count = attr->output_scales_.count_; + *mask = attr->output_scales_.mask_; + *scales = attr->output_scales_.scales_; + + return success; +} + +status_t mkldnn_primitive_attr_set_output_scales(primitive_attr_t *attr, + dim_t count, int mask, const float *scales) { + bool ok = !any_null(attr, scales) && count > 0 && mask >= 0; + if (!ok) + return invalid_arguments; + + return attr->output_scales_.set(count, mask, scales); +} + +status_t mkldnn_primitive_attr_get_post_ops(const primitive_attr_t *attr, + const post_ops_t **post_ops) { + if (any_null(attr, post_ops)) + return invalid_arguments; + + *post_ops = &attr->post_ops_; + return success; +} + +status_t mkldnn_primitive_attr_set_post_ops(primitive_attr_t *attr, + const post_ops_t *post_ops) { + if (any_null(attr, post_ops)) + return invalid_arguments; + + return attr->set_post_ops(*post_ops); +} + +status_t mkldnn_post_ops_create(post_ops_t **post_ops) { + if (post_ops == nullptr) + return invalid_arguments; + + return safe_ptr_assign(*post_ops, new mkldnn_post_ops); +} + +status_t mkldnn_post_ops_destroy(post_ops_t *post_ops) { + if (post_ops) + delete post_ops; + + return success; +} + +int mkldnn_post_ops_len(const post_ops_t *post_ops) { + if (post_ops) + return post_ops->len_; + + return 0; +} + +primitive_kind_t mkldnn_post_ops_get_kind(const post_ops_t *post_ops, + int index) { + bool ok = post_ops && 0 <= index && index < post_ops->len_; + if (!ok) + return primitive_kind::undefined; + + return post_ops->entry_[index].kind; +} + +status_t mkldnn_post_ops_append_sum(post_ops_t *post_ops, float scale) { + if (post_ops == nullptr) + return invalid_arguments; + + return post_ops->append_sum(scale); +} + +namespace { +bool simple_get_params_check(const post_ops_t *post_ops, int index, + primitive_kind_t kind) { + bool ok = true + && post_ops != nullptr + && 0 <= index + && index < post_ops->len_ + && post_ops->entry_[index].kind == kind; + return ok; +} +} + +status_t mkldnn_post_ops_get_params_sum(const post_ops_t *post_ops, int index, + float *scale) { + bool ok = true + && simple_get_params_check(post_ops, index, primitive_kind::sum) + && !any_null(scale); + if (!ok) + return invalid_arguments; + + *scale = post_ops->entry_[index].sum.scale; + return success; +} + +status_t mkldnn_post_ops_append_eltwise(post_ops_t *post_ops, float scale, + alg_kind_t kind, float alpha, float beta) { + if (post_ops == nullptr) + return invalid_arguments; + + return post_ops->append_eltwise(scale, kind, alpha, beta); +} + +status_t mkldnn_post_ops_get_params_eltwise(const post_ops_t *post_ops, + int index, float *scale, alg_kind_t *alg, float *alpha, float *beta) { + bool ok = true + && simple_get_params_check(post_ops, index, primitive_kind::eltwise) + && !any_null(scale, alpha, beta); + if (!ok) + return invalid_arguments; + + const auto &e = post_ops->entry_[index].eltwise; + *scale = e.scale; + *alg = e.alg; + *alpha = e.alpha; + *beta = e.beta; + + return success; +} + +status_t mkldnn_primitive_attr_set_rnn_data_qparams( + primitive_attr_t *attr, const float scale, const float shift) { + if (attr == nullptr) + return invalid_arguments; + + return attr->rnn_data_qparams_.set(scale, shift); +} + +status_t mkldnn_primitive_attr_set_rnn_weights_qparams( + primitive_attr_t *attr, dim_t count, int mask, const float *scales) { + bool ok = !any_null(attr, scales) && count > 0 && mask >= 0; + if (!ok) + return invalid_arguments; + + return attr->rnn_weights_qparams_.set(count, mask, scales); +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_attr.hpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_attr.hpp new file mode 100644 index 000000000000..e2130c7ab161 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_attr.hpp @@ -0,0 +1,183 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef PRIMITIVE_ATTR_HPP +#define PRIMITIVE_ATTR_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct rnn_data_qparams_t : public c_compatible { + rnn_data_qparams_t() : scale_(1.), shift_(0.) {} + bool has_default_values() const { return (scale_ == 1. && shift_ == 0.); } + + status_t set(float scale, float shift) { + scale_ = scale; + shift_ = shift; + return status::success; + } + + float scale_; + float shift_; +}; + +struct scales_t: public c_compatible { + scales_t(): count_(1), mask_(0), scales_(scales_buf_) + { set(1.); } + + scales_t(const scales_t &rhs): scales_t() + { set(rhs.count_, rhs.mask_, rhs.scales_); } + + ~scales_t() { cleanup(); } + + scales_t &operator=(const scales_t &rhs) { + if (&rhs == this) + return *this; + status_t status = set(rhs.count_, rhs.mask_, rhs.scales_); + assert(status == status::success); + (void)status; + return *this; + } + + bool has_default_values() const { + for (dim_t c = 0; c < count_; ++c) { + if(scales_[c] != 1.) return false; + } + return true; + } + + status_t set(dim_t count, int mask, const float *scales); + status_t set(float single_scale) { return this->set(1, 0, &single_scale); } + + dim_t count_; + int mask_; + float *scales_; + +private: + enum { scales_buf_size = 16 }; + float scales_buf_[scales_buf_size]; + + void cleanup() { + if (scales_ != scales_buf_ && scales_ != nullptr) + impl::free(scales_); + + count_ = 1; + mask_ = 0; + scales_ = scales_buf_; + } +}; + +} +} + +struct mkldnn_post_ops: public mkldnn::impl::c_compatible { + struct entry_t { + struct eltwise_t { + mkldnn::impl::alg_kind_t alg; + float scale, alpha, beta; + }; + + mkldnn::impl::primitive_kind_t kind; + union { + struct { float scale; } sum; + eltwise_t eltwise; + }; + + bool is_eltwise(bool require_scale_one = true) const { + using namespace mkldnn::impl; + return kind == primitive_kind::eltwise + && IMPLICATION(require_scale_one, eltwise.scale == 1.f); + } + + bool is_relu(bool require_scale_one = true, + bool require_nslope_zero = true) const { + using namespace mkldnn::impl; + return is_eltwise(require_scale_one) + && eltwise.alg == alg_kind::eltwise_relu + && IMPLICATION(require_nslope_zero, eltwise.alpha == 0.f); + } + + bool is_sum(bool require_scale_one = true) const { + using namespace mkldnn::impl; + return kind == primitive_kind::sum + && IMPLICATION(require_scale_one, sum.scale == 1.f); + } + }; + + mkldnn_post_ops(): len_(0) {} + + mkldnn::impl::status_t append_sum(float scale); + mkldnn::impl::status_t append_eltwise(float scale, + mkldnn::impl::alg_kind_t alg, float alpha, float beta); + + int find(mkldnn::impl::primitive_kind_t kind, int start = 0, + int stop = -1) const { + if (stop == -1) stop = len_; + stop = mkldnn::impl::nstl::min(stop, len_); + for (int idx = start; idx < stop; ++idx) + if (entry_[idx].kind == kind) return idx; + return -1; + } + + bool has_default_values() const { return len_ == 0; } + + bool contain(mkldnn::impl::primitive_kind_t kind, int index) const + { return find(kind, index, index + 1) == index; } + + enum { capacity = 4 }; + + int len_; + entry_t entry_[capacity]; +}; + +struct mkldnn_primitive_attr: public mkldnn::impl::c_compatible { + mkldnn_primitive_attr() + : scratchpad_mode_(mkldnn::impl::scratchpad_mode::library) + {} + + mkldnn_primitive_attr *clone() const + { return new mkldnn_primitive_attr(*this); } + + /** Returns true if the attributes have default values. + * + * @note The scratchpad_mode_ is not take into account */ + bool has_default_values() const { + return true + && output_scales_.has_default_values() + && post_ops_.has_default_values() + && rnn_data_qparams_.has_default_values() + && rnn_weights_qparams_.has_default_values(); + } + + mkldnn::impl::status_t set_scratchpad_mode( + mkldnn::impl::scratchpad_mode_t scratchpad_mode); + mkldnn::impl::status_t set_post_ops( + const mkldnn::impl::post_ops_t &post_ops); + + mkldnn::impl::scratchpad_mode_t scratchpad_mode_; + mkldnn::impl::scales_t output_scales_; + mkldnn::impl::post_ops_t post_ops_; + mkldnn::impl::rnn_data_qparams_t rnn_data_qparams_; + mkldnn::impl::scales_t rnn_weights_qparams_; +}; + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_desc.cpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_desc.cpp new file mode 100644 index 000000000000..723c41e05aec --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_desc.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "primitive_desc.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; + +status_t primitive_desc_t::query(query_t what, int idx, void *result) const { + auto safe_ret_md = [&](const memory_desc_t *_) { + if (_ == nullptr) return not_required; + *(const memory_desc_t **)result = _; + return success; + }; + + switch (what) { + case query::engine: *(engine_t**)result = engine(); break; + case query::primitive_kind: *(primitive_kind_t*)result = kind(); break; + + case query::scratchpad_engine: + *(engine_t**)result = scratchpad_engine(); break; + + case query::memory_consumption_s64: + *(dim_t *)result = scratchpad_size(scratchpad_mode::library); break; + + case query::op_d: + if (idx != 0 || op_desc() == nullptr) return invalid_arguments; + *(const_c_op_desc_t *)result + = static_cast(op_desc()); break; + + case query::src_md: return safe_ret_md(src_md(idx)); + case query::diff_src_md: return safe_ret_md(diff_src_md(idx)); + case query::dst_md: return safe_ret_md(dst_md(idx)); + case query::diff_dst_md: return safe_ret_md(diff_dst_md(idx)); + case query::weights_md: return safe_ret_md(weights_md(idx)); + case query::diff_weights_md: return safe_ret_md(diff_weights_md(idx)); + case query::workspace_md: + if (idx != 0) return status::invalid_arguments; + return safe_ret_md(workspace_md(idx)); + case query::scratchpad_md: + if (idx != 0) return status::invalid_arguments; + return safe_ret_md(scratchpad_md(idx)); + + case query::num_of_inputs_s32: *(int*)result = n_inputs(); break; + case query::num_of_outputs_s32: *(int*)result = n_outputs(); break; + + case query::impl_info_str: *(const char **)result = name(); break; + + default: return unimplemented; + } + return success; +} + +status_t mkldnn_primitive_desc_get_attr(const primitive_desc_t *primitive_desc, + const primitive_attr_t **attr) { + if (utils::any_null(primitive_desc, attr)) + return invalid_arguments; + + *attr = primitive_desc->attr(); + return success; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_desc.hpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_desc.hpp new file mode 100644 index 000000000000..536dcfa1d0b2 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_desc.hpp @@ -0,0 +1,174 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef PRIMITIVE_DESC_HPP +#define PRIMITIVE_DESC_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "primitive_attr.hpp" +#include "verbose.hpp" + +struct mkldnn_primitive_desc: public mkldnn::impl::c_compatible { + using md_t = mkldnn::impl::memory_desc_t; + + mkldnn_primitive_desc(mkldnn::impl::engine_t *engine, + const mkldnn::impl::primitive_attr_t *attr, + mkldnn::impl::primitive_kind_t kind) + : engine_(engine), attr_(*attr), kind_(kind) { info_[0] = '\0'; } + + mkldnn_primitive_desc(mkldnn::impl::engine_t *engine, + mkldnn::impl::primitive_kind_t kind) + : engine_(engine), kind_(kind) { info_[0] = '\0'; } + + virtual mkldnn_primitive_desc *clone() const = 0; + virtual ~mkldnn_primitive_desc() {} + + const mkldnn::impl::primitive_attr_t *attr() const { return &attr_; } + mkldnn::impl::engine_t *engine() const { return engine_; } + mkldnn::impl::primitive_kind_t kind() const { return kind_; } + + virtual void init_info() {} + const char *info() const { return info_; } + + mkldnn::impl::memory_tracking::registry_t &scratchpad_registry() + { return scratchpad_registry_; } + const mkldnn::impl::memory_tracking::registry_t &scratchpad_registry() const + { return scratchpad_registry_; } + virtual mkldnn::impl::engine_t *scratchpad_engine() const + { return engine_; } + + virtual const mkldnn::impl::op_desc_t *op_desc() const { return nullptr; } + + enum class arg_usage_t { unused, input, output }; + virtual arg_usage_t arg_usage( + mkldnn::impl::primitive_arg_index_t arg) const { + using mkldnn::impl::types::is_zero_md; + if (arg == MKLDNN_ARG_SCRATCHPAD && !is_zero_md(scratchpad_md())) + return arg_usage_t::output; + return arg_usage_t::unused; + } + +# define DECLARE_MD_STUB(stub) \ + virtual const mkldnn::impl::memory_desc_t *stub(int idx = 0) const \ + { return nullptr; } + + DECLARE_MD_STUB(input_md); DECLARE_MD_STUB(output_md); + DECLARE_MD_STUB(src_md); DECLARE_MD_STUB(diff_src_md); + DECLARE_MD_STUB(dst_md); DECLARE_MD_STUB(diff_dst_md); + DECLARE_MD_STUB(weights_md); DECLARE_MD_STUB(diff_weights_md); + DECLARE_MD_STUB(workspace_md); +# undef DECLARE_MD_STUB + + const mkldnn::impl::memory_desc_t *scratchpad_md(int idx = 0) const { + return idx == 0 ? &scratchpad_md_ : nullptr; + } + + virtual void init_scratchpad_md() { + auto size = scratchpad_size(mkldnn::impl::scratchpad_mode::user); + mkldnn::impl::dims_t dims = { size }; + mkldnn_memory_desc_init_by_tag(&scratchpad_md_, size ? 1 : 0, dims, + mkldnn::impl::data_type::u8, mkldnn_x); + } + + /** returns the scratchpad size for the given scratchpad mode. */ + mkldnn::impl::dim_t scratchpad_size( + mkldnn::impl::scratchpad_mode_t mode) const { + if (mode != attr_.scratchpad_mode_) return 0; + return scratchpad_registry().size(); + } + + virtual int n_inputs() const { return 0; } + virtual int n_outputs() const { return 0; } + + virtual mkldnn::impl::status_t query(mkldnn::impl::query_t what, int idx, + void *result) const; + + virtual mkldnn::impl::status_t create_primitive( + mkldnn::impl::primitive_t **primitive) const = 0; + + virtual const char *name() const { return "mkldnn_primitive_desc"; } + + /* static magic */ + + template + static mkldnn::impl::status_t create(mkldnn::impl::primitive_desc_t **pd, + const mkldnn::impl::op_desc_t *adesc, + const mkldnn::impl::primitive_attr_t *attr, + mkldnn::impl::engine_t *engine, + const mkldnn::impl::primitive_desc_t *hint_fwd) { + using namespace mkldnn::impl; + using namespace mkldnn::impl::status; + using pd_op_desc_t = typename pkind_traits::desc_type; + if (adesc->kind != pd_t::base_pkind) return invalid_arguments; + assert(hint_fwd ? hint_fwd->kind() == pd_t::base_pkind : true); + auto hint = + reinterpret_cast(hint_fwd); + auto _pd = new pd_t(engine, (const pd_op_desc_t *)adesc, attr, hint); + if (_pd == nullptr) return out_of_memory; + if (_pd->init() != success) { delete _pd; return unimplemented; } + _pd->init_info(); + _pd->init_scratchpad_md(); + *pd = _pd; + return success; + } + +protected: + mkldnn::impl::engine_t *engine_; + mkldnn::impl::primitive_attr_t attr_; + mkldnn::impl::primitive_kind_t kind_; + + mkldnn::impl::memory_desc_t scratchpad_md_; + + char info_[MKLDNN_VERBOSE_BUF_LEN]; + + mkldnn::impl::memory_tracking::registry_t scratchpad_registry_; + +protected: + /** compares ws between fwd_pd and this (make sense to use for bwd_pd) + * Expectation: this already set workspace, and this workspace should + * exactly match the one from fwd_pd */ + bool compare_ws(const mkldnn_primitive_desc *fwd_pd) const { + using namespace mkldnn::impl; + if (!workspace_md()) return true; // the impl lives fine w/o workspace + return fwd_pd && fwd_pd->workspace_md() + && *fwd_pd->workspace_md() == *workspace_md(); + } +}; + +#define DECLARE_COMMON_PD_t(impl_name, ...) \ + virtual pd_t *clone() const override { return new pd_t(*this); } \ + virtual status_t create_primitive(primitive_t **p) const override { \ + double ms = get_msec(); \ + auto ret = safe_ptr_assign(*p, new (__VA_ARGS__)(this)); \ + ms = get_msec() - ms; \ + if (mkldnn_verbose()->level >= 2) { \ + printf("mkldnn_verbose,create,%s,%g\n", this->info(), ms); \ + fflush(0); \ + } \ + return ret; \ + } \ + virtual const char *name() const override { return impl_name; } +#define DECLARE_COMMON_PD_T(impl_name, ...) \ + DECLARE_COMMON_PD_t(impl_name, __VA_ARGS__) + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.cpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.cpp new file mode 100644 index 000000000000..43e5a31ef3f7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.cpp @@ -0,0 +1,90 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "memory.hpp" +#include "primitive.hpp" +#include "primitive_exec_types.hpp" + +namespace mkldnn { +namespace impl { + +status_t cvt_primtive_args(const primitive_desc_t *pd, int nargs, + const mkldnn_exec_arg_t *c_args, exec_args_t &args) { + using namespace status; + + if (!IMPLICATION(nargs > 0, c_args != nullptr)) return invalid_arguments; + + int n_inputs = 0; + int n_outputs = 0; + + for (int i = 0; i < nargs; ++i) { + primitive_arg_index_t arg = c_args[i].arg; + auto *mem = c_args[i].memory; + + switch (pd->arg_usage(arg)) { + case primitive_desc_t::arg_usage_t::input: + if (args.count(arg) != 0) return invalid_arguments; + args[arg] = {mem, true}; + n_inputs++; + break; + case primitive_desc_t::arg_usage_t::output: + if (args.count(arg) != 0) return invalid_arguments; + args[arg] = {mem, false}; + n_outputs++; + break; + case primitive_desc_t::arg_usage_t::unused: + break; + } + } + + bool scratchpad_required = !types::is_zero_md(pd->scratchpad_md()); + + if (n_inputs != pd->n_inputs()) return invalid_arguments; + if (n_outputs != pd->n_outputs() + (scratchpad_required ? 1 : 0)) + return invalid_arguments; + + return success; +} + +const void *exec_ctx_t::input(primitive_arg_index_t arg) const { + if (args_.count(arg) != 1) return nullptr; + const auto ma = args_.at(arg); + assert(ma.is_const); + void *ptr; + status_t status = ma.mem->get_data_handle(&ptr); + assert(status == status::success); MAYBE_UNUSED(status); + return ptr; +} + +void *exec_ctx_t::output(primitive_arg_index_t arg) const { + if (args_.count(arg) != 1) return nullptr; + const auto ma = args_.at(arg); + assert(!ma.is_const); + void *ptr; + status_t status = ma.mem->get_data_handle(&ptr); + assert(status == status::success); MAYBE_UNUSED(status); + return ptr; +} + +const memory_t *exec_ctx_t::memory(primitive_arg_index_t arg) const { + assert(args_.count(arg) == 1); + const auto ma = args_.at(arg); + assert(!ma.is_const); + return ma.mem; +} + +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.hpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.hpp new file mode 100644 index 000000000000..0645891da7a9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_exec_types.hpp @@ -0,0 +1,68 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef PRIMITIVE_EXEC_TYPES_HPP +#define PRIMITIVE_EXEC_TYPES_HPP + +#include + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "memory.hpp" +#include "primitive_desc.hpp" + +namespace mkldnn { +namespace impl { + +struct memory_arg_t { + memory_t *mem; + bool is_const; +}; + +using exec_args_t = std::unordered_map; + +status_t cvt_primtive_args(const primitive_desc_t *pd, int nargs, + const mkldnn_exec_arg_t *c_args, exec_args_t &args); + +/** Primitive execution context (helps passing stream, memories, and events. */ +struct exec_ctx_t { + exec_ctx_t(const exec_ctx_t &) = default; + exec_ctx_t(exec_ctx_t &&) = default; + + exec_ctx_t(stream_t *stream): stream_(stream) {} + exec_ctx_t(stream_t *stream, exec_args_t &&args) + : stream_(stream) + , args_(std::move(args)) {} + + stream_t *stream() const { return stream_; } + const exec_args_t &args() const { return args_; } + + /* tentative solution... TODO: replace with functions return memory_t */ + const void *input(primitive_arg_index_t arg) const; + void *output(primitive_arg_index_t arg) const; + + const memory_t *memory(primitive_arg_index_t arg) const; + +private: + stream_t *stream_; + exec_args_t args_; +}; + +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.cpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.cpp new file mode 100644 index 000000000000..5a1cd7d379ac --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.cpp @@ -0,0 +1,89 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "primitive_desc.hpp" +#include "type_helpers.hpp" +#include "primitive_iterator.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; + +status_t mkldnn_primitive_desc_iterator_create( + primitive_desc_iterator_t **iterator, const_c_op_desc_t c_op_desc, + const primitive_attr_t *attr, engine_t *engine, + const primitive_desc_t *hint_fwd_pd) { + const op_desc_t *op_desc = (const op_desc_t *)c_op_desc; + + auto it = new primitive_desc_iterator_t(engine, op_desc, attr, hint_fwd_pd); + if (it == nullptr) return out_of_memory; + + ++(*it); + if (*it == it->end()) { + delete it; + return unimplemented; + } + + *iterator = it; + return success; +} + +status_t mkldnn_primitive_desc_iterator_next( + primitive_desc_iterator_t *iterator) { + if (iterator == nullptr) return invalid_arguments; + ++(*iterator); + return *iterator == iterator->end() ? iterator_ends : success; +} + +primitive_desc_t *mkldnn_primitive_desc_iterator_fetch( + const primitive_desc_iterator_t *iterator) { + if (iterator == nullptr) return nullptr; + return *(*iterator); +} + +status_t mkldnn_primitive_desc_clone(primitive_desc_t **primitive_desc, + const primitive_desc_t *existing_primitive_desc) { + if (utils::any_null(primitive_desc, existing_primitive_desc)) + return invalid_arguments; + return safe_ptr_assign(*primitive_desc, + existing_primitive_desc->clone()); +} + +status_t mkldnn_primitive_desc_iterator_destroy( + primitive_desc_iterator_t *iterator) { + if (iterator != nullptr) + delete iterator; + return success; +} + +status_t mkldnn_primitive_desc_create(primitive_desc_t **primitive_desc, + const_c_op_desc_t c_op_desc, const primitive_attr_t *attr, + engine_t *engine, const primitive_desc_t *hint_fwd_pd) { + const op_desc_t *op_desc = (const op_desc_t *)c_op_desc; + + mkldnn_primitive_desc_iterator it(engine, op_desc, attr, hint_fwd_pd); + ++it; + if (it == it.end()) return unimplemented; + + return safe_ptr_assign(*primitive_desc, *it); +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.hpp b/thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.hpp new file mode 100644 index 000000000000..4e88ab3aa5d9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/primitive_iterator.hpp @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ +#ifndef PRIMITIVE_ITERATOR_HPP +#define PRIMITIVE_ITERATOR_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "primitive_desc.hpp" +#include "type_helpers.hpp" + +struct mkldnn_primitive_desc_iterator: public mkldnn::impl::c_compatible { + using pd_create_f = mkldnn::impl::engine_t::primitive_desc_create_f; + + mkldnn_primitive_desc_iterator(mkldnn::impl::engine_t *engine, const mkldnn::impl::op_desc_t *op_desc, + const mkldnn::impl::primitive_attr_t *attr, const mkldnn::impl::primitive_desc_t *hint_fwd_pd) + : idx_(-1), engine_(engine), pd_(nullptr), op_desc_(op_desc) + , attr_(attr ? *attr : mkldnn::impl::primitive_attr_t()), hint_fwd_pd_(hint_fwd_pd) + , impl_list_(engine_->get_implementation_list()), last_idx_(0) + { + while (impl_list_[last_idx_] != nullptr) ++last_idx_; + } + ~mkldnn_primitive_desc_iterator() { if (pd_) delete pd_; } + + bool operator==(const mkldnn::impl::primitive_desc_iterator_t& rhs) const + { return idx_ == rhs.idx_ && engine_ == rhs.engine_; } + bool operator!=(const mkldnn::impl::primitive_desc_iterator_t& rhs) const + { return !operator==(rhs); } + + mkldnn::impl::primitive_desc_iterator_t end() const + { return mkldnn_primitive_desc_iterator(engine_, last_idx_); } + + mkldnn::impl::primitive_desc_iterator_t &operator++() { + if (pd_) { delete pd_; pd_ = nullptr; } + while (++idx_ != last_idx_) { + auto s = impl_list_[idx_](&pd_, op_desc_, &attr_, engine_, + hint_fwd_pd_); + if (s == mkldnn::impl::status::success) break; + } + return *this; + } + + mkldnn::impl::primitive_desc_t *operator*() const { + if (*this == end() || pd_ == nullptr) return nullptr; + return pd_->clone(); + } + +protected: + int idx_; + mkldnn::impl::engine_t *engine_; + mkldnn::impl::primitive_desc_t *pd_; + const mkldnn::impl::op_desc_t *op_desc_; + const mkldnn::impl::primitive_attr_t attr_; + const mkldnn::impl::primitive_desc_t *hint_fwd_pd_; + const pd_create_f *impl_list_; + int last_idx_; + +private: + mkldnn_primitive_desc_iterator(mkldnn::impl::engine_t *engine, int last_idx) + : idx_(last_idx), engine_(engine), pd_(nullptr) + , op_desc_(nullptr), hint_fwd_pd_(nullptr) + , impl_list_(nullptr), last_idx_(last_idx) {} +}; + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/query.cpp b/thirdparty/oidn/mkl-dnn/src/common/query.cpp new file mode 100644 index 000000000000..835cd7358156 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/query.cpp @@ -0,0 +1,59 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "primitive_desc.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; + +status_t mkldnn_primitive_desc_query(const primitive_desc_t *primitive_desc, + query_t what, int index, void *result) { + if (any_null(primitive_desc, result)) + return invalid_arguments; + + return primitive_desc->query(what, index, result); +} + +const memory_desc_t *mkldnn_primitive_desc_query_md( + const primitive_desc_t *primitive_desc, query_t what, int index) { + const memory_desc_t *res_md = nullptr; + bool args_ok = true + && primitive_desc != nullptr + && (what & query::some_md) == query::some_md + && what != query::some_md + && mkldnn_primitive_desc_query(primitive_desc, + what, index, &res_md) == success; + return args_ok ? res_md : nullptr; +} + +int mkldnn_primitive_desc_query_s32(const primitive_desc_t *primitive_desc, + query_t what, int index) { + int res_s32; + bool args_ok = primitive_desc != nullptr + && one_of(what, query::num_of_inputs_s32, query::num_of_outputs_s32) + && mkldnn_primitive_desc_query(primitive_desc, what, index, &res_s32) + == success; + return args_ok ? res_s32 : 0; +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/reorder.cpp b/thirdparty/oidn/mkl-dnn/src/common/reorder.cpp new file mode 100644 index 000000000000..d11f1a0361f1 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/reorder.cpp @@ -0,0 +1,68 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "reorder_pd.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; + +status_t mkldnn_reorder_primitive_desc_create( + primitive_desc_t **reorder_pd, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md, + const primitive_attr_t *attr) { + if (any_null(reorder_pd, src_engine, src_md, dst_engine, dst_md)) + return invalid_arguments; + + auto s_ek = src_engine->kind(); + auto d_ek = dst_engine->kind(); + if (!IMPLICATION(s_ek != d_ek, one_of(engine_kind::cpu, s_ek, d_ek))) + return invalid_arguments; + + auto r_pd = reinterpret_cast(reorder_pd); + auto s_mdw = memory_desc_wrapper(*src_md); + auto d_mdw = memory_desc_wrapper(*dst_md); + + if (!s_mdw.consistent_with(d_mdw)) + return invalid_arguments; + + auto e = (s_ek != engine_kind::cpu) ? src_engine : dst_engine; + + const primitive_attr_t dummy_attr; + if (attr == NULL) + attr = &dummy_attr; + + for (auto r = e->get_reorder_implementation_list(); *r; ++r) { + if ((*r)(r_pd, e, attr, src_engine, src_md, dst_engine, dst_md) + == success) { + (*r_pd)->init_info(); + (*r_pd)->init_scratchpad_md(); + return success; + } + } + return unimplemented; +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/reorder_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/reorder_pd.hpp new file mode 100644 index 000000000000..963cb0f58a32 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/reorder_pd.hpp @@ -0,0 +1,85 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef REORDER_PD_HPP +#define REORDER_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "primitive_attr.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct reorder_pd_t: public primitive_desc_t { + reorder_pd_t(engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) + : primitive_desc_t(engine, attr, primitive_kind::reorder) + , src_engine_(src_engine) + , dst_engine_(dst_engine) + , scratchpad_engine_(nullptr) + , src_md_(*src_md) + , dst_md_(*dst_md) + {} + + virtual const op_desc_t *op_desc() const override { return nullptr; } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_FROM) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_TO) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &src_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + + virtual int n_inputs() const override { return 1; } + virtual int n_outputs() const override { return 1; } + + float alpha() const { return attr()->output_scales_.scales_[0]; } + float beta() const { + const int sum_idx = attr()->post_ops_.find(primitive_kind::sum); + return sum_idx == -1 ? 0 : attr()->post_ops_.entry_[sum_idx].sum.scale; + } + virtual mkldnn::impl::engine_t *scratchpad_engine() const override + { return scratchpad_engine_; } + +protected: + engine_t *src_engine_; + engine_t *dst_engine_; + engine_t *scratchpad_engine_; + + memory_desc_t src_md_; + memory_desc_t dst_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/rnn.cpp b/thirdparty/oidn/mkl-dnn/src/common/rnn.cpp new file mode 100644 index 000000000000..36967431a621 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/rnn.cpp @@ -0,0 +1,400 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "cpu/gemm/os_blas.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::types; +using namespace mkldnn::impl::utils; + +namespace { +memory_desc_t copy_maybe_null(const memory_desc_t *md) { + return md ? *md : zero_md(); +} + +rnn_desc_t zero_rnn_desc() { + auto rd = rnn_desc_t(); + rd.src_layer_desc = zero_md(); + rd.src_iter_desc = zero_md(); + rd.weights_layer_desc = zero_md(); + rd.weights_iter_desc = zero_md(); + rd.bias_desc = zero_md(); + rd.dst_layer_desc = zero_md(); + rd.dst_iter_desc = zero_md(); + rd.diff_src_layer_desc = zero_md(); + rd.diff_src_iter_desc = zero_md(); + rd.diff_weights_layer_desc = zero_md(); + rd.diff_weights_iter_desc = zero_md(); + rd.diff_bias_desc = zero_md(); + rd.diff_dst_layer_desc = zero_md(); + rd.diff_dst_iter_desc = zero_md(); + return rd; +} +} + +/* Public C Api */ + +status_t mkldnn_rnn_cell_desc_init(rnn_cell_desc_t *rnn_cell_desc, + mkldnn_alg_kind_t cell_kind, mkldnn_alg_kind_t act_f, + unsigned int flags, float alpha, float clipping) { + using namespace mkldnn::impl::alg_kind; + + bool args_ok = true + && one_of(cell_kind, vanilla_rnn, vanilla_lstm, vanilla_gru, + gru_linear_before_reset) + && IMPLICATION(cell_kind == vanilla_rnn, + one_of(act_f, eltwise_relu, eltwise_tanh, eltwise_logistic)); + if (!args_ok) + return invalid_arguments; + + auto rcd = mkldnn_rnn_cell_desc_t(); + + rcd.cell_kind = cell_kind; + rcd.activation_kind = act_f; + rcd.flags = flags; + rcd.alpha = rcd.flags & mkldnn_rnn_cell_with_relu ? alpha : 0; + rcd.clipping = rcd.flags & mkldnn_rnn_cell_with_clipping ? clipping : 0; + + *rnn_cell_desc = rcd; + + return success; +} + +int mkldnn_rnn_cell_get_gates_count(const rnn_cell_desc_t *rnn_cell_desc) { + switch (rnn_cell_desc->cell_kind) { + case mkldnn::impl::alg_kind::vanilla_rnn: return 1; + case mkldnn::impl::alg_kind::vanilla_gru: return 3; + case mkldnn::impl::alg_kind::gru_linear_before_reset: return 3; + case mkldnn::impl::alg_kind::vanilla_lstm: return 4; + default: assert(!"unknown cell kind"); return 0; + } + return 0; +} + +int mkldnn_rnn_cell_get_states_count(const rnn_cell_desc_t *rnn_cell_desc) { + switch (rnn_cell_desc->cell_kind) { + case mkldnn::impl::alg_kind::vanilla_rnn: return 1; + case mkldnn::impl::alg_kind::vanilla_gru: return 1; + case mkldnn::impl::alg_kind::gru_linear_before_reset: return 1; + case mkldnn::impl::alg_kind::vanilla_lstm: return 2; + default: assert(!"unknown cell kind"); return 0; + } + return 0; +} + +status_t check_data_type_consistency_fwd(const rnn_cell_desc_t *rnn_cell_desc, + prop_kind_t prop_kind, const memory_desc_t *src_layer_desc, + const memory_desc_t *src_iter_desc, + const memory_desc_t *weights_layer_desc, + const memory_desc_t *weights_iter_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_layer_desc, + const memory_desc_t *dst_iter_desc) { + using namespace data_type; + data_type_t src_layer_dt = src_layer_desc->data_type; + data_type_t dst_layer_dt = dst_layer_desc->data_type; + data_type_t weights_iter_dt = weights_iter_desc->data_type; + data_type_t weights_layer_dt = weights_layer_desc->data_type; + + bool is_f32 = everyone_is(f32, src_layer_dt, dst_layer_dt, weights_iter_dt, + weights_layer_dt) + && IMPLICATION(!is_zero_md(src_iter_desc), + src_iter_desc->data_type == f32) + && IMPLICATION(!is_zero_md(dst_iter_desc), + dst_iter_desc->data_type == f32) + && IMPLICATION(!is_zero_md(bias_desc), bias_desc->data_type == f32); + +#if USE_MKL_PACKED_GEMM + bool is_u8u8u8 = src_layer_dt == u8 + && IMPLICATION(!is_zero_md(src_iter_desc), + src_iter_desc->data_type == u8) + && IMPLICATION(!is_zero_md(dst_iter_desc), + dst_iter_desc->data_type == u8) + && one_of(dst_layer_dt, u8, f32) + && everyone_is(s8, weights_iter_dt, weights_layer_dt) + && IMPLICATION(!is_zero_md(bias_desc), bias_desc->data_type == f32); + + bool is_f32u8f32 = src_layer_dt == u8 + && IMPLICATION(!is_zero_md(src_iter_desc), + src_iter_desc->data_type == f32) + && IMPLICATION(!is_zero_md(dst_iter_desc), + dst_iter_desc->data_type == f32) + && one_of(dst_layer_dt, u8, f32) + && everyone_is(s8, weights_iter_dt, weights_layer_dt) + && IMPLICATION(!is_zero_md(bias_desc), bias_desc->data_type == f32); + + bool is_inference = prop_kind == prop_kind::forward_inference; + bool is_lstm = rnn_cell_desc->cell_kind == mkldnn_vanilla_lstm; + + return (is_f32 || ((is_u8u8u8 || is_f32u8f32) && is_lstm && is_inference)) + ? success + : unimplemented; +#else + return is_f32 ? success : unimplemented; +#endif +} + +status_t check_dim_consistency(const rnn_cell_desc_t *rnn_cell_desc, + rnn_direction_t direction, int L, int D, int T, int N, int S, int G, + int SLC, int SIC, int DLC, int DIC, const memory_desc_t *src_layer_desc, + const memory_desc_t *src_iter_desc, + const memory_desc_t *weights_layer_desc, + const memory_desc_t *weights_iter_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_layer_desc, + const memory_desc_t *dst_iter_desc) { + bool args_ok; + + // * algorithm specific + args_ok = true + && IMPLICATION(rnn_cell_desc->cell_kind == alg_kind::vanilla_gru, + DIC == SIC); + if (!args_ok) return invalid_arguments; + int extra_bias = + rnn_cell_desc->cell_kind == alg_kind::gru_linear_before_reset; + + // * on num layers + args_ok = true + && L == weights_layer_desc->dims[0] + && L == weights_iter_desc->dims[0] + && IMPLICATION(!is_zero_md(bias_desc), L == bias_desc->dims[0]) + && IMPLICATION(!is_zero_md(src_iter_desc), L == src_iter_desc->dims[0]) + && IMPLICATION(!is_zero_md(dst_iter_desc), L == dst_iter_desc->dims[0]); + if (!args_ok) return invalid_arguments; + + // * on num directions + args_ok = true + && D == weights_layer_desc->dims[1] + && D == weights_iter_desc->dims[1] + && IMPLICATION(!is_zero_md(bias_desc), D == bias_desc->dims[1]) + && IMPLICATION(!is_zero_md(src_iter_desc), D == src_iter_desc->dims[1]) + && IMPLICATION(!is_zero_md(dst_iter_desc), D == dst_iter_desc->dims[1]); + if (!args_ok) return invalid_arguments; + + // * on num iterations + args_ok = true + && T == src_layer_desc->dims[0] + && T == dst_layer_desc->dims[0]; + if (!args_ok) return invalid_arguments; + + // * on mb + args_ok = true + && N == src_layer_desc->dims[1] + && N == dst_layer_desc->dims[1] + && IMPLICATION(!is_zero_md(src_iter_desc), N == src_iter_desc->dims[3]) + && IMPLICATION(!is_zero_md(dst_iter_desc), N == dst_iter_desc->dims[3]); + if (!args_ok) return invalid_arguments; + + // * on num gates + args_ok = true + && G == mkldnn_rnn_cell_get_gates_count(rnn_cell_desc) + && G == weights_layer_desc->dims[3] + && G == weights_iter_desc->dims[3] + && IMPLICATION(!is_zero_md(bias_desc), + G + extra_bias == bias_desc->dims[2]); + if (!args_ok) return invalid_arguments; + + // * on num states + args_ok = true + && S == mkldnn_rnn_cell_get_states_count(rnn_cell_desc) + && IMPLICATION(!is_zero_md(src_iter_desc), S == src_iter_desc->dims[2]) + && IMPLICATION(!is_zero_md(dst_iter_desc), S == dst_iter_desc->dims[2]); + if (!args_ok) return invalid_arguments; + + // * on slc + args_ok = true + && SLC == weights_layer_desc->dims[2] + && SLC == src_layer_desc->dims[2]; + if (!args_ok) return invalid_arguments; + + // * on sic + args_ok = true + && SIC == weights_iter_desc->dims[2] + && IMPLICATION(!is_zero_md(src_iter_desc), + SIC == src_iter_desc->dims[4]); + if (!args_ok) return invalid_arguments; + + // * on dlc + int dlc_multiplier = (direction == mkldnn_bidirectional_concat) ? 2 : 1; + args_ok = true + && DLC == dlc_multiplier * DIC + && DLC == dst_layer_desc->dims[2]; + if (!args_ok) return invalid_arguments; + + // * on dic + args_ok = true + && DIC == weights_layer_desc->dims[4] + && DIC == weights_iter_desc->dims[4] + && IMPLICATION(!is_zero_md(bias_desc), DIC == bias_desc->dims[3]) + && IMPLICATION(!is_zero_md(dst_iter_desc), + DIC == dst_iter_desc->dims[4]); + if (!args_ok) return invalid_arguments; + + // * unrolling/fusion conditions + args_ok = true + && IMPLICATION(L > 1, (dlc_multiplier * SLC) == DLC) + && IMPLICATION(T > 1, SIC == DIC); + if (!args_ok) return invalid_arguments; + + return success; +} + +status_t MKLDNN_API mkldnn_rnn_forward_desc_init(mkldnn_rnn_desc_t *rnn_desc, + prop_kind_t prop_kind, const rnn_cell_desc_t *rnn_cell_desc, + const rnn_direction_t direction, const memory_desc_t *src_layer_desc, + const memory_desc_t *src_iter_desc, + const memory_desc_t *weights_layer_desc, + const memory_desc_t *weights_iter_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_layer_desc, + const memory_desc_t *dst_iter_desc) { + bool args_ok = true && rnn_cell_desc != nullptr + && !any_null(src_layer_desc, weights_layer_desc, weights_iter_desc, + dst_layer_desc); + if (!args_ok) return invalid_arguments; + + //check dimensions consistency + int L = weights_layer_desc->dims[0]; + int T = src_layer_desc->dims[0]; + int N = src_layer_desc->dims[1]; + const int D = one_of(direction, mkldnn_unidirectional_left2right, + mkldnn_unidirectional_right2left) ? + 1 : + 2; + int G = mkldnn_rnn_cell_get_gates_count(rnn_cell_desc); + int S = mkldnn_rnn_cell_get_states_count(rnn_cell_desc); + int SLC = src_layer_desc->dims[2]; + int SIC = weights_iter_desc->dims[2]; + int DLC = dst_layer_desc->dims[2]; + int DIC = weights_layer_desc->dims[4]; + + CHECK(check_dim_consistency(rnn_cell_desc, direction, L, D, T, N, S, + G, SLC, SIC, DLC, DIC, src_layer_desc, src_iter_desc, + weights_layer_desc, weights_iter_desc, bias_desc, dst_layer_desc, + dst_iter_desc)); + + CHECK(check_data_type_consistency_fwd(rnn_cell_desc, prop_kind, + src_layer_desc, src_iter_desc, weights_layer_desc, + weights_iter_desc, bias_desc, dst_layer_desc, dst_iter_desc)); + + // Create the descriptor + mkldnn_rnn_desc_t rd = zero_rnn_desc(); + + rd.primitive_kind = primitive_kind::rnn; + rd.prop_kind = prop_kind; + rd.cell_desc = *rnn_cell_desc; + rd.direction = direction; + rd.src_layer_desc = copy_maybe_null(src_layer_desc); + rd.src_iter_desc = copy_maybe_null(src_iter_desc); + rd.weights_layer_desc = copy_maybe_null(weights_layer_desc); + rd.weights_iter_desc = copy_maybe_null(weights_iter_desc); + rd.bias_desc = copy_maybe_null(bias_desc); + rd.dst_layer_desc = copy_maybe_null(dst_layer_desc); + rd.dst_iter_desc = copy_maybe_null(dst_iter_desc); + + *rnn_desc = rd; + + return success; +} + +status_t MKLDNN_API mkldnn_rnn_backward_desc_init(mkldnn_rnn_desc_t *rnn_desc, + prop_kind_t prop_kind, const rnn_cell_desc_t *rnn_cell_desc, + const rnn_direction_t direction, const memory_desc_t *src_layer_desc, + const memory_desc_t *src_iter_desc, + const memory_desc_t *weights_layer_desc, + const memory_desc_t *weights_iter_desc, const memory_desc_t *bias_desc, + const memory_desc_t *dst_layer_desc, const memory_desc_t *dst_iter_desc, + const memory_desc_t *diff_src_layer_desc, + const memory_desc_t *diff_src_iter_desc, + const memory_desc_t *diff_weights_layer_desc, + const memory_desc_t *diff_weights_iter_desc, + const memory_desc_t *diff_bias_desc, + const memory_desc_t *diff_dst_layer_desc, + const memory_desc_t *diff_dst_iter_desc) { + bool args_ok = true + && !any_null(src_layer_desc, weights_layer_desc, weights_iter_desc, + dst_layer_desc, diff_src_layer_desc, + diff_weights_layer_desc, diff_weights_iter_desc, + diff_dst_layer_desc); + if (!args_ok) + return invalid_arguments; + + auto xnor_md = [=](const memory_desc_t *a_md, const memory_desc_t *b_md) { + return is_zero_md(a_md) == is_zero_md(b_md); + }; + + args_ok = args_ok && xnor_md(bias_desc, diff_bias_desc) + && xnor_md(dst_iter_desc, diff_dst_iter_desc) + && xnor_md(src_iter_desc, diff_src_iter_desc); + if (!args_ok) + return invalid_arguments; + + //check dimensions consistency + int L = weights_layer_desc->dims[0]; + int T = src_layer_desc->dims[0]; + int N = src_layer_desc->dims[1]; + const int D = one_of(direction, mkldnn_unidirectional_left2right, + mkldnn_unidirectional_right2left) ? + 1 : + 2; + int G = mkldnn_rnn_cell_get_gates_count(rnn_cell_desc); + int S = mkldnn_rnn_cell_get_states_count(rnn_cell_desc); + int SLC = src_layer_desc->dims[2]; + int SIC = weights_iter_desc->dims[2]; + int DLC = dst_layer_desc->dims[2]; + int DIC = weights_layer_desc->dims[4]; + + status_t st = check_dim_consistency(rnn_cell_desc, direction, L, D, T, N, S, + G, SLC, SIC, DLC, DIC, src_layer_desc, src_iter_desc, + weights_layer_desc, weights_iter_desc, bias_desc, dst_layer_desc, + dst_iter_desc); + if (st != success) return st; + + st = check_dim_consistency(rnn_cell_desc, direction, L, D, T, N, S, + G, SLC, SIC, DLC, DIC, diff_src_layer_desc, diff_src_iter_desc, + diff_weights_layer_desc, diff_weights_iter_desc, diff_bias_desc, + diff_dst_layer_desc, diff_dst_iter_desc); + if (st != success) return st; + + mkldnn_rnn_desc_t rd = zero_rnn_desc(); + + rd.primitive_kind = primitive_kind::rnn; + rd.prop_kind = prop_kind; + rd.cell_desc = *rnn_cell_desc; + rd.direction = direction; + + rd.src_layer_desc = copy_maybe_null(src_layer_desc); + rd.src_iter_desc = copy_maybe_null(src_iter_desc); + rd.weights_layer_desc = copy_maybe_null(weights_layer_desc); + rd.weights_iter_desc = copy_maybe_null(weights_iter_desc); + rd.bias_desc = copy_maybe_null(bias_desc); + rd.dst_layer_desc = copy_maybe_null(dst_layer_desc); + rd.dst_iter_desc = copy_maybe_null(dst_iter_desc); + rd.diff_src_layer_desc = copy_maybe_null(diff_src_layer_desc); + rd.diff_src_iter_desc = copy_maybe_null(diff_src_iter_desc); + rd.diff_weights_layer_desc = copy_maybe_null(diff_weights_layer_desc); + rd.diff_weights_iter_desc = copy_maybe_null(diff_weights_iter_desc); + rd.diff_bias_desc = copy_maybe_null(diff_bias_desc); + rd.diff_dst_layer_desc = copy_maybe_null(diff_dst_layer_desc); + rd.diff_dst_iter_desc = copy_maybe_null(diff_dst_iter_desc); + + *rnn_desc = rd; + + return success; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/rnn_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/rnn_pd.hpp new file mode 100644 index 000000000000..1ee2ba11143e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/rnn_pd.hpp @@ -0,0 +1,280 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef RNN_PD_HPP +#define RNN_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" +#include "type_helpers.hpp" + +namespace mkldnn { +namespace impl { + +struct rnn_fwd_pd_t; + +struct rnn_pd_t : public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::rnn; + + rnn_pd_t(engine_t *engine, + const rnn_desc_t *adesc, + const primitive_attr_t *attr, + const rnn_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , src_layer_md_(desc_.src_layer_desc) + , src_iter_md_(desc_.src_iter_desc) + , weights_layer_md_(desc_.weights_layer_desc) + , weights_iter_md_(desc_.weights_iter_desc) + , bias_md_(desc_.bias_desc) + , dst_layer_md_(desc_.dst_layer_desc) + , dst_iter_md_(desc_.dst_iter_desc) + , ws_md_() + {} + + const rnn_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::rnn_d: *(const rnn_desc_t **)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + virtual const memory_desc_t *src_md(int index = 0) const override { + if (index == 0) return &src_layer_md_; + if (index == 1 && with_src_iter()) return &src_iter_md_; + return nullptr; + } + virtual const memory_desc_t *weights_md(int index = 0) const override { + if (index == 0) return &weights_layer_md_; + if (index == 1) return &weights_iter_md_; + if (index == 2 && with_bias()) return &bias_md_; + return nullptr; + } + virtual const memory_desc_t *dst_md(int index = 0) const override { + if (index == 0) return &dst_layer_md_; + if (index == 1 && with_dst_iter()) return &dst_iter_md_; + return nullptr; + } + virtual const memory_desc_t *workspace_md(int index = 0) const override + { return index == 0 && !types::is_zero_md(&ws_md_) ? &ws_md_ : nullptr; } + + /* common pooling aux functions */ + + bool is_training() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::backward); + } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + + dim_t T() const { return desc_.src_layer_desc.dims[0]; } + dim_t MB() const { return desc_.src_layer_desc.dims[1]; } + + dim_t L() const { return desc_.weights_layer_desc.dims[0]; } + dim_t D() const { return desc_.weights_layer_desc.dims[1]; } + + dim_t SIC() const { return desc_.weights_iter_desc.dims[2]; } + + dim_t SLC() const { return desc_.weights_layer_desc.dims[2]; } + dim_t G() const { return desc_.weights_layer_desc.dims[3]; } + dim_t DIC() const { return desc_.weights_layer_desc.dims[4]; } + + dim_t DLC() const { return desc_.dst_layer_desc.dims[2]; } + + bool with_bias() const + { return !memory_desc_wrapper(desc_.bias_desc).is_zero(); } + + bool with_src_iter() const + { return !(memory_desc_wrapper(desc_.src_iter_desc).is_zero()); } + + bool with_dst_iter() const + { return !memory_desc_wrapper(desc_.dst_iter_desc).is_zero(); } + + mkldnn::impl::alg_kind_t cell_kind() const + { return desc_.cell_desc.cell_kind; } + mkldnn::impl::alg_kind_t activation_kind() const + { return desc_.cell_desc.activation_kind; } + + bool is_lbr() const + { return cell_kind() == mkldnn_gru_linear_before_reset; } + + mkldnn_rnn_direction_t direction() const { return desc_.direction; } + +protected: + rnn_desc_t desc_; + const rnn_fwd_pd_t *hint_fwd_pd_; + + memory_desc_t src_layer_md_; + memory_desc_t src_iter_md_; + memory_desc_t weights_layer_md_; + memory_desc_t weights_iter_md_; + memory_desc_t bias_md_; + memory_desc_t dst_layer_md_; + memory_desc_t dst_iter_md_; + + memory_desc_t ws_md_; +}; + +struct rnn_fwd_pd_t: public rnn_pd_t { + typedef rnn_fwd_pd_t base_class; + typedef rnn_fwd_pd_t hint_class; + + rnn_fwd_pd_t(engine_t *engine, + const rnn_desc_t *adesc, + const primitive_attr_t *attr, + const rnn_fwd_pd_t *hint_fwd_pd) + : rnn_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_SRC_LAYER) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_SRC_ITER && with_src_iter()) + return arg_usage_t::input; + + if (utils::one_of(arg, MKLDNN_ARG_WEIGHTS_LAYER, + MKLDNN_ARG_WEIGHTS_ITER)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_BIAS && with_bias()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST_LAYER) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_DST_ITER && with_dst_iter()) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && is_training()) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual int n_inputs() const override + { return 3 + with_bias() + with_src_iter(); } + virtual int n_outputs() const override + { return 1 + with_dst_iter() + is_training(); } +}; + +struct rnn_bwd_pd_t : public rnn_pd_t { + typedef rnn_bwd_pd_t base_class; + typedef rnn_fwd_pd_t hint_class; + + rnn_bwd_pd_t(engine_t *engine, + const rnn_desc_t *adesc, + const primitive_attr_t *attr, + const rnn_fwd_pd_t *hint_fwd_pd) + : rnn_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_src_layer_md_(desc_.diff_src_layer_desc) + , diff_src_iter_md_(desc_.diff_src_iter_desc) + , diff_weights_layer_md_(desc_.diff_weights_layer_desc) + , diff_weights_iter_md_(desc_.diff_weights_iter_desc) + , diff_bias_md_(desc_.diff_bias_desc) + , diff_dst_layer_md_(desc_.diff_dst_layer_desc) + , diff_dst_iter_md_(desc_.diff_dst_iter_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_SRC_LAYER, MKLDNN_ARG_DST_LAYER, + MKLDNN_ARG_DIFF_DST_LAYER)) + return arg_usage_t::input; + + if (with_src_iter()) { + if (arg == MKLDNN_ARG_SRC_ITER) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC_ITER) + return arg_usage_t::output; + } + + if (utils::one_of(arg, MKLDNN_ARG_WEIGHTS_LAYER, + MKLDNN_ARG_WEIGHTS_ITER)) + return arg_usage_t::input; + + if (with_bias()) { + if (arg == MKLDNN_ARG_BIAS) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_BIAS) + return arg_usage_t::output; + } + + if (utils::one_of(arg, MKLDNN_ARG_DST_ITER, MKLDNN_ARG_DIFF_DST_ITER) + && with_dst_iter()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_WORKSPACE) + return arg_usage_t::input; + + if (utils::one_of(arg, MKLDNN_ARG_DIFF_SRC_LAYER, + MKLDNN_ARG_DIFF_WEIGHTS_LAYER, + MKLDNN_ARG_DIFF_WEIGHTS_ITER)) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *diff_src_md(int index = 0) const override { + if (index == 0) return &diff_src_layer_md_; + if (index == 1 && with_src_iter()) return &diff_src_iter_md_; + return nullptr; + } + virtual const memory_desc_t *diff_weights_md( + int index = 0) const override { + if (index == 0) return &diff_weights_layer_md_; + if (index == 1) return &diff_weights_iter_md_; + if (index == 2 && with_bias()) return &diff_bias_md_; + return nullptr; + } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override { + if (index == 0) return &diff_dst_layer_md_; + if (index == 1 && with_dst_iter()) return &diff_dst_iter_md_; + return nullptr; + } + + virtual int n_inputs() const override + { return 6 + with_src_iter() + with_bias() + 2 * with_dst_iter(); } + virtual int n_outputs() const override + { return 3 + with_src_iter() + with_bias(); } + +protected: + memory_desc_t diff_src_layer_md_; + memory_desc_t diff_src_iter_md_; + memory_desc_t diff_weights_layer_md_; + memory_desc_t diff_weights_iter_md_; + memory_desc_t diff_bias_md_; + memory_desc_t diff_dst_layer_md_; + memory_desc_t diff_dst_iter_md_; +}; + +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/scratchpad.cpp b/thirdparty/oidn/mkl-dnn/src/common/scratchpad.cpp new file mode 100644 index 000000000000..6bc14fc72a67 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/scratchpad.cpp @@ -0,0 +1,112 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "scratchpad.hpp" + +namespace mkldnn { +namespace impl { + +/* Allocating memory buffers on a page boundary to reduce TLB/page misses */ +const size_t page_size = 2097152; + +/* + Implementation of the scratchpad_t interface that is compatible with + a concurrent execution +*/ +struct concurent_scratchpad_t : public scratchpad_t { + concurent_scratchpad_t(size_t size) { + size_ = size; + scratchpad_ = (char *) malloc(size, page_size); + assert(scratchpad_ != nullptr); + } + + ~concurent_scratchpad_t() { + free(scratchpad_); + } + + virtual char *get() const { + return scratchpad_; + } + +private: + char *scratchpad_; + size_t size_; +}; + +/* + Implementation of the scratchpad_t interface that uses a global + scratchpad +*/ + +struct global_scratchpad_t : public scratchpad_t { + global_scratchpad_t(size_t size) { + if (size > size_) { + if (scratchpad_ != nullptr) free(scratchpad_); + size_ = size; + scratchpad_ = (char *) malloc(size, page_size); + assert(scratchpad_ != nullptr); + } + reference_count_++; + } + + ~global_scratchpad_t() { + reference_count_--; + if (reference_count_ == 0) { + free(scratchpad_); + scratchpad_ = nullptr; + size_ = 0; + } + } + + virtual char *get() const { + return scratchpad_; + } + +private: + /* + Using thread-local here is unnecessary and even buggy! All threads + actually share the same scratchpad, which is created and queried only + on the main thread. If the scratchpad is queried on some thread other + than the one it was created on (e.g. the application calls the API from + multiple threads), thread-local causes a segfault because the scratchpad + is uninitialized on the current thread. + */ + /*thread_local*/ static char *scratchpad_; + /*thread_local*/ static size_t size_; + /*thread_local*/ static unsigned int reference_count_; +}; + +/*thread_local*/ char *global_scratchpad_t::scratchpad_ = nullptr; +/*thread_local*/ size_t global_scratchpad_t::size_ = 0; +/*thread_local*/ unsigned int global_scratchpad_t::reference_count_ = 0; + + +/* + Scratchpad creation routine +*/ +scratchpad_t *create_scratchpad(size_t size) { +#ifndef MKLDNN_ENABLE_CONCURRENT_EXEC + return new global_scratchpad_t(size); +#else + return new concurent_scratchpad_t(size); +#endif +} + +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/scratchpad.hpp b/thirdparty/oidn/mkl-dnn/src/common/scratchpad.hpp new file mode 100644 index 000000000000..f7a246bc9926 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/scratchpad.hpp @@ -0,0 +1,36 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef COMMON_SCRATCHPAD_HPP +#define COMMON_SCRATCHPAD_HPP + +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct scratchpad_t { + virtual ~scratchpad_t() {} + virtual char *get() const = 0; +}; + +scratchpad_t *create_scratchpad(size_t size); + +} +} +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/shuffle.cpp b/thirdparty/oidn/mkl-dnn/src/common/shuffle.cpp new file mode 100644 index 000000000000..e32e7352246d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/shuffle.cpp @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t shuffle_desc_init(shuffle_desc_t *shuffle_desc, prop_kind_t prop_kind, + const memory_desc_t *data_desc, int axis, dim_t group_size) { + bool args_ok = true + && !any_null(shuffle_desc, data_desc) + && one_of(prop_kind, forward_training, forward_inference, + backward, backward_data) + && axis >= 0 && axis < data_desc->ndims + && group_size > 0 && group_size <= data_desc->dims[axis]; + if (!args_ok) return invalid_arguments; + + auto sd = shuffle_desc_t(); + sd.primitive_kind = primitive_kind::shuffle; + sd.prop_kind = prop_kind; + sd.data_desc = *data_desc; + sd.axis = axis; + sd.group_size = group_size; + + bool consistency = true + && sd.data_desc.dims[axis] % sd.group_size == 0; + if (!consistency) return invalid_arguments; + + *shuffle_desc = sd; + return success; +} +} + +status_t mkldnn_shuffle_forward_desc_init(shuffle_desc_t *shuffle_desc, + prop_kind_t prop_kind, const memory_desc_t *data_desc, int axis, + dim_t group_size) { + if (!one_of(prop_kind, forward_training, forward_inference)) + return invalid_arguments; + return shuffle_desc_init(shuffle_desc, prop_kind, data_desc, axis, + group_size); +} + +status_t mkldnn_shuffle_backward_desc_init(shuffle_desc_t *shuffle_desc, + const memory_desc_t *diff_data_desc, int axis, dim_t group_size) { + return shuffle_desc_init(shuffle_desc, backward_data, diff_data_desc, axis, + group_size); +} + +// vim: et ts=5 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/shuffle_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/shuffle_pd.hpp new file mode 100644 index 000000000000..cc5553fe7fe9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/shuffle_pd.hpp @@ -0,0 +1,121 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef SHUFFLE_PD_HPP +#define SHUFFLE_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" + +namespace mkldnn { +namespace impl { + +struct shuffle_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::shuffle; + + typedef shuffle_pd_t base_class; + typedef shuffle_pd_t hint_class; + + shuffle_pd_t(engine_t *engine, + const shuffle_desc_t *adesc, + const primitive_attr_t *attr, + const shuffle_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , data_md_(desc_.data_desc) + {} + + const shuffle_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::shuffle_d: + *(const shuffle_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (is_fwd()) { + if (arg == MKLDNN_ARG_SRC) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + } else { + if (arg == MKLDNN_ARG_DIFF_DST) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + } + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 && is_fwd() ? &data_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 && is_fwd() ? &data_md_ : nullptr; } + + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 && !is_fwd() ? &data_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 && !is_fwd() ? &data_md_ : nullptr; } + + virtual int n_inputs() const override { return 1; } + virtual int n_outputs() const override { return 1; } + + /* shuffle aux functions */ + + dim_t MB() const { return data_md()->dims[0]; } + dim_t C() const { return ndims() >= 2 ? data_md()->dims[1] : 1; } + dim_t D() const { return ndims() >= 5 ? data_md()->dims[ndims() - 3] : 1; } + dim_t H() const { return ndims() >= 4 ? data_md()->dims[ndims() - 2] : 1; } + dim_t W() const { return ndims() >= 3 ? data_md()->dims[ndims() - 1] : 1; } + + int ndims() const { return data_md()->ndims; } + + int axis() const { return desc_.axis; } + dim_t group_size() const { return desc_.group_size; } + dim_t axis_size() const { return data_md()->dims[axis()]; } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + + const memory_desc_t *data_md() const { return &data_md_; } + +protected: + shuffle_desc_t desc_; + const shuffle_pd_t *hint_fwd_pd_; + memory_desc_t data_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/softmax.cpp b/thirdparty/oidn/mkl-dnn/src/common/softmax.cpp new file mode 100644 index 000000000000..82848e3d1f03 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/softmax.cpp @@ -0,0 +1,68 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "memory_desc_wrapper.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::alg_kind; +using namespace mkldnn::impl::types; + +namespace { +status_t softmax_desc_init(softmax_desc_t *softmax_desc, prop_kind_t prop_kind, + const memory_desc_t *data_desc, const memory_desc_t *diff_desc, int softmax_axis) { + bool args_ok = true + && !any_null(softmax_desc, data_desc) + && 0 <= softmax_axis + && softmax_axis < data_desc->ndims; + if (!args_ok) return invalid_arguments; + + auto sd = softmax_desc_t(); + sd.primitive_kind = primitive_kind::softmax; + sd.prop_kind = prop_kind; + + bool is_bwd = (sd.prop_kind == backward_data); + sd.data_desc = *data_desc; + sd.diff_desc = is_bwd ? *diff_desc : zero_md(); + sd.softmax_axis = softmax_axis; + + *softmax_desc = sd; + return success; +} +} + +status_t mkldnn_softmax_forward_desc_init(softmax_desc_t *softmax_desc, + prop_kind_t prop_kind, const memory_desc_t *data_desc, + int softmax_axis) { + if (!one_of(prop_kind, forward_inference, forward_training)) + return invalid_arguments; + return softmax_desc_init(softmax_desc, prop_kind, data_desc, nullptr, softmax_axis); +} + +status_t mkldnn_softmax_backward_desc_init(softmax_desc_t *softmax_desc, + const memory_desc_t *diff_desc, const mkldnn_memory_desc_t *data_desc, + int softmax_axis) { + return softmax_desc_init(softmax_desc, prop_kind::backward_data, + data_desc, diff_desc, softmax_axis); +} +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/softmax_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/softmax_pd.hpp new file mode 100644 index 000000000000..8a16ce901ca4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/softmax_pd.hpp @@ -0,0 +1,161 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef SOFTMAX_PD_HPP +#define SOFTMAX_PD_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "primitive_desc.hpp" + +namespace mkldnn { +namespace impl { + +struct softmax_fwd_pd_t; + +struct softmax_pd_t: public primitive_desc_t { + static constexpr auto base_pkind = primitive_kind::softmax; + + softmax_pd_t(engine_t *engine, + const softmax_desc_t *adesc, + const primitive_attr_t *attr, + const softmax_fwd_pd_t *hint_fwd_pd) + : primitive_desc_t(engine, attr, base_pkind) + , desc_(*adesc) + , hint_fwd_pd_(hint_fwd_pd) + , data_md_(desc_.data_desc) + {} + + const softmax_desc_t *desc() const { return &desc_; } + virtual const op_desc_t *op_desc() const override + { return reinterpret_cast(this->desc()); } + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual status_t query(query_t what, int idx, void *result) const override { + switch (what) { + case query::softmax_d: + *(const softmax_desc_t**)result = desc(); break; + default: return primitive_desc_t::query(what, idx, result); + } + return status::success; + } + + /* common softmax aux functions */ + + dim_t MB() const { return data_desc().dims[0]; } + dim_t C() const { return data_desc().dims[1]; } + dim_t D() const { return ndims() >= 5 ? data_desc().dims[ndims() - 3] : 1; } + dim_t H() const { return ndims() >= 4 ? data_desc().dims[ndims() - 2] : 1; } + dim_t W() const { return ndims() >= 3 ? data_desc().dims[ndims() - 1] : 1; } + + int ndims() const { return data_desc().ndims; } + + bool is_fwd() const { + return utils::one_of(desc_.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + } + +protected: + softmax_desc_t desc_; + const softmax_fwd_pd_t *hint_fwd_pd_; + + memory_desc_t data_md_; + +private: + const memory_desc_t &data_desc() const { return desc_.data_desc; } +}; + +struct softmax_fwd_pd_t: public softmax_pd_t { + typedef softmax_fwd_pd_t base_class; + typedef softmax_fwd_pd_t hint_class; + + softmax_fwd_pd_t(engine_t *engine, + const softmax_desc_t *adesc, + const primitive_attr_t *attr, + const softmax_fwd_pd_t *hint_fwd_pd) + : softmax_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg == MKLDNN_ARG_SRC) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && (workspace_md() != nullptr)) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + + virtual int n_inputs() const override { return 1; } + virtual int n_outputs() const override + { return 1 + (workspace_md() != nullptr); } +}; + +struct softmax_bwd_pd_t: public softmax_pd_t { + typedef softmax_bwd_pd_t base_class; + typedef softmax_fwd_pd_t hint_class; + + softmax_bwd_pd_t(engine_t *engine, + const softmax_desc_t *adesc, + const primitive_attr_t *attr, + const softmax_fwd_pd_t *hint_fwd_pd) + : softmax_pd_t(engine, adesc, attr, hint_fwd_pd) + , diff_data_md_(desc_.diff_desc) + {} + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (utils::one_of(arg, MKLDNN_ARG_DST, MKLDNN_ARG_DIFF_DST)) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DIFF_SRC) + return arg_usage_t::output; + + if (arg == MKLDNN_ARG_WORKSPACE && (workspace_md() != nullptr)) + return arg_usage_t::input; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &data_md_ : nullptr; } + virtual const memory_desc_t *diff_dst_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + virtual const memory_desc_t *diff_src_md(int index = 0) const override + { return index == 0 ? &diff_data_md_ : nullptr; } + + virtual int n_inputs() const override + { return 2 + (workspace_md() != nullptr); } + virtual int n_outputs() const override { return 1; } + +protected: + memory_desc_t diff_data_md_; +}; + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/stream.cpp b/thirdparty/oidn/mkl-dnn/src/common/stream.cpp new file mode 100644 index 000000000000..00af8935c027 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/stream.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "stream.hpp" +#include "utils.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::status; + +/* API */ + +status_t mkldnn_stream_create(stream_t **stream, engine_t *engine, + unsigned flags) { + bool args_ok = true + && !utils::any_null(stream, engine) + && flags == stream_flags::default_flags; + if (!args_ok) + return invalid_arguments; + + return safe_ptr_assign(*stream, new stream_t(engine, flags)); +} + +status_t mkldnn_stream_destroy(stream_t *stream) { + delete stream; + return success; +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/stream.hpp b/thirdparty/oidn/mkl-dnn/src/common/stream.hpp new file mode 100644 index 000000000000..f010e5f6ed8f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/stream.hpp @@ -0,0 +1,44 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef STREAM_HPP +#define STREAM_HPP + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" + +struct mkldnn_stream: public mkldnn::impl::c_compatible { + mkldnn_stream(mkldnn::impl::engine_t *engine, unsigned flags) + : engine_(engine), flags_(flags) {} + virtual ~mkldnn_stream() {} + + /** returns stream's engine */ + mkldnn::impl::engine_t *engine() const { return engine_; } + + /** returns stream's kind */ + unsigned flags() const { return flags_; } + +protected: + mkldnn::impl::engine_t *engine_; + unsigned flags_; +}; + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/sum.cpp b/thirdparty/oidn/mkl-dnn/src/common/sum.cpp new file mode 100644 index 000000000000..365663c0f86e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/sum.cpp @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "engine.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "sum_pd.hpp" + +using namespace mkldnn::impl; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::status; + +status_t mkldnn_sum_primitive_desc_create(primitive_desc_t **sum_pd, + const memory_desc_t *dst_md, int n, const float *scales, + const memory_desc_t *src_mds, const primitive_attr_t *attr, + engine_t *engine) { + bool args_ok = !any_null(sum_pd, src_mds, scales) && n > 0; + if (!args_ok) return invalid_arguments; + + const primitive_attr_t dummy_attr; + if (attr == NULL) + attr = &dummy_attr; + + const int ndims = src_mds[0].ndims; + const dims_t &dims = src_mds[0].dims; + const data_type_t dt = src_mds[0].data_type; + + for (int i = 1; i < n; ++i) { + if (src_mds[i].ndims != ndims) return invalid_arguments; + for (int d = 0; d < ndims; ++d) { + if (src_mds[i].dims[d] != dims[d]) + return invalid_arguments; + } + if (src_mds[i].data_type != dt) return invalid_arguments; + } + + memory_desc_t dummy_dst_md; + if (dst_md) { + if (dst_md->ndims != ndims) return invalid_arguments; + for (int d = 0; d < ndims; ++d) { + if (dst_md->dims[d] != dims[d]) + return invalid_arguments; + } + } else { + dummy_dst_md = src_mds[0]; + dummy_dst_md.format_kind = format_kind::any; + dst_md = &dummy_dst_md; + } + + auto s_pd = reinterpret_cast(sum_pd); + + for (auto s = engine->get_sum_implementation_list(); *s; ++s) { + if ((*s)(s_pd, engine, attr, dst_md, n, scales, src_mds) == success) { + (*s_pd)->init_info(); + (*s_pd)->init_scratchpad_md(); + return success; + } + } + return unimplemented; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/sum_pd.hpp b/thirdparty/oidn/mkl-dnn/src/common/sum_pd.hpp new file mode 100644 index 000000000000..80254667df64 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/sum_pd.hpp @@ -0,0 +1,143 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef SUM_PD_HPP +#define SUM_PD_HPP + +#include +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "primitive_desc.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +struct sum_pd_t: public primitive_desc_t { + sum_pd_t(engine_t *engine, const primitive_attr_t *attr, + const memory_desc_t *dst_md, int n, const float *scales, + const memory_desc_t *src_mds) + : primitive_desc_t(engine, attr, primitive_kind::sum) + , n_(n), dst_md_(*dst_md) + { + scales_.reserve(n_); + for (int i = 0; i < n_; ++i) scales_.push_back(scales[i]); + src_mds_.reserve(n_); + for (int i = 0; i < n_; ++i) src_mds_.push_back(src_mds[i]); + } + + virtual void init_info() override { impl::init_info(this, this->info_); } + + virtual arg_usage_t arg_usage(primitive_arg_index_t arg) const override { + if (arg >= MKLDNN_ARG_MULTIPLE_SRC + && arg < MKLDNN_ARG_MULTIPLE_SRC + n_inputs()) + return arg_usage_t::input; + + if (arg == MKLDNN_ARG_DST) + return arg_usage_t::output; + + return primitive_desc_t::arg_usage(arg); + } + + virtual const memory_desc_t *src_md(int index = 0) const override + { return index < n_inputs() ? &src_mds_[index] : nullptr; } + virtual const memory_desc_t *dst_md(int index = 0) const override + { return index == 0 ? &dst_md_ : nullptr; } + + virtual int n_inputs() const override { return n_; } + virtual int n_outputs() const override { return 1; } + + const float *scales() const { return &scales_[0]; } + +protected: + int n_; + nstl::vector scales_; + memory_desc_t dst_md_; + nstl::vector src_mds_; + +protected: + /* inits dst_md_ in simple cases. The call may fail. */ + status_t init() { + for (int i = 0; i < n_; ++i) { + const memory_desc_wrapper src_d(&src_mds_[i]); + if (!src_d.is_blocking_desc() || src_d.is_additional_buffer()) + return status::unimplemented; + } + bool ok = true + && set_default_params() == status::success + && attr()->has_default_values(); + return ok ? status::success : status::unimplemented; + } + + status_t set_default_params() { + if (dst_md_.format_kind != format_kind::any) + return status::success; + + /* The stupidest ever heuristics (but not the same as we had before): + * - Pick the first non-plain format; + * - If all formats are plain, pick the format of the first input + */ + for (int i = 0; i < n_; ++i) { + const memory_desc_wrapper src_d(src_mds_[i]); + if (!src_d.is_plain() && src_d.is_blocking_desc()) { + return memory_desc_init_by_blocking_desc(dst_md_, + src_d.blocking_desc()); + } + } + + if (src_mds_[0].format_kind != format_kind::blocked) + return status::unimplemented; + + dst_md_ = src_mds_[0]; + + return status::success; + } +}; + +#define DECLARE_SUM_PD_t(impl_name, ...) \ + static status_t create(sum_pd_t **sum_pd, \ + engine_t *engine, const primitive_attr_t *attr, \ + const memory_desc_t *dst_md, int n, const float *scales, \ + const memory_desc_t *src_mds) { \ + using namespace status; \ + auto _pd = new pd_t(engine, attr, dst_md, n, scales, src_mds); \ + if (_pd == nullptr) return out_of_memory; \ + if (_pd->init() != success) { delete _pd; return unimplemented; } \ + return safe_ptr_assign(*sum_pd, _pd); \ + } \ + virtual status_t create_primitive(primitive_t **p) const override { \ + double ms = get_msec(); \ + auto ret = safe_ptr_assign(*p, new (__VA_ARGS__)(this)); \ + ms = get_msec() - ms; \ + if (mkldnn_verbose()->level >= 2) { \ + printf("mkldnn_verbose,create,%s,%g\n", this->info(), ms); \ + fflush(0); \ + } \ + return ret; \ + } \ + virtual pd_t *clone() const override { return new pd_t(*this); } \ + virtual const char *name() const override { return impl_name; } \ + +#define DECLARE_SUM_PD_T(impl_name, ...) \ + DECLARE_SUM_PD_t(impl_name, __VA_ARGS__) + +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/tag_traits.hpp b/thirdparty/oidn/mkl-dnn/src/common/tag_traits.hpp new file mode 100644 index 000000000000..a408f45980b3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/tag_traits.hpp @@ -0,0 +1,200 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef TAG_TRAITS_HPP +#define TAG_TRAITS_HPP + +#include + +#include "c_types_map.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +enum class block_dim_t { + _, + _A, _B, + _AB, _BC, +}; + +enum class inner_blk_t { + _, + _4a, _4b, + _8a, _8b, + _16a, _16b, + + _4b4a, _4b4c, _4c4b, + _8a8b, _8b8a, _8b8c, _8c8b, + _16a16b, _16a4b, _16b16a, _16b4c, _16b16c, _16c16b, + + _2c8b4c, _8a16b2a, _4b16a4b, _8b16a2b, _8b16c2b, _4c16b4c, _8c16b2c, +}; + +/** returns the offset within the block for weights blocked over oc and ic */ +template +constexpr int AB_or_BC_blk_off(int x0, int x1) { + using ib = inner_blk_t; + static_assert(utils::one_of(f, ib::_4b4a, ib::_4b4c, ib::_4c4b, ib::_8a8b, + ib::_8b8a, ib::_8b8c, ib::_8c8b, ib::_16a16b, ib::_16a4b, + ib::_16b16a, ib::_16b4c, ib::_16b16c, ib::_16c16b, ib::_2c8b4c, + ib::_8a16b2a, ib::_4b16a4b, ib::_8b16a2b, ib::_8b16c2b, + ib::_4c16b4c, ib::_8c16b2c), + "unexpected inner_blk format"); + return false ? 0 + : (f == ib::_4b4c) ? 4 * x0 + x1 + : (f == ib::_4b4a || f == ib::_4c4b) ? 4 * x1 + x0 + : (f == ib::_8a8b || f == ib::_8b8c) ? 8 * x0 + x1 + : (f == ib::_8b8a || f == ib::_8c8b) ? 8 * x1 + x0 + : (f == ib::_16a16b || f == ib::_16b16c) ? 16 * x0 + x1 + : (f == ib::_16b16a || f == ib::_16c16b) ? 16 * x1 + x0 + : (f == ib::_16a4b || f == ib::_16b4c) ? 4 * x0 + x1 + : (f == ib::_8a16b2a || f == ib::_8b16c2b) ? (x0 / 2) * 32 + x1 * 2 + x0 % 2 + : (f == ib::_4b16a4b || f == ib::_4c16b4c) ? (x1 / 4) * 64 + x0 * 4 + x1 % 4 + : (f == ib::_8b16a2b || f == ib::_8c16b2c) ? (x1 / 2) * 32 + x0 * 2 + x1 % 2 + : (f == ib::_2c8b4c) ? (x1 / 4) * 32 + x0 * 4 + x1 % 4 + : INT_MIN; +} + +template struct inner_blk_traits { + using ib = inner_blk_t; +}; + +template struct tag_traits { + // block_dim_t block_dims; + // inner_blk_t inner_blks; + // int ndims; +}; + +#define DECL_TRAITS(_tag, _blk_fmt, _inner_blk, _ndims) \ +template <> struct tag_traits { \ + static constexpr block_dim_t block_dims = block_dim_t::_blk_fmt; \ + static constexpr inner_blk_t inner_blks = inner_blk_t::_inner_blk; \ + static constexpr int ndims = _ndims; \ +} + +DECL_TRAITS(a, _, _, 1); +DECL_TRAITS(ab, _, _, 2); +DECL_TRAITS(abc, _, _, 3); +DECL_TRAITS(abcd, _, _, 4); +DECL_TRAITS(abcde, _, _, 5); +DECL_TRAITS(abcdef, _, _, 6); +DECL_TRAITS(abdec, _, _, 5); +DECL_TRAITS(acb, _, _, 3); +DECL_TRAITS(acbde, _, _, 5); +DECL_TRAITS(acdb, _, _, 4); +DECL_TRAITS(acdeb, _, _, 5); +DECL_TRAITS(ba, _, _, 2); +DECL_TRAITS(bac, _, _, 3); +DECL_TRAITS(bacd, _, _, 4); +DECL_TRAITS(bcda, _, _, 4); +DECL_TRAITS(cba, _, _, 3); +DECL_TRAITS(cdba, _, _, 4); +DECL_TRAITS(cdeba, _, _, 5); +DECL_TRAITS(decab, _, _, 5); + +DECL_TRAITS(Abc4a, _A, _4a, 3); +DECL_TRAITS(aBc4b, _B, _4b, 3); +DECL_TRAITS(ABc4b16a4b, _AB, _4b16a4b, 3); +DECL_TRAITS(ABc4b4a, _AB, _4b4a, 3); +DECL_TRAITS(Abcd4a, _A, _4a, 4); +DECL_TRAITS(aBcd4b, _B, _4b, 4); +DECL_TRAITS(ABcd4b4a, _AB, _4b4a, 4); +DECL_TRAITS(aBCd4c16b4c, _BC, _4c16b4c, 4); +DECL_TRAITS(aBCd4c4b, _BC, _4c4b, 4); +DECL_TRAITS(Abcde4a, _A, _4a, 5); +DECL_TRAITS(aBcde4b, _B, _4b, 5); +DECL_TRAITS(ABcde4b4a, _AB, _4b4a, 5); +DECL_TRAITS(aBCde4c4b, _BC, _4c4b, 5); +DECL_TRAITS(aBcdef4b, _B, _4b, 6); +DECL_TRAITS(aBCdef4c4b, _BC, _4c4b, 6); +DECL_TRAITS(aBdc4b, _B, _4b, 4); +DECL_TRAITS(aBdec4b, _B, _4b, 5); +DECL_TRAITS(aBdefc4b, _B, _4b, 6); +DECL_TRAITS(Acb4a, _A, _4a, 3); +DECL_TRAITS(Acdb4a, _A, _4a, 4); +DECL_TRAITS(Acdeb4a, _A, _4a, 5); + +DECL_TRAITS(Abc16a, _A, _16a, 3); +DECL_TRAITS(ABc16a16b, _AB, _16a16b, 3); +DECL_TRAITS(aBc16b, _B, _16b, 3); +DECL_TRAITS(ABc16b16a, _AB, _16b16a, 3); +DECL_TRAITS(ABc8a16b2a, _AB, _8a16b2a, 3); +DECL_TRAITS(ABc8a8b, _AB, _8a8b, 3); +DECL_TRAITS(aBc8b, _B, _8b, 3); +DECL_TRAITS(ABc8b16a2b, _AB, _8b16a2b, 3); +DECL_TRAITS(ABc8b8a, _AB, _8b8a, 3); +DECL_TRAITS(Abcd16a, _A, _16a, 4); +DECL_TRAITS(ABcd16a16b, _AB, _16a16b, 4); +DECL_TRAITS(aBcd16b, _B, _16b, 4); +DECL_TRAITS(ABcd16b16a, _AB, _16b16a, 4); +DECL_TRAITS(aBCd16b16c, _BC, _16b16c, 4); +DECL_TRAITS(aBCd16c16b, _BC, _16c16b, 4); +DECL_TRAITS(ABcd4b16a4b, _AB, _4b16a4b, 4); +DECL_TRAITS(ABcd8a16b2a, _AB, _8a16b2a, 4); +DECL_TRAITS(ABcd8a8b, _AB, _8a8b, 4); +DECL_TRAITS(aBcd8b, _B, _8b, 4); +DECL_TRAITS(ABcd8b16a2b, _AB, _8b16a2b, 4); +DECL_TRAITS(aBCd8b16c2b, _BC, _8b16c2b, 4); +DECL_TRAITS(ABcd8b8a, _AB, _8b8a, 4); +DECL_TRAITS(aBCd8b8c, _BC, _8b8c, 4); +DECL_TRAITS(aBCd8c16b2c, _BC, _8c16b2c, 4); +DECL_TRAITS(aBCd8c8b, _BC, _8c8b, 4); +DECL_TRAITS(Abcde16a, _A, _16a, 5); +DECL_TRAITS(ABcde16a16b, _AB, _16a16b, 5); +DECL_TRAITS(aBcde16b, _B, _16b, 5); +DECL_TRAITS(ABcde16b16a, _AB, _16b16a, 5); +DECL_TRAITS(aBCde16b16c, _BC, _16b16c, 5); +DECL_TRAITS(aBCde16c16b, _BC, _16c16b, 5); +DECL_TRAITS(aBCde4c16b4c, _BC, _4c16b4c, 5); +DECL_TRAITS(Abcde8a, _A, _8a, 5); +DECL_TRAITS(ABcde8a8b, _AB, _8a8b, 5); +DECL_TRAITS(aBcde8b, _B, _8b, 5); +DECL_TRAITS(ABcde8b16a2b, _AB, _8b16a2b, 5); +DECL_TRAITS(aBCde8b16c2b, _BC, _8b16c2b, 5); +DECL_TRAITS(ABcde8b8a, _AB, _8b8a, 5); +DECL_TRAITS(aBCde8b8c, _BC, _8b8c, 5); +DECL_TRAITS(aBCde2c8b4c, _BC, _2c8b4c, 5); +DECL_TRAITS(aBCde8c16b2c, _BC, _8c16b2c, 5); +DECL_TRAITS(aBCde4b4c, _BC, _4b4c, 5); +DECL_TRAITS(aBCde8c8b, _BC, _8c8b, 5); +DECL_TRAITS(aBcdef16b, _B, _16b, 6); +DECL_TRAITS(aBCdef16b16c, _BC, _16b16c, 6); +DECL_TRAITS(aBCdef16c16b, _BC, _16c16b, 6); +DECL_TRAITS(aBCdef8b8c, _BC, _8b8c, 6); +DECL_TRAITS(aBCdef8c16b2c, _BC, _8c16b2c, 6); +DECL_TRAITS(aBCdef8c8b, _BC, _8c8b, 6); +DECL_TRAITS(aBdc16b, _B, _16b, 4); +DECL_TRAITS(aBdc8b, _B, _8b, 4); +DECL_TRAITS(aBdec16b, _B, _16b, 5); +DECL_TRAITS(aBdec8b, _B, _8b, 5); +DECL_TRAITS(aBdefc16b, _B, _16b, 6); +DECL_TRAITS(aBdefc8b, _B, _8b, 6); +DECL_TRAITS(Acb16a, _A, _16a, 3); +DECL_TRAITS(Acb8a, _A, _8a, 3); +DECL_TRAITS(aCBd16b16c, _BC, _16b16c, 4); +DECL_TRAITS(aCBde16b16c, _BC, _16b16c, 5); +DECL_TRAITS(Acdb16a, _A, _16a, 4); +DECL_TRAITS(Acdb8a, _A, _8a, 4); +DECL_TRAITS(Acdeb16a, _A, _16a, 5); +DECL_TRAITS(Acdeb8a, _A, _8a, 5); +DECL_TRAITS(BAc16a16b, _AB, _16a16b, 3); +DECL_TRAITS(BAcd16a16b, _AB, _16a16b, 4); + +} // namespace impl +} // namespace mkldnn + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/type_helpers.hpp b/thirdparty/oidn/mkl-dnn/src/common/type_helpers.hpp new file mode 100644 index 000000000000..4f06368738cb --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/type_helpers.hpp @@ -0,0 +1,348 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef TYPE_HELPERS_HPP +#define TYPE_HELPERS_HPP + +#include +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "mkldnn_traits.hpp" +#include "nstl.hpp" +#include "utils.hpp" +#include "math_utils.hpp" + +namespace mkldnn { +namespace impl { + +template +status_t safe_ptr_assign(T * &lhs, T* rhs) { + if (rhs == nullptr) return status::out_of_memory; + lhs = rhs; + return status::success; +} + +template struct is_subset +{ static constexpr bool value = false; }; +template struct is_subset +{ static constexpr bool value = true; }; +template struct is_subset::value, float>::type> +{ static constexpr bool value = true; }; +#define ISSPEC(t1, t2) template <> \ + struct is_subset { static constexpr bool value = true; } +ISSPEC(int16_t, int32_t); +ISSPEC(int8_t, int32_t); +ISSPEC(uint8_t, int32_t); +ISSPEC(int8_t, int16_t); +ISSPEC(uint8_t, int16_t); +#undef ISSPEC + +inline bool operator==(const memory_desc_t &lhs, const memory_desc_t &rhs); + +namespace types { + +inline size_t data_type_size(data_type_t data_type) { + using namespace data_type; + switch (data_type) { + case f32: return sizeof(prec_traits::type); + case s32: return sizeof(prec_traits::type); + case s8: return sizeof(prec_traits::type); + case u8: return sizeof(prec_traits::type); + case data_type::undef: + default: assert(!"unknown data_type"); + } + return 0; /* not supposed to be reachable */ +} + +inline format_kind_t format_tag_to_kind(format_tag_t tag) { + switch (tag) { + case format_tag::undef: return format_kind::undef; + case format_tag::any: return format_kind::any; + case format_tag::last: return format_kind::undef; + default: return format_kind::blocked; + } + + assert(!"unreachable"); + return format_kind::undef; +} + +inline bool memory_extra_desc_is_equal(const memory_extra_desc_t &lhs, + const memory_extra_desc_t &rhs) { + return true + && lhs.flags == rhs.flags + && IMPLICATION(lhs.flags & memory_extra_flags::compensation_conv_s8s8, + lhs.compensation_mask == rhs.compensation_mask) + && IMPLICATION(lhs.flags & memory_extra_flags::scale_adjust, + lhs.scale_adjust == rhs.scale_adjust); +} + +inline bool blocking_desc_is_equal(const blocking_desc_t &lhs, + const blocking_desc_t &rhs, int ndims = MKLDNN_MAX_NDIMS) { + using mkldnn::impl::utils::array_cmp; + return true + && lhs.inner_nblks == rhs.inner_nblks + && array_cmp(lhs.strides, rhs.strides, ndims) + && array_cmp(lhs.inner_blks, rhs.inner_blks, lhs.inner_nblks) + && array_cmp(lhs.inner_idxs, rhs.inner_idxs, lhs.inner_nblks); +} + +inline bool wino_desc_is_equal(const wino_desc_t &lhs, + const wino_desc_t &rhs) { + return lhs.wino_format == rhs.wino_format + && lhs.alpha == rhs.alpha + && lhs.ic == rhs.ic + && lhs.oc == rhs.oc + && lhs.ic_block == rhs.ic_block + && lhs.oc_block == rhs.oc_block + && lhs.ic2_block == rhs.ic2_block + && lhs.oc2_block == rhs.oc2_block + && lhs.r == rhs.r; +} + +inline bool rnn_packed_desc_is_equal( + const rnn_packed_desc_t &lhs, const rnn_packed_desc_t &rhs) { + bool ok = true + && lhs.format == rhs.format + && lhs.n_parts == rhs.n_parts + && lhs.offset_compensation == rhs.offset_compensation + && lhs.size == rhs.size + && lhs.n == rhs.n; + if (!ok) + return false; + + for (int i = 0; i < rhs.n_parts; i++) + ok = ok && lhs.parts[i] == rhs.parts[i]; + for (int i = 0; i < rhs.n_parts; i++) + ok = ok && lhs.part_pack_size[i] == rhs.part_pack_size[i]; + return ok; +} + +inline memory_desc_t zero_md() { + auto zero = memory_desc_t(); + return zero; +} + +inline bool is_zero_md(const memory_desc_t *md) { + return md == nullptr || *md == zero_md(); +} + +inline data_type_t default_accum_data_type(data_type_t src_dt, + data_type_t dst_dt) { + using namespace utils; + using namespace data_type; + + if (one_of(f32, src_dt, dst_dt)) return f32; + if (one_of(s32, src_dt, dst_dt)) return s32; + + if (one_of(s8, src_dt, dst_dt) || one_of(u8, src_dt, dst_dt)) return s32; + + assert(!"unimplemented use-case: no default parameters available"); + return dst_dt; +} + +inline data_type_t default_accum_data_type(data_type_t src_dt, + data_type_t wei_dt, data_type_t dst_dt, prop_kind_t prop_kind) { + using namespace utils; + using namespace data_type; + using namespace prop_kind; + + /* prop_kind doesn't matter */ + if (everyone_is(f32, src_dt, wei_dt, dst_dt)) return f32; + + if (one_of(prop_kind, forward_training, forward_inference)) { + if ((src_dt == u8 || src_dt == s8) + && wei_dt == s8 && one_of(dst_dt, f32, s32, s8, u8)) + return s32; + } else if (prop_kind == backward_data) { + if (one_of(src_dt, f32, s32, s8, u8) && wei_dt == s8 && + one_of(dst_dt, s8, u8)) + return s32; + } + + assert(!"unimplemented use-case: no default parameters available"); + return dst_dt; +} + +} + +inline bool operator==(const memory_desc_t &lhs, const memory_desc_t &rhs) { + using namespace mkldnn::impl::utils; + bool base_equal = true + && lhs.ndims == rhs.ndims + && array_cmp(lhs.dims, rhs.dims, lhs.ndims) + && lhs.data_type == rhs.data_type + && array_cmp(lhs.padded_dims, rhs.padded_dims, lhs.ndims) + && array_cmp(lhs.padded_offsets, rhs.padded_offsets, lhs.ndims) + && lhs.offset0 == rhs.offset0 + && lhs.format_kind == rhs.format_kind; + if (!base_equal) return false; + if (!types::memory_extra_desc_is_equal(lhs.extra, rhs.extra)) return false; + if (lhs.format_kind == format_kind::blocked) + return types::blocking_desc_is_equal(lhs.format_desc.blocking, + rhs.format_desc.blocking, lhs.ndims); + else if (lhs.format_kind == format_kind::wino) + return types::wino_desc_is_equal(lhs.format_desc.wino_desc, + rhs.format_desc.wino_desc); + else if (lhs.format_kind == format_kind::rnn_packed) + return types::rnn_packed_desc_is_equal(lhs.format_desc.rnn_packed_desc, + rhs.format_desc.rnn_packed_desc); + return true; +} + +inline bool operator!=(const memory_desc_t &lhs, const memory_desc_t &rhs) { + return !operator==(lhs, rhs); +} + +inline status_t memory_desc_init_by_strides(memory_desc_t &md, + const dims_t strides) { + return mkldnn_memory_desc_init_by_strides( + &md, md.ndims, md.dims, md.data_type, strides); +} + +inline status_t memory_desc_init_by_tag(memory_desc_t &md, format_tag_t tag, + const dims_t strides = nullptr) { + status_t status = mkldnn_memory_desc_init_by_tag( + &md, md.ndims, md.dims, md.data_type, tag); + if (status != status::success || strides == nullptr) + return status; + + /* TODO: add consistency check */ + + for (int d = 0; d < md.ndims; ++d) + md.format_desc.blocking.strides[d] = strides[d]; + + return status::success; +} + +/** inits memory descriptor based on logical dimensions kept in @p md, and the + * blocking structure @p blk. + * + * @note blk.strides represent the order only (from smaller to bigger) + * + * TODO: move md related functions to one single place + */ +inline status_t memory_desc_init_by_blocking_desc(memory_desc_t &md, + const blocking_desc_t &blk) { + dims_t blocks = {0}; + utils::array_set(blocks, 1, md.ndims); + dim_t block_size = 1; + for (int iblk = 0; iblk < blk.inner_nblks; ++iblk) { + blocks[blk.inner_idxs[iblk]] *= blk.inner_blks[iblk]; + block_size *= blk.inner_blks[iblk]; + } + + for (int d = 0; d < md.ndims; ++d) { + md.padded_dims[d] = utils::rnd_up(md.dims[d], blocks[d]); + md.padded_offsets[d] = 0; + } + md.offset0 = 0; + + md.format_kind = format_kind::blocked; + auto &mblk = md.format_desc.blocking; + mblk = blk; + + const int ndims = nstl::min(MKLDNN_MAX_NDIMS, md.ndims); // make GCC 5 happy + utils::array_copy(mblk.strides, blk.strides, ndims); + + int perm[MKLDNN_MAX_NDIMS]; + for (int d = 0; d < ndims; ++d) perm[d] = d; + + utils::simultaneous_sort(mblk.strides, perm, ndims, + [](stride_t a, stride_t b) { return b - a; }); + + dim_t stride = block_size; + for (int _d = ndims - 1; _d >= 0; --_d) { + const int d = perm[_d]; + md.format_desc.blocking.strides[d] = stride; + stride *= md.padded_dims[d] / blocks[d]; + } + + md.extra = utils::zero(); + + return status::success; +} + +/** returns true if memory desc @p md corresponds to the given format tag and + * strides. + * If strides are not passed (or passed as nullptr) the dense structure is + * assumed (i.e. the one that mkldnn_memory_desc_init_by_tag() returns). + * Strides might contain `0` value, indicating the stride must match the one + * that mkldnn_memory_desc_init_by_tag() returns. + * Strides might contain `-1` values, that would be ignored during the + * comparison. For instance, this can be used if a stride along minibatch + * doesn't matter. */ +inline bool memory_desc_matches_tag(const memory_desc_t &md, format_tag_t tag, + const dims_t strides = nullptr) { + if (md.format_kind != types::format_tag_to_kind(tag)) + return false; + + memory_desc_t md_gold; + status_t status = mkldnn_memory_desc_init_by_tag( + &md_gold, md.ndims, md.dims, md.data_type, tag); + if (status != status::success) return false; + + if (md.format_kind != format_kind::blocked) + return false; // unimplemented yet + + const auto &blk = md.format_desc.blocking; + const auto &blk_gold = md_gold.format_desc.blocking; + + using utils::array_cmp; + bool same_blocks = true + && blk.inner_nblks == blk_gold.inner_nblks + && array_cmp(blk.inner_blks, blk_gold.inner_blks, blk.inner_nblks) + && array_cmp(blk.inner_idxs, blk_gold.inner_idxs, blk.inner_nblks); + + if (!same_blocks) + return false; + + if (strides == nullptr) + return array_cmp(blk.strides, blk_gold.strides, md.ndims); + + for (int d = 0; d < md.ndims; ++d) { + dim_t stride = strides[d]; + if (stride == -1) continue; + if (stride == 0) stride = blk_gold.strides[d]; + if (blk.strides[d] != stride) return false; + } + + return true; +} + +/** returns matching tag (or undef if match is not found) + * XXX: This is a workaround that eventually should go away! */ +template +format_tag_t memory_desc_matches_one_of_tag(const memory_desc_t &md, + Tags ...tags) { + for (const auto tag: {tags...}) { + if (memory_desc_matches_tag(md, tag)) + return tag; + } + return format_tag::undef; +} + +} +} + +#include "memory_desc_wrapper.hpp" + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/utils.cpp b/thirdparty/oidn/mkl-dnn/src/common/utils.cpp new file mode 100644 index 000000000000..d23f4682dc32 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/utils.cpp @@ -0,0 +1,135 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#ifdef _WIN32 +#include +#include +#endif +#include +#include +#include + +#include "mkldnn.h" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { + +int getenv(const char *name, char *buffer, int buffer_size) { + if (name == NULL || buffer_size < 0 || (buffer == NULL && buffer_size > 0)) + return INT_MIN; + + int result = 0; + int term_zero_idx = 0; + size_t value_length = 0; + +#ifdef _WIN32 + value_length = GetEnvironmentVariable(name, buffer, buffer_size); +#else + const char *value = ::getenv(name); + value_length = value == NULL ? 0 : strlen(value); +#endif + + if (value_length > INT_MAX) + result = INT_MIN; + else { + int int_value_length = (int)value_length; + if (int_value_length >= buffer_size) { + result = -int_value_length; + } else { + term_zero_idx = int_value_length; + result = int_value_length; +#ifndef _WIN32 + strncpy(buffer, value, value_length); +#endif + } + } + + if (buffer != NULL) + buffer[term_zero_idx] = '\0'; + return result; +} + +int getenv_int(const char *name, int default_value) +{ + int value = default_value; + // # of digits in the longest 32-bit signed int + sign + terminating null + const int len = 12; + char value_str[len]; + if (getenv(name, value_str, len) > 0) + value = atoi(value_str); + return value; +} + +FILE *fopen(const char *filename, const char *mode) { +#ifdef _WIN32 + FILE *fp = NULL; + return ::fopen_s(&fp, filename, mode) ? NULL : fp; +#else + return ::fopen(filename, mode); +#endif +} + +void *malloc(size_t size, int alignment) { + void *ptr; + +#ifdef _WIN32 + ptr = _aligned_malloc(size, alignment); + int rc = ptr ? 0 : -1; +#else + int rc = ::posix_memalign(&ptr, alignment, size); +#endif + + return (rc == 0) ? ptr : 0; +} + +void free(void *p) { +#ifdef _WIN32 + _aligned_free(p); +#else + ::free(p); +#endif +} + +// Atomic operations +int32_t fetch_and_add(int32_t *dst, int32_t val) { +#ifdef _WIN32 + return InterlockedExchangeAdd(reinterpret_cast(dst), val); +#else + return __sync_fetch_and_add(dst, val); +#endif +} + +static int jit_dump_flag = 0; +static bool jit_dump_flag_initialized = false; +bool jit_dump_enabled() { + if (!jit_dump_flag_initialized) { + jit_dump_flag = getenv_int("MKLDNN_JIT_DUMP"); + jit_dump_flag_initialized = true; + } + return jit_dump_flag != 0; +} + +} +} + +mkldnn_status_t mkldnn_set_jit_dump(int enabled) { + using namespace mkldnn::impl::status; + mkldnn::impl::jit_dump_flag = enabled; + mkldnn::impl::jit_dump_flag_initialized = true; + return success; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/utils.hpp b/thirdparty/oidn/mkl-dnn/src/common/utils.hpp new file mode 100644 index 000000000000..d5a8ec5139fc --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/utils.hpp @@ -0,0 +1,370 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include +#include +#include +#include + +#if defined(__x86_64__) || defined(_M_X64) +#define MKLDNN_X86_64 +#endif + +#define MSAN_ENABLED 0 +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#undef MSAN_ENABLED +#define MSAN_ENABLED 1 +#include +#endif +#endif + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "z_magic.hpp" + +namespace mkldnn { +namespace impl { + +// Sanity check for 64 bits +static_assert(sizeof(void*) == 8, "Intel(R) MKL-DNN supports 64 bit only"); + +#define CHECK(f) do { \ + status_t status = f; \ + if (status != status::success) \ + return status; \ +} while (0) + +#define IMPLICATION(cause, effect) (!(cause) || !!(effect)) + +namespace utils { + +/* a bunch of std:: analogues to be compliant with any msvs version + * + * Rationale: msvs c++ (and even some c) headers contain special pragma that + * injects msvs-version check into object files in order to abi-mismatches + * during the static linking. This makes sense if e.g. std:: objects are passed + * through between application and library, which is not the case for mkl-dnn + * (since there is no any c++-rt dependent stuff, ideally...). */ + +/* SFINAE helper -- analogue to std::enable_if */ +template struct enable_if {}; +template struct enable_if { typedef T type; }; + +/* analogue std::conditional */ +template struct conditional {}; +template struct conditional +{ typedef T type; }; +template struct conditional +{ typedef F type; }; + +template struct conditional3 {}; +template +struct conditional3 { typedef T type; }; +template +struct conditional3 { typedef FT type; }; +template +struct conditional3 { typedef FF type; }; + +template struct conditional_v {}; +template struct conditional_v +{ static constexpr U value = t; }; +template struct conditional_v +{ static constexpr U value = f; }; + +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template +inline T&& forward(typename utils::remove_reference::type &t) +{ return static_cast(t); } +template +inline T&& forward(typename utils::remove_reference::type &&t) +{ return static_cast(t); } + +template +inline typename remove_reference::type zero() +{ auto zero = typename remove_reference::type(); return zero; } + +template +inline bool everyone_is(T val, P item) { return val == item; } +template +inline bool everyone_is(T val, P item, Args... item_others) { + return val == item && everyone_is(val, item_others...); +} + +template +constexpr bool one_of(T val, P item) { return val == item; } +template +constexpr bool one_of(T val, P item, Args... item_others) { + return val == item || one_of(val, item_others...); +} + +template +inline bool any_null(Args... ptrs) { return one_of(nullptr, ptrs...); } + +template +inline void array_copy(T *dst, const T *src, size_t size) { + for (size_t i = 0; i < size; ++i) dst[i] = src[i]; +} +template +inline bool array_cmp(const T *a1, const T *a2, size_t size) { + for (size_t i = 0; i < size; ++i) if (a1[i] != a2[i]) return false; + return true; +} +template +inline void array_set(T *arr, const U& val, size_t size) { + for (size_t i = 0; i < size; ++i) arr[i] = static_cast(val); +} + +namespace product_impl { +template struct int2type{}; + +template +constexpr int product_impl(const T *arr, int2type<0>) { return arr[0]; } + +template +inline T product_impl(const T *arr, int2type) { + return arr[0]*product_impl(arr+1, int2type()); } +} + +template +inline T array_product(const T *arr) { + return product_impl::product_impl(arr, product_impl::int2type()); +} + +template +inline R array_product(const T *arr, size_t size) { + R prod = 1; + for (size_t i = 0; i < size; ++i) prod *= arr[i]; + return prod; +} + +/** sorts an array of values using @p comparator. While sorting the array + * of value, the function permutes an array of @p keys accordingly. + * + * @note The arrays of @p keys can be omitted. In this case the function + * sorts the array of @vals only. + */ +template +inline void simultaneous_sort(T *vals, U *keys, size_t size, F comparator) { + if (size == 0) return; + + for (size_t i = 0; i < size - 1; ++i) { + bool swapped = false; + + for (size_t j = 0; j < size - i - 1; j++) { + if (comparator(vals[j], vals[j + 1]) > 0) { + nstl::swap(vals[j], vals[j + 1]); + if (keys) nstl::swap(keys[j], keys[j + 1]); + swapped = true; + } + } + + if (swapped == false) break; + } +} + +template +inline typename remove_reference::type div_up(const T a, const U b) { + assert(b); + return (a + b - 1) / b; +} + +template +inline typename remove_reference::type rnd_up(const T a, const U b) { + return div_up(a, b) * b; +} + +template +inline typename remove_reference::type rnd_dn(const T a, const U b) { + return (a / b) * b; +} + +template T *align_ptr(T *ptr, uintptr_t alignment) +{ return (T *)(((uintptr_t)ptr + alignment - 1) & ~(alignment - 1)); } + +template +inline U this_block_size(const T offset, const U max, const V block_size) { + assert(offset < max); + // TODO (Roma): can't use nstl::max() due to circular dependency... we + // need to fix this + const T block_boundary = offset + block_size; + if (block_boundary > max) + return max - offset; + else + return block_size; +} + +template +inline T nd_iterator_init(T start) { return start; } +template +inline T nd_iterator_init(T start, U &x, const W &X, Args &&... tuple) { + start = nd_iterator_init(start, utils::forward(tuple)...); + x = start % X; + return start / X; +} + +inline bool nd_iterator_step() { return true; } +template +inline bool nd_iterator_step(U &x, const W &X, Args &&... tuple) { + if (nd_iterator_step(utils::forward(tuple)...) ) { + x = (x + 1) % X; + return x == 0; + } + return false; +} + +template +inline bool nd_iterator_jump(U &cur, const U end, W &x, const Y &X) +{ + U max_jump = end - cur; + U dim_jump = X - x; + if (dim_jump <= max_jump) { + x = 0; + cur += dim_jump; + return true; + } else { + cur += max_jump; + x += max_jump; + return false; + } +} +template +inline bool nd_iterator_jump(U &cur, const U end, W &x, const Y &X, + Args &&... tuple) +{ + if (nd_iterator_jump(cur, end, utils::forward(tuple)...)) { + x = (x + 1) % X; + return x == 0; + } + return false; +} + +template +inline T pick(size_t i, const T &x0) { return x0; } +template +inline T pick(size_t i, const T &x0, Args &&... args) { + return i == 0 ? x0 : pick(i - 1, utils::forward(args)...); +} + +template +T pick_by_prop_kind(prop_kind_t prop_kind, const T &val_fwd_inference, + const T &val_fwd_training, const T &val_bwd_d, const T &val_bwd_w) { + switch (prop_kind) { + case prop_kind::forward_inference: return val_fwd_inference; + case prop_kind::forward_training: return val_fwd_training; + case prop_kind::backward_data: return val_bwd_d; + case prop_kind::backward_weights: return val_bwd_w; + default: assert(!"unsupported prop_kind"); + } + return T(); +} + +template +T pick_by_prop_kind(prop_kind_t prop_kind, + const T &val_fwd, const T &val_bwd_d, const T &val_bwd_w) +{ return pick_by_prop_kind(prop_kind, val_fwd, val_fwd, val_bwd_d, val_bwd_w); } + +template +struct array_offset_calculator { + template + array_offset_calculator(Telem *base, Targs... Fargs) : _dims{ Fargs... } + { + _base_ptr = base; + } + template + inline Telem &operator()(Targs... Fargs) + { + return *(_base_ptr + _offset(1, Fargs...)); + } + +private: + template + inline size_t _offset(size_t const dimension, size_t element) + { + return element; + } + + template + inline size_t _offset(size_t const dimension, size_t theta, size_t element) + { + return element + (_dims[dimension] * theta); + } + + template + inline size_t _offset(size_t const dimension, size_t theta, size_t element, + Targs... Fargs) + { + size_t t_prime = element + (_dims[dimension] * theta); + return _offset(dimension + 1, t_prime, Fargs...); + } + + Telem *_base_ptr; + const int _dims[Tdims]; +}; + +} + +int32_t fetch_and_add(int32_t *dst, int32_t val); +inline void yield_thread() {} + +// Reads an environment variable 'name' and stores its string value in the +// 'buffer' of 'buffer_size' bytes on success. +// +// - Returns the length of the environment variable string value (excluding +// the terminating 0) if it is set and its contents (including the terminating +// 0) can be stored in the 'buffer' without truncation. +// +// - Returns negated length of environment variable string value and writes +// "\0" to the buffer (if it is not NULL) if the 'buffer_size' is to small to +// store the value (including the terminating 0) without truncation. +// +// - Returns 0 and writes "\0" to the buffer (if not NULL) if the environment +// variable is not set. +// +// - Returns INT_MIN if the 'name' is NULL. +// +// - Returns INT_MIN if the 'buffer_size' is negative. +// +// - Returns INT_MIN if the 'buffer' is NULL and 'buffer_size' is greater than +// zero. Passing NULL 'buffer' with 'buffer_size' set to 0 can be used to +// retrieve the length of the environment variable value string. +// +int getenv(const char *name, char *buffer, int buffer_size); +// Reads an integer from the environment +int getenv_int(const char *name, int default_value = 0); +bool jit_dump_enabled(); +FILE *fopen(const char *filename, const char *mode); + +constexpr int msan_enabled = MSAN_ENABLED; +inline void msan_unpoison(void *ptr, size_t size) { +#if MSAN_ENABLED + __msan_unpoison(ptr, size); +#endif +} + +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/common/verbose.cpp b/thirdparty/oidn/mkl-dnn/src/common/verbose.cpp new file mode 100644 index 000000000000..89a57772cf4c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/verbose.cpp @@ -0,0 +1,665 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#ifndef _WIN32 +#include +#endif + +#include "mkldnn.h" +#include "mkldnn_version.h" +#include "c_types_map.hpp" +#include "verbose.hpp" +#include "cpu/cpu_isa_traits.hpp" + +#include "batch_normalization_pd.hpp" +#include "pooling_pd.hpp" +#include "concat_pd.hpp" +#include "reorder_pd.hpp" +#include "convolution_pd.hpp" +#include "rnn_pd.hpp" +#include "deconvolution_pd.hpp" +#include "shuffle_pd.hpp" +#include "eltwise_pd.hpp" +#include "softmax_pd.hpp" +#include "inner_product_pd.hpp" +#include "sum_pd.hpp" +#include "lrn_pd.hpp" + +/* MKL-DNN CPU ISA info */ +#define ISA_ANY "No instruction set specific optimizations" +#define SSE42 "Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2)" +#define AVX "Intel(R) Advanced Vector Extensions (Intel(R) AVX)" +#define AVX2 "Intel(R) Advanced Vector Extensions 2 (Intel(R) AVX2)" +#define AVX512_COMMON "Intel(R) Advanced Vector Extensions 512 (Intel(R) " \ + "AVX-512)" +#define AVX512_CORE "Intel(R) Advanced Vector Extensions 512 (Intel(R) " \ + "AVX-512) with AVX512BW, AVX512VL, and AVX512DQ extensions" +#define AVX512_CORE_VNNI "Intel(R) AVX512-Deep Learning Boost (Intel(R) " \ + "AVX512-DL Boost)" +#define AVX512_MIC "Intel(R) Advanced Vector Extensions 512 (Intel(R) " \ + "AVX-512) with AVX512CD, AVX512ER, and AVX512PF extensions" +#define AVX512_MIC_4OPS "Intel(R) Advanced Vector Extensions 512 (Intel(R) " \ + "AVX-512) with AVX512_4FMAPS and AVX512_4VNNIW extensions" + +namespace mkldnn { +namespace impl { + +static verbose_t verbose; +static bool initialized; +static bool version_printed = false; + +const verbose_t *mkldnn_verbose() { +#if !defined(DISABLE_VERBOSE) + if (!initialized) { + const int len = 2; + char val[len] = {0}; + if (getenv("MKLDNN_VERBOSE", val, len) == 1) + verbose.level = atoi(val); + initialized = true; + } + if (!version_printed && verbose.level > 0) { + printf("mkldnn_verbose,info," + "Intel(R) MKL-DNN v%d.%d.%d (Git Hash %s),%s\n", + mkldnn_version()->major, mkldnn_version()->minor, + mkldnn_version()->patch, mkldnn_version()->hash, + get_isa_info()); + version_printed = true; + } +#else + verbose.level = 0; +#endif + return &verbose; +} + +double get_msec() { +#ifdef _WIN32 + static LARGE_INTEGER frequency; + if (frequency.QuadPart == 0) + QueryPerformanceFrequency(&frequency); + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return 1e+3 * now.QuadPart / frequency.QuadPart; +#else + struct timeval time; + gettimeofday(&time, NULL); + return 1e+3 * time.tv_sec + 1e-3 * time.tv_usec; +#endif +} + +const char *get_isa_info() { + using namespace mkldnn::impl::cpu; + if (mayiuse(avx512_mic_4ops)) return AVX512_MIC_4OPS; + if (mayiuse(avx512_mic)) return AVX512_MIC; + if (mayiuse(avx512_core_vnni)) return AVX512_CORE_VNNI; + if (mayiuse(avx512_core)) return AVX512_CORE; + if (mayiuse(avx512_common)) return AVX512_COMMON; + if (mayiuse(avx2)) return AVX2; + if (mayiuse(avx)) return AVX; + if (mayiuse(sse42)) return SSE42; + return ISA_ANY; +} + +/* init_info section */ +namespace { +#if !defined(DISABLE_VERBOSE) +#define MKLDNN_VERBOSE_DAT_LEN 256 +#define MKLDNN_VERBOSE_AUX_LEN 384 +#define MKLDNN_VERBOSE_PRB_LEN 384 + +#define DECL_DAT_AUX_PRB_STRS() \ + int dat_written = 0, aux_written = 0, prb_written = 0; \ + MAYBE_UNUSED((dat_written * aux_written * prb_written)); \ + char dat_str[MKLDNN_VERBOSE_DAT_LEN] = {'\0'}; MAYBE_UNUSED(dat_str); \ + char aux_str[MKLDNN_VERBOSE_AUX_LEN] = {'\0'}; MAYBE_UNUSED(aux_str); \ + char prb_str[MKLDNN_VERBOSE_PRB_LEN] = {'\0'}; MAYBE_UNUSED(prb_str) + +#define DFMT "%" PRId64 + +void clear_buf(char *buf, int &written) { + /* TODO: do it better */ + buf[0] = '#'; + buf[1] = '\0'; + written = 1; +} + +#define DPRINT(buf, buf_len, written, ...) do { \ + int l = snprintf(buf + written, buf_len - written, __VA_ARGS__); \ + if (l < 0 || written + l > buf_len) { \ + clear_buf(buf, written); \ + } else { \ + written += l; \ + } \ +} while(0) + +// XXX: Outputs strings corresponding to memory formats used for data tensors. +void format_prb_desc_str(char *str, int len, const memory_desc_t *md) { + const auto dims = md->dims; + int written = 0; + if (md->ndims == 1) + DPRINT(str, len, written, + "x" DFMT, dims[0]); + else if (md->ndims == 2) + DPRINT(str, len, written, + "mb" DFMT "ic" DFMT, dims[0], dims[1]); + else if (md->ndims == 3) + DPRINT(str, len, written, + "mb" DFMT "ic" DFMT "iw" DFMT, + dims[0], dims[1], dims[2]); + else if (md->ndims == 4) + DPRINT(str, len, written, + "mb" DFMT "ic" DFMT "ih" DFMT "iw" DFMT, + dims[0], dims[1], dims[2], dims[3]); + else if (md->ndims == 5) + DPRINT(str, len, written, + "mb" DFMT "ic" DFMT "id" DFMT "ih" DFMT "iw" DFMT, + dims[0], dims[1], dims[2], dims[3], dims[4]); + else + mkldnn_md2dim_str(str, len, md); +} + +void verbose_templ(char *buffer, mkldnn_primitive_kind_t prim_kind, + const char *impl_str, mkldnn_prop_kind_t prop_kind, + const char *data_str, const char *aux_str, const char *prb_str) { + MAYBE_UNUSED(verbose_templ); + int written = 0; + DPRINT(buffer, MKLDNN_VERBOSE_BUF_LEN, written, "%s,%s,%s,%s,%s,%s", + mkldnn_prim_kind2str(prim_kind), impl_str, + mkldnn_prop_kind2str(prop_kind), data_str, aux_str, prb_str); +} + +template static void init_info_bnorm(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // data + auto md = s->src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "data_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // diff data + auto md = s->diff_src_md(); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " diff_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "flags:%u", s->desc()->flags); + + format_prb_desc_str(prb_str, MKLDNN_VERBOSE_PRB_LEN, s->src_md()); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_conv(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // src + auto md = s->desc()->prop_kind == prop_kind::backward_data + ? s->diff_src_md() : s->src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "src_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // wei + auto md = s->desc()->prop_kind == prop_kind::backward_weights + ? s->diff_weights_md() : s->weights_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " wei_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // bia + auto md = s->desc()->prop_kind == prop_kind::backward_weights + ? s->diff_weights_md(1) : s->weights_md(1); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " bia_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + if (1) { // dst + auto md = !s->is_fwd() ? s->diff_dst_md() : s->dst_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " dst_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "alg:%s", mkldnn_alg_kind2str(s->desc()->alg_kind)); + + if (s->ndims() == 5) { + if (s->with_groups()) + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "_g" DFMT "ic" DFMT "oc" DFMT + "_id" DFMT "od" DFMT "kd" DFMT "sd" DFMT "dd" DFMT "pd" DFMT + "_ih" DFMT "oh" DFMT "kh" DFMT "sh" DFMT "dh" DFMT "ph" DFMT + "_iw" DFMT "ow" DFMT "kw" DFMT "sw" DFMT "dw" DFMT "pw" DFMT, + s->MB(), s->G(), s->IC(), s->OC(), + s->ID(), s->OD(), s->KD(), s->KSD(), s->KDD(), s->padFront(), + s->IH(), s->OH(), s->KH(), s->KSH(), s->KDH(), s->padT(), + s->IW(), s->OW(), s->KW(), s->KSW(), s->KDW(), s->padL()); + else + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "_ic" DFMT "oc" DFMT + "_id" DFMT "od" DFMT "kd" DFMT "sd" DFMT "dd" DFMT "pd" DFMT + "_ih" DFMT "oh" DFMT "kh" DFMT "sh" DFMT "dh" DFMT "ph" DFMT + "_iw" DFMT "ow" DFMT "kw" DFMT "sw" DFMT "dw" DFMT "pw" DFMT, + s->MB(), s->IC(), s->OC(), + s->ID(), s->OD(), s->KD(), s->KSD(), s->KDD(), s->padFront(), + s->IH(), s->OH(), s->KH(), s->KSH(), s->KDH(), s->padT(), + s->IW(), s->OW(), s->KW(), s->KSW(), s->KDW(), s->padL()); + } else { + if (s->with_groups()) + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "_g" DFMT "ic" DFMT "oc" DFMT + "_ih" DFMT "oh" DFMT "kh" DFMT "sh" DFMT "dh" DFMT "ph" DFMT + "_iw" DFMT "ow" DFMT "kw" DFMT "sw" DFMT "dw" DFMT "pw" DFMT, + s->MB(), s->G(), s->IC(), s->OC(), + s->IH(), s->OH(), s->KH(), s->KSH(), s->KDH(), s->padT(), + s->IW(), s->OW(), s->KW(), s->KSW(), s->KDW(), s->padL()); + else + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "_ic" DFMT "oc" DFMT + "_ih" DFMT "oh" DFMT "kh" DFMT "sh" DFMT "dh" DFMT "ph" DFMT + "_iw" DFMT "ow" DFMT "kw" DFMT "sw" DFMT "dw" DFMT "pw" DFMT, + s->MB(), s->IC(), s->OC(), + s->IH(), s->OH(), s->KH(), s->KSH(), s->KDH(), s->padT(), + s->IW(), s->OW(), s->KW(), s->KSW(), s->KDW(), s->padL()); + } + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_shuffle(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + auto md = s->is_fwd() ? s->src_md() : s->diff_dst_md(); + + if (1) { // data + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "data_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "axis:%d group_size:" DFMT, s->axis(), s->group_size()); + + mkldnn_md2dim_str(prb_str, MKLDNN_VERBOSE_PRB_LEN, md); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_eltwise(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // data + auto md = s->src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "data_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // diff data + auto md = s->diff_src_md(); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " diff_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "alg:%s", mkldnn_alg_kind2str(s->desc()->alg_kind)); + + mkldnn_md2dim_str(prb_str, MKLDNN_VERBOSE_PRB_LEN, s->src_md()); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_iprod(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // src + auto md = s->desc()->prop_kind == prop_kind::backward_data + ? s->diff_src_md() : s->src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "src_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // wei + auto md = s->desc()->prop_kind == prop_kind::backward_weights + ? s->diff_weights_md() : s->weights_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " wei_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // bia + auto md = s->desc()->prop_kind == prop_kind::backward_weights + ? s->diff_weights_md(1) : s->weights_md(1); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " bia_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + if (1) { // dst + auto md = !s->is_fwd() ? s->diff_dst_md() : s->dst_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " dst_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "ic" DFMT "oc" DFMT, s->MB(), s->IC_total(), s->OC()); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_lrn(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // data + auto md = s->src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "data_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // diff data + auto md = s->diff_src_md(); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " diff_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "alg:%s", mkldnn_alg_kind2str(s->desc()->alg_kind)); + + format_prb_desc_str(prb_str, MKLDNN_VERBOSE_PRB_LEN, s->src_md()); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_mem(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // src + auto md = s->src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "src_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // dst + auto md = s->dst_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " dst_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "num:%d", s->n_inputs()); + + mkldnn_md2dim_str(prb_str, MKLDNN_VERBOSE_PRB_LEN, s->dst_md()); + + verbose_templ(buffer, s->kind(), s->name(), prop_kind::undef, dat_str, + aux_str, prb_str); +} + +template static void init_info_pool(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // src + auto md = s->is_fwd() ? s->src_md() : s->diff_src_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "src_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // dst + auto md = s->is_fwd() ? s->dst_md() : s->diff_dst_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " dst_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // ws + auto md = s->workspace_md(); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " ws_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "alg:%s", mkldnn_alg_kind2str(s->desc()->alg_kind)); + + if (s->is_3d()) { + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "ic" DFMT "_" + "id" DFMT "od" DFMT "kd" DFMT "sd" DFMT "pd" DFMT "_" + "ih" DFMT "oh" DFMT "kh" DFMT "sh" DFMT "ph" DFMT "_" + "iw" DFMT "ow" DFMT "kw" DFMT "sw" DFMT "pw" DFMT "", + s->MB(), s->C(), + s->ID(), s->OD(), s->KD(), s->KSD(), s->padFront(), + s->IH(), s->OH(), s->KH(), s->KSH(), s->padT(), + s->IW(), s->OW(), s->KW(), s->KSW(), s->padL()); + } else { + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "mb" DFMT "ic" DFMT "_" + "ih" DFMT "oh" DFMT "kh" DFMT "sh" DFMT "ph" DFMT "_" + "iw" DFMT "ow" DFMT "kw" DFMT "sw" DFMT "pw" DFMT, + s->MB(), s->C(), + s->IH(), s->OH(), s->KH(), s->KSH(), s->padT(), + s->IW(), s->OW(), s->KW(), s->KSW(), s->padL()); + } + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_softmax(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // data + auto md = s->dst_md(); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "data_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // diff data + auto md = s->diff_src_md(); + if (md) { + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " diff_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + } + + mkldnn_md2dim_str(prb_str, MKLDNN_VERBOSE_PRB_LEN, s->dst_md()); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +template static void init_info_rnn(pd_t *s, char *buffer) { + DECL_DAT_AUX_PRB_STRS(); + + if (1) { // src layer + auto md = s->is_fwd() ? s->src_md(0) : s->diff_src_md(0); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "src_layer_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // src iter + auto md = s->is_fwd() ? s->src_md(1) : s->diff_src_md(1); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "src_iter_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // wei_layer + auto md = s->is_fwd() ? s->weights_md(0) : s->diff_weights_md(0); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " wei_layer_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // wei_iter + auto md = s->is_fwd() ? s->weights_md(1) : s->diff_weights_md(1); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " wei_layer_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // bias + auto md = s->is_fwd() ? s->weights_md(2) : s->diff_weights_md(2); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, " bias_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // dst layer + auto md = s->is_fwd() ? s->dst_md(0) : s->diff_dst_md(0); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "dst_layer_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + if (1) { // dst iter + auto md = s->is_fwd() ? s->dst_md(1) : s->diff_dst_md(1); + DPRINT(dat_str, MKLDNN_VERBOSE_DAT_LEN, dat_written, "dst_iter_"); + int l = mkldnn_md2fmt_str(dat_str + dat_written, + MKLDNN_VERBOSE_DAT_LEN - dat_written, md); + if (l >= 0) dat_written += l; else clear_buf(dat_str, dat_written); + } + + alg_kind_t alg_kind = s->cell_kind(); + rnn_direction_t rnn_dir = s->direction(); + DPRINT(aux_str, MKLDNN_VERBOSE_AUX_LEN, aux_written, + "alg:%s_%s", mkldnn_alg_kind2str(alg_kind), + mkldnn_rnn_direction2str(rnn_dir)); + + DPRINT(prb_str, MKLDNN_VERBOSE_PRB_LEN, prb_written, + "l" DFMT "t" DFMT "mb" DFMT + "sic" DFMT "slc" DFMT "dic" DFMT "dlc" DFMT, + s->L(), s->T(), s->MB(), + s->SIC(), s->SLC(), s->DIC(), s->DLC()); + + verbose_templ(buffer, s->kind(), s->name(), s->desc()->prop_kind, dat_str, + aux_str, prb_str); +} + +#undef DPRINT + +#else // !defined(DISABLE_VERBOSE) + +#define DEFINE_STUB(name) \ + template \ + static void CONCAT2(init_info_, name)(pd_t *s, char *buffer) \ + { UNUSED(s); UNUSED(buffer); } + +DEFINE_STUB(bnorm); +DEFINE_STUB(conv); +DEFINE_STUB(eltwise); +DEFINE_STUB(iprod); +DEFINE_STUB(lrn); +DEFINE_STUB(mem); +DEFINE_STUB(pool); +DEFINE_STUB(softmax); +DEFINE_STUB(rnn); +DEFINE_STUB(shuffle); +#undef DEFINE_STUB + +#endif // !defined(DISABLE_VERBOSE) +} + +void init_info(batch_normalization_pd_t *s, char *b) +{ init_info_bnorm(s, b); } +void init_info(concat_pd_t *s, char *b) +{ init_info_mem(s, b); } +void init_info(convolution_pd_t *s, char *b) +{ init_info_conv(s, b); } +void init_info(deconvolution_pd_t *s, char *b) +{ init_info_conv(s, b); } +void init_info(eltwise_pd_t *s, char *b) +{ init_info_eltwise(s, b); } +void init_info(inner_product_pd_t *s, char *b) +{ init_info_iprod(s, b); } +void init_info(lrn_pd_t *s, char *b) +{ init_info_lrn(s, b); } +void init_info(pooling_pd_t *s, char *b) +{ init_info_pool(s, b); } +void init_info(reorder_pd_t *s, char *b) +{ init_info_mem(s, b); } +void init_info(rnn_pd_t *s, char *b) +{ init_info_rnn(s, b); } +void init_info(shuffle_pd_t *s, char *b) +{ init_info_shuffle(s, b); } +void init_info(softmax_pd_t *s, char *b) +{ init_info_softmax(s, b); } +void init_info(sum_pd_t *s, char *b) +{ init_info_mem(s, b); } + +} +} + +mkldnn_status_t mkldnn_set_verbose(int level) { + using namespace mkldnn::impl::status; + if (level < 0 || level > 2) return invalid_arguments; + mkldnn::impl::verbose.level = level; + mkldnn::impl::initialized = true; + return success; +} + +const mkldnn_version_t *mkldnn_version() { + static mkldnn_version_t ver = { + MKLDNN_VERSION_MAJOR, + MKLDNN_VERSION_MINOR, + MKLDNN_VERSION_PATCH, + MKLDNN_VERSION_HASH}; + return &ver; +} diff --git a/thirdparty/oidn/mkl-dnn/src/common/verbose.hpp b/thirdparty/oidn/mkl-dnn/src/common/verbose.hpp new file mode 100644 index 000000000000..e3049750cb16 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/verbose.hpp @@ -0,0 +1,62 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef VERBOSE_HPP +#define VERBOSE_HPP + +#include +#include + +#include "mkldnn_debug.h" +#include "c_types_map.hpp" +#include "utils.hpp" +#include "z_magic.hpp" + +namespace mkldnn { +namespace impl { + +struct verbose_t { + int level; +}; + +const verbose_t *mkldnn_verbose(); +double get_msec(); +const char *get_isa_info(); + +#if !defined(DISABLE_VERBOSE) +#define MKLDNN_VERBOSE_BUF_LEN 1024 +#else +#define MKLDNN_VERBOSE_BUF_LEN 1 +#endif + +void init_info(batch_normalization_pd_t *s, char *buffer); +void init_info(concat_pd_t *s, char *buffer); +void init_info(convolution_pd_t *s, char *buffer); +void init_info(deconvolution_pd_t *s, char *buffer); +void init_info(eltwise_pd_t *s, char *buffer); +void init_info(inner_product_pd_t *s, char *buffer); +void init_info(lrn_pd_t *s, char *buffer); +void init_info(pooling_pd_t *s, char *buffer); +void init_info(reorder_pd_t *s, char *buffer); +void init_info(rnn_pd_t *s, char *buffer); +void init_info(shuffle_pd_t *s, char *buffer); +void init_info(softmax_pd_t *s, char *buffer); +void init_info(sum_pd_t *s, char *buffer); + +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/common/z_magic.hpp b/thirdparty/oidn/mkl-dnn/src/common/z_magic.hpp new file mode 100644 index 000000000000..520bd4710b3b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/common/z_magic.hpp @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef Z_MAGIC_HPP +#define Z_MAGIC_HPP + +#define CHAIn2(a,b) a b +#define CHAIN2(a,b) CHAIn2(a,b) + +#define CONCAt2(a,b) a ## b +#define CONCAT2(a,b) CONCAt2(a,b) + +#define STRINGIFy(s) #s +#define STRINGIFY(s) STRINGIFy(s) + +#ifdef _MSC_VER +# define PRAGMA_MACRo(x) __pragma(x) +# define PRAGMA_MACRO(x) PRAGMA_MACRo(x) +#else +# define PRAGMA_MACRo(x) _Pragma(#x) +# define PRAGMA_MACRO(x) PRAGMA_MACRo(x) +#endif + +#define UNUSED(x) ((void)x) +#define MAYBE_UNUSED(x) UNUSED(x) + +#if defined(_WIN32) && !defined(__GNUC__) +#define __PRETTY_FUNCTION__ __FUNCSIG__ +#endif + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.cpp new file mode 100644 index 000000000000..7cf7822d9029 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.cpp @@ -0,0 +1,112 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "cpu_barrier.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace simple_barrier { + +void generate(jit_generator &code, Xbyak::Reg64 reg_ctx, + Xbyak::Reg64 reg_nthr) { +# define BAR_CTR_OFF offsetof(ctx_t, ctr) +# define BAR_SENSE_OFF offsetof(ctx_t, sense) + using namespace Xbyak; + + Xbyak::Reg64 reg_tmp = [&]() { + /* returns register which is neither reg_ctx nor reg_nthr */ + Xbyak::Reg64 regs[] = { util::rax, util::rbx, util::rcx }; + for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); ++i) + if (!utils::one_of(regs[i], reg_ctx, reg_nthr)) + return regs[i]; + return regs[0]; /* should not happen */ + }(); + + Label barrier_exit_label, barrier_exit_restore_label, spin_label; + + code.cmp(reg_nthr, 1); + code.jbe(barrier_exit_label); + + code.push(reg_tmp); + + /* take and save current sense */ + code.mov(reg_tmp, code.ptr[reg_ctx + BAR_SENSE_OFF]); + code.push(reg_tmp); + code.mov(reg_tmp, 1); + + if (mayiuse(avx512_mic)) { + code.prefetchwt1(code.ptr[reg_ctx + BAR_CTR_OFF]); + code.prefetchwt1(code.ptr[reg_ctx + BAR_CTR_OFF]); + } + + code.lock(); code.xadd(code.ptr[reg_ctx + BAR_CTR_OFF], reg_tmp); + code.add(reg_tmp, 1); + code.cmp(reg_tmp, reg_nthr); + code.pop(reg_tmp); /* restore previous sense */ + code.jne(spin_label); + + /* the last thread {{{ */ + code.mov(code.qword[reg_ctx + BAR_CTR_OFF], 0); // reset ctx + + // notify waiting threads + code.not_(reg_tmp); + code.mov(code.ptr[reg_ctx + BAR_SENSE_OFF], reg_tmp); + code.jmp(barrier_exit_restore_label); + /* }}} the last thread */ + + code.CodeGenerator::L(spin_label); + code.pause(); + code.cmp(reg_tmp, code.ptr[reg_ctx + BAR_SENSE_OFF]); + code.je(spin_label); + + code.CodeGenerator::L(barrier_exit_restore_label); + code.pop(reg_tmp); + + code.CodeGenerator::L(barrier_exit_label); +# undef BAR_CTR_OFF +# undef BAR_SENSE_OFF +} + +/** jit barrier generator */ +struct jit_t: public jit_generator { + void (*barrier)(ctx_t *ctx, size_t nthr); + + jit_t() { + generate(*this, abi_param1, abi_param2); + ret(); + barrier = reinterpret_cast(const_cast( + this->getCode())); + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_t) +}; + +void barrier(ctx_t *ctx, int nthr) { + static jit_t j; /* XXX: constructed on load ... */ + j.barrier(ctx, nthr); +} + +} + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.hpp new file mode 100644 index 000000000000..0f55e33aa84e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_barrier.hpp @@ -0,0 +1,60 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_BARRIER_HPP +#define CPU_BARRIER_HPP + +#include + +#include "jit_generator.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace simple_barrier { + +STRUCT_ALIGN(64, +struct ctx_t { + enum { CACHE_LINE_SIZE = 64 }; + volatile size_t ctr; + char pad1[CACHE_LINE_SIZE - 1 * sizeof(size_t)]; + volatile size_t sense; + char pad2[CACHE_LINE_SIZE - 1 * sizeof(size_t)]; +}); + +inline void ctx_init(ctx_t *ctx) { *ctx = utils::zero(); } +void barrier(ctx_t *ctx, int nthr); + +/** injects actual barrier implementation into another jitted code + * @params: + * code -- jit_generator object where the barrier is to be injected + * reg_ctx -- read-only register with pointer to the barrier context + * reg_nnthr -- read-only register with the # of synchronizing threads + */ +void generate(jit_generator &code, Xbyak::Reg64 reg_ctx, + Xbyak::Reg64 reg_nthr); + +} + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_pd.hpp new file mode 100644 index 000000000000..1ed5ad57b9f0 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_pd.hpp @@ -0,0 +1,40 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_BATCH_NORMALIZATION_PD_HPP +#define CPU_BATCH_NORMALIZATION_PD_HPP + +#include "batch_normalization_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_batch_normalization_fwd_pd_t: public batch_normalization_fwd_pd_t { + using batch_normalization_fwd_pd_t::batch_normalization_fwd_pd_t; +}; + +struct cpu_batch_normalization_bwd_pd_t: public batch_normalization_bwd_pd_t { + using batch_normalization_bwd_pd_t::batch_normalization_bwd_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.cpp new file mode 100644 index 000000000000..b8d5c4fcaf6d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.cpp @@ -0,0 +1,140 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +#include "cpu_batch_normalization_utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { +namespace bnorm_utils { + +void cache_balance(size_t working_set_size, dim_t C_blks, + dim_t &C_blks_per_iter, int64_t &iters) { + int nthrs = mkldnn_get_max_threads(); + int l3_size = get_cache_size(3, true) * nthrs / 2; + + C_blks_per_iter = l3_size / working_set_size; + + if (C_blks_per_iter == 0) + C_blks_per_iter = 1; + if (C_blks_per_iter > C_blks) + C_blks_per_iter = C_blks; + + iters = (C_blks + C_blks_per_iter - 1) / C_blks_per_iter; +} + +bool thread_balance(bool do_blocking, bool spatial_thr_allowed, int ithr, + int nthr, dim_t N, dim_t C_blks, dim_t SP, int &C_ithr, int &C_nthr, + dim_t &C_blk_s, dim_t &C_blk_e, int &N_ithr, int &N_nthr, dim_t &N_s, + dim_t &N_e, int &S_ithr, int &S_nthr, dim_t &S_s, dim_t &S_e) { + if (nthr <= C_blks || !mkldnn_thr_syncable()) { + C_ithr = ithr; C_nthr = nthr; + N_ithr = 0; N_nthr = 1; + S_ithr = 0; S_nthr = 1; + N_s = 0; N_e = N; S_s = 0; S_e = SP; + balance211(C_blks, C_nthr, C_ithr, C_blk_s, C_blk_e); + } else { + if (do_blocking) { + N_nthr = (int)nstl::min(N, nthr); + C_nthr = (int)nstl::min(C_blks, nthr / N_nthr); + S_nthr = (int)nstl::min(SP, nthr / (C_nthr * N_nthr)); + } else { + C_nthr = (int)math::gcd((dim_t)nthr, C_blks); + N_nthr = (int)nstl::min(N, nthr / C_nthr); + S_nthr = (int)nstl::min(SP, nthr / (C_nthr * N_nthr)); + } + + if (!spatial_thr_allowed) + S_nthr = 1; + + if (S_nthr < 1) S_nthr = 1; + if (ithr < C_nthr * N_nthr * S_nthr) { + N_ithr = (ithr / S_nthr) % N_nthr ; + C_ithr = ithr / (N_nthr * S_nthr); + S_ithr = ithr % S_nthr; + balance211(C_blks, C_nthr, C_ithr, C_blk_s, C_blk_e); + balance211(N, N_nthr, N_ithr, N_s, N_e); + balance211(SP, S_nthr, S_ithr, S_s, S_e); + } else { + S_ithr = N_ithr = C_ithr = -ithr; + S_s = S_e = N_s = N_e = C_blk_s = C_blk_e = -1; + } + } + + // spatial_thr_allowed is meant to help maintain + // consistent decisions about spatial threading + // between mutiple invocations of this routine. + // It is caller's responsibility to check the + // return value and pass it as a flag to the + // next call if needed. + if (S_nthr == 1) + spatial_thr_allowed = false; + + return spatial_thr_allowed; +} + +bool is_spatial_thr(const batch_normalization_pd_t *bdesc, int simd_w, + int data_size) { + if (!mkldnn_thr_syncable()) return false; + + dim_t nthr = mkldnn_get_max_threads(); + dim_t SP = bdesc->W() * bdesc->D() * bdesc->H(); + dim_t C_PADDED = memory_desc_wrapper(bdesc->src_md()) + .padded_dims()[1]; + assert(C_PADDED % simd_w == 0); + + size_t data = bdesc->MB() * C_PADDED * SP * data_size; + size_t l3_size_ = get_cache_size(3, true) * nthr / 2; + bool do_blocking = (data >= l3_size_ / 2 && l3_size_ > 0); + dim_t C_blks_per_iter{ 1 }, iters{ 1 }; + dim_t C_blks = C_PADDED / simd_w; + + if (do_blocking) { + int num_tensors = bdesc->is_fwd() ? 1 : 2; + size_t working_set_size + = (bdesc->MB() * SP * simd_w * data_size) * num_tensors; + cache_balance(working_set_size, C_blks, C_blks_per_iter, iters); + } + + // Spatial threading decision made in this function shall be consistent + // with thread_balance() behavior. + C_blks = do_blocking ? C_blks_per_iter : C_blks; + + if (nthr <= C_blks) return false; + + dim_t S_nthr = 1; + if (do_blocking) { + dim_t N_nthr = nstl::min(bdesc->MB(), nthr); + dim_t C_nthr = nstl::min(C_blks, nthr / N_nthr); + S_nthr = nstl::min(SP, nthr / (C_nthr * N_nthr)); + } else { + dim_t C_nthr = math::gcd(nthr, C_blks); + dim_t N_nthr = nstl::min(bdesc->MB(), nthr / C_nthr); + S_nthr = nstl::min(SP, nthr / (C_nthr * N_nthr)); + } + + return S_nthr > 1; +} + +} +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.hpp new file mode 100644 index 000000000000..0daef0716c91 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_batch_normalization_utils.hpp @@ -0,0 +1,43 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_BATCH_NORMALIZATION_UTILS_HPP +#define CPU_BATCH_NORMALIZATION_UTILS_HPP + +#include "batch_normalization_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { +namespace bnorm_utils { + +void cache_balance(size_t working_set_size, dim_t C_blks, + dim_t &C_blks_per_iter, int64_t &iters); + +bool thread_balance(bool do_blocking, bool spatial_thr_allowed, int ithr, + int nthr, dim_t N, dim_t C_blks, dim_t SP, int &C_ithr, int &C_nthr, + dim_t &C_blk_s, dim_t &C_blk_e, int &N_ithr, int &N_nthr, dim_t &N_s, + dim_t &N_e, int &S_ithr, int &S_nthr, dim_t &S_s, dim_t &S_e); + +bool is_spatial_thr(const batch_normalization_pd_t *bdesc, int simd_w, + int data_size); + +} +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat.cpp new file mode 100644 index 000000000000..b926491202ae --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat.cpp @@ -0,0 +1,51 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "cpu_engine.hpp" + +/* +#include "cpu/ref_concat.hpp" +#include "cpu/simple_concat.hpp" +*/ + +namespace mkldnn { +namespace impl { +namespace cpu { + +using cpd_create_f = mkldnn::impl::engine_t::concat_primitive_desc_create_f; + +namespace { +#define INSTANCE(...) __VA_ARGS__::pd_t::create +static const cpd_create_f cpu_concat_impl_list[] = { + /* + INSTANCE(simple_concat_t), + INSTANCE(simple_concat_t), + INSTANCE(simple_concat_t), + INSTANCE(simple_concat_t), + INSTANCE(ref_concat_t), + */ + nullptr, +}; +#undef INSTANCE +} + +const cpd_create_f *cpu_engine_t::get_concat_implementation_list() const { + return cpu_concat_impl_list; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat_pd.hpp new file mode 100644 index 000000000000..0b01bcf1633a --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_concat_pd.hpp @@ -0,0 +1,41 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_CONCAT_PD_HPP +#define CPU_CONCAT_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "concat_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_concat_pd_t: public concat_pd_t { + using concat_pd_t::concat_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_convolution_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_convolution_pd.hpp new file mode 100644 index 000000000000..52a38a229472 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_convolution_pd.hpp @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_CONVOLUTION_PD_HPP +#define CPU_CONVOLUTION_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "convolution_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_convolution_fwd_pd_t: public convolution_fwd_pd_t { + using convolution_fwd_pd_t::convolution_fwd_pd_t; + + bool has_padded_dst() const { + memory_desc_wrapper dst_d(&dst_md_); + return OC() != dst_d.padded_dims()[1]; + } + + bool wants_padded_bias() const { + if (!with_bias()) return false; + return has_padded_dst(); + } + + bool wants_zero_pad_dst(bool jit_impl = true) const { + if (!has_padded_dst()) return false; + const auto &po = attr()->post_ops_; + int idx; + if ((idx = po.find(primitive_kind::eltwise)) == -1) return false; + return !math::eltwise_fwd_preserves_zero(po.entry_[idx].eltwise.alg, + jit_impl); + } +}; + +struct cpu_convolution_bwd_data_pd_t: public convolution_bwd_data_pd_t { + using convolution_bwd_data_pd_t::convolution_bwd_data_pd_t; +}; + +struct cpu_convolution_bwd_weights_pd_t: public convolution_bwd_weights_pd_t { + using convolution_bwd_weights_pd_t::convolution_bwd_weights_pd_t; + + bool wants_padded_bias() const { + if (!with_bias()) return false; + memory_desc_wrapper diff_dst_d(&diff_dst_md_); + return OC() != diff_dst_d.padded_dims()[1]; + } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_deconvolution_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_deconvolution_pd.hpp new file mode 100644 index 000000000000..164c8601d708 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_deconvolution_pd.hpp @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_DECONVOLUTION_PD_HPP +#define CPU_DECONVOLUTION_PD_HPP + +#include + +#include "deconvolution_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_deconvolution_fwd_pd_t: public deconvolution_fwd_pd_t { + using deconvolution_fwd_pd_t::deconvolution_fwd_pd_t; +}; + +struct cpu_deconvolution_bwd_data_pd_t: public deconvolution_bwd_data_pd_t { + using deconvolution_bwd_data_pd_t::deconvolution_bwd_data_pd_t; +}; + +struct cpu_deconvolution_bwd_weights_pd_t: public deconvolution_bwd_weights_pd_t { + using deconvolution_bwd_weights_pd_t::deconvolution_bwd_weights_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_eltwise_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_eltwise_pd.hpp new file mode 100644 index 000000000000..c52f00026ecc --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_eltwise_pd.hpp @@ -0,0 +1,45 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_ELTWISE_PD_HPP +#define CPU_ELTWISE_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "eltwise_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_eltwise_fwd_pd_t: public eltwise_fwd_pd_t { + using eltwise_fwd_pd_t::eltwise_fwd_pd_t; +}; + +struct cpu_eltwise_bwd_pd_t: public eltwise_bwd_pd_t { + using eltwise_bwd_pd_t::eltwise_bwd_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.cpp new file mode 100644 index 000000000000..ce0a3667ad58 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.cpp @@ -0,0 +1,324 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "type_helpers.hpp" +#include "verbose.hpp" + +#include "cpu_engine.hpp" +#include "cpu_memory.hpp" + +//#include "cpu/rnn/ref_rnn.hpp" + +//#include "cpu/jit_avx512_core_x8s8s32x_1x1_convolution.hpp" +//#include "cpu/jit_avx512_common_1x1_convolution.hpp" +#include "cpu/jit_avx512_core_fp32_wino_conv_4x3.hpp" +#include "cpu/jit_avx512_common_convolution_winograd.hpp" +//#include "cpu/jit_avx512_core_x8s8s32x_convolution.hpp" +#include "cpu/jit_avx512_common_convolution.hpp" +//#include "cpu/jit_avx2_1x1_convolution.hpp" +//#include "cpu/jit_sse42_1x1_convolution.hpp" +#include "cpu/jit_avx2_convolution.hpp" +#include "cpu/jit_sse42_convolution.hpp" +//#include "cpu/gemm_convolution.hpp" +//#include "cpu/gemm_x8s8s32x_convolution.hpp" +//#include "cpu/ref_convolution.hpp" +//#include "cpu/jit_avx512_core_x8s8s32x_deconvolution.hpp" +//#include "cpu/jit_avx512_core_x8s8s32x_1x1_deconvolution.hpp" +//#include "cpu/ref_deconvolution.hpp" +//#include "cpu/ref_shuffle.hpp" +//#include "cpu/jit_uni_eltwise.hpp" +//#include "cpu/ref_eltwise.hpp" +//#include "cpu/ref_softmax.hpp" +#include "cpu/jit_uni_pooling.hpp" +//#include "cpu/jit_uni_i8i8_pooling.hpp" +//#include "cpu/ref_pooling.hpp" +//#include "cpu/nchw_pooling.hpp" +//#include "cpu/nhwc_pooling.hpp" +//#include "cpu/jit_avx512_common_lrn.hpp" +//#include "cpu/jit_uni_lrn.hpp" +//#include "cpu/ref_lrn.hpp" +//#include "cpu/jit_uni_batch_normalization.hpp" +//#include "cpu/ref_batch_normalization.hpp" +//#include "cpu/ncsp_batch_normalization.hpp" +//#include "cpu/nspc_batch_normalization.hpp" +//#include "cpu/ref_inner_product.hpp" +//#include "cpu/gemm_inner_product.hpp" +//#include "cpu/gemm_x8s8s32x_inner_product.hpp" +//#include "cpu/jit_uni_dw_convolution.hpp" +//#include "cpu/jit_avx512_core_u8s8s32x_wino_convolution.hpp" +#include "cpu/jit_avx512_core_fp32_wino_conv_2x3.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +status_t cpu_engine_t::memory_create(memory_t **memory, + const memory_desc_t *md, void *handle) { + auto _memory = new cpu_memory_t(this, md, handle); + if (_memory == nullptr) + return status::out_of_memory; + + status_t status = _memory->init(); + if (status != status::success) { + delete _memory; + return status; + } + + return safe_ptr_assign(*memory, _memory); +} + +using pd_create_f = mkldnn::impl::engine_t::primitive_desc_create_f; + +namespace { +using namespace mkldnn::impl::data_type; + +#define INSTANCE(...) &primitive_desc_t::create<__VA_ARGS__::pd_t> +static const pd_create_f cpu_impl_list[] = { + /* RNN */ + /* + INSTANCE(ref_rnn_fwd_f32_t), + INSTANCE(ref_rnn_fwd_u8s8_t), + INSTANCE(ref_rnn_bwd_f32_t), + */ + /* conv */ + /* + INSTANCE(jit_avx512_common_dw_convolution_fwd_t), + INSTANCE(jit_avx512_common_dw_convolution_bwd_data_t), + INSTANCE(jit_avx512_common_dw_convolution_bwd_weights_t), + INSTANCE(jit_avx512_common_1x1_convolution_fwd_f32_t), + INSTANCE(jit_avx512_common_1x1_convolution_bwd_data_f32_t), + INSTANCE(jit_avx512_common_1x1_convolution_bwd_weights_t), + */ + INSTANCE(jit_avx512_core_fp32_wino_conv_2x3_fwd_t), + INSTANCE(jit_avx512_core_fp32_wino_conv_4x3_fwd_t), + //INSTANCE(jit_avx512_core_fp32_wino_conv_4x3_bwd_data_t), + //INSTANCE(jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t), + INSTANCE(jit_avx512_common_convolution_winograd_fwd_t), + //INSTANCE(jit_avx512_common_convolution_winograd_bwd_data_t), + //INSTANCE(jit_avx512_common_convolution_winograd_bwd_weights_t), + INSTANCE(jit_avx512_common_convolution_fwd_t), + //INSTANCE(jit_avx512_common_convolution_bwd_data_t), + //INSTANCE(jit_avx512_common_convolution_bwd_weights_t), + /* + INSTANCE(jit_avx2_dw_convolution_fwd_t), + INSTANCE(jit_avx2_dw_convolution_bwd_data_t), + INSTANCE(jit_avx2_dw_convolution_bwd_weights_t), + INSTANCE(jit_avx2_1x1_convolution_fwd_t), + INSTANCE(jit_avx2_1x1_convolution_bwd_data_t), + INSTANCE(jit_avx2_1x1_convolution_bwd_weights_t), + INSTANCE(jit_sse42_dw_convolution_fwd_t), + INSTANCE(jit_sse42_dw_convolution_bwd_data_t), + INSTANCE(jit_sse42_dw_convolution_bwd_weights_t), + INSTANCE(jit_sse42_1x1_convolution_fwd_t), + */ + INSTANCE(jit_avx2_convolution_fwd_t), + //INSTANCE(jit_avx2_convolution_bwd_data_t), + //INSTANCE(jit_avx2_convolution_bwd_weights_t), + INSTANCE(jit_sse42_convolution_fwd_t), + /* + INSTANCE(gemm_convolution_fwd_t), + INSTANCE(gemm_convolution_bwd_data_t), + INSTANCE(gemm_convolution_bwd_weights_t), + INSTANCE(ref_convolution_fwd_t), + INSTANCE(ref_convolution_bwd_data_t), + INSTANCE(ref_convolution_bwd_weights_t), + */ + /* conv (int) */ + /* + INSTANCE(jit_avx512_core_u8s8s32x_wino_convolution_fwd_t), + INSTANCE(jit_avx512_core_u8s8s32x_wino_convolution_fwd_t), + INSTANCE(jit_avx512_core_u8s8s32x_wino_convolution_fwd_t), + INSTANCE(jit_avx512_core_u8s8s32x_wino_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_x8s8s32x_convolution_fwd_t), + INSTANCE(_gemm_u8s8s32x_convolution_bwd_data_t), + INSTANCE(_gemm_u8s8s32x_convolution_bwd_data_t), + INSTANCE(_gemm_u8s8s32x_convolution_bwd_data_t), + INSTANCE(_gemm_u8s8s32x_convolution_bwd_data_t), + INSTANCE(ref_convolution_fwd_t), + INSTANCE(ref_convolution_fwd_t), + INSTANCE(ref_convolution_fwd_t), + INSTANCE(ref_convolution_fwd_t), + INSTANCE(ref_convolution_bwd_data_t), + INSTANCE(ref_convolution_bwd_data_t), + INSTANCE(ref_convolution_bwd_data_t), + INSTANCE(ref_convolution_bwd_data_t), + */ + /* deconv */ + /* + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(_jit_avx512_core_x8s8s32x_deconvolution_fwd_t), + INSTANCE(ref_deconvolution_bwd_weights_t), + INSTANCE(ref_deconvolution_bwd_data_t), + INSTANCE(ref_deconvolution_fwd_t), + */ + /* shuffle */ + /* + INSTANCE(ref_shuffle_t<4>), // f32 or s32 + INSTANCE(ref_shuffle_t<1>), // s8 or u8 + */ + /* eltwise */ + /* + INSTANCE(jit_uni_eltwise_fwd_t), + INSTANCE(jit_uni_eltwise_bwd_t), + INSTANCE(jit_uni_eltwise_fwd_t), + INSTANCE(jit_uni_eltwise_bwd_t), + INSTANCE(jit_uni_eltwise_fwd_t), + INSTANCE(jit_uni_eltwise_bwd_t), + INSTANCE(ref_eltwise_fwd_t), + INSTANCE(ref_eltwise_bwd_t), + */ + /* eltwise (int) */ + /* + INSTANCE(ref_eltwise_fwd_t), + INSTANCE(ref_eltwise_fwd_t), + INSTANCE(ref_eltwise_fwd_t), + INSTANCE(ref_eltwise_bwd_t), + */ + /* softmax */ + /* + INSTANCE(ref_softmax_fwd_t), + INSTANCE(ref_softmax_bwd_t), + */ + /* pool */ + INSTANCE(jit_uni_pooling_fwd_t), + //INSTANCE(jit_uni_pooling_bwd_t), + INSTANCE(jit_uni_pooling_fwd_t), + //INSTANCE(jit_uni_pooling_bwd_t), + INSTANCE(jit_uni_pooling_fwd_t), + //INSTANCE(jit_uni_pooling_bwd_t), + /* + INSTANCE(nchw_pooling_fwd_t), + INSTANCE(nchw_pooling_bwd_t), + INSTANCE(nhwc_pooling_fwd_t), + INSTANCE(nhwc_pooling_bwd_t), + INSTANCE(ref_pooling_fwd_t), + INSTANCE(ref_pooling_bwd_t), + */ + /* pool (int) */ + /* + INSTANCE(jit_uni_i8i8_pooling_fwd_t), + INSTANCE(jit_uni_i8i8_pooling_fwd_t), + INSTANCE(ref_pooling_fwd_t), + INSTANCE(ref_pooling_fwd_t), + INSTANCE(ref_pooling_fwd_t), + INSTANCE(ref_pooling_bwd_t), + */ + /* lrn */ + /* + INSTANCE(jit_avx512_common_lrn_fwd_t), + INSTANCE(jit_avx512_common_lrn_bwd_t), + INSTANCE(jit_uni_lrn_fwd_t), + INSTANCE(jit_uni_lrn_bwd_t), + INSTANCE(jit_uni_lrn_fwd_t), + INSTANCE(ref_lrn_fwd_t), + INSTANCE(ref_lrn_bwd_t), + */ + /* batch normalization */ + /* + INSTANCE(jit_uni_batch_normalization_fwd_t), + INSTANCE(jit_uni_batch_normalization_bwd_t), + INSTANCE(jit_uni_batch_normalization_fwd_t), + INSTANCE(jit_uni_batch_normalization_bwd_t), + INSTANCE(jit_uni_batch_normalization_fwd_t), + INSTANCE(jit_uni_batch_normalization_bwd_t), + INSTANCE(ncsp_batch_normalization_fwd_t), + INSTANCE(ncsp_batch_normalization_bwd_t), + INSTANCE(nspc_batch_normalization_fwd_t), + INSTANCE(nspc_batch_normalization_bwd_t), + INSTANCE(ref_batch_normalization_fwd_t), + INSTANCE(ref_batch_normalization_bwd_t), + INSTANCE(ref_batch_normalization_fwd_t), + */ + /* inner product */ + /* + INSTANCE(gemm_inner_product_fwd_t), + INSTANCE(gemm_inner_product_bwd_data_t), + INSTANCE(gemm_inner_product_bwd_weights_t), + INSTANCE(ref_inner_product_fwd_t), + INSTANCE(ref_inner_product_bwd_data_t), + INSTANCE(ref_inner_product_bwd_weights_t), + */ + /* inner product (int) */ + /* + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(gemm_x8s8s32x_inner_product_fwd_t), + INSTANCE(ref_inner_product_fwd_t), + INSTANCE(ref_inner_product_fwd_t), + INSTANCE(ref_inner_product_fwd_t), + INSTANCE(ref_inner_product_fwd_t), + */ + /* eol */ + nullptr, +}; +#undef INSTANCE +} + +const pd_create_f* cpu_engine_t::get_implementation_list() const { + return cpu_impl_list; +} + +cpu_engine_factory_t engine_factory; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.hpp new file mode 100644 index 000000000000..e4c877ee05a8 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_engine.hpp @@ -0,0 +1,70 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_ENGINE_HPP +#define CPU_ENGINE_HPP + +#include + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "../common/engine.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +class cpu_engine_t: public engine_t { +public: + cpu_engine_t(): engine_t(engine_kind::cpu) {} + + /* implementation part */ + + virtual status_t memory_create(memory_t **memory, + const memory_desc_t *md, void *handle) override; + + virtual const concat_primitive_desc_create_f* + get_concat_implementation_list() const override; + virtual const reorder_primitive_desc_create_f* + get_reorder_implementation_list() const override; + virtual const sum_primitive_desc_create_f* + get_sum_implementation_list() const override; + virtual const primitive_desc_create_f* + get_implementation_list() const override; +}; + +class cpu_engine_factory_t: public engine_factory_t { +public: + virtual size_t count() const override { return 1; } + virtual engine_kind_t kind() const override { return engine_kind::cpu; } + virtual status_t engine_create(engine_t **engine, + size_t index) const override { + assert(index == 0); + *engine = new cpu_engine_t(); + return status::success; + }; +}; + +extern cpu_engine_factory_t engine_factory; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_inner_product_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_inner_product_pd.hpp new file mode 100644 index 000000000000..5880d3450cc7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_inner_product_pd.hpp @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_INNER_PRODUCT_PD_HPP +#define CPU_INNER_PRODUCT_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "inner_product_pd.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace { +inline bool dense_gemm_consitency_check(const memory_desc_wrapper &src_d, + const memory_desc_wrapper &wei_d, const memory_desc_wrapper &dst_d) { + using namespace utils; + + auto strides_compatible = [&]() { + bool ok = true; + auto w_str = wei_d.blocking_desc().strides; + auto d_str = src_d.blocking_desc().strides; + for (int i = 1; i < src_d.ndims() - 1; i++) { + ok = ok && w_str[i] / d_str[i] == w_str[i + 1] / d_str[i + 1]; + } + return ok && one_of(w_str[1] / d_str[1], 1, wei_d.padded_dims()[0]); + }; + return true && src_d.is_blocking_desc() && wei_d.is_blocking_desc() + && src_d.ndims() == wei_d.ndims() + && src_d.blocking_desc().inner_nblks + == wei_d.blocking_desc().inner_nblks + && utils::one_of(src_d.blocking_desc().inner_nblks, 0, 1) + && array_cmp(src_d.blocking_desc().inner_blks, + wei_d.blocking_desc().inner_blks, + wei_d.blocking_desc().inner_nblks) + && array_cmp(src_d.blocking_desc().inner_idxs, + wei_d.blocking_desc().inner_idxs, + wei_d.blocking_desc().inner_nblks) + && strides_compatible() + && dst_d.matches_tag(format_tag::nc) + && src_d.only_padded_dim(1) + && wei_d.only_padded_dim(1) + && src_d.padded_dims()[1] == wei_d.padded_dims()[1] + && src_d.is_dense(true) + && dst_d.is_dense() + && wei_d.is_dense(true); +} +} + +struct cpu_inner_product_fwd_pd_t: public inner_product_fwd_pd_t { + using inner_product_fwd_pd_t::inner_product_fwd_pd_t; +}; + +struct cpu_inner_product_bwd_data_pd_t: public inner_product_bwd_data_pd_t { + using inner_product_bwd_data_pd_t::inner_product_bwd_data_pd_t; +}; + +struct cpu_inner_product_bwd_weights_pd_t: public inner_product_bwd_weights_pd_t { + using inner_product_bwd_weights_pd_t::inner_product_bwd_weights_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_isa_traits.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_isa_traits.hpp new file mode 100644 index 000000000000..da6e9dac8ea7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_isa_traits.hpp @@ -0,0 +1,151 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_ISA_TRAITS_HPP +#define CPU_ISA_TRAITS_HPP + +#include + +#define XBYAK64 +#define XBYAK_NO_OP_NAMES +/* in order to make selinux happy memory that would be marked with X-bit should + * be obtained with mmap */ +#define XBYAK_USE_MMAP_ALLOCATOR +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +/* turn off `size_t to other-type implicit casting` warning + * currently we have a lot of jit-generated instructions that + * take uint32_t, but we pass size_t (e.g. due to using sizeof). + * FIXME: replace size_t parameters with the appropriate ones */ +#pragma warning (disable: 4267) +#endif +#include "xbyak/xbyak.h" +#include "xbyak/xbyak_util.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +typedef enum { + isa_any, + sse41, + sse42, + avx, + avx2, + avx512_common, + avx512_core, + avx512_core_vnni, + avx512_mic, + avx512_mic_4ops, +} cpu_isa_t; + +template struct cpu_isa_traits {}; /* ::vlen -> 32 (for avx2) */ + +template <> struct cpu_isa_traits { + typedef Xbyak::Xmm Vmm; + static constexpr int vlen_shift = 4; + static constexpr int vlen = 16; + static constexpr int n_vregs = 16; +}; +template <> struct cpu_isa_traits { + typedef Xbyak::Ymm Vmm; + static constexpr int vlen_shift = 5; + static constexpr int vlen = 32; + static constexpr int n_vregs = 16; +}; +template <> struct cpu_isa_traits: + public cpu_isa_traits {}; + +template <> struct cpu_isa_traits { + typedef Xbyak::Zmm Vmm; + static constexpr int vlen_shift = 6; + static constexpr int vlen = 64; + static constexpr int n_vregs = 32; +}; +template <> struct cpu_isa_traits: + public cpu_isa_traits {}; + +template <> struct cpu_isa_traits: + public cpu_isa_traits {}; + +template <> struct cpu_isa_traits: + public cpu_isa_traits {}; + +namespace { + +static Xbyak::util::Cpu cpu; +static inline bool mayiuse(const cpu_isa_t cpu_isa) { + using namespace Xbyak::util; + + switch (cpu_isa) { + case sse41: + case sse42: + // FIXME: SSE4.2 is actually NOT required + //return cpu.has(Cpu::tSSE42); + return cpu.has(Cpu::tSSE41); + case avx: + return cpu.has(Cpu::tAVX); + case avx2: + return cpu.has(Cpu::tAVX2); + case avx512_common: + return cpu.has(Cpu::tAVX512F); + case avx512_core: + return true + && cpu.has(Cpu::tAVX512F) + && cpu.has(Cpu::tAVX512BW) + && cpu.has(Cpu::tAVX512VL) + && cpu.has(Cpu::tAVX512DQ); + case avx512_core_vnni: + return true + && cpu.has(Cpu::tAVX512F) + && cpu.has(Cpu::tAVX512BW) + && cpu.has(Cpu::tAVX512VL) + && cpu.has(Cpu::tAVX512DQ) + && cpu.has(Cpu::tAVX512_VNNI); + case avx512_mic: + return true + && cpu.has(Cpu::tAVX512F) + && cpu.has(Cpu::tAVX512CD) + && cpu.has(Cpu::tAVX512ER) + && cpu.has(Cpu::tAVX512PF); + case avx512_mic_4ops: + return true + && mayiuse(avx512_mic) + && cpu.has(Cpu::tAVX512_4FMAPS) + && cpu.has(Cpu::tAVX512_4VNNIW); + case isa_any: + return true; + } + return false; +} +} + +/* whatever is required to generate string literals... */ +#include "z_magic.hpp" +#define JIT_IMPL_NAME_HELPER(prefix, isa, suffix_if_any) \ + (isa == sse42 ? prefix STRINGIFY(sse42) : \ + (isa == avx ? prefix STRINGIFY(avx) : \ + (isa == avx2 ? prefix STRINGIFY(avx2) : \ + (isa == avx512_common ? prefix STRINGIFY(avx512_common) : \ + (isa == avx512_core ? prefix STRINGIFY(avx512_core) : \ + (isa == avx512_mic ? prefix STRINGIFY(avx512_mic) : \ + (isa == avx512_mic_4ops ? prefix STRINGIFY(avx512_mic_4ops) : \ + prefix suffix_if_any))))))) + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_lrn_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_lrn_pd.hpp new file mode 100644 index 000000000000..49988f4c2dd4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_lrn_pd.hpp @@ -0,0 +1,42 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_LRN_PD_HPP +#define CPU_LRN_PD_HPP + +#include + +#include "lrn_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_lrn_fwd_pd_t: public lrn_fwd_pd_t { + using lrn_fwd_pd_t::lrn_fwd_pd_t; +}; + +struct cpu_lrn_bwd_pd_t: public lrn_bwd_pd_t { + using lrn_bwd_pd_t::lrn_bwd_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.cpp new file mode 100644 index 000000000000..3c0624cf46d9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.cpp @@ -0,0 +1,277 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "mkldnn_traits.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_memory.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl; +using namespace mkldnn::impl::data_type; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::format_tag; + +enum blk_kind_t { a, b, c, ab, ba, bc, cb }; + +template +void typed_zero_pad_blk( + const memory_desc_wrapper &m_d, typename prec_traits
::type *data) { + using data_t = typename prec_traits
::type; + const auto &dims = m_d.dims(); + const auto &pdims = m_d.padded_dims(); + const auto &blk = m_d.blocking_desc(); + auto dim_is_blocked = [&](int dim) { + for (int i = 0; i < blk.inner_nblks; i++) + if (blk.inner_idxs[i] == dim) + return true; + return false; + }; + bool A_blocked = dim_is_blocked(0), B_blocked = dim_is_blocked(1), + C_blocked = dim_is_blocked(2); + + assert(blk.inner_nblks < 4); + assert((A_blocked || B_blocked || C_blocked) || (A_blocked && B_blocked) + || (C_blocked && B_blocked)); + + const int a_tail_s = A_blocked ? dims[0] % blksize : 0; + const int b_tail_s = B_blocked ? dims[1] % blksize : 0; + const int c_tail_s = C_blocked ? dims[2] % blksize : 0; + assert(a_tail_s || b_tail_s || c_tail_s); + + const int A = A_blocked ? pdims[0] / blksize : dims[0]; + const int B = B_blocked ? pdims[1] / blksize : dims[1]; + const int C = C_blocked ? pdims[2] / blksize : dims[2]; + const int D = m_d.ndims() > 3 ? dims[3] : 1; + const int E = m_d.ndims() > 4 ? dims[4] : 1; + const int F = m_d.ndims() > 5 ? dims[5] : 1; + const int inner_blk = blk.inner_nblks == 3 ? blk.inner_blks[2] : 1; + + auto zeroize_tail = [&](data_t *d, const int tail_s) { + for (int b = tail_s; b < blksize; ++b) + d[b] = 0; + }; + auto zeroize_tail_inner = [&](data_t *d, const int tail_s) { + for (int b1 = 0; b1 < blksize; ++b1) + for (int b2 = tail_s; b2 < blksize; ++b2) + d[(b1 / inner_blk) * blksize * inner_blk + inner_blk * b2 + + b1 % inner_blk] + = 0; + }; + auto zeroize_tail_outer = [&](data_t *d, const int tail_s) { + for (int b1 = tail_s; b1 < blksize; ++b1) + for (int b2 = 0; b2 < blksize; ++b2) + d[(b1 / inner_blk) * blksize * inner_blk + inner_blk * b2 + + b1 % inner_blk] + = 0; + }; + + if (c_tail_s) { + parallel_nd(A, B, D, E, F, [&](int a, int b, int d, int e, int f) { + auto x = &data[m_d.blk_off(a, b, C - 1, d, e, f)]; + if (blk_kind == c) + zeroize_tail(x, c_tail_s); + else if (blk_kind == bc) + zeroize_tail_inner(x, c_tail_s); + else if (blk_kind == cb) + zeroize_tail_outer(x, c_tail_s); + }); + } + + if (b_tail_s) { + parallel_nd(A, C, D, E, F, [&](int a, int c, int d, int e, int f) { + auto x = &data[m_d.blk_off(a, B - 1, c, d, e, f)]; + if (blk_kind == b) + zeroize_tail(x, b_tail_s); + else if (blk_kind == ab || blk_kind == cb) + zeroize_tail_inner(x, b_tail_s); + else if (blk_kind == ba || blk_kind == bc) + zeroize_tail_outer(x, b_tail_s); + }); + } + + if (a_tail_s) { + parallel_nd(B, C, D, E, F, [&](int b, int c, int d, int e, int f) { + auto x = &data[m_d.blk_off(A - 1, b, c, d, e, f)]; + if (blk_kind == a) + zeroize_tail(x, a_tail_s); + else if (blk_kind == ba) + zeroize_tail_inner(x, a_tail_s); + else if (blk_kind == ab) + zeroize_tail_outer(x, a_tail_s); + }); + } +} + +/* + * all + */ +template +void typed_zero_pad_generic_blocked( + const memory_desc_wrapper &m_d, typename prec_traits
::type *data) { + const int ndims = m_d.ndims(); + const auto &dims = m_d.dims(); + const auto &pdims = m_d.padded_dims(); + + const ptrdiff_t nelems = (ptrdiff_t)m_d.nelems(true); + + /* [D_0] .. [D_k][D_k+1] .. [D_ndim - 1] + * | \ / + * | --------------------- + * has contiguous + * padding + * + * step <-- D_k+1 * ... * D_ndims-1 + * step_dim <-- k + */ + + ptrdiff_t step = 1; + int step_dim = ndims - 1; + for (; step_dim >= 0; --step_dim) { + if (dims[step_dim] != pdims[step_dim]) + break; + step *= dims[step_dim]; + } + + assert(step_dim >= 0 && "no zero padding is required"); + if (step_dim < 0) + return; + + parallel_nd(nelems / step, [&](ptrdiff_t e1) { + bool need_zero = false; + + ptrdiff_t idx = e1; + for (int d = step_dim; d >= 0; --d) { + if (idx % pdims[d] >= dims[d]) { + need_zero = true; + break; + } + idx /= pdims[d]; + } + + if (need_zero) { + for (ptrdiff_t e0 = 0; e0 < step; ++e0) + data[m_d.off_l(e1 * step + e0, true)] = 0; + } + }); +} + +template +status_t cpu_memory_t::typed_zero_pad() const { + const memory_desc_wrapper mdw(md()); + + if (mdw.format_kind() != format_kind::blocked) + return unimplemented; + + if (mdw.nelems(false) == mdw.nelems(true)) + return success; + + auto *data = (typename prec_traits
::type *)data_; + auto blk = mdw.blocking_desc(); + + auto get_blksize = [&](int ind) { + int blksize = 1; + for (int i = 0; i < blk.inner_nblks; i++) { + if (blk.inner_idxs[i] == ind) + blksize *= blk.inner_blks[i]; + } + return blksize; + }; + const int blksize = get_blksize(blk.inner_idxs[0]); + +# define CASE(blksize_, blk_kind) \ + do { \ + if (blksize == blksize_) { \ + typed_zero_pad_blk(mdw, data); \ + return success; \ + } \ + } while(0) + + switch (blk.inner_nblks) { + case 1: + if (blk.inner_idxs[0] == 0) { + CASE(4, a); + CASE(8, a); + CASE(16, a); + } else if (blk.inner_idxs[0] == 1) { + CASE(4, b); + CASE(8, b); + CASE(16, b); + } + break; + case 2: + case 3: + if (!IMPLICATION(blk.inner_nblks == 3, + blk.inner_idxs[0] == blk.inner_idxs[2])) + break; + + if (blk.inner_idxs[0] == 0 && blk.inner_idxs[1] == 1) { + CASE(4, ab); + CASE(8, ab); + CASE(16, ab); + } else if (blk.inner_idxs[0] == 1 && blk.inner_idxs[1] == 0) { + CASE(4, ba); + CASE(8, ba); + CASE(16, ba); + } + if (blk.inner_idxs[0] == 1 && blk.inner_idxs[1] == 2) { + CASE(4, bc); + CASE(8, bc); + CASE(16, bc); + } else if (blk.inner_idxs[0] == 2 && blk.inner_idxs[1] == 1) { + CASE(4, cb); + CASE(8, cb); + CASE(16, cb); + } + break; + default: break; + } + +# undef CASE + + // the last line of defence + typed_zero_pad_generic_blocked
(mdw, data); + return success; +} + +status_t cpu_memory_t::zero_pad() const { + memory_desc_wrapper mdw(md()); + const bool skip_zeroing = false + || data_ == nullptr + || mdw.is_zero() + || !mdw.is_blocking_desc(); + if (skip_zeroing) return success; + + switch (mdw.data_type()) { + case f32: return typed_zero_pad(); + case s32: return typed_zero_pad(); + case s8: return typed_zero_pad(); + case u8: return typed_zero_pad(); + default: assert(!"memory is undefined"); return unimplemented; + } + return unimplemented; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.hpp new file mode 100644 index 000000000000..2c01bcc6af7d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_memory.hpp @@ -0,0 +1,89 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_MEMORY_HPP +#define CPU_MEMORY_HPP + +#include + +#include "c_types_map.hpp" +#include "memory.hpp" +#include "memory_desc_wrapper.hpp" + +#include "cpu_engine.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_memory_t: public memory_t { + cpu_memory_t(cpu_engine_t *engine, const memory_desc_t *md, void *handle) + : memory_t(engine, md) + , own_data_(handle == MKLDNN_NATIVE_HANDLE_ALLOCATE) + , data_((char *)handle) {} + + cpu_memory_t(cpu_engine_t *engine, const memory_desc_t *md) + : cpu_memory_t(engine, md, nullptr) {} + + ~cpu_memory_t() { if (own_data_) free(data_); } + + virtual status_t init() override { + if (own_data_) { + data_ = nullptr; + const size_t size = memory_desc_wrapper(this->md()).size(); + if (size) { + data_ = (char *)malloc(size, 64); + if (data_ == nullptr) + return status::out_of_memory; + } + } + return zero_pad(); + } + + cpu_engine_t *engine() const { return (cpu_engine_t *)memory_t::engine(); } + + virtual status_t get_data_handle(void **handle) const override { + *handle = static_cast(data_); + return status::success; + } + + virtual mkldnn::impl::status_t set_data_handle(void *handle) override { + if (own_data_) { free(data_); own_data_ = false; } + data_ = static_cast(handle); + return zero_pad(); + } + + virtual mkldnn::impl::status_t zero_pad() const override; + +private: + bool own_data_; + char *data_; + + template + mkldnn::impl::status_t typed_zero_pad() const; + + cpu_memory_t(const cpu_memory_t &) = delete; + cpu_memory_t &operator=(const cpu_memory_t &) = delete; + cpu_memory_t &operator=(cpu_memory_t &&) = delete; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_pooling_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_pooling_pd.hpp new file mode 100644 index 000000000000..ac2daa415e18 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_pooling_pd.hpp @@ -0,0 +1,40 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_POOLING_PD_HPP +#define CPU_POOLING_PD_HPP + +#include "pooling_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_pooling_fwd_pd_t: public pooling_fwd_pd_t { + using pooling_fwd_pd_t::pooling_fwd_pd_t; +}; + +struct cpu_pooling_bwd_pd_t: public pooling_bwd_pd_t { + using pooling_bwd_pd_t::pooling_bwd_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_primitive.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_primitive.hpp new file mode 100644 index 000000000000..56127f36c274 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_primitive.hpp @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_PRIMITIVE_HPP +#define CPU_PRIMITIVE_HPP + +#include "mkldnn.h" + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "primitive.hpp" +#include "scratchpad.hpp" + +#define CTX_IN_MEM(type, arg) static_cast(ctx.input(arg)) +#define CTX_OUT_MEM(type, arg) static_cast(ctx.output(arg)) + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_memory_t; + +struct cpu_primitive_t: public primitive_t { + cpu_primitive_t(const primitive_desc_t *pd, + bool use_global_scratchpad = false) + : primitive_t(pd) + , scratchpad_buffer_(nullptr) + , global_scratchpad_(nullptr) + { + const size_t scratchpad_size = + this->pd()->scratchpad_size(scratchpad_mode::library); + + if (scratchpad_size) { + if (use_global_scratchpad) + global_scratchpad_ = create_scratchpad(scratchpad_size); + else + scratchpad_buffer_ = malloc(scratchpad_size, 64); + } + } + + virtual ~cpu_primitive_t() { + delete global_scratchpad_; + free(scratchpad_buffer_); + } + +protected: + memory_tracking::grantor_t scratchpad(const exec_ctx_t &ctx) const { + void *ptr = nullptr; + if (pd()->attr()->scratchpad_mode_ == scratchpad_mode::user) { + ptr = CTX_OUT_MEM(void *, MKLDNN_ARG_SCRATCHPAD); + } else { + ptr = global_scratchpad_ + ? global_scratchpad_->get() : scratchpad_buffer_; + } + + return pd()->scratchpad_registry().grantor(ptr); + } + +private: + void *scratchpad_buffer_; + scratchpad_t *global_scratchpad_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.cpp new file mode 100644 index 000000000000..1d41ac5ceab7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.cpp @@ -0,0 +1,544 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "mkldnn_thread.hpp" +#include "mkldnn_types.h" +#include "nstl.hpp" +#include "utils.hpp" + +#include "cpu_reducer.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace memory_tracking::names; + +void reduce_balancer_t::balance() { + using namespace nstl; + using namespace utils; + + assert(nthr_ > 0 && job_size_ > 0 && njobs_ > 0 && reduction_size_ > 0); + + const int job_complexity = 1; + + const int min_njobs_per_group = max(1, njobs_ / nthr_); + const int max_njobs_per_group = max(1, + static_cast(max_buffer_size_ / (nthr_ * job_size_))); + + /* initial guess */ + int ngroups = min(njobs_ / min_njobs_per_group, nthr_); + int nthr_per_group = syncable_ ? min(nthr_ / ngroups, reduction_size_) : 1; + int njobs_per_group_ub = div_up(njobs_, ngroups); + + /* rough upper-bound estimation, will be fixed during brute force */ + size_t thread_complexity_ub = njobs_ * job_size_ * reduction_size_; + + /* brute force parameters for the best balance... */ + for (int c_njobs_per_group = min_njobs_per_group; + c_njobs_per_group < njobs_; ++c_njobs_per_group) { + /* current assumption */ + int c_ngroups = min(njobs_ / c_njobs_per_group, nthr_); + int c_nthr_per_group = syncable_ + ? min(nthr_ / c_ngroups, reduction_size_) : 1; + int c_njobs_per_group_ub = div_up(njobs_, c_ngroups); + + if (c_nthr_per_group > 1 && c_njobs_per_group_ub > max_njobs_per_group) + continue; + + int c_thread_reduction_ub = div_up(reduction_size_, c_nthr_per_group); + size_t c_group_size_ub = job_size_ * c_njobs_per_group_ub; + size_t c_thread_complexity_ub = c_group_size_ub * ( + job_complexity * c_thread_reduction_ub + + (c_nthr_per_group != 1)); + + if (c_thread_complexity_ub < thread_complexity_ub) { + ngroups = c_ngroups; + nthr_per_group = c_nthr_per_group; + njobs_per_group_ub = c_njobs_per_group_ub; + thread_complexity_ub = c_thread_complexity_ub; + } + } + + assert(njobs_per_group_ub <= max_njobs_per_group || nthr_per_group == 1); + assert(ngroups * nthr_per_group <= nthr_); + assert((size_t)njobs_per_group_ub * job_size_ * nthr_ <= max_buffer_size_ + || nthr_per_group == 1); /* no reduction buffer overflow */ + assert(IMPLICATION(!syncable_, nthr_per_group == 1)); + + ngroups_ = ngroups; + nthr_per_group_ = nthr_per_group; + njobs_per_group_ub_ = njobs_per_group_ub; +} + +/* reducer jit-ted driver */ + +using namespace Xbyak; + +template +struct reducer_2d_driver_t: public c_compatible { + typedef typename prec_traits::type data_t; + + reducer_2d_driver_t(int n_src, size_t src_ld, + size_t src_step, size_t dst_step, bool nullify_dst) + : n_src_(n_src), src_ld_(src_ld), src_step_(src_step) + , dst_step_(dst_step), nullify_dst_(nullify_dst), ker_(nullptr) {} + virtual ~reducer_2d_driver_t() {} + void operator()(data_t *dst, const data_t *srcs, size_t ny, size_t nx) + { assert(ker_); ker_(dst, srcs, ny, nx); } + +protected: + int n_src_; + size_t src_ld_, src_step_, dst_step_; + bool nullify_dst_; + void (*ker_)(data_t *dst, const data_t *srcs, size_t ny, size_t nx); +}; + +template +struct reducer_2d_driver_f_s_32_t: public reducer_2d_driver_t, + public jit_generator +{ + DECLARE_CPU_JIT_AUX_FUNCTIONS(reducer_2d_driver_f_s_32_t) + + /* cpu specific part */ + using Vmm = typename utils::conditional::type; + const AddressFrame &vmmword = (isa == avx2) ? yword : zword; + void uni_vadd(const Xmm& x1, const Xmm& x2, const Operand& op) + { if (data_type == data_type::f32) vaddps(x1, x2, op); + else vpaddd(x1, x2, op); } + void uni_add(const Xmm& x1, const Operand& op) + { if (data_type == data_type::f32) addss(x1, op); else paddd(x1, op); } + + const int vlen = cpu_isa_traits::vlen; + const int typesize + = sizeof(typename mkldnn::impl::prec_traits::type); + Xbyak::Reg64 reg_dst = abi_param1; + Xbyak::Reg64 reg_src = abi_param2; + Xbyak::Reg64 reg_ny = abi_param3; + Xbyak::Reg64 reg_nx = abi_param4; + + Xbyak::Reg64 reg_x = rax; + Xbyak::Reg64 reg_src_id = r10; + + reducer_2d_driver_f_s_32_t(int n_src, size_t src_ld, size_t src_step, + size_t dst_step, bool nullify_dst) + : reducer_2d_driver_t(n_src, src_ld, src_step, + dst_step, nullify_dst) + { generate(); } + + void nullify_dst(int nloads, int load_len) { + UNUSED(load_len); + for (int i = 0; i < nloads; ++i) + uni_vpxor(Vmm(i), Vmm(i), Vmm(i)); + /* prefetches[dst] ? */ + } + + void load_dst(int nloads, int load_len) { + for (int i = 0; i < nloads; ++i) { + if (load_len == typesize) + movd(Xmm(i), ptr[reg_dst + i * load_len]); + else if (load_len == vlen) + vmovups(Vmm(i), ptr[reg_dst + i * load_len]); + else + assert(!"unsupported"); + } + } + + void store_dst(int nloads, int load_len) { + for (int i = 0; i < nloads; ++i) { + if (load_len == typesize) + movd(ptr[reg_dst + i * load_len], Xmm(i)); + else if (load_len == vlen) + vmovups(ptr[reg_dst + i * load_len], Vmm(i)); + else + assert(!"unsupported"); + } + } + + void accumulate(int nloads, int load_len, size_t base_off) { + for (int i = 0; i < nloads; ++i) { + size_t off = base_off + i * load_len; + + if (load_len == typesize) + uni_add(Xmm(i), ptr[reg_src + off]); + else if (load_len == vlen) + uni_vadd(Vmm(i), Vmm(i), vmmword[reg_src + off]); + else + assert(!"unsupported"); + } + } + + void loop_x() { + const int nloads[] = {cpu_isa_traits::n_vregs, 1, 1}; + const int nbranches = sizeof(nloads) / sizeof(nloads[0]); + + const int load_len[nbranches] = {vlen, vlen, typesize}; + Label loop_x_label[nbranches + 1]; + + mov(reg_x, reg_nx); + + for (int id = 0; id < nbranches; ++id) { + L(loop_x_label[id]); + + cmp(reg_x, nloads[id] * load_len[id]); + jl(loop_x_label[id + 1], T_NEAR); + + if (this->nullify_dst_) + nullify_dst(nloads[id], load_len[id]); + else + load_dst(nloads[id], load_len[id]); + + if (nloads[id] > 1) { + Label loop_srcs; + mov(reg_src_id, this->n_src_); + L(loop_srcs); + + accumulate(nloads[id], load_len[id], 0); + add(reg_src, this->src_ld_ * typesize); + + dec(reg_src_id); + jnz(loop_srcs, T_NEAR); + + sub(reg_src, this->n_src_ * this->src_ld_ * typesize); + } else { + for (int src_id = 0; src_id < this->n_src_; ++src_id) { + const size_t base_off = src_id * this->src_ld_ * typesize; + accumulate(nloads[id], load_len[id], base_off); + } + } + + store_dst(nloads[id], load_len[id]); + + add(reg_src, nloads[id] * load_len[id]); + add(reg_dst, nloads[id] * load_len[id]); + + sub(reg_x, nloads[id] * load_len[id]); + + jmp(loop_x_label[id], T_NEAR); + } + + L(loop_x_label[nbranches]); + + /* restore address registers */ + sub(reg_src, reg_nx); + sub(reg_dst, reg_nx); + } + + void generate() { + assert(isa == avx2 || isa == avx512_common || isa == avx512_mic); + + preamble(); + + shl(reg_nx, 2); + + Label ny_loop; + L(ny_loop); + + loop_x(); + + add(reg_dst, this->dst_step_ * typesize); + add(reg_src, this->src_step_ * typesize); + + dec(reg_ny); + jnz(ny_loop, T_NEAR); + + postamble(); + this->ker_ = reinterpret_castker_)>( + const_cast(this->getCode())); + } +}; + +template +inline reducer_2d_driver_t *create_reduce_2d_drv(int n_src, + size_t src_ld, size_t src_step, size_t dst_step, bool nullify_dst) { + if (mayiuse(avx512_common)) + return new reducer_2d_driver_f_s_32_t(n_src, + src_ld, src_step, dst_step, nullify_dst); + else if (mayiuse(avx2)) + return new reducer_2d_driver_f_s_32_t(n_src, src_ld, + src_step, dst_step, nullify_dst); + assert(!"unimplemented"); + return nullptr; +} + +/* cpu_reducer_t */ + +template +void cpu_reducer_t::conf_t::init_scratchpad( + memory_tracking::registrar_t &scratchpad) const { + if (balancer_.nthr_per_group_ == 1) return; + + const size_t space_size = balancer_.ngroups_ + * (balancer_.nthr_per_group_ - 1) + * cpu_reducer_t::space_per_thread(balancer_); + scratchpad.book(key_reducer_space, sizeof(data_t) * space_size, PAGE_4K); + scratchpad.book(key_reducer_space_bctx, + sizeof(simple_barrier::ctx_t) * balancer_.ngroups_); +} + +template +cpu_reducer_t::cpu_reducer_t(const conf_t &conf) + : conf_(conf), drv_(nullptr) +{ + if (balancer().nthr_per_group_ == 1) return; + + drv_ = create_reduce_2d_drv(balancer().nthr_per_group_ - 1, + space_per_thread(balancer()), 0, 0, false); +} + +template +cpu_reducer_t::~cpu_reducer_t() { delete drv_; } + +template +typename cpu_reducer_t::data_t * +cpu_reducer_t::get_local_ptr(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + const int id_in_grp = balancer().id_in_group(ithr); + + /* threads 0 from each group writes directly to the destination */ + if (id_in_grp == 0) + return dst + balancer().ithr_job_off(ithr) * balancer().job_size_; + + const int grp_id = balancer().group_id(ithr); + const int offset_factor = grp_id * (balancer().nthr_per_group_ - 1) + + (id_in_grp - 1); + + auto space = scratchpad.template get(key_reducer_space); + return space + offset_factor * space_per_thread(balancer()); +} + +template +void cpu_reducer_t::reduce_nolock(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + bool redundant_reduction = balancer().nthr_per_group_ == 1 + || balancer().idle(ithr); + if (redundant_reduction) return; + +#ifdef SIMPLE_IMPL + if (balancer().id_in_group(ithr) != 0) + return; /* only threads 0 do the reduction */ + + const int njobs_in_grp = balancer().ithr_njobs(ithr); + data_t *d = get_local_ptr(ithr, dst, scratchpad); + for (int id_in_grp = 1; id_in_grp < balancer_.nthr_per_group_; ++id_in_grp) + { + const data_t *space = get_local_ptr(ithr + id_in_grp, dst, scratchpad); + for (size_t i = 0; i < (size_t)njobs_in_grp * balancer().job_size_; ++i) + d[i] += space[i]; + } +#else + using namespace utils; + + const int id_in_grp = balancer().id_in_group(ithr); + const int njobs_in_grp = balancer().ithr_njobs(ithr); + const size_t cl = 64 / sizeof(data_t); + + const size_t reduction_size = njobs_in_grp * balancer().job_size_; + size_t start{0}, end{0}; + balance211(div_up(reduction_size, cl), balancer().nthr_per_group_, + id_in_grp, start, end); + + if (start == end) return; + + data_t *d = get_local_ptr(ithr - id_in_grp, dst, scratchpad) + start * cl; + const data_t *space = get_local_ptr(ithr - id_in_grp + 1, dst, scratchpad) + + start * cl; + const size_t len = nstl::min(end * cl, reduction_size) - start * cl; + + (*drv_)(d, space, 1, len); +#endif +} + +template struct cpu_reducer_t; +template struct cpu_reducer_t; + +/* cpu_reducer_2d_t */ + +template +void cpu_reducer_2d_t::conf_t::init_scratchpad( + memory_tracking::registrar_t &scratchpad) const { + if (balancer_.nthr_per_group_ == 1) return; + + const size_t space_size = balancer_.ngroups_ * balancer_.nthr_per_group_ + * cpu_reducer_2d_t::space_per_thread(balancer_); + scratchpad.book(key_reducer_space, sizeof(data_t) * space_size); + scratchpad.book(key_reducer_space_bctx, + sizeof(simple_barrier::ctx_t) * balancer_.ngroups_); +} + +template +cpu_reducer_2d_t::cpu_reducer_2d_t(const conf_t &conf) + : conf_(conf), drv_(nullptr) +{ + if (balancer().nthr_per_group_ == 1) return; + + drv_ = create_reduce_2d_drv(balancer().nthr_per_group_, + space_per_thread(balancer()), conf_.job_size_x_, conf_.dst_x_, + true); +} + +template +cpu_reducer_2d_t::~cpu_reducer_2d_t() { delete drv_; } + +template +typename cpu_reducer_2d_t::data_t *cpu_reducer_2d_t:: +get_local_ptr(int ithr, const memory_tracking::grantor_t &scratchpad) const { + const int id_in_grp = balancer().id_in_group(ithr); + const int grp_id = balancer().group_id(ithr); + const int offset_factor = grp_id * balancer().nthr_per_group_ + id_in_grp; + auto space = scratchpad.template get(key_reducer_space); + return space + offset_factor * space_per_thread(balancer()); +} + +template +int cpu_reducer_2d_t::choose_x_blocking(int nx, int ny, + int nthr_per_grp) const { + // find x_blocking for better balance reducing work between threads + assert(conf_.x_block_ > 0 && nx > conf_.x_block_ + && nx % conf_.x_block_ == 0); + int x_blocking = nx / conf_.x_block_; + int min_x_blocking = + utils::div_up(x_blocking, nstl::max(1, nthr_per_grp / ny)); + while (true) { + if (x_blocking % 2 == 0 && x_blocking >= min_x_blocking * 2) + x_blocking /= 2; + else if (x_blocking % 3 == 0 && x_blocking >= min_x_blocking * 3) + x_blocking /= 3; + else + break; + } + if (x_blocking >= min_x_blocking * 4) x_blocking = 1; + x_blocking *= conf_.x_block_; + return x_blocking; +} + +template +void cpu_reducer_2d_t::reduce_block(const data_t* space_base, + data_t *dst, int job, int start_y, int start_x, + int ny_start, int nx_start, int ny_step, int nx_step) const { + data_t *d = dst + (start_y + ny_start) * conf_.dst_x_ + + start_x + nx_start; + const data_t *space = space_base + job * balancer().job_size_ + + ny_start * conf_.job_size_x_ + nx_start; +#ifdef SIMPLE_IMPL + for (int idg = 0; idg < balancer().nthr_per_group_; ++idg) { + const data_t *w = &space[idg * space_per_thread(balancer())]; + for (int y = 0; y < ny_step; ++y) + for (int x = 0; x < nx_step; ++x) { + d[y * conf_.dst_x_ + x] + = (idg == 0 ? 0 : d[y * conf_.dst_x_ + x]) + + w[y * conf_.job_size_x_ + x]; + } + } +#else + (*drv_)(d, space, ny_step, nx_step); +#endif +} + +template +void cpu_reducer_2d_t::reduce_nolock(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + bool redundant_reduction = balancer().nthr_per_group_ == 1 + || balancer().idle(ithr); + if (redundant_reduction) return; + + const int id_in_grp = balancer().id_in_group(ithr); + const int njobs_in_grp = balancer().ithr_njobs(ithr); + const int njobs_x = utils::div_up(conf_.dst_x_, conf_.job_size_x_); + const int global_job_start = balancer().ithr_job_off(ithr); + + const data_t *space_base = get_local_ptr(ithr - id_in_grp, scratchpad); + + const int pr_grps = nstl::min(njobs_in_grp, balancer().nthr_per_group_); + const int pr_nthr_per_grp = balancer().nthr_per_group_ / pr_grps; + + if (id_in_grp >= pr_grps * pr_nthr_per_grp) + return; /* idle */ + + const int pr_my_grp = id_in_grp / pr_nthr_per_grp; + const int pr_my_id = id_in_grp % pr_nthr_per_grp; + + int pr_job_start{0}, pr_job_end{0}; + balance211(njobs_in_grp, pr_grps, pr_my_grp, pr_job_start, pr_job_end); + + for (int j = pr_job_start; j < pr_job_end; ++j) { + const int global_job = global_job_start + j; + const int j_y = global_job / njobs_x; + const int j_x = global_job % njobs_x; + const int start_y = j_y * conf_.job_size_y_; + const int start_x = j_x * conf_.job_size_x_; + const int ny = nstl::min(conf_.dst_y_ - start_y, conf_.job_size_y_); + const int nx = nstl::min(conf_.dst_x_ - start_x, conf_.job_size_x_); + int x_blocking = choose_x_blocking(nx, ny, pr_nthr_per_grp); + + int nxy_start{0}, nxy_end{0}; + balance211(ny * nx / x_blocking, pr_nthr_per_grp, pr_my_id, + nxy_start, nxy_end); + if (nxy_start == nxy_end) continue; + nxy_start *= x_blocking; + nxy_end *= x_blocking; + + int nxy = nxy_start; + if (nxy % nx != 0) { + int nx_step = nstl::min(nx - nxy % nx, nxy_end - nxy); + reduce_block(space_base, dst, j, start_y, start_x, + nxy / nx, nxy % nx, 1, nx_step); + nxy += nx_step; + } + if ((nxy_end - nxy) > nx) { + int ny_step = (nxy_end - nxy) / nx; + reduce_block(space_base, dst, j, start_y, start_x, + nxy / nx, nxy % nx, ny_step, nx); + nxy += nx * ny_step; + } + if ((nxy_end - nxy) > 0) { + reduce_block(space_base, dst, j, start_y, start_x, + nxy / nx, nxy % nx, 1, nxy_end - nxy); + } + } +} + +template struct cpu_reducer_2d_t; +template struct cpu_reducer_2d_t; + +/* accumulator section */ + +template +cpu_accumulator_1d_t::cpu_accumulator_1d_t(): drv_(nullptr) { + drv_ = create_reduce_2d_drv(1, 0, 0, 0, false); +} + +template +cpu_accumulator_1d_t::~cpu_accumulator_1d_t() { + delete drv_; +} + +template +void cpu_accumulator_1d_t::accumulate(data_t *dst, + const data_t *src, size_t size) { + (*drv_)(dst, src, 1, size); +} + +template struct cpu_accumulator_1d_t; +template struct cpu_accumulator_1d_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.hpp new file mode 100644 index 000000000000..27f5939cd2a3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reducer.hpp @@ -0,0 +1,334 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REDUCER_HPP +#define CPU_REDUCER_HPP + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "mkldnn_types.h" +#include "nstl.hpp" +#include "type_helpers.hpp" + +#include "cpu_barrier.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +/** class to perform balancing over 3D array + * + * Conceptually the reduction happens according to the picture below: + * + * <--job_size-> + * +-----------+ +-----------+ +-----------+ ^ + * | | | | | | | + * | | | | | | | + * | 1 | | 2 | . . . | njobs | | reduction_size + * | | | | | | | + * | | | | | | | + * +-----------+ +-----------+ +-----------+ v + * + * | | | | | | | | | + * v v v v v v v v v + * ===================================================== vertical reduction + * + * +-----------+ +-----------+ . . . +-----------+ result + * + * In a simple case the result must be contiguous in memory. + * @class cpu_reducer_t is an implementation. + * + * Threads are divided into groups. The groups are independent of each other. + * Each group may work on several jobs (the distribution is not uniform, since + * njobs might be not a multiple of groups). Threads within a group work on + * different parts of the reduction dimension. Thread 0 in each group is called + * master (@sa reduce_balancer_t::master()). + * + * If threading driver does not allow sync between sub-group of threads (e.g. + * Intel(R) TBB) the # of thread per group is enforced to be 1. + */ +struct reduce_balancer_t { + reduce_balancer_t() { init(1, 1, 1, 1, 0); } /* trivial balance */ + reduce_balancer_t(int nthr, int job_size, int njobs, int reduction_size, + size_t max_buffer_size) + { init(nthr, job_size, njobs, reduction_size, max_buffer_size); } + + reduce_balancer_t &init(int nthr, int job_size, int njobs, + int reduction_size, size_t max_buffer_size) + { + syncable_ = mkldnn_thr_syncable(); + nthr_ = nthr; + job_size_ = job_size; + njobs_ = njobs; + reduction_size_ = reduction_size; + max_buffer_size_ = max_buffer_size; + balance(); + return *this; + } + + bool syncable_; + int nthr_; + int job_size_, njobs_, reduction_size_; + + int ngroups_; /** number of independent work (thread) groups */ + int nthr_per_group_; /** number of threads within a single work group */ + int njobs_per_group_ub_; /** the max # of jobs within a work group */ + + bool master(int ithr) const { return id_in_group(ithr) == 0; } + bool idle(int ithr) const { return ithr >= nthr_per_group_ * ngroups_; } + + int group_id(int ithr) const { return ithr / nthr_per_group_; } + int id_in_group(int ithr) const { return ithr % nthr_per_group_; } + + int grp_njobs(int grp) const { + if (grp >= ngroups_) return 0; + return njobs_ / ngroups_ + (grp < njobs_ % ngroups_); + } + int grp_job_off(int grp) const { + if (grp >= ngroups_) return njobs_; + return njobs_ / ngroups_ * grp + nstl::min(grp, njobs_ % ngroups_); + } + + int ithr_njobs(int ithr) const { return grp_njobs(group_id(ithr)); } + int ithr_job_off(int ithr) const { return grp_job_off(group_id(ithr)); } + +private: + size_t max_buffer_size_; + void balance(); +}; + +/** forward declaration of reduce driver */ +template struct reducer_2d_driver_t; + +/** class to perform a reduction over 3D array + * + * Balancing is based on @class reduce_balancer_t. + * Restrictions: the result of the reduction must be contiguous in memory. * + * The reduction happens according to the picture below (once more): + * + * <--job_size-> + * +-----------+ +-----------+ +-----------+ ^ + * | | | | | | | + * | | | | | | | + * | 1 | | 2 | . . . | njobs | | reduction_size + * | | | | | | | + * | | | | | | | + * +-----------+ +-----------+ +-----------+ v + * + * | | | | | | | | | + * v v v v v v v v v + * ===================================================== vertical reduction + * + * +-----------+ +-----------+ . . . +-----------+ (contiguous) result + * + * An example how work might be shared is shown below. + * + * In this example group 0 owns 2 (independent) jobs -- 2 big squares. + * The number of threads per group is also 2 (thread 0 of group 0 and thread 1 + * of group 0). Master threads (i.e. threads with id 0 in corresponding group) + * from each group put the partial result directly into destination memory, + * while all the other threads with-in the group use workspace (on the picture + * the only thread 1). Once intermediate results obtained each group reduces + * corresponding part (own jobs) to the destination memory. + * + * <------- group 0 -------> + * + * +-----------+ +-----------+ ^ + * | | | | | thread 0 of reduces to the dest-memory + * | | | | | group 0 +-----------+ +-----------+ + * |- - - - - -| |- - - - - -| X + * | | | | | thread 1 of reduces to workspace[tid=1]: + * | | | | | group 0 +-----------+ +-----------+ + * +-----------+ +-----------+ v + * | | | | | | + * v v v v v v + * ((barrier)) ============================= + * + * dest-memory: +-----------+ +-----------+ + */ +template +struct cpu_reducer_t { + typedef typename prec_traits::type data_t; + + struct conf_t { + conf_t() = default; + conf_t &init(const reduce_balancer_t &balancer) + { balancer_ = balancer; return *this; } + + void init_scratchpad(memory_tracking::registrar_t &scratchpad) const; + + reduce_balancer_t balancer_; + }; + + cpu_reducer_t(const conf_t &conf); + ~cpu_reducer_t(); + + /** initializes reducer. + * Must be called from a single thread prior to actual usage */ + void init(const memory_tracking::grantor_t &scratchpad) const { + if (balancer().nthr_per_group_ == 1) return; + + auto bctx = scratchpad.template get( + memory_tracking::names::key_reducer_space_bctx); + for (int i = 0; i < balancer().ngroups_; ++i) + simple_barrier::ctx_init(&bctx[i]); + } + + /** for given thread returns the pointer where to put partial results. + * Reduction destination @p dst must be provided as well (master threads + * from each group will use it for partial result to reduce memory + * pressure). + * + * @note: job offset is already applied by get_local_ptr(), which means all + * threads should start writing from the very beginning of returned + * address. + */ + data_t *get_local_ptr(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; + + /** performs the reduction with built-in synchronization. */ + void reduce(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + bool redundant_reduction = balancer().nthr_per_group_ == 1 + || balancer().idle(ithr); + if (redundant_reduction) return; + + auto bctx = scratchpad.template get( + memory_tracking::names::key_reducer_space_bctx); + simple_barrier::barrier(&bctx[balancer().group_id(ithr)], + balancer().nthr_per_group_); + + reduce_nolock(ithr, dst, scratchpad); + } + + const reduce_balancer_t &balancer() const { return conf_.balancer_; } + +private: + static size_t space_per_thread(const reduce_balancer_t &balancer) + { return balancer.njobs_per_group_ub_ * balancer.job_size_; } + + /* The scratchpad is organized as follows: + * + * data_t space[nthr_][njobs_per_group_ub_][jobs_size_]; + * simple_barrier::ctx_t barriers[groups_]; */ + + const conf_t conf_; + reducer_2d_driver_t *drv_; + + void reduce_nolock(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; +}; + +template +struct cpu_reducer_2d_t { + typedef typename prec_traits::type data_t; + + struct conf_t { + conf_t() = default; + conf_t &init(const reduce_balancer_t &balancer, int job_size_x, + int job_size_y, int x_block, int dst_x, int dst_y) { + balancer_ = balancer; + job_size_x_ = job_size_x; + job_size_y_ = job_size_y; + x_block_ = x_block; + dst_x_ = dst_x; + dst_y_ = dst_y; + return *this; + } + + void init_scratchpad(memory_tracking::registrar_t &scratchpad) const; + + reduce_balancer_t balancer_; + int job_size_x_, job_size_y_, x_block_, dst_x_, dst_y_; + }; + + cpu_reducer_2d_t(const conf_t &conf); + ~cpu_reducer_2d_t(); + + /** initializes reducer. + * Must be called from a single thread prior to actual usage */ + void init(const memory_tracking::grantor_t &scratchpad) const { + if (balancer().nthr_per_group_ == 1) return; + + auto bctx = scratchpad.template get( + memory_tracking::names::key_reducer_space_bctx); + for (int i = 0; i < balancer().ngroups_; ++i) + simple_barrier::ctx_init(&bctx[i]); + } + + /** for given thread returns the pointer where to put partial results */ + data_t *get_local_ptr(int ithr, + const memory_tracking::grantor_t &scratchpad) const; + + /** performs the reduction with built-in synchronization. */ + void reduce(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + bool redundant_reduction = balancer().nthr_per_group_ == 1 + || balancer().idle(ithr); + if (redundant_reduction) return; + + auto bctx = scratchpad.template get( + memory_tracking::names::key_reducer_space_bctx); + simple_barrier::barrier(&bctx[balancer().group_id(ithr)], + balancer().nthr_per_group_); + + reduce_nolock(ithr, dst, scratchpad); + } + + const reduce_balancer_t &balancer() const { return conf_.balancer_; } + +private: + static size_t space_per_thread(const reduce_balancer_t &balancer) + { return balancer.njobs_per_group_ub_ * balancer.job_size_; } + + /* The scratchpad is organized as follows: + * + * data_t space[nthr_][njobs_per_group_ub_][jobs_size_]; + * simple_barrier::ctx_t barriers[groups_]; */ + + const conf_t conf_; + reducer_2d_driver_t *drv_; + + int choose_x_blocking(int nx, int ny, int nthr_per_grp) const; + void reduce_block(const data_t* space_base, data_t *dst, + int job, int start_y, int start_x, + int ny_start, int nx_start, int ny_step, int nx_step) const; + void reduce_nolock(int ithr, data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; +}; + +/** simple 1d accumulator: y[:] += x[:] */ +template +struct cpu_accumulator_1d_t { + typedef typename prec_traits::type data_t; + + cpu_accumulator_1d_t(); + ~cpu_accumulator_1d_t(); + void accumulate(data_t *dst, const data_t *src, size_t size); + + reducer_2d_driver_t *drv_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder.cpp new file mode 100644 index 000000000000..82be70353de9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder.cpp @@ -0,0 +1,262 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "cpu_engine.hpp" +#include "cpu_primitive.hpp" +#include "cpu_reorder_pd.hpp" +#include "cpu_memory.hpp" +#include "type_helpers.hpp" + +#include "cpu/jit_uni_reorder.hpp" +#include "cpu/simple_reorder.hpp" +#include "cpu/wino_reorder.hpp" +#include "cpu/rnn/rnn_reorders.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using rpd_create_f = mkldnn::impl::engine_t::reorder_primitive_desc_create_f; + +namespace { +using namespace mkldnn::impl::data_type; +using namespace mkldnn::impl::format_tag; + +#define REG_SR(idt, ifmt, odt, ofmt, ...) \ + simple_reorder_t::pd_t::create + +#define REG_SR_BIDIR(idt, ifmt, odt, ofmt) \ + REG_SR(idt, ifmt, odt, ofmt, fmt_order::keep), \ + REG_SR(idt, ifmt, odt, ofmt, fmt_order::reverse) + +#define REG_SR_DIRECT_COPY(idt, odt) \ + REG_SR(idt, any, odt, any, fmt_order::any, spec::direct_copy), \ + REG_SR(idt, any, odt, any, fmt_order::any, spec::direct_copy_except_dim_0) + +static const rpd_create_f cpu_reorder_impl_list[] = { + /* winograd */ + wino_reorder_t::pd_t::create, + //wino_reorder_t::pd_t::create, + + /* rnn reorders */ + rnn_data_reorder_t::pd_t::create, + rnn_weights_reorder_t::pd_t::create, + rnn_weights_reorder_t::pd_t::create, + + /* conv reorders w/ compensation */ + REG_SR(f32, any, s8, hwio, fmt_order::keep, spec::conv_s8s8), + REG_SR(f32, any, s8, hwigo, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, any, s8, hwio, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, any, s8, hwigo, fmt_order::keep, spec::conv_s8s8), + + REG_SR(f32, oiw, s8, OIw4i16o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(f32, goiw, s8, gOIw4i16o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, oiw, s8, OIw4i16o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, goiw, s8, gOIw4i16o4i, fmt_order::keep, spec::conv_s8s8), + + REG_SR(f32, oihw, s8, OIhw4i16o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(f32, goihw, s8, gOIhw4i16o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, oihw, s8, OIhw4i16o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, goihw, s8, gOIhw4i16o4i, fmt_order::keep, spec::conv_s8s8), + + REG_SR(f32, goihw, s8, gOIhw2i8o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, goihw, s8, gOIhw2i8o4i, fmt_order::keep, spec::conv_s8s8), + + REG_SR(f32, goihw, s8, gOIhw4o4i, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, goihw, s8, gOIhw4o4i, fmt_order::keep, spec::conv_s8s8), + + REG_SR(f32, goiw, s8, Goiw16g, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, goiw, s8, Goiw16g, fmt_order::keep, spec::conv_s8s8), + REG_SR(f32, goihw, s8, Goihw16g, fmt_order::keep, spec::conv_s8s8), + REG_SR(s8, goihw, s8, Goihw16g, fmt_order::keep, spec::conv_s8s8), + + /* regular reorders */ + +#if defined(__INTEL_COMPILER) || (defined(__GNUC__) && !defined(__clang__)) + /* Direct copy for icc which is faster than jitted code; + * Direct copy for gcc which might or might not be faster than jitted + * code, but still worth it because doesn't require jitting, i.e. much + * faster creation time. This is tentative solution and should be removed + * later (when we will cache jitted code?...). */ + REG_SR_DIRECT_COPY(f32, f32), +#endif + +#ifdef __INTEL_COMPILER + /* direct copy for icc, which is faster than jitted code */ + /* + REG_SR_DIRECT_COPY(f32, s32), + REG_SR_DIRECT_COPY(f32, s8), + REG_SR_DIRECT_COPY(f32, u8), + REG_SR_DIRECT_COPY(s32, f32), + REG_SR_DIRECT_COPY(s32, s32), + REG_SR_DIRECT_COPY(s32, s8), + REG_SR_DIRECT_COPY(s32, u8), + REG_SR_DIRECT_COPY(s8, f32), + REG_SR_DIRECT_COPY(s8, s32), + REG_SR_DIRECT_COPY(s8, s8), + REG_SR_DIRECT_COPY(s8, u8), + REG_SR_DIRECT_COPY(u8, f32), + REG_SR_DIRECT_COPY(u8, s32), + REG_SR_DIRECT_COPY(u8, s8), + REG_SR_DIRECT_COPY(u8, u8), + */ +#endif + + /* jit */ + jit_uni_reorder_create, + + /* fp32: flat <-> blocked with tail */ + /* + REG_SR_BIDIR(f32, any, f32, nCw4c), + REG_SR_BIDIR(f32, any, f32, nCw8c), + REG_SR_BIDIR(f32, any, f32, OIw4i4o), + REG_SR_BIDIR(f32, any, f32, OIw8i8o), + REG_SR_BIDIR(f32, any, f32, OIw8o8i), + REG_SR_BIDIR(f32, any, f32, gOIw4i4o), + REG_SR_BIDIR(f32, any, f32, gOIw8i8o), + REG_SR_BIDIR(f32, any, f32, gOIw8o8i), + + REG_SR_BIDIR(f32, any, f32, nCw16c), + REG_SR_BIDIR(f32, any, f32, OIw16o16i), + REG_SR_BIDIR(f32, any, f32, OIw16i16o), + REG_SR_BIDIR(f32, any, f32, IOw16o16i), + REG_SR_BIDIR(f32, any, f32, gOIw16o16i), + REG_SR_BIDIR(f32, any, f32, gOIw16i16o), + REG_SR_BIDIR(f32, any, f32, gIOw16o16i), + + REG_SR_BIDIR(f32, any, f32, nChw4c), + REG_SR_BIDIR(f32, any, f32, nChw8c), + REG_SR_BIDIR(f32, any, f32, OIhw4i4o), + REG_SR_BIDIR(f32, any, f32, Ohwi8o), + + REG_SR_BIDIR(f32, any, f32, OIhw8i8o), + REG_SR_BIDIR(f32, any, f32, OIhw8o8i), + REG_SR_BIDIR(f32, any, f32, gOIhw4i4o), + REG_SR_BIDIR(f32, any, f32, gOIhw4o4i), + REG_SR_BIDIR(f32, any, f32, gOhwi8o), + REG_SR_BIDIR(f32, any, f32, gOIhw8i8o), + REG_SR_BIDIR(f32, any, f32, gOIhw8o8i), + + REG_SR_BIDIR(f32, any, f32, nChw16c), + REG_SR_BIDIR(f32, any, f32, Oihw4o), + REG_SR_BIDIR(f32, any, f32, Oihw16o), + REG_SR_BIDIR(f32, any, f32, Ohwi4o), + REG_SR_BIDIR(f32, any, f32, Ohwi16o), + REG_SR_BIDIR(f32, any, f32, OIhw16o16i), + REG_SR_BIDIR(f32, any, f32, OIhw16i16o), + REG_SR_BIDIR(f32, any, f32, IOhw16o16i), + REG_SR_BIDIR(f32, any, f32, gOihw4o), + REG_SR_BIDIR(f32, any, f32, gOihw16o), + REG_SR_BIDIR(f32, any, f32, gOhwi4o), + REG_SR_BIDIR(f32, any, f32, gOhwi16o), + REG_SR_BIDIR(f32, any, f32, gOIhw16o16i), + REG_SR_BIDIR(f32, any, f32, gOIhw16i16o), + REG_SR_BIDIR(f32, any, f32, gIOhw16o16i), + + REG_SR_BIDIR(f32, any, f32, nCdhw4c), + REG_SR_BIDIR(f32, any, f32, nCdhw8c), + REG_SR_BIDIR(f32, any, f32, OIdhw4i4o), + REG_SR_BIDIR(f32, any, f32, Odhwi8o), + REG_SR_BIDIR(f32, any, f32, OIdhw8i8o), + REG_SR_BIDIR(f32, any, f32, OIdhw8o8i), + REG_SR_BIDIR(f32, any, f32, gOIdhw4i4o), + REG_SR_BIDIR(f32, any, f32, gOdhwi8o), + REG_SR_BIDIR(f32, any, f32, gOIdhw8i8o), + REG_SR_BIDIR(f32, any, f32, gOIdhw8o8i), + + REG_SR_BIDIR(f32, any, f32, nCdhw16c), + REG_SR_BIDIR(f32, any, f32, Oidhw4o), + REG_SR_BIDIR(f32, any, f32, Oidhw16o), + REG_SR_BIDIR(f32, any, f32, Odhwi16o), + REG_SR_BIDIR(f32, any, f32, OIdhw16o16i), + REG_SR_BIDIR(f32, any, f32, OIdhw16i16o), + REG_SR_BIDIR(f32, any, f32, gOidhw4o), + REG_SR_BIDIR(f32, any, f32, gOidhw16o), + REG_SR_BIDIR(f32, any, f32, gOdhwi16o), + REG_SR_BIDIR(f32, any, f32, gOIdhw16o16i), + REG_SR_BIDIR(f32, any, f32, gOIdhw16i16o), + */ + + /* fp32: blocked <-> blocked with tail */ + REG_SR_BIDIR(f32, nCw8c, f32, nCw16c), + REG_SR_BIDIR(f32, nChw8c, f32, nChw16c), + REG_SR_BIDIR(f32, nCdhw8c, f32, nCdhw16c), + + /* int: flat <-> blocked with tail */ + /* + REG_SR_BIDIR(f32, any, s32, nChw16c), + REG_SR_BIDIR(f32, any, s8, nChw16c), + REG_SR_BIDIR(f32, any, u8, nChw16c), + REG_SR_BIDIR(s32, any, f32, nChw16c), + REG_SR_BIDIR(s32, any, s32, nChw16c), + REG_SR_BIDIR(s32, any, s8, nChw16c), + REG_SR_BIDIR(s32, any, u8, nChw16c), + REG_SR_BIDIR(s8, any, f32, nChw16c), + REG_SR_BIDIR(s8, any, s32, nChw16c), + REG_SR_BIDIR(s8, any, s8, nChw16c), + REG_SR_BIDIR(s8, any, u8, nChw16c), + REG_SR_BIDIR(u8, any, f32, nChw16c), + REG_SR_BIDIR(u8, any, s32, nChw16c), + REG_SR_BIDIR(u8, any, s8, nChw16c), + REG_SR_BIDIR(u8, any, u8, nChw16c), + + REG_SR_BIDIR(f32, any, f32, OIhw4i16o4i), + REG_SR_BIDIR(f32, any, s8, OIhw4i16o4i), + REG_SR_BIDIR(s8, any, f32, OIhw4i16o4i), + REG_SR_BIDIR(s8, any, s8, OIhw4i16o4i), + REG_SR_BIDIR(f32, any, s8, gOIhw4i16o4i), + REG_SR_BIDIR(s8, any, f32, gOIhw4i16o4i), + REG_SR_BIDIR(f32, any, f32, gOIhw4i16o4i), + REG_SR_BIDIR(s8, any, s8, gOIhw4i16o4i), + */ + + /* reference: the last line of defence */ + /* + REG_SR(f32, any, f32, any, fmt_order::any, spec::reference), + REG_SR(f32, any, s32, any, fmt_order::any, spec::reference), + REG_SR(f32, any, s8, any, fmt_order::any, spec::reference), + REG_SR(f32, any, u8, any, fmt_order::any, spec::reference), + + REG_SR(s32, any, f32, any, fmt_order::any, spec::reference), + REG_SR(s32, any, s32, any, fmt_order::any, spec::reference), + REG_SR(s32, any, s8, any, fmt_order::any, spec::reference), + REG_SR(s32, any, u8, any, fmt_order::any, spec::reference), + + REG_SR(s8, any, f32, any, fmt_order::any, spec::reference), + REG_SR(s8, any, s32, any, fmt_order::any, spec::reference), + REG_SR(s8, any, s8, any, fmt_order::any, spec::reference), + REG_SR(s8, any, u8, any, fmt_order::any, spec::reference), + + REG_SR(u8, any, f32, any, fmt_order::any, spec::reference), + REG_SR(u8, any, s32, any, fmt_order::any, spec::reference), + REG_SR(u8, any, u8, any, fmt_order::any, spec::reference), + REG_SR(u8, any, s8, any, fmt_order::any, spec::reference), + */ + + /* eol */ + nullptr, +}; +} + +const rpd_create_f *cpu_engine_t::get_reorder_implementation_list() const { + return cpu_reorder_impl_list; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder_pd.hpp new file mode 100644 index 000000000000..1622eb68495b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_reorder_pd.hpp @@ -0,0 +1,48 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REORDER_PD_HPP +#define CPU_REORDER_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "reorder_pd.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_reorder_pd_t: public reorder_pd_t { + using reorder_pd_t::reorder_pd_t; + + status_t init() { + const auto &post_ops = attr()->post_ops_; + bool args_ok = IMPLICATION(post_ops.len_ != 0, post_ops.len_ == 1 + && post_ops.entry_[0].kind == primitive_kind::sum); + scratchpad_engine_ = src_engine_; + return args_ok ? status::success : status::unimplemented; + } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_shuffle_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_shuffle_pd.hpp new file mode 100644 index 000000000000..f16587b99f4c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_shuffle_pd.hpp @@ -0,0 +1,41 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_SHUFFLE_PD_HPP +#define CPU_SHUFFLE_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "shuffle_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_shuffle_pd_t: public shuffle_pd_t { + using shuffle_pd_t::shuffle_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_softmax_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_softmax_pd.hpp new file mode 100644 index 000000000000..3a39eab974b5 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_softmax_pd.hpp @@ -0,0 +1,45 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_SOFTMAX_PD_HPP +#define CPU_SOFTMAX_PD_HPP + +#include + +#include "c_types_map.hpp" +#include "softmax_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_softmax_fwd_pd_t: public softmax_fwd_pd_t { + using softmax_fwd_pd_t::softmax_fwd_pd_t; +}; + +struct cpu_softmax_bwd_pd_t: public softmax_bwd_pd_t { + using softmax_bwd_pd_t::softmax_bwd_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum.cpp new file mode 100644 index 000000000000..1ab5d9f17435 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum.cpp @@ -0,0 +1,48 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "cpu_engine.hpp" + +/* +#include "cpu/ref_sum.hpp" +#include "cpu/simple_sum.hpp" +*/ + +namespace mkldnn { +namespace impl { +namespace cpu { + +using spd_create_f = mkldnn::impl::engine_t::sum_primitive_desc_create_f; + +namespace { +#define INSTANCE(...) __VA_ARGS__::pd_t::create +static const spd_create_f cpu_sum_impl_list[] = { + /* + INSTANCE(simple_sum_t), + INSTANCE(ref_sum_t), + */ + nullptr, +}; +#undef INSTANCE +} + +const spd_create_f *cpu_engine_t::get_sum_implementation_list() const { + return cpu_sum_impl_list; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum_pd.hpp new file mode 100644 index 000000000000..0965129f9b02 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/cpu_sum_pd.hpp @@ -0,0 +1,39 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_SUM_PD_HPP +#define CPU_SUM_PD_HPP + +#include "c_types_map.hpp" +#include "sum_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_sum_pd_t: public sum_pd_t { + using sum_pd_t::sum_pd_t; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.cpp new file mode 100644 index 000000000000..a9810dec28f2 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.cpp @@ -0,0 +1,372 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ +#include + +#include "mkldnn_thread.hpp" +#include "utils.hpp" +#include "gemm_utils_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { +namespace gemm_utils { +#define BM_NOCOPY_AVX 64 +#define BN_NOCOPY_AVX 48 +#define BK_NOCOPY_AVX 384 +#define BN_LARGE_NOCOPY_AVX 192 +#define BM_SMALL_NOCOPY_AVX 16 +#define BN_SMALL_NOCOPY_AVX 1 +#define BK_SMALL_NOCOPY_AVX 4 +// Determine number of threads for each dimension of a 3-D partitioning +// algorithm based on input parameters +// m/n/k - First/second/third parameter for GEMM +// nthrs - total available number of threads +// nthrs_m/nthrs_n/nthrs_k - number of threads to use in each dimension +// BM/BN/BK - blocking values +void calc_nthr_nocopy_avx(int m, int n, int k, + int nthrs, int *nthrs_m, int *nthrs_n, int *nthrs_k, int *BM, int *BN, + int *BK) +{ + int nthr, nthr_m, nthr_n, nthr_k; + int MB, NB, KB; + + nthr = nthrs; + nthr_m = (m + BM_NOCOPY_AVX - 1) / BM_NOCOPY_AVX; + nthr_n = (n + BN_NOCOPY_AVX - 1) / BN_NOCOPY_AVX; + nthr_k = 1; + + // Partition along K dimension + // - if threading allows having barriers (e.g. OMP) + // - if there is not enough parallelism along M or N + if (mkldnn_thr_syncable()) { + int nthr_other = nthr_k = 1; + while ((nthr_m * nthr_n * nthr_other < nthr) + && (k / (nthr_other + 1) > BK_NOCOPY_AVX)) { + nthr_other++; + if ((nthr / nthr_other) * nthr_other > 0.9 * nthr) + nthr_k = nthr_other; + } + } + nthr /= nthr_k; + + if (nthr_m == 1) + nthr_n = nthr; + if (nthr_n == 1) + nthr_m = nthr; + + // Simple partition reduction + while (nthr_m * nthr_n > nthr) + if (nthr_m > nthr_n) + nthr_m--; + else + nthr_n--; + while (nthr_m * nthr_n < nthr) + if (nthr_m < nthr_n) + nthr_m++; + else + nthr_n++; + + if ((nthr_m * nthr_n > nthr) && (nthr_m > 1) && (nthr_n > 1)) { + + if (nthr_m <= nthr_n) { + nthr_m = (int)sqrt((double)nthr); + if (nthr_m > (m + BM_SMALL_NOCOPY_AVX - 1) / BM_SMALL_NOCOPY_AVX) + nthr_m = (m + BM_SMALL_NOCOPY_AVX - 1) / BM_SMALL_NOCOPY_AVX; + nthr_n = nthr / nthr_m; + + while ((nthr_m > 1) && (nthr_m * nthr_n != nthr)) { + nthr_m--; + nthr_n = nthr / nthr_m; + } + } else { + nthr_n = (int)sqrt((double)nthr); + if (nthr_n > (n + BN_SMALL_NOCOPY_AVX - 1) / BN_SMALL_NOCOPY_AVX) + nthr_n = (n + BN_SMALL_NOCOPY_AVX - 1) / BN_SMALL_NOCOPY_AVX; + nthr_m = nthr / nthr_n; + + while ((nthr_n > 1) && (nthr_m * nthr_n != nthr)) { + nthr_n--; + nthr_m = nthr / nthr_n; + } + } + } + + MB = (m + nthr_m - 1) / nthr_m + BM_SMALL_NOCOPY_AVX - 1; + MB -= MB % BM_SMALL_NOCOPY_AVX; + NB = (n + nthr_n - 1) / nthr_n + BN_SMALL_NOCOPY_AVX - 1; + NB -= NB % BN_SMALL_NOCOPY_AVX; + KB = (k + nthr_k - 1) / nthr_k + BK_SMALL_NOCOPY_AVX - 1; + KB -= KB % BK_SMALL_NOCOPY_AVX; + + if (MB * nthr_m > m) + nthr_m = (m + MB - 1) / MB; + if (NB * nthr_n > n) + nthr_n = (n + NB - 1) / NB; + if (KB * nthr_k > k) + nthr_k = (k + KB - 1) / KB; + + *nthrs_m = nthr_m; + *nthrs_n = nthr_n; + *nthrs_k = nthr_k; + + *BM = MB; + *BN = NB; + *BK = KB; +} +#undef BM_NOCOPY_AVX +#undef BN_NOCOPY_AVX +#undef BK_NOCOPY_AVX +#undef BN_LARGE_NOCOPY_AVX +#undef BM_SMALL_NOCOPY_AVX +#undef BN_SMALL_NOCOPY_AVX +#undef BK_SMALL_NOCOPY_AVX + +#define BM_NOCOPY_AVX512_COMMON 32 +#define BN_NOCOPY_AVX512_COMMON 64 +#define BK_NOCOPY_AVX512_COMMON 192 +#define BN_LARGE_NOCOPY_AVX512_COMMON 192 +#define BM_SMALL_NOCOPY_AVX512_COMMON 16 +#define BN_SMALL_NOCOPY_AVX512_COMMON 1 +#define BK_SMALL_NOCOPY_AVX512_COMMON 4 +// Determine number of threads for each dimension of a 3-D partitioning +// algorithm based on input parameters +// m/n/k - First/second/third parameter for GEMM +// nthrs - total available number of threads +// nthrs_m/nthrs_n/nthrs_k - number of threads to use in each dimension +// BM/BN/BK - blocking values +void calc_nthr_nocopy_avx512_common(int m, + int n, int k, int nthrs, int *nthrs_m, int *nthrs_n, int *nthrs_k, + int *BM, int *BN, int *BK) +{ + int nthr, nthr_m, nthr_n, nthr_k = 1; + int MB, NB, KB; + nthr = nthrs; + + int counter = 0; + float ratio_float = 1.; + int ratio = 1; + nthr = nthrs; + int nthr_m_gt_n; + + // Partition along K dimension + // - if threading allows having barriers (e.g. OMP) + // - if there is not enough parallelism along M or N + if (mkldnn_thr_syncable()) { + if (n <= 2 * BN_NOCOPY_AVX512_COMMON && + m <= 2 * BM_NOCOPY_AVX512_COMMON * nthr) { + nthr_k = k / BK_NOCOPY_AVX512_COMMON; + if (nthr_k > nthr / 4) + nthr_k = nthr / 4; + if (nthr_k < 1) + nthr_k = 1; + + while ((nthr_k > 1) && (nthr % nthr_k)) { + nthr_k--; + } + nthr /= nthr_k; + } else { + nthr_k = 1; + } + } + nthr_m = (m + BM_NOCOPY_AVX512_COMMON - 1) / BM_NOCOPY_AVX512_COMMON; + nthr_n = (n + BN_NOCOPY_AVX512_COMMON - 1) / BN_NOCOPY_AVX512_COMMON; + + if (nthr_m < 1) + nthr_m = 1; + if (nthr_n < 1) + nthr_n = 1; + + nthr_m_gt_n = nthr_m > nthr_n ? 1 : 0; + ratio_float = (float)nthr_m / nthr_n; + + if (nthr_m_gt_n) + ratio = (int)ratio_float; + else + ratio = (int)(1. / ratio_float); + + // scale down nthr_m and nthr_n if they are too large + while (nthr_m * nthr_n > 4 * nthr) { + nthr_m /= 2; + nthr_n /= 2; + } + + if (nthr_m < 1) + nthr_m = 1; + if (nthr_n < 1) + nthr_n = 1; + + // Simple partition reduction + counter = 0; + while (nthr_m * nthr_n > nthr) { + if (nthr_m > nthr_n) { + if (counter < ratio) + nthr_m--; + else { + nthr_n--; + counter = -1; + } + } else { + if (counter < ratio) + nthr_n--; + else { + nthr_m--; + counter = -1; + } + } + counter++; + } + + // Simple partition increment + counter = 0; + while (nthr_m * nthr_n < 0.95 * nthr) { + if (nthr_m > nthr_n) { + if (counter < ratio) + nthr_m++; + else { + nthr_n++; + counter = -1; + } + } else { + if (counter < ratio) + nthr_n++; + else { + nthr_m++; + counter = -1; + } + } + counter++; + } + + // if nothing works out, then this should work + if ((nthr_m * nthr_n > nthr)) { + + if (nthr_m <= nthr_n) { + nthr_m = (int)sqrt((double)nthr); + if (nthr_m > (m + BM_SMALL_NOCOPY_AVX512_COMMON - 1) + / BM_SMALL_NOCOPY_AVX512_COMMON) + nthr_m = (m + BM_SMALL_NOCOPY_AVX512_COMMON - 1) + / BM_SMALL_NOCOPY_AVX512_COMMON; + nthr_n = nthr / nthr_m; + + while ((nthr_m > 1) && (nthr_m * nthr_n != nthr)) { + nthr_m--; + nthr_n = nthr / nthr_m; + } + } else { + nthr_n = (int)sqrt((double)nthr); + if (nthr_n > (n + BN_SMALL_NOCOPY_AVX512_COMMON - 1) + / BN_SMALL_NOCOPY_AVX512_COMMON) + nthr_n = (n + BN_SMALL_NOCOPY_AVX512_COMMON - 1) + / BN_SMALL_NOCOPY_AVX512_COMMON; + nthr_m = nthr / nthr_n; + + while ((nthr_n > 1) && (nthr_m * nthr_n != nthr)) { + nthr_n--; + nthr_m = nthr / nthr_n; + } + } + } + + MB = (m + nthr_m - 1) / nthr_m + BM_SMALL_NOCOPY_AVX512_COMMON - 1; + MB -= MB % BM_SMALL_NOCOPY_AVX512_COMMON; + NB = (n + nthr_n - 1) / nthr_n + BN_SMALL_NOCOPY_AVX512_COMMON - 1; + NB -= NB % BN_SMALL_NOCOPY_AVX512_COMMON; + KB = (k + nthr_k - 1) / nthr_k + BK_SMALL_NOCOPY_AVX512_COMMON - 1; + KB -= KB % BK_SMALL_NOCOPY_AVX512_COMMON; + + if (MB * nthr_m > m) + nthr_m = (m + MB - 1) / MB; + if (NB * nthr_n > n) + nthr_n = (n + NB - 1) / NB; + if (KB * nthr_k > k) + nthr_k = (k + KB - 1) / KB; + + *nthrs_m = nthr_m; + *nthrs_n = nthr_n; + *nthrs_k = nthr_k; + + *BM = MB; + *BN = NB; + *BK = KB; +} +#undef BM_NOCOPY_AVX512_COMMON +#undef BN_NOCOPY_AVX512_COMMON +#undef BK_NOCOPY_AVX512_COMMON +#undef BN_LARGE_NOCOPY_AVX512_COMMON +#undef BM_SMALL_NOCOPY_AVX512_COMMON +#undef BN_SMALL_NOCOPY_AVX512_COMMON +#undef BK_SMALL_NOCOPY_AVX512_COMMON + +// Partition n values as equally as possible among nthr threads +// and set the offset (t_offset) and number of values (t_block) for ithr +// Assumption: 0 <= ithr < nthr +void partition_unit_diff( + int ithr, int nthr, int n, int *t_offset, int *t_block) +{ + int band = n / nthr; + if (band == 0) + band = 1; + int tail = n - band * nthr; + if (tail < 0) + tail = 0; + + if (ithr < tail) { + band++; + *t_offset = band * ithr; + *t_block = band; + } else { + *t_offset = band * ithr + tail; + *t_block = band; + } + + if (*t_offset >= n) { + *t_offset = 0; + *t_block = 0; + } + + if (*t_offset + *t_block > n) { + *t_block = n - *t_offset; + } +} + +// Sum the m*n values from p_src into p_dst, assuming the two-dimensional +// arrays have leading dimensions ld_src and ld_dst, respectively +template +void sum_two_matrices(int m, int n, + data_t * __restrict p_src, dim_t ld_src, + data_t * __restrict p_dst, dim_t ld_dst) +{ + int i, j; + for (j = 0; j < n; j++) { + for (i = 0; i < m; i++) { + p_dst[i + j * ld_dst] += p_src[i + j * ld_src]; + } + } +} + +template +void sum_two_matrices(int m, int n, + float * __restrict p_src, dim_t ld_src, + float * __restrict p_dst, dim_t ld_dst); + +template +void sum_two_matrices(int m, int n, + double * __restrict p_src, dim_t ld_src, + double * __restrict p_dst, dim_t ld_dst); +} +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.hpp new file mode 100644 index 000000000000..3352298b4aa6 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/gemm_utils_f32.hpp @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef GEMM_UTILS_HPP +#define GEMM_UTILS_HPP + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace gemm_utils { +// Alias for any dimension related variable. +typedef ptrdiff_t dim_t; + +template +struct gemm_traits {}; + +template +struct gemm_traits { + static constexpr int m = 8; + static constexpr int n = 6; + static constexpr int BM = 4032; + static constexpr int BN = isTransA ? 96 : 192; + static constexpr int BK = isTransB ? 96 : 512; +}; + +template +struct gemm_traits { + static constexpr int m = 16; + static constexpr int n = 6; + static constexpr int BM = 4032; + static constexpr int BN = isTransA ? 96 : 48; + static constexpr int BK = isTransB ? 96 : 256; +}; + +template +using unroll_factor = gemm_traits; + +template +void sum_two_matrices(int m, int n, + data_t * __restrict p_src, dim_t ld_src, + data_t * __restrict p_dst, dim_t ld_dst); + +void calc_nthr_nocopy_avx512_common(int m, + int n, int k, int nthrs, int *nthrs_m, int *nthrs_n, int *nthrs_k, + int *BM, int *BN, int *BK); + +void calc_nthr_nocopy_avx(int m, int n, int k, + int nthrs, int *nthrs_m, int *nthrs_n, int *nthrs_k, int *BM, int *BN, + int *BK); + +void partition_unit_diff( + int ithr, int nthr, int n, int *t_offset, int *t_block); +}; + +} +} +} +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.cpp new file mode 100644 index 000000000000..d7be43e39228 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.cpp @@ -0,0 +1,2131 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "ref_gemm_f32.hpp" +#include "gemm_utils_f32.hpp" +#include "jit_avx512_common_gemm_f32.hpp" + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +#define CACHE_LINE_SIZE 64 + +#define STACKSIZE get_size_of_abi_save_regs() +#ifdef _WIN32 +#define STACK_K_CAPACITY 32 +#else +#define STACK_K_CAPACITY 2048 +#endif +#define SIZE 4 +#define OFFSET 128 +#define BASE_SHIFT 2 +#define SECOND_FETCH unroll_n +#define UNROLL_M 48 +#define UNROLL_N 8 + +namespace avx512_common_gemm_f32 { +using namespace gemm_utils; + +struct xbyak_gemm : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_common_gemm_f32_xbyak_gemm) + + xbyak_gemm(char isTransA, char isTransB, float beta, bool hasBias = false, + void *code_ptr = nullptr, + size_t code_size = 80 * Xbyak::DEFAULT_MAX_CODE_SIZE) + : jit_generator(code_ptr, code_size) + { + using namespace Xbyak; + + enum { ver_avx512_core, ver_avx512_mic } ver = + mayiuse(avx512_core) ? ver_avx512_core : ver_avx512_mic; + + bool isBeta0 = (beta == 0.0); + bool isBetaN = (!isBeta0 && beta != 1.0); + + // various definitions for convenience + auto ARG_M = abi_param1; + auto ARG_N = abi_param2; + auto K = abi_param3; + auto ARG_ALPHA = abi_param4; +#ifdef _WIN32 + auto ARG_A = ptr[rsp + OFFSET_SHADOWSPACE + STACKSIZE]; + auto ARG_LDA = qword[rsp + OFFSET_SHADOWSPACE + + sizeof(float *) + STACKSIZE]; + const auto stackOffset = OFFSET_SHADOWSPACE + + sizeof(float *) + STACKSIZE; + auto A = rsi; + auto LDA = rdi; +#else + auto ARG_A = r8; + auto ARG_LDA = r9; + const auto stackOffset = STACKSIZE; + auto A = ARG_A; + auto LDA = ARG_LDA; +#endif + auto ARG_B = ptr[rsp + 8 + stackOffset]; + auto ARG_LDB = ptr[rsp + 16 + stackOffset]; + auto ARG_BETA = ptr[rsp + 24 + stackOffset]; + auto ARG_C = ptr[rsp + 32 + stackOffset]; + auto ARG_LDC = ptr[rsp + 40 + stackOffset]; + auto ARG_BIAS = ptr[rsp + 48 + stackOffset]; + auto ARG_WS = ptr[rsp + 56 + stackOffset]; + + auto B = r11; + auto LDB = rbx; + auto LDC = r13; + auto LL = rax; + auto AO1 = abi_param2; + auto BO1 = abi_param4; + auto BO2 = rbp; + auto CO1 = r14; + auto CO2 = r15; + auto LDB3 = r10; + auto LDA4 = abi_param1; + auto AA = r12; + auto BIAS1 = abi_param1; + + auto M = qword[rsp + 0]; + auto N = qword[rsp + 8]; + auto FLAG = qword[rsp + 16]; + auto I = qword[rsp + 24]; + auto C = qword[rsp + 32]; + auto BIAS = qword[rsp + 40]; + auto ALPHA = qword[rsp + 48]; + auto BETA = qword[rsp + 64]; + auto ORIG_A = qword[rsp + 80]; + auto ORIG_SP = qword[rsp + 120]; + + auto ZSTRIDE = zmm4; + auto VALPHA = zmm6; + auto VBETA = zmm7; + auto VBIAS1 = zmm1; + auto VBIAS2 = zmm2; + auto VBIAS3 = zmm3; + + auto PREFETCHSIZEA = ver == ver_avx512_core ? 48 : 80; + auto PREFETCHSIZEB = 16; + + Zmm regs[] = { zmm8, zmm9, zmm10, zmm11, zmm12, zmm13, zmm14, zmm15, + zmm16, zmm17, zmm18, zmm19, zmm20, zmm21, zmm22, zmm23, zmm24, + zmm25, zmm26, zmm27, zmm28, zmm29, zmm30, zmm31 }; + + // Function for packing if needed + auto do_pack = [&](int unroll_m) { + Label pack2, pack3, pack4, pack10; + + mov(BO1, A); + lea(AO1, ptr[rsp + 128 + OFFSET * SIZE]); + mov(LL, K); + sar(LL, 2); + jle(pack3, T_NEAR); + align(16); + + L(pack2); + if (!isTransA) { + for (int i = 0; i < 4; i++) { + vmovups(zmm0 | k1, ptr[BO1 + (0 * 16 - OFFSET) * SIZE]); + if (unroll_m > 16) + vmovups(zmm1 | k2, ptr[BO1 + (1 * 16 - OFFSET) * SIZE]); + if (unroll_m > 32) + vmovups(zmm2 | k3, ptr[BO1 + (2 * 16 - OFFSET) * SIZE]); + add(BO1, LDA); + + vmovups(ptr[AO1 + (unroll_m * i + 0 * 16 - OFFSET) * SIZE] + | k1, + zmm0); + if (unroll_m > 16) + vmovups(ptr[AO1 + + (unroll_m * i + 1 * 16 - OFFSET) + * SIZE] + | k2, + zmm1); + if (unroll_m > 32) + vmovups(ptr[AO1 + + (unroll_m * i + 2 * 16 - OFFSET) + * SIZE] + | k3, + zmm2); + } + } else { + for (int i = 0; i < 4; i++) { + kmovw(k4, k1); + vgatherqps(ymm5 | k4, + ptr[BO1 + ZSTRIDE + (i - OFFSET) * SIZE]); + lea(BO2, ptr[BO1 + LDA * 8]); + kshiftrw(k4, k1, 8); + vgatherqps(ymm6 | k4, + ptr[BO2 + ZSTRIDE + (i - OFFSET) * SIZE]); + vshuff64x2(zmm0, zmm5, zmm6, 0x44); + + if (unroll_m > 16) { + lea(BO2, ptr[BO2 + LDA * 8]); + kmovw(k4, k2); + vgatherqps(ymm5 | k4, + ptr[BO2 + ZSTRIDE + (i - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 8]); + kshiftrw(k4, k2, 8); + vgatherqps(ymm6 | k4, + ptr[BO2 + ZSTRIDE + (i - OFFSET) * SIZE]); + vshuff64x2(zmm1, zmm5, zmm6, 0x44); + } + + if (unroll_m > 32) { + lea(BO2, ptr[BO2 + LDA * 8]); + kmovw(k4, k3); + vgatherqps(ymm5 | k4, + ptr[BO2 + ZSTRIDE + (i - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 8]); + kshiftrw(k4, k3, 8); + vgatherqps(ymm6 | k4, + ptr[BO2 + ZSTRIDE + (i - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 8]); + vshuff64x2(zmm2, zmm5, zmm6, 0x44); + } + + vmovups(ptr[AO1 + (unroll_m * i + 0 * 16 - OFFSET) * SIZE], + zmm0 | k1); + if (unroll_m > 16) + vmovups(ptr[AO1 + + (unroll_m * i + 1 * 16 - OFFSET) + * SIZE], + zmm1 | k2); + if (unroll_m > 32) + vmovups(ptr[AO1 + + (unroll_m * i + 2 * 16 - OFFSET) + * SIZE], + zmm2 | k3); + } + add(BO1, 4 * SIZE); + } + add(AO1, unroll_m * 4 * SIZE); + + sub(LL, 1); + jg(pack2, T_NEAR); + align(16); + + L(pack3); + mov(LL, K); + and_(LL, 3); + jle(pack10, T_NEAR); + align(16); + + L(pack4); + if (!isTransA) { + vmovups(zmm0 | k1, ptr[BO1 + (0 * 16 - OFFSET) * SIZE]); + if (unroll_m > 16) + vmovups(zmm1 | k2, ptr[BO1 + (1 * 16 - OFFSET) * SIZE]); + if (unroll_m > 32) + vmovups(zmm2 | k3, ptr[BO1 + (2 * 16 - OFFSET) * SIZE]); + add(BO1, LDA); + } else { + kmovw(k4, k1); + vgatherqps(ymm5 | k4, ptr[BO1 + ZSTRIDE + (0 - OFFSET) * SIZE]); + lea(BO2, ptr[BO1 + LDA * 8]); + kshiftrw(k4, k1, 8); + vgatherqps(ymm6 | k4, ptr[BO2 + ZSTRIDE + (0 - OFFSET) * SIZE]); + vshuff64x2(zmm0, zmm5, zmm6, 0x44); + + if (unroll_m > 16) { + lea(BO2, ptr[BO2 + LDA * 8]); + kmovw(k4, k2); + vgatherqps(ymm5 | k4, + ptr[BO2 + ZSTRIDE + (0 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 8]); + kshiftrw(k4, k2, 8); + vgatherqps(ymm6 | k4, + ptr[BO2 + ZSTRIDE + (0 - OFFSET) * SIZE]); + vshuff64x2(zmm1, zmm5, zmm6, 0x44); + } + + if (unroll_m > 32) { + lea(BO2, ptr[BO2 + LDA * 8]); + kmovw(k4, k3); + vgatherqps(ymm5 | k4, + ptr[BO2 + ZSTRIDE + (0 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 8]); + kshiftrw(k4, k3, 8); + vgatherqps(ymm6 | k4, + ptr[BO2 + ZSTRIDE + (0 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 8]); + vshuff64x2(zmm2, zmm5, zmm6, 0x44); + } + add(BO1, SIZE); + } + + vmovups(ptr[AO1 + (unroll_m * 0 + 0 * 16 - OFFSET) * SIZE], + zmm0 | k1); + if (unroll_m > 16) + vmovups(ptr[AO1 + (unroll_m * 0 + 1 * 16 - OFFSET) * SIZE], + zmm1 | k2); + if (unroll_m > 32) + vmovups(ptr[AO1 + (unroll_m * 0 + 2 * 16 - OFFSET) * SIZE], + zmm2 | k3); + + add(AO1, unroll_m * SIZE); + sub(LL, 1); + jg(pack4, T_NEAR); + align(16); + + L(pack10); + }; + + // Function to update C, covering masking and other considerations + auto update = [&](Zmm reg, bool useCO1, int offset, int mask, + bool useScale = false) { + vmulps(reg, reg, VALPHA); + if (!isBeta0) { + if (!useScale) { + switch (mask) { + case 0: + if (useCO1) + vmovups(zmm0, ptr[CO1 + offset * SIZE]); + else + vmovups(zmm0, ptr[CO2 + offset * SIZE]); + break; + case 1: + if (useCO1) + vmovups(zmm0 | k1 | T_z, ptr[CO1 + offset * SIZE]); + else + vmovups(zmm0 | k1 | T_z, ptr[CO2 + offset * SIZE]); + break; + case 2: + if (useCO1) + vmovups(zmm0 | k2 | T_z, ptr[CO1 + offset * SIZE]); + else + vmovups(zmm0 | k2 | T_z, ptr[CO2 + offset * SIZE]); + break; + case 3: + if (useCO1) + vmovups(zmm0 | k3 | T_z, ptr[CO1 + offset * SIZE]); + else + vmovups(zmm0 | k3 | T_z, ptr[CO2 + offset * SIZE]); + break; + } + } else { + switch (mask) { + case 0: + if (useCO1) + vmovups(zmm0, ptr[CO1 + LDC + offset * SIZE]); + else + vmovups(zmm0, ptr[CO2 + LDC + offset * SIZE]); + break; + case 1: + if (useCO1) + vmovups(zmm0 | k1 | T_z, + ptr[CO1 + LDC + offset * SIZE]); + else + vmovups(zmm0 | k1 | T_z, + ptr[CO2 + LDC + offset * SIZE]); + break; + case 2: + if (useCO1) + vmovups(zmm0 | k2 | T_z, + ptr[CO1 + LDC + offset * SIZE]); + else + vmovups(zmm0 | k2 | T_z, + ptr[CO2 + LDC + offset * SIZE]); + break; + case 3: + if (useCO1) + vmovups(zmm0 | k3 | T_z, + ptr[CO1 + LDC + offset * SIZE]); + else + vmovups(zmm0 | k3 | T_z, + ptr[CO2 + LDC + offset * SIZE]); + break; + } + } + if (!isBetaN) { + vaddps(zmm0, reg, zmm0); + } else { + vfmadd132ps(zmm0, reg, VBETA); + } + if (!useScale) { + switch (mask) { + case 0: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], zmm0); + else + vmovups(ptr[CO2 + offset * SIZE], zmm0); + break; + case 1: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], zmm0 | k1); + else + vmovups(ptr[CO2 + offset * SIZE], zmm0 | k1); + break; + case 2: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], zmm0 | k2); + else + vmovups(ptr[CO2 + offset * SIZE], zmm0 | k2); + break; + case 3: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], zmm0 | k3); + else + vmovups(ptr[CO2 + offset * SIZE], zmm0 | k3); + break; + } + } else { + switch (mask) { + case 0: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], zmm0); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], zmm0); + break; + case 1: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], zmm0 | k1); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], zmm0 | k1); + break; + case 2: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], zmm0 | k2); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], zmm0 | k2); + break; + case 3: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], zmm0 | k3); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], zmm0 | k3); + break; + } + } + } else { + if (!useScale) { + switch (mask) { + case 0: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], reg); + else + vmovups(ptr[CO2 + offset * SIZE], reg); + break; + case 1: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], reg | k1); + else + vmovups(ptr[CO2 + offset * SIZE], reg | k1); + break; + case 2: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], reg | k2); + else + vmovups(ptr[CO2 + offset * SIZE], reg | k2); + break; + case 3: + if (useCO1) + vmovups(ptr[CO1 + offset * SIZE], reg | k3); + else + vmovups(ptr[CO2 + offset * SIZE], reg | k3); + break; + } + } else { + switch (mask) { + case 0: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], reg); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], reg); + break; + case 1: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], reg | k1); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], reg | k1); + break; + case 2: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], reg | k2); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], reg | k2); + break; + case 3: + if (useCO1) + vmovups(ptr[CO1 + LDC + offset * SIZE], reg | k3); + else + vmovups(ptr[CO2 + LDC + offset * SIZE], reg | k3); + break; + } + } + } + vpxorq(reg, reg, reg); + }; + + // Loop with unroll_n - 2 FMAs; called by innerkernel + auto fmaloop = [&](int unroll_m, int unroll_n, int iteration) { + for (int i = 2; i < unroll_n; i++) { + if (ver == ver_avx512_core) { + if (!isTransB) { + switch (i) { + case 2: + vbroadcastss( + zmm3, + ptr[BO1 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + break; + case 3: + vbroadcastss( + zmm3, + ptr[BO1 + LDB3 + + (iteration - OFFSET) * SIZE]); + break; + case 4: + vbroadcastss(zmm3, + ptr[BO2 + (iteration - OFFSET) * SIZE]); + break; + case 5: + vbroadcastss( + zmm3, + ptr[BO2 + LDB * 1 + + (iteration - OFFSET) * SIZE]); + break; + case 6: + vbroadcastss( + zmm3, + ptr[BO2 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + break; + case 7: + vbroadcastss( + zmm3, + ptr[BO2 + LDB3 + + (iteration - OFFSET) * SIZE]); + break; + } + } else { + vbroadcastss(zmm3, ptr[BO1 + (i - OFFSET) * SIZE]); + } + vfmadd231ps(regs[i], zmm3, zmm0); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm3, zmm1); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm3, zmm2); + } else { + if (!isTransB) { + switch (i) { + case 2: + vfmadd231ps(regs[i], zmm0, + zword_b[BO1 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO1 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO1 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + break; + case 3: + vfmadd231ps(regs[i], zmm0, + zword_b[BO1 + LDB3 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO1 + LDB3 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO1 + LDB3 + + (iteration - OFFSET) * SIZE]); + break; + case 4: + vfmadd231ps(regs[i], zmm0, + zword_b[BO2 + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO2 + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO2 + (iteration - OFFSET) * SIZE]); + break; + case 5: + vfmadd231ps(regs[i], zmm0, + zword_b[BO2 + LDB * 1 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO2 + LDB * 1 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO2 + LDB * 1 + + (iteration - OFFSET) * SIZE]); + break; + case 6: + vfmadd231ps(regs[i], zmm0, + zword_b[BO2 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO2 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO2 + LDB * 2 + + (iteration - OFFSET) * SIZE]); + break; + case 7: + vfmadd231ps(regs[i], zmm0, + zword_b[BO2 + LDB3 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO2 + LDB3 + + (iteration - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO2 + LDB3 + + (iteration - OFFSET) * SIZE]); + break; + } + } else { + vfmadd231ps( + regs[i], zmm0, zword_b[BO1 + (i - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[i + 8], zmm1, + zword_b[BO1 + (i - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[i + 16], zmm2, + zword_b[BO1 + (i - OFFSET) * SIZE]); + } + } + } + }; + + // Innerkernel; called by kernel + auto innerkernel = [&](int unroll_m, int unroll_n, bool isDirect, + bool isCopy, bool doCPrefetch, bool isUnmasked = true) { + for (int i = 0; i < 8; i++) { + if (!isDirect) { + prefetcht0(ptr[AO1 + + (PREFETCHSIZEA + i * unroll_m + 0 * 16 - OFFSET) + * SIZE]); + if (unroll_m >= 32) + prefetcht0(ptr[AO1 + + (PREFETCHSIZEA + i * unroll_m + 1 * 16 - OFFSET) + * SIZE]); + if (unroll_m >= 48) + prefetcht0(ptr[AO1 + + (PREFETCHSIZEA + i * unroll_m + 2 * 16 - OFFSET) + * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4 + (16 * 0 * SIZE)]); + if (unroll_m >= 32) + prefetcht0(ptr[AO1 + LDA4 + (16 * 1 * SIZE)]); + if (unroll_m >= 48) + prefetcht0(ptr[AO1 + LDA4 + (16 * 2 * SIZE)]); + } + + if (!isDirect) { + if (i != 0) { + if (isUnmasked || unroll_m > 16) { + vmovups(zmm0, + ptr[AO1 + + (unroll_m * i + 0 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm0 | k1 | T_z, + ptr[AO1 + + (unroll_m * i + 0 * 16 - OFFSET) + * SIZE]); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(zmm1, ptr[AO1 + + (unroll_m * i + 1 * 16 + - OFFSET) + * SIZE]); + } else { + vmovups(zmm1 | k2 | T_z, + ptr[AO1 + + (unroll_m * i + 1 * 16 + - OFFSET) + * SIZE]); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(zmm2, ptr[AO1 + + (unroll_m * i + 2 * 16 + - OFFSET) + * SIZE]); + } else { + vmovups(zmm2 | k3 | T_z, + ptr[AO1 + + (unroll_m * i + 2 * 16 + - OFFSET) + * SIZE]); + } + } + } + } else { + if (isUnmasked || unroll_m > 16) { + vmovups(zmm0, ptr[AO1 + (0 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm0 | k1 | T_z, + ptr[AO1 + (0 * 16 - OFFSET) * SIZE]); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(zmm1, ptr[AO1 + (1 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm1 | k2 | T_z, + ptr[AO1 + (1 * 16 - OFFSET) * SIZE]); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(zmm2, ptr[AO1 + (2 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm2 | k3 | T_z, + ptr[AO1 + (2 * 16 - OFFSET) * SIZE]); + } + } + add(AO1, LDA); + } + + if (ver == ver_avx512_core) { + if (!isTransB) { + vbroadcastss(zmm3, ptr[BO1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(zmm3, ptr[BO1 + (0 - OFFSET) * SIZE]); + } + vfmadd231ps(regs[0], zmm3, zmm0); + if (unroll_m >= 32) + vfmadd231ps(regs[0 + 8], zmm3, zmm1); + if (unroll_m >= 48) + vfmadd231ps(regs[0 + 16], zmm3, zmm2); + } else { + if (!isTransB) { + vfmadd231ps(regs[0], zmm0, + zword_b[BO1 + (i - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[0 + 8], zmm1, + zword_b[BO1 + (i - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[0 + 16], zmm2, + zword_b[BO1 + (i - OFFSET) * SIZE]); + } else { + vfmadd231ps(regs[0], zmm0, + zword_b[BO1 + (0 - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[0 + 8], zmm1, + zword_b[BO1 + (0 - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[0 + 16], zmm2, + zword_b[BO1 + (0 - OFFSET) * SIZE]); + } + } + + if (unroll_n >= i + 1) { + if (!isTransB) { + switch (i) { + case 0: + prefetcht0( + ptr[BO1 + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 1: + prefetcht0(ptr[BO1 + LDB + + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 2: + prefetcht0(ptr[BO1 + LDB * 2 + + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 3: + prefetcht0(ptr[BO1 + LDB3 + + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 4: + prefetcht0( + ptr[BO2 + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 5: + prefetcht0(ptr[BO2 + LDB + + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 6: + prefetcht0(ptr[BO2 + LDB * 2 + + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + case 7: + prefetcht0(ptr[BO2 + LDB3 + + (PREFETCHSIZEB - OFFSET) * SIZE]); + break; + } + } + } + + if (unroll_n >= 2) { + if (ver == ver_avx512_core) { + if (!isTransB) { + vbroadcastss(zmm3, + ptr[BO1 + LDB * 1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(zmm3, ptr[BO1 + (1 - OFFSET) * SIZE]); + } + vfmadd231ps(regs[1], zmm3, zmm0); + if (unroll_m >= 32) + vfmadd231ps(regs[1 + 8], zmm3, zmm1); + if (unroll_m >= 48) + vfmadd231ps(regs[1 + 16], zmm3, zmm2); + } else { + if (!isTransB) { + vfmadd231ps(regs[1], zmm0, + zword_b[BO1 + LDB * 1 + (i - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[1 + 8], zmm1, + zword_b[BO1 + LDB * 1 + + (i - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[1 + 16], zmm2, + zword_b[BO1 + LDB * 1 + + (i - OFFSET) * SIZE]); + } else { + vfmadd231ps(regs[1], zmm0, + zword_b[BO1 + (1 - OFFSET) * SIZE]); + if (unroll_m >= 32) + vfmadd231ps(regs[1 + 8], zmm1, + zword_b[BO1 + (1 - OFFSET) * SIZE]); + if (unroll_m >= 48) + vfmadd231ps(regs[1 + 16], zmm2, + zword_b[BO1 + (1 - OFFSET) * SIZE]); + } + } + } + + if (isCopy) { + if (isUnmasked || unroll_m > 16) { + vmovups(ptr[LDA4 + + (unroll_m * i + 0 * 16 - OFFSET) + * SIZE], + zmm0); + } else { + vmovups(ptr[LDA4 + + (unroll_m * i + 0 * 16 - OFFSET) + * SIZE], + zmm0 | k1); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(ptr[LDA4 + + (unroll_m * i + 1 * 16 - OFFSET) + * SIZE], + zmm1); + } else { + vmovups(ptr[LDA4 + + (unroll_m * i + 1 * 16 - OFFSET) + * SIZE], + zmm1 | k2); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(ptr[LDA4 + + (unroll_m * i + 2 * 16 - OFFSET) + * SIZE], + zmm2); + } else { + vmovups(ptr[LDA4 + + (unroll_m * i + 2 * 16 - OFFSET) + * SIZE], + zmm2 | k3); + } + } + if (i == 7) + sub(LDA4, -unroll_m * 8 * SIZE); + } + fmaloop(unroll_m, unroll_n, i); + + if (i == 1) { + if (doCPrefetch) { + if (ver == ver_avx512_core) + prefetchw(ptr[CO2 + 0 * 16 * SIZE]); + else + prefetcht0(ptr[CO2 + 0 * 16 * SIZE]); + } + } + if (i == 3) { + if (doCPrefetch && unroll_m >= 32) { + if (ver == ver_avx512_core) + prefetchw(ptr[CO2 + 1 * 16 * SIZE]); + else + prefetcht0(ptr[CO2 + 1 * 16 * SIZE]); + } + if (!isTransA) { + if (ver == ver_avx512_core) + prefetcht0(ptr[AA + 16 * 0 * SIZE]); + else + prefetcht2(ptr[AA + 16 * 0 * SIZE]); + } + } + if (i == 5) { + if (doCPrefetch) { + if (unroll_m >= 48) { + if (ver == ver_avx512_core) + prefetchw(ptr[CO2 + 2 * 16 * SIZE]); + else + prefetcht0(ptr[CO2 + 2 * 16 * SIZE]); + } + add(CO2, LDC); + } + if (!isTransA) { + if (unroll_m >= 32) { + if (ver == ver_avx512_core) + prefetcht0(ptr[AA + 16 * 1 * SIZE]); + else + prefetcht2(ptr[AA + 16 * 1 * SIZE]); + } + } + } + + if (isTransB) { + prefetcht0(ptr[BO1 + BO2]); + add(BO1, LDB); + } + } // end of for loop + + if (!isTransB) { + sub(BO1, -8 * SIZE); + if (unroll_n >= 4) + sub(BO2, -8 * SIZE); + } + if (!isTransA) { + if (unroll_m >= 48) { + if (ver == ver_avx512_core) + prefetcht0(ptr[AA + 16 * 2 * SIZE]); + else + prefetcht2(ptr[AA + 16 * 2 * SIZE]); + } + lea(AA, ptr[AA + LDA]); + } + + if (!isDirect) { + if (isUnmasked || unroll_m > 16) { + vmovups(zmm0, + ptr[AO1 + (unroll_m * 8 + 0 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm0 | k1 | T_z, + ptr[AO1 + (unroll_m * 8 + 0 * 16 - OFFSET) * SIZE]); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(zmm1, ptr[AO1 + + (unroll_m * 8 + 1 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm1 | k2 | T_z, + ptr[AO1 + + (unroll_m * 8 + 1 * 16 - OFFSET) + * SIZE]); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(zmm2, ptr[AO1 + + (unroll_m * 8 + 2 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm2 | k3 | T_z, + ptr[AO1 + + (unroll_m * 8 + 2 * 16 - OFFSET) + * SIZE]); + } + } + sub(AO1, -unroll_m * 8 * SIZE); + } + + sub(LL, 1); + }; + + // Main kernel; does prefetching and calls innerkernel + // After calculating results in registers, writes back to C matrix by + // calling update + auto kernel = [&](int unroll_m, int unroll_n, bool isDirect, + bool isCopy, bool isUnmasked = true) { + if (!isDirect) { + lea(AO1, ptr[rsp + 128 + OFFSET * SIZE]); + } else { + mov(AO1, A); + } + + if (isCopy) { + lea(LDA4, ptr[rsp + 128 + OFFSET * SIZE]); + } else { + auto step = ver == ver_avx512_core ? 2 : 4; + lea(LDA4, ptr[LDA * step + (16 - 1 - OFFSET) * SIZE]); + } + + if (isTransB) { + lea(BO2, ptr[LDB * 4 + (16 / 2 - 1 - OFFSET) * SIZE]); + } + + if (!isDirect) { + if (isUnmasked || unroll_m > 16) { + vmovups(zmm0, + ptr[AO1 + (unroll_m * 0 + 0 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm0 | k1 | T_z, + ptr[AO1 + (unroll_m * 0 + 0 * 16 - OFFSET) * SIZE]); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(zmm1, ptr[AO1 + + (unroll_m * 0 + 1 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm1 | k2 | T_z, + ptr[AO1 + + (unroll_m * 0 + 1 * 16 - OFFSET) + * SIZE]); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(zmm2, ptr[AO1 + + (unroll_m * 0 + 2 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm2 | k3 | T_z, + ptr[AO1 + + (unroll_m * 0 + 2 * 16 - OFFSET) + * SIZE]); + } + } + } + + Label kernel12, kernel13, kernel14, kernel15, kernel16, kernel18; + + mov(LL, K); + sar(LL, 3); + sub(LL, SECOND_FETCH); + jle(kernel13, T_NEAR); + align(16); + + L(kernel12); + innerkernel( + unroll_m, unroll_n, isDirect, isCopy, false, isUnmasked); + jg(kernel12, T_NEAR); + align(16); + + L(kernel13); + lea(CO2, ptr[CO1 + (16 - 1) * SIZE]); + add(LL, unroll_n); + jle(kernel15, T_NEAR); + align(16); + + L(kernel14); + innerkernel(unroll_m, unroll_n, isDirect, isCopy, true, isUnmasked); + jg(kernel14, T_NEAR); + align(16); + + L(kernel15); + mov(LL, K); + and_(LL, 7); + jle(kernel18, T_NEAR); + align(16); + + L(kernel16); + if (isDirect) { + if (isUnmasked || unroll_m > 16) { + vmovups(zmm0, ptr[AO1 + (0 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm0 | k1 | T_z, + ptr[AO1 + (0 * 16 - OFFSET) * SIZE]); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(zmm1, ptr[AO1 + (1 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm1 | k2 | T_z, + ptr[AO1 + (1 * 16 - OFFSET) * SIZE]); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(zmm2, ptr[AO1 + (2 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm2 | k3 | T_z, + ptr[AO1 + (2 * 16 - OFFSET) * SIZE]); + } + } + add(AO1, LDA); + } + + for (int i = 0; i < unroll_n; i++) { + if (!isTransB) { + switch (i) { + case 0: + vbroadcastss(zmm3, ptr[BO1 + (0 - OFFSET) * SIZE]); + break; + case 1: + vbroadcastss( + zmm3, ptr[BO1 + LDB * 1 + (0 - OFFSET) * SIZE]); + break; + case 2: + vbroadcastss( + zmm3, ptr[BO1 + LDB * 2 + (0 - OFFSET) * SIZE]); + break; + case 3: + vbroadcastss( + zmm3, ptr[BO1 + LDB3 + (0 - OFFSET) * SIZE]); + break; + case 4: + vbroadcastss(zmm3, ptr[BO2 + (0 - OFFSET) * SIZE]); + break; + case 5: + vbroadcastss( + zmm3, ptr[BO2 + LDB * 1 + (0 - OFFSET) * SIZE]); + break; + case 6: + vbroadcastss( + zmm3, ptr[BO2 + LDB * 2 + (0 - OFFSET) * SIZE]); + break; + case 7: + vbroadcastss( + zmm3, ptr[BO2 + LDB3 + (0 - OFFSET) * SIZE]); + break; + } + } else { + vbroadcastss(zmm3, ptr[BO1 + (i - OFFSET) * SIZE]); + } + vfmadd231ps(regs[i], zmm3, zmm0); + if (unroll_m >= 32) { + vfmadd231ps(regs[i + 8], zmm3, zmm1); + } + if (unroll_m >= 48) { + vfmadd231ps(regs[i + 16], zmm3, zmm2); + } + } + + if (isCopy) { + if (isUnmasked || unroll_m > 16) { + vmovups(ptr[LDA4 + (unroll_m * 0 + 0 * 16 - OFFSET) * SIZE], + zmm0); + } else { + vmovups(ptr[LDA4 + (unroll_m * 0 + 0 * 16 - OFFSET) * SIZE], + zmm0 | k1); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(ptr[LDA4 + + (unroll_m * 0 + 1 * 16 - OFFSET) + * SIZE], + zmm1); + } else { + vmovups(ptr[LDA4 + + (unroll_m * 0 + 1 * 16 - OFFSET) + * SIZE], + zmm1 | k2); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(ptr[LDA4 + + (unroll_m * 0 + 2 * 16 - OFFSET) + * SIZE], + zmm2); + } else { + vmovups(ptr[LDA4 + + (unroll_m * 0 + 2 * 16 - OFFSET) + * SIZE], + zmm2 | k3); + } + } + sub(LDA4, -unroll_m * SIZE); + } + + if (!isDirect) { + if (isUnmasked || unroll_m > 16) { + vmovups(zmm0, + ptr[AO1 + (unroll_m * 1 + 0 * 16 - OFFSET) * SIZE]); + } else { + vmovups(zmm0 | k1 | T_z, + ptr[AO1 + (unroll_m * 1 + 0 * 16 - OFFSET) * SIZE]); + } + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) { + vmovups(zmm1, ptr[AO1 + + (unroll_m * 1 + 1 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm1 | k2 | T_z, + ptr[AO1 + + (unroll_m * 1 + 1 * 16 - OFFSET) + * SIZE]); + } + } + if (unroll_m >= 48) { + if (isUnmasked) { + vmovups(zmm2, ptr[AO1 + + (unroll_m * 1 + 2 * 16 - OFFSET) + * SIZE]); + } else { + vmovups(zmm2 | k3 | T_z, + ptr[AO1 + + (unroll_m * 1 + 2 * 16 - OFFSET) + * SIZE]); + } + } + sub(AO1, -unroll_m * SIZE); + } + + if (!isTransB) { + sub(BO1, -SIZE); + if (unroll_n >= 4) { + sub(BO2, -SIZE); + } + } else { + add(BO1, LDB); + } + + sub(LL, 1); + jg(kernel16, T_NEAR); + align(16); + + L(kernel18); + vbroadcastss(VALPHA, ALPHA); + + if (isBetaN) { + vbroadcastss(VBETA, BETA); + } + + // Write back the results; all beta cases need to be handled + if (hasBias) { + mov(BIAS1, BIAS); + if (isUnmasked || unroll_m > 16) + vmovups(VBIAS1, ptr[BIAS1 + 0 * SIZE]); + else + vmovups(VBIAS1 | k1 | T_z, ptr[BIAS1 + 0 * SIZE]); + if (unroll_m >= 32) { + if (isUnmasked || unroll_m > 32) + vmovups(VBIAS2, ptr[BIAS1 + 16 * SIZE]); + else + vmovups(VBIAS2 | k2 | T_z, ptr[BIAS1 + 16 * SIZE]); + } + if (unroll_m >= 48) { + if (isUnmasked) + vmovups(VBIAS3, ptr[BIAS1 + 32 * SIZE]); + else + vmovups(VBIAS3 | k3 | T_z, ptr[BIAS1 + 32 * SIZE]); + } + } + + for (int i = 0; i < unroll_n; i++) { + bool useScale = i % 2 != 0; + bool useCO1 = i < 2; + if (i == 2) + lea(CO2, ptr[CO1 + LDC * 2]); + if (i == 4 || i == 6) + lea(CO2, ptr[CO2 + LDC * 2]); + if (hasBias) + vaddps(regs[i], VBIAS1, regs[i]); + if (isUnmasked || unroll_m > 16) { + update(regs[i], useCO1, 0, 0, useScale); + } else { + update(regs[i], useCO1, 0, 1, useScale); + } + if (unroll_m >= 32) { + if (hasBias) + vaddps(regs[i + 8], VBIAS2, regs[i + 8]); + if (isUnmasked || unroll_m > 32) { + update(regs[i + 8], useCO1, 16, 0, useScale); + } else { + update(regs[i + 8], useCO1, 16, 2, useScale); + } + } + if (unroll_m >= 48) { + if (hasBias) + vaddps(regs[i + 16], VBIAS3, regs[i + 16]); + if (isUnmasked) { + update(regs[i + 16], useCO1, 32, 0, useScale); + } else { + update(regs[i + 16], useCO1, 32, 3, useScale); + } + } + } + + switch (unroll_n) { + case 1: add(CO1, LDC); break; + case 2: lea(CO1, ptr[CO1 + LDC * 2]); break; + case 3: lea(CO1, ptr[CO2 + LDC * 1]); break; + case 4: lea(CO1, ptr[CO2 + LDC * 2]); break; + case 5: lea(CO1, ptr[CO2 + LDC * 1]); break; + case 6: lea(CO1, ptr[CO2 + LDC * 2]); break; + case 7: lea(CO1, ptr[CO2 + LDC * 1]); break; + case 8: lea(CO1, ptr[CO2 + LDC * 2]); break; + } + + // Compute next address of B + if (!isTransB) { + lea(rax, ptr[K * SIZE]); + switch (unroll_n) { + case 1: + add(BO1, LDB); + add(BO2, LDB); + break; + case 2: + lea(BO1, ptr[BO1 + LDB * 2]); + lea(BO2, ptr[BO2 + LDB * 2]); + break; + case 3: + lea(BO1, ptr[BO1 + LDB3]); + lea(BO2, ptr[BO2 + LDB3]); + break; + case 4: + lea(BO1, ptr[BO1 + LDB * 4]); + lea(BO2, ptr[BO2 + LDB * 4]); + break; + case 5: + lea(BO1, ptr[BO1 + LDB * 4]); + add(BO1, LDB); + lea(BO2, ptr[BO2 + LDB * 4]); + add(BO2, LDB); + break; + case 6: + lea(BO1, ptr[BO1 + LDB3 * 2]); + lea(BO2, ptr[BO2 + LDB3 * 2]); + break; + case 7: + lea(BO1, ptr[BO1 + LDB * 8]); + sub(BO1, LDB); + lea(BO2, ptr[BO2 + LDB * 8]); + sub(BO2, LDB); + break; + case 8: + lea(BO1, ptr[BO1 + LDB * 8]); + lea(BO2, ptr[BO2 + LDB * 8]); + break; + } + sub(BO1, rax); + sub(BO2, rax); + } else { + mov(rax, LDB); + imul(rax, K); + sub(BO1, rax); + add(BO1, unroll_n * SIZE); + } + }; + + // High-level subroutine; does packing if needed, then splits C matrix. + // Operates on chunks of 48 rows, 8 columns at a time (handling tail + // cases appropriately by doing 32 or 16 rows, and/or with masking, + // and/or fewer columns). + auto subloop = [&](int unroll_m) { + Label l_subloop_20x[8], l_subloop_mask_20x[8]; + Label l_subloop_30x[8], l_subloop_mask_30x[8]; + + Label subloop11, subloop11mask; + Label subloop30, subloop30mask; + Label subloop31, subloop31mask; + Label subloop96; + Label subloop98, subloop98mask; + Label subloop99; + + // Create mask + mov(BO1, rcx); + mov(rcx, M); + sub(rcx, unroll_m - 16); + mov(CO1, 16); + cmp(rcx, 16); + + cmovg(rcx, CO1); + mov(rax, 1); + sal(rax, cl); + sub(rax, 1); + mov(rcx, 0xffff); + + if (unroll_m == 16) { + kmovw(k1, eax); + } else if (unroll_m == 32) { + kmovw(k1, ecx); + kmovw(k2, eax); + } else { + kmovw(k1, ecx); + kmovw(k2, ecx); + kmovw(k3, eax); + } + mov(rcx, BO1); + + and_(rax, 0xffff); + cmp(rax, 0xffff); + jne(subloop96, T_NEAR); + + if (isTransA) { + do_pack(unroll_m); + } + + mov(CO1, C); + add(C, unroll_m * SIZE); + + mov(BO1, B); + if (!isTransB) { + lea(BO2, ptr[B + LDB * 4]); + } + + if (!isTransA) { + lea(AA, ptr[A + (unroll_m + 16 - 1 - OFFSET) * SIZE]); + cmp(M, UNROLL_M); + jg(subloop98, T_NEAR); + + mov(AA, ORIG_A); + lea(AA, ptr[AA + (16 - 1 - OFFSET) * SIZE]); + L(subloop98); + } + + mov(LL, N); + mov(I, LL); + if (!isTransA) { + // If N is too small, skip copy operation + cmp(LL, UNROLL_N * 3); + jle(subloop30, T_NEAR); + + // If A is not aligned to cache line + cmp(FLAG, 0); + je(subloop30, T_NEAR); + } else { + cmp(LL, UNROLL_N); + jl(l_subloop_20x[1], T_NEAR); + } + align(16); + + if (!isTransA) { + kernel(unroll_m, UNROLL_N, true, true); + } else { + kernel(unroll_m, UNROLL_N, false, false); + } + + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jl(l_subloop_20x[1], T_NEAR); + align(16); + + L(subloop11); + kernel(unroll_m, UNROLL_N, false, false); + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jge(subloop11, T_NEAR); + align(16); + + for (int i = 1; i <= 7; i++) { + L(l_subloop_20x[i]); + cmp(I, i); + if (i < 7) { + jne(l_subloop_20x[i + 1], T_NEAR); + } else { + jne(subloop99, T_NEAR); + } + kernel(unroll_m, i, false, false); + jmp(subloop99, T_NEAR); + align(16); + } + + if (!isTransA) { + L(subloop30); + cmp(I, UNROLL_N); + jl(l_subloop_30x[1], T_NEAR); + align(16); + + L(subloop31); + kernel(unroll_m, UNROLL_N, true, false); + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jge(subloop31, T_NEAR); + align(16); + + for (int i = 1; i <= 7; i++) { + L(l_subloop_30x[i]); + cmp(I, i); + if (i < 7) { + jne(l_subloop_30x[i + 1], T_NEAR); + } else { + jne(subloop99, T_NEAR); + } + kernel(unroll_m, i, true, false); + if (i < 7) + jmp(subloop99, T_NEAR); + align(16); + } + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop96); + if (isTransA) { + do_pack(unroll_m); + } + + mov(CO1, C); + add(C, unroll_m * SIZE); + mov(BO1, B); + if (!isTransB) { + lea(BO2, ptr[B + LDB * 4]); + } + + if (!isTransA) { + lea(AA, ptr[A + (unroll_m + 16 - 1 - OFFSET) * SIZE]); + cmp(M, UNROLL_M); + jg(subloop98mask, T_NEAR); + mov(AA, ORIG_A); + lea(AA, ptr[AA + (16 - 1 - OFFSET) * SIZE]); + L(subloop98mask); + } + + mov(LL, N); + mov(I, LL); + if (!isTransA) { + // If N is too small, skip copy operation + cmp(LL, UNROLL_N * 3); + jle(subloop30mask, T_NEAR); + + // If A is not aligned to cache line + cmp(FLAG, 0); + je(subloop30mask, T_NEAR); + } else { + cmp(LL, UNROLL_N); + jl(l_subloop_mask_20x[1], T_NEAR); + } + align(16); + + if (!isTransA) { + kernel(unroll_m, UNROLL_N, true, true, false); + } else { + kernel(unroll_m, UNROLL_N, false, false, false); + } + + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jl(l_subloop_mask_20x[1], T_NEAR); + align(16); + + L(subloop11mask); + kernel(unroll_m, UNROLL_N, false, false, false); + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jge(subloop11mask, T_NEAR); + align(16); + + for (int i = 1; i <= 7; i++) { + L(l_subloop_mask_20x[i]); + cmp(I, i); + if (i < 7) { + jne(l_subloop_mask_20x[i + 1], T_NEAR); + } else { + jne(subloop99, T_NEAR); + } + kernel(unroll_m, i, false, false, false); + jmp(subloop99, T_NEAR); + align(16); + } + + if (!isTransA) { + L(subloop30mask); + cmp(I, UNROLL_N); + jl(l_subloop_mask_30x[1], T_NEAR); + align(16); + + L(subloop31mask); + kernel(unroll_m, UNROLL_N, true, false, false); + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jge(subloop31mask, T_NEAR); + align(16); + + for (int i = 1; i <= 7; i++) { + L(l_subloop_mask_30x[i]); + cmp(I, i); + if (i < 7) { + jne(l_subloop_mask_30x[i + 1], T_NEAR); + } else { + jne(subloop99, T_NEAR); + } + kernel(unroll_m, i, true, false, false); + if (i < 7) + jmp(subloop99, T_NEAR); + align(16); + } + } + + L(subloop99); + // Compute address for A + if (!isTransA) { + add(A, unroll_m * SIZE); + } else { + mov(rax, LDA); + imul(rax, rax, unroll_m); + add(A, rax); + } + + // Compute next address of BIAS + if (hasBias) { + add(BIAS, unroll_m * SIZE); + } + }; + + preamble(); + + Label buffer_in_ws, buffer_allocated; + + // Get the registers + mov(B, ARG_B); + mov(LDB, ARG_LDB); + mov(r15, ARG_BETA); + mov(r12, ARG_C); + if (hasBias) + mov(r10, ARG_BIAS); + mov(LDC, ARG_LDC); + mov(rbp, rsp); + + vmovss(xmm0, ptr[ARG_ALPHA]); + vmovss(xmm1, ptr[r15]); + +#if _WIN32 + mov(A, ARG_A); + mov(LDA, ARG_LDA); +#endif + + cmp(K, STACK_K_CAPACITY); + jg(buffer_in_ws, T_NEAR); + + // Create buffer and align to 4kB page + lea(rax, ptr[K * SIZE]); + imul(rax, rax, 0x30); + add(rax, 256); + sub(rsp, rax); + and_(rsp, -PAGE_4K); + jmp(buffer_allocated, T_NEAR); + + L(buffer_in_ws); + mov(rsp, ARG_WS); + + L(buffer_allocated); + + mov(ORIG_SP, rbp); + mov(M, ARG_M); + mov(N, ARG_N); + mov(C, r12); + if (hasBias) + mov(BIAS, r10); + vmovss(ALPHA, xmm0); + vmovss(BETA, xmm1); + sub(A, -OFFSET * SIZE); + sub(B, -OFFSET * SIZE); + mov(ORIG_A, A); + sal(LDA, BASE_SHIFT); + sal(LDB, BASE_SHIFT); + sal(LDC, BASE_SHIFT); + lea(LDB3, ptr[LDB + LDB * 2]); + + if (isTransA) { + vpbroadcastq(zmm2, LDA); + vpxorq(ZSTRIDE, ZSTRIDE, ZSTRIDE); + mov(rax, -2); + kmovw(k4, eax); + + for (int i = 0; i < 6; i++) { + vpaddq(ZSTRIDE | k4, ZSTRIDE, zmm2); + kshiftlw(k4, k4, 1); + } + vpaddq(ZSTRIDE | k4, ZSTRIDE, zmm2); + } + + // Check A alignment and leading dimension; take copy-based path as + // needed + mov(rax, LDA); + or_(rax, A); + and_(rax, ver == ver_avx512_core ? 0x07 : 0x3f); + mov(FLAG, rax); + + for (int i = 8; i < 16; i++) { + for (int j = 0; j < 3; j++) { + vpxorq(Zmm(i + 8 * j), Zmm(i + 8 * j), Zmm(i + 8 * j)); + } + } + + Label main0, main1, main2, main999; + + cmp(M, 32); + jle(main0, T_NEAR); + align(16); + + L(main1); + subloop(48); + sub(M, UNROLL_M); + cmp(M, 32); + jg(main1, T_NEAR); + align(16); + + L(main0); + cmp(M, 16); + jle(main2, T_NEAR); + + subloop(32); + jmp(main999, T_NEAR); + align(16); + + L(main2); + cmp(M, 0); + jle(main999, T_NEAR); + subloop(16); + align(16); + + L(main999); + // Restore original stack + mov(rsp, ORIG_SP); + + vzeroupper(); + postamble(); + + ker_ = this->getCode(); + } + + typedef void (*ker_t)(dim_t m, dim_t n, dim_t k, + const float *alpha, const float *a, dim_t lda, + const float *b, dim_t ldb, const float *beta, float *c, + dim_t ldc, const float *bias, float *ws); + + void operator()(dim_t m, dim_t n, dim_t k, + const float *alpha, const float *a, dim_t lda, + const float *b, dim_t ldb, const float *beta, float *c, + dim_t ldc, const float *bias, float *ws) const + { + ker_(m, n, k, alpha, a, lda, b, ldb, beta, c, ldc, bias, ws); + } + +private: + ker_t ker_; +}; + +const xbyak_gemm *get_xbyak_gemm( + bool isTransA, bool isTransB, float beta, bool hasBias) { + auto beta_idx = [](float beta) { + return (beta == 0.0) ? 0 : (beta == 1.0 ? 1 : 2); + }; + + // Kernel table [isTransA][isTransB][hasBias][beta (0, 1, other)] + static xbyak_gemm *kernel_table[2][2][2][3]; + static std::once_flag initialized; + std::call_once(initialized, [=]{ + for (bool isTransA: {false, true}) + for (bool isTransB: {false, true}) + for (bool hasBias: {false, true}) + for (float beta: {0.0f, 1.0f, 2.0f}) { + // nocopy sgemm with bias for beta != 0.0 is not supported + if (hasBias && beta != 0.0) + continue; + kernel_table[isTransA][isTransB][hasBias][beta_idx(beta)] = + new xbyak_gemm(isTransA, isTransB, beta, hasBias); + } + }); + + return kernel_table[isTransA][isTransB][hasBias][beta_idx(beta)]; +} + +void sgemm_nocopy_driver(const char *transa, + const char *transb, int m, int n, int k, const float *alpha, + const float *a, dim_t lda, const float *b, dim_t ldb, const float *beta, + float *c, dim_t ldc, const float *bias, float *ws) +{ + bool isTransA = (*transa == 'T' || *transa == 't'); + bool isTransB = (*transb == 'T' || *transb == 't'); + + int Bm, sizeM, Bn, sizeN, Bk, sizeK; + + int i, j; + + if ((m <= 0) || (n <= 0)) + return; + + if ((k <= 0) || (alpha[0] == 0.)) { + + if (beta[0] == 0.) { + for (j = 0; j < n; j++) + for (i = 0; i < m; i++) + c[i + j * ldc] = 0.0; + } else if (beta[0] != 1.) { + for (j = 0; j < n; j++) + for (i = 0; i < m; i++) + c[i + j * ldc] *= beta[0]; + } + + return; + } + + assert(IMPLICATION(bias != nullptr, *beta == 0.0)); + + // XXX: this happens on every thread... + bool hasBias = (bias != nullptr); + auto ker_bn = get_xbyak_gemm(isTransA, isTransB, *beta, hasBias); + auto ker_b1 = get_xbyak_gemm(isTransA, isTransB, 1.0, false); + auto ker_b0 = get_xbyak_gemm(isTransA, isTransB, 0.0, false); + assert(ker_bn && ker_b1 && ker_b0); + + int BM = 4032, BN, BK; + if (mayiuse(avx512_core)) { + BN = isTransA ? 384 : 64; + BK = 384; + } else { + BN = isTransA ? 96 : 64; + BK = isTransB ? 96 : 192; + if (!isTransA && !isTransB) + BK = 128; + } + const float *curA, *curB, *curBias = nullptr; + float *curC; + + for (Bk = 0; Bk < k; Bk += sizeK) { + sizeK = k - Bk; + if (sizeK >= BK * 2) + sizeK = BK; + else { + if (sizeK > BK) + sizeK = (sizeK + 1) / 2; + } + + for (Bm = 0; Bm < m; Bm += sizeM) { + sizeM = m - Bm; + if (sizeM >= BM * 2) + sizeM = BM; + else { + if (sizeM > BM + BM / 2) + sizeM = (sizeM + 1) / 2; + } + + for (Bn = 0; Bn < n; Bn += sizeN) { + sizeN = n - Bn; + if (sizeN >= BN * 2) + sizeN = BN; + else { + if (sizeN > BN + BN / 2) + sizeN = (sizeN + 1) / 2; + } + + if (!isTransA) { + curA = a + Bm + Bk * lda; + } else { + curA = a + Bk + Bm * lda; + } + if (!isTransB) { + curB = b + Bk + Bn * ldb; + } else { + curB = b + Bn + Bk * ldb; + } + curC = c + Bm + (size_t)Bn * ldc; + if (bias != nullptr) { + if (Bk == 0) { + curBias = bias + Bm; + } else { + curBias = nullptr; + } + } + if (Bk == 0) { + if (*beta == 0.0 && bias == nullptr) + (*ker_b0)((dim_t)sizeM, (dim_t)sizeN, (dim_t)sizeK, + alpha, curA, lda, curB, ldb, beta, curC, ldc, + curBias, ws); + else + (*ker_bn)((dim_t)sizeM, (dim_t)sizeN, (dim_t)sizeK, + alpha, curA, lda, curB, ldb, beta, curC, ldc, + curBias, ws); + } else { + (*ker_b1)((dim_t)sizeM, (dim_t)sizeN, (dim_t)sizeK, + alpha, curA, lda, curB, ldb, beta, curC, ldc, + curBias, ws); + } + } + } + } +} + +} + +mkldnn_status_t jit_avx512_common_gemm_f32( + const char *transa, const char *transb, + const int *p_m, const int *p_n, const int *p_k, const float *p_alpha, + const float *A, const int *p_lda, const float *B, const int *p_ldb, + const float *p_beta, float *C, const int *p_ldc, const float *bias) +{ + using namespace mkldnn::impl::utils; + using namespace avx512_common_gemm_f32; + using namespace gemm_utils; + + if (*p_beta != 0 && bias) + return ref_gemm(transa, transb, p_m, p_n, p_k, + p_alpha, A, p_lda, B, p_lda, p_beta, C, p_ldc, bias); + + int nthr = (mkldnn_in_parallel()) ? 1 : mkldnn_get_max_threads(); + + int m = *p_m; + int n = *p_n; + int k = *p_k; + dim_t lda = *p_lda; + dim_t ldb = *p_ldb; + dim_t ldc = *p_ldc; + float beta = *p_beta; + int MB, NB, KB; + + int nthr_m, nthr_n, nthr_k, nthr_mn; + + // Determine threading partitioning + calc_nthr_nocopy_avx512_common( + m, n, k, nthr, &nthr_m, &nthr_n, &nthr_k, &MB, &NB, &KB); + assert(IMPLICATION(!mkldnn_thr_syncable(), nthr_k == 1)); + + // May not happen, but just in case + if (nthr < nthr_m * nthr_n * nthr_k) + nthr = nthr_m * nthr_n * nthr_k; + + nthr_mn = nthr_m * nthr_n; + + unsigned char * ompstatus_ = nullptr; + unsigned char volatile *ompstatus = nullptr; + + float *c_buffers = nullptr; + float *ws_buffers = nullptr; + + if (nthr_k > 1) { + ompstatus_ = (unsigned char *) malloc( + nthr * CACHE_LINE_SIZE, + CACHE_LINE_SIZE); + ompstatus = (unsigned char volatile *) ompstatus_; + assert(ompstatus); + + for (int i = 0; i < nthr; i++) + ompstatus[i * CACHE_LINE_SIZE] = 0; + + c_buffers = (float *)malloc(nthr_m * nthr_n * (nthr_k - 1) * MB * NB + * sizeof(float), PAGE_4K); + } + + const size_t ws_elems_per_thr = (size_t)k * 48 + 64; + const size_t ws_size_per_thr + = rnd_up(ws_elems_per_thr * sizeof(float), PAGE_4K); + if (k > STACK_K_CAPACITY) { + ws_buffers = (float *)malloc(nthr * ws_size_per_thr, PAGE_4K); + } + + parallel_nd(nthr, [&](const int ithr) { + int ithr_m, ithr_n, ithr_k, ithr_mn; + int m_from, m_to, myM; + int n_from, n_to, myN; + int k_from, k_to, myK; + int cbase, ibase; + const float *myA, *myB, *myBias = nullptr; + float *myC = C, myBeta; + float *ws = ws_buffers ? + ws_buffers + ithr * ws_size_per_thr / sizeof(float) : 0; + dim_t ld = ldc; + + int sum_later = (mkldnn_get_num_threads() < nthr_m * nthr_n * nthr_k); + + if (ithr < nthr_m * nthr_n * nthr_k) { + + ithr_mn = ithr % nthr_mn; + ithr_m = ithr_mn % nthr_m; + ithr_n = ithr_mn / nthr_m; + ithr_k = ithr / nthr_mn; + + /* swap ithr_k for performance improvement */ + if (ithr_k == 0) + ithr_k = nthr_k - 1; + else if (ithr_k == nthr_k - 1) + ithr_k = 0; + + m_from = MB * (ithr_m); + m_to = MB * (ithr_m + 1); + if (m_to > m) + m_to = m; + myM = m_to - m_from; + + n_from = NB * (ithr_n); + n_to = NB * (ithr_n + 1); + if (n_to > n) + n_to = n; + myN = n_to - n_from; + + k_from = KB * (ithr_k); + k_to = KB * (ithr_k + 1); + if (k_to > k) + k_to = k; + myK = k_to - k_from; + + cbase = (ithr_m + nthr_m * ithr_n) * (nthr_k - 1); + ibase = (ithr_m + nthr_m * ithr_n) * nthr_k; + + if ((myM > 0) && (myN > 0)) { + + if (*transa == 'N' || *transa == 'n') { + myA = &(A[m_from + k_from * lda]); + } else { + myA = &(A[k_from + m_from * lda]); + } + if (*transb == 'N' || *transb == 'n') { + myB = &(B[k_from + n_from * ldb]); + } else { + myB = &(B[n_from + k_from * ldb]); + } + if (ithr_k == 0) { + myC = &(C[m_from + n_from * ldc]); + myBeta = beta; + ld = ldc; + if (bias) + myBias = &(bias[m_from]); + } else { + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1); + myBeta = 0.0; + ld = MB; + myBias = nullptr; + } + + sgemm_nocopy_driver(transa, transb, myM, myN, myK, p_alpha, myA, + lda, myB, ldb, &myBeta, myC, ld, myBias, ws); + + if (nthr_k > 1 && !sum_later) + ompstatus[(ibase + ithr_k) * CACHE_LINE_SIZE] = 1; + } + + if (nthr_k > 1 && !sum_later) { + + // sum matrices partitioned along K dimension + int n1, n2; + + partition_unit_diff(ithr_k, nthr_k, myN, &n1, &n2); + + if (ithr_k > 0) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1) + + (dim_t)n1 * MB; + /* need to wait until main thread finishes */ + while (ompstatus[ibase * CACHE_LINE_SIZE] != 1) { + }; + + /* my cache is hot */ + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + + for (int ik = 1; ik < nthr_k; ++ik) { + if (ik != ithr_k) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ik - 1) + + (dim_t)n1 * MB; + + while (ompstatus[(ibase + ik) * CACHE_LINE_SIZE] != 1) { + }; + + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + } + } + } + }); + + + // handle C summation later + if (nthr_k > 1 && ompstatus[0] == 0) { + + parallel_nd(nthr, [&](const int ithr) { + int ithr_m, ithr_n, ithr_k, ithr_mn; + int m_from, m_to, myM; + int n_from, n_to, myN; + int cbase; + float *myC = C; + + if (ithr < nthr_m * nthr_n * nthr_k) { + + ithr_mn = ithr % nthr_mn; + ithr_m = ithr_mn % nthr_m; + ithr_n = ithr_mn / nthr_m; + ithr_k = ithr / nthr_mn; + + /* swap ithr_k for performance improvement */ + if (ithr_k == 0) + ithr_k = nthr_k - 1; + else if (ithr_k == nthr_k - 1) + ithr_k = 0; + + m_from = MB * (ithr_m); + m_to = MB * (ithr_m + 1); + if (m_to > m) + m_to = m; + myM = m_to - m_from; + + n_from = NB * (ithr_n); + n_to = NB * (ithr_n + 1); + if (n_to > n) + n_to = n; + myN = n_to - n_from; + + cbase = (ithr_m + nthr_m * ithr_n) * (nthr_k - 1); + + if (nthr_k > 1) { + // sum matrices partitioned along K dimension + int n1, n2; + + partition_unit_diff(ithr_k, nthr_k, myN, &n1, &n2); + + if (ithr_k > 0) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1) + + (dim_t)n1 * MB; + + /* my cache is hot */ + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + + for (int ik = 1; ik < nthr_k; ++ik) { + if (ik != ithr_k) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ik - 1) + + (dim_t)n1 * MB; + + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + } + } + } + }); + } + + free(c_buffers); + free(ompstatus_); + free(ws_buffers); + + return mkldnn_success; +} + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.hpp new file mode 100644 index 000000000000..d581b7fd7164 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx512_common_gemm_f32.hpp @@ -0,0 +1,36 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_COMMON_GEMM_F32_HPP +#define JIT_AVX512_COMMON_GEMM_F32_HPP + +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +mkldnn_status_t jit_avx512_common_gemm_f32( + const char *transa, const char *transb, const int *M, + const int *N, const int *K, const float *alpha, const float *A, + const int *lda, const float *B, const int *ldb, const float *beta, + float *C, const int *ldc, const float *bias = nullptr); + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.cpp new file mode 100644 index 000000000000..60d4220837af --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.cpp @@ -0,0 +1,2705 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "ref_gemm_f32.hpp" +#include "gemm_utils_f32.hpp" +#include "jit_avx_gemm_f32.hpp" + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +#define CACHE_LINE_SIZE 64 + +#define STACKSIZE get_size_of_abi_save_regs() +#if _WIN32 +#define STACK_K_CAPACITY 128 +#else +#define STACK_K_CAPACITY 8192 +#endif +#define SIZE 4 +#define OFFSET 32 +#define BASE_SHIFT 2 +#define SECOND_FETCH 14 + +namespace avx_gemm_f32 { +using namespace gemm_utils; + +struct xbyak_gemm : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx_gemm_f32_xbyak_gemm) + + xbyak_gemm(char isTransA, char isTransB, float beta, bool hasBias = false, + void *code_ptr = nullptr, + size_t code_size = 80 * Xbyak::DEFAULT_MAX_CODE_SIZE) + : jit_generator(code_ptr, code_size) + { + using namespace Xbyak; + + const bool is_avx2 = mayiuse(avx2); + assert(IMPLICATION(!is_avx2, mayiuse(avx))); + + const int UNROLL_M = is_avx2 ? 16 : 8; + const int UNROLL_N = 6; + + bool isBeta0 = (beta == 0.0); + bool isBetaN = (!isBeta0 && beta != 1.0); + + // various definitions for convenience + auto ARG_M = abi_param1; + auto ARG_N = abi_param2; + auto K = abi_param3; + auto ARG_ALPHA = abi_param4; +#ifdef _WIN32 + auto ARG_A = ptr[rsp + OFFSET_SHADOWSPACE + STACKSIZE]; + auto ARG_LDA = qword[rsp + OFFSET_SHADOWSPACE + + sizeof(float *) + STACKSIZE]; + const auto stackOffset = OFFSET_SHADOWSPACE + + sizeof(float *) + STACKSIZE; + auto A = rsi; + auto LDA = rdi; +#else + auto ARG_A = r8; + auto ARG_LDA = r9; + const auto stackOffset = STACKSIZE; + auto A = ARG_A; + auto LDA = ARG_LDA; +#endif + auto ARG_B = ptr[rsp + 8 + stackOffset]; + auto ARG_LDB = ptr[rsp + 16 + stackOffset]; + auto ARG_BETA = ptr[rsp + 24 + stackOffset]; + auto ARG_C = ptr[rsp + 32 + stackOffset]; + auto ARG_LDC = ptr[rsp + 40 + stackOffset]; + auto ARG_BIAS = ptr[rsp + 48 + stackOffset]; + auto ARG_WS = ptr[rsp + 56 + stackOffset]; + + auto B = r11; + auto LDB = rbx; + auto LDC = r13; + auto LL = rax; + auto AO1 = abi_param2; + auto BO1 = abi_param4; + auto BO2 = rbp; + auto CO1 = r14; + auto CO2 = r15; + auto LDB3 = r10; + auto LDA4 = abi_param1; + auto AA = r12; + auto BIAS1 = abi_param1; + + auto M = qword[rsp + 0]; + auto N = qword[rsp + 8]; + auto FLAG = qword[rsp + 16]; + auto I = qword[rsp + 24]; + auto C = qword[rsp + 32]; + auto BIAS = qword[rsp + 40]; + auto ALPHA = qword[rsp + 48]; + auto BETA = qword[rsp + 64]; + auto ORIG_A = qword[rsp + 80]; + auto MASK = dword[rsp + 88]; + auto STRIDE = qword[rsp + 120]; + auto ORIG_SP = qword[rsp + 152]; + + auto VALPHA = ymm1; + auto VBETA = ymm2; + auto VMASK = ymm3; + auto VBIAS1 = ymm2; + auto VBIAS2 = ymm4; + + auto PREFETCHSIZEA = 128; + auto PREFETCHSIZEB = (!isTransB) ? -16 : 0; + + // Function for packing if needed + auto do_pack = [&]( + int unroll_m, bool isLoad1Unmasked, bool isLoad2Unmasked) { + Label pack2, pack3, pack4, pack10; + + int regIdx; + Reg64 reg; + + mov(BO1, A); + lea(AO1, ptr[rsp + 256 + OFFSET * SIZE]); + + if (isTransA) { + lea(BO2, ptr[BO1 + LDA * 4]); + lea(CO1, ptr[LDA + LDA * 2]); + vmovupd(ymm7, STRIDE); + } + + mov(LL, K); + sar(LL, 2); + jle(pack3, T_NEAR); + align(16); + + L(pack2); + if (!isTransA) { + for (int i = 0; i < 4; i++) { + regIdx = (i % 2 == 0) ? 4 : 6; + if (isLoad1Unmasked) { + vmovups(Ymm(regIdx), + ptr[BO1 + (0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(Ymm(regIdx), VMASK, + ptr[BO1 + (0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m > 8) { + if (isLoad2Unmasked) { + vmovups(Ymm(regIdx + 1), + ptr[BO1 + (1 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(Ymm(regIdx + 1), VMASK, + ptr[BO1 + (1 * 8 - OFFSET) * SIZE]); + } + } + add(BO1, LDA); + + vmovups(ptr[AO1 + (unroll_m * i + 0 * 8 - OFFSET) * SIZE], + Ymm(regIdx)); + if (unroll_m > 8) { + vmovups(ptr[AO1 + + (unroll_m * i + 1 * 8 - OFFSET) + * SIZE], + Ymm(regIdx + 1)); + } + } + + } else { + if (isLoad1Unmasked) { + for (int i = 0; i < 2; i++) { + reg = (i % 2 == 0) ? BO1 : BO2; + vmovups(xmm0, ptr[reg + (0 * 8 - OFFSET) * SIZE]); + vmovups(xmm1, + ptr[reg + LDA * 1 + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[reg + LDA * 2]); + vunpcklps(xmm4, xmm0, xmm1); + vunpckhps(xmm5, xmm0, xmm1); + vmovups(xmm0, ptr[BO2 + (0 * 8 - OFFSET) * SIZE]); + vmovups(xmm1, + ptr[BO2 + LDA * 1 + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 2]); + vunpcklps(xmm6, xmm0, xmm1); + vunpckhps(xmm2, xmm0, xmm1); + + vunpcklpd(xmm0, xmm4, xmm6); + vunpckhpd(xmm1, xmm4, xmm6); + vmovups(ptr[AO1 + + (unroll_m * 0 + i * 4 - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * 1 + i * 4 - OFFSET) + * SIZE], + xmm1); + vunpcklpd(xmm0, xmm5, xmm2); + vunpckhpd(xmm1, xmm5, xmm2); + vmovups(ptr[AO1 + + (unroll_m * 2 + i * 4 - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * 3 + i * 4 - OFFSET) + * SIZE], + xmm1); + } + } else if (is_avx2) { + for (int i = 0; i < 2; i++) { + vmovaps(xmm4, xmm3); + vgatherqps(xmm0, + ptr[BO1 + ymm7 + ((2 * i) - OFFSET) * SIZE], + xmm4); + vmovaps(xmm4, xmm3); + vgatherqps(xmm1, + ptr[BO1 + ymm7 + ((2 * i + 1) - OFFSET) * SIZE], + xmm4); + + vmovups(ptr[AO1 + + (unroll_m * (2 * i) + 0 * 4 - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * (2 * i + 1) + 0 * 4 + - OFFSET) + * SIZE], + xmm1); + } + + lea(BO2, ptr[BO1 + LDA * 4]); + + for (int i = 0; i < 2; i++) { + vextractf128(xmm4, ymm3, 1); + vgatherqps(xmm0, + ptr[BO2 + ymm7 + ((2 * i) - OFFSET) * SIZE], + xmm4); + vextractf128(xmm4, ymm3, 1); + vgatherqps(xmm1, + ptr[BO2 + ymm7 + ((2 * i + 1) - OFFSET) * SIZE], + xmm4); + + vmovups(ptr[AO1 + + (unroll_m * (2 * i) + 1 * 4 - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * (2 * i + 1) + 1 * 4 + - OFFSET) + * SIZE], + xmm1); + } + + lea(BO2, ptr[BO2 + LDA * 4]); + } else { + vxorps(xmm4, xmm4, xmm4); + lea(BO2, ptr[BO1 + LDA * 4]); + + auto el_cp = [&](int section, int ld_step) { + RegExp src_addr = section == 0 ? BO1 : BO2; + if (ld_step == 1 || ld_step == 2) + src_addr = src_addr + LDA * ld_step; + else if (ld_step == 3) + src_addr = src_addr + CO1; + src_addr = src_addr - OFFSET * SIZE; + + vmovups(Xmm(ld_step % 2), ptr[src_addr]); + RegExp dst_addr = AO1 + + (ld_step + section * 4 - OFFSET) * SIZE; + for (int off = 0; off < 4; ++off) + pextrd(ptr[dst_addr + unroll_m * off * SIZE], + Xmm(ld_step % 2), off); + }; + + Label l_end; + el_cp(0, 0); cmp(M, 4 * 0 + 0 + 1); je(l_end, T_NEAR); + el_cp(0, 1); cmp(M, 4 * 0 + 1 + 1); je(l_end, T_NEAR); + el_cp(0, 2); cmp(M, 4 * 0 + 2 + 1); je(l_end, T_NEAR); + el_cp(0, 3); cmp(M, 4 * 0 + 3 + 1); je(l_end, T_NEAR); + el_cp(1, 0); cmp(M, 4 * 1 + 0 + 1); je(l_end, T_NEAR); + el_cp(1, 1); cmp(M, 4 * 1 + 1 + 1); je(l_end, T_NEAR); + el_cp(1, 2); + L(l_end); + + lea(BO2, ptr[BO2 + LDA * 4]); + } + + if (unroll_m >= 16) { + assert(is_avx2); + if (isLoad2Unmasked) { + for (int i = 0; i < 2; i++) { + vmovups(xmm0, ptr[BO2 + (0 * 8 - OFFSET) * SIZE]); + vmovups(xmm1, ptr[BO2 + LDA * 1 + + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 2]); + vunpcklps(xmm4, xmm0, xmm1); + vunpckhps(xmm5, xmm0, xmm1); + vmovups(xmm0, ptr[BO2 + (0 * 8 - OFFSET) * SIZE]); + vmovups(xmm1, ptr[BO2 + LDA * 1 + + (0 * 8 - OFFSET) * SIZE]); + if (i == 0) + lea(BO2, ptr[BO2 + LDA * 2]); + vunpcklps(xmm6, xmm0, xmm1); + vunpckhps(xmm2, xmm0, xmm1); + + vunpcklpd(xmm0, xmm4, xmm6); + vunpckhpd(xmm1, xmm4, xmm6); + vmovups(ptr[AO1 + + (unroll_m * 0 + (i + 2) * 4 + - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * 1 + (i + 2) * 4 + - OFFSET) + * SIZE], + xmm1); + vunpcklpd(xmm0, xmm5, xmm2); + vunpckhpd(xmm1, xmm5, xmm2); + vmovups(ptr[AO1 + + (unroll_m * 2 + (i + 2) * 4 + - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * 3 + (i + 2) * 4 + - OFFSET) + * SIZE], + xmm1); + } + } else { + for (int i = 0; i < 2; i++) { + vmovaps(xmm4, xmm3); + vgatherqps(xmm0, + ptr[BO2 + ymm7 + ((2 * i) - OFFSET) * SIZE], + xmm4); + vmovaps(xmm4, xmm3); + vgatherqps(xmm1, + ptr[BO2 + ymm7 + + ((2 * i + 1) - OFFSET) * SIZE], + xmm4); + + vmovups(ptr[AO1 + + (unroll_m * (2 * i) + 2 * 4 + - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * (2 * i + 1) + 2 * 4 + - OFFSET) + * SIZE], + xmm1); + } + + lea(BO2, ptr[BO2 + LDA * 4]); + + for (int i = 0; i < 2; i++) { + vextractf128(xmm4, ymm3, 1); + vgatherqps(xmm0, + ptr[BO2 + ymm7 + ((2 * i) - OFFSET) * SIZE], + xmm4); + vextractf128(xmm4, ymm3, 1); + vgatherqps(xmm1, + ptr[BO2 + ymm7 + + ((2 * i + 1) - OFFSET) * SIZE], + xmm4); + + vmovups(ptr[AO1 + + (unroll_m * (2 * i) + 3 * 4 + - OFFSET) + * SIZE], + xmm0); + vmovups(ptr[AO1 + + (unroll_m * (2 * i + 1) + 3 * 4 + - OFFSET) + * SIZE], + xmm1); + } + + lea(BO2, ptr[BO2 + LDA * 4]); + } + } + add(BO1, (4 * SIZE)); + } + + add(AO1, unroll_m * 4 * SIZE); + sub(LL, 1); + jg(pack2, T_NEAR); + align(16); + + L(pack3); + mov(LL, K); + and_(LL, 3); + jle(pack10, T_NEAR); + align(16); + + L(pack4); + if (!isTransA) { + if (isLoad1Unmasked) { + vmovups(ymm4, ptr[BO1 + (0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm4, VMASK, ptr[BO1 + (0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m > 8) { + if (isLoad2Unmasked) { + vmovups(ymm5, ptr[BO1 + (1 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm5, VMASK, + ptr[BO1 + (1 + 8 - OFFSET) * SIZE]); + } + } + add(BO1, LDA); + vmovups(ptr[AO1 + (unroll_m * 0 + 0 * 8 - OFFSET) * SIZE], + ymm4); + if (unroll_m > 8) { + vmovups(ptr[AO1 + (unroll_m * 0 + 1 * 8 - OFFSET) * SIZE], + ymm5); + } + } else { + if (isLoad1Unmasked) { + for (int i = 0; i < 2; i++) { + reg = (i % 2 == 0) ? BO1 : BO2; + vmovss(Xmm(i + 1), ptr[reg + (0 * 8 - OFFSET) * SIZE]); + vmovss(xmm0, + ptr[reg + LDA * 1 + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[reg + LDA * 2]); + vunpcklps(Xmm(i + 1), Xmm(i + 1), Xmm(0)); + } + vunpcklpd(xmm1, xmm1, xmm2); + vmovups(ptr[AO1 + (unroll_m * 0 + 0 * 4 - OFFSET) * SIZE], + xmm1); + + for (int i = 0; i < 2; i++) { + vmovss(Xmm(i + 1), ptr[BO2 + (0 * 8 - OFFSET) * SIZE]); + vmovss(xmm0, + ptr[BO2 + LDA * 1 + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 2]); + vunpcklps(Xmm(i + 1), Xmm(i + 1), Xmm(0)); + } + vunpcklpd(xmm1, xmm1, xmm2); + vmovups(ptr[AO1 + (unroll_m * 0 + 1 * 4 - OFFSET) * SIZE], + xmm1); + } else if (is_avx2) { + vmovaps(xmm4, xmm3); + vgatherqps(xmm1, ptr[BO1 + ymm7 + (0 * 8 - OFFSET) * SIZE], + xmm4); + lea(BO2, ptr[BO1 + LDA * 4]); + vmovups(ptr[AO1 + (unroll_m * 0 + 0 * 4 - OFFSET) * SIZE], + xmm1); + + vextractf128(xmm4, ymm3, 1); + vgatherqps(xmm1, ptr[BO2 + ymm7 + (0 * 8 - OFFSET) * SIZE], + xmm4); + lea(BO2, ptr[BO2 + LDA * 4]); + vmovups(ptr[AO1 + (unroll_m * 0 + 1 * 4 - OFFSET) * SIZE], + xmm1); + } else { + vxorps(xmm4, xmm4, xmm4); + lea(BO2, ptr[BO1 + LDA * 4]); + + auto el_cp = [&](int section, int ld_step) { + RegExp src_addr = section == 0 ? BO1 : BO2; + if (ld_step == 1 || ld_step == 2) + src_addr = src_addr + LDA * ld_step; + else if (ld_step == 3) + src_addr = src_addr + CO1; + src_addr = src_addr - OFFSET * SIZE; + + vmovss(xmm1, ptr[src_addr]); + RegExp dst_addr = AO1 + + (ld_step + section * 4 - OFFSET) * SIZE; + movss(ptr[dst_addr], xmm1); + }; + + Label l_end; + el_cp(0, 0); cmp(M, 4 * 0 + 0 + 1); je(l_end, T_NEAR); + el_cp(0, 1); cmp(M, 4 * 0 + 1 + 1); je(l_end, T_NEAR); + el_cp(0, 2); cmp(M, 4 * 0 + 2 + 1); je(l_end, T_NEAR); + el_cp(0, 3); cmp(M, 4 * 0 + 3 + 1); je(l_end, T_NEAR); + el_cp(1, 0); cmp(M, 4 * 1 + 0 + 1); je(l_end, T_NEAR); + el_cp(1, 1); cmp(M, 4 * 1 + 1 + 1); je(l_end, T_NEAR); + el_cp(1, 2); + L(l_end); + + lea(BO2, ptr[BO2 + LDA * 4]); + } + + if (unroll_m >= 16) { + assert(is_avx2); + if (isLoad2Unmasked) { + for (int i = 0; i < 2; i++) { + vmovss(Xmm(i + 1), + ptr[BO2 + (0 * 8 - OFFSET) * SIZE]); + vmovss(xmm0, ptr[BO2 + LDA * 1 + + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 2]); + vunpcklps(Xmm(i + 1), Xmm(i + 1), Xmm(0)); + } + vunpcklpd(xmm1, xmm1, xmm2); + } else { + vmovaps(xmm4, xmm3); + vgatherqps(xmm1, + ptr[BO2 + ymm7 + (0 * 8 - OFFSET) * SIZE], + xmm4); + lea(BO2, ptr[BO2 + LDA * 4]); + } + vmovups(ptr[AO1 + (unroll_m * 0 + 2 * 4 - OFFSET) * SIZE], + xmm1); + + if (isLoad2Unmasked) { + for (int i = 0; i < 2; i++) { + vmovss(Xmm(i + 1), + ptr[BO2 + (0 * 8 - OFFSET) * SIZE]); + vmovss(xmm0, ptr[BO2 + LDA * 1 + + (0 * 8 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDA * 2]); + vunpcklps(Xmm(i + 1), Xmm(i + 1), Xmm(0)); + } + vunpcklpd(xmm1, xmm1, xmm2); + } else { + vextractf128(xmm4, ymm3, 1); + vgatherqps(xmm1, + ptr[BO2 + ymm7 + (0 * 8 - OFFSET) * SIZE], + xmm4); + } + vmovups(ptr[AO1 + (unroll_m * 0 + 3 * 4 - OFFSET) * SIZE], + xmm1); + } + add(BO1, SIZE); + } + + add(AO1, unroll_m * SIZE); + sub(LL, 1); + jg(pack4, T_NEAR); + align(16); + + L(pack10); + }; + + // Fused multiply add; may become one or two instructions + auto fma = [&](bool useFma, Ymm reg0, Ymm reg1, Ymm reg2, + bool overWrite = false) { + if (useFma) { + if (is_avx2) { + vfmadd231ps(reg2, reg1, reg0); + } else { + assert(UNROLL_M == 8); + auto tent_vreg = overWrite ? reg1 : ymm1; + vmulps(tent_vreg, reg1, reg0); + vaddps(reg2, reg2, tent_vreg); + } + } else { + if (!overWrite) { + vmulps(ymm15, reg1, reg0); + vaddps(reg2, reg2, ymm15); + } else { + vmulps(reg1, reg1, reg0); + vaddps(reg2, reg2, reg1); + } + } + }; + + // Inner kernel with k=8 + auto innerkernel8 = [&](int unroll_m, int unroll_n, + bool isLoad1Unmasked, bool isLoad2Unmasked, bool isDirect, + bool isCopy, bool useFma, Ymm reg00, Ymm reg01, Ymm reg02, + Ymm reg03, Ymm reg04, Ymm reg05, Ymm reg06, Ymm reg07, + Ymm reg08, Ymm reg09, Ymm reg10, Ymm reg11, Ymm reg12, + Ymm reg13, Ymm reg14, Ymm reg15, Ymm reg16, Ymm reg17, + Ymm reg18, Ymm reg19, Ymm reg20, Ymm reg21, Ymm reg22, + Ymm reg23) { + + Ymm fmareg; + + if (!isDirect) { + prefetcht0(ptr[AO1 + (PREFETCHSIZEA + 0) * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + + for (int i = 0; i < 8; i++) { + if (isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, + ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } + } + add(AO1, LDA); + } + + if (!isTransB) { + vbroadcastss(ymm2, ptr[BO1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (0 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg00 : reg12; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg06 : reg18; + fma(useFma, ymm1, ymm2, fmareg); + } + if (i == 0) { + if (!isTransB) { + prefetcht0(ptr[BO1 + PREFETCHSIZEB * SIZE]); + } + } + if (unroll_n >= 2) { + if (!isTransB) { + if (i == 1) { + prefetcht0(ptr[BO1 + LDB + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO1 + LDB * 1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (1 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg01 : reg13; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg07 : reg19; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (isCopy) { + vmovups(ptr[LDA4 + (unroll_m * i + 0 * 8 - OFFSET) * SIZE], + ymm0); + if (unroll_m >= 16) { + vmovups(ptr[LDA4 + + (unroll_m * i + 1 * 8 - OFFSET) + * SIZE], + ymm1); + } + if (i == 7) { + sub(LDA4, -unroll_m * 8 * SIZE); + } + } + + if (unroll_n >= 3) { + if (!isTransB) { + if (i == 2) { + prefetcht0( + ptr[BO1 + LDB * 2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO1 + LDB * 2 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (2 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg02 : reg14; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg08 : reg20; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (i == 7) { + if (!isTransB) { + sub(BO1, -8 * SIZE); + } + } + + if (unroll_n >= 4) { + if (!isTransB) { + if (i == 3) { + prefetcht0(ptr[BO2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss(ymm2, ptr[BO2 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (3 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg03 : reg15; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg09 : reg21; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 5) { + if (!isTransB) { + if (i == 4) { + prefetcht0(ptr[BO2 + LDB + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO2 + LDB * 1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (4 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg04 : reg16; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg10 : reg22; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 6) { + if (!isTransB) { + if (i == 5) { + prefetcht0( + ptr[BO2 + LDB * 2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO2 + LDB * 2 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (5 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg05 : reg17; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg11 : reg23; + fma(useFma, ymm1, ymm2, fmareg); + } + } + if (isTransB) { + prefetcht0(ptr[BO1 + BO2]); + add(BO1, LDB); + } + + if (i == 0) { + if (unroll_m >= 4) { + if (!isDirect) { + prefetcht0( + ptr[AO1 + (PREFETCHSIZEA + 2 * 8) * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + } + } + if (i == 1 || i == 2) { + if (unroll_m >= 8) { + if (!isDirect) { + prefetcht0(ptr[AO1 + + (PREFETCHSIZEA + (2 + 2 * i) * 8) + * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + } + } + if (i == 3 || i == 4 || i == 5 || i == 6) { + if (unroll_m >= 16) { + if (!isDirect) { + prefetcht0(ptr[AO1 + + (PREFETCHSIZEA + (2 + 2 * i) * 8) + * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + } + } + if (i == 7) { + if (!isTransB) { + if (unroll_n >= 4) { + sub(BO2, -8 * SIZE); + } + } + if (!isTransA) { + prefetcht2(ptr[AA]); + lea(AA, ptr[AA + LDA]); + } + } + + if (!isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, + ptr[AO1 + + (unroll_m * (i + 1) + 0 * 8 - OFFSET) + * SIZE]); + } else { + vmaskmovps( + ymm0, VMASK, + ptr[AO1 + + (unroll_m * (i + 1) + 0 * 8 - OFFSET) + * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + + (unroll_m * (i + 1) + 1 * 8 + - OFFSET) + * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + + (unroll_m * (i + 1) + 1 * 8 + - OFFSET) + * SIZE]); + } + } + } + } + + if (!isDirect) { + sub(AO1, -unroll_m * 8 * SIZE); + } + sub(LL, 1); + + }; + + // Inner kernel with k=4 + auto innerkernel4 = [&](int unroll_m, int unroll_n, + bool isLoad1Unmasked, bool isLoad2Unmasked, bool isDirect, + bool isCopy, bool useFma, Ymm reg00, Ymm reg01, Ymm reg02, + Ymm reg03, Ymm reg04, Ymm reg05, Ymm reg06, Ymm reg07, + Ymm reg08, Ymm reg09, Ymm reg10, Ymm reg11, Ymm reg12, + Ymm reg13, Ymm reg14, Ymm reg15, Ymm reg16, Ymm reg17, + Ymm reg18, Ymm reg19, Ymm reg20, Ymm reg21, Ymm reg22, + Ymm reg23) { + + Ymm fmareg; + + if (!isDirect) { + prefetcht0(ptr[AO1 + (PREFETCHSIZEA + 0) * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + + for (int i = 0; i < 4; i++) { + if (isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, + ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } + } + add(AO1, LDA); + } + + if (!isTransB) { + vbroadcastss(ymm2, ptr[BO1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (0 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg00 : reg12; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg06 : reg18; + fma(useFma, ymm1, ymm2, fmareg); + } + if (i == 0) { + if (!isTransB) { + prefetcht0(ptr[BO1 + PREFETCHSIZEB * SIZE]); + } + } + if (unroll_n >= 2) { + if (!isTransB) { + if (i == 1) { + prefetcht0(ptr[BO1 + LDB + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO1 + LDB * 1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (1 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg01 : reg13; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg07 : reg19; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (isCopy) { + vmovups(ptr[LDA4 + (unroll_m * i + 0 * 8 - OFFSET) * SIZE], + ymm0); + if (unroll_m >= 16) { + vmovups(ptr[LDA4 + + (unroll_m * i + 1 * 8 - OFFSET) + * SIZE], + ymm1); + } + if (i == 3) { + sub(LDA4, -unroll_m * 4 * SIZE); + } + } + + if (unroll_n >= 3) { + if (!isTransB) { + if (i == 2) { + prefetcht0( + ptr[BO1 + LDB * 2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO1 + LDB * 2 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (2 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg02 : reg14; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg08 : reg20; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (i == 7) { + if (!isTransB) { + sub(BO1, -8 * SIZE); + } + } + + if (unroll_n >= 4) { + if (!isTransB) { + if (i == 3) { + prefetcht0(ptr[BO2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss(ymm2, ptr[BO2 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (3 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg03 : reg15; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg09 : reg21; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 5) { + if (!isTransB) { + if (i == 4) { + prefetcht0(ptr[BO2 + LDB + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO2 + LDB * 1 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (4 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg04 : reg16; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg10 : reg22; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 6) { + if (!isTransB) { + if (i == 5) { + prefetcht0( + ptr[BO2 + LDB * 2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO2 + LDB * 2 + (i - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (5 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg05 : reg17; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg11 : reg23; + fma(useFma, ymm1, ymm2, fmareg); + } + } + if (isTransB) { + prefetcht0(ptr[BO1 + BO2]); + add(BO1, LDB); + } + + if (i == 0) { + if (unroll_m >= 4) { + if (!isDirect) { + prefetcht0( + ptr[AO1 + (PREFETCHSIZEA + 2 * 8) * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + } + } + if (i == 1 || i == 2) { + if (unroll_m >= 8) { + if (!isDirect) { + prefetcht0(ptr[AO1 + + (PREFETCHSIZEA + (2 + 2 * i) * 8) + * SIZE]); + } else { + prefetcht0(ptr[AO1 + LDA4]); + } + } + } + if (i == 3) { + if (!isTransB) { + sub(BO1, -4 * SIZE); + if (unroll_n >= 4) { + sub(BO2, -4 * SIZE); + } + } + } + + if (!isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, + ptr[AO1 + + (unroll_m * (i + 1) + 0 * 8 - OFFSET) + * SIZE]); + } else { + vmaskmovps( + ymm0, VMASK, + ptr[AO1 + + (unroll_m * (i + 1) + 0 * 8 - OFFSET) + * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + + (unroll_m * (i + 1) + 1 * 8 + - OFFSET) + * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + + (unroll_m * (i + 1) + 1 * 8 + - OFFSET) + * SIZE]); + } + } + } + } + + if (!isDirect) { + sub(AO1, -unroll_m * 4 * SIZE); + } + + }; + + // Inner kernel with k=2 + auto innerkernel2 = [&](int unroll_m, int unroll_n, + bool isLoad1Unmasked, bool isLoad2Unmasked, bool isDirect, + bool isCopy, bool useFma, Ymm reg00, Ymm reg01, Ymm reg02, + Ymm reg03, Ymm reg04, Ymm reg05, Ymm reg06, Ymm reg07, + Ymm reg08, Ymm reg09, Ymm reg10, Ymm reg11, Ymm reg12, + Ymm reg13, Ymm reg14, Ymm reg15, Ymm reg16, Ymm reg17, + Ymm reg18, Ymm reg19, Ymm reg20, Ymm reg21, Ymm reg22, + Ymm reg23) { + + Ymm fmareg; + + for (int i = 0; i < 2; i++) { + if (isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, + ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } + } + add(AO1, LDA); + } + + if (!isTransB) { + vbroadcastss(ymm2, ptr[BO1 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (0 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg00 : reg12; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg06 : reg18; + fma(useFma, ymm1, ymm2, fmareg); + } + if (unroll_n >= 2) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO1 + LDB * 1 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (1 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg01 : reg13; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg07 : reg19; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 3) { + if (!isTransB) { + if (i == 2) { + prefetcht0( + ptr[BO1 + LDB * 2 + PREFETCHSIZEB * SIZE]); + } + vbroadcastss( + ymm2, ptr[BO1 + LDB * 2 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (2 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg02 : reg14; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg08 : reg20; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 4) { + if (!isTransB) { + vbroadcastss(ymm2, ptr[BO2 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (3 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg03 : reg15; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg09 : reg21; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 5) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO2 + LDB * 1 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (4 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg04 : reg16; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg10 : reg22; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (unroll_n >= 6) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO2 + LDB * 2 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (5 - OFFSET) * SIZE]); + } + fmareg = (i % 2 == 0) ? reg05 : reg17; + fma(useFma, ymm0, ymm2, fmareg); + if (unroll_m >= 16) { + fmareg = (i % 2 == 0) ? reg11 : reg23; + fma(useFma, ymm1, ymm2, fmareg); + } + } + + if (isCopy) { + vmovups(ptr[LDA4 + (unroll_m * 0 + 0 * 8 - OFFSET) * SIZE], + ymm0); + if (unroll_m >= 16) { + vmovups(ptr[LDA4 + + (unroll_m * 0 + 1 * 8 - OFFSET) + * SIZE], + ymm1); + } + sub(LDA4, -unroll_m * SIZE); + } + + if (!isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, ptr[AO1 + + (unroll_m * 1 + 0 * 8 - OFFSET) + * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, + ptr[AO1 + + (unroll_m * 1 + 0 * 8 - OFFSET) + * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, + ptr[AO1 + + (unroll_m * 1 + 1 * 8 - OFFSET) + * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + + (unroll_m * 1 + 1 * 8 - OFFSET) + * SIZE]); + } + } + sub(AO1, -unroll_m * SIZE); + } + + if (!isTransB) { + sub(BO1, -SIZE); + if (unroll_n >= 4) { + sub(BO2, -SIZE); + } + } else { + add(BO1, LDB); + } + } + + }; + + // Inner kernel with k=1 + auto innerkernel1 = [&](int unroll_m, int unroll_n, + bool isLoad1Unmasked, bool isLoad2Unmasked, bool isDirect, + bool isCopy, bool useFma, Ymm reg00, Ymm reg01, Ymm reg02, + Ymm reg03, Ymm reg04, Ymm reg05, Ymm reg06, Ymm reg07, + Ymm reg08, Ymm reg09, Ymm reg10, Ymm reg11) { + + if (isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, ptr[AO1 + (0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + (1 * 8 - OFFSET) * SIZE]); + } + } + add(AO1, LDA); + } + + if (!isTransB) { + vbroadcastss(ymm2, ptr[BO1 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (0 - OFFSET) * SIZE]); + } + fma(useFma, ymm0, ymm2, reg00); + if (unroll_m >= 16) { + fma(useFma, ymm1, ymm2, reg06); + } + + if (unroll_n >= 2) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO1 + LDB * 1 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (1 - OFFSET) * SIZE]); + } + fma(useFma, ymm0, ymm2, reg01); + if (unroll_m >= 16) { + fma(useFma, ymm1, ymm2, reg07); + } + } + + if (unroll_n >= 3) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO1 + LDB * 2 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (2 - OFFSET) * SIZE]); + } + fma(useFma, ymm0, ymm2, reg02); + if (unroll_m >= 16) { + fma(useFma, ymm1, ymm2, reg08); + } + } + + if (unroll_n >= 4) { + if (!isTransB) { + vbroadcastss(ymm2, ptr[BO2 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (3 - OFFSET) * SIZE]); + } + fma(useFma, ymm0, ymm2, reg03); + if (unroll_m >= 16) { + fma(useFma, ymm1, ymm2, reg09); + } + } + + if (unroll_n >= 5) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO2 + LDB * 1 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (4 - OFFSET) * SIZE]); + } + fma(useFma, ymm0, ymm2, reg04); + if (unroll_m >= 16) { + fma(useFma, ymm1, ymm2, reg10); + } + } + + if (unroll_n >= 6) { + if (!isTransB) { + vbroadcastss( + ymm2, ptr[BO2 + LDB * 2 + (0 - OFFSET) * SIZE]); + } else { + vbroadcastss(ymm2, ptr[BO1 + (5 - OFFSET) * SIZE]); + } + fma(useFma, ymm0, ymm2, reg05); + if (unroll_m >= 16) { + fma(useFma, ymm1, ymm2, reg11); + } + } + + if (isCopy) { + vmovups(ptr[LDA4 + (unroll_m * 0 + 0 * 8 - OFFSET) * SIZE], + ymm0); + if (unroll_m >= 16) { + vmovups(ptr[LDA4 + (unroll_m * 0 + 1 * 8 - OFFSET) * SIZE], + ymm1); + } + sub(LDA4, -unroll_m * SIZE); + } + + if (!isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, + ptr[AO1 + (unroll_m * 1 + 0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, + ptr[AO1 + (unroll_m * 1 + 0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + + (unroll_m * 1 + 1 * 8 - OFFSET) + * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + + (unroll_m * 1 + 1 * 8 - OFFSET) + * SIZE]); + } + } + sub(AO1, -unroll_m * SIZE); + } + + if (!isTransB) { + sub(BO1, -SIZE); + if (unroll_n >= 4) { + sub(BO2, -SIZE); + } + } else { + add(BO1, LDB); + } + + }; + + // Main kernel; does prefetching and calls innerkernel{1,2,4,8} as + // appropriate + // After calculating results in registers, writes back to C matrix + auto kernel = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy, bool useFma, + Ymm reg00 = Ymm(4), Ymm reg01 = Ymm(5), Ymm reg02 = Ymm(6), + Ymm reg03 = Ymm(7), Ymm reg04 = Ymm(8), Ymm reg05 = Ymm(9), + Ymm reg06 = Ymm(10), Ymm reg07 = Ymm(11), Ymm reg08 = Ymm(12), + Ymm reg09 = Ymm(13), Ymm reg10 = Ymm(14), Ymm reg11 = Ymm(15), + Ymm reg12 = Ymm(4), Ymm reg13 = Ymm(5), Ymm reg14 = Ymm(6), + Ymm reg15 = Ymm(7), Ymm reg16 = Ymm(8), Ymm reg17 = Ymm(9), + Ymm reg18 = Ymm(10), Ymm reg19 = Ymm(11), Ymm reg20 = Ymm(12), + Ymm reg21 = Ymm(13), Ymm reg22 = Ymm(14), Ymm reg23 = Ymm(15)) { + if (!isDirect) { + lea(AO1, ptr[rsp + 256 + OFFSET * SIZE]); + } else { + mov(AO1, A); + } + + if (isCopy) { + lea(LDA4, ptr[rsp + 256 + OFFSET * SIZE]); + } else { + lea(LDA4, ptr[LDA * 8 + (8 - 1 - OFFSET) * SIZE]); + } + + if (isTransB) { + lea(BO2, ptr[LDB * 4 + (8 - 1 - OFFSET) * SIZE]); + lea(BO2, ptr[BO2 + LDB * 2]); + } + + if (!isDirect) { + if (isLoad1Unmasked) { + vmovups(ymm0, + ptr[AO1 + (unroll_m * 0 + 0 * 8 - OFFSET) * SIZE]); + } else { + vmaskmovps(ymm0, VMASK, + ptr[AO1 + (unroll_m * 0 + 0 * 8 - OFFSET) * SIZE]); + } + if (unroll_m >= 16) { + if (isLoad2Unmasked) { + vmovups(ymm1, ptr[AO1 + + (unroll_m * 0 + 1 * 8 - OFFSET) + * SIZE]); + } else { + vmaskmovps(ymm1, VMASK, + ptr[AO1 + + (unroll_m * 0 + 1 * 8 - OFFSET) + * SIZE]); + } + } + } + + for (int i = 4; i < 10; i++) { + vxorps(Ymm(i), Ymm(i), Ymm(i)); + vxorps(Ymm(i + 6), Ymm(i + 6), Ymm(i + 6)); + } + + mov(LL, K); + sar(LL, 3); + + Label kernel12, kernel13, kernel14, kernel15; + Label kernel16, kernel17, kernel18; + + sub(LL, SECOND_FETCH); + jle(kernel13, T_NEAR); + align(16); + + L(kernel12); + innerkernel8(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, reg00, reg01, reg02, reg03, reg04, + reg05, reg06, reg07, reg08, reg09, reg10, reg11, reg12, + reg13, reg14, reg15, reg16, reg17, reg18, reg19, reg20, + reg21, reg22, reg23); + jg(kernel12, T_NEAR); + align(16); + + L(kernel13); + prefetcht0(ptr[CO1 + (unroll_m - 1) * SIZE]); + if (unroll_n >= 2) + prefetcht0(ptr[CO1 + LDC + (unroll_m - 1) * SIZE]); + if (unroll_n >= 3) + prefetcht0(ptr[CO1 + LDC * 2 + (unroll_m - 1) * SIZE]); + if (unroll_n >= 4) + prefetcht0(ptr[CO2 + (unroll_m - 1) * SIZE]); + if (unroll_n >= 5) + prefetcht0(ptr[CO2 + LDC + (unroll_m - 1) * SIZE]); + if (unroll_n >= 6) + prefetcht0(ptr[CO2 + LDC * 2 + (unroll_m - 1) * SIZE]); + + add(LL, SECOND_FETCH); + jle(kernel15, T_NEAR); + align(16); + + L(kernel14); + innerkernel8(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, reg00, reg01, reg02, reg03, reg04, + reg05, reg06, reg07, reg08, reg09, reg10, reg11, reg12, + reg13, reg14, reg15, reg16, reg17, reg18, reg19, reg20, + reg21, reg22, reg23); + jg(kernel14, T_NEAR); + align(16); + + L(kernel15); + test(K, 4); + jle(kernel16, T_NEAR); + innerkernel4(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, reg00, reg01, reg02, reg03, reg04, + reg05, reg06, reg07, reg08, reg09, reg10, reg11, reg12, + reg13, reg14, reg15, reg16, reg17, reg18, reg19, reg20, + reg21, reg22, reg23); + + L(kernel16); + test(K, 2); + jle(kernel17, T_NEAR); + innerkernel2(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, reg00, reg01, reg02, reg03, reg04, + reg05, reg06, reg07, reg08, reg09, reg10, reg11, reg12, + reg13, reg14, reg15, reg16, reg17, reg18, reg19, reg20, + reg21, reg22, reg23); + align(16); + + L(kernel17); + if (unroll_m == 16) { + if (unroll_n <= 3) { + vaddps(reg00, reg00, reg12); + vaddps(reg01, reg01, reg13); + vaddps(reg02, reg02, reg14); + vaddps(reg06, reg06, reg18); + vaddps(reg07, reg07, reg19); + vaddps(reg08, reg08, reg20); + } + } + + if (unroll_m <= 8) { + vaddps(reg00, reg00, reg12); + vaddps(reg01, reg01, reg13); + vaddps(reg02, reg02, reg14); + vaddps(reg03, reg03, reg15); + vaddps(reg04, reg04, reg16); + vaddps(reg05, reg05, reg17); + } + + test(K, 1); + jle(kernel18, T_NEAR); + innerkernel1(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, reg00, reg01, reg02, reg03, reg04, + reg05, reg06, reg07, reg08, reg09, reg10, reg11); + align(16); + + L(kernel18); + vbroadcastss(VALPHA, ALPHA); + + if (isBetaN) { + vbroadcastss(VBETA, BETA); + } + + // Write back the results; all beta and bias cases need to be + // handled + switch (unroll_n) { + case 1: mov(rax, LDC); break; + case 2: lea(rax, ptr[LDC * 2]); break; + case 3: lea(rax, ptr[LDC + LDC * 2]); break; + case 4: lea(rax, ptr[LDC + LDC * 4]); break; + case 5: + lea(rax, ptr[LDC * 4]); + add(rax, LDC); + break; + case 6: + lea(rax, ptr[LDC + LDC * 2]); + add(rax, rax); + break; + } + + if (hasBias) { + mov(BIAS1, BIAS); + if (isLoad1Unmasked) { + vmovups(VBIAS1, ptr[BIAS1 + 0 * SIZE]); + } else { + vmaskmovps(VBIAS1, VMASK, ptr[BIAS1 + 0 * SIZE]); + } + } + + for (int i = 0; i < unroll_n; i++) { + vmulps(Ymm(i + 4), Ymm(i + 4), VALPHA); + if (!isBeta0) { + if (isLoad1Unmasked) { + switch (i) { + case 0: vmovups(ymm0, ptr[CO1 + 0 * SIZE]); break; + case 1: vmovups(ymm0, ptr[CO1 + LDC + 0 * SIZE]); break; + case 2: + vmovups(ymm0, ptr[CO1 + LDC * 2 + 0 * SIZE]); + break; + case 3: vmovups(ymm0, ptr[CO2 + 0 * SIZE]); break; + case 4: vmovups(ymm0, ptr[CO2 + LDC + 0 * SIZE]); break; + case 5: + vmovups(ymm0, ptr[CO2 + LDC * 2 + 0 * SIZE]); + break; + } + } else { + switch (i) { + case 0: + vmaskmovps(ymm0, VMASK, ptr[CO1 + 0 * SIZE]); + break; + case 1: + vmaskmovps(ymm0, VMASK, ptr[CO1 + LDC + 0 * SIZE]); + break; + case 2: + vmaskmovps( + ymm0, VMASK, ptr[CO1 + LDC * 2 + 0 * SIZE]); + break; + case 3: + vmaskmovps(ymm0, VMASK, ptr[CO2 + 0 * SIZE]); + break; + case 4: + vmaskmovps(ymm0, VMASK, ptr[CO2 + LDC + 0 * SIZE]); + break; + case 5: + vmaskmovps( + ymm0, VMASK, ptr[CO2 + LDC * 2 + 0 * SIZE]); + break; + } + } + + if (!isBetaN) { + vaddps(Ymm(i + 4), ymm0, Ymm(i + 4)); + } else { + fma(useFma, VBETA, ymm0, Ymm(i + 4), true); + } + } + if (hasBias) { + vaddps(Ymm(i + 4), VBIAS1, Ymm(i + 4)); + } + if (isLoad1Unmasked) { + switch (i) { + case 0: vmovups(ptr[CO1 + 0 * SIZE], Ymm(i + 4)); break; + case 1: + vmovups(ptr[CO1 + LDC + 0 * SIZE], Ymm(i + 4)); + break; + case 2: + vmovups(ptr[CO1 + LDC * 2 + 0 * SIZE], Ymm(i + 4)); + break; + case 3: vmovups(ptr[CO2 + 0 * SIZE], Ymm(i + 4)); break; + case 4: + vmovups(ptr[CO2 + LDC + 0 * SIZE], Ymm(i + 4)); + break; + case 5: + vmovups(ptr[CO2 + LDC * 2 + 0 * SIZE], Ymm(i + 4)); + break; + } + } else { + switch (i) { + case 0: + vmaskmovps(ptr[CO1 + 0 * SIZE], VMASK, Ymm(i + 4)); + break; + case 1: + vmaskmovps( + ptr[CO1 + LDC + 0 * SIZE], VMASK, Ymm(i + 4)); + break; + case 2: + vmaskmovps(ptr[CO1 + LDC * 2 + 0 * SIZE], VMASK, + Ymm(i + 4)); + break; + case 3: + vmaskmovps(ptr[CO2 + 0 * SIZE], VMASK, Ymm(i + 4)); + break; + case 4: + vmaskmovps( + ptr[CO2 + LDC + 0 * SIZE], VMASK, Ymm(i + 4)); + break; + case 5: + vmaskmovps(ptr[CO2 + LDC * 2 + 0 * SIZE], VMASK, + Ymm(i + 4)); + break; + } + } + + if (unroll_m >= 16) { + // Re-use ymm4 (VBIAS2) + if (i == 0) { + if (hasBias) { + if (isLoad1Unmasked) { + vmovups(VBIAS2, ptr[BIAS1 + 8 * SIZE]); + } else { + vmaskmovps( + VBIAS2, VMASK, ptr[BIAS1 + 8 * SIZE]); + } + } + } + vmulps(Ymm(i + 10), Ymm(i + 10), VALPHA); + if (!isBeta0) { + if (isLoad2Unmasked) { + switch (i) { + case 0: vmovups(ymm0, ptr[CO1 + 8 * SIZE]); break; + case 1: + vmovups(ymm0, ptr[CO1 + LDC + 8 * SIZE]); + break; + case 2: + vmovups(ymm0, ptr[CO1 + LDC * 2 + 8 * SIZE]); + break; + case 3: vmovups(ymm0, ptr[CO2 + 8 * SIZE]); break; + case 4: + vmovups(ymm0, ptr[CO2 + LDC + 8 * SIZE]); + break; + case 5: + vmovups(ymm0, ptr[CO2 + LDC * 2 + 8 * SIZE]); + break; + } + } else { + switch (i) { + case 0: + vmaskmovps(ymm0, VMASK, ptr[CO1 + 8 * SIZE]); + break; + case 1: + vmaskmovps( + ymm0, VMASK, ptr[CO1 + LDC + 8 * SIZE]); + break; + case 2: + vmaskmovps(ymm0, VMASK, + ptr[CO1 + LDC * 2 + 8 * SIZE]); + break; + case 3: + vmaskmovps(ymm0, VMASK, ptr[CO2 + 8 * SIZE]); + break; + case 4: + vmaskmovps( + ymm0, VMASK, ptr[CO2 + LDC + 8 * SIZE]); + break; + case 5: + vmaskmovps(ymm0, VMASK, + ptr[CO2 + LDC * 2 + 8 * SIZE]); + break; + } + } + if (!isBetaN) { + vaddps(Ymm(i + 10), ymm0, Ymm(i + 10)); + } else { + fma(useFma, VBETA, ymm0, Ymm(i + 10), true); + } + } + if (hasBias) { + vaddps(Ymm(i + 10), VBIAS2, Ymm(i + 10)); + } + if (isLoad2Unmasked) { + switch (i) { + case 0: + vmovups(ptr[CO1 + 8 * SIZE], Ymm(i + 10)); + break; + case 1: + vmovups(ptr[CO1 + LDC + 8 * SIZE], Ymm(i + 10)); + break; + case 2: + vmovups(ptr[CO1 + LDC * 2 + 8 * SIZE], Ymm(i + 10)); + break; + case 3: + vmovups(ptr[CO2 + 8 * SIZE], Ymm(i + 10)); + break; + case 4: + vmovups(ptr[CO2 + LDC + 8 * SIZE], Ymm(i + 10)); + break; + case 5: + vmovups(ptr[CO2 + LDC * 2 + 8 * SIZE], Ymm(i + 10)); + break; + } + } else { + switch (i) { + case 0: + vmaskmovps(ptr[CO1 + 8 * SIZE], VMASK, Ymm(i + 10)); + break; + case 1: + vmaskmovps(ptr[CO1 + LDC + 8 * SIZE], VMASK, + Ymm(i + 10)); + break; + case 2: + vmaskmovps(ptr[CO1 + LDC * 2 + 8 * SIZE], VMASK, + Ymm(i + 10)); + break; + case 3: + vmaskmovps(ptr[CO2 + 8 * SIZE], VMASK, Ymm(i + 10)); + break; + case 4: + vmaskmovps(ptr[CO2 + LDC + 8 * SIZE], VMASK, + Ymm(i + 10)); + break; + case 5: + vmaskmovps(ptr[CO2 + LDC * 2 + 8 * SIZE], VMASK, + Ymm(i + 10)); + break; + } + } + } + if (i == 2) + add(CO1, rax); + } + if (unroll_n >= 4) { + add(CO2, rax); + } + + // Compute next address of B + if (!isTransB) { + lea(rax, ptr[K * SIZE]); + switch (unroll_n) { + case 1: + add(BO1, LDB); + add(BO2, LDB); + break; + case 2: + lea(BO1, ptr[BO1 + LDB * 2]); + lea(BO2, ptr[BO2 + LDB * 2]); + break; + case 3: + lea(BO1, ptr[BO1 + LDB3]); + lea(BO2, ptr[BO2 + LDB3]); + break; + case 4: + lea(BO1, ptr[BO1 + LDB * 4]); + lea(BO2, ptr[BO2 + LDB * 4]); + break; + case 5: + lea(BO1, ptr[BO1 + LDB * 4]); + add(BO1, LDB); + lea(BO2, ptr[BO2 + LDB * 4]); + add(BO2, LDB); + break; + case 6: + lea(BO1, ptr[BO1 + LDB3 * 2]); + lea(BO2, ptr[BO2 + LDB3 * 2]); + break; + } + sub(BO1, rax); + sub(BO2, rax); + } else { + mov(rax, LDB); + imul(rax, K); + sub(BO1, rax); + add(BO1, unroll_n * SIZE); + } + }; + + auto kernel_16x6 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, true); + }; + + auto kernel_16x5 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, true); + }; + + auto kernel_16x4 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, true); + }; + + auto kernel_16x3 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy, + bool useFma = true) { + kernel(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, Ymm(4), Ymm(5), Ymm(6), Ymm(7), + Ymm(8), Ymm(9), Ymm(10), Ymm(11), Ymm(12), Ymm(13), Ymm(14), + Ymm(15), Ymm(7), Ymm(8), Ymm(9), Ymm(7), Ymm(8), Ymm(9), + Ymm(13), Ymm(14), Ymm(15)); + }; + + auto kernel_16x2 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel_16x3(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, false); + }; + + auto kernel_16x1 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel_16x3(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, false); + }; + + auto kernel_8x6 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy, + bool useFma = true) { + kernel(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, Ymm(4), Ymm(5), Ymm(6), Ymm(7), + Ymm(8), Ymm(9), Ymm(10), Ymm(11), Ymm(12), Ymm(13), Ymm(14), + Ymm(15), Ymm(10), Ymm(11), Ymm(12), Ymm(13), Ymm(14), + Ymm(15)); + }; + + auto kernel_8x5 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel_8x6(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy); + }; + + auto kernel_8x4 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel_8x6(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy); + }; + + auto kernel_8x3 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy, + bool useFma = true) { + kernel(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, useFma, Ymm(4), Ymm(5), Ymm(6), Ymm(7), + Ymm(8), Ymm(9), Ymm(10), Ymm(11), Ymm(12), Ymm(13), Ymm(14), + Ymm(15), Ymm(7), Ymm(8), Ymm(9), Ymm(7), Ymm(8), Ymm(9), + Ymm(13), Ymm(14), Ymm(15)); + }; + + auto kernel_8x2 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel_8x3(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, false); + }; + + auto kernel_8x1 = [&](int unroll_m, int unroll_n, bool isLoad1Unmasked, + bool isLoad2Unmasked, bool isDirect, bool isCopy) { + kernel_8x3(unroll_m, unroll_n, isLoad1Unmasked, isLoad2Unmasked, + isDirect, isCopy, false); + }; + + // High-level subroutine; does packing if needed, then splits C matrix. + // Operates on chunks of 16 rows, 6 columns at a time (handling tail + // cases appropriately). + // Masking is used for tail cases where M is not divisible by 8. + auto subloop = [&]( + int unroll_m, bool isLoad1Unmasked, bool isLoad2Unmasked) { + if (isTransA) { + do_pack(unroll_m, isLoad1Unmasked, isLoad2Unmasked); + } + + Label subloop11, subloop11mask; + Label subloop20, subloop21, subloop22, subloop23; + Label subloop24, subloop25; + Label subloop30, subloop31, subloop32, subloop33; + Label subloop34, subloop35; + Label subloop98, subloop98mask; + Label subloop99, subloop99mask; + + mov(CO1, C); + lea(CO2, ptr[CO1 + LDC * 2]); + add(CO2, LDC); + add(C, unroll_m * SIZE); + mov(BO1, B); + if (!isTransB) { + lea(BO2, qword[B + LDB3]); + } + + if (!isTransA) { + lea(AA, ptr[A + (unroll_m * 2 - 1 - OFFSET) * SIZE]); + cmp(M, UNROLL_M); + jg(subloop98, T_NEAR); + + mov(AA, ORIG_A); + lea(AA, ptr[AA + (unroll_m - 1 - OFFSET) * SIZE]); + L(subloop98); + } + + mov(LL, N); + mov(I, LL); + if (!isTransA) { + // If N is too small, skip copy operation + cmp(LL, UNROLL_N * 3); + jle(subloop30, T_NEAR); + + // If A is not aligned to cache line + cmp(FLAG, 0); + je(subloop30, T_NEAR); + } else { + cmp(LL, UNROLL_N); + jl(subloop20, T_NEAR); + } + align(16); + + if (!isTransA) { + if (unroll_m == 16) { + kernel_16x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, true, true); + } else { + kernel_8x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, true, true); + } + } else { + if (unroll_m == 16) { + kernel_16x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, false, false); + } else { + kernel_8x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, false, false); + } + } + + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jl(subloop20, T_NEAR); + align(16); + + L(subloop11); + if (unroll_m == 16) { + kernel_16x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, false, false); + } else { + kernel_8x6(unroll_m, UNROLL_N, isLoad1Unmasked, isLoad2Unmasked, + false, false); + } + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jge(subloop11, T_NEAR); + align(16); + + L(subloop20); + cmp(I, 1); + jne(subloop21, T_NEAR); + if (unroll_m == 16) { + kernel_16x1(unroll_m, 1, isLoad1Unmasked, isLoad2Unmasked, + false, false); + } else { + kernel_8x1(unroll_m, 1, isLoad1Unmasked, isLoad2Unmasked, false, + false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop21); + cmp(I, 2); + jne(subloop22, T_NEAR); + if (unroll_m == 16) { + kernel_16x2(unroll_m, 2, isLoad1Unmasked, isLoad2Unmasked, + false, false); + } else { + kernel_8x2(unroll_m, 2, isLoad1Unmasked, isLoad2Unmasked, false, + false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop22); + cmp(I, 3); + jne(subloop23, T_NEAR); + if (unroll_m == 16) { + kernel_16x3(unroll_m, 3, isLoad1Unmasked, isLoad2Unmasked, + false, false); + } else { + kernel_8x3(unroll_m, 3, isLoad1Unmasked, isLoad2Unmasked, false, + false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop23); + cmp(I, 4); + jne(subloop24, T_NEAR); + if (unroll_m == 16) { + kernel_16x4(unroll_m, 4, isLoad1Unmasked, isLoad2Unmasked, + false, false); + } else { + kernel_8x4(unroll_m, 4, isLoad1Unmasked, isLoad2Unmasked, false, + false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop24); + cmp(I, 5); + jne(subloop99, T_NEAR); + if (unroll_m == 16) { + kernel_16x5(unroll_m, 5, isLoad1Unmasked, isLoad2Unmasked, + false, false); + } else { + kernel_8x5(unroll_m, 5, isLoad1Unmasked, isLoad2Unmasked, false, + false); + } + jmp(subloop99, T_NEAR); + align(16); + + if (!isTransA) { + L(subloop30); + cmp(I, UNROLL_N); + jl(subloop25, T_NEAR); + align(16); + + L(subloop31); + if (unroll_m == 16) { + kernel_16x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, true, false); + } else { + kernel_8x6(unroll_m, UNROLL_N, isLoad1Unmasked, + isLoad2Unmasked, true, false); + } + sub(I, UNROLL_N); + cmp(I, UNROLL_N); + jge(subloop31, T_NEAR); + align(16); + + L(subloop25); + cmp(I, 1); + jne(subloop32, T_NEAR); + if (unroll_m == 16) { + kernel_16x1(unroll_m, 1, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } else { + kernel_8x1(unroll_m, 1, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop32); + cmp(I, 2); + jne(subloop33, T_NEAR); + if (unroll_m == 16) { + kernel_16x2(unroll_m, 2, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } else { + kernel_8x2(unroll_m, 2, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop33); + cmp(I, 3); + jne(subloop34, T_NEAR); + if (unroll_m == 16) { + kernel_16x3(unroll_m, 3, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } else { + kernel_8x3(unroll_m, 3, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop34); + cmp(I, 4); + jne(subloop35, T_NEAR); + if (unroll_m == 16) { + kernel_16x4(unroll_m, 4, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } else { + kernel_8x4(unroll_m, 4, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } + jmp(subloop99, T_NEAR); + align(16); + + L(subloop35); + cmp(I, 5); + jne(subloop99, T_NEAR); + if (unroll_m == 16) { + kernel_16x5(unroll_m, 5, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } else { + kernel_8x5(unroll_m, 5, isLoad1Unmasked, isLoad2Unmasked, + true, false); + } + align(16); + } + + L(subloop99); + // Compute address for A + if (!isTransA) { + add(A, unroll_m * SIZE); + } else { + mov(rax, LDA); + imul(rax, rax, unroll_m); + add(A, rax); + } + + // Compute next address of BIAS + if (hasBias) { + add(BIAS, unroll_m * SIZE); + } + }; + + preamble(); + + Label buffer_in_ws, buffer_allocated; + + // Get the registers + mov(B, ARG_B); + mov(LDB, ARG_LDB); + mov(r15, ARG_BETA); + mov(r12, ARG_C); + if (hasBias) + mov(r10, ARG_BIAS); + mov(LDC, ARG_LDC); + mov(rbp, rsp); + + vmovss(xmm0, ptr[ARG_ALPHA]); + vmovss(xmm1, ptr[r15]); + +#if _WIN32 + mov(A, ARG_A); + mov(LDA, ARG_LDA); +#endif + + cmp(K, STACK_K_CAPACITY); + jg(buffer_in_ws, T_NEAR); + + // Create buffer and align to 4kB page + lea(rax, ptr[K * SIZE]); + sal(rax, 4); + add(rax, 256); + sub(rsp, rax); + and_(rsp, -PAGE_4K); + jmp(buffer_allocated, T_NEAR); + + L(buffer_in_ws); + mov(rsp, ARG_WS); + + L(buffer_allocated); + + mov(ORIG_SP, rbp); + mov(M, ARG_M); + mov(N, ARG_N); + mov(C, r12); + if (hasBias) + mov(BIAS, r10); + vmovss(ALPHA, xmm0); + vmovss(BETA, xmm1); + sub(A, -OFFSET * SIZE); + sub(B, -OFFSET * SIZE); + mov(ORIG_A, A); + sal(LDA, BASE_SHIFT); + sal(LDB, BASE_SHIFT); + sal(LDC, BASE_SHIFT); + lea(LDB3, ptr[LDB + LDB * 2]); + + for (int i = 0; i < 8; i++) { + mov(dword[rsp + 88 + i * 4], i); + } + + if (isTransA && is_avx2) { + movq(xmm0, LDA); + vpbroadcastq(ymm1, xmm0); + vinsertf128(ymm0, ymm0, xmm0, 1); + vpermilpd(ymm0, ymm0, 5); + vpaddq(ymm1, ymm1, ymm1); + vperm2f128(ymm1, ymm1, ymm1, 8); + vpaddq(ymm0, ymm0, ymm1); + vmovups(STRIDE, ymm0); + } + + // Check A alignment and leading dimension; take copy-based path as + // needed + mov(rax, LDA); + or_(rax, A); + and_(rax, 0x1f); + mov(FLAG, rax); + + Label main0, main1, main2, main3, main999; + + cmp(M, UNROLL_M); + jl(main0, T_NEAR); + align(16); + + L(main1); + subloop(UNROLL_M, true, true); + sub(M, UNROLL_M); + cmp(M, UNROLL_M); + jge(main1, T_NEAR); + align(16); + + L(main0); + cmp(M, 0); + jle(main999, T_NEAR); + + if (UNROLL_M > 8) { + cmp(M, 8); + jle(main2, T_NEAR); + + sub(M, 8); + vbroadcastss(VMASK, M); + vpcmpgtd(VMASK, VMASK, MASK); + + subloop(16, true, false); + jmp(main999, T_NEAR); + align(16); + + L(main2); + cmp(M, 8); + jne(main3, T_NEAR); + subloop(8, true, true); + jmp(main999, T_NEAR); + } + + align(16); + + L(main3); + vbroadcastss(VMASK, M); + if (is_avx2) { + vpcmpgtd(VMASK, VMASK, MASK); + } else { + auto xmask = Xmm(VMASK.getIdx()); + auto xmm_tmp = xmm4; + + vextractf128(xmm_tmp, VMASK, 1); + vpcmpgtd(xmask, xmask, MASK); + vpcmpgtd(xmm_tmp, xmm_tmp, dword[rsp + 88 + 4 * 4]); // MASK + 4 + vinsertf128(VMASK, VMASK, xmm_tmp, 1); + } + subloop(8, false, false); + align(16); + + L(main999); + // Restore original stack + mov(rsp, ORIG_SP); + + vzeroupper(); + postamble(); + + ker_ = this->getCode(); + } + + typedef void (*ker_t)(dim_t m, dim_t n, dim_t k, + const float *alpha, const float *a, dim_t lda, + const float *b, dim_t ldb, const float *beta, float *c, + dim_t ldc, const float *bias, float *ws); + + void operator()(dim_t m, dim_t n, dim_t k, + const float *alpha, const float *a, dim_t lda, + const float *b, dim_t ldb, const float *beta, float *c, + dim_t ldc, const float *bias, float *ws) const + { + ker_(m, n, k, alpha, a, lda, b, ldb, beta, c, ldc, bias, ws); + } + +private: + ker_t ker_; +}; + +const xbyak_gemm *get_xbyak_gemm( + bool isTransA, bool isTransB, float beta, bool hasBias) { + auto beta_idx = [](float beta) { + return (beta == 0.0) ? 0 : (beta == 1.0 ? 1 : 2); + }; + + // Kernel table [isTransA][isTransB][hasBias][beta (0, 1, other)] + static xbyak_gemm *kernel_table[2][2][2][3]; + static std::once_flag initialized; + std::call_once(initialized, [=]{ + for (bool isTransA: {false, true}) + for (bool isTransB: {false, true}) + for (bool hasBias: {false, true}) + for (float beta: {0.0f, 1.0f, 2.0f}) { + // nocopy sgemm with bias for beta != 0.0 is not supported + if (hasBias && beta != 0.0) + continue; + kernel_table[isTransA][isTransB][hasBias][beta_idx(beta)] = + new xbyak_gemm(isTransA, isTransB, beta, hasBias); + } + }); + + return kernel_table[isTransA][isTransB][hasBias][beta_idx(beta)]; +} + +void sgemm_nocopy_driver(const char *transa, + const char *transb, int m, int n, int k, const float *alpha, + const float *a, dim_t lda, const float *b, dim_t ldb, const float *beta, + float *c, dim_t ldc, const float *bias, float *ws) +{ + bool isTransA = (*transa == 'T' || *transa == 't'); + bool isTransB = (*transb == 'T' || *transb == 't'); + + int Bm, sizeM, Bn, sizeN, Bk, sizeK; + + int i, j; + + if ((m <= 0) || (n <= 0)) + return; + + if ((k <= 0) || (alpha[0] == 0.)) { + + if (beta[0] == 0.) { + for (j = 0; j < n; j++) + for (i = 0; i < m; i++) + c[i + j * ldc] = 0.0; + } else if (beta[0] != 1.) { + for (j = 0; j < n; j++) + for (i = 0; i < m; i++) + c[i + j * ldc] *= beta[0]; + } + + return; + } + + assert(IMPLICATION(bias != nullptr, *beta == 0.0)); + + // XXX: this happens on every thread... + bool hasBias = (bias != nullptr); + auto ker_bn = get_xbyak_gemm(isTransA, isTransB, *beta, hasBias); + auto ker_b1 = get_xbyak_gemm(isTransA, isTransB, 1.0, false); + auto ker_b0 = get_xbyak_gemm(isTransA, isTransB, 0.0, false); + assert(ker_bn && ker_b1 && ker_b0); + + int BM = 4032; + int BN = isTransA ? 96 : 48; + int BK = isTransB ? 96 : 256; + const float *curA, *curB, *curBias = nullptr; + float *curC; + + for (Bk = 0; Bk < k; Bk += sizeK) { + sizeK = k - Bk; + if (sizeK >= BK * 2) + sizeK = BK; + else { + if (sizeK > BK) + sizeK = (sizeK + 1) / 2; + } + + for (Bm = 0; Bm < m; Bm += sizeM) { + sizeM = m - Bm; + if (sizeM >= BM * 2) + sizeM = BM; + else { + if (sizeM > BM + BM / 2) + sizeM = (sizeM + 1) / 2; + } + + for (Bn = 0; Bn < n; Bn += sizeN) { + sizeN = n - Bn; + if (sizeN >= BN * 2) + sizeN = BN; + else { + if (sizeN > BN + BN / 2) + sizeN = (sizeN + 1) / 2; + } + + if (!isTransA) { + curA = a + Bm + Bk * lda; + } else { + curA = a + Bk + Bm * lda; + } + if (!isTransB) { + curB = b + Bk + Bn * ldb; + } else { + curB = b + Bn + Bk * ldb; + } + curC = c + Bm + (size_t)Bn * ldc; + if (bias != nullptr) { + if (Bk == 0) { + curBias = bias + Bm; + } else { + curBias = nullptr; + } + } + if (Bk == 0) { + if (*beta == 0.0 && bias == nullptr) + (*ker_b0)((dim_t)sizeM, (dim_t)sizeN, (dim_t)sizeK, + alpha, curA, lda, curB, ldb, beta, curC, ldc, + curBias, ws); + else + (*ker_bn)((dim_t)sizeM, (dim_t)sizeN, (dim_t)sizeK, + alpha, curA, lda, curB, ldb, beta, curC, ldc, + curBias, ws); + } else { + (*ker_b1)((dim_t)sizeM, (dim_t)sizeN, (dim_t)sizeK, + alpha, curA, lda, curB, ldb, beta, curC, ldc, + curBias, ws); + } + } + } + } +} + +} + +mkldnn_status_t jit_avx_gemm_f32( + const char *transa, const char *transb, + const int *p_m, const int *p_n, const int *p_k, const float *p_alpha, + const float *A, const int *p_lda, const float *B, const int *p_ldb, + const float *p_beta, float *C, const int *p_ldc, const float *bias) +{ + using namespace mkldnn::impl::utils; + using namespace avx_gemm_f32; + using namespace gemm_utils; + + if (*p_beta != 0 && bias) + return ref_gemm(transa, transb, p_m, p_n, p_k, + p_alpha, A, p_lda, B, p_lda, p_beta, C, p_ldc, bias); + + int nthr = (mkldnn_in_parallel()) ? 1 : mkldnn_get_max_threads(); + + int m = *p_m; + int n = *p_n; + int k = *p_k; + dim_t lda = *p_lda; + dim_t ldb = *p_ldb; + dim_t ldc = *p_ldc; + float beta = *p_beta; + int MB, NB, KB; + + int nthr_m, nthr_n, nthr_k, nthr_mn; + + // Determine threading partitioning + calc_nthr_nocopy_avx( + m, n, k, nthr, &nthr_m, &nthr_n, &nthr_k, &MB, &NB, &KB); + assert(IMPLICATION(!mkldnn_thr_syncable(), nthr_k == 1)); + + // May not happen, but just in case + if (nthr < nthr_m * nthr_n * nthr_k) + nthr = nthr_m * nthr_n * nthr_k; + + nthr_mn = nthr_m * nthr_n; + + unsigned char * ompstatus_ = nullptr; + unsigned char volatile *ompstatus = nullptr; + + float *c_buffers = nullptr; + float *ws_buffers = nullptr; + + if (nthr_k > 1) { + ompstatus_ = (unsigned char *) malloc( + nthr * CACHE_LINE_SIZE, + CACHE_LINE_SIZE); + ompstatus = (unsigned char volatile *) ompstatus_; + assert(ompstatus); + + for (int i = 0; i < nthr; i++) + ompstatus[i * CACHE_LINE_SIZE] = 0; + + c_buffers = (float *)malloc(nthr_m * nthr_n * (nthr_k - 1) * MB * NB + * sizeof(float), PAGE_4K); + } + + const size_t ws_elems_per_thr = (size_t)k * 16 + 64; + const size_t ws_size_per_thr + = rnd_up(ws_elems_per_thr * sizeof(float), PAGE_4K); + if (k > STACK_K_CAPACITY) { + ws_buffers = (float *)malloc(nthr * ws_size_per_thr, PAGE_4K); + } + + parallel_nd(nthr, [&](const int ithr) { + int ithr_m, ithr_n, ithr_k, ithr_mn; + int m_from, m_to, myM; + int n_from, n_to, myN; + int k_from, k_to, myK; + int cbase, ibase; + const float *myA, *myB, *myBias = nullptr; + float *myC = C, myBeta; + float *ws = ws_buffers ? + ws_buffers + ithr * ws_size_per_thr / sizeof(float) : 0; + dim_t ld = ldc; + + int sum_later = (mkldnn_get_num_threads() < nthr_m * nthr_n * nthr_k); + + if (ithr < nthr_m * nthr_n * nthr_k) { + + ithr_mn = ithr % nthr_mn; + ithr_m = ithr_mn % nthr_m; + ithr_n = ithr_mn / nthr_m; + ithr_k = ithr / nthr_mn; + + /* swap ithr_k for performance improvement */ + if (ithr_k == 0) + ithr_k = nthr_k - 1; + else if (ithr_k == nthr_k - 1) + ithr_k = 0; + + m_from = MB * (ithr_m); + m_to = MB * (ithr_m + 1); + if (m_to > m) + m_to = m; + myM = m_to - m_from; + + n_from = NB * (ithr_n); + n_to = NB * (ithr_n + 1); + if (n_to > n) + n_to = n; + myN = n_to - n_from; + + k_from = KB * (ithr_k); + k_to = KB * (ithr_k + 1); + if (k_to > k) + k_to = k; + myK = k_to - k_from; + + cbase = (ithr_m + nthr_m * ithr_n) * (nthr_k - 1); + ibase = (ithr_m + nthr_m * ithr_n) * nthr_k; + + if ((myM > 0) && (myN > 0)) { + + if (*transa == 'N' || *transa == 'n') { + myA = &(A[m_from + k_from * lda]); + } else { + myA = &(A[k_from + m_from * lda]); + } + if (*transb == 'N' || *transb == 'n') { + myB = &(B[k_from + n_from * ldb]); + } else { + myB = &(B[n_from + k_from * ldb]); + } + if (ithr_k == 0) { + myC = &(C[m_from + n_from * ldc]); + myBeta = beta; + ld = ldc; + if (bias) + myBias = &(bias[m_from]); + } else { + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1); + myBeta = 0.0; + ld = MB; + myBias = nullptr; + } + + sgemm_nocopy_driver(transa, transb, myM, myN, myK, p_alpha, myA, + lda, myB, ldb, &myBeta, myC, ld, myBias, ws); + + if (nthr_k > 1 && !sum_later) + ompstatus[(ibase + ithr_k) * CACHE_LINE_SIZE] = 1; + } + + if (nthr_k > 1 && !sum_later) { + + // sum matrices partitioned along K dimension + int n1, n2; + + partition_unit_diff(ithr_k, nthr_k, myN, &n1, &n2); + + if (ithr_k > 0) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1) + + (dim_t)n1 * MB; + /* need to wait until main thread finishes */ + while (ompstatus[ibase * CACHE_LINE_SIZE] != 1) { + }; + + /* my cache is hot */ + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + + for (int ik = 1; ik < nthr_k; ++ik) { + if (ik != ithr_k) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ik - 1) + + (dim_t)n1 * MB; + + while (ompstatus[(ibase + ik) * CACHE_LINE_SIZE] != 1) { + }; + + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + } + } + } + }); + + // handle C summation later + if (nthr_k > 1 && ompstatus[0] == 0) { + + parallel_nd(nthr, [&](const int ithr) { + int ithr_m, ithr_n, ithr_k, ithr_mn; + int m_from, m_to, myM; + int n_from, n_to, myN; + int cbase; + float *myC = C; + + if (ithr < nthr_m * nthr_n * nthr_k) { + + ithr_mn = ithr % nthr_mn; + ithr_m = ithr_mn % nthr_m; + ithr_n = ithr_mn / nthr_m; + ithr_k = ithr / nthr_mn; + + /* swap ithr_k for performance improvement */ + if (ithr_k == 0) + ithr_k = nthr_k - 1; + else if (ithr_k == nthr_k - 1) + ithr_k = 0; + + m_from = MB * (ithr_m); + m_to = MB * (ithr_m + 1); + if (m_to > m) + m_to = m; + myM = m_to - m_from; + + n_from = NB * (ithr_n); + n_to = NB * (ithr_n + 1); + if (n_to > n) + n_to = n; + myN = n_to - n_from; + + cbase = (ithr_m + nthr_m * ithr_n) * (nthr_k - 1); + + if (nthr_k > 1) { + // sum matrices partitioned along K dimension + int n1, n2; + + partition_unit_diff(ithr_k, nthr_k, myN, &n1, &n2); + + if (ithr_k > 0) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1) + + (dim_t)n1 * MB; + + /* my cache is hot */ + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + + for (int ik = 1; ik < nthr_k; ++ik) { + if (ik != ithr_k) { + + myC = c_buffers + (dim_t)MB * NB * (cbase + ik - 1) + + (dim_t)n1 * MB; + + sum_two_matrices(myM, n2, myC, MB, + &C[m_from + (n_from + n1) * ldc], ldc); + } + } + } + } + }); + } + + + free(c_buffers); + free(ompstatus_); + free(ws_buffers); + + return mkldnn_success; +} + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.hpp new file mode 100644 index 000000000000..aabf520a3c10 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/jit_avx_gemm_f32.hpp @@ -0,0 +1,37 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX_GEMM_F32_HPP +#define JIT_AVX_GEMM_F32_HPP + +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +mkldnn_status_t jit_avx_gemm_f32( + const char *transa, const char *transb, const int *M, + const int *N, const int *K, const float *alpha, const float *A, + const int *lda, const float *B, const int *ldb, const float *beta, + float *C, const int *ldc, const float *bias = nullptr); + + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.cpp new file mode 100644 index 000000000000..5147885a89e4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.cpp @@ -0,0 +1,346 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_types.h" + +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +#include "gemm_utils_f32.hpp" +#include "ref_gemm_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace gemm_utils; + +namespace { + +template +void copy_A( + bool isTransA, int K, const data_t *A, const dim_t lda, data_t *ws) { + for (int k = 0; k < K; k++) { + PRAGMA_OMP_SIMD() + for (int i = 0; i < unroll_factor::m; i++) { + ws[i] = isTransA ? A[i * lda + k] : A[i + k * lda]; + } + ws += unroll_factor::m; + } +} + +template +void kernel_mxn(int K, const data_t *A, const dim_t lda, + const data_t *B, const dim_t ldb, data_t *C, const dim_t ldc, + const data_t alpha, const data_t beta) { + data_t c[unroll_factor::m * unroll_factor::n] = + { static_cast(0.) }; + for (int k = 0; k < K; k++) { + for (int j = 0; j < unroll_factor::n; j++) { + data_t b = isTransB ? B[j + k * ldb] : B[k + j * ldb]; + PRAGMA_OMP_SIMD() + for (int i = 0; i < unroll_factor::m; i++) { + data_t a = isTransA ? A[i * lda + k] : A[i + lda * k]; + c[i + unroll_factor::m * j] += a * b; + } + } + } + for (int j = 0; j < unroll_factor::n; j++) { + PRAGMA_OMP_SIMD() + for (int i = 0; i < unroll_factor::m; i++) { + C[i + j * ldc] = (beta == static_cast(0.)) + ? alpha * c[i + unroll_factor::m * j] + : alpha * c[i + unroll_factor::m * j] + + beta * C[i + j * ldc]; + } + } +} + +template +void block_ker(const int M, const int N, const int K, + const data_t *A, const dim_t lda, const data_t *B, const dim_t ldb, + data_t *C, const dim_t ldc, const data_t alpha, const data_t beta, + data_t *ws, bool do_copy) { + int Nu = rnd_dn(N, unroll_factor::n); + int Mu = rnd_dn(M, unroll_factor::m); + for (int i = 0; i < Mu; i += unroll_factor::m) { + for (int j = 0; j < Nu; j += unroll_factor::n) { + const data_t *b = isTransB ? &B[j] : &B[j * ldb]; + const data_t *a = isTransA ? &A[i * lda] : &A[i]; + if (do_copy) { + if (j == 0) { + copy_A(isTransA, K, a, lda, ws); + } + kernel_mxn( + K, ws, unroll_factor::m, b, ldb, + &C[i + j * ldc], ldc, alpha, beta); + } else { + kernel_mxn( + K, a, lda, b, ldb, &C[i + j * ldc], ldc, alpha, beta); + } + } + } + // tail processing + for (int i = 0; i < M; i++) { + for (int j = Nu; j < N; j++) { + data_t c = beta == static_cast(0.) + ? static_cast(0.) + : beta * C[i + j * ldc]; + for (int p = 0; p < K; p++) { + data_t b = isTransB ? B[j + p * ldb] : B[p + j * ldb]; + data_t a = isTransA ? A[p + i * lda] : A[i + p * lda]; + c += alpha * a * b; + } + C[i + j * ldc] = c; + } + } + for (int i = Mu; i < M; i++) { + for (int j = 0; j < Nu; j++) { + data_t c = beta == static_cast(0.) + ? static_cast(0.) + : beta * C[i + j * ldc]; + for (int p = 0; p < K; p++) { + data_t b = isTransB ? B[j + p * ldb] : B[p + j * ldb]; + data_t a = isTransA ? A[p + i * lda] : A[i + p * lda]; + c += alpha * a * b; + } + C[i + j * ldc] = c; + } + } +} + +template +void gemm_ithr(const int M, const int N, const int K, const data_t alpha, + const data_t *A, const dim_t lda, const data_t *B, const dim_t ldb, + const data_t beta, data_t *C, const dim_t ldc, bool do_copy, + data_t *ws) { + constexpr int BM = gemm_traits::BM; + constexpr int BN = gemm_traits::BN; + constexpr int BK = gemm_traits::BK; + + const data_t *curA; + const data_t *curB; + data_t *curC; + + if ((M <= 0) || (N <= 0)) + return; + + if ((K <= 0) || (alpha == static_cast(0))) { + dim_t MN = N * M; + if (beta == static_cast(0.)) { + for (dim_t j = 0; j < MN; j++) + C[j] = static_cast(0.); + } else if (beta != static_cast(1.)) { + for (dim_t j = 0; j < MN; j++) + C[j] *= beta; + } + return; + } + + for (int Bk = 0; Bk < K; Bk += BK) { + int kb = nstl::min(K - Bk, BK); + for (int Bm = 0; Bm < M; Bm += BM) { + int mb = nstl::min(M - Bm, BM); + for (int Bn = 0; Bn < N; Bn += BN) { + int nb = nstl::min(N - Bn, BN); + curA = isTransA ? A + Bk + Bm * lda : A + Bm + Bk * lda; + curB = isTransB ? B + Bn + Bk * ldb : B + Bk + Bn * ldb; + curC = C + Bm + Bn * ldc; + if (Bk == 0) { + block_ker(mb, nb, kb, curA, lda, + curB, ldb, curC, ldc, alpha, beta, ws, do_copy); + } else { + block_ker(mb, nb, kb, curA, lda, + curB, ldb, curC, ldc, alpha, static_cast(1.0), + ws, do_copy); + } + } + } + } +} + +} + +template +mkldnn_status_t ref_gemm( + const char *transa_, const char *transb_, const int *M_, + const int *N_, const int *K_, const data_t *alpha_, const data_t *A, + const int *lda_, const data_t *B, const int *ldb_, const data_t *beta_, + data_t *C, const int *ldc_, const data_t *bias) { + + bool isTransA = (*transa_ == 'T' || *transa_ == 't'); + bool isTransB = (*transb_ == 'T' || *transb_ == 't'); + const int M = *M_, N = *N_, K = *K_; + const dim_t lda = *lda_, ldb = *ldb_, ldc = *ldc_; + const data_t alpha = *alpha_, beta = *beta_; + + int max_nthr = mkldnn_in_parallel() ? 1 : mkldnn_get_max_threads(); + int nthr_m, nthr_n, nthr_k; + int MB, NB, KB; + // thread balancing over M, N, K & size of blocking dimensions + calc_nthr_nocopy_avx( + M, N, K, max_nthr, &nthr_m, &nthr_n, &nthr_k, &MB, &NB, &KB); + assert(IMPLICATION(!mkldnn_thr_syncable(), nthr_k == 1)); + + data_t *c_buffers = nullptr; + data_t *ws_buffers = nullptr; + if (nthr_k > 1) { + c_buffers = (data_t *)malloc(nthr_m * nthr_n * (nthr_k - 1) * MB * NB + * sizeof(data_t), PAGE_4K); + if (!c_buffers) { + nthr_k = 1; + KB = K; + } + } + + bool do_copy = (NB / unroll_factor::n > 3); + const int nthr_mn = nthr_m * nthr_n; + const int nthr = nthr_mn * nthr_k; + const size_t ws_elems_per_thr = K * unroll_factor::m; + const size_t ws_size_per_thr + = rnd_up(ws_elems_per_thr * sizeof(data_t), PAGE_4K); + if (do_copy) { + ws_buffers = (data_t*)malloc(nthr * ws_size_per_thr, PAGE_4K); + if (!ws_buffers) + do_copy = false; + } + + auto get_thr_block = [&](int &from, int &to, int &myN, int NB, int N, + int ithr) { + from = NB * (ithr); + to = NB * (ithr + 1); + if (to > N) + to = N; + myN = to - from; + }; + + parallel_nd(nthr, [&](const int ithr) { + int ithr_mn = ithr % nthr_mn; + int ithr_m = ithr_mn % nthr_m; + int ithr_n = ithr_mn / nthr_m; + int ithr_k = ithr / nthr_mn; + + int cbase = (ithr_m + nthr_m * ithr_n) * (nthr_k - 1); + + data_t *ws = do_copy + ? ws_buffers + ithr * ws_size_per_thr / sizeof(data_t) + : nullptr; + + int m_from = 0, m_to = 0, myM = 0, n_from = 0, n_to = 0, myN = 0, + k_from = 0, k_to = 0, myK = 0; + + get_thr_block(m_from, m_to, myM, MB, M, ithr_m); + get_thr_block(n_from, n_to, myN, NB, N, ithr_n); + get_thr_block(k_from, k_to, myK, KB, K, ithr_k); + + if (myM > 0 && myN > 0) { + data_t myBeta, *myC; + dim_t ld; + if (ithr_k == 0) { + myC = &(C[m_from + n_from * ldc]); + myBeta = beta; + ld = ldc; + } else { + myC = c_buffers + (dim_t)MB * NB * (cbase + ithr_k - 1); + myBeta = 0.0f; + ld = MB; + } + const data_t *myA = isTransA + ? &(A[k_from + m_from * lda]) + : &(A[m_from + k_from * lda]); + const data_t *myB = isTransB + ? &(B[n_from + k_from * ldb]) + : &(B[k_from + n_from * ldb]); + + if (!isTransA) { + if (!isTransB) { + gemm_ithr(myM, myN, myK, alpha, myA, + lda, myB, ldb, myBeta, myC, ld, do_copy, ws); + } else { + gemm_ithr(myM, myN, myK, alpha, myA, + lda, myB, ldb, myBeta, myC, ld, do_copy, ws); + } + } else { + if (!isTransB) { + gemm_ithr(myM, myN, myK, alpha, myA, + lda, myB, ldb, myBeta, myC, ld, do_copy, ws); + } else { + gemm_ithr(myM, myN, myK, alpha, myA, + lda, myB, ldb, myBeta, myC, ld, do_copy, ws); + } + } + } + }); + + if (nthr_k > 1) { + parallel_nd(nthr, [&](const int ithr) { + int ithr_mn = ithr % nthr_mn; + int ithr_m = ithr_mn % nthr_m; + int ithr_k = ithr / nthr_mn; + int ithr_n = ithr_mn / nthr_m; + + int n_from = 0, n_to = 0, myN = 0; + int m_from = 0, m_to = 0, myM = 0; + + int cbase = (ithr_m + nthr_m * ithr_n) * (nthr_k - 1); + + get_thr_block(n_from, n_to, myN, NB, N, ithr_n); + get_thr_block(m_from, m_to, myM, MB, M, ithr_m); + + // sum matrices partitioned along K dimension + int offset = 0, block = 0; + gemm_utils::partition_unit_diff(ithr_k, nthr_k, myN, &offset, + &block); + for (int ik = 1; ik < nthr_k; ++ik) { + data_t *myC = c_buffers + + MB * ((dim_t)NB * (cbase + ik - 1) + offset); + + gemm_utils::sum_two_matrices(myM, block, myC, MB, + &C[m_from + (n_from + offset) * ldc], ldc); + } + }); + } + + if (bias) { + parallel_nd(N, M, [&](int i, int j) { + C[i*ldc + j] += bias[j]; + }); + } + + free(ws_buffers); + free(c_buffers); + + return mkldnn_success; +} + +template mkldnn_status_t ref_gemm( + const char *transa_, const char *transb_, + const int *M_, const int *N_, const int *K_, const float *alpha_, + const float *A, const int *lda_, const float *B, const int *ldb_, + const float *beta_, float *C, const int *ldc_, const float *bias); + +template mkldnn_status_t ref_gemm( + const char *transa_, const char *transb_, + const int *M_, const int *N_, const int *K_, const double *alpha_, + const double *A, const int *lda_, const double *B, const int *ldb_, + const double *beta_, double *C, const int *ldc_, const double *bias); +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.hpp new file mode 100644 index 000000000000..7c90ba62775a --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/f32/ref_gemm_f32.hpp @@ -0,0 +1,36 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef REF_GEMM_F32_HPP +#define REF_GEMM_F32_HPP + +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +mkldnn_status_t ref_gemm(const char *transa, const char *transb, const int *M, + const int *N, const int *K, const data_t *alpha, const data_t *A, + const int *lda, const data_t *B, const int *ldb, const data_t *beta, + data_t *C, const int *ldc, const data_t *bias); + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.cpp new file mode 100644 index 000000000000..3dbe07d74375 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.cpp @@ -0,0 +1,280 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn.h" + +#include "mkldnn_traits.hpp" +#include "nstl.hpp" + +#include "jit_generator.hpp" + +#include "gemm.hpp" + +#include "f32/jit_avx512_common_gemm_f32.hpp" +#include "f32/jit_avx_gemm_f32.hpp" +#include "f32/ref_gemm_f32.hpp" + +#include "s8x8s32/jit_avx512_core_gemm_s8u8s32.hpp" +#include "s8x8s32/simple_gemm_s8s8s32.hpp" +#include "s8x8s32/ref_gemm_s8x8s32.hpp" + +#include "os_blas.hpp" + +/* USE_MKL USE_CBLAS effect + * ------- --------- ------ + * yes yes use Intel(R) MKL CBLAS + * yes no use jit + * no yes system-dependent CBLAS + * no no use jit + */ + +namespace mkldnn { +namespace impl { +namespace cpu { + +mkldnn_status_t check_gemm_input(const char *transa, const char *transb, + const int *M, const int *N, const int *K, const int *lda, + const int *ldb, const int *ldc, const float *alpha, const float *beta, + const bool with_bias) { + if (utils::any_null(transa, transb, M, N, K, lda, ldb, ldc, alpha, beta)) + return mkldnn_invalid_arguments; + if (with_bias && *beta != 0) + return mkldnn_unimplemented; + bool consistency = true + && utils::one_of(*transa, 'T', 't', 'N', 'n') + && utils::one_of(*transb, 'T', 't', 'N', 'n') + && *M >= 0 + && *N >= 0 + && *K >= 0; + + if (!consistency) + return mkldnn_invalid_arguments; + bool isTransA = utils::one_of(*transa, 'T', 't'); + bool isTransB = utils::one_of(*transb, 'T', 't'); + int nrowA = isTransA ? *K : *M; + int nrowB = isTransB ? *N : *K; + consistency = true + && *lda >= nstl::max(1, nrowA) + && *ldb >= nstl::max(1, nrowB) + && *ldc >= nstl::max(1, *M); + if (!consistency) + return mkldnn_invalid_arguments; + + return mkldnn_success; +} + +mkldnn_status_t check_gemm_x8x8x32_input(const char *offsetc, + const char *transa, const char *transb, const int *M, const int *N, + const int *K, const int *lda, const int *ldb, const int *ldc, + const float *alpha, const float *beta, const bool with_bias) { + if (offsetc == nullptr) + return mkldnn_invalid_arguments; + if (!utils::one_of(*offsetc, 'F', 'f', 'C', 'c', 'R', 'r')) + return mkldnn_invalid_arguments; + + return check_gemm_input(transa, transb, M, N, K, lda, ldb, ldc, alpha, + beta, with_bias); +} + +mkldnn_status_t extended_sgemm(const char *transa, const char *transb, + const int *M, const int *N, const int *K, const float *alpha, + const float *A, const int *lda, const float *B, const int *ldb, + const float *beta, float *C, const int *ldc, + const float *bias, const bool force_jit_gemm) { + mkldnn_status_t status = check_gemm_input(transa, transb, M, N, K, + lda, ldb, ldc, alpha, beta, bias != nullptr); + if (status != mkldnn_success) + return status; + +#ifdef USE_CBLAS + if (!force_jit_gemm) { + bool trA = *transa == 't' || *transa == 'T'; + bool trB = *transb == 't' || *transb == 'T'; + CBLAS_TRANSPOSE Cblas_trA = trA ? CblasTrans : CblasNoTrans; + CBLAS_TRANSPOSE Cblas_trB = trB ? CblasTrans : CblasNoTrans; + cblas_sgemm(CblasColMajor, Cblas_trA, Cblas_trB, + *M, *N, *K, *alpha, A, *lda, B, *ldb, *beta, C, *ldc); + + if (bias) { + // Add bias if necessary (bias is applied to columns of C) + cblas_int incx = 1, incy = 1; + parallel_nd(*N, [&](int n) { + ptrdiff_t offset = (ptrdiff_t)n * (*ldc); + cblas_saxpy(*M, 1.0, bias, incx, C + offset, incy); + }); + } + return mkldnn_success; + } +#endif + + if (mayiuse(avx512_common)) + return jit_avx512_common_gemm_f32(transa, transb, + M, N, K, alpha, A, lda, B, ldb, beta, C, ldc, bias); + else if (mayiuse(avx)) + return jit_avx_gemm_f32(transa, transb, + M, N, K, alpha, A, lda, B, ldb, beta, C, ldc, bias); + else + return ref_gemm(transa, transb, + M, N, K, alpha, A, lda, B, ldb, beta, C, ldc, bias); +} + +template +mkldnn_status_t gemm_s8x8s32(const char *transa, const char *transb, + const char *offsetc, const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const b_dt *B, const int *LDB, const int8_t *bo, const float *beta, + int32_t *C, const int *LDC, const int32_t *co) { + mkldnn_status_t status = check_gemm_x8x8x32_input(offsetc, transa, transb, + M, N, K, LDA, LDB, LDC, alpha, beta, false); + if (status != mkldnn_success) + return status; + + if (*M == 0 || *N == 0 || *K == 0) + return mkldnn_success; + +#if USE_MKL_IGEMM + bool OCisR = (*offsetc == 'R' || *offsetc == 'r'); + bool OCisC = (*offsetc == 'C' || *offsetc == 'c'); + bool AisN = (*transa == 'N' || *transa == 'n'); + bool BisN = (*transb == 'N' || *transb == 'n'); + + if (data_traits::data_type == data_type::u8) { + CBLAS_TRANSPOSE Cblas_trA = AisN ? CblasNoTrans : CblasTrans; + CBLAS_TRANSPOSE Cblas_trB = BisN ? CblasNoTrans : CblasTrans; + CBLAS_OFFSET Cblas_offsetc = + OCisR + ? CblasRowOffset + : OCisC + ? CblasColOffset + : CblasFixOffset; + cblas_gemm_s8u8s32(CblasColMajor, Cblas_trA, Cblas_trB, Cblas_offsetc, + *M, *N, *K, *alpha, A, *LDA, *ao, (uint8_t *)B, *LDB, *bo, + *beta, C, *LDC, co); + return mkldnn_success; + } else { + assert(data_traits::data_type == data_type::s8); + // TODO CBLAS implementation of gemm_s8s8s32 goes here. + // mkldnn_gemm_s8s8s32 doesn't support non-zero ao and bo + if (utils::everyone_is(0, *ao, *bo)) { + return simple_gemm_s8s8s32(transa, transb, offsetc, M, + N, K, alpha, A, LDA, ao, (int8_t *)B, LDB, bo, beta, + C, LDC, co); + } else { + return ref_gemm_s8x8s32(transa, transb, offsetc, M, N, K, + alpha, A, LDA, ao, B, LDB, bo, beta, C, LDC, co); + } + } +#else + cpu_isa_t isa = isa_any; + if (mayiuse(avx512_core_vnni)) { + isa = avx512_core_vnni; + } else if (mayiuse(avx512_core)) { + isa = avx512_core; + } + + if (data_traits::data_type == data_type::u8) { + switch (isa) { + case avx512_core: + case avx512_core_vnni: + return jit_avx512_core_gemm_s8u8s32(transa, transb, offsetc, M, + N, K, alpha, A, LDA, ao, (uint8_t *)B, LDB, bo, beta, + C, LDC, co); + default: + return ref_gemm_s8x8s32(transa, transb, offsetc, M, N, K, + alpha, A, LDA, ao, B, LDB, bo, beta, C, LDC, co); + } + } else { + assert(data_traits::data_type == data_type::s8); + // mkldnn_gemm_s8s8s32 doesn't support non-zero ao and bo + if ((mayiuse(avx512_core) || mayiuse(avx512_core_vnni)) + && *ao == 0 && *bo == 0) { + return simple_gemm_s8s8s32(transa, transb, offsetc, M, + N, K, alpha, A, LDA, ao, (int8_t *)B, LDB, bo, beta, + C, LDC, co); + } else { + return ref_gemm_s8x8s32(transa, transb, offsetc, M, N, K, + alpha, A, LDA, ao, B, LDB, bo, beta, C, LDC, co); + } + } +#endif +} + +template +mkldnn_status_t gemm_s8x8s32(const char *transa, const char *transb, + const char *offsetc, const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const int8_t *B, const int *LDB, const int8_t *bo, const float *beta, + int32_t *C, const int *LDC, const int32_t *co); + +template +mkldnn_status_t gemm_s8x8s32(const char *transa, const char *transb, + const char *offsetc, const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const uint8_t *B, const int *LDB, const int8_t *bo, const float *beta, + int32_t *C, const int *LDC, const int32_t *co); + +} +} +} + +using namespace mkldnn::impl; +using namespace mkldnn::impl::cpu; + +mkldnn_status_t mkldnn_sgemm(const char *transa, const char *transb, + const int64_t *M, const int64_t *N, const int64_t *K, const float *alpha, + const float *A, const int64_t *lda, const float *B, const int64_t *ldb, + const float *beta, float *C, const int64_t *ldc) { + int M_s32 = (int)*M; + int N_s32 = (int)*N; + int K_s32 = (int)*K; + int lda_s32 = (int)*lda; + int ldb_s32 = (int)*ldb; + int ldc_s32 = (int)*ldc; + + return extended_sgemm(transa, transb, &M_s32, &N_s32, &K_s32, + alpha, A, &lda_s32, B, &ldb_s32, beta, C, &ldc_s32); +} + +mkldnn_status_t mkldnn_gemm_s8u8s32(const char *transa, const char *transb, + const char *offsetc, const int64_t *M, const int64_t *N, const int64_t *K, + const float *alpha, const int8_t *A, const int64_t *lda, const int8_t *ao, + const uint8_t *B, const int64_t *ldb, const int8_t *bo, const float *beta, + int32_t *C, const int64_t *ldc, const int32_t *co) { + int M_s32 = (int)*M; + int N_s32 = (int)*N; + int K_s32 = (int)*K; + int lda_s32 = (int)*lda; + int ldb_s32 = (int)*ldb; + int ldc_s32 = (int)*ldc; + return gemm_s8x8s32(transa, transb, offsetc, &M_s32, &N_s32, &K_s32, + alpha, A, &lda_s32, ao, B, &ldb_s32, bo, beta, C, &ldc_s32, co); +} + +mkldnn_status_t mkldnn_gemm_s8s8s32(const char *transa, const char *transb, + const char *offsetc, const int64_t *M, const int64_t *N, const int64_t *K, + const float *alpha, const int8_t *A, const int64_t *lda, const int8_t *ao, + const int8_t *B, const int64_t *ldb, const int8_t *bo, const float *beta, + int32_t *C, const int64_t *ldc, const int32_t *co) { + int M_s32 = (int)*M; + int N_s32 = (int)*N; + int K_s32 = (int)*K; + int lda_s32 = (int)*lda; + int ldb_s32 = (int)*ldb; + int ldc_s32 = (int)*ldc; + + return gemm_s8x8s32(transa, transb, offsetc, &M_s32, &N_s32, &K_s32, + alpha, A, &lda_s32, ao, B, &ldb_s32, bo, beta, C, &ldc_s32, co); +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.hpp new file mode 100644 index 000000000000..dc15ff713040 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/gemm.hpp @@ -0,0 +1,58 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef GEMM_HPP +#define GEMM_HPP + +#include "mkldnn_types.h" +#include "os_blas.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +mkldnn_status_t extended_sgemm(const char *transa, const char *transb, + const int *M, const int *N, const int *K, const float *alpha, + const float *A, const int *lda, const float *B, const int *ldb, + const float *beta, float *C, const int *ldc, + const float *bias = nullptr, bool force_jit_gemm = false); + +template +mkldnn_status_t gemm_s8x8s32(const char *transa, const char *transb, + const char *offsetc, const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *lda, const int8_t *ao, + const b_dt *B, const int *ldb, const int8_t *bo, const float *beta, + int32_t *c, const int *ldc, const int32_t *co); + +#ifdef USE_CBLAS +#define GEMM_IMPL_STR "gemm:blas" +#else +#define GEMM_IMPL_STR "gemm:jit" +#endif + +#if USE_MKL_IGEMM +#define IGEMM_S8U8S32_IMPL_STR "igemm_s8u8s32:blas" +#define IGEMM_S8S8S32_IMPL_STR "igemm_s8s8s32:blas" +#else +#define IGEMM_S8U8S32_IMPL_STR "igemm_s8u8s32:jit" +#define IGEMM_S8S8S32_IMPL_STR "igemm_s8s8s32:jit" +#endif + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/os_blas.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/os_blas.hpp new file mode 100644 index 000000000000..4d34ede0bd6d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/os_blas.hpp @@ -0,0 +1,86 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef OS_BLAS_HPP +#define OS_BLAS_HPP + +/** \file + * Common stuff respecting USE_MKL and USE_CBLAS compile flags + * + * USE_MKL USE_CBLAS effect + * ------- --------- ------ + * yes yes normal compile: jit *may* be preferred over Intel(R) MKL CBLAS + * yes no jit calls OK; assert if cblas is ever called + * no yes system-dependent CBLAS + * no no gemm convolution (or other blas) N/A; create stubs + */ + +#if defined(USE_MKL) + +#include "mkl_version.h" + +#define USE_MKL_PACKED_GEMM (INTEL_MKL_VERSION >= 20190001) +#define USE_MKL_IGEMM \ + (INTEL_MKL_VERSION >= 20180000 && __INTEL_MKL_BUILD_DATE >= 20170628) + +#include "mkl_cblas.h" +#if !defined(USE_CBLAS) +#define cblas_sgemm(...) assert(!"CBLAS is unavailable") +#endif + +#else /* defined(USE_MKL) */ + +#define USE_MKL_PACKED_GEMM 0 +#define USE_MKL_IGEMM 0 + +#if defined(_SX) +/* TODO: _SX should also define USE_CBLAS in case the later is available */ +extern "C" { +#include "cblas.h" // CHECK: does SX also have a fortran API sgemm? +} + +#elif defined(USE_CBLAS) +#include "cblas.h" // Maybe a system/cmake cblas works for you? +#else +/* put the stubs to make a code compilable but not workable */ +#define cblas_sgemm(...) assert(!"CBLAS is unavailable") +#endif /* defined(_SX) */ + +#endif /* defined(USE_MKL) */ + +namespace mkldnn { +namespace impl { +namespace cpu { + +#if defined(USE_MKL) && defined(USE_CBLAS) +typedef MKL_INT cblas_int; + +#elif defined(USE_CBLAS) +typedef int cblas_int; + +#if defined(_SX) +/* this cblas.h is peculiar... */ +typedef CBLAS_ORDER CBLAS_LAYOUT; +#endif +#endif + +} +} +} + +#endif /* OS_BLAS_HPP */ + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/common.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/common.hpp new file mode 100644 index 000000000000..dde72f4a1794 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/common.hpp @@ -0,0 +1,206 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef COMMON_H +#define COMMON_H + +#define GEMM_CODE_SIZE (4096L * 32) + +#define AVX512_UNROLL_M 48 +#define AVX512_UNROLL_N 8 +#define AVX512_UNROLL_K 1 +#define AVX512_BM 9984 +#define AVX512_BN 384 +#define AVX512_BK 768 +#define AVX512_BK_VNNI 1536 +#define AVX512_BK_TRADITIONAL 384 +#define AVX512_BLOCKING_SMALL_K 48 +#define AVX512_BN_SMALL_K 24 + + +#define PAGESIZE 4096 + +#define PADD_BYTESIZE_ONPAGE(x, size) (((x) * (size) + PAGESIZE - 1) / PAGESIZE) * PAGESIZE +#define NEXT_THR_STRIDE(x, size) (PADD_BYTESIZE_ONPAGE(x, size)) / size + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +enum { + PARTITION_1D_ROW, + PARTITION_1D_COL, + PARTITION_2D_COL_MAJOR, + PARTITION_2D = PARTITION_2D_COL_MAJOR, +}; + +enum { + COPY_NONE, + COPY_A, +}; + +enum { + NO_OFFSET, + FIX_OFFSET, + COL_OFFSET, + ROW_OFFSET, +}; + +// Alias for any dimension related variable. +typedef long long int dim_t; + +typedef struct { + // Interface arguments. + int transa, transb, offsetc; + dim_t m, n, k; + dim_t lda, ldb, ldc; + const int8_t *a; + const uint8_t *b; + int32_t *c; + const float *alpha, *beta; + + int8_t ao, bo; + const int32_t *co; + + // Kernel parameters. + dim_t um, un, uk, bm, bn, bk; + dim_t bn_small_k, bk_traditional, blocking_small_k; + + int (*copyA)(const dim_t *m, const dim_t *n, const int8_t *a, + const dim_t *lda, const int8_t *alpha, int8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + int (*copyB)(const dim_t *m, const dim_t *n, const uint8_t *a, + const dim_t *lda, const uint8_t *alpha, uint8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + int (*kernel)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_b)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_r)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_c)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_b0)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_b0_b)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_b0_r)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + int (*kernel_b0_c)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + // Gemv kernels + void (*gemv_s8u8s32_kernel)(const dim_t, const dim_t, const float, + const int8_t*, const dim_t, const uint8_t*, + const float, int32_t*); + + void (*gemv_u8s8s32_kernel)(const dim_t, const dim_t, const float, + const uint8_t*, const dim_t, const int8_t*, + const float, int32_t*); + + // Gemv parameters + int swap; + +} blas_t; + + +class jit_avx512_core_u8_copy_an_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_an_kern); + + public: + jit_avx512_core_u8_copy_an_kern(); +}; + +class jit_avx512_core_u8_copy_at_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_at_kern); + + public: + jit_avx512_core_u8_copy_at_kern(); +}; + +class jit_avx512_core_u8_copy_bn_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_bn_kern); + + public: + jit_avx512_core_u8_copy_bn_kern(); +}; + +class jit_avx512_core_u8_copy_bt_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_bt_kern); + + public: + jit_avx512_core_u8_copy_bt_kern(); +}; + +class jit_avx512_core_u8_copy_sum_an_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_sum_an_kern); + + public: + jit_avx512_core_u8_copy_sum_an_kern(); +}; + +class jit_avx512_core_u8_copy_sum_at_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_sum_at_kern); + + public: + jit_avx512_core_u8_copy_sum_at_kern(); +}; + +class jit_avx512_core_u8_copy_sum_bn_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_sum_bn_kern); + + public: + jit_avx512_core_u8_copy_sum_bn_kern(); +}; + +class jit_avx512_core_u8_copy_sum_bt_kern : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8_copy_sum_bt_kern); + + public: + jit_avx512_core_u8_copy_sum_bt_kern(); +}; + +} +} +} +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/gemv.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/gemv.hpp new file mode 100644 index 000000000000..db9dd9ef97a4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/gemv.hpp @@ -0,0 +1,28 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +int gemm_s8u8s32_jump_to_gemv_s8u8s32(blas_t *arg); +int gemv_threading_driver(blas_t *arg); + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.cpp new file mode 100644 index 000000000000..e4b8e1cde231 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.cpp @@ -0,0 +1,1409 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "common.hpp" +#include "mkldnn_types.h" +#include "nstl.hpp" +#include "utils.hpp" + +#include "jit_avx512_core_gemm_s8u8s32.hpp" +#include "jit_avx512_core_gemm_s8u8s32_kern.hpp" +#include "jit_avx512_core_kernel_gemv_s8u8s32_kern.hpp" +#include "gemv.hpp" + +#if defined(_MSC_VER) +#include +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { + +typedef struct { + int nthrs_m, nthrs_n; + int partition; + int copy_type; +} blas_thread_t; + +static inline void round_to_nearest(int32_t *rounded_val, double fp_val) { + if (fp_val >= 0.) { + fp_val += 0.5; + if (fp_val > INT32_MAX) { + fp_val = INT32_MAX; + } + } else { + fp_val -= 0.5; + if (fp_val < INT32_MIN) { + fp_val = INT32_MIN; + } + } + *rounded_val = (int32_t) fp_val; +} + +static inline void add_results(const dim_t m, const dim_t n, const dim_t k, + const float alpha, const float beta, const int32_t *c_partial_sum, + const dim_t ldcp, int32_t *c_data, const dim_t ldc, + const int32_t *a_row_sum, const int32_t *b_col_sum, const int8_t ao, + const int8_t bo, const int32_t *co, const int offsetc) +{ + for (dim_t j = 0; j < n; ++j) { + for (dim_t i = 0; i < m; ++i) { + int32_t ctemp = c_partial_sum[i + j * ldcp]; + + if (alpha == 1.0f) { + if (beta == 0.0f) { + c_data[i + j * ldc] = ctemp; + } else { + double c_float = (double) beta + * (double) c_data[i + j * ldc]; + c_float += (double) ctemp; + round_to_nearest(&c_data[i + j * ldc], c_float); + } + } else if (alpha == -1.0f) { + if (beta == 0.0f) { + c_data[i + j * ldc] = -ctemp; + } else { + double c_float = (double) beta + * (double) c_data[i + j * ldc]; + c_float -= (double) ctemp; + round_to_nearest(&c_data[i + j * ldc], c_float); + } + } else { + if (beta == 0.0f) { + double c_float = alpha * (double) ctemp; + round_to_nearest(&c_data[i + j * ldc], c_float); + } else { + double c_float = alpha * (double) ctemp + + beta * (double) c_data[i + j * ldc]; + round_to_nearest(&c_data[i + j * ldc], c_float); + } + } + + if (offsetc == FIX_OFFSET) { + c_data[i + j * ldc] += co[0]; + } else if (offsetc == ROW_OFFSET) { + c_data[i + j * ldc] += co[j]; + } else if (offsetc == COL_OFFSET) { + c_data[i + j * ldc] += co[i]; + } + } + } +} + +// TODO Find a better place for those functions. +static inline dim_t ld_padd(const dim_t x) +{ + return ((x + ((2048 / sizeof(int32_t)) - 1)) / (2048 / sizeof(int32_t))) + * (2048 / sizeof(int32_t)) + (64 / sizeof(int32_t)); +} + +void igemm_inner_kernel(const dim_t m, const dim_t n, const dim_t k, + const int8_t *a, const uint8_t *b, float beta, int32_t *c, + const dim_t ldc, const int32_t *a_row_sum, const int32_t *b_col_sum, + const int32_t *co, const int offsetc, const blas_t *arg) +{ + int8_t ao = arg->ao; + int8_t bo = arg->bo; + int32_t co_0 = (offsetc == NO_OFFSET)? 0 : co[0]; + + // Since m and n are limited by blocking, stack overflow may not happen; + // it's up to 32kB +#if !defined(_MSC_VER) + int32_t col_offset[m]; + int32_t row_offset[n]; +#else + int32_t *col_offset = (int32_t *) _alloca(sizeof(*col_offset) * m); + int32_t *row_offset = (int32_t *) _alloca(sizeof(*row_offset) * n); +#endif + + int col_req = 0; + int row_req = 0; + + if ((bo != 0) || (offsetc == COL_OFFSET)) + col_req = 1; + if ((ao != 0) || (offsetc == ROW_OFFSET)) + row_req = 1; + + // It needs one of colum or row offsets, but it doesn't need both + if (((ao != 0) && (bo != 0)) || ((offsetc == FIX_OFFSET) && (co_0 != 0))) { + if ((col_req == 0) && (row_req == 0)) { + if (m <= n) { + col_req = 1; + } else { + row_req = 1; + } + } + } + + if (col_req) { + for (dim_t i = 0; i < m; i++) + col_offset[i] = 0; + + if (offsetc == COL_OFFSET) { + for (dim_t i = 0; i < m; i++) + col_offset[i] += co[i]; + } + + if (bo != 0) { + for (dim_t i = 0; i < m; i++) + col_offset[i] += bo * a_row_sum[i]; + } + } + + if (row_req) { + for (dim_t i = 0; i < n; i++) + row_offset[i] = 0; + + if (offsetc == ROW_OFFSET) { + for (dim_t i = 0; i < n; i++) + row_offset[i] += co[i]; + } + + if (ao != 0) { + for (dim_t i = 0; i < n; i++) + row_offset[i] += ao * b_col_sum[i]; + } + } + + if ((offsetc == FIX_OFFSET) && (co_0 != 0)) { + if (col_req) { + for (dim_t i = 0; i < m; i++) + col_offset[i] += co_0; + } else { + for (dim_t i = 0; i < n; i++) + row_offset[i] += co_0; + } + } + + if ((ao != 0) && (bo != 0)) { + if (col_req) { + for (dim_t i = 0; i < m; i++) + col_offset[i] += (int32_t) k * ao * bo; + } else { + for (dim_t i = 0; i < n; i++) + row_offset[i] += (int32_t) k * ao * bo; + } + } + + if (col_req == 0) { + if (row_req == 0) { + if (beta == 0.0) { + arg->kernel_b0(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } else { + arg->kernel(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } + } else { + if (beta == 0.0) { + arg->kernel_b0_r(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } else { + arg->kernel_r(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } + } + } else { + if (row_req == 0) { + if (beta == 0.0) { + arg->kernel_b0_c(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } else { + arg->kernel_c(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } + } else { + if (beta == 0.0) { + arg->kernel_b0_b(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } else { + arg->kernel_b(&m, &n, &k, NULL, a, b, c, ldc, col_offset, + row_offset); + } + } + } +} + +static inline void *align(void *ptr, size_t alignment) +{ + return (void *) utils::rnd_up((uintptr_t) ptr, alignment); +} + +static int gemm_kernel_driver(const dim_t m, const dim_t n, const dim_t k, + const int8_t *a, const uint8_t *b, int32_t *c, const int32_t *co, + const blas_t *arg) +{ + dim_t lda = arg->lda; + dim_t ldb = arg->ldb; + dim_t ldc = arg->ldc; + int8_t ao = arg->ao; + int8_t bo = arg->bo; + float alpha = *arg->alpha; + float beta = *arg->beta; + + if (m <= 0 || n <= 0) { + return 0; + } + + // Padding along K dimension. + dim_t k_padd = 0; + if (k <= arg->bk_traditional) { + k_padd = utils::rnd_up(k, arg->uk); + k_padd = nstl::max(128LL, k_padd); + } else if (k < 2 * arg->bk) { + k_padd = utils::rnd_up(k / 2, arg->uk); + } else { + k_padd = arg->bk; + } + + // Padding along M dimension. + dim_t m_padd = utils::rnd_up(nstl::min(nstl::max(m, arg->um), arg->bm), + arg->um); + + // Padding along N dimension. + dim_t n_padd = 0; + if (k < arg->blocking_small_k) { + n_padd = utils::rnd_up(nstl::min(nstl::max(n, arg->un), + arg->bn_small_k), arg->un); + } else { + n_padd = utils::rnd_up(nstl::min(nstl::max(n, arg->un), arg->bn), + arg->un); + } + + // Padding for temporary buffer for C + dim_t ldc_buf = ld_padd(m_padd); + + dim_t strideAm = (arg->transa == 0)? 1 : lda; + dim_t strideAn = (arg->transa != 0)? 1 : lda; + dim_t strideBm = (arg->transb == 0)? 1 : ldb; + dim_t strideBn = (arg->transb != 0)? 1 : ldb; + + size_t a_buf_nelems = m_padd * k_padd; + size_t b_buf_nelems = k_padd * n_padd; + size_t a_row_sum_nelems = m_padd; + size_t b_col_sum_nelems = n_padd; + + size_t mem_size = a_buf_nelems * sizeof(*a) + PAGE_4K + + b_buf_nelems * sizeof(*b) + PAGE_4K + + a_row_sum_nelems * sizeof(*c) + PAGE_4K + + b_col_sum_nelems * sizeof(*c) + PAGE_4K; + + bool need_c_buffer = alpha != 1.0f || (beta != 1 && beta != 0); + if (need_c_buffer) { + size_t c_buf_nelems = ldc_buf * n_padd; + mem_size += c_buf_nelems * sizeof(*c) + PAGE_4K; + } + + char *mem = (char *) malloc(mem_size, 128); + + if (!mem) { + return -1; + } + + int8_t *bufferA = (int8_t *) align(mem, PAGE_4K); + uint8_t *bufferB = (uint8_t *) align(bufferA + a_buf_nelems, PAGE_4K); + int32_t *a_row_sum = (int32_t *) align(bufferB + b_buf_nelems, PAGE_4K); + int32_t *b_col_sum = (int32_t *) align(a_row_sum + a_row_sum_nelems, + PAGE_4K); + + int32_t *bufferC = NULL; + if (need_c_buffer) { + bufferC = (int32_t *) align(b_col_sum + b_col_sum_nelems, PAGE_4K); + } + + float beta_saved = beta; + + int a_block_copied = 0; + dim_t sizeM = 0; + for (dim_t Bm = 0; Bm < m; Bm += sizeM) { + sizeM = m - Bm; + if (sizeM > m_padd) + sizeM = m_padd; + + dim_t sizeK = 0; + for (dim_t Bk = 0; Bk < k; Bk += sizeK) { + sizeK = k - Bk; + if (sizeK > k_padd) + sizeK = k_padd; + + // Scale C blocks by beta only for the first time + if (Bk == 0) + beta = beta_saved; + else + beta = 1.0f; + + // Apply C offset when to the last k-block of the partial sum. + int offsetc = NO_OFFSET; + if (Bk + sizeK == k) + offsetc = arg->offsetc; + + dim_t sizeN = 0; + for (dim_t Bn = 0; Bn < n; Bn += sizeN) { + sizeN = n - Bn; + if (sizeN > n_padd) + sizeN = n_padd; + + const uint8_t *b_block = b + Bk * strideBm + Bn * strideBn; + arg->copyB(&sizeK, &sizeN, b_block, &ldb, NULL, bufferB, NULL, + NULL, b_col_sum); + + dim_t sizeUM = 0; + for (dim_t Um = 0; Um < sizeM; Um += sizeUM) { + sizeUM = sizeM - Um; + if (sizeUM > arg->um) + sizeUM = arg->um; + + /* + * Use the whole A buffer only if we have multiple B blocks + * for k-dimension, otherwise we are wasting cache to store + * B and C blocks. + */ + dim_t Um_forA = 0; + if (sizeN < n) + Um_forA = Um; + + const int8_t *a_block = a + (Bm + Um) * strideAm + + Bk * strideAn; + if (!a_block_copied) { + arg->copyA(&sizeK, &sizeUM, a_block, &lda, NULL, + bufferA + Um_forA * sizeK, NULL, NULL, + a_row_sum + Um_forA); + } + + int32_t *c_block = c + (Bm + Um) + Bn * ldc; + dim_t co_stride = 0; + if (offsetc == FIX_OFFSET) { + co_stride = 0; + } else if (offsetc == ROW_OFFSET) { + co_stride = Bn; + } else if (offsetc == COL_OFFSET) { + co_stride = Bm + Um; + } + if (need_c_buffer) { + igemm_inner_kernel(sizeUM, sizeN, sizeK, + bufferA + Um_forA * sizeK, bufferB, 0.0f, + bufferC + Um, ldc_buf, a_row_sum + Um_forA, + b_col_sum, NULL, NO_OFFSET, arg); + + // Finish the block adding the necessary alpha, beta + // and offsets. + add_results(sizeUM, sizeN, sizeK, alpha, beta, + bufferC + Um, ldc_buf, c_block, ldc, + a_row_sum + Um_forA, b_col_sum, ao, bo, + co + co_stride, offsetc); + } else { + igemm_inner_kernel(sizeUM, sizeN, sizeK, + bufferA + Um_forA * sizeK, bufferB, beta, + c_block, ldc, a_row_sum + Um_forA, b_col_sum, + co + co_stride, offsetc, arg); + } + } + a_block_copied = 1; + } + a_block_copied = 0; + } + } + + free(mem); + + return 0; +} + +static int kernel_driver_parallel_acopiedbcopy(const dim_t m, const dim_t n, + const dim_t k, const int8_t *bufferA, const uint8_t *b, + const float beta, int32_t *c, const int offsetc, const int32_t *co, + const int32_t *a_row_sum, const blas_t *arg) +{ + dim_t ldb = arg->ldb; + dim_t ldc = arg->ldc; + int8_t ao = arg->ao; + int8_t bo = arg->bo; + float alpha = *arg->alpha; + + if (m <= 0 || n <= 0) { + return 0; + } + + // Padding along N dimension. + dim_t n_padd = 0; + if (k < arg->blocking_small_k) { + n_padd = utils::rnd_up(nstl::min(nstl::max(n, arg->un), + arg->bn_small_k), arg->un); + } else { + n_padd = utils::rnd_up(nstl::min(nstl::max(n, arg->un), arg->bn), + arg->un); + } + + // Padding for temporary buffer for C + dim_t ldc_buf = ld_padd(m); + + dim_t strideBn = (arg->transb != 0)? 1 : ldb; + + size_t b_buf_nelems = k * n_padd; + size_t b_col_sum_nelems = n_padd; + + size_t mem_size = b_buf_nelems * sizeof(*b) + PAGE_4K + + b_col_sum_nelems * sizeof(*c) + PAGE_4K; + + bool need_c_buffer = alpha != 1.0f || (beta != 1 && beta != 0); + if (need_c_buffer) { + size_t c_buf_nelems = ldc_buf * n_padd; + mem_size += c_buf_nelems * sizeof(*c) + PAGE_4K; + } + + char *mem = (char *) malloc(mem_size, 128); + + if (!mem) { + return -1; + } + + uint8_t *bufferB = (uint8_t *) align(mem, PAGE_4K); + int32_t *b_col_sum = (int32_t *) align(bufferB + b_buf_nelems, PAGE_4K); + + int32_t *bufferC = NULL; + if (need_c_buffer) { + bufferC = (int32_t *) align(b_col_sum + b_col_sum_nelems, PAGE_4K); + } + + dim_t sizeN = 0; + for (dim_t Bn = 0; Bn < n; Bn += sizeN) { + sizeN = n - Bn; + if (sizeN > n_padd) + sizeN = n_padd; + + // Implement the kernel here. + const uint8_t *b_block = b + Bn * strideBn; + arg->copyB(&k, &sizeN, b_block, &ldb, NULL, bufferB, NULL, NULL, + b_col_sum); + + dim_t co_stride = 0; + if (offsetc == FIX_OFFSET) { + co_stride = 0; + } else if (offsetc == ROW_OFFSET) { + co_stride = Bn; + } else if (offsetc == COL_OFFSET) { + co_stride = 0; + } + int32_t *c_block = c + Bn * ldc; + if (need_c_buffer) { + igemm_inner_kernel(m, sizeN, k, bufferA, bufferB, 0.0f, bufferC, + ldc_buf, a_row_sum, b_col_sum, NULL, NO_OFFSET, arg); + + // Finish the block adding the necessary alpha, beta and offsets. + add_results(m, sizeN, k, alpha, beta, bufferC, ldc_buf, c_block, + ldc, a_row_sum, b_col_sum, ao, bo, co + co_stride, + offsetc); + } else { + igemm_inner_kernel(m, sizeN, k, bufferA, bufferB, beta, c_block, + ldc, a_row_sum, b_col_sum, co + co_stride, offsetc, arg); + } + } + + free(mem); + + return 0; + +} + +#define N2D_MAX_AVX512 384 +#define M2D_MIN_AVX512 384 +#define VECLEN 16 +#define NCONS 1 +static inline void set_thread_opts_avx512(int *p_nthrs, + blas_thread_t *thread_info, const blas_t *arg) +{ + int nthrs = *p_nthrs; + dim_t m = arg->m; + dim_t n = arg->n; + + thread_info->nthrs_m = 0; + thread_info->nthrs_n = 0; + thread_info->copy_type = COPY_NONE; // By default don't do parallel copy. + + int condition_2D_bsrc = -1; + if ((256 * m > nthrs * n) && (nthrs * m < 256 * n)) { + condition_2D_bsrc = 1; + } else { + condition_2D_bsrc = 0; + } + + int condition_1D_copya = 0; + if ((m >= 1000) && (n >= nthrs * N2D_MAX_AVX512 / 4)) { + condition_2D_bsrc = 0; + condition_1D_copya = 1; + } + + // If offset is non-zero, we need to keep 1D_copya to reduce update overhead + if (arg->ao != 0 || arg->bo != 0 || arg->co[0] != 0 + || arg->offsetc != FIX_OFFSET) { + condition_2D_bsrc = 0; + condition_1D_copya = 1; + } + + if (condition_2D_bsrc == 1) { + int nthrs_m = 1; + int nthrs_n = nthrs; + + while ((nthrs_n % 2 == 0) && + (n / nthrs > N2D_MAX_AVX512 || + n / nthrs_n <= N2D_MAX_AVX512 / 2) && + (m / nthrs_m >= 2 * M2D_MIN_AVX512) && + (nthrs_m < 4)) { + nthrs_m *= 2; + nthrs_n /= 2; + } + + thread_info->nthrs_m = nthrs_m; + thread_info->nthrs_n = nthrs_n; + thread_info->partition = PARTITION_2D; + + // Reset the total number of threads that will be used. + *p_nthrs = nthrs_m * nthrs_n; + + } else if (condition_1D_copya && mkldnn_thr_syncable()) { + // Use parallel copy A algorithm + thread_info->copy_type = COPY_A; + thread_info->partition = PARTITION_1D_COL; + } else { + if ((m > n) && (m / nthrs >= VECLEN || n < NCONS * nthrs)) { + thread_info->partition = PARTITION_1D_ROW; + } else { + thread_info->partition = PARTITION_1D_COL; + } + } +} +#undef N2D_MAX_AVX512 +#undef M2D_MIN_AVX512 +#undef VECLEN +#undef NCONS + +static inline void partition_1d(const int ithr, const int nthrs, const dim_t n, + dim_t *t_offset, dim_t *t_block) +{ + dim_t band = n / nthrs; + + dim_t tail = n - (nthrs - 1) * band; + if (tail > (band + 1)) + band++; + tail = n - (nthrs - 1) * band; + + if (ithr < (nthrs - 1)) + *t_block = band; + else + *t_block = tail; + + *t_offset = ithr * band; + + if (*t_offset >= n) { + *t_block = 0; + *t_offset = 0; + } else if ((*t_offset + *t_block) > n) { + *t_block = n - *t_offset; + } +} + +static inline void partition_2d(const int ithr, int *nthrs, const int ithr_i, + const int ithr_j, const int nthrs_m, const int nthrs_n, const dim_t m, + const dim_t n, dim_t *p_m_disp, dim_t *p_m_band, dim_t *p_n_disp, + dim_t *p_n_band) +{ + dim_t m_disp = 0, n_disp = 0; + dim_t m_band = 0, n_band = 0; + + int mdiv = nthrs_m; + int ndiv = nthrs_n; + + dim_t m_bandt = m / mdiv; /* size per thread */ + dim_t n_bandt = n / ndiv; /* size per thread */ + int firstmgroup = mdiv - 1; + int firstngroup = ndiv - 1; + dim_t firstmval = m_bandt; + dim_t firstnval = n_bandt; + + int mthr_used = mdiv; + if (m - (mdiv - 1) * m_bandt > m_bandt + 1) { + if (m - (mdiv - 1) * m_bandt > mdiv) + ++m_bandt; + + firstmval = m_bandt + 1; + mthr_used = (int) (m / firstmval); + + if (mthr_used * firstmval < m) + ++mthr_used; + + firstmgroup = mthr_used - 1; + } + + int nthr_used = ndiv; + if (n - (ndiv - 1) * n_bandt > n_bandt + 1) { + firstnval = n_bandt + 1; + nthr_used = (int) (n / firstnval); + + if (nthr_used * firstnval < n) + ++nthr_used; + + firstngroup = nthr_used - 1; + } + + *nthrs = mthr_used * nthr_used; + + if (ithr < *nthrs) { + if (ithr_i < firstmgroup) { + m_band = firstmval; + m_disp = ithr_i * firstmval; + } else if (ithr_i <= mthr_used - 2) { + m_band = m_bandt; + m_disp = firstmgroup * firstmval + (ithr_i - firstmgroup) * m_bandt; + } else { + m_disp = firstmgroup * firstmval + + (mthr_used - 1 - firstmgroup) * m_bandt; + m_band = nstl::max(0LL, m - m_disp); + } + + if (ithr_j < firstngroup) { + n_band = firstnval; + n_disp = ithr_j * firstnval; + } else if (ithr_j <= nthr_used - 2) { + n_band = n_bandt; + n_disp = firstngroup * firstnval + (ithr_j - firstngroup) * n_bandt; + } else { + n_disp = firstngroup * firstnval + + (nthr_used - 1 - firstngroup) * n_bandt; + n_band = nstl::max(0LL, n - n_disp); + } + m_disp = nstl::max(nstl::min(m_disp, m - 1), 0LL); + n_disp = nstl::max(nstl::min(n_disp, n - 1), 0LL); + } + + if (ithr < *nthrs) { + *p_m_disp = m_disp; + *p_n_disp = n_disp; + *p_m_band = m_band; + *p_n_band = n_band; + } else { + *p_m_disp = 0; + *p_n_disp = 0; + *p_m_band = 0; + *p_n_band = 0; + } + + return; +} + +static inline void decompose_matrices(const int ithr, int *nthrs, dim_t *m, + dim_t *n, dim_t *k, const int8_t **a, const uint8_t **b, int32_t **c, + const int32_t **co, const blas_thread_t *thread_info, const blas_t *arg) +{ + dim_t strideAm = (arg->transa == 0)? 1 : arg->lda; + dim_t strideBn = (arg->transb != 0)? 1 : arg->ldb; + int offsetc = arg->offsetc; + + switch (thread_info->partition) { + case PARTITION_1D_ROW: + { + dim_t offset = 0; + dim_t block = 0; + partition_1d(ithr, *nthrs, arg->m, &offset, &block); + + *m = block; + *n = arg->n; + *k = arg->k; + + // Set matrix A. + *a = arg->a + offset * strideAm; + + // Set matrix B. + *b = arg->b; + + // Set matrix C. + *c = arg->c + offset; + + // Set offset vector for C matrix + dim_t co_stride = 0; + if (offsetc == FIX_OFFSET) { + co_stride = 0; + } else if (offsetc == ROW_OFFSET) { + co_stride = 0; + } else if (offsetc == COL_OFFSET) { + co_stride = offset; + } + *co = arg->co + co_stride; + break; + } + + case PARTITION_1D_COL: + { + dim_t offset = 0; + dim_t block = 0; + partition_1d(ithr, *nthrs, arg->n, &offset, &block); + + *m = arg->m; + *n = block; + *k = arg->k; + + // Set matrix A. + *a = arg->a; + + // Set matrix B. + *b = arg->b + offset * strideBn; + + // Set matrix C. + *c = arg->c + offset * arg->ldc; + + // Set offset vector for C matrix + dim_t co_stride = 0; + if (offsetc == FIX_OFFSET) { + co_stride = 0; + } else if (offsetc == ROW_OFFSET) { + co_stride = offset; + } else if (offsetc == COL_OFFSET) { + co_stride = 0; + } + *co = arg->co + co_stride; + break; + } + + case PARTITION_2D_COL_MAJOR: + { + int nthrs_m = thread_info->nthrs_m; + int nthrs_n = thread_info->nthrs_n; + int ithr_i = ithr % nthrs_m; + int ithr_j = ithr / nthrs_m; + + dim_t m_disp = 0; + dim_t m_band = 0; + dim_t n_disp = 0; + dim_t n_band = 0; + + partition_2d(ithr, nthrs, ithr_i, ithr_j, nthrs_m, nthrs_n, + arg->m, arg->n, &m_disp, &m_band, &n_disp, &n_band); + + *m = m_band; + *n = n_band; + *k = arg->k; + + // Set matrix A. + *a = arg->a + m_disp * strideAm; + + // Set matrix B. + *b = arg->b + n_disp * strideBn; + + // Set matrix C. + *c = arg->c + m_disp + n_disp * arg->ldc; + + // Set offset vector for C matrix + dim_t co_stride = 0; + if (offsetc == FIX_OFFSET) { + co_stride = 0; + } else if (offsetc == ROW_OFFSET) { + co_stride = n_disp; + } else if (offsetc == COL_OFFSET) { + co_stride = m_disp; + } + *co = arg->co + co_stride; + break; + } + } +} + +#define MULTIPLIER 10 +static int parallel_a_copy(const int ithr, const int nthrs, const dim_t m, + const dim_t n, const dim_t k, const int8_t *a, const uint8_t *b, + int32_t *c, const int32_t *co, const blas_t *arg, + char **p_shared_mem) +{ + const dim_t lda = arg->lda; + const dim_t ldb = arg->ldb; + const dim_t strideAm = (arg->transa == 0)? 1 : lda; + const dim_t strideAn = (arg->transa != 0)? 1 : lda; + const dim_t strideBm = (arg->transb == 0)? 1 : ldb; + + // Padding along M dimension. + dim_t m_padd = utils::rnd_up(nstl::min(nstl::max(m, arg->um), arg->bm), + arg->um); + + // Padding along K dimension. + dim_t k_padd = 0; + if (k <= arg->bk_traditional) { + k_padd = utils::rnd_up(k, arg->uk); + k_padd = nstl::max(128LL, k_padd); + } else if (k < 2 * arg->bk) { + k_padd = utils::rnd_up(k / 2, arg->uk); + } else { + k_padd = arg->bk; + } + + m_padd *= nthrs > MULTIPLIER ? MULTIPLIER : nthrs; + if (m_padd > m) { + m_padd = utils::rnd_up(m, arg->um); + } + + size_t a_buf_nelems = m_padd * k_padd; + + // Allocate shared memory for A and its row sum buffers in master thread. + if (ithr == 0) { // If thread master + size_t a_row_sum_nelems = m_padd; + + size_t mem_size = (a_buf_nelems * sizeof(*a) + PAGE_4K) + + a_row_sum_nelems * sizeof(*c) + PAGE_4K; + + *p_shared_mem = (char *) malloc(mem_size, 128); + + } + mkldnn_thr_barrier(); + + char *mem = *p_shared_mem; + int8_t *bufferA = (int8_t *) align(mem, PAGE_4K); + int32_t *a_row_sum = (int32_t *) align(bufferA + a_buf_nelems, PAGE_4K); + + if (!mem) { + return -1; + } + + int result = 0; // Return status + + dim_t sizeK = 0; + for (dim_t Bk = 0; Bk < k; Bk += sizeK) { + sizeK = k - Bk; + if (sizeK > k_padd) + sizeK = k_padd; + + // Scale C blocks by beta only for the first term of partial sum. + float beta = 1.0f; + if (Bk == 0) + beta = *(arg->beta); + + // Apply C offset for the last k-block of the partial sum. + int offsetc = NO_OFFSET; + if (Bk + sizeK == k) + offsetc = arg->offsetc; + + dim_t sizeM = 0; + for (dim_t Bm = 0; Bm < m; Bm += sizeM) { + sizeM = m - Bm; + if (sizeM > m_padd) + sizeM = m_padd; + + if (ithr < nthrs) { + dim_t band = (sizeM + nthrs - 1) / nthrs; + band = utils::rnd_up(band, arg->um); + + dim_t offset = band * ithr; + + // If offset is too large don't use that thread for copying. + if (offset >= sizeM) { + offset = 0; + band = 0; + } + + // Handle the tail of the copy. + if (offset + band > sizeM) { + band = sizeM - offset; + } + + if (band > 0) { + const int8_t *a_block = a + (Bm + offset) * strideAm + + Bk * strideAn; + arg->copyA(&sizeK, &band, a_block, &lda, NULL, + bufferA + offset * sizeK, NULL, NULL, + a_row_sum + offset); + } + } + mkldnn_thr_barrier(); // Wait for finishing parallel copy. + + const uint8_t *b_block = b + Bk * strideBm; + int32_t *c_block = c + Bm; + dim_t co_stride = 0; + if (offsetc == FIX_OFFSET) { + co_stride = 0; + } else if (offsetc == ROW_OFFSET) { + co_stride = 0; + } else if (offsetc == COL_OFFSET) { + co_stride = Bm; + } + + result = kernel_driver_parallel_acopiedbcopy(sizeM, n, sizeK, + bufferA, b_block, beta, c_block, offsetc, co + co_stride, + a_row_sum, arg); + + mkldnn_thr_barrier(); // Wait for kernel computations to finish. + } + } + + // Free memory allocated in master thread + if (ithr == 0) { + free(mem); + } + + return result; +} +#undef MULTIPLIER + +static inline void get_omp_thread_count(dim_t m, dim_t n, dim_t k, + double fp_per_cycle, int *nthrs) +{ + double omp_overhead_small_core = 3.0e+3; + double omp_intercept_big_core = 4.0e+3; + double omp_slope_big_core = 5.0e+2; + + double gemm_cycles = 8.0 * m * n * k / fp_per_cycle; + + int i = *nthrs; + + // Use a different model for omp overheads if nthrs is <= 4 + if (*nthrs <= 4 && omp_overhead_small_core > 0) { + double omp_cycles = omp_overhead_small_core; + if (gemm_cycles < omp_cycles) { + *nthrs = 1; + return; + } else { + while (i > 1) { + if (omp_cycles * i < gemm_cycles * (i - 1)) break; + --i; + } + } + } else { + if (gemm_cycles < (omp_intercept_big_core + 2 * omp_slope_big_core)) { + *nthrs = 1; + return; + } + + // adaptive decrement to march faster· + while (i > 1) { + double omp_cycles = omp_intercept_big_core + i * omp_slope_big_core; + if (omp_cycles * i < gemm_cycles * (i - 1)) + break; + + if (i < 10) + i -= 2; + else if (i < 30) + i -= 4; + else + i -= 8; + } + } + + if (i < 1) + i = 1; + + *nthrs = i; +} + +#define CACHE_LINE_SIZE 64 +static int gemm_threading_driver(blas_t *arg) +{ + if ((arg->m <= 0) || (arg->n <= 0)) + return mkldnn_success; + + if (gemm_s8u8s32_jump_to_gemv_s8u8s32(arg)) { + return mkldnn_success; + } + + int nthr = (mkldnn_in_parallel()) ? 1 : mkldnn_get_max_threads(); + get_omp_thread_count(arg->m, arg->n, arg->k, 64.0, &nthr); + + if (nthr == 1) { + return gemm_kernel_driver(arg->m, arg->n, arg->k, arg->a, arg->b, + arg->c, arg->co, arg); + } + + int *results = (int *) malloc(sizeof(*results) * nthr * CACHE_LINE_SIZE, + PAGE_4K); + + if (!results) { + return -1; + } + + for (int i = 0; i < nthr; i++) { + results[i * CACHE_LINE_SIZE] = 0; // Initialize to success + } + + char *shared_mem = NULL; + + parallel(nthr, [&](const int ithr, const int nthr) { + int nthrs = nthr; + if (nthrs == 1) { + results[0] = gemm_kernel_driver(arg->m, arg->n, arg->k, arg->a, + arg->b, arg->c, arg->co, arg); + } else { + blas_thread_t thread_info; + set_thread_opts_avx512(&nthrs, &thread_info, arg); + + const int8_t *a = NULL; + const uint8_t *b = NULL; + int32_t *c = NULL; + const int32_t *co = NULL; + dim_t m = -1; + dim_t n = -1; + dim_t k = -1; + decompose_matrices(ithr, &nthrs, &m, &n, &k, &a, &b, &c, &co, + &thread_info, arg); + + if (ithr < nthrs) { + switch (thread_info.copy_type) { + case COPY_A: + results[ithr * CACHE_LINE_SIZE] = + parallel_a_copy(ithr, nthrs, m, n, k, a, b, c, co, arg, + &shared_mem); + break; + + default: + case COPY_NONE: + results[ithr * CACHE_LINE_SIZE] = + gemm_kernel_driver(m, n, k, a, b, c, co, arg); + break; + } + } + } + }); + + int result = 0; // Initialize to success + for (int i = 0; i < nthr; i++) { + if (results[i] != 0) { + result = results[i * CACHE_LINE_SIZE]; + break; + } + } + + free(results); + + return result; +} +#undef CACHE_LINE_SIZE + +static jit_avx512_core_u8_copy_an_kern *copy_an; +static jit_avx512_core_u8_copy_at_kern *copy_at; +static jit_avx512_core_u8_copy_bn_kern *copy_bn; +static jit_avx512_core_u8_copy_bt_kern *copy_bt; +static jit_avx512_core_u8_copy_sum_an_kern *copy_sum_an; +static jit_avx512_core_u8_copy_sum_at_kern *copy_sum_at; +static jit_avx512_core_u8_copy_sum_bn_kern *copy_sum_bn; +static jit_avx512_core_u8_copy_sum_bt_kern *copy_sum_bt; +static jit_avx512_core_gemm_s8u8s32_kern *kernel; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_b; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_r; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_c; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_b0; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_b0_b; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_b0_r; +static jit_avx512_core_gemm_s8u8s32_kern *kernel_b0_c; +static jit_avx512_core_gemv_s8u8s32_kern *gemv_s8u8s32_kernel; +static jit_avx512_core_gemv_s8u8s32_kern *gemv_u8s8s32_kernel; + +static void jit_init(blas_t *arg) +{ + static int (*copyAn)(const dim_t *m, const dim_t *n, const int8_t *a, + const dim_t *lda, const int8_t *alpha, int8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copyAt)(const dim_t *m, const dim_t *n, const int8_t *a, + const dim_t *lda, const int8_t *alpha, int8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copyBn)(const dim_t *m, const dim_t *n, const uint8_t *a, + const dim_t *lda, const uint8_t *alpha, uint8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copyBt)(const dim_t *m, const dim_t *n, const uint8_t *a, + const dim_t *lda, const uint8_t *alpha, uint8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copySumAn)(const dim_t *m, const dim_t *n, const int8_t *a, + const dim_t *lda, const int8_t *alpha, int8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copySumAt)(const dim_t *m, const dim_t *n, const int8_t *a, + const dim_t *lda, const int8_t *alpha, int8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copySumBn)(const dim_t *m, const dim_t *n, const uint8_t *a, + const dim_t *lda, const uint8_t *alpha, uint8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*copySumBt)(const dim_t *m, const dim_t *n, const uint8_t *a, + const dim_t *lda, const uint8_t *alpha, uint8_t *b, + const dim_t *dummy1, const dim_t *dummy2, int32_t *row_col_sum); + + static int (*kern)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_b)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_r)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_c)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_b0)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_b0_b)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_b0_r)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static int (*kern_b0_c)(const dim_t *m, const dim_t *n, const dim_t *k, + const float *alpha, const int8_t *a, const uint8_t *b, int32_t *c, + const dim_t ldc, const int32_t *col_offset, + const int32_t *row_offset); + + static void (*gemv_s8u8s32_kern)(const dim_t, const dim_t, const float, + const int8_t*, const dim_t, const uint8_t*, + const float, int32_t*); + + static void (*gemv_u8s8s32_kern)(const dim_t, const dim_t, const float, + const uint8_t*, const dim_t, const int8_t*, + const float, int32_t*); + + if (mayiuse(avx512_core_vnni)) { + arg->um = AVX512_UNROLL_M; + arg->un = AVX512_UNROLL_N; + arg->uk = AVX512_UNROLL_K; + arg->bm = AVX512_BM; + arg->bn = AVX512_BN; + arg->bk = AVX512_BK_VNNI; + + arg->bk_traditional = AVX512_BK_TRADITIONAL; + arg->bn_small_k = AVX512_BN_SMALL_K; + arg->blocking_small_k = AVX512_BLOCKING_SMALL_K; + } else { + arg->um = AVX512_UNROLL_M; + arg->un = AVX512_UNROLL_N; + arg->uk = AVX512_UNROLL_K; + arg->bm = AVX512_BM; + arg->bn = AVX512_BN; + arg->bk = AVX512_BK; + + arg->bk_traditional = AVX512_BK_TRADITIONAL; + arg->bn_small_k = AVX512_BN_SMALL_K; + arg->blocking_small_k = AVX512_BLOCKING_SMALL_K; + } + + static std::once_flag initialized; + std::call_once(initialized, []{ + + copy_an = new jit_avx512_core_u8_copy_an_kern(); + copy_at = new jit_avx512_core_u8_copy_at_kern(); + copy_bn = new jit_avx512_core_u8_copy_bn_kern(); + copy_bt = new jit_avx512_core_u8_copy_bt_kern(); + + copy_sum_an = new jit_avx512_core_u8_copy_sum_an_kern(); + copy_sum_at = new jit_avx512_core_u8_copy_sum_at_kern(); + copy_sum_bn = new jit_avx512_core_u8_copy_sum_bn_kern(); + copy_sum_bt = new jit_avx512_core_u8_copy_sum_bt_kern(); + + kernel = new jit_avx512_core_gemm_s8u8s32_kern(false, false, false); + kernel_b = new jit_avx512_core_gemm_s8u8s32_kern(false, true, true); + kernel_r = new jit_avx512_core_gemm_s8u8s32_kern(false, false, true); + kernel_c = new jit_avx512_core_gemm_s8u8s32_kern(false, true, false); + kernel_b0 = new jit_avx512_core_gemm_s8u8s32_kern(true, false, false); + kernel_b0_b = new jit_avx512_core_gemm_s8u8s32_kern(true, true, true); + kernel_b0_r = new jit_avx512_core_gemm_s8u8s32_kern(true, false, true); + kernel_b0_c = new jit_avx512_core_gemm_s8u8s32_kern(true, true, false); + + gemv_s8u8s32_kernel = new jit_avx512_core_gemv_s8u8s32_kern(); + gemv_u8s8s32_kernel = new jit_avx512_core_gemv_s8u8s32_kern(); + + + copyAn = copy_an->getCode(); + + copyAt = copy_at->getCode(); + + copyBn = copy_bn->getCode(); + + copyBt = copy_bt->getCode(); + + copySumAn = copy_sum_an->getCode(); + + copySumAt = copy_sum_at->getCode(); + + copySumBn = copy_sum_bn->getCode(); + + copySumBt = copy_sum_bt->getCode(); + + kern = kernel->getCode(); + + kern_b = kernel_b->getCode(); + + kern_r = kernel_r->getCode(); + + kern_c = kernel_c->getCode(); + + kern_b0 = kernel_b0->getCode(); + + kern_b0_b = kernel_b0_b->getCode(); + + kern_b0_r = kernel_b0_r->getCode(); + + kern_b0_c = kernel_b0_c->getCode(); + + gemv_s8u8s32_kern = + gemv_s8u8s32_kernel -> generate + (mayiuse(avx512_core_vnni)); + gemv_u8s8s32_kern = + gemv_u8s8s32_kernel -> generate + (mayiuse(avx512_core_vnni)); + }); + + if (arg->bo == 0) { // No need to compute A row sum if bo is zero + if (arg->transa == 0) { + arg->copyA = copyAn; + } else { + arg->copyA = copyAt; + } + } else { + if (arg->transa == 0) { + arg->copyA = copySumAn; + } else { + arg->copyA = copySumAt; + } + } + + if (arg->ao == 0) { // No need to compute B column sum if ao is zero + if (arg->transb == 0) { + arg->copyB = copyBn; + } else { + arg->copyB = copyBt; + } + } else { + if (arg->transb == 0) { + arg->copyB = copySumBn; + } else { + arg->copyB = copySumBt; + } + } + + arg->kernel = kern; + arg->kernel_b = kern_b; + arg->kernel_r = kern_r; + arg->kernel_c = kern_c; + arg->kernel_b0 = kern_b0; + arg->kernel_b0_b = kern_b0_b; + arg->kernel_b0_r = kern_b0_r; + arg->kernel_b0_c = kern_b0_c; + arg -> gemv_s8u8s32_kernel = gemv_s8u8s32_kern; + arg -> gemv_u8s8s32_kernel = gemv_u8s8s32_kern; +} + +mkldnn_status_t jit_avx512_core_gemm_s8u8s32( + const char *transA, const char *transB, const char *offsetC, + const int *m, const int *n, const int *k, + const float *alpha, const int8_t *a, const int *lda, const int8_t *oa, + const uint8_t *b, const int *ldb, const int8_t *ob, + const float *beta, int32_t *c, const int *ldc, const int32_t *oc) +{ + char transa = *transA; + char transb = *transB; + char offsetc = *offsetC; + + blas_t args; + + // Initialize blas structure + args.m = *m; + args.n = *n; + args.k = *k; + args.alpha = alpha; + args.a = a; + args.lda = *lda; + args.b = b; + args.ldb = *ldb; + args.beta = beta; + args.c = c; + args.ldc = *ldc; + args.transa = (transa == 'N' || transa == 'n') ? 0 : 1; + args.transb = (transb == 'N' || transb == 'n') ? 0 : 1; + args.um = 0; + args.un = 0; + args.bm = 0; + args.bn = 0; + args.bk = 0; + args.copyA = NULL; + args.copyB = NULL; + args.kernel = NULL; + args.kernel_b0 = NULL; + args.ao = *oa; + args.bo = *ob; + args.co = oc; + + if (offsetc == 'F' || offsetc == 'f') { + args.offsetc = FIX_OFFSET; + } else if (offsetc == 'R' || offsetc == 'r') { + args.offsetc = ROW_OFFSET; + } else { // offsetc == 'C' || offsetc == 'c' + args.offsetc = COL_OFFSET; + } + + jit_init(&args); + int result = gemm_threading_driver(&args); + + return (result < 0) ? mkldnn_out_of_memory : mkldnn_success; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.hpp new file mode 100644 index 000000000000..b2e2902a1208 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32.hpp @@ -0,0 +1,38 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_CORE_GEMM_S8U8S32_HPP +#define JIT_AVX512_CORE_GEMM_S8U8S32_HPP + +#include +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +mkldnn_status_t jit_avx512_core_gemm_s8u8s32( + const char *transA, const char *transB, const char *offsetC, + const int *m, const int *n, const int *k, + const float *alpha, const int8_t *a, const int *lda, const int8_t *oa, + const uint8_t *b, const int *ldb, const int8_t *ob, + const float *beta, int32_t *c, const int *ldc, const int32_t *oc); + +} +} +} + +#endif // JIT_AVX512_CORE_GEMM_S8U8S32_HPP diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.cpp new file mode 100644 index 000000000000..57554a185248 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.cpp @@ -0,0 +1,539 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_avx512_core_gemm_s8u8s32_kern.hpp" + + +#ifdef _WIN32 +static const bool is_windows = 1; +#else +static const bool is_windows = 0; +#endif + + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + + + + +// Convert between vector register lengths. +static inline Xmm make_xmm(const Xmm &v) { return Xmm(v.getIdx()); } +static inline Ymm make_ymm(const Xmm &v) { return Ymm(v.getIdx()); } + +// Load from or store to C. +void jit_avx512_core_gemm_s8u8s32_kern::c_load(const Xbyak::Xmm &dst, + const Xbyak::Address &src, int nelems) +{ + switch (nelems) { + default: vmovups(dst, src); break; + case 8: vmovups(make_ymm(dst), src); break; + case 4: vmovups(make_xmm(dst), src); break; + case 2: vmovlps(make_xmm(dst), src); break; + case 1: vmovss(make_xmm(dst), src); break; + } +} +void jit_avx512_core_gemm_s8u8s32_kern::c_store(const Xbyak::Address &dst, + const Xbyak::Xmm &src, int nelems) +{ + switch (nelems) { + default: vmovups(dst, src); break; + case 8: vmovups(dst, make_ymm(src)); break; + case 4: vmovups(dst, make_xmm(src)); break; + case 2: vmovsd(dst, make_xmm(src)); break; + case 1: vmovss(dst, make_xmm(src)); break; + } +} + +// Perform length-4 dot product accumulations of unsigned and signed bytes +// in parallel. +// Use vpdpbusd if VNNI available, otherwise emulate. +void jit_avx512_core_gemm_s8u8s32_kern::dot_product(const Xmm &dst, + const Xmm &src1, const Xmm &src2) +{ + if (vnni) + vpdpbusd(dst, src1, src2); + else { + vpmaddubsw(dp_scratch, src1, src2); + vpmaddwd(dp_scratch, ones, dp_scratch); + vpaddd(dst, dst, dp_scratch); + } +} + +// Inner kernel. +void jit_avx512_core_gemm_s8u8s32_kern::kernel_loop(int unroll_m, int unroll_n, + bool cfetch) +{ + int um_vecs = (unroll_m + 15) >> 4; + Label label_kernel_loop; + + L_aligned(label_kernel_loop); { + for (int h = 0; h < 4; h++) { + for (int j = 0; j < unroll_n; j++) { + const Zmm b = b_regs[j & 1]; + + vpbroadcastd(b, ptr[BO + isize * + (2 * j + 2 * h * unroll_n - offset_b)]); + dot_product(c_regs[0][j], b, a_regs[0]); + + if (j == 1 && !(h & 1)) + prefetch_b(ptr[BO + isize * (prefetch_size_b + + 2 * h * unroll_n - offset_b)]); + else if (j % 3 == 0) + prefetch_a(ptr[AO + isize * (prefetch_size_a + + 32 * (j / 3) + 2 * h * unroll_m - offset_a)]); + + for (int i = 1; i < um_vecs; i++) + dot_product(c_regs[i][j], b, a_regs[i]); + + if (cfetch && (j == std::min(1, unroll_n - 1))) { + if (h == 3) + lea(CO2, ptr[CO2 + LDC]); + else if (h < um_vecs) + prefetch_c(ptr[CO2 + (16 * h * size)]); + } + + if (h == 3 && j == std::min(3, unroll_n - 1)) + lea(AA, ptr[AA + (32 * isize)]); + } + + for (int i = 0; i < um_vecs; i++) + vmovups(a_regs[i], ptr[AO + isize * + (32 * i + 2 * (h + 1) * unroll_m - offset_a)]); + + if (h == 2) + prefetch_x(ptr[AA - (offset_a * isize)]); + } + + add(AO, 8 * isize * unroll_m); + add(BO, 8 * isize * unroll_n); + sub(LoopCount, 1); + jg(label_kernel_loop, T_NEAR); + } +} + +// k remainder loop for kernel. +void jit_avx512_core_gemm_s8u8s32_kern::remainder_kernel(int unroll_m, + int unroll_n, int unroll_k, int bwidth) +{ + if ((unroll_m > IGEMM_UNROLL_M) || (unroll_n > IGEMM_UNROLL_N) + || (unroll_m < 0) || (unroll_n < 0)) + return; + + int um_vecs = (unroll_m + 15) >> 4; + + for (int h = 0; h < unroll_k; h++) { + for (int j = 0; j < unroll_n; j++) { + Zmm b = b_regs[j & 1]; + auto b_src = ptr[BO + (-isize * offset_b + + bwidth * (j + h * unroll_n))]; + + switch (bwidth) { + case 4: + vpbroadcastd(b, b_src); + break; + case 2: + vpbroadcastw(b, b_src); + break; + case 1: + vpbroadcastb(b, b_src); + break; + } + for (int i = 0; i < um_vecs; i++) + dot_product(c_regs[i][j], b, a_regs[i]); + } + + if (unroll_k > 1) { + for (int i = 0; i < um_vecs; i++) + vmovups(a_regs[i], ptr[AO + isize * (32 * i + + (h + 1) * 2 * unroll_m - offset_a)]); + } + } + + add(AO, unroll_k * unroll_m * bwidth); + add(BO, unroll_k * unroll_n * bwidth); +} + +// Inner loop. +void jit_avx512_core_gemm_s8u8s32_kern::innerloop(int unroll_m, int unroll_n) +{ + if ((unroll_m > IGEMM_UNROLL_M) || (unroll_n > IGEMM_UNROLL_N) + || (unroll_m < 0) || (unroll_n < 0)) + return; + + int um_vecs = (unroll_m + 15) >> 4; + int stage1 = unroll_n, stage2 = unroll_n; + + Label label_kernel_loop_1, label_k_main_loop_2, label_kernel_loop_2; + Label label_k_main_loop_3, label_kernel_loop_3; + Label label_k_remainder_loop_begin, label_k_rem_4, label_k_rem_2; + Label label_k_rem_1, label_update_begin; + + mov(AO, A); + for (int i = 0; i < um_vecs; i++) + vmovups(a_regs[i], ptr[AO + isize * (32 * i - offset_a)]); + + mov(LoopCount, K); + sar(LoopCount, 4); + jle(label_k_remainder_loop_begin, T_NEAR); + + // Main k loops, broken into three parts to time C prefetching. + sub(LoopCount, stage1 + stage2); + jle(label_k_main_loop_2, T_NEAR); + + kernel_loop(unroll_m, unroll_n, false); + + L_aligned(label_k_main_loop_2); + lea(CO2, ptr[CO1 + size * (std::min(unroll_m, 16) - 1)]); + add(LoopCount, stage1); + jle(label_k_main_loop_3, T_NEAR); + + kernel_loop(unroll_m, unroll_n, true); + + L_aligned(label_k_main_loop_3); + lea(CO2, ptr[CO1 + size * (std::min(unroll_m, 16) - 1)]); + add(LoopCount, stage2); + jle(label_k_remainder_loop_begin, T_NEAR); + + kernel_loop(unroll_m, unroll_n, true); + + // k remainder handling + L_aligned(label_k_remainder_loop_begin); + mov(LoopCount, K); + test(LoopCount, 8); + je(label_k_rem_4, T_NEAR); + + remainder_kernel(unroll_m, unroll_n, 2, 4); + + L_aligned(label_k_rem_4); + mov(LoopCount, K); + test(LoopCount, 4); + je(label_k_rem_2, T_NEAR); + + remainder_kernel(unroll_m, unroll_n, 1, 4); + + L_aligned(label_k_rem_2); + mov(LoopCount, K); + test(LoopCount, 2); + je(label_k_rem_1, T_NEAR); + + Zmm zero = zmm6; + Zmm tmp = zmm5; + + vpxorq(zero, zero, zero); + for (int i = 0; i < um_vecs; i++) { + Zmm a = a_regs[i]; + vbroadcasti64x4(a, ptr[AO + isize * (16 * i - offset_a)]); + vpunpcklwd(tmp, a, zero); + vpunpckhwd(a, a, zero); + vshufi32x4(a, tmp, a, 0x44); + vshufi32x4(a, a, a, 0xD8); + } + + remainder_kernel(unroll_m, unroll_n, 1, 2); + + L_aligned(label_k_rem_1); + mov(LoopCount, K); + test(LoopCount, 1); + je(label_update_begin, T_NEAR); + + vpxorq(zero, zero, zero); + for (int i = 0; i < um_vecs; i++) { + Zmm a = a_regs[i]; + vbroadcasti32x4(a, ptr[AO + isize * (8 * i - offset_a)]); + vpunpcklbw(tmp, a, zero); + vpunpckhbw(a, a, zero); + vinsertf128(make_ymm(a), make_ymm(tmp), make_xmm(a), 1); + vpunpcklwd(tmp, a, zero); + vpunpckhwd(a, a, zero); + vshufi32x4(a, tmp, a, 0x44); + vshufi32x4(a, a, a, 0xD8); + } + + remainder_kernel(unroll_m, unroll_n, 1, 1); + + // Add offsets and update C. + L_aligned(label_update_begin); + + if (enable_offset_r) { + // Add row offsets. + mov(rax, coffset_ry); + for (int j = 0; j < unroll_n; j++) { + Zmm row_offset = zmm0; + + vbroadcastss(row_offset, ptr[rax + size * j]); + + for (int i = 0; i < um_vecs; i++) + vpaddd(c_regs[i][j], c_regs[i][j], row_offset); + } + add(coffset_ry, size * unroll_n); + } + + if (enable_offset_c) { + // Add column offsets. + mov(rax, coffset_cy); + for (int i = 0; i < um_vecs; i++) { + Zmm col_offset = zmm0; + + c_load(col_offset, ptr[rax + size * 16 * i], unroll_m); + + for (int j = 0; j < unroll_n; j++) + vpaddd(c_regs[i][j], c_regs[i][j], col_offset); + } + } + + Reg64 LDC3 = rax; + lea(LDC3, ptr[LDC + LDC * 2]); + + // C updates. + int c_off_j = 0; + for (int j = 0; j < unroll_n; j++) { + if (j > 0 && (j & 3) == 0) { + lea(CO1, ptr[CO1 + LDC * 4]); + c_off_j += 4; + } + + int jj = j - c_off_j; + + for (int i = 0; i < um_vecs; i++) { + Zmm c = c_regs[i][j]; + Zmm c_old = zmm0; + decltype(LDC * jj) ldc_mult = (jj == 3) ? LDC3 : LDC * jj; + + auto c_mem = ptr[CO1 + ldc_mult + size * 16 * i]; + + if (beta_zero) + c_store(c_mem, c, unroll_m); + else { + c_load(c_old, c_mem, unroll_m); + vpaddd(c_old, c, c_old); + c_store(c_mem, c_old, unroll_m); + } + + vpxorq(c, c, c); + } + } + + lea(CO1, ptr[CO1 + LDC * (unroll_n - c_off_j)]); +} + +// Outer loop. +void jit_avx512_core_gemm_s8u8s32_kern::outerloop(int unroll_x, int unroll_y, + Label *&cur_outerloop_label) +{ + Label label_m_loop, label_n_loop, label_n_remainder_loops[6]; + + L(*cur_outerloop_label); + cur_outerloop_label++; + if (unroll_x >= IGEMM_UNROLL_M) { + mov(J, M); + cmp(J, unroll_x); + jl(*cur_outerloop_label, T_NEAR); // Jump to next outerloop label. + } else { + test(J, unroll_x); + jle(*cur_outerloop_label, T_NEAR); + } + + L_aligned(label_m_loop); { + mov(CO1, C); + add(C, unroll_x * size); + + mov(BO, B); + + mov(AA, K); + imul(AA, AA, unroll_x * isize); + lea(AA, ptr[A + AA + isize * prefetch_size_a]); + + if (enable_offset_c) { + mov(rax, coffset_cx); + mov(coffset_cy, rax); + add(rax, unroll_x * size); + mov(coffset_cx, rax); + } + + if (enable_offset_r) { + mov(rax, coffset_rx); + mov(coffset_ry, rax); + } + + mov(I, N); + cmp(I, unroll_y); + jl(label_n_remainder_loops[0], T_NEAR); + + L_aligned(label_n_loop); { + innerloop(unroll_x, unroll_y); + sub(I, unroll_y); + cmp(I, unroll_y); + jge(label_n_loop, T_NEAR); + } + + align(16); + + int label_idx = 0; + for (int uy = 16; uy > 0; uy >>= 1) { + L(label_n_remainder_loops[label_idx++]); + if (unroll_y > uy) { + test(I, uy); + jle(label_n_remainder_loops[label_idx], T_NEAR); + + innerloop(unroll_x, uy); + align(16); + } + } + L(label_n_remainder_loops[label_idx]); + + mov(A, AO); + if (unroll_x >= IGEMM_UNROLL_M) { + sub(J, unroll_x); + cmp(J, unroll_x); + jge(label_m_loop); + } + } + + align(16); +} + +void jit_avx512_core_gemm_s8u8s32_kern::generate() +{ + // Prologue + preamble(); + sub(rsp, stack_alloc_size); + + if (is_windows) { + mov(A, arg_a); + mov(B, arg_b); + } + + mov(C, arg_c); + mov(LDC, arg_ldc); + + sub(A, -offset_a * isize); + sub(B, -offset_b * isize); + + mov(M, qword[M]); + mov(N, qword[N]); + mov(K, qword[K]); + + lea(LDC, ptr[LDC * size]); + + if (enable_offset_c) { + mov(rax, arg_coffset_c); + mov(coffset_cx, rax); + } + if (enable_offset_r) { + mov(rax, arg_coffset_r); + mov(coffset_rx, rax); + } + + for (int i = 0; i < (max_unroll_m >> 4); i++) { + for (int j = 0; j < max_unroll_n; j++) { + auto &c = c_regs[i][j]; + vpxorq(c, c, c); + } + } + + if (!vnni) { + mov(rax, 1); + movq(make_xmm(ones), rax); + vpbroadcastw(ones, make_xmm(ones)); + } + + Label outerloop_labels[8]; + Label *cur_outerloop_label = &outerloop_labels[0]; + + // Main m loop. + outerloop(IGEMM_UNROLL_M, IGEMM_UNROLL_N, cur_outerloop_label); + + // m remainder loops. + for (int um = 32; um > 0; um >>= 1) + if (IGEMM_UNROLL_M > um) + outerloop(um, IGEMM_UNROLL_N, cur_outerloop_label); + + L(*cur_outerloop_label); + + // Epilogue. + add(rsp, stack_alloc_size); + postamble(); +} + + +jit_avx512_core_gemm_s8u8s32_kern::jit_avx512_core_gemm_s8u8s32_kern(bool + beta_zero_, bool enable_offset_c_, bool enable_offset_r_) : + jit_generator(nullptr, 100000), arg_a(0), arg_b(0), arg_c(0), arg_ldc(0), + arg_coffset_c(0), arg_coffset_r(0), coffset_cx(0), coffset_cy(0), + coffset_rx(0), coffset_ry(0) +{ + beta_zero = beta_zero_; + enable_offset_c = enable_offset_c_; + enable_offset_r = enable_offset_r_; + vnni = mayiuse(avx512_core_vnni); + + // Assign integer registers + M = is_windows ? rcx : rdi; + N = is_windows ? rdx : rsi; + K = is_windows ? r8 : rdx; + A = is_windows ? rsi : r8; + B = r9; + C = r10; + LDC = r11; + I = r12; + J = r13; + LoopCount = rax; + AO = r14; + BO = r15; + CO1 = rbx; + CO2 = rbp; + AA = is_windows ? rdi : rcx; + + // Assign vector registers + dp_scratch = zmm6; + ones = zmm7; + for (int i = 0; i < (max_unroll_m >> 4); i++) + a_regs[i] = Zmm(i); + b_regs[0] = zmm4; + b_regs[1] = zmm5; + + int rn = 0; + for (int i = 0; i < (max_unroll_m >> 4); i++) + for (int j = 0; j < max_unroll_n; j++) + c_regs[i][j] = Zmm(8 + rn++); + + // Assign stack variables. + stack_alloc_size = 32; + auto args_offset = stack_alloc_size + get_size_of_abi_save_regs() + + 8 + (is_windows ? 48 : 0); + + arg_a = ptr[rsp + (args_offset - 16)]; + arg_b = ptr[rsp + (args_offset - 8)]; + arg_c = ptr[rsp + (args_offset + 0)]; + arg_ldc = ptr[rsp + (args_offset + 8)]; + arg_coffset_c = ptr[rsp + (args_offset + 16)]; + arg_coffset_r = ptr[rsp + (args_offset + 24)]; + + coffset_cx = qword[rsp + 0]; + coffset_cy = qword[rsp + 8]; + coffset_rx = qword[rsp + 16]; + coffset_ry = qword[rsp + 24]; + + generate(); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.hpp new file mode 100644 index 000000000000..e8efcc1cc87b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemm_s8u8s32_kern.hpp @@ -0,0 +1,101 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef IGEMM_KERNEL_GENERATOR_HPP +#define IGEMM_KERNEL_GENERATOR_HPP + +#include "jit_generator.hpp" + + +namespace mkldnn { +namespace impl { +namespace cpu { + +class jit_avx512_core_gemm_s8u8s32_kern : public jit_generator { +public: + jit_avx512_core_gemm_s8u8s32_kern(bool beta_zero_, bool enable_offset_c_, + bool enable_offset_r_); + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_gemm_s8u8s32_kern); + +protected: + bool beta_zero; + bool enable_offset_c, enable_offset_r; + bool vnni; + + void prefetch_a(const Xbyak::Address &src) { + prefetcht0(src); + } + void prefetch_b(const Xbyak::Address &src) { + prefetcht0(src); + } + void prefetch_c(const Xbyak::Address &src) { + prefetchw(src); + } + void prefetch_x(const Xbyak::Address &src) { + prefetcht0(src); + } + + void c_load(const Xbyak::Xmm &dst, const Xbyak::Address &src, int nelems); + void c_store(const Xbyak::Address &dst, const Xbyak::Xmm &src, int nelems); + + void dot_product(const Xbyak::Xmm &dst, const Xbyak::Xmm &src1, + const Xbyak::Xmm &src2); + void kernel_loop(int unroll_m, int unroll_n, bool cfetch); + void remainder_kernel(int unroll_m, int unroll_n, int unroll_k, int bwidth); + void innerloop(int unroll_m, int unroll_n); + void outerloop(int unroll_x, int unroll_y, Xbyak::Label *&outerloop_label); + + void generate(); + + +private: + static const int IGEMM_UNROLL_M = 48; + static const int IGEMM_UNROLL_N = 8; + + static const int isize = 2; + static const int size = 4; + + // Prefetch configuration + static const int prefetch_size_a = 32 * 5; + static const int prefetch_size_b = 32 * 4; + + static const int offset_a = 256, offset_b = 256; + static const int max_unroll_m = 48, max_unroll_n = 8; + + // Integer register assignments + Xbyak::Reg64 M, N, K, A, B, C, LDC, I, J, LoopCount; + Xbyak::Reg64 AO, BO, CO1, CO2, AA; + + // Vector register assignments + Xbyak::Zmm dp_scratch, ones, a_regs[max_unroll_m >> 4], b_regs[2]; + Xbyak::Zmm c_regs[max_unroll_m >> 4][max_unroll_n]; + + // Stack variable assignments + int stack_alloc_size; + Xbyak::Address arg_a, arg_b, arg_c, arg_ldc, arg_coffset_c, arg_coffset_r; + Xbyak::Address coffset_cx, coffset_cy, coffset_rx, coffset_ry; + + void L_aligned(Xbyak::Label &label, int alignment = 16) { + align(alignment); + L(label); + } +}; + +} +} +} + +#endif /* header guard */ diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemv_s8u8s32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemv_s8u8s32.cpp new file mode 100644 index 000000000000..4f0b10dadd44 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_gemv_s8u8s32.cpp @@ -0,0 +1,290 @@ +/******************************************************************************* + * Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "gemv.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +int gemm_s8u8s32_jump_to_gemv_s8u8s32(blas_t *arg) { + + blas_t arg_gemv = *arg; + + if ((arg -> offsetc == FIX_OFFSET) && // Fix offset + (arg -> ao == 0) && + (arg -> bo == 0) && + (arg -> co[0] == 0) && + (*(arg -> alpha) == 1.0f) && + ((*(arg -> beta) == 1.0f) || *(arg -> beta) == 0.0f)) { + + if (arg -> n == 1) { + + if (arg -> transa == 1) { // A transpose + arg_gemv.n = arg -> k; + arg_gemv.ldc = 1; + arg_gemv.swap = 0; + if (arg -> transb == 0) { // B non transpose + arg_gemv.ldb = 1; + } + // B transpose arg_gemv.ldb = arg -> ldb + gemv_threading_driver(&arg_gemv); + return 1; + } + } + + if (arg -> m == 1) { + + if (arg -> transb == 0) { // B non transpose + arg_gemv.transa = 1; + arg_gemv.m = arg -> n; + arg_gemv.n = arg -> k; + arg_gemv.a = (int8_t *) arg -> b; + arg_gemv.lda = arg -> ldb; + arg_gemv.b = (uint8_t *) arg -> a; + arg_gemv.swap = 1; + if (arg -> transa == 0) { // A non transpose + arg_gemv.ldb = arg -> lda; + } + else { // A transpose + arg_gemv.ldb = 1; + } + gemv_threading_driver(&arg_gemv); + return 1; + } + } + } + + return 0; +} + + +int gemv_kernel_driver(blas_t *arg) { + + dim_t m = arg -> m; + dim_t n = arg -> n; + uint8_t *a = (uint8_t *) arg -> a; + dim_t lda = arg -> lda; + int8_t *b = (int8_t *) arg -> b; + float beta = *(arg -> beta); + + if (arg -> swap) { + arg -> gemv_u8s8s32_kernel(m, n, 1.0f, a, lda, b, beta, arg -> c); + } + else { + arg -> gemv_s8u8s32_kernel(arg -> m, arg -> n, 1.0f, arg -> a, + arg -> lda, arg -> b, *(arg -> beta), arg -> c); + } + + return 0; +} + +int gemv_threading_driver(blas_t *arg) { + + dim_t nthr_m, nthr_n = 1; + dim_t MB, NB, UM = 16, UN = 64; + dim_t BLOCKM = 192, BLOCKN = 3072; + int status; + dim_t i; + + dim_t nthr = (mkldnn_in_parallel()) ? 1 : mkldnn_get_max_threads(); + + uint8_t *new_x = NULL; + int32_t *tmp_y = NULL, *new_y = NULL; + + dim_t m = arg -> m, n = arg -> n; + + blas_t arg_seq = *arg; + float zero = 0.0f; + + nthr_m = std::min(std::max(m / BLOCKM, (dim_t) 1), nthr); + MB = m / nthr_m; + MB = (((MB / UM) * UM) == MB) ? MB : (MB / UM) * UM + UM; + nthr_m = (((m / MB) * MB) == m) ? m / MB : m / MB + 1; + nthr_m = std::min(std::max(nthr_m, (dim_t) 1), nthr); + + while ((nthr_m * (nthr_n + 1) <= nthr) && ((n / (nthr_n + 1)) >= BLOCKN)) { + nthr_n++; + } + + NB = n / nthr_n; + NB = (((NB / UN) * UN) == NB) ? NB : (NB / UN) * UN + UN; + nthr_n = (((n / NB) * NB) == n) ? n / NB : n / NB + 1; + nthr_n = std::min(std::max(nthr_n, (dim_t) 1), nthr / nthr_m); + + nthr = nthr_m * nthr_n; + + if (arg -> ldb != 1) { + new_x = (uint8_t *)malloc(n, 64); + if (new_x == NULL) + return 1; + for (i = 0; i < n; i++) { + new_x[i] = (arg -> b)[i * arg -> ldb]; + } + arg_seq.b = new_x; + arg_seq.ldb = 1; + } + else new_x = (uint8_t *) arg -> b; + + if (arg -> ldc != 1) { + new_y = (int32_t *) malloc(nthr_m * PADD_BYTESIZE_ONPAGE(MB, sizeof(int32_t)), 64); + if (new_y == NULL) { + if (arg -> ldb != 1) { + free(new_x); + } + return 1; + } + } + + // GEMV computation + if (nthr == 1) { + + if (arg -> ldc != 1) { + if (*(arg -> beta) != 0.0f) { + for (i = 0; i < m; i++) { + new_y[i] = arg -> c[i * arg -> ldc]; + } + } + } + + status = gemv_kernel_driver(&arg_seq); + + if (arg -> ldc != 1) { + for (i = 0; i < m; i++) { + arg -> c[i * arg -> ldc] = new_y[i]; + } + } + + if (arg -> ldb != 1) { + free(new_x); + } + if (arg -> ldc != 1) { + free(new_y); + } + return status; + } + + if (nthr_n > 1) { + tmp_y = (int32_t *) malloc((nthr_n - 1) * PADD_BYTESIZE_ONPAGE(m, sizeof(int32_t)), PAGESIZE); + if (tmp_y == NULL) { + if (arg -> ldb != 1) { + free(new_x); + } + return 1; + } + } + + parallel_nd((int) nthr, [&](const dim_t ithr) { + + dim_t m_from, m_to, myM; + dim_t n_from, n_to, myN; + + dim_t n_id, m_id; + dim_t loc_incy = 1; + int32_t *loc_y; + + blas_t arg_loc = arg_seq; + int j; + + m_id = ithr / nthr_n; + n_id = ithr % nthr_n; + + m_from = MB * m_id; + m_to = MB * (m_id + 1); + if ((m_to > m) || (m_id == nthr_m - 1)) + m_to = m; + + myM = m_to - m_from; + + n_from = NB * n_id; + n_to = NB * (n_id + 1); + if ((n_to > n) || (n_id == nthr_n - 1)) + n_to = n; + + myN = n_to - n_from; + + if (n_id != 0) { + arg_loc.beta = &zero; + loc_y = tmp_y + (NEXT_THR_STRIDE(m, sizeof(int32_t))) * (n_id - 1) + m_from; + } + else { + if (arg -> ldc == 1) { + loc_y = arg_seq.c + m_from; + } + else { + // need to copy the block of c in new_y + loc_y = new_y + m_id * NEXT_THR_STRIDE(MB, sizeof(int32_t)); + if (*(arg -> beta) != 0.0f) { + for (j = 0; j < myM; j++) { + loc_y[j] = arg -> c[(m_from + j) * arg -> ldc]; + } + } + } + } + + arg_loc.m = myM; + arg_loc.n = myN; + arg_loc.a = arg_seq.a + m_from * arg_seq.lda + n_from; + arg_loc.b = arg_seq.b + n_from; + arg_loc.c = loc_y; + arg_loc.ldc = loc_incy; + + gemv_kernel_driver(&arg_loc); + + if ((n_id == 0) && (arg -> ldc != 1)) { + for (j = 0; j < myM; j++) { + arg -> c[(m_from + j) * arg -> ldc] = loc_y[j]; + } + } + + }); + + if (nthr_n > 1) { + parallel_nd((int) nthr_m, [&](const dim_t ithr) { + + dim_t j, j_from, j_to, ii; + int32_t acc; + + j_from = MB * ithr; + j_to = MB * (ithr + 1); + if ((j_to > m) || (ithr == nthr - 1)) + j_to = m; + + for (j = j_from; j < j_to; j++) { + acc = 0; + for (ii = 0; ii < nthr_n - 1; ii++) { + acc += tmp_y[ii * NEXT_THR_STRIDE(m, sizeof(int32_t)) + j]; + } + (arg -> c)[j * arg -> ldc] += acc; + } + }); + free(tmp_y); + } + + if (arg -> ldb != 1) { + free(new_x); + } + + if (arg -> ldc != 1) { + free(new_y); + } + + return 0; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.cpp new file mode 100644 index 000000000000..c57a8c1d1212 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.cpp @@ -0,0 +1,411 @@ +/******************************************************************************* + * Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "jit_avx512_core_kernel_gemv_s8u8s32_kern.hpp" + +#ifdef _WIN32 +#define is_windows 1 +#else +#define is_windows 0 +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { + +void jit_avx512_core_gemv_s8u8s32_kern::vnni(Xbyak::Zmm acc, Xbyak::Zmm b, + Xbyak::Zmm a, Xbyak::Zmm tmp, + Xbyak::Zmm one, bool swap, + int use_vnni) { + + if (use_vnni) { + if (swap) + vpdpbusd(acc, a, b); + else + vpdpbusd(acc, b, a); + } + + else { + if (swap) + vpmaddubsw(tmp, a, b); + else + vpmaddubsw(tmp, b, a); + vpmaddwd(tmp, tmp, one); + vpaddd(acc, tmp, acc); + } + +} + +void jit_avx512_core_gemv_s8u8s32_kern::n_loop_body(int start_a_idx, int start_acc_idx, + int b_idx, int nreg_acc, + Xbyak::Reg64 A, Xbyak::Reg64 lda, + Xbyak::Reg64 X, Xbyak::Zmm tmp, + Xbyak::Zmm one, bool swap, int use_vnni, + int use_mask, Xbyak::Opmask mask_n) { + + int i; + int nreg_A = nreg_acc / 2 + (nreg_acc % 2); + + // load X + j + if (use_mask) + vmovdqu8(Xbyak::Zmm(b_idx) | mask_n | T_z, ptr[X]); + else + vmovdqu8(Xbyak::Zmm(b_idx), ptr[X]); + + xor_(r14, r14); + // load values of A + for (i = 0; i < nreg_A; i++) { + if (use_mask) + vmovdqu8(Xbyak::Zmm(start_a_idx + i) | mask_n | T_z, ptr[A + r14]); + else + vmovdqu8(Xbyak::Zmm(start_a_idx + i), ptr[A + r14]); + add(r14, lda); + } + + for (i = 0; i < nreg_A; i++) { + // vnni (acc, b, a, tmp, one, swap, use_vnni) + vnni(Xbyak::Zmm(start_acc_idx + i), Xbyak::Zmm(b_idx), + Xbyak::Zmm(start_a_idx + i), tmp, one, swap, use_vnni); + } + + for (i = 0; i < nreg_A - (nreg_acc % 2); i++) { + if (use_mask) + vmovdqu8(Xbyak::Zmm(start_a_idx + i) | mask_n | T_z, ptr[A + r14]); + else + vmovdqu8(Xbyak::Zmm(start_a_idx + i), ptr[A + r14]); + add(r14, lda); + } + + for (i = 0; i < nreg_A - (nreg_acc % 2); i++) { + vnni(Xbyak::Zmm(start_acc_idx + i + nreg_A), Xbyak::Zmm(b_idx), + Xbyak::Zmm(start_a_idx + i), tmp, one, swap, use_vnni); + } + +} + +void jit_avx512_core_gemv_s8u8s32_kern::shuffle_and_add(Xbyak::Zmm dest, Xbyak::Zmm A, + Xbyak::Zmm B, Xbyak::Zmm C, + Xbyak::Zmm D) { + + vshufi32x4(dest, A, C, 0x44); + vshufi32x4(A, A, C, 0xEE); + vpaddd(C, dest, A); // C = A0 + A2|A1 + A3|C0 + C2|C1 + C3 + + vshufi32x4(dest, B, D, 0x44); + vshufi32x4(B, B, D, 0xEE); + vpaddd(D, dest, B); // D = B0 + B2|B1 + B3|D0 + D2|D1 + D3 + + vshufi32x4(A, C, D, 0x88); + vshufi32x4(B, C, D, 0xDD); + vpaddd(dest, A, B); // dest = SAi|SBi|SCi|SDi + +} + +void jit_avx512_core_gemv_s8u8s32_kern::update_c(int nreg_acc, Xbyak::Reg64 Y, + int start_a_idx, int start_acc_idx, + Xbyak::Xmm beta, int use_mask, + Xbyak::Opmask mask_m) { + + int l, i, k, j, last_it; + Xbyak::Label store_label; + + l = 0; + for (k = 0; k < nreg_acc; k += 8) { + for (i = 0, j = k; i < 8; i += 4, j += 2) { + if (j < nreg_acc) { + // shuffle per block of 4 registers + shuffle_and_add(Xbyak::Zmm(start_a_idx + l), // dest + Xbyak::Zmm(start_acc_idx + j), // A = acc0 + Xbyak::Zmm(start_acc_idx + 1 + j), // B = acc1 + Xbyak::Zmm(start_acc_idx + 4 + j), // C = acc4 + Xbyak::Zmm(start_acc_idx + 5 + j)); // D = acc5 + + // extract low and high from dest and hadd + vextracti32x8(Xbyak::Ymm(start_a_idx + l + 1), Xbyak::Zmm(start_a_idx + l), 0); + vextracti32x8(Xbyak::Ymm(start_a_idx + l + 2), Xbyak::Zmm(start_a_idx + l), 1); + vphaddd(Xbyak::Ymm(start_a_idx + l), + Xbyak::Ymm(start_a_idx + l + 1), + Xbyak::Ymm(start_a_idx + l + 2)); + } + l++; + } + + vphaddd(Xbyak::Ymm(start_a_idx + l), + Xbyak::Ymm(start_a_idx + l - 2), + Xbyak::Ymm(start_a_idx + l - 1)); + + l++; + } + + // eventually add with C and store new value + vxorps(Xbyak::Ymm(start_a_idx), + Xbyak::Ymm(start_a_idx), + Xbyak::Ymm(start_a_idx)); + vucomiss(beta, Xbyak::Ymm(start_a_idx)); + je(store_label, T_NEAR); + + // beta = 1 + for (k = 0, l = 2; k < nreg_acc; k += 8, l += 3) { + // load Y and add + last_it = (k + 8) > nreg_acc; + if (use_mask && last_it) + vmovdqu32(Xbyak::Ymm(start_a_idx + k / 8) | mask_m | T_z, ptr[Y + (k / 8) * 32]); + else + vmovdqu32(Xbyak::Ymm(start_a_idx + k / 8), ptr[Y + (k / 8) * 32]); + + vpaddd(Xbyak::Ymm(start_a_idx + l), + Xbyak::Ymm(start_a_idx + l), + Xbyak::Ymm(start_a_idx + k / 8)); + } + + // store + aligned_label(store_label); + for (k = 0, l = 2; k < nreg_acc; k += 8, l += 3) { + last_it = (k + 8) > nreg_acc; + if (use_mask && last_it) + vmovdqu32(ptr[Y + (k / 8) * 32], Xbyak::Ymm(start_a_idx + l) | mask_m); + else + vmovdqu32(ptr[Y + (k / 8) * 32], Xbyak::Ymm(start_a_idx + l)); + } + +} + +template +T jit_avx512_core_gemv_s8u8s32_kern::generate(int use_vnni) { + + Xbyak::Opmask mask_n = k1, mask_m = k2; + Xbyak::Label one_label, m_tail_label, m_loop_label, n_loop_label; + Xbyak::Label n_tail_label, update_c_label, end_label; + constexpr unsigned int n_labels = (1 << unroll_m) - 1; + Xbyak::Label m_tail_label_case[n_labels]; + Xbyak::Label n_loop_label_case[n_labels]; + Xbyak::Label n_tail_label_case[n_labels]; + Xbyak::Label update_c_label_case[n_labels]; + + int i, ii; + + Xbyak::Zmm one, tmp; + Xbyak::Reg64 n = abi_param2, m = abi_param1; + Xbyak::Reg64 A = is_windows ? abi_param4 : abi_param3; + Xbyak::Reg64 lda = is_windows ? abi_param3 : abi_param4; + Xbyak::Reg64 X = is_windows ? rdi : r8; + Xbyak::Xmm beta = xmm1; + Xbyak::Reg64 Y = is_windows ? rsi : r9; + + bool swap = !std::is_same::value; + + // Windows: read on the stack lda, X, beta, Y + + int zmm_idx = 1; + int nreg_acc = 1 << unroll_m; + int nreg_A = 1 << (unroll_m - 1); + int nreg_A_acc = nreg_acc + nreg_A; + + if (!use_vnni) { + // set a zmm register to one + tmp = Xbyak::Zmm(0); + one = Xbyak::Zmm(zmm_idx + 1); + zmm_idx += 2; // one + tmp + } + else { + beta = xmm0; + } + + preamble(); + + if (is_windows) { + mov(lda, ptr[rsp + get_size_of_abi_save_regs() + 40]); + mov(X, ptr[rsp + get_size_of_abi_save_regs() + 48]); + movss(beta, ptr[rsp + get_size_of_abi_save_regs() + 56]); + mov(Y, ptr[rsp + get_size_of_abi_save_regs() + 64]); + } + + if (use_vnni && !is_windows) { + movaps(beta, xmm1); + } + + mov(rax, (1 << unroll_n) - 1); + kmovq(k3, rax); + + and_(rax, n); // rax contains n & ((1 << unroll_n) - 1) + mov(rbx, 1); + shlx(rbx, rbx, rax); + sub(rbx, 1); + kmovq(mask_n, rbx); + // mask_n set (AVX512 only), can use rax and rbx again + + // set mask_m for update of the C matrix + // load/store on the C matrix use Ymm so tail according to Ymm size + mov(rax, 7); // 8 * 32 = 256 Ymm size + and_(rax, m); // rax contains m & 7 + mov(rbx, 1); + shlx(rbx, rbx, rax); + sub(rbx, 1); + kmovq(mask_m, rbx); + // mask_m set (AVX512 only), can use rax and rbx again + + // setup register of ones when VNNI instructions not available + if (!use_vnni) { + vmovdqu16(one, ptr[rip + one_label]); + } + + // M loop + // base pointer for A rax contains a + i * lda + // Loop stop when rax >= a + (m & mask_um) * lda = rbx + // loop increment r10 = um * lda + // rbp = Y + i + mov(rax, A); // i = 0 + mov(rbx, m); + and_(rbx, mask_um); + imul(rbx, lda); + add(rbx, A); + mov(r10, lda); + sal(r10, unroll_m); + mov(rbp, Y); + + // N loop + // base pointer for X r11 contains x + j + // Loop stop when r11 >= x + n & mask_un = r12 + // loop increment un + // r13 = rax + j = A + i * lda + j + mov(r12, n); + and_(r12, mask_un); + add(r12, X); + + // M loop + aligned_label(m_loop_label); + cmp(rax, rbx); + jge(m_tail_label, T_NEAR); + + // enter M loop + for(i = 0; i < nreg_acc; i++) { + vpxorq(Xbyak::Zmm(i + zmm_idx + nreg_A), + Xbyak::Zmm(i + zmm_idx + nreg_A), + Xbyak::Zmm(i + zmm_idx + nreg_A)); + } + + // N loop + mov(r11, X); // j = 0 + mov(r13, rax); + aligned_label(n_loop_label); + cmp(r11, r12); + jge(n_tail_label, T_NEAR); + + // enter N loop + + n_loop_body(zmm_idx, zmm_idx + nreg_A, zmm_idx + nreg_A_acc, nreg_acc, + r13, lda, r11, tmp, one, swap, use_vnni, 0, mask_n); + + // increment rax with un + add(r11, 1 << unroll_n); + add(r13, 1 << unroll_n); + jmp(n_loop_label, T_NEAR); + // end N loop + + // N tail + aligned_label(n_tail_label); + + ktestq(mask_n, k3); + je(update_c_label, T_NEAR); + n_loop_body(zmm_idx, zmm_idx + nreg_A, zmm_idx + nreg_A_acc, nreg_acc, + r13, lda, r11, tmp, one, swap, use_vnni, 1, mask_n); + + // update C matrix + aligned_label(update_c_label); + + update_c(nreg_acc, rbp, zmm_idx, zmm_idx + nreg_A, beta, 0, mask_m); + + // increment rax with um * lda + add(rax, r10); + add(rbp, 1 << (unroll_m + 2)); + jmp(m_loop_label, T_NEAR); + // end M loop + + // M tail + aligned_label(m_tail_label); + + // r10 will contain m_tail = m % unroll_m = m & (1 << unroll_m) - 1 + mov(r10, m); + and_(r10, (1 << unroll_m) - 1); + for (ii = 1; ii < 1 << unroll_m; ii++) { + aligned_label(m_tail_label_case[ii-1]); + cmp(r10, ii); + if (ii == (1 << unroll_m) - 1) + jne(end_label, T_NEAR); + else + jne(m_tail_label_case[ii], T_NEAR); + + // m_tail = i, use i accumulators + + for(i = 0; i < ii; i++) { + vpxorq(Xbyak::Zmm(i + zmm_idx + nreg_A), + Xbyak::Zmm(i + zmm_idx + nreg_A), + Xbyak::Zmm(i + zmm_idx + nreg_A)); + } + + // N loop + mov(r11, X); // j = 0 + mov(r13, rax); + aligned_label(n_loop_label_case[ii - 1]); + cmp(r11, r12); + jge(n_tail_label_case[ii - 1], T_NEAR); + + n_loop_body(zmm_idx, zmm_idx + nreg_A, zmm_idx + nreg_A_acc, ii, r13, + lda, r11, tmp, one, swap, use_vnni, 0, mask_n); + + // increment rax with un + add(r11, 1 << unroll_n); + add(r13, 1 << unroll_n); + jmp(n_loop_label_case[ii - 1], T_NEAR); + // end N loop + + // N tail + aligned_label(n_tail_label_case[ii - 1]); + ktestq(mask_n, k3); + je(update_c_label_case[ii - 1], T_NEAR); + n_loop_body(zmm_idx, zmm_idx + nreg_A, zmm_idx + nreg_A_acc, ii, r13, + lda, r11, tmp, one, swap, use_vnni, 1, mask_n); + + // update C matrix + aligned_label(update_c_label_case[ii - 1]); + update_c(ii, rbp, zmm_idx, zmm_idx + nreg_A, beta, 1, mask_m); + + if (ii < ((1 << unroll_m) - 1)) + jmp(end_label, T_NEAR); + } + + aligned_label(end_label); + + postamble(); + + if (!use_vnni) { + aligned_label(one_label); + for (i = 0; i < size_vec_reg/8; i++) + dq(0x0001000100010001); + } + + return (T) getCode(); +} + +template jit_avx512_core_gemv_s8u8s32_kern::gemv_s8u8s32_kernel_t +jit_avx512_core_gemv_s8u8s32_kern::generate(int); + +template jit_avx512_core_gemv_s8u8s32_kern::gemv_u8s8s32_kernel_t +jit_avx512_core_gemv_s8u8s32_kern::generate(int); + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.hpp new file mode 100644 index 000000000000..9ea23a5f56af --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_kernel_gemv_s8u8s32_kern.hpp @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2019 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +class jit_avx512_core_gemv_s8u8s32_kern : jit_generator { + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_gemv_s8u8s32_kern); + + // assumes untoll_{m,n} are a power of 2 + static constexpr unsigned int unroll_m = 4; // real unrolling factor is 2^unroll_m + const int mask_um = 0xFFFFFFF0; + static constexpr unsigned int unroll_n = 6; // real unrolling factor is 2^unroll_n + const int mask_un = 0xFFFFFFC0; + const int size_vec_reg = 64; // bytes + + void aligned_label(Xbyak::Label &label, int alignment = 16) { + align(alignment); + L(label); + } + + void vnni(Xbyak::Zmm, Xbyak::Zmm, Xbyak::Zmm, Xbyak::Zmm, Xbyak::Zmm, bool, int); + void n_loop_body(int, int, int, int, Xbyak::Reg64, Xbyak::Reg64, + Xbyak::Reg64, Xbyak::Zmm, Xbyak::Zmm, bool, int, int, Xbyak::Opmask); + void shuffle_and_add(Xbyak::Zmm, Xbyak::Zmm, Xbyak::Zmm, Xbyak::Zmm, Xbyak::Zmm); + void update_c(int, Xbyak::Reg64, int, int, Xbyak::Xmm, int, Xbyak::Opmask); + +public: + jit_avx512_core_gemv_s8u8s32_kern() : jit_generator(nullptr, GEMM_CODE_SIZE) {}; + + // m, n, alpha, a, lda, x, beta, y + typedef void (*gemv_s8u8s32_kernel_t)(const dim_t, const dim_t, const float, + const int8_t*, const dim_t, const uint8_t*, + const float, int32_t*); + typedef void (*gemv_u8s8s32_kernel_t)(const dim_t, const dim_t, const float, + const uint8_t*, const dim_t, const int8_t*, + const float, int32_t*); + + template + T generate(int use_vnni); + +}; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_an_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_an_kern.cpp new file mode 100644 index 000000000000..544cd2ff25cd --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_an_kern.cpp @@ -0,0 +1,819 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_an_kern::jit_avx512_core_u8_copy_an_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l170; +Xbyak::Label l1f0; +Xbyak::Label l20; +Xbyak::Label l224; +Xbyak::Label l234; +Xbyak::Label l240; +Xbyak::Label l254; +Xbyak::Label l32c; +Xbyak::Label l34; +Xbyak::Label l388; +Xbyak::Label l3b0; +Xbyak::Label l3c0; +Xbyak::Label l3cc; +Xbyak::Label l3dc; +Xbyak::Label l454; +Xbyak::Label l48c; +Xbyak::Label l4a8; +Xbyak::Label l4b8; +Xbyak::Label l4c4; +Xbyak::Label l4d8; +Xbyak::Label l570; +Xbyak::Label l5c4; +Xbyak::Label l5f0; +Xbyak::Label l60c; +Xbyak::Label l61c; +Xbyak::Label l628; +Xbyak::Label l638; +Xbyak::Label l6b0; +Xbyak::Label l6f4; +Xbyak::Label l720; +Xbyak::Label l73c; +Xbyak::Label l74c; +Xbyak::Label l758; +Xbyak::Label l76c; +Xbyak::Label l804; +Xbyak::Label l858; +Xbyak::Label l88c; +Xbyak::Label l8a4; +Xbyak::Label l8b2; +Xbyak::Label l8bc; +Xbyak::Label l8cc; +Xbyak::Label l944; +Xbyak::Label l98c; +Xbyak::Label l9b0; +Xbyak::Label l9c8; +Xbyak::Label l9d8; + + preamble(); +#ifdef _WIN32 + auto stacksize = get_size_of_abi_save_regs(); + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(M, qword[M]); + mov(N, qword[N]); + mov(LDA, qword[LDA]); + lea(LDA3, ptr[LDA+LDA*2]); + sub(A, -128); + sub(B, -128); + cmp(N, 0x30); + jl(l234, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + add(A, 0x30); + mov(I, M); + sar(I, 0x2); + jle(l170, T_NEAR); + align(4); + +L(l34); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movdqu(xword[B-0x60], xmm4); + movdqu(xword[B-0x50], xmm2); + movdqu(xmm0, xword[A1-0x70]); + movdqu(xmm1, xword[A1+LDA*1-0x70]); + movdqu(xmm2, xword[A1+LDA*2-0x70]); + movdqu(xmm3, xword[A1+LDA3*1-0x70]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + movdqu(xword[B-0x40], xmm0); + movdqu(xword[B-0x30], xmm1); + movdqu(xword[B-0x20], xmm4); + movdqu(xword[B-0x10], xmm2); + movdqu(xmm0, xword[A1-0x60]); + movdqu(xmm1, xword[A1+LDA*1-0x60]); + movdqu(xmm2, xword[A1+LDA*2-0x60]); + movdqu(xmm3, xword[A1+LDA3*1-0x60]); + lea(A1, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + movdqu(xword[B], xmm0); + movdqu(xword[B+0x10], xmm1); + movdqu(xword[B+0x20], xmm4); + movdqu(xword[B+0x30], xmm2); + sub(B, -192); + dec(I); + jg(l34, T_NEAR); + align(4); + +L(l170); + test(M, 0x2); + jle(l1f0, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1-0x70]); + movdqu(xmm2, xword[A1-0x60]); + add(A1, LDA); + movdqu(xmm3, xword[A1-0x80]); + movdqu(xmm4, xword[A1-0x70]); + movdqu(xmm5, xword[A1-0x60]); + add(A1, LDA); + movdqa(xmm6, xmm0); + punpcklbw(xmm0, xmm3); + punpckhbw(xmm6, xmm3); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm6); + movdqa(xmm6, xmm1); + punpcklbw(xmm1, xmm4); + punpckhbw(xmm6, xmm4); + movdqu(xword[B-0x60], xmm1); + movdqu(xword[B-0x50], xmm6); + movdqa(xmm6, xmm2); + punpcklbw(xmm2, xmm5); + punpckhbw(xmm6, xmm5); + movdqu(xword[B-0x40], xmm2); + movdqu(xword[B-0x30], xmm6); + sub(B, -96); + align(4); + +L(l1f0); + test(M, 0x1); + jle(l224, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1-0x70]); + movdqu(xmm2, xword[A1-0x60]); + add(A1, LDA); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movdqu(xword[B-0x60], xmm2); + sub(B, -48); + align(4); + +L(l224); + sub(N, 0x30); + cmp(N, 0x30); + jge(l20, T_NEAR); + align(4); + +L(l234); + cmp(N, 0x20); + jl(l3c0, T_NEAR); + align(4); + +L(l240); + mov(A1, A); + add(A, 0x20); + mov(I, M); + sar(I, 0x2); + jle(l32c, T_NEAR); + align(4); + +L(l254); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movdqu(xword[B-0x60], xmm4); + movdqu(xword[B-0x50], xmm2); + movdqu(xmm0, xword[A1-0x70]); + movdqu(xmm1, xword[A1+LDA*1-0x70]); + movdqu(xmm2, xword[A1+LDA*2-0x70]); + movdqu(xmm3, xword[A1+LDA3*1-0x70]); + lea(A1, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + movdqu(xword[B-0x40], xmm0); + movdqu(xword[B-0x30], xmm1); + movdqu(xword[B-0x20], xmm4); + movdqu(xword[B-0x10], xmm2); + sub(B, -128); + dec(I); + jg(l254, T_NEAR); + align(4); + +L(l32c); + test(M, 0x2); + jle(l388, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1-0x70]); + add(A1, LDA); + movdqu(xmm2, xword[A1-0x80]); + movdqu(xmm3, xword[A1-0x70]); + add(A1, LDA); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm2); + punpckhbw(xmm4, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm4); + movdqa(xmm4, xmm1); + punpcklbw(xmm1, xmm3); + punpckhbw(xmm4, xmm3); + movdqu(xword[B-0x60], xmm1); + movdqu(xword[B-0x50], xmm4); + sub(B, -64); + align(4); + +L(l388); + test(M, 0x1); + jle(l3b0, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1-0x70]); + add(A1, LDA); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l3b0); + sub(N, 0x20); + cmp(N, 0x20); + jge(l240, T_NEAR); + align(4); + +L(l3c0); + cmp(N, 0x10); + jl(l4b8, T_NEAR); + align(4); + +L(l3cc); + mov(A1, A); + add(A, 0x10); + mov(I, M); + sar(I, 0x2); + jle(l454, T_NEAR); + align(4); + +L(l3dc); + movdqu(xmm0, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm1, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm2, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm3, xword[A1-0x80]); + add(A1, LDA); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm1, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm1, xmm3); + movdqa(xmm3, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm3, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm1); + punpckhwd(xmm2, xmm1); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm3); + movdqu(xword[B-0x60], xmm4); + movdqu(xword[B-0x50], xmm2); + sub(B, -64); + dec(I); + jg(l3dc, T_NEAR); + align(4); + +L(l454); + test(M, 0x2); + jle(l48c, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm1, xword[A1-0x80]); + add(A1, LDA); + movdqa(xmm2, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm2, xmm1); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm2); + sub(B, -32); + align(4); + +L(l48c); + test(M, 0x1); + jle(l4a8, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + add(A1, LDA); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l4a8); + sub(N, 0x10); + cmp(N, 0x10); + jge(l3cc, T_NEAR); + align(4); + +L(l4b8); + cmp(N, 0x8); + jl(l61c, T_NEAR); + align(4); + +L(l4c4); + mov(A1, A); + add(A, 0x8); + mov(I, M); + sar(I, 0x3); + jle(l570, T_NEAR); + align(4); + +L(l4d8); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + dec(I); + jg(l4d8, T_NEAR); + align(4); + +L(l570); + test(M, 0x4); + jle(l5c4, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l5c4); + test(M, 0x2); + jle(l5f0, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l5f0); + test(M, 0x1); + jle(l60c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l60c); + sub(N, 0x8); + cmp(N, 0x8); + jge(l4c4, T_NEAR); + align(4); + +L(l61c); + cmp(N, 0x4); + jl(l74c, T_NEAR); + align(4); + +L(l628); + mov(A1, A); + add(A, 0x4); + mov(I, M); + sar(I, 0x3); + jle(l6b0, T_NEAR); + align(4); + +L(l638); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + dec(I); + jg(l638, T_NEAR); + align(4); + +L(l6b0); + test(M, 0x4); + jle(l6f4, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l6f4); + test(M, 0x2); + jle(l720, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l720); + test(M, 0x1); + jle(l73c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l73c); + sub(N, 0x4); + cmp(N, 0x4); + jge(l628, T_NEAR); + align(4); + +L(l74c); + cmp(N, 0x2); + jl(l8b2, T_NEAR); + align(4); + +L(l758); + mov(A1, A); + add(A, 0x2); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l804, T_NEAR); + align(4); + +L(l76c); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm4, eax, 0x0); + punpcklbw(xmm1, xmm2); + punpcklbw(xmm3, xmm4); + punpcklwd(xmm1, xmm3); + punpcklqdq(xmm0, xmm1); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(LDA3); + jg(l76c, T_NEAR); + align(4); + +L(l804); + test(M, 0x4); + jle(l858, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l858); + test(M, 0x2); + jle(l88c, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + punpcklbw(xmm0, xmm1); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l88c); + test(M, 0x1); + jle(l8a4, T_NEAR); + mov(ax, word[A1-0x80]); + mov(word[B-0x80], ax); + sub(B, -2); + align(4); + +L(l8a4); + sub(N, 0x2); + cmp(N, 0x2); + jge(l758, T_NEAR); + align(4); + +L(l8b2); + cmp(N, 0x1); + jl(l9d8, T_NEAR); + align(4); + +L(l8bc); + mov(A1, A); + add(A, 0x1); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l944, T_NEAR); + align(4); + +L(l8cc); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x7); + movq(qword[B-0x80], xmm0); + sub(B, -8); + dec(LDA3); + jg(l8cc, T_NEAR); + align(4); + +L(l944); + test(M, 0x4); + jle(l98c, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l98c); + test(M, 0x2); + jle(l9b0, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + mov(byte[B-0x80], al); + mov(al, byte[A1-0x80]); + add(A1, LDA); + mov(byte[B-0x7f], al); + sub(B, -2); + align(4); + +L(l9b0); + test(M, 0x1); + jle(l9c8, T_NEAR); + mov(al, byte[A1-0x80]); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l9c8); + sub(N, 0x1); + cmp(N, 0x1); + jge(l8bc, T_NEAR); + align(4); + +L(l9d8); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_at_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_at_kern.cpp new file mode 100644 index 000000000000..1c11fc6cef13 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_at_kern.cpp @@ -0,0 +1,2209 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_at_kern::jit_avx512_core_u8_copy_at_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l1014; +Xbyak::Label l1390; +Xbyak::Label l159c; +Xbyak::Label l173c; +Xbyak::Label l18e4; +Xbyak::Label l1a7c; +Xbyak::Label l1a8c; +Xbyak::Label l1a98; +Xbyak::Label l1ab4; +Xbyak::Label l1c64; +Xbyak::Label l1d74; +Xbyak::Label l1e50; +Xbyak::Label l1f2c; +Xbyak::Label l1ffc; +Xbyak::Label l20; +Xbyak::Label l200c; +Xbyak::Label l2018; +Xbyak::Label l2034; +Xbyak::Label l2110; +Xbyak::Label l21a0; +Xbyak::Label l2210; +Xbyak::Label l2284; +Xbyak::Label l22f0; +Xbyak::Label l2300; +Xbyak::Label l230c; +Xbyak::Label l2324; +Xbyak::Label l2398; +Xbyak::Label l23e8; +Xbyak::Label l242c; +Xbyak::Label l2474; +Xbyak::Label l24b4; +Xbyak::Label l24c4; +Xbyak::Label l24d0; +Xbyak::Label l24e8; +Xbyak::Label l2520; +Xbyak::Label l254c; +Xbyak::Label l2578; +Xbyak::Label l25a8; +Xbyak::Label l25c8; +Xbyak::Label l25d6; +Xbyak::Label l25e0; +Xbyak::Label l25f0; +Xbyak::Label l260c; +Xbyak::Label l262c; +Xbyak::Label l264c; +Xbyak::Label l2668; +Xbyak::Label l2680; +Xbyak::Label l2690; +Xbyak::Label l44; +Xbyak::Label l58c; +Xbyak::Label l8b0; +Xbyak::Label lb14; +Xbyak::Label ld84; +Xbyak::Label lfdc; +Xbyak::Label lfec; +Xbyak::Label lff8; + + preamble(); +#ifdef _WIN32 + auto stacksize = get_size_of_abi_save_regs(); + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(N, qword[N]); + mov(M, qword[M]); + mov(LDA, qword[LDA]); + sub(A, -128); + sub(B, -128); + lea(LDA3, ptr[LDA+LDA*2]); + cmp(N, 0x30); + jl(lfec, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + mov(I, LDA); + shl(I, 0x5); + lea(I, ptr[I+LDA*8]); + lea(I, ptr[I+LDA*8]); + add(A, I); + mov(I, M); + sar(I, 0x4); + jle(l58c, T_NEAR); + align(4); + +L(l44); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B+0x40], xmm1); + movdqu(xword[B+0x100], xmm4); + movdqu(xword[B+0x1c0], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B+0x50], xmm1); + movdqu(xword[B+0x110], xmm4); + movdqu(xword[B+0x1d0], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B+0x60], xmm1); + movdqu(xword[B+0x120], xmm4); + movdqu(xword[B+0x1e0], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x50], xmm0); + movdqu(xword[B+0x70], xmm1); + movdqu(xword[B+0x130], xmm4); + movdqu(xword[B+0x1f0], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x40], xmm0); + movdqu(xword[B+0x80], xmm1); + movdqu(xword[B+0x140], xmm4); + movdqu(xword[B+0x200], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x30], xmm0); + movdqu(xword[B+0x90], xmm1); + movdqu(xword[B+0x150], xmm4); + movdqu(xword[B+0x210], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x20], xmm0); + movdqu(xword[B+0xa0], xmm1); + movdqu(xword[B+0x160], xmm4); + movdqu(xword[B+0x220], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x10], xmm0); + movdqu(xword[B+0xb0], xmm1); + movdqu(xword[B+0x170], xmm4); + movdqu(xword[B+0x230], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B], xmm0); + movdqu(xword[B+0xc0], xmm1); + movdqu(xword[B+0x180], xmm4); + movdqu(xword[B+0x240], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B+0x10], xmm0); + movdqu(xword[B+0xd0], xmm1); + movdqu(xword[B+0x190], xmm4); + movdqu(xword[B+0x250], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B+0x20], xmm0); + movdqu(xword[B+0xe0], xmm1); + movdqu(xword[B+0x1a0], xmm4); + movdqu(xword[B+0x260], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B+0x30], xmm0); + movdqu(xword[B+0xf0], xmm1); + movdqu(xword[B+0x1b0], xmm4); + movdqu(xword[B+0x270], xmm3); + sub(A1, -16); + sub(B, -768); + dec(I); + jg(l44, T_NEAR); + align(4); + +L(l58c); + test(M, 0x8); + jle(l8b0, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B+0x40], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B+0x50], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B+0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x50], xmm0); + movdqu(xword[B+0x70], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x40], xmm0); + movdqu(xword[B+0x80], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x30], xmm0); + movdqu(xword[B+0x90], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x20], xmm0); + movdqu(xword[B+0xa0], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x10], xmm0); + movdqu(xword[B+0xb0], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B], xmm0); + movdqu(xword[B+0xc0], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B+0x10], xmm0); + movdqu(xword[B+0xd0], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B+0x20], xmm0); + movdqu(xword[B+0xe0], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B+0x30], xmm0); + movdqu(xword[B+0xf0], xmm1); + sub(A1, -8); + sub(B, -384); + align(4); + +L(l8b0); + test(M, 0x4); + jle(lb14, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x60], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x50], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x40], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x30], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x20], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x10], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B+0x10], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B+0x20], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B+0x30], xmm0); + sub(A1, -4); + sub(B, -192); + align(4); + +L(lb14); + test(M, 0x2); + jle(ld84, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x7); + movdqu(xword[B-0x80], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x70], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x60], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x50], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x40], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x30], xmm0); + sub(A1, -2); + sub(B, -96); + align(4); + +L(ld84); + test(M, 0x1); + jle(lfdc, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + movdqu(xword[B-0x80], xmm0); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + movdqu(xword[B-0x70], xmm0); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + movdqu(xword[B-0x60], xmm0); + sub(B, -48); + align(4); + +L(lfdc); + sub(N, 0x30); + cmp(N, 0x30); + jge(l20, T_NEAR); + align(4); + +L(lfec); + cmp(N, 0x20); + jl(l1a8c, T_NEAR); + align(4); + +L(lff8); + mov(A1, A); + mov(I, LDA); + shl(I, 0x5); + add(A, I); + mov(I, M); + sar(I, 0x4); + jle(l1390, T_NEAR); + align(4); + +L(l1014); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B], xmm1); + movdqu(xword[B+0x80], xmm4); + movdqu(xword[B+0x100], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B+0x10], xmm1); + movdqu(xword[B+0x90], xmm4); + movdqu(xword[B+0x110], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B+0x20], xmm1); + movdqu(xword[B+0xa0], xmm4); + movdqu(xword[B+0x120], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x50], xmm0); + movdqu(xword[B+0x30], xmm1); + movdqu(xword[B+0xb0], xmm4); + movdqu(xword[B+0x130], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x40], xmm0); + movdqu(xword[B+0x40], xmm1); + movdqu(xword[B+0xc0], xmm4); + movdqu(xword[B+0x140], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x30], xmm0); + movdqu(xword[B+0x50], xmm1); + movdqu(xword[B+0xd0], xmm4); + movdqu(xword[B+0x150], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x20], xmm0); + movdqu(xword[B+0x60], xmm1); + movdqu(xword[B+0xe0], xmm4); + movdqu(xword[B+0x160], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x10], xmm0); + movdqu(xword[B+0x70], xmm1); + movdqu(xword[B+0xf0], xmm4); + movdqu(xword[B+0x170], xmm3); + sub(A1, -16); + sub(B, -512); + dec(I); + jg(l1014, T_NEAR); + align(4); + +L(l1390); + test(M, 0x8); + jle(l159c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B+0x10], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B+0x20], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x50], xmm0); + movdqu(xword[B+0x30], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x40], xmm0); + movdqu(xword[B+0x40], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x30], xmm0); + movdqu(xword[B+0x50], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x20], xmm0); + movdqu(xword[B+0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x10], xmm0); + movdqu(xword[B+0x70], xmm1); + sub(A1, -8); + sub(B, -256); + align(4); + +L(l159c); + test(M, 0x4); + jle(l173c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x60], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x50], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x40], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x30], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x20], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x10], xmm0); + sub(A1, -4); + sub(B, -128); + align(4); + +L(l173c); + test(M, 0x2); + jle(l18e4, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x7); + movdqu(xword[B-0x80], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x70], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x60], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + movdqu(xword[B-0x50], xmm0); + sub(A1, -2); + sub(B, -64); + align(4); + +L(l18e4); + test(M, 0x1); + jle(l1a7c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + movdqu(xword[B-0x80], xmm0); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + align(4); + +L(l1a7c); + sub(N, 0x20); + cmp(N, 0x20); + jge(lff8, T_NEAR); + align(4); + +L(l1a8c); + cmp(N, 0x10); + jl(l200c, T_NEAR); + align(4); + +L(l1a98); + mov(A1, A); + mov(I, LDA); + shl(I, 0x4); + add(A, I); + mov(I, M); + sar(I, 0x4); + jle(l1c64, T_NEAR); + align(4); + +L(l1ab4); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x40], xmm1); + movdqu(xword[B], xmm4); + movdqu(xword[B+0x40], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B-0x30], xmm1); + movdqu(xword[B+0x10], xmm4); + movdqu(xword[B+0x50], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B-0x20], xmm1); + movdqu(xword[B+0x20], xmm4); + movdqu(xword[B+0x60], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x50], xmm0); + movdqu(xword[B-0x10], xmm1); + movdqu(xword[B+0x30], xmm4); + movdqu(xword[B+0x70], xmm3); + sub(A1, -16); + sub(B, -256); + dec(I); + jg(l1ab4, T_NEAR); + align(4); + +L(l1c64); + test(M, 0x8); + jle(l1d74, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x40], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B-0x30], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B-0x20], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x50], xmm0); + movdqu(xword[B-0x10], xmm1); + sub(A1, -8); + sub(B, -128); + align(4); + +L(l1d74); + test(M, 0x4); + jle(l1e50, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x60], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x50], xmm0); + sub(A1, -4); + sub(B, -64); + align(4); + +L(l1e50); + test(M, 0x2); + jle(l1f2c, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x7); + movdqu(xword[B-0x80], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + movdqu(xword[B-0x70], xmm0); + sub(A1, -2); + sub(B, -32); + align(4); + +L(l1f2c); + test(M, 0x1); + jle(l1ffc, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0xf); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l1ffc); + sub(N, 0x10); + cmp(N, 0x10); + jge(l1a98, T_NEAR); + align(4); + +L(l200c); + cmp(N, 0x8); + jl(l2300, T_NEAR); + align(4); + +L(l2018); + mov(A1, A); + lea(A2, ptr[A1+LDA*4]); + lea(I, ptr[A1+LDA*8]); + mov(A, I); + mov(I, M); + sar(I, 0x4); + jle(l2110, T_NEAR); + align(4); + +L(l2034); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + sub(A1, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x60], xmm1); + movdqu(xword[B-0x40], xmm4); + movdqu(xword[B-0x20], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B-0x50], xmm1); + movdqu(xword[B-0x30], xmm4); + movdqu(xword[B-0x10], xmm3); + sub(B, -128); + dec(I); + jg(l2034, T_NEAR); + align(4); + +L(l2110); + test(M, 0x8); + jle(l21a0, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + sub(A1, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + align(4); + +L(l21a0); + test(M, 0x4); + jle(l2210, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + sub(A1, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + align(4); + +L(l2210); + test(M, 0x2); + jle(l2284, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x7); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l2284); + test(M, 0x1); + jle(l22f0, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x7); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l22f0); + sub(N, 0x8); + cmp(N, 0x8); + jge(l2018, T_NEAR); + align(4); + +L(l2300); + cmp(N, 0x4); + jl(l24c4, T_NEAR); + align(4); + +L(l230c); + mov(A1, A); + lea(A2, ptr[A1+LDA*2]); + lea(I, ptr[A1+LDA*4]); + mov(A, I); + mov(I, M); + sar(I, 0x4); + jle(l2398, T_NEAR); + align(4); + +L(l2324); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + sub(A1, -16); + movdqu(xmm2, xword[A2-0x80]); + movdqu(xmm3, xword[A2+LDA*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movdqu(xword[B-0x60], xmm4); + movdqu(xword[B-0x50], xmm3); + sub(B, -64); + dec(I); + jg(l2324, T_NEAR); + align(4); + +L(l2398); + test(M, 0x8); + jle(l23e8, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + sub(A1, -8); + movq(xmm2, qword[A2-0x80]); + movq(xmm3, qword[A2+LDA*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l23e8); + test(M, 0x4); + jle(l242c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + sub(A1, -4); + movd(xmm2, dword[A2-0x80]); + movd(xmm3, dword[A2+LDA*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l242c); + test(M, 0x2); + jle(l2474, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x3); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l2474); + test(M, 0x1); + jle(l24b4, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x3); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l24b4); + sub(N, 0x4); + cmp(N, 0x4); + jge(l230c, T_NEAR); + align(4); + +L(l24c4); + cmp(N, 0x2); + jl(l25d6, T_NEAR); + align(4); + +L(l24d0); + mov(A1, A); + lea(A2, ptr[A1+LDA*1]); + lea(I, ptr[A1+LDA*2]); + mov(A, I); + mov(I, M); + sar(I, 0x4); + jle(l2520, T_NEAR); + align(4); + +L(l24e8); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + movdqu(xmm1, xword[A2-0x80]); + sub(A2, -16); + movdqa(xmm2, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm2, xmm1); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm2); + sub(B, -32); + dec(I); + jg(l24e8, T_NEAR); + align(4); + +L(l2520); + test(M, 0x8); + jle(l254c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + movq(xmm1, qword[A2-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l254c); + test(M, 0x4); + jle(l2578, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + movd(xmm1, dword[A2-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l2578); + test(M, 0x2); + jle(l25a8, T_NEAR); + mov(ax, word[A1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x1); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l25a8); + test(M, 0x1); + jle(l25c8, T_NEAR); + mov(al, byte[A1-0x80]); + mov(byte[B-0x80], al); + mov(al, byte[A2-0x80]); + mov(byte[B-0x7f], al); + sub(B, -2); + align(4); + +L(l25c8); + sub(N, 0x2); + cmp(N, 0x2); + jge(l24d0, T_NEAR); + align(4); + +L(l25d6); + cmp(N, 0x1); + jl(l2690, T_NEAR); + align(4); + +L(l25e0); + mov(A1, A); + add(A, LDA); + mov(I, M); + sar(I, 0x4); + jle(l260c, T_NEAR); + align(4); + +L(l25f0); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(I); + jg(l25f0, T_NEAR); + align(4); + +L(l260c); + test(M, 0x8); + jle(l262c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l262c); + test(M, 0x4); + jle(l264c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l264c); + test(M, 0x2); + jle(l2668, T_NEAR); + mov(ax, word[A1-0x80]); + mov(word[B-0x80], ax); + sub(A1, -2); + sub(B, -2); + align(4); + +L(l2668); + test(M, 0x1); + jle(l2680, T_NEAR); + mov(al, byte[A1-0x80]); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l2680); + sub(N, 0x1); + cmp(N, 0x1); + jge(l25e0, T_NEAR); + align(4); + +L(l2690); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bn_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bn_kern.cpp new file mode 100644 index 000000000000..56c36ee14a6e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bn_kern.cpp @@ -0,0 +1,564 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_bn_kern::jit_avx512_core_u8_copy_bn_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l118; +Xbyak::Label l1a8; +Xbyak::Label l20; +Xbyak::Label l218; +Xbyak::Label l28c; +Xbyak::Label l2f8; +Xbyak::Label l308; +Xbyak::Label l314; +Xbyak::Label l32c; +Xbyak::Label l3a0; +Xbyak::Label l3c; +Xbyak::Label l3f0; +Xbyak::Label l434; +Xbyak::Label l47c; +Xbyak::Label l4bc; +Xbyak::Label l4cc; +Xbyak::Label l4d8; +Xbyak::Label l4f0; +Xbyak::Label l528; +Xbyak::Label l554; +Xbyak::Label l580; +Xbyak::Label l5b0; +Xbyak::Label l5d0; +Xbyak::Label l5de; +Xbyak::Label l5e8; +Xbyak::Label l5f8; +Xbyak::Label l614; +Xbyak::Label l634; +Xbyak::Label l654; +Xbyak::Label l670; +Xbyak::Label l688; +Xbyak::Label l698; + + preamble(); +#ifdef _WIN32 + auto stacksize = get_size_of_abi_save_regs(); + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(N, qword[N]); + mov(M, qword[M]); + mov(LDA, qword[LDA]); + sub(A, -128); + sub(B, -128); + lea(LDA3, ptr[LDA+LDA*2]); + cmp(N, 0x8); + jl(l308, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + lea(A2, ptr[A1+LDA*4]); + lea(I, ptr[A1+LDA*8]); + mov(A, I); + mov(I, M); + sar(I, 0x4); + jle(l118, T_NEAR); + align(4); + +L(l3c); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + sub(A1, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x60], xmm1); + movdqu(xword[B-0x40], xmm4); + movdqu(xword[B-0x20], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B-0x50], xmm1); + movdqu(xword[B-0x30], xmm4); + movdqu(xword[B-0x10], xmm3); + sub(B, -128); + dec(I); + jg(l3c, T_NEAR); + align(4); + +L(l118); + test(M, 0x8); + jle(l1a8, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + sub(A1, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x70], xmm0); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + align(4); + +L(l1a8); + test(M, 0x4); + jle(l218, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + sub(A1, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + align(4); + +L(l218); + test(M, 0x2); + jle(l28c, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x7); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l28c); + test(M, 0x1); + jle(l2f8, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x7); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l2f8); + sub(N, 0x8); + cmp(N, 0x8); + jge(l20, T_NEAR); + align(4); + +L(l308); + cmp(N, 0x4); + jl(l4cc, T_NEAR); + align(4); + +L(l314); + mov(A1, A); + lea(A2, ptr[A1+LDA*2]); + lea(I, ptr[A1+LDA*4]); + mov(A, I); + mov(I, M); + sar(I, 0x4); + jle(l3a0, T_NEAR); + align(4); + +L(l32c); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + sub(A1, -16); + movdqu(xmm2, xword[A2-0x80]); + movdqu(xmm3, xword[A2+LDA*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movdqu(xword[B-0x60], xmm4); + movdqu(xword[B-0x50], xmm3); + sub(B, -64); + dec(I); + jg(l32c, T_NEAR); + align(4); + +L(l3a0); + test(M, 0x8); + jle(l3f0, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + sub(A1, -8); + movq(xmm2, qword[A2-0x80]); + movq(xmm3, qword[A2+LDA*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l3f0); + test(M, 0x4); + jle(l434, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + sub(A1, -4); + movd(xmm2, dword[A2-0x80]); + movd(xmm3, dword[A2+LDA*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l434); + test(M, 0x2); + jle(l47c, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x3); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l47c); + test(M, 0x1); + jle(l4bc, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x3); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l4bc); + sub(N, 0x4); + cmp(N, 0x4); + jge(l314, T_NEAR); + align(4); + +L(l4cc); + cmp(N, 0x2); + jl(l5de, T_NEAR); + align(4); + +L(l4d8); + mov(A1, A); + lea(A2, ptr[A1+LDA*1]); + lea(I, ptr[A1+LDA*2]); + mov(A, I); + mov(I, M); + sar(I, 0x4); + jle(l528, T_NEAR); + align(4); + +L(l4f0); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + movdqu(xmm1, xword[A2-0x80]); + sub(A2, -16); + movdqa(xmm2, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm2, xmm1); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm2); + sub(B, -32); + dec(I); + jg(l4f0, T_NEAR); + align(4); + +L(l528); + test(M, 0x8); + jle(l554, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + movq(xmm1, qword[A2-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l554); + test(M, 0x4); + jle(l580, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + movd(xmm1, dword[A2-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l580); + test(M, 0x2); + jle(l5b0, T_NEAR); + mov(ax, word[A1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x1); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l5b0); + test(M, 0x1); + jle(l5d0, T_NEAR); + mov(al, byte[A1-0x80]); + mov(byte[B-0x80], al); + mov(al, byte[A2-0x80]); + mov(byte[B-0x7f], al); + sub(B, -2); + align(4); + +L(l5d0); + sub(N, 0x2); + cmp(N, 0x2); + jge(l4d8, T_NEAR); + align(4); + +L(l5de); + cmp(N, 0x1); + jl(l698, T_NEAR); + align(4); + +L(l5e8); + mov(A1, A); + add(A, LDA); + mov(I, M); + sar(I, 0x4); + jle(l614, T_NEAR); + align(4); + +L(l5f8); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(I); + jg(l5f8, T_NEAR); + align(4); + +L(l614); + test(M, 0x8); + jle(l634, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l634); + test(M, 0x4); + jle(l654, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l654); + test(M, 0x2); + jle(l670, T_NEAR); + mov(ax, word[A1-0x80]); + mov(word[B-0x80], ax); + sub(A1, -2); + sub(B, -2); + align(4); + +L(l670); + test(M, 0x1); + jle(l688, T_NEAR); + mov(al, byte[A1-0x80]); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l688); + sub(N, 0x1); + cmp(N, 0x1); + jge(l5e8, T_NEAR); + align(4); + +L(l698); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bt_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bt_kern.cpp new file mode 100644 index 000000000000..53e99d94de18 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_bt_kern.cpp @@ -0,0 +1,501 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_bt_kern::jit_avx512_core_u8_copy_bt_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l120; +Xbyak::Label l14c; +Xbyak::Label l168; +Xbyak::Label l178; +Xbyak::Label l184; +Xbyak::Label l194; +Xbyak::Label l20; +Xbyak::Label l20c; +Xbyak::Label l250; +Xbyak::Label l27c; +Xbyak::Label l298; +Xbyak::Label l2a8; +Xbyak::Label l2b4; +Xbyak::Label l2c8; +Xbyak::Label l34; +Xbyak::Label l360; +Xbyak::Label l3b4; +Xbyak::Label l3e8; +Xbyak::Label l400; +Xbyak::Label l40e; +Xbyak::Label l418; +Xbyak::Label l428; +Xbyak::Label l4a0; +Xbyak::Label l4e8; +Xbyak::Label l50c; +Xbyak::Label l524; +Xbyak::Label l534; +Xbyak::Label lcc; + + preamble(); +#ifdef _WIN32 + auto stacksize = get_size_of_abi_save_regs(); + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(M, qword[M]); + mov(N, qword[N]); + mov(LDA, qword[LDA]); + lea(LDA3, ptr[LDA+LDA*2]); + sub(A, -128); + sub(B, -128); + cmp(N, 0x8); + jl(l178, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + add(A, 0x8); + mov(I, M); + sar(I, 0x3); + jle(lcc, T_NEAR); + align(4); + +L(l34); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + dec(I); + jg(l34, T_NEAR); + align(4); + +L(lcc); + test(M, 0x4); + jle(l120, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l120); + test(M, 0x2); + jle(l14c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l14c); + test(M, 0x1); + jle(l168, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l168); + sub(N, 0x8); + cmp(N, 0x8); + jge(l20, T_NEAR); + align(4); + +L(l178); + cmp(N, 0x4); + jl(l2a8, T_NEAR); + align(4); + +L(l184); + mov(A1, A); + add(A, 0x4); + mov(I, M); + sar(I, 0x3); + jle(l20c, T_NEAR); + align(4); + +L(l194); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + dec(I); + jg(l194, T_NEAR); + align(4); + +L(l20c); + test(M, 0x4); + jle(l250, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l250); + test(M, 0x2); + jle(l27c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l27c); + test(M, 0x1); + jle(l298, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l298); + sub(N, 0x4); + cmp(N, 0x4); + jge(l184, T_NEAR); + align(4); + +L(l2a8); + cmp(N, 0x2); + jl(l40e, T_NEAR); + align(4); + +L(l2b4); + mov(A1, A); + add(A, 0x2); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l360, T_NEAR); + align(4); + +L(l2c8); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm4, eax, 0x0); + punpcklbw(xmm1, xmm2); + punpcklbw(xmm3, xmm4); + punpcklwd(xmm1, xmm3); + punpcklqdq(xmm0, xmm1); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(LDA3); + jg(l2c8, T_NEAR); + align(4); + +L(l360); + test(M, 0x4); + jle(l3b4, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l3b4); + test(M, 0x2); + jle(l3e8, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + punpcklbw(xmm0, xmm1); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l3e8); + test(M, 0x1); + jle(l400, T_NEAR); + mov(ax, word[A1-0x80]); + mov(word[B-0x80], ax); + sub(B, -2); + align(4); + +L(l400); + sub(N, 0x2); + cmp(N, 0x2); + jge(l2b4, T_NEAR); + align(4); + +L(l40e); + cmp(N, 0x1); + jl(l534, T_NEAR); + align(4); + +L(l418); + mov(A1, A); + add(A, 0x1); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l4a0, T_NEAR); + align(4); + +L(l428); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x7); + movq(qword[B-0x80], xmm0); + sub(B, -8); + dec(LDA3); + jg(l428, T_NEAR); + align(4); + +L(l4a0); + test(M, 0x4); + jle(l4e8, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l4e8); + test(M, 0x2); + jle(l50c, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + mov(byte[B-0x80], al); + mov(al, byte[A1-0x80]); + add(A1, LDA); + mov(byte[B-0x7f], al); + sub(B, -2); + align(4); + +L(l50c); + test(M, 0x1); + jle(l524, T_NEAR); + mov(al, byte[A1-0x80]); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l524); + sub(N, 0x1); + cmp(N, 0x1); + jge(l418, T_NEAR); + align(4); + +L(l534); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_an_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_an_kern.cpp new file mode 100644 index 000000000000..49a312fc8826 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_an_kern.cpp @@ -0,0 +1,1283 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_sum_an_kern::jit_avx512_core_u8_copy_sum_an_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#define ARG_BIAS 24+stacksize+rsp + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp +#define ARG_BIAS 72+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l1024; +Xbyak::Label l1090; +Xbyak::Label l10d4; +Xbyak::Label l10fc; +Xbyak::Label l111a; +Xbyak::Label l1124; +Xbyak::Label l113c; +Xbyak::Label l11d4; +Xbyak::Label l1234; +Xbyak::Label l1278; +Xbyak::Label l129c; +Xbyak::Label l12bc; +Xbyak::Label l20; +Xbyak::Label l2a0; +Xbyak::Label l3c0; +Xbyak::Label l438; +Xbyak::Label l480; +Xbyak::Label l48c; +Xbyak::Label l4c8; +Xbyak::Label l5c; +Xbyak::Label l6a8; +Xbyak::Label l7b4; +Xbyak::Label l850; +Xbyak::Label l89c; +Xbyak::Label l8a8; +Xbyak::Label l8d0; +Xbyak::Label l9d0; +Xbyak::Label la64; +Xbyak::Label lab8; +Xbyak::Label lae8; +Xbyak::Label laf4; +Xbyak::Label lb14; +Xbyak::Label lc30; +Xbyak::Label lcc8; +Xbyak::Label ld1c; +Xbyak::Label ld54; +Xbyak::Label ld78; +Xbyak::Label ld84; +Xbyak::Label ld9c; +Xbyak::Label le58; +Xbyak::Label lebc; +Xbyak::Label lef8; +Xbyak::Label lf1c; +Xbyak::Label lf3c; +Xbyak::Label lf48; +Xbyak::Label lf60; + + preamble(); + auto stacksize = get_size_of_abi_save_regs(); +#ifdef _WIN32 + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(M, qword[M]); + mov(N, qword[N]); + mov(LDA, qword[LDA]); + lea(LDA3, ptr[LDA+LDA*2]); + sub(A, -128); + sub(B, -128); + cmp(N, 0x30); + jl(l480, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + add(A, 0x30); + vxorps(ymm8, ymm8, ymm8); + vxorps(ymm9, ymm9, ymm9); + vxorps(ymm10, ymm10, ymm10); + vxorps(ymm11, ymm11, ymm11); + vxorps(ymm12, ymm12, ymm12); + vxorps(ymm13, ymm13, ymm13); + vxorps(ymm14, ymm14, ymm14); + vxorps(ymm15, ymm15, ymm15); + mov(I, M); + sar(I, 0x2); + jle(l2a0, T_NEAR); + align(4); + +L(l5c); + vmovdqu(xmm0, xword[A1-0x80]); + vmovdqu(xmm1, xword[A1+LDA*1-0x80]); + vmovdqu(xmm2, xword[A1+LDA*2-0x80]); + vmovdqu(xmm3, xword[A1+LDA3*1-0x80]); + vpunpcklbw(xmm4, xmm0, xmm1); + vpunpckhbw(xmm5, xmm0, xmm1); + vpunpcklbw(xmm6, xmm2, xmm3); + vpunpckhbw(xmm7, xmm2, xmm3); + vpunpcklwd(xmm0, xmm4, xmm6); + vpunpckhwd(xmm1, xmm4, xmm6); + vpunpcklwd(xmm2, xmm5, xmm7); + vpunpckhwd(xmm3, xmm5, xmm7); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm8, ymm8, ymm5); + vmovdqu(xword[B-0x80], xmm0); + vmovdqu(xword[B-0x70], xmm1); + vpmovsxbw(ymm5, xmm2); + vmovhlps(xmm6, xmm2, xmm2); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm9, ymm9, ymm5); + vmovdqu(xword[B-0x60], xmm2); + vmovdqu(xword[B-0x50], xmm3); + vmovdqu(xmm0, xword[A1-0x70]); + vmovdqu(xmm1, xword[A1+LDA*1-0x70]); + vmovdqu(xmm2, xword[A1+LDA*2-0x70]); + vmovdqu(xmm3, xword[A1+LDA3*1-0x70]); + vpunpcklbw(xmm4, xmm0, xmm1); + vpunpckhbw(xmm5, xmm0, xmm1); + vpunpcklbw(xmm6, xmm2, xmm3); + vpunpckhbw(xmm7, xmm2, xmm3); + vpunpcklwd(xmm0, xmm4, xmm6); + vpunpckhwd(xmm1, xmm4, xmm6); + vpunpcklwd(xmm2, xmm5, xmm7); + vpunpckhwd(xmm3, xmm5, xmm7); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm10, ymm10, ymm5); + vmovdqu(xword[B-0x40], xmm0); + vmovdqu(xword[B-0x30], xmm1); + vpmovsxbw(ymm5, xmm2); + vmovhlps(xmm6, xmm2, xmm2); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm11, ymm11, ymm5); + vmovdqu(xword[B-0x20], xmm2); + vmovdqu(xword[B-0x10], xmm3); + vmovdqu(xmm0, xword[A1-0x60]); + vmovdqu(xmm1, xword[A1+LDA*1-0x60]); + vmovdqu(xmm2, xword[A1+LDA*2-0x60]); + vmovdqu(xmm3, xword[A1+LDA3*1-0x60]); + lea(A1, ptr[A1+LDA*4]); + vpunpcklbw(xmm4, xmm0, xmm1); + vpunpckhbw(xmm5, xmm0, xmm1); + vpunpcklbw(xmm6, xmm2, xmm3); + vpunpckhbw(xmm7, xmm2, xmm3); + vpunpcklwd(xmm0, xmm4, xmm6); + vpunpckhwd(xmm1, xmm4, xmm6); + vpunpcklwd(xmm2, xmm5, xmm7); + vpunpckhwd(xmm3, xmm5, xmm7); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm12, ymm12, ymm5); + vmovdqu(xword[B], xmm0); + vmovdqu(xword[B+0x10], xmm1); + vpmovsxbw(ymm5, xmm2); + vmovhlps(xmm6, xmm2, xmm2); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm13, ymm13, ymm5); + vmovdqu(xword[B+0x20], xmm2); + vmovdqu(xword[B+0x30], xmm3); + sub(B, -192); + dec(I); + jg(l5c, T_NEAR); + align(4); + +L(l2a0); + test(M, 0x2); + jle(l3c0, T_NEAR); + vmovdqu(xmm0, xword[A1-0x80]); + vmovdqu(xmm1, xword[A1-0x70]); + vmovdqu(xmm2, xword[A1-0x60]); + add(A1, LDA); + vmovdqu(xmm6, xword[A1-0x80]); + vmovdqu(xmm4, xword[A1-0x70]); + vmovdqu(xmm5, xword[A1-0x60]); + add(A1, LDA); + vpunpcklbw(xmm3, xmm0, xmm6); + vpunpckhbw(xmm0, xmm0, xmm6); + vpmovsxbw(ymm7, xmm3); + vmovhlps(xmm6, xmm3, xmm3); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm7, ymm7, ymm6); + vpmovsxwd(ymm7, xmm7); + vpaddd(ymm8, ymm8, ymm7); + vmovdqu(xword[B-0x80], xmm3); + vpmovsxbw(ymm7, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm7, ymm7, ymm6); + vpmovsxwd(ymm7, xmm7); + vpaddd(ymm9, ymm9, ymm7); + vmovdqu(xword[B-0x70], xmm0); + vpunpcklbw(xmm3, xmm1, xmm4); + vpunpckhbw(xmm0, xmm1, xmm4); + vpmovsxbw(ymm7, xmm3); + vmovhlps(xmm6, xmm3, xmm3); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm7, ymm7, ymm6); + vpmovsxwd(ymm7, xmm7); + vpaddd(ymm10, ymm10, ymm7); + vmovdqu(xword[B-0x60], xmm3); + vpmovsxbw(ymm7, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm7, ymm7, ymm6); + vpmovsxwd(ymm7, xmm7); + vpaddd(ymm11, ymm11, ymm7); + vmovdqu(xword[B-0x50], xmm0); + vpunpcklbw(xmm3, xmm2, xmm5); + vpunpckhbw(xmm0, xmm2, xmm5); + vpmovsxbw(ymm7, xmm3); + vmovhlps(xmm6, xmm3, xmm3); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm7, ymm7, ymm6); + vpmovsxwd(ymm7, xmm7); + vpaddd(ymm12, ymm12, ymm7); + vmovdqu(xword[B-0x40], xmm3); + vpmovsxbw(ymm7, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm7, ymm7, ymm6); + vpmovsxwd(ymm7, xmm7); + vpaddd(ymm13, ymm13, ymm7); + vmovdqu(xword[B-0x30], xmm0); + sub(B, -96); + align(4); + +L(l3c0); + test(M, 0x1); + jle(l438, T_NEAR); + vmovdqu(xmm0, xword[A1-0x80]); + vmovdqu(xmm1, xword[A1-0x70]); + vmovdqu(xmm2, xword[A1-0x60]); + add(A1, LDA); + vpmovsxbd(ymm7, xmm0); + vpaddd(ymm8, ymm8, ymm7); + vmovhlps(xmm7, xmm0, xmm0); + vpmovsxbd(ymm7, xmm7); + vpaddd(ymm9, ymm9, ymm7); + vmovdqu(xword[B-0x80], xmm0); + vpmovsxbd(ymm7, xmm1); + vpaddd(ymm10, ymm10, ymm7); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbd(ymm7, xmm7); + vpaddd(ymm11, ymm11, ymm7); + vmovdqu(xword[B-0x70], xmm1); + vpmovsxbd(ymm7, xmm2); + vpaddd(ymm12, ymm12, ymm7); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbd(ymm7, xmm7); + vpaddd(ymm13, ymm13, ymm7); + vmovdqu(xword[B-0x60], xmm2); + sub(B, -48); + align(4); + +L(l438); + mov(A1, qword[ARG_BIAS]); + vmovdqu(yword[A1], ymm8); + vmovdqu(yword[A1+0x20], ymm9); + vmovdqu(yword[A1+0x40], ymm10); + vmovdqu(yword[A1+0x60], ymm11); + vmovdqu(yword[A1+0x80], ymm12); + vmovdqu(yword[A1+0xa0], ymm13); + add(qword[ARG_BIAS], 0xc0); + sub(N, 0x30); + cmp(N, 0x30); + jge(l20, T_NEAR); + vzeroupper(); + align(4); + +L(l480); + cmp(N, 0x20); + jl(l89c, T_NEAR); + align(4); + +L(l48c); + mov(A1, A); + add(A, 0x20); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + pxor(xmm10, xmm10); + pxor(xmm11, xmm11); + pxor(xmm12, xmm12); + pxor(xmm13, xmm13); + pxor(xmm14, xmm14); + pxor(xmm15, xmm15); + mov(I, M); + sar(I, 0x2); + jle(l6a8, T_NEAR); + align(4); + +L(l4c8); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm4); + pmovsxbw(xmm5, xmm2); + movhlps(xmm6, xmm2); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm2); + movdqu(xmm0, xword[A1-0x70]); + movdqu(xmm1, xword[A1+LDA*1-0x70]); + movdqu(xmm2, xword[A1+LDA*2-0x70]); + movdqu(xmm3, xword[A1+LDA3*1-0x70]); + lea(A1, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm5); + punpckhwd(xmm2, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B-0x40], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B-0x30], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B-0x20], xmm4); + pmovsxbw(xmm5, xmm2); + movhlps(xmm6, xmm2); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B-0x10], xmm2); + sub(B, -128); + dec(I); + jg(l4c8, T_NEAR); + align(4); + +L(l6a8); + test(M, 0x2); + jle(l7b4, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1-0x70]); + add(A1, LDA); + movdqu(xmm2, xword[A1-0x80]); + movdqu(xmm3, xword[A1-0x70]); + add(A1, LDA); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm2); + punpckhbw(xmm4, xmm2); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm4); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x70], xmm4); + movdqa(xmm4, xmm1); + punpcklbw(xmm1, xmm3); + punpckhbw(xmm4, xmm3); + pmovsxbw(xmm5, xmm1); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm13, xmm6); + movdqu(xword[B-0x60], xmm1); + pmovsxbw(xmm5, xmm4); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm15, xmm6); + movdqu(xword[B-0x50], xmm4); + sub(B, -64); + align(4); + +L(l7b4); + test(M, 0x1); + jle(l850, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1-0x70]); + add(A1, LDA); + pmovsxbd(xmm5, xmm0); + paddd(xmm8, xmm5); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm9, xmm6); + pshufd(xmm5, xmm0, 0xaa); + pmovsxbd(xmm5, xmm5); + paddd(xmm10, xmm5); + pshufd(xmm6, xmm0, 0xff); + pmovsxbd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x80], xmm0); + pmovsxbd(xmm5, xmm1); + paddd(xmm12, xmm5); + pshufd(xmm6, xmm1, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm13, xmm6); + pshufd(xmm5, xmm1, 0xaa); + pmovsxbd(xmm5, xmm5); + paddd(xmm14, xmm5); + pshufd(xmm6, xmm1, 0xff); + pmovsxbd(xmm6, xmm6); + paddd(xmm15, xmm6); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l850); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + movdqu(xword[A1+0x20], xmm10); + movdqu(xword[A1+0x30], xmm11); + movdqu(xword[A1+0x40], xmm12); + movdqu(xword[A1+0x50], xmm13); + movdqu(xword[A1+0x60], xmm14); + movdqu(xword[A1+0x70], xmm15); + add(qword[ARG_BIAS], 0x80); + sub(N, 0x20); + cmp(N, 0x20); + jge(l48c, T_NEAR); + align(4); + +L(l89c); + cmp(N, 0x10); + jl(lae8, T_NEAR); + align(4); + +L(l8a8); + mov(A1, A); + add(A, 0x10); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + pxor(xmm10, xmm10); + pxor(xmm11, xmm11); + mov(I, M); + sar(I, 0x2); + jle(l9d0, T_NEAR); + align(4); + +L(l8d0); + movdqu(xmm0, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm1, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm2, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm3, xword[A1-0x80]); + add(A1, LDA); + movdqa(xmm4, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm4, xmm1); + movdqa(xmm1, xmm2); + punpcklbw(xmm2, xmm3); + punpckhbw(xmm1, xmm3); + movdqa(xmm3, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm3, xmm2); + movdqa(xmm2, xmm4); + punpcklwd(xmm4, xmm1); + punpckhwd(xmm2, xmm1); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm3); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + pmovsxbw(xmm5, xmm2); + movhlps(xmm6, xmm2); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x60], xmm4); + movdqu(xword[B-0x50], xmm2); + sub(B, -64); + dec(I); + jg(l8d0, T_NEAR); + align(4); + +L(l9d0); + test(M, 0x2); + jle(la64, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + add(A1, LDA); + movdqu(xmm1, xword[A1-0x80]); + add(A1, LDA); + movdqa(xmm2, xmm0); + punpcklbw(xmm0, xmm1); + punpckhbw(xmm2, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + pmovsxbw(xmm5, xmm2); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movhlps(xmm6, xmm2); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm2); + sub(B, -32); + align(4); + +L(la64); + test(M, 0x1); + jle(lab8, T_NEAR); + movdqu(xmm0, xword[A1-0x80]); + add(A1, LDA); + pmovsxbd(xmm5, xmm0); + paddd(xmm8, xmm5); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm9, xmm6); + pshufd(xmm5, xmm0, 0xaa); + pmovsxbd(xmm5, xmm5); + paddd(xmm10, xmm5); + pshufd(xmm6, xmm0, 0xff); + pmovsxbd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(lab8); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + movdqu(xword[A1+0x20], xmm10); + movdqu(xword[A1+0x30], xmm11); + add(qword[ARG_BIAS], 0x40); + sub(N, 0x10); + cmp(N, 0x10); + jge(l8a8, T_NEAR); + align(4); + +L(lae8); + cmp(N, 0x8); + jl(ld78, T_NEAR); + align(4); + +L(laf4); + mov(A1, A); + add(A, 0x8); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + mov(I, M); + sar(I, 0x3); + jle(lc30, T_NEAR); + align(4); + +L(lb14); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + dec(I); + jg(lb14, T_NEAR); + align(4); + +L(lc30); + test(M, 0x4); + jle(lcc8, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(lcc8); + test(M, 0x2); + jle(ld1c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(ld1c); + test(M, 0x1); + jle(ld54, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + pmovsxbd(xmm5, xmm0); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm8, xmm5); + paddd(xmm9, xmm6); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(ld54); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + add(qword[ARG_BIAS], 0x20); + sub(N, 0x8); + cmp(N, 0x8); + jge(laf4, T_NEAR); + align(4); + +L(ld78); + cmp(N, 0x4); + jl(lf3c, T_NEAR); + align(4); + +L(ld84); + mov(A1, A); + add(A, 0x4); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x3); + jle(le58, T_NEAR); + align(4); + +L(ld9c); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + dec(I); + jg(ld9c, T_NEAR); + align(4); + +L(le58); + test(M, 0x4); + jle(lebc, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(lebc); + test(M, 0x2); + jle(lef8, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(lef8); + test(M, 0x1); + jle(lf1c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(lf1c); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm7); + add(qword[ARG_BIAS], 0x10); + sub(N, 0x4); + cmp(N, 0x4); + jge(ld84, T_NEAR); + align(4); + +L(lf3c); + cmp(N, 0x2); + jl(l111a, T_NEAR); + align(4); + +L(lf48); + mov(A1, A); + add(A, 0x2); + pxor(xmm7, xmm7); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l1024, T_NEAR); + align(4); + +L(lf60); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm4, eax, 0x0); + punpcklbw(xmm1, xmm2); + punpcklbw(xmm3, xmm4); + punpcklwd(xmm1, xmm3); + punpcklqdq(xmm0, xmm1); + pshufd(xmm6, xmm0, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(LDA3); + jg(lf60, T_NEAR); + align(4); + +L(l1024); + test(M, 0x4); + jle(l1090, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l1090); + test(M, 0x2); + jle(l10d4, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + punpcklbw(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l10d4); + test(M, 0x1); + jle(l10fc, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + mov(word[B-0x80], ax); + sub(B, -2); + align(4); + +L(l10fc); + mov(A1, qword[ARG_BIAS]); + movq(qword[A1], xmm7); + add(qword[ARG_BIAS], 0x8); + sub(N, 0x2); + cmp(N, 0x2); + jge(lf48, T_NEAR); + align(4); + +L(l111a); + cmp(N, 0x1); + jl(l12bc, T_NEAR); + align(4); + +L(l1124); + mov(A1, A); + add(A, 0x1); + pxor(xmm7, xmm7); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l11d4, T_NEAR); + align(4); + +L(l113c); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + dec(LDA3); + jg(l113c, T_NEAR); + align(4); + +L(l11d4); + test(M, 0x4); + jle(l1234, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l1234); + test(M, 0x2); + jle(l1278, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(byte[B-0x80], al); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + mov(byte[B-0x7f], al); + sub(B, -2); + align(4); + +L(l1278); + test(M, 0x1); + jle(l129c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l129c); + mov(A1, qword[ARG_BIAS]); + movd(dword[A1], xmm7); + add(qword[ARG_BIAS], 0x4); + sub(N, 0x1); + cmp(N, 0x1); + jge(l1124, T_NEAR); + align(4); + +L(l12bc); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +#undef ARG_BIAS +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_at_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_at_kern.cpp new file mode 100644 index 000000000000..a4f4ff09c685 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_at_kern.cpp @@ -0,0 +1,3163 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_sum_at_kern::jit_avx512_core_u8_copy_sum_at_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#define ARG_BIAS 24+stacksize+rsp + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp +#define ARG_BIAS 72+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l1750; +Xbyak::Label l1b6c; +Xbyak::Label l1e14; +Xbyak::Label l20; +Xbyak::Label l2068; +Xbyak::Label l226c; +Xbyak::Label l22b8; +Xbyak::Label l22c4; +Xbyak::Label l22f4; +Xbyak::Label l26b4; +Xbyak::Label l28cc; +Xbyak::Label l2a2c; +Xbyak::Label l2b5c; +Xbyak::Label l2c64; +Xbyak::Label l2c94; +Xbyak::Label l2ca0; +Xbyak::Label l2cc8; +Xbyak::Label l2eac; +Xbyak::Label l2fc0; +Xbyak::Label l3078; +Xbyak::Label l3118; +Xbyak::Label l319c; +Xbyak::Label l31c0; +Xbyak::Label l31cc; +Xbyak::Label l31ec; +Xbyak::Label l32e4; +Xbyak::Label l3378; +Xbyak::Label l33dc; +Xbyak::Label l3434; +Xbyak::Label l347c; +Xbyak::Label l349c; +Xbyak::Label l34a8; +Xbyak::Label l34c8; +Xbyak::Label l3558; +Xbyak::Label l35b0; +Xbyak::Label l35f4; +Xbyak::Label l3638; +Xbyak::Label l366c; +Xbyak::Label l368a; +Xbyak::Label l3694; +Xbyak::Label l36a8; +Xbyak::Label l36ec; +Xbyak::Label l3728; +Xbyak::Label l3760; +Xbyak::Label l3794; +Xbyak::Label l37b8; +Xbyak::Label l37d8; +Xbyak::Label l5cc; +Xbyak::Label l6c; +Xbyak::Label l968; +Xbyak::Label lc80; +Xbyak::Label lf1c; +Xbyak::Label lf64; +Xbyak::Label lf70; +Xbyak::Label lfb4; + + preamble(); + auto stacksize = get_size_of_abi_save_regs(); +#ifdef _WIN32 + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(N, qword[N]); + mov(M, qword[M]); + mov(LDA, qword[LDA]); + sub(A, -128); + sub(B, -128); + lea(LDA3, ptr[LDA+LDA*2]); + cmp(N, 0x30); + jl(lf64, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + mov(I, LDA); + shl(I, 0x5); + lea(I, ptr[I+LDA*8]); + lea(I, ptr[I+LDA*8]); + add(A, I); + vxorps(ymm8, ymm8, ymm8); + vxorps(ymm9, ymm9, ymm9); + vxorps(ymm10, ymm10, ymm10); + vxorps(ymm11, ymm11, ymm11); + vxorps(ymm12, ymm12, ymm12); + vxorps(ymm13, ymm13, ymm13); + vxorps(ymm14, ymm14, ymm14); + vxorps(ymm15, ymm15, ymm15); + mov(I, M); + sar(I, 0x3); + jle(l5cc, T_NEAR); + align(4); + +L(l6c); + vmovq(xmm0, qword[A1-0x80]); + vmovq(xmm1, qword[A1+LDA*1-0x80]); + vmovq(xmm2, qword[A1+LDA*2-0x80]); + vmovq(xmm3, qword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + vpunpckldq(xmm1, xmm0, xmm1); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm1, xmm3); + vpunpckhqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x80], xmm0); + vmovdqu(xword[B+0x40], xmm1); + vmovq(xmm2, qword[A2-0x80]); + vmovq(xmm3, qword[A2+LDA*1-0x80]); + vmovq(xmm4, qword[A2+LDA*2-0x80]); + vmovq(xmm5, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpckldq(xmm5, xmm4, xmm5); + vpunpcklqdq(xmm2, xmm3, xmm5); + vpunpckhqdq(xmm3, xmm3, xmm5); + vmovdqu(xword[B-0x70], xmm2); + vmovdqu(xword[B+0x50], xmm3); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm2); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm8, ymm8, ymm5); + vpmovsxbw(ymm5, xmm1); + vmovhlps(xmm6, xmm1, xmm1); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm8, ymm8, ymm5); + vmovq(xmm0, qword[A2-0x80]); + vmovq(xmm1, qword[A2+LDA*1-0x80]); + vmovq(xmm2, qword[A2+LDA*2-0x80]); + vmovq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm0, xmm1); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm1, xmm3); + vpunpckhqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x60], xmm0); + vmovdqu(xword[B+0x60], xmm1); + vmovq(xmm2, qword[A2-0x80]); + vmovq(xmm3, qword[A2+LDA*1-0x80]); + vmovq(xmm4, qword[A2+LDA*2-0x80]); + vmovq(xmm5, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpckldq(xmm5, xmm4, xmm5); + vpunpcklqdq(xmm2, xmm3, xmm5); + vpunpckhqdq(xmm3, xmm3, xmm5); + vmovdqu(xword[B-0x50], xmm2); + vmovdqu(xword[B+0x70], xmm3); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm2); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm9, ymm9, ymm5); + vpmovsxbw(ymm5, xmm1); + vmovhlps(xmm6, xmm1, xmm1); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm9, ymm9, ymm5); + vmovq(xmm0, qword[A2-0x80]); + vmovq(xmm1, qword[A2+LDA*1-0x80]); + vmovq(xmm2, qword[A2+LDA*2-0x80]); + vmovq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm0, xmm1); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm1, xmm3); + vpunpckhqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x40], xmm0); + vmovdqu(xword[B+0x80], xmm1); + vmovq(xmm2, qword[A2-0x80]); + vmovq(xmm3, qword[A2+LDA*1-0x80]); + vmovq(xmm4, qword[A2+LDA*2-0x80]); + vmovq(xmm5, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpckldq(xmm5, xmm4, xmm5); + vpunpcklqdq(xmm2, xmm3, xmm5); + vpunpckhqdq(xmm3, xmm3, xmm5); + vmovdqu(xword[B-0x30], xmm2); + vmovdqu(xword[B+0x90], xmm3); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm2); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm10, ymm10, ymm5); + vpmovsxbw(ymm5, xmm1); + vmovhlps(xmm6, xmm1, xmm1); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm10, ymm10, ymm5); + vmovq(xmm0, qword[A2-0x80]); + vmovq(xmm1, qword[A2+LDA*1-0x80]); + vmovq(xmm2, qword[A2+LDA*2-0x80]); + vmovq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm0, xmm1); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm1, xmm3); + vpunpckhqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x20], xmm0); + vmovdqu(xword[B+0xa0], xmm1); + vmovq(xmm2, qword[A2-0x80]); + vmovq(xmm3, qword[A2+LDA*1-0x80]); + vmovq(xmm4, qword[A2+LDA*2-0x80]); + vmovq(xmm5, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpckldq(xmm5, xmm4, xmm5); + vpunpcklqdq(xmm2, xmm3, xmm5); + vpunpckhqdq(xmm3, xmm3, xmm5); + vmovdqu(xword[B-0x10], xmm2); + vmovdqu(xword[B+0xb0], xmm3); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm2); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm11, ymm11, ymm5); + vpmovsxbw(ymm5, xmm1); + vmovhlps(xmm6, xmm1, xmm1); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm11, ymm11, ymm5); + vmovq(xmm0, qword[A2-0x80]); + vmovq(xmm1, qword[A2+LDA*1-0x80]); + vmovq(xmm2, qword[A2+LDA*2-0x80]); + vmovq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm0, xmm1); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm1, xmm3); + vpunpckhqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B], xmm0); + vmovdqu(xword[B+0xc0], xmm1); + vmovq(xmm2, qword[A2-0x80]); + vmovq(xmm3, qword[A2+LDA*1-0x80]); + vmovq(xmm4, qword[A2+LDA*2-0x80]); + vmovq(xmm5, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpckldq(xmm5, xmm4, xmm5); + vpunpcklqdq(xmm2, xmm3, xmm5); + vpunpckhqdq(xmm3, xmm3, xmm5); + vmovdqu(xword[B+0x10], xmm2); + vmovdqu(xword[B+0xd0], xmm3); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm2); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm12, ymm12, ymm5); + vpmovsxbw(ymm5, xmm1); + vmovhlps(xmm6, xmm1, xmm1); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm12, ymm12, ymm5); + vmovq(xmm0, qword[A2-0x80]); + vmovq(xmm1, qword[A2+LDA*1-0x80]); + vmovq(xmm2, qword[A2+LDA*2-0x80]); + vmovq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm0, xmm1); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm1, xmm3); + vpunpckhqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B+0x20], xmm0); + vmovdqu(xword[B+0xe0], xmm1); + vmovq(xmm2, qword[A2-0x80]); + vmovq(xmm3, qword[A2+LDA*1-0x80]); + vmovq(xmm4, qword[A2+LDA*2-0x80]); + vmovq(xmm5, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm3, xmm2, xmm3); + vpunpckldq(xmm5, xmm4, xmm5); + vpunpcklqdq(xmm2, xmm3, xmm5); + vpunpckhqdq(xmm3, xmm3, xmm5); + vmovdqu(xword[B+0x30], xmm2); + vmovdqu(xword[B+0xf0], xmm3); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm2); + vmovhlps(xmm7, xmm2, xmm2); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm13, ymm13, ymm5); + vpmovsxbw(ymm5, xmm1); + vmovhlps(xmm6, xmm1, xmm1); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm3); + vmovhlps(xmm7, xmm3, xmm3); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm13, ymm13, ymm5); + sub(A1, -8); + sub(B, -384); + dec(I); + jg(l6c, T_NEAR); + align(4); + +L(l5cc); + test(M, 0x4); + jle(l968, T_NEAR); + vmovd(xmm0, dword[A1-0x80]); + vmovd(xmm1, dword[A1+LDA*1-0x80]); + vmovd(xmm2, dword[A1+LDA*2-0x80]); + vmovd(xmm3, dword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + vpunpckldq(xmm0, xmm0, xmm1); + vpunpckldq(xmm2, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm0, xmm2); + vmovdqu(xword[B-0x80], xmm0); + vmovd(xmm1, dword[A2-0x80]); + vmovd(xmm2, dword[A2+LDA*1-0x80]); + vmovd(xmm3, dword[A2+LDA*2-0x80]); + vmovd(xmm4, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm1, xmm2); + vpunpckldq(xmm3, xmm3, xmm4); + vpunpcklqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x70], xmm1); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm8, ymm8, ymm5); + vmovd(xmm0, dword[A2-0x80]); + vmovd(xmm1, dword[A2+LDA*1-0x80]); + vmovd(xmm2, dword[A2+LDA*2-0x80]); + vmovd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm0, xmm0, xmm1); + vpunpckldq(xmm2, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm0, xmm2); + vmovdqu(xword[B-0x60], xmm0); + vmovd(xmm1, dword[A2-0x80]); + vmovd(xmm2, dword[A2+LDA*1-0x80]); + vmovd(xmm3, dword[A2+LDA*2-0x80]); + vmovd(xmm4, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm1, xmm2); + vpunpckldq(xmm3, xmm3, xmm4); + vpunpcklqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x50], xmm1); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm9, ymm9, ymm5); + vmovd(xmm0, dword[A2-0x80]); + vmovd(xmm1, dword[A2+LDA*1-0x80]); + vmovd(xmm2, dword[A2+LDA*2-0x80]); + vmovd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm0, xmm0, xmm1); + vpunpckldq(xmm2, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm0, xmm2); + vmovdqu(xword[B-0x40], xmm0); + vmovd(xmm1, dword[A2-0x80]); + vmovd(xmm2, dword[A2+LDA*1-0x80]); + vmovd(xmm3, dword[A2+LDA*2-0x80]); + vmovd(xmm4, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm1, xmm2); + vpunpckldq(xmm3, xmm3, xmm4); + vpunpcklqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x30], xmm1); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm10, ymm10, ymm5); + vmovd(xmm0, dword[A2-0x80]); + vmovd(xmm1, dword[A2+LDA*1-0x80]); + vmovd(xmm2, dword[A2+LDA*2-0x80]); + vmovd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm0, xmm0, xmm1); + vpunpckldq(xmm2, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm0, xmm2); + vmovdqu(xword[B-0x20], xmm0); + vmovd(xmm1, dword[A2-0x80]); + vmovd(xmm2, dword[A2+LDA*1-0x80]); + vmovd(xmm3, dword[A2+LDA*2-0x80]); + vmovd(xmm4, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm1, xmm2); + vpunpckldq(xmm3, xmm3, xmm4); + vpunpcklqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B-0x10], xmm1); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm11, ymm11, ymm5); + vmovd(xmm0, dword[A2-0x80]); + vmovd(xmm1, dword[A2+LDA*1-0x80]); + vmovd(xmm2, dword[A2+LDA*2-0x80]); + vmovd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm0, xmm0, xmm1); + vpunpckldq(xmm2, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm0, xmm2); + vmovdqu(xword[B], xmm0); + vmovd(xmm1, dword[A2-0x80]); + vmovd(xmm2, dword[A2+LDA*1-0x80]); + vmovd(xmm3, dword[A2+LDA*2-0x80]); + vmovd(xmm4, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm1, xmm2); + vpunpckldq(xmm3, xmm3, xmm4); + vpunpcklqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B+0x10], xmm1); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm12, ymm12, ymm5); + vmovd(xmm0, dword[A2-0x80]); + vmovd(xmm1, dword[A2+LDA*1-0x80]); + vmovd(xmm2, dword[A2+LDA*2-0x80]); + vmovd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm0, xmm0, xmm1); + vpunpckldq(xmm2, xmm2, xmm3); + vpunpcklqdq(xmm0, xmm0, xmm2); + vmovdqu(xword[B+0x20], xmm0); + vmovd(xmm1, dword[A2-0x80]); + vmovd(xmm2, dword[A2+LDA*1-0x80]); + vmovd(xmm3, dword[A2+LDA*2-0x80]); + vmovd(xmm4, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpunpckldq(xmm1, xmm1, xmm2); + vpunpckldq(xmm3, xmm3, xmm4); + vpunpcklqdq(xmm1, xmm1, xmm3); + vmovdqu(xword[B+0x30], xmm1); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxbw(ymm6, xmm1); + vmovhlps(xmm7, xmm1, xmm1); + vpmovsxbw(ymm7, xmm7); + vphaddw(ymm6, ymm6, ymm7); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm13, ymm13, ymm5); + sub(A1, -4); + sub(B, -192); + align(4); + +L(l968); + test(M, 0x2); + jle(lc80, T_NEAR); + mov(ax, word[A1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x7); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm8, ymm8, ymm5); + vmovdqu(xword[B-0x80], xmm0); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm9, ymm9, ymm5); + vmovdqu(xword[B-0x70], xmm0); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm10, ymm10, ymm5); + vmovdqu(xword[B-0x60], xmm0); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm11, ymm11, ymm5); + vmovdqu(xword[B-0x50], xmm0); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm12, ymm12, ymm5); + vmovdqu(xword[B-0x40], xmm0); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrw(xmm0, xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + vpinsrw(xmm0, xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + vpmovsxbw(ymm5, xmm0); + vmovhlps(xmm6, xmm0, xmm0); + vpmovsxbw(ymm6, xmm6); + vphaddw(ymm5, ymm5, ymm6); + vpmovsxwd(ymm5, xmm5); + vpaddd(ymm13, ymm13, ymm5); + vmovdqu(xword[B-0x30], xmm0); + sub(A1, -2); + sub(B, -96); + align(4); + +L(lc80); + test(M, 0x1); + jle(lf1c, T_NEAR); + mov(al, byte[A1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0xf); + vpmovsxbd(ymm7, xmm0); + vpaddd(ymm8, ymm8, ymm7); + vmovhlps(xmm7, xmm0, xmm0); + vpmovsxbd(ymm7, xmm7); + vpaddd(ymm9, ymm9, ymm7); + vmovdqu(xword[B-0x80], xmm0); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x0); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x1); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x2); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0xf); + vpmovsxbd(ymm7, xmm0); + vpaddd(ymm10, ymm10, ymm7); + vmovhlps(xmm7, xmm0, xmm0); + vpmovsxbd(ymm7, xmm7); + vpaddd(ymm11, ymm11, ymm7); + vmovdqu(xword[B-0x70], xmm0); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x0); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x1); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x2); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + vpinsrb(xmm0, xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + vpinsrb(xmm0, xmm0, eax, 0xf); + vpmovsxbd(ymm7, xmm0); + vpaddd(ymm12, ymm12, ymm7); + vmovhlps(xmm7, xmm0, xmm0); + vpmovsxbd(ymm7, xmm7); + vpaddd(ymm13, ymm13, ymm7); + vmovdqu(xword[B-0x60], xmm0); + sub(B, -48); + align(4); + +L(lf1c); + mov(A1, qword[ARG_BIAS]); + vmovdqu(yword[A1], ymm8); + vmovdqu(yword[A1+0x20], ymm9); + vmovdqu(yword[A1+0x40], ymm10); + vmovdqu(yword[A1+0x60], ymm11); + vmovdqu(yword[A1+0x80], ymm12); + vmovdqu(yword[A1+0xa0], ymm13); + add(qword[ARG_BIAS], 0xc0); + sub(N, 0x30); + cmp(N, 0x30); + jge(l20, T_NEAR); + vzeroupper(); + align(4); + +L(lf64); + cmp(N, 0x20); + jl(l22b8, T_NEAR); + align(4); + +L(lf70); + mov(A1, A); + mov(I, LDA); + shl(I, 0x5); + add(A, I); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + pxor(xmm10, xmm10); + pxor(xmm11, xmm11); + pxor(xmm12, xmm12); + pxor(xmm13, xmm13); + pxor(xmm14, xmm14); + pxor(xmm15, xmm15); + mov(I, M); + sar(I, 0x4); + jle(l1750, T_NEAR); + align(4); + +L(lfb4); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B+0x80], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B+0x100], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B+0x10], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B+0x90], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B+0x110], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B+0x20], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B+0xa0], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B+0x120], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B+0x30], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B+0xb0], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B+0x130], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B-0x40], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B+0x40], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B+0xc0], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B+0x140], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B-0x30], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B+0x50], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B+0xd0], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B+0x150], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B-0x20], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B+0x60], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B+0xe0], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B+0x160], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B-0x10], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B+0x70], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B+0xf0], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B+0x170], xmm3); + sub(A1, -16); + sub(B, -512); + dec(I); + jg(lfb4, T_NEAR); + align(4); + +L(l1750); + test(M, 0x8); + jle(l1b6c, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B+0x10], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B+0x20], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B+0x30], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B-0x40], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B+0x40], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B-0x30], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B+0x50], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B-0x20], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B+0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B-0x10], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B+0x70], xmm1); + sub(A1, -8); + sub(B, -256); + align(4); + +L(l1b6c); + test(M, 0x4); + jle(l1e14, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movdqu(xword[B-0x40], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm13, xmm5); + movdqu(xword[B-0x30], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movdqu(xword[B-0x20], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm15, xmm5); + movdqu(xword[B-0x10], xmm0); + sub(A1, -4); + sub(B, -128); + align(4); + +L(l1e14); + test(M, 0x2); + jle(l2068, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x70], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm12, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm13, xmm6); + movdqu(xword[B-0x60], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + lea(A2, ptr[A2+LDA*4]); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm14, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm15, xmm6); + movdqu(xword[B-0x50], xmm0); + sub(A1, -2); + sub(B, -64); + align(4); + +L(l2068); + test(M, 0x1); + jle(l226c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + pmovsxbd(xmm5, xmm0); + paddd(xmm8, xmm5); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm9, xmm6); + pshufd(xmm5, xmm0, 0xaa); + pmovsxbd(xmm5, xmm5); + paddd(xmm10, xmm5); + pshufd(xmm6, xmm0, 0xff); + pmovsxbd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x80], xmm0); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xf); + pmovsxbd(xmm5, xmm0); + paddd(xmm12, xmm5); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm13, xmm6); + pshufd(xmm5, xmm0, 0xaa); + pmovsxbd(xmm5, xmm5); + paddd(xmm14, xmm5); + pshufd(xmm6, xmm0, 0xff); + pmovsxbd(xmm6, xmm6); + paddd(xmm15, xmm6); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + align(4); + +L(l226c); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + movdqu(xword[A1+0x20], xmm10); + movdqu(xword[A1+0x30], xmm11); + movdqu(xword[A1+0x40], xmm12); + movdqu(xword[A1+0x50], xmm13); + movdqu(xword[A1+0x60], xmm14); + movdqu(xword[A1+0x70], xmm15); + add(qword[ARG_BIAS], 0x80); + sub(N, 0x20); + cmp(N, 0x20); + jge(lf70, T_NEAR); + align(4); + +L(l22b8); + cmp(N, 0x10); + jl(l2c94, T_NEAR); + align(4); + +L(l22c4); + mov(A1, A); + mov(I, LDA); + shl(I, 0x4); + add(A, I); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + pxor(xmm10, xmm10); + pxor(xmm11, xmm11); + mov(I, M); + sar(I, 0x4); + jle(l26b4, T_NEAR); + align(4); + +L(l22f4); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x40], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B+0x40], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x30], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B+0x10], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B+0x50], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x20], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B+0x20], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B+0x60], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x10], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B+0x30], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B+0x70], xmm3); + sub(A1, -16); + sub(B, -256); + dec(I); + jg(l22f4, T_NEAR); + align(4); + +L(l26b4); + test(M, 0x8); + jle(l28cc, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x40], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x30], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x20], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x10], xmm1); + sub(A1, -8); + sub(B, -128); + align(4); + +L(l28cc); + test(M, 0x4); + jle(l2a2c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movdqu(xword[B-0x60], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm11, xmm5); + movdqu(xword[B-0x50], xmm0); + sub(A1, -4); + sub(B, -64); + align(4); + +L(l2a2c); + test(M, 0x2); + jle(l2b5c, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + pinsrw(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm10, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x70], xmm0); + sub(A1, -2); + sub(B, -32); + align(4); + +L(l2b5c); + test(M, 0x1); + jle(l2c64, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + lea(A2, ptr[A1+LDA*4]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0x7); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x8); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x9); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xa); + mov(al, byte[A2+LDA3*1-0x80]); + lea(A2, ptr[A2+LDA*4]); + pinsrb(xmm0, eax, 0xb); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0xc); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0xd); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0xe); + mov(al, byte[A2+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0xf); + pmovsxbd(xmm5, xmm0); + paddd(xmm8, xmm5); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm9, xmm6); + pshufd(xmm5, xmm0, 0xaa); + pmovsxbd(xmm5, xmm5); + paddd(xmm10, xmm5); + pshufd(xmm6, xmm0, 0xff); + pmovsxbd(xmm6, xmm6); + paddd(xmm11, xmm6); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l2c64); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + movdqu(xword[A1+0x20], xmm10); + movdqu(xword[A1+0x30], xmm11); + add(qword[ARG_BIAS], 0x40); + sub(N, 0x10); + cmp(N, 0x10); + jge(l22c4, T_NEAR); + align(4); + +L(l2c94); + cmp(N, 0x8); + jl(l31c0, T_NEAR); + align(4); + +L(l2ca0); + mov(A1, A); + lea(A2, ptr[A1+LDA*4]); + lea(I, ptr[A1+LDA*8]); + mov(A, I); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + mov(I, M); + sar(I, 0x4); + jle(l2eac, T_NEAR); + align(4); + +L(l2cc8); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + sub(A1, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x60], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x40], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x20], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x50], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x30], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x10], xmm3); + sub(B, -128); + dec(I); + jg(l2cc8, T_NEAR); + align(4); + +L(l2eac); + test(M, 0x8); + jle(l2fc0, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + sub(A1, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + align(4); + +L(l2fc0); + test(M, 0x4); + jle(l3078, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + sub(A1, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + align(4); + +L(l3078); + test(M, 0x2); + jle(l3118, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l3118); + test(M, 0x1); + jle(l319c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x7); + pmovsxbd(xmm5, xmm0); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm8, xmm5); + paddd(xmm9, xmm6); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l319c); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + add(qword[ARG_BIAS], 0x20); + sub(N, 0x8); + cmp(N, 0x8); + jge(l2ca0, T_NEAR); + align(4); + +L(l31c0); + cmp(N, 0x4); + jl(l349c, T_NEAR); + align(4); + +L(l31cc); + mov(A1, A); + lea(A2, ptr[A1+LDA*2]); + lea(I, ptr[A1+LDA*4]); + mov(A, I); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x4); + jle(l32e4, T_NEAR); + align(4); + +L(l31ec); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + sub(A1, -16); + movdqu(xmm2, xword[A2-0x80]); + movdqu(xmm3, xword[A2+LDA*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x60], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x50], xmm3); + sub(B, -64); + dec(I); + jg(l31ec, T_NEAR); + align(4); + +L(l32e4); + test(M, 0x8); + jle(l3378, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + sub(A1, -8); + movq(xmm2, qword[A2-0x80]); + movq(xmm3, qword[A2+LDA*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l3378); + test(M, 0x4); + jle(l33dc, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + sub(A1, -4); + movd(xmm2, dword[A2-0x80]); + movd(xmm3, dword[A2+LDA*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l33dc); + test(M, 0x2); + jle(l3434, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x3); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l3434); + test(M, 0x1); + jle(l347c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x3); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l347c); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm7); + add(qword[ARG_BIAS], 0x10); + sub(N, 0x4); + cmp(N, 0x4); + jge(l31cc, T_NEAR); + align(4); + +L(l349c); + cmp(N, 0x2); + jl(l368a, T_NEAR); + align(4); + +L(l34a8); + mov(A1, A); + lea(A2, ptr[A1+LDA*1]); + lea(I, ptr[A1+LDA*2]); + mov(A, I); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x4); + jle(l3558, T_NEAR); + align(4); + +L(l34c8); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + movdqu(xmm1, xword[A2-0x80]); + sub(A2, -16); + movdqa(xmm2, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm2, xmm1); + pshufd(xmm6, xmm0, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + pshufd(xmm6, xmm2, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm2); + sub(B, -32); + dec(I); + jg(l34c8, T_NEAR); + align(4); + +L(l3558); + test(M, 0x8); + jle(l35b0, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + movq(xmm1, qword[A2-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + pshufd(xmm6, xmm0, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l35b0); + test(M, 0x4); + jle(l35f4, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + movd(xmm1, dword[A2-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l35f4); + test(M, 0x2); + jle(l3638, T_NEAR); + mov(ax, word[A1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l3638); + test(M, 0x1); + jle(l366c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(byte[B-0x80], al); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(byte[B-0x7f], al); + sub(B, -2); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + align(4); + +L(l366c); + mov(A1, qword[ARG_BIAS]); + movq(qword[A1], xmm7); + add(qword[ARG_BIAS], 0x8); + sub(N, 0x2); + cmp(N, 0x2); + jge(l34a8, T_NEAR); + align(4); + +L(l368a); + cmp(N, 0x1); + jl(l37d8, T_NEAR); + align(4); + +L(l3694); + mov(A1, A); + add(A, LDA); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x4); + jle(l36ec, T_NEAR); + align(4); + +L(l36a8); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(I); + jg(l36a8, T_NEAR); + align(4); + +L(l36ec); + test(M, 0x8); + jle(l3728, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l3728); + test(M, 0x4); + jle(l3760, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l3760); + test(M, 0x2); + jle(l3794, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + mov(word[B-0x80], ax); + sub(A1, -2); + sub(B, -2); + align(4); + +L(l3794); + test(M, 0x1); + jle(l37b8, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l37b8); + mov(A1, qword[ARG_BIAS]); + movd(dword[A1], xmm7); + add(qword[ARG_BIAS], 0x4); + sub(N, 0x1); + cmp(N, 0x1); + jge(l3694, T_NEAR); + align(4); + +L(l37d8); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +#undef ARG_BIAS +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bn_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bn_kern.cpp new file mode 100644 index 000000000000..c7f1393c9d85 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bn_kern.cpp @@ -0,0 +1,821 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_sum_bn_kern::jit_avx512_core_u8_copy_sum_bn_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#define ARG_BIAS 24+stacksize+rsp + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp +#define ARG_BIAS 72+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l20; +Xbyak::Label l22c; +Xbyak::Label l340; +Xbyak::Label l3f8; +Xbyak::Label l48; +Xbyak::Label l498; +Xbyak::Label l51c; +Xbyak::Label l540; +Xbyak::Label l54c; +Xbyak::Label l56c; +Xbyak::Label l664; +Xbyak::Label l6f8; +Xbyak::Label l75c; +Xbyak::Label l7b4; +Xbyak::Label l7fc; +Xbyak::Label l81c; +Xbyak::Label l828; +Xbyak::Label l848; +Xbyak::Label l8d8; +Xbyak::Label l930; +Xbyak::Label l974; +Xbyak::Label l9b8; +Xbyak::Label l9ec; +Xbyak::Label la0a; +Xbyak::Label la14; +Xbyak::Label la28; +Xbyak::Label la6c; +Xbyak::Label laa8; +Xbyak::Label lae0; +Xbyak::Label lb14; +Xbyak::Label lb38; +Xbyak::Label lb58; + + preamble(); + auto stacksize = get_size_of_abi_save_regs(); +#ifdef _WIN32 + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(N, qword[N]); + mov(M, qword[M]); + mov(LDA, qword[LDA]); + sub(A, -128); + sub(B, -128); + lea(LDA3, ptr[LDA+LDA*2]); + cmp(N, 0x8); + jl(l540, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + lea(A2, ptr[A1+LDA*4]); + lea(I, ptr[A1+LDA*8]); + mov(A, I); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + mov(I, M); + sar(I, 0x4); + jle(l22c, T_NEAR); + align(4); + +L(l48); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + movdqu(xmm2, xword[A1+LDA*2-0x80]); + movdqu(xmm3, xword[A1+LDA3*1-0x80]); + sub(A1, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x60], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x40], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x20], xmm3); + movdqu(xmm0, xword[A2-0x80]); + movdqu(xmm1, xword[A2+LDA*1-0x80]); + movdqu(xmm2, xword[A2+LDA*2-0x80]); + movdqu(xmm3, xword[A2+LDA3*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x50], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x30], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x10], xmm3); + sub(B, -128); + dec(I); + jg(l48, T_NEAR); + align(4); + +L(l22c); + test(M, 0x8); + jle(l340, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + movq(xmm2, qword[A1+LDA*2-0x80]); + movq(xmm3, qword[A1+LDA3*1-0x80]); + sub(A1, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x60], xmm1); + movq(xmm0, qword[A2-0x80]); + movq(xmm1, qword[A2+LDA*1-0x80]); + movq(xmm2, qword[A2+LDA*2-0x80]); + movq(xmm3, qword[A2+LDA3*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + align(4); + +L(l340); + test(M, 0x4); + jle(l3f8, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + movd(xmm2, dword[A1+LDA*2-0x80]); + movd(xmm3, dword[A1+LDA3*1-0x80]); + sub(A1, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A2-0x80]); + movd(xmm1, dword[A2+LDA*1-0x80]); + movd(xmm2, dword[A2+LDA*2-0x80]); + movd(xmm3, dword[A2+LDA3*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + align(4); + +L(l3f8); + test(M, 0x2); + jle(l498, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A1+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A1+LDA3*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x3); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x4); + mov(ax, word[A2+LDA*1-0x80]); + pinsrw(xmm0, eax, 0x5); + mov(ax, word[A2+LDA*2-0x80]); + pinsrw(xmm0, eax, 0x6); + mov(ax, word[A2+LDA3*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l498); + test(M, 0x1); + jle(l51c, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A2+LDA*2-0x80]); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A2+LDA3*1-0x80]); + pinsrb(xmm0, eax, 0x7); + pmovsxbd(xmm5, xmm0); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm8, xmm5); + paddd(xmm9, xmm6); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l51c); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + add(qword[ARG_BIAS], 0x20); + sub(N, 0x8); + cmp(N, 0x8); + jge(l20, T_NEAR); + align(4); + +L(l540); + cmp(N, 0x4); + jl(l81c, T_NEAR); + align(4); + +L(l54c); + mov(A1, A); + lea(A2, ptr[A1+LDA*2]); + lea(I, ptr[A1+LDA*4]); + mov(A, I); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x4); + jle(l664, T_NEAR); + align(4); + +L(l56c); + movdqu(xmm0, xword[A1-0x80]); + movdqu(xmm1, xword[A1+LDA*1-0x80]); + sub(A1, -16); + movdqu(xmm2, xword[A2-0x80]); + movdqu(xmm3, xword[A2+LDA*1-0x80]); + sub(A2, -16); + movdqa(xmm4, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm4, xmm1); + movdqa(xmm5, xmm2); + punpckldq(xmm2, xmm3); + punpckhdq(xmm5, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + movdqa(xmm3, xmm4); + punpcklqdq(xmm4, xmm5); + punpckhqdq(xmm3, xmm5); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm1); + pmovsxbw(xmm5, xmm4); + movhlps(xmm6, xmm4); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x60], xmm4); + pmovsxbw(xmm5, xmm3); + movhlps(xmm6, xmm3); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x50], xmm3); + sub(B, -64); + dec(I); + jg(l56c, T_NEAR); + align(4); + +L(l664); + test(M, 0x8); + jle(l6f8, T_NEAR); + movq(xmm0, qword[A1-0x80]); + movq(xmm1, qword[A1+LDA*1-0x80]); + sub(A1, -8); + movq(xmm2, qword[A2-0x80]); + movq(xmm3, qword[A2+LDA*1-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklqdq(xmm0, xmm2); + punpckhqdq(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l6f8); + test(M, 0x4); + jle(l75c, T_NEAR); + movd(xmm0, dword[A1-0x80]); + movd(xmm1, dword[A1+LDA*1-0x80]); + sub(A1, -4); + movd(xmm2, dword[A2-0x80]); + movd(xmm3, dword[A2+LDA*1-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + punpckldq(xmm2, xmm3); + punpcklqdq(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l75c); + test(M, 0x2); + jle(l7b4, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1+LDA*1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x1); + mov(ax, word[A2-0x80]); + pinsrw(xmm0, eax, 0x2); + mov(ax, word[A2+LDA*1-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x3); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l7b4); + test(M, 0x1); + jle(l7fc, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A2+LDA*1-0x80]); + pinsrb(xmm0, eax, 0x3); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l7fc); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm7); + add(qword[ARG_BIAS], 0x10); + sub(N, 0x4); + cmp(N, 0x4); + jge(l54c, T_NEAR); + align(4); + +L(l81c); + cmp(N, 0x2); + jl(la0a, T_NEAR); + align(4); + +L(l828); + mov(A1, A); + lea(A2, ptr[A1+LDA*1]); + lea(I, ptr[A1+LDA*2]); + mov(A, I); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x4); + jle(l8d8, T_NEAR); + align(4); + +L(l848); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + movdqu(xmm1, xword[A2-0x80]); + sub(A2, -16); + movdqa(xmm2, xmm0); + punpckldq(xmm0, xmm1); + punpckhdq(xmm2, xmm1); + pshufd(xmm6, xmm0, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + pshufd(xmm6, xmm2, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm2); + sub(B, -32); + dec(I); + jg(l848, T_NEAR); + align(4); + +L(l8d8); + test(M, 0x8); + jle(l930, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + movq(xmm1, qword[A2-0x80]); + sub(A2, -8); + punpckldq(xmm0, xmm1); + pshufd(xmm6, xmm0, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l930); + test(M, 0x4); + jle(l974, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + movd(xmm1, dword[A2-0x80]); + sub(A2, -4); + punpckldq(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l974); + test(M, 0x2); + jle(l9b8, T_NEAR); + mov(ax, word[A1-0x80]); + sub(A1, -2); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A2-0x80]); + sub(A2, -2); + pinsrw(xmm0, eax, 0x1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l9b8); + test(M, 0x1); + jle(l9ec, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + mov(byte[B-0x80], al); + mov(al, byte[A2-0x80]); + pinsrb(xmm0, eax, 0x1); + mov(byte[B-0x7f], al); + sub(B, -2); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + align(4); + +L(l9ec); + mov(A1, qword[ARG_BIAS]); + movq(qword[A1], xmm7); + add(qword[ARG_BIAS], 0x8); + sub(N, 0x2); + cmp(N, 0x2); + jge(l828, T_NEAR); + align(4); + +L(la0a); + cmp(N, 0x1); + jl(lb58, T_NEAR); + align(4); + +L(la14); + mov(A1, A); + add(A, LDA); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x4); + jle(la6c, T_NEAR); + align(4); + +L(la28); + movdqu(xmm0, xword[A1-0x80]); + sub(A1, -16); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(I); + jg(la28, T_NEAR); + align(4); + +L(la6c); + test(M, 0x8); + jle(laa8, T_NEAR); + movq(xmm0, qword[A1-0x80]); + sub(A1, -8); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(laa8); + test(M, 0x4); + jle(lae0, T_NEAR); + movd(xmm0, dword[A1-0x80]); + sub(A1, -4); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(lae0); + test(M, 0x2); + jle(lb14, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + mov(word[B-0x80], ax); + sub(A1, -2); + sub(B, -2); + align(4); + +L(lb14); + test(M, 0x1); + jle(lb38, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrb(xmm0, eax, 0x0); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(lb38); + mov(A1, qword[ARG_BIAS]); + movd(dword[A1], xmm7); + add(qword[ARG_BIAS], 0x4); + sub(N, 0x1); + cmp(N, 0x1); + jge(la14, T_NEAR); + align(4); + +L(lb58); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +#undef ARG_BIAS +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bt_kern.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bt_kern.cpp new file mode 100644 index 000000000000..afe4f1713ef4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/jit_avx512_core_u8_copy_sum_bt_kern.cpp @@ -0,0 +1,647 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_generator.hpp" +#include "common.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +jit_avx512_core_u8_copy_sum_bt_kern::jit_avx512_core_u8_copy_sum_bt_kern(): jit_generator(nullptr, GEMM_CODE_SIZE) +{ + +#ifndef _WIN32 +#define M rdi +#define N rsi +#define A rdx +#define LDA rcx +#define ALPHA r8 +#define B r9 + +#define I rax +#define A1 r10 +#define A2 r8 +#define LDA3 r11 + +#define ARG_BIAS 24+stacksize+rsp + +#else + +#define M rcx +#define N rdx +#define A r8 +#define LDA r9 +#define ALPHA rax +#define B rdi + +#define I rax +#define A1 rsi +#define A2 r10 +#define LDA3 r11 + +#define ARG_ALPHA 40+stacksize+rsp +#define ARG_B 48+stacksize+rsp +#define ARG_BIAS 72+stacksize+rsp + +#endif + +inLocalLabel(); +{ + +Xbyak::Label l15c; +Xbyak::Label l1f4; +Xbyak::Label l20; +Xbyak::Label l248; +Xbyak::Label l280; +Xbyak::Label l2a4; +Xbyak::Label l2b0; +Xbyak::Label l2c8; +Xbyak::Label l384; +Xbyak::Label l3e8; +Xbyak::Label l40; +Xbyak::Label l424; +Xbyak::Label l448; +Xbyak::Label l468; +Xbyak::Label l474; +Xbyak::Label l48c; +Xbyak::Label l550; +Xbyak::Label l5bc; +Xbyak::Label l600; +Xbyak::Label l628; +Xbyak::Label l646; +Xbyak::Label l650; +Xbyak::Label l668; +Xbyak::Label l700; +Xbyak::Label l760; +Xbyak::Label l7a4; +Xbyak::Label l7c8; +Xbyak::Label l7e8; + + preamble(); + auto stacksize = get_size_of_abi_save_regs(); +#ifdef _WIN32 + mov(ALPHA, ptr[ARG_ALPHA]); + mov(B, ptr[ARG_B]); +#endif + + mov(M, qword[M]); + mov(N, qword[N]); + mov(LDA, qword[LDA]); + lea(LDA3, ptr[LDA+LDA*2]); + sub(A, -128); + sub(B, -128); + cmp(N, 0x8); + jl(l2a4, T_NEAR); + align(4); + +L(l20); + mov(A1, A); + add(A, 0x8); + pxor(xmm8, xmm8); + pxor(xmm9, xmm9); + mov(I, M); + sar(I, 0x3); + jle(l15c, T_NEAR); + align(4); + +L(l40); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x60], xmm0); + movdqu(xword[B-0x50], xmm1); + sub(B, -64); + dec(I); + jg(l40, T_NEAR); + align(4); + +L(l15c); + test(M, 0x4); + jle(l1f4, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + movq(xmm2, qword[A1-0x80]); + add(A1, LDA); + movq(xmm3, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + movdqa(xmm1, xmm0); + punpcklwd(xmm0, xmm2); + punpckhwd(xmm1, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + pmovsxbw(xmm5, xmm1); + movhlps(xmm6, xmm1); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm9, xmm5); + movdqu(xword[B-0x80], xmm0); + movdqu(xword[B-0x70], xmm1); + sub(B, -32); + align(4); + +L(l1f4); + test(M, 0x2); + jle(l248, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + movq(xmm1, qword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm8, xmm5); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm6, xmm6); + pmovsxwd(xmm6, xmm6); + paddd(xmm9, xmm6); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l248); + test(M, 0x1); + jle(l280, T_NEAR); + movq(xmm0, qword[A1-0x80]); + add(A1, LDA); + pmovsxbd(xmm5, xmm0); + pshufd(xmm6, xmm0, 0x55); + pmovsxbd(xmm6, xmm6); + paddd(xmm8, xmm5); + paddd(xmm9, xmm6); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l280); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm8); + movdqu(xword[A1+0x10], xmm9); + add(qword[ARG_BIAS], 0x20); + sub(N, 0x8); + cmp(N, 0x8); + jge(l20, T_NEAR); + align(4); + +L(l2a4); + cmp(N, 0x4); + jl(l468, T_NEAR); + align(4); + +L(l2b0); + mov(A1, A); + add(A, 0x4); + pxor(xmm7, xmm7); + mov(I, M); + sar(I, 0x3); + jle(l384, T_NEAR); + align(4); + +L(l2c8); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x70], xmm0); + sub(B, -32); + dec(I); + jg(l2c8, T_NEAR); + align(4); + +L(l384); + test(M, 0x4); + jle(l3e8, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + movd(xmm2, dword[A1-0x80]); + add(A1, LDA); + movd(xmm3, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + movhlps(xmm6, xmm0); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + align(4); + +L(l3e8); + test(M, 0x2); + jle(l424, T_NEAR); + movd(xmm0, dword[A1-0x80]); + add(A1, LDA); + movd(xmm1, dword[A1-0x80]); + add(A1, LDA); + punpcklbw(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l424); + test(M, 0x1); + jle(l448, T_NEAR); + movd(xmm0, dword[A1-0x80]); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l448); + mov(A1, qword[ARG_BIAS]); + movdqu(xword[A1], xmm7); + add(qword[ARG_BIAS], 0x10); + sub(N, 0x4); + cmp(N, 0x4); + jge(l2b0, T_NEAR); + align(4); + +L(l468); + cmp(N, 0x2); + jl(l646, T_NEAR); + align(4); + +L(l474); + mov(A1, A); + add(A, 0x2); + pxor(xmm7, xmm7); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l550, T_NEAR); + align(4); + +L(l48c); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm4, eax, 0x0); + punpcklbw(xmm1, xmm2); + punpcklbw(xmm3, xmm4); + punpcklwd(xmm1, xmm3); + punpcklqdq(xmm0, xmm1); + pshufd(xmm6, xmm0, 0xd8); + pmovsxbw(xmm5, xmm6); + movhlps(xmm6, xmm6); + pmovsxbw(xmm6, xmm6); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movdqu(xword[B-0x80], xmm0); + sub(B, -16); + dec(LDA3); + jg(l48c, T_NEAR); + align(4); + +L(l550); + test(M, 0x4); + jle(l5bc, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm2, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm3, eax, 0x0); + punpcklbw(xmm0, xmm1); + punpcklbw(xmm2, xmm3); + punpcklwd(xmm0, xmm2); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + align(4); + +L(l5bc); + test(M, 0x2); + jle(l600, T_NEAR); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm0, eax, 0x0); + mov(ax, word[A1-0x80]); + add(A1, LDA); + pinsrw(xmm1, eax, 0x0); + punpcklbw(xmm0, xmm1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l600); + test(M, 0x1); + jle(l628, T_NEAR); + mov(ax, word[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + mov(word[B-0x80], ax); + sub(B, -2); + align(4); + +L(l628); + mov(A1, qword[ARG_BIAS]); + movq(qword[A1], xmm7); + add(qword[ARG_BIAS], 0x8); + sub(N, 0x2); + cmp(N, 0x2); + jge(l474, T_NEAR); + align(4); + +L(l646); + cmp(N, 0x1); + jl(l7e8, T_NEAR); + align(4); + +L(l650); + mov(A1, A); + add(A, 0x1); + pxor(xmm7, xmm7); + mov(LDA3, M); + sar(LDA3, 0x3); + jle(l700, T_NEAR); + align(4); + +L(l668); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x4); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x5); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x6); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x7); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm6); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movq(qword[B-0x80], xmm0); + sub(B, -8); + dec(LDA3); + jg(l668, T_NEAR); + align(4); + +L(l700); + test(M, 0x4); + jle(l760, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x2); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x3); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + movd(dword[B-0x80], xmm0); + sub(B, -4); + align(4); + +L(l760); + test(M, 0x2); + jle(l7a4, T_NEAR); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x0); + mov(byte[B-0x80], al); + mov(al, byte[A1-0x80]); + add(A1, LDA); + pinsrb(xmm0, eax, 0x1); + pmovsxbw(xmm5, xmm0); + phaddw(xmm5, xmm5); + pmovsxwd(xmm5, xmm5); + paddd(xmm7, xmm5); + mov(byte[B-0x7f], al); + sub(B, -2); + align(4); + +L(l7a4); + test(M, 0x1); + jle(l7c8, T_NEAR); + mov(al, byte[A1-0x80]); + pinsrw(xmm0, eax, 0x0); + pmovsxbd(xmm5, xmm0); + paddd(xmm7, xmm5); + mov(byte[B-0x80], al); + sub(B, -1); + align(4); + +L(l7c8); + mov(A1, qword[ARG_BIAS]); + movd(dword[A1], xmm7); + add(qword[ARG_BIAS], 0x4); + sub(N, 0x1); + cmp(N, 0x1); + jge(l650, T_NEAR); + align(4); + +L(l7e8); + + postamble(); +} +outLocalLabel(); + +#undef M +#undef N +#undef A +#undef LDA +#undef ALPHA +#undef B +#undef I +#undef A1 +#undef A2 +#undef LDA3 +#ifdef _WIN32 +#undef ARG_ALPHA +#undef ARG_B +#endif +#undef ARG_BIAS +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.cpp new file mode 100644 index 000000000000..4fc11afcbc05 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.cpp @@ -0,0 +1,116 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "../f32/ref_gemm_f32.hpp" +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +mkldnn_status_t ref_gemm_s8x8s32(const char *transa, const char *transb, + const char *offsetc, const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const b_dt *B, const int *LDB, const int8_t *bo, const float *beta, + int32_t *C, const int *LDC, const int32_t *co) { + + if (*M == 0 || *N == 0 || *K == 0) + return mkldnn_success; + + bool OCisR = (*offsetc == 'R' || *offsetc == 'r'); + bool OCisC = (*offsetc == 'C' || *offsetc == 'c'); + bool AisN = (*transa == 'N' || *transa == 'n'); + bool BisN = (*transb == 'N' || *transb == 'n'); + + int m = *M, n = *N, k = *K, lda = *LDA, ldb = *LDB, ldc = *LDC; + size_t sizeA = AisN ? lda * k : lda * m; + size_t sizeB = BisN ? ldb * n : ldb * k; + size_t sizeC = ldc * n; + + double *dA = (double *)malloc(sizeA * sizeof(double), PAGE_4K); + double *dB = (double *)malloc(sizeB * sizeof(double), PAGE_4K); + double *dC = (double *)malloc(sizeC * sizeof(double), PAGE_4K); + + if (utils::any_null(dA, dB, dC)) { + free(dA); + free(dB); + free(dC); + return mkldnn_out_of_memory; + } + + auto da_setter = [=] (int i, int j, double v) { dA[j * lda + i] = v; }; + auto db_setter = [=] (int i, int j, double v) { dB[j * ldb + i] = v; }; + + auto ia_accessor = [=] (int i, int j) { return A[j * lda + i]; }; + auto ib_accessor = [=] (int i, int j) { return B[j * ldb + i]; }; + + const int a_rows = AisN ? m : k; + const int a_cols = AisN ? k : m; + mkldnn::impl::parallel_nd(a_cols, a_rows, [&](int j, int i) { + da_setter(i, j, + static_cast(ia_accessor(i, j)) + static_cast(ao[0])); + }); + + const int b_rows = BisN ? k : n; + const int b_cols = BisN ? n : k; + mkldnn::impl::parallel_nd(b_cols, b_rows, [&](int j, int i) { + db_setter(i, j, + static_cast(ib_accessor(i, j)) + static_cast(bo[0])); + }); + double one = 1.0, zero = 0.0; + ref_gemm(transa, transb, M, N, K, &one, dA, LDA, dB, LDB, &zero, + dC, LDC, nullptr); + + auto i2d = [=] (int32_t v) { return static_cast(v); }; + auto f2d = [=] (float v) { return static_cast(v); }; + + mkldnn::impl::parallel_nd(n, m, [&] (int j, int i) { + double coffset = OCisR ? i2d(co[j]) : OCisC ? i2d(co[i]) : i2d(co[0]); + double val = ((*beta == 0.0f) ? 0.0 : f2d(*beta) * i2d(C[i + j * ldc])) + + f2d(*alpha) * dC[i + j * ldc] + coffset; + C[i + j * ldc] = math::out_round(math::saturate(val)); + }); + + free(dA); + free(dB); + free(dC); + return mkldnn_success; +} + +template mkldnn_status_t ref_gemm_s8x8s32( + const char *transa, const char *transb, const char *offsetc, + const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const uint8_t *B, const int *LDB, const int8_t *bo, + const float *beta, int32_t *C, const int *LDC, const int32_t *co); + +template mkldnn_status_t ref_gemm_s8x8s32( + const char *transa, const char *transb, const char *offsetc, + const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const int8_t *B, const int *LDB, const int8_t *bo, + const float *beta, int32_t *C, const int *LDC, const int32_t *co); + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.hpp new file mode 100644 index 000000000000..6c0370ae99da --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/ref_gemm_s8x8s32.hpp @@ -0,0 +1,38 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef REF_GEMM_S8X8S32_HPP +#define REF_GEMM_S8X8S32_HPP + +#include + +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +mkldnn_status_t ref_gemm_s8x8s32(const char *transa, const char *transb, + const char *offsetc, const int *M, const int *N, const int *K, + const float *alpha, const int8_t *A, const int *LDA, const int8_t *ao, + const b_dt *B, const int *LDB, const int8_t *bo, const float *beta, + int32_t *C, const int *LDC, const int32_t *co); + +} +} +} +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.cpp new file mode 100644 index 000000000000..de1035f3b20b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.cpp @@ -0,0 +1,180 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "common.hpp" +#include "nstl.hpp" +#include "math_utils.hpp" + +#include "../gemm.hpp" +#include "jit_avx512_core_gemm_s8u8s32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +void compensation_init(const char *offsetC, int32_t *compensation, int len, + const int32_t *oc) { + bool OCisC = (*offsetC == 'C' || *offsetC == 'c'); + bool OCisF = (*offsetC == 'F' || *offsetC == 'f'); + + if (OCisF && (*oc) != 0) { + for (int i = 0; i < len; i++) + compensation[i] = *oc; + } else if (OCisC) { + for (int i = 0; i < len; i++) + compensation[i] = oc[i]; + } else { + parallel_nd(len, [=](int i) { compensation[i] = 0; }); + } +} + +void compensation_compute(bool transa, int m, int k, float alpha, + const int8_t *a, int lda, int32_t *compensation) { + if (!transa) { + const int L2_cache_size = get_cache_size(2, true); + const int blocking_factor = nstl::min(k, L2_cache_size / lda + 1); + const int npanels = k / blocking_factor; + const bool has_tile = k % blocking_factor > 0; + + parallel_nd(npanels, m, [&](int j, int i) { + int32_t val = 0; + for (int jb = 0; jb < blocking_factor; jb++) { + val += a[(i + (ptrdiff_t)j * blocking_factor * lda) + + (ptrdiff_t)jb * lda]; + } + if (alpha != 1.0f) { + val = math::out_round(math::saturate( + (double)val * alpha * -128.0)); + } else { + val *= -128; + } + fetch_and_add(&compensation[i], val); + }); + + if (has_tile) { + parallel_nd(m, [=](int i) { + int32_t val = 0; + for (int j = npanels * blocking_factor; j < k; j++) { + val += a[i + (ptrdiff_t)j * lda]; + } + if (alpha != 1.0f) { + val = math::out_round(math::saturate( + (double)val * alpha * -128.0)); + } else { + val *= -128; + } + fetch_and_add(&compensation[i], val); + }); + } + } else { + parallel_nd(m, [=](int i) { + int32_t val = 0; + for (int j = 0; j < k; j++) { + val += a[j + (ptrdiff_t)i * lda]; + } + if (alpha != 1.0f) { + val = math::out_round(math::saturate( + (double)val * alpha * -128.0)); + } else { + val *= -128; + } + compensation[i] += val; + }); + } +} + +void copy_and_shift_b(bool transb, int k, int n, uint8_t *b_u8, int ldb_u8, + const int8_t *b_s8, int ldb_s8) { + const int b_cols = transb ? k : n; + + parallel_nd(b_cols, [=](int j) { + const int b_rows = transb ? n : k; + + uint8_t *pb_u8 = b_u8 + j * ldb_u8; + const int8_t *pb_s8 = b_s8 + j * ldb_s8; + + for (int i = 0; i < b_rows; i++) { + (*pb_u8) = (*pb_s8) + 128; + pb_u8++; + pb_s8++; + } + }); +} + +/** + * gemm_s8s8s32 operation is defined as follows: + * C = alpha * op(A) * (op(B) + B_shift) + beta * C + C_offset + compensation + * + * where + * - compensation is a vector of length m that contains computed compensation + * that may contain C_offset if applicable. The compensation is applied inside + * gemm_s8u8s32 as a C_offset + * - B_shift is a k-by-n matrix, every element of B_shift is equal to 128 + * + * What is the compensation: + * In order to prepare the matrix B for gemm_s8u8s32 call the B_shift is applied: + * C = alpha * op(A) * (op(B) + B_shift) + beta * C + C_offset = + * alpha * op(A) * op(B) + alpha * op(A) * B_shift + beta * C + C_offset + * compensation = -alpha * op(A) * B_shift + * Since B_shift is a matrix, every element of which is equal to 128 then + * - if op(A) = A: compensation contains sum of the elements in each row + * scaled by -128 * alpha + * - if op(A) = A**T: compensation contains sum of the elements in each column + * scaled by -128 * alpha + * + * The rest of parameters is described in mkldnn.h + */ +mkldnn_status_t simple_gemm_s8s8s32( + const char *transA, const char *transB, const char *offsetC, + const int *m, const int *n, const int *k, + const float *alpha, const int8_t *a, const int *lda, const int8_t *oa, + const int8_t *b, const int *ldb, const int8_t *ob, + const float *beta, int32_t *c, const int *ldc, const int32_t *oc) { + if (*oa != 0 || *ob != 0) return mkldnn_unimplemented; + + int M = *m, N = *n, K = *k; + bool transa = (*transA == 'T' || *transA == 't'); + bool transb = (*transB == 'T' || *transB == 't'); + int ld = transb ? N : K; + + uint8_t *b_u8 = (uint8_t *)malloc(sizeof(uint8_t) * K * N, 64); + int32_t *compensation = (int32_t *)malloc(sizeof(int32_t) * M, 64); + + if (utils::any_null(b_u8, compensation)) { + free(b_u8); + free(compensation); + return mkldnn_out_of_memory; + } + + compensation_init(offsetC, compensation, M, oc); + compensation_compute(transa, M, K, *alpha, a, *lda, compensation); + copy_and_shift_b(transb, K, N, b_u8, ld, b, *ldb); + + gemm_s8x8s32(transA, transB, "C", m, n, k, alpha, a, lda, oa, b_u8, + &ld, ob, beta, c, ldc, compensation); + + if ((*offsetC == 'R' || *offsetC == 'r')) + parallel_nd(M, N, + [=](int i, int j) { c[i + (ptrdiff_t)j * *ldc] += oc[j]; }); + + free(b_u8); + free(compensation); + + return mkldnn_success; +} +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.hpp new file mode 100644 index 000000000000..03a3d2f7e0e6 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm/s8x8s32/simple_gemm_s8s8s32.hpp @@ -0,0 +1,37 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef SIMPLE_GEMM_S8S8S32_HPP +#define SIMPLE_GEMM_S8S8S32_HPP + +#include +#include "mkldnn_types.h" + +namespace mkldnn { +namespace impl { +namespace cpu { + +mkldnn_status_t simple_gemm_s8s8s32( + const char *transA, const char *transB, const char *offsetC, + const int *m, const int *n, const int *k, + const float *alpha, const int8_t *a, const int *lda, const int8_t *oa, + const int8_t *b, const int *ldb, const int8_t *ob, + const float *beta, int32_t *c, const int *ldc, const int32_t *oc); +} +} +} + +#endif // SIMPLE_GEMM_S8S8S32_HPP diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.cpp new file mode 100644 index 000000000000..604a728b4765 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.cpp @@ -0,0 +1,307 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "gemm_convolution.hpp" +#include "utils.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "ref_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +void gemm_convolution_fwd_t::execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + auto col = scratchpad(ctx).get(key_conv_gemm_col); + + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + const int M = jcp.os * jcp.od; + const size_t src_step = jcp.ic * jcp.ih * jcp.iw * jcp.id; + const size_t dst_step = jcp.oc * M; + const size_t weights_g_size = jcp.ic * jcp.oc * jcp.ks; + + assert(IMPLICATION( + jcp.id != 1, jcp.oh_block == jcp.oh && jcp.ow_block == jcp.ow)); + assert(IMPLICATION(jcp.ow_block != jcp.ow, jcp.oh_block == 1)); + + const int K = jcp.ic * jcp.ks; + const int N = jcp.oc; + + if (jcp.im2col_sz && jcp.id != 1) + parallel_nd(jcp.im2col_sz * jcp.nthr, + [&](ptrdiff_t i) { col[i] = (data_t)0; }); + + const int nb_oh = div_up(jcp.oh, jcp.oh_block); + const int nb_ow = div_up(jcp.ow, jcp.ow_block); + const size_t work_amount = jcp.ngroups * jcp.mb * jcp.od * nb_oh * nb_ow; + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + data_t *_col = col + (ptrdiff_t)ithr * jcp.im2col_sz; + + int g{ 0 }, n{ 0 }, od{ 0 }, ohb{ 0 }, owb{ 0 }; + size_t start = 0, end = 0; + + balance211(work_amount, nthr, ithr, start, end); + nd_iterator_init(start, g, jcp.ngroups, n, jcp.mb, od, jcp.od, ohb, + nb_oh, owb, nb_ow); + for (size_t iwork = start; iwork < end; ++iwork) { + int oh = ohb * jcp.oh_block; + int ow = owb * jcp.ow_block; + const data_t *_src = src + (n * jcp.ngroups + g) * src_step; + const data_t *_weights = weights + g * weights_g_size; + data_t *_dst_im = dst + (n * jcp.ngroups + g) * dst_step; + const int h_step = nstl::min(jcp.oh_block, jcp.oh - oh); + const int w_step = nstl::min(jcp.ow_block, jcp.ow - ow); + if (jcp.im2col_sz) { + if (jcp.id == 1) + jit_gemm_convolution_utils::im2col( + jcp, _src, _col, oh, h_step, ow, w_step); + else + jit_gemm_convolution_utils::im2col_3d(jcp, _src, _col, od); + } + + const data_t one = 1.0; + + const int m = h_step * w_step; + const int LDA = jcp.im2col_sz ? m : M; + data_t *_dst = _dst_im + od * jcp.os + oh * jcp.ow + ow; + + extended_sgemm("N", "N", &m, &N, &K, &one, + jcp.im2col_sz ? _col : _src + od * m, &LDA, _weights, &K, + &this->beta_, _dst, &M); + + data_t *d = _dst; + if (eltwise_) { + // fast branch for ReLU case + if (eltwise_->alg_ == alg_kind::eltwise_relu) { + parallel_nd(jcp.oc, [&](const int oc) { + data_t b = jcp.with_bias ? bias[g * jcp.oc + oc] : 0; + data_t *d_ = d + oc * M; + PRAGMA_OMP_SIMD() + for (int oS = 0; oS < m; ++oS) { + d_[oS] += b; + if (d_[oS] < 0) d_[oS] *= eltwise_->alpha_; + } + }); + } else { + parallel_nd(jcp.oc, [&](const int oc) { + data_t b = jcp.with_bias ? bias[g * jcp.oc + oc] : 0; + data_t *d_ = d + oc * M; + PRAGMA_OMP_SIMD() + for (int oS = 0; oS < m; ++oS) { + d_[oS] += b; + d_[oS] = eltwise_->compute_scalar(d_[oS]); + } + }); + } + } else if (jcp.with_bias) { + parallel_nd(jcp.oc, [&](const int oc) { + data_t b = bias[g * jcp.oc + oc]; + data_t *d_ = d + oc * M; + PRAGMA_OMP_SIMD() + for (int oS = 0; oS < m; ++oS) { + d_[oS] += b; + } + }); + } + nd_iterator_step(g, jcp.ngroups, n, jcp.mb, od, jcp.od, ohb, nb_oh, + owb, nb_ow); + } + }); +} + +void gemm_convolution_bwd_data_t::execute_backward_data( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + auto col = scratchpad(ctx).get(key_conv_gemm_col); + + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + const int M = jcp.os * jcp.od; + const size_t src_step = jcp.ic * jcp.ih * jcp.iw * jcp.id; + const size_t dst_step = jcp.oc * M; + const size_t weights_g_size = jcp.ic * jcp.oc * jcp.ks; + + const int m = jcp.os; + const int K = jcp.oc; + const int N = jcp.ic * jcp.ks; + const int LDC = jcp.im2col_sz ? m : M; + + const size_t work_amount = (size_t)jcp.ngroups * jcp.mb; + + if (jcp.id > 1) { + const ptrdiff_t diff_src_sz = (ptrdiff_t)(work_amount * src_step); + parallel_nd(diff_src_sz, [&](ptrdiff_t i) { diff_src[i] = (data_t)0; }); + } + + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + data_t *_col = col + (ptrdiff_t)ithr * jcp.im2col_sz; + + int g{0}, n{0}; + size_t start = 0, end = 0; + balance211(work_amount, nthr, ithr, start, end); + nd_iterator_init(start, g, jcp.ngroups, n, jcp.mb); + for (size_t iwork = start; iwork < end; ++iwork) { + + data_t *_diff_src = diff_src + (n * jcp.ngroups + g)*src_step; + const data_t *_weights = weights + g * weights_g_size; + for (int od = 0; od < jcp.od; ++od) { + const data_t *_diff_dst = diff_dst + (n * jcp.ngroups + g) + *dst_step + od * m; + + const data_t zero = 0.0, one = 1.0; + extended_sgemm("N", "T", &m, &N, &K, &one, _diff_dst, &M, + _weights, &N, &zero, + jcp.im2col_sz ? _col:_diff_src + od * m, &LDC); + + if (jcp.im2col_sz) { + if (jcp.id == 1) + jit_gemm_convolution_utils::col2im(jcp, _col, + _diff_src); + else + jit_gemm_convolution_utils::col2im_3d(jcp, _col, + _diff_src, od); + } + } + nd_iterator_step(g, jcp.ngroups, n, jcp.mb); + } + }); +} + +void gemm_convolution_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + auto col = scratchpad(ctx).get(key_conv_gemm_col); + auto wei_reduction = scratchpad(ctx).get(key_conv_wei_reduction); + + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + const int K = jcp.os * jcp.od; + const size_t src_step = jcp.ic * jcp.ih * jcp.iw * jcp.id; + const size_t dst_step = jcp.oc * K; + const size_t weights_g_size = jcp.ic * jcp.oc * jcp.ks; + + const int k = jcp.os; + const int N = jcp.oc; + const int M = jcp.ic * jcp.ks; + const int LDA = jcp.im2col_sz ? k : K; + + parallel_nd(jcp.im2col_sz * jcp.nthr, + [&](ptrdiff_t i) { col[i] = (data_t)0; }); + + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + int ithr_g, nthr_g, ithr_mb, nthr_mb; + size_t g_start{0}, g_end{0}, mb_start{0}, mb_end{0}; + + const int mb_for_balance = jcp.need_wei_reduction ? jcp.mb : 1; + jit_gemm_convolution_utils::bwd_weights_balance(ithr, nthr, jcp.ngroups, + mb_for_balance, ithr_g, nthr_g, ithr_mb, nthr_mb); + + assert(IMPLICATION(!jcp.need_wei_reduction, nthr_mb == 1)); + const int need_reduction = nthr_mb != 1; + + if (ithr_g != -1 && ithr_mb != -1) { + balance211((size_t)jcp.ngroups, nthr_g, ithr_g, g_start, g_end); + balance211((size_t)jcp.mb, nthr_mb, ithr_mb, mb_start, mb_end); + + assert(IMPLICATION((g_end - g_start) > 1, need_reduction == 0)); + + data_t *_col = col + (ptrdiff_t)ithr * jcp.im2col_sz; + data_t *weights_reduce_base = wei_reduction + + ithr_g * nthr_mb * weights_g_size; + data_t *weights_reduce = weights_reduce_base + + ithr_mb * weights_g_size; + + for (size_t g = g_start; g < g_end; ++g) { + data_t *_diff_weights = need_reduction + ? weights_reduce : (diff_weights + g * weights_g_size); + for (size_t mb = mb_start; mb < mb_end; ++mb) { + const data_t *_src = src + (mb*jcp.ngroups+g)*src_step; + for (int od = 0; od < jcp.od; ++od) { + const data_t *_diff_dst = diff_dst + + (mb*jcp.ngroups+g)*dst_step + od * k; + + if (jcp.im2col_sz) { + if (jcp.id == 1) + jit_gemm_convolution_utils::im2col( + jcp, _src, _col, 0, jcp.oh, 0, jcp.ow); + else + jit_gemm_convolution_utils::im2col_3d(jcp, _src, + _col, od); + } + + const data_t zero = 0.0, one = 1.0; + extended_sgemm( + "T", "N", &M, &N, &k, &one, + jcp.im2col_sz ? _col : _src + od * k, + &LDA, _diff_dst, &K, + mb == mb_start && od == 0 ? &zero : &one, + _diff_weights, &M); + } + } + } + if (need_reduction) { + mkldnn_thr_barrier(); + data_t *weights_base = diff_weights + g_start * weights_g_size; + jit_gemm_convolution_utils::bwd_weights_reduction_par( + ithr_mb, nthr_mb, jcp, weights_reduce_base, weights_base); + } + } else + if (need_reduction) { mkldnn_thr_barrier(); } + }); + + if (jcp.with_bias) { + parallel_nd(jcp.ngroups, jcp.oc, [&](int g, int oc) { + data_t db = 0; + size_t offset_ = (size_t)g * dst_step + (size_t)oc * K; + for (int mb = 0; mb < jcp.mb; ++mb) + { + size_t offset = offset_ + (size_t)mb * jcp.ngroups * dst_step; + for (int od = 0; od < jcp.od; ++od) + for (int oh = 0; oh < jcp.oh; ++oh) + PRAGMA_OMP_SIMD(reduction(+:db)) + for (int ow = 0; ow < jcp.ow; ++ow) { + db += diff_dst[offset]; + offset++; + } + } + diff_bias[g*jcp.oc+oc] = db; + }); + } +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.hpp new file mode 100644 index 000000000000..302e46369a3f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution.hpp @@ -0,0 +1,250 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_GEMM_CONVOLUTION_HPP +#define CPU_JIT_GEMM_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "gemm_convolution_utils.hpp" +#include "gemm/gemm.hpp" +#include "ref_eltwise.hpp" + +#include "cpu_convolution_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct gemm_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T(GEMM_IMPL_STR, gemm_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats_common(dat_tag(), wei_tag(), dat_tag()) + && post_ops_ok() + && memory_desc_matches_tag(*src_md(), dat_tag()) + && memory_desc_matches_tag(*dst_md(), dat_tag()) + && memory_desc_matches_tag(*weights_md(), wei_tag()); + if (!ok) return status::unimplemented; + + auto scratchpad = scratchpad_registry().registrar(); + return jit_gemm_convolution_utils::init_conf(jcp_, scratchpad, + *desc(), src_md(), weights_md(0), dst_md(), + mkldnn_get_max_threads()); + } + + jit_gemm_conv_conf_t jcp_; + + protected: + format_tag_t dat_tag() const { + using namespace format_tag; + return utils::pick(ndims() - 3, ncw, nchw, ncdhw); + } + + format_tag_t wei_tag() const { + using namespace format_tag; + return with_groups() + ? utils::pick(ndims() - 3, goiw, goihw, goidhw) + : utils::pick(ndims() - 3, oiw, oihw, oidhw); + } + + bool post_ops_ok() const { + auto const &po = attr()->post_ops_; + auto is_eltwise = [&](int idx) + { return po.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return po.entry_[idx].is_sum(); }; + + switch (po.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + return false; + } + }; + + gemm_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd, true) + , eltwise_(nullptr) + { + const auto &post_ops = pd()->attr()->post_ops_; + const data_t one = 1.0, zero = 0.0; + beta_ = post_ops.find(primitive_kind::sum) >= 0 ? one : zero; + + const int entry_idx = post_ops.find(primitive_kind::eltwise); + if (entry_idx != -1) eltwise_ = new ref_eltwise_scalar_fwd_t( + post_ops.entry_[entry_idx].eltwise); + } + + ~gemm_convolution_fwd_t() { delete eltwise_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + data_t beta_; + + ref_eltwise_scalar_fwd_t* eltwise_; +}; + +struct gemm_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T(GEMM_IMPL_STR, gemm_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::undef, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats_common(dat_tag(), wei_tag(), dat_tag()) + && memory_desc_matches_tag(*diff_src_md(), dat_tag()) + && memory_desc_matches_tag(*diff_dst_md(), dat_tag()) + && memory_desc_matches_tag(*weights_md(), wei_tag()); + if (!ok) return status::unimplemented; + + auto scratchpad = scratchpad_registry().registrar(); + return jit_gemm_convolution_utils::init_conf(jcp_, scratchpad, + *desc(), diff_src_md(), weights_md(0), diff_dst_md(), + mkldnn_get_max_threads()); + } + + jit_gemm_conv_conf_t jcp_; + + protected: + format_tag_t dat_tag() const { + using namespace format_tag; + return utils::pick(ndims() - 3, ncw, nchw, ncdhw); + } + + format_tag_t wei_tag() const { + using namespace format_tag; + return with_groups() + ? utils::pick(ndims() - 3, goiw, goihw, goidhw) + : utils::pick(ndims() - 3, oiw, oihw, oidhw); + } + }; + + gemm_convolution_bwd_data_t(const pd_t *apd) + : cpu_primitive_t(apd, true) {} + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct gemm_convolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T(GEMM_IMPL_STR, gemm_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats_common(dat_tag(), wei_tag(), dat_tag()) + && memory_desc_matches_tag(*src_md(), dat_tag()) + && memory_desc_matches_tag(*diff_dst_md(), dat_tag()) + && memory_desc_matches_tag(*diff_weights_md(), wei_tag()); + if (!ok) return status::unimplemented; + + auto scratchpad = scratchpad_registry().registrar(); + return jit_gemm_convolution_utils::init_conf(jcp_, scratchpad, + *desc(), src_md(), diff_weights_md(0), diff_dst_md(), + mkldnn_get_max_threads()); + } + + jit_gemm_conv_conf_t jcp_; + + protected: + format_tag_t dat_tag() const { + using namespace format_tag; + return utils::pick(ndims() - 3, ncw, nchw, ncdhw); + } + + format_tag_t wei_tag() const { + using namespace format_tag; + return with_groups() + ? utils::pick(ndims() - 3, goiw, goihw, goidhw) + : utils::pick(ndims() - 3, oiw, oihw, oidhw); + } + }; + + gemm_convolution_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd, true) {} + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.cpp new file mode 100644 index 000000000000..f133b1e62b36 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.cpp @@ -0,0 +1,771 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" +#include "cpu_isa_traits.hpp" + +#include "gemm_convolution_utils.hpp" +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; +using namespace prop_kind; +using namespace data_type; + +namespace jit_gemm_convolution_utils { + +void im2col_3d(const jit_gemm_conv_conf_t &jcp, const float *im, float *col, + int od) +{ + const size_t OHW = jcp.oh * jcp.ow; + const size_t im_step = jcp.ih * jcp.iw * jcp.id; + const size_t col_step = jcp.ks * OHW; + + parallel_nd(jcp.ic, [&](int ic) { + const float *__restrict im_loc = im + ic * im_step; + float *__restrict col_loc = col + ic * col_step; + int id = od * jcp.stride_d - jcp.f_pad; + for (int kd = 0; kd < jcp.kd; ++kd) { + float *__restrict col_ = col_loc + kd * jcp.kh * jcp.kw * OHW; + if (id < 0 || id >= jcp.id) { + int ih_ = -jcp.t_pad; + for (int kh = 0; kh < jcp.kh; ++kh) { + int ih = ih_; + for (int oh = 0; oh < jcp.oh; ++oh) { + if (ih < 0 || ih >= jcp.ih) { + ih += jcp.stride_h; + continue; + } + int iw_ = -jcp.l_pad; + for (int kw = 0; kw < jcp.kw; ++kw) { + int iw = iw_; + for (int ow = 0; ow < jcp.ow; ++ow) { + if (iw < 0 || iw >= jcp.iw) { + iw += jcp.stride_w; + continue; + } + + const size_t col_idx = kw * OHW + oh * jcp.ow + + ow; + + col_[col_idx] = 0; + iw += jcp.stride_w; + } + iw_ += (1 + jcp.dilate_w); + } + ih += jcp.stride_h; + } + ih_ += (1 + jcp.dilate_h); + col_ += jcp.kw * OHW; + } + } else { + const float *__restrict im_ = im_loc + id * jcp.ih * jcp.iw; + int ih_ = -jcp.t_pad; + for (int kh = 0; kh < jcp.kh; ++kh) { + int ih = ih_; + for (int oh = 0; oh < jcp.oh; ++oh) { + if (ih < 0 || ih >= jcp.ih) { + ih += jcp.stride_h; + continue; + } + int iw_ = -jcp.l_pad; + for (int kw = 0; kw < jcp.kw; ++kw) { + int iw = iw_; + for (int ow = 0; ow < jcp.ow; ++ow) { + if (iw < 0 || iw >= jcp.iw) { + iw += jcp.stride_w; + continue; + } + + const size_t col_idx = kw * OHW + oh * jcp.ow + + ow; + const size_t im_idx = ih * jcp.iw + iw; + + col_[col_idx] = im_[im_idx]; + iw += jcp.stride_w; + } + iw_ += (1 + jcp.dilate_w); + } + ih += jcp.stride_h; + } + ih_ += (1 + jcp.dilate_h); + col_ += jcp.kw * OHW; + } + } + id += (1 + jcp.dilate_d); + } + }); +} + +/* col[ic][kh][kw][oh][ow] <-- im2col(im[ic][ih][iw]) */ +void im2col(const jit_gemm_conv_conf_t &jcp, const float *__restrict im, + float *__restrict col, int hs, int hb, int ws, int wb) { + const size_t im_step = jcp.is; + const size_t col_step = jcp.ks * hb * wb; + if (jcp.stride_w == 1) { + // Generated code is more optimized for stride_w == 1 + // because innermost loop is by width + auto ker = [&](int ic, int kh, int kw, int oh) { + const float *__restrict im_ = im + ic * im_step; + float *__restrict col_ + = col + ic * col_step + ((kh * jcp.kw + kw) * hb + oh) * wb; + + const int ih = (oh + hs) * jcp.stride_h - jcp.t_pad + + kh * (1 + jcp.dilate_h); + if (ih < 0 || ih >= jcp.ih) { + for (int ow = 0; ow < wb; ++ow) + col_[ow] = 0.f; + } else { + for (int ow = 0; ow < wb; ++ow) { + const int iw = ow + ws - jcp.l_pad + kw * (1 + jcp.dilate_w); + if (iw < 0 || iw >= jcp.iw) + col_[ow] = 0.f; + else { + const size_t im_idx = ih * jcp.iw + iw; + col_[ow] = im_[im_idx]; + } + } + } + }; + + if (jcp.outer_threading) { + for (int ic = 0; ic < jcp.ic; ic++) + for (int kh = 0; kh < jcp.kh; kh++) + for (int kw = 0; kw < jcp.kw; kw++) + for (int oh = 0; oh < hb; oh++) + ker(ic, kh, kw, oh); + } + else { + parallel_nd(jcp.ic, jcp.kh, jcp.kw, hb, ker); + } + } else if (jcp.ic == 1) { + parallel_nd(jcp.kh, hb, [&](int kh, int oh) { + const int ih = (oh + hs) * jcp.stride_h - jcp.t_pad + + kh * (1 + jcp.dilate_h); + if (ih < 0 || ih >= jcp.ih) + for (int kw = 0; kw < jcp.kw; ++kw) { + for (int ow = 0; ow < wb; ++ow) { + const size_t col_idx + = ((kh * jcp.kw + kw) * hb + oh) * wb + ow; + col[col_idx] = 0; + } + } + else + for (int kw = 0; kw < jcp.kw; ++kw) { + for (int ow = 0; ow < wb; ++ow) { + const int iw = (ow + ws) * jcp.stride_w - jcp.l_pad + + kw * (1 + jcp.dilate_w); + const size_t col_idx + = ((kh * jcp.kw + kw) * hb + oh) * wb + ow; + const size_t im_idx = ih * jcp.iw + iw; + if (iw < 0 || iw >= jcp.iw) + col[col_idx] = 0; + else + col[col_idx] = im[im_idx]; + } + } + }); + } else { + + parallel_nd(jcp.ic, jcp.kh, jcp.kw, hb, + [&](int ic, int kh, int kw, int oh) { + const float *__restrict im_ = im + ic * im_step; + float *__restrict col_ = col + ic * col_step + + ((kh * jcp.kw + kw) * hb + oh) * wb; + + const int ih = (oh + hs) * jcp.stride_h - jcp.t_pad + + kh * (1 + jcp.dilate_h); + if (ih < 0 || ih >= jcp.ih) { + for (int ow = 0; ow < wb; ++ow) + col_[ow] = 0.f; + } else { + for (int ow = 0; ow < wb; ++ow) { + const int iw = (ow + ws) * jcp.stride_w - jcp.l_pad + + kw * (1 + jcp.dilate_w); + const size_t im_idx = ih * jcp.iw + iw; + if (iw < 0 || iw >= jcp.iw) + col_[ow] = 0.f; + else + col_[ow] = im_[im_idx]; + } + } + }); + } +} + +inline int limit(int low, int upper, int value) { + return nstl::max(low, nstl::min(upper, value)); +} + +/* col[kh][kw][ic][oh][ow] <-- im2col_u8(im[ih][iw][ic]) */ +template +void im2col_u8(const jit_gemm_conv_conf_t &jcp, const T *__restrict im, + T *__restrict imtr, uint8_t *__restrict col, int hs, int hb, int ws, + int wb) { + uint8_t shift = jcp.signed_input ? 128 : 0; + const int dh = 1 + jcp.dilate_h; + const int dw = 1 + jcp.dilate_w; + const int sh = jcp.stride_h; + const int sw = jcp.stride_w; + const int im_iw_stride = jcp.ic * jcp.ngroups; + const int im_ih_stride = jcp.iw * im_iw_stride; + const int tp = jcp.t_pad; + const int lp = jcp.l_pad; + + if (jcp.outer_threading && sh == 1 && sw == 1 && dh == 1 && dw == 1) { + /* im[ih][iw][ic] --> imtr[ic][ih][iw] --> col[kh][kw][ic][oh][ow] */ + const int hp = hs - tp; + const int wp = ws - lp; + const int ih_start = limit(0, jcp.ih, hp); + const int ih_end = limit(0, jcp.ih, hp + hb + jcp.kh); + const int iw_start = limit(0, jcp.iw, wp); + const int iw_end = limit(0, jcp.iw, wp + wb + jcp.kw); + + const int ihb = ih_end - ih_start; + const int iwb = iw_end - iw_start; + + const int imtr_ic_stride = ihb * iwb; + const ptrdiff_t imtr_idx_shift = ih_start * iwb + iw_start; + for (int ic = 0; ic < jcp.ic; ic++) { + const ptrdiff_t imtr_idx_ic = ic * imtr_ic_stride - imtr_idx_shift; + for (int ih = ih_start; ih < ih_end; ih++) { + const ptrdiff_t im_idx_ih = ic + ih * im_ih_stride; + const ptrdiff_t imtr_idx_ih = imtr_idx_ic + ih * iwb; + for (int iw = iw_start; iw < iw_end; iw++) + imtr[imtr_idx_ih + iw] = im[im_idx_ih + iw * im_iw_stride]; + } + } + + const int col_ic_str = hb * wb; + const int col_kw_stride = jcp.ic * col_ic_str; + const int col_kh_stride = jcp.kw * col_kw_stride; + + const int oh_init = ih_start - hp; + const int ow_init = iw_start - wp; + for (int kh = 0; kh < jcp.kh; kh++) { + const ptrdiff_t col_idx_kh = kh * col_kh_stride; + const int oh_kh = oh_init - kh; + const int oh_start = limit(0, hb, oh_kh); + const int oh_end = limit(0, hb, oh_kh + ihb); + for (int kw = 0; kw < jcp.kw; kw++) { + const ptrdiff_t col_idx_kw + = col_idx_kh + kw * jcp.ic * col_ic_str; + const int ow_kw = ow_init - kw; + const int imtr_shift = oh_kh * iwb + ow_kw; + const int ow_start = limit(0, wb, ow_kw); + const int ow_end = limit(0, wb, ow_kw + iwb); + for (int ic = 0; ic < jcp.ic; ic++) { + const ptrdiff_t col_idx_ic = col_idx_kw + ic * col_ic_str; + const int imtr_idx_ic = ic * imtr_ic_stride - imtr_shift; + for (int oh = 0; oh < oh_start; oh++) { + const ptrdiff_t col_idx_oh = col_idx_ic + oh * wb; + for (int ow = 0; ow < wb; ++ow) + col[col_idx_oh + ow] = shift; + } + for (int oh = oh_start; oh < oh_end; oh++) { + const ptrdiff_t col_idx_oh = col_idx_ic + oh * wb; + const ptrdiff_t imtr_idx_oh = imtr_idx_ic + oh * iwb; + for (int ow = 0; ow < ow_start; ++ow) + col[col_idx_oh + ow] = shift; + for (int ow = ow_start; ow < ow_end; ++ow) + col[col_idx_oh + ow] + = imtr[imtr_idx_oh + ow] + shift; + for (int ow = ow_end; ow < wb; ++ow) + col[col_idx_oh + ow] = shift; + } + for (int oh = oh_end; oh < hb; oh++) { + const ptrdiff_t col_idx_oh = col_idx_ic + oh * wb; + for (int ow = 0; ow < wb; ++ow) + col[col_idx_oh + ow] = shift; + } + } + } + } + } else { + parallel_nd(jcp.kh, jcp.kw, jcp.ic, hb, + [&](int kh, int kw, int ic, int oh) { + const int hp = tp - kh * dh; + const int ih = (oh + hs) * sh - hp; + const ptrdiff_t col_idx_base + = (((kh * jcp.kw + kw) * jcp.ic + ic) * hb + oh) * wb; + if (ih < 0 || ih >= jcp.ih) + for (int ow = 0; ow < wb; ow++) + col[col_idx_base + ow] = shift; + else { + const int wp = lp - kw * dw; + const int ow_start = limit(0, wb, div_up(wp, sw) - ws); + const int ow_end + = limit(0, wb, div_up(jcp.iw + wp, sw) - ws); + for (int ow = 0; ow < ow_start; ow++) + col[col_idx_base + ow] = shift; + const int iw_base = ws * sw - wp; + const ptrdiff_t im_idx_base = ih * im_ih_stride + ic; + for (int ow = ow_start; ow < ow_end; ow++) { + const int iw = iw_base + ow * sw; + const ptrdiff_t im_idx + = im_idx_base + iw * im_iw_stride; + col[col_idx_base + ow] = im[im_idx] + shift; + } + for (int ow = ow_end; ow < wb; ow++) + col[col_idx_base + ow] = shift; + } + }); + } +} + +template void im2col_u8(const jit_gemm_conv_conf_t &jcp, + const int8_t *__restrict im, int8_t *__restrict imtr, + uint8_t *__restrict col, int hs, int hb, int ws, int wb); +template void im2col_u8(const jit_gemm_conv_conf_t &jcp, + const uint8_t *__restrict im, uint8_t *__restrict imtr, + uint8_t *__restrict col, int hs, int hb, int ws, int wb); + +/* im[ih][iw][ic] <-- col2im_s32(col[oh][ow][kh][kw][ic]) */ +void col2im_s32(const jit_gemm_conv_conf_t &jcp, const int32_t *__restrict col, + int32_t *__restrict im) +{ + parallel(0, [&](const int ithr, const int nthr) { + int h_nthr = nstl::min(jcp.ih, nthr); + int w_nthr = nstl::min(jcp.iw, nthr / h_nthr); + int h_ithr = 1, h_s = 0, h_e = 0, w_ithr = 1, w_s = 0, w_e = 0; + if (ithr < h_nthr * w_nthr) { + h_ithr = ithr / w_nthr; + w_ithr = ithr % w_nthr; + balance211(jcp.ih, h_nthr, h_ithr, h_s, h_e); + balance211(jcp.iw, w_nthr, w_ithr, w_s, w_e); + } else { + h_ithr = w_ithr = -ithr; + h_s = h_e = w_s = w_e = -1; + } + + for (int ih = h_s; ih < h_e; ++ih) { + for (int iw = w_s; iw < w_e; ++iw) { + PRAGMA_OMP_SIMD() + for (int ic = 0; ic < jcp.ic; ++ic) { + im[(ih * jcp.iw + iw) * jcp.ic + ic] = 0; + } + } + } + + // TODO: reduce region: [0.. oh] --> [h_s * sh .. h_e * sh] + for (int oh = 0; oh < jcp.oh; ++oh) { + for (int ow = 0; ow < jcp.ow; ++ow) { + for (int kh = 0; kh < jcp.kh; ++kh) { + const int ih = oh * jcp.stride_h + - jcp.t_pad + kh * (1 + jcp.dilate_h); + if (ih < h_s || ih >= h_e) continue; + + for (int kw = 0; kw < jcp.kw; ++kw) { + const int iw = ow * jcp.stride_w + - jcp.l_pad + kw * (1 + jcp.dilate_w); + if (iw < w_s || iw >= w_e) continue; + + const size_t col_idx = (((oh * jcp.ow + ow) * jcp.kh + + kh) * jcp.kw + kw) * jcp.ic; + const size_t im_idx + = (ih * jcp.iw + iw) * jcp.ic; + PRAGMA_OMP_SIMD() + for (int ic = 0; ic < jcp.ic; ++ic) { + im[im_idx + ic] += col[col_idx + ic]; + } + } + } + } + } + }); +} + +void col2im_3d(const jit_gemm_conv_conf_t &jcp, const float *col, float *im, + int od) +{ + parallel_nd(jcp.ic, [&](int ic) { + const float *__restrict col_ = col + (size_t)ic * jcp.ks * jcp.os; + float *__restrict im_ic = im + (size_t)ic * jcp.ih * jcp.iw * jcp.id; + + int id = od * jcp.stride_d - jcp.f_pad; + for (int kd = 0; kd < jcp.kd; ++kd) { + if (id < 0 || id >= jcp.id) { + col_ += jcp.kh * jcp.kw * jcp.os; + id += (1 + jcp.dilate_d); + continue; + } + + float *__restrict im_ = im_ic + id * jcp.ih * jcp.iw; + + for (int oh = 0; oh < jcp.oh; ++oh) { + for (int kh = 0; kh < jcp.kh; ++kh) { + const int ih = oh * jcp.stride_h - jcp.t_pad + + kh * (1 + jcp.dilate_h); + if (ih < 0 || ih >= jcp.ih) continue; + + for (int ow = 0; ow < jcp.ow; ++ow) { + for (int kw = 0; kw < jcp.kw; ++kw) { + const int iw = ow * jcp.stride_w - jcp.l_pad + + kw * (1 + jcp.dilate_w); + if (iw < 0 || iw >= jcp.iw) continue; + + const size_t col_idx = ((kh*jcp.kw + kw)*jcp.oh+oh)*jcp.ow+ow; + const size_t im_idx = ih*jcp.iw + iw; + im_[im_idx] += col_[col_idx]; + }} + }} + + col_ += jcp.kh * jcp.kw * jcp.os; + id += (1 + jcp.dilate_d); + } + }); +} + +void col2im(const jit_gemm_conv_conf_t &jcp, const float *col, float *im) { + const size_t col_step = jcp.ks * jcp.os; + const size_t im_step = jcp.ih * jcp.iw; + const int iS = jcp.ih * jcp.iw; + + parallel_nd(jcp.ic, [&](int ic) { + float *__restrict im_ = im + ic * im_step; + const float *__restrict col_ = col + ic * col_step; + PRAGMA_OMP_SIMD() + for (int is = 0; is < iS; ++is) im_[is] = 0.; + + for (int kh = 0; kh < jcp.kh; ++kh) { + for (int oh = 0; oh < jcp.oh; ++oh) { + const int ih = + oh * jcp.stride_h - jcp.t_pad + kh * (1 + jcp.dilate_h); + if (ih < 0 || ih >= jcp.ih) continue; + + for (int kw = 0; kw < jcp.kw; ++kw) { + for (int ow = 0; ow < jcp.ow; ++ow) { + const int iw = + ow * jcp.stride_w - jcp.l_pad + kw * (1 + jcp.dilate_w); + if (iw < 0 || iw >= jcp.iw) continue; + + const size_t col_idx = ((kh*jcp.kw + kw)*jcp.oh+oh)*jcp.ow+ow; + const size_t im_idx = ih*jcp.iw + iw; + im_[im_idx] += col_[col_idx]; + } + } + } + } + }); +} + +status_t init_conf(jit_gemm_conv_conf_t &jcp, + memory_tracking::registrar_t &scratchpad, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, int max_threads) { + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + const int ndims = src_d.ndims(); + const int is_1d = ndims == 3; + const int is_3d = ndims == 5; + + jcp.prop_kind = cd.prop_kind; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.id = is_3d ? src_d.dims()[2] : 1; + jcp.ih = is_1d ? 1 : src_d.dims()[ndims - 2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.od = is_3d ? dst_d.dims()[2] : 1; + jcp.oh = is_1d ? 1 : dst_d.dims()[ndims - 2]; + jcp.ow = dst_d.dims()[ndims - 1]; + + jcp.kd = is_3d ? weights_d.dims()[with_groups + 2] : 1; + jcp.kh = is_1d ? 1 : weights_d.dims()[with_groups + ndims - 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.f_pad = is_3d ? cd.padding[0][0] : 0; + jcp.t_pad = is_1d ? 0 : cd.padding[0][ndims - 4]; + jcp.l_pad = cd.padding[0][ndims - 3]; + + jcp.stride_d = is_3d ? cd.strides[0] : 1; + jcp.stride_h = is_1d ? 1 : cd.strides[ndims - 4]; + jcp.stride_w = cd.strides[ndims - 3]; + + jcp.dilate_d = is_3d ? cd.dilates[0] : 0; + jcp.dilate_h = is_1d ? 0 : cd.dilates[ndims - 4]; + jcp.dilate_w = cd.dilates[ndims - 3]; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef + || cd.diff_bias_desc.format_kind != format_kind::undef; + + jcp.is = jcp.ih * jcp.iw; + jcp.os = jcp.oh * jcp.ow; + jcp.ks = jcp.kh * jcp.kw * jcp.kd; + + jcp.signed_input = src_d.data_type() == data_type::s8; + + jcp.im2col_sz = !everyone_is(true, + jcp.ow == jcp.iw, jcp.oh == jcp.ih, jcp.od == jcp.id, + jcp.stride_w == 1, jcp.stride_h == 1, jcp.stride_d == 1, + jcp.ks == 1, !jcp.signed_input) + ? (ptrdiff_t)jcp.ic * jcp.ks * jcp.os : 0; + + jcp.outer_threading = false; + + bool is_int8_conv = utils::one_of(src_d.data_type(), s32, s8, u8) + && weights_d.data_type() == s8; + + const int vlen = mayiuse(avx512_common) + ? cpu_isa_traits::vlen + : mayiuse(avx) + ? cpu_isa_traits::vlen + : mayiuse(sse42) ? cpu_isa_traits::vlen : 4; + const int simd_w = vlen / (is_int8_conv ? 1 : 4); + + const bool is_bwd_d = jcp.prop_kind == backward_data; + const bool is_bwd_w = jcp.prop_kind == backward_weights; + const bool is_fwd = !is_bwd_d && !is_bwd_w; + jcp.oh_block = is_fwd ? jcp.oh : jcp.ih; + jcp.ow_block = is_fwd ? jcp.ow : jcp.iw; + + using namespace memory_tracking::names; + bool is_depthwise = jcp.ic == 1 && jcp.oc == 1 && jcp.ngroups != 1; + + // TODO: maybe mitigate blocking restriction + const int wei_size = jcp.oc * jcp.ic * jcp.kh * jcp.kw; + const int L2 = get_cache_size(2, true) + / (is_int8_conv ? sizeof(int8_t) : sizeof(float)); + bool is_blocking_applicable = true + && is_fwd && jcp.im2col_sz + && jcp.id == 1 && jcp.od == 1 + && jcp.dilate_h == 0 && jcp.dilate_w == 0 + && !is_depthwise + && wei_size < L2/2; + if (is_blocking_applicable) { + // looking for oh and ow blocking + int h_block{ jcp.oh_block }, w_block{ jcp.ow_block }; + const int ic = jcp.ic; + const int oc = jcp.oc; + const int iw = jcp.iw; + const int ow = jcp.ow; + const int oh = jcp.oh; + const int os = oh * ow; + + // 1. cache requirement + int row_size = ic * ow * jcp.ks + 2 * (ic * iw + oc * ow); + if (is_int8_conv) { + // Heuristic rule: gemm needed a lot of memory for internal usage + row_size *= 5; + // memory for accumulators + row_size += oc * ow * sizeof(uint32_t); + // memory for transposition + row_size += ic * iw; + } + + h_block = nstl::max(1, nstl::min(oh, div_up(L2, row_size))); + if (h_block == 1) { + int col_size = ic * jcp.ks + 2 * (ic + oc); + if (is_int8_conv) { + col_size *= 5; + col_size += oc * sizeof(uint32_t); + col_size += ic; + } + w_block = nstl::max(1, nstl::min(ow, div_up(L2, col_size))); + } + + // 2. threading requirement + if (h_block != oh) + h_block = nstl::max(1, rnd_dn(h_block, 4)); + if (w_block != ow) + w_block = nstl::max(1, rnd_dn(w_block, simd_w)); + + float thr_eff = 0.f; + float thr_eff_treshold = 0.9f; + if (w_block == ow) { + do { + int nb_h = div_up(oh, h_block); + size_t work = jcp.ngroups * jcp.mb * jcp.od * nb_h; + float disb = (float)oh / rnd_up(oh, h_block); + thr_eff = (float)work / rnd_up(work, max_threads); + thr_eff = (thr_eff + disb) / 2.f; + if (thr_eff >= thr_eff_treshold) + break; + h_block = rnd_dn(h_block - 4, 4); + } while (h_block > 0); + } + if (thr_eff < thr_eff_treshold) // we didn't find suitable h_block + { + h_block = 1; + int nb_h = oh; + do { + int nb_w = div_up(ow, w_block); + size_t work_amount = jcp.ngroups * jcp.mb * nb_h * nb_w; + float disb = (float)ow / rnd_up(ow, w_block); + thr_eff = (float)work_amount / rnd_up(work_amount, max_threads); + thr_eff = (thr_eff + disb) / 2.f; + if (thr_eff > thr_eff_treshold) + break; + w_block = rnd_dn(w_block - simd_w, simd_w); + } while (w_block > 0); + } + h_block = nstl::max(1, h_block); + w_block = nstl::max(1, w_block); + const size_t inner_work = div_up(os, simd_w) * div_up(oc, simd_w); + const float inner_thr_eff + = (float)inner_work / rnd_up(inner_work, max_threads); + if (thr_eff >= inner_thr_eff / 2 && h_block > 0 && w_block > 0) { + jcp.oh_block = h_block; + jcp.ow_block = w_block; + jcp.outer_threading = true; + } + // updating jcp.im2col_sz + if (jcp.oh_block != 1) + jcp.ow_block = ow; + jcp.im2col_sz = (ptrdiff_t)ic * jcp.ks * jcp.oh_block * jcp.ow_block; + } + // For threading selection in bwd_d we do: + // 1. Rough estimation of efficiency for inner and outer threading. + // 2. Gemm size estimation in assumption that it does not work + // so effectively for small sizes. + // 64K - this is heuristic gemm size per thread threshold. + const int gemm_thrld = 64 * 1024; + + if (is_int8_conv) { + if (is_fwd) { + if (!jcp.outer_threading) { + bool is_depthwise = jcp.ic == 1 && jcp.oc == 1 && jcp.ngroups != 1; + const size_t outer_work = jcp.ngroups * jcp.mb; + const float outer_thr_eff + = (float)outer_work / rnd_up(outer_work, max_threads); + const size_t inner_work + = div_up(jcp.is, simd_w) * div_up(jcp.ic, simd_w); + const float inner_thr_eff + = (float)inner_work / rnd_up(inner_work, max_threads); + jcp.outer_threading = (is_depthwise + || (jcp.is / max_threads < 64 && jcp.mb != 1)) + && (outer_thr_eff / inner_thr_eff >= 1.f + || (jcp.os * jcp.ic * jcp.oc) / max_threads < gemm_thrld); + } + jcp.nthr = jcp.outer_threading ? max_threads : 1; + scratchpad.book(key_conv_gemm_col, + sizeof(int8_t) * jcp.nthr * jcp.im2col_sz); + scratchpad.book(key_conv_int_dat_in_acc_dt, + sizeof(int32_t) * jcp.nthr * jcp.oh_block * jcp.ow_block * jcp.oc); + scratchpad.book(key_conv_gemm_imtr, + sizeof(int8_t) * jcp.nthr * jcp.is * jcp.ic); + } else if (is_bwd_d) { + bool is_depthwise = jcp.ic == 1 && jcp.oc == 1 && jcp.ngroups != 1; + const size_t outer_work = jcp.ngroups * jcp.mb; + const float outer_thr_eff + = (float)outer_work / rnd_up(outer_work, max_threads); + const size_t inner_work + = div_up(jcp.is, simd_w) * div_up(jcp.ic, simd_w); + const float inner_thr_eff + = (float)inner_work / rnd_up(inner_work, max_threads); + jcp.outer_threading = (is_depthwise + || (jcp.is / max_threads < 64 && jcp.mb != 1)) + && (outer_thr_eff / inner_thr_eff >= 1.f + || (jcp.is * jcp.ic * jcp.oc) / max_threads < gemm_thrld); + + jcp.nthr = jcp.outer_threading ? max_threads : 1; + scratchpad.book(key_conv_gemm_col, + sizeof(int32_t) * jcp.nthr * jcp.im2col_sz); + scratchpad.book(key_conv_int_dat_in_acc_dt, + sizeof(int32_t) * jcp.nthr * jcp.is * jcp.ic); + } else if (is_bwd_w) { + assert(!"unimplemented prop_kind"); + return status::unimplemented; + } + } else { + if (is_fwd) { + if (!jcp.outer_threading) { + const size_t outer_work_amount = jcp.ngroups * jcp.mb * jcp.od; + const float outer_thr_eff = (float)outer_work_amount + / rnd_up(outer_work_amount, max_threads); + const size_t inner_work_amount + = div_up(jcp.os, simd_w) * div_up(jcp.oc, simd_w); + const float inner_thr_eff = (float)inner_work_amount + / rnd_up(inner_work_amount, max_threads); + jcp.outer_threading = jcp.os / max_threads < 512 + && IMPLICATION(jcp.od == 1, jcp.mb != 1 || jcp.ngroups > 2) + && (outer_thr_eff / inner_thr_eff >= 1.f + || (jcp.os * jcp.ic * jcp.oc) / max_threads < gemm_thrld); + } + } else if (is_bwd_d) { + const size_t outer_work_amount = jcp.ngroups * jcp.mb; + const float outer_thr_eff = (float)outer_work_amount + / rnd_up(outer_work_amount, max_threads); + const size_t inner_work + = div_up(jcp.is, simd_w) * div_up(jcp.ic, simd_w); + const float inner_thr_eff = (float)inner_work + / rnd_up(inner_work, max_threads); + jcp.outer_threading = (jcp.os / max_threads < 512 || jcp.ks < 64) + && (jcp.mb != 1 || jcp.ngroups > 2) + && (outer_thr_eff / inner_thr_eff >= 1.f + || (jcp.is * jcp.ic * jcp.oc) / max_threads < gemm_thrld); + } else if (is_bwd_w) + jcp.outer_threading = jcp.os / max_threads < 256 + && (jcp.mb != 1 || jcp.ngroups > 2); + + jcp.nthr = jcp.outer_threading ? max_threads : 1; + scratchpad.book(key_conv_gemm_col, + sizeof(float) * jcp.nthr * jcp.im2col_sz); + + if (is_bwd_w) { + jcp.need_wei_reduction = mkldnn_thr_syncable() + ? jcp.mb != 1 && jcp.nthr != 1 : false; + scratchpad.book(key_conv_wei_reduction, + sizeof(float) * jcp.nthr * jcp.ngroups * weights_d.size()); + } + } + + return status::success; +} + +void bwd_weights_balance(int ithr, int nthr, int ngroups, int mb, int &ithr_g, + int &nthr_g, int &ithr_mb, int &nthr_mb) { + nthr_g = nstl::min(ngroups, nthr); + nthr_mb = nstl::min(mb, nthr / nthr_g); + if (ithr / nthr_mb >= ngroups) { + ithr_g = ithr_mb = -1; + } else { + ithr_g = ithr / nthr_mb; + ithr_mb = ithr % nthr_mb; + } +} + +void bwd_weights_reduction_par(int ithr, int nthr, + const jit_gemm_conv_conf_t &jcp, const float *weights_reduce_ws, + float *weights) { + const size_t weights_g_size = jcp.ic * jcp.oc * jcp.ks; + + size_t weights_start{0}, weights_end{0}; + balance211(weights_g_size, nthr, ithr, weights_start, weights_end); + + for (int i = 0; i < nthr; ++i) { + const float *ws_i = weights_reduce_ws + i * weights_g_size; + for (size_t s = weights_start; s < weights_end; ++s) + weights[s] = (i == 0 ? 0 : weights[s]) + ws_i[s]; + } +} + +}; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.hpp new file mode 100644 index 000000000000..e006789344ea --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_convolution_utils.hpp @@ -0,0 +1,66 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_GEMM_CONVOLUTION_UTILS_HPP +#define CPU_JIT_GEMM_CONVOLUTION_UTILS_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_engine.hpp" +#include "jit_primitive_conf.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace jit_gemm_convolution_utils { + +void im2col_3d(const jit_gemm_conv_conf_t &jcp, const float *im, float *col, + int od); +void im2col(const jit_gemm_conv_conf_t &jcp, const float *__restrict im, + float *__restrict col, int hs, int hb, int ws, int wb); +template +void im2col_u8(const jit_gemm_conv_conf_t &jcp, const T *__restrict im, + T* __restrict imtr, uint8_t *__restrict col, + int hs, int hb, int ws, int wb); + +void col2im_s32(const jit_gemm_conv_conf_t &jcp, const int32_t *__restrict col, + int32_t *__restrict im); +void col2im_3d(const jit_gemm_conv_conf_t &jcp, const float *col, float *im, + int od); +void col2im(const jit_gemm_conv_conf_t &jcp, const float *col, float *im); + +status_t init_conf(jit_gemm_conv_conf_t &jcp, + memory_tracking::registrar_t &scratchpad, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, int max_threads); + +void bwd_weights_balance(int ithr, int nthr, int ngroups, int mb, + int &ithr_g, int &nthr_g, int &ithr_mb, int &nthr_mb); +void bwd_weights_reduction_par(int ithr, int nthr, + const jit_gemm_conv_conf_t &jcp, const float *weights_reduce_ws, + float *weights); + +} + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.cpp new file mode 100644 index 000000000000..2872122f0d9a --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.cpp @@ -0,0 +1,156 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" + +#include "gemm_inner_product.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::data_type; +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::primitive_kind; + +template +void gemm_inner_product_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int IC = pd()->IC_total_padded(); + + bool wei_tr = !memory_desc_matches_one_of_tag( + *pd()->weights_md(), hwio, dhwio, io); + + const auto &post_ops = pd()->attr()->post_ops_; + const bool do_relu = post_ops.len_ == 1; + + float alpha = 1.0, beta = 0.0; + extended_sgemm(wei_tr ? "T" : "N", "N", &OC, &MB, &IC, &alpha, weights, + wei_tr ? &IC : &OC, src, &IC, &beta, dst, &OC, bias); + + if (do_relu) { + float nslope = post_ops.entry_[0].eltwise.alpha; + parallel_nd(MB, OC, [&](int mb, int oc) { + size_t dst_off = mb * OC + oc; + if (dst[dst_off] < 0) + dst[dst_off] *= nslope; + }); + } +} + +template +void gemm_inner_product_bwd_data_t::execute_backward_data( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int IC = pd()->IC_total_padded(); + + bool wei_tr = memory_desc_matches_one_of_tag( + *pd()->weights_md(), hwio, dhwio, io); + + float alpha = 1.0, beta = 0.0; + extended_sgemm(wei_tr ? "T" : "N", "N", &IC, &MB, &OC, &alpha, weights, + wei_tr ? &OC : &IC, diff_dst, &OC, &beta, diff_src, &IC); +} + +template +void gemm_inner_product_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_bias_d(pd()->diff_weights_md(1)); + + diff_dst += diff_dst_d.offset0(); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int IC = pd()->IC_total_padded(); + + bool wei_tr = memory_desc_matches_one_of_tag( + *pd()->diff_weights_md(), hwio, dhwio, io); + + float alpha = 1.0, beta = 0.0; + if (wei_tr) + extended_sgemm("N", "T", &OC, &IC, &MB, &alpha, diff_dst, &OC, src, &IC, + &beta, diff_weights, &OC); + else + extended_sgemm("N", "T", &IC, &OC, &MB, &alpha, src, &IC, diff_dst, &OC, + &beta, diff_weights, &IC); + + if (diff_bias) { + diff_bias += diff_bias_d.offset0(); + constexpr int blksize = 8; + const int OC_blocks = OC / blksize; + const int rem_OC = OC % blksize; + parallel(0, [&](const int ithr, const int nthr) { + int oc_st{0}, oc_e{0}; + balance211(OC_blocks, nthr, ithr, oc_st, oc_e); + oc_st = oc_st * blksize; + oc_e = oc_e * blksize; + + PRAGMA_OMP_SIMD() + for (int oc = oc_st; oc < oc_e; ++oc) { + diff_bias[oc] = diff_dst[oc]; + } + + for (int mb = 1; mb < MB; ++mb) { + PRAGMA_OMP_SIMD() + for (int oc = oc_st; oc < oc_e; ++oc) { + diff_bias[oc] += diff_dst[mb * OC + oc]; + } + } + + if (rem_OC != 0 && ithr == nthr-1) { + for (int oc = OC_blocks * blksize; oc < OC; oc++) + diff_bias[oc] = diff_dst[oc]; + for (int mb = 1; mb < MB; ++mb) { + for (int oc = OC_blocks * blksize; oc < OC; oc++) { + diff_bias[oc] += diff_dst[mb * OC + oc]; + } + } + } + }); + } +} + +template struct gemm_inner_product_fwd_t; +template struct gemm_inner_product_bwd_data_t; +template struct gemm_inner_product_bwd_weights_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.hpp new file mode 100644 index 000000000000..acf0a49b9a8c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_inner_product.hpp @@ -0,0 +1,157 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_GEMM_INNER_PRODUCT_HPP +#define CPU_GEMM_INNER_PRODUCT_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "gemm/gemm.hpp" + +#include "cpu_inner_product_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct gemm_inner_product_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_fwd_pd_t { + using cpu_inner_product_fwd_pd_t::cpu_inner_product_fwd_pd_t; + + DECLARE_COMMON_PD_T(GEMM_IMPL_STR, gemm_inner_product_fwd_t); + + status_t init() { + using namespace utils; + + bool ok = true + && set_default_params() == status::success + && is_fwd() + && !has_zero_dim_memory() + && everyone_is(data_type, + src_md()->data_type, + weights_md()->data_type, + dst_md()->data_type, + with_bias() ? weights_md(1)->data_type : data_type) + && attr()->output_scales_.has_default_values() + && attr()->post_ops_.len_ <= 1 + && IMPLICATION(attr()->post_ops_.len_ == 1, + attr()->post_ops_.entry_[0].is_relu(true, false)) + && dense_gemm_consitency_check(src_md(), weights_md(), + dst_md()); + return ok ? status::success : status::unimplemented; + } + }; + + gemm_inner_product_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct gemm_inner_product_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_bwd_data_pd_t { + using cpu_inner_product_bwd_data_pd_t::cpu_inner_product_bwd_data_pd_t; + + DECLARE_COMMON_PD_T(GEMM_IMPL_STR, gemm_inner_product_bwd_data_t); + + status_t init() { + bool ok = true + && set_default_params() == status::success + && desc()->prop_kind == prop_kind::backward_data + && !has_zero_dim_memory() + && utils::everyone_is(data_type, + diff_src_md()->data_type, + weights_md()->data_type, + diff_dst_md()->data_type) + && attr()->has_default_values() + && dense_gemm_consitency_check(diff_src_md(), weights_md(), + diff_dst_md()); + return ok ? status::success : status::unimplemented; + } + }; + + gemm_inner_product_bwd_data_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct gemm_inner_product_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_bwd_weights_pd_t { + using cpu_inner_product_bwd_weights_pd_t::cpu_inner_product_bwd_weights_pd_t; + + DECLARE_COMMON_PD_T(GEMM_IMPL_STR, gemm_inner_product_bwd_weights_t); + + status_t init() { + bool ok = true + && set_default_params() == status::success + && desc()->prop_kind == prop_kind::backward_weights + && !has_zero_dim_memory() + && utils::everyone_is(data_type, + src_md()->data_type, + diff_weights_md()->data_type, + diff_dst_md()->data_type, + with_bias() ? diff_weights_md(1)->data_type : data_type) + && attr()->has_default_values() + && dense_gemm_consitency_check(src_md(), diff_weights_md(), + diff_dst_md()); + + return ok ? status::success : status::unimplemented; + } + }; + + gemm_inner_product_bwd_weights_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.cpp new file mode 100644 index 000000000000..fed7e4d69335 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.cpp @@ -0,0 +1,740 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "utils.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "math_utils.hpp" + +#include "simple_q10n.hpp" + +#include "gemm/gemm.hpp" +#include "gemm_x8s8s32x_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::math; +using namespace mkldnn::impl::memory_tracking::names; + +template +void _gemm_x8s8s32x_convolution_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const { + auto src_base = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto wei_base = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bia_base = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst_base = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + auto scratchpad = this->scratchpad(ctx); + + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + assert(IMPLICATION( + jcp.id != 1, jcp.oh_block == jcp.oh && jcp.ow_block == jcp.ow)); + assert(IMPLICATION(jcp.ow_block != jcp.ow, jcp.oh_block == 1)); + + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + execute_forward_thr(ithr, nthr, src_base, wei_base, bia_base, dst_base, + scratchpad); + }); +} + +template +_gemm_x8s8s32x_convolution_fwd_t::pp_ker_t::pp_ker_t( + const pd_t *pd) + : ker_(nullptr) + , jcp_(pd->jcp_) + , OC_(pd->jcp_.oc) + , OS_(pd->jcp_.os) + , bias_data_type_(data_type::undef) + , bias_data_type_size_(0) + , scale_idx_mult_(0) + , do_bias_(false) + , do_relu_(false) + , do_sum_(false) +{ + using namespace types; + + const auto dst_md = memory_desc_wrapper(pd->dst_md()); + dst_os_stride_ = dst_md.blk_off(0, 0, 0, 1); + + scale_idx_mult_ = (pd->attr()->output_scales_.mask_ == (1 << 1)); + + auto &post_ops = pd->attr()->post_ops_; + + int entry_idx = -1; + for (int idx = 0; idx < post_ops.len_; ++idx) { + const auto &e = post_ops.entry_[idx]; + if (e.is_relu(true, false)) { + entry_idx = idx; + break; + } + } + do_relu_ = entry_idx >= 0; + + do_signed_scaling_ = jcp_.signed_input; + + do_sum_ = post_ops.contain(primitive_kind::sum, 0); + do_bias_ = pd->with_bias(); + bias_data_type_ = pd->desc()->bias_desc.data_type; + if (do_bias_) { + assert(bias_data_type_ != data_type::undef); + bias_data_type_size_ = data_type_size(bias_data_type_); + } + const size_t vlen_start + = cpu_isa_traits::vlen / sizeof(float); + + for (size_t i = vlen_start; i > 0; i--) { + if (OC_ % i == 0) { + vlen_ = i; + break; + } + } + + if (!mayiuse(avx512_core)) + // use fallback code for older CPUs + return; + else + generate(); +} + +template +void _gemm_x8s8s32x_convolution_fwd_t::pp_ker_t::generate() +{ + using namespace Xbyak; + using namespace utils; + + // TODO: clean-up + Reg64 reg_param = abi_param1; + Reg64 reg_dst = rdx; + Reg64 reg_acc = rax; + Reg64 reg_bias = rbx; + Reg64 reg_scales = rsi; + + Reg64 reg_len = r8; + Reg64 reg_tmp = rcx; // intentional for shifting purposes + Reg64 reg_oc_offset = r9; + Reg64 reg_rem_mask_short = r10; + Reg64 reg_rem_mask_vlen = r11; + Opmask kreg_rem_mask_short = k1; + Opmask kreg_rem_mask_vlen = k3; + Opmask kreg_relu_cmp = k2; + + const size_t vlen = vlen_; + + Zmm vreg_zero = Zmm(0); + Zmm vreg_scale = Zmm(1); + Zmm vreg_nslope = Zmm(2); + Zmm vreg_sum_scale = Zmm(3); + Zmm vreg_signed_scale = Zmm(4); + + size_t def_unroll = 4; + size_t max_unroll = 12; + size_t zmm_step = 2; + if (do_sum_) { + max_unroll = 8; + zmm_step = 3; + } + + auto vreg_dst = [&](int idx) { + return Zmm(5 + idx * zmm_step + 0); + }; + auto vreg_bias = [&](int idx) { + return Zmm(5 + idx * zmm_step + 1); + }; + auto vreg_prev_dst = [&](int idx) { + return Zmm(5 + idx * zmm_step + 2); + }; + + preamble(); + +#define PARAM_OFF(x) offsetof(ker_args, x) + mov(reg_dst, ptr[reg_param + PARAM_OFF(dst)]); + mov(reg_acc, ptr[reg_param + PARAM_OFF(acc)]); + mov(reg_bias, ptr[reg_param + PARAM_OFF(bias)]); + mov(reg_scales, ptr[reg_param + PARAM_OFF(scales)]); + mov(reg_len, ptr[reg_param + PARAM_OFF(len)]); + mov(reg_oc_offset, ptr[reg_param + PARAM_OFF(oc_offset)]); + vbroadcastss(vreg_nslope, ptr[reg_param + PARAM_OFF(nslope)]); + vbroadcastss(vreg_sum_scale, ptr[reg_param + PARAM_OFF(sum_scale)]); + vbroadcastss(vreg_signed_scale, ptr[reg_param + PARAM_OFF(signed_scale)]); + if (scale_idx_mult_ == 0) + vbroadcastss(vreg_scale, dword[reg_scales]); + +#undef PARAM_OFF + + mov(reg_rem_mask_vlen, 1); + shl(reg_rem_mask_vlen, vlen); + sub(reg_rem_mask_vlen, 1); + kmovq(kreg_rem_mask_vlen, reg_rem_mask_vlen); + + if (do_relu_ || dst_type == data_type::u8) + vxorps(vreg_zero, vreg_zero, vreg_zero); + + // Load accumulated value, convert to float, apply sum (if any), + // bias (if any), scaling, and relu (if any); + // then convert to destination type and store + auto compute = [&](size_t offset, int idx, bool apply_mask) { + auto acc_addr = ptr[reg_acc + offset * sizeof(acc_data_t)]; + + if (scale_idx_mult_ > 0) { + assert(scale_idx_mult_ == 1); + auto scale_addr = ptr[reg_scales + offset * sizeof(float)]; + auto vreg_scale_ = vreg_scale; + if (apply_mask) + vreg_scale_ = vreg_scale_ | kreg_rem_mask_short; + else + vreg_scale_ = vreg_scale_ | kreg_rem_mask_vlen; + vmovups(vreg_scale_, scale_addr); + } + + auto vreg_dst_ = vreg_dst(idx); + if (apply_mask) + vreg_dst_ = vreg_dst_ | kreg_rem_mask_short; + else + vreg_dst_ = vreg_dst_ | kreg_rem_mask_vlen; + vcvtdq2ps(vreg_dst_, acc_addr); + + if (do_signed_scaling_) + vmulps(vreg_dst(idx), vreg_dst(idx), vreg_signed_scale); + + if (do_bias_) { + auto bias_addr = ptr[reg_bias + offset * bias_data_type_size_]; + auto vreg_bias_ = vreg_bias(idx); + if (apply_mask) + vreg_bias_ = vreg_bias_ | kreg_rem_mask_short; + else + vreg_bias_ = vreg_bias_ | kreg_rem_mask_vlen; + + switch (bias_data_type_) { + case data_type::s8: + vpmovsxbd(vreg_bias_, bias_addr); + break; + case data_type::u8: + vpmovzxbd(vreg_bias_, bias_addr); + break; + case data_type::s32: + case data_type::f32: + vmovups(vreg_bias_, bias_addr); + break; + default: assert(!"unimplemented"); + } + if (bias_data_type_ != data_type::f32) + vcvtdq2ps(vreg_bias(idx), vreg_bias(idx)); + vaddps(vreg_dst(idx), vreg_dst(idx), vreg_bias(idx)); + } + + vmulps(vreg_dst(idx), vreg_dst(idx), vreg_scale); + + auto dst_addr = ptr[reg_dst + offset * sizeof(dst_data_t)]; + + if (do_sum_) + { + auto vreg_prev_dst_ = vreg_prev_dst(idx); + if (apply_mask) + vreg_prev_dst_ = vreg_prev_dst_ | kreg_rem_mask_short; + else + vreg_prev_dst_ = vreg_prev_dst_ | kreg_rem_mask_vlen; + + switch (dst_type) { + case data_type::f32: + case data_type::s32: vmovups(vreg_prev_dst_, dst_addr); break; + case data_type::s8: vpmovsxbd(vreg_prev_dst_, dst_addr); break; + case data_type::u8: vpmovzxbd(vreg_prev_dst_, dst_addr); break; + default: assert(!"unsupported data type"); + } + if (dst_type != data_type::f32) + vcvtdq2ps(vreg_prev_dst(idx), vreg_prev_dst(idx)); + + vfmadd231ps(vreg_dst(idx), vreg_prev_dst(idx), vreg_sum_scale); + } + + if (do_relu_) { + vcmpps(kreg_relu_cmp, vreg_dst(idx), vreg_zero, _cmp_lt_os); + vmulps(vreg_dst(idx) | kreg_relu_cmp, vreg_dst(idx), vreg_nslope); + } + + if (dst_type != data_type::f32) { + vcvtps2dq(vreg_dst(idx), vreg_dst(idx)); + } + + if (dst_type == data_type::u8) + vpmaxsd(vreg_dst(idx), vreg_dst(idx), vreg_zero); + + switch (dst_type) { + case data_type::s8: + vpmovsdb(dst_addr, vreg_dst_); + break; + case data_type::u8: + vpmovusdb(dst_addr, vreg_dst_); + break; + case data_type::f32: + case data_type::s32: + vmovups(dst_addr, vreg_dst_); + break; + default: assert(!"unimplemented"); + } + }; + + // Advance all pointers by an immediate + auto advance_ptrs_imm = [&](size_t offset) { + add(reg_dst, offset * sizeof(dst_data_t)); + add(reg_acc, offset * sizeof(acc_data_t)); + if (scale_idx_mult_) { + assert(scale_idx_mult_ == 1); + add(reg_scales, offset * sizeof(float)); + } + if (do_bias_) + add(reg_bias, offset * bias_data_type_size_); + }; + + // Advance all pointers by a value stored in a register + auto advance_ptrs_reg = [&](Reg64 offset) { + lea(reg_dst, ptr[reg_dst + offset * sizeof(dst_data_t)]); + lea(reg_acc, ptr[reg_acc + offset * sizeof(acc_data_t)]); + if (scale_idx_mult_) { + assert(scale_idx_mult_ == 1); + lea(reg_scales, ptr[reg_scales + offset * sizeof(float)]); + } + if (do_bias_) + lea(reg_bias, ptr[reg_bias + offset * bias_data_type_size_]); + }; + + // Rewind pointers that point to data that is indexed by output channel + // (bias or per-oc scaling factors) + auto rewind_ptrs = [&]() { + if (do_bias_) + sub(reg_bias, OC_ * bias_data_type_size_); + if (scale_idx_mult_) { + assert(scale_idx_mult_ == 1); + sub(reg_scales, OC_ * sizeof(float)); + } + add(reg_dst, (dst_os_stride_ - OC_) * sizeof(dst_data_t)); + }; + + // <--------- OC ---------------> + // + // ^ ................+..............+-------------+....................... + // | . : not accessed |Prologue loop| . + // | . +--------------+-------------+ . + // . | | . + // O . | Main loop (unrolled) | . + // S . | | . + // . +--------------+-------------+ . + // | . | Epilogue loop|not accessed : . + // v ................+--------------+.............+....................... + + Label prologue_end; + cmp(reg_oc_offset, 0); + je(prologue_end, T_NEAR); + + // Prologue loop + { + mov(reg_tmp, OC_); + sub(reg_tmp, reg_oc_offset); + cmp(reg_tmp, reg_len); + cmovg(reg_tmp, reg_len); + sub(reg_len, reg_tmp); + + Label prologue_loop, prologue_loop_tail, prologue_loop_end; + cmp(reg_tmp, vlen); + jle(prologue_loop_tail, T_NEAR); + L(prologue_loop); { + compute(0, 0, false); + advance_ptrs_imm(vlen); + sub(reg_tmp, vlen); + cmp(reg_tmp, vlen); + jge(prologue_loop, T_NEAR); + } + + L(prologue_loop_tail); + mov(reg_rem_mask_short, 1); + // cl == reg_tmp because reg_tmp <= vlen here + shl(reg_rem_mask_short, cl); + sub(reg_rem_mask_short, 1); + jz(prologue_loop_end, T_NEAR); + + kmovq(kreg_rem_mask_short, reg_rem_mask_short); + compute(0, 0, true); + advance_ptrs_reg(reg_tmp); + + L(prologue_loop_end); + rewind_ptrs(); + } + L(prologue_end); + + // Main loop + Label main_loop_end; + { + cmp(reg_len, OC_); + jle(main_loop_end, T_NEAR); + + Label main_loop; + L(main_loop); { + size_t OC_loop, OC_tail; + if (OC_ < max_unroll * vlen) { + // Fully unroll small loops + OC_loop = 0; + OC_tail = OC_; + } + else { + OC_loop = vlen * def_unroll; + OC_tail = OC_ % OC_loop; + } + + assert(!!OC_loop || !!OC_tail); + + if (OC_tail % vlen) { + int vlen_tail = OC_tail % vlen; + unsigned tail_mask = (1 << vlen_tail) - 1; + mov(reg_tmp, tail_mask); + kmovq(kreg_rem_mask_short, reg_tmp); + } + + if (OC_loop) { + mov(reg_tmp, rnd_dn(OC_, OC_loop)); + Label oc_loop; + L(oc_loop); { + for (size_t offset = 0; offset < OC_loop; offset += vlen) + compute(offset, offset / vlen, false); + advance_ptrs_imm(OC_loop); + sub(reg_tmp, OC_loop); + jnz(oc_loop); + } + } + + if (OC_tail) { + for (size_t offset = 0; offset < OC_tail; offset += vlen) { + bool use_mask = (offset + vlen) > OC_tail; + compute(offset, offset / vlen, use_mask); + } + advance_ptrs_imm(OC_tail); + } + + rewind_ptrs(); + sub(reg_len, OC_); + cmp(reg_len, OC_); + jge(main_loop, T_NEAR); + } + } + L(main_loop_end); + + // Epilogue loop + Label epilogue_end; + { + cmp(reg_len, 0); + je(epilogue_end, T_NEAR); + + Label epilogue_loop, epilogue_loop_tail; + cmp(reg_len, vlen); + jle(epilogue_loop_tail, T_NEAR); + L(epilogue_loop); { + compute(0, 0, false); + sub(reg_len, vlen); + advance_ptrs_imm(vlen); + cmp(reg_len, vlen); + jge(epilogue_loop, T_NEAR); + } + + L(epilogue_loop_tail); + mov(reg_tmp, reg_len); // reg_tmp is rcx, and we need cl for the shift + mov(reg_rem_mask_short, 1); + shl(reg_rem_mask_short, cl); // reg_tmp == rcx and reg_tail < vlen + sub(reg_rem_mask_short, 1); + jz(epilogue_end, T_NEAR); + kmovq(kreg_rem_mask_short, reg_rem_mask_short); + compute(0, 0, true); + } + + L(epilogue_end); + + postamble(); + + ker_ = getCode(); +} + +template +void _gemm_x8s8s32x_convolution_fwd_t::pp_ker_t::operator () + (dst_data_t *dst, const acc_data_t *acc, const char *bias, + const float *scales, float nslope, float sum_scale, float signed_scale, + int g, size_t start, size_t end) +{ + using math::get_bias; + + if (end <= start) + return; + + if (ker_) { + // JIT + ker_args args; + size_t oc_offset = start % OC_; + size_t os_offset = start / OC_; + args.acc = acc + start; + args.dst = dst + os_offset * dst_os_stride_ + oc_offset; + args.bias = bias + (g * jcp_.oc + oc_offset) * bias_data_type_size_; + args.scales = scales + scale_idx_mult_ * (g * jcp_.oc + oc_offset); + args.nslope = nslope; + args.sum_scale = sum_scale; + args.signed_scale = signed_scale; + args.len = end - start; + args.oc_offset = oc_offset; + ker_(&args); + } + else { + // Fallback + const size_t first_oc = start % OC_; + const size_t last_oc = (end - 1) % OC_; + const size_t first_os = start / OC_; + const size_t last_os = (end - 1) / OC_; + for (size_t os = first_os; os <= last_os; os++) { + const size_t start_oc = (os == first_os) ? first_oc : 0; + const size_t end_oc = (os == last_os) ? last_oc : OC_ - 1; + for (size_t oc = start_oc; oc <= end_oc; oc++) { + const size_t acc_off = os * jcp_.oc + oc; + const size_t dst_off = os * dst_os_stride_ + oc; + + float d = (float)(acc[acc_off]); + if (jcp_.signed_input) + d *= signed_scale; + + if (do_bias_) + d += get_bias(bias, g * jcp_.oc + oc, + bias_data_type_); + + d *= scales[(g * jcp_.oc + oc) * scale_idx_mult_]; + if (do_sum_) + d += sum_scale * dst[dst_off]; + if (do_relu_ && d < 0) + d *= nslope; + dst[dst_off] = qz_a1b0()(d); + } + } + } +}; + +template +void _gemm_x8s8s32x_convolution_fwd_t:: +execute_forward_thr(const int ithr, const int nthr, const src_data_t *src_base, + const wei_data_t *wei_base, const char *bia_base, dst_data_t *dst_base, + const memory_tracking::grantor_t &scratchpad) const { + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + const auto src_md = memory_desc_wrapper(pd()->src_md()); + const size_t src_mb_stride = src_md.blk_off(1); + const size_t src_g_stride = src_md.blk_off(0, 1) * jcp.ic; + + const auto wei_md = memory_desc_wrapper(pd()->weights_md(0)); + const size_t wei_g_stride = pd()->with_groups() ? wei_md.blk_off(1) : 0; + + const auto dst_md = memory_desc_wrapper(pd()->dst_md()); + const size_t dst_mb_stride = dst_md.blk_off(1); + const size_t dst_g_stride = dst_md.blk_off(0, 1) * jcp.oc; + + const float *scales = pd()->attr()->output_scales_.scales_; + + const auto &post_ops = pd()->attr()->post_ops_; + const bool do_sum = post_ops.contain(primitive_kind::sum, 0); + const float sum_scale = do_sum ? post_ops.entry_[0].sum.scale : 0; + + float nslope = 0; + for (int idx = 0; idx < post_ops.len_; ++idx) { + const auto &e = post_ops.entry_[idx]; + if (e.is_relu(true, false)) { + nslope = e.eltwise.alpha; + break; + } + } + + auto col = scratchpad.get(key_conv_gemm_col) + + (ptrdiff_t)ithr * jcp.im2col_sz; + src_data_t *__restrict imtr = scratchpad.get(key_conv_gemm_imtr) + + (ptrdiff_t)ithr * jcp.is * jcp.ic; + auto acc = scratchpad.get(key_conv_int_dat_in_acc_dt) + + (ptrdiff_t)ithr * jcp.oh_block * jcp.ow_block * jcp.oc; + + const ptrdiff_t offset = (ptrdiff_t)jcp.ngroups * jcp.ks * jcp.ic * jcp.oc; + const int32_t *_wei_comp = (const int32_t *)(wei_base + offset); + + int g{ 0 }, n{ 0 }, ohb{ 0 }, owb{ 0 }; + size_t start = 0, end = 0; + + const int nb_oh = div_up(jcp.oh, jcp.oh_block); + const int nb_ow = div_up(jcp.ow, jcp.ow_block); + const size_t work_amount = jcp.ngroups * jcp.mb * nb_oh * nb_ow; + balance211(work_amount, nthr, ithr, start, end); + nd_iterator_init(start, n, jcp.mb, g, jcp.ngroups, ohb, + nb_oh, owb, nb_ow); + + for (size_t iwork = start; iwork < end; ++iwork) { + int oh = ohb * jcp.oh_block; + int ow = owb * jcp.ow_block; + const src_data_t *__restrict src = src_base + n * src_mb_stride + + g * src_g_stride; + const wei_data_t *__restrict wei = wei_base + g * wei_g_stride; + dst_data_t *__restrict dst = + dst_base + n * dst_mb_stride + g * dst_g_stride; + const int32_t *wei_comp = _wei_comp + g * jcp.oc; + const int h_step = nstl::min(jcp.oh_block, jcp.oh - oh); + const int w_step = nstl::min(jcp.ow_block, jcp.ow - ow); + + if (jcp.im2col_sz) + jit_gemm_convolution_utils::im2col_u8( + jcp, src, imtr, col, oh, h_step, ow, w_step); + + const int M = jcp.oc; + const int K = jcp.ks * jcp.ic; + const int N = h_step * w_step; + const int LDA = M * jcp.ngroups; + const int LDB = jcp.im2col_sz ? N : K; + const char *BT = jcp.im2col_sz ? "T" : "N"; + const int8_t off_a = 0, off_b = 0; + const int32_t off_c = 0; + const float onef = 1.0, zerof = 0.0; + gemm_s8x8s32("N", BT, jcp.signed_input ? "C" : "F", + &M, &N, &K, &onef, wei, &LDA, &off_a, + jcp.im2col_sz ? col : (uint8_t *)src, &LDB, &off_b, + &zerof, acc, &M, jcp.signed_input ? wei_comp : &off_c); + + auto wei_adj_scale = + (wei_md.extra().flags | memory_extra_flags::scale_adjust) + ? wei_md.extra().scale_adjust : 1.f; + + parallel(0, [&](int ithr, int nthr) { + size_t start, end; + balance211((size_t)N * jcp.oc, nthr, ithr, start, end); + (*pp_ker_)(dst + (oh * jcp.ow + ow) * pp_ker_->dst_os_stride_, + acc, bia_base, scales, nslope, sum_scale, + 1.f / wei_adj_scale, g, start, end); + }); + + nd_iterator_step(n, jcp.mb, g, jcp.ngroups, ohb, nb_oh, + owb, nb_ow); + } +} + +template +void _gemm_u8s8s32x_convolution_bwd_data_t:: +execute_backward_data(const exec_ctx_t &ctx) const { + auto diff_dst_base = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto wei_base = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bia_base = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto diff_src_base = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + auto scratchpad = this->scratchpad(ctx); + + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + execute_backward_data_thr(ithr, nthr, diff_dst_base, wei_base, + bia_base, diff_src_base, scratchpad); + }); +} + +template +void _gemm_u8s8s32x_convolution_bwd_data_t:: +execute_backward_data_thr(const int ithr, const int nthr, + const diff_dst_data_t *diff_dst_base, const wei_data_t *wei_base, + const char *bia_base, diff_src_data_t *diff_src_base, + const memory_tracking::grantor_t &scratchpad) const +{ + const jit_gemm_conv_conf_t &jcp = this->pd()->jcp_; + + const auto diff_dst_md = memory_desc_wrapper(pd()->diff_dst_md()); + const size_t diff_dst_mb_stride = diff_dst_md.blk_off(1); + const size_t diff_dst_g_stride = diff_dst_md.blk_off(0, 1) * jcp.oc; + + const auto wei_md = memory_desc_wrapper(pd()->weights_md(0)); + const size_t wei_g_stride = pd()->with_groups() ? wei_md.blk_off(1) : 0; + + const auto diff_src_md = memory_desc_wrapper(pd()->diff_src_md()); + const size_t diff_src_mb_stride = diff_src_md.blk_off(1); + const size_t diff_src_g_stride = diff_src_md.blk_off(0, 1) * jcp.ic; + const size_t diff_src_os_stride = diff_src_md.blk_off(0, 0, 0, 1); + + /* scale_idx_mult = 1 for per_oc scales and 0, otherwise */ + const int scale_idx_mult = pd()->attr()->output_scales_.mask_ == (1 << 1); + const float *scales = pd()->attr()->output_scales_.scales_; + const size_t work_amount = jcp.ngroups * jcp.mb; + + auto col = scratchpad.get(key_conv_gemm_col) + + (ptrdiff_t)ithr * jcp.im2col_sz; + auto acc = scratchpad.get(key_conv_int_dat_in_acc_dt) + + (ptrdiff_t)ithr * jcp.is * jcp.ic; + + int n{0}, g{0}; + size_t start = 0, end = 0; + + balance211(work_amount, nthr, ithr, start, end); + nd_iterator_init(start, n, jcp.mb, g, jcp.ngroups); + + for (size_t iwork = start; iwork < end; ++iwork) { + const diff_dst_data_t *diff_dst = diff_dst_base + + n * diff_dst_mb_stride + g * diff_dst_g_stride; + const wei_data_t *wei = wei_base + g * wei_g_stride; + diff_src_data_t *diff_src = diff_src_base + n * diff_src_mb_stride + + g * diff_src_g_stride; + + const int M = jcp.ks * jcp.ic; + const int N = jcp.os; + const int K = jcp.oc; + const int8_t off_a = 0, off_b = 0; + const int32_t off_c = 0; + const float onef = 1.0, zerof = 0.0; + const int LD = K * jcp.ngroups; + + gemm_s8x8s32("T", "N", "F", &M, &N, &K, &onef, + wei, &LD, &off_a, diff_dst, &LD, &off_b, + &zerof, jcp.im2col_sz ? col : acc, &M, &off_c); + + if (jcp.im2col_sz) + jit_gemm_convolution_utils::col2im_s32(jcp, col, acc); + + parallel_nd(jcp.is, jcp.ic, [&](int is, int ic) { + float d = (float)acc[is * jcp.ic + ic]; + if (jcp.with_bias) + d += get_bias(bia_base, g * jcp.ic + ic, + pd()->desc()->bias_desc.data_type); + d *= scales[(g * jcp.ic + ic) * scale_idx_mult]; + const size_t diff_src_off = is * diff_src_os_stride + ic; + diff_src[diff_src_off] = + qz_a1b0()(d); + }); + nd_iterator_step(n, jcp.mb, g, jcp.ngroups); + } +} + +using namespace data_type; + +template struct _gemm_x8s8s32x_convolution_fwd_t; +template struct _gemm_x8s8s32x_convolution_fwd_t; +template struct _gemm_x8s8s32x_convolution_fwd_t; +template struct _gemm_x8s8s32x_convolution_fwd_t; + +template struct _gemm_x8s8s32x_convolution_fwd_t; +template struct _gemm_x8s8s32x_convolution_fwd_t; +template struct _gemm_x8s8s32x_convolution_fwd_t; +template struct _gemm_x8s8s32x_convolution_fwd_t; + +template struct _gemm_u8s8s32x_convolution_bwd_data_t; +template struct _gemm_u8s8s32x_convolution_bwd_data_t; +template struct _gemm_u8s8s32x_convolution_bwd_data_t; +template struct _gemm_u8s8s32x_convolution_bwd_data_t; +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.hpp new file mode 100644 index 000000000000..9e77b890d55a --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_convolution.hpp @@ -0,0 +1,266 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef GEMM_X8S8S32X_CONVOLUTION_HPP +#define GEMM_X8S8S32X_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_primitive_conf.hpp" +#include "jit_generator.hpp" +#include "gemm_convolution_utils.hpp" + +#include "gemm/gemm.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct _gemm_x8s8s32x_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T(IGEMM_S8U8S32_IMPL_STR, + _gemm_x8s8s32x_convolution_fwd_t); + + status_t init() { + using namespace data_type; + + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, s8, data_type::undef, dst_type, + s32) + && IMPLICATION(with_bias(), utils::one_of( + desc()->bias_desc.data_type, f32, s32, s8, u8)) + && !has_zero_dim_memory() + && set_default_formats_common( + dat_tag(), format_tag::any, dat_tag()) + && post_ops_ok() + && memory_desc_matches_tag(*src_md(), dat_tag()) + && memory_desc_matches_tag(*dst_md(), dat_tag()) + && set_or_check_wei_format(); + if (!ok) return status::unimplemented; + + auto scratchpad = scratchpad_registry().registrar(); + return jit_gemm_convolution_utils::init_conf(jcp_, scratchpad, + *desc(), src_md(), weights_md(0), dst_md(), + mkldnn_get_max_threads()); + } + + jit_gemm_conv_conf_t jcp_; + + protected: + format_tag_t dat_tag() const { return format_tag::nhwc; } + + bool set_or_check_wei_format() { + using namespace format_tag; + + const bool is_src_s8 = src_md_.data_type == data_type::s8; + + memory_desc_t want_wei_md = weights_md_; + memory_desc_init_by_tag(want_wei_md, with_groups() ? hwigo : hwio); + + if (is_src_s8) { + want_wei_md.extra.flags = 0 + | memory_extra_flags::compensation_conv_s8s8 + | memory_extra_flags::scale_adjust; + want_wei_md.extra.compensation_mask = (1 << 0) + + (with_groups() ? (1 << 1) : 0); + want_wei_md.extra.scale_adjust = + mayiuse(avx512_core_vnni) ? 1.f : 0.5f; + } + + if (weights_md_.format_kind == format_kind::any) { + weights_md_ = want_wei_md; + return true; + } + + return weights_md_ == want_wei_md; + } + + bool post_ops_ok() const { + using namespace mkldnn::impl::primitive_kind; + auto const &po = attr()->post_ops_; + auto is_relu = [&](int idx) { + return po.entry_[idx].is_relu(true, false); }; + + switch (po.len_) { + case 0: return true; + case 1: return is_relu(0) || po.contain(sum, 0); + case 2: return po.contain(sum, 0) && is_relu(1); + default: return false; + } + return false; + } + }; + + _gemm_x8s8s32x_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd, true), pp_ker_(nullptr) + { pp_ker_ = new pp_ker_t(pd()); } + ~_gemm_x8s8s32x_convolution_fwd_t() { delete pp_ker_; } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + // XXX: this is throwaway code that will become unnecessary when we have a + // sufficiently advanced igemm jit generator that supports quantization, + // relu, and whatnot + class pp_ker_t : jit_generator { + public: + DECLARE_CPU_JIT_AUX_FUNCTIONS( + _gemm_x8s8s32x_convolution_fwd_t::pp_kernel); + pp_ker_t(const pd_t *pd); + + void operator()(dst_data_t *dst, const acc_data_t *acc, + const char *bias, const float *scales, + float nslope, float sum_scale, float signed_scale, + int g, size_t start, size_t end); + + size_t dst_os_stride_; + + private: + void generate(); + + struct ker_args { + dst_data_t *dst; + const acc_data_t *acc; + const char *bias; + const float *scales; + float nslope; + float sum_scale; + float signed_scale; + size_t len; + size_t oc_offset; + }; + void(*ker_)(const ker_args *args); + + const jit_gemm_conv_conf_t &jcp_; + size_t OC_; + size_t OS_; + data_type_t bias_data_type_; + size_t bias_data_type_size_; + size_t scale_idx_mult_; + bool do_bias_; + bool do_relu_; + bool do_sum_; + bool do_signed_scaling_; + size_t vlen_; + }; + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + void execute_forward(const exec_ctx_t &ctx) const; + void execute_forward_thr(const int ithr, const int nthr, + const src_data_t *src_base, const wei_data_t *wei_base, + const char *bia_base, dst_data_t *dst_base, + const memory_tracking::grantor_t &scratchpad) const; + + int nthr_; + pp_ker_t *pp_ker_; + +}; + +template +struct _gemm_u8s8s32x_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t{ + pd_t(engine_t *engine, + const convolution_desc_t *adesc, const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T(IGEMM_S8U8S32_IMPL_STR, + _gemm_u8s8s32x_convolution_bwd_data_t); + + status_t init() { + using namespace data_type; + + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(dst_type, s8, data_type::undef, u8, s32) + && IMPLICATION(with_bias(), utils::one_of( + desc()->bias_desc.data_type, f32, s32, s8, u8)) + && !has_zero_dim_memory() + && set_default_formats_common(dat_tag(), wei_tag(), dat_tag()) + && attr()->post_ops_.has_default_values() + && memory_desc_matches_tag(*diff_src_md(), dat_tag()) + && memory_desc_matches_tag(*diff_dst_md(), dat_tag()) + && memory_desc_matches_tag(*weights_md(), wei_tag()); + if (!ok) return status::unimplemented; + + auto scratchpad = scratchpad_registry().registrar(); + return jit_gemm_convolution_utils::init_conf(jcp_, scratchpad, + *desc(), diff_src_md(), weights_md(), diff_dst_md(), + mkldnn_get_max_threads()); + } + + virtual bool support_bias() const override { return true; } + + jit_gemm_conv_conf_t jcp_; + + protected: + format_tag_t dat_tag() const { return format_tag::nhwc; } + + format_tag_t wei_tag() const { + return with_groups() ? format_tag::hwigo : format_tag::hwio; + } + }; + + _gemm_u8s8s32x_convolution_bwd_data_t(const pd_t *apd) + : cpu_primitive_t(apd, true) {} + + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type diff_src_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + void execute_backward_data_thr(const int ithr, const int nthr, + const diff_dst_data_t *diff_dst_base, const wei_data_t *wei_base, + const char *bia_base, diff_src_data_t *diff_src_base, + const memory_tracking::grantor_t &scratchpad) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.cpp new file mode 100644 index 000000000000..1e435a233afe --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.cpp @@ -0,0 +1,453 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "simple_q10n.hpp" + +#include "gemm/gemm.hpp" +#include "gemm_x8s8s32x_inner_product.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace math; +using namespace format_tag; +using namespace memory_tracking::names; + +template +gemm_x8s8s32x_inner_product_fwd_t::pp_kernel_t::pp_kernel_t( + const pd_t *pd, bool dst_is_acc) + : ker_(nullptr), OC_(pd->OC()) + , bias_data_type_(data_type::undef), bias_data_type_size_(0) + , scale_idx_mult_(0), do_bias_(false), do_relu_(false) +{ + using namespace types; + + scale_idx_mult_ = (pd->attr()->output_scales_.mask_ == (1 << 1)); + + auto &post_ops = pd->attr()->post_ops_; + do_relu_ = post_ops.len_ == 1; + do_bias_ = pd->with_bias(); + bias_data_type_ = pd->desc()->bias_desc.data_type; + if (do_bias_) { + assert(bias_data_type_ != data_type::undef); + bias_data_type_size_ = data_type_size(bias_data_type_); + } + + if (!mayiuse(avx512_core)) + // use fallback code for older CPUs since they do not have optimized + // x8s8s32 GEMM anyways. The configuration variables above are used by + // the fallback code. + return; + else + generate(); +} + +template +void gemm_x8s8s32x_inner_product_fwd_t::pp_kernel_t::generate() +{ + using namespace Xbyak; + using namespace utils; + + // TODO: clean-up + Reg64 reg_param = abi_param1; + Reg64 reg_dst = rdx; + Reg64 reg_acc = rax; + Reg64 reg_bias = rbx; + Reg64 reg_scales = rsi; + + Reg64 reg_len = r8; + Reg64 reg_tmp = rcx; // intentional for shifting purposes + Reg64 reg_oc_offset = r9; + Reg64 reg_rem_mask = r10; + Opmask kreg_rem_mask = k1; + Opmask kreg_relu_cmp = k2; + + const size_t vlen = cpu_isa_traits::vlen / sizeof(float); + + Zmm vreg_zero = Zmm(0); + Zmm vreg_scale = Zmm(1); + Zmm vreg_nslope = Zmm(2); + + auto vreg_dst = [&](int idx) { return Zmm(3 + idx * 2 + 0); }; + auto vreg_bias = [&](int idx) { return Zmm(3 + idx * 2 + 1); }; + + preamble(); + +#define PARAM_OFF(x) offsetof(ker_args, x) + mov(reg_dst, ptr[reg_param + PARAM_OFF(dst)]); + mov(reg_acc, ptr[reg_param + PARAM_OFF(acc)]); + mov(reg_bias, ptr[reg_param + PARAM_OFF(bias)]); + mov(reg_scales, ptr[reg_param + PARAM_OFF(scales)]); + mov(reg_len, ptr[reg_param + PARAM_OFF(len)]); + mov(reg_oc_offset, ptr[reg_param + PARAM_OFF(oc_offset)]); + vbroadcastss(vreg_nslope, ptr[reg_param + PARAM_OFF(nslope)]); + if (scale_idx_mult_ == 0) + vbroadcastss(vreg_scale, dword[reg_scales]); +#undef PARAM_OFF + + if (do_relu_ || dst_type == data_type::u8) + vxorps(vreg_zero, vreg_zero, vreg_zero); + + // Load accumulated value, convert to float, apply bias (if any), scaling, + // and relu (if any); then convert to destination type and store + auto compute = [&](size_t offset, int idx, bool apply_mask) { + auto acc_addr = ptr[reg_acc + offset * sizeof(acc_data_t)]; + + if (scale_idx_mult_ > 0) { + assert(scale_idx_mult_ == 1); + auto scale_addr = ptr[reg_scales + offset * sizeof(float)]; + auto vreg_scale_ = vreg_scale; + if (apply_mask) + vreg_scale_ = vreg_scale_ | kreg_rem_mask; + vmovups(vreg_scale, scale_addr); + } + + auto vreg_dst_ = vreg_dst(idx); + if (apply_mask) + vreg_dst_ = vreg_dst_ | kreg_rem_mask; + vcvtdq2ps(vreg_dst_, acc_addr); + + if (do_bias_) { + auto bias_addr = ptr[reg_bias + offset * bias_data_type_size_]; + auto vreg_bias_ = vreg_bias(idx); + if (apply_mask) + vreg_bias_ = vreg_bias_ | kreg_rem_mask; + + switch (bias_data_type_) { + case data_type::s8: + vpmovsxbd(vreg_bias_, bias_addr); + break; + case data_type::u8: + vpmovzxbd(vreg_bias_, bias_addr); + break; + case data_type::s32: + case data_type::f32: + vmovups(vreg_bias_, bias_addr); + break; + default: assert(!"unimplemented"); + } + if (bias_data_type_ != data_type::f32) + vcvtdq2ps(vreg_bias(idx), vreg_bias(idx)); + vaddps(vreg_dst(idx), vreg_dst(idx), vreg_bias(idx)); + } + + vmulps(vreg_dst(idx), vreg_dst(idx), vreg_scale); + if (do_relu_) { + vcmpps(kreg_relu_cmp, vreg_dst(idx), vreg_zero, _cmp_lt_os); + vmulps(vreg_dst(idx) | kreg_relu_cmp, vreg_dst(idx), vreg_nslope); + } + + if (dst_type == data_type::u8) + vmaxps(vreg_dst(idx), vreg_dst(idx), vreg_zero); + + if (dst_type != data_type::f32) { + vcvtps2dq(vreg_dst(idx), vreg_dst(idx)); + } + + auto dst_addr = ptr[reg_dst + offset * sizeof(dst_data_t)]; + switch (dst_type) { + case data_type::s8: + vpmovsdb(dst_addr, vreg_dst_); + break; + case data_type::u8: + vpmovusdb(dst_addr, vreg_dst_); + break; + case data_type::f32: + case data_type::s32: + vmovups(dst_addr, vreg_dst_); + break; + default: assert(!"unimplemented"); + } + }; + + // Advance all pointers by an immediate + auto advance_ptrs_imm = [&](size_t offset) { + add(reg_dst, offset * sizeof(dst_data_t)); + add(reg_acc, offset * sizeof(acc_data_t)); + if (scale_idx_mult_) { + assert(scale_idx_mult_ == 1); + add(reg_scales, offset * sizeof(float)); + } + if (do_bias_) + add(reg_bias, offset * bias_data_type_size_); + }; + + // Advance all pointers by a value stored in a register + auto advance_ptrs_reg = [&](Reg64 offset) { + lea(reg_dst, ptr[reg_dst + offset * sizeof(dst_data_t)]); + lea(reg_acc, ptr[reg_acc + offset * sizeof(acc_data_t)]); + if (scale_idx_mult_) { + assert(scale_idx_mult_ == 1); + lea(reg_scales, ptr[reg_scales + offset * sizeof(float)]); + } + if (do_bias_) + lea(reg_bias, ptr[reg_bias + offset * bias_data_type_size_]); + }; + + // Rewind pointers that point to data that is indixed by output channel + // (bias or per-oc scaling factors) + auto rewind_ptrs = [&]() { + if (do_bias_) + sub(reg_bias, OC_ * bias_data_type_size_); + if (scale_idx_mult_) { + assert(scale_idx_mult_ == 1); + sub(reg_scales, OC_ * sizeof(float)); + } + }; + + // <-------------------- OC -------------------------------> + // + // ^ +....................+----------------------------------+ + // | : not accessed | Prologue loop | + // | +--------------------+----------------------------------+ + // | | + // M | Main loop (unrolled) | + // B | | + // +--------------------------------+----------------------+ + // | | Epilogue loop | not accessed : + // v +--------------------------------+......................+ + + Label prologue_end; + cmp(reg_oc_offset, 0); + je(prologue_end, T_NEAR); + + // Prologue loop + { + mov(reg_tmp, OC_); + sub(reg_tmp, reg_oc_offset); + cmp(reg_tmp, reg_len); + cmovg(reg_tmp, reg_len); + sub(reg_len, reg_tmp); + + Label prologue_loop, prologue_loop_tail, prologue_loop_end; + cmp(reg_tmp, vlen); + jle(prologue_loop_tail, T_NEAR); // Skips for reg_tmp == 16 too (?) + L(prologue_loop); { + compute(0, 0, false); + advance_ptrs_imm(vlen); + sub(reg_tmp, vlen); + cmp(reg_tmp, vlen); + jge(prologue_loop, T_NEAR); + } + + L(prologue_loop_tail); + mov(reg_rem_mask, 1); + shl(reg_rem_mask, cl); // cl == reg_tmp because reg_tmp <= vlen here + sub(reg_rem_mask, 1); + jz(prologue_loop_end, T_NEAR); + + kmovq(kreg_rem_mask, reg_rem_mask); + compute(0, 0, true); + advance_ptrs_reg(reg_tmp); + + L(prologue_loop_end); + rewind_ptrs(); + } + L(prologue_end); + + // Main loop + Label main_loop_end; + { + cmp(reg_len, OC_); + jle(main_loop_end, T_NEAR); + + Label main_loop; + L(main_loop); { + size_t def_unroll = 4; + size_t max_unroll = 13; + + size_t OC_loop, OC_tail; + if (OC_ < max_unroll * vlen) { + // Fully unroll small loops + OC_loop = 0; + OC_tail = OC_; + } else { + OC_loop = vlen * def_unroll; + OC_tail = OC_ % OC_loop; + } + + assert(!!OC_loop || !!OC_tail); + + if (OC_tail % vlen) { + int vlen_tail = OC_tail % vlen; + unsigned tail_mask = (1 << vlen_tail) - 1; + mov(reg_tmp, tail_mask); + kmovq(kreg_rem_mask, reg_tmp); + } + + if (OC_loop) { + mov(reg_tmp, rnd_dn(OC_, OC_loop)); + Label oc_loop; + L(oc_loop); { + for (size_t offset = 0; offset < OC_loop; offset += vlen) + compute(offset, offset / vlen, false); + advance_ptrs_imm(OC_loop); + sub(reg_tmp, OC_loop); + jnz(oc_loop); + } + } + + if (OC_tail) { + for (size_t offset = 0; offset < OC_tail; offset += vlen) { + bool use_mask = (offset + vlen) > OC_tail; + compute(offset, offset / vlen, use_mask); + } + advance_ptrs_imm(OC_tail); + } + + rewind_ptrs(); + sub(reg_len, OC_); + cmp(reg_len, OC_); + jge(main_loop, T_NEAR); + } + } + L(main_loop_end); + + // Epilogue loop + Label epilogue_end; + { + cmp(reg_len, 0); + je(epilogue_end, T_NEAR); + + Label epilogue_loop, epilogue_loop_tail; + cmp(reg_len, vlen); + jle(epilogue_loop_tail, T_NEAR); // Skips for reg_len == 16 (?) + L(epilogue_loop); { + compute(0, 0, false); + sub(reg_len, vlen); + advance_ptrs_imm(vlen); + cmp(reg_len, vlen); + jge(epilogue_loop, T_NEAR); + } + + L(epilogue_loop_tail); + mov(reg_tmp, reg_len); // reg_tmp is rcx, and we need cl for the shift + mov(reg_rem_mask, 1); + shl(reg_rem_mask, cl); // reg_tmp == rcx and reg_tail < vlen == 16 + sub(reg_rem_mask, 1); + jz(epilogue_end, T_NEAR); + kmovq(kreg_rem_mask, reg_rem_mask); + compute(0, 0, true); + } + + L(epilogue_end); + + postamble(); + + ker_ = getCode(); +} + +template +void gemm_x8s8s32x_inner_product_fwd_t::pp_kernel_t::operator ()( + dst_data_t *dst, const acc_data_t *acc, + const char *bias, const float *scales, float nslope, + size_t start, size_t end) +{ + using math::get_bias; + + if (end <= start) + return; + + if (ker_) { + // JIT + ker_args args; + size_t oc_offset = start % OC_; + args.dst = dst + start; + args.acc = acc + start; + args.bias = bias + oc_offset * bias_data_type_size_; + args.scales = scales + scale_idx_mult_ * oc_offset; + args.nslope = nslope; + args.len = end - start; + args.oc_offset = oc_offset; + ker_(&args); + } else { + // Fallback + size_t oc = start % OC_; + for (size_t i = start; i < end; i++) { + float d = (float)acc[i]; + float b = get_bias(bias, oc, bias_data_type_); + d = d + b; + d *= scales[oc * scale_idx_mult_]; + if (do_relu_ && d < 0) + d *= nslope; + dst[i] = qz_a1b0()(d); + oc = (oc == OC_ - 1) ? 0 : oc + 1; + } + } +}; + +template +void gemm_x8s8s32x_inner_product_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + + bool wei_tr = memory_desc_matches_one_of_tag( + *pd()->weights_md(), oiw, oihw, oidhw, oi); + + const int M = OC; + const int N = MB; + const int K = pd()->IC_total_padded(); + const int8_t off_a = 0, off_b = 0; + const int32_t off_c = 0; + + const float *scales = pd()->attr()->output_scales_.scales_; + + const auto &post_ops = pd()->attr()->post_ops_; + const bool do_relu = post_ops.len_ == 1; + const float nslope = do_relu ? post_ops.entry_[0].eltwise.alpha : 0.f; + + acc_data_t *acc = pd()->dst_is_acc_ + ? (acc_data_t *)dst + : scratchpad(ctx).template get(key_iprod_int_dat_in_acc_dt); + + const float onef = 1.0, zerof = 0.0; + gemm_s8x8s32(wei_tr ? "T" : "N", "N", "F", &M, &N, &K, &onef, weights, + wei_tr ? &K : &M, &off_a, src, &K, &off_b, &zerof, acc, &M, &off_c); + + if (!pd()->attr()->has_default_values() || !pd()->dst_is_acc_ + || pd()->with_bias()) { + const bool force_sequential = MB * OC < 2000; + parallel(force_sequential ? 1 : 0, [&](int ithr, int nthr) { + size_t start, end; + balance211((size_t)OC * MB, nthr, ithr, start, end); + (*pp_kernel_)(dst, acc, bias, scales, nslope, start, end); + }); + } +} + +using namespace data_type; + +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; +template struct gemm_x8s8s32x_inner_product_fwd_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.hpp new file mode 100644 index 000000000000..ac6a5c8f85c7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/gemm_x8s8s32x_inner_product.hpp @@ -0,0 +1,166 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef GEMM_X8S8S32X_INNER_PRODUCT_HPP +#define GEMM_X8S8S32X_INNER_PRODUCT_HPP + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "gemm/gemm.hpp" +#include "jit_generator.hpp" + +#include "cpu_inner_product_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct gemm_x8s8s32x_inner_product_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_fwd_pd_t { + using cpu_inner_product_fwd_pd_t::cpu_inner_product_fwd_pd_t; + + DECLARE_COMMON_PD_T(src_type == data_type::u8 + ? IGEMM_S8U8S32_IMPL_STR + : IGEMM_S8S8S32_IMPL_STR, + gemm_x8s8s32x_inner_product_fwd_t); + + status_t init() { + using namespace data_type; + + bool ok = true + && set_default_params() == status::success + && is_fwd() + && !has_zero_dim_memory() + && src_md()->data_type == src_type + && dst_md()->data_type == dst_type + && weights_md()->data_type == s8 + && IMPLICATION(with_bias(), utils::one_of( + weights_md(1)->data_type, f32, s32, s8, u8)) + && attr()->post_ops_.len_ <= 1 + && IMPLICATION(attr()->post_ops_.len_, + attr()->post_ops_.entry_[0].is_relu(true, false)) + && dense_gemm_consitency_check(src_md(), weights_md(), + dst_md()); + if (!ok) return status::unimplemented; + + dst_is_acc_ = utils::one_of(dst_type, s32, f32); + + init_scratchpad(); + + return status::success; + } + + bool dst_is_acc_; + + protected: + status_t set_default_params() { + using namespace format_tag; + if (src_md_.format_kind == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md_, + utils::pick(ndims() - 2, nc, nwc, nhwc, ndhwc))); + } + if (dst_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(dst_md_, nc)); + if (weights_md_.format_kind == format_kind::any) { + CHECK(memory_desc_init_by_tag(weights_md_, + utils::pick(ndims() - 2, io, wio, hwio, dhwio))); + } + return inner_product_fwd_pd_t::set_default_params(); + } + + private: + void init_scratchpad() { + if (!dst_is_acc_) { + auto scratchpad = scratchpad_registry().registrar(); + scratchpad.book( + memory_tracking::names::key_iprod_int_dat_in_acc_dt, + sizeof(acc_data_t) * MB() * OC()); + } + } + }; + + gemm_x8s8s32x_inner_product_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd, true) + { pp_kernel_ = new pp_kernel_t(apd, pd()->dst_is_acc_); } + ~gemm_x8s8s32x_inner_product_fwd_t() { delete pp_kernel_; } + + typedef typename prec_traits::type data_t; + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + // XXX: this is throwaway code that will become unnecessary when we have a + // sufficiently advanced igemm jit generator that supports quantization, + // relu, and whatnot + class pp_kernel_t: jit_generator { + public: + DECLARE_CPU_JIT_AUX_FUNCTIONS( + gemm_x8s8s32x_inner_product_fwd_t::pp_kernel); + pp_kernel_t(const pd_t *pd, bool dst_is_acc); + + void operator()(dst_data_t *dst, const acc_data_t *acc, + const char *bias, const float *scales, float nslope, + size_t start, size_t end); + private: + void generate(); + + struct ker_args { + dst_data_t *dst; + const acc_data_t *acc; + const char *bias; + const float *scales; + float nslope; + size_t len; + size_t oc_offset; + }; + void (*ker_)(const ker_args *args); + + size_t OC_; + data_type_t bias_data_type_; + size_t bias_data_type_size_; + size_t scale_idx_mult_; + bool do_bias_; + bool do_relu_; + }; + + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + pp_kernel_t *pp_kernel_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.cpp new file mode 100644 index 000000000000..6fa251d465f5 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.cpp @@ -0,0 +1,674 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* Copyright 2018 YANDEX LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_memory.hpp" + +#include "jit_avx2_1x1_conv_kernel_f32.hpp" + +#define GET_OFF(field) offsetof(jit_1x1_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +void jit_avx2_1x1_conv_kernel_f32::generate_bcast_loop(int load_loop_blk) +{ + mov(aux1_reg_bcast_data, reg_bcast_data); + mov(aux_reg_output_data, reg_output_data); + mov(bcast_loop_iter, reg_bcast_loop_work); + + Label bcast_loop, bcast_loop_tail; + + cmp(bcast_loop_iter, jcp.ur); + jl(bcast_loop_tail, T_NEAR); + + L(bcast_loop); { + assert(jcp.bcast_block % jcp.ur == 0); + int num_substeps = jcp.bcast_block / jcp.ur; + assert(num_substeps > 0 && num_substeps < 10); + for (int i = 0; i < num_substeps; i++) { + generate_reduce_loop(load_loop_blk, jcp.ur); + if (i < num_substeps - 1) { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_substep); + } else { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_step + - (num_substeps - 1) * jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_step + - (num_substeps - 1) * jcp.bcast_loop_output_substep); + } + } + sub(bcast_loop_iter, jcp.bcast_block); + cmp(bcast_loop_iter, jcp.bcast_block); + jge(bcast_loop, T_NEAR); + } + + L(bcast_loop_tail); + if (jcp.ur_tail) { + Label bcast_loop_tail_out; + cmp(bcast_loop_iter, 0); + jz(bcast_loop_tail_out, T_NEAR); + generate_reduce_loop(load_loop_blk, jcp.ur_tail); + L(bcast_loop_tail_out); + } +} + +void jit_avx2_1x1_conv_kernel_f32::generate_reduce_loop( + int load_loop_blk, int ur) +{ + auto vreg_load = [=](int i) { + return Ymm(ur * load_loop_blk + i); + }; + + auto vreg_accum = [=](int i, int j) { + return Ymm(j * load_loop_blk + i); + }; + + auto bias_ptr = [=](int i) { + return ptr[reg_bias_data + sizeof(float) * jcp.oc_block * i]; + }; + + auto bcast_ptr = [=](int u, int j) { + assert(j < jcp.ur); + assert(u <= jcp.reduce_loop_unroll); + size_t offt; + if (one_of(jcp.prop_kind, + forward_training, forward_inference, backward_data)) + { + assert(jcp.reduce_loop_unroll == (jcp.prop_kind == backward_data) + ? jcp.oc_block : jcp.ic_block); + auto height = (jcp.prop_kind == backward_data) ? jcp.os : jcp.is; + offt = (u == jcp.reduce_loop_unroll) + ? (height + j) * jcp.reduce_loop_unroll + : j * jcp.reduce_loop_unroll + u; + } else + offt = u * jcp.ic_block + j; + return ptr[aux_reg_bcast_data + sizeof(float) * offt]; + }; + + auto load_ptr = [=](int u, int i) { + size_t offt; + size_t u0 = u % jcp.reduce_loop_unroll; + size_t u1 = u / jcp.reduce_loop_unroll; + switch (jcp.prop_kind) { + case backward_data: + offt = (i * jcp.oc_block + u0) * jcp.ic_block; + break; + case backward_weights: + offt = (i * jcp.os + u0) * jcp.oc_block; + break; + default: + offt = (i * jcp.ic + u0) * jcp.oc_block; + } + return ptr[aux_reg_load_data + + u1 * jcp.reduce_loop_load_step + sizeof(float) * offt]; + }; + + auto output_ptr = [=](int i, int j) { + switch (jcp.prop_kind) { + case backward_data: + return ptr[aux_reg_output_data + + (i * jcp.is + j) * jcp.ic_block * sizeof(float)]; + case backward_weights: + return ptr[aux_reg_output_data + + (i ? reg_output_stride * i : 0) // TODO: Xbyak should allow 0 scale + + sizeof(float) * jcp.oc_block * j]; + default: + return ptr[aux_reg_output_data + + (i * jcp.os + j) * jcp.oc_block * sizeof(float)]; + } + }; + + auto init = [=]() { + Label init_done, init_zero; + + if (jcp.with_bias && one_of(jcp.prop_kind, forward_training, + forward_inference)) { + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jz(init_zero); + + for (int i = 0; i < load_loop_blk; i++) + for (int j = 0; j < ur; ++j) + vmovups(vreg_accum(i, j), bias_ptr(i)); + jmp(init_done); + } + + L(init_zero); + for (int i = 0; i < load_loop_blk; ++i) + for (int j = 0; j < ur; ++j) { + auto r = vreg_accum(i, j); + vxorps(r, r, r); + } + + L(init_done); + for (int i = 0; i < load_loop_blk; ++i) + vmovups(vreg_load(i), load_ptr(0, i)); + vbroadcastss(vreg_bcast, bcast_ptr(0, 0)); + }; + + auto store = [=]() { + Label store_noadd; + + if (!jcp.with_sum) { + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jnz(store_noadd, T_NEAR); + } + + for (int j = 0; j < ur; ++j) + for (int i = 0; i < load_loop_blk; ++i) { + auto r = vreg_accum(i, j); + vaddps(r, r, output_ptr(i, j)); + } + + L(store_noadd); + + if (jcp.with_eltwise) { + assert(ur * load_loop_blk < 14); + + Label store_norelu; + test(reg_reduce_pos_flag, FLAG_REDUCE_LAST); + jz(store_norelu, T_NEAR); + + eltwise_injector_->compute_vector_range(0, ur * load_loop_blk); + + L(store_norelu); + } + + for (int j = 0; j < ur; ++j) + for (int i = 0; i < load_loop_blk; ++i) { + vmovups(output_ptr(i, j), vreg_accum(i, j)); + } + }; + + auto fma_block = [=](bool last_block) { + for (int u = 0; u < jcp.reduce_loop_unroll; ++u) { + for (int j = 0; j < ur; ++j) { + for (int i = 0; i < load_loop_blk; ++i) { + if (mayiuse(avx2)) + vfmadd231ps(vreg_accum(i, j), vreg_load(i), vreg_bcast); + else { // Intel(R) Advanced Vector Extensions (Intel(R) AVX) support + vmulps(vtmp, vreg_bcast, vreg_load(i)); + vaddps(vreg_accum(i, j), vreg_accum(i, j), vtmp); + } + if (j == ur - 1 && !(last_block + && u == jcp.reduce_loop_unroll - 1)) + vmovups(vreg_load(i), load_ptr(u + 1, i)); + } + if (j < ur - 1) + vbroadcastss(vreg_bcast, bcast_ptr(u, j + 1)); + } + if (!last_block || u < jcp.reduce_loop_unroll - 1) + vbroadcastss(vreg_bcast, bcast_ptr(u + 1, 0)); + } + }; + + Label reduce_loop, reduce_loop_tail; + + mov(aux_reg_load_data, reg_load_data); + mov(aux_reg_bcast_data, aux1_reg_bcast_data); + + init(); + + mov(reduce_loop_iter, reg_reduce_loop_work); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jle(reduce_loop_tail, T_NEAR); + + L(reduce_loop); { + fma_block(false); + add(aux_reg_bcast_data, jcp.reduce_loop_bcast_step); + add(aux_reg_load_data, jcp.reduce_loop_load_step); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jg(reduce_loop, T_NEAR); + } + + L(reduce_loop_tail); + fma_block(true); + + store(); +} + +void jit_avx2_1x1_conv_kernel_f32::generate_diff_bias_loop(int load_loop_blk) +{ + if (!jcp.with_bias || jcp.prop_kind != backward_weights) + return; + + Label diff_bias_loop, diff_bias_loop_out, diff_bias_init_out; + Label diff_bias_load; + + auto diff_bias_ptr = [=](int i) { + return ptr[reg_diff_bias_data + i * jcp.oc_block * sizeof(float)]; + }; + + auto load_ptr = [=](int u, int i) { + return ptr[aux_reg_load_data + + (i * jcp.os + u) * jcp.oc_block * sizeof(float)]; + }; + + auto diff_bias_reg = [=](int i) { return Ymm(i); }; + + mov(reg_diff_bias_data, ptr[rsp + reg_diff_bias_data_stack_offt]); + cmp(reg_diff_bias_data, 0); + je(diff_bias_loop_out, T_NEAR); + + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jz(diff_bias_load, T_NEAR); + + for (int i = 0; i < load_loop_blk; ++i) { + auto r = diff_bias_reg(i); + vxorps(r, r, r); + } + jmp(diff_bias_init_out, T_NEAR); + + L(diff_bias_load); + for (int i = 0; i < load_loop_blk; ++i) + vmovups(diff_bias_reg(i), diff_bias_ptr(i)); + + L(diff_bias_init_out); + mov(aux_reg_load_data, reg_load_data); + mov(reduce_loop_iter, reg_reduce_loop_work); + L(diff_bias_loop); { + for(int u = 0; u < jcp.reduce_loop_unroll; ++u) + for (int i = 0; i < load_loop_blk; ++i) + vaddps(diff_bias_reg(i), diff_bias_reg(i), load_ptr(u, i)); + assert(jcp.reduce_dim % jcp.reduce_loop_unroll == 0); + add(aux_reg_load_data, jcp.reduce_loop_load_step); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jnz(diff_bias_loop, T_NEAR); + } + + for (int i = 0; i < load_loop_blk; i++) + vmovups(diff_bias_ptr(i), diff_bias_reg(i)); + add(reg_diff_bias_data, load_loop_blk * jcp.oc_block * sizeof(float)); + mov(ptr[rsp + reg_diff_bias_data_stack_offt], reg_diff_bias_data); + + L(diff_bias_loop_out); +} + +void jit_avx2_1x1_conv_kernel_f32::generate() +{ + preamble(); + + mov(reg_bcast_data, ptr[param1 + GET_OFF(bcast_data)]); + mov(reg_load_data, ptr[param1 + GET_OFF(load_data)]); + mov(reg_output_data, ptr[param1 + GET_OFF(output_data)]); + if (jcp.with_bias) { + if (jcp.prop_kind == backward_weights) { + sub(rsp, stack_space_needed); + mov(reg_diff_bias_data, ptr[param1 + GET_OFF(bias_data)]); + mov(ptr[rsp + reg_diff_bias_data_stack_offt], reg_diff_bias_data); + } else + mov(reg_bias_data, ptr[param1 + GET_OFF(bias_data)]); + } + + mov(reg_load_loop_work, ptr[param1 + GET_OFF(load_dim)]); + mov(reg_bcast_loop_work, ptr[param1 + GET_OFF(bcast_dim)]); + mov(reg_reduce_loop_work, ptr[param1 + GET_OFF(reduce_dim)]); + mov(reg_reduce_pos_flag, ptr[param1 + GET_OFF(first_last_flag)]); + if (jcp.prop_kind == backward_weights) + mov(reg_output_stride, ptr[param1 + GET_OFF(output_stride)]); + + auto generate_load_loop_body = [=] (int load_loop_blk) { + generate_bcast_loop(load_loop_blk); + add(reg_load_data, load_loop_blk * jcp.load_loop_load_step); + switch (jcp.prop_kind) { + case forward_training: + case forward_inference: + add(reg_bias_data, load_loop_blk * jcp.oc_block * sizeof(float)); + add(reg_output_data, + load_loop_blk * jcp.os * jcp.oc_block * sizeof(float)); + break; + case backward_data: + add(reg_output_data, + load_loop_blk * jcp.is * jcp.ic_block * sizeof(float)); + break; + case backward_weights: + for (int i = 0; i < load_loop_blk; i++) + add(reg_output_data, reg_output_stride); + break; + default: + assert(!"invalid prop_kind"); + } + sub(reg_load_loop_work, load_loop_blk * jcp.load_loop_iter_step); + }; + + Label load_loop_blk_8; + Label load_loop_blk_16; + Label load_loop_blk_24; + Label load_loop_blk_end; + + cmp(reg_load_loop_work, 8); + jle(load_loop_blk_8, T_NEAR); + + cmp(reg_load_loop_work, 32); + je(load_loop_blk_16, T_NEAR); + + cmp(reg_load_loop_work, 16); + jle(load_loop_blk_16, T_NEAR); + + L(load_loop_blk_24); { + generate_diff_bias_loop(3); + generate_load_loop_body(3); + cmp(reg_load_loop_work, 32); + je(load_loop_blk_16); + cmp(reg_load_loop_work, 24); + jge(load_loop_blk_24); + } + + cmp(reg_load_loop_work, 8); + jle(load_loop_blk_8, T_NEAR); + + L(load_loop_blk_16); { + generate_diff_bias_loop(2); + generate_load_loop_body(2); + cmp(reg_load_loop_work, 16); + jge(load_loop_blk_16); + } + + L(load_loop_blk_8); { + cmp(reg_load_loop_work, 0); + je(load_loop_blk_end, T_NEAR); + generate_diff_bias_loop(1); + generate_load_loop_body(1); + } + + L(load_loop_blk_end); + + if (jcp.with_bias && jcp.prop_kind == backward_weights) + add(rsp, 8); + + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_avx2_1x1_conv_kernel_f32::post_ops_ok( + jit_1x1_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +status_t jit_avx2_1x1_conv_kernel_f32::init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr) +{ + if (!mayiuse(avx)) return status::unimplemented; + + // TODO (Roma): this code is duplicated from the generic kernel; maybe the + // configuration struct could do some stuff below + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + const int ndims = src_d.ndims(); + + jcp.prop_kind = cd.prop_kind; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.oh = (ndims == 3) ? 1 : dst_d.dims()[2]; + jcp.ow = dst_d.dims()[ndims - 1]; + + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][0]; + jcp.l_pad = cd.padding[0][ndims - 3]; + + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[0]; + jcp.stride_w = cd.strides[ndims - 3]; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + jcp.os = jcp.oh * jcp.ow; + jcp.is = jcp.ih * jcp.iw; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) { + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + if (!mayiuse(avx2) && jcp.eltwise.alg != alg_kind::eltwise_relu) + return status::unimplemented; + } + + const int is_bwd_d = jcp.prop_kind == backward_data; + + format_tag_t dat_tag = ndims == 3 ? nCw8c : nChw8c; + format_tag_t wei_tag = with_groups + ? utils::pick(2 * ndims - 6 + is_bwd_d, gOIw8i8o, gOIw8o8i, gOIhw8i8o, + gOIhw8o8i) + : utils::pick(2 * ndims - 6 + is_bwd_d, OIw8i8o, OIw8o8i, OIhw8i8o, + OIhw8o8i); + + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + + const int simd_w = 8; + + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + + bool args_ok = true + && jcp.ngroups == 1 + && jcp.src_tag == dat_tag + && jcp.wei_tag == wei_tag + && jcp.dst_tag == dat_tag; + if (!args_ok) return status::unimplemented; + + args_ok = true + && jcp.ih == jcp.oh && jcp.iw == jcp.ow + && jcp.oc % simd_w == 0 && jcp.ic % simd_w == 0 + && jcp.t_pad == 0 && jcp.l_pad == 0 + && jcp.stride_w == 1 && jcp.stride_h == 1 // TODO: support some strides + && jcp.kh == 1 && jcp.kw == 1; + if (!args_ok) return status::unimplemented; + + // TODO: remove this restriction + // optimized 1x1 bwd_w does not support Intel AVX + if (jcp.prop_kind == backward_weights && !mayiuse(avx2)) + return status::unimplemented; + + jcp.ic_block = jcp.oc_block = simd_w; + + jcp.ur = mayiuse(avx2) ? 4 : 3; // Intel AVX support + + int load_blocking{ 0 }; + int load_blocking_max{ 0 }; + int bcast_blocking{ 0 }; + int bcast_blocking_max{ 0 }; + int reduce_blocking{ 0 }; + + if (one_of(jcp.prop_kind, forward_training, forward_inference)) { + jcp.reduce_dim = jcp.ic; + jcp.reduce_block = jcp.ic_block; + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.is; + jcp.bcast_block = jcp.ur; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.is * sizeof(float); + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.oc_block * sizeof(float); + + jcp.bcast_loop_output_step = jcp.ur * jcp.oc_block * sizeof(float); + jcp.bcast_loop_output_substep = -1; // unused + jcp.bcast_loop_bcast_step = jcp.ur * jcp.ic_block * sizeof(float); + jcp.bcast_loop_bcast_substep = -1; // unused + + jcp.load_loop_load_step = jcp.ic * jcp.oc_block * sizeof(float); + jcp.load_loop_iter_step = jcp.oc_block; + + load_blocking = 120; // assumes the kernel is jcp.ur x 3 + load_blocking_max = 144; + bcast_blocking = 128; // affects load balancing across threads + bcast_blocking_max = 192; + reduce_blocking = 128; // affects L1$ utilization + } else if (jcp.prop_kind == backward_data) { + jcp.reduce_dim = jcp.oc; + jcp.reduce_block = jcp.oc_block; + + jcp.load_dim = jcp.ic; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.os; + jcp.bcast_block = jcp.ur; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.os * sizeof(float); + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.ic * sizeof(float); + + jcp.bcast_loop_output_step = jcp.ur * jcp.ic_block * sizeof(float); + jcp.bcast_loop_output_substep = -1; // unused + jcp.bcast_loop_bcast_step = jcp.ur * jcp.oc_block * sizeof(float); + jcp.bcast_loop_bcast_substep = -1; // unused + + jcp.load_loop_load_step = jcp.oc_block * jcp.ic_block * sizeof(float); + jcp.load_loop_iter_step = jcp.ic_block; + + load_blocking = 96; // assumes the kernel is jcp.ur x 3 + load_blocking_max = 144; + bcast_blocking = 128; // affects load balancing across threads + bcast_blocking_max = 196; + reduce_blocking = 64; // affects L1$ utilization + } else if (jcp.prop_kind == backward_weights) { + jcp.reduce_dim = jcp.os; + jcp.reduce_block = 1; + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.ic; + jcp.bcast_block = jcp.ic_block; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.ic_block * sizeof(float); + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.oc_block * sizeof(float); + + jcp.bcast_loop_output_step = jcp.oc_block * jcp.ic_block * sizeof(float); + jcp.bcast_loop_output_substep = jcp.oc_block * jcp.ur * sizeof(float); + jcp.bcast_loop_bcast_step = jcp.ic_block * jcp.is * sizeof(float); + jcp.bcast_loop_bcast_substep = jcp.ur * sizeof(float); + + jcp.load_loop_load_step = jcp.oc_block * jcp.os * sizeof(float); + jcp.load_loop_iter_step = jcp.oc_block; + + /* --- */ + + load_blocking = div_up(jcp.load_dim, jcp.load_block); + while (true) { + if (load_blocking <= 32) break; + else if (load_blocking % 2 == 0) load_blocking /= 2; + else if (load_blocking % 3 == 0) load_blocking /= 3; + else break; + } + load_blocking *= jcp.load_block; + load_blocking_max = load_blocking; + assert(jcp.load_dim % load_blocking == 0); + + bcast_blocking = div_up(jcp.bcast_dim, jcp.bcast_block); + while (true) { + if (bcast_blocking <= 9) break; + else if (bcast_blocking % 2 == 0) bcast_blocking /= 2; + else if (bcast_blocking % 3 == 0) bcast_blocking /= 3; + else break; + } + bcast_blocking *= jcp.bcast_block; + bcast_blocking_max = bcast_blocking; + assert(jcp.bcast_dim % bcast_blocking == 0); + + reduce_blocking = 128; // affects L1$ utilization + } else + return status::unimplemented; + + assert(load_blocking); + assert(load_blocking_max); + assert(bcast_blocking); + assert(bcast_blocking_max); + assert(reduce_blocking); + + assert(jcp.bcast_block % jcp.ur == 0); + jcp.ur_tail = jcp.bcast_dim % jcp.ur; + + jcp.nb_bcast_blocking = bcast_blocking / jcp.bcast_block; + jcp.nb_bcast_blocking_max = bcast_blocking_max / jcp.bcast_block; + jcp.nb_load_blocking = load_blocking / jcp.load_block; + jcp.nb_load_blocking_max = load_blocking_max / jcp.load_block; + jcp.nb_reduce_blocking = reduce_blocking / jcp.reduce_block; + + jcp.nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + jcp.nb_load = div_up(jcp.load_dim, jcp.load_block); + jcp.nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + + return status::success; +} + +void jit_avx2_1x1_conv_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, + const jit_1x1_conv_conf_t &jcp) { + using namespace mkldnn::impl::memory_tracking::names; + + if (jcp.prop_kind != backward_data && jcp.oc != jcp.oc_without_padding) + scratchpad.book(key_conv_padded_bias, sizeof(float) * jcp.oc); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.hpp new file mode 100644 index 000000000000..bfdeb2b18d68 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_conv_kernel_f32.hpp @@ -0,0 +1,110 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX2_1x1_CONV_KERNEL_F32_HPP +#define JIT_AVX2_1x1_CONV_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "cpu_memory.hpp" +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx2_1x1_conv_kernel_f32: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx2_1x1_conv_kernel_f32) + + jit_avx2_1x1_conv_kernel_f32(jit_1x1_conv_conf_t ajcp, + const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32(this, + jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_1x1_conv_call_s *))this->getCode(); + } + + ~jit_avx2_1x1_conv_kernel_f32() { + delete eltwise_injector_; + } + + static bool post_ops_ok(jit_1x1_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_1x1_conv_conf_t &jcp); + + jit_1x1_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_1x1_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + using ymm_t = const Xbyak::Ymm; + + reg64_t reg_bcast_data = rax; + reg64_t reg_load_data = rsi; + reg64_t reg_output_data = rbx; + reg64_t aux_reg_bcast_data = rdx; + reg64_t aux1_reg_bcast_data = abi_not_param1; + reg64_t aux_reg_load_data = abi_param1; + reg64_t aux_reg_output_data = rbp; + reg64_t reg_load_loop_work = r9; + reg64_t reg_bcast_loop_work = r10; + reg64_t reg_reduce_loop_work = r11; + reg64_t load_loop_iter = r13; + reg64_t bcast_loop_iter = r14; + reg64_t reduce_loop_iter = r15; + reg64_t imm_addr64 = reduce_loop_iter; + reg64_t reg_reduce_pos_flag = r8; + reg64_t reg_output_stride = r12; + reg64_t reg_bias_data = r12; + reg64_t reg_diff_bias_data = bcast_loop_iter; + + int reg_diff_bias_data_stack_offt = 0; + int stack_space_needed = 8; + + ymm_t vreg_bcast = ymm_t(15); + ymm_t vtmp = ymm_t(14); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + void generate_bcast_loop(int load_loop_blk); + void generate_reduce_loop(int load_loop_blk, int ur); + void generate_diff_bias_loop(int load_loop_blk); + + void generate(); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.cpp new file mode 100644 index 000000000000..f116ac90564b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.cpp @@ -0,0 +1,545 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +#include "jit_avx2_1x1_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +#define data_blk_off(f, n, c, h, w) \ + ((ndims == 3) \ + ? (f).blk_off(n, c, w) \ + : (f).blk_off(n, c, h, w)) + +/* convolution forward */ + +void jit_avx2_1x1_convolution_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + auto rtus_space = scratchpad(ctx).get(key_conv_rtus_space); + + const int work_amount = jcp.mb * jcp.ngroups * jcp.nb_bcast; + const int ndims = dst_d.ndims(); + + const int stride_h = (ndims == 3) ? 1 : pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[ndims - 3]; + const int pad_t = (ndims == 3) ? 0 : pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][ndims - 3]; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + auto ker = [&](const int ithr, const int nthr) { + // TODO (Roma): remove this restriction + assert(jcp.stride_w == 1 && jcp.stride_h == 1); + + auto p = jit_1x1_conv_call_s(); + auto rp = rtus_driver_t::call_params_t(); + + const int nb_oc = jcp.nb_load; + const int nb_ic = jcp.nb_reduce; + const int nb_ic_blocking = jcp.nb_reduce_blocking; + const int os_block = jcp.bcast_block; + + int start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + int iwork = start; + while (iwork < end) { + int n{0}, g{0}, osb{0}; + nd_iterator_init(iwork, n, jcp.mb, g, jcp.ngroups, osb, + jcp.nb_bcast); + + int bcast_step = step(jcp.nb_bcast_blocking, jcp.nb_bcast - osb, + jcp.nb_bcast_blocking_max); + bcast_step = nstl::min(bcast_step, end - iwork); + + const int os = osb * os_block; + const int oh = os / jcp.ow; + const int ow = os % jcp.ow; + + const int ih = nstl::max(oh * stride_h - pad_t, 0); + const int iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + p.bcast_dim = this_block_size(os, jcp.os, bcast_step * os_block); + rp.os = p.bcast_dim; + + int ocb = 0; + while (ocb < jcp.nb_load) { + const int load_step = step(jcp.nb_load_blocking, + jcp.nb_load - ocb, jcp.nb_load_blocking_max); + + const int _ocb = g * nb_oc + ocb; + p.load_dim = this_block_size(ocb * jcp.oc_block, jcp.oc, + load_step * jcp.oc_block); + const size_t dst_off = data_blk_off(dst_d, n, _ocb, oh, ow); + + p.output_data = &dst[dst_off]; + + p.bias_data = &bias[_ocb * jcp.oc_block]; + + for (int icb = 0; icb < nb_ic; icb += nb_ic_blocking) { + p.first_last_flag = 0 + | (icb == 0 ? FLAG_REDUCE_FIRST : 0) + | (icb + nb_ic_blocking >= nb_ic + ? FLAG_REDUCE_LAST : 0); + + p.reduce_dim = this_block_size(icb * jcp.ic_block, jcp.ic, + nb_ic_blocking * jcp.ic_block); + rp.icb = p.reduce_dim / jcp.reduce_block; + + p.load_data = &weights[pd()->with_groups() + ? weights_d.blk_off(g, ocb, icb) + : weights_d.blk_off(ocb, icb)]; + + const int _icb = g * nb_ic + icb; + if (pd()->rtus_.reduce_src_) { + rp.ws = rtus_space + + ithr * pd()->rtus_.space_per_thread_ + + _icb * jcp.is * jcp.ic_block; + + if (ocb == 0) { + rp.src = src + data_blk_off(src_d, n, _icb, ih, iw); + rtus_driver_->ker_(&rp); + } + + p.bcast_data = rp.ws; + } else + p.bcast_data = src + data_blk_off(src_d, n, _icb, ih, iw); + + kernel_->jit_ker(&p); + } + + ocb += load_step; + } + + iwork += bcast_step; + } + }; + + if (pd()->wants_padded_bias()) { + auto padded_bias = scratchpad(ctx).get(key_conv_padded_bias); + utils::array_copy(padded_bias, bias, jcp.oc_without_padding); + utils::array_set(padded_bias + jcp.oc_without_padding, 0.f, + jcp.oc - jcp.oc_without_padding); + bias = padded_bias; + } + + parallel(0, ker); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); +} + +/* convolution backward wtr data */ + +void jit_avx2_1x1_convolution_bwd_data_t::execute_backward_data( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + + const auto &jcp = kernel_->jcp; + auto rtus_space = scratchpad(ctx).get(key_conv_rtus_space); + + // TODO (Roma): remove this restriction + assert(jcp.stride_w == 1 && jcp.stride_h == 1); + const int ndims = diff_dst_d.ndims(); + + const int stride_h = (ndims == 3) ? 1 : pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[ndims - 3]; + const int pad_t = (ndims == 3) ? 0 : pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][ndims - 3]; + + const int nb_ic = jcp.nb_load; + const int nb_oc = jcp.nb_reduce; + const int os_block = jcp.bcast_block; + const int nb_oc_blocking = jcp.nb_reduce_blocking; + + const int work_amount = jcp.mb * jcp.ngroups * jcp.nb_bcast; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + auto ker = [&](const int ithr, const int nthr) { + auto p = jit_1x1_conv_call_s(); + auto rp = rtus_driver_t::call_params_t(); + + int start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + int load_step = 0; + for (int icb = 0; icb < jcp.nb_load; icb += load_step) { + load_step = step(jcp.nb_load_blocking, jcp.nb_load - icb, + jcp.nb_load_blocking_max); + + p.load_dim = this_block_size(icb * jcp.ic_block, jcp.ic, + load_step * jcp.ic_block); + rp.icb = p.load_dim / jcp.ic_block; + + int bcast_step; + for (int iwork = start; iwork < end; iwork += bcast_step) { + int n{0}, g{0}, osb{0}; + nd_iterator_init(iwork, n, jcp.mb, g, jcp.ngroups, osb, + jcp.nb_bcast); + + bcast_step = step(jcp.nb_bcast_blocking, jcp.nb_bcast - osb, + jcp.nb_bcast_blocking_max); + bcast_step = nstl::min(bcast_step, end - iwork); + + const int os = osb * os_block; + p.bcast_dim = this_block_size(os, jcp.os, + bcast_step * os_block); + rp.os = p.bcast_dim; + + const int oh = os / jcp.ow; + const int ow = os % jcp.ow; + const int ih = nstl::max(oh * stride_h - pad_t, 0); + const int iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + const int _icb = g * nb_ic + icb; + rp.src = diff_src + data_blk_off(diff_src_d, n, _icb, ih, iw); + if (pd()->rtus_.reduce_src_) { + rp.ws = rtus_space + + ithr * pd()->rtus_.space_per_thread_; + p.output_data = rp.ws; + } else + p.output_data = rp.src; + + for (int ocb = 0; ocb < jcp.nb_reduce; + ocb += jcp.nb_reduce_blocking) { + const int _ocb = g * nb_oc + ocb; + size_t diff_dst_off = data_blk_off(diff_dst_d, n, _ocb, oh, + ow); + p.bcast_data = &diff_dst[diff_dst_off]; + + p.load_data = &weights[pd()->with_groups() + ? weights_d.blk_off(g, ocb, icb) + : weights_d.blk_off(ocb, icb)]; + + p.first_last_flag = ocb == 0 ? FLAG_REDUCE_FIRST : 0; + + p.reduce_dim = this_block_size(ocb * jcp.oc_block, jcp.oc, + nb_oc_blocking * jcp.oc_block); + + kernel_->jit_ker(&p); + } + + if (pd()->rtus_.reduce_src_) + rtus_driver_->ker_(&rp); + } + } + }; + + parallel(0, ker); +} + +/* convolution backward wtr weights */ + +jit_avx2_1x1_convolution_bwd_weights_t::jit_avx2_1x1_convolution_bwd_weights_t( + const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr) + , rtus_driver_(nullptr) +{ + kernel_ = new jit_avx2_1x1_conv_kernel_f32(pd()->jcp_, *pd()->attr()); + reducer_weights_ = + new cpu_reducer_2d_t(pd()->reducer_wei_conf_); + reducer_bias_ = new cpu_reducer_t(pd()->reducer_bia_conf_); + init_rtus_driver(this); +} + +void jit_avx2_1x1_convolution_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias_in = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + auto scratchpad = this->scratchpad(ctx); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + const memory_desc_wrapper diff_bias_d(pd()->diff_weights_md(1)); + + const auto &jcp = kernel_->jcp; + auto rtus_space = scratchpad.get(key_conv_rtus_space); + + data_t *diff_bias = pd()->wants_padded_bias() + ? scratchpad.get(key_conv_padded_bias) : diff_bias_in; + + auto reducer_bia_scratchpad = memory_tracking::grantor_t(scratchpad, + prefix_reducer_bia); + auto rb = this->reducer_bias_; + rb->init(reducer_bia_scratchpad); + + auto reducer_wei_scratchpad = memory_tracking::grantor_t(scratchpad, + prefix_reducer_wei); + auto rw = this->reducer_weights_; + rw->init(reducer_wei_scratchpad); + + const int ndims = diff_dst_d.ndims(); + // TODO (Roma): remove this restriction + assert(jcp.stride_w == 1 && jcp.stride_h == 1); + + const int nb_ic = jcp.nb_bcast; + const int nb_ic_blocking = jcp.nb_bcast_blocking; + const int bcast_work = div_up(nb_ic, nb_ic_blocking); + + const int nb_oc = jcp.nb_load; + const int nb_oc_blocking = jcp.nb_load_blocking; + const int load_work = div_up(nb_oc, nb_oc_blocking); + + const int sp_dim = jcp.reduce_dim; + const int mb_sp_work = jcp.mb * sp_dim; + + const int stride_h = (ndims == 3) ? 1 : pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[ndims - 3]; + const int pad_t = (ndims == 3) ? 0 : pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][ndims - 3]; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + auto oc_ic_sp_loop = [=](int sp_start, int sp_end, bool first_image, + data_t *store_to, size_t store_to_ld, const data_t *diff_dst, + const data_t *src, int ithr) { + auto p = jit_1x1_conv_call_s(); + auto rp = rtus_driver_t::call_params_t(); + + p.output_stride = store_to_ld * sizeof(float); + const int sp_step_def = jcp.nb_reduce_blocking * jcp.reduce_block; + + int oc_b_step = 0; + for (int oc_b = 0; oc_b < nb_oc_blocking; oc_b += oc_b_step) { + oc_b_step = step(12, nb_oc_blocking - oc_b, 18); + p.load_dim = oc_b_step * jcp.oc_block; + + int ic_b_step = 0; + for (int ic_b = 0; ic_b < nb_ic_blocking; ic_b += ic_b_step) { + ic_b_step = step(12, nb_ic_blocking - ic_b, 18); + p.bcast_dim = ic_b_step * jcp.ic_block; + rp.icb = p.bcast_dim / jcp.ic_block; + + p.output_data = store_to + oc_b * store_to_ld + + ic_b * jcp.ic_block * jcp.oc_block; + + /* spatial reduction */ + int sp_step = 0; + for (int sp = sp_start; sp < sp_end; sp += sp_step) { + sp_step = step(sp_step_def, sp_end - sp, 192); + p.reduce_dim = sp_step; + rp.os = p.reduce_dim; + + p.first_last_flag = sp == sp_start && first_image + ? FLAG_REDUCE_FIRST : 0; + + p.load_data = diff_dst + + (oc_b * jcp.reduce_dim + sp) * jcp.oc_block; + + if (pd()->rtus_.reduce_src_) { + const int oh = sp / jcp.ow; + const int ow = sp % jcp.ow; + + const int ih = nstl::max(oh * stride_h - pad_t, 0); + const int iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + rp.ws = rtus_space + + ithr * pd()->rtus_.space_per_thread_ + + (ic_b * jcp.is + sp) * jcp.ic_block; + if (ndims == 3) + rp.src = src + + iw * src_d.blocking_desc().strides[2]; + else + rp.src = src + + ih * src_d.blocking_desc().strides[2] + + iw * src_d.blocking_desc().strides[3]; + + if (oc_b == 0) + rtus_driver_->ker_(&rp); + + p.bcast_data = rp.ws; + } else + p.bcast_data = src + + (ic_b * jcp.reduce_dim + sp) * jcp.ic_block; + + kernel_->jit_ker(&p); + } + } + } + }; + + auto ker = [&](const int ithr, const int nthr) { + assert(nthr == rw->balancer().nthr_); + + const int w_njobs = rw->balancer().ithr_njobs(ithr); + if (w_njobs == 0) return; + + /* setup: independent work (oc, ic) */ + const int w_job_start = rw->balancer().ithr_job_off(ithr); + int g{0}, load_i{0}, bcast_i{0}; + nd_iterator_init(w_job_start, g, jcp.ngroups, load_i, load_work, + bcast_i, bcast_work); + + /* setup: reduction work (mb, sp) */ + int mb_sp_start{0}, mb_sp_end{0}; + balance211(mb_sp_work, rw->balancer().nthr_per_group_, + rw->balancer().id_in_group(ithr), mb_sp_start, mb_sp_end); + int img_start{0}, sp_start{0}; + nd_iterator_init(mb_sp_start, img_start, jcp.mb, sp_start, sp_dim); + + /* independent work */ + for (int iwork = 0; iwork < w_njobs; ++iwork) { + const int oc_b = nb_oc_blocking * load_i; + const int ic_b = nb_ic_blocking * bcast_i; + + const int _ic_b = g * nb_ic + ic_b; + const int _oc_b = g * nb_oc + oc_b; + + data_t *store_to; + size_t store_to_ld; + + if (rw->balancer().nthr_per_group_ == 1) { + const size_t off = pd()->with_groups() + ? diff_weights_d.blk_off(g, oc_b, ic_b) + : diff_weights_d.blk_off(oc_b, ic_b); + store_to = &diff_weights[off]; + store_to_ld = jcp.ic * jcp.oc_block; + } else { + const size_t off = iwork * rw->balancer().job_size_; + store_to = + rw->get_local_ptr(ithr, reducer_wei_scratchpad) + off; + store_to_ld = nb_ic_blocking * jcp.ic_block * jcp.oc_block; + } + + /* reduction work */ + int img = img_start; + int sp = sp_start; + int sp_step = 0; + for (int mb_sp = mb_sp_start; mb_sp < mb_sp_end; mb_sp += sp_step) + { + sp_step = nstl::min(sp_dim - sp, mb_sp_end - mb_sp); + + const bool first_image = img == img_start; + oc_ic_sp_loop(sp, sp + sp_step, first_image, store_to, + store_to_ld, &diff_dst[diff_dst_d.blk_off(img, _oc_b)], + &src[src_d.blk_off(img, _ic_b)], ithr); + + sp = 0; + img += 1; + } + + nd_iterator_step(g, jcp.ngroups, load_i, load_work, bcast_i, + bcast_work); + } + rw->reduce(ithr, diff_weights, reducer_wei_scratchpad); + }; + + auto ker_bias = [&](int ithr, int nthr) { + assert(nthr == rb->balancer().nthr_); + + const int b_job_start = rb->balancer().ithr_job_off(ithr); + const int b_njobs = rb->balancer().ithr_njobs(ithr); + + if (b_njobs == 0) return; + + /* reduction dimension */ + int img_start{0}, img_end{0}; + balance211(jcp.mb, rb->balancer().nthr_per_group_, + rb->balancer().id_in_group(ithr), img_start, img_end); + + /* jobs */ + int g_start{0}, ocb_start{0}; + nd_iterator_init(b_job_start, g_start, jcp.ngroups, ocb_start, nb_oc); + + for (int img = img_start; img < img_end; ++img) { + int g = g_start, ocb = ocb_start; + for (int b_job_loc = 0; b_job_loc < b_njobs; ++b_job_loc) { + const size_t _oc = g * nb_oc + ocb; + + const data_t *d_dst = &diff_dst[diff_dst_d.blk_off(img, _oc)]; + data_t *d_bias = + rb->get_local_ptr(ithr, diff_bias, reducer_bia_scratchpad) + + b_job_loc * rb->balancer().job_size_; + + if (img == img_start) + for (int o = 0; o < 8; ++o) d_bias[o] = 0.; + + for (int hw = 0; hw < jcp.oh * jcp.ow; ++hw) { + PRAGMA_OMP_SIMD() + for (int o = 0; o < 8; ++o) + d_bias[o] += d_dst[o]; + d_dst += 8; + } + + nd_iterator_step(g, jcp.ngroups, ocb, nb_oc); + } + } + rb->reduce(ithr, diff_bias, reducer_bia_scratchpad); + }; + + parallel(0, [&](const int ithr, const int nthr) { + ker(ithr, nthr); + if (pd()->with_bias()) + ker_bias(ithr, nthr); + }); + + /* TODO: put this in ker_bias */ + if (pd()->wants_padded_bias()) { + assert(jcp.ngroups == 1); + for (int oc = 0; oc < jcp.oc_without_padding; ++oc) + diff_bias_in[oc] = diff_bias[oc]; + } +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.hpp new file mode 100644 index 000000000000..976224217345 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_1x1_convolution.hpp @@ -0,0 +1,344 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX2_1x1_CONVOLUTION_HPP +#define CPU_JIT_AVX2_1x1_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" +#include "cpu_reducer.hpp" + +#include "jit_avx2_1x1_conv_kernel_f32.hpp" +#include "jit_uni_1x1_conv_utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx2_1x1_convolution_fwd_t: public cpu_primitive_t { + // TODO: (Roma) Code duplication duplication! Remove with templates + // (maybe...)! + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", avx2, ""), + jit_avx2_1x1_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *src_d = src_md(); + rtus_prepare(this, conv_d, src_d, dst_md()); + + status_t status = jit_avx2_1x1_conv_kernel_f32::init_conf(jcp_, + *conv_d, *src_d, *weights_md(), *dst_md(), *attr()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx2_1x1_conv_kernel_f32::init_scratchpad(scratchpad, jcp_); + + rtus_prepare_space_info(this, scratchpad); + + return status::success; + } + + jit_1x1_conv_conf_t jcp_; + reduce_to_unit_stride_t rtus_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, gOIw8i8o, gOIhw8i8o) + : utils::pick(ndims() - 3, OIw8i8o, OIhw8i8o); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx2_1x1_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr), rtus_driver_(nullptr) + { + kernel_ = new jit_avx2_1x1_conv_kernel_f32(pd()->jcp_, *pd()->attr()); + init_rtus_driver(this); + } + + ~jit_avx2_1x1_convolution_fwd_t() { + delete kernel_; + delete rtus_driver_; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx2_1x1_conv_kernel_f32 *kernel_; + rtus_driver_t *rtus_driver_; +}; + +struct jit_avx2_1x1_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", avx2, ""), + jit_avx2_1x1_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::undef, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *diff_src_d = diff_src_md(); + rtus_prepare(this, conv_d, diff_src_d, diff_dst_md()); + + status_t status = jit_avx2_1x1_conv_kernel_f32::init_conf(jcp_, + *conv_d, *diff_src_d, *weights_md(), *diff_dst_md(), + *attr()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx2_1x1_conv_kernel_f32::init_scratchpad(scratchpad, jcp_); + + rtus_prepare_space_info(this, scratchpad); + + return status::success; + } + + jit_1x1_conv_conf_t jcp_; + reduce_to_unit_stride_t rtus_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, gOIw8o8i, gOIhw8o8i) + : utils::pick(ndims() - 3, OIw8o8i, OIhw8o8i); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx2_1x1_convolution_bwd_data_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr) + , rtus_driver_(nullptr) + { + kernel_ = new jit_avx2_1x1_conv_kernel_f32(pd()->jcp_, *pd()->attr()); + init_rtus_driver(this); + } + + ~jit_avx2_1x1_convolution_bwd_data_t() { + delete kernel_; + delete rtus_driver_; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx2_1x1_conv_kernel_f32 *kernel_; + rtus_driver_t *rtus_driver_; +}; + +struct jit_avx2_1x1_convolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", avx2, ""), + jit_avx2_1x1_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *src_d = src_md(); + rtus_prepare(this, conv_d, src_d, diff_dst_md()); + + status_t status = jit_avx2_1x1_conv_kernel_f32::init_conf(jcp_, + *conv_d, *src_d, *diff_weights_md(), *diff_dst_md(), + *attr()); + if (status != status::success) return status; + + init_balancers(); + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx2_1x1_conv_kernel_f32::init_scratchpad(scratchpad, jcp_); + + rtus_prepare_space_info(this, scratchpad); + + auto reducer_bia_scratchpad = memory_tracking::registrar_t( + scratchpad, memory_tracking::names::prefix_reducer_bia); + reducer_bia_conf_.init_scratchpad(reducer_bia_scratchpad); + + auto reducer_wei_scratchpad = memory_tracking::registrar_t( + scratchpad, memory_tracking::names::prefix_reducer_wei); + reducer_wei_conf_.init_scratchpad(reducer_wei_scratchpad); + + return status::success; + } + + jit_1x1_conv_conf_t jcp_; + cpu_reducer_t::conf_t reducer_bia_conf_; + cpu_reducer_2d_t::conf_t reducer_wei_conf_; + reduce_to_unit_stride_t rtus_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, gOIw8i8o, gOIhw8i8o) + : utils::pick(ndims() - 3, OIw8i8o, OIhw8i8o); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + + private: + void init_balancers() { + const int ic_block = jcp_.bcast_block; + const int nb_ic = jcp_.nb_bcast; + const int nb_ic_blocking = jcp_.nb_bcast_blocking; + const int bcast_work = utils::div_up(nb_ic, nb_ic_blocking); + + const int oc_block = jcp_.load_block; + const int nb_oc = jcp_.nb_load; + const int nb_oc_blocking = jcp_.nb_load_blocking; + const int load_work = utils::div_up(nb_oc, nb_oc_blocking); + + const int job_size + = nb_oc_blocking * nb_ic_blocking * ic_block * oc_block; + const int njobs_x = bcast_work; + const int njobs_y = jcp_.ngroups * load_work; + + const int max_threads = mkldnn_get_max_threads(); + const size_t max_buffer_size = max_threads * job_size * 8; + + if (with_bias()) { + reducer_bia_conf_.init(reduce_balancer_t(max_threads, + oc_block, jcp_.ngroups * jcp_.oc / oc_block, + jcp_.mb, max_buffer_size)); + } + + reducer_wei_conf_.init( + reduce_balancer_t(max_threads, job_size, njobs_y * njobs_x, + jcp_.mb * jcp_.nb_reduce, max_buffer_size), + job_size / nb_oc_blocking, nb_oc_blocking, ic_block, + nb_ic * ic_block * oc_block, nb_oc); + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx2_1x1_convolution_bwd_weights_t(const pd_t *apd); + + ~jit_avx2_1x1_convolution_bwd_weights_t() { + delete kernel_; + delete rtus_driver_; + delete reducer_weights_; + delete reducer_bias_; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx2_1x1_conv_kernel_f32 *kernel_; + cpu_reducer_2d_t *reducer_weights_; + cpu_reducer_t *reducer_bias_; + rtus_driver_t *rtus_driver_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp new file mode 100644 index 000000000000..e24770a2dac9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp @@ -0,0 +1,1501 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* Copyright 2018 YANDEX LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "cpu_memory.hpp" + +#include "jit_avx2_conv_kernel_f32.hpp" + +#define GET_OFF(field) offsetof(jit_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +void jit_avx2_conv_fwd_kernel_f32::oh_step_unroll_kw(int ur_w, + int pad_l, int pad_r, int oc_blocks) +{ + int iw = jcp.iw; + int ih = jcp.ih; + int id = jcp.id; + int kw = jcp.kw; + int kh = jcp.kh; + int kd = jcp.kd; + int nb_ic = jcp.nb_ic; + int stride_w = jcp.stride_w; + int dilate_w = jcp.dilate_w + 1; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + + for (int ki = 0; ki < kw; ki++) { + int jj_start = nstl::max(0, div_up(pad_l - ki * dilate_w, stride_w)); + int jj_end = ur_w + - nstl::max(0, div_up(ki*dilate_w+pad_r-(kw-1)*dilate_w, stride_w)); + for (int ifm2 = 0; ifm2 < ic_blk; ifm2++) { + for (int jj = jj_start; jj < jj_end; jj++) { + size_t inp_off; + if (one_of(jcp.src_tag, ncw, nchw, ncdhw)) + inp_off = sizeof(float)*((size_t)ifm2*id*ih*iw + + (ki*dilate_w + jj*stride_w - pad_l)); + else + inp_off = sizeof(float)*((ki*dilate_w + jj*stride_w + - pad_l)*ic_blk + ifm2); + vbroadcastss(Ymm(oc_blocks * ur_w + jj), + make_safe_addr(aux_reg_input, inp_off, reg_long_offt)); + } + + for (int ii = 0; ii < oc_blocks; ii++) { + int ker_off = ii * nb_ic * kd * kh * kw * ic_blk * oc_blk + + ki * ic_blk * oc_blk + ifm2 * oc_blk; + vmovups(ymm15, ptr[aux_reg_kernel + sizeof(float) * ker_off]); + for (int jj = jj_start; jj < jj_end; jj++) + if (mayiuse(avx2)) + vfmadd231ps(Ymm(ur_w * ii + jj), + Ymm(oc_blocks * ur_w + jj), ymm15); + else { // Intel(R) Advanced Vector Extensions (Intel(R) AVX) support + vmulps(ytmp, ymm15, Ymm(oc_blocks * ur_w + jj)); + vaddps(Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj), ytmp); + } + } + } + } +} + +void jit_avx2_conv_fwd_kernel_f32::oh_step_nopad(int ur_w, + int pad_l, int pad_r, char pad_tag, + int oc_blocks, char oc_blocks_tag) +{ + Label kw_loop; + + int iw = jcp.iw; + int ih = jcp.ih; + int id = jcp.id; + int kw = jcp.kw; + int kh = jcp.kh; + int kd = jcp.kd; + int nb_ic = jcp.nb_ic; + int stride_w = jcp.stride_w; + int dilate_w = jcp.dilate_w + 1; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + + xor_(ki_iter, ki_iter); + L(kw_loop); + { + int jj_start = 0; + int jj_end = ur_w; + for (int ifm2 = 0; ifm2 < ic_blk; ifm2++) { + for (int jj = jj_start; jj < jj_end; jj++) { + size_t inp_off; + if (one_of(jcp.src_tag, ncw, nchw, ncdhw)) + inp_off = sizeof(float)*((size_t)ifm2 * id * ih * iw + + (jj * stride_w - pad_l)); + else + inp_off = sizeof(float)*((jj * stride_w - pad_l) * ic_blk + + ifm2); + vbroadcastss(Ymm(oc_blocks * ur_w + jj), + make_safe_addr(aux_reg_input, inp_off, reg_long_offt)); + } + for (int ii = 0; ii < oc_blocks; ii++) { + int aux_kernel_offset = + ii * nb_ic * kd * kh * kw * ic_blk * oc_blk + ifm2 * oc_blk; + vmovups(ymm15, ptr[aux_reg_kernel + + sizeof(float) * aux_kernel_offset]); + for (int jj = jj_start; jj < jj_end; jj++) + if (mayiuse(avx2)) + vfmadd231ps(Ymm(ur_w * ii + jj), + Ymm(oc_blocks * ur_w + jj), ymm15); + else { // Intel AVX support + vmulps(ytmp, ymm15, Ymm(oc_blocks * ur_w + jj)); + vaddps(Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj), ytmp); + } + } + } + add(aux_reg_kernel, sizeof(float) * oc_blk * ic_blk); + add(aux_reg_input, sizeof(float) * (one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? dilate_w : ic_blk * dilate_w)); + + inc(ki_iter); + cmp(ki_iter, kw); + jl(kw_loop, T_NEAR); + } +} + +void jit_avx2_conv_fwd_kernel_f32::width_blk_step(int ur_w, + int pad_l, int pad_r, char pad_tag, + int oc_blocks, char oc_blocks_tag) +{ + int iw = jcp.iw; + int kw = jcp.kw; + int ow = jcp.ow; + int oh = jcp.oh; + int od = jcp.od; + int dilate_h = jcp.dilate_h + 1; + int dilate_w = jcp.dilate_w + 1; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + const int inp_mult = one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? 1 : ic_blk; + const int inp_off = one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? dilate_w : ic_blk * dilate_w; + + Label init_done, init_first; + + if (!jcp.with_sum) { + test(reg_ci_flag, FLAG_IC_FIRST); + jne(init_first, T_NEAR); + } + + for (int ii = 0; ii < oc_blocks; ii++) { + for (int jj = 0; jj < ur_w; jj++) { + size_t offt = + sizeof(float) * ((size_t)ii * od * oh * ow + jj) * oc_blk; + vmovups(Ymm(ur_w * ii + jj), + make_safe_addr(reg_output, offt, reg_long_offt)); + } + } + + if (jcp.with_sum && jcp.with_bias) { + test(reg_ci_flag, FLAG_IC_FIRST); + je(init_done, T_NEAR); + + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + vaddps(Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj), + yword[reg_bias + sizeof(float) * ii * oc_blk]); + } + + jmp(init_done); + + L(init_first); + if (this->jcp.with_bias) { + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + vmovups(Ymm(ur_w * ii + jj), + yword[reg_bias + sizeof(float) * ii * oc_blk]); + } else { + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + uni_vpxor(Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj)); + } + + L(init_done); + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + } + + Label skip_kh_loop, skip_kd_loop, kd_loop; + if (jcp.ndims == 5) { + push(reg_output); + push(oi_iter); + + mov(reg_ki, ptr[param1 + GET_OFF(kd_padding)]); + mov(aux_reg_ker_d, ptr[param1 + GET_OFF(filt)]); + mov(aux_reg_inp_d, reg_input); + + if ((jcp.dilate_d >= jcp.id) + || (jcp.kd - 1) * (jcp.dilate_d + 1) < jcp.f_pad) { + cmp(reg_ki, 0); + je(skip_kd_loop, T_NEAR); + } + L(kd_loop); + mov(kj, ptr[param1 + GET_OFF(kh_padding)]); + } else { + mov(kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_input, aux_reg_inp_d); + mov(aux_reg_kernel, aux_reg_ker_d); + } + + if ((jcp.dilate_h >= jcp.ih) + || (jcp.kh - 1) * (jcp.dilate_h + 1) < nstl::max(jcp.t_pad, jcp.b_pad)) { + cmp(kj, 0); + je(skip_kh_loop, T_NEAR); + } + Label kh_loop; + L(kh_loop); + { + if (jcp.kw >= 5 && pad_l == 0 && pad_r == 0) { + oh_step_nopad(ur_w, pad_l, pad_r, pad_tag, oc_blocks, + oc_blocks_tag); + sub(aux_reg_input, sizeof(float) * kw * inp_off); + add(aux_reg_input, sizeof(float) * iw * dilate_h * inp_mult); + } else { + oh_step_unroll_kw(ur_w, pad_l, pad_r, oc_blocks); + add(aux_reg_kernel, sizeof(float) * kw * oc_blk * ic_blk); + add(aux_reg_input, sizeof(float) * iw * dilate_h * inp_mult); + } + + dec(kj); + cmp(kj, 0); + jg(kh_loop, T_NEAR); + } + + L(skip_kh_loop); + + if (jcp.ndims == 5) { + add(aux_reg_inp_d, + sizeof(float) * (jcp.dilate_d + 1) * jcp.ih * jcp.iw * inp_mult); + add(aux_reg_ker_d, sizeof(float) * jcp.kw * jcp.kh * jcp.oc_block + * jcp.ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_loop, T_NEAR); + L(skip_kd_loop); + + pop(oi_iter); + pop(reg_output); + } + + Label regular_store; + + if (jcp.with_eltwise) { + test(reg_ci_flag, FLAG_IC_LAST); + je(regular_store, T_NEAR); + + eltwise_injector_->compute_vector_range(0, oc_blocks * ur_w); + + L(regular_store); + } + + for (int ii = 0; ii < oc_blocks; ii++) { + for (int jj = 0; jj < ur_w; jj++) { + const size_t o_off + = sizeof(float) * ((size_t)ii * od * oh * ow + jj) * oc_blk; + Ymm reg_out = Ymm(ur_w * ii + jj); + vmovups(make_safe_addr(reg_output, o_off, reg_long_offt), reg_out); + } + } +} + +inline void jit_avx2_conv_fwd_kernel_f32::solve_common( + int oc_blocks, char oc_blocks_tag) +{ + int ur_w = jcp.ur_w; + int ur_w_tail = jcp.ur_w_tail; + int n_oi = jcp.ow / ur_w; + int iw = jcp.iw; + int kw = jcp.kw; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + int dilate_w = jcp.dilate_w + 1; + int str_w = jcp.stride_w; + const int inp_mult = one_of(jcp.src_tag, ncw, nchw, ncdhw) ? 1 : ic_blk; + + int l_pad = jcp.l_pad; + int r_pad = nstl::max(0, (int(jcp.ow) - 1) * str_w + (kw - 1) * dilate_w + - (iw + l_pad - 1)); + int r_pad1 = (ur_w * n_oi - 1) * str_w + (kw - 1) * dilate_w + - (iw + l_pad - 1); + if (r_pad1 > 0) n_oi--; + + if (l_pad > 0) { + n_oi--; + if (n_oi < 0 && r_pad1 > 0) + width_blk_step(ur_w, l_pad, r_pad1, + 'l', oc_blocks, oc_blocks_tag); // "lrpad" + else + width_blk_step(ur_w, l_pad, 0, + 'l', oc_blocks, oc_blocks_tag); // "lpad" + add(reg_input, sizeof(float) * (ur_w * str_w - l_pad) * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_blk); + } + + Label ow_loop; + xor_(oi_iter, oi_iter); + + if (n_oi > 0) { + L(ow_loop); + + width_blk_step(ur_w, 0, 0, + 'm', oc_blocks, oc_blocks_tag); // "middle" + add(reg_input, sizeof(float) * ur_w * str_w * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_blk); + + inc(oi_iter); + cmp(oi_iter, n_oi); + jl(ow_loop, T_NEAR); + } + + if (r_pad1 > 0 && n_oi >=0) { + width_blk_step(ur_w, 0, r_pad1, + 'r', oc_blocks, oc_blocks_tag); // "rpad" + add(reg_input, sizeof(float) * ur_w * str_w * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_blk); + } + + if (ur_w_tail != 0) + width_blk_step(ur_w_tail, 0, r_pad, + 't', oc_blocks, oc_blocks_tag); // "tail" +} + +void jit_avx2_conv_fwd_kernel_f32::generate() +{ + this->preamble(); + + mov(reg_input, ptr[this->param1 + GET_OFF(src)]); + mov(reg_output, ptr[this->param1 + GET_OFF(dst)]); + mov(reg_kernel, ptr[this->param1 + GET_OFF(filt)]); + if (jcp.with_bias) + mov(reg_bias, ptr[this->param1 + GET_OFF(bias)]); + mov(reg_kh, ptr[this->param1 + GET_OFF(kh_padding)]); + mov(reg_ci_flag, ptr[this->param1 + GET_OFF(flags)]); + mov(reg_oc_blocks, ptr[this->param1 + GET_OFF(oc_blocks)]); + + int nb_oc_tail = jcp.nb_oc % jcp.nb_oc_blocking; + Label tail, exit; + + if (jcp.nb_oc > jcp.nb_oc_blocking) { + cmp(reg_oc_blocks, jcp.nb_oc_blocking); + jne(nb_oc_tail ? tail : exit, T_NEAR); + + solve_common(jcp.nb_oc_blocking, '0' + jcp.nb_oc_blocking); + jmp(exit, T_NEAR); + + if (nb_oc_tail) { + L(tail); + cmp(reg_oc_blocks, nb_oc_tail); + jne(exit, T_NEAR); + solve_common(nb_oc_tail, '0' + nb_oc_tail); + } + + L(exit); + } else if (jcp.nb_oc == jcp.nb_oc_blocking) { + solve_common(jcp.nb_oc_blocking, '0' + jcp.nb_oc_blocking); + } else { + solve_common(nb_oc_tail, '0' + nb_oc_tail); + } + + this->postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_avx2_conv_fwd_kernel_f32::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +status_t jit_avx2_conv_fwd_kernel_f32::init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr) +{ + if (!mayiuse(avx)) return status::unimplemented; + + jcp.prop_kind = cd.prop_kind; + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + int ndims = src_d.ndims(); + jcp.ndims = ndims; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + jcp.id = (ndims == 5) ? src_d.dims()[2] : 1; + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[ndims-2]; + jcp.iw = src_d.dims()[ndims-1]; + jcp.od = (ndims == 5) ? dst_d.dims()[2] : 1; + jcp.oh = (ndims == 3) ? 1 :dst_d.dims()[ndims-2]; + jcp.ow = dst_d.dims()[ndims-1]; + jcp.kd = (ndims == 5) ? weights_d.dims()[with_groups + 2] : 1; + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + ndims-2]; + jcp.kw = weights_d.dims()[with_groups + ndims-1]; + + jcp.f_pad = (ndims == 5) ? cd.padding[0][0] : 0; + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][ndims-4]; + jcp.l_pad = cd.padding[0][ndims-3]; + jcp.stride_d = (ndims == 5) ? cd.strides[0] : 1; + jcp.stride_h = (ndims == 3) ? 1 :cd.strides[ndims-4]; + jcp.stride_w = cd.strides[ndims-3]; + + jcp.dilate_d = (ndims == 5) ? cd.dilates[0] : 0; + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[ndims-4]; + jcp.dilate_w = cd.dilates[ndims-3]; + + jcp.b_pad = (jcp.oh - 1) * jcp.stride_h + (jcp.kh - 1) * (jcp.dilate_h + 1) + - (jcp.ih + jcp.t_pad - 1); + + if (ndims == 3) { + jcp.src_tag = src_d.matches_one_of_tag(ncw, nwc, nCw8c); + jcp.wei_tag = weights_d.matches_one_of_tag( + Owi8o, gOwi8o, OIw8i8o, gOIw8i8o); + jcp.dst_tag = dst_d.matches_one_of_tag(nCw8c); + } else if (ndims == 4) { + jcp.src_tag = src_d.matches_one_of_tag(nchw, nhwc, nChw8c); + jcp.wei_tag = weights_d.matches_one_of_tag( + Ohwi8o, gOhwi8o, OIhw8i8o, gOIhw8i8o); + jcp.dst_tag = dst_d.matches_one_of_tag(nChw8c); + } else if (ndims == 5) { + jcp.src_tag = src_d.matches_one_of_tag(ncdhw, ndhwc, nCdhw8c); + jcp.wei_tag = weights_d.matches_one_of_tag( + Odhwi8o, gOdhwi8o, OIdhw8i8o, gOIdhw8i8o); + jcp.dst_tag = dst_d.matches_one_of_tag(nCdhw8c); + } + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) { + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + if (!mayiuse(avx2) && jcp.eltwise.alg != alg_kind::eltwise_relu) + return status::unimplemented; + } + + const int simd_w = 8; + const bool flat = jcp.ic < simd_w; + const bool mimo = !flat; + + + /* Grouped channel offset to support 'non-blocked data' format for + * convolution sizes with '(input_channel / ngroups) < simd' */ + jcp.nonblk_group_off = + one_of(jcp.src_tag, ncw, nchw, ncdhw) && jcp.ngroups > 1 ? jcp.ic : 1; + + bool ok_to_pad_channels = true + && jcp.ngroups == 1; + + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + if (mimo) + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + bool args_ok = true + && IMPLICATION(flat, true + && one_of(jcp.src_tag, ncw, nwc, nchw, nhwc, ncdhw, ndhwc) + && one_of(jcp.wei_tag, Owi8o, gOwi8o, Ohwi8o, gOhwi8o, Odhwi8o, + gOdhwi8o)) + && IMPLICATION(mimo, true + && one_of(jcp.src_tag, nCw8c, nChw8c, nCdhw8c) + && one_of(jcp.wei_tag, OIw8i8o, gOIw8i8o, OIhw8i8o, gOIhw8i8o, + OIdhw8i8o, gOIdhw8i8o)) + && one_of(jcp.dst_tag, nCw8c, nChw8c, nCdhw8c); + if (!args_ok) return status::unimplemented; + + jcp.ur_h = 1; /* no code-unrolling by h so far */ + jcp.ur_w = 3; + + jcp.oc_block = simd_w; + jcp.nb_oc = jcp.oc / jcp.oc_block; + + jcp.nb_oc_blocking = 4; /* the optimal value for the kernel */ + + // Intel AVX and Intel AVX2 kernels need 2 and 1 temporary YMMs, respectively + // Thus, we can only assign 14 or 15 YMMs for data storage + const int num_avail_regs = mayiuse(avx2) ? 15 : 14; + if (!mayiuse(avx2)) { + if ((jcp.nb_oc_blocking + 1) * jcp.ur_w > num_avail_regs) { + // current register assignment requires more YMMs than available + // adjust one of nb_oc_block, ur_w preserving to ur_w >= l_pad + if (jcp.ur_w > jcp.l_pad && jcp.ur_w > 1) + jcp.ur_w -= 1; + else + for (int b = 3; b > 1; b--) + if (jcp.nb_oc % b == 0) { + jcp.nb_oc_blocking = b; + break; + } + } + } + + if (jcp.ow < jcp.ur_w) jcp.ur_w = jcp.ow; + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + + args_ok = true + && jcp.oc % simd_w == 0 + && jcp.l_pad <= jcp.ur_w + && IMPLICATION(jcp.kw > 7, (jcp.t_pad == 0 && jcp.l_pad == 0) + || (jcp.stride_w == 1 && jcp.stride_h == 1)) + && IMPLICATION(mimo, jcp.ic % simd_w == 0); + if (!args_ok) return status::unimplemented; + + int r_pad_no_tail = nstl::max(0, (jcp.ow - jcp.ur_w_tail - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + + if (r_pad_no_tail > jcp.ur_w * jcp.stride_w && jcp.ow / jcp.ur_w > 1) { + /* recalculate ur_w, nb_oc_blocking and ur_w_tail */ + jcp.ur_w = nstl::min(r_pad_no_tail / jcp.stride_w + jcp.ur_w_tail, + nstl::min(jcp.ow, num_avail_regs / 2)); + jcp.nb_oc_blocking = (num_avail_regs - jcp.ur_w) / jcp.ur_w; + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + /* check again ... */ + r_pad_no_tail = nstl::max(0, (jcp.ow - jcp.ur_w_tail - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + if (jcp.ur_w < nstl::max(jcp.l_pad, r_pad_no_tail)) + return status::unimplemented; + } + assert(jcp.nb_oc_blocking > 0); + assert(jcp.ur_w * (jcp.nb_oc_blocking + 1) <= num_avail_regs); + + jcp.ic_block = (jcp.ic % simd_w != 0) ? jcp.ic : simd_w; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + if (one_of(jcp.prop_kind, forward_training, forward_inference)) { + jcp.nb_ic_blocking = 12; + jcp.nb_ic_blocking_max = 16; + } else { + jcp.nb_ic_blocking = 1; + jcp.nb_ic_blocking_max = jcp.nb_ic_blocking; + } + + return status::success; +} + +void jit_avx2_conv_fwd_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + if (jcp.with_bias && jcp.oc != jcp.oc_without_padding) + scratchpad.book(key_conv_padded_bias, sizeof(float) * jcp.oc); +} + +void jit_avx2_conv_bwd_data_kernel_f32::compute_loop(int ur_w, int l_overflow, + int r_overflow) +{ + int kw = jcp.kw; + int kh = jcp.kh; + int kd = jcp.kd; + int iw = jcp.iw; + int ih = jcp.ih; + int id = jcp.id; + int ow = jcp.ow; + + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int nb_ic_block = jcp.nb_ic_blocking; + int stride_w = jcp.stride_w; + int stride_h = jcp.stride_h; + + Label kd_loop, skip_kd_loop; + Label oc_loop, skip_oc_loop; + + for (int ii = 0; ii < nb_ic_block; ii++) + for (int jj = 0; jj < ur_w; jj++) { + uni_vpxor(Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj), + Ymm(ur_w * ii + jj)); + } + + if (one_of(jcp.ndims, 3, 4)) { + cmp(reg_channel_work, 0); + jle(skip_oc_loop, T_NEAR); + xor_(reg_channel, reg_channel); + + mov(aux_reg_ddst_oc_loop, reg_ddst); + mov(aux_reg_kernel_oc_loop, reg_kernel); + + L(oc_loop); + mov(aux_reg_ddst, aux_reg_ddst_oc_loop); + mov(aux_reg_kernel, aux_reg_kernel_oc_loop); + } + + if (jcp.ndims == 5) { + assert(jcp.nb_oc_blocking == 1); + push(oi_iter); + + mov(reg_ki, ptr[this->param1 + GET_OFF(kd_padding)]); + mov(aux_reg_dst_d, reg_ddst); + mov(aux_reg_ker_d, ptr[this->param1 + GET_OFF(filt)]); + + L(kd_loop); + mov(kj, ptr[this->param1 + GET_OFF(kh_padding)]); + } else { + mov(kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_ddst, aux_reg_dst_d); + mov(aux_reg_kernel, aux_reg_ker_d); + } + + Label kh_loop, skip_kh_loop; + cmp(kj, 0); + jle(skip_kh_loop, T_NEAR); + L(kh_loop); { + for (int ki = 0; ki < kw; ki++) { + int jj_start = get_iw_start(ki, l_overflow); // 0; + int jj_end = get_iw_end(ur_w, ki, r_overflow); // ur_w; + for (int ofm2 = 0; ofm2 < jcp.oc_block; ofm2++) { + + for (int jj = jj_start ; jj < jj_end; jj += stride_w) { + int aux_output_offset + = (jj + jcp.l_pad - ki) / stride_w * jcp.oc_block + ofm2; + vbroadcastss(Ymm(nb_ic_block * ur_w + jj / stride_w), + ptr[aux_reg_ddst + + sizeof(float) * aux_output_offset]); + } + + for (int ii = 0; ii < nb_ic_block; ii++) { + int aux_kernel_offset + = ii * kd * kh * kw * jcp.ic_block * jcp.oc_block + + ki * jcp.ic_block * jcp.oc_block + + ofm2 * jcp.ic_block; + vmovups(ymm15, + ptr[aux_reg_kernel + + sizeof(float) * aux_kernel_offset]); + for (int jj = jj_start; jj < jj_end; jj += stride_w) + vfmadd231ps(Ymm(ur_w * ii + jj), + Ymm(nb_ic_block * ur_w + jj / stride_w), ymm15); + } + } + } + add(aux_reg_kernel, sizeof(float) * stride_h * kw * oc_block + * ic_block); + sub(aux_reg_ddst, sizeof(float) * ow * oc_block); + + dec(kj); + cmp(kj, 0); + jg(kh_loop, T_NEAR); + } + L(skip_kh_loop); + + if (jcp.ndims == 5) { + sub(aux_reg_dst_d, + sizeof(float) * (jcp.dilate_d + 1) * jcp.oh * ow * ic_block); + add(aux_reg_ker_d, + sizeof(float) * jcp.kw * jcp.kh * oc_block * ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_loop, T_NEAR); + L(skip_kd_loop); + + pop(oi_iter); + } + + if (one_of(jcp.ndims, 3, 4)) { + int ddst_oc_shift = sizeof(float) * jcp.od * jcp.oh * jcp.ow + * jcp.oc_block; + int kernel_oc_shift = sizeof(float) * jcp.kd * jcp.kh * jcp.kw + * jcp.ic * jcp.oc_block; + + add(aux_reg_ddst_oc_loop, ddst_oc_shift); + add(aux_reg_kernel_oc_loop, kernel_oc_shift); + + inc(reg_channel); + cmp(reg_channel, reg_channel_work); + jl(oc_loop, T_NEAR); + + L(skip_oc_loop); + mov(reg_channel, ptr[param1 + GET_OFF(channel)]); + } + + Label no_update_label; + cmp(reg_channel, 0); + je(no_update_label, T_NEAR); + for (int ii = 0; ii < nb_ic_block; ii++) { + for (int jj = 0; jj < ur_w; jj++) { + size_t offt = + sizeof(float) * ((size_t)ii * id * ih * iw + jj) * ic_block; + vmovups(Ymm(15), + make_safe_addr(reg_dsrc, offt, reg_long_offt)); + vaddps(Ymm(ur_w * ii + jj), Ymm(ur_w * ii + jj), + Ymm(15)); + + } + } + L(no_update_label); + + for (int ii = 0; ii < nb_ic_block; ii++) + for (int jj = 0; jj < ur_w; jj++) { + size_t offt = + sizeof(float) * ((size_t)ii * id * ih * iw + jj) * ic_block; + vmovups(make_safe_addr(reg_dsrc, offt, reg_long_offt), + Ymm(ur_w * ii + jj)); + } +} + +void jit_avx2_conv_bwd_data_kernel_f32::generate() { + preamble(); + + mov(reg_dsrc, ptr[this->param1 + GET_OFF(src)]); + mov(reg_ddst, ptr[this->param1 + GET_OFF(dst)]); + mov(reg_kernel, ptr[this->param1 + GET_OFF(filt)]); + mov(reg_kh, ptr[this->param1 + GET_OFF(kh_padding)]); + mov(reg_channel, ptr[param1 + GET_OFF(channel)]); + mov(reg_channel_work, ptr[param1 + GET_OFF(ch_blocks)]); + + int ddst_shift = sizeof(float) * (jcp.ur_w / jcp.stride_w) * jcp.ic_block; + int dsrc_shift = sizeof(float) * jcp.ur_w * jcp.oc_block; + + int l_overflow = nstl::max(0, (jcp.kw - 1 - jcp.l_pad) / jcp.stride_w); + int r_overflow = nstl::max(0, (jcp.kw - 1 + - nstl::max(0, jcp.r_pad)) / jcp.stride_w); + int r_overflow1 = nstl::max(0, (jcp.kw - 1 + - nstl::max(0, jcp.r_pad) - jcp.ur_w_tail) / jcp.stride_w); + + int n_oi = jcp.iw / jcp.ur_w; + if (r_overflow1 > 0) + n_oi--; + + if (jcp.ur_w == jcp.iw) { + compute_loop(jcp.ur_w, l_overflow, r_overflow); + } else if (n_oi == 0) { + compute_loop(jcp.ur_w, l_overflow, r_overflow1); + add(reg_dsrc, dsrc_shift); + add(reg_ddst, ddst_shift); + if (jcp.ur_w_tail != 0) + compute_loop(jcp.ur_w_tail, 0, r_overflow); + } else { + xor_(oi_iter, oi_iter); + if (l_overflow > 0) { + compute_loop(jcp.ur_w, l_overflow, 0); + add(reg_dsrc, dsrc_shift); + add(reg_ddst, ddst_shift); + inc(oi_iter); + } + + if ((l_overflow <= 0 && n_oi > 0) || (l_overflow > 0 && n_oi > 1)) { + Label ow_loop; + L(ow_loop); { + compute_loop(jcp.ur_w, 0, 0); + add(reg_dsrc, dsrc_shift); + add(reg_ddst, ddst_shift); + inc(oi_iter); + cmp(oi_iter, n_oi); jl(ow_loop, T_NEAR); + } + } + + if (r_overflow1 > 0 ) { + compute_loop(jcp.ur_w, 0, r_overflow1); + add(reg_dsrc, dsrc_shift); + add(reg_ddst, ddst_shift); + } + + if (jcp.ur_w_tail != 0) + compute_loop(jcp.ur_w_tail, 0, r_overflow); + } + + this->postamble(); +} + +status_t jit_avx2_conv_bwd_data_kernel_f32::init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d) +{ + if (!mayiuse(avx2)) return status::unimplemented; + + const bool with_groups = weights_d.ndims() == diff_src_d.ndims() + 1; + + int ndims = diff_src_d.ndims(); + jcp.ndims = ndims; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = diff_src_d.dims()[0]; + + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = diff_src_d.dims()[1] / jcp.ngroups; + + jcp.id = (ndims == 5) ? diff_src_d.dims()[2] : 1; + jcp.ih = (ndims == 3) ? 1 : diff_src_d.dims()[ndims-2]; + jcp.iw = diff_src_d.dims()[ndims-1]; + jcp.od = (ndims == 5) ? diff_dst_d.dims()[2] : 1; + jcp.oh = (ndims == 3) ? 1 : diff_dst_d.dims()[ndims-2]; + jcp.ow = diff_dst_d.dims()[ndims-1]; + + jcp.kd = (ndims == 5) ? weights_d.dims()[with_groups + 2] : 1; + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + ndims - 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.f_pad = (ndims == 5) ? cd.padding[0][0] : 0; + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][ndims-4]; + jcp.l_pad = cd.padding[0][ndims-3]; + + jcp.stride_d = (ndims == 5) ? cd.strides[0] : 1; + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[ndims-4]; + jcp.stride_w = cd.strides[ndims-3]; + + jcp.dilate_d = (ndims == 5) ? cd.dilates[0] : 0; + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[ndims-4]; + jcp.dilate_w = cd.dilates[ndims-3]; + + const int simd_w = 8; + + /* derivatives */ + jcp.idp = jcp.id + 2 * jcp.f_pad; + jcp.ihp = jcp.ih + 2 * jcp.t_pad; + jcp.iwp = jcp.iw + 2 * jcp.l_pad; + jcp.ohp = jcp.oh; /* do we really need */ + jcp.owp = jcp.ow; /* padded output ??? */ + + bool ok_to_pad_channels = true + && jcp.ngroups == 1; + + /* gemm-based convolution performs better in these cases */ + if (jcp.ic < simd_w && jcp.kw > 3 && jcp.stride_w > 1) + return status::unimplemented; + + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + jcp.ic_block = (jcp.ic % simd_w) ? 1 : simd_w; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + jcp.oc_block = simd_w; + if (jcp.oc % jcp.oc_block) return status::unimplemented; + jcp.nb_oc = jcp.oc / jcp.oc_block; + + jcp.ur_h = 1; /* no code-unrolling by h so far */ + jcp.nb_ic_blocking = 1; + jcp.nb_oc_blocking = 1; + jcp.ur_w = 1; + + if(one_of(ndims, 3, 4) && jcp.ow < 40) + jcp.nb_oc_blocking = jcp.ow < 15 ? 4 : 2; + + if (ndims == 3) { + jcp.src_tag = diff_src_d.matches_one_of_tag(nCw8c); + jcp.wei_tag = weights_d.matches_one_of_tag(OIw8i8o, gOIw8o8i); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(nCw8c); + } else if (ndims == 4) { + jcp.src_tag = diff_src_d.matches_one_of_tag(nChw8c); + jcp.wei_tag = weights_d.matches_one_of_tag(OIhw8o8i, gOIhw8o8i); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(nChw8c); + } else if (ndims == 5) { + jcp.src_tag = diff_src_d.matches_one_of_tag(nCdhw8c); + jcp.wei_tag = weights_d.matches_one_of_tag(OIdhw8o8i, gOIdhw8o8i); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(nCdhw8c); + } + + bool args_ok = true + && one_of(jcp.src_tag, nCw8c, nChw8c, nCdhw8c) + && one_of(jcp.wei_tag, gOIw8o8i, OIw8i8o, gOIhw8o8i, OIhw8o8i, + gOIdhw8o8i, OIdhw8o8i) + && one_of(jcp.dst_tag, nCw8c, nChw8c, nCdhw8c) + && jcp.stride_w == jcp.stride_h + && jcp.stride_d == 1 + && jcp.dilate_d == 0 + && jcp.dilate_h == 0 + && jcp.dilate_w == 0 + && jcp.ic % simd_w == 0 + && jcp.oc % simd_w == 0 + && jcp.od == (jcp.idp - jcp.kd) / jcp.stride_d + 1 + && jcp.oh == (jcp.ihp - jcp.kh) / jcp.stride_h + 1 + && jcp.ow == (jcp.iwp - jcp.kw) / jcp.stride_w + 1; + if (!args_ok) return status::unimplemented; + jcp.r_pad = (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad; + jcp.b_pad = (jcp.oh - 1) * jcp.stride_h + jcp.kh - jcp.ih - jcp.t_pad; + int l_overflow = nstl::max(0, (jcp.kw - 1 - jcp.l_pad) / jcp.stride_w); + + const int max_regs = 15; /* Maximun number of registers available for + result accumulation and delta dst data. + One additional register is reserved for weights + data. */ + + /* Find the best blocking with maximum number of fma instructions + per ur_w * nb_ic_blocking compute loops. Number of required registers + is num_regs = ur_w * nb_ic_blocking + ur_w / stride_w <= max_regs. + ur_w must be divisible by stride_w */ + if (jcp.stride_w + 1 > max_regs) /* Minimal possible registers + distribution exceeds max_regs */ + return status::unimplemented; + + int best_nfmas = 0; + for (int b = 1; b <= 4; b++) + { + if (jcp.nb_ic % b != 0) + continue; + + for (int u = jcp.stride_w; + u * b + u / jcp.stride_w <= max_regs && u < jcp.iw + jcp.stride_w; + u += jcp.stride_w) + { + int ur_w = nstl::min(u, jcp.iw); + /* maximum 1 step with l_overflow so far */ + if (l_overflow * jcp.stride_w > ur_w && ur_w != jcp.iw) + continue; + int nfmas = utils::div_up(ur_w, jcp.stride_w) * b; + if (nfmas > best_nfmas + || (nfmas == best_nfmas && jcp.ur_w < ur_w)) { + jcp.ur_w = ur_w; + jcp.nb_ic_blocking = b; + best_nfmas = nfmas; + } + } + } + if (best_nfmas == 0) /* can't find appropriate blocking */ + return status::unimplemented; + + jcp.ur_w_tail = jcp.iw % jcp.ur_w; + + int r_overflow_no_tail = nstl::max(0, (jcp.kw - 1 - jcp.ur_w_tail + - nstl::max(0, jcp.r_pad) - jcp.ur_w_tail) / jcp.stride_w); + /* maximum 1 ur_w block with r_overflow so far */ + if (r_overflow_no_tail * jcp.stride_w > jcp.ur_w) + return status::unimplemented; + + if ((jcp.iw > jcp.ur_w) && (jcp.ur_w % jcp.stride_w != 0)) + return status::unimplemented; + + return status::success; +} + +void jit_avx2_conv_bwd_data_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + UNUSED(scratchpad); + UNUSED(jcp); +} + +void jit_avx2_conv_bwd_weights_kernel_f32::generate() { + this->preamble(); + + mov(reg_input, ptr[this->param1 + GET_OFF(src)]); + mov(reg_output, ptr[this->param1 + GET_OFF(dst)]); + mov(reg_kernel, ptr[this->param1 + GET_OFF(filt)]); + compute_oh_loop_common(); + this->postamble(); +} + +status_t jit_avx2_conv_bwd_weights_kernel_f32::init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &diff_weights_d, + const memory_desc_wrapper &diff_dst_d) { + if (!mayiuse(avx2)) return status::unimplemented; + + const bool with_groups = diff_weights_d.ndims() == src_d.ndims() + 1; + int ndims = src_d.ndims(); + jcp.ndims = ndims; + + jcp.ngroups = with_groups ? diff_weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + jcp.id = (ndims == 5) ? src_d.dims()[2] : 1; + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[ndims-2]; + jcp.iw = src_d.dims()[ndims-1]; + jcp.od = (ndims == 5) ? diff_dst_d.dims()[2] : 1; + jcp.oh = (ndims == 3) ? 1 : diff_dst_d.dims()[ndims-2]; + jcp.ow = diff_dst_d.dims()[ndims-1]; + + jcp.kd = (ndims == 5) ? diff_weights_d.dims()[with_groups + 2] : 1; + jcp.kh = (ndims == 3) ? 1 : diff_weights_d.dims()[with_groups + ndims-2]; + jcp.kw = diff_weights_d.dims()[with_groups + ndims-1]; + + jcp.f_pad = (ndims == 5) ? cd.padding[0][0] : 0; + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][ndims-4]; + jcp.l_pad = cd.padding[0][ndims-3]; + + jcp.stride_d = (ndims == 5) ? cd.strides[0] : 1; + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[ndims-4]; + jcp.stride_w = cd.strides[ndims-3]; + + jcp.dilate_d = (ndims == 5) ? cd.dilates[0] : 0; + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[ndims-4]; + jcp.dilate_w = cd.dilates[ndims-3]; + + if (ndims == 3) { + jcp.src_tag = src_d.matches_one_of_tag(ncw, nwc, nCw8c); + jcp.wei_tag = diff_weights_d.matches_one_of_tag( + Owi8o, gOwi8o, OIw8i8o, gOIw8i8o); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(nCw8c); + } else if (ndims == 4) { + jcp.src_tag = src_d.matches_one_of_tag(nchw, nhwc, nChw8c); + jcp.wei_tag = diff_weights_d.matches_one_of_tag( + Ohwi8o, gOhwi8o, OIhw8i8o, gOIhw8i8o); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(nChw8c); + } else if (ndims == 5) { + jcp.src_tag = src_d.matches_one_of_tag(ncdhw, ndhwc, nCdhw8c); + jcp.wei_tag = diff_weights_d.matches_one_of_tag( + Odhwi8o, gOdhwi8o, OIdhw8i8o, gOIdhw8i8o); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(nCdhw8c); + } + jcp.with_bias = cd.diff_bias_desc.format_kind != format_kind::undef; + + const bool flat = jcp.ic == 3; + const bool mimo = !flat; + + const int simd_w = 8; + + jcp.b_pad = nstl::max( + 0, (jcp.oh - 1) * jcp.stride_h + jcp.kh - jcp.ih - jcp.t_pad); + jcp.r_pad = nstl::max( + 0, (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad); + + int back_pad = nstl::max(0, (jcp.od - 1) * jcp.stride_d + jcp.kd - jcp.id + - jcp.f_pad); + if (ndims == 5) + if (jcp.f_pad != 0 || back_pad != 0) + return status::unimplemented; + + const int max_h_pad = ((jcp.kh - 1) * (jcp.dilate_h + 1) + 1); + const int max_w_pad = ((jcp.kw - 1) * (jcp.dilate_w + 1) + 1); + const bool boundaries_ok = true + && jcp.t_pad < max_h_pad && jcp.b_pad < max_h_pad + && jcp.l_pad < max_w_pad && jcp.r_pad < max_w_pad; + if (!boundaries_ok) + return status::unimplemented; + + bool ok_to_pad_channels = true + && jcp.ngroups == 1; + + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + if (mimo) + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + bool args_ok = true + && IMPLICATION(flat, true + && one_of(jcp.src_tag, ncw, nwc, nchw, nhwc, ncdhw, ndhwc) + && one_of(jcp.wei_tag, Owi8o, gOwi8o, Ohwi8o, gOhwi8o, Odhwi8o, + gOdhwi8o)) + && IMPLICATION(mimo, true + && one_of(jcp.src_tag, nCw8c, nChw8c, nCdhw8c) + && one_of(jcp.wei_tag, OIw8i8o, gOIw8i8o, OIhw8i8o, gOIhw8i8o, + OIdhw8i8o, gOIdhw8i8o)) + && one_of(jcp.dst_tag, nCw8c, nChw8c, nCdhw8c) + && IMPLICATION(mimo, jcp.ic % simd_w == 0) + && jcp.oc % simd_w == 0 + && jcp.kw < 14 + && jcp.kh <= jcp.t_pad + jcp.ih /* [bwd_w:r1] */ + && jcp.kh <= jcp.ih /* [bwd_w:r2] */ + && jcp.kd <= jcp.f_pad + jcp.id + && jcp.kd <= jcp.id + && jcp.t_pad < jcp.kh /* XXX: must fix the kernel! */ + && jcp.dilate_d == 0 + && jcp.dilate_h == 0 + && jcp.dilate_w == 0; + if (!args_ok) return status::unimplemented; + + jcp.ic_block = (jcp.ic % simd_w != 0) ? jcp.ic : simd_w; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + jcp.oc_block = simd_w; + jcp.nb_oc = jcp.oc / jcp.oc_block; + jcp.nb_ic_blocking = jcp.nb_oc_blocking = 1; + + return status::success; +} + +void jit_avx2_conv_bwd_weights_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + if (jcp.with_bias && jcp.oc != jcp.oc_without_padding) + scratchpad.book(key_conv_padded_bias, sizeof(float) * jcp.oc); +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::od_step_comeback_pointers() +{ + Label kd_comeback_loop; + mov(kj, jcp.kd); //FIXME (Anton): this works only if f_pad = back_pad = 0 + L(kd_comeback_loop); { + const int inp_mult = one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? 1 : jcp.ic_block; + sub(aux_reg_input, sizeof(float) * jcp.iw * jcp.ih * inp_mult); + sub(aux_reg_kernel, sizeof(float) * jcp.kw * jcp.kh * jcp.ic_block + * jcp.oc_block); + dec(kj); + cmp(kj, 0); + jg(kd_comeback_loop, T_NEAR); + } +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::oh_step_comeback_pointers() +{ + mov(kj, reg_kh); + Label kh_comeback_loop; + L(kh_comeback_loop); { + const int inp_mult = one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? 1 : jcp.ic_block; + sub(reg_input, sizeof(float) * jcp.iw * inp_mult); + sub(reg_kernel, sizeof(float) * jcp.kw * jcp.ic_block * jcp.oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_comeback_loop, T_NEAR); + } +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::compute_ic_block_step( + int ur_w, int pad_l, int pad_r, int ic_block_step, int input_offset, + int kernel_offset, int output_offset) +{ + const int kw = jcp.kw; + const int ic_block = jcp.ic_block; + const int oc_block = jcp.oc_block; + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + size_t off + = sizeof(float) * (i_kw * ic_block + i_ic) * jcp.oc_block + + kernel_offset; + vmovups(Ymm(i_kw * ic_block_step + i_ic), yword[reg_kernel + off]); + } + + for (int i_ur = 0; i_ur < ur_w; i_ur++) { + vmovups(Ymm(kw * ic_block_step + 0), + yword[reg_output + + sizeof(float) * i_ur * oc_block + output_offset]); + + for (int i_kw = 0; i_kw < kw; i_kw++) { + int i_iw = i_ur * jcp.stride_w + i_kw; + if (i_iw - pad_l < 0 + || i_iw > (ur_w - 1) * jcp.stride_w + kw - 1 - pad_r) + continue; + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + size_t i_off = (size_t)input_offset + sizeof(float)*( + one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? (i_iw - pad_l) + i_ic + * ((size_t)jcp.id * jcp.ih * jcp.iw) + : (i_iw - pad_l) * ic_block + i_ic); + vbroadcastss(Ymm(kw * ic_block_step + 1), + make_safe_addr(reg_input, i_off, reg_long_offt)); + vfmadd231ps(Ymm(i_kw * ic_block_step + i_ic), + Ymm(kw * ic_block_step + 0), + Ymm(kw * ic_block_step + 1)); + } + } + } + + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + size_t off + = sizeof(float) * (i_kw * ic_block + i_ic) * jcp.oc_block + + kernel_offset; + vmovups(yword[reg_kernel + off], + Ymm(i_kw * ic_block_step + i_ic)); + } +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::compute_oh_step_disp() +{ + int ic_block_step; + if (one_of(jcp.src_tag, ncw, nchw, ncdhw)) { + ic_block_step = jcp.kw >= 5 ? 1 : jcp.ic_block; + } else { + ic_block_step = jcp.kw > 7 ? 1 + : jcp.kw > 3 ? 2 + : jcp.kw > 1 ? 4 : 8; + } + + const int max_ur_w = jcp.ow > 56 ? 14 : 28; + + if (jcp.ow <= max_ur_w) + compute_oh_step_unroll_ow(ic_block_step, max_ur_w); + else + compute_oh_step_common(ic_block_step, max_ur_w); + + if (jcp.ndims == 5) { + od_step_comeback_pointers(); + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + } else { + oh_step_comeback_pointers(); + } +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::compute_oh_step_unroll_ow( + int ic_block_step, int max_ur_w) +{ + UNUSED(max_ur_w); + + const int ic_block = jcp.ic_block; + const int oc_block = jcp.oc_block; + int inp_mul = one_of(jcp.src_tag, ncw, nchw, ncdhw) ? 1 : jcp.ic_block; + Label kd_loop; + + const int r_pad + = nstl::max(0, + (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad); + + if (jcp.ndims == 5) { + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + mov(ki, jcp.kd); + L(kd_loop); + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + } + + mov(kj, reg_kh); + Label kh_loop; + L(kh_loop); { + xor_(b_ic, b_ic); + Label ic_block_loop; + L(ic_block_loop); { + compute_ic_block_step(jcp.ow, jcp.l_pad, r_pad, ic_block_step, 0, + 0, 0); + size_t inp_icblk_stride = sizeof(float) * ic_block_step + * (one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? jcp.id*jcp.ih*jcp.iw : 1); + safe_add(reg_input, inp_icblk_stride, reg_long_offt); + add(reg_kernel, sizeof(float) * ic_block_step * oc_block); + add(b_ic, ic_block_step); + cmp(b_ic, ic_block); + jl(ic_block_loop, T_NEAR); + } + if(one_of(jcp.src_tag, ncw, nchw, ncdhw)) { + size_t offt = sizeof(float) * jcp.id * jcp.ih * jcp.iw * ic_block; + safe_sub(reg_input, offt, reg_long_offt); + add(reg_input, sizeof(float) * jcp.iw); + } else { + add(reg_input, sizeof(float) * (jcp.iw - 1) * ic_block); + } + add(reg_kernel, sizeof(float) * (jcp.kw - 1) * ic_block * oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_loop, T_NEAR); + } + + if (jcp.ndims == 5) { + add(aux_reg_input, sizeof(float) * jcp.ih * jcp.iw * inp_mul); + add(aux_reg_kernel, sizeof(float) * jcp.kh * jcp.kw * ic_block + * oc_block); + dec(ki); + cmp(ki, 0); + jg(kd_loop, T_NEAR); + } + +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::compute_oh_step_common( + int ic_block_step, int max_ur_w) +{ + const int ic_block = jcp.ic_block; + const int oc_block = jcp.oc_block; + const int stride_w = jcp.stride_w; + int inp_mul = one_of(jcp.src_tag, ncw, nchw, ncdhw) ? 1 : jcp.ic_block; + Label kd_loop; + + const int r_pad = jcp.r_pad; + + int ur_w = nstl::min(jcp.ow, max_ur_w); + int ur_w_trips = jcp.ow / ur_w; + int ur_w_tail = jcp.ow % ur_w; + if ((ur_w_tail == 0 && r_pad != 0) || r_pad >= ur_w_tail) { + if (ur_w_trips > 1) { + ur_w_tail += ur_w; + ur_w_trips--; + } else { + ur_w_tail += (ur_w - ur_w / 2); + ur_w = ur_w / 2; + } + } + const int inp_mult = one_of(jcp.src_tag, ncw, nchw, ncdhw) ? 1 : ic_block; + + int input_comeback = (ur_w_trips * ur_w * stride_w - jcp.l_pad) * inp_mult; + int output_comeback = ur_w_trips * ur_w * oc_block; + + if (jcp.ndims == 5) { + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + mov(ki, jcp.kd); + L(kd_loop); + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + } + + mov(kj, reg_kh); + Label kh_loop; + L(kh_loop); { + xor_(b_ic, b_ic); + Label ic_block_loop; + L(ic_block_loop); { + if (jcp.l_pad != 0) { + ur_w_trips--; + compute_ic_block_step(ur_w, + jcp.l_pad, 0, ic_block_step, 0, 0, 0); + add(reg_input, sizeof(float) + * (ur_w * stride_w - jcp.l_pad) * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_block); + } + + if (ur_w_trips > 0) { + xor_(reg_ur_w_trips, reg_ur_w_trips); + Label ow_block_loop; + L(ow_block_loop); { + compute_ic_block_step(ur_w, 0, 0, ic_block_step, 0, 0, 0); + add(reg_input, sizeof(float) * ur_w * stride_w * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_block); + + inc(reg_ur_w_trips); + cmp(reg_ur_w_trips, ur_w_trips); + jl(ow_block_loop, T_NEAR); + } + } + + if (ur_w_tail > 0) + compute_ic_block_step(ur_w_tail, + 0, r_pad, ic_block_step, 0, 0, 0); + + sub(reg_input, sizeof(float) * input_comeback); + sub(reg_output, sizeof(float) * output_comeback); + + size_t inp_icblk_stride = sizeof(float) * ic_block_step + * (one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? jcp.id*jcp.ih*jcp.iw : 1); + safe_add(reg_input, inp_icblk_stride, reg_long_offt); + add(reg_kernel, sizeof(float) * ic_block_step * oc_block); + + add(b_ic, ic_block_step); + cmp(b_ic, jcp.ic_block); + jl(ic_block_loop, T_NEAR); + } + if (one_of(jcp.src_tag, ncw, nchw, ncdhw)) { + size_t offt = sizeof(float) * jcp.id * jcp.ih * jcp.iw * ic_block; + safe_sub(reg_input, offt, reg_long_offt); + add(reg_input, sizeof(float) * jcp.iw); + } else { + add(reg_input, sizeof(float) * (jcp.iw - 1) * ic_block); + } + add(reg_kernel, sizeof(float) * (jcp.kw - 1) * ic_block * oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_loop, T_NEAR); + } + + if (jcp.ndims == 5) { + add(aux_reg_input, sizeof(float) * jcp.ih * jcp.iw * inp_mul); + add(aux_reg_kernel, sizeof(float) * jcp.kh * jcp.kw * ic_block + * oc_block); + dec(ki); + cmp(ki, 0); + jg(kd_loop, T_NEAR); + } + +} + +inline void jit_avx2_conv_bwd_weights_kernel_f32::compute_oh_loop_common() +{ + const int icoc_block = jcp.ic_block * jcp.oc_block; + const int t_pad = jcp.t_pad; + const int stride_h = jcp.stride_h; + const int inp_mult = one_of(jcp.src_tag, ncw, nchw, ncdhw) + ? 1 : jcp.ic_block; + int b_pad = jcp.b_pad; + + Label oh_tpad_loop, oh_loop, oh_loop_end; + + mov(reg_kh, jcp.kh); + xor_(reg_ih_count, reg_ih_count); + xor_(reg_oj, reg_oj); + if (t_pad > 0) { + assert(jcp.kh <= t_pad + jcp.ih); /* [bwd_w:r1] */ + mov(reg_kh, jcp.kh <= t_pad + jcp.ih ? jcp.kh - t_pad : jcp.ih); + add(reg_kernel, sizeof(float) * t_pad * jcp.kw * icoc_block); + + L(oh_tpad_loop); { + compute_oh_step_disp(); + add(reg_output, sizeof(float) * jcp.ow * jcp.oc_block); + sub(reg_kernel, sizeof(float) * stride_h * jcp.kw * icoc_block); + + inc(reg_oj); + add(reg_ih_count, stride_h); + add(reg_kh, stride_h); + + /* the overlap between input and kernel may not reach kernel size. + * so far we do not support that (until we put constant here) */ + const int final_inp_ker_overlap = jcp.kh; /* [bwd_w:r2] */ + cmp(reg_kh, final_inp_ker_overlap); + jl(oh_tpad_loop, T_NEAR); + } + + if (t_pad % stride_h != 0) { + int inp_corr = stride_h - t_pad % stride_h; + add(reg_kernel, sizeof(float) * inp_corr * jcp.kw * icoc_block); + add(reg_input, sizeof(float) * inp_corr * jcp.iw * inp_mult); + } + } + cmp(reg_ih_count, jcp.ih + t_pad - jcp.kh + 1); + jge(oh_loop_end, T_NEAR); + cmp(reg_oj, jcp.oh); + jge(oh_loop, T_NEAR); + + mov(reg_kh, jcp.kh); + L(oh_loop); { + compute_oh_step_disp(); + add(reg_input, sizeof(float) * stride_h * jcp.iw * inp_mult); + add(reg_output, sizeof(float) * jcp.ow * jcp.oc_block); + + inc(reg_oj); + add(reg_ih_count, stride_h); + + cmp(reg_ih_count, jcp.ih + t_pad - jcp.kh + 1); + jge(oh_loop_end, T_NEAR); + + cmp(reg_oj, jcp.oh); + jl(oh_loop, T_NEAR); + } + L(oh_loop_end); + if (b_pad > 0) { + Label oh_bpad_loop, oh_bpad_loop_end; + cmp(reg_oj, jcp.oh); + jge(oh_bpad_loop_end, T_NEAR); + + mov(reg_kh, jcp.ih + t_pad); + sub(reg_kh, reg_ih_count); + L(oh_bpad_loop); { + compute_oh_step_disp(); + add(reg_input, sizeof(float) * stride_h * jcp.iw * inp_mult); + add(reg_output, sizeof(float) * jcp.ow * jcp.oc_block); + + sub(reg_kh, stride_h); + cmp(reg_kh, 0); + jle(oh_bpad_loop_end, T_NEAR); + + inc(reg_oj); + cmp(reg_oj, jcp.oh); + jl(oh_bpad_loop, T_NEAR); + } + L(oh_bpad_loop_end); + } +} + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.hpp new file mode 100644 index 000000000000..412c50c9ee2d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.hpp @@ -0,0 +1,225 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX2_CONV_KERNEL_F32_HPP +#define JIT_AVX2_CONV_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "cpu_memory.hpp" +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx2_conv_fwd_kernel_f32: public jit_generator { + jit_avx2_conv_fwd_kernel_f32(jit_conv_conf_t ajcp, + const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32(this, + jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_conv_call_s *))this->getCode(); + } + + ~jit_avx2_conv_fwd_kernel_f32() { + delete eltwise_injector_; + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx2_conv_fwd_kernel_f32) + + static bool post_ops_ok(jit_conv_conf_t &jcp, + const primitive_attr_t &attr); + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + reg64_t reg_input = rax; + reg64_t aux_reg_input = r8; + reg64_t reg_kernel = rdx; + reg64_t aux_reg_kernel = r9; + reg64_t reg_output = rsi; + reg64_t reg_bias = rbx; + + reg64_t aux_reg_inp_d = r11; + reg64_t aux_reg_ker_d = abi_not_param1; + + reg64_t reg_ki = rsi; + reg64_t kj = r10; + reg64_t oi_iter = r11; + reg64_t ki_iter = r12; + reg64_t reg_kh = abi_not_param1; + reg64_t reg_oc_blocks = r14; + reg64_t imm_addr64 = r15; + reg64_t reg_long_offt = r15; + Xbyak::Reg32 reg_ci_flag = r13d; + + Xbyak::Ymm ytmp = Xbyak::Ymm(14); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + inline void oh_step_unroll_kw(int ur_w, int pad_l, int pad_r, + int oc_blocks); + inline void oh_step_nopad(int ur_w, int pad_l, int pad_r, + char pad_label, int oc_blocks, char oc_blocks_label); + inline void width_blk_step(int ur_w, int pad_l, int pad_r, + char pad_label, int oc_blocks, char oc_blocks_label); + inline void solve_common(int oc_blocks, char oc_blocks_label); + + void generate(); +}; + +struct jit_avx2_conv_bwd_data_kernel_f32: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx2_conv_bwd_data_kernel_f32) + + jit_avx2_conv_bwd_data_kernel_f32(jit_conv_conf_t ajcp): jcp(ajcp) + { + this->generate(); + jit_ker = (void (*)(jit_conv_call_s *))this->getCode(); + } + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + + reg64_t reg_ddst = rax; + reg64_t aux_reg_ddst = r8; + reg64_t reg_kernel = rdx; + reg64_t aux_reg_kernel = r10; + reg64_t reg_dsrc = rsi; + reg64_t aux_reg_ddst_oc_loop = rbx; // used in ndims < 5 case only + reg64_t aux_reg_kernel_oc_loop = abi_not_param1; /* used in ndims < 5 + case only */ + + reg64_t aux_reg_dst_d = r12; // used in ndims == 5 case only + reg64_t aux_reg_ker_d = r14; // used in ndims == 5 case only + + reg64_t reg_ki = abi_not_param1; // used in ndims == 5 case only + reg64_t kj = r11; + reg64_t oi_iter = r12; + reg64_t reg_kh = r14; + reg64_t reg_channel = r13; // used in ndims < 5 case only + reg64_t reg_channel_work = r9; // used in ndims < 5 case only + reg64_t reg_long_offt = r15; + + inline void compute_loop(int ur_w, int l_overflow, int r_overflow); + + void generate(); + + inline int get_iw_start(int ki, int l_overflow) + { + int res = (jcp.iw - 1 + jcp.r_pad) % jcp.stride_w + + l_overflow * jcp.stride_w + - (jcp.kw - 1 - ki) * (jcp.dilate_w + 1); + while (res < 0) + res += jcp.stride_w; + + return res; + } + + inline int get_iw_end(int ur_w, int ki, int r_overflow) + { + if (utils::one_of(ur_w, jcp.iw, jcp.ur_w_tail)) + ur_w += nstl::min(0, jcp.r_pad); // remove negative padding + int res = (ur_w - 1 + jcp.l_pad) % jcp.stride_w + + r_overflow * jcp.stride_w - ki * (jcp.dilate_w + 1); + while (res < 0) + res += jcp.stride_w; + + return ur_w - res; + } +}; + +struct jit_avx2_conv_bwd_weights_kernel_f32: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx2_conv_bwd_weights_kernel_f32) + + jit_avx2_conv_bwd_weights_kernel_f32(jit_conv_conf_t ajcp): jcp(ajcp) + { + this->generate(); + jit_ker = (void (*)(jit_conv_call_s *))this->getCode(); + } + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &diff_weights_d, + const memory_desc_wrapper &diff_dst_d); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + reg64_t reg_input = rax; + reg64_t reg_kernel = rdx; + reg64_t reg_output = rsi; + reg64_t b_ic = abi_not_param1; + reg64_t kj = r8; + reg64_t reg_kh = r9; + reg64_t reg_ur_w_trips = r10; + reg64_t reg_tmp = r11; + reg64_t reg_oj = r15; + reg64_t reg_ih_count = rbx; + reg64_t aux_reg_input = r12; + reg64_t aux_reg_kernel = r13; + reg64_t ki = r14; + reg64_t reg_long_offt = r11; + + inline void od_step_comeback_pointers(); + inline void oh_step_comeback_pointers(); + inline void compute_ic_block_step(int ur_w, int pad_l, int pad_r, + int ic_block_step, int input_offset, int kernel_offset, + int output_offset); + inline void compute_oh_step_disp(); + inline void compute_oh_step_unroll_ow(int ic_block_step, int max_ur_w); + inline void compute_oh_step_common(int ic_block_step, int max_ur_w); + inline void compute_oh_loop_common(); + + void generate(); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.cpp new file mode 100644 index 000000000000..13f61e84fef2 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.cpp @@ -0,0 +1,410 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx2_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +#define src_blk_off(f, n, c, d, h, w) \ + (pd()->ndims() == 3) \ + ? (f).blk_off(n, c, w) \ + : (pd()->ndims() == 4) \ + ? (f).blk_off(n, c, h, w) \ + : (f).blk_off(n, c, d, h, w) + +#define wht_blk_off_(f, g, ...) \ + pd()->with_groups() ? (f).blk_off(g, __VA_ARGS__) : (f).blk_off(__VA_ARGS__) +#define wht_blk_off(f, g, oc, ic, kd, kh, kw) \ + (pd()->ndims() == 3) \ + ? wht_blk_off_(f, g, oc, ic, kw) \ + : (pd()->ndims() == 4) \ + ? wht_blk_off_(f, g, oc, ic, kh, kw) \ + : wht_blk_off_(f, g, oc, ic, kd, kh, kw) + +void jit_avx2_convolution_fwd_t::execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const auto &jcp = kernel_->jcp; + + int ocb_work = div_up(jcp.nb_oc, jcp.nb_oc_blocking); + const size_t work_amount = jcp.mb * jcp.ngroups * ocb_work * jcp.od + * jcp.oh; + + auto ker = [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + int icbb = 0; + while (icbb < jcp.nb_ic) { + int icb_step = jcp.nb_ic_blocking; + int icb_step_rem = jcp.nb_ic - icbb; + if (icb_step_rem < jcp.nb_ic_blocking_max) + icb_step = icb_step_rem; + + size_t n{0}, g{0}, ocbb{0}, oh{0}, od{0}; + nd_iterator_init(start, n, jcp.mb, g, jcp.ngroups, ocbb, ocb_work, + od, jcp.od, oh, jcp.oh); + for (size_t iwork = start; iwork < end; ++iwork) { + int ocb = ocbb * jcp.nb_oc_blocking; + int ocb_num = jcp.nb_oc_blocking; + + for (int icb = icbb; icb < icbb + icb_step; ++icb) { + auto par_conv = jit_conv_call_s(); + + const int ij = oh * jcp.stride_h; + const int i_t_overflow = nstl::max(0, jcp.t_pad - ij); + const int i_b_overflow = nstl::max(jcp.ih, ij + + (jcp.kh-1) * (jcp.dilate_h+1) - jcp.t_pad+1) - jcp.ih; + + const int dj = od * jcp.stride_d; + const int d_t_overflow = nstl::max(0, jcp.f_pad - dj); + const int d_b_overflow = nstl::max(jcp.id, dj + + (jcp.kd-1) * (jcp.dilate_d+1) - jcp.f_pad+1) - jcp.id; + + const size_t _oc = g * jcp.nb_oc + ocb; + const size_t _ic = g * jcp.nb_ic * jcp.nonblk_group_off + icb; + + const int ih = nstl::max(ij - jcp.t_pad + + div_up(i_t_overflow, + (jcp.dilate_h+1)) * (jcp.dilate_h + 1), 0); + + const int id = nstl::max(dj - jcp.f_pad + + div_up(d_t_overflow, + (jcp.dilate_d+1)) * (jcp.dilate_d + 1), 0); + + par_conv.src = &src[src_blk_off(src_d, n, + jcp.ic == 3 ? 0 : _ic, id, ih, 0)]; + + par_conv.dst = &dst[src_blk_off(dst_d, n, _oc, od, oh, 0)]; + + const int wh = div_up(i_t_overflow, (jcp.dilate_h + 1)); + const int wd = div_up(d_t_overflow, (jcp.dilate_d + 1)); + par_conv.filt = &weights[wht_blk_off(weights_d, g, ocb, + jcp.ic == 3 ? 0 : icb, wd, wh, 0)]; + + if (icb == 0) { + if (bias) + par_conv.bias = + &bias[bias_d.blk_off(_oc * jcp.oc_block)]; + par_conv.flags |= FLAG_IC_FIRST; + } + + if (jcp.with_eltwise && icb + 1 == jcp.nb_ic) { + par_conv.flags |= FLAG_IC_LAST; + } + + par_conv.oc_blocks = + nstl::min(ocb + ocb_num, jcp.nb_oc) - ocb; + + par_conv.kw_padding = 0; + const int kh_padding = jcp.kh + - div_up(i_t_overflow, (jcp.dilate_h + 1)) + - div_up(i_b_overflow, (jcp.dilate_h + 1)); + par_conv.kh_padding = nstl::max(0, kh_padding); + + const int kd_padding = jcp.kd + - div_up(d_t_overflow, (jcp.dilate_d + 1)) + - div_up(d_b_overflow, (jcp.dilate_d + 1)); + par_conv.kd_padding = nstl::max(0, kd_padding); + + kernel_->jit_ker(&par_conv); + } + nd_iterator_step(n, jcp.mb, g, jcp.ngroups, ocbb, ocb_work, + od, jcp.od, oh, jcp.oh); + } + icbb += icb_step; + } + }; + + if (pd()->wants_padded_bias()) { + auto padded_bias = scratchpad(ctx).get(key_conv_padded_bias); + utils::array_copy(padded_bias, bias, jcp.oc_without_padding); + utils::array_set(padded_bias + jcp.oc_without_padding, 0.f, + jcp.oc - jcp.oc_without_padding); + bias = padded_bias; + } + + parallel(0, ker); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); +} + +void jit_avx2_convolution_bwd_data_t::execute_backward_data( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + + int icb_work = jcp.nb_ic / jcp.nb_ic_blocking; + int ih_block_size = jcp.ih; + int num_ih_blocks = utils::div_up(jcp.ih, ih_block_size); + size_t work_amount = jcp.mb * jcp.ngroups * icb_work * num_ih_blocks; + if (work_amount < (size_t)2 * mkldnn_get_max_threads()) { + ih_block_size = 1; + num_ih_blocks = utils::div_up(jcp.ih, ih_block_size); + work_amount *= num_ih_blocks; + } + + auto ker = [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + size_t n{0}, g{0}, icbb{0}, ihb{0}; + nd_iterator_init(start, n, jcp.mb, g, jcp.ngroups, icbb, icb_work, + ihb, num_ih_blocks); + for (size_t iwork = start; iwork < end; ++iwork) { + for (int oc = 0; oc < jcp.nb_oc; oc += jcp.nb_oc_blocking) + for (int id = 0; id < jcp.id; ++id) { + auto par_conv = jit_conv_call_s(); + + const int idp = jcp.id + 2 * jcp.f_pad; + const int d_t_overflow = nstl::max(0, + jcp.kd - 1 - id - jcp.f_pad); + const int back_pad = idp - jcp.id - jcp.f_pad; + const int d_b_overflow = nstl::max(0, + jcp.kd - 1 - (jcp.id - 1 - id) - back_pad); + const int od = id + jcp.f_pad - d_b_overflow; + + int ih_start = ihb * ih_block_size; + int ih_end = nstl::min(jcp.ih, ih_start + ih_block_size); + for (int ih = ih_start; ih < ih_end; ++ih) { + + const int i_t_overflow = nstl::max(0, (jcp.kh - 1 + - ih - jcp.t_pad) / jcp.stride_h); + const int i_b_overflow = nstl::max(0, (jcp.kh - jcp.ih + + ih - jcp.b_pad) / jcp.stride_h); + int overflow_kh_hi = jcp.kh - 1 - abs((jcp.ih - 1 + + jcp.b_pad - ih) % jcp.stride_h); + int overflow_kh_lo = (ih + jcp.t_pad) % jcp.stride_h; + + par_conv.kd_padding = jcp.kd - d_t_overflow - d_b_overflow; + par_conv.kh_padding = (overflow_kh_hi - overflow_kh_lo) + / jcp.stride_h + 1 - i_t_overflow - i_b_overflow; + par_conv.kw_padding = 0; + + const int k_lo = overflow_kh_lo + + i_b_overflow * jcp.stride_h; + const int oh = (ih + jcp.t_pad - k_lo) / jcp.stride_h; + + par_conv.src = &diff_src[src_blk_off(diff_src_d, n, + /*jcp.ic == 3 ? 0 :*/ + g * jcp.nb_ic + jcp.nb_ic_blocking * icbb, id, ih, 0)]; + par_conv.dst = &diff_dst[src_blk_off(diff_dst_d, + n, g * jcp.nb_oc + oc, od, oh, 0)]; + par_conv.filt = &weights[wht_blk_off(weights_d, g, oc, + jcp.ic == 3 ? 0 : jcp.nb_ic_blocking * icbb, + d_b_overflow, k_lo, 0)]; + + par_conv.src_prf = nullptr; + par_conv.dst_prf = nullptr; + par_conv.filt_prf = nullptr; + par_conv.channel = oc; + par_conv.ch_blocks = nstl::min(jcp.nb_oc - oc, + jcp.nb_oc_blocking); + + kernel_->jit_ker(&par_conv); + } + } + nd_iterator_step(n, jcp.mb, g, jcp.ngroups, icbb, icb_work, ihb, + num_ih_blocks); + } + }; + + parallel(0, ker); +} + +void jit_avx2_convolution_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias_in = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + auto scratchpad = this->scratchpad(ctx); + + data_t *diff_bias = pd()->wants_padded_bias() + ? scratchpad.get(key_conv_padded_bias) : diff_bias_in; + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + + const auto &jcp = kernel_->jcp; + + auto reducer_bia_scratchpad = memory_tracking::grantor_t(scratchpad, + prefix_reducer_bia); + auto rb = this->reducer_bias_; + rb->init(reducer_bia_scratchpad); + + auto reducer_wei_scratchpad = memory_tracking::grantor_t(scratchpad, + prefix_reducer_wei); + auto rw = this->reducer_weights_; + rw->init(reducer_wei_scratchpad); + + auto ker = [&](int ithr, int nthr) { + assert(nthr == rw->balancer().nthr_); + + const int w_job_start = rw->balancer().ithr_job_off(ithr); + const int w_njobs = rw->balancer().ithr_njobs(ithr); + + if (w_njobs == 0) return; + + /* reduction dimension */ + int img_od_start{0}, img_od_end{0}, img{0}, od_s{0}; + balance211(jcp.mb * jcp.od, rw->balancer().nthr_per_group_, + rw->balancer().id_in_group(ithr), img_od_start, img_od_end); + + int img_start = img_od_start, img_end = img_od_end; + nd_iterator_init(img_start, img, jcp.mb, od_s, jcp.od); + const int img_first = img; + + /* jobs */ + int g_start{0}, ocb_start{0}, icb_start{0}; + nd_iterator_init(w_job_start, g_start, jcp.ngroups, ocb_start, + jcp.nb_oc, icb_start, jcp.nb_ic); + + while (img_start < img_end) { + int g = g_start, ocb = ocb_start, icb = icb_start; + + const int work_rem = img_end - img_start; + const int od_e = od_s + work_rem > jcp.od ? jcp.od : od_s + work_rem; + const int id_s = od_s * jcp.stride_d; + const int idp = jcp.id + jcp.f_pad + jcp.back_pad; + + if (id_s < idp - jcp.back_pad - jcp.kd + 1) + for (int w_job_loc = 0; w_job_loc < w_njobs; ++w_job_loc) { + const size_t _oc = g * jcp.nb_oc + ocb; + const size_t _ic = g * jcp.nb_ic + icb; + + /* TODO: put dw <-- 0 in kernel */ + if (img == img_first) + array_set(rw->get_local_ptr(ithr, diff_weights, + reducer_wei_scratchpad) + + w_job_loc * rw->balancer().job_size_, 0, + rw->balancer().job_size_); + + for (int od = od_s; od < od_e; ++od) { + const int id = od * jcp.stride_d; + if (id >= jcp.id - jcp.back_pad - jcp.kd + 1) break; + + auto par_conv = jit_conv_call_s(); + par_conv.src = &src[src_blk_off(src_d, img, _ic, id, 0, 0)]; + par_conv.dst = + &diff_dst[src_blk_off(diff_dst_d, img, _oc, od, 0, 0)]; + par_conv.filt = rw->get_local_ptr(ithr, diff_weights, + reducer_wei_scratchpad) + + w_job_loc * rw->balancer().job_size_; + + kernel_->jit_ker(&par_conv); + } + nd_iterator_step(g, jcp.ngroups, ocb, jcp.nb_oc, icb, + jcp.nb_ic); + } + nd_iterator_jump(img_start, img_end, img, jcp.mb, od_s, jcp.od); + } + rw->reduce(ithr, diff_weights, reducer_wei_scratchpad); + }; + + auto ker_bias = [&](int ithr, int nthr) { + assert(nthr == rb->balancer().nthr_); + + const int b_job_start = rb->balancer().ithr_job_off(ithr); + const int b_njobs = rb->balancer().ithr_njobs(ithr); + + if (b_njobs == 0) return; + + /* reduction dimension */ + int img_start{0}, img_end{0}; + balance211(jcp.mb, rb->balancer().nthr_per_group_, + rb->balancer().id_in_group(ithr), img_start, img_end); + + /* jobs */ + int g_start{0}, ocb_start{0}; + nd_iterator_init(b_job_start, g_start, jcp.ngroups, ocb_start, + jcp.nb_oc); + + for (int img = img_start; img < img_end; ++img) { + int g = g_start, ocb = ocb_start; + for (int b_job_loc = 0; b_job_loc < b_njobs; ++b_job_loc) { + const size_t _oc = g * jcp.nb_oc + ocb; + + const data_t *d_dst = &diff_dst[diff_dst_d.blk_off(img, _oc)]; + data_t *d_bias = rb->get_local_ptr(ithr, diff_bias, + reducer_bia_scratchpad) + + b_job_loc * rb->balancer().job_size_; + + if (img == img_start) + for (int o = 0; o < 8; ++o) + d_bias[o] = 0.; + + for (int dhw = 0; dhw < jcp.od * jcp.oh * jcp.ow; ++dhw) { + PRAGMA_OMP_SIMD() + for (int o = 0; o < 8; ++o) + d_bias[o] += d_dst[o]; + d_dst += 8; + } + + nd_iterator_step(g, jcp.ngroups, ocb, jcp.nb_oc); + } + } + rb->reduce(ithr, diff_bias, reducer_bia_scratchpad); + }; + + parallel(0, [&](const int ithr, const int nthr) { + ker(ithr, nthr); + if (pd()->with_bias()) + ker_bias(ithr, nthr); + }); + + /* TODO: put this in ker_bias */ + if (pd()->wants_padded_bias()) { + assert(jcp.ngroups == 1); + for (int oc = 0; oc < jcp.oc_without_padding; ++oc) + diff_bias_in[oc] = diff_bias[oc]; + } +} + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.hpp new file mode 100644 index 000000000000..bb65bce79cde --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx2_convolution.hpp @@ -0,0 +1,302 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX2_CONVOLUTION_HPP +#define CPU_JIT_AVX2_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_reducer.hpp" + +#include "jit_avx2_conv_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx2_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx2, ""), + jit_avx2_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx2_conv_fwd_kernel_f32::init_conf(jcp_, + *desc(), src_md(), weights_md(), dst_md(), *attr()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx2_conv_fwd_kernel_f32::init_scratchpad(scratchpad, jcp_); + + return status::success; + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + const bool flat = IC() < 8; + auto src_tag = flat + ? utils::pick(ndims() - 3, ncw, nchw, ncdhw) + : utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto dst_tag = + utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(2 * ndims() - 6 + flat, gOIw8i8o, gOwi8o, + gOIhw8i8o, gOhwi8o, gOIdhw8i8o, gOdhwi8o) + : utils::pick(2 * ndims() - 6 + flat, OIw8i8o, Owi8o, + OIhw8i8o, Ohwi8o, OIdhw8i8o, Odhwi8o); + + return set_default_formats_common(src_tag, wei_tag, dst_tag); + } + }; + + jit_avx2_convolution_fwd_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_avx2_conv_fwd_kernel_f32(pd()->jcp_, *pd()->attr()); } + ~jit_avx2_convolution_fwd_t() { delete kernel_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx2_conv_fwd_kernel_f32 *kernel_; +}; + +struct jit_avx2_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx2, ""), + jit_avx2_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::undef, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx2_conv_bwd_data_kernel_f32::init_conf( + jcp_, *desc(), *diff_src_md(), *weights_md(), + *diff_dst_md()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx2_conv_bwd_data_kernel_f32::init_scratchpad(scratchpad, + jcp_); + + return status::success; + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, gOIw8o8i, gOIhw8o8i, gOIdhw8o8i) + : utils::pick(ndims() - 3, OIw8o8i, OIhw8o8i, OIdhw8o8i); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + jit_avx2_convolution_bwd_data_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_avx2_conv_bwd_data_kernel_f32(pd()->jcp_); } + ~jit_avx2_convolution_bwd_data_t() { delete kernel_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx2_conv_bwd_data_kernel_f32 *kernel_; +}; + +struct jit_avx2_convolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx2, ""), + jit_avx2_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx2_conv_bwd_weights_kernel_f32::init_conf( + jcp_, *desc(), *src_md(), *diff_weights_md(), + *diff_dst_md()); + if (status != status::success) return status; + + init_balancers(); + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx2_conv_bwd_weights_kernel_f32::init_scratchpad(scratchpad, + jcp_); + + auto reducer_bia_scratchpad = memory_tracking::registrar_t( + scratchpad, memory_tracking::names::prefix_reducer_bia); + reducer_bia_conf_.init_scratchpad(reducer_bia_scratchpad); + + auto reducer_wei_scratchpad = memory_tracking::registrar_t( + scratchpad, memory_tracking::names::prefix_reducer_wei); + reducer_wei_conf_.init_scratchpad(reducer_wei_scratchpad); + + return status::success; + } + + jit_conv_conf_t jcp_; + cpu_reducer_t::conf_t reducer_bia_conf_; + cpu_reducer_t::conf_t reducer_wei_conf_; + + protected: + bool set_default_formats() { + using namespace format_tag; + const bool flat = IC() == 3; + + auto src_tag = flat + ? utils::pick(ndims() - 3, ncw, nchw, ncdhw) + : utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto dst_tag = + utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(2 * ndims() - 6 + flat, gOIw8i8o, gOwi8o, + gOIhw8i8o, gOhwi8o, gOIdhw8i8o, gOdhwi8o) + : utils::pick(2 * ndims() - 6 + flat, OIw8i8o, Owi8o, + OIhw8i8o, Ohwi8o, OIdhw8i8o, Odhwi8o); + + return set_default_formats_common(src_tag, wei_tag, dst_tag); + } + + private: + void init_balancers() { + const int max_threads = mkldnn_get_max_threads(); + const size_t max_buffer_size = 1<<21; /* just a heuristic */ + + if(with_bias()) { + reducer_bia_conf_.init(reduce_balancer_t(max_threads, + jcp_.oc_block, jcp_.ngroups * jcp_.nb_oc, jcp_.mb, + max_buffer_size)); + } + + reducer_wei_conf_.init(reduce_balancer_t(max_threads, + jcp_.kd * jcp_.kh * jcp_.kw + * jcp_.ic_block * jcp_.oc_block, + jcp_.ngroups * jcp_.nb_ic * jcp_.nb_oc, + jcp_.mb * jcp_.od, max_buffer_size)); + } + }; + + jit_avx2_convolution_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr) + , reducer_weights_(nullptr) + , reducer_bias_(nullptr) + { + kernel_ = new jit_avx2_conv_bwd_weights_kernel_f32(pd()->jcp_); + reducer_bias_ = + new cpu_reducer_t(pd()->reducer_bia_conf_); + reducer_weights_ = + new cpu_reducer_t(pd()->reducer_wei_conf_); + } + + ~jit_avx2_convolution_bwd_weights_t() { + delete kernel_; + delete reducer_weights_; + delete reducer_bias_; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx2_conv_bwd_weights_kernel_f32 *kernel_; + cpu_reducer_t *reducer_weights_, *reducer_bias_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.cpp new file mode 100644 index 000000000000..635b83b2bf5f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.cpp @@ -0,0 +1,1255 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_memory.hpp" +#include "cpu_barrier.hpp" + +#include "jit_uni_1x1_conv_utils.hpp" +#include "jit_avx512_common_1x1_conv_kernel.hpp" + +#define GET_OFF(field) offsetof(jit_1x1_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +void jit_avx512_common_1x1_conv_kernel::bcast_loop(int load_loop_blk) +{ + mov(aux1_reg_bcast_data, reg_bcast_data); + mov(aux_reg_bcast_data, reg_bcast_data); + + mov(aux_reg_output_data, reg_output_data); + mov(bcast_loop_iter, EVEX_compress_addr(rsp, bcast_loop_work_offt)); + + if (jcp.ver == ver_4fma) + { + Label bcast_loop; + Label bcast_loop_wraparound; + Label bcast_loop_out; + Label bcast_loop_ur_full; + + cmp(bcast_loop_iter, jcp.ur); + jle(bcast_loop_wraparound, T_NEAR); + + L(bcast_loop); { + assert(jcp.bcast_block % jcp.ur == 0); + int num_substeps = jcp.bcast_block / jcp.ur; + assert(num_substeps > 0 && num_substeps < 10); + for (int i = 0; i < num_substeps; i++) { + reduce_loop(load_loop_blk, jcp.ur, i, false); + if (i < num_substeps - 1) { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_substep); + } + else { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_step + - (num_substeps - 1) * jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_step + - (num_substeps - 1) * jcp.bcast_loop_output_substep); + } + } + sub(bcast_loop_iter, jcp.bcast_block); + cmp(bcast_loop_iter, jcp.bcast_block); + jg(bcast_loop, T_NEAR); + } + + L(bcast_loop_wraparound); + if (jcp.ur_tail) { + je(bcast_loop_ur_full, T_NEAR); + reduce_loop(load_loop_blk, jcp.ur_tail, 0, true); + jmp(bcast_loop_out, T_NEAR); + } + L(bcast_loop_ur_full); + reduce_loop(load_loop_blk, jcp.ur, 0, true); + L(bcast_loop_out); + } + else + { + Label bcast_loop; + Label bcast_loop_tail; + + cmp(bcast_loop_iter, jcp.ur); + jl(bcast_loop_tail, T_NEAR); + + L(bcast_loop); { + assert(jcp.bcast_block % jcp.ur == 0); + int num_substeps = jcp.bcast_block / jcp.ur; + assert(num_substeps > 0 && num_substeps < 10); + for (int i = 0; i < num_substeps; i++) { + reduce_loop(load_loop_blk, jcp.ur, i, false); + if (i < num_substeps - 1) { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_substep); + } + else { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_step + - (num_substeps - 1) * jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_step + - (num_substeps - 1) * jcp.bcast_loop_output_substep); + } + } + sub(bcast_loop_iter, jcp.bcast_block); + cmp(bcast_loop_iter, jcp.bcast_block); + jge(bcast_loop, T_NEAR); + } + + L(bcast_loop_tail); + if (jcp.ur_tail) { + Label bcast_loop_tail_out; + cmp(bcast_loop_iter, 0); + jz(bcast_loop_tail_out, T_NEAR); + reduce_loop(load_loop_blk, jcp.ur_tail, 0, true); + L(bcast_loop_tail_out); + } + } +} + +void jit_avx512_common_1x1_conv_kernel::reduce_loop(int load_loop_blk, + int ur, int substep, bool wraparound) +{ + auto vreg_load = [=](int i_load, int i_fma) { + return Zmm(utils::rnd_up(ur * load_loop_blk, jcp.fma_step) + + jcp.fma_step * i_load + i_fma); + }; + + auto vreg_accum = [=](int i_load, int i_ur) { + return Zmm(i_ur * load_loop_blk + i_load); + }; + + auto bias_ptr = [=](int i_load) { + return EVEX_compress_addr(reg_bias_data, + jcp.typesize_out * jcp.oc_block * i_load); + }; + + auto bcast_ptr = [=](int i_reduce, int i_ur, bool bcast) { + assert(i_ur < jcp.ur); + assert(i_reduce <= jcp.reduce_loop_unroll); + int offt; + if (one_of(jcp.prop_kind, forward_training, forward_inference, + backward_data)) { + assert(jcp.reduce_loop_unroll == jcp.reduce_block); + offt = (i_reduce == jcp.reduce_loop_unroll) + ? (jcp.bcast_dim + i_ur) * jcp.reduce_loop_unroll + : i_ur * jcp.reduce_loop_unroll + i_reduce; + } else { + if (jcp.transpose_src) { + const int reduce_group = i_reduce / 4; + const int reduce_shift = i_reduce % 4; + offt = 4 * (reduce_group * jcp.ic_block + i_ur) + reduce_shift; + } + else + offt = i_reduce * jcp.ic_block + i_ur; + } + return EVEX_compress_addr(aux_reg_bcast_data, jcp.typesize_in * offt, + bcast); + }; + + auto load_ptr = [=](int i_reduce, int i_load) { + int offt; + int u0 = i_reduce % jcp.reduce_loop_unroll; + int u1 = i_reduce / jcp.reduce_loop_unroll; + offt = (i_load * jcp.reduce_dim + u0) * jcp.load_block; + return EVEX_compress_addr(aux_reg_load_data, + u1 * jcp.reduce_loop_load_step + + jcp.typesize_in * offt); + }; + + auto output_ptr = [=](int i_load, int i_ur) { + if (one_of(jcp.prop_kind, forward_training, forward_inference, + backward_data)) + return EVEX_compress_addr(aux_reg_output_data, + (i_load * jcp.bcast_dim + i_ur) * jcp.load_block + * jcp.typesize_out); + else + return ptr[aux_reg_output_data + + (i_load + ? reg_output_stride * i_load + : 0) // TODO: Xbyak should allow 0 scale + + jcp.typesize_out * jcp.load_block * i_ur]; + }; + + auto init = [=]() { + Label init_done; + Label init_zero; + + if (jcp.with_sum) { + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + for (int i_ur = 0; i_ur < ur; ++i_ur) { + mic_prefetcht1(output_ptr(i_load, i_ur)); + } + } + } + + if (jcp.with_bias + && one_of(jcp.prop_kind, forward_training, forward_inference)) { + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jz(init_zero, T_NEAR); + + for (int i_load = 0; i_load < load_loop_blk; i_load++) + for (int i_ur = 0; i_ur < ur; ++i_ur) + vmovups(vreg_accum(i_load, i_ur), bias_ptr(i_load)); + jmp(init_done, T_NEAR); + } + + L(init_zero); + for (int i_load = 0; i_load < load_loop_blk; ++i_load) + for (int i_ur = 0; i_ur < ur; ++i_ur) { + auto r = vreg_accum(i_load, i_ur); + vpxord(r, r, r); + } + L(init_done); + }; + + auto store = [=]() { + Label store_noadd; + if (!jcp.with_sum) { + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jnz(store_noadd, T_NEAR); + } + + for (int i_ur = 0; i_ur < ur; ++i_ur) + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + auto r = vreg_accum(i_load, i_ur); + vaddps(r, r, output_ptr(i_load, i_ur)); + } + + L(store_noadd); + if (jcp.with_eltwise) { + Label store_noeltwise; + test(reg_reduce_pos_flag, FLAG_REDUCE_LAST); + jz(store_noeltwise, T_NEAR); + + eltwise_injector_->compute_vector_range(0, ur * load_loop_blk); + + L(store_noeltwise); + } + + auto store_output = [=](bool output_is_aligned) { + for (int i_ur = 0; i_ur < ur; ++i_ur) + for (int i_load = 0; i_load < load_loop_blk; ++i_load) + if (output_is_aligned && jcp.use_vmovntps) + vmovntps(output_ptr(i_load, i_ur), + vreg_accum(i_load, i_ur)); + else + vmovups(output_ptr(i_load, i_ur), + vreg_accum(i_load, i_ur)); + }; + + Label unaligned_store, end_store; + test(aux_reg_output_data, cpu_isa_traits::vlen - 1); + jnz(unaligned_store, T_NEAR); + store_output(true); + jmp(end_store, T_NEAR); + L(unaligned_store); { + store_output(false); + } + L(end_store); + }; + + auto prefetch_callback = [=](int ur, int i_reduce, int i_ur, int i_load, + bool last_block, bool wraparound, int reduce_step) + { + bool pf_ker_l1 = true; + bool pf_ker_l2 = wraparound; + int n_ops = (jcp.reduce_loop_unroll / reduce_step) * ur * load_loop_blk; + int i_op = (i_reduce / reduce_step) * ur * load_loop_blk + + i_ur * load_loop_blk + i_load; + + int n_pf_ker_l1 = pf_ker_l1 ? jcp.reduce_block : 0; + int n_pf_ker_l2 = pf_ker_l2 && wraparound ? jcp.reduce_block : 0; + int n_pf_out_l1 = jcp.use_vmovntps ? 0 : ur; + + int pf_inp_ops = n_ops / 2; // # of operations during which to pf input + int pf_inp_trigger; + if (jcp.prop_kind == backward_weights) + pf_inp_trigger = nstl::max(1, pf_inp_ops / jcp.reduce_block); + else + pf_inp_trigger = nstl::max(1, pf_inp_ops / ur); + + int n_other_pf = + load_loop_blk * (n_pf_ker_l1 + n_pf_ker_l2 + n_pf_out_l1); + int n_other_pf_ops = n_ops - pf_inp_ops; + int other_pf_trigger + = n_other_pf ? nstl::max(1, n_other_pf_ops / n_other_pf) : 0; + + if (i_op < pf_inp_ops && i_op % pf_inp_trigger == 0) { + // input prefetches have the highest priority b/c the + // first iteration of the kernel block touches all the + // cache lines + int i_pf = i_op / pf_inp_trigger; + auto pf_reg = wraparound && last_block + ? reg_bcast_data + : (last_block ? aux1_reg_bcast_data + : aux_reg_bcast_data); + int offt = i_pf; + if (jcp.prop_kind == backward_weights) { + offt += wraparound && last_block + ? 0 + : (last_block ? jcp.is : jcp.reduce_block); + offt *= jcp.bcast_block; + } else { + offt += wraparound && last_block + ? 0 + : (last_block ? jcp.ur : jcp.bcast_dim); + offt *= jcp.reduce_block; + } + mic_prefetcht0(ptr[pf_reg + offt * jcp.typesize_in]); + } else if (i_op >= pf_inp_ops && n_other_pf) { + // remaining prefetches are spread among the rest of the + // operations; prefetches for output take priority + // TODO: spread L2 prefetches among L1 prefetches + i_op -= pf_inp_ops; + if (i_op % other_pf_trigger == 0) { + int i_pf = i_op / (load_loop_blk * other_pf_trigger); + if (i_pf < n_pf_ker_l2) { + int offt = (i_pf + (i_load + 1) * jcp.reduce_dim) + * jcp.load_block; + mic_prefetcht1(ptr[aux_reg_load_data + + offt * jcp.typesize_in]); + } else if (i_pf < n_pf_ker_l2 + n_pf_ker_l1) { + i_pf -= n_pf_ker_l2; + auto pf_reg = last_block ? reg_load_data + : aux_reg_load_data; + int offt = (i_pf + i_load * jcp.reduce_dim + + (last_block + ? (wraparound ? jcp.reduce_dim : 0) + : jcp.reduce_block)) + * jcp.load_block; + mic_prefetcht0(ptr[pf_reg + offt * jcp.typesize_in]); + } else if (i_pf < n_pf_ker_l1 + n_pf_ker_l2 + n_pf_out_l1) { + i_pf -= n_pf_ker_l1 + n_pf_ker_l2; + int offt = i_pf * jcp.load_block; + mic_prefetcht0(ptr[aux_reg_output_data + + offt * jcp.typesize_out]); + } + } + } + }; + + auto fma_block = [=](bool last_block) { + assert(jcp.reduce_loop_unroll % jcp.fma_step == 0); + + int reduce_step = jcp.fma_step; + + for (int i_reduce = 0; i_reduce < jcp.reduce_loop_unroll; + i_reduce += reduce_step) { + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + // if transposed input data used and if spatial size is + // not divided by transpose step (4) then for last reduce step + // we should load only needed load_registers data + // and clear remaining + if (jcp.transpose_src && jcp.is % jcp.fma_step && last_block + && i_reduce == jcp.reduce_loop_unroll - reduce_step) { + Label load_all; + Label load_finish; + test(reg_reduce_pos_flag, FLAG_SP_LAST); + jz(load_all, T_NEAR); + + const int n_loads = jcp.is % jcp.fma_step; + for (int i_fma = 0; i_fma < jcp.fma_step; i_fma++) { + if (i_fma < n_loads) + vmovups(vreg_load(i_load, i_fma), + load_ptr(i_reduce + i_fma, i_load)); + else + vpxord(vreg_load(i_load, i_fma), + vreg_load(i_load, i_fma), + vreg_load(i_load, i_fma)); + } + jmp(load_finish); + + L(load_all); + for (int i_fma = 0; i_fma < jcp.fma_step; i_fma++) { + vmovups(vreg_load(i_load, i_fma), + load_ptr(i_reduce + i_fma, i_load)); + } + L(load_finish); + } else { + for (int i_fma = 0; i_fma < jcp.fma_step; i_fma++) { + vmovups(vreg_load(i_load, i_fma), + load_ptr(i_reduce + i_fma, i_load)); + } + } + } + + for (int i_ur = 0; i_ur < ur; ++i_ur) { + if (jcp.ver == ver_avx512_core && jcp.expl_bcast + && load_loop_blk > 1) + vbroadcastss(vreg_bcast, bcast_ptr(i_reduce, i_ur, false)); + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + if (jcp.ver == ver_4fma) + v4fmaddps(vreg_accum(i_load, i_ur), + vreg_load(i_load, 0), + bcast_ptr(i_reduce, i_ur, false)); + else if (jcp.ver == ver_avx512_core && jcp.expl_bcast + && load_loop_blk > 1) + vfmadd231ps(vreg_accum(i_load, i_ur), + vreg_load(i_load, 0), vreg_bcast); + else + vfmadd231ps(vreg_accum(i_load, i_ur), + vreg_load(i_load, 0), + bcast_ptr(i_reduce, i_ur, true)); + prefetch_callback(ur, i_reduce, i_ur, i_load, + last_block, wraparound, reduce_step); + } + } + } + }; + Label reduce_loop; + Label reduce_loop_tail; + + mov(aux_reg_load_data, reg_load_data); + + mov(aux_reg_bcast_data, aux1_reg_bcast_data); + init(); + + mov(reduce_loop_iter, reg_reduce_loop_work); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jle(reduce_loop_tail, T_NEAR); + + L(reduce_loop); { + fma_block(false); + add(aux_reg_bcast_data, jcp.reduce_loop_bcast_step); + add(aux_reg_load_data, jcp.reduce_loop_load_step); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jg(reduce_loop, T_NEAR); + } + + L(reduce_loop_tail); + fma_block(true); + + store(); +} + +void jit_avx512_common_1x1_conv_kernel::generate() +{ + preamble(); + + mov(reg_bcast_data, ptr[param1 + GET_OFF(bcast_data)]); + mov(reg_load_data, ptr[param1 + GET_OFF(load_data)]); + mov(reg_output_data, ptr[param1 + GET_OFF(output_data)]); + + sub(rsp, stack_space_needed); + + if (jcp.with_bias) + mov(reg_bias_data, ptr[param1 + GET_OFF(bias_data)]); + + mov(reg_load_loop_work, ptr[param1 + GET_OFF(load_dim)]); + mov(reg_bcast_loop_work, ptr[param1 + GET_OFF(bcast_dim)]); + mov(EVEX_compress_addr(rsp, bcast_loop_work_offt), reg_bcast_loop_work); + mov(reg_reduce_loop_work, ptr[param1 + GET_OFF(reduce_dim)]); + mov(reg_reduce_pos_flag, ptr[param1 + GET_OFF(first_last_flag)]); + if (one_of(jcp.prop_kind, forward_training, forward_inference)) + mov(reg_relu_ns, reinterpret_cast(&jcp.eltwise.alpha)); + if (jcp.prop_kind == backward_weights) + mov(reg_output_stride, ptr[param1 + GET_OFF(output_stride)]); + + auto load_loop_body = [=](int load_loop_blk) { + bcast_loop(load_loop_blk); + add(reg_load_data, load_loop_blk * jcp.load_loop_load_step); + switch (jcp.prop_kind) { + case forward_training: + case forward_inference: + add(reg_bias_data, + load_loop_blk * jcp.load_block * jcp.typesize_out); + add(reg_output_data, + load_loop_blk * jcp.bcast_dim * jcp.load_block * + jcp.typesize_out); + break; + case backward_data: + add(reg_output_data, + load_loop_blk * jcp.bcast_dim * jcp.load_block * + jcp.typesize_out); + break; + case backward_weights: + for (int i_load = 0; i_load < load_loop_blk; i_load++) + add(reg_output_data, reg_output_stride); + break; + default: + assert(!"invalid prop_kind"); + } + sub(reg_load_loop_work, load_loop_blk * jcp.load_loop_iter_step); + }; + + const int simd_w = 16; + + Label load_loop_blk[7]; + + static const int ur_cases_fma_embd_bcast[] = { 2, 4, 5, 8, 14, 32 }; + static const int ur_cases_fma_expl_bcast[] = { 2, 5, 6, 9, 14, 32 }; + static const int ur_cases_4fma[] = { 2, 4, 6, 12, 32 }; + + const int size_ur_cases_fma + = (jcp.ver == ver_avx512_core && jcp.expl_bcast) ? + sizeof(ur_cases_fma_expl_bcast) : + sizeof(ur_cases_fma_embd_bcast); + const int size_ur_cases_4fma = sizeof(ur_cases_4fma); + + const int *ur_cases_fma = (jcp.ver == ver_avx512_core && jcp.expl_bcast) ? + ur_cases_fma_expl_bcast : + ur_cases_fma_embd_bcast; + const int *ur_cases = jcp.ver == ver_4fma ? ur_cases_4fma : ur_cases_fma; + const int num_ur_cases = + (jcp.ver == ver_4fma ? size_ur_cases_4fma : size_ur_cases_fma) + / sizeof(*ur_cases); + + for (int ur_idx = num_ur_cases - 1; ur_idx > 0; ur_idx--) { + int label_idx = num_ur_cases - ur_idx - 1; + if (jcp.ur <= ur_cases[ur_idx]) { + cmp(reg_load_loop_work, simd_w * (label_idx + 1)); + jle(load_loop_blk[label_idx], T_NEAR); + } + } + + for (int ur_idx = 0; ur_idx < num_ur_cases; ur_idx++) { + if (jcp.ur <= ur_cases[ur_idx]) { + int label_idx = num_ur_cases - ur_idx - 1; + L(load_loop_blk[label_idx]); + { + if (label_idx == 0) { + cmp(reg_load_loop_work, 0); + je(load_loop_blk[num_ur_cases], T_NEAR); + } + load_loop_body(label_idx + 1); + if (label_idx - 1 > 0) { + cmp(reg_load_loop_work, 2 * label_idx * simd_w); + je(load_loop_blk[label_idx - 1], T_NEAR); + } + cmp(reg_load_loop_work, (label_idx + 1) * simd_w); + jge(load_loop_blk[label_idx]); + } + for (int idx = label_idx - 1; idx > 0; --idx) { + cmp(reg_load_loop_work, simd_w * (idx + 1)); + je(load_loop_blk[idx], T_NEAR); + } + if (ur_idx < num_ur_cases - 2) { + cmp(reg_load_loop_work, simd_w); + jle(load_loop_blk[0], T_NEAR); + } + } + } + L(load_loop_blk[num_ur_cases]); + + add(rsp, stack_space_needed); + + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_avx512_common_1x1_conv_kernel::post_ops_ok( + jit_1x1_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +status_t jit_avx512_common_1x1_conv_kernel::init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr, int nthreads, bool reduce_src) { + if (!mayiuse(avx512_common)) return status::unimplemented; + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + const int simd_w = cpu_isa_traits::vlen / sizeof(float); + const int ndims = src_d.ndims(); + + jcp.prop_kind = cd.prop_kind; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc_without_padding = dst_d.dims()[1] / jcp.ngroups; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + bool ok_to_pad_channels = true + && jcp.ngroups == 1 + && src_d.data_type() == data_type::f32; + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.oh = (ndims == 3) ? 1 : dst_d.dims()[2]; + jcp.ow = dst_d.dims()[ndims - 1]; + + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][0]; + jcp.l_pad = cd.padding[0][ndims - 3]; + + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[0]; + jcp.stride_w = cd.strides[ndims - 3]; + + jcp.with_bias = pick_by_prop_kind(jcp.prop_kind, cd.bias_desc.format_kind, + format_kind::undef, cd.diff_bias_desc.format_kind) + != format_kind::undef; + + jcp.os = jcp.oh * jcp.ow; + jcp.is = jcp.ih * jcp.iw; + jcp.tr_is = rnd_up(jcp.is, 4); + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) { + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + if (dst_d.data_type() == data_type::s32) return status::unimplemented; + } + + auto dat_tag = pick(ndims - 3, nCw16c, nChw16c); + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.ngroups == 1 + && jcp.src_tag == dat_tag + && jcp.dst_tag == dat_tag; + if (!args_ok) return status::unimplemented; + + args_ok = true + && jcp.oc % simd_w == 0 && jcp.ic % simd_w == 0 + && jcp.t_pad == 0 && jcp.l_pad == 0 + && jcp.stride_w == 1 && jcp.stride_h == 1 // TODO: support some strides + && jcp.kh == 1 && jcp.kw == 1; + if (!args_ok) return status::unimplemented; + + jcp.ic_block = jcp.oc_block = simd_w; + jcp.transpose_src = false; + + if (everyone_is(data_type::f32, src_d.data_type(), + weights_d.data_type(), dst_d.data_type())) + { + const int is_bwd_d = jcp.prop_kind == backward_data; + format_tag_t wei_tag = with_groups + ? pick(2 * ndims - 6 + is_bwd_d, gOIw16i16o, gIOw16o16i, + gOIhw16i16o, gIOhw16o16i) + : pick(2 * ndims - 6 + is_bwd_d, OIw16i16o, IOw16o16i, + OIhw16i16o, IOhw16o16i); + + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + if (jcp.wei_tag != wei_tag) + return status::unimplemented; + + if (jcp.prop_kind != backward_weights && mayiuse(avx512_mic_4ops) && + ((jcp.prop_kind == backward_data) ? jcp.oc_block : jcp.ic_block) % 4 + == 0) { + jcp.ver = ver_4fma; + jcp.fma_step = 4; + } else if (jcp.prop_kind == backward_weights && mayiuse(avx512_mic_4ops) + && !reduce_src + /* Heuristic condition for relation of src size to oc. Otherwise + the src transposition overhead exceed the benefit from 4fma + */ + && ((jcp.is * jcp.ic) / jcp.oc <= 2048) + && mkldnn_thr_syncable() + ) + { + jcp.transpose_src = true; + jcp.ver = ver_4fma; + jcp.fma_step = 4; + } else { + jcp.ver = (mayiuse(avx512_core)) ? ver_avx512_core : ver_fma; + jcp.fma_step = 1; + } + jcp.typesize_in = sizeof(prec_traits::type); + jcp.typesize_out = sizeof(prec_traits::type); + } else { + return status::unimplemented; + } + + /* once all the formats are set, check the padding consistency */ + args_ok = true + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= dst_d.padded_dims()[1] + && jcp.ic <= weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= weights_d.padded_dims()[with_groups + 0]; + if (!args_ok) return status::unimplemented; + + const int SMALL_SPATIAL = 10; + const int BIG_SPATIAL = 28; + const int BIG_REDUCE_DIM = 1024; + const int BIG_LOAD_DIM = 256; + + int load_blocking{ 0 }; + int load_blocking_max{ 0 }; + int bcast_blocking{ 0 }; + int bcast_blocking_max{ 0 }; + int reduce_blocking{ 0 }; + int reduce_blocking_max{ 0 }; + + jcp.load_grp_count = 1; + + const int L1_capacity = get_cache_size(1, true) / sizeof(float); + const int L2_size = get_cache_size(2, true) / sizeof(float); + const int L2_capacity = (L2_size * 3) / 4; + + if (one_of(jcp.prop_kind, forward_training, forward_inference, + backward_data)) { + if (one_of(jcp.prop_kind, forward_training, forward_inference)) { + jcp.reduce_dim = jcp.ic; + jcp.reduce_block = jcp.ic_block; + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.is; + } else { + jcp.reduce_dim = jcp.oc; + jcp.reduce_block = jcp.oc_block; + + jcp.load_dim = jcp.ic; + jcp.load_block = jcp.ic_block; + + jcp.bcast_dim = jcp.os; + } + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.bcast_dim * jcp.typesize_in; + + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.load_block * jcp.typesize_in; + jcp.load_loop_load_step + = jcp.reduce_dim * jcp.load_block * jcp.typesize_in; + + // adjusting registry blocking + int max_regs, min_regs, size_treshold, ur_step; + const int spatial + = (one_of(jcp.prop_kind, forward_training, forward_inference)) ? + jcp.oh : + jcp.ih; + if (jcp.ver == ver_avx512_core && (8 * jcp.mb) / nthreads >= 1) { + max_regs = 9; + min_regs = 6; + size_treshold = 14; + ur_step = 1; + jcp.expl_bcast = true; + + if (jcp.load_dim > 128 && jcp.load_dim < BIG_LOAD_DIM + && spatial > SMALL_SPATIAL && spatial < BIG_SPATIAL) { + max_regs = 6; + min_regs = 5; + } + } else { + max_regs = jcp.ver == ver_4fma ? 28 : 30; + min_regs = 9; + size_treshold = jcp.ver == ver_4fma ? 28 : 14; + ur_step = jcp.ver == ver_4fma ? 4 : 1; + jcp.expl_bcast = false; + jcp.use_vmovntps = true; + } + jcp.ur = 1; + for (int ur_w = max_regs; ur_w >= min_regs; ur_w -= ur_step) { + if ((spatial >= size_treshold && spatial % ur_w == 0) + || (spatial < size_treshold && jcp.os % ur_w == 0)) { + jcp.ur = ur_w; + break; + } + } + if (jcp.ur == 1) { + jcp.ur = nstl::min(max_regs, jcp.os); + int os_tail = jcp.os % max_regs; + for (int i = max_regs; i >= min_regs; i -= ur_step) { + int i_tail = jcp.os % i; + if (i_tail > os_tail || i_tail == 0) { + jcp.ur = i; + os_tail = i_tail; + if (i_tail == 0) + break; + } + } + } + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.bcast_dim * jcp.typesize_in; + + jcp.bcast_block = jcp.ur; + + jcp.bcast_loop_output_step = jcp.ur * jcp.load_block * jcp.typesize_out; + jcp.bcast_loop_output_substep = -1; // unused + jcp.bcast_loop_bcast_step = jcp.ur * jcp.reduce_block * jcp.typesize_in; + jcp.bcast_loop_bcast_substep = -1; // unused + + jcp.load_loop_iter_step = jcp.load_block; + + if (jcp.prop_kind == backward_data) + jcp.loop_order = loop_lbr; + else + jcp.loop_order = reduce_src ? loop_blr : loop_lbr; + + int nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + int nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + int nb_load = div_up(jcp.load_dim, jcp.load_block); + + if (jcp.ver == ver_avx512_core && jcp.expl_bcast) { + if (jcp.load_dim <= BIG_LOAD_DIM && spatial > SMALL_SPATIAL + && spatial < BIG_SPATIAL) + reduce_blocking = nstl::min(jcp.reduce_dim, 80); + else if (spatial > SMALL_SPATIAL) + reduce_blocking = nstl::min(jcp.reduce_dim, 512); + else + reduce_blocking = nstl::min(jcp.reduce_dim, 256); + + if ((jcp.mb > 28 && spatial >= 28) + || (jcp.mb > 112 && spatial >= 17)) + jcp.use_vmovntps = true; + else + jcp.use_vmovntps = false; + } else { + + reduce_blocking = nb_reduce; + if (spatial <= SMALL_SPATIAL && jcp.reduce_dim >= BIG_REDUCE_DIM) + reduce_blocking = 16; + else if (spatial > SMALL_SPATIAL + && jcp.reduce_dim >= BIG_REDUCE_DIM) + reduce_blocking = 8; + reduce_blocking = best_divider(nb_reduce, 1, reduce_blocking, true); + reduce_blocking *= jcp.reduce_block; + } + + // Check input data cache aliasing. + // For other ISA constants may be updated. + // 64 * 1024 is chosen due to 1MB L2 16-way cache. + // 7 is empirical value. It is about half of 16. + // So we leave about half of the set for other data - weights, dst + int way_size = (64 * 1024) / jcp.typesize_in; + int max_hits = 7; + if (jcp.bcast_dim * reduce_blocking > way_size * max_hits) { + int nrb = reduce_blocking / simd_w; + int sp = jcp.bcast_dim; + int wl = way_size / simd_w; + for (int start_off = 0; start_off < jcp.ur; start_off++) { + for (int off = start_off, hits = 0; off < sp * nrb; off += wl) { + if (off % sp >= jcp.ur || ++hits < max_hits) + continue; + int max_r_blocking = simd_w * nstl::max(1, (off + wl) / sp); + reduce_blocking + = nstl::min(reduce_blocking, max_r_blocking); + break; + } + } + } + + if (reduce_blocking < jcp.reduce_dim) { + jcp.use_vmovntps = false; + if (jcp.prop_kind == backward_data) + jcp.loop_order = reduce_src ? loop_lbr : loop_rlb; + else + jcp.loop_order = reduce_src ? loop_rbl : loop_rlb; + } + load_blocking = jcp.load_dim; + + int load_size = jcp.load_dim * jcp.reduce_dim; + int bcast_size = jcp.mb * jcp.ngroups * jcp.bcast_dim * jcp.reduce_dim; + + if (jcp.ver == ver_avx512_core && nthreads <= 28 && jcp.mb < nthreads + && nb_load * nb_bcast > nthreads) { + // Some heuristic here + float calc_koef = 0.01, best_cost = FLT_MAX; + int n_lgc = nthreads; + float ratio = (float)load_size / (float)bcast_size; + int best_lgc = ratio > 1 ? n_lgc : 1; + auto calc_job_cost = [&](int lb, int tg, float mem_k) { + int bb_size = jcp.mb * div_up(nb_bcast, tg); + float calc_size = (float)(bb_size * jcp.ur) + * (lb * jcp.load_block) * jcp.reduce_dim; + float mem_size = (float)(bb_size * jcp.ur + lb * jcp.load_block) + * jcp.reduce_dim; + return calc_koef * calc_size + mem_k * mem_size; + }; + for (int lgc, ilgc = 0; ilgc < n_lgc; ilgc++) { + lgc = ratio > 1 ? n_lgc - ilgc : ilgc + 1; + int min_lb = nb_load / lgc; + int max_lb = div_up(nb_load, lgc); + int min_tg = nthreads / lgc; + int max_tg = div_up(nthreads, lgc); + // Some heuristic here + float mem_koef = (max_tg == 1) ? 1.f : 1.3f; + float job_cost = 0.; + if (nthreads % lgc < nb_load % lgc) { + job_cost = calc_job_cost(max_lb, min_tg, mem_koef); + } else { + auto job_cost1 = calc_job_cost(max_lb, max_tg, mem_koef); + auto job_cost2 = calc_job_cost(min_lb, min_tg, mem_koef); + job_cost = nstl::max(job_cost1, job_cost2); + } + + if (job_cost < best_cost) { + best_lgc = lgc; + best_cost = job_cost; + } + } + jcp.load_grp_count = best_lgc; + load_blocking = div_up(nb_load, jcp.load_grp_count) * jcp.load_block; + } else { + jcp.load_grp_count = div_up(nthreads, jcp.mb * jcp.ngroups * nb_bcast); + jcp.load_grp_count = best_divider( + nthreads, jcp.load_grp_count, 2 * jcp.load_grp_count, false); + } + + if (jcp.ver == ver_avx512_core && jcp.expl_bcast && jcp.bcast_dim <= 64 + && load_size >= L2_size) { + jcp.load_grp_count = nstl::max(jcp.load_grp_count, 4); + } else if (jcp.bcast_dim <= 49 && jcp.mb <= nthreads + && jcp.load_dim > 512 && jcp.load_dim / jcp.reduce_dim >= 4) { + jcp.load_grp_count = nstl::max(jcp.load_grp_count, 2); + load_blocking = jcp.load_block; + } + + if (jcp.ver == ver_4fma && jcp.bcast_dim * jcp.mb < jcp.load_dim + && jcp.oh * jcp.ow > 64 + && IMPLICATION(reduce_src, jcp.load_dim < 1024)) { + /* Looking for best loading dimension blocking + * to get the best thread and data read/write efficiency + * by finding the optimal 'load_chunk' value + * Example: + * for 72 threads and convolution with mb=1, ih=iw=7, oc = 512 + * the 'best' load_chunk value should be 1 + * TODO: remove heuristic constants in above condition + * TODO: check this blocking for other ISA + */ + float best_eff = -1.f; + int best_lgc = 1; + + for (int load_chunk = 1; load_chunk <= nb_load; load_chunk++) { + int lgc = div_up(nb_load, load_chunk); + if (lgc > nthreads) + continue; + int thr_per_grp = div_up(nthreads, lgc); + int bcast_per_thr = div_up(jcp.mb * nb_bcast, thr_per_grp) + * jcp.bcast_block; + int load_per_thr = load_chunk * simd_w; + float data_norm = (bcast_per_thr + load_per_thr) / 2.f; + float data_eff = (bcast_per_thr * load_per_thr) + / (data_norm * data_norm); + float thr_eff_over_grp = (float)nstl::max(1, nthreads / lgc) + / div_up(nthreads, lgc); + float thr_eff_in_grp = ((float)jcp.mb * nb_bcast) + / rnd_up(jcp.mb * nb_bcast, thr_per_grp); + float thr_eff = thr_eff_over_grp * thr_eff_in_grp; + float load_eff = (float)nb_load / rnd_up(nb_load, lgc); + float overall_eff = data_eff + thr_eff + load_eff; + if (overall_eff > best_eff) { + best_eff = overall_eff; + best_lgc = lgc; + } + } + jcp.load_grp_count = best_lgc; + load_blocking + = div_up(nb_load, jcp.load_grp_count) * jcp.load_block; + } + bcast_blocking = div_up(jcp.mb * jcp.ngroups * nb_bcast, + div_up(nthreads, jcp.load_grp_count)) + * jcp.bcast_block; + bcast_blocking = nstl::min(jcp.bcast_dim, bcast_blocking); + bcast_blocking = rnd_up(bcast_blocking, jcp.bcast_block); + + int space_for_bcast + = (L2_capacity - /* kernel_size - */ + 2 * jcp.load_block * reduce_blocking + - jcp.ur * reduce_blocking - 3 * 1024); + if (jcp.reduce_dim * jcp.bcast_dim > L2_capacity) + space_for_bcast /= 2; + + int bcast_in_cache + = nstl::max(jcp.bcast_block, space_for_bcast / reduce_blocking); + bcast_blocking = nstl::min( + bcast_blocking, rnd_dn(bcast_in_cache, jcp.bcast_block)); + + load_blocking_max = load_blocking; + bcast_blocking_max = bcast_blocking * 3 / 2; + reduce_blocking_max = reduce_blocking; + + } else if (jcp.prop_kind == backward_weights) { + + jcp.use_vmovntps = false; + if (jcp.is > SMALL_SPATIAL * SMALL_SPATIAL && jcp.ver == ver_4fma) + jcp.use_vmovntps = true; + + if (jcp.transpose_src) + jcp.reduce_dim = jcp.tr_is; + else + jcp.reduce_dim = jcp.is; + + if (jcp.ver == ver_4fma) { + // reduce_block should be divided by fma_step + jcp.reduce_block = best_divider(jcp.reduce_dim, 4, 16, true, 4); + } else { + jcp.reduce_block = best_divider(jcp.reduce_dim, 7, 16, true); + if (jcp.reduce_dim % jcp.reduce_block != 0) + jcp.reduce_block = best_divider(jcp.iw, 4, jcp.iw, false); + if (jcp.reduce_block > 256) { + jcp.reduce_block = 1; + } + + } + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.ic; + jcp.bcast_block = jcp.ic_block; + + if (jcp.ver == ver_avx512_core && jcp.reduce_block <= 19) { + // if reduce_block is big then generated JIT code may be big + // for small values of ur because reduce_loop_unroll = reduce_block + jcp.ur = jcp.bcast_block / 2; + jcp.expl_bcast = true; + } else { + jcp.ur = jcp.bcast_block; + jcp.expl_bcast = false; + } + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.ic_block * jcp.typesize_in; + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.oc_block * jcp.typesize_in; + + jcp.bcast_loop_output_step = + jcp.oc_block * jcp.ic_block * jcp.typesize_out; + jcp.bcast_loop_output_substep = + jcp.oc_block * jcp.ur * jcp.typesize_out; + jcp.bcast_loop_bcast_step = + jcp.ic_block * jcp.reduce_dim * jcp.typesize_in; + jcp.bcast_loop_bcast_substep = jcp.ur * jcp.typesize_in; + + jcp.load_loop_load_step = jcp.oc_block * jcp.os * jcp.typesize_in; + jcp.load_loop_iter_step = jcp.oc_block; + + /* --- */ + balance(jcp, nthreads); + + load_blocking = div_up(jcp.load_dim, jcp.load_block); + load_blocking = best_divider(load_blocking, 16, load_blocking, false); + load_blocking *= jcp.load_block; + + load_blocking_max = load_blocking; + assert(jcp.load_dim % load_blocking == 0); + + int max_bcast_blocking = div_up(jcp.bcast_dim, jcp.bcast_block); + int min_bcast_blocking = 5; + + bcast_blocking = div_up(jcp.bcast_dim, jcp.bcast_block); + bcast_blocking = best_divider( + bcast_blocking, min_bcast_blocking, max_bcast_blocking, false); + bcast_blocking *= jcp.bcast_block; + bcast_blocking_max = bcast_blocking; + assert(jcp.bcast_dim % bcast_blocking == 0); + + // for reduction balance + if (jcp.ver == ver_avx512_core) { + int max_reduce_blocking + = nstl::min(L1_capacity / jcp.ur, jcp.reduce_dim); + int min_reduce_blocking = nstl::min( + L1_capacity / jcp.ur, nstl::max(jcp.iw, jcp.ih)); + reduce_blocking = best_divider(jcp.reduce_dim, min_reduce_blocking, + max_reduce_blocking, true); + reduce_blocking + = nstl::max(rnd_dn(reduce_blocking, jcp.reduce_block), + jcp.reduce_block); + } else { + int max_reduce_blocking = L2_capacity + / ((bcast_blocking + load_blocking) * jcp.reduce_block); + max_reduce_blocking = nstl::min(max_reduce_blocking, + (L1_capacity / (jcp.bcast_block)) / jcp.reduce_block); + + int num_jobs = div_up(jcp.load_dim, load_blocking) + * div_up(jcp.bcast_dim, bcast_blocking); + int threads_per_job = nstl::max(1, nthreads / num_jobs); + reduce_blocking = div_up(jcp.mb * jcp.reduce_dim, jcp.reduce_block); + reduce_blocking = div_up(reduce_blocking, threads_per_job); + + reduce_blocking = best_divider(reduce_blocking, + max_reduce_blocking - 2, max_reduce_blocking, true); + reduce_blocking *= jcp.reduce_block; + } + + reduce_blocking_max = rnd_dn(reduce_blocking * 3 / 2, jcp.reduce_block); + } else + return status::unimplemented; + + assert(load_blocking); + assert(load_blocking_max); + assert(bcast_blocking); + assert(bcast_blocking_max); + assert(reduce_blocking); + assert(reduce_blocking_max); + assert(load_blocking % jcp.load_block == 0); + assert(reduce_blocking % jcp.reduce_block == 0); + assert(load_blocking_max % jcp.load_block == 0); + assert(reduce_blocking_max % jcp.reduce_block == 0); + if (jcp.ver == ver_4fma) { + assert(jcp.reduce_loop_unroll % jcp.fma_step == 0); + assert(jcp.reduce_dim % jcp.reduce_loop_unroll == 0); + } + + assert(jcp.bcast_block % jcp.ur == 0); + assert(jcp.reduce_dim % jcp.reduce_block == 0); + + jcp.ur_tail = jcp.bcast_dim % jcp.ur; + + jcp.nb_bcast_blocking = bcast_blocking / jcp.bcast_block; + jcp.nb_bcast_blocking_max = bcast_blocking_max / jcp.bcast_block; + jcp.nb_load_blocking = load_blocking / jcp.load_block; + jcp.nb_load_blocking_max = load_blocking_max / jcp.load_block; + jcp.nb_reduce_blocking = reduce_blocking / jcp.reduce_block; + jcp.nb_reduce_blocking_max = reduce_blocking_max / jcp.reduce_block; + + jcp.nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + jcp.nb_load = div_up(jcp.load_dim, jcp.load_block); + jcp.nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + + return status::success; +} + +void jit_avx512_common_1x1_conv_kernel::init_scratchpad( + memory_tracking::registrar_t &scratchpad, + const jit_1x1_conv_conf_t &jcp) { + using namespace mkldnn::impl::memory_tracking::names; + + if (jcp.prop_kind != backward_data && jcp.with_bias + && jcp.oc != jcp.oc_without_padding) + scratchpad.book(key_conv_padded_bias, jcp.typesize_out * jcp.oc); + + if (jcp.prop_kind == backward_weights) { + const size_t wei_size = (size_t)jcp.ngroups * jcp.oc * jcp.ic; + scratchpad.book(key_conv_wei_reduction, + jcp.typesize_out * wei_size * (jcp.nthr_mb - 1)); + } + + if (jcp.transpose_src) { + const size_t tr_src_size = + (size_t)jcp.nthr_mb * jcp.ngroups * jcp.ic * jcp.tr_is; + scratchpad.book(key_conv_tr_src, jcp.typesize_out * tr_src_size); + scratchpad.book(key_conv_tr_src_bctx, + sizeof(simple_barrier::ctx_t) * jcp.nthr); + } +} + +void jit_avx512_common_1x1_conv_kernel::balance(jit_1x1_conv_conf_t &jcp, + int nthreads) +{ + // initialize jcp reduction threading properties + jcp.nthr = jcp.nthr_mb = jcp.nthr_g = jcp.nthr_oc_b = jcp.nthr_ic_b = 1; + if (nthreads < jcp.ngroups) { + /* simplification... fortunately it doesn't hurt much */ + return; + } + const int nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + const int nb_load = div_up(jcp.load_dim, jcp.load_block); + const int nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + + jcp.nthr_g = jcp.ngroups; + const int nthr = nthreads / jcp.nthr_g; + + auto calc_mem_cost = [=](int nthr_mb, int nthr_oc_b, int nthr_ic_b) { + /* calculate per thread memory cost (read/write). high level + * optimizer tries to minimize memory consumption. few notes: (n1) + * unclear why, but that essentially helps first convolution... + * (n2) assuming the reduction over minibatch is always there: + * - instead of 8 it should be 5 here (write ~= 2 read): + * kernel: temporal workspace 1 write + * reduction: 1 read from workspace and 1 write to the diff_wei + * - but experiments showed 8 works better than 5 or 6... */ + int bcast_koeff = 1; + int load_koeff = 1; + int output_koeff = 12; + if (jcp.transpose_src) { + bcast_koeff = 5; + load_koeff = 1; + output_koeff = 8; + } + return 0 + + (size_t)bcast_koeff * div_up(jcp.mb * nb_reduce, nthr_mb) + * div_up(jcp.ngroups, jcp.nthr_g) + * div_up(nb_bcast, nthr_ic_b) * jcp.ic_block * jcp.reduce_block + / jcp.stride_h / jcp.stride_w /* (n1) */ + + (size_t)load_koeff * div_up(jcp.mb * nb_reduce, nthr_mb) + * div_up(jcp.ngroups, jcp.nthr_g) + * div_up(nb_load, nthr_oc_b) * jcp.oc_block * jcp.reduce_block + + (size_t)output_koeff /* (n2) */ + * div_up(jcp.ngroups, jcp.nthr_g) * div_up(nb_load, nthr_oc_b) + * div_up(nb_bcast, nthr_ic_b) * jcp.ic_block + * jcp.oc_block; + }; + + int nthr_mb = 1, nthr_oc_b = 1, nthr_ic_b = 1; + auto best_mem_cost = calc_mem_cost(nthr_mb, nthr_oc_b, nthr_ic_b); + + /* step 1: find the best thread distribution with lowest memory cost */ + const int nthr_mb_max = nstl::min(nthr, jcp.mb * nb_reduce); + for (nthr_mb = 1; nthr_mb <= nthr_mb_max; ++nthr_mb) { + const int nthr_par = nthr / nthr_mb; + const int nthr_oc_b_max = nstl::min(nthr_par, nb_load); + for (nthr_oc_b = 1; nthr_oc_b <= nthr_oc_b_max; ++nthr_oc_b) { + nthr_ic_b = nstl::min(nthr_par / nthr_oc_b, nb_bcast); + auto mem_cost = calc_mem_cost(nthr_mb, nthr_oc_b, nthr_ic_b); + if (mem_cost <= best_mem_cost) { + best_mem_cost = mem_cost; + jcp.nthr_mb = nthr_mb; + jcp.nthr_oc_b = nthr_oc_b; + jcp.nthr_ic_b = nthr_ic_b; + } + } + + if (!mkldnn_thr_syncable()) { assert(nthr_mb == 1); break; } + } + if (jcp.nthr_mb > nthreads / 2 && jcp.nthr_mb < nthreads) + jcp.nthr_mb = nstl::min(jcp.mb, nthreads); + + jcp.nthr = jcp.nthr_mb * jcp.nthr_g * jcp.nthr_oc_b * jcp.nthr_ic_b; + assert(jcp.nthr <= nthreads); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.hpp new file mode 100644 index 000000000000..d2ae01794375 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_conv_kernel.hpp @@ -0,0 +1,108 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_COMMON_1x1_CONV_KERNEL_HPP +#define JIT_AVX512_COMMON_1x1_CONV_KERNEL_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx512_common_1x1_conv_kernel : public jit_generator { + jit_avx512_common_1x1_conv_kernel(jit_1x1_conv_conf_t ajcp, + const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32( + this, jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_1x1_conv_call_s *)) this->getCode(); + } + + ~jit_avx512_common_1x1_conv_kernel() { + delete eltwise_injector_; + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_common_1x1_conv_kernel) + + static bool post_ops_ok(jit_1x1_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr, + int nthreads, bool reduce_src); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_1x1_conv_conf_t &jcp); + + jit_1x1_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_1x1_conv_call_s *); + + private: + using reg64_t = const Xbyak::Reg64; + using zmm_t = const Xbyak::Zmm; + + reg64_t reg_bcast_data = r8; + reg64_t reg_load_data = r10; + reg64_t reg_output_data = r9; + reg64_t aux_reg_bcast_data = r14; + reg64_t aux1_reg_bcast_data = rbx; + reg64_t aux_reg_load_data = r15; + reg64_t imm_addr64 = aux_reg_load_data; + reg64_t aux_reg_output_data = abi_not_param1; + reg64_t reg_load_loop_work = rsi; + reg64_t reg_reduce_loop_work = r11; + reg64_t bcast_loop_iter = rdx; + reg64_t reduce_loop_iter = abi_param1; + reg64_t reg_reduce_pos_flag = rax; + reg64_t reg_output_stride = r13; + reg64_t reg_bias_data = r12; + reg64_t reg_relu_ns = r13; + reg64_t reg_bcast_loop_work = aux1_reg_bcast_data; + + Xbyak::Zmm vreg_bcast = Xbyak::Zmm(31); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + int bcast_loop_work_offt = 0; + int stack_space_needed = 16; + + void bcast_loop(int load_loop_blk); + void reduce_loop(int load_loop_blk, int ur, int substep, bool wraparound); + + void generate(); + static void balance(jit_1x1_conv_conf_t &jcp, int nthreads); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.cpp new file mode 100644 index 000000000000..54d58c8a3917 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.cpp @@ -0,0 +1,816 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +#include "jit_avx512_common_1x1_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +#define data_blk_off(f, n, c, h, w) \ + ((ndims == 3) \ + ? (f).blk_off(n, c, w) \ + : (f).blk_off(n, c, h, w)) + + +namespace { +template +void balance2D(U nthr, U ithr, T ny, T &ny_start, T &ny_end, + T nx, T &nx_start, T &nx_end, T nx_divider) +{ + const int grp_count = nstl::min(nx_divider, nthr); + const int grp_size_big = nthr / grp_count + 1; + const int grp_size_small = nthr / grp_count; + const int n_grp_big = nthr % grp_count; + const int threads_in_big_groups = n_grp_big * grp_size_big; + + const int ithr_bound_distance = ithr - threads_in_big_groups; + T grp, grp_ithr, grp_nthr; + if (ithr_bound_distance < 0) { // ithr in first groups + grp = ithr / grp_size_big; + grp_ithr = ithr % grp_size_big; + grp_nthr = grp_size_big; + } else { // ithr in last groups + grp = n_grp_big + ithr_bound_distance / grp_size_small; + grp_ithr = ithr_bound_distance % grp_size_small; + grp_nthr = grp_size_small; + } + + balance211(nx, grp_count, grp, nx_start, nx_end); + balance211(ny, grp_nthr, grp_ithr, ny_start, ny_end); +} +} +/* convolution forward */ + +template +void jit_avx512_common_1x1_convolution_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const dst_data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + auto scratchpad = this->scratchpad(ctx); + + const auto &jcp = kernel_->jcp; + if (pd()->wants_padded_bias()) { + auto padded_bias = scratchpad.template get( + key_conv_padded_bias); + utils::array_copy(padded_bias, bias, jcp.oc_without_padding); + utils::array_set(padded_bias + jcp.oc_without_padding, 0.f, + jcp.oc - jcp.oc_without_padding); + bias = padded_bias; + } + + parallel(0, [&](const int ithr, const int nthr) { + execute_forward_thr(ithr, nthr, src, weights, bias, dst, scratchpad); + }); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); +} + +template +void jit_avx512_common_1x1_convolution_fwd_t:: +execute_forward_thr(const int ithr, const int nthr, const src_data_t *src, + const wei_data_t *weights, const dst_data_t *bias, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + auto rtus_space = scratchpad.get(key_conv_rtus_space); + + const int ndims = src_d.ndims(); + const int stride_h = (ndims == 3) ? 1 : pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[ndims - 3]; + const int pad_t = (ndims == 3) ? 0 : pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][ndims - 3]; + + const int work_amount = jcp.mb * jcp.ngroups * jcp.nb_bcast; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + auto p = jit_1x1_conv_call_s(); + + auto rp = rtus_driver_t::call_params_t(); + + const int nb_oc = jcp.nb_load; + const int nb_ic = jcp.nb_reduce; + const int nb_ic_blocking = jcp.nb_reduce_blocking; + const int os_block = jcp.bcast_block; + + int bcast_start{0}, bcast_end{0}, ocb_start{0}, ocb_end{0}; + balance2D(nthr, ithr, work_amount, bcast_start, bcast_end, + jcp.nb_load, ocb_start, ocb_end, jcp.load_grp_count); + + auto init_bcast = [&](int iwork, int &n, int &g, int &bcast_step, + int &oh, int &ow, int &ih, int &iw) + { + int osb{0}; + nd_iterator_init(iwork, n, jcp.mb, g, jcp.ngroups, osb, + jcp.nb_bcast); + bcast_step = step(jcp.nb_bcast_blocking, jcp.nb_bcast - osb, + jcp.nb_bcast_blocking_max); + bcast_step = nstl::min(bcast_step, bcast_end - iwork); + + const int os = osb * os_block; + oh = os / jcp.ow; + ow = os % jcp.ow; + + ih = nstl::max(oh * stride_h - pad_t, 0); + iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + p.bcast_dim = this_block_size(os, jcp.os, + bcast_step * os_block); + rp.os = p.bcast_dim; + }; + + auto init_load = [&](int ocb, int &load_step) + { + load_step = step(jcp.nb_load_blocking, ocb_end - ocb, + jcp.nb_load_blocking_max); + p.load_dim = this_block_size(ocb * jcp.oc_block, + ocb_end * jcp.oc_block, load_step * jcp.oc_block); + }; + + auto init_reduce = [&](int icb) + { + const int nb_ic_blocking_step = + nstl::min(icb + nb_ic_blocking, nb_ic) - icb; + p.first_last_flag = 0 + | (icb == 0 ? FLAG_REDUCE_FIRST : 0) + | (icb + nb_ic_blocking_step >= nb_ic + ? FLAG_REDUCE_LAST : 0); + + p.reduce_dim = this_block_size(icb * jcp.ic_block, + jcp.ic, nb_ic_blocking_step * jcp.ic_block); + rp.icb = p.reduce_dim / jcp.reduce_block; + }; + + auto inner_ker = [&](int ocb, int icb, int n, int g, int oh, int ow, + int ih, int iw) + { + + const int _ocb = g * nb_oc + ocb; + const size_t dst_off = data_blk_off(dst_d, n, _ocb, oh, ow); + + p.output_data = &dst[dst_off]; + p.bias_data = &bias[_ocb * jcp.oc_block]; + p.load_data = &weights[pd()->with_groups() + ? weights_d.blk_off(g, ocb, icb) + : weights_d.blk_off(ocb, icb)]; + + const int _icb = g * nb_ic + icb; + if (pd()->rtus_.reduce_src_) { + rp.ws = rtus_space + ithr * pd()->rtus_.space_per_thread_ + + _icb * jcp.is * jcp.ic_block; + if (ocb == ocb_start) { + rp.src = src + data_blk_off(src_d, n, _icb, ih, iw); + rtus_driver_->ker_(&rp); + } + p.bcast_data = rp.ws; + } else + p.bcast_data = src + data_blk_off(src_d, n, _icb, ih, iw); + + kernel_->jit_ker(&p); + }; + + if (jcp.loop_order == loop_rlb) { + for (int icb = 0; icb < nb_ic; icb += nb_ic_blocking) { + init_reduce(icb); + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + inner_ker(ocb, icb, n, g, oh, ow, ih, iw); + iwork += bcast_step; + } + ocb += load_step; + } + } + } else if (jcp.loop_order == loop_lbr) { + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + for (int icb = 0; icb < nb_ic; icb += nb_ic_blocking) { + init_reduce(icb); + inner_ker(ocb, icb, n, g, oh, ow, ih, iw); + } + iwork += bcast_step; + } + ocb += load_step; + } + } else if (jcp.loop_order == loop_rbl) { + for (int icb = 0; icb < nb_ic; icb += nb_ic_blocking) { + init_reduce(icb); + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + inner_ker(ocb, icb, n, g, oh, ow, ih, iw); + ocb += load_step; + } + iwork += bcast_step; + } + } + } else if (jcp.loop_order == loop_blr) { + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + for (int icb = 0; icb < nb_ic; icb += nb_ic_blocking) { + init_reduce(icb); + inner_ker(ocb, icb, n, g, oh, ow, ih, iw); + } + ocb += load_step; + } + iwork += bcast_step; + } + } else { + assert(!"unsupported loop order"); + } +} + + +template struct jit_avx512_common_1x1_convolution_fwd_t; +/* convolution backward wtr data */ + +template +void jit_avx512_common_1x1_convolution_bwd_data_t::execute_backward_data(const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + + const auto &jcp = kernel_->jcp; + auto rtus_space = scratchpad(ctx).template get( + key_conv_rtus_space); + + const int ndims = diff_src_d.ndims(); + + // TODO (Roma): remove this restriction + assert(jcp.stride_w == 1 && jcp.stride_h == 1); + + const int stride_h = (ndims == 3) ? 1 : pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[ndims - 3]; + const int pad_t = (ndims == 3) ? 0 : pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][ndims - 3]; + + const int nb_ic = jcp.nb_load; + const int nb_oc = jcp.nb_reduce; + const int os_block = jcp.bcast_block; + const int nb_oc_blocking = jcp.nb_reduce_blocking; + + const int work_amount = jcp.mb * jcp.ngroups * jcp.nb_bcast; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + parallel(0, [&](const int ithr, const int nthr) { + auto p = jit_1x1_conv_call_s(); + auto rp = rtus_driver_t::call_params_t(); + + int bcast_start{0}, bcast_end{0}, icb_start{0}, icb_end{0}; + balance2D(nthr, ithr, work_amount, bcast_start, bcast_end, + jcp.nb_load, icb_start, icb_end, jcp.load_grp_count); + + bool reduce_outer = (jcp.loop_order == loop_rbl + || jcp.loop_order == loop_rlb); + int nboc_outer = reduce_outer ? nb_oc : 1; + int ocb_outer_step = reduce_outer ? nb_oc_blocking : 1; + + int nboc_inner = reduce_outer ? 1 : nb_oc; + int ocb_inner_step = reduce_outer ? 1 : nb_oc_blocking; + + for (int ocb_outer = 0; ocb_outer < nboc_outer; + ocb_outer += ocb_outer_step) { + size_t cur_ocb_outer = + nstl::min(ocb_outer + ocb_outer_step, nboc_outer) - ocb_outer; + + int load_step = 0; + for (int icb = icb_start; icb < icb_end; icb += load_step) { + load_step = step(jcp.nb_load_blocking, jcp.nb_load - icb, + jcp.nb_load_blocking_max); + + p.load_dim = this_block_size(icb * jcp.ic_block, + icb_end * jcp.ic_block, load_step * jcp.ic_block); + rp.icb = p.load_dim / jcp.ic_block; + + int bcast_step; + for (int iwork = bcast_start; iwork < bcast_end; + iwork += bcast_step) + { + int n{0}, g{0}, osb{0}; + nd_iterator_init(iwork, n, jcp.mb, g, jcp.ngroups, osb, + jcp.nb_bcast); + + bcast_step = step(jcp.nb_bcast_blocking, jcp.nb_bcast - osb, + jcp.nb_bcast_blocking_max); + bcast_step = nstl::min(bcast_step, bcast_end - iwork); + + const int os = osb * os_block; + p.bcast_dim = this_block_size(os, jcp.os, + bcast_step * os_block); + rp.os = p.bcast_dim; + + const int oh = os / jcp.ow; + const int ow = os % jcp.ow; + const int ih = nstl::max(oh * stride_h - pad_t, 0); + const int iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + const int _icb = g * nb_ic + icb; + rp.src = diff_src + data_blk_off(diff_src_d, n, _icb, ih, iw); + if (pd()->rtus_.reduce_src_) { + rp.ws = rtus_space + + ithr * pd()->rtus_.space_per_thread_; + p.output_data = rp.ws; + } else + p.output_data = rp.src; + + for (int ocb_inner = 0; ocb_inner < nboc_inner; + ocb_inner += ocb_inner_step) { + int cur_ocb_inner = + nstl::min(ocb_inner + ocb_inner_step, nboc_inner) - + ocb_inner; + + int ocb = reduce_outer ? ocb_outer : ocb_inner; + int nb_oc_blocking_step = reduce_outer + ? cur_ocb_outer : cur_ocb_inner; + const int _ocb = g * nb_oc + ocb; + size_t diff_dst_off = data_blk_off(diff_dst_d, n, _ocb, oh, ow); + p.bcast_data = &diff_dst[diff_dst_off]; + + p.load_data = &weights[pd()->with_groups() + ? weights_d.blk_off(g, ocb, icb) + : weights_d.blk_off(ocb, icb)]; + + p.first_last_flag = ocb == 0 ? FLAG_REDUCE_FIRST : 0; + + p.reduce_dim = this_block_size(ocb * jcp.oc_block, + jcp.oc, nb_oc_blocking_step * jcp.oc_block); + + kernel_->jit_ker(&p); + } + if (pd()->rtus_.reduce_src_) + rtus_driver_->ker_(&rp); + } + } + } + }); +} + +template struct jit_avx512_common_1x1_convolution_bwd_data_t; + +/* convolution backward wtr weights */ + +#define wht_blk_off(d, g, ...) \ + (pd()->with_groups() \ + ? (d).blk_off((g), __VA_ARGS__) \ + : (d).blk_off(__VA_ARGS__)) + +jit_avx512_common_1x1_convolution_bwd_weights_t :: + jit_avx512_common_1x1_convolution_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr), acc_ker_(nullptr), reducer_bias_(nullptr) + , trans_kernel_(nullptr), rtus_driver_(nullptr) +{ + kernel_ = new jit_avx512_common_1x1_conv_kernel(pd()->jcp_, *pd()->attr()); + acc_ker_ = new cpu_accumulator_1d_t(); + reducer_bias_ = new cpu_reducer_t(pd()->reducer_bia_conf_); + init_rtus_driver(this); + + const auto &jcp = kernel_->jcp; + + if (jcp.transpose_src) { + auto tp = jit_transpose4x16_src_t(); + tp.src_pf0_distance = 4; + tp.tr_src_pf0_distance = 0; + tp.src_pf1 = true; + tp.tr_src_pf1 = false; + trans_kernel_ = new jit_transpose4x16_src(&jcp, &tp); + } +} + +void jit_avx512_common_1x1_convolution_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const +{ + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias_in = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + + const auto &jcp = kernel_->jcp; + + const auto scratchpad = this->scratchpad(ctx); + + auto rtus_space = scratchpad.get(key_conv_rtus_space); + data_t *diff_bias = pd()->wants_padded_bias() + ? scratchpad.get(key_conv_padded_bias) : diff_bias_in; + auto wei_reduction = scratchpad.get(key_conv_wei_reduction); + + /* prepare src transposition barriers */ + auto tr_src = scratchpad.get(key_conv_tr_src); + auto tr_src_bctx = scratchpad.get( + key_conv_tr_src_bctx); + if (jcp.transpose_src) { + for (int i = 0; i < jcp.nthr; ++i) + simple_barrier::ctx_init(&tr_src_bctx[i]); + } + + const int ndims = src_d.ndims(); + const int wei_size = jcp.ngroups * jcp.oc * jcp.ic; + + simple_barrier::ctx_t reduction_barrier; + simple_barrier::ctx_init(&reduction_barrier); + + const auto reducer_bia_scratchpad = memory_tracking::grantor_t(scratchpad, + prefix_reducer_bia); + auto rb = this->reducer_bias_; + rb->init(reducer_bia_scratchpad); + + // TODO (Roma): remove this restriction + assert(jcp.stride_w == 1 && jcp.stride_h == 1); + + const int nb_ic = jcp.nb_bcast; + const int nb_ic_blocking = jcp.nb_bcast_blocking; + + const int nb_oc = jcp.nb_load; + const int nb_oc_blocking = jcp.nb_load_blocking; + + const int sp_nb = jcp.nb_reduce; + const int mb_sp_work = jcp.mb * sp_nb; + + const int stride_h = (ndims == 3) ? 1 : pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[ndims - 3]; + const int pad_t = (ndims == 3) ? 0 : pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][ndims - 3]; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + // TODO: use memory descriptor with the same fmt as src + // (or use a macro :)) + auto tr_src_off = [&](int img, int icb, int is) { + const size_t tr_chn_size = jcp.tr_is * jcp.ic_block; + const size_t tr_img_size = tr_chn_size * nb_ic * jcp.ngroups; + return img * tr_img_size + icb * tr_chn_size + is * jcp.ic_block; + }; + + auto uker_trans = [&](int ithr_mb, int img, int sp_b_start, int sp_size, + int g_start, int g_work, int ic_b_start, int ic_b_work, + int ithr, int nthr, int first_ic_b) + { + const int work_amount = g_work * ic_b_work; + + int start{ 0 }, end{ 0 }; + balance211(work_amount, nthr, ithr, start, end); + + int g{ 0 }, ic_b{ 0 }; + nd_iterator_init(start, g, g_work, ic_b, ic_b_work); + g += g_start; + const int ic_b_tr = g * nb_ic + first_ic_b + ic_b; + ic_b += ic_b_start; + + const int _ic = g * nb_ic + ic_b; + + const int is = sp_b_start * jcp.reduce_block; + const int ih = is / jcp.iw; + const int iw = is % jcp.iw; + + const int src1_off = data_blk_off(src_d, img, _ic, ih, iw); + data_t *src1 = (data_t *)&src[src1_off]; + data_t *tr_src1 = &tr_src[tr_src_off(ithr_mb, ic_b_tr, is)]; + + assert(jcp.ic_block == 16); + const int src_stride = jcp.is * jcp.ic_block; + const int tr_src_stride = jcp.tr_is * jcp.ic_block; + + const int my_work = end - start; + for (int iwork = 0; iwork < my_work; iwork++) { + auto par_trans = jit_src_transpose_s(); + assert(sp_size % 4 == 0 || sp_size % 4 == jcp.is % 4); + par_trans.size = sp_size; + par_trans.src = src1; + par_trans.tr_src = tr_src1; + par_trans.src_prf = src1 + 64 * 16; + par_trans.tr_src_prf = tr_src1 + 80 * 16; + trans_kernel_->jit_ker(&par_trans); + + src1 += src_stride; + tr_src1 += tr_src_stride; + } + }; + + auto ker = [&](const int ithr, const int nthr) { + assert(nthr == jcp.nthr); + assert(IMPLICATION(!mkldnn_thr_syncable(), jcp.nthr_mb == 1)); + + const int ithr_ic_b = ithr % jcp.nthr_ic_b; + const int ithr_oc_b = ithr / jcp.nthr_ic_b % jcp.nthr_oc_b; + const int ithr_g = ithr / jcp.nthr_ic_b / jcp.nthr_oc_b % jcp.nthr_g; + const int ithr_mb = ithr / jcp.nthr_ic_b / jcp.nthr_oc_b / + jcp.nthr_g; + + const int ithr_but_oc + = (ithr_mb * jcp.nthr_g + ithr_g) * jcp.nthr_ic_b + ithr_ic_b; + + /* reduction dimension */ + int mb_sp_b_start{ 0 }, mb_sp_b_end{ 0 }; + if (jcp.transpose_src && jcp.nthr_mb < jcp.mb / 2) { + // it's preferable to parallelize by mb if possible + int img_start{ 0 }, img_end{ 0 }; + balance211(jcp.mb, jcp.nthr_mb, ithr_mb, img_start, img_end); + mb_sp_b_start = img_start * sp_nb; + mb_sp_b_end = img_end * sp_nb; + } + else { + balance211(mb_sp_work, jcp.nthr_mb, ithr_mb, mb_sp_b_start, + mb_sp_b_end); + } + + /* independent dimensions */ + int g_start{ 0 }, oc_b_start{ 0 }, ic_b_start{ 0 }; + int g_end{ 0 }, oc_b_end{ 0 }, ic_b_end{ 0 }; + + balance211(jcp.ngroups, jcp.nthr_g, ithr_g, g_start, g_end); + balance211(jcp.nb_load, jcp.nthr_oc_b, ithr_oc_b, oc_b_start, + oc_b_end); + balance211(jcp.nb_bcast, jcp.nthr_ic_b, ithr_ic_b, ic_b_start, + ic_b_end); + + const int g_work = g_end - g_start; + const int oc_b_work = oc_b_end - oc_b_start; + const int ic_b_work = ic_b_end - ic_b_start; + + data_t *diff_wei = ithr_mb == 0 + ? diff_weights : wei_reduction + (ithr_mb - 1) * wei_size; + + int sp_b_step = 0; + for (int mb_sp_b = mb_sp_b_start; mb_sp_b < mb_sp_b_end; + mb_sp_b += sp_b_step) { + int img{ 0 }, sp_b{ 0 }; + nd_iterator_init(mb_sp_b, img, jcp.mb, sp_b, sp_nb); + sp_b_step = step(jcp.nb_reduce_blocking, + nstl::min(sp_nb - sp_b, mb_sp_b_end - mb_sp_b), + jcp.nb_reduce_blocking_max); + + for (int g = g_start; g < g_end; ++g) { + int load_step = 0; + int bcast_step = 0; + for (int ic_b = ic_b_start; ic_b < ic_b_end; + ic_b += bcast_step) { + bcast_step = step(nb_ic_blocking, ic_b_end - ic_b, + jcp.nb_bcast_blocking_max); + if (jcp.transpose_src) { + if (jcp.nthr_oc_b > 1) + simple_barrier::barrier( + &tr_src_bctx[ithr_but_oc], jcp.nthr_oc_b); + const int sp_size + = nstl::min(sp_b_step * jcp.reduce_block, + jcp.is - sp_b * jcp.reduce_block); + uker_trans(ithr_mb, img, sp_b, sp_size, g, 1, ic_b, + bcast_step, ithr_oc_b, jcp.nthr_oc_b, ic_b_start); + if (jcp.nthr_oc_b > 1) + simple_barrier::barrier( + &tr_src_bctx[ithr_but_oc], jcp.nthr_oc_b); + } + + for (int oc_b = oc_b_start; oc_b < oc_b_end; + oc_b += load_step) { + load_step = step(nb_oc_blocking, oc_b_end - oc_b, + jcp.nb_load_blocking_max); + const int _ic_b = g * nb_ic + ic_b; + const int _ic_b_tr = g * nb_ic + ic_b_start; + const int _oc_b = g * nb_oc + oc_b; + + data_t *store_to; + + const size_t off + = wht_blk_off(diff_weights_d, g, oc_b, ic_b); + store_to = diff_wei + off; + + const data_t *diff_src = jcp.transpose_src ? + &tr_src[tr_src_off(ithr_mb, _ic_b_tr, 0)] : + &src[src_d.blk_off(img, _ic_b)]; + + int sp_b_end = sp_b + sp_b_step; + const data_t *pdiff_dst + = &diff_dst[diff_dst_d.blk_off(img, _oc_b)]; + const data_t *local_src = diff_src; + + auto p = jit_1x1_conv_call_s(); + auto rp = rtus_driver_t::call_params_t(); + + p.output_stride + = jcp.ic * jcp.oc_block * jcp.typesize_out; + + p.load_dim = load_step * jcp.oc_block; + + p.bcast_dim = bcast_step * jcp.ic_block; + rp.icb = bcast_step; + p.output_data = store_to; + + p.reduce_dim = sp_b_step * jcp.reduce_block; + rp.os = p.reduce_dim; + + p.first_last_flag = 0 + | (mb_sp_b == mb_sp_b_start ? FLAG_REDUCE_FIRST : 0) + | (sp_b_end == sp_nb ? FLAG_SP_LAST : 0); + + int sp = sp_b * jcp.reduce_block; + p.load_data = pdiff_dst + sp * jcp.oc_block; + + if (pd()->rtus_.reduce_src_) { + const int oh = sp / jcp.ow; + const int ow = sp % jcp.ow; + + const int ih = nstl::max(oh * stride_h - pad_t, 0); + const int iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + rp.ws = rtus_space + + ithr * pd()->rtus_.space_per_thread_ + + sp * jcp.ic_block; + + if (ndims == 3) + rp.src = local_src + iw + * src_d.blocking_desc().strides[2]; + else + rp.src = local_src + ih + * src_d.blocking_desc().strides[2] + + iw * src_d.blocking_desc().strides[3]; + rtus_driver_->ker_(&rp); + + p.bcast_data = rp.ws; + } else + p.bcast_data = local_src + sp * jcp.ic_block; + + kernel_->jit_ker(&p); + } + } + } + } + + /* diff_weights[:] += sum(wei_reduction[thr_mb][:]) */ + if (jcp.nthr_mb > 1) { + simple_barrier::barrier(&reduction_barrier, jcp.nthr); + const int work = g_work * oc_b_work * ic_b_work; + int start{ 0 }, end{ 0 }; + balance211(work, jcp.nthr_mb, ithr_mb, start, end); + if (start == end) + return; + + for (int thr_mb = 1; thr_mb < jcp.nthr_mb; ++thr_mb) { + int w = start; + int sub_g_start{ 0 }, sub_oc_b_start{ 0 }, + sub_ic_b_start{ 0 }; + nd_iterator_init(w, sub_g_start, g_work, sub_oc_b_start, + oc_b_work, sub_ic_b_start, ic_b_work); + while (w < end) { + const int g = g_start + sub_g_start; + const int oc_b = oc_b_start + sub_oc_b_start; + const int ic_b = ic_b_start + sub_ic_b_start; + + const int acc_size + = nstl::min(end - w, ic_b_work - sub_ic_b_start) + * jcp.ic_block * jcp.oc_block; + + const size_t off + = wht_blk_off(diff_weights_d, g, oc_b, ic_b); + data_t *d = diff_weights + off; + data_t *s = wei_reduction + (thr_mb - 1) * wei_size + off; + + acc_ker_->accumulate(d, s, acc_size); + + nd_iterator_jump(w, end, sub_g_start, g_work, + sub_oc_b_start, oc_b_work, sub_ic_b_start, + ic_b_work); + } + } + } + }; + + auto ker_bias = [&](int ithr, int nthr) { + assert(nthr == rb->balancer().nthr_); + + const int b_job_start = rb->balancer().ithr_job_off(ithr); + const int b_njobs = rb->balancer().ithr_njobs(ithr); + + if (b_njobs == 0) + return; + + /* reduction dimension */ + int img_start{ 0 }, img_end{ 0 }; + + balance211(jcp.mb, rb->balancer().nthr_per_group_, + rb->balancer().id_in_group(ithr), img_start, img_end); + + /* jobs */ + int g_start{ 0 }, ocb_start{ 0 }; + nd_iterator_init( + b_job_start, g_start, jcp.ngroups, ocb_start, jcp.nb_load); + + for (int img = img_start; img < img_end; ++img) { + int g = g_start, ocb = ocb_start; + for (int b_job_loc = 0; b_job_loc < b_njobs; ++b_job_loc) { + const size_t _oc = g * jcp.nb_load + ocb; + + const data_t *d_dst = &diff_dst[diff_dst_d.blk_off(img, _oc)]; + data_t *d_bias = rb->get_local_ptr(ithr, diff_bias, + reducer_bia_scratchpad) + + b_job_loc * rb->balancer().job_size_; + + if (img == img_start) + for (int o = 0; o < 16; ++o) + d_bias[o] = 0.; + + for (int hw = 0; hw < jcp.oh * jcp.ow; ++hw) { + PRAGMA_OMP_SIMD() + for (int o = 0; o < 16; ++o) + d_bias[o] += d_dst[o]; + d_dst += 16; + } + + nd_iterator_step(g, jcp.ngroups, ocb, jcp.nb_load); + } + } + rb->reduce(ithr, diff_bias, reducer_bia_scratchpad); + }; + + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + ker(ithr, jcp.nthr); + if (pd()->with_bias()) + ker_bias(ithr, jcp.nthr); + }); + + /* TODO: put this in ker_bias */ + if (pd()->wants_padded_bias()) { + assert(jcp.ngroups == 1); + utils::array_copy(diff_bias_in, diff_bias, jcp.oc_without_padding); + } +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.hpp new file mode 100644 index 000000000000..2e9fda76d628 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_1x1_convolution.hpp @@ -0,0 +1,344 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_COMMON_1x1_CONVOLUTION_HPP +#define CPU_JIT_AVX512_COMMON_1x1_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" +#include "cpu_reducer.hpp" + +#include "jit_avx512_common_1x1_conv_kernel.hpp" +#include "jit_uni_1x1_conv_utils.hpp" +#include "jit_transpose_src_utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_avx512_common_1x1_convolution_fwd_t : public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", avx512_common, ""), + jit_avx512_common_1x1_convolution_fwd_t); + + status_t init() { + using namespace utils; + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, wei_type, dst_type, dst_type, + data_type::undef) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *src_d = src_md(); + rtus_prepare(this, conv_d, src_d, dst_md()); + + status_t status = jit_avx512_common_1x1_conv_kernel::init_conf( + jcp_, *conv_d, *src_d, *weights_md(), *dst_md(), *attr(), + mkldnn_get_max_threads(), rtus_.reduce_src_); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_common_1x1_conv_kernel::init_scratchpad(scratchpad, + jcp_); + + rtus_prepare_space_info(this, scratchpad); + + return status::success; + } + + jit_1x1_conv_conf_t jcp_; + reduce_to_unit_stride_t rtus_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = utils::pick(2 * ndims() - 6 + with_groups(), + OIw16i16o, gOIw16i16o, OIhw16i16o, gOIhw16i16o); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx512_common_1x1_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr), rtus_driver_(nullptr) + { + kernel_ = + new jit_avx512_common_1x1_conv_kernel(pd()->jcp_, *pd()->attr()); + init_rtus_driver(this); + } + + ~jit_avx512_common_1x1_convolution_fwd_t() { + delete kernel_; + delete rtus_driver_; + } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + + private: + void execute_forward(const exec_ctx_t &ctx) const; + void execute_forward_thr(const int ithr, const int nthr, + const src_data_t *src, const wei_data_t *weights, + const dst_data_t *bias, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_common_1x1_conv_kernel *kernel_; + rtus_driver_t *rtus_driver_; +}; + +using jit_avx512_common_1x1_convolution_fwd_f32_t + = jit_avx512_common_1x1_convolution_fwd_t; + +template +struct jit_avx512_common_1x1_convolution_bwd_data_t : public cpu_primitive_t { + struct pd_t : public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", avx512_common, ""), + jit_avx512_common_1x1_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(diff_src_type, wei_type, data_type::undef, + diff_dst_type, data_type::undef) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *diff_src_d = diff_src_md(); + rtus_prepare(this, conv_d, diff_src_d, diff_dst_md()); + + status_t status = jit_avx512_common_1x1_conv_kernel::init_conf( + jcp_, *conv_d, *diff_src_d, *weights_md(), *diff_dst_md(), + *attr(), mkldnn_get_max_threads(), rtus_.reduce_src_); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_common_1x1_conv_kernel::init_scratchpad(scratchpad, + jcp_); + + rtus_prepare_space_info(this, scratchpad); + + return status::success; + } + + // TODO (Roma): structs conf header cleanup + jit_1x1_conv_conf_t jcp_; + reduce_to_unit_stride_t rtus_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = utils::pick(2 * ndims() - 6 + with_groups(), + IOw16o16i, gIOw16o16i, IOhw16o16i, gIOhw16o16i); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx512_common_1x1_convolution_bwd_data_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr), rtus_driver_(nullptr) + { + kernel_ = new jit_avx512_common_1x1_conv_kernel(pd()->jcp_, + *pd()->attr()); + init_rtus_driver(this); + } + + ~jit_avx512_common_1x1_convolution_bwd_data_t() { + delete kernel_; + delete rtus_driver_; + } + + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type diff_src_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + + private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_common_1x1_conv_kernel *kernel_; + rtus_driver_t *rtus_driver_; +}; + +using jit_avx512_common_1x1_convolution_bwd_data_f32_t + = jit_avx512_common_1x1_convolution_bwd_data_t; + +struct jit_avx512_common_1x1_convolution_bwd_weights_t : public cpu_primitive_t +{ + struct pd_t : public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", avx512_common, ""), + jit_avx512_common_1x1_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *src_d = src_md(); + rtus_prepare(this, conv_d, src_d, diff_dst_md()); + + status_t status = jit_avx512_common_1x1_conv_kernel::init_conf( + jcp_, *conv_d, *src_d, *diff_weights_md(), *diff_dst_md(), + *attr(), mkldnn_get_max_threads(), rtus_.reduce_src_); + if (status != status::success) return status; + + init_balancers(); + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_common_1x1_conv_kernel::init_scratchpad(scratchpad, + jcp_); + + auto reducer_bia_scratchpad = memory_tracking::registrar_t( + scratchpad, memory_tracking::names::prefix_reducer_bia); + reducer_bia_conf_.init_scratchpad(reducer_bia_scratchpad); + + rtus_prepare_space_info(this, scratchpad); + + return status::success; + } + + // TODO (Roma): structs conf header cleanup + jit_1x1_conv_conf_t jcp_; + cpu_reducer_t::conf_t reducer_bia_conf_; + reduce_to_unit_stride_t rtus_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = utils::pick(2 * ndims() - 6 + with_groups(), + OIw16i16o, gOIw16i16o, OIhw16i16o, gOIhw16i16o); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + + private: + void init_balancers() { + const size_t max_buffer_size = jcp_.nthr * 3 * 5 * 5 * 16 * 16; + if (with_bias()) { + reducer_bia_conf_.init(reduce_balancer_t(jcp_.nthr, + jcp_.oc_block, jcp_.ngroups * jcp_.nb_load, + jcp_.mb, max_buffer_size)); + } + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx512_common_1x1_convolution_bwd_weights_t(const pd_t *apd); + + ~jit_avx512_common_1x1_convolution_bwd_weights_t() { + delete kernel_; + delete acc_ker_; + delete reducer_bias_; + delete rtus_driver_; + delete trans_kernel_; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + + private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_common_1x1_conv_kernel *kernel_; + cpu_accumulator_1d_t *acc_ker_; + cpu_reducer_t *reducer_bias_; + jit_transpose4x16_src *trans_kernel_; + rtus_driver_t *rtus_driver_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp new file mode 100644 index 000000000000..235fb02fefde --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp @@ -0,0 +1,4539 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_barrier.hpp" + +#include "jit_avx512_common_conv_kernel.hpp" + +#define GET_OFF(field) offsetof(jit_conv_call_s, field) +#define KNx_L2_EFFECTIVE_CAPACITY ((512-64)*1024) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +namespace { + +constexpr auto small_spatial = 14; +unsigned int L1_cache_size = get_cache_size(1, true); + +inline void pick_loop_order(jit_conv_conf_t &jcp) { + using namespace prop_kind; + assert(one_of(jcp.prop_kind, + forward_training, forward_inference, backward_data)); + auto w = (jcp.prop_kind == backward_data) ? jcp.iw : jcp.ow; + auto h = (jcp.prop_kind == backward_data) ? jcp.ih : jcp.oh; + + // ow-threading is currently implemented for forward only + // TODO: single code for fwd and bwd after ow-thr for bwd + // meaningless switch was removed + if (jcp.prop_kind == backward_data) { + jcp.loop_order = (w <= small_spatial && h <= small_spatial) + ? loop_cgn : loop_gnc; + } else { + jcp.loop_order = (w <= small_spatial && h <= small_spatial) + ? loop_cwgn : loop_gncw; + } +} + +inline bool is_1stconv(const jit_conv_conf_t &jcp) { + if (mayiuse(avx512_core)) + return (jcp.ic < 16 && jcp.ngroups == 1); + else + return one_of(jcp.ic, 1, 3); +} + +inline bool is_ow_threading_on(const jit_conv_conf_t &jcp) { + return (jcp.nb_ow > 1); +} + +inline bool is_owb_prefetching(const jit_conv_conf_t &jcp) { + return (jcp.ver == ver_4fma && is_ow_threading_on(jcp)); +} + +} + +template +void _jit_avx512_common_conv_fwd_kernel::prepare_output(int ur_w) +{ + for (int k = 0; k < jcp.nb_oc_blocking; k++) + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + vpxord(vmm, vmm, vmm); + if (!is_owb_prefetching(jcp)) { + size_t aux_output_offset = get_output_offset(j, k); + mic_prefetcht1(EVEX_compress_addr_safe(reg_out_prf, + aux_output_offset, reg_out_long_offt)); + } + } +} + +template +void _jit_avx512_common_conv_fwd_kernel::store_output(int ur_w) +{ + Label no_update_label, store_label, eltwise_label; + + mov(reg_channel, ptr[param1 + GET_OFF(channel)]); + if (jcp.with_bias) { + mov(reg_bias, ptr[param1 + GET_OFF(bias)]); + } + + if (!jcp.with_sum) { + cmp(reg_channel, 0); + je(no_update_label, T_NEAR); + } + + for (int k = 0; k < jcp.nb_oc_blocking; k++) + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + size_t aux_output_offset = get_output_offset(j, k); + vaddps(vmm, + make_safe_addr(reg_out, aux_output_offset, reg_out_long_offt)); + } + + if (!jcp.with_sum) { + jmp(eltwise_label, T_NEAR); + } else { + cmp(reg_channel, 0); + jne(eltwise_label, T_NEAR); + } + + L(no_update_label); + if (jcp.with_bias) { + for (int k = 0; k < jcp.nb_oc_blocking; k++) { + int bias_offset = jcp.typesize_out * k * jcp.oc_block; + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + vaddps(vmm, EVEX_compress_addr(reg_bias, bias_offset)); + } + mic_prefetcht1(EVEX_compress_addr(reg_bias, bias_offset + 64)); + } + } + + L(eltwise_label); + if (jcp.with_eltwise) { + cmp(reg_channel, jcp.nb_ic - 1); + jl(store_label, T_NEAR); + + if (ur_w == jcp.ur_w) { + eltwise_injector_->compute_vector_range(0, + jcp.nb_oc_blocking * jcp.ur_w); + } else { + for (int k = 0; k < jcp.nb_oc_blocking; k++) + eltwise_injector_->compute_vector_range(k * jcp.ur_w, + k * jcp.ur_w + ur_w); + } + } + + L(store_label); + for (int k = 0; k < jcp.nb_oc_blocking; k++) + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + size_t aux_output_offset = (size_t)typesize * + ((size_t)k * jcp.od * jcp.oh * jcp.ow + j) * jcp.oc_block; + vmovups(EVEX_compress_addr_safe(reg_out, aux_output_offset, + reg_out_long_offt), vmm); + if (!is_owb_prefetching(jcp)) + mic_prefetcht0(EVEX_compress_addr_safe(reg_out_prf, + aux_output_offset, reg_out_long_offt)); + } +} + +template +void _jit_avx512_common_conv_fwd_kernel::compute_loop_4fma_1st(int ur_w, + int pad_l, int pad_r) +{ +} + +template<> +void _jit_avx512_common_conv_fwd_kernel::compute_loop_4fma_1st(int ur_w, + int pad_l, int pad_r) +{ + assert(jcp.dilate_d == 0 && jcp.dilate_h == 0 && jcp.dilate_w == 0); + + int iw = jcp.iw; + int ih = jcp.ih; + int kw = jcp.kw; + int stride_w = jcp.stride_w; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + + Label kh_label, kd_label; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_inp, reg_inp); + mov(aux_reg_ker, reg_ker); + mov(aux_reg_inp_prf, reg_inp_prf); + } + + size_t max_input_offset = (size_t)jcp.typesize_in + * ((size_t)(kw + ur_w * stride_w - pad_l) + + (size_t)ic_block * iw * ih * jcp.id); + assert(reg_inp_prf == reg_long_offt); + if (max_input_offset > INT_MAX) push(reg_inp_prf); + + if (jcp.ndims == 5) { + push(reg_out_prf); + push(reg_out); + + mov(reg_ki, ptr[param1 + GET_OFF(kd_padding)]); + mov(aux_reg_ker_d, ptr[param1 + GET_OFF(filt)]); + mov(aux_reg_inp_d, reg_inp); + mov(aux_reg_inp_d_prf, reg_inp_prf); + + L(kd_label); + } + mov(reg_kj, reg_kh); + if (jcp.ndims == 5) { + mov(aux_reg_inp, aux_reg_inp_d); + mov(aux_reg_ker, aux_reg_ker_d); + mov(aux_reg_inp_prf, aux_reg_inp_d_prf); + } + + L(kh_label); + for (int ki = 0; ki < kw; ki += 4) { + for (int ic = 0; ic < ic_block; ic++) { + for (int i = 0; i < 4; i++) { + int aux_ker_offset + = jcp.typesize_in + * ((ki + i) * oc_block + + ic * kw * jcp.kh * jcp.kd * oc_block); + if (ki + i < kw) + vmovups(vmm_ker(i), + EVEX_compress_addr(aux_reg_ker, aux_ker_offset)); + else + vpxord(vmm_ker(i), vmm_ker(i), vmm_ker(i)); + } + + int j_start = get_ow_start(ki, pad_l); + int j_end = get_ow_end(ur_w, ki, pad_r); + + for (int j = j_start, prf_count=0; j < j_end; j++) { + size_t aux_input_offset = (size_t)jcp.typesize_in + * ((size_t)(ki + j * stride_w + - pad_l) + (size_t)ic * iw * ih * jcp.id); + v4fmaddps(vmm_out(j, 0), vmm_ker(0), + EVEX_compress_addr_safe(aux_reg_inp, aux_input_offset, + reg_long_offt)); + if (ki + prf_count < kw && prf_count < 4 + && ((ki < 2 && j % 4) || j % 2)) { + int aux_ker_offset = jcp.typesize_in + * ((ki + prf_count) * oc_block + + ic * kw * jcp.kh * jcp.kd * oc_block + kw * oc_block); + mic_prefetcht0(EVEX_compress_addr(aux_reg_ker, + aux_ker_offset)); + prf_count++; + } + if (ki == 0 + && j % (64 / (stride_w * jcp.typesize_in)) == 0) { + mic_prefetcht0(EVEX_compress_addr_safe(aux_reg_inp_prf, + aux_input_offset, reg_long_offt)); + } + if (ki == 1 + && j % (64 / (stride_w * jcp.typesize_in)) == 0) { + mic_prefetcht0(EVEX_compress_addr_safe(aux_reg_inp, + aux_input_offset+jcp.typesize_in * iw, reg_long_offt)); + } + } + } + } + add(aux_reg_ker, jcp.typesize_in * kw * oc_block); + add(aux_reg_inp, jcp.typesize_in * iw); + add(aux_reg_inp_prf, jcp.typesize_in * iw); + + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + + if (jcp.ndims == 5) { + add(aux_reg_inp_d, typesize * jcp.ih * jcp.iw); + add(aux_reg_ker_d, typesize * jcp.kw * jcp.kh * oc_block); + add(aux_reg_inp_d_prf, typesize * jcp.ih * jcp.iw); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + + pop(reg_out); + pop(reg_out_prf); + } + + if (max_input_offset > INT_MAX) pop(reg_inp_prf); +} + +template +void _jit_avx512_common_conv_fwd_kernel::compute_loop_4fma(int ur_w, + int pad_l, int pad_r) +{ +} + +template<> +void _jit_avx512_common_conv_fwd_kernel::compute_loop_4fma(int ur_w, + int pad_l, int pad_r) +{ + int stride_w = jcp.stride_w; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + Label kh_label, last_iter_label, loop_end_label, kd_label; + int ker_load_number = 4; + int shift_kernel_ptr = typesize * jcp.kw * jcp.oc_block * jcp.ic_block; + int shift_input_ptr = typesize * (jcp.dilate_h + 1) * jcp.iw * jcp.ic_block; + + bool check_last_kh = (jcp.kh > 3); + bool pref_current_inp = (jcp.iw < 14 || jcp.iw > 28); + + int oi_ipref_t0 = get_ow_start(0, pad_l); + int ow_end_ipref = get_ow_end(ur_w, 0, pad_r); + + assert(jcp.oc % jcp.nb_oc_blocking == 0); + + auto kernel_offset = [=](int ocb, int ic, int ki) { + int blk_idx = ocb * jcp.nb_ic * jcp.kh * jcp.kw * jcp.kd + ki; + int blk_offset = blk_idx * jcp.oc_block * jcp.ic_block; + int ic_offset = ic * jcp.oc_block; + return typesize * (blk_offset + ic_offset); + }; + auto kernel_loads = [=](int ki, int ic, int kk) { + for (int ii = 0; ii < ker_load_number; ii++) { + int aux_kernel_offset = kernel_offset(kk, ic + ii, ki); + vmovups(vmm_ker(ii), + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + } + }; + auto prefetch_inp_next_kh = [&](int ki, int ki_start, int cnt0, int cnt1) { + if (cnt1 >= ker_load_number && cnt0 >= ker_load_number + && ki >= ki_start && oi_ipref_t0 < ow_end_ipref) { + int aux_inp_offset + = typesize + * ((oi_ipref_t0 * stride_w - pad_l) * ic_block + + (jcp.dilate_h + 1) * jcp.iw * ic_block); + prefetcht0(EVEX_compress_addr(aux_reg_inp, + aux_inp_offset)); + oi_ipref_t0++; + } + }; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_inp, reg_inp); + mov(aux_reg_ker, reg_ker); + mov(aux_reg_ker_prf, reg_ker_prf); + mov(aux_reg_inp_prf, reg_inp_prf); + } + + if (jcp.ndims == 5) { + push(reg_out_prf); + push(reg_out); + + mov(reg_ki, ptr[param1 + GET_OFF(kd_padding)]); + mov(aux_reg_ker_d, ptr[param1 + GET_OFF(filt)]); + mov(aux_reg_inp_d, reg_inp); + mov(aux_reg_inp_d_prf, reg_inp_prf); + mov(aux_reg_ker_d_prf, reg_ker_prf); + L(kd_label); + mov(reg_kj, ptr[param1 + GET_OFF(kh_padding)]); + } else { + mov(reg_kj, reg_kh); + } + if (jcp.ndims == 5) { + mov(aux_reg_inp, aux_reg_inp_d); + mov(aux_reg_ker, aux_reg_ker_d); + mov(aux_reg_ker_prf, aux_reg_ker_d_prf); + mov(aux_reg_inp_prf, aux_reg_inp_d_prf); + } + + align(16); + L(kh_label); + int kw = jcp.kw; + if (check_last_kh) { + for (int ki = 0; ki < kw; ki++) + for (int ic = 0; ic < ic_block; ic += 4) + for (int kk = 0; kk < jcp.nb_oc_blocking; kk++) { + bool last_kernel_loads = (kk == jcp.nb_oc_blocking - 1 + && ki == kw - 1 && (ic + 4) == ic_block); + + if (last_kernel_loads) { + cmp(reg_kj, 1); + je(last_iter_label, T_NEAR); + } + + kernel_loads(ki, ic, kk); + for (int oi = get_ow_start(ki, pad_l), prf_count_t1 = 0, + prf_count_t0 = 0; + oi < get_ow_end(ur_w, ki, pad_r); oi++) { + int aux_input_offset = typesize + * ((ki * (jcp.dilate_w + 1) + oi * stride_w + - pad_l) * ic_block + + ic); + v4fmaddps(vmm_out(oi, kk), vmm_ker(0), + EVEX_compress_addr(aux_reg_inp, aux_input_offset)); + + if (oi % 2) { + if (prf_count_t0 < 4) { + int aux_kernel_prf; + if (last_kernel_loads) + aux_kernel_prf= kernel_offset(0, + prf_count_t0 + ic + 4 + - ic_block, 0) + typesize * kw + * oc_block * ic_block; + else + aux_kernel_prf = kernel_offset(kk, ic + 4 + + prf_count_t0, ki); + mic_prefetcht0(EVEX_compress_addr(aux_reg_ker, + aux_kernel_prf)); + prf_count_t0++; + } else if (prf_count_t1 < 4) { + mic_prefetcht1(EVEX_compress_addr( + aux_reg_ker_prf, kernel_offset(kk, ic + + prf_count_t1, ki))); + prf_count_t1++; + } + } else + prefetch_inp_next_kh(ki, 2, prf_count_t0, + prf_count_t1); + } + + if (last_kernel_loads) { + jmp(loop_end_label, T_NEAR); + + L(last_iter_label); + + kernel_loads(ki, ic, kk); + for (int oi = get_ow_start(ki, pad_l), prf_count_t1 = 0, + prf_count_t0 = 0; + oi < get_ow_end(ur_w, ki, pad_r); oi++) { + int aux_input_offset = typesize + * ((ki * (jcp.dilate_w + 1) + oi * stride_w + - pad_l) * ic_block + + ic); + v4fmaddps(vmm_out(oi, kk), vmm_ker(0), + EVEX_compress_addr(aux_reg_inp, + aux_input_offset)); + if (oi % 2) { + if (prf_count_t0 < 4) { + mic_prefetcht0(EVEX_compress_addr( + aux_reg_ker_prf, kernel_offset(0, + prf_count_t0, 0))); + prf_count_t0++; + } else if (prf_count_t1 < 4) { + mic_prefetcht1(EVEX_compress_addr( + aux_reg_ker_prf, kernel_offset(kk, + ic + prf_count_t1, ki))); + prf_count_t1++; + } + } + } + L(loop_end_label); + } + } + } else { + for (int ki = 0; ki < kw; ki++) + for (int ic = 0; ic < ic_block; ic += 4) + for (int kk = 0; kk < jcp.nb_oc_blocking; kk++) { + kernel_loads(ki, ic, kk); + for (int oi = get_ow_start(ki, pad_l), + prf_count_t1 = 0, prf_count_t0 = 0; + oi < get_ow_end(ur_w, ki, pad_r); oi++) { + int aux_input_offset = typesize + * ((ki * (jcp.dilate_w + 1) + oi * stride_w + - pad_l) * ic_block + ic); + v4fmaddps(vmm_out(oi, kk), vmm_ker(0), + EVEX_compress_addr(aux_reg_inp, + aux_input_offset)); + + if (!is_owb_prefetching(jcp)) { + if ((oi % 2) && (prf_count_t1 < 4)) { + mic_prefetcht1(EVEX_compress_addr( + aux_reg_ker_prf, kernel_offset(kk, + ic + prf_count_t1, ki))); + prf_count_t1++; + } + } else { + if (!(ki == 0 && ic == 0) + && !(ki == kw-1 && ic == 0) && + (oi % 2) && (prf_count_t1 < 4) + ) { + mic_prefetcht0(EVEX_compress_addr( + aux_reg_ker, kernel_offset(kk, + ic + 4 + prf_count_t0, ki))); + prf_count_t0++; + } + } + if (!is_owb_prefetching(jcp)) { + if (pref_current_inp) { + if (ki == 0 && ic == 0 && kk == 0) + mic_prefetcht0(EVEX_compress_addr( + aux_reg_inp, + aux_input_offset + shift_input_ptr)); + } else { + if (ki == 1 && ic == 0 && kk == 0) + mic_prefetcht1(EVEX_compress_addr( + aux_reg_inp_prf, aux_input_offset)); + } + } else { + int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + int inp_shift + = jcp.typesize_in * ur_w * stride_w * inp_mult; + bool kk_pref_slot = kk ? oi % 2 : !(oi % 2); + if (ki == 0 && ic == 0 && kk_pref_slot) + mic_prefetcht1(EVEX_compress_addr( + aux_reg_inp, + aux_input_offset + inp_shift)); + + if (ki == kw - 1 && ic == 0 && kk_pref_slot) + mic_prefetcht0(EVEX_compress_addr( + aux_reg_inp, + aux_input_offset + inp_shift)); + } + } + } + } + + add(aux_reg_ker, shift_kernel_ptr); + add(aux_reg_inp, shift_input_ptr); + add(aux_reg_ker_prf, shift_kernel_ptr); + add(aux_reg_inp_prf, shift_input_ptr); + + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + + if (jcp.ndims == 5) { + add(aux_reg_inp_d, + typesize * (jcp.dilate_d + 1) * jcp.ih * jcp.iw * jcp.ic_block); + add(aux_reg_ker_d, typesize * jcp.kw * jcp.kh * jcp.oc_block + * jcp.ic_block); + add(aux_reg_inp_d_prf, + typesize * (jcp.dilate_d + 1) * jcp.ih * jcp.iw * jcp.ic_block); + add(aux_reg_ker_d_prf, typesize * jcp.kw * jcp.kh * jcp.oc_block + * jcp.ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + + pop(reg_out); + pop(reg_out_prf); + } +} + +template +void _jit_avx512_common_conv_fwd_kernel::compute_loop_fma(int ur_w, + int pad_l, int pad_r) +{ + bool prf_ker = true; + bool prf_inp = true; + int ih = jcp.ih; + int stride_w = jcp.stride_w; + int id = jcp.id; + int iw = jcp.iw; + int kw = jcp.kw; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int nb_oc_block = jcp.nb_oc_blocking; + Label kh_label, kd_label; + + int ker_pipeline_depth = 4; + assert(ker_reg_base_idx + ker_pipeline_depth <= 32); + assert(oc_block >= ker_pipeline_depth); + + int num_ker_loads = ic_block * nb_oc_block * kw; + int num_ker_prfs = prf_ker ? num_ker_loads : 0; + int num_inp_prfs = prf_inp ? + ur_w * nstl::min(kw, stride_w) + nstl::max(0, kw - stride_w) : + 0; + if (jcp.is_1stconv && prf_inp) { + num_inp_prfs = div_up(num_inp_prfs, jcp.simd_w) * ic_block; + } + int num_prfs = num_ker_prfs + num_inp_prfs; + int num_fmas = num_ker_loads * ur_w; + int prf_inst_spacing + = (prf_ker || prf_inp) ? nstl::max(1, num_fmas / num_prfs) : 1; + int prf_inst_trigger = (num_fmas % prf_inst_spacing) / 2; + int inp_mul = !jcp.is_1stconv ? ic_block : 1; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_inp, reg_inp); + mov(aux_reg_ker, reg_ker); + mov(aux_reg_inp_prf, reg_inp_prf); + mov(aux_reg_ker_prf, reg_ker_prf); + } + + size_t max_input_offset = (size_t)jcp.typesize_in * ic_block * iw * ih * id; + assert(reg_inp_prf == reg_long_offt); + if (max_input_offset > INT_MAX) push(reg_inp_prf); + + + if (jcp.ndims == 5) { + push(reg_out_prf); + push(reg_out); + + mov(reg_ki, ptr[param1 + GET_OFF(kd_padding)]); + mov(aux_reg_ker_d, ptr[param1 + GET_OFF(filt)]); + mov(aux_reg_inp_d, reg_inp); + mov(aux_reg_inp_d_prf, reg_inp_prf); + mov(aux_reg_ker_d_prf, reg_ker_prf); + + L(kd_label); + mov(reg_kj, ptr[param1 + GET_OFF(kh_padding)]); + } else { + mov(reg_kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_inp, aux_reg_inp_d); + mov(aux_reg_ker, aux_reg_ker_d); + mov(aux_reg_ker_prf, aux_reg_ker_d_prf); + mov(aux_reg_inp_prf, aux_reg_inp_d_prf); + } + + align(16); + L(kh_label); + { + int step = 0; + int ker_prfs = 0; + for (int ki = 0; ki < kw; ki++) { + for (int ic = 0; ic < ic_block; ic++) { + int aux_kernel_offset = 0; + if (step == 0) { + for (int i = 0; i < ker_pipeline_depth; i++) { + aux_kernel_offset = get_kernel_offset(ki, ic, 0, i); + vmovups(vmm_ker(i), EVEX_compress_addr( + aux_reg_ker, aux_kernel_offset)); + } + } else if (step < num_ker_loads - ker_pipeline_depth + 1) { + int load_offset = ker_pipeline_depth - 1; + int ker_load_reg_idx + = (step + load_offset) % ker_pipeline_depth; + aux_kernel_offset + = get_kernel_offset(ki, ic, 0, load_offset); + vmovups(vmm_ker(ker_load_reg_idx), + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + } + + bool ker_prf_inserted = false; + Vmm vmm_kernel = vmm_ker(step % ker_pipeline_depth); + int j_start = get_ow_start(ki, pad_l); + int j_end = get_ow_end(ur_w, ki, pad_r); + for (int j = j_start; j < j_end; j++) { + size_t aux_input_offset = get_input_offset(ki, ic, j, pad_l); + auto addr = EVEX_compress_addr_safe(aux_reg_inp, + aux_input_offset, reg_long_offt, true); + vfmadd231ps(vmm_out(j, 0), vmm_kernel, addr); + int fma_idx = step * ur_w + j; + int prf_slot_idx = fma_idx / prf_inst_spacing; + if (fma_idx % prf_inst_spacing == prf_inst_trigger) { + if (prf_ker && !ker_prf_inserted + && ker_prfs < num_ker_prfs) { + int ker_prf_offset + = jcp.typesize_in * ker_prfs * jcp.oc_block; + mic_prefetcht2(EVEX_compress_addr( + aux_reg_ker_prf, ker_prf_offset)); + ker_prf_inserted = true; + ker_prfs++; + } else if (prf_inp) { + int inp_prf_idx = prf_slot_idx - ker_prfs; + if (inp_prf_idx < num_inp_prfs) { + size_t inp_prf_stride = nstl::max(kw, stride_w); + size_t inp_prf_offset; + if (!jcp.is_1stconv) { + inp_prf_offset + = ic_block * jcp.typesize_in + * ((inp_prf_idx / kw) + * inp_prf_stride + + (inp_prf_idx % kw)); + } else { + size_t ic_prf_stride = + (size_t)jcp.typesize_in * iw * ih * id; + size_t iw_prf_stride + = jcp.typesize_in * jcp.simd_w; + inp_prf_offset = ((inp_prf_idx / ic_block) + * iw_prf_stride + + (inp_prf_idx % ic_block) + * ic_prf_stride); + } + mic_prefetcht0(EVEX_compress_addr_safe( + aux_reg_inp_prf, inp_prf_offset, + reg_long_offt)); + } + } + } + } + step++; + } + } + add(aux_reg_ker, jcp.typesize_in * kw * oc_block * ic_block); + if (prf_ker) + add(aux_reg_ker_prf, jcp.typesize_in * kw * oc_block * ic_block); + add(aux_reg_inp, jcp.typesize_in * (jcp.dilate_h + 1) * iw * inp_mul); + if (prf_inp) + add(aux_reg_inp_prf, + jcp.typesize_in * (jcp.dilate_h + 1) * iw * inp_mul); + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + } + + + if (jcp.ndims == 5) { + add(aux_reg_inp_d, + typesize * (jcp.dilate_d + 1) * jcp.ih * jcp.iw * inp_mul); + add(aux_reg_ker_d, typesize * jcp.kw * jcp.kh * jcp.oc_block + * jcp.ic_block); + add(aux_reg_inp_d_prf, + typesize * (jcp.dilate_d + 1) * jcp.ih * jcp.iw * inp_mul); + add(aux_reg_ker_d_prf, typesize * jcp.kw * jcp.kh * jcp.oc_block + * jcp.ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + + pop(reg_out); + pop(reg_out_prf); + } + if (max_input_offset > INT_MAX) pop(reg_inp_prf); +} + +template +void _jit_avx512_common_conv_fwd_kernel::compute_loop_fma_core(int ur_w, + int pad_l, int pad_r) +{ + int kw = jcp.kw; + int stride_w = jcp.stride_w; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int nb_oc_block = jcp.nb_oc_blocking; + Label kh_label, kd_label; + int shift_kernel_ptr = jcp.typesize_in * jcp.kw * jcp.oc_block + * jcp.ic_block; + int inp_mul = !jcp.is_1stconv ? ic_block : 1; + int shift_input_ptr = jcp.typesize_in * (jcp.dilate_h + 1) * jcp.iw + * inp_mul; + + + auto input_offset = [=](int oi, int ic, int ki) { + return (size_t)jcp.typesize_in + * ((size_t)(ki * (jcp.dilate_w + 1) + oi * stride_w - pad_l) + * inp_mul + (size_t)ic + * (!jcp.is_1stconv ? 1 : (size_t)jcp.iw * jcp.ih * jcp.id)); + }; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_inp, reg_inp); + mov(aux_reg_ker, reg_ker); + } + + if (jcp.ndims == 5) { + push(reg_out); + + mov(reg_ki, ptr[param1 + GET_OFF(kd_padding)]); + mov(aux_reg_ker_d, ptr[param1 + GET_OFF(filt)]); + mov(aux_reg_inp_d, reg_inp); + + L(kd_label); + mov(reg_kj, ptr[param1 + GET_OFF(kh_padding)]); + } else { + mov(reg_kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_inp, aux_reg_inp_d); + mov(aux_reg_ker, aux_reg_ker_d); + } + + L(kh_label); + { + for (int ki = 0; ki < kw; ki++) { + int jj_start = get_ow_start(ki, pad_l); + int jj_end = get_ow_end(ur_w, ki, pad_r); + for (int ic = 0; ic < ic_block; ic++) { + if (jcp.kernel_kind == expl_bcast) { + for (int jj = jj_start; jj < jj_end; jj++) { + size_t aux_input_offset = input_offset(jj, ic, ki); + vbroadcastss(vmm_inp(jj, nb_oc_block), + EVEX_compress_addr_safe(aux_reg_inp, + aux_input_offset, reg_long_offt)); + } + } + for (int ii = 0; ii < nb_oc_block; ii++) { + int aux_kernel_offset = jcp.typesize_in + * (ii * jcp.nb_ic * jcp.kh * jcp.kw * jcp.kd * ic_block + * oc_block + ki * ic_block * oc_block + ic * oc_block); + if (jj_end - jj_start > 0) + vmovups(vmm_wei, EVEX_compress_addr(aux_reg_ker, + aux_kernel_offset)); + for (int jj = jj_start; jj < jj_end; jj++) + if (jcp.kernel_kind == expl_bcast) + vfmadd231ps(vmm_out(jj, ii), + vmm_inp(jj, nb_oc_block), vmm_wei); + else { + size_t aux_input_offset = input_offset(jj, ic, ki); + vfmadd231ps(vmm_out(jj, ii), vmm_wei, + EVEX_compress_addr_safe(aux_reg_inp, + aux_input_offset, reg_long_offt, true)); + } + } + } + } + add(aux_reg_ker, shift_kernel_ptr); + add(aux_reg_inp, shift_input_ptr); + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + } + + if (jcp.ndims == 5) { + add(aux_reg_inp_d, + typesize * (jcp.dilate_d + 1) * jcp.ih * jcp.iw * inp_mul); + add(aux_reg_ker_d, typesize * jcp.kw * jcp.kh * jcp.oc_block + * jcp.ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + + pop(reg_out); + } +} + +template +void _jit_avx512_common_conv_fwd_kernel::compute_loop(int ur_w, + int pad_l, int pad_r) +{ + if (jcp.ndims == 5) push(reg_oi); + + prepare_output(ur_w); + + Label skip_compute_loop; + if (jcp.ndims == 5) { + if ((jcp.dilate_d >= jcp.id) + || (jcp.kd - 1) * (jcp.dilate_d + 1) < nstl::max(jcp.f_pad, jcp.back_pad)) { + mov(reg_kj, ptr[param1 + GET_OFF(kd_padding)]); + cmp(reg_kj, 0); + je(skip_compute_loop, T_NEAR); + } + } + if ((jcp.dilate_h >= jcp.ih) + || (jcp.kh - 1) * (jcp.dilate_h + 1) < nstl::max(jcp.t_pad, jcp.b_pad)) { + mov(reg_kj, ptr[param1 + GET_OFF(kh_padding)]); + cmp(reg_kj, 0); + je(skip_compute_loop, T_NEAR); + } + + if (jcp.ver == ver_4fma) + if(jcp.is_1stconv) + compute_loop_4fma_1st(ur_w, pad_l, pad_r); + else + compute_loop_4fma(ur_w, pad_l, pad_r); + else if (jcp.ver == ver_fma) + if ((jcp.is_1stconv && jcp.kernel_kind != expl_bcast) + || mayiuse(avx512_mic)) + compute_loop_fma(ur_w, pad_l, pad_r); + else + if (jcp.kernel_kind == embd_bcast && jcp.nb_oc_blocking == 1) + compute_loop_fma(ur_w, pad_l, pad_r); + else + compute_loop_fma_core(ur_w, pad_l, pad_r); + else + assert(!"unknown convolution version"); + + L(skip_compute_loop); + store_output(ur_w); + if (jcp.ndims == 5) pop(reg_oi); +} + +template +void _jit_avx512_common_conv_fwd_kernel::generate() +{ + int iw = jcp.iw; + int ow = jcp.ow; + int ow_block = jcp.ow_block; + int nb_ow = jcp.nb_ow; + int kw = jcp.kw; + int l_pad = jcp.l_pad; + int ur_w = jcp.ur_w; + int ur_w_tail = jcp.ur_w_tail; + int dilate_w = jcp.dilate_w + 1; + int stride_w = jcp.stride_w; + + int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + int inp_shift_pad = jcp.typesize_in * (ur_w * stride_w - l_pad) * inp_mult; + int inp_shift = jcp.typesize_in * ur_w * stride_w * inp_mult; + int inp_shift_pad_second_block = -1 * jcp.typesize_in * l_pad * inp_mult; + int out_shift = jcp.typesize_out * ur_w * jcp.oc_block; + + preamble(); + mov(reg_inp, ptr[param1 + GET_OFF(src)]); + mov(reg_out, ptr[param1 + GET_OFF(dst)]); + mov(reg_ker, ptr[param1 + GET_OFF(filt)]); + mov(reg_ker_prf, ptr[param1 + GET_OFF(filt_prf)]); + mov(reg_kh, ptr[param1 + GET_OFF(kh_padding)]); + + int r_pad = nstl::max( + 0, (ow - 1) * stride_w + (kw - 1) * dilate_w - (iw + l_pad - 1)); + int n_oi = ow / ur_w; + int r_pad1 = (ur_w * n_oi - 1) * stride_w + (kw - 1) * dilate_w + - (iw + l_pad - 1); + + if (!is_ow_threading_on(jcp)) { + // ow is being processed as a whole - with left and right paddings + if (r_pad1 > 0) n_oi--; + + if (ow == ur_w) { + mov(reg_inp_prf, ptr[param1 + GET_OFF(src_prf)]); + mov(reg_out_prf, ptr[param1 + GET_OFF(dst_prf)]); + compute_loop(ur_w, l_pad, r_pad); + } else { + mov(reg_inp_prf, reg_inp); + mov(reg_out_prf, reg_out); + if (n_oi == 0) { + add(reg_inp_prf, inp_shift_pad); + add(reg_out_prf, out_shift); + compute_loop(ur_w, l_pad, r_pad1); + add(reg_inp, inp_shift_pad); + add(reg_out, out_shift); + if (ur_w_tail != 0) { + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w_tail, 0, r_pad); + } + } else { + xor_(reg_oi, reg_oi); + if (l_pad > 0) { + add(reg_inp_prf, inp_shift_pad); + add(reg_out_prf, out_shift); + compute_loop(ur_w, l_pad, 0); + add(reg_inp, inp_shift_pad); + add(reg_out, out_shift); + inc(reg_oi); + } + if ((l_pad <= 0 && n_oi > 0) || (l_pad > 0 && n_oi > 1)) { + Label ow_loop_label; + L(ow_loop_label); + { + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w, 0, 0); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + inc(reg_oi); + cmp(reg_oi, n_oi); + jl(ow_loop_label, T_NEAR); + } + } + if (r_pad1 > 0) { + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w, 0, r_pad1); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + } + if (ur_w_tail != 0) { + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w_tail, 0, r_pad); + } + } + } + } else { + // ow block is only processed. + // Number of block is passed as parameter owb, + // and padding processing depends on this number. + + Label end_label, last_oi_label, middle_ow_blocks_label, tail_label; + Label oi_loop_label, oi_loop_start_label, oi_loop_end_label; + + assert(ow_block % ur_w == 0); + int n_oi_not_last_ow_block = ow_block / ur_w; + // to simplify code (and general regs usage), + // size of ow block must be >= 2 * ur_w + assert(n_oi_not_last_ow_block > 1); + int n_oi_next_last_ow_block = n_oi_not_last_ow_block; + int n_oi_first_ow_block = n_oi_not_last_ow_block; + + int n_oi_last_ow_block = (ow - ow_block * (nb_ow-1)) / ur_w; + + // prepare right padding + bool next_last_ow_block_padded = r_pad1 > 0 && n_oi_last_ow_block == 0; + bool first_ow_block_padded = next_last_ow_block_padded && jcp.nb_ow == 2; + bool last_ow_block_padded = r_pad1 > 0 && n_oi_last_ow_block > 0; + + if (last_ow_block_padded) n_oi_last_ow_block--; + else if (first_ow_block_padded) n_oi_first_ow_block--; + else if (next_last_ow_block_padded) n_oi_next_last_ow_block--; + + mov(reg_owb, ptr[param1 + GET_OFF(owb)]); + cmp(reg_owb, 0); // is that the first ow-block ? + jg(middle_ow_blocks_label, T_NEAR); + + // the first ow block, compute left padding + + mov(reg_oi, n_oi_first_ow_block); + mov(reg_inp_prf, reg_inp); + mov(reg_out_prf, reg_out); + + if (l_pad > 0) { + mov(reg_ker_prf, ptr[param1 + GET_OFF(filt_prf)]); + add(reg_inp_prf, inp_shift_pad); + add(reg_out_prf, out_shift); + compute_loop(ur_w, l_pad, 0); + add(reg_inp, inp_shift_pad); + add(reg_out, out_shift); + dec(reg_oi); + } + jmp(oi_loop_label, T_NEAR); + + // middle or last ow block entry + + L(middle_ow_blocks_label); + + if (l_pad > 0) { + // just to consider left padding, not compute + add(reg_inp, inp_shift_pad_second_block); + add(reg_inp_prf, inp_shift_pad_second_block); + } + + // set number of iteration for oi-loop + cmp(reg_owb, jcp.nb_ow - 1); // last ow-block ? + mov(reg_oi, n_oi_last_ow_block); + je(oi_loop_label, T_NEAR); + cmp(reg_owb, jcp.nb_ow - 2); // next to last ow-block ? + mov(reg_oi, n_oi_next_last_ow_block); + je(oi_loop_label, T_NEAR); + mov(reg_oi, n_oi_not_last_ow_block); // other middle ow-blocks + + // oi loop w/o padding + L(oi_loop_label); + mov(reg_ker_prf, ptr[param1 + GET_OFF(filt_prf)]); + L(oi_loop_start_label); + cmp(reg_oi, 0); + jle(oi_loop_end_label, T_NEAR); + + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w, 0, 0); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + dec(reg_oi); + jmp(oi_loop_start_label, T_NEAR); + L(oi_loop_end_label); + + mov(reg_owb, ptr[param1 + GET_OFF(owb)]); + + cmp(reg_owb, 0); // first ow-block ? + if (first_ow_block_padded) { + je(last_oi_label, T_NEAR); + } else { + je(end_label, T_NEAR); + } + cmp(reg_owb, jcp.nb_ow - 2); // next to last ow-block ? + jl(end_label, T_NEAR); + if (next_last_ow_block_padded) { + je(last_oi_label, T_NEAR); + } else { + je(end_label, T_NEAR); + } + // that is last block + if (!last_ow_block_padded) { + jmp(tail_label, T_NEAR); + } + + // last oi block with right padding + L(last_oi_label); + mov(reg_ker_prf, ptr[param1 + GET_OFF(filt_prf)]); + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w, 0, r_pad1); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + + mov(reg_owb, ptr[param1 + GET_OFF(owb)]); + cmp(reg_owb, jcp.nb_ow - 1); // last ow_block? + jl(end_label, T_NEAR); + + L(tail_label); + mov(reg_ker_prf, ptr[param1 + GET_OFF(filt_prf)]); + if (ur_w_tail != 0) { + add(reg_inp_prf, inp_shift); + add(reg_out_prf, out_shift); + compute_loop(ur_w_tail, 0, r_pad); + } + L(end_label); + } + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_avx512_common_conv_fwd_kernel::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +status_t jit_avx512_common_conv_fwd_kernel::init_conf( + jit_conv_conf_t &jcp, const convolution_desc_t &cd, + memory_desc_t &src_md, memory_desc_t &weights_md, + memory_desc_t &dst_md, memory_desc_t &bias_md, + const primitive_attr_t &attr, int nthreads) +{ + using namespace prop_kind; + + if (!mayiuse(avx512_common)) + return status::unimplemented; + + const memory_desc_wrapper src_d(&src_md); + const memory_desc_wrapper weights_d(&weights_md); + const memory_desc_wrapper dst_d(&dst_md); + const memory_desc_wrapper bias_d(&bias_md); + + const int regs = 28; + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + int ndims = src_d.ndims(); + + jcp = zero(); + jcp.ndims = ndims; + jcp.prop_kind = cd.prop_kind; + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.id = (ndims == 5) ? src_d.dims()[2] : 1; + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[ndims-2]; + jcp.iw = src_d.dims()[ndims-1]; + jcp.od = (ndims == 5) ? dst_d.dims()[2] : 1; + jcp.oh = (ndims == 3) ? 1 : dst_d.dims()[ndims-2]; + jcp.ow = dst_d.dims()[ndims-1]; + jcp.kd = (ndims == 5) ? weights_d.dims()[with_groups + 2] : 1; + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + ndims-2]; + jcp.kw = weights_d.dims()[with_groups + ndims-1]; + jcp.f_pad = (ndims == 5) ? cd.padding[0][0] : 0; + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][ndims-4]; + jcp.l_pad = cd.padding[0][ndims-3]; + jcp.stride_d = (ndims == 5) ? cd.strides[0] : 1; + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[ndims-4]; + jcp.stride_w = cd.strides[ndims-3]; + + jcp.dilate_d = (ndims == 5) ? cd.dilates[0] : 0; + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[ndims-4]; + jcp.dilate_w = cd.dilates[ndims-3]; + + jcp.b_pad = (jcp.oh - 1) * jcp.stride_h + (jcp.kh - 1) * (jcp.dilate_h + 1) + - (jcp.ih + jcp.t_pad - 1); + jcp.back_pad = (jcp.od - 1) * jcp.stride_d + + (jcp.kd - 1) * (jcp.dilate_d + 1) - (jcp.id + jcp.f_pad - 1); + + jcp.is_1stconv = is_1stconv(jcp); + + bool ok_to_pad_channels = true + && jcp.ngroups == 1 + && src_d.data_type() == data_type::f32; + + const int full_simd_w = cpu_isa_traits::vlen / sizeof(float); + jcp.simd_w = full_simd_w; + bool ok_to_try_xmm = true + && mayiuse(avx512_core) + && src_d.data_type() == data_type::f32 + && !jcp.is_1stconv + && !ok_to_pad_channels + && (jcp.ic % jcp.simd_w != 0 || jcp.oc % jcp.simd_w != 0) + && (jcp.ic % 8 != 0 || jcp.oc % 8 != 0); + if (ok_to_try_xmm) + jcp.simd_w = 4; + + jcp.oc_block = jcp.simd_w; + jcp.ic_block = jcp.is_1stconv ? jcp.ic : jcp.simd_w; + jcp.aligned_threads = 0; + + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, jcp.oc_block); + jcp.ic = rnd_up(jcp.ic, jcp.ic_block); + } + bool args_ok = true + && jcp.oc % jcp.oc_block == 0 + && jcp.ic % jcp.ic_block == 0; + if (!args_ok) + return status::unimplemented; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) { + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + if (dst_d.data_type() == data_type::s32) return status::unimplemented; + } + + auto src_tag = jcp.is_1stconv + ? pick(ndims - 3, ncw, nchw, ncdhw) + : ((jcp.simd_w == 4) + ? pick(ndims - 3, nCw4c, nChw4c, nCdhw4c) + : pick(ndims - 3, nCw16c, nChw16c, nCdhw16c)); + auto dst_tag = (jcp.simd_w == 4) + ? pick(ndims - 3, nCw4c, nChw4c, nCdhw4c) + : pick(ndims - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = with_groups + ? ((jcp.simd_w == 4) + ? pick(ndims - 3, gOIw4i4o, gOIhw4i4o, gOIdhw4i4o) + : pick(ndims - 3, gOIw16i16o, gOIhw16i16o, gOIdhw16i16o)) + : ((jcp.simd_w == 4) + ? pick(ndims - 3, OIw4i4o, OIhw4i4o, OIdhw4i4o) + : pick(ndims - 3, OIw16i16o, OIhw16i16o, OIdhw16i16o)); + + if (src_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md, src_tag)); + jcp.src_tag = src_tag; + } else { + jcp.src_tag = src_d.matches_one_of_tag(src_tag); + } + if (jcp.src_tag != src_tag) + return status::unimplemented; + + if (dst_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(dst_md, dst_tag)); + jcp.dst_tag = dst_tag; + } else { + jcp.dst_tag = dst_d.matches_one_of_tag(dst_tag); + } + if (jcp.dst_tag != dst_tag) + return status::unimplemented; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + if (jcp.with_bias) { + if (bias_d.format_kind() == format_kind::any) + CHECK(memory_desc_init_by_tag(bias_md, x)); + } + + if (mayiuse(avx512_common) && + src_d.data_type() == data_type::f32 + && weights_d.data_type() == data_type::f32 + && dst_d.data_type() == data_type::f32) { + jcp.ver = ver_fma; + jcp.typesize_in = sizeof(float); + jcp.typesize_out = sizeof(float); + if (mayiuse(avx512_mic_4ops)) + jcp.ver = ver_4fma; + + if (jcp.is_1stconv) { + // TODO: fix & remove constraints below + bool not_for_4fma + = IMPLICATION(everyone_is(0, jcp.l_pad, jcp.t_pad), + nstl::max(jcp.kw, jcp.kh) < 7); + bool is_dilated + = !everyone_is(0, jcp.dilate_d, jcp.dilate_h, jcp.dilate_w); + if (one_of(true, not_for_4fma, is_dilated)) + jcp.ver = ver_fma; + if (jcp.ver == ver_4fma) { + wei_tag = with_groups + ? ((jcp.simd_w == 4) + ? pick(ndims - 3, gOiw4o, gOihw4o, gOidhw4o) + : pick(ndims - 3, gOiw16o, gOihw16o, gOidhw16o)) + : ((jcp.simd_w == 4) + ? pick(ndims - 3, Oiw4o, Oihw4o, Oidhw4o) + : pick(ndims - 3, Oiw16o, Oihw16o, Oidhw16o)); + } else { + wei_tag = with_groups + ? ((jcp.simd_w == 4) + ? pick(ndims - 3, gOwi4o, gOhwi4o, gOdhwi4o) + : pick(ndims - 3, gOwi16o, gOhwi16o, gOdhwi16o)) + : ((jcp.simd_w == 4) + ? pick(ndims - 3, Owi4o, Ohwi4o, Odhwi4o) + : pick(ndims - 3, Owi16o, Ohwi16o, Odhwi16o)); + } + } + } else { + return status::unimplemented; + } + + if (weights_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(weights_md, wei_tag)); + jcp.wei_tag = wei_tag; + } else { + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + } + if (jcp.wei_tag != wei_tag) + return status::unimplemented; + + if (jcp.is_1stconv) { + jcp.ur_w = nstl::min(jcp.ow, regs); + } else { + // avx512_core guard - just to avoid possible regression for other archs + if (jcp.ver == ver_fma && mayiuse(avx512_core)) { + jcp.ur_w = nstl::min(jcp.ow, regs); + } else { + for (int ur_w = regs; ur_w > 0; --ur_w) { + if (jcp.ow % ur_w == 0) { + jcp.ur_w = ur_w; + break; + } + } + } + if ((ndims == 5 && jcp.ur_w <= 8) || (jcp.ur_w <= 1)) { + jcp.ur_w = nstl::min(jcp.ow, regs); + } + } + // TODO (Tanya): currently applied to Segnet convolutions only. + // Need to try for other topologies + if (jcp.ow > 150 && jcp.ur_w < regs/2) + jcp.ur_w = regs; + + int n_oi = (jcp.ow / jcp.ur_w); + int r_pad = (jcp.ur_w * n_oi - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1); + if (jcp.l_pad > 0 && r_pad > 0) + n_oi--; + + bool large_code_size = jcp.ur_w != jcp.ow && jcp.l_pad > 0 && r_pad > 0 + && ((jcp.l_pad <= 0 && n_oi > 0) || (jcp.l_pad > 0 && n_oi > 1)); + if (large_code_size) { + const int max_code_size = 24 * 1024; + const int num_ops_per_reg = 6 + jcp.ic_block * jcp.kw; + int mult = 1; + if (jcp.l_pad > 0) mult += 1; + if (r_pad > 0) mult += 1; + for (int ur_w = jcp.ur_w; ur_w > regs/2; --ur_w) { + if (ur_w * mult * num_ops_per_reg * 9.0 < max_code_size) { + jcp.ur_w = ur_w; + break; + } + } + } + + /* Grouped channel offset to support 'non-blocked data' format for + * convolution sizes with '(input_channel / ngroups) < simd' */ + jcp.nonblk_group_off + = (jcp.ngroups > 1 && one_of(jcp.src_tag, ncw, nchw, ncdhw)) ? + jcp.ic : + 1; + + jcp.nb_ic = jcp.ic / jcp.ic_block; + jcp.nb_oc = jcp.oc / jcp.oc_block; + jcp.nb_ic_blocking = jcp.nb_oc_blocking = 1; + + auto is_ow_threading_applicable = [=]() { + return (true && !jcp.is_1stconv && one_of(jcp.ndims, 3, 4) + && IMPLICATION(mayiuse(avx512_mic), + jcp.ver == ver_4fma + && IMPLICATION(jcp.mb != 1, + jcp.ih == 1 && jcp.kh == 1))); + }; + + if (jcp.ver == ver_4fma && !jcp.is_1stconv) { + if ((jcp.kw <= 5 && jcp.kh <= 5 && jcp.kw == jcp.kh && jcp.ow <= 8 + && jcp.oh <= 8 && jcp.ow == jcp.oh) + || (jcp.stride_h != 1 && jcp.ur_w < jcp.ow)) { + if (jcp.nb_oc % 2 == 0) { + jcp.nb_oc_blocking = 2; + jcp.ur_w = nstl::min(jcp.ow, regs / jcp.nb_oc_blocking); + } + } else { + for (int i = jcp.nb_oc; i > 0; i--) + if (i * jcp.ur_w <= regs && jcp.nb_oc % i == 0) { + jcp.nb_oc_blocking = i; + break; + } + } + if (jcp.ver == ver_4fma && is_ow_threading_applicable()) { + if (jcp.nb_oc % 2 == 0 && jcp.ur_w < jcp.ow + && jcp.ow != 2 * jcp.ur_w) { + jcp.nb_oc_blocking = 2; + jcp.ur_w = nstl::min(jcp.ow, regs / jcp.nb_oc_blocking); + } + } + } + + jcp.ow_block = jcp.ow; + + auto get_thr_eff = [=](int nb_oc_blocking, int ow_block) { + int nb_ow = div_up(jcp.ow, ow_block); + int nb_oc_chunks = div_up(jcp.nb_oc, nb_oc_blocking); + int work_amount = jcp.mb * jcp.oh * nb_oc_chunks * nb_ow; + float disbalance = (float)jcp.ow / rnd_up(jcp.ow, ow_block); + float thr_eff = disbalance * (float)work_amount + / rnd_up(work_amount, nthreads); + return thr_eff; + }; + + auto get_ow_block = [=](int nb_oc_blocking, int ur_w, float &eff) { + int res_ow_block = jcp.ow; + eff = get_thr_eff(nb_oc_blocking, res_ow_block); + if (!is_ow_threading_applicable()) + return res_ow_block; + + int L2_part = (get_cache_size(2) * 7 / 8) / typesize; + if (jcp.ver == ver_4fma) + L2_part /= 2; + int size_src_chunk = jcp.ic_block * ur_w * jcp.kh; + int size_dst_chunk = jcp.oc_block * nb_oc_blocking * ur_w; + int size_wei_chunk = jcp.oc_block * nb_oc_blocking * jcp.ic_block + * jcp.kw * jcp.kh; + int nurw_cache = (L2_part - 2 * size_wei_chunk) + / (2 * size_dst_chunk + 2 * size_src_chunk); + // current design of generate() requires ow_block >= 2 * ur_w + int ow_block_cache = ur_w * nstl::max(2, nurw_cache); + + int ow_block_thr = ow_block_cache; + eff = get_thr_eff(nb_oc_blocking, ow_block_thr); + + int max_nb_ow = div_up(jcp.ow, 2 * ur_w); + int start_nb_ow = div_up(jcp.ow, ow_block_thr); + for (int nb_ow = start_nb_ow; nb_ow <= max_nb_ow; nb_ow++) { + int ow_block + = nstl::min(rnd_up(div_up(jcp.ow, nb_ow), ur_w), jcp.ow); + float eff_threshold = (jcp.ver == ver_4fma) ? 0.8f : 0.9f; + if (ow_block < nb_oc_blocking * jcp.oc_block && eff > eff_threshold) + break; + if (div_up(jcp.ow, ow_block) != nb_ow) + continue; + float thr_eff = get_thr_eff(nb_oc_blocking, ow_block); + float eff_step = (jcp.ver == ver_4fma) ? 1.1f : 1.f; + if (ow_block >= 2 * ur_w && thr_eff > eff_step * eff) { + ow_block_thr = ow_block; + eff = thr_eff; + } + eff_threshold = (jcp.ver == ver_4fma) ? 0.9f : 0.98f; + if (eff > eff_threshold) + break; + } + res_ow_block = nstl::min(jcp.ow, nstl::max(2 * ur_w, ow_block_thr)); + eff = get_thr_eff(nb_oc_blocking, res_ow_block); + return res_ow_block; + }; + + + if (jcp.ver == ver_fma && mayiuse(avx512_core)) { + int try_nb_oc_blocking = 2; + unsigned int ker_inp_size = typesize * div_up(jcp.iw, jcp.stride_w) + * jcp.ic_block * jcp.kh * jcp.kd; + unsigned int ker_out_size = typesize * jcp.ow * jcp.oc_block + * try_nb_oc_blocking; + unsigned int ker_wei_size = typesize * jcp.kh * jcp.kw * jcp.ic_block + * jcp.oc_block * try_nb_oc_blocking * jcp.kd; + unsigned int ker_total_size = ker_inp_size + ker_out_size + + ker_wei_size; + + bool embd_bcast_condition = true + && (jcp.kw == 3 && jcp.ow <= 28 && ker_total_size < L1_cache_size) + && !(jcp.kw == 3 && jcp.ow == 13 && jcp.ic >= 192) + && !(jcp.kw == 3 && jcp.ow == 28 && jcp.ic >= 512); + + if (jcp.mb == 1) { + unsigned int inp_size = jcp.mb * div_up(jcp.ih, jcp.stride_h) + * div_up(jcp.iw, jcp.stride_w) * jcp.ic; + unsigned int wei_size = jcp.ic * jcp.oc * jcp.kh * jcp.kw; + + // Estimate whether we need to limit the number of threads + // and calculate this number. Includes some heuristic. + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int work_amount = jcp.mb * jcp.ngroups * oc_chunks * jcp.oh; + int job_size_min = work_amount / nthreads; + int job_size_max = div_up(work_amount, nthreads); + int ch_max = rnd_up(jcp.oh, job_size_max); + int ch_min = (job_size_min == 0) + ? jcp.oh + : rnd_up(jcp.oh, job_size_min); + bool not_aligned_max = ch_max % jcp.oh != 0 && ch_max / jcp.oh < 2 + && (jcp.oh != 8 || ch_max / jcp.oh > 1); + bool not_aligned_min = ch_min % jcp.oh != 0 && ch_min / jcp.oh < 2 + && (jcp.oh != 8 || ch_min / jcp.oh > 1); + bool eligible_case = (jcp.stride_h == 1 && jcp.stride_w == 1) + || nthreads > oc_chunks; + if (jcp.loop_order == loop_cgn && oc_chunks > 1 && nthreads > 1 + && wei_size / inp_size > 24 + && (not_aligned_max || not_aligned_min) + && eligible_case) { + // Try to find nthreads > mkldnn_get_max_threads() / 2 such + // that oc_chunks is a multiple of nthreads, or nthreads is a + // multiple of oc_chunks. Otherwise, keep default value. + // TODO: implement a task-based alternative without throttling. + jcp.aligned_threads = nthreads; + for (int i = nthreads; i > nthreads / 2; i--) { + if (oc_chunks % i == 0 || i % oc_chunks == 0) { + jcp.aligned_threads = i; + break; + } + } + } + } + + if (jcp.kw > 3 + || (jcp.stride_w == 1 && jcp.stride_h == 1 + && embd_bcast_condition) + || ((jcp.stride_w != 1 || jcp.stride_h != 1) + && ((jcp.mb <= 16 && (jcp.oc <= 192 || jcp.oh <= 10) + && embd_bcast_condition))) + || (jcp.mb == 1 + && (jcp.ur_w >= jcp.ow || jcp.is_1stconv + || (jcp.ow <= 147 && jcp.oc <= 96)))) { + jcp.kernel_kind = embd_bcast; + jcp.ur_w = nstl::min(jcp.ow, regs); + jcp.nb_ic_blocking = jcp.nb_oc_blocking = 1; + if (ker_total_size < L1_cache_size && jcp.ow <= 8 && jcp.kh <= 3 + && jcp.kw <= 3 && jcp.nb_oc % try_nb_oc_blocking == 0 + && IMPLICATION(jcp.is_1stconv, jcp.mb == 1) + && IMPLICATION(jcp.mb == 1, jcp.ur_w < jcp.ow)) { + jcp.nb_oc_blocking = try_nb_oc_blocking; + jcp.ur_w = nstl::min(jcp.ow, 31 / (jcp.nb_oc_blocking + 1)); + } + } else { + jcp.kernel_kind = expl_bcast; + jcp.nb_ic_blocking = 1; + if (IMPLICATION(jcp.is_1stconv, jcp.mb > 1)) { + float best_thr_eff = 0.f; + int best_nb_oc_blocking = 1; + for (int i = nstl::min(jcp.nb_oc, 5); i > 0; i--) { + if (jcp.nb_oc % i == 0) { + float thr_eff; + int ur_w = nstl::min(jcp.ow, 31 / (i + 1)); + get_ow_block(i, ur_w, thr_eff); + if (thr_eff > 1.05f * best_thr_eff) { + best_nb_oc_blocking = i; + best_thr_eff = thr_eff; + } + } + } + jcp.nb_oc_blocking = best_nb_oc_blocking; + jcp.ur_w = nstl::min(jcp.ow, 31 / (jcp.nb_oc_blocking + 1)); + } + } + } + + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + + args_ok = true + && jcp.l_pad <= jcp.ur_w + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= dst_d.padded_dims()[1] + && jcp.ic <= weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= weights_d.padded_dims()[with_groups + 0]; + if (!args_ok) + return status::unimplemented; + + int r_pad_no_tail = nstl::max(0, (jcp.ow - jcp.ur_w_tail - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.iw + jcp.l_pad - 1)); + if (r_pad_no_tail > jcp.ur_w) + return status::unimplemented; + + pick_loop_order(jcp); + + jcp.nb_ic_L2 = jcp.nb_ic; + + float thr_eff; + jcp.ow_block = get_ow_block(jcp.nb_oc_blocking, jcp.ur_w, thr_eff); + jcp.nb_ow = div_up(jcp.ow, jcp.ow_block); + + const int L2_size = get_cache_size(2, true) / sizeof(float); + // Source and output data needs to fit in L2, + // leaving some space for weights and prefetching. + int h_L2 = int(((0.6f * L2_size) / jcp.simd_w + - nstl::min(0, jcp.kh - jcp.stride_h) * jcp.iw) + / (jcp.stride_h * jcp.iw + jcp.ow)); + jcp.h_blocking = nstl::max(1, nstl::min(jcp.oh, h_L2)); + + if (jcp.ver == ver_4fma) { + if (!is_ow_threading_on(jcp)) { + for (int divf = 2, temp_nb = jcp.nb_ic_L2; divf <= jcp.nb_ic; + divf++) { + size_t l2_src + = (size_t)jcp.iw * jcp.ic_block * jcp.ih * temp_nb * jcp.id; + size_t l2_dst = (size_t)jcp.ow * jcp.oc_block * jcp.nb_oc_blocking + * jcp.oh * jcp.od; + size_t l2_filt = (size_t)jcp.kw * jcp.oc_block * jcp.ic_block + * jcp.kh * jcp.nb_oc_blocking * temp_nb * jcp.kd; + if (4 * (l2_src + l2_dst + l2_filt) > KNx_L2_EFFECTIVE_CAPACITY) { + if (jcp.kh == 3 && jcp.oh == 7) { + jcp.nb_ic_L2 = 1; + break; + } + temp_nb = (jcp.nb_ic_L2 % divf == 0 ? jcp.nb_ic_L2 / divf + : jcp.nb_ic_L2); + } else { + jcp.nb_ic_L2 = temp_nb; + break; + } + } + } else if (jcp.ic > 64) { + jcp.nb_ic_L2 = 2; /* according to performance data*/ + } + } + + return status::success; +} + +void jit_avx512_common_conv_fwd_kernel::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + if (jcp.with_bias && jcp.oc != jcp.oc_without_padding) + scratchpad.book(key_conv_padded_bias, jcp.typesize_out * jcp.oc); +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::prepare_output(int ur_w) +{ + for (int k = 0; k < jcp.nb_ic_blocking; k++) { + for (int j = 0; j < ur_w; j++) { + Zmm zmm = zmm_out(j, k); + vpxord(zmm, zmm, zmm); + size_t aux_src_offset + = (size_t)typesize * ((size_t)k * jcp.ih * jcp.iw * jcp.id + j) + * jcp.ic_block; + mic_prefetcht1(EVEX_compress_addr_safe(reg_src_prf, aux_src_offset, + reg_long_offt)); + } + } +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::store_output(int ur_w) +{ + Label no_update_label; + + mov(reg_channel, ptr[param + GET_OFF(channel)]); + cmp(reg_channel, 0); + je(no_update_label, T_NEAR); + for (int k = 0; k < jcp.nb_ic_blocking; k++) { + for (int j = 0; j < ur_w; j++) { + Zmm zmm = zmm_out(j, k); + size_t aux_src_offset = (size_t)typesize + * ((size_t)k * jcp.ih * jcp.iw * jcp.id + j) * jcp.ic_block; + vaddps(zmm, EVEX_compress_addr_safe(reg_src, aux_src_offset, + reg_long_offt)); + } + } + + L(no_update_label); + for (int k = 0; k < jcp.nb_ic_blocking; k++) { + for (int j = 0; j < ur_w; j++) { + Zmm zmm = zmm_out(j, k); + size_t aux_src_offset = (size_t)typesize + * ((size_t)k * jcp.ih * jcp.iw * jcp.id + j) * jcp.ic_block; + vmovups(EVEX_compress_addr_safe(reg_src, aux_src_offset, + reg_long_offt), zmm); + mic_prefetcht0(EVEX_compress_addr_safe(reg_src_prf, aux_src_offset, + reg_long_offt)); + } + } +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::compute_loop_4fma( + int ur_w, int l_overflow, int r_overflow) +{ + int ow = jcp.ow; + int kw = jcp.kw; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + Label kh_label, last_iter_label, loop_end_label, kd_label; + int ker_load_number = 4; + int shift_ker_ptr = typesize * kw * oc_block * ic_block; + int shift_dst_ptr = typesize * ow * oc_block; + int ii_dpref_t0 = get_iw_start(0, l_overflow); + int iw_end_ipref = get_iw_end(ur_w, 0, r_overflow); + + bool check_last_kh = (jcp.kh > 3); + auto kernel_offset = [=](int icb, int oc, int ki) { + int blk_idx = icb * jcp.kh * jcp.kw * jcp.kd + ki; + int blk_offset = blk_idx * jcp.oc_block * jcp.ic_block; + int oc_offset = oc * jcp.oc_block; + return typesize * (blk_offset + oc_offset); + }; + auto kernel_loads = [=](int ki, int oc, int kk) { + for (int ii = 0; ii < ker_load_number; ii++) { + int aux_kernel_offset = kernel_offset(kk, oc + ii, ki); + vmovups(zmm_ker(ii), + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + } + }; + auto prefetch_dst_next_kh = [&](int ki, int ki_start, int cnt0, int cnt1) { + if (cnt1 >= ker_load_number && cnt0 >= ker_load_number + && ki >= ki_start && ii_dpref_t0 < iw_end_ipref) { + int aux_dst_offset = typesize * ((ii_dpref_t0 + + jcp.l_pad) * oc_block + jcp.ow * oc_block); + prefetcht0(EVEX_compress_addr(aux_reg_dst, aux_dst_offset)); + ii_dpref_t0++; + } + }; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_dst, reg_dst); + mov(aux_reg_ker, reg_ker); + mov(aux_reg_dst_prf, reg_dst_prf); + mov(aux_reg_ker_prf, reg_ker_prf); + } + + if (jcp.ndims == 5) { + push(reg_src_prf); + push(reg_src); + + mov(reg_ki, ptr[param + GET_OFF(kd_padding)]); + mov(aux_reg_dst_d, reg_dst); + mov(aux_reg_ker_d, ptr[param + GET_OFF(filt)]); + mov(aux_reg_dst_d_prf, reg_dst_prf); + mov(aux_reg_ker_d_prf, reg_ker_prf); + + L(kd_label); + mov(reg_kj, ptr[param + GET_OFF(kh_padding)]); + } else { + mov(reg_kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_dst, aux_reg_dst_d); + mov(aux_reg_ker, aux_reg_ker_d); + mov(aux_reg_dst_prf, aux_reg_dst_d_prf); + mov(aux_reg_ker_prf, aux_reg_ker_d_prf); + } + + align(16); + L(kh_label); + if (check_last_kh) { + for (int ki = 0; ki < kw; ki++) + for (int oc = 0; oc < oc_block; oc += 4) + for (int kk = 0; kk < jcp.nb_ic_blocking; kk++) { + bool last_kernel_loads = (kk == jcp.nb_ic_blocking - 1 + && ki == kw - 1 && (oc + 4) == oc_block); + + if (last_kernel_loads) { + cmp(reg_kj, 1); + je(last_iter_label, T_NEAR); + } + + kernel_loads(ki, oc, kk); + for (int ii = get_iw_start(ki, l_overflow), + prf_count_t0 = 0, prf_count_t1 = 0; + ii < get_iw_end(ur_w, ki, r_overflow); ii++) { + int aux_dst_offset = typesize + * ((ii + jcp.l_pad - ki) * oc_block + oc); + v4fmaddps(zmm_out(ii, kk), zmm_ker(0), + EVEX_compress_addr(aux_reg_dst, aux_dst_offset)); + + if (ii % 2) { + if (prf_count_t0 < 4) { + int aux_kernel_prf; + if (last_kernel_loads) + aux_kernel_prf= kernel_offset(0, prf_count_t0 + + oc + 4 - oc_block, 0) + typesize * kw + * oc_block * ic_block; + else + aux_kernel_prf = kernel_offset(kk, oc + 4 + + prf_count_t0, ki); + mic_prefetcht0(EVEX_compress_addr(aux_reg_ker, + aux_kernel_prf)); + prf_count_t0++; + } else if (prf_count_t1 < 4) { + mic_prefetcht1(EVEX_compress_addr(aux_reg_ker_prf, + kernel_offset(kk, oc + prf_count_t1, ki))); + prf_count_t1++; + } + } else + prefetch_dst_next_kh(ki, 2, prf_count_t0, prf_count_t1); + } + if (last_kernel_loads) { + jmp(loop_end_label, T_NEAR); + + L(last_iter_label); + + kernel_loads(ki, oc, kk); + for (int ii = get_iw_start(ki, l_overflow), + prf_count_t0 = 0, prf_count_t1 = 0; + ii < get_iw_end(ur_w, ki, r_overflow); ii++) { + int aux_dst_offset = typesize + * ((ii + jcp.l_pad - ki) * oc_block + oc); + v4fmaddps(zmm_out(ii, kk), zmm_ker(0), + EVEX_compress_addr(aux_reg_dst, aux_dst_offset)); + if (ii % 2) { + if (prf_count_t0 < 4) { + mic_prefetcht0(EVEX_compress_addr(aux_reg_ker_prf, + kernel_offset(0, prf_count_t0, 0))); + prf_count_t0++; + } else if (prf_count_t1 < 4) { + mic_prefetcht1(EVEX_compress_addr(aux_reg_ker_prf, + kernel_offset(kk, oc + prf_count_t1, ki))); + prf_count_t1++; + } + } + } + L(loop_end_label); + } + } + } else { + for (int ki = 0; ki < kw; ki++) + for (int oc = 0; oc < oc_block; oc += 4) + for (int kk = 0; kk < jcp.nb_ic_blocking; kk++) { + kernel_loads(ki, oc, kk); + + for (int ii = get_iw_start(ki, l_overflow), prf_count_t1 = 0; + ii < get_iw_end(ur_w, ki, r_overflow); ii++) { + int aux_dst_offset = typesize + * ((ii + jcp.l_pad - ki) * oc_block + oc); + v4fmaddps(zmm_out(ii, kk), zmm_ker(0), + EVEX_compress_addr(aux_reg_dst, aux_dst_offset)); + if ((ii % 2) && (prf_count_t1 < 4)) { + mic_prefetcht1(EVEX_compress_addr( + aux_reg_ker_prf, kernel_offset(kk, + oc + prf_count_t1, ki))); + prf_count_t1++; + } + if ( ki == 1 && oc == 0 && kk == 0) + mic_prefetcht1(EVEX_compress_addr( + aux_reg_dst_prf, aux_dst_offset)); + } + } + } + + add(aux_reg_ker, shift_ker_ptr); + sub(aux_reg_dst, shift_dst_ptr); + add(aux_reg_ker_prf, shift_ker_ptr); + sub(aux_reg_dst_prf, shift_dst_ptr); + + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + + if (jcp.ndims == 5) { + sub(aux_reg_dst_d, typesize * (jcp.oh * ow) * ic_block); + add(aux_reg_ker_d, typesize * jcp.kw * jcp.kh * oc_block * ic_block); + sub(aux_reg_dst_d_prf, typesize * (jcp.oh * ow) * ic_block); + add(aux_reg_ker_d_prf, typesize * jcp.kw * jcp.kh *oc_block * ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + + pop(reg_src); + pop(reg_src_prf); + } +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::compute_loop_fma( + int ur_w, int l_overflow, int r_overflow) +{ + Label kh_label, kd_label; + int kw = jcp.kw; + int ow = jcp.ow; + + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int l_pad = jcp.l_pad; + int dilate_w = jcp.dilate_w + 1; + int stride_w = jcp.stride_w; + int stride_h = jcp.stride_h; + + int ker_pipeline_depth = 4; + assert(ker_reg_base_idx + ker_pipeline_depth <= 32); + assert(oc_block >= ker_pipeline_depth); + + int num_ker_loads = oc_block * kw; + int num_inp_prfs = ur_w * nstl::min(kw, stride_w) + + nstl::max(0, kw - stride_w); + int num_prfs = num_ker_loads + num_inp_prfs; + int num_fmas = num_ker_loads * ur_w / stride_w; + int prf_inst_spacing = nstl::max(1, num_fmas / num_prfs); + int prf_inst_trigger = (num_fmas % prf_inst_spacing) / 2; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_dst, reg_dst); + mov(aux_reg_ker, reg_ker); + + mov(aux_reg_dst_prf, reg_dst_prf); + mov(aux_reg_ker_prf, reg_ker_prf); + } + + if (jcp.ndims == 5) { + push(reg_src_prf); + push(reg_src); + + mov(reg_ki, ptr[param + GET_OFF(kd_padding)]); + mov(aux_reg_dst_d, reg_dst); + mov(aux_reg_ker_d, ptr[param + GET_OFF(filt)]); + mov(aux_reg_dst_d_prf, reg_dst_prf); + mov(aux_reg_ker_d_prf, reg_ker_prf); + + L(kd_label); + mov(reg_kj, ptr[param + GET_OFF(kh_padding)]); + } else { + mov(reg_kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_dst, aux_reg_dst_d); + mov(aux_reg_ker, aux_reg_ker_d); + mov(aux_reg_dst_prf, aux_reg_dst_d_prf); + mov(aux_reg_ker_prf, aux_reg_ker_d_prf); + } + + L(kh_label); { + int step = 0; + int ker_prfs = 0; + for (int ki = 0; ki < kw; ki++) { + for (int oc = 0; oc < oc_block; oc++) { + if (step == 0) { + for (int i = 0; i < ker_pipeline_depth; i++) { + int aux_kernel_offset = typesize * ((oc + i) * oc_block + + ki * ic_block * oc_block); + vmovups(zmm_ker(i), EVEX_compress_addr( + aux_reg_ker, aux_kernel_offset)); + } + } else if (step < num_ker_loads - ker_pipeline_depth + 1) { + int load_offset = ker_pipeline_depth - 1; + int ker_load_reg_idx + = (step + load_offset) % ker_pipeline_depth; + int aux_kernel_offset = typesize * ((oc + load_offset) + * oc_block + ki * ic_block * oc_block); + vmovups(zmm_ker(ker_load_reg_idx), + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + } + + bool ker_prf_inserted = false; + auto zmm_kernel = zmm_ker(step % ker_pipeline_depth); + + int jj_start = get_iw_start(ki, l_overflow); + int jj_end = get_iw_end(ur_w, ki, r_overflow); + assert(stride_w != 1 + || jj_start == nstl::max(0, + l_overflow - (kw - 1 - ki) * dilate_w)); + assert(stride_w != 1 + || jj_end == ur_w - nstl::max(0, + r_overflow - ki * dilate_w)); + + for (int jj = jj_start; jj < jj_end; jj += stride_w) { + assert((jj + l_pad - ki * dilate_w) % stride_w == 0); + int aux_dst_offset = typesize * + (((jj + l_pad - ki * dilate_w) + / stride_w) * jcp.oc_block + oc); + vfmadd231ps(zmm_out(jj, 0), zmm_kernel, + EVEX_compress_addr(aux_reg_dst, aux_dst_offset, true)); + + int fma_idx = (step * ur_w + jj) / stride_w; + int prf_slot_idx = fma_idx / prf_inst_spacing; + if (fma_idx % prf_inst_spacing == prf_inst_trigger) { + if (!ker_prf_inserted && ker_prfs < num_ker_loads) { + int ker_prf_offset = typesize + * ker_prfs * jcp.oc_block; + mic_prefetcht1(EVEX_compress_addr( + aux_reg_ker_prf, ker_prf_offset)); + ker_prf_inserted = true; + ker_prfs++; + } else { + int inp_prf_idx = prf_slot_idx - ker_prfs; + if (inp_prf_idx < num_inp_prfs) { + int inp_prf_offset + = ic_block * typesize + * ((inp_prf_idx / kw) * kw + + (inp_prf_idx % kw)); + mic_prefetcht0(EVEX_compress_addr( + aux_reg_dst_prf, inp_prf_offset)); + } + } + } + } + step++; + } + } + + add(aux_reg_ker, typesize * stride_h * kw * oc_block * ic_block); + sub(aux_reg_dst, typesize * (jcp.dilate_h + 1) * ow * oc_block); + add(aux_reg_ker_prf, typesize * stride_h * kw * oc_block * ic_block); + sub(aux_reg_dst_prf, typesize * (jcp.dilate_h + 1) * ow * oc_block); + + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + } + if (jcp.ndims == 5) { + sub(aux_reg_dst_d, + typesize * (jcp.dilate_d + 1) * jcp.oh * ow * ic_block); + add(aux_reg_ker_d, typesize * jcp.stride_d * jcp.kw * jcp.kh + * oc_block * ic_block); + sub(aux_reg_dst_d_prf, + typesize * (jcp.dilate_d + 1) * jcp.oh * ow * ic_block); + add(aux_reg_ker_d_prf, typesize * jcp.stride_d * jcp.kw * jcp.kh + * oc_block * ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + } + + if (jcp.ndims == 5) + { + pop(reg_src); + pop(reg_src_prf); + } +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::compute_loop_fma_core( + int ur_w, int l_overflow, int r_overflow) +{ + int kw = jcp.kw; + int ow = jcp.ow; + int dilate_w = jcp.dilate_w + 1; + int stride_w = jcp.stride_w; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int nb_ic_block = jcp.nb_ic_blocking; + Label kh_label, kd_label; + + int shift_ker_ptr = typesize * kw * oc_block * ic_block; + int shift_dst_ptr = typesize * (jcp.dilate_h + 1) * ow * oc_block; + + auto output_offset = [=](int oi, int oc, int ki) { + return typesize * + (((oi + jcp.l_pad - ki * dilate_w) / stride_w) * oc_block + oc); + }; + auto kernel_offset = [=](int icb, int oc, int ki) { + int blk_idx = icb * jcp.kh * jcp.kw * jcp.kd + ki; + int blk_offset = blk_idx * jcp.oc_block * jcp.ic_block; + int oc_offset = oc * jcp.oc_block; + return typesize * (blk_offset + oc_offset); + }; + + if (one_of(jcp.ndims, 3, 4)) { + mov(aux_reg_dst, reg_dst); + mov(aux_reg_ker, reg_ker); + } + + if (jcp.ndims == 5) { + push(reg_src_prf); + push(reg_src); + + mov(reg_ki, ptr[param + GET_OFF(kd_padding)]); + mov(aux_reg_dst_d, reg_dst); + mov(aux_reg_ker_d, ptr[param + GET_OFF(filt)]); + + L(kd_label); + mov(reg_kj, ptr[param + GET_OFF(kh_padding)]); + } else { + mov(reg_kj, reg_kh); + } + + if (jcp.ndims == 5) { + mov(aux_reg_dst, aux_reg_dst_d); + mov(aux_reg_ker, aux_reg_ker_d); + } + + L(kh_label); + { + for (int ki = 0; ki < kw; ki++) { + int jj_start = get_iw_start(ki, l_overflow); + int jj_end = get_iw_end(ur_w, ki, r_overflow); + for (int oc = 0; oc < oc_block; oc++) { + if (jcp.kernel_kind == expl_bcast) { + for (int jj = jj_start; jj < jj_end; jj++) { + int aux_output_offset = output_offset(jj, oc, ki); + vbroadcastss(zmm_inp(jj, nb_ic_block), + ptr[aux_reg_dst + aux_output_offset]); + } + } + for (int ii = 0; ii < nb_ic_block; ii++) { + int aux_kernel_offset = kernel_offset(ii, oc, ki); + if (jj_end - jj_start > 0) + vmovups(zmm_wei, EVEX_compress_addr(aux_reg_ker, + aux_kernel_offset)); + for (int jj = jj_start; jj < jj_end; jj += stride_w) + if (jcp.kernel_kind == expl_bcast) + vfmadd231ps(zmm_out(jj, ii), + zmm_inp(jj, nb_ic_block), zmm_wei); + else + vfmadd231ps(zmm_out(jj, ii), zmm_wei, + EVEX_compress_addr(aux_reg_dst, + output_offset(jj, oc, ki), true)); + } + } + } + add(aux_reg_ker, shift_ker_ptr); + sub(aux_reg_dst, shift_dst_ptr); + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + } + + if (jcp.ndims == 5) { + sub(aux_reg_dst_d, + typesize * (jcp.dilate_d + 1) * jcp.oh * ow * ic_block); + add(aux_reg_ker_d, typesize * jcp.kw * jcp.kh * oc_block * ic_block); + + dec(reg_ki); + cmp(reg_ki, 0); + jg(kd_label, T_NEAR); + + pop(reg_src); + pop(reg_src_prf); + } +} + +inline void jit_avx512_common_conv_bwd_data_kernel_f32::compute_loop( + int ur_w, int l_overflow, int r_overflow) +{ + if (jcp.ndims == 5) push(reg_oi); + + prepare_output(ur_w); + + Label skip_compute_loop; + if (jcp.ndims == 5) { + mov(reg_kj, ptr[param + GET_OFF(kd_padding)]); + cmp(reg_kj, 0); + je(skip_compute_loop, T_NEAR); + } + mov(reg_kj, ptr[param + GET_OFF(kh_padding)]); + cmp(reg_kj, 0); + je(skip_compute_loop, T_NEAR); + + if (jcp.ver == ver_4fma) + compute_loop_4fma(ur_w, l_overflow, r_overflow); + else if (jcp.ver == ver_fma) + if (mayiuse(avx512_mic)) + compute_loop_fma(ur_w, l_overflow, r_overflow); + else + if (jcp.kernel_kind == embd_bcast && jcp.nb_ic_blocking == 1) + compute_loop_fma(ur_w, l_overflow, r_overflow); + else + compute_loop_fma_core(ur_w, l_overflow, r_overflow); + else + assert("!unknown convolution version"); + + L(skip_compute_loop); + store_output(ur_w); + if (jcp.ndims == 5) pop(reg_oi); +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::generate() +{ + int iw = jcp.iw; + int kw = jcp.kw; + int ur_w = jcp.ur_w; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int ur_w_tail = jcp.ur_w_tail; + int dilate_w = jcp.dilate_w + 1; + int stride_w = jcp.stride_w; + + int dst_shift = jcp.typesize_in * (ur_w / stride_w) * ic_block; + int src_shift = jcp.typesize_out * ur_w * oc_block; + + preamble(); + + mov(reg_src, ptr[param + GET_OFF(src)]); + mov(reg_dst, ptr[param + GET_OFF(dst)]); + mov(reg_ker, ptr[param + GET_OFF(filt)]); + + mov(reg_kh, ptr[param + GET_OFF(kh_padding)]); + mov(reg_src_prf, ptr[param + GET_OFF(src_prf)]); + mov(reg_dst_prf, ptr[param + GET_OFF(dst_prf)]); + mov(reg_ker_prf, ptr[param + GET_OFF(filt_prf)]); + + int l_overflow = nstl::max(0, ((kw - 1) * dilate_w - jcp.l_pad) / stride_w); + int r_overflow = nstl::max(0, ((kw - 1) * dilate_w + - nstl::max(0, jcp.r_pad)) / stride_w); + int r_overflow1 = nstl::max(0, ((kw - 1) * dilate_w + - nstl::max(0, jcp.r_pad) - ur_w_tail) / stride_w); + + int n_oi = iw / ur_w; + if (r_overflow1 > 0) n_oi--; + + if (ur_w == iw) { + compute_loop(ur_w, l_overflow, r_overflow); + } else if (n_oi == 0) { + compute_loop(ur_w, l_overflow, r_overflow1); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + add(reg_src_prf, src_shift); + add(reg_dst_prf, dst_shift); + if (ur_w_tail != 0) + compute_loop(ur_w_tail, 0, r_overflow); + } else { + xor_(reg_oi, reg_oi); + if (l_overflow > 0) { + compute_loop(ur_w, l_overflow, 0); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + add(reg_src_prf, src_shift); + add(reg_dst_prf, dst_shift); + + inc(reg_oi); + } + if ((l_overflow <= 0 && n_oi > 0) + || (l_overflow > 0 && n_oi > 1)) { + Label ow_loop_label; + L(ow_loop_label); { + compute_loop(ur_w, 0, 0); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + add(reg_src_prf, src_shift); + add(reg_dst_prf, dst_shift); + + inc(reg_oi); + cmp(reg_oi, n_oi); + jl(ow_loop_label, T_NEAR); + } + } + if (r_overflow1 > 0) { + compute_loop(ur_w, 0, r_overflow1); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + add(reg_src_prf, src_shift); + add(reg_dst_prf, dst_shift); + } + if (ur_w_tail != 0) { + compute_loop(ur_w_tail, 0, r_overflow); + } + } + + postamble(); +} + +status_t jit_avx512_common_conv_bwd_data_kernel_f32::init_conf( + jit_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d) +{ + if (!mayiuse(avx512_common)) return status::unimplemented; + + jcp = zero(); + + jcp.simd_w = cpu_isa_traits::vlen / sizeof(float); + const bool with_groups = weights_d.ndims() == diff_src_d.ndims() + 1; + int ndims = diff_src_d.ndims(); + + jcp.ndims = ndims; + jcp.prop_kind = cd.prop_kind; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = diff_src_d.dims()[0]; + + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = diff_src_d.dims()[1] / jcp.ngroups; + + jcp.id = (ndims == 5) ? diff_src_d.dims()[2] : 1; + jcp.ih = (ndims == 3) ? 1 : diff_src_d.dims()[ndims-2]; + jcp.iw = diff_src_d.dims()[ndims-1]; + jcp.od = (ndims == 5) ? diff_dst_d.dims()[2] : 1; + jcp.oh = (ndims == 3) ? 1 : diff_dst_d.dims()[ndims-2]; + jcp.ow = diff_dst_d.dims()[ndims-1]; + + jcp.kd = (ndims == 5) ? weights_d.dims()[with_groups + 2] : 1; + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + ndims - 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.f_pad = (ndims == 5) ? cd.padding[0][0] : 0; + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][ndims-4]; + jcp.l_pad = cd.padding[0][ndims-3]; + + jcp.stride_d = (ndims == 5) ? cd.strides[0] : 1; + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[ndims-4]; + jcp.stride_w = cd.strides[ndims-3]; + + jcp.dilate_d = (ndims == 5) ? cd.dilates[0] : 0; + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[ndims-4]; + jcp.dilate_w = cd.dilates[ndims-3]; + if ((jcp.dilate_w != 0 && jcp.stride_w != 1) + || (jcp.dilate_d != 0 && jcp.stride_d != 1) + || (jcp.dilate_h != 0 && jcp.stride_h != 1)) + return status::unimplemented; + + jcp.r_pad = (jcp.ow - 1) * jcp.stride_w + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.iw + jcp.l_pad - 1); + jcp.b_pad = (jcp.oh - 1) * jcp.stride_h + (jcp.kh - 1) * (jcp.dilate_h + 1) + - (jcp.ih + jcp.t_pad - 1); + jcp.back_pad = (jcp.od - 1) * jcp.stride_d + + (jcp.kd - 1) * (jcp.dilate_d + 1) - (jcp.id + jcp.f_pad - 1); + + jcp.aligned_threads = 0; + + jcp.is_1stconv = false; + + jcp.oc_block = jcp.simd_w; + jcp.ic_block = jcp.is_1stconv ? jcp.ic : jcp.simd_w; + + bool ok_to_pad_channels = true + && jcp.ngroups == 1 + && diff_src_d.data_type() == data_type::f32; + + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, jcp.oc_block); + jcp.ic = rnd_up(jcp.ic, jcp.ic_block); + } + + auto dat_tag = pick(ndims - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = with_groups + ? pick(ndims - 3, gOIw16o16i, gOIhw16o16i, gOIdhw16o16i) + : pick(ndims - 3, OIw16o16i, OIhw16o16i, OIdhw16o16i); + jcp.src_tag = diff_src_d.matches_one_of_tag(dat_tag); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.oc % jcp.oc_block == 0 + && jcp.ic % jcp.ic_block == 0 + && jcp.src_tag == dat_tag + && jcp.dst_tag == dat_tag; + if (!args_ok) + return status::unimplemented; + + jcp.nb_ic = jcp.ic / jcp.ic_block; + jcp.nb_oc = jcp.oc / jcp.oc_block; + + jcp.ur_w = jcp.stride_w; + + int regs = 28; + if (jcp.iw <= regs) + jcp.ur_w = jcp.iw; + else { + for (int ur_w = regs; ur_w > 0; --ur_w) + if (ur_w % jcp.stride_w == 0) { + jcp.ur_w = ur_w; + break; + } + } + int l_overflow = nstl::max(0, ((jcp.kw - 1) * (jcp.dilate_w + 1) + - jcp.l_pad) / jcp.stride_w); + int r_overflow1 = nstl::max(0, ((jcp.kw - 1) * (jcp.dilate_w + 1) + - nstl::max(0, jcp.r_pad) - jcp.iw % jcp.ur_w) / jcp.stride_w); + int n_oi = jcp.iw / jcp.ur_w; + if (r_overflow1 > 0) n_oi--; + + if (mayiuse(avx512_common) + && diff_dst_d.data_type() == data_type::f32 + && weights_d.data_type() == data_type::f32 + && diff_src_d.data_type() == data_type::f32) { + jcp.ver = ver_fma; + jcp.typesize_in = sizeof(float); + jcp.typesize_out = sizeof(float); + if (mayiuse(avx512_mic_4ops) + && jcp.stride_w == 1 && jcp.stride_h == 1 && jcp.stride_d == 1) { + jcp.ver = ver_4fma; + } + } else { + return status::unimplemented; + } + + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + if (jcp.wei_tag != wei_tag) + return status::unimplemented; + + if (!utils::everyone_is(0, jcp.dilate_d, jcp.dilate_h, jcp.dilate_w) + && jcp.ver != ver_fma) + return status::unimplemented; + + jcp.nb_ic_blocking = jcp.nb_oc_blocking = 1; + if (jcp.ver == ver_4fma) { + if (jcp.kw == 3 && jcp.kh == 3 && jcp.iw == 7 && jcp.ih == 7) { + jcp.nb_ic_blocking = 2; + } else { + for (int i = jcp.nb_ic; i > 0; i--) + if (i * jcp.ur_w <= regs && jcp.nb_ic % i == 0) { + jcp.nb_ic_blocking = i; + break; + } + } + } + + jcp.loop_order = loop_gnc; + + bool large_code_size = (jcp.ur_w != jcp.ow) + && ((l_overflow <= 0 && n_oi > 0) ||(l_overflow > 0 && n_oi > 1)) + && (r_overflow1 > 0) && (l_overflow > 0); + if (large_code_size) { + const int max_code_size = 24 * 1024; + const int num_ops_per_reg = 6 + jcp.oc_block * jcp.kw; + int mult = 1; + if (l_overflow > 0) mult += 1; + if (r_overflow1 > 0) mult += 1; + for (int ur_w = jcp.ur_w; ur_w > regs/2; --ur_w) { + if ((ur_w / jcp.stride_w) * mult * num_ops_per_reg * 9.2 + < max_code_size) { + if (ur_w % jcp.stride_w == 0) { + jcp.ur_w = ur_w; + break; + } + } + } + } + + if (jcp.ver == ver_fma && mayiuse(avx512_core)) { + int try_nb_ic_blocking = 2; + unsigned int ker_inp_size = typesize * jcp.iw * jcp.ic_block + * try_nb_ic_blocking * jcp.kh; + unsigned int ker_out_size = typesize * jcp.ow * jcp.oc_block; + unsigned int ker_wei_size = typesize * jcp.kh * jcp.kw * jcp.ic_block + * jcp.oc_block * try_nb_ic_blocking; + unsigned int ker_total_size = ker_inp_size + ker_out_size + + ker_wei_size; + if (!(jcp.kw == 1 || (jcp.kw == 5 && jcp.iw < 8) + || (jcp.kw < 5 && ((jcp.iw <= 5 || (jcp.iw > 8 && jcp.iw <= 13)) + || ker_total_size > L1_cache_size ))) + || jcp.stride_h > 1 || jcp.stride_d > 1) { + jcp.kernel_kind = embd_bcast; + jcp.ur_w = nstl::min(jcp.iw, regs); + jcp.nb_ic_blocking = jcp.nb_oc_blocking = 1; + if (!(jcp.kw > 3 || (jcp.kw == 3 && ker_total_size < L1_cache_size + && jcp.ow > 8)) && jcp.stride_h == 1) + if (jcp.nb_ic % try_nb_ic_blocking == 0) { + jcp.nb_ic_blocking = try_nb_ic_blocking; + jcp.ur_w = 31 / (jcp.nb_ic_blocking + 1); + if (jcp.iw < jcp.ur_w) jcp.ur_w = jcp.iw; + } + } else { + jcp.kernel_kind = expl_bcast; + jcp.nb_oc_blocking = 1; + jcp.nb_ic_blocking = 4; + if (jcp.nb_ic < jcp.nb_ic_blocking) jcp.nb_ic_blocking = jcp.nb_ic; + if (jcp.nb_ic % jcp.nb_ic_blocking != 0) + for (int i = jcp.nb_ic_blocking; i > 0; i--) + if (jcp.nb_ic % i == 0) { + jcp.nb_ic_blocking = i; + break; + } + jcp.ur_w = 31 / (jcp.nb_ic_blocking + 1); + if (jcp.iw < jcp.ur_w) jcp.ur_w = jcp.iw; + } + } + jcp.ur_w_tail = jcp.iw % jcp.ur_w; + + if (l_overflow * jcp.stride_w > jcp.ur_w) + return status::unimplemented; + int r_overflow_no_tail = nstl::max(0, ((jcp.kw - 1) * (jcp.dilate_w + 1) + - nstl::max(0, jcp.r_pad) - jcp.ur_w_tail) / jcp.stride_w); + if (r_overflow_no_tail * jcp.stride_w > jcp.ur_w) + return status::unimplemented; + if ((jcp.iw > jcp.ur_w) && (jcp.ur_w % jcp.stride_w != 0)) + return status::unimplemented; + + pick_loop_order(jcp); + + jcp.nb_oc_L2 = jcp.nb_oc; + if (jcp.ver == ver_4fma && (jcp.kh < 5 && jcp.kw < 5)) { + for (int divf = 2, temp_nb = jcp.nb_oc_L2; divf <= jcp.nb_oc; + divf++) { + size_t l2_src = jcp.iw * jcp.ic_block * jcp.nb_ic_blocking * jcp.ih + * jcp.id; + size_t l2_dst = jcp.ow * jcp.oc_block * temp_nb * jcp.oh * jcp.od; + size_t l2_filt = jcp.kw * jcp.oc_block * jcp.ic_block * jcp.kh + * jcp.kd * jcp.nb_ic_blocking * temp_nb; + if (4 * (l2_src + l2_dst + l2_filt) > KNx_L2_EFFECTIVE_CAPACITY) { + if (jcp.kh == 3 && jcp.ih == 7) { + jcp.nb_oc_L2 = 1; + break; + } + temp_nb = (jcp.nb_oc_L2 % divf == 0 ? jcp.nb_oc_L2 / divf + : jcp.nb_oc_L2); + } else { + jcp.nb_oc_L2 = temp_nb; + break; + } + } + } + + args_ok = true + && jcp.ic <= diff_src_d.padded_dims()[1] + && jcp.oc <= diff_dst_d.padded_dims()[1] + && jcp.ic <= weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= weights_d.padded_dims()[with_groups + 0]; + if (!args_ok) return status::unimplemented; + + return status::success; +} + +void jit_avx512_common_conv_bwd_data_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + UNUSED(scratchpad); + UNUSED(jcp); +} + +const int jit_avx512_common_conv_bwd_weights_kernel_f32::max_ur_w = 28; + +void jit_avx512_common_conv_bwd_weights_kernel_f32::od_step_comeback_pointers() +{ + Label kd_comeback_label; + + /* 'depth' loop count bound by 'kd_work_size' */ + mov(kj, reg_kd_count); + L(kd_comeback_label); { + int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + int iw = jcp.ver == ver_4fma ? jcp.tr_iw : jcp.iw; + sub(reg_input, + jcp.typesize_in * (jcp.dilate_d + 1) * jcp.ih * iw * inp_mult); + sub(reg_kernel, + jcp.typesize_out * jcp.kh * jcp.kw * jcp.ic_block * jcp.oc_block); + dec(kj); + cmp(kj, 0); + jg(kd_comeback_label, T_NEAR); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::oh_step_comeback_pointers() +{ + Label kh_comeback_label, kd_comeback_label; + mov(kj, reg_kh); + L(kh_comeback_label); { + int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + int iw = jcp.ver == ver_4fma ? jcp.tr_iw : jcp.iw; + sub(reg_input, jcp.typesize_in * (jcp.dilate_h + 1) * iw * inp_mult); + sub(reg_kernel, + jcp.typesize_out * jcp.kw * jcp.ic_block * jcp.oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_comeback_label, T_NEAR); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::compute_ic_block_step_fma( + int ur_w, int pad_l, int pad_r, + int ic_block_step, int input_offset, int kernel_offset, + int output_offset, bool input_wraparound) +{ + + int kw = jcp.kw; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) + vmovups(Zmm(i_kw * ic_block_step + i_ic), + EVEX_compress_addr(reg_kernel, typesize * (i_kw * ic_block + + i_ic) * jcp.oc_block + kernel_offset)); + + for (int i_ur = 0; i_ur < ur_w; i_ur++) { + if (i_ur == 0) { + vmovups(Zmm(kw * ic_block_step + (i_ur + 0) % 4), + EVEX_compress_addr(reg_output, typesize * (i_ur + 0) + * oc_block + output_offset)); + if (ur_w > 1) vmovups(Zmm(kw * ic_block_step + (i_ur + 1) % 4), + EVEX_compress_addr(reg_output, typesize * (i_ur + 1) * oc_block + + output_offset)); + if (ur_w > 2) vmovups(Zmm(kw * ic_block_step + (i_ur + 2) % 4), + EVEX_compress_addr(reg_output, typesize * (i_ur + 2) * oc_block + + output_offset)); + if (ur_w > 3) vmovups(Zmm(kw * ic_block_step + (i_ur + 3) % 4), + EVEX_compress_addr(reg_output, typesize * (i_ur + 3) * oc_block + + output_offset)); + } else if (i_ur + 3 < ur_w) + vmovups(Zmm(kw * ic_block_step + (i_ur + 3) % 4), + EVEX_compress_addr(reg_output, typesize * (i_ur + 3) * oc_block + + output_offset)); + + for (int i_kw = 0; i_kw < kw; i_kw++) { + int i_iw = i_ur * jcp.stride_w + i_kw * (jcp.dilate_w + 1); + if (i_iw - pad_l < 0 || i_iw > (ur_w - 1) * jcp.stride_w + + (kw - 1) * (jcp.dilate_w + 1) - pad_r) continue; + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + const size_t i_offset = (size_t)input_offset + + (size_t)typesize * (jcp.ver == ver_4fma + ? (i_iw - pad_l + i_ic * jcp.tr_iw) + : (jcp.is_1stconv + ? (i_iw - pad_l) + (size_t)i_ic + * ((size_t)jcp.ih*jcp.iw*jcp.id) + : (i_iw - pad_l) * ic_block + i_ic)); + vfmadd231ps(Zmm(i_kw * ic_block_step + i_ic), + Zmm(kw * ic_block_step + i_ur % 4), + EVEX_compress_addr_safe(reg_input, i_offset, reg_long_offt, + true)); + } + } + } + + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) + vmovups(EVEX_compress_addr(reg_kernel, typesize + * (i_kw * ic_block + i_ic) * jcp.oc_block + kernel_offset), + Zmm(i_kw * ic_block_step + i_ic)); +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::compute_ic_block_step_4fma( + int ur_w, int pad_l, int pad_r, + int ic_block_step, int input_offset, int kernel_offset, + int output_offset, bool input_wraparound) +{ + // TODO: add prefetches to fma version as well + + assert(jcp.ver == ver_4fma); + + int kw = jcp.kw; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + + auto zmm_ker = [=](int i_kw, int i_ic) { + return Zmm(i_kw * ic_block_step + i_ic); + }; + + auto ker_addr = [=](int i_kw, int i_ic) { + size_t local_offset + = jcp.typesize_out * (i_kw * ic_block + i_ic) * jcp.oc_block; + return EVEX_compress_addr(reg_kernel, local_offset + kernel_offset); + }; + + auto inp_addr = [=](int i_iw, int i_ic, ptrdiff_t extra_offset = 0) { + int stride = jcp.tr_iw * (jcp.is_1stconv ? jcp.ih : 1); + int local_offset = jcp.typesize_in * (i_iw + i_ic * stride); + return EVEX_compress_addr(reg_input, + local_offset + input_offset + extra_offset); + }; + + auto zmm_out = [=](int i_iw) { + // TODO: move reg calc to global member funcs + const int out_zmm_base_idx = 28; + return Zmm(out_zmm_base_idx + i_iw % 4); + }; + + auto out_addr = [=](int i_ur) { + return EVEX_compress_addr(reg_output, + jcp.typesize_in * i_ur * oc_block + output_offset); + }; + + auto pf_callback = [=](int i_ur, int i_kw, int i_ic) { + assert(i_ur % 4 == 0); + if (i_ur == 0) + prefetcht1(ker_addr(i_kw, i_ic)); + if (i_ur + 4 >= ur_w) + prefetcht0(ker_addr(i_kw, i_ic)); + + const ptrdiff_t next_input_block_offset + = jcp.typesize_in * ic_block_step * jcp.tr_iw; + if (i_ur % 16 == 4 && i_kw == 0) { + if (i_ur + 16 < ur_w) + prefetcht0(inp_addr(i_ur + 16, i_ic)); + else + prefetcht0(inp_addr(0, i_ic, next_input_block_offset)); + } + if (i_ur % 16 == 4 && i_kw == 1) { + if (input_wraparound) + prefetcht1(inp_addr(i_ur, i_ic, -input_offset)); + else + prefetcht1(inp_addr(i_ur, i_ic, next_input_block_offset)); + } + }; + + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + auto zmm = zmm_ker(i_kw, i_ic); + vpxord(zmm, zmm, zmm); + } + + for (int i_ur = 0; i_ur < ur_w; i_ur += 4) { + + for (int i = 0; i < 4; i++) { + auto zmm = zmm_out(i_ur + i); + if (i_ur + i < ur_w) + vmovups(zmm, out_addr(i_ur + i)); + else + vpxord(zmm, zmm, zmm); + prefetcht0(out_addr(i_ur + i + 4)); + } + + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + int i_iw = i_ur + i_kw; + v4fmaddps(zmm_ker(i_kw, i_ic), + zmm_out(i_ur), inp_addr(i_iw, i_ic)); + pf_callback(i_ur, i_kw, i_ic); + } + } + + for (int i_kw = 0; i_kw < kw; i_kw++) + for (int i_ic = 0; i_ic < ic_block_step; i_ic++) { + auto addr = ker_addr(i_kw, i_ic); + auto zmm = zmm_ker(i_kw, i_ic); + vaddps(zmm, zmm, addr); + vmovups(addr, zmm); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::compute_ic_block_step( + int ur_w, int pad_l, int pad_r, + int ic_block_step, int input_offset, int kernel_offset, + int output_offset, bool input_wraparound) +{ + if (jcp.ver == ver_4fma) + compute_ic_block_step_4fma(ur_w, pad_l, pad_r, + ic_block_step, input_offset, kernel_offset, output_offset, + input_wraparound); + else if (jcp.ver == ver_fma) + compute_ic_block_step_fma(ur_w, pad_l, pad_r, + ic_block_step, input_offset, kernel_offset, output_offset, + input_wraparound); + else + assert(!"unknown convolution version"); +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32 + ::compute_oh_step_unroll_ow_icblock( + int ic_block_step, int max_ur_w) +{ + UNUSED(max_ur_w); + + Label kh_label, kd_label; + + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int inp_mul = !jcp.is_1stconv ? ic_block : 1; + int iw = jcp.ver == ver_4fma ? jcp.tr_iw : jcp.iw; + int ow = jcp.ow; + + int r_pad = nstl::max(0, (ow - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + int l_pad = jcp.l_pad; + + if (jcp.ndims == 5) { + L(kd_label); + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + } + + mov(kj, reg_kh); + L(kh_label); + { + for (int i_b_ic = 0; i_b_ic < jcp.ic_block; i_b_ic += ic_block_step) { + const int input_offset = jcp.typesize_in + * (jcp.ver == ver_4fma ? i_b_ic * iw : i_b_ic); + compute_ic_block_step(jcp.ur_w, l_pad, r_pad, ic_block_step, + input_offset, jcp.typesize_out * i_b_ic * jcp.oc_block, 0, + i_b_ic + ic_block_step >= jcp.ic_block); + } + add(reg_input, jcp.typesize_in * (jcp.dilate_h + 1) * iw * inp_mul); + add(reg_kernel, jcp.typesize_out * jcp.kw * ic_block * oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_label, T_NEAR); + } + + if (jcp.ndims == 5) { + add(aux_reg_input, + jcp.typesize_in * (jcp.dilate_d + 1) * jcp.ih * iw * inp_mul); + add(aux_reg_kernel, jcp.typesize_out * jcp.kh * jcp.kw * ic_block + * oc_block); + dec(ki); + cmp(ki, 0); + jg(kd_label, T_NEAR); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32 + ::compute_oh_step_unroll_ow( + int ic_block_step, int max_ur_w) +{ + Label kh_label, ic_block_label, kd_label; + + UNUSED(max_ur_w); + + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + + int ow = jcp.ow; + + int r_pad = nstl::max(0, + (ow - 1) * jcp.stride_w + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.iw + jcp.l_pad - 1)); + int l_pad = jcp.l_pad; + + if (jcp.ndims == 5) { + L(kd_label); + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + } + + mov(kj, reg_kh); + L(kh_label); + { + xor_(b_ic, b_ic); + L(ic_block_label); { + compute_ic_block_step(ow, l_pad, r_pad, ic_block_step, + 0, 0, 0); + size_t inp_icblk_stride = jcp.is_1stconv + ? (size_t)jcp.ih * jcp.iw * jcp.id + : (jcp.ver == ver_4fma ? jcp.tr_iw : 1); + size_t input_offset + = inp_icblk_stride * jcp.typesize_in * ic_block_step; + safe_add(reg_input, input_offset, reg_long_offt); + add(reg_kernel, jcp.typesize_out * ic_block_step * oc_block); + add(b_ic, ic_block_step); + cmp(b_ic, jcp.ic_block); + jl(ic_block_label, T_NEAR); + } + + if (jcp.is_1stconv) { + size_t input_offset + = (size_t)jcp.typesize_in * jcp.id * jcp.ih * jcp.iw * ic_block; + safe_sub(reg_input, input_offset, reg_long_offt); + add(reg_input, jcp.typesize_in * (jcp.dilate_h + 1) * jcp.iw); + } else if (jcp.ver != ver_4fma) { + add(reg_input, jcp.typesize_in + * ((jcp.dilate_h + 1) * jcp.iw - 1) * ic_block); + } + add(reg_kernel, jcp.typesize_out * (jcp.kw - 1) * ic_block * oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_label, T_NEAR); + } + if (jcp.ndims == 5) { + add(aux_reg_input, jcp.typesize_in * (jcp.dilate_d + 1) * jcp.ih + * jcp.iw * (jcp.is_1stconv ? 1 : ic_block)); + add(aux_reg_kernel, jcp.typesize_out * jcp.kh * jcp.kw * ic_block + * oc_block); + dec(ki); + cmp(ki, 0); + jg(kd_label, T_NEAR); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32 + ::compute_oh_step_common( + int ic_block_step, int max_ur_w) +{ + Label kh_label, ic_block_label, ow_block_label, kd_label; + + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + + int ow = jcp.ow; + int r_pad = nstl::max(0, (ow - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + int l_pad = jcp.ver == ver_4fma ? 0 : jcp.l_pad; + + int ur_w = nstl::min(ow, max_ur_w); + int ur_w_trips = ow / ur_w; + int ur_w_tail = ow % ur_w; + if ((ur_w_tail == 0 && r_pad != 0) + || r_pad >= ur_w_tail) { + if (ur_w_trips > 1) { + ur_w_tail += ur_w; + ur_w_trips--; + } else { + ur_w_tail += (ur_w - ur_w / 2); + ur_w = ur_w / 2; + } + } + + int inp_mult = (jcp.is_1stconv || jcp.ver == ver_4fma) ? 1 : ic_block; + int input_comeback = (ur_w_trips * ur_w * jcp.stride_w - l_pad) * inp_mult; + int output_comeback = ur_w_trips * ur_w * oc_block; + + if (jcp.ndims == 5) { + L(kd_label); + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + } + + mov(kj, reg_kh); + L(kh_label); { + xor_(b_ic, b_ic); + L(ic_block_label); { + if (l_pad != 0) { + ur_w_trips--; + compute_ic_block_step(ur_w, l_pad, 0, ic_block_step, 0, 0, 0); + add(reg_input, jcp.typesize_in * (ur_w * jcp.stride_w - l_pad) + * inp_mult); + add(reg_output, jcp.typesize_in * ur_w * oc_block); + } + + if (ur_w_trips > 0) { + xor_(reg_ur_w_trips, reg_ur_w_trips); + L(ow_block_label); { + compute_ic_block_step(ur_w, 0, 0, ic_block_step, 0, 0, 0); + add(reg_input, jcp.typesize_in * ur_w * jcp.stride_w + * inp_mult); + add(reg_output, jcp.typesize_in * ur_w * oc_block); + + inc(reg_ur_w_trips); + cmp(reg_ur_w_trips, ur_w_trips); + jl(ow_block_label, T_NEAR); + } + } + + if (ur_w_tail > 0) compute_ic_block_step(ur_w_tail, 0, r_pad, + ic_block_step, 0, 0, 0); + + sub(reg_input, jcp.typesize_in * input_comeback); + sub(reg_output, jcp.typesize_in * output_comeback); + int inp_icblk_stride = jcp.is_1stconv + ? jcp.ih * jcp.iw * jcp.id + : (jcp.ver == ver_4fma ? jcp.tr_iw : 1); + size_t input_offset + = inp_icblk_stride * jcp.typesize_in * ic_block_step; + safe_add(reg_input, input_offset, reg_long_offt); + add(reg_kernel, jcp.typesize_out * ic_block_step * oc_block); + + add(b_ic, ic_block_step); + cmp(b_ic, jcp.ic_block); + jl(ic_block_label, T_NEAR); + } + if (jcp.is_1stconv) { + size_t input_offset + = (size_t)jcp.typesize_in * jcp.id * jcp.ih * jcp.iw * ic_block; + safe_sub(reg_input, input_offset, reg_long_offt); + add(reg_input, jcp.typesize_in * (jcp.dilate_h + 1) * jcp.iw); + } else if (jcp.ver != ver_4fma) { + add(reg_input, jcp.typesize_in + * ((jcp.dilate_h + 1 ) * jcp.iw - 1) * ic_block); + } + add(reg_kernel, jcp.typesize_out * (jcp.kw - 1) * ic_block * oc_block); + dec(kj); + cmp(kj, 0); + jg(kh_label, T_NEAR); + } + if (jcp.ndims == 5) { + add(aux_reg_input, jcp.typesize_in * (jcp.dilate_d + 1) * jcp.ih + * jcp.iw * (jcp.is_1stconv ? 1 : ic_block)); + add(aux_reg_kernel, jcp.typesize_out * jcp.kh * jcp.kw * ic_block + * oc_block); + dec(ki); + cmp(ki, 0); + jg(kd_label, T_NEAR); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32 + ::compute_oh_step_disp() +{ + int ic_block_step = jcp.kw <= 3 ? 8 : (jcp.kw <= 7 ? 4 : 2); + if (jcp.is_1stconv) { + bool large_code = jcp.kw >= 7 && (jcp.l_pad > 0 || jcp.t_pad > 0); + ic_block_step + = (jcp.kw * jcp.ic_block <= 28 && !large_code) ? jcp.ic_block : 1; + } + + bool too_large_to_unroll + = (jcp.kw > 1 || jcp.kh > 1 || jcp.kd > 1) + && (jcp.stride_w > 1 || jcp.stride_h > 1 || jcp.stride_d > 1); + + int ow = jcp.ow; + if (jcp.ndims == 5) { + /* NOTE: reg_kd_count = aux_reg_input = r12. The following order of + * 'movs' must be guaranteed. */ + mov(ki, reg_kd_count); + push(reg_kd_count); + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + } + + if (jcp.kw <= 3 && ow <= 16 && !too_large_to_unroll) + compute_oh_step_unroll_ow_icblock(ic_block_step, max_ur_w); + else if (ow <= max_ur_w) + compute_oh_step_unroll_ow(ic_block_step, max_ur_w); + else + compute_oh_step_common(ic_block_step, max_ur_w); + + if (jcp.ndims == 5) { + mov(reg_input, aux_reg_input); + mov(reg_kernel, aux_reg_kernel); + pop(reg_kd_count); + od_step_comeback_pointers(); + } else { + oh_step_comeback_pointers(); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::maybe_zero_kernel() +{ + Label skip_zeroing, zeroing_loop; + + mov(reg_tmp, ptr[param + GET_OFF(channel)]); + cmp(reg_tmp, 0); + jz(skip_zeroing, T_NEAR); + + Zmm zero = Zmm(0); + vpxord(zero, zero, zero); + xor_(reg_tmp, reg_tmp); + L(zeroing_loop); { + assert(jcp.oc_block * jcp.typesize_out + == cpu_isa_traits::vlen); + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) + vmovups(ptr[reg_kernel + reg_tmp + ic1 * jcp.oc_block + * jcp.typesize_out], zero); + add(reg_tmp, jcp.ic_block * jcp.oc_block * jcp.typesize_out); + cmp(reg_tmp, jcp.ic_block * jcp.oc_block * jcp.kw * jcp.kh * jcp.kd + * jcp.typesize_out); + jnz(zeroing_loop); + } + + L(skip_zeroing); +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::bias_kernel() +{ + Label skip_bias, bias_loop, skip_load_bias; + + mov(reg_tmp, ptr[param + GET_OFF(flags)]); + test(reg_tmp,reg_tmp); + jne(skip_bias, T_NEAR); + + mov(reg_bias, ptr[param + GET_OFF(bias)]); + mov(reg_output, ptr[param + GET_OFF(dst)]); + vpxord(Zmm(1), Zmm(1), Zmm(1)); + + mov(reg_tmp, ptr[param + GET_OFF(channel)]); + cmp(reg_tmp, 0); + jne(skip_load_bias, T_NEAR); + vmovups(Zmm(1), ptr[reg_bias]); + + L(skip_load_bias); + + mov(reg_oi, ptr[param + GET_OFF(d_worksize)]); + sub(reg_oi, ptr[param + GET_OFF(d_index)]); + mov(reg_tmp, jcp.oc_block * jcp.ow * jcp.oh * jcp.typesize_out); + imul(reg_oi, reg_tmp); + + xor_(reg_tmp, reg_tmp); + L(bias_loop); { + vmovups(Zmm(0), ptr[reg_output + reg_tmp]); + vaddps(Zmm(1), Zmm(1), Zmm(0)); + add(reg_tmp, jcp.oc_block * jcp.typesize_out); + cmp(reg_tmp, reg_oi); + jl(bias_loop); + } + vmovups(EVEX_compress_addr(reg_bias,0), Zmm(1)); + + L(skip_bias); +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32 + ::compute_oh_loop_common() +{ + int b_pad = jcp.b_pad; + int t_pad = jcp.t_pad; + bool is_dilated = jcp.dilate_h != 0; + int dilate_h = jcp.dilate_h + 1; + int stride_h = jcp.stride_h; + const int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + int iw = jcp.ver == ver_4fma ? jcp.tr_iw : jcp.iw; + Label oh_label, oh_label_end, oh_tpad_label, oh_tpad_tail_label, + oh_bpad_label, oh_bpad_label_end, od_label, od_label_end, + oh_dilate_label_shift, oh_dilate_label_noshift, oh_dilate_label_end; + + int ow = jcp.ow; + + mov(reg_kh, jcp.kh); + xor_(reg_ih_count, reg_ih_count); + xor_(reg_oj, reg_oj); + /* Compute 'top' edge */ + if (t_pad > 0) { + const int kh_range = 1 + (jcp.kh - 1) * dilate_h; + const int overflow + = nstl::max(0, jcp.kh - div_up(t_pad + jcp.ih, dilate_h)); + const int underflow = div_up(t_pad, dilate_h); + const int initial_inp_ker_overlap = jcp.kh - overflow - underflow; + mov(reg_kh, initial_inp_ker_overlap); + add(reg_kernel, jcp.typesize_out * underflow * jcp.kw * jcp.ic_block + * jcp.oc_block); + // generate loop to process kernel while it remains within t_pad + ih + if (kh_range < t_pad + jcp.ih) { + if (is_dilated) { + const int tail = t_pad % dilate_h; + const int shift = tail == 0 ? 0 : dilate_h - tail; + mov(reg_tmp, shift); + if (tail != 0) + add(reg_input, jcp.typesize_in * shift * iw * inp_mult); + } + L(oh_tpad_label); { + compute_oh_step_disp(); + add(reg_output, jcp.typesize_in * ow * jcp.oc_block); + if (is_dilated) { + inc(reg_tmp); + cmp(reg_tmp, dilate_h); + jl(oh_dilate_label_shift, T_NEAR); + // unshift input as new kernel element enters + sub(reg_input, jcp.typesize_in * (dilate_h - 1) * iw * inp_mult); + xor_(reg_tmp, reg_tmp); + } + // kernel overlap only changes when (t_pad + oj) % dilate_h == 0 + sub(reg_kernel, jcp.typesize_out * stride_h * jcp.kw + * jcp.ic_block * jcp.oc_block); + add(reg_kh, stride_h); + if (is_dilated) { + jmp(oh_dilate_label_noshift, T_NEAR); + L(oh_dilate_label_shift); + // shift input as old kernel element progresses + add(reg_input, jcp.typesize_in * stride_h * iw * inp_mult); + L(oh_dilate_label_noshift); + } + inc(reg_oj); + add(reg_ih_count, stride_h); + + // final number of kernel elements that overlap with input + const int final_inp_ker_overlap + = nstl::min(jcp.kh, div_up(jcp.ih, dilate_h)); + cmp(reg_kh, final_inp_ker_overlap); + jl(oh_tpad_label, T_NEAR); + } + } + // need second loop to process kernel if it is larger than the input + // (does not apply to dilations as they must have unit stride) + if (kh_range >= jcp.ih + (t_pad % stride_h == 0 ? stride_h : + t_pad % stride_h)) { + assert(!is_dilated); + mov(reg_kh, jcp.ih); + L(oh_tpad_tail_label); { + compute_oh_step_disp(); + add(reg_output, jcp.typesize_in * ow * jcp.oc_block); + sub(reg_kernel, jcp.typesize_out * stride_h * jcp.kw + * jcp.ic_block * jcp.oc_block); + + inc(reg_oj); + add(reg_ih_count, stride_h); + + cmp(reg_ih_count, nstl::min(t_pad, jcp.oh * stride_h)); + jl(oh_tpad_tail_label, T_NEAR); + } + } + // correct any excess shifts to kernel and input + // (does not apply to dilations as they must have unit stride, + // kernel must fit inside input, and padding is smaller than input) + if (t_pad <= jcp.oh * stride_h) { + // kernel has moved beyond padding (adjust for stride effects) + if (t_pad % stride_h != 0) { + assert(!is_dilated); + int inp_corr = stride_h - t_pad % stride_h; + add(reg_kernel, jcp.typesize_out * inp_corr * jcp.kw + * jcp.ic_block * jcp.oc_block); + add(reg_input, jcp.typesize_in * inp_corr * iw * inp_mult); + } + } else { + // kernel still overlaps padding (complete reset) + assert(!is_dilated); + sub(reg_kernel, jcp.typesize_out * (t_pad - jcp.oh * stride_h) + * jcp.kw * jcp.ic_block * jcp.oc_block); + } + } + + cmp(reg_ih_count, jcp.ihp - b_pad - (jcp.kh - 1) * dilate_h); + jge(oh_label_end, T_NEAR); + cmp(reg_oj, jcp.oh); + jge(oh_label, T_NEAR); + + /* Compute middle block(s) */ + mov(reg_kh, jcp.kh); + L(oh_label); { + compute_oh_step_disp(); + add(reg_input, jcp.typesize_in * stride_h * iw * inp_mult); + add(reg_output, jcp.typesize_in * ow * jcp.oc_block); + + inc(reg_oj); + add(reg_ih_count, stride_h); + + cmp(reg_ih_count, jcp.ihp - b_pad - (jcp.kh - 1) * dilate_h); + jge(oh_label_end, T_NEAR); + + cmp(reg_oj, jcp.oh); + jl(oh_label, T_NEAR); + } + L(oh_label_end); + + /* Compute bottom edge */ + if (b_pad > 0) { + cmp(reg_oj, jcp.oh); + jge(oh_bpad_label_end, T_NEAR); + + if (is_dilated) { + mov(reg_kh, jcp.kh - 1); // assumes unit stride for dilations + mov(reg_tmp, 0); + } else { + mov(reg_kh, jcp.ihp - b_pad); + sub(reg_kh, reg_ih_count); + } + L(oh_bpad_label); + { + compute_oh_step_disp(); + add(reg_input, jcp.typesize_in * stride_h * iw * inp_mult); + add(reg_output, jcp.typesize_in * ow * jcp.oc_block); + if (is_dilated) { + inc(reg_tmp); + cmp(reg_tmp, dilate_h); + jl(oh_dilate_label_end, T_NEAR); + xor_(reg_tmp, reg_tmp); + } + sub(reg_kh, stride_h); + cmp(reg_kh, 0); + jle(oh_bpad_label_end, T_NEAR); + if (is_dilated) + L(oh_dilate_label_end); + + inc(reg_oj); + cmp(reg_oj, jcp.oh); + jl(oh_bpad_label, T_NEAR); + } + L(oh_bpad_label_end); + } +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::compute_d_loop_common() { + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + const int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + int iw = jcp.ver == ver_4fma ? jcp.tr_iw : jcp.iw; + int ow = jcp.ow; + const int input_backpad_overlap + = div_up(jcp.id + jcp.f_pad - (jcp.kd - 1), jcp.stride_d); + + const size_t filter_shift + = jcp.typesize_out * jcp.kh * jcp.kw * ic_block * oc_block; + const size_t input_shift = jcp.typesize_in * jcp.ih * iw * inp_mult; + const size_t output_shift = jcp.typesize_in * jcp.oh * ow * jcp.oc_block; + + Label d_loop_label, loop_end_label, common_block_label, fpad_end_label, + backpad_end_label, backpad_label; + + if (jcp.with_bias) bias_kernel(); + + /* initially offset 'kd' by f_pad */ + add(reg_kernel, ptr[param + GET_OFF(kd_offset)]); + + mov(reg_input_d, ptr[param + GET_OFF(src)]); + mov(reg_output_d, ptr[param + GET_OFF(dst)]); + mov(reg_d_index, ptr[param + GET_OFF(d_index)]); + mov(reg_kd_count, ptr[param + GET_OFF(kd_padding)]); + + cmp(reg_d_index, ptr[param + GET_OFF(d_worksize)]); + jge(loop_end_label, T_NEAR); + + L(d_loop_label); + + mov(reg_input, reg_input_d); + mov(reg_output, reg_output_d); + + push(reg_input_d); + push(reg_output_d); + push(reg_d_index); + + compute_oh_loop_common(); + + pop(reg_d_index); + pop(reg_output_d); + pop(reg_input_d); + + /* Compute 'front' edge */ + if (jcp.f_pad > 0) { + + /* Check if within fpad region */ + cmp(reg_d_index, div_up(jcp.f_pad, jcp.stride_d)); + jge(fpad_end_label, T_NEAR); + + /* Fpad steps */ + sub(reg_kernel, filter_shift * jcp.stride_d); + add(reg_kd_count, jcp.stride_d); + + /* Final number of kernel elements that overlap with input */ + const int inp_ker_overlap = nstl::min(jcp.kd, jcp.id); + cmp(reg_kd_count, inp_ker_overlap); + jl(common_block_label, T_NEAR); + + /* Correct any excess shifts to kernel and input */ + if (jcp.f_pad <= jcp.od * jcp.stride_d) { + /* Filter has moved beyond padding (adjust for stride effects) */ + if (jcp.f_pad % jcp.stride_d != 0) { + int inp_corr = jcp.stride_d - jcp.f_pad % jcp.stride_d; + add(reg_kernel, filter_shift * inp_corr); + add(reg_input_d, input_shift * inp_corr); + } + } else { + /* Filter still overlaps padding (complete reset) */ + sub(reg_kernel, (jcp.f_pad - jcp.od * jcp.stride_d) * filter_shift); + } + + /* Apply correction */ + mov(reg_kd_count, jcp.kd); + jmp(common_block_label); + + L(fpad_end_label); + } + + /* Compute bottom edge */ + if (jcp.back_pad > 0) { + + /* Check if within back_pad region */ + cmp(reg_d_index, input_backpad_overlap - 1); + jl(backpad_end_label, T_NEAR); + jg(backpad_label, T_NEAR); + + /* Execute overlap correction between the filter and the initial + * back_pad region. */ + mov(reg_kd_count, + jcp.id + jcp.f_pad - input_backpad_overlap * jcp.stride_d); + jmp(backpad_end_label, T_NEAR); + + L(backpad_label); + sub(reg_kd_count, jcp.stride_d); + cmp(reg_kd_count, 0); + jle(loop_end_label, T_NEAR); + + L(backpad_end_label); + } + + /* Compute middle block */ + add(reg_input_d, input_shift * jcp.stride_d); + + /* Execute common block and loop */ + L(common_block_label); + add(reg_output_d, output_shift); + inc(reg_d_index); + cmp(reg_d_index, ptr[param + GET_OFF(d_worksize)]); + jl(d_loop_label, T_NEAR); + + L(loop_end_label); +} + +bool jit_avx512_common_conv_bwd_weights_kernel_f32::compute_full_spat_loop() { + // FIXME: use register mapping from the class declaration + bool ok = jcp.ver == ver_4fma + && everyone_is(0, jcp.dilate_h, jcp.dilate_w) + && everyone_is(1, jcp.stride_h, jcp.stride_w); + if (!ok) return false; + if (jcp.l_pad != jcp.kw / 2 || jcp.t_pad != jcp.kh / 2) + return false; + + // General code layout: + // + // Blocking over OH -- top level + // (Reduces L2 pressure; not very useful right now) + // Loop over all KHxKW kernel -- emit_kh_kw_loop() + // Loop over OH block -- emit_h_loop() + // Loop over OW blocks -- emit_fma_block() + // (Supports both fully unrolled and partially unrolled versions to + // reduce code size) + // Loop over OW block -- emit_fma_step() + + int max_working_set_size = 128 * 1024; + int pad_ow = jcp.ow; + + int inp_row_size = jcp.ic_block * jcp.tr_iw * jcp.typesize_in; + int out_row_size = jcp.oc_block * pad_ow * jcp.typesize_in; + int row_size = inp_row_size + out_row_size; + + int h_block_size = jcp.oh; + int working_set_size = row_size * h_block_size; + + if (working_set_size > max_working_set_size) { + int opt_working_set_size = 48 * 1024; + assert(opt_working_set_size < max_working_set_size); + + while (working_set_size > opt_working_set_size) { + for (int i = 2; i <= h_block_size; i++) + if (i == h_block_size) + h_block_size = h_block_size / 2; + else if (h_block_size % i == 0) { + h_block_size = h_block_size / i; + break; + } + working_set_size = row_size * h_block_size; + + if (h_block_size == 1 && working_set_size > opt_working_set_size) + return false; + } + } + + // NB1: t_pad <= oh_block_size and b_pad <= last_oh_block_size (see below) + if (h_block_size < nstl::max(1, jcp.t_pad) + || jcp.b_pad > (jcp.oh % h_block_size == 0 ? h_block_size + : jcp.oh % h_block_size)) + return false; + + // check that we can use simple arithmetic for prefetch address + // calculations + // TODO: we need some traits for this check (Roma) + int cache_line_size = 64; + assert(jcp.ic_block * typesize == 64); + assert(jcp.oc_block * typesize == 64); + + int num_inp_l2_pfs = jcp.tr_iw * h_block_size; + int avg_h_loop_len = h_block_size; + int num_inp_l2_pfs_per_fma_block + = div_up(num_inp_l2_pfs, avg_h_loop_len * jcp.kw * jcp.kh); + int num_out_l2_pfs = pad_ow * h_block_size; + int num_out_l2_pfs_per_fma_block + = div_up(num_out_l2_pfs, avg_h_loop_len * jcp.kw * jcp.kh); + + Opmask reg_h_block = k1; // 32-bit only on Intel(R) Xeon Phi(TM) processors + Reg64 reg_kh = rax; + Reg64 reg_kw = rbx; + Reg64 reg_tmp = abi_not_param1; + Reg32 reg_tmp_w = reg_tmp.cvt32(); + Reg64 reg_ohs = rdx; + Reg64 reg_ihs = rsi; + Reg64 reg_h = r8; + Reg64 reg_i = r9; + Reg64 reg_j = r10; + + Reg64 reg_inp = r13; + Reg64 reg_out = r14; + Reg64 reg_ker = r15; + + Reg64 reg_inp_pf_l1 = rbp; + + Reg64 reg_inp_pf_l2 = r11; + Reg64 reg_out_pf_l2 = r12; + + Xmm reg_inp_pf_save = xmm17; + Xmm reg_out_pf_save = xmm18; + + Reg64 reg_inp_save = abi_param1; + Reg64 reg_out_save = reg_tmp; + + auto zmm_out = [&](int oi) { return Zmm(24 + oi % 8); }; + auto zmm_ker = [&](int ic1) { return Zmm(ic1); }; + auto inp_addr = [&](int oi, int ic1) { + return ptr[reg_inp + (ic1 * jcp.tr_iw + oi) * jcp.typesize_in]; + }; + auto out_addr = [&](int oi, int oj = 0) { + assert(jcp.ver == ver_4fma); + return ptr[reg_out + + ((oi + oj * jcp.ow) * jcp.oc_block) * jcp.typesize_in]; + }; + auto ker_addr = [&](int ic1) { + return ptr[reg_ker + ic1 * jcp.oc_block * jcp.typesize_out]; + }; + + auto emit_block = [&](int h_block_size, + bool is_last_block, bool is_last_kh_kw_iter, bool is_last_row) + { + // TODO: add an fma version (Roma) + auto pad_ow = jcp.ow; + + int ow4u = rnd_up(pad_ow, 4); + int def_step_size = 16; + + bool has_w_tail = (pad_ow % def_step_size != 0 + || pad_ow % 4 != 0); + bool full_w_unroll = pad_ow / def_step_size < 2 + has_w_tail; + + auto emit_step = [&](int ur_ow, + int num_inp_l1_pfs_per_fma_step, + int num_inp_l2_pfs_per_fma_step, + int num_out_l2_pfs_per_fma_step, bool is_w_tail) + { + bool block_wraparound = is_w_tail && is_last_row; + + assert(ur_ow % 4 == 0); + int tail_size = ow4u % ur_ow; + int this_ur_ow + = (is_w_tail && tail_size) ? tail_size : ur_ow; + int ow_last_chunk4 = pad_ow % 4; + int ow_zero_tail4 = ow_last_chunk4 + ? 4 - ow_last_chunk4 : 0; + + auto emit_out_pf = [&](int oi) { +#if 1 + if (oi + def_step_size < ur_ow || !block_wraparound) + mic_prefetcht0(ptr[reg_out + + ((def_step_size + oi) + * jcp.oc_block * jcp.typesize_in)]); + else { + assert(block_wraparound); + assert(oi + def_step_size >= ur_ow); + mic_prefetcht0(ptr[reg_out_save + + ((oi + def_step_size - ur_ow) + * jcp.oc_block * jcp.typesize_in)]); + } +#else + // XXX: This is an alternative prefetching strategy that + // always prefetches the next row. Keeping it here for + // future experiments (Roma) + if (!block_wraparound) + mic_prefetcht0(ptr[reg_out + + (jcp.ow + oi) * jcp.oc_block * jcp.typesize_in]); + else + mic_prefetcht0(ptr[reg_out + reg_ohs + - ((h_block_size - 1) * jcp.ow + - oi) * jcp.oc_block * jcp.typesize_in]); +#endif + if (oi < num_out_l2_pfs_per_fma_step) + mic_prefetcht1(ptr[reg_out_pf_l2 + + oi * jcp.oc_block * jcp.typesize_in]); + }; + + auto emit_inp_pf = [&](int oi4, int ic1) { + int pf_slot_idx = ic1 + oi4 / 4 * jcp.ic_block; + int num_pf_slots = jcp.ic_block * ur_ow / 4; + + int num_pfs = num_inp_l1_pfs_per_fma_step + + num_inp_l2_pfs_per_fma_step; + int pf_freq = nstl::max(1, num_pf_slots / num_pfs); + + if (pf_slot_idx % pf_freq) + return; + + int pf_idx = pf_slot_idx / pf_freq; + + if (pf_idx < num_inp_l2_pfs_per_fma_step) + mic_prefetcht1(ptr[reg_inp_pf_l2 + + pf_idx * jcp.ic_block * jcp.typesize_in]); + else { + pf_idx -= num_inp_l2_pfs_per_fma_step; + // prefetch the 'tail' of the cache line because most of + // the accesses are not aligned + mic_prefetcht0(ptr[reg_inp_pf_l1 + + pf_idx * jcp.ic_block * jcp.typesize_in + + cache_line_size - jcp.typesize_in]); + } + }; + + auto numloads = 4; + + int steps = this_ur_ow; + for (int oi4 = 0; oi4 < steps; oi4 += numloads) { + for (int oi1 = 0; oi1 < numloads; oi1++) { + int oi = oi4 + oi1; + if (!is_w_tail || oi < (this_ur_ow - ow_zero_tail4)) { + vmovups(zmm_out(oi), out_addr(oi)); + emit_out_pf(oi); + } else { + auto zmm = zmm_out(oi); + vpxord(zmm, zmm, zmm); + } + } + + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) { + if (jcp.ver == ver_4fma) { + v4fmaddps(zmm_ker(ic1), + zmm_out(oi4), inp_addr(oi4, ic1)); + } else { + assert(!"unknown convolution version"); + } + emit_inp_pf(oi4, ic1); + } + } + }; + + // Input is transposed and padded but we only access about jcp.iw + // elements so use that to compute the # of cache lines in each 'row' + int num_inp_l1_pfs + = div_up(jcp.iw * jcp.typesize_in, cache_line_size) * jcp.ic_block; + + if (full_w_unroll) { + emit_step(ow4u, num_inp_l1_pfs, + num_inp_l2_pfs_per_fma_block, + num_out_l2_pfs_per_fma_block, true); + add(reg_inp_pf_l2, num_inp_l2_pfs_per_fma_block * cache_line_size); + add(reg_out_pf_l2, num_out_l2_pfs_per_fma_block * cache_line_size); + } else { + Label w_loop; + int num_w_iters = pad_ow / def_step_size; + int num_w_iters_full = num_w_iters + has_w_tail; + int num_inp_l1_pfs_per_fma_step + = div_up(num_inp_l1_pfs, num_w_iters_full); + int num_inp_l2_pfs_per_fma_step + = div_up(num_inp_l2_pfs_per_fma_block, num_w_iters_full); + int num_out_l2_pfs_per_fma_step + = div_up(num_out_l2_pfs_per_fma_block, num_w_iters_full); + mov(reg_i, num_w_iters); + L(w_loop); { + emit_step(def_step_size, num_inp_l1_pfs_per_fma_step, + num_inp_l2_pfs_per_fma_step, + num_out_l2_pfs_per_fma_step, false); + add(reg_inp, def_step_size * jcp.typesize_in); + add(reg_out, def_step_size * jcp.oc_block * jcp.typesize_in); + add(reg_inp_pf_l1, + num_inp_l1_pfs_per_fma_step * cache_line_size); + add(reg_inp_pf_l2, + num_inp_l2_pfs_per_fma_step * cache_line_size); + add(reg_out_pf_l2, + num_out_l2_pfs_per_fma_step * cache_line_size); + sub(reg_i, 1); + jnz(w_loop); + } + if (has_w_tail) { + emit_step(def_step_size, num_inp_l1_pfs_per_fma_step, + num_inp_l2_pfs_per_fma_step, + num_out_l2_pfs_per_fma_step, true); + add(reg_inp_pf_l2, + num_inp_l2_pfs_per_fma_step * cache_line_size); + add(reg_out_pf_l2, + num_out_l2_pfs_per_fma_step * cache_line_size); + } + // reset reg_inp and reg_out because emit_h_loop expects + // unmodified pointers + int w_offset = num_w_iters * def_step_size; + sub(reg_inp, w_offset * jcp.typesize_in); + sub(reg_out, w_offset * jcp.oc_block * jcp.typesize_in); + } + }; + + auto emit_h_loop = [&](int h_block_size, + bool is_last_block, bool is_last_kh_kw_iter) + { + Label h_loop, skip_h_loop; + mov(reg_j, 1); + cmp(reg_j, reg_h); + je(skip_h_loop, T_NEAR); + L(h_loop); { + + lea(reg_inp_pf_l1, + ptr[reg_inp + jcp.tr_iw * jcp.ic_block * jcp.typesize_in]); + emit_block(h_block_size, + is_last_block, is_last_kh_kw_iter, false); + + add(reg_inp, jcp.tr_iw * jcp.ic_block * jcp.typesize_in); + add(reg_out, pad_ow * jcp.oc_block * jcp.typesize_in); + add(reg_j, 1); + cmp(reg_j, reg_h); + jb(h_loop); + } + + L(skip_h_loop); + + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) + mic_prefetcht0(ker_addr(ic1)); + + lea(reg_inp_pf_l1, ptr[reg_inp_save + reg_kw * jcp.typesize_in]); + emit_block(h_block_size, is_last_block, is_last_kh_kw_iter, true); + }; + + auto emit_kh_kw_loop = [&](bool is_first_block, bool is_last_block, + int h_block_size) + { + xor_(reg_kh, reg_kh); + Label kh_loop, kh_loop_end; + + int last_oh_block_size + = jcp.oh - rnd_up(jcp.oh - h_block_size, h_block_size); + int oh_block_size = (is_last_block) ? last_oh_block_size : h_block_size; + // NB1: t_pad <= oh_block_size and b_pad <= last_oh_block_size + int ih_block_size = oh_block_size - 1 + jcp.kh + - is_first_block * jcp.t_pad - is_last_block * jcp.b_pad; + + L(kh_loop); { + // determine starting indices for this block + if (is_first_block) { + xor_(reg_tmp, reg_tmp); + mov(reg_ohs, jcp.t_pad); + sub(reg_ohs, reg_kh); + cmovb(reg_ohs, reg_tmp); + + mov(reg_ihs, reg_ohs); + sub(reg_ihs, jcp.t_pad); + add(reg_ihs, reg_kh); + } else { + xor_(reg_ohs, reg_ohs); + mov(reg_ihs, reg_kh); + } + + // determine effective size of block based on padding + mov(reg_tmp, oh_block_size); + sub(reg_tmp, reg_ohs); + mov(reg_h, ih_block_size); + sub(reg_h, reg_ihs); + cmp(reg_tmp, reg_h); + cmovb(reg_h, reg_tmp); + + Label kh_loop_work; + cmp(reg_h, 0); + jg(kh_loop_work, T_NEAR); + + // empty h loop for this jcp.kh: + // - set the output to 0 if necessary + // - move ker pt + // - jump to the end + sub(reg_h, 1); + Label skip_ker_zeroing; + + // The reg_ker ptr has highest bit set if the output needs to be + // zeroed. Those who have byte-aligned their data will suffer the + // consiquences :( + // TODO: move the flag to a mask register? (Roma) + test(reg_ker, 1); + jz(skip_ker_zeroing, T_NEAR); + + Label zeroing_loop; + vpxord(zmm0, zmm0, zmm0); + and_(reg_ker, ~1); // temporarily clear the zeroing flag + mov(reg_tmp, jcp.kw); + L(zeroing_loop); { + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) + vmovups(ker_addr(ic1), zmm0); + add(reg_ker, jcp.oc_block * jcp.ic_block * jcp.typesize_out); + sub(reg_tmp, 1); + jnz(zeroing_loop, T_NEAR); + } + // restore the zeroing flag (it will be cleared after the end of + // emit_kh_kw_loop, but we may need it until then) + or_(reg_ker, 1); + jmp(kh_loop_end, T_NEAR); + + L(skip_ker_zeroing); + add(reg_ker, jcp.oc_block * jcp.ic_block * jcp.kw + * jcp.typesize_out); + jmp(kh_loop_end, T_NEAR); + + L(kh_loop_work); + + mul_by_const(reg_ihs, reg_tmp, + jcp.tr_iw * jcp.ic_block * jcp.typesize_in); + mul_by_const(reg_ohs, reg_tmp, + pad_ow * jcp.oc_block * jcp.typesize_in); + + add(reg_inp, reg_ihs); + add(reg_out, reg_ohs); + + Label kw_loop; + xor_(reg_kw, reg_kw); + L(kw_loop); { + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) { + auto zmm = zmm_ker(ic1); + vpxord(zmm, zmm, zmm); + mic_prefetcht1(ker_addr(ic1)); + } + + mov(reg_out_save, reg_out); + mov(reg_inp_save, reg_inp); + lea(reg_inp, ptr[reg_inp + reg_kw * jcp.typesize_in]); + +#if 0 + // XXX: Generate code with special prefetches when switching + // blocks or at the end of the last block. Disabled to reduce + // code size and because there's no performance benefit (Roma) + Label regular_h_loop, end_h_loop; + cmp(reg_kw, jcp.kw - 1); + jne(regular_h_loop, T_NEAR); + cmp(reg_kh, jcp.kh - 1); + jne(regular_h_loop, T_NEAR); + + emit_h_loop(oh_block_size, is_last_block, true); + jmp(end_h_loop, T_NEAR); + + L(regular_h_loop); + emit_h_loop(oh_block_size, is_last_block, false); + + L(end_h_loop); +#else + emit_h_loop(oh_block_size, is_last_block, false); +#endif + + mov(reg_out, reg_out_save); + mov(reg_inp, reg_inp_save); + + Label do_store; + // The reg_ker ptr has highest bit set if the output needs to + // be zeroed. Those who have byte-aligned their data will + // suffer the consiquences :( + mov(reg_tmp, reg_ker); + and_(reg_ker, ~1); + test(reg_tmp, 1); + jnz(do_store, T_NEAR); + + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) { + auto zmm = zmm_ker(ic1); + if (jcp.ver == ver_4fma) { + vaddps(zmm, ker_addr(ic1)); + } else { + assert(!"unknown convolution version"); + } + } + + L(do_store); + for (int ic1 = 0; ic1 < jcp.ic_block; ic1++) { + auto zmm = zmm_ker(ic1); + vmovups(ker_addr(ic1), zmm); + } + + mov(reg_ker, reg_tmp); + add(reg_ker, jcp.ic_block * jcp.oc_block * jcp.typesize_out); + add(reg_kw, 1); + cmp(reg_kw, jcp.kw); + jl(kw_loop); + } + + sub(reg_inp, reg_ihs); + sub(reg_out, reg_ohs); + + + L(kh_loop_end); + add(reg_kh, 1); + cmp(reg_kh, jcp.kh); + jl(kh_loop); + } + }; + + mov(reg_inp, ptr[param + GET_OFF(src)]); + mov(reg_out, ptr[param + GET_OFF(dst)]); + mov(reg_ker, ptr[param + GET_OFF(filt)]); + mov(reg_inp_pf_l2, ptr[param + GET_OFF(src_prf)]); + mov(reg_out_pf_l2, ptr[param + GET_OFF(dst_prf)]); + mov(reg_tmp, ptr[param + GET_OFF(channel)]); + or_(reg_ker, reg_tmp); + + bool single_kh_kw_loop = (h_block_size == jcp.oh); + + size_t inp_row_step = jcp.tr_iw * jcp.ic_block * jcp.typesize_in; + size_t first_inp_block_step = inp_row_step * (h_block_size - jcp.t_pad); + size_t inp_block_step = inp_row_step * h_block_size; + size_t out_block_step = pad_ow * jcp.oc_block * jcp.typesize_in + * h_block_size; + + if (!single_kh_kw_loop) { + // Save the original prefetch pointers from the OpenMP driver + vmovq(reg_inp_pf_save, reg_inp_pf_l2); + vmovq(reg_out_pf_save, reg_out_pf_l2); + mov(reg_inp_pf_l2, reg_inp); + add(reg_inp_pf_l2, first_inp_block_step); + mov(reg_out_pf_l2, reg_out); + add(reg_out_pf_l2, out_block_step); + } + emit_kh_kw_loop(true, single_kh_kw_loop, h_block_size); + + if (!single_kh_kw_loop) { + size_t ker_reset_offset + = jcp.oc_block * jcp.ic_block * jcp.typesize_out * jcp.kw * jcp.kh; + sub(reg_ker, ker_reset_offset); + and_(reg_ker, ~1); // Clear the zeroing flag for subsequent updates + + add(reg_inp, first_inp_block_step); + add(reg_out, out_block_step); + mov(reg_inp_pf_l2, reg_inp); + add(reg_inp_pf_l2, inp_block_step); + mov(reg_out_pf_l2, reg_out); + add(reg_out_pf_l2, out_block_step); + + int num_innermost_iters = div_up(jcp.oh, h_block_size) - 2; + if (num_innermost_iters > 0) { + Label h_block_loop; + + mov(reg_tmp_w, num_innermost_iters); + kmovw(reg_h_block, reg_tmp_w); + L(h_block_loop); { + emit_kh_kw_loop(false, false, h_block_size); + sub(reg_ker, ker_reset_offset); + add(reg_inp, inp_row_step * h_block_size); + add(reg_out, out_block_step); + mov(reg_inp_pf_l2, reg_inp); + add(reg_inp_pf_l2, inp_block_step); + mov(reg_out_pf_l2, reg_out); + add(reg_out_pf_l2, out_block_step); + kmovw(reg_tmp_w, reg_h_block); + sub(reg_tmp_w, 1); + kmovw(reg_h_block, reg_tmp_w); + jnz(h_block_loop); + } + } + + // Restore the original prefetch pointers that came from the OpenMP + // driver + vmovq(reg_inp_pf_l2, reg_inp_pf_save); + vmovq(reg_out_pf_l2, reg_out_pf_save); + emit_kh_kw_loop(false, true, h_block_size); + } + + return true; +} + +bool jit_avx512_common_conv_bwd_weights_kernel_f32 + ::flat_4ops_compute() { + const auto &j = jcp; + const bool ok = j.ver == ver_4fma && j.is_1stconv + && everyone_is(0, j.dilate_h, j.dilate_w); + if (!ok) return false; + + Reg64 reg_ptr_tr_src = r8; + Reg64 reg_ptr_dst = r9; + Reg64 reg_ptr_wei = r10; + Reg64 reg_ptr_bia = r11; + + Reg64 reg_kh_step = rax; + Reg64 reg_oh = abi_not_param1; + Reg64 reg_kh = rdx; + + Reg32 reg_flag_save = ebx; + Reg32 reg_flag = esi; + + Zmm vbia(31); + + auto zmm_wei = [&](int kh, int kw) { + return Zmm(8 + kh * j.kw + kw); + }; + auto zmm_dst = [&](int ow) { + return Zmm(ow % 8); + }; + + auto addr_tr_src = [&](int kh, int iw) { + return ptr[reg_ptr_tr_src + + (kh * j.stride_w * j.tr_ld + iw) * jcp.typesize_in]; + }; + auto addr_dst = [&](int ow) { + return ptr[reg_ptr_dst + ow * jcp.oc_block * jcp.typesize_in]; + }; + auto addr_wei = [&](int kh, int kw) { + return ptr[reg_ptr_wei + (kh * j.kw + kw) * j.oc_block + * jcp.typesize_out]; + }; + + auto emit_fma_block = [&](int kh_step) { + for (int kh = 0; kh < kh_step; ++kh) { + for (int kw = 0; kw < j.kw; ++kw) { + auto vwei = zmm_wei(kh, kw); + vpxord(vwei, vwei, vwei); + } + } + + for (int ow = 0; ow < j.ow; ow += 4) { + for (int _ow = ow; _ow < ow + 4; ++_ow) { + auto vdst = zmm_dst(_ow); + if (_ow < j.ow) + vmovups(vdst, addr_dst(_ow)); + else + vpxord(vdst, vdst, vdst); + } + + for (int kh = 0; kh < kh_step; ++kh) { + for (int kw = 0; kw < j.kw; ++kw) { + const int iw = ow + (kw % j.stride_w) * j.tr_ld + + (kw / j.stride_w); + v4fmaddps(zmm_wei(kh, kw), zmm_dst(ow), + addr_tr_src(kh, iw)); + if (1 && kh == 0 && kw < 4) { + prefetcht1(ptr[reg_ptr_dst + + (j.ow + ow + kw) * jcp.oc_block + * jcp.typesize_in]); + } + if (j.with_bias && kh_step == 1) { /* [bwd_w:b:r1] */ + const int off = kw + 4 - j.kw; + if (off >= 0 && ow + off < j.ow) + vaddps(vbia, vbia, zmm_dst(ow + off)); + } + } + } + } + + Label l_store; + test(reg_flag, FLAG_MB_FIRST); + jnz(l_store, T_NEAR); + for (int kh = 0; kh < kh_step; ++kh) { + for (int kw = 0; kw < j.kw; ++kw) + vaddps(zmm_wei(kh, kw), addr_wei(kh, kw)); + } + L(l_store); + for (int kh = 0; kh < kh_step; ++kh) { + for (int kw = 0; kw < j.kw; ++kw) + vmovups(addr_wei(kh, kw), zmm_wei(kh, kw)); + } + }; + + auto emit_kh_loop = [&]() { + const int kh_step_rem = j.kh % j.kh_step; + xor_(reg_kh, reg_kh); + mov(reg_kh_step, j.kh_step); + + Label l_kh_loop; + L(l_kh_loop); { + Label l_done; + + if (kh_step_rem != 0) { + Label l_keep_kh_step; + cmp(reg_kh, j.kh - j.kh_step); + jle(l_keep_kh_step, T_NEAR); + + mov(reg_kh_step, kh_step_rem); + emit_fma_block(kh_step_rem); + jmp(l_done, T_NEAR); + + L(l_keep_kh_step); + } + + emit_fma_block(j.kh_step); + + L(l_done); + + add(reg_ptr_tr_src, j.kh_step * j.stride_w * j.tr_ld + * jcp.typesize_in); + add(reg_ptr_wei, j.kh_step * j.kw * j.oc_block * jcp.typesize_out); + add(reg_kh, j.kh_step); + + cmp(reg_kh, j.kh); + jl(l_kh_loop, T_NEAR); + } + + const int kh_steps = rnd_up(j.kh, j.kh_step); + sub(reg_ptr_tr_src, kh_steps * j.stride_w * j.tr_ld * jcp.typesize_in); + sub(reg_ptr_wei, kh_steps * j.kw * j.oc_block * jcp.typesize_out); + }; + + auto emit_oh_loop = [&]() { + mov(reg_oh, j.oh); + + Label l_oh_loop; + L(l_oh_loop); { + Label l_restore_mb_flag, l_jump; + + cmp(reg_oh, j.oh); + je(l_restore_mb_flag, T_NEAR); + + and_(reg_flag, ~FLAG_MB_FIRST); + jmp(l_jump, T_NEAR); + + L(l_restore_mb_flag); + mov(reg_flag, reg_flag_save); + + L(l_jump); + + emit_kh_loop(); + + add(reg_ptr_tr_src, j.stride_h * j.stride_w * j.tr_ld + * jcp.typesize_in); + add(reg_ptr_dst, j.ow * j.oc_block * jcp.typesize_in); + + dec(reg_oh); + jnz(l_oh_loop, T_NEAR); + } + }; + + auto emit_bia_store = [&]() { + if (!j.with_bias) return; + + Label l_bia_store, l_bia_skip; + test(reg_flag, FLAG_IC_FIRST); + jz(l_bia_skip); + + test(reg_flag, FLAG_MB_FIRST); + jnz(l_bia_store, T_NEAR); + vaddps(vbia, ptr[reg_ptr_bia]); + L(l_bia_store); + vmovups(ptr[reg_ptr_bia], vbia); + L(l_bia_skip); + }; + + mov(reg_ptr_tr_src, ptr[param + GET_OFF(src)]); + mov(reg_ptr_dst, ptr[param + GET_OFF(dst)]); + mov(reg_ptr_wei, ptr[param + GET_OFF(filt)]); + mov(reg_ptr_bia, ptr[param + GET_OFF(bias)]); + mov(reg_flag_save, ptr[param + GET_OFF(flags)]); + + vpxord(vbia, vbia, vbia); + emit_oh_loop(); + emit_bia_store(); + + return true; +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::compute_loop() +{ + if (flat_4ops_compute()) + return; + if (compute_full_spat_loop()) + return; + + maybe_zero_kernel(); + + if (jcp.ndims == 5) compute_d_loop_common(); + else compute_oh_loop_common(); +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::generate() +{ + preamble(); + + mov(reg_input, ptr[param + GET_OFF(src)]); + mov(reg_output, ptr[param + GET_OFF(dst)]); + mov(reg_kernel, ptr[param + GET_OFF(filt)]); + + compute_loop(); + + postamble(); +} + +status_t jit_avx512_common_conv_bwd_weights_kernel_f32::init_conf( + jit_conv_conf_t &jcp, const convolution_desc_t &cd, + memory_desc_t &src_md, memory_desc_t &diff_weights_md, + memory_desc_t &diff_bias_md, memory_desc_t &diff_dst_md) { + if (!mayiuse(avx512_common)) + return status::unimplemented; + + const memory_desc_wrapper src_d(&src_md); + const memory_desc_wrapper diff_weights_d(&diff_weights_md); + const memory_desc_wrapper diff_bias_d(&diff_bias_md); + const memory_desc_wrapper diff_dst_d(&diff_dst_md); + + const bool with_groups = diff_weights_d.ndims() == src_d.ndims() + 1; + int ndims = src_d.ndims(); + + jcp = zero(); + + jcp.simd_w = cpu_isa_traits::vlen / sizeof(float); + jcp.ndims = ndims; + jcp.prop_kind = cd.prop_kind; + + jcp.ngroups = with_groups ? diff_weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + jcp.id = (ndims == 5) ? src_d.dims()[2] : 1; + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[ndims-2]; + jcp.iw = src_d.dims()[ndims-1]; + jcp.od = (ndims == 5) ? diff_dst_d.dims()[2] : 1; + jcp.oh = (ndims == 3) ? 1 : diff_dst_d.dims()[ndims-2]; + jcp.ow = diff_dst_d.dims()[ndims-1]; + + jcp.kd = (ndims == 5) ? diff_weights_d.dims()[with_groups + 2] : 1; + jcp.kh = (ndims == 3) ? 1 : diff_weights_d.dims()[with_groups + ndims-2]; + jcp.kw = diff_weights_d.dims()[with_groups + ndims-1]; + + jcp.f_pad = (ndims == 5) ? cd.padding[0][0] : 0; + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][ndims-4]; + jcp.l_pad = cd.padding[0][ndims-3]; + + jcp.stride_d = (ndims == 5) ? cd.strides[0] : 1; + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[ndims-4]; + jcp.stride_w = cd.strides[ndims-3]; + + jcp.dilate_d = (ndims == 5) ? cd.dilates[0] : 0; + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[ndims-4]; + jcp.dilate_w = cd.dilates[ndims-3]; + + const int kh_range = 1 + (jcp.kh - 1) * (jcp.dilate_h + 1); + bool ok = true + // general condition to simplify dilations + && IMPLICATION(jcp.dilate_d != 0, jcp.stride_d == 1) + && IMPLICATION(jcp.dilate_h != 0, jcp.stride_h == 1) + && IMPLICATION(jcp.dilate_w != 0, jcp.stride_w == 1) + // special condition to simplify dilations in compute_oh_loop_common + && IMPLICATION(jcp.dilate_h != 0, kh_range <= jcp.ih); + if (!ok) + return status::unimplemented; + + jcp.r_pad = nstl::max(0, (jcp.ow - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + jcp.b_pad = nstl::max(0, (jcp.oh - 1) * jcp.stride_h + + (jcp.kh - 1) * (jcp.dilate_h + 1) - (jcp.ih + jcp.t_pad - 1)); + jcp.back_pad = nstl::max(0, (jcp.od - 1) * jcp.stride_d + + (jcp.kd - 1) * (jcp.dilate_d + 1) - (jcp.id + jcp.f_pad - 1)); + + /* XXX: currently, does not support dilation_d > 0 */ + if (ndims == 5) + if (jcp.dilate_d > 0) + return status::unimplemented; + + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + jcp.ohp = jcp.oh; + jcp.owp = jcp.ow; + jcp.aligned_threads = 0; + + /* check for the 1st convolution */ + jcp.is_1stconv = is_1stconv(jcp); + + jcp.oc_block = jcp.simd_w; + + bool ok_to_pad_channels = true + && jcp.ngroups == 1 + && src_d.data_type() == data_type::f32; + + if (ok_to_pad_channels) + jcp.oc = rnd_up(jcp.oc, jcp.simd_w); + + if (jcp.oc % jcp.oc_block) + return status::unimplemented; + + auto dst_tag = pick(ndims - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = with_groups + ? pick(ndims - 3, gOIw16i16o, gOIhw16i16o, gOIdhw16i16o) + : pick(ndims - 3, OIw16i16o, OIhw16i16o, OIdhw16i16o); + + if (diff_dst_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(diff_dst_md, dst_tag)); + jcp.dst_tag = dst_tag; + } else { + jcp.dst_tag = diff_dst_d.matches_one_of_tag(dst_tag); + } + if (jcp.dst_tag != dst_tag) + return status::unimplemented; + + /* conditions on bias memory */ + jcp.with_bias = cd.diff_bias_desc.format_kind != format_kind::undef; + if (jcp.with_bias) { + if (diff_bias_d.format_kind() == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_bias_md, x)); + } + + jcp.nb_oc = jcp.oc / jcp.oc_block; + + /* kernel applicability check wrt boundaries + * the conditions are quite general across the kernels we have, + * but ideally the check should belong to a specific kernel... */ + const int max_pad = ((jcp.kh - 1) * (jcp.dilate_h + 1) + 1) / 2; + const bool boundaries_ok = true + && jcp.t_pad <= max_pad + && jcp.b_pad <= max_pad + && IMPLICATION(jcp.f_pad > 0, jcp.kd < jcp.id + jcp.f_pad) + && jcp.f_pad < jcp.kd; + if (!boundaries_ok) + return status::unimplemented; + + /* yet another common check */ + if (jcp.kw > 14) + return status::unimplemented; + + /* setting register strategy */ + for (int ur_w = nstl::min(max_ur_w, jcp.ow); ur_w > 0; --ur_w) { + if (jcp.ow % ur_w == 0) { jcp.ur_w = ur_w; break; } + } + + if (jcp.is_1stconv) { + auto src_tag = pick(ndims - 3, ncw, nchw, ncdhw); + if (src_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md, src_tag)); + jcp.src_tag = src_tag; + } else { + jcp.src_tag = src_d.matches_one_of_tag(src_tag); + if (jcp.ic == 1 && jcp.src_tag != src_tag) + jcp.src_tag = src_d.matches_one_of_tag( + pick(ndims - 3, nwc, nhwc, ndhwc)); + } + if (jcp.src_tag == format_tag::undef) + return status::unimplemented; + + const bool src_ok = true + && utils::everyone_is(data_type::f32, + src_d.data_type(), diff_weights_d.data_type(), + diff_dst_d.data_type()) + && one_of(jcp.ic, 1, 2, 3) + && jcp.ngroups == 1; + if (!src_ok) + return status::unimplemented; + + const int tr_ld = rnd_up(div_up(jcp.iw + jcp.l_pad + jcp.r_pad, + jcp.stride_w), 16); + const int kh_step = nstl::max((28 - jcp.with_bias) / jcp.kw, 1); + const int kh_step_rem = jcp.kh % kh_step; + + const auto wei_4fma_tag = with_groups + ? pick(ndims - 3, gOiw16o, gOihw16o, gOidhw16o) + : pick(ndims - 3, Oiw16o, Oihw16o, Oidhw16o); + + auto current_wei_tag = format_tag::undef; + if (diff_weights_d.format_kind() != format_kind::any) + current_wei_tag = diff_weights_d.matches_one_of_tag(wei_4fma_tag); + + const bool use_4fma = true + && one_of(ndims, 3, 4) + && mayiuse(avx512_mic_4ops) + && mkldnn_thr_syncable() + && everyone_is(0, jcp.dilate_d, jcp.dilate_h, jcp.dilate_w) + && everyone_is(0, jcp.l_pad, jcp.r_pad, jcp.t_pad, jcp.b_pad) + && jcp.kw <= 28 - jcp.with_bias + && jcp.stride_w == 4 + && tr_ld / jcp.simd_w <= 4 /* [bwd_w:tr_src:r1] */ + && IMPLICATION(jcp.with_bias, kh_step_rem == 1) /* [bwd_w:b:r1] */ + && IMPLICATION(diff_weights_d.format_kind() != format_kind::any, + current_wei_tag == wei_4fma_tag); + + if (use_4fma) { + jcp.ver = ver_4fma; + jcp.kh_step = kh_step; + jcp.tr_ld = tr_ld; + jcp.ic_block = 1; + if (diff_weights_d.format_kind() == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_weights_md, wei_4fma_tag)); + jcp.wei_tag = wei_4fma_tag; + } else { + jcp.ver = ver_fma; + jcp.ic_block = jcp.ic; + + wei_tag = with_groups + ? pick(ndims - 3, gOwi16o, gOhwi16o, gOdhwi16o) + : pick(ndims - 3, Owi16o, Ohwi16o, Odhwi16o); + + if (diff_weights_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(diff_weights_md, wei_tag)); + jcp.wei_tag = wei_tag; + } else { + jcp.wei_tag = diff_weights_d.matches_one_of_tag(wei_tag); + } + if (jcp.wei_tag != wei_tag) + return status::unimplemented; + } + + jcp.nb_ic = jcp.ic / jcp.ic_block; + } else { + auto src_tag = pick(ndims - 3, nCw16c, nChw16c, nCdhw16c); + if (src_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md, src_tag)); + jcp.src_tag = src_tag; + } else { + jcp.src_tag = src_d.matches_one_of_tag(src_tag); + } + if (jcp.src_tag != src_tag) + return status::unimplemented; + + if (diff_weights_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(diff_weights_md, wei_tag)); + jcp.wei_tag = wei_tag; + } else { + jcp.wei_tag = diff_weights_d.matches_one_of_tag(wei_tag); + } + if (jcp.wei_tag != wei_tag) + return status::unimplemented; + + jcp.ic_block = jcp.simd_w; + if (ok_to_pad_channels) + jcp.ic = rnd_up(jcp.ic, jcp.ic_block); + jcp.nb_ic = jcp.ic / jcp.ic_block; + if ((mayiuse(avx512_mic) || mayiuse(avx512_core)) + && utils::everyone_is(data_type::f32, + src_d.data_type(), diff_weights_d.data_type(), + diff_dst_d.data_type())) { + jcp.ver = ver_fma; + if (one_of(ndims, 3, 4) && mayiuse(avx512_mic_4ops) && jcp.stride_w == 1 && + everyone_is(0, jcp.dilate_d, jcp.dilate_h, jcp.dilate_w) && + mkldnn_thr_syncable()) { + jcp.ver = ver_4fma; + } + } else { + return status::unimplemented; + } + if (jcp.ver == ver_4fma) { + jcp.ur_w = jcp.ow; + // XXX, BUGBUGBUG, but not a FIXME: this assumes that it's OK to + // cross the right boundary. The only requirement is not to have + // NaNs there because another multiplicand is always guaranteed to + // be zero. This also may require the top-level driver to allocate + // four extra guarding elements at the very end of the buffer. + // I'm not proud of this hack, but it improves performance by + // about 5-10% depending on the dimensions (Roma) + + const int tr_round = 4; + + jcp.tr_iw = rnd_up(jcp.iw + jcp.kw - 1, tr_round); + jcp.tr_src_num_guard_elems = tr_round; // upper bound + } + } + + if (utils::one_of(jcp.ver, ver_4fma, ver_fma)) { + jcp.typesize_in = sizeof(float); + jcp.typesize_out = sizeof(float); + } else + return status::unimplemented; + + bool args_ok = true + && jcp.ic % jcp.ic_block == 0 + && jcp.oc % jcp.oc_block == 0 + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= diff_dst_d.padded_dims()[1] + && jcp.ic <= diff_weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= diff_weights_d.padded_dims()[with_groups + 0]; + if (!args_ok) return status::unimplemented; + + { // balancing + int nthr, nthr_mb, nthr_g, nthr_oc_b, nthr_ic_b; + balance(jcp, nthr, nthr_mb, nthr_g, nthr_oc_b, nthr_ic_b); + jcp.nthr = nthr; + jcp.nthr_mb = nthr_mb; + jcp.nthr_g = nthr_g; + jcp.nthr_oc_b = nthr_oc_b; + jcp.nthr_ic_b = nthr_ic_b; + } + + return status::success; +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + if (jcp.ver == ver_4fma) { + if (jcp.is_1stconv) { + const size_t tr_src_size = + jcp.nthr / jcp.nthr_oc_b * jcp.ih * jcp.stride_w * jcp.tr_ld; + scratchpad.book(key_conv_tr_src, jcp.typesize_in * tr_src_size); + } else { + // XXX: See the comment about tr_iw and guarding elements in + // jit_avx512_common_conv_bwd_weights_kernel_f32::init_conf() + const size_t max_nthr = jcp.nthr_mb * jcp.ngroups * jcp.nb_ic; + const size_t min_tr_src_size_per_thr + = jcp.ih * jcp.ic_block * jcp.tr_iw; + const size_t tr_src_size = max_nthr * min_tr_src_size_per_thr + + jcp.tr_src_num_guard_elems; + scratchpad.book(key_conv_tr_src, jcp.typesize_in * tr_src_size); + } + + /* prepare synchronization contexts */ + if (jcp.nthr_oc_b > 1) { + const int tr_src_bctx_size = jcp.nthr / jcp.nthr_oc_b; + scratchpad.book(key_conv_tr_src_bctx, + sizeof(simple_barrier::ctx_t) * tr_src_bctx_size); + } + } + + if (jcp.nthr_mb > 1) { + const int wei_size = jcp.ngroups * jcp.oc * jcp.ic + * jcp.kh * jcp.kw * jcp.kd; + const int bia_size = jcp.ngroups * jcp.oc; + const size_t wei_bia_reduction_size = wei_size + bia_size; + + scratchpad.book(key_conv_wei_bia_reduction, + jcp.typesize_out * wei_bia_reduction_size * (jcp.nthr_mb - 1)); + scratchpad.book(key_conv_wei_bia_reduction_bctx, + sizeof(simple_barrier::ctx_t)); + } + + if (jcp.with_bias && jcp.oc != jcp.oc_without_padding) + scratchpad.book(key_conv_padded_bias, jcp.typesize_out * jcp.oc); +} + +void jit_avx512_common_conv_bwd_weights_kernel_f32::balance( + const jit_conv_conf_t &j, int &nthr_, int &nthr_mb_, int &nthr_g_, + int &nthr_oc_b_, int &nthr_ic_b_) +{ + nthr_ = nthr_mb_ = nthr_g_ = nthr_oc_b_ = nthr_ic_b_ = 1; + + const int max_threads = mkldnn_get_max_threads(); + + if (max_threads < j.ngroups) { + /* simplification... fortunately it doesn't hurt much */ + return; + } + + if (!mkldnn_thr_syncable() && j.ver == ver_4fma) { + // should not happen -- the driver is not ready + // for TBB-like non-synchronous threading yet + return; + } + + if (j.ver == ver_4fma && j.is_1stconv) { + nthr_g_ = 1; + nthr_oc_b_ = 1; + nthr_ic_b_ = nstl::min(j.nb_ic, max_threads); + nthr_mb_ = nstl::min(max_threads / nthr_ic_b_, j.mb); + nthr_ = nthr_mb_ * nthr_oc_b_ * nthr_ic_b_ * nthr_g_; + return; + } + + nthr_g_ = j.ngroups; + const int nthr = max_threads / nthr_g_; + + auto calc_mem_cost = [=](int nthr_mb, int nthr_oc_b, int nthr_ic_b) { + /* calculate per thread memory cost (read/write). high level optimizer + * tries to minimize memory consumption. few notes: + * (n1) unclear why, but that essentially helps first convolution... + * (n2) assuming the reduction over minibatch is always there: + * - instead of 8 it should be 5 here (write ~= 2 read): + * kernel: temporal workspace 1 write + * reduction: 1 read from workspace and 1 write to the diff_wei + * - but experiments showed 8 works better than 5 or 6... */ + + const int src_coef = j.ver == ver_4fma ? 4 : 1; + const int dst_coef = 1; + const int wei_coef = 8; + + return 0 + + src_coef + * div_up(j.mb, nthr_mb) * div_up(j.ngroups, nthr_g_) + * div_up(j.nb_ic, nthr_ic_b) * j.ic_block * j.ih * j.iw * j.id + / j.stride_d / j.stride_h / j.stride_w /* (n1) */ + + dst_coef + * div_up(j.mb, nthr_mb) * div_up(j.ngroups, nthr_g_) + * div_up(j.nb_oc, nthr_oc_b) * j.oc_block * j.oh * j.ow * j.od + + wei_coef /* (n2) */ + * div_up(j.ngroups, nthr_g_) + * div_up(j.nb_oc, nthr_oc_b) * div_up(j.nb_ic, nthr_ic_b) + * j.kh * j.kw * j.kd * j.ic_block * j.oc_block; + }; + + int best_mem_cost = calc_mem_cost(nthr_mb_, nthr_oc_b_, nthr_ic_b_); + + /* step 1: find the best thread distribution with lowest memory cost */ + const int nthr_mb_max = nstl::min(nthr, j.mb * j.od); + for (int nthr_mb = 1; nthr_mb <= nthr_mb_max; ++nthr_mb) { + const int nthr_par = nthr / nthr_mb; + const int nthr_oc_b_max = nstl::min(nthr_par, j.nb_oc); + for (int nthr_oc_b = 1; nthr_oc_b <= nthr_oc_b_max; ++nthr_oc_b) { + int nthr_ic_b = nstl::min(nthr_par / nthr_oc_b, j.nb_ic); + + int mem_cost = calc_mem_cost(nthr_mb, nthr_oc_b, nthr_ic_b); + if (mem_cost <= best_mem_cost) { + best_mem_cost = mem_cost; + nthr_mb_ = nthr_mb; + nthr_oc_b_ = nthr_oc_b; + nthr_ic_b_ = nthr_ic_b; + } + } + + if (!mkldnn_thr_syncable()) { assert(nthr_mb == 1); break; } + } + + if (!mayiuse(avx512_mic)) { + auto calc_comp_cost = [=](int nthr_mb, int nthr_oc_b, int nthr_ic_b) { + return 1 + * div_up(j.mb, nthr_mb) + * div_up(j.ngroups, nthr_g_) + * div_up(j.nb_oc, nthr_oc_b) + * div_up(j.nb_ic, nthr_ic_b); + }; + + /* step 2: search for a thread distribution with lower compute cost. + * the constrains: + * - memory cost cannot exceed 110% of the best found in the step 1 + * - unless compute cost is 133% lower than the current best case + * note: both constants were found empirically */ + int best_comp_cost = calc_comp_cost(nthr_mb_, nthr_oc_b_, nthr_ic_b_); + for (int nthr_mb = 1; nthr_mb <= nthr_mb_max; ++nthr_mb) { + const int nthr_par = nthr / nthr_mb; + const int nthr_oc_b_max = nstl::min(nthr_par, j.nb_oc); + for (int nthr_oc_b = 1; nthr_oc_b <= nthr_oc_b_max; ++nthr_oc_b) { + int nthr_ic_b = nstl::min(nthr_par / nthr_oc_b, j.nb_ic); + int mem_cost = calc_mem_cost(nthr_mb, nthr_oc_b, nthr_ic_b); + int comp_cost = calc_comp_cost(nthr_mb, nthr_oc_b, nthr_ic_b); + + const bool opt1 = comp_cost <= best_comp_cost + && mem_cost < 1.1 * best_mem_cost; + const bool opt2 = 4 * comp_cost <= 3 * best_comp_cost; + + if (opt1 || opt2) { + best_comp_cost = comp_cost; + nthr_mb_ = nthr_mb; + nthr_oc_b_ = nthr_oc_b; + nthr_ic_b_ = nthr_ic_b; + } + } + + if (!mkldnn_thr_syncable()) { assert(nthr_mb == 1); break; } + } + } + + if (nthr_mb_ > max_threads/2 && nthr_mb_ < max_threads) + nthr_mb_ = nstl::min(j.mb * j.od, max_threads); + nthr_ = nthr_mb_ * nthr_g_ * nthr_oc_b_ * nthr_ic_b_; + + assert(nthr_ <= max_threads); + assert(IMPLICATION(!mkldnn_thr_syncable(), nthr_mb_ == 1)); +} + +template struct _jit_avx512_common_conv_fwd_kernel; +template struct _jit_avx512_common_conv_fwd_kernel; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.hpp new file mode 100644 index 000000000000..f76770797ae8 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.hpp @@ -0,0 +1,423 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_COMMON_CONV_KERNEL_F32_HPP +#define JIT_AVX512_COMMON_CONV_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct _jit_avx512_common_conv_fwd_kernel : public jit_generator { + + _jit_avx512_common_conv_fwd_kernel(jit_conv_conf_t ajcp, + const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32( + this, jcp.eltwise); + + generate(); + jit_ker_ = (void (*)(jit_conv_call_s *))getCode(); + } + + ~_jit_avx512_common_conv_fwd_kernel() { + delete eltwise_injector_; + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(_jit_avx512_common_conv_fwd_kernel) + + jit_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker_)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + enum { + typesize = sizeof(float), + ker_reg_base_idx = 28, + }; + + reg64_t param = abi_param1; + reg64_t reg_inp = r8; + reg64_t reg_ker = r9; + reg64_t reg_out = r10; + + reg64_t reg_inp_prf = r11; + reg64_t reg_ker_prf = r12; + reg64_t reg_out_prf = r13; + reg64_t reg_owb = r12; + + reg64_t aux_reg_inp = r14; + reg64_t aux_reg_ker = r15; + + reg64_t aux_reg_inp_prf = rsi; + reg64_t aux_reg_ker_prf = rdx; + + reg64_t reg_channel = rsi; + reg64_t reg_bias = rdx; + + reg64_t aux_reg_ker_d = r9; + reg64_t aux_reg_inp_d = rbx; + reg64_t aux_reg_inp_d_prf = r13; + reg64_t aux_reg_ker_d_prf = abi_not_param1; + reg64_t reg_ki = r10; + + reg64_t reg_kj = rax; + reg64_t reg_relu_ns = rax; + reg64_t reg_oi = rbx; + reg64_t reg_kh = abi_not_param1; + + reg64_t reg_tmp = rbp; + + reg64_t reg_ic_loop = rdx; + reg64_t reg_inp_loop = rsi; + + reg64_t reg_init_flag = r13; + reg64_t reg_bias_ptr = param; + + reg64_t aux_reg_ic = r12; + reg64_t reg_binp = rax; + reg64_t reg_bout = r11; + reg64_t aux1_reg_inp = rbx; + reg64_t aux_reg_out = abi_not_param1; + + reg64_t reg_long_offt = r11; + reg64_t reg_out_long_offt = r14; + + inline Vmm vmm_ker(int i_ic) { + assert(i_ic < 4); + return Vmm(ker_reg_base_idx + i_ic); + } + + inline Vmm vmm_out(int i_ur, int i_oc) { + int idx = i_ur + i_oc * jcp.ur_w; + assert(idx < ker_reg_base_idx); + return Vmm(idx); + } + + inline Vmm vmm_inp(int i_ic, int nb_x_blocking) { + int idx = i_ic + nb_x_blocking * jcp.ur_w; + assert(idx < 31); + return Vmm(idx); + } + + Xbyak::Reg64 imm_addr64 = r15; + Vmm vmm_wei = Vmm(31); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + inline void prepare_output(int ur_w); + inline void store_output(int ur_w); + inline void compute_loop_fma(int ur_w, int pad_l, int pad_r); + inline void compute_loop_fma_core(int ur_w, int pad_l, int pad_r); + inline void compute_loop_4fma(int ur_w, int pad_l, int pad_r); + inline void compute_loop_4fma_1st(int ur_w, int pad_l, int pad_r); + inline void compute_loop(int ur_w, int pad_l, int pad_r); + + void generate(); + + inline size_t get_output_offset(int oi, int n_oc_block) { + return (size_t)jcp.typesize_out * ((size_t)n_oc_block * jcp.oh + * jcp.ow * jcp.od + oi) * jcp.oc_block; + } + + inline size_t get_input_offset(int ki, int ic, int oi, int pad_l) { + size_t iw_str = !jcp.is_1stconv ? jcp.ic_block : 1; + size_t ic_str = !jcp.is_1stconv ? 1 : (size_t)jcp.iw * jcp.ih * jcp.id; + return (size_t)jcp.typesize_in * ((size_t)(ki * (jcp.dilate_w + 1) + + oi * jcp.stride_w - pad_l) * iw_str + ic * ic_str); + } + + inline int get_kernel_offset(int ki,int ic,int n_oc_block,int ker_number) { + return jcp.typesize_in * jcp.oc_block + * (n_oc_block * jcp.nb_ic * jcp.ic_block * jcp.kh * jcp.kw * jcp.kd + + (ic + ker_number) + ki * jcp.ic_block); + } + + inline int get_ow_start(int ki, int pad_l) { + return nstl::max(0, + utils::div_up(pad_l - ki * (jcp.dilate_w + 1), jcp.stride_w)); + } + + inline int get_ow_end(int ur_w, int ki, int pad_r) { + return ur_w - nstl::max(0, utils::div_up(pad_r + - (jcp.kw - 1 - ki) + * (jcp.dilate_w + 1), + jcp.stride_w)); + } +}; + +struct jit_avx512_common_conv_fwd_kernel { + + jit_avx512_common_conv_fwd_kernel(jit_conv_conf_t ajcp, + const primitive_attr_t &attr) : + jit_ker(nullptr), + zmm_kernel_(nullptr), + xmm_kernel_(nullptr) { + int ch_block = ajcp.is_depthwise ? ajcp.ch_block : ajcp.oc_block; + switch (ch_block) { + case 16: + zmm_kernel_ = + new _jit_avx512_common_conv_fwd_kernel( + ajcp, attr); + jit_ker = zmm_kernel_->jit_ker_; + return; + case 4: + xmm_kernel_ = + new _jit_avx512_common_conv_fwd_kernel( + ajcp, attr); + jit_ker = xmm_kernel_->jit_ker_; + return; + default: + assert(!"invalid channel blocking"); + } + } + + ~jit_avx512_common_conv_fwd_kernel() { + delete xmm_kernel_; + delete zmm_kernel_; + } + + enum { + typesize = sizeof(float) + }; + + static bool post_ops_ok(jit_conv_conf_t &jcp, + const primitive_attr_t &attr); + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, + memory_desc_t &src_pd, + memory_desc_t &weights_pd, + memory_desc_t &dst_pd, + memory_desc_t &bias_pd, + const primitive_attr_t &attr, + int nthreads); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + void(*jit_ker)(jit_conv_call_s *); + _jit_avx512_common_conv_fwd_kernel *zmm_kernel_; + _jit_avx512_common_conv_fwd_kernel *xmm_kernel_; +}; + +struct jit_avx512_common_conv_bwd_data_kernel_f32: public jit_generator { + + jit_avx512_common_conv_bwd_data_kernel_f32(jit_conv_conf_t ajcp): jcp(ajcp) + { + generate(); + jit_ker = (void (*)(jit_conv_call_s *))getCode(); + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_common_conv_bwd_data_kernel_f32) + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + enum { + typesize = sizeof(float), + ker_reg_base_idx = 28, + }; + + reg64_t param = abi_param1; + reg64_t reg_dst = r8; + reg64_t reg_ker = r9; + reg64_t reg_src = r10; + + reg64_t reg_dst_prf = r11; + reg64_t reg_ker_prf = r12; + reg64_t reg_src_prf = r13; + + reg64_t aux_reg_dst = r14; + reg64_t aux_reg_ker = r15; + + reg64_t aux_reg_dst_prf = rsi; + reg64_t aux_reg_ker_prf = rdx; + + reg64_t aux_reg_dst_d_prf = r13; + reg64_t aux_reg_dst_d = rbx; + reg64_t aux_reg_ker_d_prf = abi_not_param1; + reg64_t aux_reg_ker_d = r9; + reg64_t reg_ki = r10; + + reg64_t reg_kj = rax; + reg64_t reg_oi = rbx; + reg64_t reg_kh = abi_not_param1; + + reg64_t reg_channel = rsi; + + reg64_t reg_tmp = rbp; + reg64_t reg_long_offt = r14; + + inline Xbyak::Zmm zmm_ker(int i_ic) { + assert(i_ic < 4); + return Xbyak::Zmm(ker_reg_base_idx + i_ic); + } + inline Xbyak::Zmm zmm_inp(int i_ic, int nb_x_blocking) { + int idx = i_ic + nb_x_blocking * jcp.ur_w; + assert(idx < 31); + return Xbyak::Zmm(idx); + } + inline Xbyak::Zmm zmm_out(int i_ur, int i_oc) { + int idx = i_ur + i_oc * jcp.ur_w; + assert(idx < ker_reg_base_idx); + return Xbyak::Zmm(idx); + } + + Xbyak::Zmm zmm_wei = Xbyak::Zmm(31); + + inline void prepare_output(int ur_w); + inline void store_output(int ur_w); + inline void compute_loop_4fma(int ur_w, int l_overflow, int r_overflow); + inline void compute_loop_fma(int ur_w, int l_overflow, int r_overflow); + inline void compute_loop_fma_core(int ur_w, int l_overflow, int r_overflow); + inline void compute_loop(int ur_w, int l_overflow, int r_overflow); + void generate(); + + inline int get_iw_start(int ki, int l_overflow) + { + int res = (jcp.iw - 1 + jcp.r_pad) % jcp.stride_w + + l_overflow * jcp.stride_w + - (jcp.kw - 1 - ki) * (jcp.dilate_w + 1); + while (res < 0) + res += jcp.stride_w; + + return res; + } + + inline int get_iw_end(int ur_w, int ki, int r_overflow) + { + if (utils::one_of(ur_w, jcp.iw, jcp.ur_w_tail)) + ur_w += nstl::min(0, jcp.r_pad); // remove negative padding + int res = (ur_w - 1 + jcp.l_pad) % jcp.stride_w + + r_overflow * jcp.stride_w - ki * (jcp.dilate_w + 1); + while (res < 0) + res += jcp.stride_w; + + return ur_w - res; + } +}; + +struct jit_avx512_common_conv_bwd_weights_kernel_f32 : public jit_generator { + + jit_avx512_common_conv_bwd_weights_kernel_f32(jit_conv_conf_t ajcp) + : jcp(ajcp) + { + generate(); + jit_ker = (void (*)(jit_conv_call_s *))getCode(); + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_common_conv_bwd_weights_kernel_f32) + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, + memory_desc_t &src_md, + memory_desc_t &diff_weights_md, + memory_desc_t &diff_bias_md, + memory_desc_t &diff_dst_md); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + enum {typesize = sizeof(float)}; + static const int max_ur_w; + + reg64_t param = abi_param1; + reg64_t reg_input = rax; + reg64_t reg_kernel = rdx; + reg64_t reg_output = rsi; + reg64_t b_ic = abi_not_param1; + reg64_t kj = r8; + reg64_t reg_kh = r9; + reg64_t reg_ur_w_trips = r10; + reg64_t reg_oj = r15; + reg64_t reg_ih_count = rbx; + reg64_t reg_tmp = r14; + reg64_t reg_long_offt = r14; + + reg64_t ki = r11; + reg64_t reg_kd_count = r12; + reg64_t reg_oi = r12; + reg64_t reg_d_index = r13; + reg64_t reg_input_d = r15; + reg64_t reg_output_d = rbx; + reg64_t aux_reg_input = r12; + reg64_t aux_reg_kernel = r13; + reg64_t reg_bias = rbx; + + inline void bias_kernel(); + inline void maybe_zero_kernel(); + inline void compute_oh_step_unroll_ow_icblock(int ic_block_step, + int max_ur_w); + inline void od_step_comeback_pointers(); + inline void oh_step_comeback_pointers(); + inline void compute_oh_step_unroll_ow(int ic_block_step, int max_ur_w); + inline void compute_ic_block_step(int ur_w, + int pad_l, int pad_r, int ic_block_step, + int input_offset, int kernel_offset, int output_offset, + bool input_wraparound = false); + inline void compute_ic_block_step_fma(int ur_w, + int pad_l, int pad_r, int ic_block_step, + int input_offset, int kernel_offset, int output_offset, + bool input_wraparound); + inline void compute_ic_block_step_4fma(int ur_w, + int pad_l, int pad_r, int ic_block_step, + int input_offset, int kernel_offset, int output_offset, + bool input_wraparound); + inline void compute_oh_step_common(int ic_block_step, int max_ur_w); + inline void compute_oh_step_disp(); + inline void compute_oh_loop_common(); + inline void compute_d_loop_common(); + + inline bool compute_full_spat_loop(); + inline bool flat_4ops_compute(); + + inline void compute_loop(); + + void generate(); + + static void balance(const jit_conv_conf_t &j, int &nthr, int &nthr_mb, + int &nthr_g, int &nthr_oc_b, int &nthr_ic_b); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp new file mode 100644 index 000000000000..1bdcd0d6a8fb --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp @@ -0,0 +1,1163 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "cpu_memory.hpp" + +#include + +#include "jit_avx512_common_conv_winograd_kernel_f32.hpp" + +#ifndef KERNEL_SIZE_THRESHOLD +#define KERNEL_SIZE_THRESHOLD 16 +#endif + +#define MIN_REQUIRED_DIMN_REG_BLOCK 14 + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace { + +using namespace mkldnn::impl::utils; + +unsigned int L1_cache_size = get_cache_size(1, true); +unsigned int L2_cache_size = get_cache_size(2, true); +unsigned int LLC_data_size = get_cache_size(3, false); + +// the test funtion takes jcp, the candidate and the current best. +// it returns true if the new candidate is better +int get_divisor_satisfying_cond(jit_conv_winograd_conf_t &jcp, int number, + int default_best, bool (*test)(jit_conv_winograd_conf_t &, int, int)) +{ + int best_divisor = default_best; + auto test_num + = [&best_divisor, test](jit_conv_winograd_conf_t &jcp, int num) { + if (test(jcp, num, best_divisor)) { + best_divisor = num; + } + }; + + for (int divisor = 1; divisor <= ::sqrt(number); divisor++) { + if (number % divisor == 0) { + test_num(jcp, divisor); + test_num(jcp, number / divisor); + } + } + + return best_divisor; +} + +namespace { +bool is_winograd_faster_than_direct(const jit_conv_winograd_conf_t &jcp) { + if (jcp.ver == ver_4fma) + return jcp.mb >= 32; + else + return jcp.mb >= 16; +} +} + +/* assumes 512 bits registers */ +/* TODO: add support for strides */ +/* TODO: handle the prefetch distance automatically */ +typedef enum cache_t_ { L1, L2, L3 } cache_t; + +template +struct prefetcher_t { + prefetcher_t(jit_generator *generator, Xbyak::Reg64 reg_base_addr, + cache_t cache_type, size_t block_size, /* in number of elements*/ + int nb_instructions_in_block, int fma_ipc) + : cg_(generator) + , reg_base_addr_(reg_base_addr) + , cache_type_(cache_type) + , cache_block_size_(block_size) + { + nb_cache_lines_to_prefetch_ = cache_block_size_ / (64 / sizeof(data_t)); + prefetch_spread_ + = div_up(nb_instructions_in_block, nb_cache_lines_to_prefetch_); + prefetch_blk_ + = div_up(nb_cache_lines_to_prefetch_, nb_instructions_in_block); + + /* assumption: when fetch in Li, data is already in L(i+1) */ + int cache_latency; + switch (cache_type_) { + case L1: cache_latency = 14; break; + case L2: + case L3: + default: cache_latency = 250; break; + } + + prefetch_distance_ = div_up(cache_latency, nb_cache_lines_to_prefetch_); + } + + void prefetch(int instruction_number) + { + if (instruction_number % prefetch_spread_ == 0) { + for (int i = 0; (i < prefetch_blk_) + && (prefetches_issued_ < nb_cache_lines_to_prefetch_); + i++, prefetches_issued_++) { + prefetch_inst_(cg_->EVEX_compress_addr( + reg_base_addr_, (cache_block_size_ * prefetch_distance_) + * sizeof(data_t) + + (prefetches_issued_ * 64))); + } + } + } + +private: + void prefetch_inst_(const Xbyak::Address &addr) + { + switch (cache_type_) { + case L1: cg_->prefetcht0(addr); break; + case L2: cg_->prefetcht1(addr); break; + case L3: cg_->prefetcht2(addr); break; + default: + break; // TODO: raise an exception or put an assert + } + } + + jit_generator *cg_; + Xbyak::Reg64 reg_base_addr_; + cache_t cache_type_; + int cache_block_size_ = 0; + int nb_cache_lines_to_prefetch_ = 0; + int prefetches_issued_ = 0; + int prefetch_spread_ = 0; + int prefetch_blk_ = 0; + int prefetch_distance_ = 0; +}; + +// utilities to support kernel parameter selection +bool check_cond1(int dimN_reg_block, int dimK_block, int dimK_reg_block, + int dimM_block, int dimM_simd_block, float C) +{ + float lhs = (dimM_block * dimN_reg_block * dimM_simd_block + + dimM_block * dimK_block * dimK_reg_block + * dimM_simd_block + + dimK_block * dimN_reg_block * dimK_reg_block) + * (float)sizeof(float); + float rhs = C * L1_cache_size; + return (lhs < rhs); +} + +bool check_cond1_bis(int dimN_reg_block, int dimK_block, int dimK_reg_block, + int dimM_block, int dimM_simd_block, float C) +{ + float lhs = (dimM_block * dimK_block * dimK_reg_block * dimM_simd_block + + dimK_block * dimN_reg_block * dimK_reg_block) + * (float)sizeof(float); + float rhs = C * L1_cache_size; + return (lhs < rhs); +} + +bool check_cond2(int nb_dimN_reg_block, int dimN_reg_block, int dimK_nb_block, + int dimK_block, int dimK_reg_block, int dimM_block, int dimM_simd_block, + float C) +{ + float lhs = (nb_dimN_reg_block * dimM_block * dimN_reg_block * dimM_simd_block + + dimK_nb_block * dimM_block * dimK_block * dimK_reg_block + * dimM_simd_block + + nb_dimN_reg_block * dimK_nb_block * dimK_block + * dimN_reg_block * dimK_reg_block) + * (float)sizeof(float); + float rhs = C * L2_cache_size; + return (lhs < rhs); +} +} + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +void _jit_avx512_common_conv_winograd_data_kernel_f32::gemm_loop_generate( + bool is_beta_zero) +{ + // const int dimK_simd_block = jcp.dimK_reg_block; + + // for (int dimM_block =0; dimM_block < jcp.dimM_block; dimM_block++) + // for (int dimK_block = 0; dimK_block < jcp.dimK_block; dimK_block++) + // for (int dimK_reg_block= 0; dimK_reg_block < jcp.dimK_reg_block; + // dimK_reg_block++) + // for (int tile =0; tile < jcp.dimN_reg_block; tile++) + // C[dimM_block][tile] += + // A[dimM_block][dimK_block][dimK_reg_block] * + // broadcast(B[dimK_block][tile][dimK_reg_block]); + // 1) We do register blocking on A[dimM_block][dimK_block][dimK_reg_block], + // so we load it before the loop on tile + // 2) the loop on tile must be fully unrolled. Don't know about the one on + // dimK_reg_block. I think it should be + + auto inner_loops = [=]() { + Label dimM_block_loop, dimK_block_loop; + const int inc_dimK_reg_block = jcp.ver == ver_4fma ? 4 : 1; + const int fma_ipc = jcp.ver == ver_4fma ? 1 : 2; + + prefetcher_t L1_pf(this, reg_srcB, L1, + jcp.dimN_reg_block * jcp.dimK_reg_block, + jcp.dimK_reg_block * jcp.dimN_reg_block / inc_dimK_reg_block, + fma_ipc); + prefetcher_t L2_pf(this, reg_srcB, L2, + jcp.dimN_reg_block * jcp.dimK_reg_block, + jcp.dimK_reg_block * jcp.dimN_reg_block / inc_dimK_reg_block, + fma_ipc); + + if (jcp.dimM_block > 1) { + mov(reg_dimM_block_loop_cnt, jcp.dimM_block); + L(dimM_block_loop); + } + { + // First, we zero the accumulators if first nb_ic iteration, + // otherwise we load them + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + Zmm zmm(jcp.zmm_start + tile); + if (is_beta_zero) + vpxord(zmm, zmm, zmm); + else + vmovups(zmm, zword[reg_dstC + 64 * tile]); + } + + if (jcp.dimK_block > 1) { + mov(reg_dimK_block_loop_cnt, jcp.dimK_block); + L(dimK_block_loop); + } + { + auto load_A = [=](int reg_idx, int offset) { + for (int i = 0; i < inc_dimK_reg_block; i++) + vmovups(Zmm(reg_idx + i), + zword[reg_srcA + 64 * (offset + i)]); + }; + + // Used when doing double buffering + int next = 0; + if (jcp.double_buffering) { + load_A(next, 0); + } + for (int dimK_reg_block = 0; + dimK_reg_block < jcp.dimK_reg_block; + dimK_reg_block += inc_dimK_reg_block) { + int current; + /* Loading the next vector from A */ + current = next; + if (jcp.double_buffering) { + next = (dimK_reg_block + inc_dimK_reg_block) + % (2 * inc_dimK_reg_block); + load_A(next, dimK_reg_block + inc_dimK_reg_block); + } else { + next = 0; + load_A(next, dimK_reg_block); + } + /* Performing the fmas */ + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + Zmm zmm(jcp.zmm_start + tile); + if (jcp.ver != ver_avx512_core) + L1_pf.prefetch( + dimK_reg_block * jcp.dimN_reg_block + tile); + if (jcp.ver == ver_4fma) + v4fmaddps(zmm, Zmm(current), + EVEX_compress_addr(reg_srcB, + 64 * tile + dimK_reg_block * 4)); + else + vfmadd231ps(zmm, Zmm(current), + EVEX_compress_addr(reg_srcB, + 64 * tile + dimK_reg_block * 4, + true)); + if (jcp.ver != ver_avx512_core) + L2_pf.prefetch( + dimK_reg_block * jcp.dimN_reg_block + tile); + } + } + + add(reg_srcA, jcp.dimK_reg_block * 64); + add(reg_srcB, jcp.dimN_reg_block * 64); + if (jcp.dimK_block > 1) { + sub(reg_dimK_block_loop_cnt, 1); + jnz(dimK_block_loop); + } + } + + + auto store_output = [=](bool output_is_aligned) { + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + Zmm zmm(jcp.zmm_start + tile); + if (output_is_aligned + && jcp.dimK_nb_block == 1 + && (jcp.dimN * jcp.dimM * alpha * alpha + * sizeof(float) > 2 * LLC_data_size)) + vmovntps(zword[reg_dstC + 64 * tile], zmm); + else + vmovups(zword[reg_dstC + 64 * tile], zmm); + } + }; + + Label unaligned_store, end_store; + test(reg_dstC, cpu_isa_traits::vlen - 1); + jnz(unaligned_store, T_NEAR); + store_output(true); + jmp(end_store, T_NEAR); + L(unaligned_store); { + store_output(false); + } + L(end_store); + + if (jcp.dimM_block > 1) { + sub(reg_srcB, jcp.dimK_block * jcp.dimN_reg_block * 64); + add(reg_dstC, jcp.dimN_reg_block * 64); + sub(reg_dimM_block_loop_cnt, 1); + jnz(dimM_block_loop); + } + } + }; + + /* Preamble */ + preamble(); + + /* kernel */ + inner_loops(); + + /* Postamble */ + postamble(); + ret(); +} + +status_t _jit_avx512_common_conv_winograd_data_kernel_f32::init_conf_common( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d) +{ + + if (mayiuse(avx512_core)) + return status::unimplemented; + else if (!mayiuse(avx512_common)) + return status::unimplemented; + else if (mayiuse(avx512_mic_4ops)) + jcp.ver = ver_4fma; + else + jcp.ver = ver_fma; + + jcp.nthr = mkldnn_get_max_threads(); + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = dst_d.dims()[2]; + jcp.ow = dst_d.dims()[3]; + jcp.kh = weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + jcp.r_pad = nstl::max( + 0, (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad); + jcp.b_pad = nstl::max( + 0, (jcp.oh - 1) * jcp.stride_h + jcp.kh - jcp.ih - jcp.t_pad); + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + jcp.ohp = jcp.oh; + jcp.owp = jcp.ow; + + bool ok_to_pad_channels = jcp.ngroups == 1; + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + if (!IMPLICATION(cd.alg_kind == alg_kind::convolution_auto, + is_winograd_faster_than_direct(jcp))) + return status::unimplemented; + + // Checking conditions not supported by these kernels + if (jcp.ngroups != 1) + return status::unimplemented; + if ((jcp.kh != 3) || (jcp.kw != 3)) + return status::unimplemented; + if ((jcp.dilate_h != 0) || (jcp.dilate_w != 0)) + return status::unimplemented; + if ((jcp.stride_h != 1) || (jcp.stride_w != 1)) + return status::unimplemented; + if ((jcp.ic % simd_w) != 0 || (jcp.oc % simd_w) != 0) + return status::unimplemented; + + format_tag_t dat_tag = nChw16c; + format_tag_t wei_tag = with_groups ? gOIhw16i16o : OIhw16i16o; + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + if (jcp.src_tag != dat_tag) return status::unimplemented; + if (jcp.wei_tag != wei_tag) return status::unimplemented; + if (jcp.dst_tag != dat_tag) return status::unimplemented; + + bool layout_consistency = true + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= dst_d.padded_dims()[1] + && jcp.ic <= weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= weights_d.padded_dims()[with_groups + 0]; + if (!layout_consistency) return status::unimplemented; + + return status::success; +} + + +status_t set_wsched_DATA_W_S_G_D_avx512_common(jit_conv_winograd_conf_t &jcp) { + + auto test_cond_dimN_reg_block = [](jit_conv_winograd_conf_t &jcp, + int dimN_reg_block, int current_best) { + return (dimN_reg_block >= MIN_REQUIRED_DIMN_REG_BLOCK) + && (dimN_reg_block < jcp.nb_reg) + && (dimN_reg_block < current_best); + }; + jcp.dimN_reg_block = get_divisor_satisfying_cond( + jcp, jcp.dimN, jcp.dimN, test_cond_dimN_reg_block); + + if (jcp.dimN_reg_block >= jcp.nb_reg) { + auto test_cond_dimN_reg_block = [](jit_conv_winograd_conf_t &jcp, + int dimN_reg_block, int current_best) { + return (dimN_reg_block < jcp.nb_reg) + && (dimN_reg_block > current_best); + }; + + jcp.dimN_reg_block = get_divisor_satisfying_cond( + jcp, jcp.dimN, 1, test_cond_dimN_reg_block); + } + + //********************* Choosing dimK_block **********************// + auto test_cond1_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond1(jcp.dimN_reg_block, dimK_block, jcp.dimK_reg_block, + 1, jcp.dimM_simd_block, .75f) + && (dimK_block > current_best); + }; + + auto test_cond1_bis_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond1_bis(jcp.dimN_reg_block, dimK_block, + jcp.dimK_reg_block, 1, jcp.dimM_simd_block, .9f) + && (dimK_block > current_best); + }; + + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_reg_block, 1, test_cond1_bis_dimK_block); + // If we are not able to use streams, we fall back to condition [1] + if (jcp.dimK_block < jcp.dimK / jcp.dimK_reg_block) + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_reg_block, 1, test_cond1_dimK_block); + jcp.dimK_nb_block = (jcp.dimK / jcp.dimK_reg_block) / jcp.dimK_block; + + //********************* Choosing dimM_block **********************// + jcp.dimM_simd_block = 16; + /*XXX: Why C=0.5 here but C=0.75 for dimK_block?*/ + auto test_cond1_dimM_block = []( + jit_conv_winograd_conf_t &jcp, int dimM_block, int current_best) { + return check_cond1(jcp.dimN_reg_block, jcp.dimK_block, + jcp.dimK_reg_block, dimM_block, jcp.dimM_simd_block, .5f) + && (dimM_block > current_best); + }; + + auto test_cond1_bis_dimM_block = []( + jit_conv_winograd_conf_t &jcp, int dimM_block, int current_best) { + return check_cond1_bis(jcp.dimN_reg_block, jcp.dimK_block, + jcp.dimK_reg_block, dimM_block, jcp.dimM_simd_block, .3f) + && (dimM_block > current_best); + }; + + if (jcp.dimK_block < jcp.dimK / jcp.dimK_reg_block) + jcp.dimM_block = get_divisor_satisfying_cond( + jcp, jcp.dimM / jcp.dimM_simd_block, 1, test_cond1_dimM_block); + else + jcp.dimM_block = get_divisor_satisfying_cond(jcp, + jcp.dimM / jcp.dimM_simd_block, 1, test_cond1_bis_dimM_block); + jcp.dimM_nb_block = (jcp.dimM / jcp.dimM_simd_block) / jcp.dimM_block; + + //******************* Choosing dimN_block *******************// + auto test_cond2_dimN_block = []( + jit_conv_winograd_conf_t &jcp, int dimN_block, int current_best) { + return check_cond2(dimN_block, jcp.dimN_reg_block, jcp.dimK_nb_block, + jcp.dimK_block, jcp.dimK_reg_block, jcp.dimM_block, + jcp.dimM_simd_block, .5f) + && (dimN_block > current_best); + }; + + jcp.dimN_block = get_divisor_satisfying_cond( + jcp, jcp.dimN / jcp.dimN_reg_block, 1, test_cond2_dimN_block); + jcp.dimN_nb_block = jcp.dimN / (jcp.dimN_reg_block * jcp.dimN_block); + jcp.sched_policy = WSCHED_DATA_W_S_G_D; + return status::success; +} + +status_t _jit_avx512_common_conv_winograd_data_kernel_f32::init_conf_kernel( + jit_conv_winograd_conf_t &jcp, int dimM, int dimN, int dimK) +{ + jcp.dimK_reg_block = 16; + jcp.dimM_simd_block = 16; + + // TODO: replace double buffering with nuple buffering to maximize register + // usage. + // the choice of the number of buffers will then come after choosing + // dimN_reg_block + jcp.double_buffering = true; + if (jcp.double_buffering) + jcp.zmm_start = 2 * ((jcp.ver == ver_4fma) ? 4 : 2); + else + jcp.zmm_start = 1; + jcp.nb_reg = 32 - jcp.zmm_start; + + jcp.dimN = dimN; + jcp.dimK = dimK; + jcp.dimM = dimM; + + jcp.sched_policy = WSCHED_INVALID; + set_wsched_DATA_W_S_G_D_avx512_common(jcp); + + assert(jcp.sched_policy == WSCHED_DATA_W_S_G_D); + return status::success; +} + +bool jit_avx512_common_conv_winograd_fwd_kernel_f32::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_relu = [&](int idx) { return p.entry_[idx].is_relu(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_relu(0) || is_sum(0); // relu or sum + case 2: return (is_sum(0) && is_relu(1)) || + (is_relu(0) && is_sum(1)); // sum->relu or relu->sum + case 3: return is_relu(0) && is_sum(1) && is_relu(2); // relu->sum->relu + default: return false; + } + + return false; +} + +status_t jit_avx512_common_conv_winograd_fwd_kernel_f32::init_conf( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, const primitive_attr_t &attr) { + status_t st = init_conf_common(jcp, cd, src_d, weights_d, dst_d); + + if (st != status::success) + return st; + + // Winograd specific initialization + jcp.itiles = (jcp.ow + tile_size - 1) / tile_size; + jcp.jtiles = (jcp.oh + tile_size - 1) / tile_size; + jcp.ntiles = jcp.mb * jcp.itiles * jcp.jtiles; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + const int eltwise_ind = p.find(primitive_kind::eltwise, 0, 1); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) jcp.eltwise = p.entry_[eltwise_ind].eltwise; + jcp.with_sum = p.find(primitive_kind::sum, 0) != -1; + + status_t res = init_conf_kernel(jcp, jcp.oc, jcp.ntiles, jcp.ic); + jcp.ic_simd_block = jcp.dimK_reg_block; + jcp.ic_block = jcp.dimK_block; + jcp.nb_ic = jcp.dimK_nb_block; + jcp.oc_simd_block = jcp.dimM_simd_block; + jcp.oc_block = jcp.dimM_block; + jcp.nb_oc = jcp.dimM_nb_block; + jcp.tile_block_ur = jcp.dimN_reg_block; + jcp.nb_tile_block_ur = jcp.dimN_block; + jcp.tile_block = jcp.dimN_nb_block; + jcp.tile_4fma_padding = 0; // only relevant for backward weights + + return res; +} + +status_t jit_avx512_common_conv_winograd_bwd_data_kernel_f32::init_conf( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d) +{ + status_t st = init_conf_common(jcp, cd, diff_src_d, weights_d, diff_dst_d); + + if (st != status::success) + return st; + + jcp.itiles = (jcp.iw + tile_size - 1) / tile_size; + jcp.jtiles = (jcp.ih + tile_size - 1) / tile_size; + jcp.ntiles = jcp.mb * jcp.itiles * jcp.jtiles; + + status_t res = init_conf_kernel(jcp, jcp.ic, jcp.ntiles, jcp.oc); + jcp.oc_simd_block = jcp.dimK_reg_block; + jcp.oc_block = jcp.dimK_block; + jcp.nb_oc = jcp.dimK_nb_block; + jcp.ic_simd_block = jcp.dimM_simd_block; + jcp.ic_block = jcp.dimM_block; + jcp.nb_ic = jcp.dimM_nb_block; + jcp.tile_block_ur = jcp.dimN_reg_block; + jcp.nb_tile_block_ur = jcp.dimN_block; + jcp.tile_block = jcp.dimN_nb_block; + jcp.tile_4fma_padding = 0; // only relevant for backward weights + + return res; +} + +void jit_avx512_common_conv_winograd_bwd_weights_kernel_f32::transpose_ker_generate() +{ + auto load_B = [=](int reg_idx, int offset) { + for (int i = 0; i < 4; i++) { + vmovups(Zmm(reg_idx + i), zword[reg_origB + (offset + i) * jcp.dimN_reg_block * sizeof(float)]); + } + }; + + preamble(); + int curr = 0; + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + int origB_offset = (j * alpha + i) * jcp.dimK_4fma; + size_t transB_offset = (size_t)(j * alpha + i) * jcp.dimK_nb_block * + jcp.dimN_block * jcp.dimK_block * jcp.dimK_reg_block * + jcp.dimK_4fma * jcp.dimN_reg_block * sizeof(float); + mov(reg_transB_idx, transB_offset); + for (int tb = 0; tb < jcp.dimK_4fma; tb+=4) { + /*double buffering to hide load latencies*/ + int next = (curr + 4) % 8; + if (i == 0 && tb == 0) { + load_B(0, origB_offset); + } + if (tb + 4 < (jcp.dimK_4fma -1)) { + load_B(next, origB_offset + 4); + } else if (i < alpha - 1) { + load_B(next, origB_offset + jcp.dimK_4fma); + } + + vunpcklps(Zmm(8), Zmm(curr), Zmm(curr + 1)); + vunpcklps(Zmm(9), Zmm(curr + 2), Zmm(curr + 3)); + vunpckhps(Zmm(curr), Zmm(curr), Zmm(curr + 1)); + vunpckhps(Zmm(curr + 1), Zmm(curr + 2), Zmm(curr + 3)); + + vunpcklpd(Zmm(curr + 2), Zmm(8), Zmm(9)); + vunpckhpd(Zmm(curr + 3), Zmm(8), Zmm(9)); + + vunpcklpd(Zmm(8), Zmm(curr), Zmm(curr + 1)); + vunpckhpd(Zmm(9), Zmm(curr), Zmm(curr + 1)); + + vmovntps(zword[reg_transB + reg_transB_idx + + sizeof(float) * tb * jcp.dimN_reg_block], + Zmm(curr+2)); + vmovntps(zword[reg_transB + reg_transB_idx + + sizeof(float) * (tb + 1) * jcp.dimN_reg_block], + Zmm(curr+3)); + vmovntps(zword[reg_transB + reg_transB_idx + + sizeof(float) * (tb + 2) * jcp.dimN_reg_block], + Zmm(8)); + vmovntps(zword[reg_transB + reg_transB_idx + + sizeof(float) * (tb + 3) * jcp.dimN_reg_block], + Zmm(9)); + curr = next; + + } + } + } + postamble(); + ret(); +} +void jit_avx512_common_conv_winograd_bwd_weights_kernel_f32::gemm_loop_generate( + bool is_first_tile) +{ + // for (int ofm2 = 0; ofm2 < jcp.oc_block; ofm2++) + // for (int ifm2 = 0; ifm2 < jcp.ic_block; ifm2++) + // for (int nb_tile_block_ur = 0; nb_tile_block_ur < + // jcp.nb_tile_block_ur; nb_tile_block_ur++) + // for (int tile_block_ur = 0; tile_block_ur < + // jcp.tile_block_ur; tile_block_ur++) + // for (int ifm3 = 0; ifm3 < jcp.ic_reg_block; ++ifm3) + // U[ofm2][ifm2][ofm3][ifm3][0:oc_simd_block] += + // M[ofm2][ofm3][nb_tile_block_ur][tile_block_ur][0:oc_simd_block] + // * + // broadcast(V[ifm2][nb_tile_block_ur][ifm3][tile_block_ur]) + auto inner_loops = [=]() { + int inc_fma = jcp.ver == ver_4fma ? 4 : 1; + const int fma_ipc = jcp.ver == ver_4fma ? 1 : 2; + prefetcher_t L1_pf(this, reg_srcB, L1, + jcp.dimK_reg_block * jcp.dimN_reg_block * jcp.dimK_4fma, + jcp.dimK_reg_block * jcp.dimN_reg_block * jcp.dimK_4fma + / inc_fma, + fma_ipc); + prefetcher_t L2_pf(this, reg_srcB, L2, + jcp.dimK_reg_block * jcp.dimN_reg_block * jcp.dimK_4fma, + jcp.dimK_reg_block * jcp.dimN_reg_block * jcp.dimK_4fma + / inc_fma, + fma_ipc); + + auto load_A = [=](int reg_idx, int offset) { + for (int i = 0; i < inc_fma; i++) { + vmovups(Zmm(reg_idx + i), + zword[reg_srcA + + sizeof(float) * jcp.dimM_simd_block * (offset + i)]); + } + }; + + Label dimM_block_loop, dimK_block_loop, dimN_block_loop; + if (jcp.dimM_block > 1) { + mov(reg_dimM_block_loop_cnt, jcp.dimM_block); + L(dimM_block_loop); + } + { /************* OC_block (M) loop ***********/ + if (jcp.dimN_block > 1) { + mov(reg_dimN_block_loop_cnt, jcp.dimN_block); + L(dimN_block_loop); + } + { /*************** IC_block (N) loop *********/ + for (int dimN_reg_block = 0; + dimN_reg_block < jcp.dimN_reg_block; ++dimN_reg_block) { + Zmm zmm(jcp.zmm_start + dimN_reg_block); + if (is_first_tile) + vpxord(zmm, zmm, zmm); + else + vmovups(zmm, zword[reg_dstC + + dimN_reg_block * jcp.dimM_simd_block * + sizeof(float)]); + } + + if (jcp.dimK_block > 1) { + mov(reg_dimK_block_loop_cnt, jcp.dimK_block); + L(dimK_block_loop); + } + { /************* nb_tile_ur(K) loop ********/ + int next = 0; + if (jcp.double_buffering) { + load_A(next, 0); + } + for (int dimK_reg_block = 0; + dimK_reg_block < jcp.dimK_reg_block; + dimK_reg_block++) { + int srcB_offset = dimK_reg_block * jcp.dimK_4fma + * jcp.dimN_reg_block; + for (int dimK_4fma = 0; dimK_4fma < jcp.dimK_4fma; + dimK_4fma += inc_fma) { + int current = next; + if (jcp.double_buffering) { + next = (dimK_reg_block * jcp.dimK_4fma + + dimK_4fma + inc_fma) + % (2 * inc_fma); + load_A(next, dimK_reg_block * jcp.dimK_4fma + + dimK_4fma + inc_fma); + } else { + next = 0; + load_A(next, dimK_reg_block * jcp.dimK_4fma + + dimK_4fma); + } + for (int dimN_reg_block = 0; + dimN_reg_block < jcp.dimN_reg_block; + ++dimN_reg_block) { + L1_pf.prefetch(srcB_offset / inc_fma + + dimK_4fma / inc_fma + * jcp.dimN_reg_block + + dimN_reg_block); + L2_pf.prefetch(srcB_offset / inc_fma + + dimK_4fma / inc_fma + * jcp.dimN_reg_block + + dimN_reg_block); + if (jcp.ver == ver_4fma) { + int srcB_trans_offset = (dimK_4fma / 4) * 64 + + dimK_4fma % 4; + v4fmaddps( + Zmm(jcp.zmm_start + dimN_reg_block), + Zmm(current), + EVEX_compress_addr(reg_srcB, + sizeof(float) * ( + srcB_offset + + srcB_trans_offset + + (dimN_reg_block % 4) * 16 + + (dimN_reg_block / 4) * 4))); + } else { + vfmadd231ps( + Zmm(jcp.zmm_start + dimN_reg_block), + Zmm(current), + EVEX_compress_addr(reg_srcB, + sizeof(float) * (srcB_offset + dimN_reg_block), + true)); + } + } + } + } + } + + add(reg_srcA, jcp.dimK_reg_block * jcp.dimK_4fma + * jcp.dimM_simd_block * sizeof(float)); + add(reg_srcB, jcp.dimK_reg_block * jcp.dimN_reg_block + * jcp.dimK_4fma * sizeof(float)); + if (jcp.dimK_block > 1) { + sub(reg_dimK_block_loop_cnt, 1); + jnz(dimK_block_loop); + } + + /******** Write C back to memory *******/ + for (int dimN_reg_block = 0; + dimN_reg_block < jcp.dimN_reg_block; ++dimN_reg_block) { + Zmm zmm(jcp.zmm_start + dimN_reg_block); + vmovups(zword[reg_dstC + + dimN_reg_block * jcp.dimM_simd_block * sizeof(float)], + zmm); + } + + sub(reg_srcA, jcp.dimK_block * jcp.dimK_reg_block * + jcp.dimK_4fma * jcp.dimM_simd_block * sizeof(float)); + add(reg_dstC, jcp.dimN_reg_block * jcp.dimM_simd_block + * sizeof(float)); + if (jcp.dimN_block > 1) { + sub(reg_dimN_block_loop_cnt, 1); + jnz(dimN_block_loop); + } + } + + if (jcp.dimM_block > 1) { + sub(reg_srcB, jcp.dimN_block * jcp.dimK_block + * jcp.dimK_reg_block * jcp.dimN_reg_block + * jcp.dimK_4fma * sizeof(float)); + add(reg_srcA, jcp.dimK_block * jcp.dimK_reg_block + * jcp.dimK_4fma * jcp.dimM_simd_block * sizeof(float)); + sub(reg_dimM_block_loop_cnt, 1); + jnz(dimM_block_loop); + } + } + }; + + /* Preamble */ + // register used to handle long fma encoding + preamble(); + mov(reg_srcA, reg_srcA_const); + inner_loops(); + + /* Postamble */ + postamble(); + ret(); +} + +namespace { +bool check_cond1_wu(int dimM_block, int dimM_simdw, int dimK_block, + int dimK_reg_block, int dimK_4fma, int dimN_reg_block, float C) +{ + float lhs = 1.0f * dimM_block * dimN_reg_block * dimM_simdw; + lhs += dimM_block * dimK_block * dimK_reg_block * dimK_4fma * dimM_simdw; + lhs += dimK_block * dimN_reg_block * dimK_reg_block * dimK_4fma; + lhs *= sizeof(float); + float rhs = C * L1_cache_size; + return (lhs <= rhs); +} + +bool check_cond1bis_wu(int dimM_block, int dimM_simdw, int dimK_block, + int dimK_reg_block, int dimK_4fma, int dimN_reg_block, float C) +{ + float lhs = 1.0f * dimM_block * dimK_block * dimK_reg_block * dimK_4fma + * dimM_simdw; + lhs += dimK_block * dimN_reg_block * dimK_reg_block * dimK_4fma; + lhs *= sizeof(float); + float rhs = C * L1_cache_size; + return (lhs <= rhs); +} + +bool check_cond2bis_wu(int dimM_block, int dimM_simdw, int dimK_block, + int dimK_reg_block, int dimK_4fma, int dimN_block, int dimN_reg_block, + float C) +{ + float lhs = 1.0f * dimM_block * dimM_simdw * dimK_block * dimK_reg_block + * dimK_4fma; + lhs += dimK_block * dimK_reg_block * dimK_4fma * dimN_block + * dimN_reg_block; + lhs *= sizeof(float); + float rhs = C * L2_cache_size; + return (lhs <= rhs); +} + +bool check_cond2_wu(int dimM_block, int dimM_simdw, int dimK_block, + int dimK_reg_block, int dimK_4fma, int dimN_block, int dimN_reg_block, + float C) +{ + float lhs = 1.0f * dimM_block * dimM_simdw * dimN_block * dimN_reg_block; + lhs += dimM_block * dimM_simdw * dimK_block * dimK_reg_block * dimK_4fma; + lhs += dimK_block * dimK_reg_block * dimK_4fma * dimN_block + * dimN_reg_block; + lhs *= sizeof(float); + float rhs = C * L2_cache_size; + return (lhs <= rhs); +} +} // namespace + +status_t set_wsched_WEI_S_D_G_W_avx512_common(jit_conv_winograd_conf_t &jcp) +{ + /*************** Choose dimN_reg_block (ic_simd_block) + * *******************************/ + jcp.dimN = jcp.ic; + /*Hardcoded to 16 because N = ic for bwd weights and + innermost dimension for ic is assumed 16 in src transforms. This + choice covers load latencies while maintaining simplicity of kernel + for POR topologies. FIXME in future??: Will not work for future topologies + when ic%16 != 0*/ + jcp.dimN_reg_block = jcp.ic_simd_block; + + /****************************** Choose dimK_block + * **************************/ + // No freedom for choosing dimM_simd_block because ic_simd_block + // is determined by input data format + jcp.dimM_simd_block = jcp.oc_simd_block; + + auto test_cond1bis_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond1bis_wu(1, jcp.dimM_simd_block, dimK_block, 1, + jcp.dimK_4fma, jcp.dimN_reg_block, 0.4f) + && (dimK_block > current_best); + }; + + auto test_cond1_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond1_wu(1, jcp.dimM_simd_block, dimK_block, 1, + jcp.dimK_4fma, jcp.dimN_reg_block, 0.4f) + && (dimK_block > current_best); + }; + + auto test_cond2bis_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond2bis_wu(1, jcp.dimM_simd_block, dimK_block, 1, + jcp.dimK_4fma, 1, jcp.dimN_reg_block, 0.5f) + && (dimK_block > current_best); + }; + + auto test_cond2_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond2_wu(1, jcp.dimM_simd_block, dimK_block, 1, + jcp.dimK_4fma, 1, jcp.dimN_reg_block, 0.1f) + && (dimK_block > current_best); + }; + + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_4fma, 1, test_cond2bis_dimK_block); + if (jcp.dimK_block < jcp.dimK / jcp.dimK_4fma) + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_4fma, 1, test_cond2_dimK_block); + + jcp.dimK_reg_block = get_divisor_satisfying_cond( + jcp, jcp.dimK_block, 1, test_cond1bis_dimK_block); + if (jcp.dimK_reg_block < jcp.dimK_block) { + jcp.dimK_reg_block = get_divisor_satisfying_cond( + jcp, jcp.dimK_block, 1, test_cond1_dimK_block); + } + jcp.dimK_block /= jcp.dimK_reg_block; + jcp.dimK_nb_block + = jcp.dimK / jcp.dimK_4fma / jcp.dimK_reg_block / jcp.dimK_block; + jcp.tile_block_ur = jcp.dimK_reg_block; + jcp.nb_tile_block_ur = jcp.dimK_block; + jcp.tile_block = jcp.dimK_nb_block; + + /***************************** Chose dimN block + * ****************************/ + auto test_cond2_dimN_block = []( + jit_conv_winograd_conf_t &jcp, int dimN_block, int current_best) { + return check_cond2_wu(1, jcp.dimM_simd_block, jcp.dimK_block, + jcp.dimK_reg_block, jcp.dimK_4fma, dimN_block, + jcp.dimN_reg_block, 0.5f) + && (dimN_block > current_best); + }; + + jcp.dimN_block = get_divisor_satisfying_cond( + jcp, jcp.dimN / jcp.dimN_reg_block, 1, test_cond2_dimN_block); + jcp.ic_block = jcp.dimN_block; + jcp.dimN_nb_block = jcp.dimN / jcp.dimN_reg_block / jcp.dimN_block; + jcp.nb_ic = jcp.dimN_nb_block; + + /********************************* Choose dimM block + * ************************/ + jcp.dimM = jcp.oc; + + auto test_cond1_dimM_block = []( + jit_conv_winograd_conf_t &jcp, int dimM_block, int current_best) { + return check_cond1_wu(dimM_block, jcp.dimM_simd_block, 1, + jcp.dimK_reg_block, jcp.dimK_4fma, jcp.dimN_reg_block, + 1.0f) + && (dimM_block > current_best) + && (jcp.dimM / jcp.dimM_simd_block / dimM_block) >= 2; + }; + + jcp.dimM_block = get_divisor_satisfying_cond( + jcp, jcp.dimM / jcp.dimM_simd_block, 1, test_cond1_dimM_block); + jcp.dimM_nb_block = (jcp.dimM / jcp.dimM_simd_block) / jcp.dimM_block; + + jcp.sched_policy = WSCHED_WEI_S_D_G_W; + return status::success; +} + +status_t jit_avx512_common_conv_winograd_bwd_weights_kernel_f32::init_conf( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &diff_dst_d, + const memory_desc_wrapper &diff_weights_d) +{ + jcp.nthr = mkldnn_get_max_threads(); + + const bool with_groups = diff_weights_d.ndims() == src_d.ndims() + 1; + + jcp.ngroups = with_groups ? diff_weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = diff_dst_d.dims()[2]; + jcp.ow = diff_dst_d.dims()[3]; + jcp.kh = diff_weights_d.dims()[with_groups + 2]; + jcp.kw = diff_weights_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.r_pad = nstl::max( + 0, (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad); + jcp.b_pad = nstl::max( + 0, (jcp.oh - 1) * jcp.stride_h + jcp.kh - jcp.ih - jcp.t_pad); + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + jcp.ohp = jcp.oh; + jcp.owp = jcp.ow; + jcp.with_bias = (cd.diff_bias_desc.format_kind != format_kind::undef); + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + bool ok_to_pad_channels = jcp.ngroups == 1; + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + if (mayiuse(avx512_core)) + return status::unimplemented; + if (!mayiuse(avx512_common)) + return status::unimplemented; + else if (mayiuse(avx512_mic_4ops)) + jcp.ver = ver_4fma; + else + jcp.ver = ver_fma; + + if (!IMPLICATION(cd.alg_kind == alg_kind::convolution_auto, + is_winograd_faster_than_direct(jcp))) + return status::unimplemented; + // Winograd specific initialization + jcp.itiles = (jcp.ow + tile_size - 1) / tile_size; + jcp.jtiles = (jcp.oh + tile_size - 1) / tile_size; + jcp.ntiles = jcp.mb * jcp.itiles * jcp.jtiles; + + // Winograd kernel works only for 3x3 convolution with stride 1 + if (jcp.ngroups != 1) + return status::unimplemented; + if ((jcp.kh != 3) || (jcp.kw != 3)) + return status::unimplemented; + if ((jcp.dilate_h != 0) || (jcp.dilate_w != 0)) + return status::unimplemented; + if ((jcp.stride_h != 1) || (jcp.stride_w != 1)) + return status::unimplemented; + if ((jcp.ic % simd_w) != 0 || (jcp.oc % simd_w) != 0) + return status::unimplemented; + + format_tag_t dat_tag = nChw16c; + format_tag_t wei_tag = with_groups ? gOIhw16i16o : OIhw16i16o; + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = diff_weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(dat_tag); + + if (jcp.src_tag != dat_tag) return status::unimplemented; + if (jcp.wei_tag != wei_tag) return status::unimplemented; + if (jcp.dst_tag != dat_tag) return status::unimplemented; + + bool layout_consistency = true + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= diff_dst_d.padded_dims()[1] + && jcp.ic <= diff_weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= diff_weights_d.padded_dims()[with_groups + 0]; + if (!layout_consistency) return status::unimplemented; + + /*************************** New Kernel Parameters + * *****************************/ + jcp.ic_simd_block = simd_w; + jcp.oc_simd_block = simd_w; + jcp.dimK_4fma = 1; + jcp.tile_4fma_padding = 0; + +#define MAX_4FMA_UR 8 + if (jcp.ver == ver_4fma) { + auto test_cond_4fma = [](jit_conv_winograd_conf_t &jcp, int dimK_4fma, + int current_best) { + return (dimK_4fma % 4 == 0) && (dimK_4fma <= MAX_4FMA_UR) + && (dimK_4fma > current_best); + }; + jcp.dimK_4fma = get_divisor_satisfying_cond( + jcp, jcp.itiles * jcp.jtiles, 4, test_cond_4fma); + if (jcp.dimK_4fma == 1) + jcp.dimK_4fma = 4; + if ((jcp.itiles * jcp.jtiles) % jcp.dimK_4fma != 0) + jcp.tile_4fma_padding = jcp.dimK_4fma + - ((jcp.itiles * jcp.jtiles) % jcp.dimK_4fma); + } + + jcp.tile_4fma = jcp.dimK_4fma; + /*NOTE: When (itiles * jtiles) % dimK_4fma != 0, transpose in diff_src + * transform + * will not work correctly, this is solved by applying padding.*/ + jcp.dimK = jcp.mb * (jcp.itiles * jcp.jtiles + jcp.tile_4fma_padding); + jcp.dimN = jcp.ic; + jcp.dimM = jcp.oc; + + jcp.double_buffering = true; + if (jcp.double_buffering) + jcp.zmm_start = jcp.ver == ver_4fma ? 8 : 2; + else + jcp.zmm_start = jcp.ver == ver_4fma ? 4 : 1; + jcp.nb_reg = 32 - jcp.zmm_start; + + jcp.sched_policy = WSCHED_INVALID; + status_t res = set_wsched_WEI_S_D_G_W_avx512_common(jcp); + assert(jcp.sched_policy == WSCHED_WEI_S_D_G_W); + + jcp.tile_block_ur = jcp.dimK_reg_block; + jcp.nb_tile_block_ur = jcp.dimK_block; + jcp.tile_block = jcp.dimK_nb_block; + + jcp.ic_block = jcp.dimN_block; + jcp.nb_ic = jcp.dimN_nb_block; + + jcp.oc_block = jcp.dimM_block; + jcp.nb_oc = jcp.dimM_nb_block; + + return res; + +} +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.hpp new file mode 100644 index 000000000000..6c117143f593 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.hpp @@ -0,0 +1,179 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_COMMON_CONV_WINOGRAD_KERNEL_F32_HPP +#define JIT_AVX512_COMMON_CONV_WINOGRAD_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "cpu_memory.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +//alpha determines the output tile_size +constexpr int alpha = 6; +constexpr int tile_size = 4; +//simd length used for vectorization +constexpr int simd_w = 16; + +struct _jit_avx512_common_conv_winograd_data_kernel_f32 : public jit_generator { + _jit_avx512_common_conv_winograd_data_kernel_f32( + jit_conv_winograd_conf_t ajcp) + : jcp(ajcp) + { + //******************* First iter kernel ********************// + this->gemm_loop_generate(true); + gemm_loop_ker_first_iter + = (decltype(gemm_loop_ker_first_iter)) this->getCode(); + + //************** Subsequent iterations kernel **************// + if (jcp.dimK_nb_block > 1) { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->gemm_loop_generate(false); + gemm_loop_ker = (decltype(gemm_loop_ker))addr; + } + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(_jit_avx512_common_conv_winograd_data_kernel_f32) + + static status_t init_conf_common(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d); + + static status_t init_conf_kernel( + jit_conv_winograd_conf_t &jcp, int dimM, int dimN, int dimK); + + jit_conv_winograd_conf_t jcp; + void (*gemm_loop_ker)(float *, const float *, const float *); + void (*gemm_loop_ker_first_iter)(float *, const float *, const float *); + +protected: + using reg64_t = const Xbyak::Reg64; + enum { typesize = sizeof(float) }; + + void gemm_loop_generate(bool is_beta_zero); + + /* registers used for GEMM */ + reg64_t reg_dstC = abi_param1; + reg64_t reg_srcA = abi_param2; + reg64_t reg_srcB = abi_param3; + + reg64_t reg_dimM_block_loop_cnt = r10; + reg64_t reg_dimK_block_loop_cnt = r11; +}; + +struct jit_avx512_common_conv_winograd_fwd_kernel_f32 + : _jit_avx512_common_conv_winograd_data_kernel_f32 { + using _jit_avx512_common_conv_winograd_data_kernel_f32:: + _jit_avx512_common_conv_winograd_data_kernel_f32; + + static bool post_ops_ok(jit_conv_conf_t &jcp, const primitive_attr_t &attr); + + static status_t init_conf(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, const primitive_attr_t &attr); +}; + +struct jit_avx512_common_conv_winograd_bwd_data_kernel_f32 + : public _jit_avx512_common_conv_winograd_data_kernel_f32 { + using _jit_avx512_common_conv_winograd_data_kernel_f32:: + _jit_avx512_common_conv_winograd_data_kernel_f32; + + static status_t init_conf(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d); +}; + +struct jit_avx512_common_conv_winograd_bwd_weights_kernel_f32 + : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(_jit_avx512_common_conv_winograd_bwd_weights_kernel_f32) + + jit_avx512_common_conv_winograd_bwd_weights_kernel_f32( + jit_conv_winograd_conf_t ajcp) + : jcp(ajcp) + { + + //******************* First iter kernel ********************// + { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->gemm_loop_generate(true); + gemm_loop_ker_first_iter = (decltype(gemm_loop_ker_first_iter))addr; + } + + if (jcp.tile_block > 1) { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->gemm_loop_generate(false); + gemm_loop_ker = (decltype(gemm_loop_ker))addr; + } + + if (jcp.ver == ver_4fma) { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->transpose_ker_generate(); + transpose_4fma_ker = (decltype(transpose_4fma_ker))addr; + } + } + + static status_t init_conf(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &diff_dst_d, + const memory_desc_wrapper &diff_weights_d); + + jit_conv_winograd_conf_t jcp; + void (*gemm_loop_ker)(float *, const float *, const float *); + void (*gemm_loop_ker_first_iter)(float *, const float *, const float *); + void (*transpose_4fma_ker)(float *, float *); + +private: + using reg64_t = const Xbyak::Reg64; + enum { typesize = sizeof(float) }; + + void gemm_loop_generate(bool is_first_tile); + void transpose_ker_generate(); + + reg64_t reg_origB = abi_param2; + reg64_t reg_transB = abi_param1; + + reg64_t reg_dstC = abi_param1; + reg64_t reg_srcA_const = abi_param2; + reg64_t reg_srcB = abi_param3; + + reg64_t reg_sp = rsp; + reg64_t reg_srcA = r9; + reg64_t reg_nb_ic = r10; + reg64_t reg_loop_cpt = r11; + reg64_t reg_transB_idx = r13; + + /* Registers used by new kernel */ + reg64_t reg_dimM_block_loop_cnt = r10; + reg64_t reg_dimK_block_loop_cnt = r12; + reg64_t reg_dimN_block_loop_cnt = r11; +}; +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp new file mode 100644 index 000000000000..abddc19221b5 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp @@ -0,0 +1,1526 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_common_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +using namespace nstl; + +using jit_conv_ker_t = void (*)(jit_conv_call_s *); + +#define PIPELINE(field) \ + do { \ + p.field = p.field ## _prf; \ + p.field ## _prf = field; \ + } while (0) + +inline void jit_conv_ker_pipeline(jit_conv_ker_t ker, jit_conv_call_s &p, + const void *src, const void *dst, const void *filt, const void *bias, + int channel, int kh_padding) +{ + PIPELINE(src); + PIPELINE(dst); + PIPELINE(filt); + PIPELINE(bias); + PIPELINE(channel); + PIPELINE(kh_padding); + + if (p.src) + ker(&p); +} +// The special case for the driver with ow-parallelization (FWD) +// TODO: implement it for BWD_D and BWD_W too +inline void jit_conv_ker_pipeline_ow_thr(jit_conv_ker_t ker, jit_conv_call_s &p, + const void *src, const void *dst, const void *filt, const void *bias, + int channel, int kh_padding, int owb) +{ + PIPELINE(src); + PIPELINE(dst); + PIPELINE(filt); + PIPELINE(bias); + PIPELINE(channel); + PIPELINE(kh_padding); + PIPELINE(owb); + + if (p.src) + ker(&p); +} + +inline void jit_conv_3d_ker_pipeline(jit_conv_ker_t ker, jit_conv_call_s &p, + const void *src, const void *dst, const void *filt, const void *bias, + int channel, int kh_padding, int kd_padding) +{ + PIPELINE(src); + PIPELINE(dst); + PIPELINE(filt); + PIPELINE(bias); + PIPELINE(channel); + PIPELINE(kh_padding); + PIPELINE(kd_padding); + + if (p.src) + ker(&p); +} +// The special case for the driver with ow-parallelization (FWD) +// TODO: implement it for BWD_D and BWD_W too +inline void jit_conv_3d_ker_pipeline_ow_thr(jit_conv_ker_t ker, + jit_conv_call_s &p, const void *src, const void *dst, const void *filt, + const void *bias, int channel, int kh_padding, int kd_padding, int owb) +{ + PIPELINE(src); + PIPELINE(dst); + PIPELINE(filt); + PIPELINE(bias); + PIPELINE(channel); + PIPELINE(kh_padding); + PIPELINE(kd_padding); + PIPELINE(owb); + + if (p.src) + ker(&p); +} + +void jit_conv_3d_ker_bwd_w_pipeline(jit_conv_ker_t ker, jit_conv_call_s &p, + const void *src, const void *dst, const void *filt, const void *bias, + int channel, int d_index, int d_worksize, + int kd_padding /* kd_work_size */, size_t kd_offset) { + PIPELINE(src); + PIPELINE(dst); + PIPELINE(filt); + PIPELINE(bias); + PIPELINE(channel); + PIPELINE(kd_padding); + PIPELINE(d_worksize); + PIPELINE(d_index); + PIPELINE(kd_offset); + + if (p.src) + ker(&p); +} +#define wht_blk_off(d, g, ...) \ + (pd()->with_groups() \ + ? (d).blk_off((g), __VA_ARGS__) \ + : (d).blk_off(__VA_ARGS__)) + +template +void jit_avx512_common_convolution_fwd_t::prepare_padded_bias(const dst_data_t *&bias, + const memory_tracking::grantor_t &scratchpad) const { + if (!pd()->wants_padded_bias()) return; + + auto padded_bias = scratchpad.template get( + key_conv_padded_bias); + utils::array_copy(padded_bias, bias, pd()->jcp_.oc_without_padding); + utils::array_set(padded_bias + pd()->jcp_.oc_without_padding, + (dst_data_t)0, pd()->jcp_.oc - pd()->jcp_.oc_without_padding); + bias = padded_bias; +} + +template +void jit_avx512_common_convolution_fwd_t:: +execute_forward_1d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const dst_data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + prepare_padded_bias(bias, this->scratchpad(ctx)); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = pd()->jcp_; + assert(jcp.nb_oc % jcp.nb_oc_blocking == 0); + + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int work_amount = jcp.mb * jcp.ngroups * oc_chunks * jcp.nb_ow; + + int nthr; + if (jcp.aligned_threads) + nthr = jcp.aligned_threads; + else + nthr = mkldnn_get_max_threads(); + + parallel(nthr, [&](const int ithr, const int nthr) { + int start{0}, end{0}, start_copy; + balance211(work_amount, nthr, ithr, start, end); + start_copy = start; + + auto par_conv = jit_conv_call_s(); + size_t src_c_stride = src_d.blk_off(0, 1); + size_t wht_ic_stride = wht_blk_off(weights_d, 0, 0, 1); + + for (int icb_l2 = 0 ; icb_l2 < jcp.nb_ic; icb_l2 += jcp.nb_ic_L2) { + start = start_copy; + int n{0}, g{0}, occ{0}, owb{0}; + + if (jcp.loop_order == loop_cwgn) { + int dummy{0}; + nd_iterator_init(start, occ, oc_chunks, owb, jcp.nb_ow, + g, jcp.ngroups, n, jcp.mb, dummy, 1); + } else if (jcp.loop_order == loop_gncw) { + int dummy{0}; + nd_iterator_init(start, g, jcp.ngroups, n, jcp.mb, occ, + oc_chunks, owb, jcp.nb_ow, dummy, 1); + } else { + assert(!"unsupported loop order"); + } + + while (start < end) { + int ocb = occ * jcp.nb_oc_blocking; + int g_ocb = g * jcp.nb_oc + ocb; + int g_oc = g_ocb * jcp.oc_block; + int g_icb = g * jcp.nb_ic * jcp.nonblk_group_off; + + int ow_s = owb * jcp.ow_block; + int iw_s = ow_s * jcp.stride_w; + auto bias_w = bias ? bias + g_oc : nullptr; + auto dst_w = dst + dst_d.blk_off(n, g_ocb, ow_s); + auto src_w = src + src_d.blk_off(n, g_icb + icb_l2, iw_s); + auto wht_w = weights + wht_blk_off(weights_d, g, ocb, icb_l2); + + for (int icb = icb_l2; + icb < min(jcp.nb_ic, icb_l2 + jcp.nb_ic_L2); ++icb) { + jit_conv_ker_pipeline_ow_thr(kernel_->jit_ker, par_conv, + src_w, dst_w, wht_w, bias_w, icb, 1, owb); + + src_w += src_c_stride; + wht_w += wht_ic_stride; + } + if (jcp.loop_order == loop_cwgn) { + int dummy{0}; + nd_iterator_jump(start, end, occ, oc_chunks, owb, jcp.nb_ow, + g, jcp.ngroups, n, jcp.mb, dummy, 1); + } else if (jcp.loop_order == loop_gncw) { + int dummy{0}; + nd_iterator_jump(start, end, g, jcp.ngroups, n, jcp.mb, + occ, oc_chunks, owb, jcp.nb_ow, dummy, 1); + } else { + assert(!"unsupported loop order"); + } + } + } + jit_conv_ker_pipeline_ow_thr(kernel_->jit_ker, par_conv, + src, dst, weights, bias, 0, 0, 0); + }); +} + +template +void jit_avx512_common_convolution_fwd_t:: +execute_forward_2d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const dst_data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + prepare_padded_bias(bias, this->scratchpad(ctx)); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = pd()->jcp_; + assert(jcp.nb_oc % jcp.nb_oc_blocking == 0); + + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int work_amount = jcp.mb * jcp.ngroups * oc_chunks * jcp.oh * jcp.nb_ow; + + int nthr; + if (jcp.aligned_threads) + nthr = jcp.aligned_threads; + else + nthr = mkldnn_get_max_threads(); + + parallel(nthr, [&](const int ithr, const int nthr) { + int start{0}, end{0}, start_copy; + balance211(work_amount, nthr, ithr, start, end); + start_copy = start; + + auto par_conv = jit_conv_call_s(); + size_t src_h_stride = src_d.blk_off(0, 0, 1); + size_t src_c_stride = src_d.blk_off(0, 1); + size_t dst_h_stride = dst_d.blk_off(0, 0, 1); + size_t wht_h_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + size_t wht_ic_stride = wht_blk_off(weights_d, 0, 0, 1); + + for (int icb_l2 = 0 ; icb_l2 < jcp.nb_ic; icb_l2 += jcp.nb_ic_L2) { + start = start_copy; + int n{0}, g{0}, occ{0}, oh_s{0}, owb{0}; + + if (jcp.loop_order == loop_cwgn) + nd_iterator_init(start, occ, oc_chunks, owb, jcp.nb_ow, + g, jcp.ngroups, n, jcp.mb, oh_s, jcp.oh); + else if (jcp.loop_order == loop_gncw) + nd_iterator_init(start, g, jcp.ngroups, n, jcp.mb, + occ, oc_chunks, owb, jcp.nb_ow, oh_s, jcp.oh); + else + assert(!"unsupported loop order"); + + while (start < end) { + int ocb = occ * jcp.nb_oc_blocking; + int g_ocb = g * jcp.nb_oc + ocb; + int g_oc = g_ocb * jcp.oc_block; + int g_icb = g * jcp.nb_ic * jcp.nonblk_group_off; + + int work_rem = end - start; + + int ow_s = owb * jcp.ow_block; + int iw_s = ow_s * jcp.stride_w; + int oh_e = oh_s + work_rem > jcp.oh ? jcp.oh : oh_s + work_rem; + auto bias_w = bias ? bias + g_oc : nullptr; + + for (int oh_b = oh_s; oh_b < oh_e; oh_b += jcp.h_blocking) { + int ih_b = -jcp.t_pad + oh_b * jcp.stride_h; + + auto dst_w = dst + dst_d.blk_off(n, g_ocb, oh_b, ow_s); + auto src_w + = src + src_d.blk_off(n, g_icb + icb_l2, ih_b, iw_s); + auto wht_w + = weights + wht_blk_off(weights_d, g, ocb, icb_l2); + + for (int icb = icb_l2; + icb < min(jcp.nb_ic, icb_l2 + jcp.nb_ic_L2); + ++icb) { + auto src_c = src_w; + auto dst_c = dst_w; + for (int oj = oh_b, ij = ih_b; + oj < min(oh_e, oh_b + jcp.h_blocking); + ++oj, ij += jcp.stride_h) { + int dilate_h = jcp.dilate_h + 1; + int i_t_overflow = div_up(max(0, -ij), dilate_h); + int i_b_overflow = div_up(max(0, ij - jcp.ih + + (jcp.kh - 1) * dilate_h + 1), dilate_h); + int kh_padding = nstl::max( + 0, jcp.kh - i_t_overflow - i_b_overflow); + + auto aux_src = src_c + + i_t_overflow * dilate_h * src_h_stride; + auto aux_wht = wht_w + i_t_overflow * wht_h_stride; + + jit_conv_ker_pipeline_ow_thr(kernel_->jit_ker, + par_conv, aux_src, dst_c, aux_wht, bias_w, icb, + kh_padding, owb); + + src_c += src_h_stride * jcp.stride_h; + dst_c += dst_h_stride; + } + src_w += src_c_stride; + wht_w += wht_ic_stride; + } + } + + if (jcp.loop_order == loop_cwgn) + nd_iterator_jump(start, end, occ, oc_chunks, owb, jcp.nb_ow, + g, jcp.ngroups, n, jcp.mb, oh_s, jcp.oh); + else if (jcp.loop_order == loop_gncw) + nd_iterator_jump(start, end, g, jcp.ngroups, n, jcp.mb, occ, + oc_chunks, owb, jcp.nb_ow, oh_s, jcp.oh); + else + assert(!"unsupported loop order"); + } + } + + jit_conv_ker_pipeline_ow_thr(kernel_->jit_ker, par_conv, + src, dst, weights, bias, 0, 0, 0); + }); +} + +template +void jit_avx512_common_convolution_fwd_t:: +execute_forward_3d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const dst_data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + prepare_padded_bias(bias, this->scratchpad(ctx)); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const auto &jcp = pd()->jcp_; + assert(jcp.nb_oc % jcp.nb_oc_blocking == 0); + + parallel(0, [&](const int ithr, const int nthr) { + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int start{0}, end{0}, start_copy; + int work_amount = jcp.mb * jcp.ngroups * oc_chunks * jcp.od * jcp.oh + * jcp.nb_ow; + balance211(work_amount, nthr, ithr, start, end); + start_copy = start; + + auto par_conv = jit_conv_call_s(); + size_t src_d_stride = src_d.blk_off(0, 0, 1); + size_t src_h_stride = src_d.blk_off(0, 0, 0, 1); + size_t src_c_stride = src_d.blk_off(0, 1); + size_t dst_h_stride = dst_d.blk_off(0, 0, 0, 1); + size_t wht_d_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + size_t wht_h_stride = wht_blk_off(weights_d, 0, 0, 0, 0, 1); + size_t wht_ic_stride = wht_blk_off(weights_d, 0, 0, 1); + + for (int icb_l2 = 0 ; icb_l2 < jcp.nb_ic; icb_l2 += jcp.nb_ic_L2) { + start = start_copy; + int n{0}, g{0}, occ{0}, oh_s{0}, od_s{0}, owb{0}; + + if (jcp.loop_order == loop_cwgn) + nd_iterator_init(start, + occ, oc_chunks, owb, jcp.nb_ow, g, jcp.ngroups, n, jcp.mb, + od_s, jcp.od, oh_s, jcp.oh); + else if (jcp.loop_order == loop_gncw) + nd_iterator_init(start, + g, jcp.ngroups, n, jcp.mb, occ, oc_chunks, owb, jcp.nb_ow, + od_s, jcp.od, oh_s, jcp.oh); + else + assert(!"unsupported loop order"); + + while (start < end) { + int ocb = occ * jcp.nb_oc_blocking; + int g_ocb = g * jcp.nb_oc + ocb; + int g_oc = g_ocb * jcp.oc_block; + int g_icb = g * jcp.nb_ic * jcp.nonblk_group_off; + + int work_rem = end - start; + int ih_s = -jcp.t_pad + oh_s * jcp.stride_h; + int ow_s = owb * jcp.ow_block; + int iw_s = ow_s * jcp.stride_w; + int oh_e = oh_s + work_rem > jcp.oh ? jcp.oh : oh_s + work_rem; + + int id_s = -jcp.f_pad + od_s * jcp.stride_d; + + int dilate_d = jcp.dilate_d + 1; + int d_t_overflow = div_up(max(0, -id_s), dilate_d); + int d_b_overflow = div_up( + max(0, id_s - jcp.id + (jcp.kd - 1) * dilate_d + 1), + dilate_d); + int kd_padding = nstl::max(0, + jcp.kd - d_t_overflow - d_b_overflow); + + auto bias_w = bias ? bias + bias_d.blk_off(g_oc) : 0; + auto dst_w = dst + dst_d.blk_off(n, g_ocb, od_s, oh_s, ow_s); + auto src_w = src + src_d.blk_off(n, g_icb + icb_l2, id_s, ih_s, + iw_s) + d_t_overflow * dilate_d * src_d_stride; + auto wht_w = weights + wht_blk_off(weights_d, g, ocb, icb_l2) + + d_t_overflow * wht_d_stride; + + for (int icb = icb_l2; + icb < min(jcp.nb_ic, icb_l2 + jcp.nb_ic_L2); ++icb) { + auto src_c = src_w; + auto dst_c = dst_w; + for (int oj = oh_s, ij = ih_s; + oj < oh_e; ++oj, ij += jcp.stride_h) + { + int dilate_h = jcp.dilate_h + 1; + int i_t_overflow = div_up(max(0, -ij), dilate_h); + int i_b_overflow = div_up( + max(0, ij - jcp.ih + (jcp.kh - 1) * dilate_h + + 1), + dilate_h); + int kh_padding = nstl::max(0, + jcp.kh - i_t_overflow - i_b_overflow); + jit_conv_3d_ker_pipeline_ow_thr(kernel_->jit_ker, + par_conv, + src_c + i_t_overflow * dilate_h * src_h_stride, + dst_c, wht_w + i_t_overflow * wht_h_stride, + bias_w, icb, kh_padding, kd_padding, owb); + + src_c += src_h_stride * jcp.stride_h; + dst_c += dst_h_stride; + } + src_w += src_c_stride; + wht_w += wht_ic_stride; + } + + if (jcp.loop_order == loop_cwgn) + nd_iterator_jump(start, end, + occ, oc_chunks, owb, jcp.nb_ow, g, jcp.ngroups, n, jcp.mb, + od_s, jcp.od, oh_s, jcp.oh); + else if (jcp.loop_order == loop_gncw) + nd_iterator_jump(start, end, + g, jcp.ngroups, n, jcp.mb, occ, oc_chunks, owb, jcp.nb_ow, + od_s, jcp.od, oh_s, jcp.oh); + else + assert(!"unsupported loop order"); + } + } + jit_conv_3d_ker_pipeline(kernel_->jit_ker, par_conv, + src, dst, weights, bias, 0, 0, 0); + }); +} + +template struct jit_avx512_common_convolution_fwd_t; + +template +void jit_avx512_common_convolution_bwd_data_t::execute_backward_data_1d(const exec_ctx_t &ctx) const +{ + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + + parallel(0, [&](const int ithr, const int nthr) { + int start{0}, end{0}, start_copy; + int ic_chunks = jcp.nb_ic / jcp.nb_ic_blocking; + int work_amount = jcp.ngroups * jcp.mb * ic_chunks * jcp.ih; + balance211(work_amount, nthr, ithr, start, end); + start_copy = start; + + auto par_conv = jit_conv_call_s(); + size_t diff_dst_c_stride = diff_dst_d.blk_off(0, 1); + size_t wht_oc_stride = wht_blk_off(weights_d, 0, 1); + + for (int ocb_l2 = 0; ocb_l2 < jcp.nb_oc; ocb_l2 += jcp.nb_oc_L2) { + start = start_copy; + int n{0}, g{0}, icc{0}; + if (jcp.loop_order == loop_cgn) { + int dummy{0}; + nd_iterator_init(start, icc, ic_chunks, g, jcp.ngroups, n, + jcp.mb, dummy, 1); + } else if (jcp.loop_order == loop_gnc) { + int dummy{0}; + nd_iterator_init(start, g, jcp.ngroups, n, jcp.mb, icc, + ic_chunks, dummy, 1); + } else { + assert(!"unsupported loop order"); + } + + while (start < end) { + int icb = icc * jcp.nb_ic_blocking; + int g_icb = g * jcp.nb_ic + icb; + int g_ocb = g * jcp.nb_oc; + + auto diff_src_w = diff_src + diff_src_d.blk_off(n, g_icb); + auto diff_dst_w = diff_dst + + diff_dst_d.blk_off(n, g_ocb + ocb_l2); + auto wht_w = weights + wht_blk_off(weights_d, g, ocb_l2, icb); + + for (int ocb = ocb_l2; + ocb < min(jcp.nb_oc, ocb_l2 + jcp.nb_oc_L2); ++ocb) { + jit_conv_ker_pipeline(kernel_->jit_ker, par_conv, + diff_src_w, diff_dst_w, wht_w, 0, ocb, 1); + diff_dst_w += diff_dst_c_stride; + wht_w += wht_oc_stride; + } + + if (jcp.loop_order == loop_cgn) { + int dummy{0}; + nd_iterator_jump(start, end, icc, ic_chunks, g, jcp.ngroups, + n, jcp.mb, dummy, 1); + } else if (jcp.loop_order == loop_gnc) { + int dummy{0}; + nd_iterator_jump(start, end, g, jcp.ngroups, n, jcp.mb, icc, + ic_chunks, dummy, 1); + } else { + assert(!"unsupported loop order"); + } + } + } + + jit_conv_ker_pipeline(kernel_->jit_ker, par_conv, + diff_src, diff_dst, weights, 0, 0, 1); + }); +} + +template +void jit_avx512_common_convolution_bwd_data_t::execute_backward_data_2d(const exec_ctx_t &ctx) const +{ + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + + parallel(0, [&](const int ithr, const int nthr) { + int start{0}, end{0}, start_copy; + int ic_chunks = jcp.nb_ic / jcp.nb_ic_blocking; + int work_amount = jcp.ngroups * jcp.mb * ic_chunks * jcp.ih; + balance211(work_amount, nthr, ithr, start, end); + start_copy = start; + + auto par_conv = jit_conv_call_s(); + size_t diff_src_h_stride = diff_src_d.blk_off(0, 0, 1); + size_t diff_dst_h_stride = diff_dst_d.blk_off(0, 0, 1); + size_t diff_dst_c_stride = diff_dst_d.blk_off(0, 1); + size_t wht_h_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + size_t wht_oc_stride = wht_blk_off(weights_d, 0, 1); + + bool is_fast_path = jcp.dilate_h == 0 && jcp.stride_h == 1; + + for (int ocb_l2 = 0; ocb_l2 < jcp.nb_oc; ocb_l2 += jcp.nb_oc_L2) { + start = start_copy; + int n{0}, g{0}, icc{0}, ih_s{0}; + if (jcp.loop_order == loop_cgn) + nd_iterator_init(start, + icc, ic_chunks, g, jcp.ngroups, n, jcp.mb, ih_s, jcp.ih); + else if (jcp.loop_order == loop_gnc) + nd_iterator_init(start, + g, jcp.ngroups, n, jcp.mb, icc, ic_chunks, ih_s, jcp.ih); + else + assert(!"unsupported loop order"); + + while (start < end) { + int icb = icc * jcp.nb_ic_blocking; + int g_icb = g * jcp.nb_ic + icb; + int g_ocb = g * jcp.nb_oc; + + int work_rem = end - start; + int ih_e = ih_s + work_rem > jcp.ih ? jcp.ih : ih_s + work_rem; + + auto diff_src_w = diff_src + diff_src_d.blk_off(n, g_icb); + auto diff_dst_w = diff_dst + + diff_dst_d.blk_off(n, g_ocb + ocb_l2); + auto wht_w = weights + wht_blk_off(weights_d, g, ocb_l2, icb); + + for (int ocb = ocb_l2; + ocb < min(jcp.nb_oc, ocb_l2 + jcp.nb_oc_L2); ++ocb) { + for (int ij = ih_s; ij < ih_e; ++ij) { + int oj, k_len, k_lo; + if (is_fast_path) { // dilate == 0 && stride == 1 + int i_t_overflow = max(0, jcp.kh - 1 - ij + - jcp.t_pad); + int i_b_overflow = max(0, jcp.kh - jcp.ih + ij + - jcp.b_pad); + k_len = jcp.kh - i_t_overflow - i_b_overflow; + k_lo = i_b_overflow; + oj = ij + jcp.t_pad - i_b_overflow; + } else if (jcp.dilate_h != 0) { // stride == 1 + int dilate_h = jcp.dilate_h + 1; + // Note: use div_up to account for "holes" in filter + int i_t_overflow + = div_up(max(0, (jcp.kh - 1) * dilate_h + - ij - jcp.t_pad), dilate_h); + int i_b_overflow + = div_up(max(0, (jcp.kh - 1) * dilate_h + 1 + - jcp.ih + ij - jcp.b_pad), dilate_h); + k_len = jcp.kh - i_t_overflow - i_b_overflow; + k_lo = i_b_overflow; + oj = ij + jcp.t_pad - i_b_overflow * dilate_h; + } else { // dilate == 0 + int i_t_overflow = max(0, (jcp.kh - 1 - ij + - jcp.t_pad) / jcp.stride_h); + int i_b_overflow = max(0, (jcp.kh - jcp.ih + ij + - jcp.b_pad) / jcp.stride_h); + int overflow_kh_hi = jcp.kh - 1 - abs((jcp.ih - 1 + + jcp.b_pad - ij) % jcp.stride_h); + int overflow_kh_lo = (ij + jcp.t_pad) + % jcp.stride_h; + + k_len = (overflow_kh_hi - overflow_kh_lo) + / jcp.stride_h + 1 - i_t_overflow + - i_b_overflow; + k_lo = overflow_kh_lo + i_b_overflow * jcp.stride_h; + oj = (ij + jcp.t_pad - k_lo) / jcp.stride_h; + } + assert(k_len >= 0); + + jit_conv_ker_pipeline(kernel_->jit_ker, par_conv, + diff_src_w + ij * diff_src_h_stride, + diff_dst_w + oj * diff_dst_h_stride, + wht_w + k_lo * wht_h_stride, + 0, ocb, k_len); + } + diff_dst_w += diff_dst_c_stride; + wht_w += wht_oc_stride; + } + + if (jcp.loop_order == loop_cgn) + nd_iterator_jump(start, end, + icc, ic_chunks, g, jcp.ngroups, n, jcp.mb, ih_s, jcp.ih); + else if (jcp.loop_order == loop_gnc) + nd_iterator_jump(start, end, + g, jcp.ngroups, n, jcp.mb, icc, ic_chunks, ih_s, jcp.ih); + else + assert(!"unsupported loop order"); + } + } + + jit_conv_ker_pipeline(kernel_->jit_ker, par_conv, + diff_src, diff_dst, weights, 0, 0, 1); + }); +} + +template +void jit_avx512_common_convolution_bwd_data_t::execute_backward_data_3d(const exec_ctx_t &ctx) const +{ + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + + parallel(0, [&](const int ithr, const int nthr) { + int start{0}, end{0}, start_copy; + int ic_chunks = jcp.nb_ic / jcp.nb_ic_blocking; + int work_amount = jcp.ngroups * jcp.mb * ic_chunks * jcp.id * jcp.ih; + balance211(work_amount, nthr, ithr, start, end); + start_copy = start; + + auto par_conv = jit_conv_call_s(); + size_t diff_src_h_stride = diff_src_d.blk_off(0, 0, 0, 1); + size_t diff_src_d_stride = diff_src_d.blk_off(0, 0, 1); + size_t diff_dst_h_stride = diff_dst_d.blk_off(0, 0, 0, 1); + size_t diff_dst_d_stride = diff_dst_d.blk_off(0, 0, 1); + size_t diff_dst_c_stride = diff_dst_d.blk_off(0, 1); + size_t wht_h_stride = wht_blk_off(weights_d, 0, 0, 0, 0, 1); + size_t wht_d_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + size_t wht_oc_stride = wht_blk_off(weights_d, 0, 1); + + bool is_fast_path_d = jcp.dilate_d == 0 && jcp.stride_d == 1; + bool is_fast_path_h = jcp.dilate_h == 0 && jcp.stride_h == 1; + + for (int ocb_l2 = 0; ocb_l2 < jcp.nb_oc; ocb_l2 += jcp.nb_oc_L2) { + start = start_copy; + int n{0}, g{0}, icc{0}, ih_s{0}, id_s{0}; + if (jcp.loop_order == loop_cgn) + nd_iterator_init(start, + icc, ic_chunks, g, jcp.ngroups, n, jcp.mb, id_s, jcp.id, + ih_s, jcp.ih); + else if (jcp.loop_order == loop_gnc) + nd_iterator_init(start, + g, jcp.ngroups, n, jcp.mb, icc, ic_chunks, id_s, jcp.id, + ih_s, jcp.ih); + else + assert(!"unsupported loop order"); + + while (start < end) { + int icb = icc * jcp.nb_ic_blocking; + int g_icb = g * jcp.nb_ic + icb; + int g_ocb = g * jcp.nb_oc; + + int work_rem = end - start; + int ih_e = ih_s + work_rem > jcp.ih ? jcp.ih : ih_s + work_rem; + int d_len = 0, d_lo = 0, d_oj = 0; + if (is_fast_path_d) { // dilate == 0 && stride == 1 + int d_t_overflow = max(0, jcp.kd - 1 - id_s + - jcp.f_pad); + int d_b_overflow = max(0, jcp.kd - jcp.id + id_s + - jcp.back_pad); + d_len = jcp.kd - d_t_overflow - d_b_overflow; + d_lo = d_b_overflow; + d_oj = id_s + jcp.f_pad - d_b_overflow; + } else if (jcp.dilate_d != 0) { // stride == 1 + int dilate_d = jcp.dilate_d + 1; + // Note: use div_up to account for "holes" in filter + int d_t_overflow = div_up(max(0, (jcp.kd - 1) * dilate_d + - id_s - jcp.f_pad), dilate_d); + int d_b_overflow = div_up(max(0, (jcp.kd - 1) * dilate_d + 1 + - jcp.id + id_s - jcp.back_pad), dilate_d); + d_len = jcp.kd - d_t_overflow - d_b_overflow; + d_lo = d_b_overflow; + d_oj = id_s + jcp.f_pad - d_b_overflow * dilate_d; + } else { // dilate == 0 + int d_t_overflow = max(0, (jcp.kd - 1 - id_s + - jcp.f_pad) / jcp.stride_d); + int d_b_overflow = max(0, (jcp.kd - jcp.id + id_s + - jcp.back_pad) / jcp.stride_d); + int overflow_kd_hi = jcp.kd - 1 - abs((jcp.id - 1 + + jcp.back_pad - id_s) % jcp.stride_d); + int overflow_kd_lo = (id_s + jcp.f_pad) + % jcp.stride_d; + + d_len = (overflow_kd_hi - overflow_kd_lo) + / jcp.stride_d + 1 - d_t_overflow + - d_b_overflow; + d_lo = overflow_kd_lo + d_b_overflow * jcp.stride_d; + d_oj = (id_s + jcp.f_pad - d_lo) / jcp.stride_d; + } + assert(d_len >= 0); + + auto diff_src_w = diff_src + diff_src_d.blk_off(n, g_icb) + + id_s * diff_src_d_stride; + auto diff_dst_w = diff_dst + + diff_dst_d.blk_off(n, g_ocb + ocb_l2) + + d_oj * diff_dst_d_stride; + auto wht_w = weights + wht_blk_off(weights_d, g, ocb_l2, icb) + + d_lo * wht_d_stride; + + for (int ocb = ocb_l2; + ocb < min(jcp.nb_oc, ocb_l2 + jcp.nb_oc_L2); ++ocb) { + for (int ij = ih_s; ij < ih_e; ++ij) { + int oj, k_len, k_lo; + if (is_fast_path_h) { // dilate == 0 && stride == 1 + int i_t_overflow = max(0, jcp.kh - 1 - ij + - jcp.t_pad); + int i_b_overflow = max(0, jcp.kh - jcp.ih + ij + - jcp.b_pad); + k_len = jcp.kh - i_t_overflow - i_b_overflow; + k_lo = i_b_overflow; + oj = ij + jcp.t_pad - i_b_overflow; + } else if (jcp.dilate_h != 0) { // stride == 1 + int dilate_h = jcp.dilate_h + 1; + // Note: use div_up to account for "holes" in filter + int i_t_overflow + = div_up(max(0, (jcp.kh - 1) * dilate_h + - ij - jcp.t_pad), dilate_h); + int i_b_overflow + = div_up(max(0, (jcp.kh - 1) * dilate_h + 1 + - jcp.ih + ij - jcp.b_pad), dilate_h); + k_len = jcp.kh - i_t_overflow - i_b_overflow; + k_lo = i_b_overflow; + oj = ij + jcp.t_pad - i_b_overflow * dilate_h; + } else { // dilate == 0 + int i_t_overflow = max(0, (jcp.kh - 1 - ij + - jcp.t_pad) / jcp.stride_h); + int i_b_overflow = max(0, (jcp.kh - jcp.ih + ij + - jcp.b_pad) / jcp.stride_h); + int overflow_kh_hi = jcp.kh - 1 - abs((jcp.ih - 1 + + jcp.b_pad - ij) % jcp.stride_h); + int overflow_kh_lo = (ij + jcp.t_pad) + % jcp.stride_h; + + k_len = (overflow_kh_hi - overflow_kh_lo) + / jcp.stride_h + 1 - i_t_overflow + - i_b_overflow; + k_lo = overflow_kh_lo + i_b_overflow * jcp.stride_h; + oj = (ij + jcp.t_pad - k_lo) / jcp.stride_h; + } + assert(k_len >= 0); + + jit_conv_3d_ker_pipeline(kernel_->jit_ker, par_conv, + diff_src_w + ij * diff_src_h_stride, + diff_dst_w + oj * diff_dst_h_stride, + wht_w + k_lo * wht_h_stride, + 0, ocb, k_len, d_len); + } + diff_dst_w += diff_dst_c_stride; + wht_w += wht_oc_stride; + } + + if (jcp.loop_order == loop_cgn) + nd_iterator_jump(start, end, + icc, ic_chunks, g, jcp.ngroups, n, jcp.mb, id_s, jcp.id, + ih_s, jcp.ih); + else if (jcp.loop_order == loop_gnc) + nd_iterator_jump(start, end, + g, jcp.ngroups, n, jcp.mb, icc, ic_chunks, id_s, jcp.id, + ih_s, jcp.ih); + else + assert(!"unsupported loop order"); + } + } + + jit_conv_3d_ker_pipeline(kernel_->jit_ker, par_conv, + diff_src, diff_dst, weights, 0, 0, 1, 1); + }); +} + +template struct jit_avx512_common_convolution_bwd_data_t; + +template +jit_avx512_common_convolution_bwd_weights_t:: +jit_avx512_common_convolution_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd), kernel_(nullptr) + , trans_kernel_(nullptr), acc_ker_(nullptr), reducer_bias_(nullptr) +{ + const auto &j = pd()->jcp_; + + nthr_ = j.nthr; + nthr_mb_ = j.nthr_mb; + nthr_g_ = j.nthr_g; + nthr_oc_b_ = j.nthr_oc_b; + nthr_ic_b_ = j.nthr_ic_b; + + kernel_ = new jit_avx512_common_conv_bwd_weights_kernel_f32(j); + + if (j.ver == ver_4fma) + trans_kernel_ = create_trans_src(&j); + + if (nthr_mb_ > 1) + acc_ker_ = new cpu_accumulator_1d_t(); + + reducer_bias_ = + new cpu_reducer_t(pd()->reducer_bia_conf_); +} + +template +struct jit_avx512_common_convolution_bwd_weights_t::thread_info_t { + const src_data_t *src; + const diff_dst_data_t *diff_dst; + const diff_weights_data_t *diff_weights; + diff_weights_data_t *diff_bias; + + const memory_tracking::grantor_t scratchpad; + + src_data_t *tr_src; + simple_barrier::ctx_t *tr_src_bctx; + + diff_dst_data_t *tr_diff_dst; + simple_barrier::ctx_t *tr_diff_dst_bctx; + + diff_weights_data_t *wei_bia_reduction; + simple_barrier::ctx_t *wei_bia_reduction_bctx; + + int ithr; + int ithr_ic_b, ithr_oc_b, ithr_g, ithr_mb; + int ithr_but_oc; + int ithr_but_ic; + + int img_start = 0, img_end = 0, img_work; + int g_start = 0, g_end = 0, g_work; + int oc_b_start = 0, oc_b_end = 0, oc_b_work; + int ic_b_start = 0, ic_b_end = 0, ic_b_work; + + thread_info_t(const jit_avx512_common_convolution_bwd_weights_t *self, + const exec_ctx_t &ctx, int ithr) + : scratchpad(self->scratchpad(ctx)), ithr(ithr) + { + diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + diff_weights = CTX_OUT_MEM(diff_weights_data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + diff_bias = self->pd()->wants_padded_bias() + ? scratchpad.template get( + key_conv_padded_bias) + : CTX_OUT_MEM(diff_weights_data_t *, MKLDNN_ARG_DIFF_BIAS); + + tr_src = scratchpad.template get(key_conv_tr_src); + tr_src_bctx = scratchpad.template get( + key_conv_tr_src_bctx); + + tr_diff_dst = scratchpad.template get( + key_conv_tr_diff_dst); + tr_diff_dst_bctx = scratchpad.template get( + key_conv_tr_diff_dst_bctx); + + wei_bia_reduction = scratchpad.template get( + key_conv_wei_bia_reduction); + wei_bia_reduction_bctx = scratchpad.template get( + key_conv_wei_bia_reduction_bctx); + + ithr_ic_b = ithr % self->nthr_ic_b_; + ithr_oc_b = ithr / self->nthr_ic_b_ % self->nthr_oc_b_; + ithr_g = ithr / self->nthr_ic_b_ / self->nthr_oc_b_ % self->nthr_g_; + ithr_mb = ithr / self->nthr_ic_b_ / self->nthr_oc_b_ / self->nthr_g_; + + ithr_but_oc = (ithr_mb * self->nthr_g_ + ithr_g) * self->nthr_ic_b_ + + ithr_ic_b; + + ithr_but_ic = (ithr_mb * self->nthr_g_ + ithr_g) * self->nthr_oc_b_ + + ithr_oc_b; + + const auto &jcp = self->kernel_->jcp; + + /* reduction dimension */ + balance211(jcp.mb*jcp.od, self->nthr_mb_, ithr_mb, img_start, img_end); + img_work = img_end - img_start; + + /* independent dimensions */ + balance211(jcp.ngroups, self->nthr_g_, ithr_g, g_start, g_end); + g_work = g_end - g_start; + + balance211(jcp.nb_oc, self->nthr_oc_b_, ithr_oc_b, oc_b_start, + oc_b_end); + oc_b_work = oc_b_end - oc_b_start; + + balance211(jcp.nb_ic, self->nthr_ic_b_, ithr_ic_b, ic_b_start, + ic_b_end); + ic_b_work = ic_b_end - ic_b_start; + } +}; + +template +void jit_avx512_common_convolution_bwd_weights_t::compute_diff_weights(const thread_info_t *ti) const { + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + + const auto &jcp = kernel_->jcp; + const int wei_size = jcp.ngroups * jcp.oc * jcp.ic * jcp.kh*jcp.kw*jcp.kd; + + diff_weights_data_t *diff_wei = ti->ithr_mb == 0 + ? (diff_weights_data_t*)ti->diff_weights + : ti->wei_bia_reduction + (ti->ithr_mb - 1) * wei_size; + diff_weights_data_t *diff_bia = ti->ithr_mb == 0 + ? (diff_weights_data_t*)ti->diff_bias + : ti->wei_bia_reduction + (nthr_mb_ - 1) * wei_size + + (ti->ithr_mb - 1) * jcp.ngroups * jcp.oc; + + // TODO: use memory descriptor with the same fmt as src (or use a macro :)) + auto tr_src_off = [&](int ithr_mb, int ic, int ij) { + const size_t tr_row_size = jcp.tr_iw * jcp.ic_block; + const size_t tr_chn_size = tr_row_size * jcp.ih; + const size_t tr_img_size = tr_chn_size * jcp.nb_ic * jcp.ngroups; + + return ti->ithr_mb * tr_img_size + ic * tr_chn_size + ij * tr_row_size; + }; + + auto uker_trans = [&](int img) { + const int work_amount = ti->g_work * ti->ic_b_work * jcp.ih; + + int start{0}, end{0}; + balance211(work_amount, nthr_oc_b_, ti->ithr_oc_b, start, end); + const int my_work = end - start; + + int g{0}, ic_b{0}, j{0}; + nd_iterator_init(start, g, ti->g_work, ic_b, ti->ic_b_work, j, jcp.ih); + g += ti->g_start; + ic_b += ti->ic_b_start; + + const int _ic = g * jcp.nb_ic + ic_b; + src_data_t *src1 = (src_data_t*)&ti->src[src_d.blk_off(img, _ic, j)]; + src_data_t *tr_src1 = &ti->tr_src[tr_src_off(ti->ithr_mb, _ic, j)]; + + assert(jcp.ic_block == 16); + const int src_stride = jcp.iw * jcp.ic_block; + const int tr_src_stride = jcp.tr_iw * jcp.ic_block; + + const int pf_depth = 2; + struct { src_data_t *src, *tr_src; } pf_circ_buf[pf_depth]; + + for (int iwork = 0; iwork < my_work + pf_depth - 1; iwork++) { + pf_circ_buf[iwork % pf_depth] = {src1, tr_src1}; + + if (iwork >= pf_depth - 1) { + int old_idx = (iwork - pf_depth + 1) % pf_depth; + auto ctx = jit_trans_src_t::ctx_t(); + ctx.src = pf_circ_buf[old_idx].src; + ctx.tr_src = pf_circ_buf[old_idx].tr_src; + ctx.src_prf = src1; + ctx.tr_src_prf = tr_src1; + (*trans_kernel_)(&ctx); + } + src1 += src_stride; + tr_src1 += tr_src_stride; + } +#if 0 + // reference transposition + const int l_pad = jcp.l_pad; + const int iwlp = l_pad + jcp.iw; + const int tr_iw = jcp.tr_iw; + + for (size_t iwork = start; iwork < end; iwork++) { + PRAGMA_OMP_SIMD() +# pragma unroll + for (int i = 0; i < l_pad; i++) + for (int j = 0; j < jcp.ic_block; j++) + tr_src1[j * jcp.tr_iw + i] = (src_data_t)0.0; + + PRAGMA_OMP_SIMD() +# pragma unroll + for (int i = l_pad; i < iwlp; i++) + for (int j = 0; j < jcp.ic_block; j++) + tr_src1[j * jcp.tr_iw + i] + = (src_data_t)src1[(i - l_pad) * 16 + j]; + + PRAGMA_OMP_SIMD() +# pragma unroll + for (int i = iwlp; i < tr_iw; i++) + for (int j = 0; j < jcp.ic_block; j++) + tr_src1[j * jcp.tr_iw + i] = (src_data_t)0.0; + + src1 += src_stride; + tr_src1 += tr_src_stride; + } +#endif + }; + + if (jcp.is_1stconv && jcp.ver == ver_4fma) { + /* prepare contexts */ + auto tr_ctx = jit_trans_src_t::ctx_t(); + tr_ctx.tr_src = ti->tr_src + + ti->ithr_but_oc * jcp.ih * jcp.stride_w * jcp.tr_ld; + + assert(IMPLICATION(!mkldnn_thr_syncable(), nthr_oc_b_ == 1)); + tr_ctx.nthr_oc_b = nthr_oc_b_; + int ih_start{0}, ih_end{0}; + balance211(jcp.ih, nthr_oc_b_, ti->ithr_oc_b, ih_start, ih_end); + tr_ctx.tr_src_ih_start = ih_start; + tr_ctx.tr_src_ih_end = ih_end; + tr_ctx.tr_src_bctx = ti->tr_src_bctx + ti->ithr_but_oc; + + auto p = jit_conv_call_s(); + p.src = tr_ctx.tr_src; + + /* zero diff_bias if applicable */ + if (jcp.with_bias && ti->ithr_ic_b == 0) { + assert(jcp.oc_block == 16); + for (int oc_b = ti->ic_b_start; oc_b < ti->oc_b_end; ++oc_b) { + diff_weights_data_t *db = &diff_bia[oc_b * 16]; + for (int o = 0; o < 16; ++o) + db[o] = 0; + } + } + + for (int img = ti->img_start; img < ti->img_end; ++img) { + p.flags = (img == ti->img_start) * FLAG_MB_FIRST; + + for (int g = ti->g_start; g < ti->g_end; ++g) { + for (int ic_b = ti->ic_b_start; ic_b < ti->ic_b_end; ++ic_b) { + const int _ic = g * jcp.nb_ic + ic_b; + tr_ctx.src = &ti->src[src_d.blk_off(img, _ic)]; + + (*trans_kernel_)(&tr_ctx); + + if (ic_b == 0) + p.flags |= FLAG_IC_FIRST; + else + p.flags &= ~FLAG_IC_FIRST; + + for (int oc_b = ti->oc_b_start; oc_b < ti->oc_b_end; ++oc_b) { + const int _oc = g * jcp.nb_oc + oc_b; + p.dst = &ti->diff_dst[diff_dst_d.blk_off(img, _oc)]; + + const size_t off = + wht_blk_off(diff_weights_d, g, oc_b, ic_b); + p.filt = diff_wei + off; + p.bias = diff_bia + _oc * jcp.oc_block; + + kernel_->jit_ker(&p); + } + } + } + } + } else { + for (int img = ti->img_start; img < ti->img_end; ++img) { + auto p = jit_conv_call_s(); + + if (jcp.ver == ver_4fma) { + /* tr_src[nb_ic][ih][16][~iw~] <- src[nb_ic][ih][iw][16] */ + using simple_barrier::barrier; + if (nthr_oc_b_ > 1) + barrier(&ti->tr_src_bctx[ti->ithr_but_oc], nthr_oc_b_); + uker_trans(img); + if (nthr_oc_b_ > 1) + barrier(&ti->tr_src_bctx[ti->ithr_but_oc], nthr_oc_b_); + } + + for (int g = ti->g_start; g < ti->g_end; ++g) { + for (int oc_b = ti->oc_b_start; oc_b < ti->oc_b_end; ++oc_b) { + for (int ic_b = ti->ic_b_start; ic_b < ti->ic_b_end; ++ic_b) { + const int _oc = g * jcp.nb_oc + oc_b; + const int _ic = g * jcp.nb_ic + ic_b; + + jit_conv_ker_pipeline(kernel_->jit_ker, p, + jcp.ver == ver_4fma + ? &ti->tr_src[tr_src_off(ti->ithr_mb, _ic, 0)] + : &ti->src[src_d.blk_off(img, _ic)], + &ti->diff_dst[diff_dst_d.blk_off(img, _oc)], + diff_wei + wht_blk_off(diff_weights_d, g, oc_b, ic_b), + 0, (img == ti->img_start), 0); + + } + } + } + + const int _oc = ti->g_start * jcp.nb_oc + ti->oc_b_start; + const int _ic = ti->g_start * jcp.nb_ic + ti->ic_b_start; + jit_conv_ker_pipeline(kernel_->jit_ker, p, + jcp.ver == ver_4fma + ? &ti->tr_src[tr_src_off(ti->ithr_mb, _ic, 0)] + : &ti->src[src_d.blk_off(img + 1, _ic)], + &ti->diff_dst[diff_dst_d.blk_off(img + 1, _oc)], + diff_wei + wht_blk_off( + diff_weights_d, ti->g_start, + ti->oc_b_start, ti->ic_b_start), + 0, 0, 0); + } + } +} + +template +void jit_avx512_common_convolution_bwd_weights_t::compute_diff_weights_3d(const thread_info_t *ti) const +{ + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + + const auto &jcp = kernel_->jcp; + const int wei_size + = jcp.ngroups * jcp.oc * jcp.ic * jcp.kh * jcp.kw * jcp.kd; + + diff_weights_data_t *diff_wei = ti->ithr_mb == 0 + ? (diff_weights_data_t*)ti->diff_weights + : ti->wei_bia_reduction + (ti->ithr_mb - 1) * wei_size; + diff_weights_data_t *diff_bia = ti->ithr_mb == 0 + ? (diff_weights_data_t*)ti->diff_bias + : ti->wei_bia_reduction + (nthr_mb_ - 1) * wei_size + + (ti->ithr_mb - 1) * jcp.ngroups * jcp.oc; + + const int inp_mult = jcp.is_1stconv ? 1 : jcp.ic_block; + const int input_step = jcp.ih * jcp.iw * inp_mult; + const int output_step = jcp.ow * jcp.oh * jcp.oc_block; + int img{0}, od_s{0}; + int img_start = ti->img_start, img_end = ti->img_end; + nd_iterator_init(img_start, img, jcp.mb, od_s, jcp.od); + const int img_first = img; + + while (img_start < img_end) { + auto p = jit_conv_call_s(); + + int work_rem = img_end - img_start; + const int od_e = od_s + work_rem > jcp.od ? jcp.od : od_s + work_rem; + const int id_s = od_s * jcp.stride_d; + const int ik_overlap = nstl::max(0, id_s - jcp.f_pad); + const int kd_front_pad = nstl::max(0, jcp.f_pad - id_s); + const int kd_back_pad + = nstl::max(0, id_s - jcp.f_pad - jcp.id + jcp.kd); + int kd_pad_off = nstl::min(jcp.kd - 1, kd_front_pad) * jcp.kh * jcp.kw + * jcp.ic_block * jcp.oc_block * jcp.typesize_out; + + for (int g = ti->g_start; g < ti->g_end; ++g) { + for (int oc_b = ti->oc_b_start; oc_b < ti->oc_b_end; ++oc_b) { + for (int ic_b = ti->ic_b_start; ic_b < ti->ic_b_end; ++ic_b) { + const int _oc = g * jcp.nb_oc + oc_b; + const int _ic = g * jcp.nb_ic + ic_b; + + auto src = &ti->src[src_d.blk_off(img, _ic) + + ik_overlap * input_step]; + auto dst = &ti->diff_dst[diff_dst_d.blk_off(img, _oc) + + od_s * output_step]; + + jit_conv_3d_ker_bwd_w_pipeline(kernel_->jit_ker, p, src, dst, + diff_wei + wht_blk_off(diff_weights_d, g, oc_b, ic_b), + diff_bia + _oc * 16, (img == img_first), od_s, od_e, + jcp.kd - kd_front_pad - kd_back_pad, kd_pad_off); + + if (ic_b == 0) p.flags = 0; + else p.flags = 1; + } + } + } + + const int _oc = ti->g_start * jcp.nb_oc + ti->oc_b_start; + const int _ic = ti->g_start * jcp.nb_ic + ti->ic_b_start; + jit_conv_3d_ker_bwd_w_pipeline(kernel_->jit_ker, p, + &ti->src[src_d.blk_off(img + 1, _ic)], + &ti->diff_dst[diff_dst_d.blk_off(img + 1, _oc)], + diff_wei + wht_blk_off(diff_weights_d, ti->g_start, + ti->oc_b_start, ti->ic_b_start), + diff_bia, 0, 0, 0, 0, 0); + nd_iterator_jump(img_start, img_end, img, jcp.mb, od_s, jcp.od); + } +} + +template +void jit_avx512_common_convolution_bwd_weights_t::reduce_diff_weights(const thread_info_t *ti) const { + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + + const auto &jcp = kernel_->jcp; + const int wei_size = jcp.ngroups * jcp.oc * jcp.ic * jcp.kh * jcp.kw; + const int bia_size = jcp.ngroups * jcp.oc; + const diff_weights_data_t *diff_bias_ws + = ti->wei_bia_reduction + (nthr_mb_ - 1) * wei_size; + + /* diff_weights[:] += sum(wei_reduction_[thr_mb][:]) */ + simple_barrier::barrier(ti->wei_bia_reduction_bctx, nthr_); + + const int ic_b_kh_work = ti->ic_b_work * jcp.kh; + const int work = ti->g_work * ti->oc_b_work * ic_b_kh_work; + + int start{0}, end{0}; + balance211(work, nthr_mb_, ti->ithr_mb, start, end); + if (start == end) return; + + for (int thr_mb = 1; thr_mb < nthr_mb_; ++thr_mb) { + int w = start; + int sub_g_start{0}, sub_oc_b_start{0}, sub_ic_b_kh_start{0}; + nd_iterator_init(w, sub_g_start, ti->g_work, sub_oc_b_start, + ti->oc_b_work, sub_ic_b_kh_start, ic_b_kh_work); + while (w < end) { + const int g = ti->g_start + sub_g_start; + const int oc_b = ti->oc_b_start + sub_oc_b_start; + const int ic_b = ti->ic_b_start + sub_ic_b_kh_start / jcp.kh; + const int kh = sub_ic_b_kh_start % jcp.kh; + + const int acc_size + = nstl::min(end - w, ic_b_kh_work - sub_ic_b_kh_start) + * jcp.kw * jcp.ic_block * jcp.oc_block; + + const size_t off + = wht_blk_off(diff_weights_d, g, oc_b, ic_b, kh); + + diff_weights_data_t *d + = (diff_weights_data_t *)ti->diff_weights + off; + diff_weights_data_t *s + = ti->wei_bia_reduction + (thr_mb - 1) * wei_size + off; + + acc_ker_->accumulate(d, s, acc_size); + + nd_iterator_jump(w, end, sub_g_start, ti->g_work, sub_oc_b_start, + ti->oc_b_work, sub_ic_b_kh_start, ic_b_kh_work); + } + + if (jcp.with_bias && jcp.is_1stconv && jcp.ver == ver_4fma) { + if (ti->ithr == 0) + acc_ker_->accumulate((diff_weights_data_t *)ti->diff_bias, + diff_bias_ws, bia_size); + diff_bias_ws += bia_size; + } + } +} + +template +void jit_avx512_common_convolution_bwd_weights_t::reduce_diff_weights_3d(const thread_info_t *ti) const { + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + + const auto &jcp = kernel_->jcp; + const int wei_size = jcp.ngroups * jcp.oc * jcp.ic * jcp.kh * jcp.kw + * jcp.kd; + + /* diff_weights[:] += sum(wei_reduction_[thr_mb][:]) */ + simple_barrier::barrier(ti->wei_bia_reduction_bctx, nthr_); + + const int ic_b_kh_work = ti->ic_b_work * jcp.kd; + const int work = ti->g_work * ti->oc_b_work * ic_b_kh_work; + + int start{0}, end{0}; + balance211(work, nthr_mb_, ti->ithr_mb, start, end); + if (start == end) return; + + for (int thr_mb = 1; thr_mb < nthr_mb_; ++thr_mb) { + int w = start; + int sub_g_start{0}, sub_oc_b_start{0}, sub_ic_b_kh_start{0}; + nd_iterator_init(w, sub_g_start, ti->g_work, sub_oc_b_start, + ti->oc_b_work, sub_ic_b_kh_start, ic_b_kh_work); + while (w < end) { + const int g = ti->g_start + sub_g_start; + const int oc_b = ti->oc_b_start + sub_oc_b_start; + const int ic_b = ti->ic_b_start + sub_ic_b_kh_start / jcp.kd; + const int kd = sub_ic_b_kh_start % jcp.kd; + + const int acc_size + = nstl::min(end - w, ic_b_kh_work - sub_ic_b_kh_start) + * jcp.kw * jcp.ic_block * jcp.oc_block * jcp.kh; + + const size_t off + = wht_blk_off(diff_weights_d, g, oc_b, ic_b, kd); + diff_weights_data_t *d + = (diff_weights_data_t *)ti->diff_weights + off; + diff_weights_data_t *s + = ti->wei_bia_reduction + (thr_mb - 1) * wei_size + off; + acc_ker_->accumulate(d, s, acc_size); + + nd_iterator_jump(w, end, sub_g_start, ti->g_work, sub_oc_b_start, + ti->oc_b_work, sub_ic_b_kh_start, ic_b_kh_work); + } + } +} + +template +void jit_avx512_common_convolution_bwd_weights_t::compute_diff_bias(const thread_info_t *ti) const { + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + + auto rb = this->reducer_bias_; + assert(nthr_ == rb->balancer().nthr_); + + const auto reducer_bia_scratchpad = memory_tracking::grantor_t( + ti->scratchpad, prefix_reducer_bia); + + const auto &jcp = kernel_->jcp; + + if (jcp.with_bias && jcp.is_1stconv && jcp.ver == ver_4fma) return; + + const int b_job_start = rb->balancer().ithr_job_off(ti->ithr); + const int b_njobs = rb->balancer().ithr_njobs(ti->ithr); + + if (b_njobs == 0) return; + + /* reduction dimension */ + int img_start{0}, img_end{0}; + balance211(jcp.mb, rb->balancer().nthr_per_group_, + rb->balancer().id_in_group(ti->ithr), img_start, img_end); + + /* jobs */ + int g_start{0}, ocb_start{0}; + nd_iterator_init(b_job_start, g_start, jcp.ngroups, ocb_start, jcp.nb_oc); + for (int img = img_start; img < img_end; ++img) { + int g = g_start, ocb = ocb_start; + for (int b_job_loc = 0; b_job_loc < b_njobs; ++b_job_loc) { + const size_t _oc = g * jcp.nb_oc + ocb; + + const diff_dst_data_t *d_dst + = &ti->diff_dst[diff_dst_d.blk_off(img, _oc)]; + diff_weights_data_t *d_bias = rb->get_local_ptr(ti->ithr, + ti->diff_bias, reducer_bia_scratchpad) + + b_job_loc * rb->balancer().job_size_; + + if (img == img_start) + for (int o = 0; o < 16; ++o) + d_bias[o] = 0; + for (int hw = 0; hw < jcp.oh * jcp.ow * jcp.od; ++hw) { + PRAGMA_OMP_SIMD() + for (int o = 0; o < 16; ++o) + d_bias[o] += d_dst[o]; + d_dst += 16; + } + + nd_iterator_step(g, jcp.ngroups, ocb, jcp.nb_oc); + } + } + + rb->reduce(ti->ithr, ti->diff_bias, reducer_bia_scratchpad); +} + +template +void jit_avx512_common_convolution_bwd_weights_t::compute_diff_bias_3d(const thread_info_t *ti) const { + + const auto &jcp = kernel_->jcp; + + const size_t wei_size = (size_t)jcp.ngroups * jcp.oc * jcp.ic * jcp.kh + * jcp.kw * jcp.kd; + const int bia_size = jcp.ngroups * jcp.oc; + const diff_weights_data_t *diff_bias_ws + = ti->wei_bia_reduction + (size_t)(nthr_mb_ - 1) * wei_size; + + if (nthr_mb_ > 1) mkldnn_thr_barrier(); + + if (ti->ithr == 0) + { + for (int thr_mb = 1; thr_mb < nthr_mb_; ++thr_mb) { + acc_ker_->accumulate(ti->diff_bias, diff_bias_ws, bia_size); + diff_bias_ws += bia_size; + } + } +} + +template +void jit_avx512_common_convolution_bwd_weights_t::prepare_scratchpad_data(const exec_ctx_t &ctx) const +{ + const auto &j = pd()->jcp_; + auto scratchpad = this->scratchpad(ctx); + + if (j.ver == ver_4fma) { + if (!j.is_1stconv) { + // XXX: See the comment about tr_iw and guarding elements in + // jit_avx512_common_conv_bwd_weights_kernel_f32::init_conf() + const int max_nthr = j.nthr_mb * j.ngroups * j.nb_ic; + const int min_tr_src_size_per_thr = j.ih * j.ic_block * j.tr_iw; + + auto tr_src = scratchpad.template get(key_conv_tr_src); + /* to avoid NaNs in computations we zero tail num_guard_elems for + * each possible thread group */ + + for (int ithr = 1; ithr <= max_nthr; ++ithr) { + src_data_t *ts = &tr_src[ithr * min_tr_src_size_per_thr]; + for (int i = 0; i < j.tr_src_num_guard_elems; ++i) + ts[i] = 0; + } + } + + if (j.nthr_oc_b > 1) { + const int tr_src_bctx_size = j.nthr / j.nthr_oc_b; + auto tr_src_bctx = scratchpad.template get( + key_conv_tr_src_bctx); + for (int i = 0; i < tr_src_bctx_size; ++i) + simple_barrier::ctx_init(&tr_src_bctx[i]); + } + } + + if (nthr_mb_ > 1) { + simple_barrier::ctx_init(scratchpad.template get( + key_conv_wei_bia_reduction_bctx)); + } + + const auto reducer_bia_scratchpad = memory_tracking::grantor_t(scratchpad, + prefix_reducer_bia); + auto rb = this->reducer_bias_; + rb->init(reducer_bia_scratchpad); +} + +template +void jit_avx512_common_convolution_bwd_weights_t::execute_backward_weights(const exec_ctx_t &ctx) const { + prepare_scratchpad_data(ctx); + + parallel(nthr_, [&](const int ithr, const int nthr) { + assert(nthr_ == nthr); + + thread_info_t thread_info(this, ctx, ithr); + + if (utils::one_of(pd()->ndims(), 3, 4)) { + compute_diff_weights(&thread_info); + if (nthr_mb_ > 1) reduce_diff_weights(&thread_info); + if (pd()->with_bias()) compute_diff_bias(&thread_info); + } else if (pd()->ndims() == 5) { + compute_diff_weights_3d(&thread_info); + if (nthr_mb_ > 1) reduce_diff_weights_3d(&thread_info); + if (pd()->with_bias()) compute_diff_bias_3d(&thread_info); + } else { + assert(false); + } + }); + + /* TODO: put that into compute_diff_bias() */ + if (pd()->wants_padded_bias()) { + auto diff_bias = scratchpad(ctx).template get( + key_conv_padded_bias); + auto diff_bias_in = CTX_OUT_MEM(diff_weights_data_t *, MKLDNN_ARG_DIFF_BIAS); + for (int oc = 0; oc < pd()->jcp_.oc_without_padding; ++oc) + diff_bias_in[oc] = diff_bias[oc]; + } +} + +template struct jit_avx512_common_convolution_bwd_weights_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.hpp new file mode 100644 index 000000000000..3341c3ebe08d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution.hpp @@ -0,0 +1,302 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_COMMON_CONVOLUTION_HPP +#define CPU_JIT_AVX512_COMMON_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_barrier.hpp" +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" +#include "cpu_reducer.hpp" + +#include "jit_transpose_src_utils.hpp" +#include "jit_avx512_common_conv_kernel.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_avx512_common_convolution_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx512_common, ""), + jit_avx512_common_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, wei_type, dst_type, dst_type, + data_type::undef) + && !has_zero_dim_memory(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_common_conv_fwd_kernel::init_conf( + jcp_, *desc(), src_md_, weights_md_, dst_md_, bias_md_, + *attr(), mkldnn_get_max_threads()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_common_conv_fwd_kernel::init_scratchpad(scratchpad, + jcp_); + + return status; + } + + jit_conv_conf_t jcp_; + }; + + jit_avx512_common_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + { + kernel_ = new jit_avx512_common_conv_fwd_kernel(pd()->jcp_, + *pd()->attr()); + } + ~jit_avx512_common_convolution_fwd_t() { delete kernel_; } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if (pd()->ndims() == 3) + execute_forward_1d(ctx); + else if (pd()->ndims() == 4) + execute_forward_2d(ctx); + else if (pd()->ndims() == 5) + execute_forward_3d(ctx); + else + assert(false); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); + + return status::success; + } + +private: + void prepare_padded_bias(const dst_data_t *&bias, + const memory_tracking::grantor_t &scratchpad) const; + void execute_forward_1d(const exec_ctx_t &ctx) const; + void execute_forward_2d(const exec_ctx_t &ctx) const; + void execute_forward_3d(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_common_conv_fwd_kernel *kernel_; +}; + +template +struct jit_avx512_common_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx512_common, ""), + jit_avx512_common_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(diff_src_type, wei_type, + data_type::undef, diff_dst_type, data_type::undef) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = + jit_avx512_common_conv_bwd_data_kernel_f32::init_conf(jcp_, + *desc(), *diff_src_md(), *weights_md(), *diff_dst_md()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_common_conv_bwd_data_kernel_f32::init_scratchpad( + scratchpad, jcp_); + + return status::success; + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw16c, nChw16c, nCdhw16c); + auto wei_tag = utils::pick(2 * ndims() - 6 + with_groups(), + OIw16o16i, gOIw16o16i, OIhw16o16i, gOIhw16o16i, + OIdhw16o16i, gOIdhw16o16i); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + jit_avx512_common_convolution_bwd_data_t(const pd_t *apd) + : cpu_primitive_t(apd) + { kernel_ = new jit_avx512_common_conv_bwd_data_kernel_f32(pd()->jcp_); } + ~jit_avx512_common_convolution_bwd_data_t() { delete kernel_; }; + + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type diff_src_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if (pd()->ndims() == 3) + execute_backward_data_1d(ctx); + else if (pd()->ndims() == 4) + execute_backward_data_2d(ctx); + else if (pd()->ndims() == 5) + execute_backward_data_3d(ctx); + else + assert(false); + return status::success; + } + +private: + void execute_backward_data_1d(const exec_ctx_t &ctx) const; + void execute_backward_data_2d(const exec_ctx_t &ctx) const; + void execute_backward_data_3d(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_common_conv_bwd_data_kernel_f32 *kernel_; +}; + +template +struct jit_avx512_common_convolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx512_common, ""), + jit_avx512_common_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, diff_weights_type, + diff_weights_type, diff_dst_type, data_type::undef) + && !has_zero_dim_memory(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_common_conv_bwd_weights_kernel_f32:: + init_conf(jcp_, *desc(), src_md_, diff_weights_md_, + diff_bias_md_, diff_dst_md_); + if (status != status::success) return status; + + init_balancers(); + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_common_conv_bwd_weights_kernel_f32::init_scratchpad( + scratchpad, jcp_); + + auto reducer_bia_scratchpad = memory_tracking::registrar_t( + scratchpad, memory_tracking::names::prefix_reducer_bia); + reducer_bia_conf_.init_scratchpad(reducer_bia_scratchpad); + + return status; + } + + jit_conv_conf_t jcp_; + typename cpu_reducer_t::conf_t reducer_bia_conf_; + + private: + void init_balancers() { + const size_t max_buffer_size = jcp_.nthr * 3 * 5 * 5 * 16 * 16; + if (with_bias()) { + reducer_bia_conf_.init(reduce_balancer_t(jcp_.nthr, + jcp_.oc_block, jcp_.ngroups * jcp_.nb_oc, jcp_.mb, + max_buffer_size)); + } + } + }; + + jit_avx512_common_convolution_bwd_weights_t(const pd_t *apd); + ~jit_avx512_common_convolution_bwd_weights_t() { + delete kernel_; + if (trans_kernel_) + delete trans_kernel_; + if (acc_ker_) + delete acc_ker_; + delete reducer_bias_; + } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type diff_weights_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + void prepare_scratchpad_data(const exec_ctx_t &ctx) const; + struct thread_info_t; + void compute_diff_weights(const thread_info_t *) const; + void compute_diff_weights_3d(const thread_info_t *) const; + void reduce_diff_weights(const thread_info_t *) const; + void reduce_diff_weights_3d(const thread_info_t *) const; + void compute_diff_bias(const thread_info_t *) const; + void compute_diff_bias_3d(const thread_info_t *) const; + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + int nthr_, nthr_mb_, nthr_g_, nthr_oc_b_, nthr_ic_b_; + + jit_avx512_common_conv_bwd_weights_kernel_f32 *kernel_; + jit_trans_src_t *trans_kernel_; + cpu_accumulator_1d_t *acc_ker_; + cpu_reducer_t *reducer_bias_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp new file mode 100644 index 000000000000..62247c0264db --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp @@ -0,0 +1,1215 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifdef __INTEL_COMPILER +#include +#endif + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_common_convolution_winograd.hpp" + +#ifndef _MSC_VER +#define pragma_unroll _Pragma("unroll") +#else +#define pragma_unroll +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace memory_tracking::names; + +namespace { + +unsigned int LLC_cache_size = get_cache_size(3, false); + +void inline load_ps(float *dest, const float *src_mem) { +#ifdef __INTEL_COMPILER + __m512 *Iv512 = (__m512 *)dest; + Iv512[0] = _mm512_load_ps(src_mem); +#else + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) dest[v] = src_mem[v]; +#endif +} + +void inline store_output(float *dest, const float *data, bool streamout) { +#ifdef __INTEL_COMPILER + if (streamout) + _mm512_stream_ps(dest, *((__m512 *)data)); + else + _mm512_store_ps(dest, *((__m512 *)data)); +#else + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) + dest[v] = data[v]; +#endif +} + +void inline accum_output( + float *dest, float *data, bool streamout, bool with_relu_postsum) { +#ifdef __INTEL_COMPILER + __m512 _data = _mm512_loadu_ps(data); + __m512 _dest = _mm512_loadu_ps(dest); + _data = _mm512_add_ps(_data, _dest); + if (with_relu_postsum) + _data = _mm512_max_ps(_data, _mm512_setzero_ps()); + if (streamout) + _mm512_stream_ps(dest, _data); + else + _mm512_store_ps(dest, _data); +#else + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) + data[v] += dest[v]; + + if (with_relu_postsum) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) + if (data[v] < 0.f) + data[v] = 0.f; + } + + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) + dest[v] = data[v]; +#endif +} +} + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; + +void trans_W_4x4_3x3(float Fw_[6][6][16][16], float F[3][3][16][16]) { + float Fw[6][16]; + float T[6][3][16]; + float t0[16]; + float t1[16]; + float t2[16]; + + for (int j = 0; j < 16; j++) { +#pragma unroll + for (int i = 0; i < 3; i++) { + PRAGMA_OMP_SIMD() + for (int k = 0; k < 16; k++) { + t0[k] = 0.26890756302521f * F[2][i][j][k]; + t1[k] = -t0[k] - 0.688403361344538f * F[0][i][j][k]; + t2[k] = t0[k] + 0.119514472455649f * F[0][i][j][k]; + + T[0][i][k] = 1.13777777777778f * F[0][i][j][k]; + T[1][i][k] = t1[k] - 0.430252100840336f * F[1][i][j][k]; + T[2][i][k] = t1[k] + 0.430252100840336f * F[1][i][j][k]; + T[3][i][k] = t2[k] + 0.179271708683473f * F[1][i][j][k]; + T[4][i][k] = t2[k] - 0.179271708683473f * F[1][i][j][k]; + T[5][i][k] = F[2][i][j][k]; + } + } +#pragma unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int k = 0; k < 16; k++) { + t0[k] = 0.26890756302521f * T[i][2][k]; + t1[k] = -t0[k] - 0.688403361344538f * T[i][0][k]; + t2[k] = t0[k] + 0.119514472455649f * T[i][0][k]; + + Fw[0][k] = 1.13777777777778f * T[i][0][k]; + Fw[1][k] = t1[k] - 0.430252100840336f * T[i][1][k]; + Fw[2][k] = t1[k] + 0.430252100840336f * T[i][1][k]; + Fw[3][k] = t2[k] + 0.179271708683473f * T[i][1][k]; + Fw[4][k] = t2[k] - 0.179271708683473f * T[i][1][k]; + Fw[5][k] = T[i][2][k]; +#pragma unroll + for (int l = 0; l < 6; l++) { + Fw_[i][l][j][k] = Fw[l][k]; + } + } + } + } +} + +void trans_O_4x4_3x3(float Mw[6][6][16], float O[4][4][16]) { + float T[4][6][16]; + float t0[16]; + float t1[16]; + float t2[16]; + float t3[16]; + +#pragma unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = Mw[1][i][v] + Mw[2][i][v]; + t1[v] = Mw[3][i][v] + Mw[4][i][v]; + t2[v] = Mw[1][i][v] - Mw[2][i][v]; + t3[v] = Mw[3][i][v] - Mw[4][i][v]; + + T[0][i][v] = t0[v] + t1[v] + Mw[0][i][v]; + T[1][i][v] = t2[v] * 0.625f + t3[v] * 1.5f; + T[2][i][v] = t0[v] * 0.390625f + t1[v] * 2.25f; + T[3][i][v] = t2[v] * 0.244140625f + t3[v] * 3.375f + Mw[5][i][v]; + } + } +#pragma unroll + for (int i = 0; i < 4; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = T[i][1][v] + T[i][2][v]; + t1[v] = T[i][3][v] + T[i][4][v]; + t2[v] = T[i][1][v] - T[i][2][v]; + t3[v] = T[i][3][v] - T[i][4][v]; + + O[i][0][v] = t0[v] + t1[v] + T[i][0][v]; + O[i][1][v] = t2[v] * 0.625f + t3[v] * 1.5f; + O[i][2][v] = t0[v] * 0.390625f + t1[v] * 2.25f; + O[i][3][v] = t2[v] * 0.244140625f + t3[v] * 3.375f + T[i][5][v]; + } + } +} + + +void trans_W_3x3_4x4(float Fw[6][6][16], float F[4][6][16]) +{ + const float rcp3 = 1.0f / 3.0f; + const float rcp4 = 1.0f / 4.0f; + const float rcp6 = 1.0f / 6.0f; + const float rcp12 = 1.0f / 12.0f; + const float rcp24 = 1.0f / 24.0f; + float t0[16]; + float t1[16]; + float t2[16]; + float t3[16]; + float t4[16]; + float T[6][4][16]; + +pragma_unroll + for (int i = 0; i < 4; i++) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < 16; j++) { + t0[j] = F[2][i][j] * rcp6; + t1[j] = F[0][i][j] * -rcp6 - t0[j]; + t2[j] = F[0][i][j] * rcp24 + t0[j]; + t3[j] = (F[1][i][j] + F[3][i][j]) * rcp6; + t4[j] = F[1][i][j] * rcp12 + F[3][i][j] * rcp3; + + T[0][i][j] = F[0][i][j] * rcp4; + T[1][i][j] = t1[j] - t3[j]; + T[2][i][j] = t1[j] + t3[j]; + T[3][i][j] = t2[j] + t4[j]; + T[4][i][j] = t2[j] - t4[j]; + T[5][i][j] = F[3][i][j]; + } + } +pragma_unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < 16; j++) { + t0[j] = T[i][2][j] * rcp6; + t1[j] = T[i][0][j] * -rcp6 - t0[j]; + t2[j] = T[i][0][j] * rcp24 + t0[j]; + t3[j] = (T[i][1][j] + T[i][3][j]) * rcp6; + t4[j] = T[i][1][j] * rcp12 + T[i][3][j] * rcp3; + + Fw[i][0][j] = T[i][0][j] * rcp4; + Fw[i][1][j] = t1[j] - t3[j]; + Fw[i][2][j] = t1[j] + t3[j]; + Fw[i][3][j] = t2[j] + t4[j]; + Fw[i][4][j] = t2[j] - t4[j]; + Fw[i][5][j] = T[i][3][j]; + } + } +} + +void trans_O_3x3_4x4(float Mw[6][6][16][16], float M[3][3][16][16]) +{ + float T[4][6][16]; + float M_[3][16]; + float t0[16]; + float t1[16]; + float t2[16]; + + for (int j = 0; j < 16; j++) { +pragma_unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int l = 0; l < 16; l++) { + t0[l] = Mw[1][i][j][l] + Mw[2][i][j][l]; + t1[l] = Mw[3][i][j][l] + Mw[4][i][j][l]; + t2[l] = t1[l] * 4.0f + Mw[5][i][j][l]; + + T[0][i][l] = Mw[0][i][j][l] + t0[l] + t1[l]; + T[1][i][l] = (Mw[1][i][j][l] - Mw[2][i][j][l]) + + 2.0f * (Mw[3][i][j][l] - Mw[4][i][j][l]); + T[2][i][l] = t0[l] + t2[l]; + } + } +pragma_unroll + for (int i = 0; i < 3; i++) { + PRAGMA_OMP_SIMD() + for (int l = 0; l < 16; l++) { + t0[l] = T[i][1][l] + T[i][2][l]; + t1[l] = T[i][3][l] + T[i][4][l]; + t2[l] = t1[l] * 4.0f + T[i][5][l]; + + M_[0][l] = T[i][0][l] + t0[l] + t1[l]; + M_[1][l] = (T[i][1][l] - T[i][2][l]) + + 2.0f * (T[i][3][l] - T[i][4][l]); + M_[2][l] = t0[l] + t2[l]; + + for (int k = 0; k < 3; k++) { + M[i][k][j][l] = M_[k][l]; + } + } + } + } +} + +void trans_I_4x4_3x3(float Iw[6][6][16], float I[6][6][16]) +{ + float T[6][6][16]; + float t0[16]; + float t1[16]; + float t2[16]; + float t3[16]; + float t4[16]; + float t5[16]; + +pragma_unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = I[2][i][v] * -2.25f + I[4][i][v]; + t1[v] = I[1][i][v] * -2.25f + I[3][i][v]; + t2[v] = I[2][i][v] * -0.390625f + I[4][i][v]; + t3[v] = I[1][i][v] * -0.390625f + I[3][i][v]; + t4[v] = I[0][i][v] * 0.87890625f + I[4][i][v]; + t5[v] = I[1][i][v] * 0.87890625f + I[5][i][v]; + + T[0][i][v] = I[2][i][v] * -2.640625f + t4[v]; + T[1][i][v] = t1[v] * 0.625f + t0[v]; + T[2][i][v] = t1[v] * -0.625f + t0[v]; + T[3][i][v] = t3[v] * 1.5f + t2[v]; + T[4][i][v] = t3[v] * -1.5f + t2[v]; + T[5][i][v] = I[3][i][v] * -2.640625f + t5[v]; + } + } + +pragma_unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = T[i][2][v] * -2.25f + T[i][4][v]; + t1[v] = T[i][1][v] * -2.25f + T[i][3][v]; + t2[v] = T[i][2][v] * -0.390625f + T[i][4][v]; + t3[v] = T[i][1][v] * -0.390625f + T[i][3][v]; + t4[v] = T[i][0][v] * 0.87890625f + T[i][4][v]; + t5[v] = T[i][1][v] * 0.87890625f + T[i][5][v]; + + Iw[i][0][v] = T[i][2][v] * -2.640625f + t4[v]; + Iw[i][1][v] = t1[v] * 0.625f + t0[v]; + Iw[i][2][v] = t1[v] * -0.625f + t0[v]; + Iw[i][3][v] = t3[v] * 1.5f + t2[v]; + Iw[i][4][v] = t3[v] * -1.5f + t2[v]; + Iw[i][5][v] = T[i][3][v] * -2.640625f + t5[v]; + } + } +} + +void trans_W_3x3_4x4_wu(float Fw[6][6][16], float F[4][6][16]) +{ + float T[6][4][16]; + float t0[16]; + float t1[16]; + float t2[16]; + float t3[16]; + float t4[16]; + +pragma_unroll + for (int i = 0; i < 4; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = F[2][i][v] * 0.26890756302521f; + t1[v] = F[0][i][v] * -0.688403361344538f - t0[v]; + t2[v] = F[0][i][v] * 0.119514472455649f + t0[v]; + t3[v] = F[1][i][v] * 0.430252100840336f + + F[3][i][v] * 0.168067226890756f; + t4[v] = F[1][i][v] * 0.179271708683473f + + F[3][i][v] * 0.403361344537815f; + + T[0][i][v] = F[0][i][v] * 1.13777777777778f; + T[1][i][v] = t1[v] - t3[v]; + T[2][i][v] = t1[v] + t3[v]; + T[3][i][v] = t2[v] + t4[v]; + T[4][i][v] = t2[v] - t4[v]; + T[5][i][v] = F[3][i][v]; + } + } +pragma_unroll + for (int i = 0; i < 6; i++) { + for (int v = 0; v < 16; v++) { + t0[v] = T[i][2][v] * 0.26890756302521f; + t1[v] = T[i][0][v] * -0.688403361344538f - t0[v]; + t2[v] = T[i][0][v] * 0.119514472455649f + t0[v]; + t3[v] = T[i][1][v] * 0.430252100840336f + + T[i][3][v] * 0.168067226890756f; + t4[v] = T[i][1][v] * 0.179271708683473f + + T[i][3][v] * 0.403361344537815f; + + Fw[i][0][v] = T[i][0][v] * 1.13777777777778f; + Fw[i][1][v] = t1[v] - t3[v]; + Fw[i][2][v] = t1[v] + t3[v]; + Fw[i][3][v] = t2[v] + t4[v]; + Fw[i][4][v] = t2[v] - t4[v]; + Fw[i][5][v] = T[i][3][v]; + } + } +} + +void trans_O_3x3_4x4_wu(float Mw[6][6][16][16], float M[3][3][16][16]) +{ + float T[3][6][16]; + float t0[16]; + float t1[16]; + float t2[16]; + float M_[3][16]; + + for (int j = 0; j < 16; j++) { +pragma_unroll + for (int i = 0; i < 6; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = Mw[1][i][j][v] + Mw[2][i][j][v]; + t1[v] = Mw[3][i][j][v] + Mw[4][i][j][v]; + t2[v] = t1[v] * 2.25f + Mw[5][i][j][v]; + + T[0][i][v] = Mw[0][i][j][v] + t0[v] + t1[v]; + T[1][i][v] = 0.625f * (Mw[1][i][j][v] - Mw[2][i][j][v]) + + 1.5f * (Mw[3][i][j][v] - Mw[4][i][j][v]); + T[2][i][v] = t0[v] * 0.390625f + t2[v]; + } + } +pragma_unroll + for (int i = 0; i < 3; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + t0[v] = T[i][1][v] + T[i][2][v]; + t1[v] = T[i][3][v] + T[i][4][v]; + t2[v] = t1[v] * 2.25f + T[i][5][v]; + + M_[0][v] = T[i][0][v] + t0[v] + t1[v]; + M_[1][v] = 0.625f * (T[i][1][v] - T[i][2][v]) + + 1.5f * (T[i][3][v] - T[i][4][v]); + M_[2][v] = t0[v] * 0.390625f + t2[v]; + } + +pragma_unroll + for (int k = 0; k < 3; k++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < 16; v++) { + M[i][k][j][v] = M_[k][v]; + } + } + } + } +} + +template +void input_transform_data(int image, const jit_conv_winograd_conf_t &jcp, + float *inp, float *tinp, bool streamout = true) +{ + const int inpw = is_fwd ? jcp.iw : jcp.ow; + const int inph = is_fwd ? jcp.ih : jcp.oh; + const int l_pad = is_fwd ? jcp.l_pad : jcp.iw + jcp.r_pad - jcp.ow; + const int t_pad = is_fwd ? jcp.t_pad : jcp.ih + jcp.t_pad - jcp.oh; + const int wp_max = inpw + l_pad; + const int hp_max = inph + t_pad; + float Iw[alpha][alpha][simd_w]; + float I[alpha][alpha][simd_w]; + + array_offset_calculator input(inp, + jcp.mb, jcp.dimK/simd_w, inph, inpw, + simd_w); + array_offset_calculator output(tinp, + jcp.dimN_nb_block, alpha, alpha, + jcp.dimN_block, jcp.dimK_nb_block, jcp.dimK_block, + jcp.dimN_reg_block, jcp.dimK_reg_block); + + int tile_base_index = image * jcp.itiles * jcp.jtiles; + int tile_block_ur = tile_base_index % jcp.tile_block_ur; + int nb_tile_block_ur = + (tile_base_index / jcp.tile_block_ur) % jcp.nb_tile_block_ur; + int tile_block = + (tile_base_index / jcp.tile_block_ur) / jcp.nb_tile_block_ur; + + for (int tj = 0; tj < jcp.jtiles; tj++) { + for (int ti = 0; ti < jcp.itiles; ti++) { + for (int j = 0; j < alpha; j++) { + int ydim = tj * tile_size + j; + if ((t_pad <= ydim) && (ydim < hp_max)) { + float *pinp_j = inp + (ydim - t_pad) * inpw * 16 ; + for (int i = 0; i < alpha; i++) { + int xdim = ti * tile_size + i; + if ((l_pad <= xdim) && (xdim < wp_max)) { + float *pinp_i = pinp_j + (xdim - l_pad) * 16; + load_ps(I[j][i], pinp_i); + } else { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = 0.0f; + } + } + } + } else { + for (int i = 0; i < alpha; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = 0.0f; + } + } + } + } + + trans_I_4x4_3x3(Iw, I); + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + store_output(&(output(tile_block, j, i, + nb_tile_block_ur, 0, 0, + tile_block_ur, 0)), + Iw[j][i], streamout); + } + } + tile_block_ur++; + if (tile_block_ur >= jcp.tile_block_ur) { + tile_block_ur = 0; + nb_tile_block_ur++; + } + if (nb_tile_block_ur >= jcp.nb_tile_block_ur) { + nb_tile_block_ur = 0; + tile_block++; + } + } + } +} + +template +void weight_transform_data(const jit_conv_winograd_conf_t &jcp, + float *wp, float *twp) +{ + const int kh = 3; + const int kw = 3; + array_offset_calculator input(wp, + jcp.oc/jcp.oc_simd_block, + jcp.ic/jcp.ic_simd_block, + jcp.kh, jcp.kw, + simd_w, simd_w); + array_offset_calculator output(twp, + jcp.dimM_nb_block, + alpha, alpha, + jcp.dimK_nb_block, + jcp.dimM_block, jcp.dimK_block, + simd_w, simd_w); + float Fw[alpha][alpha][simd_w][simd_w]; + float F[kh][kw][simd_w][simd_w]; + + for (int j = 0; j < kh; j++) { + for (int i = 0; i < kw; i++) { + for (int v1 = 0; v1 < simd_w; v1++) { + float *base_inp = is_fwd + ? &(input(0, 0, j, i, v1, 0)) + : &(input(0, 0, 2 - j, 2 - i, v1, 0)); + PRAGMA_OMP_SIMD() + for (int v2 = 0; v2 < simd_w; v2++) { + if (is_fwd) + F[j][i][v1][v2] = *(base_inp + v2); + else + F[j][i][v2][v1] = *(base_inp + v2); + } + } + } + } + + trans_W_4x4_3x3(Fw, F); + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + for (int v1 = 0; v1 < simd_w; v1++) { + PRAGMA_OMP_SIMD() + for (int v2 = 0; v2 < simd_w; v2++) { + output(0, j, i, 0, 0, 0, v1, v2) = Fw[j][i][v1][v2]; + } + } + } + } +} + +template +void output_transform_data(int image, const jit_conv_winograd_conf_t &jcp, + const post_ops_t &p_ops, float *toutp, float *pout_b, float *bias, + bool streamout = true) { + float Ow[alpha][alpha][simd_w]; + float O[tile_size][tile_size][simd_w]; + int outw = is_fwd ? jcp.ow : jcp.iw; + int outh = is_fwd ? jcp.oh : jcp.ih; + + /* Prepare for PostOps */ + bool with_relu_postsum = p_ops.find(primitive_kind::eltwise, 1) != -1; + + array_offset_calculator input(toutp, + jcp.dimN_nb_block, jcp.dimM_nb_block, + alpha, alpha, + jcp.dimN_block, jcp.dimM_block, + jcp.dimN_reg_block, jcp.dimM_simd_block); + + int tile_base_index = image * jcp.itiles * jcp.jtiles; + int tile_block_ur = tile_base_index % jcp.tile_block_ur; + int nb_tile_block_ur = + (tile_base_index / jcp.tile_block_ur) % jcp.nb_tile_block_ur; + int tile_block = + (tile_base_index / jcp.tile_block_ur) / jcp.nb_tile_block_ur; + + for (int tj = 0; tj < jcp.jtiles; tj++) { + for (int ti = 0; ti < jcp.itiles; ti++) { + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + Ow[j][i][v] = input(tile_block, 0, + j, i, + nb_tile_block_ur, 0, + tile_block_ur, v); + } + } + } + + trans_O_4x4_3x3(Ow, O); + + for (int j = 0; j < tile_size; j++) { + int ydim = tj * tile_size + j; + if (ydim < outh) { + float *pout_j = pout_b + ydim * outw * simd_w; + for (int i = 0; i < tile_size; i++) { + int xdim = ti * tile_size + i; + if (xdim < outw) { + float *pout_i = pout_j + xdim * simd_w; + if (is_fwd) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + O[j][i][v] += with_bias ? bias[v] : 0.f; + O[j][i][v] = true + && with_relu_presum && O[j][i][v] < 0.f + ? O[j][i][v] + * jcp.eltwise.alpha + : O[j][i][v]; + } + } + if (with_sum) + accum_output(pout_i, O[j][i], streamout, + with_relu_postsum); + else + store_output(pout_i, O[j][i], streamout); + } + } + } + } + tile_block_ur++; + if (tile_block_ur >= jcp.tile_block_ur) { + tile_block_ur = 0; + nb_tile_block_ur++; + } + if (nb_tile_block_ur >= jcp.nb_tile_block_ur) { + nb_tile_block_ur = 0; + tile_block++; + } + } + } +} + +template +void diff_src_transform_bwd_weights(int image, jit_conv_winograd_conf_t conv, + float *inp, float *tinp, float *Iw_temp, + void (*transpose_4fma_ker)(float *, float *)) +{ + + const int ifwp = conv.iw + conv.l_pad; + const int ifhp = conv.ih + conv.t_pad; + float I[alpha][alpha][simd_w]; + float Iw[alpha][alpha][simd_w]; + + array_offset_calculator Iw_trans_temp(Iw_temp, + alpha, alpha, conv.tile_4fma, simd_w); + array_offset_calculator input(inp, + conv.mb, conv.ic/simd_w, conv.ih, conv.iw, simd_w); + array_offset_calculator output(tinp, + conv.nb_ic, alpha, alpha, + conv.tile_block, conv.ic_block, + conv.nb_tile_block_ur, conv.tile_block_ur, + conv.ic_simd_block * conv.tile_4fma); + + int tile_base_index = + image * (conv.itiles * conv.jtiles + conv.tile_4fma_padding); + int tile_4fma = 0; + int tile_block_ur = (tile_base_index / conv.tile_4fma) % conv.tile_block_ur; + int nb_tile_block_ur = + (tile_base_index / conv.tile_4fma / conv.tile_block_ur) + % conv.nb_tile_block_ur; + int tile_block = (tile_base_index / conv.tile_4fma / conv.tile_block_ur) + / conv.nb_tile_block_ur; + + for (int tj = 0; tj < conv.jtiles; tj++) { + for (int ti = 0; ti < conv.itiles; ti++) { + for (int j = 0; j < alpha; j++) { + int ydim = tj * tile_size + j; + if ((conv.t_pad <= ydim) && ydim < ifhp) { + for (int i = 0; i < alpha; i++) { + int xdim = ti * tile_size + i; + if ((conv.l_pad <= xdim) && xdim < ifwp) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = input(0, 0, + ydim - conv.t_pad, + xdim - conv.l_pad, v); + } + } else { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = 0.0f; + } + } + } + } else { + for (int i = 0; i < alpha; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = 0.0f; + } + } + } + } + trans_I_4x4_3x3(Iw, I); + + if (ver_4fma) { + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + float *Iw_temp_base = &(Iw_trans_temp(j, i, + tile_4fma, 0)); + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + Iw_temp_base[v] = Iw[j][i][v]; + } + } + } + tile_4fma++; + if (tile_4fma == conv.tile_4fma) { + float *outp = &(output(0, 0, 0, + tile_block, 0, + nb_tile_block_ur, tile_block_ur, 0)); + transpose_4fma_ker(outp, (float *)Iw_temp); + tile_4fma = 0; + tile_block_ur++; + } + } else { + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + store_output(&(output(0, j, i, + tile_block, 0, + nb_tile_block_ur, tile_block_ur, 0)), + Iw[j][i], true); + } + } + tile_block_ur++; + } + + if (tile_block_ur == conv.tile_block_ur) { + tile_block_ur = 0; + ++nb_tile_block_ur; + } + if (nb_tile_block_ur == conv.nb_tile_block_ur) { + nb_tile_block_ur = 0; + tile_block++; + } + } + } + + if (ver_4fma && tile_4fma < conv.tile_4fma && conv.tile_4fma_padding != 0) { + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + for (int tb = tile_4fma; tb < conv.tile_4fma; tb++) { + float *Iw_temp_base = &(Iw_trans_temp(j, i, tb, 0)); + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + Iw_temp_base[v] = 0; + } + } + } + } + float *outp = &(output(0, 0, 0, + tile_block, 0, + nb_tile_block_ur, tile_block_ur, 0)); + transpose_4fma_ker(outp, (float *)Iw_temp); + } +} + +template +void diff_dst_transform_bwd_weights(int image, jit_conv_winograd_conf_t conv, + float *inp, float *tinp, float *dbias) +{ + + const int total_tiles = conv.itiles * conv.jtiles + conv.tile_4fma_padding; + float I[alpha][alpha][simd_w]; + float Iw[alpha][alpha][simd_w]; + + array_offset_calculator input(inp, + conv.mb, conv.oc/simd_w, conv.oh, conv.ow, conv.oc_simd_block); + array_offset_calculator output(tinp, + conv.nb_oc, alpha, alpha, + conv.tile_block, conv.oc_block, + conv.nb_tile_block_ur, + conv.tile_block_ur * conv.tile_4fma, conv.oc_simd_block); + + int tile_base_index = image * total_tiles; + int tile_block_ur = tile_base_index % (conv.tile_block_ur * conv.tile_4fma); + int nb_tile_block_ur = + (tile_base_index / conv.tile_block_ur / conv.tile_4fma) + % conv.nb_tile_block_ur; + int tile_block = (tile_base_index / conv.tile_block_ur / conv.tile_4fma) + / conv.nb_tile_block_ur; + + for (int tj = 0; tj < conv.jtiles; tj++) { + for (int ti = 0; ti < conv.itiles; ti++) { + for (int j = 0; j < alpha; j++) { + int ydim = tj * tile_size + j; + if (ydim < conv.oh) { + for (int i = 0; i < alpha; i++) { + int xdim = ti * tile_size + i; + if (xdim < conv.ow) { + float *input_base = &(input(0, 0, ydim, xdim, 0)); + + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = input_base[v]; + } + if (with_bias && j < tile_size && i < tile_size) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + dbias[v] += input_base[v]; + } + } + } else { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = 0.0f; + } + } + } + } else { + for (int i = 0; i < alpha; i++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + I[j][i][v] = 0.0f; + } + } + } + } + + trans_W_3x3_4x4_wu(Iw, I); + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + store_output(&(output(0, j, i, + tile_block, 0, + nb_tile_block_ur, + tile_block_ur, 0)), + Iw[j][i], true); + } + } + tile_block_ur++; + if (tile_block_ur >= conv.tile_block_ur * conv.tile_4fma) { + tile_block_ur = 0; + nb_tile_block_ur++; + } + if (nb_tile_block_ur >= conv.nb_tile_block_ur) { + nb_tile_block_ur = 0; + tile_block++; + } + } + } +} + +void diff_weights_transform_bwd_weights(jit_conv_winograd_conf_t conv, + float *wp, float *twp) +{ + const int kh = 3; + const int kw = 3; + float Fw[alpha][alpha][simd_w][simd_w]; + float F[kh][kw][simd_w][simd_w]; + + array_offset_calculator input(twp, + conv.nb_ic, conv.nb_oc, + alpha, alpha, + conv.oc_block, conv.ic_block, + conv.ic_simd_block, conv.oc_simd_block); + array_offset_calculator output(wp, + conv.oc/simd_w, conv.ic/simd_w, + conv.kh, conv.kw, + conv.ic_simd_block, conv.oc_simd_block); + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + for (int v = 0; v < conv.ic_simd_block; v++) { + PRAGMA_OMP_SIMD() + for (int k = 0; k < conv.oc_simd_block; k++) { + Fw[j][i][v][k] = input(0, 0, j, i, 0, 0, v, k); + } + } + } + } + + trans_O_3x3_4x4_wu(Fw, F); + + for (int j = 0; j < kh; j++) { + for (int i = 0; i < kw; i++) { + for (int v = 0; v < conv.ic_simd_block; v++) { + store_output(&(output(0, 0, j, i, v, 0)), + F[j][i][v], true); + } + } + } +} + +template +void _jit_avx512_common_convolution_winograd_t::_execute_data_W_S_G_D( + float *inp_ptr, float *out_ptr, float *wei_ptr, float *bias_ptr, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const auto &p_ops = attr_->post_ops_; + + const int inph = is_fwd ? jcp.ih : jcp.oh; + const int inpw = is_fwd ? jcp.iw : jcp.ow; + const int outh = is_fwd ? jcp.oh : jcp.ih; + const int outw = is_fwd ? jcp.ow : jcp.iw; + + /* Note that jcp.with_eltwise is true for both fused conv+relu primitive + * and conv primitive with PostOps with relu before sum + * (PostOps relu after sum is handled later) */ + auto output_transform = jcp.with_bias + ? (jcp.with_eltwise + ? (jcp.with_sum + ? output_transform_data + : output_transform_data) + : (jcp.with_sum + ? output_transform_data + : output_transform_data)) + : (jcp.with_eltwise + ? (jcp.with_sum + ? output_transform_data + : output_transform_data) + : (jcp.with_sum + ? output_transform_data + : output_transform_data)); + + /* Notation: + FWD: dimM:oc, dimN:ntiles, dimK:ic, + BWD: dimM:ic, dimN:ntiles, dimK:oc, + FWD/BWD: V: src/diff_dst transform, U:weight transform, + M:dst/diff_src transform */ + array_offset_calculator input(inp_ptr, + jcp.mb, jcp.dimK/jcp.dimK_reg_block, inph, inpw, + jcp.dimK_reg_block); + array_offset_calculator output(out_ptr, + jcp.mb, jcp.dimM/jcp.dimM_simd_block, outh, outw, + jcp.dimM_simd_block); + array_offset_calculator weights(wei_ptr, + jcp.oc/jcp.oc_simd_block, jcp.ic/jcp.ic_simd_block, jcp.kh, jcp.kw, + jcp.ic_simd_block, jcp.oc_simd_block); + array_offset_calculator bias(bias_ptr, + jcp.dimM/jcp.dimM_simd_block, jcp.dimM_simd_block); + + array_offset_calculator M(is_fwd + ? scratchpad.template get(key_wino_M) + : scratchpad.template get(key_wino_V), + jcp.dimN_nb_block, jcp.dimM_nb_block, + alpha, alpha, + jcp.dimN_block, jcp.dimM_block, + jcp.dimN_reg_block, jcp.dimM_simd_block); + array_offset_calculator U( + scratchpad.template get(key_wino_U), + jcp.dimM_nb_block, + alpha, alpha, + jcp.dimK_nb_block, + jcp.dimM_block, jcp.dimK_block, + jcp.dimK_reg_block, jcp.dimM_simd_block); + array_offset_calculator V(is_fwd + ? scratchpad.template get(key_wino_V) + : scratchpad.template get(key_wino_M), + jcp.dimN_nb_block, alpha, alpha, + jcp.dimN_block, jcp.dimK_nb_block, + jcp.dimK_block, jcp.dimN_reg_block, jcp.dimK_reg_block); + + bool V_streamout = jcp.dimN * jcp.dimK * alpha * alpha * sizeof(float) + > 2 * LLC_cache_size ? true : false; + + const bool output_is_aligned = ((size_t)out_ptr & (64 - 1)) == 0; + + const bool wants_padded_bias = jcp.with_bias + && jcp.oc_without_padding != jcp.oc; + float last_slice_bias[simd_w] = {0}; + if (wants_padded_bias) { + for (int oc = 0; oc < jcp.oc_without_padding % jcp.oc_simd_block; ++oc) + last_slice_bias[oc] = bias(jcp.dimM / jcp.dimM_simd_block - 1, oc); + } + + { + parallel_nd(jcp.mb, jcp.dimK_nb_block, jcp.dimK_block, + [&](int img, int K_blk1, int K_blk2) { + input_transform_data(img, jcp, + &(input(img, K_blk1 * jcp.dimK_block + K_blk2, 0, 0, 0)), + &(V(0, 0, 0, 0, K_blk1, K_blk2, 0, 0)), V_streamout); + }); + + parallel_nd(jcp.nb_oc, jcp.nb_ic, jcp.oc_block, jcp.ic_block, + [&](int ofm1, int ifm1, int ofm2, int ifm2) { + float *U_base_ptr = is_fwd + ? &(U(ofm1, 0, 0, ifm1, ofm2, ifm2, 0, 0)) + : &(U(ifm1, 0, 0, ofm1, ifm2, ofm2, 0, 0)); + weight_transform_data(jcp, + &(weights(ofm1 * jcp.oc_block + ofm2, + ifm1 * jcp.ic_block + ifm2, 0, 0, 0, 0)), U_base_ptr); + }); + + parallel_nd(jcp.dimN_nb_block, alpha, alpha, jcp.dimM_nb_block, jcp.dimN_block, + [&](int N_blk1, int oj, int oi, int M_blk1, int N_blk2) { + + kernel_->gemm_loop_ker_first_iter( + (float *)&(M(N_blk1, M_blk1, oj, oi, + N_blk2, 0, 0, 0)), + (const float *)&(U(M_blk1, oj, oi, + 0, 0, 0, 0, 0)), + (const float *)&(V(N_blk1, oj, oi, + N_blk2, 0, 0, 0, 0))); + for (int K_blk1 = 1; K_blk1 < jcp.dimK_nb_block; K_blk1++) { + kernel_->gemm_loop_ker( + (float *)&(M(N_blk1, M_blk1, oj, oi, + N_blk2, 0, 0, 0)), + (const float *)&(U(M_blk1, oj, oi, + K_blk1, 0, 0, 0, 0)), + (const float *)&(V(N_blk1, oj, oi, + N_blk2, K_blk1, + 0, 0, 0))); + } + + }); + + parallel_nd(jcp.mb, jcp.dimM_nb_block, jcp.dimM_block, + [&](int img, int M_blk1, int M_blk2) { + + const int M_blk = M_blk1 * jcp.dimM_block + M_blk2; + + float *bias_ptr = wants_padded_bias + && M_blk == jcp.dimM / jcp.dimM_simd_block - 1 + ? last_slice_bias : &bias(M_blk, 0); + + output_transform(img, jcp, p_ops, + &(M(0, M_blk1, 0, 0, 0, M_blk2, 0, 0)), + &(output(img, M_blk, 0, 0, 0)), + bias_ptr, output_is_aligned); + + }); + + } +} + +template struct _jit_avx512_common_convolution_winograd_t; +template struct _jit_avx512_common_convolution_winograd_t; + +void jit_avx512_common_convolution_winograd_bwd_weights_t:: +_maybe_execute_diff_bias_copy(float *diff_bias, + const memory_tracking::grantor_t &scratchpad) const { + if (pd()->wants_padded_bias()) { + auto padded_bias = scratchpad.get(key_conv_padded_bias); + for (int oc = 0; oc < pd()->jcp_.oc_without_padding; ++oc) + diff_bias[oc] = padded_bias[oc]; + } +} + +void jit_avx512_common_convolution_winograd_bwd_weights_t:: +_execute_backward_weights_S_D_G_W(const exec_ctx_t &ctx, + const memory_tracking::grantor_t &scratchpad) const { + auto ptr_diff_dst = CTX_IN_MEM(const float *, MKLDNN_ARG_DIFF_DST); + auto ptr_src = CTX_IN_MEM(const float *, MKLDNN_ARG_SRC); + auto ptr_diff_weights = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_WEIGHTS); + auto ptr_diff_bias = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_BIAS); + + const auto &jcp = kernel_->jcp; + const int nthreads = jcp.nthr; + + auto diff_src_transform_bwd_weights_ver = jcp.ver == ver_4fma ? + diff_src_transform_bwd_weights : + diff_src_transform_bwd_weights; + auto diff_dst_transform_bwd_weights_ver = jcp.with_bias + ? diff_dst_transform_bwd_weights + : diff_dst_transform_bwd_weights; + + array_offset_calculator src((float *)ptr_src, + jcp.mb, jcp.ic/simd_w, jcp.ih, jcp.iw, simd_w); + array_offset_calculator diff_dst((float *)ptr_diff_dst, + jcp.mb, jcp.oc/simd_w, jcp.oh, jcp.ow, simd_w); + array_offset_calculator diff_weights(ptr_diff_weights, + jcp.oc/simd_w, jcp.ic/simd_w, jcp.kh, jcp.kw, simd_w, simd_w); + array_offset_calculator diff_bias(pd()->wants_padded_bias() + ? scratchpad.get(key_conv_padded_bias) : ptr_diff_bias, + jcp.oc/simd_w, simd_w); + + array_offset_calculator U( + scratchpad.get(key_wino_U), + jcp.nb_ic, jcp.nb_oc, + alpha, alpha, + jcp.oc_block, jcp.ic_block, + jcp.ic_simd_block, jcp.oc_simd_block); + + array_offset_calculator M( + scratchpad.get(key_wino_M), + jcp.nb_oc, alpha, alpha, + jcp.tile_block, jcp.oc_block, + jcp.nb_tile_block_ur, jcp.tile_block_ur * jcp.tile_4fma, + jcp.oc_simd_block); + array_offset_calculator V( + scratchpad.get(key_wino_V), + jcp.nb_ic, alpha, alpha, + jcp.tile_block, jcp.ic_block, + jcp.nb_tile_block_ur, jcp.tile_block_ur, + jcp.ic_simd_block * jcp.tile_4fma); + + const int trans_buffer_size = alpha * alpha * jcp.tile_4fma + * jcp.ic_simd_block; + array_offset_calculator trans_buffer( + scratchpad.get(key_conv_tr_src), + nthreads, + trans_buffer_size); + + array_offset_calculator diff_bias_prv( + scratchpad.get(key_conv_bia_reduction), + nthreads, + jcp.oc); + +PRAGMA_OMP(parallel num_threads(nthreads)) + { + if (jcp.with_bias) { + parallel_nd_in_omp(nthreads, jcp.oc, [&](int ithr, int ofm) { + diff_bias_prv(ithr, ofm) = 0.0f; + }); + +PRAGMA_OMP(for nowait) + for (int bofm = 0; bofm < jcp.oc / simd_w; bofm++) { + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) + diff_bias(bofm, v) = 0.0f; + } + } + + const int ithread = mkldnn_get_thread_num(); + + parallel_nd_in_omp(jcp.mb, jcp.nb_ic, jcp.ic_block, + [&](int img, int ifm1, int ifm2) { + float *transb = jcp.ver == ver_4fma + ? &(trans_buffer(ithread, 0)) + : NULL; + diff_src_transform_bwd_weights_ver(img, jcp, + &(src(img, ifm1 * jcp.ic_block + ifm2, + 0, 0, 0)), + &(V(ifm1, 0, 0, 0, ifm2, 0, 0, 0)), + transb, + kernel_->transpose_4fma_ker); + }); + + parallel_nd_in_omp(jcp.mb, jcp.nb_oc, jcp.oc_block, + [&](int img, int ofm1, int ofm2) { + float *dbias = jcp.with_bias + ? &(diff_bias_prv(ithread, + simd_w * (ofm1 * jcp.oc_block + ofm2))) + : NULL; + diff_dst_transform_bwd_weights_ver(img, jcp, + &(diff_dst(img, ofm1 * jcp.oc_block + ofm2, + 0, 0, 0)), + &(M(ofm1, 0, 0, 0, ofm2, 0, 0, 0)), + dbias); + }); + +PRAGMA_OMP(barrier) + + for (int ifm1 = 0; ifm1 < jcp.nb_ic; ifm1++) { + parallel_nd_in_omp(alpha, alpha, jcp.nb_oc, + [&](int oj, int oi, int ofm1) { + kernel_->gemm_loop_ker_first_iter( + (float *)&(U(ifm1, ofm1, oj, oi, + 0, 0, 0, 0)), + (const float *)&(M(ofm1, oj, oi, + 0, 0, 0, 0, 0)), + (const float *)&(V(ifm1, oj, oi, + 0, 0, 0, 0, 0))); + for (int tile_block = 1; tile_block < jcp.tile_block; + tile_block++) { + kernel_->gemm_loop_ker((float *)&(U(ifm1, ofm1, + oj, oi, + 0, 0, 0, 0)), + (const float *)&(M(ofm1, oj, oi, tile_block, + 0, 0, 0, 0)), + (const float *)&(V(ifm1, oj, oi, tile_block, + 0, 0, 0, 0))); + } + }); + } + +PRAGMA_OMP(barrier) + + parallel_nd_in_omp(jcp.nb_ic, jcp.nb_oc, jcp.oc_block, jcp.ic_block, + [&](int ifm1, int ofm1, int ofm2, int ifm2) { + diff_weights_transform_bwd_weights(jcp, + &(diff_weights(ofm1 * jcp.oc_block + ofm2, + ifm1 * jcp.ic_block + ifm2, 0, 0, 0, 0)), + &(U(ifm1, ofm1, 0, 0, ofm2, ifm2, 0, 0))); + }); + + if (jcp.with_bias) { +PRAGMA_OMP(for) + for (int ofm1 = 0; ofm1 < jcp.oc / simd_w; ofm1++) { + for (int ithr = 0; ithr < nthreads; ithr++) { + float* base_bias_ptr = &(diff_bias(ofm1, 0)); + float* base_bias_prv_ptr = &(diff_bias_prv( + ithr * jcp.oc + ofm1 * simd_w)); + PRAGMA_OMP_SIMD() + for (int ofm2 = 0; ofm2 < simd_w; ofm2++) { + base_bias_ptr[ofm2] += base_bias_prv_ptr[ofm2]; + } + } + } + } + } + + _maybe_execute_diff_bias_copy(ptr_diff_bias, scratchpad); +} + +} +} +} +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.hpp new file mode 100644 index 000000000000..6c76f37c727d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.hpp @@ -0,0 +1,318 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_COMMON_CONVOLUTION_WINOGRAD_HPP +#define CPU_JIT_AVX512_COMMON_CONVOLUTION_WINOGRAD_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_avx512_common_conv_winograd_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace winograd_avx512_common { +inline void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_winograd_conf_t &jcp) { + using namespace memory_tracking::names; + + size_t U_sz = (size_t)alpha * alpha * jcp.ic * jcp.oc; + size_t V_sz = (size_t)alpha * alpha * jcp.mb * jcp.ic + * (jcp.itiles * jcp.jtiles + jcp.tile_4fma_padding); + size_t M_sz = (size_t)alpha * alpha * jcp.mb * jcp.oc + * (jcp.itiles * jcp.jtiles + jcp.tile_4fma_padding); + + scratchpad.book(key_wino_U, sizeof(float) * U_sz, PAGE_2M); + scratchpad.book(key_wino_V, sizeof(float) * V_sz, PAGE_2M); + scratchpad.book(key_wino_M, sizeof(float) * M_sz, PAGE_2M); + + if (jcp.sched_policy == WSCHED_WEI_S_D_G_W) { + const int nthr = mkldnn_get_max_threads(); + + size_t tr_src_sz = jcp.ver != ver_4fma ? 0 : (size_t)nthr + * alpha * alpha * jcp.tile_4fma * jcp.ic_simd_block; + scratchpad.book(key_conv_tr_src, sizeof(float) * tr_src_sz, PAGE_2M); + + size_t br_sz = jcp.with_bias ? nthr * jcp.oc : 0; + scratchpad.book(key_conv_bia_reduction, sizeof(float) * br_sz, PAGE_2M); + + size_t padded_bias_sz = + jcp.with_bias && jcp.oc_without_padding != jcp.oc ? jcp.oc : 0; + scratchpad.book(key_conv_padded_bias, sizeof(float) * padded_bias_sz); + } +} +} + +template +struct _jit_avx512_common_convolution_winograd_t { + _jit_avx512_common_convolution_winograd_t( + const jit_conv_winograd_conf_t &jcp, const primitive_attr_t *attr) + : kernel_(nullptr), attr_(attr) { + kernel_ = new _jit_avx512_common_conv_winograd_data_kernel_f32(jcp); + } + + ~_jit_avx512_common_convolution_winograd_t() { delete kernel_; } + + protected: + void _execute_data_W_S_G_D(float *inp_ptr, float *out_ptr, + float *wei_ptr, float *bias_ptr, + const memory_tracking::grantor_t &scratchpad) const; + _jit_avx512_common_conv_winograd_data_kernel_f32 *kernel_; + const primitive_attr_t *attr_; +}; + +struct jit_avx512_common_convolution_winograd_fwd_t + : _jit_avx512_common_convolution_winograd_t + , public cpu_primitive_t + { + struct pd_t : public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_wino:", avx512_common, ""), + jit_avx512_common_convolution_winograd_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_common_conv_winograd_fwd_kernel_f32:: + init_conf(jcp_, *desc(), *src_md(), *weights_md(), *dst_md(), + *attr()); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + auto scratchpad = scratchpad_registry().registrar(); + winograd_avx512_common::init_scratchpad(scratchpad, jcp_); + + return status; + } + + jit_conv_winograd_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + auto wei_tag = with_groups() ? gOIhw16i16o : OIhw16i16o; + return set_default_formats_common(nChw16c, wei_tag, nChw16c); + } + }; + + jit_avx512_common_convolution_winograd_fwd_t(const pd_t *apd) + : _jit_avx512_common_convolution_winograd_t(apd->jcp_, apd->attr()) + , cpu_primitive_t(apd, true) {} + + ~jit_avx512_common_convolution_winograd_fwd_t(){}; + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override + { + auto src = CTX_IN_MEM(const float *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const float *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const float *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(float *, MKLDNN_ARG_DST); + this->_execute_data_W_S_G_D((float *)src, dst, (float *)weights, + (float *)bias, this->scratchpad(ctx)); + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct jit_avx512_common_convolution_winograd_bwd_data_t + : _jit_avx512_common_convolution_winograd_t, + public cpu_primitive_t { + struct pd_t : public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_wino:", avx512_common, ""), + jit_avx512_common_convolution_winograd_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && expect_data_types(data_type::f32, data_type::f32, + data_type::undef, data_type::f32, data_type::f32) + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && !has_zero_dim_memory() + && set_default_formats() + && mkldnn_thr_syncable(); + if (!ok) return status::unimplemented; + + status_t status = + jit_avx512_common_conv_winograd_bwd_data_kernel_f32::init_conf( + jcp_, *desc(), *diff_src_md(), *weights_md(), + *diff_dst_md()); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + auto scratchpad = scratchpad_registry().registrar(); + winograd_avx512_common::init_scratchpad(scratchpad, jcp_); + + return status; + } + + jit_conv_winograd_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + auto wei_tag = with_groups() ? gOIhw16i16o : OIhw16i16o; + return set_default_formats_common(nChw16c, wei_tag, nChw16c); + } + }; + + jit_avx512_common_convolution_winograd_bwd_data_t(const pd_t *apd) + : _jit_avx512_common_convolution_winograd_t(apd->jcp_, apd->attr()) + , cpu_primitive_t(apd, true) {} + + ~jit_avx512_common_convolution_winograd_bwd_data_t(){}; + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto diff_dst = CTX_IN_MEM(const float *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const float *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_SRC); + this->_execute_data_W_S_G_D((float *)diff_dst, diff_src, + (float *)weights, nullptr, this->scratchpad(ctx)); + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct jit_avx512_common_convolution_winograd_bwd_weights_t + : public cpu_primitive_t { + struct pd_t : public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, + hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_wino:", avx512_common, ""), + jit_avx512_common_convolution_winograd_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats() + && mkldnn_thr_syncable(); + if (!ok) return status::unimplemented; + + status_t status = + jit_avx512_common_conv_winograd_bwd_weights_kernel_f32:: + init_conf(jcp_, *desc(), *src_md(), *diff_dst_md(), + *diff_weights_md()); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + auto scratchpad = scratchpad_registry().registrar(); + winograd_avx512_common::init_scratchpad(scratchpad, jcp_); + + return status; + } + + jit_conv_winograd_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + auto wei_tag = with_groups() ? gOIhw16i16o : OIhw16i16o; + return set_default_formats_common(nChw16c, wei_tag, nChw16c); + } + }; + + jit_avx512_common_convolution_winograd_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd, true), kernel_(nullptr) + { + kernel_ = new jit_avx512_common_conv_winograd_bwd_weights_kernel_f32( + pd()->jcp_); + } + + ~jit_avx512_common_convolution_winograd_bwd_weights_t() + { delete kernel_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override + { + _execute_backward_weights_S_D_G_W(ctx, scratchpad(ctx)); + return status::success; + } + +private: + void _execute_backward_weights_S_D_G_W(const exec_ctx_t &ctx, + const memory_tracking::grantor_t &scratchpad) const; + void _maybe_execute_diff_bias_copy(float *diff_bias, + const memory_tracking::grantor_t &scratchpad) const; + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_avx512_common_conv_winograd_bwd_weights_kernel_f32 *kernel_; +}; + +void trans_W_4x4_3x3(float Fw_[6][6][16][16], float F[3][3][16][16]); +void trans_O_4x4_3x3(float Mw[6][6][16], float O[4][4][16]); +void trans_W_3x3_4x4(float Fw[6][6][16], float F[4][6][16]); +void trans_O_3x3_4x4(float Mw[6][6][16][16], float M[3][3][16][16]); +void trans_I_4x4_3x3(float Iw[6][6][16], float I[6][6][16]); +void trans_W_3x3_4x4_wu(float Fw[6][6][16], float F[4][6][16]); +void trans_O_3x3_4x4_wu(float Mw[6][6][16][16], float M[3][3][16][16]); + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.cpp new file mode 100644 index 000000000000..d4a451c02134 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.cpp @@ -0,0 +1,853 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_common_lrn.hpp" + +#include "jit_generator.hpp" + +#define FWD_RBC 4 +#define BWD_RBC 3 + +#define XMM_SIZE (4*sizeof(float)) +#define ZMM_SIZE (vlen) +#define BUFFER_BLOCK (XMM_SIZE + ZMM_SIZE + XMM_SIZE) +#define BUFFER_NEXT_OFFSET (XMM_SIZE + ZMM_SIZE) +#define SRC_PREV_OFFSET (vlen - XMM_SIZE) + +#define IRB_LOOP(statement) for(int irb = 0; irb < loop_size; irb++) { \ + statement;\ +} + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +enum params { vsize = 16, vlen = 64}; + +typedef struct { + const float *src; + float *dst, *ws0, *ws1; +} jit_args_fwd_t; + +typedef struct { + const float *src, *diff_dst, *ws0, *ws1; + float *diff_src; +} jit_args_bwd_t; + +struct nChw16c_across { +/* version: + * -1: channels 0..15, + * 1: channels C-16 .. C-1, + * 0: other channels + * 3: channels only for this kernel(without prev and next) + */ + int H, W, version; + nChw16c_across(int h, int w, int v) : H(h), W(w), version(v) {} +}; + +struct jit_avx512_common_lrn_fwd_t::jit_avx512_common_lrn_kernel_f32: + public jit_generator { + int HW, W; + bool is_first; + bool is_last; + bool is_single; + + Reg64 src = rax; + Reg64 dst = r8; + Reg64 scratch0 = rdx; + Reg64 scratch1 = rsi; + Reg64 imm_addr64 = rbx; + + Zmm zalpha = zmm0; + Xmm xalpha = xmm0; + Zmm zk = zmm1; + Xmm xk = xmm1; + + Reg64 param = abi_param1; + Reg64 t = rsp; + Reg64 hw = r9; + + int xsrc_prev = 2; + int zsrc = 7; + int xsrc_next = 3; + int zc = 7; + + int za = 2; + int zb = 3; + int zd = 5; + int ze = 6; + int zsum = 4; + int zdst = 2; + int zbase = 3; + int zsum2 = 5; + + prop_kind_t pk; + int use_h_parallelism; + + float alpha, k; + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_common_lrn_kernel_f32) + + void (*ker)(jit_args_fwd_t *); + void operator()(jit_args_fwd_t *arg) { ker(arg); } + + enum { + prf0_offt = 1*FWD_RBC, + prf2_offt = 8*FWD_RBC + }; + + inline void compute_loop(int loop_size_param) + { + // loop_size - param for IRB_LOOP macro + int loop_size = FWD_RBC; + + auto xreg = [=](int irb, int i) { + return Xmm(irb*3 + i); + }; + + auto zreg = [=](int irb, int i) { + return Zmm(irb*7 + i); + }; + + if (!is_first && !is_single) { + IRB_LOOP(mic_prefetcht0(ptr[src + (irb + prf0_offt - HW)*vlen])); + IRB_LOOP(mic_prefetcht2(ptr[src + (irb + prf2_offt - HW)*vlen])); + } + IRB_LOOP(mic_prefetcht0(EVEX_compress_addr(src, (irb + prf0_offt)*vlen))); + IRB_LOOP(mic_prefetcht2(EVEX_compress_addr(src, (irb + prf2_offt)*vlen))); + if (!is_last && !is_single) { + IRB_LOOP(mic_prefetcht0(ptr[src + (irb + prf0_offt + HW)*vlen])); + IRB_LOOP(mic_prefetcht2(ptr[src + (irb + prf2_offt + HW)*vlen])); + } + if (pk != prop_kind::forward_inference) { + IRB_LOOP(mic_prefetcht0(EVEX_compress_addr(scratch0, + (irb + prf0_offt)*vlen))); + IRB_LOOP(mic_prefetcht2(EVEX_compress_addr(scratch0, + (irb + prf2_offt)*vlen))); + } + IRB_LOOP(mic_prefetcht0(EVEX_compress_addr(dst, (irb + prf0_offt)*vlen))); + IRB_LOOP(mic_prefetcht2(EVEX_compress_addr(dst, (irb + prf2_offt)*vlen))); + if (pk != prop_kind::forward_inference) { + IRB_LOOP(mic_prefetcht0(EVEX_compress_addr(scratch1, + (irb + prf0_offt) * vlen))); + IRB_LOOP(mic_prefetcht2(EVEX_compress_addr(scratch1, + (irb + prf2_offt) * vlen))); + } + + loop_size = loop_size_param; + if (loop_size == 0) + return; + if (!is_first && !is_single) { + IRB_LOOP(vmovups(xreg(irb, xsrc_prev), + ptr[src + (irb - HW) * vlen + SRC_PREV_OFFSET])); + } + IRB_LOOP(vmovups(zreg(irb, zsrc), EVEX_compress_addr(src,irb*vlen))); + if (!is_last && !is_single) { + IRB_LOOP(vmovups(xreg(irb, xsrc_next), + ptr[src + (irb + HW) * vlen])); + } + + if (!is_first && !is_single) { + IRB_LOOP(vmovups(ptr[t + irb*BUFFER_BLOCK], + xreg(irb, xsrc_prev))); + } + IRB_LOOP(vmovups(EVEX_compress_addr(t, irb*BUFFER_BLOCK + XMM_SIZE), + zreg(irb, zsrc))); + if (!is_last && !is_single) { + IRB_LOOP(vmovups(ptr[t + irb*BUFFER_BLOCK + BUFFER_NEXT_OFFSET], + xreg(irb, xsrc_next))); + } + + IRB_LOOP(vmovups(zreg(irb, za), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE - 2*sizeof(float)))); + IRB_LOOP(vmovups(zreg(irb, zb), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE - sizeof(float)))); + IRB_LOOP(vmovups(zreg(irb, zd), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE + sizeof(float)))); + IRB_LOOP(vmovups(zreg(irb, ze), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE + 2*sizeof(float)))); + + assert(zc == zsrc); + IRB_LOOP(vmulps(zreg(irb, zsum), zreg(irb, zc), zreg(irb, zc))); + + IRB_LOOP(vfmadd231ps(zreg(irb, zsum), zreg(irb, za), zreg(irb, za))); + IRB_LOOP(vfmadd231ps(zreg(irb, zsum), zreg(irb, zb), zreg(irb, zb))); + IRB_LOOP(vfmadd231ps(zreg(irb, zsum), zreg(irb, zd), zreg(irb, zd))); + IRB_LOOP(vfmadd231ps(zreg(irb, zsum), zreg(irb, ze), zreg(irb, ze))); + + IRB_LOOP(vfmadd132ps(zreg(irb, zsum), zk, zalpha)); + + IRB_LOOP(vmovaps(zreg(irb, zbase), zreg(irb, zsum))); + + IRB_LOOP(vmulps(zreg(irb, zsum2), zreg(irb, zsum), zreg(irb, zsum))); + IRB_LOOP(vmulps(zreg(irb, zsum), zreg(irb, zsum), zreg(irb, zsum2))); + + IRB_LOOP(vsqrtps(zreg(irb, zsum), zreg(irb, zsum))); + IRB_LOOP(vsqrtps(zreg(irb, zsum), zreg(irb, zsum))); + + if (pk != prop_kind::forward_inference) { + IRB_LOOP(vmovups(EVEX_compress_addr(scratch0, irb*vlen), + zreg(irb, zsum))); + } + IRB_LOOP(vdivps(zreg(irb, zdst), zreg(irb, zsrc), zreg(irb, zsum))); + IRB_LOOP(vmovups(EVEX_compress_addr(dst, irb*vlen), zreg(irb, zdst))); + if (pk != prop_kind::forward_inference) { + /* ws1 = zdst / zbase = zsrc / (zbase^1.75) */ + IRB_LOOP(vdivps(zreg(irb, zsum), zreg(irb, zdst), zreg(irb, zbase))); + IRB_LOOP(vmovups(EVEX_compress_addr(scratch1, irb*vlen), + zreg(irb, zsum))); + } + } + + jit_avx512_common_lrn_kernel_f32( + const struct nChw16c_across &J, + prop_kind_t prop_kind, + int use_h_parallel, + float A, + float K, + void *code_ptr = nullptr, + size_t code_size = 2 * Xbyak::DEFAULT_MAX_CODE_SIZE) + : jit_generator(code_ptr, code_size) + , pk(prop_kind) + , use_h_parallelism(use_h_parallel) + , alpha(A) + , k(K) + { + this->preamble(); + + mov(src, ptr[param + 0]); + mov(dst, ptr[param + 8]); + if (pk != prop_kind::forward_inference) + { + mov(scratch0, ptr[param + 16]); + mov(scratch1, ptr[param + 24]); + } + is_first = J.version == -1 || J.version == -2; + is_last = J.version == +1 || J.version == -2; + is_single = J.version == 3; + + W = J.W; + HW = J.W*J.H; + int LSB = use_h_parallelism ? W : HW; + + sub(t, FWD_RBC*BUFFER_BLOCK); + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + vbroadcastss(zalpha, xalpha); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + vbroadcastss(zk, xk); + + if (is_first || is_single) { + vxorps(xmm2, xmm2, xmm2); + for(int irb = 0; irb < FWD_RBC; irb++) { + vmovups(ptr[t + irb*BUFFER_BLOCK], xmm2); + } + } + if (is_last || is_single) { + vxorps(xmm2, xmm2, xmm2); + for(int irb = 0; irb < FWD_RBC; irb++) { + vmovups(ptr[t + irb*BUFFER_BLOCK + BUFFER_NEXT_OFFSET], + xmm2); + } + } + + int LSREST = LSB % FWD_RBC; + int LS = LSB - LSREST; + + Label lrn_loop; + + if (LS > 0) { + mov(hw, LS); + + L(lrn_loop); + { + compute_loop(FWD_RBC); + + add(src, FWD_RBC*vlen); + add(dst, FWD_RBC*vlen); + if (pk != prop_kind::forward_inference) + { + add(scratch0, FWD_RBC*vlen); + add(scratch1, FWD_RBC*vlen); + } + + for(int irb = 0; irb < FWD_RBC; irb++) + dec(hw); + cmp(hw, 0); + jne(lrn_loop, T_NEAR); + } + } + + compute_loop(LSREST); + + add(t, FWD_RBC*BUFFER_BLOCK); + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); + } +}; + +status_t jit_avx512_common_lrn_fwd_t::pd_t::init() { + using namespace prop_kind; + using namespace alg_kind; + + const memory_desc_wrapper data_d(src_md()); + bool ok = true + && mayiuse(avx512_common) + && is_fwd() + && !has_zero_dim_memory() + && everyone_is(data_type::f32, data_d.data_type()) + && data_d.ndims() == 4 + && data_d.dims()[1] % vsize == 0 + && attr()->has_default_values(); + if (!ok) return unimplemented; + + if (desc()->prop_kind == forward_training) { + dims_t ws_dims = { MB(), C(), H(), 2*W() }; + mkldnn_memory_desc_init_by_tag(&ws_md_, 4, ws_dims, data_type::f32, + format_tag::nChw16c); + } + + bool args_ok_across = true + && desc()->alg_kind == lrn_across_channels + && desc()->local_size == 5 + && desc()->lrn_beta == 0.75 + && data_d.matches_tag(format_tag::nChw16c); + + return args_ok_across ? success : unimplemented; +} + +jit_avx512_common_lrn_fwd_t::jit_avx512_common_lrn_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + , use_h_parallelism(0), ker_(nullptr), ker_first_(nullptr) + , ker_last_(nullptr) { + using namespace alg_kind; + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + const int ls = pd()->desc()->local_size; + const float alpha = pd()->desc()->lrn_alpha / ls; + const float k = pd()->desc()->lrn_k; + + auto pk = pd()->desc()->prop_kind; + + use_h_parallelism = H > 28 ? 1 : 0; + + if (C / vsize == 1) { + ker_ = new jit_avx512_common_lrn_kernel_f32(nChw16c_across(H, W, 3), pk, + use_h_parallelism, alpha, k); + } else { + ker_ = new jit_avx512_common_lrn_kernel_f32(nChw16c_across(H, W, 0), pk, + use_h_parallelism, alpha, k); + ker_first_ = new jit_avx512_common_lrn_kernel_f32( + nChw16c_across(H, W, -1), pk, use_h_parallelism, alpha, k); + ker_last_ = new jit_avx512_common_lrn_kernel_f32( + nChw16c_across(H, W, +1), pk, use_h_parallelism, alpha, k); + } +} + +jit_avx512_common_lrn_fwd_t::~jit_avx512_common_lrn_fwd_t() +{ delete ker_; delete ker_first_; delete ker_last_; } + +void jit_avx512_common_lrn_fwd_t::execute_forward(const exec_ctx_t &ctx) const +{ + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(data_t *, MKLDNN_ARG_WORKSPACE); + + const int N = pd()->MB(); + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + + parallel(0, [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + const int C16 = C / vsize; + const size_t work_amount = use_h_parallelism ? N*C16*H : N*C16; + + balance211(work_amount, nthr, ithr, start, end); + if (use_h_parallelism) { + int n{0}, c16{0}, h{0}; + nd_iterator_init(start, n, N, c16, C16, h, H); + for (size_t iwork = start; iwork < end; ++iwork) { + auto offset = n*C*H*W + c16*H*W*vsize + + h*W*vsize; + auto ws_offset0 = n*C*H*2*W + c16*H*2*W*vsize + + h*2*W*vsize; + auto ws_offset1 = ws_offset0 + W*vsize; + + jit_args_fwd_t args; + args.src = &src[offset]; + args.dst = &dst[offset]; + args.ws0 = &ws[ws_offset0]; + args.ws1 = &ws[ws_offset1]; + + if (C16 == 1) + (*ker_)(&args); + else if (c16 == 0) + (*ker_first_)(&args); + else if (c16 == C16 - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + nd_iterator_step(n, N, c16, C16, h, H); + } + } else { + int n{0}, c16{0}; + nd_iterator_init(start, n, N, c16, C16); + for (size_t iwork = start; iwork < end; ++iwork) { + auto offset = n*C*H*W + c16*H*W*vsize; + auto ws_offset0 = n*C*H*2*W + c16*H*2*W*vsize; + auto ws_offset1 = ws_offset0 + H*W*vsize; + + jit_args_fwd_t args; + args.src = &src[offset]; + args.dst = &dst[offset]; + args.ws0 = &ws[ws_offset0]; + args.ws1 = &ws[ws_offset1]; + + if (C16 == 1) + (*ker_)(&args); + else if (c16 == 0) + (*ker_first_)(&args); + else if (c16 == C16 - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + + nd_iterator_step(n, N, c16, C16); + } + } + }); +} + +struct jit_avx512_common_lrn_bwd_t::jit_avx512_common_lrn_kernel_f32: + public jit_generator { + int HW, W; + bool is_first; + bool is_last; + bool is_single; + + Reg64 src = rax; + Reg64 diffsrc = r8; + Reg64 diffdst = r9; + Reg64 workspace0 = rdx; + Reg64 workspace1 = rsi; + Reg64 imm_addr64 = rbx; + + Zmm znalphabeta = zmm0; + Xmm xnalphabeta = xmm0; + + Reg64 param = abi_param1; + Reg64 t = rsp; + Reg64 hw = r10; + + int xws1_prev = 1; + int xdiffdst_prev = 2; + int zws1 = 1; + + int zsrc = 1; + int zdiffdst = 5; + int zdiffsrc = 6; + + int xws1_next = 1; + int xdiffdst_next = 3; + + int za = 1; + int zb = 2; + int zd = 3; + int ze = 4; + int zws0 = 2; + + float nalphabeta; + + int use_h_parallelism; + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_common_lrn_kernel_f32) + + void (*ker)(jit_args_bwd_t *); + void operator()(jit_args_bwd_t *arg) { ker(arg); } + + enum { + prf0_offt = 1*BWD_RBC, + prf2_offt = 8*BWD_RBC + }; + + inline void compute_loop(int loop_size_param, int prefetchL1, + int prefetchL2) + { + // loop_size - param for IRB_LOOP macro + int loop_size = loop_size_param; + + auto xreg = [=](int irb, int i) { + return Xmm(irb*6 + i); + }; + + auto zreg = [=](int irb, int i) { + return Zmm(irb*6 + i); + }; + +// ---- prefetching ------------------------------------------- + if (!is_first && !is_single) { + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[workspace1 + (irb + prf0_offt + - 2 * HW) * vlen])); + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[diffdst + (irb + prf0_offt + - HW) * vlen])); + } + + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[src + (irb + prf0_offt)*vlen])); + if (prefetchL2) + IRB_LOOP(mic_prefetcht2(ptr[src + (irb + prf2_offt)*vlen])); + + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[workspace1 + (irb + prf0_offt)*vlen])); + + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[diffdst + (irb + prf0_offt)*vlen])); + + if (!is_last && !is_single) { + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[workspace1 + (irb + prf0_offt + + 2 * HW) * vlen])); + if (prefetchL2) + IRB_LOOP(mic_prefetcht2(ptr[workspace1 + (irb + prf2_offt + + 2 * HW) * vlen])); + + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[diffdst + (irb + prf0_offt + + HW) * vlen])); + if (prefetchL2) + IRB_LOOP(mic_prefetcht2(ptr[diffdst + (irb + prf2_offt + + HW) * vlen])); + } + if (prefetchL1) + IRB_LOOP(mic_prefetcht0(ptr[workspace0 + (irb + prf0_offt)*vlen])); + if (prefetchL2) + IRB_LOOP(mic_prefetcht2(ptr[workspace0 + (irb + prf2_offt)*vlen])); +// ----------------------------------------------------------- + + if (loop_size_param == 0) + return; + + if (!is_first && !is_single) { + IRB_LOOP(vmovups(xreg(irb, xws1_prev), ptr[workspace1 + (irb + - 2 * HW) * vlen + SRC_PREV_OFFSET])); + IRB_LOOP(vmovups(xreg(irb, xdiffdst_prev), ptr[diffdst + (irb + - HW) * vlen + SRC_PREV_OFFSET])); + IRB_LOOP(vmulps(xreg(irb, xdiffdst_prev), xreg(irb, xdiffdst_prev), + xreg(irb, xws1_prev))); + } + + IRB_LOOP(vmovups(zreg(irb, zws1), + EVEX_compress_addr(workspace1, irb*vlen))); + IRB_LOOP(vmovups(zreg(irb, zdiffdst), + EVEX_compress_addr(diffdst, irb*vlen))); + IRB_LOOP(vmulps(zreg(irb, zdiffsrc), zreg(irb, zdiffdst), + zreg(irb, zws1))); + + if (!is_last && !is_single) { + IRB_LOOP(vmovups(xreg(irb, xws1_next), ptr[workspace1 + (irb + + 2 * HW) * vlen])); + IRB_LOOP(vmovups(xreg(irb, xdiffdst_next), ptr[diffdst + (irb + + HW) * vlen])); + IRB_LOOP(vmulps(xreg(irb, xdiffdst_next), xreg(irb, xdiffdst_next), + xreg(irb, xws1_next))); + } + + if (!is_first && !is_single) { + IRB_LOOP(vmovups(ptr[t + irb*BUFFER_BLOCK], + xreg(irb, xdiffdst_prev))); + } + IRB_LOOP(vmovups(EVEX_compress_addr(t, irb*BUFFER_BLOCK + XMM_SIZE), + zreg(irb, zdiffsrc))); + if (!is_last && !is_single) { + IRB_LOOP(vmovups(ptr[t + irb*BUFFER_BLOCK + BUFFER_NEXT_OFFSET], + xreg(irb, xdiffdst_next))); + } + + IRB_LOOP(vmovups(zreg(irb, za), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE - 2*sizeof(float)))); + IRB_LOOP(vmovups(zreg(irb, zb), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE - 1*sizeof(float)))); + IRB_LOOP(vmovups(zreg(irb, zd), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE + 1*sizeof(float)))); + IRB_LOOP(vmovups(zreg(irb, ze), EVEX_compress_addr(t, irb*BUFFER_BLOCK + + XMM_SIZE + 2*sizeof(float)))); + IRB_LOOP(vaddps(zreg(irb, zdiffsrc), zreg(irb, zdiffsrc), + zreg(irb, za))); + assert(zsrc == za); + IRB_LOOP(vmovups(zreg(irb, zsrc), EVEX_compress_addr(src, irb*vlen))); + IRB_LOOP(vaddps(zreg(irb, zdiffsrc), zreg(irb, zdiffsrc), + zreg(irb, zb))); + IRB_LOOP(vaddps(zreg(irb, zdiffsrc), zreg(irb, zdiffsrc), + zreg(irb, zd))); + IRB_LOOP(vaddps(zreg(irb, zdiffsrc), zreg(irb, zdiffsrc), + zreg(irb, ze))); + IRB_LOOP(vmulps(zreg(irb, zsrc), zreg(irb, zsrc), znalphabeta)); + + IRB_LOOP(vmovups(zreg(irb, zws0), + EVEX_compress_addr(workspace0, irb*vlen))); + IRB_LOOP(vdivps(zreg(irb, zdiffdst), zreg(irb, zdiffdst), + zreg(irb, zws0))); + IRB_LOOP(vfmadd213ps(zreg(irb, zdiffsrc), zreg(irb, zsrc), + zreg(irb, zdiffdst))); + + Label unaligned_store, end_store; + test(diffsrc, vlen - 1); + jnz(unaligned_store, T_NEAR); + IRB_LOOP(uni_vmovntps(EVEX_compress_addr(diffsrc, irb*vlen), + zreg(irb, zdiffsrc))); + jmp(end_store, T_NEAR); + L(unaligned_store); { + IRB_LOOP(uni_vmovups(EVEX_compress_addr(diffsrc, irb*vlen), + zreg(irb, zdiffsrc))); + } + L(end_store); + } + + jit_avx512_common_lrn_kernel_f32( + const struct nChw16c_across &J, + float A, + float B, + int use_h_parallel, + void *code_ptr = nullptr, + size_t code_size = 1 * Xbyak::DEFAULT_MAX_CODE_SIZE) + : jit_generator(code_ptr, code_size) + , nalphabeta(-2*A*B) + , use_h_parallelism(use_h_parallel) + { + this->preamble(); + + mov(src, ptr[param + 0]); + mov(diffdst, ptr[param + 8]); + mov(workspace0, ptr[param + 16]); + mov(workspace1, ptr[param + 24]); + mov(diffsrc, ptr[param + 32]); + + W = J.W; + HW = J.H*J.W; + int LSB = this->use_h_parallelism ? W : HW; + + sub(t, BWD_RBC*BUFFER_BLOCK); + mov(imm_addr64, float2int(this->nalphabeta)); + movq(xnalphabeta, imm_addr64); + vbroadcastss(znalphabeta, xnalphabeta); + + is_first = J.version == -1 || J.version == -2; + is_last = J.version == +1 || J.version == +2; + is_single = J.version == 3; + + if (is_first || is_single) { + vxorps(xmm1, xmm1, xmm1); + for(int irb = 0; irb < BWD_RBC; irb++) { + vmovups(ptr[t + irb*BUFFER_BLOCK], xmm1); + } + } + if (is_last || is_single) { + vxorps(xmm1, xmm1, xmm1); + for(int irb = 0; irb < BWD_RBC; irb++) { + vmovups(ptr[t + irb*BUFFER_BLOCK + BUFFER_NEXT_OFFSET], xmm1); + } + } + + int LSREST = LSB % BWD_RBC; + int LS = LSB - LSREST; + + Label lrn_loop; + + if (LS > 0) { + mov(hw, LS); + + L(lrn_loop); + { + compute_loop(BWD_RBC, 1, 1); + + add(src, BWD_RBC*vlen); + add(diffsrc, BWD_RBC*vlen); + add(diffdst, BWD_RBC*vlen); + add(workspace0, BWD_RBC*vlen); + add(workspace1, BWD_RBC*vlen); + + for(int irb = 0; irb < BWD_RBC; irb++) + dec(hw); + cmp(hw, 0); + jne(lrn_loop, T_NEAR); + } + } + + compute_loop(LSREST, 1, this->use_h_parallelism ? 0 : 1); + + add(t, BWD_RBC*BUFFER_BLOCK); + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); + } + +}; + +status_t jit_avx512_common_lrn_bwd_t::pd_t::init() { + using namespace alg_kind; + + const memory_desc_wrapper data_d(src_md()); + bool ok = true + && mayiuse(avx512_common) + && !is_fwd() + && utils::everyone_is(data_type::f32, data_d.data_type()) + && !has_zero_dim_memory() + && data_d.ndims() == 4 + && data_d.dims()[1] % vsize == 0 + && attr()->has_default_values(); + if (!ok) return unimplemented; + + dims_t ws_dims = { MB(), C(), H(), 2*W() }; + mkldnn_memory_desc_init_by_tag(&ws_md_, 4, ws_dims, data_type::f32, + format_tag::nChw16c); + + if (!compare_ws(hint_fwd_pd_)) return unimplemented; + + bool args_ok_across = true + && desc()->alg_kind == lrn_across_channels + && desc()->local_size == 5 + && desc()->lrn_beta == 0.75 + && data_d.matches_tag(format_tag::nChw16c); + + return args_ok_across ? success : unimplemented; +} + +jit_avx512_common_lrn_bwd_t::jit_avx512_common_lrn_bwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + , use_h_parallelism(0), ker_(nullptr), ker_first_(nullptr) + , ker_last_(nullptr) { + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + const int ls = pd()->desc()->local_size; + const float alpha = pd()->desc()->lrn_alpha / ls; + const float beta = pd()->desc()->lrn_beta; + + use_h_parallelism = H > 28 ? 1 : 0; + + if (C / vsize == 1) { + ker_ = new jit_avx512_common_lrn_kernel_f32(nChw16c_across(H, W, 3), + alpha, beta, use_h_parallelism); + } else { + ker_ = new jit_avx512_common_lrn_kernel_f32(nChw16c_across(H, W, 0), + alpha, beta, use_h_parallelism); + ker_first_ = new jit_avx512_common_lrn_kernel_f32( + nChw16c_across(H, W, -1), alpha, beta, use_h_parallelism); + ker_last_ = new jit_avx512_common_lrn_kernel_f32( + nChw16c_across(H, W, +1), alpha, beta, use_h_parallelism); + } +} + +jit_avx512_common_lrn_bwd_t::~jit_avx512_common_lrn_bwd_t() +{ delete ker_; delete ker_first_; delete ker_last_; } + +void jit_avx512_common_lrn_bwd_t::execute_backward(const exec_ctx_t &ctx) const +{ + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto ws = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WORKSPACE); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const int N = pd()->MB(); + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + + parallel(0, [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + const int C16 = C / vsize; + const size_t work_amount = use_h_parallelism ? N*C16*H : N*C16; + + balance211(work_amount, nthr, ithr, start, end); + if (use_h_parallelism) { + int n{0}, c16{0}, h{0}; + nd_iterator_init(start, n, N, h, H, c16, C16); + for (size_t iwork = start; iwork < end; ++iwork) { + auto offset = n*C*H*W + c16*H*W*vsize + + h*W*vsize; + auto ws_offset0 = n*C*H*2*W + c16*H*2*W*vsize + + h*2*W*vsize; + auto ws_offset1 = ws_offset0 + W*vsize; + + jit_args_bwd_t args; + args.src = &src[offset]; + args.diff_dst = &diff_dst[offset]; + args.ws0 = &ws[ws_offset0]; + args.ws1 = &ws[ws_offset1]; + args.diff_src = &diff_src[offset]; + + if (C16 == 1) + (*ker_)(&args); + else if (c16 == 0) + (*ker_first_)(&args); + else if (c16 == C16 - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + nd_iterator_step(n, N, h, H, c16, C16); + } + } else { + int n{0}, c16{0}; + nd_iterator_init(start, n, N, c16, C16); + for (size_t iwork = start; iwork < end; ++iwork) { + auto offset = n*C*H*W + c16*H*W*vsize; + auto ws_offset0 = n*C*H*2*W + c16*H*2*W*vsize; + auto ws_offset1 = ws_offset0 + H*W*vsize; + + jit_args_bwd_t args; + args.src = &src[offset]; + args.diff_dst = &diff_dst[offset]; + args.ws0 = &ws[ws_offset0]; + args.ws1 = &ws[ws_offset1]; + args.diff_src = &diff_src[offset]; + + if (C16 == 1) + (*ker_)(&args); + else if (c16 == 0) + (*ker_first_)(&args); + else if (c16 == C16 - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + + nd_iterator_step(n, N, c16, C16); + } + } + }); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.hpp new file mode 100644 index 000000000000..37fbb9b3e54e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_common_lrn.hpp @@ -0,0 +1,96 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_COMMON_LRN_HPP +#define CPU_JIT_AVX512_COMMON_LRN_HPP + +#include "c_types_map.hpp" + +#include "cpu_isa_traits.hpp" +#include "cpu_lrn_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx512_common_lrn_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_lrn_fwd_pd_t { + using cpu_lrn_fwd_pd_t::cpu_lrn_fwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx512_common, ""), + jit_avx512_common_lrn_fwd_t); + + status_t init(); + }; + + jit_avx512_common_lrn_fwd_t(const pd_t *apd); + ~jit_avx512_common_lrn_fwd_t(); + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + int use_h_parallelism; + struct jit_avx512_common_lrn_kernel_f32; + jit_avx512_common_lrn_kernel_f32 *ker_, *ker_first_, *ker_last_; +}; + +struct jit_avx512_common_lrn_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_lrn_bwd_pd_t { + using cpu_lrn_bwd_pd_t::cpu_lrn_bwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", avx512_common, ""), + jit_avx512_common_lrn_bwd_t); + + status_t init(); + }; + + jit_avx512_common_lrn_bwd_t(const pd_t *apd); + ~jit_avx512_common_lrn_bwd_t(); + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + int use_h_parallelism; + struct jit_avx512_common_lrn_kernel_f32; + jit_avx512_common_lrn_kernel_f32 *ker_, *ker_first_, *ker_last_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp new file mode 100644 index 000000000000..c58d3fa0a68b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp @@ -0,0 +1,1103 @@ +/******************************************************************************* + * Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_core_fp32_wino_conv_2x3.hpp" +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_kind; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +/// SRC TRANSFORMS ///////////////////////////////////////////////////////////// +struct jit_avx512_core_fp32_wino_conv_2x3_src_trans_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS( + jit_avx512_core_fp32_wino_conv_2x3_src_trans_t) + + jit_conv_conf_2x3_wino_t jcp; + + struct call_params_t { + const void *src; + const void *wino_src; + const void *v_y_masks; + const void *v_x_masks; + }; + void (*ker_)(const call_params_t *); + + jit_avx512_core_fp32_wino_conv_2x3_src_trans_t( + jit_conv_conf_2x3_wino_t ajcp, const primitive_attr_t &attr) + : jcp(ajcp) { + generate(); + ker_ = + reinterpret_cast(const_cast(getCode())); + } + + void generate(); + + Zmm vreg_inp(int i) { + assert(i < jcp.alpha * jcp.alpha); + return Zmm(31 - i); + } + + Zmm vreg_tmp(int i) { + assert(i < jcp.alpha * jcp.alpha); + return Zmm(15 - i); + } + + Zmm vreg_out(int i) { + assert(i < jcp.alpha * jcp.alpha); + return Zmm(31 - i); + } + + Opmask y_mask = Opmask(1); + Opmask r_mask = Opmask(2); + Opmask x_mask(int id) { + assert (id < 4); + return Opmask(3 + id); + } + + Reg64 reg_ptr_v_y_masks = r12; + Reg64 reg_ptr_v_x_masks = r11; + + Reg64 reg_aux_ptr_src = r10; + Reg64 reg_aux_ptr_dst = r9; + + Reg64 reg_ic_block = r8; + +}; + +void jit_avx512_core_fp32_wino_conv_2x3_src_trans_t::generate() { + Label ic_block_label; + + const int load_block = 16; + int out_offset = 0, inp_offset = 0; + preamble(); + +#define READ_PARAM(reg, field) \ + mov(reg, ptr[abi_param1 + offsetof(call_params_t, field)]) + READ_PARAM(reg_aux_ptr_src, src); + READ_PARAM(reg_aux_ptr_dst, wino_src); + READ_PARAM(reg_ptr_v_y_masks, v_y_masks); + READ_PARAM(reg_ptr_v_x_masks, v_x_masks); +#undef READ_PARAM + + for (int i = 0; i < jcp.alpha; i++) { + kmovw(x_mask(i), ptr[reg_ptr_v_x_masks + sizeof(int16_t) * i]); + } + mov(reg_ic_block, jcp.ic / load_block); + L(ic_block_label); + { + for (int y = 0; y < jcp.alpha; y++) { + kmovw(y_mask, ptr[reg_ptr_v_y_masks + sizeof(int16_t) * y]); + for (int x = 0; x < jcp.alpha; x++) { + Zmm zmm = vreg_inp(y * jcp.alpha + x); + + vxorps(zmm, zmm, zmm); + kandw(r_mask, y_mask, x_mask(x)); + inp_offset = sizeof(float) + * ((-jcp.t_pad + y) * jcp.iw * load_block + + (-jcp.l_pad + x) * load_block); + vmovups(zmm | r_mask, + EVEX_compress_addr(reg_aux_ptr_src, inp_offset)); + } + } + for (int y = 0; y < jcp.alpha; y++) { + vsubps(vreg_tmp(y * jcp.alpha + 0), vreg_inp(y * jcp.alpha + 0), + vreg_inp(y * jcp.alpha + 2)); + vaddps(vreg_tmp(y * jcp.alpha + 1), vreg_inp(y * jcp.alpha + 1), + vreg_inp(y * jcp.alpha + 2)); + vsubps(vreg_tmp(y * jcp.alpha + 2), vreg_inp(y * jcp.alpha + 2), + vreg_inp(y * jcp.alpha + 1)); + vsubps(vreg_tmp(y * jcp.alpha + 3), vreg_inp(y * jcp.alpha + 1), + vreg_inp(y * jcp.alpha + 3)); + } + for (int x = 0; x < jcp.alpha; x++) { + vsubps(vreg_out(x + 0 * jcp.alpha), vreg_tmp(x + jcp.alpha * 0), + vreg_tmp(x + jcp.alpha * 2)); + vaddps(vreg_out(x + 1 * jcp.alpha), vreg_tmp(x + jcp.alpha * 1), + vreg_tmp(x + jcp.alpha * 2)); + vsubps(vreg_out(x + 2 * jcp.alpha), vreg_tmp(x + jcp.alpha * 2), + vreg_tmp(x + jcp.alpha * 1)); + vsubps(vreg_out(x + 3 * jcp.alpha), vreg_tmp(x + jcp.alpha * 1), + vreg_tmp(x + jcp.alpha * 3)); + } + + for (int i = 0; i < 16; i++) { + out_offset = sizeof(float) * (jcp.inp_stride * i); + vmovups(EVEX_compress_addr(reg_aux_ptr_dst, out_offset), + vreg_out(i)); + } + + add(reg_aux_ptr_src, sizeof(float) * jcp.ih * jcp.iw * load_block); + add(reg_aux_ptr_dst, sizeof(float) * load_block); + } + dec(reg_ic_block); + cmp(reg_ic_block, 0); + jg(ic_block_label, T_NEAR); + postamble(); +} + +/// DST TRANSFORMS ///////////////////////////////////////////////////////////// +struct jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS( + jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t) + + jit_conv_conf_2x3_wino_t jcp; + const primitive_attr_t &attr_; + + struct call_params_t { + const void *wino_dst; + const void *dst; + const void *v_y_masks; + const void *v_x_masks; + + const void *bias; + const void *scales; + }; + void (*ker_)(const call_params_t *); + + jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t( + jit_conv_conf_2x3_wino_t ajcp, const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr) { + generate(); + ker_ = reinterpret_cast( + const_cast(getCode())); + } + + void generate(); + bool maybe_relu(int position); + + Zmm vreg_inp(int i) { // 16 + assert(i < jcp.alpha * jcp.alpha); + return Zmm(31 - i); + } + + Zmm vreg_stg(int id) { // 8 + const int id_reg_stg = jcp.alpha * jcp.alpha + id; + assert(id_reg_stg < jcp.alpha * jcp.alpha + 8); + return Zmm(31 - id_reg_stg); + } + + Zmm vreg_out(int id) { // 4 + const int id_reg_out = jcp.alpha * jcp.alpha + 8 + id; + assert(id_reg_out < jcp.alpha * jcp.alpha + 12); + return Zmm(31 - id_reg_out); + } + + Zmm vreg_tmp(int id) { // 2 + const int id_reg_tmp = jcp.alpha * jcp.alpha + 12 + id; + assert(id_reg_tmp < jcp.alpha * jcp.alpha + 14); + return Zmm(31 - id_reg_tmp); + } + + Zmm vreg_zero = Zmm(0); + Zmm vreg_prev_dst = Zmm(0); + Zmm vreg_bias = Zmm(2); + + Opmask y_mask = Opmask(1); + Opmask r_mask = Opmask(2); + Opmask x_mask(int id) { + assert (id < 4); + return Opmask(3 + id); + } + + Reg64 reg_ptr_v_y_masks = r12; + Reg64 reg_ptr_v_x_masks = r11; + + Reg64 reg_aux_ptr_src = r10; + Reg64 reg_aux_ptr_dst = r9; + + Reg64 reg_oc_block = r8; + + Reg64 reg_ptr_bias = rbx; + Reg64 reg_ptr_scales = abi_not_param1; + Reg64 reg_ptr_sum_scale = rdx; +}; + +bool jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t::maybe_relu(int position) { + using namespace primitive_kind; + const auto &p = attr_.post_ops_; + + if (position == 0) { + /* relu before sum */ + return false + || p.contain(eltwise, 0); + } else if (position == 1) { + /* relu after sum */ + const int sum_idx = p.contain(sum, 0) + ? 0 : (p.contain(sum, 1) ? 1 : -1); + if (sum_idx == -1) + return false; + + return false + || p.contain(eltwise, sum_idx + 1); + } + + return false; +} + +void jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t::generate() { + Label oc_block_label; + + const int load_block = 16; + + auto loop_body = [=]() { + const auto &p = attr_.post_ops_; + const int sum_idx = p.find(primitive_kind::sum); + const float *p_sum_scale = (sum_idx != -1) + ? &p.entry_[sum_idx].sum.scale + : nullptr; + if (p_sum_scale && *p_sum_scale != 1.f) + mov(reg_ptr_sum_scale, (size_t)p_sum_scale); + + for (int i = 0; i < 16; i++) { + int internal_offset = sizeof(float) * jcp.out_stride * i; + vmovups(vreg_inp(i), + EVEX_compress_addr(reg_aux_ptr_src, internal_offset)); + } + for (int y = 0; y < jcp.alpha; y++) { + vaddps(vreg_tmp(0), vreg_inp(y * 4 + 0), vreg_inp(y * 4 + 1)); + vaddps(vreg_stg(y * 2), vreg_tmp(0), vreg_inp(y * 4 + 2)); + + vsubps(vreg_tmp(1), vreg_inp(y * 4 + 1), vreg_inp(y * 4 + 2)); + vsubps(vreg_stg(y * 2+1), vreg_tmp(1), vreg_inp(y * 4 + 3)); + } + for (int x = 0; x < jcp.m; x++) { + vaddps(vreg_tmp(0), vreg_stg(x), vreg_stg(x+2 * 1)); + vaddps(vreg_out(x), vreg_tmp(0), vreg_stg(x+2 * 2)); + + vsubps(vreg_tmp(1), vreg_stg(x+2 * 1), vreg_stg(x+2 * 2)); + vsubps(vreg_out(x+2), vreg_tmp(1), vreg_stg(x+2 * 3)); + } + + + if (jcp.with_bias) { + auto bias_addr = ptr [ reg_ptr_bias ]; + vmovups(vreg_bias, bias_addr); + } + for (int y = 0; y < jcp.m; y++) { + kmovw(y_mask, ptr[ reg_ptr_v_y_masks + sizeof(int16_t) * y ]); + for (int x = 0; x < jcp.m; x++) { + kandw(r_mask, y_mask, x_mask(x)); + + int i = y * jcp.m + x; + int offset = sizeof(float) * + (y * jcp.ow * jcp.oc_block + x * jcp.oc_block); + Address addr = EVEX_compress_addr(reg_aux_ptr_dst, offset); + + Zmm zmm = vreg_out(i); + if (jcp.with_bias) + vaddps(zmm, zmm, vreg_bias); + vmulps(zmm, zmm, ptr [reg_ptr_scales]); + + if (maybe_relu(0)) { + vxorps(vreg_zero, vreg_zero, vreg_zero); + vmaxps(zmm, vreg_zero, zmm); + } + if (p_sum_scale) { // post_op: sum + vxorps(vreg_prev_dst, vreg_prev_dst, vreg_prev_dst); + vmovups(vreg_prev_dst | r_mask, addr); + if (*p_sum_scale == 1.f) + vaddps(zmm, vreg_prev_dst); + else + vfmadd231ps(zmm, vreg_prev_dst, + zword_b[reg_ptr_sum_scale]); + } + if (maybe_relu(1)) { + vxorps(vreg_zero, vreg_zero, vreg_zero); + vmaxps(zmm, vreg_zero, zmm); + } + + vmovups(addr, zmm | r_mask); + } + } + }; + + preamble(); + +#define READ_PARAM(reg, field) \ + mov(reg, ptr[abi_param1 + offsetof(call_params_t, field)]) + READ_PARAM(reg_aux_ptr_src, wino_dst); + READ_PARAM(reg_aux_ptr_dst, dst); + READ_PARAM(reg_ptr_v_y_masks, v_y_masks); + READ_PARAM(reg_ptr_v_x_masks, v_x_masks); + READ_PARAM(reg_ptr_bias, bias); + READ_PARAM(reg_ptr_scales, scales); +#undef READ_PARAM + + for (int i = 0; i < jcp.alpha * jcp.alpha; i++) + vxorps(vreg_inp(i), vreg_inp(i), vreg_inp(i)); + + for (int i = 0; i < jcp.alpha; i++) + kmovw(x_mask(i), ptr[reg_ptr_v_x_masks + sizeof(int16_t) * i]); + + int oc_blocks = 1; + oc_blocks = jcp.oc / load_block; + mov(reg_oc_block, oc_blocks); + L(oc_block_label); + { + loop_body(); + add(reg_aux_ptr_src, sizeof(float) * load_block); + add(reg_aux_ptr_dst, sizeof(float) * jcp.oh * jcp.ow * load_block); + + add(reg_ptr_scales, jcp.is_oc_scale * sizeof(float) * load_block); + add(reg_ptr_bias, jcp.typesize_bia * load_block); + } + dec(reg_oc_block); + cmp(reg_oc_block, 0); + jg(oc_block_label, T_NEAR); + + sub(reg_ptr_scales, jcp.is_oc_scale * sizeof(float) * load_block); + sub(reg_ptr_bias, oc_blocks * jcp.typesize_bia * load_block); + + postamble(); + +} + +/// GEMM kernel //////////////////////////////////////////////////////////////// +struct jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t) + jit_conv_conf_2x3_wino_t jcp; + + struct call_params_t { + const void *src; + const void *dst; + const void *wei; + const void *dst_b; + }; + void (*ker_)(const call_params_t *); + + void generate(); + static bool post_ops_ok(jit_conv_conf_2x3_wino_t &jcp, + const primitive_attr_t &attr); + + jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t( + jit_conv_conf_2x3_wino_t ajcp, const primitive_attr_t &attr) + : jcp(ajcp) { + generate(); + ker_ = reinterpret_cast( + const_cast(getCode())); + } + + static status_t init_conf( + jit_conv_conf_2x3_wino_t &jcp, const convolution_desc_t &cd, + memory_desc_t &src_md, memory_desc_t &weights_md, + memory_desc_t &dst_md, memory_desc_t &bias_md, + const primitive_attr_t &attr, + memory_desc_t& expect_wei_md); + + Zmm vreg_out(int n, int m) { + const int id_reg_out = n * jcp.m_block + m; + assert(id_reg_out < jcp.n2_block * jcp.m_block); + return Zmm(31 - id_reg_out); + } + Zmm vreg_wei(int i) { + assert (31 - jcp.n2_block * jcp.m_block - i > 1); + return Zmm(31 - jcp.n2_block * jcp.m_block - i); + } + + Zmm vreg_src = Zmm(0); + Zmm vreg_one = Zmm(1); + Zmm vreg_tmp = Zmm(2); + + Reg64 reg_ptr_src = r15; + + Reg64 reg_aux_dst = r12; + Reg64 reg_aux_dst2 = r11; + Reg64 reg_aux_wei = r10; + Reg64 reg_aux_wei2 = r9; + Reg64 reg_aux_src = r8; + Reg64 reg_aux_src2 = rax; + + Reg64 reg_mb = rbx; + Reg64 reg_nnb = rdx; + Reg64 reg_K = rsi; + +}; + +bool jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t::post_ops_ok( + jit_conv_conf_2x3_wino_t &jcp, const primitive_attr_t &attr) { + using namespace primitive_kind; + const auto &p = attr.post_ops_; + + auto is_relu = [&](int idx) { return p.entry_[idx].is_relu(); }; + + switch (p.len_) { + case 0: return true; + case 1: return is_relu(0) || p.contain(sum, 0); + case 2: return (p.contain(sum, 0) && is_relu(1)) || + (p.contain(sum, 1) && is_relu(0)); + case 3: return is_relu(0) && p.contain(sum, 1) && is_relu(2); + default: return false; + } + + return false; +} + +void jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t::generate() { + Label nnb_loop_label, K_loop_label, mb_loop_label; + + preamble(); +#define READ_PARAM(reg, field) \ + mov(reg, ptr[abi_param1 + offsetof(call_params_t, field)]) + READ_PARAM(reg_ptr_src, src); + READ_PARAM(reg_aux_dst, dst); + READ_PARAM(reg_aux_wei, wei); +#undef READ_PARAM + + if (!jcp.small_mb) { + mov(reg_nnb, jcp.n_chunks); + L(nnb_loop_label); + } + mov(reg_aux_dst2, reg_aux_dst); + mov(reg_aux_src, reg_ptr_src); + mov(reg_mb, jcp.M / jcp.m_block); + L(mb_loop_label); + { + int nb2 = 0; + for (nb2 = 0; nb2 < jcp.n2_block; nb2++) { + for (int m = 0; m < jcp.m_block; m++) { + vxorps(vreg_out(nb2, m), vreg_out(nb2, m), vreg_out(nb2, m)); + } + } + mov(reg_aux_src2, reg_aux_src); + mov(reg_aux_wei2, reg_aux_wei); + + mov(reg_K, jcp.k_chunks); + L(K_loop_label); { + int wei_offset = 0; + for (int _i = 0; _i < jcp.k2_block; _i++) { + for (int nb2 = 0; nb2 < jcp.n2_block; nb2++) { + if (jcp.small_mb) { + int wei_offset = sizeof(float) + * ((nb2 * jcp.nb_ic * jcp.ic_block + * jcp.oc_block) + + _i * jcp.oc_block); + vmovups(vreg_wei(nb2), + EVEX_compress_addr(reg_aux_wei2, wei_offset)); + } else { + vmovups(vreg_wei(nb2), + EVEX_compress_addr(reg_aux_wei2, + sizeof(float) * wei_offset)); + wei_offset += jcp.oc_block; + } + } + for (int m = 0; m < jcp.m_block; m++) { + int inp_offset = sizeof(float) * (m * jcp.K + _i); + if (jcp.n2_block > 1) { + vbroadcastss(vreg_src, + EVEX_compress_addr(reg_aux_src2, inp_offset)); + for (int nb2 = 0; nb2 < jcp.n2_block; nb2++) + vfmadd231ps(vreg_out(nb2, m), vreg_wei(nb2), + vreg_src); + } else { + vfmadd231ps(vreg_out(0, m), vreg_wei(0), + EVEX_compress_addr(reg_aux_src2, inp_offset, true)); + } + } + } + add(reg_aux_src2, sizeof(float) * jcp.ic_block); + if (jcp.small_mb) + add(reg_aux_wei2, sizeof(float) * jcp.oc_block * jcp.ic_block); + else + add(reg_aux_wei2, + sizeof(float) * jcp.k2_block * jcp.n2_block + * jcp.oc_block); + } + dec(reg_K); + cmp(reg_K, 0); + jg(K_loop_label, T_NEAR); + + for (int m = 0; m < jcp.m_block; m++) { + int nb2 = 0; + for (nb2 = 0; nb2 < jcp.n2_block; nb2++) { + int offset = sizeof(float) * + (m * jcp.N + nb2 * jcp.oc_block); + vmovups(EVEX_compress_addr(reg_aux_dst2,offset), + vreg_out(nb2, m)); + } + } + add(reg_aux_src, sizeof(float) * jcp.m_block * jcp.K); + add(reg_aux_dst2, sizeof(float) * jcp.m_block * jcp.N); + } + dec(reg_mb); + cmp(reg_mb, 0); + jg(mb_loop_label, T_NEAR); + + if (!jcp.small_mb) { + add(reg_aux_dst, sizeof(float) * jcp.n2_block * jcp.oc_block); + add(reg_aux_wei, + sizeof(float) * jcp.k_chunks * jcp.ic_block * jcp.n2_block + * jcp.oc_block); + + dec(reg_nnb); + cmp(reg_nnb, 0); + jg(nnb_loop_label, T_NEAR); + } + postamble(); +} + +namespace { +bool is_winograd_faster_than_direct(const jit_conv_conf_2x3_wino_t &jcp) { + return jcp.mb >= 4; +} +} + +status_t jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t ::init_conf( + jit_conv_conf_2x3_wino_t &jcp, const convolution_desc_t &cd, + memory_desc_t &src_md, memory_desc_t &wei_md, + memory_desc_t &dst_md, memory_desc_t &bias_md, + const primitive_attr_t &attr, memory_desc_t &expect_wei_md) { + const memory_desc_wrapper src_d(&src_md); + const memory_desc_wrapper wei_d(&wei_md); + const memory_desc_wrapper dst_d(&dst_md); + const memory_desc_wrapper bias_d(&bias_md); + + const bool with_groups = wei_d.ndims() == src_d.ndims() + 1; + + jcp.nthr = mkldnn_get_max_threads(); + + jcp.ngroups = with_groups ? wei_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = dst_d.dims()[2]; + jcp.ow = dst_d.dims()[3]; + jcp.kh = wei_d.dims()[with_groups + 2]; + jcp.kw = wei_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.b_pad = cd.padding[1][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.r_pad = cd.padding[1][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + jcp.m = 2; + jcp.r = 3; + jcp.alpha = jcp.m + jcp.r - 1; + int simdw = 16; + + format_tag_t dat_tag = format_tag::nChw16c; + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + if (jcp.src_tag != dat_tag) return status::unimplemented; + if (jcp.dst_tag != dat_tag) return status::unimplemented; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + bool ok_to_pad_channels = jcp.ngroups == 1; + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simdw); + jcp.ic = rnd_up(jcp.ic, simdw); + } + + jcp.ver = ver_avx512_core; + if (!(mayiuse(avx512_core))) + return status::unimplemented; + + if (!IMPLICATION(cd.alg_kind == alg_kind::convolution_auto, + is_winograd_faster_than_direct(jcp))) + return status::unimplemented; + + if (src_d.data_type() != data_type::f32) + return status::unimplemented; + if (wei_d.data_type() != data_type::f32) + return status::unimplemented; + if (dst_d.data_type() != data_type::f32) + return status::unimplemented; + + jcp.ic_block = simdw; + jcp.oc_block = simdw; + + bool ok = true && jcp.kh == 3 && jcp.kw == 3 && jcp.ngroups == 1 + && jcp.oc % jcp.oc_block == 0 && jcp.ic % jcp.ic_block == 0 + && jcp.stride_h == 1 && jcp.stride_w == 1 && jcp.dilate_h == 0 + && jcp.dilate_w == 0 && jcp.t_pad == jcp.b_pad + && jcp.l_pad == jcp.r_pad && jcp.t_pad < 2 && jcp.t_pad >= 0 + && jcp.l_pad < 2 && jcp.l_pad >= 0; + if (!ok) + return status::unimplemented; + + const int L2_cap = get_cache_size(2, true) / sizeof(float); + const int L3_capacity = get_cache_size(3, false) / sizeof(float); + int a = jcp.alpha; + int aa = a * a; + int mb = jcp.mb; + int ic = jcp.ic; + int oc = jcp.oc; + int ih = jcp.ih; + int iw = jcp.iw; + auto wei_sz = (float)aa * ic * oc; + auto inp_sz = (float)mb * ih * iw * ic; + auto sp_sz = (float)mb * ih * iw; + + /* Heuristics here. Numbers '28','196' is an observation from data. */ + if (wei_sz / inp_sz > 5) + jcp.small_mb = true; + else + jcp.small_mb = false; + + if (mb > nstl::min(jcp.nthr, 28) + || (!jcp.small_mb + && (wei_sz >= 0.9f * L2_cap + || inp_sz > L2_cap * jcp.nthr + L3_capacity)) + || (jcp.small_mb && sp_sz > 196)) + return status::unimplemented; + + jcp.bia_dt = jcp.with_bias ? cd.bias_desc.data_type : data_type::undef; + jcp.dst_dt = cd.dst_desc.data_type; + + jcp.typesize_bia + = jcp.with_bias ? types::data_type_size(bias_d.data_type()) : 0; + + jcp.nb_oc = jcp.oc / jcp.oc_block; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + const int skx_free_regs = 30; + + auto find_m_n2_blocks = [=](int xb, int yb, int &M, int &m_block, + int &n2_block, float ®_eff) { + M = (xb * yb) / jcp.alpha; + int max_m_block = m_block = nstl::min(M, skx_free_regs); + int max_n2_block = n2_block = nstl::min(jcp.nb_oc, skx_free_regs); + reg_eff = 0; + for (int im = max_m_block; im > 0; im--) { + for (int in2 = max_n2_block; in2 > 0; in2--) { + int used_regs = in2 * im + in2; + float cur_reg_eff = ((float)in2 * im) / (im + in2) / 2.5f; + if (M % im || jcp.nb_oc % in2 || used_regs > skx_free_regs + || cur_reg_eff <= reg_eff) + continue; + reg_eff = cur_reg_eff; + m_block = im; + n2_block = in2; + } + } + }; + + int oh = jcp.oh; + int ow = jcp.ow; + int nb_oc = jcp.nb_oc; + int Z = ic + oc; + int Y = ic * oc; + const int L3_cap_per_core = get_cache_size(3, true) / sizeof(float); + + /* Selecting xb and yb blocking */ + int min_yb = jcp.alpha; + int min_xb = jcp.alpha; + int max_yb = nstl::max(min_yb, rnd_up(ih, 2)); + int max_xb = nstl::max(min_xb, rnd_up(iw, 2)); + float best_eff = 0.f; + for (int ix = max_xb; ix >= min_xb; ix -= 2) { + if (rnd_up(ow, ix) < iw - 2) + continue; + for (int iy = max_yb; iy >= min_yb; iy -= 2) { + if (rnd_up(oh, iy) < ih - 2) + continue; + int ex_y = rnd_up(oh, iy); + int ex_x = rnd_up(ow, ix); + float work_eff = (float)(ih * iw) / (ex_y * ex_x); + + int M, m_block, n2_b; + float reg_eff, thr_eff, par_eff, mem_eff, req_mem; + + find_m_n2_blocks(ix, iy, M, m_block, n2_b, reg_eff); + + /* outer parallelization */ + int nblocks = mb * div_up(ih, iy) * div_up(iw, ix); + thr_eff = (float)nblocks / rnd_up(nblocks, jcp.nthr); + + mem_eff = 1.f; + req_mem = (((float)ix + 2) * (iy + 2) + aa * M) * Z + aa * Y; + if (req_mem > L2_cap / 2) { + if (req_mem > ((L2_cap + L3_cap_per_core) * 4) / 7) + mem_eff /= (n2_b + 1) / 2.f; + else + mem_eff /= (n2_b + 1) / 3.f; + } + + float outer_eff = thr_eff + work_eff + reg_eff + mem_eff; + + /* inner parallelization */ + int bsz = iy * ix / a; + int gemmw = aa * (nb_oc / n2_b); + int bsz_r = rnd_up(bsz, jcp.nthr); + int gemmw_r = rnd_up(gemmw, jcp.nthr); + thr_eff = ((float)Z * bsz / bsz_r + Y * gemmw / gemmw_r) / (Z + Y); + + req_mem = (float)ix * iy * (ic + simdw * n2_b) + simdw * n2_b * ic; + mem_eff = nstl::min(1.f, L2_cap / req_mem); + int M_per_thr = nstl::max(2, div_up(aa, jcp.nthr)); + int oc_per_thr = + nstl::min(oc, div_up(aa * (nb_oc / n2_b), jcp.nthr)); + req_mem = (float)aa * oc_per_thr * ic + M_per_thr * M * Z; + if (req_mem > L2_cap) + mem_eff = 0.1f; + par_eff = 1 / (2.f * nblocks); + + float inner_eff = thr_eff + work_eff + mem_eff + par_eff; + + float eff = jcp.small_mb ? inner_eff : outer_eff; + if (eff > best_eff) { + best_eff = eff; + jcp.yb = iy; + jcp.xb = ix; + jcp.M = M; + jcp.m_block = m_block; + jcp.n2_block = n2_b; + } + } + } + + assert(jcp.xb % 2 == 0 && jcp.yb % 2 == 0); + + jcp.inp_stride = jcp.M * jcp.ic; + jcp.out_stride = jcp.M * jcp.oc; + jcp.wei_stride = jcp.ic * jcp.oc; + jcp.bia_stride = jcp.oc; + + jcp.N = jcp.oc; + jcp.K = jcp.ic; + + jcp.n_block = jcp.oc_block; + jcp.k_block = jcp.ic_block; + + assert(jcp.M % jcp.m_block == 0); + assert(jcp.nb_oc % jcp.n2_block == 0); + + jcp.n_chunks = jcp.nb_oc / jcp.n2_block; + jcp.k2_block = jcp.ic_block; + jcp.k_chunks = jcp.K / jcp.k2_block; + + const auto &oscales = attr.output_scales_; + jcp.is_oc_scale = oscales.mask_ == 1 << 1; + assert(IMPLICATION(!jcp.is_oc_scale, oscales.mask_ == 0)); + + /* re-create weights primitive descriptor + and set weights wino_blocking */ + expect_wei_md.format_kind = format_kind::wino; + expect_wei_md.data_type = data_type::f32; + mkldnn_wino_desc_t &wd = expect_wei_md.format_desc.wino_desc; + wd.wino_format + = jcp.small_mb ? mkldnn_wino_wei_aaOio : mkldnn_wino_wei_aaOBiOo; + wd.r = jcp.r; + wd.alpha = jcp.alpha; + wd.ic = jcp.ic; + wd.oc = jcp.oc; + wd.ic_block = jcp.ic_block; + wd.oc_block = jcp.oc_block; + wd.oc2_block = jcp.n2_block; + wd.ic2_block = 1; + wd.adj_scale = 1.f; + size_t max_size = sizeof(float) * jcp.alpha * jcp.alpha * jcp.ic * jcp.oc; + wd.size = max_size; + + return status::success; +} +//////////////////////////////////////////////////////////////////////////////// + +status_t jit_avx512_core_fp32_wino_conv_2x3_fwd_t + ::pd_t::jit_conf(memory_desc_t& expect_wei_md) { + return jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t::init_conf( + jcp_, *this->desc(), this->src_md_, this->weights_md_, + this->dst_md_,this->bias_md_, *this->attr(), expect_wei_md); +} + +jit_avx512_core_fp32_wino_conv_2x3_fwd_t:: + jit_avx512_core_fp32_wino_conv_2x3_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) +{ + kernel_ = new jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t( + pd()->jcp_, *pd()->attr()); + src_trans_ = new jit_avx512_core_fp32_wino_conv_2x3_src_trans_t( + pd()->jcp_, *pd()->attr()); + dst_trans_ = new jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t( + pd()->jcp_, *pd()->attr()); +} + +jit_avx512_core_fp32_wino_conv_2x3_fwd_t + ::~jit_avx512_core_fp32_wino_conv_2x3_fwd_t() { + delete kernel_; + delete src_trans_; + delete dst_trans_; +} + +void jit_avx512_core_fp32_wino_conv_2x3_fwd_t::execute_forward_mbN( + const float *src, const float *wei, const float *bia, float *dst, + const memory_tracking::grantor_t &scratchpad) const +{ + const auto &jcp = kernel_->jcp; + const auto &oscales = pd()->attr()->output_scales_; + + const size_t wino_size_offset = + (size_t)(pd()->jcp_.yb / 2) * (pd()->jcp_.xb / 2) + (pd()->jcp_.xb); + const size_t size_wino_src = wino_size_offset * pd()->jcp_.ic * 16; + const size_t size_wino_dst = wino_size_offset * pd()->jcp_.oc * 16; + + if (pd()->wants_padded_bias()) { + auto padded_bias = scratchpad.get(key_conv_padded_bias); + utils::array_copy(padded_bias, bia, jcp.oc_without_padding); + utils::array_set(padded_bias + jcp.oc_without_padding, 0.f, + jcp.oc - jcp.oc_without_padding); + bia = padded_bias; + } + + auto ptr_V = scratchpad.get(key_wino_V); + auto ptr_M = scratchpad.get(key_wino_M); + + parallel_nd(jcp.mb, div_up(jcp.oh,jcp.yb), div_up(jcp.ow, jcp.xb), + [&](int mb, int tile_y_b, int tile_x_b) { + int tile_y = tile_y_b * jcp.yb; + int tile_x = tile_x_b * jcp.xb; + + int ithr = mkldnn_get_thread_num(); + auto wino_src = ptr_V + size_wino_src * ithr; + auto wino_dst = ptr_M + size_wino_dst * ithr; + + auto src_trans_p = + jit_avx512_core_fp32_wino_conv_2x3_src_trans_t + ::call_params_t(); + auto dst_trans_p = + jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t + ::call_params_t(); + auto gemm_p = jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t :: + call_params_t(); + + /* transformation of input tensor to winograd domain */ + for (int y_in_block = 0; y_in_block < jcp.yb; y_in_block += 2) { + for (int x_in_block = 0; x_in_block < jcp.xb; + x_in_block += 2) { + + unsigned short v_y_masks[4], v_x_masks[4]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (y_in_block / 2) * (jcp.xb / 2) + + (x_in_block / 2); + + int v_ys = nstl::max(0, jcp.t_pad - y); + int v_ye = nstl::min(jcp.alpha, + nstl::max(0, jcp.ih + jcp.t_pad - y)); + + int v_xs = nstl::max(0, jcp.l_pad - x); + int v_xe = nstl::min(jcp.alpha, + nstl::max(0, jcp.iw + jcp.l_pad - x)); + +#pragma unroll(4) + for (int i = 0; i < jcp.alpha; i++) { + v_y_masks[i] = (i < v_ys || i >= v_ye) ? 0 : 0xffff; + v_x_masks[i] = (i < v_xs || i >= v_xe) ? 0 : 0xffff; + } + auto local_s = src + + mb * jcp.nb_ic * jcp.ih * jcp.iw + * jcp.ic_block + + y * jcp.iw * jcp.ic_block + x * jcp.ic_block; + auto local_w = wino_src + m * jcp.ic; + + src_trans_p.src = local_s; + src_trans_p.wino_src = local_w; + src_trans_p.v_y_masks = v_y_masks; + src_trans_p.v_x_masks = v_x_masks; + + src_trans_->ker_(&src_trans_p); + } + } + /* gemms */ + for (int tile_ij = 0; tile_ij < 16; tile_ij++) { + int offset = (tile_ij + ithr) % 16; + gemm_p.src = wino_src + jcp.inp_stride * offset; + gemm_p.dst = wino_dst + jcp.out_stride * offset; + gemm_p.wei = wei + jcp.wei_stride * offset; + + kernel_->ker_(&gemm_p); + } + + /* transformation from winograd domain to output tensor */ + for (int y_in_block = 0; y_in_block < jcp.yb; y_in_block += 2) { + for (int x_in_block = 0; x_in_block < jcp.xb; + x_in_block += 2) { + unsigned short v_y_masks[2], v_x_masks[2]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (y_in_block / 2) * (jcp.xb / 2) + + (x_in_block / 2); + +#pragma unroll(2) + for (int i = 0; i < jcp.m; i++) { + v_x_masks[i] = (x + i < jcp.ow) ? 0xffff : 0; + v_y_masks[i] = (y + i < jcp.oh) ? 0xffff : 0; + } + auto local_d = dst + + mb * jcp.nb_oc * jcp.oh * jcp.ow + * jcp.oc_block + + y * jcp.ow * jcp.oc_block + x * jcp.oc_block; + auto local_w = wino_dst + m * jcp.oc; + + auto scales = oscales.scales_; + dst_trans_p.dst = local_d; + dst_trans_p.wino_dst = local_w; + dst_trans_p.v_y_masks = v_y_masks; + dst_trans_p.v_x_masks = v_x_masks; + + dst_trans_p.scales = scales; + dst_trans_p.bias = bia; + + dst_trans_->ker_(&dst_trans_p); + } + } + }); +} + +void jit_avx512_core_fp32_wino_conv_2x3_fwd_t::execute_forward_small_mb( + const float *src, const float *wei, const float *bia, float *dst, + const memory_tracking::grantor_t &scratchpad) const +{ + const auto &jcp = kernel_->jcp; + const auto &oscales = pd()->attr()->output_scales_; + + if (pd()->wants_padded_bias()) { + auto padded_bias = scratchpad.get(key_conv_padded_bias); + utils::array_copy(padded_bias, bia, jcp.oc_without_padding); + utils::array_set(padded_bias + jcp.oc_without_padding, 0.f, + jcp.oc - jcp.oc_without_padding); + bia = padded_bias; + } + + auto ptr_V = scratchpad.get(key_wino_V); + auto ptr_M = scratchpad.get(key_wino_M); + + for (int mb = 0; mb < jcp.mb; mb++) { + for (int tile_y = 0; tile_y < jcp.oh; tile_y += jcp.yb) { + for (int tile_x = 0; tile_x < jcp.ow; tile_x += jcp.xb) { + /* transformation of input tensor to winograd domain */ + parallel_nd(div_up(jcp.yb, 2), div_up(jcp.xb, 2), + [&](int y_in_block_b, int x_in_block_b) { + int y_in_block = y_in_block_b * 2; + int x_in_block = x_in_block_b * 2; + + auto src_trans_p = jit_avx512_core_fp32_wino_conv_2x3_src_trans_t :: + call_params_t(); + + unsigned short v_y_masks[4], v_x_masks[4]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (y_in_block / 2) * (jcp.xb / 2) + (x_in_block / 2); + + int v_ys = nstl::max(0, jcp.t_pad - y); + int v_ye = nstl::min( + jcp.alpha, nstl::max(0, jcp.ih + jcp.t_pad - y)); + + int v_xs = nstl::max(0, jcp.l_pad - x); + int v_xe = nstl::min( + jcp.alpha, nstl::max(0, jcp.iw + jcp.l_pad - x)); + +#pragma unroll(4) + for (int i = 0; i < jcp.alpha; i++) { + v_y_masks[i] = (i < v_ys || i >= v_ye) ? 0 : 0xffff; + v_x_masks[i] = (i < v_xs || i >= v_xe) ? 0 : 0xffff; + } + auto local_s = src + + mb * jcp.nb_ic * jcp.ih * jcp.iw * jcp.ic_block + + y * jcp.iw * jcp.ic_block + x * jcp.ic_block; + auto local_w = ptr_V + m * jcp.ic; + + src_trans_p.src = local_s; + src_trans_p.wino_src = local_w; + src_trans_p.v_y_masks = v_y_masks; + src_trans_p.v_x_masks = v_x_masks; + + src_trans_->ker_(&src_trans_p); + }); + + /* gemms */ + parallel_nd(16, jcp.n_chunks, [&](int tile_ij, int nnb) { + auto gemm_p = jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t :: + call_params_t(); + + gemm_p.src = ptr_V + jcp.inp_stride * tile_ij; + gemm_p.dst = ptr_M + jcp.out_stride * tile_ij + + nnb * jcp.n2_block * jcp.n_block; + gemm_p.wei = wei + jcp.wei_stride * tile_ij + + nnb * jcp.n2_block * jcp.n_block * jcp.K; + + kernel_->ker_(&gemm_p); + }); + + /* transformation from winograd domain to output tensor */ + + parallel_nd(div_up(jcp.yb, 2), div_up(jcp.xb, 2), + [&](int y_in_block_b, int x_in_block_b) { + int y_in_block = y_in_block_b * 2; + int x_in_block = x_in_block_b * 2; + + auto dst_trans_p = jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t :: + call_params_t(); + + unsigned short v_y_masks[2], v_x_masks[2]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (y_in_block / 2) * (jcp.xb / 2) + (x_in_block / 2); + +#pragma unroll(2) + for (int i = 0; i < jcp.m; i++) { + v_x_masks[i] = (x + i < jcp.ow) ? 0xffff : 0; + v_y_masks[i] = (y + i < jcp.oh) ? 0xffff : 0; + } + auto local_d = dst + + mb * jcp.nb_oc * jcp.oh * jcp.ow * jcp.oc_block + + y * jcp.ow * jcp.oc_block + x * jcp.oc_block; + auto local_w = ptr_M + m * jcp.oc; + + auto scales = oscales.scales_; + dst_trans_p.dst = local_d; + dst_trans_p.wino_dst = local_w; + dst_trans_p.v_y_masks = v_y_masks; + dst_trans_p.v_x_masks = v_x_masks; + + dst_trans_p.scales = scales; + dst_trans_p.bias = bia; + + dst_trans_->ker_(&dst_trans_p); + }); + }}} +} + +} // namespace cpu +} // namespace impl +} // namespace mkldnn diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.hpp new file mode 100644 index 000000000000..7e38b07f5af5 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.hpp @@ -0,0 +1,144 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_FP32_WINO_CONV_2x3_HPP +#define CPU_JIT_AVX512_CORE_FP32_WINO_CONV_2x3_HPP + +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_primitive_conf.hpp" +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t; +struct jit_avx512_core_fp32_wino_conv_2x3_src_trans_t; +struct jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t; + +struct jit_avx512_core_fp32_wino_conv_2x3_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_fp32_wino_2x3:", avx512_core, ""), + jit_avx512_core_fp32_wino_conv_2x3_fwd_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::forward_inference + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && set_default_formats(); + if (!ok) return status::unimplemented; + + memory_desc_t expect_wei_md = *weights_md(); + status_t jit_conf_result = jit_conf(expect_wei_md); + if (jit_conf_result != status::success) return jit_conf_result; + set_default_alg_kind(alg_kind::convolution_winograd); + + if (weights_md_.format_kind == format_kind::any) + weights_md_ = expect_wei_md; + if (weights_md_ != expect_wei_md) + return status::unimplemented; + + init_scratchpad(); + + return status::success; + } + + jit_conv_conf_2x3_wino_t jcp_; + + protected: + status_t jit_conf(memory_desc_t& expect_wei_md); + + void init_scratchpad() { + using namespace memory_tracking::names; + + auto scratchpad = scratchpad_registry().registrar(); + + int wino_size_offset = (jcp_.yb / 2) * (jcp_.xb / 2) + jcp_.xb; + + size_t V_sz = (size_t)jcp_.ic * 16 * wino_size_offset * jcp_.nthr; + scratchpad.book(key_wino_V, sizeof(float) * V_sz, PAGE_4K); + + size_t M_sz = (size_t)jcp_.oc * 16 * wino_size_offset * jcp_.nthr; + scratchpad.book(key_wino_M, sizeof(float) * M_sz, PAGE_4K); + + if (wants_padded_bias()) { + assert(jcp_.ngroups == 1); + scratchpad.book(key_conv_padded_bias, sizeof(float) * jcp_.oc); + } + } + + bool set_default_formats() { + using namespace format_tag; + return set_default_formats_common(nChw16c, any, nChw16c); + } + }; + + jit_avx512_core_fp32_wino_conv_2x3_fwd_t(const pd_t *apd); + ~jit_avx512_core_fp32_wino_conv_2x3_fwd_t(); + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto src = CTX_IN_MEM(const float *, MKLDNN_ARG_SRC); + auto wei = CTX_IN_MEM(const float *, MKLDNN_ARG_WEIGHTS); + auto bia = CTX_IN_MEM(const float *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(float *, MKLDNN_ARG_DST); + + if (pd()->jcp_.small_mb) + execute_forward_small_mb(src, wei, bia, dst, this->scratchpad(ctx)); + else + execute_forward_mbN(src, wei, bia, dst, this->scratchpad(ctx)); + + return status::success; + } + +private: + void execute_forward_small_mb(const float *src, const float *wei, + const float *bia, float *dst, + const memory_tracking::grantor_t &scratchpad) const; + void execute_forward_mbN(const float *src, const float *wei, + const float *bia, float *dst, + const memory_tracking::grantor_t &scratchpad) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_core_fp32_wino_conv_2x3_fwd_ker_t *kernel_; + jit_avx512_core_fp32_wino_conv_2x3_src_trans_t *src_trans_; + jit_avx512_core_fp32_wino_conv_2x3_dst_trans_t *dst_trans_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp new file mode 100644 index 000000000000..96325e3ade24 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp @@ -0,0 +1,1020 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifdef __INTEL_COMPILER +#include +#endif + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_core_fp32_wino_conv_4x3.hpp" + +#ifndef _MSC_VER +#define pragma_unroll _Pragma("unroll") +#else +#define pragma_unroll +#endif + + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t +::weight_transform_data(const jit_conv_winograd_conf_t &jcp, + float *wp, float *twp) const +{ + float G[] = {0.26890756302521f, 0.688403361344538f, 0.119514472455649f, + 1.13777777777778f, 0.430252100840336f, 0.179271708683473f}; + const int kh = 3; + const int kw = 3; + float Fw[alpha][alpha][simd_w][simd_w]; + float F[kh][kw][simd_w][simd_w]; + float T[alpha][3][simd_w]; + auto p = jit_wino_transform_call_s(); + + p.src = wp; + p.dst = twp; + p.G = G; + p.M = F; + p.Mw = Fw; + p.T = T; + + kernel_->weights_transform_data_ker(&p); +} + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t::output_transform_data +(int image, const jit_conv_winograd_conf_t &jcp, + const post_ops_t &p_ops, float *toutp, float *pout_b, float *bias) const { + + float G[] = {0.625f, 1.5f, 0.390625f, 2.25f, 0.244140625f, 3.375f}; + float Ow[alpha][alpha][simd_w]; + float O[tile_size][tile_size][simd_w]; + float T[tile_size][alpha][simd_w]; + + auto p = jit_wino_transform_call_s(); + p.src = toutp; + p.dst = pout_b; + p.G = G; + p.M = O; + p.Mw = Ow; + p.T = T; + p.bias = bias; + + int tile_base_index = image * jcp.itiles * jcp.jtiles; + int tile_block_ur = tile_base_index % jcp.tile_block_ur; + int nb_tile_block_ur = + (tile_base_index / jcp.tile_block_ur) % jcp.nb_tile_block_ur; + int tile_block = + (tile_base_index / jcp.tile_block_ur) / jcp.nb_tile_block_ur; + + for (int tj = 0; tj < jcp.jtiles; tj++) { + for (int ti = 0; ti < jcp.itiles; ti++) { + + p.tile_block_ur = tile_block_ur; + p.nb_tile_block_ur = nb_tile_block_ur; + p.tile_block = tile_block; + p.tj = tj; + p.ti = ti; + + kernel_->output_transform_data_ker(&p); + + tile_block_ur++; + if (tile_block_ur >= jcp.tile_block_ur) { + tile_block_ur = 0; + nb_tile_block_ur++; + } + if (nb_tile_block_ur >= jcp.nb_tile_block_ur) { + nb_tile_block_ur = 0; + tile_block++; + } + } + } +} + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t +::output_transform_tileblock_data(int tile_block, + const jit_conv_winograd_conf_t &jcp, const post_ops_t &p_ops, + float *toutp, float *outp, float *bias) const { + + float G[] = {0.625f, 1.5f, 0.390625f, 2.25f, 0.244140625f, 3.375f}; + float Ow[alpha][alpha][simd_w]; + float O[tile_size][tile_size][simd_w]; + float T[tile_size][alpha][simd_w]; + + auto p = jit_wino_transform_call_s(); + p.src = toutp; + p.dst = outp; + p.G = G; + p.M = O; + p.Mw = Ow; + p.T = T; + p.bias = bias; + + int outw = is_fwd ? jcp.ow : jcp.iw; + int outh = is_fwd ? jcp.oh : jcp.ih; + + int tile_index = tile_block * jcp.nb_tile_block_ur * jcp.tile_block_ur; + + for (int nb_tile_block_ur = 0; + nb_tile_block_ur < jcp.nb_tile_block_ur; + nb_tile_block_ur++) { + + for (int tile_block_ur = 0; tile_block_ur < jcp.tile_block_ur; + tile_block_ur++) { + int img = tile_index / (jcp.jtiles * jcp.itiles); + int ti = tile_index % jcp.itiles; + int tj = (tile_index / jcp.itiles) % jcp.jtiles; + + p.tile_block_ur = tile_block_ur; + p.nb_tile_block_ur = nb_tile_block_ur; + p.tile_block = tile_block; + p.tj = tj; + p.ti = ti; + p.dst = outp + img * (jcp.dimM / jcp.dimM_simd_block) + * outh * outw * jcp.dimM_simd_block; + + kernel_->output_transform_data_ker(&p); + + tile_index++; + } + } +} + + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t + ::input_transform_data(int image, const jit_conv_winograd_conf_t &jcp, + float *inp, float *tinp) const +{ + float G[] = {-2.25f, -0.390625f, 0.87890625f, -2.640625f, + 0.625f, -0.625f, 1.5f, -1.5f, -2.640625f}; + + float Iw[alpha][alpha][simd_w]; + float I[alpha][alpha][simd_w]; + float T[alpha][alpha][simd_w]; + + auto p = jit_wino_transform_call_s(); + + p.src = inp; + p.dst = tinp; + p.G = G; + p.M = I; + p.Mw = Iw; + p.T = T; + + int tile_base_index = image * jcp.itiles * jcp.jtiles; + int tile_block_ur = tile_base_index % jcp.tile_block_ur; + int nb_tile_block_ur = + (tile_base_index / jcp.tile_block_ur) % jcp.nb_tile_block_ur; + int tile_block = + (tile_base_index / jcp.tile_block_ur) / jcp.nb_tile_block_ur; + + for (int tj = 0; tj < jcp.jtiles; tj++) { + for (int ti = 0; ti < jcp.itiles; ti++) { + + p.tile_block_ur = tile_block_ur; + p.nb_tile_block_ur = nb_tile_block_ur; + p.tile_block = tile_block; + p.tj = tj; + p.ti = ti; + + kernel_->input_transform_data_ker(&p); + + tile_block_ur++; + if (tile_block_ur >= jcp.tile_block_ur) { + tile_block_ur = 0; + nb_tile_block_ur++; + } + if (nb_tile_block_ur >= jcp.nb_tile_block_ur) { + nb_tile_block_ur = 0; + tile_block++; + } + } + } +} + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t + ::input_transform_tileblock_data(int tile_block, + const jit_conv_winograd_conf_t &jcp, + float *inp, float *tinp) const +{ + float G[] = {-2.25f, -0.390625f, 0.87890625f, -2.640625f, + 0.625f, -0.625f, 1.5f, -1.5f, -2.640625f}; + float Iw[alpha][alpha][simd_w]; + float I[alpha][alpha][simd_w]; + float T[alpha][alpha][simd_w]; + + const int inph = is_fwd ? jcp.ih : jcp.oh; + const int inpw = is_fwd ? jcp.iw : jcp.ow; + + array_offset_calculator input(inp, + jcp.mb, jcp.dimK / simd_w, inph, inpw, simd_w); + array_offset_calculator output(tinp, + alpha, alpha, + jcp.dimN_block, jcp.dimK_nb_block, jcp.dimK_block, + jcp.dimN_reg_block, jcp.dimK_reg_block); + + auto p = jit_wino_transform_call_s(); + + p.dst = tinp; + p.G = G; + p.M = I; + p.Mw = Iw; + p.T = T; + + + int tile_index = tile_block * jcp.nb_tile_block_ur * jcp.tile_block_ur; + + for (int nb_tile_block_ur = 0; + nb_tile_block_ur < jcp.nb_tile_block_ur; + nb_tile_block_ur++) { + + for (int tile_block_ur = 0; tile_block_ur < jcp.tile_block_ur; + tile_block_ur++) { + + int img = tile_index / (jcp.jtiles * jcp.itiles); + int ti = tile_index % jcp.itiles; + int tj = (tile_index / jcp.itiles) % jcp.jtiles; + float *pinp_b = &(input(img, 0, 0, 0, 0)); + + p.src = pinp_b; + p.tile_block_ur = tile_block_ur; + p.nb_tile_block_ur = nb_tile_block_ur; + p.tj = tj; + p.ti = ti; + + kernel_->input_transform_data_ker(&p); + + tile_index++; + } + } +} + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t::_execute_data_W_S_G_D( + float *inp_ptr, float *out_ptr, float *wei_ptr, float *bias_ptr, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const auto &p_ops = attr_->post_ops_; + + const int inph = is_fwd ? jcp.ih : jcp.oh; + const int inpw = is_fwd ? jcp.iw : jcp.ow; + const int outh = is_fwd ? jcp.oh : jcp.ih; + const int outw = is_fwd ? jcp.ow : jcp.iw; + + /* Notation: + FWD: dimM:oc, dimN:ntiles, dimK:ic, + BWD: dimM:ic, dimN:ntiles, dimK:oc, + FWD/BWD: V: src/diff_dst transform, U:weight transform, + M:dst/diff_src transform */ + array_offset_calculator input(inp_ptr, + jcp.mb, jcp.dimK/jcp.dimK_reg_block, inph, inpw, + jcp.dimK_reg_block); + array_offset_calculator output(out_ptr, + jcp.mb, jcp.dimM/jcp.dimM_simd_block, outh, outw, + jcp.dimM_simd_block); + array_offset_calculator weights(wei_ptr, + jcp.oc/jcp.oc_simd_block, jcp.ic/jcp.ic_simd_block, jcp.kh, jcp.kw, + jcp.ic_simd_block, jcp.oc_simd_block); + array_offset_calculator bias(bias_ptr, + jcp.dimM/jcp.dimM_simd_block, jcp.dimM_simd_block); + + array_offset_calculator M(is_fwd + ? scratchpad.template get(key_wino_M) + : scratchpad.template get(key_wino_V), + jcp.dimN_nb_block, jcp.dimM_nb_block, + alpha, alpha, + jcp.dimN_block, jcp.dimM_block * jcp.dimM_reg_block, + jcp.dimN_reg_block, jcp.dimM_simd_block); + + auto wino_wei = (jcp.prop_kind == prop_kind::forward_inference) + ? wei_ptr + : scratchpad.template get(key_wino_U); + + array_offset_calculator U(wino_wei, + jcp.dimM_nb_block, + alpha, alpha, + jcp.dimK_nb_block, + jcp.dimM_block * jcp.dimM_reg_block, jcp.dimK_block, + jcp.dimK_reg_block, jcp.dimM_simd_block); + array_offset_calculator V(is_fwd + ? scratchpad.template get(key_wino_V) + : scratchpad.template get(key_wino_M), + jcp.dimN_nb_block, alpha, alpha, + jcp.dimN_block, jcp.dimK_nb_block, + jcp.dimK_block, jcp.dimN_reg_block, jcp.dimK_reg_block); + + const bool wants_padded_bias = jcp.with_bias + && jcp.oc_without_padding != jcp.oc; + float last_slice_bias[simd_w] = {0}; + if (wants_padded_bias) { + for (int oc = 0; oc < jcp.oc_without_padding % jcp.oc_simd_block; ++oc) + last_slice_bias[oc] = bias(jcp.dimM / jcp.dimM_simd_block - 1, oc); + } + + { + + parallel_nd(jcp.mb, jcp.dimK_nb_block, jcp.dimK_block, + [&](int img, int K_blk1, int K_blk2) { + input_transform_data(img, jcp, + &(input(img, K_blk1 * jcp.dimK_block + K_blk2, + 0, 0, 0)), + &(V(0, 0, 0, 0, K_blk1, K_blk2, 0, 0))); + }); + + if (jcp.prop_kind != prop_kind::forward_inference) { + parallel_nd(jcp.nb_oc, jcp.nb_ic, (jcp.oc_block * jcp.oc_reg_block), + (jcp.ic_block * jcp.ic_reg_block), + [&](int ofm1, int ifm1, int ofm2, int ifm2) { + float *U_base_ptr = is_fwd + ? &(U(ofm1, 0, 0, ifm1, ofm2, ifm2, 0, 0)) + : &(U(ifm1, 0, 0, ofm1, ifm2, ofm2, 0, 0)); + weight_transform_data(jcp, + &(weights( + ofm1 * jcp.oc_block * jcp.oc_reg_block + ofm2, + ifm1 * jcp.ic_block * jcp.ic_reg_block + ifm2, + 0, 0, 0, 0)), + U_base_ptr); + }); + } + + parallel_nd(jcp.dimN_nb_block, alpha, alpha, jcp.dimM_nb_block, + [&](int N_blk1, int oj, int oi, int M_blk1) { + for (int K_blk1 = 0; K_blk1 < jcp.dimK_nb_block; + K_blk1++) + for (int N_blk2 = 0; N_blk2 < jcp.dimN_block; N_blk2++) + kernel_->gemm_loop_ker( + (float *)&(M(N_blk1, M_blk1, oj, oi, + N_blk2, 0, 0, 0)), + (const float *)&(U(M_blk1, oj, oi, + K_blk1, 0, 0, 0, 0)), + (const float *)&(V(N_blk1, oj, oi, + N_blk2, K_blk1, 0, 0, 0)), K_blk1); + }); + + parallel_nd(jcp.mb, jcp.dimM_nb_block, (jcp.dimM_block * jcp.dimM_reg_block), + [&](int img, int M_blk1, int M_blk2) { + const int M_blk = + M_blk1 * jcp.dimM_block * jcp.dimM_reg_block + M_blk2; + + float *bias_ptr = wants_padded_bias + && M_blk == jcp.dimM / jcp.dimM_simd_block - 1 + ? last_slice_bias : &bias(M_blk, 0); + output_transform_data(img, jcp, p_ops, + &(M(0, M_blk1, 0, 0, 0, M_blk2, 0, 0)), + &(output(img, M_blk, 0, 0, 0)), bias_ptr); + }); + + } +} + +template +void _jit_avx512_core_fp32_wino_conv_4x3_t::_execute_data_W_SGD( + float *inp_ptr, float *out_ptr, float *wei_ptr, float *bias_ptr, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const auto &p_ops = attr_->post_ops_; + + const int inph = is_fwd ? jcp.ih : jcp.oh; + const int inpw = is_fwd ? jcp.iw : jcp.ow; + const int outh = is_fwd ? jcp.oh : jcp.ih; + const int outw = is_fwd ? jcp.ow : jcp.iw; + + array_offset_calculator input(inp_ptr, + jcp.mb, jcp.dimK/jcp.dimK_reg_block, inph, inpw, jcp.dimK_reg_block); + array_offset_calculator output(out_ptr, + jcp.mb, jcp.dimM/jcp.dimM_simd_block, outh, outw, jcp.dimM_simd_block); + array_offset_calculator weights(wei_ptr, + jcp.oc/jcp.oc_simd_block, jcp.ic/jcp.ic_simd_block, jcp.kh, jcp.kw, + jcp.ic_simd_block, jcp.oc_simd_block); + array_offset_calculator bias(bias_ptr, + jcp.oc/jcp.oc_simd_block, jcp.oc_simd_block); + + auto wino_wei = (jcp.prop_kind == prop_kind::forward_inference) + ? wei_ptr + : scratchpad.template get(key_wino_U); + + array_offset_calculator U(wino_wei, + jcp.dimM_nb_block, + alpha, alpha, + jcp.dimK_nb_block, + jcp.dimM_block * jcp.dimM_reg_block, jcp.dimK_block, + jcp.dimK_reg_block, jcp.dimM_simd_block); + + array_offset_calculator M(is_fwd + ? scratchpad.template get(key_wino_M) + : scratchpad.template get(key_wino_V), + 0, jcp.dimM_nb_block, alpha, alpha, + jcp.dimN_block, jcp.dimM_block * jcp.dimM_reg_block, + jcp.dimN_reg_block, jcp.dimM_simd_block); + array_offset_calculator V(is_fwd + ? scratchpad.template get(key_wino_V) + : scratchpad.template get(key_wino_M), + 0, alpha, alpha, jcp.dimN_block, + jcp.dimK_nb_block, jcp.dimK_block, + jcp.dimN_reg_block, jcp.dimK_reg_block); + + const bool wants_padded_bias = jcp.with_bias + && jcp.oc_without_padding != jcp.oc; + float last_slice_bias[simd_w] = {0}; + if (wants_padded_bias) { + for (int oc = 0; oc < jcp.oc_without_padding % jcp.oc_simd_block; ++oc) + last_slice_bias[oc] = bias(jcp.dimM / jcp.dimM_simd_block - 1, oc); + } + + if (jcp.prop_kind != prop_kind::forward_inference) { + + parallel_nd(jcp.nb_oc, jcp.nb_ic, (jcp.oc_block * jcp.oc_reg_block), (jcp.ic_block * jcp.ic_reg_block), + [&](int ofm1, int ifm1, int ofm2, int ifm2) { + float *U_base_ptr = is_fwd + ? &(U(ofm1, 0, 0, ifm1, ofm2, ifm2, 0, 0)) + : &(U(ifm1, 0, 0, ofm1, ifm2, ofm2, 0, 0)); + weight_transform_data(jcp, + &(weights( + ofm1 * jcp.oc_block * jcp.oc_reg_block + ofm2, + ifm1 * jcp.ic_block * jcp.ic_reg_block + ifm2, + 0, 0, 0, 0)), + U_base_ptr); + }); + } + + parallel_nd(jcp.tile_block, [&](int tile_block) { + int ithr = mkldnn_get_thread_num(); + + for (int K_blk1 = 0; K_blk1 < jcp.dimK_nb_block; K_blk1++) { + for (int K_blk2 = 0; K_blk2 < jcp.dimK_block; K_blk2++) { + + input_transform_tileblock_data( + tile_block, jcp, + &(input(0, K_blk1 * jcp.dimK_block + K_blk2, 0, 0, 0)), + &(V(ithr, 0, 0, 0, K_blk1, K_blk2, 0, 0))); + } + } + + for (int oj = 0; oj < alpha; oj++) { + for (int oi = 0; oi < alpha; oi++) { + for (int M_blk1 = 0; M_blk1 < jcp.dimM_nb_block; M_blk1++) + for (int K_blk1 = 0; K_blk1 < jcp.dimK_nb_block; K_blk1++) + for (int N_blk = 0; N_blk < jcp.dimN_block; N_blk++) + kernel_->gemm_loop_ker( + (float *)&(M(ithr, M_blk1, oj, oi, + N_blk, 0, 0, 0)), + (const float *)&(U(M_blk1, oj, oi, K_blk1, + 0, 0, 0, 0)), + (const float *)&(V(ithr, oj, oi, + N_blk, K_blk1, 0, 0, 0)), K_blk1); + } + } + + for (int M_blk1 = 0; M_blk1 < jcp.dimM_nb_block; M_blk1++) { + for (int M_blk2 = 0; M_blk2 < jcp.dimM_block * jcp.dimM_reg_block; + M_blk2++) { + const int M_blk = + M_blk1 * jcp.dimM_block * jcp.dimM_reg_block + M_blk2; + + float *bias_ptr = wants_padded_bias + && M_blk == jcp.dimM / jcp.dimM_simd_block - 1 + ? last_slice_bias : &bias(M_blk, 0); + + output_transform_tileblock_data(tile_block, jcp, p_ops, + &(M(ithr, M_blk1, 0, 0, 0, M_blk2, 0, 0)), + &(output(0, M_blk, 0, 0, 0)), bias_ptr); + } + } + }); +} + +template struct _jit_avx512_core_fp32_wino_conv_4x3_t; +template struct _jit_avx512_core_fp32_wino_conv_4x3_t; + +namespace { + +void subarray_sum(size_t num_arrs, float *output, size_t nelems, + float *input_ptrs[], size_t input_starts[], size_t input_ends[]) { + using namespace nstl; + const size_t block_size = 16 * 1024 / sizeof(float); + const size_t blocks_number = nelems / block_size; + const size_t tail = nelems % block_size; + +PRAGMA_OMP(parallel) + { + const int ithr = mkldnn_get_thread_num(); + const int nthr = mkldnn_get_num_threads(); + size_t start{ 0 }, end{ 0 }; + balance211(blocks_number, nthr, ithr, start, end); + + for (size_t nb = start; nb < end; ++nb) { + size_t start_e = nb * block_size; + size_t end_e = start_e + block_size; + size_t input_start = max(start_e, min(input_starts[0], end_e)); + size_t input_end = max(start_e, min(input_ends[0], end_e)); + + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < input_start; e++) { + output[e] = 0.f; + } + + PRAGMA_OMP_SIMD() + for (size_t e = input_start; e < input_end; e++) { + output[e] = input_ptrs[0][e]; + } + + PRAGMA_OMP_SIMD() + for (size_t e = input_end; e < end_e; e++) { + output[e] = 0.f; + } + + for (size_t a = 1; a < num_arrs; a++) { + input_start = max(start_e, input_starts[a]); + input_end = min(input_ends[a], end_e); + + PRAGMA_OMP_SIMD() + for (size_t e = input_start; e < input_end; e++) { + output[e] += input_ptrs[a][e]; + } + } + } + + if (tail != 0 && ithr == nthr - 1) { + size_t start_e = nelems - tail; + size_t end_e = nelems; + size_t input_start = max(start_e, min(input_starts[0], end_e)); + size_t input_end = max(start_e, min(input_ends[0], end_e)); + + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < input_start; e++) { + output[e] = 0.f; + } + + PRAGMA_OMP_SIMD() + for (size_t e = input_start; e < input_end; e++) { + output[e] = input_ptrs[0][e]; + } + + PRAGMA_OMP_SIMD() + for (size_t e = input_end; e < end_e; e++) { + output[e] = 0.f; + } + + for (size_t a = 1; a < num_arrs; a++) { + input_start = max(start_e, input_starts[a]); + input_end = min(input_ends[a], end_e); + + PRAGMA_OMP_SIMD() + for (size_t e = input_start; e < input_end; e++) { + output[e] += input_ptrs[a][e]; + } + } + } + } +} + +const int max_threads_number = 1024; + +// Sum to the first buffer array +void array_sum(size_t num_arrs, float *output, + size_t nelems, float *input_ptrs[], bool reduce_to_first = true) { + const size_t block_size = 16 * 1024 / sizeof(float); + const size_t blocks_number = nelems / block_size; + const size_t tail = nelems % block_size; + +PRAGMA_OMP(parallel) + { + const size_t ithr = mkldnn_get_thread_num(); + const size_t nthr = mkldnn_get_num_threads(); + size_t start{ 0 }, end{ 0 }; + balance211(blocks_number, nthr, ithr, start, end); + + for (size_t nb = start; nb < end; ++nb) { + size_t start_e = nb * block_size; + size_t end_e = start_e + block_size; + if (!reduce_to_first) { + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] = input_ptrs[0][e]; + } + } + for (size_t a = 1; a < num_arrs; a++) { + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] += input_ptrs[a][e]; + } + } + } + + if (tail != 0 && ithr == nthr - 1) { + size_t start_e = nelems - tail; + size_t end_e = nelems; + if (!reduce_to_first) { + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] = input_ptrs[0][e]; + } + } + for (size_t a = 1; a < num_arrs; a++) { + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] += input_ptrs[a][e]; + } + } + } + } +} +} //bwdw namespace + +void jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t:: +_execute_backward_weights_SDGtWo(const float *ptr_src, + const float *ptr_diff_dst, float *ptr_diff_weights, + float *ptr_diff_bias, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const int nthreads = jcp.nthr; + + array_offset_calculator src((float *)ptr_src, + jcp.mb, jcp.ic / simd_w, jcp.ih, jcp.iw, simd_w); + array_offset_calculator diff_dst((float *)ptr_diff_dst, + jcp.mb, jcp.oc / simd_w, jcp.oh, jcp.ow, simd_w); + array_offset_calculator diff_weights(ptr_diff_weights, + jcp.oc / simd_w, jcp.ic / simd_w, jcp.kh, jcp.kw, simd_w, simd_w); + + array_offset_calculator Us(scratchpad.get(key_wino_U), + 0, alpha, alpha, + jcp.oc_block, jcp.ic_block, + jcp.ic_simd_block, + jcp.oc_reg_block, + jcp.oc_simd_block); + + const int U_sz = nthreads * alpha * alpha * jcp.oc / jcp.nb_oc + * jcp.ic / jcp.nb_ic; + array_offset_calculatordiff_weights_prv( + scratchpad.get(key_wino_U) + U_sz, + 0, jcp.oc / simd_w, jcp.ic / simd_w, jcp.kh, jcp.kw, simd_w, simd_w); + + array_offset_calculator M(scratchpad.get(key_wino_M), + 0, alpha, alpha, + jcp.oc_block, + jcp.nb_tile_block_ur, + jcp.tile_block_ur, + jcp.oc_reg_block, + jcp.oc_simd_block); + + array_offset_calculator V(scratchpad.get(key_wino_V), + 0, alpha, alpha, + jcp.ic_block, + jcp.nb_tile_block_ur, + jcp.tile_block_ur, + jcp.ic_simd_block); + + array_offset_calculator diff_bias_prv( + scratchpad.get(key_conv_bia_reduction), nthreads, jcp.oc); + + auto trans_ker_p = jit_wino_transform_call_s(); + float I[alpha][alpha][simd_w]; + float T[alpha][alpha][simd_w]; + float G_I_3x3_4x4[9] = {-2.25f, -0.390625f, 0.87890625f, -2.640625f, + 0.625f, -0.625f, 1.5f, -1.5f, -2.640625f}; + float G_W_3x3_4x4[8] = {0.26890756302521f, -0.688403361344538f, 0.119514472455649f, + 0.430252100840336f, 0.168067226890756f, 0.179271708683473f, 0.403361344537815f, + 1.13777777777778f}; + float G_O_3x3_4x4[4] = {2.25f, 0.625f, 1.5f, 0.390625f}; + +PRAGMA_OMP(parallel num_threads(nthreads) firstprivate(trans_ker_p, I, T)) +{ + if (jcp.with_bias) { + parallel_nd_in_omp(nthreads, jcp.oc / simd_w, + [&](int ithr, int ofm){ + float *pdbias = &(diff_bias_prv(ithr, ofm * simd_w)); + PRAGMA_OMP_SIMD() + for (int v = 0; v < simd_w; v++) { + pdbias[v] = 0.0f; + } + }); + } + + int ithr = mkldnn_get_thread_num(); + for (int ifm1 = 0; ifm1 < jcp.nb_ic; ++ifm1) { + int first_tblk = 0; +PRAGMA_OMP(for) + for (int tblk1 = 0; tblk1 < jcp.tile_block; ++tblk1) { + int tile_index = tblk1 * jcp.nb_tile_block_ur * jcp.tile_block_ur; + int img = tile_index / (jcp.itiles * jcp.jtiles); + trans_ker_p.ti = tile_index % jcp.itiles; + trans_ker_p.tj = (tile_index / jcp.itiles) % jcp.jtiles; + trans_ker_p.M = I; + trans_ker_p.T = T; + trans_ker_p.G = G_I_3x3_4x4; + for (int ifm2 = 0; ifm2 < jcp.ic_block; ++ifm2) { + int ifm = ifm1 * jcp.ic_block + ifm2; + trans_ker_p.src = (float *)&(src(img, ifm, 0, 0, 0)); + trans_ker_p.dst = (float *)&(V(ithr, 0, 0, ifm2, 0, 0, 0)); + kernel_->src_transform(&trans_ker_p); + } + + for (int ofm1 = 0; ofm1 < jcp.nb_oc; ++ofm1) { + trans_ker_p.G = G_W_3x3_4x4; + for (int ofm2 = 0; ofm2 < jcp.oc_block; ++ofm2) { + int ofm = (ofm1 * jcp.oc_block + ofm2) * jcp.oc_reg_block; + trans_ker_p.src = (float *)&(diff_dst(img, ofm, 0, 0, 0)); + trans_ker_p.dst = (float *)&(M(ithr, 0, 0, ofm2, 0, 0, 0, 0)); + if (jcp.with_bias && ifm1 == 0) { + trans_ker_p.bias = (float *)&(diff_bias_prv(ithr, ofm * simd_w)); + kernel_->diff_dst_transform_wbias(&trans_ker_p); + } else { + kernel_->diff_dst_transform(&trans_ker_p); + } + } + + for (int oj = 0; oj < alpha; ++oj) { + for (int oi = 0; oi < alpha; ++oi) { + kernel_->gemm_loop_ker_first_iter( + &(Us(ithr, oj, oi, 0, 0, 0, 0, 0)), + &(M(ithr, oj, oi, 0, 0, 0, 0, 0)), + &(V(ithr, oj, oi, 0, 0, 0, 0))); + } + } + trans_ker_p.G = G_O_3x3_4x4; + for (int ofm2 = 0; ofm2 < jcp.oc_block; ++ofm2) { + for (int ofm3 = 0; ofm3 < jcp.oc_reg_block; ++ofm3) { + int ofm = (ofm1 * jcp.oc_block + ofm2) * jcp.oc_reg_block + + ofm3; + for (int ifm2 = 0; ifm2 < jcp.ic_block; ++ifm2) { + int ifm = ifm1 * jcp.ic_block + ifm2; + trans_ker_p.src = (float *)&(Us(ithr, 0, 0, + ofm2, ifm2, 0, ofm3, 0)); + trans_ker_p.dst = (float *)&(diff_weights_prv(ithr, + ofm, ifm, 0, 0, 0, 0)); + if (first_tblk == 0) { + kernel_->diff_weights_transform(&trans_ker_p); + } else { + kernel_->diff_weights_transform_accum(&trans_ker_p); + } + } + } + } + } + ++first_tblk; + } + } +} + + // Reduce diff-weights + { + float *output = ptr_diff_weights; + float *input_base = scratchpad.get(key_wino_U) + U_sz; + int nelems = jcp.oc * jcp.ic * jcp.kh * jcp.kw; + float *input_ptrs[max_threads_number]; + for (int i = 0; i < nthreads; ++i) { + input_ptrs[i] = input_base + nelems * i; + } + array_sum(nthreads, output, nelems, input_ptrs, false); + + if (jcp.with_bias) { + output = ptr_diff_bias; + input_base = scratchpad.get(key_conv_bia_reduction); + for (int i = 0; i < nthreads; ++i) { + input_ptrs[i] = input_base + jcp.oc * i; + } + array_sum(nthreads, output, jcp.oc_without_padding, input_ptrs, + false); + } + } +} + +void jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t:: +_execute_backward_weights_S_D_Giot_W(const float *ptr_src, + const float *ptr_diff_dst, float *ptr_diff_weights, + float *ptr_diff_bias, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const int nthreads = jcp.nthr; + + array_offset_calculator src((float *)ptr_src, + jcp.mb, jcp.ic / simd_w, jcp.ih, jcp.iw, simd_w); + array_offset_calculator diff_dst((float *)ptr_diff_dst, + jcp.mb, jcp.oc / simd_w, jcp.oh, jcp.ow, simd_w); + array_offset_calculator diff_weights((float *)ptr_diff_weights, + jcp.oc / simd_w, jcp.ic / simd_w, jcp.kh, jcp.kw, simd_w, simd_w); + array_offset_calculator diff_bias((float *)ptr_diff_bias, jcp.oc); + + array_offset_calculator U(scratchpad.get(key_wino_U), + jcp.nb_ic, jcp.nb_oc, + alpha, alpha, + jcp.oc_block, jcp.ic_block, + jcp.ic_simd_block, + jcp.oc_reg_block, + jcp.oc_simd_block); + + const int U_size = jcp.oc * jcp.ic * alpha * alpha; + array_offset_calculator Us( + scratchpad.get(key_wino_U) + U_size, + 0, jcp.nb_ic, jcp.nb_oc, + alpha, alpha, + jcp.oc_block, jcp.ic_block, + jcp.ic_simd_block, + jcp.oc_reg_block, + jcp.oc_simd_block); + + array_offset_calculator M(scratchpad.get(key_wino_M), + jcp.nb_oc, + jcp.tile_block, + alpha, alpha, + jcp.oc_block, + jcp.nb_tile_block_ur, + jcp.tile_block_ur , + jcp.oc_reg_block, + jcp.oc_simd_block); + + array_offset_calculator V(scratchpad.get(key_wino_V), + jcp.nb_ic, + jcp.tile_block, + alpha, alpha, + jcp.ic_block, + jcp.nb_tile_block_ur, jcp.tile_block_ur, + jcp.ic_simd_block); + + array_offset_calculator diff_bias_prv( + scratchpad.get(key_conv_bia_reduction), nthreads, jcp.oc); + + size_t input_starts[max_threads_number] = {0}; + size_t input_ends[max_threads_number] = {0}; + size_t first_tblk = 0; + + auto trans_ker_p = jit_wino_transform_call_s(); + float G_I_3x3_4x4[9] = {-2.25f, -0.390625f, 0.87890625f, -2.640625f, + 0.625f, -0.625f, 1.5f, -1.5f, -2.640625f}; + float G_W_3x3_4x4[8] = {0.26890756302521f, -0.688403361344538f, + 0.119514472455649f, 0.430252100840336f, 0.168067226890756f, + 0.179271708683473f, 0.403361344537815f, 1.13777777777778f}; + float G_O_3x3_4x4[4] = {2.25f, 0.625f, 1.5f, 0.390625f}; + float I[alpha][alpha][simd_w]; + float T[alpha][alpha][simd_w]; + +PRAGMA_OMP(parallel firstprivate(first_tblk, trans_ker_p, I, T)) +{ + if (jcp.with_bias) { + parallel_nd_in_omp(nthreads, jcp.oc, [&](int ithr, int ofm) { + diff_bias_prv(ithr, ofm) = 0.0f; + }); + } + + trans_ker_p.G = G_I_3x3_4x4; + trans_ker_p.M = I; + trans_ker_p.T = T; + + parallel_nd_in_omp(jcp.nb_ic, jcp.ic_block, jcp.mb, + [&](int ifm1, int ifm2, int img){ + size_t ifm = ifm1 * jcp.ic_block + ifm2; + size_t tile_base_index = img * (jcp.itiles * jcp.jtiles); + size_t tblk3 = tile_base_index % jcp.tile_block_ur; + size_t tblk2 = (tile_base_index / jcp.tile_block_ur) + % jcp.nb_tile_block_ur; + size_t tblk1 = (tile_base_index / jcp.tile_block_ur) + / jcp.nb_tile_block_ur; + trans_ker_p.tile_count = tblk2 * jcp.tile_block_ur + tblk3; + trans_ker_p.src = (float *)&(src(img, ifm, 0, 0, 0)); + trans_ker_p.dst = (float *)&(V(ifm1, tblk1, 0, 0, ifm2, 0, 0, 0)); + kernel_->src_transform(&trans_ker_p); + }); + + int ithr = mkldnn_get_thread_num(); + trans_ker_p.G = G_W_3x3_4x4; + parallel_nd_in_omp(jcp.nb_oc, jcp.oc_block, jcp.mb, + [&](int ofm1, int ofm2, int img){ + int ofm = (ofm1 * jcp.oc_block + ofm2) * jcp.oc_reg_block; + size_t tile_base_index = img * (jcp.itiles * jcp.jtiles); + size_t tblk3 = tile_base_index % jcp.tile_block_ur; + size_t tblk2 = (tile_base_index / jcp.tile_block_ur) + % jcp.nb_tile_block_ur; + size_t tblk1 = (tile_base_index / jcp.tile_block_ur) + / jcp.nb_tile_block_ur; + trans_ker_p.tile_count = tblk2 * jcp.tile_block_ur + tblk3; + trans_ker_p.src = (float *)&(diff_dst(img, ofm, 0, 0, 0)); + trans_ker_p.dst = (float *)&(M(ofm1, tblk1, 0, 0, ofm2, 0, 0, 0, 0)); + if (jcp.with_bias) { + trans_ker_p.bias = (float *)&(diff_bias_prv(ithr, ofm * simd_w)); + kernel_->diff_dst_transform_wbias(&trans_ker_p); + } else { + kernel_->diff_dst_transform(&trans_ker_p); + } + }); + + PRAGMA_OMP(barrier) + + parallel_nd_in_omp(jcp.nb_ic, jcp.nb_oc, alpha, alpha, jcp.tile_block, + [&](int ifm1, int ofm1, int oj, int oi, int tblk1){ + if (first_tblk == 0) { + input_starts[ithr] = + (float *)&(Us(ithr, ifm1, ofm1, oj, oi, 0, 0, 0, + 0, 0)) + - (float *)&(Us(ithr, 0, 0, 0, 0, 0, 0, + 0, 0, 0)); + input_ends[ithr] = input_starts[ithr] + + jcp.oc_block * jcp.ic_block + * jcp.ic_simd_block * jcp.oc_reg_block + * jcp.oc_simd_block; + } + else if (tblk1 == 0) { + input_ends[ithr] += jcp.oc_block * jcp.ic_block + * jcp.ic_simd_block * jcp.oc_reg_block + * jcp.oc_simd_block; + } + + if (first_tblk == 0 || tblk1 == 0) { + kernel_->gemm_loop_ker_first_iter( + &(Us(ithr, ifm1, ofm1, oj, oi, + 0, 0, 0, 0, 0)), + &(M(ofm1, tblk1, oj, oi, 0, 0, 0, 0, 0)), + &(V(ifm1, tblk1, oj, oi, 0, 0, 0, 0))); + } else { + kernel_->gemm_loop_ker( + &(Us(ithr, ifm1, ofm1, oj, oi, + 0, 0, 0, 0, 0)), + &(M(ofm1, tblk1, oj, oi, 0, 0, 0, 0, 0)), + &(V(ifm1, tblk1, oj, oi, 0, 0, 0, 0))); + } + ++first_tblk; + }); +} + + // Reduce diff-weights + { + float *output = &(U(0, 0, 0, 0, 0, 0, 0, 0, 0)); + size_t nelems = jcp.ic * jcp.oc * alpha * alpha; + float *input_ptrs[max_threads_number]; + for (int i = 0; i < nthreads; ++i) + input_ptrs[i] = output + nelems * (i + 1); + subarray_sum(nthreads, output, nelems, input_ptrs, + input_starts, input_ends); + } + + trans_ker_p.G = G_O_3x3_4x4; +PRAGMA_OMP(parallel firstprivate(trans_ker_p)) + { + parallel_nd_in_omp(jcp.nb_ic, jcp.nb_oc, jcp.oc_block, jcp.ic_block, jcp.oc_reg_block, + [&](int ifm1, int ofm1, int ofm2, int ifm2, int ofm3){ + int ofm = (ofm1 * jcp.oc_block + ofm2) + * jcp.oc_reg_block + ofm3; + int ifm = ifm1 * jcp.ic_block + ifm2; + trans_ker_p.src = (float *)&(U(ifm1, ofm1, 0, 0, + ofm2, ifm2, 0, ofm3, 0)); + trans_ker_p.dst = (float *)&(diff_weights(ofm, ifm, + 0, 0, 0, 0)); + kernel_->diff_weights_transform(&trans_ker_p); + }); + } + + if (jcp.with_bias) { + parallel_nd(jcp.oc / simd_w, [&](int ofm1) { + float* pbias = &(diff_bias(ofm1 * simd_w)); + float *pbias_prv = &(diff_bias_prv(0, ofm1 * simd_w)); + + const int blk_sz = ofm1 == jcp.oc / simd_w - 1 + ? jcp.oc_without_padding - ofm1 * simd_w : simd_w; + + PRAGMA_OMP_SIMD() + for (int ofm2 = 0; ofm2 < blk_sz; ++ofm2) { + pbias[ofm2] = pbias_prv[ofm2]; + } + + for (int ithr = 1; ithr < nthreads; ++ithr) { + pbias_prv = &(diff_bias_prv(ithr, ofm1 * simd_w)); + PRAGMA_OMP_SIMD() + for (int ofm2 = 0; ofm2 < blk_sz; ++ofm2) { + pbias[ofm2] += pbias_prv[ofm2]; + } + } + }); + } +} + +} +} +} +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.hpp new file mode 100644 index 000000000000..f1a56aac70ce --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.hpp @@ -0,0 +1,386 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_FP32_WINO_CONV_4x3_HPP +#define CPU_JIT_AVX512_CORE_FP32_WINO_CONV_4x3_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_avx512_core_fp32_wino_conv_4x3_kernel.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace winograd_avx512_core { +inline void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_winograd_conf_t &jcp) { + using namespace utils; + using namespace memory_tracking::names; + + size_t U_sz = (size_t)alpha * alpha * jcp.ic * jcp.oc; + size_t V_sz = (size_t)alpha * alpha * jcp.mb * jcp.ic * jcp.itiles + * jcp.jtiles; + size_t M_sz = (size_t)alpha * alpha * jcp.mb * jcp.oc * jcp.itiles + * jcp.jtiles; + + switch (jcp.sched_policy) { + case WSCHED_DATA_W_SGD: + V_sz = (size_t)jcp.nthr * alpha * alpha * jcp.nb_tile_block_ur + * jcp.tile_block_ur * jcp.ic; + M_sz = (size_t)jcp.nthr * alpha * alpha * jcp.nb_tile_block_ur + * jcp.tile_block_ur * jcp.oc; + break; + case WSCHED_WEI_SDGtWo: + U_sz = (size_t)jcp.nthr * (alpha * alpha * jcp.oc + * (jcp.ic / jcp.nb_ic) + jcp.ic * jcp.oc * jcp.kh * jcp.kw); + M_sz = (size_t)jcp.nthr * alpha * alpha * (jcp.ntiles / jcp.tile_block) + * (jcp.oc / jcp.nb_oc); + V_sz = (size_t)jcp.nthr * alpha * alpha * (jcp.ntiles / jcp.tile_block) + * (jcp.ic / jcp.nb_ic); + break; + case WSCHED_WEI_S_D_Giot_W: + U_sz = (size_t)(jcp.nthr + 1) * alpha * alpha * jcp.ic * jcp.oc; + M_sz = (size_t)alpha * alpha * jcp.oc * jcp.ntiles; + V_sz = (size_t)alpha * alpha * jcp.ic * jcp.ntiles; + break; + default: break; + } + + scratchpad.book(key_wino_U, sizeof(float) * U_sz, PAGE_2M); + scratchpad.book(key_wino_V, sizeof(float) * V_sz, PAGE_2M); + scratchpad.book(key_wino_M, sizeof(float) * M_sz, PAGE_2M); + + if (one_of(jcp.sched_policy, WSCHED_WEI_SDGtWo, WSCHED_WEI_S_D_Giot_W)) { + size_t br_sz = (size_t)jcp.nthr * jcp.oc; + scratchpad.book(key_conv_bia_reduction, sizeof(float) * br_sz, PAGE_2M); + } +} +} + +template +struct _jit_avx512_core_fp32_wino_conv_4x3_t { + + _jit_avx512_core_fp32_wino_conv_4x3_t( + const jit_conv_winograd_conf_t &jcp, const primitive_attr_t *attr) + : kernel_(nullptr), attr_(attr) { + kernel_ = new _jit_avx512_core_fp32_wino_conv_4x3_data_kernel(jcp); + } + + ~_jit_avx512_core_fp32_wino_conv_4x3_t() { delete kernel_; } + + protected: + void weight_transform_data(const jit_conv_winograd_conf_t &jcp, + float *wp, float *twp) const; + void input_transform_data(int image, + const jit_conv_winograd_conf_t &jcp, + float *inp, float *tinp) const; + void input_transform_tileblock_data(int tile_block, + const jit_conv_winograd_conf_t &jcp, + float *inp, float *tinp) const; + void output_transform_data(int image, + const jit_conv_winograd_conf_t &jcp, + const post_ops_t &p_ops, float *toutp, float *pout_b, + float *bias) const; + void output_transform_tileblock_data(int tile_block, + const jit_conv_winograd_conf_t &jcp, const post_ops_t &p_ops, + float *toutp, float *outp, float *bias) const; + void _execute_data_W_S_G_D(float *inp_ptr, float *out_ptr, + float *wei_ptr, float *bias_ptr, + const memory_tracking::grantor_t &scratchpad) const; + void _execute_data_W_SGD(float *inp_ptr, float *out_ptr, + float *wei_ptr, float *bias_ptr, + const memory_tracking::grantor_t &scratchpad) const; + _jit_avx512_core_fp32_wino_conv_4x3_data_kernel *kernel_; + const primitive_attr_t *attr_; +}; + +struct jit_avx512_core_fp32_wino_conv_4x3_fwd_t + : _jit_avx512_core_fp32_wino_conv_4x3_t + , public cpu_primitive_t + { + struct pd_t : public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_wino_4x3:", avx512_core, ""), + jit_avx512_core_fp32_wino_conv_4x3_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_core_fp32_wino_conv_4x3_fwd_kernel:: + init_conf(jcp_, *desc(), src_md_, weights_md_, dst_md_, + *attr()); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + auto scratchpad = scratchpad_registry().registrar(); + winograd_avx512_core::init_scratchpad(scratchpad, jcp_); + + return status; + } + + jit_conv_winograd_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + auto wei_fmt = desc()->prop_kind == prop_kind::forward_training + ? (with_groups() ? gOIhw16i16o : OIhw16i16o) : any; + return set_default_formats_common(nChw16c, wei_fmt, nChw16c); + } + }; + + jit_avx512_core_fp32_wino_conv_4x3_fwd_t(const pd_t *apd) + : _jit_avx512_core_fp32_wino_conv_4x3_t(apd->jcp_, apd->attr()) + , cpu_primitive_t(apd, true) + {} + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto src = CTX_IN_MEM(const float *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const float *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const float *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(float *, MKLDNN_ARG_DST); + + auto scratchpad = this->scratchpad(ctx); + + switch ((pd()->jcp_).sched_policy) { + case WSCHED_DATA_W_S_G_D: + this->_execute_data_W_S_G_D((float *)src, dst, (float *)weights, + (float *)bias, scratchpad); + break; + case WSCHED_DATA_W_SGD: + this->_execute_data_W_SGD((float *)src, dst, (float *)weights, + (float *)bias, scratchpad); + break; + default: + break; + } + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct jit_avx512_core_fp32_wino_conv_4x3_bwd_data_t + : _jit_avx512_core_fp32_wino_conv_4x3_t, + public cpu_primitive_t { + struct pd_t : public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_wino_4x3:", avx512_core, ""), + jit_avx512_core_fp32_wino_conv_4x3_bwd_data_t); + + status_t init() { + bool ok = true + && mkldnn_thr_syncable() + && desc()->prop_kind == prop_kind::backward_data + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::f32, data_type::f32, + data_type::undef, data_type::f32, data_type::f32) + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_core_fp32_wino_conv_4x3_bwd_data_kernel + ::init_conf(jcp_, *desc(), *diff_src_md(), *weights_md(), + *diff_dst_md()); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + auto scratchpad = scratchpad_registry().registrar(); + winograd_avx512_core::init_scratchpad(scratchpad, jcp_); + + return status; + } + + jit_conv_winograd_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + auto wei_fmt = with_groups() ? gOIhw16i16o : OIhw16i16o; + return set_default_formats_common(nChw16c, wei_fmt, nChw16c); + } + }; + + jit_avx512_core_fp32_wino_conv_4x3_bwd_data_t(const pd_t *apd) + : _jit_avx512_core_fp32_wino_conv_4x3_t(apd->jcp_, apd->attr()) + , cpu_primitive_t(apd, true) + {} + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto diff_dst = CTX_IN_MEM(const float *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const float *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_SRC); + + auto scratchpad = this->scratchpad(ctx); + + switch ((pd()->jcp_).sched_policy) { + case WSCHED_DATA_W_S_G_D: + this->_execute_data_W_S_G_D((float *)diff_dst, diff_src, + (float *)weights, NULL, scratchpad); + break; + + case WSCHED_DATA_W_SGD: + this->_execute_data_W_SGD((float *)diff_dst, diff_src, + (float *)weights, NULL, scratchpad); + break; + + default: + break; + } + + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t + : public cpu_primitive_t { + struct pd_t : public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_wino_4x3:", avx512_core, ""), + jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t); + + status_t init() { + bool ok = true + && mkldnn_thr_syncable() + && desc()->prop_kind == prop_kind::backward_weights + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && set_default_formats(); + if (!ok) + return status::unimplemented; + + status_t status = + jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel:: + init_conf(jcp_, *desc(), *src_md(), *diff_dst_md(), + *diff_weights_md()); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + auto scratchpad = scratchpad_registry().registrar(); + winograd_avx512_core::init_scratchpad(scratchpad, jcp_); + + return status; + } + + jit_conv_winograd_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + auto wei_fmt = with_groups() ? gOIhw16i16o : OIhw16i16o; + return set_default_formats_common(nChw16c, wei_fmt, nChw16c); + } + }; + + jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd, true) + , kernel_(nullptr) + { + kernel_ = new jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel( + pd()->jcp_); + } + + ~jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_t() + { + delete kernel_; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto diff_dst = CTX_IN_MEM(const float *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const float *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_BIAS); + + switch (kernel_->jcp.sched_policy) { + case WSCHED_WEI_SDGtWo: + _execute_backward_weights_SDGtWo(src, diff_dst, diff_weights, + diff_bias, scratchpad(ctx)); + break; + case WSCHED_WEI_S_D_Giot_W: + _execute_backward_weights_S_D_Giot_W(src, diff_dst, diff_weights, + diff_bias, scratchpad(ctx)); + break; + default: + assert(kernel_->jcp.sched_policy != WSCHED_INVALID); + break; + } + return status::success; + } + +private: + void _execute_backward_weights_SDGtWo(const float *src, + const float *diff_dst, float *diff_weights, float *diff_bias, + const memory_tracking::grantor_t &scratchpad) const; + void _execute_backward_weights_S_D_Giot_W(const float *src, + const float *diff_dst, float *diff_weights, float *diff_bias, + const memory_tracking::grantor_t &scratchpad) const; + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel *kernel_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp new file mode 100644 index 000000000000..0d64a2d13a46 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp @@ -0,0 +1,2596 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include + +#include "jit_avx512_core_fp32_wino_conv_4x3_kernel.hpp" + +#define GET_OFF(field) offsetof(jit_wino_transform_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace { + +using namespace mkldnn::impl::utils; + +unsigned int L1_cache_size = get_cache_size(1, true); +unsigned int L2_cache_size = get_cache_size(2, true); +unsigned int LLC_data_size = get_cache_size(3, false); + +// the test funtion takes jcp, the candidate and the current best. +// it returns true if the new candidate is better +int get_divisor_satisfying_cond(jit_conv_winograd_conf_t &jcp, int number, + int default_best, bool (*test)(jit_conv_winograd_conf_t &, int, int)) +{ + int best_divisor = default_best; + auto test_num + = [&best_divisor, test](jit_conv_winograd_conf_t &jcp, int num) { + if (test(jcp, num, best_divisor)) { + best_divisor = num; + } + }; + + for (int divisor = 1; divisor <= ::sqrt(number); divisor++) { + if (number % divisor == 0) { + test_num(jcp, divisor); + test_num(jcp, number / divisor); + } + } + + return best_divisor; +} + +namespace { +bool is_winograd_faster_than_direct(const jit_conv_winograd_conf_t &jcp) { + /* Determines if current winograd implementation is faster than direct. + Following conditions are empirical and based on performance data */ + unsigned int ncores_per_socket = + cpu.getNumCores(Xbyak::util::IntelCpuTopologyLevel::CoreLevel); + unsigned int nthreads = mkldnn_get_max_threads(); + + if (jcp.prop_kind == prop_kind::forward_inference) { + return jcp.mb >= 4; + } else if (nthreads > ncores_per_socket) { + double src_dst_transforms_per_core = alpha * alpha + * (jcp.ic + jcp.oc) + * jcp.mb * ((jcp.oh + tile_size - 1) / tile_size) + * ((jcp.ow + tile_size - 1) / tile_size) + * sizeof(float) / 1024. / 1024. / nthreads; + double wei_transform = alpha * alpha + * jcp.ic * jcp.oc * sizeof(float) /1024. / 1024.; + + if (jcp.prop_kind == prop_kind::backward_weights) { + if (src_dst_transforms_per_core < 0.3 + || (src_dst_transforms_per_core <= 28 && wei_transform < 4)) + return false; + else + return true; + } else { + if (src_dst_transforms_per_core < 2.0 || wei_transform < 0.02) + return false; + } + } + + return jcp.mb > 8; +} +} + +/* assumes 512 bits registers */ +/* TODO: add support for strides */ +/* TODO: handle the prefetch distance automatically */ +typedef enum cache_t_ { L1, L2, L3 } cache_t; + +template +struct prefetcher_t { + prefetcher_t(jit_generator *generator, Xbyak::Reg64 reg_base_addr, + cache_t cache_type, size_t block_size, /* in number of elements*/ + int nb_instructions_in_block, int fma_ipc) + : cg_(generator) + , reg_base_addr_(reg_base_addr) + , cache_type_(cache_type) + , cache_block_size_(block_size) + { + nb_cache_lines_to_prefetch_ = cache_block_size_ / (64 / sizeof(data_t)); + prefetch_spread_ + = div_up(nb_instructions_in_block, nb_cache_lines_to_prefetch_); + prefetch_blk_ + = div_up(nb_cache_lines_to_prefetch_, nb_instructions_in_block); + + /* assumption: when fetch in Li, data is already in L(i+1) */ + int cache_latency; + switch (cache_type_) { + case L1: cache_latency = 14; break; + case L2: cache_latency = 250; break; + case L3: cache_latency = 250; break; + } + + prefetch_distance_ = div_up(cache_latency, nb_cache_lines_to_prefetch_); + } + + void prefetch(int instruction_number) + { + if (instruction_number % prefetch_spread_ == 0) { + for (int i = 0; (i < prefetch_blk_) + && (prefetches_issued_ < nb_cache_lines_to_prefetch_); + i++, prefetches_issued_++) { + prefetch_inst_(cg_->EVEX_compress_addr( + reg_base_addr_, (cache_block_size_ * prefetch_distance_) + * sizeof(data_t) + + (prefetches_issued_ * 64))); + } + } + } + +private: + void prefetch_inst_(const Xbyak::Address &addr) + { + switch (cache_type_) { + case L1: cg_->prefetcht0(addr); break; + case L2: cg_->prefetcht1(addr); break; + case L3: cg_->prefetcht2(addr); break; + default: + break; // TODO: raise an exception or put an assert + } + } + + jit_generator *cg_; + Xbyak::Reg64 reg_base_addr_; + cache_t cache_type_; + int cache_block_size_ = 0; + int nb_cache_lines_to_prefetch_ = 0; + int prefetches_issued_ = 0; + int prefetch_spread_ = 0; + int prefetch_blk_ = 0; + int prefetch_distance_ = 0; +}; + +// utilities to support kernel parameter selection +bool check_L2_block_per_thread(jit_conv_winograd_conf_t &jcp, + int dimN_block, float C2_min, float C2_max) { + float block_size = alpha * alpha * (2*(jcp.oc + jcp.ic) + * dimN_block * jcp.dimN_reg_block + + div_up(jcp.ic * jcp.oc,mkldnn_get_max_threads())) * (float)sizeof(float); + float L2_lb = C2_min * L2_cache_size; + float L2_ub = C2_max * L2_cache_size; + return (block_size > L2_lb && block_size < L2_ub); +} + +bool check_L1_block_gemm(jit_conv_winograd_conf_t &jcp, int dimK_block, + int dimM_block, float C1_min, float C1_max) { + float gemm_block_size = (dimM_block * jcp.dimM_simd_block * dimK_block + * jcp.dimK_reg_block * jcp.dimM_reg_block + + dimK_block * jcp.dimK_reg_block * jcp.dimN_reg_block + + dimM_block * jcp.dimM_simd_block * jcp.dimN_reg_block) + * (float)sizeof(float); + float L1_lb = C1_min * L1_cache_size; + float L1_ub = C1_max * L1_cache_size; + return (gemm_block_size > L1_lb && gemm_block_size < L1_ub); +} +bool check_cond1(int dimN_reg_block, int dimK_block, int dimK_reg_block, + int dimM_block, int dimM_reg_block, int dimM_simd_block, float C) +{ + float lhs = (dimM_block * dimN_reg_block * dimM_simd_block * dimM_reg_block + + dimM_block * dimK_block * dimK_reg_block + * dimM_simd_block * dimM_reg_block + + dimK_block * dimN_reg_block * dimK_reg_block) + * (float)sizeof(float); + float rhs = C * L1_cache_size; + return (lhs < rhs); +} +bool check_cond1_bis(int dimN_reg_block, int dimK_block, int dimK_reg_block, + int dimM_block, int dimM_reg_block, int dimM_simd_block, float C) +{ + float lhs = (dimM_block * dimM_reg_block * dimK_block * dimK_reg_block + * dimM_simd_block + dimK_block * dimN_reg_block * dimK_reg_block) + * (float)sizeof(float); + float rhs = C * L1_cache_size; + return (lhs < rhs); +} +bool check_cond2(int nb_dimN_reg_block, int dimN_reg_block, int dimK_nb_block, + int dimK_block, int dimK_reg_block, int dimM_block, int dimM_reg_block, + int dimM_simd_block, float C) +{ + float lhs = (nb_dimN_reg_block * dimM_block * dimN_reg_block + * dimM_simd_block * dimM_reg_block + + dimK_nb_block * dimM_block * dimK_block * dimK_reg_block + * dimM_simd_block * dimM_reg_block + + nb_dimN_reg_block * dimK_nb_block * dimK_block + * dimN_reg_block * dimK_reg_block) + * (float)sizeof(float); + float rhs = C * L2_cache_size; + return (lhs < rhs); +} + +bool check_kernel_cond(int dimM_block, int dimM_reg_block, int dimM_simd_block, + int dimN_block, int dimN_reg_block, int dimK, float C1, float C2) +{ + float A_size = dimM_block * dimM_reg_block * dimM_simd_block * dimK + * (float)sizeof(float); + float B_size = dimN_block * dimN_reg_block * dimK + * (float)sizeof(float); + return (A_size > C1 * L2_cache_size && B_size > C2 * L2_cache_size); +} +} + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +void _jit_avx512_core_fp32_wino_conv_4x3_data_kernel::gemm_loop_generate() +{ + // for (int dimM_block =0; dimM_block < jcp.dimM_block; dimM_block++) + // for (int dimM_reg_block =0; dimM_reg_block < jcp.dimM_reg_block; + // dimM_reg_block++) // unrolled + // for (int dimK_block = 0; dimK_block < jcp.dimK_block; dimK_block++) + // for (int dimK_reg_block= 0; dimK_reg_block < jcp.dimK_reg_block; + // dimK_reg_block++) // unrolled + // for (int tile =0; tile < jcp.dimN_reg_block; tile++) + // C[dimM_block][dimM_reg_block][tile] += + // A[dimM_block][dimM_reg_block][dimK_block][dimK_reg_block] + // * broadcast(B[dimK_block][tile][dimK_reg_block]); + // Notes: + // jcp.kernel_kind defines embedded or explicit broadcast + // dimM_reg_block=1 for embedded bcast kernel + + auto zmm_srcA = [=]() { + return Xbyak::Zmm(0); + }; + auto zmm_srcB = [=](int tile) { + int idx = 1 + tile; + assert(idx < 1 + jcp.dimN_reg_block); + return Xbyak::Zmm(idx); + }; + auto zmm_dstC = [=](int dimM_reg_block, int tile) { + int idx{0}; + if (jcp.kernel_kind == embd_bcast) + idx = 1 + tile; + else + idx = 1 + jcp.dimN_reg_block + + dimM_reg_block * jcp.dimN_reg_block + tile; + assert(idx < 32); + return Xbyak::Zmm(idx); + }; + + auto prepare_output = [=]() { + for (int dimM_reg_block = 0; dimM_reg_block < jcp.dimM_reg_block; + dimM_reg_block++) { + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + Zmm zmm = zmm_dstC(dimM_reg_block, tile); + vpxord(zmm, zmm, zmm); + } + } + }; + auto store_output = [=](bool output_is_aligned) { + Label save; + cmp(reg_is_beta_zero, 0); + je(save, T_NEAR); + + for (int dimM_reg_block = 0; dimM_reg_block < jcp.dimM_reg_block; + dimM_reg_block++) { + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + Zmm zmm = zmm_dstC(dimM_reg_block,tile); + int output_offset + = jcp.dimN_reg_block * dimM_reg_block * 64 + tile * 64; + vaddps(zmm, zmm, EVEX_compress_addr(reg_dstC, output_offset)); + } + } + + L(save); + for (int dimM_reg_block = 0; dimM_reg_block < jcp.dimM_reg_block; + dimM_reg_block++) { + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + Zmm zmm = zmm_dstC(dimM_reg_block,tile); + int output_offset + = jcp.dimN_reg_block * dimM_reg_block * 64 + tile * 64; + + // In W_SGD, output will be reused. + if (output_is_aligned + && jcp.dimK_nb_block == 1 + && jcp.sched_policy == WSCHED_DATA_W_S_G_D + && (jcp.dimN * jcp.dimM * alpha * alpha + * sizeof(float) > 2 * LLC_data_size)) + vmovntps(EVEX_compress_addr(reg_dstC, output_offset), zmm); + else vmovups(EVEX_compress_addr(reg_dstC, output_offset), zmm); + } + } + }; + + auto inner_loops = [=]() { + Label dimM_block_loop, dimK_block_loop; + + if (jcp.dimM_block > 1) { + mov(reg_dimM_block_loop_cnt, jcp.dimM_block); + L(dimM_block_loop); + } + + prepare_output(); + + if (jcp.dimK_block > 1) { + mov(reg_dimK_block_loop_cnt, jcp.dimK_block); + L(dimK_block_loop); + } + + for (int dimK_reg_block = 0; + dimK_reg_block < jcp.dimK_reg_block; + dimK_reg_block ++) { + + if (jcp.kernel_kind == expl_bcast) { + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + vbroadcastss(zmm_srcB(tile), + ptr[reg_srcB + 64 * tile + dimK_reg_block * 4]); + } + } + + /* Performing the fmas */ + + for (int dimM_reg_block = 0; dimM_reg_block < jcp.dimM_reg_block; + dimM_reg_block++) { + + vmovups(zmm_srcA(), + zword[reg_srcA + + jcp.dimK_reg_block * jcp.dimK_block * 64 + * dimM_reg_block + + dimK_reg_block * 64] + ); + + for (int tile = 0; tile < jcp.dimN_reg_block; tile++) { + if (jcp.kernel_kind == expl_bcast) + vfmadd231ps(zmm_dstC(dimM_reg_block, tile), zmm_srcA(), + zmm_srcB(tile)); + else + vfmadd231ps(zmm_dstC(dimM_reg_block, tile), zmm_srcA(), + EVEX_compress_addr(reg_srcB, + 64 * tile + dimK_reg_block * 4, true)); + } + } + } + add(reg_srcA, jcp.dimK_reg_block * 64); + add(reg_srcB, jcp.dimN_reg_block * 64); + if (jcp.dimK_block > 1) { + sub(reg_dimK_block_loop_cnt, 1); + jnz(dimK_block_loop); + } + + Label unaligned_store, end_store; + test(reg_dstC, cpu_isa_traits::vlen - 1); + jnz(unaligned_store, T_NEAR); + store_output(true); + jmp(end_store, T_NEAR); + L(unaligned_store); { + store_output(false); + } + L(end_store); + + if (jcp.dimM_block > 1) { + sub(reg_srcB, jcp.dimK_block * jcp.dimN_reg_block * 64); + add(reg_dstC, jcp.dimM_reg_block * jcp.dimN_reg_block * 64); + if (jcp.kernel_kind == expl_bcast) { + add(reg_srcA, + (jcp.dimM_reg_block-1) * jcp.dimK_reg_block * 64 + * jcp.dimK_block); + } + sub(reg_dimM_block_loop_cnt, 1); + jnz(dimM_block_loop); + } + }; + + /* Preamble */ + preamble(); + + /* kernel */ + inner_loops(); + + /* Postamble */ + postamble(); + ret(); +} + +void _jit_avx512_core_fp32_wino_conv_4x3_data_kernel + ::weights_transform_data_ker_generate() +{ + bool is_fwd = one_of(jcp.prop_kind, + mkldnn_forward_training, mkldnn_forward_inference); + int kh = jcp.kh; + int kw = jcp.kw; + + auto zmm_temp = Xbyak::Zmm(31); + auto zmm_zero = Xbyak::Zmm(30); + + auto zmm_M = [=](int i) { + return Xbyak::Zmm(i); + }; + auto zmm_MT = [=](int i) { + return Xbyak::Zmm(i + simd_w); + }; + + auto zmm_G = [=](int i) { + return Xbyak::Zmm(i); + }; + auto zmm_F = [=](int i) { + return Xbyak::Zmm(alpha + i); + }; + auto zmm_T = [=](int i) { + return Xbyak::Zmm(alpha + 3 + i); + }; + auto zmm_t = [=](int i) { + return Xbyak::Zmm(2 * alpha + 3 + i); + }; + + auto zmm_load = [=](int i) { + return Xbyak::Zmm(i); + }; + + auto init_G = [=]() { + mov(wreg_temp, ptr[param1 + GET_OFF(G)]); + for (int i = 0; i < alpha; i++) { + vbroadcastss(zmm_G(i), ptr[wreg_temp + i * typesize]); + } + vpxord(zmm_zero, zmm_zero, zmm_zero); + }; + + auto trans16x16 = [=]() { + for (int i = 0; i < simd_w; i+=2 ) { + vmovups(zmm_M(i), ptr[wreg_M + i * simd_w * 4]); + vmovups(zmm_M(i+1), ptr[wreg_M + (i + 1) * simd_w * 4]); + vunpcklps(zmm_MT(i), zmm_M(i), zmm_M(i+1)); + vunpckhps(zmm_MT(i+1), zmm_M(i), zmm_M(i+1)); + } + for (int i = 0; i < simd_w; i+=4 ) { + vunpcklpd(zmm_M(i), zmm_MT(i), zmm_MT(i+2)); + vunpckhpd(zmm_M(i+1), zmm_MT(i), zmm_MT(i+2)); + vunpcklpd(zmm_M(i+2), zmm_MT(i+1), zmm_MT(i+3)); + vunpckhpd(zmm_M(i+3), zmm_MT(i+1), zmm_MT(i+3)); + } + for (int i = 0; i < simd_w; i += 8) { + vshuff32x4(zmm_MT(i), zmm_M(i), zmm_M(i + 4), 0x88); + vshuff32x4(zmm_MT(i+1), zmm_M(i+1), zmm_M(i + 5), 0x88); + vshuff32x4(zmm_MT(i+2), zmm_M(i+2), zmm_M(i + 6), 0x88); + vshuff32x4(zmm_MT(i+3), zmm_M(i+3), zmm_M(i + 7), 0x88); + vshuff32x4(zmm_MT(i+4), zmm_M(i), zmm_M(i + 4), 0xdd); + vshuff32x4(zmm_MT(i+5), zmm_M(i+1), zmm_M(i + 5), 0xdd); + vshuff32x4(zmm_MT(i+6), zmm_M(i+2), zmm_M(i + 6), 0xdd); + vshuff32x4(zmm_MT(i+7), zmm_M(i+3), zmm_M(i + 7), 0xdd); + } + { + int i = 0; + int mask = 0x88; + vshuff32x4(zmm_M(0), zmm_MT(i), zmm_MT(i + 8), mask); + vmovups(ptr[wreg_MT + 0 * 16 * 4], zmm_M(0)); + vshuff32x4(zmm_M(1), zmm_MT(i + 1), zmm_MT(i + 9), mask); + vmovups(ptr[wreg_MT + 1 * 16 * 4], zmm_M(1)); + vshuff32x4(zmm_M(2), zmm_MT(i + 2), zmm_MT(i + 10), mask); + vmovups(ptr[wreg_MT + 2 * 16 * 4], zmm_M(2)); + vshuff32x4(zmm_M(3), zmm_MT(i + 3), zmm_MT(i + 11), mask); + vmovups(ptr[wreg_MT + 3 * 16 * 4], zmm_M(3)); + vshuff32x4(zmm_M(4), zmm_MT(i + 4), zmm_MT(i + 12), mask); + vmovups(ptr[wreg_MT + 4 * 16 * 4], zmm_M(4)); + vshuff32x4(zmm_M(5), zmm_MT(i + 5), zmm_MT(i + 13), mask); + vmovups(ptr[wreg_MT + 5 * 16 * 4], zmm_M(5)); + vshuff32x4(zmm_M(6), zmm_MT(i + 6), zmm_MT(i + 14), mask); + vmovups(ptr[wreg_MT + 6 * 16 * 4], zmm_M(6)); + vshuff32x4(zmm_M(7), zmm_MT(i + 7), zmm_MT(i + 15), mask); + vmovups(ptr[wreg_MT + 7 * 16 * 4], zmm_M(7)); + mask = 0xdd; + vshuff32x4(zmm_M(8), zmm_MT(i), zmm_MT(i + 8), mask); + vmovups(ptr[wreg_MT + 8 * 16 * 4], zmm_M(8)); + vshuff32x4(zmm_M(9), zmm_MT(i + 1), zmm_MT(i + 9), mask); + vmovups(ptr[wreg_MT + 9 * 16 * 4], zmm_M(9)); + vshuff32x4(zmm_M(10), zmm_MT(i + 2), zmm_MT(i + 10), mask); + vmovups(ptr[wreg_MT + 10 * 16 * 4], zmm_M(10)); + vshuff32x4(zmm_M(11), zmm_MT(i + 3), zmm_MT(i + 11), mask); + vmovups(ptr[wreg_MT + 11 * 16 * 4], zmm_M(11)); + vshuff32x4(zmm_M(12), zmm_MT(i + 4), zmm_MT(i + 12), mask); + vmovups(ptr[wreg_MT + 12 * 16 * 4], zmm_M(12)); + vshuff32x4(zmm_M(13), zmm_MT(i + 5), zmm_MT(i + 13), mask); + vmovups(ptr[wreg_MT + 13 * 16 * 4], zmm_M(13)); + vshuff32x4(zmm_M(14), zmm_MT(i + 6), zmm_MT(i + 14), mask); + vmovups(ptr[wreg_MT + 14 * 16 * 4], zmm_M(14)); + vshuff32x4(zmm_M(15), zmm_MT(i + 7), zmm_MT(i + 15), mask); + vmovups(ptr[wreg_MT + 15 * 16 * 4], zmm_M(15)); + } + }; + + auto load_src = [=]() { + mov(wreg_src, ptr[param1 + GET_OFF(src)]); + mov(wreg_F, ptr[param1 + GET_OFF(M)]); + for (int j = 0; j < kh; j++) { + for (int i = 0; i < kw; i++) { + if (is_fwd) { + for (int v1 = 0; v1 < simd_w; v1++) { + int offset_src = (j * kw * simd_w * simd_w + + i * simd_w * simd_w + v1 * simd_w) * typesize; + int offset_F = (j * kw * simd_w * simd_w + + i * simd_w * simd_w + v1 * simd_w) * typesize; + vmovups(zmm_temp, ptr[wreg_src + offset_src]); + vmovups(ptr[wreg_F + offset_F], zmm_temp); + } + } else { + int offset_src = ((2 - j) * kw * simd_w * simd_w + + (2 - i) * simd_w * simd_w) * typesize; + int offset_F = (j * kw * simd_w * simd_w + + i * simd_w * simd_w) * typesize; + lea(wreg_M, ptr[wreg_src + offset_src]); + lea(wreg_MT, ptr[wreg_F + offset_F]); + trans16x16(); + } + } + } + }; + + auto store_dst = [=]() { + mov(wreg_dst, ptr[param1 + GET_OFF(dst)]); + mov(wreg_Fw, ptr[param1 + GET_OFF(Mw)]); + + Label Loop_j; + mov(wreg_cnt_j, 0); + mov(wreg_dst_aux, wreg_dst); + mov(wreg_Fw_aux, wreg_Fw); + + int dim5 = jcp.dimK_nb_block * (jcp.dimM_block * jcp.dimM_reg_block) + * jcp.dimK_block * simd_w * simd_w; + + L(Loop_j); + { + for (int i = 0; i < alpha; i++) { + // touch pages + vmovups(zmm_load(0), ptr[wreg_Fw_aux + + (i * simd_w * simd_w) * typesize]); + mov(wreg_dst_idx, i * dim5 * typesize); + vmovntps(ptr[wreg_dst_aux + wreg_dst_idx], zmm_load(0)); + } + for (int i = 0; i < alpha; i++) { + for (int v1 = 1; v1 < simd_w; v1++) { + int offset_Fw = (i * simd_w * simd_w + v1 * simd_w) + * typesize; + vmovups(zmm_load(v1), ptr[wreg_Fw_aux + offset_Fw]); + } + mov(wreg_dst_idx, i * dim5 * typesize); + for (int v1 = 1; v1 < simd_w; v1++) { + int offset_dst = v1 * simd_w * typesize; + vmovntps(ptr[wreg_dst_aux + wreg_dst_idx + offset_dst], + zmm_load(v1)); + } + } + add(wreg_Fw_aux, alpha * simd_w * simd_w * typesize); + add(wreg_dst_aux, alpha * dim5 * typesize); + add(wreg_cnt_j, 1); + cmp(wreg_cnt_j, alpha); + jl(Loop_j, T_NEAR); + } + }; + + auto trans_W_4x4_3x3 = [=]() { + auto fma4 = [=](Zmm dst, Zmm a, Zmm b, Zmm c) { + vmovups(dst, a); + vfmadd231ps(dst, b, c); + }; + auto fms4 = [=](Zmm dst, Zmm a, Zmm b, Zmm c) { + vmulps(zmm_temp, b, c); + vsubps(dst, a, zmm_temp); + }; + auto fnms4 = [=](Zmm dst, Zmm a, Zmm b, Zmm c) { + vsubps(dst, zmm_zero, a); + vfnmadd231ps(dst, b, c); + }; + + mov(wreg_Fw, ptr[param1 + GET_OFF(Mw)]); + mov(wreg_F, ptr[param1 + GET_OFF(M)]); + mov(wreg_T, ptr[param1 + GET_OFF(T)]); + + Label Loop_j; + mov(wreg_cnt_j, 0); + L(Loop_j); + mov(wreg_F_aux, wreg_F); + mov(wreg_Fw_aux, wreg_Fw); + mov(wreg_temp, wreg_cnt_j); + shl(wreg_temp, 4 + 2); + lea(wreg_F_aux, ptr[wreg_F + wreg_temp]); + lea(wreg_Fw_aux, ptr[wreg_Fw + wreg_temp]); + + for (int i = 0; i < 3; i++) { + for (int idx = 0; idx < 3; idx ++) { + vmovups(zmm_F(idx), ptr[wreg_F_aux + (idx * 3 * simd_w + * simd_w + i * simd_w * simd_w) * typesize]); + } + vmulps(zmm_t(0), zmm_G(0), zmm_F(2)); + fnms4(zmm_t(1), zmm_t(0), zmm_G(1), zmm_F(0)); + fma4(zmm_t(2), zmm_t(0), zmm_G(2), zmm_F(0)); + + vmulps(zmm_T(0), zmm_G(3), zmm_F(0)); + fms4(zmm_T(1), zmm_t(1), zmm_G(4), zmm_F(1)); + fma4(zmm_T(2), zmm_t(1), zmm_G(4), zmm_F(1)); + fma4(zmm_T(3), zmm_t(2), zmm_G(5), zmm_F(1)); + fms4(zmm_T(4), zmm_t(2), zmm_G(5), zmm_F(1)); + vmovaps(zmm_T(5), zmm_F(2)); + + for (int idx = 0; idx < 6; idx ++) { + vmovups(ptr[wreg_T + (idx * 3 * simd_w + i * simd_w) + * typesize], zmm_T(idx)); + } + } + for (int i = 0; i < 6; i++) { + + for (int idx = 0; idx < 3; idx ++) { + vmovups(zmm_T(idx), ptr[wreg_T + + (i * 3 * simd_w + idx * simd_w) * typesize]); + } + vmulps(zmm_t(0), zmm_G(0), zmm_T(2)); + fnms4(zmm_t(1), zmm_t(0), zmm_G(1), zmm_T(0)); + fma4(zmm_t(2), zmm_t(0), zmm_G(2), zmm_T(0)); + + vmulps(zmm_F(0), zmm_G(3), zmm_T(0)); + fms4(zmm_F(1), zmm_t(1), zmm_G(4), zmm_T(1)); + fma4(zmm_F(2), zmm_t(1), zmm_G(4), zmm_T(1)); + fma4(zmm_F(3), zmm_t(2), zmm_G(5), zmm_T(1)); + fms4(zmm_F(4), zmm_t(2), zmm_G(5), zmm_T(1)); + vmovaps(zmm_F(5), zmm_T(2)); + + for (int l = 0; l < 6; l++) { + vmovups(ptr[wreg_Fw_aux + (i * 6 * simd_w * simd_w + + l * simd_w * simd_w) * typesize], zmm_F(l)); + } + } + add(wreg_cnt_j, 1); + cmp(wreg_cnt_j, 16); + jl(Loop_j, T_NEAR); + }; + + auto inner_loops = [=]() { + load_src(); + init_G(); + trans_W_4x4_3x3(); + store_dst(); + }; + + preamble(); + inner_loops(); + postamble(); +} + +void _jit_avx512_core_fp32_wino_conv_4x3_data_kernel + ::output_transform_data_ker_generate() +{ + bool is_fwd = one_of(jcp.prop_kind, + mkldnn_forward_training, mkldnn_forward_inference); + int outw = is_fwd ? jcp.ow : jcp.iw; + int outh = is_fwd ? jcp.oh : jcp.ih; + bool not_tiled = jcp.sched_policy == WSCHED_DATA_W_S_G_D; + bool with_bias = jcp.with_bias; + bool with_relu = jcp.with_eltwise; + bool with_relu_postsum = jcp.with_relu_postsum; + bool with_sum = jcp.with_sum; + + auto zmm_zero = Xbyak::Zmm(0); + auto zmm_temp = Xbyak::Zmm(31); + auto zmm_G = [=](int i) { + return Xbyak::Zmm(1 + i); + }; + auto zmm_O = [=](int i) { + return Xbyak::Zmm(1 + alpha + i); + }; + auto zmm_T = [=](int i) { + return Xbyak::Zmm(1 + 2 * alpha + i); + }; + auto zmm_t = [=](int i) { + return Xbyak::Zmm(1 + 3 * alpha + i); + }; + + auto init_G = [=]() { + mov(oreg_temp, ptr[param1 + GET_OFF(G)]); + for (int i = 0; i < 6; i++) { + vbroadcastss(zmm_G(i), ptr[oreg_temp + i * typesize]); + } + }; + + auto load_src = [=]() { + mov(oreg_Ow, ptr[param1 + GET_OFF(Mw)]); + mov(oreg_src, ptr[param1 + GET_OFF(src)]); + + mov(oreg_nb_tile_block_ur, ptr[param1 + GET_OFF(nb_tile_block_ur)]); + imul(oreg_nb_tile_block_ur, oreg_nb_tile_block_ur, + (jcp.dimM_block * jcp.dimM_reg_block) * jcp.dimN_reg_block + * jcp.dimM_simd_block * typesize); + add(oreg_src, oreg_nb_tile_block_ur); + + mov(oreg_tile_block_ur, ptr[param1 + GET_OFF(tile_block_ur)]); + imul(oreg_tile_block_ur, oreg_tile_block_ur, + jcp.dimM_simd_block * typesize); + add(oreg_src, oreg_tile_block_ur); + + if (not_tiled) { + mov(oreg_tile_block, ptr[param1 + GET_OFF(tile_block)]); + imul(oreg_tile_block, oreg_tile_block, + jcp.dimM_nb_block * alpha * alpha * jcp.dimN_block + * (jcp.dimM_block * jcp.dimM_reg_block) * jcp.dimN_reg_block + * jcp.dimM_simd_block * typesize); + add(oreg_src, oreg_tile_block); + } + + int last4dim = jcp.dimN_block * (jcp.dimM_block * jcp.dimM_reg_block) + * jcp.dimN_reg_block * jcp.dimM_simd_block * typesize; + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + int j_base_offset = j * alpha * last4dim; + int i_base_offset = i * last4dim; + vmovups(zmm_temp, ptr[oreg_src + j_base_offset + i_base_offset]); + vmovups(ptr[oreg_Ow + (j * alpha * simd_w + i * simd_w) + * typesize], zmm_temp); + } + } + }; + + auto store_dst = [=]() { + vpxord(zmm_zero, zmm_zero, zmm_zero); + mov(oreg_dst, ptr[param1 + GET_OFF(dst)]); + mov(oreg_O, ptr[param1 + GET_OFF(M)]); + mov(oreg_ydim, ptr[param1 + GET_OFF(tj)]); + shl(oreg_ydim, 2); // tj * tile_size (==4) + mov(oreg_xdim, ptr[param1 + GET_OFF(ti)]); + shl(oreg_xdim, 2); // ti * tilesize (==4) + + if (with_bias) + mov(oreg_bias, ptr[param1 + GET_OFF(bias)]); + + auto store_one = [=](int j, int i, bool is_aligned) { + auto zmm_O = Xbyak::Zmm(31); + auto zmm_relu_ns = Xbyak::Zmm(30); + auto xmm_relu_ns = Xbyak::Xmm(30); + int offset = (j * tile_size * simd_w + i * simd_w) * typesize; + + vmovups(zmm_O, ptr[oreg_O + offset]); + if (is_fwd) { + if (with_bias) { + vaddps(zmm_O, zmm_O, ptr[oreg_bias]); + } + if (with_relu) { + if (jcp.eltwise.alpha == 0) { + vmaxps(zmm_O, zmm_O, zmm_zero); + } else { + Opmask kmask = Opmask(7); + mov(imm_addr64, float2int(jcp.eltwise.alpha)); + vmovq(xmm_relu_ns, imm_addr64); + vbroadcastss(zmm_relu_ns, xmm_relu_ns); + vcmpps(kmask, zmm_O, zmm_zero, _cmp_lt_os); + vmulps(zmm_O | kmask, zmm_O, zmm_relu_ns); + } + } + } + if (with_sum) { + vaddps(zmm_O, zmm_O, ptr[oreg_out_j + oreg_temp]); + if (with_relu_postsum) // orig: with_relu_postsum + vmaxps(zmm_O, zmm_O, zmm_zero); + } + if (is_aligned) + vmovntps(ptr[oreg_out_j + oreg_temp], zmm_O); + else + vmovups(ptr[oreg_out_j + oreg_temp], zmm_O); + }; + + auto i_loop = [=](int j, bool is_aligned) { + for (int i = 0; i < tile_size; i++) { + Label next; + mov(oreg_temp, oreg_xdim); + add(oreg_temp, i); + cmp(oreg_temp, outw); + jge(next, T_NEAR); + shl(oreg_temp, 4 + 2); // * 16 * 4 + + store_one(j, i, is_aligned); + + L(next); + } + }; + + + for (int j = 0; j < tile_size; j++) { + Label next, unaligned; + mov(oreg_temp, oreg_ydim); + add(oreg_temp, j); + cmp(oreg_temp, outh); + jge(next, T_NEAR); + + mov(oreg_out_j, oreg_dst); + imul(oreg_temp, oreg_temp, outw * simd_w * typesize); + add(oreg_out_j, oreg_temp); + + test(oreg_dst, 63); + jnz(unaligned, T_NEAR); + + i_loop(j, true); + jmp(next, T_NEAR); + + L(unaligned); + i_loop(j, false); + + L(next); + } + }; + + auto trans_O_4x4_3x3 = [=]() { + auto fma2 = [=](Zmm dst, Zmm v1, Zmm u1, Zmm v2, Zmm u2){ + vmulps(dst, v1, u1); + vfmadd231ps(dst, v2, u2); + }; + mov(oreg_Ow, ptr[param1 + GET_OFF(Mw)]); + mov(oreg_T, ptr[param1 + GET_OFF(T)]); + mov(oreg_O, ptr[param1 + GET_OFF(M)]); + + for (int i = 0; i < alpha; i++) { + for (int j = 0; j < alpha; j++) { + vmovups(zmm_O(j), ptr[oreg_Ow + (j * alpha * simd_w + + i * simd_w) * typesize]); + } + + vaddps(zmm_t(0), zmm_O(1), zmm_O(2)); + vaddps(zmm_t(1), zmm_O(3), zmm_O(4)); + vsubps(zmm_t(2), zmm_O(1), zmm_O(2)); + vsubps(zmm_t(3), zmm_O(3), zmm_O(4)); + + vaddps(zmm_T(0), zmm_t(0), zmm_t(1)); + vaddps(zmm_T(0), zmm_T(0), zmm_O(0)); + fma2(zmm_T(1), zmm_t(2), zmm_G(0), zmm_t(3), zmm_G(1)); + fma2(zmm_T(2), zmm_t(0), zmm_G(2), zmm_t(1), zmm_G(3)); + fma2(zmm_T(3), zmm_t(2), zmm_G(4), zmm_t(3), zmm_G(5)); + vaddps(zmm_T(3), zmm_T(3), zmm_O(5)); + + for (int j = 0; j < tile_size; j++) { + vmovups(ptr[oreg_T + (j * alpha * simd_w + + i * simd_w) * typesize], zmm_T(j)); + } + } + for (int j = 0; j < tile_size; j++) { + for (int i = 0; i < alpha; i++) { + vmovups(zmm_T(i), ptr[oreg_T + (j * alpha * simd_w + + i * simd_w) * typesize]); + } + vaddps(zmm_t(0), zmm_T(1), zmm_T(2)); + vaddps(zmm_t(1), zmm_T(3), zmm_T(4)); + vsubps(zmm_t(2), zmm_T(1), zmm_T(2)); + vsubps(zmm_t(3), zmm_T(3), zmm_T(4)); + + vaddps(zmm_O(0), zmm_t(0), zmm_t(1)); + vaddps(zmm_O(0), zmm_O(0), zmm_T(0)); + fma2(zmm_O(1), zmm_t(2), zmm_G(0), zmm_t(3), zmm_G(1)); + fma2(zmm_O(2), zmm_t(0), zmm_G(2), zmm_t(1), zmm_G(3)); + fma2(zmm_O(3), zmm_t(2), zmm_G(4), zmm_t(3), zmm_G(5)); + vaddps(zmm_O(3), zmm_O(3), zmm_T(5)); + + for (int i = 0; i < tile_size; i++) { + vmovups(ptr[oreg_O + (j * tile_size * simd_w + + i * simd_w) * typesize], zmm_O(i)); + } + } + }; + + auto inner_loops = [=]() { + init_G(); + load_src(); + trans_O_4x4_3x3(); + store_dst(); + }; + + preamble(); + inner_loops(); + postamble(); +} + +void _jit_avx512_core_fp32_wino_conv_4x3_data_kernel + ::input_transform_data_ker_generate() +{ + bool is_fwd = one_of(jcp.prop_kind, + mkldnn_forward_training, mkldnn_forward_inference); + int inpw = is_fwd ? jcp.iw : jcp.ow; + int inph = is_fwd ? jcp.ih : jcp.oh; + int l_pad = is_fwd ? jcp.l_pad : jcp.iw + jcp.r_pad - jcp.ow; + int t_pad = is_fwd ? jcp.t_pad : jcp.ih + jcp.t_pad - jcp.oh; + int wp_max = inpw + l_pad; + int hp_max = inph + t_pad; + bool not_tiled = jcp.sched_policy == WSCHED_DATA_W_S_G_D; + int G_size = 9; + + auto zmm_zero = Xbyak::Zmm(0); + auto zmm_temp = Xbyak::Zmm(31); + auto zmm_G = [=](int i) { + return Xbyak::Zmm(1 + i); + }; + auto zmm_I = [=](int i) { + return Xbyak::Zmm(1 + G_size + i); + }; + auto zmm_T = [=](int i) { + return Xbyak::Zmm(1 + G_size + alpha + i); + }; + auto zmm_t = [=](int i) { + return Xbyak::Zmm(1 + G_size + 2 * alpha + i); + }; + + auto init_G = [=]() { + mov(ireg_temp, ptr[param1 + GET_OFF(G)]); + for (int i = 0; i < G_size; i++) { + vbroadcastss(zmm_G(i), ptr[ireg_temp + i * typesize]); + } + }; + + auto load_src = [=]() { + mov(ireg_src, ptr[param1 + GET_OFF(src)]); // base addr of inp + mov(ireg_I, ptr[param1 + GET_OFF(M)]); + + xor_(ireg_zero, ireg_zero); + vpxord(zmm_zero, zmm_zero, zmm_zero); + + mov(ireg_ydim, ptr[param1 + GET_OFF(tj)]); + shl(ireg_ydim, 2); // tj * tile_size (==4) + mov(ireg_xdim, ptr[param1 + GET_OFF(ti)]); + shl(ireg_xdim, 2); // ti * tilesize (==4) + + for (int j = 0; j < alpha; j++) { + mov(ireg_temp, ireg_ydim); + add(ireg_temp, j); + + mov(ireg_mask_j, 0xffff); + cmp(ireg_temp, t_pad); + cmovl(ireg_mask_j, ireg_zero); + cmp(ireg_temp, hp_max); + cmovge(ireg_mask_j, ireg_zero); + + sub(ireg_temp, t_pad); + imul(ireg_temp, ireg_temp, inpw * simd_w * typesize); + mov(ireg_inp_j, ireg_src); + add(ireg_inp_j, ireg_temp); + + for (int i = 0; i < alpha; i++) { + + mov(ireg_temp, ireg_xdim); + add(ireg_temp, i); + + mov(ireg_mask, 0xffff); + cmp(ireg_temp, l_pad); + cmovl(ireg_mask, ireg_zero); + cmp(ireg_temp, wp_max); + cmovge(ireg_mask, ireg_zero); + and_(ireg_mask, ireg_mask_j); + + sub(ireg_temp, l_pad); + shl(ireg_temp, 4 + 2); + + vpxord(zmm_temp, zmm_temp, zmm_temp); + Opmask kmask = Opmask(7); + kmovw(kmask, ireg_mask_32); + vmovups(zmm_temp | kmask, ptr[ireg_inp_j + ireg_temp]); + vmovups(ptr[ireg_I + (j * alpha * simd_w + i * simd_w) + * typesize], zmm_temp); + } + } + }; + + auto store_Iw = [=]() { + + mov(ireg_Iw, ptr[param1 + GET_OFF(Mw)]); + mov(ireg_output, ptr[param1 + GET_OFF(dst)]); + + bool streamout + = jcp.dimN * jcp.dimK * alpha * alpha * sizeof(float) + > 2 * LLC_data_size + ? true : false; + + if (not_tiled) { + mov(ireg_tile_block, ptr[param1 + GET_OFF(tile_block)]); + imul(ireg_tile_block, ireg_tile_block, + alpha * alpha * jcp.dimN_block * jcp.dimK_nb_block + * jcp.dimK_block * jcp.dimN_reg_block * jcp.dimK_reg_block + * typesize); + } + + mov(ireg_nb_tile_block_ur, ptr[param1 + GET_OFF(nb_tile_block_ur)]); + imul(ireg_nb_tile_block_ur, ireg_nb_tile_block_ur, + jcp.dimK_nb_block * jcp.dimK_block * jcp.dimN_reg_block + * jcp.dimK_reg_block * typesize); + + mov(ireg_tile_block_ur, ptr[param1 + GET_OFF(tile_block_ur)]); + imul(ireg_tile_block_ur, ireg_tile_block_ur, + jcp.dimK_reg_block * typesize); + + add(ireg_output, ireg_nb_tile_block_ur); + add(ireg_output, ireg_tile_block_ur); + if (not_tiled) + add(ireg_output, ireg_tile_block); + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + vmovups(zmm_temp,ptr[ireg_Iw + (j * alpha * simd_w + + i * simd_w) * typesize]); + + int j_base_offset = + j * alpha * jcp.dimN_block * jcp.dimK_nb_block + * jcp.dimK_block * jcp.dimN_reg_block * jcp.dimK_reg_block + * typesize; + int i_base_offset = + i * jcp.dimN_block * jcp.dimK_nb_block * jcp.dimK_block + * jcp.dimN_reg_block * jcp.dimK_reg_block * typesize; + + if (not_tiled && streamout) + vmovntps(ptr[ireg_output + j_base_offset + i_base_offset], + zmm_temp); + else + vmovups(ptr[ireg_output + j_base_offset + i_base_offset], + zmm_temp); + } + } + }; + + auto fma4 = [=](Zmm dst, Zmm a, Zmm b, Zmm c) { + vmulps(zmm_temp, a, b); + vaddps(dst, zmm_temp, c); + }; + + auto trans_I_4x4_3x3 = [=]() { + mov(ireg_Iw, ptr[param1 + GET_OFF(Mw)]); + mov(ireg_T, ptr[param1 + GET_OFF(T)]); + mov(ireg_I, ptr[param1 + GET_OFF(M)]); + + mov(ireg_output, ptr[param1 + GET_OFF(dst)]); // for prefetch + for (int i = 0; i < alpha; i++) { + for (int idx = 0; idx < alpha; idx++) { + vmovups(zmm_I(idx), ptr[ireg_I + (idx * alpha * simd_w + + i * simd_w) * typesize]); + int j_base_offset = + i * alpha * jcp.dimN_block * jcp.dimK_nb_block + * jcp.dimK_block * jcp.dimN_reg_block * jcp.dimK_reg_block + * typesize; + int idx_base_offset = + idx * jcp.dimN_block * jcp.dimK_nb_block * jcp.dimK_block + * jcp.dimN_reg_block * jcp.dimK_reg_block * typesize; + prefetcht0(ptr[ireg_output + j_base_offset + idx_base_offset]); + } + + fma4(zmm_t(0), zmm_I(2), zmm_G(0), zmm_I(4)); + fma4(zmm_t(1), zmm_I(1), zmm_G(0), zmm_I(3)); + fma4(zmm_t(2), zmm_I(2), zmm_G(1), zmm_I(4)); + fma4(zmm_t(3), zmm_I(1), zmm_G(1), zmm_I(3)); + fma4(zmm_t(4), zmm_I(0), zmm_G(2), zmm_I(4)); + fma4(zmm_t(5), zmm_I(1), zmm_G(2), zmm_I(5)); + + fma4(zmm_T(0), zmm_I(2), zmm_G(3), zmm_t(4)); + fma4(zmm_T(1), zmm_t(1), zmm_G(4), zmm_t(0)); + fma4(zmm_T(2), zmm_t(1), zmm_G(5), zmm_t(0)); + fma4(zmm_T(3), zmm_t(3), zmm_G(6), zmm_t(2)); + fma4(zmm_T(4), zmm_t(3), zmm_G(7), zmm_t(2)); + fma4(zmm_T(5), zmm_I(3), zmm_G(8), zmm_t(5)); + + for (int idx = 0; idx < alpha; idx++) { + vmovups(ptr[ireg_T + (idx * alpha * simd_w + i * simd_w) + * typesize],zmm_T(idx)); + } + } + for (int i = 0; i < alpha; i++) { + for (int idx = 0; idx < alpha; idx++) { + vmovups(zmm_T(idx), ptr[ireg_T + (i * alpha * simd_w + idx + * simd_w) * typesize]); + } + + fma4(zmm_t(0), zmm_T(2), zmm_G(0), zmm_T(4)); + fma4(zmm_t(1), zmm_T(1), zmm_G(0), zmm_T(3)); + fma4(zmm_t(2), zmm_T(2), zmm_G(1), zmm_T(4)); + fma4(zmm_t(3), zmm_T(1), zmm_G(1), zmm_T(3)); + fma4(zmm_t(4), zmm_T(0), zmm_G(2), zmm_T(4)); + fma4(zmm_t(5), zmm_T(1), zmm_G(2), zmm_T(5)); + + fma4(zmm_I(0), zmm_T(2), zmm_G(3), zmm_t(4)); + fma4(zmm_I(1), zmm_t(1), zmm_G(4), zmm_t(0)); + fma4(zmm_I(2), zmm_t(1), zmm_G(5), zmm_t(0)); + fma4(zmm_I(3), zmm_t(3), zmm_G(6), zmm_t(2)); + fma4(zmm_I(4), zmm_t(3), zmm_G(7), zmm_t(2)); + fma4(zmm_I(5), zmm_T(3), zmm_G(8), zmm_t(5)); + + for (int idx = 0; idx < alpha; idx++) { + vmovups(ptr[ireg_Iw + (i * alpha * simd_w + idx * simd_w) + * typesize],zmm_I(idx)); + } + } + }; + + auto inner_loops = [=]() { + init_G(); + load_src(); + trans_I_4x4_3x3(); + store_Iw(); + }; + + preamble(); + inner_loops(); + postamble(); +} + +status_t _jit_avx512_core_fp32_wino_conv_4x3_data_kernel::init_conf_common( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d) +{ + if (!mayiuse(avx512_core)) { + return status::unimplemented; + } + + jcp.nthr = mkldnn_get_max_threads(); + + jcp.ver = ver_avx512_core; + jcp.prop_kind = cd.prop_kind; + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = dst_d.dims()[2]; + jcp.ow = dst_d.dims()[3]; + jcp.kh = weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + jcp.r_pad = nstl::max( + 0, (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad); + jcp.b_pad = nstl::max( + 0, (jcp.oh - 1) * jcp.stride_h + jcp.kh - jcp.ih - jcp.t_pad); + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + jcp.ohp = jcp.oh; + jcp.owp = jcp.ow; + + bool ok_to_pad_channels = jcp.ngroups == 1; + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + // Checking conditions not supported by these kernels + if (!IMPLICATION(cd.alg_kind == alg_kind::convolution_auto, + is_winograd_faster_than_direct(jcp))) + return status::unimplemented; + + if (jcp.ngroups != 1) + return status::unimplemented; + if ((jcp.kh != 3) || (jcp.kw != 3)) + return status::unimplemented; + if ((jcp.dilate_h != 0) || (jcp.dilate_w != 0)) + return status::unimplemented; + if ((jcp.stride_h != 1) || (jcp.stride_w != 1)) + return status::unimplemented; + if ((jcp.ic % simd_w) != 0 || (jcp.oc % simd_w) != 0) + return status::unimplemented; + + format_tag_t dat_tag = nChw16c; + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + if (jcp.src_tag != dat_tag) return status::unimplemented; + if (jcp.dst_tag != dat_tag) return status::unimplemented; + + if (!one_of(weights_d.format_kind(), format_kind::any, format_kind::wino)) { + format_tag_t wei_tag = with_groups ? gOIhw16i16o : OIhw16i16o; + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + if (jcp.wei_tag != wei_tag) + return status::unimplemented; + } + + bool layout_consistency = true + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= dst_d.padded_dims()[1] + && (one_of(weights_d.format_kind(), + format_kind::any, format_kind::wino) + || (jcp.ic <= weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= weights_d.padded_dims()[with_groups + 0])); + if (!layout_consistency) + return status::unimplemented; + + return status::success; +} + +void set_kernel_dims_reg_block(jit_conv_winograd_conf_t &jcp) { + + /* ----------- dimM reg block ---------------------*/ + auto test_cond_dimM_reg_block = [](jit_conv_winograd_conf_t &jcp, + int dimM_reg_block, int current_best) { + int max_dimM_reg_block = jcp.kernel_kind == embd_bcast ? 1 : 4; + return (dimM_reg_block >= 1) + && (dimM_reg_block <= max_dimM_reg_block ) + && (dimM_reg_block > current_best); + }; + jcp.dimM_reg_block = get_divisor_satisfying_cond(jcp, + jcp.dimM/jcp.dimM_simd_block, 1, test_cond_dimM_reg_block); + + /* ----------- dimN reg block ---------------------*/ + + auto test_cond_dimN_reg_block = [](jit_conv_winograd_conf_t &jcp, + int dimN_reg_block, int current_best) { + return jcp.kernel_kind == embd_bcast + ? dimN_reg_block < jcp.nb_reg && dimN_reg_block > current_best + : dimN_reg_block >= 1 + && (dimN_reg_block * jcp.dimM_reg_block + dimN_reg_block) + < jcp.nb_reg + && dimN_reg_block > current_best; + }; + jcp.dimN_reg_block = get_divisor_satisfying_cond(jcp, + jcp.dimN, 1, test_cond_dimN_reg_block); +} + +status_t set_wsched_DATA_W_SGD_avx512_core(jit_conv_winograd_conf_t &jcp) { + if (jcp.ver != ver_avx512_core) + return status::unimplemented; + + jcp.kernel_kind = embd_bcast; + + set_kernel_dims_reg_block(jcp); + + /*-------------- L2 blocking for dimN block ---------*/ + + auto test_cond_dimN_block = [](jit_conv_winograd_conf_t &jcp, + int dimN_block, int current_best) { + return check_L2_block_per_thread(jcp, dimN_block, 0.1, 2.0) + && (dimN_block > current_best) + && ((jcp.dimN / dimN_block / jcp.dimN_reg_block) + >= 1.5 * mkldnn_get_max_threads()); + }; + + jcp.dimN_block = get_divisor_satisfying_cond( + jcp, jcp.dimN / jcp.dimN_reg_block, 1, test_cond_dimN_block); + jcp.dimN_nb_block = jcp.dimN / jcp.dimN_block / jcp.dimN_reg_block; + + if (check_L2_block_per_thread(jcp, jcp.dimN_block, 0.1, 3.2) + && (jcp.dimN_nb_block >= 1.5 * mkldnn_get_max_threads())) { + + /* ------------------- L1 blocking for GEMM --------------*/ + /* -------------------- Choose dimK block ----------------*/ + + auto test_cond_dimK_block = [](jit_conv_winograd_conf_t &jcp, + int dimK_block, int current_best) { + return check_L1_block_gemm(jcp, dimK_block, 1, 0.1, 0.5) + && (dimK_block > current_best); + }; + + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_reg_block, 1, test_cond_dimK_block); + + if (check_L1_block_gemm(jcp, jcp.dimK_block, 1, 0.1, 1.0)) { + jcp.dimK_nb_block = jcp.dimK / jcp.dimK_block / jcp.dimK_reg_block; + + /* -------------- Choose dimM block -------------------*/ + auto test_cond_dimM_block = [](jit_conv_winograd_conf_t &jcp, + int dimM_block, int current_best) { + return check_L1_block_gemm(jcp, jcp.dimK_block, dimM_block, + 0.2, 0.5) && (dimM_block > current_best); + }; + + jcp.dimM_block = get_divisor_satisfying_cond(jcp, + jcp.dimM / (jcp.dimM_simd_block * jcp.dimM_reg_block), 1, + test_cond_dimM_block); + jcp.dimM_nb_block = jcp.dimM / jcp.dimM_block / jcp.dimM_reg_block + / jcp.dimM_simd_block; + + jcp.sched_policy = WSCHED_DATA_W_SGD; + return status::success; + } + + } + return status::unimplemented; +} + +void set_kernel_blocking_DATA_W_S_G_D(jit_conv_winograd_conf_t &jcp) { + + set_kernel_dims_reg_block(jcp); + + //********************* Choosing dimK_block **********************// + auto test_cond1_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond1(jcp.dimN_reg_block, dimK_block, jcp.dimK_reg_block, + 1, jcp.dimM_reg_block, jcp.dimM_simd_block, .75f) + && (dimK_block > current_best); + }; + + auto test_cond1_bis_dimK_block = []( + jit_conv_winograd_conf_t &jcp, int dimK_block, int current_best) { + return check_cond1_bis(jcp.dimN_reg_block, dimK_block, + jcp.dimK_reg_block, 1, jcp.dimM_reg_block, + jcp.dimM_simd_block, .9f) + && (dimK_block > current_best); + }; + + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_reg_block, 1, test_cond1_bis_dimK_block); + // If we are not able to use streams, we fall back to condition [1] + if (jcp.dimK_block < jcp.dimK / jcp.dimK_reg_block) + jcp.dimK_block = get_divisor_satisfying_cond( + jcp, jcp.dimK / jcp.dimK_reg_block, 1, test_cond1_dimK_block); + jcp.dimK_nb_block = (jcp.dimK / jcp.dimK_reg_block) / jcp.dimK_block; + + //********************* Choosing dimM_block **********************// + auto test_cond1_dimM_block = []( + jit_conv_winograd_conf_t &jcp, int dimM_block, int current_best) { + return check_cond1(jcp.dimN_reg_block, jcp.dimK_block, + jcp.dimK_reg_block, dimM_block, jcp.dimM_reg_block, + jcp.dimM_simd_block, .5f) + && (dimM_block > current_best); + }; + + auto test_cond1_bis_dimM_block = []( + jit_conv_winograd_conf_t &jcp, int dimM_block, int current_best) { + return check_cond1_bis(jcp.dimN_reg_block, jcp.dimK_block, + jcp.dimK_reg_block, dimM_block, jcp.dimM_reg_block, + jcp.dimM_simd_block, .3f) + && (dimM_block > current_best); + }; + + if (jcp.dimK_block < jcp.dimK / jcp.dimK_reg_block) + jcp.dimM_block = get_divisor_satisfying_cond( + jcp, jcp.dimM / (jcp.dimM_simd_block*jcp.dimM_reg_block), 1, + test_cond1_dimM_block); + else + jcp.dimM_block = get_divisor_satisfying_cond(jcp, + jcp.dimM / (jcp.dimM_simd_block*jcp.dimM_reg_block), 1, + test_cond1_bis_dimM_block); + jcp.dimM_nb_block = jcp.dimM / (jcp.dimM_simd_block * jcp.dimM_block + * jcp.dimM_reg_block); + + //******************* Choosing dimN_block *******************// + auto test_cond2_dimN_block = []( + jit_conv_winograd_conf_t &jcp, int dimN_block, int current_best) { + return check_cond2(dimN_block, jcp.dimN_reg_block, jcp.dimK_nb_block, + jcp.dimK_block, jcp.dimK_reg_block, jcp.dimM_block, + jcp.dimM_reg_block, jcp.dimM_simd_block, .9f) + && (dimN_block > current_best); + }; + + jcp.dimN_block = get_divisor_satisfying_cond( + jcp, jcp.dimN / jcp.dimN_reg_block, 1, test_cond2_dimN_block); + jcp.dimN_nb_block = jcp.dimN / (jcp.dimN_reg_block * jcp.dimN_block); +} + +status_t set_wsched_DATA_W_S_G_D_avx512_core(jit_conv_winograd_conf_t &jcp) { + + jcp.kernel_kind = expl_bcast; + set_kernel_blocking_DATA_W_S_G_D(jcp); + if (!(check_kernel_cond(jcp.dimM_block, jcp.dimM_reg_block, + jcp.dimM_simd_block, jcp.dimN_block, jcp.dimN_reg_block, jcp.dimK, + .1f, .35f))) { + jcp.kernel_kind = embd_bcast; + set_kernel_blocking_DATA_W_S_G_D(jcp); + } + jcp.sched_policy = WSCHED_DATA_W_S_G_D; + return status::success; +} + +status_t _jit_avx512_core_fp32_wino_conv_4x3_data_kernel::init_conf_kernel( + jit_conv_winograd_conf_t &jcp, int dimM, int dimN, int dimK) +{ + jcp.nb_reg = 32; + jcp.dimN = dimN; + jcp.dimK = dimK; + jcp.dimM = dimM; + jcp.sched_policy = WSCHED_INVALID; + + jcp.dimK_reg_block = 16; + jcp.dimM_simd_block = 16; + + if (jcp.kernel_kind == embd_bcast) { + jcp.dimM_reg_block = 1; + } + + if (!(set_wsched_DATA_W_SGD_avx512_core(jcp) == status::success)) + set_wsched_DATA_W_S_G_D_avx512_core(jcp); + + assert(jcp.sched_policy != WSCHED_INVALID); + return status::success; +} + +bool jit_avx512_core_fp32_wino_conv_4x3_fwd_kernel::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_relu = [&](int idx) { return p.entry_[idx].is_relu(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_relu(0) || is_sum(0); // relu or sum + case 2: return (is_sum(0) && is_relu(1)) + || (is_relu(0) && is_sum(1)); // sum->relu or relu->sum + case 3: return is_relu(0) && is_sum(1) && is_relu(2); // relu->sum->relu + default: return false; + } + + return false; +} + +status_t jit_avx512_core_fp32_wino_conv_4x3_fwd_kernel::init_conf( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_t &src_md, memory_desc_t &weights_md, + const memory_desc_t &dst_md, const primitive_attr_t &attr) { + + status_t st = init_conf_common(jcp, cd, src_md, weights_md, dst_md); + + if (st != status::success) + return st; + + // Winograd specific initialization + jcp.itiles = (jcp.ow + tile_size - 1) / tile_size; + jcp.jtiles = (jcp.oh + tile_size - 1) / tile_size; + jcp.ntiles = jcp.mb * jcp.itiles * jcp.jtiles; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + const int eltwise_ind = p.find(primitive_kind::eltwise, 0, 1); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + jcp.with_sum = p.find(primitive_kind::sum, 0) != -1; + jcp.with_relu_postsum = p.find(primitive_kind::eltwise, 1) != -1; + + status_t res = init_conf_kernel(jcp, jcp.oc, jcp.ntiles, jcp.ic); + + jcp.ic_simd_block = jcp.dimK_reg_block; + jcp.ic_block = jcp.dimK_block; + jcp.nb_ic = jcp.dimK_nb_block; + jcp.oc_simd_block = jcp.dimM_simd_block; + jcp.oc_block = jcp.dimM_block; + jcp.oc_reg_block = jcp.dimM_reg_block; + jcp.ic_reg_block = 1; + jcp.nb_oc = jcp.dimM_nb_block; + jcp.tile_block_ur = jcp.dimN_reg_block; + jcp.nb_tile_block_ur = jcp.dimN_block; + jcp.tile_block = jcp.dimN_nb_block; + + /* re-create weights primitive descriptor + and set weights wino_blocking */ + if (cd.prop_kind == mkldnn_forward_inference) { + memory_desc_t expect_wei_md = weights_md; + + expect_wei_md.format_kind = format_kind::wino; + expect_wei_md.data_type = data_type::f32; + mkldnn_wino_desc_t &wd = expect_wei_md.format_desc.wino_desc; + wd.wino_format = mkldnn_wino_wei_OBaaIBOIio; + wd.r = 3; + wd.alpha = 6; + + wd.ic = jcp.ic; + wd.oc = jcp.oc; + wd.ic_block = jcp.dimK_reg_block; + wd.oc_block = jcp.dimM_simd_block; + wd.ic2_block = jcp.dimK_block; + wd.oc2_block = jcp.dimM_block * jcp.dimM_reg_block; + size_t max_size = sizeof(float) * wd.alpha * wd.alpha * jcp.ic * jcp.oc; + wd.size = max_size; + wd.adj_scale = 1.f; + + if (weights_md.format_kind == format_kind::any) + weights_md = expect_wei_md; + if (weights_md != expect_wei_md) + return status::unimplemented; + } + + return res; +} + +status_t jit_avx512_core_fp32_wino_conv_4x3_bwd_data_kernel::init_conf( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d) +{ + status_t st = init_conf_common(jcp, cd, diff_src_d, weights_d, diff_dst_d); + + if (st != status::success) + return st; + + jcp.itiles = (jcp.iw + tile_size - 1) / tile_size; + jcp.jtiles = (jcp.ih + tile_size - 1) / tile_size; + jcp.ntiles = jcp.mb * jcp.itiles * jcp.jtiles; + + status_t res = init_conf_kernel(jcp, jcp.ic, jcp.ntiles, jcp.oc); + + jcp.oc_simd_block = jcp.dimK_reg_block; + jcp.oc_block = jcp.dimK_block; + jcp.nb_oc = jcp.dimK_nb_block; + jcp.ic_simd_block = jcp.dimM_simd_block; + jcp.ic_block = jcp.dimM_block; + jcp.ic_reg_block = jcp.dimM_reg_block; + jcp.oc_reg_block = 1; + jcp.nb_ic = jcp.dimM_nb_block; + jcp.tile_block_ur = jcp.dimN_reg_block; + jcp.nb_tile_block_ur = jcp.dimN_block; + jcp.tile_block = jcp.dimN_nb_block; + + return res; +} + +void jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel:: +src_transform_generate() { + constexpr int G_size = 9; + const size_t ifwp = jcp.iw + jcp.l_pad; + const size_t ifhp = jcp.ih + jcp.t_pad; + + auto zmm_G = [=](int i) { + return Xbyak::Zmm(i); + }; + auto zmm_I = [=](int i) { + return Xbyak::Zmm(G_size + i); + }; + auto zmm_T = [=](int i) { + return Xbyak::Zmm(G_size + alpha + i); + }; + auto zmm_t = [=](int i) { + return Xbyak::Zmm(G_size + 2 * alpha + i); + }; + + auto init_G = [=]() { + mov(reg_G, ptr[reg_transp + GET_OFF(G)]); + for (int i = 0; i < G_size; i++) { + vbroadcastss(zmm_G(i), ptr[reg_G + i * typesize]); + } + }; + + auto load_src = [=]() { + mov(reg_I, ptr[reg_transp + GET_OFF(M)]); + xor_(reg_zero, reg_zero); + + mov(reg_ydim, reg_tj); + shl(reg_ydim, 2); //tj * tile_size(=4) + + for (int j = 0; j < alpha; j++) { + /* check if tile index is within physical spatial boundaries*/ + mov(reg_maskj, 0xffff); + cmp(reg_ydim, jcp.t_pad); + cmovl(reg_maskj, reg_zero); + cmp(reg_ydim, ifhp); + cmovge(reg_maskj, reg_zero); + + /*address offset for tile in src*/ + mov(reg_src_offset, reg_ydim); + sub(reg_src_offset, jcp.t_pad); // tj*tile_size - t_pad + imul(reg_src_offset, reg_src_offset, jcp.iw); + + mov(reg_xdim, reg_ti); + shl(reg_xdim, 2); // xdim = ti * tile_size + + add(reg_src_offset, reg_xdim); + sub(reg_src_offset, jcp.l_pad); + imul(reg_src_offset, reg_src_offset, simd_w * typesize); + for (int i = 0; i < alpha; i++) { + /* check if tile index is within physical spatial boundaries*/ + mov(reg_maski, 0xffff); + cmp(reg_xdim, jcp.l_pad); + cmovl(reg_maski, reg_zero); + cmp(reg_xdim, ifwp); + cmovge(reg_maski, reg_zero); + and_(reg_maski, reg_maskj); + + Opmask kmask_src = Xbyak::Opmask(7); + auto zmm_src = Xbyak::Zmm(31); + kmovw(kmask_src, reg_maski_32); + vpxord(zmm_src, zmm_src, zmm_src); + vmovups(zmm_src | kmask_src, ptr[reg_src + reg_src_offset]); + vmovups(ptr[reg_I], zmm_src); + + add(reg_xdim, 1); //xdim = ti * tile_size + i + add(reg_src_offset, simd_w * typesize); + add(reg_I, simd_w * typesize); + } + add(reg_ydim, 1); + } + }; + + auto fma4 = [=](Xbyak::Zmm dst, Xbyak::Zmm a, Xbyak::Zmm b, Xbyak::Zmm c) { + vmovups(dst, c); + vfmadd231ps(dst, a, b); + }; + + auto trans_I_3x3_4x4 = [=]() { + //Use 24 registers + mov(reg_I, ptr[reg_transp + GET_OFF(M)]); + mov(reg_T, ptr[reg_transp + GET_OFF(T)]); + for (int i = 0; i < alpha; i++) { + for (int j = 0; j < alpha; j++) { + size_t I_off = (j * alpha + i) * simd_w * typesize; + vmovups(zmm_I(j), ptr[reg_I + I_off]); + } + + fma4(zmm_t(0), zmm_I(2), zmm_G(0), zmm_I(4)); + fma4(zmm_t(1), zmm_I(1), zmm_G(0), zmm_I(3)); + fma4(zmm_t(2), zmm_I(2), zmm_G(1), zmm_I(4)); + fma4(zmm_t(3), zmm_I(1), zmm_G(1), zmm_I(3)); + fma4(zmm_t(4), zmm_I(0), zmm_G(2), zmm_I(4)); + fma4(zmm_t(5), zmm_I(1), zmm_G(2), zmm_I(5)); + + fma4(zmm_T(0), zmm_I(2), zmm_G(3), zmm_t(4)); + fma4(zmm_T(1), zmm_t(1), zmm_G(4), zmm_t(0)); + fma4(zmm_T(2), zmm_t(1), zmm_G(5), zmm_t(0)); + fma4(zmm_T(3), zmm_t(3), zmm_G(6), zmm_t(2)); + fma4(zmm_T(4), zmm_t(3), zmm_G(7), zmm_t(2)); + fma4(zmm_T(5), zmm_I(3), zmm_G(8), zmm_t(5)); + + for (int j = 0; j < alpha; j++) { + vmovups(ptr[reg_T + (j * alpha + i) * simd_w * typesize], + zmm_T(j)); + } + + } + + for (int j = 0; j < alpha; j++) { + for (int i = 0; i < alpha; i++) { + vmovups(zmm_T(i), ptr[reg_T + (j * alpha + i) * simd_w * typesize]); + } + + fma4(zmm_t(0), zmm_T(2), zmm_G(0), zmm_T(4)); + fma4(zmm_t(1), zmm_T(1), zmm_G(0), zmm_T(3)); + fma4(zmm_t(2), zmm_T(2), zmm_G(1), zmm_T(4)); + fma4(zmm_t(3), zmm_T(1), zmm_G(1), zmm_T(3)); + fma4(zmm_t(4), zmm_T(0), zmm_G(2), zmm_T(4)); + fma4(zmm_t(5), zmm_T(1), zmm_G(2), zmm_T(5)); + + fma4(zmm_I(0), zmm_T(2), zmm_G(3), zmm_t(4)); + fma4(zmm_I(1), zmm_t(1), zmm_G(4), zmm_t(0)); + fma4(zmm_I(2), zmm_t(1), zmm_G(5), zmm_t(0)); + fma4(zmm_I(3), zmm_t(3), zmm_G(6), zmm_t(2)); + fma4(zmm_I(4), zmm_t(3), zmm_G(7), zmm_t(2)); + fma4(zmm_I(5), zmm_T(3), zmm_G(8), zmm_t(5)); + + for (int i = 0; i < alpha; i++) { + size_t dst_off = (j * alpha * jcp.ic_block + * jcp.nb_tile_block_ur * jcp.tile_block_ur + + i * jcp.ic_block * jcp.nb_tile_block_ur * jcp.tile_block_ur) + * simd_w * typesize; + vmovups(ptr[reg_dst + dst_off], zmm_I(i)); + } + } + }; + + auto compute_transform_SDGtWo = [=]() { + mov(reg_ti, ptr[reg_transp + GET_OFF(ti)]); + mov(reg_tj, ptr[reg_transp + GET_OFF(tj)]); + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + xor_(reg_tile_count, reg_tile_count); + Label loop_mb, loop_jtiles, loop_itiles, done; + L(loop_mb); + { + L(loop_jtiles); + { + L(loop_itiles); + { + load_src(); + + trans_I_3x3_4x4(); + + add(reg_tile_count, 1); + cmp(reg_tile_count, jcp.nb_tile_block_ur * jcp.tile_block_ur); + jge(done); + + add(reg_dst, simd_w * typesize); + add(reg_ti, 1); + cmp(reg_ti, jcp.itiles); + jl(loop_itiles); + } + xor_(reg_ti, reg_ti); + add(reg_tj, 1); + cmp(reg_tj, jcp.jtiles); + jl(loop_jtiles); + } + xor_(reg_tj, reg_tj); + add(reg_src, jcp.ic * jcp.iw * jcp.ih * typesize); + jmp(loop_mb); + } + L(done); + }; + + auto compute_transform = [=]() { + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + xor_(reg_ti, reg_ti); + xor_(reg_tj, reg_tj); + + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + mov(reg_tile_count, ptr[reg_transp + GET_OFF(tile_count)]); + imul(reg_temp, reg_tile_count, simd_w * typesize); + add(reg_dst, reg_temp); + + Label loop_jtiles, loop_itiles, next_tile_block, next_tile; + L(loop_jtiles); + + { + L(loop_itiles); + { + load_src(); + + trans_I_3x3_4x4(); + + add(reg_tile_count, 1); + cmp(reg_tile_count, jcp.nb_tile_block_ur * jcp.tile_block_ur); + jge(next_tile_block); + add(reg_dst, simd_w * typesize); + jmp(next_tile); + + L(next_tile_block); + sub(reg_dst, (jcp.nb_tile_block_ur * jcp.tile_block_ur - 1) + * simd_w * typesize); + size_t tblk_off = alpha * alpha * jcp.ic_block + * jcp.nb_tile_block_ur * jcp.tile_block_ur + * simd_w * typesize; + add(reg_dst, tblk_off); + xor_(reg_tile_count, reg_tile_count); + + L(next_tile); + add(reg_ti, 1); + cmp(reg_ti, jcp.itiles); + jl(loop_itiles); + } + xor_(reg_ti, reg_ti); + add(reg_tj, 1); + cmp(reg_tj, jcp.jtiles); + jl(loop_jtiles); + } + }; + + preamble(); + init_G(); + if (jcp.sched_policy == WSCHED_WEI_SDGtWo) + compute_transform_SDGtWo(); + else + compute_transform(); + postamble(); +} + +void jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel:: +diff_dst_transform_generate(bool with_bias) { + + constexpr int G_size = 8; + auto zmm_G = [](int i) { + return Xbyak::Zmm(31); + }; + + auto zmm_src = [=](int j, int i) { + return Xbyak::Zmm(G_size + j * 4 + i); + }; + + auto zmm_bias = Xbyak::Zmm(31); + + auto load_src = [=]() { + if (with_bias) vmovups(zmm_bias, ptr[reg_bias]); + mov(reg_ydim, reg_tj); + shl(reg_ydim, 2); //tj * tile_size(=4) + for (int j = 0; j < tile_size; j++) { + /* check if tile index is within physical spatial boundaries*/ + mov(reg_maskj, 0xffff); + cmp(reg_ydim, jcp.oh); + cmovge(reg_maskj, reg_zero); + + /*address offset for tile in src*/ + mov(reg_src_offset, reg_ydim); + imul(reg_src_offset, reg_src_offset, jcp.ow); + + mov(reg_xdim, reg_ti); + shl(reg_xdim, 2); // xdim = ti * tile_size + + add(reg_src_offset, reg_xdim); + imul(reg_src_offset, reg_src_offset, simd_w * typesize); + for (int i = 0; i < tile_size; i++) { + /* check if tile index is within physical spatial boundaries*/ + mov(reg_maski, 0xffff); + cmp(reg_xdim, jcp.ow); + cmovge(reg_maski, reg_zero); + and_(reg_maski, reg_maskj); + + Opmask kmask_src = Xbyak::Opmask(7); + kmovw(kmask_src, reg_maski_32); + vpxord(zmm_src(j, i), zmm_src(j, i), zmm_src(j, i)); + vmovups(zmm_src(j, i) | kmask_src, ptr[reg_src + reg_src_offset]); + if (with_bias) vaddps(zmm_bias | kmask_src, zmm_bias, + ptr[reg_src + reg_src_offset]); + + add(reg_xdim, 1); //xdim = ti * tile_size + i + add(reg_src_offset, simd_w * typesize); + } + add(reg_ydim, 1); + } + if(with_bias) vmovups(ptr[reg_bias], zmm_bias); + }; + + auto zmm_t = [=](int i) { + return Xbyak::Zmm(G_size + 16 + i); + }; + + auto zmm_T = [=](int j, int i) { + return Xbyak::Zmm(j * 4 + i); + }; + + auto movps = [=](Xbyak::Reg64 reg_dst, size_t dst_off, Xbyak::Zmm a) { + if (jcp.sched_policy == WSCHED_WEI_SDGtWo) + vmovups(ptr[reg_dst + dst_off], a); + else + vmovntps(ptr[reg_dst + dst_off], a); + }; + + auto trans_W_3x3_4x4 = [=]() { + mov(reg_G, ptr[reg_transp + GET_OFF(G)]); + for (int i = 0; i < tile_size; i++) { + vbroadcastss(zmm_G(0), ptr[reg_G]); + vmulps(zmm_t(0), zmm_src(2, i), zmm_G(0)); + + vbroadcastss(zmm_G(1), ptr[reg_G + typesize]); + vmovups(zmm_t(1), zmm_t(0)); + vfmsub231ps(zmm_t(1), zmm_src(0, i), zmm_G(1)); + + vbroadcastss(zmm_G(2), ptr[reg_G + 2 * typesize]); + vmovups(zmm_t(2), zmm_t(0)); + vfmadd231ps(zmm_t(2), zmm_src(0, i), zmm_G(2)); + + vbroadcastss(zmm_G(3), ptr[reg_G + 3 * typesize]); + vmulps(zmm_t(3), zmm_src(1, i), zmm_G(3)); + + vbroadcastss(zmm_G(4), ptr[reg_G + 4 * typesize]); + vfmadd231ps(zmm_t(3), zmm_src(3, i), zmm_G(4)); + + vbroadcastss(zmm_G(5), ptr[reg_G + 5 * typesize]); + vmulps(zmm_t(4), zmm_src(1, i), zmm_G(5)); + + vbroadcastss(zmm_G(6), ptr[reg_G + 6 * typesize]); + vfmadd231ps(zmm_t(4), zmm_src(3, i), zmm_G(6)); + + vbroadcastss(zmm_G(7), ptr[reg_G + 7 * typesize]); + vmulps(zmm_T(0, i), zmm_src(0, i), zmm_G(7)); + vsubps(zmm_T(1, i), zmm_t(1), zmm_t(3)); + vaddps(zmm_T(2, i), zmm_t(1), zmm_t(3)); + vaddps(zmm_T(3, i), zmm_t(2), zmm_t(4)); + vsubps(zmm_T(4, i), zmm_t(2), zmm_t(4)); + vmovups(zmm_T(5, i), zmm_src(3, i)); + } + + for (int j = 0; j < alpha; j++) { + vbroadcastss(zmm_G(0), ptr[reg_G]); + vmulps(zmm_t(0), zmm_T(j, 2), zmm_G(0)); + + vbroadcastss(zmm_G(1), ptr[reg_G + typesize]); + vmovups(zmm_t(1), zmm_t(0)); + vfmsub231ps(zmm_t(1), zmm_T(j, 0), zmm_G(1)); + + vbroadcastss(zmm_G(2), ptr[reg_G + 2 * typesize]); + vmovups(zmm_t(2), zmm_t(0)); + vfmadd231ps(zmm_t(2), zmm_T(j, 0), zmm_G(2)); + + vbroadcastss(zmm_G(3), ptr[reg_G + 3 * typesize]); + vmulps(zmm_t(3), zmm_T(j, 1), zmm_G(3)); + + vbroadcastss(zmm_G(4), ptr[reg_G + 4 * typesize]); + vfmadd231ps(zmm_t(3), zmm_T(j, 3), zmm_G(4)); + + vbroadcastss(zmm_G(5), ptr[reg_G + 5 * typesize]); + vmulps(zmm_t(4), zmm_T(j, 1), zmm_G(5)); + + vbroadcastss(zmm_G(6), ptr[reg_G + 6 * typesize]); + vfmadd231ps(zmm_t(4), zmm_T(j, 3), zmm_G(6)); + + vbroadcastss(zmm_G(7), ptr[reg_G + 7 * typesize]); + vmulps(zmm_t(0), zmm_T(j, 0), zmm_G(7)); + vsubps(zmm_t(5), zmm_t(1), zmm_t(3)); + vaddps(zmm_t(1), zmm_t(1), zmm_t(3)); + vaddps(zmm_t(6), zmm_t(2), zmm_t(4)); + vsubps(zmm_t(2), zmm_t(2), zmm_t(4)); + vmovups(zmm_t(3), zmm_T(j, 3)); + + int alpha_offset = (jcp.oc / jcp.nb_oc) + * (jcp.ntiles / jcp.tile_block) * typesize; + int dst_off = j * alpha * alpha_offset; + movps(reg_dst, dst_off, zmm_t(0)); + dst_off += alpha_offset; + movps(reg_dst, dst_off, zmm_t(5)); + dst_off += alpha_offset; + movps(reg_dst, dst_off, zmm_t(1)); + dst_off += alpha_offset; + movps(reg_dst, dst_off, zmm_t(6)); + dst_off += alpha_offset; + movps(reg_dst, dst_off, zmm_t(2)); + dst_off += alpha_offset; + movps(reg_dst, dst_off, zmm_t(3)); + } + + }; + auto compute_transform_SDGtWo = [=]() { + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + if (with_bias) mov(reg_bias, ptr[reg_transp + GET_OFF(bias)]); + + xor_(reg_zero, reg_zero); + xor_(reg_oc_ur, reg_oc_ur); + Label loop_mb, loop_jtiles, loop_itiles, loop_oc_ur, tiles_done; + + L(loop_oc_ur); + { + mov(reg_ti, ptr[reg_transp + GET_OFF(ti)]); + mov(reg_tj, ptr[reg_transp + GET_OFF(tj)]); + xor_(reg_tile_count, reg_tile_count); + L(loop_mb); + { + L(loop_jtiles); + { + L(loop_itiles); + { + load_src(); + + trans_W_3x3_4x4(); + + add(reg_tile_count, 1); + cmp(reg_tile_count, jcp.nb_tile_block_ur * jcp.tile_block_ur); + jge(tiles_done); + + add(reg_dst, jcp.oc_reg_block * simd_w * typesize); + add(reg_ti, 1); + cmp(reg_ti, jcp.itiles); + jl(loop_itiles); + } + xor_(reg_ti, reg_ti); + add(reg_tj, 1); + cmp(reg_tj, jcp.jtiles); + jl(loop_jtiles); + } + xor_(reg_tj, reg_tj); + add(reg_src, jcp.oc * jcp.ow * jcp.oh * typesize); + jmp(loop_mb); + } + + L(tiles_done); + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + add(reg_dst, simd_w * typesize); + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + add(reg_src, jcp.oh * jcp.ow * simd_w * typesize); + + if (with_bias) add(reg_bias, simd_w * typesize); + add(reg_oc_ur, 1); + cmp(reg_oc_ur, jcp.oc_reg_block); + jl(loop_oc_ur); + } + }; + + auto compute_transform = [=]() { + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + mov(reg_G, ptr[reg_transp + GET_OFF(G)]); + if (with_bias) mov(reg_bias, ptr[reg_transp + GET_OFF(bias)]); + + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + mov(reg_tile_count, ptr[reg_transp + GET_OFF(tile_count)]); + imul(reg_temp, reg_tile_count, jcp.oc_reg_block * simd_w * typesize); + add(reg_dst, reg_temp); + + xor_(reg_zero, reg_zero); + xor_(reg_oc_ur, reg_oc_ur); + Label loop_mb, loop_jtiles, loop_itiles, loop_oc_ur, next_tile_block, next_tile; + + L(loop_oc_ur); + { + xor_(reg_ti, reg_ti); + xor_(reg_tj, reg_tj); + + L(loop_jtiles); + { + L(loop_itiles); + { + load_src(); + + trans_W_3x3_4x4(); + + add(reg_tile_count, 1); + cmp(reg_tile_count, jcp.nb_tile_block_ur * jcp.tile_block_ur); + jge(next_tile_block); + add(reg_dst, jcp.oc_reg_block * simd_w * typesize); + jmp(next_tile); + + L(next_tile_block); + sub(reg_dst, (jcp.nb_tile_block_ur * jcp.tile_block_ur - 1) + * jcp.oc_reg_block * simd_w * typesize); + int tblk_off = alpha * alpha * (jcp.oc/jcp.nb_oc) + * (jcp.ntiles/jcp.tile_block) * typesize; + add(reg_dst, tblk_off); + xor_(reg_tile_count, reg_tile_count); + + L(next_tile); + add(reg_ti, 1); + cmp(reg_ti, jcp.itiles); + jl(loop_itiles); + } + xor_(reg_ti, reg_ti); + add(reg_tj, 1); + cmp(reg_tj, jcp.jtiles); + jl(loop_jtiles); + } + + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + mov(reg_tile_count, ptr[reg_transp + GET_OFF(tile_count)]); + imul(reg_temp, reg_tile_count, jcp.oc_reg_block * simd_w * typesize); + add(reg_dst, reg_temp); + add(reg_dst, simd_w * typesize); + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + add(reg_src, jcp.oh * jcp.ow * simd_w * typesize); + + if (with_bias) add(reg_bias, simd_w * typesize); + add(reg_oc_ur, 1); + cmp(reg_oc_ur, jcp.oc_reg_block); + jl(loop_oc_ur); + } + }; + + preamble(); + if (jcp.sched_policy == WSCHED_WEI_SDGtWo) { + compute_transform_SDGtWo(); + } else { + compute_transform(); + } + postamble(); +} + +void jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel:: +diff_weights_transform_generate(bool first_tile) { + int G_size = 4; + + auto zmm_G = [](int i) { + return Xbyak::Zmm(i); + }; + + auto init_G = [=]() { + mov(reg_G, ptr[reg_transp + GET_OFF(G)]); + for (int i = 0; i < G_size; i++) + vbroadcastss(zmm_G(i), ptr[reg_G + i * typesize]); + }; + + auto zmm_src = [=](int i) { + return Xbyak::Zmm(G_size + i); + }; + + auto load_src = [=](int i) { + for (int j = 0; j < alpha; j++) { + size_t alpha_offset = jcp.oc_block * jcp.oc_reg_block + * jcp.ic_block * simd_w * simd_w * typesize; + size_t src_off = (j * alpha + i) * alpha_offset; + vmovups(zmm_src(j), EVEX_compress_addr(reg_src, src_off)); + } + }; + + auto zmm_t = [=](int i) { + return Xbyak::Zmm(G_size + 6 + i); + }; + + auto zmm_T = [=](int j, int i) { + return Xbyak::Zmm(G_size + 6 + 3 + j * 6 + i); + }; + + auto zmm_dst = [=](int i) { + return Xbyak::Zmm(G_size + i); + }; + + auto zmm_temp = Xbyak::Zmm(31); + + auto store_dst = [=](int j) { + for (int i = 0; i < jcp.kw; i++) { + size_t dst_off = (j * jcp.kw + i) * simd_w * simd_w * typesize; + + if (!first_tile) { + vmovups(zmm_temp, EVEX_compress_addr(reg_dst, dst_off)); + vaddps(zmm_dst(i), zmm_dst(i), zmm_temp); + } + vmovntps(EVEX_compress_addr(reg_dst, dst_off), zmm_dst(i)); + } + }; + + auto compute_transform = [=] () { + mov(reg_src, ptr[reg_transp + GET_OFF(src)]); + mov(reg_dst, ptr[reg_transp + GET_OFF(dst)]); + + xor_(reg_ic_simd, reg_ic_simd); + Label loop_ic_simd; + L(loop_ic_simd); + { + for (int i = 0; i < alpha; i++) { + load_src(i); + + vaddps(zmm_t(0), zmm_src(1), zmm_src(2)); + vaddps(zmm_t(1), zmm_src(3), zmm_src(4)); + vmovups(zmm_t(2), zmm_src(5)); + vfmadd231ps(zmm_t(2), zmm_t(1), zmm_G(0)); + + vaddps(zmm_T(0, i), zmm_src(0), zmm_t(0)); + vaddps(zmm_T(0, i), zmm_T(0, i), zmm_t(1)); + vsubps(zmm_T(1, i), zmm_src(1), zmm_src(2)); + vmulps(zmm_T(1, i), zmm_T(1, i), zmm_G(1)); + vsubps(zmm_temp, zmm_src(3), zmm_src(4)); + vfmadd231ps(zmm_T(1, i), zmm_temp, zmm_G(2)); + vmovups(zmm_T(2, i), zmm_t(2)); + vfmadd231ps(zmm_T(2, i), zmm_t(0), zmm_G(3)); + } + + for (int j = 0; j < jcp.kh; j++) { + vaddps(zmm_t(0), zmm_T(j, 1), zmm_T(j, 2)); + vaddps(zmm_t(1), zmm_T(j, 3), zmm_T(j, 4)); + vmovups(zmm_t(2), zmm_T(j, 5)); + vfmadd231ps(zmm_t(2), zmm_t(1), zmm_G(0)); + + vaddps(zmm_dst(0), zmm_T(j, 0), zmm_t(0)); + vaddps(zmm_dst(0), zmm_dst(0), zmm_t(1)); + vsubps(zmm_dst(1), zmm_T(j, 1), zmm_T(j, 2)); + vmulps(zmm_dst(1), zmm_dst(1), zmm_G(1)); + vsubps(zmm_temp, zmm_T(j, 3), zmm_T(j, 4)); + vfmadd231ps(zmm_dst(1), zmm_temp, zmm_G(2)); + vmovups(zmm_dst(2), zmm_t(2)); + vfmadd231ps(zmm_dst(2), zmm_t(0), zmm_G(3)); + + store_dst(j); + } + + add(reg_src, jcp.oc_reg_block * simd_w * typesize); + add(reg_dst, simd_w * typesize); + add(reg_ic_simd, 1); + cmp(reg_ic_simd, simd_w); + jl(loop_ic_simd); + } + }; + preamble(); + push(reg_EVEX_max_8b_offt); + mov(reg_EVEX_max_8b_offt, 2 * EVEX_max_8b_offt); + init_G(); + compute_transform(); + pop(reg_EVEX_max_8b_offt); + postamble(); +} + +void jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel::gemm_loop_generate( + bool is_first_tile) +{ + auto zmm_srcA = [=]() { + return Xbyak::Zmm(0); + }; + + auto zmm_srcB = [=] (size_t N_ur){ + return Xbyak::Zmm(N_ur + 1); + }; + + auto broadcastB = [=](size_t K_ur) { + for (int N_bcast = 0; N_bcast < jcp.dimN_bcast_ur; N_bcast++) { + size_t srcB_off = (K_ur * jcp.dimN_reg_block + N_bcast) + * sizeof(float); + vbroadcastss(zmm_srcB(N_bcast), EVEX_compress_addr(reg_srcB, srcB_off)); + } + }; + + auto load_srcA = [=] (size_t K_ur, int M_ur) { + size_t srcA_off = (K_ur * jcp.dimM_reg_block * jcp.dimM_simd_block + + M_ur * jcp.dimM_simd_block) * sizeof(float); + vmovups(zmm_srcA(), EVEX_compress_addr(reg_srcA, srcA_off)); + }; + + auto zmm_dstC = [=](size_t M_reg_ur, int N_bcast){ + size_t idx = 1 // zmm_srcA + + jcp.dimN_bcast_ur // zmm_srcB + + M_reg_ur * jcp.dimN_bcast_ur + N_bcast; + assert(idx < 32); + return Xbyak::Zmm(idx); + }; + auto prepare_accumm = [=](){ + for (int M_reg_ur = 0; M_reg_ur < jcp.dimM_reg_block; M_reg_ur++) { + for (int N_bcast = 0; N_bcast < jcp.dimN_bcast_ur; N_bcast++) { + Zmm zmm = zmm_dstC(M_reg_ur, N_bcast); + vpxord(zmm, zmm, zmm); + } + } + }; + + auto store_dstC = [=](){ + /******** Write C back to memory *******/ + for (int M_reg = 0; M_reg < jcp.dimM_reg_block; M_reg++) { + for (int N_ur = 0; N_ur < jcp.dimN_bcast_ur; ++N_ur) { + Zmm zmm = zmm_dstC(M_reg, N_ur); + size_t C_off = (N_ur * jcp.dimM_reg_block * jcp.dimM_simd_block + + M_reg * jcp.dimM_simd_block) * sizeof(float); + if (!is_first_tile) { + vmovups(Xbyak::Zmm(0), EVEX_compress_addr(reg_dstC, C_off)); + vaddps(zmm, zmm, Xbyak::Zmm(0)); + } + vmovups(EVEX_compress_addr(reg_dstC, C_off), zmm); + } + } + }; + + auto inner_loops = [=]() { + Label dimM_block_loop, dimK_block_loop, dimN_block_loop, dimN_bcast_ur; + + mov(reg_dimM_block_loop_cnt, jcp.dimM_block); + L(dimM_block_loop); + { /************* OC_block (M) loop ***********/ + mov(reg_dimN_block_loop_cnt, jcp.dimN_block); + L(dimN_block_loop); + { /*************** IC_block (N) loop *********/ + + mov(reg_nb_dimN_bcast_ur, jcp.dimN_reg_block/jcp.dimN_bcast_ur); + L(dimN_bcast_ur); + { + prepare_accumm(); + + mov(reg_dimK_block_loop_cnt, jcp.dimK_block); + L(dimK_block_loop); + { + /************* nb_tile_ur(K) loop ********/ + for (int K_ur = 0; K_ur < jcp.dimK_reg_block; K_ur++) { + + broadcastB(K_ur); + + for (int M_reg_ur = 0; M_reg_ur < jcp.dimM_reg_block; M_reg_ur++) { + load_srcA(K_ur, M_reg_ur); + for (int N_bcast = 0; N_bcast < jcp.dimN_bcast_ur; ++N_bcast) { + vfmadd231ps(zmm_dstC(M_reg_ur, N_bcast), zmm_srcA(), + zmm_srcB(N_bcast)); + } + } + } + add(reg_srcA, jcp.dimK_reg_block + * jcp.dimM_reg_block * jcp.dimM_simd_block + * sizeof(float)); + add(reg_srcB, jcp.dimK_reg_block + * jcp.dimN_reg_block + * sizeof(float)); + sub(reg_dimK_block_loop_cnt, 1); + jnz(dimK_block_loop); + } + + store_dstC(); + + sub(reg_srcA, jcp.dimK_block * jcp.dimK_reg_block + * jcp.dimM_reg_block * jcp.dimM_simd_block + * sizeof(float)); + sub(reg_srcB, jcp.dimK_block * jcp.dimK_reg_block + * jcp.dimN_reg_block + * sizeof(float)); + add(reg_srcB, jcp.dimN_bcast_ur * sizeof(float)); + add(reg_dstC, jcp.dimN_bcast_ur + * jcp.dimM_reg_block * jcp.dimM_simd_block + * sizeof(float)); + sub(reg_nb_dimN_bcast_ur, 1); + jnz(dimN_bcast_ur); + } + + sub(reg_srcB, jcp.dimN_reg_block * sizeof(float)); + add(reg_srcB, jcp.dimK_block + * jcp.dimK_reg_block + * jcp.dimN_reg_block * sizeof(float)); + sub(reg_dimN_block_loop_cnt, 1); + jnz(dimN_block_loop); + } + + sub(reg_srcB, jcp.dimN_block + * jcp.dimK_block * jcp.dimK_reg_block + * jcp.dimN_reg_block + * sizeof(float)); + add(reg_srcA, jcp.dimK_block * jcp.dimK_reg_block + * jcp.dimM_reg_block * jcp.dimM_simd_block + * sizeof(float)); + sub(reg_dimM_block_loop_cnt, 1); + jnz(dimM_block_loop); + } + }; + + /* Preamble */ + preamble(); + + inner_loops(); + + /* Postamble */ + postamble(); + ret(); +} + +namespace { + +void set_jcp_WEI_params(jit_conv_winograd_conf_t &jcp) { +/*M params*/ + jcp.dimM_nb_block = jcp.dimM / jcp.dimM_block / jcp.dimM_reg_block + / jcp.dimM_simd_block; + jcp.oc_reg_block = jcp.dimM_reg_block; + jcp.oc_block = jcp.dimM_block; + jcp.nb_oc = jcp.dimM_nb_block; + /*N params*/ + jcp.dimN_nb_block = jcp.dimN / jcp.dimN_block / jcp.dimN_reg_block; + jcp.ic_block = jcp.dimN_block; + jcp.nb_ic = jcp.dimN_nb_block; + + /*K params*/ + jcp.dimK_nb_block = jcp.dimK / jcp.dimK_block / jcp.dimK_reg_block; + jcp.tile_block_ur = jcp.dimK_reg_block; + jcp.nb_tile_block_ur = jcp.dimK_block; + jcp.tile_block = jcp.dimK_nb_block; +} + +status_t set_wsched_WEI_SDGtWo(jit_conv_winograd_conf_t &jcp) { + + size_t K_blk_ur, N_blk, M_blk; + /* IS this strategy feasible? */ + auto test_MV_large_enough = [](jit_conv_winograd_conf_t &jcp) { + size_t M_sz = alpha * alpha * jcp.dimM * jcp.dimK * sizeof(float); + size_t V_sz = alpha * alpha * jcp.dimN * jcp.dimK * sizeof(float); + size_t nthreads = mkldnn_get_max_threads(); + return (((V_sz + M_sz) / nthreads) >= 2 * L2_cache_size) + && (jcp.dimK / nthreads >= 1.0); + }; + + auto test_min_dimK_L1 = [](jit_conv_winograd_conf_t &jcp, int dimK_block_ur, + int max_block=1) { + size_t L1_block_M = jcp.dimM_reg_block * jcp.dimM_simd_block * dimK_block_ur * sizeof(float); + size_t L1_block_N = jcp.dimN_reg_block * dimK_block_ur * sizeof(float); + size_t M_L2_block = alpha * alpha * jcp.dimM * dimK_block_ur * sizeof(float); + size_t nthreads = mkldnn_get_max_threads(); + bool load_balance=true; + if (!(jcp.dimK % nthreads)) { + load_balance = ((jcp.dimK / dimK_block_ur) % nthreads == 0); + } + return (L1_block_M + L1_block_N >= 0.1 * L1_cache_size) + && (L1_block_M + L1_block_N <= 0.5 * L1_cache_size) + && load_balance + && (M_L2_block < L2_cache_size); + }; + + auto test_dimK_ur = [](jit_conv_winograd_conf_t &jcp, int dimK_ur, + int useless_arg=0) { + return (dimK_ur >= 2) && (dimK_ur <= 8); + }; + + auto blocking_ok = [&](){ + size_t M_L2_block = alpha * alpha * M_blk * jcp.dimM_reg_block * jcp.dimM_simd_block + * K_blk_ur * sizeof(float); + size_t V_L2_block = alpha * alpha * N_blk * jcp.dimN_reg_block + * K_blk_ur * sizeof(float); + size_t U_L2_block = alpha * alpha * M_blk * jcp.dimM_reg_block * jcp.dimM_simd_block + * N_blk * jcp.dimN_reg_block * sizeof(float); + size_t L2_block = M_L2_block + V_L2_block + U_L2_block; + /*Replace 2.375 with L2+L3 cache size*/ + return (L2_block > 0.1 * L2_cache_size) && (L2_block <= 1.2 * L2_cache_size); + }; + + if (test_MV_large_enough(jcp)) { + if ((jcp.dimM/jcp.dimM_simd_block) % 2 == 0) { + jcp.dimM_reg_block = 2; + } else { + jcp.dimM_reg_block = 1; + } + jcp.dimM_simd_block = jcp.oc_simd_block; + jcp.dimN_reg_block = jcp.ic_simd_block; + jcp.dimN_bcast_ur = 8; + /*dimK_block and dimK_ur*/ + size_t min_dimK_block_ur = get_divisor_satisfying_cond(jcp, jcp.dimK, 1, test_min_dimK_L1); + + jcp.dimM_block = jcp.dimM/jcp.dimM_reg_block/jcp.dimM_simd_block; + jcp.dimN_block = jcp.dimN/jcp.dimN_reg_block; + for (K_blk_ur = min_dimK_block_ur; K_blk_ur >= 1; --K_blk_ur) { + if (test_min_dimK_L1(jcp, K_blk_ur) && !(jcp.dimK % K_blk_ur)) { + for (N_blk = jcp.dimN_block; N_blk >= 1; --N_blk) { + if (!(jcp.dimN_block % N_blk)) { + for (M_blk = jcp.dimM_block; M_blk >= 1; --M_blk) { + if (!(jcp.dimM_block % M_blk) && blocking_ok()) { + jcp.dimK_reg_block = get_divisor_satisfying_cond(jcp, K_blk_ur, 1, test_dimK_ur); + if (!test_dimK_ur(jcp, jcp.dimK_reg_block)) return status::unimplemented; + jcp.dimK_block = K_blk_ur / jcp.dimK_reg_block; + jcp.dimN_block = N_blk; + jcp.dimM_block = M_blk; + jcp.sched_policy = WSCHED_WEI_SDGtWo; + set_jcp_WEI_params(jcp); + jcp.nthr = nstl::min(mkldnn_get_max_threads(), + jcp.tile_block); + return status::success; + } + } + } + } + } + } + } + return status::unimplemented; +} + +status_t set_wsched_WEI_S_D_Giot_W(jit_conv_winograd_conf_t &jcp) { + if ((jcp.dimM/jcp.dimM_simd_block) % 2 == 0) { + jcp.dimM_reg_block = 2; + } else { + jcp.dimM_reg_block = 1; + } + jcp.dimN_bcast_ur = 8; + jcp.dimN_reg_block = jcp.ic_simd_block; + jcp.dimM_simd_block = jcp.oc_simd_block; + jcp.dimN_block = jcp.dimN / jcp.dimN_reg_block; + jcp.dimM_block = jcp.dimM / jcp.dimM_reg_block / jcp.dimM_simd_block; + float C1 = 0.0, C2 = 0.0; + float C1_max = 0.5, C2_max = 1.4; + int N_blk, M_blk, K_blk_ur; + + auto test_dimK_ur = [](jit_conv_winograd_conf_t &jcp, int dimK_ur, + int useless_arg=0) { + return (dimK_ur >= 2) && (dimK_ur <= 8); + }; + + auto blocking_ok = [&]() -> bool { + size_t L1_block_M = jcp.dimM_reg_block * jcp.dimM_simd_block * K_blk_ur * sizeof(float); + size_t L1_block_N = jcp.dimN_reg_block * K_blk_ur * sizeof(float); + bool L1_cond = ((L1_block_N + L1_block_M) >= C1 * L1_cache_size) + && ((L1_block_N + L1_block_M) <= C1_max * L1_cache_size); + + size_t nb_N_blk = jcp.dimN/N_blk/jcp.dimN_reg_block; + size_t nb_M_blk = jcp.dimM/M_blk/jcp.dimM_reg_block/jcp.dimM_simd_block; + size_t nb_K_blk = jcp.dimK / K_blk_ur; + size_t nthreads = mkldnn_get_max_threads(); + bool load_balance = (nb_K_blk * nb_N_blk * nb_M_blk) >= nthreads; + if (!(nb_K_blk % nthreads)) { + load_balance = load_balance && (nb_K_blk % nthreads == 0); + } + + size_t V_L2_block = alpha * alpha * N_blk * jcp.dimN_reg_block * K_blk_ur * sizeof(float); + + size_t L2_block = V_L2_block; + /*Replace 2.375 with L2+L3 cache size*/ + bool L2_cond = (L2_block >= C2 * L2_cache_size) && (L2_block <= C2_max * L2_cache_size); + return L1_cond && load_balance && L2_cond; + }; + + for (K_blk_ur = jcp.dimK; K_blk_ur >= 1; --K_blk_ur) { + if (jcp.dimK % K_blk_ur == 0) { + for (N_blk = jcp.dimN_block; N_blk >= 1; --N_blk) { + if (jcp.dimN_block % N_blk == 0) { + for (M_blk = jcp.dimM_block; M_blk >= 1; --M_blk) { + if (jcp.dimM_block % M_blk == 0) { + if (blocking_ok()) { + jcp.dimN_block = N_blk; + jcp.dimM_block = M_blk; + jcp.dimK_reg_block = get_divisor_satisfying_cond(jcp, K_blk_ur, 1, test_dimK_ur); + jcp.dimK_block = K_blk_ur / jcp.dimK_reg_block; + jcp.sched_policy = WSCHED_WEI_S_D_Giot_W; + set_jcp_WEI_params(jcp); + return status::success; + } + } + } + } + } + } + } + jcp.dimK_reg_block = 1; + jcp.dimK_block = 1; + jcp.sched_policy = WSCHED_WEI_S_D_Giot_W; + set_jcp_WEI_params(jcp); + return status::success; +} +} // namespace +status_t jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel::init_conf( + jit_conv_winograd_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &diff_dst_d, + const memory_desc_wrapper &diff_weights_d) { + if (!mayiuse(avx512_core)) + return status::unimplemented; + else + jcp.ver = ver_avx512_core; + + jcp.nthr = mkldnn_get_max_threads(); + + jcp.prop_kind = cd.prop_kind; + const bool with_groups = diff_weights_d.ndims() == src_d.ndims() + 1; + jcp.mb = src_d.dims()[0]; + jcp.ngroups = with_groups ? diff_weights_d.dims()[0] : 1; + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = diff_dst_d.dims()[2]; + jcp.ow = diff_dst_d.dims()[3]; + jcp.kh = diff_weights_d.dims()[with_groups + 2]; + jcp.kw = diff_weights_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.r_pad = nstl::max( + 0, (jcp.ow - 1) * jcp.stride_w + jcp.kw - jcp.iw - jcp.l_pad); + jcp.b_pad = nstl::max( + 0, (jcp.oh - 1) * jcp.stride_h + jcp.kh - jcp.ih - jcp.t_pad); + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + jcp.ohp = jcp.oh; + jcp.owp = jcp.ow; + jcp.with_bias = (cd.diff_bias_desc.format_kind != format_kind::undef); + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + bool ok_to_pad_channels = jcp.ngroups == 1; + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + } + + // Winograd specific initialization + jcp.itiles = (jcp.ow + tile_size - 1) / tile_size; + jcp.jtiles = (jcp.oh + tile_size - 1) / tile_size; + jcp.ntiles = jcp.mb * jcp.itiles * jcp.jtiles; + + // Winograd kernel works only for 3x3 convolution with stride 1 + if (!IMPLICATION(cd.alg_kind == alg_kind::convolution_auto, + is_winograd_faster_than_direct(jcp))) + return status::unimplemented; + + if (jcp.ngroups != 1) + return status::unimplemented; + if ((jcp.kh != 3) || (jcp.kw != 3)) + return status::unimplemented; + if ((jcp.dilate_h != 0) || (jcp.dilate_w != 0)) + return status::unimplemented; + if ((jcp.stride_h != 1) || (jcp.stride_w != 1)) + return status::unimplemented; + if ((jcp.ic % simd_w) != 0 || (jcp.oc % simd_w) != 0) + return status::unimplemented; + + format_tag_t dat_tag = nChw16c; + format_tag_t wei_tag = with_groups ? gOIhw16i16o : OIhw16i16o; + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = diff_weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(dat_tag); + + if (jcp.src_tag != dat_tag) return status::unimplemented; + if (jcp.wei_tag != wei_tag) return status::unimplemented; + if (jcp.dst_tag != dat_tag) return status::unimplemented; + + bool layout_consistency = true + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= diff_dst_d.padded_dims()[1] + && jcp.ic <= diff_weights_d.padded_dims()[with_groups + 1] + && jcp.oc <= diff_weights_d.padded_dims()[with_groups + 0]; + if (!layout_consistency) return status::unimplemented; + + /******************Kernel blocking Parameters ***********/ + jcp.ic_simd_block = simd_w; + jcp.oc_simd_block = simd_w; + + jcp.dimK = jcp.ntiles; + jcp.dimN = jcp.ic; + jcp.dimM = jcp.oc; + jcp.dimM_simd_block = jcp.oc_simd_block; + jcp.dimN_reg_block = jcp.ic_simd_block; + jcp.sched_policy = WSCHED_INVALID; + status_t res = set_wsched_WEI_SDGtWo(jcp); + if (res == status::unimplemented) { + res = set_wsched_WEI_S_D_Giot_W(jcp); + assert(res == status::success); + } + return res; +} +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.hpp new file mode 100644 index 000000000000..025a554d92a0 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.hpp @@ -0,0 +1,291 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_CORE_FP32_WINO_CONV_4x3_KERNEL_HPP +#define JIT_AVX512_CORE_FP32_WINO_CONV_4x3_KERNEL_HPP + +#include "c_types_map.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" + +#include "jit_avx512_common_conv_winograd_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct _jit_avx512_core_fp32_wino_conv_4x3_data_kernel + : public jit_generator { + _jit_avx512_core_fp32_wino_conv_4x3_data_kernel( + jit_conv_winograd_conf_t ajcp) + : jcp(ajcp) { + { + this->weights_transform_data_ker_generate(); + weights_transform_data_ker + = (decltype(weights_transform_data_ker)) this->getCode(); + } + { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->input_transform_data_ker_generate(); + input_transform_data_ker = (decltype(input_transform_data_ker))addr; + } + { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->output_transform_data_ker_generate(); + output_transform_data_ker + = (decltype(output_transform_data_ker))addr; + } + { + align(); + const Xbyak::uint8 *addr = getCurr(); + this->gemm_loop_generate(); + gemm_loop_ker = (decltype(gemm_loop_ker))addr; + } + } + + DECLARE_CPU_JIT_AUX_FUNCTIONS(_jit_avx512_core_fp32_wino_conv_4x3_data_kernel) + + static status_t init_conf_common(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d); + + static status_t init_conf_kernel( + jit_conv_winograd_conf_t &jcp, int dimM, int dimN, int dimK); + + jit_conv_winograd_conf_t jcp; + void (*gemm_loop_ker)(float *, const float *, const float *, const int); + void (*input_transform_data_ker)(jit_wino_transform_call_s *); + void (*output_transform_data_ker)(jit_wino_transform_call_s *); + void (*weights_transform_data_ker)(jit_wino_transform_call_s *); + +protected: + using reg64_t = const Xbyak::Reg64; + using reg32_t = const Xbyak::Reg32; + enum { typesize = sizeof(float) }; + + void gemm_loop_generate(); + void input_transform_data_ker_generate(); + void output_transform_data_ker_generate(); + void weights_transform_data_ker_generate(); + + /* registers used for GEMM */ + reg64_t reg_dstC = abi_param1; + reg64_t reg_srcA = abi_param2; + reg64_t reg_srcB = abi_param3; + reg64_t reg_is_beta_zero = abi_param4; + + reg64_t reg_dimM_block_loop_cnt = r10; + reg64_t reg_dimK_block_loop_cnt = r11; + + /* registers used for transforms*/ + reg64_t param = abi_param1; + + /* registers used for output_transform_data_ker */ + reg64_t oreg_temp = abi_not_param1; + reg64_t oreg_Ow = r9; + reg64_t oreg_src = r11; + reg64_t oreg_tile_block = r12; + reg64_t oreg_tile_block_ur = r13; + reg64_t oreg_nb_tile_block_ur = r14; + reg64_t oreg_O = r8; + reg64_t oreg_T = r10; + reg64_t oreg_dst = r11; + reg64_t oreg_ydim = r14; + reg64_t oreg_xdim = r15; + reg64_t oreg_out_j = r12; + reg64_t oreg_bias = rbx; + reg64_t imm_addr64 = rax; + + /* registers used for input_transform_data_ker */ + reg64_t ireg_temp = abi_not_param1; + reg64_t ireg_jtiles = rax; + reg64_t ireg_itiles = rbx; + reg64_t ireg_I = r8; + reg64_t ireg_src = r13; + reg64_t ireg_ydim = r14; + reg64_t ireg_xdim = r15; + reg64_t ireg_inp_j = r12; + reg64_t ireg_inp_i = rdx; + reg64_t ireg_mask_j = r11; + reg64_t ireg_mask = rsi; + reg32_t ireg_mask_32 = esi; + reg64_t ireg_zero = r9; + reg64_t ireg_Iw = r9; + reg64_t ireg_T = r10; + reg64_t ireg_tile_block = r12; + reg64_t ireg_tile_block_ur = r13; + reg64_t ireg_nb_tile_block_ur = r14; + reg64_t ireg_output = r15; + + /* registers used for wei transform */ + reg64_t wreg_temp = abi_not_param1; + reg64_t wreg_F = r8; + reg64_t wreg_src = r9; + reg64_t wreg_MT = r15; + reg64_t wreg_M = r14; + reg64_t wreg_dst = r10; + reg64_t wreg_dst_aux = r9; + reg64_t wreg_dst_idx = r8; + reg64_t wreg_Fw = r11; + reg64_t wreg_T = r12; + reg64_t wreg_cnt_j = rdx; + reg64_t wreg_F_aux = r14; + reg64_t wreg_Fw_aux = r15; +}; + +struct jit_avx512_core_fp32_wino_conv_4x3_fwd_kernel + : _jit_avx512_core_fp32_wino_conv_4x3_data_kernel { + using _jit_avx512_core_fp32_wino_conv_4x3_data_kernel:: + _jit_avx512_core_fp32_wino_conv_4x3_data_kernel; + + static bool post_ops_ok(jit_conv_conf_t &jcp, const primitive_attr_t &attr); + + static status_t init_conf(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_t &src_md, + memory_desc_t &weights_md, const memory_desc_t &dst_md, + const primitive_attr_t &attr); +}; + +struct jit_avx512_core_fp32_wino_conv_4x3_bwd_data_kernel + : public _jit_avx512_core_fp32_wino_conv_4x3_data_kernel { + using _jit_avx512_core_fp32_wino_conv_4x3_data_kernel:: + _jit_avx512_core_fp32_wino_conv_4x3_data_kernel; + + static status_t init_conf(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d); +}; + +struct jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel + : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS( + _jit_avx512_core_conv_winograd_bwd_weights_kernel_f32) + + jit_avx512_core_fp32_wino_conv_4x3_bwd_weights_kernel( + jit_conv_winograd_conf_t ajcp) + : jcp(ajcp) + { + //******************* First iter kernel ********************// + this->gemm_loop_generate(true); + gemm_loop_ker_first_iter = (decltype(gemm_loop_ker_first_iter))this->getCode(); + + align(); + const Xbyak::uint8 *addr = getCurr(); + this->src_transform_generate(); + src_transform = (decltype(src_transform))addr; + + if (jcp.with_bias) { + align(); + addr = getCurr(); + this->diff_dst_transform_generate(true); + diff_dst_transform_wbias = (decltype(diff_dst_transform_wbias))addr; + } + + align(); + addr = getCurr(); + this->diff_dst_transform_generate(false); + diff_dst_transform = (decltype(diff_dst_transform))addr; + + if (jcp.sched_policy != WSCHED_WEI_SDGtWo && jcp.tile_block > 1) { + align(); + addr = getCurr(); + this->gemm_loop_generate(false); + gemm_loop_ker = (decltype(gemm_loop_ker))addr; + } + + align(); + addr = getCurr(); + this->diff_weights_transform_generate(true); + diff_weights_transform = (decltype(diff_weights_transform))addr; + + if (jcp.sched_policy == WSCHED_WEI_SDGtWo) { + align(); + addr = getCurr(); + this->diff_weights_transform_generate(false); + diff_weights_transform_accum = + (decltype(diff_weights_transform_accum))addr; + }; + } + + static status_t init_conf(jit_conv_winograd_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &diff_dst_d, + const memory_desc_wrapper &diff_weights_d); + + jit_conv_winograd_conf_t jcp; + void (*gemm_loop_ker)(float *, const float *, const float *); + void (*gemm_loop_ker_first_iter)(float *, const float *, const float *); + void (*src_transform)(jit_wino_transform_call_s *); + void (*diff_dst_transform)(jit_wino_transform_call_s *); + void (*diff_dst_transform_wbias)(jit_wino_transform_call_s *); + void (*diff_weights_transform)(jit_wino_transform_call_s *); + void (*diff_weights_transform_accum)(jit_wino_transform_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + using reg32_t = const Xbyak::Reg32; + enum { typesize = sizeof(float) }; + + void src_transform_generate(); + void diff_dst_transform_generate(bool with_bias); + void diff_weights_transform_generate(bool first_tile); + + /*registers common to transforms*/ + reg64_t reg_transp = abi_param1; + reg64_t reg_ti = rbx; + reg64_t reg_tj = abi_not_param1; + reg64_t reg_src = r8; + reg64_t reg_dst = r9; + reg64_t reg_G = rsi; /*TODO: check if this is ok*/ + reg64_t reg_temp = rsi; + + /*registers common to src/diff_dst transform*/ + reg64_t reg_I = r10; + reg64_t reg_ydim = r11; + reg64_t reg_xdim = r12; + reg64_t reg_src_offset = r13; + reg64_t reg_zero = r14; + reg64_t reg_tile_count = r15; + reg64_t reg_maski = rsi; + reg32_t reg_maski_32 = esi; + reg64_t reg_maskj = rdx; + + reg64_t reg_T = rax; + reg64_t reg_oc_ur = rax; + reg64_t reg_ic_simd = r14; + reg64_t reg_bias = r10; + + void gemm_loop_generate(bool is_first_tile); + + reg64_t reg_dstC = abi_param1; + reg64_t reg_srcA = abi_param2; + reg64_t reg_srcB = abi_param3; + + reg64_t reg_dimM_block_loop_cnt = r9; + reg64_t reg_dimN_block_loop_cnt = r10; + reg64_t reg_nb_dimN_bcast_ur = r11; + reg64_t reg_dimK_block_loop_cnt = r12; +}; +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.cpp new file mode 100644 index 000000000000..002010ffa2a3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.cpp @@ -0,0 +1,1284 @@ +/******************************************************************************* + * Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_core_u8s8s32x_wino_convolution.hpp" +#include "jit_generator.hpp" + +#include + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +namespace { + // Below scales are applied to source and weights data accordingly + // because this winograd implementation + // transforms source which may increase values up to 4x + // and transforms weights which may increase values up to 9/4x + const float adj_src_scale = 1.f / 4.f; + const float adj_wei_scale = 4.f / 9.f; + // Winograd transforms need ic and oc to be multiples of 16 + const int load_block = 16; +} + +/// SRC TRANSFORMS ///////////////////////////////////////////////////////////// +struct jit_avx512_core_u8s8s32x_wino_conv_src_trans_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS( + jit_avx512_core_u8s8s32x_wino_conv_src_trans_t) + + jit_conv_conf_2x3_wino_t jcp; + const primitive_attr_t &attr_; + + struct call_params_t { + const void *src; + const void *wino_src; + const void *v_y_masks; + const void *v_x_masks; + }; + void (*ker_)(const call_params_t *); + + jit_avx512_core_u8s8s32x_wino_conv_src_trans_t( + jit_conv_conf_2x3_wino_t ajcp, const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), unsign_val_in_wino_domain(5) { + generate(); + ker_ = reinterpret_cast(const_cast(getCode())); + } + void generate(); + + int reg_inp_ind(int i) { + assert(i < jcp.alpha * jcp.alpha); + return (31 - i); + } + + Xmm vreg_inp(int i) { + return Xmm(reg_inp_ind(i)); + } + + Zmm zmm_inp(int i) { + return Zmm(reg_inp_ind(i)); + } + + Xmm vreg_tmp(int i) { + assert(i < jcp.alpha * jcp.alpha); + return Xmm(15 - i); + } + Xmm vreg_out(int i) { + assert(i < jcp.alpha * jcp.alpha); + return Xmm(31 - i); + } + + Opmask y_mask = Opmask(1); + Opmask r_mask = Opmask(2); + Opmask x_mask(int id) { + assert(id < 4); + return Opmask(3 + id); + } + + Reg64 reg_ptr_src = r14; + Reg64 reg_ptr_dst = r13; + + Reg64 reg_ptr_v_y_masks = r12; + Reg64 reg_ptr_v_x_masks = r11; + + Reg64 reg_aux_ptr_src = r10; + Reg64 reg_aux_ptr_dst = r9; + + Reg64 reg_ic_block = r8; + + int unsign_val_in_wino_domain; + + Reg64 reg_scratch_src_alpha = rdx; + Xmm xmm_src_alpha = Xmm(0); + Zmm zmm_src_alpha = Zmm(0); + + Reg64 reg_shift = rax; + Xmm xmm_shift = Xmm(1); + Xmm xmm_zero = Xmm(0); + + Reg64 reg_maskx = rbx; + Reg64 reg_masky = rsi; + Reg64 reg_nomask = reg_maskx; +}; + +void jit_avx512_core_u8s8s32x_wino_conv_src_trans_t::generate() { + Label ic_block_label; + Label end_label; + Label mask_label; + Label nomask_label; + + auto load_src = [=](bool mask) { + for (int y = 0; y < jcp.alpha; y++) { + if (mask) + kmovw(y_mask, ptr[reg_ptr_v_y_masks + sizeof(uint16_t) * y]); + for (int x = 0; x < jcp.alpha; x++) { + Zmm zmm_i = zmm_inp(y * jcp.alpha + x); + Xmm vreg_i = vreg_inp(y * jcp.alpha + x); + int inp_offset = sizeof(uint8_t) + * ((-jcp.t_pad + y) * jcp.iw * jcp.ic + + (-jcp.l_pad + x) * jcp.ic); + if (mask) { + kandw(r_mask, y_mask, x_mask(x)); + vmovdqu8(vreg_i | r_mask | T_z, + EVEX_compress_addr(reg_aux_ptr_src, inp_offset)); + } else { + vmovdqu8(vreg_i, + EVEX_compress_addr(reg_aux_ptr_src, inp_offset)); + } + vpmovzxbd(zmm_i, vreg_i); // to int32 + vcvtdq2ps(zmm_i, zmm_i); // to fp32 + vmulps(zmm_i, zmm_i, zmm_src_alpha); // *alpha + vcvtps2dq(zmm_i, zmm_i); // to int32 + vpmovusdb(vreg_i, zmm_i); // to u8 + } + } + }; + + preamble(); + +# define READ_PARAM(reg, field) \ + mov(reg, ptr[abi_param1 + offsetof(call_params_t, field)]) + READ_PARAM(reg_ptr_src, src); + READ_PARAM(reg_ptr_dst, wino_src); + READ_PARAM(reg_ptr_v_y_masks, v_y_masks); + READ_PARAM(reg_ptr_v_x_masks, v_x_masks); +# undef READ_PARAM + + mov(reg_maskx, ptr[reg_ptr_v_x_masks]); + mov(reg_masky, ptr[reg_ptr_v_y_masks]); + test(reg_maskx, reg_maskx); + jz(end_label, T_NEAR); // skip kernel if x mask is all 0's + test(reg_masky, reg_masky); + jz(end_label, T_NEAR); // skip kernel if y mask is all 0's + and_(reg_maskx, reg_masky); + mov(reg_nomask, reg_maskx); + not_(reg_nomask); // zero if x and y masks are all 1's + + xor_(reg_shift, reg_shift); + mov(reg_shift.cvt8(), (int8_t)-128); + + mov(reg_aux_ptr_src, reg_ptr_src); + mov(reg_aux_ptr_dst, reg_ptr_dst); + + for (int i = 0; i < jcp.alpha; i++) { + kmovw(x_mask(i), ptr[reg_ptr_v_x_masks + sizeof(uint16_t) * i]); + } + + mov(reg_scratch_src_alpha, float2int(adj_src_scale)); + + mov(reg_ic_block, jcp.ic / load_block); + L(ic_block_label); + { + vmovq(xmm_src_alpha, reg_scratch_src_alpha); + vbroadcastss(zmm_src_alpha, xmm_src_alpha); + + test(reg_nomask, reg_nomask); + jz(nomask_label, T_NEAR); + load_src(true); + jmp(mask_label, T_NEAR); + L(nomask_label); + load_src(false); + L(mask_label); + + for(int y = 0; y < 4; y++) { + vpsubb(vreg_tmp(y*4+0), vreg_inp(y*4+0), vreg_inp(y*4+2)); + vpaddb(vreg_tmp(y*4+1), vreg_inp(y*4+1), vreg_inp(y*4+2)); + vpsubb(vreg_tmp(y*4+2), vreg_inp(y*4+2), vreg_inp(y*4+1)); + vpsubb(vreg_tmp(y*4+3), vreg_inp(y*4+1), vreg_inp(y*4+3)); + } + for(int x = 0;x < 4; x++) { + vpsubb(vreg_out(x+0*4), vreg_tmp(x+4*0), vreg_tmp(x+4*2)); + vpaddb(vreg_out(x+1*4), vreg_tmp(x+4*1), vreg_tmp(x+4*2)); + vpsubb(vreg_out(x+2*4), vreg_tmp(x+4*2), vreg_tmp(x+4*1)); + vpsubb(vreg_out(x+3*4), vreg_tmp(x+4*1), vreg_tmp(x+4*3)); + } + + vmovd(xmm_shift, reg_shift.cvt32()); + vpxor(xmm_zero, xmm_zero, xmm_zero); + vpshufb(xmm_shift, xmm_shift, xmm_zero); + + for (int i = 0; i < 16; i++) { + int out_offset = sizeof(uint8_t) * (jcp.inp_stride * i); + if (i != unsign_val_in_wino_domain) + vpsubb(vreg_out(i), vreg_out(i), Xmm(1)); + vmovups(EVEX_compress_addr(reg_aux_ptr_dst, out_offset), vreg_out(i)); + } + + add(reg_aux_ptr_src, sizeof(uint8_t) * load_block); + add(reg_aux_ptr_dst, sizeof(uint8_t) * load_block); + } + dec(reg_ic_block); + jnz(ic_block_label, T_NEAR); + + L(end_label); + postamble(); +} + +/// DST TRANSFORMS ///////////////////////////////////////////////////////////// +struct jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS( + jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t) + + jit_conv_conf_2x3_wino_t jcp; + const primitive_attr_t &attr_; + + struct call_params_t { + const void *wino_dst; + const void *dst; + const void *v_y_masks; + const void *v_x_masks; + + const void *bias; + const void *scales; + }; + void (*ker_)(const call_params_t *); + + jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t( + jit_conv_conf_2x3_wino_t ajcp, const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr) { + generate(); + ker_ = reinterpret_cast(const_cast(getCode())); + } + + void generate(); + bool maybe_relu(int position); + + Zmm vreg_inp(int i) { // 16 + assert(i < jcp.alpha * jcp.alpha); + return Zmm(31 - i); + } + Zmm vreg_stg(int id) { // 8 + const int id_reg_stg = jcp.alpha * jcp.alpha + id; + assert(id < 8); + return Zmm(31 - id_reg_stg); + } + Zmm vreg_out(int id) { // 4 + const int id_reg_out = jcp.alpha * jcp.alpha + 8 + id; + assert(id < 4); + return Zmm(31 - id_reg_out); + } + Xmm xmm_out(int id) { // 4 + const int id_reg_out = jcp.alpha * jcp.alpha + 8 + id; + assert(id < 4); + return Xmm(31 - id_reg_out); + } + Zmm vreg_tmp(int id) { // 2 + const int id_reg_tmp = jcp.alpha * jcp.alpha + 12 + id; + assert(id < 2); + return Zmm(31 - id_reg_tmp); + } + + Zmm vreg_zero = Zmm(0); + Zmm vreg_bias = Zmm(1); + Zmm vreg_prev_dst = Zmm(2); + Zmm zmm_bias_alpha = Zmm(2); + Xmm xmm_bias_alpha = Xmm(2); + + Opmask y_mask = Opmask(1); + Opmask r_mask = Opmask(2); + Opmask x_mask(int id) { + assert(id < 4); + return Opmask(3 + id); + } + + Reg64 reg_scratch_bias_alpha = r15; + + Reg64 reg_ptr_src = r14; + Reg64 reg_ptr_dst = r13; + + Reg64 reg_ptr_v_y_masks = r12; + Reg64 reg_ptr_v_x_masks = r11; + + Reg64 reg_aux_ptr_src = r10; + Reg64 reg_aux_ptr_dst = r9; + + Reg64 reg_oc_block = r8; + + Reg64 reg_ptr_bias = rbx; + Reg64 reg_ptr_scales = abi_not_param1; + Reg64 reg_ptr_sum_scale = rdx; +}; + +bool jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t::maybe_relu(int position) { + using namespace primitive_kind; + const auto &p = attr_.post_ops_; + + if (position == 0) { + /* relu before sum */ + return false + || p.contain(eltwise, 0) + || (jcp.dst_dt == data_type::u8 && !p.contain(sum, 0)); + } else if (position == 1) { + /* relu after sum */ + const int sum_idx = p.contain(sum, 0) + ? 0 : (p.contain(sum, 1) ? 1 : -1); + if (sum_idx == -1) + return false; + + return false + || p.contain(eltwise, sum_idx + 1) + || jcp.dst_dt == data_type::u8; + } + + return false; +} + +void jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t::generate() { + Label oc_block_label; + + auto loop_body = [=]() { + const auto &p = attr_.post_ops_; + const int sum_idx = p.find(primitive_kind::sum); + const float *p_sum_scale = (sum_idx != -1) + ? &p.entry_[sum_idx].sum.scale + : nullptr; + if (p_sum_scale && *p_sum_scale != 1.f) + mov(reg_ptr_sum_scale, (size_t)p_sum_scale); + + for(int i = 0; i < 16; i++) { + int internal_offset = sizeof(int32_t) * jcp.out_stride * i; + vmovups(vreg_inp(i), + EVEX_compress_addr(reg_aux_ptr_src, internal_offset)); + } + for(int y = 0; y < jcp.alpha; y++) { + vpaddd(vreg_tmp(0), vreg_inp(y*4 + 0), vreg_inp(y*4 + 1)); + vpaddd(vreg_stg(y*2), vreg_tmp(0), vreg_inp(y*4 + 2)); + + vpsubd(vreg_tmp(1), vreg_inp(y*4 + 1), vreg_inp(y*4 + 2)); + vpsubd(vreg_stg(y*2+1), vreg_tmp(1), vreg_inp(y*4 + 3)); + } + for(int x = 0; x < jcp.m; x++) { + vpaddd(vreg_tmp(0), vreg_stg(x), vreg_stg(x+2*1)); + vpaddd(vreg_out(x), vreg_tmp(0), vreg_stg(x+2*2)); + + vpsubd(vreg_tmp(1), vreg_stg(x+2*1), vreg_stg(x+2*2)); + vpsubd(vreg_out(x+2), vreg_tmp(1), vreg_stg(x+2*3)); + } + + + if (jcp.with_bias) { + vmovq(xmm_bias_alpha, reg_scratch_bias_alpha); + vbroadcastss(zmm_bias_alpha, xmm_bias_alpha); + + auto bias_addr = ptr [ reg_ptr_bias ]; + switch (jcp.bia_dt) { + case data_type::f32: + case data_type::s32: vmovups(vreg_bias, bias_addr); break; + case data_type::s8: vpmovsxbd(vreg_bias, bias_addr); break; + case data_type::u8: vpmovzxbd(vreg_bias, bias_addr); break; + default: assert(!"unsupported dst data type"); + } + if (jcp.bia_dt != data_type::f32) + vcvtdq2ps(vreg_bias, vreg_bias); + vmulps(vreg_bias, vreg_bias, zmm_bias_alpha); // *alpha + } + for(int y = 0; y < jcp.m; y++) { + kmovw(y_mask, ptr[ reg_ptr_v_y_masks + sizeof(uint16_t) * y ]); + for(int x = 0; x < jcp.m; x++) { + kandw(r_mask, y_mask, x_mask(x)); + + int i = y * jcp.m + x; + int offset = jcp.typesize_out * + (y * jcp.ow * jcp.oc + x * jcp.oc); + Address addr = EVEX_compress_addr(reg_aux_ptr_dst, offset); + + Zmm zmm = vreg_out(i); + Xmm xmm = xmm_out(i); + vcvtdq2ps(zmm, zmm); + if (jcp.with_bias) + vaddps(zmm, zmm, vreg_bias); + vmulps(zmm, zmm, ptr [reg_ptr_scales]); + if (maybe_relu(0)) + vmaxps(zmm, vreg_zero, zmm); + if (p_sum_scale) { // post_op: sum + vpxord(vreg_prev_dst, vreg_prev_dst, vreg_prev_dst); + switch (jcp.dst_dt) { + case data_type::f32: + case data_type::s32: + vmovups(vreg_prev_dst | r_mask, addr); break; + case data_type::s8: + vpmovsxbd(vreg_prev_dst | r_mask, addr); break; + case data_type::u8: + vpmovzxbd(vreg_prev_dst | r_mask, addr); break; + default: assert(!"unknown dst_dt"); + } + if (jcp.dst_dt != data_type::f32) + vcvtdq2ps(vreg_prev_dst, vreg_prev_dst); + if (*p_sum_scale == 1.f) + vaddps(zmm, vreg_prev_dst); + else + vfmadd231ps(zmm, vreg_prev_dst, + zword_b[reg_ptr_sum_scale]); + } + if (maybe_relu(1)) + vmaxps(zmm, vreg_zero, zmm); + if (jcp.dst_dt != data_type::f32) + vcvtps2dq(zmm, zmm); + switch (jcp.dst_dt) { + case data_type::f32: + case data_type::s32: + vmovups(addr, zmm | r_mask); break; + case data_type::s8: + vpmovsdb(xmm, zmm); vmovups(addr, xmm | r_mask); break; + case data_type::u8: + vpmovusdb(xmm, zmm); vmovups(addr, xmm | r_mask); break; + default: assert(!"unknown dst_dt"); + } + } + } + }; + + preamble(); + +# define READ_PARAM(reg, field) \ + mov(reg, ptr[abi_param1 + offsetof(call_params_t, field)]) + READ_PARAM(reg_ptr_src, wino_dst); + READ_PARAM(reg_ptr_dst, dst); + READ_PARAM(reg_ptr_v_y_masks, v_y_masks); + READ_PARAM(reg_ptr_v_x_masks, v_x_masks); + READ_PARAM(reg_ptr_bias, bias); + READ_PARAM(reg_ptr_scales, scales); +# undef READ_PARAM + + if (jcp.with_bias) + mov(reg_scratch_bias_alpha, float2int(adj_src_scale * adj_wei_scale)); + + mov(reg_aux_ptr_src, reg_ptr_src); + mov(reg_aux_ptr_dst, reg_ptr_dst); + + vpxord(vreg_zero, vreg_zero, vreg_zero); + + for (int i = 0; i < jcp.m; i++) + kmovw(x_mask(i), ptr[reg_ptr_v_x_masks + sizeof(uint16_t) * i]); + + int oc_blocks = jcp.oc / load_block; + mov(reg_oc_block, oc_blocks); + L(oc_block_label); { + loop_body(); + add(reg_aux_ptr_src, sizeof(int32_t) * load_block); + add(reg_aux_ptr_dst, jcp.typesize_out * load_block); + + add(reg_ptr_scales, jcp.is_oc_scale * sizeof(float) * load_block); + add(reg_ptr_bias, sizeof(jcp.typesize_bia) * load_block); + } + dec(reg_oc_block); + jnz(oc_block_label, T_NEAR); + + postamble(); + +} + +/// GEMM kernel //////////////////////////////////////////////////////////////// +struct jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t) + jit_conv_conf_2x3_wino_t jcp; + const primitive_attr_t &attr_; + + struct call_params_t { + const void *src; + const void *dst; + const void *wei; + const void *dst_b; + }; + void (*ker_)(const call_params_t *); + + void generate(); + static bool post_ops_ok(jit_conv_conf_2x3_wino_t &jcp, + const primitive_attr_t &attr); + + jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t( + jit_conv_conf_2x3_wino_t ajcp, const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr) + { + generate(); + ker_ = reinterpret_cast(const_cast(getCode())); + } + + static status_t init_conf( + jit_conv_conf_2x3_wino_t &jcp, const convolution_desc_t &cd, + memory_desc_t &src_md, memory_desc_t &weights_md, + memory_desc_t &dst_md, memory_desc_t &bias_md, + const primitive_attr_t &attr); + + Zmm vreg_out(int n, int m) { + const int id_reg_out = n * jcp.m_block + m; + assert(id_reg_out < jcp.n2_block * jcp.m_block); + return Zmm(31 - id_reg_out); + } + Zmm vreg_wei(int i) { + assert(31 - jcp.n2_block * jcp.m_block - i + > (jcp.ver == ver_vnni ? 0 : 2)); + return Zmm(31 - jcp.n2_block * jcp.m_block - i); + } + + Zmm vreg_src = Zmm(0); + Zmm vreg_one = Zmm(1); + Zmm vreg_tmp = Zmm(2); + + Reg64 reg_ptr_src = r15; + + Reg64 reg_aux_dst_b = r13; + Reg64 reg_aux_dst = r12; + Reg64 reg_aux_dst2 = r11; + Reg64 reg_aux_wei = r10; + Reg64 reg_aux_wei2 = r9; + Reg64 reg_aux_src = r8; + Reg64 reg_aux_src2 = rax; + Reg64 reg_mb = rbx; + Reg64 reg_nnb = abi_not_param1; + Reg64 reg_scratch = rdx; + Reg64 reg_K = rsi; +}; + +bool jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t::post_ops_ok( + jit_conv_conf_2x3_wino_t &jcp, const primitive_attr_t &attr) { + using namespace primitive_kind; + const auto &p = attr.post_ops_; + + auto is_relu = [&](int idx) { return p.entry_[idx].is_relu(); }; + + switch (p.len_) { + case 0: return true; + case 1: return is_relu(0) || p.contain(sum, 0); + case 2: return (p.contain(sum, 0) && is_relu(1)) || + (p.contain(sum, 1) && is_relu(0)); + case 3: return is_relu(0) && p.contain(sum, 1) && is_relu(2); + default: return false; + } + + return false; +} + +void jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t::generate() { + Label nnb_loop_label, K_loop_label, mb_loop_label; + + auto compute = [=](Zmm vreg_acc, Zmm vreg_wei, Zmm vreg_src) { + if (jcp.ver == ver_vnni) { + vpdpbusd(vreg_acc, vreg_src, vreg_wei); + } else { + vpmaddubsw(vreg_tmp, vreg_src, vreg_wei); + vpmaddwd(vreg_tmp, vreg_tmp, vreg_one); + vpaddd(vreg_acc, vreg_acc, vreg_tmp); + } + }; + + preamble(); +# define READ_PARAM(reg, field) \ + mov(reg, ptr[abi_param1 + offsetof(call_params_t, field)]) + READ_PARAM(reg_ptr_src, src); + READ_PARAM(reg_aux_dst, dst); + READ_PARAM(reg_aux_wei, wei); + READ_PARAM(reg_aux_dst_b, dst_b); +# undef READ_PARAM + + if (jcp.ver != ver_vnni) { + xor_(reg_scratch, reg_scratch); + Reg16 _t = reg_scratch.cvt16(); + mov(_t, 0x1); + vpbroadcastw(vreg_one, _t); + } + + if (!jcp.small_mb) { + mov(reg_nnb, jcp.n_chunks); + L(nnb_loop_label); + } + mov(reg_aux_dst2, reg_aux_dst); + mov(reg_aux_src, reg_ptr_src); + mov(reg_mb, jcp.M / jcp.m_block); + L(mb_loop_label); + { + for (int nb2 = 0; nb2 < jcp.n2_block; nb2++) { + for (int m = 0; m < jcp.m_block; m++) { + int offset = jcp.typesize_acc * nb2 * jcp.n_block; + vmovups(vreg_out(nb2, m), + EVEX_compress_addr(reg_aux_dst_b, offset)); + } + } + mov(reg_aux_src2, reg_aux_src); + mov(reg_aux_wei2, reg_aux_wei); + mov(reg_K, jcp.k_chunks); + L(K_loop_label); + { + for (int k = 0; k < jcp.k2_block; k += 4) { + for (int nb2 = 0; nb2 < jcp.n2_block; nb2++) { + int wei_offset + = jcp.typesize_in * (nb2 * jcp.n_block * jcp.K); + vmovups(vreg_wei(nb2), + EVEX_compress_addr(reg_aux_wei2, wei_offset)); + } + for (int m = 0; m < jcp.m_block; m++) { + int inp_offset = jcp.typesize_in * m * jcp.K; + vpbroadcastd(vreg_src, + EVEX_compress_addr(reg_aux_src2, inp_offset)); + for (int nb2 = 0; nb2 < jcp.n2_block; nb2++) + compute(vreg_out(nb2, m), vreg_wei(nb2), vreg_src); + } + add(reg_aux_src2, jcp.typesize_in * 4); + add(reg_aux_wei2, jcp.typesize_in * 4 * jcp.n_block); + } + } + dec(reg_K); + jnz(K_loop_label, T_NEAR); + + for (int m = 0; m < jcp.m_block; m++) { + for (int nb2 = 0; nb2 < jcp.n2_block; nb2++) { + int offset = jcp.typesize_acc * (m * jcp.N + nb2 * jcp.n_block); + vmovups(EVEX_compress_addr(reg_aux_dst2, offset), + vreg_out(nb2, m)); + } + } + add(reg_aux_src, jcp.typesize_in * jcp.m_block * jcp.K); + add(reg_aux_dst2, jcp.typesize_acc * jcp.m_block * jcp.N); + } + dec(reg_mb); + jnz(mb_loop_label, T_NEAR); + + if (!jcp.small_mb) { + add(reg_aux_dst, jcp.typesize_acc * jcp.n2_block * jcp.n_block); + add(reg_aux_dst_b, jcp.typesize_acc * jcp.n2_block * jcp.n_block); + add(reg_aux_wei, jcp.typesize_in * jcp.n2_block * jcp.n_block * jcp.K); + + dec(reg_nnb); + jnz(nnb_loop_label, T_NEAR); + } + + postamble(); +} +namespace { +bool is_winograd_faster_than_direct(const jit_conv_conf_2x3_wino_t &jcp) { + if (jcp.ver == ver_vnni) { + return (jcp.mb <= mkldnn_get_max_threads() + && (jcp.mb > 4 + && jcp.ic > 64 + && !(jcp.oc > 128 && jcp.ih < 14))) + || jcp.mb > mkldnn_get_max_threads(); + } + return true; +} +} + +status_t jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t +::init_conf(jit_conv_conf_2x3_wino_t &jcp, + const convolution_desc_t &cd, memory_desc_t &src_md, + memory_desc_t &wei_md, memory_desc_t &dst_md, + memory_desc_t &bias_md, const primitive_attr_t &attr) { + const memory_desc_wrapper src_d(&src_md); + const memory_desc_wrapper wei_d(&wei_md); + const memory_desc_wrapper dst_d(&dst_md); + const memory_desc_wrapper bias_d(&bias_md); + + const bool with_groups = wei_d.ndims() == src_d.ndims() + 1; + + jcp.nthr = mkldnn_get_max_threads(); + + jcp.ngroups = with_groups ? wei_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = dst_d.dims()[2]; + jcp.ow = dst_d.dims()[3]; + jcp.kh = wei_d.dims()[with_groups + 2]; + jcp.kw = wei_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.b_pad = cd.padding[1][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.r_pad = cd.padding[1][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + jcp.ver = ver_avx512_core; + if (!(mayiuse(avx512_core) && + src_d.data_type() == data_type::u8 + && wei_d.data_type() == data_type::s8 + && one_of(dst_d.data_type(), data_type::f32, data_type::s32, + data_type::s8, data_type::u8))) + return status::unimplemented; + if (mayiuse(avx512_core_vnni)) + jcp.ver = ver_vnni; + + if (!IMPLICATION(cd.alg_kind == alg_kind::convolution_auto, + is_winograd_faster_than_direct(jcp))) + return status::unimplemented; + + // block sizes needed for GEMM kernel + jcp.ic_block = 4; + jcp.oc_block = 16; + + bool ok = true + && jcp.ngroups == 1 + && jcp.oc % load_block == 0 && jcp.ic % load_block == 0 + && jcp.oc % jcp.oc_block == 0 && jcp.ic % jcp.ic_block == 0 + && everyone_is(3, jcp.kh, jcp.kw) + && everyone_is(1, jcp.stride_h, jcp.stride_w) + && everyone_is(0, jcp.dilate_h, jcp.dilate_w) + && jcp.t_pad == jcp.b_pad && jcp.l_pad == jcp.r_pad + && one_of(jcp.t_pad, 0, 1) + && one_of(jcp.l_pad, 0, 1); + if (!ok) return status::unimplemented; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + jcp.bia_dt = jcp.with_bias ? cd.bias_desc.data_type : data_type::undef; + jcp.dst_dt = cd.dst_desc.data_type; + + jcp.typesize_in = types::data_type_size(src_d.data_type()); + jcp.typesize_out = types::data_type_size(dst_d.data_type()); + jcp.typesize_acc = sizeof(int32_t); + jcp.typesize_bia = jcp.with_bias + ? types::data_type_size(bias_d.data_type()) + : 0; + + jcp.nb_oc = jcp.oc / jcp.oc_block; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + jcp.m = 2; + jcp.r = 3; + jcp.alpha = jcp.m + jcp.r - 1; + + int aa = jcp.alpha * jcp.alpha; + int L1_cap = get_cache_size(1, true); + int L2_cap = get_cache_size(2, true); + // need 1 extra reg for bcast, and 2 tmp regs for non-vnni + int free_regs = jcp.ver == ver_vnni ? 31 : 29; + + auto get_thr_eff = [&](int small_mb, int ix, int iy, int n2_b) { + float thr_eff; + float Z = (float)jcp.ic + jcp.oc; + float Y = (float)jcp.ic * jcp.oc; + if (small_mb == 0) { // outer par + int nblocks = jcp.mb * div_up(jcp.oh, iy) * div_up(jcp.ow, ix); + thr_eff = (float)nblocks / rnd_up(nblocks, jcp.nthr); + } else { // inner par + int tranw = iy * ix / jcp.alpha; + int gemmw = aa * (jcp.nb_oc / n2_b); + int tranw_r = rnd_up(tranw, jcp.nthr); + int gemmw_r = rnd_up(gemmw, jcp.nthr); + thr_eff = (Z * tranw / tranw_r + Y * gemmw / gemmw_r) / (Z + Y); + } + return thr_eff; + }; + + auto get_mem_eff = [&](int small_mb, int ix, int iy, int n2_b) { + float mem_eff, req_mem; + int M = ix * iy / jcp.alpha; + if (small_mb == 0) { // outer parallelization strategy + // memory for wino transforms (other memory has poor reuse) + req_mem = (float)aa * M * (jcp.ic + jcp.typesize_acc * jcp.oc); + mem_eff = req_mem < L1_cap ? 1.f : req_mem < L2_cap ? 0.5f : 0.f; + } else { // inner parallelization strategy + // memory used during gemm + int N = jcp.oc_block * n2_b; + req_mem = (float)jcp.ic * (M + N) + jcp.typesize_acc * M * N; + mem_eff = nstl::min(1.f, L2_cap / req_mem); + // memory used during wino transforms + int M_per_thr = div_up(M, jcp.nthr); + req_mem = (float)aa * M_per_thr + * (jcp.ic + jcp.typesize_acc * jcp.oc); + if (req_mem > L2_cap) + mem_eff = 0.1f; + } + return mem_eff; + }; + + auto get_tot_eff = [&](int small_mb, float thr_eff, float work_eff, + float mem_eff, float reg_eff) { + // these coefficients are chosen empirically + float mem_fac = 0.1f, reg_fac = 0.2f; + // normalized overhead relative to memory and register components + float tot_eff = 1.f + mem_fac * mem_eff + reg_fac * reg_eff; + // thread and work components affect all others + tot_eff *= thr_eff * work_eff; + return tot_eff; + }; + + auto find_m_n2_blocks = [&](bool small_mb, int ix, int iy, float work_eff, + int &m_block, int &n2_block, float &tot_eff) { + int M = (ix * iy) / jcp.alpha; + int max_m_block = nstl::min(M, free_regs); + int max_n2_block = nstl::min(jcp.nb_oc, free_regs); + tot_eff = 0.f; + for (int im = max_m_block; im > 0; im--) { + if (M % im) + continue; + for (int in2 = max_n2_block; in2 > 0; in2--) { + int used_regs = (im + 1) * in2; + float mem_eff = get_mem_eff(small_mb, ix, iy, in2); + float reg_eff = (float)(im * in2) / (im + in2); + float thr_eff = get_thr_eff(small_mb, ix, iy, in2); + float cur_tot_eff = get_tot_eff( + small_mb, thr_eff, work_eff, mem_eff, reg_eff); + if (jcp.nb_oc % in2 || used_regs > free_regs + || cur_tot_eff <= tot_eff) + continue; + tot_eff = cur_tot_eff; + m_block = im; + n2_block = in2; + } + } + }; + + /* Selecting xb and yb blocking */ + int min_yb = jcp.m; + int min_xb = jcp.m; + int max_yb = nstl::max(min_yb, rnd_up(jcp.oh, 2)); + int max_xb = nstl::max(min_xb, rnd_up(jcp.ow, 2)); + float best_eff = 0.f; + for (int ix = min_xb; ix <= max_xb; ix += 2) { + assert(rnd_up(jcp.ow, ix) >= jcp.iw - 2); + for (int iy = max_yb; iy >= min_yb; iy -= 2) { + assert(rnd_up(jcp.oh, iy) >= jcp.ih - 2); + + int m_b[2]; + int n2_b[2]; + bool small_mb; + float inner_eff, outer_eff, work_eff; + + int tiled_area = rnd_up(jcp.oh, iy) * rnd_up(jcp.ow, ix); + work_eff = (float)jcp.oh * jcp.ow / tiled_area; + if (best_eff > 0.f && work_eff < 4.f / 9.f) + continue; // no gain from Winograd transformation + + /* outer parallelization */ + find_m_n2_blocks(0, ix, iy, work_eff, m_b[0], n2_b[0], outer_eff); + + /* inner parallelization */ + find_m_n2_blocks(1, ix, iy, work_eff, m_b[1], n2_b[1], inner_eff); + + small_mb = inner_eff > outer_eff; + float eff = small_mb ? inner_eff : outer_eff; + if (eff > best_eff) { + best_eff = eff; + jcp.yb = iy; + jcp.xb = ix; + jcp.m_block = m_b[small_mb]; + jcp.n2_block = n2_b[small_mb]; + jcp.small_mb = small_mb; + } + } + } + + assert((jcp.m_block + 1) * jcp.n2_block <= free_regs); + assert(jcp.xb % 2 == 0 && jcp.yb % 2 == 0); + + jcp.mb_block = 1; + if (jcp.small_mb) { + // For small mb harness, set mb_block as large as possible subject to + // the constraint that winograd activations fit into available L3 cache + int L3_cap = get_cache_size(3, true); + int M = jcp.xb * jcp.yb / 4; + int wino_src_size = 16 * M * jcp.ic * jcp.typesize_in; + int wino_dst_size = 16 * M * jcp.oc * jcp.typesize_acc; + int max_mb_block = nstl::min( + jcp.mb, jcp.nthr * L3_cap / (wino_src_size + wino_dst_size)); + for (int i = max_mb_block; i > 1; i--) { + if (jcp.mb % i == 0) { + jcp.mb_block = i; + break; + } + } + } + jcp.nb_mb = jcp.mb / jcp.mb_block; + + jcp.M = jcp.mb_block * jcp.xb * jcp.yb / 4; + jcp.N = jcp.oc; + jcp.K = jcp.ic; + + jcp.inp_stride = jcp.M * jcp.ic; + jcp.out_stride = jcp.M * jcp.oc; + jcp.wei_stride = jcp.ic * jcp.oc; + jcp.bia_stride = jcp.oc; + + jcp.n_block = jcp.oc_block; + jcp.k_block = jcp.ic_block; + + jcp.n_chunks = (jcp.N / jcp.n_block) / jcp.n2_block; + + // We need jcp.k2_block to be a multiple of jcp.k_block = jcp.ic_block = 4 + // and jcp.K = jcp.ic to be a multiple of jcp.k2_block. Since jcp.ic is + // a multiple of load_block = 16, we just use that for now. + jcp.k2_block = load_block; + jcp.k_chunks = jcp.K / jcp.k2_block; + + const auto &oscales = attr.output_scales_; + jcp.is_oc_scale = oscales.mask_ == 1 << 1; + assert(IMPLICATION(!jcp.is_oc_scale, oscales.mask_ == 0)); + + /* re-create weights primitive descriptor + and set weights wino_blocking */ + memory_desc_t expect_wei_md = wei_md; + + expect_wei_md.format_kind = format_kind::wino; + expect_wei_md.data_type = data_type::s8; + mkldnn_wino_desc_t &wd = expect_wei_md.format_desc.wino_desc; + wd.wino_format = mkldnn_wino_wei_aaOIoi; + wd.r = jcp.r; + wd.alpha = jcp.alpha; + wd.ic = jcp.ic; + wd.oc = jcp.oc; + wd.ic_block = jcp.ic_block; + wd.oc_block = jcp.oc_block; + wd.oc2_block = jcp.n2_block; + wd.ic2_block = 1; + wd.adj_scale = adj_wei_scale; + + size_t max_size = types::data_type_size(data_type::s8) * + jcp.alpha * jcp.alpha * jcp.ic * jcp.oc; + max_size += types::data_type_size(data_type::s32) * + jcp.alpha * jcp.alpha * jcp.oc; + wd.size = max_size; + + if (wei_md.format_kind == format_kind::any) + wei_md = expect_wei_md; + if (wei_md != expect_wei_md) + return status::unimplemented; + + const int tilesize = jcp.alpha * jcp.alpha; + const int numtiles = jcp.M; + const int alltiles = numtiles * tilesize; + + jcp.size_wino_src + = utils::rnd_up(jcp.typesize_in * alltiles * jcp.ic, PAGE_4K) + / jcp.typesize_in; + jcp.size_wino_wei = tilesize * jcp.oc * jcp.ic; + jcp.size_wino_dst = alltiles * jcp.oc; + + return status::success; +} +//////////////////////////////////////////////////////////////////////////////// + +template +status_t jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: + pd_t::jit_conf() { + return jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t::init_conf( + jcp_, *this->desc(), this->src_md_, this->weights_md_, + this->dst_md_,this->bias_md_, *this->attr()); +} + +template +void jit_avx512_core_u8s8s32x_wino_convolution_fwd_t::pd_t:: +init_scratchpad() { + auto scratchpad = this->scratchpad_registry().registrar(); + + int nthr_multiplier = jcp_.small_mb ? 1 : jcp_.nthr; + scratchpad.book(key_wino_V, + sizeof(src_data_t) * jcp_.size_wino_src * nthr_multiplier, PAGE_4K); + scratchpad.book(key_wino_M, + sizeof(acc_data_t) * jcp_.size_wino_dst * nthr_multiplier, PAGE_4K); + + dim_t scale_count = attr()->output_scales_.count_; + scratchpad.book(key_conv_adjusted_scales, + sizeof(float) * nstl::max(scale_count, 16)); +} + +template +jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: + jit_avx512_core_u8s8s32x_wino_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) +{ + kernel_ = new jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t( + pd()->jcp_, *pd()->attr()); + src_trans_ = new jit_avx512_core_u8s8s32x_wino_conv_src_trans_t( + pd()->jcp_, *pd()->attr()); + dst_trans_ = new jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t( + pd()->jcp_, *pd()->attr()); +} + +template +jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: + ~jit_avx512_core_u8s8s32x_wino_convolution_fwd_t() { + delete kernel_; + delete src_trans_; + delete dst_trans_; +} + +template +const float *jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: +adjust_oscales(const memory_tracking::grantor_t &scratchpad) const { + const float *oscales = pd()->attr()->output_scales_.scales_; + auto loc_scales = scratchpad.template get(key_conv_adjusted_scales); + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / (adj_src_scale * adj_wei_scale); + if (count == 1) + utils::array_set(loc_scales, oscales[0] * factor, 16); + else + for (size_t c = 0; c < count; c++) loc_scales[c] = oscales[c] * factor; + return loc_scales; +} + +template +void jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const auto &jcp = kernel_->jcp; + if (jcp.small_mb) + execute_forward_small_mb(src, weights, bias, dst, this->scratchpad(ctx)); + else + execute_forward_mbN(src, weights, bias, dst, this->scratchpad(ctx)); +} + +template +void jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: +execute_forward_mbN(const src_data_t *src, const wei_data_t *wei, + const char *bia, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const float *oscales = adjust_oscales(scratchpad); + + auto dst_bias = (const acc_data_t *)(wei + jcp.size_wino_wei); + auto wino_src_base = scratchpad.template get(key_wino_V); + auto wino_dst_base = scratchpad.template get(key_wino_M); + + parallel_nd(jcp.mb, div_up(jcp.oh, jcp.yb), div_up(jcp.ow, jcp.xb), + [&](int mb, int tile_y_b, int tile_x_b) { + + int tile_y = tile_y_b * jcp.yb; + int tile_x = tile_x_b * jcp.xb; + + int ithr = mkldnn_get_thread_num(); + auto wino_src = wino_src_base + jcp.size_wino_src * ithr; + auto wino_dst = wino_dst_base + jcp.size_wino_dst * ithr; + + auto src_trans_p = + jit_avx512_core_u8s8s32x_wino_conv_src_trans_t::call_params_t(); + auto dst_trans_p = + jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t::call_params_t(); + auto gemm_p = + jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t::call_params_t(); + + /* transformation of input tensor to winograd domain */ + for (int y_in_block = 0; y_in_block < jcp.yb; y_in_block += 2) { + for (int x_in_block = 0; x_in_block < jcp.xb; x_in_block += 2) { + uint16_t v_y_masks[4], v_x_masks[4]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (y_in_block / 2) * (jcp.xb / 2) + (x_in_block / 2); + + int v_ys = nstl::max(0, jcp.t_pad - y); + int v_ye = nstl::min(jcp.alpha, + nstl::max(0, jcp.ih + jcp.t_pad - y)); + + int v_xs = nstl::max(0, jcp.l_pad - x); + int v_xe = nstl::min(jcp.alpha, + nstl::max(0, jcp.iw + jcp.l_pad - x)); + +#pragma unroll(4) + for (int i = 0; i < jcp.alpha; i++) { + v_y_masks[i] = uint16_t(i < v_ys || i >= v_ye ? 0 : 0xffff); + v_x_masks[i] = uint16_t(i < v_xs || i >= v_xe ? 0 : 0xffff); + } + auto local_s = src + + mb * jcp.ih * jcp.iw * jcp.ic + + y * jcp.iw * jcp.ic + x * jcp.ic; + auto local_w = wino_src + m * jcp.ic; + + src_trans_p.src = local_s; + src_trans_p.wino_src = local_w; + src_trans_p.v_y_masks = v_y_masks; + src_trans_p.v_x_masks = v_x_masks; + + src_trans_->ker_(&src_trans_p); + } + } + /* gemms */ + for (int tile_ij = 0; tile_ij < 16; tile_ij++) { + // start threads at different GEMMs to help bring weights into LLC + int offset = (tile_ij + ithr) % 16; + gemm_p.src = wino_src + jcp.inp_stride * offset; + gemm_p.dst = wino_dst + jcp.out_stride * offset; + gemm_p.wei = wei + jcp.wei_stride * offset; + gemm_p.dst_b = dst_bias + jcp.bia_stride * offset; + + kernel_->ker_(&gemm_p); + } + + /* transformation from winograd domain to output tensor */ + for (int y_in_block = 0; y_in_block < jcp.yb; y_in_block += 2) { + for (int x_in_block = 0; x_in_block < jcp.xb; x_in_block += 2) { + uint16_t v_y_masks[2], v_x_masks[2]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (y_in_block / 2) * (jcp.xb / 2) + (x_in_block / 2); + +#pragma unroll(2) + for (int i = 0; i < jcp.m; i++) { + v_x_masks[i] = uint16_t(x + i < jcp.ow ? 0xffff : 0); + v_y_masks[i] = uint16_t(y + i < jcp.oh ? 0xffff : 0); + } + auto local_d = dst + + mb * jcp.oh * jcp.ow * jcp.oc + + y * jcp.ow * jcp.oc + x * jcp.oc; + auto local_w = wino_dst + m * jcp.oc; + + auto scales = oscales; + dst_trans_p.dst = local_d; + dst_trans_p.wino_dst = local_w; + dst_trans_p.v_y_masks = v_y_masks; + dst_trans_p.v_x_masks = v_x_masks; + + dst_trans_p.scales = scales; + dst_trans_p.bias = bia; + + dst_trans_->ker_(&dst_trans_p); + } + } + }); +} + +template +void jit_avx512_core_u8s8s32x_wino_convolution_fwd_t:: +execute_forward_small_mb(const src_data_t *src, const wei_data_t *wei, + const char *bia, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + const auto &jcp = kernel_->jcp; + const float *oscales = adjust_oscales(scratchpad); + + auto dst_bias = (const acc_data_t *)(wei + jcp.size_wino_wei); + auto wino_src = scratchpad.template get(key_wino_V); + auto wino_dst = scratchpad.template get(key_wino_M); + + for (int mbb = 0; mbb < jcp.nb_mb; mbb++) { + for (int tile_y = 0; tile_y < jcp.oh; tile_y += jcp.yb) { + for (int tile_x = 0; tile_x < jcp.ow; tile_x += jcp.xb) { + /* transformation of input tensor to winograd domain */ + parallel_nd(div_up(jcp.yb, 2), div_up(jcp.xb, 2), jcp.mb_block, + [&](int y_in_block_b, int x_in_block_b, int mb) { + int y_in_block = y_in_block_b * 2; + int x_in_block = x_in_block_b * 2; + + auto src_trans_p = + jit_avx512_core_u8s8s32x_wino_conv_src_trans_t::call_params_t(); + + uint16_t v_y_masks[4], v_x_masks[4]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (mb * (jcp.yb / 2) + (y_in_block / 2)) * (jcp.xb / 2) + + (x_in_block / 2); + + int v_ys = nstl::max(0, jcp.t_pad - y); + int v_ye = nstl::min( + jcp.alpha, nstl::max(0, jcp.ih + jcp.t_pad - y)); + + int v_xs = nstl::max(0, jcp.l_pad - x); + int v_xe = nstl::min( + jcp.alpha, nstl::max(0, jcp.iw + jcp.l_pad - x)); + +#pragma unroll(4) + for (int i = 0; i < jcp.alpha; i++) { + v_y_masks[i] = uint16_t(i < v_ys || i >= v_ye ? 0 : 0xffff); + v_x_masks[i] = uint16_t(i < v_xs || i >= v_xe ? 0 : 0xffff); + } + auto local_s = src + + (mbb * jcp.mb_block + mb) * jcp.ih * jcp.iw * jcp.ic + + y * jcp.iw * jcp.ic + x * jcp.ic; + auto local_w = wino_src + m * jcp.ic; + + src_trans_p.src = local_s; + src_trans_p.wino_src = local_w; + src_trans_p.v_y_masks = v_y_masks; + src_trans_p.v_x_masks = v_x_masks; + + src_trans_->ker_(&src_trans_p); + }); + + /* gemms */ + parallel_nd(16, jcp.n_chunks, [&](int tile_ij, int nnb) { + auto gemm_p = jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t:: + call_params_t(); + + gemm_p.src = wino_src + jcp.inp_stride * tile_ij; + gemm_p.dst = wino_dst + jcp.out_stride * tile_ij + + nnb * jcp.n2_block * jcp.n_block; + gemm_p.wei = wei + jcp.wei_stride * tile_ij + + nnb * jcp.n2_block * jcp.n_block * jcp.K; + gemm_p.dst_b = dst_bias + jcp.bia_stride * tile_ij + + nnb * jcp.n2_block * jcp.n_block; + + kernel_->ker_(&gemm_p); + }); + + /* transformation from winograd domain to output tensor */ + parallel_nd(div_up(jcp.yb, 2), div_up(jcp.xb, 2), jcp.mb_block, + [&](int y_in_block_b, int x_in_block_b, int mb) { + int y_in_block = y_in_block_b * 2; + int x_in_block = x_in_block_b * 2; + + auto dst_trans_p = + jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t::call_params_t(); + + uint16_t v_y_masks[2], v_x_masks[2]; + + int y = y_in_block + tile_y; + int x = x_in_block + tile_x; + int m = (mb * (jcp.yb / 2) + (y_in_block / 2)) * (jcp.xb / 2) + + (x_in_block / 2); + +#pragma unroll(2) + for (int i = 0; i < jcp.m; i++) { + v_x_masks[i] = uint16_t(x + i < jcp.ow ? 0xffff : 0); + v_y_masks[i] = uint16_t(y + i < jcp.oh ? 0xffff : 0); + } + auto local_d = dst + + (mbb * jcp.mb_block + mb) * jcp.oh * jcp.ow * jcp.oc + + y * jcp.ow * jcp.oc + x * jcp.oc; + auto local_w = wino_dst + m * jcp.oc; + + auto scales = oscales; + dst_trans_p.dst = local_d; + dst_trans_p.wino_dst = local_w; + dst_trans_p.v_y_masks = v_y_masks; + dst_trans_p.v_x_masks = v_x_masks; + + dst_trans_p.scales = scales; + dst_trans_p.bias = bia; + + dst_trans_->ker_(&dst_trans_p); + }); + }}} +} + +template struct jit_avx512_core_u8s8s32x_wino_convolution_fwd_t; +template struct jit_avx512_core_u8s8s32x_wino_convolution_fwd_t; +template struct jit_avx512_core_u8s8s32x_wino_convolution_fwd_t; +template struct jit_avx512_core_u8s8s32x_wino_convolution_fwd_t; + +} // namespace cpu +} // namespace impl +} // namespace mkldnn diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.hpp new file mode 100644 index 000000000000..9e6e57b051c6 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_u8s8s32x_wino_convolution.hpp @@ -0,0 +1,128 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_U8S8S32X_WINO_CONVOLUTION_HPP +#define CPU_JIT_AVX512_CORE_U8S8S32X_WINO_CONVOLUTION_HPP + +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_primitive_conf.hpp" +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t; +struct jit_avx512_core_u8s8s32x_wino_conv_src_trans_t; +struct jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t; + +template +struct jit_avx512_core_u8s8s32x_wino_convolution_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_int8_wino:", avx512_core, ""), + jit_avx512_core_u8s8s32x_wino_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && utils::one_of(desc()->alg_kind, + alg_kind::convolution_auto, + alg_kind::convolution_winograd) + && expect_data_types(data_type::u8, data_type::s8, + data_type::undef, dst_data_type, data_type::s32) + && IMPLICATION(with_bias(), utils::one_of( + desc()->bias_desc.data_type, data_type::f32, + data_type::s32, data_type::s8, data_type::u8)) + && !has_zero_dim_memory() + && set_default_formats(); + + if (!ok) return status::unimplemented; + + status_t status = jit_conf(); + if (status != status::success) return status; + set_default_alg_kind(alg_kind::convolution_winograd); + + init_scratchpad(); + + return status; + } + + jit_conv_conf_2x3_wino_t jcp_; + + protected: + status_t jit_conf(); + void init_scratchpad(); + + bool set_default_formats() { + using namespace format_tag; + return set_default_formats_common(nhwc, any, nhwc); + } + }; + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type acc_data_t; + typedef typename prec_traits::type dst_data_t; + + jit_avx512_core_u8s8s32x_wino_convolution_fwd_t(const pd_t *apd); + ~jit_avx512_core_u8s8s32x_wino_convolution_fwd_t(); + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + const float *adjust_oscales(const memory_tracking::grantor_t &scratchpad) + const; + void execute_forward(const exec_ctx_t &ctx) const; + void execute_forward_small_mb(const src_data_t *src, const wei_data_t *wei, + const char *bia, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; + void execute_forward_mbN(const src_data_t *src, const wei_data_t *wei, + const char *bia, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_core_u8s8s32x_wino_conv_fwd_ker_t *kernel_; + jit_avx512_core_u8s8s32x_wino_conv_src_trans_t *src_trans_; + jit_avx512_core_u8s8s32x_wino_conv_dst_trans_t *dst_trans_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.cpp new file mode 100644 index 000000000000..f4ec29ab00e3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.cpp @@ -0,0 +1,820 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_memory.hpp" + +#include "jit_uni_1x1_conv_utils.hpp" +#include "jit_avx512_core_x8s8s32x_1x1_conv_kernel.hpp" + +#define GET_OFF(field) offsetof(jit_1x1_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +bool jit_avx512_core_x8s8s32x_1x1_conv_kernel::maybe_eltwise(int position) +{ + using namespace primitive_kind; + const auto &p = attr_.post_ops_; + + if (position == 0) { + /* eltwise before sum */ + return p.contain(eltwise, 0); + } else if (position == 1) { + /* eltwise after sum */ + return p.contain(sum, 0) && p.contain(eltwise, 1); + } + + return false; +} + +void jit_avx512_core_x8s8s32x_1x1_conv_kernel::bcast_loop(int load_loop_blk) +{ + mov(aux1_reg_bcast_data, reg_bcast_data); + mov(aux_reg_bcast_data, reg_bcast_data); + + mov(aux_reg_output_data, reg_output_data); + mov(bcast_loop_iter, EVEX_compress_addr(rsp, bcast_loop_work_off)); + + Label bcast_loop; + Label bcast_loop_tail; + + cmp(bcast_loop_iter, jcp.ur); + jl(bcast_loop_tail, T_NEAR); + + L(bcast_loop); { + assert(jcp.bcast_block % jcp.ur == 0); + int num_substeps = jcp.bcast_block / jcp.ur; + assert(num_substeps > 0 && num_substeps < 10); + for (int i = 0; i < num_substeps; i++) { + reduce_loop(load_loop_blk, jcp.ur, i, false); + if (i < num_substeps - 1) { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_substep); + } + else { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_step + - (num_substeps - 1) * jcp.bcast_loop_bcast_substep); + int output_offset = jcp.bcast_loop_output_step + - (num_substeps - 1) * jcp.bcast_loop_output_substep; + + add(aux_reg_output_data, output_offset); + } + } + sub(bcast_loop_iter, jcp.bcast_block); + cmp(bcast_loop_iter, jcp.bcast_block); + jge(bcast_loop, T_NEAR); + } + + L(bcast_loop_tail); + if (jcp.ur_tail) { + Label bcast_loop_tail_out; + cmp(bcast_loop_iter, 0); + jz(bcast_loop_tail_out, T_NEAR); + reduce_loop(load_loop_blk, jcp.ur_tail, 0, true); + L(bcast_loop_tail_out); + } +} + +void jit_avx512_core_x8s8s32x_1x1_conv_kernel::cvt2ps(data_type_t type_in, + zmm_t zmm_in, const Xbyak::Operand &op, bool mask_flag) { + zmm_t zmm = mask_flag ? zmm_in | ktail_mask | T_z : zmm_in; + switch (type_in) { + case data_type::f32: + case data_type::s32: vmovups(zmm, op); break; + case data_type::s8: vpmovsxbd(zmm, op); break; + case data_type::u8: vpmovzxbd(zmm, op); break; + default: assert(!"unsupported data type"); + } + if (type_in != data_type::f32) + vcvtdq2ps(zmm_in, zmm_in); +} + +void jit_avx512_core_x8s8s32x_1x1_conv_kernel::reduce_loop(int load_loop_blk, + int ur, int substep, bool wraparound) +{ + auto vreg_load = [=](int i_load) { + return Zmm(ur * load_loop_blk + i_load); + }; + + auto vreg_accum = [=](int i_load, int i_ur) { + return Zmm(i_ur * load_loop_blk + i_load); + }; + + auto zmm_bias_alpha = [=]() { + return Zmm(ur * load_loop_blk); + }; + + auto xmm_bias_alpha = [=]() { + return Xmm(ur * load_loop_blk); + }; + auto bias_ptr = [=](int i_load) { + return EVEX_compress_addr(reg_bias_data, + jcp.typesize_bia * jcp.oc_block * i_load); + }; + + auto comp_ptr = [=](int i_load) { + return EVEX_compress_addr(reg_comp_data, + sizeof(int32_t) * jcp.oc_block * i_load); + }; + + auto scale_ptr = [=](int i_load) { + return EVEX_compress_addr(reg_ptr_scales, + jcp.is_oc_scale * (sizeof(float) * jcp.oc_block * i_load)); + }; + + auto bcast_ptr = [=](int i_reduce, int i_ur, bool bcast) { + assert(i_ur < jcp.ur); + assert(i_reduce <= jcp.reduce_loop_unroll); + assert(jcp.reduce_loop_unroll == jcp.reduce_block); + + int offt = (jcp.ic_without_padding * i_ur + i_reduce); + + return EVEX_compress_addr(aux_reg_bcast_data, jcp.typesize_in * offt, + bcast); + }; + + auto load_ptr = [=](int i_reduce, int i_load) { + int u0 = i_reduce % jcp.reduce_loop_unroll; + int u1 = i_reduce / jcp.reduce_loop_unroll; + + int offt = (i_load * jcp.reduce_dim + u0) * jcp.load_block; + + return EVEX_compress_addr(aux_reg_load_data, + u1 * jcp.reduce_loop_load_step + + jcp.typesize_in * offt); + }; + + auto output_ptr = [=](int i_load, int i_ur) { + return EVEX_compress_addr(aux_reg_output_data, + jcp.typesize_out * (jcp.oc_without_padding * i_ur + + i_load * jcp.load_block)); + }; + + auto init = [=]() { + for (int i_load = 0; i_load < load_loop_blk; ++i_load) + for (int i_ur = 0; i_ur < ur; ++i_ur) { + auto r = vreg_accum(i_load, i_ur); + vpxord(r, r, r); + } + if (jcp.signed_input) { + xor_(reg_scratch, reg_scratch); + Reg8 _t8 = reg_scratch.cvt8(); + mov(_t8, (int8_t)-128); + vpbroadcastb(zmm_shift, _t8); + } + }; + + auto store = [=](const bool mask_flag_in) { + const auto &p = attr_.post_ops_; + const int sum_idx = p.find(primitive_kind::sum); + const float *p_sum_scale = (sum_idx != -1) + ? &p.entry_[sum_idx].sum.scale + : nullptr; + mov(EVEX_compress_addr(rsp, reg_bcast_data_off), reg_bcast_data); + mov(reg_ptr_scales, EVEX_compress_addr(rsp, reg_ptr_sum_scale_off)); + if (p_sum_scale && *p_sum_scale != 1.f) { + mov(EVEX_compress_addr(rsp, reg_load_data_off), reg_load_data); + mov(reg_ptr_sum_scale, (size_t)p_sum_scale); + } + if (jcp.signed_input && jcp.ver != ver_vnni) { + mov(reg_scratch, float2int(jcp.wei_adj_scale)); + vmovq(xmm_bias_alpha(), reg_scratch); + vbroadcastss(zmm_bias_alpha(), xmm_bias_alpha()); + } + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + const bool mask_flag = mask_flag_in && i_load == load_loop_blk - 1; + auto zmm_bias = zmm_tmp; + auto zmm_comp = zmm_bcast; + if (jcp.with_bias) { + if (jcp.signed_input) + mov(reg_bias_data, + EVEX_compress_addr(rsp,reg_bias_data_off)); + cvt2ps(jcp.bia_dt, zmm_bias, bias_ptr(i_load), mask_flag); + if (jcp.signed_input && jcp.ver != ver_vnni) + vmulps(zmm_bias, zmm_bias, zmm_bias_alpha()); + } + if (jcp.signed_input) { + mov(reg_comp_data, EVEX_compress_addr(rsp, reg_comp_data_off)); + cvt2ps(data_type::s32, zmm_comp, comp_ptr(i_load), mask_flag); + } + + for (int i_ur = 0; i_ur < ur; ++i_ur) { + auto r = vreg_accum(i_load, i_ur); + vcvtdq2ps(r, r); + if (jcp.signed_input) + vaddps(r, r, zmm_comp); + if (jcp.with_bias) + vaddps(r, r, zmm_bias); + + zmm_t mask_zmm = mask_flag ? r | ktail_mask | T_z : r; + vmulps(mask_zmm, r, scale_ptr(i_load)); + } + } + + if (maybe_eltwise(0)) + eltwise_injector_->compute_vector_range(0, ur * load_loop_blk); + + if (p_sum_scale) { // post_op: sum + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + const bool mask_flag = mask_flag_in && + i_load == load_loop_blk - 1; + for (int i_ur = 0; i_ur < ur; ++i_ur) { + vpxord(zmm_zero, zmm_zero, zmm_zero); + auto zmm_prev_dst = zmm_zero; + + auto r = vreg_accum(i_load, i_ur); + cvt2ps(jcp.dst_dt, zmm_prev_dst, output_ptr(i_load, i_ur), + mask_flag); + + if (*p_sum_scale == 1.f) + vaddps(r, zmm_prev_dst); + else + vfmadd231ps(r, zmm_prev_dst, zword_b[reg_ptr_sum_scale]); + } + } + } + + if (maybe_eltwise(1)) + eltwise_injector_->compute_vector_range(0, ur * load_loop_blk); + + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + const bool mask_flag = mask_flag_in && + i_load == load_loop_blk - 1; + for (int i_ur = 0; i_ur < ur; ++i_ur) { + auto r = vreg_accum(i_load, i_ur); + if (jcp.dst_dt == data_type::u8) { + vpxord(zmm_zero, zmm_zero, zmm_zero); + vmaxps(r, zmm_zero, r); + } + if (jcp.dst_dt != data_type::f32) + vcvtps2dq(r, r); + } + for (int i_ur = 0; i_ur < ur; ++i_ur) { + auto r = vreg_accum(i_load, i_ur); + zmm_t r_zmm = mask_flag ? r | ktail_mask : r; + + switch (jcp.dst_dt) { + case data_type::f32: + case data_type::s32: + vmovups(output_ptr(i_load, i_ur), r_zmm); break; + case data_type::s8: + vpmovsdb(output_ptr(i_load, i_ur), r_zmm); break; + case data_type::u8: + vpmovusdb(output_ptr(i_load, i_ur), r_zmm); break; + default: assert(!"unknown dst_dt"); + } + } + } + mov(reg_bcast_data, EVEX_compress_addr(rsp, reg_bcast_data_off)); + if (p_sum_scale && *p_sum_scale != 1.f) + mov(reg_load_data, EVEX_compress_addr(rsp, reg_load_data_off)); + }; + + auto compute = [=](Zmm vreg_acc, Zmm vreg_wei, Zmm vreg_src) { + if (jcp.ver == ver_vnni) { + vpdpbusd(vreg_acc, vreg_src, vreg_wei); + } else { + vpmaddubsw(zmm_tmp, vreg_src, vreg_wei); + vpmaddwd(zmm_tmp, zmm_tmp, zmm_one); + vpaddd(vreg_acc, vreg_acc, zmm_tmp); + } + }; + + auto fma_block = [=](bool last_block) { + int reduce_step = 4; + int tail_size = jcp.ic_without_padding % reduce_step; + int loop_unroll = last_block && jcp.ic != jcp.ic_without_padding + ? rnd_up(jcp.ic_without_padding % jcp.ic_block, reduce_step) + : jcp.reduce_loop_unroll; + for (int i_reduce = 0; i_reduce < loop_unroll; + i_reduce += reduce_step) { + for (int i_load = 0; i_load < load_loop_blk; ++i_load) + vmovups(vreg_load(i_load), load_ptr(i_reduce, i_load)); + for (int i_ur = 0; i_ur < ur; ++i_ur) { + if (last_block && tail_size != 0 + && i_reduce == loop_unroll - reduce_step) { + Xmm xmm_bcast = Xmm(zmm_bcast.getIdx()); + for (int r = 0; r < tail_size; ++r) + vpinsrb(xmm_bcast, xmm_bcast, ptr[aux_reg_bcast_data + + jcp.ic_without_padding * i_ur + i_reduce + r], r); + vpbroadcastd(zmm_bcast, xmm_bcast); + } else { + vpbroadcastd(zmm_bcast, bcast_ptr(i_reduce, i_ur, false)); + } + if (jcp.signed_input) + vpsubb(zmm_bcast, zmm_bcast, zmm_shift); + for (int i_load = 0; i_load < load_loop_blk; ++i_load) { + compute(vreg_accum(i_load, i_ur), + vreg_load(i_load), zmm_bcast); + } + } + } + }; + + Label reduce_loop; + Label reduce_loop_tail; + + mov(aux_reg_load_data, reg_load_data); + + mov(aux_reg_bcast_data, aux1_reg_bcast_data); + init(); + + mov(reduce_loop_iter, reg_reduce_loop_work); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jle(reduce_loop_tail, T_NEAR); + + L(reduce_loop); { + fma_block(false); + add(aux_reg_bcast_data, jcp.reduce_loop_bcast_step); + add(aux_reg_load_data, jcp.reduce_loop_load_step); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jg(reduce_loop, T_NEAR); + } + + L(reduce_loop_tail); + if (jcp.ic != jcp.ic_without_padding) { + fma_block(true); + } else { + fma_block(false); + } + + if (jcp.oc_without_padding != jcp.oc) { + Label end_store, common_store; + mov(EVEX_compress_addr(rsp, reg_bcast_data_off), reg_bcast_data); + + /*Check if it is the last load_loop_blk*/ + sub(reg_load_loop_work, load_loop_blk * jcp.load_loop_iter_step); + cmp(reg_load_loop_work, 0); + jg(common_store, T_NEAR); + + /*Check if it is the last ocb*/ + test(reg_reduce_pos_flag, FLAG_OC_LAST); + jz(common_store, T_NEAR); + + store(true); + jmp(end_store, T_NEAR); + + L(common_store); + store(false); + + L(end_store); + + add(reg_load_loop_work, load_loop_blk * jcp.load_loop_iter_step); + } else { + store(false); + } +} + +void jit_avx512_core_x8s8s32x_1x1_conv_kernel::generate() +{ + preamble(); + + xor_(reg_scratch, reg_scratch); + Reg16 _t = reg_scratch.cvt16(); + mov(_t, 0x1); + vpbroadcastw(zmm_one, _t); + + sub(rsp, stack_space_needed); + + if (jcp.oc_without_padding != jcp.oc) { + int tail_size = jcp.oc_without_padding % jcp.oc_block; + int mask = (1 << tail_size) - 1; + Reg32 regw_tmp = reg_last_load.cvt32(); + mov(regw_tmp, mask); + kmovw(ktail_mask, regw_tmp); + } + + if (jcp.with_bias) + mov(reg_bias_data, ptr[param1 + GET_OFF(bias_data)]); + if (jcp.signed_input) { + mov(EVEX_compress_addr(rsp, reg_bias_data_off), reg_bias_data); + mov(reg_comp_data, ptr[param1 + GET_OFF(compensation)]); + mov(EVEX_compress_addr(rsp, reg_comp_data_off), reg_comp_data); + } + mov(reg_ptr_scales, ptr[param1 + GET_OFF(scales)]); + mov(EVEX_compress_addr(rsp, reg_ptr_sum_scale_off), reg_ptr_scales); + mov(reg_bcast_data, ptr[param1 + GET_OFF(bcast_data)]); + mov(reg_load_data, ptr[param1 + GET_OFF(load_data)]); + mov(reg_output_data, ptr[param1 + GET_OFF(output_data)]); + + mov(reg_load_loop_work, ptr[param1 + GET_OFF(load_dim)]); + mov(reg_bcast_loop_work, ptr[param1 + GET_OFF(bcast_dim)]); + mov(EVEX_compress_addr(rsp, bcast_loop_work_off), reg_bcast_loop_work); + mov(reg_reduce_loop_work, ptr[param1 + GET_OFF(reduce_dim)]); + mov(reg_reduce_pos_flag, ptr[param1 + GET_OFF(first_last_flag)]); + + + auto load_loop_body = [=](int load_loop_blk) { + bcast_loop(load_loop_blk); + add(reg_load_data, load_loop_blk * jcp.load_loop_load_step); + if (jcp.with_bias) { + if (jcp.signed_input) + mov(reg_bias_data, EVEX_compress_addr(rsp, reg_bias_data_off)); + add(reg_bias_data, + load_loop_blk * jcp.load_block * jcp.typesize_bia); + if (jcp.signed_input) + mov(EVEX_compress_addr(rsp, reg_bias_data_off), reg_bias_data); + } + if (jcp.signed_input) { + mov(reg_comp_data, EVEX_compress_addr(rsp, reg_comp_data_off)); + add(reg_comp_data, + load_loop_blk * jcp.load_block * sizeof(int32_t)); + mov(EVEX_compress_addr(rsp, reg_comp_data_off), reg_comp_data); + } + mov(EVEX_compress_addr(rsp, reg_bcast_data_off), reg_bcast_data); + mov(reg_ptr_scales, EVEX_compress_addr(rsp, reg_ptr_sum_scale_off)); + add(reg_ptr_scales, + jcp.is_oc_scale * load_loop_blk * jcp.load_block * sizeof(float)); + mov(EVEX_compress_addr(rsp, reg_ptr_sum_scale_off), reg_ptr_scales); + mov(reg_bcast_data, EVEX_compress_addr(rsp, reg_bcast_data_off)); + add(reg_output_data, + load_loop_blk * jcp.load_block * jcp.typesize_out); + sub(reg_load_loop_work, load_loop_blk * jcp.load_loop_iter_step); + }; + + const int simd_w = 16; + + Label load_loop_blk[7]; + + static const int ur_cases_fma_expl_bcast[] = { 2, 5, 6, 9, 14, 32 }; + const int size_ur_cases_fma = sizeof(ur_cases_fma_expl_bcast); + const int *ur_cases_fma = ur_cases_fma_expl_bcast; + const int *ur_cases = ur_cases_fma; + const int num_ur_cases = (size_ur_cases_fma) / sizeof(*ur_cases); + + for (int ur_idx = num_ur_cases - 1; ur_idx > 0; ur_idx--) { + int label_idx = num_ur_cases - ur_idx - 1; + if (jcp.ur <= ur_cases[ur_idx]) { + cmp(reg_load_loop_work, simd_w * (label_idx + 1)); + jle(load_loop_blk[label_idx], T_NEAR); + } + } + + for (int ur_idx = 0; ur_idx < num_ur_cases; ur_idx++) { + if (jcp.ur <= ur_cases[ur_idx]) { + int label_idx = num_ur_cases - ur_idx - 1; + L(load_loop_blk[label_idx]); + { + if (label_idx == 0) { + cmp(reg_load_loop_work, 0); + je(load_loop_blk[num_ur_cases], T_NEAR); + } + + for (int _i = 1; _i <= label_idx + 1; _i++) { + prefetcht0(ptr [ reg_load_data + _i * jcp.ic * jcp.oc_block ]); + prefetcht1(ptr [ reg_output_data + _i * jcp.oc_block ]); + } + + load_loop_body(label_idx + 1); + if (label_idx - 1 > 0) { + cmp(reg_load_loop_work, 2 * label_idx * simd_w); + je(load_loop_blk[label_idx - 1], T_NEAR); + } + cmp(reg_load_loop_work, (label_idx + 1) * simd_w); + jge(load_loop_blk[label_idx]); + } + for (int idx = label_idx - 1; idx > 0; --idx) { + cmp(reg_load_loop_work, simd_w * (idx + 1)); + je(load_loop_blk[idx], T_NEAR); + } + if (ur_idx < num_ur_cases - 2) { + cmp(reg_load_loop_work, simd_w); + jle(load_loop_blk[0], T_NEAR); + } + } + } + L(load_loop_blk[num_ur_cases]); + + add(rsp, stack_space_needed); + + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_avx512_core_x8s8s32x_1x1_conv_kernel::post_ops_ok( + jit_1x1_conv_conf_t &jcp, const primitive_attr_t &attr) { + using namespace primitive_kind; + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + + switch (p.len_) { + case 0: return true; + case 1: return is_eltwise(0) || p.contain(sum, 0); + case 2: return (p.contain(sum, 0) && is_eltwise(1)) + || (p.contain(sum, 1) && is_eltwise(0)); + default: return false; + } + + return false; +} + +status_t jit_avx512_core_x8s8s32x_1x1_conv_kernel::init_conf( + jit_1x1_conv_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, const memory_desc_wrapper &bias_d, + const primitive_attr_t &attr, int nthreads, bool reduce_src) { + if (!mayiuse(avx512_core)) return status::unimplemented; + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + if (!one_of(src_d.data_type(), data_type::u8, data_type::s8) + || weights_d.data_type() != data_type::s8 + || !one_of(dst_d.data_type(), + data_type::f32, data_type::s32, data_type::s8, data_type::u8)) + return status::unimplemented; + jcp.ver = ver_avx512_core; + if (mayiuse(avx512_core_vnni)) + jcp.ver = ver_vnni; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ic_without_padding = jcp.ic; + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = dst_d.dims()[2]; + jcp.ow = dst_d.dims()[3]; + jcp.kh = weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + 3]; + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + jcp.signed_input = (src_d.data_type() == data_type::s8) ? true : false; + + jcp.os = jcp.oh * jcp.ow; + jcp.is = jcp.ih * jcp.iw; + jcp.tr_is = rnd_up(jcp.is, 4); + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + format_tag_t dat_tag = format_tag::nhwc; + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.ngroups == 1 + && jcp.src_tag == dat_tag + && jcp.dst_tag == dat_tag; + if (!args_ok) return status::unimplemented; + + const int simd_w = 16; + + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.ic, simd_w); + + args_ok = true + && jcp.oc % simd_w == 0 && jcp.ic % simd_w == 0 + && jcp.t_pad == 0 && jcp.l_pad == 0 + && jcp.stride_w == 1 && jcp.stride_h == 1 // TODO: support some strides + && jcp.kh == 1 && jcp.kw == 1; + if (!args_ok) return status::unimplemented; + + jcp.bia_dt = jcp.with_bias ? cd.bias_desc.data_type : data_type::undef; + jcp.dst_dt = cd.dst_desc.data_type; + + jcp.ic_block = jcp.oc_block = simd_w; + + jcp.typesize_in = types::data_type_size(src_d.data_type()); + jcp.typesize_out = types::data_type_size(dst_d.data_type()); + jcp.typesize_bia = jcp.with_bias + ? types::data_type_size(bias_d.data_type()) + : 0; + + const int SMALL_SPATIAL = 7 * 7; + const int BIG_REDUCE_DIM = 1024; + + int load_blocking = 0; + int load_blocking_max = 0; + int bcast_blocking = 0; + int bcast_blocking_max = 0; + int reduce_blocking = 0; + int reduce_blocking_max = 0; + jcp.load_grp_count = 1; + jcp.use_vmovntps = false; + + const int L2_size = get_cache_size(2, true) / sizeof(jcp.typesize_in); + const int L2_capacity = (L2_size * 3) / 4; + + int size_treshold = 28; + int max_regs = 0; + int min_regs = 6; + if (jcp.ver == ver_vnni) + max_regs = ((jcp.oh > size_treshold && jcp.ow > size_treshold) + && (jcp.oc < 128 || jcp.ic < 128)) ? min_regs : 9; + else + max_regs = 8; + jcp.expl_bcast = true; + + if (jcp.mb == 1 && jcp.ic > 128 + && (jcp.oh <= size_treshold && jcp.ow <= size_treshold)) { + if (jcp.os <= SMALL_SPATIAL && jcp.oc * jcp.ic < L2_size) + max_regs = min_regs; // mobilenet_v2 performance improvement + jcp.ur = nstl::min(max_regs, jcp.os); + } else { + const int spatial = jcp.oh; + jcp.ur = 1; + for (int ur_w = max_regs; ur_w >= min_regs; ur_w--) { + if ((spatial >= size_treshold && spatial % ur_w == 0) + || (spatial < size_treshold && jcp.os % ur_w == 0)) { + jcp.ur = ur_w; + break; + } + } + if (jcp.ur == 1) { + jcp.ur = nstl::min(max_regs, jcp.os); + int os_tail = jcp.os % max_regs; + for (int i = max_regs; i >= min_regs; i--) { + int i_tail = jcp.os % i; + if (i_tail > os_tail || i_tail == 0) { + jcp.ur = i; + os_tail = i_tail; + if (i_tail == 0) + break; + } + } + } + } + + jcp.reduce_dim = jcp.ic; + jcp.reduce_block = jcp.ic_block; + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.is; + + jcp.bcast_block = jcp.ur; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.typesize_in; + + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.load_block * jcp.typesize_in; + + jcp.bcast_loop_output_step = jcp.ur * jcp.oc_without_padding * jcp.typesize_out; + jcp.bcast_loop_output_substep = -1; // unused + jcp.bcast_loop_bcast_step = jcp.ur * jcp.ic_without_padding * jcp.typesize_in; + jcp.bcast_loop_bcast_substep = -1; // unused + + jcp.load_loop_load_step + = jcp.reduce_dim * jcp.load_block * jcp.typesize_in; + + jcp.load_loop_iter_step = jcp.load_block; + + jcp.loop_order = reduce_src ? loop_blr : loop_lbr; + + int nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + int nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + + reduce_blocking = nb_reduce; + if (jcp.bcast_dim <= SMALL_SPATIAL && jcp.reduce_dim >= BIG_REDUCE_DIM) + reduce_blocking = 64; + else if (jcp.bcast_dim > SMALL_SPATIAL && jcp.reduce_dim >= BIG_REDUCE_DIM) + reduce_blocking = 16; + reduce_blocking = best_divider(nb_reduce, 1, reduce_blocking, true); + reduce_blocking *= jcp.reduce_block; + + bool cmp_reduce = reduce_blocking <= jcp.reduce_dim; + if (cmp_reduce) + jcp.loop_order = reduce_src ? loop_rbl : loop_rlb; + load_blocking = jcp.load_dim; + + jcp.load_grp_count = div_up(nthreads, jcp.mb * jcp.ngroups * nb_bcast); + jcp.load_grp_count = best_divider( + nthreads, jcp.load_grp_count, 2 * jcp.load_grp_count, false); + + if (jcp.bcast_dim <= SMALL_SPATIAL && jcp.load_dim * jcp.reduce_dim >= L2_size) { + jcp.load_grp_count = nstl::max(jcp.load_grp_count, 4); + } else if (jcp.bcast_dim <= SMALL_SPATIAL && jcp.mb <= nthreads + && jcp.load_dim > 512 && jcp.load_dim / jcp.reduce_dim >= 4) { + jcp.load_grp_count = nstl::max(jcp.load_grp_count, 2); // + load_blocking = jcp.load_block; + } + + bcast_blocking = div_up(jcp.mb * jcp.ngroups * nb_bcast, + div_up(nthreads, jcp.load_grp_count)) * jcp.bcast_block; + bcast_blocking = nstl::min(jcp.bcast_dim, bcast_blocking); + bcast_blocking = rnd_up(bcast_blocking, jcp.bcast_block); + + int space_for_bcast + = (L2_capacity - /* kernel_size - */ + 2 * jcp.load_block * reduce_blocking + - jcp.ur * reduce_blocking - 3 * 1024); + if (jcp.reduce_dim * jcp.bcast_dim > L2_capacity) + space_for_bcast /= 2; + + int bcast_in_cache + = nstl::max(jcp.bcast_block, space_for_bcast / reduce_blocking); + bcast_blocking = nstl::min( + bcast_blocking, rnd_dn(bcast_in_cache, jcp.bcast_block)); + + load_blocking_max = load_blocking; + bcast_blocking_max = bcast_blocking * 3 / 2; + reduce_blocking_max = reduce_blocking; + + assert(load_blocking); + assert(load_blocking_max); + assert(bcast_blocking); + assert(bcast_blocking_max); + assert(reduce_blocking); + assert(reduce_blocking_max); + assert(load_blocking % jcp.load_block == 0); + assert(reduce_blocking % jcp.reduce_block == 0); + assert(load_blocking_max % jcp.load_block == 0); + assert(reduce_blocking_max % jcp.reduce_block == 0); + + assert(jcp.reduce_loop_unroll % 4 == 0); + assert(jcp.reduce_dim % jcp.reduce_loop_unroll == 0); + + assert(jcp.bcast_block % jcp.ur == 0); + assert(jcp.reduce_dim % jcp.reduce_block == 0); + + jcp.ur_tail = jcp.bcast_dim % jcp.ur; + + jcp.nb_bcast_blocking = bcast_blocking / jcp.bcast_block; + jcp.nb_bcast_blocking_max = bcast_blocking_max / jcp.bcast_block; + jcp.nb_load_blocking = load_blocking / jcp.load_block; + jcp.nb_load_blocking_max = load_blocking_max / jcp.load_block; + jcp.nb_reduce_blocking = reduce_blocking / jcp.reduce_block; + jcp.nb_reduce_blocking_max = reduce_blocking_max / jcp.reduce_block; + + jcp.nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + jcp.nb_load = div_up(jcp.load_dim, jcp.load_block); + jcp.nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + + // miniumum size of load dim chunk for work distribution within threads + jcp.nb_load_chunk = 1; + // peformance improvements for googlenet_v3, mb=1; + // TODO: generalize this condition and rewrite it in appropriate manner + if (jcp.mb == 1 && jcp.nb_load % 4 == 0 && jcp.ic / jcp.oc >= 4 + && jcp.ic * jcp.oc <= L2_size) { + jcp.nb_load_chunk = 4; + jcp.load_grp_count = nstl::max(jcp.nb_load / 4, jcp.load_grp_count); + } + + const auto &oscales = attr.output_scales_; + jcp.is_oc_scale = oscales.mask_ == 1 << 1; + assert(IMPLICATION(!jcp.is_oc_scale, oscales.mask_ == 0)); + + jcp.wei_adj_scale = + (weights_d.extra().flags | memory_extra_flags::scale_adjust) + ? weights_d.extra().scale_adjust : 1.f; + + return status::success; +} + +void jit_avx512_core_x8s8s32x_1x1_conv_kernel::init_scratchpad( + memory_tracking::registrar_t &scratchpad, + const jit_1x1_conv_conf_t &jcp, const primitive_attr_t &attr) { + using namespace mkldnn::impl::memory_tracking::names; + + if (jcp.signed_input && jcp.ver != ver_vnni) { + dim_t count = nstl::max(attr.output_scales_.count_, 16); + scratchpad.book(key_conv_adjusted_scales, sizeof(float) * count); + } +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.hpp new file mode 100644 index 000000000000..22e9732a1f3b --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_conv_kernel.hpp @@ -0,0 +1,131 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_AVX512_CORE_X8S8S32X_1X1_CONV_KERNEL_HPP +#define JIT_AVX512_CORE_X8S8S32X_1X1_CONV_KERNEL_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_avx512_core_x8s8s32x_1x1_conv_kernel: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_x8s8s32x_1x1_conv_fwd_ker_t) + jit_avx512_core_x8s8s32x_1x1_conv_kernel(jit_1x1_conv_conf_t ajcp, + const primitive_attr_t &attr) : jcp(ajcp), attr_(attr), + eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32( + this, jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_1x1_conv_call_s *)) this->getCode(); + } + + ~jit_avx512_core_x8s8s32x_1x1_conv_kernel() { + delete eltwise_injector_; + } + + static bool post_ops_ok(jit_1x1_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, + const memory_desc_wrapper &bias_d, + const primitive_attr_t &attr, + int nthreads, bool reduce_src); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_1x1_conv_conf_t &jcp, const primitive_attr_t &attr); + + bool maybe_eltwise(int position); + + jit_1x1_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_1x1_conv_call_s *); + + private: + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + using reg64_t = const Xbyak::Reg64; + using zmm_t = const Xbyak::Zmm; + using mask_t = const Xbyak::Opmask; + + reg64_t reg_bcast_data = r8; + reg64_t reg_ptr_scales = r8; + reg64_t reg_output_data = r9; + reg64_t reg_load_data = r10; + reg64_t reg_ptr_sum_scale = r10; + reg64_t reg_reduce_loop_work = r11; + reg64_t reg_bias_data = r12; + reg64_t reg_comp_data = r12; + reg64_t reg_scratch = r13; + reg64_t aux_reg_bcast_data = r14; + reg64_t aux_reg_load_data = r15; + reg64_t imm_addr64 = r15; + reg64_t reg_reduce_pos_flag = rax; + reg64_t aux1_reg_bcast_data = rbx; + reg64_t reg_bcast_loop_work = rbx; + reg64_t bcast_loop_iter = rdx; // Note: Fix me + reg64_t reg_load_loop_work = rsi; + reg64_t aux_reg_output_data = abi_not_param1; + reg64_t reduce_loop_iter = abi_param1; + + reg64_t reg_last_load = r8; + mask_t ktail_mask = k6; + + mask_t vmask = k7; + + Xbyak::Zmm zmm_tmp = Xbyak::Zmm(28); + Xbyak::Zmm zmm_one = Xbyak::Zmm(29); + Xbyak::Zmm zmm_zero = Xbyak::Zmm(30); + Xbyak::Zmm zmm_bcast = Xbyak::Zmm(31); + Xbyak::Zmm zmm_shift = Xbyak::Zmm(30); + + Xbyak::Zmm zmm_bias_alpha = Xbyak::Zmm(31); + Xbyak::Xmm xmm_bias_alpha = Xbyak::Xmm(31); + + int bcast_loop_work_off = 0; + int reg_bias_data_off = 8; + int reg_bcast_data_off = 16; + int reg_load_data_off = 24; + int reg_ptr_sum_scale_off = 32; + int reg_comp_data_off = 40; + int stack_space_needed = 48; + + void bcast_loop(int load_loop_blk); + void reduce_loop(int load_loop_blk, int ur, int substep, bool wraparound); + + void generate(); + void cvt2ps(data_type_t type_in, zmm_t zmm_in, const Xbyak::Operand &op, + bool mask_flag); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.cpp new file mode 100644 index 000000000000..0bf09fc6776e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.cpp @@ -0,0 +1,292 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +#include "jit_avx512_core_x8s8s32x_1x1_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +namespace { +template +void balance2D(U nthr, U ithr, T ny, T &ny_start, T &ny_end, + T nx, T &nx_start, T &nx_end, T nx_divider) +{ + const T grp_size = utils::div_up(nthr, nx_divider); + const T grp_count = utils::div_up(nthr, grp_size); + + T grp = ithr / grp_size; + T grp_ithr = ithr % grp_size; + T grp_nthr = grp_size; + T first_grps = nthr % grp_count; + if (first_grps > 0 && grp >= first_grps) { + ithr -= first_grps * grp_size; + grp_nthr--; + grp = ithr / grp_nthr + first_grps; + grp_ithr = ithr % grp_nthr; + } + balance211(nx, grp_count, grp, nx_start, nx_end); + balance211(ny, grp_nthr, grp_ithr, ny_start, ny_end); +} +} + +/* convolution forward */ +template +void jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const +{ + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + auto scratchpad = this->scratchpad(ctx); + + if (pd()->jcp_.signed_input && pd()->jcp_.ver != ver_vnni) { + auto local_scales = scratchpad.template get( + key_conv_adjusted_scales); + auto scales = pd()->attr()->output_scales_.scales_; + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / pd()->jcp_.wei_adj_scale; + if (count == 1) { + utils::array_set(local_scales, scales[0] * factor, 16); + } else { + for (size_t c = 0; c < count; c++) + local_scales[c] = scales[c] * factor; + } + } + + parallel(kernel_->jcp.nthr, [&](const int ithr, const int nthr) { + execute_forward_thr(ithr, nthr, src, weights, bias, dst, scratchpad); + }); +} + +template +void jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t +::execute_forward_thr(const int ithr, const int nthr, const src_data_t *src, + const wei_data_t *weights, const char *bias, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const { + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const size_t bia_dt_size = pd()->with_bias() + ? types::data_type_size(pd()->desc()->bias_desc.data_type) : 0; + + const auto &jcp = kernel_->jcp; + auto rtus_space = scratchpad.get(key_conv_rtus_space); + auto local_scales = scratchpad.get(key_conv_adjusted_scales); + + const int work_amount = jcp.mb * jcp.ngroups * jcp.nb_bcast; + + const int stride_h = pd()->desc()->strides[0]; + const int stride_w = pd()->desc()->strides[1]; + const int pad_t = pd()->desc()->padding[0][0]; + const int pad_l = pd()->desc()->padding[0][1]; + + const auto &oscales = pd()->attr()->output_scales_; + + int offset = jcp.ngroups * (jcp.oc / jcp.oc_block) * (jcp.ic / jcp.ic_block) + * jcp.oc_block * jcp.ic_block; + wei_data_t *w = const_cast(weights); + int32_t* compensation = (jcp.signed_input) + ? reinterpret_cast(w + offset) : 0; + + auto step = [](int default_step, int remaining, int tail_step) { + assert(default_step <= tail_step); + return remaining < tail_step ? remaining : default_step; + }; + + auto p = jit_1x1_conv_call_s(); + + auto rp = rtus_driver_t::call_params_t(); + const int nb_oc = jcp.nb_load; + const int os_block = jcp.bcast_block; + + int bcast_start{0}, bcast_end{0}, ocb_start{0}, ocb_end{0}; + balance2D(nthr, ithr, work_amount, bcast_start, bcast_end, + jcp.nb_load / jcp.nb_load_chunk, ocb_start, ocb_end, + jcp.load_grp_count); + if (jcp.nb_load_chunk > 1) { + ocb_start *= jcp.nb_load_chunk; + ocb_end *= jcp.nb_load_chunk; + } + + auto init_bcast = [&](int iwork, int &n, int &g, int &bcast_step, + int &oh, int &ow, int &ih, int &iw) + { + int osb{0}; + nd_iterator_init(iwork, n, jcp.mb, g, jcp.ngroups, osb, + jcp.nb_bcast); + bcast_step = step(jcp.nb_bcast_blocking, jcp.nb_bcast - osb, + jcp.nb_bcast_blocking_max); + bcast_step = nstl::min(bcast_step, bcast_end - iwork); + + const int os = osb * os_block; + oh = os / jcp.ow; + ow = os % jcp.ow; + + ih = nstl::max(oh * stride_h - pad_t, 0); + iw = nstl::max(ow * stride_w - pad_l, 0); + rp.iw_start = iw; + + p.bcast_dim = this_block_size(os, jcp.os, + bcast_step * os_block); + rp.os = p.bcast_dim; + }; + + auto init_load = [&](int ocb, int &load_step) + { + load_step = step(jcp.nb_load_blocking, ocb_end - ocb, + jcp.nb_load_blocking_max); + p.load_dim = this_block_size(ocb * jcp.oc_block, + ocb_end * jcp.oc_block, load_step * jcp.oc_block); + + if (ocb + load_step >= nb_oc) + p.first_last_flag |= FLAG_OC_LAST; + else + p.first_last_flag &= ~FLAG_OC_LAST; + + }; + + auto init_reduce = [&]() + { + p.reduce_dim = this_block_size(0, jcp.ic, jcp.ic); + rp.icb = p.reduce_dim / jcp.reduce_block; + }; + + auto inner_ker = [&](int ocb, int n, int g, int oh, int ow, + int ih, int iw) + { + const int icb = 0; // Start from the first IC block + const int _ocb = g * nb_oc + ocb; + const int _icb = g; + + const size_t dst_off = dst_d.blk_off(n, _ocb * jcp.oc_block, oh, ow); + + p.output_data = &dst[dst_off]; + p.load_data = &weights[pd()->with_groups() + ? weights_d.blk_off(g, ocb, icb) + : weights_d.blk_off(ocb, icb)]; + p.bias_data = &bias[_ocb * jcp.oc_block * bia_dt_size]; + p.compensation = (jcp.signed_input) + ? &compensation[_ocb * jcp.oc_block] : 0; + p.scales = (jcp.signed_input && jcp.ver != ver_vnni) + ? &local_scales[jcp.is_oc_scale * _ocb * jcp.oc_block] + : &oscales.scales_[jcp.is_oc_scale * _ocb * jcp.oc_block]; + if (pd()->rtus_.reduce_src_) { + rp.ws = rtus_space + ithr * pd()->rtus_.space_per_thread_ + + _icb * jcp.is * jcp.ic_block; + if (ocb == ocb_start) { + rp.src = src + src_d.blk_off(n, _icb * jcp.ic_block, ih, iw); + rtus_driver_->ker_(&rp); + } + p.bcast_data = rp.ws; + } else + p.bcast_data = src + src_d.blk_off(n, _icb * jcp.ic_block, ih, iw); + + kernel_->jit_ker(&p); + }; + + if (jcp.loop_order == loop_rlb) { + init_reduce(); + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + inner_ker(ocb, n, g, oh, ow, ih, iw); + iwork += bcast_step; + } + ocb += load_step; + } + } else if (jcp.loop_order == loop_lbr) { + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + init_reduce(); + inner_ker(ocb, n, g, oh, ow, ih, iw); + iwork += bcast_step; + } + ocb += load_step; + } + } else if (jcp.loop_order == loop_rbl) { + init_reduce(); + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + inner_ker(ocb, n, g, oh, ow, ih, iw); + ocb += load_step; + } + iwork += bcast_step; + } + } else if (jcp.loop_order == loop_blr) { + int iwork = bcast_start; + while (iwork < bcast_end) { + int n, g, bcast_step, oh, ow, ih, iw; + init_bcast(iwork, n, g, bcast_step, oh, ow, ih, iw); + int ocb = ocb_start; + while (ocb < ocb_end) { + int load_step; + init_load(ocb, load_step); + init_reduce(); + inner_ker(ocb, n, g, oh, ow, ih, iw); + ocb += load_step; + } + iwork += bcast_step; + } + } else { + assert(!"unsupported loop order"); + } +} + +using namespace data_type; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; +template struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.hpp new file mode 100644 index 000000000000..ad9027ac174d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_convolution.hpp @@ -0,0 +1,159 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_X8S8S32X_1X1_CONVOLUTION_HPP +#define CPU_JIT_AVX512_CORE_X8S8S32X_1X1_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_avx512_core_x8s8s32x_1x1_conv_kernel.hpp" +#include "jit_uni_1x1_conv_utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t : public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_(), rtus_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_int8_1x1:", avx512_core, ""), + jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t< + src_type, dst_type>); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, data_type::s8, data_type::undef, + dst_type, data_type::s32) + && IMPLICATION(with_bias(), utils::one_of( + desc()->bias_desc.data_type, data_type::f32, + data_type::s32, data_type::s8, data_type::u8)) + && !has_zero_dim_memory() + && set_default_formats_common(dat_tag(), format_tag::any, + dat_tag()) + && set_or_check_wei_format(); + if (!ok) return status::unimplemented; + + const convolution_desc_t *conv_d = desc(); + const memory_desc_t *src_d = src_md(); + rtus_prepare(this, conv_d, src_d, dst_md()); + + status_t status = jit_avx512_core_x8s8s32x_1x1_conv_kernel:: + init_conf(jcp_, *conv_d, *src_d, *weights_md(), *dst_md(), + *weights_md(1), *attr(), mkldnn_get_max_threads(), + rtus_.reduce_src_); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_core_x8s8s32x_1x1_conv_kernel::init_scratchpad( + scratchpad, jcp_, *attr()); + + rtus_prepare_space_info(this, scratchpad); + + return status::success; + } + + jit_1x1_conv_conf_t jcp_; + reduce_to_unit_stride_t rtus_; + + protected: + format_tag_t dat_tag() const { return format_tag::nhwc; } + + bool set_or_check_wei_format() { + using namespace format_tag; + + const bool is_src_s8 = src_md_.data_type == data_type::s8; + format_tag_t wei_tag = with_groups() ? gOIhw4i16o4i : OIhw4i16o4i; + + memory_desc_t want_wei_md = weights_md_; + memory_desc_init_by_tag(want_wei_md, wei_tag); + if (is_src_s8) { + want_wei_md.extra.flags = 0 + | memory_extra_flags::compensation_conv_s8s8 + | memory_extra_flags::scale_adjust; + want_wei_md.extra.compensation_mask = (1 << 0) + + (with_groups() ? (1 << 1) : 0); + want_wei_md.extra.scale_adjust = + mayiuse(avx512_core_vnni) ? 1.f : 0.5f; + } + + if (weights_md_.format_kind == format_kind::any) { + weights_md_ = want_wei_md; + return true; + } + + return weights_md_ == want_wei_md; + } + }; + + template + friend void init_rtus_driver(conv_t *self); + + jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr), rtus_driver_(nullptr) + { + kernel_ = new jit_avx512_core_x8s8s32x_1x1_conv_kernel(pd()->jcp_, + *pd()->attr()); + init_rtus_driver(this); + } + + ~jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t() { + delete kernel_; + delete rtus_driver_; + } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + + private: + void execute_forward(const exec_ctx_t &ctx) const; + void execute_forward_thr(const int ithr, const int nthr, + const src_data_t *src, const wei_data_t *weights, + const char *bias, dst_data_t *dst, + const memory_tracking::grantor_t &scratchpad) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_core_x8s8s32x_1x1_conv_kernel *kernel_; + rtus_driver_t *rtus_driver_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_deconvolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_deconvolution.hpp new file mode 100644 index 000000000000..e89d0683028e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_1x1_deconvolution.hpp @@ -0,0 +1,140 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_X8S8S32X_1X1_DECONVOLUTION_HPP +#define CPU_JIT_AVX512_CORE_X8S8S32X_1X1_DECONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" +#include "type_helpers.hpp" +#include "primitive_iterator.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_deconvolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_uni_1x1_conv_utils.hpp" +#include "jit_avx512_core_x8s8s32x_1x1_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t + : public cpu_primitive_t { + struct pd_t : public cpu_deconvolution_fwd_pd_t { + pd_t(engine_t *engine, const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : cpu_deconvolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , conv_pd_(nullptr) {} + + pd_t(const pd_t &other) + : cpu_deconvolution_fwd_pd_t(other) + , conv_pd_(other.conv_pd_->clone()) + {} + + ~pd_t() { delete conv_pd_; } + + DECLARE_COMMON_PD_T(conv_pd_->name(), + jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t); + + status_t init_convolution() { + convolution_desc_t cd; + status_t status; + + auto dd = desc(); + status = conv_desc_init(&cd, prop_kind::forward_training, + alg_kind::convolution_direct, &(dd->src_desc), + &(dd->weights_desc), &(dd->bias_desc), &(dd->dst_desc), + dd->strides, dd->dilates, dd->padding[0], dd->padding[1], + dd->padding_kind); + + if (status == status::success) { + status = mkldnn_primitive_desc::create( + &conv_pd_, (op_desc_t *)&cd, &attr_, engine_, nullptr); + } + + if (status == status::success) + status = set_default_params(); + + return status; + }; + + status_t init() { + bool ok = true + && is_fwd() + && desc()->alg_kind == alg_kind::deconvolution_direct + && !has_zero_dim_memory() + && desc()->src_desc.data_type == src_type + && desc()->dst_desc.data_type == dst_type + && desc()->weights_desc.data_type == data_type::s8 + && IMPLICATION(with_bias(), utils::one_of( + desc()->bias_desc.data_type, data_type::f32, + data_type::s32, data_type::s8, data_type::u8)) + && desc()->accum_data_type == data_type::s32; + if (!ok) return status::unimplemented; + + CHECK(init_convolution()); + + return status::success; + } + + virtual void init_scratchpad_md() override { + const auto conv_1x1_pd = static_cast(conv_pd_); + scratchpad_md_ = *conv_1x1_pd->scratchpad_md(); + } + + protected: + status_t set_default_params() { + auto conv_1x1_pd_ = static_cast(conv_pd_); + src_md_ = *conv_1x1_pd_->src_md(); + dst_md_ = *conv_1x1_pd_->dst_md(); + weights_md_ = *conv_1x1_pd_->weights_md(); + if (with_bias()) + bias_md_ = *conv_1x1_pd_->weights_md(1); + return status::success; + } + + using conv_pd_t = typename jit_avx512_core_x8s8s32x_1x1_convolution_fwd_t + ::pd_t; + friend jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t; + primitive_desc_t *conv_pd_; + }; + + jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + { pd()->conv_pd_->create_primitive((primitive_t **)&conv_p_); } + + ~jit_avx512_core_x8s8s32x_1x1_deconvolution_fwd_t() + { delete conv_p_; } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + return conv_p_->execute(ctx); + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + primitive_t *conv_p_; +}; + +} +} +} + +#endif /* CPU_JIT_AVX512_CORE_X8S8S32X_1X1_DECONVOLUTION_HPP */ diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.cpp new file mode 100644 index 000000000000..10e98a00c45a --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.cpp @@ -0,0 +1,1182 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_memory.hpp" + +#include "jit_avx512_core_x8s8s32x_conv_kernel.hpp" + +#define GET_OFF(field) offsetof(jit_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +namespace { +void pick_loop_order(jit_conv_conf_t &jcp, int nthr) +{ + jcp.loop_order = loop_cwgn; + if (jcp.ngroups > 1) { + jcp.loop_order = loop_ngcw; + if (jcp.mb < nthr) + jcp.loop_order = jcp.ndims == 3 ? loop_nwcg : loop_nhwcg; + } +} +} + +template +bool _jit_avx512_core_x8s8s32x_fwd_kernel::maybe_eltwise(int position) +{ + using namespace primitive_kind; + const auto &p = attr_.post_ops_; + + if (position == 0) { + /* eltwise before sum */ + return p.contain(eltwise, 0); + } else if (position == 1) { + /* eltwise after sum */ + return p.contain(sum, 0) && p.contain(eltwise, 1); + } + + return false; +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::prepare_output(int ur_w) +{ + int nb_oc_block + = jcp.is_depthwise ? jcp.nb_ch_blocking : jcp.nb_oc_blocking; + for (int k = 0; k < nb_oc_block; k++) + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + vpxord(vmm, vmm, vmm); + } + if (jcp.signed_input) { + xor_(reg_scratch, reg_scratch); + if (jcp.is_depthwise && !jcp.is_fast_depthwise) { + Reg32 _t32 = reg_scratch.cvt32(); + mov(_t32, (uint32_t)128); + vpbroadcastd(vmm_shift, _t32); + } else { + Reg8 _t8 = reg_scratch.cvt8(); + mov(_t8, (int8_t)128); + vpbroadcastb(vmm_shift, _t8); + } + } +} + +template +const Vmm _jit_avx512_core_x8s8s32x_fwd_kernel:: + vmm_mask(const Vmm vmm_in, bool mask_flag, bool store) { + return vmm_in; +} + +template<> +const Zmm _jit_avx512_core_x8s8s32x_fwd_kernel:: + vmm_mask(const Zmm zmm_in, bool mask_flag, bool store) { + return mask_flag ? (store ? zmm_in | ktail_mask : zmm_in | ktail_mask | T_z) + : zmm_in; +} + + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::cvt2ps(data_type_t type_in, + const Vmm vmm_in, const Operand &op, bool mask_flag) { + //const Vmm vmm = mask_flag ? vmm_in | ktail_mask | T_z : vmm_in; + const Vmm vmm = vmm_mask(vmm_in, mask_flag); + switch (type_in) { + case data_type::f32: + case data_type::s32: vmovups(vmm, op); break; + case data_type::s8: vpmovsxbd(vmm, op); break; + case data_type::u8: vpmovzxbd(vmm, op); break; + default: assert(!"unsupported data type"); + } + if (type_in != data_type::f32) + vcvtdq2ps(vmm_in, vmm_in); +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::compute_eltwise(int ur_w) { + int nb_oc_block + = jcp.is_depthwise ? jcp.nb_ch_blocking : jcp.nb_oc_blocking; + if (ur_w == jcp.ur_w) + eltwise_injector_->compute_vector_range(0, nb_oc_block * jcp.ur_w); + else + for (int k = 0; k < nb_oc_block; k++) + eltwise_injector_->compute_vector_range(k * jcp.ur_w, + k * jcp.ur_w + ur_w); +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::store_output( + int ur_w, bool last_oc_block_flag) { + int nb_oc_block + = jcp.is_depthwise ? jcp.nb_ch_blocking : jcp.nb_oc_blocking; + int oc_block = jcp.is_depthwise ? jcp.ch_block : jcp.oc_block; + + mov(reg_bias, ptr[param1 + GET_OFF(bias)]); + mov(reg_ptr_scales, ptr[param1 + GET_OFF(scales)]); + if (jcp.signed_input) + mov(reg_compensation, ptr[param1 + GET_OFF(compensation)]); + + const auto &p = attr_.post_ops_; + const int sum_idx = p.find(primitive_kind::sum); + const float *p_sum_scale = nullptr; + if (sum_idx != -1) { + const auto &p_entry = p.entry_[sum_idx]; + p_sum_scale = &p_entry.sum.scale; + } + + if (p_sum_scale && *p_sum_scale != 1.f) + mov(reg_ptr_sum_scale, (size_t)p_sum_scale); + + if (jcp.signed_input && jcp.ver != ver_vnni) { + /* put 'wei_adj_scale = 0.5' for bias calculation */ + mov(reg_bias_alpha, float2int(jcp.wei_adj_scale)); + vmovq(xmm_bias_alpha(), reg_bias_alpha); + vbroadcastss(vmm_bias_alpha(), xmm_bias_alpha()); + } + + for (int k = 0; k < nb_oc_block; k++) { + const bool mask_flag = last_oc_block_flag && k == nb_oc_block - 1; + int scale_offset = jcp.is_oc_scale * (sizeof(float) * k * oc_block); + if (jcp.with_bias) { + int bias_offset = jcp.typesize_bia * k * oc_block; + auto bias_addr = EVEX_compress_addr(reg_bias, bias_offset); + + cvt2ps(jcp.bia_dt, vmm_bias, bias_addr, mask_flag); + if (jcp.signed_input && jcp.ver != ver_vnni) + /* bias *= 0.5 */ + vmulps(vmm_bias, vmm_bias, vmm_bias_alpha()); + } + if (jcp.signed_input) { + int comp_offset = sizeof(int32_t) * k * oc_block; + auto comp_addr = EVEX_compress_addr(reg_compensation, comp_offset); + + cvt2ps(data_type::s32, vmm_comp, comp_addr, mask_flag); + } + /* add to zmm_accum: compensation, bias and permute */ + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + if (jcp.is_fast_depthwise) + vpermd(zmm_out(j, k), zmm_permute, zmm_out(j, k)); + vcvtdq2ps(vmm, vmm); + if (jcp.signed_input) + vaddps(vmm, vmm, vmm_comp); + if (jcp.with_bias) + vaddps(vmm, vmm, vmm_bias); + + const Vmm vmm_k = vmm_mask(vmm, mask_flag); + vmulps(vmm_k, vmm, + EVEX_compress_addr(reg_ptr_scales, scale_offset)); + } + } + + /* Do post-ops */ + if (maybe_eltwise(0)) compute_eltwise(ur_w); + if (p_sum_scale) { // post_op: sum + for (int k = 0; k < nb_oc_block; k++) { + const bool mask_flag = last_oc_block_flag && k == nb_oc_block - 1; + for (int j = 0; j < ur_w; j++) { + int aux_output_offset + = jcp.typesize_out + * (k * oc_block + + j * jcp.oc_without_padding * jcp.ngroups); + auto addr = EVEX_compress_addr(reg_out, aux_output_offset); + Vmm vmm = vmm_out(j, k); + cvt2ps(jcp.dst_dt, vmm_prev_dst, addr, mask_flag); + if (*p_sum_scale == 1.f) + vaddps(vmm, vmm_prev_dst); + else + vfmadd231ps(vmm, vmm_prev_dst, zword_b[reg_ptr_sum_scale]); + } + } + } + if (maybe_eltwise(1)) compute_eltwise(ur_w); + + /* write out register to output_addr */ + for (int k = 0; k < nb_oc_block; k++) { + const bool mask_flag = last_oc_block_flag && k == nb_oc_block - 1; + for (int j = 0; j < ur_w; j++) { + Vmm vmm = vmm_out(j, k); + if (jcp.dst_dt == data_type::u8) { + vpxord(vmm_zero, vmm_zero, vmm_zero); + vmaxps(vmm, vmm_zero, vmm); + } + + if (jcp.dst_dt != data_type::f32) { + /* Note: using Zmm for rounding in Xmm/Ymm kernel + because there is no instruction to do rounding + from Xmm/Ymm -> Xmm/Ymm. + Embedded rounding is not supported for Xmm. + TODO: maybe avoid Zmm if it helps performance.*/ + Zmm zmm = zmm_out(j, k); + vcvtps2dq(zmm, zmm); + } + } + + for (int j = 0; j < ur_w; j++) { + int aux_output_offset = jcp.typesize_out + * (k * oc_block + j * jcp.oc_without_padding * jcp.ngroups); + auto addr = EVEX_compress_addr(reg_out, aux_output_offset); + + Vmm vmm = vmm_out(j, k); + const Vmm r_vmm = vmm_mask(vmm, mask_flag, true); + + switch (jcp.dst_dt) { + case data_type::f32: + case data_type::s32: vmovups(addr, r_vmm); break; + case data_type::s8: vpmovsdb(addr, r_vmm); break; + case data_type::u8: vpmovusdb(addr, r_vmm); break; + default: assert(!"unknown dst_dt"); + } + } + } + +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::compute_ker_dw( + int ur_w, int pad_l, int pad_r, ic_block_t last_ic_block_flag, bool h_padded) { + assert(!"invalid group blocking for depthwise convolution"); +} + +template <> +void _jit_avx512_core_x8s8s32x_fwd_kernel::compute_ker_dw( + int ur_w, int pad_l, int pad_r, ic_block_t last_ic_block_flag, bool h_padded) { + + auto input_spatial_index = [=](int oi, int ki) { + return (ki * (jcp.dilate_w + 1) + oi * jcp.stride_w - pad_l); + }; + + auto input_offset2 = [=](int ii, int ci) { + return jcp.typesize_in * (ii * jcp.ngroups + ci * jcp.ch_block); + }; + + auto input_offset3 = [=](int oi, int ci, int ki) { + return jcp.typesize_in * input_offset2(input_spatial_index(oi, ki), ci); + }; + + auto kernel_offset = [=](int ci, int ki) { + return jcp.typesize_in * ((ci * jcp.kh * jcp.kw + ki) * jcp.ch_block); + }; + + auto compute = [=](Zmm vreg_acc, Zmm vreg_wei, Zmm vreg_src) { + // okay for depthwise since src is zero-extended + if (jcp.ver == ver_vnni) { + vpdpbusd(vreg_acc, vreg_src, vreg_wei); + } else { + vpmaddwd(zmm_tmp, vreg_src, vreg_wei); + vpaddd(vreg_acc, vreg_acc, zmm_tmp); + } + }; + + int ii_start = 0; + int ii_end = -1; + if (jcp.is_resrc_depthwise && !h_padded) { + // find bounds of input spatial indices + bool first = true; + for (int ki = 0; ki < jcp.kw; ki++) { + int oi_start = get_ow_start(ki, pad_l); + int oi_end = get_ow_end(ur_w, ki, pad_r); + for (int oi = oi_start; oi < oi_end; oi++) { + int ii = input_spatial_index(oi, ki); + if (first || ii < ii_start) + ii_start = ii; + if (first || ii > ii_end) + ii_end = ii; + first = false; + } + } + } + + if (jcp.signed_input) { + vpxord(zmm_shifted_zero, zmm_shifted_zero, zmm_shifted_zero); + vpaddb(zmm_shifted_zero, zmm_shifted_zero, vmm_shift); + } + for (int ci = 0; ci < jcp.nb_ch_blocking; ci++) { + const bool mask_flag = last_ic_block_flag != no_last_block + && ci == jcp.nb_ch_blocking - 1; + if (jcp.is_resrc_depthwise && !h_padded) { + // now we can load input once and reuse up to jcp.kw times + for (int ii = ii_start; ii <= ii_end; ii++) { + int aux_input_offset = input_offset2(ii, ci); + const Zmm zmm_inp_tmp = zmm_inp(ii, jcp.nb_ch_blocking); + const Zmm zmm_inp_msk = mask_flag + ? zmm_inp_tmp | ktail_mask | T_z + : zmm_inp_tmp; + if (jcp.is_fast_depthwise) { + assert(!mask_flag); + vbroadcasti32x4(zmm_inp_msk, + EVEX_compress_addr(aux_reg_inp, aux_input_offset)); + } else { + vpmovzxbd(zmm_inp_msk, + EVEX_compress_addr(aux_reg_inp, aux_input_offset)); + } + if (jcp.signed_input) + vpaddb(zmm_inp_tmp, zmm_inp_tmp, vmm_shift); + } + } + for (int ki = 0; ki < jcp.kw; ki++) { + int aux_kernel_offset = kernel_offset(ci, ki); + if (jcp.is_fast_depthwise) { + vbroadcasti32x4(zmm_wei, + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + vmovdqu8(zmm_wei | kblend_mask | T_z, zmm_wei); + } else { + vpmovsxbd(zmm_wei, + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + } + if (h_padded) { + assert(jcp.signed_input); + for (int oi = 0; oi < ur_w; oi++) + compute(zmm_out(oi, ci), zmm_wei, zmm_shifted_zero); + } else { + const Zmm r_zmm_src = mask_flag ? zmm_src | ktail_mask : zmm_src; + int oi_start = get_ow_start(ki, pad_l); + int oi_end = get_ow_end(ur_w, ki, pad_r); + int start_ = jcp.signed_input ? 0 : oi_start; + int end_ = jcp.signed_input ? ur_w : oi_end; + for (int oi = start_; oi < end_; oi++) { + if (oi >= oi_start && oi < oi_end) { + if (jcp.is_resrc_depthwise) { + int ii = input_spatial_index(oi, ki); + zmm_src = zmm_inp(ii, jcp.nb_ch_blocking); + } else { + int aux_input_offset = input_offset3(oi, ci, ki); + if (jcp.is_fast_depthwise) { + assert(!mask_flag); + vbroadcasti32x4(r_zmm_src, + EVEX_compress_addr(aux_reg_inp, + aux_input_offset)); + } else { + vpmovzxbd(r_zmm_src, + EVEX_compress_addr(aux_reg_inp, + aux_input_offset)); + } + if (jcp.signed_input) + vpaddb(zmm_src, zmm_src, vmm_shift); + } + } else if (jcp.signed_input) { + zmm_src = zmm_shifted_zero; + } + compute(zmm_out(oi, ci), zmm_wei, zmm_src); + } + } + } + } +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::compute_ker(int ur_w, int pad_l, + int pad_r, ic_block_t last_ic_block_flag, bool h_padded) { + if (jcp.is_depthwise) + return compute_ker_dw(ur_w, pad_l, pad_r, last_ic_block_flag, h_padded); + + int kw = jcp.kw; + int stride_w = jcp.stride_w; + int ic_block = jcp.ic_block; + int oc_block = jcp.oc_block; + int ch_block_all = jcp.ch_block * ic_block * oc_block; + + int nb_oc_block = jcp.nb_oc_blocking; + + auto input_offset = [=](int oi, int ic, int ki) { + return jcp.typesize_in + * ((ki * (jcp.dilate_w + 1) + oi * stride_w - pad_l) + * jcp.ic_without_padding * jcp.ngroups + 4 * ic); + }; + auto kernel_offset = [=](int ii, int ic, int ki) { + return jcp.typesize_in + * ((ii * jcp.nb_ic * jcp.kh * jcp.kw + ki) * ch_block_all + + 4 * ic * oc_block); + }; + auto compute = [=](Vmm vreg_acc, Vmm vreg_wei, Vmm vreg_src) { + if (jcp.ver == ver_vnni) { + vpdpbusd(vreg_acc, vreg_src, vreg_wei); + } else { + vpmaddubsw(vmm_tmp, vreg_src, vreg_wei); + vpmaddwd(vmm_tmp, vmm_tmp, vmm_one); + vpaddd(vreg_acc, vreg_acc, vmm_tmp); + } + }; + + for (int ki = 0; ki < kw; ki++) { + int jj_start = get_ow_start(ki, pad_l); + int jj_end = get_ow_end(ur_w, ki, pad_r); + int tail_size = jcp.ic_without_padding % 4; + int _start = (jcp.signed_input) ? 0 : jj_start; + int _end = (jcp.signed_input) ? ur_w : jj_end; + /* Skip the last loads of input if (ic%16)/4 < ic_block/4 */ + int icb = (last_ic_block_flag != no_last_block) + ? div_up((jcp.ic_without_padding % ic_block), 4) + : ic_block / 4; + for (int ic = 0; ic < icb; ic++) { + if (h_padded == true) { + /* fill padded area with shifted values */ + Vmm inp = vmm_inp(0,nb_oc_block); + vpxord(inp, inp, inp); + vpaddb(inp, inp, vmm_shift); + } else { + for (int jj = _start; jj < _end; jj++) { + int aux_input_offset = input_offset(jj, ic, ki); + if (jj >= jj_start && jj < jj_end) { + if (last_ic_block_flag == last_sp_block + && tail_size != 0 && ic == icb - 1) { + Xmm xmm_tmp = Xmm(vmm_inp(jj, nb_oc_block).getIdx()); + for (int r = 0; r < tail_size; ++r) + vpinsrb(xmm_tmp, xmm_tmp, + ptr[aux_reg_inp + aux_input_offset + r], r); + vpbroadcastd(vmm_inp(jj, nb_oc_block), xmm_tmp); + } else { + vpbroadcastd(vmm_inp(jj, nb_oc_block), + EVEX_compress_addr( + aux_reg_inp, aux_input_offset)); + } + if (jcp.signed_input) + vpaddb(vmm_inp(jj, nb_oc_block), + vmm_inp(jj, nb_oc_block), vmm_shift); + } else { + /* fill padded area with shifted values */ + if (jcp.signed_input) { + Vmm inp = vmm_inp(jj, nb_oc_block); + vpxord(inp, inp, inp); + vpaddb(inp, inp, vmm_shift); + } + } + } + } + for (int ii = 0; ii < nb_oc_block; ii++) { + int aux_kernel_offset = kernel_offset(ii, ic, ki); + vmovups(vmm_wei, + EVEX_compress_addr(aux_reg_ker, aux_kernel_offset)); + for (int jj = _start; jj < _end; jj++) { + Vmm inp = (h_padded == true) + ? vmm_inp(0,nb_oc_block) : vmm_inp(jj, nb_oc_block); + compute(vmm_out(jj, ii), vmm_wei, inp); + } + } + } + } +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::kh_loop( + int ur_w, int pad_l, int pad_r, ic_block_t last_ic_block_flag) { + Label kh_label, skip_kh_loop; + Label t_overflow_label, no_t_overflow_label, + b_overflow_label, no_b_overflow_label; + + int ch_block_all = jcp.ch_block * jcp.ic_block * jcp.oc_block; + int shift_kernel_ptr = jcp.typesize_in * jcp.kw * ch_block_all; + int shift_input_ptr = jcp.typesize_in * (jcp.dilate_h + 1) * jcp.iw + * jcp.ic_without_padding * jcp.ngroups; + + mov(aux_reg_inp, reg_inp); + mov(aux_reg_ker, reg_ker); + + if (jcp.signed_input && jcp.ndims > 3) { + mov(reg_overflow, ptr[param1 + GET_OFF(t_overflow)]); + cmp(reg_overflow, 0); + je(no_t_overflow_label, T_NEAR); + L(t_overflow_label); { + compute_ker(ur_w, pad_l, pad_r, last_ic_block_flag, true); + + add(aux_reg_ker, shift_kernel_ptr); + dec(reg_overflow); + cmp(reg_overflow, 0); + jg(t_overflow_label, T_NEAR); + } + L(no_t_overflow_label); + } + mov(reg_kj, ptr[param1 + GET_OFF(kh_padding)]); + if ((jcp.signed_input) || (!jcp.signed_input && + (jcp.kh - 1) * (jcp.dilate_h + 1) < nstl::max(jcp.t_pad, jcp.b_pad))) { + cmp(reg_kj, 0); + je(skip_kh_loop, T_NEAR); + } + L(kh_label); { + compute_ker(ur_w, pad_l, pad_r, last_ic_block_flag, false); + + add(aux_reg_ker, shift_kernel_ptr); + add(aux_reg_inp, shift_input_ptr); + dec(reg_kj); + cmp(reg_kj, 0); + jg(kh_label, T_NEAR); + } + L(skip_kh_loop); + if (jcp.signed_input && jcp.ndims > 3) { + mov(reg_overflow, ptr[param1 + GET_OFF(b_overflow)]); + cmp(reg_overflow, 0); + je(no_b_overflow_label, T_NEAR); + L(b_overflow_label); { + compute_ker(ur_w, pad_l, pad_r, last_ic_block_flag, true); + + add(aux_reg_ker, shift_kernel_ptr); + dec(reg_overflow); + cmp(reg_overflow, 0); + jg(b_overflow_label, T_NEAR); + } + L(no_b_overflow_label); + } +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::icb_loop( + int ur_w, int pad_l, int pad_r, bool is_last_sp_block) +{ + prepare_output(ur_w); + + // IC loop + Label icb_label; + mov(reg_icb, jcp.nb_ic); + L(icb_label); + if (jcp.ngroups % jcp.ch_block != 0 || jcp.ic_without_padding != jcp.ic) { + Label common_ker, end_ker; + + cmp(reg_icb, 1); // The last IC block + jne(common_ker, T_NEAR); + + kh_loop(ur_w, pad_l, pad_r, + is_last_sp_block ? last_sp_block : last_ic_block); + jmp(end_ker, T_NEAR); + + L(common_ker); + kh_loop(ur_w, pad_l, pad_r, no_last_block); + + L(end_ker); + } else { + kh_loop(ur_w, pad_l, pad_r, no_last_block); + } + // End of IC Loop + int inp_step = jcp.ic_block; + int ker_step = jcp.kh * jcp.kw * jcp.oc_block * jcp.ic_block; + add(reg_inp, jcp.typesize_in * inp_step); + add(reg_ker, jcp.typesize_in * ker_step); + + dec(reg_icb); + cmp(reg_icb, 0); + jg(icb_label, T_NEAR); + + sub(reg_inp, jcp.typesize_in * inp_step * jcp.nb_ic); + sub(reg_ker, jcp.typesize_in * ker_step * jcp.nb_ic); + + if (jcp.ngroups % jcp.ch_block != 0 || jcp.oc_without_padding != jcp.oc) { + Label common_store, end_store; + + if (jcp.is_depthwise) + cmp(reg_oc_blocks, jcp.nb_ch - jcp.nb_ch_blocking); + else + cmp(reg_oc_blocks, jcp.nb_oc - jcp.nb_oc_blocking); + + jne(common_store, T_NEAR); + + store_output(ur_w, true); // last oc block + jmp(end_store, T_NEAR); + + L(common_store); + store_output(ur_w, false); + + L(end_store); + } else { + store_output(ur_w, false); + } +} + +template +void _jit_avx512_core_x8s8s32x_fwd_kernel::generate() +{ + Label permute_index_table; + int inp_shift_pad = jcp.typesize_in * (jcp.ur_w * jcp.stride_w - jcp.l_pad) + * jcp.ic_without_padding * jcp.ngroups; + int inp_shift_pad_second_block = -1 * jcp.typesize_in * jcp.l_pad + * jcp.ic_without_padding * jcp.ngroups; + int inp_shift = jcp.typesize_in * + (jcp.ur_w * jcp.stride_w * jcp.ic_without_padding + * jcp.ngroups); + int out_shift = jcp.typesize_out * + (jcp.ur_w * jcp.oc_without_padding * jcp.ngroups); + preamble(); + + if (jcp.is_depthwise) { + int idx = jcp.max_regs_ur - 1; + if (!jcp.is_resrc_depthwise) + zmm_src = Zmm(++idx); + if (jcp.ver != ver_vnni) + zmm_tmp = Zmm(++idx); + if (jcp.is_fast_depthwise) + zmm_permute = Zmm(++idx); + if (jcp.signed_input) { + zmm_shifted_zero = Zmm(++idx); + ++idx; // due to extra register used for shifts and compensations + } + assert(idx == ker_dw_reg_base_idx); + } + + if (!jcp.is_depthwise && jcp.ver != ver_vnni) { + xor_(reg_scratch, reg_scratch); + Reg16 _t16 = reg_scratch.cvt16(); + mov(_t16, 0x1); + vpbroadcastw(vmm_one, _t16); + } + + mov(reg_inp, ptr[param1 + GET_OFF(src)]); + mov(reg_out, ptr[param1 + GET_OFF(dst)]); + mov(reg_ker, ptr[param1 + GET_OFF(filt)]); + + if (jcp.ngroups % jcp.ch_block != 0 || jcp.oc_without_padding != jcp.oc) { + int tail_size = jcp.is_depthwise + ? jcp.ngroups % jcp.ch_block + : jcp.oc_without_padding % jcp.oc_block; + int mask = (1 << tail_size) - 1; + mov(reg_oc_blocks, ptr[param1 + GET_OFF(oc_blocks)]); + Reg32 regw_tmp = reg_oi.cvt32(); + mov(regw_tmp, mask); + kmovw(ktail_mask, regw_tmp); + } + if (jcp.is_fast_depthwise) { + // prepare mask register for blending weights + mov(reg_scratch, 0x8888444422221111); + kmovq(kblend_mask, reg_scratch); + // load permute indices from data section + mov(reg_scratch, permute_index_table); + vmovdqu32(zmm_permute, ptr[reg_scratch]); + } + + int r_pad = nstl::max(0, (jcp.ow - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.iw + jcp.l_pad - 1)); + int n_oi = jcp.ow / jcp.ur_w; + int r_pad1 = (jcp.ur_w * n_oi - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1); + + if (jcp.nb_ow == 1) { + if (r_pad1 > 0 || jcp.ur_w_tail == 0) + n_oi--; + + xor_(reg_oi, reg_oi); + if (jcp.ow == jcp.ur_w) { + icb_loop(jcp.ur_w, jcp.l_pad, r_pad, true); + } else { + if (n_oi == 0) { + icb_loop(jcp.ur_w, jcp.l_pad, r_pad1, jcp.ur_w_tail == 0); + add(reg_inp, inp_shift_pad); + add(reg_out, out_shift); + if (jcp.ur_w_tail != 0) { + icb_loop(jcp.ur_w_tail, 0, r_pad, true); + } + } else { + if (jcp.l_pad > 0) { + icb_loop(jcp.ur_w, jcp.l_pad, 0, false); + add(reg_inp, inp_shift_pad); + add(reg_out, out_shift); + + inc(reg_oi); + } + if ((jcp.l_pad <= 0 && n_oi > 0) || (jcp.l_pad > 0 && n_oi > 1)) + { + Label ow_loop_label; + L(ow_loop_label); { + icb_loop(jcp.ur_w, 0, 0, false); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + + inc(reg_oi); + cmp(reg_oi, n_oi); + jl(ow_loop_label, T_NEAR); + } + } + if (r_pad1 > 0 || jcp.ur_w_tail == 0) { + icb_loop(jcp.ur_w, 0, r_pad1, jcp.ur_w_tail == 0); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + } + if (jcp.ur_w_tail != 0) { + icb_loop(jcp.ur_w_tail, 0, r_pad, true); + } + } + } + } else { + // ow block is only processed. + // Number of block is passed as parameter owb, + // and padding processing depends on this number. + Label end_label, last_oi_label, middle_ow_blocks_label, tail_label, + oi_loop_label, oi_loop_end_label; + + assert(jcp.ow_block % jcp.ur_w == 0); + int n_oi_not_last_ow_block = jcp.ow_block / jcp.ur_w; + // to simplify code (and general regs usage), + // size of ow block must be >= 2 * ur_w + assert(n_oi_not_last_ow_block > 1); + int n_oi_next_last_ow_block = n_oi_not_last_ow_block; + int n_oi_first_ow_block = n_oi_not_last_ow_block; + int n_oi_last_ow_block + = (jcp.ow - jcp.ow_block * (jcp.nb_ow - 1)) / jcp.ur_w; + // prepare right padding + bool next_last_ow_block_padded = r_pad1 > 0 && n_oi_last_ow_block == 0; + bool first_ow_block_padded + = next_last_ow_block_padded && jcp.nb_ow == 2; + bool last_ow_block_padded + = (r_pad1 > 0 || jcp.ur_w_tail == 0) && n_oi_last_ow_block > 0; + + if (last_ow_block_padded) n_oi_last_ow_block--; + else if (first_ow_block_padded) n_oi_first_ow_block--; + else if (next_last_ow_block_padded) n_oi_next_last_ow_block--; + + mov(reg_owb, ptr[param1 + GET_OFF(owb)]); + cmp(reg_owb, 0); // is that the first ow-block ? + jg(middle_ow_blocks_label, T_NEAR); + + // the first ow block, compute left padding + mov(reg_oi, n_oi_first_ow_block); + if (jcp.l_pad > 0) { + icb_loop(jcp.ur_w, jcp.l_pad, 0, false); + add(reg_inp, inp_shift_pad); + add(reg_out, out_shift); + + dec(reg_oi); + } + jmp(oi_loop_label, T_NEAR); + + // middle or last ow block entry + L(middle_ow_blocks_label); + + if (jcp.l_pad > 0) { + // just to consider left padding, not compute + add(reg_inp, inp_shift_pad_second_block); + } + + // set number of iteration for oi-loop + if (n_oi_last_ow_block != n_oi_not_last_ow_block) { + cmp(reg_owb, jcp.nb_ow - 1); // last ow-block ? + mov(reg_oi, n_oi_last_ow_block); + je(oi_loop_label, T_NEAR); + } + + if (n_oi_next_last_ow_block != n_oi_not_last_ow_block) { + cmp(reg_owb, jcp.nb_ow - 2); // next to last ow-block ? + + mov(reg_oi, n_oi_next_last_ow_block); + je(oi_loop_label, T_NEAR); + } + mov(reg_oi, n_oi_not_last_ow_block); // other middle ow-blocks + + // oi loop w/o padding + L(oi_loop_label); { + cmp(reg_oi, 0); + jle(oi_loop_end_label, T_NEAR); + + icb_loop(jcp.ur_w, 0, 0, false); + + add(reg_inp, inp_shift); + add(reg_out, out_shift); + dec(reg_oi); + + jmp(oi_loop_label, T_NEAR); + } + L(oi_loop_end_label); + + mov(reg_owb, ptr[param1 + GET_OFF(owb)]); + cmp(reg_owb, 0); // first ow-block ? + if (first_ow_block_padded) + je(last_oi_label, T_NEAR); + else + je(end_label, T_NEAR); + + cmp(reg_owb, jcp.nb_ow - 2); // next to last ow-block ? + jl(end_label, T_NEAR); + if (next_last_ow_block_padded) + je(last_oi_label, T_NEAR); + else + je(end_label, T_NEAR); + + // that is last block + if (!last_ow_block_padded) + jmp(tail_label, T_NEAR); + + // last oi block with right padding + L(last_oi_label); + icb_loop(jcp.ur_w, 0, r_pad1, jcp.ur_w_tail == 0); + add(reg_inp, inp_shift); + add(reg_out, out_shift); + + mov(reg_owb, ptr[param1 + GET_OFF(owb)]); + cmp(reg_owb, jcp.nb_ow - 1); // last ow_block? + jl(end_label, T_NEAR); + + // ur_w tail + L(tail_label); + if (jcp.ur_w_tail != 0) { + icb_loop(jcp.ur_w_tail, 0, r_pad, true); + } + L(end_label); + } + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); + + if (jcp.is_fast_depthwise) { + align(64); + L(permute_index_table); + const uint32_t _idx[] + = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; + for (size_t i = 0; i < sizeof(_idx) / sizeof(_idx[0]); ++i) + dd(_idx[i]); + } +} + +bool jit_avx512_core_x8s8s32x_fwd_kernel::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) +{ + using namespace primitive_kind; + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + + switch (p.len_) { + case 0: return true; + case 1: return is_eltwise(0) || p.contain(sum, 0); + case 2: return (p.contain(sum, 0) && is_eltwise(1)) || + (p.contain(sum, 1) && is_eltwise(0)); + default: return false; + } + + return false; +} + +status_t jit_avx512_core_x8s8s32x_fwd_kernel::init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, memory_desc_t &src_md, + memory_desc_t &weights_md, memory_desc_t &dst_md, + memory_desc_t &bias_md, const primitive_attr_t &attr, + int nthreads) +{ + using namespace prop_kind; + + const memory_desc_wrapper src_d(&src_md); + const memory_desc_wrapper weights_d(&weights_md); + const memory_desc_wrapper dst_d(&dst_md); + const memory_desc_wrapper bias_d(&bias_md); + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + int ndims = src_d.ndims(); + bool is_1d = ndims == 3; + + if (!(mayiuse(avx512_core) + && one_of(src_d.data_type(), data_type::u8, data_type::s8) + && weights_d.data_type() == data_type::s8 + && one_of(dst_d.data_type(), data_type::f32, data_type::s32, + data_type::s8, data_type::u8))) + return status::unimplemented; + + jcp = zero(); + jcp.ndims = ndims; + jcp.prop_kind = cd.prop_kind; + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.ic_without_padding = jcp.ic; + jcp.ih = is_1d ? 1 : src_d.dims()[ndims - 2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.oh = is_1d ? 1 : dst_d.dims()[ndims - 2]; + jcp.ow = dst_d.dims()[ndims - 1]; + jcp.kh = is_1d ? 1 : weights_d.dims()[with_groups + ndims - 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + jcp.t_pad = is_1d ? 0 : cd.padding[0][ndims - 4]; + jcp.l_pad = cd.padding[0][ndims - 3]; + jcp.stride_h = is_1d ? 1 : cd.strides[ndims - 4]; + jcp.stride_w = cd.strides[ndims - 3]; + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + jcp.ur_h = 1; /* no code-unrolling by h so far */ + + jcp.dilate_h = is_1d ? 0 : cd.dilates[ndims - 4]; + jcp.dilate_w = cd.dilates[ndims - 3]; + + jcp.signed_input = (src_d.data_type() == data_type::s8) ? true : false; + jcp.is_depthwise = true && with_groups && everyone_is(1, jcp.ic, jcp.oc); + + if (jcp.is_depthwise) { + jcp.ch_block = 16; + jcp.ic_block = 1; + jcp.oc_block = 1; + } else { + jcp.ch_block = 1; + jcp.ic_block = 16; + jcp.oc_block = 16; + + if (jcp.ngroups == 1) { + /* For non grouped convolutions, pad channels by 16 if needed */ + jcp.oc = rnd_up(jcp.oc, jcp.oc_block); + jcp.ic = rnd_up(jcp.ic, jcp.ic_block); + } else if (!is_1d && jcp.ngroups != 1 && jcp.ic % jcp.ic_block != 0) { + /* For grouped convolutions, MKL-DNN doesn't support padding. + Use Ymm when channels per group is multiple of 8, + Xmm when channels per group is multiple of 4 */ + jcp.ic_block = jcp.ic % 8 == 0 ? 8 : 4; + jcp.oc_block = jcp.ic_block; + } + if (jcp.ic % jcp.ic_block !=0 || jcp.oc % jcp.oc_block != 0) + return status::unimplemented; + } + + jcp.b_pad = (jcp.oh - 1) * jcp.stride_h + (jcp.kh - 1) * (jcp.dilate_h + 1) + - (jcp.ih + jcp.t_pad - 1); + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + jcp.ver = mayiuse(avx512_core_vnni) ? ver_vnni : ver_avx512_core; + jcp.is_fast_depthwise = true && jcp.is_depthwise && jcp.ver == ver_vnni + && jcp.ngroups % jcp.ch_block == 0; // for groups not multiple of 16 + // would require byte masking + // for load from src + jcp.is_resrc_depthwise = jcp.is_depthwise && jcp.stride_w < jcp.kw + && jcp.kw < 4 && jcp.dilate_w == 0; + if (jcp.is_depthwise) { + jcp.max_regs_ur = 31 - jcp.is_fast_depthwise - !jcp.is_resrc_depthwise + - 2 * jcp.signed_input - (jcp.ver != ver_vnni); + } else { + jcp.max_regs_ur = jcp.ver == ver_vnni ? 31 : 28; + } + + auto set_or_check_wei_format = [&]() { + using namespace format_tag; + format_tag_t wei_tag; + if (jcp.ic_block == 16 || jcp.ch_block == 16) { + if (is_1d) { + wei_tag = with_groups + ? jcp.is_depthwise ? Goiw16g : gOIw4i16o4i + : OIw4i16o4i; + } else { + wei_tag = with_groups + ? jcp.is_depthwise ? Goihw16g : gOIhw4i16o4i + : OIhw4i16o4i; + } + } else if (with_groups && jcp.ic_block == 8) { + wei_tag = gOIhw2i8o4i; + } else + wei_tag = gOIhw4o4i; + + memory_desc_t want_wei_md = weights_md; + memory_desc_init_by_tag(want_wei_md, wei_tag); + if (jcp.signed_input) { + want_wei_md.extra.flags = 0 + | memory_extra_flags::compensation_conv_s8s8 + | memory_extra_flags::scale_adjust; + want_wei_md.extra.compensation_mask = (1 << 0) + + (with_groups && !jcp.is_depthwise ? (1 << 1) : 0); + want_wei_md.extra.scale_adjust = + mayiuse(avx512_core_vnni) ? 1.f : 0.5f; + } + + if (weights_md.format_kind == format_kind::any) { + weights_md = want_wei_md; + return true; + } + + return weights_md == want_wei_md; + }; + + if (!set_or_check_wei_format()) + return status::unimplemented; + + format_tag_t dat_tag = utils::pick(ndims - 3, + format_tag::nwc, format_tag::nhwc); + + if (src_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md, dat_tag)); + jcp.src_tag = dat_tag; + } else { + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + } + if (jcp.src_tag != dat_tag) + return status::unimplemented; + + if (dst_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(dst_md, dat_tag)); + jcp.dst_tag = dat_tag; + } else { + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + } + if (jcp.dst_tag != dat_tag) + return status::unimplemented; + + if (jcp.with_bias) { + if (bias_d.format_kind() == format_kind::any) + CHECK(memory_desc_init_by_tag(bias_md, format_tag::x)); + } + + jcp.bia_dt = jcp.with_bias ? cd.bias_desc.data_type : data_type::undef; + jcp.dst_dt = cd.dst_desc.data_type; + + jcp.typesize_in = types::data_type_size(src_d.data_type()); + jcp.typesize_out = types::data_type_size(dst_d.data_type()); + jcp.typesize_bia = jcp.with_bias + ? types::data_type_size(bias_d.data_type()) + : 0; + + jcp.nb_ch = div_up(jcp.ngroups, jcp.ch_block); + jcp.nb_ic = jcp.ic / jcp.ic_block; + jcp.nb_oc = jcp.oc / jcp.oc_block; + + // Try to use 4 channel-groups at a time to avoid false sharing (depthwise) + int nb_ch_blocking = 4; + for ( /* init above */ ; nb_ch_blocking > 1; nb_ch_blocking--) + if (jcp.nb_ch % nb_ch_blocking == 0) + break; + jcp.nb_ch_blocking = jcp.is_depthwise ? nb_ch_blocking : 1; + + // If OC blocking is incommensurate with the number of OC blocks (general + // requirement for all convolutions), or if it results in an unrolling + // factor smaller than the left padding (special requirement for SSD:fc6), + // then search for a smaller OC blocking that satisfies both constraints. + auto is_oc_blocking_ok = [&](int block) { + int ur_w = nstl::min(jcp.ow, jcp.max_regs_ur / (block + 1)); + return jcp.nb_oc % block == 0 + && jcp.l_pad <= ur_w && jcp.ow % ur_w != 1; + }; + + // choose nb_oc work chunk size for distribution within threads + int max_threading_nb_oc_chunk = 4; + // Performance improvements for googlenet_v3 and resnet_50 with mb = 1; + // TODO: generalize this condition and rewrite it in appropriate manner + if (jcp.ver == ver_vnni && jcp.mb == 1 && jcp.kh == 3 && jcp.kw == 3 + && jcp.stride_w == 1 && jcp.ic % 64 == 0) + max_threading_nb_oc_chunk = 2; + jcp.nb_oc_blocking_thr_chunk = + nstl::min(max_threading_nb_oc_chunk, jcp.nb_oc); + for (; jcp.nb_oc_blocking_thr_chunk > 1; jcp.nb_oc_blocking_thr_chunk--) { + if (is_oc_blocking_ok(jcp.nb_oc_blocking_thr_chunk)) + break; + } + + // choose oc blocking for computational kernel + jcp.nb_oc_blocking = jcp.nb_oc_blocking_thr_chunk; + // Performance improvements for googlenet_v3 with mb = 1; + // TODO: generalize this condition and rewrite it in appropriate manner + const int size_treshold_for_nb_oc_blocking_reduction = 17; + if (jcp.mb == 1 && jcp.ow <= size_treshold_for_nb_oc_blocking_reduction + && jcp.stride_w == 1 + && !(jcp.kh == 1 && jcp.kw == 3) + && !(jcp.kh >= 7 && jcp.oc % 64 == 0)) { + const int max_nb_oc_blocking = 2; + jcp.nb_oc_blocking = nstl::min(max_nb_oc_blocking, jcp.nb_oc); + for (; jcp.nb_oc_blocking > 1; jcp.nb_oc_blocking--) + if (jcp.nb_oc_blocking_thr_chunk % jcp.nb_oc_blocking == 0 + && is_oc_blocking_ok(jcp.nb_oc_blocking)) + break; + } + + if (jcp.is_resrc_depthwise) + jcp.ur_w = (jcp.max_regs_ur - jcp.kw + jcp.stride_w) + / (jcp.nb_ch_blocking + jcp.stride_w); + else + jcp.ur_w + = jcp.max_regs_ur / (jcp.is_depthwise ? jcp.nb_ch_blocking + : jcp.nb_oc_blocking + 1); + if (jcp.ow < jcp.ur_w) + jcp.ur_w = jcp.ow; + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + + jcp.ow_block = jcp.ow; + int base_work_amount = jcp.mb * jcp.nb_ch * jcp.oh + * (jcp.nb_oc / jcp.nb_oc_blocking_thr_chunk); + float best_thr_eff + = (float)base_work_amount / rnd_up(base_work_amount, nthreads); + int max_nb_ow = div_up(jcp.ow, 2 * jcp.ur_w); + for (int nb_ow = 1; nb_ow <= max_nb_ow; nb_ow++) { + int ow_block + = nstl::min(rnd_up(div_up(jcp.ow, nb_ow), jcp.ur_w), jcp.ow); + if (ow_block < jcp.nb_oc_blocking_thr_chunk * jcp.oc_block + && best_thr_eff > 0.8f) + break; + if (div_up(jcp.ow, ow_block) != nb_ow) + continue; + auto work_amount = base_work_amount * nb_ow; + float thr_eff = (float)work_amount / rnd_up(work_amount, nthreads); + if (ow_block >= 2 * jcp.ur_w && thr_eff > 1.1f * best_thr_eff) { + jcp.ow_block = ow_block; + best_thr_eff = thr_eff; + } + if (best_thr_eff > 0.9f) + break; + } + jcp.nb_ow = div_up(jcp.ow, jcp.ow_block); + + bool args_ok = true + && jcp.oc % jcp.oc_block == 0 + && jcp.l_pad <= jcp.ur_w + && IMPLICATION(!jcp.is_1stconv, jcp.ic % jcp.ic_block == 0); + if (!args_ok) + return status::unimplemented; + + int r_pad_no_tail = nstl::max(0, (jcp.ow - jcp.ur_w_tail - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.iw + jcp.l_pad - 1)); + if (r_pad_no_tail > jcp.ur_w) + return status::unimplemented; + + pick_loop_order(jcp, nthreads); + + jcp.nb_ic_L2 = jcp.nb_ic; + + const auto &oscales = attr.output_scales_; + jcp.is_oc_scale = oscales.mask_ == 1 << 1; + + assert(IMPLICATION(!jcp.is_oc_scale, oscales.mask_ == 0)); + + jcp.wei_adj_scale = + (weights_d.extra().flags | memory_extra_flags::scale_adjust) + ? weights_d.extra().scale_adjust : 1.f; + + return status::success; +} + +void jit_avx512_core_x8s8s32x_fwd_kernel::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp, + const primitive_attr_t &attr) { + if (jcp.signed_input && jcp.ver != ver_vnni) { + dim_t count = nstl::max(attr.output_scales_.count_, (dim_t)jcp.ic_block); + scratchpad.book(key_conv_adjusted_scales, sizeof(float) * count); + } +} + +template struct _jit_avx512_core_x8s8s32x_fwd_kernel; +template struct _jit_avx512_core_x8s8s32x_fwd_kernel; +template struct _jit_avx512_core_x8s8s32x_fwd_kernel; +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.hpp new file mode 100644 index 000000000000..d8a05ad53eec --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_conv_kernel.hpp @@ -0,0 +1,239 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_X8S8S32X_CONV_KERNEL_HPP +#define CPU_JIT_AVX512_CORE_X8S8S32X_CONV_KERNEL_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct _jit_avx512_core_x8s8s32x_fwd_kernel : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(_jit_avx512_core_x8s8s32x_conv_fwd_ker_t) + + enum { STATE_FIRST_DST_LOAD = 0x1U }; + + _jit_avx512_core_x8s8s32x_fwd_kernel(jit_conv_conf_t ajcp, + const primitive_attr_t &attr) : jcp(ajcp), attr_(attr), + eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32( + this, jcp.eltwise); + + generate(); + jit_ker_ = (void (*)(jit_conv_call_s *))getCode(); + } + + ~_jit_avx512_core_x8s8s32x_fwd_kernel() { + delete eltwise_injector_; + } + + jit_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker_)(jit_conv_call_s *); + +private: + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + enum { + typesize = sizeof(float), + ker_reg_base_idx = 28, + ker_dw_reg_base_idx = 30, + }; + typedef enum { + no_last_block, + last_ic_block, + last_sp_block, + } ic_block_t; + + /* data regs */ + const Xbyak::Reg64 reg_ptr_scales = rax; + const Xbyak::Reg64 reg_inp = r8; + const Xbyak::Reg64 reg_ker = r9; + const Xbyak::Reg64 reg_out = r10; + const Xbyak::Reg64 aux_reg_inp = r11; + const Xbyak::Reg64 reg_ptr_sum_scale = r11; + const Xbyak::Reg64 aux_reg_ker = r12; + const Xbyak::Reg64 reg_compensation = r14; + /* counter regs */ + const Xbyak::Reg64 reg_bias_alpha = abi_not_param1; + const Xbyak::Reg64 reg_oi = rbx; + const Xbyak::Reg64 reg_bias = rdx; + const Xbyak::Reg64 reg_oc_blocks = rsi; + const Xbyak::Reg64 reg_owb = aux_reg_ker; + const Xbyak::Reg64 reg_scratch = reg_compensation; + const Xbyak::Reg64 reg_kj = reg_ptr_scales; + const Xbyak::Reg64 reg_overflow = reg_ptr_scales; + const Xbyak::Reg64 reg_icb = reg_bias; + + const Xbyak::Opmask ktail_mask = Xbyak::Opmask(2); + const Xbyak::Opmask kblend_mask = Xbyak::Opmask(3); + + const Vmm vmm_wei = Vmm(31); + /* used during bias section of store_output */ + const Vmm vmm_comp = Vmm(30); // only for signed input + const Vmm vmm_bias = Vmm(31); + /* used during post_op sum section of store_output */ + const Vmm vmm_prev_dst = Vmm(31); + /* used during write-out section of store_output */ + const Vmm vmm_zero = Vmm(31); + + /* used in compute_ker (but set during prepare_output) */ + const Vmm vmm_shift = vmm_comp; // only for signed input + /* used in compute_ker (but only for pre-VNNI machines) */ + const Vmm vmm_tmp = Vmm(28); // not used for depthwise + const Vmm vmm_one = Vmm(29); // set at start of kernel, not used for depthwise. + + /* registers use only for depthwise + groups are always blocked by 16(padded if needed), + hence use only Zmm registers */ + const Xbyak::Zmm zmm_wei = Xbyak::Zmm(31); + Xbyak::Zmm zmm_tmp; + Xbyak::Zmm zmm_src; + Xbyak::Zmm zmm_shifted_zero; + Xbyak::Zmm zmm_permute; + + Vmm vmm_out(int i_ur, int i_oc) { + int idx = i_ur + i_oc * jcp.ur_w; + assert(idx < (jcp.is_depthwise + ? ker_dw_reg_base_idx : ker_reg_base_idx)); + return Vmm(idx); + } + Xbyak::Zmm zmm_out(int i_ur, int i_oc) { + int idx = i_ur + i_oc * jcp.ur_w; + assert(idx < (jcp.is_depthwise + ? ker_dw_reg_base_idx : ker_reg_base_idx)); + return Xbyak::Zmm(idx); + } + Vmm vmm_inp(int i_ic, int nb_x_blocking) { + int idx = i_ic + nb_x_blocking * jcp.ur_w; + assert(idx < 31); + return Vmm(idx); + } + Xbyak::Zmm zmm_inp(int i_ic, int nb_x_blocking) { + int idx = i_ic + nb_x_blocking * jcp.ur_w; + assert(idx < 31); + return Xbyak::Zmm(idx); + } + Vmm vmm_bias_alpha() { + int nb_c_block = jcp.is_depthwise ? jcp.nb_ch_blocking : jcp.nb_oc_blocking; + return Vmm(nb_c_block * jcp.ur_w); + } + Xbyak::Xmm xmm_bias_alpha() { + int nb_c_block = jcp.is_depthwise ? jcp.nb_ch_blocking : jcp.nb_oc_blocking; + return Xbyak::Xmm(nb_c_block * jcp.ur_w); + } + int get_ow_start(int ki, int pad_l) { + return nstl::max(0, + utils::div_up(pad_l - ki * (jcp.dilate_w + 1), jcp.stride_w)); + } + int get_ow_end(int ur_w, int ki, int pad_r) { + return ur_w - nstl::max(0, utils::div_up(pad_r + - (jcp.kw - 1 - ki) + * (jcp.dilate_w + 1), + jcp.stride_w)); + } + + bool maybe_eltwise(int position); + void prepare_output(int ur_w); + void store_output(int ur_w, bool last_oc_block_flag); + void compute_ker_dw( + int ur_w, int pad_l, int pad_r, ic_block_t last_ic_block_flag, bool h_padded); + void compute_ker(int ur_w, int pad_l, int pad_r, + ic_block_t last_ic_block_flag, bool h_padded = false); + void compute_eltwise(int ur_w); + void kh_loop(int ur_w, int pad_l, int pad_r, ic_block_t last_ic_block_flag); + void icb_loop( + int ur_w, int pad_l, int pad_r, bool is_last_spatial_block); + void generate(); + void cvt2ps(data_type_t type_in, Vmm ymm_in, const Xbyak::Operand &op, + bool mask_flag); + const Vmm vmm_mask(const Vmm vmm_in, bool mask_flag, bool store = false); +}; + +struct jit_avx512_core_x8s8s32x_fwd_kernel { + + jit_avx512_core_x8s8s32x_fwd_kernel(jit_conv_conf_t ajcp, + const primitive_attr_t &attr) : + jit_ker(nullptr), + zmm_kernel_(nullptr), + ymm_kernel_(nullptr), + xmm_kernel_(nullptr) { + int ch_block = ajcp.is_depthwise ? ajcp.ch_block : ajcp.ic_block; + switch (ch_block) { + case 16: + zmm_kernel_ = + new _jit_avx512_core_x8s8s32x_fwd_kernel( + ajcp, attr); + jit_ker = zmm_kernel_->jit_ker_; + return; + case 8: + ymm_kernel_ = + new _jit_avx512_core_x8s8s32x_fwd_kernel( + ajcp, attr); + jit_ker = ymm_kernel_->jit_ker_; + return; + case 4: + xmm_kernel_ = + new _jit_avx512_core_x8s8s32x_fwd_kernel( + ajcp, attr); + jit_ker = xmm_kernel_->jit_ker_; + return; + default: + assert(!"invalid channel blocking"); + } + } + + ~jit_avx512_core_x8s8s32x_fwd_kernel() { + delete xmm_kernel_; + delete ymm_kernel_; + delete zmm_kernel_; + } + + static bool post_ops_ok(jit_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, + memory_desc_t &src_pd, + memory_desc_t &weights_pd, + memory_desc_t &dst_pd, + memory_desc_t &bias_pd, + const primitive_attr_t &attr, + int nthreads); + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp, const primitive_attr_t &attr); + + void (*jit_ker)(jit_conv_call_s *); + _jit_avx512_core_x8s8s32x_fwd_kernel *zmm_kernel_; + _jit_avx512_core_x8s8s32x_fwd_kernel *ymm_kernel_; + _jit_avx512_core_x8s8s32x_fwd_kernel *xmm_kernel_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.cpp new file mode 100644 index 000000000000..cdbf333d5eb0 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.cpp @@ -0,0 +1,423 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_avx512_core_x8s8s32x_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +using namespace nstl; + +using jit_conv_ker_t = void (*)(jit_conv_call_s *); + +#define wht_blk_off(d, g, ...) \ + (pd()->with_groups() \ + ? (d).blk_off((g), __VA_ARGS__) \ + : (d).blk_off(__VA_ARGS__)) + +template +void jit_avx512_core_x8s8s32x_convolution_fwd_t::execute_forward_1d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const size_t bia_dt_size = pd()->with_bias() + ? types::data_type_size(pd()->desc()->bias_desc.data_type) : 0; + + const auto &jcp = pd()->jcp_; + assert(jcp.nb_oc % jcp.nb_oc_blocking == 0); + assert(jcp.nb_ch % jcp.nb_ch_blocking == 0); + + const float *oscales = pd()->attr()->output_scales_.scales_; + if (jcp.signed_input && jcp.ver != ver_vnni) { + auto local_scales = scratchpad(ctx).template get( + key_conv_adjusted_scales); + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / pd()->jcp_.wei_adj_scale; + if (count == 1) { + utils::array_set(local_scales, oscales[0] * factor, 16); + } else { + for (size_t c = 0; c < count; c++) + local_scales[c] = oscales[c] * factor; + } + oscales = local_scales; + } + + size_t offset = weights_d.size() - weights_d.additional_buffer_size(); + auto w = const_cast(weights); + int32_t* compensation = (jcp.signed_input) + ? reinterpret_cast(&w[offset]) : 0; + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int nb_groups = jcp.nb_ch / jcp.nb_ch_blocking; + int group_block = jcp.ch_block; + int work_amount = jcp.mb * nb_groups * oc_chunks * jcp.nb_ow; + + parallel(0, [&](const int ithr, const int nthr) { + + int start{ 0 }, end{ 0 }; + balance211(work_amount, nthr, ithr, start, end); + + auto p = jit_conv_call_s(); + + int n{ 0 }, gg{ 0 }, occ{ 0 }, owb{ 0 }; + switch (jcp.loop_order) { + case loop_cwgn: + nd_iterator_init(start, occ, oc_chunks, owb, jcp.nb_ow, gg, + nb_groups, n, jcp.mb); + break; + case loop_gncw: + nd_iterator_init(start, gg, nb_groups, n, jcp.mb, occ, oc_chunks, + owb, jcp.nb_ow); + break; + case loop_ngcw: + nd_iterator_init(start, n, jcp.mb, gg, nb_groups, occ, oc_chunks, + owb, jcp.nb_ow); + break; + case loop_nwcg: + nd_iterator_init(start, n, jcp.mb, owb, jcp.nb_ow, occ, oc_chunks, + gg, nb_groups); + break; + default: assert(!"unsupported loop order"); + } + while (start < end) { + int ocb = occ * jcp.nb_oc_blocking; + int gb = gg * jcp.nb_ch_blocking; + int g = gb * group_block; + int g_oc = (g * jcp.nb_oc + ocb) * jcp.oc_block; + int g_ic = g * jcp.nb_ic * jcp.ic_block; + int ow_s = owb * jcp.ow_block; + int iw_s = ow_s * jcp.stride_w; + + p.bias = bias ? bias + (bias_d.blk_off(g_oc) * bia_dt_size) : 0; + p.compensation = (jcp.signed_input) ? compensation + g_oc : 0; + p.dst = dst + dst_d.blk_off(n, g_oc, ow_s); + p.src = src + src_d.blk_off(n, g_ic, iw_s); + p.filt = weights + wht_blk_off(weights_d, gb, ocb, 0); + p.scales = &oscales[jcp.is_oc_scale * g_oc]; + p.oc_blocks = jcp.is_depthwise ? gb : ocb; + p.kh_padding = jcp.kh; + p.t_overflow = 0; + p.b_overflow = 0; + p.owb = owb; + + kernel_->jit_ker(&p); + + ++start; + switch (jcp.loop_order) { + case loop_cwgn: + nd_iterator_step(occ, oc_chunks, owb, jcp.nb_ow, gg, nb_groups, + n, jcp.mb); + break; + case loop_gncw: + nd_iterator_step(gg, nb_groups, n, jcp.mb, occ, oc_chunks, owb, + jcp.nb_ow); + break; + case loop_ngcw: + nd_iterator_step(n, jcp.mb, gg, nb_groups, occ, oc_chunks, owb, + jcp.nb_ow); + break; + case loop_nwcg: + nd_iterator_step(n, jcp.mb, owb, jcp.nb_ow, occ, oc_chunks, gg, + nb_groups); + break; + default: assert(!"unsupported loop order"); + } + } + }); +} + +template +void jit_avx512_core_x8s8s32x_convolution_fwd_t::execute_forward_2d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const size_t bia_dt_size = pd()->with_bias() + ? types::data_type_size(pd()->desc()->bias_desc.data_type) : 0; + + const auto &jcp = pd()->jcp_; + assert(jcp.ch_block == 1); + assert(jcp.nb_ch_blocking == 1); + assert(jcp.nb_oc % jcp.nb_oc_blocking == 0); + assert(jcp.nb_ch % jcp.nb_ch_blocking == 0); + + const float *oscales = pd()->attr()->output_scales_.scales_; + if (jcp.signed_input && jcp.ver != ver_vnni) { + auto local_scales = scratchpad(ctx).template get( + key_conv_adjusted_scales); + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / pd()->jcp_.wei_adj_scale; + if (count == 1) { + utils::array_set(local_scales, oscales[0] * factor, 16); + } else { + for (size_t c = 0; c < count; c++) + local_scales[c] = oscales[c] * factor; + } + oscales = local_scales; + } + + size_t offset = weights_d.size() - weights_d.additional_buffer_size(); + auto w = const_cast(weights); + int32_t* compensation = (jcp.signed_input) + ? reinterpret_cast(&w[offset]) : 0; + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking_thr_chunk; + int nb_groups = jcp.nb_ch; + int work_amount = jcp.mb * nb_groups * oc_chunks * jcp.oh * jcp.nb_ow; + + parallel(0, [&](const int ithr, const int nthr) { + + int start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + auto p = jit_conv_call_s(); + + size_t src_h_stride = src_d.blk_off(0, 0, 1); + size_t dst_h_stride = dst_d.blk_off(0, 0, 1); + size_t wht_h_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + + int n{ 0 }, g{ 0 }, occ{ 0 }, oh_s{ 0 }, owb{ 0 }; + switch (jcp.loop_order) { + case loop_cwgn: + nd_iterator_init(start, occ, oc_chunks, owb, jcp.nb_ow, g, + nb_groups, n, jcp.mb, oh_s, jcp.oh); + break; + case loop_ngcw: + nd_iterator_init(start, n, jcp.mb, g, nb_groups, occ, oc_chunks, + owb, jcp.nb_ow, oh_s, jcp.oh); + break; + case loop_nhwcg: + nd_iterator_init(start, n, jcp.mb, oh_s, jcp.oh, owb, jcp.nb_ow, + occ, oc_chunks, g, nb_groups); + break; + default: assert(!"unsupported loop order"); + } + while (start < end) { + for (int occ1 = 0; occ1 < jcp.nb_oc_blocking_thr_chunk; + occ1 += jcp.nb_oc_blocking) { + int ocb = occ * jcp.nb_oc_blocking_thr_chunk + occ1; + int g_oc = (g * jcp.nb_oc + ocb) * jcp.oc_block; + + int g_ic = g * jcp.nb_ic * jcp.ic_block; + + int work_rem = end - start; + int ih_s = -jcp.t_pad + oh_s * jcp.stride_h; + int oh_e = oh_s + work_rem > jcp.oh ? jcp.oh : oh_s + work_rem; + if (jcp.loop_order == loop_nhwcg) + oh_e = oh_s + 1; // step instead + int ow_s = owb * jcp.ow_block; + int iw_s = ow_s * jcp.stride_w; + + auto bias_w = bias + ? bias + (bias_d.blk_off(g_oc) * bia_dt_size) + : 0; + int32_t *compensation_w = (jcp.signed_input) + ? compensation + g_oc : 0; + + auto dst_w = dst + dst_d.blk_off(n, g_oc, oh_s, ow_s); + auto src_w = src + src_d.blk_off(n, g_ic, ih_s, iw_s); + auto wht_w = weights + wht_blk_off(weights_d, g, ocb, 0); + + auto scales = &oscales[jcp.is_oc_scale * g_oc]; + + for (int oj = oh_s, ij = ih_s; oj < oh_e; + ++oj, ij += jcp.stride_h) { + int dilate_h = jcp.dilate_h + 1; + int i_t_overflow = nstl::min(jcp.kh, + div_up(max(0, -ij), dilate_h)); + int i_b_overflow = nstl::min(jcp.kh, div_up( + max(0, ij - jcp.ih + (jcp.kh - 1) * dilate_h + 1), + dilate_h)); + int kh_padding = nstl::max(0, + jcp.kh - i_t_overflow - i_b_overflow); + + size_t wei_stride = (!jcp.signed_input) + ? i_t_overflow * wht_h_stride : 0; + p.src = src_w + i_t_overflow * dilate_h * src_h_stride; + p.dst = dst_w; + p.filt = wht_w + wei_stride; + p.bias = bias_w; + p.compensation = compensation_w; + p.oc_blocks = ocb; + p.kh_padding = kh_padding; + p.scales = scales; + p.t_overflow = i_t_overflow; + p.b_overflow = i_b_overflow; + p.owb = owb; + + kernel_->jit_ker(&p); + src_w += src_h_stride * jcp.stride_h; + dst_w += dst_h_stride; + } + } + switch (jcp.loop_order) { + case loop_cwgn: + nd_iterator_jump(start, end, occ, oc_chunks, owb, jcp.nb_ow, g, + nb_groups, n, jcp.mb, oh_s, jcp.oh); + break; + case loop_ngcw: + nd_iterator_jump(start, end, n, jcp.mb, g, nb_groups, occ, + oc_chunks, owb, jcp.nb_ow, oh_s, jcp.oh); + break; + case loop_nhwcg: + ++start; + nd_iterator_step(n, jcp.mb, oh_s, jcp.oh, owb, jcp.nb_ow, occ, + oc_chunks, g, nb_groups); + break; + default: assert(!"unsupported loop order"); + } + } + }); +} + +template +void jit_avx512_core_x8s8s32x_convolution_fwd_t::execute_forward_2d_dw(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const size_t bia_dt_size = pd()->with_bias() + ? types::data_type_size(pd()->desc()->bias_desc.data_type) : 0; + + const auto &jcp = pd()->jcp_; + assert(jcp.ic_block == 1); + assert(jcp.oc_block == 1); + assert(jcp.nb_ic == 1); + assert(jcp.nb_oc == 1); + assert(jcp.nb_oc_blocking == 1); + assert(jcp.nb_ch % jcp.nb_ch_blocking == 0); + + const float *oscales = pd()->attr()->output_scales_.scales_; + if (jcp.signed_input && jcp.ver != ver_vnni) { + auto local_scales = scratchpad(ctx).template get( + key_conv_adjusted_scales); + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / pd()->jcp_.wei_adj_scale; + if (count == 1) { + utils::array_set(local_scales, oscales[0] * factor, 16); + } else { + for (size_t c = 0; c < count; c++) + local_scales[c] = oscales[c] * factor; + } + oscales = local_scales; + } + + size_t offset = weights_d.size() - weights_d.additional_buffer_size(); + auto w = const_cast(weights); + int32_t* compensation = (jcp.signed_input) + ? reinterpret_cast(&w[offset]) : 0; + int nb_groups = jcp.nb_ch / jcp.nb_ch_blocking; + int group_block = jcp.ch_block; + + parallel_nd(jcp.mb, jcp.oh, jcp.nb_ow, nb_groups, + [&](int n, int oh_s, int owb, int gg) { + + auto p = jit_conv_call_s(); + + size_t src_h_stride = src_d.blk_off(0, 0, 1); + size_t wht_h_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + + int gb = gg * jcp.nb_ch_blocking; + int g = gb * group_block; + + int ih_s = -jcp.t_pad + oh_s * jcp.stride_h; + int ow_s = owb * jcp.ow_block; + int iw_s = ow_s * jcp.stride_w; + + auto bias_w = bias ? bias + (bias_d.blk_off(g) * bia_dt_size) : 0; + int32_t *compensation_w = jcp.signed_input ? compensation + g : 0; + + auto dst_w = dst + dst_d.blk_off(n, g, oh_s, ow_s); + auto src_w = src + src_d.blk_off(n, g, ih_s, iw_s); + auto wht_w = weights + wht_blk_off(weights_d, gb, 0); + + auto scales = &oscales[jcp.is_oc_scale * g]; + + int dilate_h = jcp.dilate_h + 1; + int i_t_overflow = nstl::min(jcp.kh, div_up(max(0, -ih_s), dilate_h)); + int i_b_overflow = nstl::min(jcp.kh, + div_up(max(0, ih_s - jcp.ih + (jcp.kh - 1) * dilate_h + 1), + dilate_h)); + int kh_padding = nstl::max(0, jcp.kh - i_t_overflow - i_b_overflow); + + size_t wei_stride = jcp.signed_input ? 0 : i_t_overflow * wht_h_stride; + p.src = src_w + i_t_overflow * dilate_h * src_h_stride; + p.dst = dst_w; + p.filt = wht_w + wei_stride; + p.bias = bias_w; + p.compensation = compensation_w; + p.oc_blocks = gb; + p.kh_padding = kh_padding; + p.scales = scales; + p.t_overflow = i_t_overflow; + p.b_overflow = i_b_overflow; + p.owb = owb; + + kernel_->jit_ker(&p); + }); +} + +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::s8, data_type::u8>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::u8, data_type::u8>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::s8, data_type::s8>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::u8, data_type::s8>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::s8, data_type::s32>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::u8, data_type::s32>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::s8, data_type::f32>; +template struct jit_avx512_core_x8s8s32x_convolution_fwd_t< + data_type::u8, data_type::f32>; +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.hpp new file mode 100644 index 000000000000..203ebdf94207 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_convolution.hpp @@ -0,0 +1,115 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_X8S8S32X_CONVOLUTION_HPP +#define CPU_JIT_AVX512_CORE_X8S8S32X_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_avx512_core_x8s8s32x_conv_kernel.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_avx512_core_x8s8s32x_convolution_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_int8:", avx512_core, ""), + jit_avx512_core_x8s8s32x_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, data_type::s8, data_type::undef, + dst_type, data_type::s32) + && IMPLICATION(with_bias(), utils::one_of(bias_md_.data_type, + data_type::f32, data_type::s32, data_type::s8, + data_type::u8)) + && !has_zero_dim_memory(); + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_core_x8s8s32x_fwd_kernel::init_conf( + jcp_, *desc(), src_md_, weights_md_, dst_md_, bias_md_, + *attr(), mkldnn_get_max_threads()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_core_x8s8s32x_fwd_kernel::init_scratchpad(scratchpad, + jcp_, *attr()); + + return status; + } + + jit_conv_conf_t jcp_; + }; + + jit_avx512_core_x8s8s32x_convolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + { + kernel_ = new jit_avx512_core_x8s8s32x_fwd_kernel(pd()->jcp_, + *pd()->attr()); + } + + ~jit_avx512_core_x8s8s32x_convolution_fwd_t() { delete kernel_; } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override + { + const auto &_pd = pd(); + if (_pd->ndims() == 3) + execute_forward_1d(ctx); + else if (_pd->jcp_.is_depthwise) + execute_forward_2d_dw(ctx); + else + execute_forward_2d(ctx); + return status::success; + } + +private: + void execute_forward_1d(const exec_ctx_t &ctx) const; + void execute_forward_2d(const exec_ctx_t &ctx) const; + void execute_forward_2d_dw(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_avx512_core_x8s8s32x_fwd_kernel *kernel_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.cpp new file mode 100644 index 000000000000..142af1f54128 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.cpp @@ -0,0 +1,1034 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_avx512_core_x8s8s32x_deconvolution.hpp" + +#define GET_OFF(field) offsetof(jit_deconv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; +using namespace Xbyak; + +using namespace nstl; + +#define wht_blk_off(d, g, ...) \ + (pd()->with_groups() ? (d).blk_off((g), __VA_ARGS__) : \ + (d).blk_off(__VA_ARGS__)) + +status_t jit_avx512_core_x8s8s32x_deconv_fwd_kernel::init_conf( + jit_conv_conf_t &jcp, const deconvolution_desc_t &cd, + memory_desc_t &src_md, memory_desc_t &weights_md, + memory_desc_t &dst_md, const bool with_bias, + memory_desc_t &bias_md, const primitive_attr_t &attr) { + const memory_desc_wrapper src_d(&src_md); + const memory_desc_wrapper dst_d(&dst_md); + const memory_desc_wrapper weights_d(&weights_md); + const memory_desc_wrapper bias_d(&bias_md); + + if (!(mayiuse(avx512_core) + && one_of(src_d.data_type(), data_type::u8, data_type::s8) + && weights_d.data_type() == data_type::s8 + && one_of(dst_d.data_type(), data_type::f32, data_type::s32, + data_type::s8, data_type::u8))) + return status::unimplemented; + + jcp = zero(); + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + jcp.signed_input = src_d.data_type() == data_type::s8; + const int ndims = jcp.ndims = dst_d.ndims(); + const bool is_1d = ndims == 3; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + jcp.oc_without_padding = dst_d.dims()[1] / jcp.ngroups; + jcp.ic_without_padding = src_d.dims()[1] / jcp.ngroups; + jcp.is_depthwise = true && with_groups + && utils::everyone_is(1, jcp.ic_without_padding, + jcp.oc_without_padding); + + /* TODO: future work, on hold until depthwise specialized kernel is + * implemented. */ + if (jcp.is_depthwise && jcp.signed_input) + return status::unimplemented; + + format_tag_t dat_tag = utils::pick(ndims - 3, + format_tag::nwc, format_tag::nhwc); + + if (src_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(src_md, dat_tag)); + jcp.src_tag = dat_tag; + } else { + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + } + if (jcp.src_tag != dat_tag) + return status::unimplemented; + + if (dst_d.format_kind() == format_kind::any) { + CHECK(memory_desc_init_by_tag(dst_md, dat_tag)); + jcp.dst_tag = dat_tag; + } else { + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + } + if (jcp.dst_tag != dat_tag) + return status::unimplemented; + + auto set_or_check_wei_format = [&]() { + using namespace format_tag; + + format_tag_t wei_tag = is_1d + ? (jcp.is_depthwise + ? Goiw16g : (with_groups ? gOIw4i16o4i : OIw4i16o4i)) + : (jcp.is_depthwise + ? Goihw16g : (with_groups ? gOIhw4i16o4i : OIhw4i16o4i)); + + memory_desc_t want_wei_md = weights_md; + memory_desc_init_by_tag(want_wei_md, wei_tag); + if (jcp.signed_input && !jcp.is_depthwise) { + want_wei_md.extra.flags = 0 + | memory_extra_flags::compensation_conv_s8s8 + | memory_extra_flags::scale_adjust; + want_wei_md.extra.compensation_mask = (1 << 0) + + (with_groups && !jcp.is_depthwise ? (1 << 1) : 0); + want_wei_md.extra.scale_adjust = + mayiuse(avx512_core_vnni) ? 1.f : 0.5f; + } + + if (weights_md.format_kind == format_kind::any) { + weights_md = want_wei_md; + return true; + } + + return weights_md == want_wei_md; + }; + + if (!set_or_check_wei_format()) + return status::unimplemented; + + jcp.with_bias = with_bias; + if (jcp.with_bias) { + if (bias_d.format_kind() == format_kind::any) + CHECK(memory_desc_init_by_tag(bias_md, format_tag::x)); + } + + jcp.prop_kind = cd.prop_kind; + jcp.mb = src_d.dims()[0]; + jcp.ih = is_1d ? 1 : src_d.dims()[ndims - 2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.oh = is_1d ? 1 : dst_d.dims()[ndims - 2]; + jcp.ow = dst_d.dims()[ndims - 1]; + jcp.kh = is_1d ? 1 : weights_d.dims()[with_groups + ndims - 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + jcp.t_pad = is_1d ? 0 : cd.padding[0][ndims - 4]; + jcp.l_pad = cd.padding[0][ndims - 3]; + jcp.stride_h = is_1d ? 1 : cd.strides[ndims - 4]; + jcp.stride_w = cd.strides[ndims - 3]; + + if (jcp.is_depthwise) { + jcp.ch_block = 16; + jcp.oc_block = 1; + jcp.ic_block = 1; + } else { + jcp.ch_block = 1; + jcp.oc_block = 16; + jcp.ic_block = 16; + + if (jcp.ngroups == 1) { + jcp.oc = utils::rnd_up(jcp.oc_without_padding, jcp.oc_block); + jcp.ic = utils::rnd_up(jcp.ic_without_padding, jcp.ic_block); + } + if (jcp.ic % jcp.ic_block != 0) + return status::unimplemented; + } + + jcp.dilate_h = is_1d ? 0 : cd.dilates[ndims - 4]; + jcp.dilate_w = cd.dilates[ndims - 3]; + + if (!IMPLICATION(jcp.dilate_h, jcp.stride_h == 1) + || !IMPLICATION(jcp.dilate_w, jcp.stride_w == 1)) + return status::unimplemented; + + /* padding: bottom and right */ + jcp.b_pad = (jcp.ih - 1) * jcp.stride_h + (jcp.kh - 1) * (jcp.dilate_h + 1) + - (jcp.oh + jcp.t_pad - 1); + jcp.r_pad = (jcp.iw - 1) * jcp.stride_w + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.ow + jcp.l_pad - 1); + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + jcp.ver = ver_avx512_core; + if (mayiuse(avx512_core_vnni)) + jcp.ver = ver_vnni; + const auto &oscales = attr.output_scales_; + jcp.is_oc_scale = oscales.mask_ == 1 << 1; + + assert(IMPLICATION(!jcp.is_oc_scale, oscales.mask_ == 0)); + + jcp.dst_dt = dst_d.data_type(); + jcp.bia_dt = jcp.with_bias ? bias_d.data_type() : data_type::undef; + jcp.typesize_bia + = jcp.with_bias ? types::data_type_size(bias_d.data_type()) : 0; + jcp.typesize_in = types::data_type_size(src_d.data_type()); + jcp.typesize_out = types::data_type_size(dst_d.data_type()); + + jcp.nb_ch = div_up(jcp.ngroups, jcp.ch_block); + jcp.nb_oc = jcp.oc / jcp.oc_block; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + /* kernel blocking params */ + const int regs = jcp.ver == ver_vnni ? 30 : 28; + jcp.nb_oc_blocking = nstl::min(4, jcp.nb_oc); + for (; jcp.nb_oc_blocking > 1; jcp.nb_oc_blocking--) + if (jcp.nb_oc % jcp.nb_oc_blocking == 0 + && jcp.l_pad <= regs / (jcp.nb_oc_blocking + 1)) + break; + + jcp.ur_w = regs / (jcp.nb_oc_blocking + 1); + int l_overflow = max( + 0, ((jcp.kw - 1) * (jcp.dilate_w + 1) - jcp.l_pad) / jcp.stride_w); + + if (jcp.ow < jcp.ur_w) { + jcp.ur_w = jcp.ow; + jcp.ur_w_tail = 0; + } else { + for (; jcp.ur_w >= 1; jcp.ur_w--) { + /* ur_w should be multiple of stride_w in order + to simplify logic for get_ow_start and get_ow_end */ + bool is_multiple_of_stride = jcp.ur_w % jcp.stride_w == 0; + + /* boundary conditions: + These conditions ensure all elements close to boundary + are computed in a single call of compute loop */ + bool left_boundary_covered = jcp.ur_w >= l_overflow * jcp.stride_w; + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + int r_overflow_no_tail + = max(0, ((jcp.kw - 1) * (jcp.dilate_w + 1) + - max(0, jcp.r_pad) - jcp.ur_w_tail) + / jcp.stride_w); + bool right_boundary_covered + = jcp.ur_w >= r_overflow_no_tail * jcp.stride_w; + + if (is_multiple_of_stride && left_boundary_covered + && right_boundary_covered) + break; + else if (jcp.ur_w == 1) + /* The boundary conditions above are also important + to maintain simplicity of calls to icb_loop, + if those conditions are not satisfied, + then special cases will need to be added + to use correct l_overflow/r_overflow values + when different iterations of compute loop + work on the locations close to boundary. + So to keep code simple, return unimplemented + for extreme case when a good ur_w cannot be found. + */ + return status::unimplemented; + } + } + + jcp.wei_adj_scale = + (weights_d.extra().flags | memory_extra_flags::scale_adjust) + ? weights_d.extra().scale_adjust : 1.f; + + jcp.loop_order = jcp.ngroups > 1 ? loop_ngc : loop_cgn; + return status::success; +} + +bool jit_avx512_core_x8s8s32x_deconv_fwd_kernel::maybe_eltwise(int position) { + using namespace primitive_kind; + const auto &p = attr_.post_ops_; + + if (position == 0) { + /* eltwise before sum */ + return p.contain(eltwise, 0); + } else if (position == 1) { + /* eltwise after sum */ + return p.contain(sum, 0) && p.contain(eltwise, 1); + } + return false; +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::compute_eltwise(int ur_w) { + int nb_oc_block + = jcp.is_depthwise ? jcp.nb_ch_blocking : jcp.nb_oc_blocking; + eltwise_injector_->compute_vector_range(0, nb_oc_block * ur_w); +} + +bool jit_avx512_core_x8s8s32x_deconv_fwd_kernel::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + using namespace primitive_kind; + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + + switch (p.len_) { + case 0: return true; + case 1: return is_eltwise(0) || p.contain(sum, 0); + case 2: + return (p.contain(sum, 0) && is_eltwise(1)) + || (p.contain(sum, 1) && is_eltwise(0)); + default: return false; + } + + return false; +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp, + const primitive_attr_t &attr) { + if (jcp.signed_input && jcp.ver != ver_vnni) { + dim_t count = nstl::max(attr.output_scales_.count_, 16); + scratchpad.book(key_conv_adjusted_scales, sizeof(float) * count); + } +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::compute_ker(int ur_w, + int l_overflow, int r_overflow, ker_block_t last_ic_block_flag, + bool h_padded) { + + const int ch_block_all = jcp.ch_block * jcp.ic_block * jcp.oc_block; + const int ur_w_stride = jcp.signed_input ? 1 : jcp.stride_w; + + auto src_offset = [=](int oj, int icb, int ki) { + return jcp.typesize_in + * (((oj + jcp.l_pad - ki * (jcp.dilate_w + 1)) / jcp.stride_w) + * jcp.ngroups * jcp.ic_without_padding + + icb * 4); + }; + + auto kernel_offset = [=](int ocb, int icb, int ki) { + return jcp.typesize_in + * (ocb * jcp.nb_ic * jcp.kh * jcp.kw * ch_block_all + + icb * jcp.oc_block * jcp.ic_block / 4 + + ki * ch_block_all); + }; + + auto compute = [=](zmm_t vreg_acc, zmm_t vreg_wei, zmm_t vreg_src) { + if (jcp.ver == ver_vnni) { + vpdpbusd(vreg_acc, vreg_src, vreg_wei); + } else if (jcp.is_depthwise) { + vpmulld(zmm_tmp, vreg_src, vreg_wei); + vpaddd(vreg_acc, vreg_acc, zmm_tmp); + } else { + vpmaddubsw(zmm_tmp, vreg_src, vreg_wei); + vpmaddwd(zmm_tmp, zmm_tmp, zmm_one); + vpaddd(vreg_acc, vreg_acc, zmm_tmp); + } + }; + + for (int ki = 0; ki < jcp.kw; ki++) { + + int jj_start = get_ow_start(ki, l_overflow); + int jj_end = get_ow_end(ur_w, ki, r_overflow); + + int _start = (jcp.signed_input) ? 0 : jj_start; + int _end = (jcp.signed_input) ? ur_w : jj_end; + + int tail_size = jcp.ic_without_padding % 4; + int n_ic_blocks = jcp.is_depthwise ? + 1 : + (last_ic_block_flag & ~no_last_block ? + div_up(jcp.ic_without_padding % jcp.ic_block, + 4) : + jcp.ic_block / 4); + + for (int icb1 = 0; icb1 < n_ic_blocks; icb1++) { + if (h_padded == true) { + /* fill padded area with shifted values */ + Zmm inp = zmm_inp(0, jcp.nb_oc_blocking); + vpxord(inp, inp, inp); + vpsubb(inp, inp, zmm_shift); + } else { + + for (int jj = _start; jj < _end; jj += ur_w_stride) { + + int aux_src_off = src_offset(jj, icb1, ki); + + if (jj >= jj_start && jj < jj_end + && ((jj + jcp.l_pad - ki) % jcp.stride_w == 0)) { + if (jcp.is_depthwise) { + vpmovzxbd(zmm_inp(jj, jcp.nb_oc_blocking), + EVEX_compress_addr( + aux_reg_src, aux_src_off)); + } else if ((last_ic_block_flag & last_sp_block) + && tail_size != 0 && icb1 == n_ic_blocks - 1) { + xmm_t xmm_tmp = xmm_t( + zmm_inp(jj, jcp.nb_oc_blocking).getIdx()); + for (int r = 0; r < tail_size; ++r) + vpinsrb(xmm_tmp, xmm_tmp, + ptr[aux_reg_src + aux_src_off + r], r); + vpbroadcastd( + zmm_inp(jj, jcp.nb_oc_blocking), xmm_tmp); + } else { + vpbroadcastd(zmm_inp(jj, jcp.nb_oc_blocking), + EVEX_compress_addr( + aux_reg_src, aux_src_off)); + } + if (jcp.signed_input) + vpsubb(zmm_inp(jj, jcp.nb_oc_blocking), + zmm_inp(jj, jcp.nb_oc_blocking), zmm_shift); + } else { + /* fill padded area with shifted values */ + if (jcp.signed_input) { + Zmm inp = zmm_inp(jj, jcp.nb_oc_blocking); + vpxord(inp, inp, inp); + vpsubb(inp, inp, zmm_shift); + } + } + } + } + for (int ocb = 0; ocb < jcp.nb_oc_blocking; ocb++) { + int aux_filt_off = kernel_offset(ocb, icb1, ki); + + if (_end - _start > 0) { + if (jcp.is_depthwise) + vpmovsxbd(zmm_wei, + EVEX_compress_addr(aux_reg_filt, aux_filt_off)); + else + vmovups(zmm_wei, + EVEX_compress_addr(aux_reg_filt, aux_filt_off)); + } + for (int jj = _start; jj < _end; jj += ur_w_stride) { + Zmm inp = (h_padded == true) ? + zmm_inp(0, jcp.nb_oc_blocking) : + zmm_inp(jj, jcp.nb_oc_blocking); + compute(zmm_out(jj, ocb), zmm_wei, inp); + } + } + } + } +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::kh_loop(int ur_w, + int l_overflow, int r_overflow, ker_block_t last_ic_block_flag) { + + int ch_block_all = jcp.ch_block * jcp.ic_block * jcp.oc_block; + int shift_src_ih = jcp.typesize_in * (jcp.dilate_h + 1) * jcp.iw + * jcp.ngroups * jcp.ic_without_padding; + const int stride_h = jcp.signed_input ? 1 : jcp.stride_h; + int shift_filt_kh = jcp.typesize_in * jcp.kw * ch_block_all * stride_h; + + Label kh_loop_label, skip_kh_loop; + Label t_overflow_label, no_t_overflow_label, b_overflow_label, + no_b_overflow_label; + + mov(aux_reg_src, reg_src); + mov(aux_reg_filt, reg_filt); + + if (jcp.signed_input && jcp.ndims > 3) { + /* Weights are transposed, so first compute 'bottom' padding. */ + mov(reg_overflow, ptr[param1 + GET_OFF(b_overflow)]); + cmp(reg_overflow, 0); + je(no_b_overflow_label, T_NEAR); + L(b_overflow_label); { + compute_ker(ur_w, 0, 0, last_ic_block_flag, true); + + add(aux_reg_filt, shift_filt_kh); + dec(reg_overflow); + cmp(reg_overflow, 0); + jg(b_overflow_label, T_NEAR); + } + L(no_b_overflow_label); + } + + mov(reg_kh, ptr[param1 + GET_OFF(kh_padding)]); + + if (jcp.signed_input || ((!jcp.signed_input) + && ((min(jcp.t_pad, jcp.b_pad) < 0) + || ((jcp.kh - 1) * (jcp.dilate_h + 1) + < nstl::max(jcp.t_pad, jcp.b_pad))))) { + cmp(reg_kh, 0); + je(skip_kh_loop, T_NEAR); + } + + L(kh_loop_label); { + compute_ker(ur_w, l_overflow, r_overflow, last_ic_block_flag, false); + sub(aux_reg_src, shift_src_ih); + add(aux_reg_filt, shift_filt_kh); + dec(reg_kh); + + /* Insert weight compensation in stride 'holes' */ + if (jcp.signed_input && jcp.stride_h > 1) { + Label kh_comp_loop; + + cmp(reg_kh, 0); + je(skip_kh_loop, T_NEAR); + mov(reg_comp_strides, jcp.stride_h - 1); + L(kh_comp_loop); + { + compute_ker( + ur_w, 0, 0, last_ic_block_flag, true); + add(aux_reg_filt, shift_filt_kh); + dec(reg_comp_strides); + cmp(reg_comp_strides, 0); + jg(kh_comp_loop, T_NEAR); + } + } + cmp(reg_kh, 0); + jg(kh_loop_label, T_NEAR); + } + L(skip_kh_loop); + if (jcp.signed_input && jcp.ndims > 3) { + mov(reg_overflow, ptr[param1 + GET_OFF(t_overflow)]); + cmp(reg_overflow, 0); + je(no_t_overflow_label, T_NEAR); + L(t_overflow_label); { + compute_ker(ur_w, 0, 0, last_ic_block_flag, true); + + add(aux_reg_filt, shift_filt_kh); + dec(reg_overflow); + cmp(reg_overflow, 0); + jg(t_overflow_label, T_NEAR); + } + L(no_t_overflow_label); + } +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::prepare_output(int ur_w) { + for (int ocb = 0; ocb < jcp.nb_oc_blocking; ocb++) { + for (int ur = 0; ur < ur_w; ur++) { + zmm_t zmm = zmm_out(ur, ocb); + vpxord(zmm, zmm, zmm); + } + } + if (jcp.signed_input) { + xor_(reg_scratch, reg_scratch); + Reg8 _t8 = reg_scratch.cvt8(); + mov(_t8, (int8_t)-128); + vpbroadcastb(zmm_shift, _t8); + } +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::cvt2ps( + data_type_t type_in, zmm_t zmm_in, const Operand &op, bool mask_flag) { + zmm_t zmm = mask_flag ? zmm_in | ktail_mask | T_z : zmm_in; + switch (type_in) { + case data_type::f32: + case data_type::s32: vmovups(zmm, op); break; + case data_type::s8: vpmovsxbd(zmm, op); break; + case data_type::u8: vpmovzxbd(zmm, op); break; + default: assert(!"unsupported data type"); + } + if (type_in != data_type::f32) + vcvtdq2ps(zmm_in, zmm_in); +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::store_output( + int ur_w, bool last_oc_block) { + mov(reg_bias, ptr[param1 + GET_OFF(bias)]); + mov(reg_ptr_scales, ptr[param1 + GET_OFF(scales)]); + + if (jcp.signed_input) + mov(reg_compensation, ptr[param1 + GET_OFF(compensation)]); + + const auto &p = attr_.post_ops_; + const int sum_idx = p.find(primitive_kind::sum); + const float *p_sum_scale + = (sum_idx != -1) ? &p.entry_[sum_idx].sum.scale : nullptr; + if (p_sum_scale && *p_sum_scale != 1.f) + mov(reg_ptr_sum_scale, (size_t)p_sum_scale); + + if (jcp.with_bias && jcp.signed_input && jcp.ver != ver_vnni) { + mov(reg_bias_alpha, float2int(jcp.wei_adj_scale)); + vmovq(xmm_bias_alpha(), reg_bias_alpha); + vbroadcastss(zmm_bias_alpha(), xmm_bias_alpha()); + } + + for (int ocb = 0; ocb < jcp.nb_oc_blocking; ocb++) { + const bool mask_flag = last_oc_block && ocb == jcp.nb_oc_blocking - 1; + int scale_offset + = jcp.is_oc_scale * (sizeof(float) * ocb * jcp.oc_block); + + auto zmm_bias = zmm_tmp; + if (jcp.with_bias) { + int bias_offset = jcp.typesize_bia * ocb * jcp.oc_block; + auto bias_addr = EVEX_compress_addr(reg_bias, bias_offset); + cvt2ps(jcp.bia_dt, zmm_bias, bias_addr, mask_flag); + if (jcp.signed_input && jcp.ver != ver_vnni) + vmulps(zmm_bias, zmm_bias, zmm_bias_alpha()); + } + if (jcp.signed_input) { + int comp_offset = sizeof(int32_t) * ocb * jcp.oc_block; + auto comp_addr = EVEX_compress_addr(reg_compensation, comp_offset); + cvt2ps(data_type::s32, zmm_comp, comp_addr, mask_flag); + } + + for (int ur = 0; ur < ur_w; ur++) { + zmm_t zmm = zmm_out(ur, ocb); + vcvtdq2ps(zmm, zmm); + if (jcp.signed_input) + vaddps(zmm, zmm, zmm_comp); + if (jcp.with_bias) + vaddps(zmm, zmm, zmm_bias); + zmm_t mask_zmm = mask_flag ? zmm | ktail_mask | T_z : zmm; + vmulps(mask_zmm, zmm, + EVEX_compress_addr(reg_ptr_scales, scale_offset)); + } + } + if (maybe_eltwise(0)) + compute_eltwise(ur_w); + if (p_sum_scale) { // post_op: sum + for (int k = 0; k < jcp.nb_oc_blocking; k++) { + const bool mask_flag + = last_oc_block == 1 && k == jcp.nb_oc_blocking - 1; + for (int j = 0; j < ur_w; j++) { + int aux_output_offset + = jcp.typesize_out + * (k * jcp.oc_block + + j * jcp.oc_without_padding * jcp.ngroups); + auto addr = EVEX_compress_addr(reg_dst, aux_output_offset); + Zmm zmm = zmm_out(j, k); + cvt2ps(jcp.dst_dt, zmm_prev_dst, addr, mask_flag); + if (*p_sum_scale == 1.f) + vaddps(zmm, zmm_prev_dst); + else + vfmadd231ps(zmm, zmm_prev_dst, zword_b[reg_ptr_sum_scale]); + } + } + } + if (maybe_eltwise(1)) + compute_eltwise(ur_w); + + for (int ocb = 0; ocb < jcp.nb_oc_blocking; ocb++) { + const bool mask_flag = last_oc_block && ocb == jcp.nb_oc_blocking - 1; + for (int ur = 0; ur < ur_w; ur++) { + zmm_t zmm = zmm_out(ur, ocb); + if (jcp.dst_dt == data_type::u8) { + vpxord(zmm_zero, zmm_zero, zmm_zero); + vmaxps(zmm, zmm_zero, zmm); + } + if (jcp.dst_dt != data_type::f32) + vcvtps2dq(zmm, zmm); + } + for (int ur = 0; ur < ur_w; ur++) { + int aux_dst_off = jcp.typesize_out + * (ur * jcp.ngroups * jcp.oc_without_padding + + ocb * jcp.oc_block); + auto addr = EVEX_compress_addr(reg_dst, aux_dst_off); + + zmm_t zmm = zmm_out(ur, ocb); + zmm_t r_zmm = mask_flag ? zmm | ktail_mask : zmm; + switch (jcp.dst_dt) { + case data_type::f32: + case data_type::s32: vmovups(addr, r_zmm); break; + case data_type::s8: vpmovsdb(addr, r_zmm); break; + case data_type::u8: vpmovusdb(addr, r_zmm); break; + default: assert(!"unknown dst_dt"); + } + } + } +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::icb_loop( + int ur_w, int l_overflow, int r_overflow, bool is_last_sp_block) { + + int shift_src_icb = jcp.typesize_in * jcp.ic_block; + int shift_filt_icb + = jcp.typesize_in * jcp.kh * jcp.kw * jcp.ic_block * jcp.oc_block; + + prepare_output(ur_w); + + Label skip_icb_loop, icb_loop_label; + + mov(reg_icb, jcp.nb_ic); + L(icb_loop_label); { + + if (jcp.ic_without_padding != jcp.ic) { + Label common_ker, end_ker; + cmp(reg_icb, 1); + jg(common_ker, T_NEAR); + + kh_loop(ur_w, l_overflow, r_overflow, + is_last_sp_block ? last_sp_block : last_ic_block); + jmp(end_ker, T_NEAR); + + L(common_ker); + kh_loop(ur_w, l_overflow, r_overflow, no_last_block); + + L(end_ker); + } else { + kh_loop(ur_w, l_overflow, r_overflow, no_last_block); + } + + add(reg_src, shift_src_icb); + add(reg_filt, shift_filt_icb); + dec(reg_icb); + cmp(reg_icb, 0); + jg(icb_loop_label, T_NEAR); + } + + /* come-back pointers */ + sub(reg_src, jcp.nb_ic * shift_src_icb); + sub(reg_filt, jcp.nb_ic * shift_filt_icb); + L(skip_icb_loop); + + if (jcp.ngroups % jcp.ch_block != 0 || jcp.oc_without_padding != jcp.oc) { + Label common_store, end_store; + mov(reg_oc_blocks, ptr[param1 + GET_OFF(oc_blocks)]); + if (jcp.is_depthwise) + cmp(reg_oc_blocks, jcp.nb_ch - 1); + else + cmp(reg_oc_blocks, jcp.nb_oc - jcp.nb_oc_blocking); + jne(common_store, T_NEAR); + + store_output(ur_w, true); + jmp(end_store, T_NEAR); + + L(common_store); + store_output(ur_w, false); + + L(end_store); + + } else { + store_output(ur_w, false); + } +} + +void jit_avx512_core_x8s8s32x_deconv_fwd_kernel::generate() { + preamble(); + + xor_(reg_scratch, reg_scratch); + Reg16 _t = reg_scratch.cvt16(); + mov(_t, 0x1); + vpbroadcastw(zmm_one, _t); + + if (jcp.ngroups % jcp.ch_block != 0 || jcp.oc_without_padding != jcp.oc) { + int tail_size = jcp.is_depthwise ? + jcp.ngroups % jcp.ch_block : + jcp.oc_without_padding % jcp.oc_block; + int mask = (1 << tail_size) - 1; + Reg32 regw_tmp = reg_nur_w.cvt32(); + mov(regw_tmp, mask); + kmovw(ktail_mask, regw_tmp); + } + + mov(reg_src, ptr[param1 + GET_OFF(src)]); + mov(reg_filt, ptr[param1 + GET_OFF(filt)]); + mov(reg_dst, ptr[param1 + GET_OFF(dst)]); + + int dst_shift = jcp.typesize_out * jcp.ur_w * jcp.ngroups + * jcp.oc_without_padding; + int src_shift = jcp.typesize_in * (jcp.ur_w / jcp.stride_w) * jcp.ngroups + * jcp.ic_without_padding; + + int l_overflow = max( + 0, ((jcp.kw - 1) * (jcp.dilate_w + 1) - jcp.l_pad) / jcp.stride_w); + int r_overflow + = max(0, ((jcp.kw - 1) * (jcp.dilate_w + 1) - max(0, jcp.r_pad)) + / jcp.stride_w); + + int r_overflow1 + = nstl::max(0, ((jcp.kw - 1) * (jcp.dilate_w + 1) + - nstl::max(0, jcp.r_pad) - jcp.ur_w_tail) + / jcp.stride_w); + int nur_w = jcp.ow / jcp.ur_w; + if (r_overflow1 > 0) + nur_w--; + + if (jcp.ur_w == jcp.ow) { + icb_loop(jcp.ur_w, l_overflow, r_overflow, true); + } else if (nur_w == 0) { + icb_loop(jcp.ur_w, l_overflow, r_overflow1, jcp.ur_w_tail == 0); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + if (jcp.ur_w_tail != 0) + icb_loop(jcp.ur_w_tail, 0, r_overflow, true); + } else { + xor_(reg_nur_w, reg_nur_w); + if (l_overflow > 0) { + icb_loop(jcp.ur_w, l_overflow, 0, false); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + inc(reg_nur_w); + } + if ((l_overflow <= 0 && nur_w > 0) || (l_overflow > 0 && nur_w > 1)) { + Label ow_loop_label; + L(ow_loop_label); + { + icb_loop(jcp.ur_w, 0, 0, false); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + inc(reg_nur_w); + cmp(reg_nur_w, nur_w); + jl(ow_loop_label, T_NEAR); + } + } + if (r_overflow1 > 0) { + icb_loop(jcp.ur_w, 0, r_overflow1, jcp.ur_w_tail == 0); + add(reg_src, src_shift); + add(reg_dst, dst_shift); + } + if (jcp.ur_w_tail != 0) { + icb_loop(jcp.ur_w_tail, 0, r_overflow, true); + } + } + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +template +void _jit_avx512_core_x8s8s32x_deconvolution_fwd_t::execute_forward_1d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + auto &jcp = kernel_->jcp; + + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int nb_groups = jcp.nb_ch; + + const float *oscales = pd()->attr()->output_scales_.scales_; + if (jcp.signed_input && jcp.ver != ver_vnni) { + auto local_scales + = scratchpad(ctx).template get(key_conv_adjusted_scales); + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / pd()->jcp_.wei_adj_scale; + if (count == 1) { + utils::array_set(local_scales, oscales[0] * factor, 16); + } else { + for (size_t c = 0; c < count; c++) + local_scales[c] = oscales[c] * factor; + } + oscales = local_scales; + } + size_t offset = (size_t)jcp.ngroups * jcp.oc * jcp.ic * jcp.kh * jcp.kw; + auto w = const_cast(weights); + int32_t *compensation + = (jcp.signed_input) ? reinterpret_cast(&w[offset]) : 0; + + parallel(0, [&](const int ithr, const int nthr) { + int start{ 0 }, end{ 0 }; + int work_amount = jcp.mb * nb_groups * oc_chunks; + balance211(work_amount, nthr, ithr, start, end); + + auto p = jit_deconv_call_s(); + + int n{ 0 }, g{ 0 }, occ{ 0 }; + if (jcp.loop_order == loop_ngc) + nd_iterator_init(start, n, jcp.mb, g, nb_groups, occ, oc_chunks); + else if (jcp.loop_order == loop_cgn) + nd_iterator_init(start, occ, oc_chunks, g, nb_groups, n, jcp.mb); + else + assert(!"unsupported loop order"); + while (start < end) { + + int ocb = occ * jcp.nb_oc_blocking; + int g_oc = (g * jcp.ch_block * jcp.nb_oc + ocb) * jcp.oc_block; + int g_ic = g * jcp.ch_block * jcp.ic; + + p.dst = dst + dst_d.blk_off(n, g_oc); + p.src = src + src_d.blk_off(n, g_ic); + p.filt = weights + wht_blk_off(weights_d, g, ocb, 0); + p.bias = jcp.with_bias ? + bias + (bias_d.blk_off(g_oc) * jcp.typesize_bia) : + 0; + p.compensation = (jcp.signed_input) ? compensation + g_oc : 0; + p.scales = &oscales[jcp.is_oc_scale * g_oc]; + p.t_overflow = 0; + p.b_overflow = 0; + p.kh_padding = jcp.kh; + p.oc_blocks = jcp.is_depthwise ? g : ocb; + + kernel_->jit_ker(&p); + + ++start; + if (jcp.loop_order == loop_ngc) + nd_iterator_step(n, jcp.mb, g, nb_groups, occ, oc_chunks); + else if (jcp.loop_order == loop_cgn) + nd_iterator_step(occ, oc_chunks, g, nb_groups, n, jcp.mb); + else + assert(!"unsupported loop order"); + } + }); +} + +template +void _jit_avx512_core_x8s8s32x_deconvolution_fwd_t::execute_forward_2d(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + auto &jcp = kernel_->jcp; + + int oc_chunks = jcp.nb_oc / jcp.nb_oc_blocking; + int nb_groups = jcp.nb_ch; + + size_t src_h_stride = src_d.blk_off(0, 0, 1); + size_t dst_h_stride = dst_d.blk_off(0, 0, 1); + size_t wht_kh_stride = wht_blk_off(weights_d, 0, 0, 0, 1); + + const float *oscales = pd()->attr()->output_scales_.scales_; + if (jcp.signed_input && jcp.ver != ver_vnni) { + auto local_scales + = scratchpad(ctx).template get(key_conv_adjusted_scales); + size_t count = pd()->attr()->output_scales_.count_; + float factor = 1.f / pd()->jcp_.wei_adj_scale; + if (count == 1) { + utils::array_set(local_scales, oscales[0] * factor, 16); + } else { + for (size_t c = 0; c < count; c++) + local_scales[c] = oscales[c] * factor; + } + oscales = local_scales; + } + size_t offset = (size_t)jcp.ngroups * jcp.oc * jcp.ic * jcp.kh * jcp.kw; + auto w = const_cast(weights); + int32_t *compensation + = (jcp.signed_input) ? reinterpret_cast(&w[offset]) : 0; + + parallel(0, [&](const int ithr, const int nthr) { + int start{ 0 }, end{ 0 }; + int work_amount = jcp.mb * nb_groups * oc_chunks * jcp.oh; + balance211(work_amount, nthr, ithr, start, end); + + auto p = jit_deconv_call_s(); + + /*loop order = cgn*/ + int n{ 0 }, g{ 0 }, occ{ 0 }, oh_s{ 0 }; + if (jcp.loop_order == loop_ngc) + nd_iterator_init(start, n, jcp.mb, g, nb_groups, occ, oc_chunks, + oh_s, jcp.oh); + else if (jcp.loop_order == loop_cgn) + nd_iterator_init(start, occ, oc_chunks, g, nb_groups, n, jcp.mb, + oh_s, jcp.oh); + else + assert(!"unsupported loop order"); + while (start < end) { + + int ocb = occ * jcp.nb_oc_blocking; + int g_oc = (g * jcp.ch_block * jcp.nb_oc + ocb) * jcp.oc_block; + int g_ic = g * jcp.ch_block * jcp.ic; + int work_rem = end - start; + int oh_e = oh_s + work_rem > jcp.oh ? jcp.oh : oh_s + work_rem; + + auto dst_w = dst + dst_d.blk_off(n, g_oc); + auto src_w = src + src_d.blk_off(n, g_ic); + auto wht_w = weights + wht_blk_off(weights_d, g, ocb, 0); + auto bias_w = jcp.with_bias ? + bias + (bias_d.blk_off(g_oc) * jcp.typesize_bia) : + 0; + int32_t *compensation_w + = (jcp.signed_input) ? compensation + g_oc : 0; + + auto scales = &oscales[jcp.is_oc_scale * g_oc]; + for (int oj = oh_s; oj < oh_e; oj++) { + int ih_max = 0, kh_lo = 0, kh_len = 0; + if (jcp.dilate_h != 0 && jcp.stride_h == 1) { + /* dilation */ + int dilate_h = jcp.dilate_h + 1; + // Note: use div_up to account for "holes" in filter + int o_t_overflow = div_up( + max(0, (jcp.kh - 1) * dilate_h - oj - jcp.t_pad), + dilate_h); + int o_b_overflow + = div_up(max(0, (jcp.kh - 1) * dilate_h + 1 - jcp.oh + + oj - jcp.b_pad), + dilate_h); + kh_len = jcp.kh - o_t_overflow - o_b_overflow; + kh_lo = o_b_overflow; + ih_max = oj + jcp.t_pad - o_b_overflow * dilate_h; + } else { + int o_t_overflow = max( + 0, (jcp.kh - (oj + 1 + jcp.t_pad)) / jcp.stride_h); + int o_b_overflow + = max(0, ((oj + jcp.kh) - (jcp.oh + jcp.b_pad)) + / jcp.stride_h); + int overflow_kh_hi = jcp.kh - 1 + - abs(jcp.oh + jcp.b_pad - (oj + 1)) % jcp.stride_h; + int overflow_kh_lo = (oj + jcp.t_pad) % jcp.stride_h; + + kh_len = (overflow_kh_hi - overflow_kh_lo) / jcp.stride_h + + 1 - o_t_overflow - o_b_overflow; + kh_lo = overflow_kh_lo + o_b_overflow * jcp.stride_h; + ih_max = (oj + jcp.t_pad - kh_lo) / jcp.stride_h; + } + + int wei_stride + = (!jcp.signed_input) ? kh_lo * wht_kh_stride : 0; + p.src = src_w + ih_max * src_h_stride; + p.dst = dst_w + oj * dst_h_stride; + p.filt = wht_w + wei_stride; + p.bias = bias_w; + p.compensation = compensation_w; + p.t_overflow = max( + 0, jcp.kh - (kh_lo + max(0, kh_len - 1) * jcp.stride_h + + 1)); + p.b_overflow = kh_lo; + p.kh_padding = kh_len; + p.scales = scales; + p.oc_blocks = jcp.is_depthwise ? g : ocb; + kernel_->jit_ker(&p); + } + if (jcp.loop_order == loop_ngc) + nd_iterator_jump(start, end, n, jcp.mb, g, nb_groups, occ, + oc_chunks, oh_s, jcp.oh); + else if (jcp.loop_order == loop_cgn) + nd_iterator_jump(start, end, occ, oc_chunks, g, nb_groups, n, + jcp.mb, oh_s, jcp.oh); + else + assert(!"unsupported loop order"); + } + }); +} + +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +template struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t; +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.hpp new file mode 100644 index 000000000000..901038fa4841 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_avx512_core_x8s8s32x_deconvolution.hpp @@ -0,0 +1,237 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX512_CORE_U8S8S32X_DECONVOLUTION_HPP +#define CPU_JIT_AVX512_CORE_U8S8S32X_DECONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "cpu_primitive.hpp" +#include "cpu_memory.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "nstl.hpp" + +#include "cpu_deconvolution_pd.hpp" +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +typedef enum { + no_last_block = 0x1U, + last_ic_block = 0x2U, + last_sp_block = 0x4U, +} ker_block_t; + +struct jit_avx512_core_x8s8s32x_deconv_fwd_kernel : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_avx512_core_x8s8s32x_deconv_fwd_ker_t); + + jit_avx512_core_x8s8s32x_deconv_fwd_kernel( + const jit_conv_conf_t &ajcp, const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32( + this, jcp.eltwise); + generate(); + jit_ker = (void (*)(jit_deconv_call_s *))getCode(); + } + + ~jit_avx512_core_x8s8s32x_deconv_fwd_kernel() { + delete eltwise_injector_; + } + + static bool post_ops_ok(jit_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_conv_conf_t &jcp, + const deconvolution_desc_t &cd, + memory_desc_t &src_md, + memory_desc_t &weights_md, + memory_desc_t &dst_md, + const bool with_bias, + memory_desc_t &bias_md, + const primitive_attr_t &attr); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp, const primitive_attr_t &attr); + + const jit_conv_conf_t &jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_deconv_call_s *); +private: + jit_uni_eltwise_injector_f32 *eltwise_injector_; + using reg64_t = const Xbyak::Reg64; + using zmm_t = const Xbyak::Zmm; + using xmm_t = const Xbyak::Xmm; + + reg64_t reg_src = r8; + reg64_t reg_filt = r9; + reg64_t reg_dst = r10; + reg64_t param1 = abi_param1; + reg64_t reg_kh = abi_not_param1; + reg64_t reg_nur_w = rbx; + reg64_t reg_bias = rdx; + reg64_t reg_icb = reg_bias; + reg64_t reg_ptr_scales = rax; + reg64_t reg_oc_blocks = rsi; + + reg64_t aux_reg_src = r11; + reg64_t aux_reg_filt = r12; + + reg64_t reg_compensation = r14; + reg64_t reg_scratch = r14; + reg64_t reg_ptr_sum_scale = r11; + reg64_t reg_bias_alpha = abi_not_param1; + reg64_t reg_overflow = rax; + reg64_t reg_comp_strides = reg_overflow; + + Xbyak::Opmask ktail_mask = Xbyak::Opmask(2); + zmm_t zmm_tmp = zmm_t(28); + zmm_t zmm_one = zmm_t(29); + /* used during write-out section of store_output */ + zmm_t zmm_zero = zmm_t(31); + zmm_t zmm_wei = zmm_t(31); + + /* signed input */ + zmm_t zmm_shift = zmm_t(30); + zmm_t zmm_comp = zmm_t(30); + zmm_t zmm_bias = zmm_t(31); + zmm_t zmm_prev_dst = zmm_t(31); + + zmm_t zmm_out(int i_ur, int i_oc) { + int idx = i_ur * jcp.nb_oc_blocking + i_oc; + assert(idx < 31); + return zmm_t(idx); + } + zmm_t zmm_inp(int i_ic, int nb_x_blocking) { + int idx = i_ic + nb_x_blocking * jcp.ur_w; + assert(idx < 31); + return zmm_t(idx); + } + zmm_t zmm_bias_alpha() { + return zmm_t(jcp.nb_oc_blocking * jcp.ur_w); + } + xmm_t xmm_bias_alpha() { + return xmm_t(jcp.nb_oc_blocking * jcp.ur_w); + } + + int get_ow_start(int ki, int l_overflow) { + int res = (jcp.ow - 1 + jcp.r_pad) % jcp.stride_w + + l_overflow * jcp.stride_w + - (jcp.kw - 1 - ki) * (jcp.dilate_w + 1); + while (res < 0) + res += jcp.stride_w; + return res; + } + + int get_ow_end(int ur_w, int ki, int r_overflow) { + if (utils::one_of(ur_w, jcp.ow, jcp.ur_w_tail)) + ur_w += nstl::min(0, jcp.r_pad); // remove negative padding + int res = (ur_w - 1 + jcp.l_pad) % jcp.stride_w + + r_overflow * jcp.stride_w - ki * (jcp.dilate_w + 1); + while (res < 0) + res += jcp.stride_w; + return ur_w - res; + } + bool maybe_eltwise(int position); + void compute_eltwise(int ur_w); + void prepare_output(int ur_w); + void store_output(int ur_w, bool last_oc_block); + void compute_ker(int ur_w, int l_overflow, int r_overflow, + ker_block_t last_ic_block_flag, bool h_padded = false); + void kh_loop(int ur_w, int pad_l, int pad_r, ker_block_t last_ker_block); + void icb_loop(int ur_w, int pad_l, int pad_r, bool last_block); + void generate(); + void cvt2ps(data_type_t type_in, zmm_t zmm_in, const Xbyak::Operand &op, + bool mask_flag); +}; + +template +struct _jit_avx512_core_x8s8s32x_deconvolution_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_deconvolution_fwd_pd_t { + using cpu_deconvolution_fwd_pd_t::cpu_deconvolution_fwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_deconvolution:", avx512_core, ""), + _jit_avx512_core_x8s8s32x_deconvolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && (desc()->alg_kind & alg_kind::deconvolution_direct) + && desc()->src_desc.data_type == src_type + && desc()->dst_desc.data_type == dst_type + && IMPLICATION(with_bias(), utils::one_of( + desc()->bias_desc.data_type, data_type::f32, + data_type::s32, data_type::s8, data_type::u8)) + && desc()->accum_data_type == data_type::s32; + if (!ok) return status::unimplemented; + + status_t status = jit_avx512_core_x8s8s32x_deconv_fwd_kernel:: + init_conf(jcp_, *desc(), src_md_, weights_md_, dst_md_, + with_bias(), bias_md_, *attr()); + + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_avx512_core_x8s8s32x_deconv_fwd_kernel::init_scratchpad(scratchpad, + jcp_, *attr()); + + return status::success; + } + + jit_conv_conf_t jcp_; + }; + + _jit_avx512_core_x8s8s32x_deconvolution_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + { + kernel_ = new jit_avx512_core_x8s8s32x_deconv_fwd_kernel(pd()->jcp_, + *pd()->attr()); + } + + ~_jit_avx512_core_x8s8s32x_deconvolution_fwd_t() { delete kernel_; } + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if(pd()->ndims() == 3) + execute_forward_1d(ctx); + else + execute_forward_2d(ctx); + return status::success; + } + +private: + void execute_forward_1d(const exec_ctx_t &ctx) const; + void execute_forward_2d(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_avx512_core_x8s8s32x_deconv_fwd_kernel *kernel_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_generator.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_generator.hpp new file mode 100644 index 000000000000..c09592d5c950 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_generator.hpp @@ -0,0 +1,773 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_AVX2_GENERATOR_HPP +#define CPU_JIT_AVX2_GENERATOR_HPP + +#include + +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_isa_traits.hpp" +#include "jit_utils/jit_utils.hpp" + +#if defined(_WIN32) && !defined(__GNUC__) +# define STRUCT_ALIGN(al, ...) __declspec(align(al)) __VA_ARGS__ +#else +# define STRUCT_ALIGN(al, ...) __VA_ARGS__ __attribute__((__aligned__(al))) +#endif + +#if defined(_WIN32) +# define OFFSET_SHADOWSPACE 0x28 +#endif + +#define DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_name) \ + const char *name() const override { return STRINGIFY(jit_name); } \ + const char *source_file() const override { return __FILE__; } + +namespace mkldnn { +namespace impl { +namespace cpu { + +// TODO: move this to jit_generator class? +namespace { + +typedef enum { + PAGE_4K = 4096, + PAGE_2M = 2097152, +} cpu_page_size_t; + +// TODO: move this somewhere else? Although this is only used by jit kernels +// (Roma) +static inline int float2int(float x) { + union { + float vfloat; + int vint; + } cvt; + cvt.vfloat = x; + return cvt.vint; +} + +// TODO: A GPR class that hides ABI details from the JIT kernels and allows +// numbering registers from 0 to 14 (x86_64) / 6 (x32) (gpr0, gpr1, ...) and +// stack register (sr). +// +// This will allow using syntax like this: +// +// param = gpr0; +// reg_input = gpr0; +// reg_output = gpr1; +// ... +// +// #ifndef XBYAK64 +// mov(param, ptr[sr]) +// #endif +// +// (Roma) + +#ifdef XBYAK64 +constexpr Xbyak::Operand::Code abi_save_gpr_regs[] = { + Xbyak::Operand::RBX, Xbyak::Operand::RBP, Xbyak::Operand::R12, + Xbyak::Operand::R13, Xbyak::Operand::R14, Xbyak::Operand::R15, +#ifdef _WIN32 + Xbyak::Operand::RDI, Xbyak::Operand::RSI, +#endif +}; + +#ifdef _WIN32 +static const Xbyak::Reg64 abi_param1(Xbyak::Operand::RCX), + abi_param2(Xbyak::Operand::RDX), + abi_param3(Xbyak::Operand::R8), + abi_param4(Xbyak::Operand::R9), + abi_not_param1(Xbyak::Operand::RDI); +#else +static const Xbyak::Reg64 abi_param1(Xbyak::Operand::RDI), + abi_param2(Xbyak::Operand::RSI), + abi_param3(Xbyak::Operand::RDX), + abi_param4(Xbyak::Operand::RCX), + abi_param5(Xbyak::Operand::R8), + abi_param6(Xbyak::Operand::R9), + abi_not_param1(Xbyak::Operand::RCX); +#endif +#endif + +inline unsigned int get_cache_size(int level, bool per_core = true){ + unsigned int l = level - 1; + // Currently, if XByak is not able to fetch the cache topology + // we default to 32KB of L1, 512KB of L2 and 1MB of L3 per core. + if (cpu.getDataCacheLevels() == 0){ + const int L1_cache_per_core = 32000; + const int L2_cache_per_core = 512000; + const int L3_cache_per_core = 1024000; + int num_cores = per_core ? 1 : mkldnn_get_max_threads(); + switch(l){ + case(0): return L1_cache_per_core * num_cores; + case(1): return L2_cache_per_core * num_cores; + case(2): return L3_cache_per_core * num_cores; + default: return 0; + } + } + if (l < cpu.getDataCacheLevels()) { + return cpu.getDataCacheSize(l) + / (per_core ? cpu.getCoresSharingDataCache(l) : 1); + } else + return 0; +} + +} + +class jit_generator : public Xbyak::CodeGenerator +{ +private: + const size_t xmm_len = 16; +#ifdef _WIN32 + const size_t xmm_to_preserve_start = 6; + const size_t xmm_to_preserve = 10; +#else + const size_t xmm_to_preserve_start = 0; + const size_t xmm_to_preserve = 0; +#endif + + const size_t num_abi_save_gpr_regs + = sizeof(abi_save_gpr_regs) / sizeof(abi_save_gpr_regs[0]); + + const size_t size_of_abi_save_regs + = num_abi_save_gpr_regs * rax.getBit() / 8 + + xmm_to_preserve * xmm_len; + +public: + enum { + _cmp_eq_oq = 0u, + _cmp_lt_os = 1u, + _cmp_le_os = 2u, + _cmp_neq_uq = 4u, + _cmp_nlt_us = 5u, + _cmp_nle_us = 6u, + + _op_floor = 1u, + _op_mxcsr = 4u, + }; + + Xbyak::Reg64 param1 = abi_param1; + const int EVEX_max_8b_offt = 0x200; + const Xbyak::Reg64 reg_EVEX_max_8b_offt = rbp; + + inline size_t get_size_of_abi_save_regs() { + return size_of_abi_save_regs; + } + + void preamble() { + if (xmm_to_preserve) { + sub(rsp, xmm_to_preserve * xmm_len); + for (size_t i = 0; i < xmm_to_preserve; ++i) + movdqu(ptr[rsp + i * xmm_len], Xbyak::Xmm(xmm_to_preserve_start + i)); + } + for (size_t i = 0; i < num_abi_save_gpr_regs; ++i) + push(Xbyak::Reg64(abi_save_gpr_regs[i])); + if (mayiuse(avx512_common)) { + mov(reg_EVEX_max_8b_offt, 2 * EVEX_max_8b_offt); + } + } + + void mic_prefetcht0(Xbyak::Address a) { + if (mayiuse(avx512_mic)) + prefetcht0(a); + } + + void mic_prefetcht1(Xbyak::Address a) { + if (mayiuse(avx512_mic)) + prefetcht1(a); + } + + void mic_prefetcht2(Xbyak::Address a) { + if (mayiuse(avx512_mic)) + prefetcht2(a); + } + + void uni_vzeroupper() { + if (mayiuse(avx) && !mayiuse(avx512_mic)) + vzeroupper(); + } + + void postamble() { + for (size_t i = 0; i < num_abi_save_gpr_regs; ++i) + pop(Xbyak::Reg64(abi_save_gpr_regs[num_abi_save_gpr_regs - 1 - i])); + if (xmm_to_preserve) { + for (size_t i = 0; i < xmm_to_preserve; ++i) + movdqu(Xbyak::Xmm(xmm_to_preserve_start + i), ptr[rsp + i * xmm_len]); + add(rsp, xmm_to_preserve * xmm_len); + } + uni_vzeroupper(); + ret(); + } + + template + Xbyak::Address EVEX_compress_addr(Xbyak::Reg64 base, + T raw_offt, bool bcast = false) + { + using Xbyak::Zmm; + using Xbyak::Reg64; + using Xbyak::Address; + using Xbyak::RegExp; + + assert(raw_offt <= INT_MAX); + auto offt = static_cast(raw_offt); + + int scale = 0; + + if (EVEX_max_8b_offt <= offt && offt < 3 * EVEX_max_8b_offt) { + offt = offt - 2 * EVEX_max_8b_offt; + scale = 1; + } else if (3 * EVEX_max_8b_offt <= offt && offt < 5 * EVEX_max_8b_offt) { + offt = offt - 4 * EVEX_max_8b_offt; + scale = 2; + } + + auto re = RegExp() + base + offt; + if (scale) + re = re + reg_EVEX_max_8b_offt * scale; + + if (bcast) + return zword_b [re]; + else + return zword [re]; + } + + Xbyak::Address make_safe_addr(const Xbyak::Reg64 ®_out, size_t offt, + const Xbyak::Reg64 &tmp_reg, bool bcast = false) { + if (offt > INT_MAX) { + mov(tmp_reg, offt); + return bcast ? ptr_b[reg_out + tmp_reg] : ptr[reg_out + tmp_reg]; + } else { + return bcast ? ptr_b[reg_out + offt] : ptr[reg_out + offt]; + } + } + + Xbyak::Address EVEX_compress_addr_safe(const Xbyak::Reg64 &base, + size_t raw_offt, const Xbyak::Reg64 ®_offt, bool bcast = false) { + if (raw_offt > INT_MAX) { + return make_safe_addr(base, raw_offt, reg_offt, bcast); + } else { + return EVEX_compress_addr(base, raw_offt, bcast); + } + } + + void safe_add(const Xbyak::Reg64 &base, size_t raw_offt, + const Xbyak::Reg64 ®_offt) { + if (raw_offt > INT_MAX) { + mov(reg_offt, raw_offt); + add(base, reg_offt); + } else { + add(base, raw_offt); + } + } + + void safe_sub(const Xbyak::Reg64 &base, size_t raw_offt, + const Xbyak::Reg64 ®_offt) { + if (raw_offt > INT_MAX) { + mov(reg_offt, raw_offt); + sub(base, reg_offt); + } else { + sub(base, raw_offt); + } + } + + // Disallow char-based labels completely + void L(const char *label) = delete; + void L(Xbyak::Label& label) { Xbyak::CodeGenerator::L(label); } + + void uni_vpxor(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + assert(x1.getIdx() == x2.getIdx()); + pxor(x2, op); + } + void uni_vpxor(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op) { + if (mayiuse(avx2)) { + vpxor(x1, x2, op); + } else { + vxorps(x1, x2, op); + } + } + void uni_vpxor(const Xbyak::Zmm &x1, const Xbyak::Zmm &x2, + const Xbyak::Operand &op) { + vpxord(x1, x2, op); + } + + void uni_vmovss(const Xbyak::Address& addr, const Xbyak::Xmm &x) { + movss(addr, x); + } + void uni_vmovss(const Xbyak::Address& addr, const Xbyak::Ymm &x) { + vmovss(addr, x); + } + void uni_vmovss(const Xbyak::Xmm &x, const Xbyak::Address& addr) { + movss(x, addr); + } + void uni_vmovss(const Xbyak::Ymm &x, const Xbyak::Address& addr) { + vmovss(x, addr); + } + + void uni_vmovsd(const Xbyak::Address& addr, const Xbyak::Xmm &x) { + movsd(addr, x); + } + void uni_vmovsd(const Xbyak::Address& addr, const Xbyak::Ymm &x) { + vmovsd(addr, x); + } + void uni_vmovsd(const Xbyak::Xmm &x, const Xbyak::Address& addr) { + movsd(x, addr); + } + void uni_vmovsd(const Xbyak::Ymm &x, const Xbyak::Address& addr) { + vmovsd(x, addr); + } + + void uni_vmovdqu(const Xbyak::Address &addr, const Xbyak::Xmm &x) { + movdqu(addr, x); + } + void uni_vmovdqu(const Xbyak::Address &addr, const Xbyak::Ymm &x) { + vmovdqu(addr, x); + } + void uni_vmovdqu(const Xbyak::Address &addr, const Xbyak::Zmm &x) { + vmovdqu32(addr, x); + } + + void uni_vmovdqu(const Xbyak::Xmm &x, const Xbyak::Address &addr) { + movdqu(x, addr); + } + void uni_vmovdqu(const Xbyak::Ymm &x, const Xbyak::Address &addr) { + vmovdqu(x, addr); + } + void uni_vmovdqu(const Xbyak::Zmm &x, const Xbyak::Address &addr) { + vmovdqu32(x, addr); + } + + void uni_vmovups(const Xbyak::Address &addr, const Xbyak::Xmm &x) { + movups(addr, x); + } + void uni_vmovups(const Xbyak::Address &addr, const Xbyak::Ymm &x) { + vmovups(addr, x); + } + + void uni_vmovups(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + movups(x, op); + } + void uni_vmovups(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + vmovups(x, op); + } + + void uni_vmovntps(const Xbyak::Address &addr, const Xbyak::Xmm &x) { + movntps(addr, x); + } + void uni_vmovntps(const Xbyak::Address &addr, const Xbyak::Ymm &x) { + vmovntps(addr, x); + } + + void uni_vbroadcastss(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + movss(x, op); + shufps(x, x, 0x0); + } + void uni_vbroadcastss(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + if (op.isMEM() || mayiuse(avx2)) { + vbroadcastss(x, op); + } else { + Xbyak::Xmm t(x.getIdx()); + if (t.getIdx() != op.getIdx()) movss(t, op); + vinsertf128(x, x, t, 1); + vshufps(x, x, x, 0); + } + } + + void uni_vpbroadcastd(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + movsd(x, op); + pshufd(x, x, 0x0); + } + void uni_vpbroadcastd(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + if (mayiuse(avx2)) { + vpbroadcastd(x, op); + } else { + Xbyak::Xmm t(x.getIdx()); + if (t.getIdx() != op.getIdx()) movsd(t, op); + vinsertf128(x, x, t, 1); + vshufps(x, x, x, 0); + } + } + + void uni_vrcpss(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + rcpss(x, op); + } + void uni_vrcpss(const Xbyak::Ymm &x1, const Xbyak::Xmm &x2) { + Xbyak::Xmm x1_(x1.getIdx()); + Xbyak::Xmm x2_(x2.getIdx()); + vrcpss(x1_, x1_, x2_); + } + void uni_vrcpss(const Xbyak::Ymm &x, const Xbyak::Address &op) { + Xbyak::Xmm x_(x.getIdx()); + vrcpss(x_, x_, op); + } + + void uni_vrcpps(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + rcpps(x, op); + } + void uni_vrcpps(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + vrcpps(x, op); + } + void uni_vrcpps(const Xbyak::Zmm &x, const Xbyak::Operand &op) { + vrcp14ps(x, op); + } + + void uni_vdivps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + assert(x.getIdx() == op1.getIdx()); + divps(x, op2); + } + void uni_vdivps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + vdivps(x, op1, op2); + } + + void uni_vdivps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2, const Xbyak::Xmm &buf) { + movups(buf, op1); + divps(buf, op2); + if (x.getIdx() != buf.getIdx()) { + movups(x, buf); + } + } + + void uni_vdivps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2, const Xbyak::Ymm &buf) { + vdivps(x, op1, op2); + } + + void uni_vaddps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + assert(x.getIdx() == op1.getIdx()); + addps(x, op2); + } + void uni_vaddps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + vaddps(x, op1, op2); + } + + void uni_vpsignd(const Xbyak::Xmm& x1, const Xbyak::Xmm& x2, + const Xbyak::Operand& op) { + assert(x1.getIdx() == x2.getIdx()); + psignd(x1, op); + } + void uni_vpsignd(const Xbyak::Ymm& x1, const Xbyak::Ymm& x2, + const Xbyak::Operand& op) { + vpsignd(x1, x2, op); + } + + void uni_vsubps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + assert(x.getIdx() == op1.getIdx()); + subps(x, op2); + } + void uni_vsubps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + vsubps(x, op1, op2); + } + + void uni_vsubps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2, const Xbyak::Xmm &buf) { + movups(buf, op1); + subps(buf, op2); + if (x.getIdx() != buf.getIdx()) { + movups(x, buf); + } + } + + void uni_vsubps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2, const Xbyak::Ymm &buf) { + vsubps(x, op1, op2); + } + + void uni_vmulps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + assert(x.getIdx() == op1.getIdx()); + mulps(x, op2); + } + void uni_vmulps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + vmulps(x, op1, op2); + } + + void uni_vfmadd213ps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + mulps(x1, x2); + addps(x1, op); + } + void uni_vfmadd213ps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op) { + vfmadd213ps(x1, x2, op); + } + + void uni_vfmadd231ps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + mulps(x2, op); + addps(x1, x2); + } + void uni_vfmadd231ps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op) { + vfmadd231ps(x1, x2, op); + } + + void uni_vfnmadd231ps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + mulps(x2, op); + subps(x1, x2); + } + + void uni_vfnmadd231ps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op) { + vfnmadd231ps(x1, x2, op); + } + + void uni_vsqrtps(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + sqrtps(x, op); + } + void uni_vsqrtps(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + vsqrtps(x, op); + } + + void uni_vpaddd(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + assert(x1.getIdx() == x2.getIdx()); + paddd(x2, op); + } + void uni_vpaddd(const Xbyak::Ymm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + vpaddd(x1, x2, op); + } + + void uni_vandps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op = Xbyak::Operand()) { + assert(x1.getIdx() == x2.getIdx()); + andps(x1, op); + } + void uni_vandps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op = Xbyak::Operand()) { + if (!mayiuse(avx512_common) || x1.getBit() < 512) + vandps(x1, x2, op); + else + vpandd(x1, x2, op); + } + + void uni_vorps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op = Xbyak::Operand()) { + assert(x1.getIdx() == x2.getIdx()); + orps(x1, op); + } + void uni_vorps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op = Xbyak::Operand()) { + if (!mayiuse(avx512_common) || x1.getBit() < 512) + vorps(x1, x2, op); + else + vpord(x1, x2, op); + } + + void uni_vpslld(const Xbyak::Xmm &x, const Xbyak::Operand &op, + const int imm) { + assert(x.getIdx() == op.getIdx()); + pslld(x, imm); + } + void uni_vpslld(const Xbyak::Ymm &x, const Xbyak::Operand &op, + const int imm) { + vpslld(x, op, imm); + } + + void uni_vpsrld(const Xbyak::Xmm &x, const Xbyak::Operand &op, + const int imm) { + assert(x.getIdx() == op.getIdx()); + psrld(x, imm); + } + void uni_vpsrld(const Xbyak::Ymm &x, const Xbyak::Operand &op, + const int imm) { + vpsrld(x, op, imm); + } + + void uni_vmaxps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + assert(x.getIdx() == op1.getIdx()); + maxps(x, op2); + } + void uni_vmaxps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + vmaxps(x, op1, op2); + } + + void uni_vminps(const Xbyak::Xmm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + assert(x.getIdx() == op1.getIdx()); + minps(x, op2); + } + void uni_vminps(const Xbyak::Ymm &x, const Xbyak::Operand &op1, + const Xbyak::Operand &op2 = Xbyak::Operand()) { + vminps(x, op1, op2); + } + + void uni_vcmpgtps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + assert(x1.getIdx() == x2.getIdx()); + cmpps(x1, op, _cmp_nle_us); + } + + void uni_vcmpgtps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op) { + vcmpgtps(x1, x2, op); + } + + void uni_vcmpgeps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op) { + assert(x1.getIdx() == x2.getIdx()); + cmpps(x1, op, _cmp_nlt_us); + } + + void uni_vcmpgeps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op) { + vcmpps(x1, x2, op, _cmp_nlt_us); + } + + void uni_vtestps(const Xbyak::Xmm &x1, const Xbyak::Operand &op) { + ptest(x1, op); + } + + void uni_vtestps(const Xbyak::Ymm &x1, const Xbyak::Operand &op) { + assert(!(x1.isZMM() || op.isZMM())); + vtestps(x1, op); + } + + void uni_vblendvps(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, + const Xbyak::Operand &op, const Xbyak::Xmm &msk) { + assert(x1.getIdx() == x2.getIdx()); + assert(msk.getIdx() == 0); + blendvps(x1, op); + } + void uni_vblendvps(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, + const Xbyak::Operand &op, const Xbyak::Ymm &msk) { + vblendvps(x1, x2, op, msk); + } + + void uni_vroundps(const Xbyak::Xmm &x, const Xbyak::Operand &op, + const int imm) { + roundps(x, op, imm); + } + void uni_vroundps(const Xbyak::Ymm &x, const Xbyak::Operand &op, + const int imm) { + vroundps(x, op, imm); + } + + void uni_vcvtps2dq(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + cvtps2dq(x, op); + } + void uni_vcvtps2dq(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + vcvtps2dq(x, op); + } + + void uni_vcvtdq2ps(const Xbyak::Xmm &x, const Xbyak::Operand &op) { + cvtdq2ps(x, op); + } + void uni_vcvtdq2ps(const Xbyak::Ymm &x, const Xbyak::Operand &op) { + vcvtdq2ps(x, op); + } + + void uni_vmovmskps(const Xbyak::Reg &x1, const Xbyak::Xmm &x2) { + movmskps(x1.cvt64(), x2); + } + void uni_vmovmskps(const Xbyak::Reg &x1, const Xbyak::Ymm &x2) { + vmovmskps(x1, x2); + } + + void uni_vpackssdw(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, const Xbyak::Operand &op){ + assert(x1.getIdx() == x1.getIdx()); + packssdw(x1, op); + } + void uni_vpackssdw(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, const Xbyak::Operand &op){ + vpackssdw(x1, x2, op); + } + + void uni_vpackuswb(const Xbyak::Xmm &x1, const Xbyak::Xmm &x2, const Xbyak::Operand &op){ + assert(x1.getIdx() == x1.getIdx()); + packuswb(x1, op); + } + void uni_vpackuswb(const Xbyak::Ymm &x1, const Xbyak::Ymm &x2, const Xbyak::Operand &op){ + vpackuswb(x1, x2, op); + } + + + void mul_by_const(const Xbyak::Reg &out, + const Xbyak::Reg64 &tmp, int value) { + // Generates a shift + add sequence for multiplicating contents of the + // out register by a known JIT-time value. Clobbers the tmp register. + // + // Pros compared to mul/imul: + // - does not require using known registers + // - not microcoded on Intel(R) Xeon Phi(TM) processors + // Still, there are probably a lot of cases when mul/imul is faster on + // Intel(R) Core(TM) processors. Not intended for critical path. + + // TODO: detect when overflow is emminent (Roma) + // TODO: detect when using mul/imul is a better option (Roma) + + int p = 0; // the current power of 2 + int old_p = 0; // the last seen power of 2 such that value[old_p] != 0 + + xor_(tmp, tmp); + while (value) { + if (value & 1) { + int shift = p - old_p; + if (shift) { + shl(out, shift); + old_p = p; + } + add(tmp, out); + } + value >>= 1; + p++; + } + mov(out, tmp); + } + +public: + jit_generator( + void *code_ptr = nullptr, + size_t code_size = 256 * 1024 + ) : Xbyak::CodeGenerator(code_size, code_ptr) + { + } + virtual ~jit_generator() {} + + virtual const char *name() const = 0; + virtual const char *source_file() const = 0; + + const Xbyak::uint8 *getCode() { + const Xbyak::uint8 *code = CodeGenerator::getCode(); + size_t code_size = getSize(); + jit_utils::register_jit_code(code, code_size, name(), source_file()); + return code; + } + + template const F getCode() { + return (const F)getCode(); + } +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_primitive_conf.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_primitive_conf.hpp new file mode 100644 index 000000000000..56d7f592e2a2 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_primitive_conf.hpp @@ -0,0 +1,481 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_PRIMITIVE_CONF_HPP +#define JIT_PRIMITIVE_CONF_HPP + +#include + +#include "common/primitive_attr.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +/* convolution */ +enum conv_version_t {ver_unused, ver_fma, ver_avx512_core, ver_4fma, ver_vnni}; +enum conv_loop_order_t {loop_cgn, loop_gnc, loop_ngc, loop_gncw, loop_cwgn, + loop_ngcw, loop_nhwcg, loop_nwcg}; +enum conv_1x1_loop_order_t {loop_rbl, loop_rlb, loop_lbr, loop_lrb, loop_blr, + loop_brl}; +enum conv_kernel_kind_t {embd_bcast, expl_bcast}; + +enum { + FLAG_MB_FIRST = 1 << 0, FLAG_MB_LAST = 1 << 1, + FLAG_OC_FIRST = 1 << 2, FLAG_OC_LAST = 1 << 3, + FLAG_IC_FIRST = 1 << 4, FLAG_IC_LAST = 1 << 5, + FLAG_SP_FIRST = 1 << 6, FLAG_SP_LAST = 1 << 7, + FLAG_REDUCE_FIRST = 1<<8, FLAG_REDUCE_LAST = 1<<9, + FLAG_ZERO_FILTER = 1 << 0, /* Controls whether the inner kernel skips + loading weights-data from memory; this + needs to happen on the first Group/16 + iteration. */ + FLAG_ZERO_BIAS = 1 << 1, /* Controls whether the inner kernel skip + loading bias data from memory */ + FLAG_COMPUTE_BIAS = 1 << 2, /* Controls bias computation during execution + pass */ +}; + +struct jit_conv_conf_t { + prop_kind_t prop_kind; + conv_version_t ver; + conv_loop_order_t loop_order; + + int simd_w; + int ndims; + int mb; + int ngroups, ic, oc, oc_without_padding, ic_without_padding; + int id, ih, iw, od, oh, ow; + int f_pad, l_pad, t_pad; + int back_pad, r_pad, b_pad; + int kd, kh, kw; + int stride_d, stride_h, stride_w; + int dilate_d, dilate_h, dilate_w; + format_tag_t src_tag, wei_tag, dst_tag; // temporary workaround + bool with_bias; + bool with_sum; + bool with_eltwise; + + post_ops_t::entry_t::eltwise_t eltwise; + + int nthr, nthr_mb, nthr_g, nthr_oc_b, nthr_ic_b; + + int idp, ihp, iwp, ohp, owp; + int nb_ic, ic_block; + int nb_oc, oc_block; + int nb_ow, ow_block; + int nb_oc_blocking; /* used in jit kernels for nb_oc work bloking taking + into account vector registers distribution */ + int nb_oc_blocking_thr_chunk; /* used for distibution of nb_oc work + within threads */ + int nb_ic_blocking, nb_ic_blocking_max; // blocking of nb_ic work + int nb_ic_L2; + int h_blocking; + int nb_oc_L2; + int ur_h, ur_w; + int ur_w_tail; + bool is_1stconv; + int nonblk_group_off; + /* fma avx512_core */ + conv_kernel_kind_t kernel_kind; + /* 4fma */ + int tr_iw; + int tr_src_num_guard_elems; + /* 1st conv: 4fma */ + int tr_ld; + int kh_step; + /* 4vnni */ + int typesize_in; + int typesize_out; + int typesize_bia; + int typesize_acc; + /* avx512_u8s8u8 */ + int ic_nb1, ic_nb2; + int oc_nb1; + int ur_ow_max, ur_ow, ur_ow_tail; + int ur_ow_nsteps; + data_type_t bia_dt; + data_type_t dst_dt; + /* avx512: max possible value is nregs(32) - aux_regs(4) */ + int src_offsets[28]; + int src_count; + bool expl_bcast; + bool large_spatial; + int is_oc_scale; + int max_regs_ur; // maximum accumulation registers + // dw conv + int nb_ch, ch_block, nb_ch_blocking; + bool is_depthwise, is_fast_depthwise, is_resrc_depthwise; + int aligned_threads; + // large spatial + int oh_blk_size; + // s8s8 convolution + bool signed_input; + float wei_adj_scale; +}; + +struct jit_conv_conf_2x3_wino_t { + conv_version_t ver; + + int m; + int r; + int alpha; + int tile_h, tile_w; + + int mb; + int ngroups, ic, oc, oc_without_padding; + int ih, iw, oh, ow; + int l_pad, t_pad; + int r_pad, b_pad; + int kh, kw; + int stride_h, stride_w; + int dilate_h, dilate_w; + + int nb_ic, ic_block; + int nb_oc, oc_block; + + int w_block_size, h_block_size; + + data_type_t bia_dt; + data_type_t dst_dt; + + int is_oc_scale; + int typesize_in; + int typesize_out; + int typesize_bia; + int typesize_acc; + + format_tag_t src_tag, dst_tag; // temporary workaround + bool with_bias; + bool small_mb; + + int xb, yb; + int inp_stride; + int out_stride; + int wei_stride; + int bia_stride; + + int M, N, K; + int m_block, n_block, k_block; + int n2_block, n_chunks; + int k2_block, k_chunks; + + int mb_block, nb_mb; + + size_t size_wino_src, size_wino_wei, size_wino_dst; + + int nthr; +}; + +/* + Winograd sched policy: + + Computation Unit: + W: weights transform + S: src transform + D: dst transform + G: gemm + + Thread grouping by: + i: nb_ic + o: nb_oc + t: tile_block + e: element in tile + + Note: 'i' and 'o' are omited if + i. not comblined with t or + ii. with discrete transforms + + Current policies supported: +*/ +enum winograd_sched_t { + WSCHED_INVALID = 0, + + /* Forward & backward-data */ + /* W_S_G_D implements discrete transforms */ + WSCHED_DATA_W_S_G_D, + /* W_SGD implements tiled transforms s.t. GEMM could reuse data in L2*/ + WSCHED_DATA_W_SGD, + + /* Backward-weights */ + WSCHED_WEI_S_D_G_W, + WSCHED_WEI_SDGtWo, + WSCHED_WEI_S_D_Giot_W, + WSCHED_WEI_SDGt_W, +}; + +struct jit_conv_winograd_conf_t : public jit_conv_conf_t { + int itiles; + int jtiles; + int ntiles; + int ic_simd_block=16; + int tile_4fma_padding; + int tile_4fma; + int oc_simd_block=16; + int oc_reg_block; + int ic_reg_block; + int tile_block; + int tile_block_ur; + int nb_tile_block_ur; + + bool double_buffering; + bool with_relu_postsum; + int zmm_start; + int nb_reg; + + int dimK; + int dimK_4fma; + int dimK_reg_block; + int dimK_block; + int dimK_nb_block; + + int dimM; + int dimM_reg_block; + int dimM_simd_block; + int dimM_block; + int dimM_nb_block; + + int dimN; + int dimN_reg_block; + int dimN_bcast_ur; + int dimN_block; + int dimN_nb_block; + + winograd_sched_t sched_policy; +}; + +struct jit_conv_call_s { + const void *src; /* hack, non-const for backward_data */ + const void *dst; /* hack, non-const for forward */ + const void *filt; /* hack, non-const for backward_weights */ + const void *bias; /* hack, non-const for backward_bias */ + const void *src_prf; + const void *dst_prf; + const void *filt_prf; + const void *bias_prf; + const void *scales; + const void *acc_s32; + const void *compensation; + size_t kd_offset; + size_t kd_offset_prf; + size_t d_index; + size_t d_index_prf; + size_t d_worksize; + size_t d_worksize_prf; + size_t kd_padding; + size_t kd_padding_prf; + size_t kh_padding; + size_t kh_padding_prf; + size_t owb; + size_t owb_prf; + size_t kw_padding; + size_t channel; + size_t channel_prf; + size_t oc_blocks; + size_t ur_w; + size_t ur_str_w; + size_t ch_blocks; + size_t t_overflow; + size_t b_overflow; + int flags; +}; + +struct jit_deconv_call_s { + const void *src; /* hack, non-const for backward_data */ + const void *dst; /* hack, non-const for forward */ + const void *filt; /* hack, non-const for backward_weights */ + const void *bias; /* hack, non-const for backward_bias */ + const void *scales; + const void *compensation; + size_t t_overflow; + size_t b_overflow; + size_t kh_padding; + size_t oc_blocks; +}; + +struct jit_dw_conv_call_s { + const void *input; + const void *output; + const void *filter; + const void *bias; + size_t kh_count; + size_t oh_count; + size_t oh_index; + size_t filter_pad_off; + unsigned char + exec_flags; /* Flags passed by driver execution to inner kernel */ +}; + +struct jit_wino_transform_call_s { + size_t tile_block; + size_t tile_block_ur; + size_t nb_tile_block_ur; + size_t tile_count; + size_t tj; + size_t ti; + void *src; + void *dst; + void *Mw; + void *M; + void *T; + void *G; + void *bias; +}; + +struct jit_1x1_conv_conf_t { + prop_kind_t prop_kind; + conv_version_t ver; + + int mb; + int ngroups, ic, oc, oc_without_padding, ic_without_padding; + int iw, ih, ow, oh; + int l_pad, t_pad; + int kh, kw; + int stride_h, stride_w; + format_tag_t src_tag, wei_tag, dst_tag; // temporary workaround + bool with_bias; + bool with_sum; + bool with_eltwise; + + post_ops_t::entry_t::eltwise_t eltwise; + + int is, os; + int ic_block, oc_block; + + int ur, ur_tail; + + int reduce_dim, reduce_block, nb_reduce, + nb_reduce_blocking, nb_reduce_blocking_max; + int load_dim, load_block, nb_load, + nb_load_blocking, nb_load_blocking_max, nb_load_chunk; + int bcast_dim, bcast_block, nb_bcast, + nb_bcast_blocking, nb_bcast_blocking_max; + + int reduce_loop_unroll, reduce_loop_bcast_step, reduce_loop_load_step; + int load_loop_load_step, load_loop_iter_step; + int bcast_loop_output_step, bcast_loop_output_substep; + int bcast_loop_bcast_step, bcast_loop_bcast_substep; + int fma_step; + int load_grp_count; + conv_1x1_loop_order_t loop_order; + bool use_vmovntps; + /* avx512 core */ + bool expl_bcast; + /* 4vnni */ + int typesize_in; + int typesize_out; + int typesize_bia; + int typesize_acc; + /* 4fma */ + bool transpose_src; + int tr_is; + int nthr, nthr_mb, nthr_g, nthr_oc_b, nthr_ic_b; + int is_oc_scale; + data_type_t bia_dt; + data_type_t dst_dt; + bool signed_input; + float wei_adj_scale; +}; + +struct jit_gemm_conv_conf_t { + prop_kind_t prop_kind; + + int mb; + int ngroups, ic, oc; + int iw, ih, id, ow, oh, od; + int l_pad, t_pad, f_pad; + int kh, kw, kd; + int stride_h, stride_w, stride_d; + int dilate_h, dilate_w, dilate_d; + bool with_bias; + + int is, os, ks; + int ic_block, oc_block; + + int nthr; + ptrdiff_t im2col_sz; + bool need_wei_reduction; + bool signed_input; + int oh_block; + int ow_block; + bool outer_threading; +}; + +struct jit_1x1_conv_call_s { + const void *bcast_data; + const void *load_data; + const void *output_data; + const void *bias_data; // used in forward and backward_weights only + const void *acc_s32; + const void *scales; + const void *compensation; + + size_t load_dim; + size_t bcast_dim; + size_t reduce_dim; + + size_t output_stride; // used in backward_weights only + + size_t first_last_flag; +}; + +/* pooling */ +struct jit_pool_conf_t { + int ndims; + int mb, c; + int id, ih, iw, od, oh, ow; + int stride_d, stride_h, stride_w; + int kd, kh, kw; + int f_pad, t_pad, l_pad; + alg_kind_t alg; + bool is_training; + bool pad_w_is_null; + bool is_backward; + bool simple_alg; + data_type_t ind_dt; + + int c_block, c_tail, nb_c; + int ur_c, ur_c_tail; + int ur_w; + int ur_w_tail; + size_t tail[4]; + data_type_t src_dt; + data_type_t dst_dt; +}; + +struct jit_pool_call_s { + const float *src; + const float *dst; + const void *indices; + const float *src_prf; + const float *dst_prf; + const void *indices_prf; + size_t oh; + size_t kd_padding; + size_t kh_padding; + size_t kh_padding_shift; + size_t kd_padding_shift; + size_t kw_padding; + const float* init_value; + float ker_area_h; +}; + + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.cpp new file mode 100644 index 000000000000..94d2101d6eab --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.cpp @@ -0,0 +1,677 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "cpu_memory.hpp" + +#include "jit_sse42_1x1_conv_kernel_f32.hpp" + +#define GET_OFF(field) offsetof(jit_1x1_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +void jit_sse42_1x1_conv_kernel_f32::generate_bcast_loop(int load_loop_blk) +{ + mov(aux1_reg_bcast_data, reg_bcast_data); + mov(aux_reg_output_data, reg_output_data); + mov(bcast_loop_iter, reg_bcast_loop_work); + + Label bcast_loop; + Label bcast_loop_tail; + + cmp(bcast_loop_iter, jcp.ur); + jl(bcast_loop_tail, T_NEAR); + + L(bcast_loop); { + assert(jcp.bcast_block % jcp.ur == 0); + int num_substeps = jcp.bcast_block / jcp.ur; + assert(num_substeps > 0 && num_substeps < 10); + for (int i = 0; i < num_substeps; i++) { + generate_reduce_loop(load_loop_blk, jcp.ur); + if (i < num_substeps - 1) { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_substep); + } else { + add(aux1_reg_bcast_data, jcp.bcast_loop_bcast_step + - (num_substeps - 1) * jcp.bcast_loop_bcast_substep); + add(aux_reg_output_data, jcp.bcast_loop_output_step + - (num_substeps - 1) * jcp.bcast_loop_output_substep); + } + } + sub(bcast_loop_iter, jcp.bcast_block); + cmp(bcast_loop_iter, jcp.bcast_block); + jge(bcast_loop, T_NEAR); + } + + L(bcast_loop_tail); + if (jcp.ur_tail) { + Label bcast_loop_tail_out; + cmp(bcast_loop_iter, 0); + jz(bcast_loop_tail_out, T_NEAR); + generate_reduce_loop(load_loop_blk, jcp.ur_tail); + L(bcast_loop_tail_out); + } +} + +void jit_sse42_1x1_conv_kernel_f32::generate_reduce_loop( + int load_loop_blk, int ur) +{ + auto reg_load = [=](int i, int n) { + return Xmm(2*ur * load_loop_blk + 2*i + n + 1); + }; + + auto reg_accum = [=](int i, int j, int n) { + return Xmm(2*j * load_loop_blk + 2*i + n + 1); + }; + + auto bias_ptr = [=](int i, int n) { + return ptr[reg_bias_data + sizeof(float) * jcp.oc_block * i + n*4*sizeof(float)]; + }; + + auto bcast_ptr = [=](int u, int j) { + assert(j < jcp.ur); + assert(u <= jcp.reduce_loop_unroll); + size_t offt; + if (one_of(jcp.prop_kind, + forward_training, forward_inference, backward_data)) { + assert(jcp.reduce_loop_unroll == (jcp.prop_kind == backward_data) + ? jcp.oc_block : jcp.ic_block); + auto height = (jcp.prop_kind == backward_data) ? jcp.os : jcp.is; + offt = (u == jcp.reduce_loop_unroll) + ? (height + j) * jcp.reduce_loop_unroll + : j * jcp.reduce_loop_unroll + u; + } else + offt = u * jcp.ic_block + j; + return ptr[aux_reg_bcast_data + sizeof(float) * offt]; + }; + + auto load_ptr = [=](int u, int i, int n) { + size_t offt; + size_t u0 = u % jcp.reduce_loop_unroll; + size_t u1 = u / jcp.reduce_loop_unroll; + switch (jcp.prop_kind) { + case backward_data: + offt = (i * jcp.oc_block + u0) * jcp.ic_block; + break; + case backward_weights: + offt = (i * jcp.os + u0) * jcp.oc_block; + break; + default: + offt = (i * jcp.ic + u0) * jcp.oc_block; + } + return ptr[aux_reg_load_data + + u1 * jcp.reduce_loop_load_step + sizeof(float) * offt + n * 4 * sizeof(float)]; + }; + + auto output_ptr = [=](int i, int j, int n) { + switch (jcp.prop_kind) { + case backward_data: + return ptr[aux_reg_output_data + + (i * jcp.is + j) * jcp.ic_block * sizeof(float) + n * 4 * sizeof(float)]; + case backward_weights: + return ptr[aux_reg_output_data + + (i ? reg_output_stride * i : 0) // TODO: Xbyak should allow 0 scale + + sizeof(float) * jcp.oc_block * j + n * 4 * sizeof(float)]; + default: + return ptr[aux_reg_output_data + + (i * jcp.os + j) * jcp.oc_block * sizeof(float) + n*4*sizeof(float)]; + } + }; + + auto init = [=]() { + Label init_done; + Label init_zero; + + if (jcp.with_bias && one_of(jcp.prop_kind, forward_training, + forward_inference)) { + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jz(init_zero); + + for (int i = 0; i < load_loop_blk; i++) + for (int j = 0; j < ur; ++j) { + movups(reg_accum(i, j, 0), bias_ptr(i, 0)); + movups(reg_accum(i, j, 1), bias_ptr(i, 1)); + } + jmp(init_done); + } + + L(init_zero); + for (int i = 0; i < load_loop_blk; ++i) + for (int j = 0; j < ur; ++j) { + auto r0 = reg_accum(i, j, 0); + auto r1 = reg_accum(i, j, 1); + xorps(r0, r0); + xorps(r1, r1); + } + + L(init_done); + + // load weights + for (int i = 0; i < load_loop_blk; ++i) { + movups(reg_load(i, 0), load_ptr(0, i, 0)); + movups(reg_load(i, 1), load_ptr(0, i, 1)); + } + + movss(reg_bcast, bcast_ptr(0, 0)); + shufps(reg_bcast, reg_bcast, 0); + }; // init() + + auto store = [=]() { + Label store_noadd; + + if (!jcp.with_sum) { + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jnz(store_noadd, T_NEAR); + } + + for (int j = 0; j < ur; ++j) + for (int i = 0; i < load_loop_blk; ++i) { + auto r0 = reg_accum(i, j, 0); + auto r1 = reg_accum(i, j, 1); + addps(r0, output_ptr(i, j, 0)); + addps(r1, output_ptr(i, j, 1)); + } + + L(store_noadd); + + if (jcp.with_eltwise) { + assert(ur * load_loop_blk < 14); + + Label store_norelu; + test(reg_reduce_pos_flag, FLAG_REDUCE_LAST); + jz(store_norelu, T_NEAR); + + eltwise_injector_->compute_vector_range(1, + 2 * ur * load_loop_blk + 1); + + L(store_norelu); + } + + for (int j = 0; j < ur; ++j) + for (int i = 0; i < load_loop_blk; ++i) { + movups(output_ptr(i, j, 0), reg_accum(i, j, 0)); + movups(output_ptr(i, j, 1), reg_accum(i, j, 1)); + } + }; + + auto fma_block = [=](bool last_block) { + for (int u = 0; u < jcp.reduce_loop_unroll; ++u) { + for (int j = 0; j < ur; ++j) { + for (int i = 0; i < load_loop_blk; ++i) { + mulps(reg_load(i, 0), reg_bcast); + mulps(reg_load(i, 1), reg_bcast); + addps(reg_accum(i, j, 0), reg_load(i, 0)); + addps(reg_accum(i, j, 1), reg_load(i, 1)); + + if (j == ur - 1 && !(last_block && u == jcp.reduce_loop_unroll - 1)) { + movups(reg_load(i, 0), load_ptr(u + 1, i, 0)); + movups(reg_load(i, 1), load_ptr(u + 1, i, 1)); + } + } + if (j < ur - 1) { + movss(reg_bcast, bcast_ptr(u, j + 1)); + shufps(reg_bcast, reg_bcast, 0); + } + } // for ur + if (!last_block || u < jcp.reduce_loop_unroll - 1) { + movss(reg_bcast, bcast_ptr(u + 1, 0)); + shufps(reg_bcast, reg_bcast, 0); + } + } // for reduce_loop_unroll + }; + + Label reduce_loop; + Label reduce_loop_tail; + + mov(aux_reg_load_data, reg_load_data); + mov(aux_reg_bcast_data, aux1_reg_bcast_data); + + init(); + + mov(reduce_loop_iter, reg_reduce_loop_work); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jle(reduce_loop_tail, T_NEAR); + + L(reduce_loop); { + fma_block(false); + add(aux_reg_bcast_data, jcp.reduce_loop_bcast_step); + add(aux_reg_load_data, jcp.reduce_loop_load_step); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jg(reduce_loop, T_NEAR); + } + + L(reduce_loop_tail); + fma_block(true); + + store(); +} // reduce_loop() + +void jit_sse42_1x1_conv_kernel_f32::generate_diff_bias_loop(int load_loop_blk) +{ + if (!jcp.with_bias || jcp.prop_kind != backward_weights) + return; + + Label diff_bias_loop, diff_bias_loop_out, diff_bias_init_out; + Label diff_bias_load; + + auto diff_bias_ptr = [=](int i, int n) { + return ptr[reg_diff_bias_data + i * jcp.oc_block * sizeof(float)+ 4*n*sizeof(float)]; + }; + + auto load_ptr = [=](int u, int i, int n) { + return ptr[aux_reg_load_data + + (i * jcp.os + u) * jcp.oc_block * sizeof(float) + 4*n*sizeof(float)]; + }; + + auto diff_bias_reg = [=](int i, int n) { return Xmm(2*i + n + 1); }; + + mov(reg_diff_bias_data, ptr[rsp + reg_diff_bias_data_stack_offt]); + cmp(reg_diff_bias_data, 0); + je(diff_bias_loop_out, T_NEAR); + + test(reg_reduce_pos_flag, FLAG_REDUCE_FIRST); + jz(diff_bias_load, T_NEAR); + + for (int i = 0; i < load_loop_blk; ++i) { + auto r0 = diff_bias_reg(i, 0); + auto r1 = diff_bias_reg(i, 1); + xorps(r0, r0); + xorps(r1, r1); + } + jmp(diff_bias_init_out, T_NEAR); + + L(diff_bias_load); + for (int i = 0; i < load_loop_blk; ++i) { + movups(diff_bias_reg(i, 0), diff_bias_ptr(i, 0)); + movups(diff_bias_reg(i, 1), diff_bias_ptr(i, 1)); + } + + L(diff_bias_init_out); + mov(aux_reg_load_data, reg_load_data); + mov(reduce_loop_iter, reg_reduce_loop_work); + L(diff_bias_loop); { + for(int u = 0; u < jcp.reduce_loop_unroll; ++u) + for (int i = 0; i < load_loop_blk; ++i) { + addps(diff_bias_reg(i, 0), load_ptr(u, i, 0)); + addps(diff_bias_reg(i, 1), load_ptr(u, i, 1)); + } + assert(jcp.reduce_dim % jcp.reduce_loop_unroll == 0); + add(aux_reg_load_data, jcp.reduce_loop_load_step); + sub(reduce_loop_iter, jcp.reduce_loop_unroll); + jnz(diff_bias_loop, T_NEAR); + } + + for (int i = 0; i < load_loop_blk; i++) { + movups(diff_bias_ptr(i, 0), diff_bias_reg(i, 0)); + movups(diff_bias_ptr(i, 1), diff_bias_reg(i, 1)); + } + + add(reg_diff_bias_data, load_loop_blk * jcp.oc_block * sizeof(float)); + mov(ptr[rsp + reg_diff_bias_data_stack_offt], reg_diff_bias_data); + + L(diff_bias_loop_out); +} + +void jit_sse42_1x1_conv_kernel_f32::generate() +{ + preamble(); + + mov(reg_bcast_data, ptr[param1 + GET_OFF(bcast_data)]); + mov(reg_load_data, ptr[param1 + GET_OFF(load_data)]); + mov(reg_output_data, ptr[param1 + GET_OFF(output_data)]); + if (jcp.with_bias) { + if (jcp.prop_kind == backward_weights) { + sub(rsp, stack_space_needed); + mov(reg_diff_bias_data, ptr[param1 + GET_OFF(bias_data)]); + mov(ptr[rsp + reg_diff_bias_data_stack_offt], reg_diff_bias_data); + } else + mov(reg_bias_data, ptr[param1 + GET_OFF(bias_data)]); + } + + mov(reg_load_loop_work, ptr[param1 + GET_OFF(load_dim)]); + mov(reg_bcast_loop_work, ptr[param1 + GET_OFF(bcast_dim)]); + mov(reg_reduce_loop_work, ptr[param1 + GET_OFF(reduce_dim)]); + mov(reg_reduce_pos_flag, ptr[param1 + GET_OFF(first_last_flag)]); + if (jcp.prop_kind == backward_weights) + mov(reg_output_stride, ptr[param1 + GET_OFF(output_stride)]); + + auto generate_load_loop_body = [=] (int load_loop_blk) { + generate_bcast_loop(load_loop_blk); + add(reg_load_data, load_loop_blk * jcp.load_loop_load_step); + switch (jcp.prop_kind) { + case forward_training: + case forward_inference: + add(reg_bias_data, load_loop_blk * jcp.oc_block * sizeof(float)); + add(reg_output_data, + load_loop_blk * jcp.os * jcp.oc_block * sizeof(float)); + break; + case backward_data: + add(reg_output_data, + load_loop_blk * jcp.is * jcp.ic_block * sizeof(float)); + break; + case backward_weights: + for (int i = 0; i < load_loop_blk; i++) + add(reg_output_data, reg_output_stride); + break; + default: + assert(!"invalid prop_kind"); + } + sub(reg_load_loop_work, load_loop_blk * jcp.load_loop_iter_step); + }; + + Label load_loop_blk_8; + Label load_loop_blk_16; + Label load_loop_blk_24; + Label load_loop_blk_end; + + cmp(reg_load_loop_work, 8); + jle(load_loop_blk_8, T_NEAR); + + cmp(reg_load_loop_work, 32); + je(load_loop_blk_16, T_NEAR); + + cmp(reg_load_loop_work, 16); + jle(load_loop_blk_16, T_NEAR); + + L(load_loop_blk_24); { + generate_diff_bias_loop(3); + generate_load_loop_body(3); + cmp(reg_load_loop_work, 32); + je(load_loop_blk_16); + cmp(reg_load_loop_work, 24); + jge(load_loop_blk_24); + } + + cmp(reg_load_loop_work, 8); + jle(load_loop_blk_8, T_NEAR); + + L(load_loop_blk_16); { + generate_diff_bias_loop(2); + generate_load_loop_body(2); + cmp(reg_load_loop_work, 16); + jge(load_loop_blk_16); + } + + L(load_loop_blk_8); { + cmp(reg_load_loop_work, 0); + je(load_loop_blk_end, T_NEAR); + generate_diff_bias_loop(1); + generate_load_loop_body(1); + } + + L(load_loop_blk_end); + + if (jcp.with_bias && jcp.prop_kind == backward_weights) + add(rsp, stack_space_needed); + + postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_sse42_1x1_conv_kernel_f32::post_ops_ok( + jit_1x1_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +status_t jit_sse42_1x1_conv_kernel_f32::init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr) +{ + if (!mayiuse(sse42)) + return status::unimplemented; + + // TODO (Roma): this code is duplicated from the generic kernel; maybe the + // configuration struct could do some stuff below + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + const int ndims = src_d.ndims(); + + jcp.prop_kind = cd.prop_kind; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.oh = (ndims == 3) ? 1 : dst_d.dims()[2]; + jcp.ow = dst_d.dims()[ndims - 1]; + + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][0]; + jcp.l_pad = cd.padding[0][ndims - 3]; + + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[0]; + jcp.stride_w = cd.strides[ndims - 3]; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + jcp.os = jcp.oh * jcp.ow; + jcp.is = jcp.ih * jcp.iw; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + const int is_bwd_d = jcp.prop_kind == backward_data; + + format_tag_t dat_tag = ndims == 3 ? nCw8c : nChw8c; + format_tag_t wei_tag = with_groups + ? utils::pick(2 * ndims - 6 + is_bwd_d, gOIw8i8o, gOIw8o8i, gOIhw8i8o, + gOIhw8o8i) + : utils::pick(2 * ndims - 6 + is_bwd_d, OIw8i8o, OIw8o8i, OIhw8i8o, + OIhw8o8i); + + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.ngroups == 1 + && jcp.src_tag == dat_tag + && jcp.wei_tag == wei_tag + && jcp.dst_tag == dat_tag; + if (!args_ok) return status::unimplemented; + + const int simd_w = 4; + jcp.ic_block = jcp.oc_block = simd_w*2; + + args_ok = true + && jcp.oc % jcp.oc_block == 0 + && jcp.ic % jcp.ic_block == 0 + && jcp.t_pad == 0 && jcp.l_pad == 0 + && jcp.stride_w == 1 && jcp.stride_h == 1 // TODO: support some strides + && jcp.kh == 1 && jcp.kw == 1; + if (!args_ok) return status::unimplemented; + + jcp.ur = 1; + + int load_blocking{ 0 }; + int load_blocking_max{ 0 }; + int bcast_blocking{ 0 }; + int bcast_blocking_max{ 0 }; + int reduce_blocking{ 0 }; + + if (one_of(jcp.prop_kind, forward_training, forward_inference)) { + jcp.reduce_dim = jcp.ic; + jcp.reduce_block = jcp.ic_block; + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.is; + jcp.bcast_block = jcp.ur; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.is * sizeof(float); + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.oc_block * sizeof(float); + + jcp.bcast_loop_output_step = jcp.ur * jcp.oc_block * sizeof(float); + jcp.bcast_loop_output_substep = -1; // unused + jcp.bcast_loop_bcast_step = jcp.ur * jcp.ic_block * sizeof(float); + jcp.bcast_loop_bcast_substep = -1; // unused + + jcp.load_loop_load_step = jcp.ic * jcp.oc_block * sizeof(float); + jcp.load_loop_iter_step = jcp.oc_block; + + load_blocking = 120; // assumes the kernel is jcp.ur x 3 + load_blocking_max = 144; + bcast_blocking = 128; // affects load balancing across threads + bcast_blocking_max = 192; + reduce_blocking = 128; // affects L1$ utilization + } else if (jcp.prop_kind == backward_data) { + jcp.reduce_dim = jcp.oc; + jcp.reduce_block = jcp.oc_block; + + jcp.load_dim = jcp.ic; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.os; + jcp.bcast_block = jcp.ur; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.os * sizeof(float); + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.ic * sizeof(float); + + jcp.bcast_loop_output_step = jcp.ur * jcp.ic_block * sizeof(float); + jcp.bcast_loop_output_substep = -1; // unused + jcp.bcast_loop_bcast_step = jcp.ur * jcp.oc_block * sizeof(float); + jcp.bcast_loop_bcast_substep = -1; // unused + + jcp.load_loop_load_step = jcp.oc_block * jcp.ic_block * sizeof(float); + jcp.load_loop_iter_step = jcp.ic_block; + + load_blocking = 96; // assumes the kernel is jcp.ur x 3 + load_blocking_max = 144; + bcast_blocking = 128; // affects load balancing across threads + bcast_blocking_max = 196; + reduce_blocking = 64; // affects L1$ utilization + } else if (jcp.prop_kind == backward_weights) { + jcp.reduce_dim = jcp.os; + jcp.reduce_block = 1; + + jcp.load_dim = jcp.oc; + jcp.load_block = jcp.oc_block; + + jcp.bcast_dim = jcp.ic; + jcp.bcast_block = jcp.ic_block; + + jcp.reduce_loop_unroll = jcp.reduce_block; + jcp.reduce_loop_bcast_step + = jcp.reduce_loop_unroll * jcp.ic_block * sizeof(float); + jcp.reduce_loop_load_step + = jcp.reduce_loop_unroll * jcp.oc_block * sizeof(float); + + jcp.bcast_loop_output_step = jcp.oc_block * jcp.ic_block * sizeof(float); + jcp.bcast_loop_output_substep = jcp.oc_block * jcp.ur * sizeof(float); + jcp.bcast_loop_bcast_step = jcp.ic_block * jcp.is * sizeof(float); + jcp.bcast_loop_bcast_substep = jcp.ur * sizeof(float); + + jcp.load_loop_load_step = jcp.oc_block * jcp.os * sizeof(float); + jcp.load_loop_iter_step = jcp.oc_block; + + /* --- */ + + load_blocking = div_up(jcp.load_dim, jcp.load_block); + while (true) { + if (load_blocking <= 32) break; + else if (load_blocking % 2 == 0) load_blocking /= 2; + else if (load_blocking % 3 == 0) load_blocking /= 3; + else break; + } + load_blocking *= jcp.load_block; + load_blocking_max = load_blocking; + assert(jcp.load_dim % load_blocking == 0); + + bcast_blocking = div_up(jcp.bcast_dim, jcp.bcast_block); + while (true) { + if (bcast_blocking <= 9) break; + else if (bcast_blocking % 2 == 0) bcast_blocking /= 2; + else if (bcast_blocking % 3 == 0) bcast_blocking /= 3; + else break; + } + bcast_blocking *= jcp.bcast_block; + bcast_blocking_max = bcast_blocking; + assert(jcp.bcast_dim % bcast_blocking == 0); + + reduce_blocking = 128; // affects L1$ utilization + } else + return status::unimplemented; + + assert(load_blocking); + assert(load_blocking_max); + assert(bcast_blocking); + assert(bcast_blocking_max); + assert(reduce_blocking); + + assert(jcp.bcast_block % jcp.ur == 0); + jcp.ur_tail = jcp.bcast_dim % jcp.ur; + + jcp.nb_bcast_blocking = bcast_blocking / jcp.bcast_block; + jcp.nb_bcast_blocking_max = bcast_blocking_max / jcp.bcast_block; + jcp.nb_load_blocking = load_blocking / jcp.load_block; + jcp.nb_load_blocking_max = load_blocking_max / jcp.load_block; + jcp.nb_reduce_blocking = reduce_blocking / jcp.reduce_block; + + jcp.nb_bcast = div_up(jcp.bcast_dim, jcp.bcast_block); + jcp.nb_load = div_up(jcp.load_dim, jcp.load_block); + jcp.nb_reduce = div_up(jcp.reduce_dim, jcp.reduce_block); + + return status::success; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.hpp new file mode 100644 index 000000000000..b314a5098c3c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_conv_kernel_f32.hpp @@ -0,0 +1,104 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_SSE42_1x1_CONV_KERNEL_F32_HPP +#define JIT_SSE42_1x1_CONV_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "cpu_memory.hpp" +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_sse42_1x1_conv_kernel_f32: public jit_generator { + jit_sse42_1x1_conv_kernel_f32(jit_1x1_conv_conf_t ajcp, + const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32(this, + jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_1x1_conv_call_s *))this->getCode(); + } + + ~jit_sse42_1x1_conv_kernel_f32() { + delete eltwise_injector_; + } + + static bool post_ops_ok(jit_1x1_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_1x1_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr); + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_sse42_1x1_conv_kernel_f32) + + jit_1x1_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_1x1_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + using xmm_t = const Xbyak::Xmm; + + reg64_t reg_bcast_data = rax; + reg64_t reg_load_data = rsi; + reg64_t reg_output_data = rbx; + reg64_t aux_reg_bcast_data = rdx; + reg64_t aux1_reg_bcast_data = abi_not_param1; + reg64_t aux_reg_load_data = abi_param1; + reg64_t aux_reg_output_data = rbp; + reg64_t reg_load_loop_work = r9; + reg64_t reg_bcast_loop_work = r10; + reg64_t reg_reduce_loop_work = r11; + reg64_t load_loop_iter = r13; + reg64_t imm_addr64 = load_loop_iter; + reg64_t bcast_loop_iter = r14; + reg64_t reduce_loop_iter = r15; + reg64_t reg_reduce_pos_flag = r8; + reg64_t reg_output_stride = r12; + reg64_t reg_bias_data = r12; + reg64_t reg_diff_bias_data = bcast_loop_iter; + + int reg_diff_bias_data_stack_offt = 0; + int stack_space_needed = 8; + + xmm_t reg_bcast = xmm_t(15); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + void generate_bcast_loop(int load_loop_blk); + void generate_reduce_loop(int load_loop_blk, int ur); + void generate_diff_bias_loop(int load_loop_blk); + + void generate(); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.cpp new file mode 100644 index 000000000000..30c137641e3c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.cpp @@ -0,0 +1,134 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "jit_sse42_1x1_convolution.hpp" +#include "utils.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +#define data_blk_off(f, n, c, h, w) \ + ((ndims == 3) \ + ? (f).blk_off(n, c, w) \ + : (f).blk_off(n, c, h, w)) + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; + +void jit_sse42_1x1_convolution_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + const int ndims = src_d.ndims(); + + const int work_amount = jcp.mb * jcp.ngroups * jcp.nb_bcast; + + parallel(0, [&](const int ithr, const int nthr) { + // TODO (Roma): remove this restriction + assert(jcp.stride_w == 1 && jcp.stride_h == 1); + + auto par_conv = jit_1x1_conv_call_s(); + + const int nb_oc = jcp.nb_load; + const int nb_ic = jcp.nb_reduce; + const int nb_ic_blocking = jcp.nb_reduce_blocking; + const int os_block = jcp.bcast_block; + + int start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + + int iwork = start; + while (iwork < end) { + int n{0}, g{0}, osb{0}; + nd_iterator_init(iwork, n, jcp.mb, g, jcp.ngroups, osb, + jcp.nb_bcast); + + const int bcast_step_rem = jcp.nb_bcast - osb; + int bcast_step = bcast_step_rem <= jcp.nb_bcast_blocking_max + ? bcast_step_rem : jcp.nb_bcast_blocking; + bcast_step = nstl::min(bcast_step, end - iwork); + + const int os = osb * os_block; + const int ow = os % jcp.ow; + const int oh = os / jcp.ow; + const int iw = nstl::max(ow * jcp.stride_w - jcp.l_pad, 0); + const int ih = nstl::max(oh * jcp.stride_h - jcp.t_pad, 0); + + par_conv.bcast_dim = this_block_size(os, jcp.os, + bcast_step * os_block); + + int ocb = 0; + while (ocb < jcp.nb_load) { + const int load_step_rem = jcp.nb_load - ocb; + const int load_step = load_step_rem < jcp.nb_load_blocking_max + ? load_step_rem : jcp.nb_load_blocking; + + const size_t _ocb = g * nb_oc + ocb; + par_conv.load_dim = this_block_size(ocb * jcp.oc_block, jcp.oc, + load_step * jcp.oc_block); + + const size_t dst_off = data_blk_off(dst_d, n, _ocb, oh, ow); + par_conv.output_data = &dst[dst_off]; + + par_conv.bias_data = &bias[_ocb * jcp.oc_block]; + + for (int icb = 0; icb < nb_ic; icb += nb_ic_blocking) { + par_conv.first_last_flag = 0 + | (icb == 0) * FLAG_REDUCE_FIRST + | (icb + nb_ic_blocking >= nb_ic) * FLAG_REDUCE_LAST; + + par_conv.reduce_dim = this_block_size(icb * jcp.ic_block, + jcp.ic, nb_ic_blocking * jcp.ic_block); + + const size_t _icb = g * nb_ic + icb; + const size_t src_off = data_blk_off(src_d, n, _icb, ih, iw); + par_conv.bcast_data = &src[src_off]; + + par_conv.load_data = &weights[pd()->with_groups() + ? weights_d.blk_off(g, ocb, icb) + : weights_d.blk_off(ocb, icb)]; + + kernel_->jit_ker(&par_conv); + } + + ocb += load_step; + } + + iwork += bcast_step; + } + }); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.hpp new file mode 100644 index 000000000000..b32b1e4784da --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_1x1_convolution.hpp @@ -0,0 +1,96 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_SSE42_1x1_CONVOLUTION_HPP +#define CPU_JIT_SSE42_1x1_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" +#include "jit_sse42_1x1_conv_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_sse42_1x1_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_1x1:", sse42, ""), + jit_sse42_1x1_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + return jit_sse42_1x1_conv_kernel_f32::init_conf(jcp_, *desc(), + *src_md(), *weights_md(), *dst_md(), *attr()); + } + + jit_1x1_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, gOIw8i8o, gOIhw8i8o) + : utils::pick(ndims() - 3, OIw8i8o, OIhw8i8o); + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + jit_sse42_1x1_convolution_fwd_t(const pd_t *apd): cpu_primitive_t(apd) { + kernel_ = new jit_sse42_1x1_conv_kernel_f32(pd()->jcp_, *pd()->attr()); + } + ~jit_sse42_1x1_convolution_fwd_t() { delete kernel_; }; + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_sse42_1x1_conv_kernel_f32 *kernel_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp new file mode 100644 index 000000000000..17cabc11866c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp @@ -0,0 +1,497 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "cpu_memory.hpp" + +#include "jit_sse42_conv_kernel_f32.hpp" + +#define GET_OFF(field) offsetof(jit_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +void jit_sse42_conv_fwd_kernel_f32::oh_step_unroll_kw(int ur_w, + int pad_l, int pad_r, int oc_blocks) +{ + int iw = jcp.iw; + int ih = jcp.ih; + int kw = jcp.kw; + int kh = jcp.kh; + int nb_ic = jcp.nb_ic; + int stride_w = jcp.stride_w; + int dilate_w = jcp.dilate_w + 1; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + + for (int ki = 0; ki < kw; ki++) { + int jj_start = nstl::max(0, div_up(pad_l - ki * dilate_w, stride_w)); + int jj_end = ur_w + - nstl::max(0, div_up(ki*dilate_w + pad_r - (kw-1)*dilate_w, stride_w)); + for (int ifm2 = 0; ifm2 < ic_blk; ifm2++) { + for (int jj = jj_start; jj < jj_end; jj++) { + int inp_off; + if (one_of(jcp.src_tag, ncw, nchw)) + inp_off = ifm2*ih*iw + (ki*dilate_w + jj*stride_w - pad_l); + else + inp_off = (ki*dilate_w + jj*stride_w - pad_l)*ic_blk + ifm2; + + movss(Xmm(oc_blocks * ur_w + jj + 1), + ptr[aux_reg_input + sizeof(float) * inp_off]); + shufps(Xmm(oc_blocks * ur_w + jj + 1), + Xmm(oc_blocks * ur_w + jj + 1), 0x0); + } + + for (int ii = 0; ii < oc_blocks; ii++) { + int ker_off = ii * nb_ic * kh * kw * ic_blk * oc_blk + + ki * ic_blk * oc_blk + ifm2 * oc_blk; + + for (int jj = jj_start; jj < jj_end; jj++) + { + movups(xmm0, + ptr[aux_reg_kernel + sizeof(float) * ker_off]); + mulps(xmm0, Xmm(oc_blocks * ur_w + jj + 1)); + addps(Xmm(ur_w * ii + jj + 1), xmm0); + } + } + } + } +} + +void jit_sse42_conv_fwd_kernel_f32::oh_step_nopad(int ur_w, + int pad_l, int pad_r, int oc_blocks) +{ + Label kw_loop; + + int iw = jcp.iw; + int ih = jcp.ih; + int kw = jcp.kw; + int kh = jcp.kh; + int nb_ic = jcp.nb_ic; + int stride_w = jcp.stride_w; + int dilate_w = jcp.dilate_w + 1; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + + xor_(ki_iter, ki_iter); + L(kw_loop); + { + int jj_start = 0; + int jj_end = ur_w; + for (int ifm2 = 0; ifm2 < ic_blk; ifm2++) { + for (int jj = jj_start; jj < jj_end; jj++) { + int inp_off; + if (one_of(jcp.src_tag, ncw, nchw)) + inp_off = ifm2 * ih * iw + (jj * stride_w - pad_l); + else + inp_off = (jj * stride_w - pad_l) * ic_blk + ifm2; + + movss(Xmm(oc_blocks * ur_w + jj + 1), + ptr[aux_reg_input + sizeof(float) * inp_off]); + shufps(Xmm(oc_blocks * ur_w + jj + 1), + Xmm(oc_blocks * ur_w + jj + 1), 0x0); + } + for (int ii = 0; ii < oc_blocks; ii++) { + int aux_kernel_offset = ii * nb_ic * kh * kw * ic_blk * oc_blk + + ifm2 * oc_blk; + for (int jj = jj_start; jj < jj_end; jj++) { + movups(xmm0, + ptr[aux_reg_kernel + sizeof(float) * aux_kernel_offset]); + mulps(xmm0, Xmm(oc_blocks * ur_w + jj + 1)); + addps(Xmm(ur_w * ii + jj + 1), xmm0); + } + } + } + add(aux_reg_kernel, sizeof(float) * oc_blk * ic_blk); + add(aux_reg_input, sizeof(float) * (one_of(jcp.src_tag, ncw, nchw) ? + dilate_w : ic_blk * dilate_w)); + + inc(ki_iter); + cmp(ki_iter, kw); + jl(kw_loop, T_NEAR); + } +} + +void jit_sse42_conv_fwd_kernel_f32::width_blk_step(int ur_w, + int pad_l, int pad_r, int oc_blocks) +{ + int iw = jcp.iw; + int kw = jcp.kw; + int ow = jcp.ow; + int oh = jcp.oh; + int dilate_h = jcp.dilate_h + 1; + int dilate_w = jcp.dilate_w + 1; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + const int inp_mult = one_of(jcp.src_tag, ncw, nchw) + ? dilate_h : ic_blk * dilate_h; + const int inp_off = one_of(jcp.src_tag, ncw, nchw) + ? dilate_w : ic_blk * dilate_w; + + xor_(simd_iter, simd_iter); + + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + + Label init_simd_iter_loop; + Label init_done; + Label init_first; + + L(init_simd_iter_loop); + + if (!jcp.with_sum) { + test(reg_ci_flag, FLAG_IC_FIRST); + jne(init_first, T_NEAR); + } + + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + movups(Xmm(ur_w * ii + jj + 1), xword[reg_output + + sizeof(float) * (ii * oh * ow + jj) * oc_blk]); + + if (jcp.with_sum && jcp.with_bias) { + test(reg_ci_flag, FLAG_IC_FIRST); + je(init_done, T_NEAR); + + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + addps(Xmm(ur_w * ii + jj + 1), + xword[reg_bias + sizeof(float) * ii * oc_blk]); + } + + jmp(init_done); + + L(init_first); + if (this->jcp.with_bias) { + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + movups(Xmm(ur_w * ii + jj + 1), + xword[reg_bias + sizeof(float) * ii * oc_blk]); + } else { + for (int ii = 0; ii < oc_blocks; ii++) + for (int jj = 0; jj < ur_w; jj++) + pxor(Xmm(ur_w * ii + jj + 1), Xmm(ur_w * ii + jj + 1)); + } + + L(init_done); + + Label skip_kh_loop; + mov(kj, reg_kh); + if ((jcp.dilate_h >= jcp.ih) + || (jcp.kh - 1) * (jcp.dilate_h + 1) < nstl::max(jcp.t_pad, jcp.b_pad)) { + cmp(kj, 0); + je(skip_kh_loop, T_NEAR); + } + Label kh_loop; + L(kh_loop); + { + if (jcp.kw >= 5 && pad_l == 0 && pad_r == 0) { + oh_step_nopad(ur_w, pad_l, pad_r, oc_blocks); + sub(aux_reg_input, sizeof(float) * kw * inp_off); + add(aux_reg_input, sizeof(float) * iw * inp_mult); + } else { + oh_step_unroll_kw(ur_w, pad_l, pad_r, oc_blocks); + add(aux_reg_kernel, sizeof(float) * kw * oc_blk * ic_blk); + add(aux_reg_input, sizeof(float) * iw * inp_mult); + } + + dec(kj); + cmp(kj, 0); + jg(kh_loop, T_NEAR); + } + + L(skip_kh_loop); + + if (jcp.with_eltwise) { + Label regular_store; + test(reg_ci_flag, FLAG_IC_LAST); + je(regular_store, T_NEAR); + + eltwise_injector_->compute_vector_range(1, oc_blocks * ur_w + 1); + + L(regular_store); + } + + for (int ii = 0; ii < oc_blocks; ii++) { + for (int jj = 0; jj < ur_w; jj++) { + const size_t o_off = (ii * oh * ow + jj) * oc_blk; + + Xmm reg_out = Xmm(ur_w * ii + jj + 1); + movups(xword[reg_output + sizeof(float) * o_off], reg_out); + } + } + + mov(aux_reg_kernel, reg_kernel); + mov(aux_reg_input, reg_input); + add(aux_reg_kernel, sizeof(float) * 4); + add(reg_output, sizeof(float) * 4); + add(reg_bias, sizeof(float) * 4); + + inc(simd_iter); + cmp(simd_iter, 2); + jl(init_simd_iter_loop, T_NEAR); + + sub(reg_output, sizeof(float) * 8); + sub(reg_bias, sizeof(float) * 8); +} + +inline void jit_sse42_conv_fwd_kernel_f32::solve_common(int oc_blocks) +{ + int ur_w = jcp.ur_w; + int ur_w_tail = jcp.ur_w_tail; + int n_oi = jcp.ow / ur_w; + int iw = jcp.iw; + int kw = jcp.kw; + int ic_blk = jcp.ic_block; + int oc_blk = jcp.oc_block; + int dilate_w = jcp.dilate_w + 1; + int str_w = jcp.stride_w; + const int inp_mult = one_of(jcp.src_tag, ncw, nchw) ? 1 : ic_blk; + + int l_pad = jcp.l_pad; + int r_pad = nstl::max(0, (int(jcp.ow) - 1) * str_w + (kw - 1) * dilate_w + - (iw + l_pad - 1)); + int r_pad1 = (ur_w * n_oi - 1) * str_w + (kw - 1) * dilate_w + - (iw + l_pad - 1); + if (r_pad1 > 0) n_oi--; + + if (l_pad > 0) { + n_oi--; + if (n_oi < 0 && r_pad1 > 0) + width_blk_step(ur_w, l_pad, r_pad1, oc_blocks); // "lrpad" + else + width_blk_step(ur_w, l_pad, 0, oc_blocks); // "lpad" + add(reg_input, sizeof(float) * (ur_w * str_w - l_pad) * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_blk); + } + + Label ow_loop; + xor_(oi_iter, oi_iter); + + if (n_oi > 0) { + L(ow_loop); + + width_blk_step(ur_w, 0, 0, oc_blocks); // "middle" + add(reg_input, sizeof(float) * ur_w * str_w * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_blk); + + inc(oi_iter); + cmp(oi_iter, n_oi); + jl(ow_loop, T_NEAR); + } + + if (r_pad1 > 0 && n_oi >=0) { + width_blk_step(ur_w, 0, r_pad1, oc_blocks); // "rpad" + add(reg_input, sizeof(float) * ur_w * str_w * inp_mult); + add(reg_output, sizeof(float) * ur_w * oc_blk); + } + + if (ur_w_tail != 0) + width_blk_step(ur_w_tail, 0, r_pad, oc_blocks); // "tail" +} + +void jit_sse42_conv_fwd_kernel_f32::generate() +{ + this->preamble(); + + mov(reg_input, ptr[this->param1 + GET_OFF(src)]); + mov(reg_output, ptr[this->param1 + GET_OFF(dst)]); + mov(reg_kernel, ptr[this->param1 + GET_OFF(filt)]); + if (jcp.with_bias) + mov(reg_bias, ptr[this->param1 + GET_OFF(bias)]); + mov(reg_kh, ptr[this->param1 + GET_OFF(kh_padding)]); + mov(reg_ci_flag, ptr[this->param1 + GET_OFF(flags)]); + mov(reg_oc_blocks, ptr[this->param1 + GET_OFF(oc_blocks)]); + + int nb_oc_tail = jcp.nb_oc % jcp.nb_oc_blocking; + Label tail, exit; + + cmp(reg_oc_blocks, jcp.nb_oc_blocking); + jne(nb_oc_tail ? tail : exit, T_NEAR); + + solve_common(jcp.nb_oc_blocking); + jmp(exit, T_NEAR); + + if (nb_oc_tail) { + L(tail); + cmp(reg_oc_blocks, nb_oc_tail); + jne(exit, T_NEAR); + solve_common(nb_oc_tail); + } + + L(exit); + + this->postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +bool jit_sse42_conv_fwd_kernel_f32::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +status_t jit_sse42_conv_fwd_kernel_f32::init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr) +{ + if (!mayiuse(sse42)) return status::unimplemented; + + jcp.prop_kind = cd.prop_kind; + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + const int ndims = src_d.ndims(); + jcp.ndims = ndims; + + jcp.ngroups = with_groups ? weights_d.dims()[0] : 1; + jcp.mb = src_d.dims()[0]; + + jcp.oc = dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + jcp.ih = (ndims == 3) ? 1 : src_d.dims()[2]; + jcp.iw = src_d.dims()[ndims - 1]; + jcp.oh = (ndims == 3) ? 1 : dst_d.dims()[2]; + jcp.ow = dst_d.dims()[ndims - 1]; + + jcp.kh = (ndims == 3) ? 1 : weights_d.dims()[with_groups + 2]; + jcp.kw = weights_d.dims()[with_groups + ndims - 1]; + + jcp.t_pad = (ndims == 3) ? 0 : cd.padding[0][0]; + jcp.l_pad = cd.padding[0][ndims - 3]; + + jcp.stride_h = (ndims == 3) ? 1 : cd.strides[0]; + jcp.stride_w = cd.strides[ndims - 3]; + + jcp.dilate_h = (ndims == 3) ? 0 : cd.dilates[0]; + jcp.dilate_w = cd.dilates[ndims - 3]; + jcp.b_pad = (jcp.oh - 1) * jcp.stride_h + (jcp.kh - 1) * (jcp.dilate_h + 1) + - (jcp.ih + jcp.t_pad - 1); + + if (ndims == 3) { + jcp.src_tag = src_d.matches_one_of_tag(ncw, nwc, nCw8c); + jcp.wei_tag = weights_d.matches_one_of_tag( + Owi8o, gOwi8o, OIw8i8o, gOIw8i8o); + jcp.dst_tag = dst_d.matches_one_of_tag(nCw8c); + } else if (ndims == 4) { + jcp.src_tag = src_d.matches_one_of_tag(nchw, nhwc, nChw8c); + jcp.wei_tag = weights_d.matches_one_of_tag( + Ohwi8o, gOhwi8o, OIhw8i8o, gOIhw8i8o); + jcp.dst_tag = dst_d.matches_one_of_tag(nChw8c); + } + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + const bool flat = jcp.ic == 3; + const bool mimo = !flat; + + bool args_ok = true + && IMPLICATION(flat, one_of(jcp.src_tag, ncw, nwc, nchw, nhwc) + && one_of(jcp.wei_tag, Owi8o, gOwi8o, Ohwi8o, gOhwi8o)) + && IMPLICATION(mimo, one_of(jcp.src_tag, nCw8c, nChw8c) + && one_of(jcp.wei_tag, OIw8i8o, gOIw8i8o, OIhw8i8o, gOIhw8i8o)) + && one_of(jcp.dst_tag, nCw8c, nChw8c); + if (!args_ok) return status::unimplemented; + + const int simd_w = 8; // 2 SSE vectors processing at once + + jcp.ur_h = 1; /* no code-unrolling by h so far */ + jcp.ur_w = 3; + if (jcp.ow < jcp.ur_w) jcp.ur_w = jcp.ow; + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + + jcp.nb_oc_blocking = 4; /* the optimal value for the kernel */ + + args_ok = true + && jcp.oc % simd_w == 0 + && jcp.l_pad <= jcp.ur_w + && IMPLICATION(jcp.kw > 7, (jcp.t_pad == 0 && jcp.l_pad == 0) + || (jcp.stride_w == 1 && jcp.stride_h == 1)) + && IMPLICATION(mimo, jcp.ic % simd_w == 0); + if (!args_ok) return status::unimplemented; + + int r_pad_no_tail = nstl::max(0, (jcp.ow - jcp.ur_w_tail - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + + // kernel needs 1 temporary YMM register + const int num_avail_regs = 15; + if (r_pad_no_tail > jcp.ur_w * jcp.stride_w && jcp.ow / jcp.ur_w > 1) { + /* recalculate ur_w, nb_oc_blocking and ur_w_tail */ + jcp.ur_w = nstl::min(r_pad_no_tail / jcp.stride_w + jcp.ur_w_tail, + nstl::min(jcp.ow, num_avail_regs / 2)); + jcp.nb_oc_blocking = (num_avail_regs - jcp.ur_w) / jcp.ur_w; + jcp.ur_w_tail = jcp.ow % jcp.ur_w; + /* check again ... */ + r_pad_no_tail = nstl::max(0, (jcp.ow - jcp.ur_w_tail - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) - (jcp.iw + jcp.l_pad - 1)); + if (jcp.ur_w < nstl::max(jcp.l_pad, r_pad_no_tail)) + return status::unimplemented; + } + assert(jcp.nb_oc_blocking > 0); + assert(jcp.ur_w * (jcp.nb_oc_blocking + 1) <= num_avail_regs); + + jcp.ic_block = (jcp.ic % simd_w != 0) ? jcp.ic : simd_w; + jcp.nb_ic = jcp.ic / jcp.ic_block; + + jcp.oc_block = simd_w; + jcp.nb_oc = jcp.oc / jcp.oc_block; + + if (one_of(jcp.prop_kind, forward_training, forward_inference)) { + jcp.nb_ic_blocking = 12; + jcp.nb_ic_blocking_max = 16; + } else { + jcp.nb_ic_blocking = 1; + jcp.nb_ic_blocking_max = jcp.nb_ic_blocking; + } + + return status::success; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.hpp new file mode 100644 index 000000000000..33c26ef0811d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.hpp @@ -0,0 +1,93 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_SSE42_CONV_KERNEL_F32_HPP +#define JIT_SSE42_CONV_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "cpu_memory.hpp" +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_sse42_conv_fwd_kernel_f32: public jit_generator { + jit_sse42_conv_fwd_kernel_f32(jit_conv_conf_t ajcp, + const primitive_attr_t &attr) + : jcp(ajcp), attr_(attr), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32(this, + jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_conv_call_s *))this->getCode(); + } + + ~jit_sse42_conv_fwd_kernel_f32() { + delete eltwise_injector_; + } + + static bool post_ops_ok(jit_conv_conf_t &jcp, + const primitive_attr_t &attr); + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, const primitive_attr_t &attr); + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_sse42_conv_fwd_kernel_f32) + jit_conv_conf_t jcp; + const primitive_attr_t &attr_; + void (*jit_ker)(jit_conv_call_s *); + +private: + using reg64_t = const Xbyak::Reg64; + reg64_t reg_input = rax; + reg64_t aux_reg_input = r8; + reg64_t reg_kernel = rdx; + reg64_t aux_reg_kernel = r9; + reg64_t reg_output = rsi; + reg64_t reg_bias = rbx; + + reg64_t kj = r10; + reg64_t oi_iter = r11; + reg64_t ki_iter = r12; + reg64_t reg_kh = abi_not_param1; + reg64_t simd_iter = r15; + reg64_t reg_oc_blocks = r14; + reg64_t imm_addr64 = reg_oc_blocks; + Xbyak::Reg32 reg_ci_flag = r13d; + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + inline void oh_step_unroll_kw(int ur_w, int pad_l, int pad_r, + int oc_blocks); + inline void oh_step_nopad(int ur_w, int pad_l, int pad_r, int oc_blocks); + inline void width_blk_step(int ur_w, int pad_l, int pad_r, int oc_blocks); + inline void solve_common(int oc_blocks); + + void generate(); +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.cpp new file mode 100644 index 000000000000..5f77d692f51f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.cpp @@ -0,0 +1,136 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "jit_sse42_convolution.hpp" +#include "mkldnn_thread.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; + +#define src_blk_off(f, n, c, h, w) \ + (pd()->ndims() == 3) \ + ? (f).blk_off(n, c, w) \ + : (f).blk_off(n, c, h, w) + +#define wht_blk_off_(f, g, ...) \ + pd()->with_groups() \ + ? (f).blk_off(g, __VA_ARGS__) \ + : (f).blk_off(__VA_ARGS__) +#define wht_blk_off(f, g, oc, ic, kh, kw) \ + pd()->ndims() == 3 \ + ? wht_blk_off_(f, g, oc, ic, kw) \ + : wht_blk_off_(f, g, oc, ic, kh, kw) + +void jit_sse42_convolution_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const auto &jcp = kernel_->jcp; + + int ocb_work = div_up(jcp.nb_oc, jcp.nb_oc_blocking); + const size_t work_amount = jcp.mb * jcp.ngroups * ocb_work * jcp.oh; + + parallel(0, [&](const int ithr, const int nthr) { + size_t start{ 0 }, end{ 0 }; + balance211(work_amount, nthr, ithr, start, end); + + int icbb = 0; + while (icbb < jcp.nb_ic) { + int icb_step = jcp.nb_ic_blocking; + int icb_step_rem = jcp.nb_ic - icbb; + if (icb_step_rem < jcp.nb_ic_blocking_max) + icb_step = icb_step_rem; + + size_t n{0}, g{0}, ocbb{0}, oh{0}; + nd_iterator_init(start, n, jcp.mb, g, jcp.ngroups, ocbb, ocb_work, + oh, jcp.oh); + for (size_t iwork = start; iwork < end; ++iwork) { + int ocb = ocbb * jcp.nb_oc_blocking; + int ocb_num = jcp.nb_oc_blocking; + + for (int icb = icbb; icb < icbb + icb_step; ++icb) { + auto par_conv = jit_conv_call_s(); + + const int ij = oh * jcp.stride_h; + const int i_t_overflow = nstl::max(0, jcp.t_pad - ij); + const int i_b_overflow = nstl::max(jcp.ih, ij + + (jcp.kh-1) * (jcp.dilate_h+1) - jcp.t_pad+1) - jcp.ih; + + const size_t _oc = g * jcp.nb_oc + ocb; + const size_t _ic = g * jcp.nb_ic + icb; + + const int ih = nstl::max(ij - jcp.t_pad + + div_up(i_t_overflow, + (jcp.dilate_h+1)) * (jcp.dilate_h + 1), 0); + par_conv.src = &src[src_blk_off(src_d, n, + jcp.ic == 3 ? 0 : _ic, ih, 0)]; + + par_conv.dst = &dst[src_blk_off(dst_d, n, _oc, oh, 0)]; + + const int wh = div_up(i_t_overflow, (jcp.dilate_h + 1)); + par_conv.filt = &weights[wht_blk_off(weights_d, g, ocb, + jcp.ic == 3 ? 0 : icb, wh, 0)]; + + if (icb == 0) { + if (bias) + par_conv.bias = + &bias[bias_d.blk_off(_oc * jcp.oc_block)]; + par_conv.flags |= FLAG_IC_FIRST; + } + + if (jcp.with_eltwise && icb + 1 == jcp.nb_ic) { + par_conv.flags |= FLAG_IC_LAST; + } + + par_conv.oc_blocks = + nstl::min(ocb + ocb_num, jcp.nb_oc) - ocb; + + par_conv.kw_padding = 0; + const int kh_padding = jcp.kh + - div_up(i_t_overflow, (jcp.dilate_h + 1)) + - div_up(i_b_overflow, (jcp.dilate_h + 1)); + par_conv.kh_padding = nstl::max(0, kh_padding); + kernel_->jit_ker(&par_conv); + } + nd_iterator_step(n, jcp.mb, g, jcp.ngroups, ocbb, ocb_work, + oh, jcp.oh); + } + icbb += icb_step; + } + }); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.hpp new file mode 100644 index 000000000000..d2f0a38c5cef --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_sse42_convolution.hpp @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_SSE42_CONVOLUTION_HPP +#define CPU_JIT_SSE42_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_primitive_conf.hpp" +#include "jit_sse42_conv_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_sse42_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", sse42, ""), + jit_sse42_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + return jit_sse42_conv_fwd_kernel_f32::init_conf(jcp_, *desc(), + *src_md(), *weights_md(), *dst_md(), *attr()); + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + const bool flat = IC() == 3; + auto src_tag = flat + ? utils::pick(ndims() - 3, ncw, nchw, ncdhw) + : utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto dst_tag = + utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c); + auto wei_tag = with_groups() + ? utils::pick(2 * ndims() - 6 + flat, gOIw8i8o, gOwi8o, + gOIhw8i8o, gOhwi8o, gOIdhw8i8o, gOdhwi8o) + : utils::pick(2 * ndims() - 6 + flat, OIw8i8o, Owi8o, + OIhw8i8o, Ohwi8o, OIdhw8i8o, Odhwi8o); + + return set_default_formats_common(src_tag, wei_tag, dst_tag); + } + }; + + jit_sse42_convolution_fwd_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_sse42_conv_fwd_kernel_f32(pd()->jcp_, *pd()->attr()); } + ~jit_sse42_convolution_fwd_t() { delete kernel_; }; + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_sse42_conv_fwd_kernel_f32 *kernel_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.cpp new file mode 100644 index 000000000000..0e734f72650f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.cpp @@ -0,0 +1,1192 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "nstl.hpp" +#include "utils.hpp" +#include "jit_generator.hpp" +#include "cpu_barrier.hpp" + +#include "jit_transpose_src_utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + +#define GET_OFF(x) offsetof(ctx_t, x) + +struct jit_trans_iw_ic_t: public jit_trans_src_t, public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_trans_iw_ic_t) + + jit_trans_iw_ic_t(const jit_conv_conf_t *conf): jit_trans_src_t(conf) { + generate(); + ker_ = (decltype(ker_))this->getCode(); + } + +private: + using reg64_t = const Xbyak::Reg64; + using reg32_t = const Xbyak::Reg32; + using opmask_t = const Xbyak::Opmask; + + enum { typesize = sizeof(float), transpose_size = 16, small_spatial = 14 }; + int src_stride, tr_src_stride; + int tail; + bool enable_prefetch; + + opmask_t k3333 = k1; + opmask_t k5555 = k2; + opmask_t kAAAA = k3; + opmask_t kCCCC = k4; + opmask_t k0F0F = k5; + opmask_t kF0F0 = k6; + opmask_t kTail = k7; + + reg64_t reg_src = r8; + reg64_t reg_tr_src = r9; + reg64_t reg_src_prf = r10; + reg64_t reg_tr_src_prf = r11; + reg64_t reg_loop = r12; + reg64_t reg_tr_src_tmp = r13; + reg32_t regw_tmp = r14d; + + void transpose(int nrows, int l_pad, int r_pad, bool nontemporal_stores); + void generate(); +}; + +void jit_trans_iw_ic_t::transpose(int nrows, int l_pad, int r_pad, + bool nontemporal_stores) { + assert(nrows >= 0 && nrows <= transpose_size); + static_assert(transpose_size == 16, "Unsupported transpose size"); + if (!nrows) + return; + + auto pf_src_t0 = [=](int i) { + if(enable_prefetch) prefetcht0(EVEX_compress_addr(reg_src, + (transpose_size + i) * src_stride)); + }; + + auto pf_tr_src_t0 = [=](int i) { + int offset = (transpose_size) * typesize + i * tr_src_stride; + if(enable_prefetch) prefetcht0(EVEX_compress_addr(reg_tr_src, offset)); + if(enable_prefetch) prefetcht0(EVEX_compress_addr(reg_tr_src, + offset + 64)); + }; + + auto pf_src_t1 = [=](int i) { + if(enable_prefetch) prefetcht1(EVEX_compress_addr(reg_src_prf, + i * src_stride)); + }; + + auto pf_tr_src_t1 = [=](int i) { + if(enable_prefetch) prefetchwt1(EVEX_compress_addr(reg_tr_src_prf, + i * tr_src_stride)); + }; + + auto src_zmm = [=](int i) { + assert(i >= 0 && i < 16); + return Zmm(i); + }; + + auto tmp_zmm = [=](int i) { + assert(i >= 0 && i < 16); + return Zmm(16 + i); + }; + + auto load = [=](int i) { + vmovups(src_zmm(i), EVEX_compress_addr(reg_src, i * src_stride)); + }; + + auto store = [=](Zmm r, int i) { + auto kmovw = [=](Opmask k, unsigned w) { + mov(regw_tmp, w); + jit_generator::kmovw(k, regw_tmp); + }; + + auto padding = [=] (Reg64 reg, int pad) { + kmovw(kTail, (1 << pad) - 1); + auto k = kTail; + auto base = reg; + base.setOpmaskIdx(k.getIdx(), true); + + auto zmm_zero = r; + vpxord(zmm_zero, zmm_zero, zmm_zero); + auto addr = EVEX_compress_addr(base, i * tr_src_stride); + vmovups(addr, zmm_zero); + }; + + mov(reg_tr_src_tmp, reg_tr_src); + if (l_pad > 0) + add(reg_tr_src_tmp, l_pad * typesize); + + if (tail != transpose_size) + kmovw(kTail, (1 << tail) - 1); + + // Xbyak does not allow k0 to be specified explicitly via the '|' + // operator, so we have to do this via a method call (implicitly + // EVEX encoding uses k0 to mean 'no mask') + bool partial_store = nrows < 16; + auto k = partial_store ? kTail : k0; + auto base = reg_tr_src_tmp; + base.setOpmaskIdx(k.getIdx(), true); + + auto addr = EVEX_compress_addr(base, i * tr_src_stride); + if (nontemporal_stores && !partial_store) + vmovntps(addr, r); + else + vmovups(addr, r); + + if (r_pad > 0) { + add(reg_tr_src_tmp, tail * typesize); + padding(reg_tr_src_tmp, r_pad); + } + + if (l_pad > 0) { + padding(reg_tr_src, l_pad); + } + }; + + auto transpose16x8 = [=](int base_idx) { + assert(base_idx == 0 || base_idx == 8); + + // swap 1 + for (int i = 0; i < 4; i++) { + int src_idx0 = base_idx + i * 2; + int src_idx1 = src_idx0 + 1; + + int next_src_idx0 = src_idx0 + 2; + int next_src_idx1 = src_idx1 + 2; + bool load_next = base_idx == 0 || i < 3; + + if (base_idx == 0 && i == 0) { + load(src_idx0); + load(src_idx1); + } + + auto tmp0 = tmp_zmm(src_idx0); + auto tmp1 = tmp_zmm(src_idx1); + auto src0 = src_zmm(src_idx0); + auto src1 = src_zmm(src_idx1); + + if (next_src_idx0 < nrows && load_next) + load(next_src_idx0); + valignd(tmp0, src0, src0, 0x1); + pf_src_t1(base_idx + i); + + if (next_src_idx1 < nrows && load_next) + load(next_src_idx1); + valignd(tmp1, src1, src1, 0xf); + pf_src_t0(base_idx + i); + + vmovaps(src0 | kAAAA, tmp1); + vmovaps(src1 | k5555, tmp0); + } + // swap 2 + for (int i = 0; i < 4; i++) { + int select_half = (i < 2) ? 0 : 2; + int src_idx0 = base_idx + i + select_half + 0; + int src_idx2 = src_idx0 + 2; + + auto tmp0 = tmp_zmm(src_idx0); + auto tmp1 = tmp_zmm(src_idx2); + auto src0 = src_zmm(src_idx0); + auto src2 = src_zmm(src_idx2); + + valignd(tmp0, src0, src0, 0x2); + pf_src_t1(base_idx + 4 + i); + valignd(tmp1, src2, src2, 0xe); + pf_src_t0(base_idx + 4 + i); + vmovaps(src2 | k3333, tmp0); + vmovaps(src0 | kCCCC, tmp1); + } + + // swap 4 + for (int i = 0; i < 4; i++) { + int src_idx0 = base_idx + i; + int src_idx4 = src_idx0 + 4; + + auto tmp0 = tmp_zmm(src_idx0); + auto src0 = src_zmm(src_idx0); + auto src4 = src_zmm(src_idx4); + + vmovaps(tmp0, src0); + vshuff32x4(src0 | kF0F0, src4, src4, 0xb1); + pf_tr_src_t1(base_idx / 2 + i); + vshuff32x4(src4 | k0F0F, tmp0, tmp0, 0xb1); + pf_tr_src_t0(base_idx / 2 + i); + } + }; + + auto fixup16x16 = [=]() { + // swap 8 + for (int i = 0; i < 8; i++) { + auto tmp = tmp_zmm(i); + auto src0 = src_zmm(i); + auto src8 = src_zmm(8 + i); + vshuff64x2(tmp, src0, src8, 0x44); + store(tmp, i); + if (i % 2 == 0) { + pf_tr_src_t1(8 + i / 2); + pf_tr_src_t0(8 + i / 2); + } + } + + for (int i = 0; i < 8; i++) { + auto tmp = tmp_zmm(8 + i); + auto src0 = src_zmm(i); + auto src8 = src_zmm(8 + i); + vshuff64x2(tmp, src0, src8, 0xee); + store(tmp, 8 + i); + if (i % 2 == 0) { + pf_tr_src_t1(12 + i / 2); + pf_tr_src_t0(12 + i / 2); + } + } + }; + + transpose16x8(0); + transpose16x8(8); + fixup16x16(); +} + +void jit_trans_iw_ic_t::generate() { + preamble(); + + const int ic_block = conf_->ic_block; + const int iw = conf_->iw; + const int tr_iw = conf_->tr_iw; + const int transposes = utils::div_up(iw, transpose_size); + int loop_iters = nstl::max(0, transposes - 1); + tail = iw - loop_iters * transpose_size; + + src_stride = ic_block * typesize; + assert(src_stride == 64); + tr_src_stride = tr_iw * typesize; + + bool nontemporal_stores = false; + enable_prefetch = iw > small_spatial ? 1 : 0; + + assert(transpose_size == ic_block); + const int src_step = ic_block * transpose_size * typesize; + const int tr_src_step = ic_block * typesize; + + const int left_pad = conf_->l_pad; + const int right_pad = tr_iw - iw - left_pad; + + mov(reg_src, ptr [param1 + GET_OFF(src)]); + mov(reg_tr_src, ptr [param1 + GET_OFF(tr_src)]); + mov(reg_src_prf, ptr [param1 + GET_OFF(src_prf)]); + mov(reg_tr_src_prf, ptr [param1 + GET_OFF(tr_src_prf)]); + + auto kmovw = [=](Opmask k, unsigned w) { + mov(regw_tmp, w); + jit_generator::kmovw(k, regw_tmp); + }; + + kmovw(k3333, 0x3333); // 0011001100110011 + kmovw(k5555, 0x5555); // 0101010101010101 + kmovw(kAAAA, 0xaaaa); // 1010101010101010 + kmovw(kCCCC, 0xcccc); // 1100110011001100 + kmovw(k0F0F, 0x0f0f); // 0000111100001111 + kmovw(kF0F0, 0xf0f0); // 1111000011110000 + + if (left_pad > 0 && loop_iters > 0) { + loop_iters--; + transpose(transpose_size, left_pad, 0, nontemporal_stores); + add(reg_src, src_step); + add(reg_tr_src, tr_src_step + left_pad * typesize); + add(reg_src_prf, src_step); + add(reg_tr_src_prf, tr_src_step + left_pad * typesize); + } + + if (loop_iters) { + mov(reg_loop, loop_iters); + Label loop; + L(loop); { + transpose(transpose_size, 0, 0, nontemporal_stores); + add(reg_src, src_step); + add(reg_tr_src, tr_src_step); + add(reg_src_prf, src_step); + add(reg_tr_src_prf, tr_src_step); + sub(reg_loop, 1); + jnz(loop); + } + } + if (transposes > 1) + transpose(tail, 0, right_pad, nontemporal_stores); + else + transpose(tail, left_pad, right_pad, nontemporal_stores); + + postamble(); +} + +struct jit_trans_iw_ic_int16_t: public jit_trans_src_t, public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_trans_iw_ic_int16_t) + jit_trans_iw_ic_int16_t(const jit_conv_conf_t *conf): + jit_trans_src_t(conf) { + generate(); + ker_ = (decltype(ker_))this->getCode(); + } + +private: + using reg64_t = const Xbyak::Reg64; + using reg32_t = const Xbyak::Reg32; + using opmask_t = const Xbyak::Opmask; + + enum { typesize = sizeof(int16_t), transpose_size = 16, small_spatial = 14 }; + int src_stride, tr_src_stride; + int tail; + bool enable_prefetch; + + opmask_t kFFFF = k1; + opmask_t k5555 = k2; + opmask_t kAAAA = k3; + opmask_t kAA = k4; + opmask_t k55 = k5; + opmask_t kCC = k6; + opmask_t k33 = k7; + opmask_t kTail = k1; + + reg64_t reg_src = r8; + reg64_t reg_tr_src = r9; + reg64_t reg_src_prf = r10; + reg64_t reg_tr_src_prf = r11; + reg64_t reg_loop = r12; + reg64_t reg_tr_src_tmp = r13; + reg32_t regw_tmp = r14d; + reg64_t imm_addr64 = rbx; + + Xbyak::Zmm vidx1 = zmm31; + Xbyak::Zmm vidx2 = zmm30; + Xbyak::Zmm vidx3 = zmm29; + Xbyak::Zmm vidx4 = zmm28; + Xbyak::Zmm vidx5 = zmm27; + Xbyak::Zmm zmm_tmp = zmm26; + + + void transpose(int nrows, int l_pad, int r_pad, bool nontemporal_stores); + void generate(); +}; + +void jit_trans_iw_ic_int16_t::transpose(int nrows, int l_pad, int r_pad, + bool nontemporal_stores) { + assert(nrows >= 0 && nrows <= transpose_size); + static_assert(transpose_size == 16, "Unsupported transpose size"); + if (!nrows) + return; + + auto src_zmm = [=](int i) { + return Zmm(i); + }; + + auto src_ymm = [=](int i) { + assert(i >= 0 && i < 16); + return Ymm(i); + }; + + auto load_ymm = [=](int i) { + vmovups(src_ymm(i), EVEX_compress_addr(reg_src, i * src_stride)); + }; + + auto kmovw = [=](Opmask k, unsigned w) { + mov(regw_tmp, w); + jit_generator::kmovw(k, regw_tmp); + }; + + auto store = [=](Zmm r, int i) { + + auto padding = [=] (Reg64 reg, int pad) { + kmovw(kTail, (1 << pad) - 1); + auto k = kTail; + auto base = reg; + base.setOpmaskIdx(k.getIdx(), true); + + auto zmm_zero = zmm_tmp; + vpxord(zmm_zero, zmm_zero, zmm_zero); + auto addr = EVEX_compress_addr(base, i * tr_src_stride); + vmovups(addr, zmm_zero); + }; + + int store_tail = (nrows%2) ? nrows+1 : nrows; + + int store_pad = (l_pad%2) ? l_pad/2 + 1 : l_pad/2; + mov(reg_tr_src_tmp, reg_tr_src); + if (l_pad > 0) { + padding(reg_tr_src, store_pad); + add(reg_tr_src_tmp, l_pad * typesize); + } + if (r_pad > 0) { + store_pad = (r_pad%2) ? r_pad/2 + 1 : r_pad/2; + int addr_shift = (r_pad%2) ? 1 : 0; + add(reg_tr_src_tmp, (nrows - addr_shift) * typesize); + padding(reg_tr_src_tmp, store_pad); + } + + mov(reg_tr_src_tmp, reg_tr_src); + add(reg_tr_src_tmp, l_pad * typesize); + + kmovw(kTail, (1 << store_tail/2) - 1); + auto k = kTail; + auto base = reg_tr_src_tmp; + base.setOpmaskIdx(k.getIdx(), true); + + auto addr = EVEX_compress_addr(base, i * tr_src_stride); + vmovups(addr, r); + + }; + + kmovw(kFFFF, 0xffff); + //all loads + for (int i=0; i<16; i++){ + vpxord(src_zmm(i), src_zmm(i), src_zmm(i)); + } + + for (int i = 0; i < nrows/2; i++) { + auto src0 = src_ymm(2*i); + auto src1 = src_ymm(2*i+1); + auto zmm_src0 = src_zmm(2*i); + load_ymm(2*i); + + vpunpcklwd(src1, src0, + EVEX_compress_addr(reg_src, (2*i+1) * src_stride)); + vpunpckhwd(src0, src0, + EVEX_compress_addr(reg_src, (2*i+1) * src_stride)); + vinserti64x4(zmm_src0, zmm_src0, src1, 1); + vpermps(zmm_src0 | kFFFF, vidx4, zmm_src0); + } + + // for odd numbers we need to mix row with zeroes + if (nrows%2) { + int i = nrows-1; + auto src0 = src_ymm(i); + auto src1 = src_ymm(i+1); //zero + + auto zmm_src0 = src_zmm(i); + vpxor(src1, src1, src1); + + load_ymm(i); + vpunpckhwd(src0, src0, src1); + vinserti64x4(zmm_tmp, zmm_tmp, src0, 0); + vpxor(src0, src0, src0); + load_ymm(i); + vpunpcklwd(src1, src0, src1); + vinserti64x4(zmm_tmp, zmm_tmp, src1, 1); + vpxord(zmm_src0, zmm_src0, zmm_src0); + vmovups(zmm_src0, zmm_tmp); + vpermps(zmm_src0 | kFFFF, vidx4, zmm_src0); + } + + // swap 1 + for (int i=0; i<4; i++) { + auto zmm0 = src_zmm(4*i); + auto zmm1 = src_zmm(4*i+2); + auto tmp0 = src_zmm(4*i+1); + auto tmp1 = src_zmm(4*i+3); + + vmovups(tmp0, zmm0); + vmovups(tmp1, zmm1); + + vpermps(tmp0 | kAAAA, vidx3, zmm1); + vpermps(tmp1 | k5555, vidx3, zmm0); + } + // swap 2 + int base_idx; + base_idx=0; + for (int i=0; i<2; i++) { + auto zmm0 = src_zmm(base_idx+2*i+1); + auto zmm1 = src_zmm(base_idx+2*i+5); + + auto tmp0 = src_zmm(base_idx+2*i); + auto tmp1 = src_zmm(base_idx+2*i+4); + + vmovupd(tmp0, zmm0); + vmovupd(tmp1, zmm1); + + vpermpd(tmp0 | kAA, vidx2, zmm1); + vpermpd(tmp1 | k55, vidx2, zmm0); + } + base_idx=8; + for (int i=0; i<2; i++) { + auto zmm0 = src_zmm(base_idx+2*i+1); + auto zmm1 = src_zmm(base_idx+2*i+5); + + auto tmp0 = src_zmm(base_idx+2*i); + auto tmp1 = src_zmm(base_idx+2*i+4); + + vmovupd(tmp0, zmm0); + vmovupd(tmp1, zmm1); + + vpermpd(tmp0 | kAA, vidx2, zmm1); + vpermpd(tmp1 | k55, vidx2, zmm0); + } + + // swap 3 + for (int i=0; i<4; i++) { + auto zmm0 = src_zmm(2*i); + auto zmm1 = src_zmm(2*i+8); + + auto tmp0 = src_zmm(2*i+1); + auto tmp1 = src_zmm(2*i+9); + + vmovupd(tmp0, zmm0); + vmovupd(tmp1, zmm1); + + vpermpd(tmp0 | kCC, vidx1, zmm1); + vpermpd(tmp1 | k33, vidx1, zmm0); + } + + // all stores + for (int i=0; i<8; i++) + vextracti64x4(src_ymm(2*i), src_zmm(2*i+1), 1); + + store(src_zmm(1), 0); + store(src_zmm(0), 1); + store(src_zmm(3), 2); + store(src_zmm(2), 3); + store(src_zmm(9), 4); + store(src_zmm(8), 5); + store(src_zmm(11), 6); + store(src_zmm(10), 7); + store(src_zmm(5), 8); + store(src_zmm(4), 9); + store(src_zmm(7), 10); + store(src_zmm(6), 11); + store(src_zmm(13), 12); + store(src_zmm(12), 13); + store(src_zmm(15), 14); + store(src_zmm(14), 15); + +} + +void jit_trans_iw_ic_int16_t::generate() { + preamble(); + + alignas(64) static constexpr const int64_t idx1[8] + = { 2, 3, 0, 1, 6, 7, 4, 5 }; + alignas(64) static constexpr const int64_t idx2[8] + = { 1, 0, 3, 2, 5, 4, 7, 6 }; + alignas(64) static constexpr const int32_t idx3[16] + = { 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14 }; + alignas(64) static constexpr const int32_t idx4[16] + = { 8, 10, 12, 14, 0, 2, 4, 6, 9, 11, 13, 15, 1, 3, 5, 7 }; + alignas(64) static constexpr const int32_t idx5[16] + = { 8, 10, 12, 14, 0, 2, 4, 6, 9, 11, 13, 15, 1, 3, 5, 7 }; + + const int ic_block = conf_->ic_block; + const int iw = conf_->iw; + const int tr_iw = conf_->tr_iw; + const int transposes = utils::div_up(iw, transpose_size); + int loop_iters = nstl::max(0, transposes - 1); + tail = iw - loop_iters * transpose_size; + + src_stride = ic_block * typesize; + tr_src_stride = tr_iw * typesize; + + bool nontemporal_stores = false; + enable_prefetch = iw > small_spatial ? 1 : 0; + + assert(transpose_size == ic_block); + const int src_step = ic_block * transpose_size * typesize; + const int tr_src_step = ic_block * typesize; + + const int left_pad = conf_->l_pad; + const int right_pad = tr_iw - iw - left_pad; + + mov(reg_src, ptr [param1 + GET_OFF(src)]); + mov(reg_tr_src, ptr [param1 + GET_OFF(tr_src)]); + mov(reg_src_prf, ptr [param1 + GET_OFF(src_prf)]); + mov(reg_tr_src_prf, ptr [param1 + GET_OFF(tr_src_prf)]); + + auto kmovw = [=](Opmask k, unsigned w) { + mov(regw_tmp, w); + jit_generator::kmovw(k, regw_tmp); + }; + + kmovw(kFFFF, 0xffff); + kmovw(k5555, 0x5555); + kmovw(kAAAA, 0xaaaa); + kmovw(kAA, 0xaa); + kmovw(k55, 0x55); + kmovw(kCC, 0xcc); + kmovw(k33, 0x33); + + auto vmovdqa64 = [=](Zmm z, const int64_t *addr) { + mov(imm_addr64, reinterpret_cast(addr)); + jit_generator::vmovdqa64(z, ptr[imm_addr64]); + }; + + auto vmovdqa32 = [=](Zmm z, const int32_t *addr) { + mov(imm_addr64, reinterpret_cast(addr)); + jit_generator::vmovdqa32(z, ptr[imm_addr64]); + }; + + vmovdqa64(vidx1, idx1); + vmovdqa64(vidx2, idx2); + vmovdqa32(vidx3, idx3); + vmovdqa32(vidx4, idx4); + vmovdqa32(vidx5, idx5); + + if (left_pad > 0 && loop_iters > 0) { + loop_iters--; + transpose(transpose_size, left_pad, 0, nontemporal_stores); + add(reg_src, src_step); + add(reg_tr_src, tr_src_step + left_pad * typesize); + add(reg_src_prf, src_step); + add(reg_tr_src_prf, tr_src_step + left_pad * typesize); + } + + if (loop_iters) { + mov(reg_loop, loop_iters); + Label loop; + L(loop); { + transpose(transpose_size, 0, 0, nontemporal_stores); + add(reg_src, src_step); + add(reg_tr_src, tr_src_step); + add(reg_src_prf, src_step); + add(reg_tr_src_prf, tr_src_step); + sub(reg_loop, 1); + jnz(loop); + } + } + if (transposes > 1) + transpose(tail, 0, right_pad, nontemporal_stores); + else + transpose(tail, left_pad, right_pad, nontemporal_stores); + + postamble(); + +} + +struct jit_trans_ow_oc_t: public jit_trans_dst_t, public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_trans_ow_oc_t) + jit_trans_ow_oc_t(const jit_conv_conf_t *conf): jit_trans_dst_t(conf) { + generate(); + ker_ = (decltype(ker_))this->getCode(); + } + +private: + using reg64_t = const Xbyak::Reg64; + using reg32_t = const Xbyak::Reg32; + using opmask_t = const Xbyak::Opmask; + using zmm = const Xbyak::Zmm; + + enum { typesize = sizeof(int16_t), transpose_size = 16, small_spatial = 14 }; + int src_stride, tr_src_stride; + int tail; + bool enable_prefetch; + + opmask_t kFF = k1; + + zmm vidx1 = zmm31; + + reg64_t reg_src = r8; + reg64_t reg_tr_src = r9; + reg64_t reg_src_prf = r10; + reg64_t reg_tr_src_prf = r11; + reg64_t reg_loop = r12; + reg64_t reg_tr_src_tmp = r13; + reg32_t regw_tmp = r14d; + reg64_t imm_addr64 = rbx; + + void transpose(int nrows, int l_pad, int r_pad, bool nontemporal_stores); + void generate(); +}; + +void jit_trans_ow_oc_t::transpose(int nrows, int l_pad, int r_pad, + bool nontemporal_stores) { + assert(nrows >= 0 && nrows <= transpose_size); + static_assert(transpose_size == 16, "Unsupported transpose size"); + if (!nrows) + return; + + auto src_zmm = [=](int i) { + return Zmm(i); + }; + + auto src_ymm = [=](int i) { + assert(i >= 0 && i < 16); + return Ymm(i); + }; + + auto load_ymm = [=](int i) { + vmovups(src_ymm(i), EVEX_compress_addr(reg_src, i * src_stride)); + }; + + + auto store = [=](Zmm r, int i) { + auto addr = EVEX_compress_addr(reg_tr_src, i * tr_src_stride); + if (nontemporal_stores) + vmovntps(addr, r); + else + vmovups(addr, r); + }; + + for (int i = 0; i < nrows/2; i++) { + auto src0 = src_ymm(2*i); + auto src1 = src_ymm(2*i+1); + auto zmm_src0 = src_zmm(2*i); + load_ymm(2*i); + vpunpcklwd(src1, src0, + EVEX_compress_addr(reg_src, (2*i+1) * src_stride)); + vpunpckhwd(src0, src0, + EVEX_compress_addr(reg_src, (2*i+1) * src_stride)); + vinserti64x4(zmm_src0, zmm_src0, src1, 1); + vpermpd(zmm_src0 | kFF, vidx1, zmm_src0); + store(zmm_src0, 2*i); + } + if (r_pad > 0) { + auto src0 = src_ymm(nrows-1); + auto src1 = src_ymm(nrows); + auto zmm_src0 = src_zmm(30); + load_ymm(nrows-1); + + vpxor(src1, src1, src1); + vpunpckhwd(src1, src0, src1); + vinserti64x4(zmm_src0, zmm_src0, src1, 0); + vpxor(src1, src1, src1); + vpunpcklwd(src0, src0, src1); + vinserti64x4(zmm_src0, zmm_src0, src0, 1); + vpermpd(zmm_src0 | kFF, vidx1, zmm_src0); + store(zmm_src0, nrows-1); + } +} + +void jit_trans_ow_oc_t::generate() { + preamble(); + + alignas(64) static constexpr const int64_t idx1[8] + = { 4, 5, 0, 1, 6, 7, 2, 3 }; + + const int oc_block = conf_->oc_block; + const int ow = conf_->ow; + const int transposes = utils::div_up(ow, transpose_size); + int loop_iters = nstl::max(0, transposes - 1); + tail = ow - loop_iters * transpose_size; + + src_stride = oc_block * typesize; + tr_src_stride = oc_block * typesize; + + bool nontemporal_stores = false; + enable_prefetch = ow > small_spatial ? 1 : 0; + + const int src_step = oc_block * transpose_size * typesize; + const int tr_src_step = oc_block * transpose_size * typesize; + const int right_pad = ow % 2; + + mov(reg_src, ptr [param1 + GET_OFF(src)]); + mov(reg_tr_src, ptr [param1 + GET_OFF(tr_src)]); + mov(reg_src_prf, ptr [param1 + GET_OFF(src_prf)]); + mov(reg_tr_src_prf, ptr [param1 + GET_OFF(tr_src_prf)]); + + auto kmovw = [=](Opmask k, unsigned w) { + mov(regw_tmp, w); + jit_generator::kmovw(k, regw_tmp); + }; + + kmovw(kFF, 0xFF); + + auto vmovdqa64 = [=](Zmm z, const int64_t *addr) { + mov(imm_addr64, reinterpret_cast(addr)); + jit_generator::vmovdqa64(z, ptr[imm_addr64]); + }; + + vmovdqa64(vidx1, idx1); + if (loop_iters) { + mov(reg_loop, loop_iters); + Label loop; + L(loop); { + transpose(transpose_size, 0, 0, nontemporal_stores); + add(reg_src, src_step); + add(reg_tr_src, tr_src_step); + add(reg_src_prf, src_step); + add(reg_tr_src_prf, tr_src_step); + sub(reg_loop, 1); + jnz(loop); + } + } + transpose(tail, 0, right_pad, nontemporal_stores); + + postamble(); +} + +struct jit_trans_iw_x4_4x_t: public jit_trans_src_t, public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_trans_iw_x4_4x_t) + + jit_trans_iw_x4_4x_t(const jit_conv_conf_t *conf): jit_trans_src_t(conf) { + generate(); + ker_ = (decltype(ker_))this->getCode(); + } + + void generate(); + enum { typesize = (int)sizeof(float) }; +}; + +/** @brief transposition of the form [:][iw/4][4] -> [:][4][iw/4] + * required for 1st 4fma backward by weights convolution */ +void jit_trans_iw_x4_4x_t::generate() { + using namespace utils; + + /* TODO: put into code */ + static int mask[16] = { + 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, }; + + const auto &c = *conf_; + const int simd_w = cpu_isa_traits::vlen / typesize; + const int niters = c.tr_ld / simd_w; + + assert(niters <= 4); /* [bwd_w:tr_src:r1] */ + + Reg64 reg_ptr_src = r8; + Reg64 reg_ptr_tr_src = r9; + + Reg64 reg_ih = rax; + Reg64 reg_ih_end = rbx; + + Reg64 reg_nthr_oc_b = rsi; + Reg64 reg_ptr_tr_src_bctx = abi_not_param1; + + Reg64 reg_tmp = rdx; + + Zmm vmsk = Zmm(31); + Opmask kmsk = k7; + + auto emit_tr_sync = [&]() { + simple_barrier::generate(*this, reg_ptr_tr_src_bctx, reg_nthr_oc_b); + }; + + auto emit_tr_iw = [&]() { + auto vreg = [](int iter, int i) { + assert(4 * iter + i < 24); + return Zmm(4 * iter + i); + }; + auto vtmp = [](int i) { return Zmm(24 + i); }; + + auto emit_load = [&](int iter) { + for (int i = 0; i < 4; ++i) { + auto v = vreg(iter, i); + const int off = (iter * 4 + i) * simd_w; + + if (off + simd_w <= c.iw) + vmovups(v, ptr[reg_ptr_src + off * typesize]); + else if (off < c.iw) + vmovups(v | kmsk | T_z, ptr[reg_ptr_src + off * typesize]); + else + vpxord(v, v, v); + } + }; + + auto emit_tr = [&](int iter) { + for (int i = 0; i < 4; ++i) + vpermps(vreg(iter, i), vmsk, vreg(iter, i)); + + vshuff32x4(vtmp(0), vreg(iter, 0), vreg(iter, 1), 0x88); + vshuff32x4(vtmp(1), vreg(iter, 0), vreg(iter, 1), 0xdd); + vshuff32x4(vtmp(2), vreg(iter, 2), vreg(iter, 3), 0x88); + vshuff32x4(vtmp(3), vreg(iter, 2), vreg(iter, 3), 0xdd); + + vshuff32x4(vreg(iter, 0), vtmp(0), vtmp(2), 0x88); + vshuff32x4(vreg(iter, 2), vtmp(0), vtmp(2), 0xdd); + vshuff32x4(vreg(iter, 1), vtmp(1), vtmp(3), 0x88); + vshuff32x4(vreg(iter, 3), vtmp(1), vtmp(3), 0xdd); + }; + + auto emit_store = [&]() { + for (int i = 0; i < 4; ++i) { + for (int iter = 0; iter < niters; ++iter) { + const size_t off = i * c.tr_ld + iter * simd_w; + vmovups(ptr[reg_ptr_tr_src + off * typesize], vreg(iter, i)); + } + } + }; + + for (int iter = 0; iter < niters; ++iter) + emit_load(iter); + + for (int iter = 0; iter < niters; ++iter) + emit_tr(iter); + + emit_store(); + }; + + preamble(); + + mov(reg_ptr_src, ptr[abi_param1 + GET_OFF(src)]); + mov(reg_ptr_tr_src, ptr[abi_param1 + GET_OFF(tr_src)]); + + mov(reg_nthr_oc_b.cvt32(), ptr[abi_param1 + GET_OFF(nthr_oc_b)]); + mov(reg_ih.cvt32(), ptr[abi_param1 + GET_OFF(tr_src_ih_start)]); + mov(reg_ih_end.cvt32(), ptr[abi_param1 + GET_OFF(tr_src_ih_end)]); + mov(reg_ptr_tr_src_bctx, ptr[abi_param1 + GET_OFF(tr_src_bctx)]); + + emit_tr_sync(); + + Label l_ih_loop, l_tr_done; + cmp(reg_ih, reg_ih_end); + je(l_tr_done, T_NEAR); + + mov(reg_tmp, (size_t)&mask[0]); + vmovups(vmsk, ptr[reg_tmp]); + + if (c.iw % simd_w) { + const char load_mask = (1 << (c.iw % simd_w)) - 1; + mov(reg_tmp, load_mask); + kmovw(kmsk, reg_tmp.cvt32()); + } + + /* src += ih_start * c.iw; */ + imul(reg_tmp, reg_ih, c.iw * typesize); + add(reg_ptr_src, reg_tmp); + /* tr_src += ih_start * c.stride_w * c.tr_ld; */ + imul(reg_tmp, reg_ih, c.stride_w * c.tr_ld * typesize); + add(reg_ptr_tr_src, reg_tmp); + + L(l_ih_loop); { + emit_tr_iw(); + + add(reg_ptr_src, c.iw * typesize); + add(reg_ptr_tr_src, c.stride_w * c.tr_ld * typesize); + + inc(reg_ih); + cmp(reg_ih, reg_ih_end); + jl(l_ih_loop, T_NEAR); + } + + L(l_tr_done); + + emit_tr_sync(); + + postamble(); +} + +/* +// ------------------------------------------------- +// jit_transpose4x16_src +// ------------------------------------------------- +*/ + +void jit_transpose4x16_src::transpose(int nrows) +{ + assert(nrows >= 0 && nrows <= transpose_size); + static_assert(transpose_size == 4, "Unsupported transpose size"); + if (!nrows) + return; + + auto pf_src_t0 = [=](int i) { + if (tparams->src_pf0_distance) + prefetcht0(EVEX_compress_addr( + reg_src, (tparams->src_pf0_distance + i) * src_stride)); + }; + + auto pf_tr_src_t0 = [=](int i) { + if (tparams->tr_src_pf0_distance) + prefetcht0(EVEX_compress_addr(reg_tr_src, + (tparams->tr_src_pf0_distance + i) * src_stride)); + }; + + auto pf_src_t1 = [=](int i) { + if (tparams->src_pf1) + prefetcht1(EVEX_compress_addr(reg_src_prf, i * src_stride)); + }; + + auto pf_tr_src_t1 = [=](int i) { + if (tparams->tr_src_pf1) + prefetchwt1(EVEX_compress_addr(reg_tr_src_prf, i * tr_src_stride)); + }; + + auto src_zmm = [=](int i) { + assert(i >= 0 && i < 4); + return Zmm(i); + }; + + auto tmp_zmm = [=](int i) { + assert(i >= 0 && i < 4); + return Zmm(4 + i); + }; + + auto load = [=](int i) { + vmovups(src_zmm(i), EVEX_compress_addr(reg_src, i * src_stride)); + }; + + auto store = [=](Zmm r, int i) { + vmovups(EVEX_compress_addr(reg_tr_src, i * tr_src_stride), r); + }; + + auto tmp0 = tmp_zmm(0); + auto tmp1 = tmp_zmm(1); + auto tmp2 = tmp_zmm(2); + auto tmp3 = tmp_zmm(3); + + auto src0 = src_zmm(0); + auto src1 = src_zmm(1); + auto src2 = src_zmm(2); + auto src3 = src_zmm(3); + for (int i = 0; i < nrows; i++) { + load(i); + } + + for (size_t i = nrows; i < 4; i++) { + vpxord(src_zmm(i), src_zmm(i), src_zmm(i)); + } + + vmovupd(tmp0, src0); + vmovupd(tmp1, src1); + pf_src_t0(0); + vpermpd(tmp0 | kF0, vidx01, src2); + vpermpd(tmp1 | kF0, vidx01, src3); + + valignd(src0, src0, src0, 8); + valignd(src1, src1, src1, 8); + pf_src_t0(1); + vmovupd(tmp2, src0); + vmovupd(tmp3, src1); + pf_src_t0(2); + vpermpd(tmp2 | kF0, vidx10, src2); + vpermpd(tmp3 | kF0, vidx10, src3); + pf_src_t0(3); + + vmovupd(src0, tmp0); + pf_src_t1(0); + vmovupd(src1, tmp2); + pf_src_t1(1); + vmovupd(src2, tmp1); + pf_src_t1(2); + vmovupd(src3, tmp3); + pf_src_t1(3); + vpermpd(src0 | kCC, vidx1, tmp1); + vpermpd(src1 | kCC, vidx1, tmp3); + pf_tr_src_t0(0); + vpermpd(src2 | k33, vidx1, tmp0); + vpermpd(src3 | k33, vidx1, tmp2); + pf_tr_src_t0(1); + + vmovupd(tmp0, src0); + vmovupd(tmp1, src2); + pf_tr_src_t0(2); + vmovupd(tmp2, src1); + vmovupd(tmp3, src3); + pf_tr_src_t0(3); + vpermps(tmp0 | kFFFF, vidxP, src0); + pf_tr_src_t1(0); + vpermps(tmp1 | kFFFF, vidxP, src2); + pf_tr_src_t1(1); + vpermps(tmp2 | kFFFF, vidxP, src1); + pf_tr_src_t1(3); + vpermps(tmp3 | kFFFF, vidxP, src3); + pf_tr_src_t1(4); + + store(tmp0, 0); + store(tmp1, 1); + store(tmp2, 2); + store(tmp3, 3); +} + +alignas(64) static constexpr const int64_t idx01[8] + = { 0, 0, 0, 0, 0, 1, 2, 3 }; +alignas(64) static constexpr const int64_t idx10[8] + = { 0, 0, 0, 0, 4, 5, 6, 7 }; +alignas(64) static constexpr const int64_t idx1[8] = { 2, 3, 0, 1, 6, 7, 4, 5 }; +alignas(64) static constexpr const int32_t idxP[16] + = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; + +void jit_transpose4x16_src::generate() +{ + preamble(); + + const int ic_block = params->ic_block; + const int is = params->is; + int tail = is % transpose_size; + + src_stride = ic_block * typesize; + assert(src_stride == 64); + tr_src_stride = ic_block * typesize; + + const int src_step = ic_block * transpose_size * typesize; + const int tr_src_step = ic_block * transpose_size * typesize; + +#define GET_TR_OFF(x) offsetof(jit_src_transpose_s, x) + mov(reg_loop, ptr[param1 + GET_TR_OFF(size)]); + mov(reg_src, ptr[param1 + GET_TR_OFF(src)]); + mov(reg_tr_src, ptr[param1 + GET_TR_OFF(tr_src)]); + mov(reg_src_prf, ptr[param1 + GET_TR_OFF(src_prf)]); + mov(reg_tr_src_prf, ptr[param1 + GET_TR_OFF(tr_src_prf)]); +#undef GET_TR_OFF + + auto kmovw = [=](Opmask k, unsigned w) { + mov(regw_tmp, w); + jit_generator::kmovw(k, regw_tmp); + }; + + auto vmovdqa64 = [=](Zmm z, const int64_t *addr) { + mov(imm_addr64, reinterpret_cast(addr)); + jit_generator::vmovdqa64(z, ptr[imm_addr64]); + }; + + auto vmovdqa32 = [=](Zmm z, const int32_t *addr) { + mov(imm_addr64, reinterpret_cast(addr)); + jit_generator::vmovdqa32(z, ptr[imm_addr64]); + }; + + kmovw(kF0, 0xf0); // 11110000 + kmovw(kCC, 0xcc); // 11001100 + kmovw(k33, 0x33); // 00110011 + kmovw(kFFFF, 0xffff); // 1111111111111111 + + vmovdqa64(vidx01, idx01); + vmovdqa64(vidx10, idx10); + vmovdqa64(vidx1, idx1); + vmovdqa32(vidxP, idxP); + + Label loop_label; + Label tail_label; + + cmp(reg_loop, transpose_size); + jl(tail_label, T_NEAR); + + L(loop_label); + { + transpose(transpose_size); + add(reg_src, src_step); + add(reg_tr_src, tr_src_step); + add(reg_src_prf, src_step); + add(reg_tr_src_prf, tr_src_step); + sub(reg_loop, transpose_size); + cmp(reg_loop, transpose_size); + jge(loop_label, T_NEAR); + } + L(tail_label); + transpose(tail); + + postamble(); +} + +jit_trans_src_t *create_trans_src(const jit_conv_conf_t *conf) { + if (conf->ver == ver_4fma && !conf->is_1stconv) + return new jit_trans_iw_ic_t(conf); + if (conf->ver == ver_4fma && conf->is_1stconv) + return new jit_trans_iw_x4_4x_t(conf); + assert(!"unsupported configuration"); + return nullptr; +} + +jit_trans_dst_t *create_trans_dst(const jit_conv_conf_t *conf) { + assert(!"unsupported configuration"); + return nullptr; +} +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.hpp new file mode 100644 index 000000000000..565e97e4fc5e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_transpose_src_utils.hpp @@ -0,0 +1,145 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_TRANSPOSE_SRC_HPP +#define CPU_JIT_TRANSPOSE_SRC_HPP + +#include "cpu_barrier.hpp" +#include "jit_primitive_conf.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_trans_src_t { + struct ctx_t { + const void *src; + const void *tr_src; + const void *src_prf; + const void *tr_src_prf; + + /* 1st conv 4fma: backward by weights */ + int nthr_oc_b; /* number of threads process given src image */ + int tr_src_ih_start, tr_src_ih_end; /* thread's transposition bounds */ + simple_barrier::ctx_t *tr_src_bctx; /* transposition synchronization */ + }; + + jit_trans_src_t(const jit_conv_conf_t *conf) + : conf_(conf), ker_(nullptr) {} + virtual ~jit_trans_src_t() {} + + void operator()(const ctx_t *ctx) + { assert(ker_); ker_(ctx); } + + const jit_conv_conf_t *conf_; + void (*ker_)(const ctx_t *); +}; + +struct jit_src_transpose_s { + size_t size; + const void *src; + const void *tr_src; + const void *src_prf; + const void *tr_src_prf; +}; + +struct jit_trans_dst_t { + struct ctx_t { + const void *src; + const void *tr_src; + const void *src_prf; + const void *tr_src_prf; + + /* 1st conv 4fma: backward by weights */ + int nthr_oc_b; /* number of threads process given src image */ + int tr_src_ih_start, tr_src_ih_end; /* thread's transposition bounds */ + simple_barrier::ctx_t *tr_src_bctx; /* transposition synchronization */ + }; + + jit_trans_dst_t(const jit_conv_conf_t *conf) + : conf_(conf), ker_(nullptr) {} + virtual ~jit_trans_dst_t() {} + + void operator()(const ctx_t *ctx) + { assert(ker_); ker_(ctx); } + + const jit_conv_conf_t *conf_; + void (*ker_)(const ctx_t *); +}; + +struct jit_transpose4x16_src_t { + int src_pf0_distance; + int tr_src_pf0_distance; + bool src_pf1; + bool tr_src_pf1; +}; + +struct jit_transpose4x16_src : public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_transpose4x16_src) + + jit_transpose4x16_src(const jit_1x1_conv_conf_t *aparams, + jit_transpose4x16_src_t *tparams_) + : params(aparams), tparams(tparams_) + { + this->generate(); + jit_ker = (decltype(jit_ker))this->getCode(); + } + + const jit_1x1_conv_conf_t *params; + const jit_transpose4x16_src_t *tparams; + void (*jit_ker)(jit_src_transpose_s *); + + void operator()(jit_src_transpose_s *arg) { jit_ker(arg); } + + static const int transpose_size = 4; +private: + static const int typesize = sizeof(float); + + int src_stride, tr_src_stride; + + Xbyak::Reg64 imm_addr64 = rbx; + + Xbyak::Opmask kF0 = k1; + Xbyak::Opmask kCC = k2; + Xbyak::Opmask k33 = k3; + Xbyak::Opmask kFFFF = k4; + + Xbyak::Zmm vidx01 = zmm31; + Xbyak::Zmm vidx10 = zmm30; + Xbyak::Zmm vidx1 = zmm29; + Xbyak::Zmm vidxP = zmm28; + + Xbyak::Reg64 reg_src = r8; + Xbyak::Reg64 reg_tr_src = r9; + Xbyak::Reg64 reg_src_prf = r10; + Xbyak::Reg64 reg_tr_src_prf = r11; + Xbyak::Reg64 reg_loop = r12; + Xbyak::Reg64 reg_tr_src_tmp = r13; + Xbyak::Reg32 regw_tmp = r14d; + + void transpose_block(int ur, int nrows); + void transpose(int nrows); + void generate(); +}; + +jit_trans_src_t *create_trans_src(const jit_conv_conf_t *conf); +jit_trans_dst_t *create_trans_dst(const jit_conv_conf_t *conf); + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_1x1_conv_utils.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_1x1_conv_utils.hpp new file mode 100644 index 000000000000..53313f9f01b8 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_1x1_conv_utils.hpp @@ -0,0 +1,327 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_UNI_1x1_CONV_UTILS_HPP +#define JIT_UNI_1x1_CONV_UTILS_HPP + +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; + +struct reduce_to_unit_stride_t { + convolution_desc_t conv_d_; + bool reduce_src_; + size_t space_per_thread_; +}; + +/* 1x1-kernel does not support non-unit strides so far, so the idea is: + * - for fwd or bwd_weights: to copy src to a scratch memory (with strides + * equal to 1) and then call the kernel + * - for bwd_data: reduce the problem to the one with unit stride by + * performing computations in a scratch memory (with strides equal to 1) + * and then copy the result to diff_src */ +template +inline void rtus_prepare(conv_pd_t *self, const convolution_desc_t *&conv_d, + const memory_desc_t *&src_d, const memory_desc_t *dst_d) { + const bool is_bwd_data = self->desc()->prop_kind + == prop_kind::backward_data; + + const int ndims = src_d->ndims; + const auto dat_tag = ndims == 3 + ? memory_desc_wrapper(dst_d).matches_one_of_tag( + format_tag::nCw8c, format_tag::nCw16c) + : memory_desc_wrapper(dst_d).matches_one_of_tag( + format_tag::nChw8c, format_tag::nChw16c); + + bool rtus_applicable = true + && utils::pick(ndims - 3, + (conv_d->strides[0] != 1 && !one_of(conv_d->src_desc.data_type, + data_type::s32)), + (conv_d->strides[0] != 1 || conv_d->strides[1] != 1)) + && dat_tag != format_tag::undef; + for (int d = 2; d < ndims; ++d) { + /* TODO: relax these conditions (by improving reducer) */ + rtus_applicable = rtus_applicable + && conv_d->padding[0][d - 2] == 0 + && dst_d->dims[d] * conv_d->strides[d - 2] == src_d->dims[d]; + } + + if (rtus_applicable) { + self->rtus_.reduce_src_ = true; + conv_d = &(self->rtus_.conv_d_ = *conv_d); + self->rtus_.conv_d_.strides[0] = 1; + if (ndims == 4) + self->rtus_.conv_d_.strides[1] = 1; + utils::array_set(self->rtus_.conv_d_.padding[0], 0, 2); + if (ndims == 4) + utils::array_set(self->rtus_.conv_d_.padding[1], 0, 2); + const int ic = src_d->dims[1]; + if (is_bwd_data) { + src_d = &(self->rtus_.conv_d_.diff_src_desc = *dst_d); + self->rtus_.conv_d_.diff_src_desc.dims[1] = ic; + memory_desc_wrapper::compute_blocking( + self->rtus_.conv_d_.diff_src_desc, dat_tag); + } else { + data_type_t data_type = self->rtus_.conv_d_.src_desc.data_type; + src_d = &(self->rtus_.conv_d_.src_desc = *dst_d); + self->rtus_.conv_d_.src_desc.dims[1] = ic; + self->rtus_.conv_d_.src_desc.data_type = data_type; + memory_desc_wrapper::compute_blocking( + self->rtus_.conv_d_.src_desc, dat_tag); + } + } +} + +template +inline void rtus_prepare_space_info(conv_pd_t *self, + memory_tracking::registrar_t &scratchpad) { + const auto &jcp = self->jcp_; + + const int max_threads = mkldnn_get_max_threads(); + const size_t factor = utils::pick_by_prop_kind(self->desc()->prop_kind, + jcp.nb_reduce, jcp.nb_load_blocking_max, jcp.nb_bcast_blocking); + size_t typesize = types::data_type_size( + conv_prop_invariant_src_d(self->desc())->data_type); + + self->rtus_.space_per_thread_ = factor * jcp.is * jcp.ic_block; + scratchpad.book(memory_tracking::names::key_conv_rtus_space, + typesize * max_threads * self->rtus_.space_per_thread_); +} + +template +struct rtus_driver_t: public jit_generator { + + struct call_params_t { + const void *ws; /* reduced image (w/ strides = 1) */ + const void *src; /* source image (w/ non-unit strides) */ + size_t icb; + size_t os; + size_t iw_start; + }; + + void (*ker_)(const call_params_t *p); + + DECLARE_CPU_JIT_AUX_FUNCTIONS(rtus_driver_t) + + /* cpu specific part */ + using Vmm = typename utils::conditional::type; + + Xbyak::Reg64 reg_ws = abi_param1; + Xbyak::Reg64 reg_src = abi_not_param1; + Xbyak::Reg64 reg_icb = rdx; + Xbyak::Reg64 reg_os = r11; + Xbyak::Reg64 reg_iw_start = r8; + + Xbyak::Reg64 reg_cur_os = rax; + Xbyak::Reg64 reg_cur_iw = r9; + Xbyak::Reg64 reg_cur_src = r10; + + int iw_, stride_w_; + int src_step_h_, src_step_icb_, ws_step_icb_, vlen_, vlen_shift_; + bool src_to_ws_; + size_t typesize_; + Vmm reg_zero; + Vmm reg_v; + + rtus_driver_t(int iw, int stride_w, int src_step_h, + int src_step_icb, int ws_step_icb, bool src_to_ws, size_t typesize) + : iw_(iw), stride_w_(stride_w), src_step_h_(src_step_h) + , src_step_icb_(src_step_icb), ws_step_icb_(ws_step_icb) + , src_to_ws_(src_to_ws), typesize_(typesize) + { + using namespace Xbyak; + vlen_ = cpu_isa_traits::vlen; + vlen_shift_ = cpu_isa_traits::vlen_shift; + if (typesize_ == 2) { + vlen_ /= 2; + vlen_shift_--; + } + + reg_zero = Vmm(0); + reg_v = Vmm(1); + + generate(); + } + + void loop_is() { + using namespace Xbyak; + + mov(reg_cur_src, reg_src); + mov(reg_cur_iw, reg_iw_start); + mov(reg_cur_os, reg_os); + + Label is_loop, skip_h_step; + L(is_loop); + + if (src_to_ws_) { + vmovups(reg_v, ptr[reg_cur_src]); + vmovups(ptr[reg_ws], reg_v); + } else { + vmovups(reg_v, ptr[reg_ws]); + vmovups(ptr[reg_cur_src], reg_v); + for (int w = 1; w < stride_w_; ++w) + vmovups(ptr[reg_cur_src + w * vlen_], reg_zero); + } + + add(reg_ws, vlen_); + + add(reg_cur_iw, stride_w_); + add(reg_cur_src, stride_w_ * vlen_); + + cmp(reg_cur_iw, iw_); + jl(skip_h_step); + /* for 1d convolution the loop over h should be skipped */ + if (src_step_icb_ == iw_) jmp(skip_h_step); + + if (src_to_ws_) { + add(reg_cur_src, (src_step_h_ - iw_) * vlen_); + } else { + Xbyak::Reg64 reg_cur_src_fin = reg_cur_iw; /* just reuse */ + mov(reg_cur_src_fin, reg_cur_src); + add(reg_cur_src_fin, (src_step_h_ - iw_) * vlen_); + Label ih_loop; + L(ih_loop); + + for (int w = 0; w < stride_w_; ++w) + vmovups(ptr[reg_cur_src + w * vlen_], reg_zero); + + add(reg_cur_src, stride_w_ * vlen_); + cmp(reg_cur_src, reg_cur_src_fin); + jl(ih_loop); + } + xor_(reg_cur_iw, reg_cur_iw); + + L(skip_h_step); + + sub(reg_cur_os, vlen_); + jnz(is_loop); + + /* restore dst */ + sub(reg_ws, reg_os); + } + + void generate() { + using namespace Xbyak; + assert(isa == avx2 || isa == avx512_common + || isa == avx512_core || isa == avx512_mic); + +#if defined(_WIN32) + assert(reg_src == abi_not_param1 && abi_not_param1 == rdi); + push(rdi); +#endif + +#define READ_PARAM(what) \ + mov(reg_ ## what, ptr[abi_param1 + offsetof(call_params_t, what)]) + READ_PARAM(src); + READ_PARAM(icb); + READ_PARAM(os); + READ_PARAM(iw_start); + + assert(reg_ws == abi_param1); + READ_PARAM(ws); /* reg_ws should always be read the last */ +#undef READ_PARAM + + shl(reg_os, vlen_shift_); + + if (!src_to_ws_) + uni_vpxor(reg_zero, reg_zero, reg_zero); + + Label icb_loop; + L(icb_loop); + + loop_is(); + + add(reg_ws, ws_step_icb_ * vlen_); + add(reg_src, src_step_icb_ * vlen_); + + dec(reg_icb); + jnz(icb_loop, T_NEAR); + +#if defined(_WIN32) + pop(rdi); +#endif + + uni_vzeroupper(); + ret(); + this->ker_ = reinterpret_cast(const_cast( + this->getCode())); + } +}; + +template +inline void init_rtus_driver(conv_t *self) { + const auto &conf = *self->pd(); + if (!conf.rtus_.reduce_src_) return; + + const auto &cd = *conf.desc(); + const int ndims = conf.ndims(); + const int stride_h = (conf.ndims() == 3) ? 1 : cd.strides[0]; + const int stride_w = cd.strides[ndims - 3]; + + const bool is_bwd_data = cd.prop_kind == prop_kind::backward_data; + const auto &src_d = is_bwd_data ? *conf.diff_src_md() : *conf.src_md(); + + const int ih = ndims == 3 ? 1 : src_d.dims[2]; + const int iw = src_d.dims[ndims - 1]; + + const int src_step_h = stride_h * iw; + const int src_step_icb = ih * iw; + const int ws_step_icb = conf.jcp_.is; + const bool src_to_ws = !is_bwd_data; + const size_t typesize = types::data_type_size( + conv_prop_invariant_src_d(self->pd()->desc())->data_type); + + self->rtus_driver_ = new rtus_driver_t(iw, stride_w, src_step_h, + src_step_icb, ws_step_icb, src_to_ws, typesize); +} + +inline int best_divider(int value, int min_divider, int max_divider, + bool find_max, int step = 1) +{ + max_divider = nstl::max(1, nstl::min(max_divider, value)); + min_divider = nstl::max(1, nstl::min(min_divider, max_divider)); + + auto loss_ratio = [](int total, int chunk) + { return float(rnd_up(total, chunk) - total) / rnd_up(total, chunk); }; + + float min_loss = FLT_MAX; + int x_divider = max_divider; + for (int divider = max_divider; divider >= min_divider; divider -= step) { + const float loss = loss_ratio(value, divider); + if ((find_max && loss < min_loss) || (!find_max && loss <= min_loss)) { + min_loss = loss; + x_divider = divider; + } + } + return x_divider; +} + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.cpp new file mode 100644 index 000000000000..72fe3a810971 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.cpp @@ -0,0 +1,1407 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "math_utils.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_barrier.hpp" +#include "cpu_batch_normalization_utils.hpp" +#include "jit_generator.hpp" + +#include "jit_uni_batch_normalization.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace { + +using namespace memory_tracking::names; + +using namespace Xbyak; +namespace barrier = simple_barrier; + +typedef float data_t; + +template +struct jit_bnorm_t: public jit_generator { + struct call_params_t { + // keep all sizes at 8 bytes -- jit code expects this + size_t N_ithr, N_nthr; + size_t coff_max, soff_max; + size_t mb_stride_Bc, spat_size, spat_size_loc; + size_t S_s, S_tail; + size_t is_cblk_tail; + data_t chan_size, eps, one; + const data_t *scale_shift; + const data_t *mean, *var; + const data_t *diff_scale_shift; + const data_t *src, *dst; + const data_t *diff_src, *diff_dst; + const data_t *rbuf1, *rbuf2; + const uint8_t *ws; + barrier::ctx_t *barrier; + }; + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_bnorm_t) + + /* cpu specific part */ + using Vmm = typename utils::conditional3::type; + const AddressFrame &vmmword = (isa == sse42) ? xword : + (isa == avx2) ? yword : zword; + + const int vlen = isa == sse42 ? 32 : cpu_isa_traits::vlen; + + const batch_normalization_pd_t *bdesc_; + bool is_spatial_thr_; + + void (*ker)(const call_params_t *); + void operator()(const call_params_t *p) { (*ker)(p); } + + Reg64 reg_param = abi_param1; + + Reg64 reg_scale_shift = rbx; + Reg64 reg_rbuf1 = abi_not_param1; + Reg64 reg_rbuf2 = rdx; + + Reg64 reg_mean = rbp; + Reg64 reg_var = reg_param; + Reg64 reg_diff_scale_shift = rax; + + Reg64 reg_coff = r8; + Reg64 reg_coff_max = r9; + Reg64 reg_soff = r10; + Reg64 reg_soff_max = r11; + Reg64 reg_ctr = r12; + Reg64 reg_roff = r13; + + Reg64 reg_mb_stride_Bc = r14; + + Reg64 reg_src = r15; + Reg64 reg_diff_src = reg_rbuf1; + Reg64 reg_dst = rsi; + Reg64 reg_diff_dst = reg_dst; + + Reg64 reg_tmp_off = reg_roff; + + // Reuse loop counters + Reg64 reg_bar = reg_coff; + Reg64 reg_nnthr = reg_soff; // must be usable w/ loops over coff + Reg64 reg_tmp = reg_ctr; + + // Relu section + bool with_relu, with_relu_inf_only; + Vmm vzero; // is_fwd() ? vdiff_beta : vbeta + Reg64 reg_ws = reg_roff; + Label l_relu_mask_avx2; + Opmask kstore_mask = Opmask(1); + + // channel tail processing + Opmask ktail_mask = Opmask(2); + + size_t unroll_blocks; + size_t unroll_regs; + Vmm vbuf = Vmm(isa == avx512_common ? 20 : 5); + Vmm vdiff_beta = Vmm(isa == avx512_common ? 21 : 6); + Vmm vdiff_gamma = Vmm(isa == avx512_common ? 22 : 7); + Vmm vsqrtvar = Vmm(isa == avx512_common ? 23 : 8); + Vmm vone = Vmm(isa == avx512_common ? 24 : 9); + Vmm vmean = Vmm(isa == avx512_common ? 25 : 10); + Vmm vgamma = Vmm(isa == avx512_common ? 26 : 11); + Vmm vbeta = Vmm(isa == avx512_common ? 27 : 12); + Vmm veps = Vmm(isa == avx512_common ? 28 : 13); + Vmm vchan_size = Vmm(isa == avx512_common ? 29 : 14); + Vmm vtail_mask = Vmm(isa == avx512_common ? 30 : 15); + + size_t t0_pf_offt; + size_t t1_pf_offt; + size_t spat_size; + size_t chan_data_offt; + + enum { + stack_off_N_nthr = 0, + stack_off_N_ithr = 8, + stack_off_src = 16, + stack_off_dst = 24, + stack_off_diff_src = 32, + stack_off_diff_dst = 40, + stack_off_diff_scale_shift = 48, + stack_off_ws = 56, + stack_off_barrier = 64, + stack_off_spat_size_loc = 72, + stack_off_s_s = 80, + stack_off_s_tail = 88, + stack_off_is_cblk_tail = 96, + stack_size_required = 104, + }; + + bool is_c_padded() const { + const memory_desc_wrapper data_d(bdesc_->src_md()); + return bdesc_->C() != data_d.padded_dims()[1]; + } + + void compute_static_strides() { + spat_size = bdesc_->D() * bdesc_->W() * bdesc_->H(); + chan_data_offt = bdesc_->C() * sizeof(data_t); + + if (isa == avx512_mic) { + t0_pf_offt = 4096; + t1_pf_offt = 0; + } else { + t0_pf_offt = 0; + t1_pf_offt = 0; + } + } + + void load_common_params() { +# define PARAM_OFF(x) offsetof(call_params_t, x) + mov(reg_rbuf1, ptr[reg_param + PARAM_OFF(rbuf1)]); + if (bdesc_->is_bwd()) + mov(reg_rbuf2, ptr[reg_param + PARAM_OFF(rbuf2)]); + mov(reg_coff_max, ptr[reg_param + PARAM_OFF(coff_max)]); + mov(reg_soff_max, ptr[reg_param + PARAM_OFF(soff_max)]); + mov(reg_mb_stride_Bc, ptr[reg_param + PARAM_OFF(mb_stride_Bc)]); + shl(reg_coff_max, 2); + shl(reg_soff_max, 2); + shl(reg_mb_stride_Bc, 2); + + mov(reg_mean, ptr[reg_param + PARAM_OFF(mean)]); + mov(reg_scale_shift, ptr[reg_param + PARAM_OFF(scale_shift)]); + + uni_vbroadcastss(vchan_size, vmmword[reg_param + PARAM_OFF(chan_size)]); + uni_vbroadcastss(vone, vmmword[reg_param + PARAM_OFF(one)]); + uni_vbroadcastss(veps, vmmword[reg_param + PARAM_OFF(eps)]); + + mov(reg_tmp, ptr[reg_param + PARAM_OFF(N_nthr)]); + mov(ptr[rsp + stack_off_N_nthr], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(N_ithr)]); + mov(ptr[rsp + stack_off_N_ithr], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(src)]); + mov(ptr[rsp + stack_off_src], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(dst)]); + mov(ptr[rsp + stack_off_dst], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(diff_src)]); + mov(ptr[rsp + stack_off_diff_src], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(diff_dst)]); + mov(ptr[rsp + stack_off_diff_dst], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(ws)]); + mov(ptr[rsp + stack_off_ws], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(barrier)]); + mov(ptr[rsp + stack_off_barrier], reg_tmp); + if (is_spatial_thr_) { + mov(reg_tmp, ptr[reg_param + PARAM_OFF(spat_size_loc)]); + mov(ptr[rsp + stack_off_spat_size_loc], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(S_s)]); + mov(ptr[rsp + stack_off_s_s], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(S_tail)]); + mov(ptr[rsp + stack_off_s_tail], reg_tmp); + } + if (is_c_padded()) { + mov(reg_tmp, ptr[reg_param + PARAM_OFF(is_cblk_tail)]); + mov(ptr[rsp + stack_off_is_cblk_tail], reg_tmp); + } + + if (bdesc_->is_fwd()) { + mov(reg_tmp, ptr[reg_param + PARAM_OFF(var)]); + mov(reg_var, reg_tmp); + } else { + mov(reg_tmp, ptr[reg_param + PARAM_OFF(diff_scale_shift)]); + mov(ptr[rsp + stack_off_diff_scale_shift], reg_tmp); + mov(reg_tmp, ptr[reg_param + PARAM_OFF(var)]); + mov(reg_var, reg_tmp); + } +# undef PARAM_OFF + } + + void prepare_tail_mask_avx512_common() { + if (!is_c_padded()) return; + + const int tail = bdesc_->C() % (int)(vlen / sizeof(float)); + const int mask = (1 << tail) - 1; + + Reg32 regw_tmp = reg_tmp.cvt32(); + mov(regw_tmp, mask); + kmovw(ktail_mask, regw_tmp); + } + + void prepare_tail_mask_avx2_common() { + if (!is_c_padded()) return; + + const int tail = bdesc_->C() % (int)(vlen / sizeof(float)); + static const uint32_t mask[16] = {0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0, 0, 0, 0, 0, 0, 0, 0}; + + mov(reg_tmp, reinterpret_cast(&mask[8 - tail])); + vmovups(vtail_mask, ptr[reg_tmp]); + } + + void prepare_relu() { + with_relu = bdesc_->is_fwd() + ? bdesc_->with_relu_post_op() || bdesc_->fuse_bn_relu() + : bdesc_->fuse_bn_relu(); + with_relu_inf_only = with_relu && bdesc_->is_fwd() + && !(bdesc_->fuse_bn_relu() && bdesc_->is_training()); + + vzero = bdesc_->is_fwd() ? vdiff_beta : vbeta; + if (with_relu) { + uni_vpxor(vzero, vzero, vzero); + if (!bdesc_->is_fwd() && isa == avx2) + prepare_l_relu_mask_avx2(); + } + } + + void prepare_l_relu_mask_avx2() { + Label l_mask_after; + jmp(l_mask_after); + align(32); + L(l_relu_mask_avx2); /* [0x80 0x40 0x20 0x10 0x08 0x04 0x02 0x01] */ + for (int i = 0; i < 8; ++i) dd(1< + void spat_loop(size_t len, size_t blocks, size_t regs, + init_t init, body_t body, fini_t fini) { + size_t factor = regs * blocks; + size_t loop_unroll = len / factor * factor; + size_t loop_tail = len - loop_unroll; + size_t num_active_regs = (len < regs) ? len : regs; + for (size_t i = 0; i < num_active_regs; i++) + init(i); + if (loop_unroll) { + if (is_spatial_thr_) { + mov(reg_ctr, ptr[rsp + stack_off_spat_size_loc]); + add(reg_soff, ptr[rsp + stack_off_s_s]); + } else { + mov(reg_ctr, loop_unroll); + } + Label label; + L(label); { + for (size_t i = 0; i < factor; i++) { + size_t base_reg = i % regs; + body(base_reg, i); + } + add(reg_soff, factor * vlen); + sub(reg_ctr, factor); + jnz(label); + } + if (is_spatial_thr_) { + add(reg_soff, ptr[rsp + stack_off_s_tail]); + } + } + + for (size_t i = 0; i < loop_tail; i++) { + size_t base_reg = i % regs; + body(base_reg, i); + } + if (loop_tail) + add(reg_soff, loop_tail * vlen); + + for (size_t i = 0; i < num_active_regs; i++) + fini(i); + } + + void mean_channels() { + Label ch_label; + L(ch_label); { + uni_vmovups(Vmm(0), vmmword[reg_rbuf1 + reg_coff]); + spat_loop(spat_size, unroll_blocks, + unroll_regs, + [=](size_t base_reg) { + Vmm v = Vmm(base_reg * 2); + if (base_reg) + uni_vpxor(v, v, v); + }, + [=](size_t base_reg, size_t i) { + Vmm v0 = Vmm(base_reg * 2 + 0); + Vmm v1 = Vmm(base_reg * 2 + 1); + size_t offt = i * vlen; + uni_vmovups(v1, + vmmword[reg_src + reg_soff + offt]); + uni_vaddps(v0, v0, v1); + mic_prefetcht0(ptr[reg_src + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht1(ptr[reg_src + reg_soff + offt + + t1_pf_offt]); + }, + [=](size_t base_reg) { + Vmm b = Vmm(0); + Vmm v = Vmm(base_reg * 2); + if (base_reg) + uni_vaddps(b, b, v); + }); + uni_vmovups(vmmword[reg_rbuf1 + reg_coff], Vmm(0)); + + add(reg_coff, vlen); + cmp(reg_coff, reg_coff_max); + jl(ch_label); + } + } + + void var_channels() { + Label ch_label; + L(ch_label); { + uni_vmovups_maybe_tail(vmean, mean_ptr()); + uni_vmovups(Vmm(0), vmmword[reg_rbuf1 + reg_coff]); + spat_loop(spat_size, unroll_blocks, unroll_regs, + [=](size_t base_reg) { + Vmm v = Vmm(base_reg * 3); + if (base_reg > 0) + uni_vpxor(v, v, v); + }, + [=](size_t base_reg, size_t i) { + Vmm v = Vmm(3 * base_reg); + Vmm vtmp0 = Vmm(3 * base_reg + 1); + Vmm vtmp1 = Vmm(3 * base_reg + 2); + size_t offt = i * vlen; + uni_vmovups(vtmp0, + vmmword[reg_src + reg_soff + offt]); + if (isa == sse42) { + movups(vtmp1, vmean); + subps(vtmp1, vtmp0); + } else { + vsubps(vtmp1, vmean, vtmp0); + } + uni_vfmadd231ps(v, vtmp1, vtmp1); + + mic_prefetcht0(ptr[reg_src + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht1(ptr[reg_src + reg_soff + offt + + t1_pf_offt]); + }, + [=](size_t base_reg) { + Vmm b = Vmm(0); + Vmm v = Vmm(base_reg * 3); + if (base_reg) + uni_vaddps(b, b, v); + }); + uni_vmovups(vmmword[reg_rbuf1 + reg_coff], Vmm(0)); + add(reg_coff, vlen); + cmp(reg_coff, reg_coff_max); + jl(ch_label); + } + } + + void compute_mean_variance() { + uni_vpxor(Vmm(0), Vmm(0), Vmm(0)); + xor_(reg_coff, reg_coff); + Label zero_rbuf; + L(zero_rbuf); { + uni_vmovups(vmmword[reg_rbuf1 + reg_coff], Vmm(0)); + add(reg_coff, isa == sse42 ? vlen / 2 : vlen); + cmp(reg_coff, reg_coff_max); + jne(zero_rbuf); + } + + mov(reg_src, ptr[rsp + stack_off_src]); + + xor_(reg_soff, reg_soff); + Label mean_spatial; + L(mean_spatial); { + xor_(reg_coff, reg_coff); + + if (isa == sse42) + mov(reg_tmp_off, reg_soff); + + mean_channels(); + + if (isa == sse42) { + mov(reg_soff, reg_tmp_off); + add(reg_src, vlen / 2); + mov(reg_coff, vlen / 2); + + mean_channels(); + + sub(reg_src, vlen / 2); + } + + add(reg_soff, reg_mb_stride_Bc); + cmp(reg_soff, reg_soff_max); + jne(mean_spatial); + } + + Label no_mean_reduction; + barrier(); { + mov(reg_tmp, ptr[rsp + stack_off_N_ithr]); + cmp(reg_tmp, 0); + jne(no_mean_reduction); + mov(reg_nnthr, ptr[rsp + stack_off_N_nthr]); + xor_(reg_coff, reg_coff); + Label mean_reduction_channels; + L(mean_reduction_channels); { + mov(reg_roff, reg_coff); + uni_vpxor(Vmm(0), Vmm(0), Vmm(0)); + uni_vpxor(Vmm(1), Vmm(1), Vmm(1)); + mov(reg_ctr, reg_nnthr); + Label mean_reduction_thrs; + L(mean_reduction_thrs); { + uni_vaddps(Vmm(1), Vmm(1), vmmword[reg_rbuf1 + reg_roff]); + uni_vmovups(vmmword[reg_rbuf1 + reg_roff], Vmm(0)); + add(reg_roff, reg_coff_max); + sub(reg_ctr, 1); + jnz(mean_reduction_thrs); + } + uni_vdivps(Vmm(1), Vmm(1), vchan_size); + uni_vmovups_maybe_tail(mean_ptr(), Vmm(1)); + + add(reg_coff, isa == sse42 ? vlen / 2 : vlen); + + cmp(reg_coff, reg_coff_max); + jne(mean_reduction_channels); + } + } + L(no_mean_reduction); + barrier(); + + xor_(reg_soff, reg_soff); + Label var_spatial; + L(var_spatial); { + xor_(reg_coff, reg_coff); + + if (isa == sse42) + mov(reg_tmp_off, reg_soff); + + var_channels(); + + if (isa == sse42) { + mov(reg_soff, reg_tmp_off); + add(reg_src, vlen / 2); + mov(reg_coff, vlen / 2); + + var_channels(); + + sub(reg_src, vlen / 2); + } + + add(reg_soff, reg_mb_stride_Bc); + cmp(reg_soff, reg_soff_max); + jne(var_spatial); + } + + Label no_var_reduction; + barrier(); { + mov(reg_tmp, ptr[rsp + stack_off_N_ithr]); + cmp(reg_tmp, 0); + jne(no_var_reduction); + + mov(reg_nnthr, ptr[rsp + stack_off_N_nthr]); + xor_(reg_coff, reg_coff); + Label var_reduction_channels; + L(var_reduction_channels); { + mov(reg_roff, reg_coff); + uni_vpxor(Vmm(1), Vmm(1), Vmm(1)); + mov(reg_ctr, reg_nnthr); + Label var_reduction_thrs; + L(var_reduction_thrs); { // TODO: unroll (?) + uni_vaddps(Vmm(1), Vmm(1), vmmword[reg_rbuf1 + reg_roff]); + add(reg_roff, reg_coff_max); + sub(reg_ctr, 1); + jnz(var_reduction_thrs); + } + uni_vdivps(Vmm(1), Vmm(1), vchan_size); + uni_vmovups_maybe_tail(var_ptr(), Vmm(1)); + add(reg_coff, isa == sse42 ? vlen / 2 : vlen); + + cmp(reg_coff, reg_coff_max); + jne(var_reduction_channels); + } + } + L(no_var_reduction); + barrier(); + } + + void forward_channels() { + Label ch_label; + L(ch_label); { + uni_vmovups_maybe_tail(vmean, mean_ptr()); + uni_vmovups_maybe_tail(vsqrtvar, var_ptr()); + uni_vaddps(vsqrtvar, vsqrtvar, veps); + uni_vsqrtps(vsqrtvar, vsqrtvar); + + if (bdesc_->use_scaleshift()) { + uni_vmovups_maybe_tail(vgamma, gamma_ptr()); + uni_vmovups_maybe_tail(vbeta, beta_ptr()); + } + + Vmm vscale = bdesc_->use_scaleshift() ? vgamma : vone; + Vmm vdiv = bdesc_->use_scaleshift() ? vgamma : vsqrtvar; + + if (isa == sse42) { + movups(vbuf, vscale); + divps(vbuf, vsqrtvar); + movups(vdiv, vbuf); + } else { + vdivps(vdiv, vscale, vsqrtvar); + } + + auto compute = [=](bool output_is_aligned) { + spat_loop(spat_size, unroll_blocks, unroll_regs, + [](size_t base_reg) {UNUSED(base_reg);}, + [=](size_t base_reg, size_t i) { + Vmm v = Vmm(base_reg); + size_t offt = i * vlen; + uni_vmovups(v, + vmmword[reg_src + reg_soff + offt]); + mic_prefetcht0(ptr[reg_src + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht1(ptr[reg_src + reg_soff + offt + + t1_pf_offt]); + uni_vsubps(v, v, vmean); + if (bdesc_->use_scaleshift()) { + uni_vfmadd213ps(v, vgamma, vbeta); + } else { + uni_vmulps(v, v, vsqrtvar); + } + if (with_relu_inf_only) { + uni_vmaxps(v, v, vzero); + } else if (with_relu) { + if (isa == avx512_common) + fwd_process_relu_avx512_common(v, offt); + else + fwd_process_relu_avx2(v, offt, Vmm(3)); + } + if (output_is_aligned) { + uni_vmovntps( + vmmword[reg_dst + reg_soff + offt], v); + } else { + uni_vmovups( + vmmword[reg_dst + reg_soff + offt], v); + } + }, + [](size_t base_reg) {UNUSED(base_reg);}); + }; + + Label unaligned_store, end_store; + test(reg_dst, vlen - 1); + jnz(unaligned_store, T_NEAR); + compute(true); + jmp(end_store, T_NEAR); + L(unaligned_store); { + compute(false); + } + L(end_store); + + add(reg_coff, vlen); + cmp(reg_coff, reg_coff_max); + jl(ch_label); + } + } + + void forward() { + mov(reg_src, ptr[rsp + stack_off_src]); + mov(reg_dst, ptr[rsp + stack_off_dst]); + mov(reg_ws, ptr[rsp + stack_off_ws]); + + xor_(reg_soff, reg_soff); + Label dst_spatial; + L(dst_spatial); { + xor_(reg_coff, reg_coff); + if (isa == sse42) + mov(reg_tmp_off, reg_soff); + + forward_channels(); + + if (isa == sse42) { + mov(reg_soff, reg_tmp_off); + add(reg_src, vlen / 2); + add(reg_dst, vlen / 2); + mov(reg_coff, vlen / 2); + + forward_channels(); + + sub(reg_src, vlen / 2); + sub(reg_dst, vlen / 2); + } + + add(reg_soff, reg_mb_stride_Bc); + cmp(reg_soff, reg_soff_max); + jnz(dst_spatial); + } + } + + void backward_sh_channels() { + Label sh_channels; + L(sh_channels); { + uni_vmovups_maybe_tail(vmean, mean_ptr()); + uni_vmovups(Vmm(0), vmmword[reg_rbuf1 + reg_coff]); + uni_vmovups(Vmm(1), vmmword[reg_rbuf2 + reg_coff]); + spat_loop(spat_size, 1, 1, + [=](size_t base_reg) { + if (base_reg > 0) { + for (int i = 0; i < 2; i++) { + Vmm v(base_reg * 5 + i); + uni_vpxor(v, v, v); + } + } + }, + [=](size_t base_reg, size_t i) { + Vmm o0 = Vmm(base_reg * 5 + 0); + Vmm o1 = Vmm(base_reg * 5 + 1); + Vmm t1 = Vmm(base_reg * 5 + 2); + Vmm t2 = Vmm(base_reg * 5 + 3); + Vmm t3 = Vmm(base_reg * 5 + 4); + size_t offt = i * vlen; + uni_vmovups(t1, vmmword[reg_src + reg_soff + offt]); + uni_vmovups(t2, vmmword[reg_diff_dst + reg_soff + + offt]); + if (with_relu) { + if (isa == avx512_common) + bwd_process_relu_avx512_common(t2, offt); + else if (isa == avx2) + bwd_process_relu_avx2(t2, offt, t3); + else + assert(false); + } + uni_vsubps(t3, vmean, t1, t3); + if (isa == sse42) { + mulps(t3, t2); + subps(o0, t3); + } else { + vfnmadd231ps(o0, t3, t2); + } + uni_vaddps(o1, o1, t2); + mic_prefetcht0(ptr[reg_diff_dst + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht0(ptr[reg_src + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht1(ptr[reg_diff_dst + reg_soff + offt + + t1_pf_offt]); + mic_prefetcht1(ptr[reg_src + reg_soff + offt + + t1_pf_offt]); + }, + [=](size_t base_reg) { + Vmm b0 = Vmm(0); + Vmm b1 = Vmm(1); + if (base_reg) { + uni_vaddps(b0, b0, Vmm(base_reg * 5 + 0)); + uni_vaddps(b1, b1, Vmm(base_reg * 5 + 1)); + } + }); + uni_vmovups(vmmword[reg_rbuf1 + reg_coff], Vmm(0)); + uni_vmovups(vmmword[reg_rbuf2 + reg_coff], Vmm(1)); + add(reg_coff, vlen); + cmp(reg_coff, reg_coff_max); + jl(sh_channels); + } + } + + void backward_diff_channels() { + Label diff_channels; + L(diff_channels); { + uni_vmovups_maybe_tail(vmean, mean_ptr()); + uni_vmovups_maybe_tail(vsqrtvar, var_ptr()); + uni_vaddps(vsqrtvar, vsqrtvar, veps); + uni_vsqrtps(vsqrtvar, vsqrtvar); + uni_vdivps(vsqrtvar, vone, vsqrtvar, vbuf); + if (bdesc_->use_scaleshift()) + uni_vmovups_maybe_tail(vgamma, gamma_ptr()); + uni_vmovups_maybe_tail(vdiff_gamma, diff_gamma_ptr()); + uni_vmovups_maybe_tail(vdiff_beta, diff_beta_ptr()); + uni_vmulps(vdiff_gamma, vdiff_gamma, vsqrtvar); + uni_vdivps(vdiff_beta, vdiff_beta, vchan_size); + uni_vdivps(vdiff_gamma, vdiff_gamma, vchan_size); + + auto compute = [=](bool output_is_aligned) { + spat_loop(spat_size, unroll_blocks, unroll_regs, + [=](size_t base_reg) {UNUSED(base_reg);}, + [=](size_t base_reg, size_t i) { + Vmm v(base_reg * 2 + 0); + Vmm t(base_reg * 2 + 1); + Vmm t1(base_reg * 2 + 2); + size_t offt = i * vlen; + uni_vmovups(v, vmmword[reg_diff_dst + reg_soff + + offt]); + if (with_relu) { + if (isa == avx512_common) + bwd_process_relu_avx512_common(v, offt); + else if (isa == avx2) + bwd_process_relu_avx2(v, offt, t); + else + assert(false); + } + if (!bdesc_->use_global_stats()) { + uni_vsubps(v, v, vdiff_beta); + uni_vmovups(t, vmmword[reg_src + reg_soff + + offt]); + uni_vsubps(t, vmean, t, t1); + uni_vmulps(t, t, vdiff_gamma); + uni_vaddps(v, v, t); + } + uni_vmulps(v, v, vsqrtvar); + if (bdesc_->use_scaleshift()) { + uni_vmulps(v, v, vgamma); + } + if (output_is_aligned) { + uni_vmovntps( + vmmword[reg_diff_src + reg_soff + offt], + v); + } else { + uni_vmovups( + vmmword[reg_diff_src + reg_soff + offt], + v); + } + mic_prefetcht0(ptr[reg_diff_dst + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht0(ptr[reg_src + reg_soff + offt + + t0_pf_offt]); + mic_prefetcht1(ptr[reg_diff_dst + reg_soff + + offt + t1_pf_offt]); + mic_prefetcht1(ptr[reg_src + reg_soff + offt + + t1_pf_offt]); + }, + [=](size_t base_reg) {UNUSED(base_reg);}); + }; + + Label unaligned_store, end_store; + test(reg_diff_src, vlen - 1); + jnz(unaligned_store, T_NEAR); + compute(true); + jmp(end_store, T_NEAR); + L(unaligned_store); { + compute(false); + } + L(end_store); + + add(reg_coff, vlen); + cmp(reg_coff, reg_coff_max); + jl(diff_channels); + } + } + + void backward() { + uni_vpxor(Vmm(0), Vmm(0), Vmm(0)); + xor_(reg_coff, reg_coff); + Label zero_rbuf, sh_spatial; + + L(zero_rbuf); { + uni_vmovups(vmmword[reg_rbuf1 + reg_coff], Vmm(0)); + uni_vmovups(vmmword[reg_rbuf2 + reg_coff], Vmm(0)); + add(reg_coff, isa == sse42 ? vlen / 2 : vlen); + cmp(reg_coff, reg_coff_max); + jne(zero_rbuf); + } + + mov(reg_src, ptr[rsp + stack_off_src]); + mov(reg_diff_dst, ptr[rsp + stack_off_diff_dst]); + if (with_relu) { + assert(isa == avx2 || isa == avx512_common); + mov(reg_ws, ptr[rsp + stack_off_ws]); + } + + xor_(reg_soff, reg_soff); + L(sh_spatial); { + xor_(reg_coff, reg_coff); + if (isa == sse42) { + mov(reg_tmp_off, reg_soff); + } + backward_sh_channels(); + if (isa == sse42) { + mov(reg_soff, reg_tmp_off); + add(reg_diff_dst, vlen / 2); + add(reg_src, vlen / 2); + mov(reg_coff, vlen / 2); + backward_sh_channels(); + sub(reg_diff_dst, vlen / 2); + sub(reg_src, vlen / 2); + } + add(reg_soff, reg_mb_stride_Bc); + cmp(reg_soff, reg_soff_max); + jne(sh_spatial); + } + + mov(reg_diff_scale_shift, ptr[rsp + stack_off_diff_scale_shift]); + + Label no_sh_reduction; + barrier(); { + mov(reg_tmp, ptr[rsp + stack_off_N_ithr]); + cmp(reg_tmp, 0); + Label sh_reduction_channels; + jne(no_sh_reduction, T_NEAR); + + mov(reg_nnthr, ptr[rsp + stack_off_N_nthr]); + xor_(reg_coff, reg_coff); + L(sh_reduction_channels); { + mov(reg_roff, reg_coff); + uni_vpxor(Vmm(0), Vmm(0), Vmm(0)); + uni_vpxor(Vmm(1), Vmm(1), Vmm(1)); + uni_vmovups_maybe_tail(vsqrtvar, var_ptr()); + uni_vaddps(vsqrtvar, vsqrtvar, veps); + uni_vsqrtps(vsqrtvar, vsqrtvar); + uni_vdivps(vsqrtvar, vone, vsqrtvar, vbuf); + mov(reg_ctr, reg_nnthr); + Label sh_reduction_thrs; + L(sh_reduction_thrs); { // TODO: unroll (?) + uni_vaddps(Vmm(0), Vmm(0), vmmword[reg_rbuf1 + reg_roff]); + uni_vaddps(Vmm(1), Vmm(1), vmmword[reg_rbuf2 + reg_roff]); + add(reg_roff, reg_coff_max); + sub(reg_ctr, 1); + jnz(sh_reduction_thrs); + } + uni_vmulps(Vmm(0), Vmm(0), vsqrtvar); + uni_vmovups_maybe_tail(diff_gamma_ptr(), Vmm(0)); + uni_vmovups_maybe_tail(diff_beta_ptr(), Vmm(1)); + add(reg_coff, isa == sse42 ? vlen / 2 : vlen); + cmp(reg_coff, reg_coff_max); + jne(sh_reduction_channels); + } + } + L(no_sh_reduction); + barrier(); + + mov(reg_diff_src, ptr[rsp + stack_off_diff_src]); + if (with_relu) { + assert(isa == avx2 || isa == avx512_common); + mov(reg_ws, ptr[rsp + stack_off_ws]); + } + + xor_(reg_soff, reg_soff); + Label diff_spatial; + L(diff_spatial); { + xor_(reg_coff, reg_coff); + if (isa == sse42) { + mov(reg_tmp_off, reg_soff); + } + backward_diff_channels(); + if (isa == sse42) { + mov(reg_soff, reg_tmp_off); + add(reg_diff_dst, vlen / 2); + add(reg_diff_src, vlen / 2); + add(reg_src, vlen / 2); + mov(reg_coff, vlen / 2); + backward_diff_channels(); + sub(reg_diff_dst, vlen / 2); + sub(reg_diff_src, vlen / 2); + sub(reg_src, vlen / 2); + } + add(reg_soff, reg_mb_stride_Bc); + cmp(reg_soff, reg_soff_max); + jne(diff_spatial); + } + } + + jit_bnorm_t(const batch_normalization_pd_t *bdesc): bdesc_(bdesc) { + static_assert(isa == sse42 || isa == avx2 || isa == avx512_common + || isa == avx512_mic, "unsupported isa"); + + const int simd_w = isa == sse42 ? 8 : + cpu_isa_traits::vlen / sizeof(data_t); + is_spatial_thr_ = + bnorm_utils::is_spatial_thr(bdesc_, simd_w, sizeof(data_t)); + + unroll_blocks = isa == avx512_common && !is_spatial_thr_ ? 4 : 1; + unroll_regs = isa == avx512_common && !is_spatial_thr_ ? 4 : 1; + + preamble(); + + if (isa == avx512_common) + prepare_tail_mask_avx512_common(); + else if (isa == avx2) + prepare_tail_mask_avx2_common(); + + compute_static_strides(); + sub(rsp, stack_size_required); + load_common_params(); + prepare_relu(); + + if (bdesc_->is_fwd()) { + if (!bdesc_->stats_is_src()) { + compute_mean_variance(); + } + forward(); + } else { + backward(); + } + add(rsp, stack_size_required); + postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); + } +}; + +template +struct uni_bnorm_driver_t: public c_compatible { + uni_bnorm_driver_t(const batch_normalization_pd_t *bdesc) + : bdesc_(bdesc), ker_(bdesc_) + { + const int nthrs = mkldnn_get_max_threads(); + const dim_t C_PADDED = get_c_padded(bdesc_); + + size_t data_size = sizeof(data_t) * bdesc_->MB() * C_PADDED + * bdesc_->D() * bdesc_->H() * bdesc_->W(); + l3_size_ = get_cache_size(3, true) * nthrs / 2; + do_blocking_ = (data_size >= l3_size_ / 2 && l3_size_ > 0); + } + + ~uni_bnorm_driver_t() {} + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const batch_normalization_pd_t *bdesc) { + int nthrs = mkldnn_get_max_threads(); + dim_t C_PADDED = get_c_padded(bdesc); + + int sbuf_sz = use_tmp_stats(bdesc) * 2 * C_PADDED; + int pbuf_sz = use_tmp_diff_scale_shift(bdesc) * 2 * C_PADDED; + int rbuf_sz = (bdesc->is_fwd() ? 1 : 2) * C_PADDED * nthrs; + + scratchpad.book(key_bnorm_tmp_stats, sizeof(data_t) * sbuf_sz); + scratchpad.book(key_bnorm_tmp_diff_ss, sizeof(data_t) * pbuf_sz); + scratchpad.book(key_bnorm_reduction, sizeof(data_t) * rbuf_sz); + + if (mkldnn_thr_syncable()) { + int n_barriers = C_PADDED / simd_w; + scratchpad.book(key_barrier, sizeof(barrier::ctx_t) * n_barriers); + } + } + + void exec(int ithr, int nthr, const data_t *src, data_t *diff_src, + data_t *dst, const data_t *diff_dst, const data_t *scale_shift, + data_t *diff_scale_shift, const data_t *mean, const data_t *var, + const uint8_t *ws, const memory_tracking::grantor_t &scratchpad) { + auto sbuf = scratchpad.get(key_bnorm_tmp_stats); + auto pbuf = scratchpad.get(key_bnorm_tmp_diff_ss); + auto rbuf = scratchpad.get(key_bnorm_reduction); + auto barriers = scratchpad.get(key_barrier); + + dim_t N = bdesc_->MB(); + dim_t C = bdesc_->C(); + dim_t C_PADDED = get_c_padded(bdesc_); + dim_t D = bdesc_->D(); + dim_t H = bdesc_->H(); + dim_t W = bdesc_->W(); + dim_t SP = D * H * W; + dim_t img_size = C_PADDED * D * H * W; + const int vlen = isa == sse42 ? 32 : cpu_isa_traits::vlen; + + typename jit_bnorm_t::call_params_t p; + + p.eps = bdesc_->desc()->batch_norm_epsilon; + p.one = 1.0f; + p.spat_size = D * H * W; + p.chan_size = 1.0f * N * p.spat_size; + + dim_t C_blks = C_PADDED / simd_w; + + int C_ithr{0}, C_nthr{0}, N_ithr{0}, N_nthr{0}, S_ithr{0}, S_nthr{0}; + dim_t C_blk_s{0}, C_blk_e{0}, N_s{0}, N_e{0}, S_s{0}, S_e{0}; + + dim_t C_blks_per_iter{ 1 }; + int64_t iters{ 1 }; + if (do_blocking_) { + int num_tensors = bdesc_->is_fwd() ? 1 : 2; + size_t working_set_size + = (N * D * H * W * simd_w * sizeof(data_t)) * num_tensors; + bnorm_utils::cache_balance(working_set_size, C_blks, + C_blks_per_iter, iters); + } + + bool spatial_thr_allowed = bnorm_utils::thread_balance(do_blocking_, + true, ithr, nthr, N, do_blocking_ ? C_blks_per_iter : C_blks, + SP, C_ithr, C_nthr, C_blk_s, C_blk_e, N_ithr, N_nthr, N_s, N_e, + S_ithr, S_nthr, S_s, S_e); + + int SP_N_ithr = N_ithr * S_nthr + S_ithr; + int SP_N_nthr = N_nthr * S_nthr; + assert(IMPLICATION(!mkldnn_thr_syncable(), SP_N_nthr == 1)); + + p.N_ithr = SP_N_ithr; + p.N_nthr = SP_N_nthr; + + int last_iter_blks = C_blks - (iters - 1) * C_blks_per_iter; + int global_C_blk_s; + int global_barriers_per_iter = C_nthr; + + for (int64_t it = 0; it < iters; it++) { + if (it == iters - 1 && iters > 1) { + C_blk_s = C_blk_e = N_s = N_e = 0; + spatial_thr_allowed = bnorm_utils::thread_balance(do_blocking_, + spatial_thr_allowed, ithr, nthr, N, last_iter_blks, SP, + C_ithr, C_nthr, C_blk_s, C_blk_e, N_ithr, N_nthr, N_s, + N_e, S_ithr, S_nthr, S_s, S_e); + + // Update call parameters for JIT, last iteration + p.N_ithr = N_ithr * S_nthr + S_ithr; + p.N_nthr = N_nthr * S_nthr; + } + + global_C_blk_s = do_blocking_ ? + (C_blk_s == -1) ? -1 : it * C_blks_per_iter + C_blk_s : + C_blk_s; + + int C_blks_thr = C_blk_e - C_blk_s; + int N_thr = N_e - N_s; + + size_t coff_base = global_C_blk_s * simd_w; + size_t soff_base + = global_C_blk_s * p.spat_size * simd_w + N_s * img_size; + + p.spat_size_loc = S_e - S_s; + p.S_s = S_s * vlen; + p.S_tail = (p.spat_size - S_e) * vlen; + p.coff_max = C_blks_thr * simd_w; + p.mean = (use_tmp_stats(bdesc_) ? sbuf : mean) + coff_base; + p.var = (use_tmp_stats(bdesc_) ? sbuf + C_PADDED : var) + coff_base; + p.scale_shift = scale_shift + coff_base; + p.diff_scale_shift = (use_tmp_diff_scale_shift(bdesc_) + ? pbuf : diff_scale_shift) + coff_base; + + p.soff_max = N_thr * img_size; + p.src = src + soff_base; + p.dst = dst + soff_base; + p.diff_src = diff_src + soff_base; + p.diff_dst = diff_dst + soff_base; + p.ws = ws + soff_base / 8; + + p.mb_stride_Bc = img_size - p.coff_max * p.spat_size; + + // use SP_N_nthr which is the same as p.N_nthr except maybe for + // the last iteration. + p.rbuf1 = rbuf + ((it * C_blks_per_iter) * SP_N_nthr + + C_blk_s * p.N_nthr + p.N_ithr * C_blks_thr) * simd_w; + // rbuf1 and rbuf2 have to be disjoint + p.rbuf2 = p.rbuf1 + C_PADDED * nthr; + p.is_cblk_tail = (it * C_blks_per_iter + C_blk_e) * simd_w > C; + + size_t iter_bariers + = do_blocking_ ? it * global_barriers_per_iter : 0; + p.barrier = barriers + C_ithr + iter_bariers; + if (p.soff_max != 0 && p.coff_max != 0) + ker_(&p); + } + } + + void init_barriers(const memory_tracking::grantor_t &scratchpad) { + auto barriers = scratchpad.get(key_barrier); + if (barriers) { + const int n_barriers = get_c_padded(bdesc_) / simd_w; + for (int i = 0; i < n_barriers; ++i) + barrier::ctx_init(&barriers[i]); + } + } + +private: + enum { + simd_w = isa == sse42 ? 8 : cpu_isa_traits::vlen / sizeof(data_t) + }; + + static bool use_tmp_stats(const batch_normalization_pd_t *bdesc) { + return true + && !bdesc->stats_is_src() + && bdesc->desc()->prop_kind == prop_kind::forward_inference; + } + + static bool use_tmp_diff_scale_shift(const batch_normalization_pd_t *bdesc) + { + return false + || (bdesc->is_bwd() && !bdesc->use_scaleshift()) + || bdesc->desc()->prop_kind == prop_kind::backward_data; + } + + static dim_t get_c_padded(const batch_normalization_pd_t *bdesc) + { return bdesc->src_md()->padded_dims[1]; } + + const batch_normalization_pd_t *bdesc_; + bool do_blocking_; + size_t l3_size_; + + jit_bnorm_t ker_; +}; + +} + +using namespace data_type; +using namespace format_tag; +using namespace utils; + +/* fwd */ + +template +status_t jit_uni_batch_normalization_fwd_t::pd_t::init() { + auto desired_fmt_tag = (ndims() == 4) + ? isa == avx512_common ? nChw16c : nChw8c + : isa == avx512_common ? nCdhw16c : nCdhw8c; + + bool ok = true + && mayiuse(isa) + && is_fwd() + && !has_zero_dim_memory() + && one_of(ndims(), 4, 5) + && src_md()->data_type == f32 + && IMPLICATION(use_scaleshift(), weights_md()->data_type == f32) + && memory_desc_matches_tag(*src_md(), desired_fmt_tag) + && (attr()->has_default_values() || this->with_relu_post_op()); + if (!ok) return status::unimplemented; + + if (is_training() && fuse_bn_relu()) { + if (isa < avx2) return status::unimplemented; + init_default_ws(1); + } + + if (memory_desc_wrapper(src_md()).padded_dims()[1] != C() + && isa < avx2) + return status::unimplemented; + + auto scratchpad = scratchpad_registry().registrar(); + uni_bnorm_driver_t::init_scratchpad(scratchpad, this); + + return status::success; +} + +template +jit_uni_batch_normalization_fwd_t::jit_uni_batch_normalization_fwd_t( + const pd_t *apd): cpu_primitive_t(apd) +{ bnorm_driver_ = new uni_bnorm_driver_t(pd()); } + +template +status_t jit_uni_batch_normalization_fwd_t::execute( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto scale_shift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + + auto mean = pd()->stats_is_src() + ? const_cast(CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN)) + : CTX_OUT_MEM(data_t *, MKLDNN_ARG_MEAN); + auto var = pd()->stats_is_src() + ? const_cast(CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE)) + : CTX_OUT_MEM(data_t *, MKLDNN_ARG_VARIANCE); + + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(uint8_t *, MKLDNN_ARG_WORKSPACE); + + auto scratchpad = this->scratchpad(ctx); + + bnorm_driver_->init_barriers(scratchpad); + + parallel(0, [&](const int ithr, const int nthr) { + bnorm_driver_->exec(ithr, nthr, src, nullptr, dst, nullptr, + scale_shift, nullptr, mean, var, ws, scratchpad); + }); + + return status::success; +} + +template +jit_uni_batch_normalization_fwd_t::~jit_uni_batch_normalization_fwd_t() +{ delete bnorm_driver_; } + +/* bwd */ + +template +status_t jit_uni_batch_normalization_bwd_t::pd_t::init() { + auto desired_fmt_tag = (ndims() == 4) + ? one_of(isa, sse42, avx2) ? nChw8c : nChw16c + : one_of(isa, sse42, avx2) ? nCdhw8c : nCdhw16c; + + bool ok = true + && mayiuse(isa) + && is_bwd() + && !has_zero_dim_memory() + && one_of(ndims(), 4, 5) + && everyone_is(f32, src_md()->data_type, diff_src_md()->data_type) + && IMPLICATION(use_scaleshift(), + utils::everyone_is(f32, + weights_md()->data_type, + diff_weights_md()->data_type)) + && memory_desc_matches_tag(*src_md(), desired_fmt_tag) + && memory_desc_matches_tag(*diff_src_md(), desired_fmt_tag) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + if (memory_desc_wrapper(src_md()).padded_dims()[1] != C() + && isa < avx2) + return status::unimplemented; + + if (fuse_bn_relu()) { + if (isa < avx2) return status::unimplemented; + init_default_ws(1); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + /* TODO: extra checks required */ + + auto scratchpad = scratchpad_registry().registrar(); + uni_bnorm_driver_t::init_scratchpad(scratchpad, this); + + return status::success; +} + +template +jit_uni_batch_normalization_bwd_t::jit_uni_batch_normalization_bwd_t( + const pd_t *apd): cpu_primitive_t(apd) +{ bnorm_driver_ = new uni_bnorm_driver_t(pd()); } + +template +status_t jit_uni_batch_normalization_bwd_t::execute( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto mean = CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN); + auto var = CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto scale_shift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + auto ws = CTX_IN_MEM(const uint8_t *, MKLDNN_ARG_WORKSPACE); + + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + auto diff_scale_shift = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SCALE_SHIFT); + + auto scratchpad = this->scratchpad(ctx); + + bnorm_driver_->init_barriers(scratchpad); + + parallel(0, [&](const int ithr, const int nthr) { + bnorm_driver_->exec(ithr, nthr, src, diff_src, nullptr, diff_dst, + scale_shift, diff_scale_shift, mean, var, ws, scratchpad); + }); + + return status::success; +} + +template +jit_uni_batch_normalization_bwd_t::~jit_uni_batch_normalization_bwd_t() +{ delete bnorm_driver_; } + +/* struct instantiation */ +template struct jit_uni_batch_normalization_fwd_t; +template struct jit_uni_batch_normalization_bwd_t; +template struct jit_uni_batch_normalization_fwd_t; +template struct jit_uni_batch_normalization_bwd_t; +template struct jit_uni_batch_normalization_fwd_t; +template struct jit_uni_batch_normalization_bwd_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.hpp new file mode 100644 index 000000000000..96410ec84ea7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_batch_normalization.hpp @@ -0,0 +1,100 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_UNI_BATCH_NORMALIZATION_HPP +#define JIT_UNI_BATCH_NORMALIZATION_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_batch_normalization_pd.hpp" +#include "cpu_isa_traits.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace { template struct uni_bnorm_driver_t; } + +template +struct jit_uni_batch_normalization_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_batch_normalization_fwd_pd_t { + pd_t(engine_t *engine, const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : cpu_batch_normalization_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_batch_normalization_fwd_t); + + status_t init(); + }; + + typedef typename prec_traits::type data_t; + + jit_uni_batch_normalization_fwd_t(const pd_t *apd); + ~jit_uni_batch_normalization_fwd_t(); + + virtual status_t execute(const exec_ctx_t &ctx) const override; + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + uni_bnorm_driver_t *bnorm_driver_; +}; + +template +struct jit_uni_batch_normalization_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_batch_normalization_bwd_pd_t { + pd_t(engine_t *engine, const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : cpu_batch_normalization_bwd_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_batch_normalization_bwd_t); + + status_t init(); + }; + + typedef typename prec_traits::type data_t; + + jit_uni_batch_normalization_bwd_t(const pd_t *apd); + ~jit_uni_batch_normalization_bwd_t(); + + virtual status_t execute(const exec_ctx_t &ctx) const override; + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + uni_bnorm_driver_t *bnorm_driver_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.cpp new file mode 100644 index 000000000000..b7dc5f85c5ad --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.cpp @@ -0,0 +1,1302 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "cpu_memory.hpp" + +#include "jit_uni_dw_conv_kernel_f32.hpp" + +#define GET_OFF(field) offsetof(jit_conv_call_s, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::prop_kind; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +using namespace Xbyak; + +template +void jit_uni_dw_conv_fwd_kernel_f32::load_src(int ur_ch_blocks, int ur_w) { + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + for (int ow = 0; ow < ur_w; ow++) { + Vmm vmm_acc = get_acc_reg(i*ur_ch_blocks*ur_w + ch*ur_w + ow); + + int b_off = ch*jcp.ch_block + i*4; + if (this->jcp.with_bias) + uni_vmovups(vmm_acc, + vmmword[reg_bias + b_off*sizeof(float)]); + else + uni_vpxor(vmm_acc, vmm_acc, vmm_acc); + + int o_off = ch*jcp.oh*jcp.ow*jcp.ch_block + + ow*jcp.ch_block + i*4; + if (this->jcp.with_sum) + uni_vaddps(vmm_acc, vmm_acc, + vmmword[reg_output + o_off*sizeof(float)]); + } + } + } +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::apply_filter( + int ur_ch_blocks, int ur_w) { + int ch_blk = jcp.ch_block; + int dilate_h = jcp.dilate_h + 1; + int dilate_w = jcp.dilate_w + 1; + int stride_w = jcp.stride_w; + + Label iter_exit_label; + + cmp(reg_kh, 0); + je(iter_exit_label, T_NEAR); + cmp(reg_kw, 0); + je(iter_exit_label, T_NEAR); + + mov(iter_kh, reg_kh); + Label kh_label; + L(kh_label); { + mov(iter_kw, reg_kw); + mov(aux1_reg_input, aux_reg_input); + mov(aux1_reg_kernel, aux_reg_kernel); + + Label kw_label; + L(kw_label); { + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + int ker_off = ch*jcp.kh*jcp.kw*ch_blk + i*4; + Vmm vmm_ker = get_ker_reg(0); + uni_vmovups(vmm_ker, ptr[aux1_reg_kernel + + ker_off*sizeof(float)]); + + for (int ow = 0; ow < ur_w; ow++) { + int inp_off = ch*jcp.ih*jcp.iw*ch_blk + + ow*stride_w*ch_blk + i*4; + Vmm vmm_src = get_src_reg(0); + uni_vmovups(vmm_src, ptr[aux1_reg_input + + inp_off*sizeof(float)]); + + Vmm vmm_acc = get_acc_reg(i*ur_ch_blocks*ur_w + + ch*ur_w + ow); + uni_vfmadd231ps(vmm_acc, vmm_src, vmm_ker); + } + } + } + add(aux1_reg_kernel, ch_blk*sizeof(float)); + add(aux1_reg_input, ch_blk*dilate_w*sizeof(float)); + + dec(iter_kw); + cmp(iter_kw, 0); + jg(kw_label, T_NEAR); + } + add(aux_reg_kernel, jcp.kw*ch_blk*sizeof(float)); + add(aux_reg_input, jcp.iw*ch_blk*dilate_h*sizeof(float)); + + dec(iter_kh); + cmp(iter_kh, 0); + jg(kh_label, T_NEAR); + } + + L(iter_exit_label); +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::apply_filter_unrolled( + int ur_ch_blocks, int ur_w) { + int ch_blk = jcp.ch_block; + int dilate_h = jcp.dilate_h + 1; + int dilate_w = jcp.dilate_w + 1; + int stride_w = jcp.stride_w; + + Label iter_exit_label; + + cmp(reg_kh, 0); + je(iter_exit_label, T_NEAR); + + mov(iter_kh, reg_kh); + Label kh_label; + L(kh_label); { + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + for (int kw = 0; kw < jcp.kw; kw++) { + int ker_off = ch*jcp.kh*jcp.kw*ch_blk + kw*ch_blk + i*4; + + Vmm vmm_ker = get_ker_reg(0); + uni_vmovups(vmm_ker, ptr[aux_reg_kernel + + ker_off*sizeof(float)]); + + for (int ow = 0; ow < ur_w; ow++) { + int inp_off = ch*jcp.ih*jcp.iw*ch_blk + + ow*stride_w*ch_blk + kw*ch_blk*dilate_w + i*4; + + Vmm vmm_src = get_src_reg(0); + uni_vmovups(vmm_src, ptr[aux_reg_input + + inp_off*sizeof(float)]); + + Vmm vmm_acc = get_acc_reg(i*ur_ch_blocks*ur_w + + ch*ur_w + ow); + uni_vfmadd231ps(vmm_acc, vmm_src, vmm_ker); + } + } + } + } + + add(aux_reg_kernel, jcp.kw*ch_blk*sizeof(float)); + add(aux_reg_input, jcp.iw*ch_blk*dilate_h*sizeof(float)); + + dec(iter_kh); + cmp(iter_kh, 0); + jg(kh_label, T_NEAR); + } + + L(iter_exit_label); +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::apply_activation( + int ur_ch_blocks, int ur_w) { + if (this->jcp.with_eltwise) { + int repeats = isa == sse42 ? 2 : 1; + eltwise_injector_->compute_vector_range(4, repeats * ur_w * ur_ch_blocks + 4); + } +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::store_dst( + int ur_ch_blocks, int ur_w) { + int ch_blk = jcp.ch_block; + + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + for (int ow = 0; ow < ur_w; ow++) { + int o_off = ch*jcp.oh*jcp.ow*ch_blk + ow*ch_blk + i*4; + Vmm vmm_dst = get_acc_reg(i*ur_ch_blocks*ur_w + ch*ur_w + ow); + + uni_vmovups(vmmword[reg_output + o_off*sizeof(float)], vmm_dst); + } + } + } +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::loop_body(int ur_ch_blocks) { + Label unrolled_w_label; + Label tail_w_label; + Label exit_label; + + L(unrolled_w_label); { + int ur_w = jcp.ur_w; + + cmp(reg_ur_w, ur_w); + jl(tail_w_label, T_NEAR); + + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + + load_src(ur_ch_blocks, ur_w); + apply_filter_unrolled(ur_ch_blocks, ur_w); + apply_activation(ur_ch_blocks, ur_w); + store_dst(ur_ch_blocks, ur_w); + + add(reg_input, sizeof(float) * ur_w * jcp.ch_block * jcp.stride_w); + add(reg_output, sizeof(float) * ur_w * jcp.ch_block); + + sub(reg_ur_w, ur_w); + jmp(unrolled_w_label); + } + + L(tail_w_label); { + int ur_w = 1; + + cmp(reg_ur_w, ur_w); + jl(exit_label, T_NEAR); + + mov(aux_reg_input, reg_input); + mov(aux_reg_kernel, reg_kernel); + + load_src(ur_ch_blocks, ur_w); + apply_filter(ur_ch_blocks, ur_w); + apply_activation(ur_ch_blocks, ur_w); + store_dst(ur_ch_blocks, ur_w); + + add(reg_input, sizeof(float) * ur_w * jcp.ch_block * jcp.stride_w); + add(reg_output, sizeof(float) * ur_w * jcp.ch_block); + + sub(reg_ur_w, ur_w); + jmp(tail_w_label); + } + + L(exit_label); +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::generate() { + this->preamble(); + + mov(reg_input, ptr[this->param1 + GET_OFF(src)]); + mov(reg_output, ptr[this->param1 + GET_OFF(dst)]); + mov(reg_kernel, ptr[this->param1 + GET_OFF(filt)]); + if (jcp.with_bias) + mov(reg_bias, ptr[this->param1 + GET_OFF(bias)]); + mov(reg_kh, ptr[this->param1 + GET_OFF(kh_padding)]); + mov(reg_kw, ptr[this->param1 + GET_OFF(kw_padding)]); + mov(reg_ch_blocks, ptr[this->param1 + GET_OFF(ch_blocks)]); + mov(reg_ur_w, ptr[this->param1 + GET_OFF(ur_w)]); + + Label ch_blocks_tail_label; + Label exit_label; + + int ch_blocks_tail = jcp.nb_ch % jcp.nb_ch_blocking; + + cmp(reg_ch_blocks, jcp.nb_ch_blocking); + jne(ch_blocks_tail ? ch_blocks_tail_label : exit_label, T_NEAR); + + loop_body(jcp.nb_ch_blocking); // channel main loop + + if (ch_blocks_tail) { + L(ch_blocks_tail_label); + + cmp(reg_ch_blocks, ch_blocks_tail); + jne(exit_label, T_NEAR); + + loop_body(ch_blocks_tail); // channel tail loop + } + + L(exit_label); + + this->postamble(); + + if (jcp.with_eltwise) + eltwise_injector_->prepare_table(); +} + +template +bool jit_uni_dw_conv_fwd_kernel_f32::post_ops_ok( + jit_conv_conf_t &jcp, const primitive_attr_t &attr) { + const auto &p = attr.post_ops_; + + auto is_eltwise = [&](int idx) { return p.entry_[idx].is_eltwise(); }; + auto is_sum = [&](int idx) { return p.entry_[idx].is_sum(); }; + + switch (p.len_) { + case 0: return true; // no post_ops + case 1: return is_eltwise(0) || is_sum(0); // sum OR eltwise + case 2: return is_sum(0) && is_eltwise(1); // sum -> eltwise + default: return false; + } + + return false; +} + +template +status_t jit_uni_dw_conv_fwd_kernel_f32::init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, const memory_desc_wrapper &dst_d, + const primitive_attr_t &attr) +{ + if (!mayiuse(isa)) return status::unimplemented; + + const int simd_w = isa == avx512_common ? 16 : 8; + + jcp.prop_kind = cd.prop_kind; + + const bool with_groups = weights_d.ndims() == src_d.ndims() + 1; + if (!with_groups) return status::unimplemented; + + jcp.ngroups = weights_d.dims()[0]; + jcp.mb = src_d.dims()[0]; + + jcp.oc = dst_d.dims()[1]; + jcp.oc_without_padding = jcp.oc; + jcp.ic = src_d.dims()[1]; + + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = dst_d.dims()[2]; + jcp.ow = dst_d.dims()[3]; + + jcp.kh = weights_d.dims()[3]; + jcp.kw = weights_d.dims()[4]; + + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.b_pad = cd.padding[1][0]; + jcp.r_pad = cd.padding[1][1]; + + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + jcp.with_bias = cd.bias_desc.format_kind != format_kind::undef; + + if (!post_ops_ok(jcp, attr)) + return status::unimplemented; + + const auto &p = attr.post_ops_; + jcp.with_sum = p.find(primitive_kind::sum) != -1; + const int eltwise_ind = p.find(primitive_kind::eltwise); + jcp.with_eltwise = eltwise_ind != -1; + if (jcp.with_eltwise) + jcp.eltwise = p.entry_[eltwise_ind].eltwise; + + bool ok_to_pad_channels = true + && jcp.oc == jcp.ngroups + && jcp.ic == jcp.ngroups + && one_of(isa, avx512_common, avx2); + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.oc, simd_w); + jcp.ngroups = rnd_up(jcp.ngroups, simd_w); + } + + auto dat_tag = isa == avx512_common ? nChw16c : nChw8c; + auto wei_tag = isa == avx512_common ? Goihw16g : Goihw8g; + + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.oc == jcp.ngroups + && jcp.ic == jcp.ngroups + && jcp.ngroups % simd_w == 0 + && jcp.src_tag == dat_tag + && jcp.wei_tag == wei_tag + && jcp.dst_tag == dat_tag + && jcp.ic <= src_d.padded_dims()[1] + && jcp.oc <= dst_d.padded_dims()[1] + && jcp.ngroups <= weights_d.padded_dims()[0]; + if (!args_ok) return status::unimplemented; + + jcp.ur_w = isa == avx512_common ? 6 : isa == avx2 ? 4 : 3; + + jcp.ch_block = simd_w; + jcp.nb_ch = jcp.oc / jcp.ch_block; + jcp.nb_ch_blocking = isa == avx512_common ? 4 : isa == avx2 ? 3 : 2; + if (jcp.nb_ch < jcp.nb_ch_blocking) + jcp.nb_ch_blocking = jcp.nb_ch; + + return status::success; +} + +template +void jit_uni_dw_conv_fwd_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + if (jcp.with_bias && jcp.oc_without_padding != jcp.oc) + scratchpad.book(key_conv_padded_bias, sizeof(float) * jcp.oc); +} + +template struct jit_uni_dw_conv_fwd_kernel_f32; +template struct jit_uni_dw_conv_fwd_kernel_f32; +template struct jit_uni_dw_conv_fwd_kernel_f32; + +template +inline void jit_uni_dw_conv_bwd_data_kernel_f32::load_ddst( + int ur_ch_blocks, int ur_str_w) { + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + for (int w = 0; w < ur_str_w; w++) { + Vmm vmm_acc = get_acc_reg(i*ur_ch_blocks*ur_str_w + + ch*ur_str_w + w); + uni_vpxor(vmm_acc, vmm_acc, vmm_acc); + } + } + } +} + +template +inline void jit_uni_dw_conv_bwd_data_kernel_f32::apply_filter( + int ur_ch_blocks, int ur_str_w) { + int kw = jcp.kw; + int kh = jcp.kh; + int ow = jcp.ow; + int oh = jcp.oh; + + int ch_blk = jcp.ch_block; + int stride_h = jcp.stride_h; + int stride_w = jcp.stride_w; + + Label iter_exit_label; + + cmp(reg_kh, 0); + je(iter_exit_label, T_NEAR); + + cmp(reg_kw, 0); + je(iter_exit_label, T_NEAR); + + mov(iter_kh, reg_kh); + Label kh_label; + L(kh_label); { + mov(aux1_reg_ddst, aux_reg_ddst); + mov(aux1_reg_kernel, aux_reg_kernel); + + mov(iter_kw, reg_kw); + Label kw_label; + L(kw_label); { + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + int ker_off = ch*kh*kw*ch_blk + i*4; + Vmm vmm_ker = get_ker_reg(0); + uni_vmovups(vmm_ker, ptr[aux1_reg_kernel + + ker_off*sizeof(float)]); + + for (int w = 0; w < ur_str_w; w++) { + int ddst_off = (ch*oh*ow + w)*ch_blk + i*4; + + Vmm vmm_src = get_src_reg(0); + uni_vmovups(vmm_src, ptr[aux1_reg_ddst + + ddst_off*sizeof(float)]); + + Vmm vmm_acc = get_acc_reg(i*ur_ch_blocks*ur_str_w + + ch*ur_str_w + w); + uni_vfmadd231ps(vmm_acc, vmm_src, vmm_ker); + } + } + } + + add(aux1_reg_kernel, ch_blk*stride_w*sizeof(float)); + sub(aux1_reg_ddst, ch_blk*sizeof(float)); + + sub(iter_kw, stride_w); + cmp(iter_kw, 0); + jg(kw_label, T_NEAR); + } + + add(aux_reg_kernel, kw*ch_blk*stride_h*sizeof(float)); + sub(aux_reg_ddst, ow*ch_blk*sizeof(float)); + + sub(iter_kh, stride_h); + cmp(iter_kh, 0); + jg(kh_label, T_NEAR); + } + + L(iter_exit_label); +} + +template +inline void jit_uni_dw_conv_bwd_data_kernel_f32::store_dsrc( + int ur_ch_blocks, int ur_str_w) { + int ch_blk = jcp.ch_block; + int iw = jcp.iw; + int ih = jcp.ih; + int stride_w = jcp.stride_w; + + int repeats = isa == sse42 ? 2 : 1; + for (int i = 0; i < repeats; i++) { + for (int ch = 0; ch < ur_ch_blocks; ch++) { + for (int w = 0; w < ur_str_w; w++) { + int dsrc_off = (ch*ih*iw + w*stride_w)*ch_blk + i*4; + Vmm vmm_acc = get_acc_reg(i*ur_ch_blocks*ur_str_w + + ch*ur_str_w + w); + + uni_vmovups(ptr[reg_dsrc + dsrc_off*sizeof(float)], vmm_acc); + } + } + } +} + +template +inline void jit_uni_dw_conv_bwd_data_kernel_f32::loop_body( + int ur_ch_blocks) { + Label unrolled_w_label; + Label tail_w_label; + Label exit_label; + + L(unrolled_w_label); { + int ur_w = jcp.ur_w; + + cmp(reg_ur_str_w, ur_w); + jl(tail_w_label, T_NEAR); + + mov(aux_reg_ddst, reg_ddst); + mov(aux_reg_kernel, reg_kernel); + + load_ddst(ur_ch_blocks, ur_w); + apply_filter(ur_ch_blocks, ur_w); + store_dsrc(ur_ch_blocks, ur_w); + + add(reg_dsrc, sizeof(float) * ur_w * jcp.ch_block * jcp.stride_w); + add(reg_ddst, sizeof(float) * ur_w * jcp.ch_block); + + sub(reg_ur_str_w, ur_w); + jmp(unrolled_w_label); + } + + L(tail_w_label); { + int ur_w = 1; + + cmp(reg_ur_str_w, ur_w); + jl(exit_label, T_NEAR); + + mov(aux_reg_ddst, reg_ddst); + mov(aux_reg_kernel, reg_kernel); + + load_ddst(ur_ch_blocks, ur_w); + apply_filter(ur_ch_blocks, ur_w); + store_dsrc(ur_ch_blocks, ur_w); + + add(reg_dsrc, sizeof(float) * ur_w * jcp.ch_block * jcp.stride_w); + add(reg_ddst, sizeof(float) * ur_w * jcp.ch_block); + + sub(reg_ur_str_w, ur_w); + jmp(tail_w_label); + } + + L(exit_label); +} + +template +void jit_uni_dw_conv_bwd_data_kernel_f32::generate() { + preamble(); + + mov(reg_dsrc, ptr[this->param1 + GET_OFF(src)]); + mov(reg_ddst, ptr[this->param1 + GET_OFF(dst)]); + mov(reg_kernel, ptr[this->param1 + GET_OFF(filt)]); + mov(reg_kh, ptr[this->param1 + GET_OFF(kh_padding)]); + mov(reg_kw, ptr[this->param1 + GET_OFF(kw_padding)]); + mov(reg_ch_blocks, ptr[this->param1 + GET_OFF(ch_blocks)]); + mov(reg_ur_str_w, ptr[this->param1 + GET_OFF(ur_str_w)]); + + Label ch_blocks_tail_label; + Label exit_label; + + int ch_blocks_tail = jcp.nb_ch % jcp.nb_ch_blocking; + + cmp(reg_ch_blocks, jcp.nb_ch_blocking); + jne(ch_blocks_tail ? ch_blocks_tail_label : exit_label, T_NEAR); + + loop_body(jcp.nb_ch_blocking); // channel main loop + + if (ch_blocks_tail) { + L(ch_blocks_tail_label); + + cmp(reg_ch_blocks, ch_blocks_tail); + jne(exit_label, T_NEAR); + + loop_body(ch_blocks_tail); // channel tail loop + } + + L(exit_label); + + this->postamble(); +} + +template +status_t jit_uni_dw_conv_bwd_data_kernel_f32::init_conf( + jit_conv_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d) { + if (!mayiuse(isa)) return status::unimplemented; + + const int simd_w = isa == avx512_common ? 16 : 8; + + const bool with_groups = weights_d.ndims() == diff_src_d.ndims() + 1; + if (!with_groups) return status::unimplemented; + + jcp.ngroups = weights_d.dims()[0]; + jcp.mb = diff_src_d.dims()[0]; + + jcp.oc = diff_dst_d.dims()[1]; + jcp.oc_without_padding = jcp.oc; + jcp.ic = diff_src_d.dims()[1]; + + jcp.ih = diff_src_d.dims()[2]; + jcp.iw = diff_src_d.dims()[3]; + jcp.oh = diff_dst_d.dims()[2]; + jcp.ow = diff_dst_d.dims()[3]; + + jcp.kh = weights_d.dims()[3]; + jcp.kw = weights_d.dims()[4]; + + jcp.t_pad = cd.padding[0][0]; + jcp.l_pad = cd.padding[0][1]; + jcp.b_pad = cd.padding[1][0]; + jcp.r_pad = cd.padding[1][1]; + + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + + bool ok_to_pad_channels = true + && jcp.oc == jcp.ngroups + && jcp.ic == jcp.ngroups + && one_of(isa, avx512_common, avx2); + if (ok_to_pad_channels) { + jcp.oc = rnd_up(jcp.oc, simd_w); + jcp.ic = rnd_up(jcp.oc, simd_w); + jcp.ngroups = rnd_up(jcp.ngroups, simd_w); + } + + auto dat_tag = isa == avx512_common ? nChw16c : nChw8c; + auto wei_tag = isa == avx512_common ? Goihw16g : Goihw8g; + + jcp.src_tag = diff_src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.oc == jcp.ngroups + && jcp.ic == jcp.ngroups + && jcp.ngroups % simd_w == 0 + && jcp.dilate_h == 0 + && jcp.dilate_w == 0 + && jcp.src_tag == dat_tag + && jcp.wei_tag == wei_tag + && jcp.dst_tag == dat_tag + && jcp.oh == (jcp.ihp - jcp.kh) / jcp.stride_h + 1 + && jcp.ow == (jcp.iwp - jcp.kw) / jcp.stride_w + 1 + && jcp.ic <= diff_src_d.padded_dims()[1] + && jcp.oc <= diff_dst_d.padded_dims()[1] + && jcp.ngroups <= weights_d.padded_dims()[0]; + if (!args_ok) return status::unimplemented; + + jcp.ur_w = isa == avx512_common ? 6 : isa == avx2 ? 4 : 3; + + jcp.ch_block = simd_w; + jcp.nb_ch = jcp.ic / jcp.ch_block; + jcp.nb_ch_blocking = isa == avx512_common ? 4 : isa == avx2 ? 3 : 2; + if (jcp.nb_ch < jcp.nb_ch_blocking) + jcp.nb_ch_blocking = jcp.nb_ch; + + return status::success; +} + +template +void jit_uni_dw_conv_bwd_data_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + UNUSED(scratchpad); + UNUSED(jcp); +} + +template struct jit_uni_dw_conv_bwd_data_kernel_f32; +template struct jit_uni_dw_conv_bwd_data_kernel_f32; +template struct jit_uni_dw_conv_bwd_data_kernel_f32; + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::zero_filter() { + for (int r = 0; r < reg_repeats; ++r) { + for (int i = 0; i < jcp.kw; ++i) { + Vmm vmm_acc = get_acc_reg(r * jcp.kw + i); + uni_vpxor(vmm_acc, vmm_acc, vmm_acc); + } + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::load_filter() { + for (int r = 0; r < reg_repeats; ++r) { + const int reg_set = r * jcp.kw; + for (int i = 0; i < jcp.kw; ++i) { + int off_filter = (reg_set + i) * simd_w; + Vmm vmm_acc = get_acc_reg(reg_set + i); + uni_vmovups(vmm_acc, + vmmword[reg_tmp_filter + off_filter * sizeof(float)]); + } + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::zero_bias() { + for (int r = 0; r < reg_repeats; ++r) { + Vmm vmm_bias = get_bias_reg(r); + uni_vpxor(vmm_bias, vmm_bias, vmm_bias); + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::load_bias() { + for (int r = 0; r < reg_repeats; ++r) { + Vmm vmm_bias = get_bias_reg(r); + uni_vmovups( + vmm_bias, vmmword[reg_bias_baddr + r * simd_w * sizeof(float)]); + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::compute_ow_step_unroll( + int unroll_w, int l_pad, int pad_offset, int ow_block) { + + const int iw_block = ow_block * jcp.stride_w; + const int right_border = jcp.iw - iw_block; + + const int cascade_input = nstl::min(jcp.stride_w, jcp.kw); + + /* preamble count for number of cascaded LOAD + FMA operation */ + const int input_overlap = nstl::max(jcp.kw - l_pad, 0); + + /* LOAD initial input registers, then cascade LOADs and FMAs*/ + for (int r = 0; r < reg_repeats; ++r) { + for (int i_ur = 0; i_ur < unroll_w; ++i_ur) { + int off_output = (i_ur * reg_repeats + r) * simd_w; + Vmm vmm_output = get_output_reg(r); + uni_vmovups(vmm_output, + ptr[reg_tmp_output + off_output * sizeof(float)]); + if (i_ur == 0) { + for (int c = 0; c < input_overlap; ++c) { + int off_input + = ((c - pad_offset) * reg_repeats + r) * simd_w; + Vmm vmm_input + = get_input_reg((c % jcp.kw) * reg_repeats + r); + uni_vmovups(vmm_input, + ptr[reg_tmp_input + off_input * sizeof(float)]); + } + } else { + for (int c = 0; c < cascade_input; ++c) { + int overlap = (i_ur - 1) * jcp.stride_w + input_overlap; + int off_input + = ((overlap + c - pad_offset) * reg_repeats + r) + * simd_w; + Vmm vmm_input = get_input_reg( + ((overlap + c) % jcp.kw) * reg_repeats + r); + uni_vmovups(vmm_input, + ptr[reg_tmp_input + off_input * sizeof(float)]); + } + } + + for (int i_kw = 0; i_kw < jcp.kw; ++i_kw) { + int io_overlap = i_kw + (i_ur * jcp.stride_w); + + /* Don't apply FMAs that fall into the padded region */ + if (io_overlap - l_pad < 0 + || io_overlap - jcp.l_pad >= right_border) + continue; + + Vmm vmm_input = get_input_reg( + ((io_overlap - l_pad) % jcp.kw) * reg_repeats + r); + Vmm vmm_acc = get_acc_reg(i_kw * reg_repeats + r); + Vmm vmm_aux = isa == sse42 ? get_aux_reg() : vmm_input; + if (isa == sse42) + uni_vmovups(vmm_aux, vmm_input); + uni_vfmadd231ps(vmm_acc, vmm_aux, vmm_output); + } + } + } +} + +template +inline void +jit_uni_dw_conv_bwd_weights_kernel_f32::compute_bias_step_unroll( + const int unroll_w) { + for (int r = 0; r < reg_repeats; ++r) { + for (int i = 0; i < unroll_w; ++i) { + Vmm vmm_bias = get_bias_reg(r); + int off_output = (i * reg_repeats + r) * simd_w; + if (isa == sse42) { + /* Need to support unaligned address loads for SSE42*/ + Vmm vmm_output = get_output_reg(1 + r); + uni_vmovups(vmm_output, + ptr[reg_tmp_output + off_output * sizeof(float)]); + uni_vaddps(vmm_bias, vmm_bias, vmm_output); + } else { + uni_vaddps(vmm_bias, vmm_bias, + vmmword[reg_tmp_output + off_output * sizeof(float)]); + } + } + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::store_filter() { + for (int r = 0; r < reg_repeats; ++r) { + const int reg_set = r * jcp.kw; + for (int i = 0; i < jcp.kw; ++i) { + int off_filter = (i + reg_set) * simd_w; + Vmm vmm_acc = get_acc_reg(i + reg_set); + uni_vmovups(vmmword[reg_tmp_filter + off_filter * sizeof(float)], + vmm_acc); + } + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::store_bias() { + for (int r = 0; r < reg_repeats; ++r) { + Vmm vmm_bias = get_bias_reg(r); + uni_vmovups( + vmmword[reg_bias_baddr + r * simd_w * sizeof(float)], vmm_bias); + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::compute_bias_loop( + const int block_size) { + Label oh_label; + Label ow_blk_label; + + const int unroll_w = nstl::min(block_size, jcp.ow); + const int unroll_w_trips = jcp.ow / unroll_w; + const int tail_w = jcp.ow > block_size ? jcp.ow % block_size : 0; + + const int ch_offset = jcp.ch_block; + + mov(reg_oh, ptr[this->param1 + offsetof(jit_dw_conv_call_s, oh_index)]); + mov(reg_oh_worksize, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, oh_count)]); + + mov(reg_tmp_output, reg_output_baddr); + L(oh_label); + { + + mov(iter_ow_blk, unroll_w_trips); + L(ow_blk_label); + { + + compute_bias_step_unroll(unroll_w); + add(reg_tmp_output, unroll_w * ch_offset * sizeof(float)); + + dec(iter_ow_blk); + cmp(iter_ow_blk, 0); + jg(ow_blk_label, T_NEAR); + } + + if (tail_w > 0) { + compute_bias_step_unroll(tail_w); + add(reg_tmp_output, tail_w * ch_offset * sizeof(float)); + } + + inc(reg_oh); + cmp(reg_oh, reg_oh_worksize); + jl(oh_label, T_NEAR); + } +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::compute_zero_filter() { + + const int ch_offset = jcp.ch_block; + + Label kh_loop_label, skip_zeroing_label; + + mov(reg_exec_flags, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, exec_flags)]); + and_(reg_exec_flags, FLAG_ZERO_FILTER); + test(reg_exec_flags, reg_exec_flags); + je(skip_zeroing_label); + + zero_filter(); + + mov(reg_tmp_filter, reg_filter_baddr); + mov(reg_kh, jcp.kh); + L(kh_loop_label); + { + store_filter(); + + add(reg_tmp_filter, jcp.kw * ch_offset * sizeof(float)); + dec(reg_kh); + cmp(reg_kh, 0); + jg(kh_loop_label); + } + + /* Comeback pointers */ + sub(reg_tmp_filter, jcp.kh * jcp.kw * ch_offset * sizeof(float)); + + L(skip_zeroing_label); +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::compute_h_step( + int unroll_w, int l_pad, int pad_offset, int ow_block) { + + const int ch_offset = jcp.ch_block; + + Label kh_loop_label, skip_loop_label; + + cmp(reg_kh_count, 0); + je(skip_loop_label, T_NEAR); + + mov(reg_kh, reg_kh_count); + L(kh_loop_label); + { + load_filter(); + compute_ow_step_unroll(unroll_w, l_pad, pad_offset, ow_block); + store_filter(); + + add(reg_tmp_filter, jcp.kw * ch_offset * sizeof(float)); + add(reg_tmp_input, jcp.iw * ch_offset * sizeof(float)); + dec(reg_kh); + cmp(reg_kh, 0); + jg(kh_loop_label); + } + + /* Comeback pointers */ + Label kh_comeback_label; + mov(reg_kh, reg_kh_count); + L(kh_comeback_label); + { + sub(reg_tmp_input, jcp.iw * ch_offset * sizeof(float)); + sub(reg_tmp_filter, jcp.kw * ch_offset * sizeof(float)); + dec(reg_kh); + cmp(reg_kh, 0); + jg(kh_comeback_label, T_NEAR); + } + + L(skip_loop_label); +} + +template +inline void jit_uni_dw_conv_bwd_weights_kernel_f32::compute_h_loop( + int unroll_w, int l_pad, int pad_offset, int ow_block) { + + const size_t io_overlap = jcp.ih / jcp.stride_h < jcp.oh ? + jcp.ih / jcp.stride_h - 1 : + jcp.oh - jcp.b_pad - 1; + const int ch_offset = jcp.ch_block; + const int t_overlap_off = jcp.t_pad % jcp.stride_h == 0 ? jcp.stride_h : 1; + const int b_overlap_off = jcp.b_pad % jcp.stride_h == 0 ? jcp.stride_h : 1; + + Label tpad_loop_label, h_loop_label, skip_tpad_label, skip_bpad_label, + end_h_loop_label; + + mov(reg_oh, ptr[this->param1 + offsetof(jit_dw_conv_call_s, oh_index)]); + mov(reg_oh_worksize, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, oh_count)]); + mov(reg_kh_count, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, kh_count)]); + + mov(reg_tmp_output, reg_output_baddr); + mov(reg_tmp_input, reg_input_baddr); + mov(reg_tmp_filter, reg_filter_baddr); + + L(h_loop_label); + { + + compute_h_step(unroll_w, l_pad, pad_offset, ow_block); + + add(reg_tmp_output, jcp.ow * ch_offset * sizeof(float)); + + /* If within the top_pad region */ + if (jcp.t_pad > 0) { + /* Skip t_pad area if no longer in initial h_block */ + cmp(reg_oh, jcp.t_pad); + jg(skip_tpad_label, T_NEAR); + + cmp(reg_kh_count, jcp.kh); + jge(skip_tpad_label, T_NEAR); + + add(reg_kh_count, t_overlap_off); + sub(reg_tmp_filter, + t_overlap_off * jcp.kw * ch_offset * sizeof(float)); + + /* kernel has moved beyond padding (adjust for stride effects) */ + if (jcp.t_pad % jcp.stride_h != 0) { + int inp_corr = jcp.stride_h - jcp.t_pad % jcp.stride_h; + add(reg_tmp_input, + inp_corr * jcp.iw * ch_offset * sizeof(float)); + } + jmp(tpad_loop_label, T_NEAR); + } + + L(skip_tpad_label); + + cmp(reg_oh, io_overlap); + jl(skip_bpad_label, T_NEAR); + sub(reg_kh_count, b_overlap_off); + + L(skip_bpad_label); + add(reg_tmp_input, jcp.stride_h * jcp.iw * ch_offset * sizeof(float)); + + L(tpad_loop_label); + + cmp(reg_oh, jcp.ih / jcp.stride_h); + jge(end_h_loop_label, T_NEAR); + + inc(reg_oh); + + cmp(reg_oh, reg_oh_worksize); + jl(h_loop_label, T_NEAR); + } + L(end_h_loop_label); +} + +template +inline void +jit_uni_dw_conv_bwd_weights_kernel_f32::compute_ow_block_unroll() { + + const int ch_offset = jcp.ch_block; + int ow = jcp.ow; + int pad_offset = 0; + int l_pad = jcp.l_pad; + + /* Calculate effective padding */ + int r_pad = nstl::max(0, (ow - 1) * jcp.stride_w + + (jcp.kw - 1) * (jcp.dilate_w + 1) + - (jcp.iw + jcp.l_pad - 1)); + + /* Is this strictly defined by: + * -code-size (?) + * -address size (?) */ + const int max_unroll_w = 30; + const int block_size = 15; + + int unroll_w_tail = 0; + int unroll_w = 0; + int unroll_w_trips = 0; + + if (jcp.ow > max_unroll_w) { + unroll_w = nstl::min(block_size, jcp.ow); + unroll_w_trips = ow / unroll_w; + /* calculate tail */ + unroll_w_tail = ow % unroll_w; + /* Perform some rebalancing if tail too small*/ + if ((unroll_w_tail == 0 && r_pad != 0) + || (r_pad > 0 && r_pad >= unroll_w_tail)) { + if (unroll_w_trips > 1) { + unroll_w_tail += unroll_w; + unroll_w_trips--; + } else { + /* Idealy, this case shouldn't happen */ + unroll_w_tail += (unroll_w - unroll_w / 2); + unroll_w = unroll_w / 2; + } + } + } else { + unroll_w = jcp.ow; + unroll_w_trips = nstl::max(1, ow / unroll_w); + } + if (jcp.with_bias) { + Label skip_load_bias; + mov(reg_bias_baddr, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, bias)]); + + zero_bias(); + + mov(reg_exec_flags, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, exec_flags)]); + and_(reg_exec_flags, FLAG_ZERO_BIAS); + test(reg_exec_flags, reg_exec_flags); + jne(skip_load_bias); + + load_bias(); + + L(skip_load_bias); + compute_bias_loop(block_size); + + store_bias(); + } + + /* Pass filter address, then offset for h_padding. */ + compute_zero_filter(); + mov(reg_kh_offset, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, filter_pad_off)]); + add(reg_filter_baddr, reg_kh_offset); + + /* compute left padded block */ + if (l_pad) { + compute_h_loop(unroll_w, l_pad, 0, 0); + add(reg_output_baddr, unroll_w * ch_offset * sizeof(float)); + add(reg_input_baddr, + unroll_w * jcp.stride_w * ch_offset * sizeof(float)); + unroll_w_trips--; + pad_offset = l_pad; + l_pad = 0; + } + + /* compute middle block */ + Label ow_blk_label; + + /* Insert loop for 'ow' block when middle block needs to execute more + * than once */ + bool do_ow_blk_loop = unroll_w_trips > 1; + if (do_ow_blk_loop) { + mov(iter_ow_blk, unroll_w_trips); + L(ow_blk_label); + } + if (unroll_w_trips > 0) { + compute_h_loop(unroll_w, l_pad, pad_offset, 0); + add(reg_output_baddr, unroll_w * ch_offset * sizeof(float)); + add(reg_input_baddr, + unroll_w * jcp.stride_w * ch_offset * sizeof(float)); + } + if (do_ow_blk_loop) { + dec(iter_ow_blk); + cmp(iter_ow_blk, 0); + jg(ow_blk_label, T_NEAR); + } + + /* compute right padded block */ + if (unroll_w_tail) { + compute_h_loop(unroll_w_tail, 0, pad_offset, jcp.ow - unroll_w_tail); + } +} + +template +void jit_uni_dw_conv_bwd_weights_kernel_f32::generate() { + preamble(); + + mov(reg_input_baddr, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, input)]); + mov(reg_output_baddr, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, output)]); + mov(reg_filter_baddr, + ptr[this->param1 + offsetof(jit_dw_conv_call_s, filter)]); + + compute_ow_block_unroll(); + + this->postamble(); +} + +template +status_t jit_uni_dw_conv_bwd_weights_kernel_f32::init_conf( + jit_conv_conf_t &jcp, const convolution_desc_t &cd, + const memory_desc_wrapper &src_d, + const memory_desc_wrapper &diff_weights_d, + const memory_desc_wrapper &diff_dst_d, int nthreads) { + if (!mayiuse(isa)) + return status::unimplemented; + + jcp.ngroups = diff_weights_d.dims()[0]; + jcp.oc = diff_dst_d.dims()[1] / jcp.ngroups; + jcp.ic = src_d.dims()[1] / jcp.ngroups; + + const bool with_groups = diff_weights_d.ndims() == src_d.ndims() + 1; + + jcp.is_depthwise = true && with_groups && everyone_is(1, jcp.oc, jcp.ic); + + if (!jcp.is_depthwise) + return status::unimplemented; + + jcp.ch_block = isa == avx512_common ? 16 : 8; + + jcp.mb = src_d.dims()[0]; + + jcp.ih = src_d.dims()[2]; + jcp.iw = src_d.dims()[3]; + jcp.oh = diff_dst_d.dims()[2]; + jcp.ow = diff_dst_d.dims()[3]; + + jcp.kh = diff_weights_d.dims()[3]; + jcp.kw = diff_weights_d.dims()[4]; + + jcp.stride_h = cd.strides[0]; + jcp.stride_w = cd.strides[1]; + + jcp.t_pad = cd.padding[0][0]; + jcp.b_pad = cd.padding[1][0]; + + jcp.l_pad = cd.padding[0][1]; + jcp.r_pad = cd.padding[1][1]; + + jcp.dilate_h = cd.dilates[0]; + jcp.dilate_w = cd.dilates[1]; + + jcp.ihp = jcp.ih + jcp.t_pad + jcp.b_pad; + jcp.iwp = jcp.iw + jcp.l_pad + jcp.r_pad; + + jcp.with_bias = cd.diff_bias_desc.format_kind != format_kind::undef; + + auto dat_tag = isa == avx512_common ? nChw16c : nChw8c; + auto wei_tag = isa == avx512_common ? Goihw16g : Goihw8g; + + jcp.src_tag = src_d.matches_one_of_tag(dat_tag); + jcp.wei_tag = diff_weights_d.matches_one_of_tag(wei_tag); + jcp.dst_tag = diff_dst_d.matches_one_of_tag(dat_tag); + + bool args_ok = true + && jcp.src_tag == dat_tag + && jcp.wei_tag == wei_tag + && jcp.dst_tag == dat_tag + && jcp.ngroups % jcp.ch_block == 0 && jcp.dilate_h == 0 + && jcp.dilate_w == 0 && jcp.kw <= 3 + && jcp.oh == (jcp.ihp - jcp.kh) / jcp.stride_h + 1 + && jcp.ow == (jcp.iwp - jcp.kw) / jcp.stride_w + 1; + if (!args_ok) + return status::unimplemented; + + jcp.nb_ch = jcp.ngroups / jcp.ch_block; + + /* kernel applicability check wrt boundaries + * the conditions are quite general across the kernels we have, + * but ideally the check should belong to a specific kernel... */ + const int max_hpad = (jcp.kh - 1 + 1) / 2; + const int max_wpad = (jcp.kw - 1 + 1) / 2; + const bool boundaries_ok = true && jcp.t_pad <= max_hpad + && jcp.b_pad <= max_hpad && jcp.l_pad <= max_wpad + && jcp.r_pad <= max_wpad; + if (!boundaries_ok) + return status::unimplemented; + + balance(jcp, nthreads); + + return status::success; +} + +template +void jit_uni_dw_conv_bwd_weights_kernel_f32::init_scratchpad( + memory_tracking::registrar_t &scratchpad, const jit_conv_conf_t &jcp) { + /* Notes: if splitting thread work on 'mb', then a reduction has to take + * place. Hence, book a per-thread, local weights-buffer for the + * reduction */ + if (jcp.nthr_mb > 1) { + const size_t wei_size = jcp.ngroups * jcp.kh * jcp.kw; + scratchpad.book(key_conv_wei_reduction, + sizeof(float) * wei_size * (jcp.nthr_mb - 1)); + + if (jcp.with_bias) + scratchpad.book(key_conv_bia_reduction, + sizeof(float) * jcp.ngroups * (jcp.nthr_mb - 1)); + } +} + +template +void jit_uni_dw_conv_bwd_weights_kernel_f32::balance(jit_conv_conf_t &jcp, + int nthreads) { + jcp.nthr = nthreads; + jcp.nthr_g = jcp.nthr_mb = 1; + + /* Basic-Heuristics for parallel strategy: + * 1) Tries to parallel on the number of Groups (g) where tasks are + * independent. Otherwise, + * 2) Tries to split the work across g and MiniBatch (mb). + * Parallelizing on mb requires computing a reduction for weights. + * + * NOTE: because of 'task partitioning' scheme, there will be unbalanced + * per-thread load when the number of threads is high (e.g. > 16). + */ + jcp.nthr_g = nstl::min(jcp.nb_ch, jcp.nthr); + jcp.nthr_mb = nstl::min(nstl::max(1, jcp.nthr / jcp.nthr_g), jcp.mb); + + jcp.nthr = jcp.nthr_g * jcp.nthr_mb; +} + +template struct jit_uni_dw_conv_bwd_weights_kernel_f32; +template struct jit_uni_dw_conv_bwd_weights_kernel_f32; +template struct jit_uni_dw_conv_bwd_weights_kernel_f32; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.hpp new file mode 100644 index 000000000000..9c08fc4a0980 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_conv_kernel_f32.hpp @@ -0,0 +1,253 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_UNI_DW_CONV_KERNEL_F32_HPP +#define JIT_UNI_DW_CONV_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" +#include "jit_uni_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_uni_dw_conv_fwd_kernel_f32: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_dw_conv_fwd_kernel_f32) + + jit_uni_dw_conv_fwd_kernel_f32(jit_conv_conf_t ajcp) + : jcp(ajcp), eltwise_injector_(nullptr) + { + if (jcp.with_eltwise) + eltwise_injector_ = new jit_uni_eltwise_injector_f32(this, + jcp.eltwise); + + this->generate(); + jit_ker = (void (*)(jit_conv_call_s *))this->getCode(); + } + + ~jit_uni_dw_conv_fwd_kernel_f32() { + delete eltwise_injector_; + } + + static bool post_ops_ok(jit_conv_conf_t &jcp, + const primitive_attr_t &attr); + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &dst_d, const primitive_attr_t &attr); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_conv_call_s *); + +private: + using Vmm = typename utils::conditional3::type; + using reg64_t = const Xbyak::Reg64; + const Xbyak::AddressFrame &vmmword = (isa == sse42) + ? xword : (isa == avx2) ? yword : zword; + const int vlen = cpu_isa_traits::vlen; + + // dw convolution + reg64_t reg_input = r8; + reg64_t aux_reg_input = r9; + reg64_t aux1_reg_input = r10; + reg64_t reg_kernel = r11; + reg64_t aux_reg_kernel = r12; + reg64_t aux1_reg_kernel = r13; + reg64_t reg_output = r14; + reg64_t reg_bias = r15; + reg64_t reg_kh = rax; + reg64_t reg_kw = rbx; + reg64_t iter_kh = rdx; + reg64_t iter_kw = rsi; + reg64_t reg_ur_w = rbp; + reg64_t reg_ch_blocks = aux1_reg_input; + reg64_t imm_addr64 = aux1_reg_input; + + inline Vmm get_ker_reg(int idx) { return Vmm(idx + 0); } + inline Vmm get_src_reg(int idx) { return Vmm(idx + 1); } + inline Vmm get_acc_reg(int idx) { return Vmm(idx + 4); } + + inline void load_src(int ur_ch_blocks, int ur_w); + inline void apply_filter(int ur_ch_blocks, int ur_w); + inline void apply_filter_unrolled(int ur_ch_blocks, int ur_w); + inline void apply_activation(int ur_ch_blocks, int ur_w); + inline void store_dst(int ur_ch_blocks, int ur_w); + inline void loop_body(int ur_ch_blocks); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; + + void generate(); +}; + +template +struct jit_uni_dw_conv_bwd_data_kernel_f32: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_dw_conv_bwd_data_kernel_f32) + + jit_uni_dw_conv_bwd_data_kernel_f32(jit_conv_conf_t ajcp): jcp(ajcp) { + this->generate(); + jit_ker = (void (*)(jit_conv_call_s *))this->getCode(); + } + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, + const memory_desc_wrapper &diff_src_d, + const memory_desc_wrapper &weights_d, + const memory_desc_wrapper &diff_dst_d); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_conv_call_s *); + +private: + using Vmm = typename utils::conditional3::type; + using reg64_t = const Xbyak::Reg64; + + inline Vmm get_ker_reg(int idx) { return Vmm(idx + 0); } + inline Vmm get_src_reg(int idx) { return Vmm(idx + 1); } + inline Vmm get_acc_reg(int idx) { return Vmm(idx + 4); } + + reg64_t reg_ddst = rax; + reg64_t aux_reg_ddst = r8; + reg64_t aux1_reg_ddst = abi_not_param1; + reg64_t reg_kernel = rdx; + reg64_t aux_reg_kernel = r10; + reg64_t aux1_reg_kernel = rbp; + reg64_t reg_dsrc = rsi; + + reg64_t reg_ur_str_w = r9; + reg64_t reg_ch_blocks = rbx; + + reg64_t iter_kh = r11; + reg64_t iter_kw = r12; + reg64_t reg_kh = r13; + reg64_t reg_kw = r14; + + inline void loop_body(int ur_ch_blocks); + inline void load_ddst(int ur_ch_blocks, int ur_str_w); + inline void apply_filter(int ur_ch_blocks, int ur_str_w); + inline void store_dsrc(int ur_ch_blocks, int ur_str_w); + + void generate(); +}; + +template +struct jit_uni_dw_conv_bwd_weights_kernel_f32 : public jit_generator { + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_dw_conv_bwd_weights_kernel_f32) + + jit_uni_dw_conv_bwd_weights_kernel_f32(jit_conv_conf_t ajcp) : jcp(ajcp) { + this->generate(); + jit_ker = (void (*)(jit_dw_conv_call_s *)) this->getCode(); + } + + static status_t init_conf(jit_conv_conf_t &jcp, + const convolution_desc_t &cd, const memory_desc_wrapper &src_d, + const memory_desc_wrapper &diff_weights_d, + const memory_desc_wrapper &diff_dst_d, int nthreads); + + static void init_scratchpad(memory_tracking::registrar_t &scratchpad, + const jit_conv_conf_t &jcp); + + static void balance(jit_conv_conf_t &jcp, int nthreads); + + jit_conv_conf_t jcp; + void (*jit_ker)(jit_dw_conv_call_s *); + +private: + using Vmm = typename utils::conditional3::type; + using reg64_t = const Xbyak::Reg64; + const int simd_w = cpu_isa_traits::vlen / sizeof(float); + const int reg_repeats = (isa == sse42) ? 2 : 1; + + const Xbyak::AddressFrame &vmmword + = (isa == sse42) ? xword : (isa == avx2) ? yword : zword; + + /* XXX: offset between input and accummulators is 3, therefore, assume 'kw' + * is no larger than 3*/ + inline Vmm get_bias_reg(int idx = 0) { return Vmm(idx); } + inline Vmm get_output_reg(int idx) { return Vmm(idx + 1); } + inline Vmm get_input_reg(int idx) { return Vmm(idx + 4 * reg_repeats + 1); } + inline Vmm get_acc_reg(int idx) { return Vmm(idx + 1 * reg_repeats + 1); } + inline Vmm get_aux_reg() { return Vmm(0); } + + reg64_t reg_tmp_input = r9; + reg64_t reg_tmp_output = r10; + reg64_t reg_tmp_filter = r13; + reg64_t reg_kh_offset = rax; + + /* parameter passed by driver into kernel */ + Xbyak::Reg8 reg_exec_flags = bl; + + reg64_t reg_oh_worksize = r14; + reg64_t reg_oh = rax; + + reg64_t iter_ow_blk = r11; + + reg64_t reg_kh = rsi; + reg64_t reg_kh_count = rdx; + + /* Base addresses for convolution parameters. */ + reg64_t reg_input_baddr = r15; + reg64_t reg_output_baddr = r12; + reg64_t reg_filter_baddr = abi_not_param1; + reg64_t reg_bias_baddr = r13; + + /* Micro-kernel JIT'ing, fusing 'kw' and 'ow_block' loops into unrolled FMAs + */ + inline void compute_ow_step_unroll( + int unroll_w, int l_pad, int pad_offset, int ow_block); + + /* JIT'ing the outer loops for the micro-kernel -> {kh, oh_block} */ + inline void compute_h_step( + int unroll_w, int l_pad, int pad_offset, int ow_block); + inline void compute_h_loop( + int unroll_w, int l_pad, int pad_offset, int ow_block); + + /* Write 'width' micro-kernel JITs; depending on the padding and convolution + * size, write a micro-kernel for the left ow-block, middle ow-block(s), and + * right ow-block.*/ + inline void compute_ow_block_unroll(); + + inline void compute_zero_filter(); + inline void load_filter(); + inline void zero_filter(); + inline void load_bias(); + inline void zero_bias(); + inline void compute_bias_step_unroll(const int unroll_w); + inline void compute_bias_loop(const int block_size); + inline void store_filter(); + inline void store_bias(); + + void generate(); +}; +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.cpp new file mode 100644 index 000000000000..58449601a343 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.cpp @@ -0,0 +1,427 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "mkldnn_thread.hpp" + +#include "jit_uni_dw_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::memory_tracking::names; +using namespace mkldnn::impl::utils; + +template +void _jit_uni_dw_convolution_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const auto &jcp = kernel_->jcp; + + if (pd()->wants_padded_bias()) { + auto padded_bias = this->scratchpad(ctx).template get( + key_conv_padded_bias); + utils::array_copy(padded_bias, bias, jcp.oc_without_padding); + utils::array_set(padded_bias + jcp.oc_without_padding, 0.f, + jcp.oc - jcp.oc_without_padding); + bias = padded_bias; + } + + int dil_h = jcp.dilate_h + 1; + int dil_w = jcp.dilate_w + 1; + int str_h = jcp.stride_h; + int str_w = jcp.stride_w; + + auto kernel_params = [&](int ur_w_step, int ow, int oh, int ih, int kh, + int kh_padding, int ch, int ch_num, int n) { + auto par_conv = jit_conv_call_s(); + + const int i_l_overflow = nstl::max(0, (jcp.l_pad - ow * str_w)); + const int i_r_overflow = nstl::max(jcp.iw, (ow * str_w + + (jcp.kw - 1)*dil_w - jcp.l_pad + 1)) - jcp.iw; + + const int iw = nstl::max((ow*str_w - jcp.l_pad + + div_up(i_l_overflow, dil_w)*dil_w), 0); + const int kw = div_up(i_l_overflow, dil_w); + + const int kw_padding = jcp.kw - div_up(i_l_overflow, dil_w) + - div_up(i_r_overflow, dil_w); + + par_conv.src = &src[src_d.blk_off(n, ch, ih, iw)]; + par_conv.dst = &dst[dst_d.blk_off(n, ch, oh, ow)]; + + par_conv.filt = &weights[weights_d.blk_off(ch, 0, 0, kh, kw)]; + if (bias) par_conv.bias = &bias[bias_d.blk_off(ch*jcp.ch_block)]; + + par_conv.kh_padding = (size_t)nstl::max(0, kh_padding); + par_conv.kw_padding = (size_t)nstl::max(0, kw_padding); + + par_conv.ur_w = (size_t)ur_w_step; + + par_conv.ch_blocks = nstl::min(ch + ch_num, jcp.nb_ch) - ch; + + return par_conv; + }; + + const int chb_work = utils::div_up(jcp.nb_ch, jcp.nb_ch_blocking); + parallel_nd(jcp.mb, chb_work, jcp.oh, + [&](int n, int chb, int oh) { + int ch = chb * jcp.nb_ch_blocking; + int ch_num = jcp.nb_ch_blocking; + + const int i_t_overflow = nstl::max(0, (int)(jcp.t_pad - oh*str_h)); + const int i_b_overflow = nstl::max(jcp.ih, + (int)(oh*str_h + (jcp.kh - 1)*dil_h - jcp.t_pad + 1)) - jcp.ih; + + const int ih = nstl::max((int)(oh*str_h - jcp.t_pad + + div_up(i_t_overflow, dil_h)*dil_h), 0); + const int kh = div_up(i_t_overflow, dil_h); + const int kh_padding = jcp.kh - div_up(i_t_overflow, dil_h) + - div_up(i_b_overflow, dil_h); + + // left border + int ow = 0; + int l_border = nstl::min(div_up(jcp.l_pad, str_w), jcp.ow); + int ur_w_step = 1; + for (; ow < l_border; ow++) { + jit_conv_call_s par_conv = kernel_params(ur_w_step, ow, oh, ih, + kh, kh_padding, ch, ch_num, n); + + kernel_->jit_ker(&par_conv); + } + + // main loop + ur_w_step = (jcp.iw - (jcp.kw - 1)*dil_w + jcp.l_pad - 1) + / jcp.stride_w - ow + 1; + if (ur_w_step > 0) { + jit_conv_call_s par_conv = kernel_params(ur_w_step, ow, oh, ih, + kh, kh_padding, ch, ch_num, n); + + kernel_->jit_ker(&par_conv); + + ow += ur_w_step; + } + + // right border + ur_w_step = 1; + for (; ow < jcp.ow; ow++) { + jit_conv_call_s par_conv = kernel_params(ur_w_step, ow, oh, ih, + kh, kh_padding, ch, ch_num, n); + + kernel_->jit_ker(&par_conv); + } + }); + + if (pd()->wants_zero_pad_dst()) + ctx.memory(MKLDNN_ARG_DST)->zero_pad(); +} + +template struct _jit_uni_dw_convolution_fwd_t; +template struct _jit_uni_dw_convolution_fwd_t; +template struct _jit_uni_dw_convolution_fwd_t; + +template +void _jit_uni_dw_convolution_bwd_data_t::execute_backward_data( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + + const auto &jcp = kernel_->jcp; + + auto kernel_params = [&](int ur_str_w, int iw, int oh, int ih, + int i_t_overflow, int i_b_overflow, int stride_off_h, + int ch, int ch_num, int n) { + auto par_conv = jit_conv_call_s(); + + const int i_l_overflow = nstl::max(0, (jcp.kw - 1 - iw - jcp.l_pad)); + const int i_r_overflow = nstl::max(0, (jcp.kw - 1 - (jcp.iw - 1 - iw) + - jcp.r_pad)); + + int ow = iw + jcp.l_pad - i_r_overflow; + int stride_off_w = ow % jcp.stride_w; + ow /= jcp.stride_w; + + par_conv.src = &diff_src[diff_src_d.blk_off(n, ch, ih, iw)]; + par_conv.dst = &diff_dst[diff_dst_d.blk_off(n, ch, oh, ow)]; + par_conv.filt = &weights[weights_d.blk_off(ch, 0, 0, i_b_overflow + + stride_off_h, i_r_overflow + stride_off_w)]; + + par_conv.kh_padding = nstl::max(0, jcp.kh - i_t_overflow - i_b_overflow + - stride_off_h); + par_conv.kw_padding = nstl::max(0, jcp.kw - i_l_overflow - i_r_overflow + - stride_off_w); + + par_conv.ur_str_w = ur_str_w; + + par_conv.ch_blocks = nstl::min(ch + ch_num, jcp.nb_ch) - ch; + + return par_conv; + }; + + const int chb_work = utils::div_up(jcp.nb_ch, jcp.nb_ch_blocking); + parallel_nd(jcp.mb, chb_work, jcp.ih, + [&](int n, int chb, int ih) { + int ch = chb * jcp.nb_ch_blocking; + int ch_num = jcp.nb_ch_blocking; + + const int i_t_overflow = nstl::max(0, (int)(jcp.kh - 1 - ih + - jcp.t_pad)); + const int i_b_overflow = nstl::max(0, (int)(jcp.kh - 1 + - (jcp.ih - 1 - ih) - jcp.b_pad)); + + int oh = ih + jcp.t_pad - i_b_overflow; + int stride_off_h = oh % jcp.stride_h; + oh /= jcp.stride_h; + + for (int i_str_w = 0; i_str_w < jcp.stride_w; i_str_w++) { + // left border + int iw = i_str_w; + int l_border = nstl::min(jcp.kw - 1 - jcp.l_pad, jcp.iw); + int ur_str_w = 1; + for (; iw < l_border; iw += jcp.stride_w) { + jit_conv_call_s par_conv = kernel_params(ur_str_w, iw, oh, + ih, i_t_overflow, i_b_overflow, + stride_off_h, ch, ch_num, n); + + kernel_->jit_ker(&par_conv); + } + + // main loop + ur_str_w = nstl::min((jcp.iw - jcp.kw + jcp.r_pad - iw) + / jcp.stride_w, jcp.iw); + if (ur_str_w > 0) { + jit_conv_call_s par_conv = kernel_params(ur_str_w, iw, oh, + ih, i_t_overflow, i_b_overflow, + stride_off_h, ch, ch_num, n); + + kernel_->jit_ker(&par_conv); + + iw += ur_str_w * jcp.stride_w; + } + + // right border + ur_str_w = 1; + for (; iw < jcp.iw; iw += jcp.stride_w) { + jit_conv_call_s par_conv = kernel_params(ur_str_w, iw, oh, + ih, i_t_overflow, i_b_overflow, + stride_off_h, ch, ch_num, n); + + kernel_->jit_ker(&par_conv); + } + } + }); +} + +template struct _jit_uni_dw_convolution_bwd_data_t; +template struct _jit_uni_dw_convolution_bwd_data_t; +template struct _jit_uni_dw_convolution_bwd_data_t; + +template +_jit_uni_dw_convolution_bwd_weights_t:: +_jit_uni_dw_convolution_bwd_weights_t(const pd_t *apd) + : cpu_primitive_t(apd) + , kernel_(nullptr), acc_ker_(nullptr) +{ + kernel_ = new jit_uni_dw_conv_bwd_weights_kernel_f32(pd()->jcp_); + if (pd()->jcp_.nthr_mb > 1 && do_parallel_reduction()) + acc_ker_ = new cpu_accumulator_1d_t(); +} + +template +void _jit_uni_dw_convolution_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + auto diff_wei_reduction_buf = + scratchpad(ctx).template get(key_conv_wei_reduction); + auto diff_bia_reduction_buf = + scratchpad(ctx).template get(key_conv_bia_reduction); + + const auto &jcp = kernel_->jcp; + + /* Used when executing a parallel reduction */ + simple_barrier::ctx_t reduction_bctx; + simple_barrier::ctx_init(&reduction_bctx); + + const size_t wei_size = jcp.ngroups * jcp.kh * jcp.kw; + const size_t bias_size = jcp.with_bias ? jcp.ngroups : 0; + + const int ch_block = jcp.ch_block; + + auto set_kernel_params = [&](jit_dw_conv_call_s *conv_params, + const int batch, const int group, const int oh_start, + const int work_size, const unsigned char exec_flag, + const size_t kh_padding, const size_t filter_off) { + const int tpad_underflow_off = jcp.t_pad - filter_off; + + conv_params->exec_flags = exec_flag; + conv_params->kh_count = jcp.kh - kh_padding; + + const int oh_s = oh_start; + const int oh_e = oh_start + work_size; + const int ih_s = oh_s * jcp.stride_h; + + conv_params->filter_pad_off + = filter_off * jcp.kw * ch_block * sizeof(float); + conv_params->oh_index = oh_s; + conv_params->oh_count = oh_e; + + size_t diff_dst_off + = ((batch * (jcp.ngroups / ch_block) + group) * jcp.oh + + oh_start) + * jcp.ow; + + size_t src_off = ((batch * (jcp.ngroups / ch_block) + group) * jcp.ih + + ih_s - tpad_underflow_off) * jcp.iw; + + conv_params->output = &diff_dst[diff_dst_off * ch_block]; + conv_params->input = &src[src_off * ch_block]; + }; + + parallel(jcp.nthr, [&](const int ithr, const int nthr) { + assert(nthr == jcp.nthr); + + auto conv_params = jit_dw_conv_call_s(); + const int h_block_size = 15; + + /* assign iteration space to thread */ + const int ithr_g = ithr % jcp.nthr_g; + const int ithr_mb = (ithr / jcp.nthr_g) % jcp.nthr_mb; + + /* split dimensions */ + int g_start{ 0 }, g_end{ 0 }; + balance211(jcp.nb_ch, jcp.nthr_g, ithr_g, g_start, g_end); + + int mb_start{ 0 }, mb_end{ 0 }; + balance211(jcp.mb, jcp.nthr_mb, ithr_mb, mb_start, mb_end); + + auto diff_wei = ithr_mb == 0 + ? diff_weights : diff_wei_reduction_buf + (ithr_mb - 1) * wei_size; + auto diff_bia = ithr_mb == 0 + ? diff_bias : diff_bia_reduction_buf + (ithr_mb - 1) * bias_size; + + for (int g = g_start; g < g_end; ++g) { + unsigned char zero_filter_flag = FLAG_ZERO_FILTER; + unsigned char zero_bias_flag = jcp.with_bias ? FLAG_ZERO_BIAS : 0; + + size_t diff_wei_off = g * jcp.kh * jcp.kw; + conv_params.filter = &diff_wei[diff_wei_off * ch_block]; + + if (jcp.with_bias) + conv_params.bias = &diff_bia[g * ch_block]; + + for (int mb = mb_start; mb < mb_end; ++mb) { + int oh = 0; + while (oh < jcp.oh) { + const int h_work = nstl::min(h_block_size, jcp.oh - oh); + auto kh_t_padding = nstl::max(0, jcp.t_pad - oh); + auto kh_b_padding + = (oh * jcp.stride_h + jcp.kh - 1 > jcp.ih) ? + jcp.b_pad - (h_work - 1) : + 0; + + set_kernel_params(&conv_params, mb, g, oh, h_work, + zero_filter_flag | zero_bias_flag, + kh_t_padding + kh_b_padding, kh_t_padding); + kernel_->jit_ker(&conv_params); + + zero_bias_flag &= ~FLAG_ZERO_BIAS; + zero_filter_flag &= ~FLAG_ZERO_FILTER; + oh += h_work; + } + } + } + + if (do_parallel_reduction() && jcp.nthr_mb > 1) { + size_t reduct_start{ 0 }, reduct_end{ 0 }; + balance211(wei_size, nthr, ithr, reduct_start, reduct_end); + + const int acc_size = reduct_end - reduct_start; + const size_t reduct_off = reduct_start; + auto *acc_data = diff_weights + reduct_off; + + simple_barrier::barrier(&reduction_bctx, nthr); + + for (int thr_mb = 1; thr_mb < jcp.nthr_mb; ++thr_mb) { + auto *src_data = diff_wei_reduction_buf + + (thr_mb - 1) * wei_size + reduct_off; + acc_ker_->accumulate(acc_data, src_data, acc_size); + } + } + }); + + if (jcp.nthr_mb <= 1) return; + + /* Apply single-threaded 'mb' reduction */ + for (int thr_mb = 1; thr_mb < jcp.nthr_mb; ++thr_mb) { + size_t mb_accum_offset = (thr_mb - 1) * wei_size; + size_t b_accum_offset = (thr_mb - 1) * bias_size; + + for (int g = 0; g < jcp.nb_ch; ++g) { + /* Reduction on Bias */ + if (jcp.with_bias) { + PRAGMA_OMP_SIMD() + for (int g_block = 0; g_block < ch_block; ++g_block) { + size_t bias_offset = g * ch_block + g_block; + diff_bias[bias_offset] += diff_bia_reduction_buf[ + b_accum_offset + bias_offset]; + } + } + + if (do_parallel_reduction()) continue; + + for (int kh = 0; kh < jcp.kh; ++kh) + for (int kw = 0; kw < jcp.kw; ++kw) + { + size_t wei_offset = (g * jcp.kh + kh) * jcp.kw + kw; + PRAGMA_OMP_SIMD() + for (int g_block = 0; g_block < ch_block; ++g_block) { + const size_t off = wei_offset * ch_block + g_block; + diff_weights[off] += + diff_wei_reduction_buf[mb_accum_offset + off]; + } + } + } + } +} + +template struct _jit_uni_dw_convolution_bwd_weights_t; +template struct _jit_uni_dw_convolution_bwd_weights_t; +template struct _jit_uni_dw_convolution_bwd_weights_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.hpp new file mode 100644 index 000000000000..ca53749ec2e7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_dw_convolution.hpp @@ -0,0 +1,266 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_UNI_DW_CONVOLUTION_HPP +#define CPU_JIT_UNI_DW_CONVOLUTION_HPP + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" + +#include "cpu_barrier.hpp" +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" +#include "cpu_reducer.hpp" + +#include "jit_uni_dw_conv_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct _jit_uni_dw_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + pd_t(engine_t *engine, const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const typename pd_t::base_class *hint_fwd_pd) + : cpu_convolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_dw:", isa, ""), + _jit_uni_dw_convolution_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + status_t status = jit_uni_dw_conv_fwd_kernel_f32::init_conf( + jcp_, *desc(), src_md(), *weights_md(), *dst_md(), *attr()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_uni_dw_conv_fwd_kernel_f32::init_scratchpad(scratchpad, + jcp_); + + return status::success; + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = isa == avx512_common ? nChw16c : nChw8c; + auto wei_tag = isa == avx512_common ? Goihw16g : Goihw8g; + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + _jit_uni_dw_convolution_fwd_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_uni_dw_conv_fwd_kernel_f32(pd()->jcp_); } + + ~_jit_uni_dw_convolution_fwd_t() { delete kernel_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_uni_dw_conv_fwd_kernel_f32 *kernel_; +}; + +using jit_avx512_common_dw_convolution_fwd_t = + _jit_uni_dw_convolution_fwd_t; +using jit_avx2_dw_convolution_fwd_t = _jit_uni_dw_convolution_fwd_t; +using jit_sse42_dw_convolution_fwd_t = _jit_uni_dw_convolution_fwd_t; + +template +struct _jit_uni_dw_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() + {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_dw:", isa, ""), + _jit_uni_dw_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::undef, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + + if (!ok) return status::unimplemented; + + status_t status = jit_uni_dw_conv_bwd_data_kernel_f32:: + init_conf(jcp_, *desc(), *diff_src_md(), *weights_md(), + *diff_dst_md()); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_uni_dw_conv_bwd_data_kernel_f32::init_scratchpad( + scratchpad, jcp_); + + return status::success; + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = isa == avx512_common ? nChw16c : nChw8c; + auto wei_tag = isa == avx512_common ? Goihw16g : Goihw8g; + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + _jit_uni_dw_convolution_bwd_data_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_uni_dw_conv_bwd_data_kernel_f32(pd()->jcp_); } + ~_jit_uni_dw_convolution_bwd_data_t() { delete kernel_; }; + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_uni_dw_conv_bwd_data_kernel_f32 *kernel_; +}; + +using jit_avx512_common_dw_convolution_bwd_data_t = + _jit_uni_dw_convolution_bwd_data_t; +using jit_avx2_dw_convolution_bwd_data_t = + _jit_uni_dw_convolution_bwd_data_t; +using jit_sse42_dw_convolution_bwd_data_t = + _jit_uni_dw_convolution_bwd_data_t; + +template +struct _jit_uni_dw_convolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_weights_pd_t { + pd_t(engine_t *engine, + const convolution_desc_t *adesc, + const primitive_attr_t *attr, + const convolution_fwd_pd_t *hint_fwd_pd) + : cpu_convolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , jcp_() {} + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit_dw:", isa, ""), + _jit_uni_dw_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(data_type::f32, data_type::f32, + data_type::f32, data_type::f32, data_type::f32) + && !has_zero_dim_memory() + && set_default_formats(); + if (!ok) return status::unimplemented; + + const int max_threads = mkldnn_in_parallel() + ? 1 : mkldnn_get_max_threads(); + + status_t status = jit_uni_dw_conv_bwd_weights_kernel_f32:: + init_conf(jcp_, *desc(), *src_md(), *diff_weights_md(), + *diff_dst_md(), max_threads); + if (status != status::success) return status; + + auto scratchpad = scratchpad_registry().registrar(); + jit_uni_dw_conv_bwd_weights_kernel_f32::init_scratchpad( + scratchpad, jcp_); + + return status::success; + } + + jit_conv_conf_t jcp_; + + protected: + bool set_default_formats() { + using namespace format_tag; + + auto dat_tag = isa == avx512_common ? nChw16c : nChw8c; + auto wei_tag = isa == avx512_common ? Goihw16g : Goihw8g; + + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + _jit_uni_dw_convolution_bwd_weights_t(const pd_t *apd); + ~_jit_uni_dw_convolution_bwd_weights_t() { + delete kernel_; + delete acc_ker_; + }; + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + bool do_parallel_reduction() const { return false; } + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_uni_dw_conv_bwd_weights_kernel_f32 *kernel_; + cpu_accumulator_1d_t *acc_ker_; +}; + +using jit_avx512_common_dw_convolution_bwd_weights_t = + _jit_uni_dw_convolution_bwd_weights_t; +using jit_avx2_dw_convolution_bwd_weights_t = + _jit_uni_dw_convolution_bwd_weights_t; +using jit_sse42_dw_convolution_bwd_weights_t = + _jit_uni_dw_convolution_bwd_weights_t; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.cpp new file mode 100644 index 000000000000..2af643587183 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.cpp @@ -0,0 +1,1142 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "utils.hpp" + +#include "jit_uni_eltwise.hpp" + +#define GET_OFF(field) offsetof(jit_args, field) + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + +template +void jit_uni_eltwise_injector_f32::injector_preamble(size_t start_idx, + size_t end_idx) { + preserved_vecs_count = 0; + vecs_to_preserve = (size_t)aux_vecs_count(alg_); + start_idx_tail = start_idx; + + // For sse42 mask register has to be Xmm(0) + if (isa == sse42 && vecs_to_preserve > 0) { + size_t idx = 0; + assert(idx < start_idx); + preserved_vec_idxs[preserved_vecs_count++] = idx; + } + + for (size_t idx = preserved_vecs_count; idx < vecs_count; idx++) { + if (preserved_vecs_count >= vecs_to_preserve) break; + if (start_idx <= idx && idx < end_idx) continue; + + preserved_vec_idxs[preserved_vecs_count++] = idx; + } + + size_t preserved_vecs_count_tail = vecs_to_preserve - preserved_vecs_count; + for (size_t i = 0; i < preserved_vecs_count_tail; i++) { + preserved_vec_idxs[preserved_vecs_count++] = start_idx_tail++; + } + + assert(preserved_vecs_count == vecs_to_preserve); + + if (save_state_) { + h->push(p_table); + + if (preserved_vecs_count) + h->sub(h->rsp, preserved_vecs_count * vlen); + + for (size_t i = 0; i < preserved_vecs_count; ++i) + h->uni_vmovups(h->ptr[h->rsp + i * vlen], + Vmm(preserved_vec_idxs[i])); + + load_table_addr(); + } + + assign_regs(); +} + +template +void jit_uni_eltwise_injector_f32::injector_preamble_tail(size_t start_idx) +{ + size_t tail_vecs_to_preserve = start_idx_tail - start_idx; + if (tail_vecs_to_preserve == 0) return; + + const int idx_off = vecs_to_preserve - tail_vecs_to_preserve; + + if (save_state_) { + if (idx_off) + h->add(h->rsp, idx_off * vlen); + + for (size_t i = 0; i < tail_vecs_to_preserve; ++i) + h->uni_vmovups(Vmm(preserved_vec_idxs[idx_off + i]), + h->ptr[h->rsp + i * vlen]); + } + + for (size_t i = 0; i < tail_vecs_to_preserve; ++i) + preserved_vec_idxs[idx_off + i] += tail_vecs_to_preserve; + + if (save_state_) { + for (size_t i = 0; i < tail_vecs_to_preserve; ++i) + h->uni_vmovups(h->ptr[h->rsp + i * vlen], + Vmm(preserved_vec_idxs[idx_off + i])); + + if (idx_off) + h->sub(h->rsp, idx_off * vlen); + } + + assign_regs(); +} + +template +void jit_uni_eltwise_injector_f32::injector_postamble() { + if (!save_state_) return; + + for (size_t i = 0; i < preserved_vecs_count; ++i) + h->uni_vmovups(Vmm(preserved_vec_idxs[i]), + h->ptr[h->rsp + i * vlen]); + + if (preserved_vecs_count) + h->add(h->rsp, preserved_vecs_count * vlen); + + h->pop(p_table); +} + +template +void jit_uni_eltwise_injector_f32::assign_regs() { + vmm_mask = Vmm(preserved_vec_idxs[0]); + vmm_aux0 = Vmm(preserved_vec_idxs[0]); + vmm_aux1 = Vmm(preserved_vec_idxs[1]); + vmm_aux2 = Vmm(preserved_vec_idxs[2]); + vmm_aux3 = Vmm(preserved_vec_idxs[3]); + vmm_aux4 = Vmm(preserved_vec_idxs[4]); +} + +template +void jit_uni_eltwise_injector_f32::exp_compute_vector(const Vmm &vmm_src) { + h->uni_vminps(vmm_src, vmm_src, table_val(10)); + h->uni_vmaxps(vmm_src, vmm_src, table_val(11)); + h->uni_vmovups(vmm_aux0, vmm_src); + //calculate exp(x) + // fx = x * log2ef + 0.5 + h->uni_vmulps(vmm_src, vmm_src, table_val(2)); + h->uni_vaddps(vmm_src, vmm_src, table_val(1)); + + // tmp = floorf(fx) + if (isa == avx512_common) { + h->vcvtps2dq(vmm_aux1 | h->T_rd_sae, vmm_src); + h->vcvtdq2ps(vmm_aux1, vmm_aux1); + + h->vcmpps(k_mask, vmm_aux1, vmm_src, _cmp_nle_us); + h->vmovups(vmm_aux3 | k_mask | h->T_z, table_val(0)); + + h->uni_vsubps(vmm_aux1, vmm_aux1, vmm_aux3); + } else { + h->uni_vroundps(vmm_aux1, vmm_src, _op_floor); + } + + //keep fx for further computations + h->uni_vmovups(vmm_src, vmm_aux1); //vmm_src = fx + + //x = x - fx * ln2 + h->uni_vfnmadd231ps(vmm_aux0, vmm_aux1, table_val(3)); + + // compute 2^n + h->uni_vcvtps2dq(vmm_aux1, vmm_src); + h->uni_vpaddd(vmm_aux1, vmm_aux1, table_val(4)); + h->uni_vpslld(vmm_aux1, vmm_aux1, 23); //Vmm(6) = 2^-fx + + // y = p5 + h->uni_vmovups(vmm_src, table_val(9)); + // y = y * x + p4 + h->uni_vfmadd213ps(vmm_src, vmm_aux0, table_val(8)); + // y = y * x + p3 + h->uni_vfmadd213ps(vmm_src, vmm_aux0, table_val(7)); + // y = y * x + p2 + h->uni_vfmadd213ps(vmm_src, vmm_aux0, table_val(6)); + // y = y * x + p1 + h->uni_vfmadd213ps(vmm_src, vmm_aux0, table_val(0)); + // y = y * x + p0 + h->uni_vfmadd213ps(vmm_src, vmm_aux0, table_val(5)); //exp(q) + // y = y * 2^n + h->uni_vmulps(vmm_src, vmm_src, vmm_aux1); +} + +template +void jit_uni_eltwise_injector_f32::relu_compute_vector(const Vmm &vmm_src) +{ + const int alpha_off = 0, zero_off = 1; + + h->uni_vmovups(vmm_aux1, vmm_src); + if (isa == sse42) { + h->movups(vmm_mask, vmm_src); + h->mulps(vmm_src, table_val(alpha_off)); + h->cmpps(vmm_mask, table_val(zero_off), _cmp_nle_us); + h->blendvps(vmm_src, vmm_aux1); + } else if (isa == avx2) { + h->vmulps(vmm_src, vmm_src, table_val(alpha_off)); + h->vcmpgtps(vmm_mask, vmm_aux1, table_val(zero_off)); + h->vblendvps(vmm_src, vmm_src, vmm_aux1, vmm_mask); + } else if (isa == avx512_common) { + h->vmulps(vmm_src, vmm_src, table_val(alpha_off)); + h->vcmpps(k_mask, vmm_aux1, table_val(zero_off), _cmp_nle_us); + h->vblendmps(vmm_src | k_mask, vmm_src, vmm_aux1); + } +} + +template +void jit_uni_eltwise_injector_f32::relu_zero_ns_compute_vector( + const Vmm &vmm_src) { + const int zero_off = 1; + h->uni_vmaxps(vmm_src, vmm_src, table_val(zero_off)); +} + +template +void jit_uni_eltwise_injector_f32::elu_compute_vector(const Vmm &vmm_src) { + const int alpha_off = 23, zero_off = 24; + + // compute exponent + h->uni_vmovups(vmm_aux2, vmm_src); + exp_compute_vector(vmm_src); + + // alpha * (exp(x) - 1) + h->uni_vsubps(vmm_src, vmm_src, table_val(0)); + h->uni_vmulps(vmm_src, vmm_src, table_val(alpha_off)); + + // combine with mask + if (isa == sse42) { + h->pxor(vmm_mask, vmm_mask); + h->cmpps(vmm_mask, vmm_aux2, _cmp_le_os); + h->blendvps(vmm_src, vmm_aux2); + } else if (isa == avx2) { + h->uni_vcmpgtps(vmm_mask, vmm_aux2, table_val(zero_off)); + h->uni_vblendvps(vmm_src, vmm_src, vmm_aux2, vmm_mask); + } else if (isa == avx512_common) { + h->vcmpps(k_mask, vmm_aux2, table_val(zero_off), _cmp_nle_us); + h->vblendmps(vmm_src | k_mask, vmm_src, vmm_aux2); + } +} + +template +void jit_uni_eltwise_injector_f32::tanh_compute_vector(const Vmm &vmm_src) +{ + // # comes from Taylor expansion error bound + // > linear_sat_point = single(sqrt(3) * 1b-12); + // # comes from the exp formula cancellation + // > exp_bound_point = (single(log(3)/2)); + // # comes from rounding accuracy in float + // > one_sat_point = round(atanh(1 - 1b-25), single, RU); + // > P = fpminimax(f, [|1, 3, 5, 7, 9|], [|24... |], + // [linear_sat_point, exp_bound_point], relative, floating); + // > err_bound = D(sup(supnorm(P, tanh(x), + // [linear_sat_point, exp_bound_point], relative, theta))); + // 0x1.fffd6f00b9539p-25 + // > P; + // x * (0x1.fffffep-1 + x^0x1p1 * (-0x1.55539ep-2 + x^0x1p1 * + // (0x1.10be3ep-3 + x^0x1p1 * (-0x1.ae57b4p-5 + // + x^0x1p1 * 0x1.09fa1p-6)))) + + // register mapping + // vmm_src contains input + // vmm_aux0 contains mask of currently valid results. + // 1 is need computation, 0 is already computed + // vmm_aux1 contains current output + // vmm_aux2, vmm_aux3 contains auxiliary values + // vmm_aux4 contains the original sign of inputs + + Label end_tanh_label; + + auto test_exit =[&](Xbyak::Address threshold){ + // is not necessary for >AVX, but should not matter on perf + h->uni_vmovups(vmm_aux0, vmm_src); + if (isa == avx512_common){ + h->vcmpps(k_mask, vmm_aux0, threshold, 0x5); + h->kortestw(k_mask, k_mask); + } else { + h->uni_vcmpgeps(vmm_aux0, vmm_aux0, threshold); + h->uni_vtestps(vmm_aux0, vmm_aux0); + } + h->jz(end_tanh_label, Xbyak::CodeGenerator::T_NEAR); + }; + + auto blend_results=[&](Vmm vmm_partial_res){ + if (isa == avx512_common) + h->vblendmps(vmm_aux1 | k_mask, vmm_aux1, vmm_partial_res); + else + h->uni_vblendvps(vmm_aux1, vmm_aux1, vmm_partial_res, vmm_aux0); + }; + + // because tanh(x) = -tanh(-x), we extract sign to make x postive + // and reapply sign at the end + // mov is not necessary for >AVX, but should not matter for performance + h->uni_vmovups(vmm_aux4, vmm_src); + h->uni_vandps(vmm_aux4, vmm_aux4, table_val(12)); + h->uni_vandps(vmm_src, vmm_src, table_val(17)); + + // if x < linear_sat_point for all inputs, we just return the input + h->uni_vmovups(vmm_aux1, vmm_src); + test_exit(table_val(13)); + + // if one of the mask is one, we have to compute an better approx + h->uni_vmovups(vmm_aux2, vmm_src); + h->uni_vmulps(vmm_aux2, vmm_aux2, vmm_aux2); + h->uni_vmovups(vmm_aux3, table_val(22)); + h->uni_vfmadd213ps(vmm_aux3, vmm_aux2, table_val(21)); + h->uni_vfmadd213ps(vmm_aux3, vmm_aux2, table_val(20)); + h->uni_vfmadd213ps(vmm_aux3, vmm_aux2, table_val(19)); + h->uni_vfmadd213ps(vmm_aux3, vmm_aux2, table_val(18)); + h->uni_vmulps(vmm_aux3, vmm_aux3, vmm_src); + + // we blend only the result that need update + blend_results(vmm_aux3); + + // if x < exp_bound_point, we go to return point + test_exit(table_val(14)); + + // if not we use a better approx 1 - 2 / (1 + exp(2x)) + // compute 2x + h->uni_vmovups(vmm_aux3, vmm_src); + h->uni_vaddps(vmm_aux3, vmm_aux3, vmm_aux3); + + // Compute exp(2x) + // We need to save kmask, vmm_aux0, vmm_aux1 and vmm_src as exp can use them + // vmm_src is not more read afterwards, so we do not have to save it + auto stack_size = 3 * vlen + (isa == avx512_common) * 4; + h->sub(h->rsp, stack_size); + h->uni_vmovups(h->ptr[h->rsp + 0 * vlen], vmm_aux0); + h->uni_vmovups(h->ptr[h->rsp + 1 * vlen], vmm_aux1); + h->uni_vmovups(h->ptr[h->rsp + 2 * vlen], vmm_src); + if (isa == avx512_common) + h->kmovw(h->ptr[h->rsp + 3 * vlen], k_mask); + + exp_compute_vector(vmm_aux3); + + h->uni_vmovups(vmm_aux0, h->ptr[h->rsp + 0 * vlen]); + h->uni_vmovups(vmm_aux1, h->ptr[h->rsp + 1 * vlen]); + h->uni_vmovups(vmm_src, h->ptr[h->rsp + 2 * vlen]); + if (isa == avx512_common) + h->kmovw(k_mask, h->ptr[h->rsp + 3 * vlen]); + h->add(h->rsp, stack_size); + + // 1 + exp(2x) + h->uni_vaddps(vmm_aux3, vmm_aux3, table_val(0)); + + // 1 - 2 / (1 + exp(2x)) + h->uni_vmovups(vmm_aux2, table_val(16)); + h->uni_vdivps(vmm_aux2, vmm_aux2, vmm_aux3); + h->uni_vaddps(vmm_aux2, vmm_aux2, table_val(0)); + + // we blend only the result that need update + blend_results(vmm_aux2); + + // finally, we saturate to 1 if needed + // TODO: maybe move that up if most inputs saturate in practice + if (isa == avx512_common) + h->vcmpps(k_mask, vmm_aux0, table_val(15), 0x5); + else { + h->uni_vmovups(vmm_aux0, vmm_src); + h->uni_vcmpgeps(vmm_aux0, vmm_aux0, table_val(15)); + } + h->uni_vmovups(vmm_aux2, table_val(0)); + blend_results(vmm_aux2); + + h->L(end_tanh_label); + { + // we apply the sign of x to the result and we are done + h->uni_vmovups(vmm_src, vmm_aux1); + h->uni_vpxor(vmm_src, vmm_src, vmm_aux4); + } +} + +template +void jit_uni_eltwise_injector_f32::square_compute_vector( + const Vmm &vmm_src) { + h->uni_vmulps(vmm_src, vmm_src, vmm_src); +} + +template +void jit_uni_eltwise_injector_f32::abs_compute_vector(const Vmm &vmm_src) { + // compute abs(x) = _mm_and_ps(x, 01111..111)); + h->uni_vandps(vmm_src, vmm_src, table_val(0)); +} + +template +void jit_uni_eltwise_injector_f32::sqrt_compute_vector(const Vmm &vmm_src) +{ + if (isa == avx512_common) { + h->vcmpps(k_mask, vmm_src, table_val(0), _cmp_nle_us); + h->uni_vsqrtps(vmm_aux1, vmm_src); + h->uni_vmovups(vmm_src, table_val(0)); + h->vblendmps(vmm_src | k_mask, vmm_src, vmm_aux1); + } else { + h->uni_vmovups(vmm_mask, vmm_src); + h->uni_vcmpgtps(vmm_mask, vmm_mask, table_val(0)); + h->uni_vsqrtps(vmm_aux1, vmm_src); + h->uni_vmovups(vmm_src, table_val(0)); + h->uni_vblendvps(vmm_src, vmm_src, vmm_aux1, vmm_mask); + } +} + +template +void jit_uni_eltwise_injector_f32::linear_compute_vector( + const Vmm &vmm_src) { + // compute x = alpha * x + beta; + h->uni_vmovups(vmm_aux0, table_val(0)); + h->uni_vfmadd213ps(vmm_src, vmm_aux0, table_val(1)); +} + +template +void jit_uni_eltwise_injector_f32::bounded_relu_compute_vector( + const Vmm &vmm_src) { + // compute bounded relu */ + h->uni_vmaxps(vmm_src, vmm_src, table_val(1)); + h->uni_vminps(vmm_src, vmm_src, table_val(0)); +} + +template +void jit_uni_eltwise_injector_f32::soft_relu_compute_vector( + const Vmm &vmm_src) { + // duplicate src + h->uni_vmovups(vmm_aux2, vmm_src); + + h->uni_vminps(vmm_src, vmm_src, table_val(24)); + h->uni_vmaxps(vmm_src, vmm_src, table_val(25)); + h->uni_vmovups(vmm_aux1, vmm_src); + // calculate exp(x) + // fx = x * log2ef + 0.5 + h->uni_vmulps(vmm_src, vmm_src, table_val(2)); + h->uni_vaddps(vmm_src, vmm_src, table_val(1)); + + // tmp = floorf(fx) + if (isa == avx512_common) { + h->vcvtps2dq(vmm_aux0 | h->T_rd_sae, vmm_src); + h->vcvtdq2ps(vmm_aux0, vmm_aux0); + + h->vcmpps(k_mask, vmm_aux0, vmm_src, _cmp_nle_us); + h->vmovups(vmm_aux3 | k_mask | h->T_z, table_val(0)); + + h->vsubps(vmm_aux0, vmm_aux0, vmm_aux3); + } else { + h->uni_vroundps(vmm_aux0, vmm_src, _op_floor); + } + + // keep fx for further computations + h->uni_vmovups(vmm_src, vmm_aux0); //vmm_src = fx + // calculation fx * ln2 + h->uni_vmulps(vmm_aux0, vmm_aux0, table_val(3)); + // x = x - fx * ln2 + h->uni_vsubps(vmm_aux1, vmm_aux1, vmm_aux0); + // y = p5 + h->uni_vmovups(vmm_aux3, table_val(22)); + // y = y * x + p4 + h->uni_vfmadd213ps(vmm_aux3, vmm_aux1, table_val(21)); + // y = y * x + p3 + h->uni_vfmadd213ps(vmm_aux3, vmm_aux1, table_val(20)); + // y = y * x + p2 + h->uni_vfmadd213ps(vmm_aux3, vmm_aux1, table_val(19)); + // y = y * x + p1 + h->uni_vfmadd213ps(vmm_aux3, vmm_aux1, table_val(0)); + // y = y * x + p0 + h->uni_vfmadd213ps(vmm_aux3, vmm_aux1, table_val(17)); + + // compute 2^(-n) + if (isa == avx512_common) { + h->vmulps(vmm_aux1, vmm_src, table_val(23)); + h->vcvtps2dq(vmm_aux1, vmm_aux1); + } else { + h->uni_vcvtps2dq(vmm_aux1, vmm_src); + h->uni_vpsignd(vmm_aux1, vmm_aux1, table_val(23)); + } + + h->uni_vpaddd(vmm_aux1, vmm_aux1, table_val(4)); + h->uni_vpslld(vmm_aux1, vmm_aux1, 23); //vmm_aux1 = 2^-fx + // calculate ln(1 + y) + h->uni_vaddps(vmm_aux3, vmm_aux3, vmm_aux1); + // x = y; y is free; keep x for further computations + h->uni_vmovups(vmm_src, vmm_aux3); + // frexp() + h->uni_vpsrld(vmm_src, vmm_src, 23); + h->uni_vcvtdq2ps(vmm_src, vmm_src); + // got n. where n is x = 2^n * y. y = 0.5 .. 1 + h->uni_vsubps(vmm_src, vmm_src, table_val(5)); + + h->uni_vandps(vmm_aux3, vmm_aux3, table_val(6)); + // got y. (mantisa) 0.5 < y < 1 + h->uni_vorps(vmm_aux3, vmm_aux3, table_val(7)); + // y = y - 1 + h->uni_vsubps(vmm_aux3, vmm_aux3, table_val(0)); + // y = p8 + h->uni_vmovups(vmm_aux1, table_val(16)); + // y = y * x + p7 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(15)); + // y = y * x + p6 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(14)); + // y = y * x + p5 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(13)); + // y = y * x + p4 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(12)); + // y = y * x + p3 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(11)); + // y = y * x + p2 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(10)); + // y = y * x + p1 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(9)); + // y = y * x + p0 ; p0 = 0 + h->uni_vfmadd213ps(vmm_aux1, vmm_aux3, table_val(8)); + //calculate ln(2) * n + h->uni_vmulps(vmm_src, vmm_src, table_val(3)); + h->uni_vaddps(vmm_aux1, vmm_aux1, vmm_src); + h->uni_vaddps(vmm_aux1, vmm_aux1, vmm_aux0); + + // get vmm_mask = src > max logf + h->uni_vmovups(vmm_mask, vmm_aux2); + if (isa == avx512_common) { + // y = (x < max log f) ? soft_relu(x) : x + h->vcmpps(k_mask, vmm_mask, table_val(24), _cmp_nle_us); + h->vblendmps(vmm_aux1 | k_mask, vmm_aux1, vmm_aux2); + } else { + // y = (x < max log f) ? soft_relu(x) : x + h->uni_vcmpgtps(vmm_mask, vmm_mask, table_val(24)); + h->uni_vblendvps(vmm_aux1, vmm_aux1, vmm_aux2, vmm_mask); + } + + h->uni_vmovups(vmm_src, vmm_aux1); +} + +template +void jit_uni_eltwise_injector_f32::logistic_compute_vector( + const Vmm &vmm_src) { + // we store the original sign and make x negative + // IMPORTANT: we assume vmm_aux0 to be xmm0, as for sse4.2 path it is required + // IMPORTANT: we use vmm_aux2 for the mask as exp_compute does not use it. + h->uni_vmovups(vmm_aux2, vmm_src); + h->uni_vandps(vmm_aux2, vmm_aux2, table_val(12)); + h->uni_vorps(vmm_src, vmm_src, table_val(12)); + + exp_compute_vector(vmm_src); + // dup exp(x) + h->uni_vmovups(vmm_aux1, vmm_src); + // (exp(x) + 1) + h->uni_vaddps(vmm_aux1, vmm_aux1, table_val(0)); + // y = exp(x) / (exp(x) + 1) + h->uni_vdivps(vmm_src, vmm_src, vmm_aux1); + + // Now we have to apply the "symmetry" based on original sign + h->uni_vmovups(vmm_aux3, table_val(0)); + h->uni_vsubps(vmm_aux3, vmm_aux3, vmm_src); + if (isa == avx512_common) { + h->vptestmd(k_mask, vmm_aux2, vmm_aux2); + h->vblendmps(vmm_aux3 | k_mask, vmm_aux3, vmm_src); + } else { + h->uni_vmovups(vmm_aux0, vmm_aux2);// The mask should be xmm0 for sse4.2 + h->uni_vblendvps(vmm_aux3, vmm_aux3, vmm_src, vmm_aux0); + } + h->uni_vmovups(vmm_src, vmm_aux3); +} + +template +void jit_uni_eltwise_injector_f32::relu_prepare_table() { + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(float2int(alpha_)); + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(0); +} + +template +void jit_uni_eltwise_injector_f32::elu_prepare_table() { + const unsigned int cvals[] = { + 0x3f800000, // [0] 1.0f + 0x3f000000, // [1] 0.5f + 0x3fb8aa3b, // [2] log2ef = 1.44269502f + 0x3f317218, // [3] ln2f = 0.69314718f + 0x0000007f, // [4] 0x7f + // exp(x) polynom + 0x3f800001, // [5] p0 = 1.0000001f + 0x3efffe85, // [6] p2 = 0.4999887f + 0x3e2aaa3e, // [7] p3 = 0.16666505f + 0x3d2bb1b1, // [8] p4 = 0.041917507f + 0x3c091ec1, // [9] p5 = 0.008369149f + 0x42b0c0a5, //[10] max logf = 88.3762589f + 0xc1766666, //[11] min logf = -14.5f + // tanh(x) constants, + 0x80000000, //[12] mask to extract sign + 0x39ddb3d7, //[13] arg below which tanh(x) = x + 0x3f0c9f54, //[14] arg below which pol approx is valid + 0x41102cb4, //[15] arg after which tanh(x) = 1 + 0xc0000000, //[16] -2.0f + 0x7fffffff, //[17] mask to make positive + // tanh pol approx + 0x3f7fffff, //[18] p0 + 0xbeaaa9cf, //[19] p1 + 0x3e085f1f, //[20] p2 + 0xbd572bda, //[21] p3 + 0x3c84fd08, //[22] p4 + }; + + for (size_t i = 0; i < sizeof(cvals) / sizeof(cvals[0]); ++i) { + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(cvals[i]); + } + + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(float2int(alpha_)); + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(0); +} + +template +void jit_uni_eltwise_injector_f32::soft_relu_prepare_table() { + const unsigned int cvals[] = { + 0x3f800000, // [0] 1.0f + 0x3f000000, // [1] 0.5f + 0x3fb8aa3b, // [2] log2ef = 1.44269502f + 0x3f317218, // [3] ln2f = 0.69314718f + 0x0000007f, // [4] 0x7f + 0x42fc0000, // [5] 126 + 0x807fffff, // [6] and with (to get 0.5 * mantissa) + 0x3f000000, // [7] or with (to get 0.5 * mantissa) + // ln(1 + x) polynomial + 0xb2b4637d, // [8] p0 = 0.0000000244f + 0x3f7fff8e, // [9] p1 = 0.9999976971f + 0xbf001759, //[10] p2 = -0.5002478215f + 0x3ea70608, //[11] p3 = 0.3272714505f + 0xbea3d7bf, //[12] p4 = -0.3153830071f + 0xbe361d04, //[13] p5 = -0.1701777461f + 0xbfa8f1e6, //[14] p6 = -1.3254635147f + 0xbfe1e812, //[15] p7 = -1.7971917960f + 0xbfc4d30e, //[16] p8 = -1.5652673123f + // exp(x) polynomial + 0x3f800001, //[17] p0 = 1.0000001f + 0x3f800000, //[18] p1 = 1.0f + 0x3efffe85, //[19] p2 = 0.4999887f + 0x3e2aaa3e, //[20] p3 = 0.16666505f + 0x3d2bb1b1, //[21] p4 = 0.041917507f + 0x3c091ec1, //[22] p5 = 0.008369149f + 0xbf800000, //[23] is required for sign changing + 0x42b0c0a5, //[24] max logf = 88.3762589f + 0xc1766666 //[25] min logf = -14.5f + }; + + for (size_t i = 0; i < sizeof(cvals) / sizeof(cvals[0]); ++i) { + for (size_t d = 0; d < vlen / sizeof(float); ++d) { + h->dd(cvals[i]); + } + } +} + +template +void jit_uni_eltwise_injector_f32::abs_prepare_table() { + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(0x7fffffff); +} + +template +void jit_uni_eltwise_injector_f32::sqrt_prepare_table() { + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(0); +} + +template +void jit_uni_eltwise_injector_f32::linear_prepare_table() { + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(float2int(alpha_)); + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(float2int(beta_)); +} + +template +void jit_uni_eltwise_injector_f32::bounded_relu_prepare_table() { + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(float2int(alpha_)); + for (size_t d = 0; d < vlen / sizeof(float); ++d) h->dd(0); +} + +template +int jit_uni_eltwise_injector_f32::aux_vecs_count(alg_kind_t alg_) { + switch (alg_) { + case alg_kind::eltwise_relu: return (alpha_ == 0.f) ? 0 : 2; + case alg_kind::eltwise_elu: return 4; + case alg_kind::eltwise_tanh: return 5; + case alg_kind::eltwise_square: return 0; + case alg_kind::eltwise_abs: return 0; + case alg_kind::eltwise_sqrt: return 2; + case alg_kind::eltwise_linear: return 1; + case alg_kind::eltwise_bounded_relu: return 0; + case alg_kind::eltwise_soft_relu: return 4; + case alg_kind::eltwise_logistic: return 4; + default: assert(!"unsupported eltwise algorithm"); + } + + return 0; +} + +template +void jit_uni_eltwise_injector_f32::compute_body(size_t start_idx, + size_t end_idx) { + using namespace alg_kind; + for (size_t idx = start_idx; idx < end_idx; idx++) { + switch (alg_) { + case eltwise_relu: + if (alpha_ == 0.f) relu_zero_ns_compute_vector(Vmm(idx)); + else relu_compute_vector(Vmm(idx)); + break; + case eltwise_elu: elu_compute_vector(Vmm(idx)); break; + case eltwise_tanh: tanh_compute_vector(Vmm(idx)); break; + case eltwise_square: square_compute_vector(Vmm(idx)); break; + case eltwise_abs: abs_compute_vector(Vmm(idx)); break; + case eltwise_sqrt: sqrt_compute_vector(Vmm(idx)); break; + case eltwise_linear: linear_compute_vector(Vmm(idx)); break; + case eltwise_bounded_relu: bounded_relu_compute_vector(Vmm(idx)); break; + case eltwise_soft_relu: soft_relu_compute_vector(Vmm(idx)); break; + case eltwise_logistic: logistic_compute_vector(Vmm(idx)); break; + default: assert(!"unsupported eltwise algorithm"); + } + } +} + +template +void jit_uni_eltwise_injector_f32::compute_vector_range(size_t start_idx, + size_t end_idx) { + assert(start_idx < end_idx && end_idx <= vecs_count); + + injector_preamble(start_idx, end_idx); + compute_body(start_idx_tail, end_idx); + injector_preamble_tail(start_idx); + compute_body(start_idx, start_idx_tail); + injector_postamble(); +} + +template +void jit_uni_eltwise_injector_f32::prepare_table(bool gen_table) { + using namespace alg_kind; + + h->align(64); + h->L(l_table); + + if (gen_table) { + switch (alg_) { + case eltwise_relu: relu_prepare_table(); break; + case eltwise_elu: + case eltwise_tanh: + case eltwise_logistic: + elu_prepare_table(); break; + case eltwise_soft_relu: soft_relu_prepare_table(); break; + case eltwise_abs: abs_prepare_table(); break; + case eltwise_sqrt: sqrt_prepare_table(); break; + case eltwise_linear: linear_prepare_table(); break; + case eltwise_bounded_relu: bounded_relu_prepare_table(); break; + case eltwise_square: break; + default: assert(!"unsupported eltwise algorithm"); + } + } +} + +template struct jit_uni_eltwise_injector_f32; +template struct jit_uni_eltwise_injector_f32; +template struct jit_uni_eltwise_injector_f32; + + +struct jit_args { + const float *from; + const float *for_comparison; + const float *to; + size_t work_amount; +}; + +struct jit_uni_eltwise_kernel_f32 : public c_compatible { + const eltwise_desc_t &desc_; + + void (*ker_)(const jit_args *); + void operator()(const jit_args *args) { assert(ker_); ker_(args); } + + jit_uni_eltwise_kernel_f32(const eltwise_desc_t &desc) + : desc_(desc), ker_(nullptr) {} + virtual ~jit_uni_eltwise_kernel_f32() {} + +protected: + bool is_bwd() const { return desc_.prop_kind == prop_kind::backward_data; } +}; + +/* jit kernels */ +namespace { + +template +struct jit_uni_relu_kernel_f32 : public jit_uni_eltwise_kernel_f32, + public jit_generator +{ + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_relu_kernel_f32) + + void compute_step(bool vectorize, const int uf, const int shift) { + for (int i = 0; i < uf; i++) { + if (vectorize) { + uni_vmovups(Vmm(i + 1), ptr[reg_from + i * shift]); + if (is_bwd()) + uni_vmovups(Vmm(uf + i + 1), + ptr[reg_for_comparison + i * shift]); + } else { + movss(Xmm(i + 1), ptr[reg_from + i * shift]); + if (is_bwd()) + movss(Xmm(uf + i + 1), + ptr[reg_for_comparison + i * shift]); + } + } + + if (isa == sse42) { + for (int i = 0; i < uf; i++) { + movups(Vmm(2 * uf + i + 1), Vmm(i + 1)); + mulps(Vmm(2 * uf + i + 1), vmm_ns); + + Vmm mask = Vmm(0); + if (is_bwd()) { + movups(mask, Vmm(uf + i + 1)); + cmpps(mask, vmm_zero, _cmp_nle_us); + } else { + movups(mask, Vmm(i + 1)); + cmpps(mask, vmm_zero, _cmp_nle_us); + } + blendvps(Vmm(2 * uf + i + 1), Vmm(i + 1)); + } + } else { + for (int i = 0; i < uf; i++) { + vmulps(Vmm(2 * uf + i + 1), Vmm(i + 1), vmm_ns); + if (isa == avx2) { + if (is_bwd()) + vcmpgtps(vmm_mask, Vmm(uf + i + 1), vmm_zero); + else + vcmpgtps(vmm_mask, Vmm(i + 1), vmm_zero); + + vblendvps(Vmm(2 * uf + i + 1), Vmm(2 * uf + i + 1), + Vmm(i + 1), vmm_mask); + + } else { + if (is_bwd()) + vcmpps(k_mask, Vmm(uf + i + 1), vmm_zero, _cmp_nle_us); + else + vcmpps(k_mask, Vmm(i + 1), vmm_zero, _cmp_nle_us); + vblendmps(Vmm(2 * uf + i + 1) | k_mask, Vmm(2 * uf + i + 1), + Vmm(i + 1)); + } + } + } + + for (int i = 0; i < uf; i++) { + if (vectorize) { + uni_vmovups(ptr[reg_to + i * shift], Vmm(2 * uf + i + 1)); + } else { + movss(ptr[reg_to + i * shift], Xmm(2 * uf + i + 1)); + } + } + } + + jit_uni_relu_kernel_f32(const eltwise_desc_t &desc) + : jit_uni_eltwise_kernel_f32(desc), jit_generator() { + assert(desc.alg_kind == alg_kind::eltwise_relu); + assert(isa == sse42 || isa == avx2 || isa == avx512_common); + + Reg64 param = abi_param1; + + const int simd_w = cpu_isa_traits::vlen / sizeof(float); + const int loop_dec[] = {simd_w, 1}; + const int uf[] = {1, 1}; + const int shift[] = {cpu_isa_traits::vlen, sizeof(float)}; + const bool loop_vectorize[] = {true, false}; + + this->preamble(); + + mov(reg_from, ptr[param + GET_OFF(from)]); + if (is_bwd()) + mov(reg_for_comparison, ptr[param + GET_OFF(for_comparison)]); + mov(reg_to, ptr[param + GET_OFF(to)]); + mov(reg_work_amount, ptr[param + GET_OFF(work_amount)]); + + mov(imm_addr64, float2int(desc.alpha)); + movq(xmm_ns, imm_addr64); + uni_vbroadcastss(vmm_ns, xmm_ns); + + uni_vpxor(vmm_zero, vmm_zero, vmm_zero); + + Label loop_label[3]; + + for (int id = 0; id < 2; id++) { + L(loop_label[id]); + cmp(reg_work_amount, uf[id] * loop_dec[id] - 1); + jle(loop_label[id + 1], T_NEAR); + + compute_step(loop_vectorize[id], uf[id], shift[id]); + + add(reg_from, uf[id] * shift[id]); + add(reg_to, uf[id] * shift[id]); + if (is_bwd()) + add(reg_for_comparison, uf[id] * shift[id]); + + sub(reg_work_amount, uf[id] * loop_dec[id]); + jmp(loop_label[id]); + } + + L(loop_label[2]); + this->postamble(); + + ker_ = (decltype(ker_))this->getCode(); + } + +private: + using Vmm = typename utils::conditional3::type; + + Reg64 reg_from = rax; + Reg64 reg_for_comparison = is_bwd() ? rdx : reg_from; + Reg64 reg_to = r8; + Reg64 reg_work_amount = rsi; + Reg64 imm_addr64 = rbx; + + Xmm xmm_ns = Xmm(14); + + Vmm vmm_ns = Vmm(isa == avx512_common ? 30 : 14); + Vmm vmm_zero = Vmm(isa == avx512_common ? 31 : 15); + + Vmm vmm_mask = Vmm(isa == avx512_common ? 28 : 12); + Opmask k_mask = Opmask(1); +}; + +template +struct jit_uni_kernel_fwd_f32: public jit_uni_eltwise_kernel_f32, + public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_kernel_fwd_f32) + + jit_uni_kernel_fwd_f32(const eltwise_desc_t &desc) + : jit_uni_eltwise_kernel_f32(desc), jit_generator() { + + eltwise_injector_ = new jit_uni_eltwise_injector_f32(this, + desc.alg_kind, desc.alpha, desc.beta, false, r9, Opmask(1)); + + using namespace alg_kind; + + assert(is_bwd() == false); + assert(utils::one_of(desc.alg_kind, eltwise_tanh, eltwise_elu, + eltwise_square, eltwise_abs, eltwise_sqrt, eltwise_linear, + eltwise_bounded_relu, eltwise_soft_relu, eltwise_logistic)); + + preamble(); + + Reg64 param = abi_param1; + mov(reg_from, ptr[param + GET_OFF(from)]); + mov(reg_to, ptr[param + GET_OFF(to)]); + mov(reg_work_amount, ptr[param + GET_OFF(work_amount)]); + eltwise_injector_->load_table_addr(); + + Label reminder_loop_start, reminder_loop_end; + Label vectorized_loop_start, vectorized_loop_end; + + cmp(reg_work_amount, simd_w); + jl(reminder_loop_start, T_NEAR); + + L(vectorized_loop_start); + + uni_vmovups(vmm_src, ptr[reg_from]); + eltwise_injector_->compute_vector(vmm_src.getIdx()); + uni_vmovups(ptr[reg_to], vmm_src); + + add(reg_from, vlen); + add(reg_to, vlen); + + sub(reg_work_amount, simd_w); + cmp(reg_work_amount, simd_w); + jge(vectorized_loop_start, T_NEAR); + + L(vectorized_loop_end); + + L(reminder_loop_start); + + cmp(reg_work_amount, 0); + jle(reminder_loop_end, T_NEAR); + + movss(xmm_src, ptr[reg_from]); + eltwise_injector_->compute_vector(xmm_src.getIdx()); + movss(ptr[reg_to], xmm_src); + + add(reg_from, sizeof(float)); + add(reg_to, sizeof(float)); + + dec(reg_work_amount); + jmp(reminder_loop_start, T_NEAR); + + L(reminder_loop_end); + + postamble(); + + eltwise_injector_->prepare_table(); + + ker_ = (decltype(ker_))this->getCode(); + } + + ~jit_uni_kernel_fwd_f32() { delete eltwise_injector_; } + +private: + using Vmm = typename utils::conditional3::type; + + const int simd_w = cpu_isa_traits::vlen / sizeof(float); + const int vlen = cpu_isa_traits::vlen; + + Reg64 reg_from = rax; + Reg64 reg_to = r8; + Reg64 reg_work_amount = rsi; + Reg64 imm_addr64 = rbx; + + Xmm xmm_src = Xmm(1); + Vmm vmm_src = Vmm(1); + + jit_uni_eltwise_injector_f32 *eltwise_injector_; +}; + +} /* namespace */ + +template +status_t jit_uni_eltwise_fwd_t::pd_t::init() { + using namespace alg_kind; + + bool ok = true + && mayiuse(isa) + && is_fwd() + && utils::everyone_is(data_type::f32, desc()->data_desc.data_type) + && !has_zero_dim_memory() + && utils::one_of(desc()->alg_kind, eltwise_relu, eltwise_tanh, + eltwise_elu, eltwise_square, eltwise_abs, eltwise_sqrt, + eltwise_linear, eltwise_bounded_relu, eltwise_soft_relu, + eltwise_logistic) + && memory_desc_wrapper(src_md()).is_dense(true) + && IMPLICATION(!memory_desc_wrapper(src_md()).is_dense(false), + math::eltwise_fwd_preserves_zero(desc()->alg_kind, true)) + && attr()->has_default_values(); + + return ok ? status::success : status::unimplemented; +} + +template +jit_uni_eltwise_fwd_t::jit_uni_eltwise_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd), kernel_(nullptr) { + const auto &desc = *pd()->desc(); + switch (desc.alg_kind) { + case alg_kind::eltwise_relu: + kernel_ = new jit_uni_relu_kernel_f32(desc); break; + default: + kernel_ = new jit_uni_kernel_fwd_f32(desc); + } +} + +template +jit_uni_eltwise_fwd_t::~jit_uni_eltwise_fwd_t() +{ delete kernel_; } + +template +void jit_uni_eltwise_fwd_t::execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper data_d(pd()->src_md()); + + const size_t nelems = data_d.nelems(true); + + src += data_d.offset0(); + dst += data_d.offset0(); + + parallel(0, [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + + const int cache_line = 16; + + balance211(utils::div_up(nelems, cache_line), nthr, ithr, start, end); + start = nstl::min(nelems, start * cache_line); + end = nstl::min(nelems, end * cache_line); + + auto arg = jit_args(); + arg.from = &src[start]; + arg.for_comparison = &src[start]; + arg.to = &dst[start]; + arg.work_amount = end - start; + if (arg.work_amount) + (*kernel_)(&arg); + }); +} + +template +status_t jit_uni_eltwise_bwd_t::pd_t::init() { + bool ok = true + && !is_fwd() + && utils::one_of(desc()->alg_kind, alg_kind::eltwise_relu) + && src_md()->data_type == data_type::f32 + && !has_zero_dim_memory() + && mayiuse(isa) + && memory_desc_wrapper(src_md()).is_dense() + && memory_desc_wrapper(diff_dst_md()) == memory_desc_wrapper(src_md()) + && attr()->has_default_values(); + + return ok ? status::success : status::unimplemented; +} + +template +jit_uni_eltwise_bwd_t::jit_uni_eltwise_bwd_t(const pd_t *apd) + : cpu_primitive_t(apd), kernel_(nullptr) { + const auto &desc = *pd()->desc(); + switch (desc.alg_kind) { + case alg_kind::eltwise_relu: + kernel_ = new jit_uni_relu_kernel_f32(desc); break; + default: assert(!"unknown eltwise alg_kind"); + } +} + +template +jit_uni_eltwise_bwd_t::~jit_uni_eltwise_bwd_t() +{ delete kernel_; } + +template +void jit_uni_eltwise_bwd_t::execute_backward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper data_d(pd()->src_md()); + const memory_desc_wrapper diff_data_d(pd()->diff_src_md()); + + const size_t nelems = data_d.nelems(); + + src += data_d.offset0(); + diff_dst += diff_data_d.offset0(); + diff_src += diff_data_d.offset0(); + + parallel(0, [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + + const int cache_line = 16; + + balance211(utils::div_up(nelems, cache_line), nthr, ithr, start, end); + start = nstl::min(nelems, start * cache_line); + end = nstl::min(nelems, end * cache_line); + + auto arg = jit_args(); + arg.from = &diff_dst[start]; + arg.to = &diff_src[start]; + arg.for_comparison = &src[start]; + arg.work_amount = end - start; + if (arg.work_amount) + (*kernel_)(&arg); + }); +} + +template struct jit_uni_eltwise_fwd_t; +template struct jit_uni_eltwise_bwd_t; +template struct jit_uni_eltwise_fwd_t; +template struct jit_uni_eltwise_bwd_t; +template struct jit_uni_eltwise_fwd_t; +template struct jit_uni_eltwise_bwd_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.hpp new file mode 100644 index 000000000000..45436b9f46e4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_eltwise.hpp @@ -0,0 +1,193 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_UNI_ELTWISE_HPP +#define CPU_JIT_UNI_ELTWISE_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_eltwise_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_uni_eltwise_injector_f32 { + using Vmm = typename utils::conditional3::type; + + jit_uni_eltwise_injector_f32(jit_generator *host, alg_kind_t alg, + float alpha, float beta, bool save_state = true, + Xbyak::Reg64 p_table = Xbyak::util::rax, + Xbyak::Opmask k_mask = Xbyak::Opmask(1)) + : alg_(alg), alpha_(alpha), beta_(beta), h(host) + , save_state_(save_state), p_table(p_table), k_mask(k_mask) + { + using namespace alg_kind; + assert(utils::one_of(isa, sse42, avx2, avx512_common)); + assert(utils::one_of(alg_, eltwise_relu, eltwise_tanh, eltwise_elu, + eltwise_square, eltwise_abs, eltwise_sqrt, eltwise_linear, + eltwise_bounded_relu, eltwise_soft_relu, eltwise_logistic)); + } + + // note that eltwise.scale is ignored + jit_uni_eltwise_injector_f32(jit_generator *host, + const post_ops_t::entry_t::eltwise_t &eltwise, + bool save_state = true, Xbyak::Reg64 p_table = Xbyak::util::rax, + Xbyak::Opmask k_mask = Xbyak::Opmask(1)) + : jit_uni_eltwise_injector_f32(host, eltwise.alg, eltwise.alpha, + eltwise.beta, save_state, p_table, k_mask) {} + + void compute_vector_range(size_t start_idx, size_t end_idx); + void compute_vector(size_t idx) { compute_vector_range(idx, idx + 1); } + void prepare_table(bool gen_table=true); + void load_table_addr() { h->mov(p_table, l_table); } + + const alg_kind_t alg_; + const float alpha_; + const float beta_; + + jit_generator * const h; + + const bool save_state_; + const Xbyak::Reg64 p_table; + const Xbyak::Opmask k_mask; + Xbyak::Label l_table; + +private: + // if only the injector was inherited from jit_generator... + enum { + _cmp_le_os = jit_generator::_cmp_le_os, + _cmp_nle_us = jit_generator::_cmp_nle_us, + _op_floor = jit_generator::_op_floor, + }; + + size_t vlen = cpu_isa_traits::vlen; + + const static size_t preserved_vecs_max = 5; + + size_t vecs_to_preserve = 0; + size_t vecs_count = isa == avx512_common ? 32 : 16; + size_t preserved_vecs_count = 0; + size_t preserved_vec_idxs[preserved_vecs_max] = {0}; + size_t start_idx_tail = 0; + + Vmm vmm_mask, vmm_aux0, vmm_aux1, vmm_aux2, vmm_aux3, vmm_aux4; + + Xbyak::Address table_val(int index) + { return h->ptr[p_table + index * vlen]; } + + int aux_vecs_count(alg_kind_t alg); + + void compute_body(size_t start_idx, size_t end_idx); + void injector_preamble(size_t start_idx, size_t end_idx); + void injector_preamble_tail(size_t start_idx); + void injector_postamble(); + void assign_regs(); + + void exp_compute_vector(const Vmm &vmm_src); + void relu_compute_vector(const Vmm &vmm_src); + void relu_zero_ns_compute_vector(const Vmm &vmm_src); + void elu_compute_vector(const Vmm &vmm_src); + void tanh_compute_vector(const Vmm &vmm_src); + void square_compute_vector(const Vmm &vmm_src); + void abs_compute_vector(const Vmm &vmm_src); + void sqrt_compute_vector(const Vmm &vmm_src); + void linear_compute_vector(const Vmm &vmm_src); + void bounded_relu_compute_vector(const Vmm &vmm_src); + void soft_relu_compute_vector(const Vmm &vmm_src); + void logistic_compute_vector(const Vmm &vmm_src); + + void relu_prepare_table(); + void elu_prepare_table(); + void soft_relu_prepare_table(); + void abs_prepare_table(); + void sqrt_prepare_table(); + void linear_prepare_table(); + void bounded_relu_prepare_table(); +}; + +struct jit_uni_eltwise_kernel_f32; + +template +struct jit_uni_eltwise_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_eltwise_fwd_pd_t { + using cpu_eltwise_fwd_pd_t::cpu_eltwise_fwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_eltwise_fwd_t); + + status_t init(); + }; + + jit_uni_eltwise_fwd_t(const pd_t *apd); + ~jit_uni_eltwise_fwd_t(); + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_uni_eltwise_kernel_f32 *kernel_; +}; + +template +struct jit_uni_eltwise_bwd_t : public cpu_primitive_t { + struct pd_t : public cpu_eltwise_bwd_pd_t { + using cpu_eltwise_bwd_pd_t::cpu_eltwise_bwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_eltwise_bwd_t); + + status_t init(); + }; + + jit_uni_eltwise_bwd_t(const pd_t *apd); + ~jit_uni_eltwise_bwd_t(); + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_uni_eltwise_kernel_f32 *kernel_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.cpp new file mode 100644 index 000000000000..a3ca6273a0b1 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.cpp @@ -0,0 +1,949 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "jit_uni_i8i8_pooling.hpp" + +#include + +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::types; +using namespace alg_kind; + +template +struct jit_uni_i8i8_pooling_fwd_ker_t: public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_i8i8_pooling_fwd_ker_t) + + struct call_params_t { + const char *src_i8; + const char *dst_i8; + size_t kw_range; + size_t kh_range; + float idivider; + }; + + using Vmm = typename cpu_isa_traits::Vmm; + Xmm xreg(int idx) const { return Xmm(idx); } + Ymm yreg(int idx) const { return Ymm(xreg(idx).getIdx()); } + Vmm vreg(int idx) const { return Vmm(xreg(idx).getIdx()); } + + // In case of avx2 with data type i8 we need to use + // maskmovdqu instruction which has its destination hardcoded in rdi. + // Windows ABI: abi_param1 is rcx - nothing to do else + // Unix ABI: abi_param1 is rdi - copy it to rcx and use it as abi_param1 + Reg64 reg_param = rcx; // Our "unified abi_param1" + Reg64 reg_ptr_src_i8 = r8; + Reg64 reg_ptr_dst_i8 = r9; + Reg64 reg_ptr_maskmovdqu_dst = rdi; // store destination - must be rdi + + Reg64 ki = r10; + Reg64 kj = r11; + Reg64 reg_kw = r12; + Reg64 reg_kh = r13; + Reg64 c_iter = r14; + + Reg64 aux_reg_src_h = rax; + Reg64 aux_reg_src_w = rbx; + + Reg64 reg_tmp = rdx; + + Reg64 reg_mask = r15; + + Opmask k_cmp_mask = Opmask(7); + + Opmask mask(int idx) { + return Opmask(6 - idx); + } + + // ref to any of XYZ-regs via xreg/yreg/vreg functions + Xmm xmm_tmp = xreg(0); // temp to init vreg_tmp + Vmm vreg_tmp = vreg(0); // max pooling : holds minimum values for data_type + Vmm vreg_zeros = vreg(1); + + // only in case of == avx2 + Vmm vreg_mask = vreg(2); // full byte-mask + Xmm xreg_mask_lo = xreg(2); // low 128-bits part of byte-mask (alias for xmm part of vreg_mask) + Xmm xreg_mask_hi = xreg(3); // "max" - high 128-bits part of byte-mask (stored separately) + Xmm xreg_mask_q = xreg(3); // "avg" - 1/4 part of the mask for s8/u8 operations + Vmm vreg_mask_q = vreg(3); // "avg" - 1/4 part for non-zero tails + + enum:int {vidx_base = isa == avx2 ? 4 : 2}; + Vmm base_vr(int idx) const { return vreg(vidx_base + idx); } + + size_t sizeof_src_dt() const { return data_type_size(jpp.src_dt); } + size_t sizeof_dst_dt() const { return data_type_size(jpp.dst_dt); } + + /* max pooling */ + Vmm vreg_src(int idx) const { return base_vr(idx); } // [0 .. ur_c-1] + Vmm vreg_dst(int idx) const { return base_vr(jpp.ur_c + idx); } // [ur_c .. 2*ur_c-1] + + /* avg pooling */ + // s32 used for processing of s8/u8 data + // thus we need to take into account ratio of sizes s32/i8 = 4 + static constexpr data_type_t avg_proc_dt = data_type::s32; + enum:int { + s32_to_i8_ratio = sizeof(typename prec_traits::type) + / sizeof(typename prec_traits::type), + max_num_ll = s32_to_i8_ratio + }; + Vmm vreg_src_s32(int jj, int ll) { return base_vr(3*max_num_ll*jj + ll + 0*max_num_ll); } // ll: 0..4 [0..3] + Vmm vreg_dst_s32(int jj, int ll) { return base_vr(3*max_num_ll*jj + ll + 1*max_num_ll); } // ll: 0..4 [4..7] + Vmm vreg_dst_f32(int jj, int ll) { return base_vr(3*max_num_ll*jj + ll + 2*max_num_ll); } // ll: 0..4 [8..11] + + void (*ker_)(const call_params_t *); + jit_pool_conf_t jpp; + + void init_tmp_reg(); + void init_mask(); + + void load_vreg_mask_q(int ll) {}; + + void load_src_max_op(int jj, int ll, size_t offset, bool masked, uint64_t msk); + void load_src_avg_op(int jj, int ll, size_t offset, bool masked, uint64_t msk); + void load_src(int jj, int ll, int c_tail); + + void store_dst_max_op(int jj, int ll, size_t offset, bool masked, uint64_t msk); + void store_dst_avg_op(int jj, int ll, size_t offset, bool masked, uint64_t msk); + void store_dst(int jj, int ll, int c_tail); + + void compute_avg_step(int ur_c, int c_tail); + void compute_max_op(const int jj); + void compute_max_step(int ur_c, int c_tail); + void compute_step(int ur_c, int c_tail); + + void compute_c_block(); + void generate(); + + static status_t init_conf(jit_pool_conf_t &jpp, const pooling_pd_t *ppd); + + jit_uni_i8i8_pooling_fwd_ker_t(const jit_pool_conf_t &jpp_) + : jpp(jpp_) { + generate(); + ker_ = reinterpret_cast(const_cast( + getCode())); + } +}; + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::load_vreg_mask_q(int ll) { + + // extract ll-th part of mask (ll-th QWORD) + vpblendd(vreg_mask_q, vreg_zeros, vreg_mask, 0x3 << ll); // 0x3 - mask for 2 x DWORD + + // Move mask from ll-th pos to 0-th pos + if (ll>0) + vpermq(vreg_mask_q, vreg_mask_q, ll); +}; + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::load_src_max_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + if (masked) { + if (jpp.src_dt == s32) { + vpblendd(vreg_src(jj), vreg_tmp, ptr[aux_reg_src_w + offset], static_cast(msk)); + } else { + vpblendvb(vreg_src(jj), vreg_tmp, ptr[aux_reg_src_w + offset], vreg_mask); + } + } else + vmovups(vreg_src(jj), ptr[aux_reg_src_w + offset]); +}; + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::load_src_max_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + if (masked) { + if (jpp.src_dt == s32) + vmovups(vreg_src(jj) | mask(0), ptr[aux_reg_src_w + offset]); + else + vmovdqu8(vreg_src(jj) | mask(0), ptr[aux_reg_src_w + offset]); + } else + vmovups(vreg_src(jj), ptr[aux_reg_src_w + offset]); +}; + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::load_src_avg_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + // Don't generate useless code + if (masked && !msk) + return; + + auto load_i8 = [&](bool is_signed, const Vmm& vr_src) { + + // Need to use mask of tail? + if (masked) { + + // load ll-th part of mask into vreg_mask_q + load_vreg_mask_q(ll); + + // Load by mask from mem into register vr_src + vpblendvb(vr_src, vreg_zeros, ptr[aux_reg_src_w + offset], vreg_mask_q); + + // Conversion s8/u8 -> s32 + if (is_signed) + vpmovsxbd(vr_src, vr_src); + else + vpmovzxbd(vr_src, vr_src); + } else { + + // Load from mem into vr_src with conversion + if (is_signed) + vpmovsxbd(vr_src, ptr[aux_reg_src_w + offset]); + else + vpmovzxbd(vr_src, ptr[aux_reg_src_w + offset]); + } + }; + + switch (jpp.src_dt) { + case s32: + if (masked) + vpblendd(vreg_src_s32(jj, ll), vreg_zeros, ptr[aux_reg_src_w + offset], + static_cast(msk)); + else + vmovups(vreg_src_s32(jj, ll), ptr[aux_reg_src_w + offset]); + break; + case s8: + load_i8(true, vreg_src_s32(jj, ll)); + break; + case u8: + load_i8(false, vreg_src_s32(jj, ll)); + break; + default: assert(!"unsupported src data type"); + } +}; + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::load_src_avg_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + // Don't generate useless code + if (masked && !msk) + return; + + const Vmm& vr_src = masked ? + vreg_src_s32(jj, ll) | mask(ll) : + vreg_src_s32(jj, ll); + + switch (jpp.src_dt) { + case s32: + vmovups(vr_src, ptr[aux_reg_src_w + offset]); + break; + case s8: + vpmovsxbd(vr_src, ptr[aux_reg_src_w + offset]); + break; + case u8: + vpmovzxbd(vr_src, ptr[aux_reg_src_w + offset]); + break; + default: assert(!"unsupported src data type"); + } +}; + +template +void jit_uni_i8i8_pooling_fwd_ker_t::load_src(int jj, int ll, int c_tail) { + using namespace data_type; + + int c_block = jpp.c_block; + int ur_c = jpp.ur_c; + + switch (jpp.alg) { + case pooling_max: { + auto offset = jj*c_block*sizeof_src_dt(); + bool masked = jj == ur_c - 1 && c_tail; + load_src_max_op(jj, ll, offset, masked, jpp.tail[0]); + break; + } + case pooling_avg_include_padding: + case pooling_avg_exclude_padding: { + auto offset = (ll*(c_block/max_num_ll) + jj*c_block)*sizeof_src_dt(); + bool masked = jj == ur_c - 1 && c_tail; + load_src_avg_op(jj, ll, offset, masked, jpp.tail[ll]); + break; + } + default: assert(!"unsupported algorithm"); + } +} + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::store_dst_max_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + int c_block = jpp.c_block; + + if (masked) { + switch (jpp.src_dt) { + case s32: + vpmaskmovd(ptr[reg_ptr_dst_i8 + offset], vreg_mask, vreg_dst(jj)); + break; + case s8: + case u8: { + // Store low half by mask (bytes 0...15) + lea(reg_ptr_maskmovdqu_dst, ptr[reg_ptr_dst_i8 + offset]); + maskmovdqu(vreg_dst(jj), xreg_mask_lo); + + // Do we need to store high half (bytes 16...31) ? + const uint64_t low_mask = (1ULL << (c_block/2))-1; + if (msk & ~low_mask) { + vextracti128(Xmm(vreg_dst(jj).getIdx()), vreg_dst(jj), 1); + add(reg_ptr_maskmovdqu_dst, c_block / 2); + maskmovdqu(vreg_dst(jj), xreg_mask_hi); + } + } break; + default: assert(!"unsupported src data type"); + } + } else + vmovups(ptr[reg_ptr_dst_i8 + offset], vreg_dst(jj)); +} + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::store_dst_max_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + if (masked) { + switch (jpp.src_dt) { + case s32: + vmovups(ptr[reg_ptr_dst_i8 + offset], vreg_dst(jj) | mask(0)); + break; + case s8: + case u8: + vmovdqu8(ptr[reg_ptr_dst_i8 + offset], vreg_dst(jj) | mask(0)); + break; + default: assert(!"unsupported src data type"); + } + } else + vmovups(ptr[reg_ptr_dst_i8 + offset], vreg_dst(jj)); +} + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::store_dst_avg_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk){ + using namespace data_type; + + // Don't generate useless code + if (masked && !msk) + return; + + auto s32_to_i8 = [&](bool is_signed, const Vmm& vr_dst) { + + // conversion: s32 -> s16/u16 : {8 x s32}{8 x 0} -> {16 x s16/u16} + // Result QWORDs (qw0, qw1) permuted: {qw0, 0, qw1, 0} + if (is_signed) + vpackssdw(vr_dst, vr_dst, vreg_zeros); + else + vpackusdw(vr_dst, vr_dst, vreg_zeros); + + // Permute qwords to restore original order + // {qw0, 0, qw1, 0} -> {qw0, qw1, 0, 0} + vpermq(vr_dst, vr_dst, 0x58); + + // conversion: s16/u16 -> s8/u8 : {16 x s16/u16}{16 x 0} -> {32 x s8/u8} + // Target QWORD qw = {8 x s8/u8} has proper position: {qw, xx, xx, xx} + if (is_signed) + vpacksswb(vr_dst, vr_dst, vreg_zeros); + else + vpackuswb(vr_dst, vr_dst, vreg_zeros); + + }; + + auto store_i8 = [&](bool is_signed, bool is_masked, const Vmm& vr_dst) { + + // Conversion s32 -> s8/u8 + s32_to_i8(is_signed, vr_dst); + + // Need to use mask of tail? + if (is_masked) { + // load ll-th part of mask into vreg_mask_q + load_vreg_mask_q(ll); + } + + // store 8 bytes + lea(reg_ptr_maskmovdqu_dst, ptr[reg_ptr_dst_i8 + offset]); + maskmovdqu(vr_dst, xreg_mask_q); + }; + + switch (jpp.dst_dt) { + case s32: + if (masked) { + vpmaskmovd(ptr[reg_ptr_dst_i8 + offset], vreg_mask, vreg_dst_s32(jj, ll)); + } else + vmovups(ptr[reg_ptr_dst_i8 + offset], vreg_dst_s32(jj, ll)); + break; + case s8: + store_i8(true, masked, vreg_dst_s32(jj, ll)); + break; + case u8: + store_i8(false, masked, vreg_dst_s32(jj, ll)); + break; + default: assert(!"unsuppotred dst data_type"); + } +} + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::store_dst_avg_op(int jj, int ll, + size_t offset, bool masked, uint64_t msk) { + using namespace data_type; + + // Don't generate useless code + if (masked && !msk) + return; + + const Vmm& vr_dst = masked ? + vreg_dst_s32(jj, ll) | mask(ll) : + vreg_dst_s32(jj, ll); + + switch (jpp.dst_dt) { + case s32: + vmovups(ptr[reg_ptr_dst_i8 + offset], vr_dst); + break; + case s8: + vpmovdb(ptr[reg_ptr_dst_i8 + offset], vr_dst); + break; + case u8: + vpmovusdb(ptr[reg_ptr_dst_i8 + offset], vr_dst); + break; + default: assert(!"unsupported dst data_type"); + } +} + + +template +void jit_uni_i8i8_pooling_fwd_ker_t::store_dst(int jj, int ll, + int c_tail) { + using namespace data_type; + + int c_block = jpp.c_block; + int ur_c = jpp.ur_c; + + switch(jpp.alg) { + case pooling_max: { + auto offset = jj*c_block*sizeof_dst_dt(); + bool masked = jj == ur_c - 1 && c_tail; + store_dst_max_op(jj, ll, offset, masked, jpp.tail[ll]); + break; + } + case pooling_avg_include_padding: + case pooling_avg_exclude_padding: { + auto offset = (ll*(c_block/max_num_ll) + jj*c_block)*sizeof_dst_dt(); + bool masked = jj == ur_c - 1 && c_tail; + store_dst_avg_op(jj, ll, offset, masked, jpp.tail[ll]); + break; + } + default: assert(!"unsupported pooling algorithm"); + } +} + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::compute_max_op(const int jj) +{ + using namespace data_type; + switch (jpp.src_dt) { + case s32: + vpmaxsd(vreg_dst(jj), vreg_dst(jj), vreg_src(jj)); + break; + case s8: + vpmaxsb(vreg_dst(jj), vreg_dst(jj), vreg_src(jj)); + break; + case u8: + vpmaxub(vreg_dst(jj), vreg_dst(jj), vreg_src(jj)); + break; + default: assert(!"unsupported src data type"); + } +} + +template <> +void jit_uni_i8i8_pooling_fwd_ker_t::compute_max_op(const int jj) +{ + using namespace data_type; + + // Compare + switch (jpp.src_dt) { + case s32: + vpcmpd(k_cmp_mask, vreg_dst(jj), vreg_src(jj), _cmp_lt_os); + break; + case s8: + vpcmpb(k_cmp_mask, vreg_dst(jj), vreg_src(jj), _cmp_lt_os); + break; + case u8: + vpcmpub(k_cmp_mask, vreg_dst(jj), vreg_src(jj), _cmp_lt_os); + break; + default: assert(!"unsupported src data type"); + } + + // move max values into vreg_dst + if (jpp.src_dt == s32) + vpblendmd(vreg_dst(jj) | k_cmp_mask, vreg_dst(jj), vreg_src(jj)); + else + vpblendmb(vreg_dst(jj) | k_cmp_mask, vreg_dst(jj), vreg_src(jj)); +} + + +template +void jit_uni_i8i8_pooling_fwd_ker_t::compute_max_step(int ur_c, int c_tail) +{ + Label l_kw, l_kh; + + int iw = jpp.iw; + int c = jpp.c; + + for (int jj = 0; jj < ur_c; jj++) + vmovups(vreg_dst(jj), vreg_tmp); + + mov(aux_reg_src_h, reg_ptr_src_i8); + + xor_(kj, kj); + L(l_kh); + { + mov(aux_reg_src_w, aux_reg_src_h); + xor_(ki, ki); + L(l_kw); + { + for (int jj = 0; jj < ur_c; jj++) { + load_src(jj, 0, c_tail); + compute_max_op(jj); + } + add(aux_reg_src_w, c * sizeof_src_dt()); + inc(ki); + cmp(ki, reg_kw); + jl(l_kw, T_NEAR); + } + add(aux_reg_src_h, iw * c * sizeof_src_dt()); + inc(kj); + cmp(kj, reg_kh); + jl(l_kh, T_NEAR); + } + + for (int jj = 0; jj < ur_c; jj++) + store_dst(jj, 0, c_tail); +} + +template +void jit_uni_i8i8_pooling_fwd_ker_t::compute_avg_step(int ur_c, int c_tail) +{ + using namespace data_type; + + Label l_kw, l_kh; + + int iw = jpp.iw; + int c = jpp.c; + + const int num_ll = data_type_size(avg_proc_dt)/data_type_size(jpp.src_dt); + + for (int jj = 0; jj < ur_c; jj++) { + for (int ll = 0; ll < num_ll; ll++) { + bool masked = jj == ur_c - 1 && c_tail; + size_t msk = jpp.tail[ll]; + if (!(masked && !msk)) { + uni_vpxor(vreg_src_s32(jj, ll), vreg_src_s32(jj, ll), vreg_src_s32(jj, ll)); + uni_vpxor(vreg_dst_s32(jj, ll), vreg_dst_s32(jj, ll), vreg_dst_s32(jj, ll)); + } + } + } + + mov(aux_reg_src_h, reg_ptr_src_i8); + + xor_(kj, kj); + L(l_kh); + { + mov(aux_reg_src_w, aux_reg_src_h); + xor_(ki, ki); + L(l_kw); + { + for (int jj = 0; jj < ur_c; jj++) { + for (int ll = 0; ll < num_ll; ll++) { + bool masked = jj == ur_c - 1 && c_tail; + size_t msk = jpp.tail[ll]; + if (!(masked && !msk)) { + load_src(jj, ll, c_tail); + vpaddd(vreg_dst_s32(jj, ll), vreg_dst_s32(jj, ll), + vreg_src_s32(jj, ll)); + } + } + } + add(aux_reg_src_w, c * sizeof_src_dt()); + inc(ki); + cmp(ki, reg_kw); + jl(l_kw, T_NEAR); + } + add(aux_reg_src_h, iw * c * sizeof_src_dt()); + inc(kj); + cmp(kj, reg_kh); + jl(l_kh, T_NEAR); + } + + for (int jj = 0; jj < ur_c; jj++) { + for (int ll = 0; ll < num_ll; ll++) { + bool masked = jj == ur_c - 1 && c_tail; + size_t msk = jpp.tail[ll]; + if (!(masked && !msk)) { + vcvtdq2ps(vreg_dst_f32(jj, ll), vreg_dst_s32(jj, ll)); + vfmadd132ps(vreg_dst_f32(jj, ll), vreg_zeros, vreg_tmp); + vcvtps2dq(vreg_dst_s32(jj, ll), vreg_dst_f32(jj, ll)); + store_dst(jj, ll, c_tail); + } + } + } +} + +template +void jit_uni_i8i8_pooling_fwd_ker_t::compute_step(int ur_c, int c_tail) { + switch (jpp.alg) { + case pooling_max: + compute_max_step(ur_c, c_tail); break; + case pooling_avg_include_padding: + case pooling_avg_exclude_padding: + compute_avg_step(ur_c, c_tail); break; + default: assert(!"unsupported pooling algorithm"); + } +} + +template +void jit_uni_i8i8_pooling_fwd_ker_t::compute_c_block(){ + Label l_main_loop; + + int nb_c = jpp.nb_c; + int c_block = jpp.c_block; + int ur_c = jpp.ur_c; + int ur_c_tail = jpp.ur_c_tail; + int c_steps = nb_c / ur_c; + int c_tail = jpp.c_tail; + + xor_(c_iter, c_iter); + if (c_steps > 0) { + L(l_main_loop); { + compute_step(ur_c, 0); + add(reg_ptr_src_i8, ur_c*c_block*sizeof_src_dt()); + add(reg_ptr_dst_i8, ur_c*c_block*sizeof_dst_dt()); + inc(c_iter); + cmp(c_iter, c_steps); + jl(l_main_loop, T_NEAR); + } + } + + if (ur_c_tail != 0) { + compute_step(ur_c_tail, c_tail); + } +} + +template<> +void jit_uni_i8i8_pooling_fwd_ker_t::init_mask() { + using namespace data_type; + using cpu_isa = cpu_isa_traits; + + // AVX2 mask initialization: mask stored in Ymm-regs + auto init = [&](uint64_t bit_mask, bool init_mask_q) { + const size_t QW_PER_VREG = cpu_isa::vlen / sizeof(uint64_t); + + uint64_t vmask[QW_PER_VREG]; + for (size_t i = 0; i < QW_PER_VREG; i++){ + + uint64_t qw_vmask=0ULL; + const size_t DBITS = 8*sizeof_src_dt(); + const uint64_t VMSK = 1ULL << (DBITS-1); + const size_t D_PER_QW = (8*sizeof(qw_vmask))/DBITS; + for (size_t j = 0; j < D_PER_QW; j++) { + if (bit_mask & 1) + qw_vmask |= VMSK << DBITS * j; + bit_mask >>= 1; + } + vmask[i] = qw_vmask; + } + + // Put QWORDS with target mask into xmm regs + const int xdst_i[QW_PER_VREG] = { + xreg_mask_lo.getIdx(), + xreg_mask_lo.getIdx(), + xreg_mask_hi.getIdx(), + xreg_mask_hi.getIdx() + }; + const int xsrc_i[QW_PER_VREG] = { + vreg_zeros.getIdx(), // 0-th qword insert in zeros -> {qw0, 0} + xreg_mask_lo.getIdx(), // 1-st and 0-th merge -> {qw0,qw1} + vreg_zeros.getIdx(), + xreg_mask_hi.getIdx() + }; + const uint8 qw_dst_idx[QW_PER_VREG] = {0, 1, 0, 1}; // qword index in 128-bit xreg + + for (size_t i = 0; i < QW_PER_VREG; i++) { + mov(reg_mask, vmask[i]); + vpinsrq(Xmm(xdst_i[i]), Xmm(xsrc_i[i]), reg_mask, qw_dst_idx[i]); + } + + // Merge Low (xreg_mask_lo alias for vreg_mask.xreg) + // and High (xreg_mask_hi) into full vreg_mask + // vreg_mask -> {xreg_mask_hi, vreg_mask.xreg} + vinserti128(vreg_mask, vreg_mask, xreg_mask_hi, 1); + + // Keep only low qword of mask in xreg_mask_q + if (init_mask_q) { + mov(reg_mask, vmask[0]); + vpinsrq(xreg_mask_q, Xmm(vreg_zeros.getIdx()), reg_mask, 0); + } + }; + + uint64_t tail_mask = (1ULL << jpp.c_tail) - 1; + switch (jpp.alg) { + case pooling_max: + // For "max" we need mask only in case of non-zero tail + if (tail_mask) + init(tail_mask, false); + break; + case pooling_avg_include_padding: + case pooling_avg_exclude_padding: + // For "avg" we need mask: + // - s32 - in case of the non-zero tail + // - s8/u8 - irrespective of the tail + switch (jpp.src_dt) { + case s32: + if (tail_mask) + init(tail_mask, false); + break; + case s8: + case u8: + init(tail_mask ? tail_mask : ~0ULL, tail_mask == 0); + break; + default: assert(!"unsupported src data type"); + } + break; + default: assert(!"unsupported pooling algorithm"); + } +} + +template<> +void jit_uni_i8i8_pooling_fwd_ker_t::init_mask() { + + for (int ll = 0; ll < max_num_ll; ll++) { + mov(reg_mask, jpp.tail[ll]); + kmovq(mask(ll), reg_mask); + } +} + +template +void jit_uni_i8i8_pooling_fwd_ker_t::init_tmp_reg() { + using namespace data_type; + + switch (jpp.alg) { + case pooling_avg_include_padding: + case pooling_avg_exclude_padding: + mov(reg_tmp, ptr[reg_param + offsetof(call_params_t, idivider)]); + movq(xmm_tmp, reg_tmp); + vpbroadcastd(vreg_tmp, xmm_tmp); + break; + case pooling_max: + switch (jpp.src_dt) { + case s32: + mov(reg_tmp, nstl::numeric_limits::lowest()); + break; + case s8: + mov(reg_tmp, nstl::numeric_limits::lowest()); + break; + case u8: + mov(reg_tmp, nstl::numeric_limits::lowest()); + break; + default: assert(!"unsupported src data_type"); + } + + movq(xmm_tmp, reg_tmp); + if (jpp.src_dt == s32) + vpbroadcastd(vreg_tmp, xmm_tmp); + else + vpbroadcastb(vreg_tmp, xmm_tmp); + break; + default: assert(!"unsupported pooling algorithm"); + } + +} + +template +void jit_uni_i8i8_pooling_fwd_ker_t::generate() { + preamble(); + +#if !defined(_WIN32) + // Always use rcx as abi_param1 - + // see the note about maskmovdqu near reg_param. + mov(rcx, rdi); +#endif + +# define READ_PARAM(reg, field) \ + mov(reg, ptr[reg_param + offsetof(call_params_t, field)]) + READ_PARAM(reg_ptr_src_i8, src_i8); + READ_PARAM(reg_ptr_dst_i8, dst_i8); + READ_PARAM(reg_kw, kw_range); + READ_PARAM(reg_kh, kh_range); + +# undef READ_PARAM + + uni_vpxor(vreg_zeros, vreg_zeros, vreg_zeros); + + init_mask(); + + init_tmp_reg(); + + compute_c_block(); + + postamble(); +} + +template +status_t jit_uni_i8i8_pooling_fwd_ker_t::init_conf(jit_pool_conf_t &jpp, + const pooling_pd_t *ppd) { + if (!mayiuse(isa)) + return status::unimplemented; + + const auto &pd = *ppd->desc(); + const memory_desc_wrapper src_d(ppd->src_md()); + const memory_desc_wrapper dst_d(ppd->dst_md()); + + jpp.mb = src_d.dims()[0]; + jpp.c = src_d.dims()[1]; + jpp.ih = src_d.dims()[2]; + jpp.iw = src_d.dims()[3]; + jpp.oh = dst_d.dims()[2]; + jpp.ow = dst_d.dims()[3]; + + jpp.stride_h = pd.strides[0]; + jpp.stride_w = pd.strides[1]; + jpp.kh = pd.kernel[0]; + jpp.kw = pd.kernel[1]; + + jpp.t_pad = pd.padding[0][0]; + jpp.l_pad = pd.padding[0][1]; + + jpp.alg = pd.alg_kind; + + jpp.src_dt = pd.src_desc.data_type; + jpp.dst_dt = pd.dst_desc.data_type; + + // data_type items per one vreg on the + // isa == avx2 : 32 bytes -> 32 for s8/u8, 8 for s32 + // isa == avx512* : 64 bytes -> 64 for s8/u8, 16 for s32 + int simd_w = cpu_isa_traits::vlen / data_type_size(jpp.src_dt); + + jpp.c_block = simd_w; + jpp.c_tail = jpp.c % jpp.c_block; + jpp.nb_c = jpp.c / jpp.c_block; + jpp.ur_c = 1; + jpp.ur_c_tail = jpp.nb_c - (jpp.nb_c / jpp.ur_c)*jpp.ur_c + + (jpp.c_tail != 0); + + size_t tail_mask = (1ULL << jpp.c_tail) - 1; + + switch (jpp.alg) { + case pooling_max: + jpp.tail[0] = tail_mask; + jpp.tail[1] = 0; + jpp.tail[2] = 0; + jpp.tail[3] = 0; + break; + case pooling_avg_include_padding: + case pooling_avg_exclude_padding: { + // avg_proc_dt (s32) defines granularity (because u8/s8 processed as s32) + // avx2 : 8, avx512 : 16 + const size_t msk_gran = cpu_isa_traits::vlen / data_type_size(avg_proc_dt); + const size_t msk_msk = (1ULL << msk_gran) - 1; + size_t m = tail_mask; + for (size_t ll = 0; ll < max_num_ll; ll++) { + jpp.tail[ll] = m & msk_msk; + m = m >> msk_gran; + } + break; + } + default: return status::unimplemented; + } + + return status::success; +} + +template +status_t jit_uni_i8i8_pooling_fwd_t::pd_t::jit_conf() { + return jit_uni_i8i8_pooling_fwd_ker_t::init_conf(jpp_, this); +} + +template +jit_uni_i8i8_pooling_fwd_t:: +jit_uni_i8i8_pooling_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd), ker_(nullptr) +{ ker_ = new jit_uni_i8i8_pooling_fwd_ker_t(pd()->jpp_); } + +template +jit_uni_i8i8_pooling_fwd_t:: +~jit_uni_i8i8_pooling_fwd_t() { delete ker_; } + +template +void jit_uni_i8i8_pooling_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + auto src_i8 = CTX_IN_MEM(const char *, MKLDNN_ARG_SRC); + auto dst_i8 = CTX_OUT_MEM(char *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + + const auto &jpp = pd()->jpp_; + + parallel_nd(jpp.mb, jpp.oh, jpp.ow, + [&](int n, int oh, int ow) { + const int ih = nstl::max(oh*jpp.stride_h - jpp.t_pad, 0); + const int iw = nstl::max(ow*jpp.stride_w - jpp.l_pad, 0); + + const int kh_start = nstl::max(0, jpp.t_pad - oh * jpp.stride_h); + const int kh_end = nstl::min(jpp.kh, + jpp.ih + jpp.t_pad - oh * jpp.stride_h); + const int kw_start = nstl::max(0, jpp.l_pad - ow * jpp.stride_w); + const int kw_end = nstl::min(jpp.kw, + jpp.iw + jpp.l_pad - ow * jpp.stride_w); + + auto p = typename jit_uni_i8i8_pooling_fwd_ker_t::call_params_t(); + p.src_i8 = &src_i8[ + src_d.blk_off(n, 0, ih, iw) * src_d.data_type_size()]; + p.dst_i8 = &dst_i8[ + dst_d.blk_off(n, 0, oh, ow) * dst_d.data_type_size()]; + p.kw_range = (size_t)(kw_end - kw_start); + p.kh_range = (size_t)(kh_end - kh_start); + p.idivider = 1.0f / ((jpp.alg == pooling_avg_exclude_padding) ? + p.kh_range*p.kw_range : jpp.kw*jpp.kh); + + ker_->ker_(&p); + }); +} + +// Explicit instantiation only for supported values. +// +template struct jit_uni_i8i8_pooling_fwd_ker_t; +template struct jit_uni_i8i8_pooling_fwd_t; + +template struct jit_uni_i8i8_pooling_fwd_ker_t; +template struct jit_uni_i8i8_pooling_fwd_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.hpp new file mode 100644 index 000000000000..d757679df5e4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_i8i8_pooling.hpp @@ -0,0 +1,89 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_UNI_I8I8_POOLING_HPP +#define CPU_JIT_UNI_I8I8_POOLING_HPP + +#include "c_types_map.hpp" + +#include "cpu_pooling_pd.hpp" +#include "cpu_primitive.hpp" + +#include "cpu_isa_traits.hpp" +#include "jit_primitive_conf.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_uni_i8i8_pooling_fwd_ker_t; + +template +struct jit_uni_i8i8_pooling_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_pooling_fwd_pd_t { + using cpu_pooling_fwd_pd_t::cpu_pooling_fwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_i8i8_pooling_fwd_t); + + status_t init() { + bool ok = true + && mayiuse(isa) + && ndims() == 4 + && set_default_params() == status::success + && desc()->prop_kind == prop_kind::forward_inference + && utils::one_of(desc()->alg_kind, alg_kind::pooling_max, + alg_kind::pooling_avg_include_padding, + alg_kind::pooling_avg_exclude_padding) + && utils::one_of(src_md()->data_type, data_type::s32, + data_type::s8, data_type::u8) + && src_md()->data_type == dst_md()->data_type + && attr()->has_default_values() + && memory_desc_matches_tag(*src_md(), format_tag::nhwc) + && memory_desc_matches_tag(*dst_md(), format_tag::nhwc); + if (!ok) return status::unimplemented; + + return jit_conf(); + } + + jit_pool_conf_t jpp_; + + protected: + status_t jit_conf(); + }; + + jit_uni_i8i8_pooling_fwd_t(const pd_t *apd); + ~jit_uni_i8i8_pooling_fwd_t(); + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_uni_i8i8_pooling_fwd_ker_t *ker_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.cpp new file mode 100644 index 000000000000..2c5a8e8973b6 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.cpp @@ -0,0 +1,305 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_uni_lrn_kernel_f32.hpp" +#include "jit_uni_lrn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::utils; + +template +jit_uni_lrn_fwd_t::jit_uni_lrn_fwd_t(const pd_t *apd) + : cpu_primitive_t(apd), ker_(nullptr) + , ker_first_(nullptr), ker_last_(nullptr) +{ + using namespace alg_kind; + + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + const int ls = pd()->desc()->local_size; + float A = pd()->desc()->lrn_alpha / ls; + float K = pd()->desc()->lrn_k; + + auto pk = pd()->desc()->prop_kind; + auto ak = pd()->desc()->alg_kind; + auto dat_tag = pd()->dat_tag_; + + if (dat_tag == nChw8c && ls == 5 && ak == lrn_across_channels) { + ker_ = new jit_uni_lrn_fwd_kernel_f32( + nchw8c_across(H, W, 0), A, K, pk); + ker_first_ = new jit_uni_lrn_fwd_kernel_f32( + nchw8c_across(H, W, -1), A, K, pk); + ker_last_ = new jit_uni_lrn_fwd_kernel_f32( + nchw8c_across(H, W, +1), A, K, pk); + } else if (dat_tag == nChw8c && ak == lrn_within_channel) { + /* within channel, local_size (x) local_size */ + A /= ls; /* XXX: why? */ + ker_ = new jit_uni_lrn_fwd_kernel_f32( + nchw8c_within(H, W, ls), A, K, pk); + } else if (dat_tag == nchw && ls == 5 && ak == lrn_across_channels) { + ker_ = new jit_uni_lrn_fwd_kernel_f32( + nchw_across(C, H*W, 0), A, K, pk); + int remind = (H*W) % VECTOR_LENGTH; + if (remind != 0) { + ker_last_ = new jit_uni_lrn_fwd_kernel_f32( + nchw_across(C, H*W, remind), A, K, pk); + } + } else if (true /* XXX: why */) { + ker_ = new jit_uni_lrn_fwd_kernel_f32(nhwc_across(C), A, K, pk); + } +} + +template +jit_uni_lrn_fwd_t::~jit_uni_lrn_fwd_t() +{ delete ker_; delete ker_first_; delete ker_last_; } + +template +void jit_uni_lrn_fwd_t::execute_forward(const exec_ctx_t &ctx) const { + using namespace alg_kind; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(data_t *, MKLDNN_ARG_WORKSPACE); + + const int N = pd()->MB(); + const int C = pd()->C(); + const int HW = pd()->H() * pd()->W(); + const int ls = pd()->desc()->local_size; + + auto ak = pd()->desc()->alg_kind; + auto dat_tag = pd()->dat_tag_; + + if (dat_tag == nChw8c && ls == 5 && ak == lrn_across_channels) { + parallel_nd(N, C / VECTOR_LENGTH, [&](int n, int c8) { + jit_args_fwd_t args; + args.src = &src[n*HW*C + c8 * HW * VECTOR_LENGTH]; + args.dst = &dst[n*HW*C + c8 * HW * VECTOR_LENGTH]; + args.scratch = &ws[n*HW*C + c8 * HW * VECTOR_LENGTH]; + if (c8 == 0) + (*ker_first_)(&args); + else if (c8 == C / VECTOR_LENGTH - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + }); + } + else if (dat_tag == nChw8c && ak == lrn_within_channel) { + parallel_nd(N, C / VECTOR_LENGTH, [&](int n, int c8) { + jit_args_fwd_t args; + args.src = &src[n*HW*C + c8 * HW * VECTOR_LENGTH]; + args.dst = &dst[n*HW*C + c8 * HW * VECTOR_LENGTH]; + args.scratch = &ws[n*HW*C + c8 * HW * VECTOR_LENGTH]; + (*ker_)(&args); + }); + } + else if (dat_tag == nchw && ls == 5 && ak == lrn_across_channels) { + parallel_nd(N, (HW + VECTOR_LENGTH - 1) / VECTOR_LENGTH, + [&](int n, int hw8) { + jit_args_fwd_t args; + args.src = &src[n*HW*C + hw8 * VECTOR_LENGTH]; + args.dst = &dst[n*HW*C + hw8 * VECTOR_LENGTH]; + args.scratch = &ws[n*HW*C + hw8 * VECTOR_LENGTH]; + if ((hw8 + 1)*VECTOR_LENGTH > HW) + (*ker_last_)(&args); + else + (*ker_)(&args); + }); + } + else { // nhwc + parallel_nd(N, HW, [&](int n, int hw) { + jit_args_fwd_t args; + args.src = &src[n*HW*C + hw * C]; + args.dst = &dst[n*HW*C + hw * C]; + args.scratch = &ws[n*HW*C + hw * C]; + (*ker_)(&args); + }); + } +} + +template +status_t jit_uni_lrn_fwd_t::pd_t::init() { + using namespace prop_kind; + using namespace alg_kind; + + const memory_desc_wrapper data_d(src_md()); + bool ok = true + && mayiuse(isa) + && is_fwd() + && everyone_is(data_type::f32, data_d.data_type()) + && !has_zero_dim_memory() + && data_d.ndims() == 4 + && data_d.dims()[1] % VECTOR_LENGTH == 0 + && data_d.dims()[1] >= 2 * VECTOR_LENGTH + && desc()->lrn_beta == 0.75 + && attr()->has_default_values(); + if (!ok) return unimplemented; + + if (desc_.prop_kind == forward_training) ws_md_ = *src_md(); + + dat_tag_ = memory_desc_matches_one_of_tag(*src_md(), nChw8c, nchw, nhwc); + + bool args_ok_across = true + && desc()->alg_kind == lrn_across_channels + && desc()->local_size == 5 + && one_of(dat_tag_, nChw8c, nchw, nhwc); + + const int jit_max_local_size = 5; // bigger size triggers too big code size + bool args_ok_within = true + && desc()->alg_kind == lrn_within_channel + && desc()->local_size <= ( jit_max_local_size <= MAX_LOCAL_SIZE + ? jit_max_local_size : MAX_LOCAL_SIZE) + && data_d.dims()[2] >= desc()->local_size + && data_d.dims()[3] >= desc()->local_size + && one_of(dat_tag_, nChw8c); + + return args_ok_across || args_ok_within ? success : unimplemented; +} + +template +jit_uni_lrn_bwd_t::jit_uni_lrn_bwd_t(const pd_t *apd) + : cpu_primitive_t(apd) + , ker_(nullptr), ker_first_(nullptr), ker_last_(nullptr) +{ + using namespace alg_kind; + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + const int ls = pd()->desc()->local_size; + float A = pd()->desc()->lrn_alpha / ls; + float B = pd()->desc()->lrn_beta; + + int use_h_parallelizm = 0;// XXX + if (C / VECTOR_LENGTH == 1) { + ker_ = new jit_uni_lrn_bwd_kernel_f32( + nchw8c_across(H, W, 3), A, B, use_h_parallelizm); + } + else { + ker_ = new jit_uni_lrn_bwd_kernel_f32( + nchw8c_across(H, W, 0), A, B, use_h_parallelizm); + ker_first_ = new jit_uni_lrn_bwd_kernel_f32( + nchw8c_across(H, W, -1), A, B, use_h_parallelizm); + ker_last_ = new jit_uni_lrn_bwd_kernel_f32( + nchw8c_across(H, W, +1), A, B, use_h_parallelizm); + } +} + +template +jit_uni_lrn_bwd_t::~jit_uni_lrn_bwd_t() +{ + delete ker_; delete ker_first_; delete ker_last_; +} + +template +void jit_uni_lrn_bwd_t::execute_backward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto ws = CTX_IN_MEM(const data_t *, MKLDNN_ARG_WORKSPACE); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const int N = pd()->MB(); + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + + int use_h_parallelizm = 0; // XXX + if (use_h_parallelizm) { + parallel_nd(N, C / VECTOR_LENGTH, H, [&](int n, int c8, int h) { + auto offset = n*C*H*W + c8*H*W*VECTOR_LENGTH + + h*W*VECTOR_LENGTH; + jit_args_bwd_t args; + args.src = &src[offset]; + args.diff_dst = &diff_dst[offset]; + args.scratch = &ws[offset]; + args.diff_src = &diff_src[offset]; + if (C / VECTOR_LENGTH == 1) + (*ker_)(&args); + else if (c8 == 0) + (*ker_first_)(&args); + else if (c8 == C / VECTOR_LENGTH - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + }); + } + else { + parallel_nd(N, C / VECTOR_LENGTH, [&](int n, int c8) { + auto offset = n*C*H*W + c8*H*W*VECTOR_LENGTH; + jit_args_bwd_t args; + args.src = &src[offset]; + args.diff_dst = &diff_dst[offset]; + args.scratch = &ws[offset]; + args.diff_src = &diff_src[offset]; + if (C / VECTOR_LENGTH == 1) + (*ker_)(&args); + else if (c8 == 0) + (*ker_first_)(&args); + else if (c8 == C / VECTOR_LENGTH - 1) + (*ker_last_)(&args); + else + (*ker_)(&args); + }); + } +} + +template +status_t jit_uni_lrn_bwd_t::pd_t::init() { + using namespace prop_kind; + using namespace alg_kind; + + const memory_desc_wrapper data_d(src_md()); + bool ok = true + && mayiuse(isa) + && !is_fwd() + && utils::everyone_is(data_type::f32, data_d.data_type()) + && !has_zero_dim_memory() + && data_d.ndims() == 4 + && data_d.dims()[1] % VECTOR_LENGTH == 0 + && desc()->lrn_beta == 0.75 + && attr()->has_default_values(); + if (!ok) return unimplemented; + + ws_md_ = *src_md(); + if (!compare_ws(hint_fwd_pd_)) return unimplemented; + + dat_tag_ = memory_desc_matches_one_of_tag(*src_md(), nChw8c); + + bool args_ok_across = true + && desc()->alg_kind == lrn_across_channels + && desc()->local_size == 5 + && utils::one_of(dat_tag_, nChw8c); + + return args_ok_across ? success : unimplemented; +} + +template struct jit_uni_lrn_fwd_t; +template struct jit_uni_lrn_fwd_t; +template struct jit_uni_lrn_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.hpp new file mode 100644 index 000000000000..333cd3396d36 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn.hpp @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_UNI_LRN_HPP +#define CPU_JIT_UNI_LRN_HPP + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_isa_traits.hpp" +#include "cpu_lrn_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template struct jit_uni_lrn_fwd_kernel_f32; +template struct jit_uni_lrn_bwd_kernel_f32; + +template +struct jit_uni_lrn_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_lrn_fwd_pd_t { + using cpu_lrn_fwd_pd_t::cpu_lrn_fwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_lrn_fwd_t); + + status_t init(); + + format_tag_t dat_tag_; + }; + + jit_uni_lrn_fwd_t(const pd_t *apd); + ~jit_uni_lrn_fwd_t(); + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_uni_lrn_fwd_kernel_f32 *ker_, *ker_first_, *ker_last_; +}; + +template +struct jit_uni_lrn_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_lrn_bwd_pd_t { + using cpu_lrn_bwd_pd_t::cpu_lrn_bwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_lrn_bwd_t); + + status_t init(); + + format_tag_t dat_tag_; + }; + + jit_uni_lrn_bwd_t(const pd_t *apd); + ~jit_uni_lrn_bwd_t(); + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + jit_uni_lrn_bwd_kernel_f32 *ker_, *ker_first_, *ker_last_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.cpp new file mode 100644 index 000000000000..89af47272cae --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.cpp @@ -0,0 +1,1487 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "utils.hpp" + +#include "jit_uni_lrn_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + +////////////////////////////////////////////////////////////////////////////// +// forward kernel +template +void jit_uni_lrn_fwd_kernel_f32::within_body( + int hoff, int Hoff, int woff, int Woff, int stride, + Xbyak::Ymm ysum, Xbyak::Ymm ydst, Xbyak::Ymm ytmp, Xbyak::Ymm ysum2, + prop_kind_t pk) +{ + vxorps(ysum, ysum, ysum); + for (int i = hoff; i <= Hoff; ++i) + { + for (int j = woff; j <= Woff; ++j) + { + if (i == 0 && j == 0) + { + vmovups(ydst, ptr[src]); + vfmadd231ps(ysum, ydst, ydst); + } + else + { + vmovups(ytmp, ptr[src + (i*stride + j)*VECTOR_LENGTH*4]); + vfmadd231ps(ysum, ytmp, ytmp); + } + } + } + vfmadd132ps(ysum, yk, yalpha); // ysum <- ysum*yalpha+yk + vmovaps(ytmp, ysum); + if (pk != prop_kind::forward_inference) + vmovups(ptr[scratch], ytmp); + vmulps(ysum2, ysum, ysum); + vmulps(ysum, ysum, ysum2); // ysum = (ysum*yalpha+yk)^3; + vsqrtps(ysum, ysum); + vsqrtps(ysum, ysum); // ysum = (ysum*yalpha+yk)^0.75 + vdivps(ydst, ydst, ysum); // ydst <- ydst / ysum + vmovups(ptr[dst], ydst); + add(src, 32); + add(dst, 32); + if (pk != prop_kind::forward_inference) + add(scratch, 32); +} + +template +void jit_uni_lrn_fwd_kernel_f32::within_body_sse42( + int hoff, int Hoff, int woff, int Woff, int stride, prop_kind_t pk) +{ + Xbyak::Xmm xtmp_lo = xmm12; + Xbyak::Xmm xtmp_hi = xmm13; + Xbyak::Xmm xsum_lo = xmm8; + Xbyak::Xmm xsum_hi = xmm9; + Xbyak::Xmm xdst_lo = xmm10; + Xbyak::Xmm xdst_hi = xmm11; + Xbyak::Xmm xsum2_lo = xmm14; + Xbyak::Xmm xsum2_hi = xmm15; + + xorps(xsum_lo, xsum_lo); + xorps(xsum_hi, xsum_hi); + for (int i = hoff; i <= Hoff; ++i) + { + for (int j = woff; j <= Woff; ++j) + { + if (i == 0 && j == 0) + { + movups(xdst_lo, ptr[src]); + movups(xdst_hi, ptr[src + 4 * sizeof(float)]); + mulps(xdst_lo, xdst_lo); + mulps(xdst_hi, xdst_hi); + addps(xsum_lo, xdst_lo); + addps(xsum_hi, xdst_hi); + } + else + { + movups(xtmp_lo, ptr[src + (i*stride + j)*VECTOR_LENGTH * 4]); + movups(xtmp_hi, ptr[src + (i*stride + j)*VECTOR_LENGTH * 4 + 4 * sizeof(float)]); + mulps(xtmp_lo, xtmp_lo); + mulps(xtmp_hi, xtmp_hi); + addps(xsum_lo, xtmp_lo); + addps(xsum_hi, xtmp_hi); + } + } + } + mulps(xsum_lo, xalpha); + mulps(xsum_hi, xalpha); + addps(xsum_lo, xk); + addps(xsum_hi, xk); // xsum <- xsum*xalpha+xk + movaps(xtmp_lo, xsum_lo); + movaps(xtmp_hi, xsum_hi); + if (pk != prop_kind::forward_inference) { + movups(ptr[scratch], xtmp_lo); + movups(ptr[scratch + 4 * sizeof(float)], xtmp_hi); + } + movaps(xsum2_lo, xsum_lo); + movaps(xsum2_hi, xsum_hi); + mulps(xsum2_lo, xsum_lo); + mulps(xsum2_hi, xsum_hi); + mulps(xsum_lo, xsum2_lo); + mulps(xsum_hi, xsum2_hi); // xsum = (xsum*xalpha+xk)^3; + + sqrtps(xsum_lo, xsum_lo); + sqrtps(xsum_hi, xsum_hi); + sqrtps(xsum_lo, xsum_lo); + sqrtps(xsum_hi, xsum_hi); // xsum = (xsum*xalpha+xk)^0.75 + + movups(xdst_lo, ptr[src]); + movups(xdst_hi, ptr[src + 4 * sizeof(float)]); + divps(xdst_lo, xsum_lo); + divps(xdst_hi, xsum_hi); // xdst <- xdst / xsum + + movups(ptr[dst], xdst_lo); + movups(ptr[dst + 4 * sizeof(float)], xdst_hi); + add(src, 32); + add(dst, 32); + if (pk != prop_kind::forward_inference) + add(scratch, 32); +} + +template +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + const struct nchw8c_within &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + Xbyak::Reg64 h = r9; + Xbyak::Reg64 w = r10; + Vmm ysum = Vmm(isa == avx2 ? 9 : 9); + Vmm ysum2 = Vmm(isa == avx2 ? 10 : 10); + Vmm ydst = Vmm(isa == avx2 ? 11 : 11); + Vmm ytmp = Vmm(isa == avx2 ? 12 : 12); + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + if (isa == avx2) { + vbroadcastss(yalpha, xalpha); + } else { + shufps(xalpha, xalpha, 0); + } + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + if (isa == avx2) { + vbroadcastss(yk, xk); + } else { + shufps(xk, xk, 0); + } + + int s2 = (J.size - 1) / 2, S2 = J.size - s2 - 1; + + for (int i = 0; i < s2; ++i) + { + Label label_t; + for (int j = 0; j < s2; ++j) { + if (isa == avx2) { + within_body(-i, S2, -j, S2, J.W, ysum, ydst, ytmp, ysum2, pk); + } + else { + within_body_sse42(-i, S2, -j, S2, J.W, pk); + } + } + mov(w, J.W - J.size + 1); + L(label_t); + if (isa == avx2) { + within_body(-i, S2, -s2, S2, J.W, ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-i, S2, -s2, S2, J.W, pk); + } + dec(w); + cmp(w, 0); + jne(label_t, T_NEAR); + for (int j = J.W - S2; j < J.W; ++j) { + if (isa == avx2) { + within_body(-i, S2, -s2, J.W - 1 - j, J.W, + ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-i, S2, -s2, J.W - 1 - j, J.W, pk); + } + } + } + + mov(h, J.H - J.size + 1); + Label lrn_loop_h; + L(lrn_loop_h); + for (int j = 0; j < s2; ++j) { + if (isa == avx2) { + within_body(-s2, S2, -j, S2, J.W, ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-s2, S2, -j, S2, J.W, pk); + } + } + mov(w, J.W - J.size + 1); + Label lrn_loop_w; + L(lrn_loop_w); + if (isa == avx2) { + within_body(-s2, S2, -s2, S2, J.W, ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-s2, S2, -s2, S2, J.W, pk); + } + dec(w); + cmp(w, 0); + jne(lrn_loop_w, T_NEAR); + for (int j = J.W - S2; j < J.W; ++j) { + if (isa == avx2) { + within_body(-s2, S2, -s2, J.W - 1 - j, J.W, + ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-s2, S2, -s2, J.W - 1 - j, J.W, pk); + } + } + dec(h); + cmp(h, 0); + jne(lrn_loop_h, T_NEAR); + + for (int i = J.H - S2; i < J.H; ++i) + { + for (int j = 0; j < s2; ++j) { + if (isa == avx2) { + within_body(-s2, J.H - 1 - i, -j, S2, J.W, + ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-s2, J.H - 1 - i, -j, S2, J.W, pk); + } + } + + mov(w, J.W - J.size + 1); + Label label_b; + L(label_b); + if (isa == avx2) { + within_body(-s2, J.H - 1 - i, -s2, S2, J.W, + ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-s2, J.H - 1 - i, -s2, S2, J.W, pk); + } + dec(w); + cmp(w, 0); + jne(label_b, T_NEAR); + + for (int j = J.W - S2; j < J.W; ++j) { + if (isa == avx2) { + within_body(-s2, J.H - 1 - i, -s2, J.W - 1 - j, J.W, + ysum, ydst, ytmp, ysum2, pk); + } else { + within_body_sse42(-s2, J.H - 1 - i, -s2, J.W - 1 - j, J.W, pk); + } + } + } + + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template<> +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + const struct nchw8c_across &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + Xbyak::Reg64 t = rsp; + Xbyak::Reg64 hw = r9; + Xbyak::Xmm xsrc_prev = xmm2; + Xbyak::Ymm ysrc = ymm3; + Xbyak::Ymm yc = ymm3; + Xbyak::Xmm xsrc_next = xmm4; + Xbyak::Ymm ya = ymm5; + Xbyak::Ymm yb = ymm6; + Xbyak::Ymm yd = ymm7; + Xbyak::Ymm ye = ymm8; + Xbyak::Ymm ysum = ymm9; + Xbyak::Ymm ysum2 = ymm10; + Xbyak::Ymm ydst = ymm11; + Xbyak::Ymm ybase = ymm12; + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + sub(t, 64); + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + vbroadcastss(yalpha, xalpha); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + vbroadcastss(yk, xk); + + if (J.version == -1) + { + vxorps(xsrc_prev, xsrc_prev, xsrc_prev); + vmovups(ptr[t + 0], xsrc_prev); + } + if (J.version == +1) + { + vxorps(xsrc_next, xsrc_next, xsrc_next); + vmovups(ptr[t + 48], xsrc_next); + } + + mov(hw, J.H*J.W); + + Label lrn_loop; + L(lrn_loop); + + if (J.version != -1) vmovups(xsrc_prev, ptr[src - J.H*J.W * 32 + 16]); + vmovups(ysrc, ptr[src]); + if (J.version != +1) vmovups(xsrc_next, ptr[src + J.H*J.W * 32]); + + if (J.version != -1) vmovups(ptr[t + 0], xsrc_prev); + vmovups(ptr[t + 16], ysrc); + if (J.version != +1) vmovups(ptr[t + 48], xsrc_next); + + vmovups(ya, ptr[t + 16 - 8]); + vmovups(yb, ptr[t + 16 - 4]); + vmovups(yd, ptr[t + 16 + 4]); + vmovups(ye, ptr[t + 16 + 8]); + vmulps(ysum, yc, yc); + vfmadd231ps(ysum, ya, ya); // ysum <- ysum + ya*ya + vfmadd231ps(ysum, yb, yb); + vfmadd231ps(ysum, yd, yd); + vfmadd231ps(ysum, ye, ye); + vfmadd132ps(ysum, yk, yalpha); // ysum <- ysum*yalpha+yk + + vmovaps(ybase, ysum); + if (pk != prop_kind::forward_inference) + vmovups(ptr[scratch], ybase); + vmulps(ysum2, ysum, ysum); + vmulps(ysum, ysum, ysum2); // ysum = ybase^3; + vsqrtps(ysum, ysum); + vsqrtps(ysum, ysum); // ysum = ybase^0.75 + vdivps(ydst, ysrc, ysum); // ydst = ysrc / ysum + vmovups(ptr[dst], ydst); + + add(src, 32); + add(dst, 32); + if (pk != prop_kind::forward_inference) + add(scratch, 32); + dec(hw); + cmp(hw, 0); + jne(lrn_loop, T_NEAR); + + add(t, 64); + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template<> +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + const struct nchw8c_across &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + Xbyak::Reg64 t = rsp; + Xbyak::Reg64 hw = r9; + + Xbyak::Xmm xsrc_lo = xmm2; + Xbyak::Xmm xsrc_hi = xmm3; + Xbyak::Xmm xc_lo = xmm4; + Xbyak::Xmm xc_hi = xmm5; + Xbyak::Xmm xsum_lo = xc_lo; + Xbyak::Xmm xsum_hi = xc_hi; + Xbyak::Xmm xsrc_prev = xmm6; + Xbyak::Xmm xsrc_next = xmm7; + Xbyak::Xmm xa_lo = xmm8; + Xbyak::Xmm xa_hi = xmm9; + Xbyak::Xmm xb_lo = xmm10; + Xbyak::Xmm xb_hi = xmm11; + Xbyak::Xmm xd_lo = xmm12; + Xbyak::Xmm xd_hi = xmm13; + Xbyak::Xmm xe_lo = xmm14; + Xbyak::Xmm xe_hi = xmm15; + Xbyak::Xmm xbase_lo = xmm14; + Xbyak::Xmm xbase_hi = xmm15; + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + sub(t, 64); + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + shufps(xalpha, xalpha, 0); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + shufps(xk, xk, 0); + + if (J.version == -1) + { + xorps(xsrc_prev, xsrc_prev); + movups(ptr[t + 0], xsrc_prev); + } + if (J.version == +1) + { + xorps(xsrc_next, xsrc_next); + movups(ptr[t + 48], xsrc_next); + } + + mov(hw, J.H*J.W); + Label lrn_loop; + L(lrn_loop); + + if (J.version != -1) movups(xsrc_prev, ptr[src - J.H*J.W * 32 + 16]); + movups(xsrc_lo, ptr[src]); + movups(xsrc_hi, ptr[src + 4 * sizeof(float)]); + if (J.version != +1) movups(xsrc_next, ptr[src + J.H*J.W * 32]); + + if (J.version != -1) movups(ptr[t + 0], xsrc_prev); + movups(ptr[t + 16], xsrc_lo); + movups(ptr[t + 16 + 4 * sizeof(float)], xsrc_hi); + if (J.version != +1) movups(ptr[t + 48], xsrc_next); + + movups(xa_lo, ptr[t + 16 - 8]); + movups(xa_hi, ptr[t + 16 - 8 + 4 * sizeof(float)]); + movups(xb_lo, ptr[t + 16 - 4]); + movups(xb_hi, ptr[t + 16 - 4 + 4 * sizeof(float)]); + movups(xd_lo, ptr[t + 16 + 4]); + movups(xd_hi, ptr[t + 16 + 4 + 4 * sizeof(float)]); + movups(xe_lo, ptr[t + 16 + 8]); + movups(xe_hi, ptr[t + 16 + 8 + 4 * sizeof(float)]); + movaps(xc_lo, xsrc_lo); + movaps(xc_hi, xsrc_hi); + mulps(xsum_lo, xc_lo); + mulps(xsum_hi, xc_hi); + mulps(xa_lo, xa_lo); + mulps(xa_hi, xa_hi); + addps(xsum_lo, xa_lo); + addps(xsum_hi, xa_hi); // xsum <- xsum + xa*xa + mulps(xb_lo, xb_lo); + mulps(xb_hi, xb_hi); + addps(xsum_lo, xb_lo); + addps(xsum_hi, xb_hi); + mulps(xd_lo, xd_lo); + mulps(xd_hi, xd_hi); + addps(xsum_lo, xd_lo); + addps(xsum_hi, xd_hi); + mulps(xe_lo, xe_lo); + mulps(xe_hi, xe_hi); + addps(xsum_lo, xe_lo); + addps(xsum_hi, xe_hi); + + mulps(xsum_lo, xalpha); + mulps(xsum_hi, xalpha); + addps(xsum_lo, xk); + addps(xsum_hi, xk); // xsum <- xsum*xalpha+xk + + movaps(xbase_lo, xsum_lo); + movaps(xbase_hi, xsum_hi); + if (pk != prop_kind::forward_inference) { + movups(ptr[scratch], xbase_lo); + movups(ptr[scratch + 4 * sizeof(float)], xbase_hi); + } + mulps(xsum_lo, xsum_lo); + mulps(xsum_hi, xsum_hi); + mulps(xsum_lo, xbase_lo); + mulps(xsum_hi, xbase_hi); // xsum = xbase^3; + sqrtps(xsum_lo, xsum_lo); + sqrtps(xsum_hi, xsum_hi); + sqrtps(xsum_lo, xsum_lo); + sqrtps(xsum_hi, xsum_hi); // xsum = xbase^0.75 + divps(xsrc_lo, xsum_lo); + divps(xsrc_hi, xsum_hi); // xdst = xsrc / xsum + movups(ptr[dst], xsrc_lo); + movups(ptr[dst + 4 * sizeof(float)], xsrc_hi); + + add(src, 32); + add(dst, 32); + if (pk != prop_kind::forward_inference) + add(scratch, 32); + dec(hw); + cmp(hw, 0); + jne(lrn_loop, T_NEAR); + + add(t, 64); + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template<> +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + const struct nhwc_across &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + static const uint32_t mask[] = { + 0, 0, 0x80000000, 0x80000000, 0x80000000, 0x80000000, + 0x80000000, 0x80000000, 0x80000000, 0, 0 + }; + + Xbyak::Reg64 c = r9; + Xbyak::Ymm ya = ymm2; + Xbyak::Ymm yb = ymm3; + Xbyak::Ymm yc = ymm4; + Xbyak::Ymm yd = ymm5; + Xbyak::Ymm ye = ymm6; + Xbyak::Ymm ysum = ymm7; + Xbyak::Ymm ydst = ymm8; + Xbyak::Ymm ybase = ymm9; + Xbyak::Ymm ymask = ymm10; + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + vbroadcastss(yalpha, xalpha); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + vbroadcastss(yk, xk); + + vxorps(ysum, ysum, ysum); + + mov(imm_addr64, reinterpret_cast(&mask[0])); + vmovups(ymask, ptr[imm_addr64]); + vmaskmovps(ya, ymask, ptr[src - 8]); + vfmadd231ps(ysum, ya, ya); // ysum <- ysum + ya^2+yb^2+yc^2+yd^2+ye^2 + + mov(imm_addr64, reinterpret_cast(&mask[1])); + vmovups(ymask, ptr[imm_addr64]); + vmaskmovps(yb, ymask, ptr[src - 4]); + vfmadd231ps(ysum, yb, yb); + + mov(c, J.C / 8 - 1); + Label lrn_loop; + L(lrn_loop); + + vmovups(yc, ptr[src]); + vmovups(yd, ptr[src + 4]); + vmovups(ye, ptr[src + 8]); + vfmadd231ps(ysum, yc, yc); + vfmadd231ps(ysum, yd, yd); + vfmadd231ps(ysum, ye, ye); + + vmovups(ydst, ysum); + vfmadd132ps(ydst, yk, yalpha); // ydst <- ysum*yalpha+yk + + vmovaps(ybase, ydst); + if (pk != prop_kind::forward_inference) + vmovups(ptr[scratch], ybase); + vmulps(ydst, ydst, ydst); + vmulps(ydst, ydst, ybase); // ydst = (ysum*yalpha+yk)^3; + vsqrtps(ydst, ydst); + vsqrtps(ydst, ydst); // ydst = (ysum*yalpha+yk)^0.75 + + vdivps(ydst, yc, ydst); // ydst = ysrc / (ysum*yalpha+yk)^0.75 + vmovups(ptr[dst], ydst); + + vxorps(ysum, ysum, ysum); + + add(src, 32); + add(dst, 32); + if (pk != prop_kind::forward_inference) + add(scratch, 32); + + vmovups(ya, ptr[src - 8]); + vfmadd231ps(ysum, ya, ya); + vmovups(yb, ptr[src - 4]); + vfmadd231ps(ysum, yb, yb); + + dec(c); + cmp(c, 0); + jne(lrn_loop, T_NEAR); + + vmovups(yc, ptr[src]); + vfmadd231ps(ysum, yc, yc); + + mov(imm_addr64, reinterpret_cast(&mask[2])); + vmovups(ymask, ptr[imm_addr64]); + vmaskmovps(yd, ymask, ptr[src + 4]); + vfmadd231ps(ysum, yd, yd); // ysum <- ysum + ya^2+yb^2+yc^2+yd^2+ye^2 + + mov(imm_addr64, reinterpret_cast(&mask[3])); + vmovups(ymask, ptr[imm_addr64]); + vmaskmovps(ye, ymask, ptr[src + 8]); + vfmadd231ps(ysum, ye, ye); + + vmovups(ydst, ysum); + vfmadd132ps(ydst, yk, yalpha); // ydst <- ysum*yalpha+yk + + vmovaps(ybase, ydst); + if (pk != prop_kind::forward_inference) + vmovups(ptr[scratch], ybase); + vmulps(ydst, ydst, ydst); + vmulps(ydst, ydst, ybase); // ydst = (ysum*yalpha+yk)^3; + vsqrtps(ydst, ydst); + vsqrtps(ydst, ydst); // ydst = (ysum*yalpha+yk)^0.75 + vdivps(ydst, yc, ydst); // ydst = ysrc / (ysum*yalpha+yk)^0.75 + + vmovups(ptr[dst], ydst); + + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template<> +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + const struct nhwc_across &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + static const uint32_t mask[] = { + 0, 0, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0, 0 + }; + + static uint32_t store[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + Xbyak::Reg64 c = r9; + + Xbyak::Xmm xdst_lo = xmm0; + Xbyak::Xmm xdst_hi = xmm1; + Xbyak::Xmm xa_lo = xmm2; + Xbyak::Xmm xa_hi = xmm3; + Xbyak::Xmm xb_lo = xmm2; + Xbyak::Xmm xb_hi = xmm3; + Xbyak::Xmm xc_lo = xmm4; + Xbyak::Xmm xc_hi = xmm5; + Xbyak::Xmm xd_lo = xmm6; + Xbyak::Xmm xd_hi = xmm7; + Xbyak::Xmm xe_lo = xmm8; + Xbyak::Xmm xe_hi = xmm9; + Xbyak::Xmm xsum_lo = xmm10; + Xbyak::Xmm xsum_hi = xmm11; + Xbyak::Xmm xmask_lo = xmm12; + Xbyak::Xmm xmask_hi = xmm13; + Xbyak::Xmm xbase_lo = xmm14; + Xbyak::Xmm xbase_hi = xmm15; + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + shufps(xalpha, xalpha, 0); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + shufps(xk, xk, 0); + + mov(store_addr, reinterpret_cast(&store[0])); + and_(store_addr, -15); + movups(ptr[store_addr], xalpha); + movups(ptr[store_addr + 4 * sizeof(float)], xk); + + xorps(xsum_lo, xsum_lo); + xorps(xsum_hi, xsum_hi); + + mov(imm_addr64, reinterpret_cast(&mask[0])); + movups(xmask_lo, ptr[imm_addr64]); + movups(xmask_hi, ptr[imm_addr64 + 4 * sizeof(float)]); + movups(xa_lo, ptr[src - 8]); + movups(xa_hi, ptr[src - 8 + 4 * sizeof(float)]); + andps(xa_lo, xmask_lo); + andps(xa_hi, xmask_hi); + mulps(xa_lo, xa_lo); + mulps(xa_hi, xa_hi); + addps(xsum_lo, xa_lo); + addps(xsum_hi, xa_hi); // xsum <- xsum + xa^2+xb^2+xc^2+xd^2+xe^2 + + mov(imm_addr64, reinterpret_cast(&mask[1])); + movups(xmask_lo, ptr[imm_addr64]); + movups(xmask_hi, ptr[imm_addr64 + 4 * sizeof(float)]); + movups(xb_lo, ptr[src - 4]); + movups(xb_hi, ptr[src - 4 + 4 * sizeof(float)]); + andps(xb_lo, xmask_lo); + andps(xb_hi, xmask_hi); + mulps(xb_lo, xb_lo); + mulps(xb_hi, xb_hi); + addps(xsum_lo, xb_lo); + addps(xsum_hi, xb_hi); + + mov(c, J.C / 8 - 1); + Label lrn_loop; + L(lrn_loop); + + movups(xc_lo, ptr[src]); + movups(xc_hi, ptr[src + 4 * sizeof(float)]); + movups(xd_lo, ptr[src + 4]); + movups(xd_hi, ptr[src + 4 + 4 * sizeof(float)]); + movups(xe_lo, ptr[src + 8]); + movups(xe_hi, ptr[src + 8 + 4 * sizeof(float)]); + mulps(xc_lo, xc_lo); + mulps(xc_hi, xc_hi); + addps(xsum_lo, xc_lo); + addps(xsum_hi, xc_hi); + mulps(xd_lo, xd_lo); + mulps(xd_hi, xd_hi); + addps(xsum_lo, xd_lo); + addps(xsum_hi, xd_hi); + mulps(xe_lo, xe_lo); + mulps(xe_hi, xe_hi); + addps(xsum_lo, xe_lo); + addps(xsum_hi, xe_hi); + + movaps(xdst_lo, xsum_lo); + movaps(xdst_hi, xsum_hi); + // xdst <- xsum*xalpha+xk + mulps(xdst_lo, ptr[store_addr]); + mulps(xdst_hi, ptr[store_addr]); + addps(xdst_lo, ptr[store_addr + 4 * sizeof(float)]); + addps(xdst_hi, ptr[store_addr + 4 * sizeof(float)]); + + movaps(xbase_lo, xdst_lo); + movaps(xbase_hi, xdst_hi); + if (pk != prop_kind::forward_inference) { + movups(ptr[scratch], xbase_lo); + movups(ptr[scratch + 4 * sizeof(float)], xbase_hi); + } + mulps(xdst_lo, xdst_lo); + mulps(xdst_hi, xdst_hi); + mulps(xdst_lo, xbase_lo); + mulps(xdst_hi, xbase_hi); // xdst = (xsum*xalpha+xk)^3; + sqrtps(xdst_lo, xdst_lo); + sqrtps(xdst_hi, xdst_hi); + sqrtps(xdst_lo, xdst_lo); + sqrtps(xdst_hi, xdst_hi); // xdst = (xsum*xalpha+xk)^0.75 + + movups(xc_lo, ptr[src]); + movups(xc_hi, ptr[src + 4 * sizeof(float)]); + divps(xc_lo, xdst_lo); + divps(xc_hi, xdst_hi); // xdst = xsrc / (xsum*xalpha+xk)^0.75 + movups(ptr[dst], xc_lo); + movups(ptr[dst + 4 * sizeof(float)], xc_hi); + + xorps(xsum_lo, xsum_lo); + xorps(xsum_hi, xsum_hi); + + add(src, 32); + add(dst, 32); + if (pk != prop_kind::forward_inference) + add(scratch, 32); + + movups(xa_lo, ptr[src - 8]); + movups(xa_hi, ptr[src - 8 + 4 * sizeof(float)]); + mulps(xa_lo, xa_lo); + mulps(xa_hi, xa_hi); + addps(xsum_lo, xa_lo); + addps(xsum_hi, xa_hi); + movups(xb_lo, ptr[src - 4]); + movups(xb_hi, ptr[src - 4 + 4 * sizeof(float)]); + mulps(xb_lo, xb_lo); + mulps(xb_hi, xb_hi); + addps(xsum_lo, xb_lo); + addps(xsum_hi, xb_hi); + + dec(c); + cmp(c, 0); + jne(lrn_loop, T_NEAR); + + movups(xc_lo, ptr[src]); + movups(xc_hi, ptr[src + 4 * sizeof(float)]); + mulps(xc_lo, xc_lo); + mulps(xc_hi, xc_hi); + addps(xsum_lo, xc_lo); + addps(xsum_hi, xc_hi); + + mov(imm_addr64, reinterpret_cast(&mask[2])); + movups(xmask_lo, ptr[imm_addr64]); + movups(xmask_hi, ptr[imm_addr64 + 4 * sizeof(float)]); + movups(xd_lo, ptr[src + 4]); + movups(xd_hi, ptr[src + 4 + 4 * sizeof(float)]); + andps(xd_lo, xmask_lo); + andps(xd_hi, xmask_hi); + mulps(xd_lo, xd_lo); + mulps(xd_hi, xd_hi); + addps(xsum_lo, xd_lo); + addps(xsum_hi, xd_hi); // xsum <- xsum + xa^2+xb^2+xc^2+xd^2+xe^2 + + mov(imm_addr64, reinterpret_cast(&mask[3])); + movups(xmask_lo, ptr[imm_addr64]); + movups(xmask_hi, ptr[imm_addr64 + 4 * sizeof(float)]); + movups(xe_lo, ptr[src + 8]); + movups(xe_hi, ptr[src + 8 + 4 * sizeof(float)]); + andps(xe_lo, xmask_lo); + andps(xe_hi, xmask_hi); + mulps(xe_lo, xe_lo); + mulps(xe_hi, xe_hi); + addps(xsum_lo, xe_lo); + addps(xsum_hi, xe_hi); + + movups(xdst_lo, xsum_lo); + movups(xdst_hi, xsum_hi); + // xdst <- xsum*xalpha+xk + mulps(xdst_lo, ptr[store_addr]); + mulps(xdst_hi, ptr[store_addr]); + addps(xdst_lo, ptr[store_addr + 4 * sizeof(float)]); + addps(xdst_hi, ptr[store_addr + 4 * sizeof(float)]); + + movaps(xbase_lo, xdst_lo); + movaps(xbase_hi, xdst_hi); + if (pk != prop_kind::forward_inference) { + movups(ptr[scratch], xbase_lo); + movups(ptr[scratch + 4 * sizeof(float)], xbase_hi); + } + mulps(xdst_lo, xdst_lo); + mulps(xdst_hi, xdst_hi); + mulps(xdst_lo, xbase_lo); + mulps(xdst_hi, xbase_hi); // xdst = (xsum*xalpha+xk)^3; + sqrtps(xdst_lo, xdst_lo); + sqrtps(xdst_hi, xdst_hi); + sqrtps(xdst_lo, xdst_lo); + sqrtps(xdst_hi, xdst_hi); // xdst = (xsum*xalpha+xk)^0.75 + movups(xc_lo, ptr[src]); + movups(xc_hi, ptr[src + 4 * sizeof(float)]); + divps(xc_lo, xdst_lo); + divps(xc_hi, xdst_hi); // xdst = xsrc / (xsum*xalpha+xk)^0.75 + + movups(ptr[dst], xc_lo); + movups(ptr[dst + 4 * sizeof(float)], xc_hi); + + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template<> +void jit_uni_lrn_fwd_kernel_f32::nchw_body( + int tail, int HW, prop_kind_t pk, + Xbyak::Ymm ymask, + Xbyak::Ymm ya, + Xbyak::Ymm yb, + Xbyak::Ymm yc, + Xbyak::Ymm yd, + Xbyak::Ymm ye, + Xbyak::Ymm ysum) {} + +template<> +void jit_uni_lrn_fwd_kernel_f32::nchw_body( + int tail, int HW, prop_kind_t pk, + Xbyak::Ymm ymask, + Xbyak::Ymm ya, + Xbyak::Ymm yb, + Xbyak::Ymm yc, + Xbyak::Ymm yd, + Xbyak::Ymm ye, + Xbyak::Ymm ysum) +{ + Xbyak::Ymm ydst = ymm14; + Xbyak::Ymm ybase = ymm15; + + vfmadd231ps(ysum, ye, ye); + + vmovups(ydst, ysum); + vfmadd132ps(ydst, yk, yalpha); // ydst <- ysum*yalpha+yk + + vmovaps(ybase, ydst); + if (pk != prop_kind::forward_inference) + { + if (tail != 0) + vmaskmovps(ptr[scratch], ymask, ybase); + else + vmovups(ptr[scratch], ybase); + } + vmulps(ydst, ydst, ydst); + vmulps(ydst, ydst, ybase); // ydst = (ysum*yalpha+yk)^3; + vsqrtps(ydst, ydst); + vsqrtps(ydst, ydst); // ydst = (ysum*yalpha+yk)^0.75 + vdivps(ydst, yc, ydst); // ydst = ysrc / (ysum*yalpha+yk)^0.75 + + if (tail != 0) + vmaskmovps(ptr[dst], ymask, ydst); + else + vmovups(ptr[dst], ydst); + + + vfnmadd231ps(ysum, ya, ya); + vmovups(ya, yb); + vmovups(yb, yc); + vmovups(yc, yd); + vmovups(yd, ye); +} + +template<> +void jit_uni_lrn_fwd_kernel_f32::nchw_tail_sse42( + int tail, Xbyak::Reg64 reg_dst, Xbyak::Xmm xtail_lo, Xbyak::Xmm xtail_hi) +{} + +template<> +void jit_uni_lrn_fwd_kernel_f32::nchw_tail_sse42( + int tail, Xbyak::Reg64 reg_dst, Xbyak::Xmm xtail_lo, Xbyak::Xmm xtail_hi) +{ + Xbyak::Xmm xmm_tmp = xmm10; + movaps(xmm_tmp, xtail_lo); + size_t offset = 0; + + if (tail > 4) { + movups(ptr[reg_dst], xtail_lo); + movaps(xmm_tmp, xtail_hi); + offset += 4 * sizeof(float); + tail -= 4; + } + movss(ptr[reg_dst + offset], xmm_tmp); + for (int i = 1; i < tail; i++) + { + psrldq(xmm_tmp, 4); + movss(ptr[reg_dst + offset + i * sizeof(float)], xmm_tmp); + } +} + +template<> +void jit_uni_lrn_fwd_kernel_f32::nchw_body_sse42( + int tail, int HW, prop_kind_t pk, + Xbyak::Xmm xmask_lo, Xbyak::Xmm xmask_hi, + Xbyak::Xmm xe_lo, Xbyak::Xmm xe_hi, + Xbyak::Xmm xsum_lo, Xbyak::Xmm xsum_hi) +{ + Xbyak::Xmm xdst_lo = xmm0; + Xbyak::Xmm xdst_hi = xmm1; + Xbyak::Xmm xbase_lo = xmm6; + Xbyak::Xmm xbase_hi = xmm7; + Xbyak::Xmm xtmp_lo = xmm8; + Xbyak::Xmm xtmp_hi = xmm9; + Xbyak::Xmm xa_lo = xmm6; + Xbyak::Xmm xa_hi = xmm7; + Xbyak::Xmm xb_lo = xmm8; + Xbyak::Xmm xb_hi = xmm9; + Xbyak::Xmm xc_lo = xmm10; + Xbyak::Xmm xc_hi = xmm11; + Xbyak::Xmm xd_lo = xmm12; + Xbyak::Xmm xd_hi = xmm13; + + // store xe + movaps(ptr[store_addr + 10 * 4 * sizeof(float)], xe_lo); + movaps(ptr[store_addr + 11 * 4 * sizeof(float)], xe_hi); + + mulps(xe_lo, xe_lo); + mulps(xe_hi, xe_hi); + addps(xsum_lo, xe_lo); + addps(xsum_hi, xe_hi); + + // xdst <- xsum*xalpha+xk + movaps(xdst_lo, xsum_lo); + movaps(xdst_hi, xsum_hi); + mulps(xdst_lo, ptr[store_addr + 0 * 4 * sizeof(float)]); + mulps(xdst_hi, ptr[store_addr + 0 * 4 * sizeof(float)]); + addps(xdst_lo, ptr[store_addr + 1 * 4 * sizeof(float)]); + addps(xdst_hi, ptr[store_addr + 1 * 4 * sizeof(float)]); + + movaps(xbase_lo, xdst_lo); + movaps(xbase_hi, xdst_hi); + if (pk != prop_kind::forward_inference) + { + if (tail != 0) { + nchw_tail_sse42(tail, scratch, xbase_lo, xbase_hi); + } + else { + movups(ptr[scratch], xbase_lo); + movups(ptr[scratch + 4 * sizeof(float)], xbase_hi); + } + } + mulps(xdst_lo, xdst_lo); + mulps(xdst_hi, xdst_hi); + mulps(xdst_lo, xbase_lo); + mulps(xdst_hi, xbase_hi); // xdst = (xsum*xalpha+xk)^3; + sqrtps(xdst_lo, xdst_lo); + sqrtps(xdst_hi, xdst_hi); + sqrtps(xdst_lo, xdst_lo); + sqrtps(xdst_hi, xdst_hi); // xdst = (xsum*xalpha+xk)^0.75 + movaps(xtmp_lo, ptr[store_addr + 6 * 4 * sizeof(float)]); + movaps(xtmp_hi, ptr[store_addr + 7 * 4 * sizeof(float)]); + divps(xtmp_lo, xdst_lo); + divps(xtmp_hi, xdst_hi); // xdst = xsrc / (xsum*xalpha+xk)^0.75 + movaps(xdst_lo, xtmp_lo); + movaps(xdst_hi, xtmp_hi); + + if (tail != 0) { + nchw_tail_sse42(tail, dst, xdst_lo, xdst_hi); + } + else { + movups(ptr[dst], xdst_lo); + movups(ptr[dst + 4 * sizeof(float)], xdst_hi); + } + + movaps(xa_lo, ptr[store_addr + 2 * 4 * sizeof(float)]); + movaps(xa_hi, ptr[store_addr + 3 * 4 * sizeof(float)]); + mulps(xa_lo, xa_lo); + mulps(xa_hi, xa_hi); + subps(xsum_lo, xa_lo); + subps(xsum_hi, xa_hi); + + // xa <- xb + movaps(xb_lo, ptr[store_addr + 4 * 4 * sizeof(float)]); + movaps(xb_hi, ptr[store_addr + 5 * 4 * sizeof(float)]); + movaps(ptr[store_addr + 2 * 4 * sizeof(float)], xb_lo); + movaps(ptr[store_addr + 3 * 4 * sizeof(float)], xb_hi); + + // xb <- xc + movaps(xc_lo, ptr[store_addr + 6 * 4 * sizeof(float)]); + movaps(xc_hi, ptr[store_addr + 7 * 4 * sizeof(float)]); + movaps(ptr[store_addr + 4 * 4 * sizeof(float)], xc_lo); + movaps(ptr[store_addr + 5 * 4 * sizeof(float)], xc_hi); + + // xc <- xd + movaps(xd_lo, ptr[store_addr + 8 * 4 * sizeof(float)]); + movaps(xd_hi, ptr[store_addr + 9 * 4 * sizeof(float)]); + movaps(ptr[store_addr + 6 * 4 * sizeof(float)], xd_lo); + movaps(ptr[store_addr + 7 * 4 * sizeof(float)], xd_hi); + + // xd <- xe + movaps(xe_lo, ptr[store_addr + 10 * 4 * sizeof(float)]); + movaps(xe_hi, ptr[store_addr + 11 * 4 * sizeof(float)]); + movaps(ptr[store_addr + 8 * 4 * sizeof(float)], xe_lo); + movaps(ptr[store_addr + 9 * 4 * sizeof(float)], xe_hi); +} + +template<> +void jit_uni_lrn_fwd_kernel_f32::nchw_body_sse42( + int tail, int HW, prop_kind_t pk, + Xbyak::Xmm xmask_lo, Xbyak::Xmm xmask_hi, + Xbyak::Xmm xe_lo, Xbyak::Xmm xe_hi, + Xbyak::Xmm xsum_lo, Xbyak::Xmm xsum_hi) {} + +template<> +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + struct nchw_across J, + float A, + float K, + prop_kind_t pk, + void* code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + static const uint32_t mask[] = { + 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, + 0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0 + }; + Xbyak::Reg64 c = r10; + Xbyak::Ymm ymask = ymm2; + Xbyak::Ymm ye = ymm3; + Xbyak::Ymm ya = ymm4; + Xbyak::Ymm yb = ymm5; + Xbyak::Ymm yc = ymm6; + Xbyak::Ymm yd = ymm7; + Xbyak::Ymm ysum = ymm8; + + this->preamble(); + + if (J.tail != 0) + { + mov(imm_addr64, reinterpret_cast(&mask[7 - J.tail])); + vmovups(ymask, ptr[imm_addr64]); + } + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + vbroadcastss(yalpha, xalpha); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + vbroadcastss(yk, xk); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + + vxorps(ya, ya, ya); + vxorps(yb, yb, yb); + if (J.tail != 0) + vmaskmovps(yc, ymask, ptr[src + J.HW * 0]); + else + vmovups(yc, ptr[src + J.HW * 0]); + if (J.tail != 0) + vmaskmovps(yd, ymask, ptr[src + J.HW * 4]); + else + vmovups(yd, ptr[src + J.HW * 4]); + + vxorps(ysum, ysum, ysum); + vfmadd231ps(ysum, yc, yc); // ysum <- ysum + ya^2+yb^2+yc^2+yd^2+ye^2 + vfmadd231ps(ysum, yd, yd); + + mov(c, J.C - 2); + Label lrn_loop; + L(lrn_loop); + + if (J.tail != 0) + vmaskmovps(ye, ymask, ptr[src + J.HW * 8]); + else + vmovups(ye, ptr[src + J.HW * 8]); + + nchw_body(J.tail, J.HW, pk, ymask, ya, yb, yc, yd, ye, ysum); + + add(src, J.HW * 4); + add(dst, J.HW * 4); + if (pk != prop_kind::forward_inference) + add(scratch, J.HW * 4); + dec(c); + cmp(c, 0); + jne(lrn_loop, T_NEAR); + + vxorps(ye, ye, ye); + + nchw_body(J.tail, J.HW, pk, ymask, ya, yb, yc, yd, ye, ysum); + add(src, J.HW * 4); + add(dst, J.HW * 4); + if (pk != prop_kind::forward_inference) + add(scratch, J.HW * 4); + + nchw_body(J.tail, J.HW, pk, ymask, ya, yb, yc, yd, ye, ysum); + + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template<> +jit_uni_lrn_fwd_kernel_f32::jit_uni_lrn_fwd_kernel_f32( + struct nchw_across J, + float A, + float K, + prop_kind_t pk, + void* code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , alpha(A), k(K) +{ + static const uint32_t mask[] = { + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0, 0, 0, 0, 0, 0, 0 + }; + + Xbyak::Reg64 c = r10; + + Xbyak::Xmm xmask_lo = xmm2; + Xbyak::Xmm xmask_hi = xmm3; + Xbyak::Xmm xsum_lo = xmm4; + Xbyak::Xmm xsum_hi = xmm5; + Xbyak::Xmm xa_lo = xmm6; + Xbyak::Xmm xa_hi = xmm7; + Xbyak::Xmm xb_lo = xmm8; + Xbyak::Xmm xb_hi = xmm9; + Xbyak::Xmm xc_lo = xmm10; + Xbyak::Xmm xc_hi = xmm11; + Xbyak::Xmm xd_lo = xmm12; + Xbyak::Xmm xd_hi = xmm13; + Xbyak::Xmm xe_lo = xmm14; + Xbyak::Xmm xe_hi = xmm15; + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(dst, ptr[this->param1 + 8]); + if (pk != prop_kind::forward_inference) + mov(scratch, ptr[this->param1 + 16]); + + sub(rsp, stack_space_needed); + mov(store_addr, rsp); + and_(store_addr, -15); + + mov(imm_addr64, float2int(this->alpha)); + movq(xalpha, imm_addr64); + shufps(xalpha, xalpha, 0); + + mov(imm_addr64, float2int(this->k)); + movq(xk, imm_addr64); + shufps(xk, xk, 0); + + // put alpha and k into store (free up regs) + movaps(ptr[store_addr + 0 * 4 * sizeof(float)], xalpha); + movaps(ptr[store_addr + 1 * 4 * sizeof(float)], xk); + + if (J.tail != 0) + { + mov(imm_addr64, reinterpret_cast(&mask[7 - J.tail])); + movups(xmask_lo, ptr[imm_addr64]); + movups(xmask_hi, ptr[imm_addr64 + 4 * sizeof(float)]); + } + // init xa, xb + xorps(xa_lo, xa_lo); + xorps(xa_hi, xa_hi); + xorps(xb_lo, xb_lo); + xorps(xb_hi, xb_hi); + + // read xc, xd + if (J.tail != 0) { + movups(xc_lo, ptr[src + J.HW * 0]); + movups(xc_hi, ptr[src + J.HW * 0 + 4 * sizeof(float)]); + andps(xc_lo, xmask_lo); + andps(xc_hi, xmask_hi); + } + else { + movups(xc_lo, ptr[src + J.HW * 0]); + movups(xc_hi, ptr[src + J.HW * 0 + 4 * sizeof(float)]); + } + if (J.tail != 0) { + movups(xd_lo, ptr[src + J.HW * 4]); + movups(xd_hi, ptr[src + J.HW * 4 + 4 * sizeof(float)]); + andps(xd_lo, xmask_lo); + andps(xd_hi, xmask_hi); + } + else { + movups(xd_lo, ptr[src + J.HW * 4]); + movups(xd_hi, ptr[src + J.HW * 4 + 4 * sizeof(float)]); + } + + // put xa, xb, xc, xd into store to free-up regs + movaps(ptr[store_addr + 2 * 4 * sizeof(float)], xa_lo); + movaps(ptr[store_addr + 3 * 4 * sizeof(float)], xa_hi); + movaps(ptr[store_addr + 4 * 4 * sizeof(float)], xb_lo); + movaps(ptr[store_addr + 5 * 4 * sizeof(float)], xb_hi); + movaps(ptr[store_addr + 6 * 4 * sizeof(float)], xc_lo); + movaps(ptr[store_addr + 7 * 4 * sizeof(float)], xc_hi); + movaps(ptr[store_addr + 8 * 4 * sizeof(float)], xd_lo); + movaps(ptr[store_addr + 9 * 4 * sizeof(float)], xd_hi); + + xorps(xsum_lo, xsum_lo); + xorps(xsum_hi, xsum_hi); + mulps(xc_lo, xc_lo); + mulps(xc_hi, xc_hi); + addps(xsum_lo, xc_lo); + addps(xsum_hi, xc_hi); + mulps(xd_lo, xd_lo); + mulps(xd_hi, xd_hi); + addps(xsum_lo, xd_lo); + addps(xsum_hi, xd_hi); // xsum <- xsum + xa^2+xb^2+xc^2+xd^2+xe^2 + + mov(c, J.C - 2); + Label lrn_loop; + L(lrn_loop); + + if (J.tail != 0) { + movups(xe_lo, ptr[src + J.HW * 8]); + movups(xe_hi, ptr[src + J.HW * 8 + 4 * sizeof(float)]); + andps(xe_lo, xmask_lo); + andps(xe_hi, xmask_hi); + } + else { + movups(xe_lo, ptr[src + J.HW * 8]); + movups(xe_hi, ptr[src + J.HW * 8 + 4 * sizeof(float)]); + } + + nchw_body_sse42(J.tail, J.HW, pk, xmask_lo, xmask_hi, + xe_lo, xe_hi, + xsum_lo, xsum_hi); + + add(src, J.HW * 4); + add(dst, J.HW * 4); + if (pk != prop_kind::forward_inference) + add(scratch, J.HW * 4); + dec(c); + cmp(c, 0); + jne(lrn_loop, T_NEAR); + + xorps(xe_lo, xe_lo); + xorps(xe_hi, xe_hi); + + nchw_body_sse42(J.tail, J.HW, pk, xmask_lo, xmask_hi, + xe_lo, xe_hi, + xsum_lo, xsum_hi); + add(src, J.HW * 4); + add(dst, J.HW * 4); + if (pk != prop_kind::forward_inference) + add(scratch, J.HW * 4); + + nchw_body_sse42(J.tail, J.HW, pk, xmask_lo, xmask_hi, + xe_lo, xe_hi, + xsum_lo, xsum_hi); + + add(rsp, stack_space_needed); + + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +////////////////////////////////////////////////////////////////////////////// +// backward kernel +template +jit_uni_lrn_bwd_kernel_f32::jit_uni_lrn_bwd_kernel_f32( + const struct nchw8c_across &J, + float A, + float B, + int use_h_parallel, + void *code_ptr, + size_t code_size) + : jit_generator(code_ptr, code_size) + , nalphabeta(-2 * A*B) + , use_h_parallelizm(use_h_parallel) +{ + Xbyak::Reg64 t = rsp; + Xbyak::Reg64 hw = r10; + + Xbyak::Xmm xsrc_prev = xmm1; + Xbyak::Xmm xws_prev = xmm2; + Xbyak::Xmm xdiffdst_prev = xmm3; + Xbyak::Ymm ysrc = ymm4; + Xbyak::Ymm yws = ymm5; + Xbyak::Ymm ydiffdst = ymm6; + Xbyak::Xmm xsrc_next = xmm7; + Xbyak::Xmm xws_next = xmm8; + Xbyak::Xmm xdiffdst_next = xmm9; + Xbyak::Ymm ya = ymm10; + Xbyak::Xmm xa = xmm10; + Xbyak::Ymm yb = ymm11; + Xbyak::Ymm yd = ymm12; + Xbyak::Ymm ye = ymm13; + Xbyak::Ymm ysum = ymm14; + Xbyak::Ymm ydiffsrc = ymm15; + + this->preamble(); + + mov(src, ptr[this->param1 + 0]); + mov(diffdst, ptr[this->param1 + 8]); + mov(workspace, ptr[this->param1 + 16]); + mov(diffsrc, ptr[this->param1 + 24]); + + sub(t, 64); + mov(imm_addr64, float2int(this->nalphabeta)); + movq(xnalphabeta, imm_addr64); + vbroadcastss(ynalphabeta, xnalphabeta); + + bool is_single = J.version == 3; + bool is_first = J.version == -1 || J.version == -2; + bool is_last = J.version == +1 || J.version == -2; + + if (is_first || is_single) { + vxorps(xsrc_prev, xsrc_prev, xsrc_prev); + vmovups(ptr[t + 0], xsrc_prev); + } + if (is_last || is_single) { + vxorps(xsrc_next, xsrc_next, xsrc_next); + vmovups(ptr[t + 48], xsrc_next); + } + mov(hw, this->use_h_parallelizm ? J.W : J.H*J.W); + Label lrn_loop; + L(lrn_loop); + { + if (!is_first && !is_single) { + vmovups(xws_prev, ptr[workspace - J.H*J.W * 32 + 16]); + vmovups(xsrc_prev, ptr[src - J.H*J.W * 32 + 16]); + vmovups(xdiffdst_prev, ptr[diffdst - J.H*J.W * 32 + 16]); + vmulps(xa, xws_prev, xws_prev); + vmulps(xa, xa, xws_prev); + vsqrtps(xa, xa); + vsqrtps(xa, xa); + vmulps(xa, xa, xws_prev); + vdivps(xsrc_prev, xsrc_prev, xa); + vmulps(xdiffdst_prev, xdiffdst_prev, xsrc_prev); + } + + vmovups(ysrc, ptr[src]); + vmovups(yws, ptr[workspace]); + vmovups(ydiffdst, ptr[diffdst]); + vmulps(ya, yws, yws); + vmulps(ya, ya, yws); + vsqrtps(ya, ya); + vsqrtps(ya, ya); + vdivps(ydiffsrc, ydiffdst, ya); + vdivps(ysum, ydiffsrc, yws); + vmulps(ysum, ysum, ysrc); + + if (!is_last && !is_single) { + vmovups(xws_next, ptr[workspace + J.H*J.W * 32]); + vmovups(xsrc_next, ptr[src + J.H*J.W * 32]); + vmovups(xdiffdst_next, ptr[diffdst + J.H*J.W * 32]); + vmulps(xa, xws_next, xws_next); + vmulps(xa, xa, xws_next); + vsqrtps(xa, xa); + vsqrtps(xa, xa); + vmulps(xa, xa, xws_next); + vdivps(xsrc_next, xsrc_next, xa); + vdivps(xsrc_next, xsrc_next, xws_next); + vmulps(xdiffdst_next, xdiffdst_next, xsrc_next); + } + + if (!is_first && !is_single) vmovups(ptr[t + 0], xdiffdst_prev); + vmovups(ptr[t + 16], ysum); + if (!is_last && !is_single) vmovups(ptr[t + 48], xdiffdst_next); + + vmovups(ya, ptr[t + 16 - 8]); + vmovups(yb, ptr[t + 16 - 4]); + vaddps(ysum, ysum, ya); + vmulps(ysrc, ysrc, ynalphabeta); + vaddps(ysum, ysum, yb); + + vmovups(yd, ptr[t + 16 + 4]); + vmovups(ye, ptr[t + 16 + 8]); + vaddps(ysum, ysum, yd); + vaddps(ysum, ysum, ye); + + vfmadd231ps(ydiffsrc, ysum, ysrc); + + vmovups(ptr[diffsrc], ydiffsrc); + + add(src, 32); + add(diffsrc, 32); + add(diffdst, 32); + add(workspace, 32); + + dec(hw); + cmp(hw, 0); + jne(lrn_loop, T_NEAR); + } + + add(t, 64); + this->postamble(); + + ker = reinterpret_cast(const_cast( + this->getCode())); +} + +template struct jit_uni_lrn_fwd_kernel_f32; +template struct jit_uni_lrn_fwd_kernel_f32; +template struct jit_uni_lrn_bwd_kernel_f32; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.hpp new file mode 100644 index 000000000000..2b3ed43cd4b7 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_lrn_kernel_f32.hpp @@ -0,0 +1,183 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_UNI_LRN_KERNEL_F32_HPP +#define CPU_JIT_UNI_LRN_KERNEL_F32_HPP + +#include "c_types_map.hpp" +#include "type_helpers.hpp" + +#include "jit_generator.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + +enum params { VECTOR_LENGTH = 8, MAX_LOCAL_SIZE = 32 }; + +typedef struct { + const float *src; + float *dst, *scratch; +} jit_args_fwd_t; + +typedef struct { + const float *src, *diff_dst, *scratch; + float *diff_src; +} jit_args_bwd_t; + +struct nchw8c_across { + /* version: + * -1: channels 0..7, + * 1: channels C-8 .. C-1, + * 0: other channels + * 3: channels only for this kernel(without prev and next) + */ + int H, W, version; + nchw8c_across(int h, int w, int v) : H(h), W(w), version(v) {} +}; + +struct nchw8c_within { + int H, W, size; + nchw8c_within(int h, int w, int s) : H(h), W(w), size(s) {} +}; + +struct nchw_across { + int C, HW, tail; + nchw_across(int c, int hw, int t) : C(c), HW(hw), tail(t) {} +}; + +struct nhwc_across { + int C; + nhwc_across(int c) : C(c) {} +}; + +template +struct jit_uni_lrn_fwd_kernel_f32 : public jit_generator { + Xbyak::Reg64 src = rax; + Xbyak::Reg64 dst = r8; + Xbyak::Reg64 scratch = rdx; + Xbyak::Reg64 imm_addr64 = rbx; + Xbyak::Reg64 store_addr = rbp; + + Xbyak::Xmm xalpha = xmm0; + Xbyak::Ymm yalpha = ymm0; + Xbyak::Xmm xk = xmm1; + Xbyak::Ymm yk = ymm1; + + float alpha; + float k; + + int stack_space_needed = 11 * 4 * sizeof(float) + 16; + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_lrn_fwd_kernel_f32) + + /* cpu specific part */ + using Vmm = typename utils::conditional::type; + + jit_uni_lrn_fwd_kernel_f32( + const struct nchw8c_within &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr = nullptr, + size_t code_size = 4 * Xbyak::DEFAULT_MAX_CODE_SIZE); + jit_uni_lrn_fwd_kernel_f32( + const struct nchw8c_across &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr = nullptr, + size_t code_size = 1 * Xbyak::DEFAULT_MAX_CODE_SIZE); + jit_uni_lrn_fwd_kernel_f32( + const struct nhwc_across &J, + float A, + float K, + prop_kind_t pk, + void *code_ptr = nullptr, + size_t code_size = 1 * Xbyak::DEFAULT_MAX_CODE_SIZE); + jit_uni_lrn_fwd_kernel_f32( + struct nchw_across J, + float A, + float K, + prop_kind_t pk, + void* code_ptr = nullptr, + size_t code_size = 2 * Xbyak::DEFAULT_MAX_CODE_SIZE); + + void within_body( + int hoff, int Hoff, int woff, int Woff, int stride, + Xbyak::Ymm ysum, Xbyak::Ymm ydst, Xbyak::Ymm ytmp, Xbyak::Ymm ysum2, + prop_kind_t pk); + void within_body_sse42( + int hoff, int Hoff, int woff, int Woff, int stride, prop_kind_t pk); + + + void nchw_body(int tail, int HW, prop_kind_t pk, + Xbyak::Ymm ymask, + Xbyak::Ymm ya, + Xbyak::Ymm yb, + Xbyak::Ymm yc, + Xbyak::Ymm yd, + Xbyak::Ymm ye, + Xbyak::Ymm ysum); + void nchw_body_sse42(int tail, int HW, prop_kind_t pk, + Xbyak::Xmm xmask_lo, Xbyak::Xmm xmask_hi, + Xbyak::Xmm xe_lo, Xbyak::Xmm xe_hi, + Xbyak::Xmm xsum_lo, Xbyak::Xmm xsum_hi); + void nchw_tail_sse42(int tail, Xbyak::Reg64 reg_dst, + Xbyak::Xmm xtail_lo, Xbyak::Xmm xtail_hi); + + void operator()(jit_args_fwd_t *arg) { ker(arg); } + void(*ker)(jit_args_fwd_t *); +}; + +template +struct jit_uni_lrn_bwd_kernel_f32 : public jit_generator { + Xbyak::Reg64 src = rax; + Xbyak::Reg64 diffsrc = r8; + Xbyak::Reg64 diffdst = r9; + Xbyak::Reg64 workspace = rdx; + Xbyak::Reg64 imm_addr64 = rsi; + + Xbyak::Xmm xnalphabeta = xmm0; + Xbyak::Ymm ynalphabeta = ymm0; + + float nalphabeta; + + int use_h_parallelizm; + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_lrn_bwd_kernel_f32) + + jit_uni_lrn_bwd_kernel_f32( + const struct nchw8c_across &J, + float A, + float B, + int use_h_parallel, + void *code_ptr = nullptr, + size_t code_size = 1 * Xbyak::DEFAULT_MAX_CODE_SIZE); + + void operator()(jit_args_bwd_t *arg) { ker(arg); } + void(*ker)(jit_args_bwd_t *); +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp new file mode 100644 index 000000000000..bf8e609d23db --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp @@ -0,0 +1,699 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* Copyright 2018 YANDEX LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "utils.hpp" +#include "cpu_pooling_pd.hpp" + +#include "jit_uni_pool_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; +using namespace alg_kind; + +#define GET_OFF(field) offsetof(jit_pool_call_s, field) + +template +status_t jit_uni_pool_kernel_f32::init_conf(jit_pool_conf_t &jpp, + const pooling_pd_t *ppd) { + const auto &pd = *ppd->desc(); + const memory_desc_wrapper src_d( + ppd->is_fwd() ? ppd->src_md() : ppd->diff_src_md()); + const memory_desc_wrapper dst_d( + ppd->is_fwd() ? ppd->dst_md() : ppd->diff_dst_md()); + + bool args_ok = true + && mayiuse(isa) + && utils::one_of(pd.alg_kind, pooling_max, + pooling_avg_include_padding, + pooling_avg_exclude_padding); + if (!args_ok) return status::unimplemented; + + const int simd_w = isa == avx512_common ? 16 : 8; + const int ndims = src_d.ndims(); + + jpp.ndims = ndims; + jpp.mb = src_d.dims()[0]; + + jpp.c = utils::rnd_up(src_d.dims()[1], simd_w); + if (jpp.c > src_d.padded_dims()[1]) + return status::unimplemented; + + jpp.id = (ndims == 5) ? src_d.dims()[2] : 1; + jpp.ih = src_d.dims()[ndims-2]; + jpp.iw = src_d.dims()[ndims-1]; + jpp.od = (ndims == 5) ? dst_d.dims()[2] : 1; + jpp.oh = dst_d.dims()[ndims-2]; + jpp.ow = dst_d.dims()[ndims-1]; + + jpp.stride_d = (ndims == 5 ) ? pd.strides[0] : 1; + jpp.stride_h = pd.strides[ndims-4]; + jpp.stride_w = pd.strides[ndims-3]; + jpp.kd = (ndims == 5) ? pd.kernel[0] : 1; + jpp.kh = pd.kernel[ndims-4]; + jpp.kw = pd.kernel[ndims-3]; + + jpp.f_pad = (ndims == 5 ) ? pd.padding[0][0] : 0; + jpp.t_pad = pd.padding[0][ndims-4]; + jpp.l_pad = pd.padding[0][ndims-3]; + + jpp.alg = pd.alg_kind; + + jpp.is_training = pd.prop_kind == prop_kind::forward_training; + jpp.is_backward = pd.prop_kind == prop_kind::backward_data; + jpp.ind_dt = ppd->workspace_md() + ? ppd->workspace_md()->data_type : data_type::undef; + + jpp.simple_alg = jpp.is_training + || IMPLICATION(jpp.is_backward, jpp.kd <= jpp.stride_d); + + jpp.c_block = simd_w; + + jpp.nb_c = jpp.c / jpp.c_block; + if (jpp.alg == pooling_max) { + jpp.ur_w = isa == avx512_common ? 16 : 4; + if (jpp.is_training) + jpp.ur_w = isa == avx512_common ? 9 : 3; + else if (jpp.is_backward) + jpp.ur_w = isa == avx512_common ? 6 : 3; + } else { + if (jpp.is_backward) + jpp.ur_w = isa == avx512_common ? 12 : 6; + else + jpp.ur_w = isa == avx512_common ? 24 : 12; + } + if (jpp.ow < jpp.ur_w) jpp.ur_w = jpp.ow; + if (jpp.l_pad > jpp.ur_w) return status::unimplemented; + + jpp.ur_w_tail = jpp.ow % jpp.ur_w; + + return status::success; +} + +template +inline void jit_uni_pool_kernel_f32::maybe_recalculate_divisor(int jj, + int ur_w, int pad_l, int pad_r) { + if (jpp.alg == pooling_avg_exclude_padding) { + int kw = jpp.kw; + int stride_w = jpp.stride_w; + + int non_zero_kw = kw; + non_zero_kw -= nstl::max(0, pad_l - jj*stride_w); + non_zero_kw -= nstl::max(0, pad_r - (ur_w - 1 - jj)*stride_w); + + if (non_zero_kw != prev_kw) { + mov(tmp_gpr, float2int((float)non_zero_kw)); + movq(xmm_tmp, tmp_gpr); + uni_vbroadcastss(vmm_tmp, xmm_tmp); + uni_vmulps(vmm_tmp, vmm_tmp, vmm_ker_area_h); + prev_kw = non_zero_kw; + } + } +} + +template +inline void jit_uni_pool_kernel_f32::avg_step(int ur_w, int pad_l, + int pad_r) { + + int iw = jpp.iw; + int kw = jpp.kw; + int stride_w = jpp.stride_w; + int c_block = jpp.c_block; + Label kd_label, kh_label; + + for (int jj = 0; jj < ur_w; jj++) { + if (jpp.is_backward) { + uni_vmovups(vreg(jj), ptr[reg_output + sizeof(float)*jj*c_block]); + maybe_recalculate_divisor(jj, ur_w, pad_l, pad_r); + uni_vdivps(vreg(jj), vreg(jj), vmm_tmp); + } else { + uni_vpxor(vreg(jj), vreg(jj), vreg(jj)); + } + } + + if (jpp.simple_alg && jpp.ndims == 5) { + push(reg_input); + push(reg_output); + mov(aux_reg_input_d, reg_input); + mov(ki, ptr[reg_param + GET_OFF(kd_padding)]); + L(kd_label); + mov(aux_reg_input, aux_reg_input_d); + } else { + mov(aux_reg_input, reg_input); + } + + xor_(kj, kj); + L(kh_label); + { + for (int ki = 0; ki < kw; ki++) { + int jj_start = nstl::max(0, pad_l - ki); + int jj_end = ur_w + - utils::div_up(nstl::max(0, ki + pad_r - (kw-1)), stride_w); + for (int jj = jj_start; jj < jj_end; jj++) { + int aux_input_offset = (ki+jj*stride_w-pad_l)* c_block; + if (aux_input_offset > iw * c_block) + continue; + int input_offset = sizeof(float)*aux_input_offset; + if (jpp.is_backward) { + uni_vmovups(vreg(ur_w+jj), + ptr[aux_reg_input + input_offset]); + uni_vaddps(vreg(ur_w+jj), vreg(ur_w+jj), vreg(jj)); + uni_vmovups(vmmword[aux_reg_input + input_offset], + vreg(ur_w+jj)); + } else { + uni_vaddps(vreg(jj), vreg(jj), + ptr[aux_reg_input + input_offset]); + } + } + } + add(aux_reg_input, sizeof(float) * iw * c_block); + inc(kj); + cmp(kj, reg_kh); + jl(kh_label, T_NEAR); + } + + if (jpp.simple_alg && jpp.ndims == 5) + { + add(aux_reg_input_d, sizeof(float) * jpp.ih * iw * c_block); + dec(ki); + cmp(ki, 0); + jg(kd_label, T_NEAR); + pop(reg_output); + pop(reg_input); + } + + if (!jpp.is_backward) { + for (int jj = 0; jj < ur_w; jj++) { + maybe_recalculate_divisor(jj, ur_w, pad_l, pad_r); + uni_vdivps(vreg(jj), vreg(jj), vmm_tmp); + uni_vmovups(vmmword[reg_output + sizeof(float)*jj*c_block], + vreg(jj)); + } + } +} + +template +inline void jit_uni_pool_kernel_f32::max_step_fwd(int ur_w, int pad_l, + int pad_r) { + int iw = jpp.iw; + int kw = jpp.kw; + int stride_w = jpp.stride_w; + int c_block = jpp.c_block; + Label kd_label, kh_label; + + mov(tmp_gpr, float2int(nstl::numeric_limits::lowest())); + movq(xmm_tmp, tmp_gpr); + uni_vbroadcastss(vmm_tmp, xmm_tmp); + + for (int jj = 0; jj < ur_w; jj++) { + uni_vmovups(vreg(jj), vmm_tmp); + if (jpp.is_training) + uni_vpxor(vreg(2*ur_w+jj), vreg(2*ur_w+jj), vreg(2*ur_w+jj)); + } + if (jpp.is_training) + { + movq(xmm_tmp, reg_k_shift); + uni_vpbroadcastd(vmm_k_offset, xmm_tmp); + } + + if (jpp.ndims == 5) { + push(reg_input); + push(reg_output); + mov(aux_reg_input_d, reg_input); + mov(ki, ptr[reg_param + GET_OFF(kd_padding)]); + L(kd_label); + mov(aux_reg_input, aux_reg_input_d); + } else { + mov(aux_reg_input, reg_input); + } + xor_(kj, kj); + L(kh_label); + { + for (int ki = 0; ki < kw; ki++) { + int jj_start = nstl::max(0, pad_l - ki); + int jj_end = ur_w + - utils::div_up(nstl::max(0, ki + pad_r - (kw-1)), stride_w); + for (int jj = jj_start; jj < jj_end; jj++) { + int aux_input_offset = (ki+jj*stride_w-pad_l)* c_block; + if (aux_input_offset > iw * c_block) + continue; + int input_offset = sizeof(float)*aux_input_offset; + uni_vmovups(vreg(ur_w+jj), ptr[aux_reg_input + input_offset]); + if (isa == sse42) { + movups(vmm_mask, vreg(jj)); + cmpps(vmm_mask, vreg(ur_w+jj), _cmp_lt_os); + blendvps(vreg(jj), vreg(ur_w+jj)); + if (jpp.is_training) + blendvps(vreg(2*ur_w+jj), vmm_k_offset); + } else if (isa == avx) { + vcmpps(vreg(3*ur_w+jj), vreg(jj), vreg(ur_w+jj), + _cmp_lt_os); + vblendvps(vreg(jj), vreg(jj), vreg(ur_w+jj), + vreg(3*ur_w+jj)); + if (jpp.is_training) + vblendvps(vreg(2*ur_w+jj), vreg(2*ur_w+jj), + vmm_k_offset, vreg(3*ur_w+jj)); + } else { + vcmpps(k_store_mask, vreg(jj), vreg(ur_w+jj), _cmp_lt_os); + vblendmps(vreg(jj) | k_store_mask, vreg(jj), vreg(ur_w+jj)); + if (jpp.is_training) + vblendmps(vreg(2*ur_w+jj) | k_store_mask, + vreg(2*ur_w+jj), vmm_k_offset); + } + } + if (jpp.is_training) { + if (isa == avx && !mayiuse(avx2)) { + avx_vpadd1(vmm_k_offset, vmm_one, xmm_tmp); + } else { + uni_vpaddd(vmm_k_offset, vmm_k_offset, vmm_one); + } + } + } + add(aux_reg_input, sizeof(float) * iw * c_block); + inc(kj); + cmp(kj, reg_kh); + jl(kh_label, T_NEAR); + } + + if (jpp.ndims == 5) + { + add(aux_reg_input_d, sizeof(float) * jpp.ih * iw * c_block); + if (jpp.is_training) { + mov(tmp_gpr, ptr[reg_param + GET_OFF(kd_padding_shift)]); + movq(xmm_tmp, tmp_gpr); + uni_vpbroadcastd(vmm_tmp, xmm_tmp); + if (isa == avx && !mayiuse(avx2)) { + Xmm t(vmm_mask.getIdx()); + avx_vpadd1(vmm_k_offset, xmm_tmp, t); + } else { + uni_vpaddd(vmm_k_offset, vmm_k_offset, vmm_tmp); + } + } + + dec(ki); + cmp(ki, 0); + jg(kd_label, T_NEAR); + pop(reg_output); + pop(reg_input); + } + + for (int jj = 0; jj < ur_w; jj++) { + uni_vmovups(vmmword[reg_output + sizeof(float)*jj*c_block], vreg(jj)); + if (jpp.is_training) { + const size_t step_index + = jj * c_block * types::data_type_size(jpp.ind_dt); + + auto x = xreg(2 * ur_w + jj); + if (jpp.ind_dt == data_type::u8) { + if (isa == sse42) { + for (int i = 0; i < 4; ++i) + pextrb(ptr[reg_index + step_index + i], x, 4*i); + } else if (isa == avx) { + auto y = yreg(2 * ur_w + jj); + if (jj == 0) { + movd(xmm_tmp, reg_shuf_mask); + uni_vpbroadcastd(vmm_tmp, xmm_tmp); + } + if (mayiuse(avx2)) { + vpshufb(y, y, vmm_tmp); + movd(ptr[reg_index + step_index], x); + vperm2i128(y, y, y, 0x1u); + movd(ptr[reg_index + step_index + 4], x); + } else { + Xmm t(vmm_mask.getIdx()); + vextractf128(t, y, 0); + vpshufb(t, t, xmm_tmp); + movd(ptr[reg_index + step_index], t); + vextractf128(t, y, 1); + vpshufb(t, t, xmm_tmp); // ymm_tmp[:128]==ymm_tmp[127:0] + movd(ptr[reg_index + step_index + 4], t); + } + } else { + auto v = vreg(2 * ur_w + jj); + vpmovusdb(x, v); + vmovups(ptr[reg_index + step_index], v | k_index_mask); + } + } else { + uni_vmovups(ptr[reg_index + step_index], vreg(2*ur_w+jj)); + } + } + } +} + +template +inline void jit_uni_pool_kernel_f32::max_step_bwd(int ur_w, int pad_l, + int pad_r) { + + int iw = jpp.iw; + int kw = jpp.kw; + int stride_w = jpp.stride_w; + int c_block = jpp.c_block; + Label kd_label, kh_label; + + for (int jj = 0; jj < ur_w; jj++) { + uni_vmovups(vreg(jj), ptr[reg_output + sizeof(float)*jj*c_block]); + + const size_t step_index + = jj * c_block * types::data_type_size(jpp.ind_dt); + if (jpp.ind_dt == data_type::u8) { + if (isa == sse42) { + movd(xreg(ur_w+jj), ptr[reg_index + step_index]); + pmovzxbd(vreg(ur_w+jj), xreg(ur_w+jj)); + } else if (isa == avx) { + movq(xreg(ur_w+jj), ptr[reg_index + step_index]); + if (!mayiuse(avx2)) { + avx_pmovzxbd(vreg(ur_w+jj), xreg(ur_w+jj), xmm_tmp); + } else { + vpmovzxbd(vreg(ur_w+jj), xreg(ur_w+jj)); + } + } else { + vmovups(vreg(ur_w+jj) | k_index_mask, + ptr[reg_index + step_index]); + vpmovzxbd(vreg(ur_w+jj), xreg(ur_w+jj)); + } + } else { + uni_vmovups(vreg(ur_w+jj), ptr[reg_index + step_index]); + } + } + movq(xmm_tmp, reg_k_shift); + uni_vpbroadcastd(vmm_k_offset, xmm_tmp); + + if (jpp.simple_alg && jpp.ndims == 5) { + push(reg_input); + push(reg_output); + if (isa == sse42) { + // Save rdi since it is used in maskmovdqu + assert(dst_ptr == rdi); + push(dst_ptr); + } + mov(aux_reg_input_d, reg_input); + mov(ki, ptr[reg_param + GET_OFF(kd_padding)]); + mov(reg_kd_pad_shift, ptr[reg_param + GET_OFF(kd_padding_shift)]); + L(kd_label); + mov(aux_reg_input, aux_reg_input_d); + } else { + mov(aux_reg_input, reg_input); + } + + xor_(kj, kj); + L(kh_label); + { + for (int ki = 0; ki < kw; ki++) { + int jj_start = nstl::max(0, pad_l - ki); + int jj_end = ur_w + - utils::div_up(nstl::max(0, ki + pad_r - (kw-1)), stride_w); + for (int jj = jj_start; jj < jj_end; jj++) { + int aux_input_offset = (ki+jj*stride_w-pad_l)* c_block; + if (aux_input_offset > iw * c_block) + continue; + int input_offset = sizeof(float)*aux_input_offset; + uni_vmovups(vreg(2*ur_w+jj), ptr[aux_reg_input + input_offset]); + if (isa == sse42) { + mov(dst_ptr, aux_reg_input); + add(dst_ptr, input_offset); + + movups(vreg(3*ur_w+jj), vreg(ur_w+jj)); + pcmpeqd(vreg(3*ur_w+jj), vmm_k_offset); + addps(vreg(2*ur_w+jj), vreg(jj)); + maskmovdqu(vreg(2*ur_w+jj), vreg(3*ur_w+jj)); + } else if (isa == avx) { + if (mayiuse(avx2)) { + vpcmpeqd(vreg(3*ur_w+jj), vreg(ur_w+jj), vmm_k_offset); + } else { + avx_pcmpeqd(vreg(3*ur_w+jj), vreg(ur_w+jj), vmm_k_offset, xmm_tmp); + } + vaddps(vreg(2*ur_w+jj), vreg(2*ur_w+jj), vreg(jj)); + vmaskmovps(vmmword[aux_reg_input + input_offset], + vreg(3*ur_w+jj), vreg(2*ur_w+jj)); + } else { + vpcmpeqd(k_store_mask, vreg(ur_w+jj), vmm_k_offset); + vblendmps(vmm_tmp | k_store_mask | T_z, vreg(jj), vreg(jj)); + vaddps(vreg(2*ur_w+jj), vreg(2*ur_w+jj), vmm_tmp); + vmovups(vmmword[aux_reg_input + + sizeof(float)*aux_input_offset], vreg(2*ur_w+jj)); + } + } + if (isa == avx && !mayiuse(avx2)) { + avx_vpadd1(vmm_k_offset, vmm_one, xmm_tmp); + } else { + uni_vpaddd(vmm_k_offset, vmm_k_offset, vmm_one); + } + } + add(aux_reg_input, sizeof(float) * iw * c_block); + inc(kj); + cmp(kj, reg_kh); + jl(kh_label, T_NEAR); + } + if (jpp.simple_alg && jpp.ndims == 5) + { + add(aux_reg_input_d, sizeof(float) * jpp.ih * iw * c_block); + + mov(tmp_gpr, reg_kd_pad_shift); + movq(xmm_tmp, tmp_gpr); + uni_vpbroadcastd(vmm_tmp, xmm_tmp); + if (isa == avx && !mayiuse(avx2)) { + Xmm t(vmm_mask.getIdx()); + avx_vpadd1(vmm_k_offset, vmm_tmp, t); + } else { + uni_vpaddd(vmm_k_offset, vmm_k_offset, vmm_tmp); + } + + dec(ki); + cmp(ki, 0); + jg(kd_label, T_NEAR); + if (isa == sse42) { + // Save rdi since it is used in maskmovdqu + assert(dst_ptr == rdi); + pop(dst_ptr); + } + pop(reg_output); + pop(reg_input); + } +} + +template +void jit_uni_pool_kernel_f32::maybe_zero_diff_src() { + assert(jpp.c_block * sizeof(float) % cpu_isa_traits::vlen == 0); + Label l_skip, l_zero; + + auto reg_oh = tmp_gpr; + mov(reg_oh, ptr[reg_param + GET_OFF(oh)]); + cmp(reg_oh, 0); + jz(l_skip, T_NEAR); + + if (jpp.ndims == 5) { + mov(zero_size, ptr[reg_param + GET_OFF(oh)]); + mov(tmp_gpr, jpp.ih * jpp.iw * jpp.c_block * sizeof(float)); + imul(zero_size, tmp_gpr); + } + + auto vzero = vmm_tmp; + uni_vpxor(vzero, vzero, vzero); + + auto reg_off = tmp_gpr; + xor_(reg_off, reg_off); + + L(l_zero); + { + const int dim = jpp.iw * jpp.c_block * sizeof(float); + for (int i = 0; i < dim; i += cpu_isa_traits::vlen) + uni_vmovups(ptr[reg_input + reg_off + i], vzero); + add(reg_off, dim); + if (jpp.ndims == 5) cmp(reg_off, zero_size); + else cmp(reg_off, jpp.ih * dim); + jl(l_zero, T_NEAR); + } + + L(l_skip); +} + +template +void jit_uni_pool_kernel_f32::generate() { + + this->preamble(); + + int ow = jpp.ow; + int iw = jpp.iw; + int kw = jpp.kw; + int kh = jpp.kh; + int ur_w = jpp.ur_w; + int c_block = jpp.c_block; + int stride_w = jpp.stride_w; + int l_pad = jpp.l_pad; + int ur_w_tail = jpp.ur_w_tail; + + int n_oi = ow / ur_w; + + prev_kw = 0; + + int vlen = cpu_isa_traits::vlen; + +#if defined(_WIN32) + // Always mimic the Unix ABI (see the note about maskmovdqu in the header + // file). + xor_(rdi, rcx); + xor_(rcx, rdi); + xor_(rdi, rcx); +#endif + + mov(reg_input, ptr[reg_param + GET_OFF(src)]); + mov(reg_output, ptr[reg_param + GET_OFF(dst)]); + if (jpp.alg == pooling_max && (jpp.is_training || jpp.is_backward)) + mov(reg_index, ptr[reg_param + GET_OFF(indices)]); + mov(reg_kh, ptr[reg_param + GET_OFF(kh_padding)]); + mov(reg_k_shift, ptr[reg_param + GET_OFF(kh_padding_shift)]); + mov(reg_ker_area_h, ptr[reg_param + GET_OFF(ker_area_h)]); + + if (jpp.is_backward) + maybe_zero_diff_src(); + + if (jpp.alg == pooling_max && (jpp.is_training || jpp.is_backward)) { + mov(tmp_gpr, 1); + movq(xmm_one, tmp_gpr); + uni_vpbroadcastd(vmm_one, xmm_one); + + if (isa == avx) { + mov(reg_shuf_mask, 0x0c080400); + } else if (isa >= avx512_common) { + mov(tmp_gpr.cvt32(), 0x000f); + kmovw(k_index_mask, tmp_gpr.cvt32()); + } + } + + int r_pad = nstl::max(0, ((ow-1)*stride_w) + kw - 1 - (iw + l_pad - 1)); + int r_pad1 = (ur_w*n_oi - 1)*stride_w + kw - 1 - (iw + l_pad - 1); + if (r_pad1 > 0) n_oi--; + + if (jpp.alg == pooling_avg_exclude_padding) { + movq(xmm_ker_area_h, reg_ker_area_h); + uni_vpbroadcastd(vmm_ker_area_h, xmm_ker_area_h); + } + + if (jpp.alg == pooling_avg_include_padding) { + mov(tmp_gpr, float2int((float)(kw * kh * jpp.kd))); + movq(xmm_tmp, tmp_gpr); + uni_vpbroadcastd(vmm_tmp, xmm_tmp); + } + if (l_pad > 0) { + n_oi--; + if (n_oi < 0 && r_pad1 > 0) { + step(ur_w, l_pad, r_pad1); + } else { + step(ur_w, l_pad, 0); + } + + if (isa == sse42) { + if (n_oi < 0 && r_pad1 > 0) { + step_high_half(ur_w, l_pad, r_pad1); + } else { + step_high_half(ur_w, l_pad, 0); + } + } + + if (isa == sse42) { + add(reg_input, sizeof(float)*(ur_w*stride_w-l_pad)*c_block - vlen); + add(reg_output, sizeof(float)*ur_w*c_block - vlen); + if (jpp.alg == pooling_max && (jpp.is_training || jpp.is_backward)) + add(reg_index, (2 * ur_w - 1) * c_block / 2 + * types::data_type_size(jpp.ind_dt)); + } else { + add(reg_input, sizeof(float)*(ur_w*stride_w - l_pad)*c_block); + add(reg_output, sizeof(float)*ur_w*c_block); + if (jpp.alg == pooling_max && (jpp.is_training || jpp.is_backward)) + add(reg_index, ur_w * c_block + * types::data_type_size(jpp.ind_dt)); + } + } + + xor_(oi_iter, oi_iter); + if (n_oi > 0) { + Label ow_loop; + L(ow_loop); { + step(ur_w, 0, 0); + + if (isa == sse42) { + step_high_half(ur_w, 0, 0); + } + + if (isa == sse42) { + add(reg_input, sizeof(float)*ur_w*stride_w*c_block - vlen); + add(reg_output, sizeof(float)*ur_w*c_block - vlen); + if (jpp.alg == pooling_max && + (jpp.is_training || jpp.is_backward)) + add(reg_index, (2 * ur_w - 1) * c_block / 2 + * types::data_type_size(jpp.ind_dt)); + } else { + add(reg_input, sizeof(float)*ur_w*stride_w*c_block); + add(reg_output, sizeof(float)*ur_w*c_block); + if (jpp.alg == pooling_max && + (jpp.is_training || jpp.is_backward)) + add(reg_index, ur_w * c_block + * types::data_type_size(jpp.ind_dt)); + } + + inc(oi_iter); + cmp(oi_iter, n_oi); + jl(ow_loop, T_NEAR); + } + } + + if (r_pad1 > 0 && n_oi >= 0) { + step(ur_w, 0, r_pad1); + + if (isa == sse42) { + step_high_half(ur_w, 0, r_pad1); + } + + if (isa == sse42) { + add(reg_input, sizeof(float)*ur_w*stride_w*c_block - vlen); + add(reg_output, sizeof(float)*ur_w*c_block - vlen); + if (jpp.alg == pooling_max && (jpp.is_training || jpp.is_backward)) + add(reg_index, (2 * ur_w - 1) * c_block / 2 + * types::data_type_size(jpp.ind_dt)); + } else { + add(reg_input, sizeof(float)*ur_w*stride_w*c_block); + add(reg_output, sizeof(float)*ur_w*c_block); + if (jpp.alg == pooling_max && (jpp.is_training || jpp.is_backward)) + add(reg_index, ur_w * c_block + * types::data_type_size(jpp.ind_dt)); + } + } + + if (ur_w_tail != 0) { + step(ur_w_tail, 0, r_pad); + + if (isa == sse42) { + step_high_half(ur_w_tail, 0, r_pad); + } + } + + this->postamble(); +} + +template struct jit_uni_pool_kernel_f32; +template struct jit_uni_pool_kernel_f32; // implements both and +template struct jit_uni_pool_kernel_f32; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.hpp new file mode 100644 index 000000000000..992b526587fd --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.hpp @@ -0,0 +1,192 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* Copyright 2018 YANDEX LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_UNI_POOL_KERNEL_F32_HPP +#define JIT_UNI_POOL_KERNEL_F32_HPP + +#include + +#include "c_types_map.hpp" +#include "pooling_pd.hpp" +#include "type_helpers.hpp" + +#include "jit_generator.hpp" +#include "jit_primitive_conf.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace Xbyak; + +template +struct jit_uni_pool_kernel_f32: public jit_generator { + jit_uni_pool_kernel_f32(jit_pool_conf_t ajpp): jpp(ajpp) + { + this->generate(); + jit_ker = (decltype(jit_ker))this->getCode(); + } + + jit_pool_conf_t jpp; + + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_pool_kernel_f32) + + void operator()(jit_pool_call_s *arg) { jit_ker(arg); } + static status_t init_conf(jit_pool_conf_t &jbp, const pooling_pd_t *ppd); + +private: + using Vmm = typename utils::conditional3::type; + Xmm xreg(int idx) { return Xmm((isa == avx512_common ? 31 : 15) - idx); } + Ymm yreg(int idx) { return Ymm(xreg(idx).getIdx()); } + Vmm vreg(int idx) { return Vmm(xreg(idx).getIdx()); } + + const AddressFrame &vmmword = (isa == sse42) ? xword : + (isa == avx) ? yword : zword; + + Xmm vmm_mask = Xmm(0); + Xmm xmm_ker_area_h = Xmm(2); + Xmm xmm_one = Xmm(2); + Xmm xmm_tmp = Xmm(3); + + Vmm vmm_ker_area_h = Vmm(2); + Vmm vmm_one = Vmm(2); + Vmm vmm_tmp = Vmm(3); + + Vmm vmm_k_offset = Vmm(1); + + Opmask k_index_mask = Opmask(6); + Opmask k_store_mask = Opmask(7); + + // Here be some (tame) dragons. This kernel does not follow the regular + // OS-agnostic ABI pattern because when isa is sse42 it uses maskmovdqu + // instruction which has its destination hardcoded in rdi. Therefore: + // - all registers are hardcoded + // - on Windows rdi and rcx are swapped to mimic the Unix x86_64 ABI + // + // While this is only required by the backward pass, the quirk above + // is applied to the forward pass as well to keep things simpler. + + using reg64_t = const Xbyak::Reg64; + reg64_t reg_param = rdi; // Always mimic the Unix ABI + reg64_t reg_input = r8; + reg64_t aux_reg_input = r9; + reg64_t reg_index = r10; + reg64_t reg_output = r12; + reg64_t reg_kd_pad_shift = r13; + reg64_t dst_ptr = rdi; // Must be rdi due to maskmovdqu + + reg64_t kj = r14; + reg64_t oi_iter = r15; + reg64_t reg_kh = rax; + reg64_t reg_k_shift = rbx; + reg64_t tmp_gpr = rcx; // Must be rcx because rdi is used above + reg64_t reg_ker_area_h = rdx; + + reg64_t zero_size = r15; + reg64_t ki = r12; + reg64_t aux_reg_input_d = r8; + + Xbyak::Reg32 reg_shuf_mask = esi; + + int prev_kw; + void (*jit_ker)(jit_pool_call_s *); + + void maybe_recalculate_divisor(int jj, int ur_w, int pad_l, int pad_r); + void avg_step(int ur_w, int pad_l, int pad_r); + void max_step_fwd(int ur_w, int pad_l, int pad_r); + void max_step_bwd(int ur_w, int pad_l, int pad_r); + + void maybe_zero_diff_src(); + + void step(int ur_w, int pad_l, int pad_r) { + if (jpp.alg == alg_kind::pooling_max) { + if(jpp.is_backward) + max_step_bwd(ur_w, pad_l, pad_r); + else + max_step_fwd(ur_w, pad_l, pad_r); + } + else + avg_step(ur_w, pad_l, pad_r); + } + + void step_high_half(int ur_w, int pad_l, int pad_r) { + add(reg_input, sizeof(float) * 4); + add(reg_output, sizeof(float) * 4); + if (jpp.alg == alg_kind::pooling_max && + (jpp.is_training || jpp.is_backward)) + add(reg_index, types::data_type_size(jpp.ind_dt) * 4); + + step(ur_w, pad_l, pad_r); + } + + void generate(); + + void avx_vpadd1(const Ymm& y0, const Xmm& x1, const Xmm& xtmp) { + assert(y0.getIdx() != x1.getIdx()); + vextractf128(xtmp, y0, 0); + vpaddd(xtmp, xtmp, x1); + vinsertf128(y0, y0, xtmp, 0); + vextractf128(xtmp, y0, 1); + vpaddd(xtmp, xtmp, x1); + vinsertf128(y0, y0, xtmp, 1); + } + + void avx_vpadd1(const Xmm& x0, const Xmm& x1, const Xmm&) { + assert(false /*function should not be used*/); + paddd(x0, x1); + } + + void avx_pmovzxbd(const Ymm& y0, const Xmm& x1, const Xmm& xtmp) { + Xmm x0(y0.getIdx()); + pshufd(xmm_tmp, x1, 1); + pmovzxbd(x0, x1); + pmovzxbd(xmm_tmp, xmm_tmp); + vinsertf128(y0, y0, xmm_tmp, 1); + } + + void avx_pmovzxbd(const Xmm& x0, const Xmm& x1, const Xmm&) { + assert(false /*function should not be used*/); + pmovzxbd(x0, x1); + } + + void avx_pcmpeqd(const Ymm& y0, const Ymm& y1, const Ymm& y2, const Xmm& xtmp) { + assert(y0.getIdx() != y1.getIdx()); + assert(y0.getIdx() != y2.getIdx()); + Xmm x0(y0.getIdx()); + Xmm x2(y2.getIdx()); + vextractf128(x0, y1, 1); + vextractf128(xtmp, y2, 1); + pcmpeqd(xtmp, x0); + vextractf128(x0, y1, 0); + pcmpeqd(x0, x2); + vinsertf128(y0, y0, xtmp, 1); + } + + void avx_pcmpeqd(const Xmm& x0, const Xmm& x1, const Xmm&, const Xmm&) { + assert(false /*function should not be used*/); + pcmpeqd(x0, x1); + } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.cpp new file mode 100644 index 000000000000..afbcf996d8ac --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.cpp @@ -0,0 +1,264 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_types.h" + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "nstl.hpp" + +#include "jit_uni_pooling.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +void jit_uni_pooling_fwd_t::execute_forward(const data_t *src, + data_t *dst, char *indices) const { + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper indices_d(pd()->workspace_md()); + const size_t ind_dt_size = indices + ? types::data_type_size(indices_d.data_type()) : 0; + + const auto &jpp = pd()->jpp_; + + auto ker = [&](int n, int b_c, int oh) { + auto arg = jit_pool_call_s(); + + const int ij = oh * jpp.stride_h; + const int i_t_overflow = nstl::max(0, jpp.t_pad-ij); + const int i_b_overflow = nstl::max(jpp.ih, ij+jpp.kh-jpp.t_pad)-jpp.ih; + const int ih = nstl::max(ij - jpp.t_pad, 0); + + arg.src = &src[src_d.blk_off(n, b_c, ih)]; + arg.dst = &dst[dst_d.blk_off(n, b_c, oh)]; + if (indices) { + const size_t ind_off = indices_d.blk_off(n, b_c, oh); + arg.indices = &indices[ind_off * ind_dt_size]; + } + arg.oh = oh == 0; + arg.kh_padding = jpp.kh - i_t_overflow - i_b_overflow; + arg.kh_padding_shift = i_t_overflow*jpp.kw; + arg.kw_padding = 0; + arg.ker_area_h = (float)(jpp.kh - + nstl::max(0, oh*jpp.stride_h - jpp.t_pad + jpp.kh - jpp.ih) - + nstl::max(0, jpp.t_pad - oh*jpp.stride_h)); + (*kernel_)(&arg); + }; + + parallel_nd(jpp.mb, jpp.nb_c, jpp.oh, + [&](int n, int b_c, int oh) { + ker(n, b_c, oh); + }); +} + +template +void jit_uni_pooling_fwd_t::execute_forward_3d(const data_t *src, + data_t *dst, char *indices) const { + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper indices_d(pd()->workspace_md()); + const size_t ind_dt_size = indices + ? types::data_type_size(indices_d.data_type()) : 0; + + const auto &jpp = pd()->jpp_; + + auto ker = [&](int n, int b_c, int od, int oh, int id, int d_t_overflow, + int d_b_overflow) { + auto arg = jit_pool_call_s(); + + const int ij = oh * jpp.stride_h; + const int i_t_overflow = nstl::max(0, jpp.t_pad-ij); + const int i_b_overflow = nstl::max(jpp.ih, ij+jpp.kh-jpp.t_pad)-jpp.ih; + const int ih = nstl::max(ij - jpp.t_pad, 0); + + arg.src = &src[src_d.blk_off(n, b_c, id, ih)]; + arg.dst = &dst[dst_d.blk_off(n, b_c, od, oh)]; + if (indices) { + const size_t ind_off = indices_d.blk_off(n, b_c, od, oh); + arg.indices = &indices[ind_off * ind_dt_size]; + } + arg.oh = (oh + od == 0); + arg.kd_padding = jpp.kd - d_t_overflow - d_b_overflow; + arg.kh_padding = jpp.kh - i_t_overflow - i_b_overflow; + arg.kh_padding_shift = i_t_overflow*jpp.kw + d_t_overflow*jpp.kw*jpp.kh; + arg.kd_padding_shift = (i_t_overflow + i_b_overflow)*jpp.kw; + arg.kw_padding = 0; + arg.ker_area_h = (float)(jpp.kh - + nstl::max(0, oh*jpp.stride_h - jpp.t_pad + jpp.kh - jpp.ih) - + nstl::max(0, jpp.t_pad - oh*jpp.stride_h)) * (jpp.kd - + nstl::max(0, od*jpp.stride_d - jpp.f_pad + jpp.kd - jpp.id) - + nstl::max(0, jpp.f_pad - od*jpp.stride_d)); + + + (*kernel_)(&arg); + }; + + parallel_nd(jpp.mb, jpp.nb_c, jpp.od, + [&](int n, int b_c, int od) { + const int ik = od * jpp.stride_d; + const int d_t_overflow = nstl::max(0, jpp.f_pad-ik); + const int d_b_overflow = nstl::max(jpp.id, ik+jpp.kd-jpp.f_pad) + -jpp.id; + const int id = nstl::max(ik - jpp.f_pad, 0); + for (int oh = 0; oh < jpp.oh; ++oh) { + ker(n, b_c, od, oh, id, d_t_overflow, d_b_overflow); + } + }); +} + +template +void jit_uni_pooling_bwd_t::execute_backward(const data_t *diff_dst, + const char *indices, data_t *diff_src) const { + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper indices_d(pd()->workspace_md()); + const size_t ind_dt_size = indices + ? types::data_type_size(indices_d.data_type()) : 0; + + const auto &jpp = pd()->jpp_; + + auto ker = [&](int n, int b_c, int oh) { + auto arg = jit_pool_call_s(); + + const int ij = oh * jpp.stride_h; + const int i_t_overflow = nstl::max(0, jpp.t_pad-ij); + const int i_b_overflow = nstl::max(jpp.ih, ij+jpp.kh-jpp.t_pad)-jpp.ih; + const int ih = nstl::max(ij - jpp.t_pad, 0); + + arg.src = &diff_src[diff_src_d.blk_off(n, b_c, ih)]; + arg.dst = &diff_dst[diff_dst_d.blk_off(n, b_c, oh)]; + if (indices) { + const size_t ind_off = indices_d.blk_off(n, b_c, oh); + arg.indices = &indices[ind_off * ind_dt_size]; + } + arg.oh = (oh == 0); + arg.kh_padding = jpp.kh - i_t_overflow - i_b_overflow; + arg.kh_padding_shift = i_t_overflow*jpp.kw; + arg.kw_padding = 0; + arg.ker_area_h = (float)(jpp.kh - + nstl::max(0, oh*jpp.stride_h - jpp.t_pad + jpp.kh - jpp.ih) - + nstl::max(0, jpp.t_pad - oh*jpp.stride_h)); + + (*kernel_)(&arg); + }; + + parallel_nd(jpp.mb, jpp.nb_c, [&](int n, int b_c) { + for (int oh = 0; oh < jpp.oh; ++oh) { + ker(n, b_c, oh); + } + }); +} + +template +void jit_uni_pooling_bwd_t::execute_backward_3d(const data_t *diff_dst, + const char *indices, data_t *diff_src) const { + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper indices_d(pd()->workspace_md()); + const size_t ind_dt_size = indices + ? types::data_type_size(indices_d.data_type()) : 0; + + const auto &jpp = pd()->jpp_; + + auto ker = [&](int n, int b_c, int od, int oh, int id, int d_t_overflow, + int d_b_overflow, int zero_size, int kd) { + auto arg = jit_pool_call_s(); + + const int ij = oh * jpp.stride_h; + const int i_t_overflow = nstl::max(0, jpp.t_pad-ij); + const int i_b_overflow = nstl::max(jpp.ih, ij+jpp.kh-jpp.t_pad)-jpp.ih; + const int ih = nstl::max(ij - jpp.t_pad, 0); + + arg.src = &diff_src[diff_src_d.blk_off(n, b_c, id + kd, ih)]; + arg.dst = &diff_dst[diff_dst_d.blk_off(n, b_c, od, oh)]; + if (indices) { + const size_t ind_off = indices_d.blk_off(n, b_c, od, oh); + arg.indices = &indices[ind_off * ind_dt_size]; + } + arg.oh = zero_size; + arg.kd_padding = jpp.kd - d_t_overflow - d_b_overflow; + arg.kh_padding = jpp.kh - i_t_overflow - i_b_overflow; + arg.kh_padding_shift = i_t_overflow*jpp.kw + d_t_overflow*jpp.kw*jpp.kh + + kd * jpp.kw * jpp.kh; + arg.kd_padding_shift = (i_t_overflow + i_b_overflow)*jpp.kw; + arg.kw_padding = 0; + arg.ker_area_h = (float)(jpp.kh - + nstl::max(0, oh*jpp.stride_h - jpp.t_pad + jpp.kh - jpp.ih) - + nstl::max(0, jpp.t_pad - oh*jpp.stride_h)) * (jpp.kd - + nstl::max(0, od*jpp.stride_d - jpp.f_pad + jpp.kd - jpp.id) - + nstl::max(0, jpp.f_pad - od*jpp.stride_d)); + + (*kernel_)(&arg); + }; + + if (jpp.simple_alg) { + + parallel_nd(jpp.mb, jpp.nb_c, jpp.od, + [&](int n, int b_c, int od) { + const int ik = od * jpp.stride_d; + const int d_t_overflow = nstl::max(0, jpp.f_pad - ik); + const int d_b_overflow = nstl::max(jpp.id, ik + jpp.kd + - jpp.f_pad) - jpp.id; + const int id = nstl::max(ik - jpp.f_pad, 0); + int zero_s = jpp.stride_d - d_t_overflow - (nstl::max( + jpp.id, ik + jpp.stride_d - jpp.f_pad) - jpp.id); + for (int oh = 0; oh < jpp.oh; ++oh) { + ker(n, b_c, od, oh, id, d_t_overflow, d_b_overflow, + (oh == 0) ? zero_s : 0, 0); + } + }); + } else { + ptrdiff_t nelems = (ptrdiff_t)jpp.mb * (ptrdiff_t)jpp.c + * (ptrdiff_t)jpp.id * (ptrdiff_t)jpp.ih * (ptrdiff_t)jpp.iw; + + parallel_nd(nelems, [&](ptrdiff_t i) { diff_src[i] = 0.f; }); + + for (int kd = 0; kd < jpp.kd; ++kd) { + parallel_nd(jpp.mb, jpp.nb_c, [&](int n, int b_c) { + for (int od = 0; od < jpp.od; ++od) { + const int ik = od * jpp.stride_d; + const int d_t_overflow = nstl::max(0, jpp.f_pad-ik); + const int d_b_overflow = nstl::max(jpp.id, ik + jpp.kd + - jpp.f_pad) - jpp.id; + if (kd >= jpp.kd - d_t_overflow - d_b_overflow) + continue; + const int id = nstl::max(ik - jpp.f_pad, 0); + for (int oh = 0; oh < jpp.oh; ++oh) { + ker(n, b_c, od, oh, id, d_t_overflow, d_b_overflow, + 0, kd); + } + } + }); + } + } +} + + +template struct jit_uni_pooling_fwd_t; +template struct jit_uni_pooling_bwd_t; +template struct jit_uni_pooling_fwd_t; +template struct jit_uni_pooling_bwd_t; +template struct jit_uni_pooling_fwd_t; +template struct jit_uni_pooling_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.hpp new file mode 100644 index 000000000000..57bebacdeef3 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_pooling.hpp @@ -0,0 +1,182 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_JIT_UNI_POOLING_HPP +#define CPU_JIT_UNI_POOLING_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_pooling_pd.hpp" +#include "cpu_primitive.hpp" + +#include "jit_uni_pool_kernel_f32.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct jit_uni_pooling_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_fwd_pd_t { + using cpu_pooling_fwd_pd_t::cpu_pooling_fwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_pooling_fwd_t); + + status_t init() { + using namespace utils; + + bool ok = true + && set_default_params() == status::success + && is_fwd() + && !has_zero_dim_memory() + && everyone_is(data_type::f32, + src_md()->data_type, + dst_md()->data_type) + && attr()->has_default_values() + && memory_desc_matches_tag(*src_md(), desired_fmt_tag()) + && memory_desc_matches_tag(*dst_md(), desired_fmt_tag()); + if (!ok) return status::unimplemented; + + bool is_training = desc_.prop_kind == prop_kind::forward_training; + if (desc()->alg_kind == alg_kind::pooling_max && is_training) + init_default_ws(); + + return jit_uni_pool_kernel_f32::init_conf(jpp_, this); + } + + format_tag_t desired_fmt_tag() { + using namespace format_tag; + return ndims() == 4 + ? isa == avx512_common ? nChw16c : nChw8c + : isa == avx512_common ? nCdhw16c : nCdhw8c; + } + + jit_pool_conf_t jpp_; + }; + + jit_uni_pooling_fwd_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_uni_pool_kernel_f32(pd()->jpp_); } + + ~jit_uni_pooling_fwd_t() { delete kernel_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(char *, MKLDNN_ARG_WORKSPACE); + + if (pd()->ndims() == 5) + execute_forward_3d(src, dst, ws); + else + execute_forward(src, dst, ws); + + return status::success; + } + +private: + void execute_forward(const data_t *src, data_t *dst, char *indices) const; + void execute_forward_3d(const data_t *src, data_t *dst, + char *indices) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_uni_pool_kernel_f32 *kernel_; +}; + +template +struct jit_uni_pooling_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_bwd_pd_t { + using cpu_pooling_bwd_pd_t::cpu_pooling_bwd_pd_t; + + DECLARE_COMMON_PD_T( + JIT_IMPL_NAME_HELPER("jit:", isa, ""), + jit_uni_pooling_bwd_t); + + status_t init() { + using namespace utils; + + bool ok = true + && set_default_params() == status::success + && !is_fwd() + && !has_zero_dim_memory() + && everyone_is(data_type::f32, + diff_src_md()->data_type, + diff_dst_md()->data_type) + && attr()->has_default_values() + && memory_desc_matches_tag(*diff_dst_md(), desired_fmt_tag()) + && memory_desc_matches_tag(*diff_src_md(), desired_fmt_tag()); + if (!ok) return status::unimplemented; + + if (desc()->alg_kind == alg_kind::pooling_max) { + init_default_ws(); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + return jit_uni_pool_kernel_f32::init_conf(jpp_, this); + } + + format_tag_t desired_fmt_tag() { + using namespace format_tag; + return ndims() + ? isa == avx512_common ? nChw16c : nChw8c + : isa == avx512_common ? nCdhw16c : nCdhw8c; + } + + jit_pool_conf_t jpp_; + }; + + jit_uni_pooling_bwd_t(const pd_t *apd): cpu_primitive_t(apd) + { kernel_ = new jit_uni_pool_kernel_f32(pd()->jpp_); } + + ~jit_uni_pooling_bwd_t() { delete kernel_; } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto ws = CTX_IN_MEM(const char *, MKLDNN_ARG_WORKSPACE); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + if (pd()->ndims() == 5) + execute_backward_3d(diff_dst, ws, diff_src); + else + execute_backward(diff_dst, ws, diff_src); + + return status::success; + } + +private: + void execute_backward(const data_t *diff_dst, const char *indices, + data_t *diff_src) const; + void execute_backward_3d(const data_t *diff_dst, const char *indices, + data_t *diff_src) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + jit_uni_pool_kernel_f32 *kernel_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.cpp new file mode 100644 index 000000000000..98796503b793 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.cpp @@ -0,0 +1,1006 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "memory_desc_wrapper.hpp" +#include "mkldnn_debug.h" +#include "nstl.hpp" +#include "type_helpers.hpp" + +#include "cpu_primitive.hpp" +#include "cpu_reorder_pd.hpp" +#include "jit_uni_reorder.hpp" + +#include "jit_generator.hpp" + +// #define TR_DEBUG +#if defined(TR_DEBUG) +#define DEBUg(...) do { __VA_ARGS__ } while (0) +#else +#define DEBUg(...) +#endif +#define DEBUG(...) DEBUg(__VA_ARGS__) + +#ifdef _WIN32 +/* seems like s_addr is a reserved macro on Windows */ +#undef s_addr +#endif + +using namespace Xbyak; +using namespace mkldnn::impl::types; + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace tr { + +/** Minimal reasonable/desirable kernel size. + * The constant might be used to determine how a problem should be split + * between kernel and threading driver. */ +const size_t ker_prb_size_min = 64; + +/* kernel */ +struct jit_uni_reorder_kernel_f32: public kernel_t, public jit_generator { + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_reorder_kernel_f32) + + enum { + len_unroll_max = 256, + ndims_jit_loop_max = 3, + }; + + struct simple_impl_desc_t { + int ndims_full_unroll; + int len_last_dim_unroll; + int len_unroll; + }; + + static bool simple_impl_desc_init(const prb_t &prb, + simple_impl_desc_t *desc) { + const int ndims = prb.ndims; + + int ndims_full_unroll = 0; + int len_last_dim_unroll = 1; + int len_unroll = 1; + + for (int d = 0; d < ndims; ++d) { + auto &node = prb.nodes[d]; + if (len_unroll * node.n <= len_unroll_max) { + ndims_full_unroll++; + len_unroll *= node.n; + } else { + len_last_dim_unroll = len_unroll_max / len_unroll; + while (node.n % len_last_dim_unroll) + --len_last_dim_unroll; + len_unroll *= len_last_dim_unroll; + break; + } + } + + if (prb.ndims - ndims_full_unroll > ndims_jit_loop_max) + return false; + + if (desc) { + desc->ndims_full_unroll = ndims_full_unroll; + desc->len_last_dim_unroll = len_last_dim_unroll; + desc->len_unroll = len_unroll; + } + + return true; + } + + static bool applicable(const prb_t &p) { + using namespace data_type; + + bool ok = true + && p.ndims > 0 + && utils::one_of(p.itype, f32, s32, s8, u8) + && utils::one_of(p.otype, f32, s32, s8, u8) + && utils::everyone_is(0, p.ioff, p.ooff) /* do we need this? */ + && utils::one_of(p.beta, 0.f, 1.f) /* anything else? */ + && simple_impl_desc_init(p, nullptr) + && mayiuse(sse42) + && IMPLICATION(!utils::everyone_is(f32, p.itype, p.otype), + mayiuse(avx)); + if (!ok) return false; + + const ptrdiff_t max_stride = (1LL<<31) - 1; + for (int d = 0; d < p.ndims; ++d) { + const ptrdiff_t cms = max_stride / p.nodes[d].n; + bool strides_ok = true + && p.nodes[d].is < cms / (int)data_type_size(p.itype) + && p.nodes[d].os < cms / (int)data_type_size(p.otype); + if (!strides_ok) return false; + } + + return true; + } + + int n(int d) { assert(d < prb_.ndims); return (int)prb_.nodes[d].n; } + int is(int d) { assert(d < prb_.ndims); return (int)prb_.nodes[d].is; } + int os(int d) { assert(d < prb_.ndims); return (int)prb_.nodes[d].os; } + int ss(int d) { assert(d < prb_.ndims); return (int)prb_.nodes[d].ss; } + + Address i_addr(int i_off) + { return ptr[reg_ptr_in + reg_off_in + i_off * itype_sz]; } + + Address o_addr(int o_off) + { return ptr[reg_ptr_out + reg_off_out + o_off * otype_sz]; } + + Address s_addr(int s_off) + { return ptr[reg_ptr_scale + reg_off_scale + s_off * stype_sz]; } + + void step(int off, int prev_i_off, int prev_o_off, int prev_s_off, + int &i_off, int &o_off, int &s_off, int step_size = 1) { + i_off = prev_i_off; + o_off = prev_o_off; + s_off = prev_s_off; + + if (off == 0) return; + + int start_dim = 0, dims_prod = 1; + for (; start_dim < prb_.ndims && dims_prod != step_size; ++start_dim) + dims_prod *= n(start_dim); + assert(start_dim < prb_.ndims); + off /= step_size; + + for (int d = start_dim; d < prb_.ndims; ++d) { + i_off += is(d); + o_off += os(d); + s_off += ss(d); + + if (off % n(d)) break; + + i_off += - n(d) * is(d); + o_off += - n(d) * os(d); + s_off += - n(d) * ss(d); + off /= n(d); + + if (off == 0) break; /* FIXME: is it really required? */ + } + } + + void step(int off, int prev_i_off, int prev_o_off, int &i_off, int &o_off, + int step_size = 1) { + int dummy = 0; + step(off, prev_i_off, prev_o_off, dummy, i_off, o_off, dummy, + step_size); + } + + void tr8x8_avx2(int i_off, int o_off) { + for (int i = 0; i < 8; i++) + vmovups(Ymm(i), i_addr(i_off + i * 8)); + + for (int i = 0; i < 8 / 2; i++) { + vunpcklps(Ymm(8 + i), Ymm(2 * i), Ymm(2 * i + 1)); + vunpckhps(Ymm(i), Ymm(2 * i), Ymm(2 * i + 1)); + } + + const unsigned int lfloat = 0x44; + const unsigned int ufloat = 0xee; + for (int i = 0; i < 8 / 2; i++) { + int j = i % 2 == 0 ? 8 + i : i - 1; + vshufps(Ymm(8 / 2 + 2 * i), Ymm(j), Ymm(j + 1), lfloat); + vshufps(Ymm(8 / 2 + 2 * i + 1), Ymm(j), Ymm(j + 1), ufloat); + } + + const unsigned int lquad = 0x20; + for (int i = 0; i < 8 / 2; i++) + vperm2f128(Ymm(i), Ymm(8 / 2 + i), Ymm(8 + i), lquad); + + const unsigned int uquad = 0x31; + for (int i = 8 / 2; i < 8; i++) + vperm2f128(Ymm(i), Ymm(i), Ymm(8 / 2 + i), uquad); + + for (int i = 0; i < 8; i++) + vmovups(o_addr(o_off + i * 8), Ymm(i)); + } + + bool process_unroll_tr8x8(int len) { + bool can_do = true + && mayiuse(avx2) + && prb_.ndims >= 2 + && utils::everyone_is(4, itype_sz, otype_sz) + && utils::everyone_is(8, n(0), n(1)) + && utils::everyone_is(1, os(0), is(1)) + && utils::everyone_is(8, os(1), is(0)) + && prb_.scale_type == scale_type_t::NONE + && prb_.beta == 0.f; + if (!can_do) return false; + + const int step_size = n(0) * n(1); + int i_off = 0, o_off = 0; + for (int off = 0; off < len; off += step_size) { + step(off, i_off, o_off, i_off, o_off, step_size); + tr8x8_avx2(i_off, o_off); + } + + return true; + } + + template + bool process_direct_copy(int len) { + using namespace data_type; + + using Vmm = typename cpu_isa_traits::Vmm; + const int simd_w = cpu_isa_traits::vlen / itype_sz; + + bool can_do = true + && mayiuse(isa) + && utils::everyone_is(1, os(0), is(0)) + && (false + || prb_.itype == prb_.otype + || (prb_.itype == s32 && prb_.otype == f32) + || (prb_.itype == f32 && prb_.otype == s32) + ) + && len % simd_w == 0 + && n(0) % len == 0 + && prb_.scale_type == scale_type_t::NONE + && prb_.beta == 0.f; + if (!can_do) return false; + + for (int off = 0; off < len;) { + const int unroll = nstl::min(16, (len - off) / simd_w); + + for (int ur = 0; ur < unroll; ++ur) + uni_vmovups(Vmm(ur), i_addr(off + ur * simd_w)); + + if (prb_.itype != prb_.otype) { + for (int ur = 0; ur < unroll; ++ur) { + if (prb_.itype == s32 && prb_.otype == f32) + uni_vcvtdq2ps(Vmm(ur), Vmm(ur)); + else if (prb_.itype == f32 && prb_.otype == s32) + uni_vcvtps2dq(Vmm(ur), Vmm(ur)); + else assert(!"unreachable"); + } + } + + for (int ur = 0; ur < unroll; ++ur) + uni_vmovups(o_addr(off + ur * simd_w), Vmm(ur)); + + off += unroll * simd_w; + } + + return true; + } + + void process_unroll_generic_step(int reg_unroll, const int *i_off, + const int *o_off, const int *s_off) { + using namespace data_type; + + auto cvt2ps = [=](const Xmm &dst, const Operand &src, data_type_t idt) { + Xmm dst_pure = Xmm(dst.getIdx()); + switch (idt) { + case f32: + if (src.isMEM() || src.getIdx() != dst.getIdx()) + vmovups(dst, src); + break; + case s32: vcvtdq2ps(dst, src); break; + case s8: vpmovsxbd(dst, src); vcvtdq2ps(dst_pure, dst); break; + case u8: vpmovzxbd(dst, src); vcvtdq2ps(dst_pure, dst); break; + default: assert(!"unreachable"); + } + }; + + auto cvt2int = [=](const Xmm &xmm, data_type_t odt, data_type_t idt) { + switch (odt) { + case s32: + if (idt == f32) vcvtps2dq(xmm, xmm); + else if (idt == s8) vpmovsxbd(xmm, xmm); + else if (idt == u8) vpmovzxbd(xmm, xmm); + break; + case s8: + if (idt == f32) vcvtps2dq(xmm, xmm); + if (idt == f32 || idt == s32) { + if (mayiuse(avx512_core)) { + vpmovsdb(xmm, xmm); + } else { + vpackssdw(xmm, xmm, xmm_zero); + vpacksswb(xmm, xmm, xmm_zero); + } + } + if (idt == u8) vpminub(xmm, xmm, xmm_4x127b); + break; + case u8: + if (idt == f32) vcvtps2dq(xmm, xmm); + if (idt == f32 || idt == s32) { + if (mayiuse(avx512_core)) { + vpmaxsd(xmm, xmm, xmm_zero); + vpmovusdb(xmm, xmm); + } else { + vpackssdw(xmm, xmm, xmm_zero); + vpackuswb(xmm, xmm, xmm_zero); + } + } + if (idt == s8) vpmaxsb(xmm, xmm, xmm_zero); + break; + default: assert(!"unreachable"); + } + }; + + auto load = [=](const Xmm &xmm, const Address &addr, int size) { + switch (size) { + case 16: movups(xmm, addr); break; + case 4: movss(xmm, addr); break; + case 1: pinsrb(xmm, addr, 0x0); break; + default: assert(!"unreachable"); + } + }; + + auto store = [=](const Address &addr, const Xmm &xmm, int size) { + switch (size) { + case 16: movups(addr, xmm); break; + case 4: movss(addr, xmm); break; + case 1: pextrb(addr, xmm, 0x0); break; + default: assert(!"unreachable"); + } + }; + + /* check whether loading 4 values at once is possible */ + bool can_load_xmm = mayiuse(avx) && reg_unroll % 4 == 0; + for (int ur = 1; ur < reg_unroll; ++ur) + if (i_off[ur] != i_off[ur - 1] + 1) + can_load_xmm = false; + const int load_step = can_load_xmm ? 4 : 1; + + /* check whether storing 4 values at once is possible */ + bool can_store_xmm = reg_unroll % 4 == 0; + for (int ur = 1; ur < reg_unroll; ++ur) + if (o_off[ur] != o_off[ur - 1] + 1) + can_store_xmm = false; + const int ur_step = can_store_xmm ? 4 : 1; + + const bool interim_f32 = false + || utils::one_of(f32, prb_.itype, prb_.otype) + || prb_.scale_type != scale_type_t::NONE + || prb_.beta != 0.f; + + if (!can_load_xmm && can_store_xmm) { + assert(ur_step == 4); + /* load with stride */ + for (int ur = 0; ur < reg_unroll; ur += ur_step) { + for (int r = 0; r < ur_step; ++r) { + if (itype_sz == 4) + pinsrd(Xmm(ur), i_addr(i_off[ur + r]), r); + else + pinsrb(Xmm(ur), i_addr(i_off[ur + r]), r); + } + } + } else { + for (int ur = 0; ur < reg_unroll; ur += load_step) + load(Xmm(ur), i_addr(i_off[ur]), load_step * itype_sz); + } + + /* xmm[:] <-- (f32)xmm[:] */ + if (interim_f32) { + const int cvt_step = nstl::max(load_step, ur_step); + for (int ur = 0; ur < reg_unroll; ur += cvt_step) + cvt2ps(Xmm(ur), Xmm(ur), prb_.itype); + } + + if (can_load_xmm && !can_store_xmm) { + const bool fast_return = true // transposition on the fly + && prb_.scale_type != scale_type_t::MANY + && prb_.beta == 0.f; + if (fast_return) { + for (int ur = 0; ur < reg_unroll; ur += load_step) { + if (prb_.scale_type == scale_type_t::COMMON) + mulps(Xmm(ur), xmm_scale); + if (prb_.otype != f32) + cvt2int(Xmm(ur), prb_.otype, + interim_f32 ? f32 : prb_.itype); + for (int r = 0; r < load_step; ++r) { + if (otype_sz == 4) + pextrd(o_addr(o_off[ur + r]), Xmm(ur), r); + else + pextrb(o_addr(o_off[ur + r]), Xmm(ur), r); + } + } + return; + } + + /* scatter elements of xmm into 4 xmms */ + if (itype_sz == 4 || interim_f32) { + for (int ur = 0; ur < reg_unroll; ur += load_step) + for (int r = 1; r < load_step; ++r) + vshufps(Xmm(ur + r), Xmm(ur), Xmm(ur), r); + } else { + for (int ur = 0; ur < reg_unroll; ur += load_step) + for (int r = 1; r < load_step; ++r) + vpalignr(Xmm(ur + r), Xmm(ur), Xmm(ur), r); + } + } + + /* scale and beta processing */ + if (can_store_xmm) { + /* xmm <-- scale * xmm[:] */ + if (prb_.scale_type == scale_type_t::COMMON) { + for (int ur = 0; ur < reg_unroll; ur += ur_step) + mulps(Xmm(ur), xmm_scale); + } else if (prb_.scale_type == scale_type_t::MANY) { + enum class scale_load_type_t { bcast, load, gather }; + + for (int ur = 0; ur < reg_unroll; ur += ur_step) { + scale_load_type_t scale_load_type = + scale_load_type_t::bcast; // the best case + + for (int r = ur + 1; r < ur + ur_step; ++r) + if (s_off[r] != s_off[r - 1] + 0) + scale_load_type = scale_load_type_t::load; + + if (scale_load_type == scale_load_type_t::bcast) { + movss(xmm_scale, s_addr(s_off[ur])); + shufps(xmm_scale, xmm_scale, 0x0); + mulps(Xmm(ur), xmm_scale); + continue; + } + + // bcast doesn't work, the next try -- load + for (int r = ur + 1; r < ur + ur_step; ++r) + if (s_off[r] != s_off[r - 1] + 1) + scale_load_type = scale_load_type_t::gather; + + if (scale_load_type == scale_load_type_t::load) { + movups(xmm_scale, s_addr(s_off[ur])); + mulps(Xmm(ur), xmm_scale); + continue; + } + + // load doesn't work as well + // so gather the scale factors one by one + for (int r = ur; r < ur + ur_step; ++r) + pinsrd(xmm_scale, s_addr(s_off[r]), r - ur); + mulps(Xmm(ur), xmm_scale); + } + } + + /* dst <-- beta * dst + xmm[:] */ + assert(prb_.beta == 0.f || prb_.beta == 1.f); + if (prb_.beta == 1.f) { + for (int ur = 0; ur < reg_unroll; ur += ur_step) { + if (prb_.otype == f32) { + /* non VEX instructions do not support unaligned + * memory for instructions other than movups. */ + if (mayiuse(avx)) { + vaddps(Xmm(ur), o_addr(o_off[ur])); + } else { + /* register xmm(1) is unused */ + movups(Xmm(1), o_addr(o_off[ur])); + addps(Xmm(ur), Xmm(1)); + } + } else { + cvt2ps(Xmm(1), o_addr(o_off[ur]), prb_.otype); + vaddps(Xmm(ur), Xmm(1)); + } + } + } + } else { + /* xmm[0] <-- scale * xmm[0] */ + if (prb_.scale_type == scale_type_t::COMMON) { + for (int ur = 0; ur < reg_unroll; ur += ur_step) + mulss(Xmm(ur), xmm_scale); + } else if (prb_.scale_type == scale_type_t::MANY) { + for (int ur = 0; ur < reg_unroll; ur += ur_step) { + mulss(Xmm(ur), s_addr(s_off[ur])); + } + } + + /* dst <-- beta * dst + xmm[0] */ + assert(prb_.beta == 0.f || prb_.beta == 1.f); + if (prb_.beta == 1.f) { + for (int ur = 0; ur < reg_unroll; ur += ur_step) { + if (prb_.otype == f32) { + addss(Xmm(ur), o_addr(o_off[ur])); + } else { + if (prb_.otype == s32) { + vmovss(xmm_tmp, o_addr(o_off[ur])); + } else if (utils::one_of(prb_.otype, s8, u8)) { + pinsrb(xmm_tmp, o_addr(o_off[ur]), 0x0); + } else { + assert(!"unsupported o_type"); + } + cvt2ps(xmm_tmp, xmm_tmp, prb_.otype); + addps(Xmm(ur), xmm_tmp); + } + } + } + } + + for (int ur = 0; ur < reg_unroll; ur += ur_step) { + if (prb_.otype != f32) + cvt2int(Xmm(ur), prb_.otype, interim_f32 ? f32 : prb_.itype); + store(o_addr(o_off[ur]), Xmm(ur), ur_step * otype_sz); + } + } + + void process_unroll_generic(int len) { + const int blk = 8; + + int i_off[2 * blk] = {0}; + int o_off[2 * blk] = {0}; + int s_off[2 * blk] = {0}; + + int curr = 0; // will switch between 0 and 1 + + for (int off = 0; off < len; off += blk) { + const int reg_unroll = nstl::min(off + blk, len) - off; + + /* compute offsets */ + for (int ur = off != 0 ? 0 : 1; ur < reg_unroll; ++ur) { + const int ur_c = curr * blk + ur; + const int ur_p = (ur_c - 1 + 2 * blk) % (2 * blk); // prev ur + step(off + ur, + i_off[ur_p], o_off[ur_p], s_off[ur_p], + i_off[ur_c], o_off[ur_c], s_off[ur_c]); + } + + process_unroll_generic_step(reg_unroll, i_off + curr * blk, + o_off + curr * blk, s_off + curr * blk); + + curr = 1 - curr; + } + } + + void loop_begin(Label &l, Reg64 reg_cnt, int len) { + mov(reg_cnt, len); + L(l); + } + + void loop_end(Label &l, Reg64 reg_cnt, int len, + int i_step, int o_step, int s_step) { + add(reg_off_in, i_step * itype_sz); + add(reg_off_out, o_step * otype_sz); + if (prb_.scale_type == scale_type_t::MANY) + add(reg_off_scale, s_step * stype_sz); + dec(reg_cnt); + jnz(l); + + sub(reg_off_in, len * i_step * itype_sz); + sub(reg_off_out, len * o_step * otype_sz); + if (prb_.scale_type == scale_type_t::MANY) + sub(reg_off_scale, len * s_step * stype_sz); + } + + bool simple_impl() { + simple_impl_desc_t d; + if (!simple_impl_desc_init(prb_, &d)) return false; + + const int nfu = d.ndims_full_unroll; + const int ldu = d.len_last_dim_unroll; + const int n_jit_loops = prb_.ndims - d.ndims_full_unroll; + assert(n_jit_loops <= ndims_jit_loop_max); + + xor_(reg_off_in, reg_off_in); + xor_(reg_off_out, reg_off_out); + if (prb_.scale_type == scale_type_t::MANY) + xor_(reg_off_scale, reg_off_scale); + + Label l_loop[3]; + Reg64 reg_cnt[3] = {r15, r14, r13}; + + if (n_jit_loops > 2) + loop_begin(l_loop[2], reg_cnt[2], n(nfu + 2)); + + if (n_jit_loops > 1) + loop_begin(l_loop[1], reg_cnt[1], n(nfu + 1)); + + if (n_jit_loops > 0) + loop_begin(l_loop[0], reg_cnt[0], n(nfu + 0) / ldu); + + const bool optimized = false + || process_direct_copy(d.len_unroll) + || process_direct_copy(d.len_unroll) + || process_unroll_tr8x8(d.len_unroll); + if (!optimized) + process_unroll_generic(d.len_unroll); + + if (n_jit_loops > 0) + loop_end(l_loop[0], reg_cnt[0], + n(nfu + 0) / ldu, is(nfu + 0) * ldu, os(nfu + 0) * ldu, + ss(nfu + 0) * ldu); + + if (n_jit_loops > 1) + loop_end(l_loop[1], reg_cnt[1], + n(nfu + 1), is(nfu + 1), os(nfu + 1), ss(nfu + 1)); + + if (n_jit_loops > 2) + loop_end(l_loop[2], reg_cnt[2], + n(nfu + 2), is(nfu + 2), os(nfu + 2), ss(nfu + 2)); + + return true; + } + + void impl() { + if (simple_impl()) return; + assert(!"no implementation available"); + } + + jit_uni_reorder_kernel_f32(const desc_t &desc) + : kernel_t(desc), jit_generator() { + itype_sz = data_type_size(prb_.itype); + otype_sz = data_type_size(prb_.otype); + stype_sz = sizeof(float); + + preamble(); +# define PARAM(x) ptr[abi_param1 + offsetof(call_param_t, x)] + if (prb_.scale_type == scale_type_t::COMMON) { + auto reg_ptr_scale_tmp = reg_ptr_in; + mov(reg_ptr_scale_tmp, PARAM(scale)); + movups(xmm_scale, ptr[reg_ptr_scale_tmp]); + } else if (prb_.scale_type == scale_type_t::MANY) { + mov(reg_ptr_scale, PARAM(scale)); + } + mov(reg_ptr_in, PARAM(in)); + mov(reg_ptr_out, PARAM(out)); +# undef PARAM + + if (mayiuse(avx)) { + vxorps(xmm_zero, xmm_zero, xmm_zero); + + if (prb_.itype == data_type::u8 && prb_.otype == data_type::s8) { + mov(reg_tmp.cvt32(), 0x7f7f7f7f); + movd(xmm_4x127b, reg_tmp.cvt32()); + } + } + + impl(); + postamble(); + ker_ = (void (*)(const call_param_t *))getCode(); + } + +private: + int itype_sz; + int otype_sz; + int stype_sz; + + Reg64 reg_ptr_in = rsi; + Reg64 reg_ptr_out = rdx; + Reg64 reg_ptr_scale = abi_not_param1; + + Reg64 reg_off_in = r8; + Reg64 reg_off_out = r9; + Reg64 reg_off_scale = r10; + + Reg64 reg_tmp = rax; + + Xmm xmm_scale = xmm15; + Xmm xmm_zero = xmm14; + Xmm xmm_4x127b = xmm13; // TODO: unite with xmm_zero + Xmm xmm_tmp = xmm12; +}; + +status_t kernel_t::desc_init(kernel_t::desc_t &desc, const prb_t &prb, + int ndims_ker_max) { + desc.prb = prb; + desc.prb.ioff = desc.prb.ooff = 0; + + if (ndims_ker_max > prb.ndims) + return status::invalid_arguments; + + auto ndims_ker_max_f = [&]() { + size_t cur_size = 1; + for (int d = 0; d < prb.ndims; cur_size *= prb.nodes[d++].n) + if (cur_size >= ker_prb_size_min) return d; + return prb.ndims; + }; + + if (ndims_ker_max <= 0) + ndims_ker_max = ndims_ker_max_f(); + + /* traverse through kernel implementations */ + /* TODO: find a better way to do that... */ + desc.id = 0; + for (int ndims_ker = ndims_ker_max; ndims_ker > 0; --ndims_ker) { + desc.prb.ndims = ndims_ker; + if (jit_uni_reorder_kernel_f32::applicable(desc.prb)) + return status::success; + } + + return status::unimplemented; +} + +kernel_t *kernel_t::create(const kernel_t::desc_t &desc) { + switch (desc.id) { + case 0: return new jit_uni_reorder_kernel_f32(desc); + default: assert(!"unknown kernel id"); return nullptr; + } + + return nullptr; +} + +} + +static void prb_block_for_cache(tr::prb_t &prb) { + if (prb.nodes[0].is % 64 == 0 && prb.nodes[0].n > 16) { + /** an attempt to use caches more efficient and + * address the 4K-aliasing issue */ + /* TODO: improve the logic around here */ + int j = 1; + for (; j < prb.ndims && prb.nodes[j].is != 1; ++j); + if (j == prb.ndims) return; + + /* it makes sense to re-prioritize sequential read over + * sequential write if the former would not trash the + * cache, i.e. is == 1 and os % 2^smth != 0. Smth is + * set to 2 at the moment */ + const int move_to = prb.nodes[j].os % 4 != 0 ? 0 : 1; + if (j == move_to) return; + + if (prb.nodes[j].n > 16 && prb.nodes[j].n % 16 == 0) + prb_node_split(prb, j, 16); + + prb_node_move(prb, j, move_to); + DEBUG({ printf("cache: "); prb_dump(prb); }); + } +} + +/** finds the maximum number of dimension the kernel should process and + * optionally splits one of the dimension to achieve better balance between + * parallel driver and the kernel. */ +static void prb_thread_kernel_balance(tr::prb_t &prb, int &ndims_ker_max) { + size_t sz_total = 1; + for (int d = 0; d < prb.ndims; ++d) + sz_total *= prb.nodes[d].n; + + /* sz_drv_min is the minimal size for the parallel + * driver required for good parallelization */ + const size_t sz_drv_min = nstl::min( + 16 * mkldnn_get_max_threads(), + utils::div_up(sz_total, 1024)); + + /* kdims -- # of dimensions processed by a kernel + * sz_ker_cur -- product of the dimension processed by a kernel + * sz_drv_cur -- product of the dimension processed by a driver */ + + int kdims = prb.ndims; + size_t sz_drv_cur = 1; + for (; kdims > 1 && sz_drv_cur < sz_drv_min; --kdims) + sz_drv_cur *= prb.nodes[kdims - 1].n; + + size_t sz_ker_cur = 1; + for (int d = 0; d < kdims; ++d) + sz_ker_cur *= prb.nodes[d].n; + + /* Initially kdims is chosen so that sz_drv_cur >= sz_drv_min. + * + * It might happen that for chosen kdims the sz_ker_cur is too small + * (less than tr::ker_prb_size_min). In that case try to split the + * innermost driver dimension into two, to increase sz_ker_cur. */ + bool want_borrow_ker_from_drv = true + && kdims < prb.ndims + && sz_ker_cur < tr::ker_prb_size_min + && sz_drv_cur > sz_drv_min; + if (want_borrow_ker_from_drv) { + /* sz_want_borrow is the minimal sz, so that: + * o) sz_ker_cur * sz_want_borrow >= tr::ker_prb_size_min + * o) current innermost driver dimension is divisible by + * sz_want_borrow (so that we can evenly split that + * dimension into two) + * + * In the worst case the minimal sz_want_borrow is equal + * to the innermost driver dimension itself. In that case + * we will sacrifice it in favor of kernel (is it fine?). */ + size_t sz_want_borrow + = utils::div_up(tr::ker_prb_size_min, sz_ker_cur); + for (; prb.nodes[kdims].n % sz_want_borrow; ++sz_want_borrow); + if (sz_want_borrow != prb.nodes[kdims].n) + prb_node_split(prb, kdims, sz_want_borrow); + kdims += 1; + } + + /* On the other hand it might happen that for chosen kdims + * the sz_drv_cur is too small (less than sz_drv_min). In that case + * try to split the outermost kernel dimension into two, to increase + * sz_drv_cur. */ + bool want_borrow_drv_from_ker = true + && sz_ker_cur > tr::ker_prb_size_min + && sz_drv_cur < sz_drv_min; + if (want_borrow_drv_from_ker) { + size_t sz_want_borrow = utils::div_up(sz_drv_min, sz_drv_cur); + for (; prb.nodes[kdims - 1].n % sz_want_borrow; ++sz_want_borrow); + if (sz_want_borrow != prb.nodes[kdims - 1].n) + prb_node_split(prb, kdims - 1, + prb.nodes[kdims - 1].n / sz_want_borrow); + } + + ndims_ker_max = kdims; + + if (want_borrow_ker_from_drv || want_borrow_drv_from_ker) { + DEBUG({ printf("split: "); prb_dump(prb); + printf("ndims_ker_max = %d\n", ndims_ker_max); }); + } +} + +struct jit_uni_reorder_t : public cpu_primitive_t { + struct pd_t : public cpu_reorder_pd_t { + using cpu_reorder_pd_t::cpu_reorder_pd_t; + + DECLARE_COMMON_PD_T("jit:uni", jit_uni_reorder_t); + + static status_t create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { + auto prb = tr::prb_t(); + + status_t prb_init_status = prb_init(prb, *src_md, *dst_md, attr); + if (prb_init_status != status::success) return prb_init_status; + + DEBUG({ printf("init : "); prb_dump(prb); }); + prb_normalize(prb); + DEBUG({ printf("norm : "); prb_dump(prb); }); + prb_simplify(prb); + DEBUG({ printf("smpl : "); prb_dump(prb); }); + + prb_block_for_cache(prb); + + int ndims_ker_max; + prb_thread_kernel_balance(prb, ndims_ker_max); + + tr::kernel_t::desc_t ker_desc; + status_t ker_init_status + = tr::kernel_t::desc_init(ker_desc, prb, ndims_ker_max); + if (ker_init_status != status::success) return ker_init_status; + + const int ndims_driver = prb.ndims - ker_desc.prb.ndims; + if (ndims_driver > jit_uni_reorder_t::ndims_driver_max) + return status::unimplemented; + + DEBUG({ printf("ker : "); prb_dump(ker_desc.prb); }); + + auto _pd = new pd_t(engine, attr, src_engine, src_md, dst_engine, + dst_md); + if (_pd == nullptr) return status::out_of_memory; + if (_pd->init() != status::success) { + delete _pd; + return status::unimplemented; + } + _pd->prb_ = prb; + _pd->ker_desc_ = ker_desc; + return safe_ptr_assign(*reorder_pd, _pd); + } + + tr::prb_t prb_; + tr::kernel_t::desc_t ker_desc_; + }; + + jit_uni_reorder_t(const pd_t *apd): cpu_primitive_t(apd) { + kernel_ = tr::kernel_t::create(pd()->ker_desc_); + assert(kernel_); + } + ~jit_uni_reorder_t() { delete kernel_; } + + void omp_driver_0d(int off, const char *in, char *out, + const float *scale) const { + tr::call_param_t c{in, out, scale}; + (*kernel_)(&c); + } + + void omp_driver_1d(int ithr, int nthr, int off, const char *in, char *out, + const float *scale) const { + const tr::node_t *ns = pd()->prb_.nodes + off; + for_nd(ithr, nthr, (ptrdiff_t)ns[0].n, [&](ptrdiff_t d0) { + auto c = tr::call_param_t(); + c.in = in + d0 * ns[0].is * data_type_size(pd()->prb_.itype); + c.out = out + d0 * ns[0].os * data_type_size(pd()->prb_.otype); + c.scale = scale + d0 * ns[0].ss; + (*kernel_)(&c); + }); + } + + void omp_driver_2d(int ithr, int nthr, int off, const char *in, char *out, + const float *scale) const { + const tr::node_t *ns = pd()->prb_.nodes + off; + for_nd(ithr, nthr, (ptrdiff_t)ns[1].n, (ptrdiff_t)ns[0].n, + [&](ptrdiff_t d1, ptrdiff_t d0) { + auto c = tr::call_param_t(); + c.in = in + (d0 * ns[0].is + d1 * ns[1].is) + * data_type_size(pd()->prb_.itype); + c.out = out + (d0 * ns[0].os + d1 * ns[1].os) + * data_type_size(pd()->prb_.otype); + c.scale = scale + d0 * ns[0].ss + d1 * ns[1].ss; + (*kernel_)(&c); + }); + } + + void omp_driver_3d(int ithr, int nthr, int off, const char *in, char *out, + const float *scale) const { + const tr::node_t *ns = pd()->prb_.nodes + off; + for_nd(ithr, nthr, (ptrdiff_t)ns[2].n, (ptrdiff_t)ns[1].n, + (ptrdiff_t)ns[0].n, + [&](ptrdiff_t d2, ptrdiff_t d1, ptrdiff_t d0) { + auto c = tr::call_param_t(); + c.in = in + (d0 * ns[0].is + d1 * ns[1].is + d2 * ns[2].is) + * data_type_size(pd()->prb_.itype); + c.out = out + (d0 * ns[0].os + d1 * ns[1].os + d2 * ns[2].os) + * data_type_size(pd()->prb_.otype); + c.scale = scale + d0 * ns[0].ss + d1 * ns[1].ss + d2 * ns[2].ss; + (*kernel_)(&c); + }); + } + + void omp_driver_4d(int ithr, int nthr, int off, const char *in, char *out, + const float *scale) const { + const tr::node_t *ns = pd()->prb_.nodes + off; + for_nd(ithr, nthr, (ptrdiff_t)ns[3].n, (ptrdiff_t)ns[2].n, + (ptrdiff_t)ns[1].n, (ptrdiff_t)ns[0].n, + [&](ptrdiff_t d3, ptrdiff_t d2, ptrdiff_t d1, ptrdiff_t d0) { + auto c = tr::call_param_t(); + c.in = in + (d0 * ns[0].is + d1 * ns[1].is + d2 * ns[2].is + + d3 * ns[3].is) * data_type_size(pd()->prb_.itype); + c.out = out + (d0 * ns[0].os + d1 * ns[1].os + d2 * ns[2].os + + d3 * ns[3].os) * data_type_size(pd()->prb_.otype); + c.scale = scale + d0 * ns[0].ss + d1 * ns[1].ss + d2 * ns[2].ss + + d3 * ns[3].ss; + (*kernel_)(&c); + }); + } + + void omp_driver(const char *in, char *out, const float *scale) const { + in += pd()->prb_.ioff * data_type_size(pd()->prb_.itype); + out += pd()->prb_.ooff * data_type_size(pd()->prb_.otype); + + DEBUG({ printf("prb : "); tr::prb_dump(pd()->prb_); }); + DEBUG({ printf("ker : "); tr::prb_dump(pd()->ker_desc_.prb); }); + + int ndims = pd()->prb_.ndims; + int ndims_ker = pd()->ker_desc_.prb.ndims; + assert(ndims - ndims_ker <= ndims_driver_max); + + if (ndims - ndims_ker == 0) { + omp_driver_0d(ndims_ker, in, out, scale); + } else { + parallel(0, [&](const int ithr, const int nthr) { + switch (ndims - ndims_ker) { + case 1: omp_driver_1d(ithr, nthr, ndims_ker, in, out, scale); break; + case 2: omp_driver_2d(ithr, nthr, ndims_ker, in, out, scale); break; + case 3: omp_driver_3d(ithr, nthr, ndims_ker, in, out, scale); break; + case 4: omp_driver_4d(ithr, nthr, ndims_ker, in, out, scale); break; + default: assert(!"unimplemented"); + } + }); + } + } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto in = CTX_IN_MEM(const char *, MKLDNN_ARG_FROM); + auto out = CTX_OUT_MEM(char *, MKLDNN_ARG_TO); + + omp_driver(in, out, pd()->attr()->output_scales_.scales_); + + return status::success; + } + + enum { ndims_driver_max = 4 }; + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + tr::kernel_t *kernel_; +}; + +status_t jit_uni_reorder_create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { + return jit_uni_reorder_t::pd_t::create(reorder_pd, engine, attr, + src_engine, src_md, dst_engine, dst_md); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.hpp new file mode 100644 index 000000000000..0746ea61d396 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder.hpp @@ -0,0 +1,127 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef _JIT_UNI_REORDER_HPP +#define _JIT_UNI_REORDER_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" + +#include "cpu_primitive.hpp" +#include "cpu_reorder_pd.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace tr { + +constexpr int max_ndims = MKLDNN_MAX_NDIMS; + +struct node_t { + size_t n; + ptrdiff_t is; // input stride + ptrdiff_t os; // output stride + ptrdiff_t ss; // scale stride +}; + +enum class scale_type_t { NONE, COMMON, MANY }; + +struct prb_t { + data_type_t itype; + data_type_t otype; + int ndims; + node_t nodes[max_ndims]; + ptrdiff_t ioff; + ptrdiff_t ooff; + scale_type_t scale_type; + float beta; +}; + +status_t prb_init(prb_t &prb, const memory_desc_t &imd, + const memory_desc_t &omd, const primitive_attr_t *attr); + +/** sorts the problem nodes so that output strides come in ascending order */ +void prb_normalize(prb_t &p); + +/** folds nodes together if possible */ +void prb_simplify(prb_t &p); + +/** splits the node dim into two of sizes n1 and n / n1 + * @warning n must be multiple of n1 */ +void prb_node_split(prb_t &p, int dim, size_t n1); + +/** swaps d0 and d1 nodes */ +void prb_node_swap(prb_t &p, int d0, int d1); + +/** moves node d0 to the d1 position. + * nodes (d0, d1] are shifted to the left if d0 < d1 or + * to the right if d0 > d1 */ +void prb_node_move(prb_t &p, int d0, int d1); + +/** dumps the problem to stdout */ +void prb_dump(const prb_t &p); + +struct call_param_t { + const void *in; + void *out; + const float *scale; +}; + +struct kernel_t { + struct desc_t { + int id; + prb_t prb; + }; + + kernel_t(const desc_t &desc): desc_(desc), ker_(nullptr) {} + void operator()(const call_param_t *c) const { assert(ker_); ker_(c); } + virtual ~kernel_t() {} + + /** inits kernel descriptor: + * desc -- kernel descriptor (output) + * prb -- transposition problem (input) + * ndims_ker_max -- limit the maximum number of dimensions kernel + * will process (optional, 0 -- no limitation) */ + static status_t desc_init(desc_t &desc, const prb_t &prb, + int ndims_ker_max = 0); + + /** creates kernel for the problem described in desc */ + static kernel_t *create(const desc_t &desc); + +protected: + const desc_t desc_; + const prb_t &prb_ = desc_.prb; + void (*ker_)(const call_param_t *); +}; + +/* TODO: add trans_t class */ + +} + +/* for cpu reorder list */ +status_t jit_uni_reorder_create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md); + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp new file mode 100644 index 000000000000..69b7a33604ce --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp @@ -0,0 +1,313 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "memory_desc_wrapper.hpp" +#include "mkldnn_debug.h" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "jit_uni_reorder.hpp" + +using namespace mkldnn::impl::types; +using namespace mkldnn::impl::status; + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace tr { + +/** ad-hoc structure to describe blocked memory layout */ +struct layout_desc_t { + data_type_t dt; + int ndims; + dims_t id; + dims_t dims; + strides_t strides; +}; + +status_t cvt_mem_desc_to_layout_desc(const memory_desc_t &md_, + layout_desc_t &ld) { + const auto md = memory_desc_wrapper(md_); + + bool ok = true + && md.is_blocking_desc() + && md.extra().flags == 0; + if (!ok) return invalid_arguments; + + const auto &bd = md.blocking_desc(); + + ld.ndims = 0; + ld.dt = md.data_type(); + + auto P = [&ld](int id, int dim, ptrdiff_t stride) { + assert((size_t)ld.ndims < sizeof(ld.dims) / sizeof(ld.dims[0])); + ld.id[ld.ndims] = id; + ld.dims[ld.ndims] = dim; + ld.strides[ld.ndims] = stride; + ++ld.ndims; + }; + + dims_t blocks; + md.compute_blocks(blocks); + + for (int d = 0; d < md.ndims(); ++d) { + const int ld_ndims_start = ld.ndims; + if (blocks[d] != 1) { + stride_t stride = 1; + for (int iblk = bd.inner_nblks - 1; iblk >= 0; --iblk) { + if (bd.inner_idxs[iblk] == d) + P(d, bd.inner_blks[iblk], stride); + stride *= bd.inner_blks[iblk]; + } + } + P(d, md.padded_dims()[d] / blocks[d], bd.strides[d]); + + // TODO: NOW: revisit, do we need a reverse? + // TODO: NOW: consider using strides instead of block sizes in md + // reverse the order of dims + for (int ld_d = 0; ld_d < (ld.ndims - ld_ndims_start) / 2; ++ld_d) { + const int idx0 = ld_ndims_start + ld_d; + const int idx1 = ld.ndims - 1 - ld_d; + nstl::swap(ld.dims[idx0], ld.dims[idx1]); + nstl::swap(ld.strides[idx0], ld.strides[idx1]); + } + } + + return success; +} + +status_t prb_init(prb_t &p, const memory_desc_t &imd, const memory_desc_t &omd, + const primitive_attr_t *attr) { + auto im_d = memory_desc_wrapper(imd); + auto om_d = memory_desc_wrapper(omd); + + bool ok = true + && im_d.is_blocking_desc() + && om_d.is_blocking_desc() + && !im_d.has_zero_dim() + && !om_d.has_zero_dim(); + if (!ok) + return unimplemented; + + dims_t iblocks, oblocks; + im_d.compute_blocks(iblocks); + om_d.compute_blocks(oblocks); + + /* padding_dim consistency check */ + for (int d = 0; d < im_d.ndims(); ++d) { + const auto pdim = im_d.padded_dims()[d]; + bool ok = true + && pdim == om_d.padded_dims()[d] + && pdim % iblocks[d] == 0 + && pdim % oblocks[d] == 0; + if (!ok) return unimplemented; + } + + layout_desc_t ild, old; + status_t status = cvt_mem_desc_to_layout_desc(imd, ild); + if (status != success) return status; + status = cvt_mem_desc_to_layout_desc(omd, old); + if (status != success) return status; + + p.itype = ild.dt; + p.otype = old.dt; + + p.scale_type = attr->output_scales_.has_default_values() + ? scale_type_t::NONE + : (attr->output_scales_.mask_ == 0 + ? scale_type_t::COMMON + : scale_type_t::MANY); + + ptrdiff_t ss[max_ndims] = {0}; + if (p.scale_type == scale_type_t::MANY) { + ptrdiff_t last_ss = 1; + for (int d = old.ndims - 1; d >=0; --d) { + assert((d == 0 || old.id[d - 1] <= old.id[d]) + && "logical dimensions should be in ascending order"); + if (attr->output_scales_.mask_ & (1 << old.id[d])) { + ss[d] = last_ss; + last_ss *= old.dims[d]; + } + } + } + + int ndims = 0; + + int i_pos = 0; /* state for input -- current dimension */ + int o_pos = 0; /* state for output -- current dimension */ + + while (i_pos < ild.ndims && o_pos < old.ndims) { + assert(ild.id[i_pos] == old.id[o_pos]); + if (ild.id[i_pos] != old.id[o_pos]) + return runtime_error; + + assert(ndims < max_ndims); + if (ndims == max_ndims) + return runtime_error; + + if (ild.dims[i_pos] == old.dims[o_pos]) { + p.nodes[ndims].n = ild.dims[i_pos]; + p.nodes[ndims].is = ild.strides[i_pos]; + p.nodes[ndims].os = old.strides[o_pos]; + p.nodes[ndims].ss = ss[o_pos]; + ++ndims; + ++i_pos; + ++o_pos; + } else if (ild.dims[i_pos] < old.dims[o_pos]) { + assert(old.dims[o_pos] % ild.dims[i_pos] == 0); + int factor = old.dims[o_pos] / ild.dims[i_pos]; + p.nodes[ndims].n = ild.dims[i_pos]; + p.nodes[ndims].is = ild.strides[i_pos]; + p.nodes[ndims].os = old.strides[o_pos] * factor; + p.nodes[ndims].ss = ss[o_pos] * factor; + ++ndims; + ++i_pos; + old.dims[o_pos] = factor; + } else if (ild.dims[i_pos] > old.dims[o_pos]) { + assert(ild.dims[i_pos] % old.dims[o_pos] == 0); + int factor = ild.dims[i_pos] / old.dims[o_pos]; + p.nodes[ndims].n = old.dims[o_pos]; + p.nodes[ndims].is = ild.strides[i_pos] * factor; + p.nodes[ndims].os = old.strides[o_pos]; + p.nodes[ndims].ss = ss[o_pos]; + ++ndims; + ++o_pos; + ild.dims[i_pos] = factor; + } + } + p.ndims = ndims; + + dims_t zero_pos = {0}; + p.ioff = memory_desc_wrapper(imd).off_v(zero_pos); + p.ooff = memory_desc_wrapper(omd).off_v(zero_pos); + + const int sum_idx = attr->post_ops_.find(primitive_kind::sum); + p.beta = sum_idx == -1 ? 0.f : attr->post_ops_.entry_[sum_idx].sum.scale; + + return success; +} + +void prb_normalize(prb_t &p) { + for (int d = 0; d < p.ndims; ++d) { + int min_pos = d; + for (int j = d + 1; j < p.ndims; ++j) { + bool new_min = false + || p.nodes[j].os < p.nodes[min_pos].os + || (true + && p.nodes[j].os == p.nodes[min_pos].os + && p.nodes[j].n < p.nodes[min_pos].n); + if (new_min) min_pos = j; + } + if (min_pos != d) + nstl::swap(p.nodes[d], p.nodes[min_pos]); + } +} + +void prb_simplify(prb_t &p) { +#if defined(__GNUC__) && __GNUC__ >= 4 +/* GCC produces bogus array subscript is above array bounds warning for + * the `p.nodes[j - 1] = p.nodes[j]` line below, so disable it for now. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + for (int d = 0; d < p.ndims - 1; ++d) { + auto &this_node = p.nodes[d + 0]; + auto &next_node = p.nodes[d + 1]; + const bool fold = false + || next_node.n == (size_t)1 // trivial case, just drop next node + || (true // or real folding if possible + && next_node.is == (ptrdiff_t)this_node.n * this_node.is + && next_node.os == (ptrdiff_t)this_node.n * this_node.os + && next_node.ss == (ptrdiff_t)this_node.n * this_node.ss); + if (fold) { + this_node.n *= next_node.n; + for (int j = d + 2; j < p.ndims; ++j) + p.nodes[j - 1] = p.nodes[j]; + --p.ndims; + --d; // make another try + } + } +#if defined(__GNUC__) && __GNUC__ >= 4 +#pragma GCC diagnostic pop +#endif +} + +void prb_node_split(prb_t &p, int dim, size_t n1) { + assert(dim < p.ndims); + assert(p.ndims < max_ndims); + assert(p.nodes[dim].n % n1 == 0); + + p.ndims += 1; + + for (int d = p.ndims; d > dim + 1; --d) + p.nodes[d] = p.nodes[d - 1]; + + p.nodes[dim + 1].n = p.nodes[dim].n / n1; + p.nodes[dim + 1].is = p.nodes[dim].is * n1; + p.nodes[dim + 1].os = p.nodes[dim].os * n1; + p.nodes[dim + 1].ss = p.nodes[dim].ss * n1; + + p.nodes[dim].n = n1; +} + +void prb_node_swap(prb_t &p, int d0, int d1) { + assert(d0 < p.ndims); + assert(d1 < p.ndims); + assert(p.ndims < max_ndims); + + if (d0 == d1) return; + + nstl::swap(p.nodes[d0], p.nodes[d1]); +} + +void prb_node_move(prb_t &p, int d0, int d1) { + assert(d0 < p.ndims); + assert(d1 < p.ndims); + assert(p.ndims < max_ndims); + + if (d0 == d1) return; + + node_t node = p.nodes[d0]; + + if (d0 < d1) + for (int d = d0; d < d1; ++d) + p.nodes[d] = p.nodes[d + 1]; + else + for (int d = d0; d > d1; --d) + p.nodes[d] = p.nodes[d - 1]; + + p.nodes[d1] = node; +} + +void prb_dump(const prb_t &p) { + printf("@@@ type:%s:%s ndims:%d ", mkldnn_dt2str(p.itype), + mkldnn_dt2str(p.otype), p.ndims); + for (int d = 0; d < p.ndims; ++d) + printf("[%zu:%td:%td:%td]", + p.nodes[d].n, p.nodes[d].is, p.nodes[d].os, p.nodes[d].ss); + printf(" off:%zu:%zu\n", p.ioff, p.ooff); +} + +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.cpp new file mode 100644 index 000000000000..08747aa89c9c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.cpp @@ -0,0 +1,115 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "utils.hpp" + +#ifndef MKLDNN_ENABLE_JIT_PROFILING +#define MKLDNN_ENABLE_JIT_PROFILING 1 +#endif + +#ifndef MKLDNN_ENABLE_JIT_DUMP +#define MKLDNN_ENABLE_JIT_DUMP 1 +#endif + +#if MKLDNN_ENABLE_JIT_PROFILING +#include "jitprofiling/jitprofiling.h" +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { +namespace jit_utils { + +// WARNING: These functions are not thread safe and must be protected by a +// mutex + +void dump_jit_code(const void *code, size_t code_size, const char *code_name) +{ +#if MKLDNN_ENABLE_JIT_DUMP + if (code && jit_dump_enabled()) { + static int counter = 0; +#define MAX_FNAME_LEN 256 + char fname[MAX_FNAME_LEN + 1]; + // TODO (Roma): support prefix for code / linux perf dumps + snprintf(fname, MAX_FNAME_LEN, "mkldnn_dump_%s.%d.bin", code_name, + counter); + counter++; + + FILE *fp = fopen(fname, "w+"); + // Failure to dump code is not fatal + if (fp) { + size_t unused = fwrite(code, code_size, 1, fp); + UNUSED(unused); + fclose(fp); + } + } +#undef MAX_FNAME_LEN +#else + UNUSED(code); + UNUSED(code_size); + UNUSED(code_name); +#endif +} + +void register_jit_code_vtune(const void *code, size_t code_size, + const char *code_name, const char *source_file_name) +{ +#if MKLDNN_ENABLE_JIT_PROFILING + if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON) { + auto jmethod = iJIT_Method_Load(); + jmethod.method_id = iJIT_GetNewMethodID(); // XXX: not thread-safe + jmethod.method_name = (char *)code_name; // XXX: dropping const + jmethod.class_file_name = NULL; + jmethod.source_file_name = (char *)source_file_name; // XXX: dropping const + jmethod.method_load_address = (void *)code; + jmethod.method_size = (unsigned int)code_size; + + iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, + (void*)&jmethod); + } +#else + UNUSED(code); + UNUSED(code_size); + UNUSED(code_name); + UNUSED(source_file_name); +#endif +} + +void register_jit_code(const void *code, size_t code_size, + const char *code_name, const char *source_file_name) +{ + // The #ifdef guards are required to avoid generating a function that only + // consists of lock and unlock code +#if MKLDNN_ENABLE_JIT_PROFILING || MKLDNN_ENABLE_JIT_DUMP + static std::mutex m; + std::lock_guard guard(m); + + dump_jit_code(code, code_size, code_name); + register_jit_code_vtune(code, code_size, code_name, source_file_name); +#else + UNUSED(code); + UNUSED(code_size); + UNUSED(code_name); + UNUSED(source_file_name); +#endif +} + +} +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.hpp new file mode 100644 index 000000000000..2f52dba4acd9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jit_utils.hpp @@ -0,0 +1,32 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef JIT_SUPPORT_HPP +#define JIT_SUPPORT_HPP + +namespace mkldnn { +namespace impl { +namespace cpu { +namespace jit_utils { + +void register_jit_code(const void *code, size_t code_size, + const char *code_name, const char *source_file_name); + +} +} +} +} +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/LICENSE.BSD b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/LICENSE.BSD new file mode 100644 index 000000000000..4fd21cea5774 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/LICENSE.BSD @@ -0,0 +1,27 @@ +Copyright (c) 2011, Intel Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/README.md b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/README.md new file mode 100644 index 000000000000..fc67c4f134fe --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/README.md @@ -0,0 +1 @@ +This code is from [Intel SEAPI library](https://github.com/intel/IntelSEAPI) diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_config.h b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_config.h new file mode 100644 index 000000000000..edbf4a15f0fc --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_config.h @@ -0,0 +1,595 @@ +/* + + Contact Information: + http://software.intel.com/en-us/articles/intel-vtune-amplifier-xe/ + + BSD LICENSE + + Copyright (c) 2005-2014 Intel Corporation. All rights reserved. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _ITTNOTIFY_CONFIG_H_ +#define _ITTNOTIFY_CONFIG_H_ + +/** @cond exclude_from_documentation */ +#ifndef ITT_OS_WIN +# define ITT_OS_WIN 1 +#endif /* ITT_OS_WIN */ + +#ifndef ITT_OS_LINUX +# define ITT_OS_LINUX 2 +#endif /* ITT_OS_LINUX */ + +#ifndef ITT_OS_MAC +# define ITT_OS_MAC 3 +#endif /* ITT_OS_MAC */ + +#ifndef ITT_OS_FREEBSD +# define ITT_OS_FREEBSD 4 +#endif /* ITT_OS_FREEBSD */ + +#ifndef ITT_OS +# if defined WIN32 || defined _WIN32 +# define ITT_OS ITT_OS_WIN +# elif defined( __APPLE__ ) && defined( __MACH__ ) +# define ITT_OS ITT_OS_MAC +# elif defined( __FreeBSD__ ) +# define ITT_OS ITT_OS_FREEBSD +# else +# define ITT_OS ITT_OS_LINUX +# endif +#endif /* ITT_OS */ + +#ifndef ITT_PLATFORM_WIN +# define ITT_PLATFORM_WIN 1 +#endif /* ITT_PLATFORM_WIN */ + +#ifndef ITT_PLATFORM_POSIX +# define ITT_PLATFORM_POSIX 2 +#endif /* ITT_PLATFORM_POSIX */ + +#ifndef ITT_PLATFORM_MAC +# define ITT_PLATFORM_MAC 3 +#endif /* ITT_PLATFORM_MAC */ + +#ifndef ITT_PLATFORM_FREEBSD +# define ITT_PLATFORM_FREEBSD 4 +#endif /* ITT_PLATFORM_FREEBSD */ + +#ifndef ITT_PLATFORM +# if ITT_OS==ITT_OS_WIN +# define ITT_PLATFORM ITT_PLATFORM_WIN +# elif ITT_OS==ITT_OS_MAC +# define ITT_PLATFORM ITT_PLATFORM_MAC +# elif ITT_OS==ITT_OS_FREEBSD +# define ITT_PLATFORM ITT_PLATFORM_FREEBSD +# else +# define ITT_PLATFORM ITT_PLATFORM_POSIX +# endif +#endif /* ITT_PLATFORM */ + +#if defined(_UNICODE) && !defined(UNICODE) +#define UNICODE +#endif + +#include +#if ITT_PLATFORM==ITT_PLATFORM_WIN +#include +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +#include +#if defined(UNICODE) || defined(_UNICODE) +#include +#endif /* UNICODE || _UNICODE */ +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + +#ifndef ITTAPI_CDECL +# if ITT_PLATFORM==ITT_PLATFORM_WIN +# define ITTAPI_CDECL __cdecl +# else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +# if defined _M_IX86 || defined __i386__ +# define ITTAPI_CDECL __attribute__ ((cdecl)) +# else /* _M_IX86 || __i386__ */ +# define ITTAPI_CDECL /* actual only on x86 platform */ +# endif /* _M_IX86 || __i386__ */ +# endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +#endif /* ITTAPI_CDECL */ + +#ifndef STDCALL +# if ITT_PLATFORM==ITT_PLATFORM_WIN +# define STDCALL __stdcall +# else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +# if defined _M_IX86 || defined __i386__ +# define STDCALL __attribute__ ((stdcall)) +# else /* _M_IX86 || __i386__ */ +# define STDCALL /* supported only on x86 platform */ +# endif /* _M_IX86 || __i386__ */ +# endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +#endif /* STDCALL */ + +#define ITTAPI ITTAPI_CDECL +#define LIBITTAPI ITTAPI_CDECL + +/* TODO: Temporary for compatibility! */ +#define ITTAPI_CALL ITTAPI_CDECL +#define LIBITTAPI_CALL ITTAPI_CDECL + +#if ITT_PLATFORM==ITT_PLATFORM_WIN +/* use __forceinline (VC++ specific) */ +#define ITT_INLINE __forceinline +#define ITT_INLINE_ATTRIBUTE /* nothing */ +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +/* + * Generally, functions are not inlined unless optimization is specified. + * For functions declared inline, this attribute inlines the function even + * if no optimization level was specified. + */ +#ifdef __STRICT_ANSI__ +#define ITT_INLINE static +#define ITT_INLINE_ATTRIBUTE __attribute__((unused)) +#else /* __STRICT_ANSI__ */ +#define ITT_INLINE static inline +#define ITT_INLINE_ATTRIBUTE __attribute__((always_inline, unused)) +#endif /* __STRICT_ANSI__ */ +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +/** @endcond */ + +#ifndef ITT_ARCH_IA32 +# define ITT_ARCH_IA32 1 +#endif /* ITT_ARCH_IA32 */ + +#ifndef ITT_ARCH_IA32E +# define ITT_ARCH_IA32E 2 +#endif /* ITT_ARCH_IA32E */ + +#ifndef ITT_ARCH_ARM +# define ITT_ARCH_ARM 4 +#endif /* ITT_ARCH_ARM */ + +#ifndef ITT_ARCH_PPC64 +# define ITT_ARCH_PPC64 5 +#endif /* ITT_ARCH_PPC64 */ + +#ifndef ITT_ARCH +# if defined _M_IX86 || defined __i386__ +# define ITT_ARCH ITT_ARCH_IA32 +# elif defined _M_X64 || defined _M_AMD64 || defined __x86_64__ +# define ITT_ARCH ITT_ARCH_IA32E +# elif defined _M_IA64 || defined __ia64__ +# define ITT_ARCH ITT_ARCH_IA64 +# elif defined _M_ARM || defined __arm__ +# define ITT_ARCH ITT_ARCH_ARM +# elif defined __powerpc64__ +# define ITT_ARCH ITT_ARCH_PPC64 +# endif +#endif + +#ifdef __cplusplus +# define ITT_EXTERN_C extern "C" +# define ITT_EXTERN_C_BEGIN extern "C" { +# define ITT_EXTERN_C_END } +#else +# define ITT_EXTERN_C /* nothing */ +# define ITT_EXTERN_C_BEGIN /* nothing */ +# define ITT_EXTERN_C_END /* nothing */ +#endif /* __cplusplus */ + +#define ITT_TO_STR_AUX(x) #x +#define ITT_TO_STR(x) ITT_TO_STR_AUX(x) + +#define __ITT_BUILD_ASSERT(expr, suffix) do { \ + static char __itt_build_check_##suffix[(expr) ? 1 : -1]; \ + __itt_build_check_##suffix[0] = 0; \ +} while(0) +#define _ITT_BUILD_ASSERT(expr, suffix) __ITT_BUILD_ASSERT((expr), suffix) +#define ITT_BUILD_ASSERT(expr) _ITT_BUILD_ASSERT((expr), __LINE__) + +#define ITT_MAGIC { 0xED, 0xAB, 0xAB, 0xEC, 0x0D, 0xEE, 0xDA, 0x30 } + +/* Replace with snapshot date YYYYMMDD for promotion build. */ +#define API_VERSION_BUILD 20151119 + +#ifndef API_VERSION_NUM +#define API_VERSION_NUM 0.0.0 +#endif /* API_VERSION_NUM */ + +#define API_VERSION "ITT-API-Version " ITT_TO_STR(API_VERSION_NUM) \ + " (" ITT_TO_STR(API_VERSION_BUILD) ")" + +/* OS communication functions */ +#if ITT_PLATFORM==ITT_PLATFORM_WIN +#include +typedef HMODULE lib_t; +typedef DWORD TIDT; +typedef CRITICAL_SECTION mutex_t; +#define MUTEX_INITIALIZER { 0 } +#define strong_alias(name, aliasname) /* empty for Windows */ +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +#include +#if defined(UNICODE) || defined(_UNICODE) +#include +#endif /* UNICODE */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 /* need for PTHREAD_MUTEX_RECURSIVE */ +#endif /* _GNU_SOURCE */ +#ifndef __USE_UNIX98 +#define __USE_UNIX98 1 /* need for PTHREAD_MUTEX_RECURSIVE, on SLES11.1 with gcc 4.3.4 wherein pthread.h missing dependency on __USE_XOPEN2K8 */ +#endif /*__USE_UNIX98*/ +#include +typedef void* lib_t; +typedef pthread_t TIDT; +typedef pthread_mutex_t mutex_t; +#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#define _strong_alias(name, aliasname) \ + extern __typeof (name) aliasname __attribute__ ((alias (#name))); +#define strong_alias(name, aliasname) _strong_alias(name, aliasname) +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + +#if ITT_PLATFORM==ITT_PLATFORM_WIN +#define __itt_get_proc(lib, name) GetProcAddress(lib, name) +#define __itt_mutex_init(mutex) InitializeCriticalSection(mutex) +#define __itt_mutex_lock(mutex) EnterCriticalSection(mutex) +#define __itt_mutex_unlock(mutex) LeaveCriticalSection(mutex) +#define __itt_load_lib(name) LoadLibraryA(name) +#define __itt_unload_lib(handle) FreeLibrary(handle) +#define __itt_system_error() (int)GetLastError() +#define __itt_fstrcmp(s1, s2) lstrcmpA(s1, s2) +#define __itt_fstrnlen(s, l) strnlen_s(s, l) +#define __itt_fstrcpyn(s1, b, s2, l) strncpy_s(s1, b, s2, l) +#define __itt_fstrdup(s) _strdup(s) +#define __itt_thread_id() GetCurrentThreadId() +#define __itt_thread_yield() SwitchToThread() +#ifndef ITT_SIMPLE_INIT +ITT_INLINE long +__itt_interlocked_increment(volatile long* ptr) ITT_INLINE_ATTRIBUTE; +ITT_INLINE long __itt_interlocked_increment(volatile long* ptr) +{ + return InterlockedIncrement(ptr); +} +#endif /* ITT_SIMPLE_INIT */ + +#define DL_SYMBOLS (1) +#define PTHREAD_SYMBOLS (1) + +#else /* ITT_PLATFORM!=ITT_PLATFORM_WIN */ +#define __itt_get_proc(lib, name) dlsym(lib, name) +#define __itt_mutex_init(mutex) {\ + pthread_mutexattr_t mutex_attr; \ + int error_code = pthread_mutexattr_init(&mutex_attr); \ + if (error_code) \ + __itt_report_error(__itt_error_system, "pthread_mutexattr_init", \ + error_code); \ + error_code = pthread_mutexattr_settype(&mutex_attr, \ + PTHREAD_MUTEX_RECURSIVE); \ + if (error_code) \ + __itt_report_error(__itt_error_system, "pthread_mutexattr_settype", \ + error_code); \ + error_code = pthread_mutex_init(mutex, &mutex_attr); \ + if (error_code) \ + __itt_report_error(__itt_error_system, "pthread_mutex_init", \ + error_code); \ + error_code = pthread_mutexattr_destroy(&mutex_attr); \ + if (error_code) \ + __itt_report_error(__itt_error_system, "pthread_mutexattr_destroy", \ + error_code); \ +} +#define __itt_mutex_lock(mutex) pthread_mutex_lock(mutex) +#define __itt_mutex_unlock(mutex) pthread_mutex_unlock(mutex) +#define __itt_load_lib(name) dlopen(name, RTLD_LAZY) +#define __itt_unload_lib(handle) dlclose(handle) +#define __itt_system_error() errno +#define __itt_fstrcmp(s1, s2) strcmp(s1, s2) + +/* makes customer code define safe APIs for SDL_STRNLEN_S and SDL_STRNCPY_S */ +#ifdef SDL_STRNLEN_S +#define __itt_fstrnlen(s, l) SDL_STRNLEN_S(s, l) +#else +#define __itt_fstrnlen(s, l) strlen(s) +#endif /* SDL_STRNLEN_S */ +#ifdef SDL_STRNCPY_S +#define __itt_fstrcpyn(s1, b, s2, l) SDL_STRNCPY_S(s1, b, s2, l) +#else +#define __itt_fstrcpyn(s1, b, s2, l) strncpy(s1, s2, l) +#endif /* SDL_STRNCPY_S */ + +#define __itt_fstrdup(s) strdup(s) +#define __itt_thread_id() pthread_self() +#define __itt_thread_yield() sched_yield() +#if ITT_ARCH==ITT_ARCH_IA64 +#ifdef __INTEL_COMPILER +#define __TBB_machine_fetchadd4(addr, val) __fetchadd4_acq((void *)addr, val) +#else /* __INTEL_COMPILER */ +/* TODO: Add Support for not Intel compilers for IA-64 architecture */ +#endif /* __INTEL_COMPILER */ +#elif ITT_ARCH==ITT_ARCH_IA32 || ITT_ARCH==ITT_ARCH_IA32E /* ITT_ARCH!=ITT_ARCH_IA64 */ +ITT_INLINE long +__TBB_machine_fetchadd4(volatile void* ptr, long addend) ITT_INLINE_ATTRIBUTE; +ITT_INLINE long __TBB_machine_fetchadd4(volatile void* ptr, long addend) +{ + long result; + __asm__ __volatile__("lock\nxadd %0,%1" + : "=r"(result),"=m"(*(int*)ptr) + : "0"(addend), "m"(*(int*)ptr) + : "memory"); + return result; +} +#elif ITT_ARCH==ITT_ARCH_ARM || ITT_ARCH==ITT_ARCH_PPC64 +#define __TBB_machine_fetchadd4(addr, val) __sync_fetch_and_add(addr, val) +#endif /* ITT_ARCH==ITT_ARCH_IA64 */ +#ifndef ITT_SIMPLE_INIT +ITT_INLINE long +__itt_interlocked_increment(volatile long* ptr) ITT_INLINE_ATTRIBUTE; +ITT_INLINE long __itt_interlocked_increment(volatile long* ptr) +{ + return __TBB_machine_fetchadd4(ptr, 1) + 1L; +} +#endif /* ITT_SIMPLE_INIT */ + +void* dlopen(const char*, int) __attribute__((weak)); +void* dlsym(void*, const char*) __attribute__((weak)); +int dlclose(void*) __attribute__((weak)); +#define DL_SYMBOLS (dlopen && dlsym && dlclose) + +int pthread_mutex_init(pthread_mutex_t*, const pthread_mutexattr_t*) __attribute__((weak)); +int pthread_mutex_lock(pthread_mutex_t*) __attribute__((weak)); +int pthread_mutex_unlock(pthread_mutex_t*) __attribute__((weak)); +int pthread_mutex_destroy(pthread_mutex_t*) __attribute__((weak)); +int pthread_mutexattr_init(pthread_mutexattr_t*) __attribute__((weak)); +int pthread_mutexattr_settype(pthread_mutexattr_t*, int) __attribute__((weak)); +int pthread_mutexattr_destroy(pthread_mutexattr_t*) __attribute__((weak)); +pthread_t pthread_self(void) __attribute__((weak)); +#define PTHREAD_SYMBOLS (pthread_mutex_init && pthread_mutex_lock && pthread_mutex_unlock && pthread_mutex_destroy && pthread_mutexattr_init && pthread_mutexattr_settype && pthread_mutexattr_destroy && pthread_self) + +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + +typedef enum { + __itt_collection_normal = 0, + __itt_collection_paused = 1 +} __itt_collection_state; + +typedef enum { + __itt_thread_normal = 0, + __itt_thread_ignored = 1 +} __itt_thread_state; + +#pragma pack(push, 8) + +typedef struct ___itt_thread_info +{ + const char* nameA; /*!< Copy of original name in ASCII. */ +#if defined(UNICODE) || defined(_UNICODE) + const wchar_t* nameW; /*!< Copy of original name in UNICODE. */ +#else /* UNICODE || _UNICODE */ + void* nameW; +#endif /* UNICODE || _UNICODE */ + TIDT tid; + __itt_thread_state state; /*!< Thread state (paused or normal) */ + int extra1; /*!< Reserved to the runtime */ + void* extra2; /*!< Reserved to the runtime */ + struct ___itt_thread_info* next; +} __itt_thread_info; + +#include "ittnotify_types.h" /* For __itt_group_id definition */ + +typedef struct ___itt_api_info_20101001 +{ + const char* name; + void** func_ptr; + void* init_func; + __itt_group_id group; +} __itt_api_info_20101001; + +typedef struct ___itt_api_info +{ + const char* name; + void** func_ptr; + void* init_func; + void* null_func; + __itt_group_id group; +} __itt_api_info; + +typedef struct __itt_counter_info +{ + const char* nameA; /*!< Copy of original name in ASCII. */ +#if defined(UNICODE) || defined(_UNICODE) + const wchar_t* nameW; /*!< Copy of original name in UNICODE. */ +#else /* UNICODE || _UNICODE */ + void* nameW; +#endif /* UNICODE || _UNICODE */ + const char* domainA; /*!< Copy of original name in ASCII. */ +#if defined(UNICODE) || defined(_UNICODE) + const wchar_t* domainW; /*!< Copy of original name in UNICODE. */ +#else /* UNICODE || _UNICODE */ + void* domainW; +#endif /* UNICODE || _UNICODE */ + int type; + long index; + int extra1; /*!< Reserved to the runtime */ + void* extra2; /*!< Reserved to the runtime */ + struct __itt_counter_info* next; +} __itt_counter_info_t; + +struct ___itt_domain; +struct ___itt_string_handle; + +typedef struct ___itt_global +{ + unsigned char magic[8]; + unsigned long version_major; + unsigned long version_minor; + unsigned long version_build; + volatile long api_initialized; + volatile long mutex_initialized; + volatile long atomic_counter; + mutex_t mutex; + lib_t lib; + void* error_handler; + const char** dll_path_ptr; + __itt_api_info* api_list_ptr; + struct ___itt_global* next; + /* Joinable structures below */ + __itt_thread_info* thread_list; + struct ___itt_domain* domain_list; + struct ___itt_string_handle* string_list; + __itt_collection_state state; + __itt_counter_info_t* counter_list; +} __itt_global; + +#pragma pack(pop) + +#define NEW_THREAD_INFO_W(gptr,h,h_tail,t,s,n) { \ + h = (__itt_thread_info*)malloc(sizeof(__itt_thread_info)); \ + if (h != NULL) { \ + h->tid = t; \ + h->nameA = NULL; \ + h->nameW = n ? _wcsdup(n) : NULL; \ + h->state = s; \ + h->extra1 = 0; /* reserved */ \ + h->extra2 = NULL; /* reserved */ \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->thread_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_THREAD_INFO_A(gptr,h,h_tail,t,s,n) { \ + h = (__itt_thread_info*)malloc(sizeof(__itt_thread_info)); \ + if (h != NULL) { \ + h->tid = t; \ + h->nameA = n ? __itt_fstrdup(n) : NULL; \ + h->nameW = NULL; \ + h->state = s; \ + h->extra1 = 0; /* reserved */ \ + h->extra2 = NULL; /* reserved */ \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->thread_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_DOMAIN_W(gptr,h,h_tail,name) { \ + h = (__itt_domain*)malloc(sizeof(__itt_domain)); \ + if (h != NULL) { \ + h->flags = 1; /* domain is enabled by default */ \ + h->nameA = NULL; \ + h->nameW = name ? _wcsdup(name) : NULL; \ + h->extra1 = 0; /* reserved */ \ + h->extra2 = NULL; /* reserved */ \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->domain_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_DOMAIN_A(gptr,h,h_tail,name) { \ + h = (__itt_domain*)malloc(sizeof(__itt_domain)); \ + if (h != NULL) { \ + h->flags = 1; /* domain is enabled by default */ \ + h->nameA = name ? __itt_fstrdup(name) : NULL; \ + h->nameW = NULL; \ + h->extra1 = 0; /* reserved */ \ + h->extra2 = NULL; /* reserved */ \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->domain_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_STRING_HANDLE_W(gptr,h,h_tail,name) { \ + h = (__itt_string_handle*)malloc(sizeof(__itt_string_handle)); \ + if (h != NULL) { \ + h->strA = NULL; \ + h->strW = name ? _wcsdup(name) : NULL; \ + h->extra1 = 0; /* reserved */ \ + h->extra2 = NULL; /* reserved */ \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->string_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_STRING_HANDLE_A(gptr,h,h_tail,name) { \ + h = (__itt_string_handle*)malloc(sizeof(__itt_string_handle)); \ + if (h != NULL) { \ + h->strA = name ? __itt_fstrdup(name) : NULL; \ + h->strW = NULL; \ + h->extra1 = 0; /* reserved */ \ + h->extra2 = NULL; /* reserved */ \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->string_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_COUNTER_W(gptr,h,h_tail,name,domain,type) { \ + h = (__itt_counter_info_t*)malloc(sizeof(__itt_counter_info_t)); \ + if (h != NULL) { \ + h->nameA = NULL; \ + h->nameW = name ? _wcsdup(name) : NULL; \ + h->domainA = NULL; \ + h->domainW = name ? _wcsdup(domain) : NULL; \ + h->type = type; \ + h->index = 0; \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->counter_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#define NEW_COUNTER_A(gptr,h,h_tail,name,domain,type) { \ + h = (__itt_counter_info_t*)malloc(sizeof(__itt_counter_info_t)); \ + if (h != NULL) { \ + h->nameA = name ? __itt_fstrdup(name) : NULL; \ + h->nameW = NULL; \ + h->domainA = domain ? __itt_fstrdup(domain) : NULL; \ + h->domainW = NULL; \ + h->type = type; \ + h->index = 0; \ + h->next = NULL; \ + if (h_tail == NULL) \ + (gptr)->counter_list = h; \ + else \ + h_tail->next = h; \ + } \ +} + +#endif /* _ITTNOTIFY_CONFIG_H_ */ diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_types.h b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_types.h new file mode 100644 index 000000000000..99fbc240547f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/ittnotify_types.h @@ -0,0 +1,94 @@ +/* + + Contact Information: + http://software.intel.com/en-us/articles/intel-vtune-amplifier-xe/ + + BSD LICENSE + + Copyright (c) 2005-2014 Intel Corporation. All rights reserved. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ITTNOTIFY_TYPES_H_ +#define _ITTNOTIFY_TYPES_H_ + +typedef enum ___itt_group_id +{ + __itt_group_none = 0, + __itt_group_legacy = 1<<0, + __itt_group_control = 1<<1, + __itt_group_thread = 1<<2, + __itt_group_mark = 1<<3, + __itt_group_sync = 1<<4, + __itt_group_fsync = 1<<5, + __itt_group_jit = 1<<6, + __itt_group_model = 1<<7, + __itt_group_splitter_min = 1<<7, + __itt_group_counter = 1<<8, + __itt_group_frame = 1<<9, + __itt_group_stitch = 1<<10, + __itt_group_heap = 1<<11, + __itt_group_splitter_max = 1<<12, + __itt_group_structure = 1<<12, + __itt_group_suppress = 1<<13, + __itt_group_arrays = 1<<14, + __itt_group_all = -1 +} __itt_group_id; + +#pragma pack(push, 8) + +typedef struct ___itt_group_list +{ + __itt_group_id id; + const char* name; +} __itt_group_list; + +#pragma pack(pop) + +#define ITT_GROUP_LIST(varname) \ + static __itt_group_list varname[] = { \ + { __itt_group_all, "all" }, \ + { __itt_group_control, "control" }, \ + { __itt_group_thread, "thread" }, \ + { __itt_group_mark, "mark" }, \ + { __itt_group_sync, "sync" }, \ + { __itt_group_fsync, "fsync" }, \ + { __itt_group_jit, "jit" }, \ + { __itt_group_model, "model" }, \ + { __itt_group_counter, "counter" }, \ + { __itt_group_frame, "frame" }, \ + { __itt_group_stitch, "stitch" }, \ + { __itt_group_heap, "heap" }, \ + { __itt_group_structure, "structure" }, \ + { __itt_group_suppress, "suppress" }, \ + { __itt_group_arrays, "arrays" }, \ + { __itt_group_none, NULL } \ + } + +#endif /* _ITTNOTIFY_TYPES_H_ */ diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c new file mode 100644 index 000000000000..15f4b9929baa --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c @@ -0,0 +1,293 @@ +/* + + Contact Information: + http://software.intel.com/en-us/articles/intel-vtune-amplifier-xe/ + + BSD LICENSE + + Copyright (c) 2005-2014 Intel Corporation. All rights reserved. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ittnotify_config.h" + +#if ITT_PLATFORM==ITT_PLATFORM_WIN +#include +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ +#if ITT_PLATFORM != ITT_PLATFORM_MAC && ITT_PLATFORM != ITT_PLATFORM_FREEBSD +#include +#endif +#include + +#include "jitprofiling.h" + +static const char rcsid[] = "\n@(#) $Revision: 471937 $\n"; + +#define DLL_ENVIRONMENT_VAR "VS_PROFILER" + +#ifndef NEW_DLL_ENVIRONMENT_VAR +#if ITT_ARCH==ITT_ARCH_IA32 +#define NEW_DLL_ENVIRONMENT_VAR "INTEL_JIT_PROFILER32" +#else +#define NEW_DLL_ENVIRONMENT_VAR "INTEL_JIT_PROFILER64" +#endif +#endif /* NEW_DLL_ENVIRONMENT_VAR */ + +#if ITT_PLATFORM==ITT_PLATFORM_WIN +#define DEFAULT_DLLNAME "JitPI.dll" +HINSTANCE m_libHandle = NULL; +#elif ITT_PLATFORM==ITT_PLATFORM_MAC +#define DEFAULT_DLLNAME "libJitPI.dylib" +void* m_libHandle = NULL; +#else +#define DEFAULT_DLLNAME "libJitPI.so" +void* m_libHandle = NULL; +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + +/* default location of JIT profiling agent on Android */ +#define ANDROID_JIT_AGENT_PATH "/data/intel/libittnotify.so" + +/* the function pointers */ +typedef unsigned int(JITAPI *TPInitialize)(void); +static TPInitialize FUNC_Initialize=NULL; + +typedef unsigned int(JITAPI *TPNotify)(unsigned int, void*); +static TPNotify FUNC_NotifyEvent=NULL; + +static iJIT_IsProfilingActiveFlags executionMode = iJIT_NOTHING_RUNNING; + +/* end collector dll part. */ + +/* loadiJIT_Funcs() : this function is called just in the beginning + * and is responsible to load the functions from BistroJavaCollector.dll + * result: + * on success: the functions loads, iJIT_DLL_is_missing=0, return value = 1 + * on failure: the functions are NULL, iJIT_DLL_is_missing=1, return value = 0 + */ +static int loadiJIT_Funcs(void); + +/* global representing whether the collector can't be loaded */ +static int iJIT_DLL_is_missing = 0; + +ITT_EXTERN_C int JITAPI +iJIT_NotifyEvent(iJIT_JVM_EVENT event_type, void *EventSpecificData) +{ + int ReturnValue = 0; + + /* initialization part - the collector has not been loaded yet. */ + if (!FUNC_NotifyEvent) + { + if (iJIT_DLL_is_missing) + return 0; + + if (!loadiJIT_Funcs()) + return 0; + } + + if (event_type == iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED || + event_type == iJVM_EVENT_TYPE_METHOD_UPDATE) + { + if (((piJIT_Method_Load)EventSpecificData)->method_id == 0) + return 0; + } + else if (event_type == iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2) + { + if (((piJIT_Method_Load_V2)EventSpecificData)->method_id == 0) + return 0; + } + else if (event_type == iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V3) + { + if (((piJIT_Method_Load_V3)EventSpecificData)->method_id == 0) + return 0; + } + else if (event_type == iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED) + { + if (((piJIT_Method_Inline_Load)EventSpecificData)->method_id == 0 || + ((piJIT_Method_Inline_Load)EventSpecificData)->parent_method_id == 0) + return 0; + } + + ReturnValue = (int)FUNC_NotifyEvent(event_type, EventSpecificData); + + return ReturnValue; +} + +ITT_EXTERN_C iJIT_IsProfilingActiveFlags JITAPI iJIT_IsProfilingActive() +{ + if (!iJIT_DLL_is_missing) + { + loadiJIT_Funcs(); + } + + return executionMode; +} + +/* This function loads the collector dll and the relevant functions. + * on success: all functions load, iJIT_DLL_is_missing = 0, return value = 1 + * on failure: all functions are NULL, iJIT_DLL_is_missing = 1, return value = 0 + */ +static int loadiJIT_Funcs() +{ + static int bDllWasLoaded = 0; + char *dllName = (char*)rcsid; /* !! Just to avoid unused code elimination */ +#if ITT_PLATFORM==ITT_PLATFORM_WIN + DWORD dNameLength = 0; +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + + if(bDllWasLoaded) + { + /* dll was already loaded, no need to do it for the second time */ + return 1; + } + + /* Assumes that the DLL will not be found */ + iJIT_DLL_is_missing = 1; + FUNC_NotifyEvent = NULL; + + if (m_libHandle) + { +#if ITT_PLATFORM==ITT_PLATFORM_WIN + FreeLibrary(m_libHandle); +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + dlclose(m_libHandle); +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + m_libHandle = NULL; + } + + /* Try to get the dll name from the environment */ +#if ITT_PLATFORM==ITT_PLATFORM_WIN + dNameLength = GetEnvironmentVariableA(NEW_DLL_ENVIRONMENT_VAR, NULL, 0); + if (dNameLength) + { + DWORD envret = 0; + dllName = (char*)malloc(sizeof(char) * (dNameLength + 1)); + if(dllName != NULL) + { + envret = GetEnvironmentVariableA(NEW_DLL_ENVIRONMENT_VAR, + dllName, dNameLength); + if (envret) + { + /* Try to load the dll from the PATH... */ + m_libHandle = LoadLibraryExA(dllName, + NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + } + free(dllName); + } + } else { + /* Try to use old VS_PROFILER variable */ + dNameLength = GetEnvironmentVariableA(DLL_ENVIRONMENT_VAR, NULL, 0); + if (dNameLength) + { + DWORD envret = 0; + dllName = (char*)malloc(sizeof(char) * (dNameLength + 1)); + if(dllName != NULL) + { + envret = GetEnvironmentVariableA(DLL_ENVIRONMENT_VAR, + dllName, dNameLength); + if (envret) + { + /* Try to load the dll from the PATH... */ + m_libHandle = LoadLibraryA(dllName); + } + free(dllName); + } + } + } +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + dllName = getenv(NEW_DLL_ENVIRONMENT_VAR); + if (!dllName) + dllName = getenv(DLL_ENVIRONMENT_VAR); +#if defined(__ANDROID__) || defined(ANDROID) + if (!dllName) + dllName = ANDROID_JIT_AGENT_PATH; +#endif + if (dllName) + { + /* Try to load the dll from the PATH... */ + m_libHandle = dlopen(dllName, RTLD_LAZY); + } +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + + if (!m_libHandle) + { +#if ITT_PLATFORM==ITT_PLATFORM_WIN + m_libHandle = LoadLibraryA(DEFAULT_DLLNAME); +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + m_libHandle = dlopen(DEFAULT_DLLNAME, RTLD_LAZY); +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + } + + /* if the dll wasn't loaded - exit. */ + if (!m_libHandle) + { + iJIT_DLL_is_missing = 1; /* don't try to initialize + * JIT agent the second time + */ + return 0; + } + +#if ITT_PLATFORM==ITT_PLATFORM_WIN + FUNC_NotifyEvent = (TPNotify)GetProcAddress(m_libHandle, "NotifyEvent"); +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + FUNC_NotifyEvent = (TPNotify)dlsym(m_libHandle, "NotifyEvent"); +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + if (!FUNC_NotifyEvent) + { + FUNC_Initialize = NULL; + return 0; + } + +#if ITT_PLATFORM==ITT_PLATFORM_WIN + FUNC_Initialize = (TPInitialize)GetProcAddress(m_libHandle, "Initialize"); +#else /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + FUNC_Initialize = (TPInitialize)dlsym(m_libHandle, "Initialize"); +#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */ + if (!FUNC_Initialize) + { + FUNC_NotifyEvent = NULL; + return 0; + } + + executionMode = (iJIT_IsProfilingActiveFlags)FUNC_Initialize(); + + bDllWasLoaded = 1; + iJIT_DLL_is_missing = 0; /* DLL is ok. */ + + return 1; +} + +ITT_EXTERN_C unsigned int JITAPI iJIT_GetNewMethodID() +{ + static unsigned int methodID = 1; + + if (methodID == 0) + return 0; /* ERROR : this is not a valid value */ + + return methodID++; +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.h b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.h new file mode 100644 index 000000000000..bf0489b1a188 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.h @@ -0,0 +1,673 @@ +/* + + Contact Information: + http://software.intel.com/en-us/articles/intel-vtune-amplifier-xe/ + + BSD LICENSE + + Copyright (c) 2005-2014 Intel Corporation. All rights reserved. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __JITPROFILING_H__ +#define __JITPROFILING_H__ + +/** + * @brief JIT Profiling APIs + * + * The JIT Profiling API is used to report information about just-in-time + * generated code that can be used by performance tools. The user inserts + * calls in the code generator to report information before JIT-compiled + * code goes to execution. This information is collected at runtime and used + * by tools like Intel(R) VTune(TM) Amplifier to display performance metrics + * associated with JIT-compiled code. + * + * These APIs can be used to\n + * - **Profile trace-based and method-based JIT-compiled + * code**. Some examples of environments that you can profile with these APIs: + * dynamic JIT compilation of JavaScript code traces, JIT execution in OpenCL(TM) + * software technology, Java/.NET managed execution environments, and custom + * ISV JIT engines. + * @code + * #include + * + * if (iJIT_IsProfilingActive != iJIT_SAMPLING_ON) { + * return; + * } + * + * iJIT_Method_Load jmethod = {0}; + * jmethod.method_id = iJIT_GetNewMethodID(); + * jmethod.method_name = "method_name"; + * jmethod.class_file_name = "class_name"; + * jmethod.source_file_name = "source_file_name"; + * jmethod.method_load_address = code_addr; + * jmethod.method_size = code_size; + * + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&jmethod); + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_SHUTDOWN, NULL); + * @endcode + * + * * Expected behavior: + * * If any iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED event overwrites an + * already reported method, then such a method becomes invalid and its + * memory region is treated as unloaded. VTune Amplifier displays the metrics + * collected by the method until it is overwritten. + * * If supplied line number information contains multiple source lines for + * the same assembly instruction (code location), then VTune Amplifier picks up + * the first line number. + * * Dynamically generated code can be associated with a module name. + * Use the iJIT_Method_Load_V2 structure.\n + * Clarification of some cases: + * * If you register a function with the same method ID multiple times, + * specifying different module names, then the VTune Amplifier picks up + * the module name registered first. If you want to distinguish the same + * function between different JIT engines, supply different method IDs for + * each function. Other symbolic information (for example, source file) + * can be identical. + * + * - **Analyze split functions** (multiple joint or disjoint code regions + * belonging to the same function) **including re-JIT** + * with potential overlapping of code regions in time, which is common in + * resource-limited environments. + * @code + * #include + * + * unsigned int method_id = iJIT_GetNewMethodID(); + * + * iJIT_Method_Load a = {0}; + * a.method_id = method_id; + * a.method_load_address = 0x100; + * a.method_size = 0x20; + * + * iJIT_Method_Load b = {0}; + * b.method_id = method_id; + * b.method_load_address = 0x200; + * b.method_size = 0x30; + * + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&a); + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&b); + * @endcode + * + * * Expected behaviour: + * * If a iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED event overwrites an + * already reported method, then such a method becomes invalid and + * its memory region is treated as unloaded. + * * All code regions reported with the same method ID are considered as + * belonging to the same method. Symbolic information (method name, + * source file name) will be taken from the first notification, and all + * subsequent notifications with the same method ID will be processed + * only for line number table information. So, the VTune Amplifier will map + * samples to a source line using the line number table from the current + * notification while taking the source file name from the very first one.\n + * Clarification of some cases:\n + * * If you register a second code region with a different source file + * name and the same method ID, then this information will be saved and + * will not be considered as an extension of the first code region, but + * VTune Amplifier will use the source file of the first code region and map + * performance metrics incorrectly. + * * If you register a second code region with the same source file as + * for the first region and the same method ID, then the source file will be + * discarded but VTune Amplifier will map metrics to the source file correctly. + * * If you register a second code region with a null source file and + * the same method ID, then provided line number info will be associated + * with the source file of the first code region. + * + * - **Explore inline functions** including multi-level hierarchy of + * nested inline methods which shows how performance metrics are distributed through them. + * @code + * #include + * + * // method_id parent_id + * // [-- c --] 3000 2000 + * // [---- d -----] 2001 1000 + * // [---- b ----] 2000 1000 + * // [------------ a ----------------] 1000 n/a + * + * iJIT_Method_Load a = {0}; + * a.method_id = 1000; + * + * iJIT_Method_Inline_Load b = {0}; + * b.method_id = 2000; + * b.parent_method_id = 1000; + * + * iJIT_Method_Inline_Load c = {0}; + * c.method_id = 3000; + * c.parent_method_id = 2000; + * + * iJIT_Method_Inline_Load d = {0}; + * d.method_id = 2001; + * d.parent_method_id = 1000; + * + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&a); + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED, (void*)&b); + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED, (void*)&c); + * iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED, (void*)&d); + * @endcode + * + * * Requirements: + * * Each inline (iJIT_Method_Inline_Load) method should be associated + * with two method IDs: one for itself; one for its immediate parent. + * * Address regions of inline methods of the same parent method cannot + * overlap each other. + * * Execution of the parent method must not be started until it and all + * its inline methods are reported. + * * Expected behaviour: + * * In case of nested inline methods an order of + * iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED events is not important. + * * If any event overwrites either inline method or top parent method, + * then the parent, including inline methods, becomes invalid and its memory + * region is treated as unloaded. + * + * **Life time of allocated data**\n + * The client sends an event notification to the agent with event-specific + * data, which is a structure. The pointers in the structure refer to memory + * allocated by the client, which responsible for releasing it. The pointers are + * used by the iJIT_NotifyEvent method to copy client's data in a trace file, + * and they are not used after the iJIT_NotifyEvent method returns. + */ + +/** + * @defgroup jitapi JIT Profiling + * @ingroup internal + * @{ + */ + +/** + * @brief Enumerator for the types of notifications + */ +typedef enum iJIT_jvm_event +{ + iJVM_EVENT_TYPE_SHUTDOWN = 2, /**<\brief Send this to shutdown the agent. + * Use NULL for event data. */ + + iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED = 13, /**<\brief Send when dynamic code is + * JIT compiled and loaded into + * memory by the JIT engine, but + * before the code is executed. + * Use iJIT_Method_Load as event + * data. */ +/** @cond exclude_from_documentation */ + iJVM_EVENT_TYPE_METHOD_UNLOAD_START, /**<\brief Send when compiled dynamic + * code is being unloaded from memory. + * Use iJIT_Method_Load as event data.*/ +/** @endcond */ + + iJVM_EVENT_TYPE_METHOD_UPDATE, /**<\brief Send to provide new content for + * a previously reported dynamic code. + * The previous content will be invalidated + * starting from the time of the notification. + * Use iJIT_Method_Load as event data but + * required fields are following: + * - method_id identify the code to update. + * - method_load_address specify start address + * within identified code range + * where update should be started. + * - method_size specify length of updated code + * range. */ + + + iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED, /**<\brief Send when an inline dynamic + * code is JIT compiled and loaded + * into memory by the JIT engine, + * but before the parent code region + * starts executing. + * Use iJIT_Method_Inline_Load as event data.*/ + +/** @cond exclude_from_documentation */ + iJVM_EVENT_TYPE_METHOD_UPDATE_V2, +/** @endcond */ + + iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2 = 21, /**<\brief Send when a dynamic code is + * JIT compiled and loaded into + * memory by the JIT engine, but + * before the code is executed. + * Use iJIT_Method_Load_V2 as event data. */ + + iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V3 /**<\brief Send when a dynamic code is + * JIT compiled and loaded into + * memory by the JIT engine, but + * before the code is executed. + * Use iJIT_Method_Load_V3 as event data. */ +} iJIT_JVM_EVENT; + +/** + * @brief Enumerator for the agent's mode + */ +typedef enum _iJIT_IsProfilingActiveFlags +{ + iJIT_NOTHING_RUNNING = 0x0000, /**<\brief The agent is not running; + * iJIT_NotifyEvent calls will + * not be processed. */ + iJIT_SAMPLING_ON = 0x0001, /**<\brief The agent is running and + * ready to process notifications. */ +} iJIT_IsProfilingActiveFlags; + +/** + * @brief Description of a single entry in the line number information of a code region. + * @details A table of line number entries gives information about how the reported code region + * is mapped to source file. + * Intel(R) VTune(TM) Amplifier uses line number information to attribute + * the samples (virtual address) to a line number. \n + * It is acceptable to report different code addresses for the same source line: + * @code + * Offset LineNumber + * 1 2 + * 12 4 + * 15 2 + * 18 1 + * 21 30 + * + * VTune Amplifier constructs the following table using the client data + * + * Code subrange Line number + * 0-1 2 + * 1-12 4 + * 12-15 2 + * 15-18 1 + * 18-21 30 + * @endcode + */ +typedef struct _LineNumberInfo +{ + unsigned int Offset; /**<\brief Offset from the begining of the code region. */ + unsigned int LineNumber; /**<\brief Matching source line number offset (from beginning of source file). */ + +} *pLineNumberInfo, LineNumberInfo; + +/** + * @brief Enumerator for the code architecture. + */ +typedef enum _iJIT_CodeArchitecture +{ + iJIT_CA_NATIVE = 0, /**<\brief Native to the process architecture that is calling it. */ + + iJIT_CA_32, /**<\brief 32-bit machine code. */ + + iJIT_CA_64 /**<\brief 64-bit machine code. */ + +} iJIT_CodeArchitecture; + +#pragma pack(push, 8) + +/** + * @brief Description of a JIT-compiled method + * @details When you use the iJIT_Method_Load structure to describe + * the JIT compiled method, use iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED + * as an event type to report it. + */ +typedef struct _iJIT_Method_Load +{ + unsigned int method_id; /**<\brief Unique method ID. Cannot be 0. + * You must either use the API function + * iJIT_GetNewMethodID to get a valid and unique + * method ID, or else manage ID uniqueness + * and correct range by yourself.\n + * You must use the same method ID for all code + * regions of the same method, otherwise different + * method IDs specify different methods. */ + + char* method_name; /**<\brief The name of the method. It can be optionally + * prefixed with its class name and appended with + * its complete signature. Can't be NULL. */ + + void* method_load_address; /**<\brief The start virtual address of the method code + * region. If NULL, data provided with + * event are not accepted. */ + + unsigned int method_size; /**<\brief The code size of the method in memory. + * If 0, then data provided with the event are not + * accepted. */ + + unsigned int line_number_size; /**<\brief The number of entries in the line number + * table.0 if none. */ + + pLineNumberInfo line_number_table; /**<\brief Pointer to the line numbers info + * array. Can be NULL if + * line_number_size is 0. See + * LineNumberInfo Structure for a + * description of a single entry in + * the line number info array */ + + unsigned int class_id; /**<\brief This field is obsolete. */ + + char* class_file_name; /**<\brief Class name. Can be NULL.*/ + + char* source_file_name; /**<\brief Source file name. Can be NULL.*/ + +} *piJIT_Method_Load, iJIT_Method_Load; + +/** + * @brief Description of a JIT-compiled method + * @details When you use the iJIT_Method_Load_V2 structure to describe + * the JIT compiled method, use iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2 + * as an event type to report it. + */ +typedef struct _iJIT_Method_Load_V2 +{ + unsigned int method_id; /**<\brief Unique method ID. Cannot be 0. + * You must either use the API function + * iJIT_GetNewMethodID to get a valid and unique + * method ID, or else manage ID uniqueness + * and correct range by yourself.\n + * You must use the same method ID for all code + * regions of the same method, otherwise different + * method IDs specify different methods. */ + + char* method_name; /**<\brief The name of the method. It can be optionally + * prefixed with its class name and appended with + * its complete signature. Can't be NULL. */ + + void* method_load_address; /**<\brief The start virtual address of the method code + * region. If NULL, then data provided with the + * event are not accepted. */ + + unsigned int method_size; /**<\brief The code size of the method in memory. + * If 0, then data provided with the event are not + * accepted. */ + + unsigned int line_number_size; /**<\brief The number of entries in the line number + * table. 0 if none. */ + + pLineNumberInfo line_number_table; /**<\brief Pointer to the line numbers info + * array. Can be NULL if + * line_number_size is 0. See + * LineNumberInfo Structure for a + * description of a single entry in + * the line number info array. */ + + char* class_file_name; /**<\brief Class name. Can be NULL. */ + + char* source_file_name; /**<\brief Source file name. Can be NULL. */ + + char* module_name; /**<\brief Module name. Can be NULL. + The module name can be useful for distinguishing among + different JIT engines. VTune Amplifier will display + reported methods grouped by specific module. */ + +} *piJIT_Method_Load_V2, iJIT_Method_Load_V2; + +/** + * @brief Description of a JIT-compiled method + * @details The iJIT_Method_Load_V3 structure is the same as iJIT_Method_Load_V2 + * with a newly introduced 'arch' field that specifies architecture of the code region. + * When you use the iJIT_Method_Load_V3 structure to describe + * the JIT compiled method, use iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V3 + * as an event type to report it. + */ +typedef struct _iJIT_Method_Load_V3 +{ + unsigned int method_id; /**<\brief Unique method ID. Cannot be 0. + * You must either use the API function + * iJIT_GetNewMethodID to get a valid and unique + * method ID, or manage ID uniqueness + * and correct range by yourself.\n + * You must use the same method ID for all code + * regions of the same method, otherwise they are + * treated as regions of different methods. */ + + char* method_name; /**<\brief The name of the method. It can be optionally + * prefixed with its class name and appended with + * its complete signature. Cannot be NULL. */ + + void* method_load_address; /**<\brief The start virtual address of the method code + * region. If NULL, then data provided with the + * event are not accepted. */ + + unsigned int method_size; /**<\brief The code size of the method in memory. + * If 0, then data provided with the event are not + * accepted. */ + + unsigned int line_number_size; /**<\brief The number of entries in the line number + * table. 0 if none. */ + + pLineNumberInfo line_number_table; /**<\brief Pointer to the line numbers info + * array. Can be NULL if + * line_number_size is 0. See + * LineNumberInfo Structure for a + * description of a single entry in + * the line number info array. */ + + char* class_file_name; /**<\brief Class name. Can be NULL. */ + + char* source_file_name; /**<\brief Source file name. Can be NULL. */ + + char* module_name; /**<\brief Module name. Can be NULL. + * The module name can be useful for distinguishing among + * different JIT engines. VTune Amplifier will display + * reported methods grouped by specific module. */ + + iJIT_CodeArchitecture module_arch; /**<\brief Architecture of the method's code region. + * By default, it is the same as the process + * architecture that is calling it. + * For example, you can use it if your 32-bit JIT + * engine generates 64-bit code. + * + * If JIT engine reports both 32-bit and 64-bit types + * of methods then VTune Amplifier splits the methods + * with the same module name but with different + * architectures in two different modules. VTune Amplifier + * modifies the original name provided with a 64-bit method + * version by ending it with '(64)' */ + +} *piJIT_Method_Load_V3, iJIT_Method_Load_V3; + +/** + * @brief Description of an inline JIT-compiled method + * @details When you use the_iJIT_Method_Inline_Load structure to describe + * the JIT compiled method, use iJVM_EVENT_TYPE_METHOD_INLINE_LOAD_FINISHED + * as an event type to report it. + */ +typedef struct _iJIT_Method_Inline_Load +{ + unsigned int method_id; /**<\brief Unique method ID. Cannot be 0. + * You must either use the API function + * iJIT_GetNewMethodID to get a valid and unique + * method ID, or else manage ID uniqueness + * and correct range by yourself. */ + + unsigned int parent_method_id; /**<\brief Unique immediate parent's method ID. + * Cannot be 0. + * You must either use the API function + * iJIT_GetNewMethodID to get a valid and unique + * method ID, or else manage ID uniqueness + * and correct range by yourself. */ + + char* method_name; /**<\brief The name of the method. It can be optionally + * prefixed with its class name and appended with + * its complete signature. Can't be NULL. */ + + void* method_load_address; /** <\brief The virtual address on which the method + * is inlined. If NULL, then data provided with + * the event are not accepted. */ + + unsigned int method_size; /**<\brief The code size of the method in memory. + * If 0, then data provided with the event are not + * accepted. */ + + unsigned int line_number_size; /**<\brief The number of entries in the line number + * table. 0 if none. */ + + pLineNumberInfo line_number_table; /**<\brief Pointer to the line numbers info + * array. Can be NULL if + * line_number_size is 0. See + * LineNumberInfo Structure for a + * description of a single entry in + * the line number info array */ + + char* class_file_name; /**<\brief Class name. Can be NULL.*/ + + char* source_file_name; /**<\brief Source file name. Can be NULL.*/ + +} *piJIT_Method_Inline_Load, iJIT_Method_Inline_Load; + +/** @cond exclude_from_documentation */ +/** + * @brief Description of a segment type + * @details Use the segment type to specify a type of data supplied + * with the iJVM_EVENT_TYPE_METHOD_UPDATE_V2 event to be applied to + * a certain code trace. + */ +typedef enum _iJIT_SegmentType +{ + iJIT_CT_UNKNOWN = 0, + + iJIT_CT_CODE, /**<\brief Executable code. */ + + iJIT_CT_DATA, /**<\brief Data (not executable code). + * VTune Amplifier uses the format string + * (see iJIT_Method_Update) to represent + * this data in the VTune Amplifier GUI */ + + iJIT_CT_KEEP, /**<\brief Use the previous markup for the trace. + * Can be used for the following + * iJVM_EVENT_TYPE_METHOD_UPDATE_V2 events, + * if the type of the previously reported segment + * type is the same. */ + iJIT_CT_EOF +} iJIT_SegmentType; + +/** + * @brief Description of a dynamic update of the content within JIT-compiled method + * @details The JIT engine may generate the methods that are updated at runtime + * partially by mixed (data + executable code) content. When you use the iJIT_Method_Update + * structure to describe the update of the content within a JIT-compiled method, + * use iJVM_EVENT_TYPE_METHOD_UPDATE_V2 as an event type to report it. + * + * On the first Update event, VTune Amplifier copies the original code range reported by + * the iJVM_EVENT_TYPE_METHOD_LOAD event, then modifies it with the supplied bytes and + * adds the modified range to the original method. For next update events, VTune Amplifier + * does the same but it uses the latest modified version of a code region for update. + * Eventually, VTune Amplifier GUI displays multiple code ranges for the method reported by + * the iJVM_EVENT_TYPE_METHOD_LOAD event. + * Notes: + * - Multiple update events with different types for the same trace are allowed + * but they must be reported for the same code ranges. + * Example, + * @code + * [-- data---] Allowed + * [-- code --] Allowed + * [code] Ignored + * [-- data---] Allowed + * [-- code --] Allowed + * [------------ trace ---------] + * @endcode + * - The types of previously reported events can be changed but they must be reported + * for the same code ranges. + * Example, + * @code + * [-- data---] Allowed + * [-- code --] Allowed + * [-- data---] Allowed + * [-- code --] Allowed + * [------------ trace ---------] + * @endcode + */ + +typedef struct _iJIT_Method_Update +{ + void* load_address; /**<\brief Start address of the update within a method */ + + unsigned int size; /**<\brief The update size */ + + iJIT_SegmentType type; /**<\brief Type of the update */ + + const char* data_format; /**<\brief C string that contains a format string + * that follows the same specifications as format in printf. + * The format string is used for iJIT_CT_CODE only + * and cannot be NULL. + * Format can be changed on the fly. */ +} *piJIT_Method_Update, iJIT_Method_Update; + +/** @endcond */ + +#pragma pack(pop) + +/** @cond exclude_from_documentation */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef JITAPI_CDECL +# if defined WIN32 || defined _WIN32 +# define JITAPI_CDECL __cdecl +# else /* defined WIN32 || defined _WIN32 */ +# if defined _M_IX86 || defined __i386__ +# define JITAPI_CDECL __attribute__ ((cdecl)) +# else /* _M_IX86 || __i386__ */ +# define JITAPI_CDECL /* actual only on x86_64 platform */ +# endif /* _M_IX86 || __i386__ */ +# endif /* defined WIN32 || defined _WIN32 */ +#endif /* JITAPI_CDECL */ + +#define JITAPI JITAPI_CDECL +/** @endcond */ + +/** + * @brief Generates a new unique method ID. + * + * You must use this API to obtain unique and valid method IDs for methods or + * traces reported to the agent if you don't have your own mechanism to generate + * unique method IDs. + * + * @return a new unique method ID. When out of unique method IDs, this API + * returns 0, which is not an accepted value. + */ +unsigned int JITAPI iJIT_GetNewMethodID(void); + +/** + * @brief Returns the current mode of the agent. + * + * @return iJIT_SAMPLING_ON, indicating that agent is running, or + * iJIT_NOTHING_RUNNING if no agent is running. + */ +iJIT_IsProfilingActiveFlags JITAPI iJIT_IsProfilingActive(void); + +/** + * @brief Reports infomation about JIT-compiled code to the agent. + * + * The reported information is used to attribute samples obtained from any + * Intel(R) VTune(TM) Amplifier collector. This API needs to be called + * after JIT compilation and before the first entry into the JIT-compiled + * code. + * + * @param[in] event_type - type of the data sent to the agent + * @param[in] EventSpecificData - pointer to event-specific data + * + * @returns 1 on success, otherwise 0. + */ +int JITAPI iJIT_NotifyEvent(iJIT_JVM_EVENT event_type, void *EventSpecificData); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +/** @endcond */ + +/** @} jitapi group */ + +#endif /* __JITPROFILING_H__ */ diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.cpp new file mode 100644 index 000000000000..ef4c42bacf00 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.cpp @@ -0,0 +1,317 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" + +#include "nchw_pooling.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +void nchw_pooling_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + using namespace alg_kind; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(unsigned char *, MKLDNN_ARG_WORKSPACE); + + const memory_desc_wrapper ws_d(pd()->workspace_md()); + const data_type_t ws_dt = ws ? ws_d.data_type() : data_type::undef; + + const int MB = pd()->MB(); + const int C = pd()->C(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + const int SD = pd()->KSD(); + const int SH = pd()->KSH(); + const int SW = pd()->KSW(); + const int padF = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + auto alg = pd()->desc()->alg_kind; + + auto apply_offset = [=](int index, int offset) { + return (index > offset) ? index - offset : 0; + }; + + auto set_ws = [=](int mb, int c, int od, int oh, int ow, int value) { + if (ws) { + assert(ws_dt == data_type::u8 || ws_dt == data_type::s32); + size_t ws_offset + = (size_t)OW * OH * OD * C * mb + + (size_t)OW * OH * OD * c + + (size_t)OW * OH * od + + (size_t)OW * oh + + (size_t)ow; + if (ws_dt == data_type::u8) { + assert(0 <= value && value <= 255); + ws[ws_offset] = value; + } else + reinterpret_cast(ws)[ws_offset] = value; + } + }; + + auto ker_max = [=](data_t *d, int mb, int c, int od, int oh, int ow) { + for (int kd = 0; kd < KD; ++kd) { + for (int kh = 0; kh < KH; ++kh) { + for (int kw = 0; kw < KW; ++kw) { + const int id = od * SD - padF + kd; + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + if (id < 0 || id >= ID) continue; + if (ih < 0 || ih >= IH) continue; + if (iw < 0 || iw >= IW) continue; + + auto src_offset + = (size_t)IW * IH * ID * C * mb + + (size_t)IW * IH * ID * c + + (size_t)IW * IH * id + + (size_t)IW * ih + + (size_t)iw; + auto s = src[src_offset]; + if (s > d[0]) { + d[0] = s; + set_ws(mb, c, od, oh, ow, kd*KH*KW + kh*KW + kw); + } + } + } + } + }; + + auto ker_avg = [=](data_t *d, int mb, int c, int od, int oh, int ow) { + auto id_start = apply_offset(od*SD, padF); + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto id_end = nstl::min(od*SD - padF + KD, ID); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + auto num_summands = (alg == pooling_avg_include_padding) ? KD*KW*KH + : (id_end - id_start)*(ih_end - ih_start)*(iw_end - iw_start); + + for (int id = id_start; id < id_end; ++id) { + for (int ih = ih_start; ih < ih_end; ++ih) { + for (int iw = iw_start; iw < iw_end; ++iw) { + auto src_offset + = (size_t)IW * IH * ID * C * mb + + (size_t)IW * IH * ID * c + + (size_t)IW * IH * id + + (size_t)IW * ih + + (size_t)iw; + d[0] += src[src_offset]; + } + } + } + + d[0] = math::out_round((float)d[0] / num_summands); + }; + + + if (pd()->desc()->alg_kind == pooling_max) { + parallel_nd(MB, C, OD, OH, OW, + [&](int mb, int c, int od, int oh, int ow) { + size_t dst_offset + = (size_t)OW * OH * OD * C * mb + + (size_t)OW * OH * OD * c + + (size_t)OW * OH * od + + (size_t)OW * oh + + (size_t)ow; + data_t *d = &dst[dst_offset]; + d[0] = nstl::numeric_limits::lowest(); + set_ws(mb, c, od, oh, ow, 0); + ker_max(d, mb, c, od, oh, ow); + }); + } else { + parallel_nd(MB, C, OD, OH, OW, + [&](int mb, int c, int od, int oh, int ow) { + size_t dst_offset + = (size_t)OW * OH * OD * C * mb + + (size_t)OW * OH * OD * c + + (size_t)OW * OH * od + + (size_t)OW * oh + + (size_t)ow; + data_t *d = &dst[dst_offset]; + d[0] = 0; + ker_avg(d, mb, c, od, oh, ow); + }); + } +} + +template +void nchw_pooling_bwd_t::execute_backward( + const exec_ctx_t &ctx) const { + using namespace alg_kind; + + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto ws = CTX_IN_MEM(const unsigned char *, MKLDNN_ARG_WORKSPACE); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper ws_d(pd()->workspace_md()); + + const int MB = pd()->MB(); + const int C = pd()->C(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + const int SD = pd()->KSD(); + const int SH = pd()->KSH(); + const int SW = pd()->KSW(); + const int padF = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + const bool is_3d = pd()->desc()->diff_src_desc.ndims == 5; + + auto alg = pd()->desc()->alg_kind; + + auto apply_offset = [=](int index, int offset) { + return (index > offset) ? index - offset : 0; + }; + + auto ker_zero = [=](int mb, int c) { + size_t diff_src_offset = (size_t)mb*C*ID*IH*IW + (size_t)c*ID*IH*IW; + for (int id = 0; id < ID; ++id) { + for (int ih = 0; ih < IH; ++ih) { + for (int iw = 0; iw < IW; ++iw) { + diff_src[diff_src_offset++] = 0; + } + } + } + }; + + auto ker_max = [=](const data_t *d, int mb, int c, int od, int oh, int ow) { + auto b_c = ws_d.blocking_desc().inner_nblks == 0 + ? 1 : ws_d.blocking_desc().inner_blks[0]; + auto ws_offset = is_3d + ? ws_d.blk_off(mb, c / b_c, od, oh, ow) + c % b_c + : ws_d.blk_off(mb, c / b_c, oh, ow) + c % b_c; + + const int index = ws_d.data_type() == data_type::u8 + ? (int)ws[ws_offset] : ((const int *)ws)[ws_offset]; + const int kw = index % KW; + const int kh = (index / KW) % KH; + const int kd = (index / KW) / KH; + + const int id = od * SD - padF + kd; + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + // If padding area could fit the kernel, + // then input displacement would be out of bounds. + // No need to back propagate there as padding is + // virtual in pooling_max case. + if (id < 0 || id >= ID) + return; + if (ih < 0 || ih >= IH) + return; + if (iw < 0 || iw >= IW) + return; + + size_t diff_src_offset = + (size_t)mb*C*ID*IH*IW + (size_t)c*ID*IH*IW + (size_t)id*IH*IW + + (size_t)ih*IW + (size_t)iw; + diff_src[diff_src_offset] += d[0]; + }; + + auto ker_avg = [=](const data_t *d, int mb, int c, int od, int oh, int ow) { + auto id_start = apply_offset(od*SD, padF); + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto id_end = nstl::min(od*SD - padF + KD, ID); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + size_t num_summands = (alg == pooling_avg_include_padding) + ? (size_t)KW*KH*KD + : (size_t)(id_end - id_start)*(ih_end - ih_start) + *(iw_end - iw_start); + + for (int id = id_start; id < id_end; ++id) { + for (int ih = ih_start; ih < ih_end; ++ih) { + for (int iw = iw_start; iw < iw_end; ++iw) { + size_t diff_src_offset = (size_t)mb*C*ID*IH*IW + + (size_t)c*ID*IH*IW + (size_t)id*IH*IW + + (size_t)ih*IW + (size_t)iw; + diff_src[diff_src_offset] += d[0] / num_summands; + } + } + } + }; + + if (pd()->desc()->alg_kind == pooling_max) { + parallel_nd(MB, C, [&](int mb, int c) { + size_t diff_dst_offset = (size_t)mb*C*OD*OH*OW + + (size_t)c*OD*OH*OW; + ker_zero(mb, c); + for (int od = 0; od < OD; ++od) { + for (int oh = 0; oh < OH; ++oh) { + for (int ow = 0; ow < OW; ++ow) { + const data_t *d = &diff_dst[diff_dst_offset++]; + ker_max(d, mb, c, od, oh, ow); + } + } + } + }); + } else { + parallel_nd(MB, C, [&](int mb, int c) { + size_t diff_dst_offset = (size_t)mb*C*OD*OH*OW + + (size_t)c*OD*OH*OW; + ker_zero(mb, c); + for (int od = 0; od < OD; ++od) { + for (int oh = 0; oh < OH; ++oh) { + for (int ow = 0; ow < OW; ++ow) { + const data_t *d = &diff_dst[diff_dst_offset++]; + ker_avg(d, mb, c, od, oh, ow); + } + } + } + }); + } +} + +template struct nchw_pooling_fwd_t; +template struct nchw_pooling_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.hpp new file mode 100644 index 000000000000..bbdd04f6b924 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/nchw_pooling.hpp @@ -0,0 +1,147 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_NCHW_POOLING_HPP +#define CPU_NCHW_POOLING_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_pooling_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct nchw_pooling_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_fwd_pd_t { + using cpu_pooling_fwd_pd_t::cpu_pooling_fwd_pd_t; + + DECLARE_COMMON_PD_T("nchw_pooling:any", nchw_pooling_fwd_t); + + status_t init() { + const format_tag_t desired_fmt_tag = + ndims() == 4 ? format_tag::nchw : format_tag::ncdhw; + + bool ok = true + && set_default_params() == status::success + && is_fwd() + && utils::one_of(desc()->alg_kind, alg_kind::pooling_max, + alg_kind::pooling_avg_include_padding, + alg_kind::pooling_avg_exclude_padding) + && !has_zero_dim_memory() + && utils::everyone_is(data_type, src_md()->data_type, + dst_md()->data_type) + && attr()->has_default_values() + && memory_desc_matches_tag(*src_md(), desired_fmt_tag) + && memory_desc_matches_tag(*dst_md(), desired_fmt_tag); + if (!ok) return status::unimplemented; + + bool is_training = desc_.prop_kind == prop_kind::forward_training; + if (desc()->alg_kind == alg_kind::pooling_max && is_training) + init_default_ws(); + + return status::success; + } + }; + + nchw_pooling_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct nchw_pooling_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_bwd_pd_t { + using cpu_pooling_bwd_pd_t::cpu_pooling_bwd_pd_t; + + DECLARE_COMMON_PD_T("nchw:any", nchw_pooling_bwd_t); + + status_t init() { + const format_tag_t desired_fmt_tag = + ndims() == 4 ? format_tag::nchw : format_tag::ncdhw; + + bool ok = true + && set_default_params() == status::success + && !is_fwd() + && utils::one_of(desc()->alg_kind, alg_kind::pooling_max, + alg_kind::pooling_avg_include_padding, + alg_kind::pooling_avg_exclude_padding) + && !has_zero_dim_memory() + && utils::everyone_is(data_type, + diff_dst_md()->data_type, + diff_src_md()->data_type) + && attr()->has_default_values() + && memory_desc_matches_tag(*diff_dst_md(), desired_fmt_tag) + && memory_desc_matches_tag(*diff_src_md(), desired_fmt_tag); + if (!ok) return status::unimplemented; + + if (desc()->alg_kind == alg_kind::pooling_max) { + bool ws_ok = true + && hint_fwd_pd_ + && hint_fwd_pd_->workspace_md(); + if (!ws_ok) + return status::unimplemented; + + const auto &ws_blk = + hint_fwd_pd_->workspace_md()->format_desc.blocking; + ws_ok = ws_ok + && ws_blk.inner_nblks < 1 + && IMPLICATION(ws_blk.inner_nblks == 1, + ws_blk.inner_idxs[0] == 1); + if (!ws_ok) + return status::unimplemented; + + ws_md_ = *hint_fwd_pd_->workspace_md(); + } + + return status::success; + } + }; + + nchw_pooling_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.cpp new file mode 100644 index 000000000000..c0e93fefe4d6 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.cpp @@ -0,0 +1,382 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" + +#include "cpu_batch_normalization_utils.hpp" +#include "jit_generator.hpp" + +#include "ncsp_batch_normalization.hpp" + +// clang 6 and 7 generate incorrect code with OMP_SIMD in some particular cases +#if (defined __clang_major__) && (__clang_major__ >= 6) +#define SAFE_TO_USE_OMP_SIMD 0 +#else +#define SAFE_TO_USE_OMP_SIMD 1 +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace memory_tracking::names; + +void ncsp_batch_normalization_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + const bool calculate_stats = !pd()->stats_is_src(); + const bool save_stats = pd()->is_training(); + const bool is_training = pd()->is_training(); + const bool fuse_bn_relu = pd()->fuse_bn_relu(); + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto scaleshift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + + auto scratchpad = this->scratchpad(ctx); + auto *ws_reduce = scratchpad.get(key_bnorm_reduction); + + data_t *mean, *variance; + if (!calculate_stats) { + mean = const_cast( + CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN)); + variance = const_cast( + CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE)); + } else { + if (save_stats) { + mean = CTX_OUT_MEM(data_t *, MKLDNN_ARG_MEAN); + variance = CTX_OUT_MEM(data_t *, MKLDNN_ARG_VARIANCE); + } else { + mean = scratchpad.get(key_bnorm_tmp_mean); + variance = scratchpad.get(key_bnorm_tmp_var); + } + } + + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(uint8_t *, MKLDNN_ARG_WORKSPACE); + + const float eps = pd()->desc()->batch_norm_epsilon; + const bool use_scaleshift = pd()->use_scaleshift(); + const bool with_relu = pd()->with_relu_post_op(); + auto maybe_post_op + = [&](data_t res) { return (with_relu && res < 0) ? 0 : res; }; + const bool has_spatial = utils::one_of(pd()->ndims(), 4, 5); + dim_t SP = (has_spatial) ? pd()->H() * pd()->W() * pd()->D() : 1; + dim_t N = pd()->MB(); + dim_t C = pd()->C(); + + int nthr = mkldnn_get_max_threads(); + size_t l3_size_ = get_cache_size(3, true) * nthr / 2; + size_t data_size = N * C * SP * sizeof(data_t); + bool do_blocking = (data_size >= l3_size_ / 2 && l3_size_ > 0); + + parallel(0, [&](const int ithr, const int nthr) { + int C_ithr = 0, C_nthr = 0; + int N_ithr = 0, N_nthr = 0; + int S_ithr = 0, S_nthr = 0; + + dim_t C_blk_gl_s = 0, C_blk_gl_e = 0, C_blk_s = 0, C_blk_e = 0; + dim_t N_s = 0, N_e = 0; + dim_t S_s = 0, S_e = 0; + + dim_t C_blks_per_iter = 1; + int64_t iters = 1; + + if (do_blocking) { + size_t working_set_size = N * SP * sizeof(data_t); + bnorm_utils::cache_balance( + working_set_size, C, C_blks_per_iter, iters); + } else + C_blks_per_iter = C; + int64_t last_iter_blks = C - (iters - 1) * C_blks_per_iter; + bool spatial_thr_allowed + = bnorm_utils::thread_balance(do_blocking, true, ithr, nthr, N, + C_blks_per_iter, SP, C_ithr, C_nthr, C_blk_s, C_blk_e, + N_ithr, N_nthr, N_s, N_e, S_ithr, S_nthr, S_s, S_e); + balance211(C_blks_per_iter, nthr, ithr, C_blk_gl_s, C_blk_gl_e); + int SP_N_ithr = N_ithr * S_nthr + S_ithr; + int SP_N_nthr = N_nthr * S_nthr; + for (int64_t it = 0; it < iters; ++it) { + if (it == iters - 1 && iters > 1) { + // On the last iteration the access pattern to ws_reduce + // might change (due to re-balance on C). So sync the + // threads if they are not synced by the algorithm. + if (SP_N_nthr == 1 && mkldnn_thr_syncable()) + mkldnn_thr_barrier(); + + S_s = S_e = C_blk_s = C_blk_e = N_s = N_e = 0; + spatial_thr_allowed = bnorm_utils::thread_balance(do_blocking, + spatial_thr_allowed, ithr, nthr, N, last_iter_blks, SP, + C_ithr, C_nthr, C_blk_s, C_blk_e, N_ithr, N_nthr, N_s, + N_e, S_ithr, S_nthr, S_s, S_e); + balance211(last_iter_blks, nthr, ithr, C_blk_gl_s, C_blk_gl_e); + SP_N_ithr = N_ithr * S_nthr + S_ithr; + SP_N_nthr = N_nthr * S_nthr; + } + size_t C_off = it * C_blks_per_iter; + // On the last iteration the access pattern to ws_reduce + // might change (due to re-balance on C). Since sync is not always + // possible (in case of TBB) use different parts of ws for each + // iteration if threads are not synced by the algorithm. + size_t ws_iter_off = (mkldnn_thr_syncable() ? 0 : 1) * C_off; + + if (calculate_stats) { + data_t *mean_blk = mean + C_off; + data_t *variance_blk = variance + C_off; + for (dim_t c = C_blk_s; c < C_blk_e; c++) { + size_t off = (c + C_off) * SP; + data_t sum = 0; + for (dim_t n = N_s; n < N_e; ++n) + PRAGMA_OMP_SIMD(reduction(+ : sum)) + for (dim_t sp = S_s; sp < S_e; ++sp) { + sum += src[off + n * C * SP + sp]; + } + ws_reduce[ws_iter_off + SP_N_ithr * C_blks_per_iter + c] + = sum; + } + + if (SP_N_nthr > 1) mkldnn_thr_barrier(); + + for (dim_t c = C_blk_gl_s; c < C_blk_gl_e; c++) { + mean_blk[c] = 0.; + for (dim_t n = 0; n < SP_N_nthr; n++) + mean_blk[c] += ws_reduce[ws_iter_off + + n * C_blks_per_iter + c]; + mean_blk[c] /= (N * SP); + } + + if (SP_N_nthr > 1) mkldnn_thr_barrier(); + + for (dim_t c = C_blk_s; c < C_blk_e; c++) { + size_t off = c + C_off; + data_t sum = 0.; + for (dim_t n = N_s; n < N_e; ++n) + PRAGMA_OMP_SIMD(reduction(+ : sum)) + for (dim_t sp = S_s; sp < S_e; ++sp) { + data_t m = src[off * SP + n * C * SP + sp] + - mean[off]; + sum += m * m; + } + ws_reduce[ws_iter_off + SP_N_ithr * C_blks_per_iter + c] + = sum; + } + + if (SP_N_nthr > 1) mkldnn_thr_barrier(); + + for (dim_t c = C_blk_gl_s; c < C_blk_gl_e; c++) { + variance_blk[c] = 0.; + for (dim_t n = 0; n < SP_N_nthr; n++) + variance_blk[c] += ws_reduce[ws_iter_off + + n * C_blks_per_iter + c]; + variance_blk[c] /= (N * SP); + } + + if (SP_N_nthr > 1) mkldnn_thr_barrier(); + } + + for (dim_t c = C_blk_s; c < C_blk_e; c++) { + size_t off = c + C_off; + data_t sqrt_variance + = static_cast(sqrtf(variance[off] + eps)); + data_t sm = (use_scaleshift ? scaleshift[off] : 1.0f) / sqrt_variance; + data_t sv = use_scaleshift ? scaleshift[C + off] : 0; + for (dim_t n = N_s; n < N_e; ++n) +#if SAFE_TO_USE_OMP_SIMD + PRAGMA_OMP_SIMD() +#endif + for (dim_t sp = S_s; sp < S_e; ++sp) { + size_t d_off = off * SP + n * C * SP + sp; + data_t bn_res + = sm * (src[d_off] - mean[off]) + sv; + if (fuse_bn_relu) { + if (bn_res <= 0) { + bn_res = 0; + if (is_training) + ws[d_off] = 0; + } else { + if (is_training) + ws[d_off] = 1; + } + } + dst[d_off] = maybe_post_op(bn_res); + } + } + } + }); +} + +void ncsp_batch_normalization_bwd_t::execute_backward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto mean = CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN); + auto variance = CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto scaleshift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + auto ws = CTX_IN_MEM(const uint8_t *, MKLDNN_ARG_WORKSPACE); + + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + auto diff_scaleshift = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SCALE_SHIFT); + + auto scratchpad = this->scratchpad(ctx); + auto *ws_reduce = scratchpad.get(key_bnorm_reduction); + + if (diff_scaleshift == nullptr) + diff_scaleshift = scratchpad.get(key_bnorm_tmp_diff_ss); + + const bool has_spatial = utils::one_of(pd()->ndims(), 4, 5); + dim_t SP = (has_spatial) ? pd()->H() * pd()->W() * pd()->D() : 1; + dim_t C = pd()->C(), N = pd()->MB(); + const bool use_scaleshift = pd()->use_scaleshift(); + const float eps = pd()->desc()->batch_norm_epsilon; + const bool calculate_diff_stats = !pd()->use_global_stats(); + const bool fuse_bn_relu = pd()->fuse_bn_relu(); + + int nthr = mkldnn_get_max_threads(); + size_t l3_size_ = get_cache_size(3, true) * nthr / 2; + size_t data_size = N * C * SP * sizeof(data_t); + bool do_blocking = (data_size >= l3_size_ / 2 && l3_size_ > 0); + + parallel(0, [&](const int ithr, const int nthr) { + int C_ithr = 0, C_nthr = 0; + int N_ithr = 0, N_nthr = 0; + int S_ithr = 0, S_nthr = 0; + + dim_t C_blk_gl_s = 0, C_blk_gl_e = 0, C_blk_s = 0, C_blk_e = 0; + dim_t N_s = 0, N_e = 0; + dim_t S_s = 0, S_e = 0; + + dim_t C_blks_per_iter = 1; + int64_t iters = 1; + + if (do_blocking) { + size_t working_set_size = 2 * N * SP * sizeof(data_t); + bnorm_utils::cache_balance( + working_set_size, C, C_blks_per_iter, iters); + } else + C_blks_per_iter = C; + int64_t last_iter_blks = C - (iters - 1) * C_blks_per_iter; + bool spatial_thr_allowed + = bnorm_utils::thread_balance(do_blocking, true, ithr, nthr, N, + C_blks_per_iter, SP, C_ithr, C_nthr, C_blk_s, C_blk_e, + N_ithr, N_nthr, N_s, N_e, S_ithr, S_nthr, S_s, S_e); + balance211(C_blks_per_iter, nthr, ithr, C_blk_gl_s, C_blk_gl_e); + int SP_N_ithr = N_ithr * S_nthr + S_ithr; + int SP_N_nthr = N_nthr * S_nthr; + + for (int64_t it = 0; it < iters; ++it) { + if (it == iters - 1 && iters > 1) { + // On the last iteration the access pattern to ws_reduce + // might change (due to re-balance on C). So sync the + // threads if they are not synced by the algorithm. + if (SP_N_nthr == 1 && mkldnn_thr_syncable()) + mkldnn_thr_barrier(); + + C_blk_s = C_blk_e = N_s = N_e = 0; + spatial_thr_allowed = bnorm_utils::thread_balance(do_blocking, + spatial_thr_allowed, ithr, nthr, N, last_iter_blks, SP, + C_ithr, C_nthr, C_blk_s, C_blk_e, N_ithr, N_nthr, N_s, + N_e, S_ithr, S_nthr, S_s, S_e); + balance211(last_iter_blks, nthr, ithr, C_blk_gl_s, C_blk_gl_e); + SP_N_ithr = N_ithr * S_nthr + S_ithr; + SP_N_nthr = N_nthr * S_nthr; + } + size_t C_off = it * C_blks_per_iter; + // On the last iteration the access pattern to ws_reduce + // might change (due to re-balance on C). Since sync is not always + // possible (in case of TBB) use different parts of ws for each + // iteration if threads are not synced by the algorithm. + size_t ws_iter_off = (mkldnn_thr_syncable() ? 0 : 1) * 2 * C_off; + + data_t *diff_gamma_blk = diff_scaleshift + C_off; + data_t *diff_beta_blk = diff_scaleshift + C + C_off; + for (dim_t c = C_blk_s; c < C_blk_e; c++) { + size_t off = c + C_off; + data_t diff_gamma = 0.0, diff_beta = 0.0; + data_t v_mean = mean[off]; + for (dim_t n = N_s; n < N_e; ++n) + PRAGMA_OMP_SIMD(reduction(+ : diff_gamma, diff_beta)) + for (dim_t sp = S_s; sp < S_e; ++sp) { + const size_t d_off = off * SP + n * C * SP + sp; + data_t dd; + if (fuse_bn_relu) + dd = (!ws[d_off]) ? 0 : diff_dst[d_off]; + else + dd = diff_dst[d_off]; + diff_gamma += (src[d_off] - v_mean) * dd; + diff_beta += dd; + } + ws_reduce[ws_iter_off + SP_N_ithr * C_blks_per_iter + c] + = diff_gamma; + ws_reduce[ws_iter_off + SP_N_nthr * C_blks_per_iter + + SP_N_ithr * C_blks_per_iter + c] = diff_beta; + } + + if (SP_N_nthr > 1) mkldnn_thr_barrier(); + + for (dim_t c = C_blk_gl_s; c < C_blk_gl_e; c++) { + data_t sqrt_variance = static_cast( + 1.0f / sqrtf(variance[c + C_off] + eps)); + diff_gamma_blk[c] = 0.; + diff_beta_blk[c] = 0.; + for (dim_t n = 0; n < SP_N_nthr; n++) { + diff_gamma_blk[c] += ws_reduce[ws_iter_off + + n * C_blks_per_iter + c]; + diff_beta_blk[c] += ws_reduce[ws_iter_off + + SP_N_nthr * C_blks_per_iter + n * C_blks_per_iter + + c]; + } + diff_gamma_blk[c] *= sqrt_variance; + } + + if (SP_N_nthr > 1) mkldnn_thr_barrier(); + + for (dim_t c = C_blk_s; c < C_blk_e; c++) { + size_t off = c + C_off; + data_t gamma = use_scaleshift ? scaleshift[off] : 1; + data_t sqrt_variance + = static_cast(1.0f / sqrtf(variance[off] + eps)); + data_t v_mean = mean[off]; + for (dim_t n = N_s; n < N_e; ++n) +#if SAFE_TO_USE_OMP_SIMD + PRAGMA_OMP_SIMD() +#endif + for (dim_t sp = S_s; sp < S_e; ++sp) { + const size_t d_off = off * SP + n * C * SP + sp; + + data_t v_diff_src; + if (fuse_bn_relu) + v_diff_src = (!ws[d_off]) ? 0 : diff_dst[d_off]; + else + v_diff_src = diff_dst[d_off]; + if (calculate_diff_stats) { + v_diff_src -= diff_beta_blk[c] / (SP * N) + + (src[d_off] - v_mean) * diff_gamma_blk[c] + * sqrt_variance / (SP * N); + } + v_diff_src *= gamma * sqrt_variance; + diff_src[d_off] = v_diff_src; + } + } + } + }); +} +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.hpp new file mode 100644 index 000000000000..97ca3b003f69 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ncsp_batch_normalization.hpp @@ -0,0 +1,160 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_NCSP_BATCH_NORMALIZATION_HPP +#define CPU_NCSP_BATCH_NORMALIZATION_HPP + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_batch_normalization_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct ncsp_batch_normalization_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_batch_normalization_fwd_pd_t { + using cpu_batch_normalization_fwd_pd_t::cpu_batch_normalization_fwd_pd_t; + + DECLARE_COMMON_PD_T("ncsp_bnorm:any", ncsp_batch_normalization_fwd_t); + + status_t init() { + using namespace data_type; + using namespace prop_kind; + using namespace format_tag; + + bool ok = true + && is_fwd() + && !has_zero_dim_memory() + && src_md()->data_type == f32 + && IMPLICATION(use_scaleshift(), weights_md()->data_type == f32) + && memory_desc_matches_one_of_tag(*src_md(), ncdhw, nchw, nc) + && (attr()->has_default_values() || this->with_relu_post_op()); + if (!ok) return status::unimplemented; + + if (is_training() && fuse_bn_relu()) init_default_ws(8); + + init_scratchpad(); + + return status::success; + } + + private: + void init_scratchpad() { + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + if (!stats_is_src()) { + scratchpad.book(key_bnorm_reduction, + sizeof(data_t) * C() * mkldnn_get_max_threads()); + + if (!is_training()) { + scratchpad.book(key_bnorm_tmp_mean, sizeof(data_t) * C()); + scratchpad.book(key_bnorm_tmp_var, sizeof(data_t) * C()); + } + } + } + }; + + typedef typename prec_traits::type data_t; + + ncsp_batch_normalization_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + ~ncsp_batch_normalization_fwd_t() {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct ncsp_batch_normalization_bwd_t : public cpu_primitive_t { + struct pd_t : public cpu_batch_normalization_bwd_pd_t { + using cpu_batch_normalization_bwd_pd_t::cpu_batch_normalization_bwd_pd_t; + + DECLARE_COMMON_PD_T("ncsp_bnorm:any", ncsp_batch_normalization_bwd_t); + + status_t init() { + using namespace data_type; + using namespace format_tag; + + bool ok = true + && is_bwd() + && !has_zero_dim_memory() + && utils::everyone_is(f32, src_md()->data_type, + diff_src_md()->data_type) + && IMPLICATION(use_scaleshift(), + utils::everyone_is(f32, + weights_md()->data_type, + diff_weights_md()->data_type)) + && memory_desc_matches_one_of_tag(*src_md(), ncdhw, nchw, nc) + && memory_desc_matches_one_of_tag(*diff_src_md(), ncdhw, nchw, nc) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + if (fuse_bn_relu()) { + init_default_ws(8); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + init_scratchpad(); + + return status::success; + } + + private: + void init_scratchpad() { + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + scratchpad.book(key_bnorm_reduction, + sizeof(data_t) * 2 * C() * mkldnn_get_max_threads()); + if (!(use_scaleshift() && desc()->prop_kind == prop_kind::backward)) + scratchpad.book(key_bnorm_tmp_diff_ss, + sizeof(data_t) * 2 * C()); + } + }; + + typedef typename prec_traits::type data_t; + + ncsp_batch_normalization_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + ~ncsp_batch_normalization_bwd_t() {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.cpp new file mode 100644 index 000000000000..38cfb28dce30 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.cpp @@ -0,0 +1,392 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" + +#include "nhwc_pooling.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +#define MEM_D(name) name##_d + +#define DECLARE_READ_STRIDES(name) \ + const size_t name##_n_stride = MEM_D(name).blocking_desc().strides[0]; \ + const size_t name##_d_stride = (!is_3d) \ + ? 0 \ + : MEM_D(name).blocking_desc().strides[2]; \ + const size_t name##_h_stride = (!is_3d) \ + ? MEM_D(name).blocking_desc().strides[2] \ + : MEM_D(name).blocking_desc().strides[3]; \ + const size_t name##_w_stride = (!is_3d) \ + ? MEM_D(name).blocking_desc().strides[3] \ + : MEM_D(name).blocking_desc().strides[4]; + +namespace nhwc_pooling { + size_t strided_offset(const int _n, const size_t _sn, + const int _d, const size_t _sd, + const int _h, const size_t _sh, + const int _w, const size_t _sw) + { + return _n * _sn + + _d * _sd + + _h * _sh + + _w * _sw; + } +} + +template +void nhwc_pooling_fwd_t::array_div_by_const(const int n, + const data_t *src, const size_t num, data_t *dst) const +{ + for (int i = 0; i < n; ++i) + { + float ftmp = (float)src[i]; + ftmp = ftmp / num; + dst[i] = math::out_round(ftmp); + } +} + +template +void nhwc_pooling_fwd_t::array_add(const int n, const data_t *src, + data_t *dst) const +{ + for (int i = 0; i < n; ++i) + { + dst[i] += src[i]; + } +} + +template +void nhwc_pooling_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + using namespace alg_kind; + using namespace prop_kind; + using namespace nhwc_pooling; + + auto alg = pd()->desc()->alg_kind; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(unsigned char *, MKLDNN_ARG_WORKSPACE); + + const memory_desc_wrapper MEM_D(src)(pd()->src_md()); + const memory_desc_wrapper MEM_D(dst)(pd()->dst_md()); + const memory_desc_wrapper MEM_D(ws)(pd()->workspace_md()); + + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + const int SD = pd()->KSD(); + const int SH = pd()->KSH(); + const int SW = pd()->KSW(); + const int padF = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + const int MB = pd()->MB(); + const int OC = pd()->C(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + + const bool is_3d = pd()->desc()->src_desc.ndims == 5; + const data_type_t ws_dt = ws ? ws_d.data_type() : data_type::undef; + + DECLARE_READ_STRIDES(src); + DECLARE_READ_STRIDES(dst); + + auto apply_offset = [=](int index, int offset) { + return (index > offset) ? index - offset : 0; + }; + + parallel_nd(MB, OD, OH, OW, + [&](int mb, int od, int oh, int ow) { + size_t dst_offset_init = strided_offset(mb, dst_n_stride, + od, dst_d_stride, + oh, dst_h_stride, + ow, dst_w_stride); + if (alg == pooling_max) { + size_t ws_offset_init = 0; + if (ws) + { + DECLARE_READ_STRIDES(ws); + ws_offset_init = strided_offset(mb, ws_n_stride, + od, ws_d_stride, + oh, ws_h_stride, + ow, ws_w_stride); + } + // Note: GCC 4.8.5 won't vectorize below + // simple loops unless they are singled out + // into separate helper routines: + // array_nhwc_initialize, array_nhwc_max + if (!ws) + array_nhwc_initialize(OC, dst + dst_offset_init, + ws, ws_offset_init, ws_dt); + else + array_nhwc_initialize(OC, dst + dst_offset_init, + ws, ws_offset_init, ws_dt); + + + for (int kd = 0; kd < KD; ++kd) + for (int kh = 0; kh < KH; ++kh) + for (int kw = 0; kw < KW; ++kw) { + const int id = od * SD - padF + kd; + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + if (id < 0 || id >= ID) + continue; + if (ih < 0 || ih >= IH) + continue; + if (iw < 0 || iw >= IW) + continue; + + size_t src_offset_init = strided_offset(mb, src_n_stride, + id, src_d_stride, + ih, src_h_stride, + iw, src_w_stride); + + if (!ws) + array_nhwc_max(OC, + dst + dst_offset_init, + src + src_offset_init, + ws, ws_offset_init, + ws_dt, + kd * KH * KW + kh * KW + kw + ); + else + array_nhwc_max(OC, + dst + dst_offset_init, + src + src_offset_init, + ws, ws_offset_init, + ws_dt, + kd * KH * KW + kh * KW + kw + ); + } + } else { + // pooling_avg + auto d = dst + dst_offset_init; + + utils::array_set(d, 0, OC); + + auto id_start = apply_offset(od * SD, padF); + auto ih_start = apply_offset(oh * SH, padT); + auto iw_start = apply_offset(ow * SW, padL); + auto id_end = nstl::min(od * SD - padF + KD, ID); + auto ih_end = nstl::min(oh * SH - padT + KH, IH); + auto iw_end = nstl::min(ow * SW - padL + KW, IW); + + // it is cheaper to actually count this in a loop + // as the typical kernel is small + size_t num_summands = 0; + + for (int id = id_start; id < id_end; ++id) + for (int ih = ih_start; ih < ih_end; ++ih) + for (int iw = iw_start; iw < iw_end; ++iw) { + size_t src_offset_init = strided_offset(mb, src_n_stride, + id, src_d_stride, + ih, src_h_stride, + iw, src_w_stride); + auto s = src + src_offset_init; + + // need to move the loop to separate function + // for GCC 4.8.5 to vectorize + array_add(OC, s, d); + + num_summands++; + } + + num_summands = (alg == pooling_avg_include_padding) ? + KW * KH * KD : num_summands; + + // need to move the loop to separate function + // for GCC 4.8.5 to vectorize + array_div_by_const(OC, d, num_summands, d); + } + }); +} + +template +void nhwc_pooling_bwd_t::execute_backward( + const exec_ctx_t &ctx) const { + using namespace alg_kind; + using namespace nhwc_pooling; + + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto ws = CTX_IN_MEM(const unsigned char *, MKLDNN_ARG_WORKSPACE); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper MEM_D(diff_src)(pd()->diff_src_md()); + const memory_desc_wrapper MEM_D(diff_dst)(pd()->diff_dst_md()); + const memory_desc_wrapper MEM_D(ws)(pd()->workspace_md()); + + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + const int SD = pd()->KSD(); + const int SH = pd()->KSH(); + const int SW = pd()->KSW(); + const int OC = pd()->C(); + const int padF = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + + const bool is_3d = pd()->desc()->diff_src_desc.ndims == 5; + auto alg = pd()->desc()->alg_kind; + + DECLARE_READ_STRIDES(diff_src); + DECLARE_READ_STRIDES(diff_dst); + + auto apply_offset = [=](int index, int offset) { + return (index > offset) ? index - offset : 0; + }; + + const int MB = pd()->MB(); + + parallel_nd(MB, ID, IH, IW, + [&](int mb, int id, int ih, int iw) { + size_t src_offset_init = strided_offset(mb, diff_src_n_stride, + id, diff_src_d_stride, + ih, diff_src_h_stride, + iw, diff_src_w_stride); + + // check if kernel windows are disjoint, in this case there's no + // update needed and we just write there once, no initialization + // required. + if (!(KD == SD && KH == SH && KW == SW)) + for (int oc = 0; oc < OC; ++oc) + diff_src[src_offset_init + oc] = data_type_t(0); + + // Find out which output cells may correspond to current + // input position. Current input postition divided by + // stride, with integer divide rounding down, is the + // right-most output. + // Left-most output may be computed if we decrement input + // by (kernel_size - 1) and then do the same division by + // stride. + int od_left = nstl::max((id + padF - KD + 1) / SD, 0); + int oh_left = nstl::max((ih + padT - KH + 1) / SH, 0); + int ow_left = nstl::max((iw + padL - KW + 1) / SW, 0); + // Notice +1 here to preserve the C loop "less than" + // condition for continuing the for loop. + int od_right = nstl::min((id + padF) / SD + 1 , OD); + int oh_right = nstl::min((ih + padT) / SH + 1 , OH); + int ow_right = nstl::min((iw + padL) / SW + 1 , OW); + + for (int od = od_left; od < od_right; ++od) + for (int oh = oh_left; oh < oh_right; ++oh) + for (int ow = ow_left; ow < ow_right; ++ow) { + const int kd = id - od*SD + padF; + const int kh = ih - oh*SH + padT; + const int kw = iw - ow*SW + padL; + + if (kd < 0 || kd >= KD) + continue; + if (kh < 0 || kh >= KH) + continue; + if (kw < 0 || kw >= KW) + continue; + + size_t dst_offset_init = strided_offset(mb, diff_dst_n_stride, + od, diff_dst_d_stride, + oh, diff_dst_h_stride, + ow, diff_dst_w_stride); + + if (alg == pooling_max) { + DECLARE_READ_STRIDES(ws); + size_t ws_offset_init = strided_offset(mb, ws_n_stride, + od, ws_d_stride, + oh, ws_h_stride, + ow, ws_w_stride); + const int index = kd * KH * KW + kh * KW + kw; + + PRAGMA_OMP_SIMD() + for (int oc = 0; oc < OC; ++oc) { + const int index_from_ws = + (MEM_D(ws).data_type() == data_type::u8) + ? (int)ws[ws_offset_init + oc] + : ((int *)ws)[ws_offset_init + oc]; + + const data_t d = diff_dst[dst_offset_init + oc]; + + // Check if kernel windows are disjoint, in this case + // there's no update needed and we just write there once + // otherwise we add value to the contents. + if (!(KD == SD && KH == SH && KW == SW)) + diff_src[src_offset_init + oc] += + (index_from_ws == index) + ? d + : data_type_t(0); + else + diff_src[src_offset_init + oc] = + (index_from_ws == index) + ? d + : data_type_t(0); + } + } else { + // pooling_avg + auto id_start = apply_offset(od*SD, padF); + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto id_end = nstl::min(od*SD - padF + KD, ID); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + auto num_summands = (alg == pooling_avg_include_padding) + ? KW*KH*KD + : (ih_end - ih_start)*(iw_end - iw_start)*(id_end - id_start); + + PRAGMA_OMP_SIMD() + for (int oc = 0; oc < OC; ++oc) { + const data_t d = diff_dst[dst_offset_init + oc]; + // Check if kernel windows are disjoint, in this case + // there's no update needed and we just write there once + // otherwise we add value to the contents. + if (!(KD == SD && KH == SH && KW == SW)) + diff_src[src_offset_init + oc] += d / num_summands; + else + diff_src[src_offset_init + oc] = d / num_summands; + } + } + } + }); +} + +template struct nhwc_pooling_fwd_t; +template struct nhwc_pooling_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.hpp new file mode 100644 index 000000000000..7e33b6869fba --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/nhwc_pooling.hpp @@ -0,0 +1,210 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_NHWC_POOLING_HPP +#define CPU_NHWC_POOLING_HPP + +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_pooling_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace nhwc_pooling { +size_t strided_offset(const int _n, const size_t _sn, const int _d, + const size_t _sd, const int _h, const size_t _sh, const int _w, + const size_t _sw); +} + +template +struct nhwc_pooling_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_fwd_pd_t { + using cpu_pooling_fwd_pd_t::cpu_pooling_fwd_pd_t; + + DECLARE_COMMON_PD_T("nhwc_pooling:any", nhwc_pooling_fwd_t); + + status_t init() { + const format_tag_t desired_fmt_tag = + ndims() == 4 ? format_tag::nhwc : format_tag::ndhwc; + + bool ok = true + && set_default_params() == status::success + && is_fwd() + && utils::one_of(desc()->alg_kind, alg_kind::pooling_max, + alg_kind::pooling_avg_include_padding, + alg_kind::pooling_avg_exclude_padding) + && utils::everyone_is(data_type, + src_md()->data_type, + dst_md()->data_type) + && attr()->has_default_values() + && memory_desc_matches_tag(*src_md(), desired_fmt_tag) + && memory_desc_matches_tag(*dst_md(), desired_fmt_tag); + if (!ok) return status::unimplemented; + + bool is_training = desc_.prop_kind == prop_kind::forward_training; + if (desc()->alg_kind == alg_kind::pooling_max && is_training) + init_default_ws(); + + return status::success; + } + }; + + nhwc_pooling_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + void array_div_by_const(const int n, const data_t *src, const size_t num, + data_t *dst) const; + void array_add(const int n, const data_t *src, data_t *dst) const; + + template + void array_nhwc_max(const int n, data_t *dst, const data_t *src, + unsigned char *ws, const size_t ws_offset, const data_type_t ws_dt, + const int index) const { + assert(!((use_workspace == false) ^ (!ws))); // ensure ws pointer exists + PRAGMA_OMP_SIMD() + for (int oc = 0; oc < n; ++oc) { + auto s = src[oc]; + data_t mv = dst[oc]; + + // update index of maximum +#if defined __INTEL_COMPILER + if ((use_workspace) && (s > mv)) { + assert(ws_dt == data_type::u8 || ws_dt == data_type::s32); + if (ws_dt == data_type::u8) { + assert(0 <= index && index <= 255); + ws[ws_offset + oc] = index; + } else + reinterpret_cast(ws)[ws_offset + oc] = index; + } +#else + // Need to add explicit predicates for GCC to vectorize this. + // And although the resulting code is ugly, it is still 4 times + // faster than scalar + if (use_workspace) { + assert(ws_dt == data_type::u8 || ws_dt == data_type::s32); + + if (ws_dt == data_type::u8) { + assert(0 <= index && index <= 255); + unsigned char predicate = (s > mv) ? 0xff : 0; + unsigned char current_value = ws[ws_offset + oc]; + current_value = (predicate & (unsigned char)index) + | ((~predicate) & current_value); + ws[ws_offset + oc] = current_value; + } else { + auto wint = reinterpret_cast(ws); + unsigned int predicate = (s > mv) ? 0xffffffff : 0; + unsigned int current_value = wint[ws_offset + oc]; + current_value = (predicate & (unsigned int)index) + | ((~predicate) & current_value); + wint[ws_offset + oc] = current_value; + } + } +#endif + // update maximum + dst[oc] = nstl::max(s, mv); + } + } + + template + void array_nhwc_initialize(const int n, data_t *dst, unsigned char *ws, + const size_t ws_offset, const data_type_t ws_dt) const { + assert(!((use_workspace == false) ^ (!ws))); // ensure ws pointer exists + for (int oc = 0; oc < n; ++oc) { + if (use_workspace) { + assert(ws_dt == data_type::u8 || ws_dt == data_type::s32); + if (ws_dt == data_type::u8) { + ws[ws_offset + oc] = 0; + } else + reinterpret_cast(ws)[ws_offset + oc] = 0; + } + dst[oc] = nstl::numeric_limits::lowest(); + } + } + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct nhwc_pooling_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_bwd_pd_t { + using cpu_pooling_bwd_pd_t::cpu_pooling_bwd_pd_t; + + DECLARE_COMMON_PD_T("nhwc:any", nhwc_pooling_bwd_t); + + status_t init() { + const format_tag_t desired_fmt_tag = + ndims() == 4 ? format_tag::nchw : format_tag::ncdhw; + + bool ok = true + && set_default_params() == status::success + && !is_fwd() + && utils::one_of(desc()->alg_kind, alg_kind::pooling_max, + alg_kind::pooling_avg_include_padding, + alg_kind::pooling_avg_exclude_padding) + && utils::everyone_is(data_type, + diff_dst_md()->data_type, + diff_src_md()->data_type) + && attr()->has_default_values() + && memory_desc_matches_tag(*diff_dst_md(), desired_fmt_tag) + && memory_desc_matches_tag(*diff_src_md(), desired_fmt_tag); + if (!ok) return status::unimplemented; + + if (desc()->alg_kind == alg_kind::pooling_max) { + init_default_ws(); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + return status::success; + } + }; + + nhwc_pooling_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +}// namespace cpu +}// namespace impl +}// namespace mkldnn + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.cpp new file mode 100644 index 000000000000..e20333e66fe0 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.cpp @@ -0,0 +1,288 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" + +#include "cpu_batch_normalization_utils.hpp" +#include "jit_generator.hpp" + +#include "nspc_batch_normalization.hpp" + +// clang 6 and 7 generate incorrect code with OMP_SIMD in some particular cases +#if (defined __clang_major__) && (__clang_major__ >= 6) +#define SAFE_TO_USE_OMP_SIMD 0 +#else +#define SAFE_TO_USE_OMP_SIMD 1 +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace memory_tracking::names; + +void nspc_batch_normalization_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + const bool save_stats = pd()->is_training(); + const bool is_training = pd()->is_training(); + const bool fuse_bn_relu = pd()->fuse_bn_relu(); + const bool calculate_stats = !pd()->stats_is_src(); + const bool with_relu = pd()->with_relu_post_op(); + + auto scratchpad = this->scratchpad(ctx); + auto tmp_mean = scratchpad.get(key_bnorm_tmp_mean); + auto tmp_var = scratchpad.get(key_bnorm_tmp_var); + auto *ws_reduce = scratchpad.get(key_bnorm_reduction); + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto scaleshift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + + data_t *mean, *variance; + if (!calculate_stats) { + mean = const_cast( + CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN)); + variance = const_cast( + CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE)); + } else { + if (save_stats) { + mean = CTX_OUT_MEM(data_t *, MKLDNN_ARG_MEAN); + variance = CTX_OUT_MEM(data_t *, MKLDNN_ARG_VARIANCE); + } else { + mean = tmp_mean; + variance = tmp_var; + } + } + + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(uint8_t *, MKLDNN_ARG_WORKSPACE); + + const dim_t N = pd()->MB(); + const dim_t C = pd()->C(); + const dim_t SP = pd()->H() * pd()->W() * pd()->D(); + + const float eps = pd()->desc()->batch_norm_epsilon; + const bool use_scaleshift = pd()->use_scaleshift(); + auto maybe_post_op + = [&](data_t res) { return (with_relu && res < 0) ? 0 : res; }; + + assert(mkldnn_thr_syncable()); + parallel(0, [&](const int ithr, const int nthr) { + dim_t N_s = 0, N_e = 0, C_s = 0, C_e = 0; + balance211(N, nthr, ithr, N_s, N_e); + balance211(C, nthr, ithr, C_s, C_e); + data_t *mean_loc = tmp_mean + nstl::max(C, (dim_t)16) * ithr; + data_t *variance_loc = tmp_var + nstl::max(C, (dim_t)16) * ithr; + + if (calculate_stats) { + for (dim_t c = 0; c < C; c++) + ws_reduce[C * ithr + c] = 0.; + + for (dim_t n = N_s; n < N_e; n++) + for (dim_t sp = 0; sp < SP; sp++) + PRAGMA_OMP_SIMD() + for (dim_t c = 0; c < C; c++) + ws_reduce[C * ithr + c] += src[(size_t)n * SP * C + + sp * C + c]; + + mkldnn_thr_barrier(); + + for (dim_t c = C_s; c < C_e; c++) { + mean[c] = 0; + for (dim_t n = 0; n < nthr; n++) + mean[c] += ws_reduce[C * n + c]; + mean[c] /= SP * N; + } + + mkldnn_thr_barrier(); + + for (dim_t c = 0; c < C; c++) { + mean_loc[c] = mean[c]; + ws_reduce[C * ithr + c] = 0.; + } + + for (dim_t n = N_s; n < N_e; n++) + for (dim_t sp = 0; sp < SP; sp++) + PRAGMA_OMP_SIMD() + for (dim_t c = 0; c < C; c++) { + data_t m = src[(size_t)n * SP * C + sp * C + c] + - mean_loc[c]; + ws_reduce[C * ithr + c] += m * m; + } + + mkldnn_thr_barrier(); + + for (dim_t c = C_s; c < C_e; c++) { + variance[c] = 0; + for (dim_t n = 0; n < nthr; n++) + variance[c] += ws_reduce[C * n + c]; + variance[c] /= SP * N; + } + + mkldnn_thr_barrier(); + + for (dim_t c = 0; c < C; c++) + variance_loc[c] = variance[c]; + } else { + variance_loc = variance; + mean_loc = mean; + } + + for (dim_t n = N_s; n < N_e; n++) { + for (dim_t sp = 0; sp < SP; sp++) { +#if SAFE_TO_USE_OMP_SIMD + PRAGMA_OMP_SIMD() +#endif + for (dim_t c = 0; c < C; c++) { + data_t sqrt_variance = static_cast( + sqrtf(variance_loc[c] + eps)); + data_t sm = (use_scaleshift ? scaleshift[c] : 1.0f) / sqrt_variance; + data_t sv = use_scaleshift ? scaleshift[C + c] : 0; + size_t d_off = (size_t)n * SP * C + sp * C + c; + data_t bn_res = sm * (src[d_off] - mean_loc[c]) + sv; + if (fuse_bn_relu) { + if (bn_res <= 0) { + bn_res = 0; + if (is_training) + ws[d_off] = 0; + } else { + if (is_training) + ws[d_off] = 1; + } + } + dst[d_off] = maybe_post_op(bn_res); + } + } + } + }); +} + +void nspc_batch_normalization_bwd_t::execute_backward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto mean = CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN); + auto variance = CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto scaleshift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + auto ws = CTX_IN_MEM(const uint8_t *, MKLDNN_ARG_WORKSPACE); + + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + auto diff_scaleshift = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SCALE_SHIFT); + + auto scratchpad = this->scratchpad(ctx); + auto tmp_diff_ss = scratchpad.get(key_bnorm_tmp_diff_ss); + + if (diff_scaleshift == nullptr) + diff_scaleshift = tmp_diff_ss; + + const dim_t N = pd()->MB(); + const dim_t C = pd()->C(); + const dim_t SP = pd()->D() * pd()->H() * pd()->W(); + data_t *diff_gamma = diff_scaleshift, *diff_beta = diff_scaleshift + C; + auto *ws_reduce = scratchpad.get(key_bnorm_reduction); + + const float eps = pd()->desc()->batch_norm_epsilon; + const bool use_scaleshift = pd()->use_scaleshift(); + const bool calculate_diff_stats = !pd()->use_global_stats(); + const bool fuse_bn_relu = pd()->fuse_bn_relu(); + + assert(mkldnn_thr_syncable()); + parallel(0, [&](const int ithr, const int nthr) { + dim_t N_s = 0, N_e = 0, C_s = 0, C_e = 0; + balance211(N, nthr, ithr, N_s, N_e); + balance211(C, nthr, ithr, C_s, C_e); + + data_t *diff_gamma_loc = tmp_diff_ss + 2 * C + C * ithr; + data_t *diff_beta_loc = tmp_diff_ss + 2 * C + C * (nthr + ithr); + + for (dim_t c = 0; c < C; c++) { + ws_reduce[C * ithr + c] = 0.; + ws_reduce[C * nthr + C * ithr + c] = 0.; + } + + for (dim_t n = N_s; n < N_e; n++) + for (dim_t sp = 0; sp < SP; sp++) +#if SAFE_TO_USE_OMP_SIMD + PRAGMA_OMP_SIMD() +#endif + for (dim_t c = 0; c < C; c++) { + const size_t d_off = (size_t)n * SP * C + sp * C + c; + data_t dd; + if (fuse_bn_relu) + dd = (!ws[d_off]) ? 0 : diff_dst[d_off]; + else + dd = diff_dst[d_off]; + ws_reduce[C * ithr + c] += (src[d_off] - mean[c]) * dd; + ws_reduce[C * nthr + C * ithr + c] += dd; + } + + mkldnn_thr_barrier(); + + for (dim_t c = C_s; c < C_e; c++) { + data_t sqrt_variance + = static_cast(1.0f / sqrtf(variance[c] + eps)); + diff_gamma[c] = 0; + diff_beta[c] = 0; + for (dim_t n = 0; n < nthr; n++) { + diff_gamma[c] += ws_reduce[C * n + c]; + diff_beta[c] += ws_reduce[C * nthr + C * n + c]; + } + diff_gamma[c] *= sqrt_variance; + } + + mkldnn_thr_barrier(); + + for (dim_t c = 0; c < C; c++) { + diff_gamma_loc[c] = diff_gamma[c]; + diff_beta_loc[c] = diff_beta[c]; + } + + for (dim_t n = N_s; n < N_e; n++) { + for (dim_t sp = 0; sp < SP; sp++) { +#if SAFE_TO_USE_OMP_SIMD + PRAGMA_OMP_SIMD() +#endif + for (dim_t c = 0; c < C; c++) { + const size_t d_off = (size_t)n * SP * C + sp * C + c; + data_t gamma = use_scaleshift ? scaleshift[c] : 1; + data_t sqrt_variance + = static_cast(1.0f / sqrtf(variance[c] + eps)); + data_t v_diff_src; + if (fuse_bn_relu) + v_diff_src = (!ws[d_off]) ? 0 : diff_dst[d_off]; + else + v_diff_src = diff_dst[d_off]; + if (calculate_diff_stats) { + v_diff_src -= diff_beta_loc[c] / (SP * N) + + (src[d_off] - mean[c]) * diff_gamma_loc[c] + * sqrt_variance / (SP * N); + } + v_diff_src *= gamma * sqrt_variance; + diff_src[d_off] = v_diff_src; + } + } + } + }); +} + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.hpp new file mode 100644 index 000000000000..aad86b05a772 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/nspc_batch_normalization.hpp @@ -0,0 +1,169 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_NSPC_BATCH_NORMALIZATION_HPP +#define CPU_NSPC_BATCH_NORMALIZATION_HPP + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_batch_normalization_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct nspc_batch_normalization_fwd_t : public cpu_primitive_t { + struct pd_t : public cpu_batch_normalization_fwd_pd_t { + pd_t(engine_t *engine, const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : cpu_batch_normalization_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + DECLARE_COMMON_PD_T("nspc_bnorm:any", nspc_batch_normalization_fwd_t); + + status_t init() { + using namespace data_type; + using namespace prop_kind; + + bool ok = true + /* the algorithm requires barriers while switching + * between parallelization over N and C dimensions */ + && mkldnn_thr_syncable() + && is_fwd() + && !has_zero_dim_memory() + && src_md()->data_type == f32 + && IMPLICATION(use_scaleshift(), weights_md()->data_type == f32) + && memory_desc_matches_tag(*src_md(), format_tag::nhwc) + && (attr()->has_default_values() || this->with_relu_post_op()); + if (!ok) return status::unimplemented; + + if (is_training() && fuse_bn_relu()) init_default_ws(8); + + init_scratchpad(); + + return status::success; + } + + private: + void init_scratchpad() { + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + if (!stats_is_src()) { + dim_t sz = nstl::max(C(), 16) * mkldnn_get_max_threads(); + scratchpad.book(key_bnorm_reduction, sizeof(data_t) * sz); + scratchpad.book(key_bnorm_tmp_mean, sizeof(data_t) * sz); + scratchpad.book(key_bnorm_tmp_var, sizeof(data_t) * sz); + } + } + }; + + typedef typename prec_traits::type data_t; + + nspc_batch_normalization_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + ~nspc_batch_normalization_fwd_t() {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +struct nspc_batch_normalization_bwd_t : public cpu_primitive_t { + struct pd_t : public cpu_batch_normalization_bwd_pd_t { + pd_t(engine_t *engine, const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : cpu_batch_normalization_bwd_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + DECLARE_COMMON_PD_T("nspc_bnorm:any", nspc_batch_normalization_bwd_t); + + status_t init() { + using namespace data_type; + using namespace prop_kind; + + bool ok = true + /* the algorithm requires barriers while switching + * between parallelization over N and C dimensions */ + && mkldnn_thr_syncable() + && is_bwd() + && !has_zero_dim_memory() + && utils::everyone_is(f32, src_md()->data_type, + diff_src_md()->data_type) + && IMPLICATION(use_scaleshift(), + utils::everyone_is(f32, + weights_md()->data_type, + diff_weights_md()->data_type)) + && memory_desc_matches_tag(*src_md(), format_tag::nhwc) + && memory_desc_matches_tag(*diff_src_md(), format_tag::nhwc) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + if (fuse_bn_relu()) { + init_default_ws(8); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + init_scratchpad(); + + return status::success; + } + + private: + void init_scratchpad() { + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + scratchpad.book(key_bnorm_reduction, + sizeof(data_t) * 2 * C() * mkldnn_get_max_threads()); + scratchpad.book(key_bnorm_tmp_diff_ss, sizeof(data_t) * 2 * C() + * (mkldnn_get_max_threads() + 1)); + } + }; + + typedef typename prec_traits::type data_t; + + nspc_batch_normalization_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + ~nspc_batch_normalization_bwd_t() {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.cpp new file mode 100644 index 000000000000..d79b1a034ba1 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.cpp @@ -0,0 +1,265 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "simple_q10n.hpp" + +#include "ref_batch_normalization.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +void ref_batch_normalization_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + /* fast return */ + if (this->pd()->has_zero_dim_memory()) return; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto scaleshift = CTX_IN_MEM(const float *, MKLDNN_ARG_SCALE_SHIFT); + + auto mean = pd()->stats_is_src() + ? const_cast(CTX_IN_MEM(const float *, MKLDNN_ARG_MEAN)) + : CTX_OUT_MEM(float *, MKLDNN_ARG_MEAN); + auto variance = pd()->stats_is_src() + ? const_cast(CTX_IN_MEM(const float *, MKLDNN_ARG_VARIANCE)) + : CTX_OUT_MEM(float *, MKLDNN_ARG_VARIANCE); + + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(uint8_t *, MKLDNN_ARG_WORKSPACE); + + const memory_desc_wrapper data_d(pd()->src_md()); + const memory_desc_wrapper scaleshift_d(pd()->weights_md()); + + const dim_t N = pd()->MB(); + const dim_t C = pd()->C(); + dim_t H = 1, W = 1, D = 1; + const bool has_spatial = utils::one_of(data_d.ndims(), 4, 5); + if (has_spatial) { + D = pd()->D(); + H = pd()->H(); + W = pd()->W(); + } + + const float eps = pd()->desc()->batch_norm_epsilon; + const bool use_scaleshift = pd()->use_scaleshift();; + const bool save_stats = pd()->is_training(); + const bool is_training = pd()->is_training(); + const bool fuse_bn_relu = pd()->fuse_bn_relu(); + const bool calculate_stats = !pd()->stats_is_src(); + + const bool with_relu = pd()->with_relu_post_op(); + auto maybe_post_op = [&](float res) { + return (with_relu && res < 0.0f) ? 0.0f : res; + }; + const bool is_3d = data_d.ndims() == 5; + + auto data_offset = [&](const memory_desc_wrapper &data_d, dim_t n, dim_t c, + dim_t d, dim_t h, dim_t w) { + if (has_spatial) { + if (is_3d) + return data_d.off(n, c, d, h, w); + else + return data_d.off(n, c, h, w); + } else + return data_d.off(n, c); + }; + + parallel_nd(C, [&](dim_t c) { + float v_mean = calculate_stats ? 0 : mean[c]; + float v_variance = calculate_stats ? 0 : variance[c]; + + if (calculate_stats) { + for (dim_t n = 0; n < N; ++n) + for (dim_t d = 0; d < D; ++d) + for (dim_t h = 0; h < H; ++h) + for (dim_t w = 0; w < W; ++w) + v_mean += src[data_offset(data_d, n, c, d, h, w)]; + v_mean /= W*N*H*D; + + for (dim_t n = 0; n < N; ++n) + for (dim_t d = 0; d < D; ++d) + for (dim_t h = 0; h < H; ++h) + for (dim_t w = 0; w < W; ++w) { + float m = src[data_offset(data_d, n, c, d, h, w)] - v_mean; + v_variance += m*m; + } + v_variance /= W*H*N*D; + } + + float sqrt_variance = sqrtf(v_variance + eps); + float sm = (use_scaleshift + ? scaleshift[scaleshift_d.off(0, c)] + : 1.0f) / sqrt_variance; + float sv = use_scaleshift ? scaleshift[scaleshift_d.off(1, c)] : 0; + + for (dim_t n = 0; n < N; ++n) + for (dim_t d = 0; d < D; ++d) + for (dim_t h = 0; h < H; ++h) + for (dim_t w = 0; w < W; ++w) { + auto d_off = data_offset(data_d,n,c,d,h,w); + float bn_res = sm * ((float)src[d_off] - v_mean) + sv; + if (fuse_bn_relu) { + if (bn_res <= 0) { + bn_res = 0; + if (is_training) + ws[d_off] = 0; + } else { + if (is_training) + ws[d_off] = 1; + } + } + if (data_type == data_type::s8) { + dst[d_off] = qz_a1b0()(maybe_post_op(bn_res)); + } else { + dst[d_off] = static_cast(maybe_post_op(bn_res)); + } + } + + if (calculate_stats) { + if (save_stats) { + mean[c] = v_mean; + variance[c] = v_variance; + } + } + }); +} + +template struct ref_batch_normalization_fwd_t; +template struct ref_batch_normalization_fwd_t; + +template +void ref_batch_normalization_bwd_t::execute_backward( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto mean = CTX_IN_MEM(const data_t *, MKLDNN_ARG_MEAN); + auto variance = CTX_IN_MEM(const data_t *, MKLDNN_ARG_VARIANCE); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto scaleshift = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SCALE_SHIFT); + auto ws = CTX_IN_MEM(const uint8_t *, MKLDNN_ARG_WORKSPACE); + + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + auto diff_scaleshift = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SCALE_SHIFT); + + const memory_desc_wrapper data_d(pd()->src_md()); + const memory_desc_wrapper diff_data_d(pd()->diff_src_md()); + const memory_desc_wrapper scaleshift_d(pd()->weights_md()); + const memory_desc_wrapper diff_scaleshift_d(pd()->diff_weights_md()); + + const dim_t C = pd()->C(); + + /* fast return */ + if (this->pd()->has_zero_dim_memory()) { + if (diff_scaleshift) { + for (dim_t c = 0; c < C; ++c) { + diff_scaleshift[diff_scaleshift_d.off(0, c)] = 0; + diff_scaleshift[diff_scaleshift_d.off(1, c)] = 0; + } + } + return; + } + + const dim_t N = pd()->MB(); + dim_t H = 1, W = 1, D = 1; + const bool has_spatial = utils::one_of(data_d.ndims(), 4, 5); + if (has_spatial) { + D = pd()->D(); + H = pd()->H(); + W = pd()->W(); + } + + const float eps = pd()->desc()->batch_norm_epsilon; + const bool use_scaleshift = pd()->use_scaleshift(); + const bool calculate_diff_stats = !pd()->use_global_stats(); + const bool fuse_bn_relu = pd()->fuse_bn_relu(); + + const bool is_3d = data_d.ndims() == 5; + + auto data_offset = [&](const memory_desc_wrapper &data_d, dim_t n, dim_t c, + dim_t d, dim_t h, dim_t w) { + if (has_spatial) { + if (is_3d) + return data_d.off(n, c, d, h, w); + else + return data_d.off(n, c, h, w); + } else + return data_d.off(n, c); + }; + + parallel_nd(C, [&](dim_t c) { + data_t v_mean = mean[c]; + data_t v_variance = variance[c]; + data_t sqrt_variance = static_cast(1.0f / sqrtf(v_variance + eps)); + data_t gamma = use_scaleshift ? scaleshift[scaleshift_d.off(0, c)] : 1; + data_t diff_gamma = data_t(0); + data_t diff_beta = data_t(0); + diff_gamma = 0.0; + diff_beta = 0.0; + + for (dim_t n = 0; n < N; ++n) + for (dim_t d = 0; d < D; ++d) + for (dim_t h = 0; h < H; ++h) + for (dim_t w = 0; w < W; ++w) { + const size_t s_off = data_offset(data_d, n, c, d, h, w); + data_t dd = diff_dst[data_offset(diff_data_d, n, c, d, h, w)]; + if (fuse_bn_relu && !ws[s_off]) + dd = 0; + + diff_gamma += (src[s_off] - v_mean) * dd; + diff_beta += dd; + } + diff_gamma *= sqrt_variance; + + if (diff_scaleshift) { + diff_scaleshift[diff_scaleshift_d.off(0, c)] = diff_gamma; + diff_scaleshift[diff_scaleshift_d.off(1, c)] = diff_beta; + } + + for (dim_t n = 0; n < N; ++n) + for (dim_t d = 0; d < D; ++d) + for (dim_t h = 0; h < H; ++h) + for (dim_t w = 0; w < W; ++w) { + const size_t s_off = data_offset(data_d, n, c, d, h, w); + const size_t dd_off = data_offset(diff_data_d, n, c, d, h, w); + data_t dd = diff_dst[dd_off]; + if (fuse_bn_relu && !ws[s_off]) + dd = 0; + + data_t v_diff_src = dd; + if (calculate_diff_stats) { + v_diff_src -= diff_beta/(D*W*H*N) + + (src[s_off] - v_mean) * + diff_gamma*sqrt_variance/(D*W*H*N); + } + v_diff_src *= gamma*sqrt_variance; + diff_src[dd_off] = v_diff_src; + } + }); +} + +template struct ref_batch_normalization_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.hpp new file mode 100644 index 000000000000..aa9f74125a69 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_batch_normalization.hpp @@ -0,0 +1,127 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_BATCH_NORMALIZATION_HPP +#define CPU_REF_BATCH_NORMALIZATION_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_batch_normalization_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_batch_normalization_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_batch_normalization_fwd_pd_t { + pd_t(engine_t *engine, const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : cpu_batch_normalization_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + DECLARE_COMMON_PD_T("ref:any", ref_batch_normalization_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && src_md()->data_type == data_type + && IMPLICATION(use_scaleshift(), + weights_md()->data_type == data_type::f32) + && (attr()->has_default_values() || with_relu_post_op()); + if (!ok) return status::unimplemented; + + if (src_md()->data_type == data_type::s8 && !stats_is_src()) + return status::unimplemented; + + if (is_training() && fuse_bn_relu()) init_default_ws(8); + + return status::success; + } + }; + + ref_batch_normalization_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_batch_normalization_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_batch_normalization_bwd_pd_t { + pd_t(engine_t *engine, const batch_normalization_desc_t *adesc, + const primitive_attr_t *attr, + const batch_normalization_fwd_pd_t *hint_fwd_pd) + : cpu_batch_normalization_bwd_pd_t(engine, adesc, attr, hint_fwd_pd) + {} + + DECLARE_COMMON_PD_T("ref:any", ref_batch_normalization_bwd_t); + + status_t init() { + bool ok = true + && is_bwd() + && utils::everyone_is(data_type, src_md()->data_type, + diff_src_md()->data_type) + && IMPLICATION(use_scaleshift(), utils::everyone_is(data_type, + weights_md()->data_type, + diff_weights_md()->data_type)) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + if (fuse_bn_relu()) { + init_default_ws(8); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + return status::success; + } + }; + + ref_batch_normalization_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_concat.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_concat.hpp new file mode 100644 index 000000000000..4c534b550820 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_concat.hpp @@ -0,0 +1,97 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef REF_CONCAT_HPP +#define REF_CONCAT_HPP + +#include "reorder_pd.hpp" + +#include "cpu_concat_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct ref_concat_t: public cpu_primitive_t { + struct pd_t: public cpu_concat_pd_t { + using cpu_concat_pd_t::cpu_concat_pd_t; + + pd_t(const pd_t &rhs): cpu_concat_pd_t(rhs) { + for (size_t i = 0; i < rhs.reorder_pds_.size(); ++i) + reorder_pds_.push_back( + (const reorder_pd_t *)rhs.reorder_pds_[i]->clone()); + } + ~pd_t() { for (auto &rpd: reorder_pds_) delete rpd; } + + DECLARE_CONCAT_PD_T("ref:any", ref_concat_t); + + status_t init() { + bool ok = cpu_concat_pd_t::init() == status::success; + if (!ok) return status::unimplemented; + + for (int i = 0; i < n_; ++i) { + auto r_impls = engine_->get_reorder_implementation_list(); + for (auto r = r_impls; *r; ++r) { + const primitive_attr_t attr; /* alpha == 1. */ + reorder_pd_t *r_pd = nullptr; + if ((*r)(&r_pd, engine_, &attr, engine_, src_md(i), + engine_, src_image_md(i)) == status::success) { + r_pd->init_info(); + reorder_pds_.push_back(r_pd); + break; + } + } + } + + ok = reorder_pds_.size() == (size_t)n_; + return ok ? status::success : status::unimplemented; + } + + nstl::vector reorder_pds_; + }; + + ref_concat_t(const pd_t *apd): cpu_primitive_t(apd) { + const int n = pd()->n_inputs(); + reorders_.resize(n); + for (int i = 0; i < n; ++i) + pd()->reorder_pds_[i]->create_primitive(&reorders_[i]); + } + + ~ref_concat_t() { for (auto &r: reorders_) delete r; } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + const auto n = pd()->n_inputs(); + for (int i = 0; i < n; ++i) { + exec_args_t r_args; + r_args[MKLDNN_ARG_SRC] = ctx.args().at(MKLDNN_ARG_MULTIPLE_SRC + i); + r_args[MKLDNN_ARG_DST] = ctx.args().at(MKLDNN_ARG_DST); + exec_ctx_t r_ctx(ctx.stream(), std::move(r_args)); + reorders_[i]->execute(r_ctx); + } + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + nstl::vector reorders_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.cpp new file mode 100644 index 000000000000..c0a979c4cf44 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.cpp @@ -0,0 +1,395 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "mkldnn_traits.hpp" +#include "type_helpers.hpp" + +#include "ref_convolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using math::saturate; +using math::get_bias; + +template +void ref_convolution_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const bool with_groups = pd()->with_groups(); + + const int G = pd()->G(); + const int MB = pd()->MB(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + + const int OC = pd()->OC() / G; + const int IC = pd()->IC() / G; + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + + const int KSD = pd()->KSD(); + const int KSH = pd()->KSH(); + const int KSW = pd()->KSW(); + + const int KDD = pd()->KDD(); + const int KDH = pd()->KDH(); + const int KDW = pd()->KDW(); + + const int padFront = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + const bool with_relu = 0; // TODO: change if support post_ops + const float nslope = 0.f; + + const int ndims = pd()->desc()->src_desc.ndims; + + auto ker = [=](int g, int mb, int oc, int od, int oh, + int ow) { + acc_data_t d = 0; + for (int ic = 0; ic < IC; ++ic) + for (int kd = 0; kd < KD; ++kd) + for (int kh = 0; kh < KH; ++kh) + for (int kw = 0; kw < KW; ++kw) { + const int id = od * KSD - padFront + kd * (1 + KDD); + const int ih = oh * KSH - padT + kh * (1 + KDH); + const int iw = ow * KSW - padL + kw * (1 + KDW); + + if (id < 0 || id >= ID) continue; + if (ih < 0 || ih >= IH) continue; + if (iw < 0 || iw >= IW) continue; + + if (ndims == 5) + d += (acc_data_t)src[src_d.off(mb, g*IC + ic, id, ih, iw)] + * (with_groups + ? weights[weights_d.off(g, oc, ic, kd, kh, kw)] + : weights[weights_d.off(oc, ic, kd, kh, kw)]); + else if (ndims == 4) + d += (acc_data_t)src[src_d.off(mb, g*IC + ic, ih, iw)] + * (with_groups + ? weights[weights_d.off(g, oc, ic, kh, kw)] + : weights[weights_d.off(oc, ic, kh, kw)]); + else if (ndims == 3) + d += (acc_data_t)src[src_d.off(mb, g*IC + ic, iw)] + * (with_groups + ? weights[weights_d.off(g, oc, ic, kw)] + : weights[weights_d.off(oc, ic, kw)]); + else + assert(false); + + } + return d; + }; + + parallel_nd(G, MB, OC, OD, OH, OW, + [&](int g, int mb, int oc, int od, int oh, int ow) { + float a = bias + ? get_bias(bias, bias_d.off(g * OC + oc), + pd()->desc()->bias_desc.data_type) + : 0; + a += ker(g, mb, oc, od, oh, ow); + if (with_relu && a < 0) + a = a * nslope; + if (ndims == 5) + dst[dst_d.off(mb, g*OC + oc, od, oh, ow)] = saturate(a); + else if (ndims == 4) + dst[dst_d.off(mb, g*OC + oc, oh, ow)] = saturate(a); + else if (ndims == 3) + dst[dst_d.off(mb, g*OC + oc, ow)] = saturate(a); + else + assert(false); + }); +} + +template +void ref_convolution_bwd_data_t::execute_backward_data(const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto diff_src = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const bool with_groups = pd()->with_groups(); + + const int G = pd()->G(); + const int MB = pd()->MB(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + + const int OC = pd()->OC() / G; + const int IC = pd()->IC() / G; + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + + const int KSD = pd()->KSD(); + const int KSH = pd()->KSH(); + const int KSW = pd()->KSW(); + + const int KDD = pd()->KDD(); + const int KDH = pd()->KDH(); + const int KDW = pd()->KDW(); + + const int padFront = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + const int ndims = pd()->desc()->diff_src_desc.ndims; + + auto ker = [=](int g, int mb, int ic, int id, int ih, + int iw) { + acc_data_t d = 0; + for (int oc = 0; oc < OC; ++oc) + for (int kd = 0; kd < KD; ++kd) + for (int kh = 0; kh < KH; ++kh) + for (int kw = 0; kw < KW; ++kw) { + if (iw + padL < kw * (1 + KDW) + || ih + padT < kh * (1 + KDH) + || id + padFront < kd * (1 + KDD)) + continue; + int ow = iw - kw * (1 + KDW) + padL; + int oh = ih - kh * (1 + KDH) + padT; + int od = id - kd * (1 + KDD) + padFront; + if (ow % KSW != 0 || oh % KSH != 0 || od % KSD != 0) + continue; + + ow /= KSW; + oh /= KSH; + od /= KSD; + + if (od < OD && oh < OH && ow < OW) { + if (ndims == 5) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + + oc, od, oh, ow)] * (with_groups + ? weights[weights_d.off(g, oc, ic, kd, kh, kw)] + : weights[weights_d.off(oc, ic, kd, kh, kw)]); + else if (ndims == 4) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + + oc, oh, ow)] * (with_groups + ? weights[weights_d.off(g, oc, ic, kh, kw)] + : weights[weights_d.off(oc, ic, kh, kw)]); + else if (ndims == 3) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + + oc, ow)] * (with_groups + ? weights[weights_d.off(g, oc, ic, kw)] + : weights[weights_d.off(oc, ic, kw)]); + else + assert(false); + } + } + return d; + }; + + parallel_nd(G, MB, IC, ID, IH, IW, + [&](int g, int mb, int ic, int id, int ih, int iw) { + auto ds_idx = (ndims == 5) + ? diff_src_d.off(mb, g*IC + ic, id, ih, iw) + : (ndims == 4) + ? diff_src_d.off(mb, g*IC + ic, ih, iw) + : diff_src_d.off(mb, g*IC + ic, iw); + float a = bias + ? get_bias(bias, bias_d.off(g * IC + ic), + pd()->desc()->bias_desc.data_type) + : 0; + a += ker(g, mb, ic, id, ih, iw); + diff_src[ds_idx] = saturate(a); + }); +} + +template +void ref_convolution_bwd_weights_t::execute_backward_weights(const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(diff_wei_data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias = CTX_OUT_MEM(diff_wei_data_t *, MKLDNN_ARG_DIFF_BIAS); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + const memory_desc_wrapper diff_bias_d(pd()->diff_weights_md(1)); + + const bool with_groups = pd()->with_groups(); + + const int G = pd()->G(); + const int MB = pd()->MB(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + + const int OC = pd()->OC() / G; + const int IC = pd()->IC() / G; + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + + const int KSD = pd()->KSD(); + const int KSH = pd()->KSH(); + const int KSW = pd()->KSW(); + + const int KDD = pd()->KDD(); + const int KDH = pd()->KDH(); + const int KDW = pd()->KDW(); + + const int padFront = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + const int ndims = pd()->desc()->src_desc.ndims; + +auto ker = [=](acc_data_t &d, int g, int oc, int ic, int kd, int kh, int kw) { + for (int mb = 0; mb < MB; ++mb) + for (int od = 0; od < OD; ++od) + for (int oh = 0; oh < OH; ++oh) + for (int ow = 0; ow < OW; ++ow) { + if (ow*KSW + kw * (1 + KDW) < padL + || oh*KSH + kh * (1 + KDH) < padT + || od*KSD + kd * (1 + KDD) < padFront + || ow*KSW + kw * (1 + KDW) >= IW + padL + || oh*KSH + kh * (1 + KDH) >= IH + padT + || od*KSD + kd * (1 + KDD) >= ID + padFront) + continue; + + int id = od*KSD - padFront + kd * (1 + KDD); + int ih = oh*KSH - padT + kh * (1 + KDH); + int iw = ow*KSW - padL + kw * (1 + KDW); + if (ndims == 5) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + oc, od, + oh, ow)] * src[src_d.off(mb, g*IC + ic, id, ih, iw)]; + else if (ndims == 4) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + oc, oh, ow)] + * src[src_d.off(mb, g*IC + ic, ih, iw)]; + else if (ndims == 3) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + oc, ow)] + * src[src_d.off(mb, g*IC + ic, iw)]; + else + assert(false); + } + }; + + auto ker_bias = [=](acc_data_t &d, int g, int oc) { + for (int mb = 0; mb < MB; ++mb) + for (int od = 0; od < OD; ++od) + for (int oh = 0; oh < OH; ++oh) + for (int ow = 0; ow < OW; ++ow) { + if (ndims == 5) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + oc, od, oh, + ow)]; + else if (ndims == 4) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + oc, oh, + ow)]; + else if (ndims == 3) + d += (acc_data_t)diff_dst[diff_dst_d.off(mb, g*OC + oc, ow)]; + else + assert(false); + } + }; + + parallel_nd(G, OC, [&](int g, int oc) { + if (diff_bias) { + // XXX: loss of precision when bias is a float... + acc_data_t db = 0; + ker_bias(db, g, oc); + diff_bias[diff_bias_d.off(g*OC+oc)] + = saturate(db); + } + + for (int ic = 0; ic < IC; ++ic) + for (int kd = 0; kd < KD; ++kd) + for (int kh = 0; kh < KH; ++kh) + for (int kw = 0; kw < KW; ++kw) { + acc_data_t dw = 0; + ker(dw, g, oc, ic, kd, kh, kw); + + if (ndims == 5) { + auto idx = with_groups + ? diff_weights_d.off(g, oc, ic, kd, kh, kw) + : diff_weights_d.off(oc, ic, kd, kh, kw); + diff_weights[idx] = saturate(dw); + } else if (ndims == 4) { + auto idx = with_groups + ? diff_weights_d.off(g, oc, ic, kh, kw) + : diff_weights_d.off(oc, ic, kh, kw); + diff_weights[idx] = saturate(dw); + } else if (ndims == 3) { + auto idx = with_groups + ? diff_weights_d.off(g, oc, ic, kw) + : diff_weights_d.off(oc, ic, kw); + diff_weights[idx] = saturate(dw); + } else { + assert(false); + } + } + }); +} + +using namespace data_type; + +template struct ref_convolution_fwd_t; + +template struct ref_convolution_fwd_t; +template struct ref_convolution_fwd_t; +template struct ref_convolution_fwd_t; +template struct ref_convolution_fwd_t; + +template struct ref_convolution_bwd_data_t; + +template struct ref_convolution_bwd_data_t; +template struct ref_convolution_bwd_data_t; +template struct ref_convolution_bwd_data_t; +template struct ref_convolution_bwd_data_t; + +template struct ref_convolution_bwd_weights_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.hpp new file mode 100644 index 000000000000..7c83d0c6d4fc --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_convolution.hpp @@ -0,0 +1,194 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_CONVOLUTION_HPP +#define CPU_REF_CONVOLUTION_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_convolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_fwd_pd_t { + using cpu_convolution_fwd_pd_t::cpu_convolution_fwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_convolution_fwd_t); + + status_t init() { + using namespace data_type; + + bool ok = true + && is_fwd() + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, wei_type, data_type::undef, + dst_type, acc_type) + && IMPLICATION(with_bias(), true + && IMPLICATION(src_type == u8, + utils::one_of(bias_md_.data_type, f32, s32, s8, u8)) + && IMPLICATION(src_type == f32, + bias_md_.data_type == f32)) + && set_default_formats() + && attr()->has_default_values(); + return ok ? status::success : status::unimplemented; + } + + protected: + bool set_default_formats() { + using namespace format_tag; + auto dat_tag = utils::pick(ndims() - 3, ncw, nchw, ncdhw); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, goiw, goihw, goidhw) + : utils::pick(ndims() - 3, oiw, oihw, oidhw); + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + ref_convolution_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_convolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_data_pd_t { + using cpu_convolution_bwd_data_pd_t::cpu_convolution_bwd_data_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_convolution_bwd_data_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(diff_src_type, wei_type, data_type::undef, + diff_dst_type, acc_type) + && set_default_formats() + && attr()->has_default_values(); + + return ok ? status::success : status::unimplemented; + } + + virtual bool support_bias() const override { return true; } + + protected: + bool set_default_formats() { + using namespace format_tag; + auto dat_tag = utils::pick(ndims() - 3, ncw, nchw, ncdhw); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, goiw, goihw, goidhw) + : utils::pick(ndims() - 3, oiw, oihw, oidhw); + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + ref_convolution_bwd_data_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type diff_src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_convolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_convolution_bwd_weights_pd_t { + using cpu_convolution_bwd_weights_pd_t::cpu_convolution_bwd_weights_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_convolution_bwd_weights_t); + + status_t init() { + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && set_default_alg_kind(alg_kind::convolution_direct) + && expect_data_types(src_type, diff_wei_type, diff_wei_type, + diff_dst_type, acc_type) + && set_default_formats() + && attr()->has_default_values(); + return ok ? status::success : status::unimplemented; + } + + protected: + bool set_default_formats() { + using namespace format_tag; + auto dat_tag = utils::pick(ndims() - 3, ncw, nchw, ncdhw); + auto wei_tag = with_groups() + ? utils::pick(ndims() - 3, goiw, goihw, goidhw) + : utils::pick(ndims() - 3, oiw, oihw, oidhw); + return set_default_formats_common(dat_tag, wei_tag, dat_tag); + } + }; + + ref_convolution_bwd_weights_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type diff_wei_data_t; + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.cpp new file mode 100644 index 000000000000..541a303aabee --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.cpp @@ -0,0 +1,199 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "mkldnn_traits.hpp" +#include "math_utils.hpp" + +#include "ref_deconvolution.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +void ref_deconvolution_fwd_t::compute_fwd_bias(const data_t *bias, + data_t *dst) const { + const memory_desc_wrapper dst_d(pd()->dst_md()); + + const int G = pd()->G(); + const int MB = pd()->MB(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int OD = pd()->OD(); + const int OC = pd()->OC() / G; + const int ndims = pd()->desc()->src_desc.ndims; + + parallel_nd(MB, G, OC, OD, OH, OW, + [&](int mb, int g, int oc, int od, int oh, int ow) { + auto b = bias[g * OC + oc]; + switch (ndims) { + case 5: dst[dst_d.off(mb, g * OC + oc, od, oh, ow)] += b; break; + case 4: dst[dst_d.off(mb, g * OC + oc, oh, ow)] += b; break; + case 3: dst[dst_d.off(mb, g * OC + oc, ow)] += b; break; + default: assert(!"invalid dimension size"); + } + }); +} + +void ref_deconvolution_fwd_t::compute_fwd_bias_ncdhw(const data_t *bias, + data_t *dst) const { + const memory_desc_wrapper dst_d(pd()->dst_md()); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int SP = pd()->OW()*pd()->OH()*pd()->OD(); + + parallel_nd(MB, OC, [&](int mb, int oc) { + PRAGMA_OMP_SIMD() + for (int sp = 0; sp < SP; ++sp) { + auto offset = (size_t)(mb * OC + oc) * SP + sp; + dst[offset] += bias[oc]; + } + }); +} + +template +void ref_deconvolution_fwd_t::compute_fwd_bias_nCdhwXc(const data_t *bias, + data_t *dst) const { + const memory_desc_wrapper dst_d(pd()->dst_md()); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int SP = pd()->OW() * pd()->OH() * pd()->OD(); + + const ptrdiff_t stride_mb = dst_d.blocking_desc().strides[0]; + + parallel_nd(MB, utils::div_up(OC, blksize), SP, + [&](int mb, int oc_blk, int sp) { + int oc = oc_blk * blksize; + auto offset = mb * stride_mb + oc * SP + sp * blksize; + const int blk = nstl::min(blksize, OC - oc); + + PRAGMA_OMP_SIMD() + for (int i = 0; i < blk; ++i) + dst[offset + i] += bias[oc + i]; + }); +} + +void ref_deconvolution_bwd_weights_t::compute_bwd_bias(const data_t *diff_dst, + data_t *diff_bias) const { + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + + const int G = pd()->G(); + const int MB = pd()->MB(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + const int OC = pd()->OC() / G; + const int OD = pd()->OD(); + const int ndims = pd()->desc()->src_desc.ndims; + + parallel_nd(G, OC, [&](int g, int oc) { + data_t db = 0; + for (int mb = 0; mb < MB; ++mb) { + for (int od = 0; od < OD; ++od) { + for (int oh = 0; oh < OH; ++oh) { + for (int ow = 0; ow < OW; ++ow) { + switch (ndims) { + case 5: + db += diff_dst[diff_dst_d.off( + mb, g * OC + oc, od, oh, ow)]; + break; + case 4: + db += diff_dst[diff_dst_d.off( + mb, g * OC + oc, oh, ow)]; + break; + case 3: + db += diff_dst[diff_dst_d.off(mb, g * OC + oc, ow)]; + break; + default: assert(!"invalid dimension size"); + } + } + } + } + } + diff_bias[g * OC + oc] = db; + }); +} + +void ref_deconvolution_bwd_weights_t::compute_bwd_bias_ncdhw( + const data_t *diff_dst, data_t *diff_bias) const { + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + + const int OC = pd()->OC(); + const int MB = pd()->MB(); + const int SP = pd()->OH()*pd()->OW()*pd()->OD(); + + parallel_nd(OC, [&](int oc) { + data_t db = 0; + for (int mb = 0; mb < MB; ++mb) { + PRAGMA_OMP_SIMD() + for (int sp = 0; sp < SP; ++sp) { + auto offset = (size_t)(mb * OC + oc) * SP + sp; + db += diff_dst[offset]; + } + } + diff_bias[oc] = db; + }); +} + +template +void ref_deconvolution_bwd_weights_t::compute_bwd_bias_nCdhwXc( + const data_t *diff_dst, data_t *diff_bias) const { + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + + const int OC = pd()->OC(); + const int MB = pd()->MB(); + const int SP = pd()->OH() * pd()->OW() * pd()->OD(); + + const ptrdiff_t stride_mb = diff_dst_d.blocking_desc().strides[0]; + + parallel_nd(utils::div_up(OC, blksize), [&](int ocb) { + data_t db[blksize] = {0}; + + for (int mb = 0; mb < MB; ++mb) { + for (int sp = 0; sp < SP; ++sp) { + auto offset = mb * stride_mb + (ocb * SP + sp) * blksize; + + PRAGMA_OMP_SIMD() + for (int i = 0; i < blksize; ++i) + db[i] += diff_dst[offset+i]; + } + } + + const int blk = nstl::min(blksize, OC - ocb * blksize); + + PRAGMA_OMP_SIMD() + for (int i = 0; i < blk; ++i) + diff_bias[ocb * blksize + i] = db[i]; + }); +} + +template void ref_deconvolution_fwd_t::compute_fwd_bias_nCdhwXc<8>( + const data_t *diff_dst, data_t *diff_bias) const; +template void ref_deconvolution_fwd_t::compute_fwd_bias_nCdhwXc<16>( + const data_t *diff_dst, data_t *diff_bias) const; +template void ref_deconvolution_bwd_weights_t::compute_bwd_bias_nCdhwXc<8>( + const data_t *diff_dst, data_t *diff_bias) const; +template void ref_deconvolution_bwd_weights_t::compute_bwd_bias_nCdhwXc<16>( + const data_t *diff_dst, data_t *diff_bias) const; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.hpp new file mode 100644 index 000000000000..d61903c32d6f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_deconvolution.hpp @@ -0,0 +1,502 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_DECONVOLUTION_HPP +#define CPU_REF_DECONVOLUTION_HPP + +#include +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "primitive_iterator.hpp" + +#include "cpu_convolution_pd.hpp" +#include "cpu_deconvolution_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +static status_t compute_blocked_format(bool with_groups, + const memory_desc_t *oi_md, memory_desc_t *io_md) +{ + /* Computes blocking for *i*o* format from *o*i* format */ + + bool sanity_check_ok = true + && oi_md->ndims == io_md->ndims + && oi_md->format_kind == format_kind::blocked; + if (!sanity_check_ok) return status::invalid_arguments; + + const blocking_desc_t &oi_blk = oi_md->format_desc.blocking; + blocking_desc_t io_blk = io_md->format_desc.blocking; + + io_md->format_kind = format_kind::blocked; + io_blk = oi_blk; + + const int ID_OC = 0 + with_groups; + const int ID_IC = 1 + with_groups; + + nstl::swap(io_blk.strides[ID_OC], io_blk.strides[ID_IC]); + for (int i_blk = 0; i_blk < io_blk.inner_nblks; ++i_blk) { + if (utils::one_of(io_blk.inner_idxs[i_blk], ID_OC, ID_IC)) { + io_blk.inner_idxs[i_blk] = + (io_blk.inner_idxs[i_blk] == ID_OC ? ID_IC : ID_OC); + } + } + + return memory_desc_init_by_blocking_desc(*io_md, io_blk); +} + +static status_t conv_descr_create(const deconvolution_desc_t *dd, + convolution_desc_t *cd) +{ + using namespace prop_kind; + alg_kind_t alg_kind = dd->alg_kind == alg_kind::deconvolution_direct + ? alg_kind::convolution_direct : alg_kind::convolution_winograd; + + const memory_desc_t *src_md, *dst_md, *d_weights_d; + prop_kind_t prop_kind; + memory_desc_t c_weights_d; + if (utils::one_of(dd->prop_kind, forward_training, forward_inference)) { + prop_kind = backward_data; + src_md = &dd->dst_desc; + dst_md = &dd->src_desc; + d_weights_d = &dd->weights_desc; + } else if (dd->prop_kind == backward_data) { + prop_kind = forward_training; + src_md = &dd->diff_dst_desc; + dst_md = &dd->diff_src_desc; + d_weights_d = &dd->weights_desc; + } else { + prop_kind = dd->prop_kind; + src_md = &dd->diff_dst_desc; + dst_md = &dd->src_desc; + d_weights_d = &dd->diff_weights_desc; + } + + const bool with_groups = d_weights_d->ndims == src_md->ndims + 1; + + /* create weights desc for convolution */ + c_weights_d = *d_weights_d; + + const int ID_OC = 0 + with_groups; + const int ID_IC = 1 + with_groups; + + nstl::swap(c_weights_d.dims[ID_OC], c_weights_d.dims[ID_IC]); + nstl::swap(c_weights_d.padded_dims[ID_OC], c_weights_d.padded_dims[ID_IC]); + nstl::swap(c_weights_d.padded_offsets[ID_OC], c_weights_d.padded_offsets[ID_IC]); + + if (c_weights_d.format_kind != format_kind::any) + CHECK(compute_blocked_format(with_groups, d_weights_d, &c_weights_d)); + + return conv_desc_init(cd, prop_kind, alg_kind, src_md, &c_weights_d, + prop_kind != backward_weights ? &dd->bias_desc : nullptr, + dst_md, dd->strides, dd->dilates, + dd->padding[0], dd->padding[1], dd->padding_kind); +} + +struct ref_deconvolution_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_deconvolution_fwd_pd_t { + pd_t(engine_t *engine, + const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : cpu_deconvolution_fwd_pd_t(engine, adesc, attr, hint_fwd_pd) + , conv_pd_(nullptr) + {} + + pd_t(const pd_t &other) + : cpu_deconvolution_fwd_pd_t(other) + , conv_pd_(other.conv_pd_->clone()) + , conv_supports_bias_(other.conv_supports_bias_) + , dst_tag_(other.dst_tag_) + {} + + ~pd_t() { delete conv_pd_; } + + DECLARE_COMMON_PD_T(conv_pd_->name(), ref_deconvolution_fwd_t); + + status_t init_convolution() { + using namespace types; + + convolution_desc_t cd; + CHECK(conv_descr_create(desc(), &cd)); + + mkldnn_primitive_desc_iterator it(engine_, (op_desc_t *)&cd, + &attr_, nullptr); + while (++it != it.end()) { + conv_pd_ = *it; + conv_supports_bias_ = + static_cast(conv_pd_) + ->support_bias(); + bool output_f32 = utils::everyone_is(data_type::f32, + desc()->accum_data_type, desc()->dst_desc.data_type); + + bool ok = true + && conv_pd_->weights_md()->extra.flags == 0 + /* deconv reference code can process only f32 bias */ + && IMPLICATION(with_bias(), + conv_supports_bias_ || output_f32); + if (ok) return status::success; + + delete conv_pd_; + } + conv_pd_ = nullptr; + return status::unimplemented; + } + + status_t init() { + using namespace format_tag; + bool ok = true + && is_fwd() + && utils::one_of(desc()->alg_kind, + alg_kind::deconvolution_direct, + alg_kind::deconvolution_winograd) + && attr()->post_ops_.has_default_values(); + + if (ok) { + CHECK(init_convolution()); + if (weights_md_.format_kind == format_kind::any) { + CHECK(compute_blocked_format(with_groups(), + conv_pd_->weights_md(), &desc_.weights_desc)); + weights_md_ = desc_.weights_desc; + } + if (src_md_.format_kind == format_kind::any) + src_md_ = *conv_pd_->diff_dst_md(); + if (dst_md_.format_kind == format_kind::any) + dst_md_ = *conv_pd_->diff_src_md(); + if (bias_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(bias_md_, x)); + + dst_tag_ = memory_desc_matches_one_of_tag(dst_md_, + utils::pick(ndims() - 3, ncw, nchw, ncdhw), + utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c), + utils::pick(ndims() - 3, nCw16c, nChw16c, nCdhw16c)); + + return status::success; + } + + return status::unimplemented; + } + + virtual void init_scratchpad_md() override { + scratchpad_md_ = *conv_pd_->scratchpad_md(); + } + + primitive_desc_t *conv_pd_; + bool conv_supports_bias_; + format_tag_t dst_tag_; + }; + + typedef typename prec_traits::type data_t; + + ref_deconvolution_fwd_t(const pd_t *apd): cpu_primitive_t(apd) + { pd()->conv_pd_->create_primitive((primitive_t **)&conv_p_); } + ~ref_deconvolution_fwd_t() { delete conv_p_; } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + const auto &args = ctx.args(); + exec_args_t conv_args; + conv_args[MKLDNN_ARG_DIFF_DST] = args.at(MKLDNN_ARG_SRC); + conv_args[MKLDNN_ARG_WEIGHTS] = args.at(MKLDNN_ARG_WEIGHTS); + if (pd()->with_bias() && pd()->conv_supports_bias_) + conv_args[MKLDNN_ARG_BIAS] = args.at(MKLDNN_ARG_BIAS); + conv_args[MKLDNN_ARG_DIFF_SRC] = args.at(MKLDNN_ARG_DST); + if (!types::is_zero_md(pd()->scratchpad_md())) + conv_args[MKLDNN_ARG_SCRATCHPAD] = args.at(MKLDNN_ARG_SCRATCHPAD); + const exec_ctx_t conv_ctx(ctx.stream(), std::move(conv_args)); + + conv_p_->execute(conv_ctx); + + if (pd()->with_bias() && !pd()->conv_supports_bias_) { + using namespace format_tag; + + auto bias = CTX_IN_MEM(const data_t *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + switch (pd()->dst_tag_) { + case ncdhw: case nchw: case ncw: + compute_fwd_bias_ncdhw(bias, dst); + break; + case nCdhw8c: case nChw8c: case nCw8c: + compute_fwd_bias_nCdhwXc<8>(bias, dst); + break; + case nCdhw16c: case nChw16c: case nCw16c: + compute_fwd_bias_nCdhwXc<16>(bias, dst); + break; + default: + compute_fwd_bias(bias, dst); + break; + } + } + return status::success; + } + +private: + void compute_fwd_bias(const data_t *bias, data_t *dst) const; + void compute_fwd_bias_ncdhw(const data_t *bias, data_t *dst) const; + template void compute_fwd_bias_nCdhwXc(const data_t *bias, + data_t *dst) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + primitive_t *conv_p_; +}; + +struct ref_deconvolution_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_deconvolution_bwd_data_pd_t { + pd_t(engine_t *engine, const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : cpu_deconvolution_bwd_data_pd_t(engine, adesc, attr, hint_fwd_pd) + , conv_pd_(nullptr) + {} + + pd_t(const pd_t &other) + : cpu_deconvolution_bwd_data_pd_t(other) + , conv_pd_(other.conv_pd_->clone()) {} + + ~pd_t() { delete conv_pd_; } + + DECLARE_COMMON_PD_T(conv_pd_->name(), ref_deconvolution_bwd_data_t); + + status_t init_convolution() { + using namespace types; + + convolution_desc_t cd; + status_t status = conv_descr_create(desc(), &cd); + if (status != status::success) return status; + + mkldnn_primitive_desc_iterator it(engine_, (op_desc_t *)&cd, + &attr_, nullptr); + while (++it != it.end()) { + conv_pd_ = *it; + if (conv_pd_->weights_md()->extra.flags == 0) + return status::success; + delete conv_pd_; + } + + return status::unimplemented; + } + + status_t init() { + using namespace data_type; + bool ok = true + && desc()->prop_kind == prop_kind::backward_data + && utils::everyone_is(data_type::f32, + desc()->diff_src_desc.data_type, + desc()->weights_desc.data_type, + desc()->diff_dst_desc.data_type) + && utils::one_of(desc()->alg_kind, + alg_kind::deconvolution_direct, + alg_kind::deconvolution_winograd); + + if (ok) { + CHECK(init_convolution()); + if (weights_md_.format_kind == format_kind::any) { + CHECK(compute_blocked_format(with_groups(), + conv_pd_->weights_md(), &desc_.weights_desc)); + weights_md_ = desc_.weights_desc; + } + if (diff_src_md_.format_kind == format_kind::any) + diff_src_md_ = *conv_pd_->dst_md(); + if (diff_dst_md_.format_kind == format_kind::any) + diff_dst_md_ = *conv_pd_->src_md(); + + return status::success; + } + + return status::unimplemented; + } + + virtual void init_scratchpad_md() override { + scratchpad_md_ = *conv_pd_->scratchpad_md(); + } + + primitive_desc_t *conv_pd_; + }; + + typedef typename prec_traits::type data_t; + + ref_deconvolution_bwd_data_t(const pd_t *apd): cpu_primitive_t(apd) + { pd()->conv_pd_->create_primitive((primitive_t **)&conv_p_); } + ~ref_deconvolution_bwd_data_t() { delete conv_p_; } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + const auto &args = ctx.args(); + exec_args_t conv_args; + conv_args[MKLDNN_ARG_SRC] = args.at(MKLDNN_ARG_DIFF_DST); + conv_args[MKLDNN_ARG_WEIGHTS] = args.at(MKLDNN_ARG_WEIGHTS); + conv_args[MKLDNN_ARG_DST] = args.at(MKLDNN_ARG_DIFF_SRC); + if (!types::is_zero_md(pd()->scratchpad_md())) + conv_args[MKLDNN_ARG_SCRATCHPAD] = args.at(MKLDNN_ARG_SCRATCHPAD); + const exec_ctx_t conv_ctx(ctx.stream(), std::move(conv_args)); + + conv_p_->execute(conv_ctx); + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + primitive_t *conv_p_; +}; + +struct ref_deconvolution_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_deconvolution_bwd_weights_pd_t { + pd_t(engine_t *engine, + const deconvolution_desc_t *adesc, + const primitive_attr_t *attr, + const deconvolution_fwd_pd_t *hint_fwd_pd) + : cpu_deconvolution_bwd_weights_pd_t(engine, adesc, attr, hint_fwd_pd) + , conv_pd_(nullptr) + {} + + pd_t(const pd_t &other) + : cpu_deconvolution_bwd_weights_pd_t(other) + , conv_pd_(other.conv_pd_->clone()) + , dst_tag_(other.dst_tag_) + {} + + ~pd_t() { delete conv_pd_; } + + DECLARE_COMMON_PD_T(conv_pd_->name(), ref_deconvolution_bwd_weights_t); + + status_t init_convolution() { + using namespace types; + + convolution_desc_t cd; + status_t status = conv_descr_create(desc(), &cd); + if (status != status::success) return status; + + mkldnn_primitive_desc_iterator it(engine_, (op_desc_t *)&cd, + &attr_, nullptr); + while (++it != it.end()) { + conv_pd_ = *it; + if (conv_pd_->diff_weights_md()->extra.flags == 0) + return status::success; + delete conv_pd_; + } + return status::unimplemented; + } + + status_t init() { + using namespace format_tag; + bool ok = true + && desc()->prop_kind == prop_kind::backward_weights + && utils::everyone_is(data_type::f32, + desc()->src_desc.data_type, + desc()->diff_weights_desc.data_type, + desc()->diff_dst_desc.data_type) + && utils::one_of(desc()->alg_kind, + alg_kind::deconvolution_direct, + alg_kind::deconvolution_winograd) + && attr()->has_default_values(); + if (ok) { + CHECK(init_convolution()); + if (diff_weights_md_.format_kind == format_kind::any) { + CHECK(compute_blocked_format(with_groups(), + conv_pd_->diff_weights_md(), + &desc_.diff_weights_desc)); + diff_weights_md_ = desc_.diff_weights_desc; + } + if (src_md_.format_kind == format_kind::any) + src_md_ = *conv_pd_->diff_dst_md(); + if (diff_dst_md_.format_kind == format_kind::any) + diff_dst_md_ = *conv_pd_->src_md(); + if (diff_bias_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_bias_md_, x)); + + dst_tag_ = memory_desc_matches_one_of_tag(diff_dst_md_, + utils::pick(ndims() - 3, ncw, nchw, ncdhw), + utils::pick(ndims() - 3, nCw8c, nChw8c, nCdhw8c), + utils::pick(ndims() - 3, nCw16c, nChw16c, nCdhw16c)); + + return status::success; + } + + return status::unimplemented; + } + + virtual void init_scratchpad_md() override { + scratchpad_md_ = *conv_pd_->scratchpad_md(); + } + + primitive_desc_t *conv_pd_; + format_tag_t dst_tag_; + }; + + typedef typename prec_traits::type data_t; + + ref_deconvolution_bwd_weights_t(const pd_t *apd): cpu_primitive_t(apd) + { pd()->conv_pd_->create_primitive((primitive_t **)&conv_p_); } + ~ref_deconvolution_bwd_weights_t() { delete conv_p_; } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + const auto &args = ctx.args(); + exec_args_t conv_args; + conv_args[MKLDNN_ARG_DIFF_DST] = args.at(MKLDNN_ARG_SRC); + conv_args[MKLDNN_ARG_SRC] = args.at(MKLDNN_ARG_DIFF_DST); + conv_args[MKLDNN_ARG_DIFF_WEIGHTS] = args.at(MKLDNN_ARG_DIFF_WEIGHTS); + if (!types::is_zero_md(pd()->scratchpad_md())) + conv_args[MKLDNN_ARG_SCRATCHPAD] = args.at(MKLDNN_ARG_SCRATCHPAD); + const exec_ctx_t conv_ctx(ctx.stream(), std::move(conv_args)); + + status_t status = conv_p_->execute(conv_ctx); + if (status != status::success) return status; + + if (pd()->with_bias()) { + using namespace format_tag; + + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_bias = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + switch (pd()->dst_tag_) { + case ncdhw: case nchw: case ncw: + compute_bwd_bias_ncdhw(diff_dst, diff_bias); + break; + case nCdhw8c: case nChw8c: case nCw8c: + compute_bwd_bias_nCdhwXc<8>(diff_dst, diff_bias); + break; + case nCdhw16c: case nChw16c: case nCw16c: + compute_bwd_bias_nCdhwXc<16>(diff_dst, diff_bias); + break; + default: + compute_bwd_bias(diff_dst, diff_bias); + break; + } + } + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + void compute_bwd_bias(const data_t *diff_dst, data_t *diff_bias) const; + void compute_bwd_bias_ncdhw(const data_t *diff_dst, + data_t *diff_bias) const; + template void compute_bwd_bias_nCdhwXc( + const data_t *diff_dst, data_t *diff_bias) const; + + primitive_t *conv_p_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.cpp new file mode 100644 index 000000000000..7beee8d32370 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.cpp @@ -0,0 +1,297 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "ref_eltwise.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace alg_kind; +using namespace math; + +ref_eltwise_scalar_fwd_t::ref_eltwise_scalar_fwd_t(alg_kind_t alg, float alpha, + float beta): alg_(alg), alpha_(alpha), beta_(beta) { + assert(utils::one_of(alg_, eltwise_relu, eltwise_tanh, eltwise_elu, + eltwise_square, eltwise_abs, eltwise_sqrt, eltwise_linear, + eltwise_bounded_relu, eltwise_soft_relu, eltwise_logistic)); +} + +ref_eltwise_scalar_fwd_t::ref_eltwise_scalar_fwd_t( + const post_ops_t::entry_t::eltwise_t &eltwise) + : ref_eltwise_scalar_fwd_t(eltwise.alg, eltwise.alpha, eltwise.beta) {} + +float ref_eltwise_scalar_fwd_t::compute_scalar(float s) { + switch (alg_) { + case eltwise_relu: return relu_fwd(s, alpha_); + case eltwise_tanh: return tanh_fwd(s); + case eltwise_elu: return elu_fwd(s, alpha_); + case eltwise_square: return square_fwd(s); + case eltwise_abs: return abs_fwd(s); + case eltwise_sqrt: return sqrt_fwd(s); + case eltwise_linear: return linear_fwd(s, alpha_, beta_); + case eltwise_bounded_relu: return bounded_relu_fwd(s, alpha_); + case eltwise_soft_relu: return soft_relu_fwd(s); + case eltwise_logistic: return logistic_fwd(s); + default: assert(!"unknown eltwise alg_kind"); + } + + return 0.f; +} + +template +void ref_eltwise_fwd_t::execute_forward_nCspBc_padded( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper data_d(pd()->src_md()); + const blocking_desc_t &blk = data_d.blocking_desc(); + const int block = blk.inner_blks[0]; + + const int MB = pd()->MB(); + const int C = pd()->C() / block; + const int C_PADDED = data_d.padded_dims()[1] / block; + const int tail = pd()->C() % block; + const int SP = pd()->D() * pd()->H() * pd()->W(); + const auto alg_kind = pd()->desc()->alg_kind; + const float alpha = pd()->desc()->alpha; + const float beta = pd()->desc()->beta; + + auto ker = [=] (data_t &d, data_t s) { + switch (alg_kind) { + case eltwise_linear: d = linear_fwd(s, alpha, beta); break; + case eltwise_bounded_relu: + d = bounded_relu_fwd(s, alpha); break; + case eltwise_soft_relu: d = soft_relu_fwd(s); break; + case eltwise_logistic: d = logistic_fwd(s); break; + default: assert(!"unknown eltwise alg_kind"); + } + }; + + // FIXME: integer overflow? + + parallel_nd(MB, C_PADDED, SP, + [&](int n, int c, int sp) { + auto d_off = (n*C_PADDED*SP + c*SP + sp) * block; + if (c < C) { + for (int v = 0; v < block; v++) + ker(dst[d_off + v], src[d_off + v]); + } else { + for (int v = 0; v < tail; v++) + ker(dst[d_off + v], src[d_off + v]); + } + }); +} + +template +void ref_eltwise_fwd_t::execute_forward_generic( + const exec_ctx_t &ctx) const { + /* fast return */ + if (pd()->has_zero_dim_memory()) return; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper data_d(pd()->src_md()); + + const int MB = pd()->MB(); + const int C = pd()->C(); + const int D = pd()->D(); + const int H = pd()->H(); + const int W = pd()->W(); + const auto alg_kind = pd()->desc()->alg_kind; + const float alpha = pd()->desc()->alpha; + const float beta = pd()->desc()->beta; + const bool is_3d = pd()->desc()->data_desc.ndims == 5; + + parallel_nd(MB, C, D, H, W, + [&](int n, int c, int id, int h, int w) { + auto d_off = is_3d + ? data_d.off(n, c, id, h, w) : data_d.off(n, c, h, w); + data_t s = src[d_off]; + data_t &d = dst[d_off]; + switch (alg_kind) { + case eltwise_relu: d = relu_fwd(s, alpha); break; + case eltwise_tanh: d = tanh_fwd(s); break; + case eltwise_elu: d = elu_fwd(s, alpha); break; + case eltwise_square: d = square_fwd(s); break; + case eltwise_abs: d = abs_fwd(s); break; + case eltwise_sqrt: d = sqrt_fwd(s); break; + case eltwise_linear: d = linear_fwd(s, alpha, beta); break; + case eltwise_bounded_relu: + d = bounded_relu_fwd(s, alpha); break; + case eltwise_soft_relu: d = soft_relu_fwd(s); break; + case eltwise_logistic: d = logistic_fwd(s); break; + default: assert(!"unknown eltwise alg_kind"); + } + }); +} + +template +void ref_eltwise_fwd_t::execute_forward_dense( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper data_d(pd()->src_md()); + + const ptrdiff_t nelems = static_cast(data_d.nelems(true)); + const auto alg_kind = pd()->desc()->alg_kind; + const float alpha = pd()->desc()->alpha; + const float beta = pd()->desc()->beta; + + src += data_d.offset0(); + dst += data_d.offset0(); + + if (alg_kind == eltwise_relu) { + // a fast path for relu as the most popular activation + parallel_nd(nelems, [&](ptrdiff_t e) { + dst[e] = relu_fwd(src[e], alpha); + }); + return; + } + + parallel_nd(nelems, [&](ptrdiff_t e) { + const data_t s = src[e]; + data_t &d = dst[e]; + + switch (alg_kind) { + case eltwise_tanh: d = tanh_fwd(s); break; + case eltwise_elu: d = elu_fwd(s, alpha); break; + case eltwise_square: d = square_fwd(s); break; + case eltwise_abs: d = abs_fwd(s); break; + case eltwise_sqrt: d = sqrt_fwd(s); break; + case eltwise_linear: d = linear_fwd(s, alpha, beta); break; + case eltwise_bounded_relu: d = bounded_relu_fwd(s, alpha); break; + case eltwise_soft_relu: d = soft_relu_fwd(s); break; + case eltwise_logistic: d = logistic_fwd(s); break; + default: assert(!"unknown eltwise alg_kind"); + } + }); +} + +template +void ref_eltwise_bwd_t::execute_backward_generic( + const exec_ctx_t &ctx) const { + /* fast return */ + if (pd()->has_zero_dim_memory()) return; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper data_d(pd()->src_md()); + const memory_desc_wrapper diff_data_d(pd()->diff_src_md()); + + const int MB = pd()->MB(); + const int C = pd()->C(); + const int D = pd()->D(); + const int H = pd()->H(); + const int W = pd()->W(); + const auto alg_kind = pd()->desc()->alg_kind; + const float alpha = pd()->desc()->alpha; + const float beta = pd()->desc()->beta; + const bool is_3d = pd()->desc()->data_desc.ndims == 5; + + parallel_nd(MB, C, D, H, W, + [&](int n, int c, int d, int h, int w) { + auto data_off = is_3d + ? data_d.off(n, c, d, h, w) : data_d.off(n, c, h, w); + auto diff_data_off = is_3d + ? diff_data_d.off(n, c, d, h, w) + : diff_data_d.off(n, c, h, w); + data_t s = src[data_off]; + data_t dd = diff_dst[diff_data_off]; + data_t &ds = diff_src[diff_data_off]; + switch (alg_kind) { + case eltwise_relu: ds = relu_bwd(dd, s, alpha); break; + case eltwise_tanh: ds = tanh_bwd(dd, s); break; + case eltwise_elu: ds = elu_bwd(dd, s, alpha); break; + case eltwise_square: ds = square_bwd(dd, s); break; + case eltwise_abs: ds = abs_bwd(dd, s); break; + case eltwise_sqrt: ds = sqrt_bwd(dd, s); break; + case eltwise_linear: + ds = linear_bwd(dd, s, alpha, beta); break; + case eltwise_bounded_relu: + ds = bounded_relu_bwd(dd, s, alpha); break; + case eltwise_soft_relu: ds = soft_relu_bwd(dd, s); break; + case eltwise_logistic: ds = logistic_bwd(dd, s); break; + default: assert(!"unknown eltwise alg_kind"); + } + }); +} + +template +void ref_eltwise_bwd_t::execute_backward_dense( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper data_d(pd()->src_md()); + const memory_desc_wrapper diff_data_d(pd()->diff_src_md()); + + const ptrdiff_t nelems = static_cast(data_d.nelems(true)); + const auto alg_kind = pd()->desc()->alg_kind; + const float alpha = pd()->desc()->alpha; + const float beta = pd()->desc()->beta; + + src += data_d.offset0(); + diff_dst += diff_data_d.offset0(); + diff_src += diff_data_d.offset0(); + + parallel_nd(nelems, [&](ptrdiff_t e) { + const data_t dd = diff_dst[e]; + const data_t s = src[e]; + data_t &ds = diff_src[e]; + + switch (alg_kind) { + case eltwise_relu: ds = relu_bwd(dd, s, alpha); break; + case eltwise_tanh: ds = tanh_bwd(dd, s); break; + case eltwise_elu: ds = elu_bwd(dd, s, alpha); break; + case eltwise_square: ds = square_bwd(dd, s); break; + case eltwise_abs: ds = abs_bwd(dd, s); break; + case eltwise_sqrt: ds = sqrt_bwd(dd, s); break; + case eltwise_linear: ds = linear_bwd(dd, s, alpha, beta); break; + case eltwise_bounded_relu: ds = bounded_relu_bwd(dd, s, alpha); break; + case eltwise_soft_relu: ds = soft_relu_bwd(dd, s); break; + case eltwise_logistic: ds = logistic_bwd(dd, s); break; + default: assert(!"unknown eltwise alg_kind"); + } + }); +} + +template struct ref_eltwise_fwd_t; +template struct ref_eltwise_fwd_t; +template struct ref_eltwise_fwd_t; +template struct ref_eltwise_fwd_t; + +template struct ref_eltwise_bwd_t; +template struct ref_eltwise_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.hpp new file mode 100644 index 000000000000..8f4ab354135c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_eltwise.hpp @@ -0,0 +1,168 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_ELTWISE_HPP +#define CPU_REF_ELTWISE_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_eltwise_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct ref_eltwise_scalar_fwd_t { +public: + ref_eltwise_scalar_fwd_t(alg_kind_t alg, float alpha, float beta); + + // note that eltwise.scale is ignored + ref_eltwise_scalar_fwd_t(const post_ops_t::entry_t::eltwise_t &eltwise); + + float compute_scalar(float s); + + const alg_kind_t alg_; + const float alpha_; + const float beta_; +}; + +template +struct ref_eltwise_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_eltwise_fwd_pd_t { + using cpu_eltwise_fwd_pd_t::cpu_eltwise_fwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_eltwise_fwd_t); + + status_t init() { + using namespace utils; + + auto src_d = memory_desc_wrapper(src_md()); + + use_dense_ = false + || src_d.is_dense() + || (src_d.is_dense(true) && is_zero_preserved()); + + use_nCspBc_padded_ = !use_dense_ + && src_d.blocking_desc().inner_nblks == 1 + && one_of(src_d.blocking_desc().inner_blks[0], 8, 16) + && src_d.blocking_desc().inner_idxs[0] == 1 + && src_d.only_padded_dim(1) + && src_d.is_dense(true); + + if (has_zero_dim_memory()) + use_dense_ = use_nCspBc_padded_ = false; + + const bool use_generic = !use_dense_ && !use_nCspBc_padded_; + + bool ok = true + && is_fwd() + && everyone_is(data_type, desc()->data_desc.data_type) + && IMPLICATION(use_generic, one_of(src_d.ndims(), 4, 5)) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + return status::success; + } + + bool use_dense_, use_nCspBc_padded_; + }; + + ref_eltwise_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if (pd()->use_dense_) + execute_forward_dense(ctx); + else if (pd()->use_nCspBc_padded_) + execute_forward_nCspBc_padded(ctx); + else + execute_forward_generic(ctx); + return status::success; + } + +private: + void execute_forward_nCspBc_padded(const exec_ctx_t &ctx) const; + void execute_forward_dense(const exec_ctx_t &ctx) const; + void execute_forward_generic(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_eltwise_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_eltwise_bwd_pd_t { + using cpu_eltwise_bwd_pd_t::cpu_eltwise_bwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_eltwise_bwd_t); + + status_t init() { + using namespace utils; + + bool ok = true + && !is_fwd() + && everyone_is(data_type, + desc()->data_desc.data_type, + desc()->diff_data_desc.data_type) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + auto diff_dst_d = memory_desc_wrapper(diff_dst_md()); + const bool same_fmt_ = diff_dst_d == memory_desc_wrapper(src_md()); + + use_dense_ = true + && same_fmt_ + && diff_dst_d.is_dense(true) + && is_zero_preserved() + && !has_zero_dim_memory(); + const bool use_generic = !use_dense_; + + if (use_generic && !one_of(diff_dst_d.ndims(), 4, 5)) + return status::unimplemented; + + return status::success; + } + + bool use_dense_; + }; + + ref_eltwise_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if (pd()->use_dense_) + execute_backward_dense(ctx); + else + execute_backward_generic(ctx); + return status::success; + } + +private: + void execute_backward_dense(const exec_ctx_t &ctx) const; + void execute_backward_generic(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.cpp new file mode 100644 index 000000000000..c807a9ffd066 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.cpp @@ -0,0 +1,285 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "mkldnn_traits.hpp" +#include "math_utils.hpp" + +#include "ref_inner_product.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using math::saturate; +using math::get_bias; + +template +void ref_inner_product_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto bias = CTX_IN_MEM(const char *, MKLDNN_ARG_BIAS); + auto dst = CTX_OUT_MEM(dst_data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper bias_d(pd()->weights_md(1)); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int IC = pd()->IC(); + + const bool src_has_spatial = utils::one_of(src_d.ndims(), 3, 4, 5); + const int ndims = src_d.ndims() - 2; + + const auto &post_ops = pd()->attr()->post_ops_; + const bool do_relu = post_ops.len_ == 1; + const float nslope = do_relu ? post_ops.entry_[0].eltwise.alpha : 0.f; + + auto ker_has_spatial = [=](int mb, int oc) { + acc_data_t d = 0; + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + for (int ic = 0; ic < IC; ++ic) { + for (int kd = 0; kd < KD; ++kd) { + for (int kh = 0; kh < KH; ++kh) { + for (int kw = 0; kw < KW; ++kw) { + switch (ndims) { + case 3: + d += (acc_data_t)src[src_d.off(mb, ic, kd, kh, kw)] + * weights[weights_d.off( + oc, ic, kd, kh, kw)]; + break; + case 2: + d += (acc_data_t)src[src_d.off(mb, ic, kh, kw)] + * weights[weights_d.off(oc, ic, kh, kw)]; + break; + case 1: + d += (acc_data_t)src[src_d.off(mb, ic, kw)] + * weights[weights_d.off(oc, ic, kw)]; + break; + default: assert(!"unsupported ndims size"); + } + } + } + } + } + return d; + }; + + auto ker_no_spatial = [=](int mb, int oc) { + acc_data_t d = 0; + for (int ic = 0; ic < IC; ++ic) { + d += (acc_data_t)src[src_d.off(mb, ic)] + * weights[weights_d.off(oc, ic)]; + } + return d; + }; + + parallel_nd(MB, OC, [&](int mb, int oc) { + float a = bias + ? get_bias(bias, bias_d.off(oc), pd()->desc()->bias_desc.data_type) + : 0; + if (src_has_spatial) + a += ker_has_spatial(mb, oc); + else + a += ker_no_spatial(mb, oc); + if (do_relu && a < (acc_data_t)0) + a *= nslope; + dst[dst_d.off(mb, oc)] = saturate(a); + }); +} + +using namespace data_type; +template struct ref_inner_product_fwd_t; +template struct ref_inner_product_fwd_t; +template struct ref_inner_product_fwd_t; +template struct ref_inner_product_fwd_t; +template struct ref_inner_product_fwd_t; + +template +void ref_inner_product_bwd_data_t::execute_backward_data(const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const diff_dst_data_t *, MKLDNN_ARG_DIFF_DST); + auto weights = CTX_IN_MEM(const wei_data_t *, MKLDNN_ARG_WEIGHTS); + auto diff_src = CTX_OUT_MEM(diff_src_data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper weights_d(pd()->weights_md(0)); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int IC = pd()->IC(); + + const bool diff_src_has_spatial + = utils::one_of(diff_src_d.ndims(), 3, 4, 5); + const int ndims = diff_src_d.ndims() - 2; + + parallel_nd(MB, IC, [&](int mb, int ic) { + if (diff_src_has_spatial) { + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + for (int kd = 0; kd < KD; ++kd) + for (int kh = 0; kh < KH; ++kh) + for (int kw = 0; kw < KW; ++kw) { + acc_data_t ds = acc_data_t(0); + for (int oc = 0; oc < OC; ++oc) { + switch (ndims) { + case 3: + ds += (acc_data_t)(diff_dst[diff_dst_d.off(mb, oc)] + * weights[weights_d.off(oc, ic, kd, kh, kw)]); + break; + case 2: + ds += (acc_data_t)(diff_dst[diff_dst_d.off(mb, oc)] + * weights[weights_d.off(oc, ic, kh, kw)]); + break; + case 1: + ds += (acc_data_t)(diff_dst[diff_dst_d.off(mb, oc)] + * weights[weights_d.off(oc, ic, kw)]); + break; + default: assert(!"unsupported ndims size"); + } + } + switch (ndims) { + case 3: + diff_src[diff_src_d.off(mb, ic, kd, kh, kw)] + = (diff_src_data_t)ds; + break; + case 2: + diff_src[diff_src_d.off(mb, ic, kh, kw)] + = (diff_src_data_t)ds; + break; + case 1: + diff_src[diff_src_d.off(mb, ic, kw)] = (diff_src_data_t)ds; + break; + default: assert(!"unsupported ndims size"); + } + } + } else { + acc_data_t ds = acc_data_t(0); + for (int oc = 0; oc < OC; ++oc) { + ds += (acc_data_t)(diff_dst[diff_dst_d.off(mb, oc)] * + weights[weights_d.off(oc, ic)]); + } + diff_src[diff_src_d.off(mb, ic)] = (diff_src_data_t)ds; + } + }); +} + +template struct ref_inner_product_bwd_data_t; + +template +void ref_inner_product_bwd_weights_t::execute_backward_weights( + const exec_ctx_t &ctx) const { + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_weights = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_WEIGHTS); + auto diff_bias = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_BIAS); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_weights_d(pd()->diff_weights_md(0)); + const memory_desc_wrapper diff_bias_d(pd()->diff_weights_md(1)); + + const int MB = pd()->MB(); + const int OC = pd()->OC(); + const int IC = pd()->IC(); + + const bool src_has_spatial = utils::one_of(src_d.ndims(), 3, 4 ,5); + const int ndims = src_d.ndims() - 2; + + parallel_nd(OC, IC, [&](int oc, int ic) { + if (src_has_spatial) { + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + for (int kd = 0; kd < KD; ++kd) { + for (int kh = 0; kh < KH; ++kh) { + for (int kw = 0; kw < KW; ++kw) { + data_t *dw(nullptr); + switch (ndims) { + case 3: + dw = &diff_weights[diff_weights_d.off( + oc, ic, kd, kh, kw)]; + break; + case 2: + dw = &diff_weights[diff_weights_d.off( + oc, ic, kh, kw)]; + break; + case 1: + dw = &diff_weights[diff_weights_d.off(oc, ic, kw)]; + break; + default: assert(!"unsupported ndims size"); + } + *dw = data_t(0); + for (int mb = 0; mb < MB; ++mb) { + switch (ndims) { + case 3: + *dw += diff_dst[diff_dst_d.off(mb, oc)] + * src[src_d.off(mb, ic, kd, kh, kw)]; + break; + case 2: + *dw += diff_dst[diff_dst_d.off(mb, oc)] + * src[src_d.off(mb, ic, kh, kw)]; + break; + case 1: + *dw += diff_dst[diff_dst_d.off(mb, oc)] + * src[src_d.off(mb, ic, kw)]; + break; + default: assert(!"unsupported ndims size"); + } + } + } + } + } + } else { + data_t *dw = &diff_weights[diff_weights_d.off(oc, ic)]; + *dw = data_t(0); + for (int mb = 0; mb < MB; ++mb) { + *dw += diff_dst[diff_dst_d.off(mb, oc)] * + src[src_d.off(mb, ic)]; + } + } + }); + + if (diff_bias) { + diff_bias += diff_bias_d.offset0(); + + parallel_nd(OC, [&](int oc) { + data_t *db = &diff_bias[oc]; + *db = data_t(0); + for (int mb = 0; mb < MB; ++mb) + *db += diff_dst[diff_dst_d.off(mb, oc)]; + }); + } +} + +template struct ref_inner_product_bwd_weights_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.hpp new file mode 100644 index 000000000000..bf87dbd51459 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_inner_product.hpp @@ -0,0 +1,159 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_INNER_PRODUCT_HPP +#define CPU_REF_INNER_PRODUCT_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_inner_product_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_inner_product_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_fwd_pd_t { + using cpu_inner_product_fwd_pd_t::cpu_inner_product_fwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_inner_product_fwd_t); + + status_t init() { + using namespace data_type; + + bool ok = true + && set_default_params() == status::success + && is_fwd() + && src_md()->data_type == src_type + && weights_md()->data_type == wei_type + && desc()->accum_data_type == acc_type + && dst_md()->data_type == dst_type + && IMPLICATION(with_bias(), utils::one_of( + weights_md(1)->data_type, f32, s32, s8, u8)) + && attr()->output_scales_.has_default_values() + && attr()->post_ops_.len_ <= 1 + && IMPLICATION(attr()->post_ops_.len_ == 1, + attr()->post_ops_.entry_[0].is_relu(true, false)); + return ok ? status::success : status::unimplemented; + } + }; + + ref_inner_product_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_inner_product_bwd_data_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_bwd_data_pd_t { + using cpu_inner_product_bwd_data_pd_t::cpu_inner_product_bwd_data_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_inner_product_bwd_data_t); + + status_t init() { + bool ok = true + && set_default_params() == status::success + && desc()->prop_kind == prop_kind::backward_data + && diff_src_md()->data_type == diff_src_type + && weights_md()->data_type == wei_type + && desc()->accum_data_type == acc_type + && diff_dst_md()->data_type == diff_dst_type + && attr()->has_default_values(); + return ok ? status::success : status::unimplemented; + } + }; + + ref_inner_product_bwd_data_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type diff_src_data_t; + typedef typename prec_traits::type wei_data_t; + typedef typename prec_traits::type diff_dst_data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_data(ctx); + return status::success; + } + +private: + void execute_backward_data(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_inner_product_bwd_weights_t: public cpu_primitive_t { + struct pd_t: public cpu_inner_product_bwd_weights_pd_t { + using cpu_inner_product_bwd_weights_pd_t::cpu_inner_product_bwd_weights_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_inner_product_bwd_weights_t); + + status_t init() { + bool ok = true + && set_default_params() == status::success + && desc()->prop_kind == prop_kind::backward_weights + && utils::everyone_is(data_type, + src_md()->data_type, + diff_dst_md()->data_type, + diff_weights_md()->data_type) + && IMPLICATION(with_bias(), + data_type == diff_weights_md(1)->data_type) + && attr()->has_default_values(); + return ok ? status::success : status::unimplemented; + } + }; + + ref_inner_product_bwd_weights_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward_weights(ctx); + return status::success; + } + +private: + void execute_backward_weights(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.cpp new file mode 100644 index 000000000000..325e97963bf6 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.cpp @@ -0,0 +1,252 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" + +#include "ref_lrn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +static inline float fast_negative_powf(float omega, float beta) { + float Y; +/* + * Y = omega^(-3/4) = + * = 1.0f / sqrtf(omega) * sqrtf(1.0f / sqrtf(omega)) + * = sqrtf(1.0f / sqrtf(omega)) * 1.0f / sqrtf(omega) + * = sqrtf(1.0f / sqrtf(omega)) / sqrtf(omega) + * = sqrtf(1.0f / sqrtf(omega) / omega) + * = sqrtf(1.0f / (sqrtf(omega) * omega)) + */ + if (beta == 0.75f) { + Y = sqrtf(1.0f / (sqrtf(omega) * omega)); + } else { + Y = 1.0f / powf(omega, beta); + } + return Y; +}; + +template +template +void ref_lrn_fwd_t::execute_forward(const exec_ctx_t &ctx) const { + using namespace alg_kind; + using namespace format_tag; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper data_d(pd()->src_md()); + + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + const size_t stride_mb = data_d.blocking_desc().strides[0]; + const bool across_channels = pd()->desc()->alg_kind == lrn_across_channels; + constexpr int blksize = tag == nChw16c ? 16 : 8; + + auto data_off = [&](int mb, int c, int h, int w) -> size_t { + switch (tag) { + case nChw16c: + case nChw8c: return mb * stride_mb + c / blksize * H * W * blksize + + h * W * blksize + w * blksize + c % blksize; + case nchw: return mb * stride_mb + c * H * W + h * W + w; + case nhwc: return mb * stride_mb + h * W * C + w * C + c; + default: return data_d.off(mb, c, h, w); + } + }; + + auto ker = [=](data_t *d, int mb, int oc, int oh, int ow) { + const float alpha = static_cast(pd()->desc()->lrn_alpha); + const float beta = static_cast(pd()->desc()->lrn_beta); + const float k = static_cast(pd()->desc()->lrn_k); + + const int size = pd()->desc()->local_size; + const int half_size = (size - 1) / 2; + + float sum = 0; + if (across_channels) { + const int c_st = nstl::max(oc - half_size + 0, 0); + const int c_en = nstl::min(oc + half_size + 1, C); + + for (int c = c_st; c < c_en; ++c) { + const float s = src[data_off(mb, c, oh, ow)]; + sum += s * s; + } + } else { + int h_st = nstl::max(oh - half_size + 0, 0); + int h_en = nstl::min(oh + half_size + 1, H); + int w_st = nstl::max(ow - half_size + 0, 0); + int w_en = nstl::min(ow + half_size + 1, W); + for (int h = h_st; h < h_en; ++h) { + for (int w = w_st; w < w_en; ++w) { + const float s = src[data_off(mb, oc, h, w)]; + sum += s * s; + } + } + } + const int summands = across_channels ? size : size * size; + sum = k + alpha * sum / summands; + size_t off = data_off(mb, oc, oh, ow); + d[0] = static_cast(src[off] * fast_negative_powf(sum, beta)); + }; + + const int MB = pd()->MB(); + if (tag == nChw16c || tag == nChw8c) { + parallel_nd(MB, utils::div_up(C, blksize), H, W, + [&](int mb, int c_blk, int h, int w) { + int c = c_blk * blksize; + const size_t off = mb * stride_mb + c * H * W + + (h * W + w) * blksize; + PRAGMA_OMP_SIMD() + for (int cc = 0; cc < nstl::min(blksize, C - c); ++cc) + ker(&dst[off + cc], mb, c + cc, h, w); + }); + } else if (tag == nhwc) { + parallel_nd(MB, H, W, C, + [&](int mb, int h, int w, int c) { + const size_t off = mb * stride_mb + h * W * C + w * C + c; + ker(&dst[off], mb, c, h, w); + }); + } else { + parallel_nd(MB, C, H, W, + [&](int mb, int c, int h, int w) { + const size_t off = data_off(mb, c, h, w); + ker(&dst[off], mb, c, h, w); + }); + } +} + +template +template +void ref_lrn_bwd_t::execute_backward(const exec_ctx_t &ctx) const { + using namespace alg_kind; + using namespace format_tag; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper data_d(pd()->src_md()); + + const int MB = pd()->MB(); + const int C = pd()->C(); + const int H = pd()->H(); + const int W = pd()->W(); + const size_t stride_mb = data_d.blocking_desc().strides[0]; + constexpr int blksize = tag == nChw16c ? 16 : 8; + + const float alpha = static_cast(pd()->desc()->lrn_alpha); + const float beta = static_cast(pd()->desc()->lrn_beta); + const float k = static_cast(pd()->desc()->lrn_k); + const int kernel_size = pd()->desc()->local_size; + const int half_ksize = (kernel_size - 1) / 2; + + auto data_off = [&](int mb, int c, int h, int w) -> size_t { + switch (tag) { + case nChw16c: + case nChw8c: return mb * stride_mb + c/blksize * H * W * blksize + + h * W * blksize + w * blksize + c%blksize; + case nchw: return mb * stride_mb + c * H * W + h * W + w; + case nhwc: return mb * stride_mb + h * W * C + w * C + c; + default: return data_d.off(mb, c, h, w); + } + }; + + auto ker = [=](data_t *d, int mb, int oc, int oh, int ow) { + const int c_st = nstl::max(oc - half_ksize + 0, 0); + const int c_en = nstl::min(oc + half_ksize + 1, C); + + float A = 0, B = 0, omega_mid = 0; + for (int c = c_st; c < c_en; c++) { + float sum = 0.0; + const int i_st = nstl::max(c - half_ksize, 0); + const int i_en = nstl::min(c + kernel_size - half_ksize, C); + + for (int i = i_st; i < i_en; ++i) { + const float value = src[data_off(mb, i, oh, ow)]; + sum += value * value; + } + const float omega = static_cast(k + sum * alpha / kernel_size); + if (c == oc) omega_mid = omega; + float t = src[data_off(mb, c, oh, ow)] + * fast_negative_powf(omega, beta); + B += 1.0f / omega * t * diff_dst[data_off(mb, c, oh, ow)]; + } + + const size_t off = data_off(mb, oc, oh, ow); + A = fast_negative_powf(omega_mid, beta) * diff_dst[off]; + B *= src[off]; + B *= (2.0f * alpha * beta) / kernel_size; + *d = static_cast(A - B); // final cast down to data_t + }; + + if (tag == nChw16c || tag == nChw8c) { + parallel_nd(MB, utils::div_up(C, blksize), H, W, + [&](int mb, int c_blk, int h, int w) { + int c = c_blk * blksize; + const size_t off = mb * stride_mb + c * H * W + + (h * W + w) * blksize; + PRAGMA_OMP_SIMD() + for (int cc = 0; cc < nstl::min(blksize, C - c); ++cc) + ker(&diff_src[off + cc], mb, c + cc, h, w); + }); + } else if (tag == nhwc) { + parallel_nd(MB, H, W, C, + [&](int mb, int h, int w, int c) { + const size_t off = mb * stride_mb + h * W * C + w * C + c; + ker(&diff_src[off], mb, c, h, w); + }); + } else { + parallel_nd(MB, C, H, W, + [&](int mb, int c, int h, int w) { + const size_t off = data_off(mb, c, h, w); + ker(&diff_src[off], mb, c, h, w); + }); + } +} + +template void ref_lrn_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const; +template void ref_lrn_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const; +template void ref_lrn_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const; +template void ref_lrn_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const; +template void ref_lrn_fwd_t:: +execute_forward(const exec_ctx_t &ctx) const; +template void ref_lrn_bwd_t:: +execute_backward(const exec_ctx_t &ctx) const; +template void ref_lrn_bwd_t:: +execute_backward(const exec_ctx_t &ctx) const; +template void ref_lrn_bwd_t:: +execute_backward(const exec_ctx_t &ctx) const; +template void ref_lrn_bwd_t:: +execute_backward(const exec_ctx_t &ctx) const; +template void ref_lrn_bwd_t:: +execute_backward(const exec_ctx_t &ctx) const; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.hpp new file mode 100644 index 000000000000..f25cfb7faefa --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_lrn.hpp @@ -0,0 +1,136 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_LRN_HPP +#define CPU_REF_LRN_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_lrn_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_lrn_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_lrn_fwd_pd_t { + using cpu_lrn_fwd_pd_t::cpu_lrn_fwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_lrn_fwd_t); + + status_t init() { + using namespace format_tag; + + bool ok = true + && is_fwd() + && src_md()->data_type == data_type + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + dat_tag_ = memory_desc_matches_one_of_tag( + *src_md(), nChw16c, nChw8c, nchw, nhwc); + + return status::success; + } + + format_tag_t dat_tag_; + }; + + ref_lrn_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + using namespace format_tag; + switch (pd()->dat_tag_) { + case nChw16c: execute_forward(ctx); break; + case nChw8c: execute_forward(ctx); break; + case nchw: execute_forward(ctx); break; + case nhwc: execute_forward(ctx); break; + default: execute_forward(ctx); + } + return status::success; + } + +private: + template + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_lrn_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_lrn_bwd_pd_t { + using cpu_lrn_bwd_pd_t::cpu_lrn_bwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_lrn_bwd_t); + + status_t init() { + using namespace format_tag; + using namespace alg_kind; + + bool ok = true + && !is_fwd() + && utils::one_of(desc()->alg_kind, lrn_across_channels + /*, lrn_within_channel */) // not supported yet + && utils::everyone_is(data_type, + src_md()->data_type, + diff_src_md()->data_type) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + dat_tag_ = memory_desc_matches_one_of_tag( + *src_md(), nChw16c, nChw8c, nchw, nhwc); + + return status::success; + } + + format_tag_t dat_tag_; + }; + + ref_lrn_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + using namespace format_tag; + switch (pd()->dat_tag_) { + case nChw16c: execute_backward(ctx); break; + case nChw8c: execute_backward(ctx); break; + case nchw: execute_backward(ctx); break; + case nhwc: execute_backward(ctx); break; + default: execute_backward(ctx); + } + return status::success; + } + +private: + template + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.cpp new file mode 100644 index 000000000000..65b934e12335 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.cpp @@ -0,0 +1,381 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" + +#include "ref_pooling.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +void ref_pooling_fwd_t::execute_forward( + const exec_ctx_t &ctx) const { + using namespace alg_kind; + using namespace prop_kind; + + auto alg = pd()->desc()->alg_kind; + + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + auto ws = CTX_OUT_MEM(unsigned char *, MKLDNN_ARG_WORKSPACE); + + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + const memory_desc_wrapper ws_d(pd()->workspace_md()); + const data_type_t ws_dt = ws ? ws_d.data_type() : data_type::undef; + + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + const int SD = pd()->KSD(); + const int SH = pd()->KSH(); + const int SW = pd()->KSW(); + const int padF = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + const bool is_3d = pd()->desc()->src_desc.ndims == 5; + + auto apply_offset = [=](int index, int offset) { + return (index > offset) ? index - offset : 0; + }; + + auto set_ws = [=](int mb, int oc, int od, int oh, int ow, int value) { + if (ws) { + assert(ws_dt == data_type::u8 || ws_dt == data_type::s32); + size_t offset = is_3d + ? ws_d.off(mb, oc, od, oh, ow) : ws_d.off(mb, oc, oh, ow);; + if (ws_dt == data_type::u8) { + assert(0 <= value && value <= 255); + ws[offset] = value; + } else + reinterpret_cast(ws)[offset] = value; + } + }; + + auto ker_max = [=](data_t *d, int mb, int oc, int oh, int ow) { + for (int kh = 0; kh < KH; ++kh) { + for (int kw = 0; kw < KW; ++kw) { + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + if (ih < 0 || ih >= IH) continue; + if (iw < 0 || iw >= IW) continue; + + auto s = src[src_d.off(mb, oc, ih, iw)]; + if (s > d[0]) { + d[0] = s; + set_ws(mb, oc, 1, oh, ow, kh*KW + kw); + } + } + } + }; + + auto ker_avg = [=](data_t *d, int mb, int oc, int oh, int ow) { + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + auto num_summands = (alg == pooling_avg_include_padding) ? KW*KH + : (ih_end - ih_start)*(iw_end - iw_start); + + acc_data_t dst = 0; + for (int ih = ih_start; ih < ih_end; ++ih) { + for (int iw = iw_start; iw < iw_end; ++iw) { + dst += src[src_d.off(mb, oc, ih, iw)]; + } + } + + d[0] = math::out_round((float)dst / num_summands); + }; + + auto ker_max_3d = [=](data_t *d, int mb, int oc, int od, int oh, int ow) { + for (int kd = 0; kd < KD; ++kd) { + for (int kh = 0; kh < KH; ++kh) { + for (int kw = 0; kw < KW; ++kw) { + const int id = od * SD - padF + kd; + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + if (id < 0 || id >= ID) continue; + if (ih < 0 || ih >= IH) continue; + if (iw < 0 || iw >= IW) continue; + + auto s = src[src_d.off(mb, oc, id, ih, iw)]; + if (s > d[0]) { + d[0] = s; + set_ws(mb, oc, od, oh, ow, kd * KH * KW + kh*KW + kw); + } + } + } + } + }; + + auto ker_avg_3d = [=](data_t *d, int mb, int oc, int od, int oh, int ow) { + auto id_start = apply_offset(od*SD, padF); + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto id_end = nstl::min(od*SD - padF + KD, ID); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + auto num_summands = (alg == pooling_avg_include_padding) ? KW*KH*KD + : (ih_end - ih_start)*(iw_end - iw_start)*(id_end - id_start); + + acc_data_t dst = 0; + for (int id = id_start; id < id_end; ++id) { + for (int ih = ih_start; ih < ih_end; ++ih) { + for (int iw = iw_start; iw < iw_end; ++iw) { + dst += src[src_d.off(mb, oc, id, ih, iw)]; + } + } + } + + d[0] = math::out_round((float)dst / num_summands); + }; + + const int MB = pd()->MB(); + const int OC = pd()->C(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + + if (alg == pooling_max) { + parallel_nd(MB, OC, OD, OH, OW, + [&](int mb, int oc, int od, int oh, int ow) { + data_t *d = is_3d + ? &dst[dst_d.off(mb, oc, od, oh, ow)] + : &dst[dst_d.off(mb, oc, oh, ow)]; + d[0] = nstl::numeric_limits::lowest(); + set_ws(mb, oc, od, oh, ow, 0); + if (is_3d) ker_max_3d(d, mb, oc, od, oh, ow); + else ker_max(d, mb, oc, oh, ow); + }); + } else { + parallel_nd(MB, OC, OD, OH, OW, + [&](int mb, int oc, int od, int oh, int ow) { + data_t *d = is_3d + ? &dst[dst_d.off(mb, oc, od, oh, ow)] + : &dst[dst_d.off(mb, oc, oh, ow)]; + d[0] = 0; + if (is_3d) ker_avg_3d(d, mb, oc, od, oh, ow); + else ker_avg(d, mb, oc, oh, ow); + }); + } +} + +template +void ref_pooling_bwd_t::execute_backward( + const exec_ctx_t &ctx) const { + using namespace alg_kind; + + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto ws = CTX_IN_MEM(const unsigned char *, MKLDNN_ARG_WORKSPACE); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_dst_d(pd()->diff_dst_md()); + const memory_desc_wrapper diff_src_d(pd()->diff_src_md()); + const memory_desc_wrapper ws_d(pd()->workspace_md()); + + const int ID = pd()->ID(); + const int IH = pd()->IH(); + const int IW = pd()->IW(); + const int KD = pd()->KD(); + const int KH = pd()->KH(); + const int KW = pd()->KW(); + const int SD = pd()->KSD(); + const int SH = pd()->KSH(); + const int SW = pd()->KSW(); + const int padF = pd()->padFront(); + const int padT = pd()->padT(); + const int padL = pd()->padL(); + + const bool is_3d = pd()->desc()->diff_src_desc.ndims == 5; + + auto alg = pd()->desc()->alg_kind; + + auto apply_offset = [=](int index, int offset) { + return (index > offset) ? index - offset : 0; + }; + + auto ker_zero = [=](int _mb, int _oc) { + for (int ih = 0; ih < IH; ++ih) { + for (int iw = 0; iw < IW; ++iw) { + diff_src[diff_src_d.off(_mb, _oc, ih, iw)] = data_type_t(0); + } + } + }; + + auto ker_max = [=](const data_t *d, int mb, int oc, int oh, int ow) { + const size_t ws_off = ws_d.off(mb, oc, oh, ow); + const int index = ws_d.data_type() == data_type::u8 + ? (int)ws[ws_off] : ((int *)ws)[ws_off]; + const int kw = index % KW; + const int kh = index / KW; + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + // If padding area could fit the kernel, + // then input displacement would be out of bounds. + // No need to back propagate there as padding is + // virtual in pooling_max case. + if (ih < 0 || ih >= IH) + return; + if (iw < 0 || iw >= IW) + return; + + diff_src[diff_src_d.off(mb, oc, ih, iw)] += d[0]; + }; + + auto ker_avg = [=](const data_t *d, int mb, int oc, int oh, int ow) { + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + auto num_summands = (alg == pooling_avg_include_padding) ? KW*KH + : (ih_end - ih_start)*(iw_end - iw_start); + + for (int ih = ih_start; ih < ih_end; ++ih) { + for (int iw = iw_start; iw < iw_end; ++iw) { + diff_src[diff_src_d.off(mb, oc, ih, iw)] += d[0] / num_summands; + } + } + }; + + auto ker_zero_3d = [=](int _mb, int _oc) { + for (int id = 0; id < ID; ++id) { + for (int ih = 0; ih < IH; ++ih) { + for (int iw = 0; iw < IW; ++iw) { + diff_src[diff_src_d.off(_mb, _oc, id, ih, iw)] = + data_type_t(0); + } + } + } + }; + + auto ker_max_3d = [=](const data_t *d, int mb, int oc, int od, int oh, + int ow) { + const size_t ws_off = ws_d.off(mb, oc, od, oh, ow); + const int index = ws_d.data_type() == data_type::u8 + ? (int)ws[ws_off] : ((int *)ws)[ws_off]; + const int kw = index % KW; + const int kh = (index / KW) % KH; + const int kd = (index / KW) / KH; + const int id = od * SD - padF + kd; + const int ih = oh * SH - padT + kh; + const int iw = ow * SW - padL + kw; + + // If padding area could fit the kernel, + // then input displacement would be out of bounds. + // No need to back propagate there as padding is + // virtual in pooling_max case. + if (id < 0 || id >= ID) + return; + if (ih < 0 || ih >= IH) + return; + if (iw < 0 || iw >= IW) + return; + + diff_src[diff_src_d.off(mb, oc, id, ih, iw)] += d[0]; + }; + + auto ker_avg_3d = [=](const data_t *d, int mb, int oc, int od, int oh, + int ow) { + auto id_start = apply_offset(od*SD, padF); + auto ih_start = apply_offset(oh*SH, padT); + auto iw_start = apply_offset(ow*SW, padL); + auto id_end = nstl::min(od*SD - padF + KD, ID); + auto ih_end = nstl::min(oh*SH - padT + KH, IH); + auto iw_end = nstl::min(ow*SW - padL + KW, IW); + + auto num_summands = (alg == pooling_avg_include_padding) ? KW*KH*KD + : (ih_end - ih_start)*(iw_end - iw_start)*(id_end - id_start); + + for (int id = id_start; id < id_end; ++id) + for (int ih = ih_start; ih < ih_end; ++ih) + for (int iw = iw_start; iw < iw_end; ++iw) { + diff_src[diff_src_d.off(mb, oc, id, ih, iw)] += d[0] / num_summands; + } + }; + + const int MB = pd()->MB(); + const int OC = pd()->C(); + const int OD = pd()->OD(); + const int OH = pd()->OH(); + const int OW = pd()->OW(); + + if (pd()->desc()->alg_kind == alg_kind::pooling_max) { + parallel_nd(MB, OC, [&](int mb, int oc) { + if (is_3d) ker_zero_3d(mb, oc); + else ker_zero(mb, oc); + for (int od = 0; od < OD; ++od) { + for (int oh = 0; oh < OH; ++oh) { + for (int ow = 0; ow < OW; ++ow) { + const data_t *d = is_3d + ? &diff_dst[diff_dst_d.off(mb, oc, od, oh, ow)] + : &diff_dst[diff_dst_d.off(mb, oc, oh, ow)]; + if (is_3d) ker_max_3d(d, mb, oc, od, oh, ow); + else ker_max(d, mb, oc, oh, ow); + } + } + } + }); + } else { + parallel_nd(MB, OC, [&](int mb, int oc) { + if (is_3d) ker_zero_3d(mb, oc); + else ker_zero(mb, oc); + for (int od = 0; od < OD; ++od) { + for (int oh = 0; oh < OH; ++oh) { + for (int ow = 0; ow < OW; ++ow) { + const data_t *d = is_3d + ? &diff_dst[diff_dst_d.off(mb, oc, od, oh, ow)] + : &diff_dst[diff_dst_d.off(mb, oc, oh, ow)]; + if (is_3d) ker_avg_3d(d, mb, oc, od, oh, ow); + else ker_avg(d, mb, oc, oh, ow); + } + } + } + }); + } +} + +template struct ref_pooling_fwd_t; +template struct ref_pooling_fwd_t; +template struct ref_pooling_fwd_t; +template struct ref_pooling_fwd_t; + +template struct ref_pooling_bwd_t; +template struct ref_pooling_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.hpp new file mode 100644 index 000000000000..e43ceaa82b24 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_pooling.hpp @@ -0,0 +1,119 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_POOLING_HPP +#define CPU_REF_POOLING_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_pooling_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_pooling_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_fwd_pd_t { + using cpu_pooling_fwd_pd_t::cpu_pooling_fwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_pooling_fwd_t); + + status_t init() { + bool ok = true + && set_default_params() == status::success + && is_fwd() + && utils::everyone_is(data_type, src_md()->data_type, + dst_md()->data_type) + && desc()->accum_data_type == acc_type + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + bool is_training = desc_.prop_kind == prop_kind::forward_training; + if (desc()->alg_kind == alg_kind::pooling_max && is_training) + init_default_ws(); + + return status::success; + } + }; + + ref_pooling_fwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + + typedef typename prec_traits::type data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_forward(ctx); + return status::success; + } + +private: + void execute_forward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct ref_pooling_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_pooling_bwd_pd_t { + using cpu_pooling_bwd_pd_t::cpu_pooling_bwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_pooling_bwd_t); + + status_t init() { + bool ok = true + && set_default_params() == status::success + && !is_fwd() + && utils::everyone_is(data_type, diff_dst_md()->data_type, + diff_src_md()->data_type) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + if (desc()->alg_kind == alg_kind::pooling_max) { + init_default_ws(); + if (!compare_ws(hint_fwd_pd_)) + return status::unimplemented; + } + + return status::success; + } + }; + + ref_pooling_bwd_t(const pd_t *apd): cpu_primitive_t(apd) {} + typedef typename prec_traits::type data_t; + typedef typename prec_traits::type acc_data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_backward(ctx); + return status::success; + } + +private: + void execute_backward(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.cpp new file mode 100644 index 000000000000..af2774311064 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.cpp @@ -0,0 +1,153 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" + +#include "ref_shuffle.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace format_tag; + +template +template +void ref_shuffle_t::execute_(const exec_ctx_t &ctx) const { + using namespace prop_kind; + using namespace utils; + + const memory_desc_wrapper data_d(pd()->data_md()); + + auto i_arg = pd()->is_fwd() ? MKLDNN_ARG_SRC : MKLDNN_ARG_DIFF_DST; + auto o_arg = pd()->is_fwd() ? MKLDNN_ARG_DST : MKLDNN_ARG_DIFF_SRC; + auto input = CTX_IN_MEM(const data_t *, i_arg); + auto output = CTX_OUT_MEM(data_t *, o_arg); + + const int axis = pd()->axis(); + const int axis_size = pd()->axis_size(); + + const int MB = pd()->MB(); + const int C = pd()->C(); + int H = 1, W = 1, D = 1, HW = 1, SP = 1; + const bool has_spatial = utils::one_of(data_d.ndims(), 3, 4 ,5); + if (has_spatial) + { + D = pd()->D(); + H = pd()->H(); + W = pd()->W(); + HW = H * W; + SP = D * HW; + } + const size_t stride_mb = data_d.blocking_desc().strides[0]; + constexpr int blksize = one_of(tag, nChw16c, nCdhw16c) ? 16 : 8; + + if (axis == 1 && one_of(tag, nChw16c, nChw8c, nCdhw16c, nCdhw16c)) { +#if MKLDNN_THR == MKLDNN_THR_OMP +# pragma omp parallel for collapse(3) schedule(static) + for (int mb = 0; mb < MB; ++mb) + for (int cb = 0; cb < C; cb += blksize) + for (int sp = 0; sp < SP; ++sp) { + const size_t off = mb * stride_mb + sp * blksize; + const size_t output_off = off + cb * SP; + PRAGMA_OMP_SIMD() + for (int cc = 0; cc < nstl::min(blksize, C - cb); ++cc) + { + int input_c = rev_transposed_[cb + cc]; + const size_t input_off = off + input_c / blksize * SP * blksize + + input_c % blksize; + output[output_off + cc] = input[input_off]; + } + } +#else + parallel_nd(MB, utils::div_up(C, blksize), SP, [&](int mb, int c, + int sp) { + const size_t off = mb * stride_mb + sp * blksize; + const int cb = c * blksize; + const size_t output_off = off + cb * SP; + for (int cc = 0; cc < nstl::min(blksize, C - cb); ++cc) + { + int input_c = rev_transposed_[cb + cc]; + const size_t input_off = off + input_c / blksize * SP * blksize + + input_c % blksize; + output[output_off + cc] = input[input_off]; + } + }); +#endif + } else if (axis == 1 && one_of(tag, nhwc, ndhwc)) { + parallel_nd(MB, SP, [&](int mb, int sp) { + const size_t off = mb * stride_mb + sp * C; + PRAGMA_OMP_SIMD() + for (int c = 0; c < C; ++c) + output[off + c] = input[off + rev_transposed_[c]]; + }); + } else if (axis == 1 && one_of(tag, nchw, ncdhw)) { + parallel_nd(MB, C, [&](int mb, int c) { + const size_t output_off = mb * stride_mb + c * SP; + const size_t input_off = mb * stride_mb + rev_transposed_[c] * SP; + PRAGMA_OMP_SIMD() + for (int sp = 0; sp < SP; ++sp) { + output[output_off + sp] = input[input_off + sp]; + } + }); + } else { + auto dims = pd()->desc()->data_desc.dims; + auto ndims = pd()->desc()->data_desc.ndims; + const size_t outer_size = utils::array_product(dims, axis); + const size_t inner_size = utils::array_product(dims + axis + 1, + ndims - axis - 1); + const size_t dim = axis_size * inner_size; + + parallel_nd(outer_size, axis_size, inner_size, [&](size_t ou, int a, + size_t in) + { + const size_t off = ou * dim + in; + auto &o = output[data_d.off_l(off + a * inner_size)]; + o = input[data_d.off_l(off + rev_transposed_[a] * inner_size)]; + }); + } +} + +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<4>::execute_(const exec_ctx_t &ctx) const; + +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; +template void ref_shuffle_t<1>::execute_(const exec_ctx_t &ctx) const; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.hpp new file mode 100644 index 000000000000..5e09a1a69bc8 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_shuffle.hpp @@ -0,0 +1,111 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_SHUFFLE_HPP +#define CPU_REF_SHUFFLE_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_shuffle_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_shuffle_t : public cpu_primitive_t { + using shuffle_class = ref_shuffle_t; + + struct pd_t: public cpu_shuffle_pd_t { + using cpu_shuffle_pd_t::cpu_shuffle_pd_t; + + DECLARE_COMMON_PD_T("ref:any", shuffle_class); + + status_t init() { + using namespace format_tag; + + bool ok = true + && data_type_size + == types::data_type_size(data_md()->data_type); + if (!ok) return status::unimplemented; + + if (ndims() == 5) { + dat_tag_ = memory_desc_matches_one_of_tag( + *data_md(), nCdhw16c, nCdhw8c, ncdhw, ndhwc); + } else if (ndims() == 4) { + dat_tag_ = memory_desc_matches_one_of_tag( + *data_md(), nChw16c, nChw8c, nchw, nhwc); + } else + dat_tag_ = any; + + return status::success; + } + + format_tag_t dat_tag_; + }; + + ref_shuffle_t(const pd_t *apd): cpu_primitive_t(apd) { + const int axis_size = pd()->axis_size(); + const int group_size = pd()->group_size(); + const int transpose_row = pd()->is_fwd() ? group_size + : axis_size / group_size; + const int transpose_col = pd()->is_fwd() ? axis_size / group_size + : group_size; + rev_transposed_ = (int *)malloc(axis_size * sizeof(int), 64); + parallel_nd(transpose_col, transpose_row, [&](int i, int j) { + rev_transposed_[j * transpose_col + i] = i * transpose_row + j; + }); + } + + ~ref_shuffle_t() { free(rev_transposed_); } + + typedef typename typesize_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + using namespace format_tag; + switch (pd()->dat_tag_) { + case nCdhw16c: execute_(ctx); break; + case nChw16c: execute_(ctx); break; + case nCdhw8c: execute_(ctx); break; + case nChw8c: execute_(ctx); break; + case ncdhw: execute_(ctx); break; + case nchw: execute_(ctx); break; + case ndhwc: execute_(ctx); break; + case nhwc: execute_(ctx); break; + default: execute_(ctx); break; + } + return status::success; + } + +private: + template + void execute_(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + int *rev_transposed_; +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.cpp new file mode 100644 index 000000000000..36d5237f564e --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.cpp @@ -0,0 +1,264 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include +#include +#include + +#include "c_types_map.hpp" +#include "mkldnn_thread.hpp" +#include "type_helpers.hpp" + +#include "ref_softmax.hpp" +#include "gemm/os_blas.hpp" + +#ifdef USE_MKL +#include "mkl_vml_functions.h" +#endif + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +void ref_softmax_fwd_t::execute_forward_dense( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + parallel_nd(outer_size_, [&](int ou) { + const data_t *src_data = src + ou * channels_; + data_t *dst_data = dst + ou * channels_; + data_t scalar = 0; + + _max(channels_, src_data, &scalar); + _sub(channels_, scalar, src_data, dst_data); + _exp(channels_, dst_data, dst_data); + _sum(channels_, dst_data, &scalar); + _scal(channels_, data_t(1)/scalar, dst_data); + }); +} + +template +void ref_softmax_fwd_t::execute_forward_generic( + const exec_ctx_t &ctx) const { + auto src = CTX_IN_MEM(const data_t *, MKLDNN_ARG_SRC); + auto dst = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + data_t space_max_val = 0, space_denom_val = 0; + data_t *space_max = &space_max_val, *space_denom = &space_denom_val; + if (inner_size_ > 1) { + using namespace memory_tracking::names; + space_max = scratchpad(ctx).template get(key_softmax_reduction); + space_denom = space_max + inner_size_; + } + + const memory_desc_wrapper data_d(pd()->src_md()); + const size_t dim = channels_ * inner_size_; + + for (int ou = 0; ou < outer_size_; ou++) { + utils::array_set(space_max, -FLT_MAX, inner_size_); + utils::array_set(space_denom, 0, inner_size_); + + for (int c = 0; c < channels_; c++) { + for(int in = 0; in < inner_size_; in++) { + size_t off = data_d.off_l(ou * dim + c * inner_size_ + in); + space_max[in] = nstl::max(space_max[in], src[off]); + } + } + + for (int c = 0; c < channels_; c++) { + for(int in = 0; in < inner_size_; in++) { + size_t off = data_d.off_l(ou * dim + c * inner_size_ + in); + space_denom[in] += dst[off] = exp(src[off] - space_max[in]); + } + } + + for (int c = 0; c < channels_; c++) { + for (int in = 0; in < inner_size_; in++) { + size_t off = data_d.off_l(ou * dim + c * inner_size_ + in); + dst[off] /= space_denom[in]; + } + } + } +} + +template +void ref_softmax_fwd_t::_max(int n, const data_t *x, + data_t *max_data) const { +// Intel(R) C++ Compiler generates the maxps + shuffle pattern +// for the max search which works faster +#if !defined(__INTEL_COMPILER) + // The code below makes a compiler to generate maxps instruction + // rather than maxss, which is generated for the 'else' code path + auto max_wrapper = [](data_t a, data_t b) { return nstl::max(a, b); }; + auto min_wrapper = [](int a, int b) { return nstl::min(a, b); }; + + constexpr int unroll_factor = 32; + data_t max_values[unroll_factor]; + + if (n < unroll_factor) { + data_t max_val = x[0]; + for (int i = 1; i < n; i++) { + max_val = max_wrapper(max_val, x[i]); + } + max_data[0] = max_val; + return; + } + for (int i = 0; i < unroll_factor; i++) { + max_values[i] = x[i]; + } + for (int i = unroll_factor; i < n; i += unroll_factor) { + int offset = min_wrapper(i, n - unroll_factor); + for (int j = 0; j < unroll_factor; j++) { + max_values[j] = max_wrapper(max_values[j], x[offset + j]); + } + } + data_t max_val = max_values[0]; + for (int i = 1; i < unroll_factor; i++) { + max_val = max_wrapper(max_val, max_values[i]); + } + max_data[0] = max_val; +#else + max_data[0] = x[0]; + for (int c = 1; c < n; ++c) + max_data[0] = nstl::max(max_data[0], x[c]); +#endif +} + +template +void ref_softmax_fwd_t::_sub(int n, data_t alpha, const data_t *x, + data_t *y) const { + constexpr int unroll_factor = 32; + int tail = n % unroll_factor; + for (int i = 0; i < n - tail; i += unroll_factor) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < unroll_factor; j++) { + y[i + j] = x[i + j] - alpha; + } + } + PRAGMA_OMP_SIMD() + for (int i = n - tail; i < n; i++) { + y[i] = x[i] - alpha; + } +} + +template +void ref_softmax_fwd_t::_exp(int n, const data_t *a, + data_t *r) const { +#ifdef USE_MKL + if (data_type == data_type::f32) { + vsExp(n, a, r); + return; + } +#endif + parallel_nd(n, [&](int c) { r[c] = expf(a[c]); }); +} + +template +void ref_softmax_fwd_t::_sum(int n, const data_t *x, + data_t *sum_data) const { +#ifdef USE_CBLAS + // Here we are summing x's eg. e^z , which are positives + // so we can use BLAS ASUM + if (data_type == data_type::f32) { + sum_data[0] = cblas_sasum(n, x, 1); + return; + } +#endif + data_t tsum = static_cast(0); + PRAGMA_OMP_SIMD(reduction(+ : tsum)) + for (int c = 0; c < n; ++c) + tsum += x[c]; + sum_data[0] = tsum; +} + +template +void ref_softmax_fwd_t::_scal(int n, data_t alpha, data_t *x) const { +#ifdef USE_CBLAS + if (data_type == data_type::f32) { + cblas_sscal(n, alpha, x, 1); + return; + } +#endif + parallel_nd(n, [&](int c) { x[c] *= alpha; }); +} + +template struct ref_softmax_fwd_t; + + +// NC/NCHW softmax for along final axe (1 for NC, 3 for NCHW) +template +void ref_softmax_bwd_t::execute_backward_dense( + const exec_ctx_t &ctx) const { + auto dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DST); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + parallel_nd(outer_size_, [&](int ou) { + data_t sbr = 0; + size_t off = channels_*ou; + for (int c = 0; c < channels_; c++) { + size_t loff = off + c; + data_t ldata = dst[loff]; + sbr += diff_dst[loff]*ldata; + diff_src[loff] = ldata; + } + + for(int c=0; c < channels_ ; ++c) { + size_t loff = off + c; + diff_src[loff] *= (diff_dst[loff] - sbr); + } + }); +} + +template +void ref_softmax_bwd_t::execute_backward_generic( + const exec_ctx_t &ctx) const { + auto dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DST); + auto diff_dst = CTX_IN_MEM(const data_t *, MKLDNN_ARG_DIFF_DST); + auto diff_src = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DIFF_SRC); + + const memory_desc_wrapper diff_d(pd()->diff_src_md()); + const memory_desc_wrapper data_d(pd()->dst_md()); + + const size_t dim = channels_ * inner_size_; + + parallel_nd(outer_size_, [&](int ou) { + for (int in = 0; in < inner_size_; in++) { + data_t sbr = 0; + for (int c = 0; c < channels_; c++) { + size_t off_diff = diff_d.off_l(ou * dim + c * inner_size_ + in); + size_t off_data = diff_d.off_l(ou * dim + c * inner_size_ + in); + sbr += diff_dst[off_diff] * dst[off_data]; + } + + for(int c=0; c < channels_ ; ++c) { + size_t off_diff = diff_d.off_l(ou * dim + c * inner_size_ + in); + size_t off_data = data_d.off_l(ou * dim + c * inner_size_ + in); + diff_src[off_diff] = dst[off_data] * (diff_dst[off_diff] - sbr); + } + } + }); +} + +template struct ref_softmax_bwd_t; + +} +} +} + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.hpp new file mode 100644 index 000000000000..5cb74d8007a9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_softmax.hpp @@ -0,0 +1,186 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_SOFTMAX_HPP +#define CPU_REF_SOFTMAX_HPP + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "cpu_softmax_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct ref_softmax_fwd_t: public cpu_primitive_t { + struct pd_t: public cpu_softmax_fwd_pd_t { + using cpu_softmax_fwd_pd_t::cpu_softmax_fwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_softmax_fwd_t); + + status_t init() { + bool ok = true + && is_fwd() + && src_md()->data_type == data_type + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + init_scratchpad(); + + return status::success; + } + + private: + void init_scratchpad() { + const int inner_size = utils::array_product( + desc()->data_desc.dims + desc()->softmax_axis + 1, + desc()->data_desc.ndims - desc()->softmax_axis - 1); + + if (inner_size > 1) { + auto scratchpad = scratchpad_registry().registrar(); + scratchpad.book(memory_tracking::names::key_softmax_reduction, + sizeof(data_t) * 2 * inner_size); + } + } + }; + + ref_softmax_fwd_t(const pd_t *apd): cpu_primitive_t(apd) + { + auto ndims = pd()->desc()->data_desc.ndims; + auto dims = pd()->desc()->data_desc.dims; + auto axis = pd()->desc()->softmax_axis; + + outer_size_ = utils::array_product(dims, axis); + channels_ = dims[axis]; + inner_size_ = utils::array_product(dims + axis + 1, ndims - axis - 1); + + const memory_desc_wrapper data_d(pd()->src_md()); + + bool no_axis_blocking = true; + for (int iblk = 0; iblk < data_d.blocking_desc().inner_nblks; ++iblk) + if (data_d.blocking_desc().inner_idxs[iblk] == axis) + no_axis_blocking = false; + + use_dense_ = inner_size_ == 1 && data_d.is_dense() + && no_axis_blocking + && data_d.blocking_desc().strides[axis] == 1; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if (use_dense_) + execute_forward_dense(ctx); + else + execute_forward_generic(ctx); + return status::success; + } + +private: + void execute_forward_dense(const exec_ctx_t &ctx) const; + void execute_forward_generic(const exec_ctx_t &ctx) const; + + void _max(int n, const data_t *x, data_t *max_data) const; + void _sub(int n, data_t alpha, const data_t *x, data_t *y) const; + void _exp(int n, const data_t *a, data_t *r) const; + void _sum(int n, const data_t *x, data_t *sum_data) const; + void _scal(int n, data_t alpha, data_t *x) const; + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + bool use_dense_; + int outer_size_, channels_, inner_size_; +}; + +template +struct ref_softmax_bwd_t: public cpu_primitive_t { + struct pd_t: public cpu_softmax_bwd_pd_t { + using cpu_softmax_bwd_pd_t::cpu_softmax_bwd_pd_t; + + DECLARE_COMMON_PD_T("ref:any", ref_softmax_bwd_t); + + status_t init() { + bool ok = true + && !is_fwd() + && utils::everyone_is(data_type, + dst_md()->data_type, + diff_src_md()->data_type) + && attr()->has_default_values(); + if (!ok) return status::unimplemented; + + return status::success; + } + }; + + ref_softmax_bwd_t(const pd_t *apd): cpu_primitive_t(apd) { + auto dims = pd()->desc()->diff_desc.dims; + auto axis = pd()->desc()->softmax_axis; + auto ndims = pd()->desc()->diff_desc.ndims; + + outer_size_ = utils::array_product(dims, axis); + channels_ = dims[axis]; + inner_size_ = utils::array_product(dims + axis + 1, ndims - axis - 1); + + const memory_desc_wrapper data_d(pd()->dst_md()); + const memory_desc_wrapper diff_d(pd()->diff_dst_md()); + + bool no_axis_blocking = true; + for (int iblk = 0; iblk < diff_d.blocking_desc().inner_nblks; ++iblk) + if (diff_d.blocking_desc().inner_idxs[iblk] == axis) + no_axis_blocking = false; + + use_dense_ = true + && inner_size_ == 1 + && diff_d == data_d + && diff_d.is_dense() + && no_axis_blocking + && diff_d.blocking_desc().strides[axis] == 1; + } + + typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + if (use_dense_) + execute_backward_dense(ctx); + else + execute_backward_generic(ctx); + return status::success; + } + +private: + void execute_backward_dense(const exec_ctx_t &ctx) const; + void execute_backward_generic(const exec_ctx_t &ctx) const; + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + bool use_dense_; + int outer_size_, channels_, inner_size_; +}; + + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/ref_sum.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/ref_sum.hpp new file mode 100644 index 000000000000..3b2a75d99b87 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/ref_sum.hpp @@ -0,0 +1,101 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef REF_SUM_HPP +#define REF_SUM_HPP + +#include "reorder_pd.hpp" + +#include "cpu_sum_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct ref_sum_t: public cpu_primitive_t { + struct pd_t: public cpu_sum_pd_t { + using cpu_sum_pd_t::cpu_sum_pd_t; + + pd_t(const pd_t &rhs): cpu_sum_pd_t(rhs) { + for (size_t i = 0; i < rhs.reorder_pds_.size(); ++i) + reorder_pds_.push_back( + (const reorder_pd_t *)rhs.reorder_pds_[i]->clone()); + } + + ~pd_t() { for (auto &rpd: reorder_pds_) delete rpd; } + + DECLARE_SUM_PD_T("ref:any", ref_sum_t); + + status_t init() { + bool ok = cpu_sum_pd_t::init() == status::success; + if (!ok) return status::unimplemented; + + for (int i = 0; i < n_; ++i) { + auto r_impls = engine_->get_reorder_implementation_list(); + for (auto r = r_impls; *r; ++r) { + primitive_attr_t attr; + attr.output_scales_.set(scales_[i]); + if (i != 0) attr.post_ops_.append_sum(1.0); + + reorder_pd_t *r_pd; + if ((*r)(&r_pd, engine_, &attr, engine_, src_md(i), + engine_, dst_md()) == status::success) { + r_pd->init_info(); + reorder_pds_.push_back(r_pd); + break; + } + } + } + + ok = reorder_pds_.size() == (size_t)n_; + return ok ? status::success : status::unimplemented; + } + + nstl::vector reorder_pds_; + }; + + ref_sum_t(const pd_t *apd): cpu_primitive_t(apd) { + const int n = pd()->n_inputs(); + reorders_.resize(n); + for (int i = 0; i < n; ++i) + pd()->reorder_pds_[i]->create_primitive(&reorders_[i]); + } + + ~ref_sum_t() { for (auto &r: reorders_) delete r; } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + const auto n = pd()->n_inputs(); + for (int i = 0; i < n; ++i) { + exec_args_t r_args; + r_args[MKLDNN_ARG_SRC] = ctx.args().at(MKLDNN_ARG_MULTIPLE_SRC + i); + r_args[MKLDNN_ARG_DST] = ctx.args().at(MKLDNN_ARG_DST); + exec_ctx_t r_ctx(ctx.stream(), std::move(r_args)); + reorders_[i]->execute(r_ctx); + } + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + nstl::vector reorders_; +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_common.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_common.cpp new file mode 100644 index 000000000000..537084db9120 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_common.cpp @@ -0,0 +1,90 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + * Common for RNN and LSTM cell execution + */ +#include "ref_rnn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { +using namespace rnn_utils; + +template +rnn_cell_execution_sig( + (_ref_rnn_common_t::cell_execution)) { + if (!rnn.merge_gemm_layer) { + (this->*gemm_layer_func)('N', 'N', rnn.n_gates * rnn.dic, rnn.mb, + rnn.slc, 1.0, w_layer_[0], rnn.weights_layer_ld, + states_t_lm1_, rnn.states_ws_ld, 0.0, ws_gates_, + rnn.gates_ws_ld); + } + (this->*gemm_iter_func)('N', 'N', rnn.n_gates * rnn.dic, rnn.mb, rnn.sic, + 1.0, w_iter_[0], rnn.weights_iter_ld, states_tm1_l_, + rnn.states_ws_ld, 1.0, ws_gates_, rnn.gates_ws_ld); + + if (rnn_postgemm_ != nullptr) + rnn_postgemm_->execute(rnn, ws_gates_, states_t_l_, c_states_t_l_, + states_tm1_l_, c_states_tm1_l_, diff_states_t_l_, + diff_states_t_lp1_, diff_states_tp1_l_, bias_[0], ws_grid_, + ws_cell_); + else + (this->*elemwise_func)(rnn, ws_gates_, states_t_l_, c_states_t_l_, + states_tm1_l_, c_states_tm1_l_, diff_states_t_l_, + diff_states_t_lp1_, diff_states_tp1_l_, bias_[0], ws_grid_, + ws_cell_); +} +template rnn_cell_execution_sig(ref_rnn_fwd_f32_t::cell_execution); +template rnn_cell_execution_sig(ref_rnn_fwd_u8s8_t::cell_execution); + +template <> +rnn_cell_execution_sig(ref_rnn_bwd_f32_t::cell_execution) { + ws_diff_states_aoc_t diff_states_t_l(rnn, diff_states_t_l_); + (this->*elemwise_func)(rnn, ws_gates_, states_t_l_, c_states_t_l_, + states_tm1_l_, c_states_tm1_l_, diff_states_t_l_, + diff_states_t_lp1_, diff_states_tp1_l_, bias_[0], ws_grid_, + ws_cell_); + + /// bwd by data on the cell + (this->*gemm_iter_func)('N', 'N', rnn.sic, rnn.mb, rnn.n_gates * rnn.dic, + 1.0, w_iter_[0], rnn.weights_iter_ld, ws_gates_, rnn.gates_ws_ld, + 0.0, diff_states_t_l_, rnn.states_ws_ld); + + if (!rnn.merge_gemm_layer) { + (this->*gemm_layer_func)('N', 'N', rnn.slc, rnn.mb, + rnn.n_gates * rnn.dic, 1.0, w_layer_[0], + rnn.weights_layer_ld, ws_gates_, rnn.gates_ws_ld, 0.0, + &diff_states_t_l(rnn.n_states, 0, 0), rnn.states_ws_ld); + + /// bwd by weights on the cell + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.slc, rnn.mb, 1.0, ws_gates_, + rnn.gates_ws_ld, states_t_lm1_, rnn.states_ws_ld, 1.0, + diff_w_layer_, rnn.diff_weights_layer_ld); + } + + if (!rnn.merge_gemm_iter) + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.sic, rnn.mb, 1.0, ws_gates_, + rnn.gates_ws_ld, states_tm1_l_, rnn.states_ws_ld, 1.0, + diff_w_iter_, rnn.diff_weights_iter_ld); + + /// bwd by bias we just accumulate diffs from the gates + gates_reduction(rnn, ws_gates_, diff_bias_); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru.cpp new file mode 100644 index 000000000000..e1a61d4c6233 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru.cpp @@ -0,0 +1,180 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + * Cell execution GRU + */ + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "ref_rnn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::math; +using namespace rnn_utils; + +#define AOC array_offset_calculator +template <> +rnn_cell_execution_sig(ref_rnn_fwd_f32_t::cell_execution_gru) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_[0]); + ws_states_aoc_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t states_tm1_l(rnn, states_tm1_l_); + + // 1. gemm Wx[0-2],x + if (!rnn.merge_gemm_layer) { + (this->*gemm_layer_func)('N', 'N', rnn.n_gates * rnn.dic, rnn.mb, + rnn.slc, 1.0, w_layer_[0], rnn.weights_layer_ld, + states_t_lm1_, rnn.states_ws_ld, 0.0, ws_gates_, + rnn.gates_ws_ld); + } + + // 2. gemm Wh[0-1],h + (this->*gemm_iter_func)('N', 'N', (rnn.n_gates - 1) * rnn.dic, rnn.mb, + rnn.sic, 1.0, w_iter_[0], rnn.weights_iter_ld, states_tm1_l_, + rnn.states_ws_ld, 1.0, ws_gates_, rnn.gates_ws_ld); + + // 3. activation zt and rt + elemwise multiplication rt,ht-1 + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + ws_gates(i, 0, j) = logistic_fwd(ws_gates(i, 0, j) + bias(0, j)); + ws_gates(i, 1, j) = logistic_fwd(ws_gates(i, 1, j) + bias(1, j)); + states_t_l(i, j) = states_tm1_l(i, j) * ws_gates(i, 1, j); + } + }); + + // 4. gemm Wh[2],h~t + (this->*gemm_iter_func)('N', 'N', rnn.dic, rnn.mb, rnn.sic, 1.0, w_iter_[1], + rnn.weights_iter_ld, states_t_l_, rnn.states_ws_ld, 1.0, + &(ws_gates(0, 2, 0)), rnn.gates_ws_ld); + + // 5. activation h~t + calculate ht + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + ws_gates(i, 2, j) = tanh_fwd(ws_gates(i, 2, j) + bias(2, j)); + states_t_l(i, j) = states_tm1_l(i, j) * ws_gates(i, 0, j) + + (1.0f - ws_gates(i, 0, j)) * ws_gates(i, 2, j); + } + }); +} + +template <> +rnn_cell_execution_sig(ref_rnn_fwd_u8s8_t::cell_execution_gru) { + assert(!"GRU int8 is not supported"); +} + +template <> +rnn_cell_execution_sig(ref_rnn_bwd_f32_t::cell_execution_gru) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + ws_states_aoc_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t states_tm1_l(rnn, states_tm1_l_); + ws_diff_w_iter_aoc_t diff_w_iter(rnn, diff_w_iter_); + ws_diff_states_aoc_t diff_states_t_l(rnn, diff_states_t_l_); + ws_diff_states_aoc_t diff_states_tp1_l(rnn, diff_states_tp1_l_); + ws_diff_states_aoc_t diff_states_t_lp1(rnn, diff_states_t_lp1_); + + // use state memory for intermediate computations + // TODO: use cell ws for that + float *dhG1_ = &(diff_states_t_l(rnn.n_states, 0, 0)); + float *hG1_ = dhG1_; + AOC dhG1(dhG1_, rnn.states_nld, rnn.states_ws_ld); + AOC hG1(hG1_, rnn.states_nld, rnn.states_ws_ld); + + // 1. calculate dG2, dG1, and part of dht-1 + // dG2^ = dh * (1 - G0) * (1 - G2^2) + // dG0^ = dh * (ht-1 - G2) * u * (1 - G0) + // dht-1 (part) = dh * G0 + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + float h = states_tm1_l(i, j); + float dHt = diff_states_tp1_l(0, i, j) + + diff_states_t_lp1(rnn.n_states, i, j); + float dG2 = (1.0f - ws_gates(i, 0, j)) * dHt + * one_m_square(ws_gates(i, 2, j)); + float dG0 = (h - ws_gates(i, 2, j)) * dHt + * x_m_square(ws_gates(i, 0, j)); + + diff_states_t_l(0, i, j) = dHt * ws_gates(i, 0, j); + ws_gates(i, 0, j) = dG0; + ws_gates(i, 2, j) = dG2; + } + }); + + // 2. calculate intermediate d(hG1) + // d(hG1) = dG2 * W2h^t + (this->*gemm_iter_func)('N', 'N', rnn.sic, rnn.mb, rnn.dic, 1.0, w_iter_[1], + rnn.weights_iter_ld, &(ws_gates(0, 2, 0)), rnn.gates_ws_ld, 0.0, + dhG1_, rnn.states_ws_ld); + + // 3. calculate dG1^ and part of dht-1 + // dG1^ = d(hG1) * h * G1 * (1 - G1) + // dht-1 (part) += d(hG1) * G1 + // h * G1 (required for dWh) + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + float h = states_tm1_l(i, j); + float G1 = ws_gates(i, 1, j); + diff_states_t_l(0, i, j) += dhG1(i, j) * G1; + ws_gates(i, 1, j) = dhG1(i, j) * h * x_m_square(G1); + hG1(i, j) = G1 * h; + } + }); + + // 4. calculate diff weights + // dWh1 += dG1 * h, dWh2 += dG2 * h, dWh3 += dG3 * (G1(*)h) + gemm('N', 'T', (rnn.n_gates - 1) * rnn.dic, rnn.sic, rnn.mb, 1.0, ws_gates_, + rnn.gates_ws_ld, states_tm1_l_, rnn.states_ws_ld, 1.0, diff_w_iter_, + rnn.diff_weights_iter_ld); + gemm('N', 'T', rnn.dic, rnn.sic, rnn.mb, 1.0, &(ws_gates(0, 2, 0)), + rnn.gates_ws_ld, hG1_, rnn.states_ws_ld, 1.0, + &(diff_w_iter(0, 2, 0)), rnn.diff_weights_iter_ld); + + // 5. calculate diff states + // dht-1 += dG1 * W1h + dG0 * W0h + (this->*gemm_iter_func)('N', 'N', rnn.sic, rnn.mb, + (rnn.n_gates - 1) * rnn.dic, 1.0, w_iter_[0], + rnn.weights_iter_ld, ws_gates_, rnn.gates_ws_ld, 1.0, + diff_states_t_l_, rnn.states_ws_ld); + + if (!rnn.merge_gemm_layer) { + // dWx += [dG0 dG1 dG2] * [x] + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.slc, rnn.mb, 1.0, ws_gates_, + rnn.gates_ws_ld, states_t_lm1_, rnn.states_ws_ld, 1.0, + diff_w_layer_, rnn.diff_weights_layer_ld); + // dx = dG2 * W2x + dG1 * W1x + dG0 * W0x + (this->*gemm_layer_func)('N', 'N', rnn.slc, rnn.mb, + rnn.n_gates * rnn.dic, 1.0, w_layer_[0], + rnn.weights_layer_ld, ws_gates_, rnn.gates_ws_ld, 0.0, + &(diff_states_t_l(rnn.n_states, 0, 0)), rnn.states_ws_ld); + } + + // 6. calculate diff bias + gates_reduction(rnn, ws_gates_, diff_bias_); +} +#undef AOC + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru_lbr.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru_lbr.cpp new file mode 100644 index 000000000000..8dea8c90a402 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_gru_lbr.cpp @@ -0,0 +1,170 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + * Cell execution GRU with linear before reset + */ + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "ref_rnn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::math; +using namespace rnn_utils; +#define AOC array_offset_calculator + +template <> +rnn_elemwise_sig(ref_rnn_fwd_f32_t::gru_lbr_elemwise) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_); + ws_states_aoc_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t states_tm1_l(rnn, states_tm1_l_); + ws_gates_aoc_t ws_gemm_state(rnn, ws_cell_); + AOC ws_Wh_b(ws_grid_, rnn.mb, rnn.dic); + + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + float Wh_b = ws_gemm_state(i, 2, j) + bias(3, j); + ws_gates(i, 0, j) = logistic_fwd( + ws_gates(i, 0, j) + ws_gemm_state(i, 0, j) + bias(0, j)); + ws_gates(i, 1, j) = logistic_fwd( + ws_gates(i, 1, j) + ws_gemm_state(i, 1, j) + bias(1, j)); + ws_gates(i, 2, j) = tanh_fwd( + ws_gates(i, 2, j) + ws_gates(i, 1, j) * Wh_b + bias(2, j)); + states_t_l(i, j) = states_tm1_l(i, j) * ws_gates(i, 0, j) + + (1.0f - ws_gates(i, 0, j)) * ws_gates(i, 2, j); + if (rnn.is_training) + ws_Wh_b(i, j) = Wh_b; + } + }); +} + +template <> +rnn_elemwise_sig(ref_rnn_fwd_u8s8_t::gru_lbr_elemwise) { + assert(!"GRU LBR int8 is not supported"); +} + +template <> +rnn_cell_execution_sig(ref_rnn_fwd_f32_t::cell_execution_gru_lbr) { + if (!rnn.merge_gemm_layer) { + (this->*gemm_layer_func)('N', 'N', rnn.n_gates * rnn.dic, rnn.mb, + rnn.slc, 1.0, w_layer_[0], rnn.weights_layer_ld, + states_t_lm1_, rnn.states_ws_ld, 0.0, ws_gates_, + rnn.gates_ws_ld); + } + (this->*gemm_iter_func)('N', 'N', rnn.n_gates * rnn.dic, rnn.mb, rnn.sic, + 1.0, w_iter_[0], rnn.weights_iter_ld, states_tm1_l_, + rnn.states_ws_ld, 0.0, ws_cell_, rnn.gates_ws_ld); + (this->*elemwise_func)(rnn, ws_gates_, states_t_l_, c_states_t_l_, + states_tm1_l_, c_states_tm1_l_, diff_states_t_l_, + diff_states_t_lp1_, diff_states_tp1_l_, bias_[0], ws_grid_, + ws_cell_); +} + +template <> +rnn_cell_execution_sig(ref_rnn_fwd_u8s8_t::cell_execution_gru_lbr) { + assert(!"GRU LBR int8 is not supported"); +} + +template <> +rnn_elemwise_sig(ref_rnn_bwd_f32_t::gru_lbr_elemwise) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + ws_states_aoc_t states_tm1_l(rnn, states_tm1_l_); + ws_diff_states_aoc_t diff_states_t_l(rnn, diff_states_t_l_); + ws_diff_states_aoc_t diff_states_tp1_l(rnn, diff_states_tp1_l_); + ws_diff_states_aoc_t diff_states_t_lp1(rnn, diff_states_t_lp1_); + ws_gates_aoc_t ws_gates_r(rnn, ws_cell_); + AOC ws_Wh_b(ws_grid_, rnn.mb, rnn.dic); + + // 1. calculate dG1 dG2 dG3 + // dG0 = (dht - G2) * dht * (1 - G0) * G0 + // dG1 = (W*h + b) * dG2 * (1 - G1) * G1 + // dG2 = (1 - G0) * dht * (1 - G2*G2) + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + float h = states_tm1_l(i, j); + float dHt = diff_states_tp1_l(0, i, j) + + diff_states_t_lp1(rnn.n_states, i, j); + float dG0 = (h - ws_gates(i, 2, j)) * dHt + * x_m_square(ws_gates(i, 0, j)); + float dG2 = (1.0f - ws_gates(i, 0, j)) + * one_m_square(ws_gates(i, 2, j)) * dHt; + float dG1 = ws_Wh_b(i, j) * dG2 * x_m_square(ws_gates(i, 1, j)); + + diff_states_t_l(0, i, j) = dHt * ws_gates(i, 0, j); + ws_gates(i, 2, j) = dG2; + ws_gates_r(i, 2, j) = dG2 * ws_gates(i, 1, j); + ws_gates(i, 0, j) = ws_gates_r(i, 0, j) = dG0; + ws_gates(i, 1, j) = ws_gates_r(i, 1, j) = dG1; + } + }); +} + +template <> +rnn_cell_execution_sig(ref_rnn_bwd_f32_t::cell_execution_gru_lbr) { + ws_gates_aoc_t ws_gates_r(rnn, ws_cell_); + ws_diff_states_aoc_t diff_states_t_l(rnn, diff_states_t_l_); + + (this->*elemwise_func)(rnn, ws_gates_, states_t_l_, c_states_t_l_, + states_tm1_l_, c_states_tm1_l_, diff_states_t_l_, + diff_states_t_lp1_, diff_states_tp1_l_, bias_[0], ws_grid_, + ws_cell_); + + if (!rnn.merge_gemm_layer) { + // dx = dG * Wx^t + (this->*gemm_layer_func)('N', 'N', rnn.slc, rnn.mb, + rnn.n_gates * rnn.dic, 1.0, w_layer_[0], + rnn.weights_layer_ld, ws_gates_, rnn.gates_ws_ld, 0.0, + &diff_states_t_l(rnn.n_states, 0, 0), rnn.states_ws_ld); + // dWx += dG^t * x + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.slc, rnn.mb, 1.0, ws_gates_, + rnn.gates_ws_ld, states_t_lm1_, rnn.states_ws_ld, 1.0, + diff_w_layer_, rnn.diff_weights_layer_ld); + } + // dh += dGr * Wh^t + (this->*gemm_iter_func)('N', 'N', rnn.sic, rnn.mb, rnn.n_gates * rnn.dic, + 1.0, w_iter_[0], rnn.weights_iter_ld, ws_cell_, rnn.gates_ws_ld, + 1.0, diff_states_t_l_, rnn.states_ws_ld); + + // dWh += dGr^t * h + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.sic, rnn.mb, 1.0, ws_cell_, + rnn.gates_ws_ld, states_tm1_l_, rnn.states_ws_ld, 1.0, diff_w_iter_, + rnn.diff_weights_layer_ld); + + // db1-3 += e * dG + // db4 += e * (r * dG2) + gates_reduction(rnn, ws_gates_, diff_bias_); + + parallel_nd(rnn.dic, [&](int j) { + for (int i = 0; i < rnn.mb; i++) { + diff_bias_[3 * rnn.dic + j] += ws_gates_r(i, 2, j); + } + }); +} + +#undef AOC + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_lstm.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_lstm.cpp new file mode 100644 index 000000000000..a15ba00d4c4f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_lstm.cpp @@ -0,0 +1,143 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + * Cell execution LSTM + */ + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "../simple_q10n.hpp" +#include "ref_rnn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::math; +using namespace rnn_utils; + +template <> +rnn_elemwise_sig(ref_rnn_fwd_f32_t::lstm_elemwise) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_); + ws_states_aoc_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t c_states_t_l(rnn, c_states_t_l_); + ws_states_aoc_t c_states_tm1_l(rnn, c_states_tm1_l_); + + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + ws_gates(i, 0, j) = logistic_fwd(ws_gates(i, 0, j) + bias(0, j)); + ws_gates(i, 1, j) = logistic_fwd(ws_gates(i, 1, j) + bias(1, j)); + ws_gates(i, 2, j) = tanh_fwd(ws_gates(i, 2, j) + bias(2, j)); + ws_gates(i, 3, j) = logistic_fwd(ws_gates(i, 3, j) + bias(3, j)); + + float tmp = ws_gates(i, 1, j) * c_states_tm1_l(i, j) + + ws_gates(i, 0, j) * ws_gates(i, 2, j); + states_t_l(i, j) = ws_gates(i, 3, j) * tanh_fwd(tmp); + c_states_t_l(i, j) = tmp; + } + }); +} + +template <> +rnn_elemwise_sig(ref_rnn_fwd_u8s8_t::lstm_elemwise) { + ws_gates_aoc_s32_t ws_gates_s32(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_); + ws_states_aoc_u8_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t c_states_t_l(rnn, c_states_t_l_); + ws_states_aoc_t c_states_tm1_l(rnn, c_states_tm1_l_); + + float *weights_scales = pd()->attr()->rnn_weights_qparams_.scales_; + float data_shift = pd()->attr()->rnn_data_qparams_.shift_; + float data_scale = pd()->attr()->rnn_data_qparams_.scale_; + + auto q_d = [&](float f) { + float qf = f * data_scale + data_shift; + return qz_a1b0()(qf); + }; + + auto deq_w = [&](acc_data_t s, int gate, int j) { + return pd()->attr()->rnn_weights_qparams_.mask_ == 0 ? + saturate(s) * (1.f / (weights_scales[0] * data_scale)) : + saturate(s) * (1.f / (weights_scales[gate * rnn.dic + j] + * data_scale)); + }; + + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + float G0 = logistic_fwd( + deq_w(ws_gates_s32(i, 0, j), 0, j) + bias(0, j)); + float G1 = logistic_fwd( + deq_w(ws_gates_s32(i, 1, j), 1, j) + bias(1, j)); + float G2 = tanh_fwd( + deq_w(ws_gates_s32(i, 2, j), 2, j) + bias(2, j)); + float G3 = logistic_fwd( + deq_w(ws_gates_s32(i, 3, j), 3, j) + bias(3, j)); + float tmp = G1 * c_states_tm1_l(i, j) + G0 * G2; + states_t_l(i, j) = q_d(G3 * tanh_fwd(tmp)); + c_states_t_l(i, j) = tmp; + } + }); +} + +template <> +rnn_elemwise_sig(ref_rnn_bwd_f32_t::lstm_elemwise) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_); + ws_states_aoc_t c_states_t_l(rnn, c_states_t_l_); + ws_states_aoc_t c_states_tm1_l(rnn, c_states_tm1_l_); + ws_diff_states_aoc_t diff_states_t_l(rnn, diff_states_t_l_); + ws_diff_states_aoc_t diff_states_tp1_l(rnn, diff_states_tp1_l_); + ws_diff_states_aoc_t diff_states_t_lp1(rnn, diff_states_t_lp1_); + + parallel_nd(rnn.mb, [&](int i) { + PRAGMA_OMP_SIMD() + for (int j = 0; j < rnn.dic; j++) { + float Ct = c_states_t_l(i, j); + /// @todo save it in the workspace in fwd pass or recompute it to + /// save bw + float tanhCt = tanh_fwd(Ct); + // we have 2 incoming diffs on Ht + float dHt = diff_states_tp1_l(0, i, j) + + diff_states_t_lp1(rnn.n_states, i, j); + float dCt = diff_states_tp1_l(1, i, j) + + one_m_square(tanhCt) * ws_gates(i, 3, j) * dHt; + + float dG1 = c_states_tm1_l(i, j) * dCt + * x_m_square(ws_gates(i, 1, j)); + float dG0 = ws_gates(i, 2, j) * dCt * x_m_square(ws_gates(i, 0, j)); + float dG3 = tanhCt * dHt * x_m_square(ws_gates(i, 3, j)); + float dG2 + = ws_gates(i, 0, j) * dCt * one_m_square(ws_gates(i, 2, j)); + + diff_states_t_l(1, i, j) = dCt * ws_gates(i, 1, j); + + ws_gates(i, 0, j) = dG0; + ws_gates(i, 1, j) = dG1; + ws_gates(i, 2, j) = dG2; + ws_gates(i, 3, j) = dG3; + } + }); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_rnn.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_rnn.cpp new file mode 100644 index 000000000000..4536e8dfad7c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cell_rnn.cpp @@ -0,0 +1,113 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + * Cell execution of Vanilla RNN + */ + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "ref_rnn.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::math; +using namespace rnn_utils; + +template <> +float activation( + float dd, float s, float alpha, float cliping) { + return relu_fwd(s, alpha); +} + +template <> +float activation( + float dd, float s, float alpha, float cliping) { + return relu_bwd(dd, s, alpha); +} + +template <> +float activation( + float dd, float s, float alpha, float cliping) { + return tanh_fwd(s); +} + +template <> +float activation( + float dd, float s, float alpha, float cliping) { + return dd * one_m_square(s); +} + +template <> +float activation( + float dd, float s, float alpha, float cliping) { + return logistic_fwd(s); +} + +template <> +float activation( + float dd, float s, float alpha, float cliping) { + return dd * x_m_square(s); +} + +template <> +rnn_elemwise_sig(ref_rnn_fwd_f32_t::rnn_elemwise) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_); + ws_states_aoc_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t states_tm1_l(rnn, states_tm1_l_); + + parallel_nd(rnn.mb, [&](int i) { + for (int j = 0; j < rnn.dic; j++) { + const float h + = activation_func(0, ws_gates(i, 0, j) + bias(0, j), 0, 0); + ws_gates(i, 0, j) = states_t_l(i, j) = h; + } + }); +} + +template <> +rnn_elemwise_sig(ref_rnn_fwd_u8s8_t::rnn_elemwise) { + assert(!"VANILLA RNN int8 is not supported"); +} + +template <> +rnn_elemwise_sig(ref_rnn_bwd_f32_t::rnn_elemwise) { + ws_gates_aoc_t ws_gates(rnn, ws_gates_); + bias_aoc_t bias(rnn, bias_); + ws_states_aoc_t states_t_l(rnn, states_t_l_); + ws_states_aoc_t states_tm1_l(rnn, states_tm1_l_); + ws_diff_states_aoc_t diff_states_t_l(rnn, diff_states_t_l_); + ws_diff_states_aoc_t diff_states_tp1_l(rnn, diff_states_tp1_l_); + ws_diff_states_aoc_t diff_states_t_lp1(rnn, diff_states_t_lp1_); + + parallel_nd(rnn.mb, [&](int i) { + for (int j = 0; j < rnn.dic; ++j) { + const float dH = diff_states_t_lp1(rnn.n_states, i, j) + + diff_states_tp1_l(0, i, j); + auto g = ws_gates(i, 0, j); + ws_gates(i, 0, j) = activation_func(dH, g, 0, 0); + } + }); +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cpu_rnn_pd.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cpu_rnn_pd.hpp new file mode 100644 index 000000000000..b39427caf94d --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/cpu_rnn_pd.hpp @@ -0,0 +1,191 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_RNN_PD_HPP +#define CPU_RNN_PD_HPP + +#include "c_types_map.hpp" +#include "nstl.hpp" +#include "rnn_pd.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" +#include "rnn_utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct cpu_rnn_fwd_pd_t : public rnn_fwd_pd_t { + using rnn_fwd_pd_t::rnn_fwd_pd_t; + +protected: + status_t set_default_params() { + using namespace format_tag; + if (src_layer_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(src_layer_md_, tnc)); + if (dst_layer_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(dst_layer_md_, tnc)); + + // Optional parameters + if (with_src_iter() && src_iter_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(src_iter_md_, ldsnc)); + if (with_bias() && bias_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(bias_md_, ldgo)); + if (with_dst_iter() && dst_iter_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(dst_iter_md_, ldsnc)); + + return status::success; + } + + status_t check_layout_consistency() { + using namespace format_tag; + using namespace data_type; + using namespace types; + + auto is_blocked = [&](memory_desc_t md, int ndims) { + return md.format_kind == format_kind::blocked && md.ndims == ndims; + }; + + bool ok = true; + ok = ok && is_blocked(src_layer_md_, 3) + && is_blocked(dst_layer_md_, 3); + ok = ok && IMPLICATION(!is_zero_md(&src_iter_md_), + is_blocked(src_iter_md_, 5)) + && IMPLICATION(!is_zero_md(&dst_iter_md_), + is_blocked(dst_iter_md_, 5)); + + if (weights_layer_md_.format_kind == format_kind::rnn_packed) + ok = ok && (weights_layer_md_.format_desc.rnn_packed_desc.format + == mkldnn_ldigo_p); + else + ok = ok && rnn_utils::is_ldigo(&weights_layer_md_); + + if (weights_iter_md_.format_kind == format_kind::rnn_packed) + ok = ok && (weights_iter_md_.format_desc.rnn_packed_desc.format + == mkldnn_ldigo_p); + else + ok = ok && rnn_utils::is_ldigo(&weights_iter_md_); + + ok = ok && IMPLICATION(!is_zero_md(&bias_md_), + memory_desc_matches_tag(bias_md_, ldgo)); + + /* Int8 is supported only for packed weights */ + data_type_t weights_iter_dt = weights_iter_md_.data_type; + data_type_t weights_layer_dt = weights_layer_md_.data_type; + ok = ok && IMPLICATION( + weights_iter_dt == s8, weights_iter_md_.format_kind + == format_kind::rnn_packed); + ok = ok && IMPLICATION( + weights_layer_dt == s8, weights_layer_md_.format_kind + == format_kind::rnn_packed); + + return ok ? status::success : status::unimplemented; + } +}; + +struct cpu_rnn_bwd_pd_t : public rnn_bwd_pd_t { + using rnn_bwd_pd_t::rnn_bwd_pd_t; + +protected: + status_t set_default_params() { + using namespace format_tag; + if (src_layer_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(src_layer_md_, tnc)); + if (dst_layer_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(dst_layer_md_, tnc)); + + if (diff_src_layer_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_src_layer_md_, tnc)); + if (diff_weights_layer_md_.format_kind == format_kind::any) { + CHECK(memory_desc_init_by_tag(diff_weights_layer_md_, ldigo)); + CHECK(rnn_utils::set_good_strides(diff_weights_layer_md_, ldigo)); + } + if (diff_weights_iter_md_.format_kind == format_kind::any) { + CHECK(memory_desc_init_by_tag(diff_weights_iter_md_, ldigo)); + CHECK(rnn_utils::set_good_strides(diff_weights_iter_md_, ldigo)); + } + if (diff_dst_layer_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_dst_layer_md_, tnc)); + + // Optional parameters + if (with_src_iter() && src_iter_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(src_iter_md_, ldsnc)); + if (with_bias() && bias_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(bias_md_, ldgo)); + if (with_dst_iter() && dst_iter_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(dst_iter_md_, ldsnc)); + + if (with_src_iter() && diff_src_iter_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_src_iter_md_, ldsnc)); + if (with_bias() && diff_bias_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_bias_md_, ldgo)); + if (with_dst_iter() && diff_dst_iter_md_.format_kind == format_kind::any) + CHECK(memory_desc_init_by_tag(diff_dst_iter_md_, ldsnc)); + + return status::success; + } + + status_t check_layout_consistency() { + using namespace format_tag; + using namespace types; + + auto is_blocked = [&](memory_desc_t md, int ndims) { + return md.format_kind == format_kind::blocked && md.ndims == ndims; + }; + + bool ok = true; + ok = ok && is_blocked(src_layer_md_, 3) + && is_blocked(dst_layer_md_, 3); + ok = ok && IMPLICATION(!is_zero_md(&src_iter_md_), + is_blocked(src_iter_md_, 5)) + && IMPLICATION(!is_zero_md(&dst_iter_md_), + is_blocked(dst_iter_md_, 5)); + + if (weights_layer_md_.format_kind == format_kind::rnn_packed) + ok = ok && (weights_layer_md_.format_desc.rnn_packed_desc.format + == mkldnn_ldgoi_p); + else + ok = ok && rnn_utils::is_ldgoi(&weights_layer_md_); + + if (weights_iter_md_.format_kind == format_kind::rnn_packed) + ok = ok && (weights_iter_md_.format_desc.rnn_packed_desc.format + == mkldnn_ldgoi_p); + else + ok = ok && rnn_utils::is_ldgoi(&weights_iter_md_); + + ok = ok && IMPLICATION(!is_zero_md(&bias_md_), + memory_desc_matches_tag(bias_md_, ldgo)); + + ok = ok && is_blocked(diff_src_layer_md_, 3) + && is_blocked(diff_dst_layer_md_, 3); + ok = ok && IMPLICATION(!is_zero_md(&diff_src_iter_md_), + is_blocked(diff_src_iter_md_, 5)) + && IMPLICATION(!is_zero_md(&diff_dst_iter_md_), + is_blocked(diff_dst_iter_md_, 5)); + + ok = ok && rnn_utils::is_ldigo(&diff_weights_layer_md_) + && rnn_utils::is_ldigo(&diff_weights_iter_md_); + ok = ok && IMPLICATION(!is_zero_md(&diff_bias_md_), + memory_desc_matches_tag(diff_bias_md_, ldgo)); + + return ok ? status::success : status::unimplemented; + } +}; +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/jit_uni_rnn_postgemm.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/jit_uni_rnn_postgemm.hpp new file mode 100644 index 000000000000..09445648aa97 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/jit_uni_rnn_postgemm.hpp @@ -0,0 +1,401 @@ +/******************************************************************************* +* Copyright 2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + * Cell execution LSTM + */ + +#include "rnn_utils.hpp" +#include "../jit_generator.hpp" +#include "../jit_uni_eltwise.hpp" +#include "c_types_map.hpp" +#include "utils.hpp" + +#include "mkldnn_thread.hpp" + + +namespace mkldnn { +namespace impl { +namespace cpu { + +struct jit_uni_rnn_postgemm_kernel : public jit_generator { + + typedef void (*kernel_t)(void *gates_, const void *bias, void *states_t_l_, + void *c_states_t_l_, void *c_states_tm1_l_); + + jit_uni_rnn_postgemm_kernel(const rnn_utils::rnn_conf_t &rnn, const primitive_attr_t *attr): rnn_(rnn), attr_(attr){} + + virtual void init() = 0; + +template + rnn_elemwise_sig(execute) { + rnn_utils::ws_gates_aoc ws_gates(rnn, ws_gates_); + rnn_utils::bias_aoc_t bias(rnn, bias_); + rnn_utils::ws_states_aoc states_t_l(rnn, states_t_l_); + rnn_utils::ws_states_aoc_t c_states_t_l(rnn, c_states_t_l_); + rnn_utils::ws_states_aoc_t c_states_tm1_l(rnn, c_states_tm1_l_); + + // Todo: add parallelization on dic for the batch 1 case + // Assumption: the kernel runs a loop on dic elements + parallel_nd(rnn.mb, [&](int i) { + auto b_ = &bias(0, 0); + auto g_ = &ws_gates(i, 0, 0); + auto s_tl_ = &states_t_l(i, 0); + auto c_tl_ = &c_states_t_l(i, 0); + auto c_tm1l_ = &c_states_tm1_l(i, 0); + kernel_(g_, b_, s_tl_, c_tm1l_, c_tl_); + }); + } + +protected: + kernel_t kernel_; + const rnn_utils::rnn_conf_t &rnn_; + const primitive_attr_t *attr_; +}; + +template +struct jit_uni_lstm_postgemm_kernel_fwd: public jit_uni_rnn_postgemm_kernel +{ + DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_uni_lstm_postgemm_kernel_fwd) + + typedef typename utils::conditional::type acc_data_t; + typedef typename utils::conditional, + jit_uni_eltwise_injector_f32>::type injector_t; + + jit_uni_lstm_postgemm_kernel_fwd(const rnn_utils::rnn_conf_t &rnn, const primitive_attr_t *attr) + : jit_uni_rnn_postgemm_kernel(rnn, attr){} + + void init() override { + // we use rax for both constant tables as they use the same table + sigmoid_injector_ = new injector_t(this, + alg_kind::eltwise_logistic, 0.0f, 0.0f, true, rax); + tanh_injector_ = new injector_t(this, + alg_kind::eltwise_tanh, 0.0f, 0.0f, true, rax); + generate(); + kernel_ = (kernel_t) this->getCode(); + } + +protected: + injector_t *sigmoid_injector_; + injector_t *tanh_injector_; + + // register size in bytes + using Vmm = typename jit_uni_eltwise_injector_f32::Vmm; + size_t vlen = cpu_isa_traits::vlen; + size_t vlen_dst = (src_data_t == data_type::u8) ? vlen/4 : vlen; + size_t cstate_dt_size = sizeof(float); + size_t hstate_dt_size = (src_data_t == data_type::u8) ? sizeof(uint8_t) : sizeof(float); + size_t gate_dt_size = (src_data_t == data_type::u8) ? sizeof(uint32_t) : sizeof(float); + size_t qscale_dt_size = sizeof(float); + size_t bias_dt_size = sizeof(float); + + void generate() { + using namespace Xbyak; + + int mask = attr_->rnn_weights_qparams_.mask_; + float *weights_scales = attr_->rnn_weights_qparams_.scales_; + float data_scale = attr_->rnn_data_qparams_.scale_; + float data_shift = attr_->rnn_data_qparams_.shift_; + + // Labels declaration + Label vector_loop_start_label, vector_loop_end_label; + Label rem_loop_start_label, rem_loop_end_label; + Label table_label; + + // Register map + Reg64 loop_cnt(r11); // loop counter + Reg64 table_reg(rbx); // table is used for data scale and shifts + Reg64 weights_scales_reg(r13); + // We skip vmm0 as it can be used by the injector for masks on sse4.2 + Vmm G0(1), G1(2), G2(3), G3(4), tmp1_vmm(5), tmp2_vmm(6), zero_vmm(7); + + // constant table map + Address dscale_off_addr = ptr[table_reg]; + Address dshift_off_addr = ptr[table_reg + vlen]; + Address ymm_perm_mask_addr = ptr[table_reg + 2*vlen]; + Address zmm_perm_mask_addr = ptr[table_reg + 2*vlen + cpu_isa_traits::vlen]; + + // quantize from float to u8 + auto q_d = [&](Vmm f, Vmm tmp_vmm) { + uni_vpxor(tmp_vmm, tmp_vmm, tmp_vmm); + uni_vmulps(f, f, dscale_off_addr); // apply scale + uni_vaddps(f, f, dshift_off_addr); // apply shift + uni_vcvtps2dq(f, f); // convert to int32 + uni_vpackssdw(f, f, tmp_vmm); // convert from s32 to s16 + uni_vpackuswb(f, f, tmp_vmm); // convert from s16 to u8 with saturation + // Note that the results are interleaved by 128 bit chunks, so we need to merge them together + switch (vlen) { + case 64: { //avx512 + Zmm fz(f.getIdx()), tmpz(tmp_vmm.getIdx()); + uni_vmovups(tmpz, zmm_perm_mask_addr); + vpermd(fz, tmpz, fz); + break; } + case 32: { //avx + Ymm fy(f.getIdx()), tmpy(tmp_vmm.getIdx()); + uni_vmovups(tmpy, ymm_perm_mask_addr); + vpermd(fy, tmpy, fy); + break; } + case 16: // sse: nothing to do + break; + default: assert(!"Unsupported case"); + }; + }; + + auto fast_recip =[&](Vmm s, Vmm tmp, bool packed) { + if (packed) + uni_vrcpps(tmp, s); + else + uni_vrcpss(tmp, s); // prevent divide by zero + // we add one Newton iteration + uni_vmulps(s, s, tmp); + uni_vmulps(s, s, tmp); // s <- s * tmp^2 + uni_vaddps(tmp, tmp, tmp); + uni_vsubps(tmp, tmp, s); + uni_vmovups(s, tmp); // s <- 2 * tmp - s * tmp^2 + }; + + // dequantize from s32 to float + auto deq_w = [&](Vmm s, Vmm tmp1, Vmm tmp2, int gate, bool packed) { + // TODO: if mask is 0 precompute mul and inverse + if (mask == 0) + uni_vbroadcastss(tmp1, ptr[weights_scales_reg]); + else + uni_vmovups(tmp1, ptr[weights_scales_reg + gate * rnn_.dic * qscale_dt_size]); + uni_vcvtdq2ps(s, s); + uni_vmulps(tmp1, tmp1, dscale_off_addr); + fast_recip(tmp1, tmp2, packed); + uni_vmulps(s, s, tmp1); + }; + + // We start code generations here + preamble(); + + // extract addresses passed as parameter +#ifdef _WIN32 + auto addr_ws_gates_reg = abi_param1; + auto addr_bias_reg = abi_param2; + auto addr_states_t_l_reg = abi_param3; + auto addr_c_states_tm1_l_reg = abi_param4; + auto addr_c_states_t_l_reg = r10; + // Here we cannot use rbp to have initial stack pointer so we + // use rsp and offset it with the size of pushed registers in + // preamble + mov(addr_c_states_t_l_reg, ptr[rsp + get_size_of_abi_save_regs() + 40]); +#else + auto addr_ws_gates_reg = abi_param1; + auto addr_bias_reg = abi_param2; + auto addr_states_t_l_reg = abi_param3; + auto addr_c_states_tm1_l_reg = abi_param4; + auto addr_c_states_t_l_reg = abi_param5; +#endif + + // initialize registers with addresses and constants + mov(table_reg, table_label); + mov(weights_scales_reg, size_t(weights_scales)); + // both sigmoid and tanh use the same table so load address just once in rax + sigmoid_injector_->load_table_addr(); + + mov(loop_cnt, rnn_.dic * gate_dt_size); + cmp(loop_cnt, vlen); + jl(vector_loop_end_label, Xbyak::CodeGenerator::T_NEAR); + + L(vector_loop_start_label); + { + // load G0 G1 G2 G3 + uni_vmovups(G0, ptr[addr_ws_gates_reg + 0 * rnn_.dic * gate_dt_size]); + uni_vmovups(G1, ptr[addr_ws_gates_reg + 1 * rnn_.dic * gate_dt_size]); + uni_vmovups(G2, ptr[addr_ws_gates_reg + 2 * rnn_.dic * gate_dt_size]); + uni_vmovups(G3, ptr[addr_ws_gates_reg + 3 * rnn_.dic * gate_dt_size]); + + // dequantize the gates from s32 to f32 if needed + if (src_data_t == data_type::u8){ + deq_w(G0, tmp1_vmm, tmp2_vmm, 0, true); + deq_w(G1, tmp1_vmm, tmp2_vmm, 1, true); + deq_w(G2, tmp1_vmm, tmp2_vmm, 2, true); + deq_w(G3, tmp1_vmm, tmp2_vmm, 3, true); + } + + // add biases + uni_vaddps(G0, G0, ptr[addr_bias_reg + 0 * rnn_.dic * bias_dt_size]); + uni_vaddps(G1, G1, ptr[addr_bias_reg + 1 * rnn_.dic * bias_dt_size]); + uni_vaddps(G2, G2, ptr[addr_bias_reg + 2 * rnn_.dic * bias_dt_size]); + uni_vaddps(G3, G3, ptr[addr_bias_reg + 3 * rnn_.dic * bias_dt_size]); + + // inject eltwise code + sigmoid_injector_->compute_vector(G0.getIdx()); + sigmoid_injector_->compute_vector(G1.getIdx()); + tanh_injector_->compute_vector(G2.getIdx()); + sigmoid_injector_->compute_vector(G3.getIdx()); + + // compute c_states_t_l = G1 * c_tm1_l + G0 * G2 + uni_vmovups(tmp1_vmm, ptr[addr_c_states_tm1_l_reg]); + uni_vmulps(tmp1_vmm, tmp1_vmm, G1); + uni_vfmadd231ps(tmp1_vmm, G0, G2); + uni_vmovups(ptr[addr_c_states_t_l_reg], tmp1_vmm); + + // states_t_l = G3 * tanh(c_states_t_l) + tanh_injector_->compute_vector(tmp1_vmm.getIdx()); + uni_vmulps(tmp1_vmm, tmp1_vmm, G3); + + // if int8, we quantize the resulting state + if (src_data_t == data_type::u8) + q_d(tmp1_vmm, tmp2_vmm); + + // write back the result + if(vlen_dst == vlen) + uni_vmovups(ptr[addr_states_t_l_reg], tmp1_vmm); + else + // we write only 1/4 of the register + switch(vlen_dst){ + case 16: uni_vmovups(ptr[addr_states_t_l_reg], Xmm(tmp1_vmm.getIdx())); break; + case 8: uni_vmovsd(ptr[addr_states_t_l_reg], Xmm(tmp1_vmm.getIdx())); break; + case 4: uni_vmovss(ptr[addr_states_t_l_reg], Xmm(tmp1_vmm.getIdx())); break; + default: + assert(!"Unsuported vector length for quantization"); + } + + // increment address pointers + add(addr_ws_gates_reg, vlen); + add(addr_bias_reg, vlen); + add(addr_states_t_l_reg, vlen_dst); + add(addr_c_states_tm1_l_reg, vlen); + add(addr_c_states_t_l_reg, vlen); + if (mask != 0) + add(weights_scales_reg, vlen); + + // increment loop counter + sub(loop_cnt, vlen); + cmp(loop_cnt, vlen); + jge(vector_loop_start_label); + } + L(vector_loop_end_label); + + cmp(loop_cnt, 0); + je(rem_loop_end_label, Xbyak::CodeGenerator::T_NEAR); + // Same code as above, we just use movuss for accessing inputs + // TODO: smarter handling of tails with Zmm -> Ymm -> Xmm -> scalar + L(rem_loop_start_label); + { + // remaping registers to Xmms + Xmm G0s(G0.getIdx()), G1s(G1.getIdx()), G2s(G2.getIdx()), G3s(G3.getIdx()); + Xmm tmp1s_vmm(tmp1_vmm.getIdx()); + + // load G0 G1 G2 G3 + uni_vmovss(G0s, ptr[addr_ws_gates_reg + 0 * rnn_.dic * gate_dt_size]); + uni_vmovss(G1s, ptr[addr_ws_gates_reg + 1 * rnn_.dic * gate_dt_size]); + uni_vmovss(G2s, ptr[addr_ws_gates_reg + 2 * rnn_.dic * gate_dt_size]); + uni_vmovss(G3s, ptr[addr_ws_gates_reg + 3 * rnn_.dic * gate_dt_size]); + + // dequantize the gates from s32 to f32 if needed + if (src_data_t == data_type::u8){ + deq_w(G0, tmp1_vmm, tmp2_vmm, 0, false); + deq_w(G1, tmp1_vmm, tmp2_vmm, 1, false); + deq_w(G2, tmp1_vmm, tmp2_vmm, 2, false); + deq_w(G3, tmp1_vmm, tmp2_vmm, 3, false); + } + + // add biases + uni_vmovss(tmp1s_vmm, ptr[addr_bias_reg + 0 * rnn_.dic * bias_dt_size]); + uni_vaddps(G0s, G0s, tmp1s_vmm); + uni_vmovss(tmp1s_vmm, ptr[addr_bias_reg + 1 * rnn_.dic * bias_dt_size]); + uni_vaddps(G1s, G1s, tmp1s_vmm); + uni_vmovss(tmp1s_vmm, ptr[addr_bias_reg + 2 * rnn_.dic * bias_dt_size]); + uni_vaddps(G2s, G2s, tmp1s_vmm); + uni_vmovss(tmp1s_vmm, ptr[addr_bias_reg + 3 * rnn_.dic * bias_dt_size]); + uni_vaddps(G3s, G3s, tmp1s_vmm); + + // inject eltwise code + sigmoid_injector_->compute_vector(G0s.getIdx()); + sigmoid_injector_->compute_vector(G1s.getIdx()); + tanh_injector_->compute_vector(G2s.getIdx()); + sigmoid_injector_->compute_vector(G3s.getIdx()); + + // compute c_states_t_l = G1 * c_tm1_l + G0s * G2 + uni_vmovups(tmp1s_vmm, ptr[addr_c_states_tm1_l_reg]); + uni_vmulps(tmp1s_vmm, tmp1s_vmm, G1s); + uni_vfmadd231ps(tmp1s_vmm, G0s, G2s); + uni_vmovss(ptr[addr_c_states_t_l_reg], tmp1s_vmm); + + // states_t_l = G3 * tanh(c_states_t_l) + tanh_injector_->compute_vector(tmp1s_vmm.getIdx()); + uni_vmulps(tmp1s_vmm, tmp1s_vmm, G3s); + + // if int8, we quantize the resulting state + if (src_data_t == data_type::u8) + q_d(tmp1_vmm, tmp2_vmm); + + // write back the result + if(vlen_dst == vlen) + uni_vmovups(ptr[addr_states_t_l_reg], tmp1s_vmm); + else + // we write only 1/4 of the register + switch(vlen_dst){ + case 16: uni_vmovups(ptr[addr_states_t_l_reg], Xmm(tmp1s_vmm.getIdx())); break; + case 8: uni_vmovsd(ptr[addr_states_t_l_reg], Xmm(tmp1s_vmm.getIdx())); break; + case 4: uni_vmovss(ptr[addr_states_t_l_reg], Xmm(tmp1s_vmm.getIdx())); break; + default: + assert(!"Unsuported vector length for quantization"); + } + + // increment address pointers + add(addr_ws_gates_reg, gate_dt_size); + add(addr_bias_reg, bias_dt_size); + add(addr_states_t_l_reg, hstate_dt_size); + add(addr_c_states_tm1_l_reg, cstate_dt_size); + add(addr_c_states_t_l_reg, cstate_dt_size); + if (mask != 0) + add(weights_scales_reg, qscale_dt_size); + + // increment loop counter + sub(loop_cnt, gate_dt_size); + cmp(loop_cnt, 0); + jg(rem_loop_start_label); + + } + L(rem_loop_end_label); + + postamble(); + + // Again, only one table is needed and shared between sigmoid and tanh + sigmoid_injector_->prepare_table(false); + tanh_injector_->prepare_table(true); + + L(table_label); + { + for (size_t i = 0; i < vlen / sizeof(float); i++) dd(float2int(data_scale)); + for (size_t i = 0; i < vlen / sizeof(float); i++) dd(float2int(data_shift)); + // perm mask for ymm + dd(0); dd(4); dd(2); dd(3); dd(1); dd(5); dd(6); dd(7); + // perm mask for zmm + dd(0); dd(4); dd(8); dd(12); dd(1); dd(5); dd(6); dd(7); + dd(2); dd(9); dd(10); dd(11); dd(3); dd(12); dd(13); dd(14); + } + } + +}; + +template struct jit_uni_lstm_postgemm_kernel_fwd; +template struct jit_uni_lstm_postgemm_kernel_fwd; +template struct jit_uni_lstm_postgemm_kernel_fwd; + +template struct jit_uni_lstm_postgemm_kernel_fwd; +template struct jit_uni_lstm_postgemm_kernel_fwd; +template struct jit_uni_lstm_postgemm_kernel_fwd; +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.cpp new file mode 100644 index 000000000000..ead536816c1c --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.cpp @@ -0,0 +1,788 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* + General architecture + + for diff states, we have n_states + 1 as we have n_states diff + to propagate to the previous iteration and 1 states to propagate + to the previous layer + index 0 is dh for cell(t-1, l) to consume + index 1 is dc for cell(t-1, l) to consume + index 2 is dh for cell(t, l-1) to consume + this indexing enables to have the same indexing for states in elemwise + function + only the cell execution function should be impacted + + */ + +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "ref_rnn.hpp" +#include "../gemm/gemm.hpp" +#include "../simple_q10n.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace mkldnn::impl::memory_tracking::names; +using namespace rnn_utils; +#define AOC array_offset_calculator + +template +void _ref_rnn_common_t::gates_reduction( + const rnn_conf_t &rnn, const acc_data_t *ws_gates_, + float *diff_bias_) const { + auto body = [&](int i, int k) { + for (int j = 0; j < rnn.mb; j++) + diff_bias_[i * rnn.dic + k] + += ws_gates_[j * rnn.gates_ws_ld + i * rnn.dic + k]; + }; + + // @todo block k on simd-width +#if MKLDNN_THR == MKLDNN_THR_OMP && _OPENMP >= 201307 \ + /* icc 17.0 has a problem with simd collapse */ \ + && !((defined __INTEL_COMPILER) && (__INTEL_COMPILER == 1700)) +#pragma omp parallel for simd collapse(2) + for (int i = 0; i < rnn.n_gates; i++) + for (int k = 0; k < rnn.dic; k++) + body(i, k); +#else + parallel_nd(rnn.n_gates, rnn.dic, body); +#endif +} + +template +rnn_gemm_sig((_ref_rnn_common_t::gemm)) { + assert(ldA * ldB * ldC != 0); + extended_sgemm(&transA, &transB, &m, &n, &k, &alpha, a_, &ldA, b_, &ldB, + &beta, c_, &ldC, nullptr, pd()->rnn_.use_jit_gemm); +} + +template <> +rnn_gemm_sig((ref_rnn_fwd_u8s8_t::gemm)) { + assert(!"non packed gemm is disabled for int8"); +} + +template +rnn_gemm_sig((_ref_rnn_common_t::packed_gemm)) { +#if (USE_MKL_PACKED_GEMM) + assert(transA == 'N'); + cblas_sgemm_compute(CblasColMajor, CblasPacked, + (transB == 'T') ? CblasTrans : CblasNoTrans, m, n, k, a_, ldA, b_, + ldB, beta, c_, ldC); +#else + UNUSED(transA); + UNUSED(transB); + UNUSED(m); + UNUSED(n); + UNUSED(k); + UNUSED(alpha); + UNUSED(ldA); + UNUSED(b_); + UNUSED(ldB); + UNUSED(beta); + UNUSED(c_); + UNUSED(ldC); + assert(!"packed gemm is disabled"); +#endif +} + +template <> +rnn_gemm_sig((ref_rnn_fwd_u8s8_t::packed_gemm)) { +#if (USE_MKL_PACKED_GEMM) + int8_t offseta = 0, offsetb = 0; + int32_t offsetc = 0; + cblas_gemm_s8u8s32_compute(CblasColMajor, (CBLAS_TRANSPOSE)CblasPacked, + CblasNoTrans, CblasFixOffset, m, n, k, alpha, a_, ldA, offseta, b_, + ldB, offsetb, beta, c_, ldC, &offsetc); +#else + UNUSED(transA); + UNUSED(transB); + UNUSED(m); + UNUSED(n); + UNUSED(k); + UNUSED(alpha); + UNUSED(ldA); + UNUSED(b_); + UNUSED(ldB); + UNUSED(beta); + UNUSED(c_); + UNUSED(ldC); + assert(!"packed gemm is disabled"); +#endif +} + +//*************** Grid computations strategy: linear ***************// +template +rnn_grid_execution_sig( + (_ref_rnn_common_t::linear_execution)) { + AOC ws_states(ws_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.states_nld * rnn.states_ws_ld); + AOC ws_c_states(ws_c_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.states_nld * rnn.states_ws_ld); + AOC ws_diff_states(ws_diff_states_, rnn.n_layer + 1, rnn.n_dir, + (rnn.n_states + 1), rnn.n_iter + 1, + rnn.states_nld * rnn.states_ws_ld); + AOC ws_gates(ws_gates_, rnn.n_layer, rnn.n_dir, rnn.n_iter, + rnn.gates_nld * rnn.gates_ws_ld); + AOC weights_input( + weights_layer_, rnn.n_layer, rnn.n_dir, rnn.n_parts_weights_layer); + AOC weights_states( + weights_states_, rnn.n_layer, rnn.n_dir, rnn.n_parts_weights_iter); + AOC bias( + bias_, rnn.n_layer, rnn.n_dir, rnn.n_parts_bias); + AOC diff_weights_layer(diff_weights_layer_, rnn.n_layer, + rnn.n_dir, + rnn.diff_weights_layer_nld * rnn.diff_weights_layer_ld); + AOC diff_weights_iter(diff_weights_iter_, rnn.n_layer, rnn.n_dir, + rnn.diff_weights_iter_nld * rnn.diff_weights_iter_ld); + AOC diff_bias( + diff_bias_, rnn.n_layer, rnn.n_dir, rnn.n_bias * rnn.dic); + AOC ws_grid( + ws_grid_, rnn.n_layer, rnn.n_dir, rnn.n_iter, (int)rnn.ws_per_cell); + + // We run the grid of computation + for (int dir = 0; dir < rnn.n_dir; dir++) { + for (int j = 0; j < rnn.n_layer; j++) { + int lay = (aprop == prop_kind::forward) ? j : rnn.n_layer - j - 1; + + if ((aprop == prop_kind::forward) && rnn.merge_gemm_layer) { + (this->*gemm_layer_func)('N', 'N', rnn.n_gates * rnn.dic, + rnn.mb * rnn.n_iter, rnn.slc, 1.0, + weights_input(lay, dir, 0), rnn.weights_iter_ld, + &(ws_states(lay, dir, 1, 0)), rnn.states_ws_ld, 0.0, + &(ws_gates(lay, dir, 0, 0)), rnn.gates_ws_ld); + } + + for (int i = 0; i < rnn.n_iter; i++) { + int iter = (aprop == prop_kind::forward) ? i : rnn.n_iter - i - 1; + (this->*cell_func)(rnn, + &(ws_states(lay + 1, dir, iter + 1, 0)), + &(ws_c_states(lay + 1, dir, iter + 1, 0)), + &(ws_diff_states(lay, dir, 0, iter, 0)), + &(weights_input(lay, dir, 0)), + &(weights_states(lay, dir, 0)), + &(bias(lay, dir, 0)), + &(ws_states(lay, dir, iter + 1, 0)), + &(ws_states(lay + 1, dir, iter, 0)), + &(ws_c_states(lay + 1, dir, iter, 0)), + &(ws_diff_states(lay + 1, dir, 0, iter, 0)), + &(ws_diff_states(lay, dir, 0, iter + 1, 0)), + &(diff_weights_layer(lay, dir, 0)), + &(diff_weights_iter(lay, dir, 0)), + &(diff_bias(lay, dir, 0)), + &(ws_gates(lay, dir, iter, 0)), + &(ws_grid(lay, dir, iter, 0)), + ws_cell_); + } + + if ((aprop == prop_kind::backward) && rnn.merge_gemm_layer) { + (this->*gemm_layer_func)('N', 'N', rnn.slc, rnn.mb * rnn.n_iter, + rnn.n_gates * rnn.dic, 1.0, weights_input(lay, dir, 0), + rnn.weights_layer_ld, + (src_data_t *)(&(ws_gates(lay, dir, 0, 0))), + rnn.gates_ws_ld, 0.0, + (acc_data_t *)(&(ws_diff_states( + lay, dir, rnn.n_states, 0, 0))), + rnn.states_ws_ld); + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.slc, + rnn.mb * rnn.n_iter, 1.0, + (weights_data_t *)(&(ws_gates(lay, dir, 0, 0))), + rnn.gates_ws_ld, + (src_data_t *)(&(ws_states(lay, dir, 1, 0))), + rnn.states_ws_ld, 1.0, + (acc_data_t *)(&(diff_weights_layer(lay, dir, 0))), + rnn.diff_weights_layer_ld); + } + if ((aprop == prop_kind::backward) && rnn.merge_gemm_iter) { + gemm('N', 'T', rnn.n_gates * rnn.dic, rnn.sic, + rnn.mb * rnn.n_iter, 1.0, + (weights_data_t *)(&(ws_gates(lay, dir, 0, 0))), + rnn.gates_ws_ld, + (src_data_t *)(&(ws_states(lay + 1, dir, 0, 0))), + rnn.states_ws_ld, 1.0, + (acc_data_t *)(&(diff_weights_iter(lay, dir, 0))), + rnn.diff_weights_iter_ld); + } + } + } +} + +//********* GRID computations strategy: utility functions **********// + +template +void _ref_rnn_common_t::copy_init_layer( + const rnn_conf_t &rnn, src_data_t *__restrict ws_states_, + float *__restrict ws_diff_states_, const src_data_t *__restrict xt_, + const float *__restrict diff_dst_layer_) const { + + AOC ws_states( + ws_states_, rnn.n_dir, rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + auto xt_d = memory_desc_wrapper(pd()->src_md(0)); + + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + auto xxt = xt_ + xt_d.blk_off(it, b); + src_data_t *ws_l2r_ptr = &(ws_states(0, it + 1, b, 0)); + src_data_t *ws_r2l_ptr = &(ws_states(rnn.n_dir - 1, rnn.n_iter - it, b, 0)); + if (rnn.exec_dir != r2l) + for (int c = 0; c < rnn.slc; c++) + ws_l2r_ptr[c] = xxt[c]; + if (rnn.exec_dir != l2r) + for (int c = 0; c < rnn.slc; c++) + ws_r2l_ptr[c] = xxt[c]; + }); +} + +template <> +void ref_rnn_bwd_f32_t::copy_init_layer(const rnn_conf_t &rnn, + src_data_t *ws_states_, float *ws_diff_states_, const src_data_t *xt_, + const float *diff_dst_layer_) const { + AOC ws_diff_states(ws_diff_states_, rnn.n_layer + 1, rnn.n_dir, + (rnn.n_states + 1), rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + auto diff_dst_layer_d = memory_desc_wrapper(pd()->diff_dst_md(0)); + + switch (rnn.exec_dir) { + case bi_concat: + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + auto diff_dst_layer_x + = diff_dst_layer_ + diff_dst_layer_d.blk_off(it, b); + for (int s = 0; s < rnn.dic; s++) { + ws_diff_states(rnn.n_layer, 0, rnn.n_states, it, b, s) + = diff_dst_layer_x[s]; + ws_diff_states( + rnn.n_layer, 1, rnn.n_states, rnn.n_iter - it - 1, b, s) + = diff_dst_layer_x[rnn.dic + s]; + } + }); + break; + case bi_sum: + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + auto diff_dst_layer_x + = diff_dst_layer_ + diff_dst_layer_d.blk_off(it, b); + for (int s = 0; s < rnn.dic; s++) { + ws_diff_states(rnn.n_layer, 0, rnn.n_states, it, b, s) + = diff_dst_layer_x[s]; + ws_diff_states( + rnn.n_layer, 1, rnn.n_states, rnn.n_iter - it - 1, b, s) + = diff_dst_layer_x[s]; + } + }); + break; + case l2r: + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + auto diff_dst_layer_x + = diff_dst_layer_ + diff_dst_layer_d.blk_off(it, b); + for (int s = 0; s < rnn.dic; s++) { + ws_diff_states(rnn.n_layer, 0, rnn.n_states, it, b, s) + = diff_dst_layer_x[s]; + } + }); + break; + case r2l: + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + auto diff_dst_layer_x = diff_dst_layer_ + + diff_dst_layer_d.blk_off(rnn.n_iter - it - 1, b); + for (int s = 0; s < rnn.dic; s++) { + ws_diff_states(rnn.n_layer, 0, rnn.n_states, it, b, s) + = diff_dst_layer_x[s]; + } + }); + break; + default: assert(!"Unsupported direction"); break; + } +} + +/* For int8 configuration, input iteration states may be of types f32 or u8 + * Internally h_state is always stored in u8 and c_state is always stored in f32 + * If input states are of type u8 then h state is copied and c state is dequantized + * If input states are of type f32 then h state is quantized and c_state is copied + * */ +template +template +void _ref_rnn_common_t::copy_init_iter( + const rnn_conf_t &rnn, src_data_t *__restrict ws_states_, + float *__restrict ws_c_states_, float *__restrict ws_diff_states_, + const input_data_t *__restrict firstit_states_, + const float *__restrict diff_dst_iter_) const { + AOC ws_states(ws_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + AOC ws_c_states(ws_c_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + float data_shift = pd()->attr()->rnn_data_qparams_.shift_; + float data_scale = pd()->attr()->rnn_data_qparams_.scale_; + + const bool quantize = pd()->with_src_iter() + && pd()->src_md(1)->data_type == data_type::f32 + && rnn.dt_conf != all_f32; + auto maybe_q = [&](input_data_t f) { + if (quantize) { + float qf = f * data_scale + data_shift; + return qz_a1b0()(qf); + } else + return (src_data_t)f; + }; + + const bool dequantize = pd()->with_src_iter() + && pd()->src_md(1)->data_type == data_type::u8; + auto maybe_deq = [&](input_data_t s) { + if (dequantize) + return (((float)s - data_shift) / data_scale); + else + return (float)s; + }; + auto firstit_states_d = memory_desc_wrapper(pd()->src_md(1)); + if (firstit_states_) { + parallel_nd( + rnn.n_layer, rnn.n_dir, rnn.mb, [&](int lay, int dir, int b) { + for (int s = 0; s < rnn.sic; s++) + ws_states(lay + 1, dir, 0, b, s) = maybe_q( + firstit_states_[firstit_states_d.blk_off( + lay, dir, 0, b, s)]); + if (pd()->cell_kind() == alg_kind::vanilla_lstm) + for (int s = 0; s < rnn.sic; s++) + ws_c_states(lay + 1, dir, 0, b, s) = maybe_deq( + firstit_states_[firstit_states_d.blk_off( + lay, dir, 1, b, s)]); + }); + } else { + parallel_nd( + rnn.n_layer, rnn.n_dir, rnn.mb, [&](int lay, int dir, int b) { + for (int j = 0; j < rnn.sic; j++) { + ws_states(lay + 1, dir, 0, b, j) = (src_data_t)0; + ws_c_states(lay + 1, dir, 0, b, j) = 0.0f; + } + }); + } +} + +template <> +template +void ref_rnn_bwd_f32_t::copy_init_iter(const rnn_conf_t &rnn, + src_data_t *ws_states_, float *ws_c_states_, float *ws_diff_states_, + const input_data_t *firstit_states_, + const float *diff_dst_iter_) const { + AOC ws_diff_states(ws_diff_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_states + 1, rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + auto diff_dst_iter_d = memory_desc_wrapper(pd()->diff_dst_md(1)); + if (diff_dst_iter_) { + parallel_nd(rnn.n_layer, rnn.n_dir, rnn.n_states, rnn.mb, + [&](int lay, int dir, int state, int b) { + array_copy(&(ws_diff_states( + lay, dir, state, rnn.n_iter, b, 0)), + diff_dst_iter_ + + diff_dst_iter_d.blk_off( + lay, dir, state, b), + rnn.dic); + }); + } else { + parallel_nd(rnn.n_layer, rnn.n_dir, rnn.n_states, rnn.mb, + [&](int lay, int dir, int state, int i) { + for (int j = 0; j < rnn.dic; j++) + ws_diff_states(lay, dir, state, rnn.n_iter, i, j) + = 0.0f; + }); + } +} + +template +template +void _ref_rnn_common_t::copy_res_layer( + const rnn_conf_t &rnn, dst_data_t *dst_layer_, float *diff_src_layer, + const src_data_t *ws_states_, const float *ws_diff_states_) const { + + auto dst_layer_d = memory_desc_wrapper(pd()->dst_md(0)); + AOC ws_states(ws_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + float shift = (pd()->attr()->rnn_data_qparams_.shift_); + float scale = (pd()->attr()->rnn_data_qparams_.scale_); + + const bool dequantize = pd()->dst_md(0)->data_type == data_type::f32 + && rnn.dt_conf != all_f32; + auto maybe_deq = [&](src_data_t s) { + if (dequantize) + return (dst_data_t)(((float)s - shift) / scale); + else + return (dst_data_t)s; + }; + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + int dir = 0; + if (rnn.exec_dir != r2l) { + for (int s = 0; s < rnn.dic; s++) { + dst_layer_[dst_layer_d.blk_off(it, b, dir * rnn.dic + s)] + = maybe_deq(ws_states(rnn.n_layer, dir, it + 1, b, s)); + } + dir = 1; + } + if (rnn.exec_dir != l2r) { + for (int s = 0; s < rnn.dic; s++) + switch (rnn.exec_dir) { + case bi_sum: + dst_layer_[dst_layer_d.blk_off(it, b, s)] + += maybe_deq(ws_states( + rnn.n_layer, dir, rnn.n_iter - it, b, s)); + break; + default: + dst_layer_[dst_layer_d.blk_off(it, b, dir * rnn.dic + s)] + = maybe_deq(ws_states( + rnn.n_layer, dir, rnn.n_iter - it, b, s)); + } + } + }); +} + +template <> +template +void ref_rnn_bwd_f32_t::copy_res_layer( + const rnn_conf_t &rnn, dst_data_t *dst_layer_, float *diff_src_layer_, + const src_data_t *ws_states_, const float *ws_diff_states_) const { + auto diff_src_layer_d = memory_desc_wrapper(pd()->diff_src_md(0)); + AOC ws_diff_states(ws_diff_states_, rnn.n_layer + 1, + rnn.n_dir, rnn.n_states + 1, rnn.n_iter + 1, rnn.mb, + rnn.states_ws_ld); + + parallel_nd(rnn.n_iter, rnn.mb, [&](int it, int b) { + int dir = 0; + for (int s = 0; s < rnn.slc; s++) { + float *dst_addr = diff_src_layer_ + + diff_src_layer_d.blk_off( + (rnn.exec_dir == r2l) ? rnn.n_iter - 1 - it : it, + b, dir * rnn.slc + s); + float res = ws_diff_states(0, 0, rnn.n_states, it, b, s); + if (rnn.n_dir - 1) + res += ws_diff_states( + 0, 1, rnn.n_states, rnn.n_iter - 1 - it, b, s); + dst_addr[0] = res; + } + }); +} + +template +template +void _ref_rnn_common_t::copy_res_iter( + const rnn_conf_t &rnn, output_data_t *dst_iter_, float *diff_src_iter_, + const src_data_t *ws_states_, float *ws_c_states_, + const float *ws_diff_states_) const { + auto dst_iter_d = memory_desc_wrapper(pd()->dst_md(1)); + AOC ws_states(ws_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + AOC ws_c_states(ws_c_states_, rnn.n_layer + 1, rnn.n_dir, + rnn.n_iter + 1, rnn.mb, rnn.states_ws_ld); + float data_shift = pd()->attr()->rnn_data_qparams_.shift_; + float data_scale = pd()->attr()->rnn_data_qparams_.scale_; + + const bool quantize = pd()->with_dst_iter() + && pd()->dst_md(1)->data_type == data_type::u8 + && rnn.dt_conf != all_f32; + auto maybe_q = [&](float f) { + if (quantize) { + float qf = f * data_scale + data_shift; + return qz_a1b0()(qf); + } else + return (output_data_t)f; + }; + + const bool dequantize = pd()->with_dst_iter() + && pd()->dst_md(1)->data_type == data_type::f32 + && rnn.dt_conf != all_f32; + auto maybe_deq = [&](src_data_t s) { + if (dequantize) + return (output_data_t)(((float)s - data_shift) / data_scale); + else + return (output_data_t)s; + }; + if (dst_iter_) { + parallel_nd(rnn.n_layer, rnn.n_dir, rnn.mb, + [&](int lay, int dir, int b) { + for (int s = 0; s < rnn.dic; s++) { + dst_iter_[dst_iter_d.blk_off(lay, dir, 0, b, s)] + = maybe_deq(ws_states(lay + 1, dir, rnn.n_iter, b, s)); + } + if (pd()->cell_kind() == alg_kind::vanilla_lstm) + for (int s = 0; s < rnn.dic; s++) { + dst_iter_[dst_iter_d.blk_off(lay, dir, 1, b, s)] + = maybe_q(ws_c_states( + lay + 1, dir, rnn.n_iter, b, s)); + } + }); + } +} + +template <> +template +void ref_rnn_bwd_f32_t::copy_res_iter( + const rnn_conf_t &rnn, output_data_t *dst_iter_, float *diff_src_iter_, + const src_data_t *ws_states_, float *ws_c_states_, + const float *ws_diff_states_) const { + auto diff_src_iter_d = memory_desc_wrapper(pd()->diff_src_md(1)); + AOC ws_diff_states(ws_diff_states_, rnn.n_layer + 1, + rnn.n_dir, rnn.n_states + 1, rnn.n_iter + 1, rnn.mb, + rnn.states_ws_ld); + if (diff_src_iter_) { + parallel_nd(rnn.n_layer, rnn.n_dir, rnn.n_states, rnn.mb, + [&](int lay, int dir, int state, int b) { + for (int s = 0; s < rnn.sic; s++) { + diff_src_iter_[diff_src_iter_d.blk_off( + lay, dir, state, b, s)] + = ws_diff_states(lay, dir, state, 0, b, s); + } + }); + } +} + +template +rnn_bias_prepare_sig((_ref_rnn_common_t::bias_prepare)) { + /* Original set of bias provided by the user */ + AOC b( + b_, rnn.n_layer, rnn.n_dir, rnn.n_bias * rnn.dic); + /* Array of pointers initialized in packing */ + AOC bias(bias_, rnn.n_layer, rnn.n_dir, rnn.n_parts_bias); + AOC scratch_bias( + scratch_bias_, rnn.n_layer, rnn.n_dir, rnn.n_bias * rnn.dic); + + if (rnn.copy_bias) { + parallel_nd(rnn.n_layer * rnn.n_dir * rnn.n_bias * rnn.dic, + [&](size_t i) { scratch_bias_[i] = b_[i]; }); + } + + for (int i = 0; i < rnn.n_layer; i++) { + for (int d = 0; d < rnn.n_dir; d++) { + int offset_bias = 0; + for (int p = 0; p < rnn.n_parts_bias; p++) { + bias(i, d, p) = rnn.copy_bias + ? (float *) &scratch_bias(i, d, offset_bias) + : (float *) &b(i, d, offset_bias); + offset_bias += rnn.parts_bias[p] * rnn.dic; + } + } + } + +} + +template +rnn_bias_finalize_sig( + (_ref_rnn_common_t::bias_finalize)) { + if (rnn.dt_conf != all_f32) { + float data_shift = pd()->attr()->rnn_data_qparams_.shift_; + float data_scale = pd()->attr()->rnn_data_qparams_.scale_; + float *weights_scales = pd()->attr()->rnn_weights_qparams_.scales_; + bool scale_per_oc = pd()->attr()->rnn_weights_qparams_.mask_ != 0; + for (int i = 0; i < rnn.n_layer * rnn.n_dir; i++) + for (int j = 0; j < rnn.n_bias * rnn.dic; j++) { + size_t off = i * rnn.n_bias * rnn.dic + j; + float weights_scale + = scale_per_oc ? weights_scales[j] : weights_scales[0]; + scratch_bias_[off] -= (w_iter_comp[off] + w_layer_comp[off]) + * data_shift / (weights_scale * data_scale); + } + } +} + +template +rnn_weights_assign_sig((_ref_rnn_common_t::assign_packed_weights)) { + assert(md->format_kind == format_kind::rnn_packed); + const auto packed_desc = md->format_desc.rnn_packed_desc; + AOC weights(weights_, + rnn.n_layer, rnn.n_dir, packed_desc.n_parts); + + size_t offset_packed = 0; + for (int l = 0; l < rnn.n_layer; l++) + for (int d = 0; d < rnn.n_dir; d++) { + for (int p = 0; p < packed_desc.n_parts; p++) { + weights(l, d, p) = (weights_data_t *)&w_[offset_packed]; + offset_packed + += packed_desc.part_pack_size[p] / sizeof(weights_data_t); + } + } +} + +template +rnn_weights_assign_sig( + (_ref_rnn_common_t::assign_weights)) { + assert(md->format_kind == format_kind::blocked); + const auto &blk = md->format_desc.blocking; + /* Original set of weights provided by the user */ + AOC w(w_, + rnn.n_layer, rnn.n_dir, (int)blk.strides[1]); + /* Array of pointers for each part of weights */ + AOC weights(weights_, rnn.n_layer, rnn.n_dir, n_parts); + + for (int i = 0; i < rnn.n_layer; i++) + for (int d = 0; d < rnn.n_dir; d++) { + size_t offset_weights = 0; + for (int p = 0; p < n_parts; p++) { + weights(i, d, p) = (weights_data_t *)&w(i, d, offset_weights); + offset_weights += gates_per_part[p] * blk.strides[3]; + } + } +} + +//********************* Execution function *********************// +template +void _ref_rnn_common_t::execute_( + const exec_ctx_t &ctx) const { + const rnn_conf_t &rnn = this->pd()->rnn_; + auto input = CTX_IN_MEM(const src_data_t *, MKLDNN_ARG_SRC_LAYER); + auto states = CTX_IN_MEM(const char *, MKLDNN_ARG_SRC_ITER); + auto layer_weights_n_comp = CTX_IN_MEM(const char *, MKLDNN_ARG_WEIGHTS_LAYER); + auto iter_weights_n_comp = CTX_IN_MEM(const char *, MKLDNN_ARG_WEIGHTS_ITER); + auto bias = CTX_IN_MEM(const float *, MKLDNN_ARG_BIAS); + + auto dst_last_layer = rnn.is_fwd + ? CTX_OUT_MEM(char *, MKLDNN_ARG_DST_LAYER) + : const_cast(CTX_IN_MEM(const char *, MKLDNN_ARG_DST_LAYER)); + auto dst_last_iter = rnn.is_fwd + ? CTX_OUT_MEM(char *, MKLDNN_ARG_DST_ITER) + : const_cast(CTX_IN_MEM(const char *, MKLDNN_ARG_DST_ITER)); + + auto diff_dst_layer = CTX_IN_MEM(const float *, MKLDNN_ARG_DIFF_DST_LAYER); + auto diff_dst_iter = CTX_IN_MEM(const float *, MKLDNN_ARG_DIFF_DST_ITER); + + auto w_layer = reinterpret_cast(layer_weights_n_comp); + auto w_iter = reinterpret_cast(iter_weights_n_comp); + auto w_iter_comp = reinterpret_cast( + iter_weights_n_comp + rnn.weights_iter_comp_offset); + auto w_layer_comp = reinterpret_cast( + layer_weights_n_comp + rnn.weights_layer_comp_offset); + + auto scratchpad = this->scratchpad(ctx); + + auto ptr_wei_layer + = scratchpad.template get(key_rnn_ptrs_wei_layer); + auto ptr_wei_iter + = scratchpad.template get(key_rnn_ptrs_wei_iter); + auto ptr_bias = + scratchpad.template get(key_rnn_ptrs_bia); + + // fetchihg buffers from the workspace + // if no workspace was provided we use the scratchpad + char *scratch_ptr = scratchpad.template get(key_rnn_space); + char *ws_ptr = nullptr; + if (rnn.use_workspace) + ws_ptr = rnn.is_fwd + ? CTX_OUT_MEM(char *, MKLDNN_ARG_WORKSPACE) + : const_cast(CTX_IN_MEM(const char *, MKLDNN_ARG_WORKSPACE)); + + char *base_ptr = rnn.use_workspace ? ws_ptr : scratch_ptr; + acc_data_t *ws_gates = (acc_data_t *)(base_ptr + ws_gates_offset_); + src_data_t *ws_states = (src_data_t *)(base_ptr + ws_states_offset_); + float *ws_c_states = (float *)(base_ptr + ws_c_states_offset_); + float *ws_diff_states = (float *)(base_ptr + ws_diff_states_offset_); + float *ws_grid = (float *)(base_ptr + ws_grid_comp_offset_); + float *ws_cell = (float *)(base_ptr + ws_cell_comp_offset_); + + auto diff_src_layer = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_SRC_LAYER); + auto diff_src_iter = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_SRC_ITER); + + auto diff_weights_layer = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_WEIGHTS_LAYER); + auto diff_weights_iter = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_WEIGHTS_ITER); + auto diff_bias = CTX_OUT_MEM(float *, MKLDNN_ARG_DIFF_BIAS); + + // Fetching extra buffers from scratchpad + float *ws_bias = (float *)(scratch_ptr + ws_bias_offset_); + + // initialize diff_states to 0 + if (aprop == prop_kind::backward) + array_set(ws_diff_states, 0.0f, rnn.ws_diff_states_size / sizeof(float)); + + /* Pack(if using packed gemm API) or copy(if input arrays have bad leading + * dimension */ + (this->*bias_preparation_func)(rnn, ptr_bias, bias, ws_bias); + + (this->*weights_iter_assign_func)(rnn, pd()->weights_md(1), + rnn.weights_iter_nld, rnn.weights_iter_ld, rnn.dic, + rnn.sic, rnn.n_parts_weights_iter, rnn.parts_weights_iter, + rnn.part_weights_iter_pack_size, ptr_wei_iter, w_iter, + ptr_bias, bias, ws_bias); + (this->*weights_layer_assign_func)(rnn, pd()->weights_md(0), + rnn.weights_layer_nld, rnn.weights_layer_ld, rnn.dic, rnn.slc, + rnn.n_parts_weights_layer, rnn.parts_weights_layer, + rnn.part_weights_layer_pack_size, ptr_wei_layer, w_layer, ptr_bias, + bias, ws_bias); + + (this->*bias_finalization_func)(rnn, ws_bias, w_iter_comp, w_layer_comp); + + // we first need to copy the initial states and input into ws + copy_init_layer(rnn, ws_states, ws_diff_states, input, diff_dst_layer); + if (rnn.dt_conf == f32u8f32u8 || rnn.dt_conf == f32u8f32f32 + || rnn.dt_conf == all_f32) + copy_init_iter(rnn, ws_states, ws_c_states, ws_diff_states, + (const float *)states, diff_dst_iter); + else if (rnn.dt_conf == u8u8u8u8 || rnn.dt_conf == u8u8u8f32) + copy_init_iter(rnn, ws_states, ws_c_states, ws_diff_states, + (const uint8_t *)states, diff_dst_iter); + else + assert(!"unimplemented"); + + // run the execution on the grid + (this->*grid_computation)(rnn, ptr_wei_layer, ptr_wei_iter, ptr_bias, + ws_states, ws_c_states, ws_diff_states, ws_gates, ws_cell, ws_grid, + diff_weights_layer, diff_weights_iter, diff_bias); + + // Finally we copy the results to the result buffers + if (rnn.dt_conf == u8u8u8f32 || rnn.dt_conf == f32u8f32f32 + || rnn.dt_conf == all_f32) + copy_res_layer(rnn, (float *)dst_last_layer, diff_src_layer, ws_states, + ws_diff_states); + else if (rnn.dt_conf == u8u8u8u8 || rnn.dt_conf == f32u8f32u8) + copy_res_layer(rnn, (uint8_t *)dst_last_layer, diff_src_layer, + ws_states, ws_diff_states); + else + assert(!"unimplemented"); + + if (rnn.dt_conf == f32u8f32u8 || rnn.dt_conf == f32u8f32f32 + || rnn.dt_conf == all_f32) + copy_res_iter(rnn, (float *)dst_last_iter, diff_src_iter, ws_states, + ws_c_states, ws_diff_states); + else if (rnn.dt_conf == u8u8u8u8 || rnn.dt_conf == u8u8u8f32) + copy_res_iter(rnn, (uint8_t *)dst_last_iter, diff_src_iter, ws_states, + ws_c_states, ws_diff_states); + else + assert(!"unimplemented"); +}; + +/* Fix for MSVS warning C4661 */ +template<> rnn_cell_execution_sig(ref_rnn_fwd_f32_t::cell_execution); +template<> rnn_cell_execution_sig(ref_rnn_fwd_u8s8_t::cell_execution); +template<> rnn_cell_execution_sig(ref_rnn_bwd_f32_t::cell_execution); +template<> rnn_cell_execution_sig(ref_rnn_fwd_f32_t::cell_execution_gru); +template<> rnn_cell_execution_sig(ref_rnn_fwd_u8s8_t::cell_execution_gru); +template<> rnn_cell_execution_sig(ref_rnn_bwd_f32_t::cell_execution_gru); +template<> rnn_cell_execution_sig(ref_rnn_fwd_f32_t::cell_execution_gru_lbr); +template<> rnn_cell_execution_sig(ref_rnn_fwd_u8s8_t::cell_execution_gru_lbr); +template<> rnn_cell_execution_sig(ref_rnn_bwd_f32_t::cell_execution_gru_lbr); +template<> rnn_elemwise_sig(ref_rnn_fwd_f32_t::rnn_elemwise); +template<> rnn_elemwise_sig(ref_rnn_fwd_u8s8_t::rnn_elemwise); +template<> rnn_elemwise_sig(ref_rnn_bwd_f32_t::rnn_elemwise); +template<> rnn_elemwise_sig(ref_rnn_fwd_f32_t::lstm_elemwise); +template<> rnn_elemwise_sig(ref_rnn_fwd_u8s8_t::lstm_elemwise); +template<> rnn_elemwise_sig(ref_rnn_bwd_f32_t::lstm_elemwise); +template<> rnn_elemwise_sig(ref_rnn_fwd_f32_t::gru_lbr_elemwise); +template<> rnn_elemwise_sig(ref_rnn_fwd_u8s8_t::gru_lbr_elemwise); +template<> rnn_elemwise_sig(ref_rnn_bwd_f32_t::gru_lbr_elemwise); + +template struct _ref_rnn_common_t; +template struct _ref_rnn_common_t; +template struct _ref_rnn_common_t; + +#undef AOC +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.hpp new file mode 100644 index 000000000000..6f449a9016f8 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/ref_rnn.hpp @@ -0,0 +1,328 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_REF_RNN_HPP +#define CPU_REF_RNN_HPP + +#include + +#include "c_types_map.hpp" +#include "memory_tracking.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +#include "../cpu_isa_traits.hpp" +#include "../gemm/os_blas.hpp" + +#include "cpu_rnn_pd.hpp" +#include "../cpu_primitive.hpp" +#include "rnn_utils.hpp" +#include "jit_uni_rnn_postgemm.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +float activation(float s, float alpha, float cliping, float dd); + +template +struct _ref_rnn_common_t : public cpu_primitive_t { + typedef typename prec_traits::type src_data_t; + typedef typename prec_traits::type weights_data_t; + typedef typename utils::conditional::type acc_data_t; + + using class_name = _ref_rnn_common_t; + + typedef rnn_elemwise_sig((class_name::*elemwise_f)); + typedef rnn_cell_execution_sig((class_name::*cell_execution_f)); + typedef rnn_grid_execution_sig((class_name::*grid_execution_f)); + + typedef rnn_gemm_sig((class_name::*gemm_t)); + typedef rnn_bias_prepare_sig((class_name::*bias_prepare_t)); + typedef rnn_bias_finalize_sig((class_name::*bias_finalize_t)); + typedef rnn_weights_assign_sig((class_name::*weights_assign_t)); + + using base_pd_t = + typename utils::conditional::type; + + struct pd_t : public base_pd_t { + using base_pd_t::base_pd_t; + + DECLARE_COMMON_PD_T("ref:any", class_name); + + status_t init() { + using namespace prop_kind; + using namespace utils; + using namespace format_tag; + using namespace rnn_utils; + const alg_kind_t cell_kind = this->desc()->cell_desc.cell_kind; + + data_type_t src_layer_dt = this->desc()->src_layer_desc.data_type; + data_type_t weights_iter_dt + = this->desc()->weights_iter_desc.data_type; + data_type_t weights_layer_dt + = this->desc()->weights_layer_desc.data_type; + + bool ok = true + && one_of(cell_kind, alg_kind::vanilla_rnn, + alg_kind::vanilla_lstm, alg_kind::vanilla_gru, + alg_kind::gru_linear_before_reset) + && IMPLICATION(aprop == prop_kind::forward, + one_of(this->desc()->prop_kind, forward_training, + forward_inference)) + && IMPLICATION(aprop == backward, + one_of(this->desc()->prop_kind, backward)) + && src_layer_dt == src_type + && everyone_is( + weights_type, weights_iter_dt, weights_layer_dt) + && this->set_default_params() == status::success + && this->with_bias(); + if (!ok) + return status::unimplemented; + + init_conf(rnn_, *this->desc(), this->src_md(0), this->src_md(1), + this->weights_md(0), this->weights_md(1), this->dst_md(0)); + + if (rnn_.dt_conf == all_f32) + ok = ok && this->attr()->has_default_values(); + + // Set weights descriptors to desired format + memory_desc_t new_weights_layer_md = *this->weights_md(0); + CHECK(set_expected_desc(rnn_, new_weights_layer_md, false)); + if (this->weights_layer_md_.format_kind == format_kind::any) { + this->weights_layer_md_ = new_weights_layer_md; + } else if (this->weights_layer_md_.format_kind + == format_kind::rnn_packed) { + if (this->weights_layer_md_ != new_weights_layer_md) + return status::unimplemented; + } + + memory_desc_t new_weights_iter_md = *this->weights_md(1); + CHECK(set_expected_desc(rnn_, new_weights_iter_md, true)); + if (this->weights_iter_md_.format_kind == format_kind::any) { + this->weights_iter_md_ = new_weights_iter_md; + } else if (this->weights_iter_md_.format_kind + == format_kind::rnn_packed) { + if (this->weights_iter_md_ != new_weights_iter_md) + return status::unimplemented; + } + + CHECK(this->check_layout_consistency()); + + set_conf(rnn_, *this->desc(), this->weights_md(0), + this->weights_md(1), this->diff_weights_md(0), + this->diff_weights_md(1)); + + size_t scratchpad_sz{0}, ws_sz{0}; + get_scratchpad_and_workspace_sizes(rnn_, scratchpad_sz, ws_sz); + + // initialize the workspace if needed + if (rnn_.is_training) { + dims_t ws_dims = { (int)ws_sz }; + mkldnn_memory_desc_init_by_tag(&this->ws_md_, 1, ws_dims, + data_type::u8, format_tag::x); + } + + init_scratchpad(scratchpad_sz); + + return status::success; + } + + rnn_utils::rnn_conf_t rnn_; + + private: + void init_scratchpad(size_t scratchpad_sz) { + using namespace memory_tracking::names; + auto scratchpad = this->scratchpad_registry().registrar(); + scratchpad.book(key_rnn_space, sizeof(float) * scratchpad_sz, 4096); + + int max_nparts = this->cell_kind() == alg_kind::vanilla_gru ? 2 : 1; + int ptr_wei_sz = rnn_.n_layer * rnn_.n_dir * max_nparts; + scratchpad.book(key_rnn_ptrs_wei_layer, + sizeof(float *) * ptr_wei_sz); + scratchpad.book(key_rnn_ptrs_wei_iter, + sizeof(float *) * ptr_wei_sz); + scratchpad.book(key_rnn_ptrs_bia, + sizeof(float *) * ptr_wei_sz); + } + }; + + _ref_rnn_common_t(const pd_t *apd) + : cpu_primitive_t(apd, true), rnn_postgemm_(nullptr) { + /// @todo set max_feature_size assuming that we limit the number of + /// iterations and layer to one if slc != dic and sic != dic + /// respectively + + bias_preparation_func = &class_name::bias_prepare; + bias_finalization_func = &class_name::bias_finalize; + + auto set_gemm_funcs + = [](bool packed_gemm, gemm_t &g, weights_assign_t &a) { + if (packed_gemm) { + g = &class_name::packed_gemm; + a = &class_name::assign_packed_weights; + } else { + g = &class_name::gemm; + a = &class_name::assign_weights; + } + }; + set_gemm_funcs(pd()->rnn_.use_iter_packed_gemm, gemm_iter_func, + weights_iter_assign_func); + + set_gemm_funcs(pd()->rnn_.use_layer_packed_gemm, gemm_layer_func, + weights_layer_assign_func); + + switch (pd()->cell_kind()) { + case alg_kind::vanilla_lstm: + cell_func = &class_name::cell_execution; + if (aprop == prop_kind::forward) { + if (mayiuse(avx512_core)) + rnn_postgemm_ = new jit_uni_lstm_postgemm_kernel_fwd( + pd()->rnn_, pd()->attr()); + else if (mayiuse(avx2)) + rnn_postgemm_ = new jit_uni_lstm_postgemm_kernel_fwd( + pd()->rnn_, pd()->attr()); + else if (mayiuse(sse42)) + rnn_postgemm_ = new jit_uni_lstm_postgemm_kernel_fwd( + pd()->rnn_, pd()->attr()); + assert(rnn_postgemm_ != nullptr); + rnn_postgemm_->init(); + } + elemwise_func = &class_name::lstm_elemwise; + break; + case alg_kind::vanilla_rnn: // @todo switch on cell kind + cell_func = &class_name::cell_execution; + elemwise_func = &class_name::rnn_elemwise; + switch (pd()->activation_kind()) { + case alg_kind::eltwise_relu: + activation_func = &activation; + break; + case alg_kind::eltwise_tanh: + activation_func = &activation; + break; + case alg_kind::eltwise_logistic: + activation_func = &activation; + break; + default: break; + } + break; + case alg_kind::vanilla_gru: + cell_func = &class_name::cell_execution_gru; + break; + case alg_kind::gru_linear_before_reset: + cell_func = &class_name::cell_execution_gru_lbr; + elemwise_func = &class_name::gru_lbr_elemwise; + break; + default: break; + } + + grid_computation = &class_name::linear_execution; + + size_t scratchpad_size, workspace_size; + rnn_utils::set_offsets(pd()->rnn_, ws_gates_offset_, ws_states_offset_, + ws_c_states_offset_, ws_diff_states_offset_, + ws_grid_comp_offset_, ws_cell_comp_offset_, + ws_bias_offset_, scratchpad_size, workspace_size); + } + + ~_ref_rnn_common_t() {} + + // typedef typename prec_traits::type data_t; + + virtual status_t execute(const exec_ctx_t &ctx) const override { + execute_(ctx); + return status::success; + } + +private: + void execute_(const exec_ctx_t &ctx) const; + rnn_grid_execution_sig(linear_execution); + rnn_cell_execution_sig(cell_execution); + rnn_cell_execution_sig(cell_execution_gru); + rnn_cell_execution_sig(cell_execution_gru_lbr); + rnn_elemwise_sig(rnn_elemwise); + rnn_elemwise_sig(lstm_elemwise); + rnn_elemwise_sig(gru_lbr_elemwise); + rnn_gemm_sig(gemm); + rnn_gemm_sig(packed_gemm); + rnn_bias_prepare_sig(bias_prepare); + rnn_bias_finalize_sig(bias_finalize); + rnn_weights_assign_sig(assign_weights); + rnn_weights_assign_sig(assign_packed_weights); + + float (*activation_func)(float dd, float s, float alpha, float cliping); + + void copy_init_layer(const rnn_utils::rnn_conf_t &rnn, + src_data_t *ws_states_, float *ws_diff_states_, + const src_data_t *xt_, const float *diff_dst_layer) const; + + template + void copy_init_iter(const rnn_utils::rnn_conf_t &rnn, + src_data_t *ws_states_, float *ws_c_states, float *ws_diff_states_, + const input_data_t *firstit_states_, + const float *diff_dst_iter) const; + + template + void copy_res_layer(const rnn_utils::rnn_conf_t &rnn, + dst_data_t *dst_layer_, float *diff_src_layer, + const src_data_t *ws_states_, const float *ws_diff_states_) const; + + template + void copy_res_iter(const rnn_utils::rnn_conf_t &rnn, + output_data_t *dst_iter_, float *diff_src_iter, + const src_data_t *ws_states_, float *ws_c_states, + const float *ws_diff_states_) const; + + void gates_reduction(const rnn_utils::rnn_conf_t &rnn, + const acc_data_t *ws_gates_, float *diff_bias_) const; + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + + size_t ws_gates_offset_; + size_t ws_states_offset_; + size_t ws_c_states_offset_; + size_t ws_bias_offset_; + size_t ws_diff_states_offset_; + size_t ws_grid_comp_offset_; + size_t ws_cell_comp_offset_; + jit_uni_rnn_postgemm_kernel *rnn_postgemm_; + + grid_execution_f grid_computation; + cell_execution_f cell_func; + + bias_prepare_t bias_preparation_func; + bias_finalize_t bias_finalization_func; + weights_assign_t weights_layer_assign_func; + weights_assign_t weights_iter_assign_func; + + gemm_t gemm_layer_func; + gemm_t gemm_iter_func; + elemwise_f elemwise_func; +}; + +using ref_rnn_fwd_f32_t = _ref_rnn_common_t; +using ref_rnn_bwd_f32_t = _ref_rnn_common_t; +using ref_rnn_fwd_u8s8_t = _ref_rnn_common_t; +} +} +} +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp new file mode 100644 index 000000000000..78cdedbae456 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp @@ -0,0 +1,380 @@ +/******************************************************************************* + * Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef CPU_RNN_REORDERS_HPP +#define CPU_RNN_REORDERS_HPP + +#include + +#include "type_helpers.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" +#include "simple_q10n.hpp" +#include "cpu_reorder_pd.hpp" +#include "../gemm/os_blas.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct rnn_data_reorder_t : public cpu_primitive_t { + struct pd_t : public cpu_reorder_pd_t { + using cpu_reorder_pd_t::cpu_reorder_pd_t; + + DECLARE_COMMON_PD_T("rnn_data_reorder", rnn_data_reorder_t); + + static status_t create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { + const memory_desc_wrapper id(src_md), od(dst_md); + bool args_ok = true + && id.data_type() == type_i + && od.data_type() == type_o + && id.matches_one_of_tag(format_tag::tnc, format_tag::ldsnc) + && od == id; + if (!args_ok) return status::invalid_arguments; + + auto _pd = new pd_t(engine, attr, src_engine, src_md, dst_engine, + dst_md); + if (_pd == nullptr) return out_of_memory; + if (_pd->init() != success) { delete _pd; return unimplemented; } + return safe_ptr_assign(*reorder_pd, _pd); + } + }; + +private: + typedef typename prec_traits::type in_data_t; + typedef typename prec_traits::type out_data_t; + + rnn_data_reorder_t(const pd_t *apd): cpu_primitive_t(apd) {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto input = CTX_IN_MEM(const in_data_t *, MKLDNN_ARG_FROM); + auto output = CTX_OUT_MEM(out_data_t *, MKLDNN_ARG_TO); + const memory_desc_wrapper &input_d = pd()->src_md(); + const memory_desc_wrapper &output_d = pd()->dst_md(); + const size_t nelems = input_d.nelems(); + const float scale = pd()->attr()->rnn_data_qparams_.scale_; + const float shift = pd()->attr()->rnn_data_qparams_.shift_; + + parallel_nd(nelems, [&](size_t i) { + float in = (float)input[input_d.off_l(i)] * scale + shift; + output[output_d.off_l(i)] = qz_a1b0()(in); + }); + + return status::success; + } + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template +struct rnn_weights_reorder_t : public cpu_primitive_t { + struct pd_t : public cpu_reorder_pd_t { + using cpu_reorder_pd_t::cpu_reorder_pd_t; + + DECLARE_COMMON_PD_T("rnn_weights_reorder", rnn_weights_reorder_t); + + static status_t create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { +#if !USE_MKL_PACKED_GEMM + return status::unimplemented; +#endif + const memory_desc_wrapper id(src_md), od(dst_md); + bool args_ok = true + && id.data_type() == type_i + && od.data_type() == type_o + && od.format_kind() == format_kind::rnn_packed + && od.rnn_packed_desc().format == mkldnn_ldigo_p + && od.rnn_packed_desc().n_parts == 1 + && attr != nullptr; + if (!args_ok) return status::invalid_arguments; + + format_tag_t itag = id.matches_one_of_tag( + format_tag::ldigo, format_tag::ldgoi); + if (itag == format_tag::undef) return status::invalid_arguments; + + const int mask = attr->rnn_weights_qparams_.mask_; + if (!utils::one_of(mask, 0, 3)) return status::unimplemented; + + auto _pd = new pd_t(engine, attr, src_engine, src_md, dst_engine, + dst_md); + if (_pd == nullptr) return out_of_memory; + _pd->itag_ = itag; + if (_pd->init() != success) { delete _pd; return unimplemented; } + return safe_ptr_assign(*reorder_pd, _pd); + } + + status_t init() { + status_t status = cpu_reorder_pd_t::init(); + if (status != status::success) return status; + + init_scratchpad(); + + return status::success; + } + + format_tag_t itag_ = mkldnn_format_tag_undef; + + private: + void init_scratchpad() { + const memory_desc_wrapper id(src_md()); + const size_t nelems = id.nelems(); + const auto &dims = id.dims(); + + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + size_t quantization_size = sizeof(int8_t) * nelems; + size_t reduction_size = itag_ == ldigo + ? sizeof(int32_t) * mkldnn_get_max_threads() * dims[0] + * dims[1] * dims[3] * dims[4] + : 0; + scratchpad.book( + key_reorder_rnn_weights_quantization, quantization_size); + scratchpad.book(key_reorder_rnn_weights_reduction, reduction_size); + } + }; + +private: + typedef typename prec_traits::type in_data_t; + typedef typename prec_traits::type out_data_t; + + rnn_weights_reorder_t(const pd_t *apd): cpu_primitive_t(apd) {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { +#if USE_MKL_PACKED_GEMM + auto input = CTX_IN_MEM(const in_data_t *, MKLDNN_ARG_FROM); + auto output = CTX_OUT_MEM(char *, MKLDNN_ARG_TO); + const memory_desc_wrapper &input_d = pd()->src_md(); + const memory_desc_wrapper &output_d = pd()->dst_md(); + const auto &dims = input_d.dims(); + + const int L = dims[0]; + const int D = dims[1]; + const int I = dims[2]; + const int G = dims[3]; + const int O = dims[4]; + + const bool is_igo = pd()->itag_ == format_tag::ldigo; + + /* Quantize input & compute compensation */ + auto quantized = (int8_t * __restrict)scratchpad(ctx).template get( + memory_tracking::names::key_reorder_rnn_weights_quantization); + auto reduction = (int32_t * __restrict)scratchpad(ctx).template get( + memory_tracking::names::key_reorder_rnn_weights_reduction); + float *comp = reinterpret_cast( + output + output_d.rnn_packed_desc().offset_compensation); + const float *scales = pd()->attr()->rnn_weights_qparams_.scales_; + const int mask = pd()->attr()->rnn_weights_qparams_.mask_; + + if (is_igo) { + int nthr = mkldnn_get_max_threads(); + int LD_nthr = nstl::min(L * D, nthr); + int I_nthr = nstl::min(I, nthr / LD_nthr); + parallel(nthr, [&](const int ithr, const int nthr) { + int LD_ithr = -1, LD_s = -1, LD_e = -1; + int I_ithr = -1, I_s = -1, I_e = -1; + if (ithr < LD_nthr * I_nthr) { + LD_ithr = ithr % LD_nthr; + I_ithr = ithr / LD_nthr; + balance211(L * D, LD_nthr, LD_ithr, LD_s, LD_e); + balance211(I, I_nthr, I_ithr, I_s, I_e); + } + int32_t *comp_ithr = reduction + I_ithr * L * D * G * O; + for (int ld = LD_s; ld < LD_e; ld++) { + for (int go = 0; go < G * O; go++) + comp_ithr[ld * G * O + go] = 0; + for (int i = I_s; i < I_e; i++) { + PRAGMA_OMP_SIMD() + for (int go = 0; go < G * O; go++) { + const float s = scales[(mask == 0) ? 0 : go]; + int8_t q = qz_b0()( + input[ld * I * G * O + i * G * O + go], s); + quantized[ld * I * G * O + i * G * O + go] + = (int32_t)q; + comp_ithr[ld * G * O + go] += (int32_t)q; + } + } + } + }); + parallel_nd(L * D * G * O, + [&](int s) { comp[s] = saturate(reduction[s]); }); + for (int i = 1; i < I_nthr; i++) { + parallel_nd(L * D * G * O, [&](int s) { + comp[s] += saturate( + reduction[i * L * D * G * O + s]); + }); + } + } else { + parallel_nd(L * D, G * O, [&](int ld, int go) { + int32_t compensation = 0; + const float s = scales[(mask == 0) ? 0 : go]; + PRAGMA_OMP_SIMD() + for (int i = 0; i < I; i++) { + int8_t q = qz_b0()( + input[ld * G * O * I + go * I + i], s); + compensation += (int32_t)q; + quantized[ld * G * O * I + go * I + i] = q; + } + comp[ld * G * O + go] = saturate(compensation); + }); + } + + /* Pack */ + auto off_igo = [&](int l, int d, int i, int g, int o) { + return l * D * I * G * O + d * I * G * O + i * G * O + g * O + o; + }; + auto off_goi = [&](int l, int d, int i, int g, int o) { + return l * D * G * O * I + d * G * O * I + g * O * I + o * I + i; + }; + int n_parts = output_d.rnn_packed_desc().n_parts; + const size_t *size_packed_cell + = output_d.rnn_packed_desc().part_pack_size; + const int *parts = output_d.rnn_packed_desc().parts; + const int n = output_d.rnn_packed_desc().n; + char *to_pack = output; + for (int l = 0; l < L; l++) { + for (int d = 0; d < D; d++) { + for (int p = 0; p < n_parts; p++) { + int g = (p > 0) ? parts[p - 1] : 0; + int m_p = parts[p] * O; + int k_p = I; + cblas_gemm_s8u8s32_pack(CblasColMajor, CblasAMatrix, + is_igo ? CblasNoTrans : CblasTrans, m_p, n, k_p, + &quantized[is_igo ? off_igo(l, d, 0, g, 0) : + off_goi(l, d, g, 0, 0)], + is_igo ? G * O : I, to_pack); + to_pack += size_packed_cell[p]; + } + } + } +#endif + return status::success; + } + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +template <> +struct rnn_weights_reorder_t + : public cpu_primitive_t { + struct pd_t : public cpu_reorder_pd_t { + using cpu_reorder_pd_t::cpu_reorder_pd_t; + + DECLARE_COMMON_PD_T("rnn_weights_reorder", rnn_weights_reorder_t); + + static status_t create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { +#if !USE_MKL_PACKED_GEMM + return status::unimplemented; +#endif + const memory_desc_wrapper id(src_md), od(dst_md); + bool args_ok = true + && id.data_type() == data_type::f32 + && od.data_type() == data_type::f32 + && od.format_kind() == format_kind::rnn_packed + && utils::one_of(od.rnn_packed_desc().format, + mkldnn_ldigo_p, mkldnn_ldgoi_p) + && attr->has_default_values(); + if (!args_ok) return status::invalid_arguments; + + format_tag_t itag = id.matches_one_of_tag( + format_tag::ldigo, format_tag::ldgoi); + if (itag == format_tag::undef) return status::invalid_arguments; + + const int mask = attr->rnn_weights_qparams_.mask_; + if (!utils::one_of(mask, 0, 3)) return status::unimplemented; + + auto _pd = new pd_t(engine, attr, src_engine, src_md, dst_engine, + dst_md); + if (_pd == nullptr) return out_of_memory; + if (_pd->init() != success) { delete _pd; return unimplemented; } + _pd->itag_ = itag; + return safe_ptr_assign(*reorder_pd, _pd); + } + + format_tag_t itag_; + }; + +private: + rnn_weights_reorder_t(const pd_t *apd): cpu_primitive_t(apd) {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { +#if USE_MKL_PACKED_GEMM + auto input = CTX_IN_MEM(const float *, MKLDNN_ARG_FROM); + auto output = CTX_OUT_MEM(float *, MKLDNN_ARG_TO); + const memory_desc_wrapper &input_d = pd()->src_md(); + const memory_desc_wrapper &output_d = pd()->dst_md(); + const auto &dims = input_d.dims(); + const rnn_packed_desc_t &rnn_pdata = output_d.rnn_packed_desc(); + const int L = dims[0]; + const int D = dims[1]; + const int I = dims[2]; + const int G = dims[3]; + const int O = dims[4]; + + /* Pack */ + bool cross_case = false + || (pd()->itag_ == format_tag::ldigo + && rnn_pdata.format == mkldnn_ldgoi_p) + || (pd()->itag_ == format_tag::ldgoi + && rnn_pdata.format == mkldnn_ldigo_p); + auto trans = cross_case ? CblasTrans : CblasNoTrans; + int n_parts = rnn_pdata.n_parts; + const size_t *size_packed_cell = rnn_pdata.part_pack_size; + const int *parts = rnn_pdata.parts; + const int n = rnn_pdata.n; + + const bool is_igo = pd()->itag_ == format_tag::ldigo; + auto off_igo = [&](int l, int d, int i, int g, int o) { + return l * D * I * G * O + d * I * G * O + i * G * O + g * O + o; + }; + auto off_goi = [&](int l, int d, int i, int g, int o) { + return l * D * G * O * I + d * G * O * I + g * O * I + o * I + i; + }; + for (int l = 0; l < L; l++) { + for (int d = 0; d < D; d++) { + for (int p = 0; p < n_parts; p++) { + int g = (p > 0) ? parts[p - 1] : 0; + int m_p = is_igo ? parts[p] * O : I; + int k_p = is_igo ? I : parts[p] * O; + int ld = is_igo ? G * O : I; + cblas_sgemm_pack(CblasColMajor, CblasAMatrix, trans, m_p, n, + k_p, 1.0f, &input[is_igo ? off_igo(l, d, 0, g, 0) : + off_goi(l, d, 0, g, 0)], + ld, output); + output += size_packed_cell[p] / sizeof(float); + } + } + } +#endif + return status::success; + } + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} // namespace cpu +} // namespace impl +} // namespace mkldnn + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.cpp new file mode 100644 index 000000000000..1d60415cbcdb --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.cpp @@ -0,0 +1,426 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "c_types_map.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" + +#include "ref_rnn.hpp" +#include "rnn_utils.hpp" +#include "type_helpers.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::utils; +using namespace rnn_utils; +using namespace format_tag; +using namespace rnn_packed_format; +using namespace data_type; + +bool rnn_utils::is_ldigo(const memory_desc_wrapper &md) { + if (md.format_kind() != format_kind::blocked) + return false; + + auto blk = md.blocking_desc(); + auto str = blk.strides; + auto dims = md.dims(); + return md.ndims() == 5 && blk.inner_nblks == 0 && str[4] == 1 + && str[3] == dims[4] && str[1] == str[2] * dims[2] + && str[0] == str[1] * dims[1]; +}; + +bool rnn_utils::is_ldgoi(const memory_desc_wrapper &md) { + if (md.format_kind() != format_kind::blocked) + return false; + + auto blk = md.blocking_desc(); + auto str = blk.strides; + auto dims = md.dims(); + return md.ndims() == 5 && blk.inner_nblks == 0 && str[2] == 1 + && str[3] == dims[4] * str[4] && str[1] == str[3] * dims[3] + && str[0] == str[1] * dims[1]; +}; + +void rnn_utils::init_conf(rnn_conf_t &rnn, const rnn_desc_t &rd, + const memory_desc_wrapper &src_layer_d, + const memory_desc_wrapper &src_iter_d, + const memory_desc_wrapper &weights_layer_d, + const memory_desc_wrapper &weights_iter_d, + const memory_desc_wrapper &dst_layer_d) { + rnn.is_fwd = utils::one_of(rd.prop_kind, prop_kind::forward_training, + prop_kind::forward_inference); + rnn.is_training = utils::one_of( + rd.prop_kind, prop_kind::forward_training, prop_kind::backward); + rnn.is_lbr = rd.cell_desc.cell_kind == mkldnn_gru_linear_before_reset; + + switch (rd.direction) { + case mkldnn_unidirectional_left2right: rnn.exec_dir = l2r; break; + case mkldnn_unidirectional_right2left: rnn.exec_dir = r2l; break; + case mkldnn_bidirectional_concat: rnn.exec_dir = bi_concat; break; + case mkldnn_bidirectional_sum: rnn.exec_dir = bi_sum; break; + default: break; + } + + if (everyone_is(f32, src_layer_d.data_type(), dst_layer_d.data_type(), + weights_layer_d.data_type())) + rnn.dt_conf = all_f32; + else if (dst_layer_d.data_type() == u8) { + if (IMPLICATION(src_iter_d.md_, src_iter_d.data_type() == u8)) + rnn.dt_conf = u8u8u8u8; + else + rnn.dt_conf = f32u8f32u8; + } else { + if (IMPLICATION(src_iter_d.md_, src_iter_d.data_type() == u8)) + rnn.dt_conf = u8u8u8f32; + else + rnn.dt_conf = f32u8f32f32; + } + + rnn.n_layer = weights_layer_d.dims()[0]; + rnn.n_iter = src_layer_d.dims()[0]; + rnn.n_dir = weights_layer_d.dims()[1]; + rnn.n_gates = weights_layer_d.dims()[3]; + rnn.n_states = mkldnn_rnn_cell_get_states_count(&rd.cell_desc); + rnn.n_bias = rnn.n_gates + rnn.is_lbr; + rnn.mb = src_layer_d.dims()[1]; + rnn.sic = weights_iter_d.dims()[2]; + rnn.slc = weights_layer_d.dims()[2]; + rnn.dic = weights_layer_d.dims()[4]; + rnn.dlc = dst_layer_d.dims()[2]; + + rnn.gates_ld = rnn.dic * rnn.n_gates; + rnn.gates_nld = rnn.mb; + rnn.states_nld = rnn.mb; + + /* Set the correct number of weights parts */ + bool is_orig_gru = rd.cell_desc.cell_kind == alg_kind::vanilla_gru; + rnn.n_parts_weights_layer = 1; + rnn.parts_weights_layer[0] = rnn.n_gates; + rnn.parts_weights_layer[1] = 0; + + rnn.n_parts_weights_iter = is_orig_gru ? 2 : 1; + rnn.parts_weights_iter[0] = is_orig_gru ? 2 : rnn.n_gates; + rnn.parts_weights_iter[1] = is_orig_gru ? 1 : 0; + + rnn.n_parts_bias = 1; + rnn.parts_bias[0] = rnn.n_bias; + rnn.parts_bias[1] = 0; + + /* Decide wich gemm implementation to use: packed/nonpacked jit/cblas + * and if to mergre gemm across iterations */ + bool is_int8 = rnn.dt_conf != all_f32; + rnn.merge_gemm_layer = ((rnn.is_fwd && rnn.mb < 128) || !rnn.is_fwd) + || is_int8; + bool is_gru = utils::one_of(rd.cell_desc.cell_kind, alg_kind::vanilla_gru, + alg_kind::gru_linear_before_reset); + rnn.merge_gemm_iter = !(rnn.is_fwd || is_gru) || is_int8; + bool is_inference = !rnn.is_training; + + rnn.use_jit_gemm = !mayiuse(avx512_mic) + && ((is_inference && (rnn.n_layer > 1 || rnn.mb < 100)) + || (rnn.is_training && rnn.dic < 500)); + + /* Decide to copy bias */ + rnn.copy_bias = rnn.dt_conf != all_f32; + +#if USE_MKL_PACKED_GEMM + rnn.use_layer_packed_gemm + = (weights_layer_d.format_kind() == format_kind::any + && rnn.slc > 760 && rnn.dic > 760 && is_inference) + || is_int8; // packed gemm is the only supported option for int8 + rnn.use_iter_packed_gemm + = (weights_iter_d.format_kind() == format_kind::any && rnn.sic > 760 + && rnn.dic > 760 && is_inference) + || is_int8; +#else + rnn.use_layer_packed_gemm = false; + rnn.use_iter_packed_gemm = false; +#endif + + /* Set packed gemm sizes */ + if (rnn.use_layer_packed_gemm) { + rnn.weights_layer_pack_size = 0; + for (int p = 0; p < rnn.n_parts_weights_layer; p++) { + int m_p = rnn.is_fwd + ? (rnn.parts_weights_layer[p] * rnn.dic) + : rnn.slc; + int k_p = rnn.is_fwd + ? rnn.slc + : (rnn.parts_weights_layer[p] * rnn.dic); + int n_p = rnn.merge_gemm_layer ? rnn.mb * rnn.n_iter : rnn.mb; + +#if USE_MKL_PACKED_GEMM + if (rnn.dt_conf == all_f32) + rnn.part_weights_layer_pack_size[p] = cblas_sgemm_pack_get_size( + CblasAMatrix, m_p, n_p, k_p); + else + rnn.part_weights_layer_pack_size[p] + = cblas_gemm_s8u8s32_pack_get_size( + CblasAMatrix, m_p, n_p, k_p); +#else + UNUSED(m_p); + UNUSED(k_p); + UNUSED(n_p); + rnn.part_weights_layer_pack_size[p] = 0; +#endif + rnn.weights_layer_pack_size += rnn.n_layer * rnn.n_dir + * rnn.part_weights_layer_pack_size[p]; + } + rnn.weights_layer_comp_offset = rnn.weights_layer_pack_size; + rnn.weights_layer_pack_size += rnn.dt_conf == all_f32 ? 0 : rnn.n_layer + * rnn.n_dir * rnn.n_gates * rnn.dlc * sizeof(float); + } + + if (rnn.use_iter_packed_gemm) { + rnn.weights_iter_pack_size = 0; + for (int p = 0; p < rnn.n_parts_weights_iter; p++) { + int m_p = rnn.is_fwd ? (rnn.parts_weights_iter[p] * rnn.dic) : + rnn.sic; + int k_p = rnn.is_fwd ? rnn.sic : + (rnn.parts_weights_iter[p] * rnn.dic); + int n_p = rnn.merge_gemm_iter ? rnn.mb * rnn.n_iter : rnn.mb; + +#if USE_MKL_PACKED_GEMM + if (rnn.dt_conf == all_f32) + rnn.part_weights_iter_pack_size[p] = cblas_sgemm_pack_get_size( + CblasAMatrix, m_p, n_p, k_p); + else + rnn.part_weights_iter_pack_size[p] + = cblas_gemm_s8u8s32_pack_get_size( + CblasAMatrix, m_p, n_p, k_p); +#else + UNUSED(m_p); + UNUSED(k_p); + UNUSED(n_p); + rnn.part_weights_iter_pack_size[p] = 0; +#endif + rnn.weights_iter_pack_size += rnn.n_layer * rnn.n_dir + * rnn.part_weights_iter_pack_size[p]; + } + rnn.weights_iter_comp_offset = rnn.weights_iter_pack_size; + rnn.weights_iter_pack_size += rnn.dt_conf == all_f32 ? 0 : rnn.n_layer + * rnn.n_dir * rnn.n_gates * rnn.dic * sizeof(float); + } + +} + +void rnn_utils::set_conf(rnn_conf_t &rnn, const rnn_desc_t &rd, + const memory_desc_wrapper &weights_layer_d, + const memory_desc_wrapper &weights_iter_d, + const memory_desc_wrapper &diff_weights_layer_d, + const memory_desc_wrapper &diff_weights_iter_d) { + + /* Set leading dimensions for input weights arrays depending on input format + */ + rnn.weights_layer_is_packed + = weights_layer_d.format_kind() == format_kind::rnn_packed; + rnn.weights_iter_is_packed + = weights_iter_d.format_kind() == format_kind::rnn_packed; + + auto set_dims = [&](const memory_desc_wrapper &md, int &ld, int &nld) { + ld = 0; nld = 0; + if (md.is_blocking_desc()) { + if (is_ldigo(md)) { + ld = (int)md.blocking_desc().strides[2]; + nld = md.dims()[2]; + } else if (is_ldgoi(md)) { + ld = (int)md.blocking_desc().strides[4]; + nld = md.dims()[3] * md.dims()[4]; + } else + assert(!"unsupported weights format"); + } + }; + set_dims(weights_layer_d, rnn.weights_layer_ld, rnn.weights_layer_nld); + set_dims(weights_iter_d, rnn.weights_iter_ld, rnn.weights_iter_nld); + if (!rnn.is_fwd) { + set_dims(diff_weights_layer_d, rnn.diff_weights_layer_ld, + rnn.diff_weights_layer_nld); + set_dims(diff_weights_iter_d, rnn.diff_weights_iter_ld, + rnn.diff_weights_iter_nld); + } + + int sizeof_states_dt + = rnn.dt_conf == all_f32 ? sizeof(float) : sizeof(uint8_t); + rnn.states_ws_ld + = get_good_ld(nstl::max(rnn.slc, nstl::max(rnn.sic, rnn.dic)), + sizeof_states_dt); + rnn.gates_ws_ld = get_good_ld(rnn.gates_ld, sizeof(float)); + + /* Set workspace sizes to store: + * states to copmute a pass + * diff states to copmute bwd pass (training only) + * intermediate results from the gates + */ + rnn.use_workspace = rnn.is_training; + rnn.ws_states_size = (size_t)(rnn.n_layer + 1) * rnn.n_dir + * (rnn.n_iter + 1) * rnn.mb * rnn.states_ws_ld * sizeof_states_dt; + bool is_lstm = rd.cell_desc.cell_kind == mkldnn_vanilla_lstm; + rnn.ws_c_states_size = is_lstm + ? (size_t)(rnn.n_layer + 1) * rnn.n_dir * (rnn.n_iter + 1) * rnn.mb + * rnn.states_ws_ld * sizeof(float) + : 0; + rnn.ws_diff_states_size = rnn.is_training + ? (size_t)(rnn.n_layer + 1) * rnn.n_dir * (rnn.n_iter + 1) + * (rnn.n_states + 1) * rnn.mb * rnn.states_ws_ld + * sizeof(float) + : (size_t)0; + rnn.ws_gates_size = (size_t)rnn.n_layer * rnn.n_dir * rnn.n_iter * rnn.mb + * rnn.gates_ws_ld * sizeof(float); + + /* set other sizes */ + rnn.ws_per_cell = (size_t)rnn.is_lbr * rnn.mb * rnn.dic * sizeof(float); + rnn.ws_cell_comp_size + = rnn.is_lbr || rnn.dt_conf != all_f32 + ? (size_t) rnn.gates_nld * rnn.gates_ws_ld * sizeof(float) + : 0; + rnn.ws_grid_comp_size = (size_t)rnn.is_lbr * rnn.is_training * rnn.n_layer + * rnn.n_dir * rnn.n_iter * rnn.ws_per_cell * sizeof(float); + rnn.ws_bias_size = (size_t)rnn.n_layer * rnn.n_dir * rnn.n_bias * rnn.dic + * sizeof(float); +} + +int rnn_utils::get_good_ld(int dim, int sizeof_dt) { + // we want matrices leading dimentions to be 64-byte aligned, + // and not divisible by 256 to avoid 4K aliasing effects + int ld = rnd_up(dim, 64 / sizeof_dt); + return (ld % 256 == 0) ? ld + 64 / sizeof_dt : ld; +} + +void rnn_utils::set_offsets(const rnn_conf_t &rnn, size_t &ws_gates_offset, + size_t &ws_states_offset, size_t &ws_c_states_offset, + size_t &ws_diff_states_offset, size_t &ws_grid_comp_offset, + size_t &ws_cell_comp_offset, size_t &ws_bias_offset, + size_t &scratchpad_size, size_t &workspace_size) { + + const size_t page_size = 4096; // 2097152; + size_t current_offset; + /* Mandatory workspaces: go to workspace if use_workspace, scratchpad + * otherwise */ + current_offset = 0; // assumes the workspace base pointer is page aligned + ws_gates_offset = current_offset; + current_offset += rnn.ws_gates_size; + + current_offset = utils::rnd_up(current_offset, page_size); + ws_states_offset = current_offset; + current_offset += rnn.ws_states_size; + + current_offset = utils::rnd_up(current_offset, page_size); + ws_c_states_offset = current_offset; + current_offset += rnn.ws_c_states_size; + + current_offset = utils::rnd_up(current_offset, page_size); + ws_diff_states_offset = current_offset; + current_offset += rnn.ws_diff_states_size; + + current_offset = utils::rnd_up(current_offset, page_size); + ws_grid_comp_offset = current_offset; + current_offset += rnn.ws_grid_comp_size; + + current_offset = utils::rnd_up(current_offset, page_size); + ws_cell_comp_offset = current_offset; + current_offset += rnn.ws_cell_comp_size; + + workspace_size = rnn.use_workspace ? current_offset : 0; + + /* Optional scratchpads */ + // Assumes the scratchpad base pointer is page aligned. + // If use_workspace, the following goes to scratchpad alone, + // otherwise, all goes to scratchpad and continue incrementing offset + current_offset = rnn.use_workspace ? 0 : current_offset; + + if (rnn.copy_bias) { + current_offset = utils::rnd_up(current_offset, page_size); + ws_bias_offset = current_offset; + current_offset += rnn.ws_bias_size; + } + + scratchpad_size = current_offset; +} + +void rnn_utils::get_scratchpad_and_workspace_sizes(const rnn_conf_t &rnn, + size_t &scratchpad_size, size_t &workspace_size) { + size_t ws_gates_offset, ws_states_offset, ws_c_states_offset, + ws_diff_states_offset, ws_grid_comp_offset, ws_cell_comp_offset, + ws_bias_offset; + set_offsets(rnn, ws_gates_offset, ws_states_offset, ws_diff_states_offset, + ws_c_states_offset, ws_grid_comp_offset, ws_cell_comp_offset, + ws_bias_offset, scratchpad_size, workspace_size); +} + +status_t rnn_utils::set_good_strides( + memory_desc_t &weights_md, format_tag_t tag) { + auto &strides = weights_md.format_desc.blocking.strides; + auto dims = weights_md.dims; + + if (tag == ldigo) { + strides[2] = rnn_utils::get_good_ld((int)strides[2], + (int)types::data_type_size(weights_md.data_type)); + strides[1] = dims[2] * strides[2]; + strides[0] = dims[1] * strides[1]; + } else if (tag == ldgoi) { + strides[4] = rnn_utils::get_good_ld((int)strides[4], + (int)types::data_type_size(weights_md.data_type)); + strides[3] = dims[4] * strides[4]; + strides[1] = dims[3] * strides[3]; + strides[0] = dims[1] * strides[1]; + } else + return status::unimplemented; + + return status::success; +} + +status_t rnn_utils::set_expected_desc(rnn_conf_t &rnn, + memory_desc_t &weights_md, bool is_iter) { + using namespace format_tag; + bool use_packed_gemm = is_iter + ? rnn.use_iter_packed_gemm + : rnn.use_layer_packed_gemm; + if (use_packed_gemm) { + weights_md.format_kind = format_kind::rnn_packed; + rnn_packed_desc_t &rnn_pdata = weights_md.format_desc.rnn_packed_desc; + rnn_pdata.format = rnn.is_fwd ? mkldnn_ldigo_p : mkldnn_ldgoi_p; + if (is_iter) { + rnn_pdata.n = rnn.mb; + rnn_pdata.n_parts = rnn.n_parts_weights_iter; + array_copy(rnn_pdata.parts, rnn.parts_weights_iter, + MKLDNN_RNN_MAX_N_PARTS); + array_copy(rnn_pdata.part_pack_size, + rnn.part_weights_iter_pack_size, MKLDNN_RNN_MAX_N_PARTS); + rnn_pdata.offset_compensation = rnn.weights_iter_comp_offset; + rnn_pdata.size = rnn.weights_iter_pack_size; + } else { + rnn_pdata.n = rnn.merge_gemm_layer ? rnn.n_iter * rnn.mb : rnn.mb; + rnn_pdata.n_parts = rnn.n_parts_weights_layer; + array_copy(rnn_pdata.parts, rnn.parts_weights_layer, + MKLDNN_RNN_MAX_N_PARTS); + array_copy(rnn_pdata.part_pack_size, + rnn.part_weights_layer_pack_size, MKLDNN_RNN_MAX_N_PARTS); + rnn_pdata.offset_compensation = rnn.weights_layer_comp_offset; + rnn_pdata.size = rnn.weights_layer_pack_size; + } + } else { + CHECK(memory_desc_init_by_tag(weights_md, rnn.is_fwd ? ldigo : ldgoi)); + // Adjust strides for good leading dimension in GEMM + CHECK(set_good_strides(weights_md, rnn.is_fwd ? ldigo : ldgoi)); + } + return status::success; +} + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.hpp new file mode 100644 index 000000000000..99eb787a64ed --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_utils.hpp @@ -0,0 +1,225 @@ +/******************************************************************************* +* Copyright 2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef RNN_UTILS_HPP +#define RNN_UTILS_HPP + +#include "mkldnn.h" + +#include "cpu_rnn_pd.hpp" + + +#define rnn_elemwise_sig(f) \ + void f(const rnn_utils::rnn_conf_t &rnn, acc_data_t *ws_gates_, \ + src_data_t *states_t_l_, float *c_states_t_l_, \ + src_data_t *states_tm1_l_, float *c_states_tm1_l_, \ + float *diff_states_t_l_, float *diff_states_t_lp1_, \ + float *diff_states_tp1_l_, float *bias_, float *ws_grid_, \ + float *ws_cell_) const + +#define rnn_cell_execution_sig(f) \ + void f(const rnn_utils::rnn_conf_t &rnn, src_data_t *states_t_l_, \ + float *c_states_t_l_, float *diff_states_t_l_, \ + weights_data_t **w_layer_, weights_data_t **w_iter_, \ + float **bias_, src_data_t *states_t_lm1_, \ + src_data_t *states_tm1_l_, float *c_states_tm1_l_, \ + float *diff_states_t_lp1_, float *diff_states_tp1_l_, \ + float *diff_w_layer_, float *diff_w_iter_, float *diff_bias_, \ + acc_data_t *ws_gates_, float *ws_grid_, float *ws_cell_) const + +#define rnn_grid_execution_sig(f) \ + void f(const rnn_utils::rnn_conf_t &rnn, weights_data_t **weights_layer_, \ + weights_data_t **weights_states_, float **bias_, \ + src_data_t *ws_states_, float *ws_c_states_, \ + float *ws_diff_states_, acc_data_t *ws_gates_, float *ws_cell_, \ + float *ws_grid_, float *diff_weights_layer_, \ + float *diff_weights_iter_, float *diff_bias_) const + +#define rnn_gemm_sig(f) \ + void f(const char transA, const char transB, int m, int n, int k, \ + const float alpha, const weights_data_t *a_, const int ldA, \ + const src_data_t *b_, const int ldB, const float beta, \ + acc_data_t *c_, const int ldC) const + +#define rnn_bias_prepare_sig(f) \ + void f(const rnn_utils::rnn_conf_t &rnn, float **bias_, const float *b_, \ + float *scratch_bias_) const + +#define rnn_bias_finalize_sig(f) \ + void f(const rnn_utils::rnn_conf_t &rnn, float *scratch_bias_, \ + const float *w_iter_comp, const float *w_layer_comp) const + +#define rnn_weights_assign_sig(f) \ + void f(const rnn_utils::rnn_conf_t &rnn, const memory_desc_t *md, int nld, \ + int ld, int OC_size, int IC_size, const int n_parts, \ + const int *gates_per_part, const size_t *part_weights_pack_size, \ + weights_data_t **weights_, const weights_data_t *w_, \ + float **bias_, const float *b_, float *scratch_bias_) const + + +namespace mkldnn { +namespace impl { +namespace cpu { + +namespace rnn_utils { + +using namespace mkldnn::impl::utils; + +enum execution_direction_t { + l2r, + r2l, + bi_concat, + bi_sum, +}; + +enum data_type_conf_t { + all_f32, + u8u8u8f32, + f32u8f32f32, + u8u8u8u8, + f32u8f32u8 +}; + +struct rnn_conf_t { + execution_direction_t exec_dir; + data_type_conf_t dt_conf; + int n_layer, n_iter, n_dir, n_gates, n_states; + int mb; + int slc, sic, dic, dlc; + int gates_ld, gates_nld, gates_ws_ld; + int n_parts_weights_layer, parts_weights_layer[MKLDNN_RNN_MAX_N_PARTS]; + int n_parts_weights_iter, parts_weights_iter[MKLDNN_RNN_MAX_N_PARTS]; + int n_bias, n_parts_bias, parts_bias[MKLDNN_RNN_MAX_N_PARTS]; + size_t part_weights_iter_pack_size[MKLDNN_RNN_MAX_N_PARTS], + part_weights_layer_pack_size[MKLDNN_RNN_MAX_N_PARTS]; + bool weights_layer_is_packed, weights_iter_is_packed; + /* Size of packed data in bytes */ + size_t weights_layer_comp_offset, weights_layer_pack_size, + weights_iter_comp_offset, weights_iter_pack_size; + + bool copy_bias; + int weights_layer_ld, weights_layer_nld; + int diff_weights_layer_ld, diff_weights_layer_nld; + int weights_iter_ld, weights_iter_nld; + int diff_weights_iter_ld, diff_weights_iter_nld; + int states_nld, states_ws_ld; + int weights_iter_compensation_size, weights_layer_compensation_size; + bool is_fwd, is_training, is_lbr; + bool use_workspace; + + /* Size of workspace for each tensor in bytes */ + size_t ws_gates_size, ws_states_size, ws_c_states_size, ws_diff_states_size, + ws_cell_comp_size, ws_grid_comp_size, ws_per_cell, ws_bias_size; + bool merge_gemm_iter, merge_gemm_layer, use_jit_gemm, use_layer_packed_gemm, + use_iter_packed_gemm; +}; + +bool is_ldigo(const memory_desc_wrapper &md); +bool is_ldgoi(const memory_desc_wrapper &md); + +int get_good_ld(int dim, int sizeof_dt); + +void init_conf(rnn_conf_t &rnn, const rnn_desc_t &rd, + const memory_desc_wrapper &src_layer_d, + const memory_desc_wrapper &src_iter_d, + const memory_desc_wrapper &weights_layer_d, + const memory_desc_wrapper &weights_iter_d, + const memory_desc_wrapper &dst_layer_d); + +void set_conf(rnn_conf_t &rnn, const rnn_desc_t &rd, + const memory_desc_wrapper &weights_layer_d, + const memory_desc_wrapper &weights_iter_d, + const memory_desc_wrapper &diff_weights_layer_d, + const memory_desc_wrapper &diff_weights_iter_d); + +void set_offsets(const rnn_conf_t &rnn, size_t &ws_gates_offset, + size_t &ws_h_state_offset, size_t &ws_c_state_offset, + size_t &ws_diff_states_offset, size_t &ws_grid_comp_offset, + size_t &ws_cell_comp_offset, size_t &ws_bias_offset, + size_t &scratchpad_size, size_t &workspace_size); + +void get_scratchpad_and_workspace_sizes(const rnn_conf_t &rnn, + size_t &scratchpad_size, size_t &workspace_size); +status_t set_expected_desc( + rnn_conf_t &rnn, memory_desc_t &weights_md, bool is_iter); +status_t set_good_strides(memory_desc_t &weights_md, format_tag_t tag); + +template +struct ws_gates_aoc { + ws_gates_aoc(const rnn_conf_t &rnn, T *data) + : gates_(data, rnn.gates_nld, rnn.gates_ws_ld), DIC_(rnn.dic) {} + T &operator()(int batch, int gate, int dic) { + return gates_(batch, gate * DIC_ + dic); + } + +private: + mkldnn::impl::utils::array_offset_calculator gates_; + int DIC_; +}; +using ws_gates_aoc_t = ws_gates_aoc; +using ws_gates_aoc_s32_t = ws_gates_aoc; + +struct bias_aoc_t { + bias_aoc_t(const rnn_conf_t &rnn, const float *data) + : bias_(data, rnn.n_bias, rnn.dic) {} + const float &operator()(int bias_n, int dic) { return bias_(bias_n, dic); } + +private: + mkldnn::impl::utils::array_offset_calculator bias_; +}; + +template +struct ws_states_aoc { + ws_states_aoc(const rnn_conf_t &rnn, T *data) + : state_(data, rnn.states_nld, rnn.states_ws_ld) {} + T &operator()(int batch, int dic) { return state_(batch, dic); } + +private: + mkldnn::impl::utils::array_offset_calculator state_; +}; +using ws_states_aoc_t = ws_states_aoc; +using ws_states_aoc_u8_t = ws_states_aoc; + +struct ws_diff_states_aoc_t { + ws_diff_states_aoc_t(const rnn_conf_t &rnn, float *data) + : diff_states_(data, rnn.n_states + 1, rnn.n_iter + 1, rnn.states_nld, + rnn.states_ws_ld) {} + float &operator()(int state_n, int batch, int dic) { + return diff_states_(state_n, 0, batch, dic); + } + +private: + mkldnn::impl::utils::array_offset_calculator diff_states_; +}; + +struct ws_diff_w_iter_aoc_t { + ws_diff_w_iter_aoc_t(const rnn_conf_t &rnn, float *data) + : diff_weights_iter_( + data, rnn.diff_weights_iter_nld, rnn.diff_weights_iter_ld) + , DIC_(rnn.dic) {} + float &operator()(int sic, int gate, int dic) { + return diff_weights_iter_(sic, gate * DIC_ + dic); + } + +private: + mkldnn::impl::utils::array_offset_calculator diff_weights_iter_; + int DIC_; +}; +} +} +} +} +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.cpp new file mode 100644 index 000000000000..0420f87aa52f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_thread.hpp" + +#include "simple_concat.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace memory_tracking::names; + +template +status_t simple_concat_t::execute(const exec_ctx_t &ctx) const { + auto scratchpad = this->scratchpad(ctx); + auto iptrs = scratchpad.template get(key_concat_iptrs); + auto optrs = scratchpad.template get(key_concat_optrs); + auto nelems_to_copy = scratchpad.template get(key_concat_nelems); + auto is = scratchpad.template get(key_concat_istrides); + + const int num_arrs = pd()->n_inputs(); + const int *perm = pd()->perm_, *iperm = pd()->iperm_; + const int concat_dim = pd()->concat_dim(); + auto o_base_ptr = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + for (int a = 0; a < num_arrs; ++a) { + const memory_desc_wrapper i_d(pd()->src_md(a)); + const memory_desc_wrapper o_d(pd()->src_image_md(a)); + + iptrs[a] = CTX_IN_MEM(const data_t *, MKLDNN_ARG_MULTIPLE_SRC + a) + + i_d.blk_off(0); + optrs[a] = o_base_ptr + o_d.blk_off(0); + nelems_to_copy[a] = pd()->nelems_to_concat(i_d); + for (int i = 0; i < MKLDNN_MAX_NDIMS; i++) { + if (i < perm[concat_dim]) + is[a][i] = size_t(i_d.blocking_desc().strides[iperm[i]]); + else + is[a][i] = 0; + } + } + + const memory_desc_wrapper o_d(pd()->src_image_md(0)); + + strides_t os = { 0 }; + for (int i = 0; i < perm[concat_dim]; i++) + os[i] = o_d.blocking_desc().strides[iperm[i]]; + + dims_t phys_dims; + for (size_t i = 0; i < sizeof(phys_dims)/sizeof(phys_dims[0]); i++) + phys_dims[i] = (i < (size_t)perm[concat_dim]) + ? o_d.dims()[iperm[i]] / pd()->blocks_[iperm[i]] : 1; + + if (perm[concat_dim] == 0) { + for (int a = 0; a < num_arrs; ++a) { + const data_t *i = &iptrs[a][0]; + data_t *o = &optrs[a][0]; + parallel_nd((ptrdiff_t)nelems_to_copy[a], + [&](ptrdiff_t e) { o[e] = i[e]; }); + } + } else { + parallel_nd(phys_dims[0], phys_dims[1], phys_dims[2], phys_dims[3], + phys_dims[4], num_arrs, + [&](dim_t n0, dim_t n1, dim_t n2, dim_t n3, dim_t n4, int a) { + // XXX: this code may access uninitialized values in is[*][0-4] -- + // that's why we have to set them to zero although this is + // probably benign + size_t in_off = is[a][0] * n0 + is[a][1] * n1 + is[a][2] * n2 + + is[a][3] * n3 + is[a][4] * n4; + size_t out_off = os[0] * n0 + os[1] * n1 + os[2] * n2 + + os[3] * n3 + os[4] * n4; + const data_t *i = &iptrs[a][in_off]; + data_t *o = &optrs[a][out_off]; +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) + // The code below performs data copying: o[e] = i[e] + // and uses a workaround to make GNU compilers optimize it + uint8_t *ptro = reinterpret_cast(o); + const uint8_t *ptri = reinterpret_cast(i); + const dim_t main_part = + nelems_to_copy[a] * sizeof(data_t) / sizeof(uint32_t); + const dim_t tail_part = + nelems_to_copy[a] % sizeof(data_t) / sizeof(uint32_t); + + PRAGMA_OMP_SIMD() + for (dim_t e = 0; e < main_part; ++e) { + *(reinterpret_cast(ptro)) + = *(reinterpret_cast(ptri)); + ptro += sizeof(uint32_t); + ptri += sizeof(uint32_t); + } + for (dim_t e = 0; e < tail_part; ++e) { + *ptro = *ptri; + ++ptro; + ++ptri; + } +#else + PRAGMA_OMP_SIMD() + for (dim_t e = 0; e < nelems_to_copy[a]; ++e) o[e] = i[e]; +#endif + }); + } + + return status::success; +} + +template struct simple_concat_t; +template struct simple_concat_t; +template struct simple_concat_t; +template struct simple_concat_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp new file mode 100644 index 000000000000..057cc3c4c7e1 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp @@ -0,0 +1,155 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef SIMPLE_CONCAT_HPP +#define SIMPLE_CONCAT_HPP + +#include "memory_tracking.hpp" + +#include "cpu_concat_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct simple_concat_t: public cpu_primitive_t { + struct pd_t: public cpu_concat_pd_t { + using cpu_concat_pd_t::cpu_concat_pd_t; + + pd_t(const pd_t &rhs): cpu_concat_pd_t(rhs) { + int ndims = rhs.dst_md_.ndims; + utils::array_copy(perm_, rhs.perm_, ndims); + utils::array_copy(iperm_, rhs.iperm_, ndims); + utils::array_copy(blocks_, rhs.blocks_, ndims); + } + + DECLARE_CONCAT_PD_T("simple:any", simple_concat_t); + + status_t init() { + const memory_desc_wrapper dst_d(dst_md()); + bool ok = true + && cpu_concat_pd_t::init() == status::success + && dst_d.ndims() <= 6; + if (!ok) return status::unimplemented; + + for (size_t i = 0; i < src_mds_.size(); ++i) { + const memory_desc_wrapper i_d(&src_mds_[i]); + const memory_desc_wrapper o_d(&src_image_mds_[i]); + + const int ignore_strides = 0; + + ok = ok + && utils::everyone_is(data_type, i_d.data_type(), + o_d.data_type()) + && utils::everyone_is(format_kind::blocked, + i_d.format_kind(), o_d.format_kind()) + && types::blocking_desc_is_equal(i_d.blocking_desc(), + o_d.blocking_desc(), ignore_strides) + && types::blocking_desc_is_equal(i_d.blocking_desc(), + dst_d.blocking_desc(), ignore_strides) + && !i_d.is_additional_buffer(); + if (!ok) return status::unimplemented; + } + + dst_d.compute_blocks(blocks_); + format_perm(); + + // start dim is the first dimension after which the concatenation + // would happen contiguously + const int start_dim = perm_[concat_dim()]; + + // check that contiguous part is indeed contiguous (i.e. dense) + if (nelems_to_concat(dst_d) != + dst_d.padded_dims()[concat_dim()] / blocks_[concat_dim()] + * dst_d.blocking_desc().strides[concat_dim()]) + return status::unimplemented; + + // check that all inputs have the same strides for the + // contiguous part [concat_dim .. ndims] for the *major* dims. + // the block part is already checked above + for (size_t i = 0; i < src_mds_.size(); ++i) { + const memory_desc_wrapper i_d(&src_mds_[i]); + for (int d = start_dim; d < dst_d.ndims(); ++d) { + if (dst_d.blocking_desc().strides[iperm_[d]] + != i_d.blocking_desc().strides[iperm_[d]]) + return status::unimplemented; + } + } + + init_scratchpad(); + + return status::success; + } + + int perm_[MKLDNN_MAX_NDIMS] {}; + int iperm_[MKLDNN_MAX_NDIMS] {}; + dims_t blocks_ {}; + + dim_t nelems_to_concat(const memory_desc_wrapper &data_d) const { + const int ndims = data_d.ndims(); + + dim_t nelems = 1; + for (int i = perm_[concat_dim()]; i < ndims; i++) + nelems *= data_d.dims()[iperm_[i]] / blocks_[iperm_[i]]; + for (int i = 0; i < ndims; i++) + nelems *= blocks_[i]; + + return nelems; + } + + private: + void format_perm() { + const memory_desc_wrapper dst_d(dst_md()); + const int ndims = dst_d.ndims(); + + strides_t strides; + utils::array_copy(strides, dst_d.blocking_desc().strides, ndims); + for (int i = 0; i < ndims; i++) iperm_[i] = i; + + utils::simultaneous_sort(strides, iperm_, ndims, + [](stride_t a, stride_t b) { return b - a; }); + + for (int i = 0; i < ndims; i++) perm_[iperm_[i]] = i; + } + + void init_scratchpad() { + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + scratchpad.book(key_concat_iptrs, sizeof(data_t *) * n_inputs()); + scratchpad.book(key_concat_optrs, sizeof(data_t *) * n_inputs()); + scratchpad.book(key_concat_nelems, sizeof(dim_t) * n_inputs()); + scratchpad.book(key_concat_istrides, + sizeof(strides_t) * n_inputs()); + } + }; + + simple_concat_t(const pd_t *apd): cpu_primitive_t(apd) {} + + virtual status_t execute(const exec_ctx_t &ctx) const override; + + typedef typename prec_traits::type data_t; + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_q10n.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_q10n.hpp new file mode 100644 index 000000000000..e6c3b8d7afcd --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_q10n.hpp @@ -0,0 +1,98 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_SIMPLE_Q10N_HPP +#define CPU_SIMPLE_Q10N_HPP + +#include + +#include "c_types_map.hpp" +#include "math_utils.hpp" +#include "nstl.hpp" +#include "type_helpers.hpp" +#include "utils.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::math; + +template +inline out_t round_and_saturate(float f) +{ return math::saturate(out_round(f)); } + +/* Quantization with alpha == 1 and beta == 0 */ +template +struct qz_a1b0 { + out_t operator()(in_t in) + { return round_and_saturate((float)in); } +}; + +template +struct qz_a1b0::value + && !is_subset::value + >::type> { + out_t operator()(in_t in) { return math::saturate(in); } +}; + +template +struct qz_a1b0::value>::type> { + out_t operator()(in_t in) { return (out_t)in; } +}; + +/* Quantization with alpha == 1 */ +template struct qz_a1 { + out_t operator()(in_t in, out_t out, float beta) + { return round_and_saturate((float)in + beta * out); } +}; + +template struct qz_a1 { + float operator()(in_t in, float out, float beta) + { return (float)in + beta * out; } +}; + +/* Quantization with beta == 0 */ +template struct qz_b0 { + out_t operator()(in_t in, float alpha) + { return round_and_saturate(alpha * in); } +}; + +template struct qz_b0 { + float operator()(in_t in, float alpha) { return alpha * in; } +}; + +/* Quantization */ +template struct qz { + out_t operator()(in_t in, out_t out, float alpha, float beta) { + return round_and_saturate( + alpha * in + (beta ? beta * out : 0)); + } +}; + +template struct qz { + float operator()(in_t in, float out, float alpha, float beta) + { return alpha * in + (beta ? beta * out : 0); } +}; + +} +} +} + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_reorder.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_reorder.hpp new file mode 100644 index 000000000000..ff845f5bd36f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_reorder.hpp @@ -0,0 +1,1022 @@ +/******************************************************************************* +* Copyright 2016-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef CPU_SIMPLE_REORDER_HPP +#define CPU_SIMPLE_REORDER_HPP + +#include + +#include "c_types_map.hpp" +#include "type_helpers.hpp" +#include "math_utils.hpp" +#include "mkldnn_thread.hpp" +#include "utils.hpp" + +#include "tag_traits.hpp" +#include "cpu_reorder_pd.hpp" +#include "cpu_primitive.hpp" + +#include "simple_q10n.hpp" +#include "cpu_isa_traits.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +using namespace mkldnn::impl::status; +using namespace mkldnn::impl::format_tag; +using namespace mkldnn::impl::data_type; + +using bd = block_dim_t; +using ib = inner_blk_t; + +using namespace mkldnn::impl::utils; +using math::saturate; + +template +using data_t = typename prec_traits::type; + +template +using _qz_a1b0 = qz_a1b0, data_t>; + +template +using _qz = qz, data_t>; + +namespace fmt_order { + const bool keep = true; + const bool reverse = false; + const bool any = keep; +} + +namespace spec { +struct direct_copy {}; +struct direct_copy_except_dim_0 {}; +struct reference {}; +struct conv_s8s8 {}; +} + +#define SIMPLE_REORDER_TEMPL_DECL \ + impl::data_type_t type_i, impl::format_tag_t tag_i, \ + impl::data_type_t type_o, impl::format_tag_t tag_o, bool order_keep +#define SIMPLE_REORDER_TEMPL_CALL \ + type_i, tag_i, type_o, tag_o, order_keep + +#define DECLARE_COMMON_PARAMS() \ + const memory_desc_wrapper &input_d = pd->src_md(); \ + const memory_desc_wrapper &output_d = pd->dst_md(); \ + const float alpha = pd->alpha(); MAYBE_UNUSED(alpha); \ + const float beta = pd->beta(); MAYBE_UNUSED(beta); + +/* specific reorders: common template */ +template +struct simple_reorder_impl {}; + +namespace { +inline bool simple_fmt_check(bool order_keep, impl::format_tag_t tag_i, + impl::format_tag_t tag_o, const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d) { + return input_d.matches_tag(order_keep ? tag_i : tag_o) + && output_d.matches_tag(order_keep ? tag_o : tag_i); +} +inline bool simple_attr_check(const primitive_attr_t *attr, bool many_scales_support) { + if (many_scales_support) + return true; + return IMPLICATION(attr, attr->output_scales_.mask_ == 0); +} +} + +/* specific reorders: implementation */ +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) + { + const size_t D_mask = utils::array_product(input_d.dims(), + math::ilog2q(attr->output_scales_.mask_ + 1)); + const int oc = (input_d.dims()[tag_o == hwigo + 0]); + const int g = (tag_o == hwigo) ? (input_d.dims()[0]) : 1; + + return output_d.matches_tag(tag_o) + && (output_d.extra().flags & memory_extra_flags::compensation_conv_s8s8) + && (input_d.data_type() == f32 || input_d.data_type() == s8) + && output_d.data_type() == s8 + && (D_mask == 1 || D_mask == (size_t)g * oc); + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + static constexpr bool w_groups = tag_o == hwigo; + + const auto &dims = input_d.dims(); + const auto &pdims = output_d.padded_dims(); + + const int G = w_groups ? dims[0] : 1; + const int OC = dims[w_groups + 0]; + const int IC = dims[w_groups + 1]; + const int H = dims[w_groups + 2]; + const int W = dims[w_groups + 3]; + + const float *scales = pd->attr()->output_scales_.scales_; + const size_t D_mask = utils::array_product(input_d.dims(), + math::ilog2q(pd->attr()->output_scales_.mask_ + 1)); + + assert(output_d.extra().flags + & memory_extra_flags::compensation_conv_s8s8); + float adj_scale = + (output_d.extra().flags & memory_extra_flags::scale_adjust) + ? output_d.extra().scale_adjust : 1.f; + + size_t offset = G * pdims[w_groups + 0] * pdims[w_groups + 1] * H * W; + int32_t *cp = reinterpret_cast(output + offset); + + parallel_nd(G, OC, [&](int g, int oc) { + cp[g * OC + oc] = 0; + for (int ic = 0; ic < IC; ic++) + for (int h = 0; h < H; h++) + for (int w = 0; w < W; w++) { + auto i = input[input_d.blk_off(g, oc, ic, h, w)]; + auto &o = output[output_d.blk_off(g, oc, ic, h, w)]; + const float s = scales[(D_mask == 1) ? 0 : g * OC + oc]; + + o = qz_b0, data_t>()( + i, s * adj_scale); + cp[g * OC + oc] -= (int32_t)o; + } + cp [g * OC + oc] *= 128; + }); + return success; + } +}; + +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) + { + const size_t D_mask = utils::array_product(input_d.dims(), + math::ilog2q(attr->output_scales_.mask_ + 1)); + const bool w_groups = !utils::one_of(tag_o, OIw4i16o4i, OIhw4i16o4i); + const int oc = (input_d.dims()[w_groups ? 1 : 0]); + const int g = w_groups ? input_d.dims()[0] : 1; + + return input_d.matches_tag(tag_i) + && output_d.matches_tag(tag_o) + && (output_d.extra().flags & memory_extra_flags::compensation_conv_s8s8) + && (input_d.data_type() == f32 || input_d.data_type() == s8) + && output_d.data_type() == s8 + && (D_mask == 1 || D_mask == (size_t)g * oc); + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + static constexpr bool w_groups = + !utils::one_of(tag_o, OIw4i16o4i, OIhw4i16o4i); + constexpr int is_1d = + utils::one_of(tag_o, gOIw4i16o4i, OIw4i16o4i); + constexpr int blksize = tag_traits::inner_blks == ib::_4b4c + ? 4 + : tag_traits::inner_blks == ib::_2c8b4c + ? 8 + : 16; + + const auto &_g_oihw_d = order_keep ? input_d : output_d; + const auto &dims = input_d.dims(); + const auto &pdims = order_keep + ? output_d.padded_dims() + : input_d.padded_dims(); + + const int G = w_groups ? dims[0] : 1; + const int OC = dims[w_groups + 0]; + const int NB_OC = pdims[w_groups + 0] / blksize; + const int IC = dims[w_groups + 1]; + const int NB_IC = pdims[w_groups + 1] / blksize; + const int H = is_1d ? 1 : dims[w_groups + 2]; + const int W = dims[w_groups + 3 - is_1d]; + + const float *scales = pd->attr()->output_scales_.scales_; + const size_t D_mask = utils::array_product(input_d.dims(), + math::ilog2q(pd->attr()->output_scales_.mask_ + 1)); + + assert(output_d.extra().flags + & memory_extra_flags::compensation_conv_s8s8); + float adj_scale = + (output_d.extra().flags & memory_extra_flags::scale_adjust) + ? output_d.extra().scale_adjust : 1.f; + + auto ker = [&](const data_t *inp, data_t *out, + int32_t *c, const float *s, const int oc_block, const int ic_block) { +# define index AB_or_BC_blk_off::inner_blks> + + for (int ic = 0; ic < ic_block; ++ic) { + for (int oc = 0; oc < oc_block; ++oc) { + const auto _g_oihw_off = + oc * _g_oihw_d.blocking_desc().strides[w_groups + 0] + + ic * _g_oihw_d.blocking_desc().strides[w_groups + 1]; + out[index(oc, ic)] + = qz_b0, data_t>()( + inp[_g_oihw_off], s[oc] * adj_scale); + c[oc] -= (128 * (int32_t)(out[index(oc, ic)])); + } + } +# undef index + }; + + constexpr int i_mult = blksize; + constexpr int o_mult = 1; + + size_t offset = G * pdims[w_groups+0] * pdims[w_groups+1] * H * W; + int32_t *cp = reinterpret_cast(output + offset); + parallel_nd(G * NB_OC * blksize, [&](int i) { + cp[i] = 0; + }); + +# define wei_blk_off(md, g, o, i, h, w) \ + (is_1d ? (md).blk_off(g, o, i, w) \ + : (md).blk_off(g, o, i, h, w)) + + parallel_nd(G, NB_OC, [&](int g, int O) { + for (int I = 0; I < NB_IC; I++) + for (int h = 0; h < H; h++) + for (int w = 0; w < W; w++) { + auto i = &input[wei_blk_off( + input_d, g, i_mult * O, i_mult * I, h, w)]; + auto o = &output[wei_blk_off( + output_d, g, o_mult * O, o_mult * I, h, w)]; + const int oc_block = nstl::min(blksize, OC - O * blksize); + const int ic_block = nstl::min(blksize, IC - I * blksize); + + int _offset = (g * NB_OC + O) * blksize; + ker(i, o, (order_keep) ? &cp[_offset] : nullptr, + &scales[(D_mask == 1) ? 0 : _offset], + oc_block, ic_block); + } + }); + +# undef wei_blk_off + + return success; + } +}; + +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) { + const size_t D_mask = utils::array_product(input_d.dims(), + math::ilog2q(attr->output_scales_.mask_ + 1)); + const int oc = input_d.dims()[1]; + const int g = input_d.dims()[0]; + + return true + && order_keep + && input_d.matches_tag(tag_i) + && output_d.matches_tag(tag_o) + && (output_d.extra().flags & memory_extra_flags::compensation_conv_s8s8) + && (input_d.data_type() == f32 || input_d.data_type() == s8) + && output_d.data_type() == s8 + && (D_mask == 1 || D_mask == (size_t)g * oc); + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + constexpr bool is_1d = tag_i == goiw; + constexpr int blksize = 16; + + const auto &dims = input_d.dims(); + const auto &pdims = output_d.padded_dims(); + const int G = dims[0]; + const int Gp = pdims[0]; + const int OC = dims[1]; + const int IC = dims[2]; + const int H = is_1d ? 1 : dims[3]; + const int W = dims[4 - is_1d]; + + const size_t D_mask = utils::array_product(input_d.dims(), + math::ilog2q(pd->attr()->output_scales_.mask_ + 1)); + const float *scales = pd->attr()->output_scales_.scales_; + + assert(output_d.extra().flags + & memory_extra_flags::compensation_conv_s8s8); + float adj_scale = + (output_d.extra().flags & memory_extra_flags::scale_adjust) + ? output_d.extra().scale_adjust : 1.f; + + auto ker = [&](const data_t *inp, data_t *out, + int32_t *cp, const float *s, const int g_block) { + PRAGMA_OMP_SIMD() + for (int g = 0; g < g_block; g++) { + const auto i_off = g * input_d.blocking_desc().strides[0]; + out[g] = qz_b0, data_t>()( + inp[i_off], s[g * OC] * adj_scale); + cp[g * OC] -= 128 * (int32_t)(out[g]); + } + }; + + size_t cp_offset = output_d.size() - output_d.additional_buffer_size(); + int32_t *cp = reinterpret_cast(output + cp_offset); + parallel_nd((Gp/blksize) * OC, [&](int ib) { + PRAGMA_OMP_SIMD() + for (int i = 0; i < blksize; i++) + cp[ib * blksize + i] = 0; + }); + +# define wei_blk_off(md, g, o, i, h, w) \ + (is_1d ? (md).blk_off(g, o, i, w) : (md).blk_off(g, o, i, h, w)) + + parallel_nd(Gp/blksize, OC, [&](int gb, int O) { + for (int I = 0; I < IC; I++) { + for (int h = 0; h < H; h++) + for (int w = 0; w < W; w++) + { + const int g_block = nstl::min(G - gb * blksize, blksize); + const auto inp = &input[wei_blk_off( + input_d, gb * blksize, O, I, h, w)]; + const auto out = &output[wei_blk_off( + output_d, gb, O, I, h, w)]; + int offset = gb * blksize + O; + ker(inp, out, &cp[offset], + &scales[(D_mask == 1) ? 0 : offset], g_block); + } + } + }); + +# undef wei_blk_off + + return success; + } +}; + +/* reorders with tail support */ + +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) + { + return simple_fmt_check(order_keep, tag_i, tag_o, input_d, output_d) + && simple_attr_check(attr, false); + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + constexpr int is_1d = tag_i == nCw8c; + constexpr int is_3d = tag_i == nCdhw8c; + constexpr int blksize_16 = 16; + constexpr int blksize_8 = 8; + constexpr int ic_mult = order_keep ? 2 : 1; + constexpr int oc_mult = order_keep ? 1 : 2; + + const auto &dims = input_d.dims(); + const auto &pdims = order_keep ? output_d.padded_dims() + : input_d.padded_dims(); + + const int C = dims[1]; + const int D = is_3d ? dims[2] : 1; + const int H = is_1d ? 1 : dims[2 + is_3d]; + const int W = dims[3 + is_3d - is_1d]; + + auto ker = [&](const data_t *i, data_t *o, + const int block_16) { + const int nb = (block_16 - 1) / blksize_8 + 1; + if (alpha == 1.0 && beta == 0.0) { + for (int b = 0; b < nb; ++b) { + const ptrdiff_t i_off = order_keep ? b : b * blksize_8; + const ptrdiff_t o_off = order_keep ? b * blksize_8 : b; + const int block_8 = nstl::min(blksize_8, + block_16 - b * blksize_8); + for (int c = 0; c < block_8; ++c) { + o[o_off + c] = _qz_a1b0()( + i[i_off + c]); + } + } + } else { + for (int b = 0; b < nb; ++b) { + const ptrdiff_t i_off = order_keep ? b : b * blksize_8; + const ptrdiff_t o_off = order_keep ? b * blksize_8 : b; + const int block_8 = nstl::min(blksize_8, + block_16 - b * blksize_8); + for (int c = 0; c < block_8; ++c) { + o[o_off + c] = _qz()(i[i_off + c], + o[o_off + c], alpha, beta); + } + } + } + }; + +# define data_blk_off(md, n, c, d, h, w) \ + ( is_1d ? (md).blk_off(n, c, w) \ + : is_3d ? (md).blk_off(n, c, d, h, w) : (md).blk_off(n, c, h, w)) + + parallel_nd(dims[0], pdims[1] / blksize_16, D, H, W, + [&](int n, int nb_c, int d, int h, int w) { + auto i = &input[data_blk_off(input_d, n, ic_mult * nb_c, d, h, w)]; + auto o = &output[data_blk_off(output_d, n, oc_mult * nb_c, d, h, w)]; + const int block_16 = nstl::min(blksize_16, C - nb_c * blksize_16); + ker(i, o, block_16); + }); + +# undef data_blk_off + + return success; + } +}; + +#define PLAIN_TO_BLOCKED_IS_APPLICABLE() \ + static bool is_applicable(const memory_desc_wrapper &input_d, \ + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) { \ + return simple_attr_check(attr, false) && (order_keep \ + ? output_d.matches_tag(tag_o) && input_d.is_plain() \ + : input_d.matches_tag(tag_o) && output_d.is_plain()); \ + } + +template +struct simple_reorder_impl::block_dims == bd::_A + || tag_traits::block_dims == bd::_B) + && tag_traits::ndims >= 3 + && tag_traits::ndims <= 6 + >::type> +{ + PLAIN_TO_BLOCKED_IS_APPLICABLE(); + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + const auto &flat_d = order_keep ? input_d : output_d; + const auto &block_d = order_keep ? output_d : input_d; + const auto &dims = input_d.dims(); + const auto &pdims = block_d.padded_dims(); + + constexpr int ndims = tag_traits::ndims; + constexpr int blk_idx = tag_traits::block_dims == bd::_A ? 0 : 1; + + const dim_t H0 = dims[0]; + const dim_t H1 = dims[1]; + const dim_t M0 = ndims >= 6 ? dims[ndims - 4] : 1; + const dim_t M1 = ndims >= 5 ? dims[ndims - 3] : 1; + const dim_t M2 = ndims >= 4 ? dims[ndims - 2] : 1; + const dim_t L = dims[ndims - 1]; + const dim_t l_blk_stride = block_d.blocking_desc().strides[ndims - 1]; + + constexpr int blksize = false ? 0 + : utils::one_of(tag_traits::inner_blks, ib::_4a, ib::_4b) ? 4 + : utils::one_of(tag_traits::inner_blks, ib::_8a, ib::_8b) ? 8 + : 16; + + auto ker = [&](const data_t *i, data_t *o, int block) { + if (alpha == 1.0 && beta == 0.0) { + for (int l = 0; l < L; ++l) + for (int blk = 0; blk < block; ++blk) { + const dim_t flat_off = 0 + + blk * flat_d.blocking_desc().strides[blk_idx] + + l * flat_d.blocking_desc().strides[ndims - 1]; + if (order_keep) { + o[l * l_blk_stride + blk] = _qz_a1b0()( + i[flat_off]); + } else { + o[flat_off] = _qz_a1b0()( + i[l * l_blk_stride + blk]); + } + } + } else { + for (int l = 0; l < L; ++l) + for (int blk = 0; blk < block; ++blk) { + const dim_t flat_off = 0 + + blk * flat_d.blocking_desc().strides[blk_idx] + + l * flat_d.blocking_desc().strides[ndims - 1]; + if (order_keep) { + o[l * l_blk_stride + blk] = _qz()( + i[flat_off], o[l * blksize + blk], + alpha, beta); + } else { + o[flat_off] = _qz()( + i[l * l_blk_stride + blk], o[flat_off], + alpha, beta); + } + } + } + }; + +# define off(md, h0, h1, m0, m1, m2) \ + (ndims >= 6 ? (md).blk_off(h0, h1, m0, m1, m2) \ + : ndims >= 5 ? (md).blk_off(h0, h1, m1, m2) \ + : ndims >= 4 ? (md).blk_off(h0, h1, m2) \ + : /* ndims >= 3 ? */ (md).blk_off(h0, h1)) + + constexpr int i_mult = order_keep ? blksize : 1; + constexpr int o_mult = order_keep ? 1 : blksize; + + if (blk_idx == 0) { + const dim_t BH0 = pdims[0] / blksize; + parallel_nd(BH0, H1, M0, M1, M2, + [&](dim_t bh0, dim_t h1, dim_t m0, dim_t m1, dim_t m2) { + auto i = &input[off(input_d, bh0 * i_mult, h1, m0, m1, m2)]; + auto o = &output[off(output_d, bh0 * o_mult, h1, m0, m1, m2)]; + const int block = nstl::min(blksize, H0 - bh0 * blksize); + ker(i, o, block); + }); + } else if (blk_idx == 1) { + const dim_t BH1 = pdims[1] / blksize; + parallel_nd(H0, BH1, M0, M1, M2, + [&](dim_t h0, dim_t bh1, dim_t m0, dim_t m1, dim_t m2) { + auto i = &input[off(input_d, h0, bh1 * i_mult, m0, m1, m2)]; + auto o = &output[off(output_d, h0, bh1 * o_mult, m0, m1, m2)]; + const int block = nstl::min(blksize, H1 - bh1 * blksize); + ker(i, o, block); + }); + } else { + assert(!"unimplemented"); + } + +# undef off + + return success; + } +}; + +template +struct simple_reorder_impl::block_dims == bd::_AB + || tag_traits::block_dims == bd::_BC) + && IMPLICATION(tag_traits::block_dims == bd::_AB, + tag_traits::ndims >= 3 && tag_traits::ndims <= 5) + && IMPLICATION(tag_traits::block_dims == bd::_BC, + tag_traits::ndims >= 4 && tag_traits::ndims <= 6) + >::type> +{ + PLAIN_TO_BLOCKED_IS_APPLICABLE(); + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + const auto &flat_d = order_keep ? input_d : output_d; + const auto &dims = input_d.dims(); + const auto &pdims = order_keep + ? output_d.padded_dims() + : input_d.padded_dims(); + + constexpr int ndims = tag_traits::ndims; + + static constexpr bool with_g = tag_traits::block_dims == bd::_BC; + const dim_t G = with_g ? dims[0] : 1; + + const dim_t H0 = dims[0 + with_g]; + const dim_t H1 = dims[1 + with_g]; + + const dim_t M0 = ndims >= 5 + with_g ? dims[ndims - 3] : 1; + const dim_t M1 = ndims >= 4 + with_g ? dims[ndims - 2] : 1; + const dim_t M2 = ndims >= 3 + with_g ? dims[ndims - 1] : 1; + + constexpr int blksize_0 = false ? 0 + : utils::one_of(tag_traits::inner_blks, + ib::_4b4a, ib::_4b4c, ib::_4c4b) + ? 4 + : utils::one_of(tag_traits::inner_blks, + ib::_8a8b, ib::_8b8a, ib::_8b8c, ib::_8c8b, ib::_2c8b4c) + ? 8 + : utils::one_of(tag_traits::inner_blks, + ib::_16a16b, ib::_16a4b, ib::_16b16a, ib::_16b4c, + ib::_16b16c, ib::_16c16b, ib::_8a16b2a, ib::_4b16a4b, + ib::_8b16a2b, ib::_8b16c2b, ib::_4c16b4c, ib::_8c16b2c) + ? 16 : INT_MIN; + + constexpr int blksize_1 = utils::one_of(tag_traits::inner_blks, + ib::_8a8b, ib::_8b8a, ib::_8b8c, ib::_8c8b, ib::_2c8b4c) + ? 8 + : utils::one_of(tag_traits::inner_blks, + ib::_16a16b, ib::_16b16a, ib::_16b16c, ib::_16c16b, + ib::_8a16b2a, ib::_4b16a4b, ib::_8b16a2b, ib::_8b16c2b, + ib::_4c16b4c, ib::_8c16b2c) + ? 16 + : utils::one_of(tag_traits::inner_blks, + ib::_4b4a, ib::_4b4c, ib::_4c4b, + ib::_16a4b, ib::_16b4c) + ? 4 + : INT_MIN; + + const dim_t NB_H0 = pdims[0 + with_g] / blksize_0; + const dim_t NB_H1 = pdims[1 + with_g] / blksize_1; + + auto ker = [&](const data_t *i, data_t *o, + const int block_h0, const int block_h1) { +# define blk_off AB_or_BC_blk_off::inner_blks> + + if (alpha == 1.0 && beta == 0.0) { + for (int h0 = 0; h0 < block_h0; ++h0) + for (int h1 = 0; h1 < block_h1; ++h1) { + const dim_t flat_off = 0 + + h0 * flat_d.blocking_desc().strides[with_g + 0] + + h1 * flat_d.blocking_desc().strides[with_g + 1]; + if (order_keep) { + o[blk_off(h0, h1)] = _qz_a1b0()( + i[flat_off]); + } else { + o[flat_off] = _qz_a1b0()( + i[blk_off(h0, h1)]); + } + } + } else { + for (int h0 = 0; h0 < block_h0; ++h0) + for (int h1 = 0; h1 < block_h1; ++h1) { + const dim_t flat_off = 0 + + h0 * flat_d.blocking_desc().strides[with_g + 0] + + h1 * flat_d.blocking_desc().strides[with_g + 1]; + if (order_keep) { + o[blk_off(h0, h1)] = _qz()(i[flat_off], + o[blk_off(h0, h1)], alpha, beta); + } else { + o[flat_off] = _qz()(i[blk_off(h0, h1)], + o[flat_off], alpha, beta); + } + } + } + +# undef blk_off + }; + + constexpr int i_mult_0 = order_keep ? blksize_0 : 1; + constexpr int o_mult_0 = order_keep ? 1 : blksize_0; + + constexpr int i_mult_1 = order_keep ? blksize_1 : 1; + constexpr int o_mult_1 = order_keep ? 1 : blksize_1; + +# define off(md, g, h0, h1, m0, m1, m2) \ + (ndims >= 5 + with_g ? (md).blk_off(g, h0, h1, m0, m1, m2) \ + : ndims >= 4 + with_g ? (md).blk_off(g, h0, h1, m1, m2) \ + : /* ndims >= 3 + with_g ? */ (md).blk_off(g, h0, h1, m2)) + + parallel_nd(G, NB_H0, NB_H1, M0, M1, M2, + [&](dim_t g, dim_t nb_h0, dim_t nb_h1, dim_t m0, dim_t m1, dim_t m2) { + auto i = &input[off(input_d, + g, i_mult_0 * nb_h0, i_mult_1 * nb_h1, m0, m1, m2)]; + auto o = &output[off(output_d, + g, o_mult_0 * nb_h0, o_mult_1 * nb_h1, m0, m1, m2)]; + const int block_h0 = nstl::min(blksize_0, H0 - nb_h0 * blksize_0); + const int block_h1 = nstl::min(blksize_1, H1 - nb_h1 * blksize_1); + ker(i, o, block_h0, block_h1); + }); + +# undef off + + return success; + } +}; + +/* generic and direct-copy reorders */ + +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) { + /* FIXME: is the formula correct? */ + return input_d.similar_to(output_d, true, false, 0) + && input_d.is_dense() && output_d.is_dense() + && simple_attr_check(attr, false); + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + assert(input_d.is_dense()); + + input += input_d.blk_off(0); + output += output_d.blk_off(0); + + const size_t nelems = input_d.nelems(); + + constexpr int block_size = 16; + const auto num_blocks = nelems / block_size; + const auto rem_elems = nelems % block_size; + + parallel(0, [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + balance211(num_blocks, nthr, ithr, start, end); + start = start * block_size; + end = end * block_size; + + if (alpha == 1.0 && beta == 0.0) { + PRAGMA_OMP_SIMD() + for (size_t e = start; e < end; ++e) { + output[e] = qz_a1b0, data_t>() + (input[e]); + } + } else if (alpha == 1.0) { + PRAGMA_OMP_SIMD() + for (size_t e = start; e < end; ++e) { + output[e] = qz_a1, data_t>() + (input[e], output[e], beta); + } + } else if (beta == 0.0) { + PRAGMA_OMP_SIMD() + for (size_t e = start; e < end; ++e) { + output[e] = qz_b0, data_t>() + (input[e], alpha); + } + } else { + PRAGMA_OMP_SIMD() + for (size_t e = start; e < end; ++e) { + output[e] = qz, data_t>() + (input[e], output[e], alpha, beta); + } + } + + if (rem_elems != 0 && ithr == nthr - 1){ + if (alpha == 1.0 && beta == 0.0) { + PRAGMA_OMP_SIMD() + for (size_t e = nelems - rem_elems; e < nelems; ++e) { + output[e] = qz_a1b0, + data_t>()(input[e]); + } + } else if (alpha == 1.0) { + PRAGMA_OMP_SIMD() + for (size_t e = nelems - rem_elems; e < nelems; ++e) { + output[e] = qz_a1, + data_t>()(input[e], output[e], beta); + } + } else if (beta == 0.0) { + PRAGMA_OMP_SIMD() + for (size_t e = nelems - rem_elems; e < nelems; ++e) { + output[e] = qz_b0, + data_t>()(input[e], alpha); + } + } else { + PRAGMA_OMP_SIMD() + for (size_t e = nelems - rem_elems; e < nelems; ++e) { + output[e] = qz, data_t>() + (input[e], output[e], alpha, beta); + } + } + } + }); + return success; + } +}; + +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) { + auto is_dense_no_0 = [](const memory_desc_wrapper &data_d) { + return nelems_no_dim_0(data_d) == _size_no_dim_0(data_d); + }; + /* FIXME: is the formula correct? */ + return input_d.similar_to(output_d, true, false, 1) + && is_dense_no_0(input_d) && is_dense_no_0(output_d) + && simple_attr_check(attr, false); + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + input += input_d.blk_off(0); + output += output_d.blk_off(0); + + const int N = input_d.dims()[0]; + const dim_t is = input_d.blocking_desc().strides[0]; + const dim_t os = output_d.blocking_desc().strides[0]; + const dim_t nelems_no_d0 = nelems_no_dim_0(input_d); + const dim_t work_amount = N * nelems_no_d0; + + if (alpha == 1.0 && beta == 0.0) { + parallel(0, [&](const int ithr, const int nthr) { + dim_t n{0}, dim1_s{0}; + dim_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + nd_iterator_init(start, n, N, dim1_s, nelems_no_d0); + while(start < end) { + dim_t work_rem = end - start; + dim_t dim1_e = dim1_s + work_rem > nelems_no_d0 + ? nelems_no_d0 : dim1_s + work_rem; + PRAGMA_OMP_SIMD() + for (dim_t e = dim1_s; e < dim1_e; ++e) { + output[os * n + e] = _qz_a1b0()( + input[is * n + e]); + } + nd_iterator_jump(start, end, n, N, dim1_s, nelems_no_d0); + } + }); + } else { + parallel(0, [&](const int ithr, const int nthr) { + dim_t n{0}, dim1_s{0}; + dim_t start{0}, end{0}; + balance211(work_amount, nthr, ithr, start, end); + nd_iterator_init(start, n, N, dim1_s, nelems_no_d0); + while(start < end) { + dim_t work_rem = end - start; + dim_t dim1_e = + dim1_s + work_rem > nelems_no_d0 ? nelems_no_d0 + : dim1_s + work_rem; + PRAGMA_OMP_SIMD() + for (dim_t e = dim1_s; e < dim1_e; ++e){ + output[os * n + e] = _qz()( + input[is * n + e], output[os * n + e], alpha, + beta); + } + nd_iterator_jump(start, end, n, N, dim1_s, nelems_no_d0); + } + }); + } + + return success; + } + +private: + static dim_t nelems_no_dim_0(const memory_desc_wrapper &data_d) { + const int ndims = data_d.ndims(); + if (ndims <= 1) return 1; + return utils::array_product(data_d.dims() + 1, data_d.ndims() - 1); + } + + static dim_t _size_no_dim_0(const memory_desc_wrapper &data_d) { + dims_t blocks; + data_d.compute_blocks(blocks); + + const auto &blk = data_d.blocking_desc(); + + dim_t blk_size = 1; + for (int iblk = 0; iblk < blk.inner_nblks; ++iblk) + blk_size *= blk.inner_blks[iblk]; + + dim_t max_size = blk_size; + for (int d = 1; d < data_d.ndims(); ++d) { + max_size = nstl::max(max_size, + data_d.padded_dims()[d] / blocks[d] * blk.strides[d]); + } + + return max_size; + } +}; + +template +struct simple_reorder_impl::type> +{ + static bool is_applicable(const memory_desc_wrapper &input_d, + const memory_desc_wrapper &output_d, const primitive_attr_t *attr) { + /* supported smask: 0x0...011..10...0, + * i.e. 1 should be contiguous */ + int smask = attr ? attr->output_scales_.mask_ : 0; + for (; smask > 0 && !(smask & 0x1); smask >>= 1); + for (; smask > 0 && smask & 0x1; smask >>= 1); + return true + && input_d.is_blocking_desc() + && output_d.is_blocking_desc() + && !output_d.is_additional_buffer() + && !input_d.is_additional_buffer() + && smask == 0; + } + + static status_t execute(const cpu_reorder_pd_t *pd, + const data_t *input, data_t *output) { + DECLARE_COMMON_PARAMS(); + + const size_t nelems = input_d.nelems(); + + int ndims_start = 0, ndims_mask = 0; + int smask = pd->attr()->output_scales_.mask_; + for (; smask > 0 && !(smask & 0x1); smask >>= 1) ++ndims_start; + for (; smask > 0 && smask & 0x1; smask >>= 1) ++ndims_mask; + assert(smask == 0); + + const ptrdiff_t D_start + = utils::array_product(input_d.dims(), ndims_start); + const ptrdiff_t D_mask + = utils::array_product(input_d.dims() + ndims_start, ndims_mask); + const ptrdiff_t D_rest = nelems / D_start / D_mask; + + const float *scales = pd->attr()->output_scales_.scales_; + + parallel_nd(D_start, D_mask, D_rest, + [&](ptrdiff_t ds, ptrdiff_t dm, ptrdiff_t dr) { + const float scale = scales[dm]; + + const size_t e = (ds * D_mask + dm) * D_rest + dr; + const auto &i = input[input_d.off_l(e)]; + auto &o = output[output_d.off_l(e)]; + + o = _qz()(i, o, scale, beta); + }); + + return success; + } +}; + + +/* high level class declaration */ + +template +struct simple_reorder_t: public cpu_primitive_t { + struct pd_t: public cpu_reorder_pd_t { + using cpu_reorder_pd_t::cpu_reorder_pd_t; + + DECLARE_COMMON_PD_T("simple:any", simple_reorder_t); + + static status_t create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { + bool args_ok = true + && src_md->data_type == type_i + && dst_md->data_type == type_o + && simple_reorder_impl:: + is_applicable(src_md, dst_md, attr); + if (!args_ok) + return status::invalid_arguments; + + auto _pd = new pd_t(engine, attr, src_engine, src_md, dst_engine, + dst_md); + if (_pd == nullptr) return status::out_of_memory; + if (_pd->init() != status::success) { + delete _pd; + return status::unimplemented; + } + return safe_ptr_assign(*reorder_pd, _pd); + } + }; + + simple_reorder_t(const pd_t *apd): cpu_primitive_t(apd) {} + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto input = CTX_IN_MEM(const data_t *, MKLDNN_ARG_FROM); + auto output = CTX_OUT_MEM(data_t *, MKLDNN_ARG_TO); + simple_reorder_impl::execute( + pd(), input, output); + return status::success; + } + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +#undef SIMPLE_REORDER_TEMPL_DECL +#undef SIMPLE_REORDER_TEMPL_CALL + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.cpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.cpp new file mode 100644 index 000000000000..f0947573a932 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#include "mkldnn_thread.hpp" + +#include "simple_sum.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +status_t simple_sum_t::execute(const exec_ctx_t &ctx) const { + auto output = CTX_OUT_MEM(data_t *, MKLDNN_ARG_DST); + + const memory_desc_wrapper o_d(pd()->dst_md()); + output += o_d.blk_off(0); + + const int num_arrs = pd()->n_inputs(); + const data_t *input_ptrs[max_num_arrs]; + const size_t nelems = o_d.nelems(); + + for (int a = 0; a < num_arrs; ++a) { + const memory_desc_wrapper i_d(pd()->src_md(a)); + input_ptrs[a] = CTX_IN_MEM(const data_t *, MKLDNN_ARG_MULTIPLE_SRC + a) + + i_d.blk_off(0); + } + + const size_t block_size = 16 * 1024 / sizeof(data_type); + const size_t blocks_number = nelems / block_size; + const size_t tail = nelems % block_size; + + const auto scales = pd()->scales(); + parallel(0, [&](const int ithr, const int nthr) { + size_t start{0}, end{0}; + balance211(blocks_number, nthr, ithr, start, end); + + for (size_t nb = start; nb < end; ++nb) { + size_t start_e = nb * block_size; + size_t end_e = start_e + block_size; + + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] = data_t(scales[0] * input_ptrs[0][e]); + } + for (int a = 1; a < num_arrs; a++) { + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] += data_t(scales[a] * input_ptrs[a][e]); + } + } + } + + if (tail != 0 && ithr == nthr - 1) { + size_t start_e = nelems - tail; + size_t end_e = nelems; + + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] = data_t(scales[0] * input_ptrs[0][e]); + } + for (int a = 1; a < num_arrs; a++) { + PRAGMA_OMP_SIMD() + for (size_t e = start_e; e < end_e; e++) { + output[e] += data_t(scales[a] * input_ptrs[a][e]); + } + } + } + }); + + return status::success; +} + +template struct simple_sum_t; + +} +} +} diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.hpp new file mode 100644 index 000000000000..2a0187a1845f --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_sum.hpp @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright 2017-2018 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +#ifndef SIMPLE_SUM_HPP +#define SIMPLE_SUM_HPP + +#include "cpu_sum_pd.hpp" +#include "cpu_primitive.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct simple_sum_t: public cpu_primitive_t { + struct pd_t: public cpu_sum_pd_t { + using cpu_sum_pd_t::cpu_sum_pd_t; + + DECLARE_SUM_PD_T("simple:any", simple_sum_t); + + status_t init() { + const int n = n_inputs(); + + bool ok = true + && cpu_sum_pd_t::init() == status::success + && n <= max_num_arrs; + if (!ok) return status::unimplemented; + + const memory_desc_wrapper o_d(dst_md()); + ok = ok + && o_d.data_type() == data_type + && o_d.is_dense(); + if (!ok) return status::unimplemented; + + for (int i = 0; i < n; ++i) { + const memory_desc_wrapper i_d(src_md(i)); + if (i_d != o_d) return status::unimplemented; + } + + return status::success; + } + }; + + simple_sum_t(const pd_t *apd): cpu_primitive_t(apd) {} + + virtual status_t execute(const exec_ctx_t &ctx) const override; + + enum {max_num_arrs = 16 }; + typedef typename prec_traits::type data_t; + +private: + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } +}; + +} +} +} + +#endif + +// vim: et ts=4 sw=4 cindent cino^=l0,\:0,N-s diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/wino_reorder.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/wino_reorder.hpp new file mode 100644 index 000000000000..c2082d7d62f4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/wino_reorder.hpp @@ -0,0 +1,376 @@ +/******************************************************************************* + * Copyright 2017-2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef CPU_WINO_REORDER_HPP +#define CPU_WINO_REORDER_HPP + +#include "mkldnn_thread.hpp" + +#include "simple_q10n.hpp" + +namespace mkldnn { +namespace impl { +namespace cpu { + +template +struct wino_reorder_t : public cpu_primitive_t { + struct pd_t : public cpu_reorder_pd_t { + using cpu_reorder_pd_t::cpu_reorder_pd_t; + + DECLARE_COMMON_PD_T("wino_reorder", wino_reorder_t); + + static status_t create(reorder_pd_t **reorder_pd, + engine_t *engine, const primitive_attr_t *attr, + engine_t *src_engine, const memory_desc_t *src_md, + engine_t *dst_engine, const memory_desc_t *dst_md) { + const memory_desc_wrapper id(src_md), od(dst_md); + bool args_ok = true + && id.data_type() == type_i + && od.data_type() == type_o + && id.matches_tag(utils::pick(id.ndims() - 4, + format_tag::oihw, format_tag::goihw)) + && od.format_kind() == format_kind::wino + && utils::one_of(od.wino_desc().wino_format, + mkldnn_wino_wei_aaOIoi, mkldnn_wino_wei_aaOio, + mkldnn_wino_wei_aaOBiOo, mkldnn_wino_wei_OBaaIBOIio); + if (!args_ok) return status::invalid_arguments; + + auto _pd = new pd_t(engine, attr, src_engine, src_md, dst_engine, + dst_md); + if (_pd == nullptr) return status::out_of_memory; + if (_pd->init() != status::success) { + delete _pd; + return status::unimplemented; + } + return safe_ptr_assign(*reorder_pd, _pd); + } + + status_t init() { + status_t status = cpu_reorder_pd_t::init(); + if (status != status::success) return status; + + init_scratchpad(); + + return status::success; + } + + private: + void init_scratchpad() { + auto &o = memory_desc_wrapper(dst_md()).wino_desc(); + size_t transform_space_size = (size_t)o.r * o.alpha * o.oc_block; + size_t plain_size = (size_t)o.alpha * o.alpha * o.oc * o.ic; + + using namespace memory_tracking::names; + auto scratchpad = scratchpad_registry().registrar(); + scratchpad.book(key_reorder_wino_transform_space, + sizeof(in_data_t) * transform_space_size); + scratchpad.book(key_reorder_wino_plain, + sizeof(out_data_t) * plain_size); + } + }; + +private: + typedef typename prec_traits::type in_data_t; + typedef typename prec_traits::type out_data_t; + const int unsign_val_in_wino_domain_ = 5; + + wino_reorder_t(const pd_t *apd): cpu_primitive_t(apd) { + const memory_desc_wrapper src_d(pd()->src_md()); + const memory_desc_wrapper dst_d(pd()->dst_md()); + + r_ = dst_d.wino_desc().r; + w_alpha_ = dst_d.wino_desc().alpha; + wino_format_ = dst_d.wino_desc().wino_format; + + const auto &in_dims = src_d.dims(); + int groups; + int groups_offset; + if (src_d.ndims() == 5) { + groups = in_dims[0]; + groups_offset = 1; + } else { + groups = 1; + groups_offset = 0; + } + assert(groups == 1); // groups are not supported now + MAYBE_UNUSED(groups); + + or_oc_ = in_dims[0 + groups_offset]; + or_ic_ = in_dims[1 + groups_offset]; + kh_ = in_dims[2 + groups_offset]; + kw_ = in_dims[3 + groups_offset]; + + oc_ = dst_d.wino_desc().oc; + ic_ = dst_d.wino_desc().ic; + oc_block_ = dst_d.wino_desc().oc_block; + ic_block_ = dst_d.wino_desc().ic_block; + assert(oc_ % oc_block_ == 0 && ic_ % ic_block_ == 0); + nb_oc_ = oc_ / oc_block_; + nb_ic_ = ic_ / ic_block_; + ic2_block_ = 1; + if (wino_format_ == mkldnn_wino_wei_OBaaIBOIio) + ic2_block_ = dst_d.wino_desc().ic2_block; + oc2_block_ = dst_d.wino_desc().oc2_block; + assert(nb_ic_ % ic2_block_ == 0 && nb_oc_ % oc2_block_ == 0); + + adj_scale_ = dst_d.wino_desc().adj_scale; + + size_wino_wei_ = w_alpha_ * w_alpha_ * oc_ * ic_; + size_wspace_ = r_ * w_alpha_ * oc_block_; + } + + void transform(out_data_t *__restrict tmp_wei, + const in_data_t *__restrict input, + in_data_t *__restrict wspace) const { + const memory_desc_wrapper src_d(pd()->src_md()); + + const int smask = pd()->attr()->output_scales_.mask_; + const int ndims_mask = math::ilog2q(smask + 1); + const size_t D_mask = utils::array_product(src_d.dims(), ndims_mask); + const float *__restrict scales = pd()->attr()->output_scales_.scales_; + assert(D_mask == 1 || D_mask == (size_t)oc_); + + /* transform weights to winograd domain */ + const float G_2x2_3x3[4][3] = { { 1.0, 0.0, 0.0 }, { 0.5, 0.5, 0.5 }, + { 0.5, -0.5, 0.5 }, { 0.0, 0.0, 1.0 } }; + + const float G_4x4_3x3[6][3] = { { 1.13777777777778f, 0.f, 0.f }, + { -0.688403361344538f, -0.430252100840336f, -0.26890756302521f }, + { -0.688403361344538f, 0.430252100840336f, -0.26890756302521f }, + { 0.119514472455649f, 0.179271708683473f, 0.26890756302521f }, + { 0.119514472455649f, -0.179271708683473f, 0.26890756302521f }, + { 0.f, 0.f, 1.f } }; + + float *__restrict g; + if (utils::one_of(wino_format_, mkldnn_wino_wei_aaOIoi, + mkldnn_wino_wei_aaOio, mkldnn_wino_wei_aaOBiOo)) + g = (float *)G_2x2_3x3; + else if (wino_format_ == mkldnn_wino_wei_OBaaIBOIio) + g = (float *)G_4x4_3x3; + else { + assert("Unknown winograd weights target layout"); + return; + } + + int Z = oc_ * ic_; + assert(r_ == kh_ && r_ == kw_); + + for (int iic = 0; iic < ic_; iic++) { + for (int ob = 0; ob < nb_oc_; ob++) { + const in_data_t *__restrict _inp + = input + (ob * oc_block_ * or_ic_ + iic) * kh_ * kw_; + out_data_t *__restrict _out + = tmp_wei + (iic * nb_oc_ + ob) * oc_block_; + + for_nd(0, 1, size_wspace_, [&](int i) { wspace[i] = 0.f; }); + + for_nd(0, 1, r_, w_alpha_, oc_block_, + [&](int ih, int j, int ioc) { + for (int iw = 0; iw < r_; ++iw) { + int inp_oc = ob * oc_block_ + ioc; + int inp_ic = iic; + in_data_t inp_v = (inp_ic < or_ic_ && inp_oc < or_oc_) + ? _inp[ioc * or_ic_ * kh_ * kw_ + ih * kw_ + iw] + : 0.f; + wspace[(ih * w_alpha_ + j) * oc_block_ + ioc] + += inp_v * g[j * r_ + iw]; + } + }); + + for_nd(0, 1, w_alpha_, w_alpha_, oc_block_, + [&](int i, int j, int ioc) { + float t = 0; + for (int k = 0; k < r_; ++k) + t += g[i * r_ + k] + * wspace[(k * w_alpha_ + j) * oc_block_ + ioc]; + if (type_o == data_type::s8) { + const float scale = (D_mask == 1) + ? scales[0] + : scales[ob * oc_block_ + ioc]; + _out[(i * w_alpha_ + j) * Z + ioc] + = qz_b0()( + (in_data_t)t, scale * adj_scale_); + } else { + _out[(i * w_alpha_ + j) * Z + ioc] = (out_data_t)t; + } + }); + }} + } + + void reorder_to_aaOIoi(out_data_t *__restrict output, + const out_data_t *__restrict tmp_wei) const { + int32_t *__restrict dst_bias = nullptr; + if (type_o == data_type::s8) { + const auto bias_shift = sizeof(out_data_t) * size_wino_wei_; + const size_t bias_size = w_alpha_ * w_alpha_ * oc_; + + dst_bias = (int32_t *)(output + bias_shift); + utils::array_set((int32_t *)dst_bias, 0, bias_size); + } + int index = 0; + for (int u_h = 0; u_h < w_alpha_; u_h++) { + for (int u_w = 0; u_w < w_alpha_; u_w++) { + for_nd(0, 1, nb_oc_, oc_block_, [&](int ob, int o) { + int u_h_shift = u_h * w_alpha_ * ic_ * oc_; + int u_w_shift = u_w * ic_ * oc_; + int u_h_shift_b = u_h * w_alpha_ * oc_; + int u_w_shift_b = u_w * oc_; + int oc_block_shift = ob * oc_block_ * ic_ + o * ic_block_; + for (int ib = 0; ib < nb_ic_; ib++) { + for (int i = 0; i < ic_block_; i++) { + int _i = ib * ic_block_; + int _o = ob * oc_block_; + int ic_shift = (_i + i) * oc_; + int oc_shift = (_o + o); + int ic_block_shift = ib * oc_block_ * ic_block_ + i; + int src_offset = + u_h_shift + u_w_shift + ic_shift + oc_shift; + int dst_offset = u_h_shift + u_w_shift + oc_block_shift + + ic_block_shift; + + output[dst_offset] = tmp_wei[src_offset]; + if (type_o == data_type::s8) { + int bias_offset = u_h_shift_b + u_w_shift_b + oc_shift; + if (index != unsign_val_in_wino_domain_) + dst_bias[bias_offset] + -= (128 * (int32_t)output[dst_offset]); + else + dst_bias[bias_offset] = 0; + } + }} + }); + index++; + }} + } + + void reorder_to_aaOio(out_data_t *__restrict output, + const out_data_t *__restrict tmp_wei) const { + for_nd(0, 1, w_alpha_, w_alpha_, nb_oc_, + [&](int u_h, int u_w, int ob) { + for (int ib = 0; ib < nb_ic_; ib++) { + for (int i = 0; i < ic_block_; i++) { + for (int o = 0; o < oc_block_; o++) { + int src_offset = u_h * w_alpha_ * ic_ * oc_ + u_w * ic_ * oc_ + + (ib * ic_block_ + i) * oc_ + (ob * oc_block_ + o); + + int dst_offset + = u_h * w_alpha_ * nb_oc_ * nb_ic_ * ic_block_ * oc_block_ + + u_w * nb_oc_ * nb_ic_ * ic_block_ * oc_block_ + + ob * nb_ic_ * ic_block_ * oc_block_ + + ib * ic_block_ * oc_block_ + i * oc_block_ + o; + output[dst_offset] = tmp_wei[src_offset]; + }}} + }); + } + + void reorder_to_aaOBiOo(out_data_t *__restrict output, + const out_data_t *__restrict tmp_wei) const { + int oc_chunks = nb_oc_ / oc2_block_; + + for_nd(0, 1, w_alpha_, w_alpha_, oc_chunks, + [&](int u_h, int u_w, int occ) { + for (int ib = 0; ib < nb_ic_; ib++) { + out_data_t *__restrict wei_ptr = output + + (((u_h * w_alpha_ + u_w) * oc_chunks + occ) * nb_ic_ + ib) + * oc2_block_ * ic_block_ * oc_block_; + int wei_offset = 0; + for (int i = 0; i < ic_block_; i++) { + for (int ob2 = 0; ob2 < oc2_block_; ob2++) { + for (int o = 0; o < oc_block_; o++) { + int icp = ib * ic_block_ + i; + int ocp = + occ * oc2_block_ * oc_block_ + ob2 * oc_block_ + o; + + int src_offset = u_h * w_alpha_ * ic_ * oc_ + + u_w * ic_ * oc_ + icp * oc_ + ocp; + wei_ptr[wei_offset + o] = tmp_wei[src_offset]; + } + wei_offset += oc_block_; + }} + } + }); + } + + void reorder_to_OBaaIBOIio(out_data_t *__restrict output, + const out_data_t *__restrict tmp_wei) const { + int ic_chunks = nb_ic_ / ic2_block_; + int oc_chunks = nb_oc_ / oc2_block_; + + for_nd(0, 1, oc_chunks, w_alpha_, w_alpha_, + [&](int occ, int u_h, int u_w) { + for (int icc = 0; icc < ic_chunks; icc++) { + for (int ob = 0; ob < oc2_block_; ob++) { + int ocp = (occ * oc2_block_ + ob) * oc_block_; + for (int ib = 0; ib < ic2_block_; ib++) { + for (int i = 0; i < ic_block_; i++) { + int icp = (icc * ic2_block_ + ib) * ic_block_ + i; + + int src_offset = u_h * w_alpha_ * ic_ * oc_ + + u_w * ic_ * oc_ + icp * oc_ + ocp; + int wei_offset + = ((((((occ * w_alpha_ + u_h) * w_alpha_ + u_w) + * ic_chunks + icc) * oc2_block_ + ob) * ic2_block_ + + ib) * ic_block_ + i) * oc_block_; + for (int o = 0; o < oc_block_; o++) + output[wei_offset + o] = tmp_wei[src_offset + o]; + }} + }} + }); + } + + virtual status_t execute(const exec_ctx_t &ctx) const override { + auto input = CTX_IN_MEM(const in_data_t *, MKLDNN_ARG_FROM); + auto output = CTX_OUT_MEM(out_data_t *, MKLDNN_ARG_TO); + + auto wspace = (in_data_t *__restrict)scratchpad(ctx).template get( + memory_tracking::names::key_reorder_wino_transform_space); + auto tmp_wei = (out_data_t *__restrict)scratchpad(ctx).template get( + memory_tracking::names::key_reorder_wino_plain); + + transform(tmp_wei, input, wspace); + + /* reorder to winograd domain */ + switch (wino_format_) { + case mkldnn_wino_wei_aaOIoi: + reorder_to_aaOIoi(output, tmp_wei); break; + case mkldnn_wino_wei_aaOio: + reorder_to_aaOio(output, tmp_wei); break; + case mkldnn_wino_wei_aaOBiOo: + reorder_to_aaOBiOo(output, tmp_wei); break; + case mkldnn_wino_wei_OBaaIBOIio: + reorder_to_OBaaIBOIio(output, tmp_wei); break; + default: assert("Unknown wino format"); break; + } + + return status::success; + } + + const pd_t *pd() const { return (const pd_t *)primitive_t::pd(); } + int r_, w_alpha_; + int ic_, oc_, or_ic_, or_oc_, kh_, kw_; + int oc_block_, ic_block_, oc2_block_, ic2_block_; + float adj_scale_; + int nb_oc_, nb_ic_; + mkldnn_wino_memory_format_t wino_format_; + int size_wino_wei_; + int size_wspace_; +}; + +} // namespace cpu +} // namespace impl +} // namespace mkldnn + +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/COPYRIGHT b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/COPYRIGHT new file mode 100644 index 000000000000..66b6ea55d000 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/COPYRIGHT @@ -0,0 +1,47 @@ + +Copyright (c) 2007 MITSUNARI Shigeo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. +Neither the name of the copyright owner nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +ソースコード形式かバイナリ形式か、変更するかしないかを問わず、以下の条件を満た +す場合に限り、再頒布および使用が許可されます。 + +ソースコードを再頒布する場合、上記の著作権表示、本条件一覧、および下記免責条項 +を含めること。 +バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作 +権表示、本条件一覧、および下記免責条項を含めること。 +書面による特別の許可なしに、本ソフトウェアから派生した製品の宣伝または販売促進 +に、著作権者の名前またはコントリビューターの名前を使用してはならない。 +本ソフトウェアは、著作権者およびコントリビューターによって「現状のまま」提供さ +れており、明示黙示を問わず、商業的な使用可能性、および特定の目的に対する適合性 +に関する暗黙の保証も含め、またそれに限定されない、いかなる保証もありません。 +著作権者もコントリビューターも、事由のいかんを問わず、 損害発生の原因いかんを +問わず、かつ責任の根拠が契約であるか厳格責任であるか(過失その他の)不法行為で +あるかを問わず、仮にそのような損害が発生する可能性を知らされていたとしても、 +本ソフトウェアの使用によって発生した(代替品または代用サービスの調達、使用の +喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接 +損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、 +一切責任を負わないものとします。 diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak.h b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak.h new file mode 100644 index 000000000000..cf5771332fc4 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak.h @@ -0,0 +1,2658 @@ +/******************************************************************************* +* Copyright 2016-2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/******************************************************************************* +* Copyright (c) 2007 MITSUNARI Shigeo +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* Neither the name of the copyright owner nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +* THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +#pragma once +#ifndef XBYAK_XBYAK_H_ +#define XBYAK_XBYAK_H_ +/*! + @file xbyak.h + @brief Xbyak ; JIT assembler for x86(IA32)/x64 by C++ + @author herumi + @url https://github.com/herumi/xbyak + @note modified new BSD license + http://opensource.org/licenses/BSD-3-Clause +*/ +#ifndef XBYAK_NO_OP_NAMES + #if not +0 // trick to detect whether 'not' is operator or not + #error "use -fno-operator-names option if you want to use and(), or(), xor(), not() as function names, Or define XBYAK_NO_OP_NAMES and use and_(), or_(), xor_(), not_()." + #endif +#endif + +#include // for debug print +#include +#include +#include +#include +#ifndef NDEBUG +#include +#endif + +// #define XBYAK_DISABLE_AVX512 + +//#define XBYAK_USE_MMAP_ALLOCATOR +#if !defined(__GNUC__) || defined(__MINGW32__) + #undef XBYAK_USE_MMAP_ALLOCATOR +#endif + +#ifdef __GNUC__ + #define XBYAK_GNUC_PREREQ(major, minor) ((__GNUC__) * 100 + (__GNUC_MINOR__) >= (major) * 100 + (minor)) +#else + #define XBYAK_GNUC_PREREQ(major, minor) 0 +#endif + +// This covers -std=(gnu|c)++(0x|11|1y), -stdlib=libc++, and modern Microsoft. +#if ((defined(_MSC_VER) && (_MSC_VER >= 1600)) || defined(_LIBCPP_VERSION) ||\ + ((__cplusplus >= 201103) || defined(__GXX_EXPERIMENTAL_CXX0X__))) + #include + #define XBYAK_STD_UNORDERED_SET std::unordered_set + #include + #define XBYAK_STD_UNORDERED_MAP std::unordered_map + #define XBYAK_STD_UNORDERED_MULTIMAP std::unordered_multimap + +/* + Clang/llvm-gcc and ICC-EDG in 'GCC-mode' always claim to be GCC 4.2, using + libstdcxx 20070719 (from GCC 4.2.1, the last GPL 2 version). +*/ +#elif XBYAK_GNUC_PREREQ(4, 5) || (XBYAK_GNUC_PREREQ(4, 2) && __GLIBCXX__ >= 20070719) || defined(__INTEL_COMPILER) || defined(__llvm__) + #include + #define XBYAK_STD_UNORDERED_SET std::tr1::unordered_set + #include + #define XBYAK_STD_UNORDERED_MAP std::tr1::unordered_map + #define XBYAK_STD_UNORDERED_MULTIMAP std::tr1::unordered_multimap + +#elif defined(_MSC_VER) && (_MSC_VER >= 1500) && (_MSC_VER < 1600) + #include + #define XBYAK_STD_UNORDERED_SET std::tr1::unordered_set + #include + #define XBYAK_STD_UNORDERED_MAP std::tr1::unordered_map + #define XBYAK_STD_UNORDERED_MULTIMAP std::tr1::unordered_multimap + +#else + #include + #define XBYAK_STD_UNORDERED_SET std::set + #include + #define XBYAK_STD_UNORDERED_MAP std::map + #define XBYAK_STD_UNORDERED_MULTIMAP std::multimap +#endif +#ifdef _WIN32 + #include + #include + #include +#elif defined(__GNUC__) + #include + #include + #include +#endif +#if !defined(_MSC_VER) || (_MSC_VER >= 1600) + #include +#endif + +#if defined(_WIN64) || defined(__MINGW64__) || (defined(__CYGWIN__) && defined(__x86_64__)) + #define XBYAK64_WIN +#elif defined(__x86_64__) + #define XBYAK64_GCC +#endif +#if !defined(XBYAK64) && !defined(XBYAK32) + #if defined(XBYAK64_GCC) || defined(XBYAK64_WIN) + #define XBYAK64 + #else + #define XBYAK32 + #endif +#endif + +#if (__cplusplus >= 201103) || (_MSC_VER >= 1800) + #define XBYAK_VARIADIC_TEMPLATE +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4514) /* remove inline function */ + #pragma warning(disable : 4786) /* identifier is too long */ + #pragma warning(disable : 4503) /* name is too long */ + #pragma warning(disable : 4127) /* constant expresison */ +#endif + +namespace Xbyak { + +enum { + DEFAULT_MAX_CODE_SIZE = 4096, + VERSION = 0x5760 /* 0xABCD = A.BC(D) */ +}; + +#ifndef MIE_INTEGER_TYPE_DEFINED +#define MIE_INTEGER_TYPE_DEFINED +#ifdef _MSC_VER + typedef unsigned __int64 uint64; + typedef __int64 sint64; +#else + typedef uint64_t uint64; + typedef int64_t sint64; +#endif +typedef unsigned int uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; +#endif + +#ifndef MIE_ALIGN + #ifdef _MSC_VER + #define MIE_ALIGN(x) __declspec(align(x)) + #else + #define MIE_ALIGN(x) __attribute__((aligned(x))) + #endif +#endif +#ifndef MIE_PACK // for shufps + #define MIE_PACK(x, y, z, w) ((x) * 64 + (y) * 16 + (z) * 4 + (w)) +#endif + +enum { + ERR_NONE = 0, + ERR_BAD_ADDRESSING, + ERR_CODE_IS_TOO_BIG, + ERR_BAD_SCALE, + ERR_ESP_CANT_BE_INDEX, + ERR_BAD_COMBINATION, + ERR_BAD_SIZE_OF_REGISTER, + ERR_IMM_IS_TOO_BIG, + ERR_BAD_ALIGN, + ERR_LABEL_IS_REDEFINED, + ERR_LABEL_IS_TOO_FAR, + ERR_LABEL_IS_NOT_FOUND, + ERR_CODE_ISNOT_COPYABLE, + ERR_BAD_PARAMETER, + ERR_CANT_PROTECT, + ERR_CANT_USE_64BIT_DISP, + ERR_OFFSET_IS_TOO_BIG, + ERR_MEM_SIZE_IS_NOT_SPECIFIED, + ERR_BAD_MEM_SIZE, + ERR_BAD_ST_COMBINATION, + ERR_OVER_LOCAL_LABEL, // not used + ERR_UNDER_LOCAL_LABEL, + ERR_CANT_ALLOC, + ERR_ONLY_T_NEAR_IS_SUPPORTED_IN_AUTO_GROW, + ERR_BAD_PROTECT_MODE, + ERR_BAD_PNUM, + ERR_BAD_TNUM, + ERR_BAD_VSIB_ADDRESSING, + ERR_CANT_CONVERT, + ERR_LABEL_ISNOT_SET_BY_L, + ERR_LABEL_IS_ALREADY_SET_BY_L, + ERR_BAD_LABEL_STR, + ERR_MUNMAP, + ERR_OPMASK_IS_ALREADY_SET, + ERR_ROUNDING_IS_ALREADY_SET, + ERR_K0_IS_INVALID, + ERR_EVEX_IS_INVALID, + ERR_SAE_IS_INVALID, + ERR_ER_IS_INVALID, + ERR_INVALID_BROADCAST, + ERR_INVALID_OPMASK_WITH_MEMORY, + ERR_INVALID_ZERO, + ERR_INVALID_RIP_IN_AUTO_GROW, + ERR_INVALID_MIB_ADDRESS, + ERR_INTERNAL, + ERR_X2APIC_IS_NOT_SUPPORTED +}; + +class Error : public std::exception { + int err_; +public: + explicit Error(int err) : err_(err) + { + if (err_ < 0 || err_ > ERR_INTERNAL) { + fprintf(stderr, "bad err=%d in Xbyak::Error\n", err_); + //exit(1); + } + } + operator int() const { return err_; } + const char *what() const throw() + { + static const char *errTbl[] = { + "none", + "bad addressing", + "code is too big", + "bad scale", + "esp can't be index", + "bad combination", + "bad size of register", + "imm is too big", + "bad align", + "label is redefined", + "label is too far", + "label is not found", + "code is not copyable", + "bad parameter", + "can't protect", + "can't use 64bit disp(use (void*))", + "offset is too big", + "MEM size is not specified", + "bad mem size", + "bad st combination", + "over local label", + "under local label", + "can't alloc", + "T_SHORT is not supported in AutoGrow", + "bad protect mode", + "bad pNum", + "bad tNum", + "bad vsib addressing", + "can't convert", + "label is not set by L()", + "label is already set by L()", + "bad label string", + "err munmap", + "opmask is already set", + "rounding is already set", + "k0 is invalid", + "evex is invalid", + "sae(suppress all exceptions) is invalid", + "er(embedded rounding) is invalid", + "invalid broadcast", + "invalid opmask with memory", + "invalid zero", + "invalid rip in AutoGrow", + "invalid mib address", + "internal error", + "x2APIC is not supported" + }; + assert((size_t)err_ < sizeof(errTbl) / sizeof(*errTbl)); + return errTbl[err_]; + } +}; + +inline const char *ConvertErrorToString(const Error& err) +{ + return err.what(); +} + +inline void *AlignedMalloc(size_t size, size_t alignment) +{ +#ifdef __MINGW32__ + return __mingw_aligned_malloc(size, alignment); +#elif defined(_WIN32) + return _aligned_malloc(size, alignment); +#else + void *p; + int ret = posix_memalign(&p, alignment, size); + return (ret == 0) ? p : 0; +#endif +} + +inline void AlignedFree(void *p) +{ +#ifdef __MINGW32__ + __mingw_aligned_free(p); +#elif defined(_MSC_VER) + _aligned_free(p); +#else + free(p); +#endif +} + +template +inline const To CastTo(From p) throw() +{ + return (const To)(size_t)(p); +} +namespace inner { + +static const size_t ALIGN_PAGE_SIZE = 4096; + +inline bool IsInDisp8(uint32 x) { return 0xFFFFFF80 <= x || x <= 0x7F; } +inline bool IsInInt32(uint64 x) { return ~uint64(0x7fffffffu) <= x || x <= 0x7FFFFFFFU; } + +inline uint32 VerifyInInt32(uint64 x) +{ +#ifdef XBYAK64 + if (!IsInInt32(x)) throw Error(ERR_OFFSET_IS_TOO_BIG); +#endif + return static_cast(x); +} + +enum LabelMode { + LasIs, // as is + Labs, // absolute + LaddTop // (addr + top) for mov(reg, label) with AutoGrow +}; + +} // inner + +/* + custom allocator +*/ +struct Allocator { + virtual uint8 *alloc(size_t size) { return reinterpret_cast(AlignedMalloc(size, inner::ALIGN_PAGE_SIZE)); } + virtual void free(uint8 *p) { AlignedFree(p); } + virtual ~Allocator() {} + /* override to return false if you call protect() manually */ + virtual bool useProtect() const { return true; } +}; + +#ifdef XBYAK_USE_MMAP_ALLOCATOR +class MmapAllocator : Allocator { + typedef XBYAK_STD_UNORDERED_MAP SizeList; + SizeList sizeList_; +public: + uint8 *alloc(size_t size) + { + const size_t alignedSizeM1 = inner::ALIGN_PAGE_SIZE - 1; + size = (size + alignedSizeM1) & ~alignedSizeM1; +#ifdef MAP_ANONYMOUS + const int mode = MAP_PRIVATE | MAP_ANONYMOUS; +#elif defined(MAP_ANON) + const int mode = MAP_PRIVATE | MAP_ANON; +#else + #error "not supported" +#endif + void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, mode, -1, 0); + if (p == MAP_FAILED) throw Error(ERR_CANT_ALLOC); + assert(p); + sizeList_[(uintptr_t)p] = size; + return (uint8*)p; + } + void free(uint8 *p) + { + if (p == 0) return; + SizeList::iterator i = sizeList_.find((uintptr_t)p); + if (i == sizeList_.end()) throw Error(ERR_BAD_PARAMETER); + if (munmap((void*)i->first, i->second) < 0) throw Error(ERR_MUNMAP); + sizeList_.erase(i); + } +}; +#endif + +class Address; +class Reg; + +class Operand { + static const uint8 EXT8BIT = 0x20; + unsigned int idx_:6; // 0..31 + EXT8BIT = 1 if spl/bpl/sil/dil + unsigned int kind_:9; + unsigned int bit_:10; +protected: + unsigned int zero_:1; + unsigned int mask_:3; + unsigned int rounding_:3; + void setIdx(int idx) { idx_ = idx; } +public: + enum Kind { + NONE = 0, + MEM = 1 << 0, + REG = 1 << 1, + MMX = 1 << 2, + FPU = 1 << 3, + XMM = 1 << 4, + YMM = 1 << 5, + ZMM = 1 << 6, + OPMASK = 1 << 7, + BNDREG = 1 << 8 + }; + enum Code { +#ifdef XBYAK64 + RAX = 0, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15, + R8D = 8, R9D, R10D, R11D, R12D, R13D, R14D, R15D, + R8W = 8, R9W, R10W, R11W, R12W, R13W, R14W, R15W, + R8B = 8, R9B, R10B, R11B, R12B, R13B, R14B, R15B, + SPL = 4, BPL, SIL, DIL, +#endif + EAX = 0, ECX, EDX, EBX, ESP, EBP, ESI, EDI, + AX = 0, CX, DX, BX, SP, BP, SI, DI, + AL = 0, CL, DL, BL, AH, CH, DH, BH + }; + Operand() : idx_(0), kind_(0), bit_(0), zero_(0), mask_(0), rounding_(0) { } + Operand(int idx, Kind kind, int bit, bool ext8bit = 0) + : idx_(static_cast(idx | (ext8bit ? EXT8BIT : 0))) + , kind_(kind) + , bit_(bit) + , zero_(0), mask_(0), rounding_(0) + { + assert((bit_ & (bit_ - 1)) == 0); // bit must be power of two + } + Kind getKind() const { return static_cast(kind_); } + int getIdx() const { return idx_ & (EXT8BIT - 1); } + bool isNone() const { return kind_ == 0; } + bool isMMX() const { return is(MMX); } + bool isXMM() const { return is(XMM); } + bool isYMM() const { return is(YMM); } + bool isZMM() const { return is(ZMM); } + bool isXMEM() const { return is(XMM | MEM); } + bool isYMEM() const { return is(YMM | MEM); } + bool isZMEM() const { return is(ZMM | MEM); } + bool isOPMASK() const { return is(OPMASK); } + bool isBNDREG() const { return is(BNDREG); } + bool isREG(int bit = 0) const { return is(REG, bit); } + bool isMEM(int bit = 0) const { return is(MEM, bit); } + bool isFPU() const { return is(FPU); } + bool isExt8bit() const { return (idx_ & EXT8BIT) != 0; } + bool isExtIdx() const { return (getIdx() & 8) != 0; } + bool isExtIdx2() const { return (getIdx() & 16) != 0; } + bool hasEvex() const { return isZMM() || isExtIdx2() || getOpmaskIdx() || getRounding(); } + bool hasRex() const { return isExt8bit() || isREG(64) || isExtIdx(); } + bool hasZero() const { return zero_; } + int getOpmaskIdx() const { return mask_; } + int getRounding() const { return rounding_; } + void setKind(Kind kind) + { + if ((kind & (XMM|YMM|ZMM)) == 0) return; + kind_ = kind; + bit_ = kind == XMM ? 128 : kind == YMM ? 256 : 512; + } + void setBit(int bit) { bit_ = bit; } + void setOpmaskIdx(int idx, bool ignore_idx0 = false) + { + if (!ignore_idx0 && idx == 0) throw Error(ERR_K0_IS_INVALID); + if (mask_) throw Error(ERR_OPMASK_IS_ALREADY_SET); + mask_ = idx; + } + void setRounding(int idx) + { + if (rounding_) throw Error(ERR_ROUNDING_IS_ALREADY_SET); + rounding_ = idx; + } + void setZero() { zero_ = true; } + // ah, ch, dh, bh? + bool isHigh8bit() const + { + if (!isBit(8)) return false; + if (isExt8bit()) return false; + const int idx = getIdx(); + return AH <= idx && idx <= BH; + } + // any bit is accetable if bit == 0 + bool is(int kind, uint32 bit = 0) const + { + return (kind == 0 || (kind_ & kind)) && (bit == 0 || (bit_ & bit)); // cf. you can set (8|16) + } + bool isBit(uint32 bit) const { return (bit_ & bit) != 0; } + uint32 getBit() const { return bit_; } + const char *toString() const + { + const int idx = getIdx(); + if (kind_ == REG) { + if (isExt8bit()) { + static const char *tbl[4] = { "spl", "bpl", "sil", "dil" }; + return tbl[idx - 4]; + } + static const char *tbl[4][16] = { + { "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh", "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" }, + { "ax", "cx", "dx", "bx", "sp", "bp", "si", "di", "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" }, + { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" }, + { "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" }, + }; + return tbl[bit_ == 8 ? 0 : bit_ == 16 ? 1 : bit_ == 32 ? 2 : 3][idx]; + } else if (isOPMASK()) { + static const char *tbl[8] = { "k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7" }; + return tbl[idx]; + } else if (isZMM()) { + static const char *tbl[32] = { + "zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5", "zmm6", "zmm7", "zmm8", "zmm9", "zmm10", "zmm11", "zmm12", "zmm13", "zmm14", "zmm15", + "zmm16", "zmm17", "zmm18", "zmm19", "zmm20", "zmm21", "zmm22", "zmm23", "zmm24", "zmm25", "zmm26", "zmm27", "zmm28", "zmm29", "zmm30", "zmm31" + }; + return tbl[idx]; + } else if (isYMM()) { + static const char *tbl[32] = { + "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7", "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15", + "ymm16", "ymm17", "ymm18", "ymm19", "ymm20", "ymm21", "ymm22", "ymm23", "ymm24", "ymm25", "ymm26", "ymm27", "ymm28", "ymm29", "ymm30", "ymm31" + }; + return tbl[idx]; + } else if (isXMM()) { + static const char *tbl[32] = { + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", + "xmm16", "xmm17", "xmm18", "xmm19", "xmm20", "xmm21", "xmm22", "xmm23", "xmm24", "xmm25", "xmm26", "xmm27", "xmm28", "xmm29", "xmm30", "xmm31" + }; + return tbl[idx]; + } else if (isMMX()) { + static const char *tbl[8] = { "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7" }; + return tbl[idx]; + } else if (isFPU()) { + static const char *tbl[8] = { "st0", "st1", "st2", "st3", "st4", "st5", "st6", "st7" }; + return tbl[idx]; + } else if (isBNDREG()) { + static const char *tbl[4] = { "bnd0", "bnd1", "bnd2", "bnd3" }; + return tbl[idx]; + } + throw Error(ERR_INTERNAL); + } + bool isEqualIfNotInherited(const Operand& rhs) const { return idx_ == rhs.idx_ && kind_ == rhs.kind_ && bit_ == rhs.bit_ && zero_ == rhs.zero_ && mask_ == rhs.mask_ && rounding_ == rhs.rounding_; } + bool operator==(const Operand& rhs) const; + bool operator!=(const Operand& rhs) const { return !operator==(rhs); } + const Address& getAddress() const; + const Reg& getReg() const; +}; + +class Label; + +struct Reg8; +struct Reg16; +struct Reg32; +#ifdef XBYAK64 +struct Reg64; +#endif +class Reg : public Operand { +public: + Reg() { } + Reg(int idx, Kind kind, int bit = 0, bool ext8bit = false) : Operand(idx, kind, bit, ext8bit) { } + Reg changeBit(int bit) const { return Reg(getIdx(), getKind(), bit, isExt8bit()); } + uint8 getRexW() const { return isREG(64) ? 8 : 0; } + uint8 getRexR() const { return isExtIdx() ? 4 : 0; } + uint8 getRexX() const { return isExtIdx() ? 2 : 0; } + uint8 getRexB() const { return isExtIdx() ? 1 : 0; } + uint8 getRex(const Reg& base = Reg()) const + { + uint8 rex = getRexW() | getRexR() | base.getRexW() | base.getRexB(); + if (rex || isExt8bit() || base.isExt8bit()) rex |= 0x40; + return rex; + } + Reg8 cvt8() const; + Reg16 cvt16() const; + Reg32 cvt32() const; +#ifdef XBYAK64 + Reg64 cvt64() const; +#endif +}; + +inline const Reg& Operand::getReg() const +{ + assert(!isMEM()); + return static_cast(*this); +} + +struct Reg8 : public Reg { + explicit Reg8(int idx = 0, bool ext8bit = false) : Reg(idx, Operand::REG, 8, ext8bit) { } +}; + +struct Reg16 : public Reg { + explicit Reg16(int idx = 0) : Reg(idx, Operand::REG, 16) { } +}; + +struct Mmx : public Reg { + explicit Mmx(int idx = 0, Kind kind = Operand::MMX, int bit = 64) : Reg(idx, kind, bit) { } +}; + +struct EvexModifierRounding { + enum { + T_RN_SAE = 1, + T_RD_SAE = 2, + T_RU_SAE = 3, + T_RZ_SAE = 4, + T_SAE = 5 + }; + explicit EvexModifierRounding(int rounding) : rounding(rounding) {} + int rounding; +}; +struct EvexModifierZero{EvexModifierZero() {}}; + +struct Xmm : public Mmx { + explicit Xmm(int idx = 0, Kind kind = Operand::XMM, int bit = 128) : Mmx(idx, kind, bit) { } + Xmm(Kind kind, int idx) : Mmx(idx, kind, kind == XMM ? 128 : kind == YMM ? 256 : 512) { } + Xmm operator|(const EvexModifierRounding& emr) const { Xmm r(*this); r.setRounding(emr.rounding); return r; } + Xmm copyAndSetIdx(int idx) const { Xmm ret(*this); ret.setIdx(idx); return ret; } + Xmm copyAndSetKind(Operand::Kind kind) const { Xmm ret(*this); ret.setKind(kind); return ret; } +}; + +struct Ymm : public Xmm { + explicit Ymm(int idx = 0, Kind kind = Operand::YMM, int bit = 256) : Xmm(idx, kind, bit) { } + Ymm operator|(const EvexModifierRounding& emr) const { Ymm r(*this); r.setRounding(emr.rounding); return r; } +}; + +struct Zmm : public Ymm { + explicit Zmm(int idx = 0) : Ymm(idx, Operand::ZMM, 512) { } + Zmm operator|(const EvexModifierRounding& emr) const { Zmm r(*this); r.setRounding(emr.rounding); return r; } +}; + +struct Opmask : public Reg { + explicit Opmask(int idx = 0) : Reg(idx, Operand::OPMASK, 64) {} +}; + +struct BoundsReg : public Reg { + explicit BoundsReg(int idx = 0) : Reg(idx, Operand::BNDREG, 128) {} +}; + +templateT operator|(const T& x, const Opmask& k) { T r(x); r.setOpmaskIdx(k.getIdx()); return r; } +templateT operator|(const T& x, const EvexModifierZero&) { T r(x); r.setZero(); return r; } +templateT operator|(const T& x, const EvexModifierRounding& emr) { T r(x); r.setRounding(emr.rounding); return r; } + +struct Fpu : public Reg { + explicit Fpu(int idx = 0) : Reg(idx, Operand::FPU, 32) { } +}; + +struct Reg32e : public Reg { + explicit Reg32e(int idx, int bit) : Reg(idx, Operand::REG, bit) {} +}; +struct Reg32 : public Reg32e { + explicit Reg32(int idx = 0) : Reg32e(idx, 32) {} +}; +#ifdef XBYAK64 +struct Reg64 : public Reg32e { + explicit Reg64(int idx = 0) : Reg32e(idx, 64) {} +}; +struct RegRip { + sint64 disp_; + const Label* label_; + bool isAddr_; + explicit RegRip(sint64 disp = 0, const Label* label = 0, bool isAddr = false) : disp_(disp), label_(label), isAddr_(isAddr) {} + friend const RegRip operator+(const RegRip& r, int disp) { + return RegRip(r.disp_ + disp, r.label_, r.isAddr_); + } + friend const RegRip operator-(const RegRip& r, int disp) { + return RegRip(r.disp_ - disp, r.label_, r.isAddr_); + } + friend const RegRip operator+(const RegRip& r, sint64 disp) { + return RegRip(r.disp_ + disp, r.label_, r.isAddr_); + } + friend const RegRip operator-(const RegRip& r, sint64 disp) { + return RegRip(r.disp_ - disp, r.label_, r.isAddr_); + } + friend const RegRip operator+(const RegRip& r, const Label& label) { + if (r.label_ || r.isAddr_) throw Error(ERR_BAD_ADDRESSING); + return RegRip(r.disp_, &label); + } + friend const RegRip operator+(const RegRip& r, const void *addr) { + if (r.label_ || r.isAddr_) throw Error(ERR_BAD_ADDRESSING); + return RegRip(r.disp_ + (sint64)addr, 0, true); + } +}; +#endif + +inline Reg8 Reg::cvt8() const +{ + const int idx = getIdx(); + if (isBit(8)) return Reg8(idx, isExt8bit()); +#ifdef XBYAK32 + if (idx >= 4) throw Error(ERR_CANT_CONVERT); +#endif + return Reg8(idx, 4 <= idx && idx < 8); +} + +inline Reg16 Reg::cvt16() const +{ + const int idx = getIdx(); + if (isBit(8) && (4 <= idx && idx < 8) && !isExt8bit()) throw Error(ERR_CANT_CONVERT); + return Reg16(idx); +} + +inline Reg32 Reg::cvt32() const +{ + const int idx = getIdx(); + if (isBit(8) && (4 <= idx && idx < 8) && !isExt8bit()) throw Error(ERR_CANT_CONVERT); + return Reg32(idx); +} + +#ifdef XBYAK64 +inline Reg64 Reg::cvt64() const +{ + const int idx = getIdx(); + if (isBit(8) && (4 <= idx && idx < 8) && !isExt8bit()) throw Error(ERR_CANT_CONVERT); + return Reg64(idx); +} +#endif + +#ifndef XBYAK_DISABLE_SEGMENT +// not derived from Reg +class Segment { + int idx_; +public: + enum { + es, cs, ss, ds, fs, gs + }; + explicit Segment(int idx) : idx_(idx) { assert(0 <= idx_ && idx_ < 6); } + int getIdx() const { return idx_; } + const char *toString() const + { + static const char tbl[][3] = { + "es", "cs", "ss", "ds", "fs", "gs" + }; + return tbl[idx_]; + } +}; +#endif + +class RegExp { +public: +#ifdef XBYAK64 + enum { i32e = 32 | 64 }; +#else + enum { i32e = 32 }; +#endif + RegExp(size_t disp = 0) : scale_(0), disp_(disp) { } + RegExp(const Reg& r, int scale = 1) + : scale_(scale) + , disp_(0) + { + if (!r.isREG(i32e) && !r.is(Reg::XMM|Reg::YMM|Reg::ZMM)) throw Error(ERR_BAD_SIZE_OF_REGISTER); + if (scale == 0) return; + if (scale != 1 && scale != 2 && scale != 4 && scale != 8) throw Error(ERR_BAD_SCALE); + if (r.getBit() >= 128 || scale != 1) { // xmm/ymm is always index + index_ = r; + } else { + base_ = r; + } + } + bool isVsib(int bit = 128 | 256 | 512) const { return index_.isBit(bit); } + RegExp optimize() const + { + RegExp exp = *this; + // [reg * 2] => [reg + reg] + if (index_.isBit(i32e) && !base_.getBit() && scale_ == 2) { + exp.base_ = index_; + exp.scale_ = 1; + } + return exp; + } + bool operator==(const RegExp& rhs) const + { + return base_ == rhs.base_ && index_ == rhs.index_ && disp_ == rhs.disp_ && scale_ == rhs.scale_; + } + const Reg& getBase() const { return base_; } + const Reg& getIndex() const { return index_; } + int getScale() const { return scale_; } + size_t getDisp() const { return disp_; } + void verify() const + { + if (base_.getBit() >= 128) throw Error(ERR_BAD_SIZE_OF_REGISTER); + if (index_.getBit() && index_.getBit() <= 64) { + if (index_.getIdx() == Operand::ESP) throw Error(ERR_ESP_CANT_BE_INDEX); + if (base_.getBit() && base_.getBit() != index_.getBit()) throw Error(ERR_BAD_SIZE_OF_REGISTER); + } + } + friend RegExp operator+(const RegExp& a, const RegExp& b); + friend RegExp operator-(const RegExp& e, size_t disp); + uint8 getRex() const + { + uint8 rex = index_.getRexX() | base_.getRexB(); + return rex ? uint8(rex | 0x40) : 0; + } +private: + /* + [base_ + index_ * scale_ + disp_] + base : Reg32e, index : Reg32e(w/o esp), Xmm, Ymm + */ + Reg base_; + Reg index_; + int scale_; + size_t disp_; +}; + +inline RegExp operator+(const RegExp& a, const RegExp& b) +{ + if (a.index_.getBit() && b.index_.getBit()) throw Error(ERR_BAD_ADDRESSING); + RegExp ret = a; + if (!ret.index_.getBit()) { ret.index_ = b.index_; ret.scale_ = b.scale_; } + if (b.base_.getBit()) { + if (ret.base_.getBit()) { + if (ret.index_.getBit()) throw Error(ERR_BAD_ADDRESSING); + // base + base => base + index * 1 + ret.index_ = b.base_; + // [reg + esp] => [esp + reg] + if (ret.index_.getIdx() == Operand::ESP) std::swap(ret.base_, ret.index_); + ret.scale_ = 1; + } else { + ret.base_ = b.base_; + } + } + ret.disp_ += b.disp_; + return ret; +} +inline RegExp operator*(const Reg& r, int scale) +{ + return RegExp(r, scale); +} +inline RegExp operator-(const RegExp& e, size_t disp) +{ + RegExp ret = e; + ret.disp_ -= disp; + return ret; +} + +// 2nd parameter for constructor of CodeArray(maxSize, userPtr, alloc) +void *const AutoGrow = (void*)1; //-V566 +void *const DontSetProtectRWE = (void*)2; //-V566 + +class CodeArray { + enum Type { + USER_BUF = 1, // use userPtr(non alignment, non protect) + ALLOC_BUF, // use new(alignment, protect) + AUTO_GROW // automatically move and grow memory if necessary + }; + CodeArray(const CodeArray& rhs); + void operator=(const CodeArray&); + bool isAllocType() const { return type_ == ALLOC_BUF || type_ == AUTO_GROW; } + struct AddrInfo { + size_t codeOffset; // position to write + size_t jmpAddr; // value to write + int jmpSize; // size of jmpAddr + inner::LabelMode mode; + AddrInfo(size_t _codeOffset, size_t _jmpAddr, int _jmpSize, inner::LabelMode _mode) + : codeOffset(_codeOffset), jmpAddr(_jmpAddr), jmpSize(_jmpSize), mode(_mode) {} + uint64 getVal(const uint8 *top) const + { + uint64 disp = (mode == inner::LaddTop) ? jmpAddr + size_t(top) : (mode == inner::LasIs) ? jmpAddr : jmpAddr - size_t(top); + if (jmpSize == 4) disp = inner::VerifyInInt32(disp); + return disp; + } + }; + typedef std::list AddrInfoList; + AddrInfoList addrInfoList_; + const Type type_; +#ifdef XBYAK_USE_MMAP_ALLOCATOR + MmapAllocator defaultAllocator_; +#else + Allocator defaultAllocator_; +#endif + Allocator *alloc_; +protected: + size_t maxSize_; + uint8 *top_; + size_t size_; + bool isCalledCalcJmpAddress_; + + bool useProtect() const { return alloc_->useProtect(); } + /* + allocate new memory and copy old data to the new area + */ + void growMemory() + { + const size_t newSize = (std::max)(DEFAULT_MAX_CODE_SIZE, maxSize_ * 2); + uint8 *newTop = alloc_->alloc(newSize); + if (newTop == 0) throw Error(ERR_CANT_ALLOC); + for (size_t i = 0; i < size_; i++) newTop[i] = top_[i]; + alloc_->free(top_); + top_ = newTop; + maxSize_ = newSize; + } + /* + calc jmp address for AutoGrow mode + */ + void calcJmpAddress() + { + if (isCalledCalcJmpAddress_) return; + for (AddrInfoList::const_iterator i = addrInfoList_.begin(), ie = addrInfoList_.end(); i != ie; ++i) { + uint64 disp = i->getVal(top_); + rewrite(i->codeOffset, disp, i->jmpSize); + } + isCalledCalcJmpAddress_ = true; + } +public: + enum ProtectMode { + PROTECT_RW = 0, // read/write + PROTECT_RWE = 1, // read/write/exec + PROTECT_RE = 2 // read/exec + }; + explicit CodeArray(size_t maxSize, void *userPtr = 0, Allocator *allocator = 0) + : type_(userPtr == AutoGrow ? AUTO_GROW : (userPtr == 0 || userPtr == DontSetProtectRWE) ? ALLOC_BUF : USER_BUF) + , alloc_(allocator ? allocator : (Allocator*)&defaultAllocator_) + , maxSize_(maxSize) + , top_(type_ == USER_BUF ? reinterpret_cast(userPtr) : alloc_->alloc((std::max)(maxSize, 1))) + , size_(0) + , isCalledCalcJmpAddress_(false) + { + if (maxSize_ > 0 && top_ == 0) throw Error(ERR_CANT_ALLOC); + if ((type_ == ALLOC_BUF && userPtr != DontSetProtectRWE && useProtect()) && !setProtectMode(PROTECT_RWE, false)) { + alloc_->free(top_); + throw Error(ERR_CANT_PROTECT); + } + } + virtual ~CodeArray() + { + if (isAllocType()) { + if (useProtect()) setProtectModeRW(false); + alloc_->free(top_); + } + } + bool setProtectMode(ProtectMode mode, bool throwException = true) + { + bool isOK = protect(top_, maxSize_, mode); + if (isOK) return true; + if (throwException) throw Error(ERR_CANT_PROTECT); + return false; + } + bool setProtectModeRE(bool throwException = true) { return setProtectMode(PROTECT_RE, throwException); } + bool setProtectModeRW(bool throwException = true) { return setProtectMode(PROTECT_RW, throwException); } + void resetSize() + { + size_ = 0; + addrInfoList_.clear(); + isCalledCalcJmpAddress_ = false; + } + void db(int code) + { + if (size_ >= maxSize_) { + if (type_ == AUTO_GROW) { + growMemory(); + } else { + throw Error(ERR_CODE_IS_TOO_BIG); + } + } + top_[size_++] = static_cast(code); + } + void db(const uint8 *code, size_t codeSize) + { + for (size_t i = 0; i < codeSize; i++) db(code[i]); + } + void db(uint64 code, size_t codeSize) + { + if (codeSize > 8) throw Error(ERR_BAD_PARAMETER); + for (size_t i = 0; i < codeSize; i++) db(static_cast(code >> (i * 8))); + } + void dw(uint32 code) { db(code, 2); } + void dd(uint32 code) { db(code, 4); } + void dq(uint64 code) { db(code, 8); } + const uint8 *getCode() const { return top_; } + template + const F getCode() const { return reinterpret_cast(top_); } + const uint8 *getCurr() const { return &top_[size_]; } + template + const F getCurr() const { return reinterpret_cast(&top_[size_]); } + size_t getSize() const { return size_; } + void setSize(size_t size) + { + if (size > maxSize_) throw Error(ERR_OFFSET_IS_TOO_BIG); + size_ = size; + } + void dump() const + { + const uint8 *p = getCode(); + size_t bufSize = getSize(); + size_t remain = bufSize; + for (int i = 0; i < 4; i++) { + size_t disp = 16; + if (remain < 16) { + disp = remain; + } + for (size_t j = 0; j < 16; j++) { + if (j < disp) { + printf("%02X", p[i * 16 + j]); + } + } + putchar('\n'); + remain -= disp; + if (remain == 0) { + break; + } + } + } + /* + @param offset [in] offset from top + @param disp [in] offset from the next of jmp + @param size [in] write size(1, 2, 4, 8) + */ + void rewrite(size_t offset, uint64 disp, size_t size) + { + assert(offset < maxSize_); + if (size != 1 && size != 2 && size != 4 && size != 8) throw Error(ERR_BAD_PARAMETER); + uint8 *const data = top_ + offset; + for (size_t i = 0; i < size; i++) { + data[i] = static_cast(disp >> (i * 8)); + } + } + void save(size_t offset, size_t val, int size, inner::LabelMode mode) + { + addrInfoList_.push_back(AddrInfo(offset, val, size, mode)); + } + bool isAutoGrow() const { return type_ == AUTO_GROW; } + bool isCalledCalcJmpAddress() const { return isCalledCalcJmpAddress_; } + /** + change exec permission of memory + @param addr [in] buffer address + @param size [in] buffer size + @param protectMode [in] mode(RW/RWE/RE) + @return true(success), false(failure) + */ + static inline bool protect(const void *addr, size_t size, int protectMode) + { +#if defined(_WIN32) + const DWORD c_rw = PAGE_READWRITE; + const DWORD c_rwe = PAGE_EXECUTE_READWRITE; + const DWORD c_re = PAGE_EXECUTE_READ; + DWORD mode; +#else + const int c_rw = PROT_READ | PROT_WRITE; + const int c_rwe = PROT_READ | PROT_WRITE | PROT_EXEC; + const int c_re = PROT_READ | PROT_EXEC; + int mode; +#endif + switch (protectMode) { + case PROTECT_RW: mode = c_rw; break; + case PROTECT_RWE: mode = c_rwe; break; + case PROTECT_RE: mode = c_re; break; + default: + return false; + } +#if defined(_WIN32) + DWORD oldProtect; + return VirtualProtect(const_cast(addr), size, mode, &oldProtect) != 0; +#elif defined(__GNUC__) + size_t pageSize = sysconf(_SC_PAGESIZE); + size_t iaddr = reinterpret_cast(addr); + size_t roundAddr = iaddr & ~(pageSize - static_cast(1)); +#ifndef NDEBUG + if (pageSize != 4096) fprintf(stderr, "large page(%zd) is used. not tested enough.\n", pageSize); +#endif + return mprotect(reinterpret_cast(roundAddr), size + (iaddr - roundAddr), mode) == 0; +#else + return true; +#endif + } + /** + get aligned memory pointer + @param addr [in] address + @param alignedSize [in] power of two + @return aligned addr by alingedSize + */ + static inline uint8 *getAlignedAddress(uint8 *addr, size_t alignedSize = 16) + { + return reinterpret_cast((reinterpret_cast(addr) + alignedSize - 1) & ~(alignedSize - static_cast(1))); + } +}; + +class Address : public Operand { +public: + enum Mode { + M_ModRM, + M_64bitDisp, + M_rip, + M_ripAddr + }; + Address(uint32 sizeBit, bool broadcast, const RegExp& e) + : Operand(0, MEM, sizeBit), e_(e), label_(0), mode_(M_ModRM), broadcast_(broadcast) + { + e_.verify(); + } +#ifdef XBYAK64 + explicit Address(size_t disp) + : Operand(0, MEM, 64), e_(disp), label_(0), mode_(M_64bitDisp), broadcast_(false){ } + Address(uint32 sizeBit, bool broadcast, const RegRip& addr) + : Operand(0, MEM, sizeBit), e_(addr.disp_), label_(addr.label_), mode_(addr.isAddr_ ? M_ripAddr : M_rip), broadcast_(broadcast) { } +#endif + RegExp getRegExp(bool optimize = true) const + { + return optimize ? e_.optimize() : e_; + } + Mode getMode() const { return mode_; } + bool is32bit() const { return e_.getBase().getBit() == 32 || e_.getIndex().getBit() == 32; } + bool isOnlyDisp() const { return !e_.getBase().getBit() && !e_.getIndex().getBit(); } // for mov eax + size_t getDisp() const { return e_.getDisp(); } + uint8 getRex() const + { + if (mode_ != M_ModRM) return 0; + return getRegExp().getRex(); + } + bool is64bitDisp() const { return mode_ == M_64bitDisp; } // for moffset + bool isBroadcast() const { return broadcast_; } + const Label* getLabel() const { return label_; } + bool operator==(const Address& rhs) const + { + return getBit() == rhs.getBit() && e_ == rhs.e_ && label_ == rhs.label_ && mode_ == rhs.mode_ && broadcast_ == rhs.broadcast_; + } + bool operator!=(const Address& rhs) const { return !operator==(rhs); } + bool isVsib() const { return e_.isVsib(); } +private: + RegExp e_; + const Label* label_; + Mode mode_; + bool broadcast_; +}; + +inline const Address& Operand::getAddress() const +{ + assert(isMEM()); + return static_cast(*this); +} + +inline bool Operand::operator==(const Operand& rhs) const +{ + if (isMEM() && rhs.isMEM()) return this->getAddress() == rhs.getAddress(); + return isEqualIfNotInherited(rhs); +} + +class AddressFrame { + void operator=(const AddressFrame&); + AddressFrame(const AddressFrame&); +public: + const uint32 bit_; + const bool broadcast_; + explicit AddressFrame(uint32 bit, bool broadcast = false) : bit_(bit), broadcast_(broadcast) { } + Address operator[](const RegExp& e) const + { + return Address(bit_, broadcast_, e); + } + Address operator[](const void *disp) const + { + return Address(bit_, broadcast_, RegExp(reinterpret_cast(disp))); + } +#ifdef XBYAK64 + Address operator[](uint64 disp) const { return Address(disp); } + Address operator[](const RegRip& addr) const { return Address(bit_, broadcast_, addr); } +#endif +}; + +struct JmpLabel { + size_t endOfJmp; /* offset from top to the end address of jmp */ + int jmpSize; + inner::LabelMode mode; + size_t disp; // disp for [rip + disp] + explicit JmpLabel(size_t endOfJmp = 0, int jmpSize = 0, inner::LabelMode mode = inner::LasIs, size_t disp = 0) + : endOfJmp(endOfJmp), jmpSize(jmpSize), mode(mode), disp(disp) + { + } +}; + +class LabelManager; + +class Label { + mutable LabelManager *mgr; + mutable int id; + friend class LabelManager; +public: + Label() : mgr(0), id(0) {} + Label(const Label& rhs); + Label& operator=(const Label& rhs); + ~Label(); + void clear() { mgr = 0; id = 0; } + int getId() const { return id; } + const uint8 *getAddress() const; + + // backward compatibility + static inline std::string toStr(int num) + { + char buf[16]; +#if defined(_MSC_VER) && (_MSC_VER < 1900) + _snprintf_s +#else + snprintf +#endif + (buf, sizeof(buf), ".%08x", num); + return buf; + } +}; + +class LabelManager { + // for string label + struct SlabelVal { + size_t offset; + SlabelVal(size_t offset) : offset(offset) {} + }; + typedef XBYAK_STD_UNORDERED_MAP SlabelDefList; + typedef XBYAK_STD_UNORDERED_MULTIMAP SlabelUndefList; + struct SlabelState { + SlabelDefList defList; + SlabelUndefList undefList; + }; + typedef std::list StateList; + // for Label class + struct ClabelVal { + ClabelVal(size_t offset = 0) : offset(offset), refCount(1) {} + size_t offset; + int refCount; + }; + typedef XBYAK_STD_UNORDERED_MAP ClabelDefList; + typedef XBYAK_STD_UNORDERED_MULTIMAP ClabelUndefList; + typedef XBYAK_STD_UNORDERED_SET LabelPtrList; + + CodeArray *base_; + // global : stateList_.front(), local : stateList_.back() + StateList stateList_; + mutable int labelId_; + ClabelDefList clabelDefList_; + ClabelUndefList clabelUndefList_; + LabelPtrList labelPtrList_; + + int getId(const Label& label) const + { + if (label.id == 0) label.id = labelId_++; + return label.id; + } + template + void define_inner(DefList& defList, UndefList& undefList, const T& labelId, size_t addrOffset) + { + // add label + typename DefList::value_type item(labelId, addrOffset); + std::pair ret = defList.insert(item); + if (!ret.second) throw Error(ERR_LABEL_IS_REDEFINED); + // search undefined label + for (;;) { + typename UndefList::iterator itr = undefList.find(labelId); + if (itr == undefList.end()) break; + const JmpLabel *jmp = &itr->second; + const size_t offset = jmp->endOfJmp - jmp->jmpSize; + size_t disp; + if (jmp->mode == inner::LaddTop) { + disp = addrOffset; + } else if (jmp->mode == inner::Labs) { + disp = size_t(base_->getCurr()); + } else { + disp = addrOffset - jmp->endOfJmp + jmp->disp; +#ifdef XBYAK64 + if (jmp->jmpSize <= 4 && !inner::IsInInt32(disp)) throw Error(ERR_OFFSET_IS_TOO_BIG); +#endif + if (jmp->jmpSize == 1 && !inner::IsInDisp8((uint32)disp)) throw Error(ERR_LABEL_IS_TOO_FAR); + } + if (base_->isAutoGrow()) { + base_->save(offset, disp, jmp->jmpSize, jmp->mode); + } else { + base_->rewrite(offset, disp, jmp->jmpSize); + } + undefList.erase(itr); + } + } + template + bool getOffset_inner(const DefList& defList, size_t *offset, const T& label) const + { + typename DefList::const_iterator i = defList.find(label); + if (i == defList.end()) return false; + *offset = i->second.offset; + return true; + } + friend class Label; + void incRefCount(int id, Label *label) + { + clabelDefList_[id].refCount++; + labelPtrList_.insert(label); + } + void decRefCount(int id, Label *label) + { + labelPtrList_.erase(label); + ClabelDefList::iterator i = clabelDefList_.find(id); + if (i == clabelDefList_.end()) return; + if (i->second.refCount == 1) { + clabelDefList_.erase(id); + } else { + --i->second.refCount; + } + } + template + bool hasUndefinedLabel_inner(const T& list) const + { +#ifndef NDEBUG + for (typename T::const_iterator i = list.begin(); i != list.end(); ++i) { + std::cerr << "undefined label:" << i->first << std::endl; + } +#endif + return !list.empty(); + } + // detach all labels linked to LabelManager + void resetLabelPtrList() + { + for (LabelPtrList::iterator i = labelPtrList_.begin(), ie = labelPtrList_.end(); i != ie; ++i) { + (*i)->clear(); + } + labelPtrList_.clear(); + } +public: + LabelManager() + { + reset(); + } + ~LabelManager() + { + resetLabelPtrList(); + } + void reset() + { + base_ = 0; + labelId_ = 1; + stateList_.clear(); + stateList_.push_back(SlabelState()); + stateList_.push_back(SlabelState()); + clabelDefList_.clear(); + clabelUndefList_.clear(); + resetLabelPtrList(); + } + void enterLocal() + { + stateList_.push_back(SlabelState()); + } + void leaveLocal() + { + if (stateList_.size() <= 2) throw Error(ERR_UNDER_LOCAL_LABEL); + if (hasUndefinedLabel_inner(stateList_.back().undefList)) throw Error(ERR_LABEL_IS_NOT_FOUND); + stateList_.pop_back(); + } + void set(CodeArray *base) { base_ = base; } + void defineSlabel(std::string label) + { + if (label == "@b" || label == "@f") throw Error(ERR_BAD_LABEL_STR); + if (label == "@@") { + SlabelDefList& defList = stateList_.front().defList; + SlabelDefList::iterator i = defList.find("@f"); + if (i != defList.end()) { + defList.erase(i); + label = "@b"; + } else { + i = defList.find("@b"); + if (i != defList.end()) { + defList.erase(i); + } + label = "@f"; + } + } + SlabelState& st = *label.c_str() == '.' ? stateList_.back() : stateList_.front(); + define_inner(st.defList, st.undefList, label, base_->getSize()); + } + void defineClabel(Label& label) + { + define_inner(clabelDefList_, clabelUndefList_, getId(label), base_->getSize()); + label.mgr = this; + labelPtrList_.insert(&label); + } + void assign(Label& dst, const Label& src) + { + ClabelDefList::const_iterator i = clabelDefList_.find(src.id); + if (i == clabelDefList_.end()) throw Error(ERR_LABEL_ISNOT_SET_BY_L); + define_inner(clabelDefList_, clabelUndefList_, dst.id, i->second.offset); + dst.mgr = this; + labelPtrList_.insert(&dst); + } + bool getOffset(size_t *offset, std::string& label) const + { + const SlabelDefList& defList = stateList_.front().defList; + if (label == "@b") { + if (defList.find("@f") != defList.end()) { + label = "@f"; + } else if (defList.find("@b") == defList.end()) { + throw Error(ERR_LABEL_IS_NOT_FOUND); + } + } else if (label == "@f") { + if (defList.find("@f") != defList.end()) { + label = "@b"; + } + } + const SlabelState& st = *label.c_str() == '.' ? stateList_.back() : stateList_.front(); + return getOffset_inner(st.defList, offset, label); + } + bool getOffset(size_t *offset, const Label& label) const + { + return getOffset_inner(clabelDefList_, offset, getId(label)); + } + void addUndefinedLabel(const std::string& label, const JmpLabel& jmp) + { + SlabelState& st = *label.c_str() == '.' ? stateList_.back() : stateList_.front(); + st.undefList.insert(SlabelUndefList::value_type(label, jmp)); + } + void addUndefinedLabel(const Label& label, const JmpLabel& jmp) + { + clabelUndefList_.insert(ClabelUndefList::value_type(label.id, jmp)); + } + bool hasUndefSlabel() const + { + for (StateList::const_iterator i = stateList_.begin(), ie = stateList_.end(); i != ie; ++i) { + if (hasUndefinedLabel_inner(i->undefList)) return true; + } + return false; + } + bool hasUndefClabel() const { return hasUndefinedLabel_inner(clabelUndefList_); } + const uint8 *getCode() const { return base_->getCode(); } + bool isReady() const { return !base_->isAutoGrow() || base_->isCalledCalcJmpAddress(); } +}; + +inline Label::Label(const Label& rhs) +{ + id = rhs.id; + mgr = rhs.mgr; + if (mgr) mgr->incRefCount(id, this); +} +inline Label& Label::operator=(const Label& rhs) +{ + if (id) throw Error(ERR_LABEL_IS_ALREADY_SET_BY_L); + id = rhs.id; + mgr = rhs.mgr; + if (mgr) mgr->incRefCount(id, this); + return *this; +} +inline Label::~Label() +{ + if (id && mgr) mgr->decRefCount(id, this); +} +inline const uint8* Label::getAddress() const +{ + if (mgr == 0 || !mgr->isReady()) return 0; + size_t offset; + if (!mgr->getOffset(&offset, *this)) return 0; + return mgr->getCode() + offset; +} + +class CodeGenerator : public CodeArray { +public: + enum LabelType { + T_SHORT, + T_NEAR, + T_AUTO // T_SHORT if possible + }; +private: + CodeGenerator operator=(const CodeGenerator&); // don't call +#ifdef XBYAK64 + enum { i32e = 32 | 64, BIT = 64 }; + static const size_t dummyAddr = (size_t(0x11223344) << 32) | 55667788; + typedef Reg64 NativeReg; +#else + enum { i32e = 32, BIT = 32 }; + static const size_t dummyAddr = 0x12345678; + typedef Reg32 NativeReg; +#endif + // (XMM, XMM|MEM) + static inline bool isXMM_XMMorMEM(const Operand& op1, const Operand& op2) + { + return op1.isXMM() && (op2.isXMM() || op2.isMEM()); + } + // (MMX, MMX|MEM) or (XMM, XMM|MEM) + static inline bool isXMMorMMX_MEM(const Operand& op1, const Operand& op2) + { + return (op1.isMMX() && (op2.isMMX() || op2.isMEM())) || isXMM_XMMorMEM(op1, op2); + } + // (XMM, MMX|MEM) + static inline bool isXMM_MMXorMEM(const Operand& op1, const Operand& op2) + { + return op1.isXMM() && (op2.isMMX() || op2.isMEM()); + } + // (MMX, XMM|MEM) + static inline bool isMMX_XMMorMEM(const Operand& op1, const Operand& op2) + { + return op1.isMMX() && (op2.isXMM() || op2.isMEM()); + } + // (XMM, REG32|MEM) + static inline bool isXMM_REG32orMEM(const Operand& op1, const Operand& op2) + { + return op1.isXMM() && (op2.isREG(i32e) || op2.isMEM()); + } + // (REG32, XMM|MEM) + static inline bool isREG32_XMMorMEM(const Operand& op1, const Operand& op2) + { + return op1.isREG(i32e) && (op2.isXMM() || op2.isMEM()); + } + // (REG32, REG32|MEM) + static inline bool isREG32_REG32orMEM(const Operand& op1, const Operand& op2) + { + return op1.isREG(i32e) && ((op2.isREG(i32e) && op1.getBit() == op2.getBit()) || op2.isMEM()); + } + void rex(const Operand& op1, const Operand& op2 = Operand()) + { + uint8 rex = 0; + const Operand *p1 = &op1, *p2 = &op2; + if (p1->isMEM()) std::swap(p1, p2); + if (p1->isMEM()) throw Error(ERR_BAD_COMBINATION); + if (p2->isMEM()) { + const Address& addr = p2->getAddress(); + if (BIT == 64 && addr.is32bit()) db(0x67); + rex = addr.getRex() | p1->getReg().getRex(); + } else { + // ModRM(reg, base); + rex = op2.getReg().getRex(op1.getReg()); + } + // except movsx(16bit, 32/64bit) + if ((op1.isBit(16) && !op2.isBit(i32e)) || (op2.isBit(16) && !op1.isBit(i32e))) db(0x66); + if (rex) db(rex); + } + enum AVXtype { + // low 3 bit + T_N1 = 1, + T_N2 = 2, + T_N4 = 3, + T_N8 = 4, + T_N16 = 5, + T_N32 = 6, + T_NX_MASK = 7, + // + T_N_VL = 1 << 3, // N * (1, 2, 4) for VL + T_DUP = 1 << 4, // N = (8, 32, 64) + T_66 = 1 << 5, + T_F3 = 1 << 6, + T_F2 = 1 << 7, + T_0F = 1 << 8, + T_0F38 = 1 << 9, + T_0F3A = 1 << 10, + T_L0 = 1 << 11, + T_L1 = 1 << 12, + T_W0 = 1 << 13, + T_W1 = 1 << 14, + T_EW0 = 1 << 15, + T_EW1 = 1 << 16, + T_YMM = 1 << 17, // support YMM, ZMM + T_EVEX = 1 << 18, + T_ER_X = 1 << 19, // xmm{er} + T_ER_Y = 1 << 20, // ymm{er} + T_ER_Z = 1 << 21, // zmm{er} + T_SAE_X = 1 << 22, // xmm{sae} + T_SAE_Y = 1 << 23, // ymm{sae} + T_SAE_Z = 1 << 24, // zmm{sae} + T_MUST_EVEX = 1 << 25, // contains T_EVEX + T_B32 = 1 << 26, // m32bcst + T_B64 = 1 << 27, // m64bcst + T_M_K = 1 << 28, // mem{k} + T_VSIB = 1 << 29, + T_MEM_EVEX = 1 << 30, // use evex if mem + T_XXX + }; + void vex(const Reg& reg, const Reg& base, const Operand *v, int type, int code, bool x = false) + { + int w = (type & T_W1) ? 1 : 0; + bool is256 = (type & T_L1) ? true : (type & T_L0) ? false : reg.isYMM(); + bool r = reg.isExtIdx(); + bool b = base.isExtIdx(); + int idx = v ? v->getIdx() : 0; + if ((idx | reg.getIdx() | base.getIdx()) >= 16) throw Error(ERR_BAD_COMBINATION); + uint32 pp = (type & T_66) ? 1 : (type & T_F3) ? 2 : (type & T_F2) ? 3 : 0; + uint32 vvvv = (((~idx) & 15) << 3) | (is256 ? 4 : 0) | pp; + if (!b && !x && !w && (type & T_0F)) { + db(0xC5); db((r ? 0 : 0x80) | vvvv); + } else { + uint32 mmmm = (type & T_0F) ? 1 : (type & T_0F38) ? 2 : (type & T_0F3A) ? 3 : 0; + db(0xC4); db((r ? 0 : 0x80) | (x ? 0 : 0x40) | (b ? 0 : 0x20) | mmmm); db((w << 7) | vvvv); + } + db(code); + } + void verifySAE(const Reg& r, int type) const + { + if (((type & T_SAE_X) && r.isXMM()) || ((type & T_SAE_Y) && r.isYMM()) || ((type & T_SAE_Z) && r.isZMM())) return; + throw Error(ERR_SAE_IS_INVALID); + } + void verifyER(const Reg& r, int type) const + { + if (((type & T_ER_X) && r.isXMM()) || ((type & T_ER_Y) && r.isYMM()) || ((type & T_ER_Z) && r.isZMM())) return; + throw Error(ERR_ER_IS_INVALID); + } + // (a, b, c) contains non zero two or three values then err + int verifyDuplicate(int a, int b, int c, int err) + { + int v = a | b | c; + if ((a > 0 && a != v) + (b > 0 && b != v) + (c > 0 && c != v) > 0) return Error(err); + return v; + } + int evex(const Reg& reg, const Reg& base, const Operand *v, int type, int code, bool x = false, bool b = false, int aaa = 0, uint32 VL = 0, bool Hi16Vidx = false) + { + if (!(type & (T_EVEX | T_MUST_EVEX))) throw Error(ERR_EVEX_IS_INVALID); + int w = (type & T_EW1) ? 1 : 0; + uint32 mm = (type & T_0F) ? 1 : (type & T_0F38) ? 2 : (type & T_0F3A) ? 3 : 0; + uint32 pp = (type & T_66) ? 1 : (type & T_F3) ? 2 : (type & T_F2) ? 3 : 0; + + int idx = v ? v->getIdx() : 0; + uint32 vvvv = ~idx; + + bool R = !reg.isExtIdx(); + bool X = x ? false : !base.isExtIdx2(); + bool B = !base.isExtIdx(); + bool Rp = !reg.isExtIdx2(); + int LL; + int rounding = verifyDuplicate(reg.getRounding(), base.getRounding(), v ? v->getRounding() : 0, ERR_ROUNDING_IS_ALREADY_SET); + int disp8N = 1; + if (rounding) { + if (rounding == EvexModifierRounding::T_SAE) { + verifySAE(base, type); LL = 0; + } else { + verifyER(base, type); LL = rounding - 1; + } + b = true; + } else { + if (v) VL = (std::max)(VL, v->getBit()); + VL = (std::max)((std::max)(reg.getBit(), base.getBit()), VL); + LL = (VL == 512) ? 2 : (VL == 256) ? 1 : 0; + if (b) { + disp8N = (type & T_B32) ? 4 : 8; + } else if (type & T_DUP) { + disp8N = VL == 128 ? 8 : VL == 256 ? 32 : 64; + } else { + if ((type & (T_NX_MASK | T_N_VL)) == 0) { + type |= T_N16 | T_N_VL; // default + } + int low = type & T_NX_MASK; + if (low > 0) { + disp8N = 1 << (low - 1); + if (type & T_N_VL) disp8N *= (VL == 512 ? 4 : VL == 256 ? 2 : 1); + } + } + } + bool Vp = !((v ? v->isExtIdx2() : 0) | Hi16Vidx); + bool z = reg.hasZero() || base.hasZero() || (v ? v->hasZero() : false); + if (aaa == 0) aaa = verifyDuplicate(base.getOpmaskIdx(), reg.getOpmaskIdx(), (v ? v->getOpmaskIdx() : 0), ERR_OPMASK_IS_ALREADY_SET); + db(0x62); + db((R ? 0x80 : 0) | (X ? 0x40 : 0) | (B ? 0x20 : 0) | (Rp ? 0x10 : 0) | (mm & 3)); + db((w == 1 ? 0x80 : 0) | ((vvvv & 15) << 3) | 4 | (pp & 3)); + db((z ? 0x80 : 0) | ((LL & 3) << 5) | (b ? 0x10 : 0) | (Vp ? 8 : 0) | (aaa & 7)); + db(code); + return disp8N; + } + void setModRM(int mod, int r1, int r2) + { + db(static_cast((mod << 6) | ((r1 & 7) << 3) | (r2 & 7))); + } + void setSIB(const RegExp& e, int reg, int disp8N = 0) + { + size_t disp64 = e.getDisp(); +#ifdef XBYAK64 + size_t high = disp64 >> 32; + if (high != 0 && high != 0xFFFFFFFF) throw Error(ERR_OFFSET_IS_TOO_BIG); +#endif + uint32 disp = static_cast(disp64); + const Reg& base = e.getBase(); + const Reg& index = e.getIndex(); + const int baseIdx = base.getIdx(); + const int baseBit = base.getBit(); + const int indexBit = index.getBit(); + enum { + mod00 = 0, mod01 = 1, mod10 = 2 + }; + int mod = mod10; // disp32 + if (!baseBit || ((baseIdx & 7) != Operand::EBP && disp == 0)) { + mod = mod00; + } else { + if (disp8N == 0) { + if (inner::IsInDisp8(disp)) { + mod = mod01; + } + } else { + // disp must be casted to signed + uint32 t = static_cast(static_cast(disp) / disp8N); + if ((disp % disp8N) == 0 && inner::IsInDisp8(t)) { + disp = t; + mod = mod01; + } + } + } + const int newBaseIdx = baseBit ? (baseIdx & 7) : Operand::EBP; + /* ModR/M = [2:3:3] = [Mod:reg/code:R/M] */ + bool hasSIB = indexBit || (baseIdx & 7) == Operand::ESP; +#ifdef XBYAK64 + if (!baseBit && !indexBit) hasSIB = true; +#endif + if (hasSIB) { + setModRM(mod, reg, Operand::ESP); + /* SIB = [2:3:3] = [SS:index:base(=rm)] */ + const int idx = indexBit ? (index.getIdx() & 7) : Operand::ESP; + const int scale = e.getScale(); + const int SS = (scale == 8) ? 3 : (scale == 4) ? 2 : (scale == 2) ? 1 : 0; + setModRM(SS, idx, newBaseIdx); + } else { + setModRM(mod, reg, newBaseIdx); + } + if (mod == mod01) { + db(disp); + } else if (mod == mod10 || (mod == mod00 && !baseBit)) { + dd(disp); + } + } + LabelManager labelMgr_; + bool isInDisp16(uint32 x) const { return 0xFFFF8000 <= x || x <= 0x7FFF; } + void opModR(const Reg& reg1, const Reg& reg2, int code0, int code1 = NONE, int code2 = NONE) + { + rex(reg2, reg1); + db(code0 | (reg1.isBit(8) ? 0 : 1)); if (code1 != NONE) db(code1); if (code2 != NONE) db(code2); + setModRM(3, reg1.getIdx(), reg2.getIdx()); + } + void opModM(const Address& addr, const Reg& reg, int code0, int code1 = NONE, int code2 = NONE, int immSize = 0) + { + if (addr.is64bitDisp()) throw Error(ERR_CANT_USE_64BIT_DISP); + rex(addr, reg); + db(code0 | (reg.isBit(8) ? 0 : 1)); if (code1 != NONE) db(code1); if (code2 != NONE) db(code2); + opAddr(addr, reg.getIdx(), immSize); + } + void opMIB(const Address& addr, const Reg& reg, int code0, int code1) + { + if (addr.is64bitDisp()) throw Error(ERR_CANT_USE_64BIT_DISP); + if (addr.getMode() != Address::M_ModRM) throw Error(ERR_INVALID_MIB_ADDRESS); + if (BIT == 64 && addr.is32bit()) db(0x67); + const RegExp& regExp = addr.getRegExp(false); + uint8 rex = regExp.getRex(); + if (rex) db(rex); + db(code0); db(code1); + setSIB(regExp, reg.getIdx()); + } + void makeJmp(uint32 disp, LabelType type, uint8 shortCode, uint8 longCode, uint8 longPref) + { + const int shortJmpSize = 2; + const int longHeaderSize = longPref ? 2 : 1; + const int longJmpSize = longHeaderSize + 4; + if (type != T_NEAR && inner::IsInDisp8(disp - shortJmpSize)) { + db(shortCode); db(disp - shortJmpSize); + } else { + if (type == T_SHORT) throw Error(ERR_LABEL_IS_TOO_FAR); + if (longPref) db(longPref); + db(longCode); dd(disp - longJmpSize); + } + } + template + void opJmp(T& label, LabelType type, uint8 shortCode, uint8 longCode, uint8 longPref) + { + if (isAutoGrow() && size_ + 16 >= maxSize_) growMemory(); /* avoid splitting code of jmp */ + size_t offset = 0; + if (labelMgr_.getOffset(&offset, label)) { /* label exists */ + makeJmp(inner::VerifyInInt32(offset - size_), type, shortCode, longCode, longPref); + } else { + int jmpSize = 0; + if (type == T_NEAR) { + jmpSize = 4; + if (longPref) db(longPref); + db(longCode); dd(0); + } else { + jmpSize = 1; + db(shortCode); db(0); + } + JmpLabel jmp(size_, jmpSize, inner::LasIs); + labelMgr_.addUndefinedLabel(label, jmp); + } + } + void opJmpAbs(const void *addr, LabelType type, uint8 shortCode, uint8 longCode, uint8 longPref = 0) + { + if (isAutoGrow()) { + if (type != T_NEAR) throw Error(ERR_ONLY_T_NEAR_IS_SUPPORTED_IN_AUTO_GROW); + if (size_ + 16 >= maxSize_) growMemory(); + if (longPref) db(longPref); + db(longCode); + dd(0); + save(size_ - 4, size_t(addr) - size_, 4, inner::Labs); + } else { + makeJmp(inner::VerifyInInt32(reinterpret_cast(addr) - getCurr()), type, shortCode, longCode, longPref); + } + + } + // reg is reg field of ModRM + // immSize is the size for immediate value + // disp8N = 0(normal), disp8N = 1(force disp32), disp8N = {2, 4, 8} ; compressed displacement + void opAddr(const Address &addr, int reg, int immSize = 0, int disp8N = 0, bool permitVisb = false) + { + if (!permitVisb && addr.isVsib()) throw Error(ERR_BAD_VSIB_ADDRESSING); + if (addr.getMode() == Address::M_ModRM) { + setSIB(addr.getRegExp(), reg, disp8N); + } else if (addr.getMode() == Address::M_rip || addr.getMode() == Address::M_ripAddr) { + setModRM(0, reg, 5); + if (addr.getLabel()) { // [rip + Label] + putL_inner(*addr.getLabel(), true, addr.getDisp() - immSize); + } else { + size_t disp = addr.getDisp(); + if (addr.getMode() == Address::M_ripAddr) { + if (isAutoGrow()) throw Error(ERR_INVALID_RIP_IN_AUTO_GROW); + disp -= (size_t)getCurr() + 4 + immSize; + } + dd(inner::VerifyInInt32(disp)); + } + } + } + /* preCode is for SSSE3/SSE4 */ + void opGen(const Operand& reg, const Operand& op, int code, int pref, bool isValid(const Operand&, const Operand&), int imm8 = NONE, int preCode = NONE) + { + if (isValid && !isValid(reg, op)) throw Error(ERR_BAD_COMBINATION); + if (pref != NONE) db(pref); + if (op.isMEM()) { + opModM(op.getAddress(), reg.getReg(), 0x0F, preCode, code, (imm8 != NONE) ? 1 : 0); + } else { + opModR(reg.getReg(), op.getReg(), 0x0F, preCode, code); + } + if (imm8 != NONE) db(imm8); + } + void opMMX_IMM(const Mmx& mmx, int imm8, int code, int ext) + { + if (mmx.isXMM()) db(0x66); + opModR(Reg32(ext), mmx, 0x0F, code); + db(imm8); + } + void opMMX(const Mmx& mmx, const Operand& op, int code, int pref = 0x66, int imm8 = NONE, int preCode = NONE) + { + opGen(mmx, op, code, mmx.isXMM() ? pref : NONE, isXMMorMMX_MEM, imm8, preCode); + } + void opMovXMM(const Operand& op1, const Operand& op2, int code, int pref) + { + if (pref != NONE) db(pref); + if (op1.isXMM() && op2.isMEM()) { + opModM(op2.getAddress(), op1.getReg(), 0x0F, code); + } else if (op1.isMEM() && op2.isXMM()) { + opModM(op1.getAddress(), op2.getReg(), 0x0F, code | 1); + } else { + throw Error(ERR_BAD_COMBINATION); + } + } + void opExt(const Operand& op, const Mmx& mmx, int code, int imm, bool hasMMX2 = false) + { + if (hasMMX2 && op.isREG(i32e)) { /* pextrw is special */ + if (mmx.isXMM()) db(0x66); + opModR(op.getReg(), mmx, 0x0F, 0xC5); db(imm); + } else { + opGen(mmx, op, code, 0x66, isXMM_REG32orMEM, imm, 0x3A); + } + } + void opR_ModM(const Operand& op, int bit, int ext, int code0, int code1 = NONE, int code2 = NONE, bool disableRex = false, int immSize = 0) + { + int opBit = op.getBit(); + if (disableRex && opBit == 64) opBit = 32; + if (op.isREG(bit)) { + opModR(Reg(ext, Operand::REG, opBit), op.getReg().changeBit(opBit), code0, code1, code2); + } else if (op.isMEM()) { + opModM(op.getAddress(), Reg(ext, Operand::REG, opBit), code0, code1, code2, immSize); + } else { + throw Error(ERR_BAD_COMBINATION); + } + } + void opShift(const Operand& op, int imm, int ext) + { + verifyMemHasSize(op); + opR_ModM(op, 0, ext, (0xC0 | ((imm == 1 ? 1 : 0) << 4)), NONE, NONE, false, (imm != 1) ? 1 : 0); + if (imm != 1) db(imm); + } + void opShift(const Operand& op, const Reg8& _cl, int ext) + { + if (_cl.getIdx() != Operand::CL) throw Error(ERR_BAD_COMBINATION); + opR_ModM(op, 0, ext, 0xD2); + } + void opModRM(const Operand& op1, const Operand& op2, bool condR, bool condM, int code0, int code1 = NONE, int code2 = NONE, int immSize = 0) + { + if (condR) { + opModR(op1.getReg(), op2.getReg(), code0, code1, code2); + } else if (condM) { + opModM(op2.getAddress(), op1.getReg(), code0, code1, code2, immSize); + } else { + throw Error(ERR_BAD_COMBINATION); + } + } + void opShxd(const Operand& op, const Reg& reg, uint8 imm, int code, const Reg8 *_cl = 0) + { + if (_cl && _cl->getIdx() != Operand::CL) throw Error(ERR_BAD_COMBINATION); + opModRM(reg, op, (op.isREG(16 | i32e) && op.getBit() == reg.getBit()), op.isMEM() && (reg.isREG(16 | i32e)), 0x0F, code | (_cl ? 1 : 0), NONE, _cl ? 0 : 1); + if (!_cl) db(imm); + } + // (REG, REG|MEM), (MEM, REG) + void opRM_RM(const Operand& op1, const Operand& op2, int code) + { + if (op1.isREG() && op2.isMEM()) { + opModM(op2.getAddress(), op1.getReg(), code | 2); + } else { + opModRM(op2, op1, op1.isREG() && op1.getKind() == op2.getKind(), op1.isMEM() && op2.isREG(), code); + } + } + // (REG|MEM, IMM) + void opRM_I(const Operand& op, uint32 imm, int code, int ext) + { + verifyMemHasSize(op); + uint32 immBit = inner::IsInDisp8(imm) ? 8 : isInDisp16(imm) ? 16 : 32; + if (op.isBit(8)) immBit = 8; + if (op.getBit() < immBit) throw Error(ERR_IMM_IS_TOO_BIG); + if (op.isBit(32|64) && immBit == 16) immBit = 32; /* don't use MEM16 if 32/64bit mode */ + if (op.isREG() && op.getIdx() == 0 && (op.getBit() == immBit || (op.isBit(64) && immBit == 32))) { // rax, eax, ax, al + rex(op); + db(code | 4 | (immBit == 8 ? 0 : 1)); + } else { + int tmp = immBit < (std::min)(op.getBit(), 32U) ? 2 : 0; + opR_ModM(op, 0, ext, 0x80 | tmp, NONE, NONE, false, immBit / 8); + } + db(imm, immBit / 8); + } + void opIncDec(const Operand& op, int code, int ext) + { + verifyMemHasSize(op); +#ifndef XBYAK64 + if (op.isREG() && !op.isBit(8)) { + rex(op); db(code | op.getIdx()); + return; + } +#endif + code = 0xFE; + if (op.isREG()) { + opModR(Reg(ext, Operand::REG, op.getBit()), op.getReg(), code); + } else { + opModM(op.getAddress(), Reg(ext, Operand::REG, op.getBit()), code); + } + } + void opPushPop(const Operand& op, int code, int ext, int alt) + { + int bit = op.getBit(); + if (bit == 16 || bit == BIT) { + if (bit == 16) db(0x66); + if (op.isREG()) { + if (op.getReg().getIdx() >= 8) db(0x41); + db(alt | (op.getIdx() & 7)); + return; + } + if (op.isMEM()) { + opModM(op.getAddress(), Reg(ext, Operand::REG, 32), code); + return; + } + } + throw Error(ERR_BAD_COMBINATION); + } + void verifyMemHasSize(const Operand& op) const + { + if (op.isMEM() && op.getBit() == 0) throw Error(ERR_MEM_SIZE_IS_NOT_SPECIFIED); + } + /* + mov(r, imm) = db(imm, mov_imm(r, imm)) + */ + int mov_imm(const Reg& reg, size_t imm) + { + int bit = reg.getBit(); + const int idx = reg.getIdx(); + int code = 0xB0 | ((bit == 8 ? 0 : 1) << 3); + if (bit == 64 && (imm & ~size_t(0xffffffffu)) == 0) { + rex(Reg32(idx)); + bit = 32; + } else { + rex(reg); + if (bit == 64 && inner::IsInInt32(imm)) { + db(0xC7); + code = 0xC0; + bit = 32; + } + } + db(code | (idx & 7)); + return bit / 8; + } + template + void putL_inner(T& label, bool relative = false, size_t disp = 0) + { + const int jmpSize = relative ? 4 : (int)sizeof(size_t); + if (isAutoGrow() && size_ + 16 >= maxSize_) growMemory(); + size_t offset = 0; + if (labelMgr_.getOffset(&offset, label)) { + if (relative) { + db(inner::VerifyInInt32(offset + disp - size_ - jmpSize), jmpSize); + } else if (isAutoGrow()) { + db(uint64(0), jmpSize); + save(size_ - jmpSize, offset, jmpSize, inner::LaddTop); + } else { + db(size_t(top_) + offset, jmpSize); + } + return; + } + db(uint64(0), jmpSize); + JmpLabel jmp(size_, jmpSize, (relative ? inner::LasIs : isAutoGrow() ? inner::LaddTop : inner::Labs), disp); + labelMgr_.addUndefinedLabel(label, jmp); + } + void opMovxx(const Reg& reg, const Operand& op, uint8 code) + { + if (op.isBit(32)) throw Error(ERR_BAD_COMBINATION); + int w = op.isBit(16); +#ifdef XBYAK64 + if (op.isHigh8bit()) throw Error(ERR_BAD_COMBINATION); +#endif + bool cond = reg.isREG() && (reg.getBit() > op.getBit()); + opModRM(reg, op, cond && op.isREG(), cond && op.isMEM(), 0x0F, code | w); + } + void opFpuMem(const Address& addr, uint8 m16, uint8 m32, uint8 m64, uint8 ext, uint8 m64ext) + { + if (addr.is64bitDisp()) throw Error(ERR_CANT_USE_64BIT_DISP); + uint8 code = addr.isBit(16) ? m16 : addr.isBit(32) ? m32 : addr.isBit(64) ? m64 : 0; + if (!code) throw Error(ERR_BAD_MEM_SIZE); + if (m64ext && addr.isBit(64)) ext = m64ext; + + rex(addr, st0); + db(code); + opAddr(addr, ext); + } + // use code1 if reg1 == st0 + // use code2 if reg1 != st0 && reg2 == st0 + void opFpuFpu(const Fpu& reg1, const Fpu& reg2, uint32 code1, uint32 code2) + { + uint32 code = reg1.getIdx() == 0 ? code1 : reg2.getIdx() == 0 ? code2 : 0; + if (!code) throw Error(ERR_BAD_ST_COMBINATION); + db(uint8(code >> 8)); + db(uint8(code | (reg1.getIdx() | reg2.getIdx()))); + } + void opFpu(const Fpu& reg, uint8 code1, uint8 code2) + { + db(code1); db(code2 | reg.getIdx()); + } + void opVex(const Reg& r, const Operand *p1, const Operand& op2, int type, int code, int imm8 = NONE) + { + if (op2.isMEM()) { + const Address& addr = op2.getAddress(); + const RegExp& regExp = addr.getRegExp(); + const Reg& base = regExp.getBase(); + const Reg& index = regExp.getIndex(); + if (BIT == 64 && addr.is32bit()) db(0x67); + int disp8N = 0; + bool x = index.isExtIdx(); + if ((type & (T_MUST_EVEX|T_MEM_EVEX)) || r.hasEvex() || (p1 && p1->hasEvex()) || addr.isBroadcast() || addr.getOpmaskIdx()) { + int aaa = addr.getOpmaskIdx(); + if (aaa && !(type & T_M_K)) throw Error(ERR_INVALID_OPMASK_WITH_MEMORY); + bool b = false; + if (addr.isBroadcast()) { + if (!(type & (T_B32 | T_B64))) throw Error(ERR_INVALID_BROADCAST); + b = true; + } + int VL = regExp.isVsib() ? index.getBit() : 0; + disp8N = evex(r, base, p1, type, code, x, b, aaa, VL, index.isExtIdx2()); + } else { + vex(r, base, p1, type, code, x); + } + opAddr(addr, r.getIdx(), (imm8 != NONE) ? 1 : 0, disp8N, (type & T_VSIB) != 0); + } else { + const Reg& base = op2.getReg(); + if ((type & T_MUST_EVEX) || r.hasEvex() || (p1 && p1->hasEvex()) || base.hasEvex()) { + evex(r, base, p1, type, code); + } else { + vex(r, base, p1, type, code); + } + setModRM(3, r.getIdx(), base.getIdx()); + } + if (imm8 != NONE) db(imm8); + } + // (r, r, r/m) if isR_R_RM + // (r, r/m, r) + void opGpr(const Reg32e& r, const Operand& op1, const Operand& op2, int type, uint8 code, bool isR_R_RM, int imm8 = NONE) + { + const Operand *p1 = &op1; + const Operand *p2 = &op2; + if (!isR_R_RM) std::swap(p1, p2); + const unsigned int bit = r.getBit(); + if (p1->getBit() != bit || (p2->isREG() && p2->getBit() != bit)) throw Error(ERR_BAD_COMBINATION); + type |= (bit == 64) ? T_W1 : T_W0; + opVex(r, p1, *p2, type, code, imm8); + } + void opAVX_X_X_XM(const Xmm& x1, const Operand& op1, const Operand& op2, int type, int code0, int imm8 = NONE) + { + const Xmm *x2 = static_cast(&op1); + const Operand *op = &op2; + if (op2.isNone()) { // (x1, op1) -> (x1, x1, op1) + x2 = &x1; + op = &op1; + } + // (x1, x2, op) + if (!((x1.isXMM() && x2->isXMM()) || ((type & T_YMM) && ((x1.isYMM() && x2->isYMM()) || (x1.isZMM() && x2->isZMM()))))) throw Error(ERR_BAD_COMBINATION); + opVex(x1, x2, *op, type, code0, imm8); + } + void opAVX_K_X_XM(const Opmask& k, const Xmm& x2, const Operand& op3, int type, int code0, int imm8 = NONE) + { + if (!op3.isMEM() && (x2.getKind() != op3.getKind())) throw Error(ERR_BAD_COMBINATION); + opVex(k, &x2, op3, type, code0, imm8); + } + // (x, x/m), (y, x/m256), (z, y/m) + void checkCvt1(const Operand& x, const Operand& op) const + { + if (!op.isMEM() && !(x.is(Operand::XMM | Operand::YMM) && op.isXMM()) && !(x.isZMM() && op.isYMM())) throw Error(ERR_BAD_COMBINATION); + } + // (x, x/m), (x, y/m256), (y, z/m) + void checkCvt2(const Xmm& x, const Operand& op) const + { + if (!(x.isXMM() && op.is(Operand::XMM | Operand::YMM | Operand::MEM)) && !(x.isYMM() && op.is(Operand::ZMM | Operand::MEM))) throw Error(ERR_BAD_COMBINATION); + } + void opCvt2(const Xmm& x, const Operand& op, int type, int code) + { + checkCvt2(x, op); + Operand::Kind kind = x.isXMM() ? (op.isBit(256) ? Operand::YMM : Operand::XMM) : Operand::ZMM; + opVex(x.copyAndSetKind(kind), &xm0, op, type, code); + } + void opCvt3(const Xmm& x1, const Xmm& x2, const Operand& op, int type, int type64, int type32, uint8 code) + { + if (!(x1.isXMM() && x2.isXMM() && (op.isREG(i32e) || op.isMEM()))) throw Error(ERR_BAD_SIZE_OF_REGISTER); + Xmm x(op.getIdx()); + const Operand *p = op.isREG() ? &x : &op; + opVex(x1, &x2, *p, type | (op.isBit(64) ? type64 : type32), code); + } + const Xmm& cvtIdx0(const Operand& x) const + { + return x.isZMM() ? zm0 : x.isYMM() ? ym0 : xm0; + } + // support (x, x/m, imm), (y, y/m, imm) + void opAVX_X_XM_IMM(const Xmm& x, const Operand& op, int type, int code, int imm8 = NONE) + { + opAVX_X_X_XM(x, cvtIdx0(x), op, type, code, imm8); + } + // QQQ:need to refactor + void opSp1(const Reg& reg, const Operand& op, uint8 pref, uint8 code0, uint8 code1) + { + if (reg.isBit(8)) throw Error(ERR_BAD_SIZE_OF_REGISTER); + bool is16bit = reg.isREG(16) && (op.isREG(16) || op.isMEM()); + if (!is16bit && !(reg.isREG(i32e) && (op.isREG(reg.getBit()) || op.isMEM()))) throw Error(ERR_BAD_COMBINATION); + if (is16bit) db(0x66); + db(pref); opModRM(reg.changeBit(i32e == 32 ? 32 : reg.getBit()), op, op.isREG(), true, code0, code1); + } + void opGather(const Xmm& x1, const Address& addr, const Xmm& x2, int type, uint8 code, int mode) + { + const RegExp& regExp = addr.getRegExp(); + if (!regExp.isVsib(128 | 256)) throw Error(ERR_BAD_VSIB_ADDRESSING); + const int y_vx_y = 0; + const int y_vy_y = 1; +// const int x_vy_x = 2; + const bool isAddrYMM = regExp.getIndex().getBit() == 256; + if (!x1.isXMM() || isAddrYMM || !x2.isXMM()) { + bool isOK = false; + if (mode == y_vx_y) { + isOK = x1.isYMM() && !isAddrYMM && x2.isYMM(); + } else if (mode == y_vy_y) { + isOK = x1.isYMM() && isAddrYMM && x2.isYMM(); + } else { // x_vy_x + isOK = !x1.isYMM() && isAddrYMM && !x2.isYMM(); + } + if (!isOK) throw Error(ERR_BAD_VSIB_ADDRESSING); + } + opAVX_X_X_XM(isAddrYMM ? Ymm(x1.getIdx()) : x1, isAddrYMM ? Ymm(x2.getIdx()) : x2, addr, type, code); + } + enum { + xx_yy_zz = 0, + xx_yx_zy = 1, + xx_xy_yz = 2 + }; + void checkGather2(const Xmm& x1, const Reg& x2, int mode) const + { + if (x1.isXMM() && x2.isXMM()) return; + switch (mode) { + case xx_yy_zz: if ((x1.isYMM() && x2.isYMM()) || (x1.isZMM() && x2.isZMM())) return; + break; + case xx_yx_zy: if ((x1.isYMM() && x2.isXMM()) || (x1.isZMM() && x2.isYMM())) return; + break; + case xx_xy_yz: if ((x1.isXMM() && x2.isYMM()) || (x1.isYMM() && x2.isZMM())) return; + break; + } + throw Error(ERR_BAD_VSIB_ADDRESSING); + } + void opGather2(const Xmm& x, const Address& addr, int type, uint8 code, int mode) + { + if (x.hasZero()) throw Error(ERR_INVALID_ZERO); + checkGather2(x, addr.getRegExp().getIndex(), mode); + opVex(x, 0, addr, type, code); + } + /* + xx_xy_yz ; mode = true + xx_xy_xz ; mode = false + */ + void opVmov(const Operand& op, const Xmm& x, int type, uint8 code, bool mode) + { + if (mode) { + if (!op.isMEM() && !((op.isXMM() && x.isXMM()) || (op.isXMM() && x.isYMM()) || (op.isYMM() && x.isZMM()))) throw Error(ERR_BAD_COMBINATION); + } else { + if (!op.isMEM() && !op.isXMM()) throw Error(ERR_BAD_COMBINATION); + } + opVex(x, 0, op, type, code); + } + void opGatherFetch(const Address& addr, const Xmm& x, int type, uint8 code, Operand::Kind kind) + { + if (addr.hasZero()) throw Error(ERR_INVALID_ZERO); + if (addr.getRegExp().getIndex().getKind() != kind) throw Error(ERR_BAD_VSIB_ADDRESSING); + opVex(x, 0, addr, type, code); + } +public: + unsigned int getVersion() const { return VERSION; } + using CodeArray::db; + const Mmx mm0, mm1, mm2, mm3, mm4, mm5, mm6, mm7; + const Xmm xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7; + const Ymm ymm0, ymm1, ymm2, ymm3, ymm4, ymm5, ymm6, ymm7; + const Zmm zmm0, zmm1, zmm2, zmm3, zmm4, zmm5, zmm6, zmm7; + const Xmm &xm0, &xm1, &xm2, &xm3, &xm4, &xm5, &xm6, &xm7; + const Ymm &ym0, &ym1, &ym2, &ym3, &ym4, &ym5, &ym6, &ym7; + const Ymm &zm0, &zm1, &zm2, &zm3, &zm4, &zm5, &zm6, &zm7; + const Reg32 eax, ecx, edx, ebx, esp, ebp, esi, edi; + const Reg16 ax, cx, dx, bx, sp, bp, si, di; + const Reg8 al, cl, dl, bl, ah, ch, dh, bh; + const AddressFrame ptr, byte, word, dword, qword, xword, yword, zword; // xword is same as oword of NASM + const AddressFrame ptr_b, xword_b, yword_b, zword_b; // broadcast such as {1to2}, {1to4}, {1to8}, {1to16}, {b} + const Fpu st0, st1, st2, st3, st4, st5, st6, st7; + const Opmask k0, k1, k2, k3, k4, k5, k6, k7; + const BoundsReg bnd0, bnd1, bnd2, bnd3; + const EvexModifierRounding T_sae, T_rn_sae, T_rd_sae, T_ru_sae, T_rz_sae; // {sae}, {rn-sae}, {rd-sae}, {ru-sae}, {rz-sae} + const EvexModifierZero T_z; // {z} +#ifdef XBYAK64 + const Reg64 rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15; + const Reg32 r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d; + const Reg16 r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w; + const Reg8 r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b; + const Reg8 spl, bpl, sil, dil; + const Xmm xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15; + const Xmm xmm16, xmm17, xmm18, xmm19, xmm20, xmm21, xmm22, xmm23; + const Xmm xmm24, xmm25, xmm26, xmm27, xmm28, xmm29, xmm30, xmm31; + const Ymm ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15; + const Ymm ymm16, ymm17, ymm18, ymm19, ymm20, ymm21, ymm22, ymm23; + const Ymm ymm24, ymm25, ymm26, ymm27, ymm28, ymm29, ymm30, ymm31; + const Zmm zmm8, zmm9, zmm10, zmm11, zmm12, zmm13, zmm14, zmm15; + const Zmm zmm16, zmm17, zmm18, zmm19, zmm20, zmm21, zmm22, zmm23; + const Zmm zmm24, zmm25, zmm26, zmm27, zmm28, zmm29, zmm30, zmm31; + const Xmm &xm8, &xm9, &xm10, &xm11, &xm12, &xm13, &xm14, &xm15; // for my convenience + const Xmm &xm16, &xm17, &xm18, &xm19, &xm20, &xm21, &xm22, &xm23; + const Xmm &xm24, &xm25, &xm26, &xm27, &xm28, &xm29, &xm30, &xm31; + const Ymm &ym8, &ym9, &ym10, &ym11, &ym12, &ym13, &ym14, &ym15; + const Ymm &ym16, &ym17, &ym18, &ym19, &ym20, &ym21, &ym22, &ym23; + const Ymm &ym24, &ym25, &ym26, &ym27, &ym28, &ym29, &ym30, &ym31; + const Zmm &zm8, &zm9, &zm10, &zm11, &zm12, &zm13, &zm14, &zm15; + const Zmm &zm16, &zm17, &zm18, &zm19, &zm20, &zm21, &zm22, &zm23; + const Zmm &zm24, &zm25, &zm26, &zm27, &zm28, &zm29, &zm30, &zm31; + const RegRip rip; +#endif +#ifndef XBYAK_DISABLE_SEGMENT + const Segment es, cs, ss, ds, fs, gs; +#endif + void L(const std::string& label) { labelMgr_.defineSlabel(label); } + void L(Label& label) { labelMgr_.defineClabel(label); } + Label L() { Label label; L(label); return label; } + void inLocalLabel() { labelMgr_.enterLocal(); } + void outLocalLabel() { labelMgr_.leaveLocal(); } + /* + assign src to dst + require + dst : does not used by L() + src : used by L() + */ + void assignL(Label& dst, const Label& src) { labelMgr_.assign(dst, src); } + /* + put address of label to buffer + @note the put size is 4(32-bit), 8(64-bit) + */ + void putL(std::string label) { putL_inner(label); } + void putL(const Label& label) { putL_inner(label); } + + void jmp(const Operand& op) { opR_ModM(op, BIT, 4, 0xFF, NONE, NONE, true); } + void jmp(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0xEB, 0xE9, 0); } + void jmp(const char *label, LabelType type = T_AUTO) { jmp(std::string(label), type); } + void jmp(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0xEB, 0xE9, 0); } + void jmp(const void *addr, LabelType type = T_AUTO) { opJmpAbs(addr, type, 0xEB, 0xE9); } + + void call(const Operand& op) { opR_ModM(op, 16 | i32e, 2, 0xFF, NONE, NONE, true); } + // call(string label), not const std::string& + void call(std::string label) { opJmp(label, T_NEAR, 0, 0xE8, 0); } + void call(const char *label) { call(std::string(label)); } + void call(const Label& label) { opJmp(label, T_NEAR, 0, 0xE8, 0); } + // call(function pointer) +#ifdef XBYAK_VARIADIC_TEMPLATE + template + void call(Ret(*func)(Params...)) { call(reinterpret_cast(func)); } +#endif + void call(const void *addr) { opJmpAbs(addr, T_NEAR, 0, 0xE8); } + + void test(const Operand& op, const Reg& reg) + { + opModRM(reg, op, op.isREG() && (op.getKind() == reg.getKind()), op.isMEM(), 0x84); + } + void test(const Operand& op, uint32 imm) + { + verifyMemHasSize(op); + int immSize = (std::min)(op.getBit() / 8, 4U); + if (op.isREG() && op.getIdx() == 0) { // al, ax, eax + rex(op); + db(0xA8 | (op.isBit(8) ? 0 : 1)); + } else { + opR_ModM(op, 0, 0, 0xF6, NONE, NONE, false, immSize); + } + db(imm, immSize); + } + void imul(const Reg& reg, const Operand& op) + { + opModRM(reg, op, op.isREG() && (reg.getKind() == op.getKind()), op.isMEM(), 0x0F, 0xAF); + } + void imul(const Reg& reg, const Operand& op, int imm) + { + int s = inner::IsInDisp8(imm) ? 1 : 0; + int immSize = s ? 1 : reg.isREG(16) ? 2 : 4; + opModRM(reg, op, op.isREG() && (reg.getKind() == op.getKind()), op.isMEM(), 0x69 | (s << 1), NONE, NONE, immSize); + db(imm, immSize); + } + void push(const Operand& op) { opPushPop(op, 0xFF, 6, 0x50); } + void pop(const Operand& op) { opPushPop(op, 0x8F, 0, 0x58); } + void push(const AddressFrame& af, uint32 imm) + { + if (af.bit_ == 8 && inner::IsInDisp8(imm)) { + db(0x6A); db(imm); + } else if (af.bit_ == 16 && isInDisp16(imm)) { + db(0x66); db(0x68); dw(imm); + } else { + db(0x68); dd(imm); + } + } + /* use "push(word, 4)" if you want "push word 4" */ + void push(uint32 imm) + { + if (inner::IsInDisp8(imm)) { + push(byte, imm); + } else { + push(dword, imm); + } + } + void mov(const Operand& reg1, const Operand& reg2) + { + const Reg *reg = 0; + const Address *addr = 0; + uint8 code = 0; + if (reg1.isREG() && reg1.getIdx() == 0 && reg2.isMEM()) { // mov eax|ax|al, [disp] + reg = ®1.getReg(); + addr= ®2.getAddress(); + code = 0xA0; + } else + if (reg1.isMEM() && reg2.isREG() && reg2.getIdx() == 0) { // mov [disp], eax|ax|al + reg = ®2.getReg(); + addr= ®1.getAddress(); + code = 0xA2; + } +#ifdef XBYAK64 + if (addr && addr->is64bitDisp()) { + if (code) { + rex(*reg); + db(reg1.isREG(8) ? 0xA0 : reg1.isREG() ? 0xA1 : reg2.isREG(8) ? 0xA2 : 0xA3); + db(addr->getDisp(), 8); + } else { + throw Error(ERR_BAD_COMBINATION); + } + } else +#else + if (code && addr->isOnlyDisp()) { + rex(*reg, *addr); + db(code | (reg->isBit(8) ? 0 : 1)); + dd(static_cast(addr->getDisp())); + } else +#endif + { + opRM_RM(reg1, reg2, 0x88); + } + } + void mov(const Operand& op, size_t imm) + { + if (op.isREG()) { + const int size = mov_imm(op.getReg(), imm); + db(imm, size); + } else if (op.isMEM()) { + verifyMemHasSize(op); + int immSize = op.getBit() / 8; + if (immSize <= 4) { + sint64 s = sint64(imm) >> (immSize * 8); + if (s != 0 && s != -1) throw Error(ERR_IMM_IS_TOO_BIG); + } else { + if (!inner::IsInInt32(imm)) throw Error(ERR_IMM_IS_TOO_BIG); + immSize = 4; + } + opModM(op.getAddress(), Reg(0, Operand::REG, op.getBit()), 0xC6, NONE, NONE, immSize); + db(static_cast(imm), immSize); + } else { + throw Error(ERR_BAD_COMBINATION); + } + } + void mov(const NativeReg& reg, const char *label) // can't use std::string + { + if (label == 0) { + mov(static_cast(reg), 0); // call imm + return; + } + mov_imm(reg, dummyAddr); + putL(label); + } + void mov(const NativeReg& reg, const Label& label) + { + mov_imm(reg, dummyAddr); + putL(label); + } + void xchg(const Operand& op1, const Operand& op2) + { + const Operand *p1 = &op1, *p2 = &op2; + if (p1->isMEM() || (p2->isREG(16 | i32e) && p2->getIdx() == 0)) { + p1 = &op2; p2 = &op1; + } + if (p1->isMEM()) throw Error(ERR_BAD_COMBINATION); + if (p2->isREG() && (p1->isREG(16 | i32e) && p1->getIdx() == 0) +#ifdef XBYAK64 + && (p2->getIdx() != 0 || !p1->isREG(32)) +#endif + ) { + rex(*p2, *p1); db(0x90 | (p2->getIdx() & 7)); + return; + } + opModRM(*p1, *p2, (p1->isREG() && p2->isREG() && (p1->getBit() == p2->getBit())), p2->isMEM(), 0x86 | (p1->isBit(8) ? 0 : 1)); + } + +#ifndef XBYAK_DISABLE_SEGMENT + void push(const Segment& seg) + { + switch (seg.getIdx()) { + case Segment::es: db(0x06); break; + case Segment::cs: db(0x0E); break; + case Segment::ss: db(0x16); break; + case Segment::ds: db(0x1E); break; + case Segment::fs: db(0x0F); db(0xA0); break; + case Segment::gs: db(0x0F); db(0xA8); break; + default: + assert(0); + } + } + void pop(const Segment& seg) + { + switch (seg.getIdx()) { + case Segment::es: db(0x07); break; + case Segment::cs: throw Error(ERR_BAD_COMBINATION); + case Segment::ss: db(0x17); break; + case Segment::ds: db(0x1F); break; + case Segment::fs: db(0x0F); db(0xA1); break; + case Segment::gs: db(0x0F); db(0xA9); break; + default: + assert(0); + } + } + void putSeg(const Segment& seg) + { + switch (seg.getIdx()) { + case Segment::es: db(0x2E); break; + case Segment::cs: db(0x36); break; + case Segment::ss: db(0x3E); break; + case Segment::ds: db(0x26); break; + case Segment::fs: db(0x64); break; + case Segment::gs: db(0x65); break; + default: + assert(0); + } + } + void mov(const Operand& op, const Segment& seg) + { + opModRM(Reg8(seg.getIdx()), op, op.isREG(16|i32e), op.isMEM(), 0x8C); + } + void mov(const Segment& seg, const Operand& op) + { + opModRM(Reg8(seg.getIdx()), op.isREG(16|i32e) ? static_cast(op.getReg().cvt32()) : op, op.isREG(16|i32e), op.isMEM(), 0x8E); + } +#endif + + enum { NONE = 256 }; + // constructor + CodeGenerator(size_t maxSize = DEFAULT_MAX_CODE_SIZE, void *userPtr = 0, Allocator *allocator = 0) + : CodeArray(maxSize, userPtr, allocator) + , mm0(0), mm1(1), mm2(2), mm3(3), mm4(4), mm5(5), mm6(6), mm7(7) + , xmm0(0), xmm1(1), xmm2(2), xmm3(3), xmm4(4), xmm5(5), xmm6(6), xmm7(7) + , ymm0(0), ymm1(1), ymm2(2), ymm3(3), ymm4(4), ymm5(5), ymm6(6), ymm7(7) + , zmm0(0), zmm1(1), zmm2(2), zmm3(3), zmm4(4), zmm5(5), zmm6(6), zmm7(7) + // for my convenience + , xm0(xmm0), xm1(xmm1), xm2(xmm2), xm3(xmm3), xm4(xmm4), xm5(xmm5), xm6(xmm6), xm7(xmm7) + , ym0(ymm0), ym1(ymm1), ym2(ymm2), ym3(ymm3), ym4(ymm4), ym5(ymm5), ym6(ymm6), ym7(ymm7) + , zm0(zmm0), zm1(zmm1), zm2(zmm2), zm3(zmm3), zm4(zmm4), zm5(zmm5), zm6(zmm6), zm7(zmm7) + + , eax(Operand::EAX), ecx(Operand::ECX), edx(Operand::EDX), ebx(Operand::EBX), esp(Operand::ESP), ebp(Operand::EBP), esi(Operand::ESI), edi(Operand::EDI) + , ax(Operand::AX), cx(Operand::CX), dx(Operand::DX), bx(Operand::BX), sp(Operand::SP), bp(Operand::BP), si(Operand::SI), di(Operand::DI) + , al(Operand::AL), cl(Operand::CL), dl(Operand::DL), bl(Operand::BL), ah(Operand::AH), ch(Operand::CH), dh(Operand::DH), bh(Operand::BH) + , ptr(0), byte(8), word(16), dword(32), qword(64), xword(128), yword(256), zword(512) + , ptr_b(0, true), xword_b(128, true), yword_b(256, true), zword_b(512, true) + , st0(0), st1(1), st2(2), st3(3), st4(4), st5(5), st6(6), st7(7) + , k0(0), k1(1), k2(2), k3(3), k4(4), k5(5), k6(6), k7(7) + , bnd0(0), bnd1(1), bnd2(2), bnd3(3) + , T_sae(EvexModifierRounding::T_SAE), T_rn_sae(EvexModifierRounding::T_RN_SAE), T_rd_sae(EvexModifierRounding::T_RD_SAE), T_ru_sae(EvexModifierRounding::T_RU_SAE), T_rz_sae(EvexModifierRounding::T_RZ_SAE) + , T_z() +#ifdef XBYAK64 + , rax(Operand::RAX), rcx(Operand::RCX), rdx(Operand::RDX), rbx(Operand::RBX), rsp(Operand::RSP), rbp(Operand::RBP), rsi(Operand::RSI), rdi(Operand::RDI), r8(Operand::R8), r9(Operand::R9), r10(Operand::R10), r11(Operand::R11), r12(Operand::R12), r13(Operand::R13), r14(Operand::R14), r15(Operand::R15) + , r8d(8), r9d(9), r10d(10), r11d(11), r12d(12), r13d(13), r14d(14), r15d(15) + , r8w(8), r9w(9), r10w(10), r11w(11), r12w(12), r13w(13), r14w(14), r15w(15) + , r8b(8), r9b(9), r10b(10), r11b(11), r12b(12), r13b(13), r14b(14), r15b(15) + , spl(Operand::SPL, true), bpl(Operand::BPL, true), sil(Operand::SIL, true), dil(Operand::DIL, true) + , xmm8(8), xmm9(9), xmm10(10), xmm11(11), xmm12(12), xmm13(13), xmm14(14), xmm15(15) + , xmm16(16), xmm17(17), xmm18(18), xmm19(19), xmm20(20), xmm21(21), xmm22(22), xmm23(23) + , xmm24(24), xmm25(25), xmm26(26), xmm27(27), xmm28(28), xmm29(29), xmm30(30), xmm31(31) + , ymm8(8), ymm9(9), ymm10(10), ymm11(11), ymm12(12), ymm13(13), ymm14(14), ymm15(15) + , ymm16(16), ymm17(17), ymm18(18), ymm19(19), ymm20(20), ymm21(21), ymm22(22), ymm23(23) + , ymm24(24), ymm25(25), ymm26(26), ymm27(27), ymm28(28), ymm29(29), ymm30(30), ymm31(31) + , zmm8(8), zmm9(9), zmm10(10), zmm11(11), zmm12(12), zmm13(13), zmm14(14), zmm15(15) + , zmm16(16), zmm17(17), zmm18(18), zmm19(19), zmm20(20), zmm21(21), zmm22(22), zmm23(23) + , zmm24(24), zmm25(25), zmm26(26), zmm27(27), zmm28(28), zmm29(29), zmm30(30), zmm31(31) + // for my convenience + , xm8(xmm8), xm9(xmm9), xm10(xmm10), xm11(xmm11), xm12(xmm12), xm13(xmm13), xm14(xmm14), xm15(xmm15) + , xm16(xmm16), xm17(xmm17), xm18(xmm18), xm19(xmm19), xm20(xmm20), xm21(xmm21), xm22(xmm22), xm23(xmm23) + , xm24(xmm24), xm25(xmm25), xm26(xmm26), xm27(xmm27), xm28(xmm28), xm29(xmm29), xm30(xmm30), xm31(xmm31) + , ym8(ymm8), ym9(ymm9), ym10(ymm10), ym11(ymm11), ym12(ymm12), ym13(ymm13), ym14(ymm14), ym15(ymm15) + , ym16(ymm16), ym17(ymm17), ym18(ymm18), ym19(ymm19), ym20(ymm20), ym21(ymm21), ym22(ymm22), ym23(ymm23) + , ym24(ymm24), ym25(ymm25), ym26(ymm26), ym27(ymm27), ym28(ymm28), ym29(ymm29), ym30(ymm30), ym31(ymm31) + , zm8(zmm8), zm9(zmm9), zm10(zmm10), zm11(zmm11), zm12(zmm12), zm13(zmm13), zm14(zmm14), zm15(zmm15) + , zm16(zmm16), zm17(zmm17), zm18(zmm18), zm19(zmm19), zm20(zmm20), zm21(zmm21), zm22(zmm22), zm23(zmm23) + , zm24(zmm24), zm25(zmm25), zm26(zmm26), zm27(zmm27), zm28(zmm28), zm29(zmm29), zm30(zmm30), zm31(zmm31) + , rip() +#endif +#ifndef XBYAK_DISABLE_SEGMENT + , es(Segment::es), cs(Segment::cs), ss(Segment::ss), ds(Segment::ds), fs(Segment::fs), gs(Segment::gs) +#endif + { + labelMgr_.set(this); + } + void reset() + { + resetSize(); + labelMgr_.reset(); + labelMgr_.set(this); + } + bool hasUndefinedLabel() const { return labelMgr_.hasUndefSlabel() || labelMgr_.hasUndefClabel(); } + /* + MUST call ready() to complete generating code if you use AutoGrow mode. + It is not necessary for the other mode if hasUndefinedLabel() is true. + */ + void ready(ProtectMode mode = PROTECT_RWE) + { + if (hasUndefinedLabel()) throw Error(ERR_LABEL_IS_NOT_FOUND); + if (isAutoGrow()) { + calcJmpAddress(); + if (useProtect()) setProtectMode(mode); + } + } + // set read/exec + void readyRE() { return ready(PROTECT_RE); } +#ifdef XBYAK_TEST + void dump(bool doClear = true) + { + CodeArray::dump(); + if (doClear) size_ = 0; + } +#endif + +#ifdef XBYAK_UNDEF_JNL + #undef jnl +#endif + + /* + use single byte nop if useMultiByteNop = false + */ + void nop(size_t size = 1, bool useMultiByteNop = true) + { + if (!useMultiByteNop) { + for (size_t i = 0; i < size; i++) { + db(0x90); + } + return; + } + /* + Intel Architectures Software Developer's Manual Volume 2 + recommended multi-byte sequence of NOP instruction + AMD and Intel seem to agree on the same sequences for up to 9 bytes: + https://support.amd.com/TechDocs/55723_SOG_Fam_17h_Processors_3.00.pdf + */ + static const uint8 nopTbl[9][9] = { + {0x90}, + {0x66, 0x90}, + {0x0F, 0x1F, 0x00}, + {0x0F, 0x1F, 0x40, 0x00}, + {0x0F, 0x1F, 0x44, 0x00, 0x00}, + {0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00}, + {0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00}, + {0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00}, + }; + const size_t n = sizeof(nopTbl) / sizeof(nopTbl[0]); + while (size > 0) { + size_t len = (std::min)(n, size); + const uint8 *seq = nopTbl[len - 1]; + db(seq, len); + size -= len; + } + } + +#ifndef XBYAK_DONT_READ_LIST +#include "xbyak_mnemonic.h" + /* + use single byte nop if useMultiByteNop = false + */ + void align(size_t x = 16, bool useMultiByteNop = true) + { + if (x == 1) return; + if (x < 1 || (x & (x - 1))) throw Error(ERR_BAD_ALIGN); + if (isAutoGrow() && x > inner::ALIGN_PAGE_SIZE) fprintf(stderr, "warning:autoGrow mode does not support %d align\n", (int)x); + size_t remain = size_t(getCurr()) % x; + if (remain) { + nop(x - remain, useMultiByteNop); + } + } +#endif +}; + +namespace util { +static const Mmx mm0(0), mm1(1), mm2(2), mm3(3), mm4(4), mm5(5), mm6(6), mm7(7); +static const Xmm xmm0(0), xmm1(1), xmm2(2), xmm3(3), xmm4(4), xmm5(5), xmm6(6), xmm7(7); +static const Ymm ymm0(0), ymm1(1), ymm2(2), ymm3(3), ymm4(4), ymm5(5), ymm6(6), ymm7(7); +static const Zmm zmm0(0), zmm1(1), zmm2(2), zmm3(3), zmm4(4), zmm5(5), zmm6(6), zmm7(7); +static const Reg32 eax(Operand::EAX), ecx(Operand::ECX), edx(Operand::EDX), ebx(Operand::EBX), esp(Operand::ESP), ebp(Operand::EBP), esi(Operand::ESI), edi(Operand::EDI); +static const Reg16 ax(Operand::AX), cx(Operand::CX), dx(Operand::DX), bx(Operand::BX), sp(Operand::SP), bp(Operand::BP), si(Operand::SI), di(Operand::DI); +static const Reg8 al(Operand::AL), cl(Operand::CL), dl(Operand::DL), bl(Operand::BL), ah(Operand::AH), ch(Operand::CH), dh(Operand::DH), bh(Operand::BH); +static const AddressFrame ptr(0), byte(8), word(16), dword(32), qword(64), xword(128), yword(256), zword(512); +static const AddressFrame ptr_b(0, true), xword_b(128, true), yword_b(256, true), zword_b(512, true); +static const Fpu st0(0), st1(1), st2(2), st3(3), st4(4), st5(5), st6(6), st7(7); +static const Opmask k0(0), k1(1), k2(2), k3(3), k4(4), k5(5), k6(6), k7(7); +static const BoundsReg bnd0(0), bnd1(1), bnd2(2), bnd3(3); +static const EvexModifierRounding T_sae(EvexModifierRounding::T_SAE), T_rn_sae(EvexModifierRounding::T_RN_SAE), T_rd_sae(EvexModifierRounding::T_RD_SAE), T_ru_sae(EvexModifierRounding::T_RU_SAE), T_rz_sae(EvexModifierRounding::T_RZ_SAE); +static const EvexModifierZero T_z; +#ifdef XBYAK64 +static const Reg64 rax(Operand::RAX), rcx(Operand::RCX), rdx(Operand::RDX), rbx(Operand::RBX), rsp(Operand::RSP), rbp(Operand::RBP), rsi(Operand::RSI), rdi(Operand::RDI), r8(Operand::R8), r9(Operand::R9), r10(Operand::R10), r11(Operand::R11), r12(Operand::R12), r13(Operand::R13), r14(Operand::R14), r15(Operand::R15); +static const Reg32 r8d(8), r9d(9), r10d(10), r11d(11), r12d(12), r13d(13), r14d(14), r15d(15); +static const Reg16 r8w(8), r9w(9), r10w(10), r11w(11), r12w(12), r13w(13), r14w(14), r15w(15); +static const Reg8 r8b(8), r9b(9), r10b(10), r11b(11), r12b(12), r13b(13), r14b(14), r15b(15), spl(Operand::SPL, true), bpl(Operand::BPL, true), sil(Operand::SIL, true), dil(Operand::DIL, true); +static const Xmm xmm8(8), xmm9(9), xmm10(10), xmm11(11), xmm12(12), xmm13(13), xmm14(14), xmm15(15); +static const Xmm xmm16(16), xmm17(17), xmm18(18), xmm19(19), xmm20(20), xmm21(21), xmm22(22), xmm23(23); +static const Xmm xmm24(24), xmm25(25), xmm26(26), xmm27(27), xmm28(28), xmm29(29), xmm30(30), xmm31(31); +static const Ymm ymm8(8), ymm9(9), ymm10(10), ymm11(11), ymm12(12), ymm13(13), ymm14(14), ymm15(15); +static const Ymm ymm16(16), ymm17(17), ymm18(18), ymm19(19), ymm20(20), ymm21(21), ymm22(22), ymm23(23); +static const Ymm ymm24(24), ymm25(25), ymm26(26), ymm27(27), ymm28(28), ymm29(29), ymm30(30), ymm31(31); +static const Zmm zmm8(8), zmm9(9), zmm10(10), zmm11(11), zmm12(12), zmm13(13), zmm14(14), zmm15(15); +static const Zmm zmm16(16), zmm17(17), zmm18(18), zmm19(19), zmm20(20), zmm21(21), zmm22(22), zmm23(23); +static const Zmm zmm24(24), zmm25(25), zmm26(26), zmm27(27), zmm28(28), zmm29(29), zmm30(30), zmm31(31); +static const RegRip rip; +#endif +#ifndef XBYAK_DISABLE_SEGMENT +static const Segment es(Segment::es), cs(Segment::cs), ss(Segment::ss), ds(Segment::ds), fs(Segment::fs), gs(Segment::gs); +#endif +} // util + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +} // end of namespace + +#endif // XBYAK_XBYAK_H_ diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_bin2hex.h b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_bin2hex.h new file mode 100644 index 000000000000..a22e5224c3cb --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_bin2hex.h @@ -0,0 +1,303 @@ +/******************************************************************************* +* Copyright 2016-2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/******************************************************************************* +* Copyright (c) 2007 MITSUNARI Shigeo +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* Neither the name of the copyright owner nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +* THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +enum { + B00000000= 0, + B00000001= 1, + B00000010= 2, + B00000011= 3, + B00000100= 4, + B00000101= 5, + B00000110= 6, + B00000111= 7, + B00001000= 8, + B00001001= 9, + B00001010= 10, + B00001011= 11, + B00001100= 12, + B00001101= 13, + B00001110= 14, + B00001111= 15, + B00010000= 16, + B00010001= 17, + B00010010= 18, + B00010011= 19, + B00010100= 20, + B00010101= 21, + B00010110= 22, + B00010111= 23, + B00011000= 24, + B00011001= 25, + B00011010= 26, + B00011011= 27, + B00011100= 28, + B00011101= 29, + B00011110= 30, + B00011111= 31, + B00100000= 32, + B00100001= 33, + B00100010= 34, + B00100011= 35, + B00100100= 36, + B00100101= 37, + B00100110= 38, + B00100111= 39, + B00101000= 40, + B00101001= 41, + B00101010= 42, + B00101011= 43, + B00101100= 44, + B00101101= 45, + B00101110= 46, + B00101111= 47, + B00110000= 48, + B00110001= 49, + B00110010= 50, + B00110011= 51, + B00110100= 52, + B00110101= 53, + B00110110= 54, + B00110111= 55, + B00111000= 56, + B00111001= 57, + B00111010= 58, + B00111011= 59, + B00111100= 60, + B00111101= 61, + B00111110= 62, + B00111111= 63, + B01000000= 64, + B01000001= 65, + B01000010= 66, + B01000011= 67, + B01000100= 68, + B01000101= 69, + B01000110= 70, + B01000111= 71, + B01001000= 72, + B01001001= 73, + B01001010= 74, + B01001011= 75, + B01001100= 76, + B01001101= 77, + B01001110= 78, + B01001111= 79, + B01010000= 80, + B01010001= 81, + B01010010= 82, + B01010011= 83, + B01010100= 84, + B01010101= 85, + B01010110= 86, + B01010111= 87, + B01011000= 88, + B01011001= 89, + B01011010= 90, + B01011011= 91, + B01011100= 92, + B01011101= 93, + B01011110= 94, + B01011111= 95, + B01100000= 96, + B01100001= 97, + B01100010= 98, + B01100011= 99, + B01100100= 100, + B01100101= 101, + B01100110= 102, + B01100111= 103, + B01101000= 104, + B01101001= 105, + B01101010= 106, + B01101011= 107, + B01101100= 108, + B01101101= 109, + B01101110= 110, + B01101111= 111, + B01110000= 112, + B01110001= 113, + B01110010= 114, + B01110011= 115, + B01110100= 116, + B01110101= 117, + B01110110= 118, + B01110111= 119, + B01111000= 120, + B01111001= 121, + B01111010= 122, + B01111011= 123, + B01111100= 124, + B01111101= 125, + B01111110= 126, + B01111111= 127, + B10000000= 128, + B10000001= 129, + B10000010= 130, + B10000011= 131, + B10000100= 132, + B10000101= 133, + B10000110= 134, + B10000111= 135, + B10001000= 136, + B10001001= 137, + B10001010= 138, + B10001011= 139, + B10001100= 140, + B10001101= 141, + B10001110= 142, + B10001111= 143, + B10010000= 144, + B10010001= 145, + B10010010= 146, + B10010011= 147, + B10010100= 148, + B10010101= 149, + B10010110= 150, + B10010111= 151, + B10011000= 152, + B10011001= 153, + B10011010= 154, + B10011011= 155, + B10011100= 156, + B10011101= 157, + B10011110= 158, + B10011111= 159, + B10100000= 160, + B10100001= 161, + B10100010= 162, + B10100011= 163, + B10100100= 164, + B10100101= 165, + B10100110= 166, + B10100111= 167, + B10101000= 168, + B10101001= 169, + B10101010= 170, + B10101011= 171, + B10101100= 172, + B10101101= 173, + B10101110= 174, + B10101111= 175, + B10110000= 176, + B10110001= 177, + B10110010= 178, + B10110011= 179, + B10110100= 180, + B10110101= 181, + B10110110= 182, + B10110111= 183, + B10111000= 184, + B10111001= 185, + B10111010= 186, + B10111011= 187, + B10111100= 188, + B10111101= 189, + B10111110= 190, + B10111111= 191, + B11000000= 192, + B11000001= 193, + B11000010= 194, + B11000011= 195, + B11000100= 196, + B11000101= 197, + B11000110= 198, + B11000111= 199, + B11001000= 200, + B11001001= 201, + B11001010= 202, + B11001011= 203, + B11001100= 204, + B11001101= 205, + B11001110= 206, + B11001111= 207, + B11010000= 208, + B11010001= 209, + B11010010= 210, + B11010011= 211, + B11010100= 212, + B11010101= 213, + B11010110= 214, + B11010111= 215, + B11011000= 216, + B11011001= 217, + B11011010= 218, + B11011011= 219, + B11011100= 220, + B11011101= 221, + B11011110= 222, + B11011111= 223, + B11100000= 224, + B11100001= 225, + B11100010= 226, + B11100011= 227, + B11100100= 228, + B11100101= 229, + B11100110= 230, + B11100111= 231, + B11101000= 232, + B11101001= 233, + B11101010= 234, + B11101011= 235, + B11101100= 236, + B11101101= 237, + B11101110= 238, + B11101111= 239, + B11110000= 240, + B11110001= 241, + B11110010= 242, + B11110011= 243, + B11110100= 244, + B11110101= 245, + B11110110= 246, + B11110111= 247, + B11111000= 248, + B11111001= 249, + B11111010= 250, + B11111011= 251, + B11111100= 252, + B11111101= 253, + B11111110= 254, + B11111111= 255 +}; diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_mnemonic.h b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_mnemonic.h new file mode 100644 index 000000000000..28d2d222f9f9 --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_mnemonic.h @@ -0,0 +1,2017 @@ +/******************************************************************************* +* Copyright 2016-2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/******************************************************************************* +* Copyright (c) 2007 MITSUNARI Shigeo +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* Neither the name of the copyright owner nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +* THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +const char *getVersionString() const { return "5.76"; } +void adc(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x10, 2); } +void adc(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x10); } +void adcx(const Reg32e& reg, const Operand& op) { opGen(reg, op, 0xF6, 0x66, isREG32_REG32orMEM, NONE, 0x38); } +void add(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x00, 0); } +void add(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x00); } +void addpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x58, 0x66, isXMM_XMMorMEM); } +void addps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x58, 0x100, isXMM_XMMorMEM); } +void addsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x58, 0xF2, isXMM_XMMorMEM); } +void addss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x58, 0xF3, isXMM_XMMorMEM); } +void addsubpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xD0, 0x66, isXMM_XMMorMEM); } +void addsubps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xD0, 0xF2, isXMM_XMMorMEM); } +void adox(const Reg32e& reg, const Operand& op) { opGen(reg, op, 0xF6, 0xF3, isREG32_REG32orMEM, NONE, 0x38); } +void aesdec(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xDE, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void aesdeclast(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xDF, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void aesenc(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xDC, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void aesenclast(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xDD, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void aesimc(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xDB, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void aeskeygenassist(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0xDF, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void and_(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x20, 4); } +void and_(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x20); } +void andn(const Reg32e& r1, const Reg32e& r2, const Operand& op) { opGpr(r1, r2, op, T_0F38, 0xf2, true); } +void andnpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x55, 0x66, isXMM_XMMorMEM); } +void andnps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x55, 0x100, isXMM_XMMorMEM); } +void andpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x54, 0x66, isXMM_XMMorMEM); } +void andps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x54, 0x100, isXMM_XMMorMEM); } +void bextr(const Reg32e& r1, const Operand& op, const Reg32e& r2) { opGpr(r1, op, r2, T_0F38, 0xf7, false); } +void blendpd(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x0D, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void blendps(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x0C, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void blendvpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x15, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void blendvps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x14, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void blsi(const Reg32e& r, const Operand& op) { opGpr(Reg32e(3, r.getBit()), op, r, T_0F38, 0xf3, false); } +void blsmsk(const Reg32e& r, const Operand& op) { opGpr(Reg32e(2, r.getBit()), op, r, T_0F38, 0xf3, false); } +void blsr(const Reg32e& r, const Operand& op) { opGpr(Reg32e(1, r.getBit()), op, r, T_0F38, 0xf3, false); } +void bnd() { db(0xF2); } +void bndcl(const BoundsReg& bnd, const Operand& op) { db(0xF3); opR_ModM(op, i32e, bnd.getIdx(), 0x0F, 0x1A, NONE, !op.isMEM()); } +void bndcn(const BoundsReg& bnd, const Operand& op) { db(0xF2); opR_ModM(op, i32e, bnd.getIdx(), 0x0F, 0x1B, NONE, !op.isMEM()); } +void bndcu(const BoundsReg& bnd, const Operand& op) { db(0xF2); opR_ModM(op, i32e, bnd.getIdx(), 0x0F, 0x1A, NONE, !op.isMEM()); } +void bndldx(const BoundsReg& bnd, const Address& addr) { opMIB(addr, bnd, 0x0F, 0x1A); } +void bndmk(const BoundsReg& bnd, const Address& addr) { db(0xF3); opModM(addr, bnd, 0x0F, 0x1B); } +void bndmov(const Address& addr, const BoundsReg& bnd) { db(0x66); opModM(addr, bnd, 0x0F, 0x1B); } +void bndmov(const BoundsReg& bnd, const Operand& op) { db(0x66); opModRM(bnd, op, op.isBNDREG(), op.isMEM(), 0x0F, 0x1A); } +void bndstx(const Address& addr, const BoundsReg& bnd) { opMIB(addr, bnd, 0x0F, 0x1B); } +void bsf(const Reg®, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0xBC); } +void bsr(const Reg®, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0xBD); } +void bswap(const Reg32e& reg) { opModR(Reg32(1), reg, 0x0F); } +void bt(const Operand& op, const Reg& reg) { opModRM(reg, op, op.isREG(16|32|64) && op.getBit() == reg.getBit(), op.isMEM(), 0x0f, 0xA3); } +void bt(const Operand& op, uint8 imm) { opR_ModM(op, 16|32|64, 4, 0x0f, 0xba, NONE, false, 1); db(imm); } +void btc(const Operand& op, const Reg& reg) { opModRM(reg, op, op.isREG(16|32|64) && op.getBit() == reg.getBit(), op.isMEM(), 0x0f, 0xBB); } +void btc(const Operand& op, uint8 imm) { opR_ModM(op, 16|32|64, 7, 0x0f, 0xba, NONE, false, 1); db(imm); } +void btr(const Operand& op, const Reg& reg) { opModRM(reg, op, op.isREG(16|32|64) && op.getBit() == reg.getBit(), op.isMEM(), 0x0f, 0xB3); } +void btr(const Operand& op, uint8 imm) { opR_ModM(op, 16|32|64, 6, 0x0f, 0xba, NONE, false, 1); db(imm); } +void bts(const Operand& op, const Reg& reg) { opModRM(reg, op, op.isREG(16|32|64) && op.getBit() == reg.getBit(), op.isMEM(), 0x0f, 0xAB); } +void bts(const Operand& op, uint8 imm) { opR_ModM(op, 16|32|64, 5, 0x0f, 0xba, NONE, false, 1); db(imm); } +void bzhi(const Reg32e& r1, const Operand& op, const Reg32e& r2) { opGpr(r1, op, r2, T_0F38, 0xf5, false); } +void cbw() { db(0x66); db(0x98); } +void cdq() { db(0x99); } +void clc() { db(0xF8); } +void cld() { db(0xFC); } +void clflush(const Address& addr) { opModM(addr, Reg32(7), 0x0F, 0xAE); } +void cli() { db(0xFA); } +void cmc() { db(0xF5); } +void cmova(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 7); }//-V524 +void cmovae(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 3); }//-V524 +void cmovb(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 2); }//-V524 +void cmovbe(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 6); }//-V524 +void cmovc(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 2); }//-V524 +void cmove(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 4); }//-V524 +void cmovg(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 15); }//-V524 +void cmovge(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 13); }//-V524 +void cmovl(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 12); }//-V524 +void cmovle(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 14); }//-V524 +void cmovna(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 6); }//-V524 +void cmovnae(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 2); }//-V524 +void cmovnb(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 3); }//-V524 +void cmovnbe(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 7); }//-V524 +void cmovnc(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 3); }//-V524 +void cmovne(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 5); }//-V524 +void cmovng(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 14); }//-V524 +void cmovnge(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 12); }//-V524 +void cmovnl(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 13); }//-V524 +void cmovnle(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 15); }//-V524 +void cmovno(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 1); }//-V524 +void cmovnp(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 11); }//-V524 +void cmovns(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 9); }//-V524 +void cmovnz(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 5); }//-V524 +void cmovo(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 0); }//-V524 +void cmovp(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 10); }//-V524 +void cmovpe(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 10); }//-V524 +void cmovpo(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 11); }//-V524 +void cmovs(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 8); }//-V524 +void cmovz(const Reg& reg, const Operand& op) { opModRM(reg, op, op.isREG(16 | i32e), op.isMEM(), 0x0F, 0x40 | 4); }//-V524 +void cmp(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x38, 7); } +void cmp(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x38); } +void cmpeqpd(const Xmm& x, const Operand& op) { cmppd(x, op, 0); } +void cmpeqps(const Xmm& x, const Operand& op) { cmpps(x, op, 0); } +void cmpeqsd(const Xmm& x, const Operand& op) { cmpsd(x, op, 0); } +void cmpeqss(const Xmm& x, const Operand& op) { cmpss(x, op, 0); } +void cmplepd(const Xmm& x, const Operand& op) { cmppd(x, op, 2); } +void cmpleps(const Xmm& x, const Operand& op) { cmpps(x, op, 2); } +void cmplesd(const Xmm& x, const Operand& op) { cmpsd(x, op, 2); } +void cmpless(const Xmm& x, const Operand& op) { cmpss(x, op, 2); } +void cmpltpd(const Xmm& x, const Operand& op) { cmppd(x, op, 1); } +void cmpltps(const Xmm& x, const Operand& op) { cmpps(x, op, 1); } +void cmpltsd(const Xmm& x, const Operand& op) { cmpsd(x, op, 1); } +void cmpltss(const Xmm& x, const Operand& op) { cmpss(x, op, 1); } +void cmpneqpd(const Xmm& x, const Operand& op) { cmppd(x, op, 4); } +void cmpneqps(const Xmm& x, const Operand& op) { cmpps(x, op, 4); } +void cmpneqsd(const Xmm& x, const Operand& op) { cmpsd(x, op, 4); } +void cmpneqss(const Xmm& x, const Operand& op) { cmpss(x, op, 4); } +void cmpnlepd(const Xmm& x, const Operand& op) { cmppd(x, op, 6); } +void cmpnleps(const Xmm& x, const Operand& op) { cmpps(x, op, 6); } +void cmpnlesd(const Xmm& x, const Operand& op) { cmpsd(x, op, 6); } +void cmpnless(const Xmm& x, const Operand& op) { cmpss(x, op, 6); } +void cmpnltpd(const Xmm& x, const Operand& op) { cmppd(x, op, 5); } +void cmpnltps(const Xmm& x, const Operand& op) { cmpps(x, op, 5); } +void cmpnltsd(const Xmm& x, const Operand& op) { cmpsd(x, op, 5); } +void cmpnltss(const Xmm& x, const Operand& op) { cmpss(x, op, 5); } +void cmpordpd(const Xmm& x, const Operand& op) { cmppd(x, op, 7); } +void cmpordps(const Xmm& x, const Operand& op) { cmpps(x, op, 7); } +void cmpordsd(const Xmm& x, const Operand& op) { cmpsd(x, op, 7); } +void cmpordss(const Xmm& x, const Operand& op) { cmpss(x, op, 7); } +void cmppd(const Xmm& xmm, const Operand& op, uint8 imm8) { opGen(xmm, op, 0xC2, 0x66, isXMM_XMMorMEM, imm8); } +void cmpps(const Xmm& xmm, const Operand& op, uint8 imm8) { opGen(xmm, op, 0xC2, 0x100, isXMM_XMMorMEM, imm8); } +void cmpsb() { db(0xA6); } +void cmpsd() { db(0xA7); } +void cmpsd(const Xmm& xmm, const Operand& op, uint8 imm8) { opGen(xmm, op, 0xC2, 0xF2, isXMM_XMMorMEM, imm8); } +void cmpss(const Xmm& xmm, const Operand& op, uint8 imm8) { opGen(xmm, op, 0xC2, 0xF3, isXMM_XMMorMEM, imm8); } +void cmpsw() { db(0x66); db(0xA7); } +void cmpunordpd(const Xmm& x, const Operand& op) { cmppd(x, op, 3); } +void cmpunordps(const Xmm& x, const Operand& op) { cmpps(x, op, 3); } +void cmpunordsd(const Xmm& x, const Operand& op) { cmpsd(x, op, 3); } +void cmpunordss(const Xmm& x, const Operand& op) { cmpss(x, op, 3); } +void cmpxchg(const Operand& op, const Reg& reg) { opModRM(reg, op, (op.isREG() && reg.isREG() && op.getBit() == reg.getBit()), op.isMEM(), 0x0F, 0xB0 | (reg.isBit(8) ? 0 : 1)); } +void cmpxchg8b(const Address& addr) { opModM(addr, Reg32(1), 0x0F, 0xC7); } +void comisd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x2F, 0x66, isXMM_XMMorMEM); } +void comiss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x2F, 0x100, isXMM_XMMorMEM); } +void cpuid() { db(0x0F); db(0xA2); } +void crc32(const Reg32e& reg, const Operand& op) { if (reg.isBit(32) && op.isBit(16)) db(0x66); db(0xF2); opModRM(reg, op, op.isREG(), op.isMEM(), 0x0F, 0x38, 0xF0 | (op.isBit(8) ? 0 : 1)); } +void cvtdq2pd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xE6, 0xF3, isXMM_XMMorMEM); } +void cvtdq2ps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5B, 0x100, isXMM_XMMorMEM); } +void cvtpd2dq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xE6, 0xF2, isXMM_XMMorMEM); } +void cvtpd2pi(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2D, 0x66, isMMX_XMMorMEM); } +void cvtpd2ps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5A, 0x66, isXMM_XMMorMEM); } +void cvtpi2pd(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2A, 0x66, isXMM_MMXorMEM); } +void cvtpi2ps(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2A, 0x100, isXMM_MMXorMEM); } +void cvtps2dq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5B, 0x66, isXMM_XMMorMEM); } +void cvtps2pd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5A, 0x100, isXMM_XMMorMEM); } +void cvtps2pi(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2D, 0x100, isMMX_XMMorMEM); } +void cvtsd2si(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2D, 0xF2, isREG32_XMMorMEM); } +void cvtsd2ss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5A, 0xF2, isXMM_XMMorMEM); } +void cvtsi2sd(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2A, 0xF2, isXMM_REG32orMEM); } +void cvtsi2ss(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2A, 0xF3, isXMM_REG32orMEM); } +void cvtss2sd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5A, 0xF3, isXMM_XMMorMEM); } +void cvtss2si(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2D, 0xF3, isREG32_XMMorMEM); } +void cvttpd2dq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xE6, 0x66, isXMM_XMMorMEM); } +void cvttpd2pi(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2C, 0x66, isMMX_XMMorMEM); } +void cvttps2dq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5B, 0xF3, isXMM_XMMorMEM); } +void cvttps2pi(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2C, 0x100, isMMX_XMMorMEM); } +void cvttsd2si(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2C, 0xF2, isREG32_XMMorMEM); } +void cvttss2si(const Operand& reg, const Operand& op) { opGen(reg, op, 0x2C, 0xF3, isREG32_XMMorMEM); } +void cwd() { db(0x66); db(0x99); } +void cwde() { db(0x98); } +void dec(const Operand& op) { opIncDec(op, 0x48, 1); } +void div(const Operand& op) { opR_ModM(op, 0, 6, 0xF6); } +void divpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5E, 0x66, isXMM_XMMorMEM); } +void divps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5E, 0x100, isXMM_XMMorMEM); } +void divsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5E, 0xF2, isXMM_XMMorMEM); } +void divss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5E, 0xF3, isXMM_XMMorMEM); } +void dppd(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x41, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void dpps(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x40, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void emms() { db(0x0F); db(0x77); } +void extractps(const Operand& op, const Xmm& xmm, uint8 imm) { opExt(op, xmm, 0x17, imm); } +void f2xm1() { db(0xD9); db(0xF0); } +void fabs() { db(0xD9); db(0xE1); } +void fadd(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 0, 0); } +void fadd(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xD8C0, 0xDCC0); } +void fadd(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xD8C0, 0xDCC0); } +void faddp() { db(0xDE); db(0xC1); } +void faddp(const Fpu& reg1) { opFpuFpu(reg1, st0, 0x0000, 0xDEC0); } +void faddp(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0x0000, 0xDEC0); } +void fchs() { db(0xD9); db(0xE0); } +void fcmovb(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDAC0, 0x00C0); } +void fcmovb(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDAC0, 0x00C0); } +void fcmovbe(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDAD0, 0x00D0); } +void fcmovbe(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDAD0, 0x00D0); } +void fcmove(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDAC8, 0x00C8); } +void fcmove(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDAC8, 0x00C8); } +void fcmovnb(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDBC0, 0x00C0); } +void fcmovnb(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDBC0, 0x00C0); } +void fcmovnbe(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDBD0, 0x00D0); } +void fcmovnbe(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDBD0, 0x00D0); } +void fcmovne(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDBC8, 0x00C8); } +void fcmovne(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDBC8, 0x00C8); } +void fcmovnu(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDBD8, 0x00D8); } +void fcmovnu(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDBD8, 0x00D8); } +void fcmovu(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDAD8, 0x00D8); } +void fcmovu(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDAD8, 0x00D8); } +void fcom() { db(0xD8); db(0xD1); } +void fcom(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 2, 0); } +void fcom(const Fpu& reg) { opFpu(reg, 0xD8, 0xD0); } +void fcomi(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDBF0, 0x00F0); } +void fcomi(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDBF0, 0x00F0); } +void fcomip(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDFF0, 0x00F0); } +void fcomip(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDFF0, 0x00F0); } +void fcomp() { db(0xD8); db(0xD9); } +void fcomp(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 3, 0); } +void fcomp(const Fpu& reg) { opFpu(reg, 0xD8, 0xD8); } +void fcompp() { db(0xDE); db(0xD9); } +void fcos() { db(0xD9); db(0xFF); } +void fdecstp() { db(0xD9); db(0xF6); } +void fdiv(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 6, 0); } +void fdiv(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xD8F0, 0xDCF8); } +void fdiv(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xD8F0, 0xDCF8); } +void fdivp() { db(0xDE); db(0xF9); } +void fdivp(const Fpu& reg1) { opFpuFpu(reg1, st0, 0x0000, 0xDEF8); } +void fdivp(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0x0000, 0xDEF8); } +void fdivr(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 7, 0); } +void fdivr(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xD8F8, 0xDCF0); } +void fdivr(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xD8F8, 0xDCF0); } +void fdivrp() { db(0xDE); db(0xF1); } +void fdivrp(const Fpu& reg1) { opFpuFpu(reg1, st0, 0x0000, 0xDEF0); } +void fdivrp(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0x0000, 0xDEF0); } +void ffree(const Fpu& reg) { opFpu(reg, 0xDD, 0xC0); } +void fiadd(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 0, 0); } +void ficom(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 2, 0); } +void ficomp(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 3, 0); } +void fidiv(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 6, 0); } +void fidivr(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 7, 0); } +void fild(const Address& addr) { opFpuMem(addr, 0xDF, 0xDB, 0xDF, 0, 5); } +void fimul(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 1, 0); } +void fincstp() { db(0xD9); db(0xF7); } +void finit() { db(0x9B); db(0xDB); db(0xE3); } +void fist(const Address& addr) { opFpuMem(addr, 0xDF, 0xDB, 0x00, 2, 0); } +void fistp(const Address& addr) { opFpuMem(addr, 0xDF, 0xDB, 0xDF, 3, 7); } +void fisttp(const Address& addr) { opFpuMem(addr, 0xDF, 0xDB, 0xDD, 1, 0); } +void fisub(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 4, 0); } +void fisubr(const Address& addr) { opFpuMem(addr, 0xDE, 0xDA, 0x00, 5, 0); } +void fld(const Address& addr) { opFpuMem(addr, 0x00, 0xD9, 0xDD, 0, 0); } +void fld(const Fpu& reg) { opFpu(reg, 0xD9, 0xC0); } +void fld1() { db(0xD9); db(0xE8); } +void fldcw(const Address& addr) { opModM(addr, Reg32(5), 0xD9, 0x100); } +void fldl2e() { db(0xD9); db(0xEA); } +void fldl2t() { db(0xD9); db(0xE9); } +void fldlg2() { db(0xD9); db(0xEC); } +void fldln2() { db(0xD9); db(0xED); } +void fldpi() { db(0xD9); db(0xEB); } +void fldz() { db(0xD9); db(0xEE); } +void fmul(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 1, 0); } +void fmul(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xD8C8, 0xDCC8); } +void fmul(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xD8C8, 0xDCC8); } +void fmulp() { db(0xDE); db(0xC9); } +void fmulp(const Fpu& reg1) { opFpuFpu(reg1, st0, 0x0000, 0xDEC8); } +void fmulp(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0x0000, 0xDEC8); } +void fninit() { db(0xDB); db(0xE3); } +void fnop() { db(0xD9); db(0xD0); } +void fpatan() { db(0xD9); db(0xF3); } +void fprem() { db(0xD9); db(0xF8); } +void fprem1() { db(0xD9); db(0xF5); } +void fptan() { db(0xD9); db(0xF2); } +void frndint() { db(0xD9); db(0xFC); } +void fscale() { db(0xD9); db(0xFD); } +void fsin() { db(0xD9); db(0xFE); } +void fsincos() { db(0xD9); db(0xFB); } +void fsqrt() { db(0xD9); db(0xFA); } +void fst(const Address& addr) { opFpuMem(addr, 0x00, 0xD9, 0xDD, 2, 0); } +void fst(const Fpu& reg) { opFpu(reg, 0xDD, 0xD0); } +void fstcw(const Address& addr) { db(0x9B); opModM(addr, Reg32(7), 0xD9, NONE); } +void fstp(const Address& addr) { opFpuMem(addr, 0x00, 0xD9, 0xDD, 3, 0); } +void fstp(const Fpu& reg) { opFpu(reg, 0xDD, 0xD8); } +void fsub(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 4, 0); } +void fsub(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xD8E0, 0xDCE8); } +void fsub(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xD8E0, 0xDCE8); } +void fsubp() { db(0xDE); db(0xE9); } +void fsubp(const Fpu& reg1) { opFpuFpu(reg1, st0, 0x0000, 0xDEE8); } +void fsubp(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0x0000, 0xDEE8); } +void fsubr(const Address& addr) { opFpuMem(addr, 0x00, 0xD8, 0xDC, 5, 0); } +void fsubr(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xD8E8, 0xDCE0); } +void fsubr(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xD8E8, 0xDCE0); } +void fsubrp() { db(0xDE); db(0xE1); } +void fsubrp(const Fpu& reg1) { opFpuFpu(reg1, st0, 0x0000, 0xDEE0); } +void fsubrp(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0x0000, 0xDEE0); } +void ftst() { db(0xD9); db(0xE4); } +void fucom() { db(0xDD); db(0xE1); } +void fucom(const Fpu& reg) { opFpu(reg, 0xDD, 0xE0); } +void fucomi(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDBE8, 0x00E8); } +void fucomi(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDBE8, 0x00E8); } +void fucomip(const Fpu& reg1) { opFpuFpu(st0, reg1, 0xDFE8, 0x00E8); } +void fucomip(const Fpu& reg1, const Fpu& reg2) { opFpuFpu(reg1, reg2, 0xDFE8, 0x00E8); } +void fucomp() { db(0xDD); db(0xE9); } +void fucomp(const Fpu& reg) { opFpu(reg, 0xDD, 0xE8); } +void fucompp() { db(0xDA); db(0xE9); } +void fwait() { db(0x9B); } +void fxam() { db(0xD9); db(0xE5); } +void fxch() { db(0xD9); db(0xC9); } +void fxch(const Fpu& reg) { opFpu(reg, 0xD9, 0xC8); } +void fxtract() { db(0xD9); db(0xF4); } +void fyl2x() { db(0xD9); db(0xF1); } +void fyl2xp1() { db(0xD9); db(0xF9); } +void gf2p8affineinvqb(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0xCF, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void gf2p8affineqb(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0xCE, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void gf2p8mulb(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xCF, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void haddpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x7C, 0x66, isXMM_XMMorMEM); } +void haddps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x7C, 0xF2, isXMM_XMMorMEM); } +void hsubpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x7D, 0x66, isXMM_XMMorMEM); } +void hsubps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x7D, 0xF2, isXMM_XMMorMEM); } +void idiv(const Operand& op) { opR_ModM(op, 0, 7, 0xF6); } +void imul(const Operand& op) { opR_ModM(op, 0, 5, 0xF6); } +void inc(const Operand& op) { opIncDec(op, 0x40, 0); } +void insertps(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x21, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void ja(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x77, 0x87, 0x0F); }//-V524 +void ja(const char *label, LabelType type = T_AUTO) { ja(std::string(label), type); }//-V524 +void ja(const void *addr) { opJmpAbs(addr, T_NEAR, 0x77, 0x87, 0x0F); }//-V524 +void ja(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x77, 0x87, 0x0F); }//-V524 +void jae(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x73, 0x83, 0x0F); }//-V524 +void jae(const char *label, LabelType type = T_AUTO) { jae(std::string(label), type); }//-V524 +void jae(const void *addr) { opJmpAbs(addr, T_NEAR, 0x73, 0x83, 0x0F); }//-V524 +void jae(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x73, 0x83, 0x0F); }//-V524 +void jb(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x72, 0x82, 0x0F); }//-V524 +void jb(const char *label, LabelType type = T_AUTO) { jb(std::string(label), type); }//-V524 +void jb(const void *addr) { opJmpAbs(addr, T_NEAR, 0x72, 0x82, 0x0F); }//-V524 +void jb(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x72, 0x82, 0x0F); }//-V524 +void jbe(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x76, 0x86, 0x0F); }//-V524 +void jbe(const char *label, LabelType type = T_AUTO) { jbe(std::string(label), type); }//-V524 +void jbe(const void *addr) { opJmpAbs(addr, T_NEAR, 0x76, 0x86, 0x0F); }//-V524 +void jbe(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x76, 0x86, 0x0F); }//-V524 +void jc(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x72, 0x82, 0x0F); }//-V524 +void jc(const char *label, LabelType type = T_AUTO) { jc(std::string(label), type); }//-V524 +void jc(const void *addr) { opJmpAbs(addr, T_NEAR, 0x72, 0x82, 0x0F); }//-V524 +void jc(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x72, 0x82, 0x0F); }//-V524 +void je(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x74, 0x84, 0x0F); }//-V524 +void je(const char *label, LabelType type = T_AUTO) { je(std::string(label), type); }//-V524 +void je(const void *addr) { opJmpAbs(addr, T_NEAR, 0x74, 0x84, 0x0F); }//-V524 +void je(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x74, 0x84, 0x0F); }//-V524 +void jg(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7F, 0x8F, 0x0F); }//-V524 +void jg(const char *label, LabelType type = T_AUTO) { jg(std::string(label), type); }//-V524 +void jg(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7F, 0x8F, 0x0F); }//-V524 +void jg(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7F, 0x8F, 0x0F); }//-V524 +void jge(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7D, 0x8D, 0x0F); }//-V524 +void jge(const char *label, LabelType type = T_AUTO) { jge(std::string(label), type); }//-V524 +void jge(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7D, 0x8D, 0x0F); }//-V524 +void jge(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7D, 0x8D, 0x0F); }//-V524 +void jl(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7C, 0x8C, 0x0F); }//-V524 +void jl(const char *label, LabelType type = T_AUTO) { jl(std::string(label), type); }//-V524 +void jl(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7C, 0x8C, 0x0F); }//-V524 +void jl(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7C, 0x8C, 0x0F); }//-V524 +void jle(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7E, 0x8E, 0x0F); }//-V524 +void jle(const char *label, LabelType type = T_AUTO) { jle(std::string(label), type); }//-V524 +void jle(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7E, 0x8E, 0x0F); }//-V524 +void jle(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7E, 0x8E, 0x0F); }//-V524 +void jna(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x76, 0x86, 0x0F); }//-V524 +void jna(const char *label, LabelType type = T_AUTO) { jna(std::string(label), type); }//-V524 +void jna(const void *addr) { opJmpAbs(addr, T_NEAR, 0x76, 0x86, 0x0F); }//-V524 +void jna(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x76, 0x86, 0x0F); }//-V524 +void jnae(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x72, 0x82, 0x0F); }//-V524 +void jnae(const char *label, LabelType type = T_AUTO) { jnae(std::string(label), type); }//-V524 +void jnae(const void *addr) { opJmpAbs(addr, T_NEAR, 0x72, 0x82, 0x0F); }//-V524 +void jnae(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x72, 0x82, 0x0F); }//-V524 +void jnb(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x73, 0x83, 0x0F); }//-V524 +void jnb(const char *label, LabelType type = T_AUTO) { jnb(std::string(label), type); }//-V524 +void jnb(const void *addr) { opJmpAbs(addr, T_NEAR, 0x73, 0x83, 0x0F); }//-V524 +void jnb(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x73, 0x83, 0x0F); }//-V524 +void jnbe(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x77, 0x87, 0x0F); }//-V524 +void jnbe(const char *label, LabelType type = T_AUTO) { jnbe(std::string(label), type); }//-V524 +void jnbe(const void *addr) { opJmpAbs(addr, T_NEAR, 0x77, 0x87, 0x0F); }//-V524 +void jnbe(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x77, 0x87, 0x0F); }//-V524 +void jnc(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x73, 0x83, 0x0F); }//-V524 +void jnc(const char *label, LabelType type = T_AUTO) { jnc(std::string(label), type); }//-V524 +void jnc(const void *addr) { opJmpAbs(addr, T_NEAR, 0x73, 0x83, 0x0F); }//-V524 +void jnc(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x73, 0x83, 0x0F); }//-V524 +void jne(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x75, 0x85, 0x0F); }//-V524 +void jne(const char *label, LabelType type = T_AUTO) { jne(std::string(label), type); }//-V524 +void jne(const void *addr) { opJmpAbs(addr, T_NEAR, 0x75, 0x85, 0x0F); }//-V524 +void jne(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x75, 0x85, 0x0F); }//-V524 +void jng(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7E, 0x8E, 0x0F); }//-V524 +void jng(const char *label, LabelType type = T_AUTO) { jng(std::string(label), type); }//-V524 +void jng(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7E, 0x8E, 0x0F); }//-V524 +void jng(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7E, 0x8E, 0x0F); }//-V524 +void jnge(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7C, 0x8C, 0x0F); }//-V524 +void jnge(const char *label, LabelType type = T_AUTO) { jnge(std::string(label), type); }//-V524 +void jnge(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7C, 0x8C, 0x0F); }//-V524 +void jnge(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7C, 0x8C, 0x0F); }//-V524 +void jnl(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7D, 0x8D, 0x0F); }//-V524 +void jnl(const char *label, LabelType type = T_AUTO) { jnl(std::string(label), type); }//-V524 +void jnl(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7D, 0x8D, 0x0F); }//-V524 +void jnl(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7D, 0x8D, 0x0F); }//-V524 +void jnle(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7F, 0x8F, 0x0F); }//-V524 +void jnle(const char *label, LabelType type = T_AUTO) { jnle(std::string(label), type); }//-V524 +void jnle(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7F, 0x8F, 0x0F); }//-V524 +void jnle(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7F, 0x8F, 0x0F); }//-V524 +void jno(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x71, 0x81, 0x0F); }//-V524 +void jno(const char *label, LabelType type = T_AUTO) { jno(std::string(label), type); }//-V524 +void jno(const void *addr) { opJmpAbs(addr, T_NEAR, 0x71, 0x81, 0x0F); }//-V524 +void jno(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x71, 0x81, 0x0F); }//-V524 +void jnp(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7B, 0x8B, 0x0F); }//-V524 +void jnp(const char *label, LabelType type = T_AUTO) { jnp(std::string(label), type); }//-V524 +void jnp(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7B, 0x8B, 0x0F); }//-V524 +void jnp(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7B, 0x8B, 0x0F); }//-V524 +void jns(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x79, 0x89, 0x0F); }//-V524 +void jns(const char *label, LabelType type = T_AUTO) { jns(std::string(label), type); }//-V524 +void jns(const void *addr) { opJmpAbs(addr, T_NEAR, 0x79, 0x89, 0x0F); }//-V524 +void jns(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x79, 0x89, 0x0F); }//-V524 +void jnz(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x75, 0x85, 0x0F); }//-V524 +void jnz(const char *label, LabelType type = T_AUTO) { jnz(std::string(label), type); }//-V524 +void jnz(const void *addr) { opJmpAbs(addr, T_NEAR, 0x75, 0x85, 0x0F); }//-V524 +void jnz(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x75, 0x85, 0x0F); }//-V524 +void jo(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x70, 0x80, 0x0F); }//-V524 +void jo(const char *label, LabelType type = T_AUTO) { jo(std::string(label), type); }//-V524 +void jo(const void *addr) { opJmpAbs(addr, T_NEAR, 0x70, 0x80, 0x0F); }//-V524 +void jo(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x70, 0x80, 0x0F); }//-V524 +void jp(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7A, 0x8A, 0x0F); }//-V524 +void jp(const char *label, LabelType type = T_AUTO) { jp(std::string(label), type); }//-V524 +void jp(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7A, 0x8A, 0x0F); }//-V524 +void jp(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7A, 0x8A, 0x0F); }//-V524 +void jpe(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7A, 0x8A, 0x0F); }//-V524 +void jpe(const char *label, LabelType type = T_AUTO) { jpe(std::string(label), type); }//-V524 +void jpe(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7A, 0x8A, 0x0F); }//-V524 +void jpe(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7A, 0x8A, 0x0F); }//-V524 +void jpo(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x7B, 0x8B, 0x0F); }//-V524 +void jpo(const char *label, LabelType type = T_AUTO) { jpo(std::string(label), type); }//-V524 +void jpo(const void *addr) { opJmpAbs(addr, T_NEAR, 0x7B, 0x8B, 0x0F); }//-V524 +void jpo(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x7B, 0x8B, 0x0F); }//-V524 +void js(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x78, 0x88, 0x0F); }//-V524 +void js(const char *label, LabelType type = T_AUTO) { js(std::string(label), type); }//-V524 +void js(const void *addr) { opJmpAbs(addr, T_NEAR, 0x78, 0x88, 0x0F); }//-V524 +void js(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x78, 0x88, 0x0F); }//-V524 +void jz(const Label& label, LabelType type = T_AUTO) { opJmp(label, type, 0x74, 0x84, 0x0F); }//-V524 +void jz(const char *label, LabelType type = T_AUTO) { jz(std::string(label), type); }//-V524 +void jz(const void *addr) { opJmpAbs(addr, T_NEAR, 0x74, 0x84, 0x0F); }//-V524 +void jz(std::string label, LabelType type = T_AUTO) { opJmp(label, type, 0x74, 0x84, 0x0F); }//-V524 +void lahf() { db(0x9F); } +void lddqu(const Xmm& xmm, const Address& addr) { db(0xF2); opModM(addr, xmm, 0x0F, 0xF0); } +void ldmxcsr(const Address& addr) { opModM(addr, Reg32(2), 0x0F, 0xAE); } +void lea(const Reg& reg, const Address& addr) { if (!reg.isBit(16 | i32e)) throw Error(ERR_BAD_SIZE_OF_REGISTER); opModM(addr, reg, 0x8D); } +void lfence() { db(0x0F); db(0xAE); db(0xE8); } +void lock() { db(0xF0); } +void lzcnt(const Reg®, const Operand& op) { opSp1(reg, op, 0xF3, 0x0F, 0xBD); } +void maskmovdqu(const Xmm& reg1, const Xmm& reg2) { db(0x66); opModR(reg1, reg2, 0x0F, 0xF7); } +void maskmovq(const Mmx& reg1, const Mmx& reg2) { if (!reg1.isMMX() || !reg2.isMMX()) throw Error(ERR_BAD_COMBINATION); opModR(reg1, reg2, 0x0F, 0xF7); } +void maxpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5F, 0x66, isXMM_XMMorMEM); } +void maxps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5F, 0x100, isXMM_XMMorMEM); } +void maxsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5F, 0xF2, isXMM_XMMorMEM); } +void maxss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5F, 0xF3, isXMM_XMMorMEM); } +void mfence() { db(0x0F); db(0xAE); db(0xF0); } +void minpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5D, 0x66, isXMM_XMMorMEM); } +void minps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5D, 0x100, isXMM_XMMorMEM); } +void minsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5D, 0xF2, isXMM_XMMorMEM); } +void minss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5D, 0xF3, isXMM_XMMorMEM); } +void monitor() { db(0x0F); db(0x01); db(0xC8); } +void movapd(const Address& addr, const Xmm& xmm) { db(0x66); opModM(addr, xmm, 0x0F, 0x29); } +void movapd(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x28, 0x66); } +void movaps(const Address& addr, const Xmm& xmm) { opModM(addr, xmm, 0x0F, 0x29); } +void movaps(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x28, 0x100); } +void movbe(const Address& addr, const Reg& reg) { opModM(addr, reg, 0x0F, 0x38, 0xF1); } +void movbe(const Reg& reg, const Address& addr) { opModM(addr, reg, 0x0F, 0x38, 0xF0); } +void movd(const Address& addr, const Mmx& mmx) { if (mmx.isXMM()) db(0x66); opModM(addr, mmx, 0x0F, 0x7E); } +void movd(const Mmx& mmx, const Address& addr) { if (mmx.isXMM()) db(0x66); opModM(addr, mmx, 0x0F, 0x6E); } +void movd(const Mmx& mmx, const Reg32& reg) { if (mmx.isXMM()) db(0x66); opModR(mmx, reg, 0x0F, 0x6E); } +void movd(const Reg32& reg, const Mmx& mmx) { if (mmx.isXMM()) db(0x66); opModR(mmx, reg, 0x0F, 0x7E); } +void movddup(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x12, 0xF2, isXMM_XMMorMEM, NONE, NONE); } +void movdq2q(const Mmx& mmx, const Xmm& xmm) { db(0xF2); opModR(mmx, xmm, 0x0F, 0xD6); } +void movdqa(const Address& addr, const Xmm& xmm) { db(0x66); opModM(addr, xmm, 0x0F, 0x7F); } +void movdqa(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x6F, 0x66); } +void movdqu(const Address& addr, const Xmm& xmm) { db(0xF3); opModM(addr, xmm, 0x0F, 0x7F); } +void movdqu(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x6F, 0xF3); } +void movhlps(const Xmm& reg1, const Xmm& reg2) { opModR(reg1, reg2, 0x0F, 0x12); } +void movhpd(const Operand& op1, const Operand& op2) { opMovXMM(op1, op2, 0x16, 0x66); } +void movhps(const Operand& op1, const Operand& op2) { opMovXMM(op1, op2, 0x16, 0x100); } +void movlhps(const Xmm& reg1, const Xmm& reg2) { opModR(reg1, reg2, 0x0F, 0x16); } +void movlpd(const Operand& op1, const Operand& op2) { opMovXMM(op1, op2, 0x12, 0x66); } +void movlps(const Operand& op1, const Operand& op2) { opMovXMM(op1, op2, 0x12, 0x100); } +void movmskpd(const Reg32e& reg, const Xmm& xmm) { db(0x66); movmskps(reg, xmm); } +void movmskps(const Reg32e& reg, const Xmm& xmm) { opModR(reg, xmm, 0x0F, 0x50); } +void movntdq(const Address& addr, const Xmm& reg) { opModM(addr, Reg16(reg.getIdx()), 0x0F, 0xE7); } +void movntdqa(const Xmm& xmm, const Address& addr) { db(0x66); opModM(addr, xmm, 0x0F, 0x38, 0x2A); } +void movnti(const Address& addr, const Reg32e& reg) { opModM(addr, reg, 0x0F, 0xC3); } +void movntpd(const Address& addr, const Xmm& reg) { opModM(addr, Reg16(reg.getIdx()), 0x0F, 0x2B); } +void movntps(const Address& addr, const Xmm& xmm) { opModM(addr, Mmx(xmm.getIdx()), 0x0F, 0x2B); } +void movntq(const Address& addr, const Mmx& mmx) { if (!mmx.isMMX()) throw Error(ERR_BAD_COMBINATION); opModM(addr, mmx, 0x0F, 0xE7); } +void movq(const Address& addr, const Mmx& mmx) { if (mmx.isXMM()) db(0x66); opModM(addr, mmx, 0x0F, mmx.isXMM() ? 0xD6 : 0x7F); } +void movq(const Mmx& mmx, const Operand& op) { if (mmx.isXMM()) db(0xF3); opModRM(mmx, op, (mmx.getKind() == op.getKind()), op.isMEM(), 0x0F, mmx.isXMM() ? 0x7E : 0x6F); } +void movq2dq(const Xmm& xmm, const Mmx& mmx) { db(0xF3); opModR(xmm, mmx, 0x0F, 0xD6); } +void movsb() { db(0xA4); } +void movsd() { db(0xA5); } +void movsd(const Address& addr, const Xmm& xmm) { db(0xF2); opModM(addr, xmm, 0x0F, 0x11); } +void movsd(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x10, 0xF2); } +void movshdup(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x16, 0xF3, isXMM_XMMorMEM, NONE, NONE); } +void movsldup(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x12, 0xF3, isXMM_XMMorMEM, NONE, NONE); } +void movss(const Address& addr, const Xmm& xmm) { db(0xF3); opModM(addr, xmm, 0x0F, 0x11); } +void movss(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x10, 0xF3); } +void movsw() { db(0x66); db(0xA5); } +void movsx(const Reg& reg, const Operand& op) { opMovxx(reg, op, 0xBE); } +void movupd(const Address& addr, const Xmm& xmm) { db(0x66); opModM(addr, xmm, 0x0F, 0x11); } +void movupd(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x10, 0x66); } +void movups(const Address& addr, const Xmm& xmm) { opModM(addr, xmm, 0x0F, 0x11); } +void movups(const Xmm& xmm, const Operand& op) { opMMX(xmm, op, 0x10, 0x100); } +void movzx(const Reg& reg, const Operand& op) { opMovxx(reg, op, 0xB6); } +void mpsadbw(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x42, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void mul(const Operand& op) { opR_ModM(op, 0, 4, 0xF6); } +void mulpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x59, 0x66, isXMM_XMMorMEM); } +void mulps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x59, 0x100, isXMM_XMMorMEM); } +void mulsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x59, 0xF2, isXMM_XMMorMEM); } +void mulss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x59, 0xF3, isXMM_XMMorMEM); } +void mulx(const Reg32e& r1, const Reg32e& r2, const Operand& op) { opGpr(r1, r2, op, T_F2 | T_0F38, 0xf6, true); } +void mwait() { db(0x0F); db(0x01); db(0xC9); } +void neg(const Operand& op) { opR_ModM(op, 0, 3, 0xF6); } +void not_(const Operand& op) { opR_ModM(op, 0, 2, 0xF6); } +void or_(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x08, 1); } +void or_(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x08); } +void orpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x56, 0x66, isXMM_XMMorMEM); } +void orps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x56, 0x100, isXMM_XMMorMEM); } +void pabsb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x1C, 0x66, NONE, 0x38); } +void pabsd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x1E, 0x66, NONE, 0x38); } +void pabsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x1D, 0x66, NONE, 0x38); } +void packssdw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x6B); } +void packsswb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x63); } +void packusdw(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x2B, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void packuswb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x67); } +void paddb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xFC); } +void paddd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xFE); } +void paddq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD4); } +void paddsb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xEC); } +void paddsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xED); } +void paddusb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xDC); } +void paddusw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xDD); } +void paddw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xFD); } +void palignr(const Mmx& mmx, const Operand& op, int imm) { opMMX(mmx, op, 0x0f, 0x66, static_cast(imm), 0x3a); } +void pand(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xDB); } +void pandn(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xDF); } +void pause() { db(0xF3); db(0x90); } +void pavgb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE0); } +void pavgw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE3); } +void pblendvb(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x10, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pblendw(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x0E, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void pclmulhqhdq(const Xmm& xmm, const Operand& op) { pclmulqdq(xmm, op, 0x11); } +void pclmulhqlqdq(const Xmm& xmm, const Operand& op) { pclmulqdq(xmm, op, 0x01); } +void pclmullqhdq(const Xmm& xmm, const Operand& op) { pclmulqdq(xmm, op, 0x10); } +void pclmullqlqdq(const Xmm& xmm, const Operand& op) { pclmulqdq(xmm, op, 0x00); } +void pclmulqdq(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x44, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void pcmpeqb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x74); } +void pcmpeqd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x76); } +void pcmpeqq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x29, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pcmpeqw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x75); } +void pcmpestri(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x61, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void pcmpestrm(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x60, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void pcmpgtb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x64); } +void pcmpgtd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x66); } +void pcmpgtq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x37, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pcmpgtw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x65); } +void pcmpistri(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x63, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void pcmpistrm(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x62, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void pdep(const Reg32e& r1, const Reg32e& r2, const Operand& op) { opGpr(r1, r2, op, T_F2 | T_0F38, 0xf5, true); } +void pext(const Reg32e& r1, const Reg32e& r2, const Operand& op) { opGpr(r1, r2, op, T_F3 | T_0F38, 0xf5, true); } +void pextrb(const Operand& op, const Xmm& xmm, uint8 imm) { opExt(op, xmm, 0x14, imm); } +void pextrd(const Operand& op, const Xmm& xmm, uint8 imm) { opExt(op, xmm, 0x16, imm); } +void pextrw(const Operand& op, const Mmx& xmm, uint8 imm) { opExt(op, xmm, 0x15, imm, true); } +void phaddd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x02, 0x66, NONE, 0x38); } +void phaddsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x03, 0x66, NONE, 0x38); } +void phaddw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x01, 0x66, NONE, 0x38); } +void phminposuw(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x41, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void phsubd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x06, 0x66, NONE, 0x38); } +void phsubsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x07, 0x66, NONE, 0x38); } +void phsubw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x05, 0x66, NONE, 0x38); } +void pinsrb(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x20, 0x66, isXMM_REG32orMEM, imm, 0x3A); } +void pinsrd(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x22, 0x66, isXMM_REG32orMEM, imm, 0x3A); } +void pinsrw(const Mmx& mmx, const Operand& op, int imm) { if (!op.isREG(32) && !op.isMEM()) throw Error(ERR_BAD_COMBINATION); opGen(mmx, op, 0xC4, mmx.isXMM() ? 0x66 : NONE, 0, imm); } +void pmaddubsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x04, 0x66, NONE, 0x38); } +void pmaddwd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF5); } +void pmaxsb(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x3C, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmaxsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x3D, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmaxsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xEE); } +void pmaxub(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xDE); } +void pmaxud(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x3F, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmaxuw(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x3E, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pminsb(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x38, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pminsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x39, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pminsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xEA); } +void pminub(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xDA); } +void pminud(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x3B, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pminuw(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x3A, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovmskb(const Reg32e& reg, const Mmx& mmx) { if (mmx.isXMM()) db(0x66); opModR(reg, mmx, 0x0F, 0xD7); } +void pmovsxbd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x21, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovsxbq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x22, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovsxbw(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x20, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovsxdq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x25, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovsxwd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x23, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovsxwq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x24, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovzxbd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x31, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovzxbq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x32, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovzxbw(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x30, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovzxdq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x35, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovzxwd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x33, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmovzxwq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x34, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmuldq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x28, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmulhrsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x0B, 0x66, NONE, 0x38); } +void pmulhuw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE4); } +void pmulhw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE5); } +void pmulld(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x40, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void pmullw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD5); } +void pmuludq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF4); } +void popcnt(const Reg®, const Operand& op) { opSp1(reg, op, 0xF3, 0x0F, 0xB8); } +void popf() { db(0x9D); } +void por(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xEB); } +void prefetchnta(const Address& addr) { opModM(addr, Reg32(0), 0x0F, 0x18); } +void prefetcht0(const Address& addr) { opModM(addr, Reg32(1), 0x0F, 0x18); } +void prefetcht1(const Address& addr) { opModM(addr, Reg32(2), 0x0F, 0x18); } +void prefetcht2(const Address& addr) { opModM(addr, Reg32(3), 0x0F, 0x18); } +void prefetchw(const Address& addr) { opModM(addr, Reg32(1), 0x0F, 0x0D); } +void prefetchwt1(const Address& addr) { opModM(addr, Reg32(2), 0x0F, 0x0D); } +void psadbw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF6); } +void pshufb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x00, 0x66, NONE, 0x38); } +void pshufd(const Mmx& mmx, const Operand& op, uint8 imm8) { opMMX(mmx, op, 0x70, 0x66, imm8); } +void pshufhw(const Mmx& mmx, const Operand& op, uint8 imm8) { opMMX(mmx, op, 0x70, 0xF3, imm8); } +void pshuflw(const Mmx& mmx, const Operand& op, uint8 imm8) { opMMX(mmx, op, 0x70, 0xF2, imm8); } +void pshufw(const Mmx& mmx, const Operand& op, uint8 imm8) { opMMX(mmx, op, 0x70, 0x00, imm8); } +void psignb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x08, 0x66, NONE, 0x38); } +void psignd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x0A, 0x66, NONE, 0x38); } +void psignw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x09, 0x66, NONE, 0x38); } +void pslld(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF2); } +void pslld(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x72, 6); } +void pslldq(const Xmm& xmm, int imm8) { opMMX_IMM(xmm, imm8, 0x73, 7); } +void psllq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF3); } +void psllq(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x73, 6); } +void psllw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF1); } +void psllw(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x71, 6); } +void psrad(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE2); } +void psrad(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x72, 4); } +void psraw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE1); } +void psraw(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x71, 4); } +void psrld(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD2); } +void psrld(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x72, 2); } +void psrldq(const Xmm& xmm, int imm8) { opMMX_IMM(xmm, imm8, 0x73, 3); } +void psrlq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD3); } +void psrlq(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x73, 2); } +void psrlw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD1); } +void psrlw(const Mmx& mmx, int imm8) { opMMX_IMM(mmx, imm8, 0x71, 2); } +void psubb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF8); } +void psubd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xFA); } +void psubq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xFB); } +void psubsb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE8); } +void psubsw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xE9); } +void psubusb(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD8); } +void psubusw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xD9); } +void psubw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xF9); } +void ptest(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x17, 0x66, isXMM_XMMorMEM, NONE, 0x38); } +void punpckhbw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x68); } +void punpckhdq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x6A); } +void punpckhqdq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x6D, 0x66, isXMM_XMMorMEM); } +void punpckhwd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x69); } +void punpcklbw(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x60); } +void punpckldq(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x62); } +void punpcklqdq(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x6C, 0x66, isXMM_XMMorMEM); } +void punpcklwd(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0x61); } +void pushf() { db(0x9C); } +void pxor(const Mmx& mmx, const Operand& op) { opMMX(mmx, op, 0xEF); } +void rcl(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 2); } +void rcl(const Operand& op, int imm) { opShift(op, imm, 2); } +void rcpps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x53, 0x100, isXMM_XMMorMEM); } +void rcpss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x53, 0xF3, isXMM_XMMorMEM); } +void rcr(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 3); } +void rcr(const Operand& op, int imm) { opShift(op, imm, 3); } +void rdmsr() { db(0x0F); db(0x32); } +void rdpmc() { db(0x0F); db(0x33); } +void rdrand(const Reg& r) { if (r.isBit(8)) throw Error(ERR_BAD_SIZE_OF_REGISTER); opModR(Reg(6, Operand::REG, r.getBit()), r, 0x0F, 0xC7); } +void rdseed(const Reg& r) { if (r.isBit(8)) throw Error(ERR_BAD_SIZE_OF_REGISTER); opModR(Reg(7, Operand::REG, r.getBit()), r, 0x0F, 0xC7); } +void rdtsc() { db(0x0F); db(0x31); } +void rdtscp() { db(0x0F); db(0x01); db(0xF9); } +void rep() { db(0xF3); } +void ret(int imm = 0) { if (imm) { db(0xC2); dw(imm); } else { db(0xC3); } } +void rol(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 0); } +void rol(const Operand& op, int imm) { opShift(op, imm, 0); } +void ror(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 1); } +void ror(const Operand& op, int imm) { opShift(op, imm, 1); } +void rorx(const Reg32e& r, const Operand& op, uint8 imm) { opGpr(r, op, Reg32e(0, r.getBit()), T_0F3A | T_F2, 0xF0, false, imm); } +void roundpd(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x09, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void roundps(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0x08, 0x66, isXMM_XMMorMEM, imm, 0x3A); } +void roundsd(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x0B, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void roundss(const Xmm& xmm, const Operand& op, int imm) { opGen(xmm, op, 0x0A, 0x66, isXMM_XMMorMEM, static_cast(imm), 0x3A); } +void rsqrtps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x52, 0x100, isXMM_XMMorMEM); } +void rsqrtss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x52, 0xF3, isXMM_XMMorMEM); } +void sahf() { db(0x9E); } +void sal(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 4); } +void sal(const Operand& op, int imm) { opShift(op, imm, 4); } +void sar(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 7); } +void sar(const Operand& op, int imm) { opShift(op, imm, 7); } +void sarx(const Reg32e& r1, const Operand& op, const Reg32e& r2) { opGpr(r1, op, r2, T_F3 | T_0F38, 0xf7, false); } +void sbb(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x18, 3); } +void sbb(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x18); } +void scasb() { db(0xAE); } +void scasd() { db(0xAF); } +void scasw() { db(0x66); db(0xAF); } +void seta(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 7); }//-V524 +void setae(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 3); }//-V524 +void setb(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 2); }//-V524 +void setbe(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 6); }//-V524 +void setc(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 2); }//-V524 +void sete(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 4); }//-V524 +void setg(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 15); }//-V524 +void setge(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 13); }//-V524 +void setl(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 12); }//-V524 +void setle(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 14); }//-V524 +void setna(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 6); }//-V524 +void setnae(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 2); }//-V524 +void setnb(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 3); }//-V524 +void setnbe(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 7); }//-V524 +void setnc(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 3); }//-V524 +void setne(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 5); }//-V524 +void setng(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 14); }//-V524 +void setnge(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 12); }//-V524 +void setnl(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 13); }//-V524 +void setnle(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 15); }//-V524 +void setno(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 1); }//-V524 +void setnp(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 11); }//-V524 +void setns(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 9); }//-V524 +void setnz(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 5); }//-V524 +void seto(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 0); }//-V524 +void setp(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 10); }//-V524 +void setpe(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 10); }//-V524 +void setpo(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 11); }//-V524 +void sets(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 8); }//-V524 +void setz(const Operand& op) { opR_ModM(op, 8, 0, 0x0F, 0x90 | 4); }//-V524 +void sfence() { db(0x0F); db(0xAE); db(0xF8); } +void sha1msg1(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xC9, NONE, isXMM_XMMorMEM, NONE, 0x38); } +void sha1msg2(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xCA, NONE, isXMM_XMMorMEM, NONE, 0x38); } +void sha1nexte(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xC8, NONE, isXMM_XMMorMEM, NONE, 0x38); } +void sha1rnds4(const Xmm& xmm, const Operand& op, uint8 imm) { opGen(xmm, op, 0xCC, NONE, isXMM_XMMorMEM, imm, 0x3A); } +void sha256msg1(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xCC, NONE, isXMM_XMMorMEM, NONE, 0x38); } +void sha256msg2(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xCD, NONE, isXMM_XMMorMEM, NONE, 0x38); } +void sha256rnds2(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0xCB, NONE, isXMM_XMMorMEM, NONE, 0x38); } +void shl(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 4); } +void shl(const Operand& op, int imm) { opShift(op, imm, 4); } +void shld(const Operand& op, const Reg& reg, const Reg8& _cl) { opShxd(op, reg, 0, 0xA4, &_cl); } +void shld(const Operand& op, const Reg& reg, uint8 imm) { opShxd(op, reg, imm, 0xA4); } +void shlx(const Reg32e& r1, const Operand& op, const Reg32e& r2) { opGpr(r1, op, r2, T_66 | T_0F38, 0xf7, false); } +void shr(const Operand& op, const Reg8& _cl) { opShift(op, _cl, 5); } +void shr(const Operand& op, int imm) { opShift(op, imm, 5); } +void shrd(const Operand& op, const Reg& reg, const Reg8& _cl) { opShxd(op, reg, 0, 0xAC, &_cl); } +void shrd(const Operand& op, const Reg& reg, uint8 imm) { opShxd(op, reg, imm, 0xAC); } +void shrx(const Reg32e& r1, const Operand& op, const Reg32e& r2) { opGpr(r1, op, r2, T_F2 | T_0F38, 0xf7, false); } +void shufpd(const Xmm& xmm, const Operand& op, uint8 imm8) { opGen(xmm, op, 0xC6, 0x66, isXMM_XMMorMEM, imm8); } +void shufps(const Xmm& xmm, const Operand& op, uint8 imm8) { opGen(xmm, op, 0xC6, 0x100, isXMM_XMMorMEM, imm8); } +void sqrtpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x51, 0x66, isXMM_XMMorMEM); } +void sqrtps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x51, 0x100, isXMM_XMMorMEM); } +void sqrtsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x51, 0xF2, isXMM_XMMorMEM); } +void sqrtss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x51, 0xF3, isXMM_XMMorMEM); } +void stac() { db(0x0F); db(0x01); db(0xCB); } +void stc() { db(0xF9); } +void std() { db(0xFD); } +void sti() { db(0xFB); } +void stmxcsr(const Address& addr) { opModM(addr, Reg32(3), 0x0F, 0xAE); } +void stosb() { db(0xAA); } +void stosd() { db(0xAB); } +void stosw() { db(0x66); db(0xAB); } +void sub(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x28, 5); } +void sub(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x28); } +void subpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5C, 0x66, isXMM_XMMorMEM); } +void subps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5C, 0x100, isXMM_XMMorMEM); } +void subsd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5C, 0xF2, isXMM_XMMorMEM); } +void subss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x5C, 0xF3, isXMM_XMMorMEM); } +void tzcnt(const Reg®, const Operand& op) { opSp1(reg, op, 0xF3, 0x0F, 0xBC); } +void ucomisd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x2E, 0x66, isXMM_XMMorMEM); } +void ucomiss(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x2E, 0x100, isXMM_XMMorMEM); } +void ud2() { db(0x0F); db(0x0B); } +void unpckhpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x15, 0x66, isXMM_XMMorMEM); } +void unpckhps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x15, 0x100, isXMM_XMMorMEM); } +void unpcklpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x14, 0x66, isXMM_XMMorMEM); } +void unpcklps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x14, 0x100, isXMM_XMMorMEM); } +void vaddpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x58); } +void vaddps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x58); } +void vaddsd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F2 | T_EW1 | T_EVEX | T_ER_Z | T_N8, 0x58); } +void vaddss(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F3 | T_EW0 | T_EVEX | T_ER_Z | T_N4, 0x58); } +void vaddsubpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F | T_YMM, 0xD0); } +void vaddsubps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_F2 | T_0F | T_YMM, 0xD0); } +void vaesdec(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F38 | T_YMM | T_EVEX, 0xDE); } +void vaesdeclast(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F38 | T_YMM | T_EVEX, 0xDF); } +void vaesenc(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F38 | T_YMM | T_EVEX, 0xDC); } +void vaesenclast(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F38 | T_YMM | T_EVEX, 0xDD); } +void vaesimc(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_W0, 0xDB); } +void vaeskeygenassist(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A, 0xDF, imm); } +void vandnpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x55); } +void vandnps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x55); } +void vandpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x54); } +void vandps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x54); } +void vblendpd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM, 0x0D, imm); } +void vblendps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM, 0x0C, imm); } +void vblendvpd(const Xmm& x1, const Xmm& x2, const Operand& op, const Xmm& x4) { opAVX_X_X_XM(x1, x2, op, T_0F3A | T_66 | T_YMM, 0x4B, x4.getIdx() << 4); } +void vblendvps(const Xmm& x1, const Xmm& x2, const Operand& op, const Xmm& x4) { opAVX_X_X_XM(x1, x2, op, T_0F3A | T_66 | T_YMM, 0x4A, x4.getIdx() << 4); } +void vbroadcastf128(const Ymm& y, const Address& addr) { opAVX_X_XM_IMM(y, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x1A); } +void vbroadcasti128(const Ymm& y, const Address& addr) { opAVX_X_XM_IMM(y, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x5A); } +void vbroadcastsd(const Ymm& y, const Operand& op) { if (!op.isMEM() && !(y.isYMM() && op.isXMM()) && !(y.isZMM() && op.isXMM())) throw Error(ERR_BAD_COMBINATION); opAVX_X_XM_IMM(y, op, T_0F38 | T_66 | T_W0 | T_YMM | T_EVEX | T_EW1 | T_N8, 0x19); } +void vbroadcastss(const Xmm& x, const Operand& op) { if (!(op.isXMM() || op.isMEM())) throw Error(ERR_BAD_COMBINATION); opAVX_X_XM_IMM(x, op, T_N4 | T_66 | T_0F38 | T_W0 | T_YMM | T_EVEX, 0x18); } +void vcmpeq_ospd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 16); } +void vcmpeq_osps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 16); } +void vcmpeq_ossd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 16); } +void vcmpeq_osss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 16); } +void vcmpeq_uqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 8); } +void vcmpeq_uqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 8); } +void vcmpeq_uqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 8); } +void vcmpeq_uqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 8); } +void vcmpeq_uspd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 24); } +void vcmpeq_usps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 24); } +void vcmpeq_ussd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 24); } +void vcmpeq_usss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 24); } +void vcmpeqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 0); } +void vcmpeqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 0); } +void vcmpeqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 0); } +void vcmpeqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 0); } +void vcmpfalse_ospd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 27); } +void vcmpfalse_osps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 27); } +void vcmpfalse_ossd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 27); } +void vcmpfalse_osss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 27); } +void vcmpfalsepd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 11); } +void vcmpfalseps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 11); } +void vcmpfalsesd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 11); } +void vcmpfalsess(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 11); } +void vcmpge_oqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 29); } +void vcmpge_oqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 29); } +void vcmpge_oqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 29); } +void vcmpge_oqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 29); } +void vcmpgepd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 13); } +void vcmpgeps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 13); } +void vcmpgesd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 13); } +void vcmpgess(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 13); } +void vcmpgt_oqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 30); } +void vcmpgt_oqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 30); } +void vcmpgt_oqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 30); } +void vcmpgt_oqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 30); } +void vcmpgtpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 14); } +void vcmpgtps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 14); } +void vcmpgtsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 14); } +void vcmpgtss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 14); } +void vcmple_oqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 18); } +void vcmple_oqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 18); } +void vcmple_oqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 18); } +void vcmple_oqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 18); } +void vcmplepd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 2); } +void vcmpleps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 2); } +void vcmplesd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 2); } +void vcmpless(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 2); } +void vcmplt_oqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 17); } +void vcmplt_oqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 17); } +void vcmplt_oqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 17); } +void vcmplt_oqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 17); } +void vcmpltpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 1); } +void vcmpltps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 1); } +void vcmpltsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 1); } +void vcmpltss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 1); } +void vcmpneq_oqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 12); } +void vcmpneq_oqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 12); } +void vcmpneq_oqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 12); } +void vcmpneq_oqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 12); } +void vcmpneq_ospd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 28); } +void vcmpneq_osps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 28); } +void vcmpneq_ossd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 28); } +void vcmpneq_osss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 28); } +void vcmpneq_uspd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 20); } +void vcmpneq_usps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 20); } +void vcmpneq_ussd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 20); } +void vcmpneq_usss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 20); } +void vcmpneqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 4); } +void vcmpneqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 4); } +void vcmpneqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 4); } +void vcmpneqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 4); } +void vcmpnge_uqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 25); } +void vcmpnge_uqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 25); } +void vcmpnge_uqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 25); } +void vcmpnge_uqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 25); } +void vcmpngepd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 9); } +void vcmpngeps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 9); } +void vcmpngesd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 9); } +void vcmpngess(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 9); } +void vcmpngt_uqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 26); } +void vcmpngt_uqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 26); } +void vcmpngt_uqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 26); } +void vcmpngt_uqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 26); } +void vcmpngtpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 10); } +void vcmpngtps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 10); } +void vcmpngtsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 10); } +void vcmpngtss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 10); } +void vcmpnle_uqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 22); } +void vcmpnle_uqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 22); } +void vcmpnle_uqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 22); } +void vcmpnle_uqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 22); } +void vcmpnlepd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 6); } +void vcmpnleps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 6); } +void vcmpnlesd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 6); } +void vcmpnless(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 6); } +void vcmpnlt_uqpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 21); } +void vcmpnlt_uqps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 21); } +void vcmpnlt_uqsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 21); } +void vcmpnlt_uqss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 21); } +void vcmpnltpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 5); } +void vcmpnltps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 5); } +void vcmpnltsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 5); } +void vcmpnltss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 5); } +void vcmpord_spd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 23); } +void vcmpord_sps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 23); } +void vcmpord_ssd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 23); } +void vcmpord_sss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 23); } +void vcmpordpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 7); } +void vcmpordps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 7); } +void vcmpordsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 7); } +void vcmpordss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 7); } +void vcmppd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0xC2, imm); } +void vcmpps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_0F | T_YMM, 0xC2, imm); } +void vcmpsd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_F2 | T_0F, 0xC2, imm); } +void vcmpss(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_F3 | T_0F, 0xC2, imm); } +void vcmptrue_uspd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 31); } +void vcmptrue_usps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 31); } +void vcmptrue_ussd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 31); } +void vcmptrue_usss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 31); } +void vcmptruepd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 15); } +void vcmptrueps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 15); } +void vcmptruesd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 15); } +void vcmptruess(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 15); } +void vcmpunord_spd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 19); } +void vcmpunord_sps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 19); } +void vcmpunord_ssd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 19); } +void vcmpunord_sss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 19); } +void vcmpunordpd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmppd(x1, x2, op, 3); } +void vcmpunordps(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpps(x1, x2, op, 3); } +void vcmpunordsd(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpsd(x1, x2, op, 3); } +void vcmpunordss(const Xmm& x1, const Xmm& x2, const Operand& op) { vcmpss(x1, x2, op, 3); } +void vcomisd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_66 | T_0F | T_EW1 | T_EVEX | T_SAE_X, 0x2F); } +void vcomiss(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N4 | T_0F | T_EW0 | T_EVEX | T_SAE_X, 0x2F); } +void vcvtdq2pd(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_0F | T_F3 | T_YMM | T_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL, 0xE6); } +void vcvtdq2ps(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x5B); } +void vcvtpd2dq(const Xmm& x, const Operand& op) { opCvt2(x, op, T_0F | T_F2 | T_YMM | T_EVEX | T_EW1 | T_B64 | T_ER_Z, 0xE6); } +void vcvtpd2ps(const Xmm& x, const Operand& op) { opCvt2(x, op, T_0F | T_66 | T_YMM | T_EVEX | T_EW1 | T_B64 | T_ER_Z, 0x5A); } +void vcvtph2ps(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_0F38 | T_66 | T_W0 | T_EVEX | T_EW0 | T_N8 | T_N_VL | T_SAE_Y, 0x13); } +void vcvtps2dq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x5B); } +void vcvtps2pd(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_0F | T_YMM | T_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL | T_SAE_Y, 0x5A); } +void vcvtps2ph(const Operand& op, const Xmm& x, uint8 imm) { checkCvt1(x, op); opVex(x, 0, op, T_0F3A | T_66 | T_W0 | T_EVEX | T_EW0 | T_N8 | T_N_VL | T_SAE_Y, 0x1D, imm); } +void vcvtsd2si(const Reg32& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F2 | T_W0 | T_EVEX | T_EW0 | T_N4 | T_ER_X, 0x2D); } +void vcvtsd2ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_F2 | T_0F | T_EW1 | T_EVEX | T_ER_X, 0x5A); } +void vcvtsi2sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opCvt3(x1, x2, op, T_0F | T_F2 | T_EVEX, T_W1 | T_EW1 | T_ER_X | T_N8, T_W0 | T_EW0 | T_N4, 0x2A); } +void vcvtsi2ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opCvt3(x1, x2, op, T_0F | T_F3 | T_EVEX | T_ER_X, T_W1 | T_EW1 | T_N8, T_W0 | T_EW0 | T_N4, 0x2A); } +void vcvtss2sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_F3 | T_0F | T_EW0 | T_EVEX | T_SAE_X, 0x5A); } +void vcvtss2si(const Reg32& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F3 | T_W0 | T_EVEX | T_EW0 | T_ER_X | T_N8, 0x2D); } +void vcvttpd2dq(const Xmm& x, const Operand& op) { opCvt2(x, op, T_66 | T_0F | T_YMM | T_EVEX |T_EW1 | T_B64 | T_ER_Z, 0xE6); } +void vcvttps2dq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_F3 | T_0F | T_EW0 | T_YMM | T_EVEX | T_SAE_Z | T_B32, 0x5B); } +void vcvttsd2si(const Reg32& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F2 | T_W0 | T_EVEX | T_EW0 | T_N4 | T_SAE_X, 0x2C); } +void vcvttss2si(const Reg32& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F3 | T_W0 | T_EVEX | T_EW0 | T_SAE_X | T_N8, 0x2C); } +void vdivpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x5E); } +void vdivps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x5E); } +void vdivsd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F2 | T_EW1 | T_EVEX | T_ER_Z | T_N8, 0x5E); } +void vdivss(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F3 | T_EW0 | T_EVEX | T_ER_Z | T_N4, 0x5E); } +void vdppd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0, 0x41, imm); } +void vdpps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM, 0x40, imm); } +void vextractf128(const Operand& op, const Ymm& y, uint8 imm) { if (!(op.isXMEM() && y.isYMM())) throw Error(ERR_BAD_COMBINATION); opVex(y, 0, op, T_0F3A | T_66 | T_W0 | T_YMM, 0x19, imm); } +void vextracti128(const Operand& op, const Ymm& y, uint8 imm) { if (!(op.isXMEM() && y.isYMM())) throw Error(ERR_BAD_COMBINATION); opVex(y, 0, op, T_0F3A | T_66 | T_W0 | T_YMM, 0x39, imm); } +void vextractps(const Operand& op, const Xmm& x, uint8 imm) { if (!((op.isREG(32) || op.isMEM()) && x.isXMM())) throw Error(ERR_BAD_COMBINATION); opVex(x, 0, op, T_0F3A | T_66 | T_W0 | T_EVEX | T_N4, 0x17, imm); } +void vfmadd132pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x98); } +void vfmadd132ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x98); } +void vfmadd132sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0x99); } +void vfmadd132ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0x99); } +void vfmadd213pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xA8); } +void vfmadd213ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xA8); } +void vfmadd213sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xA9); } +void vfmadd213ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xA9); } +void vfmadd231pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xB8); } +void vfmadd231ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xB8); } +void vfmadd231sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xB9); } +void vfmadd231ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xB9); } +void vfmaddsub132pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x96); } +void vfmaddsub132ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x96); } +void vfmaddsub213pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xA6); } +void vfmaddsub213ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xA6); } +void vfmaddsub231pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xB6); } +void vfmaddsub231ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xB6); } +void vfmsub132pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x9A); } +void vfmsub132ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x9A); } +void vfmsub132sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0x9B); } +void vfmsub132ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0x9B); } +void vfmsub213pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xAA); } +void vfmsub213ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xAA); } +void vfmsub213sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xAB); } +void vfmsub213ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xAB); } +void vfmsub231pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xBA); } +void vfmsub231ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xBA); } +void vfmsub231sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xBB); } +void vfmsub231ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xBB); } +void vfmsubadd132pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x97); } +void vfmsubadd132ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x97); } +void vfmsubadd213pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xA7); } +void vfmsubadd213ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xA7); } +void vfmsubadd231pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xB7); } +void vfmsubadd231ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xB7); } +void vfnmadd132pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x9C); } +void vfnmadd132ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x9C); } +void vfnmadd132sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0x9D); } +void vfnmadd132ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0x9D); } +void vfnmadd213pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xAC); } +void vfnmadd213ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xAC); } +void vfnmadd213sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xAD); } +void vfnmadd213ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xAD); } +void vfnmadd231pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xBC); } +void vfnmadd231ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xBC); } +void vfnmadd231sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xBD); } +void vfnmadd231ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xBD); } +void vfnmsub132pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x9E); } +void vfnmsub132ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x9E); } +void vfnmsub132sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0x9F); } +void vfnmsub132ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0x9F); } +void vfnmsub213pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xAE); } +void vfnmsub213ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xAE); } +void vfnmsub213sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xAF); } +void vfnmsub213ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xAF); } +void vfnmsub231pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0xBE); } +void vfnmsub231ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0xBE); } +void vfnmsub231sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_W1 | T_EW1 | T_EVEX | T_ER_X, 0xBF); } +void vfnmsub231ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_W0 | T_EW0 | T_EVEX | T_ER_X, 0xBF); } +void vgatherdpd(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W1, 0x92, 0); } +void vgatherdps(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W0, 0x92, 1); } +void vgatherqpd(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W1, 0x93, 1); } +void vgatherqps(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W0, 0x93, 2); } +void vgf2p8affineinvqb(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W1 | T_EW1 | T_YMM | T_EVEX | T_SAE_Z | T_B64, 0xCF, imm); } +void vgf2p8affineqb(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W1 | T_EW1 | T_YMM | T_EVEX | T_SAE_Z | T_B64, 0xCE, imm); } +void vgf2p8mulb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_SAE_Z, 0xCF); } +void vhaddpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F | T_YMM, 0x7C); } +void vhaddps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_F2 | T_0F | T_YMM, 0x7C); } +void vhsubpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_66 | T_0F | T_YMM, 0x7D); } +void vhsubps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_F2 | T_0F | T_YMM, 0x7D); } +void vinsertf128(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { if (!(y1.isYMM() && y2.isYMM() && op.isXMEM())) throw Error(ERR_BAD_COMBINATION); opVex(y1, &y2, op, T_0F3A | T_66 | T_W0 | T_YMM, 0x18, imm); } +void vinserti128(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { if (!(y1.isYMM() && y2.isYMM() && op.isXMEM())) throw Error(ERR_BAD_COMBINATION); opVex(y1, &y2, op, T_0F3A | T_66 | T_W0 | T_YMM, 0x38, imm); } +void vinsertps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F3A | T_W0 | T_EW0 | T_EVEX, 0x21, imm); } +void vlddqu(const Xmm& x, const Address& addr) { opAVX_X_X_XM(x, cvtIdx0(x), addr, T_0F | T_F2 | T_W0 | T_YMM, 0xF0); } +void vldmxcsr(const Address& addr) { opAVX_X_X_XM(xm2, xm0, addr, T_0F, 0xAE); } +void vmaskmovdqu(const Xmm& x1, const Xmm& x2) { opAVX_X_X_XM(x1, xm0, x2, T_0F | T_66, 0xF7); } +void vmaskmovpd(const Address& addr, const Xmm& x1, const Xmm& x2) { opAVX_X_X_XM(x2, x1, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x2F); } +void vmaskmovpd(const Xmm& x1, const Xmm& x2, const Address& addr) { opAVX_X_X_XM(x1, x2, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x2D); } +void vmaskmovps(const Address& addr, const Xmm& x1, const Xmm& x2) { opAVX_X_X_XM(x2, x1, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x2E); } +void vmaskmovps(const Xmm& x1, const Xmm& x2, const Address& addr) { opAVX_X_X_XM(x1, x2, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x2C); } +void vmaxpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x5F); } +void vmaxps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x5F); } +void vmaxsd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F2 | T_EW1 | T_EVEX | T_ER_Z | T_N8, 0x5F); } +void vmaxss(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F3 | T_EW0 | T_EVEX | T_ER_Z | T_N4, 0x5F); } +void vminpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x5D); } +void vminps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x5D); } +void vminsd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F2 | T_EW1 | T_EVEX | T_ER_Z | T_N8, 0x5D); } +void vminss(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F3 | T_EW0 | T_EVEX | T_ER_Z | T_N4, 0x5D); } +void vmovapd(const Address& addr, const Xmm& xmm) { opAVX_X_XM_IMM(xmm, addr, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_M_K, 0x29); } +void vmovapd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX, 0x28); } +void vmovaps(const Address& addr, const Xmm& xmm) { opAVX_X_XM_IMM(xmm, addr, T_0F | T_EW0 | T_YMM | T_EVEX | T_M_K, 0x29); } +void vmovaps(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_0F | T_EW0 | T_YMM | T_EVEX, 0x28); } +void vmovd(const Operand& op, const Xmm& x) { if (!op.isREG(32) && !op.isMEM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x, xm0, op, T_0F | T_66 | T_W0 | T_EVEX | T_N4, 0x7E); } +void vmovd(const Xmm& x, const Operand& op) { if (!op.isREG(32) && !op.isMEM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x, xm0, op, T_0F | T_66 | T_W0 | T_EVEX | T_N4, 0x6E); } +void vmovddup(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_DUP | T_F2 | T_0F | T_EW1 | T_YMM | T_EVEX | T_ER_X | T_ER_Y | T_ER_Z, 0x12); } +void vmovdqa(const Address& addr, const Xmm& xmm) { opAVX_X_XM_IMM(xmm, addr, T_66 | T_0F | T_YMM, 0x7F); } +void vmovdqa(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F | T_YMM, 0x6F); } +void vmovdqu(const Address& addr, const Xmm& xmm) { opAVX_X_XM_IMM(xmm, addr, T_F3 | T_0F | T_YMM, 0x7F); } +void vmovdqu(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_F3 | T_0F | T_YMM, 0x6F); } +void vmovhlps(const Xmm& x1, const Xmm& x2, const Operand& op = Operand()) { if (!op.isNone() && !op.isXMM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x1, x2, op, T_0F | T_EVEX | T_EW0, 0x12); } +void vmovhpd(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_0F | T_66 | T_EVEX | T_EW1 | T_N8, 0x17); } +void vmovhpd(const Xmm& x, const Operand& op1, const Operand& op2 = Operand()) { if (!op2.isNone() && !op2.isMEM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x, op1, op2, T_0F | T_66 | T_EVEX | T_EW1 | T_N8, 0x16); } +void vmovhps(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_0F | T_EVEX | T_EW0 | T_N8, 0x17); } +void vmovhps(const Xmm& x, const Operand& op1, const Operand& op2 = Operand()) { if (!op2.isNone() && !op2.isMEM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x, op1, op2, T_0F | T_EVEX | T_EW0 | T_N8, 0x16); } +void vmovlhps(const Xmm& x1, const Xmm& x2, const Operand& op = Operand()) { if (!op.isNone() && !op.isXMM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x1, x2, op, T_0F | T_EVEX | T_EW0, 0x16); } +void vmovlpd(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_0F | T_66 | T_EVEX | T_EW1 | T_N8, 0x13); } +void vmovlpd(const Xmm& x, const Operand& op1, const Operand& op2 = Operand()) { if (!op2.isNone() && !op2.isMEM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x, op1, op2, T_0F | T_66 | T_EVEX | T_EW1 | T_N8, 0x12); } +void vmovlps(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_0F | T_EVEX | T_EW0 | T_N8, 0x13); } +void vmovlps(const Xmm& x, const Operand& op1, const Operand& op2 = Operand()) { if (!op2.isNone() && !op2.isMEM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x, op1, op2, T_0F | T_EVEX | T_EW0 | T_N8, 0x12); } +void vmovmskpd(const Reg& r, const Xmm& x) { if (!r.isBit(i32e)) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x.isXMM() ? Xmm(r.getIdx()) : Ymm(r.getIdx()), cvtIdx0(x), x, T_0F | T_66 | T_W0 | T_YMM, 0x50); } +void vmovmskps(const Reg& r, const Xmm& x) { if (!r.isBit(i32e)) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x.isXMM() ? Xmm(r.getIdx()) : Ymm(r.getIdx()), cvtIdx0(x), x, T_0F | T_W0 | T_YMM, 0x50); } +void vmovntdq(const Address& addr, const Xmm& x) { opVex(x, 0, addr, T_0F | T_66 | T_YMM | T_EVEX | T_EW0, 0xE7); } +void vmovntdqa(const Xmm& x, const Address& addr) { opVex(x, 0, addr, T_0F38 | T_66 | T_YMM | T_EVEX | T_EW0, 0x2A); } +void vmovntpd(const Address& addr, const Xmm& x) { opVex(x, 0, addr, T_0F | T_66 | T_YMM | T_EVEX | T_EW1, 0x2B); } +void vmovntps(const Address& addr, const Xmm& x) { opVex(x, 0, addr, T_0F | T_YMM | T_EVEX | T_EW0, 0x2B); } +void vmovq(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_0F | T_66 | T_EVEX | T_EW1 | T_N8, x.getIdx() < 16 ? 0xD6 : 0x7E); } +void vmovq(const Xmm& x, const Address& addr) { int type, code; if (x.getIdx() < 16) { type = T_0F | T_F3; code = 0x7E; } else { type = T_0F | T_66 | T_EVEX | T_EW1 | T_N8; code = 0x6E; } opAVX_X_X_XM(x, xm0, addr, type, code); } +void vmovq(const Xmm& x1, const Xmm& x2) { opAVX_X_X_XM(x1, xm0, x2, T_0F | T_F3 | T_EVEX | T_EW1 | T_N8, 0x7E); } +void vmovsd(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_N8 | T_F2 | T_0F | T_EW1 | T_EVEX | T_M_K, 0x11); } +void vmovsd(const Xmm& x, const Address& addr) { opAVX_X_X_XM(x, xm0, addr, T_N8 | T_F2 | T_0F | T_EW1 | T_EVEX, 0x10); } +void vmovsd(const Xmm& x1, const Xmm& x2, const Operand& op = Operand()) { if (!op.isNone() && !op.isXMM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x1, x2, op, T_N8 | T_F2 | T_0F | T_EW1 | T_EVEX, 0x10); } +void vmovshdup(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_F3 | T_0F | T_EW0 | T_YMM | T_EVEX, 0x16); } +void vmovsldup(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_F3 | T_0F | T_EW0 | T_YMM | T_EVEX, 0x12); } +void vmovss(const Address& addr, const Xmm& x) { opAVX_X_X_XM(x, xm0, addr, T_N4 | T_F3 | T_0F | T_EW0 | T_EVEX | T_M_K, 0x11); } +void vmovss(const Xmm& x, const Address& addr) { opAVX_X_X_XM(x, xm0, addr, T_N4 | T_F3 | T_0F | T_EW0 | T_EVEX, 0x10); } +void vmovss(const Xmm& x1, const Xmm& x2, const Operand& op = Operand()) { if (!op.isNone() && !op.isXMM()) throw Error(ERR_BAD_COMBINATION); opAVX_X_X_XM(x1, x2, op, T_N4 | T_F3 | T_0F | T_EW0 | T_EVEX, 0x10); } +void vmovupd(const Address& addr, const Xmm& xmm) { opAVX_X_XM_IMM(xmm, addr, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_M_K, 0x11); } +void vmovupd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX, 0x10); } +void vmovups(const Address& addr, const Xmm& xmm) { opAVX_X_XM_IMM(xmm, addr, T_0F | T_EW0 | T_YMM | T_EVEX | T_M_K, 0x11); } +void vmovups(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_0F | T_EW0 | T_YMM | T_EVEX, 0x10); } +void vmpsadbw(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM, 0x42, imm); } +void vmulpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x59); } +void vmulps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x59); } +void vmulsd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F2 | T_EW1 | T_EVEX | T_ER_Z | T_N8, 0x59); } +void vmulss(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F3 | T_EW0 | T_EVEX | T_ER_Z | T_N4, 0x59); } +void vorpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x56); } +void vorps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x56); } +void vpabsb(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x1C); } +void vpabsd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x1E); } +void vpabsw(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x1D); } +void vpackssdw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0x6B); } +void vpacksswb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0x63); } +void vpackusdw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x2B); } +void vpackuswb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0x67); } +void vpaddb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xFC); } +void vpaddd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0xFE); } +void vpaddq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0xD4); } +void vpaddsb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xEC); } +void vpaddsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xED); } +void vpaddusb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xDC); } +void vpaddusw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xDD); } +void vpaddw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xFD); } +void vpalignr(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_YMM | T_EVEX, 0x0F, imm); } +void vpand(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0xDB); } +void vpandn(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0xDF); } +void vpavgb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xE0); } +void vpavgw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xE3); } +void vpblendd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM, 0x02, imm); } +void vpblendvb(const Xmm& x1, const Xmm& x2, const Operand& op, const Xmm& x4) { opAVX_X_X_XM(x1, x2, op, T_0F3A | T_66 | T_YMM, 0x4C, x4.getIdx() << 4); } +void vpblendw(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM, 0x0E, imm); } +void vpbroadcastb(const Xmm& x, const Operand& op) { if (!(op.isXMM() || op.isMEM())) throw Error(ERR_BAD_COMBINATION); opAVX_X_XM_IMM(x, op, T_N1 | T_66 | T_0F38 | T_W0 | T_YMM | T_EVEX, 0x78); } +void vpbroadcastd(const Xmm& x, const Operand& op) { if (!(op.isXMM() || op.isMEM())) throw Error(ERR_BAD_COMBINATION); opAVX_X_XM_IMM(x, op, T_N4 | T_66 | T_0F38 | T_W0 | T_YMM | T_EVEX, 0x58); } +void vpbroadcastq(const Xmm& x, const Operand& op) { if (!(op.isXMM() || op.isMEM())) throw Error(ERR_BAD_COMBINATION); opAVX_X_XM_IMM(x, op, T_N8 | T_66 | T_0F38 | T_W0 | T_EW1 | T_YMM | T_EVEX, 0x59); } +void vpbroadcastw(const Xmm& x, const Operand& op) { if (!(op.isXMM() || op.isMEM())) throw Error(ERR_BAD_COMBINATION); opAVX_X_XM_IMM(x, op, T_N2 | T_66 | T_0F38 | T_W0 | T_YMM | T_EVEX, 0x79); } +void vpclmulqdq(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0 | T_YMM | T_EVEX, 0x44, imm); } +void vpcmpeqb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0x74); } +void vpcmpeqd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0x76); } +void vpcmpeqq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x29); } +void vpcmpeqw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0x75); } +void vpcmpestri(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A, 0x61, imm); } +void vpcmpestrm(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A, 0x60, imm); } +void vpcmpgtb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0x64); } +void vpcmpgtd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0x66); } +void vpcmpgtq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x37); } +void vpcmpgtw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0x65); } +void vpcmpistri(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A, 0x63, imm); } +void vpcmpistrm(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A, 0x62, imm); } +void vperm2f128(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { if (!(y1.isYMM() && y2.isYMM() && op.isYMEM())) throw Error(ERR_BAD_COMBINATION); opVex(y1, &y2, op, T_0F3A | T_66 | T_W0 | T_YMM, 0x06, imm); } +void vperm2i128(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { if (!(y1.isYMM() && y2.isYMM() && op.isYMEM())) throw Error(ERR_BAD_COMBINATION); opVex(y1, &y2, op, T_0F3A | T_66 | T_W0 | T_YMM, 0x46, imm); } +void vpermd(const Ymm& y1, const Ymm& y2, const Operand& op) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x36); } +void vpermilpd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x0D); } +void vpermilpd(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_EVEX | T_B64, 0x05, imm); } +void vpermilps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x0C); } +void vpermilps(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_EVEX | T_B32, 0x04, imm); } +void vpermpd(const Ymm& y, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(y, op, T_66 | T_0F3A | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x01, imm); } +void vpermpd(const Ymm& y1, const Ymm& y2, const Operand& op) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x16); } +void vpermps(const Ymm& y1, const Ymm& y2, const Operand& op) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x16); } +void vpermq(const Ymm& y, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(y, op, T_66 | T_0F3A | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x00, imm); } +void vpermq(const Ymm& y1, const Ymm& y2, const Operand& op) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F38 | T_W0 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x36); } +void vpextrb(const Operand& op, const Xmm& x, uint8 imm) { if (!((op.isREG(8|16|i32e) || op.isMEM()) && x.isXMM())) throw Error(ERR_BAD_COMBINATION); opVex(x, 0, op, T_0F3A | T_66 | T_EVEX | T_N1, 0x14, imm); } +void vpextrd(const Operand& op, const Xmm& x, uint8 imm) { if (!((op.isREG(32) || op.isMEM()) && x.isXMM())) throw Error(ERR_BAD_COMBINATION); opVex(x, 0, op, T_0F3A | T_66 | T_W0 | T_EVEX | T_EW0 | T_N4, 0x16, imm); } +void vpextrq(const Operand& op, const Xmm& x, uint8 imm) { if (!((op.isREG(64) || op.isMEM()) && x.isXMM())) throw Error(ERR_BAD_COMBINATION); opVex(x, 0, op, T_0F3A | T_66 | T_W1 | T_EVEX | T_EW1 | T_N8, 0x16, imm); } +void vpextrw(const Operand& op, const Xmm& x, uint8 imm) { if (!((op.isREG(16|i32e) || op.isMEM()) && x.isXMM())) throw Error(ERR_BAD_COMBINATION); if (op.isREG() && x.getIdx() < 16) { opAVX_X_X_XM(Xmm(op.getIdx()), xm0, x, T_0F | T_66, 0xC5, imm); } else { opVex(x, 0, op, T_0F3A | T_66 | T_EVEX | T_N2, 0x15, imm); } } +void vpgatherdd(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W0, 0x90, 1); } +void vpgatherdq(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W1, 0x90, 0); } +void vpgatherqd(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W0, 0x91, 2); } +void vpgatherqq(const Xmm& x1, const Address& addr, const Xmm& x2) { opGather(x1, addr, x2, T_0F38 | T_66 | T_YMM | T_VSIB | T_W1, 0x91, 1); } +void vphaddd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x02); } +void vphaddsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x03); } +void vphaddw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x01); } +void vphminposuw(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38, 0x41); } +void vphsubd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x06); } +void vphsubsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x07); } +void vphsubw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x05); } +void vpinsrb(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { if (!(x1.isXMM() && x2.isXMM() && (op.isREG(32) || op.isMEM()))) throw Error(ERR_BAD_COMBINATION); opVex(x1, &x2, op, T_0F3A | T_66 | T_EVEX | T_N1, 0x20, imm); } +void vpinsrd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { if (!(x1.isXMM() && x2.isXMM() && (op.isREG(32) || op.isMEM()))) throw Error(ERR_BAD_COMBINATION); opVex(x1, &x2, op, T_0F3A | T_66 | T_W0 | T_EVEX | T_EW0 | T_N4, 0x22, imm); } +void vpinsrq(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { if (!(x1.isXMM() && x2.isXMM() && (op.isREG(64) || op.isMEM()))) throw Error(ERR_BAD_COMBINATION); opVex(x1, &x2, op, T_0F3A | T_66 | T_W1 | T_EVEX | T_EW1 | T_N8, 0x22, imm); } +void vpinsrw(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { if (!(x1.isXMM() && x2.isXMM() && (op.isREG(32) || op.isMEM()))) throw Error(ERR_BAD_COMBINATION); opVex(x1, &x2, op, T_0F | T_66 | T_EVEX | T_N2, 0xC4, imm); } +void vpmaddubsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x04); } +void vpmaddwd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xF5); } +void vpmaskmovd(const Address& addr, const Xmm& x1, const Xmm& x2) { opAVX_X_X_XM(x2, x1, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x8E); } +void vpmaskmovd(const Xmm& x1, const Xmm& x2, const Address& addr) { opAVX_X_X_XM(x1, x2, addr, T_0F38 | T_66 | T_W0 | T_YMM, 0x8C); } +void vpmaskmovq(const Address& addr, const Xmm& x1, const Xmm& x2) { opAVX_X_X_XM(x2, x1, addr, T_0F38 | T_66 | T_W1 | T_YMM, 0x8E); } +void vpmaskmovq(const Xmm& x1, const Xmm& x2, const Address& addr) { opAVX_X_X_XM(x1, x2, addr, T_0F38 | T_66 | T_W1 | T_YMM, 0x8C); } +void vpmaxsb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x3C); } +void vpmaxsd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x3D); } +void vpmaxsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xEE); } +void vpmaxub(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xDE); } +void vpmaxud(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x3F); } +void vpmaxuw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x3E); } +void vpminsb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x38); } +void vpminsd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x39); } +void vpminsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xEA); } +void vpminub(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xDA); } +void vpminud(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x3B); } +void vpminuw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x3A); } +void vpmovmskb(const Reg32e& r, const Xmm& x) { if (!x.is(Operand::XMM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(x.isYMM() ? Ymm(r.getIdx()) : Xmm(r.getIdx()), 0, x, T_0F | T_66 | T_YMM, 0xD7); } +void vpmovsxbd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N4 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x21); } +void vpmovsxbq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N2 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x22); } +void vpmovsxbw(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x20); } +void vpmovsxdq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_N_VL | T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX, 0x25); } +void vpmovsxwd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x23); } +void vpmovsxwq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N4 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x24); } +void vpmovzxbd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N4 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x31); } +void vpmovzxbq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N2 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x32); } +void vpmovzxbw(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x30); } +void vpmovzxdq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_N_VL | T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX, 0x35); } +void vpmovzxwd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x33); } +void vpmovzxwq(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N4 | T_N_VL | T_66 | T_0F38 | T_YMM | T_EVEX, 0x34); } +void vpmuldq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x28); } +void vpmulhrsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x0B); } +void vpmulhuw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xE4); } +void vpmulhw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xE5); } +void vpmulld(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x40); } +void vpmullw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xD5); } +void vpmuludq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0xF4); } +void vpor(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0xEB); } +void vpsadbw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xF6); } +void vpshufb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM | T_EVEX, 0x00); } +void vpshufd(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0x70, imm); } +void vpshufhw(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_F3 | T_0F | T_YMM | T_EVEX, 0x70, imm); } +void vpshuflw(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_F2 | T_0F | T_YMM | T_EVEX, 0x70, imm); } +void vpsignb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x08); } +void vpsignd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x0A); } +void vpsignw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_YMM, 0x09); } +void vpslld(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 6), x, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32 | T_MEM_EVEX, 0x72, imm); } +void vpslld(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_EW0 | T_YMM | T_EVEX, 0xF2); } +void vpslldq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 7), x, op, T_66 | T_0F | T_YMM | T_EVEX | T_MEM_EVEX, 0x73, imm); } +void vpsllq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 6), x, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64 | T_MEM_EVEX, 0x73, imm); } +void vpsllq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_EW1 | T_YMM | T_EVEX, 0xF3); } +void vpsllvd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x47); } +void vpsllvq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x47); } +void vpsllw(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 6), x, op, T_66 | T_0F | T_YMM | T_EVEX | T_MEM_EVEX, 0x71, imm); } +void vpsllw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_YMM | T_EVEX, 0xF1); } +void vpsrad(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 4), x, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32 | T_MEM_EVEX, 0x72, imm); } +void vpsrad(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_EW0 | T_YMM | T_EVEX, 0xE2); } +void vpsravd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x46); } +void vpsraw(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 4), x, op, T_66 | T_0F | T_YMM | T_EVEX | T_MEM_EVEX, 0x71, imm); } +void vpsraw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_YMM | T_EVEX, 0xE1); } +void vpsrld(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 2), x, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32 | T_MEM_EVEX, 0x72, imm); } +void vpsrld(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_EW0 | T_YMM | T_EVEX, 0xD2); } +void vpsrldq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 3), x, op, T_66 | T_0F | T_YMM | T_EVEX | T_MEM_EVEX, 0x73, imm); } +void vpsrlq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 2), x, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64 | T_MEM_EVEX, 0x73, imm); } +void vpsrlq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_EW1 | T_YMM | T_EVEX, 0xD3); } +void vpsrlvd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W0 | T_EW0 | T_YMM | T_EVEX | T_B32, 0x45); } +void vpsrlvq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_W1 | T_EW1 | T_YMM | T_EVEX | T_B64, 0x45); } +void vpsrlw(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 2), x, op, T_66 | T_0F | T_YMM | T_EVEX | T_MEM_EVEX, 0x71, imm); } +void vpsrlw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_YMM | T_EVEX, 0xD1); } +void vpsubb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xF8); } +void vpsubd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0xFA); } +void vpsubq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0xFB); } +void vpsubsb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xE8); } +void vpsubsw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xE9); } +void vpsubusb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xD8); } +void vpsubusw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xD9); } +void vpsubw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0xF9); } +void vptest(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_YMM, 0x17); } +void vpunpckhbw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0x68); } +void vpunpckhdq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0x6A); } +void vpunpckhqdq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0x6D); } +void vpunpckhwd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0x69); } +void vpunpcklbw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0x60); } +void vpunpckldq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0x62); } +void vpunpcklqdq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0x6C); } +void vpunpcklwd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM | T_EVEX, 0x61); } +void vpxor(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_YMM, 0xEF); } +void vrcpps(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_0F | T_YMM, 0x53); } +void vrcpss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_F3 | T_0F, 0x53); } +void vroundpd(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A | T_YMM, 0x09, imm); } +void vroundps(const Xmm& xm, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F3A | T_YMM, 0x08, imm); } +void vroundsd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0, 0x0B, imm); } +void vroundss(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_W0, 0x0A, imm); } +void vrsqrtps(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_0F | T_YMM, 0x52); } +void vrsqrtss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_F3 | T_0F, 0x52); } +void vshufpd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0xC6, imm); } +void vshufps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0xC6, imm); } +void vsqrtpd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x51); } +void vsqrtps(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x51); } +void vsqrtsd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_F2 | T_0F | T_EW1 | T_EVEX | T_ER_X, 0x51); } +void vsqrtss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_F3 | T_0F | T_EW0 | T_EVEX | T_ER_X, 0x51); } +void vstmxcsr(const Address& addr) { opAVX_X_X_XM(xm3, xm0, addr, T_0F, 0xAE); } +void vsubpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x5C); } +void vsubps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x5C); } +void vsubsd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F2 | T_EW1 | T_EVEX | T_ER_Z | T_N8, 0x5C); } +void vsubss(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_F3 | T_EW0 | T_EVEX | T_ER_Z | T_N4, 0x5C); } +void vtestpd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_YMM, 0x0F); } +void vtestps(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_66 | T_0F38 | T_YMM, 0x0E); } +void vucomisd(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N8 | T_66 | T_0F | T_EW1 | T_EVEX | T_SAE_X, 0x2E); } +void vucomiss(const Xmm& xm, const Operand& op) { opAVX_X_XM_IMM(xm, op, T_N4 | T_0F | T_EW0 | T_EVEX | T_SAE_X, 0x2E); } +void vunpckhpd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0x15); } +void vunpckhps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0x15); } +void vunpcklpd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_EVEX | T_B64, 0x14); } +void vunpcklps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_0F | T_EW0 | T_YMM | T_EVEX | T_B32, 0x14); } +void vxorpd(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_66 | T_EW1 | T_YMM | T_EVEX | T_ER_Z | T_B64, 0x57); } +void vxorps(const Xmm& xmm, const Operand& op1, const Operand& op2 = Operand()) { opAVX_X_X_XM(xmm, op1, op2, T_0F | T_EW0 | T_YMM | T_EVEX | T_ER_Z | T_B32, 0x57); } +void vzeroall() { db(0xC5); db(0xFC); db(0x77); } +void vzeroupper() { db(0xC5); db(0xF8); db(0x77); } +void wait() { db(0x9B); } +void wbinvd() { db(0x0F); db(0x09); } +void wrmsr() { db(0x0F); db(0x30); } +void xadd(const Operand& op, const Reg& reg) { opModRM(reg, op, (op.isREG() && reg.isREG() && op.getBit() == reg.getBit()), op.isMEM(), 0x0F, 0xC0 | (reg.isBit(8) ? 0 : 1)); } +void xgetbv() { db(0x0F); db(0x01); db(0xD0); } +void xlatb() { db(0xD7); } +void xor_(const Operand& op, uint32 imm) { opRM_I(op, imm, 0x30, 6); } +void xor_(const Operand& op1, const Operand& op2) { opRM_RM(op1, op2, 0x30); } +void xorpd(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x57, 0x66, isXMM_XMMorMEM); } +void xorps(const Xmm& xmm, const Operand& op) { opGen(xmm, op, 0x57, 0x100, isXMM_XMMorMEM); } +#ifdef XBYAK_ENABLE_OMITTED_OPERAND +void vblendpd(const Xmm& x, const Operand& op, uint8 imm) { vblendpd(x, x, op, imm); } +void vblendps(const Xmm& x, const Operand& op, uint8 imm) { vblendps(x, x, op, imm); } +void vblendvpd(const Xmm& x1, const Operand& op, const Xmm& x4) { vblendvpd(x1, x1, op, x4); } +void vblendvps(const Xmm& x1, const Operand& op, const Xmm& x4) { vblendvps(x1, x1, op, x4); } +void vcmpeq_ospd(const Xmm& x, const Operand& op) { vcmpeq_ospd(x, x, op); } +void vcmpeq_osps(const Xmm& x, const Operand& op) { vcmpeq_osps(x, x, op); } +void vcmpeq_ossd(const Xmm& x, const Operand& op) { vcmpeq_ossd(x, x, op); } +void vcmpeq_osss(const Xmm& x, const Operand& op) { vcmpeq_osss(x, x, op); } +void vcmpeq_uqpd(const Xmm& x, const Operand& op) { vcmpeq_uqpd(x, x, op); } +void vcmpeq_uqps(const Xmm& x, const Operand& op) { vcmpeq_uqps(x, x, op); } +void vcmpeq_uqsd(const Xmm& x, const Operand& op) { vcmpeq_uqsd(x, x, op); } +void vcmpeq_uqss(const Xmm& x, const Operand& op) { vcmpeq_uqss(x, x, op); } +void vcmpeq_uspd(const Xmm& x, const Operand& op) { vcmpeq_uspd(x, x, op); } +void vcmpeq_usps(const Xmm& x, const Operand& op) { vcmpeq_usps(x, x, op); } +void vcmpeq_ussd(const Xmm& x, const Operand& op) { vcmpeq_ussd(x, x, op); } +void vcmpeq_usss(const Xmm& x, const Operand& op) { vcmpeq_usss(x, x, op); } +void vcmpeqpd(const Xmm& x, const Operand& op) { vcmpeqpd(x, x, op); } +void vcmpeqps(const Xmm& x, const Operand& op) { vcmpeqps(x, x, op); } +void vcmpeqsd(const Xmm& x, const Operand& op) { vcmpeqsd(x, x, op); } +void vcmpeqss(const Xmm& x, const Operand& op) { vcmpeqss(x, x, op); } +void vcmpfalse_ospd(const Xmm& x, const Operand& op) { vcmpfalse_ospd(x, x, op); } +void vcmpfalse_osps(const Xmm& x, const Operand& op) { vcmpfalse_osps(x, x, op); } +void vcmpfalse_ossd(const Xmm& x, const Operand& op) { vcmpfalse_ossd(x, x, op); } +void vcmpfalse_osss(const Xmm& x, const Operand& op) { vcmpfalse_osss(x, x, op); } +void vcmpfalsepd(const Xmm& x, const Operand& op) { vcmpfalsepd(x, x, op); } +void vcmpfalseps(const Xmm& x, const Operand& op) { vcmpfalseps(x, x, op); } +void vcmpfalsesd(const Xmm& x, const Operand& op) { vcmpfalsesd(x, x, op); } +void vcmpfalsess(const Xmm& x, const Operand& op) { vcmpfalsess(x, x, op); } +void vcmpge_oqpd(const Xmm& x, const Operand& op) { vcmpge_oqpd(x, x, op); } +void vcmpge_oqps(const Xmm& x, const Operand& op) { vcmpge_oqps(x, x, op); } +void vcmpge_oqsd(const Xmm& x, const Operand& op) { vcmpge_oqsd(x, x, op); } +void vcmpge_oqss(const Xmm& x, const Operand& op) { vcmpge_oqss(x, x, op); } +void vcmpgepd(const Xmm& x, const Operand& op) { vcmpgepd(x, x, op); } +void vcmpgeps(const Xmm& x, const Operand& op) { vcmpgeps(x, x, op); } +void vcmpgesd(const Xmm& x, const Operand& op) { vcmpgesd(x, x, op); } +void vcmpgess(const Xmm& x, const Operand& op) { vcmpgess(x, x, op); } +void vcmpgt_oqpd(const Xmm& x, const Operand& op) { vcmpgt_oqpd(x, x, op); } +void vcmpgt_oqps(const Xmm& x, const Operand& op) { vcmpgt_oqps(x, x, op); } +void vcmpgt_oqsd(const Xmm& x, const Operand& op) { vcmpgt_oqsd(x, x, op); } +void vcmpgt_oqss(const Xmm& x, const Operand& op) { vcmpgt_oqss(x, x, op); } +void vcmpgtpd(const Xmm& x, const Operand& op) { vcmpgtpd(x, x, op); } +void vcmpgtps(const Xmm& x, const Operand& op) { vcmpgtps(x, x, op); } +void vcmpgtsd(const Xmm& x, const Operand& op) { vcmpgtsd(x, x, op); } +void vcmpgtss(const Xmm& x, const Operand& op) { vcmpgtss(x, x, op); } +void vcmple_oqpd(const Xmm& x, const Operand& op) { vcmple_oqpd(x, x, op); } +void vcmple_oqps(const Xmm& x, const Operand& op) { vcmple_oqps(x, x, op); } +void vcmple_oqsd(const Xmm& x, const Operand& op) { vcmple_oqsd(x, x, op); } +void vcmple_oqss(const Xmm& x, const Operand& op) { vcmple_oqss(x, x, op); } +void vcmplepd(const Xmm& x, const Operand& op) { vcmplepd(x, x, op); } +void vcmpleps(const Xmm& x, const Operand& op) { vcmpleps(x, x, op); } +void vcmplesd(const Xmm& x, const Operand& op) { vcmplesd(x, x, op); } +void vcmpless(const Xmm& x, const Operand& op) { vcmpless(x, x, op); } +void vcmplt_oqpd(const Xmm& x, const Operand& op) { vcmplt_oqpd(x, x, op); } +void vcmplt_oqps(const Xmm& x, const Operand& op) { vcmplt_oqps(x, x, op); } +void vcmplt_oqsd(const Xmm& x, const Operand& op) { vcmplt_oqsd(x, x, op); } +void vcmplt_oqss(const Xmm& x, const Operand& op) { vcmplt_oqss(x, x, op); } +void vcmpltpd(const Xmm& x, const Operand& op) { vcmpltpd(x, x, op); } +void vcmpltps(const Xmm& x, const Operand& op) { vcmpltps(x, x, op); } +void vcmpltsd(const Xmm& x, const Operand& op) { vcmpltsd(x, x, op); } +void vcmpltss(const Xmm& x, const Operand& op) { vcmpltss(x, x, op); } +void vcmpneq_oqpd(const Xmm& x, const Operand& op) { vcmpneq_oqpd(x, x, op); } +void vcmpneq_oqps(const Xmm& x, const Operand& op) { vcmpneq_oqps(x, x, op); } +void vcmpneq_oqsd(const Xmm& x, const Operand& op) { vcmpneq_oqsd(x, x, op); } +void vcmpneq_oqss(const Xmm& x, const Operand& op) { vcmpneq_oqss(x, x, op); } +void vcmpneq_ospd(const Xmm& x, const Operand& op) { vcmpneq_ospd(x, x, op); } +void vcmpneq_osps(const Xmm& x, const Operand& op) { vcmpneq_osps(x, x, op); } +void vcmpneq_ossd(const Xmm& x, const Operand& op) { vcmpneq_ossd(x, x, op); } +void vcmpneq_osss(const Xmm& x, const Operand& op) { vcmpneq_osss(x, x, op); } +void vcmpneq_uspd(const Xmm& x, const Operand& op) { vcmpneq_uspd(x, x, op); } +void vcmpneq_usps(const Xmm& x, const Operand& op) { vcmpneq_usps(x, x, op); } +void vcmpneq_ussd(const Xmm& x, const Operand& op) { vcmpneq_ussd(x, x, op); } +void vcmpneq_usss(const Xmm& x, const Operand& op) { vcmpneq_usss(x, x, op); } +void vcmpneqpd(const Xmm& x, const Operand& op) { vcmpneqpd(x, x, op); } +void vcmpneqps(const Xmm& x, const Operand& op) { vcmpneqps(x, x, op); } +void vcmpneqsd(const Xmm& x, const Operand& op) { vcmpneqsd(x, x, op); } +void vcmpneqss(const Xmm& x, const Operand& op) { vcmpneqss(x, x, op); } +void vcmpnge_uqpd(const Xmm& x, const Operand& op) { vcmpnge_uqpd(x, x, op); } +void vcmpnge_uqps(const Xmm& x, const Operand& op) { vcmpnge_uqps(x, x, op); } +void vcmpnge_uqsd(const Xmm& x, const Operand& op) { vcmpnge_uqsd(x, x, op); } +void vcmpnge_uqss(const Xmm& x, const Operand& op) { vcmpnge_uqss(x, x, op); } +void vcmpngepd(const Xmm& x, const Operand& op) { vcmpngepd(x, x, op); } +void vcmpngeps(const Xmm& x, const Operand& op) { vcmpngeps(x, x, op); } +void vcmpngesd(const Xmm& x, const Operand& op) { vcmpngesd(x, x, op); } +void vcmpngess(const Xmm& x, const Operand& op) { vcmpngess(x, x, op); } +void vcmpngt_uqpd(const Xmm& x, const Operand& op) { vcmpngt_uqpd(x, x, op); } +void vcmpngt_uqps(const Xmm& x, const Operand& op) { vcmpngt_uqps(x, x, op); } +void vcmpngt_uqsd(const Xmm& x, const Operand& op) { vcmpngt_uqsd(x, x, op); } +void vcmpngt_uqss(const Xmm& x, const Operand& op) { vcmpngt_uqss(x, x, op); } +void vcmpngtpd(const Xmm& x, const Operand& op) { vcmpngtpd(x, x, op); } +void vcmpngtps(const Xmm& x, const Operand& op) { vcmpngtps(x, x, op); } +void vcmpngtsd(const Xmm& x, const Operand& op) { vcmpngtsd(x, x, op); } +void vcmpngtss(const Xmm& x, const Operand& op) { vcmpngtss(x, x, op); } +void vcmpnle_uqpd(const Xmm& x, const Operand& op) { vcmpnle_uqpd(x, x, op); } +void vcmpnle_uqps(const Xmm& x, const Operand& op) { vcmpnle_uqps(x, x, op); } +void vcmpnle_uqsd(const Xmm& x, const Operand& op) { vcmpnle_uqsd(x, x, op); } +void vcmpnle_uqss(const Xmm& x, const Operand& op) { vcmpnle_uqss(x, x, op); } +void vcmpnlepd(const Xmm& x, const Operand& op) { vcmpnlepd(x, x, op); } +void vcmpnleps(const Xmm& x, const Operand& op) { vcmpnleps(x, x, op); } +void vcmpnlesd(const Xmm& x, const Operand& op) { vcmpnlesd(x, x, op); } +void vcmpnless(const Xmm& x, const Operand& op) { vcmpnless(x, x, op); } +void vcmpnlt_uqpd(const Xmm& x, const Operand& op) { vcmpnlt_uqpd(x, x, op); } +void vcmpnlt_uqps(const Xmm& x, const Operand& op) { vcmpnlt_uqps(x, x, op); } +void vcmpnlt_uqsd(const Xmm& x, const Operand& op) { vcmpnlt_uqsd(x, x, op); } +void vcmpnlt_uqss(const Xmm& x, const Operand& op) { vcmpnlt_uqss(x, x, op); } +void vcmpnltpd(const Xmm& x, const Operand& op) { vcmpnltpd(x, x, op); } +void vcmpnltps(const Xmm& x, const Operand& op) { vcmpnltps(x, x, op); } +void vcmpnltsd(const Xmm& x, const Operand& op) { vcmpnltsd(x, x, op); } +void vcmpnltss(const Xmm& x, const Operand& op) { vcmpnltss(x, x, op); } +void vcmpord_spd(const Xmm& x, const Operand& op) { vcmpord_spd(x, x, op); } +void vcmpord_sps(const Xmm& x, const Operand& op) { vcmpord_sps(x, x, op); } +void vcmpord_ssd(const Xmm& x, const Operand& op) { vcmpord_ssd(x, x, op); } +void vcmpord_sss(const Xmm& x, const Operand& op) { vcmpord_sss(x, x, op); } +void vcmpordpd(const Xmm& x, const Operand& op) { vcmpordpd(x, x, op); } +void vcmpordps(const Xmm& x, const Operand& op) { vcmpordps(x, x, op); } +void vcmpordsd(const Xmm& x, const Operand& op) { vcmpordsd(x, x, op); } +void vcmpordss(const Xmm& x, const Operand& op) { vcmpordss(x, x, op); } +void vcmppd(const Xmm& x, const Operand& op, uint8 imm) { vcmppd(x, x, op, imm); } +void vcmpps(const Xmm& x, const Operand& op, uint8 imm) { vcmpps(x, x, op, imm); } +void vcmpsd(const Xmm& x, const Operand& op, uint8 imm) { vcmpsd(x, x, op, imm); } +void vcmpss(const Xmm& x, const Operand& op, uint8 imm) { vcmpss(x, x, op, imm); } +void vcmptrue_uspd(const Xmm& x, const Operand& op) { vcmptrue_uspd(x, x, op); } +void vcmptrue_usps(const Xmm& x, const Operand& op) { vcmptrue_usps(x, x, op); } +void vcmptrue_ussd(const Xmm& x, const Operand& op) { vcmptrue_ussd(x, x, op); } +void vcmptrue_usss(const Xmm& x, const Operand& op) { vcmptrue_usss(x, x, op); } +void vcmptruepd(const Xmm& x, const Operand& op) { vcmptruepd(x, x, op); } +void vcmptrueps(const Xmm& x, const Operand& op) { vcmptrueps(x, x, op); } +void vcmptruesd(const Xmm& x, const Operand& op) { vcmptruesd(x, x, op); } +void vcmptruess(const Xmm& x, const Operand& op) { vcmptruess(x, x, op); } +void vcmpunord_spd(const Xmm& x, const Operand& op) { vcmpunord_spd(x, x, op); } +void vcmpunord_sps(const Xmm& x, const Operand& op) { vcmpunord_sps(x, x, op); } +void vcmpunord_ssd(const Xmm& x, const Operand& op) { vcmpunord_ssd(x, x, op); } +void vcmpunord_sss(const Xmm& x, const Operand& op) { vcmpunord_sss(x, x, op); } +void vcmpunordpd(const Xmm& x, const Operand& op) { vcmpunordpd(x, x, op); } +void vcmpunordps(const Xmm& x, const Operand& op) { vcmpunordps(x, x, op); } +void vcmpunordsd(const Xmm& x, const Operand& op) { vcmpunordsd(x, x, op); } +void vcmpunordss(const Xmm& x, const Operand& op) { vcmpunordss(x, x, op); } +void vcvtsd2ss(const Xmm& x, const Operand& op) { vcvtsd2ss(x, x, op); } +void vcvtsi2sd(const Xmm& x, const Operand& op) { vcvtsi2sd(x, x, op); } +void vcvtsi2ss(const Xmm& x, const Operand& op) { vcvtsi2ss(x, x, op); } +void vcvtss2sd(const Xmm& x, const Operand& op) { vcvtss2sd(x, x, op); } +void vdppd(const Xmm& x, const Operand& op, uint8 imm) { vdppd(x, x, op, imm); } +void vdpps(const Xmm& x, const Operand& op, uint8 imm) { vdpps(x, x, op, imm); } +void vinsertps(const Xmm& x, const Operand& op, uint8 imm) { vinsertps(x, x, op, imm); } +void vmpsadbw(const Xmm& x, const Operand& op, uint8 imm) { vmpsadbw(x, x, op, imm); } +void vpackssdw(const Xmm& x, const Operand& op) { vpackssdw(x, x, op); } +void vpacksswb(const Xmm& x, const Operand& op) { vpacksswb(x, x, op); } +void vpackusdw(const Xmm& x, const Operand& op) { vpackusdw(x, x, op); } +void vpackuswb(const Xmm& x, const Operand& op) { vpackuswb(x, x, op); } +void vpaddb(const Xmm& x, const Operand& op) { vpaddb(x, x, op); } +void vpaddd(const Xmm& x, const Operand& op) { vpaddd(x, x, op); } +void vpaddq(const Xmm& x, const Operand& op) { vpaddq(x, x, op); } +void vpaddsb(const Xmm& x, const Operand& op) { vpaddsb(x, x, op); } +void vpaddsw(const Xmm& x, const Operand& op) { vpaddsw(x, x, op); } +void vpaddusb(const Xmm& x, const Operand& op) { vpaddusb(x, x, op); } +void vpaddusw(const Xmm& x, const Operand& op) { vpaddusw(x, x, op); } +void vpaddw(const Xmm& x, const Operand& op) { vpaddw(x, x, op); } +void vpalignr(const Xmm& x, const Operand& op, uint8 imm) { vpalignr(x, x, op, imm); } +void vpand(const Xmm& x, const Operand& op) { vpand(x, x, op); } +void vpandn(const Xmm& x, const Operand& op) { vpandn(x, x, op); } +void vpavgb(const Xmm& x, const Operand& op) { vpavgb(x, x, op); } +void vpavgw(const Xmm& x, const Operand& op) { vpavgw(x, x, op); } +void vpblendd(const Xmm& x, const Operand& op, uint8 imm) { vpblendd(x, x, op, imm); } +void vpblendvb(const Xmm& x1, const Operand& op, const Xmm& x4) { vpblendvb(x1, x1, op, x4); } +void vpblendw(const Xmm& x, const Operand& op, uint8 imm) { vpblendw(x, x, op, imm); } +void vpclmulqdq(const Xmm& x, const Operand& op, uint8 imm) { vpclmulqdq(x, x, op, imm); } +void vpcmpeqb(const Xmm& x, const Operand& op) { vpcmpeqb(x, x, op); } +void vpcmpeqd(const Xmm& x, const Operand& op) { vpcmpeqd(x, x, op); } +void vpcmpeqq(const Xmm& x, const Operand& op) { vpcmpeqq(x, x, op); } +void vpcmpeqw(const Xmm& x, const Operand& op) { vpcmpeqw(x, x, op); } +void vpcmpgtb(const Xmm& x, const Operand& op) { vpcmpgtb(x, x, op); } +void vpcmpgtd(const Xmm& x, const Operand& op) { vpcmpgtd(x, x, op); } +void vpcmpgtq(const Xmm& x, const Operand& op) { vpcmpgtq(x, x, op); } +void vpcmpgtw(const Xmm& x, const Operand& op) { vpcmpgtw(x, x, op); } +void vphaddd(const Xmm& x, const Operand& op) { vphaddd(x, x, op); } +void vphaddsw(const Xmm& x, const Operand& op) { vphaddsw(x, x, op); } +void vphaddw(const Xmm& x, const Operand& op) { vphaddw(x, x, op); } +void vphsubd(const Xmm& x, const Operand& op) { vphsubd(x, x, op); } +void vphsubsw(const Xmm& x, const Operand& op) { vphsubsw(x, x, op); } +void vphsubw(const Xmm& x, const Operand& op) { vphsubw(x, x, op); } +void vpinsrb(const Xmm& x, const Operand& op, uint8 imm) { vpinsrb(x, x, op, imm); } +void vpinsrd(const Xmm& x, const Operand& op, uint8 imm) { vpinsrd(x, x, op, imm); } +void vpinsrq(const Xmm& x, const Operand& op, uint8 imm) { vpinsrq(x, x, op, imm); } +void vpinsrw(const Xmm& x, const Operand& op, uint8 imm) { vpinsrw(x, x, op, imm); } +void vpmaddubsw(const Xmm& x, const Operand& op) { vpmaddubsw(x, x, op); } +void vpmaddwd(const Xmm& x, const Operand& op) { vpmaddwd(x, x, op); } +void vpmaxsb(const Xmm& x, const Operand& op) { vpmaxsb(x, x, op); } +void vpmaxsd(const Xmm& x, const Operand& op) { vpmaxsd(x, x, op); } +void vpmaxsw(const Xmm& x, const Operand& op) { vpmaxsw(x, x, op); } +void vpmaxub(const Xmm& x, const Operand& op) { vpmaxub(x, x, op); } +void vpmaxud(const Xmm& x, const Operand& op) { vpmaxud(x, x, op); } +void vpmaxuw(const Xmm& x, const Operand& op) { vpmaxuw(x, x, op); } +void vpminsb(const Xmm& x, const Operand& op) { vpminsb(x, x, op); } +void vpminsd(const Xmm& x, const Operand& op) { vpminsd(x, x, op); } +void vpminsw(const Xmm& x, const Operand& op) { vpminsw(x, x, op); } +void vpminub(const Xmm& x, const Operand& op) { vpminub(x, x, op); } +void vpminud(const Xmm& x, const Operand& op) { vpminud(x, x, op); } +void vpminuw(const Xmm& x, const Operand& op) { vpminuw(x, x, op); } +void vpmuldq(const Xmm& x, const Operand& op) { vpmuldq(x, x, op); } +void vpmulhrsw(const Xmm& x, const Operand& op) { vpmulhrsw(x, x, op); } +void vpmulhuw(const Xmm& x, const Operand& op) { vpmulhuw(x, x, op); } +void vpmulhw(const Xmm& x, const Operand& op) { vpmulhw(x, x, op); } +void vpmulld(const Xmm& x, const Operand& op) { vpmulld(x, x, op); } +void vpmullw(const Xmm& x, const Operand& op) { vpmullw(x, x, op); } +void vpmuludq(const Xmm& x, const Operand& op) { vpmuludq(x, x, op); } +void vpor(const Xmm& x, const Operand& op) { vpor(x, x, op); } +void vpsadbw(const Xmm& x, const Operand& op) { vpsadbw(x, x, op); } +void vpsignb(const Xmm& x, const Operand& op) { vpsignb(x, x, op); } +void vpsignd(const Xmm& x, const Operand& op) { vpsignd(x, x, op); } +void vpsignw(const Xmm& x, const Operand& op) { vpsignw(x, x, op); } +void vpslld(const Xmm& x, const Operand& op) { vpslld(x, x, op); } +void vpslld(const Xmm& x, uint8 imm) { vpslld(x, x, imm); } +void vpslldq(const Xmm& x, uint8 imm) { vpslldq(x, x, imm); } +void vpsllq(const Xmm& x, const Operand& op) { vpsllq(x, x, op); } +void vpsllq(const Xmm& x, uint8 imm) { vpsllq(x, x, imm); } +void vpsllw(const Xmm& x, const Operand& op) { vpsllw(x, x, op); } +void vpsllw(const Xmm& x, uint8 imm) { vpsllw(x, x, imm); } +void vpsrad(const Xmm& x, const Operand& op) { vpsrad(x, x, op); } +void vpsrad(const Xmm& x, uint8 imm) { vpsrad(x, x, imm); } +void vpsraw(const Xmm& x, const Operand& op) { vpsraw(x, x, op); } +void vpsraw(const Xmm& x, uint8 imm) { vpsraw(x, x, imm); } +void vpsrld(const Xmm& x, const Operand& op) { vpsrld(x, x, op); } +void vpsrld(const Xmm& x, uint8 imm) { vpsrld(x, x, imm); } +void vpsrldq(const Xmm& x, uint8 imm) { vpsrldq(x, x, imm); } +void vpsrlq(const Xmm& x, const Operand& op) { vpsrlq(x, x, op); } +void vpsrlq(const Xmm& x, uint8 imm) { vpsrlq(x, x, imm); } +void vpsrlw(const Xmm& x, const Operand& op) { vpsrlw(x, x, op); } +void vpsrlw(const Xmm& x, uint8 imm) { vpsrlw(x, x, imm); } +void vpsubb(const Xmm& x, const Operand& op) { vpsubb(x, x, op); } +void vpsubd(const Xmm& x, const Operand& op) { vpsubd(x, x, op); } +void vpsubq(const Xmm& x, const Operand& op) { vpsubq(x, x, op); } +void vpsubsb(const Xmm& x, const Operand& op) { vpsubsb(x, x, op); } +void vpsubsw(const Xmm& x, const Operand& op) { vpsubsw(x, x, op); } +void vpsubusb(const Xmm& x, const Operand& op) { vpsubusb(x, x, op); } +void vpsubusw(const Xmm& x, const Operand& op) { vpsubusw(x, x, op); } +void vpsubw(const Xmm& x, const Operand& op) { vpsubw(x, x, op); } +void vpunpckhbw(const Xmm& x, const Operand& op) { vpunpckhbw(x, x, op); } +void vpunpckhdq(const Xmm& x, const Operand& op) { vpunpckhdq(x, x, op); } +void vpunpckhqdq(const Xmm& x, const Operand& op) { vpunpckhqdq(x, x, op); } +void vpunpckhwd(const Xmm& x, const Operand& op) { vpunpckhwd(x, x, op); } +void vpunpcklbw(const Xmm& x, const Operand& op) { vpunpcklbw(x, x, op); } +void vpunpckldq(const Xmm& x, const Operand& op) { vpunpckldq(x, x, op); } +void vpunpcklqdq(const Xmm& x, const Operand& op) { vpunpcklqdq(x, x, op); } +void vpunpcklwd(const Xmm& x, const Operand& op) { vpunpcklwd(x, x, op); } +void vpxor(const Xmm& x, const Operand& op) { vpxor(x, x, op); } +void vrcpss(const Xmm& x, const Operand& op) { vrcpss(x, x, op); } +void vroundsd(const Xmm& x, const Operand& op, uint8 imm) { vroundsd(x, x, op, imm); } +void vroundss(const Xmm& x, const Operand& op, uint8 imm) { vroundss(x, x, op, imm); } +void vrsqrtss(const Xmm& x, const Operand& op) { vrsqrtss(x, x, op); } +void vshufpd(const Xmm& x, const Operand& op, uint8 imm) { vshufpd(x, x, op, imm); } +void vshufps(const Xmm& x, const Operand& op, uint8 imm) { vshufps(x, x, op, imm); } +void vsqrtsd(const Xmm& x, const Operand& op) { vsqrtsd(x, x, op); } +void vsqrtss(const Xmm& x, const Operand& op) { vsqrtss(x, x, op); } +void vunpckhpd(const Xmm& x, const Operand& op) { vunpckhpd(x, x, op); } +void vunpckhps(const Xmm& x, const Operand& op) { vunpckhps(x, x, op); } +void vunpcklpd(const Xmm& x, const Operand& op) { vunpcklpd(x, x, op); } +void vunpcklps(const Xmm& x, const Operand& op) { vunpcklps(x, x, op); } +#endif +#ifdef XBYAK64 +void jecxz(std::string label) { db(0x67); opJmp(label, T_SHORT, 0xe3, 0, 0); } +void jecxz(const Label& label) { db(0x67); opJmp(label, T_SHORT, 0xe3, 0, 0); } +void jrcxz(std::string label) { opJmp(label, T_SHORT, 0xe3, 0, 0); } +void jrcxz(const Label& label) { opJmp(label, T_SHORT, 0xe3, 0, 0); } +void cdqe() { db(0x48); db(0x98); } +void cqo() { db(0x48); db(0x99); } +void cmpsq() { db(0x48); db(0xA7); } +void movsq() { db(0x48); db(0xA5); } +void scasq() { db(0x48); db(0xAF); } +void stosq() { db(0x48); db(0xAB); } +void cmpxchg16b(const Address& addr) { opModM(addr, Reg64(1), 0x0F, 0xC7); } +void movq(const Reg64& reg, const Mmx& mmx) { if (mmx.isXMM()) db(0x66); opModR(mmx, reg, 0x0F, 0x7E); } +void movq(const Mmx& mmx, const Reg64& reg) { if (mmx.isXMM()) db(0x66); opModR(mmx, reg, 0x0F, 0x6E); } +void movsxd(const Reg64& reg, const Operand& op) { if (!op.isBit(32)) throw Error(ERR_BAD_COMBINATION); opModRM(reg, op, op.isREG(), op.isMEM(), 0x63); } +void pextrq(const Operand& op, const Xmm& xmm, uint8 imm) { if (!op.isREG(64) && !op.isMEM()) throw Error(ERR_BAD_COMBINATION); opGen(Reg64(xmm.getIdx()), op, 0x16, 0x66, 0, imm, 0x3A); } +void pinsrq(const Xmm& xmm, const Operand& op, uint8 imm) { if (!op.isREG(64) && !op.isMEM()) throw Error(ERR_BAD_COMBINATION); opGen(Reg64(xmm.getIdx()), op, 0x22, 0x66, 0, imm, 0x3A); } +void vcvtss2si(const Reg64& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F3 | T_W1 | T_EVEX | T_EW1 | T_ER_X | T_N8, 0x2D); } +void vcvttss2si(const Reg64& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F3 | T_W1 | T_EVEX | T_EW1 | T_SAE_X | T_N8, 0x2C); } +void vcvtsd2si(const Reg64& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F2 | T_W1 | T_EVEX | T_EW1 | T_N4 | T_ER_X, 0x2D); } +void vcvttsd2si(const Reg64& r, const Operand& op) { opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, T_0F | T_F2 | T_W1 | T_EVEX | T_EW1 | T_N4 | T_SAE_X, 0x2C); } +void vmovq(const Xmm& x, const Reg64& r) { opAVX_X_X_XM(x, xm0, Xmm(r.getIdx()), T_66 | T_0F | T_W1 | T_EVEX | T_EW1, 0x6E); } +void vmovq(const Reg64& r, const Xmm& x) { opAVX_X_X_XM(x, xm0, Xmm(r.getIdx()), T_66 | T_0F | T_W1 | T_EVEX | T_EW1, 0x7E); } +#else +void jcxz(std::string label) { db(0x67); opJmp(label, T_SHORT, 0xe3, 0, 0); } +void jcxz(const Label& label) { db(0x67); opJmp(label, T_SHORT, 0xe3, 0, 0); } +void jecxz(std::string label) { opJmp(label, T_SHORT, 0xe3, 0, 0); } +void jecxz(const Label& label) { opJmp(label, T_SHORT, 0xe3, 0, 0); } +void aaa() { db(0x37); } +void aad() { db(0xD5); db(0x0A); } +void aam() { db(0xD4); db(0x0A); } +void aas() { db(0x3F); } +void daa() { db(0x27); } +void das() { db(0x2F); } +void popad() { db(0x61); } +void popfd() { db(0x9D); } +void pusha() { db(0x60); } +void pushad() { db(0x60); } +void pushfd() { db(0x9C); } +void popa() { db(0x61); } +#endif +#ifndef XBYAK_NO_OP_NAMES +void and(const Operand& op1, const Operand& op2) { and_(op1, op2); } +void and(const Operand& op, uint32 imm) { and_(op, imm); } +void or(const Operand& op1, const Operand& op2) { or_(op1, op2); } +void or(const Operand& op, uint32 imm) { or_(op, imm); } +void xor(const Operand& op1, const Operand& op2) { xor_(op1, op2); } +void xor(const Operand& op, uint32 imm) { xor_(op, imm); } +void not(const Operand& op) { not_(op); } +#endif +#ifndef XBYAK_DISABLE_AVX512 +void kaddb(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x4A); } +void kaddd(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W1, 0x4A); } +void kaddq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x4A); } +void kaddw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x4A); } +void kandb(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x41); } +void kandd(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W1, 0x41); } +void kandnb(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x42); } +void kandnd(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W1, 0x42); } +void kandnq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x42); } +void kandnw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x42); } +void kandq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x41); } +void kandw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x41); } +void kmovb(const Address& addr, const Opmask& k) { opVex(k, 0, addr, T_L0 | T_0F | T_66 | T_W0, 0x91); } +void kmovb(const Opmask& k, const Operand& op) { opVex(k, 0, op, T_L0 | T_0F | T_66 | T_W0, 0x90); } +void kmovb(const Opmask& k, const Reg32& r) { opVex(k, 0, r, T_L0 | T_0F | T_66 | T_W0, 0x92); } +void kmovb(const Reg32& r, const Opmask& k) { opVex(r, 0, k, T_L0 | T_0F | T_66 | T_W0, 0x93); } +void kmovd(const Address& addr, const Opmask& k) { opVex(k, 0, addr, T_L0 | T_0F | T_66 | T_W1, 0x91); } +void kmovd(const Opmask& k, const Operand& op) { opVex(k, 0, op, T_L0 | T_0F | T_66 | T_W1, 0x90); } +void kmovd(const Opmask& k, const Reg32& r) { opVex(k, 0, r, T_L0 | T_0F | T_F2 | T_W0, 0x92); } +void kmovd(const Reg32& r, const Opmask& k) { opVex(r, 0, k, T_L0 | T_0F | T_F2 | T_W0, 0x93); } +void kmovq(const Address& addr, const Opmask& k) { opVex(k, 0, addr, T_L0 | T_0F | T_W1, 0x91); } +void kmovq(const Opmask& k, const Operand& op) { opVex(k, 0, op, T_L0 | T_0F | T_W1, 0x90); } +void kmovw(const Address& addr, const Opmask& k) { opVex(k, 0, addr, T_L0 | T_0F | T_W0, 0x91); } +void kmovw(const Opmask& k, const Operand& op) { opVex(k, 0, op, T_L0 | T_0F | T_W0, 0x90); } +void kmovw(const Opmask& k, const Reg32& r) { opVex(k, 0, r, T_L0 | T_0F | T_W0, 0x92); } +void kmovw(const Reg32& r, const Opmask& k) { opVex(r, 0, k, T_L0 | T_0F | T_W0, 0x93); } +void knotb(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_66 | T_W0, 0x44); } +void knotd(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_66 | T_W1, 0x44); } +void knotq(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_W1, 0x44); } +void knotw(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_W0, 0x44); } +void korb(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x45); } +void kord(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W1, 0x45); } +void korq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x45); } +void kortestb(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_66 | T_W0, 0x98); } +void kortestd(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_66 | T_W1, 0x98); } +void kortestq(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_W1, 0x98); } +void kortestw(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_W0, 0x98); } +void korw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x45); } +void kshiftlb(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W0, 0x32, imm); } +void kshiftld(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W0, 0x33, imm); } +void kshiftlq(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W1, 0x33, imm); } +void kshiftlw(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W1, 0x32, imm); } +void kshiftrb(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W0, 0x30, imm); } +void kshiftrd(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W0, 0x31, imm); } +void kshiftrq(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W1, 0x31, imm); } +void kshiftrw(const Opmask& r1, const Opmask& r2, uint8 imm) { opVex(r1, 0, r2, T_66 | T_0F3A | T_W1, 0x30, imm); } +void ktestb(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_66 | T_W0, 0x99); } +void ktestd(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_66 | T_W1, 0x99); } +void ktestq(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_W1, 0x99); } +void ktestw(const Opmask& r1, const Opmask& r2) { opVex(r1, 0, r2, T_0F | T_W0, 0x99); } +void kunpckbw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x4B); } +void kunpckdq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x4B); } +void kunpckwd(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x4B); } +void kxnorb(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x46); } +void kxnord(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W1, 0x46); } +void kxnorq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x46); } +void kxnorw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x46); } +void kxorb(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W0, 0x47); } +void kxord(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_66 | T_W1, 0x47); } +void kxorq(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W1, 0x47); } +void kxorw(const Opmask& r1, const Opmask& r2, const Opmask& r3) { opVex(r1, &r2, r3, T_L1 | T_0F | T_W0, 0x47); } +void v4fmaddps(const Zmm& z1, const Zmm& z2, const Address& addr) { opAVX_X_X_XM(z1, z2, addr, T_0F38 | T_F2 | T_EW0 | T_YMM | T_MUST_EVEX | T_N16, 0x9A); } +void v4fmaddss(const Xmm& x1, const Xmm& x2, const Address& addr) { opAVX_X_X_XM(x1, x2, addr, T_0F38 | T_F2 | T_EW0 | T_MUST_EVEX | T_N16, 0x9B); } +void v4fnmaddps(const Zmm& z1, const Zmm& z2, const Address& addr) { opAVX_X_X_XM(z1, z2, addr, T_0F38 | T_F2 | T_EW0 | T_YMM | T_MUST_EVEX | T_N16, 0xAA); } +void v4fnmaddss(const Xmm& x1, const Xmm& x2, const Address& addr) { opAVX_X_X_XM(x1, x2, addr, T_0F38 | T_F2 | T_EW0 | T_MUST_EVEX | T_N16, 0xAB); } +void valignd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x03, imm); } +void valignq(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x03, imm); } +void vblendmpd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x65); } +void vblendmps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x65); } +void vbroadcastf32x2(const Ymm& y, const Operand& op) { opAVX_X_XM_IMM(y, op, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0 | T_N8, 0x19); } +void vbroadcastf32x4(const Ymm& y, const Address& addr) { opAVX_X_XM_IMM(y, addr, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0 | T_N16, 0x1A); } +void vbroadcastf32x8(const Zmm& y, const Address& addr) { opAVX_X_XM_IMM(y, addr, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0 | T_N32, 0x1B); } +void vbroadcastf64x2(const Ymm& y, const Address& addr) { opAVX_X_XM_IMM(y, addr, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW1 | T_N16, 0x1A); } +void vbroadcastf64x4(const Zmm& y, const Address& addr) { opAVX_X_XM_IMM(y, addr, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW1 | T_N32, 0x1B); } +void vbroadcasti32x2(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0 | T_N8, 0x59); } +void vbroadcasti32x4(const Ymm& y, const Operand& op) { opAVX_X_XM_IMM(y, op, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0 | T_N16, 0x5A); } +void vbroadcasti32x8(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0 | T_N32, 0x5B); } +void vbroadcasti64x2(const Ymm& y, const Operand& op) { opAVX_X_XM_IMM(y, op, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW1 | T_N16, 0x5A); } +void vbroadcasti64x4(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW1 | T_N32, 0x5B); } +void vcmppd(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0xC2, imm); } +void vcmpps(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_0F | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0xC2, imm); } +void vcmpsd(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_N8 | T_F2 | T_0F | T_EW1 | T_SAE_Z | T_MUST_EVEX, 0xC2, imm); } +void vcmpss(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_N4 | T_F3 | T_0F | T_EW0 | T_SAE_Z | T_MUST_EVEX, 0xC2, imm); } +void vcompressb(const Operand& op, const Xmm& x) { opAVX_X_XM_IMM(x, op, T_N1 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x63); } +void vcompresspd(const Operand& op, const Xmm& x) { opAVX_X_XM_IMM(x, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x8A); } +void vcompressps(const Operand& op, const Xmm& x) { opAVX_X_XM_IMM(x, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x8A); } +void vcompressw(const Operand& op, const Xmm& x) { opAVX_X_XM_IMM(x, op, T_N2 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x63); } +void vcvtpd2qq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F | T_EW1 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B64, 0x7B); } +void vcvtpd2udq(const Xmm& x, const Operand& op) { opCvt2(x, op, T_0F | T_YMM | T_MUST_EVEX | T_EW1 | T_B64 | T_ER_Z, 0x79); } +void vcvtpd2uqq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F | T_EW1 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B64, 0x79); } +void vcvtps2qq(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_66 | T_0F | T_YMM | T_MUST_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL | T_ER_Y, 0x7B); } +void vcvtps2udq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_0F | T_EW0 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B32, 0x79); } +void vcvtps2uqq(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_66 | T_0F | T_YMM | T_MUST_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL | T_ER_Y, 0x79); } +void vcvtqq2pd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F3 | T_0F | T_EW1 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B64, 0xE6); } +void vcvtqq2ps(const Xmm& x, const Operand& op) { opCvt2(x, op, T_0F | T_YMM | T_MUST_EVEX | T_EW1 | T_B64 | T_ER_Z, 0x5B); } +void vcvtsd2usi(const Reg32e& r, const Operand& op) { int type = (T_F2 | T_0F | T_MUST_EVEX | T_N8 | T_ER_X) | (r.isREG(64) ? T_EW1 : T_EW0); opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, type, 0x79); } +void vcvtss2usi(const Reg32e& r, const Operand& op) { int type = (T_F3 | T_0F | T_MUST_EVEX | T_N4 | T_ER_X) | (r.isREG(64) ? T_EW1 : T_EW0); opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, type, 0x79); } +void vcvttpd2qq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x7A); } +void vcvttpd2udq(const Xmm& x, const Operand& op) { opCvt2(x, op, T_0F | T_YMM | T_MUST_EVEX | T_EW1 | T_B64 | T_SAE_Z, 0x78); } +void vcvttpd2uqq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x78); } +void vcvttps2qq(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_66 | T_0F | T_YMM | T_MUST_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL | T_SAE_Y, 0x7A); } +void vcvttps2udq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_0F | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x78); } +void vcvttps2uqq(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_66 | T_0F | T_YMM | T_MUST_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL | T_SAE_Y, 0x78); } +void vcvttsd2usi(const Reg32e& r, const Operand& op) { int type = (T_F2 | T_0F | T_MUST_EVEX | T_N8 | T_SAE_X) | (r.isREG(64) ? T_EW1 : T_EW0); opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, type, 0x78); } +void vcvttss2usi(const Reg32e& r, const Operand& op) { int type = (T_F3 | T_0F | T_MUST_EVEX | T_N4 | T_SAE_X) | (r.isREG(64) ? T_EW1 : T_EW0); opAVX_X_X_XM(Xmm(r.getIdx()), xm0, op, type, 0x78); } +void vcvtudq2pd(const Xmm& x, const Operand& op) { checkCvt1(x, op); opVex(x, 0, op, T_F3 | T_0F | T_YMM | T_MUST_EVEX | T_EW0 | T_B32 | T_N8 | T_N_VL, 0x7A); } +void vcvtudq2ps(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F2 | T_0F | T_EW0 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B32, 0x7A); } +void vcvtuqq2pd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F3 | T_0F | T_EW1 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B64, 0x7A); } +void vcvtuqq2ps(const Xmm& x, const Operand& op) { opCvt2(x, op, T_F2 | T_0F | T_YMM | T_MUST_EVEX | T_EW1 | T_B64 | T_ER_Z, 0x7A); } +void vcvtusi2sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opCvt3(x1, x2, op, T_F2 | T_0F | T_MUST_EVEX, T_W1 | T_EW1 | T_ER_X | T_N8, T_W0 | T_EW0 | T_N4, 0x7B); } +void vcvtusi2ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opCvt3(x1, x2, op, T_F3 | T_0F | T_MUST_EVEX | T_ER_X, T_W1 | T_EW1 | T_N8, T_W0 | T_EW0 | T_N4, 0x7B); } +void vdbpsadbw(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x42, imm); } +void vexp2pd(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1 | T_B64 | T_SAE_Z, 0xC8); } +void vexp2ps(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0 | T_B32 | T_SAE_Z, 0xC8); } +void vexpandpd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x88); } +void vexpandps(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x88); } +void vextractf32x4(const Operand& op, const Ymm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::XMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N16 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x19, imm); } +void vextractf32x8(const Operand& op, const Zmm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N32 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x1B, imm); } +void vextractf64x2(const Operand& op, const Ymm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::XMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N16 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x19, imm); } +void vextractf64x4(const Operand& op, const Zmm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N32 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x1B, imm); } +void vextracti32x4(const Operand& op, const Ymm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::XMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N16 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x39, imm); } +void vextracti32x8(const Operand& op, const Zmm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N32 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x3B, imm); } +void vextracti64x2(const Operand& op, const Ymm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::XMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N16 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x39, imm); } +void vextracti64x4(const Operand& op, const Zmm& r, uint8 imm) { if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r, 0, op, T_N32 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x3B, imm); } +void vfixupimmpd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x54, imm); } +void vfixupimmps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x54, imm); } +void vfixupimmsd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F3A | T_EW1 | T_SAE_Z | T_MUST_EVEX, 0x55, imm); } +void vfixupimmss(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F3A | T_EW0 | T_SAE_Z | T_MUST_EVEX, 0x55, imm); } +void vfpclasspd(const Opmask& k, const Operand& op, uint8 imm) { if (!op.isBit(128|256|512)) throw Error(ERR_BAD_MEM_SIZE); Reg x = k; x.setBit(op.getBit()); opVex(x, 0, op, T_66 | T_0F3A | T_MUST_EVEX | T_YMM | T_EW1 | T_B64, 0x66, imm); } +void vfpclassps(const Opmask& k, const Operand& op, uint8 imm) { if (!op.isBit(128|256|512)) throw Error(ERR_BAD_MEM_SIZE); Reg x = k; x.setBit(op.getBit()); opVex(x, 0, op, T_66 | T_0F3A | T_MUST_EVEX | T_YMM | T_EW0 | T_B32, 0x66, imm); } +void vfpclasssd(const Opmask& k, const Operand& op, uint8 imm) { if (!op.isXMEM()) throw Error(ERR_BAD_MEM_SIZE); opVex(k, 0, op, T_66 | T_0F3A | T_MUST_EVEX | T_EW1 | T_N8, 0x67, imm); } +void vfpclassss(const Opmask& k, const Operand& op, uint8 imm) { if (!op.isXMEM()) throw Error(ERR_BAD_MEM_SIZE); opVex(k, 0, op, T_66 | T_0F3A | T_MUST_EVEX | T_EW0 | T_N4, 0x67, imm); } +void vgatherdpd(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_VSIB, 0x92, 1); } +void vgatherdps(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_VSIB, 0x92, 0); } +void vgatherpf0dpd(const Address& addr) { opGatherFetch(addr, zm1, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::YMM); } +void vgatherpf0dps(const Address& addr) { opGatherFetch(addr, zm1, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::ZMM); } +void vgatherpf0qpd(const Address& addr) { opGatherFetch(addr, zm1, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vgatherpf0qps(const Address& addr) { opGatherFetch(addr, zm1, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vgatherpf1dpd(const Address& addr) { opGatherFetch(addr, zm2, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::YMM); } +void vgatherpf1dps(const Address& addr) { opGatherFetch(addr, zm2, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::ZMM); } +void vgatherpf1qpd(const Address& addr) { opGatherFetch(addr, zm2, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vgatherpf1qps(const Address& addr) { opGatherFetch(addr, zm2, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vgatherqpd(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_VSIB, 0x93, 0); } +void vgatherqps(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_VSIB, 0x93, 2); } +void vgetexppd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x42); } +void vgetexpps(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x42); } +void vgetexpsd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_SAE_X | T_MUST_EVEX, 0x43); } +void vgetexpss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_SAE_X | T_MUST_EVEX, 0x43); } +void vgetmantpd(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x26, imm); } +void vgetmantps(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x26, imm); } +void vgetmantsd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F3A | T_EW1 | T_SAE_X | T_MUST_EVEX, 0x27, imm); } +void vgetmantss(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F3A | T_EW0 | T_SAE_X | T_MUST_EVEX, 0x27, imm); } +void vinsertf32x4(const Ymm& r1, const Ymm& r2, const Operand& op, uint8 imm) {if (!(r1.getKind() == r2.getKind() && op.is(Operand::MEM | Operand::XMM))) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N16 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x18, imm); } +void vinsertf32x8(const Zmm& r1, const Zmm& r2, const Operand& op, uint8 imm) {if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N32 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x1A, imm); } +void vinsertf64x2(const Ymm& r1, const Ymm& r2, const Operand& op, uint8 imm) {if (!(r1.getKind() == r2.getKind() && op.is(Operand::MEM | Operand::XMM))) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N16 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x18, imm); } +void vinsertf64x4(const Zmm& r1, const Zmm& r2, const Operand& op, uint8 imm) {if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N32 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x1A, imm); } +void vinserti32x4(const Ymm& r1, const Ymm& r2, const Operand& op, uint8 imm) {if (!(r1.getKind() == r2.getKind() && op.is(Operand::MEM | Operand::XMM))) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N16 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x38, imm); } +void vinserti32x8(const Zmm& r1, const Zmm& r2, const Operand& op, uint8 imm) {if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N32 | T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x3A, imm); } +void vinserti64x2(const Ymm& r1, const Ymm& r2, const Operand& op, uint8 imm) {if (!(r1.getKind() == r2.getKind() && op.is(Operand::MEM | Operand::XMM))) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N16 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x38, imm); } +void vinserti64x4(const Zmm& r1, const Zmm& r2, const Operand& op, uint8 imm) {if (!op.is(Operand::MEM | Operand::YMM)) throw Error(ERR_BAD_COMBINATION); opVex(r1, &r2, op, T_N32 | T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x3A, imm); } +void vmovdqa32(const Address& addr, const Xmm& x) { opAVX_X_XM_IMM(x, addr, T_66 | T_0F | T_EW0 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX | T_M_K, 0x7F); } +void vmovdqa32(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F | T_EW0 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX, 0x6F); } +void vmovdqa64(const Address& addr, const Xmm& x) { opAVX_X_XM_IMM(x, addr, T_66 | T_0F | T_EW1 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX | T_M_K, 0x7F); } +void vmovdqa64(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F | T_EW1 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX, 0x6F); } +void vmovdqu16(const Address& addr, const Xmm& x) { opAVX_X_XM_IMM(x, addr, T_F2 | T_0F | T_EW1 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX | T_M_K, 0x7F); } +void vmovdqu16(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F2 | T_0F | T_EW1 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX, 0x6F); } +void vmovdqu32(const Address& addr, const Xmm& x) { opAVX_X_XM_IMM(x, addr, T_F3 | T_0F | T_EW0 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX | T_M_K, 0x7F); } +void vmovdqu32(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F3 | T_0F | T_EW0 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX, 0x6F); } +void vmovdqu64(const Address& addr, const Xmm& x) { opAVX_X_XM_IMM(x, addr, T_F3 | T_0F | T_EW1 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX | T_M_K, 0x7F); } +void vmovdqu64(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F3 | T_0F | T_EW1 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX, 0x6F); } +void vmovdqu8(const Address& addr, const Xmm& x) { opAVX_X_XM_IMM(x, addr, T_F2 | T_0F | T_EW0 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX | T_M_K, 0x7F); } +void vmovdqu8(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_F2 | T_0F | T_EW0 | T_YMM | T_ER_X | T_ER_Y | T_ER_Z | T_MUST_EVEX, 0x6F); } +void vp4dpwssd(const Zmm& z1, const Zmm& z2, const Address& addr) { opAVX_X_X_XM(z1, z2, addr, T_0F38 | T_F2 | T_EW0 | T_YMM | T_MUST_EVEX | T_N16, 0x52); } +void vp4dpwssds(const Zmm& z1, const Zmm& z2, const Address& addr) { opAVX_X_X_XM(z1, z2, addr, T_0F38 | T_F2 | T_EW0 | T_YMM | T_MUST_EVEX | T_N16, 0x53); } +void vpabsq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_MUST_EVEX | T_EW1 | T_B64 | T_YMM, 0x1F); } +void vpandd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0xDB); } +void vpandnd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0xDF); } +void vpandnq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xDF); } +void vpandq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xDB); } +void vpblendmb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x66); } +void vpblendmd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x64); } +void vpblendmq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x64); } +void vpblendmw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x66); } +void vpbroadcastb(const Xmm& x, const Reg8& r) { opVex(x, 0, r, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x7A); } +void vpbroadcastd(const Xmm& x, const Reg32& r) { opVex(x, 0, r, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x7C); } +void vpbroadcastmb2q(const Xmm& x, const Opmask& k) { opVex(x, 0, k, T_F3 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW1, 0x2A); } +void vpbroadcastmw2d(const Xmm& x, const Opmask& k) { opVex(x, 0, k, T_F3 | T_0F38 | T_YMM | T_MUST_EVEX | T_EW0, 0x3A); } +void vpbroadcastw(const Xmm& x, const Reg16& r) { opVex(x, 0, r, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x7B); } +void vpcmpb(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x3F, imm); } +void vpcmpd(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x1F, imm); } +void vpcmpeqb(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_YMM | T_MUST_EVEX, 0x74); } +void vpcmpeqd(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_YMM | T_MUST_EVEX | T_B32, 0x76); } +void vpcmpeqq(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x29); } +void vpcmpeqw(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_YMM | T_MUST_EVEX, 0x75); } +void vpcmpgtb(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_YMM | T_MUST_EVEX, 0x64); } +void vpcmpgtd(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x66); } +void vpcmpgtq(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x37); } +void vpcmpgtw(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F | T_YMM | T_MUST_EVEX, 0x65); } +void vpcmpq(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x1F, imm); } +void vpcmpub(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX, 0x3E, imm); } +void vpcmpud(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x1E, imm); } +void vpcmpuq(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x1E, imm); } +void vpcmpuw(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x3E, imm); } +void vpcmpw(const Opmask& k, const Xmm& x, const Operand& op, uint8 imm) { opAVX_K_X_XM(k, x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX, 0x3F, imm); } +void vpcompressd(const Operand& op, const Xmm& x) { opAVX_X_XM_IMM(x, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x8B); } +void vpcompressq(const Operand& op, const Xmm& x) { opAVX_X_XM_IMM(x, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x8B); } +void vpconflictd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0xC4); } +void vpconflictq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xC4); } +void vpdpbusd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x50); } +void vpdpbusds(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x51); } +void vpdpwssd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x52); } +void vpdpwssds(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x53); } +void vpermb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x8D); } +void vpermi2b(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x75); } +void vpermi2d(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x76); } +void vpermi2pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x77); } +void vpermi2ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x77); } +void vpermi2q(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x76); } +void vpermi2w(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x75); } +void vpermt2b(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x7D); } +void vpermt2d(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x7E); } +void vpermt2pd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x7F); } +void vpermt2ps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x7F); } +void vpermt2q(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x7E); } +void vpermt2w(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x7D); } +void vpermw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x8D); } +void vpexpandb(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_N1 | T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x62); } +void vpexpandd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x89); } +void vpexpandq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x89); } +void vpexpandw(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_N2 | T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x62); } +void vpgatherdd(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_VSIB, 0x90, 0); } +void vpgatherdq(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_VSIB, 0x90, 1); } +void vpgatherqd(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_VSIB, 0x91, 2); } +void vpgatherqq(const Xmm& x, const Address& addr) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_VSIB, 0x91, 0); } +void vplzcntd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x44); } +void vplzcntq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x44); } +void vpmadd52huq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xB5); } +void vpmadd52luq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xB4); } +void vpmaxsq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x3D); } +void vpmaxuq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x3F); } +void vpminsq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x39); } +void vpminuq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x3B); } +void vpmovb2m(const Opmask& k, const Xmm& x) { opVex(k, 0, x, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0, 0x29); } +void vpmovd2m(const Opmask& k, const Xmm& x) { opVex(k, 0, x, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0, 0x39); } +void vpmovdb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N4 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x31, false); } +void vpmovdw(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x33, true); } +void vpmovm2b(const Xmm& x, const Opmask& k) { opVex(x, 0, k, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0, 0x28); } +void vpmovm2d(const Xmm& x, const Opmask& k) { opVex(x, 0, k, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0, 0x38); } +void vpmovm2q(const Xmm& x, const Opmask& k) { opVex(x, 0, k, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1, 0x38); } +void vpmovm2w(const Xmm& x, const Opmask& k) { opVex(x, 0, k, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1, 0x28); } +void vpmovq2m(const Opmask& k, const Xmm& x) { opVex(k, 0, x, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1, 0x39); } +void vpmovqb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N2 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x32, false); } +void vpmovqd(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x35, true); } +void vpmovqw(const Operand& op, const Xmm& x) { opVmov(op, x, T_N4 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x34, false); } +void vpmovsdb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N4 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x21, false); } +void vpmovsdw(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x23, true); } +void vpmovsqb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N2 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x22, false); } +void vpmovsqd(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x25, true); } +void vpmovsqw(const Operand& op, const Xmm& x) { opVmov(op, x, T_N4 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x24, false); } +void vpmovswb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x20, true); } +void vpmovusdb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N4 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x11, false); } +void vpmovusdw(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x13, true); } +void vpmovusqb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N2 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x12, false); } +void vpmovusqd(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x15, true); } +void vpmovusqw(const Operand& op, const Xmm& x) { opVmov(op, x, T_N4 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x14, false); } +void vpmovuswb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x10, true); } +void vpmovw2m(const Opmask& k, const Xmm& x) { opVex(k, 0, x, T_F3 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1, 0x29); } +void vpmovwb(const Operand& op, const Xmm& x) { opVmov(op, x, T_N8 | T_N_VL | T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x30, true); } +void vpmullq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x40); } +void vpmultishiftqb(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x83); } +void vpopcntb(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x54); } +void vpopcntd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x55); } +void vpopcntq(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x55); } +void vpopcntw(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x54); } +void vpord(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0xEB); } +void vporq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xEB); } +void vprold(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 1), x, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x72, imm); } +void vprolq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 1), x, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x72, imm); } +void vprolvd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x15); } +void vprolvq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x15); } +void vprord(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 0), x, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x72, imm); } +void vprorq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 0), x, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x72, imm); } +void vprorvd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x14); } +void vprorvq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x14); } +void vpscatterdd(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA0, 0); } +void vpscatterdq(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA0, 1); } +void vpscatterqd(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA1, 2); } +void vpscatterqq(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA1, 0); } +void vpshldd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x71, imm); } +void vpshldq(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x71, imm); } +void vpshldvd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x71); } +void vpshldvq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x71); } +void vpshldvw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x70); } +void vpshldw(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x70, imm); } +void vpshrdd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x73, imm); } +void vpshrdq(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x73, imm); } +void vpshrdvd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x73); } +void vpshrdvq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x73); } +void vpshrdvw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x72); } +void vpshrdw(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX, 0x72, imm); } +void vpshufbitqmb(const Opmask& k, const Xmm& x, const Operand& op) { opVex(k, &x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x8F); } +void vpsllvw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x12); } +void vpsraq(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_X_XM(Xmm(x.getKind(), 4), x, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x72, imm); } +void vpsraq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N16 | T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX, 0xE2); } +void vpsravq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x46); } +void vpsravw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x11); } +void vpsrlvw(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x10); } +void vpternlogd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x25, imm); } +void vpternlogq(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x25, imm); } +void vptestmb(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x26); } +void vptestmd(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x27); } +void vptestmq(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x27); } +void vptestmw(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x26); } +void vptestnmb(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x26); } +void vptestnmd(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_F3 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x27); } +void vptestnmq(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_F3 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x27); } +void vptestnmw(const Opmask& k, const Xmm& x, const Operand& op) { opAVX_K_X_XM(k, x, op, T_F3 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x26); } +void vpxord(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0xEF); } +void vpxorq(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0xEF); } +void vrangepd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x50, imm); } +void vrangeps(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x50, imm); } +void vrangesd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F3A | T_EW1 | T_SAE_X | T_MUST_EVEX, 0x51, imm); } +void vrangess(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F3A | T_EW0 | T_SAE_X | T_MUST_EVEX, 0x51, imm); } +void vrcp14pd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x4C); } +void vrcp14ps(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x4C); } +void vrcp14sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX, 0x4D); } +void vrcp14ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX, 0x4D); } +void vrcp28pd(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1 | T_B64 | T_SAE_Z, 0xCA); } +void vrcp28ps(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0 | T_B32 | T_SAE_Z, 0xCA); } +void vrcp28sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_SAE_X | T_MUST_EVEX, 0xCB); } +void vrcp28ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_SAE_X | T_MUST_EVEX, 0xCB); } +void vreducepd(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B64, 0x56, imm); } +void vreduceps(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_SAE_Z | T_MUST_EVEX | T_B32, 0x56, imm); } +void vreducesd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F3A | T_EW1 | T_SAE_X | T_MUST_EVEX, 0x57, imm); } +void vreducess(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F3A | T_EW0 | T_SAE_X | T_MUST_EVEX, 0x57, imm); } +void vrndscalepd(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(x, op, T_66 | T_0F3A | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x09, imm); } +void vrndscaleps(const Xmm& x, const Operand& op, uint8 imm) { opAVX_X_XM_IMM(x, op, T_66 | T_0F3A | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x08, imm); } +void vrndscalesd(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F3A | T_EW1 | T_MUST_EVEX, 0x0B, imm); } +void vrndscaless(const Xmm& x1, const Xmm& x2, const Operand& op, uint8 imm) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F3A | T_EW0 | T_MUST_EVEX, 0x0A, imm); } +void vrsqrt14pd(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_B64, 0x4E); } +void vrsqrt14ps(const Xmm& x, const Operand& op) { opAVX_X_XM_IMM(x, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_B32, 0x4E); } +void vrsqrt14sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x4F); } +void vrsqrt14ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX, 0x4F); } +void vrsqrt28pd(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW1 | T_B64 | T_SAE_Z, 0xCC); } +void vrsqrt28ps(const Zmm& z, const Operand& op) { opAVX_X_XM_IMM(z, op, T_66 | T_0F38 | T_MUST_EVEX | T_YMM | T_EW0 | T_B32 | T_SAE_Z, 0xCC); } +void vrsqrt28sd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_SAE_X | T_MUST_EVEX, 0xCD); } +void vrsqrt28ss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_SAE_X | T_MUST_EVEX, 0xCD); } +void vscalefpd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW1 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B64, 0x2C); } +void vscalefps(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_66 | T_0F38 | T_EW0 | T_YMM | T_ER_Z | T_MUST_EVEX | T_B32, 0x2C); } +void vscalefsd(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N8 | T_66 | T_0F38 | T_EW1 | T_ER_X | T_MUST_EVEX, 0x2D); } +void vscalefss(const Xmm& x1, const Xmm& x2, const Operand& op) { opAVX_X_X_XM(x1, x2, op, T_N4 | T_66 | T_0F38 | T_EW0 | T_ER_X | T_MUST_EVEX, 0x2D); } +void vscatterdpd(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA2, 1); } +void vscatterdps(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA2, 0); } +void vscatterpf0dpd(const Address& addr) { opGatherFetch(addr, zm5, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::YMM); } +void vscatterpf0dps(const Address& addr) { opGatherFetch(addr, zm5, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::ZMM); } +void vscatterpf0qpd(const Address& addr) { opGatherFetch(addr, zm5, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vscatterpf0qps(const Address& addr) { opGatherFetch(addr, zm5, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vscatterpf1dpd(const Address& addr) { opGatherFetch(addr, zm6, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::YMM); } +void vscatterpf1dps(const Address& addr) { opGatherFetch(addr, zm6, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC6, Operand::ZMM); } +void vscatterpf1qpd(const Address& addr) { opGatherFetch(addr, zm6, T_N8 | T_66 | T_0F38 | T_EW1 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vscatterpf1qps(const Address& addr) { opGatherFetch(addr, zm6, T_N4 | T_66 | T_0F38 | T_EW0 | T_MUST_EVEX | T_M_K | T_VSIB, 0xC7, Operand::ZMM); } +void vscatterqpd(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N8 | T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA3, 0); } +void vscatterqps(const Address& addr, const Xmm& x) { opGather2(x, addr, T_N4 | T_66 | T_0F38 | T_EW0 | T_YMM | T_MUST_EVEX | T_M_K | T_VSIB, 0xA3, 2); } +void vshuff32x4(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F3A | T_YMM | T_MUST_EVEX | T_EW0 | T_B32, 0x23, imm); } +void vshuff64x2(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F3A | T_YMM | T_MUST_EVEX | T_EW1 | T_B64, 0x23, imm); } +void vshufi32x4(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F3A | T_YMM | T_MUST_EVEX | T_EW0 | T_B32, 0x43, imm); } +void vshufi64x2(const Ymm& y1, const Ymm& y2, const Operand& op, uint8 imm) { opAVX_X_X_XM(y1, y2, op, T_66 | T_0F3A | T_YMM | T_MUST_EVEX | T_EW1 | T_B64, 0x43, imm); } +#ifdef XBYAK64 +void kmovq(const Opmask& k, const Reg64& r) { opVex(k, 0, r, T_L0 | T_0F | T_F2 | T_W1, 0x92); } +void kmovq(const Reg64& r, const Opmask& k) { opVex(r, 0, k, T_L0 | T_0F | T_F2 | T_W1, 0x93); } +void vpbroadcastq(const Xmm& x, const Reg64& r) { opVex(x, 0, r, T_66 | T_0F38 | T_EW1 | T_YMM | T_MUST_EVEX, 0x7C); } +#endif +#endif diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_util.h b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_util.h new file mode 100644 index 000000000000..8ef076e680ca --- /dev/null +++ b/thirdparty/oidn/mkl-dnn/src/cpu/xbyak/xbyak_util.h @@ -0,0 +1,772 @@ +/******************************************************************************* +* Copyright 2016-2019 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/******************************************************************************* +* Copyright (c) 2007 MITSUNARI Shigeo +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* Neither the name of the copyright owner nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +* THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +#ifndef XBYAK_XBYAK_UTIL_H_ +#define XBYAK_XBYAK_UTIL_H_ + +/** + utility class and functions for Xbyak + Xbyak::util::Clock ; rdtsc timer + Xbyak::util::Cpu ; detect CPU + @note this header is UNDER CONSTRUCTION! +*/ +#include "xbyak.h" + +#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) + #define XBYAK_INTEL_CPU_SPECIFIC +#endif + +#ifdef XBYAK_INTEL_CPU_SPECIFIC +#ifdef _MSC_VER + #if (_MSC_VER < 1400) && defined(XBYAK32) + static inline __declspec(naked) void __cpuid(int[4], int) + { + __asm { + push ebx + push esi + mov eax, dword ptr [esp + 4 * 2 + 8] // eaxIn + cpuid + mov esi, dword ptr [esp + 4 * 2 + 4] // data + mov dword ptr [esi], eax + mov dword ptr [esi + 4], ebx + mov dword ptr [esi + 8], ecx + mov dword ptr [esi + 12], edx + pop esi + pop ebx + ret + } + } + #else + #include // for __cpuid + #endif +#else + #ifndef __GNUC_PREREQ + #define __GNUC_PREREQ(major, minor) ((((__GNUC__) << 16) + (__GNUC_MINOR__)) >= (((major) << 16) + (minor))) + #endif + #if __GNUC_PREREQ(4, 3) && !defined(__APPLE__) + #include + #else + #if defined(__APPLE__) && defined(XBYAK32) // avoid err : can't find a register in class `BREG' while reloading `asm' + #define __cpuid(eaxIn, a, b, c, d) __asm__ __volatile__("pushl %%ebx\ncpuid\nmovl %%ebp, %%esi\npopl %%ebx" : "=a"(a), "=S"(b), "=c"(c), "=d"(d) : "0"(eaxIn)) + #define __cpuid_count(eaxIn, ecxIn, a, b, c, d) __asm__ __volatile__("pushl %%ebx\ncpuid\nmovl %%ebp, %%esi\npopl %%ebx" : "=a"(a), "=S"(b), "=c"(c), "=d"(d) : "0"(eaxIn), "2"(ecxIn)) + #else + #define __cpuid(eaxIn, a, b, c, d) __asm__ __volatile__("cpuid\n" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(eaxIn)) + #define __cpuid_count(eaxIn, ecxIn, a, b, c, d) __asm__ __volatile__("cpuid\n" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(eaxIn), "2"(ecxIn)) + #endif + #endif +#endif +#endif + +namespace Xbyak { namespace util { + +typedef enum { + SmtLevel = 1, + CoreLevel = 2 +} IntelCpuTopologyLevel; + +/** + CPU detection class +*/ +class Cpu { + uint64 type_; + //system topology + bool x2APIC_supported_; + static const size_t maxTopologyLevels = 2; + unsigned int numCores_[maxTopologyLevels]; + + static const unsigned int maxNumberCacheLevels = 10; + unsigned int dataCacheSize_[maxNumberCacheLevels]; + unsigned int coresSharignDataCache_[maxNumberCacheLevels]; + unsigned int dataCacheLevels_; + + unsigned int get32bitAsBE(const char *x) const + { + return x[0] | (x[1] << 8) | (x[2] << 16) | (x[3] << 24); + } + unsigned int mask(int n) const + { + return (1U << n) - 1; + } + void setFamily() + { + unsigned int data[4] = {}; + getCpuid(1, data); + stepping = data[0] & mask(4); + model = (data[0] >> 4) & mask(4); + family = (data[0] >> 8) & mask(4); + // type = (data[0] >> 12) & mask(2); + extModel = (data[0] >> 16) & mask(4); + extFamily = (data[0] >> 20) & mask(8); + if (family == 0x0f) { + displayFamily = family + extFamily; + } else { + displayFamily = family; + } + if (family == 6 || family == 0x0f) { + displayModel = (extModel << 4) + model; + } else { + displayModel = model; + } + } + unsigned int extractBit(unsigned int val, unsigned int base, unsigned int end) + { + return (val >> base) & ((1u << (end - base)) - 1); + } + void setNumCores() + { + if ((type_ & tINTEL) == 0) return; + + unsigned int data[4] = {}; + + /* CAUTION: These numbers are configuration as shipped by Intel. */ + getCpuidEx(0x0, 0, data); + if (data[0] >= 0xB) { + /* + if leaf 11 exists(x2APIC is supported), + we use it to get the number of smt cores and cores on socket + + leaf 0xB can be zeroed-out by a hypervisor + */ + x2APIC_supported_ = true; + for (unsigned int i = 0; i < maxTopologyLevels; i++) { + getCpuidEx(0xB, i, data); + IntelCpuTopologyLevel level = (IntelCpuTopologyLevel)extractBit(data[2], 8, 15); + if (level == SmtLevel || level == CoreLevel) { + numCores_[level - 1] = extractBit(data[1], 0, 15); + } + } + } else { + /* + Failed to deremine num of cores without x2APIC support. + TODO: USE initial APIC ID to determine ncores. + */ + numCores_[SmtLevel - 1] = 0; + numCores_[CoreLevel - 1] = 0; + } + + } + void setCacheHierarchy() + { + if ((type_ & tINTEL) == 0) return; + const unsigned int NO_CACHE = 0; + const unsigned int DATA_CACHE = 1; +// const unsigned int INSTRUCTION_CACHE = 2; + const unsigned int UNIFIED_CACHE = 3; + unsigned int smt_width = 0; + unsigned int logical_cores = 0; + unsigned int data[4] = {}; + + if (x2APIC_supported_) { + smt_width = numCores_[0]; + logical_cores = numCores_[1]; + } + + /* + Assumptions: + the first level of data cache is not shared (which is the + case for every existing architecture) and use this to + determine the SMT width for arch not supporting leaf 11. + when leaf 4 reports a number of core less than numCores_ + on socket reported by leaf 11, then it is a correct number + of cores not an upperbound. + */ + for (int i = 0; dataCacheLevels_ < maxNumberCacheLevels; i++) { + getCpuidEx(0x4, i, data); + unsigned int cacheType = extractBit(data[0], 0, 4); + if (cacheType == NO_CACHE) break; + if (cacheType == DATA_CACHE || cacheType == UNIFIED_CACHE) { + unsigned int actual_logical_cores = extractBit(data[0], 14, 25) + 1; + if (logical_cores != 0) { // true only if leaf 0xB is supported and valid + actual_logical_cores = (std::min)(actual_logical_cores, logical_cores); + } + assert(actual_logical_cores != 0); + dataCacheSize_[dataCacheLevels_] = + (extractBit(data[1], 22, 31) + 1) + * (extractBit(data[1], 12, 21) + 1) + * (extractBit(data[1], 0, 11) + 1) + * (data[2] + 1); + if (cacheType == DATA_CACHE && smt_width == 0) smt_width = actual_logical_cores; + assert(smt_width != 0); + // FIXME: check and fix number of cores sharing L3 cache for different configurations + // (HT-, 2 sockets), (HT-, 1 socket), (HT+, 2 sockets), (HT+, 1 socket) + coresSharignDataCache_[dataCacheLevels_] = (std::max)(actual_logical_cores / smt_width, 1u); + dataCacheLevels_++; + } + } + } + +public: + int model; + int family; + int stepping; + int extModel; + int extFamily; + int displayFamily; // family + extFamily + int displayModel; // model + extModel + + unsigned int getNumCores(IntelCpuTopologyLevel level) { + if (level != SmtLevel && level != CoreLevel) throw Error(ERR_BAD_PARAMETER); + if (!x2APIC_supported_) throw Error(ERR_X2APIC_IS_NOT_SUPPORTED); + return (level == CoreLevel) + ? numCores_[level - 1] / numCores_[SmtLevel - 1] + : numCores_[level - 1]; + } + + unsigned int getDataCacheLevels() const { return dataCacheLevels_; } + unsigned int getCoresSharingDataCache(unsigned int i) const + { + if (i >= dataCacheLevels_) throw Error(ERR_BAD_PARAMETER); + return coresSharignDataCache_[i]; + } + unsigned int getDataCacheSize(unsigned int i) const + { + if (i >= dataCacheLevels_) throw Error(ERR_BAD_PARAMETER); + return dataCacheSize_[i]; + } + + /* + data[] = { eax, ebx, ecx, edx } + */ + static inline void getCpuid(unsigned int eaxIn, unsigned int data[4]) + { +#ifdef XBYAK_INTEL_CPU_SPECIFIC + #ifdef _MSC_VER + __cpuid(reinterpret_cast(data), eaxIn); + #else + __cpuid(eaxIn, data[0], data[1], data[2], data[3]); + #endif +#else + (void)eaxIn; + (void)data; +#endif + } + static inline void getCpuidEx(unsigned int eaxIn, unsigned int ecxIn, unsigned int data[4]) + { +#ifdef XBYAK_INTEL_CPU_SPECIFIC + #ifdef _MSC_VER + __cpuidex(reinterpret_cast(data), eaxIn, ecxIn); + #else + __cpuid_count(eaxIn, ecxIn, data[0], data[1], data[2], data[3]); + #endif +#else + (void)eaxIn; + (void)ecxIn; + (void)data; +#endif + } + static inline uint64 getXfeature() + { +#ifdef XBYAK_INTEL_CPU_SPECIFIC + #ifdef _MSC_VER + return _xgetbv(0); + #else + unsigned int eax, edx; + // xgetvb is not support on gcc 4.2 +// __asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); + __asm__ volatile(".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(0)); + return ((uint64)edx << 32) | eax; + #endif +#else + return 0; +#endif + } + typedef uint64 Type; + + static const Type NONE = 0; + static const Type tMMX = 1 << 0; + static const Type tMMX2 = 1 << 1; + static const Type tCMOV = 1 << 2; + static const Type tSSE = 1 << 3; + static const Type tSSE2 = 1 << 4; + static const Type tSSE3 = 1 << 5; + static const Type tSSSE3 = 1 << 6; + static const Type tSSE41 = 1 << 7; + static const Type tSSE42 = 1 << 8; + static const Type tPOPCNT = 1 << 9; + static const Type tAESNI = 1 << 10; + static const Type tSSE5 = 1 << 11; + static const Type tOSXSAVE = 1 << 12; + static const Type tPCLMULQDQ = 1 << 13; + static const Type tAVX = 1 << 14; + static const Type tFMA = 1 << 15; + + static const Type t3DN = 1 << 16; + static const Type tE3DN = 1 << 17; + static const Type tSSE4a = 1 << 18; + static const Type tRDTSCP = 1 << 19; + static const Type tAVX2 = 1 << 20; + static const Type tBMI1 = 1 << 21; // andn, bextr, blsi, blsmsk, blsr, tzcnt + static const Type tBMI2 = 1 << 22; // bzhi, mulx, pdep, pext, rorx, sarx, shlx, shrx + static const Type tLZCNT = 1 << 23; + + static const Type tINTEL = 1 << 24; + static const Type tAMD = 1 << 25; + + static const Type tENHANCED_REP = 1 << 26; // enhanced rep movsb/stosb + static const Type tRDRAND = 1 << 27; + static const Type tADX = 1 << 28; // adcx, adox + static const Type tRDSEED = 1 << 29; // rdseed + static const Type tSMAP = 1 << 30; // stac + static const Type tHLE = uint64(1) << 31; // xacquire, xrelease, xtest + static const Type tRTM = uint64(1) << 32; // xbegin, xend, xabort + static const Type tF16C = uint64(1) << 33; // vcvtph2ps, vcvtps2ph + static const Type tMOVBE = uint64(1) << 34; // mobve + static const Type tAVX512F = uint64(1) << 35; + static const Type tAVX512DQ = uint64(1) << 36; + static const Type tAVX512_IFMA = uint64(1) << 37; + static const Type tAVX512IFMA = tAVX512_IFMA; + static const Type tAVX512PF = uint64(1) << 38; + static const Type tAVX512ER = uint64(1) << 39; + static const Type tAVX512CD = uint64(1) << 40; + static const Type tAVX512BW = uint64(1) << 41; + static const Type tAVX512VL = uint64(1) << 42; + static const Type tAVX512_VBMI = uint64(1) << 43; + static const Type tAVX512VBMI = tAVX512_VBMI; // changed by Intel's manual + static const Type tAVX512_4VNNIW = uint64(1) << 44; + static const Type tAVX512_4FMAPS = uint64(1) << 45; + static const Type tPREFETCHWT1 = uint64(1) << 46; + static const Type tPREFETCHW = uint64(1) << 47; + static const Type tSHA = uint64(1) << 48; + static const Type tMPX = uint64(1) << 49; + static const Type tAVX512_VBMI2 = uint64(1) << 50; + static const Type tGFNI = uint64(1) << 51; + static const Type tVAES = uint64(1) << 52; + static const Type tVPCLMULQDQ = uint64(1) << 53; + static const Type tAVX512_VNNI = uint64(1) << 54; + static const Type tAVX512_BITALG = uint64(1) << 55; + static const Type tAVX512_VPOPCNTDQ = uint64(1) << 56; + + Cpu() + : type_(NONE) + , x2APIC_supported_(false) + , numCores_() + , dataCacheSize_() + , coresSharignDataCache_() + , dataCacheLevels_(0) + { + unsigned int data[4] = {}; + const unsigned int& EAX = data[0]; + const unsigned int& EBX = data[1]; + const unsigned int& ECX = data[2]; + const unsigned int& EDX = data[3]; + getCpuid(0, data); + const unsigned int maxNum = EAX; + static const char intel[] = "ntel"; + static const char amd[] = "cAMD"; + if (ECX == get32bitAsBE(amd)) { + type_ |= tAMD; + getCpuid(0x80000001, data); + if (EDX & (1U << 31)) type_ |= t3DN; + if (EDX & (1U << 15)) type_ |= tCMOV; + if (EDX & (1U << 30)) type_ |= tE3DN; + if (EDX & (1U << 22)) type_ |= tMMX2; + if (EDX & (1U << 27)) type_ |= tRDTSCP; + } + if (ECX == get32bitAsBE(intel)) { + type_ |= tINTEL; + getCpuid(0x80000001, data); + if (EDX & (1U << 27)) type_ |= tRDTSCP; + if (ECX & (1U << 5)) type_ |= tLZCNT; + if (ECX & (1U << 8)) type_ |= tPREFETCHW; + } + getCpuid(1, data); + if (ECX & (1U << 0)) type_ |= tSSE3; + if (ECX & (1U << 9)) type_ |= tSSSE3; + if (ECX & (1U << 19)) type_ |= tSSE41; + if (ECX & (1U << 20)) type_ |= tSSE42; + if (ECX & (1U << 22)) type_ |= tMOVBE; + if (ECX & (1U << 23)) type_ |= tPOPCNT; + if (ECX & (1U << 25)) type_ |= tAESNI; + if (ECX & (1U << 1)) type_ |= tPCLMULQDQ; + if (ECX & (1U << 27)) type_ |= tOSXSAVE; + if (ECX & (1U << 30)) type_ |= tRDRAND; + if (ECX & (1U << 29)) type_ |= tF16C; + + if (EDX & (1U << 15)) type_ |= tCMOV; + if (EDX & (1U << 23)) type_ |= tMMX; + if (EDX & (1U << 25)) type_ |= tMMX2 | tSSE; + if (EDX & (1U << 26)) type_ |= tSSE2; + + if (type_ & tOSXSAVE) { + // check XFEATURE_ENABLED_MASK[2:1] = '11b' + uint64 bv = getXfeature(); + if ((bv & 6) == 6) { + if (ECX & (1U << 28)) type_ |= tAVX; + if (ECX & (1U << 12)) type_ |= tFMA; + if (((bv >> 5) & 7) == 7) { + getCpuidEx(7, 0, data); + if (EBX & (1U << 16)) type_ |= tAVX512F; + if (type_ & tAVX512F) { + if (EBX & (1U << 17)) type_ |= tAVX512DQ; + if (EBX & (1U << 21)) type_ |= tAVX512_IFMA; + if (EBX & (1U << 26)) type_ |= tAVX512PF; + if (EBX & (1U << 27)) type_ |= tAVX512ER; + if (EBX & (1U << 28)) type_ |= tAVX512CD; + if (EBX & (1U << 30)) type_ |= tAVX512BW; + if (EBX & (1U << 31)) type_ |= tAVX512VL; + if (ECX & (1U << 1)) type_ |= tAVX512_VBMI; + if (ECX & (1U << 6)) type_ |= tAVX512_VBMI2; + if (ECX & (1U << 8)) type_ |= tGFNI; + if (ECX & (1U << 9)) type_ |= tVAES; + if (ECX & (1U << 10)) type_ |= tVPCLMULQDQ; + if (ECX & (1U << 11)) type_ |= tAVX512_VNNI; + if (ECX & (1U << 12)) type_ |= tAVX512_BITALG; + if (ECX & (1U << 14)) type_ |= tAVX512_VPOPCNTDQ; + if (EDX & (1U << 2)) type_ |= tAVX512_4VNNIW; + if (EDX & (1U << 3)) type_ |= tAVX512_4FMAPS; + } + } + } + } + if (maxNum >= 7) { + getCpuidEx(7, 0, data); + if (type_ & tAVX && (EBX & (1U << 5))) type_ |= tAVX2; + if (EBX & (1U << 3)) type_ |= tBMI1; + if (EBX & (1U << 8)) type_ |= tBMI2; + if (EBX & (1U << 9)) type_ |= tENHANCED_REP; + if (EBX & (1U << 18)) type_ |= tRDSEED; + if (EBX & (1U << 19)) type_ |= tADX; + if (EBX & (1U << 20)) type_ |= tSMAP; + if (EBX & (1U << 4)) type_ |= tHLE; + if (EBX & (1U << 11)) type_ |= tRTM; + if (EBX & (1U << 14)) type_ |= tMPX; + if (EBX & (1U << 29)) type_ |= tSHA; + if (ECX & (1U << 0)) type_ |= tPREFETCHWT1; + } + setFamily(); + setNumCores(); + setCacheHierarchy(); + } + void putFamily() const + { + printf("family=%d, model=%X, stepping=%d, extFamily=%d, extModel=%X\n", + family, model, stepping, extFamily, extModel); + printf("display:family=%X, model=%X\n", displayFamily, displayModel); + } + bool has(Type type) const + { + return (type & type_) != 0; + } +}; + +class Clock { +public: + static inline uint64 getRdtsc() + { +#ifdef XBYAK_INTEL_CPU_SPECIFIC + #ifdef _MSC_VER + return __rdtsc(); + #else + unsigned int eax, edx; + __asm__ volatile("rdtsc" : "=a"(eax), "=d"(edx)); + return ((uint64)edx << 32) | eax; + #endif +#else + // TODO: Need another impl of Clock or rdtsc-equivalent for non-x86 cpu + return 0; +#endif + } + Clock() + : clock_(0) + , count_(0) + { + } + void begin() + { + clock_ -= getRdtsc(); + } + void end() + { + clock_ += getRdtsc(); + count_++; + } + int getCount() const { return count_; } + uint64 getClock() const { return clock_; } + void clear() { count_ = 0; clock_ = 0; } +private: + uint64 clock_; + int count_; +}; + +#ifdef XBYAK64 +const int UseRCX = 1 << 6; +const int UseRDX = 1 << 7; + +class Pack { + static const size_t maxTblNum = 15; + const Xbyak::Reg64 *tbl_[maxTblNum]; + size_t n_; +public: + Pack() : tbl_(), n_(0) {} + Pack(const Xbyak::Reg64 *tbl, size_t n) { init(tbl, n); } + Pack(const Pack& rhs) + : n_(rhs.n_) + { + for (size_t i = 0; i < n_; i++) tbl_[i] = rhs.tbl_[i]; + } + Pack& operator=(const Pack& rhs) + { + n_ = rhs.n_; + for (size_t i = 0; i < n_; i++) tbl_[i] = rhs.tbl_[i]; + return *this; + } + Pack(const Xbyak::Reg64& t0) + { n_ = 1; tbl_[0] = &t0; } + Pack(const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 2; tbl_[0] = &t0; tbl_[1] = &t1; } + Pack(const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 3; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; } + Pack(const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 4; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; } + Pack(const Xbyak::Reg64& t4, const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 5; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; tbl_[4] = &t4; } + Pack(const Xbyak::Reg64& t5, const Xbyak::Reg64& t4, const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 6; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; tbl_[4] = &t4; tbl_[5] = &t5; } + Pack(const Xbyak::Reg64& t6, const Xbyak::Reg64& t5, const Xbyak::Reg64& t4, const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 7; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; tbl_[4] = &t4; tbl_[5] = &t5; tbl_[6] = &t6; } + Pack(const Xbyak::Reg64& t7, const Xbyak::Reg64& t6, const Xbyak::Reg64& t5, const Xbyak::Reg64& t4, const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 8; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; tbl_[4] = &t4; tbl_[5] = &t5; tbl_[6] = &t6; tbl_[7] = &t7; } + Pack(const Xbyak::Reg64& t8, const Xbyak::Reg64& t7, const Xbyak::Reg64& t6, const Xbyak::Reg64& t5, const Xbyak::Reg64& t4, const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 9; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; tbl_[4] = &t4; tbl_[5] = &t5; tbl_[6] = &t6; tbl_[7] = &t7; tbl_[8] = &t8; } + Pack(const Xbyak::Reg64& t9, const Xbyak::Reg64& t8, const Xbyak::Reg64& t7, const Xbyak::Reg64& t6, const Xbyak::Reg64& t5, const Xbyak::Reg64& t4, const Xbyak::Reg64& t3, const Xbyak::Reg64& t2, const Xbyak::Reg64& t1, const Xbyak::Reg64& t0) + { n_ = 10; tbl_[0] = &t0; tbl_[1] = &t1; tbl_[2] = &t2; tbl_[3] = &t3; tbl_[4] = &t4; tbl_[5] = &t5; tbl_[6] = &t6; tbl_[7] = &t7; tbl_[8] = &t8; tbl_[9] = &t9; } + Pack& append(const Xbyak::Reg64& t) + { + if (n_ == maxTblNum) { + fprintf(stderr, "ERR Pack::can't append\n"); + throw Error(ERR_BAD_PARAMETER); + } + tbl_[n_++] = &t; + return *this; + } + void init(const Xbyak::Reg64 *tbl, size_t n) + { + if (n > maxTblNum) { + fprintf(stderr, "ERR Pack::init bad n=%d\n", (int)n); + throw Error(ERR_BAD_PARAMETER); + } + n_ = n; + for (size_t i = 0; i < n; i++) { + tbl_[i] = &tbl[i]; + } + } + const Xbyak::Reg64& operator[](size_t n) const + { + if (n >= n_) { + fprintf(stderr, "ERR Pack bad n=%d(%d)\n", (int)n, (int)n_); + throw Error(ERR_BAD_PARAMETER); + } + return *tbl_[n]; + } + size_t size() const { return n_; } + /* + get tbl[pos, pos + num) + */ + Pack sub(size_t pos, size_t num = size_t(-1)) const + { + if (num == size_t(-1)) num = n_ - pos; + if (pos + num > n_) { + fprintf(stderr, "ERR Pack::sub bad pos=%d, num=%d\n", (int)pos, (int)num); + throw Error(ERR_BAD_PARAMETER); + } + Pack pack; + pack.n_ = num; + for (size_t i = 0; i < num; i++) { + pack.tbl_[i] = tbl_[pos + i]; + } + return pack; + } + void put() const + { + for (size_t i = 0; i < n_; i++) { + printf("%s ", tbl_[i]->toString()); + } + printf("\n"); + } +}; + +class StackFrame { +#ifdef XBYAK64_WIN + static const int noSaveNum = 6; + static const int rcxPos = 0; + static const int rdxPos = 1; +#else + static const int noSaveNum = 8; + static const int rcxPos = 3; + static const int rdxPos = 2; +#endif + static const int maxRegNum = 14; // maxRegNum = 16 - rsp - rax + Xbyak::CodeGenerator *code_; + int pNum_; + int tNum_; + bool useRcx_; + bool useRdx_; + int saveNum_; + int P_; + bool makeEpilog_; + Xbyak::Reg64 pTbl_[4]; + Xbyak::Reg64 tTbl_[maxRegNum]; + Pack p_; + Pack t_; + StackFrame(const StackFrame&); + void operator=(const StackFrame&); +public: + const Pack& p; + const Pack& t; + /* + make stack frame + @param sf [in] this + @param pNum [in] num of function parameter(0 <= pNum <= 4) + @param tNum [in] num of temporary register(0 <= tNum, with UseRCX, UseRDX) #{pNum + tNum [+rcx] + [rdx]} <= 14 + @param stackSizeByte [in] local stack size + @param makeEpilog [in] automatically call close() if true + + you can use + rax + gp0, ..., gp(pNum - 1) + gt0, ..., gt(tNum-1) + rcx if tNum & UseRCX + rdx if tNum & UseRDX + rsp[0..stackSizeByte - 1] + */ + StackFrame(Xbyak::CodeGenerator *code, int pNum, int tNum = 0, int stackSizeByte = 0, bool makeEpilog = true) + : code_(code) + , pNum_(pNum) + , tNum_(tNum & ~(UseRCX | UseRDX)) + , useRcx_((tNum & UseRCX) != 0) + , useRdx_((tNum & UseRDX) != 0) + , saveNum_(0) + , P_(0) + , makeEpilog_(makeEpilog) + , p(p_) + , t(t_) + { + using namespace Xbyak; + if (pNum < 0 || pNum > 4) throw Error(ERR_BAD_PNUM); + const int allRegNum = pNum + tNum_ + (useRcx_ ? 1 : 0) + (useRdx_ ? 1 : 0); + if (tNum_ < 0 || allRegNum > maxRegNum) throw Error(ERR_BAD_TNUM); + const Reg64& _rsp = code->rsp; + saveNum_ = (std::max)(0, allRegNum - noSaveNum); + const int *tbl = getOrderTbl() + noSaveNum; + for (int i = 0; i < saveNum_; i++) { + code->push(Reg64(tbl[i])); + } + P_ = (stackSizeByte + 7) / 8; + if (P_ > 0 && (P_ & 1) == (saveNum_ & 1)) P_++; // (rsp % 16) == 8, then increment P_ for 16 byte alignment + P_ *= 8; + if (P_ > 0) code->sub(_rsp, P_); + int pos = 0; + for (int i = 0; i < pNum; i++) { + pTbl_[i] = Xbyak::Reg64(getRegIdx(pos)); + } + for (int i = 0; i < tNum_; i++) { + tTbl_[i] = Xbyak::Reg64(getRegIdx(pos)); + } + if (useRcx_ && rcxPos < pNum) code_->mov(code_->r10, code_->rcx); + if (useRdx_ && rdxPos < pNum) code_->mov(code_->r11, code_->rdx); + p_.init(pTbl_, pNum); + t_.init(tTbl_, tNum_); + } + /* + make epilog manually + @param callRet [in] call ret() if true + */ + void close(bool callRet = true) + { + using namespace Xbyak; + const Reg64& _rsp = code_->rsp; + const int *tbl = getOrderTbl() + noSaveNum; + if (P_ > 0) code_->add(_rsp, P_); + for (int i = 0; i < saveNum_; i++) { + code_->pop(Reg64(tbl[saveNum_ - 1 - i])); + } + + if (callRet) code_->ret(); + } + ~StackFrame() + { + if (!makeEpilog_) return; + try { + close(); + } catch (std::exception& e) { + printf("ERR:StackFrame %s\n", e.what()); + //exit(1); + } + } +private: + const int *getOrderTbl() const + { + using namespace Xbyak; + static const int tbl[] = { +#ifdef XBYAK64_WIN + Operand::RCX, Operand::RDX, Operand::R8, Operand::R9, Operand::R10, Operand::R11, Operand::RDI, Operand::RSI, +#else + Operand::RDI, Operand::RSI, Operand::RDX, Operand::RCX, Operand::R8, Operand::R9, Operand::R10, Operand::R11, +#endif + Operand::RBX, Operand::RBP, Operand::R12, Operand::R13, Operand::R14, Operand::R15 + }; + return &tbl[0]; + } + int getRegIdx(int& pos) const + { + assert(pos < maxRegNum); + using namespace Xbyak; + const int *tbl = getOrderTbl(); + int r = tbl[pos++]; + if (useRcx_) { + if (r == Operand::RCX) { return Operand::R10; } + if (r == Operand::R10) { r = tbl[pos++]; } + } + if (useRdx_) { + if (r == Operand::RDX) { return Operand::R11; } + if (r == Operand::R11) { return tbl[pos++]; } + } + return r; + } +}; +#endif + +} } // end of util +#endif diff --git a/thirdparty/oidn/patches/godot-changes-c58c5216.patch b/thirdparty/oidn/patches/godot-changes-c58c5216.patch new file mode 100644 index 000000000000..c01f00187b99 --- /dev/null +++ b/thirdparty/oidn/patches/godot-changes-c58c5216.patch @@ -0,0 +1,337 @@ +diff --git a/common/platform.h b/common/platform.h +index be14bc7..9373b61 100644 +--- a/common/platform.h ++++ b/common/platform.h +@@ -19,7 +19,7 @@ + #if defined(_WIN32) + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX +- #include ++ #include + #elif defined(__APPLE__) + #include + #endif +@@ -129,4 +129,3 @@ namespace oidn { + std::string getBuildName(); + + } // namespace oidn +- +diff --git a/core/autoencoder.cpp b/core/autoencoder.cpp +index d6915e6..d8da684 100644 +--- a/core/autoencoder.cpp ++++ b/core/autoencoder.cpp +@@ -90,13 +90,19 @@ namespace oidn { + if (!dirty) + return; + +- device->executeTask([&]() +- { ++ // -- GODOT start -- ++ //device->executeTask([&]() ++ //{ ++ // GODOT end -- ++ + if (mayiuse(avx512_common)) + net = buildNet<16>(); + else + net = buildNet<8>(); +- }); ++ ++ // GODOT start -- ++ //}); ++ // GODOT end -- + + dirty = false; + } +@@ -108,9 +114,10 @@ namespace oidn { + + if (!net) + return; +- +- device->executeTask([&]() +- { ++ // -- GODOT start -- ++ //device->executeTask([&]() ++ //{ ++ // -- GODOT end -- + Progress progress; + progress.func = progressFunc; + progress.userPtr = progressUserPtr; +@@ -156,7 +163,9 @@ namespace oidn { + tileIndex++; + } + } +- }); ++ // -- GODOT start -- ++ //}); ++ // -- GODOT end -- + } + + void AutoencoderFilter::computeTileSize() +@@ -464,6 +473,11 @@ namespace oidn { + return std::make_shared(); + } + ++// -- GODOT start -- ++// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. ++#if 0 ++// -- GODOT end -- ++ + // -------------------------------------------------------------------------- + // RTFilter + // -------------------------------------------------------------------------- +@@ -491,6 +505,9 @@ namespace oidn { + weightData.hdr_alb = weights::rt_hdr_alb; + weightData.hdr_alb_nrm = weights::rt_hdr_alb_nrm; + } ++// -- GODOT start -- ++#endif ++// -- GODOT end -- + + // -------------------------------------------------------------------------- + // RTLightmapFilter +diff --git a/core/autoencoder.h b/core/autoencoder.h +index c199052..98b6108 100644 +--- a/core/autoencoder.h ++++ b/core/autoencoder.h +@@ -93,11 +93,18 @@ namespace oidn { + // RTFilter - Generic ray tracing denoiser + // -------------------------------------------------------------------------- + ++// -- GODOT start -- ++// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. ++#if 0 ++// -- GODOT end -- + class RTFilter : public AutoencoderFilter + { + public: + explicit RTFilter(const Ref& device); + }; ++// -- GODOT start -- ++#endif ++// -- GODOT end -- + + // -------------------------------------------------------------------------- + // RTLightmapFilter - Ray traced lightmap denoiser +diff --git a/core/common.h b/core/common.h +index a3a7e8a..a35dd90 100644 +--- a/core/common.h ++++ b/core/common.h +@@ -27,7 +27,9 @@ + #include "common/ref.h" + #include "common/exception.h" + #include "common/thread.h" +-#include "common/tasking.h" ++// -- GODOT start -- ++//#include "common/tasking.h" ++// -- GODOT end -- + #include "math.h" + + namespace oidn { +diff --git a/core/device.cpp b/core/device.cpp +index c455695..3cd658b 100644 +--- a/core/device.cpp ++++ b/core/device.cpp +@@ -29,7 +29,9 @@ namespace oidn { + + Device::~Device() + { +- observer.reset(); ++ // -- GODOT start -- ++ //observer.reset(); ++ // -- GODOT end -- + } + + void Device::setError(Device* device, Error code, const std::string& message) +@@ -141,6 +143,9 @@ namespace oidn { + if (isCommitted()) + throw Exception(Error::InvalidOperation, "device can be committed only once"); + ++ // -- GODOT start -- ++ #if 0 ++ // -- GODOT end -- + // Get the optimal thread affinities + if (setAffinity) + { +@@ -157,7 +162,10 @@ namespace oidn { + // Automatically set the thread affinities + if (affinity) + observer = std::make_shared(affinity, *arena); +- ++ // -- GODOT start -- ++ #endif ++ numThreads = 1; ++ // -- GODOT end -- + dirty = false; + + if (isVerbose()) +@@ -191,9 +199,17 @@ namespace oidn { + + Ref filter; + ++// -- GODOT start -- ++// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. ++#if 0 ++// -- GODOT end -- + if (type == "RT") + filter = makeRef(Ref(this)); +- else if (type == "RTLightmap") ++// -- GODOT start -- ++// Godot doesn't need Raytracing filters. Removing them saves space in the weights files. ++#endif ++ if (type == "RTLightmap") ++// -- GODOT end -- + filter = makeRef(Ref(this)); + else + throw Exception(Error::InvalidArgument, "unknown filter type"); +@@ -210,11 +226,12 @@ namespace oidn { + std::cout << " Build : " << getBuildName() << std::endl; + std::cout << " Platform: " << getPlatformName() << std::endl; + +- std::cout << " Tasking :"; +- std::cout << " TBB" << TBB_VERSION_MAJOR << "." << TBB_VERSION_MINOR; +- std::cout << " TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << tbb::TBB_runtime_interface_version(); +- std::cout << std::endl; +- ++// -- GODOT start -- ++// std::cout << " Tasking :"; ++// std::cout << " TBB" << TBB_VERSION_MAJOR << "." << TBB_VERSION_MINOR; ++// std::cout << " TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << tbb::TBB_runtime_interface_version(); ++// std::cout << std::endl; ++// -- GODOT end -- + std::cout << std::endl; + } + +diff --git a/core/device.h b/core/device.h +index c2df714..d9cfd85 100644 +--- a/core/device.h ++++ b/core/device.h +@@ -41,10 +41,12 @@ namespace oidn { + ErrorFunction errorFunc = nullptr; + void* errorUserPtr = nullptr; + +- // Tasking +- std::shared_ptr arena; +- std::shared_ptr observer; +- std::shared_ptr affinity; ++// -- GODOT start -- ++// // Tasking ++// std::shared_ptr arena; ++// std::shared_ptr observer; ++// std::shared_ptr affinity; ++// -- GODOT end -- + + // Parameters + int numThreads = 0; // autodetect by default +@@ -66,17 +68,19 @@ namespace oidn { + + void commit(); + +- template +- void executeTask(F& f) +- { +- arena->execute(f); +- } ++// -- GODOT start -- ++// template ++// void executeTask(F& f) ++// { ++// arena->execute(f); ++// } + +- template +- void executeTask(const F& f) +- { +- arena->execute(f); +- } ++// template ++// void executeTask(const F& f) ++// { ++// arena->execute(f); ++// } ++// -- GODOT end -- + + Ref newBuffer(size_t byteSize); + Ref newBuffer(void* ptr, size_t byteSize); +@@ -86,7 +90,10 @@ namespace oidn { + __forceinline std::mutex& getMutex() { return mutex; } + + private: +- bool isCommitted() const { return bool(arena); } ++// -- GODOT start -- ++ //bool isCommitted() const { return bool(arena); } ++ bool isCommitted() const { return false; } ++// -- GODOT end -- + void checkCommitted(); + + void print(); +diff --git a/core/network.cpp b/core/network.cpp +index 8c2de09..ed8328c 100644 +--- a/core/network.cpp ++++ b/core/network.cpp +@@ -17,6 +17,9 @@ + #include "upsample.h" + #include "weights_reorder.h" + #include "network.h" ++// -- GODOT start -- ++#include ++// -- GODOT end -- + + namespace oidn { + +diff --git a/core/transfer_function.cpp b/core/transfer_function.cpp +index 601f814..ce5deca 100644 +--- a/core/transfer_function.cpp ++++ b/core/transfer_function.cpp +@@ -38,16 +38,24 @@ namespace oidn { + // Compute the average log luminance of the downsampled image + using Sum = std::pair; + +- Sum sum = +- tbb::parallel_reduce( +- tbb::blocked_range2d(0, HK, 0, WK), +- Sum(0.f, 0), +- [&](const tbb::blocked_range2d& r, Sum sum) -> Sum ++ // -- GODOT start -- ++ // Sum sum = ++ // tbb::parallel_reduce( ++ // tbb::blocked_range2d(0, HK, 0, WK), ++ // Sum(0.f, 0), ++ // [&](const tbb::blocked_range2d& r, Sum sum) -> Sum ++ // { ++ // // Iterate over blocks ++ // for (int i = r.rows().begin(); i != r.rows().end(); ++i) ++ // { ++ // for (int j = r.cols().begin(); j != r.cols().end(); ++j) ++ // { ++ ++ Sum sum = Sum(0.0f, 0); ++ ++ for (int i = 0; i != HK; ++i) + { +- // Iterate over blocks +- for (int i = r.rows().begin(); i != r.rows().end(); ++i) +- { +- for (int j = r.cols().begin(); j != r.cols().end(); ++j) ++ for (int j = 0; j != WK; ++j) + { + // Compute the average luminance in the current block + const int beginH = int(ptrdiff_t(i) * H / HK); +@@ -82,11 +90,12 @@ namespace oidn { + } + } + +- return sum; +- }, +- [](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); }, +- tbb::static_partitioner() +- ); ++ // return sum; ++ // }, ++ // [](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); }, ++ // tbb::static_partitioner() ++ // ); ++ // -- GODOT end -- + + return (sum.second > 0) ? (key / exp2(sum.first / float(sum.second))) : 1.f; + } diff --git a/thirdparty/oidn/patches/mkl-dnn-fix-vs2017-build.patch b/thirdparty/oidn/patches/mkl-dnn-fix-vs2017-build.patch new file mode 100644 index 000000000000..50d94ebffa85 --- /dev/null +++ b/thirdparty/oidn/patches/mkl-dnn-fix-vs2017-build.patch @@ -0,0 +1,45 @@ +Rediffed by @akien-mga to match oidn 1.1.0 source. + +From 1e42e6db81e1a5270ecc0191c5385ce7e7d978e9 Mon Sep 17 00:00:00 2001 +From: Jeremy Wong +Date: Wed, 11 Sep 2019 04:46:53 +0800 +Subject: [PATCH] src: initialize members in some structures to prevent compile + errors with VS2017 + +addresses "error C3615: constexpr function '...' cannot result in a constant expression" with VS2017 +--- + src/cpu/rnn/rnn_reorders.hpp | 2 +- + src/cpu/simple_concat.hpp | 6 +++--- + src/cpu/simple_sum.hpp | 2 +- + 3 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp +index 597c63e3f8..ae1551390a 100644 +--- a/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp ++++ b/thirdparty/oidn/mkl-dnn/src/cpu/rnn/rnn_reorders.hpp +@@ -131,7 +131,7 @@ struct rnn_weights_reorder_t : public cpu_primitive_t { + return status::success; + } + +- format_tag_t itag_; ++ format_tag_t itag_ = mkldnn_format_tag_undef; + + private: + void init_scratchpad() { +diff --git a/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp b/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp +index 5177275452..057cc3c4c7 100644 +--- a/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp ++++ b/thirdparty/oidn/mkl-dnn/src/cpu/simple_concat.hpp +@@ -96,9 +96,9 @@ struct simple_concat_t: public cpu_primitive_t { + return status::success; + } + +- int perm_[MKLDNN_MAX_NDIMS]; +- int iperm_[MKLDNN_MAX_NDIMS]; +- dims_t blocks_; ++ int perm_[MKLDNN_MAX_NDIMS] {}; ++ int iperm_[MKLDNN_MAX_NDIMS] {}; ++ dims_t blocks_ {}; + + dim_t nelems_to_concat(const memory_desc_wrapper &data_d) const { + const int ndims = data_d.ndims(); diff --git a/thirdparty/oidn/weights/LICENSE.txt b/thirdparty/oidn/weights/LICENSE.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/thirdparty/oidn/weights/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/thirdparty/oidn/weights/rtlightmap_hdr.tza b/thirdparty/oidn/weights/rtlightmap_hdr.tza new file mode 100644 index 0000000000000000000000000000000000000000..12459a33bc889cea2c1bcd628d48981eac8d7de4 GIT binary patch literal 5660131 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

8uGoi4m|#Y7QdY~3x6#P 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%0AYXp0*8$_+docRtZVJ_v9aHO&o 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 z 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⅊%t?Ipt)@nM;y}gYp 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 N`nV9qNZEy|^Ln&P47J|Ok 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 z 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

    7md}GVmc9_h^R-huck1 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

    8a>M2-6 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

    gTgr5WJojZn$ntcSVPQMXYCOxF3 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

      Sq7v4ugnvExnSm=hisV$t5f)DOc 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�%&_9?Oscjkn4WF%r%878 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%0D~F)?X5I&xQLm>EbL5oe%*xCNlW7QXeJ$ieR|i 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

    1. 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

      UGipDft7>FWN!fXbh@cD?+W&aPVJu0dFih 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

      Z*WS@iOp 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 z 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 ?pWId5bxQ1j%~c(%Bn>VBRNp<@iPs$z`#B0b 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
      mg?6TRp-|#`nCg2K=YCv|&$GlJPWRWf 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
      7^A1rWab1`gbk0v&xLMkBi$H+=NKKh{ejzwH(2 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 fk0#=W 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

      e@;{oB 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%0Aim6gS_DH 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¨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 {WO^f$W_6!woo5CA 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%0DcP&M;chNL2Bmy^{O+dq5Yq+}l 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 Z*^kLS>~;sk8W8$wh{ccRAt1>XE~IJZM5q1;)J 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%0Ar0=^6 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

      d(#$R3|4t8XzN|(suU4|N;2nG6r4%N2y(b+{ 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⋙A?baA)@pIvcJc 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%0A96xEs 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 _D)gW|8_^xhNs&%4xME({0xN_%P3h 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 z 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≺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

      JNvX_o)EsHTW!&2WgMEvs#k+R;Hp5@Yb{|E9 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

      LgJ$hHrw$+xzdROXc=H-F3l+PUAE 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⁣g<^Y)r~t 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вG~xbWjOJXcL& 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

      $-a)eRyvX%PP3BkEz}3#N;D20s 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 z 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 QpxYUj)A+we)O7cGZdsu@^TGWf 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

      vf~(>+m3VVp&nm~We+L+1Isj%k1>A1ntQh4US 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≧2-o_g+pGcg3927TvSv# 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 Date: Sat, 19 Dec 2020 14:50:20 +0100 Subject: [PATCH 2/7] Add Embree thirdparty library --- modules/raycast/SCsub | 93 + modules/raycast/config.py | 13 + modules/raycast/godot_update_embree.py | 259 +++ modules/raycast/lightmap_raycaster.cpp | 196 ++ modules/raycast/lightmap_raycaster.h | 72 + modules/raycast/register_types.cpp | 39 + modules/raycast/register_types.h | 32 + thirdparty/README.md | 16 + .../common/algorithms/parallel_any_of.h | 55 + .../common/algorithms/parallel_filter.cpp | 56 + .../common/algorithms/parallel_filter.h | 93 + .../embree/common/algorithms/parallel_for.cpp | 48 + .../embree/common/algorithms/parallel_for.h | 156 ++ .../common/algorithms/parallel_for_for.cpp | 63 + .../common/algorithms/parallel_for_for.h | 149 ++ .../parallel_for_for_prefix_sum.cpp | 85 + .../algorithms/parallel_for_for_prefix_sum.h | 112 ++ .../embree/common/algorithms/parallel_map.cpp | 47 + .../embree/common/algorithms/parallel_map.h | 85 + .../common/algorithms/parallel_partition.cpp | 53 + .../common/algorithms/parallel_partition.h | 283 +++ .../common/algorithms/parallel_prefix_sum.cpp | 48 + .../common/algorithms/parallel_prefix_sum.h | 85 + .../common/algorithms/parallel_reduce.cpp | 49 + .../common/algorithms/parallel_reduce.h | 146 ++ .../embree/common/algorithms/parallel_set.cpp | 43 + .../embree/common/algorithms/parallel_set.h | 52 + .../common/algorithms/parallel_sort.cpp | 50 + .../embree/common/algorithms/parallel_sort.h | 454 +++++ thirdparty/embree/common/lexers/parsestream.h | 101 + thirdparty/embree/common/lexers/stream.h | 215 ++ .../embree/common/lexers/streamfilters.h | 39 + .../embree/common/lexers/stringstream.cpp | 48 + .../embree/common/lexers/stringstream.h | 29 + .../embree/common/lexers/tokenstream.cpp | 181 ++ thirdparty/embree/common/lexers/tokenstream.h | 164 ++ thirdparty/embree/common/math/affinespace.h | 361 ++++ thirdparty/embree/common/math/bbox.h | 331 ++++ thirdparty/embree/common/math/col3.h | 47 + thirdparty/embree/common/math/col4.h | 47 + thirdparty/embree/common/math/color.h | 241 +++ thirdparty/embree/common/math/constants.cpp | 27 + thirdparty/embree/common/math/constants.h | 197 ++ thirdparty/embree/common/math/interval.h | 161 ++ thirdparty/embree/common/math/lbbox.h | 289 +++ thirdparty/embree/common/math/linearspace2.h | 148 ++ thirdparty/embree/common/math/linearspace3.h | 213 ++ thirdparty/embree/common/math/math.h | 352 ++++ thirdparty/embree/common/math/obbox.h | 39 + thirdparty/embree/common/math/quaternion.h | 254 +++ thirdparty/embree/common/math/range.h | 137 ++ .../embree/common/math/transcendental.h | 525 +++++ thirdparty/embree/common/math/vec2.h | 235 +++ thirdparty/embree/common/math/vec2fa.h | 297 +++ thirdparty/embree/common/math/vec3.h | 350 ++++ thirdparty/embree/common/math/vec3ba.h | 120 ++ thirdparty/embree/common/math/vec3fa.h | 723 +++++++ thirdparty/embree/common/math/vec3ia.h | 186 ++ thirdparty/embree/common/math/vec4.h | 258 +++ thirdparty/embree/common/simd/avx.h | 34 + thirdparty/embree/common/simd/avx512.h | 41 + thirdparty/embree/common/simd/simd.h | 110 ++ thirdparty/embree/common/simd/sse.cpp | 34 + thirdparty/embree/common/simd/sse.h | 35 + thirdparty/embree/common/simd/varying.h | 132 ++ thirdparty/embree/common/simd/vboold4_avx.h | 155 ++ .../embree/common/simd/vboold4_avx512.h | 140 ++ .../embree/common/simd/vboold8_avx512.h | 148 ++ .../embree/common/simd/vboolf16_avx512.h | 150 ++ .../embree/common/simd/vboolf4_avx512.h | 143 ++ thirdparty/embree/common/simd/vboolf4_sse2.h | 173 ++ thirdparty/embree/common/simd/vboolf8_avx.h | 186 ++ .../embree/common/simd/vboolf8_avx512.h | 143 ++ thirdparty/embree/common/simd/vdouble4_avx.h | 317 +++ .../embree/common/simd/vdouble8_avx512.h | 356 ++++ .../embree/common/simd/vfloat16_avx512.h | 771 ++++++++ thirdparty/embree/common/simd/vfloat4_sse2.h | 708 +++++++ thirdparty/embree/common/simd/vfloat8_avx.h | 780 ++++++++ thirdparty/embree/common/simd/vint16_avx512.h | 490 +++++ thirdparty/embree/common/simd/vint4_sse2.h | 583 ++++++ thirdparty/embree/common/simd/vint8_avx.h | 459 +++++ thirdparty/embree/common/simd/vint8_avx2.h | 505 +++++ thirdparty/embree/common/simd/vllong4_avx2.h | 358 ++++ .../embree/common/simd/vllong8_avx512.h | 381 ++++ .../embree/common/simd/vuint16_avx512.h | 443 +++++ thirdparty/embree/common/simd/vuint4_sse2.h | 428 ++++ thirdparty/embree/common/simd/vuint8_avx.h | 375 ++++ thirdparty/embree/common/simd/vuint8_avx2.h | 434 ++++ thirdparty/embree/common/sys/alloc.cpp | 306 +++ thirdparty/embree/common/sys/alloc.h | 164 ++ thirdparty/embree/common/sys/array.h | 222 +++ thirdparty/embree/common/sys/atomic.h | 59 + thirdparty/embree/common/sys/barrier.cpp | 289 +++ thirdparty/embree/common/sys/barrier.h | 112 ++ thirdparty/embree/common/sys/condition.cpp | 81 + thirdparty/embree/common/sys/condition.h | 31 + thirdparty/embree/common/sys/filename.cpp | 138 ++ thirdparty/embree/common/sys/filename.h | 81 + thirdparty/embree/common/sys/intrinsics.h | 478 +++++ thirdparty/embree/common/sys/library.cpp | 85 + thirdparty/embree/common/sys/library.h | 21 + thirdparty/embree/common/sys/mutex.cpp | 57 + thirdparty/embree/common/sys/mutex.h | 98 + thirdparty/embree/common/sys/platform.h | 374 ++++ thirdparty/embree/common/sys/ref.h | 122 ++ thirdparty/embree/common/sys/regression.cpp | 30 + thirdparty/embree/common/sys/regression.h | 25 + thirdparty/embree/common/sys/string.cpp | 42 + thirdparty/embree/common/sys/string.h | 37 + thirdparty/embree/common/sys/sysinfo.cpp | 629 ++++++ thirdparty/embree/common/sys/sysinfo.h | 182 ++ thirdparty/embree/common/sys/thread.cpp | 425 ++++ thirdparty/embree/common/sys/thread.h | 49 + thirdparty/embree/common/sys/vector.h | 242 +++ .../embree/common/tasking/taskscheduler.h | 15 + .../common/tasking/taskschedulerinternal.cpp | 408 ++++ .../common/tasking/taskschedulerinternal.h | 376 ++++ .../embree/common/tasking/taskschedulerppl.h | 46 + .../embree/common/tasking/taskschedulertbb.h | 73 + thirdparty/embree/include/embree3/rtcore.h | 14 + .../embree/include/embree3/rtcore_buffer.h | 51 + .../embree/include/embree3/rtcore_builder.h | 125 ++ .../embree/include/embree3/rtcore_common.h | 326 +++ .../embree/include/embree3/rtcore_config.h | 57 + .../embree/include/embree3/rtcore_device.h | 87 + .../embree/include/embree3/rtcore_geometry.h | 383 ++++ .../include/embree3/rtcore_quaternion.h | 101 + .../embree/include/embree3/rtcore_ray.h | 378 ++++ .../embree/include/embree3/rtcore_scene.h | 160 ++ .../kernels/builders/bvh_builder_hair.h | 411 ++++ .../kernels/builders/bvh_builder_morton.h | 501 +++++ .../kernels/builders/bvh_builder_msmblur.h | 692 +++++++ .../builders/bvh_builder_msmblur_hair.h | 526 +++++ .../embree/kernels/builders/bvh_builder_sah.h | 669 +++++++ .../kernels/builders/heuristic_binning.h | 972 +++++++++ .../heuristic_binning_array_aligned.h | 205 ++ .../heuristic_binning_array_unaligned.h | 302 +++ .../builders/heuristic_openmerge_array.h | 443 +++++ .../kernels/builders/heuristic_spatial.h | 414 ++++ .../builders/heuristic_spatial_array.h | 552 ++++++ .../kernels/builders/heuristic_strand_array.h | 188 ++ .../builders/heuristic_timesplit_array.h | 237 +++ thirdparty/embree/kernels/builders/priminfo.h | 362 ++++ .../embree/kernels/builders/primrefgen.cpp | 244 +++ .../embree/kernels/builders/primrefgen.h | 28 + .../kernels/builders/primrefgen_presplit.h | 371 ++++ thirdparty/embree/kernels/builders/splitter.h | 169 ++ thirdparty/embree/kernels/bvh/bvh.cpp | 190 ++ thirdparty/embree/kernels/bvh/bvh.h | 235 +++ .../embree/kernels/bvh/bvh4_factory.cpp | 1325 +++++++++++++ thirdparty/embree/kernels/bvh/bvh4_factory.h | 316 +++ .../embree/kernels/bvh/bvh8_factory.cpp | 1165 +++++++++++ thirdparty/embree/kernels/bvh/bvh8_factory.h | 280 +++ thirdparty/embree/kernels/bvh/bvh_builder.cpp | 60 + thirdparty/embree/kernels/bvh/bvh_builder.h | 114 ++ .../embree/kernels/bvh/bvh_builder_morton.cpp | 531 +++++ .../embree/kernels/bvh/bvh_builder_sah.cpp | 640 ++++++ .../embree/kernels/bvh/bvh_builder_sah_mb.cpp | 705 +++++++ .../kernels/bvh/bvh_builder_sah_spatial.cpp | 201 ++ .../kernels/bvh/bvh_builder_twolevel.cpp | 377 ++++ .../embree/kernels/bvh/bvh_builder_twolevel.h | 263 +++ .../bvh/bvh_builder_twolevel_internal.h | 267 +++ .../embree/kernels/bvh/bvh_collider.cpp | 375 ++++ thirdparty/embree/kernels/bvh/bvh_collider.h | 72 + thirdparty/embree/kernels/bvh/bvh_factory.h | 21 + .../embree/kernels/bvh/bvh_intersector1.cpp | 330 ++++ .../embree/kernels/bvh/bvh_intersector1.h | 37 + .../kernels/bvh/bvh_intersector1_bvh4.cpp | 61 + .../kernels/bvh/bvh_intersector_hybrid.h | 61 + .../kernels/bvh/bvh_intersector_stream.h | 284 +++ .../bvh/bvh_intersector_stream_filters.h | 41 + thirdparty/embree/kernels/bvh/bvh_node_aabb.h | 213 ++ .../embree/kernels/bvh/bvh_node_aabb_mb.h | 247 +++ .../embree/kernels/bvh/bvh_node_aabb_mb4d.h | 107 + thirdparty/embree/kernels/bvh/bvh_node_base.h | 43 + thirdparty/embree/kernels/bvh/bvh_node_obb.h | 98 + .../embree/kernels/bvh/bvh_node_obb_mb.h | 90 + .../embree/kernels/bvh/bvh_node_qaabb.h | 265 +++ thirdparty/embree/kernels/bvh/bvh_node_ref.h | 242 +++ thirdparty/embree/kernels/bvh/bvh_refit.cpp | 247 +++ thirdparty/embree/kernels/bvh/bvh_refit.h | 95 + thirdparty/embree/kernels/bvh/bvh_rotate.cpp | 127 ++ thirdparty/embree/kernels/bvh/bvh_rotate.h | 37 + .../embree/kernels/bvh/bvh_statistics.cpp | 165 ++ .../embree/kernels/bvh/bvh_statistics.h | 285 +++ .../embree/kernels/bvh/bvh_traverser1.h | 676 +++++++ .../embree/kernels/bvh/bvh_traverser_stream.h | 154 ++ .../embree/kernels/bvh/node_intersector.h | 31 + .../embree/kernels/bvh/node_intersector1.h | 1695 ++++++++++++++++ .../kernels/bvh/node_intersector_frustum.h | 253 +++ .../kernels/bvh/node_intersector_packet.h | 805 ++++++++ .../bvh/node_intersector_packet_stream.h | 189 ++ thirdparty/embree/kernels/common/accel.h | 556 ++++++ .../embree/kernels/common/accelinstance.h | 41 + thirdparty/embree/kernels/common/acceln.cpp | 232 +++ thirdparty/embree/kernels/common/acceln.h | 49 + thirdparty/embree/kernels/common/accelset.cpp | 17 + thirdparty/embree/kernels/common/accelset.h | 248 +++ thirdparty/embree/kernels/common/alloc.cpp | 79 + thirdparty/embree/kernels/common/alloc.h | 962 +++++++++ thirdparty/embree/kernels/common/buffer.h | 263 +++ thirdparty/embree/kernels/common/builder.h | 60 + thirdparty/embree/kernels/common/context.h | 131 ++ thirdparty/embree/kernels/common/default.h | 268 +++ thirdparty/embree/kernels/common/device.cpp | 560 ++++++ thirdparty/embree/kernels/common/device.h | 85 + thirdparty/embree/kernels/common/geometry.cpp | 259 +++ thirdparty/embree/kernels/common/geometry.h | 582 ++++++ thirdparty/embree/kernels/common/hit.h | 114 ++ .../embree/kernels/common/instance_stack.h | 199 ++ thirdparty/embree/kernels/common/isa.h | 271 +++ .../embree/kernels/common/motion_derivative.h | 325 +++ .../embree/kernels/common/point_query.h | 136 ++ thirdparty/embree/kernels/common/primref.h | 138 ++ thirdparty/embree/kernels/common/primref_mb.h | 262 +++ thirdparty/embree/kernels/common/profile.h | 159 ++ thirdparty/embree/kernels/common/ray.h | 1517 ++++++++++++++ thirdparty/embree/kernels/common/rtcore.cpp | 1760 +++++++++++++++++ thirdparty/embree/kernels/common/rtcore.h | 126 ++ .../embree/kernels/common/rtcore_builder.cpp | 442 +++++ thirdparty/embree/kernels/common/scene.cpp | 953 +++++++++ thirdparty/embree/kernels/common/scene.h | 390 ++++ .../embree/kernels/common/scene_curves.h | 341 ++++ .../embree/kernels/common/scene_grid_mesh.h | 215 ++ .../embree/kernels/common/scene_instance.h | 272 +++ .../kernels/common/scene_line_segments.h | 307 +++ .../embree/kernels/common/scene_points.h | 282 +++ .../embree/kernels/common/scene_quad_mesh.h | 277 +++ .../embree/kernels/common/scene_subdiv_mesh.h | 326 +++ .../kernels/common/scene_triangle_mesh.cpp | 243 +++ .../kernels/common/scene_triangle_mesh.h | 264 +++ .../kernels/common/scene_user_geometry.h | 77 + thirdparty/embree/kernels/common/stack_item.h | 125 ++ thirdparty/embree/kernels/common/stat.cpp | 128 ++ thirdparty/embree/kernels/common/stat.h | 116 ++ thirdparty/embree/kernels/common/state.cpp | 528 +++++ thirdparty/embree/kernels/common/state.h | 197 ++ thirdparty/embree/kernels/common/vector.h | 76 + thirdparty/embree/kernels/config.h | 76 + thirdparty/embree/kernels/geometry/cone.h | 321 +++ .../kernels/geometry/coneline_intersector.h | 209 ++ .../kernels/geometry/conelinei_intersector.h | 141 ++ thirdparty/embree/kernels/geometry/curveNi.h | 222 +++ .../kernels/geometry/curveNi_intersector.h | 569 ++++++ .../embree/kernels/geometry/curveNi_mb.h | 278 +++ .../kernels/geometry/curveNi_mb_intersector.h | 516 +++++ thirdparty/embree/kernels/geometry/curveNv.h | 101 + .../kernels/geometry/curveNv_intersector.h | 181 ++ .../kernels/geometry/curve_intersector.h | 98 + .../geometry/curve_intersector_distance.h | 129 ++ .../geometry/curve_intersector_oriented.h | 417 ++++ .../curve_intersector_precalculations.h | 49 + .../geometry/curve_intersector_ribbon.h | 214 ++ .../geometry/curve_intersector_sweep.h | 362 ++++ .../geometry/curve_intersector_virtual.h | 671 +++++++ thirdparty/embree/kernels/geometry/cylinder.h | 223 +++ .../kernels/geometry/disc_intersector.h | 216 ++ .../kernels/geometry/disci_intersector.h | 277 +++ thirdparty/embree/kernels/geometry/filter.h | 204 ++ .../kernels/geometry/grid_intersector.h | 99 + thirdparty/embree/kernels/geometry/grid_soa.h | 275 +++ .../kernels/geometry/grid_soa_intersector1.h | 207 ++ .../geometry/grid_soa_intersector_packet.h | 445 +++++ thirdparty/embree/kernels/geometry/instance.h | 78 + .../kernels/geometry/instance_intersector.h | 84 + .../kernels/geometry/intersector_epilog.h | 1074 ++++++++++ .../kernels/geometry/intersector_iterators.h | 172 ++ .../kernels/geometry/line_intersector.h | 141 ++ thirdparty/embree/kernels/geometry/linei.h | 709 +++++++ .../kernels/geometry/linei_intersector.h | 124 ++ thirdparty/embree/kernels/geometry/object.h | 84 + .../kernels/geometry/object_intersector.h | 127 ++ thirdparty/embree/kernels/geometry/plane.h | 57 + thirdparty/embree/kernels/geometry/pointi.h | 417 ++++ .../embree/kernels/geometry/primitive.h | 49 + .../embree/kernels/geometry/primitive4.cpp | 379 ++++ .../kernels/geometry/quad_intersector.h | 76 + .../geometry/quad_intersector_moeller.h | 566 ++++++ .../geometry/quad_intersector_pluecker.h | 529 +++++ thirdparty/embree/kernels/geometry/quadi.h | 483 +++++ .../kernels/geometry/quadi_intersector.h | 350 ++++ thirdparty/embree/kernels/geometry/quadv.h | 165 ++ .../kernels/geometry/quadv_intersector.h | 181 ++ .../kernels/geometry/roundline_intersector.h | 710 +++++++ .../kernels/geometry/roundlinei_intersector.h | 136 ++ .../kernels/geometry/sphere_intersector.h | 183 ++ .../kernels/geometry/spherei_intersector.h | 156 ++ .../embree/kernels/geometry/subdivpatch1.h | 38 + .../geometry/subdivpatch1_intersector.h | 237 +++ thirdparty/embree/kernels/geometry/subgrid.h | 517 +++++ .../kernels/geometry/subgrid_intersector.h | 518 +++++ .../geometry/subgrid_intersector_moeller.h | 493 +++++ .../geometry/subgrid_intersector_pluecker.h | 508 +++++ .../kernels/geometry/subgrid_mb_intersector.h | 236 +++ thirdparty/embree/kernels/geometry/triangle.h | 162 ++ .../kernels/geometry/triangle_intersector.h | 96 + .../geometry/triangle_intersector_moeller.h | 403 ++++ .../geometry/triangle_intersector_pluecker.h | 247 +++ .../geometry/triangle_intersector_woop.h | 418 ++++ .../geometry/triangle_triangle_intersector.h | 132 ++ .../embree/kernels/geometry/trianglei.h | 442 +++++ .../kernels/geometry/trianglei_intersector.h | 336 ++++ .../embree/kernels/geometry/trianglev.h | 157 ++ .../kernels/geometry/trianglev_intersector.h | 206 ++ .../embree/kernels/geometry/trianglev_mb.h | 201 ++ .../geometry/trianglev_mb_intersector.h | 211 ++ thirdparty/embree/kernels/hash.h | 5 + .../embree/kernels/subdiv/bezier_curve.h | 669 +++++++ .../embree/kernels/subdiv/bezier_patch.h | 372 ++++ .../embree/kernels/subdiv/bilinear_patch.h | 191 ++ .../embree/kernels/subdiv/bspline_curve.h | 319 +++ .../embree/kernels/subdiv/bspline_patch.h | 449 +++++ .../subdiv/catmullclark_coefficients.h | 85 + .../kernels/subdiv/catmullclark_patch.h | 562 ++++++ .../embree/kernels/subdiv/catmullclark_ring.h | 826 ++++++++ .../embree/kernels/subdiv/catmullrom_curve.h | 296 +++ .../kernels/subdiv/feature_adaptive_eval.h | 226 +++ .../subdiv/feature_adaptive_eval_grid.h | 359 ++++ .../subdiv/feature_adaptive_eval_simd.h | 186 ++ .../embree/kernels/subdiv/gregory_patch.h | 893 +++++++++ .../kernels/subdiv/gregory_patch_dense.h | 113 ++ thirdparty/embree/kernels/subdiv/gridrange.h | 96 + thirdparty/embree/kernels/subdiv/half_edge.h | 371 ++++ .../embree/kernels/subdiv/hermite_curve.h | 38 + .../kernels/subdiv/linear_bezier_patch.h | 403 ++++ thirdparty/embree/kernels/subdiv/patch.h | 371 ++++ thirdparty/embree/kernels/subdiv/patch_eval.h | 129 ++ .../embree/kernels/subdiv/patch_eval_grid.h | 245 +++ .../embree/kernels/subdiv/patch_eval_simd.h | 127 ++ .../embree/kernels/subdiv/subdivpatch1base.h | 156 ++ .../embree/kernels/subdiv/tessellation.h | 161 ++ .../kernels/subdiv/tessellation_cache.h | 325 +++ thirdparty/embree/pathces/godot-changes.patch | 91 + 333 files changed, 88649 insertions(+) create mode 100644 modules/raycast/SCsub create mode 100644 modules/raycast/config.py create mode 100644 modules/raycast/godot_update_embree.py create mode 100644 modules/raycast/lightmap_raycaster.cpp create mode 100644 modules/raycast/lightmap_raycaster.h create mode 100644 modules/raycast/register_types.cpp create mode 100644 modules/raycast/register_types.h create mode 100644 thirdparty/embree/common/algorithms/parallel_any_of.h create mode 100644 thirdparty/embree/common/algorithms/parallel_filter.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_filter.h create mode 100644 thirdparty/embree/common/algorithms/parallel_for.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_for.h create mode 100644 thirdparty/embree/common/algorithms/parallel_for_for.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_for_for.h create mode 100644 thirdparty/embree/common/algorithms/parallel_for_for_prefix_sum.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_for_for_prefix_sum.h create mode 100644 thirdparty/embree/common/algorithms/parallel_map.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_map.h create mode 100644 thirdparty/embree/common/algorithms/parallel_partition.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_partition.h create mode 100644 thirdparty/embree/common/algorithms/parallel_prefix_sum.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_prefix_sum.h create mode 100644 thirdparty/embree/common/algorithms/parallel_reduce.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_reduce.h create mode 100644 thirdparty/embree/common/algorithms/parallel_set.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_set.h create mode 100644 thirdparty/embree/common/algorithms/parallel_sort.cpp create mode 100644 thirdparty/embree/common/algorithms/parallel_sort.h create mode 100644 thirdparty/embree/common/lexers/parsestream.h create mode 100644 thirdparty/embree/common/lexers/stream.h create mode 100644 thirdparty/embree/common/lexers/streamfilters.h create mode 100644 thirdparty/embree/common/lexers/stringstream.cpp create mode 100644 thirdparty/embree/common/lexers/stringstream.h create mode 100644 thirdparty/embree/common/lexers/tokenstream.cpp create mode 100644 thirdparty/embree/common/lexers/tokenstream.h create mode 100644 thirdparty/embree/common/math/affinespace.h create mode 100644 thirdparty/embree/common/math/bbox.h create mode 100644 thirdparty/embree/common/math/col3.h create mode 100644 thirdparty/embree/common/math/col4.h create mode 100644 thirdparty/embree/common/math/color.h create mode 100644 thirdparty/embree/common/math/constants.cpp create mode 100644 thirdparty/embree/common/math/constants.h create mode 100644 thirdparty/embree/common/math/interval.h create mode 100644 thirdparty/embree/common/math/lbbox.h create mode 100644 thirdparty/embree/common/math/linearspace2.h create mode 100644 thirdparty/embree/common/math/linearspace3.h create mode 100644 thirdparty/embree/common/math/math.h create mode 100644 thirdparty/embree/common/math/obbox.h create mode 100644 thirdparty/embree/common/math/quaternion.h create mode 100644 thirdparty/embree/common/math/range.h create mode 100644 thirdparty/embree/common/math/transcendental.h create mode 100644 thirdparty/embree/common/math/vec2.h create mode 100644 thirdparty/embree/common/math/vec2fa.h create mode 100644 thirdparty/embree/common/math/vec3.h create mode 100644 thirdparty/embree/common/math/vec3ba.h create mode 100644 thirdparty/embree/common/math/vec3fa.h create mode 100644 thirdparty/embree/common/math/vec3ia.h create mode 100644 thirdparty/embree/common/math/vec4.h create mode 100644 thirdparty/embree/common/simd/avx.h create mode 100644 thirdparty/embree/common/simd/avx512.h create mode 100644 thirdparty/embree/common/simd/simd.h create mode 100644 thirdparty/embree/common/simd/sse.cpp create mode 100644 thirdparty/embree/common/simd/sse.h create mode 100644 thirdparty/embree/common/simd/varying.h create mode 100644 thirdparty/embree/common/simd/vboold4_avx.h create mode 100644 thirdparty/embree/common/simd/vboold4_avx512.h create mode 100644 thirdparty/embree/common/simd/vboold8_avx512.h create mode 100644 thirdparty/embree/common/simd/vboolf16_avx512.h create mode 100644 thirdparty/embree/common/simd/vboolf4_avx512.h create mode 100644 thirdparty/embree/common/simd/vboolf4_sse2.h create mode 100644 thirdparty/embree/common/simd/vboolf8_avx.h create mode 100644 thirdparty/embree/common/simd/vboolf8_avx512.h create mode 100644 thirdparty/embree/common/simd/vdouble4_avx.h create mode 100644 thirdparty/embree/common/simd/vdouble8_avx512.h create mode 100644 thirdparty/embree/common/simd/vfloat16_avx512.h create mode 100644 thirdparty/embree/common/simd/vfloat4_sse2.h create mode 100644 thirdparty/embree/common/simd/vfloat8_avx.h create mode 100644 thirdparty/embree/common/simd/vint16_avx512.h create mode 100644 thirdparty/embree/common/simd/vint4_sse2.h create mode 100644 thirdparty/embree/common/simd/vint8_avx.h create mode 100644 thirdparty/embree/common/simd/vint8_avx2.h create mode 100644 thirdparty/embree/common/simd/vllong4_avx2.h create mode 100644 thirdparty/embree/common/simd/vllong8_avx512.h create mode 100644 thirdparty/embree/common/simd/vuint16_avx512.h create mode 100644 thirdparty/embree/common/simd/vuint4_sse2.h create mode 100644 thirdparty/embree/common/simd/vuint8_avx.h create mode 100644 thirdparty/embree/common/simd/vuint8_avx2.h create mode 100644 thirdparty/embree/common/sys/alloc.cpp create mode 100644 thirdparty/embree/common/sys/alloc.h create mode 100644 thirdparty/embree/common/sys/array.h create mode 100644 thirdparty/embree/common/sys/atomic.h create mode 100644 thirdparty/embree/common/sys/barrier.cpp create mode 100644 thirdparty/embree/common/sys/barrier.h create mode 100644 thirdparty/embree/common/sys/condition.cpp create mode 100644 thirdparty/embree/common/sys/condition.h create mode 100644 thirdparty/embree/common/sys/filename.cpp create mode 100644 thirdparty/embree/common/sys/filename.h create mode 100644 thirdparty/embree/common/sys/intrinsics.h create mode 100644 thirdparty/embree/common/sys/library.cpp create mode 100644 thirdparty/embree/common/sys/library.h create mode 100644 thirdparty/embree/common/sys/mutex.cpp create mode 100644 thirdparty/embree/common/sys/mutex.h create mode 100644 thirdparty/embree/common/sys/platform.h create mode 100644 thirdparty/embree/common/sys/ref.h create mode 100644 thirdparty/embree/common/sys/regression.cpp create mode 100644 thirdparty/embree/common/sys/regression.h create mode 100644 thirdparty/embree/common/sys/string.cpp create mode 100644 thirdparty/embree/common/sys/string.h create mode 100644 thirdparty/embree/common/sys/sysinfo.cpp create mode 100644 thirdparty/embree/common/sys/sysinfo.h create mode 100644 thirdparty/embree/common/sys/thread.cpp create mode 100644 thirdparty/embree/common/sys/thread.h create mode 100644 thirdparty/embree/common/sys/vector.h create mode 100644 thirdparty/embree/common/tasking/taskscheduler.h create mode 100644 thirdparty/embree/common/tasking/taskschedulerinternal.cpp create mode 100644 thirdparty/embree/common/tasking/taskschedulerinternal.h create mode 100644 thirdparty/embree/common/tasking/taskschedulerppl.h create mode 100644 thirdparty/embree/common/tasking/taskschedulertbb.h create mode 100644 thirdparty/embree/include/embree3/rtcore.h create mode 100644 thirdparty/embree/include/embree3/rtcore_buffer.h create mode 100644 thirdparty/embree/include/embree3/rtcore_builder.h create mode 100644 thirdparty/embree/include/embree3/rtcore_common.h create mode 100644 thirdparty/embree/include/embree3/rtcore_config.h create mode 100644 thirdparty/embree/include/embree3/rtcore_device.h create mode 100644 thirdparty/embree/include/embree3/rtcore_geometry.h create mode 100644 thirdparty/embree/include/embree3/rtcore_quaternion.h create mode 100644 thirdparty/embree/include/embree3/rtcore_ray.h create mode 100644 thirdparty/embree/include/embree3/rtcore_scene.h create mode 100644 thirdparty/embree/kernels/builders/bvh_builder_hair.h create mode 100644 thirdparty/embree/kernels/builders/bvh_builder_morton.h create mode 100644 thirdparty/embree/kernels/builders/bvh_builder_msmblur.h create mode 100644 thirdparty/embree/kernels/builders/bvh_builder_msmblur_hair.h create mode 100644 thirdparty/embree/kernels/builders/bvh_builder_sah.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_binning.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_binning_array_aligned.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_binning_array_unaligned.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_openmerge_array.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_spatial.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_spatial_array.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_strand_array.h create mode 100644 thirdparty/embree/kernels/builders/heuristic_timesplit_array.h create mode 100644 thirdparty/embree/kernels/builders/priminfo.h create mode 100644 thirdparty/embree/kernels/builders/primrefgen.cpp create mode 100644 thirdparty/embree/kernels/builders/primrefgen.h create mode 100644 thirdparty/embree/kernels/builders/primrefgen_presplit.h create mode 100644 thirdparty/embree/kernels/builders/splitter.h create mode 100644 thirdparty/embree/kernels/bvh/bvh.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh.h create mode 100644 thirdparty/embree/kernels/bvh/bvh4_factory.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh4_factory.h create mode 100644 thirdparty/embree/kernels/bvh/bvh8_factory.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh8_factory.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_morton.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_sah.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_sah_mb.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_sah_spatial.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_twolevel.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_twolevel.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_builder_twolevel_internal.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_collider.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_collider.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_factory.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_intersector1.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_intersector1.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_intersector1_bvh4.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_intersector_hybrid.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_intersector_stream.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_intersector_stream_filters.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_aabb.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_aabb_mb.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_aabb_mb4d.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_base.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_obb.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_obb_mb.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_qaabb.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_node_ref.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_refit.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_refit.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_rotate.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_rotate.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_statistics.cpp create mode 100644 thirdparty/embree/kernels/bvh/bvh_statistics.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_traverser1.h create mode 100644 thirdparty/embree/kernels/bvh/bvh_traverser_stream.h create mode 100644 thirdparty/embree/kernels/bvh/node_intersector.h create mode 100644 thirdparty/embree/kernels/bvh/node_intersector1.h create mode 100644 thirdparty/embree/kernels/bvh/node_intersector_frustum.h create mode 100644 thirdparty/embree/kernels/bvh/node_intersector_packet.h create mode 100644 thirdparty/embree/kernels/bvh/node_intersector_packet_stream.h create mode 100644 thirdparty/embree/kernels/common/accel.h create mode 100644 thirdparty/embree/kernels/common/accelinstance.h create mode 100644 thirdparty/embree/kernels/common/acceln.cpp create mode 100644 thirdparty/embree/kernels/common/acceln.h create mode 100644 thirdparty/embree/kernels/common/accelset.cpp create mode 100644 thirdparty/embree/kernels/common/accelset.h create mode 100644 thirdparty/embree/kernels/common/alloc.cpp create mode 100644 thirdparty/embree/kernels/common/alloc.h create mode 100644 thirdparty/embree/kernels/common/buffer.h create mode 100644 thirdparty/embree/kernels/common/builder.h create mode 100644 thirdparty/embree/kernels/common/context.h create mode 100644 thirdparty/embree/kernels/common/default.h create mode 100644 thirdparty/embree/kernels/common/device.cpp create mode 100644 thirdparty/embree/kernels/common/device.h create mode 100644 thirdparty/embree/kernels/common/geometry.cpp create mode 100644 thirdparty/embree/kernels/common/geometry.h create mode 100644 thirdparty/embree/kernels/common/hit.h create mode 100644 thirdparty/embree/kernels/common/instance_stack.h create mode 100644 thirdparty/embree/kernels/common/isa.h create mode 100644 thirdparty/embree/kernels/common/motion_derivative.h create mode 100644 thirdparty/embree/kernels/common/point_query.h create mode 100644 thirdparty/embree/kernels/common/primref.h create mode 100644 thirdparty/embree/kernels/common/primref_mb.h create mode 100644 thirdparty/embree/kernels/common/profile.h create mode 100644 thirdparty/embree/kernels/common/ray.h create mode 100644 thirdparty/embree/kernels/common/rtcore.cpp create mode 100644 thirdparty/embree/kernels/common/rtcore.h create mode 100644 thirdparty/embree/kernels/common/rtcore_builder.cpp create mode 100644 thirdparty/embree/kernels/common/scene.cpp create mode 100644 thirdparty/embree/kernels/common/scene.h create mode 100644 thirdparty/embree/kernels/common/scene_curves.h create mode 100644 thirdparty/embree/kernels/common/scene_grid_mesh.h create mode 100644 thirdparty/embree/kernels/common/scene_instance.h create mode 100644 thirdparty/embree/kernels/common/scene_line_segments.h create mode 100644 thirdparty/embree/kernels/common/scene_points.h create mode 100644 thirdparty/embree/kernels/common/scene_quad_mesh.h create mode 100644 thirdparty/embree/kernels/common/scene_subdiv_mesh.h create mode 100644 thirdparty/embree/kernels/common/scene_triangle_mesh.cpp create mode 100644 thirdparty/embree/kernels/common/scene_triangle_mesh.h create mode 100644 thirdparty/embree/kernels/common/scene_user_geometry.h create mode 100644 thirdparty/embree/kernels/common/stack_item.h create mode 100644 thirdparty/embree/kernels/common/stat.cpp create mode 100644 thirdparty/embree/kernels/common/stat.h create mode 100644 thirdparty/embree/kernels/common/state.cpp create mode 100644 thirdparty/embree/kernels/common/state.h create mode 100644 thirdparty/embree/kernels/common/vector.h create mode 100644 thirdparty/embree/kernels/config.h create mode 100644 thirdparty/embree/kernels/geometry/cone.h create mode 100644 thirdparty/embree/kernels/geometry/coneline_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/conelinei_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/curveNi.h create mode 100644 thirdparty/embree/kernels/geometry/curveNi_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/curveNi_mb.h create mode 100644 thirdparty/embree/kernels/geometry/curveNi_mb_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/curveNv.h create mode 100644 thirdparty/embree/kernels/geometry/curveNv_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector_distance.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector_oriented.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector_precalculations.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector_ribbon.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector_sweep.h create mode 100644 thirdparty/embree/kernels/geometry/curve_intersector_virtual.h create mode 100644 thirdparty/embree/kernels/geometry/cylinder.h create mode 100644 thirdparty/embree/kernels/geometry/disc_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/disci_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/filter.h create mode 100644 thirdparty/embree/kernels/geometry/grid_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/grid_soa.h create mode 100644 thirdparty/embree/kernels/geometry/grid_soa_intersector1.h create mode 100644 thirdparty/embree/kernels/geometry/grid_soa_intersector_packet.h create mode 100644 thirdparty/embree/kernels/geometry/instance.h create mode 100644 thirdparty/embree/kernels/geometry/instance_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/intersector_epilog.h create mode 100644 thirdparty/embree/kernels/geometry/intersector_iterators.h create mode 100644 thirdparty/embree/kernels/geometry/line_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/linei.h create mode 100644 thirdparty/embree/kernels/geometry/linei_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/object.h create mode 100644 thirdparty/embree/kernels/geometry/object_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/plane.h create mode 100644 thirdparty/embree/kernels/geometry/pointi.h create mode 100644 thirdparty/embree/kernels/geometry/primitive.h create mode 100644 thirdparty/embree/kernels/geometry/primitive4.cpp create mode 100644 thirdparty/embree/kernels/geometry/quad_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/quad_intersector_moeller.h create mode 100644 thirdparty/embree/kernels/geometry/quad_intersector_pluecker.h create mode 100644 thirdparty/embree/kernels/geometry/quadi.h create mode 100644 thirdparty/embree/kernels/geometry/quadi_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/quadv.h create mode 100644 thirdparty/embree/kernels/geometry/quadv_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/roundline_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/roundlinei_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/sphere_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/spherei_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/subdivpatch1.h create mode 100644 thirdparty/embree/kernels/geometry/subdivpatch1_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/subgrid.h create mode 100644 thirdparty/embree/kernels/geometry/subgrid_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/subgrid_intersector_moeller.h create mode 100644 thirdparty/embree/kernels/geometry/subgrid_intersector_pluecker.h create mode 100644 thirdparty/embree/kernels/geometry/subgrid_mb_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/triangle.h create mode 100644 thirdparty/embree/kernels/geometry/triangle_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/triangle_intersector_moeller.h create mode 100644 thirdparty/embree/kernels/geometry/triangle_intersector_pluecker.h create mode 100644 thirdparty/embree/kernels/geometry/triangle_intersector_woop.h create mode 100644 thirdparty/embree/kernels/geometry/triangle_triangle_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/trianglei.h create mode 100644 thirdparty/embree/kernels/geometry/trianglei_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/trianglev.h create mode 100644 thirdparty/embree/kernels/geometry/trianglev_intersector.h create mode 100644 thirdparty/embree/kernels/geometry/trianglev_mb.h create mode 100644 thirdparty/embree/kernels/geometry/trianglev_mb_intersector.h create mode 100644 thirdparty/embree/kernels/hash.h create mode 100644 thirdparty/embree/kernels/subdiv/bezier_curve.h create mode 100644 thirdparty/embree/kernels/subdiv/bezier_patch.h create mode 100644 thirdparty/embree/kernels/subdiv/bilinear_patch.h create mode 100644 thirdparty/embree/kernels/subdiv/bspline_curve.h create mode 100644 thirdparty/embree/kernels/subdiv/bspline_patch.h create mode 100644 thirdparty/embree/kernels/subdiv/catmullclark_coefficients.h create mode 100644 thirdparty/embree/kernels/subdiv/catmullclark_patch.h create mode 100644 thirdparty/embree/kernels/subdiv/catmullclark_ring.h create mode 100644 thirdparty/embree/kernels/subdiv/catmullrom_curve.h create mode 100644 thirdparty/embree/kernels/subdiv/feature_adaptive_eval.h create mode 100644 thirdparty/embree/kernels/subdiv/feature_adaptive_eval_grid.h create mode 100644 thirdparty/embree/kernels/subdiv/feature_adaptive_eval_simd.h create mode 100644 thirdparty/embree/kernels/subdiv/gregory_patch.h create mode 100644 thirdparty/embree/kernels/subdiv/gregory_patch_dense.h create mode 100644 thirdparty/embree/kernels/subdiv/gridrange.h create mode 100644 thirdparty/embree/kernels/subdiv/half_edge.h create mode 100644 thirdparty/embree/kernels/subdiv/hermite_curve.h create mode 100644 thirdparty/embree/kernels/subdiv/linear_bezier_patch.h create mode 100644 thirdparty/embree/kernels/subdiv/patch.h create mode 100644 thirdparty/embree/kernels/subdiv/patch_eval.h create mode 100644 thirdparty/embree/kernels/subdiv/patch_eval_grid.h create mode 100644 thirdparty/embree/kernels/subdiv/patch_eval_simd.h create mode 100644 thirdparty/embree/kernels/subdiv/subdivpatch1base.h create mode 100644 thirdparty/embree/kernels/subdiv/tessellation.h create mode 100644 thirdparty/embree/kernels/subdiv/tessellation_cache.h create mode 100644 thirdparty/embree/pathces/godot-changes.patch diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub new file mode 100644 index 000000000000..8c0f81ea0331 --- /dev/null +++ b/modules/raycast/SCsub @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +embree_src = [ + "common/sys/sysinfo.cpp", + "common/sys/alloc.cpp", + "common/sys/filename.cpp", + "common/sys/library.cpp", + "common/sys/thread.cpp", + "common/sys/string.cpp", + "common/sys/regression.cpp", + "common/sys/mutex.cpp", + "common/sys/condition.cpp", + "common/sys/barrier.cpp", + "common/math/constants.cpp", + "common/simd/sse.cpp", + "common/lexers/stringstream.cpp", + "common/lexers/tokenstream.cpp", + "common/tasking/taskschedulerinternal.cpp", + "common/algorithms/parallel_for.cpp", + "common/algorithms/parallel_reduce.cpp", + "common/algorithms/parallel_prefix_sum.cpp", + "common/algorithms/parallel_for_for.cpp", + "common/algorithms/parallel_for_for_prefix_sum.cpp", + "common/algorithms/parallel_partition.cpp", + "common/algorithms/parallel_sort.cpp", + "common/algorithms/parallel_set.cpp", + "common/algorithms/parallel_map.cpp", + "common/algorithms/parallel_filter.cpp", + "kernels/common/device.cpp", + "kernels/common/stat.cpp", + "kernels/common/acceln.cpp", + "kernels/common/accelset.cpp", + "kernels/common/state.cpp", + "kernels/common/rtcore.cpp", + "kernels/common/rtcore_builder.cpp", + "kernels/common/scene.cpp", + "kernels/common/alloc.cpp", + "kernels/common/geometry.cpp", + "kernels/common/scene_triangle_mesh.cpp", + "kernels/geometry/primitive4.cpp", + "kernels/builders/primrefgen.cpp", + "kernels/bvh/bvh.cpp", + "kernels/bvh/bvh_statistics.cpp", + "kernels/bvh/bvh4_factory.cpp", + "kernels/bvh/bvh8_factory.cpp", + "kernels/bvh/bvh_collider.cpp", + "kernels/bvh/bvh_rotate.cpp", + "kernels/bvh/bvh_refit.cpp", + "kernels/bvh/bvh_builder.cpp", + "kernels/bvh/bvh_builder_morton.cpp", + "kernels/bvh/bvh_builder_sah.cpp", + "kernels/bvh/bvh_builder_sah_spatial.cpp", + "kernels/bvh/bvh_builder_sah_mb.cpp", + "kernels/bvh/bvh_builder_twolevel.cpp", + "kernels/bvh/bvh_intersector1_bvh4.cpp", +] + +embree_dir = "#thirdparty/embree/" + +env_embree = env_modules.Clone() +embree_sources = [embree_dir + file for file in embree_src] +env_embree.Prepend(CPPPATH=[embree_dir, embree_dir + "include/embree3"]) +env_embree.Append( + CPPFLAGS=[ + "-DEMBREE_TARGET_SSE2", + "-DEMBREE_LOWEST_ISA", + "-msse2", + "-DTASKING_INTERNAL", + "-DNDEBUG", + "-D__SSE2__", + "-D__SSE__", + ] +) + +if not env_embree.msvc: + env_embree.Append(CPPFLAGS=["-mxsave"]) + +if env["platform"] == "windows": + if env.msvc: + env.Append(LINKFLAGS=["psapi.lib"]) + else: + env.Append(LIBS=["psapi"]) + +env_embree.disable_warnings() +env_embree.add_source_files(env.modules_sources, embree_sources) + +env_raycast = env_modules.Clone() +env_raycast.Prepend(CPPPATH=[embree_dir, embree_dir + "include/embree3", embree_dir + "common"]) + +env_raycast.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/raycast/config.py b/modules/raycast/config.py new file mode 100644 index 000000000000..85416c16cc8d --- /dev/null +++ b/modules/raycast/config.py @@ -0,0 +1,13 @@ +def can_build(env, platform): + # Embree requires at least SSE2 to be available, so 32-bit and ARM64 builds are + # not supported. + # It's also only relevant for tools build and desktop platforms, + # as doing lightmap generation on Android or HTML5 would be a bit far-fetched. + supported_platform = platform in ["x11", "osx", "windows", "server"] + supported_bits = env["bits"] == "64" + supported_arch = env["arch"] != "arm64" + return env["tools"] and supported_platform and supported_bits and supported_arch + + +def configure(env): + pass diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py new file mode 100644 index 000000000000..92649bbf7427 --- /dev/null +++ b/modules/raycast/godot_update_embree.py @@ -0,0 +1,259 @@ +import glob, os, shutil, subprocess, re + +include_dirs = [ + "common/tasking", + "kernels/bvh", + "kernels/builders", + "common/sys", + "kernels", + "kernels/common", + "common/math", + "common/algorithms", + "common/lexers", + "common/simd", + "include/embree3", + "kernels/subdiv", + "kernels/geometry", +] + +cpp_files = [ + "common/sys/sysinfo.cpp", + "common/sys/alloc.cpp", + "common/sys/filename.cpp", + "common/sys/library.cpp", + "common/sys/thread.cpp", + "common/sys/string.cpp", + "common/sys/regression.cpp", + "common/sys/mutex.cpp", + "common/sys/condition.cpp", + "common/sys/barrier.cpp", + "common/math/constants.cpp", + "common/simd/sse.cpp", + "common/lexers/stringstream.cpp", + "common/lexers/tokenstream.cpp", + "common/tasking/taskschedulerinternal.cpp", + "common/algorithms/parallel_for.cpp", + "common/algorithms/parallel_reduce.cpp", + "common/algorithms/parallel_prefix_sum.cpp", + "common/algorithms/parallel_for_for.cpp", + "common/algorithms/parallel_for_for_prefix_sum.cpp", + "common/algorithms/parallel_partition.cpp", + "common/algorithms/parallel_sort.cpp", + "common/algorithms/parallel_set.cpp", + "common/algorithms/parallel_map.cpp", + "common/algorithms/parallel_filter.cpp", + "kernels/common/device.cpp", + "kernels/common/stat.cpp", + "kernels/common/acceln.cpp", + "kernels/common/accelset.cpp", + "kernels/common/state.cpp", + "kernels/common/rtcore.cpp", + "kernels/common/rtcore_builder.cpp", + "kernels/common/scene.cpp", + "kernels/common/alloc.cpp", + "kernels/common/geometry.cpp", + "kernels/common/scene_triangle_mesh.cpp", + "kernels/geometry/primitive4.cpp", + "kernels/builders/primrefgen.cpp", + "kernels/bvh/bvh.cpp", + "kernels/bvh/bvh_statistics.cpp", + "kernels/bvh/bvh4_factory.cpp", + "kernels/bvh/bvh8_factory.cpp", + "kernels/bvh/bvh_collider.cpp", + "kernels/bvh/bvh_rotate.cpp", + "kernels/bvh/bvh_refit.cpp", + "kernels/bvh/bvh_builder.cpp", + "kernels/bvh/bvh_builder_morton.cpp", + "kernels/bvh/bvh_builder_sah.cpp", + "kernels/bvh/bvh_builder_sah_spatial.cpp", + "kernels/bvh/bvh_builder_sah_mb.cpp", + "kernels/bvh/bvh_builder_twolevel.cpp", + "kernels/bvh/bvh_intersector1.cpp", + "kernels/bvh/bvh_intersector1_bvh4.cpp", +] + +os.chdir("../../thirdparty") + +if os.path.exists("embree"): + shutil.rmtree("embree") + +subprocess.run(["git", "clone", "https://github.com/embree/embree.git", "embree-tmp"]) +os.chdir("embree-tmp") + +commit_hash = str(subprocess.check_output(["git", "rev-parse", "HEAD"], universal_newlines=True)).strip() + +dest_dir = "../embree" +all_files = set(cpp_files) + +for include_dir in include_dirs: + headers = glob.iglob(os.path.join(include_dir, "*.h")) + all_files.update(headers) + +for f in all_files: + d = os.path.join(dest_dir, os.path.dirname(f)) + if not os.path.exists(d): + os.makedirs(d) + shutil.copy2(f, d) + +with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: + hash_file.write( + f""" +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#define RTC_HASH "{commit_hash}" +""" + ) + +with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: + config_file.write( + """ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +/* #undef EMBREE_RAY_MASK */ +/* #undef EMBREE_STAT_COUNTERS */ +/* #undef EMBREE_BACKFACE_CULLING */ +/* #undef EMBREE_BACKFACE_CULLING_CURVES */ +#define EMBREE_FILTER_FUNCTION +/* #undef EMBREE_IGNORE_INVALID_RAYS */ +#define EMBREE_GEOMETRY_TRIANGLE +/* #undef EMBREE_GEOMETRY_QUAD */ +/* #undef EMBREE_GEOMETRY_CURVE */ +/* #undef EMBREE_GEOMETRY_SUBDIVISION */ +/* #undef EMBREE_GEOMETRY_USER */ +/* #undef EMBREE_GEOMETRY_INSTANCE */ +/* #undef EMBREE_GEOMETRY_GRID */ +/* #undef EMBREE_GEOMETRY_POINT */ +/* #undef EMBREE_RAY_PACKETS */ +/* #undef EMBREE_COMPACT_POLYS */ + +#define EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR 2.0 + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + #define IF_ENABLED_TRIS(x) x +#else + #define IF_ENABLED_TRIS(x) +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + #define IF_ENABLED_QUADS(x) x +#else + #define IF_ENABLED_QUADS(x) +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) || defined(EMBREE_GEOMETRY_POINT) + #define IF_ENABLED_CURVES_OR_POINTS(x) x +#else + #define IF_ENABLED_CURVES_OR_POINTS(x) +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) + #define IF_ENABLED_CURVES(x) x +#else + #define IF_ENABLED_CURVES(x) +#endif + +#if defined(EMBREE_GEOMETRY_POINT) + #define IF_ENABLED_POINTS(x) x +#else + #define IF_ENABLED_POINTS(x) +#endif + +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + #define IF_ENABLED_SUBDIV(x) x +#else + #define IF_ENABLED_SUBDIV(x) +#endif + +#if defined(EMBREE_GEOMETRY_USER) + #define IF_ENABLED_USER(x) x +#else + #define IF_ENABLED_USER(x) +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + #define IF_ENABLED_INSTANCE(x) x +#else + #define IF_ENABLED_INSTANCE(x) +#endif + +#if defined(EMBREE_GEOMETRY_GRID) + #define IF_ENABLED_GRIDS(x) x +#else + #define IF_ENABLED_GRIDS(x) +#endif +""" + ) + + +with open("CMakeLists.txt", "r") as cmake_file: + cmake_content = cmake_file.read() + major_version = int(re.compile(r"EMBREE_VERSION_MAJOR\s(\d+)").findall(cmake_content)[0]) + minor_version = int(re.compile(r"EMBREE_VERSION_MINOR\s(\d+)").findall(cmake_content)[0]) + patch_version = int(re.compile(r"EMBREE_VERSION_PATCH\s(\d+)").findall(cmake_content)[0]) + +with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as config_file: + config_file.write( + f""" +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define RTC_VERSION_MAJOR {major_version} +#define RTC_VERSION_MINOR {minor_version} +#define RTC_VERSION_PATCH {patch_version} +#define RTC_VERSION {major_version}{minor_version:02d}{patch_version:02d} +#define RTC_VERSION_STRING "{major_version}.{minor_version}.{patch_version}" + +#define RTC_MAX_INSTANCE_LEVEL_COUNT 1 + +#define EMBREE_MIN_WIDTH 0 +#define RTC_MIN_WIDTH EMBREE_MIN_WIDTH + +#define EMBREE_STATIC_LIB +/* #undef EMBREE_API_NAMESPACE */ + +#if defined(EMBREE_API_NAMESPACE) +# define RTC_NAMESPACE +# define RTC_NAMESPACE_BEGIN namespace {{ +# define RTC_NAMESPACE_END }} +# define RTC_NAMESPACE_USE using namespace ; +# define RTC_API_EXTERN_C +# undef EMBREE_API_NAMESPACE +#else +# define RTC_NAMESPACE_BEGIN +# define RTC_NAMESPACE_END +# define RTC_NAMESPACE_USE +# if defined(__cplusplus) +# define RTC_API_EXTERN_C extern "C" +# else +# define RTC_API_EXTERN_C +# endif +#endif + +#if defined(ISPC) +# define RTC_API_IMPORT extern "C" unmasked +# define RTC_API_EXPORT extern "C" unmasked +#elif defined(EMBREE_STATIC_LIB) +# define RTC_API_IMPORT RTC_API_EXTERN_C +# define RTC_API_EXPORT RTC_API_EXTERN_C +#elif defined(_WIN32) +# define RTC_API_IMPORT RTC_API_EXTERN_C __declspec(dllimport) +# define RTC_API_EXPORT RTC_API_EXTERN_C __declspec(dllexport) +#else +# define RTC_API_IMPORT RTC_API_EXTERN_C +# define RTC_API_EXPORT RTC_API_EXTERN_C __attribute__ ((visibility ("default"))) +#endif + +#if defined(RTC_EXPORT_API) +# define RTC_API RTC_API_EXPORT +#else +# define RTC_API RTC_API_IMPORT +#endif +""" + ) + +os.chdir("..") +shutil.rmtree("embree-tmp") diff --git a/modules/raycast/lightmap_raycaster.cpp b/modules/raycast/lightmap_raycaster.cpp new file mode 100644 index 000000000000..b5eb15de4abf --- /dev/null +++ b/modules/raycast/lightmap_raycaster.cpp @@ -0,0 +1,196 @@ +/*************************************************************************/ +/* lightmap_raycaster.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "lightmap_raycaster.h" +#include "math/vec2.h" +#include "math/vec3.h" + +using namespace embree; + +LightmapRaycaster *LightmapRaycasterEmbree::create_embree_raycaster() { + return memnew(LightmapRaycasterEmbree); +} + +void LightmapRaycasterEmbree::make_default_raycaster() { + create_function = create_embree_raycaster; +} + +void LightmapRaycasterEmbree::filter_function(const struct RTCFilterFunctionNArguments *p_args) { + + RTCHit *hit = (RTCHit *)p_args->hit; + + unsigned int geomID = hit->geomID; + float u = hit->u; + float v = hit->v; + + LightmapRaycasterEmbree *scene = (LightmapRaycasterEmbree *)p_args->geometryUserPtr; + RTCGeometry geom = rtcGetGeometry(scene->embree_scene, geomID); + + rtcInterpolate0(geom, hit->primID, hit->u, hit->v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 0, &hit->u, 2); + + if (scene->alpha_textures.has(geomID)) { + const AlphaTextureData &alpha_texture = scene->alpha_textures[geomID]; + + if (alpha_texture.sample(hit->u, hit->v) < 128) { + p_args->valid[0] = 0; + return; + } + } + + rtcInterpolate0(geom, hit->primID, u, v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 1, &hit->Ng_x, 3); +} + +bool LightmapRaycasterEmbree::intersect(Ray &r_ray) { + RTCIntersectContext context; + + rtcInitIntersectContext(&context); + + rtcIntersect1(embree_scene, &context, (RTCRayHit *)&r_ray); + return r_ray.geomID != RTC_INVALID_GEOMETRY_ID; +} + +void LightmapRaycasterEmbree::intersect(Vector &r_rays) { + Ray *rays = r_rays.ptrw(); + for (int i = 0; i < r_rays.size(); ++i) { + intersect(rays[i]); + } +} + +void LightmapRaycasterEmbree::set_mesh_alpha_texture(Ref p_alpha_texture, unsigned int p_id) { + if (p_alpha_texture.is_valid() && p_alpha_texture->get_size() != Vector2i()) { + AlphaTextureData tex; + tex.size = p_alpha_texture->get_size(); + tex.data.resize(tex.size.x * tex.size.y); + + { + PoolVector::Read r = p_alpha_texture->get_data().read(); + uint8_t *ptrw = tex.data.ptrw(); + for (int i = 0; i < tex.size.x * tex.size.y; ++i) { + ptrw[i] = r[i]; + } + } + + alpha_textures.insert(p_id, tex); + } +} + +float blerp(float c00, float c10, float c01, float c11, float tx, float ty) { + return Math::lerp(Math::lerp(c00, c10, tx), Math::lerp(c01, c11, tx), ty); +} + +uint8_t LightmapRaycasterEmbree::AlphaTextureData::sample(float u, float v) const { + float x = u * size.x; + float y = v * size.y; + int xi = (int)x; + int yi = (int)y; + + uint8_t texels[4]; + + for (int i = 0; i < 4; ++i) { + int sample_x = CLAMP(xi + i % 2, 0, size.x - 1); + int sample_y = CLAMP(yi + i / 2, 0, size.y - 1); + texels[i] = data[sample_y * size.x + sample_x]; + } + + return Math::round(blerp(texels[0], texels[1], texels[2], texels[3], x - xi, y - yi)); +} + +void LightmapRaycasterEmbree::add_mesh(const Vector &p_vertices, const Vector &p_normals, const Vector &p_uv2s, unsigned int p_id) { + + RTCGeometry embree_mesh = rtcNewGeometry(embree_device, RTC_GEOMETRY_TYPE_TRIANGLE); + + rtcSetGeometryVertexAttributeCount(embree_mesh, 2); + + int vertex_count = p_vertices.size(); + + ERR_FAIL_COND(vertex_count % 3 != 0); + ERR_FAIL_COND(vertex_count != p_uv2s.size()); + + Vec3fa *embree_vertices = (Vec3fa *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, sizeof(Vec3fa), vertex_count); + Vec2fa *embree_light_uvs = (Vec2fa *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 0, RTC_FORMAT_FLOAT2, sizeof(Vec2fa), vertex_count); + uint32_t *embree_triangles = (uint32_t *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, sizeof(uint32_t) * 3, vertex_count / 3); + + Vec3fa *embree_normals = nullptr; + if (!p_normals.empty()) { + embree_normals = (Vec3fa *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 1, RTC_FORMAT_FLOAT3, sizeof(Vec3fa), vertex_count); + } + + for (uint32_t i = 0; i < vertex_count; i++) { + embree_vertices[i] = Vec3fa(p_vertices[i].x, p_vertices[i].y, p_vertices[i].z); + embree_light_uvs[i] = Vec2fa(p_uv2s[i].x, p_uv2s[i].y); + if (embree_normals != nullptr) { + embree_normals[i] = Vec3fa(p_normals[i].x, p_normals[i].y, p_normals[i].z); + } + embree_triangles[i] = i; + } + + rtcCommitGeometry(embree_mesh); + rtcSetGeometryIntersectFilterFunction(embree_mesh, filter_function); + rtcSetGeometryUserData(embree_mesh, this); + rtcAttachGeometryByID(embree_scene, embree_mesh, p_id); + rtcReleaseGeometry(embree_mesh); +} + +void LightmapRaycasterEmbree::commit() { + rtcCommitScene(embree_scene); +} + +void LightmapRaycasterEmbree::set_mesh_filter(const Set &p_mesh_ids) { + for (Set::Element *E = p_mesh_ids.front(); E; E = E->next()) { + rtcDisableGeometry(rtcGetGeometry(embree_scene, E->get())); + } + rtcCommitScene(embree_scene); + filter_meshes = p_mesh_ids; +} + +void LightmapRaycasterEmbree::clear_mesh_filter() { + for (Set::Element *E = filter_meshes.front(); E; E = E->next()) { + rtcEnableGeometry(rtcGetGeometry(embree_scene, E->get())); + } + rtcCommitScene(embree_scene); + filter_meshes.clear(); +} + +void embree_error_handler(void *p_user_data, RTCError p_code, const char *p_str) { + print_error("Embree error: " + String(p_str)); +} + +LightmapRaycasterEmbree::LightmapRaycasterEmbree() { + embree_device = rtcNewDevice(nullptr); + rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr); + embree_scene = rtcNewScene(embree_device); +} + +LightmapRaycasterEmbree::~LightmapRaycasterEmbree() { + if (embree_scene != nullptr) + rtcReleaseScene(embree_scene); + if (embree_device != nullptr) + rtcReleaseDevice(embree_device); +} diff --git a/modules/raycast/lightmap_raycaster.h b/modules/raycast/lightmap_raycaster.h new file mode 100644 index 000000000000..46ed2dfba2be --- /dev/null +++ b/modules/raycast/lightmap_raycaster.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* lightmap_raycaster.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/object.h" +#include "scene/3d/lightmapper.h" +#include "scene/resources/mesh.h" +#include "thirdparty/embree/include/embree3/rtcore.h" + +class LightmapRaycasterEmbree : public LightmapRaycaster { + GDCLASS(LightmapRaycasterEmbree, LightmapRaycaster); + +private: + struct AlphaTextureData { + Vector data; + Vector2i size; + + uint8_t sample(float u, float v) const; + }; + + RTCDevice embree_device; + RTCScene embree_scene; + + static void filter_function(const struct RTCFilterFunctionNArguments *p_args); + + Map alpha_textures; + Set filter_meshes; + +public: + virtual bool intersect(Ray &p_ray); + + virtual void intersect(Vector &r_rays); + + virtual void add_mesh(const Vector &p_vertices, const Vector &p_normals, const Vector &p_uv2s, unsigned int p_id); + virtual void set_mesh_alpha_texture(Ref p_alpha_texture, unsigned int p_id); + virtual void commit(); + + virtual void set_mesh_filter(const Set &p_mesh_ids); + virtual void clear_mesh_filter(); + + static LightmapRaycaster *create_embree_raycaster(); + static void make_default_raycaster(); + + LightmapRaycasterEmbree(); + ~LightmapRaycasterEmbree(); +}; diff --git a/modules/raycast/register_types.cpp b/modules/raycast/register_types.cpp new file mode 100644 index 000000000000..8009fbbbeccd --- /dev/null +++ b/modules/raycast/register_types.cpp @@ -0,0 +1,39 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" +#include "lightmap_raycaster.h" + +void register_raycast_types() { + LightmapRaycasterEmbree::make_default_raycaster(); +} + +void unregister_raycast_types() { +} diff --git a/modules/raycast/register_types.h b/modules/raycast/register_types.h new file mode 100644 index 000000000000..341359b23708 --- /dev/null +++ b/modules/raycast/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +void register_raycast_types(); +void unregister_raycast_types(); diff --git a/thirdparty/README.md b/thirdparty/README.md index 3a7100f732a5..81d87717ba96 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -39,6 +39,22 @@ Files extracted from upstream source: - all .cpp, .h, and .txt files in ConvectionKernels/ +## embree + +- Upstream: https://github.com/embree/embree +- Version: 3.12.1 (69bd4c272f1ed608494f233ecfff3feec516880b, 2020) +- License: Apache 2.0 + +Files extracted from upstream: + +- All cpp files listed in `modules/raytrace/godot_update_embree.py` +- All header files in the directories listed in `modules/raytrace/godot_update_embree.py` + +Some minor changes have been made in order to fix build errors. +They are marked with `// -- GODOT start --` and `// -- GODOT end --` +comments. Apply the patches in the `patches/` folder when syncing on newer upstream +commits. + ## enet diff --git a/thirdparty/embree/common/algorithms/parallel_any_of.h b/thirdparty/embree/common/algorithms/parallel_any_of.h new file mode 100644 index 000000000000..01f1f80f6c5d --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_any_of.h @@ -0,0 +1,55 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "parallel_reduce.h" + +namespace embree +{ + + template + __forceinline bool parallel_any_of (Index first, Index last, UnaryPredicate pred) + { + bool ret = false; + +#if defined(TASKING_TBB) +#if TBB_INTERFACE_VERSION >= 12002 + tbb::task_group_context context; + tbb::parallel_for(tbb::blocked_range{first, last}, [&ret,pred,&context](const tbb::blocked_range& r) { + if (context.is_group_execution_cancelled()) return; + for (size_t i = r.begin(); i != r.end(); ++i) { + if (pred(i)) { + ret = true; + context.cancel_group_execution(); + } + } + }); +#else + tbb::parallel_for(tbb::blocked_range{first, last}, [&ret,pred](const tbb::blocked_range& r) { + if (tbb::task::self().is_cancelled()) return; + for (size_t i = r.begin(); i != r.end(); ++i) { + if (pred(i)) { + ret = true; + tbb::task::self().cancel_group_execution(); + } + } + }); +#endif +#else + ret = parallel_reduce (first, last, false, [pred](const range& r)->bool { + bool localret = false; + for (auto i=r.begin(); i() + ); +#endif + + return ret; + } + +} // end namespace diff --git a/thirdparty/embree/common/algorithms/parallel_filter.cpp b/thirdparty/embree/common/algorithms/parallel_filter.cpp new file mode 100644 index 000000000000..acddc0ff8168 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_filter.cpp @@ -0,0 +1,56 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_filter.h" +#include "../sys/regression.h" +#include + +namespace embree +{ + struct parallel_filter_regression_test : public RegressionTest + { + parallel_filter_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + auto pred = [&]( uint32_t v ) { return (v & 0x3) == 0; }; + + for (size_t N=10; N<1000000; N=size_t(2.1*N)) + { + size_t N0 = rand() % N; + + /* initialize array with random numbers */ + std::vector src(N); + std::map m; + for (size_t i=0; i + inline Index sequential_filter( Ty* data, const Index first, const Index last, const Predicate& predicate) + { + Index j = first; + for (Index i=first; i + inline Index parallel_filter( Ty* data, const Index begin, const Index end, const Index minStepSize, const Predicate& predicate) + { + /* sequential fallback */ + if (end-begin <= minStepSize) + return sequential_filter(data,begin,end,predicate); + + /* calculate number of tasks to use */ + enum { MAX_TASKS = 64 }; + const Index numThreads = TaskScheduler::threadCount(); + const Index numBlocks = (end-begin+minStepSize-1)/minStepSize; + const Index taskCount = min(numThreads,numBlocks,(Index)MAX_TASKS); + + /* filter blocks */ + Index nused[MAX_TASKS]; + Index nfree[MAX_TASKS]; + parallel_for(taskCount, [&](const Index taskIndex) + { + const Index i0 = begin+(taskIndex+0)*(end-begin)/taskCount; + const Index i1 = begin+(taskIndex+1)*(end-begin)/taskCount; + const Index i2 = sequential_filter(data,i0,i1,predicate); + nused[taskIndex] = i2-i0; + nfree[taskIndex] = i1-i2; + }); + + /* calculate offsets */ + Index sused=0; + Index sfree=0; + Index pfree[MAX_TASKS]; + for (Index i=0; i0; i--) + { + if (k0 > r1) break; + Index k1 = k0+nused[i]; + Index src = begin+(i+0)*(end-begin)/taskCount+nused[i]; + for (Index i=max(r0,k0); i= begin && dst < end); + assert(isrc >= begin && isrc < end); + data[dst++] = data[isrc]; + } + k0 = k1; + } + }); + + return begin+sused; + } +} diff --git a/thirdparty/embree/common/algorithms/parallel_for.cpp b/thirdparty/embree/common/algorithms/parallel_for.cpp new file mode 100644 index 000000000000..ef070ebc4d07 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_for.cpp @@ -0,0 +1,48 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_for.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_for_regression_test : public RegressionTest + { + parallel_for_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + + const size_t M = 10; + for (size_t N=10; N<10000000; N=size_t(2.1*N)) + { + /* sequentially calculate sum of squares */ + size_t sum0 = 0; + for (size_t i=0; i sum1(0); + parallel_for( size_t(0), size_t(N), size_t(1024), [&](const range& r) + { + size_t s = 0; + for (size_t i=r.begin(); i + __forceinline void parallel_for( const Index N, const Func& func) + { +#if defined(TASKING_INTERNAL) + if (N) { + TaskScheduler::spawn(Index(0),N,Index(1),[&] (const range& r) { + assert(r.size() == 1); + func(r.begin()); + }); + if (!TaskScheduler::wait()) + throw std::runtime_error("task cancelled"); + } + +#elif defined(TASKING_TBB) + #if TBB_INTERFACE_VERSION >= 12002 + tbb::task_group_context context; + tbb::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + },context); + if (context.is_group_execution_cancelled()) + throw std::runtime_error("task cancelled"); + #else + tbb::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + }); + if (tbb::task::self().is_cancelled()) + throw std::runtime_error("task cancelled"); + #endif + +#elif defined(TASKING_PPL) + concurrency::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + }); +#else +# error "no tasking system enabled" +#endif + } + + /* parallel for with range and granulatity */ + template + __forceinline void parallel_for( const Index first, const Index last, const Index minStepSize, const Func& func) + { + assert(first <= last); +#if defined(TASKING_INTERNAL) + TaskScheduler::spawn(first,last,minStepSize,func); + if (!TaskScheduler::wait()) + throw std::runtime_error("task cancelled"); + +#elif defined(TASKING_TBB) + #if TBB_INTERFACE_VERSION >= 12002 + tbb::task_group_context context; + tbb::parallel_for(tbb::blocked_range(first,last,minStepSize),[&](const tbb::blocked_range& r) { + func(range(r.begin(),r.end())); + },context); + if (context.is_group_execution_cancelled()) + throw std::runtime_error("task cancelled"); + #else + tbb::parallel_for(tbb::blocked_range(first,last,minStepSize),[&](const tbb::blocked_range& r) { + func(range(r.begin(),r.end())); + }); + if (tbb::task::self().is_cancelled()) + throw std::runtime_error("task cancelled"); + #endif + +#elif defined(TASKING_PPL) + concurrency::parallel_for(first, last, Index(1) /*minStepSize*/, [&](Index i) { + func(range(i,i+1)); + }); + +#else +# error "no tasking system enabled" +#endif + } + + /* parallel for with range */ + template + __forceinline void parallel_for( const Index first, const Index last, const Func& func) + { + assert(first <= last); + parallel_for(first,last,(Index)1,func); + } + +#if defined(TASKING_TBB) && (TBB_INTERFACE_VERSION > 4001) + + template + __forceinline void parallel_for_static( const Index N, const Func& func) + { + #if TBB_INTERFACE_VERSION >= 12002 + tbb::task_group_context context; + tbb::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + },tbb::simple_partitioner(),context); + if (context.is_group_execution_cancelled()) + throw std::runtime_error("task cancelled"); + #else + tbb::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + },tbb::simple_partitioner()); + if (tbb::task::self().is_cancelled()) + throw std::runtime_error("task cancelled"); + #endif + } + + typedef tbb::affinity_partitioner affinity_partitioner; + + template + __forceinline void parallel_for_affinity( const Index N, const Func& func, tbb::affinity_partitioner& ap) + { + #if TBB_INTERFACE_VERSION >= 12002 + tbb::task_group_context context; + tbb::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + },ap,context); + if (context.is_group_execution_cancelled()) + throw std::runtime_error("task cancelled"); + #else + tbb::parallel_for(Index(0),N,Index(1),[&](Index i) { + func(i); + },ap); + if (tbb::task::self().is_cancelled()) + throw std::runtime_error("task cancelled"); + #endif + } + +#else + + template + __forceinline void parallel_for_static( const Index N, const Func& func) + { + parallel_for(N,func); + } + + struct affinity_partitioner { + }; + + template + __forceinline void parallel_for_affinity( const Index N, const Func& func, affinity_partitioner& ap) + { + parallel_for(N,func); + } + +#endif +} diff --git a/thirdparty/embree/common/algorithms/parallel_for_for.cpp b/thirdparty/embree/common/algorithms/parallel_for_for.cpp new file mode 100644 index 000000000000..0337611b350a --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_for_for.cpp @@ -0,0 +1,63 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_for_for.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_for_for_regression_test : public RegressionTest + { + parallel_for_for_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + + /* create vector with random numbers */ + size_t sum0 = 0; + size_t K = 0; + const size_t M = 1000; + std::vector* > array2(M); + for (size_t i=0; i(N); + for (size_t j=0; j> verify_k(K); + for (size_t i=0; i sum1(0); + parallel_for_for( array2, size_t(1), [&](std::vector* v, const range& r, size_t k) -> size_t + { + size_t s = 0; + for (size_t i=r.begin(); i + __forceinline void sequential_for_for( ArrayArray& array2, const size_t minStepSize, const Func& func ) + { + size_t k=0; + for (size_t i=0; i!=array2.size(); ++i) { + const size_t N = array2[i]->size(); + if (N) func(array2[i],range(0,N),k); + k+=N; + } + } + + class ParallelForForState + { + public: + + enum { MAX_TASKS = 64 }; + + __forceinline ParallelForForState () + : taskCount(0) {} + + template + __forceinline ParallelForForState (ArrayArray& array2, const size_t minStepSize) { + init(array2,minStepSize); + } + + template + __forceinline void init ( ArrayArray& array2, const size_t minStepSize ) + { + /* first calculate total number of elements */ + size_t N = 0; + for (size_t i=0; isize() : 0; + } + this->N = N; + + /* calculate number of tasks to use */ + const size_t numThreads = TaskScheduler::threadCount(); + const size_t numBlocks = (N+minStepSize-1)/minStepSize; + taskCount = max(size_t(1),min(numThreads,numBlocks,size_t(ParallelForForState::MAX_TASKS))); + + /* calculate start (i,j) for each task */ + size_t taskIndex = 0; + i0[taskIndex] = 0; + j0[taskIndex] = 0; + size_t k0 = (++taskIndex)*N/taskCount; + for (size_t i=0, k=0; taskIndex < taskCount; i++) + { + assert(isize() : 0; + while (j= k0 && taskIndex < taskCount) { + assert(taskIndex + __forceinline void parallel_for_for( ArrayArray& array2, const size_t minStepSize, const Func& func ) + { + ParallelForForState state(array2,minStepSize); + + parallel_for(state.taskCount, [&](const size_t taskIndex) + { + /* calculate range */ + const size_t k0 = (taskIndex+0)*state.size()/state.taskCount; + const size_t k1 = (taskIndex+1)*state.size()/state.taskCount; + size_t i0 = state.i0[taskIndex]; + size_t j0 = state.j0[taskIndex]; + + /* iterate over arrays */ + size_t k=k0; + for (size_t i=i0; ksize() : 0; + const size_t r0 = j0, r1 = min(N,r0+k1-k); + if (r1 > r0) func(array2[i],range(r0,r1),k); + k+=r1-r0; j0 = 0; + } + }); + } + + template + __forceinline void parallel_for_for( ArrayArray& array2, const Func& func ) + { + parallel_for_for(array2,1,func); + } + + template + __forceinline Value parallel_for_for_reduce( ArrayArray& array2, const size_t minStepSize, const Value& identity, const Func& func, const Reduction& reduction ) + { + ParallelForForState state(array2,minStepSize); + Value temp[ParallelForForState::MAX_TASKS]; + + for (size_t i=0; isize() : 0; + const size_t r0 = j0, r1 = min(N,r0+k1-k); + if (r1 > r0) temp[taskIndex] = reduction(temp[taskIndex],func(array2[i],range(r0,r1),k)); + k+=r1-r0; j0 = 0; + } + }); + + Value ret = identity; + for (size_t i=0; i + __forceinline Value parallel_for_for_reduce( ArrayArray& array2, const Value& identity, const Func& func, const Reduction& reduction) + { + return parallel_for_for_reduce(array2,1,identity,func,reduction); + } +} diff --git a/thirdparty/embree/common/algorithms/parallel_for_for_prefix_sum.cpp b/thirdparty/embree/common/algorithms/parallel_for_for_prefix_sum.cpp new file mode 100644 index 000000000000..0169d8e48189 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_for_for_prefix_sum.cpp @@ -0,0 +1,85 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_for_for_prefix_sum.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_for_for_prefix_sum_regression_test : public RegressionTest + { + parallel_for_for_prefix_sum_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + + /* create vector with random numbers */ + const size_t M = 10; + std::vector> flattened; + typedef std::vector* > ArrayArray; + ArrayArray array2(M); + size_t K = 0; + for (size_t i=0; i(N); + for (size_t j=0; j> verify_k(K); + for (size_t i=0; i state(array2,size_t(1)); + + /* dry run only counts */ + size_t S = parallel_for_for_prefix_sum0( state, array2, size_t(0), [&](std::vector* v, const range& r, size_t k, size_t i) -> size_t + { + size_t s = 0; + for (size_t i=r.begin(); i* v, const range& r, size_t k, size_t i, const size_t base) -> size_t + { + size_t s = 0; + for (size_t i=r.begin(); i + struct ParallelForForPrefixSumState : public ParallelForForState + { + __forceinline ParallelForForPrefixSumState () {} + + template + __forceinline ParallelForForPrefixSumState (ArrayArray& array2, const size_t minStepSize) + : ParallelForForState(array2,minStepSize) {} + + ParallelPrefixSumState prefix_state; + }; + + template + __forceinline Value parallel_for_for_prefix_sum0( ParallelForForPrefixSumState& state, ArrayArray& array2, Index minStepSize, + const Value& identity, const Func& func, const Reduction& reduction) + { + /* calculate number of tasks to use */ + const size_t taskCount = state.taskCount; + /* perform parallel prefix sum */ + parallel_for(taskCount, [&](const size_t taskIndex) + { + const size_t k0 = (taskIndex+0)*state.size()/taskCount; + const size_t k1 = (taskIndex+1)*state.size()/taskCount; + size_t i0 = state.i0[taskIndex]; + size_t j0 = state.j0[taskIndex]; + + /* iterate over arrays */ + size_t k=k0; + Value N=identity; + for (size_t i=i0; ksize() : 0; + const size_t r0 = j0, r1 = min(size,r0+k1-k); + if (r1 > r0) N = reduction(N, func(array2[i],range((Index)r0,(Index)r1),(Index)k,(Index)i)); + k+=r1-r0; j0 = 0; + } + state.prefix_state.counts[taskIndex] = N; + }); + + /* calculate prefix sum */ + Value sum=identity; + for (size_t i=0; i + __forceinline Value parallel_for_for_prefix_sum1( ParallelForForPrefixSumState& state, ArrayArray& array2, Index minStepSize, + const Value& identity, const Func& func, const Reduction& reduction) + { + /* calculate number of tasks to use */ + const size_t taskCount = state.taskCount; + /* perform parallel prefix sum */ + parallel_for(taskCount, [&](const size_t taskIndex) + { + const size_t k0 = (taskIndex+0)*state.size()/taskCount; + const size_t k1 = (taskIndex+1)*state.size()/taskCount; + size_t i0 = state.i0[taskIndex]; + size_t j0 = state.j0[taskIndex]; + + /* iterate over arrays */ + size_t k=k0; + Value N=identity; + for (size_t i=i0; ksize() : 0; + const size_t r0 = j0, r1 = min(size,r0+k1-k); + if (r1 > r0) N = reduction(N, func(array2[i],range((Index)r0,(Index)r1),(Index)k,(Index)i,reduction(state.prefix_state.sums[taskIndex],N))); + k+=r1-r0; j0 = 0; + } + state.prefix_state.counts[taskIndex] = N; + }); + + /* calculate prefix sum */ + Value sum=identity; + for (size_t i=0; i + __forceinline Value parallel_for_for_prefix_sum0( ParallelForForPrefixSumState& state, ArrayArray& array2, + const Value& identity, const Func& func, const Reduction& reduction) + { + return parallel_for_for_prefix_sum0(state,array2,size_t(1),identity,func,reduction); + } + + template + __forceinline Value parallel_for_for_prefix_sum1( ParallelForForPrefixSumState& state, ArrayArray& array2, + const Value& identity, const Func& func, const Reduction& reduction) + { + return parallel_for_for_prefix_sum1(state,array2,size_t(1),identity,func,reduction); + } +} diff --git a/thirdparty/embree/common/algorithms/parallel_map.cpp b/thirdparty/embree/common/algorithms/parallel_map.cpp new file mode 100644 index 000000000000..09dc303f8146 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_map.cpp @@ -0,0 +1,47 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_map.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_map_regression_test : public RegressionTest + { + parallel_map_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + + /* create key/value vectors with random numbers */ + const size_t N = 10000; + std::vector keys(N); + std::vector vals(N); + for (size_t i=0; i map; + map.init(keys,vals); + + /* check that all keys are properly mapped */ + for (size_t i=0; i + class parallel_map + { + /* key/value pair to build the map */ + struct KeyValue + { + __forceinline KeyValue () {} + + __forceinline KeyValue (const Key key, const Val val) + : key(key), val(val) {} + + __forceinline operator Key() const { + return key; + } + + public: + Key key; + Val val; + }; + + public: + + /*! parallel map constructors */ + parallel_map () {} + + /*! construction from pair of vectors */ + template + parallel_map (const KeyVector& keys, const ValVector& values) { init(keys,values); } + + /*! initialized the parallel map from a vector with keys and values */ + template + void init(const KeyVector& keys, const ValVector& values) + { + /* reserve sufficient space for all data */ + assert(keys.size() == values.size()); + vec.resize(keys.size()); + + /* generate key/value pairs */ + parallel_for( size_t(0), keys.size(), size_t(4*4096), [&](const range& r) { + for (size_t i=r.begin(); i temp(keys.size()); + radix_sort(vec.data(),temp.data(),keys.size()); + } + + /*! Returns a pointer to the value associated with the specified key. The pointer will be nullptr of the key is not contained in the map. */ + __forceinline const Val* lookup(const Key& key) const + { + typename std::vector::const_iterator i = std::lower_bound(vec.begin(), vec.end(), key); + if (i == vec.end()) return nullptr; + if (i->key != key) return nullptr; + return &i->val; + } + + /*! If the key is in the map, the function returns the value associated with the key, otherwise it returns the default value. */ + __forceinline Val lookup(const Key& key, const Val& def) const + { + typename std::vector::const_iterator i = std::lower_bound(vec.begin(), vec.end(), key); + if (i == vec.end()) return def; + if (i->key != key) return def; + return i->val; + } + + /*! clears all state */ + void clear() { + vec.clear(); + } + + private: + std::vector vec; //!< vector containing sorted elements + }; +} diff --git a/thirdparty/embree/common/algorithms/parallel_partition.cpp b/thirdparty/embree/common/algorithms/parallel_partition.cpp new file mode 100644 index 000000000000..eb20c4465d2c --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_partition.cpp @@ -0,0 +1,53 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_partition.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_partition_regression_test : public RegressionTest + { + parallel_partition_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + + for (size_t i=0; i<100; i++) + { + /* create random permutation */ + size_t N = std::rand() % 1000000; + std::vector array(N); + for (unsigned i=0; i= split; + } + + return passed; + } + }; + + parallel_partition_regression_test parallel_partition_regression("parallel_partition_regression_test"); +} diff --git a/thirdparty/embree/common/algorithms/parallel_partition.h b/thirdparty/embree/common/algorithms/parallel_partition.h new file mode 100644 index 000000000000..3b3ad7c8541f --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_partition.h @@ -0,0 +1,283 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "parallel_for.h" +#include "../math/range.h" + +namespace embree +{ + /* serial partitioning */ + template + __forceinline size_t serial_partitioning(T* array, + const size_t begin, + const size_t end, + V& leftReduction, + V& rightReduction, + const IsLeft& is_left, + const Reduction_T& reduction_t) + { + T* l = array + begin; + T* r = array + end - 1; + + while(1) + { + /* *l < pivot */ + while (likely(l <= r && is_left(*l) )) + { + //prefetchw(l+4); // FIXME: enable? + reduction_t(leftReduction,*l); + ++l; + } + /* *r >= pivot) */ + while (likely(l <= r && !is_left(*r))) + { + //prefetchw(r-4); FIXME: enable? + reduction_t(rightReduction,*r); + --r; + } + if (r + class __aligned(64) parallel_partition_task + { + ALIGNED_CLASS_(64); + private: + + static const size_t MAX_TASKS = 64; + + T* array; + size_t N; + const IsLeft& is_left; + const Reduction_T& reduction_t; + const Reduction_V& reduction_v; + const Vi& identity; + + size_t numTasks; + __aligned(64) size_t counter_start[MAX_TASKS+1]; + __aligned(64) size_t counter_left[MAX_TASKS+1]; + __aligned(64) range leftMisplacedRanges[MAX_TASKS]; + __aligned(64) range rightMisplacedRanges[MAX_TASKS]; + __aligned(64) V leftReductions[MAX_TASKS]; + __aligned(64) V rightReductions[MAX_TASKS]; + + public: + + __forceinline parallel_partition_task(T* array, + const size_t N, + const Vi& identity, + const IsLeft& is_left, + const Reduction_T& reduction_t, + const Reduction_V& reduction_v, + const size_t BLOCK_SIZE) + + : array(array), N(N), is_left(is_left), reduction_t(reduction_t), reduction_v(reduction_v), identity(identity), + numTasks(min((N+BLOCK_SIZE-1)/BLOCK_SIZE,min(TaskScheduler::threadCount(),MAX_TASKS))) {} + + __forceinline const range* findStartRange(size_t& index, const range* const r, const size_t numRanges) + { + size_t i = 0; + while(index >= (size_t)r[i].size()) + { + assert(i < numRanges); + index -= (size_t)r[i].size(); + i++; + } + return &r[i]; + } + + __forceinline void swapItemsInMisplacedRanges(const size_t numLeftMisplacedRanges, + const size_t numRightMisplacedRanges, + const size_t startID, + const size_t endID) + { + size_t leftLocalIndex = startID; + size_t rightLocalIndex = startID; + const range* l_range = findStartRange(leftLocalIndex,leftMisplacedRanges,numLeftMisplacedRanges); + const range* r_range = findStartRange(rightLocalIndex,rightMisplacedRanges,numRightMisplacedRanges); + + size_t l_left = l_range->size() - leftLocalIndex; + size_t r_left = r_range->size() - rightLocalIndex; + T *__restrict__ l = &array[l_range->begin() + leftLocalIndex]; + T *__restrict__ r = &array[r_range->begin() + rightLocalIndex]; + size_t size = endID - startID; + size_t items = min(size,min(l_left,r_left)); + + while (size) + { + if (unlikely(l_left == 0)) + { + l_range++; + l_left = l_range->size(); + l = &array[l_range->begin()]; + items = min(size,min(l_left,r_left)); + } + + if (unlikely(r_left == 0)) + { + r_range++; + r_left = r_range->size(); + r = &array[r_range->begin()]; + items = min(size,min(l_left,r_left)); + } + + size -= items; + l_left -= items; + r_left -= items; + + while(items) { + items--; + xchg(*l++,*r++); + } + } + } + + __forceinline size_t partition(V& leftReduction, V& rightReduction) + { + /* partition the individual ranges for each task */ + parallel_for(numTasks,[&] (const size_t taskID) { + const size_t startID = (taskID+0)*N/numTasks; + const size_t endID = (taskID+1)*N/numTasks; + V local_left(identity); + V local_right(identity); + const size_t mid = serial_partitioning(array,startID,endID,local_left,local_right,is_left,reduction_t); + counter_start[taskID] = startID; + counter_left [taskID] = mid-startID; + leftReductions[taskID] = local_left; + rightReductions[taskID] = local_right; + }); + counter_start[numTasks] = N; + counter_left[numTasks] = 0; + + /* finalize the reductions */ + for (size_t i=0; i globalLeft (0,mid); + const range globalRight(mid,N); + + /* calculate all left and right ranges that are on the wrong global side */ + size_t numMisplacedRangesLeft = 0; + size_t numMisplacedRangesRight = 0; + size_t numMisplacedItemsLeft = 0; + size_t numMisplacedItemsRight = 0; + + for (size_t i=0; i left_range (counter_start[i], counter_start[i] + counter_left[i]); + const range right_range(counter_start[i] + counter_left[i], counter_start[i+1]); + const range left_misplaced = globalLeft. intersect(right_range); + const range right_misplaced = globalRight.intersect(left_range); + + if (!left_misplaced.empty()) + { + numMisplacedItemsLeft += left_misplaced.size(); + leftMisplacedRanges[numMisplacedRangesLeft++] = left_misplaced; + } + + if (!right_misplaced.empty()) + { + numMisplacedItemsRight += right_misplaced.size(); + rightMisplacedRanges[numMisplacedRangesRight++] = right_misplaced; + } + } + assert( numMisplacedItemsLeft == numMisplacedItemsRight ); + + /* if no items are misplaced we are done */ + if (numMisplacedItemsLeft == 0) + return mid; + + /* otherwise we copy the items to the right place in parallel */ + parallel_for(numTasks,[&] (const size_t taskID) { + const size_t startID = (taskID+0)*numMisplacedItemsLeft/numTasks; + const size_t endID = (taskID+1)*numMisplacedItemsLeft/numTasks; + swapItemsInMisplacedRanges(numMisplacedRangesLeft,numMisplacedRangesRight,startID,endID); + }); + + return mid; + } + }; + + template + __noinline size_t parallel_partitioning(T* array, + const size_t begin, + const size_t end, + const Vi &identity, + V &leftReduction, + V &rightReduction, + const IsLeft& is_left, + const Reduction_T& reduction_t, + const Reduction_V& reduction_v, + size_t BLOCK_SIZE = 128) + { + /* fall back to single threaded partitioning for small N */ + if (unlikely(end-begin < BLOCK_SIZE)) + return serial_partitioning(array,begin,end,leftReduction,rightReduction,is_left,reduction_t); + + /* otherwise use parallel code */ + else { + typedef parallel_partition_task partition_task; + std::unique_ptr p(new partition_task(&array[begin],end-begin,identity,is_left,reduction_t,reduction_v,BLOCK_SIZE)); + return begin+p->partition(leftReduction,rightReduction); + } + } + + template + __noinline size_t parallel_partitioning(T* array, + const size_t begin, + const size_t end, + const Vi &identity, + V &leftReduction, + V &rightReduction, + const IsLeft& is_left, + const Reduction_T& reduction_t, + const Reduction_V& reduction_v, + size_t BLOCK_SIZE, + size_t PARALLEL_THRESHOLD) + { + /* fall back to single threaded partitioning for small N */ + if (unlikely(end-begin < PARALLEL_THRESHOLD)) + return serial_partitioning(array,begin,end,leftReduction,rightReduction,is_left,reduction_t); + + /* otherwise use parallel code */ + else { + typedef parallel_partition_task partition_task; + std::unique_ptr p(new partition_task(&array[begin],end-begin,identity,is_left,reduction_t,reduction_v,BLOCK_SIZE)); + return begin+p->partition(leftReduction,rightReduction); + } + } + + + template + inline size_t parallel_partitioning(T* array, + const size_t begin, + const size_t end, + const IsLeft& is_left, + size_t BLOCK_SIZE = 128) + { + size_t leftReduction = 0; + size_t rightReduction = 0; + return parallel_partitioning( + array,begin,end,0,leftReduction,rightReduction,is_left, + [] (size_t& t,const T& ref) { }, + [] (size_t& t0,size_t& t1) { }, + BLOCK_SIZE); + } + +} diff --git a/thirdparty/embree/common/algorithms/parallel_prefix_sum.cpp b/thirdparty/embree/common/algorithms/parallel_prefix_sum.cpp new file mode 100644 index 000000000000..685952c3dce5 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_prefix_sum.cpp @@ -0,0 +1,48 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_prefix_sum.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_prefix_sum_regression_test : public RegressionTest + { + parallel_prefix_sum_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + const size_t M = 10; + + for (size_t N=10; N<10000000; N=size_t(2.1*N)) + { + /* initialize array with random numbers */ + uint32_t sum0 = 0; + std::vector src(N); + for (size_t i=0; i dst(N); + for (auto& v : dst) v = 0; + + for (size_t i=0; i()); + passed &= (sum0 == sum1); + } + + /* check if prefix sum is correct */ + for (size_t i=0, sum=0; i + struct ParallelPrefixSumState + { + enum { MAX_TASKS = 64 }; + Value counts[MAX_TASKS]; + Value sums [MAX_TASKS]; + }; + + template + __forceinline Value parallel_prefix_sum( ParallelPrefixSumState& state, Index first, Index last, Index minStepSize, const Value& identity, const Func& func, const Reduction& reduction) + { + /* calculate number of tasks to use */ + const size_t numThreads = TaskScheduler::threadCount(); + const size_t numBlocks = (last-first+minStepSize-1)/minStepSize; + const size_t taskCount = min(numThreads,numBlocks,size_t(ParallelPrefixSumState::MAX_TASKS)); + + /* perform parallel prefix sum */ + parallel_for(taskCount, [&](const size_t taskIndex) + { + const size_t i0 = first+(taskIndex+0)*(last-first)/taskCount; + const size_t i1 = first+(taskIndex+1)*(last-first)/taskCount; + state.counts[taskIndex] = func(range(i0,i1),state.sums[taskIndex]); + }); + + /* calculate prefix sum */ + Value sum=identity; + for (size_t i=0; i + __forceinline Value parallel_prefix_sum(const SrcArray& src, DstArray& dst, size_t N, const Value& identity, const Add& add, const size_t SINGLE_THREAD_THRESHOLD = 4096) + { + /* perform single threaded prefix operation for small N */ + if (N < SINGLE_THREAD_THRESHOLD) + { + Value sum=identity; + for (size_t i=0; i state; + + /* initial run just sets up start values for subtasks */ + parallel_prefix_sum( state, size_t(0), size_t(N), size_t(1024), identity, [&](const range& r, const Value& sum) -> Value { + + Value s = identity; + for (size_t i=r.begin(); i& r, const Value& sum) -> Value { + + Value s = identity; + for (size_t i=r.begin(); i& r) -> size_t + { + size_t s = 0; + for (size_t i=r.begin(); i + __forceinline Value sequential_reduce( const Index first, const Index last, const Value& identity, const Func& func, const Reduction& reduction ) + { + return func(range(first,last)); + } + + template + __forceinline Value sequential_reduce( const Index first, const Index last, const Index minStepSize, const Value& identity, const Func& func, const Reduction& reduction ) + { + return func(range(first,last)); + } + + template + __noinline Value parallel_reduce_internal( Index taskCount, const Index first, const Index last, const Index minStepSize, const Value& identity, const Func& func, const Reduction& reduction ) + { + const Index maxTasks = 512; + const Index threadCount = (Index) TaskScheduler::threadCount(); + taskCount = min(taskCount,threadCount,maxTasks); + + /* parallel invokation of all tasks */ + dynamic_large_stack_array(Value,values,taskCount,8192); // consumes at most 8192 bytes on the stack + parallel_for(taskCount, [&](const Index taskIndex) { + const Index k0 = first+(taskIndex+0)*(last-first)/taskCount; + const Index k1 = first+(taskIndex+1)*(last-first)/taskCount; + values[taskIndex] = func(range(k0,k1)); + }); + + /* perform reduction over all tasks */ + Value v = identity; + for (Index i=0; i + __forceinline Value parallel_reduce( const Index first, const Index last, const Index minStepSize, const Value& identity, const Func& func, const Reduction& reduction ) + { +#if defined(TASKING_INTERNAL) + + /* fast path for small number of iterations */ + Index taskCount = (last-first+minStepSize-1)/minStepSize; + if (likely(taskCount == 1)) { + return func(range(first,last)); + } + return parallel_reduce_internal(taskCount,first,last,minStepSize,identity,func,reduction); + +#elif defined(TASKING_TBB) + #if TBB_INTERFACE_VERSION >= 12002 + tbb::task_group_context context; + const Value v = tbb::parallel_reduce(tbb::blocked_range(first,last,minStepSize),identity, + [&](const tbb::blocked_range& r, const Value& start) { return reduction(start,func(range(r.begin(),r.end()))); }, + reduction,context); + if (context.is_group_execution_cancelled()) + throw std::runtime_error("task cancelled"); + return v; + #else + const Value v = tbb::parallel_reduce(tbb::blocked_range(first,last,minStepSize),identity, + [&](const tbb::blocked_range& r, const Value& start) { return reduction(start,func(range(r.begin(),r.end()))); }, + reduction); + if (tbb::task::self().is_cancelled()) + throw std::runtime_error("task cancelled"); + return v; + #endif +#else // TASKING_PPL + struct AlignedValue + { + char storage[__alignof(Value)+sizeof(Value)]; + static uintptr_t alignUp(uintptr_t p, size_t a) { return p + (~(p - 1) % a); }; + Value* getValuePtr() { return reinterpret_cast(alignUp(uintptr_t(storage), __alignof(Value))); } + const Value* getValuePtr() const { return reinterpret_cast(alignUp(uintptr_t(storage), __alignof(Value))); } + AlignedValue(const Value& v) { new(getValuePtr()) Value(v); } + AlignedValue(const AlignedValue& v) { new(getValuePtr()) Value(*v.getValuePtr()); } + AlignedValue(const AlignedValue&& v) { new(getValuePtr()) Value(*v.getValuePtr()); }; + AlignedValue& operator = (const AlignedValue& v) { *getValuePtr() = *v.getValuePtr(); return *this; }; + AlignedValue& operator = (const AlignedValue&& v) { *getValuePtr() = *v.getValuePtr(); return *this; }; + operator Value() const { return *getValuePtr(); } + }; + + struct Iterator_Index + { + Index v; + typedef std::forward_iterator_tag iterator_category; + typedef AlignedValue value_type; + typedef Index difference_type; + typedef Index distance_type; + typedef AlignedValue* pointer; + typedef AlignedValue& reference; + __forceinline Iterator_Index() {} + __forceinline Iterator_Index(Index v) : v(v) {} + __forceinline bool operator== (Iterator_Index other) { return v == other.v; } + __forceinline bool operator!= (Iterator_Index other) { return v != other.v; } + __forceinline Iterator_Index operator++() { return Iterator_Index(++v); } + __forceinline Iterator_Index operator++(int) { return Iterator_Index(v++); } + }; + + auto range_reduction = [&](Iterator_Index begin, Iterator_Index end, const AlignedValue& start) { + assert(begin.v < end.v); + return reduction(start, func(range(begin.v, end.v))); + }; + const Value v = concurrency::parallel_reduce(Iterator_Index(first), Iterator_Index(last), AlignedValue(identity), range_reduction, reduction); + return v; +#endif + } + + template + __forceinline Value parallel_reduce( const Index first, const Index last, const Index minStepSize, const Index parallel_threshold, const Value& identity, const Func& func, const Reduction& reduction ) + { + if (likely(last-first < parallel_threshold)) { + return func(range(first,last)); + } else { + return parallel_reduce(first,last,minStepSize,identity,func,reduction); + } + } + + template + __forceinline Value parallel_reduce( const range range, const Index minStepSize, const Index parallel_threshold, const Value& identity, const Func& func, const Reduction& reduction ) + { + return parallel_reduce(range.begin(),range.end(),minStepSize,parallel_threshold,identity,func,reduction); + } + + template + __forceinline Value parallel_reduce( const Index first, const Index last, const Value& identity, const Func& func, const Reduction& reduction ) + { + auto funcr = [&] ( const range r ) { + Value v = identity; + for (Index i=r.begin(); i + __forceinline Value parallel_reduce( const range range, const Value& identity, const Func& func, const Reduction& reduction ) + { + return parallel_reduce(range.begin(),range.end(),Index(1),identity,func,reduction); + } +} diff --git a/thirdparty/embree/common/algorithms/parallel_set.cpp b/thirdparty/embree/common/algorithms/parallel_set.cpp new file mode 100644 index 000000000000..20b639c1c9b5 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_set.cpp @@ -0,0 +1,43 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_set.h" +#include "../sys/regression.h" + +namespace embree +{ + struct parallel_set_regression_test : public RegressionTest + { + parallel_set_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + + /* create vector with random numbers */ + const size_t N = 10000; + std::vector unsorted(N); + for (size_t i=0; i sorted; + sorted.init(unsorted); + + /* check that all elements are in the set */ + for (size_t i=0; i + class parallel_set + { + public: + + /*! default constructor for the parallel set */ + parallel_set () {} + + /*! construction from vector */ + template + parallel_set (const Vector& in) { init(in); } + + /*! initialized the parallel set from a vector */ + template + void init(const Vector& in) + { + /* copy data to internal vector */ + vec.resize(in.size()); + parallel_for( size_t(0), in.size(), size_t(4*4096), [&](const range& r) { + for (size_t i=r.begin(); i temp(in.size()); + radix_sort(vec.data(),temp.data(),vec.size()); + } + + /*! tests if some element is in the set */ + __forceinline bool lookup(const T& elt) const { + return std::binary_search(vec.begin(), vec.end(), elt); + } + + /*! clears all state */ + void clear() { + vec.clear(); + } + + private: + std::vector vec; //!< vector containing sorted elements + }; +} diff --git a/thirdparty/embree/common/algorithms/parallel_sort.cpp b/thirdparty/embree/common/algorithms/parallel_sort.cpp new file mode 100644 index 000000000000..5e7ec79ac191 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_sort.cpp @@ -0,0 +1,50 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "parallel_sort.h" +#include "../sys/regression.h" + +namespace embree +{ + template + struct RadixSortRegressionTest : public RegressionTest + { + RadixSortRegressionTest(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + const size_t M = 10; + + for (size_t N=10; N<1000000; N=size_t(2.1*N)) + { + std::vector src(N); memset(src.data(),0,N*sizeof(Key)); + std::vector tmp(N); memset(tmp.data(),0,N*sizeof(Key)); + for (size_t i=0; i(src.data(),tmp.data(),N); + } + + /* calculate checksum */ + Key sum1 = 0; for (size_t i=0; i test_u32("RadixSortRegressionTestU32"); + RadixSortRegressionTest test_u64("RadixSortRegressionTestU64"); +} diff --git a/thirdparty/embree/common/algorithms/parallel_sort.h b/thirdparty/embree/common/algorithms/parallel_sort.h new file mode 100644 index 000000000000..5a3382079375 --- /dev/null +++ b/thirdparty/embree/common/algorithms/parallel_sort.h @@ -0,0 +1,454 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../simd/simd.h" +#include "parallel_for.h" +#include + +namespace embree +{ + template + __forceinline void insertionsort_ascending(T *__restrict__ array, const size_t length) + { + for(size_t i = 1;i 0 && v < array[j-1]) + { + array[j] = array[j-1]; + --j; + } + array[j] = v; + } + } + + template + __forceinline void insertionsort_decending(T *__restrict__ array, const size_t length) + { + for(size_t i = 1;i 0 && v > array[j-1]) + { + array[j] = array[j-1]; + --j; + } + array[j] = v; + } + } + + template + void quicksort_ascending(T *__restrict__ t, + const ssize_t begin, + const ssize_t end) + { + if (likely(begin < end)) + { + const T pivotvalue = t[begin]; + ssize_t left = begin - 1; + ssize_t right = end + 1; + + while(1) + { + while (t[--right] > pivotvalue); + while (t[++left] < pivotvalue); + + if (left >= right) break; + + const T temp = t[right]; + t[right] = t[left]; + t[left] = temp; + } + + const int pivot = right; + quicksort_ascending(t, begin, pivot); + quicksort_ascending(t, pivot + 1, end); + } + } + + template + void quicksort_decending(T *__restrict__ t, + const ssize_t begin, + const ssize_t end) + { + if (likely(begin < end)) + { + const T pivotvalue = t[begin]; + ssize_t left = begin - 1; + ssize_t right = end + 1; + + while(1) + { + while (t[--right] < pivotvalue); + while (t[++left] > pivotvalue); + + if (left >= right) break; + + const T temp = t[right]; + t[right] = t[left]; + t[left] = temp; + } + + const int pivot = right; + quicksort_decending(t, begin, pivot); + quicksort_decending(t, pivot + 1, end); + } + } + + + template + void quicksort_insertionsort_ascending(T *__restrict__ t, + const ssize_t begin, + const ssize_t end) + { + if (likely(begin < end)) + { + const ssize_t size = end-begin+1; + if (likely(size <= THRESHOLD)) + { + insertionsort_ascending(&t[begin],size); + } + else + { + const T pivotvalue = t[begin]; + ssize_t left = begin - 1; + ssize_t right = end + 1; + + while(1) + { + while (t[--right] > pivotvalue); + while (t[++left] < pivotvalue); + + if (left >= right) break; + + const T temp = t[right]; + t[right] = t[left]; + t[left] = temp; + } + + const ssize_t pivot = right; + quicksort_insertionsort_ascending(t, begin, pivot); + quicksort_insertionsort_ascending(t, pivot + 1, end); + } + } + } + + + template + void quicksort_insertionsort_decending(T *__restrict__ t, + const ssize_t begin, + const ssize_t end) + { + if (likely(begin < end)) + { + const ssize_t size = end-begin+1; + if (likely(size <= THRESHOLD)) + { + insertionsort_decending(&t[begin],size); + } + else + { + + const T pivotvalue = t[begin]; + ssize_t left = begin - 1; + ssize_t right = end + 1; + + while(1) + { + while (t[--right] < pivotvalue); + while (t[++left] > pivotvalue); + + if (left >= right) break; + + const T temp = t[right]; + t[right] = t[left]; + t[left] = temp; + } + + const ssize_t pivot = right; + quicksort_insertionsort_decending(t, begin, pivot); + quicksort_insertionsort_decending(t, pivot + 1, end); + } + } + } + + template + static void radixsort32(T* const morton, const size_t num, const unsigned int shift = 3*8) + { + static const unsigned int BITS = 8; + static const unsigned int BUCKETS = (1 << BITS); + static const unsigned int CMP_SORT_THRESHOLD = 16; + + __aligned(64) unsigned int count[BUCKETS]; + + /* clear buckets */ + for (size_t i=0;i> shift) & (BUCKETS-1)]++; + + /* prefix sums */ + __aligned(64) unsigned int head[BUCKETS]; + __aligned(64) unsigned int tail[BUCKETS]; + + head[0] = 0; + for (size_t i=1; i> shift) & (BUCKETS-1); + if (b == i) break; + std::swap(v,morton[head[b]++]); + } + assert((unsigned(v) >> shift & (BUCKETS-1)) == i); + morton[head[i]++] = v; + } + } + if (shift == 0) return; + + size_t offset = 0; + for (size_t i=0;i> shift) & (BUCKETS-1)) == i); + + if (unlikely(count[i] < CMP_SORT_THRESHOLD)) + insertionsort_ascending(morton + offset, count[i]); + else + radixsort32(morton + offset, count[i], shift-BITS); + + for (size_t j=offset;j + class ParallelRadixSort + { + static const size_t MAX_TASKS = 64; + static const size_t BITS = 8; + static const size_t BUCKETS = (1 << BITS); + typedef unsigned int TyRadixCount[BUCKETS]; + + template + static bool compare(const T& v0, const T& v1) { + return (Key)v0 < (Key)v1; + } + + private: + ParallelRadixSort (const ParallelRadixSort& other) DELETED; // do not implement + ParallelRadixSort& operator= (const ParallelRadixSort& other) DELETED; // do not implement + + + public: + ParallelRadixSort (Ty* const src, Ty* const tmp, const size_t N) + : radixCount(nullptr), src(src), tmp(tmp), N(N) {} + + void sort(const size_t blockSize) + { + assert(blockSize > 0); + + /* perform single threaded sort for small N */ + if (N<=blockSize) // handles also special case of 0! + { + /* do inplace sort inside destination array */ + std::sort(src,src+N,compare); + } + + /* perform parallel sort for large N */ + else + { + const size_t numThreads = min((N+blockSize-1)/blockSize,TaskScheduler::threadCount(),size_t(MAX_TASKS)); + tbbRadixSort(numThreads); + } + } + + ~ParallelRadixSort() + { + alignedFree(radixCount); + radixCount = nullptr; + } + + private: + + void tbbRadixIteration0(const Key shift, + const Ty* __restrict const src, + Ty* __restrict const dst, + const size_t threadIndex, const size_t threadCount) + { + const size_t startID = (threadIndex+0)*N/threadCount; + const size_t endID = (threadIndex+1)*N/threadCount; + + /* mask to extract some number of bits */ + const Key mask = BUCKETS-1; + + /* count how many items go into the buckets */ + for (size_t i=0; i> (size_t)shift) & (size_t)mask; +#else + const Key index = ((Key)src[i] >> shift) & mask; +#endif + count[index]++; + } + } + + void tbbRadixIteration1(const Key shift, + const Ty* __restrict const src, + Ty* __restrict const dst, + const size_t threadIndex, const size_t threadCount) + { + const size_t startID = (threadIndex+0)*N/threadCount; + const size_t endID = (threadIndex+1)*N/threadCount; + + /* mask to extract some number of bits */ + const Key mask = BUCKETS-1; + + /* calculate total number of items for each bucket */ + __aligned(64) unsigned int total[BUCKETS]; + /* + for (size_t i=0; i> (size_t)shift) & (size_t)mask; +#else + const size_t index = ((Key)src[i] >> shift) & mask; +#endif + dst[offset[index]++] = elt; + } + } + + void tbbRadixIteration(const Key shift, const bool last, + const Ty* __restrict src, Ty* __restrict dst, + const size_t numTasks) + { + affinity_partitioner ap; + parallel_for_affinity(numTasks,[&] (size_t taskIndex) { tbbRadixIteration0(shift,src,dst,taskIndex,numTasks); },ap); + parallel_for_affinity(numTasks,[&] (size_t taskIndex) { tbbRadixIteration1(shift,src,dst,taskIndex,numTasks); },ap); + } + + void tbbRadixSort(const size_t numTasks) + { + radixCount = (TyRadixCount*) alignedMalloc(MAX_TASKS*sizeof(TyRadixCount),64); + + if (sizeof(Key) == sizeof(uint32_t)) { + tbbRadixIteration(0*BITS,0,src,tmp,numTasks); + tbbRadixIteration(1*BITS,0,tmp,src,numTasks); + tbbRadixIteration(2*BITS,0,src,tmp,numTasks); + tbbRadixIteration(3*BITS,1,tmp,src,numTasks); + } + else if (sizeof(Key) == sizeof(uint64_t)) + { + tbbRadixIteration(0*BITS,0,src,tmp,numTasks); + tbbRadixIteration(1*BITS,0,tmp,src,numTasks); + tbbRadixIteration(2*BITS,0,src,tmp,numTasks); + tbbRadixIteration(3*BITS,0,tmp,src,numTasks); + tbbRadixIteration(4*BITS,0,src,tmp,numTasks); + tbbRadixIteration(5*BITS,0,tmp,src,numTasks); + tbbRadixIteration(6*BITS,0,src,tmp,numTasks); + tbbRadixIteration(7*BITS,1,tmp,src,numTasks); + } + } + + private: + TyRadixCount* radixCount; + Ty* const src; + Ty* const tmp; + const size_t N; + }; + + template + void radix_sort(Ty* const src, Ty* const tmp, const size_t N, const size_t blockSize = 8192) + { + ParallelRadixSort(src,tmp,N).sort(blockSize); + } + + template + void radix_sort(Ty* const src, Ty* const tmp, const size_t N, const size_t blockSize = 8192) + { + ParallelRadixSort(src,tmp,N).sort(blockSize); + } + + template + void radix_sort_u32(Ty* const src, Ty* const tmp, const size_t N, const size_t blockSize = 8192) { + radix_sort(src,tmp,N,blockSize); + } + + template + void radix_sort_u64(Ty* const src, Ty* const tmp, const size_t N, const size_t blockSize = 8192) { + radix_sort(src,tmp,N,blockSize); + } +} diff --git a/thirdparty/embree/common/lexers/parsestream.h b/thirdparty/embree/common/lexers/parsestream.h new file mode 100644 index 000000000000..db46dc114fb2 --- /dev/null +++ b/thirdparty/embree/common/lexers/parsestream.h @@ -0,0 +1,101 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "stringstream.h" +#include "../sys/filename.h" +#include "../math/vec2.h" +#include "../math/vec3.h" +#include "../math/col3.h" +#include "../math/color.h" + +namespace embree +{ + /*! helper class for simple command line parsing */ + class ParseStream : public Stream + { + public: + ParseStream (const Ref >& cin) : cin(cin) {} + + ParseStream (const Ref >& cin, const std::string& seps = "\n\t\r ", + const std::string& endl = "", bool multiLine = false) + : cin(new StringStream(cin,seps,endl,multiLine)) {} + + public: + ParseLocation location() { return cin->loc(); } + std::string next() { return cin->get(); } + + void force(const std::string& next) { + std::string token = getString(); + if (token != next) + THROW_RUNTIME_ERROR("token \""+next+"\" expected but token \""+token+"\" found"); + } + + std::string getString() { + return get(); + } + + FileName getFileName() { + return FileName(get()); + } + + int getInt () { + return atoi(get().c_str()); + } + + Vec2i getVec2i() { + int x = atoi(get().c_str()); + int y = atoi(get().c_str()); + return Vec2i(x,y); + } + + Vec3ia getVec3ia() { + int x = atoi(get().c_str()); + int y = atoi(get().c_str()); + int z = atoi(get().c_str()); + return Vec3ia(x,y,z); + } + + float getFloat() { + return (float)atof(get().c_str()); + } + + Vec2f getVec2f() { + float x = (float)atof(get().c_str()); + float y = (float)atof(get().c_str()); + return Vec2f(x,y); + } + + Vec3f getVec3f() { + float x = (float)atof(get().c_str()); + float y = (float)atof(get().c_str()); + float z = (float)atof(get().c_str()); + return Vec3f(x,y,z); + } + + Vec3fa getVec3fa() { + float x = (float)atof(get().c_str()); + float y = (float)atof(get().c_str()); + float z = (float)atof(get().c_str()); + return Vec3fa(x,y,z); + } + + Col3f getCol3f() { + float x = (float)atof(get().c_str()); + float y = (float)atof(get().c_str()); + float z = (float)atof(get().c_str()); + return Col3f(x,y,z); + } + + Color getColor() { + float r = (float)atof(get().c_str()); + float g = (float)atof(get().c_str()); + float b = (float)atof(get().c_str()); + return Color(r,g,b); + } + + private: + Ref > cin; + }; +} diff --git a/thirdparty/embree/common/lexers/stream.h b/thirdparty/embree/common/lexers/stream.h new file mode 100644 index 000000000000..3f75677e6874 --- /dev/null +++ b/thirdparty/embree/common/lexers/stream.h @@ -0,0 +1,215 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/ref.h" +#include "../sys/filename.h" +#include "../sys/string.h" + +#include +#include +#include +#include + +namespace embree +{ + /*! stores the location of a stream element in the source */ + class ParseLocation + { + public: + ParseLocation () : lineNumber(-1), colNumber(-1) {} + ParseLocation (std::shared_ptr fileName, ssize_t lineNumber, ssize_t colNumber, ssize_t /*charNumber*/) + : fileName(fileName), lineNumber(lineNumber), colNumber(colNumber) {} + + std::string str() const + { + std::string str = "unknown"; + if (fileName) str = *fileName; + if (lineNumber >= 0) str += " line " + toString(lineNumber); + if (lineNumber >= 0 && colNumber >= 0) str += " character " + toString(colNumber); + return str; + } + + private: + std::shared_ptr fileName; /// name of the file (or stream) the token is from + ssize_t lineNumber; /// the line number the token is from + ssize_t colNumber; /// the character number in the current line + }; + + /*! a stream class templated over the stream elements */ + template class Stream : public RefCount + { + enum { BUF_SIZE = 1024 }; + + private: + virtual T next() = 0; + virtual ParseLocation location() = 0; + __forceinline std::pair nextHelper() { + ParseLocation l = location(); + T v = next(); + return std::pair(v,l); + } + __forceinline void push_back(const std::pair& v) { + if (past+future == BUF_SIZE) pop_front(); + size_t end = (start+past+future++)%BUF_SIZE; + buffer[end] = v; + } + __forceinline void pop_front() { + if (past == 0) THROW_RUNTIME_ERROR("stream buffer empty"); + start = (start+1)%BUF_SIZE; past--; + } + public: + Stream () : start(0), past(0), future(0), buffer(BUF_SIZE) {} + virtual ~Stream() {} + + public: + + const ParseLocation& loc() { + if (future == 0) push_back(nextHelper()); + return buffer[(start+past)%BUF_SIZE].second; + } + T get() { + if (future == 0) push_back(nextHelper()); + T t = buffer[(start+past)%BUF_SIZE].first; + past++; future--; + return t; + } + const T& peek() { + if (future == 0) push_back(nextHelper()); + return buffer[(start+past)%BUF_SIZE].first; + } + const T& unget(size_t n = 1) { + if (past < n) THROW_RUNTIME_ERROR ("cannot unget that many items"); + past -= n; future += n; + return peek(); + } + void drop() { + if (future == 0) push_back(nextHelper()); + past++; future--; + } + private: + size_t start,past,future; + std::vector > buffer; + }; + + /*! warps an iostream stream */ + class StdStream : public Stream + { + public: + StdStream (std::istream& cin, const std::string& name = "std::stream") + : cin(cin), lineNumber(1), colNumber(0), charNumber(0), name(std::shared_ptr(new std::string(name))) {} + ~StdStream() {} + ParseLocation location() { + return ParseLocation(name,lineNumber,colNumber,charNumber); + } + int next() { + int c = cin.get(); + if (c == '\n') { lineNumber++; colNumber = 0; } else if (c != '\r') colNumber++; + charNumber++; + return c; + } + private: + std::istream& cin; + ssize_t lineNumber; /// the line number the token is from + ssize_t colNumber; /// the character number in the current line + ssize_t charNumber; /// the character in the file + std::shared_ptr name; /// name of buffer + }; + + /*! creates a stream from a file */ + class FileStream : public Stream + { + public: + + FileStream (FILE* file, const std::string& name = "file") + : file(file), lineNumber(1), colNumber(0), charNumber(0), name(std::shared_ptr(new std::string(name))) {} + + FileStream (const FileName& fileName) + : lineNumber(1), colNumber(0), charNumber(0), name(std::shared_ptr(new std::string(fileName.str()))) + { + file = fopen(fileName.c_str(),"r"); + if (file == nullptr) THROW_RUNTIME_ERROR("cannot open file " + fileName.str()); + } + ~FileStream() { if (file) fclose(file); } + + public: + ParseLocation location() { + return ParseLocation(name,lineNumber,colNumber,charNumber); + } + + int next() { + int c = fgetc(file); + if (c == '\n') { lineNumber++; colNumber = 0; } else if (c != '\r') colNumber++; + charNumber++; + return c; + } + + private: + FILE* file; + ssize_t lineNumber; /// the line number the token is from + ssize_t colNumber; /// the character number in the current line + ssize_t charNumber; /// the character in the file + std::shared_ptr name; /// name of buffer + }; + + /*! creates a stream from a string */ + class StrStream : public Stream + { + public: + + StrStream (const char* str) + : str(str), lineNumber(1), colNumber(0), charNumber(0) {} + + public: + ParseLocation location() { + return ParseLocation(std::shared_ptr(),lineNumber,colNumber,charNumber); + } + + int next() { + int c = str[charNumber]; + if (c == 0) return EOF; + if (c == '\n') { lineNumber++; colNumber = 0; } else if (c != '\r') colNumber++; + charNumber++; + return c; + } + + private: + const char* str; + ssize_t lineNumber; /// the line number the token is from + ssize_t colNumber; /// the character number in the current line + ssize_t charNumber; /// the character in the file + }; + + /*! creates a character stream from a command line */ + class CommandLineStream : public Stream + { + public: + CommandLineStream (int argc, char** argv, const std::string& name = "command line") + : i(0), j(0), charNumber(0), name(std::shared_ptr(new std::string(name))) + { + if (argc > 0) { + for (size_t i=0; argv[0][i] && i<1024; i++) charNumber++; + charNumber++; + } + for (ssize_t k=1; k args; + ssize_t charNumber; /// the character in the file + std::shared_ptr name; /// name of buffer + }; +} diff --git a/thirdparty/embree/common/lexers/streamfilters.h b/thirdparty/embree/common/lexers/streamfilters.h new file mode 100644 index 000000000000..25580a77b8ed --- /dev/null +++ b/thirdparty/embree/common/lexers/streamfilters.h @@ -0,0 +1,39 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "stream.h" + +namespace embree +{ + /* removes all line comments from a stream */ + class LineCommentFilter : public Stream + { + public: + LineCommentFilter (const FileName& fileName, const std::string& lineComment) + : cin(new FileStream(fileName)), lineComment(lineComment) {} + LineCommentFilter (Ref > cin, const std::string& lineComment) + : cin(cin), lineComment(lineComment) {} + + ParseLocation location() { return cin->loc(); } + + int next() + { + /* look if the line comment starts here */ + for (size_t j=0; jpeek() != lineComment[j]) { cin->unget(j); goto not_found; } + cin->get(); + } + /* eat all characters until the end of the line (or file) */ + while (cin->peek() != '\n' && cin->peek() != EOF) cin->get(); + + not_found: + return cin->get(); + } + + private: + Ref > cin; + std::string lineComment; + }; +} diff --git a/thirdparty/embree/common/lexers/stringstream.cpp b/thirdparty/embree/common/lexers/stringstream.cpp new file mode 100644 index 000000000000..7e7b9faef800 --- /dev/null +++ b/thirdparty/embree/common/lexers/stringstream.cpp @@ -0,0 +1,48 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "stringstream.h" + +namespace embree +{ + static const std::string stringChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _.,+-=:/*\\"; + + /* creates map for fast categorization of characters */ + static void createCharMap(bool map[256], const std::string& chrs) { + for (size_t i=0; i<256; i++) map[i] = false; + for (size_t i=0; i >& cin, const std::string& seps, const std::string& endl, bool multiLine) + : cin(cin), endl(endl), multiLine(multiLine) + { + createCharMap(isSepMap,seps); + createCharMap(isValidCharMap,stringChars); + } + + std::string StringStream::next() + { + /* skip separators */ + while (cin->peek() != EOF) { + if (endl != "" && cin->peek() == '\n') { cin->drop(); return endl; } + if (multiLine && cin->peek() == '\\') { + cin->drop(); + if (cin->peek() == '\n') { cin->drop(); continue; } + cin->unget(); + } + if (!isSeparator(cin->peek())) break; + cin->drop(); + } + + /* parse everything until the next separator */ + std::vector str; str.reserve(64); + while (cin->peek() != EOF && !isSeparator(cin->peek())) { + int c = cin->get(); + if (!isValidChar(c)) throw std::runtime_error("invalid character "+std::string(1,c)+" in input"); + str.push_back((char)c); + } + str.push_back(0); + return std::string(str.data()); + } +} diff --git a/thirdparty/embree/common/lexers/stringstream.h b/thirdparty/embree/common/lexers/stringstream.h new file mode 100644 index 000000000000..e6dbd4aecc24 --- /dev/null +++ b/thirdparty/embree/common/lexers/stringstream.h @@ -0,0 +1,29 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "stream.h" + +namespace embree +{ + /*! simple tokenizer that produces a string stream */ + class StringStream : public Stream + { + public: + StringStream(const Ref >& cin, const std::string& seps = "\n\t\r ", + const std::string& endl = "", bool multiLine = false); + public: + ParseLocation location() { return cin->loc(); } + std::string next(); + private: + __forceinline bool isSeparator(unsigned int c) const { return c<256 && isSepMap[c]; } + __forceinline bool isValidChar(unsigned int c) const { return c<256 && isValidCharMap[c]; } + private: + Ref > cin; /*! source character stream */ + bool isSepMap[256]; /*! map for fast classification of separators */ + bool isValidCharMap[256]; /*! map for valid characters */ + std::string endl; /*! the token of the end of line */ + bool multiLine; /*! whether to parse lines wrapped with \ */ + }; +} diff --git a/thirdparty/embree/common/lexers/tokenstream.cpp b/thirdparty/embree/common/lexers/tokenstream.cpp new file mode 100644 index 000000000000..d05be65862f8 --- /dev/null +++ b/thirdparty/embree/common/lexers/tokenstream.cpp @@ -0,0 +1,181 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "tokenstream.h" +#include "../math/math.h" + +namespace embree +{ + /* shorthands for common sets of characters */ + const std::string TokenStream::alpha = "abcdefghijklmnopqrstuvwxyz"; + const std::string TokenStream::ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const std::string TokenStream::numbers = "0123456789"; + const std::string TokenStream::separators = "\n\t\r "; + const std::string TokenStream::stringChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _.,+-=:/*\\"; + + /* creates map for fast categorization of characters */ + static void createCharMap(bool map[256], const std::string& chrs) { + for (size_t i=0; i<256; i++) map[i] = false; + for (size_t i=0; i >& cin, //< stream to read from + const std::string& alpha, //< valid characters for identifiers + const std::string& seps, //< characters that act as separators + const std::vector& symbols) //< symbols + : cin(cin), symbols(symbols) + { + createCharMap(isAlphaMap,alpha); + createCharMap(isSepMap,seps); + createCharMap(isStringCharMap,stringChars); + } + + bool TokenStream::decDigits(std::string& str_o) + { + bool ok = false; + std::string str; + if (cin->peek() == '+' || cin->peek() == '-') str += (char)cin->get(); + while (isDigit(cin->peek())) { ok = true; str += (char)cin->get(); } + if (ok) str_o += str; + else cin->unget(str.size()); + return ok; + } + + bool TokenStream::decDigits1(std::string& str_o) + { + bool ok = false; + std::string str; + while (isDigit(cin->peek())) { ok = true; str += (char)cin->get(); } + if (ok) str_o += str; else cin->unget(str.size()); + return ok; + } + + bool TokenStream::trySymbol(const std::string& symbol) + { + size_t pos = 0; + while (pos < symbol.size()) { + if (symbol[pos] != cin->peek()) { cin->unget(pos); return false; } + cin->drop(); pos++; + } + return true; + } + + bool TokenStream::trySymbols(Token& token, const ParseLocation& loc) + { + for (size_t i=0; ipeek() == '.') { + str += (char)cin->get(); + decDigits(str); + if (cin->peek() == 'e' || cin->peek() == 'E') { + str += (char)cin->get(); + if (decDigits(str)) ok = true; // 1.[2]E2 + } + else ok = true; // 1.[2] + } + else if (cin->peek() == 'e' || cin->peek() == 'E') { + str += (char)cin->get(); + if (decDigits(str)) ok = true; // 1E2 + } + } + else + { + if (cin->peek() == '.') { + str += (char)cin->get(); + if (decDigits(str)) { + if (cin->peek() == 'e' || cin->peek() == 'E') { + str += (char)cin->get(); + if (decDigits(str)) ok = true; // .3E2 + } + else ok = true; // .3 + } + } + } + if (ok) { + token = Token((float)atof(str.c_str()),loc); + } + else cin->unget(str.size()); + return ok; + } + + bool TokenStream::tryInt(Token& token, const ParseLocation& loc) { + std::string str; + if (decDigits(str)) { + token = Token(atoi(str.c_str()),loc); + return true; + } + return false; + } + + bool TokenStream::tryString(Token& token, const ParseLocation& loc) + { + std::string str; + if (cin->peek() != '\"') return false; + cin->drop(); + while (cin->peek() != '\"') { + const int c = cin->get(); + if (!isStringChar(c)) THROW_RUNTIME_ERROR("invalid string character "+std::string(1,c)+" at "+loc.str()); + str += (char)c; + } + cin->drop(); + token = Token(str,Token::TY_STRING,loc); + return true; + } + + bool TokenStream::tryIdentifier(Token& token, const ParseLocation& loc) + { + std::string str; + if (!isAlpha(cin->peek())) return false; + str += (char)cin->get(); + while (isAlphaNum(cin->peek())) str += (char)cin->get(); + token = Token(str,Token::TY_IDENTIFIER,loc); + return true; + } + + void TokenStream::skipSeparators() + { + /* skip separators */ + while (cin->peek() != EOF && isSeparator(cin->peek())) + cin->drop(); + } + + Token TokenStream::next() + { + Token token; + skipSeparators(); + ParseLocation loc = cin->loc(); + if (trySymbols (token,loc)) return token; /**< try to parse a symbol */ + if (tryFloat (token,loc)) return token; /**< try to parse float */ + if (tryInt (token,loc)) return token; /**< try to parse integer */ + if (tryString (token,loc)) return token; /**< try to parse string */ + if (tryIdentifier(token,loc)) return token; /**< try to parse identifier */ + if (cin->peek() == EOF ) return Token(loc); /**< return EOF token */ + return Token((char)cin->get(),loc); /**< return invalid character token */ + } +} diff --git a/thirdparty/embree/common/lexers/tokenstream.h b/thirdparty/embree/common/lexers/tokenstream.h new file mode 100644 index 000000000000..72a7b4f2f335 --- /dev/null +++ b/thirdparty/embree/common/lexers/tokenstream.h @@ -0,0 +1,164 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "stream.h" +#include +#include + +namespace embree +{ + /*! token class */ + class Token + { + public: + + enum Type { TY_EOF, TY_CHAR, TY_INT, TY_FLOAT, TY_IDENTIFIER, TY_STRING, TY_SYMBOL }; + + Token ( const ParseLocation& loc = ParseLocation()) : ty(TY_EOF ), loc(loc) {} + Token (char c, const ParseLocation& loc = ParseLocation()) : ty(TY_CHAR ), c(c), loc(loc) {} + Token (int i, const ParseLocation& loc = ParseLocation()) : ty(TY_INT ), i(i), loc(loc) {} + Token (float f,const ParseLocation& loc = ParseLocation()) : ty(TY_FLOAT), f(f), loc(loc) {} + Token (std::string str, Type ty, const ParseLocation& loc = ParseLocation()) : ty(ty), str(str), loc(loc) {} + + static Token Eof() { return Token(); } + static Token Sym(std::string str) { return Token(str,TY_SYMBOL); } + static Token Str(std::string str) { return Token(str,TY_STRING); } + static Token Id (std::string str) { return Token(str,TY_IDENTIFIER); } + + char Char() const { + if (ty == TY_CHAR) return c; + THROW_RUNTIME_ERROR(loc.str()+": character expected"); + } + + int Int() const { + if (ty == TY_INT) return i; + THROW_RUNTIME_ERROR(loc.str()+": integer expected"); + } + + float Float(bool cast = true) const { + if (ty == TY_FLOAT) return f; + if (ty == TY_INT && cast) return (float)i; + THROW_RUNTIME_ERROR(loc.str()+": float expected"); + } + + std::string Identifier() const { + if (ty == TY_IDENTIFIER) return str; + THROW_RUNTIME_ERROR(loc.str()+": identifier expected"); + } + + std::string String() const { + if (ty == TY_STRING) return str; + THROW_RUNTIME_ERROR(loc.str()+": string expected"); + } + + std::string Symbol() const { + if (ty == TY_SYMBOL) return str; + THROW_RUNTIME_ERROR(loc.str()+": symbol expected"); + } + + const ParseLocation& Location() const { return loc; } + + friend bool operator==(const Token& a, const Token& b) + { + if (a.ty != b.ty) return false; + if (a.ty == TY_CHAR) return a.c == b.c; + if (a.ty == TY_INT) return a.i == b.i; + if (a.ty == TY_FLOAT) return a.f == b.f; + if (a.ty == TY_IDENTIFIER) return a.str == b.str; + if (a.ty == TY_STRING) return a.str == b.str; + if (a.ty == TY_SYMBOL) return a.str == b.str; + return true; + } + + friend bool operator!=(const Token& a, const Token& b) { + return !(a == b); + } + + friend bool operator <( const Token& a, const Token& b ) { + if (a.ty != b.ty) return (int)a.ty < (int)b.ty; + if (a.ty == TY_CHAR) return a.c < b.c; + if (a.ty == TY_INT) return a.i < b.i; + if (a.ty == TY_FLOAT) return a.f < b.f; + if (a.ty == TY_IDENTIFIER) return a.str < b.str; + if (a.ty == TY_STRING) return a.str < b.str; + if (a.ty == TY_SYMBOL) return a.str < b.str; + return false; + } + + friend std::ostream& operator<<(std::ostream& cout, const Token& t) + { + if (t.ty == TY_EOF) return cout << "eof"; + if (t.ty == TY_CHAR) return cout << "Char(" << t.c << ")"; + if (t.ty == TY_INT) return cout << "Int(" << t.i << ")"; + if (t.ty == TY_FLOAT) return cout << "Float(" << t.f << ")"; + if (t.ty == TY_IDENTIFIER) return cout << "Id(" << t.str << ")"; + if (t.ty == TY_STRING) return cout << "String(" << t.str << ")"; + if (t.ty == TY_SYMBOL) return cout << "Symbol(" << t.str << ")"; + return cout << "unknown"; + } + + private: + Type ty; //< the type of the token + union { + char c; //< data for char tokens + int i; //< data for int tokens + float f; //< data for float tokens + }; + std::string str; //< data for string and identifier tokens + ParseLocation loc; //< the location the token is from + }; + + /*! build full tokenizer that takes list of valid characters and keywords */ + class TokenStream : public Stream + { + public: + + /*! shorthands for common sets of characters */ + static const std::string alpha; + static const std::string ALPHA; + static const std::string numbers; + static const std::string separators; + static const std::string stringChars; + + public: + TokenStream(const Ref >& cin, + const std::string& alpha, //< valid characters for identifiers + const std::string& seps, //< characters that act as separators + const std::vector& symbols = std::vector()); //< symbols + public: + ParseLocation location() { return cin->loc(); } + Token next(); + bool trySymbol(const std::string& symbol); + + private: + void skipSeparators(); + bool decDigits(std::string& str); + bool decDigits1(std::string& str); + bool trySymbols(Token& token, const ParseLocation& loc); + bool tryFloat(Token& token, const ParseLocation& loc); + bool tryInt(Token& token, const ParseLocation& loc); + bool tryString(Token& token, const ParseLocation& loc); + bool tryIdentifier(Token& token, const ParseLocation& loc); + + Ref > cin; + bool isSepMap[256]; + bool isAlphaMap[256]; + bool isStringCharMap[256]; + std::vector symbols; + + /*! checks if a character is a separator */ + __forceinline bool isSeparator(unsigned int c) const { return c<256 && isSepMap[c]; } + + /*! checks if a character is a number */ + __forceinline bool isDigit(unsigned int c) const { return c >= '0' && c <= '9'; } + + /*! checks if a character is valid inside a string */ + __forceinline bool isStringChar(unsigned int c) const { return c<256 && isStringCharMap[c]; } + + /*! checks if a character is legal for an identifier */ + __forceinline bool isAlpha(unsigned int c) const { return c<256 && isAlphaMap[c]; } + __forceinline bool isAlphaNum(unsigned int c) const { return isAlpha(c) || isDigit(c); } + }; +} diff --git a/thirdparty/embree/common/math/affinespace.h b/thirdparty/embree/common/math/affinespace.h new file mode 100644 index 000000000000..32452fbe72b5 --- /dev/null +++ b/thirdparty/embree/common/math/affinespace.h @@ -0,0 +1,361 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "linearspace2.h" +#include "linearspace3.h" +#include "quaternion.h" +#include "bbox.h" +#include "vec4.h" + +namespace embree +{ + #define VectorT typename L::Vector + #define ScalarT typename L::Vector::Scalar + + //////////////////////////////////////////////////////////////////////////////// + // Affine Space + //////////////////////////////////////////////////////////////////////////////// + + template + struct AffineSpaceT + { + L l; /*< linear part of affine space */ + VectorT p; /*< affine part of affine space */ + + //////////////////////////////////////////////////////////////////////////////// + // Constructors, Assignment, Cast, Copy Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline AffineSpaceT ( ) { } + __forceinline AffineSpaceT ( const AffineSpaceT& other ) { l = other.l; p = other.p; } + __forceinline AffineSpaceT ( const L & other ) { l = other ; p = VectorT(zero); } + __forceinline AffineSpaceT& operator=( const AffineSpaceT& other ) { l = other.l; p = other.p; return *this; } + + __forceinline AffineSpaceT( const VectorT& vx, const VectorT& vy, const VectorT& vz, const VectorT& p ) : l(vx,vy,vz), p(p) {} + __forceinline AffineSpaceT( const L& l, const VectorT& p ) : l(l), p(p) {} + + template __forceinline AffineSpaceT( const AffineSpaceT& s ) : l(s.l), p(s.p) {} + + //////////////////////////////////////////////////////////////////////////////// + // Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline AffineSpaceT( ZeroTy ) : l(zero), p(zero) {} + __forceinline AffineSpaceT( OneTy ) : l(one), p(zero) {} + + /*! return matrix for scaling */ + static __forceinline AffineSpaceT scale(const VectorT& s) { return L::scale(s); } + + /*! return matrix for translation */ + static __forceinline AffineSpaceT translate(const VectorT& p) { return AffineSpaceT(one,p); } + + /*! return matrix for rotation, only in 2D */ + static __forceinline AffineSpaceT rotate(const ScalarT& r) { return L::rotate(r); } + + /*! return matrix for rotation around arbitrary point (2D) or axis (3D) */ + static __forceinline AffineSpaceT rotate(const VectorT& u, const ScalarT& r) { return L::rotate(u,r); } + + /*! return matrix for rotation around arbitrary axis and point, only in 3D */ + static __forceinline AffineSpaceT rotate(const VectorT& p, const VectorT& u, const ScalarT& r) { return translate(+p) * rotate(u,r) * translate(-p); } + + /*! return matrix for looking at given point, only in 3D */ + static __forceinline AffineSpaceT lookat(const VectorT& eye, const VectorT& point, const VectorT& up) { + VectorT Z = normalize(point-eye); + VectorT U = normalize(cross(up,Z)); + VectorT V = normalize(cross(Z,U)); + return AffineSpaceT(L(U,V,Z),eye); + } + + }; + + // template specialization to get correct identity matrix for type AffineSpace3fa + template<> + __forceinline AffineSpaceT::AffineSpaceT( OneTy ) : l(one), p(0.f, 0.f, 0.f, 1.f) {} + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline AffineSpaceT operator -( const AffineSpaceT& a ) { return AffineSpaceT(-a.l,-a.p); } + template __forceinline AffineSpaceT operator +( const AffineSpaceT& a ) { return AffineSpaceT(+a.l,+a.p); } + template __forceinline AffineSpaceT rcp( const AffineSpaceT& a ) { L il = rcp(a.l); return AffineSpaceT(il,-(il*a.p)); } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline const AffineSpaceT operator +( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l+b.l,a.p+b.p); } + template __forceinline const AffineSpaceT operator -( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l-b.l,a.p-b.p); } + + template __forceinline const AffineSpaceT operator *( const ScalarT & a, const AffineSpaceT& b ) { return AffineSpaceT(a*b.l,a*b.p); } + template __forceinline const AffineSpaceT operator *( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l*b.l,a.l*b.p+a.p); } + template __forceinline const AffineSpaceT operator /( const AffineSpaceT& a, const AffineSpaceT& b ) { return a * rcp(b); } + template __forceinline const AffineSpaceT operator /( const AffineSpaceT& a, const ScalarT & b ) { return a * rcp(b); } + + template __forceinline AffineSpaceT& operator *=( AffineSpaceT& a, const AffineSpaceT& b ) { return a = a * b; } + template __forceinline AffineSpaceT& operator *=( AffineSpaceT& a, const ScalarT & b ) { return a = a * b; } + template __forceinline AffineSpaceT& operator /=( AffineSpaceT& a, const AffineSpaceT& b ) { return a = a / b; } + template __forceinline AffineSpaceT& operator /=( AffineSpaceT& a, const ScalarT & b ) { return a = a / b; } + + template __forceinline VectorT xfmPoint (const AffineSpaceT& m, const VectorT& p) { return madd(VectorT(p.x),m.l.vx,madd(VectorT(p.y),m.l.vy,madd(VectorT(p.z),m.l.vz,m.p))); } + template __forceinline VectorT xfmVector(const AffineSpaceT& m, const VectorT& v) { return xfmVector(m.l,v); } + template __forceinline VectorT xfmNormal(const AffineSpaceT& m, const VectorT& n) { return xfmNormal(m.l,n); } + + __forceinline const BBox xfmBounds(const AffineSpaceT >& m, const BBox& b) + { + BBox3fa dst = empty; + const Vec3fa p0(b.lower.x,b.lower.y,b.lower.z); dst.extend(xfmPoint(m,p0)); + const Vec3fa p1(b.lower.x,b.lower.y,b.upper.z); dst.extend(xfmPoint(m,p1)); + const Vec3fa p2(b.lower.x,b.upper.y,b.lower.z); dst.extend(xfmPoint(m,p2)); + const Vec3fa p3(b.lower.x,b.upper.y,b.upper.z); dst.extend(xfmPoint(m,p3)); + const Vec3fa p4(b.upper.x,b.lower.y,b.lower.z); dst.extend(xfmPoint(m,p4)); + const Vec3fa p5(b.upper.x,b.lower.y,b.upper.z); dst.extend(xfmPoint(m,p5)); + const Vec3fa p6(b.upper.x,b.upper.y,b.lower.z); dst.extend(xfmPoint(m,p6)); + const Vec3fa p7(b.upper.x,b.upper.y,b.upper.z); dst.extend(xfmPoint(m,p7)); + return dst; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const AffineSpaceT& a, const AffineSpaceT& b ) { return a.l == b.l && a.p == b.p; } + template __forceinline bool operator !=( const AffineSpaceT& a, const AffineSpaceT& b ) { return a.l != b.l || a.p != b.p; } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline AffineSpaceT select ( const typename L::Vector::Scalar::Bool& s, const AffineSpaceT& t, const AffineSpaceT& f ) { + return AffineSpaceT(select(s,t.l,f.l),select(s,t.p,f.p)); + } + + //////////////////////////////////////////////////////////////////////////////// + // Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template static embree_ostream operator<<(embree_ostream cout, const AffineSpaceT& m) { + return cout << "{ l = " << m.l << ", p = " << m.p << " }"; + } + + //////////////////////////////////////////////////////////////////////////////// + // Template Instantiations + //////////////////////////////////////////////////////////////////////////////// + + typedef AffineSpaceT AffineSpace2f; + typedef AffineSpaceT AffineSpace3f; + typedef AffineSpaceT AffineSpace3fa; + typedef AffineSpaceT AffineSpace3fx; + typedef AffineSpaceT AffineSpace3ff; + typedef AffineSpaceT OrthonormalSpace3f; + + template using AffineSpace3vf = AffineSpaceT>>>; + typedef AffineSpaceT>>> AffineSpace3vf4; + typedef AffineSpaceT>>> AffineSpace3vf8; + typedef AffineSpaceT>>> AffineSpace3vf16; + + template using AffineSpace3vff = AffineSpaceT>>>; + typedef AffineSpaceT>>> AffineSpace3vfa4; + typedef AffineSpaceT>>> AffineSpace3vfa8; + typedef AffineSpaceT>>> AffineSpace3vfa16; + + ////////////////////////////////////////////////////////////////////////////// + /// Interpolation + ////////////////////////////////////////////////////////////////////////////// + template + __forceinline AffineSpaceT lerp(const AffineSpaceT& M0, + const AffineSpaceT& M1, + const R& t) + { + return AffineSpaceT(lerp(M0.l,M1.l,t),lerp(M0.p,M1.p,t)); + } + + // slerp interprets the 16 floats of the matrix M = D * R * S as components of + // three matrizes (D, R, S) that are interpolated individually. + template __forceinline AffineSpaceT>> + slerp(const AffineSpaceT>>& M0, + const AffineSpaceT>>& M1, + const T& t) + { + QuaternionT q0(M0.p.w, M0.l.vx.w, M0.l.vy.w, M0.l.vz.w); + QuaternionT q1(M1.p.w, M1.l.vx.w, M1.l.vy.w, M1.l.vz.w); + QuaternionT q = slerp(q0, q1, t); + + AffineSpaceT>> S = lerp(M0, M1, t); + AffineSpaceT>> D(one); + D.p.x = S.l.vx.y; + D.p.y = S.l.vx.z; + D.p.z = S.l.vy.z; + S.l.vx.y = 0; + S.l.vx.z = 0; + S.l.vy.z = 0; + + AffineSpaceT>> R = LinearSpace3>(q); + return D * R * S; + } + + // this is a specialized version for Vec3fa because that does + // not play along nicely with the other templated Vec3/Vec4 types + __forceinline AffineSpace3fa slerp(const AffineSpace3ff& M0, + const AffineSpace3ff& M1, + const float& t) + { + Quaternion3f q0(M0.p.w, M0.l.vx.w, M0.l.vy.w, M0.l.vz.w); + Quaternion3f q1(M1.p.w, M1.l.vx.w, M1.l.vy.w, M1.l.vz.w); + Quaternion3f q = slerp(q0, q1, t); + + AffineSpace3fa S = lerp(M0, M1, t); + AffineSpace3fa D(one); + D.p.x = S.l.vx.y; + D.p.y = S.l.vx.z; + D.p.z = S.l.vy.z; + S.l.vx.y = 0; + S.l.vx.z = 0; + S.l.vy.z = 0; + + AffineSpace3fa R = LinearSpace3fa(q); + return D * R * S; + } + + __forceinline AffineSpace3fa quaternionDecompositionToAffineSpace(const AffineSpace3ff& qd) + { + // compute affine transform from quaternion decomposition + Quaternion3f q(qd.p.w, qd.l.vx.w, qd.l.vy.w, qd.l.vz.w); + AffineSpace3fa M = qd; + AffineSpace3fa D(one); + D.p.x = M.l.vx.y; + D.p.y = M.l.vx.z; + D.p.z = M.l.vy.z; + M.l.vx.y = 0; + M.l.vx.z = 0; + M.l.vy.z = 0; + AffineSpace3fa R = LinearSpace3fa(q); + return D * R * M; + } + + __forceinline void quaternionDecomposition(const AffineSpace3ff& qd, Vec3fa& T, Quaternion3f& q, AffineSpace3fa& S) + { + q = Quaternion3f(qd.p.w, qd.l.vx.w, qd.l.vy.w, qd.l.vz.w); + S = qd; + T.x = qd.l.vx.y; + T.y = qd.l.vx.z; + T.z = qd.l.vy.z; + S.l.vx.y = 0; + S.l.vx.z = 0; + S.l.vy.z = 0; + } + + __forceinline AffineSpace3fx quaternionDecomposition(Vec3fa const& T, Quaternion3f const& q, AffineSpace3fa const& S) + { + AffineSpace3ff M = S; + M.l.vx.w = q.i; + M.l.vy.w = q.j; + M.l.vz.w = q.k; + M.p.w = q.r; + M.l.vx.y = T.x; + M.l.vx.z = T.y; + M.l.vy.z = T.z; + return M; + } + + struct __aligned(16) QuaternionDecomposition + { + float scale_x = 1.f; + float scale_y = 1.f; + float scale_z = 1.f; + float skew_xy = 0.f; + float skew_xz = 0.f; + float skew_yz = 0.f; + float shift_x = 0.f; + float shift_y = 0.f; + float shift_z = 0.f; + float quaternion_r = 1.f; + float quaternion_i = 0.f; + float quaternion_j = 0.f; + float quaternion_k = 0.f; + float translation_x = 0.f; + float translation_y = 0.f; + float translation_z = 0.f; + }; + + __forceinline QuaternionDecomposition quaternionDecomposition(AffineSpace3ff const& M) + { + QuaternionDecomposition qd; + qd.scale_x = M.l.vx.x; + qd.scale_y = M.l.vy.y; + qd.scale_z = M.l.vz.z; + qd.shift_x = M.p.x; + qd.shift_y = M.p.y; + qd.shift_z = M.p.z; + qd.translation_x = M.l.vx.y; + qd.translation_y = M.l.vx.z; + qd.translation_z = M.l.vy.z; + qd.skew_xy = M.l.vy.x; + qd.skew_xz = M.l.vz.x; + qd.skew_yz = M.l.vz.y; + qd.quaternion_r = M.p.w; + qd.quaternion_i = M.l.vx.w; + qd.quaternion_j = M.l.vy.w; + qd.quaternion_k = M.l.vz.w; + return qd; + } + + //////////////////////////////////////////////////////////////////////////////// + /* + * ! Template Specialization for 2D: return matrix for rotation around point + * (rotation around arbitrarty vector is not meaningful in 2D) + */ + template<> __forceinline + AffineSpace2f AffineSpace2f::rotate(const Vec2f& p, const float& r) { + return translate(+p)*AffineSpace2f(LinearSpace2f::rotate(r))*translate(-p); + } + + //////////////////////////////////////////////////////////////////////////////// + // Similarity Transform + // + // checks, if M is a similarity transformation, i.e if there exists a factor D + // such that for all x,y: distance(Mx, My) = D * distance(x, y) + //////////////////////////////////////////////////////////////////////////////// + __forceinline bool similarityTransform(const AffineSpace3fa& M, float* D) + { + if (D) *D = 0.f; + if (abs(dot(M.l.vx, M.l.vy)) > 1e-5f) return false; + if (abs(dot(M.l.vx, M.l.vz)) > 1e-5f) return false; + if (abs(dot(M.l.vy, M.l.vz)) > 1e-5f) return false; + + const float D_x = dot(M.l.vx, M.l.vx); + const float D_y = dot(M.l.vy, M.l.vy); + const float D_z = dot(M.l.vz, M.l.vz); + + if (abs(D_x - D_y) > 1e-5f || + abs(D_x - D_z) > 1e-5f || + abs(D_y - D_z) > 1e-5f) + return false; + + if (D) *D = sqrtf(D_x); + return true; + } + + __forceinline void AffineSpace3fa_store_unaligned(const AffineSpace3fa &source, AffineSpace3fa* ptr) + { + Vec3fa::storeu(&ptr->l.vx, source.l.vx); + Vec3fa::storeu(&ptr->l.vy, source.l.vy); + Vec3fa::storeu(&ptr->l.vz, source.l.vz); + Vec3fa::storeu(&ptr->p, source.p); + } + + __forceinline AffineSpace3fa AffineSpace3fa_load_unaligned(AffineSpace3fa* ptr) + { + AffineSpace3fa space; + space.l.vx = Vec3fa::loadu(&ptr->l.vx); + space.l.vy = Vec3fa::loadu(&ptr->l.vy); + space.l.vz = Vec3fa::loadu(&ptr->l.vz); + space.p = Vec3fa::loadu(&ptr->p); + return space; + } + + #undef VectorT + #undef ScalarT +} diff --git a/thirdparty/embree/common/math/bbox.h b/thirdparty/embree/common/math/bbox.h new file mode 100644 index 000000000000..24d5b87223a2 --- /dev/null +++ b/thirdparty/embree/common/math/bbox.h @@ -0,0 +1,331 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "vec2.h" +#include "vec3.h" + +namespace embree +{ + namespace internal { + + template __forceinline T divideByTwo(const T& v) { return v / T(2); } + template <> __forceinline float divideByTwo(const float& v) { return v * 0.5f; } + template <> __forceinline double divideByTwo(const double& v) { return v * 0.5; } + + } // namespace internal + template + struct BBox + { + T lower, upper; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline BBox ( ) { } + template + __forceinline BBox ( const BBox& other ) : lower(other.lower), upper(other.upper) {} + __forceinline BBox& operator=( const BBox& other ) { lower = other.lower; upper = other.upper; return *this; } + + __forceinline BBox ( const T& v ) : lower(v), upper(v) {} + __forceinline BBox ( const T& lower, const T& upper ) : lower(lower), upper(upper) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Extending Bounds + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const BBox& extend(const BBox& other) { lower = min(lower,other.lower); upper = max(upper,other.upper); return *this; } + __forceinline const BBox& extend(const T & other) { lower = min(lower,other ); upper = max(upper,other ); return *this; } + + /*! tests if box is empty */ + __forceinline bool empty() const { for (int i=0; i upper[i]) return true; return false; } + + /*! computes the size of the box */ + __forceinline T size() const { return upper - lower; } + + /*! computes the center of the box */ + __forceinline T center() const { return internal::divideByTwo(lower+upper); } + + /*! computes twice the center of the box */ + __forceinline T center2() const { return lower+upper; } + + /*! merges two boxes */ + __forceinline static const BBox merge (const BBox& a, const BBox& b) { + return BBox(min(a.lower, b.lower), max(a.upper, b.upper)); + } + + /*! enlarge box by some scaling factor */ + __forceinline BBox enlarge_by(const float a) const { + return BBox(lower - T(a)*abs(lower), upper + T(a)*abs(upper)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline BBox( EmptyTy ) : lower(pos_inf), upper(neg_inf) {} + __forceinline BBox( FullTy ) : lower(neg_inf), upper(pos_inf) {} + __forceinline BBox( FalseTy ) : lower(pos_inf), upper(neg_inf) {} + __forceinline BBox( TrueTy ) : lower(neg_inf), upper(pos_inf) {} + __forceinline BBox( NegInfTy ): lower(pos_inf), upper(neg_inf) {} + __forceinline BBox( PosInfTy ): lower(neg_inf), upper(pos_inf) {} + }; + + template<> __forceinline bool BBox::empty() const { + return lower > upper; + } + +#if defined(__SSE__) + template<> __forceinline bool BBox::empty() const { + return !all(le_mask(lower,upper)); + } + template<> __forceinline bool BBox::empty() const { + return !all(le_mask(lower,upper)); + } +#endif + + /*! tests if box is finite */ + __forceinline bool isvalid( const BBox& v ) { + return all(gt_mask(v.lower,Vec3fa_t(-FLT_LARGE)) & lt_mask(v.upper,Vec3fa_t(+FLT_LARGE))); + } + + /*! tests if box is finite and non-empty*/ + __forceinline bool isvalid_non_empty( const BBox& v ) { + return all(gt_mask(v.lower,Vec3fa_t(-FLT_LARGE)) & lt_mask(v.upper,Vec3fa_t(+FLT_LARGE)) & le_mask(v.lower,v.upper)); + } + + /*! tests if box has finite entries */ + __forceinline bool is_finite( const BBox& b) { + return is_finite(b.lower) && is_finite(b.upper); + } + + /*! test if point contained in box */ + __forceinline bool inside ( const BBox& b, const Vec3fa& p ) { return all(ge_mask(p,b.lower) & le_mask(p,b.upper)); } + + /*! computes the center of the box */ + template __forceinline const T center2(const BBox& box) { return box.lower + box.upper; } + template __forceinline const T center (const BBox& box) { return internal::divideByTwo(center2(box)); } + + /*! computes the volume of a bounding box */ + __forceinline float volume ( const BBox& b ) { return reduce_mul(b.size()); } + __forceinline float safeVolume( const BBox& b ) { if (b.empty()) return 0.0f; else return volume(b); } + + /*! computes the volume of a bounding box */ + __forceinline float volume( const BBox& b ) { return reduce_mul(b.size()); } + + /*! computes the surface area of a bounding box */ + template __forceinline const T area( const BBox >& b ) { const Vec2 d = b.size(); return d.x*d.y; } + + template __forceinline const T halfArea( const BBox >& b ) { return halfArea(b.size()); } + template __forceinline const T area( const BBox >& b ) { return T(2)*halfArea(b); } + + __forceinline float halfArea( const BBox& b ) { return halfArea(b.size()); } + __forceinline float area( const BBox& b ) { return 2.0f*halfArea(b); } + + __forceinline float halfArea( const BBox& b ) { return halfArea(b.size()); } + __forceinline float area( const BBox& b ) { return 2.0f*halfArea(b); } + + template __forceinline float safeArea( const BBox& b ) { if (b.empty()) return 0.0f; else return area(b); } + + template __forceinline float expectedApproxHalfArea(const BBox& box) { + return halfArea(box); + } + + /*! merges bounding boxes and points */ + template __forceinline const BBox merge( const BBox& a, const T& b ) { return BBox(min(a.lower, b ), max(a.upper, b )); } + template __forceinline const BBox merge( const T& a, const BBox& b ) { return BBox(min(a , b.lower), max(a , b.upper)); } + template __forceinline const BBox merge( const BBox& a, const BBox& b ) { return BBox(min(a.lower, b.lower), max(a.upper, b.upper)); } + + /*! Merges three boxes. */ + template __forceinline const BBox merge( const BBox& a, const BBox& b, const BBox& c ) { return merge(a,merge(b,c)); } + + /*! Merges four boxes. */ + template __forceinline BBox merge(const BBox& a, const BBox& b, const BBox& c, const BBox& d) { + return merge(merge(a,b),merge(c,d)); + } + + /*! Comparison Operators */ + template __forceinline bool operator==( const BBox& a, const BBox& b ) { return a.lower == b.lower && a.upper == b.upper; } + template __forceinline bool operator!=( const BBox& a, const BBox& b ) { return a.lower != b.lower || a.upper != b.upper; } + + /*! scaling */ + template __forceinline BBox operator *( const float& a, const BBox& b ) { return BBox(a*b.lower,a*b.upper); } + template __forceinline BBox operator *( const T& a, const BBox& b ) { return BBox(a*b.lower,a*b.upper); } + + /*! translations */ + template __forceinline BBox operator +( const BBox& a, const BBox& b ) { return BBox(a.lower+b.lower,a.upper+b.upper); } + template __forceinline BBox operator -( const BBox& a, const BBox& b ) { return BBox(a.lower-b.lower,a.upper-b.upper); } + template __forceinline BBox operator +( const BBox& a, const T & b ) { return BBox(a.lower+b ,a.upper+b ); } + template __forceinline BBox operator -( const BBox& a, const T & b ) { return BBox(a.lower-b ,a.upper-b ); } + + /*! extension */ + template __forceinline BBox enlarge(const BBox& a, const T& b) { return BBox(a.lower-b, a.upper+b); } + + /*! intersect bounding boxes */ + template __forceinline const BBox intersect( const BBox& a, const BBox& b ) { return BBox(max(a.lower, b.lower), min(a.upper, b.upper)); } + template __forceinline const BBox intersect( const BBox& a, const BBox& b, const BBox& c ) { return intersect(a,intersect(b,c)); } + template __forceinline const BBox intersect( const BBox& a, const BBox& b, const BBox& c, const BBox& d ) { return intersect(intersect(a,b),intersect(c,d)); } + + /*! subtract bounds from each other */ + template __forceinline void subtract(const BBox& a, const BBox& b, BBox& c, BBox& d) + { + c.lower = a.lower; + c.upper = min(a.upper,b.lower); + d.lower = max(a.lower,b.upper); + d.upper = a.upper; + } + + /*! tests if bounding boxes (and points) are disjoint (empty intersection) */ + template __inline bool disjoint( const BBox& a, const BBox& b ) { return intersect(a,b).empty(); } + template __inline bool disjoint( const BBox& a, const T& b ) { return disjoint(a,BBox(b)); } + template __inline bool disjoint( const T& a, const BBox& b ) { return disjoint(BBox(a),b); } + + /*! tests if bounding boxes (and points) are conjoint (non-empty intersection) */ + template __inline bool conjoint( const BBox& a, const BBox& b ) { return !intersect(a,b).empty(); } + template __inline bool conjoint( const BBox& a, const T& b ) { return conjoint(a,BBox(b)); } + template __inline bool conjoint( const T& a, const BBox& b ) { return conjoint(BBox(a),b); } + + /*! subset relation */ + template __inline bool subset( const BBox& a, const BBox& b ) + { + for ( size_t i = 0; i < T::N; i++ ) if ( a.lower[i] < b.lower[i] ) return false; + for ( size_t i = 0; i < T::N; i++ ) if ( a.upper[i] > b.upper[i] ) return false; + return true; + } + + template<> __inline bool subset( const BBox& a, const BBox& b ) { + return all(ge_mask(a.lower,b.lower)) & all(le_mask(a.upper,b.upper)); + } + + template<> __inline bool subset( const BBox& a, const BBox& b ) { + return all(ge_mask(a.lower,b.lower)) & all(le_mask(a.upper,b.upper)); + } + + /*! blending */ + template + __forceinline BBox lerp(const BBox& b0, const BBox& b1, const float t) { + return BBox(lerp(b0.lower,b1.lower,t),lerp(b0.upper,b1.upper,t)); + } + + /*! output operator */ + template __forceinline embree_ostream operator<<(embree_ostream cout, const BBox& box) { + return cout << "[" << box.lower << "; " << box.upper << "]"; + } + + /*! default template instantiations */ + typedef BBox BBox1f; + typedef BBox BBox2f; + typedef BBox BBox2fa; + typedef BBox BBox3f; + typedef BBox BBox3fa; + typedef BBox BBox3fx; + typedef BBox BBox3ff; +} + +//////////////////////////////////////////////////////////////////////////////// +/// SSE / AVX / MIC specializations +//////////////////////////////////////////////////////////////////////////////// + +#if defined __SSE__ +#include "../simd/sse.h" +#endif + +#if defined __AVX__ +#include "../simd/avx.h" +#endif + +#if defined(__AVX512F__) +#include "../simd/avx512.h" +#endif + +namespace embree +{ + template + __forceinline BBox>> transpose(const BBox3fa* bounds); + + template<> + __forceinline BBox> transpose<4>(const BBox3fa* bounds) + { + BBox> dest; + + transpose((vfloat4&)bounds[0].lower, + (vfloat4&)bounds[1].lower, + (vfloat4&)bounds[2].lower, + (vfloat4&)bounds[3].lower, + dest.lower.x, + dest.lower.y, + dest.lower.z); + + transpose((vfloat4&)bounds[0].upper, + (vfloat4&)bounds[1].upper, + (vfloat4&)bounds[2].upper, + (vfloat4&)bounds[3].upper, + dest.upper.x, + dest.upper.y, + dest.upper.z); + + return dest; + } + +#if defined(__AVX__) + template<> + __forceinline BBox> transpose<8>(const BBox3fa* bounds) + { + BBox> dest; + + transpose((vfloat4&)bounds[0].lower, + (vfloat4&)bounds[1].lower, + (vfloat4&)bounds[2].lower, + (vfloat4&)bounds[3].lower, + (vfloat4&)bounds[4].lower, + (vfloat4&)bounds[5].lower, + (vfloat4&)bounds[6].lower, + (vfloat4&)bounds[7].lower, + dest.lower.x, + dest.lower.y, + dest.lower.z); + + transpose((vfloat4&)bounds[0].upper, + (vfloat4&)bounds[1].upper, + (vfloat4&)bounds[2].upper, + (vfloat4&)bounds[3].upper, + (vfloat4&)bounds[4].upper, + (vfloat4&)bounds[5].upper, + (vfloat4&)bounds[6].upper, + (vfloat4&)bounds[7].upper, + dest.upper.x, + dest.upper.y, + dest.upper.z); + + return dest; + } +#endif + + template + __forceinline BBox3fa merge(const BBox3fa* bounds); + + template<> + __forceinline BBox3fa merge<4>(const BBox3fa* bounds) + { + const Vec3fa lower = min(min(bounds[0].lower,bounds[1].lower), + min(bounds[2].lower,bounds[3].lower)); + const Vec3fa upper = max(max(bounds[0].upper,bounds[1].upper), + max(bounds[2].upper,bounds[3].upper)); + return BBox3fa(lower,upper); + } + +#if defined(__AVX__) + template<> + __forceinline BBox3fa merge<8>(const BBox3fa* bounds) + { + const Vec3fa lower = min(min(min(bounds[0].lower,bounds[1].lower),min(bounds[2].lower,bounds[3].lower)), + min(min(bounds[4].lower,bounds[5].lower),min(bounds[6].lower,bounds[7].lower))); + const Vec3fa upper = max(max(max(bounds[0].upper,bounds[1].upper),max(bounds[2].upper,bounds[3].upper)), + max(max(bounds[4].upper,bounds[5].upper),max(bounds[6].upper,bounds[7].upper))); + return BBox3fa(lower,upper); + } +#endif +} + diff --git a/thirdparty/embree/common/math/col3.h b/thirdparty/embree/common/math/col3.h new file mode 100644 index 000000000000..2a477ec1319b --- /dev/null +++ b/thirdparty/embree/common/math/col3.h @@ -0,0 +1,47 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "math.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// RGB Color Class + //////////////////////////////////////////////////////////////////////////////// + + template struct Col3 + { + T r, g, b; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Col3 ( ) { } + __forceinline Col3 ( const Col3& other ) { r = other.r; g = other.g; b = other.b; } + __forceinline Col3& operator=( const Col3& other ) { r = other.r; g = other.g; b = other.b; return *this; } + + __forceinline explicit Col3 (const T& v) : r(v), g(v), b(v) {} + __forceinline Col3 (const T& r, const T& g, const T& b) : r(r), g(g), b(b) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Col3 (ZeroTy) : r(zero) , g(zero) , b(zero) {} + __forceinline Col3 (OneTy) : r(one) , g(one) , b(one) {} + __forceinline Col3 (PosInfTy) : r(pos_inf), g(pos_inf), b(pos_inf) {} + __forceinline Col3 (NegInfTy) : r(neg_inf), g(neg_inf), b(neg_inf) {} + }; + + /*! output operator */ + template __forceinline embree_ostream operator<<(embree_ostream cout, const Col3& a) { + return cout << "(" << a.r << ", " << a.g << ", " << a.b << ")"; + } + + /*! default template instantiations */ + typedef Col3 Col3uc; + typedef Col3 Col3f; +} diff --git a/thirdparty/embree/common/math/col4.h b/thirdparty/embree/common/math/col4.h new file mode 100644 index 000000000000..27849840ec5b --- /dev/null +++ b/thirdparty/embree/common/math/col4.h @@ -0,0 +1,47 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "math.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// RGBA Color Class + //////////////////////////////////////////////////////////////////////////////// + + template struct Col4 + { + T r, g, b, a; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Col4 ( ) { } + __forceinline Col4 ( const Col4& other ) { r = other.r; g = other.g; b = other.b; a = other.a; } + __forceinline Col4& operator=( const Col4& other ) { r = other.r; g = other.g; b = other.b; a = other.a; return *this; } + + __forceinline explicit Col4 (const T& v) : r(v), g(v), b(v), a(v) {} + __forceinline Col4 (const T& r, const T& g, const T& b, const T& a) : r(r), g(g), b(b), a(a) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Col4 (ZeroTy) : r(zero) , g(zero) , b(zero) , a(zero) {} + __forceinline Col4 (OneTy) : r(one) , g(one) , b(one) , a(one) {} + __forceinline Col4 (PosInfTy) : r(pos_inf), g(pos_inf), b(pos_inf), a(pos_inf) {} + __forceinline Col4 (NegInfTy) : r(neg_inf), g(neg_inf), b(neg_inf), a(neg_inf) {} + }; + + /*! output operator */ + template __forceinline embree_ostream operator<<(embree_ostream cout, const Col4& a) { + return cout << "(" << a.r << ", " << a.g << ", " << a.b << ", " << a.a << ")"; + } + + /*! default template instantiations */ + typedef Col4 Col4uc; + typedef Col4 Col4f; +} diff --git a/thirdparty/embree/common/math/color.h b/thirdparty/embree/common/math/color.h new file mode 100644 index 000000000000..eae7b72ecfe0 --- /dev/null +++ b/thirdparty/embree/common/math/color.h @@ -0,0 +1,241 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "constants.h" +#include "col3.h" +#include "col4.h" + +#include "../simd/sse.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// SSE RGBA Color Class + //////////////////////////////////////////////////////////////////////////////// + + struct Color4 + { + union { + __m128 m128; + struct { float r,g,b,a; }; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Color4 () {} + __forceinline Color4 ( const __m128 a ) : m128(a) {} + + __forceinline explicit Color4 (const float v) : m128(_mm_set1_ps(v)) {} + __forceinline Color4 (const float r, const float g, const float b, const float a) : m128(_mm_set_ps(a,b,g,r)) {} + + __forceinline explicit Color4 ( const Col3uc& other ) { m128 = _mm_mul_ps(_mm_set_ps(255.0f,other.b,other.g,other.r),_mm_set1_ps(one_over_255)); } + __forceinline explicit Color4 ( const Col3f& other ) { m128 = _mm_set_ps(1.0f,other.b,other.g,other.r); } + __forceinline explicit Color4 ( const Col4uc& other ) { m128 = _mm_mul_ps(_mm_set_ps(other.a,other.b,other.g,other.r),_mm_set1_ps(one_over_255)); } + __forceinline explicit Color4 ( const Col4f& other ) { m128 = _mm_set_ps(other.a,other.b,other.g,other.r); } + + __forceinline Color4 ( const Color4& other ) : m128(other.m128) {} + __forceinline Color4& operator=( const Color4& other ) { m128 = other.m128; return *this; } + + __forceinline operator const __m128&() const { return m128; } + __forceinline operator __m128&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Set + //////////////////////////////////////////////////////////////////////////////// + + __forceinline void set(Col3f& d) const { d.r = r; d.g = g; d.b = b; } + __forceinline void set(Col4f& d) const { d.r = r; d.g = g; d.b = b; d.a = a; } + __forceinline void set(Col3uc& d) const + { + vfloat4 s = clamp(vfloat4(m128))*255.0f; + d.r = (unsigned char)(s[0]); + d.g = (unsigned char)(s[1]); + d.b = (unsigned char)(s[2]); + } + __forceinline void set(Col4uc& d) const + { + vfloat4 s = clamp(vfloat4(m128))*255.0f; + d.r = (unsigned char)(s[0]); + d.g = (unsigned char)(s[1]); + d.b = (unsigned char)(s[2]); + d.a = (unsigned char)(s[3]); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Color4( ZeroTy ) : m128(_mm_set1_ps(0.0f)) {} + __forceinline Color4( OneTy ) : m128(_mm_set1_ps(1.0f)) {} + __forceinline Color4( PosInfTy ) : m128(_mm_set1_ps(pos_inf)) {} + __forceinline Color4( NegInfTy ) : m128(_mm_set1_ps(neg_inf)) {} + }; + + //////////////////////////////////////////////////////////////////////////////// + /// SSE RGB Color Class + //////////////////////////////////////////////////////////////////////////////// + + struct Color + { + union { + __m128 m128; + struct { float r,g,b; }; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Color () {} + __forceinline Color ( const __m128 a ) : m128(a) {} + + __forceinline explicit Color (const float v) : m128(_mm_set1_ps(v)) {} + __forceinline Color (const float r, const float g, const float b) : m128(_mm_set_ps(0.0f,b,g,r)) {} + + __forceinline Color ( const Color& other ) : m128(other.m128) {} + __forceinline Color& operator=( const Color& other ) { m128 = other.m128; return *this; } + + __forceinline Color ( const Color4& other ) : m128(other.m128) {} + __forceinline Color& operator=( const Color4& other ) { m128 = other.m128; return *this; } + + __forceinline operator const __m128&() const { return m128; } + __forceinline operator __m128&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Set + //////////////////////////////////////////////////////////////////////////////// + + __forceinline void set(Col3f& d) const { d.r = r; d.g = g; d.b = b; } + __forceinline void set(Col4f& d) const { d.r = r; d.g = g; d.b = b; d.a = 1.0f; } + __forceinline void set(Col3uc& d) const + { + vfloat4 s = clamp(vfloat4(m128))*255.0f; + d.r = (unsigned char)(s[0]); + d.g = (unsigned char)(s[1]); + d.b = (unsigned char)(s[2]); + } + __forceinline void set(Col4uc& d) const + { + vfloat4 s = clamp(vfloat4(m128))*255.0f; + d.r = (unsigned char)(s[0]); + d.g = (unsigned char)(s[1]); + d.b = (unsigned char)(s[2]); + d.a = 255; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Color( ZeroTy ) : m128(_mm_set1_ps(0.0f)) {} + __forceinline Color( OneTy ) : m128(_mm_set1_ps(1.0f)) {} + __forceinline Color( PosInfTy ) : m128(_mm_set1_ps(pos_inf)) {} + __forceinline Color( NegInfTy ) : m128(_mm_set1_ps(neg_inf)) {} + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const Color operator +( const Color& a ) { return a; } + __forceinline const Color operator -( const Color& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x80000000)); + return _mm_xor_ps(a.m128, mask); + } + __forceinline const Color abs ( const Color& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff)); + return _mm_and_ps(a.m128, mask); + } + __forceinline const Color rcp ( const Color& a ) + { +#if defined(__AVX512VL__) + const Color r = _mm_rcp14_ps(a.m128); +#else + const Color r = _mm_rcp_ps(a.m128); +#endif + return _mm_sub_ps(_mm_add_ps(r, r), _mm_mul_ps(_mm_mul_ps(r, r), a)); + } + __forceinline const Color rsqrt( const Color& a ) + { +#if defined(__AVX512VL__) + __m128 r = _mm_rsqrt14_ps(a.m128); +#else + __m128 r = _mm_rsqrt_ps(a.m128); +#endif + return _mm_add_ps(_mm_mul_ps(_mm_set1_ps(1.5f),r), _mm_mul_ps(_mm_mul_ps(_mm_mul_ps(a, _mm_set1_ps(-0.5f)), r), _mm_mul_ps(r, r))); + } + __forceinline const Color sqrt ( const Color& a ) { return _mm_sqrt_ps(a.m128); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const Color operator +( const Color& a, const Color& b ) { return _mm_add_ps(a.m128, b.m128); } + __forceinline const Color operator -( const Color& a, const Color& b ) { return _mm_sub_ps(a.m128, b.m128); } + __forceinline const Color operator *( const Color& a, const Color& b ) { return _mm_mul_ps(a.m128, b.m128); } + __forceinline const Color operator *( const Color& a, const float b ) { return a * Color(b); } + __forceinline const Color operator *( const float a, const Color& b ) { return Color(a) * b; } + __forceinline const Color operator /( const Color& a, const Color& b ) { return a * rcp(b); } + __forceinline const Color operator /( const Color& a, const float b ) { return a * rcp(b); } + + __forceinline const Color min( const Color& a, const Color& b ) { return _mm_min_ps(a.m128,b.m128); } + __forceinline const Color max( const Color& a, const Color& b ) { return _mm_max_ps(a.m128,b.m128); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const Color operator+=(Color& a, const Color& b) { return a = a + b; } + __forceinline const Color operator-=(Color& a, const Color& b) { return a = a - b; } + __forceinline const Color operator*=(Color& a, const Color& b) { return a = a * b; } + __forceinline const Color operator/=(Color& a, const Color& b) { return a = a / b; } + __forceinline const Color operator*=(Color& a, const float b ) { return a = a * b; } + __forceinline const Color operator/=(Color& a, const float b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline float reduce_add(const Color& v) { return v.r+v.g+v.b; } + __forceinline float reduce_mul(const Color& v) { return v.r*v.g*v.b; } + __forceinline float reduce_min(const Color& v) { return min(v.r,v.g,v.b); } + __forceinline float reduce_max(const Color& v) { return max(v.r,v.g,v.b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator ==( const Color& a, const Color& b ) { return (_mm_movemask_ps(_mm_cmpeq_ps (a.m128, b.m128)) & 7) == 7; } + __forceinline bool operator !=( const Color& a, const Color& b ) { return (_mm_movemask_ps(_mm_cmpneq_ps(a.m128, b.m128)) & 7) != 0; } + __forceinline bool operator < ( const Color& a, const Color& b ) { + if (a.r != b.r) return a.r < b.r; + if (a.g != b.g) return a.g < b.g; + if (a.b != b.b) return a.b < b.b; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const Color select( bool s, const Color& t, const Color& f ) { + __m128 mask = s ? _mm_castsi128_ps(_mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())) : _mm_setzero_ps(); + return blendv_ps(f, t, mask); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Special Operators + //////////////////////////////////////////////////////////////////////////////// + + /*! computes luminance of a color */ + __forceinline float luminance (const Color& a) { return madd(0.212671f,a.r,madd(0.715160f,a.g,0.072169f*a.b)); } + + /*! output operator */ + __forceinline embree_ostream operator<<(embree_ostream cout, const Color& a) { + return cout << "(" << a.r << ", " << a.g << ", " << a.b << ")"; + } +} diff --git a/thirdparty/embree/common/math/constants.cpp b/thirdparty/embree/common/math/constants.cpp new file mode 100644 index 000000000000..26968297d951 --- /dev/null +++ b/thirdparty/embree/common/math/constants.cpp @@ -0,0 +1,27 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "constants.h" + +namespace embree +{ + TrueTy True; + FalseTy False; + ZeroTy zero; + OneTy one; + NegInfTy neg_inf; + PosInfTy inf; + PosInfTy pos_inf; + NaNTy nan; + UlpTy ulp; + PiTy pi; + OneOverPiTy one_over_pi; + TwoPiTy two_pi; + OneOverTwoPiTy one_over_two_pi; + FourPiTy four_pi; + OneOverFourPiTy one_over_four_pi; + StepTy step; + ReverseStepTy reverse_step; + EmptyTy empty; + UndefinedTy undefined; +} diff --git a/thirdparty/embree/common/math/constants.h b/thirdparty/embree/common/math/constants.h new file mode 100644 index 000000000000..77c2b7aec2bf --- /dev/null +++ b/thirdparty/embree/common/math/constants.h @@ -0,0 +1,197 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" + +#include + +#define _USE_MATH_DEFINES +#include // using cmath causes issues under Windows +#include +#include + +namespace embree +{ + static MAYBE_UNUSED const float one_over_255 = 1.0f/255.0f; + static MAYBE_UNUSED const float min_rcp_input = 1E-18f; // for abs(x) >= min_rcp_input the newton raphson rcp calculation does not fail + + /* we consider floating point numbers in that range as valid input numbers */ + static MAYBE_UNUSED float FLT_LARGE = 1.844E18f; + + struct TrueTy { + __forceinline operator bool( ) const { return true; } + }; + + extern MAYBE_UNUSED TrueTy True; + + struct FalseTy { + __forceinline operator bool( ) const { return false; } + }; + + extern MAYBE_UNUSED FalseTy False; + + struct ZeroTy + { + __forceinline operator double ( ) const { return 0; } + __forceinline operator float ( ) const { return 0; } + __forceinline operator long long( ) const { return 0; } + __forceinline operator unsigned long long( ) const { return 0; } + __forceinline operator long ( ) const { return 0; } + __forceinline operator unsigned long ( ) const { return 0; } + __forceinline operator int ( ) const { return 0; } + __forceinline operator unsigned int ( ) const { return 0; } + __forceinline operator short ( ) const { return 0; } + __forceinline operator unsigned short ( ) const { return 0; } + __forceinline operator char ( ) const { return 0; } + __forceinline operator unsigned char ( ) const { return 0; } + }; + + extern MAYBE_UNUSED ZeroTy zero; + + struct OneTy + { + __forceinline operator double ( ) const { return 1; } + __forceinline operator float ( ) const { return 1; } + __forceinline operator long long( ) const { return 1; } + __forceinline operator unsigned long long( ) const { return 1; } + __forceinline operator long ( ) const { return 1; } + __forceinline operator unsigned long ( ) const { return 1; } + __forceinline operator int ( ) const { return 1; } + __forceinline operator unsigned int ( ) const { return 1; } + __forceinline operator short ( ) const { return 1; } + __forceinline operator unsigned short ( ) const { return 1; } + __forceinline operator char ( ) const { return 1; } + __forceinline operator unsigned char ( ) const { return 1; } + }; + + extern MAYBE_UNUSED OneTy one; + + struct NegInfTy + { + __forceinline operator double ( ) const { return -std::numeric_limits::infinity(); } + __forceinline operator float ( ) const { return -std::numeric_limits::infinity(); } + __forceinline operator long long( ) const { return std::numeric_limits::min(); } + __forceinline operator unsigned long long( ) const { return std::numeric_limits::min(); } + __forceinline operator long ( ) const { return std::numeric_limits::min(); } + __forceinline operator unsigned long ( ) const { return std::numeric_limits::min(); } + __forceinline operator int ( ) const { return std::numeric_limits::min(); } + __forceinline operator unsigned int ( ) const { return std::numeric_limits::min(); } + __forceinline operator short ( ) const { return std::numeric_limits::min(); } + __forceinline operator unsigned short ( ) const { return std::numeric_limits::min(); } + __forceinline operator char ( ) const { return std::numeric_limits::min(); } + __forceinline operator unsigned char ( ) const { return std::numeric_limits::min(); } + + }; + + extern MAYBE_UNUSED NegInfTy neg_inf; + + struct PosInfTy + { + __forceinline operator double ( ) const { return std::numeric_limits::infinity(); } + __forceinline operator float ( ) const { return std::numeric_limits::infinity(); } + __forceinline operator long long( ) const { return std::numeric_limits::max(); } + __forceinline operator unsigned long long( ) const { return std::numeric_limits::max(); } + __forceinline operator long ( ) const { return std::numeric_limits::max(); } + __forceinline operator unsigned long ( ) const { return std::numeric_limits::max(); } + __forceinline operator int ( ) const { return std::numeric_limits::max(); } + __forceinline operator unsigned int ( ) const { return std::numeric_limits::max(); } + __forceinline operator short ( ) const { return std::numeric_limits::max(); } + __forceinline operator unsigned short ( ) const { return std::numeric_limits::max(); } + __forceinline operator char ( ) const { return std::numeric_limits::max(); } + __forceinline operator unsigned char ( ) const { return std::numeric_limits::max(); } + }; + + extern MAYBE_UNUSED PosInfTy inf; + extern MAYBE_UNUSED PosInfTy pos_inf; + + struct NaNTy + { + __forceinline operator double( ) const { return std::numeric_limits::quiet_NaN(); } + __forceinline operator float ( ) const { return std::numeric_limits::quiet_NaN(); } + }; + + extern MAYBE_UNUSED NaNTy nan; + + struct UlpTy + { + __forceinline operator double( ) const { return std::numeric_limits::epsilon(); } + __forceinline operator float ( ) const { return std::numeric_limits::epsilon(); } + }; + + extern MAYBE_UNUSED UlpTy ulp; + + struct PiTy + { + __forceinline operator double( ) const { return double(M_PI); } + __forceinline operator float ( ) const { return float(M_PI); } + }; + + extern MAYBE_UNUSED PiTy pi; + + struct OneOverPiTy + { + __forceinline operator double( ) const { return double(M_1_PI); } + __forceinline operator float ( ) const { return float(M_1_PI); } + }; + + extern MAYBE_UNUSED OneOverPiTy one_over_pi; + + struct TwoPiTy + { + __forceinline operator double( ) const { return double(2.0*M_PI); } + __forceinline operator float ( ) const { return float(2.0*M_PI); } + }; + + extern MAYBE_UNUSED TwoPiTy two_pi; + + struct OneOverTwoPiTy + { + __forceinline operator double( ) const { return double(0.5*M_1_PI); } + __forceinline operator float ( ) const { return float(0.5*M_1_PI); } + }; + + extern MAYBE_UNUSED OneOverTwoPiTy one_over_two_pi; + + struct FourPiTy + { + __forceinline operator double( ) const { return double(4.0*M_PI); } + __forceinline operator float ( ) const { return float(4.0*M_PI); } + }; + + extern MAYBE_UNUSED FourPiTy four_pi; + + struct OneOverFourPiTy + { + __forceinline operator double( ) const { return double(0.25*M_1_PI); } + __forceinline operator float ( ) const { return float(0.25*M_1_PI); } + }; + + extern MAYBE_UNUSED OneOverFourPiTy one_over_four_pi; + + struct StepTy { + }; + + extern MAYBE_UNUSED StepTy step; + + struct ReverseStepTy { + }; + + extern MAYBE_UNUSED ReverseStepTy reverse_step; + + struct EmptyTy { + }; + + extern MAYBE_UNUSED EmptyTy empty; + + struct FullTy { + }; + + extern MAYBE_UNUSED FullTy full; + + struct UndefinedTy { + }; + + extern MAYBE_UNUSED UndefinedTy undefined; +} diff --git a/thirdparty/embree/common/math/interval.h b/thirdparty/embree/common/math/interval.h new file mode 100644 index 000000000000..f06478e881fc --- /dev/null +++ b/thirdparty/embree/common/math/interval.h @@ -0,0 +1,161 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "vec2.h" +#include "vec3.h" +#include "bbox.h" + +namespace embree +{ + template + struct Interval + { + V lower, upper; + + __forceinline Interval() {} + __forceinline Interval ( const Interval& other ) { lower = other.lower; upper = other.upper; } + __forceinline Interval& operator=( const Interval& other ) { lower = other.lower; upper = other.upper; return *this; } + + __forceinline Interval(const V& a) : lower(a), upper(a) {} + __forceinline Interval(const V& lower, const V& upper) : lower(lower), upper(upper) {} + __forceinline Interval(const BBox& a) : lower(a.lower), upper(a.upper) {} + + /*! tests if box is empty */ + //__forceinline bool empty() const { return lower > upper; } + + /*! computes the size of the interval */ + __forceinline V size() const { return upper - lower; } + + __forceinline V center() const { return 0.5f*(lower+upper); } + + __forceinline const Interval& extend(const Interval& other) { lower = min(lower,other.lower); upper = max(upper,other.upper); return *this; } + __forceinline const Interval& extend(const V & other) { lower = min(lower,other ); upper = max(upper,other ); return *this; } + + __forceinline friend Interval operator +( const Interval& a, const Interval& b ) { + return Interval(a.lower+b.lower,a.upper+b.upper); + } + + __forceinline friend Interval operator -( const Interval& a, const Interval& b ) { + return Interval(a.lower-b.upper,a.upper-b.lower); + } + + __forceinline friend Interval operator -( const Interval& a, const V& b ) { + return Interval(a.lower-b,a.upper-b); + } + + __forceinline friend Interval operator *( const Interval& a, const Interval& b ) + { + const V ll = a.lower*b.lower; + const V lu = a.lower*b.upper; + const V ul = a.upper*b.lower; + const V uu = a.upper*b.upper; + return Interval(min(ll,lu,ul,uu),max(ll,lu,ul,uu)); + } + + __forceinline friend Interval merge( const Interval& a, const Interval& b) { + return Interval(min(a.lower,b.lower),max(a.upper,b.upper)); + } + + __forceinline friend Interval merge( const Interval& a, const Interval& b, const Interval& c) { + return merge(merge(a,b),c); + } + + __forceinline friend Interval merge( const Interval& a, const Interval& b, const Interval& c, const Interval& d) { + return merge(merge(a,b),merge(c,d)); + } + + /*! intersect bounding boxes */ + __forceinline friend const Interval intersect( const Interval& a, const Interval& b ) { return Interval(max(a.lower, b.lower), min(a.upper, b.upper)); } + __forceinline friend const Interval intersect( const Interval& a, const Interval& b, const Interval& c ) { return intersect(a,intersect(b,c)); } + __forceinline friend const Interval intersect( const Interval& a, const Interval& b, const Interval& c, const Interval& d ) { return intersect(intersect(a,b),intersect(c,d)); } + + friend embree_ostream operator<<(embree_ostream cout, const Interval& a) { + return cout << "[" << a.lower << ", " << a.upper << "]"; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Interval( EmptyTy ) : lower(pos_inf), upper(neg_inf) {} + __forceinline Interval( FullTy ) : lower(neg_inf), upper(pos_inf) {} + }; + + __forceinline bool isEmpty(const Interval& v) { + return v.lower > v.upper; + } + + __forceinline vboolx isEmpty(const Interval& v) { + return v.lower > v.upper; + } + + /*! subset relation */ + template __forceinline bool subset( const Interval& a, const Interval& b ) { + return (a.lower > b.lower) && (a.upper < b.upper); + } + + template __forceinline bool subset( const Vec2>& a, const Vec2>& b ) { + return subset(a.x,b.x) && subset(a.y,b.y); + } + + template __forceinline const Vec2> intersect( const Vec2>& a, const Vec2>& b ) { + return Vec2>(intersect(a.x,b.x),intersect(a.y,b.y)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Interval select ( bool s, const Interval& t, const Interval& f ) { + return Interval(select(s,t.lower,f.lower),select(s,t.upper,f.upper)); + } + + template __forceinline Interval select ( const typename T::Bool& s, const Interval& t, const Interval& f ) { + return Interval(select(s,t.lower,f.lower),select(s,t.upper,f.upper)); + } + + __forceinline int numRoots(const Interval& p0, const Interval& p1) + { + float eps = 1E-4f; + bool neg0 = p0.lower < eps; bool pos0 = p0.upper > -eps; + bool neg1 = p1.lower < eps; bool pos1 = p1.upper > -eps; + return (neg0 && pos1) || (pos0 && neg1) || (neg0 && pos0) || (neg1 && pos1); + } + + typedef Interval Interval1f; + typedef Vec2> Interval2f; + typedef Vec3> Interval3f; + +inline void swap(float& a, float& b) { float tmp = a; a = b; b = tmp; } + +inline Interval1f shift(const Interval1f& v, float shift) { return Interval1f(v.lower + shift, v.upper + shift); } + +#define TWO_PI (2.0*M_PI) +inline Interval1f sin(Interval1f interval) +{ + if (interval.upper-interval.lower >= M_PI) { return Interval1f(-1.0, 1.0); } + if (interval.upper > TWO_PI) { interval = shift(interval, -TWO_PI*floor(interval.upper/TWO_PI)); } + if (interval.lower < 0) { interval = shift(interval, -TWO_PI*floor(interval.lower/TWO_PI)); } + float sinLower = sin(interval.lower); + float sinUpper = sin(interval.upper); + if (sinLower > sinUpper) swap(sinLower, sinUpper); + if (interval.lower < M_PI / 2.0 && interval.upper > M_PI / 2.0) sinUpper = 1.0; + if (interval.lower < 3.0 * M_PI / 2.0 && interval.upper > 3.0 * M_PI / 2.0) sinLower = -1.0; + return Interval1f(sinLower, sinUpper); +} + +inline Interval1f cos(Interval1f interval) +{ + if (interval.upper-interval.lower >= M_PI) { return Interval1f(-1.0, 1.0); } + if (interval.upper > TWO_PI) { interval = shift(interval, -TWO_PI*floor(interval.upper/TWO_PI)); } + if (interval.lower < 0) { interval = shift(interval, -TWO_PI*floor(interval.lower/TWO_PI)); } + float cosLower = cos(interval.lower); + float cosUpper = cos(interval.upper); + if (cosLower > cosUpper) swap(cosLower, cosUpper); + if (interval.lower < M_PI && interval.upper > M_PI) cosLower = -1.0; + return Interval1f(cosLower, cosUpper); +} +#undef TWO_PI +} diff --git a/thirdparty/embree/common/math/lbbox.h b/thirdparty/embree/common/math/lbbox.h new file mode 100644 index 000000000000..95df4a918d7b --- /dev/null +++ b/thirdparty/embree/common/math/lbbox.h @@ -0,0 +1,289 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bbox.h" +#include "range.h" + +namespace embree +{ + template + __forceinline std::pair globalLinear(const std::pair& v, const BBox1f& dt) + { + const float rcp_dt_size = float(1.0f)/dt.size(); + const T g0 = lerp(v.first,v.second,-dt.lower*rcp_dt_size); + const T g1 = lerp(v.first,v.second,(1.0f-dt.lower)*rcp_dt_size); + return std::make_pair(g0,g1); + } + + template + struct LBBox + { + public: + __forceinline LBBox () {} + + template + __forceinline LBBox ( const LBBox& other ) + : bounds0(other.bounds0), bounds1(other.bounds1) {} + + __forceinline LBBox& operator= ( const LBBox& other ) { + bounds0 = other.bounds0; bounds1 = other.bounds1; return *this; + } + + __forceinline LBBox (EmptyTy) + : bounds0(EmptyTy()), bounds1(EmptyTy()) {} + + __forceinline explicit LBBox ( const BBox& bounds) + : bounds0(bounds), bounds1(bounds) { } + + __forceinline LBBox ( const BBox& bounds0, const BBox& bounds1) + : bounds0(bounds0), bounds1(bounds1) { } + + LBBox ( const avector>& bounds ) + { + assert(bounds.size()); + BBox b0 = bounds.front(); + BBox b1 = bounds.back(); + for (size_t i=1; i bt = lerp(b0,b1,f); + const T dlower = min(bounds[i].lower-bt.lower,T(zero)); + const T dupper = max(bounds[i].upper-bt.upper,T(zero)); + b0.lower += dlower; b1.lower += dlower; + b0.upper += dupper; b1.upper += dupper; + } + bounds0 = b0; + bounds1 = b1; + } + + /*! calculates the linear bounds of a primitive for the specified time range */ + template + __forceinline LBBox(const BoundsFunc& bounds, const BBox1f& time_range, float numTimeSegments) + { + const float lower = time_range.lower*numTimeSegments; + const float upper = time_range.upper*numTimeSegments; + const float ilowerf = floor(lower); + const float iupperf = ceil(upper); + const int ilower = (int)ilowerf; + const int iupper = (int)iupperf; + + const BBox blower0 = bounds(ilower); + const BBox bupper1 = bounds(iupper); + + if (iupper-ilower == 1) { + bounds0 = lerp(blower0, bupper1, lower-ilowerf); + bounds1 = lerp(bupper1, blower0, iupperf-upper); + return; + } + + const BBox blower1 = bounds(ilower+1); + const BBox bupper0 = bounds(iupper-1); + BBox b0 = lerp(blower0, blower1, lower-ilowerf); + BBox b1 = lerp(bupper1, bupper0, iupperf-upper); + + for (int i = ilower+1; i < iupper; i++) + { + const float f = (float(i)/numTimeSegments - time_range.lower) / time_range.size(); + const BBox bt = lerp(b0, b1, f); + const BBox bi = bounds(i); + const T dlower = min(bi.lower-bt.lower, T(zero)); + const T dupper = max(bi.upper-bt.upper, T(zero)); + b0.lower += dlower; b1.lower += dlower; + b0.upper += dupper; b1.upper += dupper; + } + + bounds0 = b0; + bounds1 = b1; + } + + /*! calculates the linear bounds of a primitive for the specified time range */ + template + __forceinline LBBox(const BoundsFunc& bounds, const BBox1f& time_range_in, const BBox1f& geom_time_range, float geom_time_segments) + { + /* normalize global time_range_in to local geom_time_range */ + const BBox1f time_range((time_range_in.lower-geom_time_range.lower)/geom_time_range.size(), + (time_range_in.upper-geom_time_range.lower)/geom_time_range.size()); + + const float lower = time_range.lower*geom_time_segments; + const float upper = time_range.upper*geom_time_segments; + const float ilowerf = floor(lower); + const float iupperf = ceil(upper); + const float ilowerfc = max(0.0f,ilowerf); + const float iupperfc = min(iupperf,geom_time_segments); + const int ilowerc = (int)ilowerfc; + const int iupperc = (int)iupperfc; + assert(iupperc-ilowerc > 0); + + /* this larger iteration range guarantees that we process borders of geom_time_range is (partially) inside time_range_in */ + const int ilower_iter = max(-1,(int)ilowerf); + const int iupper_iter = min((int)iupperf,(int)geom_time_segments+1); + + const BBox blower0 = bounds(ilowerc); + const BBox bupper1 = bounds(iupperc); + if (iupper_iter-ilower_iter == 1) { + bounds0 = lerp(blower0, bupper1, max(0.0f,lower-ilowerfc)); + bounds1 = lerp(bupper1, blower0, max(0.0f,iupperfc-upper)); + return; + } + + const BBox blower1 = bounds(ilowerc+1); + const BBox bupper0 = bounds(iupperc-1); + BBox b0 = lerp(blower0, blower1, max(0.0f,lower-ilowerfc)); + BBox b1 = lerp(bupper1, bupper0, max(0.0f,iupperfc-upper)); + + for (int i = ilower_iter+1; i < iupper_iter; i++) + { + const float f = (float(i)/geom_time_segments - time_range.lower) / time_range.size(); + const BBox bt = lerp(b0, b1, f); + const BBox bi = bounds(i); + const T dlower = min(bi.lower-bt.lower, T(zero)); + const T dupper = max(bi.upper-bt.upper, T(zero)); + b0.lower += dlower; b1.lower += dlower; + b0.upper += dupper; b1.upper += dupper; + } + + bounds0 = b0; + bounds1 = b1; + } + + /*! calculates the linear bounds of a primitive for the specified time range */ + template + __forceinline LBBox(const BoundsFunc& bounds, const range& time_range, int numTimeSegments) + { + const int ilower = time_range.begin(); + const int iupper = time_range.end(); + + BBox b0 = bounds(ilower); + BBox b1 = bounds(iupper); + + if (iupper-ilower == 1) + { + bounds0 = b0; + bounds1 = b1; + return; + } + + for (int i = ilower+1; i bt = lerp(b0, b1, f); + const BBox bi = bounds(i); + const T dlower = min(bi.lower-bt.lower, T(zero)); + const T dupper = max(bi.upper-bt.upper, T(zero)); + b0.lower += dlower; b1.lower += dlower; + b0.upper += dupper; b1.upper += dupper; + } + + bounds0 = b0; + bounds1 = b1; + } + + public: + + __forceinline bool empty() const { + return bounds().empty(); + } + + __forceinline BBox bounds () const { + return merge(bounds0,bounds1); + } + + __forceinline BBox interpolate( const float t ) const { + return lerp(bounds0,bounds1,t); + } + + __forceinline LBBox interpolate( const BBox1f& dt ) const { + return LBBox(interpolate(dt.lower),interpolate(dt.upper)); + } + + __forceinline void extend( const LBBox& other ) { + bounds0.extend(other.bounds0); + bounds1.extend(other.bounds1); + } + + __forceinline float expectedHalfArea() const; + + __forceinline float expectedHalfArea(const BBox1f& dt) const { + return interpolate(dt).expectedHalfArea(); + } + + __forceinline float expectedApproxHalfArea() const { + return 0.5f*(halfArea(bounds0) + halfArea(bounds1)); + } + + /* calculates bounds for [0,1] time range from bounds in dt time range */ + __forceinline LBBox global(const BBox1f& dt) const + { + const float rcp_dt_size = 1.0f/dt.size(); + const BBox b0 = interpolate(-dt.lower*rcp_dt_size); + const BBox b1 = interpolate((1.0f-dt.lower)*rcp_dt_size); + return LBBox(b0,b1); + } + + /*! Comparison Operators */ + //template friend __forceinline bool operator==( const LBBox& a, const LBBox& b ) { return a.bounds0 == b.bounds0 && a.bounds1 == b.bounds1; } + //template friend __forceinline bool operator!=( const LBBox& a, const LBBox& b ) { return a.bounds0 != b.bounds0 || a.bounds1 != b.bounds1; } + friend __forceinline bool operator==( const LBBox& a, const LBBox& b ) { return a.bounds0 == b.bounds0 && a.bounds1 == b.bounds1; } + friend __forceinline bool operator!=( const LBBox& a, const LBBox& b ) { return a.bounds0 != b.bounds0 || a.bounds1 != b.bounds1; } + + /*! output operator */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const LBBox& box) { + return cout << "LBBox { " << box.bounds0 << "; " << box.bounds1 << " }"; + } + + public: + BBox bounds0, bounds1; + }; + + /*! tests if box is finite */ + template + __forceinline bool isvalid( const LBBox& v ) { + return isvalid(v.bounds0) && isvalid(v.bounds1); + } + + template + __forceinline bool isvalid_non_empty( const LBBox& v ) { + return isvalid_non_empty(v.bounds0) && isvalid_non_empty(v.bounds1); + } + + template + __forceinline T expectedArea(const T& a0, const T& a1, const T& b0, const T& b1) + { + const T da = a1-a0; + const T db = b1-b0; + return a0*b0+(a0*db+da*b0)*T(0.5f) + da*db*T(1.0f/3.0f); + } + + template<> __forceinline float LBBox::expectedHalfArea() const + { + const Vec3fa d0 = bounds0.size(); + const Vec3fa d1 = bounds1.size(); + return reduce_add(expectedArea(Vec3fa(d0.x,d0.y,d0.z), + Vec3fa(d1.x,d1.y,d1.z), + Vec3fa(d0.y,d0.z,d0.x), + Vec3fa(d1.y,d1.z,d1.x))); + } + + template + __forceinline float expectedApproxHalfArea(const LBBox& box) { + return box.expectedApproxHalfArea(); + } + + template + __forceinline LBBox merge(const LBBox& a, const LBBox& b) { + return LBBox(merge(a.bounds0, b.bounds0), merge(a.bounds1, b.bounds1)); + } + + /*! subset relation */ + template __inline bool subset( const LBBox& a, const LBBox& b ) { + return subset(a.bounds0,b.bounds0) && subset(a.bounds1,b.bounds1); + } + + /*! default template instantiations */ + typedef LBBox LBBox1f; + typedef LBBox LBBox2f; + typedef LBBox LBBox3f; + typedef LBBox LBBox3fa; + typedef LBBox LBBox3fx; +} diff --git a/thirdparty/embree/common/math/linearspace2.h b/thirdparty/embree/common/math/linearspace2.h new file mode 100644 index 000000000000..b9a382962cf6 --- /dev/null +++ b/thirdparty/embree/common/math/linearspace2.h @@ -0,0 +1,148 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "vec2.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// 2D Linear Transform (2x2 Matrix) + //////////////////////////////////////////////////////////////////////////////// + + template struct LinearSpace2 + { + typedef T Vector; + typedef typename T::Scalar Scalar; + + /*! default matrix constructor */ + __forceinline LinearSpace2 ( ) {} + __forceinline LinearSpace2 ( const LinearSpace2& other ) { vx = other.vx; vy = other.vy; } + __forceinline LinearSpace2& operator=( const LinearSpace2& other ) { vx = other.vx; vy = other.vy; return *this; } + + template __forceinline LinearSpace2( const LinearSpace2& s ) : vx(s.vx), vy(s.vy) {} + + /*! matrix construction from column vectors */ + __forceinline LinearSpace2(const Vector& vx, const Vector& vy) + : vx(vx), vy(vy) {} + + /*! matrix construction from row mayor data */ + __forceinline LinearSpace2(const Scalar& m00, const Scalar& m01, + const Scalar& m10, const Scalar& m11) + : vx(m00,m10), vy(m01,m11) {} + + /*! compute the determinant of the matrix */ + __forceinline const Scalar det() const { return vx.x*vy.y - vx.y*vy.x; } + + /*! compute adjoint matrix */ + __forceinline const LinearSpace2 adjoint() const { return LinearSpace2(vy.y,-vy.x,-vx.y,vx.x); } + + /*! compute inverse matrix */ + __forceinline const LinearSpace2 inverse() const { return adjoint()/det(); } + + /*! compute transposed matrix */ + __forceinline const LinearSpace2 transposed() const { return LinearSpace2(vx.x,vx.y,vy.x,vy.y); } + + /*! returns first row of matrix */ + __forceinline Vector row0() const { return Vector(vx.x,vy.x); } + + /*! returns second row of matrix */ + __forceinline Vector row1() const { return Vector(vx.y,vy.y); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline LinearSpace2( ZeroTy ) : vx(zero), vy(zero) {} + __forceinline LinearSpace2( OneTy ) : vx(one, zero), vy(zero, one) {} + + /*! return matrix for scaling */ + static __forceinline LinearSpace2 scale(const Vector& s) { + return LinearSpace2(s.x, 0, + 0 , s.y); + } + + /*! return matrix for rotation */ + static __forceinline LinearSpace2 rotate(const Scalar& r) { + Scalar s = sin(r), c = cos(r); + return LinearSpace2(c, -s, + s, c); + } + + /*! return closest orthogonal matrix (i.e. a general rotation including reflection) */ + LinearSpace2 orthogonal() const + { + LinearSpace2 m = *this; + + // mirrored? + Scalar mirror(one); + if (m.det() < Scalar(zero)) { + m.vx = -m.vx; + mirror = -mirror; + } + + // rotation + for (int i = 0; i < 99; i++) { + const LinearSpace2 m_next = 0.5 * (m + m.transposed().inverse()); + const LinearSpace2 d = m_next - m; + m = m_next; + // norm^2 of difference small enough? + if (max(dot(d.vx, d.vx), dot(d.vy, d.vy)) < 1e-8) + break; + } + + // rotation * mirror_x + return LinearSpace2(mirror*m.vx, m.vy); + } + + public: + + /*! the column vectors of the matrix */ + Vector vx,vy; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline LinearSpace2 operator -( const LinearSpace2& a ) { return LinearSpace2(-a.vx,-a.vy); } + template __forceinline LinearSpace2 operator +( const LinearSpace2& a ) { return LinearSpace2(+a.vx,+a.vy); } + template __forceinline LinearSpace2 rcp ( const LinearSpace2& a ) { return a.inverse(); } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline LinearSpace2 operator +( const LinearSpace2& a, const LinearSpace2& b ) { return LinearSpace2(a.vx+b.vx,a.vy+b.vy); } + template __forceinline LinearSpace2 operator -( const LinearSpace2& a, const LinearSpace2& b ) { return LinearSpace2(a.vx-b.vx,a.vy-b.vy); } + + template __forceinline LinearSpace2 operator*(const typename T::Scalar & a, const LinearSpace2& b) { return LinearSpace2(a*b.vx, a*b.vy); } + template __forceinline T operator*(const LinearSpace2& a, const T & b) { return b.x*a.vx + b.y*a.vy; } + template __forceinline LinearSpace2 operator*(const LinearSpace2& a, const LinearSpace2& b) { return LinearSpace2(a*b.vx, a*b.vy); } + + template __forceinline LinearSpace2 operator/(const LinearSpace2& a, const typename T::Scalar & b) { return LinearSpace2(a.vx/b, a.vy/b); } + template __forceinline LinearSpace2 operator/(const LinearSpace2& a, const LinearSpace2& b) { return a * rcp(b); } + + template __forceinline LinearSpace2& operator *=( LinearSpace2& a, const LinearSpace2& b ) { return a = a * b; } + template __forceinline LinearSpace2& operator /=( LinearSpace2& a, const LinearSpace2& b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const LinearSpace2& a, const LinearSpace2& b ) { return a.vx == b.vx && a.vy == b.vy; } + template __forceinline bool operator !=( const LinearSpace2& a, const LinearSpace2& b ) { return a.vx != b.vx || a.vy != b.vy; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template static embree_ostream operator<<(embree_ostream cout, const LinearSpace2& m) { + return cout << "{ vx = " << m.vx << ", vy = " << m.vy << "}"; + } + + /*! Shortcuts for common linear spaces. */ + typedef LinearSpace2 LinearSpace2f; + typedef LinearSpace2 LinearSpace2fa; +} diff --git a/thirdparty/embree/common/math/linearspace3.h b/thirdparty/embree/common/math/linearspace3.h new file mode 100644 index 000000000000..12b5bb776bfb --- /dev/null +++ b/thirdparty/embree/common/math/linearspace3.h @@ -0,0 +1,213 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "vec3.h" +#include "quaternion.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// 3D Linear Transform (3x3 Matrix) + //////////////////////////////////////////////////////////////////////////////// + + template struct LinearSpace3 + { + typedef T Vector; + typedef typename T::Scalar Scalar; + + /*! default matrix constructor */ + __forceinline LinearSpace3 ( ) {} + __forceinline LinearSpace3 ( const LinearSpace3& other ) { vx = other.vx; vy = other.vy; vz = other.vz; } + __forceinline LinearSpace3& operator=( const LinearSpace3& other ) { vx = other.vx; vy = other.vy; vz = other.vz; return *this; } + + template __forceinline LinearSpace3( const LinearSpace3& s ) : vx(s.vx), vy(s.vy), vz(s.vz) {} + + /*! matrix construction from column vectors */ + __forceinline LinearSpace3(const Vector& vx, const Vector& vy, const Vector& vz) + : vx(vx), vy(vy), vz(vz) {} + + /*! construction from quaternion */ + __forceinline LinearSpace3( const QuaternionT& q ) + : vx((q.r*q.r + q.i*q.i - q.j*q.j - q.k*q.k), 2.0f*(q.i*q.j + q.r*q.k), 2.0f*(q.i*q.k - q.r*q.j)) + , vy(2.0f*(q.i*q.j - q.r*q.k), (q.r*q.r - q.i*q.i + q.j*q.j - q.k*q.k), 2.0f*(q.j*q.k + q.r*q.i)) + , vz(2.0f*(q.i*q.k + q.r*q.j), 2.0f*(q.j*q.k - q.r*q.i), (q.r*q.r - q.i*q.i - q.j*q.j + q.k*q.k)) {} + + /*! matrix construction from row mayor data */ + __forceinline LinearSpace3(const Scalar& m00, const Scalar& m01, const Scalar& m02, + const Scalar& m10, const Scalar& m11, const Scalar& m12, + const Scalar& m20, const Scalar& m21, const Scalar& m22) + : vx(m00,m10,m20), vy(m01,m11,m21), vz(m02,m12,m22) {} + + /*! compute the determinant of the matrix */ + __forceinline const Scalar det() const { return dot(vx,cross(vy,vz)); } + + /*! compute adjoint matrix */ + __forceinline const LinearSpace3 adjoint() const { return LinearSpace3(cross(vy,vz),cross(vz,vx),cross(vx,vy)).transposed(); } + + /*! compute inverse matrix */ + __forceinline const LinearSpace3 inverse() const { return adjoint()/det(); } + + /*! compute transposed matrix */ + __forceinline const LinearSpace3 transposed() const { return LinearSpace3(vx.x,vx.y,vx.z,vy.x,vy.y,vy.z,vz.x,vz.y,vz.z); } + + /*! returns first row of matrix */ + __forceinline Vector row0() const { return Vector(vx.x,vy.x,vz.x); } + + /*! returns second row of matrix */ + __forceinline Vector row1() const { return Vector(vx.y,vy.y,vz.y); } + + /*! returns third row of matrix */ + __forceinline Vector row2() const { return Vector(vx.z,vy.z,vz.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline LinearSpace3( ZeroTy ) : vx(zero), vy(zero), vz(zero) {} + __forceinline LinearSpace3( OneTy ) : vx(one, zero, zero), vy(zero, one, zero), vz(zero, zero, one) {} + + /*! return matrix for scaling */ + static __forceinline LinearSpace3 scale(const Vector& s) { + return LinearSpace3(s.x, 0, 0, + 0 , s.y, 0, + 0 , 0, s.z); + } + + /*! return matrix for rotation around arbitrary axis */ + static __forceinline LinearSpace3 rotate(const Vector& _u, const Scalar& r) { + Vector u = normalize(_u); + Scalar s = sin(r), c = cos(r); + return LinearSpace3(u.x*u.x+(1-u.x*u.x)*c, u.x*u.y*(1-c)-u.z*s, u.x*u.z*(1-c)+u.y*s, + u.x*u.y*(1-c)+u.z*s, u.y*u.y+(1-u.y*u.y)*c, u.y*u.z*(1-c)-u.x*s, + u.x*u.z*(1-c)-u.y*s, u.y*u.z*(1-c)+u.x*s, u.z*u.z+(1-u.z*u.z)*c); + } + + public: + + /*! the column vectors of the matrix */ + Vector vx,vy,vz; + }; + + /*! compute transposed matrix */ + template<> __forceinline const LinearSpace3 LinearSpace3::transposed() const { + vfloat4 rx,ry,rz; transpose((vfloat4&)vx,(vfloat4&)vy,(vfloat4&)vz,vfloat4(zero),rx,ry,rz); + return LinearSpace3(Vec3fa(rx),Vec3fa(ry),Vec3fa(rz)); + } + + template + __forceinline const LinearSpace3 transposed(const LinearSpace3& xfm) { + return xfm.transposed(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline LinearSpace3 operator -( const LinearSpace3& a ) { return LinearSpace3(-a.vx,-a.vy,-a.vz); } + template __forceinline LinearSpace3 operator +( const LinearSpace3& a ) { return LinearSpace3(+a.vx,+a.vy,+a.vz); } + template __forceinline LinearSpace3 rcp ( const LinearSpace3& a ) { return a.inverse(); } + + /* constructs a coordinate frame form a normalized normal */ + template __forceinline LinearSpace3 frame(const T& N) + { + const T dx0(0,N.z,-N.y); + const T dx1(-N.z,0,N.x); + const T dx = normalize(select(dot(dx0,dx0) > dot(dx1,dx1),dx0,dx1)); + const T dy = normalize(cross(N,dx)); + return LinearSpace3(dx,dy,N); + } + + /* constructs a coordinate frame from a normal and approximate x-direction */ + template __forceinline LinearSpace3 frame(const T& N, const T& dxi) + { + if (abs(dot(dxi,N)) > 0.99f) return frame(N); // fallback in case N and dxi are very parallel + const T dx = normalize(cross(dxi,N)); + const T dy = normalize(cross(N,dx)); + return LinearSpace3(dx,dy,N); + } + + /* clamps linear space to range -1 to +1 */ + template __forceinline LinearSpace3 clamp(const LinearSpace3& space) { + return LinearSpace3(clamp(space.vx,T(-1.0f),T(1.0f)), + clamp(space.vy,T(-1.0f),T(1.0f)), + clamp(space.vz,T(-1.0f),T(1.0f))); + } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline LinearSpace3 operator +( const LinearSpace3& a, const LinearSpace3& b ) { return LinearSpace3(a.vx+b.vx,a.vy+b.vy,a.vz+b.vz); } + template __forceinline LinearSpace3 operator -( const LinearSpace3& a, const LinearSpace3& b ) { return LinearSpace3(a.vx-b.vx,a.vy-b.vy,a.vz-b.vz); } + + template __forceinline LinearSpace3 operator*(const typename T::Scalar & a, const LinearSpace3& b) { return LinearSpace3(a*b.vx, a*b.vy, a*b.vz); } + template __forceinline T operator*(const LinearSpace3& a, const T & b) { return madd(T(b.x),a.vx,madd(T(b.y),a.vy,T(b.z)*a.vz)); } + template __forceinline LinearSpace3 operator*(const LinearSpace3& a, const LinearSpace3& b) { return LinearSpace3(a*b.vx, a*b.vy, a*b.vz); } + + template __forceinline LinearSpace3 operator/(const LinearSpace3& a, const typename T::Scalar & b) { return LinearSpace3(a.vx/b, a.vy/b, a.vz/b); } + template __forceinline LinearSpace3 operator/(const LinearSpace3& a, const LinearSpace3& b) { return a * rcp(b); } + + template __forceinline LinearSpace3& operator *=( LinearSpace3& a, const LinearSpace3& b ) { return a = a * b; } + template __forceinline LinearSpace3& operator /=( LinearSpace3& a, const LinearSpace3& b ) { return a = a / b; } + + template __forceinline T xfmPoint (const LinearSpace3& s, const T & a) { return madd(T(a.x),s.vx,madd(T(a.y),s.vy,T(a.z)*s.vz)); } + template __forceinline T xfmVector(const LinearSpace3& s, const T & a) { return madd(T(a.x),s.vx,madd(T(a.y),s.vy,T(a.z)*s.vz)); } + template __forceinline T xfmNormal(const LinearSpace3& s, const T & a) { return xfmVector(s.inverse().transposed(),a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const LinearSpace3& a, const LinearSpace3& b ) { return a.vx == b.vx && a.vy == b.vy && a.vz == b.vz; } + template __forceinline bool operator !=( const LinearSpace3& a, const LinearSpace3& b ) { return a.vx != b.vx || a.vy != b.vy || a.vz != b.vz; } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline LinearSpace3 select ( const typename T::Scalar::Bool& s, const LinearSpace3& t, const LinearSpace3& f ) { + return LinearSpace3(select(s,t.vx,f.vx),select(s,t.vy,f.vy),select(s,t.vz,f.vz)); + } + + /*! blending */ + template + __forceinline LinearSpace3 lerp(const LinearSpace3& l0, const LinearSpace3& l1, const float t) + { + return LinearSpace3(lerp(l0.vx,l1.vx,t), + lerp(l0.vy,l1.vy,t), + lerp(l0.vz,l1.vz,t)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template static embree_ostream operator<<(embree_ostream cout, const LinearSpace3& m) { + return cout << "{ vx = " << m.vx << ", vy = " << m.vy << ", vz = " << m.vz << "}"; + } + + /*! Shortcuts for common linear spaces. */ + typedef LinearSpace3 LinearSpace3f; + typedef LinearSpace3 LinearSpace3fa; + typedef LinearSpace3 LinearSpace3fx; + typedef LinearSpace3 LinearSpace3ff; + + template using LinearSpace3vf = LinearSpace3>>; + typedef LinearSpace3>> LinearSpace3vf4; + typedef LinearSpace3>> LinearSpace3vf8; + typedef LinearSpace3>> LinearSpace3vf16; + + /*! blending */ + template + __forceinline LinearSpace3 lerp(const LinearSpace3& l0, + const LinearSpace3& l1, + const S& t) + { + return LinearSpace3(lerp(l0.vx,l1.vx,t), + lerp(l0.vy,l1.vy,t), + lerp(l0.vz,l1.vz,t)); + } + +} diff --git a/thirdparty/embree/common/math/math.h b/thirdparty/embree/common/math/math.h new file mode 100644 index 000000000000..1982c27c15e4 --- /dev/null +++ b/thirdparty/embree/common/math/math.h @@ -0,0 +1,352 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/intrinsics.h" +#include "constants.h" +#include + +#include +#include +#include + +#if defined(__WIN32__) && !defined(__MINGW32__) +#if (__MSV_VER <= 1700) +namespace std +{ + __forceinline bool isinf ( const float x ) { return _finite(x) == 0; } + __forceinline bool isnan ( const float x ) { return _isnan(x) != 0; } + __forceinline bool isfinite (const float x) { return _finite(x) != 0; } +} +#endif +#endif + +namespace embree +{ + __forceinline bool isvalid ( const float& v ) { + return (v > -FLT_LARGE) & (v < +FLT_LARGE); + } + + __forceinline int cast_f2i(float f) { + union { float f; int i; } v; v.f = f; return v.i; + } + + __forceinline float cast_i2f(int i) { + union { float f; int i; } v; v.i = i; return v.f; + } + + __forceinline int toInt (const float& a) { return int(a); } + __forceinline float toFloat(const int& a) { return float(a); } + +#if defined(__WIN32__) + __forceinline bool finite ( const float x ) { return _finite(x) != 0; } +#endif + + __forceinline float sign ( const float x ) { return x<0?-1.0f:1.0f; } + __forceinline float sqr ( const float x ) { return x*x; } + + __forceinline float rcp ( const float x ) + { + const __m128 a = _mm_set_ss(x); + +#if defined(__AVX512VL__) + const __m128 r = _mm_rcp14_ss(_mm_set_ss(0.0f),a); +#else + const __m128 r = _mm_rcp_ss(a); +#endif + +#if defined(__AVX2__) + return _mm_cvtss_f32(_mm_mul_ss(r,_mm_fnmadd_ss(r, a, _mm_set_ss(2.0f)))); +#else + return _mm_cvtss_f32(_mm_mul_ss(r,_mm_sub_ss(_mm_set_ss(2.0f), _mm_mul_ss(r, a)))); +#endif + } + + __forceinline float signmsk ( const float x ) { + return _mm_cvtss_f32(_mm_and_ps(_mm_set_ss(x),_mm_castsi128_ps(_mm_set1_epi32(0x80000000)))); + } + __forceinline float xorf( const float x, const float y ) { + return _mm_cvtss_f32(_mm_xor_ps(_mm_set_ss(x),_mm_set_ss(y))); + } + __forceinline float andf( const float x, const unsigned y ) { + return _mm_cvtss_f32(_mm_and_ps(_mm_set_ss(x),_mm_castsi128_ps(_mm_set1_epi32(y)))); + } + __forceinline float rsqrt( const float x ) + { + const __m128 a = _mm_set_ss(x); +#if defined(__AVX512VL__) + const __m128 r = _mm_rsqrt14_ss(_mm_set_ss(0.0f),a); +#else + const __m128 r = _mm_rsqrt_ss(a); +#endif + const __m128 c = _mm_add_ss(_mm_mul_ss(_mm_set_ss(1.5f), r), + _mm_mul_ss(_mm_mul_ss(_mm_mul_ss(a, _mm_set_ss(-0.5f)), r), _mm_mul_ss(r, r))); + return _mm_cvtss_f32(c); + } + +#if defined(__WIN32__) && (__MSC_VER <= 1700) + __forceinline float nextafter(float x, float y) { if ((x0)) return x*(1.1f+float(ulp)); else return x*(0.9f-float(ulp)); } + __forceinline double nextafter(double x, double y) { return _nextafter(x, y); } + __forceinline int roundf(float f) { return (int)(f + 0.5f); } +#else + __forceinline float nextafter(float x, float y) { return ::nextafterf(x, y); } + __forceinline double nextafter(double x, double y) { return ::nextafter(x, y); } +#endif + + __forceinline float abs ( const float x ) { return ::fabsf(x); } + __forceinline float acos ( const float x ) { return ::acosf (x); } + __forceinline float asin ( const float x ) { return ::asinf (x); } + __forceinline float atan ( const float x ) { return ::atanf (x); } + __forceinline float atan2( const float y, const float x ) { return ::atan2f(y, x); } + __forceinline float cos ( const float x ) { return ::cosf (x); } + __forceinline float cosh ( const float x ) { return ::coshf (x); } + __forceinline float exp ( const float x ) { return ::expf (x); } + __forceinline float fmod ( const float x, const float y ) { return ::fmodf (x, y); } + __forceinline float log ( const float x ) { return ::logf (x); } + __forceinline float log10( const float x ) { return ::log10f(x); } + __forceinline float pow ( const float x, const float y ) { return ::powf (x, y); } + __forceinline float sin ( const float x ) { return ::sinf (x); } + __forceinline float sinh ( const float x ) { return ::sinhf (x); } + __forceinline float sqrt ( const float x ) { return ::sqrtf (x); } + __forceinline float tan ( const float x ) { return ::tanf (x); } + __forceinline float tanh ( const float x ) { return ::tanhf (x); } + __forceinline float floor( const float x ) { return ::floorf (x); } + __forceinline float ceil ( const float x ) { return ::ceilf (x); } + __forceinline float frac ( const float x ) { return x-floor(x); } + + __forceinline double abs ( const double x ) { return ::fabs(x); } + __forceinline double sign ( const double x ) { return x<0?-1.0:1.0; } + __forceinline double acos ( const double x ) { return ::acos (x); } + __forceinline double asin ( const double x ) { return ::asin (x); } + __forceinline double atan ( const double x ) { return ::atan (x); } + __forceinline double atan2( const double y, const double x ) { return ::atan2(y, x); } + __forceinline double cos ( const double x ) { return ::cos (x); } + __forceinline double cosh ( const double x ) { return ::cosh (x); } + __forceinline double exp ( const double x ) { return ::exp (x); } + __forceinline double fmod ( const double x, const double y ) { return ::fmod (x, y); } + __forceinline double log ( const double x ) { return ::log (x); } + __forceinline double log10( const double x ) { return ::log10(x); } + __forceinline double pow ( const double x, const double y ) { return ::pow (x, y); } + __forceinline double rcp ( const double x ) { return 1.0/x; } + __forceinline double rsqrt( const double x ) { return 1.0/::sqrt(x); } + __forceinline double sin ( const double x ) { return ::sin (x); } + __forceinline double sinh ( const double x ) { return ::sinh (x); } + __forceinline double sqr ( const double x ) { return x*x; } + __forceinline double sqrt ( const double x ) { return ::sqrt (x); } + __forceinline double tan ( const double x ) { return ::tan (x); } + __forceinline double tanh ( const double x ) { return ::tanh (x); } + __forceinline double floor( const double x ) { return ::floor (x); } + __forceinline double ceil ( const double x ) { return ::ceil (x); } + +#if defined(__SSE4_1__) + __forceinline float mini(float a, float b) { + const __m128i ai = _mm_castps_si128(_mm_set_ss(a)); + const __m128i bi = _mm_castps_si128(_mm_set_ss(b)); + const __m128i ci = _mm_min_epi32(ai,bi); + return _mm_cvtss_f32(_mm_castsi128_ps(ci)); + } +#endif + +#if defined(__SSE4_1__) + __forceinline float maxi(float a, float b) { + const __m128i ai = _mm_castps_si128(_mm_set_ss(a)); + const __m128i bi = _mm_castps_si128(_mm_set_ss(b)); + const __m128i ci = _mm_max_epi32(ai,bi); + return _mm_cvtss_f32(_mm_castsi128_ps(ci)); + } +#endif + + template + __forceinline T twice(const T& a) { return a+a; } + + __forceinline int min(int a, int b) { return a __forceinline T min(const T& a, const T& b, const T& c) { return min(min(a,b),c); } + template __forceinline T min(const T& a, const T& b, const T& c, const T& d) { return min(min(a,b),min(c,d)); } + template __forceinline T min(const T& a, const T& b, const T& c, const T& d, const T& e) { return min(min(min(a,b),min(c,d)),e); } + + template __forceinline T mini(const T& a, const T& b, const T& c) { return mini(mini(a,b),c); } + template __forceinline T mini(const T& a, const T& b, const T& c, const T& d) { return mini(mini(a,b),mini(c,d)); } + template __forceinline T mini(const T& a, const T& b, const T& c, const T& d, const T& e) { return mini(mini(mini(a,b),mini(c,d)),e); } + + __forceinline int max(int a, int b) { return a __forceinline T max(const T& a, const T& b, const T& c) { return max(max(a,b),c); } + template __forceinline T max(const T& a, const T& b, const T& c, const T& d) { return max(max(a,b),max(c,d)); } + template __forceinline T max(const T& a, const T& b, const T& c, const T& d, const T& e) { return max(max(max(a,b),max(c,d)),e); } + + template __forceinline T maxi(const T& a, const T& b, const T& c) { return maxi(maxi(a,b),c); } + template __forceinline T maxi(const T& a, const T& b, const T& c, const T& d) { return maxi(maxi(a,b),maxi(c,d)); } + template __forceinline T maxi(const T& a, const T& b, const T& c, const T& d, const T& e) { return maxi(maxi(maxi(a,b),maxi(c,d)),e); } + +#if defined(__MACOSX__) + __forceinline ssize_t min(ssize_t a, ssize_t b) { return a __forceinline T clamp(const T& x, const T& lower = T(zero), const T& upper = T(one)) { return max(min(x,upper),lower); } + template __forceinline T clampz(const T& x, const T& upper) { return max(T(zero), min(x,upper)); } + + template __forceinline T deg2rad ( const T& x ) { return x * T(1.74532925199432957692e-2f); } + template __forceinline T rad2deg ( const T& x ) { return x * T(5.72957795130823208768e1f); } + template __forceinline T sin2cos ( const T& x ) { return sqrt(max(T(zero),T(one)-x*x)); } + template __forceinline T cos2sin ( const T& x ) { return sin2cos(x); } + +#if defined(__AVX2__) + __forceinline float madd ( const float a, const float b, const float c) { return _mm_cvtss_f32(_mm_fmadd_ss(_mm_set_ss(a),_mm_set_ss(b),_mm_set_ss(c))); } + __forceinline float msub ( const float a, const float b, const float c) { return _mm_cvtss_f32(_mm_fmsub_ss(_mm_set_ss(a),_mm_set_ss(b),_mm_set_ss(c))); } + __forceinline float nmadd ( const float a, const float b, const float c) { return _mm_cvtss_f32(_mm_fnmadd_ss(_mm_set_ss(a),_mm_set_ss(b),_mm_set_ss(c))); } + __forceinline float nmsub ( const float a, const float b, const float c) { return _mm_cvtss_f32(_mm_fnmsub_ss(_mm_set_ss(a),_mm_set_ss(b),_mm_set_ss(c))); } +#else + __forceinline float madd ( const float a, const float b, const float c) { return a*b+c; } + __forceinline float msub ( const float a, const float b, const float c) { return a*b-c; } + __forceinline float nmadd ( const float a, const float b, const float c) { return -a*b+c;} + __forceinline float nmsub ( const float a, const float b, const float c) { return -a*b-c; } +#endif + + /*! random functions */ + template T random() { return T(0); } +#if defined(_WIN32) + template<> __forceinline int random() { return int(rand()) ^ (int(rand()) << 8) ^ (int(rand()) << 16); } + template<> __forceinline uint32_t random() { return uint32_t(rand()) ^ (uint32_t(rand()) << 8) ^ (uint32_t(rand()) << 16); } +#else + template<> __forceinline int random() { return int(rand()); } + template<> __forceinline uint32_t random() { return uint32_t(rand()) ^ (uint32_t(rand()) << 16); } +#endif + template<> __forceinline float random() { return rand()/float(RAND_MAX); } + template<> __forceinline double random() { return rand()/double(RAND_MAX); } + +#if _WIN32 + __forceinline double drand48() { + return double(rand())/double(RAND_MAX); + } + + __forceinline void srand48(long seed) { + return srand(seed); + } +#endif + + /*! selects */ + __forceinline bool select(bool s, bool t , bool f) { return s ? t : f; } + __forceinline int select(bool s, int t, int f) { return s ? t : f; } + __forceinline float select(bool s, float t, float f) { return s ? t : f; } + + __forceinline bool all(bool s) { return s; } + + __forceinline float lerp(const float v0, const float v1, const float t) { + return madd(1.0f-t,v0,t*v1); + } + + template + __forceinline T lerp2(const float x0, const float x1, const float x2, const float x3, const T& u, const T& v) { + return madd((1.0f-u),madd((1.0f-v),T(x0),v*T(x2)),u*madd((1.0f-v),T(x1),v*T(x3))); + } + + /*! exchange */ + template __forceinline void xchg ( T& a, T& b ) { const T tmp = a; a = b; b = tmp; } + + /*! bit reverse operation */ + template + __forceinline T bitReverse(const T& vin) + { + T v = vin; + v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); + v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); + v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); + v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); + v = ( v >> 16 ) | ( v << 16); + return v; + } + + /*! bit interleave operation */ + template + __forceinline T bitInterleave(const T& xin, const T& yin, const T& zin) + { + T x = xin, y = yin, z = zin; + x = (x | (x << 16)) & 0x030000FF; + x = (x | (x << 8)) & 0x0300F00F; + x = (x | (x << 4)) & 0x030C30C3; + x = (x | (x << 2)) & 0x09249249; + + y = (y | (y << 16)) & 0x030000FF; + y = (y | (y << 8)) & 0x0300F00F; + y = (y | (y << 4)) & 0x030C30C3; + y = (y | (y << 2)) & 0x09249249; + + z = (z | (z << 16)) & 0x030000FF; + z = (z | (z << 8)) & 0x0300F00F; + z = (z | (z << 4)) & 0x030C30C3; + z = (z | (z << 2)) & 0x09249249; + + return x | (y << 1) | (z << 2); + } + +#if defined(__AVX2__) + + template<> + __forceinline unsigned int bitInterleave(const unsigned int &xi, const unsigned int& yi, const unsigned int& zi) + { + const unsigned int xx = pdep(xi,0x49249249 /* 0b01001001001001001001001001001001 */ ); + const unsigned int yy = pdep(yi,0x92492492 /* 0b10010010010010010010010010010010 */); + const unsigned int zz = pdep(zi,0x24924924 /* 0b00100100100100100100100100100100 */); + return xx | yy | zz; + } + +#endif + + /*! bit interleave operation for 64bit data types*/ + template + __forceinline T bitInterleave64(const T& xin, const T& yin, const T& zin){ + T x = xin & 0x1fffff; + T y = yin & 0x1fffff; + T z = zin & 0x1fffff; + + x = (x | x << 32) & 0x1f00000000ffff; + x = (x | x << 16) & 0x1f0000ff0000ff; + x = (x | x << 8) & 0x100f00f00f00f00f; + x = (x | x << 4) & 0x10c30c30c30c30c3; + x = (x | x << 2) & 0x1249249249249249; + + y = (y | y << 32) & 0x1f00000000ffff; + y = (y | y << 16) & 0x1f0000ff0000ff; + y = (y | y << 8) & 0x100f00f00f00f00f; + y = (y | y << 4) & 0x10c30c30c30c30c3; + y = (y | y << 2) & 0x1249249249249249; + + z = (z | z << 32) & 0x1f00000000ffff; + z = (z | z << 16) & 0x1f0000ff0000ff; + z = (z | z << 8) & 0x100f00f00f00f00f; + z = (z | z << 4) & 0x10c30c30c30c30c3; + z = (z | z << 2) & 0x1249249249249249; + + return x | (y << 1) | (z << 2); + } +} diff --git a/thirdparty/embree/common/math/obbox.h b/thirdparty/embree/common/math/obbox.h new file mode 100644 index 000000000000..032b56904eef --- /dev/null +++ b/thirdparty/embree/common/math/obbox.h @@ -0,0 +1,39 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bbox.h" +#include "linearspace3.h" + +namespace embree +{ + /*! Oriented bounding box */ + template + struct OBBox + { + public: + + __forceinline OBBox () {} + + __forceinline OBBox (EmptyTy) + : space(one), bounds(empty) {} + + __forceinline OBBox (const BBox& bounds) + : space(one), bounds(bounds) {} + + __forceinline OBBox (const LinearSpace3& space, const BBox& bounds) + : space(space), bounds(bounds) {} + + friend embree_ostream operator<<(embree_ostream cout, const OBBox& p) { + return cout << "{ space = " << p.space << ", bounds = " << p.bounds << "}"; + } + + public: + LinearSpace3 space; //!< orthonormal transformation + BBox bounds; //!< bounds in transformed space + }; + + typedef OBBox OBBox3f; + typedef OBBox OBBox3fa; +} diff --git a/thirdparty/embree/common/math/quaternion.h b/thirdparty/embree/common/math/quaternion.h new file mode 100644 index 000000000000..20c69bc62f45 --- /dev/null +++ b/thirdparty/embree/common/math/quaternion.h @@ -0,0 +1,254 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "vec3.h" +#include "vec4.h" + +#include "transcendental.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////// + // Quaternion Struct + //////////////////////////////////////////////////////////////// + + template + struct QuaternionT + { + typedef Vec3 Vector; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline QuaternionT () { } + __forceinline QuaternionT ( const QuaternionT& other ) { r = other.r; i = other.i; j = other.j; k = other.k; } + __forceinline QuaternionT& operator=( const QuaternionT& other ) { r = other.r; i = other.i; j = other.j; k = other.k; return *this; } + + __forceinline QuaternionT( const T& r ) : r(r), i(zero), j(zero), k(zero) {} + __forceinline explicit QuaternionT( const Vec3& v ) : r(zero), i(v.x), j(v.y), k(v.z) {} + __forceinline explicit QuaternionT( const Vec4& v ) : r(v.x), i(v.y), j(v.z), k(v.w) {} + __forceinline QuaternionT( const T& r, const T& i, const T& j, const T& k ) : r(r), i(i), j(j), k(k) {} + __forceinline QuaternionT( const T& r, const Vec3& v ) : r(r), i(v.x), j(v.y), k(v.z) {} + + __inline QuaternionT( const Vec3& vx, const Vec3& vy, const Vec3& vz ); + __inline QuaternionT( const T& yaw, const T& pitch, const T& roll ); + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline QuaternionT( ZeroTy ) : r(zero), i(zero), j(zero), k(zero) {} + __forceinline QuaternionT( OneTy ) : r( one), i(zero), j(zero), k(zero) {} + + /*! return quaternion for rotation around arbitrary axis */ + static __forceinline QuaternionT rotate(const Vec3& u, const T& r) { + return QuaternionT(cos(T(0.5)*r),sin(T(0.5)*r)*normalize(u)); + } + + /*! returns the rotation axis of the quaternion as a vector */ + __forceinline Vec3 v( ) const { return Vec3(i, j, k); } + + public: + T r, i, j, k; + }; + + template __forceinline QuaternionT operator *( const T & a, const QuaternionT& b ) { return QuaternionT(a * b.r, a * b.i, a * b.j, a * b.k); } + template __forceinline QuaternionT operator *( const QuaternionT& a, const T & b ) { return QuaternionT(a.r * b, a.i * b, a.j * b, a.k * b); } + + //////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////// + + template __forceinline QuaternionT operator +( const QuaternionT& a ) { return QuaternionT(+a.r, +a.i, +a.j, +a.k); } + template __forceinline QuaternionT operator -( const QuaternionT& a ) { return QuaternionT(-a.r, -a.i, -a.j, -a.k); } + template __forceinline QuaternionT conj ( const QuaternionT& a ) { return QuaternionT(a.r, -a.i, -a.j, -a.k); } + template __forceinline T abs ( const QuaternionT& a ) { return sqrt(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + template __forceinline QuaternionT rcp ( const QuaternionT& a ) { return conj(a)*rcp(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + template __forceinline QuaternionT normalize ( const QuaternionT& a ) { return a*rsqrt(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + + // evaluates a*q-r + template __forceinline QuaternionT + msub(const T& a, const QuaternionT& q, const QuaternionT& p) + { + return QuaternionT(msub(a, q.r, p.r), + msub(a, q.i, p.i), + msub(a, q.j, p.j), + msub(a, q.k, p.k)); + } + // evaluates a*q-r + template __forceinline QuaternionT + madd (const T& a, const QuaternionT& q, const QuaternionT& p) + { + return QuaternionT(madd(a, q.r, p.r), + madd(a, q.i, p.i), + madd(a, q.j, p.j), + madd(a, q.k, p.k)); + } + + //////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////// + + template __forceinline QuaternionT operator +( const T & a, const QuaternionT& b ) { return QuaternionT(a + b.r, b.i, b.j, b.k); } + template __forceinline QuaternionT operator +( const QuaternionT& a, const T & b ) { return QuaternionT(a.r + b, a.i, a.j, a.k); } + template __forceinline QuaternionT operator +( const QuaternionT& a, const QuaternionT& b ) { return QuaternionT(a.r + b.r, a.i + b.i, a.j + b.j, a.k + b.k); } + template __forceinline QuaternionT operator -( const T & a, const QuaternionT& b ) { return QuaternionT(a - b.r, -b.i, -b.j, -b.k); } + template __forceinline QuaternionT operator -( const QuaternionT& a, const T & b ) { return QuaternionT(a.r - b, a.i, a.j, a.k); } + template __forceinline QuaternionT operator -( const QuaternionT& a, const QuaternionT& b ) { return QuaternionT(a.r - b.r, a.i - b.i, a.j - b.j, a.k - b.k); } + + template __forceinline Vec3 operator *( const QuaternionT& a, const Vec3 & b ) { return (a*QuaternionT(b)*conj(a)).v(); } + template __forceinline QuaternionT operator *( const QuaternionT& a, const QuaternionT& b ) { + return QuaternionT(a.r*b.r - a.i*b.i - a.j*b.j - a.k*b.k, + a.r*b.i + a.i*b.r + a.j*b.k - a.k*b.j, + a.r*b.j - a.i*b.k + a.j*b.r + a.k*b.i, + a.r*b.k + a.i*b.j - a.j*b.i + a.k*b.r); + } + template __forceinline QuaternionT operator /( const T & a, const QuaternionT& b ) { return a*rcp(b); } + template __forceinline QuaternionT operator /( const QuaternionT& a, const T & b ) { return a*rcp(b); } + template __forceinline QuaternionT operator /( const QuaternionT& a, const QuaternionT& b ) { return a*rcp(b); } + + template __forceinline QuaternionT& operator +=( QuaternionT& a, const T & b ) { return a = a+b; } + template __forceinline QuaternionT& operator +=( QuaternionT& a, const QuaternionT& b ) { return a = a+b; } + template __forceinline QuaternionT& operator -=( QuaternionT& a, const T & b ) { return a = a-b; } + template __forceinline QuaternionT& operator -=( QuaternionT& a, const QuaternionT& b ) { return a = a-b; } + template __forceinline QuaternionT& operator *=( QuaternionT& a, const T & b ) { return a = a*b; } + template __forceinline QuaternionT& operator *=( QuaternionT& a, const QuaternionT& b ) { return a = a*b; } + template __forceinline QuaternionT& operator /=( QuaternionT& a, const T & b ) { return a = a*rcp(b); } + template __forceinline QuaternionT& operator /=( QuaternionT& a, const QuaternionT& b ) { return a = a*rcp(b); } + + template __forceinline QuaternionT + select(const M& m, const QuaternionT& q, const QuaternionT& p) + { + return QuaternionT(select(m, q.r, p.r), + select(m, q.i, p.i), + select(m, q.j, p.j), + select(m, q.k, p.k)); + } + + + template __forceinline Vec3 xfmPoint ( const QuaternionT& a, const Vec3& b ) { return (a*QuaternionT(b)*conj(a)).v(); } + template __forceinline Vec3 xfmVector( const QuaternionT& a, const Vec3& b ) { return (a*QuaternionT(b)*conj(a)).v(); } + template __forceinline Vec3 xfmNormal( const QuaternionT& a, const Vec3& b ) { return (a*QuaternionT(b)*conj(a)).v(); } + + template __forceinline T dot(const QuaternionT& a, const QuaternionT& b) { return a.r*b.r + a.i*b.i + a.j*b.j + a.k*b.k; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const QuaternionT& a, const QuaternionT& b ) { return a.r == b.r && a.i == b.i && a.j == b.j && a.k == b.k; } + template __forceinline bool operator !=( const QuaternionT& a, const QuaternionT& b ) { return a.r != b.r || a.i != b.i || a.j != b.j || a.k != b.k; } + + + //////////////////////////////////////////////////////////////////////////////// + /// Orientation Functions + //////////////////////////////////////////////////////////////////////////////// + + template QuaternionT::QuaternionT( const Vec3& vx, const Vec3& vy, const Vec3& vz ) + { + if ( vx.x + vy.y + vz.z >= T(zero) ) + { + const T t = T(one) + (vx.x + vy.y + vz.z); + const T s = rsqrt(t)*T(0.5f); + r = t*s; + i = (vy.z - vz.y)*s; + j = (vz.x - vx.z)*s; + k = (vx.y - vy.x)*s; + } + else if ( vx.x >= max(vy.y, vz.z) ) + { + const T t = (T(one) + vx.x) - (vy.y + vz.z); + const T s = rsqrt(t)*T(0.5f); + r = (vy.z - vz.y)*s; + i = t*s; + j = (vx.y + vy.x)*s; + k = (vz.x + vx.z)*s; + } + else if ( vy.y >= vz.z ) // if ( vy.y >= max(vz.z, vx.x) ) + { + const T t = (T(one) + vy.y) - (vz.z + vx.x); + const T s = rsqrt(t)*T(0.5f); + r = (vz.x - vx.z)*s; + i = (vx.y + vy.x)*s; + j = t*s; + k = (vy.z + vz.y)*s; + } + else //if ( vz.z >= max(vy.y, vx.x) ) + { + const T t = (T(one) + vz.z) - (vx.x + vy.y); + const T s = rsqrt(t)*T(0.5f); + r = (vx.y - vy.x)*s; + i = (vz.x + vx.z)*s; + j = (vy.z + vz.y)*s; + k = t*s; + } + } + + template QuaternionT::QuaternionT( const T& yaw, const T& pitch, const T& roll ) + { + const T cya = cos(yaw *T(0.5f)); + const T cpi = cos(pitch*T(0.5f)); + const T cro = cos(roll *T(0.5f)); + const T sya = sin(yaw *T(0.5f)); + const T spi = sin(pitch*T(0.5f)); + const T sro = sin(roll *T(0.5f)); + r = cro*cya*cpi + sro*sya*spi; + i = cro*cya*spi + sro*sya*cpi; + j = cro*sya*cpi - sro*cya*spi; + k = sro*cya*cpi - cro*sya*spi; + } + + ////////////////////////////////////////////////////////////////////////////// + /// Output Operators + ////////////////////////////////////////////////////////////////////////////// + + template static embree_ostream operator<<(embree_ostream cout, const QuaternionT& q) { + return cout << "{ r = " << q.r << ", i = " << q.i << ", j = " << q.j << ", k = " << q.k << " }"; + } + + /*! default template instantiations */ + typedef QuaternionT Quaternion3f; + typedef QuaternionT Quaternion3d; + + template using Quaternion3vf = QuaternionT>; + typedef QuaternionT> Quaternion3vf4; + typedef QuaternionT> Quaternion3vf8; + typedef QuaternionT> Quaternion3vf16; + + ////////////////////////////////////////////////////////////////////////////// + /// Interpolation + ////////////////////////////////////////////////////////////////////////////// + template + __forceinline QuaternionTlerp(const QuaternionT& q0, + const QuaternionT& q1, + const T& factor) + { + QuaternionT q; + q.r = lerp(q0.r, q1.r, factor); + q.i = lerp(q0.i, q1.i, factor); + q.j = lerp(q0.j, q1.j, factor); + q.k = lerp(q0.k, q1.k, factor); + return q; + } + + template + __forceinline QuaternionT slerp(const QuaternionT& q0, + const QuaternionT& q1_, + const T& t) + { + T cosTheta = dot(q0, q1_); + QuaternionT q1 = select(cosTheta < 0.f, -q1_, q1_); + cosTheta = select(cosTheta < 0.f, -cosTheta, cosTheta); + if (unlikely(all(cosTheta > 0.9995f))) { + return normalize(lerp(q0, q1, t)); + } + const T phi = t * fastapprox::acos(cosTheta); + T sinPhi, cosPhi; + fastapprox::sincos(phi, sinPhi, cosPhi); + QuaternionT qperp = sinPhi * normalize(msub(cosTheta, q0, q1)); + return msub(cosPhi, q0, qperp); + } +} diff --git a/thirdparty/embree/common/math/range.h b/thirdparty/embree/common/math/range.h new file mode 100644 index 000000000000..762d9cd9eab5 --- /dev/null +++ b/thirdparty/embree/common/math/range.h @@ -0,0 +1,137 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../math/math.h" + +namespace embree +{ + template + struct range + { + __forceinline range() {} + + __forceinline range(const Ty& begin) + : _begin(begin), _end(begin+1) {} + + __forceinline range(const Ty& begin, const Ty& end) + : _begin(begin), _end(end) {} + + __forceinline range(const range& other) + : _begin(other._begin), _end(other._end) {} + + template + __forceinline range(const range& other) + : _begin(Ty(other._begin)), _end(Ty(other._end)) {} + + template + __forceinline range& operator =(const range& other) { + _begin = other._begin; + _end = other._end; + return *this; + } + + __forceinline Ty begin() const { + return _begin; + } + + __forceinline Ty end() const { + return _end; + } + + __forceinline range intersect(const range& r) const { + return range (max(_begin,r._begin),min(_end,r._end)); + } + + __forceinline Ty size() const { + return _end - _begin; + } + + __forceinline bool empty() const { + return _end <= _begin; + } + + __forceinline Ty center() const { + return (_begin + _end)/2; + } + + __forceinline std::pair split() const + { + const Ty _center = center(); + return std::make_pair(range(_begin,_center),range(_center,_end)); + } + + __forceinline void split(range& left_o, range& right_o) const + { + const Ty _center = center(); + left_o = range(_begin,_center); + right_o = range(_center,_end); + } + + __forceinline friend bool operator< (const range& r0, const range& r1) { + return r0.size() < r1.size(); + } + + friend embree_ostream operator<<(embree_ostream cout, const range& r) { + return cout << "range [" << r.begin() << ", " << r.end() << "]"; + } + + Ty _begin, _end; + }; + + template + range make_range(const Ty& begin, const Ty& end) { + return range(begin,end); + } + + template + struct extended_range : public range + { + __forceinline extended_range () {} + + __forceinline extended_range (const Ty& begin) + : range(begin), _ext_end(begin+1) {} + + __forceinline extended_range (const Ty& begin, const Ty& end) + : range(begin,end), _ext_end(end) {} + + __forceinline extended_range (const Ty& begin, const Ty& end, const Ty& ext_end) + : range(begin,end), _ext_end(ext_end) {} + + __forceinline Ty ext_end() const { + return _ext_end; + } + + __forceinline Ty ext_size() const { + return _ext_end - range::_begin; + } + + __forceinline Ty ext_range_size() const { + return _ext_end - range::_end; + } + + __forceinline bool has_ext_range() const { + assert(_ext_end >= range::_end); + return (_ext_end - range::_end) > 0; + } + + __forceinline void set_ext_range(const size_t ext_end){ + assert(ext_end >= range::_end); + _ext_end = ext_end; + } + + __forceinline void move_right(const size_t plus){ + range::_begin += plus; + range::_end += plus; + _ext_end += plus; + } + + friend embree_ostream operator<<(embree_ostream cout, const extended_range& r) { + return cout << "extended_range [" << r.begin() << ", " << r.end() << " (" << r.ext_end() << ")]"; + } + + Ty _ext_end; + }; +} diff --git a/thirdparty/embree/common/math/transcendental.h b/thirdparty/embree/common/math/transcendental.h new file mode 100644 index 000000000000..6855d82b5341 --- /dev/null +++ b/thirdparty/embree/common/math/transcendental.h @@ -0,0 +1,525 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Transcendental functions from "ispc": https://github.com/ispc/ispc/ +// Most of the transcendental implementations in ispc code come from +// Solomon Boulos's "syrah": https://github.com/boulos/syrah/ + +#include "../simd/simd.h" + +namespace embree +{ + +namespace fastapprox +{ + +template +__forceinline T sin(const T &v) +{ + static const float piOverTwoVec = 1.57079637050628662109375; + static const float twoOverPiVec = 0.636619746685028076171875; + auto scaled = v * twoOverPiVec; + auto kReal = floor(scaled); + auto k = toInt(kReal); + + // Reduced range version of x + auto x = v - kReal * piOverTwoVec; + auto kMod4 = k & 3; + auto sinUseCos = (kMod4 == 1 | kMod4 == 3); + auto flipSign = (kMod4 > 1); + + // These coefficients are from sollya with fpminimax(sin(x)/x, [|0, 2, + // 4, 6, 8, 10|], [|single...|], [0;Pi/2]); + static const float sinC2 = -0.16666667163372039794921875; + static const float sinC4 = +8.333347737789154052734375e-3; + static const float sinC6 = -1.9842604524455964565277099609375e-4; + static const float sinC8 = +2.760012648650445044040679931640625e-6; + static const float sinC10 = -2.50293279435709337121807038784027099609375e-8; + + static const float cosC2 = -0.5; + static const float cosC4 = +4.166664183139801025390625e-2; + static const float cosC6 = -1.388833043165504932403564453125e-3; + static const float cosC8 = +2.47562347794882953166961669921875e-5; + static const float cosC10 = -2.59630184018533327616751194000244140625e-7; + + auto outside = select(sinUseCos, 1., x); + auto c2 = select(sinUseCos, T(cosC2), T(sinC2)); + auto c4 = select(sinUseCos, T(cosC4), T(sinC4)); + auto c6 = select(sinUseCos, T(cosC6), T(sinC6)); + auto c8 = select(sinUseCos, T(cosC8), T(sinC8)); + auto c10 = select(sinUseCos, T(cosC10), T(sinC10)); + + auto x2 = x * x; + auto formula = x2 * c10 + c8; + formula = x2 * formula + c6; + formula = x2 * formula + c4; + formula = x2 * formula + c2; + formula = x2 * formula + 1.; + formula *= outside; + + formula = select(flipSign, -formula, formula); + return formula; +} + +template +__forceinline T cos(const T &v) +{ + static const float piOverTwoVec = 1.57079637050628662109375; + static const float twoOverPiVec = 0.636619746685028076171875; + auto scaled = v * twoOverPiVec; + auto kReal = floor(scaled); + auto k = toInt(kReal); + + // Reduced range version of x + auto x = v - kReal * piOverTwoVec; + + auto kMod4 = k & 3; + auto cosUseCos = (kMod4 == 0 | kMod4 == 2); + auto flipSign = (kMod4 == 1 | kMod4 == 2); + + const float sinC2 = -0.16666667163372039794921875; + const float sinC4 = +8.333347737789154052734375e-3; + const float sinC6 = -1.9842604524455964565277099609375e-4; + const float sinC8 = +2.760012648650445044040679931640625e-6; + const float sinC10 = -2.50293279435709337121807038784027099609375e-8; + + const float cosC2 = -0.5; + const float cosC4 = +4.166664183139801025390625e-2; + const float cosC6 = -1.388833043165504932403564453125e-3; + const float cosC8 = +2.47562347794882953166961669921875e-5; + const float cosC10 = -2.59630184018533327616751194000244140625e-7; + + auto outside = select(cosUseCos, 1., x); + auto c2 = select(cosUseCos, T(cosC2), T(sinC2)); + auto c4 = select(cosUseCos, T(cosC4), T(sinC4)); + auto c6 = select(cosUseCos, T(cosC6), T(sinC6)); + auto c8 = select(cosUseCos, T(cosC8), T(sinC8)); + auto c10 = select(cosUseCos, T(cosC10), T(sinC10)); + + auto x2 = x * x; + auto formula = x2 * c10 + c8; + formula = x2 * formula + c6; + formula = x2 * formula + c4; + formula = x2 * formula + c2; + formula = x2 * formula + 1.; + formula *= outside; + + formula = select(flipSign, -formula, formula); + return formula; +} + +template +__forceinline void sincos(const T &v, T &sinResult, T &cosResult) +{ + const float piOverTwoVec = 1.57079637050628662109375; + const float twoOverPiVec = 0.636619746685028076171875; + auto scaled = v * twoOverPiVec; + auto kReal = floor(scaled); + auto k = toInt(kReal); + + // Reduced range version of x + auto x = v - kReal * piOverTwoVec; + auto kMod4 = k & 3; + auto cosUseCos = ((kMod4 == 0) | (kMod4 == 2)); + auto sinUseCos = ((kMod4 == 1) | (kMod4 == 3)); + auto sinFlipSign = (kMod4 > 1); + auto cosFlipSign = ((kMod4 == 1) | (kMod4 == 2)); + + const float oneVec = +1.; + const float sinC2 = -0.16666667163372039794921875; + const float sinC4 = +8.333347737789154052734375e-3; + const float sinC6 = -1.9842604524455964565277099609375e-4; + const float sinC8 = +2.760012648650445044040679931640625e-6; + const float sinC10 = -2.50293279435709337121807038784027099609375e-8; + + const float cosC2 = -0.5; + const float cosC4 = +4.166664183139801025390625e-2; + const float cosC6 = -1.388833043165504932403564453125e-3; + const float cosC8 = +2.47562347794882953166961669921875e-5; + const float cosC10 = -2.59630184018533327616751194000244140625e-7; + + auto x2 = x * x; + + auto sinFormula = x2 * sinC10 + sinC8; + auto cosFormula = x2 * cosC10 + cosC8; + sinFormula = x2 * sinFormula + sinC6; + cosFormula = x2 * cosFormula + cosC6; + + sinFormula = x2 * sinFormula + sinC4; + cosFormula = x2 * cosFormula + cosC4; + + sinFormula = x2 * sinFormula + sinC2; + cosFormula = x2 * cosFormula + cosC2; + + sinFormula = x2 * sinFormula + oneVec; + cosFormula = x2 * cosFormula + oneVec; + + sinFormula *= x; + + sinResult = select(sinUseCos, cosFormula, sinFormula); + cosResult = select(cosUseCos, cosFormula, sinFormula); + + sinResult = select(sinFlipSign, -sinResult, sinResult); + cosResult = select(cosFlipSign, -cosResult, cosResult); +} + +template +__forceinline T tan(const T &v) +{ + const float piOverFourVec = 0.785398185253143310546875; + const float fourOverPiVec = 1.27323949337005615234375; + + auto xLt0 = v < 0.; + auto y = select(xLt0, -v, v); + auto scaled = y * fourOverPiVec; + + auto kReal = floor(scaled); + auto k = toInt(kReal); + + auto x = y - kReal * piOverFourVec; + + // If k & 1, x -= Pi/4 + auto needOffset = (k & 1) != 0; + x = select(needOffset, x - piOverFourVec, x); + + // If k & 3 == (0 or 3) let z = tan_In...(y) otherwise z = -cot_In0To... + auto kMod4 = k & 3; + auto useCotan = (kMod4 == 1) | (kMod4 == 2); + + const float oneVec = 1.0; + + const float tanC2 = +0.33333075046539306640625; + const float tanC4 = +0.13339905440807342529296875; + const float tanC6 = +5.3348250687122344970703125e-2; + const float tanC8 = +2.46033705770969390869140625e-2; + const float tanC10 = +2.892402000725269317626953125e-3; + const float tanC12 = +9.500005282461643218994140625e-3; + + const float cotC2 = -0.3333333432674407958984375; + const float cotC4 = -2.222204394638538360595703125e-2; + const float cotC6 = -2.11752182804048061370849609375e-3; + const float cotC8 = -2.0846328698098659515380859375e-4; + const float cotC10 = -2.548247357481159269809722900390625e-5; + const float cotC12 = -3.5257363606433500535786151885986328125e-7; + + auto x2 = x * x; + T z; + if (any(useCotan)) + { + auto cotVal = x2 * cotC12 + cotC10; + cotVal = x2 * cotVal + cotC8; + cotVal = x2 * cotVal + cotC6; + cotVal = x2 * cotVal + cotC4; + cotVal = x2 * cotVal + cotC2; + cotVal = x2 * cotVal + oneVec; + // The equation is for x * cot(x) but we need -x * cot(x) for the tan part. + cotVal /= -x; + z = cotVal; + } + auto useTan = !useCotan; + if (any(useTan)) + { + auto tanVal = x2 * tanC12 + tanC10; + tanVal = x2 * tanVal + tanC8; + tanVal = x2 * tanVal + tanC6; + tanVal = x2 * tanVal + tanC4; + tanVal = x2 * tanVal + tanC2; + tanVal = x2 * tanVal + oneVec; + // Equation was for tan(x)/x + tanVal *= x; + z = select(useTan, tanVal, z); + } + return select(xLt0, -z, z); +} + +template +__forceinline T asin(const T &x0) +{ + auto isneg = (x0 < 0.f); + auto x = abs(x0); + auto isnan = (x > 1.f); + + // sollya + // fpminimax(((asin(x)-pi/2)/-sqrt(1-x)), [|0,1,2,3,4,5|],[|single...|], + // [1e-20;.9999999999999999]); + // avg error: 1.1105439e-06, max error 1.3187528e-06 + auto v = 1.57079517841339111328125f + + x * (-0.21450997889041900634765625f + + x * (8.78556668758392333984375e-2f + + x * (-4.489909112453460693359375e-2f + + x * (1.928029954433441162109375e-2f + + x * (-4.3095736764371395111083984375e-3f))))); + + v *= -sqrt(1.f - x); + v = v + 1.57079637050628662109375f; + + v = select(v < 0.f, T(0.f), v); + v = select(isneg, -v, v); + v = select(isnan, T(cast_i2f(0x7fc00000)), v); + + return v; +} + +template +__forceinline T acos(const T &v) +{ + return 1.57079637050628662109375f - asin(v); +} + +template +__forceinline T atan(const T &v) +{ + const float piOverTwoVec = 1.57079637050628662109375; + // atan(-x) = -atan(x) (so flip from negative to positive first) + // If x > 1 -> atan(x) = Pi/2 - atan(1/x) + auto xNeg = v < 0.f; + auto xFlipped = select(xNeg, -v, v); + + auto xGt1 = xFlipped > 1.; + auto x = select(xGt1, rcpSafe(xFlipped), xFlipped); + + // These coefficients approximate atan(x)/x + const float atanC0 = +0.99999988079071044921875; + const float atanC2 = -0.3333191573619842529296875; + const float atanC4 = +0.199689209461212158203125; + const float atanC6 = -0.14015688002109527587890625; + const float atanC8 = +9.905083477497100830078125e-2; + const float atanC10 = -5.93664981424808502197265625e-2; + const float atanC12 = +2.417283318936824798583984375e-2; + const float atanC14 = -4.6721356920897960662841796875e-3; + + auto x2 = x * x; + auto result = x2 * atanC14 + atanC12; + result = x2 * result + atanC10; + result = x2 * result + atanC8; + result = x2 * result + atanC6; + result = x2 * result + atanC4; + result = x2 * result + atanC2; + result = x2 * result + atanC0; + result *= x; + + result = select(xGt1, piOverTwoVec - result, result); + result = select(xNeg, -result, result); + return result; +} + +template +__forceinline T atan2(const T &y, const T &x) +{ + const float piVec = 3.1415926536; + // atan2(y, x) = + // + // atan2(y > 0, x = +-0) -> Pi/2 + // atan2(y < 0, x = +-0) -> -Pi/2 + // atan2(y = +-0, x < +0) -> +-Pi + // atan2(y = +-0, x >= +0) -> +-0 + // + // atan2(y >= 0, x < 0) -> Pi + atan(y/x) + // atan2(y < 0, x < 0) -> -Pi + atan(y/x) + // atan2(y, x > 0) -> atan(y/x) + // + // and then a bunch of code for dealing with infinities. + auto yOverX = y * rcpSafe(x); + auto atanArg = atan(yOverX); + auto xLt0 = x < 0.f; + auto yLt0 = y < 0.f; + auto offset = select(xLt0, + select(yLt0, T(-piVec), T(piVec)), 0.f); + return offset + atanArg; +} + +template +__forceinline T exp(const T &v) +{ + const float ln2Part1 = 0.6931457519; + const float ln2Part2 = 1.4286067653e-6; + const float oneOverLn2 = 1.44269502162933349609375; + + auto scaled = v * oneOverLn2; + auto kReal = floor(scaled); + auto k = toInt(kReal); + + // Reduced range version of x + auto x = v - kReal * ln2Part1; + x -= kReal * ln2Part2; + + // These coefficients are for e^x in [0, ln(2)] + const float one = 1.; + const float c2 = 0.4999999105930328369140625; + const float c3 = 0.166668415069580078125; + const float c4 = 4.16539050638675689697265625e-2; + const float c5 = 8.378830738365650177001953125e-3; + const float c6 = 1.304379315115511417388916015625e-3; + const float c7 = 2.7555381529964506626129150390625e-4; + + auto result = x * c7 + c6; + result = x * result + c5; + result = x * result + c4; + result = x * result + c3; + result = x * result + c2; + result = x * result + one; + result = x * result + one; + + // Compute 2^k (should differ for float and double, but I'll avoid + // it for now and just do floats) + const int fpbias = 127; + auto biasedN = k + fpbias; + auto overflow = kReal > fpbias; + // Minimum exponent is -126, so if k is <= -127 (k + 127 <= 0) + // we've got underflow. -127 * ln(2) -> -88.02. So the most + // negative float input that doesn't result in zero is like -88. + auto underflow = kReal <= -fpbias; + const int infBits = 0x7f800000; + biasedN <<= 23; + // Reinterpret this thing as float + auto twoToTheN = asFloat(biasedN); + // Handle both doubles and floats (hopefully eliding the copy for float) + auto elemtype2n = twoToTheN; + result *= elemtype2n; + result = select(overflow, cast_i2f(infBits), result); + result = select(underflow, 0., result); + return result; +} + +// Range reduction for logarithms takes log(x) -> log(2^n * y) -> n +// * log(2) + log(y) where y is the reduced range (usually in [1/2, 1)). +template +__forceinline void __rangeReduceLog(const T &input, + T &reduced, + R &exponent) +{ + auto intVersion = asInt(input); + // single precision = SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM + // exponent mask = 0111 1111 1000 0000 0000 0000 0000 0000 + // 0x7 0xF 0x8 0x0 0x0 0x0 0x0 0x0 + // non-exponent = 1000 0000 0111 1111 1111 1111 1111 1111 + // = 0x8 0x0 0x7 0xF 0xF 0xF 0xF 0xF + + //const int exponentMask(0x7F800000) + static const int nonexponentMask = 0x807FFFFF; + + // We want the reduced version to have an exponent of -1 which is + // -1 + 127 after biasing or 126 + static const int exponentNeg1 = (126l << 23); + // NOTE(boulos): We don't need to mask anything out since we know + // the sign bit has to be 0. If it's 1, we need to return infinity/nan + // anyway (log(x), x = +-0 -> infinity, x < 0 -> NaN). + auto biasedExponent = intVersion >> 23; // This number is [0, 255] but it means [-127, 128] + + auto offsetExponent = biasedExponent + 1; // Treat the number as if it were 2^{e+1} * (1.m)/2 + exponent = offsetExponent - 127; // get the real value + + // Blend the offset_exponent with the original input (do this in + // int for now, until I decide if float can have & and ¬) + auto blended = (intVersion & nonexponentMask) | (exponentNeg1); + reduced = asFloat(blended); +} + +template struct ExponentType { }; +template struct ExponentType> { typedef vint Ty; }; +template <> struct ExponentType { typedef int Ty; }; + +template +__forceinline T log(const T &v) +{ + T reduced; + typename ExponentType::Ty exponent; + + const int nanBits = 0x7fc00000; + const int negInfBits = 0xFF800000; + const float nan = cast_i2f(nanBits); + const float negInf = cast_i2f(negInfBits); + auto useNan = v < 0.; + auto useInf = v == 0.; + auto exceptional = useNan | useInf; + const float one = 1.0; + + auto patched = select(exceptional, one, v); + __rangeReduceLog(patched, reduced, exponent); + + const float ln2 = 0.693147182464599609375; + + auto x1 = one - reduced; + const float c1 = +0.50000095367431640625; + const float c2 = +0.33326041698455810546875; + const float c3 = +0.2519190013408660888671875; + const float c4 = +0.17541764676570892333984375; + const float c5 = +0.3424419462680816650390625; + const float c6 = -0.599632322788238525390625; + const float c7 = +1.98442304134368896484375; + const float c8 = -2.4899270534515380859375; + const float c9 = +1.7491014003753662109375; + + auto result = x1 * c9 + c8; + result = x1 * result + c7; + result = x1 * result + c6; + result = x1 * result + c5; + result = x1 * result + c4; + result = x1 * result + c3; + result = x1 * result + c2; + result = x1 * result + c1; + result = x1 * result + one; + + // Equation was for -(ln(red)/(1-red)) + result *= -x1; + result += toFloat(exponent) * ln2; + + return select(exceptional, + select(useNan, T(nan), T(negInf)), + result); +} + +template +__forceinline T pow(const T &x, const T &y) +{ + auto x1 = abs(x); + auto z = exp(y * log(x1)); + + // Handle special cases + const float twoOver23 = 8388608.0f; + auto yInt = y == round(y); + auto yOddInt = select(yInt, asInt(abs(y) + twoOver23) << 31, 0); // set sign bit + + // x == 0 + z = select(x == 0.0f, + select(y < 0.0f, T(inf) | signmsk(x), + select(y == 0.0f, T(1.0f), asFloat(yOddInt) & x)), z); + + // x < 0 + auto xNegative = x < 0.0f; + if (any(xNegative)) + { + auto z1 = z | asFloat(yOddInt); + z1 = select(yInt, z1, std::numeric_limits::quiet_NaN()); + z = select(xNegative, z1, z); + } + + auto xFinite = isfinite(x); + auto yFinite = isfinite(y); + if (all(xFinite & yFinite)) + return z; + + // x finite and y infinite + z = select(andn(xFinite, yFinite), + select(x1 == 1.0f, 1.0f, + select((x1 > 1.0f) ^ (y < 0.0f), inf, T(0.0f))), z); + + // x infinite + z = select(xFinite, z, + select(y == 0.0f, 1.0f, + select(y < 0.0f, T(0.0f), inf) | (asFloat(yOddInt) & x))); + + return z; +} + +template +__forceinline T pow(const T &x, float y) +{ + return pow(x, T(y)); +} + +} // namespace fastapprox + +} // namespace embree diff --git a/thirdparty/embree/common/math/vec2.h b/thirdparty/embree/common/math/vec2.h new file mode 100644 index 000000000000..0ecf8c6384d5 --- /dev/null +++ b/thirdparty/embree/common/math/vec2.h @@ -0,0 +1,235 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "math.h" + +namespace embree +{ + struct Vec2fa; + + //////////////////////////////////////////////////////////////////////////////// + /// Generic 2D vector Class + //////////////////////////////////////////////////////////////////////////////// + + template struct Vec2 + { + enum { N = 2 }; + union { + struct { T x, y; }; +#if !(defined(__WIN32__) && _MSC_VER == 1800) // workaround for older VS 2013 compiler + T components[N]; +#endif + }; + + typedef T Scalar; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2( ) {} + __forceinline explicit Vec2( const T& a ) : x(a), y(a) {} + __forceinline Vec2( const T& x, const T& y ) : x(x), y(y) {} + + __forceinline Vec2( const Vec2& other ) { x = other.x; y = other.y; } + __forceinline Vec2( const Vec2fa& other ); + + template __forceinline Vec2( const Vec2& a ) : x(T(a.x)), y(T(a.y)) {} + template __forceinline Vec2& operator =( const Vec2& other ) { x = other.x; y = other.y; return *this; } + + __forceinline Vec2& operator =( const Vec2& other ) { x = other.x; y = other.y; return *this; } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2( ZeroTy ) : x(zero), y(zero) {} + __forceinline Vec2( OneTy ) : x(one), y(one) {} + __forceinline Vec2( PosInfTy ) : x(pos_inf), y(pos_inf) {} + __forceinline Vec2( NegInfTy ) : x(neg_inf), y(neg_inf) {} + +#if defined(__WIN32__) && _MSC_VER == 1800 // workaround for older VS 2013 compiler + __forceinline const T& operator [](const size_t axis) const { assert(axis < 2); return (&x)[axis]; } + __forceinline T& operator [](const size_t axis) { assert(axis < 2); return (&x)[axis]; } +#else + __forceinline const T& operator [](const size_t axis) const { assert(axis < 2); return components[axis]; } + __forceinline T& operator [](const size_t axis ) { assert(axis < 2); return components[axis]; } +#endif + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec2 operator +( const Vec2& a ) { return Vec2(+a.x, +a.y); } + template __forceinline Vec2 operator -( const Vec2& a ) { return Vec2(-a.x, -a.y); } + template __forceinline Vec2 abs ( const Vec2& a ) { return Vec2(abs (a.x), abs (a.y)); } + template __forceinline Vec2 rcp ( const Vec2& a ) { return Vec2(rcp (a.x), rcp (a.y)); } + template __forceinline Vec2 rsqrt ( const Vec2& a ) { return Vec2(rsqrt(a.x), rsqrt(a.y)); } + template __forceinline Vec2 sqrt ( const Vec2& a ) { return Vec2(sqrt (a.x), sqrt (a.y)); } + template __forceinline Vec2 frac ( const Vec2& a ) { return Vec2(frac (a.x), frac (a.y)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec2 operator +( const Vec2& a, const Vec2& b ) { return Vec2(a.x + b.x, a.y + b.y); } + template __forceinline Vec2 operator +( const Vec2& a, const T& b ) { return Vec2(a.x + b , a.y + b ); } + template __forceinline Vec2 operator +( const T& a, const Vec2& b ) { return Vec2(a + b.x, a + b.y); } + template __forceinline Vec2 operator -( const Vec2& a, const Vec2& b ) { return Vec2(a.x - b.x, a.y - b.y); } + template __forceinline Vec2 operator -( const Vec2& a, const T& b ) { return Vec2(a.x - b , a.y - b ); } + template __forceinline Vec2 operator -( const T& a, const Vec2& b ) { return Vec2(a - b.x, a - b.y); } + template __forceinline Vec2 operator *( const Vec2& a, const Vec2& b ) { return Vec2(a.x * b.x, a.y * b.y); } + template __forceinline Vec2 operator *( const T& a, const Vec2& b ) { return Vec2(a * b.x, a * b.y); } + template __forceinline Vec2 operator *( const Vec2& a, const T& b ) { return Vec2(a.x * b , a.y * b ); } + template __forceinline Vec2 operator /( const Vec2& a, const Vec2& b ) { return Vec2(a.x / b.x, a.y / b.y); } + template __forceinline Vec2 operator /( const Vec2& a, const T& b ) { return Vec2(a.x / b , a.y / b ); } + template __forceinline Vec2 operator /( const T& a, const Vec2& b ) { return Vec2(a / b.x, a / b.y); } + + template __forceinline Vec2 min(const Vec2& a, const Vec2& b) { return Vec2(min(a.x, b.x), min(a.y, b.y)); } + template __forceinline Vec2 max(const Vec2& a, const Vec2& b) { return Vec2(max(a.x, b.x), max(a.y, b.y)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec2 madd ( const Vec2& a, const Vec2& b, const Vec2& c) { return Vec2( madd(a.x,b.x,c.x), madd(a.y,b.y,c.y) ); } + template __forceinline Vec2 msub ( const Vec2& a, const Vec2& b, const Vec2& c) { return Vec2( msub(a.x,b.x,c.x), msub(a.y,b.y,c.y) ); } + template __forceinline Vec2 nmadd ( const Vec2& a, const Vec2& b, const Vec2& c) { return Vec2(nmadd(a.x,b.x,c.x),nmadd(a.y,b.y,c.y) ); } + template __forceinline Vec2 nmsub ( const Vec2& a, const Vec2& b, const Vec2& c) { return Vec2(nmsub(a.x,b.x,c.x),nmsub(a.y,b.y,c.y) ); } + + template __forceinline Vec2 madd ( const T& a, const Vec2& b, const Vec2& c) { return Vec2( madd(a,b.x,c.x), madd(a,b.y,c.y) ); } + template __forceinline Vec2 msub ( const T& a, const Vec2& b, const Vec2& c) { return Vec2( msub(a,b.x,c.x), msub(a,b.y,c.y) ); } + template __forceinline Vec2 nmadd ( const T& a, const Vec2& b, const Vec2& c) { return Vec2(nmadd(a,b.x,c.x),nmadd(a,b.y,c.y) ); } + template __forceinline Vec2 nmsub ( const T& a, const Vec2& b, const Vec2& c) { return Vec2(nmsub(a,b.x,c.x),nmsub(a,b.y,c.y) ); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec2& operator +=( Vec2& a, const Vec2& b ) { a.x += b.x; a.y += b.y; return a; } + template __forceinline Vec2& operator -=( Vec2& a, const Vec2& b ) { a.x -= b.x; a.y -= b.y; return a; } + template __forceinline Vec2& operator *=( Vec2& a, const T& b ) { a.x *= b ; a.y *= b ; return a; } + template __forceinline Vec2& operator /=( Vec2& a, const T& b ) { a.x /= b ; a.y /= b ; return a; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline T reduce_add( const Vec2& a ) { return a.x + a.y; } + template __forceinline T reduce_mul( const Vec2& a ) { return a.x * a.y; } + template __forceinline T reduce_min( const Vec2& a ) { return min(a.x, a.y); } + template __forceinline T reduce_max( const Vec2& a ) { return max(a.x, a.y); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const Vec2& a, const Vec2& b ) { return a.x == b.x && a.y == b.y; } + template __forceinline bool operator !=( const Vec2& a, const Vec2& b ) { return a.x != b.x || a.y != b.y; } + template __forceinline bool operator < ( const Vec2& a, const Vec2& b ) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Shift Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec2 shift_right_1( const Vec2& a ) { + return Vec2(shift_right_1(a.x),shift_right_1(a.y)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline T dot ( const Vec2& a, const Vec2& b ) { return madd(a.x,b.x,a.y*b.y); } + template __forceinline Vec2 cross ( const Vec2& a ) { return Vec2(-a.y,a.x); } + template __forceinline T length ( const Vec2& a ) { return sqrt(dot(a,a)); } + template __forceinline Vec2 normalize( const Vec2& a ) { return a*rsqrt(dot(a,a)); } + template __forceinline T distance ( const Vec2& a, const Vec2& b ) { return length(a-b); } + template __forceinline T det ( const Vec2& a, const Vec2& b ) { return a.x*b.y - a.y*b.x; } + + template __forceinline Vec2 normalize_safe( const Vec2& a ) { + const T d = dot(a,a); return select(d == T( zero ),a, a*rsqrt(d) ); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec2 select ( bool s, const Vec2& t, const Vec2& f ) { + return Vec2(select(s,t.x,f.x),select(s,t.y,f.y)); + } + + template __forceinline Vec2 select ( const Vec2& s, const Vec2& t, const Vec2& f ) { + return Vec2(select(s.x,t.x,f.x),select(s.y,t.y,f.y)); + } + + template __forceinline Vec2 select ( const typename T::Bool& s, const Vec2& t, const Vec2& f ) { + return Vec2(select(s,t.x,f.x),select(s,t.y,f.y)); + } + + template + __forceinline Vec2 lerp(const Vec2& v0, const Vec2& v1, const T& t) { + return madd(Vec2(T(1.0f)-t),v0,t*v1); + } + + template __forceinline int maxDim ( const Vec2& a ) + { + const Vec2 b = abs(a); + if (b.x > b.y) return 0; + else return 1; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline embree_ostream operator<<(embree_ostream cout, const Vec2& a) { + return cout << "(" << a.x << ", " << a.y << ")"; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Default template instantiations + //////////////////////////////////////////////////////////////////////////////// + + typedef Vec2 Vec2b; + typedef Vec2 Vec2i; + typedef Vec2 Vec2f; +} + +#include "vec2fa.h" + +#if defined __SSE__ +#include "../simd/sse.h" +#endif + +#if defined __AVX__ +#include "../simd/avx.h" +#endif + +#if defined(__AVX512F__) +#include "../simd/avx512.h" +#endif + +namespace embree +{ + template<> __forceinline Vec2::Vec2(const Vec2fa& a) : x(a.x), y(a.y) {} + +#if defined(__SSE__) + template<> __forceinline Vec2::Vec2(const Vec2fa& a) : x(a.x), y(a.y) {} +#endif + +#if defined(__AVX__) + template<> __forceinline Vec2::Vec2(const Vec2fa& a) : x(a.x), y(a.y) {} +#endif + +#if defined(__AVX512F__) + template<> __forceinline Vec2::Vec2(const Vec2fa& a) : x(a.x), y(a.y) {} +#endif +} diff --git a/thirdparty/embree/common/math/vec2fa.h b/thirdparty/embree/common/math/vec2fa.h new file mode 100644 index 000000000000..6b1b6f33f237 --- /dev/null +++ b/thirdparty/embree/common/math/vec2fa.h @@ -0,0 +1,297 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/alloc.h" +#include "math.h" +#include "../simd/sse.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// SSE Vec2fa Type + //////////////////////////////////////////////////////////////////////////////// + + struct __aligned(16) Vec2fa + { + ALIGNED_STRUCT_(16); + + typedef float Scalar; + enum { N = 2 }; + union { + __m128 m128; + struct { float x,y,az,aw; }; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2fa( ) {} + __forceinline Vec2fa( const __m128 a ) : m128(a) {} + + __forceinline Vec2fa ( const Vec2& other ) { x = other.x; y = other.y; } + __forceinline Vec2fa& operator =( const Vec2& other ) { x = other.x; y = other.y; return *this; } + + __forceinline Vec2fa ( const Vec2fa& other ) { m128 = other.m128; } + __forceinline Vec2fa& operator =( const Vec2fa& other ) { m128 = other.m128; return *this; } + + __forceinline explicit Vec2fa( const float a ) : m128(_mm_set1_ps(a)) {} + __forceinline Vec2fa( const float x, const float y) : m128(_mm_set_ps(y, y, y, x)) {} + + __forceinline explicit Vec2fa( const __m128i a ) : m128(_mm_cvtepi32_ps(a)) {} + + __forceinline operator const __m128&() const { return m128; } + __forceinline operator __m128&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline Vec2fa load( const void* const a ) { + return Vec2fa(_mm_and_ps(_mm_load_ps((float*)a),_mm_castsi128_ps(_mm_set_epi32(0, 0, -1, -1)))); + } + + static __forceinline Vec2fa loadu( const void* const a ) { + return Vec2fa(_mm_and_ps(_mm_loadu_ps((float*)a),_mm_castsi128_ps(_mm_set_epi32(0, 0, -1, -1)))); + } + + static __forceinline void storeu ( void* ptr, const Vec2fa& v ) { + _mm_storeu_ps((float*)ptr,v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2fa( ZeroTy ) : m128(_mm_setzero_ps()) {} + __forceinline Vec2fa( OneTy ) : m128(_mm_set1_ps(1.0f)) {} + __forceinline Vec2fa( PosInfTy ) : m128(_mm_set1_ps(pos_inf)) {} + __forceinline Vec2fa( NegInfTy ) : m128(_mm_set1_ps(neg_inf)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const float& operator []( const size_t index ) const { assert(index < 2); return (&x)[index]; } + __forceinline float& operator []( const size_t index ) { assert(index < 2); return (&x)[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2fa operator +( const Vec2fa& a ) { return a; } + __forceinline Vec2fa operator -( const Vec2fa& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x80000000)); + return _mm_xor_ps(a.m128, mask); + } + __forceinline Vec2fa abs ( const Vec2fa& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff)); + return _mm_and_ps(a.m128, mask); + } + __forceinline Vec2fa sign ( const Vec2fa& a ) { + return blendv_ps(Vec2fa(one), -Vec2fa(one), _mm_cmplt_ps (a,Vec2fa(zero))); + } + + __forceinline Vec2fa rcp ( const Vec2fa& a ) + { +#if defined(__AVX512VL__) + const Vec2fa r = _mm_rcp14_ps(a.m128); +#else + const Vec2fa r = _mm_rcp_ps(a.m128); +#endif + +#if defined(__AVX2__) + const Vec2fa res = _mm_mul_ps(r,_mm_fnmadd_ps(r, a, vfloat4(2.0f))); +#else + const Vec2fa res = _mm_mul_ps(r,_mm_sub_ps(vfloat4(2.0f), _mm_mul_ps(r, a))); + //return _mm_sub_ps(_mm_add_ps(r, r), _mm_mul_ps(_mm_mul_ps(r, r), a)); +#endif + + return res; + } + + __forceinline Vec2fa sqrt ( const Vec2fa& a ) { return _mm_sqrt_ps(a.m128); } + __forceinline Vec2fa sqr ( const Vec2fa& a ) { return _mm_mul_ps(a,a); } + + __forceinline Vec2fa rsqrt( const Vec2fa& a ) + { +#if defined(__AVX512VL__) + __m128 r = _mm_rsqrt14_ps(a.m128); +#else + __m128 r = _mm_rsqrt_ps(a.m128); +#endif + return _mm_add_ps(_mm_mul_ps(_mm_set1_ps(1.5f),r), _mm_mul_ps(_mm_mul_ps(_mm_mul_ps(a, _mm_set1_ps(-0.5f)), r), _mm_mul_ps(r, r))); + } + + __forceinline Vec2fa zero_fix(const Vec2fa& a) { + return blendv_ps(a, _mm_set1_ps(min_rcp_input), _mm_cmplt_ps (abs(a).m128, _mm_set1_ps(min_rcp_input))); + } + __forceinline Vec2fa rcp_safe(const Vec2fa& a) { + return rcp(zero_fix(a)); + } + __forceinline Vec2fa log ( const Vec2fa& a ) { + return Vec2fa(logf(a.x),logf(a.y)); + } + + __forceinline Vec2fa exp ( const Vec2fa& a ) { + return Vec2fa(expf(a.x),expf(a.y)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2fa operator +( const Vec2fa& a, const Vec2fa& b ) { return _mm_add_ps(a.m128, b.m128); } + __forceinline Vec2fa operator -( const Vec2fa& a, const Vec2fa& b ) { return _mm_sub_ps(a.m128, b.m128); } + __forceinline Vec2fa operator *( const Vec2fa& a, const Vec2fa& b ) { return _mm_mul_ps(a.m128, b.m128); } + __forceinline Vec2fa operator *( const Vec2fa& a, const float b ) { return a * Vec2fa(b); } + __forceinline Vec2fa operator *( const float a, const Vec2fa& b ) { return Vec2fa(a) * b; } + __forceinline Vec2fa operator /( const Vec2fa& a, const Vec2fa& b ) { return _mm_div_ps(a.m128,b.m128); } + __forceinline Vec2fa operator /( const Vec2fa& a, const float b ) { return _mm_div_ps(a.m128,_mm_set1_ps(b)); } + __forceinline Vec2fa operator /( const float a, const Vec2fa& b ) { return _mm_div_ps(_mm_set1_ps(a),b.m128); } + + __forceinline Vec2fa min( const Vec2fa& a, const Vec2fa& b ) { return _mm_min_ps(a.m128,b.m128); } + __forceinline Vec2fa max( const Vec2fa& a, const Vec2fa& b ) { return _mm_max_ps(a.m128,b.m128); } + +#if defined(__SSE4_1__) + __forceinline Vec2fa mini(const Vec2fa& a, const Vec2fa& b) { + const vint4 ai = _mm_castps_si128(a); + const vint4 bi = _mm_castps_si128(b); + const vint4 ci = _mm_min_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } +#endif + +#if defined(__SSE4_1__) + __forceinline Vec2fa maxi(const Vec2fa& a, const Vec2fa& b) { + const vint4 ai = _mm_castps_si128(a); + const vint4 bi = _mm_castps_si128(b); + const vint4 ci = _mm_max_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } +#endif + + __forceinline Vec2fa pow ( const Vec2fa& a, const float& b ) { + return Vec2fa(powf(a.x,b),powf(a.y,b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX2__) + __forceinline Vec2fa madd ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return _mm_fmadd_ps(a,b,c); } + __forceinline Vec2fa msub ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return _mm_fmsub_ps(a,b,c); } + __forceinline Vec2fa nmadd ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return _mm_fnmadd_ps(a,b,c); } + __forceinline Vec2fa nmsub ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return _mm_fnmsub_ps(a,b,c); } +#else + __forceinline Vec2fa madd ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return a*b+c; } + __forceinline Vec2fa msub ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return a*b-c; } + __forceinline Vec2fa nmadd ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return -a*b+c;} + __forceinline Vec2fa nmsub ( const Vec2fa& a, const Vec2fa& b, const Vec2fa& c) { return -a*b-c; } +#endif + + __forceinline Vec2fa madd ( const float a, const Vec2fa& b, const Vec2fa& c) { return madd(Vec2fa(a),b,c); } + __forceinline Vec2fa msub ( const float a, const Vec2fa& b, const Vec2fa& c) { return msub(Vec2fa(a),b,c); } + __forceinline Vec2fa nmadd ( const float a, const Vec2fa& b, const Vec2fa& c) { return nmadd(Vec2fa(a),b,c); } + __forceinline Vec2fa nmsub ( const float a, const Vec2fa& b, const Vec2fa& c) { return nmsub(Vec2fa(a),b,c); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2fa& operator +=( Vec2fa& a, const Vec2fa& b ) { return a = a + b; } + __forceinline Vec2fa& operator -=( Vec2fa& a, const Vec2fa& b ) { return a = a - b; } + __forceinline Vec2fa& operator *=( Vec2fa& a, const Vec2fa& b ) { return a = a * b; } + __forceinline Vec2fa& operator *=( Vec2fa& a, const float b ) { return a = a * b; } + __forceinline Vec2fa& operator /=( Vec2fa& a, const Vec2fa& b ) { return a = a / b; } + __forceinline Vec2fa& operator /=( Vec2fa& a, const float b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline float reduce_add(const Vec2fa& v) { return v.x+v.y; } + __forceinline float reduce_mul(const Vec2fa& v) { return v.x*v.y; } + __forceinline float reduce_min(const Vec2fa& v) { return min(v.x,v.y); } + __forceinline float reduce_max(const Vec2fa& v) { return max(v.x,v.y); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator ==( const Vec2fa& a, const Vec2fa& b ) { return (_mm_movemask_ps(_mm_cmpeq_ps (a.m128, b.m128)) & 3) == 3; } + __forceinline bool operator !=( const Vec2fa& a, const Vec2fa& b ) { return (_mm_movemask_ps(_mm_cmpneq_ps(a.m128, b.m128)) & 3) != 0; } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__SSE4_1__) + __forceinline float dot ( const Vec2fa& a, const Vec2fa& b ) { + return _mm_cvtss_f32(_mm_dp_ps(a,b,0x3F)); + } +#else + __forceinline float dot ( const Vec2fa& a, const Vec2fa& b ) { + return reduce_add(a*b); + } +#endif + + __forceinline Vec2fa cross ( const Vec2fa& a ) { + return Vec2fa(-a.y,a.x); + } + + __forceinline float sqr_length ( const Vec2fa& a ) { return dot(a,a); } + __forceinline float rcp_length ( const Vec2fa& a ) { return rsqrt(dot(a,a)); } + __forceinline float rcp_length2( const Vec2fa& a ) { return rcp(dot(a,a)); } + __forceinline float length ( const Vec2fa& a ) { return sqrt(dot(a,a)); } + __forceinline Vec2fa normalize( const Vec2fa& a ) { return a*rsqrt(dot(a,a)); } + __forceinline float distance ( const Vec2fa& a, const Vec2fa& b ) { return length(a-b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec2fa select( bool s, const Vec2fa& t, const Vec2fa& f ) { + __m128 mask = s ? _mm_castsi128_ps(_mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())) : _mm_setzero_ps(); + return blendv_ps(f, t, mask); + } + + __forceinline Vec2fa lerp(const Vec2fa& v0, const Vec2fa& v1, const float t) { + return madd(1.0f-t,v0,t*v1); + } + + __forceinline int maxDim ( const Vec2fa& a ) + { + const Vec2fa b = abs(a); + if (b.x > b.y) return 0; + else return 1; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Rounding Functions + //////////////////////////////////////////////////////////////////////////////// + +#if defined (__SSE4_1__) + //__forceinline Vec2fa trunc( const Vec2fa& a ) { return _mm_round_ps(a, _MM_FROUND_TO_NEAREST_INT); } + __forceinline Vec2fa floor( const Vec2fa& a ) { return _mm_round_ps(a, _MM_FROUND_TO_NEG_INF ); } + __forceinline Vec2fa ceil ( const Vec2fa& a ) { return _mm_round_ps(a, _MM_FROUND_TO_POS_INF ); } +#else + //__forceinline Vec2fa trunc( const Vec2fa& a ) { return Vec2fa(truncf(a.x),truncf(a.y),truncf(a.z)); } + __forceinline Vec2fa floor( const Vec2fa& a ) { return Vec2fa(floorf(a.x),floorf(a.y)); } + __forceinline Vec2fa ceil ( const Vec2fa& a ) { return Vec2fa(ceilf (a.x),ceilf (a.y)); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator<<(embree_ostream cout, const Vec2fa& a) { + return cout << "(" << a.x << ", " << a.y << ")"; + } + + typedef Vec2fa Vec2fa_t; +} diff --git a/thirdparty/embree/common/math/vec3.h b/thirdparty/embree/common/math/vec3.h new file mode 100644 index 000000000000..ab4753545b80 --- /dev/null +++ b/thirdparty/embree/common/math/vec3.h @@ -0,0 +1,350 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "math.h" + +namespace embree +{ + struct Vec3fa; + + //////////////////////////////////////////////////////////////////////////////// + /// Generic 3D vector Class + //////////////////////////////////////////////////////////////////////////////// + + template struct Vec3 + { + enum { N = 3 }; + + union { + struct { + T x, y, z; + }; +#if !(defined(__WIN32__) && _MSC_VER == 1800) // workaround for older VS 2013 compiler + T components[N]; +#endif + }; + + typedef T Scalar; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3( ) {} + __forceinline explicit Vec3( const T& a ) : x(a), y(a), z(a) {} + __forceinline Vec3( const T& x, const T& y, const T& z ) : x(x), y(y), z(z) {} + + __forceinline Vec3( const Vec3& other ) { x = other.x; y = other.y; z = other.z; } + __forceinline Vec3( const Vec3fa& other ); + + template __forceinline Vec3( const Vec3& a ) : x(T(a.x)), y(T(a.y)), z(T(a.z)) {} + template __forceinline Vec3& operator =(const Vec3& other) { x = other.x; y = other.y; z = other.z; return *this; } + + __forceinline Vec3& operator =(const Vec3& other) { x = other.x; y = other.y; z = other.z; return *this; } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3( ZeroTy ) : x(zero), y(zero), z(zero) {} + __forceinline Vec3( OneTy ) : x(one), y(one), z(one) {} + __forceinline Vec3( PosInfTy ) : x(pos_inf), y(pos_inf), z(pos_inf) {} + __forceinline Vec3( NegInfTy ) : x(neg_inf), y(neg_inf), z(neg_inf) {} + +#if defined(__WIN32__) && (_MSC_VER == 1800) // workaround for older VS 2013 compiler + __forceinline const T& operator []( const size_t axis ) const { assert(axis < 3); return (&x)[axis]; } + __forceinline T& operator []( const size_t axis ) { assert(axis < 3); return (&x)[axis]; } +#else + __forceinline const T& operator [](const size_t axis) const { assert(axis < 3); return components[axis]; } + __forceinline T& operator [](const size_t axis) { assert(axis < 3); return components[axis]; } +#endif + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3 operator +( const Vec3& a ) { return Vec3(+a.x, +a.y, +a.z); } + template __forceinline Vec3 operator -( const Vec3& a ) { return Vec3(-a.x, -a.y, -a.z); } + template __forceinline Vec3 abs ( const Vec3& a ) { return Vec3(abs (a.x), abs (a.y), abs (a.z)); } + template __forceinline Vec3 rcp ( const Vec3& a ) { return Vec3(rcp (a.x), rcp (a.y), rcp (a.z)); } + template __forceinline Vec3 rsqrt ( const Vec3& a ) { return Vec3(rsqrt(a.x), rsqrt(a.y), rsqrt(a.z)); } + template __forceinline Vec3 sqrt ( const Vec3& a ) { return Vec3(sqrt (a.x), sqrt (a.y), sqrt (a.z)); } + + template __forceinline Vec3 zero_fix( const Vec3& a ) + { + return Vec3(select(abs(a.x) __forceinline Vec3 rcp_safe(const Vec3& a) { return rcp(zero_fix(a)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3 operator +( const Vec3& a, const Vec3& b ) { return Vec3(a.x + b.x, a.y + b.y, a.z + b.z); } + template __forceinline Vec3 operator -( const Vec3& a, const Vec3& b ) { return Vec3(a.x - b.x, a.y - b.y, a.z - b.z); } + template __forceinline Vec3 operator *( const Vec3& a, const Vec3& b ) { return Vec3(a.x * b.x, a.y * b.y, a.z * b.z); } + template __forceinline Vec3 operator *( const T& a, const Vec3& b ) { return Vec3(a * b.x, a * b.y, a * b.z); } + template __forceinline Vec3 operator *( const Vec3& a, const T& b ) { return Vec3(a.x * b , a.y * b , a.z * b ); } + template __forceinline Vec3 operator /( const Vec3& a, const T& b ) { return Vec3(a.x / b , a.y / b , a.z / b ); } + template __forceinline Vec3 operator /( const T& a, const Vec3& b ) { return Vec3(a / b.x, a / b.y, a / b.z); } + template __forceinline Vec3 operator /( const Vec3& a, const Vec3& b ) { return Vec3(a.x / b.x, a.y / b.y, a.z / b.z); } + + template __forceinline Vec3 min(const Vec3& a, const Vec3& b) { return Vec3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); } + template __forceinline Vec3 max(const Vec3& a, const Vec3& b) { return Vec3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); } + + template __forceinline Vec3 operator >>( const Vec3& a, const int b ) { return Vec3(a.x >> b, a.y >> b, a.z >> b); } + template __forceinline Vec3 operator <<( const Vec3& a, const int b ) { return Vec3(a.x << b, a.y << b, a.z << b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3 madd ( const Vec3& a, const Vec3& b, const Vec3& c) { return Vec3( madd(a.x,b.x,c.x), madd(a.y,b.y,c.y), madd(a.z,b.z,c.z)); } + template __forceinline Vec3 msub ( const Vec3& a, const Vec3& b, const Vec3& c) { return Vec3( msub(a.x,b.x,c.x), msub(a.y,b.y,c.y), msub(a.z,b.z,c.z)); } + template __forceinline Vec3 nmadd ( const Vec3& a, const Vec3& b, const Vec3& c) { return Vec3(nmadd(a.x,b.x,c.x),nmadd(a.y,b.y,c.y),nmadd(a.z,b.z,c.z));} + template __forceinline Vec3 nmsub ( const Vec3& a, const Vec3& b, const Vec3& c) { return Vec3(nmsub(a.x,b.x,c.x),nmsub(a.y,b.y,c.y),nmsub(a.z,b.z,c.z)); } + + template __forceinline Vec3 madd ( const T& a, const Vec3& b, const Vec3& c) { return Vec3( madd(a,b.x,c.x), madd(a,b.y,c.y), madd(a,b.z,c.z)); } + template __forceinline Vec3 msub ( const T& a, const Vec3& b, const Vec3& c) { return Vec3( msub(a,b.x,c.x), msub(a,b.y,c.y), msub(a,b.z,c.z)); } + template __forceinline Vec3 nmadd ( const T& a, const Vec3& b, const Vec3& c) { return Vec3(nmadd(a,b.x,c.x),nmadd(a,b.y,c.y),nmadd(a,b.z,c.z));} + template __forceinline Vec3 nmsub ( const T& a, const Vec3& b, const Vec3& c) { return Vec3(nmsub(a,b.x,c.x),nmsub(a,b.y,c.y),nmsub(a,b.z,c.z)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3& operator +=( Vec3& a, const T b ) { a.x += b; a.y += b; a.z += b; return a; } + template __forceinline Vec3& operator +=( Vec3& a, const Vec3& b ) { a.x += b.x; a.y += b.y; a.z += b.z; return a; } + template __forceinline Vec3& operator -=( Vec3& a, const Vec3& b ) { a.x -= b.x; a.y -= b.y; a.z -= b.z; return a; } + template __forceinline Vec3& operator *=( Vec3& a, const T& b ) { a.x *= b ; a.y *= b ; a.z *= b ; return a; } + template __forceinline Vec3& operator /=( Vec3& a, const T& b ) { a.x /= b ; a.y /= b ; a.z /= b ; return a; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline T reduce_add( const Vec3& a ) { return a.x + a.y + a.z; } + template __forceinline T reduce_mul( const Vec3& a ) { return a.x * a.y * a.z; } + template __forceinline T reduce_min( const Vec3& a ) { return min(a.x, a.y, a.z); } + template __forceinline T reduce_max( const Vec3& a ) { return max(a.x, a.y, a.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const Vec3& a, const Vec3& b ) { return a.x == b.x && a.y == b.y && a.z == b.z; } + template __forceinline bool operator !=( const Vec3& a, const Vec3& b ) { return a.x != b.x || a.y != b.y || a.z != b.z; } + template __forceinline bool operator < ( const Vec3& a, const Vec3& b ) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + if (a.z != b.z) return a.z < b.z; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Shift Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3 shift_right_1( const Vec3& a ) { + return Vec3(shift_right_1(a.x),shift_right_1(a.y),shift_right_1(a.z)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3 select ( bool s, const Vec3& t, const Vec3& f ) { + return Vec3(select(s,t.x,f.x),select(s,t.y,f.y),select(s,t.z,f.z)); + } + + template __forceinline Vec3 select ( const Vec3& s, const Vec3& t, const Vec3& f ) { + return Vec3(select(s.x,t.x,f.x),select(s.y,t.y,f.y),select(s.z,t.z,f.z)); + } + + template __forceinline Vec3 select ( const typename T::Bool& s, const Vec3& t, const Vec3& f ) { + return Vec3(select(s,t.x,f.x),select(s,t.y,f.y),select(s,t.z,f.z)); + } + + template + __forceinline Vec3 lerp(const Vec3& v0, const Vec3& v1, const T& t) { + return madd(Vec3(T(1.0f)-t),v0,t*v1); + } + + template __forceinline int maxDim ( const Vec3& a ) + { + const Vec3 b = abs(a); + if (b.x > b.y) { + if (b.x > b.z) return 0; else return 2; + } else { + if (b.y > b.z) return 1; else return 2; + } + } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec3 eq_mask( const Vec3& a, const Vec3& b ) { return Vec3(a.x==b.x,a.y==b.y,a.z==b.z); } + template __forceinline Vec3 neq_mask(const Vec3& a, const Vec3& b ) { return Vec3(a.x!=b.x,a.y!=b.y,a.z!=b.z); } + template __forceinline Vec3 lt_mask( const Vec3& a, const Vec3& b ) { return Vec3(a.x< b.x,a.y< b.y,a.z< b.z); } + template __forceinline Vec3 le_mask( const Vec3& a, const Vec3& b ) { return Vec3(a.x<=b.x,a.y<=b.y,a.z<=b.z); } + template __forceinline Vec3 gt_mask( const Vec3& a, const Vec3& b ) { return Vec3(a.x> b.x,a.y> b.y,a.z> b.z); } + template __forceinline Vec3 ge_mask( const Vec3& a, const Vec3& b ) { return Vec3(a.x>=b.x,a.y>=b.y,a.z>=b.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline T sqr ( const Vec3& a ) { return dot(a,a); } + template __forceinline T dot ( const Vec3& a, const Vec3& b ) { return madd(a.x,b.x,madd(a.y,b.y,a.z*b.z)); } + template __forceinline T length ( const Vec3& a ) { return sqrt(sqr(a)); } + template __forceinline T rcp_length( const Vec3& a ) { return rsqrt(sqr(a)); } + template __forceinline Vec3 normalize( const Vec3& a ) { return a*rsqrt(sqr(a)); } + template __forceinline T distance ( const Vec3& a, const Vec3& b ) { return length(a-b); } + template __forceinline Vec3 cross ( const Vec3& a, const Vec3& b ) { return Vec3(msub(a.y,b.z,a.z*b.y), msub(a.z,b.x,a.x*b.z), msub(a.x,b.y,a.y*b.x)); } + + template __forceinline Vec3 stable_triangle_normal( const Vec3& a, const Vec3& b, const Vec3& c ) + { + const T ab_x = a.z*b.y, ab_y = a.x*b.z, ab_z = a.y*b.x; + const T bc_x = b.z*c.y, bc_y = b.x*c.z, bc_z = b.y*c.x; + const Vec3 cross_ab(msub(a.y,b.z,ab_x), msub(a.z,b.x,ab_y), msub(a.x,b.y,ab_z)); + const Vec3 cross_bc(msub(b.y,c.z,bc_x), msub(b.z,c.x,bc_y), msub(b.x,c.y,bc_z)); + const auto sx = abs(ab_x) < abs(bc_x); + const auto sy = abs(ab_y) < abs(bc_y); + const auto sz = abs(ab_z) < abs(bc_z); + return Vec3(select(sx,cross_ab.x,cross_bc.x), + select(sy,cross_ab.y,cross_bc.y), + select(sz,cross_ab.z,cross_bc.z)); + } + + template __forceinline T sum ( const Vec3& a ) { return a.x+a.y+a.z; } + + template __forceinline T halfArea ( const Vec3& d ) { return madd(d.x,(d.y+d.z),d.y*d.z); } + template __forceinline T area ( const Vec3& d ) { return 2.0f*halfArea(d); } + + template __forceinline Vec3 normalize_safe( const Vec3& a ) { + const T d = dot(a,a); return select(d == T( zero ), a , a*rsqrt(d) ); + } + + template __forceinline T sqr_point_to_line_distance(const Vec3& P, const Vec3& Q0, const Vec3& Q1) + { + const Vec3 N = cross(P-Q0,Q1-Q0); + const Vec3 D = Q1-Q0; + return dot(N,N)*rcp(dot(D,D)); + } + + template __forceinline T sqr_point_to_line_distance(const Vec3& PmQ0, const Vec3& Q1mQ0) + { + const Vec3 N = cross(PmQ0,Q1mQ0); + const Vec3 D = Q1mQ0; + return dot(N,N)*rcp(dot(D,D)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline embree_ostream operator<<(embree_ostream cout, const Vec3& a) { + return cout << "(" << a.x << ", " << a.y << ", " << a.z << ")"; + } + + typedef Vec3 Vec3b; + typedef Vec3 Vec3i; + typedef Vec3 Vec3f; +} + +#include "vec3ba.h" +#include "vec3ia.h" +#include "vec3fa.h" + +//////////////////////////////////////////////////////////////////////////////// +/// SSE / AVX / MIC specializations +//////////////////////////////////////////////////////////////////////////////// + +#if defined __SSE__ +#include "../simd/sse.h" +#endif + +#if defined __AVX__ +#include "../simd/avx.h" +#endif + +#if defined(__AVX512F__) +#include "../simd/avx512.h" +#endif + +namespace embree +{ + template + __forceinline Vec3 broadcast(const Vec3& a, const size_t k) { + return Vec3(Out(a.x[k]), Out(a.y[k]), Out(a.z[k])); + } + + template<> __forceinline Vec3::Vec3(const Vec3fa& a) { x = a.x; y = a.y; z = a.z; } + +#if defined(__AVX__) + template<> __forceinline Vec3::Vec3(const Vec3fa& a) { + x = a.x; y = a.y; z = a.z; + } +#elif defined(__SSE__) + template<> + __forceinline Vec3::Vec3(const Vec3fa& a) { + const vfloat4 v = vfloat4(a.m128); x = shuffle<0,0,0,0>(v); y = shuffle<1,1,1,1>(v); z = shuffle<2,2,2,2>(v); + } +#endif + +#if defined(__SSE__) + __forceinline Vec3 broadcast4f(const Vec3& a, const size_t k) { + return Vec3(vfloat4::broadcast(&a.x[k]), vfloat4::broadcast(&a.y[k]), vfloat4::broadcast(&a.z[k])); + } + + template<> + __forceinline Vec3 broadcast(const Vec3& a, const size_t k) { + return Vec3(vfloat4::broadcast(&a.x[k]), vfloat4::broadcast(&a.y[k]), vfloat4::broadcast(&a.z[k])); + } + + template + __forceinline Vec3 shuffle(const Vec3& b) { + return Vec3(shuffle(b.x), shuffle(b.y), shuffle(b.z)); + } +#endif + +#if defined(__AVX__) + template<> + __forceinline Vec3::Vec3(const Vec3fa& a) { + x = a.x; y = a.y; z = a.z; + } + __forceinline Vec3 broadcast4f(const Vec3& a, const size_t k) { + return Vec3(vfloat4::broadcast(&a.x[k]), vfloat4::broadcast(&a.y[k]), vfloat4::broadcast(&a.z[k])); + } + __forceinline Vec3 broadcast8f(const Vec3& a, const size_t k) { + return Vec3(vfloat8::broadcast(&a.x[k]), vfloat8::broadcast(&a.y[k]), vfloat8::broadcast(&a.z[k])); + } + __forceinline Vec3 broadcast8f(const Vec3& a, const size_t k) { + return Vec3(vfloat8::broadcast(&a.x[k]), vfloat8::broadcast(&a.y[k]), vfloat8::broadcast(&a.z[k])); + } + + template<> + __forceinline Vec3 broadcast(const Vec3& a, const size_t k) { + return Vec3(vfloat8::broadcast(&a.x[k]), vfloat8::broadcast(&a.y[k]), vfloat8::broadcast(&a.z[k])); + } + template<> + __forceinline Vec3 broadcast(const Vec3& a, const size_t k) { + return Vec3(vfloat8::broadcast(&a.x[k]), vfloat8::broadcast(&a.y[k]), vfloat8::broadcast(&a.z[k])); + } + + template + __forceinline Vec3 shuffle(const Vec3& b) { + return Vec3(shuffle(b.x), shuffle(b.y), shuffle(b.z)); + } +#endif + +#if defined(__AVX512F__) + template<> __forceinline Vec3::Vec3(const Vec3fa& a) : x(a.x), y(a.y), z(a.z) {} +#endif +} diff --git a/thirdparty/embree/common/math/vec3ba.h b/thirdparty/embree/common/math/vec3ba.h new file mode 100644 index 000000000000..90f31739c2d6 --- /dev/null +++ b/thirdparty/embree/common/math/vec3ba.h @@ -0,0 +1,120 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/alloc.h" +#include "math.h" +#include "../simd/sse.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// SSE Vec3ba Type + //////////////////////////////////////////////////////////////////////////////// + + struct __aligned(16) Vec3ba + { + ALIGNED_STRUCT_(16); + + union { + __m128 m128; + struct { int x,y,z; }; + }; + + typedef int Scalar; + enum { N = 3 }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ba( ) {} + __forceinline Vec3ba( const __m128 input ) : m128(input) {} + __forceinline Vec3ba( const Vec3ba& other ) : m128(other.m128) {} + __forceinline Vec3ba& operator =(const Vec3ba& other) { m128 = other.m128; return *this; } + + __forceinline explicit Vec3ba( bool a ) + : m128(mm_lookupmask_ps[(size_t(a) << 3) | (size_t(a) << 2) | (size_t(a) << 1) | size_t(a)]) {} + __forceinline Vec3ba( bool a, bool b, bool c) + : m128(mm_lookupmask_ps[(size_t(c) << 2) | (size_t(b) << 1) | size_t(a)]) {} + + __forceinline operator const __m128&() const { return m128; } + __forceinline operator __m128&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ba( FalseTy ) : m128(_mm_setzero_ps()) {} + __forceinline Vec3ba( TrueTy ) : m128(_mm_castsi128_ps(_mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128()))) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const int& operator []( const size_t index ) const { assert(index < 3); return (&x)[index]; } + __forceinline int& operator []( const size_t index ) { assert(index < 3); return (&x)[index]; } + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ba operator !( const Vec3ba& a ) { return _mm_xor_ps(a.m128, Vec3ba(embree::True)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ba operator &( const Vec3ba& a, const Vec3ba& b ) { return _mm_and_ps(a.m128, b.m128); } + __forceinline Vec3ba operator |( const Vec3ba& a, const Vec3ba& b ) { return _mm_or_ps (a.m128, b.m128); } + __forceinline Vec3ba operator ^( const Vec3ba& a, const Vec3ba& b ) { return _mm_xor_ps(a.m128, b.m128); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ba& operator &=( Vec3ba& a, const Vec3ba& b ) { return a = a & b; } + __forceinline Vec3ba& operator |=( Vec3ba& a, const Vec3ba& b ) { return a = a | b; } + __forceinline Vec3ba& operator ^=( Vec3ba& a, const Vec3ba& b ) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator ==( const Vec3ba& a, const Vec3ba& b ) { + return (_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpeq_epi32(_mm_castps_si128(a.m128), _mm_castps_si128(b.m128)))) & 7) == 7; + } + __forceinline bool operator !=( const Vec3ba& a, const Vec3ba& b ) { + return (_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpeq_epi32(_mm_castps_si128(a.m128), _mm_castps_si128(b.m128)))) & 7) != 7; + } + __forceinline bool operator < ( const Vec3ba& a, const Vec3ba& b ) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + if (a.z != b.z) return a.z < b.z; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool reduce_and( const Vec3ba& a ) { return (_mm_movemask_ps(a) & 0x7) == 0x7; } + __forceinline bool reduce_or ( const Vec3ba& a ) { return (_mm_movemask_ps(a) & 0x7) != 0x0; } + + __forceinline bool all ( const Vec3ba& b ) { return (_mm_movemask_ps(b) & 0x7) == 0x7; } + __forceinline bool any ( const Vec3ba& b ) { return (_mm_movemask_ps(b) & 0x7) != 0x0; } + __forceinline bool none ( const Vec3ba& b ) { return (_mm_movemask_ps(b) & 0x7) == 0x0; } + + __forceinline size_t movemask(const Vec3ba& a) { return _mm_movemask_ps(a) & 0x7; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator<<(embree_ostream cout, const Vec3ba& a) { + return cout << "(" << (a.x ? "1" : "0") << ", " << (a.y ? "1" : "0") << ", " << (a.z ? "1" : "0") << ")"; + } +} diff --git a/thirdparty/embree/common/math/vec3fa.h b/thirdparty/embree/common/math/vec3fa.h new file mode 100644 index 000000000000..6576a15b4f24 --- /dev/null +++ b/thirdparty/embree/common/math/vec3fa.h @@ -0,0 +1,723 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/alloc.h" +#include "math.h" +#include "../simd/sse.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// SSE Vec3fa Type + //////////////////////////////////////////////////////////////////////////////// + + struct __aligned(16) Vec3fa + { + ALIGNED_STRUCT_(16); + + typedef float Scalar; + enum { N = 3 }; + union { + __m128 m128; + struct { float x,y,z; }; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fa( ) {} + __forceinline Vec3fa( const __m128 a ) : m128(a) {} + + __forceinline Vec3fa ( const Vec3& other ) { m128 = _mm_set_ps(0, other.z, other.y, other.x); } + //__forceinline Vec3fa& operator =( const Vec3& other ) { m128 = _mm_set_ps(0, other.z, other.y, other.x); return *this; } + + __forceinline Vec3fa ( const Vec3fa& other ) { m128 = other.m128; } + __forceinline Vec3fa& operator =( const Vec3fa& other ) { m128 = other.m128; return *this; } + + __forceinline explicit Vec3fa( const float a ) : m128(_mm_set1_ps(a)) {} + __forceinline Vec3fa( const float x, const float y, const float z) : m128(_mm_set_ps(0, z, y, x)) {} + + __forceinline explicit Vec3fa( const __m128i a ) : m128(_mm_cvtepi32_ps(a)) {} + + __forceinline explicit operator const vfloat4() const { return vfloat4(m128); } + __forceinline explicit operator const vint4() const { return vint4(_mm_cvtps_epi32(m128)); } + __forceinline explicit operator const Vec2fa() const { return Vec2fa(m128); } + __forceinline explicit operator const Vec3ia() const { return Vec3ia(_mm_cvtps_epi32(m128)); } + + //__forceinline operator const __m128&() const { return m128; } + //__forceinline operator __m128&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline Vec3fa load( const void* const a ) { + return Vec3fa(_mm_and_ps(_mm_load_ps((float*)a),_mm_castsi128_ps(_mm_set_epi32(0, -1, -1, -1)))); + } + + static __forceinline Vec3fa loadu( const void* const a ) { + return Vec3fa(_mm_loadu_ps((float*)a)); + } + + static __forceinline void storeu ( void* ptr, const Vec3fa& v ) { + _mm_storeu_ps((float*)ptr,v.m128); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fa( ZeroTy ) : m128(_mm_setzero_ps()) {} + __forceinline Vec3fa( OneTy ) : m128(_mm_set1_ps(1.0f)) {} + __forceinline Vec3fa( PosInfTy ) : m128(_mm_set1_ps(pos_inf)) {} + __forceinline Vec3fa( NegInfTy ) : m128(_mm_set1_ps(neg_inf)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const float& operator []( const size_t index ) const { assert(index < 3); return (&x)[index]; } + __forceinline float& operator []( const size_t index ) { assert(index < 3); return (&x)[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fa operator +( const Vec3fa& a ) { return a; } + __forceinline Vec3fa operator -( const Vec3fa& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x80000000)); + return _mm_xor_ps(a.m128, mask); + } + __forceinline Vec3fa abs ( const Vec3fa& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff)); + return _mm_and_ps(a.m128, mask); + } + __forceinline Vec3fa sign ( const Vec3fa& a ) { + return blendv_ps(Vec3fa(one).m128, (-Vec3fa(one)).m128, _mm_cmplt_ps (a.m128,Vec3fa(zero).m128)); + } + + __forceinline Vec3fa rcp ( const Vec3fa& a ) + { +#if defined(__AVX512VL__) + const Vec3fa r = _mm_rcp14_ps(a.m128); +#else + const Vec3fa r = _mm_rcp_ps(a.m128); +#endif + +#if defined(__AVX2__) + const Vec3fa res = _mm_mul_ps(r.m128,_mm_fnmadd_ps(r.m128, a.m128, vfloat4(2.0f))); +#else + const Vec3fa res = _mm_mul_ps(r.m128,_mm_sub_ps(vfloat4(2.0f), _mm_mul_ps(r.m128, a.m128))); + //return _mm_sub_ps(_mm_add_ps(r, r), _mm_mul_ps(_mm_mul_ps(r, r), a)); +#endif + + return res; + } + + __forceinline Vec3fa sqrt ( const Vec3fa& a ) { return _mm_sqrt_ps(a.m128); } + __forceinline Vec3fa sqr ( const Vec3fa& a ) { return _mm_mul_ps(a.m128,a.m128); } + + __forceinline Vec3fa rsqrt( const Vec3fa& a ) + { +#if defined(__AVX512VL__) + __m128 r = _mm_rsqrt14_ps(a.m128); +#else + __m128 r = _mm_rsqrt_ps(a.m128); +#endif + return _mm_add_ps(_mm_mul_ps(_mm_set1_ps(1.5f),r), _mm_mul_ps(_mm_mul_ps(_mm_mul_ps(a.m128, _mm_set1_ps(-0.5f)), r), _mm_mul_ps(r, r))); + } + + __forceinline Vec3fa zero_fix(const Vec3fa& a) { + return blendv_ps(a.m128, _mm_set1_ps(min_rcp_input), _mm_cmplt_ps (abs(a).m128, _mm_set1_ps(min_rcp_input))); + } + __forceinline Vec3fa rcp_safe(const Vec3fa& a) { + return rcp(zero_fix(a)); + } + __forceinline Vec3fa log ( const Vec3fa& a ) { + return Vec3fa(logf(a.x),logf(a.y),logf(a.z)); + } + + __forceinline Vec3fa exp ( const Vec3fa& a ) { + return Vec3fa(expf(a.x),expf(a.y),expf(a.z)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fa operator +( const Vec3fa& a, const Vec3fa& b ) { return _mm_add_ps(a.m128, b.m128); } + __forceinline Vec3fa operator -( const Vec3fa& a, const Vec3fa& b ) { return _mm_sub_ps(a.m128, b.m128); } + __forceinline Vec3fa operator *( const Vec3fa& a, const Vec3fa& b ) { return _mm_mul_ps(a.m128, b.m128); } + __forceinline Vec3fa operator *( const Vec3fa& a, const float b ) { return a * Vec3fa(b); } + __forceinline Vec3fa operator *( const float a, const Vec3fa& b ) { return Vec3fa(a) * b; } + __forceinline Vec3fa operator /( const Vec3fa& a, const Vec3fa& b ) { return _mm_div_ps(a.m128,b.m128); } + __forceinline Vec3fa operator /( const Vec3fa& a, const float b ) { return _mm_div_ps(a.m128,_mm_set1_ps(b)); } + __forceinline Vec3fa operator /( const float a, const Vec3fa& b ) { return _mm_div_ps(_mm_set1_ps(a),b.m128); } + + __forceinline Vec3fa min( const Vec3fa& a, const Vec3fa& b ) { return _mm_min_ps(a.m128,b.m128); } + __forceinline Vec3fa max( const Vec3fa& a, const Vec3fa& b ) { return _mm_max_ps(a.m128,b.m128); } + +#if defined(__SSE4_1__) + __forceinline Vec3fa mini(const Vec3fa& a, const Vec3fa& b) { + const vint4 ai = _mm_castps_si128(a.m128); + const vint4 bi = _mm_castps_si128(b.m128); + const vint4 ci = _mm_min_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } +#endif + +#if defined(__SSE4_1__) + __forceinline Vec3fa maxi(const Vec3fa& a, const Vec3fa& b) { + const vint4 ai = _mm_castps_si128(a.m128); + const vint4 bi = _mm_castps_si128(b.m128); + const vint4 ci = _mm_max_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } +#endif + + __forceinline Vec3fa pow ( const Vec3fa& a, const float& b ) { + return Vec3fa(powf(a.x,b),powf(a.y,b),powf(a.z,b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX2__) + __forceinline Vec3fa madd ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return _mm_fmadd_ps(a.m128,b.m128,c.m128); } + __forceinline Vec3fa msub ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return _mm_fmsub_ps(a.m128,b.m128,c.m128); } + __forceinline Vec3fa nmadd ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return _mm_fnmadd_ps(a.m128,b.m128,c.m128); } + __forceinline Vec3fa nmsub ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return _mm_fnmsub_ps(a.m128,b.m128,c.m128); } +#else + __forceinline Vec3fa madd ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return a*b+c; } + __forceinline Vec3fa msub ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return a*b-c; } + __forceinline Vec3fa nmadd ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return -a*b+c;} + __forceinline Vec3fa nmsub ( const Vec3fa& a, const Vec3fa& b, const Vec3fa& c) { return -a*b-c; } +#endif + + __forceinline Vec3fa madd ( const float a, const Vec3fa& b, const Vec3fa& c) { return madd(Vec3fa(a),b,c); } + __forceinline Vec3fa msub ( const float a, const Vec3fa& b, const Vec3fa& c) { return msub(Vec3fa(a),b,c); } + __forceinline Vec3fa nmadd ( const float a, const Vec3fa& b, const Vec3fa& c) { return nmadd(Vec3fa(a),b,c); } + __forceinline Vec3fa nmsub ( const float a, const Vec3fa& b, const Vec3fa& c) { return nmsub(Vec3fa(a),b,c); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fa& operator +=( Vec3fa& a, const Vec3fa& b ) { return a = a + b; } + __forceinline Vec3fa& operator -=( Vec3fa& a, const Vec3fa& b ) { return a = a - b; } + __forceinline Vec3fa& operator *=( Vec3fa& a, const Vec3fa& b ) { return a = a * b; } + __forceinline Vec3fa& operator *=( Vec3fa& a, const float b ) { return a = a * b; } + __forceinline Vec3fa& operator /=( Vec3fa& a, const Vec3fa& b ) { return a = a / b; } + __forceinline Vec3fa& operator /=( Vec3fa& a, const float b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline float reduce_add(const Vec3fa& v) { + const vfloat4 a(v.m128); + const vfloat4 b = shuffle<1>(a); + const vfloat4 c = shuffle<2>(a); + return _mm_cvtss_f32(a+b+c); + } + + __forceinline float reduce_mul(const Vec3fa& v) { return v.x*v.y*v.z; } + __forceinline float reduce_min(const Vec3fa& v) { return min(v.x,v.y,v.z); } + __forceinline float reduce_max(const Vec3fa& v) { return max(v.x,v.y,v.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator ==( const Vec3fa& a, const Vec3fa& b ) { return (_mm_movemask_ps(_mm_cmpeq_ps (a.m128, b.m128)) & 7) == 7; } + __forceinline bool operator !=( const Vec3fa& a, const Vec3fa& b ) { return (_mm_movemask_ps(_mm_cmpneq_ps(a.m128, b.m128)) & 7) != 0; } + + __forceinline Vec3ba eq_mask( const Vec3fa& a, const Vec3fa& b ) { return _mm_cmpeq_ps (a.m128, b.m128); } + __forceinline Vec3ba neq_mask(const Vec3fa& a, const Vec3fa& b ) { return _mm_cmpneq_ps(a.m128, b.m128); } + __forceinline Vec3ba lt_mask( const Vec3fa& a, const Vec3fa& b ) { return _mm_cmplt_ps (a.m128, b.m128); } + __forceinline Vec3ba le_mask( const Vec3fa& a, const Vec3fa& b ) { return _mm_cmple_ps (a.m128, b.m128); } + __forceinline Vec3ba gt_mask( const Vec3fa& a, const Vec3fa& b ) { return _mm_cmpnle_ps(a.m128, b.m128); } + __forceinline Vec3ba ge_mask( const Vec3fa& a, const Vec3fa& b ) { return _mm_cmpnlt_ps(a.m128, b.m128); } + + __forceinline bool isvalid ( const Vec3fa& v ) { + return all(gt_mask(v,Vec3fa(-FLT_LARGE)) & lt_mask(v,Vec3fa(+FLT_LARGE))); + } + + __forceinline bool is_finite ( const Vec3fa& a ) { + return all(ge_mask(a,Vec3fa(-FLT_MAX)) & le_mask(a,Vec3fa(+FLT_MAX))); + } + + __forceinline bool isvalid4 ( const Vec3fa& v ) { + return all((vfloat4(v.m128) > vfloat4(-FLT_LARGE)) & (vfloat4(v.m128) < vfloat4(+FLT_LARGE))); + } + + __forceinline bool is_finite4 ( const Vec3fa& a ) { + return all((vfloat4(a.m128) >= vfloat4(-FLT_MAX)) & (vfloat4(a.m128) <= vfloat4(+FLT_MAX))); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__SSE4_1__) + __forceinline float dot ( const Vec3fa& a, const Vec3fa& b ) { + return _mm_cvtss_f32(_mm_dp_ps(a.m128,b.m128,0x7F)); + } +#else + __forceinline float dot ( const Vec3fa& a, const Vec3fa& b ) { + return reduce_add(a*b); + } +#endif + + __forceinline Vec3fa cross ( const Vec3fa& a, const Vec3fa& b ) + { + vfloat4 a0 = vfloat4(a.m128); + vfloat4 b0 = shuffle<1,2,0,3>(vfloat4(b.m128)); + vfloat4 a1 = shuffle<1,2,0,3>(vfloat4(a.m128)); + vfloat4 b1 = vfloat4(b.m128); + return Vec3fa(shuffle<1,2,0,3>(msub(a0,b0,a1*b1))); + } + + __forceinline float sqr_length ( const Vec3fa& a ) { return dot(a,a); } + __forceinline float rcp_length ( const Vec3fa& a ) { return rsqrt(dot(a,a)); } + __forceinline float rcp_length2( const Vec3fa& a ) { return rcp(dot(a,a)); } + __forceinline float length ( const Vec3fa& a ) { return sqrt(dot(a,a)); } + __forceinline Vec3fa normalize( const Vec3fa& a ) { return a*rsqrt(dot(a,a)); } + __forceinline float distance ( const Vec3fa& a, const Vec3fa& b ) { return length(a-b); } + __forceinline float halfArea ( const Vec3fa& d ) { return madd(d.x,(d.y+d.z),d.y*d.z); } + __forceinline float area ( const Vec3fa& d ) { return 2.0f*halfArea(d); } + + __forceinline Vec3fa normalize_safe( const Vec3fa& a ) { + const float d = dot(a,a); if (unlikely(d == 0.0f)) return a; else return a*rsqrt(d); + } + + /*! differentiated normalization */ + __forceinline Vec3fa dnormalize(const Vec3fa& p, const Vec3fa& dp) + { + const float pp = dot(p,p); + const float pdp = dot(p,dp); + return (pp*dp-pdp*p)*rcp(pp)*rsqrt(pp); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fa select( bool s, const Vec3fa& t, const Vec3fa& f ) { + __m128 mask = s ? _mm_castsi128_ps(_mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())) : _mm_setzero_ps(); + return blendv_ps(f.m128, t.m128, mask); + } + + __forceinline Vec3fa select( const Vec3ba& s, const Vec3fa& t, const Vec3fa& f ) { + return blendv_ps(f.m128, t.m128, s); + } + + __forceinline Vec3fa lerp(const Vec3fa& v0, const Vec3fa& v1, const float t) { + return madd(1.0f-t,v0,t*v1); + } + + __forceinline int maxDim ( const Vec3fa& a ) + { + const Vec3fa b = abs(a); + if (b.x > b.y) { + if (b.x > b.z) return 0; else return 2; + } else { + if (b.y > b.z) return 1; else return 2; + } + } + + //////////////////////////////////////////////////////////////////////////////// + /// Rounding Functions + //////////////////////////////////////////////////////////////////////////////// + +#if defined (__SSE4_1__) + __forceinline Vec3fa trunc( const Vec3fa& a ) { return _mm_round_ps(a.m128, _MM_FROUND_TO_NEAREST_INT); } + __forceinline Vec3fa floor( const Vec3fa& a ) { return _mm_round_ps(a.m128, _MM_FROUND_TO_NEG_INF ); } + __forceinline Vec3fa ceil ( const Vec3fa& a ) { return _mm_round_ps(a.m128, _MM_FROUND_TO_POS_INF ); } +#else + __forceinline Vec3fa trunc( const Vec3fa& a ) { return Vec3fa(truncf(a.x),truncf(a.y),truncf(a.z)); } + __forceinline Vec3fa floor( const Vec3fa& a ) { return Vec3fa(floorf(a.x),floorf(a.y),floorf(a.z)); } + __forceinline Vec3fa ceil ( const Vec3fa& a ) { return Vec3fa(ceilf (a.x),ceilf (a.y),ceilf (a.z)); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator<<(embree_ostream cout, const Vec3fa& a) { + return cout << "(" << a.x << ", " << a.y << ", " << a.z << ")"; + } + + typedef Vec3fa Vec3fa_t; + + + //////////////////////////////////////////////////////////////////////////////// + /// SSE Vec3fx Type + //////////////////////////////////////////////////////////////////////////////// + + struct __aligned(16) Vec3fx + { + ALIGNED_STRUCT_(16); + + typedef float Scalar; + enum { N = 3 }; + union { + __m128 m128; + struct { float x,y,z; union { int a; unsigned u; float w; }; }; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fx( ) {} + __forceinline Vec3fx( const __m128 a ) : m128(a) {} + + __forceinline explicit Vec3fx(const Vec3fa& v) : m128(v.m128) {} + __forceinline operator Vec3fa () const { return Vec3fa(m128); } + + __forceinline explicit Vec3fx ( const Vec3& other ) { m128 = _mm_set_ps(0, other.z, other.y, other.x); } + //__forceinline Vec3fx& operator =( const Vec3& other ) { m128 = _mm_set_ps(0, other.z, other.y, other.x); return *this; } + + __forceinline Vec3fx ( const Vec3fx& other ) { m128 = other.m128; } + + __forceinline Vec3fx& operator =( const Vec3fx& other ) { m128 = other.m128; return *this; } + + __forceinline explicit Vec3fx( const float a ) : m128(_mm_set1_ps(a)) {} + __forceinline Vec3fx( const float x, const float y, const float z) : m128(_mm_set_ps(0, z, y, x)) {} + + __forceinline Vec3fx( const Vec3fa& other, const int a1) { m128 = other.m128; a = a1; } + __forceinline Vec3fx( const Vec3fa& other, const unsigned a1) { m128 = other.m128; u = a1; } + __forceinline Vec3fx( const Vec3fa& other, const float w1) { +#if defined (__SSE4_1__) + m128 = _mm_insert_ps(other.m128, _mm_set_ss(w1),3 << 4); +#else + const vint4 mask(-1,-1,-1,0); + m128 = select(vboolf4(_mm_castsi128_ps(mask)),vfloat4(other.m128),vfloat4(w1)); +#endif + } + //__forceinline Vec3fx( const float x, const float y, const float z, const int a) : x(x), y(y), z(z), a(a) {} // not working properly! + //__forceinline Vec3fx( const float x, const float y, const float z, const unsigned a) : x(x), y(y), z(z), u(a) {} // not working properly! + __forceinline Vec3fx( const float x, const float y, const float z, const float w) : m128(_mm_set_ps(w, z, y, x)) {} + + //__forceinline explicit Vec3fx( const __m128i a ) : m128(_mm_cvtepi32_ps(a)) {} + + __forceinline explicit operator const vfloat4() const { return vfloat4(m128); } + __forceinline explicit operator const vint4() const { return vint4(_mm_cvtps_epi32(m128)); } + __forceinline explicit operator const Vec2fa() const { return Vec2fa(m128); } + __forceinline explicit operator const Vec3ia() const { return Vec3ia(_mm_cvtps_epi32(m128)); } + + //__forceinline operator const __m128&() const { return m128; } + //__forceinline operator __m128&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline Vec3fx load( const void* const a ) { + return Vec3fx(_mm_and_ps(_mm_load_ps((float*)a),_mm_castsi128_ps(_mm_set_epi32(0, -1, -1, -1)))); + } + + static __forceinline Vec3fx loadu( const void* const a ) { + return Vec3fx(_mm_loadu_ps((float*)a)); + } + + static __forceinline void storeu ( void* ptr, const Vec3fx& v ) { + _mm_storeu_ps((float*)ptr,v.m128); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fx( ZeroTy ) : m128(_mm_setzero_ps()) {} + __forceinline Vec3fx( OneTy ) : m128(_mm_set1_ps(1.0f)) {} + __forceinline Vec3fx( PosInfTy ) : m128(_mm_set1_ps(pos_inf)) {} + __forceinline Vec3fx( NegInfTy ) : m128(_mm_set1_ps(neg_inf)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const float& operator []( const size_t index ) const { assert(index < 3); return (&x)[index]; } + __forceinline float& operator []( const size_t index ) { assert(index < 3); return (&x)[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fx operator +( const Vec3fx& a ) { return a; } + __forceinline Vec3fx operator -( const Vec3fx& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x80000000)); + return _mm_xor_ps(a.m128, mask); + } + __forceinline Vec3fx abs ( const Vec3fx& a ) { + const __m128 mask = _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff)); + return _mm_and_ps(a.m128, mask); + } + __forceinline Vec3fx sign ( const Vec3fx& a ) { + return blendv_ps(Vec3fx(one).m128, (-Vec3fx(one)).m128, _mm_cmplt_ps (a.m128,Vec3fx(zero).m128)); + } + + __forceinline Vec3fx rcp ( const Vec3fx& a ) + { +#if defined(__AVX512VL__) + const Vec3fx r = _mm_rcp14_ps(a.m128); +#else + const Vec3fx r = _mm_rcp_ps(a.m128); +#endif + +#if defined(__AVX2__) + const Vec3fx res = _mm_mul_ps(r.m128,_mm_fnmadd_ps(r.m128, a.m128, vfloat4(2.0f))); +#else + const Vec3fx res = _mm_mul_ps(r.m128,_mm_sub_ps(vfloat4(2.0f), _mm_mul_ps(r.m128, a.m128))); + //return _mm_sub_ps(_mm_add_ps(r, r), _mm_mul_ps(_mm_mul_ps(r, r), a)); +#endif + + return res; + } + + __forceinline Vec3fx sqrt ( const Vec3fx& a ) { return _mm_sqrt_ps(a.m128); } + __forceinline Vec3fx sqr ( const Vec3fx& a ) { return _mm_mul_ps(a.m128,a.m128); } + + __forceinline Vec3fx rsqrt( const Vec3fx& a ) + { +#if defined(__AVX512VL__) + __m128 r = _mm_rsqrt14_ps(a.m128); +#else + __m128 r = _mm_rsqrt_ps(a.m128); +#endif + return _mm_add_ps(_mm_mul_ps(_mm_set1_ps(1.5f),r), _mm_mul_ps(_mm_mul_ps(_mm_mul_ps(a.m128, _mm_set1_ps(-0.5f)), r), _mm_mul_ps(r, r))); + } + + __forceinline Vec3fx zero_fix(const Vec3fx& a) { + return blendv_ps(a.m128, _mm_set1_ps(min_rcp_input), _mm_cmplt_ps (abs(a).m128, _mm_set1_ps(min_rcp_input))); + } + __forceinline Vec3fx rcp_safe(const Vec3fx& a) { + return rcp(zero_fix(a)); + } + __forceinline Vec3fx log ( const Vec3fx& a ) { + return Vec3fx(logf(a.x),logf(a.y),logf(a.z)); + } + + __forceinline Vec3fx exp ( const Vec3fx& a ) { + return Vec3fx(expf(a.x),expf(a.y),expf(a.z)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fx operator +( const Vec3fx& a, const Vec3fx& b ) { return _mm_add_ps(a.m128, b.m128); } + __forceinline Vec3fx operator -( const Vec3fx& a, const Vec3fx& b ) { return _mm_sub_ps(a.m128, b.m128); } + __forceinline Vec3fx operator *( const Vec3fx& a, const Vec3fx& b ) { return _mm_mul_ps(a.m128, b.m128); } + __forceinline Vec3fx operator *( const Vec3fx& a, const float b ) { return a * Vec3fx(b); } + __forceinline Vec3fx operator *( const float a, const Vec3fx& b ) { return Vec3fx(a) * b; } + __forceinline Vec3fx operator /( const Vec3fx& a, const Vec3fx& b ) { return _mm_div_ps(a.m128,b.m128); } + __forceinline Vec3fx operator /( const Vec3fx& a, const float b ) { return _mm_div_ps(a.m128,_mm_set1_ps(b)); } + __forceinline Vec3fx operator /( const float a, const Vec3fx& b ) { return _mm_div_ps(_mm_set1_ps(a),b.m128); } + + __forceinline Vec3fx min( const Vec3fx& a, const Vec3fx& b ) { return _mm_min_ps(a.m128,b.m128); } + __forceinline Vec3fx max( const Vec3fx& a, const Vec3fx& b ) { return _mm_max_ps(a.m128,b.m128); } + +#if defined(__SSE4_1__) + __forceinline Vec3fx mini(const Vec3fx& a, const Vec3fx& b) { + const vint4 ai = _mm_castps_si128(a.m128); + const vint4 bi = _mm_castps_si128(b.m128); + const vint4 ci = _mm_min_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } +#endif + +#if defined(__SSE4_1__) + __forceinline Vec3fx maxi(const Vec3fx& a, const Vec3fx& b) { + const vint4 ai = _mm_castps_si128(a.m128); + const vint4 bi = _mm_castps_si128(b.m128); + const vint4 ci = _mm_max_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } +#endif + + __forceinline Vec3fx pow ( const Vec3fx& a, const float& b ) { + return Vec3fx(powf(a.x,b),powf(a.y,b),powf(a.z,b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX2__) + __forceinline Vec3fx madd ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return _mm_fmadd_ps(a.m128,b.m128,c.m128); } + __forceinline Vec3fx msub ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return _mm_fmsub_ps(a.m128,b.m128,c.m128); } + __forceinline Vec3fx nmadd ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return _mm_fnmadd_ps(a.m128,b.m128,c.m128); } + __forceinline Vec3fx nmsub ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return _mm_fnmsub_ps(a.m128,b.m128,c.m128); } +#else + __forceinline Vec3fx madd ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return a*b+c; } + __forceinline Vec3fx msub ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return a*b-c; } + __forceinline Vec3fx nmadd ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return -a*b+c;} + __forceinline Vec3fx nmsub ( const Vec3fx& a, const Vec3fx& b, const Vec3fx& c) { return -a*b-c; } +#endif + + __forceinline Vec3fx madd ( const float a, const Vec3fx& b, const Vec3fx& c) { return madd(Vec3fx(a),b,c); } + __forceinline Vec3fx msub ( const float a, const Vec3fx& b, const Vec3fx& c) { return msub(Vec3fx(a),b,c); } + __forceinline Vec3fx nmadd ( const float a, const Vec3fx& b, const Vec3fx& c) { return nmadd(Vec3fx(a),b,c); } + __forceinline Vec3fx nmsub ( const float a, const Vec3fx& b, const Vec3fx& c) { return nmsub(Vec3fx(a),b,c); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fx& operator +=( Vec3fx& a, const Vec3fx& b ) { return a = a + b; } + __forceinline Vec3fx& operator -=( Vec3fx& a, const Vec3fx& b ) { return a = a - b; } + __forceinline Vec3fx& operator *=( Vec3fx& a, const Vec3fx& b ) { return a = a * b; } + __forceinline Vec3fx& operator *=( Vec3fx& a, const float b ) { return a = a * b; } + __forceinline Vec3fx& operator /=( Vec3fx& a, const Vec3fx& b ) { return a = a / b; } + __forceinline Vec3fx& operator /=( Vec3fx& a, const float b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline float reduce_add(const Vec3fx& v) { + const vfloat4 a(v.m128); + const vfloat4 b = shuffle<1>(a); + const vfloat4 c = shuffle<2>(a); + return _mm_cvtss_f32(a+b+c); + } + + __forceinline float reduce_mul(const Vec3fx& v) { return v.x*v.y*v.z; } + __forceinline float reduce_min(const Vec3fx& v) { return min(v.x,v.y,v.z); } + __forceinline float reduce_max(const Vec3fx& v) { return max(v.x,v.y,v.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator ==( const Vec3fx& a, const Vec3fx& b ) { return (_mm_movemask_ps(_mm_cmpeq_ps (a.m128, b.m128)) & 7) == 7; } + __forceinline bool operator !=( const Vec3fx& a, const Vec3fx& b ) { return (_mm_movemask_ps(_mm_cmpneq_ps(a.m128, b.m128)) & 7) != 0; } + + __forceinline Vec3ba eq_mask( const Vec3fx& a, const Vec3fx& b ) { return _mm_cmpeq_ps (a.m128, b.m128); } + __forceinline Vec3ba neq_mask(const Vec3fx& a, const Vec3fx& b ) { return _mm_cmpneq_ps(a.m128, b.m128); } + __forceinline Vec3ba lt_mask( const Vec3fx& a, const Vec3fx& b ) { return _mm_cmplt_ps (a.m128, b.m128); } + __forceinline Vec3ba le_mask( const Vec3fx& a, const Vec3fx& b ) { return _mm_cmple_ps (a.m128, b.m128); } + __forceinline Vec3ba gt_mask( const Vec3fx& a, const Vec3fx& b ) { return _mm_cmpnle_ps(a.m128, b.m128); } + __forceinline Vec3ba ge_mask( const Vec3fx& a, const Vec3fx& b ) { return _mm_cmpnlt_ps(a.m128, b.m128); } + + __forceinline bool isvalid ( const Vec3fx& v ) { + return all(gt_mask(v,Vec3fx(-FLT_LARGE)) & lt_mask(v,Vec3fx(+FLT_LARGE))); + } + + __forceinline bool is_finite ( const Vec3fx& a ) { + return all(ge_mask(a,Vec3fx(-FLT_MAX)) & le_mask(a,Vec3fx(+FLT_MAX))); + } + + __forceinline bool isvalid4 ( const Vec3fx& v ) { + return all((vfloat4(v.m128) > vfloat4(-FLT_LARGE)) & (vfloat4(v.m128) < vfloat4(+FLT_LARGE))); + } + + __forceinline bool is_finite4 ( const Vec3fx& a ) { + return all((vfloat4(a.m128) >= vfloat4(-FLT_MAX)) & (vfloat4(a.m128) <= vfloat4(+FLT_MAX))); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__SSE4_1__) + __forceinline float dot ( const Vec3fx& a, const Vec3fx& b ) { + return _mm_cvtss_f32(_mm_dp_ps(a.m128,b.m128,0x7F)); + } +#else + __forceinline float dot ( const Vec3fx& a, const Vec3fx& b ) { + return reduce_add(a*b); + } +#endif + + __forceinline Vec3fx cross ( const Vec3fx& a, const Vec3fx& b ) + { + vfloat4 a0 = vfloat4(a.m128); + vfloat4 b0 = shuffle<1,2,0,3>(vfloat4(b.m128)); + vfloat4 a1 = shuffle<1,2,0,3>(vfloat4(a.m128)); + vfloat4 b1 = vfloat4(b.m128); + return Vec3fx(shuffle<1,2,0,3>(msub(a0,b0,a1*b1))); + } + + __forceinline float sqr_length ( const Vec3fx& a ) { return dot(a,a); } + __forceinline float rcp_length ( const Vec3fx& a ) { return rsqrt(dot(a,a)); } + __forceinline float rcp_length2( const Vec3fx& a ) { return rcp(dot(a,a)); } + __forceinline float length ( const Vec3fx& a ) { return sqrt(dot(a,a)); } + __forceinline Vec3fx normalize( const Vec3fx& a ) { return a*rsqrt(dot(a,a)); } + __forceinline float distance ( const Vec3fx& a, const Vec3fx& b ) { return length(a-b); } + __forceinline float halfArea ( const Vec3fx& d ) { return madd(d.x,(d.y+d.z),d.y*d.z); } + __forceinline float area ( const Vec3fx& d ) { return 2.0f*halfArea(d); } + + __forceinline Vec3fx normalize_safe( const Vec3fx& a ) { + const float d = dot(a,a); if (unlikely(d == 0.0f)) return a; else return a*rsqrt(d); + } + + /*! differentiated normalization */ + __forceinline Vec3fx dnormalize(const Vec3fx& p, const Vec3fx& dp) + { + const float pp = dot(p,p); + const float pdp = dot(p,dp); + return (pp*dp-pdp*p)*rcp(pp)*rsqrt(pp); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3fx select( bool s, const Vec3fx& t, const Vec3fx& f ) { + __m128 mask = s ? _mm_castsi128_ps(_mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())) : _mm_setzero_ps(); + return blendv_ps(f.m128, t.m128, mask); + } + + __forceinline Vec3fx select( const Vec3ba& s, const Vec3fx& t, const Vec3fx& f ) { + return blendv_ps(f.m128, t.m128, s); + } + + __forceinline Vec3fx lerp(const Vec3fx& v0, const Vec3fx& v1, const float t) { + return madd(1.0f-t,v0,t*v1); + } + + __forceinline int maxDim ( const Vec3fx& a ) + { + const Vec3fx b = abs(a); + if (b.x > b.y) { + if (b.x > b.z) return 0; else return 2; + } else { + if (b.y > b.z) return 1; else return 2; + } + } + + //////////////////////////////////////////////////////////////////////////////// + /// Rounding Functions + //////////////////////////////////////////////////////////////////////////////// + +#if defined (__SSE4_1__) + __forceinline Vec3fx trunc( const Vec3fx& a ) { return _mm_round_ps(a.m128, _MM_FROUND_TO_NEAREST_INT); } + __forceinline Vec3fx floor( const Vec3fx& a ) { return _mm_round_ps(a.m128, _MM_FROUND_TO_NEG_INF ); } + __forceinline Vec3fx ceil ( const Vec3fx& a ) { return _mm_round_ps(a.m128, _MM_FROUND_TO_POS_INF ); } +#else + __forceinline Vec3fx trunc( const Vec3fx& a ) { return Vec3fx(truncf(a.x),truncf(a.y),truncf(a.z)); } + __forceinline Vec3fx floor( const Vec3fx& a ) { return Vec3fx(floorf(a.x),floorf(a.y),floorf(a.z)); } + __forceinline Vec3fx ceil ( const Vec3fx& a ) { return Vec3fx(ceilf (a.x),ceilf (a.y),ceilf (a.z)); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator<<(embree_ostream cout, const Vec3fx& a) { + return cout << "(" << a.x << ", " << a.y << ", " << a.z << ")"; + } + + + typedef Vec3fx Vec3ff; +} diff --git a/thirdparty/embree/common/math/vec3ia.h b/thirdparty/embree/common/math/vec3ia.h new file mode 100644 index 000000000000..e1c997299400 --- /dev/null +++ b/thirdparty/embree/common/math/vec3ia.h @@ -0,0 +1,186 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/alloc.h" +#include "math.h" +#include "../simd/sse.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// SSE Vec3ia Type + //////////////////////////////////////////////////////////////////////////////// + + struct __aligned(16) Vec3ia + { + ALIGNED_STRUCT_(16); + + union { + __m128i m128; + struct { int x,y,z; }; + }; + + typedef int Scalar; + enum { N = 3 }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ia( ) {} + __forceinline Vec3ia( const __m128i a ) : m128(a) {} + __forceinline Vec3ia( const Vec3ia& other ) : m128(other.m128) {} + __forceinline Vec3ia& operator =(const Vec3ia& other) { m128 = other.m128; return *this; } + + __forceinline explicit Vec3ia( const int a ) : m128(_mm_set1_epi32(a)) {} + __forceinline Vec3ia( const int x, const int y, const int z) : m128(_mm_set_epi32(z, z, y, x)) {} + __forceinline explicit Vec3ia( const __m128 a ) : m128(_mm_cvtps_epi32(a)) {} + + __forceinline operator const __m128i&() const { return m128; } + __forceinline operator __m128i&() { return m128; } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ia( ZeroTy ) : m128(_mm_setzero_si128()) {} + __forceinline Vec3ia( OneTy ) : m128(_mm_set1_epi32(1)) {} + __forceinline Vec3ia( PosInfTy ) : m128(_mm_set1_epi32(pos_inf)) {} + __forceinline Vec3ia( NegInfTy ) : m128(_mm_set1_epi32(neg_inf)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const int& operator []( const size_t index ) const { assert(index < 3); return (&x)[index]; } + __forceinline int& operator []( const size_t index ) { assert(index < 3); return (&x)[index]; } + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ia operator +( const Vec3ia& a ) { return a; } + __forceinline Vec3ia operator -( const Vec3ia& a ) { return _mm_sub_epi32(_mm_setzero_si128(), a.m128); } +#if defined(__SSSE3__) + __forceinline Vec3ia abs ( const Vec3ia& a ) { return _mm_abs_epi32(a.m128); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ia operator +( const Vec3ia& a, const Vec3ia& b ) { return _mm_add_epi32(a.m128, b.m128); } + __forceinline Vec3ia operator +( const Vec3ia& a, const int b ) { return a+Vec3ia(b); } + __forceinline Vec3ia operator +( const int a, const Vec3ia& b ) { return Vec3ia(a)+b; } + + __forceinline Vec3ia operator -( const Vec3ia& a, const Vec3ia& b ) { return _mm_sub_epi32(a.m128, b.m128); } + __forceinline Vec3ia operator -( const Vec3ia& a, const int b ) { return a-Vec3ia(b); } + __forceinline Vec3ia operator -( const int a, const Vec3ia& b ) { return Vec3ia(a)-b; } + +#if defined(__SSE4_1__) + __forceinline Vec3ia operator *( const Vec3ia& a, const Vec3ia& b ) { return _mm_mullo_epi32(a.m128, b.m128); } + __forceinline Vec3ia operator *( const Vec3ia& a, const int b ) { return a * Vec3ia(b); } + __forceinline Vec3ia operator *( const int a, const Vec3ia& b ) { return Vec3ia(a) * b; } +#endif + + __forceinline Vec3ia operator &( const Vec3ia& a, const Vec3ia& b ) { return _mm_and_si128(a.m128, b.m128); } + __forceinline Vec3ia operator &( const Vec3ia& a, const int b ) { return a & Vec3ia(b); } + __forceinline Vec3ia operator &( const int a, const Vec3ia& b ) { return Vec3ia(a) & b; } + + __forceinline Vec3ia operator |( const Vec3ia& a, const Vec3ia& b ) { return _mm_or_si128(a.m128, b.m128); } + __forceinline Vec3ia operator |( const Vec3ia& a, const int b ) { return a | Vec3ia(b); } + __forceinline Vec3ia operator |( const int a, const Vec3ia& b ) { return Vec3ia(a) | b; } + + __forceinline Vec3ia operator ^( const Vec3ia& a, const Vec3ia& b ) { return _mm_xor_si128(a.m128, b.m128); } + __forceinline Vec3ia operator ^( const Vec3ia& a, const int b ) { return a ^ Vec3ia(b); } + __forceinline Vec3ia operator ^( const int a, const Vec3ia& b ) { return Vec3ia(a) ^ b; } + + __forceinline Vec3ia operator <<( const Vec3ia& a, const int n ) { return _mm_slli_epi32(a.m128, n); } + __forceinline Vec3ia operator >>( const Vec3ia& a, const int n ) { return _mm_srai_epi32(a.m128, n); } + + __forceinline Vec3ia sll ( const Vec3ia& a, const int b ) { return _mm_slli_epi32(a.m128, b); } + __forceinline Vec3ia sra ( const Vec3ia& a, const int b ) { return _mm_srai_epi32(a.m128, b); } + __forceinline Vec3ia srl ( const Vec3ia& a, const int b ) { return _mm_srli_epi32(a.m128, b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ia& operator +=( Vec3ia& a, const Vec3ia& b ) { return a = a + b; } + __forceinline Vec3ia& operator +=( Vec3ia& a, const int& b ) { return a = a + b; } + + __forceinline Vec3ia& operator -=( Vec3ia& a, const Vec3ia& b ) { return a = a - b; } + __forceinline Vec3ia& operator -=( Vec3ia& a, const int& b ) { return a = a - b; } + +#if defined(__SSE4_1__) + __forceinline Vec3ia& operator *=( Vec3ia& a, const Vec3ia& b ) { return a = a * b; } + __forceinline Vec3ia& operator *=( Vec3ia& a, const int& b ) { return a = a * b; } +#endif + + __forceinline Vec3ia& operator &=( Vec3ia& a, const Vec3ia& b ) { return a = a & b; } + __forceinline Vec3ia& operator &=( Vec3ia& a, const int& b ) { return a = a & b; } + + __forceinline Vec3ia& operator |=( Vec3ia& a, const Vec3ia& b ) { return a = a | b; } + __forceinline Vec3ia& operator |=( Vec3ia& a, const int& b ) { return a = a | b; } + + __forceinline Vec3ia& operator <<=( Vec3ia& a, const int& b ) { return a = a << b; } + __forceinline Vec3ia& operator >>=( Vec3ia& a, const int& b ) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int reduce_add(const Vec3ia& v) { return v.x+v.y+v.z; } + __forceinline int reduce_mul(const Vec3ia& v) { return v.x*v.y*v.z; } + __forceinline int reduce_min(const Vec3ia& v) { return min(v.x,v.y,v.z); } + __forceinline int reduce_max(const Vec3ia& v) { return max(v.x,v.y,v.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator ==( const Vec3ia& a, const Vec3ia& b ) { return (_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpeq_epi32(a.m128, b.m128))) & 7) == 7; } + __forceinline bool operator !=( const Vec3ia& a, const Vec3ia& b ) { return (_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpeq_epi32(a.m128, b.m128))) & 7) != 7; } + __forceinline bool operator < ( const Vec3ia& a, const Vec3ia& b ) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + if (a.z != b.z) return a.z < b.z; + return false; + } + + __forceinline Vec3ba eq_mask( const Vec3ia& a, const Vec3ia& b ) { return _mm_castsi128_ps(_mm_cmpeq_epi32 (a.m128, b.m128)); } + __forceinline Vec3ba lt_mask( const Vec3ia& a, const Vec3ia& b ) { return _mm_castsi128_ps(_mm_cmplt_epi32 (a.m128, b.m128)); } + __forceinline Vec3ba gt_mask( const Vec3ia& a, const Vec3ia& b ) { return _mm_castsi128_ps(_mm_cmpgt_epi32 (a.m128, b.m128)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3ia select( const Vec3ba& m, const Vec3ia& t, const Vec3ia& f ) { +#if defined(__SSE4_1__) + return _mm_castps_si128(_mm_blendv_ps(_mm_castsi128_ps(f), _mm_castsi128_ps(t), m)); +#else + return _mm_or_si128(_mm_and_si128(_mm_castps_si128(m), t), _mm_andnot_si128(_mm_castps_si128(m), f)); +#endif + } + +#if defined(__SSE4_1__) + __forceinline Vec3ia min( const Vec3ia& a, const Vec3ia& b ) { return _mm_min_epi32(a.m128,b.m128); } + __forceinline Vec3ia max( const Vec3ia& a, const Vec3ia& b ) { return _mm_max_epi32(a.m128,b.m128); } +#else + __forceinline Vec3ia min( const Vec3ia& a, const Vec3ia& b ) { return select(lt_mask(a,b),a,b); } + __forceinline Vec3ia max( const Vec3ia& a, const Vec3ia& b ) { return select(gt_mask(a,b),a,b); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator<<(embree_ostream cout, const Vec3ia& a) { + return cout << "(" << a.x << ", " << a.y << ", " << a.z << ")"; + } +} diff --git a/thirdparty/embree/common/math/vec4.h b/thirdparty/embree/common/math/vec4.h new file mode 100644 index 000000000000..3354b443178a --- /dev/null +++ b/thirdparty/embree/common/math/vec4.h @@ -0,0 +1,258 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "math.h" +#include "vec3.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// Generic 4D vector Class + //////////////////////////////////////////////////////////////////////////////// + + template struct Vec4 + { + enum { N = 4 }; + union { + struct { T x, y, z, w; }; +#if !(defined(__WIN32__) && _MSC_VER == 1800) // workaround for older VS 2013 compiler + T components[N]; +#endif + }; + + typedef T Scalar; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec4( ) {} + __forceinline explicit Vec4( const T& a ) : x(a), y(a), z(a), w(a) {} + __forceinline Vec4( const T& x, const T& y, const T& z, const T& w ) : x(x), y(y), z(z), w(w) {} + __forceinline Vec4( const Vec3& xyz, const T& w ) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {} + + __forceinline Vec4( const Vec4& other ) { x = other.x; y = other.y; z = other.z; w = other.w; } + __forceinline Vec4( const Vec3fx& other ); + + template __forceinline Vec4( const Vec4& a ) : x(T(a.x)), y(T(a.y)), z(T(a.z)), w(T(a.w)) {} + template __forceinline Vec4& operator =(const Vec4& other) { x = other.x; y = other.y; z = other.z; w = other.w; return *this; } + + __forceinline Vec4& operator =(const Vec4& other) { x = other.x; y = other.y; z = other.z; w = other.w; return *this; } + + __forceinline operator Vec3 () const { return Vec3(x,y,z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec4( ZeroTy ) : x(zero), y(zero), z(zero), w(zero) {} + __forceinline Vec4( OneTy ) : x(one), y(one), z(one), w(one) {} + __forceinline Vec4( PosInfTy ) : x(pos_inf), y(pos_inf), z(pos_inf), w(pos_inf) {} + __forceinline Vec4( NegInfTy ) : x(neg_inf), y(neg_inf), z(neg_inf), w(neg_inf) {} + +#if defined(__WIN32__) && (_MSC_VER == 1800) // workaround for older VS 2013 compiler + __forceinline const T& operator [](const size_t axis) const { assert(axis < 4); return (&x)[axis]; } + __forceinline T& operator [](const size_t axis) { assert(axis < 4); return (&x)[axis]; } +#else + __forceinline const T& operator [](const size_t axis ) const { assert(axis < 4); return components[axis]; } + __forceinline T& operator [](const size_t axis) { assert(axis < 4); return components[axis]; } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Swizzles + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Vec3 xyz() const { return Vec3(x, y, z); } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec4 operator +( const Vec4& a ) { return Vec4(+a.x, +a.y, +a.z, +a.w); } + template __forceinline Vec4 operator -( const Vec4& a ) { return Vec4(-a.x, -a.y, -a.z, -a.w); } + template __forceinline Vec4 abs ( const Vec4& a ) { return Vec4(abs (a.x), abs (a.y), abs (a.z), abs (a.w)); } + template __forceinline Vec4 rcp ( const Vec4& a ) { return Vec4(rcp (a.x), rcp (a.y), rcp (a.z), rcp (a.w)); } + template __forceinline Vec4 rsqrt ( const Vec4& a ) { return Vec4(rsqrt(a.x), rsqrt(a.y), rsqrt(a.z), rsqrt(a.w)); } + template __forceinline Vec4 sqrt ( const Vec4& a ) { return Vec4(sqrt (a.x), sqrt (a.y), sqrt (a.z), sqrt (a.w)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec4 operator +( const Vec4& a, const Vec4& b ) { return Vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); } + template __forceinline Vec4 operator -( const Vec4& a, const Vec4& b ) { return Vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); } + template __forceinline Vec4 operator *( const Vec4& a, const Vec4& b ) { return Vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); } + template __forceinline Vec4 operator *( const T& a, const Vec4& b ) { return Vec4(a * b.x, a * b.y, a * b.z, a * b.w); } + template __forceinline Vec4 operator *( const Vec4& a, const T& b ) { return Vec4(a.x * b , a.y * b , a.z * b , a.w * b ); } + template __forceinline Vec4 operator /( const Vec4& a, const Vec4& b ) { return Vec4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); } + template __forceinline Vec4 operator /( const Vec4& a, const T& b ) { return Vec4(a.x / b , a.y / b , a.z / b , a.w / b ); } + template __forceinline Vec4 operator /( const T& a, const Vec4& b ) { return Vec4(a / b.x, a / b.y, a / b.z, a / b.w); } + + template __forceinline Vec4 min(const Vec4& a, const Vec4& b) { return Vec4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); } + template __forceinline Vec4 max(const Vec4& a, const Vec4& b) { return Vec4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec4 madd ( const Vec4& a, const Vec4& b, const Vec4& c) { return Vec4( madd(a.x,b.x,c.x), madd(a.y,b.y,c.y), madd(a.z,b.z,c.z), madd(a.w,b.w,c.w)); } + template __forceinline Vec4 msub ( const Vec4& a, const Vec4& b, const Vec4& c) { return Vec4( msub(a.x,b.x,c.x), msub(a.y,b.y,c.y), msub(a.z,b.z,c.z), msub(a.w,b.w,c.w)); } + template __forceinline Vec4 nmadd ( const Vec4& a, const Vec4& b, const Vec4& c) { return Vec4(nmadd(a.x,b.x,c.x),nmadd(a.y,b.y,c.y),nmadd(a.z,b.z,c.z),nmadd(a.w,b.w,c.w)); } + template __forceinline Vec4 nmsub ( const Vec4& a, const Vec4& b, const Vec4& c) { return Vec4(nmsub(a.x,b.x,c.x),nmsub(a.y,b.y,c.y),nmsub(a.z,b.z,c.z),nmsub(a.w,b.w,c.w)); } + + template __forceinline Vec4 madd ( const T& a, const Vec4& b, const Vec4& c) { return Vec4( madd(a,b.x,c.x), madd(a,b.y,c.y), madd(a,b.z,c.z), madd(a,b.w,c.w)); } + template __forceinline Vec4 msub ( const T& a, const Vec4& b, const Vec4& c) { return Vec4( msub(a,b.x,c.x), msub(a,b.y,c.y), msub(a,b.z,c.z), msub(a,b.w,c.w)); } + template __forceinline Vec4 nmadd ( const T& a, const Vec4& b, const Vec4& c) { return Vec4(nmadd(a,b.x,c.x),nmadd(a,b.y,c.y),nmadd(a,b.z,c.z),nmadd(a,b.w,c.w)); } + template __forceinline Vec4 nmsub ( const T& a, const Vec4& b, const Vec4& c) { return Vec4(nmsub(a,b.x,c.x),nmsub(a,b.y,c.y),nmsub(a,b.z,c.z),nmsub(a,b.w,c.w)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec4& operator +=( Vec4& a, const Vec4& b ) { a.x += b.x; a.y += b.y; a.z += b.z; a.w += b.w; return a; } + template __forceinline Vec4& operator -=( Vec4& a, const Vec4& b ) { a.x -= b.x; a.y -= b.y; a.z -= b.z; a.w -= b.w; return a; } + template __forceinline Vec4& operator *=( Vec4& a, const T& b ) { a.x *= b ; a.y *= b ; a.z *= b ; a.w *= b ; return a; } + template __forceinline Vec4& operator /=( Vec4& a, const T& b ) { a.x /= b ; a.y /= b ; a.z /= b ; a.w /= b ; return a; } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline T reduce_add( const Vec4& a ) { return a.x + a.y + a.z + a.w; } + template __forceinline T reduce_mul( const Vec4& a ) { return a.x * a.y * a.z * a.w; } + template __forceinline T reduce_min( const Vec4& a ) { return min(a.x, a.y, a.z, a.w); } + template __forceinline T reduce_max( const Vec4& a ) { return max(a.x, a.y, a.z, a.w); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline bool operator ==( const Vec4& a, const Vec4& b ) { return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; } + template __forceinline bool operator !=( const Vec4& a, const Vec4& b ) { return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; } + template __forceinline bool operator < ( const Vec4& a, const Vec4& b ) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + if (a.z != b.z) return a.z < b.z; + if (a.w != b.w) return a.w < b.w; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Shift Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec4 shift_right_1( const Vec4& a ) { + return Vec4(shift_right_1(a.x),shift_right_1(a.y),shift_right_1(a.z),shift_right_1(a.w)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline T dot ( const Vec4& a, const Vec4& b ) { return madd(a.x,b.x,madd(a.y,b.y,madd(a.z,b.z,a.w*b.w))); } + + template __forceinline T length ( const Vec4& a ) { return sqrt(dot(a,a)); } + template __forceinline Vec4 normalize( const Vec4& a ) { return a*rsqrt(dot(a,a)); } + template __forceinline T distance ( const Vec4& a, const Vec4& b ) { return length(a-b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline Vec4 select ( bool s, const Vec4& t, const Vec4& f ) { + return Vec4(select(s,t.x,f.x),select(s,t.y,f.y),select(s,t.z,f.z),select(s,t.w,f.w)); + } + + template __forceinline Vec4 select ( const Vec4& s, const Vec4& t, const Vec4& f ) { + return Vec4(select(s.x,t.x,f.x),select(s.y,t.y,f.y),select(s.z,t.z,f.z),select(s.w,t.w,f.w)); + } + + template __forceinline Vec4 select ( const typename T::Bool& s, const Vec4& t, const Vec4& f ) { + return Vec4(select(s,t.x,f.x),select(s,t.y,f.y),select(s,t.z,f.z),select(s,t.w,f.w)); + } + + template + __forceinline Vec4 lerp(const Vec4& v0, const Vec4& v1, const T& t) { + return madd(Vec4(T(1.0f)-t),v0,t*v1); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template __forceinline embree_ostream operator<<(embree_ostream cout, const Vec4& a) { + return cout << "(" << a.x << ", " << a.y << ", " << a.z << ", " << a.w << ")"; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Default template instantiations + //////////////////////////////////////////////////////////////////////////////// + + typedef Vec4 Vec4b; + typedef Vec4 Vec4uc; + typedef Vec4 Vec4i; + typedef Vec4 Vec4f; +} + +#include "vec3ba.h" +#include "vec3ia.h" +#include "vec3fa.h" + +//////////////////////////////////////////////////////////////////////////////// +/// SSE / AVX / MIC specializations +//////////////////////////////////////////////////////////////////////////////// + +#if defined __SSE__ +#include "../simd/sse.h" +#endif + +#if defined __AVX__ +#include "../simd/avx.h" +#endif + +#if defined __AVX512F__ +#include "../simd/avx512.h" +#endif + +namespace embree +{ + template<> __forceinline Vec4::Vec4( const Vec3fx& a ) { x = a.x; y = a.y; z = a.z; w = a.w; } + +#if defined(__AVX__) + template<> __forceinline Vec4::Vec4( const Vec3fx& a ) { + x = a.x; y = a.y; z = a.z; w = a.w; + } +#elif defined(__SSE__) + template<> __forceinline Vec4::Vec4( const Vec3fx& a ) { + const vfloat4 v = vfloat4(a.m128); x = shuffle<0,0,0,0>(v); y = shuffle<1,1,1,1>(v); z = shuffle<2,2,2,2>(v); w = shuffle<3,3,3,3>(v); + } +#endif + +#if defined(__SSE__) + __forceinline Vec4 broadcast4f( const Vec4& a, const size_t k ) { + return Vec4(vfloat4::broadcast(&a.x[k]), vfloat4::broadcast(&a.y[k]), vfloat4::broadcast(&a.z[k]), vfloat4::broadcast(&a.w[k])); + } +#endif + +#if defined(__AVX__) + template<> __forceinline Vec4::Vec4( const Vec3fx& a ) { + x = a.x; y = a.y; z = a.z; w = a.w; + } + __forceinline Vec4 broadcast4f( const Vec4& a, const size_t k ) { + return Vec4(vfloat4::broadcast(&a.x[k]), vfloat4::broadcast(&a.y[k]), vfloat4::broadcast(&a.z[k]), vfloat4::broadcast(&a.w[k])); + } + __forceinline Vec4 broadcast8f( const Vec4& a, const size_t k ) { + return Vec4(vfloat8::broadcast(&a.x[k]), vfloat8::broadcast(&a.y[k]), vfloat8::broadcast(&a.z[k]), vfloat8::broadcast(&a.w[k])); + } + __forceinline Vec4 broadcast8f( const Vec4& a, const size_t k ) { + return Vec4(vfloat8::broadcast(&a.x[k]), vfloat8::broadcast(&a.y[k]), vfloat8::broadcast(&a.z[k]), vfloat8::broadcast(&a.w[k])); + } +#endif + +#if defined(__AVX512F__) + template<> __forceinline Vec4::Vec4( const Vec3fx& a ) : x(a.x), y(a.y), z(a.z), w(a.w) {} +#endif +} diff --git a/thirdparty/embree/common/simd/avx.h b/thirdparty/embree/common/simd/avx.h new file mode 100644 index 000000000000..c840e418058a --- /dev/null +++ b/thirdparty/embree/common/simd/avx.h @@ -0,0 +1,34 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "sse.h" + +#if defined(__AVX512VL__) +#include "vboolf8_avx512.h" +#include "vboold4_avx512.h" +#else +#include "vboolf8_avx.h" +#include "vboold4_avx.h" +#endif + +#if defined(__AVX2__) +#include "vint8_avx2.h" +#include "vuint8_avx2.h" +#if defined(__X86_64__) +#include "vllong4_avx2.h" +#endif +#else +#include "vint8_avx.h" +#include "vuint8_avx.h" +#endif +#include "vfloat8_avx.h" +#if defined(__X86_64__) +#include "vdouble4_avx.h" +#endif + +#if defined(__AVX512F__) +#include "avx512.h" +#endif + diff --git a/thirdparty/embree/common/simd/avx512.h b/thirdparty/embree/common/simd/avx512.h new file mode 100644 index 000000000000..25414ab5b1db --- /dev/null +++ b/thirdparty/embree/common/simd/avx512.h @@ -0,0 +1,41 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/intrinsics.h" +#include "../math/constants.h" +#include "../sys/alloc.h" +#include "varying.h" + +#include "vboolf16_avx512.h" +#include "vint16_avx512.h" +#include "vuint16_avx512.h" +#include "vfloat16_avx512.h" + +#include "vboold8_avx512.h" +#include "vllong8_avx512.h" +#include "vdouble8_avx512.h" + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// Prefetching + //////////////////////////////////////////////////////////////////////////////// + +#define PFHINT_L1 0 +#define PFHINT_L2 1 +#define PFHINT_NT 2 + + template + __forceinline void prefetch(const void * __restrict__ const m) + { + if (mode == PFHINT_L1) + _mm_prefetch((const char*)m,_MM_HINT_T0); + else if (mode == PFHINT_L2) + _mm_prefetch((const char*)m,_MM_HINT_T1); + else if (mode == PFHINT_NT) + _mm_prefetch((const char*)m,_MM_HINT_NTA); + } +} diff --git a/thirdparty/embree/common/simd/simd.h b/thirdparty/embree/common/simd/simd.h new file mode 100644 index 000000000000..c1351c2c8884 --- /dev/null +++ b/thirdparty/embree/common/simd/simd.h @@ -0,0 +1,110 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../math/math.h" + +/* include SSE wrapper classes */ +#if defined(__SSE__) +# include "sse.h" +#endif + +/* include AVX wrapper classes */ +#if defined(__AVX__) +# include "avx.h" +#endif + +/* include AVX512 wrapper classes */ +#if defined (__AVX512F__) +# include "avx512.h" +#endif + +namespace embree +{ + template + __forceinline vbool isfinite(const vfloat& v) + { + return (v >= vfloat(-std::numeric_limits::max())) + & (v <= vfloat( std::numeric_limits::max())); + } + + /* foreach unique */ + template + __forceinline void foreach_unique(const vbool& valid0, const vint& vi, const Closure& closure) + { + vbool valid1 = valid0; + while (any(valid1)) { + const int j = int(bsf(movemask(valid1))); + const int i = vi[j]; + const vbool valid2 = valid1 & (i == vi); + valid1 = andn(valid1, valid2); + closure(valid2, i); + } + } + + /* returns the next unique value i in vi and the corresponding valid_i mask */ + template + __forceinline int next_unique(vbool& valid, const vint& vi, /*out*/ vbool& valid_i) + { + assert(any(valid)); + const int j = int(bsf(movemask(valid))); + const int i = vi[j]; + valid_i = valid & (i == vi); + valid = andn(valid, valid_i); + return i; + } + + /* foreach unique index */ + template + __forceinline void foreach_unique_index(const vbool& valid0, const vint& vi, const Closure& closure) + { + vbool valid1 = valid0; + while (any(valid1)) { + const int j = int(bsf(movemask(valid1))); + const int i = vi[j]; + const vbool valid2 = valid1 & (i == vi); + valid1 = andn(valid1, valid2); + closure(valid2, i, j); + } + } + + /* returns the index of the next unique value i in vi and the corresponding valid_i mask */ + template + __forceinline int next_unique_index(vbool& valid, const vint& vi, /*out*/ vbool& valid_i) + { + assert(any(valid)); + const int j = int(bsf(movemask(valid))); + const int i = vi[j]; + valid_i = valid & (i == vi); + valid = andn(valid, valid_i); + return j; + } + + template + __forceinline void foreach2(int x0, int x1, int y0, int y1, const Closure& closure) + { + __aligned(64) int U[2*VSIZEX]; + __aligned(64) int V[2*VSIZEX]; + int index = 0; + for (int y=y0; y=y1; + const vintx vy = y; + for (int x=x0; x= x1; + vintx vx = x+vintx(step); + vintx::storeu(&U[index], vx); + vintx::storeu(&V[index], vy); + const int dx = min(x1-x,VSIZEX); + index += dx; + x += dx; + if (index >= VSIZEX || (lastx && lasty)) { + const vboolx valid = vintx(step) < vintx(index); + closure(valid, vintx::load(U), vintx::load(V)); + x-= max(0, index-VSIZEX); + index = 0; + } + } + } + } +} diff --git a/thirdparty/embree/common/simd/sse.cpp b/thirdparty/embree/common/simd/sse.cpp new file mode 100644 index 000000000000..1732cfa42155 --- /dev/null +++ b/thirdparty/embree/common/simd/sse.cpp @@ -0,0 +1,34 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "sse.h" + +namespace embree +{ + const __m128 mm_lookupmask_ps[16] = { + _mm_castsi128_ps(_mm_set_epi32( 0, 0, 0, 0)), + _mm_castsi128_ps(_mm_set_epi32( 0, 0, 0,-1)), + _mm_castsi128_ps(_mm_set_epi32( 0, 0,-1, 0)), + _mm_castsi128_ps(_mm_set_epi32( 0, 0,-1,-1)), + _mm_castsi128_ps(_mm_set_epi32( 0,-1, 0, 0)), + _mm_castsi128_ps(_mm_set_epi32( 0,-1, 0,-1)), + _mm_castsi128_ps(_mm_set_epi32( 0,-1,-1, 0)), + _mm_castsi128_ps(_mm_set_epi32( 0,-1,-1,-1)), + _mm_castsi128_ps(_mm_set_epi32(-1, 0, 0, 0)), + _mm_castsi128_ps(_mm_set_epi32(-1, 0, 0,-1)), + _mm_castsi128_ps(_mm_set_epi32(-1, 0,-1, 0)), + _mm_castsi128_ps(_mm_set_epi32(-1, 0,-1,-1)), + _mm_castsi128_ps(_mm_set_epi32(-1,-1, 0, 0)), + _mm_castsi128_ps(_mm_set_epi32(-1,-1, 0,-1)), + _mm_castsi128_ps(_mm_set_epi32(-1,-1,-1, 0)), + _mm_castsi128_ps(_mm_set_epi32(-1,-1,-1,-1)) + }; + + const __m128d mm_lookupmask_pd[4] = { + _mm_castsi128_pd(_mm_set_epi32( 0, 0, 0, 0)), + _mm_castsi128_pd(_mm_set_epi32( 0, 0,-1,-1)), + _mm_castsi128_pd(_mm_set_epi32(-1,-1, 0, 0)), + _mm_castsi128_pd(_mm_set_epi32(-1,-1,-1,-1)) + }; + +} diff --git a/thirdparty/embree/common/simd/sse.h b/thirdparty/embree/common/simd/sse.h new file mode 100644 index 000000000000..67df3ec00980 --- /dev/null +++ b/thirdparty/embree/common/simd/sse.h @@ -0,0 +1,35 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/intrinsics.h" +#include "../sys/alloc.h" +#include "../math/constants.h" +#include "varying.h" + +namespace embree +{ +#if defined(__SSE4_1__) + __forceinline __m128 blendv_ps(__m128 f, __m128 t, __m128 mask) { + return _mm_blendv_ps(f,t,mask); + } +#else + __forceinline __m128 blendv_ps(__m128 f, __m128 t, __m128 mask) { + return _mm_or_ps(_mm_and_ps(mask, t), _mm_andnot_ps(mask, f)); + } +#endif + + extern const __m128 mm_lookupmask_ps[16]; + extern const __m128d mm_lookupmask_pd[4]; +} + +#if defined(__AVX512VL__) +#include "vboolf4_avx512.h" +#else +#include "vboolf4_sse2.h" +#endif +#include "vint4_sse2.h" +#include "vuint4_sse2.h" +#include "vfloat4_sse2.h" diff --git a/thirdparty/embree/common/simd/varying.h b/thirdparty/embree/common/simd/varying.h new file mode 100644 index 000000000000..9a46817da913 --- /dev/null +++ b/thirdparty/embree/common/simd/varying.h @@ -0,0 +1,132 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" + +namespace embree +{ + /* Varying numeric types */ + template + struct vfloat + { + union { float f[N]; int i[N]; }; + __forceinline const float& operator [](size_t index) const { assert(index < N); return f[index]; } + __forceinline float& operator [](size_t index) { assert(index < N); return f[index]; } + }; + + template + struct vdouble + { + union { double f[N]; long long i[N]; }; + __forceinline const double& operator [](size_t index) const { assert(index < N); return f[index]; } + __forceinline double& operator [](size_t index) { assert(index < N); return f[index]; } + }; + + template + struct vint + { + int i[N]; + __forceinline const int& operator [](size_t index) const { assert(index < N); return i[index]; } + __forceinline int& operator [](size_t index) { assert(index < N); return i[index]; } + }; + + template + struct vuint + { + unsigned int i[N]; + __forceinline const unsigned int& operator [](size_t index) const { assert(index < N); return i[index]; } + __forceinline unsigned int& operator [](size_t index) { assert(index < N); return i[index]; } + }; + + template + struct vllong + { + long long i[N]; + __forceinline const long long& operator [](size_t index) const { assert(index < N); return i[index]; } + __forceinline long long& operator [](size_t index) { assert(index < N); return i[index]; } + }; + + /* Varying bool types */ + template struct vboolf { int i[N]; }; // for float/int + template struct vboold { long long i[N]; }; // for double/long long + + /* Aliases to default types */ + template using vreal = vfloat; + template using vbool = vboolf; + + /* Varying size constants */ +#if defined(__AVX512VL__) // SKX + const int VSIZEX = 8; // default size + const int VSIZEL = 16; // large size +#elif defined(__AVX512F__) // KNL + const int VSIZEX = 16; + const int VSIZEL = 16; +#elif defined(__AVX__) + const int VSIZEX = 8; + const int VSIZEL = 8; +#else + const int VSIZEX = 4; + const int VSIZEL = 4; +#endif + + /* Extends varying size N to optimal or up to max(N, N2) */ + template + struct vextend + { +#if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL + /* use 16-wide SIMD calculations on KNL even for 4 and 8 wide SIMD */ + static const int size = (N2 == VSIZEX) ? VSIZEX : N; + #define SIMD_MODE(N) N, 16 +#else + /* calculate with same SIMD width otherwise */ + static const int size = N; + #define SIMD_MODE(N) N, N +#endif + }; + + /* 4-wide shortcuts */ + typedef vfloat<4> vfloat4; + typedef vdouble<4> vdouble4; + typedef vreal<4> vreal4; + typedef vint<4> vint4; + typedef vuint<4> vuint4; + typedef vllong<4> vllong4; + typedef vbool<4> vbool4; + typedef vboolf<4> vboolf4; + typedef vboold<4> vboold4; + + /* 8-wide shortcuts */ + typedef vfloat<8> vfloat8; + typedef vdouble<8> vdouble8; + typedef vreal<8> vreal8; + typedef vint<8> vint8; + typedef vuint<8> vuint8; + typedef vllong<8> vllong8; + typedef vbool<8> vbool8; + typedef vboolf<8> vboolf8; + typedef vboold<8> vboold8; + + /* 16-wide shortcuts */ + typedef vfloat<16> vfloat16; + typedef vdouble<16> vdouble16; + typedef vreal<16> vreal16; + typedef vint<16> vint16; + typedef vuint<16> vuint16; + typedef vllong<16> vllong16; + typedef vbool<16> vbool16; + typedef vboolf<16> vboolf16; + typedef vboold<16> vboold16; + + /* Default shortcuts */ + typedef vfloat vfloatx; + typedef vdouble vdoublex; + typedef vreal vrealx; + typedef vint vintx; + typedef vuint vuintx; + typedef vllong vllongx; + typedef vbool vboolx; + typedef vboolf vboolfx; + typedef vboold vbooldx; +} diff --git a/thirdparty/embree/common/simd/vboold4_avx.h b/thirdparty/embree/common/simd/vboold4_avx.h new file mode 100644 index 000000000000..44e423b001be --- /dev/null +++ b/thirdparty/embree/common/simd/vboold4_avx.h @@ -0,0 +1,155 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide AVX bool type for 64bit data types*/ + template<> + struct vboold<4> + { + ALIGNED_STRUCT_(32); + + typedef vboold4 Bool; + + enum { size = 4 }; // number of SIMD elements + union { // data + __m256d v; + struct { __m128d vl,vh; }; + long long i[4]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold() {} + __forceinline vboold(const vboold4& a) { v = a.v; } + __forceinline vboold4& operator =(const vboold4& a) { v = a.v; return *this; } + + __forceinline vboold(__m256d a) : v(a) {} + __forceinline vboold(__m256i a) : v(_mm256_castsi256_pd(a)) {} + + __forceinline operator const __m256() const { return _mm256_castpd_ps(v); } + __forceinline operator const __m256i() const { return _mm256_castpd_si256(v); } + __forceinline operator const __m256d() const { return v; } + + __forceinline vboold(int a) + { + assert(a >= 0 && a <= 255); +#if defined (__AVX2__) + const __m256i mask = _mm256_set_epi64x(0x8, 0x4, 0x2, 0x1); + const __m256i b = _mm256_set1_epi64x(a); + const __m256i c = _mm256_and_si256(b,mask); + v = _mm256_castsi256_pd(_mm256_cmpeq_epi64(c,mask)); +#else + vl = mm_lookupmask_pd[a & 0x3]; + vh = mm_lookupmask_pd[a >> 2]; +#endif + } + + __forceinline vboold(__m128d a, __m128d b) : vl(a), vh(b) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold(FalseTy) : v(_mm256_setzero_pd()) {} + __forceinline vboold(TrueTy) : v(_mm256_cmp_pd(_mm256_setzero_pd(), _mm256_setzero_pd(), _CMP_EQ_OQ)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { assert(index < 4); return (_mm256_movemask_pd(v) >> index) & 1; } + __forceinline long long& operator [](size_t index) { assert(index < 4); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 operator !(const vboold4& a) { return _mm256_xor_pd(a, vboold4(embree::True)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 operator &(const vboold4& a, const vboold4& b) { return _mm256_and_pd(a, b); } + __forceinline vboold4 operator |(const vboold4& a, const vboold4& b) { return _mm256_or_pd (a, b); } + __forceinline vboold4 operator ^(const vboold4& a, const vboold4& b) { return _mm256_xor_pd(a, b); } + + __forceinline vboold4 andn(const vboold4& a, const vboold4& b) { return _mm256_andnot_pd(b, a); } + + __forceinline vboold4& operator &=(vboold4& a, const vboold4& b) { return a = a & b; } + __forceinline vboold4& operator |=(vboold4& a, const vboold4& b) { return a = a | b; } + __forceinline vboold4& operator ^=(vboold4& a, const vboold4& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 operator !=(const vboold4& a, const vboold4& b) { return _mm256_xor_pd(a, b); } + __forceinline vboold4 operator ==(const vboold4& a, const vboold4& b) { return _mm256_xor_pd(_mm256_xor_pd(a,b),vboold4(embree::True)); } + + __forceinline vboold4 select(const vboold4& mask, const vboold4& t, const vboold4& f) { + return _mm256_blendv_pd(f, t, mask); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 unpacklo(const vboold4& a, const vboold4& b) { return _mm256_unpacklo_pd(a, b); } + __forceinline vboold4 unpackhi(const vboold4& a, const vboold4& b) { return _mm256_unpackhi_pd(a, b); } + + +#if defined(__AVX2__) + template + __forceinline vboold4 shuffle(const vboold4& v) { + return _mm256_permute4x64_pd(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vboold4 shuffle(const vboold4& v) { + return _mm256_permute4x64_pd(v, _MM_SHUFFLE(i, i, i, i)); + } +#endif + + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool reduce_and(const vboold4& a) { return _mm256_movemask_pd(a) == (unsigned int)0xf; } + __forceinline bool reduce_or (const vboold4& a) { return !_mm256_testz_pd(a,a); } + + __forceinline bool all (const vboold4& a) { return _mm256_movemask_pd(a) == (unsigned int)0xf; } + __forceinline bool any (const vboold4& a) { return !_mm256_testz_pd(a,a); } + __forceinline bool none(const vboold4& a) { return _mm256_testz_pd(a,a) != 0; } + + __forceinline bool all (const vboold4& valid, const vboold4& b) { return all((!valid) | b); } + __forceinline bool any (const vboold4& valid, const vboold4& b) { return any(valid & b); } + __forceinline bool none(const vboold4& valid, const vboold4& b) { return none(valid & b); } + + __forceinline unsigned int movemask(const vboold4& a) { return _mm256_movemask_pd(a); } + __forceinline size_t popcnt (const vboold4& a) { return popcnt((size_t)_mm256_movemask_pd(a)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboold4& a, size_t index) { return a[index]; } + __forceinline void set (vboold4& a, size_t index) { a[index] = -1; } + __forceinline void clear(vboold4& a, size_t index) { a[index] = 0; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboold4& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " + << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboold4_avx512.h b/thirdparty/embree/common/simd/vboold4_avx512.h new file mode 100644 index 000000000000..4fe730d713d6 --- /dev/null +++ b/thirdparty/embree/common/simd/vboold4_avx512.h @@ -0,0 +1,140 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide AVX-512 bool type */ + template<> + struct vboold<4> + { + typedef vboold4 Bool; + typedef vint4 Int; + + enum { size = 4 }; // number of SIMD elements + __mmask8 v; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold() {} + __forceinline vboold(const vboold4& t) { v = t.v; } + __forceinline vboold4& operator =(const vboold4& f) { v = f.v; return *this; } + + __forceinline vboold(const __mmask8 &t) { v = t; } + __forceinline operator __mmask8() const { return v; } + + __forceinline vboold(bool b) { v = b ? 0xf : 0x0; } + __forceinline vboold(int t) { v = (__mmask8)t; } + __forceinline vboold(unsigned int t) { v = (__mmask8)t; } + + /* return int8 mask */ + __forceinline __m128i mask8() const { + return _mm_movm_epi8(v); + } + + /* return int32 mask */ + __forceinline __m128i mask32() const { + return _mm_movm_epi32(v); + } + + /* return int64 mask */ + __forceinline __m256i mask64() const { + return _mm256_movm_epi64(v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold(FalseTy) : v(0x0) {} + __forceinline vboold(TrueTy) : v(0xf) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { + assert(index < 4); return (mm512_mask2int(v) >> index) & 1; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 operator !(const vboold4& a) { return _mm512_kandn(a, 0xf); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 operator &(const vboold4& a, const vboold4& b) { return _mm512_kand(a, b); } + __forceinline vboold4 operator |(const vboold4& a, const vboold4& b) { return _mm512_kor(a, b); } + __forceinline vboold4 operator ^(const vboold4& a, const vboold4& b) { return _mm512_kxor(a, b); } + + __forceinline vboold4 andn(const vboold4& a, const vboold4& b) { return _mm512_kandn(b, a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4& operator &=(vboold4& a, const vboold4& b) { return a = a & b; } + __forceinline vboold4& operator |=(vboold4& a, const vboold4& b) { return a = a | b; } + __forceinline vboold4& operator ^=(vboold4& a, const vboold4& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold4 operator !=(const vboold4& a, const vboold4& b) { return _mm512_kxor(a, b); } + __forceinline vboold4 operator ==(const vboold4& a, const vboold4& b) { return _mm512_kand(_mm512_kxnor(a, b), 0xf); } + + __forceinline vboold4 select(const vboold4& s, const vboold4& a, const vboold4& b) { + return _mm512_kor(_mm512_kand(s, a), _mm512_kandn(s, b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int all (const vboold4& a) { return a.v == 0xf; } + __forceinline int any (const vboold4& a) { return _mm512_kortestz(a, a) == 0; } + __forceinline int none(const vboold4& a) { return _mm512_kortestz(a, a) != 0; } + + __forceinline int all (const vboold4& valid, const vboold4& b) { return all((!valid) | b); } + __forceinline int any (const vboold4& valid, const vboold4& b) { return any(valid & b); } + __forceinline int none(const vboold4& valid, const vboold4& b) { return none(valid & b); } + + __forceinline size_t movemask(const vboold4& a) { return _mm512_kmov(a); } + __forceinline size_t popcnt (const vboold4& a) { return popcnt(a.v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Conversion Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline unsigned int toInt(const vboold4& a) { return mm512_mask2int(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboold4& a, size_t index) { assert(index < 4); return (toInt(a) >> index) & 1; } + __forceinline void set(vboold4& a, size_t index) { assert(index < 4); a |= 1 << index; } + __forceinline void clear(vboold4& a, size_t index) { assert(index < 4); a = andn(a, 1 << index); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboold4& a) + { + cout << "<"; + for (size_t i=0; i<4; i++) { + if ((a.v >> i) & 1) cout << "1"; else cout << "0"; + } + return cout << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboold8_avx512.h b/thirdparty/embree/common/simd/vboold8_avx512.h new file mode 100644 index 000000000000..fdf3f00de544 --- /dev/null +++ b/thirdparty/embree/common/simd/vboold8_avx512.h @@ -0,0 +1,148 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX-512 bool type */ + template<> + struct vboold<8> + { + typedef vboold8 Bool; + typedef vint8 Int; + + enum { size = 8 }; // number of SIMD elements + __mmask8 v; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold() {} + __forceinline vboold(const vboold8& t) { v = t.v; } + __forceinline vboold8& operator =(const vboold8& f) { v = f.v; return *this; } + + __forceinline vboold(const __mmask8& t) { v = t; } + __forceinline operator __mmask8() const { return v; } + + __forceinline vboold(bool b) { v = b ? 0xff : 0x00; } + __forceinline vboold(int t) { v = (__mmask8)t; } + __forceinline vboold(unsigned int t) { v = (__mmask8)t; } + + /* return int8 mask */ + __forceinline __m128i mask8() const { +#if defined(__AVX512BW__) + return _mm_movm_epi8(v); +#else + const __m512i f = _mm512_set1_epi64(0); + const __m512i t = _mm512_set1_epi64(-1); + const __m512i m = _mm512_mask_or_epi64(f,v,t,t); + return _mm512_cvtepi64_epi8(m); +#endif + } + + /* return int64 mask */ + __forceinline __m512i mask64() const { +#if defined(__AVX512DQ__) + return _mm512_movm_epi64(v); +#else + const __m512i f = _mm512_set1_epi64(0); + const __m512i t = _mm512_set1_epi64(-1); + return _mm512_mask_or_epi64(f,v,t,t); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold(FalseTy) : v(0x00) {} + __forceinline vboold(TrueTy) : v(0xff) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { + assert(index < 8); return (mm512_mask2int(v) >> index) & 1; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8 operator !(const vboold8& a) { return _mm512_knot(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8 operator &(const vboold8& a, const vboold8& b) { return _mm512_kand(a, b); } + __forceinline vboold8 operator |(const vboold8& a, const vboold8& b) { return _mm512_kor(a, b); } + __forceinline vboold8 operator ^(const vboold8& a, const vboold8& b) { return _mm512_kxor(a, b); } + + __forceinline vboold8 andn(const vboold8& a, const vboold8& b) { return _mm512_kandn(b, a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8& operator &=(vboold8& a, const vboold8& b) { return a = a & b; } + __forceinline vboold8& operator |=(vboold8& a, const vboold8& b) { return a = a | b; } + __forceinline vboold8& operator ^=(vboold8& a, const vboold8& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8 operator !=(const vboold8& a, const vboold8& b) { return _mm512_kxor(a, b); } + __forceinline vboold8 operator ==(const vboold8& a, const vboold8& b) { return _mm512_kxnor(a, b); } + + __forceinline vboold8 select(const vboold8& s, const vboold8& a, const vboold8& b) { + return _mm512_kor(_mm512_kand(s, a), _mm512_kandn(s, b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int all (const vboold8& a) { return a.v == 0xff; } + __forceinline int any (const vboold8& a) { return _mm512_kortestz(a, a) == 0; } + __forceinline int none(const vboold8& a) { return _mm512_kortestz(a, a) != 0; } + + __forceinline int all (const vboold8& valid, const vboold8& b) { return all((!valid) | b); } + __forceinline int any (const vboold8& valid, const vboold8& b) { return any(valid & b); } + __forceinline int none(const vboold8& valid, const vboold8& b) { return none(valid & b); } + + __forceinline size_t movemask(const vboold8& a) { return _mm512_kmov(a); } + __forceinline size_t popcnt (const vboold8& a) { return popcnt(a.v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Conversion Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline unsigned int toInt(const vboold8& a) { return mm512_mask2int(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboold8& a, size_t index) { assert(index < 8); return (toInt(a) >> index) & 1; } + __forceinline void set(vboold8& a, size_t index) { assert(index < 8); a |= 1 << index; } + __forceinline void clear(vboold8& a, size_t index) { assert(index < 8); a = andn(a, 1 << index); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboold8& a) + { + cout << "<"; + for (size_t i=0; i<8; i++) { + if ((a.v >> i) & 1) cout << "1"; else cout << "0"; + } + return cout << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboolf16_avx512.h b/thirdparty/embree/common/simd/vboolf16_avx512.h new file mode 100644 index 000000000000..238cdc8eb920 --- /dev/null +++ b/thirdparty/embree/common/simd/vboolf16_avx512.h @@ -0,0 +1,150 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 16-wide AVX-512 bool type */ + template<> + struct vboolf<16> + { + typedef vboolf16 Bool; + typedef vint16 Int; + typedef vfloat16 Float; + + enum { size = 16 }; // number of SIMD elements + __mmask16 v; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf() {} + __forceinline vboolf(const vboolf16& t) { v = t.v; } + __forceinline vboolf16& operator =(const vboolf16& f) { v = f.v; return *this; } + + __forceinline vboolf(const __mmask16& t) { v = t; } + __forceinline operator __mmask16() const { return v; } + + __forceinline vboolf(bool b) { v = b ? 0xFFFF : 0x0000; } + __forceinline vboolf(int t) { v = (__mmask16)t; } + __forceinline vboolf(unsigned int t) { v = (__mmask16)t; } + + /* return int8 mask */ + __forceinline __m128i mask8() const { +#if defined(__AVX512BW__) + return _mm_movm_epi8(v); +#else + const __m512i f = _mm512_set1_epi32(0); + const __m512i t = _mm512_set1_epi32(-1); + const __m512i m = _mm512_mask_or_epi32(f,v,t,t); + return _mm512_cvtepi32_epi8(m); +#endif + } + + /* return int32 mask */ + __forceinline __m512i mask32() const { +#if defined(__AVX512DQ__) + return _mm512_movm_epi32(v); +#else + const __m512i f = _mm512_set1_epi32(0); + const __m512i t = _mm512_set1_epi32(-1); + return _mm512_mask_or_epi32(f,v,t,t); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf(FalseTy) : v(0x0000) {} + __forceinline vboolf(TrueTy) : v(0xffff) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { + assert(index < 16); return (mm512_mask2int(v) >> index) & 1; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 operator !(const vboolf16& a) { return _mm512_knot(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 operator &(const vboolf16& a, const vboolf16& b) { return _mm512_kand(a,b); } + __forceinline vboolf16 operator |(const vboolf16& a, const vboolf16& b) { return _mm512_kor(a,b); } + __forceinline vboolf16 operator ^(const vboolf16& a, const vboolf16& b) { return _mm512_kxor(a,b); } + + __forceinline vboolf16 andn(const vboolf16& a, const vboolf16& b) { return _mm512_kandn(b,a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16& operator &=(vboolf16& a, const vboolf16& b) { return a = a & b; } + __forceinline vboolf16& operator |=(vboolf16& a, const vboolf16& b) { return a = a | b; } + __forceinline vboolf16& operator ^=(vboolf16& a, const vboolf16& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 operator !=(const vboolf16& a, const vboolf16& b) { return _mm512_kxor(a, b); } + __forceinline vboolf16 operator ==(const vboolf16& a, const vboolf16& b) { return _mm512_kxnor(a, b); } + + __forceinline vboolf16 select(const vboolf16& s, const vboolf16& a, const vboolf16& b) { + return _mm512_kor(_mm512_kand(s,a),_mm512_kandn(s,b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int all (const vboolf16& a) { return _mm512_kortestc(a,a) != 0; } + __forceinline int any (const vboolf16& a) { return _mm512_kortestz(a,a) == 0; } + __forceinline int none(const vboolf16& a) { return _mm512_kortestz(a,a) != 0; } + + __forceinline int all (const vboolf16& valid, const vboolf16& b) { return all((!valid) | b); } + __forceinline int any (const vboolf16& valid, const vboolf16& b) { return any(valid & b); } + __forceinline int none(const vboolf16& valid, const vboolf16& b) { return none(valid & b); } + + __forceinline size_t movemask(const vboolf16& a) { return _mm512_kmov(a); } + __forceinline size_t popcnt (const vboolf16& a) { return popcnt(a.v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Convertion Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline unsigned int toInt (const vboolf16& a) { return mm512_mask2int(a); } + __forceinline vboolf16 toMask(const int& a) { return mm512_int2mask(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboolf16& a, size_t index) { assert(index < 16); return (toInt(a) >> index) & 1; } + __forceinline void set(vboolf16& a, size_t index) { assert(index < 16); a |= 1 << index; } + __forceinline void clear(vboolf16& a, size_t index) { assert(index < 16); a = andn(a, 1 << index); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboolf16& a) + { + cout << "<"; + for (size_t i=0; i<16; i++) { + if ((a.v >> i) & 1) cout << "1"; else cout << "0"; + } + return cout << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboolf4_avx512.h b/thirdparty/embree/common/simd/vboolf4_avx512.h new file mode 100644 index 000000000000..2ae4c4470e2d --- /dev/null +++ b/thirdparty/embree/common/simd/vboolf4_avx512.h @@ -0,0 +1,143 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide AVX-512 bool type */ + template<> + struct vboolf<4> + { + typedef vboolf4 Bool; + typedef vint4 Int; + + enum { size = 4 }; // number of SIMD elements + __mmask8 v; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf() {} + __forceinline vboolf(const vboolf4& t) { v = t.v; } + __forceinline vboolf4& operator =(const vboolf4& f) { v = f.v; return *this; } + + __forceinline vboolf(const __mmask8 &t) { v = t; } + __forceinline operator __mmask8() const { return v; } + + __forceinline vboolf(bool b) { v = b ? 0xf : 0x0; } + __forceinline vboolf(int t) { v = (__mmask8)t; } + __forceinline vboolf(unsigned int t) { v = (__mmask8)t; } + + __forceinline vboolf(bool a, bool b, bool c, bool d) + : v((__mmask8)((int(d) << 3) | (int(c) << 2) | (int(b) << 1) | int(a))) {} + + /* return int8 mask */ + __forceinline __m128i mask8() const { + return _mm_movm_epi8(v); + } + + /* return int32 mask */ + __forceinline __m128i mask32() const { + return _mm_movm_epi32(v); + } + + /* return int64 mask */ + __forceinline __m256i mask64() const { + return _mm256_movm_epi64(v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf(FalseTy) : v(0x0) {} + __forceinline vboolf(TrueTy) : v(0xf) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { + assert(index < 4); return (mm512_mask2int(v) >> index) & 1; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 operator !(const vboolf4& a) { return _mm512_kandn(a, 0xf); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 operator &(const vboolf4& a, const vboolf4& b) { return _mm512_kand(a, b); } + __forceinline vboolf4 operator |(const vboolf4& a, const vboolf4& b) { return _mm512_kor(a, b); } + __forceinline vboolf4 operator ^(const vboolf4& a, const vboolf4& b) { return _mm512_kxor(a, b); } + + __forceinline vboolf4 andn(const vboolf4& a, const vboolf4& b) { return _mm512_kandn(b, a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4& operator &=(vboolf4& a, const vboolf4& b) { return a = a & b; } + __forceinline vboolf4& operator |=(vboolf4& a, const vboolf4& b) { return a = a | b; } + __forceinline vboolf4& operator ^=(vboolf4& a, const vboolf4& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 operator !=(const vboolf4& a, const vboolf4& b) { return _mm512_kxor(a, b); } + __forceinline vboolf4 operator ==(const vboolf4& a, const vboolf4& b) { return _mm512_kand(_mm512_kxnor(a, b), 0xf); } + + __forceinline vboolf4 select(const vboolf4& s, const vboolf4& a, const vboolf4& b) { + return _mm512_kor(_mm512_kand(s, a), _mm512_kandn(s, b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int all (const vboolf4& a) { return a.v == 0xf; } + __forceinline int any (const vboolf4& a) { return _mm512_kortestz(a, a) == 0; } + __forceinline int none(const vboolf4& a) { return _mm512_kortestz(a, a) != 0; } + + __forceinline int all (const vboolf4& valid, const vboolf4& b) { return all((!valid) | b); } + __forceinline int any (const vboolf4& valid, const vboolf4& b) { return any(valid & b); } + __forceinline int none(const vboolf4& valid, const vboolf4& b) { return none(valid & b); } + + __forceinline size_t movemask(const vboolf4& a) { return _mm512_kmov(a); } + __forceinline size_t popcnt (const vboolf4& a) { return popcnt(a.v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Conversion Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline unsigned int toInt(const vboolf4& a) { return mm512_mask2int(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboolf4& a, size_t index) { assert(index < 4); return (toInt(a) >> index) & 1; } + __forceinline void set(vboolf4& a, size_t index) { assert(index < 4); a |= 1 << index; } + __forceinline void clear(vboolf4& a, size_t index) { assert(index < 4); a = andn(a, 1 << index); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboolf4& a) + { + cout << "<"; + for (size_t i=0; i<4; i++) { + if ((a.v >> i) & 1) cout << "1"; else cout << "0"; + } + return cout << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboolf4_sse2.h b/thirdparty/embree/common/simd/vboolf4_sse2.h new file mode 100644 index 000000000000..afec10fd499e --- /dev/null +++ b/thirdparty/embree/common/simd/vboolf4_sse2.h @@ -0,0 +1,173 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide SSE bool type */ + template<> + struct vboolf<4> + { + ALIGNED_STRUCT_(16); + + typedef vboolf4 Bool; + typedef vint4 Int; + typedef vfloat4 Float; + + enum { size = 4 }; // number of SIMD elements + union { __m128 v; int i[4]; }; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf() {} + __forceinline vboolf(const vboolf4& other) { v = other.v; } + __forceinline vboolf4& operator =(const vboolf4& other) { v = other.v; return *this; } + + __forceinline vboolf(__m128 input) : v(input) {} + __forceinline operator const __m128&() const { return v; } + __forceinline operator const __m128i() const { return _mm_castps_si128(v); } + __forceinline operator const __m128d() const { return _mm_castps_pd(v); } + + __forceinline vboolf(bool a) + : v(mm_lookupmask_ps[(size_t(a) << 3) | (size_t(a) << 2) | (size_t(a) << 1) | size_t(a)]) {} + __forceinline vboolf(bool a, bool b) + : v(mm_lookupmask_ps[(size_t(b) << 3) | (size_t(a) << 2) | (size_t(b) << 1) | size_t(a)]) {} + __forceinline vboolf(bool a, bool b, bool c, bool d) + : v(mm_lookupmask_ps[(size_t(d) << 3) | (size_t(c) << 2) | (size_t(b) << 1) | size_t(a)]) {} + __forceinline vboolf(int mask) { assert(mask >= 0 && mask < 16); v = mm_lookupmask_ps[mask]; } + __forceinline vboolf(unsigned int mask) { assert(mask < 16); v = mm_lookupmask_ps[mask]; } + + /* return int32 mask */ + __forceinline __m128i mask32() const { + return _mm_castps_si128(v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf(FalseTy) : v(_mm_setzero_ps()) {} + __forceinline vboolf(TrueTy) : v(_mm_castsi128_ps(_mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128()))) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { assert(index < 4); return (_mm_movemask_ps(v) >> index) & 1; } + __forceinline int& operator [](size_t index) { assert(index < 4); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 operator !(const vboolf4& a) { return _mm_xor_ps(a, vboolf4(embree::True)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 operator &(const vboolf4& a, const vboolf4& b) { return _mm_and_ps(a, b); } + __forceinline vboolf4 operator |(const vboolf4& a, const vboolf4& b) { return _mm_or_ps (a, b); } + __forceinline vboolf4 operator ^(const vboolf4& a, const vboolf4& b) { return _mm_xor_ps(a, b); } + + __forceinline vboolf4 andn(const vboolf4& a, const vboolf4& b) { return _mm_andnot_ps(b, a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4& operator &=(vboolf4& a, const vboolf4& b) { return a = a & b; } + __forceinline vboolf4& operator |=(vboolf4& a, const vboolf4& b) { return a = a | b; } + __forceinline vboolf4& operator ^=(vboolf4& a, const vboolf4& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 operator !=(const vboolf4& a, const vboolf4& b) { return _mm_xor_ps(a, b); } + __forceinline vboolf4 operator ==(const vboolf4& a, const vboolf4& b) { return _mm_castsi128_ps(_mm_cmpeq_epi32(a, b)); } + + __forceinline vboolf4 select(const vboolf4& m, const vboolf4& t, const vboolf4& f) { +#if defined(__SSE4_1__) + return _mm_blendv_ps(f, t, m); +#else + return _mm_or_ps(_mm_and_ps(m, t), _mm_andnot_ps(m, f)); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf4 unpacklo(const vboolf4& a, const vboolf4& b) { return _mm_unpacklo_ps(a, b); } + __forceinline vboolf4 unpackhi(const vboolf4& a, const vboolf4& b) { return _mm_unpackhi_ps(a, b); } + + template + __forceinline vboolf4 shuffle(const vboolf4& v) { + return _mm_castsi128_ps(_mm_shuffle_epi32(v, _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vboolf4 shuffle(const vboolf4& a, const vboolf4& b) { + return _mm_shuffle_ps(a, b, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vboolf4 shuffle(const vboolf4& v) { + return shuffle(v); + } + +#if defined(__SSE3__) + template<> __forceinline vboolf4 shuffle<0, 0, 2, 2>(const vboolf4& v) { return _mm_moveldup_ps(v); } + template<> __forceinline vboolf4 shuffle<1, 1, 3, 3>(const vboolf4& v) { return _mm_movehdup_ps(v); } + template<> __forceinline vboolf4 shuffle<0, 1, 0, 1>(const vboolf4& v) { return _mm_castpd_ps(_mm_movedup_pd(v)); } +#endif + +#if defined(__SSE4_1__) + template __forceinline vboolf4 insert(const vboolf4& a, const vboolf4& b) { return _mm_insert_ps(a, b, (dst << 4) | (src << 6) | clr); } + template __forceinline vboolf4 insert(const vboolf4& a, const vboolf4& b) { return insert(a, b); } + template __forceinline vboolf4 insert(const vboolf4& a, const bool b) { return insert(a, vboolf4(b)); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool reduce_and(const vboolf4& a) { return _mm_movemask_ps(a) == 0xf; } + __forceinline bool reduce_or (const vboolf4& a) { return _mm_movemask_ps(a) != 0x0; } + + __forceinline bool all (const vboolf4& b) { return _mm_movemask_ps(b) == 0xf; } + __forceinline bool any (const vboolf4& b) { return _mm_movemask_ps(b) != 0x0; } + __forceinline bool none(const vboolf4& b) { return _mm_movemask_ps(b) == 0x0; } + + __forceinline bool all (const vboolf4& valid, const vboolf4& b) { return all((!valid) | b); } + __forceinline bool any (const vboolf4& valid, const vboolf4& b) { return any(valid & b); } + __forceinline bool none(const vboolf4& valid, const vboolf4& b) { return none(valid & b); } + + __forceinline size_t movemask(const vboolf4& a) { return _mm_movemask_ps(a); } +#if defined(__SSE4_2__) + __forceinline size_t popcnt(const vboolf4& a) { return popcnt((size_t)_mm_movemask_ps(a)); } +#else + __forceinline size_t popcnt(const vboolf4& a) { return bool(a[0])+bool(a[1])+bool(a[2])+bool(a[3]); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboolf4& a, size_t index) { return a[index]; } + __forceinline void set(vboolf4& a, size_t index) { a[index] = -1; } + __forceinline void clear(vboolf4& a, size_t index) { a[index] = 0; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboolf4& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboolf8_avx.h b/thirdparty/embree/common/simd/vboolf8_avx.h new file mode 100644 index 000000000000..5d7c0d68c1b0 --- /dev/null +++ b/thirdparty/embree/common/simd/vboolf8_avx.h @@ -0,0 +1,186 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX bool type */ + template<> + struct vboolf<8> + { + ALIGNED_STRUCT_(32); + + typedef vboolf8 Bool; + typedef vint8 Int; + typedef vfloat8 Float; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m256 v; + struct { __m128 vl,vh; }; + int i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf() {} + __forceinline vboolf(const vboolf8& a) { v = a.v; } + __forceinline vboolf8& operator =(const vboolf8& a) { v = a.v; return *this; } + + __forceinline vboolf(__m256 a) : v(a) {} + __forceinline operator const __m256&() const { return v; } + __forceinline operator const __m256i() const { return _mm256_castps_si256(v); } + __forceinline operator const __m256d() const { return _mm256_castps_pd(v); } + + __forceinline vboolf(int a) + { + assert(a >= 0 && a <= 255); +#if defined (__AVX2__) + const __m256i mask = _mm256_set_epi32(0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1); + const __m256i b = _mm256_set1_epi32(a); + const __m256i c = _mm256_and_si256(b,mask); + v = _mm256_castsi256_ps(_mm256_cmpeq_epi32(c,mask)); +#else + vl = mm_lookupmask_ps[a & 0xF]; + vh = mm_lookupmask_ps[a >> 4]; +#endif + } + + __forceinline vboolf(const vboolf4& a) : v(_mm256_insertf128_ps(_mm256_castps128_ps256(a),a,1)) {} + __forceinline vboolf(const vboolf4& a, const vboolf4& b) : v(_mm256_insertf128_ps(_mm256_castps128_ps256(a),b,1)) {} + __forceinline vboolf(__m128 a, __m128 b) : vl(a), vh(b) {} + + __forceinline vboolf(bool a) : v(vboolf8(vboolf4(a), vboolf4(a))) {} + __forceinline vboolf(bool a, bool b) : v(vboolf8(vboolf4(a), vboolf4(b))) {} + __forceinline vboolf(bool a, bool b, bool c, bool d) : v(vboolf8(vboolf4(a,b), vboolf4(c,d))) {} + __forceinline vboolf(bool a, bool b, bool c, bool d, bool e, bool f, bool g, bool h) : v(vboolf8(vboolf4(a,b,c,d), vboolf4(e,f,g,h))) {} + + /* return int32 mask */ + __forceinline __m256i mask32() const { + return _mm256_castps_si256(v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf(FalseTy) : v(_mm256_setzero_ps()) {} + __forceinline vboolf(TrueTy) : v(_mm256_cmp_ps(_mm256_setzero_ps(), _mm256_setzero_ps(), _CMP_EQ_OQ)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { assert(index < 8); return (_mm256_movemask_ps(v) >> index) & 1; } + __forceinline int& operator [](size_t index) { assert(index < 8); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator !(const vboolf8& a) { return _mm256_xor_ps(a, vboolf8(embree::True)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator &(const vboolf8& a, const vboolf8& b) { return _mm256_and_ps(a, b); } + __forceinline vboolf8 operator |(const vboolf8& a, const vboolf8& b) { return _mm256_or_ps (a, b); } + __forceinline vboolf8 operator ^(const vboolf8& a, const vboolf8& b) { return _mm256_xor_ps(a, b); } + + __forceinline vboolf8 andn(const vboolf8& a, const vboolf8& b) { return _mm256_andnot_ps(b, a); } + + __forceinline vboolf8& operator &=(vboolf8& a, const vboolf8& b) { return a = a & b; } + __forceinline vboolf8& operator |=(vboolf8& a, const vboolf8& b) { return a = a | b; } + __forceinline vboolf8& operator ^=(vboolf8& a, const vboolf8& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator !=(const vboolf8& a, const vboolf8& b) { return _mm256_xor_ps(a, b); } + __forceinline vboolf8 operator ==(const vboolf8& a, const vboolf8& b) { return _mm256_xor_ps(_mm256_xor_ps(a,b),vboolf8(embree::True)); } + + __forceinline vboolf8 select(const vboolf8& mask, const vboolf8& t, const vboolf8& f) { + return _mm256_blendv_ps(f, t, mask); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 unpacklo(const vboolf8& a, const vboolf8& b) { return _mm256_unpacklo_ps(a, b); } + __forceinline vboolf8 unpackhi(const vboolf8& a, const vboolf8& b) { return _mm256_unpackhi_ps(a, b); } + + template + __forceinline vboolf8 shuffle(const vboolf8& v) { + return _mm256_permute_ps(v, _MM_SHUFFLE(i, i, i, i)); + } + + template + __forceinline vboolf8 shuffle4(const vboolf8& v) { + return _mm256_permute2f128_ps(v, v, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vboolf8 shuffle4(const vboolf8& a, const vboolf8& b) { + return _mm256_permute2f128_ps(a, b, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vboolf8 shuffle(const vboolf8& v) { + return _mm256_permute_ps(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vboolf8 shuffle(const vboolf8& a, const vboolf8& b) { + return _mm256_shuffle_ps(a, b, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template<> __forceinline vboolf8 shuffle<0, 0, 2, 2>(const vboolf8& v) { return _mm256_moveldup_ps(v); } + template<> __forceinline vboolf8 shuffle<1, 1, 3, 3>(const vboolf8& v) { return _mm256_movehdup_ps(v); } + template<> __forceinline vboolf8 shuffle<0, 1, 0, 1>(const vboolf8& v) { return _mm256_castpd_ps(_mm256_movedup_pd(_mm256_castps_pd(v))); } + + template __forceinline vboolf8 insert4(const vboolf8& a, const vboolf4& b) { return _mm256_insertf128_ps(a, b, i); } + template __forceinline vboolf4 extract4 (const vboolf8& a) { return _mm256_extractf128_ps(a, i); } + template<> __forceinline vboolf4 extract4<0>(const vboolf8& a) { return _mm256_castps256_ps128(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool reduce_and(const vboolf8& a) { return _mm256_movemask_ps(a) == (unsigned int)0xff; } + __forceinline bool reduce_or (const vboolf8& a) { return !_mm256_testz_ps(a,a); } + + __forceinline bool all (const vboolf8& a) { return _mm256_movemask_ps(a) == (unsigned int)0xff; } + __forceinline bool any (const vboolf8& a) { return !_mm256_testz_ps(a,a); } + __forceinline bool none(const vboolf8& a) { return _mm256_testz_ps(a,a) != 0; } + + __forceinline bool all (const vboolf8& valid, const vboolf8& b) { return all((!valid) | b); } + __forceinline bool any (const vboolf8& valid, const vboolf8& b) { return any(valid & b); } + __forceinline bool none(const vboolf8& valid, const vboolf8& b) { return none(valid & b); } + + __forceinline unsigned int movemask(const vboolf8& a) { return _mm256_movemask_ps(a); } + __forceinline size_t popcnt (const vboolf8& a) { return popcnt((size_t)_mm256_movemask_ps(a)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboolf8& a, size_t index) { return a[index]; } + __forceinline void set(vboolf8& a, size_t index) { a[index] = -1; } + __forceinline void clear(vboolf8& a, size_t index) { a[index] = 0; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboolf8& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " + << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vboolf8_avx512.h b/thirdparty/embree/common/simd/vboolf8_avx512.h new file mode 100644 index 000000000000..2a52b554c79c --- /dev/null +++ b/thirdparty/embree/common/simd/vboolf8_avx512.h @@ -0,0 +1,143 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX-512 bool type */ + template<> + struct vboolf<8> + { + typedef vboolf8 Bool; + typedef vint8 Int; + + enum { size = 8 }; // number of SIMD elements + __mmask8 v; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf() {} + __forceinline vboolf(const vboolf8& t) { v = t.v; } + __forceinline vboolf8& operator =(const vboolf8& f) { v = f.v; return *this; } + + __forceinline vboolf(const __mmask8 &t) { v = t; } + __forceinline operator __mmask8() const { return v; } + + __forceinline vboolf(bool b) { v = b ? 0xff : 0x00; } + __forceinline vboolf(int t) { v = (__mmask8)t; } + __forceinline vboolf(unsigned int t) { v = (__mmask8)t; } + + __forceinline vboolf(bool a, bool b, bool c, bool d, bool e, bool f, bool g, bool h) + : v((__mmask8)((int(h) << 7) | (int(g) << 6) | (int(f) << 5) | (int(e) << 4) | (int(d) << 3) | (int(c) << 2) | (int(b) << 1) | int(a))) {} + + /* return int8 mask */ + __forceinline __m128i mask8() const { + return _mm_movm_epi8(v); + } + + /* return int32 mask */ + __forceinline __m256i mask32() const { + return _mm256_movm_epi32(v); + } + + /* return int64 mask */ + __forceinline __m512i mask64() const { + return _mm512_movm_epi64(v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf(FalseTy) : v(0x00) {} + __forceinline vboolf(TrueTy) : v(0xff) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool operator [](size_t index) const { + assert(index < 8); return (mm512_mask2int(v) >> index) & 1; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator !(const vboolf8& a) { return _mm512_knot(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator &(const vboolf8& a, const vboolf8& b) { return _mm512_kand(a, b); } + __forceinline vboolf8 operator |(const vboolf8& a, const vboolf8& b) { return _mm512_kor(a, b); } + __forceinline vboolf8 operator ^(const vboolf8& a, const vboolf8& b) { return _mm512_kxor(a, b); } + + __forceinline vboolf8 andn(const vboolf8& a, const vboolf8& b) { return _mm512_kandn(b, a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8& operator &=(vboolf8& a, const vboolf8& b) { return a = a & b; } + __forceinline vboolf8& operator |=(vboolf8& a, const vboolf8& b) { return a = a | b; } + __forceinline vboolf8& operator ^=(vboolf8& a, const vboolf8& b) { return a = a ^ b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator !=(const vboolf8& a, const vboolf8& b) { return _mm512_kxor(a, b); } + __forceinline vboolf8 operator ==(const vboolf8& a, const vboolf8& b) { return _mm512_kxnor(a, b); } + + __forceinline vboolf8 select(const vboolf8& s, const vboolf8& a, const vboolf8& b) { + return _mm512_kor(_mm512_kand(s, a), _mm512_kandn(s, b)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reduction Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int all (const vboolf8& a) { return a.v == 0xff; } + __forceinline int any (const vboolf8& a) { return _mm512_kortestz(a, a) == 0; } + __forceinline int none(const vboolf8& a) { return _mm512_kortestz(a, a) != 0; } + + __forceinline int all (const vboolf8& valid, const vboolf8& b) { return all((!valid) | b); } + __forceinline int any (const vboolf8& valid, const vboolf8& b) { return any(valid & b); } + __forceinline int none(const vboolf8& valid, const vboolf8& b) { return none(valid & b); } + + __forceinline size_t movemask(const vboolf8& a) { return _mm512_kmov(a); } + __forceinline size_t popcnt (const vboolf8& a) { return popcnt(a.v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Conversion Operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline unsigned int toInt(const vboolf8& a) { return mm512_mask2int(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Get/Set Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline bool get(const vboolf8& a, size_t index) { assert(index < 8); return (toInt(a) >> index) & 1; } + __forceinline void set(vboolf8& a, size_t index) { assert(index < 8); a |= 1 << index; } + __forceinline void clear(vboolf8& a, size_t index) { assert(index < 8); a = andn(a, 1 << index); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vboolf8& a) + { + cout << "<"; + for (size_t i=0; i<8; i++) { + if ((a.v >> i) & 1) cout << "1"; else cout << "0"; + } + return cout << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vdouble4_avx.h b/thirdparty/embree/common/simd/vdouble4_avx.h new file mode 100644 index 000000000000..eedb04aafbe6 --- /dev/null +++ b/thirdparty/embree/common/simd/vdouble4_avx.h @@ -0,0 +1,317 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide AVX 64-bit double type */ + template<> + struct vdouble<4> + { + ALIGNED_STRUCT_(32); + + typedef vboold4 Bool; + + enum { size = 4 }; // number of SIMD elements + union { // data + __m256d v; + double i[4]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble() {} + __forceinline vdouble(const vdouble4& t) { v = t.v; } + __forceinline vdouble4& operator =(const vdouble4& f) { v = f.v; return *this; } + + __forceinline vdouble(const __m256d& t) { v = t; } + __forceinline operator __m256d() const { return v; } + + __forceinline vdouble(double i) { + v = _mm256_set1_pd(i); + } + + __forceinline vdouble(double a, double b, double c, double d) { + v = _mm256_set_pd(d,c,b,a); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble(ZeroTy) : v(_mm256_setzero_pd()) {} + __forceinline vdouble(OneTy) : v(_mm256_set1_pd(1)) {} + __forceinline vdouble(StepTy) : v(_mm256_set_pd(3.0,2.0,1.0,0.0)) {} + __forceinline vdouble(ReverseStepTy) : v(_mm256_setr_pd(3.0,2.0,1.0,0.0)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline void store_nt(double *__restrict__ ptr, const vdouble4& a) { + _mm256_stream_pd(ptr, a); + } + + static __forceinline vdouble4 loadu(const double* addr) { + return _mm256_loadu_pd(addr); + } + + static __forceinline vdouble4 load(const vdouble4* addr) { + return _mm256_load_pd((double*)addr); + } + + static __forceinline vdouble4 load(const double* addr) { + return _mm256_load_pd(addr); + } + + static __forceinline void store(double* ptr, const vdouble4& v) { + _mm256_store_pd(ptr, v); + } + + static __forceinline void storeu(double* ptr, const vdouble4& v) { + _mm256_storeu_pd(ptr, v); + } + + static __forceinline vdouble4 broadcast(const void* a) { return _mm256_set1_pd(*(double*)a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline double& operator [](size_t index) { assert(index < 4); return i[index]; } + __forceinline const double& operator [](size_t index) const { assert(index < 4); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX2__) + __forceinline vdouble4 asDouble(const vllong4& a) { return _mm256_castsi256_pd(a); } + __forceinline vllong4 asLLong (const vdouble4& a) { return _mm256_castpd_si256(a); } +#endif + + __forceinline vdouble4 operator +(const vdouble4& a) { return a; } + __forceinline vdouble4 operator -(const vdouble4& a) { return _mm256_sub_pd(_mm256_setzero_pd(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble4 operator +(const vdouble4& a, const vdouble4& b) { return _mm256_add_pd(a, b); } + __forceinline vdouble4 operator +(const vdouble4& a, double b) { return a + vdouble4(b); } + __forceinline vdouble4 operator +(double a, const vdouble4& b) { return vdouble4(a) + b; } + + __forceinline vdouble4 operator -(const vdouble4& a, const vdouble4& b) { return _mm256_sub_pd(a, b); } + __forceinline vdouble4 operator -(const vdouble4& a, double b) { return a - vdouble4(b); } + __forceinline vdouble4 operator -(double a, const vdouble4& b) { return vdouble4(a) - b; } + + __forceinline vdouble4 operator *(const vdouble4& a, const vdouble4& b) { return _mm256_mul_pd(a, b); } + __forceinline vdouble4 operator *(const vdouble4& a, double b) { return a * vdouble4(b); } + __forceinline vdouble4 operator *(double a, const vdouble4& b) { return vdouble4(a) * b; } + + __forceinline vdouble4 operator &(const vdouble4& a, const vdouble4& b) { return _mm256_and_pd(a, b); } + __forceinline vdouble4 operator &(const vdouble4& a, double b) { return a & vdouble4(b); } + __forceinline vdouble4 operator &(double a, const vdouble4& b) { return vdouble4(a) & b; } + + __forceinline vdouble4 operator |(const vdouble4& a, const vdouble4& b) { return _mm256_or_pd(a, b); } + __forceinline vdouble4 operator |(const vdouble4& a, double b) { return a | vdouble4(b); } + __forceinline vdouble4 operator |(double a, const vdouble4& b) { return vdouble4(a) | b; } + + __forceinline vdouble4 operator ^(const vdouble4& a, const vdouble4& b) { return _mm256_xor_pd(a, b); } + __forceinline vdouble4 operator ^(const vdouble4& a, double b) { return a ^ vdouble4(b); } + __forceinline vdouble4 operator ^(double a, const vdouble4& b) { return vdouble4(a) ^ b; } + + __forceinline vdouble4 min(const vdouble4& a, const vdouble4& b) { return _mm256_min_pd(a, b); } + __forceinline vdouble4 min(const vdouble4& a, double b) { return min(a,vdouble4(b)); } + __forceinline vdouble4 min(double a, const vdouble4& b) { return min(vdouble4(a),b); } + + __forceinline vdouble4 max(const vdouble4& a, const vdouble4& b) { return _mm256_max_pd(a, b); } + __forceinline vdouble4 max(const vdouble4& a, double b) { return max(a,vdouble4(b)); } + __forceinline vdouble4 max(double a, const vdouble4& b) { return max(vdouble4(a),b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__FMA__) + __forceinline vdouble4 madd (const vdouble4& a, const vdouble4& b, const vdouble4& c) { return _mm256_fmadd_pd(a,b,c); } + __forceinline vdouble4 msub (const vdouble4& a, const vdouble4& b, const vdouble4& c) { return _mm256_fmsub_pd(a,b,c); } + __forceinline vdouble4 nmadd(const vdouble4& a, const vdouble4& b, const vdouble4& c) { return _mm256_fnmadd_pd(a,b,c); } + __forceinline vdouble4 nmsub(const vdouble4& a, const vdouble4& b, const vdouble4& c) { return _mm256_fnmsub_pd(a,b,c); } +#else + __forceinline vdouble4 madd (const vdouble4& a, const vdouble4& b, const vdouble4& c) { return a*b+c; } + __forceinline vdouble4 msub (const vdouble4& a, const vdouble4& b, const vdouble4& c) { return a*b-c; } + __forceinline vdouble4 nmadd(const vdouble4& a, const vdouble4& b, const vdouble4& c) { return -a*b+c;} + __forceinline vdouble4 nmsub(const vdouble4& a, const vdouble4& b, const vdouble4& c) { return -a*b-c; } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble4& operator +=(vdouble4& a, const vdouble4& b) { return a = a + b; } + __forceinline vdouble4& operator +=(vdouble4& a, double b) { return a = a + b; } + + __forceinline vdouble4& operator -=(vdouble4& a, const vdouble4& b) { return a = a - b; } + __forceinline vdouble4& operator -=(vdouble4& a, double b) { return a = a - b; } + + __forceinline vdouble4& operator *=(vdouble4& a, const vdouble4& b) { return a = a * b; } + __forceinline vdouble4& operator *=(vdouble4& a, double b) { return a = a * b; } + + __forceinline vdouble4& operator &=(vdouble4& a, const vdouble4& b) { return a = a & b; } + __forceinline vdouble4& operator &=(vdouble4& a, double b) { return a = a & b; } + + __forceinline vdouble4& operator |=(vdouble4& a, const vdouble4& b) { return a = a | b; } + __forceinline vdouble4& operator |=(vdouble4& a, double b) { return a = a | b; } + + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboold4 operator ==(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd_mask(a, b, _MM_CMPINT_EQ); } + __forceinline vboold4 operator !=(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd_mask(a, b, _MM_CMPINT_NE); } + __forceinline vboold4 operator < (const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd_mask(a, b, _MM_CMPINT_LT); } + __forceinline vboold4 operator >=(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd_mask(a, b, _MM_CMPINT_GE); } + __forceinline vboold4 operator > (const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd_mask(a, b, _MM_CMPINT_GT); } + __forceinline vboold4 operator <=(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd_mask(a, b, _MM_CMPINT_LE); } +#else + __forceinline vboold4 operator ==(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd(a, b, _CMP_EQ_OQ); } + __forceinline vboold4 operator !=(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd(a, b, _CMP_NEQ_UQ); } + __forceinline vboold4 operator < (const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd(a, b, _CMP_LT_OS); } + __forceinline vboold4 operator >=(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd(a, b, _CMP_NLT_US); } + __forceinline vboold4 operator > (const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd(a, b, _CMP_NLE_US); } + __forceinline vboold4 operator <=(const vdouble4& a, const vdouble4& b) { return _mm256_cmp_pd(a, b, _CMP_LE_OS); } +#endif + + __forceinline vboold4 operator ==(const vdouble4& a, double b) { return a == vdouble4(b); } + __forceinline vboold4 operator ==(double a, const vdouble4& b) { return vdouble4(a) == b; } + + __forceinline vboold4 operator !=(const vdouble4& a, double b) { return a != vdouble4(b); } + __forceinline vboold4 operator !=(double a, const vdouble4& b) { return vdouble4(a) != b; } + + __forceinline vboold4 operator < (const vdouble4& a, double b) { return a < vdouble4(b); } + __forceinline vboold4 operator < (double a, const vdouble4& b) { return vdouble4(a) < b; } + + __forceinline vboold4 operator >=(const vdouble4& a, double b) { return a >= vdouble4(b); } + __forceinline vboold4 operator >=(double a, const vdouble4& b) { return vdouble4(a) >= b; } + + __forceinline vboold4 operator > (const vdouble4& a, double b) { return a > vdouble4(b); } + __forceinline vboold4 operator > (double a, const vdouble4& b) { return vdouble4(a) > b; } + + __forceinline vboold4 operator <=(const vdouble4& a, double b) { return a <= vdouble4(b); } + __forceinline vboold4 operator <=(double a, const vdouble4& b) { return vdouble4(a) <= b; } + + __forceinline vboold4 eq(const vdouble4& a, const vdouble4& b) { return a == b; } + __forceinline vboold4 ne(const vdouble4& a, const vdouble4& b) { return a != b; } + __forceinline vboold4 lt(const vdouble4& a, const vdouble4& b) { return a < b; } + __forceinline vboold4 ge(const vdouble4& a, const vdouble4& b) { return a >= b; } + __forceinline vboold4 gt(const vdouble4& a, const vdouble4& b) { return a > b; } + __forceinline vboold4 le(const vdouble4& a, const vdouble4& b) { return a <= b; } + +#if defined(__AVX512VL__) + __forceinline vboold4 eq(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return _mm256_mask_cmp_pd_mask(mask, a, b, _MM_CMPINT_EQ); } + __forceinline vboold4 ne(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return _mm256_mask_cmp_pd_mask(mask, a, b, _MM_CMPINT_NE); } + __forceinline vboold4 lt(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return _mm256_mask_cmp_pd_mask(mask, a, b, _MM_CMPINT_LT); } + __forceinline vboold4 ge(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return _mm256_mask_cmp_pd_mask(mask, a, b, _MM_CMPINT_GE); } + __forceinline vboold4 gt(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return _mm256_mask_cmp_pd_mask(mask, a, b, _MM_CMPINT_GT); } + __forceinline vboold4 le(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return _mm256_mask_cmp_pd_mask(mask, a, b, _MM_CMPINT_LE); } +#else + __forceinline vboold4 eq(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return mask & (a == b); } + __forceinline vboold4 ne(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return mask & (a != b); } + __forceinline vboold4 lt(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return mask & (a < b); } + __forceinline vboold4 ge(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return mask & (a >= b); } + __forceinline vboold4 gt(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return mask & (a > b); } + __forceinline vboold4 le(const vboold4& mask, const vdouble4& a, const vdouble4& b) { return mask & (a <= b); } +#endif + + __forceinline vdouble4 select(const vboold4& m, const vdouble4& t, const vdouble4& f) { +#if defined(__AVX512VL__) + return _mm256_mask_blend_pd(m, f, t); +#else + return _mm256_blendv_pd(f, t, m); +#endif + } + + __forceinline void xchg(const vboold4& m, vdouble4& a, vdouble4& b) { + const vdouble4 c = a; a = select(m,b,a); b = select(m,c,b); + } + + __forceinline vboold4 test(const vdouble4& a, const vdouble4& b) { +#if defined(__AVX512VL__) + return _mm256_test_epi64_mask(_mm256_castpd_si256(a),_mm256_castpd_si256(b)); +#else + return _mm256_testz_si256(_mm256_castpd_si256(a),_mm256_castpd_si256(b)); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vdouble4 shuffle(const vdouble4& v) { + return _mm256_permute_pd(v, (i1 << 3) | (i0 << 2) | (i1 << 1) | i0); + } + + template + __forceinline vdouble4 shuffle(const vdouble4& v) { + return shuffle(v); + } + + template + __forceinline vdouble4 shuffle2(const vdouble4& v) { + return _mm256_permute2f128_pd(v, v, (i1 << 4) | i0); + } + + __forceinline double toScalar(const vdouble4& v) { + return _mm_cvtsd_f64(_mm256_castpd256_pd128(v)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble4 vreduce_min2(const vdouble4& x) { return min(x, shuffle<1,0>(x)); } + __forceinline vdouble4 vreduce_min (const vdouble4& y) { const vdouble4 x = vreduce_min2(y); return min(x, shuffle2<1,0>(x)); } + + __forceinline vdouble4 vreduce_max2(const vdouble4& x) { return max(x,shuffle<1,0>(x)); } + __forceinline vdouble4 vreduce_max (const vdouble4& y) { const vdouble4 x = vreduce_max2(y); return max(x, shuffle2<1,0>(x)); } + + __forceinline vdouble4 vreduce_and2(const vdouble4& x) { return x & shuffle<1,0>(x); } + __forceinline vdouble4 vreduce_and (const vdouble4& y) { const vdouble4 x = vreduce_and2(y); return x & shuffle2<1,0>(x); } + + __forceinline vdouble4 vreduce_or2(const vdouble4& x) { return x | shuffle<1,0>(x); } + __forceinline vdouble4 vreduce_or (const vdouble4& y) { const vdouble4 x = vreduce_or2(y); return x | shuffle2<1,0>(x); } + + __forceinline vdouble4 vreduce_add2(const vdouble4& x) { return x + shuffle<1,0>(x); } + __forceinline vdouble4 vreduce_add (const vdouble4& y) { const vdouble4 x = vreduce_add2(y); return x + shuffle2<1,0>(x); } + + __forceinline double reduce_add(const vdouble4& a) { return toScalar(vreduce_add(a)); } + __forceinline double reduce_min(const vdouble4& a) { return toScalar(vreduce_min(a)); } + __forceinline double reduce_max(const vdouble4& a) { return toScalar(vreduce_max(a)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Memory load and store operations + //////////////////////////////////////////////////////////////////////////////// + + + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vdouble4& v) + { + cout << "<" << v[0]; + for (size_t i=1; i<4; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vdouble8_avx512.h b/thirdparty/embree/common/simd/vdouble8_avx512.h new file mode 100644 index 000000000000..4eec7d2f6a0f --- /dev/null +++ b/thirdparty/embree/common/simd/vdouble8_avx512.h @@ -0,0 +1,356 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX-512 64-bit double type */ + template<> + struct vdouble<8> + { + ALIGNED_STRUCT_(64); + + typedef vboold8 Bool; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m512d v; + double i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble() {} + __forceinline vdouble(const vdouble8& t) { v = t.v; } + __forceinline vdouble8& operator =(const vdouble8& f) { v = f.v; return *this; } + + __forceinline vdouble(const __m512d& t) { v = t; } + __forceinline operator __m512d() const { return v; } + __forceinline operator __m256d() const { return _mm512_castpd512_pd256(v); } + + __forceinline vdouble(double i) { + v = _mm512_set1_pd(i); + } + + __forceinline vdouble(double a, double b, double c, double d) { + v = _mm512_set4_pd(d,c,b,a); + } + + __forceinline vdouble(double a0, double a1, double a2, double a3, + double a4, double a5, double a6, double a7) + { + v = _mm512_set_pd(a7,a6,a5,a4,a3,a2,a1,a0); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble(ZeroTy) : v(_mm512_setzero_pd()) {} + __forceinline vdouble(OneTy) : v(_mm512_set1_pd(1)) {} + __forceinline vdouble(StepTy) : v(_mm512_set_pd(7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0)) {} + __forceinline vdouble(ReverseStepTy) : v(_mm512_setr_pd(7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline void store_nt(void *__restrict__ ptr, const vdouble8& a) { + _mm512_stream_pd((double*)ptr, a); + } + + static __forceinline vdouble8 loadu(const void* addr) { + return _mm512_loadu_pd((double*)addr); + } + + static __forceinline vdouble8 load(const vdouble8* addr) { + return _mm512_load_pd((double*)addr); + } + + static __forceinline vdouble8 load(const double* addr) { + return _mm512_load_pd(addr); + } + + static __forceinline void store(void* ptr, const vdouble8& v) { + _mm512_store_pd(ptr, v); + } + + static __forceinline void storeu(void* ptr, const vdouble8& v) { + _mm512_storeu_pd(ptr, v); + } + + static __forceinline void storeu(const vboold8& mask, double* ptr, const vdouble8& f) { + _mm512_mask_storeu_pd(ptr, mask, f); + } + + static __forceinline void store(const vboold8& mask, void* addr, const vdouble8& v2) { + _mm512_mask_store_pd(addr, mask, v2); + } + + /* pass by value to avoid compiler generating inefficient code */ + static __forceinline void storeu_compact(const vboold8 mask,void * addr, const vdouble8& reg) { + _mm512_mask_compressstoreu_pd(addr, mask, reg); + } + + static __forceinline vdouble8 compact64bit(const vboold8& mask, vdouble8& v) { + return _mm512_mask_compress_pd(v, mask, v); + } + + static __forceinline vdouble8 compact(const vboold8& mask, vdouble8& v) { + return _mm512_mask_compress_pd(v, mask, v); + } + + static __forceinline vdouble8 compact(const vboold8& mask, const vdouble8& a, vdouble8& b) { + return _mm512_mask_compress_pd(a, mask, b); + } + + static __forceinline vdouble8 broadcast(const void* a) { return _mm512_set1_pd(*(double*)a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline double& operator [](size_t index) { assert(index < 8); return i[index]; } + __forceinline const double& operator [](size_t index) const { assert(index < 8); return i[index]; } + + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble8 asDouble(const vllong8& a) { return _mm512_castsi512_pd(a); } + __forceinline vllong8 asLLong (const vdouble8& a) { return _mm512_castpd_si512(a); } + + __forceinline vdouble8 operator +(const vdouble8& a) { return a; } + __forceinline vdouble8 operator -(const vdouble8& a) { return _mm512_sub_pd(_mm512_setzero_pd(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble8 operator +(const vdouble8& a, const vdouble8& b) { return _mm512_add_pd(a, b); } + __forceinline vdouble8 operator +(const vdouble8& a, double b) { return a + vdouble8(b); } + __forceinline vdouble8 operator +(double a, const vdouble8& b) { return vdouble8(a) + b; } + + __forceinline vdouble8 operator -(const vdouble8& a, const vdouble8& b) { return _mm512_sub_pd(a, b); } + __forceinline vdouble8 operator -(const vdouble8& a, double b) { return a - vdouble8(b); } + __forceinline vdouble8 operator -(double a, const vdouble8& b) { return vdouble8(a) - b; } + + __forceinline vdouble8 operator *(const vdouble8& a, const vdouble8& b) { return _mm512_mul_pd(a, b); } + __forceinline vdouble8 operator *(const vdouble8& a, double b) { return a * vdouble8(b); } + __forceinline vdouble8 operator *(double a, const vdouble8& b) { return vdouble8(a) * b; } + + __forceinline vdouble8 operator &(const vdouble8& a, const vdouble8& b) { return _mm512_and_pd(a, b); } + __forceinline vdouble8 operator &(const vdouble8& a, double b) { return a & vdouble8(b); } + __forceinline vdouble8 operator &(double a, const vdouble8& b) { return vdouble8(a) & b; } + + __forceinline vdouble8 operator |(const vdouble8& a, const vdouble8& b) { return _mm512_or_pd(a, b); } + __forceinline vdouble8 operator |(const vdouble8& a, double b) { return a | vdouble8(b); } + __forceinline vdouble8 operator |(double a, const vdouble8& b) { return vdouble8(a) | b; } + + __forceinline vdouble8 operator ^(const vdouble8& a, const vdouble8& b) { return _mm512_xor_pd(a, b); } + __forceinline vdouble8 operator ^(const vdouble8& a, double b) { return a ^ vdouble8(b); } + __forceinline vdouble8 operator ^(double a, const vdouble8& b) { return vdouble8(a) ^ b; } + + __forceinline vdouble8 operator <<(const vdouble8& a, const unsigned int n) { return _mm512_castsi512_pd(_mm512_slli_epi64(_mm512_castpd_si512(a), n)); } + __forceinline vdouble8 operator >>(const vdouble8& a, const unsigned int n) { return _mm512_castsi512_pd(_mm512_srai_epi64(_mm512_castpd_si512(a), n)); } + + __forceinline vdouble8 operator <<(const vdouble8& a, const vllong8& n) { return _mm512_castsi512_pd(_mm512_sllv_epi64(_mm512_castpd_si512(a), n)); } + __forceinline vdouble8 operator >>(const vdouble8& a, const vllong8& n) { return _mm512_castsi512_pd(_mm512_srav_epi64(_mm512_castpd_si512(a), n)); } + + __forceinline vdouble8 sll (const vdouble8& a, const unsigned int b) { return _mm512_castsi512_pd(_mm512_slli_epi64(_mm512_castpd_si512(a), b)); } + __forceinline vdouble8 sra (const vdouble8& a, const unsigned int b) { return _mm512_castsi512_pd(_mm512_srai_epi64(_mm512_castpd_si512(a), b)); } + __forceinline vdouble8 srl (const vdouble8& a, const unsigned int b) { return _mm512_castsi512_pd(_mm512_srli_epi64(_mm512_castpd_si512(a), b)); } + + __forceinline vdouble8 min(const vdouble8& a, const vdouble8& b) { return _mm512_min_pd(a, b); } + __forceinline vdouble8 min(const vdouble8& a, double b) { return min(a,vdouble8(b)); } + __forceinline vdouble8 min(double a, const vdouble8& b) { return min(vdouble8(a),b); } + + __forceinline vdouble8 max(const vdouble8& a, const vdouble8& b) { return _mm512_max_pd(a, b); } + __forceinline vdouble8 max(const vdouble8& a, double b) { return max(a,vdouble8(b)); } + __forceinline vdouble8 max(double a, const vdouble8& b) { return max(vdouble8(a),b); } + + __forceinline vdouble8 mask_add(const vboold8& mask, vdouble8& c, const vdouble8& a, const vdouble8& b) { return _mm512_mask_add_pd(c,mask,a,b); } + __forceinline vdouble8 mask_sub(const vboold8& mask, vdouble8& c, const vdouble8& a, const vdouble8& b) { return _mm512_mask_sub_pd(c,mask,a,b); } + + __forceinline vdouble8 mask_and(const vboold8& m,vdouble8& c, const vdouble8& a, const vdouble8& b) { return _mm512_mask_and_pd(c,m,a,b); } + __forceinline vdouble8 mask_or (const vboold8& m,vdouble8& c, const vdouble8& a, const vdouble8& b) { return _mm512_mask_or_pd(c,m,a,b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble8 madd (const vdouble8& a, const vdouble8& b, const vdouble8& c) { return _mm512_fmadd_pd(a,b,c); } + __forceinline vdouble8 msub (const vdouble8& a, const vdouble8& b, const vdouble8& c) { return _mm512_fmsub_pd(a,b,c); } + __forceinline vdouble8 nmadd(const vdouble8& a, const vdouble8& b, const vdouble8& c) { return _mm512_fnmadd_pd(a,b,c); } + __forceinline vdouble8 nmsub(const vdouble8& a, const vdouble8& b, const vdouble8& c) { return _mm512_fnmsub_pd(a,b,c); } + + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble8& operator +=(vdouble8& a, const vdouble8& b) { return a = a + b; } + __forceinline vdouble8& operator +=(vdouble8& a, double b) { return a = a + b; } + + __forceinline vdouble8& operator -=(vdouble8& a, const vdouble8& b) { return a = a - b; } + __forceinline vdouble8& operator -=(vdouble8& a, double b) { return a = a - b; } + + __forceinline vdouble8& operator *=(vdouble8& a, const vdouble8& b) { return a = a * b; } + __forceinline vdouble8& operator *=(vdouble8& a, double b) { return a = a * b; } + + __forceinline vdouble8& operator &=(vdouble8& a, const vdouble8& b) { return a = a & b; } + __forceinline vdouble8& operator &=(vdouble8& a, double b) { return a = a & b; } + + __forceinline vdouble8& operator |=(vdouble8& a, const vdouble8& b) { return a = a | b; } + __forceinline vdouble8& operator |=(vdouble8& a, double b) { return a = a | b; } + + __forceinline vdouble8& operator <<=(vdouble8& a, const double b) { return a = a << b; } + __forceinline vdouble8& operator >>=(vdouble8& a, const double b) { return a = a >> b; } + + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8 operator ==(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboold8 operator ==(const vdouble8& a, double b) { return a == vdouble8(b); } + __forceinline vboold8 operator ==(double a, const vdouble8& b) { return vdouble8(a) == b; } + + __forceinline vboold8 operator !=(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboold8 operator !=(const vdouble8& a, double b) { return a != vdouble8(b); } + __forceinline vboold8 operator !=(double a, const vdouble8& b) { return vdouble8(a) != b; } + + __forceinline vboold8 operator < (const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboold8 operator < (const vdouble8& a, double b) { return a < vdouble8(b); } + __forceinline vboold8 operator < (double a, const vdouble8& b) { return vdouble8(a) < b; } + + __forceinline vboold8 operator >=(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboold8 operator >=(const vdouble8& a, double b) { return a >= vdouble8(b); } + __forceinline vboold8 operator >=(double a, const vdouble8& b) { return vdouble8(a) >= b; } + + __forceinline vboold8 operator > (const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboold8 operator > (const vdouble8& a, double b) { return a > vdouble8(b); } + __forceinline vboold8 operator > (double a, const vdouble8& b) { return vdouble8(a) > b; } + + __forceinline vboold8 operator <=(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboold8 operator <=(const vdouble8& a, double b) { return a <= vdouble8(b); } + __forceinline vboold8 operator <=(double a, const vdouble8& b) { return vdouble8(a) <= b; } + + __forceinline vboold8 eq(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboold8 ne(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboold8 lt(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboold8 ge(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboold8 gt(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboold8 le(const vdouble8& a, const vdouble8& b) { return _mm512_cmp_pd_mask(a,b,_MM_CMPINT_LE); } + + __forceinline vboold8 eq(const vboold8 mask, const vdouble8& a, const vdouble8& b) { return _mm512_mask_cmp_pd_mask(mask,a,b,_MM_CMPINT_EQ); } + __forceinline vboold8 ne(const vboold8 mask, const vdouble8& a, const vdouble8& b) { return _mm512_mask_cmp_pd_mask(mask,a,b,_MM_CMPINT_NE); } + __forceinline vboold8 lt(const vboold8 mask, const vdouble8& a, const vdouble8& b) { return _mm512_mask_cmp_pd_mask(mask,a,b,_MM_CMPINT_LT); } + __forceinline vboold8 ge(const vboold8 mask, const vdouble8& a, const vdouble8& b) { return _mm512_mask_cmp_pd_mask(mask,a,b,_MM_CMPINT_GE); } + __forceinline vboold8 gt(const vboold8 mask, const vdouble8& a, const vdouble8& b) { return _mm512_mask_cmp_pd_mask(mask,a,b,_MM_CMPINT_GT); } + __forceinline vboold8 le(const vboold8 mask, const vdouble8& a, const vdouble8& b) { return _mm512_mask_cmp_pd_mask(mask,a,b,_MM_CMPINT_LE); } + + __forceinline vdouble8 select(const vboold8& m, const vdouble8& t, const vdouble8& f) { + return _mm512_mask_or_pd(f,m,t,t); + } + + __forceinline void xchg(const vboold8& m, vdouble8& a, vdouble8& b) { + const vdouble8 c = a; a = select(m,b,a); b = select(m,c,b); + } + + __forceinline vboold8 test(const vboold8& m, const vdouble8& a, const vdouble8& b) { + return _mm512_mask_test_epi64_mask(m,_mm512_castpd_si512(a),_mm512_castpd_si512(b)); + } + + __forceinline vboold8 test(const vdouble8& a, const vdouble8& b) { + return _mm512_test_epi64_mask(_mm512_castpd_si512(a),_mm512_castpd_si512(b)); + } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vdouble8 shuffle(const vdouble8& v) { + return _mm512_permute_pd(v, (i1 << 7) | (i0 << 6) | (i1 << 5) | (i0 << 4) | (i1 << 3) | (i0 << 2) | (i1 << 1) | i0); + } + + template + __forceinline vdouble8 shuffle(const vdouble8& v) { + return shuffle(v); + } + + template + __forceinline vdouble8 shuffle(const vdouble8& v) { + return _mm512_permutex_pd(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vdouble8 shuffle4(const vdouble8& v) { + return _mm512_shuffle_f64x2(v, v, _MM_SHUFFLE(i1*2+1, i1*2, i0*2+1, i0*2)); + } + + template + __forceinline vdouble8 shuffle4(const vdouble8& v) { + return shuffle4(v); + } + + template + __forceinline vdouble8 align_shift_right(const vdouble8& a, const vdouble8& b) { + return _mm512_castsi512_pd(_mm512_alignr_epi64(_mm512_castpd_si512(a), _mm512_castpd_si512(b), i)); + } + + __forceinline double toScalar(const vdouble8& v) { + return _mm_cvtsd_f64(_mm512_castpd512_pd128(v)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble8 vreduce_add2(vdouble8 x) { return x + shuffle<1,0,3,2>(x); } + __forceinline vdouble8 vreduce_add4(vdouble8 x) { x = vreduce_add2(x); return x + shuffle<2,3,0,1>(x); } + __forceinline vdouble8 vreduce_add (vdouble8 x) { x = vreduce_add4(x); return x + shuffle4<1,0>(x); } + + __forceinline vdouble8 vreduce_min2(vdouble8 x) { return min(x, shuffle<1,0,3,2>(x)); } + __forceinline vdouble8 vreduce_min4(vdouble8 x) { x = vreduce_min2(x); return min(x, shuffle<2,3,0,1>(x)); } + __forceinline vdouble8 vreduce_min (vdouble8 x) { x = vreduce_min4(x); return min(x, shuffle4<1,0>(x)); } + + __forceinline vdouble8 vreduce_max2(vdouble8 x) { return max(x, shuffle<1,0,3,2>(x)); } + __forceinline vdouble8 vreduce_max4(vdouble8 x) { x = vreduce_max2(x); return max(x, shuffle<2,3,0,1>(x)); } + __forceinline vdouble8 vreduce_max (vdouble8 x) { x = vreduce_max4(x); return max(x, shuffle4<1,0>(x)); } + + __forceinline double reduce_add(const vdouble8& v) { return toScalar(vreduce_add(v)); } + __forceinline double reduce_min(const vdouble8& v) { return toScalar(vreduce_min(v)); } + __forceinline double reduce_max(const vdouble8& v) { return toScalar(vreduce_max(v)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Memory load and store operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vdouble8 permute(const vdouble8& v, const vllong8& index) { + return _mm512_permutexvar_pd(index, v); + } + + __forceinline vdouble8 reverse(const vdouble8& a) { + return permute(a, vllong8(reverse_step)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vdouble8& v) + { + cout << "<" << v[0]; + for (size_t i=1; i<8; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vfloat16_avx512.h b/thirdparty/embree/common/simd/vfloat16_avx512.h new file mode 100644 index 000000000000..aed2419b779b --- /dev/null +++ b/thirdparty/embree/common/simd/vfloat16_avx512.h @@ -0,0 +1,771 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 16-wide AVX-512 float type */ + template<> + struct vfloat<16> + { + ALIGNED_STRUCT_(64); + + typedef vboolf16 Bool; + typedef vint16 Int; + typedef vfloat16 Float; + + enum { size = 16 }; // number of SIMD elements + union { // data + __m512 v; + float f[16]; + int i[16]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat() {} + __forceinline vfloat(const vfloat16& t) { v = t; } + __forceinline vfloat16& operator =(const vfloat16& f) { v = f.v; return *this; } + + __forceinline vfloat(const __m512& t) { v = t; } + __forceinline operator __m512() const { return v; } + __forceinline operator __m256() const { return _mm512_castps512_ps256(v); } + __forceinline operator __m128() const { return _mm512_castps512_ps128(v); } + + __forceinline vfloat(float f) { + v = _mm512_set1_ps(f); + } + + __forceinline vfloat(float a, float b, float c, float d) { + v = _mm512_set4_ps(a, b, c, d); + } + + __forceinline vfloat(const vfloat4& i) { + v = _mm512_broadcast_f32x4(i); + } + + __forceinline vfloat(const vfloat4& a, const vfloat4& b, const vfloat4& c, const vfloat4& d) { + v = _mm512_castps128_ps512(a); + v = _mm512_insertf32x4(v, b, 1); + v = _mm512_insertf32x4(v, c, 2); + v = _mm512_insertf32x4(v, d, 3); + } + + __forceinline vfloat(const vboolf16& mask, const vfloat4& a, const vfloat4& b) { + v = _mm512_broadcast_f32x4(a); + v = _mm512_mask_broadcast_f32x4(v,mask,b); + } + + __forceinline vfloat(const vfloat8& i) { + v = _mm512_castpd_ps(_mm512_broadcast_f64x4(_mm256_castps_pd(i))); + } + + __forceinline vfloat(const vfloat8& a, const vfloat8& b) { + v = _mm512_castps256_ps512(a); +#if defined(__AVX512DQ__) + v = _mm512_insertf32x8(v, b, 1); +#else + v = _mm512_castpd_ps(_mm512_insertf64x4(_mm512_castps_pd(v), _mm256_castps_pd(b), 1)); +#endif + } + + /* WARNING: due to f64x4 the mask is considered as an 8bit mask */ + __forceinline vfloat(const vboolf16& mask, const vfloat8& a, const vfloat8& b) { + __m512d aa = _mm512_broadcast_f64x4(_mm256_castps_pd(a)); + aa = _mm512_mask_broadcast_f64x4(aa,mask,_mm256_castps_pd(b)); + v = _mm512_castpd_ps(aa); + } + + __forceinline explicit vfloat(const vint16& a) { + v = _mm512_cvtepi32_ps(a); + } + + __forceinline explicit vfloat(const vuint16& a) { + v = _mm512_cvtepu32_ps(a); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat(ZeroTy) : v(_mm512_setzero_ps()) {} + __forceinline vfloat(OneTy) : v(_mm512_set1_ps(1.0f)) {} + __forceinline vfloat(PosInfTy) : v(_mm512_set1_ps(pos_inf)) {} + __forceinline vfloat(NegInfTy) : v(_mm512_set1_ps(neg_inf)) {} + __forceinline vfloat(StepTy) : v(_mm512_set_ps(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) {} + __forceinline vfloat(NaNTy) : v(_mm512_set1_ps(nan)) {} + __forceinline vfloat(UndefinedTy) : v(_mm512_undefined_ps()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vfloat16 load (const void* ptr) { return _mm512_load_ps((float*)ptr); } + static __forceinline vfloat16 loadu(const void* ptr) { return _mm512_loadu_ps((float*)ptr); } + + static __forceinline vfloat16 load (const vboolf16& mask, const void* ptr) { return _mm512_mask_load_ps (_mm512_setzero_ps(),mask,(float*)ptr); } + static __forceinline vfloat16 loadu(const vboolf16& mask, const void* ptr) { return _mm512_mask_loadu_ps(_mm512_setzero_ps(),mask,(float*)ptr); } + + static __forceinline void store (void* ptr, const vfloat16& v) { _mm512_store_ps ((float*)ptr,v); } + static __forceinline void storeu(void* ptr, const vfloat16& v) { _mm512_storeu_ps((float*)ptr,v); } + + static __forceinline void store (const vboolf16& mask, void* ptr, const vfloat16& v) { _mm512_mask_store_ps ((float*)ptr,mask,v); } + static __forceinline void storeu(const vboolf16& mask, void* ptr, const vfloat16& v) { _mm512_mask_storeu_ps((float*)ptr,mask,v); } + + static __forceinline void store_nt(void* __restrict__ ptr, const vfloat16& a) { + _mm512_stream_ps((float*)ptr,a); + } + + static __forceinline vfloat16 broadcast(const float* f) { + return _mm512_set1_ps(*f); + } + + static __forceinline vfloat16 compact(const vboolf16& mask, vfloat16 &v) { + return _mm512_mask_compress_ps(v, mask, v); + } + static __forceinline vfloat16 compact(const vboolf16& mask, vfloat16 &a, const vfloat16& b) { + return _mm512_mask_compress_ps(a, mask, b); + } + + static __forceinline vfloat16 expand(const vboolf16& mask, const vfloat16& a, vfloat16& b) { + return _mm512_mask_expand_ps(b, mask, a); + } + + static __forceinline vfloat16 loadu_compact(const vboolf16& mask, const void* ptr) { + return _mm512_mask_expandloadu_ps(_mm512_setzero_ps(), mask, (float*)ptr); + } + + static __forceinline void storeu_compact(const vboolf16& mask, float *addr, const vfloat16 reg) { + _mm512_mask_compressstoreu_ps(addr, mask, reg); + } + + static __forceinline void storeu_compact_single(const vboolf16& mask, float * addr, const vfloat16& reg) { + //_mm512_mask_compressstoreu_ps(addr,mask,reg); + *addr = mm512_cvtss_f32(_mm512_mask_compress_ps(reg, mask, reg)); + } + + template + static __forceinline vfloat16 gather(const float* ptr, const vint16& index) { + return _mm512_i32gather_ps(index, ptr, scale); + } + + template + static __forceinline vfloat16 gather(const vboolf16& mask, const float* ptr, const vint16& index) { + vfloat16 r = zero; + return _mm512_mask_i32gather_ps(r, mask, index, ptr, scale); + } + + template + static __forceinline void scatter(float* ptr, const vint16& index, const vfloat16& v) { + _mm512_i32scatter_ps(ptr, index, v, scale); + } + + template + static __forceinline void scatter(const vboolf16& mask, float* ptr, const vint16& index, const vfloat16& v) { + _mm512_mask_i32scatter_ps(ptr, mask, index, v, scale); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline float& operator [](size_t index) { assert(index < 16); return f[index]; } + __forceinline const float& operator [](size_t index) const { assert(index < 16); return f[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 asFloat(const vint16& a) { return _mm512_castsi512_ps(a); } + __forceinline vint16 asInt (const vfloat16& a) { return _mm512_castps_si512(a); } + __forceinline vuint16 asUInt (const vfloat16& a) { return _mm512_castps_si512(a); } + + __forceinline vint16 toInt (const vfloat16& a) { return vint16(a); } + __forceinline vfloat16 toFloat(const vint16& a) { return vfloat16(a); } + + __forceinline vfloat16 operator +(const vfloat16& a) { return a; } + __forceinline vfloat16 operator -(const vfloat16& a) { return _mm512_mul_ps(a,vfloat16(-1)); } + + __forceinline vfloat16 abs (const vfloat16& a) { return _mm512_castsi512_ps(_mm512_and_epi32(_mm512_castps_si512(a),_mm512_set1_epi32(0x7FFFFFFF))); } + __forceinline vfloat16 signmsk(const vfloat16& a) { return _mm512_castsi512_ps(_mm512_and_epi32(_mm512_castps_si512(a),_mm512_set1_epi32(0x80000000))); } + + __forceinline vfloat16 rcp(const vfloat16& a) { +#if defined(__AVX512ER__) + return _mm512_rcp28_ps(a); +#else + const vfloat16 r = _mm512_rcp14_ps(a); + return _mm512_mul_ps(r, _mm512_fnmadd_ps(r, a, vfloat16(2.0f))); +#endif + } + + __forceinline vfloat16 sqr (const vfloat16& a) { return _mm512_mul_ps(a,a); } + __forceinline vfloat16 sqrt(const vfloat16& a) { return _mm512_sqrt_ps(a); } + + __forceinline vfloat16 rsqrt(const vfloat16& a) + { +#if defined(__AVX512VL__) + const vfloat16 r = _mm512_rsqrt14_ps(a); + return _mm512_fmadd_ps(_mm512_set1_ps(1.5f), r, + _mm512_mul_ps(_mm512_mul_ps(_mm512_mul_ps(a, _mm512_set1_ps(-0.5f)), r), _mm512_mul_ps(r, r))); +#else + return _mm512_rsqrt28_ps(a); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 operator +(const vfloat16& a, const vfloat16& b) { return _mm512_add_ps(a, b); } + __forceinline vfloat16 operator +(const vfloat16& a, float b) { return a + vfloat16(b); } + __forceinline vfloat16 operator +(float a, const vfloat16& b) { return vfloat16(a) + b; } + + __forceinline vfloat16 operator -(const vfloat16& a, const vfloat16& b) { return _mm512_sub_ps(a, b); } + __forceinline vfloat16 operator -(const vfloat16& a, float b) { return a - vfloat16(b); } + __forceinline vfloat16 operator -(float a, const vfloat16& b) { return vfloat16(a) - b; } + + __forceinline vfloat16 operator *(const vfloat16& a, const vfloat16& b) { return _mm512_mul_ps(a, b); } + __forceinline vfloat16 operator *(const vfloat16& a, float b) { return a * vfloat16(b); } + __forceinline vfloat16 operator *(float a, const vfloat16& b) { return vfloat16(a) * b; } + + __forceinline vfloat16 operator /(const vfloat16& a, const vfloat16& b) { return _mm512_div_ps(a,b); } + __forceinline vfloat16 operator /(const vfloat16& a, float b) { return a/vfloat16(b); } + __forceinline vfloat16 operator /(float a, const vfloat16& b) { return vfloat16(a)/b; } + + __forceinline vfloat16 operator &(const vfloat16& a, const vfloat16& b) { return _mm512_and_ps(a,b); } + __forceinline vfloat16 operator |(const vfloat16& a, const vfloat16& b) { return _mm512_or_ps(a,b); } + __forceinline vfloat16 operator ^(const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_xor_epi32(_mm512_castps_si512(a),_mm512_castps_si512(b))); + } + + __forceinline vfloat16 min(const vfloat16& a, const vfloat16& b) { + return _mm512_min_ps(a,b); + } + __forceinline vfloat16 min(const vfloat16& a, float b) { + return _mm512_min_ps(a,vfloat16(b)); + } + __forceinline vfloat16 min(const float& a, const vfloat16& b) { + return _mm512_min_ps(vfloat16(a),b); + } + + __forceinline vfloat16 max(const vfloat16& a, const vfloat16& b) { + return _mm512_max_ps(a,b); + } + __forceinline vfloat16 max(const vfloat16& a, float b) { + return _mm512_max_ps(a,vfloat16(b)); + } + __forceinline vfloat16 max(const float& a, const vfloat16& b) { + return _mm512_max_ps(vfloat16(a),b); + } + + __forceinline vfloat16 mask_add(const vboolf16& mask, const vfloat16& c, const vfloat16& a, const vfloat16& b) { return _mm512_mask_add_ps (c,mask,a,b); } + __forceinline vfloat16 mask_min(const vboolf16& mask, const vfloat16& c, const vfloat16& a, const vfloat16& b) { + return _mm512_mask_min_ps(c,mask,a,b); + }; + __forceinline vfloat16 mask_max(const vboolf16& mask, const vfloat16& c, const vfloat16& a, const vfloat16& b) { + return _mm512_mask_max_ps(c,mask,a,b); + }; + + __forceinline vfloat16 mini(const vfloat16& a, const vfloat16& b) { +#if !defined(__AVX512ER__) // SKX + const vint16 ai = _mm512_castps_si512(a); + const vint16 bi = _mm512_castps_si512(b); + const vint16 ci = _mm512_min_epi32(ai,bi); + return _mm512_castsi512_ps(ci); +#else // KNL + return min(a,b); +#endif + } + + __forceinline vfloat16 maxi(const vfloat16& a, const vfloat16& b) { +#if !defined(__AVX512ER__) // SKX + const vint16 ai = _mm512_castps_si512(a); + const vint16 bi = _mm512_castps_si512(b); + const vint16 ci = _mm512_max_epi32(ai,bi); + return _mm512_castsi512_ps(ci); +#else // KNL + return max(a,b); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 madd (const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmadd_ps(a,b,c); } + __forceinline vfloat16 msub (const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmsub_ps(a,b,c); } + __forceinline vfloat16 nmadd(const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fnmadd_ps(a,b,c); } + __forceinline vfloat16 nmsub(const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fnmsub_ps(a,b,c); } + + __forceinline vfloat16 mask_msub(const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_fmsub_ps(a,mask,b,c); } + + __forceinline vfloat16 madd231 (const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmadd_ps(c,b,a); } + __forceinline vfloat16 msub213 (const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmsub_ps(a,b,c); } + __forceinline vfloat16 msub231 (const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmsub_ps(c,b,a); } + __forceinline vfloat16 msubr231(const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fnmadd_ps(c,b,a); } + + + //////////////////////////////////////////////////////////////////////////////// + /// Operators with rounding + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 madd_round_down(const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmadd_round_ps(a,b,c,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 madd_round_up (const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_fmadd_round_ps(a,b,c,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 mul_round_down(const vfloat16& a, const vfloat16& b) { return _mm512_mul_round_ps(a,b,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 mul_round_up (const vfloat16& a, const vfloat16& b) { return _mm512_mul_round_ps(a,b,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 add_round_down(const vfloat16& a, const vfloat16& b) { return _mm512_add_round_ps(a,b,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 add_round_up (const vfloat16& a, const vfloat16& b) { return _mm512_add_round_ps(a,b,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 sub_round_down(const vfloat16& a, const vfloat16& b) { return _mm512_sub_round_ps(a,b,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 sub_round_up (const vfloat16& a, const vfloat16& b) { return _mm512_sub_round_ps(a,b,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 div_round_down(const vfloat16& a, const vfloat16& b) { return _mm512_div_round_ps(a,b,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 div_round_up (const vfloat16& a, const vfloat16& b) { return _mm512_div_round_ps(a,b,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 mask_msub_round_down(const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_fmsub_round_ps(a,mask,b,c,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 mask_msub_round_up (const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_fmsub_round_ps(a,mask,b,c,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 mask_mul_round_down(const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_mul_round_ps(a,mask,b,c,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 mask_mul_round_up (const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_mul_round_ps(a,mask,b,c,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + __forceinline vfloat16 mask_sub_round_down(const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_sub_round_ps(a,mask,b,c,_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); } + __forceinline vfloat16 mask_sub_round_up (const vboolf16& mask,const vfloat16& a, const vfloat16& b, const vfloat16& c) { return _mm512_mask_sub_round_ps(a,mask,b,c,_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); } + + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16& operator +=(vfloat16& a, const vfloat16& b) { return a = a + b; } + __forceinline vfloat16& operator +=(vfloat16& a, float b) { return a = a + b; } + + __forceinline vfloat16& operator -=(vfloat16& a, const vfloat16& b) { return a = a - b; } + __forceinline vfloat16& operator -=(vfloat16& a, float b) { return a = a - b; } + + __forceinline vfloat16& operator *=(vfloat16& a, const vfloat16& b) { return a = a * b; } + __forceinline vfloat16& operator *=(vfloat16& a, float b) { return a = a * b; } + + __forceinline vfloat16& operator /=(vfloat16& a, const vfloat16& b) { return a = a / b; } + __forceinline vfloat16& operator /=(vfloat16& a, float b) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 operator ==(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 operator ==(const vfloat16& a, float b) { return a == vfloat16(b); } + __forceinline vboolf16 operator ==(float a, const vfloat16& b) { return vfloat16(a) == b; } + + __forceinline vboolf16 operator !=(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 operator !=(const vfloat16& a, float b) { return a != vfloat16(b); } + __forceinline vboolf16 operator !=(float a, const vfloat16& b) { return vfloat16(a) != b; } + + __forceinline vboolf16 operator < (const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 operator < (const vfloat16& a, float b) { return a < vfloat16(b); } + __forceinline vboolf16 operator < (float a, const vfloat16& b) { return vfloat16(a) < b; } + + __forceinline vboolf16 operator >=(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 operator >=(const vfloat16& a, float b) { return a >= vfloat16(b); } + __forceinline vboolf16 operator >=(float a, const vfloat16& b) { return vfloat16(a) >= b; } + + __forceinline vboolf16 operator > (const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 operator > (const vfloat16& a, float b) { return a > vfloat16(b); } + __forceinline vboolf16 operator > (float a, const vfloat16& b) { return vfloat16(a) > b; } + + __forceinline vboolf16 operator <=(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 operator <=(const vfloat16& a, float b) { return a <= vfloat16(b); } + __forceinline vboolf16 operator <=(float a, const vfloat16& b) { return vfloat16(a) <= b; } + + __forceinline vboolf16 eq(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 ne(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 lt(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 ge(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 gt(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 le(const vfloat16& a, const vfloat16& b) { return _mm512_cmp_ps_mask(a,b,_MM_CMPINT_LE); } + + __forceinline vboolf16 eq(const vboolf16& mask, const vfloat16& a, const vfloat16& b) { return _mm512_mask_cmp_ps_mask(mask,a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 ne(const vboolf16& mask, const vfloat16& a, const vfloat16& b) { return _mm512_mask_cmp_ps_mask(mask,a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 lt(const vboolf16& mask, const vfloat16& a, const vfloat16& b) { return _mm512_mask_cmp_ps_mask(mask,a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 ge(const vboolf16& mask, const vfloat16& a, const vfloat16& b) { return _mm512_mask_cmp_ps_mask(mask,a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 gt(const vboolf16& mask, const vfloat16& a, const vfloat16& b) { return _mm512_mask_cmp_ps_mask(mask,a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 le(const vboolf16& mask, const vfloat16& a, const vfloat16& b) { return _mm512_mask_cmp_ps_mask(mask,a,b,_MM_CMPINT_LE); } + + __forceinline vfloat16 select(const vboolf16& s, const vfloat16& t, const vfloat16& f) { + return _mm512_mask_blend_ps(s, f, t); + } + + __forceinline vfloat16 lerp(const vfloat16& a, const vfloat16& b, const vfloat16& t) { + return madd(t,b-a,a); + } + + __forceinline void xchg(vboolf16 m, vfloat16& a, vfloat16& b) + { + vfloat16 c = a; + a = select(m,b,a); + b = select(m,c,b); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Rounding Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 floor(const vfloat16& a) { + return _mm512_floor_ps(a); + } + __forceinline vfloat16 ceil (const vfloat16& a) { + return _mm512_ceil_ps(a); + } + __forceinline vfloat16 round (const vfloat16& a) { + return _mm512_roundscale_ps(a, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + } + __forceinline vint16 floori (const vfloat16& a) { + return _mm512_cvt_roundps_epi32(a, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 unpacklo(const vfloat16& a, const vfloat16& b) { return _mm512_unpacklo_ps(a, b); } + __forceinline vfloat16 unpackhi(const vfloat16& a, const vfloat16& b) { return _mm512_unpackhi_ps(a, b); } + + template + __forceinline vfloat16 shuffle(const vfloat16& v) { + return _mm512_permute_ps(v, _MM_SHUFFLE(i, i, i, i)); + } + + template + __forceinline vfloat16 shuffle(const vfloat16& v) { + return _mm512_permute_ps(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vfloat16 shuffle4(const vfloat16& v) { + return _mm512_shuffle_f32x4(v, v ,_MM_SHUFFLE(i, i, i, i)); + } + + template + __forceinline vfloat16 shuffle4(const vfloat16& v) { + return _mm512_shuffle_f32x4(v, v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + __forceinline vfloat16 interleave_even(const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_mask_shuffle_epi32(_mm512_castps_si512(a), mm512_int2mask(0xaaaa), _mm512_castps_si512(b), (_MM_PERM_ENUM)0xb1)); + } + + __forceinline vfloat16 interleave_odd(const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_mask_shuffle_epi32(_mm512_castps_si512(b), mm512_int2mask(0x5555), _mm512_castps_si512(a), (_MM_PERM_ENUM)0xb1)); + } + + __forceinline vfloat16 interleave2_even(const vfloat16& a, const vfloat16& b) { + /* mask should be 8-bit but is 16-bit to reuse for interleave_even */ + return _mm512_castsi512_ps(_mm512_mask_permutex_epi64(_mm512_castps_si512(a), mm512_int2mask(0xaaaa), _mm512_castps_si512(b), (_MM_PERM_ENUM)0xb1)); + } + + __forceinline vfloat16 interleave2_odd(const vfloat16& a, const vfloat16& b) { + /* mask should be 8-bit but is 16-bit to reuse for interleave_odd */ + return _mm512_castsi512_ps(_mm512_mask_permutex_epi64(_mm512_castps_si512(b), mm512_int2mask(0x5555), _mm512_castps_si512(a), (_MM_PERM_ENUM)0xb1)); + } + + __forceinline vfloat16 interleave4_even(const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_mask_permutex_epi64(_mm512_castps_si512(a), mm512_int2mask(0xcc), _mm512_castps_si512(b), (_MM_PERM_ENUM)0x4e)); + } + + __forceinline vfloat16 interleave4_odd(const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_mask_permutex_epi64(_mm512_castps_si512(b), mm512_int2mask(0x33), _mm512_castps_si512(a), (_MM_PERM_ENUM)0x4e)); + } + + __forceinline vfloat16 permute(vfloat16 v, __m512i index) { + return _mm512_castsi512_ps(_mm512_permutexvar_epi32(index, _mm512_castps_si512(v))); + } + + __forceinline vfloat16 reverse(const vfloat16& v) { + return permute(v,_mm512_setr_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)); + } + + template + __forceinline vfloat16 align_shift_right(const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_alignr_epi32(_mm512_castps_si512(a),_mm512_castps_si512(b),i)); + }; + + template + __forceinline vfloat16 mask_align_shift_right(const vboolf16& mask, vfloat16& c, const vfloat16& a, const vfloat16& b) { + return _mm512_castsi512_ps(_mm512_mask_alignr_epi32(_mm512_castps_si512(c),mask,_mm512_castps_si512(a),_mm512_castps_si512(b),i)); + }; + + __forceinline vfloat16 shift_left_1(const vfloat16& a) { + vfloat16 z = zero; + return mask_align_shift_right<15>(0xfffe,z,a,a); + } + + __forceinline vfloat16 shift_right_1(const vfloat16& x) { + return align_shift_right<1>(zero,x); + } + + __forceinline float toScalar(const vfloat16& v) { return mm512_cvtss_f32(v); } + + + template __forceinline vfloat16 insert4(const vfloat16& a, const vfloat4& b) { return _mm512_insertf32x4(a, b, i); } + + template + vfloat extractN(const vfloat16& v); + + template<> __forceinline vfloat4 extractN<4,0>(const vfloat16& v) { return _mm512_castps512_ps128(v); } + template<> __forceinline vfloat4 extractN<4,1>(const vfloat16& v) { return _mm512_extractf32x4_ps(v, 1); } + template<> __forceinline vfloat4 extractN<4,2>(const vfloat16& v) { return _mm512_extractf32x4_ps(v, 2); } + template<> __forceinline vfloat4 extractN<4,3>(const vfloat16& v) { return _mm512_extractf32x4_ps(v, 3); } + + template<> __forceinline vfloat8 extractN<8,0>(const vfloat16& v) { return _mm512_castps512_ps256(v); } + template<> __forceinline vfloat8 extractN<8,1>(const vfloat16& v) { return _mm512_extractf32x8_ps(v, 1); } + + template __forceinline vfloat4 extract4 (const vfloat16& v) { return _mm512_extractf32x4_ps(v, i); } + template<> __forceinline vfloat4 extract4<0>(const vfloat16& v) { return _mm512_castps512_ps128(v); } + + template __forceinline vfloat8 extract8 (const vfloat16& v) { return _mm512_extractf32x8_ps(v, i); } + template<> __forceinline vfloat8 extract8<0>(const vfloat16& v) { return _mm512_castps512_ps256(v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Transpose + //////////////////////////////////////////////////////////////////////////////// + + __forceinline void transpose(const vfloat16& r0, const vfloat16& r1, const vfloat16& r2, const vfloat16& r3, + vfloat16& c0, vfloat16& c1, vfloat16& c2, vfloat16& c3) + { +#if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL + vfloat16 a0a1_c0c1 = interleave_even(r0, r1); + vfloat16 a2a3_c2c3 = interleave_even(r2, r3); + vfloat16 b0b1_d0d1 = interleave_odd (r0, r1); + vfloat16 b2b3_d2d3 = interleave_odd (r2, r3); + + c0 = interleave2_even(a0a1_c0c1, a2a3_c2c3); + c1 = interleave2_even(b0b1_d0d1, b2b3_d2d3); + c2 = interleave2_odd (a0a1_c0c1, a2a3_c2c3); + c3 = interleave2_odd (b0b1_d0d1, b2b3_d2d3); +#else + vfloat16 a0a2_b0b2 = unpacklo(r0, r2); + vfloat16 c0c2_d0d2 = unpackhi(r0, r2); + vfloat16 a1a3_b1b3 = unpacklo(r1, r3); + vfloat16 c1c3_d1d3 = unpackhi(r1, r3); + + c0 = unpacklo(a0a2_b0b2, a1a3_b1b3); + c1 = unpackhi(a0a2_b0b2, a1a3_b1b3); + c2 = unpacklo(c0c2_d0d2, c1c3_d1d3); + c3 = unpackhi(c0c2_d0d2, c1c3_d1d3); +#endif + } + + __forceinline void transpose(const vfloat4& r0, const vfloat4& r1, const vfloat4& r2, const vfloat4& r3, + const vfloat4& r4, const vfloat4& r5, const vfloat4& r6, const vfloat4& r7, + const vfloat4& r8, const vfloat4& r9, const vfloat4& r10, const vfloat4& r11, + const vfloat4& r12, const vfloat4& r13, const vfloat4& r14, const vfloat4& r15, + vfloat16& c0, vfloat16& c1, vfloat16& c2, vfloat16& c3) + { + return transpose(vfloat16(r0, r4, r8, r12), vfloat16(r1, r5, r9, r13), vfloat16(r2, r6, r10, r14), vfloat16(r3, r7, r11, r15), + c0, c1, c2, c3); + } + + __forceinline void transpose(const vfloat16& r0, const vfloat16& r1, const vfloat16& r2, const vfloat16& r3, + const vfloat16& r4, const vfloat16& r5, const vfloat16& r6, const vfloat16& r7, + vfloat16& c0, vfloat16& c1, vfloat16& c2, vfloat16& c3, + vfloat16& c4, vfloat16& c5, vfloat16& c6, vfloat16& c7) + { + vfloat16 a0a1a2a3_e0e1e2e3, b0b1b2b3_f0f1f2f3, c0c1c2c3_g0g1g2g3, d0d1d2d3_h0h1h2h3; + transpose(r0, r1, r2, r3, a0a1a2a3_e0e1e2e3, b0b1b2b3_f0f1f2f3, c0c1c2c3_g0g1g2g3, d0d1d2d3_h0h1h2h3); + + vfloat16 a4a5a6a7_e4e5e6e7, b4b5b6b7_f4f5f6f7, c4c5c6c7_g4g5g6g7, d4d5d6d7_h4h5h6h7; + transpose(r4, r5, r6, r7, a4a5a6a7_e4e5e6e7, b4b5b6b7_f4f5f6f7, c4c5c6c7_g4g5g6g7, d4d5d6d7_h4h5h6h7); + + c0 = interleave4_even(a0a1a2a3_e0e1e2e3, a4a5a6a7_e4e5e6e7); + c1 = interleave4_even(b0b1b2b3_f0f1f2f3, b4b5b6b7_f4f5f6f7); + c2 = interleave4_even(c0c1c2c3_g0g1g2g3, c4c5c6c7_g4g5g6g7); + c3 = interleave4_even(d0d1d2d3_h0h1h2h3, d4d5d6d7_h4h5h6h7); + c4 = interleave4_odd (a0a1a2a3_e0e1e2e3, a4a5a6a7_e4e5e6e7); + c5 = interleave4_odd (b0b1b2b3_f0f1f2f3, b4b5b6b7_f4f5f6f7); + c6 = interleave4_odd (c0c1c2c3_g0g1g2g3, c4c5c6c7_g4g5g6g7); + c7 = interleave4_odd (d0d1d2d3_h0h1h2h3, d4d5d6d7_h4h5h6h7); + } + + __forceinline void transpose(const vfloat8& r0, const vfloat8& r1, const vfloat8& r2, const vfloat8& r3, + const vfloat8& r4, const vfloat8& r5, const vfloat8& r6, const vfloat8& r7, + const vfloat8& r8, const vfloat8& r9, const vfloat8& r10, const vfloat8& r11, + const vfloat8& r12, const vfloat8& r13, const vfloat8& r14, const vfloat8& r15, + vfloat16& c0, vfloat16& c1, vfloat16& c2, vfloat16& c3, + vfloat16& c4, vfloat16& c5, vfloat16& c6, vfloat16& c7) + { + return transpose(vfloat16(r0, r8), vfloat16(r1, r9), vfloat16(r2, r10), vfloat16(r3, r11), + vfloat16(r4, r12), vfloat16(r5, r13), vfloat16(r6, r14), vfloat16(r7, r15), + c0, c1, c2, c3, c4, c5, c6, c7); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 vreduce_add2(vfloat16 x) { return x + shuffle<1,0,3,2>(x); } + __forceinline vfloat16 vreduce_add4(vfloat16 x) { x = vreduce_add2(x); return x + shuffle<2,3,0,1>(x); } + __forceinline vfloat16 vreduce_add8(vfloat16 x) { x = vreduce_add4(x); return x + shuffle4<1,0,3,2>(x); } + __forceinline vfloat16 vreduce_add (vfloat16 x) { x = vreduce_add8(x); return x + shuffle4<2,3,0,1>(x); } + + __forceinline vfloat16 vreduce_min2(vfloat16 x) { return min(x, shuffle<1,0,3,2>(x)); } + __forceinline vfloat16 vreduce_min4(vfloat16 x) { x = vreduce_min2(x); return min(x, shuffle<2,3,0,1>(x)); } + __forceinline vfloat16 vreduce_min8(vfloat16 x) { x = vreduce_min4(x); return min(x, shuffle4<1,0,3,2>(x)); } + __forceinline vfloat16 vreduce_min (vfloat16 x) { x = vreduce_min8(x); return min(x, shuffle4<2,3,0,1>(x)); } + + __forceinline vfloat16 vreduce_max2(vfloat16 x) { return max(x, shuffle<1,0,3,2>(x)); } + __forceinline vfloat16 vreduce_max4(vfloat16 x) { x = vreduce_max2(x); return max(x, shuffle<2,3,0,1>(x)); } + __forceinline vfloat16 vreduce_max8(vfloat16 x) { x = vreduce_max4(x); return max(x, shuffle4<1,0,3,2>(x)); } + __forceinline vfloat16 vreduce_max (vfloat16 x) { x = vreduce_max8(x); return max(x, shuffle4<2,3,0,1>(x)); } + + __forceinline float reduce_add(const vfloat16& v) { return toScalar(vreduce_add(v)); } + __forceinline float reduce_min(const vfloat16& v) { return toScalar(vreduce_min(v)); } + __forceinline float reduce_max(const vfloat16& v) { return toScalar(vreduce_max(v)); } + + __forceinline size_t select_min(const vfloat16& v) { + return bsf(_mm512_kmov(_mm512_cmp_epi32_mask(_mm512_castps_si512(v),_mm512_castps_si512(vreduce_min(v)),_MM_CMPINT_EQ))); + } + + __forceinline size_t select_max(const vfloat16& v) { + return bsf(_mm512_kmov(_mm512_cmp_epi32_mask(_mm512_castps_si512(v),_mm512_castps_si512(vreduce_max(v)),_MM_CMPINT_EQ))); + } + + __forceinline size_t select_min(const vboolf16& valid, const vfloat16& v) + { + const vfloat16 a = select(valid,v,vfloat16(pos_inf)); + const vbool16 valid_min = valid & (a == vreduce_min(a)); + return bsf(movemask(any(valid_min) ? valid_min : valid)); + } + + __forceinline size_t select_max(const vboolf16& valid, const vfloat16& v) + { + const vfloat16 a = select(valid,v,vfloat16(neg_inf)); + const vbool16 valid_max = valid & (a == vreduce_max(a)); + return bsf(movemask(any(valid_max) ? valid_max : valid)); + } + + __forceinline vfloat16 prefix_sum(const vfloat16& a) + { + const vfloat16 z(zero); + vfloat16 v = a; + v = v + align_shift_right<16-1>(v,z); + v = v + align_shift_right<16-2>(v,z); + v = v + align_shift_right<16-4>(v,z); + v = v + align_shift_right<16-8>(v,z); + return v; + } + + __forceinline vfloat16 reverse_prefix_sum(const vfloat16& a) + { + const vfloat16 z(zero); + vfloat16 v = a; + v = v + align_shift_right<1>(z,v); + v = v + align_shift_right<2>(z,v); + v = v + align_shift_right<4>(z,v); + v = v + align_shift_right<8>(z,v); + return v; + } + + __forceinline vfloat16 prefix_min(const vfloat16& a) + { + const vfloat16 z(pos_inf); + vfloat16 v = a; + v = min(v,align_shift_right<16-1>(v,z)); + v = min(v,align_shift_right<16-2>(v,z)); + v = min(v,align_shift_right<16-4>(v,z)); + v = min(v,align_shift_right<16-8>(v,z)); + return v; + } + + __forceinline vfloat16 prefix_max(const vfloat16& a) + { + const vfloat16 z(neg_inf); + vfloat16 v = a; + v = max(v,align_shift_right<16-1>(v,z)); + v = max(v,align_shift_right<16-2>(v,z)); + v = max(v,align_shift_right<16-4>(v,z)); + v = max(v,align_shift_right<16-8>(v,z)); + return v; + } + + + __forceinline vfloat16 reverse_prefix_min(const vfloat16& a) + { + const vfloat16 z(pos_inf); + vfloat16 v = a; + v = min(v,align_shift_right<1>(z,v)); + v = min(v,align_shift_right<2>(z,v)); + v = min(v,align_shift_right<4>(z,v)); + v = min(v,align_shift_right<8>(z,v)); + return v; + } + + __forceinline vfloat16 reverse_prefix_max(const vfloat16& a) + { + const vfloat16 z(neg_inf); + vfloat16 v = a; + v = max(v,align_shift_right<1>(z,v)); + v = max(v,align_shift_right<2>(z,v)); + v = max(v,align_shift_right<4>(z,v)); + v = max(v,align_shift_right<8>(z,v)); + return v; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Memory load and store operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat16 loadAOS4to16f(const float& x, const float& y, const float& z) + { + vfloat16 f = zero; + f = select(0x1111,vfloat16::broadcast(&x),f); + f = select(0x2222,vfloat16::broadcast(&y),f); + f = select(0x4444,vfloat16::broadcast(&z),f); + return f; + } + + __forceinline vfloat16 loadAOS4to16f(unsigned int index, + const vfloat16& x, + const vfloat16& y, + const vfloat16& z) + { + vfloat16 f = zero; + f = select(0x1111,vfloat16::broadcast((float*)&x + index),f); + f = select(0x2222,vfloat16::broadcast((float*)&y + index),f); + f = select(0x4444,vfloat16::broadcast((float*)&z + index),f); + return f; + } + + __forceinline vfloat16 loadAOS4to16f(unsigned int index, + const vfloat16& x, + const vfloat16& y, + const vfloat16& z, + const vfloat16& fill) + { + vfloat16 f = fill; + f = select(0x1111,vfloat16::broadcast((float*)&x + index),f); + f = select(0x2222,vfloat16::broadcast((float*)&y + index),f); + f = select(0x4444,vfloat16::broadcast((float*)&z + index),f); + return f; + } + + __forceinline vfloat16 rcp_safe(const vfloat16& a) { + return rcp(select(a != vfloat16(zero), a, vfloat16(min_rcp_input))); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vfloat16& v) + { + cout << "<" << v[0]; + for (int i=1; i<16; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vfloat4_sse2.h b/thirdparty/embree/common/simd/vfloat4_sse2.h new file mode 100644 index 000000000000..96f984cebd5e --- /dev/null +++ b/thirdparty/embree/common/simd/vfloat4_sse2.h @@ -0,0 +1,708 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide SSE float type */ + template<> + struct vfloat<4> + { + ALIGNED_STRUCT_(16); + + typedef vboolf4 Bool; + typedef vint4 Int; + typedef vfloat4 Float; + + enum { size = 4 }; // number of SIMD elements + union { __m128 v; float f[4]; int i[4]; }; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat() {} + __forceinline vfloat(const vfloat4& other) { v = other.v; } + __forceinline vfloat4& operator =(const vfloat4& other) { v = other.v; return *this; } + + __forceinline vfloat(__m128 a) : v(a) {} + __forceinline operator const __m128&() const { return v; } + __forceinline operator __m128&() { return v; } + + __forceinline vfloat(float a) : v(_mm_set1_ps(a)) {} + __forceinline vfloat(float a, float b, float c, float d) : v(_mm_set_ps(d, c, b, a)) {} + + __forceinline explicit vfloat(const vint4& a) : v(_mm_cvtepi32_ps(a)) {} + __forceinline explicit vfloat(const vuint4& x) { + const __m128i a = _mm_and_si128(x,_mm_set1_epi32(0x7FFFFFFF)); + const __m128i b = _mm_and_si128(_mm_srai_epi32(x,31),_mm_set1_epi32(0x4F000000)); //0x4F000000 = 2^31 + const __m128 af = _mm_cvtepi32_ps(a); + const __m128 bf = _mm_castsi128_ps(b); + v = _mm_add_ps(af,bf); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat(ZeroTy) : v(_mm_setzero_ps()) {} + __forceinline vfloat(OneTy) : v(_mm_set1_ps(1.0f)) {} + __forceinline vfloat(PosInfTy) : v(_mm_set1_ps(pos_inf)) {} + __forceinline vfloat(NegInfTy) : v(_mm_set1_ps(neg_inf)) {} + __forceinline vfloat(StepTy) : v(_mm_set_ps(3.0f, 2.0f, 1.0f, 0.0f)) {} + __forceinline vfloat(NaNTy) : v(_mm_set1_ps(nan)) {} + __forceinline vfloat(UndefinedTy) : v(_mm_undefined_ps()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vfloat4 load (const void* a) { return _mm_load_ps((float*)a); } + static __forceinline vfloat4 loadu(const void* a) { return _mm_loadu_ps((float*)a); } + + static __forceinline void store (void* ptr, const vfloat4& v) { _mm_store_ps((float*)ptr,v); } + static __forceinline void storeu(void* ptr, const vfloat4& v) { _mm_storeu_ps((float*)ptr,v); } + +#if defined(__AVX512VL__) + + static __forceinline vfloat4 compact(const vboolf4& mask, vfloat4 &v) { + return _mm_mask_compress_ps(v, mask, v); + } + static __forceinline vfloat4 compact(const vboolf4& mask, vfloat4 &a, const vfloat4& b) { + return _mm_mask_compress_ps(a, mask, b); + } + + static __forceinline vfloat4 load (const vboolf4& mask, const void* ptr) { return _mm_mask_load_ps (_mm_setzero_ps(),mask,(float*)ptr); } + static __forceinline vfloat4 loadu(const vboolf4& mask, const void* ptr) { return _mm_mask_loadu_ps(_mm_setzero_ps(),mask,(float*)ptr); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vfloat4& v) { _mm_mask_store_ps ((float*)ptr,mask,v); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vfloat4& v) { _mm_mask_storeu_ps((float*)ptr,mask,v); } +#elif defined(__AVX__) + static __forceinline vfloat4 load (const vboolf4& mask, const void* ptr) { return _mm_maskload_ps((float*)ptr,mask); } + static __forceinline vfloat4 loadu(const vboolf4& mask, const void* ptr) { return _mm_maskload_ps((float*)ptr,mask); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vfloat4& v) { _mm_maskstore_ps((float*)ptr,(__m128i)mask,v); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vfloat4& v) { _mm_maskstore_ps((float*)ptr,(__m128i)mask,v); } +#else + static __forceinline vfloat4 load (const vboolf4& mask, const void* ptr) { return _mm_and_ps(_mm_load_ps ((float*)ptr),mask); } + static __forceinline vfloat4 loadu(const vboolf4& mask, const void* ptr) { return _mm_and_ps(_mm_loadu_ps((float*)ptr),mask); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vfloat4& v) { store (ptr,select(mask,v,load (ptr))); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vfloat4& v) { storeu(ptr,select(mask,v,loadu(ptr))); } +#endif + +#if defined(__AVX__) + static __forceinline vfloat4 broadcast(const void* a) { return _mm_broadcast_ss((float*)a); } +#else + static __forceinline vfloat4 broadcast(const void* a) { return _mm_set1_ps(*(float*)a); } +#endif + + static __forceinline vfloat4 load_nt (const float* ptr) { +#if defined (__SSE4_1__) + return _mm_castsi128_ps(_mm_stream_load_si128((__m128i*)ptr)); +#else + return _mm_load_ps(ptr); +#endif + } + +#if defined(__SSE4_1__) + static __forceinline vfloat4 load(const char* ptr) { + return _mm_cvtepi32_ps(_mm_cvtepi8_epi32(_mm_loadu_si128((__m128i*)ptr))); + } +#else + static __forceinline vfloat4 load(const char* ptr) { + return vfloat4(ptr[0],ptr[1],ptr[2],ptr[3]); + } +#endif + +#if defined(__SSE4_1__) + static __forceinline vfloat4 load(const unsigned char* ptr) { + return _mm_cvtepi32_ps(_mm_cvtepu8_epi32(_mm_loadu_si128((__m128i*)ptr))); + } +#else + static __forceinline vfloat4 load(const unsigned char* ptr) { + //return _mm_cvtpu8_ps(*(__m64*)ptr); // don't enable, will use MMX instructions + return vfloat4(ptr[0],ptr[1],ptr[2],ptr[3]); + } +#endif + +#if defined(__SSE4_1__) + static __forceinline vfloat4 load(const short* ptr) { + return _mm_cvtepi32_ps(_mm_cvtepi16_epi32(_mm_loadu_si128((__m128i*)ptr))); + } +#else + static __forceinline vfloat4 load(const short* ptr) { + return vfloat4(ptr[0],ptr[1],ptr[2],ptr[3]); + } +#endif + + static __forceinline vfloat4 load(const unsigned short* ptr) { + return _mm_mul_ps(vfloat4(vint4::load(ptr)),vfloat4(1.0f/65535.0f)); + } + + static __forceinline void store_nt(void* ptr, const vfloat4& v) + { +#if defined (__SSE4_1__) + _mm_stream_ps((float*)ptr,v); +#else + _mm_store_ps((float*)ptr,v); +#endif + } + + template + static __forceinline vfloat4 gather(const float* ptr, const vint4& index) { +#if defined(__AVX2__) + return _mm_i32gather_ps(ptr, index, scale); +#else + return vfloat4( + *(float*)(((char*)ptr)+scale*index[0]), + *(float*)(((char*)ptr)+scale*index[1]), + *(float*)(((char*)ptr)+scale*index[2]), + *(float*)(((char*)ptr)+scale*index[3])); +#endif + } + + template + static __forceinline vfloat4 gather(const vboolf4& mask, const float* ptr, const vint4& index) { + vfloat4 r = zero; +#if defined(__AVX512VL__) + return _mm_mmask_i32gather_ps(r, mask, index, ptr, scale); +#elif defined(__AVX2__) + return _mm_mask_i32gather_ps(r, ptr, index, mask, scale); +#else + if (likely(mask[0])) r[0] = *(float*)(((char*)ptr)+scale*index[0]); + if (likely(mask[1])) r[1] = *(float*)(((char*)ptr)+scale*index[1]); + if (likely(mask[2])) r[2] = *(float*)(((char*)ptr)+scale*index[2]); + if (likely(mask[3])) r[3] = *(float*)(((char*)ptr)+scale*index[3]); + return r; +#endif + } + + template + static __forceinline void scatter(void* ptr, const vint4& index, const vfloat4& v) + { +#if defined(__AVX512VL__) + _mm_i32scatter_ps((float*)ptr, index, v, scale); +#else + *(float*)(((char*)ptr)+scale*index[0]) = v[0]; + *(float*)(((char*)ptr)+scale*index[1]) = v[1]; + *(float*)(((char*)ptr)+scale*index[2]) = v[2]; + *(float*)(((char*)ptr)+scale*index[3]) = v[3]; +#endif + } + + template + static __forceinline void scatter(const vboolf4& mask, void* ptr, const vint4& index, const vfloat4& v) + { +#if defined(__AVX512VL__) + _mm_mask_i32scatter_ps((float*)ptr ,mask, index, v, scale); +#else + if (likely(mask[0])) *(float*)(((char*)ptr)+scale*index[0]) = v[0]; + if (likely(mask[1])) *(float*)(((char*)ptr)+scale*index[1]) = v[1]; + if (likely(mask[2])) *(float*)(((char*)ptr)+scale*index[2]) = v[2]; + if (likely(mask[3])) *(float*)(((char*)ptr)+scale*index[3]) = v[3]; +#endif + } + + static __forceinline void store(const vboolf4& mask, char* ptr, const vint4& ofs, const vfloat4& v) { + scatter<1>(mask,ptr,ofs,v); + } + static __forceinline void store(const vboolf4& mask, float* ptr, const vint4& ofs, const vfloat4& v) { + scatter<4>(mask,ptr,ofs,v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const float& operator [](size_t index) const { assert(index < 4); return f[index]; } + __forceinline float& operator [](size_t index) { assert(index < 4); return f[index]; } + + friend __forceinline vfloat4 select(const vboolf4& m, const vfloat4& t, const vfloat4& f) { +#if defined(__AVX512VL__) + return _mm_mask_blend_ps(m, f, t); +#elif defined(__SSE4_1__) + return _mm_blendv_ps(f, t, m); +#else + return _mm_or_ps(_mm_and_ps(m, t), _mm_andnot_ps(m, f)); +#endif + } + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat4 asFloat(const vint4& a) { return _mm_castsi128_ps(a); } + __forceinline vint4 asInt (const vfloat4& a) { return _mm_castps_si128(a); } + __forceinline vuint4 asUInt (const vfloat4& a) { return _mm_castps_si128(a); } + + __forceinline vint4 toInt (const vfloat4& a) { return vint4(a); } + __forceinline vfloat4 toFloat(const vint4& a) { return vfloat4(a); } + + __forceinline vfloat4 operator +(const vfloat4& a) { return a; } + __forceinline vfloat4 operator -(const vfloat4& a) { return _mm_xor_ps(a, _mm_castsi128_ps(_mm_set1_epi32(0x80000000))); } + + __forceinline vfloat4 abs(const vfloat4& a) { return _mm_and_ps(a, _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff))); } +#if defined(__AVX512VL__) + __forceinline vfloat4 sign(const vfloat4& a) { return _mm_mask_blend_ps(_mm_cmp_ps_mask(a, vfloat4(zero), _CMP_LT_OQ), vfloat4(one), -vfloat4(one)); } +#else + __forceinline vfloat4 sign(const vfloat4& a) { return blendv_ps(vfloat4(one), -vfloat4(one), _mm_cmplt_ps(a, vfloat4(zero))); } +#endif + __forceinline vfloat4 signmsk(const vfloat4& a) { return _mm_and_ps(a,_mm_castsi128_ps(_mm_set1_epi32(0x80000000))); } + + __forceinline vfloat4 rcp(const vfloat4& a) + { +#if defined(__AVX512VL__) + const vfloat4 r = _mm_rcp14_ps(a); +#else + const vfloat4 r = _mm_rcp_ps(a); +#endif + +#if defined(__AVX2__) + return _mm_mul_ps(r,_mm_fnmadd_ps(r, a, vfloat4(2.0f))); +#else + return _mm_mul_ps(r,_mm_sub_ps(vfloat4(2.0f), _mm_mul_ps(r, a))); +#endif + } + __forceinline vfloat4 sqr (const vfloat4& a) { return _mm_mul_ps(a,a); } + __forceinline vfloat4 sqrt(const vfloat4& a) { return _mm_sqrt_ps(a); } + + __forceinline vfloat4 rsqrt(const vfloat4& a) + { +#if defined(__AVX512VL__) + const vfloat4 r = _mm_rsqrt14_ps(a); +#else + const vfloat4 r = _mm_rsqrt_ps(a); +#endif + +#if defined(__AVX2__) + return _mm_fmadd_ps(_mm_set1_ps(1.5f), r, + _mm_mul_ps(_mm_mul_ps(_mm_mul_ps(a, _mm_set1_ps(-0.5f)), r), _mm_mul_ps(r, r))); +#else + return _mm_add_ps(_mm_mul_ps(_mm_set1_ps(1.5f), r), + _mm_mul_ps(_mm_mul_ps(_mm_mul_ps(a, _mm_set1_ps(-0.5f)), r), _mm_mul_ps(r, r))); +#endif + } + + __forceinline vboolf4 isnan(const vfloat4& a) { + const vfloat4 b = _mm_and_ps(a, _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff))); +#if defined(__AVX512VL__) + return _mm_cmp_epi32_mask(_mm_castps_si128(b), _mm_set1_epi32(0x7f800000), _MM_CMPINT_GT); +#else + return _mm_castsi128_ps(_mm_cmpgt_epi32(_mm_castps_si128(b), _mm_set1_epi32(0x7f800000))); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat4 operator +(const vfloat4& a, const vfloat4& b) { return _mm_add_ps(a, b); } + __forceinline vfloat4 operator +(const vfloat4& a, float b) { return a + vfloat4(b); } + __forceinline vfloat4 operator +(float a, const vfloat4& b) { return vfloat4(a) + b; } + + __forceinline vfloat4 operator -(const vfloat4& a, const vfloat4& b) { return _mm_sub_ps(a, b); } + __forceinline vfloat4 operator -(const vfloat4& a, float b) { return a - vfloat4(b); } + __forceinline vfloat4 operator -(float a, const vfloat4& b) { return vfloat4(a) - b; } + + __forceinline vfloat4 operator *(const vfloat4& a, const vfloat4& b) { return _mm_mul_ps(a, b); } + __forceinline vfloat4 operator *(const vfloat4& a, float b) { return a * vfloat4(b); } + __forceinline vfloat4 operator *(float a, const vfloat4& b) { return vfloat4(a) * b; } + + __forceinline vfloat4 operator /(const vfloat4& a, const vfloat4& b) { return _mm_div_ps(a,b); } + __forceinline vfloat4 operator /(const vfloat4& a, float b) { return a/vfloat4(b); } + __forceinline vfloat4 operator /(float a, const vfloat4& b) { return vfloat4(a)/b; } + + __forceinline vfloat4 operator &(const vfloat4& a, const vfloat4& b) { return _mm_and_ps(a,b); } + __forceinline vfloat4 operator |(const vfloat4& a, const vfloat4& b) { return _mm_or_ps(a,b); } + __forceinline vfloat4 operator ^(const vfloat4& a, const vfloat4& b) { return _mm_xor_ps(a,b); } + __forceinline vfloat4 operator ^(const vfloat4& a, const vint4& b) { return _mm_xor_ps(a,_mm_castsi128_ps(b)); } + + __forceinline vfloat4 min(const vfloat4& a, const vfloat4& b) { return _mm_min_ps(a,b); } + __forceinline vfloat4 min(const vfloat4& a, float b) { return _mm_min_ps(a,vfloat4(b)); } + __forceinline vfloat4 min(float a, const vfloat4& b) { return _mm_min_ps(vfloat4(a),b); } + + __forceinline vfloat4 max(const vfloat4& a, const vfloat4& b) { return _mm_max_ps(a,b); } + __forceinline vfloat4 max(const vfloat4& a, float b) { return _mm_max_ps(a,vfloat4(b)); } + __forceinline vfloat4 max(float a, const vfloat4& b) { return _mm_max_ps(vfloat4(a),b); } + +#if defined(__SSE4_1__) + __forceinline vfloat4 mini(const vfloat4& a, const vfloat4& b) { + const vint4 ai = _mm_castps_si128(a); + const vint4 bi = _mm_castps_si128(b); + const vint4 ci = _mm_min_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } + + __forceinline vfloat4 maxi(const vfloat4& a, const vfloat4& b) { + const vint4 ai = _mm_castps_si128(a); + const vint4 bi = _mm_castps_si128(b); + const vint4 ci = _mm_max_epi32(ai,bi); + return _mm_castsi128_ps(ci); + } + + __forceinline vfloat4 minui(const vfloat4& a, const vfloat4& b) { + const vint4 ai = _mm_castps_si128(a); + const vint4 bi = _mm_castps_si128(b); + const vint4 ci = _mm_min_epu32(ai,bi); + return _mm_castsi128_ps(ci); + } + + __forceinline vfloat4 maxui(const vfloat4& a, const vfloat4& b) { + const vint4 ai = _mm_castps_si128(a); + const vint4 bi = _mm_castps_si128(b); + const vint4 ci = _mm_max_epu32(ai,bi); + return _mm_castsi128_ps(ci); + } +#else + __forceinline vfloat4 mini(const vfloat4& a, const vfloat4& b) { + return min(a,b); + } + + __forceinline vfloat4 maxi(const vfloat4& a, const vfloat4& b) { + return max(a,b); + } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX2__) + __forceinline vfloat4 madd (const vfloat4& a, const vfloat4& b, const vfloat4& c) { return _mm_fmadd_ps(a,b,c); } + __forceinline vfloat4 msub (const vfloat4& a, const vfloat4& b, const vfloat4& c) { return _mm_fmsub_ps(a,b,c); } + __forceinline vfloat4 nmadd(const vfloat4& a, const vfloat4& b, const vfloat4& c) { return _mm_fnmadd_ps(a,b,c); } + __forceinline vfloat4 nmsub(const vfloat4& a, const vfloat4& b, const vfloat4& c) { return _mm_fnmsub_ps(a,b,c); } +#else + __forceinline vfloat4 madd (const vfloat4& a, const vfloat4& b, const vfloat4& c) { return a*b+c; } + __forceinline vfloat4 msub (const vfloat4& a, const vfloat4& b, const vfloat4& c) { return a*b-c; } + __forceinline vfloat4 nmadd(const vfloat4& a, const vfloat4& b, const vfloat4& c) { return -a*b+c;} + __forceinline vfloat4 nmsub(const vfloat4& a, const vfloat4& b, const vfloat4& c) { return -a*b-c; } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat4& operator +=(vfloat4& a, const vfloat4& b) { return a = a + b; } + __forceinline vfloat4& operator +=(vfloat4& a, float b) { return a = a + b; } + + __forceinline vfloat4& operator -=(vfloat4& a, const vfloat4& b) { return a = a - b; } + __forceinline vfloat4& operator -=(vfloat4& a, float b) { return a = a - b; } + + __forceinline vfloat4& operator *=(vfloat4& a, const vfloat4& b) { return a = a * b; } + __forceinline vfloat4& operator *=(vfloat4& a, float b) { return a = a * b; } + + __forceinline vfloat4& operator /=(vfloat4& a, const vfloat4& b) { return a = a / b; } + __forceinline vfloat4& operator /=(vfloat4& a, float b) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf4 operator ==(const vfloat4& a, const vfloat4& b) { return _mm_cmp_ps_mask(a, b, _MM_CMPINT_EQ); } + __forceinline vboolf4 operator !=(const vfloat4& a, const vfloat4& b) { return _mm_cmp_ps_mask(a, b, _MM_CMPINT_NE); } + __forceinline vboolf4 operator < (const vfloat4& a, const vfloat4& b) { return _mm_cmp_ps_mask(a, b, _MM_CMPINT_LT); } + __forceinline vboolf4 operator >=(const vfloat4& a, const vfloat4& b) { return _mm_cmp_ps_mask(a, b, _MM_CMPINT_GE); } + __forceinline vboolf4 operator > (const vfloat4& a, const vfloat4& b) { return _mm_cmp_ps_mask(a, b, _MM_CMPINT_GT); } + __forceinline vboolf4 operator <=(const vfloat4& a, const vfloat4& b) { return _mm_cmp_ps_mask(a, b, _MM_CMPINT_LE); } +#else + __forceinline vboolf4 operator ==(const vfloat4& a, const vfloat4& b) { return _mm_cmpeq_ps (a, b); } + __forceinline vboolf4 operator !=(const vfloat4& a, const vfloat4& b) { return _mm_cmpneq_ps(a, b); } + __forceinline vboolf4 operator < (const vfloat4& a, const vfloat4& b) { return _mm_cmplt_ps (a, b); } + __forceinline vboolf4 operator >=(const vfloat4& a, const vfloat4& b) { return _mm_cmpnlt_ps(a, b); } + __forceinline vboolf4 operator > (const vfloat4& a, const vfloat4& b) { return _mm_cmpnle_ps(a, b); } + __forceinline vboolf4 operator <=(const vfloat4& a, const vfloat4& b) { return _mm_cmple_ps (a, b); } +#endif + + __forceinline vboolf4 operator ==(const vfloat4& a, float b) { return a == vfloat4(b); } + __forceinline vboolf4 operator ==(float a, const vfloat4& b) { return vfloat4(a) == b; } + + __forceinline vboolf4 operator !=(const vfloat4& a, float b) { return a != vfloat4(b); } + __forceinline vboolf4 operator !=(float a, const vfloat4& b) { return vfloat4(a) != b; } + + __forceinline vboolf4 operator < (const vfloat4& a, float b) { return a < vfloat4(b); } + __forceinline vboolf4 operator < (float a, const vfloat4& b) { return vfloat4(a) < b; } + + __forceinline vboolf4 operator >=(const vfloat4& a, float b) { return a >= vfloat4(b); } + __forceinline vboolf4 operator >=(float a, const vfloat4& b) { return vfloat4(a) >= b; } + + __forceinline vboolf4 operator > (const vfloat4& a, float b) { return a > vfloat4(b); } + __forceinline vboolf4 operator > (float a, const vfloat4& b) { return vfloat4(a) > b; } + + __forceinline vboolf4 operator <=(const vfloat4& a, float b) { return a <= vfloat4(b); } + __forceinline vboolf4 operator <=(float a, const vfloat4& b) { return vfloat4(a) <= b; } + + __forceinline vboolf4 eq(const vfloat4& a, const vfloat4& b) { return a == b; } + __forceinline vboolf4 ne(const vfloat4& a, const vfloat4& b) { return a != b; } + __forceinline vboolf4 lt(const vfloat4& a, const vfloat4& b) { return a < b; } + __forceinline vboolf4 ge(const vfloat4& a, const vfloat4& b) { return a >= b; } + __forceinline vboolf4 gt(const vfloat4& a, const vfloat4& b) { return a > b; } + __forceinline vboolf4 le(const vfloat4& a, const vfloat4& b) { return a <= b; } + +#if defined(__AVX512VL__) + __forceinline vboolf4 eq(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return _mm_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_EQ); } + __forceinline vboolf4 ne(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return _mm_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_NE); } + __forceinline vboolf4 lt(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return _mm_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_LT); } + __forceinline vboolf4 ge(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return _mm_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_GE); } + __forceinline vboolf4 gt(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return _mm_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_GT); } + __forceinline vboolf4 le(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return _mm_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_LE); } +#else + __forceinline vboolf4 eq(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return mask & (a == b); } + __forceinline vboolf4 ne(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return mask & (a != b); } + __forceinline vboolf4 lt(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return mask & (a < b); } + __forceinline vboolf4 ge(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return mask & (a >= b); } + __forceinline vboolf4 gt(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return mask & (a > b); } + __forceinline vboolf4 le(const vboolf4& mask, const vfloat4& a, const vfloat4& b) { return mask & (a <= b); } +#endif + + template + __forceinline vfloat4 select(const vfloat4& t, const vfloat4& f) + { +#if defined(__SSE4_1__) + return _mm_blend_ps(f, t, mask); +#else + return select(vboolf4(mask), t, f); +#endif + } + + __forceinline vfloat4 lerp(const vfloat4& a, const vfloat4& b, const vfloat4& t) { + return madd(t,b-a,a); + } + + __forceinline bool isvalid(const vfloat4& v) { + return all((v > vfloat4(-FLT_LARGE)) & (v < vfloat4(+FLT_LARGE))); + } + + __forceinline bool is_finite(const vfloat4& a) { + return all((a >= vfloat4(-FLT_MAX)) & (a <= vfloat4(+FLT_MAX))); + } + + __forceinline bool is_finite(const vboolf4& valid, const vfloat4& a) { + return all(valid, (a >= vfloat4(-FLT_MAX)) & (a <= vfloat4(+FLT_MAX))); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Rounding Functions + //////////////////////////////////////////////////////////////////////////////// + +#if defined (__SSE4_1__) + __forceinline vfloat4 floor(const vfloat4& a) { return _mm_round_ps(a, _MM_FROUND_TO_NEG_INF ); } + __forceinline vfloat4 ceil (const vfloat4& a) { return _mm_round_ps(a, _MM_FROUND_TO_POS_INF ); } + __forceinline vfloat4 trunc(const vfloat4& a) { return _mm_round_ps(a, _MM_FROUND_TO_ZERO ); } + __forceinline vfloat4 round(const vfloat4& a) { return _mm_round_ps(a, _MM_FROUND_TO_NEAREST_INT); } +#else + __forceinline vfloat4 floor(const vfloat4& a) { return vfloat4(floorf(a[0]),floorf(a[1]),floorf(a[2]),floorf(a[3])); } + __forceinline vfloat4 ceil (const vfloat4& a) { return vfloat4(ceilf (a[0]),ceilf (a[1]),ceilf (a[2]),ceilf (a[3])); } + __forceinline vfloat4 trunc(const vfloat4& a) { return vfloat4(truncf(a[0]),truncf(a[1]),truncf(a[2]),truncf(a[3])); } + __forceinline vfloat4 round(const vfloat4& a) { return vfloat4(roundf(a[0]),roundf(a[1]),roundf(a[2]),roundf(a[3])); } +#endif + __forceinline vfloat4 frac(const vfloat4& a) { return a-floor(a); } + + __forceinline vint4 floori(const vfloat4& a) { +#if defined(__SSE4_1__) + return vint4(floor(a)); +#else + return vint4(a-vfloat4(0.5f)); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat4 unpacklo(const vfloat4& a, const vfloat4& b) { return _mm_unpacklo_ps(a, b); } + __forceinline vfloat4 unpackhi(const vfloat4& a, const vfloat4& b) { return _mm_unpackhi_ps(a, b); } + + template + __forceinline vfloat4 shuffle(const vfloat4& v) { + return _mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vfloat4 shuffle(const vfloat4& a, const vfloat4& b) { + return _mm_shuffle_ps(a, b, _MM_SHUFFLE(i3, i2, i1, i0)); + } + +#if defined (__SSSE3__) + __forceinline vfloat4 shuffle8(const vfloat4& a, const vint4& shuf) { + return _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(a), shuf)); + } +#endif + +#if defined(__SSE3__) + template<> __forceinline vfloat4 shuffle<0, 0, 2, 2>(const vfloat4& v) { return _mm_moveldup_ps(v); } + template<> __forceinline vfloat4 shuffle<1, 1, 3, 3>(const vfloat4& v) { return _mm_movehdup_ps(v); } + template<> __forceinline vfloat4 shuffle<0, 1, 0, 1>(const vfloat4& v) { return _mm_castpd_ps(_mm_movedup_pd(_mm_castps_pd(v))); } +#endif + + template + __forceinline vfloat4 shuffle(const vfloat4& v) { + return shuffle(v); + } + +#if defined (__SSE4_1__) && !defined(__GNUC__) + template __forceinline float extract(const vfloat4& a) { return _mm_cvtss_f32(_mm_extract_ps(a,i)); } +#else + template __forceinline float extract(const vfloat4& a) { return _mm_cvtss_f32(shuffle(a)); } +#endif + template<> __forceinline float extract<0>(const vfloat4& a) { return _mm_cvtss_f32(a); } + +#if defined (__SSE4_1__) + template __forceinline vfloat4 insert(const vfloat4& a, const vfloat4& b) { return _mm_insert_ps(a, b, (dst << 4) | (src << 6) | clr); } + template __forceinline vfloat4 insert(const vfloat4& a, const vfloat4& b) { return insert(a, b); } + template __forceinline vfloat4 insert(const vfloat4& a, const float b) { return insert(a, _mm_set_ss(b)); } +#else + template __forceinline vfloat4 insert(const vfloat4& a, const vfloat4& b) { vfloat4 c = a; c[dst&3] = b[src&3]; return c; } + template __forceinline vfloat4 insert(const vfloat4& a, float b) { vfloat4 c = a; c[dst&3] = b; return c; } +#endif + + __forceinline float toScalar(const vfloat4& v) { return _mm_cvtss_f32(v); } + + __forceinline vfloat4 broadcast4f(const vfloat4& a, size_t k) { + return vfloat4::broadcast(&a[k]); + } + + __forceinline vfloat4 shift_right_1(const vfloat4& x) { + return _mm_castsi128_ps(_mm_srli_si128(_mm_castps_si128(x), 4)); + } + +#if defined (__AVX2__) + __forceinline vfloat4 permute(const vfloat4 &a, const __m128i &index) { + return _mm_permutevar_ps(a,index); + } + + __forceinline vfloat4 broadcast1f(const void* a) { return _mm_broadcast_ss((float*)a); } + +#endif + +#if defined(__AVX512VL__) + template + __forceinline vfloat4 align_shift_right(const vfloat4& a, const vfloat4& b) { + return _mm_castsi128_ps(_mm_alignr_epi32(_mm_castps_si128(a), _mm_castps_si128(b), i)); + } +#endif + + + //////////////////////////////////////////////////////////////////////////////// + /// Sorting Network + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat4 sort_ascending(const vfloat4& v) + { + const vfloat4 a0 = v; + const vfloat4 b0 = shuffle<1,0,3,2>(a0); + const vfloat4 c0 = min(a0,b0); + const vfloat4 d0 = max(a0,b0); + const vfloat4 a1 = select<0x5 /* 0b0101 */>(c0,d0); + const vfloat4 b1 = shuffle<2,3,0,1>(a1); + const vfloat4 c1 = min(a1,b1); + const vfloat4 d1 = max(a1,b1); + const vfloat4 a2 = select<0x3 /* 0b0011 */>(c1,d1); + const vfloat4 b2 = shuffle<0,2,1,3>(a2); + const vfloat4 c2 = min(a2,b2); + const vfloat4 d2 = max(a2,b2); + const vfloat4 a3 = select<0x2 /* 0b0010 */>(c2,d2); + return a3; + } + + __forceinline vfloat4 sort_descending(const vfloat4& v) + { + const vfloat4 a0 = v; + const vfloat4 b0 = shuffle<1,0,3,2>(a0); + const vfloat4 c0 = max(a0,b0); + const vfloat4 d0 = min(a0,b0); + const vfloat4 a1 = select<0x5 /* 0b0101 */>(c0,d0); + const vfloat4 b1 = shuffle<2,3,0,1>(a1); + const vfloat4 c1 = max(a1,b1); + const vfloat4 d1 = min(a1,b1); + const vfloat4 a2 = select<0x3 /* 0b0011 */>(c1,d1); + const vfloat4 b2 = shuffle<0,2,1,3>(a2); + const vfloat4 c2 = max(a2,b2); + const vfloat4 d2 = min(a2,b2); + const vfloat4 a3 = select<0x2 /* 0b0010 */>(c2,d2); + return a3; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Transpose + //////////////////////////////////////////////////////////////////////////////// + + __forceinline void transpose(const vfloat4& r0, const vfloat4& r1, const vfloat4& r2, const vfloat4& r3, vfloat4& c0, vfloat4& c1, vfloat4& c2, vfloat4& c3) + { + vfloat4 l02 = unpacklo(r0,r2); + vfloat4 h02 = unpackhi(r0,r2); + vfloat4 l13 = unpacklo(r1,r3); + vfloat4 h13 = unpackhi(r1,r3); + c0 = unpacklo(l02,l13); + c1 = unpackhi(l02,l13); + c2 = unpacklo(h02,h13); + c3 = unpackhi(h02,h13); + } + + __forceinline void transpose(const vfloat4& r0, const vfloat4& r1, const vfloat4& r2, const vfloat4& r3, vfloat4& c0, vfloat4& c1, vfloat4& c2) + { + vfloat4 l02 = unpacklo(r0,r2); + vfloat4 h02 = unpackhi(r0,r2); + vfloat4 l13 = unpacklo(r1,r3); + vfloat4 h13 = unpackhi(r1,r3); + c0 = unpacklo(l02,l13); + c1 = unpackhi(l02,l13); + c2 = unpacklo(h02,h13); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat4 vreduce_min(const vfloat4& v) { vfloat4 h = min(shuffle<1,0,3,2>(v),v); return min(shuffle<2,3,0,1>(h),h); } + __forceinline vfloat4 vreduce_max(const vfloat4& v) { vfloat4 h = max(shuffle<1,0,3,2>(v),v); return max(shuffle<2,3,0,1>(h),h); } + __forceinline vfloat4 vreduce_add(const vfloat4& v) { vfloat4 h = shuffle<1,0,3,2>(v) + v ; return shuffle<2,3,0,1>(h) + h ; } + + __forceinline float reduce_min(const vfloat4& v) { return _mm_cvtss_f32(vreduce_min(v)); } + __forceinline float reduce_max(const vfloat4& v) { return _mm_cvtss_f32(vreduce_max(v)); } + __forceinline float reduce_add(const vfloat4& v) { return _mm_cvtss_f32(vreduce_add(v)); } + + __forceinline size_t select_min(const vboolf4& valid, const vfloat4& v) + { + const vfloat4 a = select(valid,v,vfloat4(pos_inf)); + const vbool4 valid_min = valid & (a == vreduce_min(a)); + return bsf(movemask(any(valid_min) ? valid_min : valid)); + } + __forceinline size_t select_max(const vboolf4& valid, const vfloat4& v) + { + const vfloat4 a = select(valid,v,vfloat4(neg_inf)); + const vbool4 valid_max = valid & (a == vreduce_max(a)); + return bsf(movemask(any(valid_max) ? valid_max : valid)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline float dot(const vfloat4& a, const vfloat4& b) { + return reduce_add(a*b); + } + + __forceinline vfloat4 cross(const vfloat4& a, const vfloat4& b) + { + const vfloat4 a0 = a; + const vfloat4 b0 = shuffle<1,2,0,3>(b); + const vfloat4 a1 = shuffle<1,2,0,3>(a); + const vfloat4 b1 = b; + return shuffle<1,2,0,3>(msub(a0,b0,a1*b1)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vfloat4& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ">"; + } + +} diff --git a/thirdparty/embree/common/simd/vfloat8_avx.h b/thirdparty/embree/common/simd/vfloat8_avx.h new file mode 100644 index 000000000000..09d7ccc71e96 --- /dev/null +++ b/thirdparty/embree/common/simd/vfloat8_avx.h @@ -0,0 +1,780 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX float type */ + template<> + struct vfloat<8> + { + ALIGNED_STRUCT_(32); + + typedef vboolf8 Bool; + typedef vint8 Int; + typedef vfloat8 Float; + + enum { size = 8 }; // number of SIMD elements + union { __m256 v; float f[8]; int i[8]; }; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat() {} + __forceinline vfloat(const vfloat8& other) { v = other.v; } + __forceinline vfloat8& operator =(const vfloat8& other) { v = other.v; return *this; } + + __forceinline vfloat(__m256 a) : v(a) {} + __forceinline operator const __m256&() const { return v; } + __forceinline operator __m256&() { return v; } + + __forceinline explicit vfloat(const vfloat4& a) : v(_mm256_insertf128_ps(_mm256_castps128_ps256(a),a,1)) {} + __forceinline vfloat(const vfloat4& a, const vfloat4& b) : v(_mm256_insertf128_ps(_mm256_castps128_ps256(a),b,1)) {} + + __forceinline explicit vfloat(const char* a) : v(_mm256_loadu_ps((const float*)a)) {} + __forceinline vfloat(float a) : v(_mm256_set1_ps(a)) {} + __forceinline vfloat(float a, float b) : v(_mm256_set_ps(b, a, b, a, b, a, b, a)) {} + __forceinline vfloat(float a, float b, float c, float d) : v(_mm256_set_ps(d, c, b, a, d, c, b, a)) {} + __forceinline vfloat(float a, float b, float c, float d, float e, float f, float g, float h) : v(_mm256_set_ps(h, g, f, e, d, c, b, a)) {} + + __forceinline explicit vfloat(__m256i a) : v(_mm256_cvtepi32_ps(a)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat(ZeroTy) : v(_mm256_setzero_ps()) {} + __forceinline vfloat(OneTy) : v(_mm256_set1_ps(1.0f)) {} + __forceinline vfloat(PosInfTy) : v(_mm256_set1_ps(pos_inf)) {} + __forceinline vfloat(NegInfTy) : v(_mm256_set1_ps(neg_inf)) {} + __forceinline vfloat(StepTy) : v(_mm256_set_ps(7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f, 0.0f)) {} + __forceinline vfloat(NaNTy) : v(_mm256_set1_ps(nan)) {} + __forceinline vfloat(UndefinedTy) : v(_mm256_undefined_ps()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vfloat8 broadcast(const void* a) { + return _mm256_broadcast_ss((float*)a); + } + + static __forceinline vfloat8 broadcast2(const float* a, const float* b) { +#if defined(__INTEL_COMPILER) + const vfloat8 v0 = _mm256_broadcast_ss(a); + const vfloat8 v1 = _mm256_broadcast_ss(b); + return _mm256_blend_ps(v1, v0, 0xf); +#else + return _mm256_set_ps(*b,*b,*b,*b,*a,*a,*a,*a); +#endif + } + + static __forceinline vfloat8 broadcast4f(const vfloat4* ptr) { + return _mm256_broadcast_ps((__m128*)ptr); + } + + static __forceinline vfloat8 load(const char* ptr) { +#if defined(__AVX2__) + return _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(_mm_loadu_si128((__m128i*)ptr))); +#else + return vfloat8(vfloat4::load(ptr),vfloat4::load(ptr+4)); +#endif + } + + static __forceinline vfloat8 load(const unsigned char* ptr) { +#if defined(__AVX2__) + return _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadu_si128((__m128i*)ptr))); +#else + return vfloat8(vfloat4::load(ptr),vfloat4::load(ptr+4)); +#endif + } + + static __forceinline vfloat8 load(const short* ptr) { +#if defined(__AVX2__) + return _mm256_cvtepi32_ps(_mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i*)ptr))); +#else + return vfloat8(vfloat4::load(ptr),vfloat4::load(ptr+4)); +#endif + } + + static __forceinline vfloat8 load (const void* ptr) { return _mm256_load_ps((float*)ptr); } + static __forceinline vfloat8 loadu(const void* ptr) { return _mm256_loadu_ps((float*)ptr); } + + static __forceinline void store (void* ptr, const vfloat8& v) { return _mm256_store_ps((float*)ptr,v); } + static __forceinline void storeu(void* ptr, const vfloat8& v) { return _mm256_storeu_ps((float*)ptr,v); } + +#if defined(__AVX512VL__) + + static __forceinline vfloat8 compact(const vboolf8& mask, vfloat8 &v) { + return _mm256_mask_compress_ps(v, mask, v); + } + static __forceinline vfloat8 compact(const vboolf8& mask, vfloat8 &a, const vfloat8& b) { + return _mm256_mask_compress_ps(a, mask, b); + } + + static __forceinline vfloat8 load (const vboolf8& mask, const void* ptr) { return _mm256_mask_load_ps (_mm256_setzero_ps(),mask,(float*)ptr); } + static __forceinline vfloat8 loadu(const vboolf8& mask, const void* ptr) { return _mm256_mask_loadu_ps(_mm256_setzero_ps(),mask,(float*)ptr); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vfloat8& v) { _mm256_mask_store_ps ((float*)ptr,mask,v); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vfloat8& v) { _mm256_mask_storeu_ps((float*)ptr,mask,v); } +#else + static __forceinline vfloat8 load (const vboolf8& mask, const void* ptr) { return _mm256_maskload_ps((float*)ptr,(__m256i)mask); } + static __forceinline vfloat8 loadu(const vboolf8& mask, const void* ptr) { return _mm256_maskload_ps((float*)ptr,(__m256i)mask); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vfloat8& v) { _mm256_maskstore_ps((float*)ptr,(__m256i)mask,v); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vfloat8& v) { _mm256_maskstore_ps((float*)ptr,(__m256i)mask,v); } +#endif + +#if defined(__AVX2__) + static __forceinline vfloat8 load_nt(void* ptr) { + return _mm256_castsi256_ps(_mm256_stream_load_si256((__m256i*)ptr)); + } +#endif + + static __forceinline void store_nt(void* ptr, const vfloat8& v) { + _mm256_stream_ps((float*)ptr,v); + } + + template + static __forceinline vfloat8 gather(const float* ptr, const vint8& index) { +#if defined(__AVX2__) + return _mm256_i32gather_ps(ptr, index ,scale); +#else + return vfloat8( + *(float*)(((char*)ptr)+scale*index[0]), + *(float*)(((char*)ptr)+scale*index[1]), + *(float*)(((char*)ptr)+scale*index[2]), + *(float*)(((char*)ptr)+scale*index[3]), + *(float*)(((char*)ptr)+scale*index[4]), + *(float*)(((char*)ptr)+scale*index[5]), + *(float*)(((char*)ptr)+scale*index[6]), + *(float*)(((char*)ptr)+scale*index[7])); +#endif + } + + template + static __forceinline vfloat8 gather(const vboolf8& mask, const float* ptr, const vint8& index) { + vfloat8 r = zero; +#if defined(__AVX512VL__) + return _mm256_mmask_i32gather_ps(r, mask, index, ptr, scale); +#elif defined(__AVX2__) + return _mm256_mask_i32gather_ps(r, ptr, index, mask, scale); +#else + if (likely(mask[0])) r[0] = *(float*)(((char*)ptr)+scale*index[0]); + if (likely(mask[1])) r[1] = *(float*)(((char*)ptr)+scale*index[1]); + if (likely(mask[2])) r[2] = *(float*)(((char*)ptr)+scale*index[2]); + if (likely(mask[3])) r[3] = *(float*)(((char*)ptr)+scale*index[3]); + if (likely(mask[4])) r[4] = *(float*)(((char*)ptr)+scale*index[4]); + if (likely(mask[5])) r[5] = *(float*)(((char*)ptr)+scale*index[5]); + if (likely(mask[6])) r[6] = *(float*)(((char*)ptr)+scale*index[6]); + if (likely(mask[7])) r[7] = *(float*)(((char*)ptr)+scale*index[7]); + return r; + #endif + } + + template + static __forceinline void scatter(void* ptr, const vint8& ofs, const vfloat8& v) + { +#if defined(__AVX512VL__) + _mm256_i32scatter_ps((float*)ptr, ofs, v, scale); +#else + *(float*)(((char*)ptr)+scale*ofs[0]) = v[0]; + *(float*)(((char*)ptr)+scale*ofs[1]) = v[1]; + *(float*)(((char*)ptr)+scale*ofs[2]) = v[2]; + *(float*)(((char*)ptr)+scale*ofs[3]) = v[3]; + *(float*)(((char*)ptr)+scale*ofs[4]) = v[4]; + *(float*)(((char*)ptr)+scale*ofs[5]) = v[5]; + *(float*)(((char*)ptr)+scale*ofs[6]) = v[6]; + *(float*)(((char*)ptr)+scale*ofs[7]) = v[7]; +#endif + } + + template + static __forceinline void scatter(const vboolf8& mask, void* ptr, const vint8& ofs, const vfloat8& v) + { +#if defined(__AVX512VL__) + _mm256_mask_i32scatter_ps((float*)ptr, mask, ofs, v, scale); +#else + if (likely(mask[0])) *(float*)(((char*)ptr)+scale*ofs[0]) = v[0]; + if (likely(mask[1])) *(float*)(((char*)ptr)+scale*ofs[1]) = v[1]; + if (likely(mask[2])) *(float*)(((char*)ptr)+scale*ofs[2]) = v[2]; + if (likely(mask[3])) *(float*)(((char*)ptr)+scale*ofs[3]) = v[3]; + if (likely(mask[4])) *(float*)(((char*)ptr)+scale*ofs[4]) = v[4]; + if (likely(mask[5])) *(float*)(((char*)ptr)+scale*ofs[5]) = v[5]; + if (likely(mask[6])) *(float*)(((char*)ptr)+scale*ofs[6]) = v[6]; + if (likely(mask[7])) *(float*)(((char*)ptr)+scale*ofs[7]) = v[7]; +#endif + } + + static __forceinline void store(const vboolf8& mask, char* ptr, const vint8& ofs, const vfloat8& v) { + scatter<1>(mask,ptr,ofs,v); + } + static __forceinline void store(const vboolf8& mask, float* ptr, const vint8& ofs, const vfloat8& v) { + scatter<4>(mask,ptr,ofs,v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const float& operator [](size_t index) const { assert(index < 8); return f[index]; } + __forceinline float& operator [](size_t index) { assert(index < 8); return f[index]; } + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8 asFloat(const vint8& a) { return _mm256_castsi256_ps(a); } + __forceinline vint8 asInt (const vfloat8& a) { return _mm256_castps_si256(a); } + + __forceinline vint8 toInt (const vfloat8& a) { return vint8(a); } + __forceinline vfloat8 toFloat(const vint8& a) { return vfloat8(a); } + + __forceinline vfloat8 operator +(const vfloat8& a) { return a; } + __forceinline vfloat8 operator -(const vfloat8& a) { + const __m256 mask = _mm256_castsi256_ps(_mm256_set1_epi32(0x80000000)); + return _mm256_xor_ps(a, mask); + } + __forceinline vfloat8 abs(const vfloat8& a) { + const __m256 mask = _mm256_castsi256_ps(_mm256_set1_epi32(0x7fffffff)); + return _mm256_and_ps(a, mask); + } + __forceinline vfloat8 sign (const vfloat8& a) { return _mm256_blendv_ps(vfloat8(one), -vfloat8(one), _mm256_cmp_ps(a, vfloat8(zero), _CMP_NGE_UQ)); } + __forceinline vfloat8 signmsk(const vfloat8& a) { return _mm256_and_ps(a,_mm256_castsi256_ps(_mm256_set1_epi32(0x80000000))); } + + + static __forceinline vfloat8 rcp(const vfloat8& a) + { +#if defined(__AVX512VL__) + const vfloat8 r = _mm256_rcp14_ps(a); +#else + const vfloat8 r = _mm256_rcp_ps(a); +#endif + +#if defined(__AVX2__) + return _mm256_mul_ps(r, _mm256_fnmadd_ps(r, a, vfloat8(2.0f))); +#else + return _mm256_mul_ps(r, _mm256_sub_ps(vfloat8(2.0f), _mm256_mul_ps(r, a))); +#endif + } + __forceinline vfloat8 sqr (const vfloat8& a) { return _mm256_mul_ps(a,a); } + __forceinline vfloat8 sqrt(const vfloat8& a) { return _mm256_sqrt_ps(a); } + + static __forceinline vfloat8 rsqrt(const vfloat8& a) + { +#if defined(__AVX512VL__) + const vfloat8 r = _mm256_rsqrt14_ps(a); +#else + const vfloat8 r = _mm256_rsqrt_ps(a); +#endif + +#if defined(__AVX2__) + return _mm256_fmadd_ps(_mm256_set1_ps(1.5f), r, + _mm256_mul_ps(_mm256_mul_ps(_mm256_mul_ps(a, _mm256_set1_ps(-0.5f)), r), _mm256_mul_ps(r, r))); +#else + return _mm256_add_ps(_mm256_mul_ps(_mm256_set1_ps(1.5f), r), + _mm256_mul_ps(_mm256_mul_ps(_mm256_mul_ps(a, _mm256_set1_ps(-0.5f)), r), _mm256_mul_ps(r, r))); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8 operator +(const vfloat8& a, const vfloat8& b) { return _mm256_add_ps(a, b); } + __forceinline vfloat8 operator +(const vfloat8& a, float b) { return a + vfloat8(b); } + __forceinline vfloat8 operator +(float a, const vfloat8& b) { return vfloat8(a) + b; } + + __forceinline vfloat8 operator -(const vfloat8& a, const vfloat8& b) { return _mm256_sub_ps(a, b); } + __forceinline vfloat8 operator -(const vfloat8& a, float b) { return a - vfloat8(b); } + __forceinline vfloat8 operator -(float a, const vfloat8& b) { return vfloat8(a) - b; } + + __forceinline vfloat8 operator *(const vfloat8& a, const vfloat8& b) { return _mm256_mul_ps(a, b); } + __forceinline vfloat8 operator *(const vfloat8& a, float b) { return a * vfloat8(b); } + __forceinline vfloat8 operator *(float a, const vfloat8& b) { return vfloat8(a) * b; } + + __forceinline vfloat8 operator /(const vfloat8& a, const vfloat8& b) { return _mm256_div_ps(a, b); } + __forceinline vfloat8 operator /(const vfloat8& a, float b) { return a / vfloat8(b); } + __forceinline vfloat8 operator /(float a, const vfloat8& b) { return vfloat8(a) / b; } + + __forceinline vfloat8 operator &(const vfloat8& a, const vfloat8& b) { return _mm256_and_ps(a,b); } + __forceinline vfloat8 operator |(const vfloat8& a, const vfloat8& b) { return _mm256_or_ps(a,b); } + __forceinline vfloat8 operator ^(const vfloat8& a, const vfloat8& b) { return _mm256_xor_ps(a,b); } + __forceinline vfloat8 operator ^(const vfloat8& a, const vint8& b) { return _mm256_xor_ps(a,_mm256_castsi256_ps(b)); } + + __forceinline vfloat8 min(const vfloat8& a, const vfloat8& b) { return _mm256_min_ps(a, b); } + __forceinline vfloat8 min(const vfloat8& a, float b) { return _mm256_min_ps(a, vfloat8(b)); } + __forceinline vfloat8 min(float a, const vfloat8& b) { return _mm256_min_ps(vfloat8(a), b); } + + __forceinline vfloat8 max(const vfloat8& a, const vfloat8& b) { return _mm256_max_ps(a, b); } + __forceinline vfloat8 max(const vfloat8& a, float b) { return _mm256_max_ps(a, vfloat8(b)); } + __forceinline vfloat8 max(float a, const vfloat8& b) { return _mm256_max_ps(vfloat8(a), b); } + + /* need "static __forceinline for MSVC, otherwise we'll link the wrong version in debug mode */ +#if defined(__AVX2__) + + static __forceinline vfloat8 mini(const vfloat8& a, const vfloat8& b) { + const vint8 ai = _mm256_castps_si256(a); + const vint8 bi = _mm256_castps_si256(b); + const vint8 ci = _mm256_min_epi32(ai,bi); + return _mm256_castsi256_ps(ci); + } + + static __forceinline vfloat8 maxi(const vfloat8& a, const vfloat8& b) { + const vint8 ai = _mm256_castps_si256(a); + const vint8 bi = _mm256_castps_si256(b); + const vint8 ci = _mm256_max_epi32(ai,bi); + return _mm256_castsi256_ps(ci); + } + + static __forceinline vfloat8 minui(const vfloat8& a, const vfloat8& b) { + const vint8 ai = _mm256_castps_si256(a); + const vint8 bi = _mm256_castps_si256(b); + const vint8 ci = _mm256_min_epu32(ai,bi); + return _mm256_castsi256_ps(ci); + } + + static __forceinline vfloat8 maxui(const vfloat8& a, const vfloat8& b) { + const vint8 ai = _mm256_castps_si256(a); + const vint8 bi = _mm256_castps_si256(b); + const vint8 ci = _mm256_max_epu32(ai,bi); + return _mm256_castsi256_ps(ci); + } + +#else + + static __forceinline vfloat8 mini(const vfloat8& a, const vfloat8& b) { + return asFloat(min(asInt(a),asInt(b))); + } + + static __forceinline vfloat8 maxi(const vfloat8& a, const vfloat8& b) { + return asFloat(max(asInt(a),asInt(b))); + } + +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Ternary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX2__) + static __forceinline vfloat8 madd (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return _mm256_fmadd_ps(a,b,c); } + static __forceinline vfloat8 msub (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return _mm256_fmsub_ps(a,b,c); } + static __forceinline vfloat8 nmadd (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return _mm256_fnmadd_ps(a,b,c); } + static __forceinline vfloat8 nmsub (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return _mm256_fnmsub_ps(a,b,c); } +#else + static __forceinline vfloat8 madd (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return a*b+c; } + static __forceinline vfloat8 msub (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return a*b-c; } + static __forceinline vfloat8 nmadd (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return -a*b+c;} + static __forceinline vfloat8 nmsub (const vfloat8& a, const vfloat8& b, const vfloat8& c) { return -a*b-c; } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8& operator +=(vfloat8& a, const vfloat8& b) { return a = a + b; } + __forceinline vfloat8& operator +=(vfloat8& a, float b) { return a = a + b; } + + __forceinline vfloat8& operator -=(vfloat8& a, const vfloat8& b) { return a = a - b; } + __forceinline vfloat8& operator -=(vfloat8& a, float b) { return a = a - b; } + + __forceinline vfloat8& operator *=(vfloat8& a, const vfloat8& b) { return a = a * b; } + __forceinline vfloat8& operator *=(vfloat8& a, float b) { return a = a * b; } + + __forceinline vfloat8& operator /=(vfloat8& a, const vfloat8& b) { return a = a / b; } + __forceinline vfloat8& operator /=(vfloat8& a, float b) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + static __forceinline vboolf8 operator ==(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps_mask(a, b, _MM_CMPINT_EQ); } + static __forceinline vboolf8 operator !=(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps_mask(a, b, _MM_CMPINT_NE); } + static __forceinline vboolf8 operator < (const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps_mask(a, b, _MM_CMPINT_LT); } + static __forceinline vboolf8 operator >=(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps_mask(a, b, _MM_CMPINT_GE); } + static __forceinline vboolf8 operator > (const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps_mask(a, b, _MM_CMPINT_GT); } + static __forceinline vboolf8 operator <=(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps_mask(a, b, _MM_CMPINT_LE); } + + static __forceinline vfloat8 select(const vboolf8& m, const vfloat8& t, const vfloat8& f) { + return _mm256_mask_blend_ps(m, f, t); + } +#else + static __forceinline vboolf8 operator ==(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps(a, b, _CMP_EQ_OQ); } + static __forceinline vboolf8 operator !=(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps(a, b, _CMP_NEQ_UQ); } + static __forceinline vboolf8 operator < (const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps(a, b, _CMP_LT_OS); } + static __forceinline vboolf8 operator >=(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps(a, b, _CMP_NLT_US); } + static __forceinline vboolf8 operator > (const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps(a, b, _CMP_NLE_US); } + static __forceinline vboolf8 operator <=(const vfloat8& a, const vfloat8& b) { return _mm256_cmp_ps(a, b, _CMP_LE_OS); } + + static __forceinline vfloat8 select(const vboolf8& m, const vfloat8& t, const vfloat8& f) { + return _mm256_blendv_ps(f, t, m); + } +#endif + + template + __forceinline vfloat8 select(const vfloat8& t, const vfloat8& f) { + return _mm256_blend_ps(f, t, mask); + } + + __forceinline vboolf8 operator ==(const vfloat8& a, const float& b) { return a == vfloat8(b); } + __forceinline vboolf8 operator ==(const float& a, const vfloat8& b) { return vfloat8(a) == b; } + + __forceinline vboolf8 operator !=(const vfloat8& a, const float& b) { return a != vfloat8(b); } + __forceinline vboolf8 operator !=(const float& a, const vfloat8& b) { return vfloat8(a) != b; } + + __forceinline vboolf8 operator < (const vfloat8& a, const float& b) { return a < vfloat8(b); } + __forceinline vboolf8 operator < (const float& a, const vfloat8& b) { return vfloat8(a) < b; } + + __forceinline vboolf8 operator >=(const vfloat8& a, const float& b) { return a >= vfloat8(b); } + __forceinline vboolf8 operator >=(const float& a, const vfloat8& b) { return vfloat8(a) >= b; } + + __forceinline vboolf8 operator > (const vfloat8& a, const float& b) { return a > vfloat8(b); } + __forceinline vboolf8 operator > (const float& a, const vfloat8& b) { return vfloat8(a) > b; } + + __forceinline vboolf8 operator <=(const vfloat8& a, const float& b) { return a <= vfloat8(b); } + __forceinline vboolf8 operator <=(const float& a, const vfloat8& b) { return vfloat8(a) <= b; } + + __forceinline vboolf8 eq(const vfloat8& a, const vfloat8& b) { return a == b; } + __forceinline vboolf8 ne(const vfloat8& a, const vfloat8& b) { return a != b; } + __forceinline vboolf8 lt(const vfloat8& a, const vfloat8& b) { return a < b; } + __forceinline vboolf8 ge(const vfloat8& a, const vfloat8& b) { return a >= b; } + __forceinline vboolf8 gt(const vfloat8& a, const vfloat8& b) { return a > b; } + __forceinline vboolf8 le(const vfloat8& a, const vfloat8& b) { return a <= b; } + +#if defined(__AVX512VL__) + static __forceinline vboolf8 eq(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return _mm256_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_EQ); } + static __forceinline vboolf8 ne(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return _mm256_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_NE); } + static __forceinline vboolf8 lt(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return _mm256_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_LT); } + static __forceinline vboolf8 ge(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return _mm256_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_GE); } + static __forceinline vboolf8 gt(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return _mm256_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_GT); } + static __forceinline vboolf8 le(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return _mm256_mask_cmp_ps_mask(mask, a, b, _MM_CMPINT_LE); } +#else + static __forceinline vboolf8 eq(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return mask & (a == b); } + static __forceinline vboolf8 ne(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return mask & (a != b); } + static __forceinline vboolf8 lt(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return mask & (a < b); } + static __forceinline vboolf8 ge(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return mask & (a >= b); } + static __forceinline vboolf8 gt(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return mask & (a > b); } + static __forceinline vboolf8 le(const vboolf8& mask, const vfloat8& a, const vfloat8& b) { return mask & (a <= b); } +#endif + + __forceinline vfloat8 lerp(const vfloat8& a, const vfloat8& b, const vfloat8& t) { + return madd(t,b-a,a); + } + + __forceinline bool isvalid (const vfloat8& v) { + return all((v > vfloat8(-FLT_LARGE)) & (v < vfloat8(+FLT_LARGE))); + } + + __forceinline bool is_finite (const vfloat8& a) { + return all((a >= vfloat8(-FLT_MAX)) & (a <= vfloat8(+FLT_MAX))); + } + + __forceinline bool is_finite (const vboolf8& valid, const vfloat8& a) { + return all(valid, (a >= vfloat8(-FLT_MAX)) & (a <= vfloat8(+FLT_MAX))); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Rounding Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8 floor(const vfloat8& a) { return _mm256_round_ps(a, _MM_FROUND_TO_NEG_INF ); } + __forceinline vfloat8 ceil (const vfloat8& a) { return _mm256_round_ps(a, _MM_FROUND_TO_POS_INF ); } + __forceinline vfloat8 trunc(const vfloat8& a) { return _mm256_round_ps(a, _MM_FROUND_TO_ZERO ); } + __forceinline vfloat8 round(const vfloat8& a) { return _mm256_round_ps(a, _MM_FROUND_TO_NEAREST_INT); } + __forceinline vfloat8 frac (const vfloat8& a) { return a-floor(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8 unpacklo(const vfloat8& a, const vfloat8& b) { return _mm256_unpacklo_ps(a, b); } + __forceinline vfloat8 unpackhi(const vfloat8& a, const vfloat8& b) { return _mm256_unpackhi_ps(a, b); } + + template + __forceinline vfloat8 shuffle(const vfloat8& v) { + return _mm256_permute_ps(v, _MM_SHUFFLE(i, i, i, i)); + } + + template + __forceinline vfloat8 shuffle4(const vfloat8& v) { + return _mm256_permute2f128_ps(v, v, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vfloat8 shuffle4(const vfloat8& a, const vfloat8& b) { + return _mm256_permute2f128_ps(a, b, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vfloat8 shuffle(const vfloat8& v) { + return _mm256_permute_ps(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vfloat8 shuffle(const vfloat8& a, const vfloat8& b) { + return _mm256_shuffle_ps(a, b, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template<> __forceinline vfloat8 shuffle<0, 0, 2, 2>(const vfloat8& v) { return _mm256_moveldup_ps(v); } + template<> __forceinline vfloat8 shuffle<1, 1, 3, 3>(const vfloat8& v) { return _mm256_movehdup_ps(v); } + template<> __forceinline vfloat8 shuffle<0, 1, 0, 1>(const vfloat8& v) { return _mm256_castpd_ps(_mm256_movedup_pd(_mm256_castps_pd(v))); } + + __forceinline vfloat8 broadcast(const float* ptr) { return _mm256_broadcast_ss(ptr); } + template __forceinline vfloat8 insert4(const vfloat8& a, const vfloat4& b) { return _mm256_insertf128_ps(a, b, i); } + template __forceinline vfloat4 extract4 (const vfloat8& a) { return _mm256_extractf128_ps(a, i); } + template<> __forceinline vfloat4 extract4<0>(const vfloat8& a) { return _mm256_castps256_ps128(a); } + + __forceinline float toScalar(const vfloat8& v) { return _mm_cvtss_f32(_mm256_castps256_ps128(v)); } + + __forceinline vfloat8 assign(const vfloat4& a) { return _mm256_castps128_ps256(a); } + +#if defined (__AVX2__) + static __forceinline vfloat8 permute(const vfloat8& a, const __m256i& index) { + return _mm256_permutevar8x32_ps(a, index); + } +#endif + +#if defined(__AVX512VL__) + template + static __forceinline vfloat8 align_shift_right(const vfloat8& a, const vfloat8& b) { + return _mm256_castsi256_ps(_mm256_alignr_epi32(_mm256_castps_si256(a), _mm256_castps_si256(b), i)); + } +#endif + +#if defined (__AVX_I__) + template + static __forceinline vint4 convert_to_hf16(const vfloat8& a) { + return _mm256_cvtps_ph(a, mode); + } + + static __forceinline vfloat8 convert_from_hf16(const vint4& a) { + return _mm256_cvtph_ps(a); + } +#endif + + __forceinline vfloat4 broadcast4f(const vfloat8& a, const size_t k) { + return vfloat4::broadcast(&a[k]); + } + + __forceinline vfloat8 broadcast8f(const vfloat8& a, const size_t k) { + return vfloat8::broadcast(&a[k]); + } + +#if defined(__AVX512VL__) + static __forceinline vfloat8 shift_right_1(const vfloat8& x) { + return align_shift_right<1>(zero,x); + } +#else + static __forceinline vfloat8 shift_right_1(const vfloat8& x) { + const vfloat8 t0 = shuffle<1,2,3,0>(x); + const vfloat8 t1 = shuffle4<1,0>(t0); + return _mm256_blend_ps(t0,t1,0x88); + } +#endif + + __forceinline vint8 floori(const vfloat8& a) { + return vint8(floor(a)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Transpose + //////////////////////////////////////////////////////////////////////////////// + + __forceinline void transpose(const vfloat8& r0, const vfloat8& r1, const vfloat8& r2, const vfloat8& r3, vfloat8& c0, vfloat8& c1, vfloat8& c2, vfloat8& c3) + { + vfloat8 l02 = unpacklo(r0,r2); + vfloat8 h02 = unpackhi(r0,r2); + vfloat8 l13 = unpacklo(r1,r3); + vfloat8 h13 = unpackhi(r1,r3); + c0 = unpacklo(l02,l13); + c1 = unpackhi(l02,l13); + c2 = unpacklo(h02,h13); + c3 = unpackhi(h02,h13); + } + + __forceinline void transpose(const vfloat8& r0, const vfloat8& r1, const vfloat8& r2, const vfloat8& r3, vfloat8& c0, vfloat8& c1, vfloat8& c2) + { + vfloat8 l02 = unpacklo(r0,r2); + vfloat8 h02 = unpackhi(r0,r2); + vfloat8 l13 = unpacklo(r1,r3); + vfloat8 h13 = unpackhi(r1,r3); + c0 = unpacklo(l02,l13); + c1 = unpackhi(l02,l13); + c2 = unpacklo(h02,h13); + } + + __forceinline void transpose(const vfloat8& r0, const vfloat8& r1, const vfloat8& r2, const vfloat8& r3, const vfloat8& r4, const vfloat8& r5, const vfloat8& r6, const vfloat8& r7, + vfloat8& c0, vfloat8& c1, vfloat8& c2, vfloat8& c3, vfloat8& c4, vfloat8& c5, vfloat8& c6, vfloat8& c7) + { + vfloat8 h0,h1,h2,h3; transpose(r0,r1,r2,r3,h0,h1,h2,h3); + vfloat8 h4,h5,h6,h7; transpose(r4,r5,r6,r7,h4,h5,h6,h7); + c0 = shuffle4<0,2>(h0,h4); + c1 = shuffle4<0,2>(h1,h5); + c2 = shuffle4<0,2>(h2,h6); + c3 = shuffle4<0,2>(h3,h7); + c4 = shuffle4<1,3>(h0,h4); + c5 = shuffle4<1,3>(h1,h5); + c6 = shuffle4<1,3>(h2,h6); + c7 = shuffle4<1,3>(h3,h7); + } + + __forceinline void transpose(const vfloat4& r0, const vfloat4& r1, const vfloat4& r2, const vfloat4& r3, const vfloat4& r4, const vfloat4& r5, const vfloat4& r6, const vfloat4& r7, + vfloat8& c0, vfloat8& c1, vfloat8& c2, vfloat8& c3) + { + transpose(vfloat8(r0,r4), vfloat8(r1,r5), vfloat8(r2,r6), vfloat8(r3,r7), c0, c1, c2, c3); + } + + __forceinline void transpose(const vfloat4& r0, const vfloat4& r1, const vfloat4& r2, const vfloat4& r3, const vfloat4& r4, const vfloat4& r5, const vfloat4& r6, const vfloat4& r7, + vfloat8& c0, vfloat8& c1, vfloat8& c2) + { + transpose(vfloat8(r0,r4), vfloat8(r1,r5), vfloat8(r2,r6), vfloat8(r3,r7), c0, c1, c2); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8 vreduce_min2(const vfloat8& v) { return min(v,shuffle<1,0,3,2>(v)); } + __forceinline vfloat8 vreduce_min4(const vfloat8& v) { vfloat8 v1 = vreduce_min2(v); return min(v1,shuffle<2,3,0,1>(v1)); } + __forceinline vfloat8 vreduce_min (const vfloat8& v) { vfloat8 v1 = vreduce_min4(v); return min(v1,shuffle4<1,0>(v1)); } + + __forceinline vfloat8 vreduce_max2(const vfloat8& v) { return max(v,shuffle<1,0,3,2>(v)); } + __forceinline vfloat8 vreduce_max4(const vfloat8& v) { vfloat8 v1 = vreduce_max2(v); return max(v1,shuffle<2,3,0,1>(v1)); } + __forceinline vfloat8 vreduce_max (const vfloat8& v) { vfloat8 v1 = vreduce_max4(v); return max(v1,shuffle4<1,0>(v1)); } + + __forceinline vfloat8 vreduce_add2(const vfloat8& v) { return v + shuffle<1,0,3,2>(v); } + __forceinline vfloat8 vreduce_add4(const vfloat8& v) { vfloat8 v1 = vreduce_add2(v); return v1 + shuffle<2,3,0,1>(v1); } + __forceinline vfloat8 vreduce_add (const vfloat8& v) { vfloat8 v1 = vreduce_add4(v); return v1 + shuffle4<1,0>(v1); } + + __forceinline float reduce_min(const vfloat8& v) { return toScalar(vreduce_min(v)); } + __forceinline float reduce_max(const vfloat8& v) { return toScalar(vreduce_max(v)); } + __forceinline float reduce_add(const vfloat8& v) { return toScalar(vreduce_add(v)); } + + __forceinline size_t select_min(const vboolf8& valid, const vfloat8& v) + { + const vfloat8 a = select(valid,v,vfloat8(pos_inf)); + const vbool8 valid_min = valid & (a == vreduce_min(a)); + return bsf(movemask(any(valid_min) ? valid_min : valid)); + } + + __forceinline size_t select_max(const vboolf8& valid, const vfloat8& v) + { + const vfloat8 a = select(valid,v,vfloat8(neg_inf)); + const vbool8 valid_max = valid & (a == vreduce_max(a)); + return bsf(movemask(any(valid_max) ? valid_max : valid)); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Euclidian Space Operators (pairs of Vec3fa's) + //////////////////////////////////////////////////////////////////////////////// + + //__forceinline vfloat8 dot(const vfloat8& a, const vfloat8& b) { + // return vreduce_add4(a*b); + //} + + __forceinline vfloat8 dot(const vfloat8& a, const vfloat8& b) { + return _mm256_dp_ps(a,b,0x7F); + } + + __forceinline vfloat8 cross(const vfloat8& a, const vfloat8& b) + { + const vfloat8 a0 = a; + const vfloat8 b0 = shuffle<1,2,0,3>(b); + const vfloat8 a1 = shuffle<1,2,0,3>(a); + const vfloat8 b1 = b; + return shuffle<1,2,0,3>(msub(a0,b0,a1*b1)); + } + + //__forceinline float sqr_length (const vfloat<8>& a) { return dot(a,a); } + //__forceinline float rcp_length (const vfloat<8>& a) { return rsqrt(dot(a,a)); } + //__forceinline float rcp_length2(const vfloat<8>& a) { return rcp(dot(a,a)); } + //__forceinline float length (const vfloat<8>& a) { return sqrt(dot(a,a)); } + __forceinline vfloat<8> normalize(const vfloat<8>& a) { return a*rsqrt(dot(a,a)); } + //__forceinline float distance(const vfloat<8>& a, const vfloat<8>& b) { return length(a-b); } + //__forceinline float halfArea(const vfloat<8>& d) { return madd(d.x,(d.y+d.z),d.y*d.z); } + //__forceinline float area (const vfloat<8>& d) { return 2.0f*halfArea(d); } + //__forceinline vfloat<8> reflect(const vfloat<8>& V, const vfloat<8>& N) { return 2.0f*dot(V,N)*N-V; } + + //__forceinline vfloat<8> normalize_safe(const vfloat<8>& a) { + // const float d = dot(a,a); if (unlikely(d == 0.0f)) return a; else return a*rsqrt(d); + //} + + //////////////////////////////////////////////////////////////////////////////// + /// In Register Sorting + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vfloat8 sort_ascending(const vfloat8& v) + { + const vfloat8 a0 = v; + const vfloat8 b0 = shuffle<1,0,3,2>(a0); + const vfloat8 c0 = min(a0,b0); + const vfloat8 d0 = max(a0,b0); + const vfloat8 a1 = select<0x99 /* 0b10011001 */>(c0,d0); + const vfloat8 b1 = shuffle<2,3,0,1>(a1); + const vfloat8 c1 = min(a1,b1); + const vfloat8 d1 = max(a1,b1); + const vfloat8 a2 = select<0xc3 /* 0b11000011 */>(c1,d1); + const vfloat8 b2 = shuffle<1,0,3,2>(a2); + const vfloat8 c2 = min(a2,b2); + const vfloat8 d2 = max(a2,b2); + const vfloat8 a3 = select<0xa5 /* 0b10100101 */>(c2,d2); + const vfloat8 b3 = shuffle4<1,0>(a3); + const vfloat8 c3 = min(a3,b3); + const vfloat8 d3 = max(a3,b3); + const vfloat8 a4 = select<0xf /* 0b00001111 */>(c3,d3); + const vfloat8 b4 = shuffle<2,3,0,1>(a4); + const vfloat8 c4 = min(a4,b4); + const vfloat8 d4 = max(a4,b4); + const vfloat8 a5 = select<0x33 /* 0b00110011 */>(c4,d4); + const vfloat8 b5 = shuffle<1,0,3,2>(a5); + const vfloat8 c5 = min(a5,b5); + const vfloat8 d5 = max(a5,b5); + const vfloat8 a6 = select<0x55 /* 0b01010101 */>(c5,d5); + return a6; + } + + __forceinline vfloat8 sort_descending(const vfloat8& v) + { + const vfloat8 a0 = v; + const vfloat8 b0 = shuffle<1,0,3,2>(a0); + const vfloat8 c0 = max(a0,b0); + const vfloat8 d0 = min(a0,b0); + const vfloat8 a1 = select<0x99 /* 0b10011001 */>(c0,d0); + const vfloat8 b1 = shuffle<2,3,0,1>(a1); + const vfloat8 c1 = max(a1,b1); + const vfloat8 d1 = min(a1,b1); + const vfloat8 a2 = select<0xc3 /* 0b11000011 */>(c1,d1); + const vfloat8 b2 = shuffle<1,0,3,2>(a2); + const vfloat8 c2 = max(a2,b2); + const vfloat8 d2 = min(a2,b2); + const vfloat8 a3 = select<0xa5 /* 0b10100101 */>(c2,d2); + const vfloat8 b3 = shuffle4<1,0>(a3); + const vfloat8 c3 = max(a3,b3); + const vfloat8 d3 = min(a3,b3); + const vfloat8 a4 = select<0xf /* 0b00001111 */>(c3,d3); + const vfloat8 b4 = shuffle<2,3,0,1>(a4); + const vfloat8 c4 = max(a4,b4); + const vfloat8 d4 = min(a4,b4); + const vfloat8 a5 = select<0x33 /* 0b00110011 */>(c4,d4); + const vfloat8 b5 = shuffle<1,0,3,2>(a5); + const vfloat8 c5 = max(a5,b5); + const vfloat8 d5 = min(a5,b5); + const vfloat8 a6 = select<0x55 /* 0b01010101 */>(c5,d5); + return a6; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vfloat8& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vint16_avx512.h b/thirdparty/embree/common/simd/vint16_avx512.h new file mode 100644 index 000000000000..34e3d5ca0785 --- /dev/null +++ b/thirdparty/embree/common/simd/vint16_avx512.h @@ -0,0 +1,490 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 16-wide AVX-512 integer type */ + template<> + struct vint<16> + { + ALIGNED_STRUCT_(64); + + typedef vboolf16 Bool; + typedef vint16 Int; + typedef vfloat16 Float; + + enum { size = 16 }; // number of SIMD elements + union { // data + __m512i v; + int i[16]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint() {} + __forceinline vint(const vint16& t) { v = t.v; } + __forceinline vint16& operator =(const vint16& f) { v = f.v; return *this; } + + __forceinline vint(const __m512i& t) { v = t; } + __forceinline operator __m512i() const { return v; } + __forceinline operator __m256i() const { return _mm512_castsi512_si256(v); } + + __forceinline vint(int i) { + v = _mm512_set1_epi32(i); + } + + __forceinline vint(int a, int b, int c, int d) { + v = _mm512_set4_epi32(d,c,b,a); + } + + __forceinline vint(int a0 , int a1 , int a2 , int a3, + int a4 , int a5 , int a6 , int a7, + int a8 , int a9 , int a10, int a11, + int a12, int a13, int a14, int a15) + { + v = _mm512_set_epi32(a15,a14,a13,a12,a11,a10,a9,a8,a7,a6,a5,a4,a3,a2,a1,a0); + } + + __forceinline vint(const vint4& i) { + v = _mm512_broadcast_i32x4(i); + } + + __forceinline vint(const vint4& a, const vint4& b, const vint4& c, const vint4& d) { + v = _mm512_castsi128_si512(a); + v = _mm512_inserti32x4(v, b, 1); + v = _mm512_inserti32x4(v, c, 2); + v = _mm512_inserti32x4(v, d, 3); + } + + __forceinline vint(const vint8& i) { + v = _mm512_castps_si512(_mm512_castpd_ps(_mm512_broadcast_f64x4(_mm256_castsi256_pd(i)))); + } + + __forceinline vint(const vint8& a, const vint8& b) { + v = _mm512_castsi256_si512(a); + v = _mm512_inserti64x4(v, b, 1); + } + + __forceinline explicit vint(const __m512& f) { + v = _mm512_cvtps_epi32(f); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint(ZeroTy) : v(_mm512_setzero_epi32()) {} + __forceinline vint(OneTy) : v(_mm512_set1_epi32(1)) {} + __forceinline vint(PosInfTy) : v(_mm512_set1_epi32(pos_inf)) {} + __forceinline vint(NegInfTy) : v(_mm512_set1_epi32(neg_inf)) {} + __forceinline vint(StepTy) : v(_mm512_set_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) {} + __forceinline vint(ReverseStepTy) : v(_mm512_setr_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vint16 load (const void* addr) { return _mm512_load_si512((int*)addr); } + + static __forceinline vint16 load(const unsigned char* ptr) { return _mm512_cvtepu8_epi32(_mm_load_si128((__m128i*)ptr)); } + static __forceinline vint16 load(const unsigned short* ptr) { return _mm512_cvtepu16_epi32(_mm256_load_si256((__m256i*)ptr)); } + + static __forceinline vint16 loadu(const unsigned char* ptr) { return _mm512_cvtepu8_epi32(_mm_loadu_si128((__m128i*)ptr)); } + static __forceinline vint16 loadu(const unsigned short* ptr) { return _mm512_cvtepu16_epi32(_mm256_loadu_si256((__m256i*)ptr)); } + + static __forceinline vint16 loadu(const void* addr) { return _mm512_loadu_si512(addr); } + + static __forceinline vint16 load (const vboolf16& mask, const void* addr) { return _mm512_mask_load_epi32 (_mm512_setzero_epi32(),mask,addr); } + static __forceinline vint16 loadu(const vboolf16& mask, const void* addr) { return _mm512_mask_loadu_epi32(_mm512_setzero_epi32(),mask,addr); } + + static __forceinline void store (void* ptr, const vint16& v) { _mm512_store_si512 (ptr,v); } + static __forceinline void storeu(void* ptr, const vint16& v) { _mm512_storeu_si512(ptr,v); } + + static __forceinline void store (const vboolf16& mask, void* addr, const vint16& v2) { _mm512_mask_store_epi32(addr,mask,v2); } + static __forceinline void storeu(const vboolf16& mask, void* ptr, const vint16& f) { _mm512_mask_storeu_epi32((int*)ptr,mask,f); } + + static __forceinline void store_nt(void* __restrict__ ptr, const vint16& a) { _mm512_stream_si512((__m512i*)ptr,a); } + + /* pass by value to avoid compiler generating inefficient code */ + static __forceinline void storeu_compact(const vboolf16 mask, void* addr, vint16 reg) { + _mm512_mask_compressstoreu_epi32(addr,mask,reg); + } + + static __forceinline void storeu_compact_single(const vboolf16 mask, void* addr, vint16 reg) { + //_mm512_mask_compressstoreu_epi32(addr,mask,reg); + *(float*)addr = mm512_cvtss_f32(_mm512_mask_compress_ps(_mm512_castsi512_ps(reg),mask,_mm512_castsi512_ps(reg))); + } + + static __forceinline vint16 compact64bit(const vboolf16& mask, vint16 &v) { + return _mm512_mask_compress_epi64(v,mask,v); + } + + static __forceinline vint16 compact(const vboolf16& mask, vint16 &v) { + return _mm512_mask_compress_epi32(v,mask,v); + } + + static __forceinline vint16 compact(const vboolf16& mask, const vint16 &a, vint16 &b) { + return _mm512_mask_compress_epi32(a,mask,b); + } + + static __forceinline vint16 expand(const vboolf16& mask, const vint16& a, vint16& b) { + return _mm512_mask_expand_epi32(b,mask,a); + } + + template + static __forceinline vint16 gather(const int* ptr, const vint16& index) { + return _mm512_i32gather_epi32(index,ptr,scale); + } + + template + static __forceinline vint16 gather(const vboolf16& mask, const int* ptr, const vint16& index) { + return _mm512_mask_i32gather_epi32(_mm512_undefined_epi32(),mask,index,ptr,scale); + } + + template + static __forceinline vint16 gather(const vboolf16& mask, vint16& dest, const int* ptr, const vint16& index) { + return _mm512_mask_i32gather_epi32(dest,mask,index,ptr,scale); + } + + template + static __forceinline void scatter(int* ptr, const vint16& index, const vint16& v) { + _mm512_i32scatter_epi32((int*)ptr,index,v,scale); + } + + template + static __forceinline void scatter(const vboolf16& mask, int* ptr, const vint16& index, const vint16& v) { + _mm512_mask_i32scatter_epi32((int*)ptr,mask,index,v,scale); + } + + static __forceinline vint16 broadcast64bit(size_t v) { + return _mm512_set1_epi64(v); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline int& operator [](size_t index) { assert(index < 16); return i[index]; } + __forceinline const int& operator [](size_t index) const { assert(index < 16); return i[index]; } + + __forceinline unsigned int uint (size_t index) const { assert(index < 16); return ((unsigned int*)i)[index]; } + __forceinline size_t& uint64_t(size_t index) const { assert(index < 8); return ((size_t*)i)[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 asBool(const vint16& a) { return _mm512_movepi32_mask(a); } + + __forceinline vint16 operator +(const vint16& a) { return a; } + __forceinline vint16 operator -(const vint16& a) { return _mm512_sub_epi32(_mm512_setzero_epi32(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint16 operator +(const vint16& a, const vint16& b) { return _mm512_add_epi32(a, b); } + __forceinline vint16 operator +(const vint16& a, int b) { return a + vint16(b); } + __forceinline vint16 operator +(int a, const vint16& b) { return vint16(a) + b; } + + __forceinline vint16 operator -(const vint16& a, const vint16& b) { return _mm512_sub_epi32(a, b); } + __forceinline vint16 operator -(const vint16& a, int b) { return a - vint16(b); } + __forceinline vint16 operator -(int a, const vint16& b) { return vint16(a) - b; } + + __forceinline vint16 operator *(const vint16& a, const vint16& b) { return _mm512_mullo_epi32(a, b); } + __forceinline vint16 operator *(const vint16& a, int b) { return a * vint16(b); } + __forceinline vint16 operator *(int a, const vint16& b) { return vint16(a) * b; } + + __forceinline vint16 operator &(const vint16& a, const vint16& b) { return _mm512_and_epi32(a, b); } + __forceinline vint16 operator &(const vint16& a, int b) { return a & vint16(b); } + __forceinline vint16 operator &(int a, const vint16& b) { return vint16(a) & b; } + + __forceinline vint16 operator |(const vint16& a, const vint16& b) { return _mm512_or_epi32(a, b); } + __forceinline vint16 operator |(const vint16& a, int b) { return a | vint16(b); } + __forceinline vint16 operator |(int a, const vint16& b) { return vint16(a) | b; } + + __forceinline vint16 operator ^(const vint16& a, const vint16& b) { return _mm512_xor_epi32(a, b); } + __forceinline vint16 operator ^(const vint16& a, int b) { return a ^ vint16(b); } + __forceinline vint16 operator ^(int a, const vint16& b) { return vint16(a) ^ b; } + + __forceinline vint16 operator <<(const vint16& a, int n) { return _mm512_slli_epi32(a, n); } + __forceinline vint16 operator >>(const vint16& a, int n) { return _mm512_srai_epi32(a, n); } + + __forceinline vint16 operator <<(const vint16& a, const vint16& n) { return _mm512_sllv_epi32(a, n); } + __forceinline vint16 operator >>(const vint16& a, const vint16& n) { return _mm512_srav_epi32(a, n); } + + __forceinline vint16 sll (const vint16& a, int b) { return _mm512_slli_epi32(a, b); } + __forceinline vint16 sra (const vint16& a, int b) { return _mm512_srai_epi32(a, b); } + __forceinline vint16 srl (const vint16& a, int b) { return _mm512_srli_epi32(a, b); } + + __forceinline vint16 min(const vint16& a, const vint16& b) { return _mm512_min_epi32(a, b); } + __forceinline vint16 min(const vint16& a, int b) { return min(a,vint16(b)); } + __forceinline vint16 min(int a, const vint16& b) { return min(vint16(a),b); } + + __forceinline vint16 max(const vint16& a, const vint16& b) { return _mm512_max_epi32(a, b); } + __forceinline vint16 max(const vint16& a, int b) { return max(a,vint16(b)); } + __forceinline vint16 max(int a, const vint16& b) { return max(vint16(a),b); } + + __forceinline vint16 umin(const vint16& a, const vint16& b) { return _mm512_min_epu32(a, b); } + __forceinline vint16 umax(const vint16& a, const vint16& b) { return _mm512_max_epu32(a, b); } + + __forceinline vint16 mask_add(const vboolf16& mask, vint16& c, const vint16& a, const vint16& b) { return _mm512_mask_add_epi32(c,mask,a,b); } + __forceinline vint16 mask_sub(const vboolf16& mask, vint16& c, const vint16& a, const vint16& b) { return _mm512_mask_sub_epi32(c,mask,a,b); } + + __forceinline vint16 mask_and(const vboolf16& m, vint16& c, const vint16& a, const vint16& b) { return _mm512_mask_and_epi32(c,m,a,b); } + __forceinline vint16 mask_or (const vboolf16& m, vint16& c, const vint16& a, const vint16& b) { return _mm512_mask_or_epi32(c,m,a,b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint16& operator +=(vint16& a, const vint16& b) { return a = a + b; } + __forceinline vint16& operator +=(vint16& a, int b) { return a = a + b; } + + __forceinline vint16& operator -=(vint16& a, const vint16& b) { return a = a - b; } + __forceinline vint16& operator -=(vint16& a, int b) { return a = a - b; } + + __forceinline vint16& operator *=(vint16& a, const vint16& b) { return a = a * b; } + __forceinline vint16& operator *=(vint16& a, int b) { return a = a * b; } + + __forceinline vint16& operator &=(vint16& a, const vint16& b) { return a = a & b; } + __forceinline vint16& operator &=(vint16& a, int b) { return a = a & b; } + + __forceinline vint16& operator |=(vint16& a, const vint16& b) { return a = a | b; } + __forceinline vint16& operator |=(vint16& a, int b) { return a = a | b; } + + __forceinline vint16& operator <<=(vint16& a, int b) { return a = a << b; } + __forceinline vint16& operator >>=(vint16& a, int b) { return a = a >> b; } + + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 operator ==(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 operator ==(const vint16& a, int b) { return a == vint16(b); } + __forceinline vboolf16 operator ==(int a, const vint16& b) { return vint16(a) == b; } + + __forceinline vboolf16 operator !=(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 operator !=(const vint16& a, int b) { return a != vint16(b); } + __forceinline vboolf16 operator !=(int a, const vint16& b) { return vint16(a) != b; } + + __forceinline vboolf16 operator < (const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 operator < (const vint16& a, int b) { return a < vint16(b); } + __forceinline vboolf16 operator < (int a, const vint16& b) { return vint16(a) < b; } + + __forceinline vboolf16 operator >=(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 operator >=(const vint16& a, int b) { return a >= vint16(b); } + __forceinline vboolf16 operator >=(int a, const vint16& b) { return vint16(a) >= b; } + + __forceinline vboolf16 operator > (const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 operator > (const vint16& a, int b) { return a > vint16(b); } + __forceinline vboolf16 operator > (int a, const vint16& b) { return vint16(a) > b; } + + __forceinline vboolf16 operator <=(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 operator <=(const vint16& a, int b) { return a <= vint16(b); } + __forceinline vboolf16 operator <=(int a, const vint16& b) { return vint16(a) <= b; } + + __forceinline vboolf16 eq(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 ne(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 lt(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 ge(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 gt(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 le(const vint16& a, const vint16& b) { return _mm512_cmp_epi32_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 uint_le(const vint16& a, const vint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 uint_gt(const vint16& a, const vint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_GT); } + + __forceinline vboolf16 eq(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epi32_mask(mask,a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 ne(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epi32_mask(mask,a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 lt(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epi32_mask(mask,a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 ge(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epi32_mask(mask,a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 gt(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epi32_mask(mask,a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 le(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epi32_mask(mask,a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 uint_le(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 uint_gt(const vboolf16 mask, const vint16& a, const vint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_GT); } + + + __forceinline vint16 select(const vboolf16& m, const vint16& t, const vint16& f) { + return _mm512_mask_or_epi32(f,m,t,t); + } + + __forceinline void xchg(const vboolf16& m, vint16& a, vint16& b) { + const vint16 c = a; a = select(m,b,a); b = select(m,c,b); + } + + __forceinline vboolf16 test(const vboolf16& m, const vint16& a, const vint16& b) { + return _mm512_mask_test_epi32_mask(m,a,b); + } + + __forceinline vboolf16 test(const vint16& a, const vint16& b) { + return _mm512_test_epi32_mask(a,b); + } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint16 unpacklo(const vint16& a, const vint16& b) { return _mm512_unpacklo_epi32(a, b); } + __forceinline vint16 unpackhi(const vint16& a, const vint16& b) { return _mm512_unpackhi_epi32(a, b); } + + template + __forceinline vint16 shuffle(const vint16& v) { + return _mm512_castps_si512(_mm512_permute_ps(_mm512_castsi512_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vint16 shuffle(const vint16& v) { + return _mm512_castps_si512(_mm512_permute_ps(_mm512_castsi512_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vint16 shuffle4(const vint16& v) { + return _mm512_castps_si512(_mm512_shuffle_f32x4(_mm512_castsi512_ps(v), _mm512_castsi512_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vint16 shuffle4(const vint16& v) { + return _mm512_castps_si512(_mm512_shuffle_f32x4(_mm512_castsi512_ps(v), _mm512_castsi512_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vint16 align_shift_right(const vint16& a, const vint16& b) { + return _mm512_alignr_epi32(a, b, i); + }; + + __forceinline int toScalar(const vint16& v) { + return _mm_cvtsi128_si32(_mm512_castsi512_si128(v)); + } + + template __forceinline vint16 insert4(const vint16& a, const vint4& b) { return _mm512_inserti32x4(a, b, i); } + + __forceinline size_t extract64bit(const vint16& v) { + return _mm_cvtsi128_si64(_mm512_castsi512_si128(v)); + } + + template + vint extractN(const vint16& v); + + template<> __forceinline vint4 extractN<4,0>(const vint16& v) { return _mm512_castsi512_si128(v); } + template<> __forceinline vint4 extractN<4,1>(const vint16& v) { return _mm512_extracti32x4_epi32(v, 1); } + template<> __forceinline vint4 extractN<4,2>(const vint16& v) { return _mm512_extracti32x4_epi32(v, 2); } + template<> __forceinline vint4 extractN<4,3>(const vint16& v) { return _mm512_extracti32x4_epi32(v, 3); } + + template<> __forceinline vint8 extractN<8,0>(const vint16& v) { return _mm512_castsi512_si256(v); } + template<> __forceinline vint8 extractN<8,1>(const vint16& v) { return _mm512_extracti32x8_epi32(v, 1); } + + template __forceinline vint4 extract4 (const vint16& v) { return _mm512_extracti32x4_epi32(v, i); } + template<> __forceinline vint4 extract4<0>(const vint16& v) { return _mm512_castsi512_si128(v); } + + template __forceinline vint8 extract8 (const vint16& v) { return _mm512_extracti32x8_epi32(v, i); } + template<> __forceinline vint8 extract8<0>(const vint16& v) { return _mm512_castsi512_si256(v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint16 vreduce_min2(vint16 x) { return min(x, shuffle<1,0,3,2>(x)); } + __forceinline vint16 vreduce_min4(vint16 x) { x = vreduce_min2(x); return min(x, shuffle<2,3,0,1>(x)); } + __forceinline vint16 vreduce_min8(vint16 x) { x = vreduce_min4(x); return min(x, shuffle4<1,0,3,2>(x)); } + __forceinline vint16 vreduce_min (vint16 x) { x = vreduce_min8(x); return min(x, shuffle4<2,3,0,1>(x)); } + + __forceinline vint16 vreduce_max2(vint16 x) { return max(x, shuffle<1,0,3,2>(x)); } + __forceinline vint16 vreduce_max4(vint16 x) { x = vreduce_max2(x); return max(x, shuffle<2,3,0,1>(x)); } + __forceinline vint16 vreduce_max8(vint16 x) { x = vreduce_max4(x); return max(x, shuffle4<1,0,3,2>(x)); } + __forceinline vint16 vreduce_max (vint16 x) { x = vreduce_max8(x); return max(x, shuffle4<2,3,0,1>(x)); } + + __forceinline vint16 vreduce_and2(vint16 x) { return x & shuffle<1,0,3,2>(x); } + __forceinline vint16 vreduce_and4(vint16 x) { x = vreduce_and2(x); return x & shuffle<2,3,0,1>(x); } + __forceinline vint16 vreduce_and8(vint16 x) { x = vreduce_and4(x); return x & shuffle4<1,0,3,2>(x); } + __forceinline vint16 vreduce_and (vint16 x) { x = vreduce_and8(x); return x & shuffle4<2,3,0,1>(x); } + + __forceinline vint16 vreduce_or2(vint16 x) { return x | shuffle<1,0,3,2>(x); } + __forceinline vint16 vreduce_or4(vint16 x) { x = vreduce_or2(x); return x | shuffle<2,3,0,1>(x); } + __forceinline vint16 vreduce_or8(vint16 x) { x = vreduce_or4(x); return x | shuffle4<1,0,3,2>(x); } + __forceinline vint16 vreduce_or (vint16 x) { x = vreduce_or8(x); return x | shuffle4<2,3,0,1>(x); } + + __forceinline vint16 vreduce_add2(vint16 x) { return x + shuffle<1,0,3,2>(x); } + __forceinline vint16 vreduce_add4(vint16 x) { x = vreduce_add2(x); return x + shuffle<2,3,0,1>(x); } + __forceinline vint16 vreduce_add8(vint16 x) { x = vreduce_add4(x); return x + shuffle4<1,0,3,2>(x); } + __forceinline vint16 vreduce_add (vint16 x) { x = vreduce_add8(x); return x + shuffle4<2,3,0,1>(x); } + + __forceinline int reduce_min(const vint16& v) { return toScalar(vreduce_min(v)); } + __forceinline int reduce_max(const vint16& v) { return toScalar(vreduce_max(v)); } + __forceinline int reduce_and(const vint16& v) { return toScalar(vreduce_and(v)); } + __forceinline int reduce_or (const vint16& v) { return toScalar(vreduce_or (v)); } + __forceinline int reduce_add(const vint16& v) { return toScalar(vreduce_add(v)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Memory load and store operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint16 conflict(const vint16& index) + { + return _mm512_conflict_epi32(index); + } + + __forceinline vint16 conflict(const vboolf16& mask, vint16& dest, const vint16& index) + { + return _mm512_mask_conflict_epi32(dest,mask,index); + } + + __forceinline vint16 convert_uint32_t(const __m512& f) { + return _mm512_cvtps_epu32(f); + } + + __forceinline vint16 permute(vint16 v, vint16 index) { + return _mm512_permutexvar_epi32(index,v); + } + + __forceinline vint16 reverse(const vint16 &a) { + return permute(a,vint16(reverse_step)); + } + + __forceinline vint16 prefix_sum(const vint16& a) + { + const vint16 z(zero); + vint16 v = a; + v = v + align_shift_right<16-1>(v,z); + v = v + align_shift_right<16-2>(v,z); + v = v + align_shift_right<16-4>(v,z); + v = v + align_shift_right<16-8>(v,z); + return v; + } + + __forceinline vint16 reverse_prefix_sum(const vint16& a) + { + const vint16 z(zero); + vint16 v = a; + v = v + align_shift_right<1>(z,v); + v = v + align_shift_right<2>(z,v); + v = v + align_shift_right<4>(z,v); + v = v + align_shift_right<8>(z,v); + return v; + } + + /* this should use a vbool8 and a vint8_64...*/ + template + __forceinline void gather_prefetch64(const void* base_addr, const vbool16& mask, const vint16& offset) + { +#if defined(__AVX512PF__) + _mm512_mask_prefetch_i64gather_pd(offset, mask, base_addr, scale, hint); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vint16& v) + { + cout << "<" << v[0]; + for (int i=1; i<16; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vint4_sse2.h b/thirdparty/embree/common/simd/vint4_sse2.h new file mode 100644 index 000000000000..458f8cfaa670 --- /dev/null +++ b/thirdparty/embree/common/simd/vint4_sse2.h @@ -0,0 +1,583 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../math/math.h" + +namespace embree +{ + /* 4-wide SSE integer type */ + template<> + struct vint<4> + { + ALIGNED_STRUCT_(16); + + typedef vboolf4 Bool; + typedef vint4 Int; + typedef vfloat4 Float; + + enum { size = 4 }; // number of SIMD elements + union { __m128i v; int i[4]; }; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint() {} + __forceinline vint(const vint4& a) { v = a.v; } + __forceinline vint4& operator =(const vint4& a) { v = a.v; return *this; } + + __forceinline vint(__m128i a) : v(a) {} + __forceinline operator const __m128i&() const { return v; } + __forceinline operator __m128i&() { return v; } + + __forceinline vint(int a) : v(_mm_set1_epi32(a)) {} + __forceinline vint(int a, int b, int c, int d) : v(_mm_set_epi32(d, c, b, a)) {} + + __forceinline explicit vint(__m128 a) : v(_mm_cvtps_epi32(a)) {} +#if defined(__AVX512VL__) + __forceinline explicit vint(const vboolf4& a) : v(_mm_movm_epi32(a)) {} +#else + __forceinline explicit vint(const vboolf4& a) : v(_mm_castps_si128((__m128)a)) {} +#endif + + __forceinline vint(long long a, long long b) : v(_mm_set_epi64x(b,a)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint(ZeroTy) : v(_mm_setzero_si128()) {} + __forceinline vint(OneTy) : v(_mm_set_epi32(1, 1, 1, 1)) {} + __forceinline vint(PosInfTy) : v(_mm_set_epi32(pos_inf, pos_inf, pos_inf, pos_inf)) {} + __forceinline vint(NegInfTy) : v(_mm_set_epi32(neg_inf, neg_inf, neg_inf, neg_inf)) {} + __forceinline vint(StepTy) : v(_mm_set_epi32(3, 2, 1, 0)) {} + __forceinline vint(ReverseStepTy) : v(_mm_set_epi32(0, 1, 2, 3)) {} + + __forceinline vint(TrueTy) { v = _mm_cmpeq_epi32(v,v); } + __forceinline vint(UndefinedTy) : v(_mm_castps_si128(_mm_undefined_ps())) {} + + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vint4 load (const void* a) { return _mm_load_si128((__m128i*)a); } + static __forceinline vint4 loadu(const void* a) { return _mm_loadu_si128((__m128i*)a); } + + static __forceinline void store (void* ptr, const vint4& v) { _mm_store_si128((__m128i*)ptr,v); } + static __forceinline void storeu(void* ptr, const vint4& v) { _mm_storeu_si128((__m128i*)ptr,v); } + +#if defined(__AVX512VL__) + + static __forceinline vint4 compact(const vboolf4& mask, vint4 &v) { + return _mm_mask_compress_epi32(v, mask, v); + } + static __forceinline vint4 compact(const vboolf4& mask, vint4 &a, const vint4& b) { + return _mm_mask_compress_epi32(a, mask, b); + } + + static __forceinline vint4 load (const vboolf4& mask, const void* ptr) { return _mm_mask_load_epi32 (_mm_setzero_si128(),mask,ptr); } + static __forceinline vint4 loadu(const vboolf4& mask, const void* ptr) { return _mm_mask_loadu_epi32(_mm_setzero_si128(),mask,ptr); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vint4& v) { _mm_mask_store_epi32 (ptr,mask,v); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vint4& v) { _mm_mask_storeu_epi32(ptr,mask,v); } +#elif defined(__AVX__) + static __forceinline vint4 load (const vbool4& mask, const void* a) { return _mm_castps_si128(_mm_maskload_ps((float*)a,mask)); } + static __forceinline vint4 loadu(const vbool4& mask, const void* a) { return _mm_castps_si128(_mm_maskload_ps((float*)a,mask)); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vint4& i) { _mm_maskstore_ps((float*)ptr,(__m128i)mask,_mm_castsi128_ps(i)); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vint4& i) { _mm_maskstore_ps((float*)ptr,(__m128i)mask,_mm_castsi128_ps(i)); } +#else + static __forceinline vint4 load (const vbool4& mask, const void* a) { return _mm_and_si128(_mm_load_si128 ((__m128i*)a),mask); } + static __forceinline vint4 loadu(const vbool4& mask, const void* a) { return _mm_and_si128(_mm_loadu_si128((__m128i*)a),mask); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vint4& i) { store (ptr,select(mask,i,load (ptr))); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vint4& i) { storeu(ptr,select(mask,i,loadu(ptr))); } +#endif + + +#if defined(__SSE4_1__) + static __forceinline vint4 load(const unsigned char* ptr) { + return _mm_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); + } + + static __forceinline vint4 loadu(const unsigned char* ptr) { + return _mm_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); + } +#else + + static __forceinline vint4 load(const unsigned char* ptr) { + return vint4(ptr[0],ptr[1],ptr[2],ptr[3]); + } + + static __forceinline vint4 loadu(const unsigned char* ptr) { + return vint4(ptr[0],ptr[1],ptr[2],ptr[3]); + } + +#endif + + static __forceinline vint4 load(const unsigned short* ptr) { +#if defined (__SSE4_1__) + return _mm_cvtepu16_epi32(_mm_loadu_si128((__m128i*)ptr)); +#else + return vint4(ptr[0],ptr[1],ptr[2],ptr[3]); +#endif + } + + static __forceinline void store(unsigned char* ptr, const vint4& v) { +#if defined(__SSE4_1__) + __m128i x = v; + x = _mm_packus_epi32(x, x); + x = _mm_packus_epi16(x, x); + *(int*)ptr = _mm_cvtsi128_si32(x); +#else + for (size_t i=0;i<4;i++) + ptr[i] = (unsigned char)v[i]; +#endif + } + + static __forceinline void store(unsigned short* ptr, const vint4& v) { + for (size_t i=0;i<4;i++) + ptr[i] = (unsigned short)v[i]; + } + + static __forceinline vint4 load_nt(void* ptr) { +#if defined(__SSE4_1__) + return _mm_stream_load_si128((__m128i*)ptr); +#else + return _mm_load_si128((__m128i*)ptr); +#endif + } + + static __forceinline void store_nt(void* ptr, const vint4& v) { +#if defined(__SSE4_1__) + _mm_stream_ps((float*)ptr, _mm_castsi128_ps(v)); +#else + _mm_store_si128((__m128i*)ptr,v); +#endif + } + + template + static __forceinline vint4 gather(const int* ptr, const vint4& index) { +#if defined(__AVX2__) + return _mm_i32gather_epi32(ptr, index, scale); +#else + return vint4( + *(int*)(((char*)ptr)+scale*index[0]), + *(int*)(((char*)ptr)+scale*index[1]), + *(int*)(((char*)ptr)+scale*index[2]), + *(int*)(((char*)ptr)+scale*index[3])); +#endif + } + + template + static __forceinline vint4 gather(const vboolf4& mask, const int* ptr, const vint4& index) { + vint4 r = zero; +#if defined(__AVX512VL__) + return _mm_mmask_i32gather_epi32(r, mask, index, ptr, scale); +#elif defined(__AVX2__) + return _mm_mask_i32gather_epi32(r, ptr, index, mask, scale); +#else + if (likely(mask[0])) r[0] = *(int*)(((char*)ptr)+scale*index[0]); + if (likely(mask[1])) r[1] = *(int*)(((char*)ptr)+scale*index[1]); + if (likely(mask[2])) r[2] = *(int*)(((char*)ptr)+scale*index[2]); + if (likely(mask[3])) r[3] = *(int*)(((char*)ptr)+scale*index[3]); + return r; +#endif + } + + template + static __forceinline void scatter(void* ptr, const vint4& index, const vint4& v) + { +#if defined(__AVX512VL__) + _mm_i32scatter_epi32((int*)ptr, index, v, scale); +#else + *(int*)(((char*)ptr)+scale*index[0]) = v[0]; + *(int*)(((char*)ptr)+scale*index[1]) = v[1]; + *(int*)(((char*)ptr)+scale*index[2]) = v[2]; + *(int*)(((char*)ptr)+scale*index[3]) = v[3]; +#endif + } + + template + static __forceinline void scatter(const vboolf4& mask, void* ptr, const vint4& index, const vint4& v) + { +#if defined(__AVX512VL__) + _mm_mask_i32scatter_epi32((int*)ptr, mask, index, v, scale); +#else + if (likely(mask[0])) *(int*)(((char*)ptr)+scale*index[0]) = v[0]; + if (likely(mask[1])) *(int*)(((char*)ptr)+scale*index[1]) = v[1]; + if (likely(mask[2])) *(int*)(((char*)ptr)+scale*index[2]) = v[2]; + if (likely(mask[3])) *(int*)(((char*)ptr)+scale*index[3]) = v[3]; +#endif + } + +#if defined(__x86_64__) + static __forceinline vint4 broadcast64(long long a) { return _mm_set1_epi64x(a); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const int& operator [](size_t index) const { assert(index < 4); return i[index]; } + __forceinline int& operator [](size_t index) { assert(index < 4); return i[index]; } + + friend __forceinline vint4 select(const vboolf4& m, const vint4& t, const vint4& f) { +#if defined(__AVX512VL__) + return _mm_mask_blend_epi32(m, (__m128i)f, (__m128i)t); +#elif defined(__SSE4_1__) + return _mm_castps_si128(_mm_blendv_ps(_mm_castsi128_ps(f), _mm_castsi128_ps(t), m)); +#else + return _mm_or_si128(_mm_and_si128(m, t), _mm_andnot_si128(m, f)); +#endif + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf4 asBool(const vint4& a) { return _mm_movepi32_mask(a); } +#else + __forceinline vboolf4 asBool(const vint4& a) { return _mm_castsi128_ps(a); } +#endif + + __forceinline vint4 operator +(const vint4& a) { return a; } + __forceinline vint4 operator -(const vint4& a) { return _mm_sub_epi32(_mm_setzero_si128(), a); } +#if defined(__SSSE3__) + __forceinline vint4 abs(const vint4& a) { return _mm_abs_epi32(a); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint4 operator +(const vint4& a, const vint4& b) { return _mm_add_epi32(a, b); } + __forceinline vint4 operator +(const vint4& a, int b) { return a + vint4(b); } + __forceinline vint4 operator +(int a, const vint4& b) { return vint4(a) + b; } + + __forceinline vint4 operator -(const vint4& a, const vint4& b) { return _mm_sub_epi32(a, b); } + __forceinline vint4 operator -(const vint4& a, int b) { return a - vint4(b); } + __forceinline vint4 operator -(int a, const vint4& b) { return vint4(a) - b; } + +#if defined(__SSE4_1__) + __forceinline vint4 operator *(const vint4& a, const vint4& b) { return _mm_mullo_epi32(a, b); } +#else + __forceinline vint4 operator *(const vint4& a, const vint4& b) { return vint4(a[0]*b[0],a[1]*b[1],a[2]*b[2],a[3]*b[3]); } +#endif + __forceinline vint4 operator *(const vint4& a, int b) { return a * vint4(b); } + __forceinline vint4 operator *(int a, const vint4& b) { return vint4(a) * b; } + + __forceinline vint4 operator &(const vint4& a, const vint4& b) { return _mm_and_si128(a, b); } + __forceinline vint4 operator &(const vint4& a, int b) { return a & vint4(b); } + __forceinline vint4 operator &(int a, const vint4& b) { return vint4(a) & b; } + + __forceinline vint4 operator |(const vint4& a, const vint4& b) { return _mm_or_si128(a, b); } + __forceinline vint4 operator |(const vint4& a, int b) { return a | vint4(b); } + __forceinline vint4 operator |(int a, const vint4& b) { return vint4(a) | b; } + + __forceinline vint4 operator ^(const vint4& a, const vint4& b) { return _mm_xor_si128(a, b); } + __forceinline vint4 operator ^(const vint4& a, int b) { return a ^ vint4(b); } + __forceinline vint4 operator ^(int a, const vint4& b) { return vint4(a) ^ b; } + + __forceinline vint4 operator <<(const vint4& a, int n) { return _mm_slli_epi32(a, n); } + __forceinline vint4 operator >>(const vint4& a, int n) { return _mm_srai_epi32(a, n); } + + __forceinline vint4 sll (const vint4& a, int b) { return _mm_slli_epi32(a, b); } + __forceinline vint4 sra (const vint4& a, int b) { return _mm_srai_epi32(a, b); } + __forceinline vint4 srl (const vint4& a, int b) { return _mm_srli_epi32(a, b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint4& operator +=(vint4& a, const vint4& b) { return a = a + b; } + __forceinline vint4& operator +=(vint4& a, int b) { return a = a + b; } + + __forceinline vint4& operator -=(vint4& a, const vint4& b) { return a = a - b; } + __forceinline vint4& operator -=(vint4& a, int b) { return a = a - b; } + +#if defined(__SSE4_1__) + __forceinline vint4& operator *=(vint4& a, const vint4& b) { return a = a * b; } + __forceinline vint4& operator *=(vint4& a, int b) { return a = a * b; } +#endif + + __forceinline vint4& operator &=(vint4& a, const vint4& b) { return a = a & b; } + __forceinline vint4& operator &=(vint4& a, int b) { return a = a & b; } + + __forceinline vint4& operator |=(vint4& a, const vint4& b) { return a = a | b; } + __forceinline vint4& operator |=(vint4& a, int b) { return a = a | b; } + + __forceinline vint4& operator <<=(vint4& a, int b) { return a = a << b; } + __forceinline vint4& operator >>=(vint4& a, int b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf4 operator ==(const vint4& a, const vint4& b) { return _mm_cmp_epi32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf4 operator !=(const vint4& a, const vint4& b) { return _mm_cmp_epi32_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf4 operator < (const vint4& a, const vint4& b) { return _mm_cmp_epi32_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf4 operator >=(const vint4& a, const vint4& b) { return _mm_cmp_epi32_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf4 operator > (const vint4& a, const vint4& b) { return _mm_cmp_epi32_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf4 operator <=(const vint4& a, const vint4& b) { return _mm_cmp_epi32_mask(a,b,_MM_CMPINT_LE); } +#else + __forceinline vboolf4 operator ==(const vint4& a, const vint4& b) { return _mm_castsi128_ps(_mm_cmpeq_epi32(a, b)); } + __forceinline vboolf4 operator !=(const vint4& a, const vint4& b) { return !(a == b); } + __forceinline vboolf4 operator < (const vint4& a, const vint4& b) { return _mm_castsi128_ps(_mm_cmplt_epi32(a, b)); } + __forceinline vboolf4 operator >=(const vint4& a, const vint4& b) { return !(a < b); } + __forceinline vboolf4 operator > (const vint4& a, const vint4& b) { return _mm_castsi128_ps(_mm_cmpgt_epi32(a, b)); } + __forceinline vboolf4 operator <=(const vint4& a, const vint4& b) { return !(a > b); } +#endif + + __forceinline vboolf4 operator ==(const vint4& a, int b) { return a == vint4(b); } + __forceinline vboolf4 operator ==(int a, const vint4& b) { return vint4(a) == b; } + + __forceinline vboolf4 operator !=(const vint4& a, int b) { return a != vint4(b); } + __forceinline vboolf4 operator !=(int a, const vint4& b) { return vint4(a) != b; } + + __forceinline vboolf4 operator < (const vint4& a, int b) { return a < vint4(b); } + __forceinline vboolf4 operator < (int a, const vint4& b) { return vint4(a) < b; } + + __forceinline vboolf4 operator >=(const vint4& a, int b) { return a >= vint4(b); } + __forceinline vboolf4 operator >=(int a, const vint4& b) { return vint4(a) >= b; } + + __forceinline vboolf4 operator > (const vint4& a, int b) { return a > vint4(b); } + __forceinline vboolf4 operator > (int a, const vint4& b) { return vint4(a) > b; } + + __forceinline vboolf4 operator <=(const vint4& a, int b) { return a <= vint4(b); } + __forceinline vboolf4 operator <=(int a, const vint4& b) { return vint4(a) <= b; } + + __forceinline vboolf4 eq(const vint4& a, const vint4& b) { return a == b; } + __forceinline vboolf4 ne(const vint4& a, const vint4& b) { return a != b; } + __forceinline vboolf4 lt(const vint4& a, const vint4& b) { return a < b; } + __forceinline vboolf4 ge(const vint4& a, const vint4& b) { return a >= b; } + __forceinline vboolf4 gt(const vint4& a, const vint4& b) { return a > b; } + __forceinline vboolf4 le(const vint4& a, const vint4& b) { return a <= b; } + +#if defined(__AVX512VL__) + __forceinline vboolf4 eq(const vboolf4& mask, const vint4& a, const vint4& b) { return _mm_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_EQ); } + __forceinline vboolf4 ne(const vboolf4& mask, const vint4& a, const vint4& b) { return _mm_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_NE); } + __forceinline vboolf4 lt(const vboolf4& mask, const vint4& a, const vint4& b) { return _mm_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_LT); } + __forceinline vboolf4 ge(const vboolf4& mask, const vint4& a, const vint4& b) { return _mm_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_GE); } + __forceinline vboolf4 gt(const vboolf4& mask, const vint4& a, const vint4& b) { return _mm_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_GT); } + __forceinline vboolf4 le(const vboolf4& mask, const vint4& a, const vint4& b) { return _mm_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_LE); } +#else + __forceinline vboolf4 eq(const vboolf4& mask, const vint4& a, const vint4& b) { return mask & (a == b); } + __forceinline vboolf4 ne(const vboolf4& mask, const vint4& a, const vint4& b) { return mask & (a != b); } + __forceinline vboolf4 lt(const vboolf4& mask, const vint4& a, const vint4& b) { return mask & (a < b); } + __forceinline vboolf4 ge(const vboolf4& mask, const vint4& a, const vint4& b) { return mask & (a >= b); } + __forceinline vboolf4 gt(const vboolf4& mask, const vint4& a, const vint4& b) { return mask & (a > b); } + __forceinline vboolf4 le(const vboolf4& mask, const vint4& a, const vint4& b) { return mask & (a <= b); } +#endif + + template + __forceinline vint4 select(const vint4& t, const vint4& f) { +#if defined(__SSE4_1__) + return _mm_castps_si128(_mm_blend_ps(_mm_castsi128_ps(f), _mm_castsi128_ps(t), mask)); +#else + return select(vboolf4(mask), t, f); +#endif + } + +#if defined(__SSE4_1__) + __forceinline vint4 min(const vint4& a, const vint4& b) { return _mm_min_epi32(a, b); } + __forceinline vint4 max(const vint4& a, const vint4& b) { return _mm_max_epi32(a, b); } + + __forceinline vint4 umin(const vint4& a, const vint4& b) { return _mm_min_epu32(a, b); } + __forceinline vint4 umax(const vint4& a, const vint4& b) { return _mm_max_epu32(a, b); } + +#else + __forceinline vint4 min(const vint4& a, const vint4& b) { return select(a < b,a,b); } + __forceinline vint4 max(const vint4& a, const vint4& b) { return select(a < b,b,a); } +#endif + + __forceinline vint4 min(const vint4& a, int b) { return min(a,vint4(b)); } + __forceinline vint4 min(int a, const vint4& b) { return min(vint4(a),b); } + __forceinline vint4 max(const vint4& a, int b) { return max(a,vint4(b)); } + __forceinline vint4 max(int a, const vint4& b) { return max(vint4(a),b); } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint4 unpacklo(const vint4& a, const vint4& b) { return _mm_castps_si128(_mm_unpacklo_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b))); } + __forceinline vint4 unpackhi(const vint4& a, const vint4& b) { return _mm_castps_si128(_mm_unpackhi_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b))); } + + template + __forceinline vint4 shuffle(const vint4& v) { + return _mm_shuffle_epi32(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vint4 shuffle(const vint4& a, const vint4& b) { + return _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); + } + +#if defined(__SSE3__) + template<> __forceinline vint4 shuffle<0, 0, 2, 2>(const vint4& v) { return _mm_castps_si128(_mm_moveldup_ps(_mm_castsi128_ps(v))); } + template<> __forceinline vint4 shuffle<1, 1, 3, 3>(const vint4& v) { return _mm_castps_si128(_mm_movehdup_ps(_mm_castsi128_ps(v))); } + template<> __forceinline vint4 shuffle<0, 1, 0, 1>(const vint4& v) { return _mm_castpd_si128(_mm_movedup_pd (_mm_castsi128_pd(v))); } +#endif + + template + __forceinline vint4 shuffle(const vint4& v) { + return shuffle(v); + } + +#if defined(__SSE4_1__) + template __forceinline int extract(const vint4& b) { return _mm_extract_epi32(b, src); } + template __forceinline vint4 insert(const vint4& a, const int b) { return _mm_insert_epi32(a, b, dst); } +#else + template __forceinline int extract(const vint4& b) { return b[src&3]; } + template __forceinline vint4 insert(const vint4& a, int b) { vint4 c = a; c[dst&3] = b; return c; } +#endif + + + template<> __forceinline int extract<0>(const vint4& b) { return _mm_cvtsi128_si32(b); } + + __forceinline int toScalar(const vint4& v) { return _mm_cvtsi128_si32(v); } + + __forceinline size_t toSizeT(const vint4& v) { +#if defined(__WIN32__) && !defined(__X86_64__) // win32 workaround + return toScalar(v); +#else + return _mm_cvtsi128_si64(v); +#endif + } + +#if defined(__AVX512VL__) + + __forceinline vint4 permute(const vint4 &a, const vint4 &index) { + return _mm_castps_si128(_mm_permutevar_ps(_mm_castsi128_ps(a),index)); + } + + template + __forceinline vint4 align_shift_right(const vint4& a, const vint4& b) { + return _mm_alignr_epi32(a, b, i); + } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__SSE4_1__) + __forceinline vint4 vreduce_min(const vint4& v) { vint4 h = min(shuffle<1,0,3,2>(v),v); return min(shuffle<2,3,0,1>(h),h); } + __forceinline vint4 vreduce_max(const vint4& v) { vint4 h = max(shuffle<1,0,3,2>(v),v); return max(shuffle<2,3,0,1>(h),h); } + __forceinline vint4 vreduce_add(const vint4& v) { vint4 h = shuffle<1,0,3,2>(v) + v ; return shuffle<2,3,0,1>(h) + h ; } + + __forceinline int reduce_min(const vint4& v) { return toScalar(vreduce_min(v)); } + __forceinline int reduce_max(const vint4& v) { return toScalar(vreduce_max(v)); } + __forceinline int reduce_add(const vint4& v) { return toScalar(vreduce_add(v)); } + + __forceinline size_t select_min(const vint4& v) { return bsf(movemask(v == vreduce_min(v))); } + __forceinline size_t select_max(const vint4& v) { return bsf(movemask(v == vreduce_max(v))); } + + __forceinline size_t select_min(const vboolf4& valid, const vint4& v) { const vint4 a = select(valid,v,vint4(pos_inf)); return bsf(movemask(valid & (a == vreduce_min(a)))); } + __forceinline size_t select_max(const vboolf4& valid, const vint4& v) { const vint4 a = select(valid,v,vint4(neg_inf)); return bsf(movemask(valid & (a == vreduce_max(a)))); } + +#else + + __forceinline int reduce_min(const vint4& v) { return min(v[0],v[1],v[2],v[3]); } + __forceinline int reduce_max(const vint4& v) { return max(v[0],v[1],v[2],v[3]); } + __forceinline int reduce_add(const vint4& v) { return v[0]+v[1]+v[2]+v[3]; } + +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Sorting networks + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__SSE4_1__) + + __forceinline vint4 usort_ascending(const vint4& v) + { + const vint4 a0 = v; + const vint4 b0 = shuffle<1,0,3,2>(a0); + const vint4 c0 = umin(a0,b0); + const vint4 d0 = umax(a0,b0); + const vint4 a1 = select<0x5 /* 0b0101 */>(c0,d0); + const vint4 b1 = shuffle<2,3,0,1>(a1); + const vint4 c1 = umin(a1,b1); + const vint4 d1 = umax(a1,b1); + const vint4 a2 = select<0x3 /* 0b0011 */>(c1,d1); + const vint4 b2 = shuffle<0,2,1,3>(a2); + const vint4 c2 = umin(a2,b2); + const vint4 d2 = umax(a2,b2); + const vint4 a3 = select<0x2 /* 0b0010 */>(c2,d2); + return a3; + } + + __forceinline vint4 usort_descending(const vint4& v) + { + const vint4 a0 = v; + const vint4 b0 = shuffle<1,0,3,2>(a0); + const vint4 c0 = umax(a0,b0); + const vint4 d0 = umin(a0,b0); + const vint4 a1 = select<0x5 /* 0b0101 */>(c0,d0); + const vint4 b1 = shuffle<2,3,0,1>(a1); + const vint4 c1 = umax(a1,b1); + const vint4 d1 = umin(a1,b1); + const vint4 a2 = select<0x3 /* 0b0011 */>(c1,d1); + const vint4 b2 = shuffle<0,2,1,3>(a2); + const vint4 c2 = umax(a2,b2); + const vint4 d2 = umin(a2,b2); + const vint4 a3 = select<0x2 /* 0b0010 */>(c2,d2); + return a3; + } + +#else + + __forceinline vint4 usort_ascending(const vint4& v) + { + const vint4 a0 = v-vint4(0x80000000); + const vint4 b0 = shuffle<1,0,3,2>(a0); + const vint4 c0 = min(a0,b0); + const vint4 d0 = max(a0,b0); + const vint4 a1 = select<0x5 /* 0b0101 */>(c0,d0); + const vint4 b1 = shuffle<2,3,0,1>(a1); + const vint4 c1 = min(a1,b1); + const vint4 d1 = max(a1,b1); + const vint4 a2 = select<0x3 /* 0b0011 */>(c1,d1); + const vint4 b2 = shuffle<0,2,1,3>(a2); + const vint4 c2 = min(a2,b2); + const vint4 d2 = max(a2,b2); + const vint4 a3 = select<0x2 /* 0b0010 */>(c2,d2); + return a3+vint4(0x80000000); + } + + __forceinline vint4 usort_descending(const vint4& v) + { + const vint4 a0 = v-vint4(0x80000000); + const vint4 b0 = shuffle<1,0,3,2>(a0); + const vint4 c0 = max(a0,b0); + const vint4 d0 = min(a0,b0); + const vint4 a1 = select<0x5 /* 0b0101 */>(c0,d0); + const vint4 b1 = shuffle<2,3,0,1>(a1); + const vint4 c1 = max(a1,b1); + const vint4 d1 = min(a1,b1); + const vint4 a2 = select<0x3 /* 0b0011 */>(c1,d1); + const vint4 b2 = shuffle<0,2,1,3>(a2); + const vint4 c2 = max(a2,b2); + const vint4 d2 = min(a2,b2); + const vint4 a3 = select<0x2 /* 0b0010 */>(c2,d2); + return a3+vint4(0x80000000); + } + +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vint4& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ">"; + } +} + diff --git a/thirdparty/embree/common/simd/vint8_avx.h b/thirdparty/embree/common/simd/vint8_avx.h new file mode 100644 index 000000000000..c373907e9c32 --- /dev/null +++ b/thirdparty/embree/common/simd/vint8_avx.h @@ -0,0 +1,459 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX integer type */ + template<> + struct vint<8> + { + ALIGNED_STRUCT_(32); + + typedef vboolf8 Bool; + typedef vint8 Int; + typedef vfloat8 Float; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m256i v; + struct { __m128i vl,vh; }; + int i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint() {} + __forceinline vint(const vint8& a) { v = a.v; } + __forceinline vint8& operator =(const vint8& a) { v = a.v; return *this; } + + __forceinline vint(__m256i a) : v(a) {} + __forceinline operator const __m256i&() const { return v; } + __forceinline operator __m256i&() { return v; } + + __forceinline explicit vint(const vint4& a) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),a,1)) {} + __forceinline vint(const vint4& a, const vint4& b) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),b,1)) {} + __forceinline vint(const __m128i& a, const __m128i& b) : vl(a), vh(b) {} + + __forceinline explicit vint(const int* a) : v(_mm256_castps_si256(_mm256_loadu_ps((const float*)a))) {} + __forceinline vint(int a) : v(_mm256_set1_epi32(a)) {} + __forceinline vint(int a, int b) : v(_mm256_set_epi32(b, a, b, a, b, a, b, a)) {} + __forceinline vint(int a, int b, int c, int d) : v(_mm256_set_epi32(d, c, b, a, d, c, b, a)) {} + __forceinline vint(int a, int b, int c, int d, int e, int f, int g, int vh) : v(_mm256_set_epi32(vh, g, f, e, d, c, b, a)) {} + + __forceinline explicit vint(__m256 a) : v(_mm256_cvtps_epi32(a)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint(ZeroTy) : v(_mm256_setzero_si256()) {} + __forceinline vint(OneTy) : v(_mm256_set_epi32(1,1,1,1,1,1,1,1)) {} + __forceinline vint(PosInfTy) : v(_mm256_set_epi32(pos_inf,pos_inf,pos_inf,pos_inf,pos_inf,pos_inf,pos_inf,pos_inf)) {} + __forceinline vint(NegInfTy) : v(_mm256_set_epi32(neg_inf,neg_inf,neg_inf,neg_inf,neg_inf,neg_inf,neg_inf,neg_inf)) {} + __forceinline vint(StepTy) : v(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0)) {} + __forceinline vint(ReverseStepTy) : v(_mm256_set_epi32(0, 1, 2, 3, 4, 5, 6, 7)) {} + __forceinline vint(UndefinedTy) : v(_mm256_undefined_si256()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vint8 load (const void* a) { return _mm256_castps_si256(_mm256_load_ps((float*)a)); } + static __forceinline vint8 loadu(const void* a) { return _mm256_castps_si256(_mm256_loadu_ps((float*)a)); } + + static __forceinline vint8 load (const vboolf8& mask, const void* a) { return _mm256_castps_si256(_mm256_maskload_ps((float*)a,mask)); } + static __forceinline vint8 loadu(const vboolf8& mask, const void* a) { return _mm256_castps_si256(_mm256_maskload_ps((float*)a,mask)); } + + static __forceinline void store (void* ptr, const vint8& f) { _mm256_store_ps((float*)ptr,_mm256_castsi256_ps(f)); } + static __forceinline void storeu(void* ptr, const vint8& f) { _mm256_storeu_ps((float*)ptr,_mm256_castsi256_ps(f)); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vint8& f) { _mm256_maskstore_ps((float*)ptr,(__m256i)mask,_mm256_castsi256_ps(f)); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vint8& f) { _mm256_maskstore_ps((float*)ptr,(__m256i)mask,_mm256_castsi256_ps(f)); } + + static __forceinline void store_nt(void* ptr, const vint8& v) { + _mm256_stream_ps((float*)ptr,_mm256_castsi256_ps(v)); + } + + static __forceinline vint8 load(const unsigned char* ptr) { + vint4 il = vint4::load(ptr+0); + vint4 ih = vint4::load(ptr+4); + return vint8(il,ih); + } + + static __forceinline vint8 loadu(const unsigned char* ptr) { + vint4 il = vint4::loadu(ptr+0); + vint4 ih = vint4::loadu(ptr+4); + return vint8(il,ih); + } + + static __forceinline vint8 load(const unsigned short* ptr) { + vint4 il = vint4::load(ptr+0); + vint4 ih = vint4::load(ptr+4); + return vint8(il,ih); + } + + static __forceinline vint8 loadu(const unsigned short* ptr) { + vint4 il = vint4::loadu(ptr+0); + vint4 ih = vint4::loadu(ptr+4); + return vint8(il,ih); + } + + static __forceinline void store(unsigned char* ptr, const vint8& i) { + vint4 il(i.vl); + vint4 ih(i.vh); + vint4::store(ptr + 0,il); + vint4::store(ptr + 4,ih); + } + + static __forceinline void store(unsigned short* ptr, const vint8& v) { + for (size_t i=0;i<8;i++) + ptr[i] = (unsigned short)v[i]; + } + + template + static __forceinline vint8 gather(const int* ptr, const vint8& index) { + return vint8( + *(int*)(((char*)ptr)+scale*index[0]), + *(int*)(((char*)ptr)+scale*index[1]), + *(int*)(((char*)ptr)+scale*index[2]), + *(int*)(((char*)ptr)+scale*index[3]), + *(int*)(((char*)ptr)+scale*index[4]), + *(int*)(((char*)ptr)+scale*index[5]), + *(int*)(((char*)ptr)+scale*index[6]), + *(int*)(((char*)ptr)+scale*index[7])); + } + + template + static __forceinline vint8 gather(const vboolf8& mask, const int* ptr, const vint8& index) { + vint8 r = zero; + if (likely(mask[0])) r[0] = *(int*)(((char*)ptr)+scale*index[0]); + if (likely(mask[1])) r[1] = *(int*)(((char*)ptr)+scale*index[1]); + if (likely(mask[2])) r[2] = *(int*)(((char*)ptr)+scale*index[2]); + if (likely(mask[3])) r[3] = *(int*)(((char*)ptr)+scale*index[3]); + if (likely(mask[4])) r[4] = *(int*)(((char*)ptr)+scale*index[4]); + if (likely(mask[5])) r[5] = *(int*)(((char*)ptr)+scale*index[5]); + if (likely(mask[6])) r[6] = *(int*)(((char*)ptr)+scale*index[6]); + if (likely(mask[7])) r[7] = *(int*)(((char*)ptr)+scale*index[7]); + return r; + } + + template + static __forceinline void scatter(void* ptr, const vint8& ofs, const vint8& v) + { + *(int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + *(int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + *(int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + *(int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + *(int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + *(int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + *(int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + *(int*)(((char*)ptr)+scale*ofs[7]) = v[7]; + } + + template + static __forceinline void scatter(const vboolf8& mask, void* ptr, const vint8& ofs, const vint8& v) + { + if (likely(mask[0])) *(int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + if (likely(mask[1])) *(int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + if (likely(mask[2])) *(int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + if (likely(mask[3])) *(int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + if (likely(mask[4])) *(int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + if (likely(mask[5])) *(int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + if (likely(mask[6])) *(int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + if (likely(mask[7])) *(int*)(((char*)ptr)+scale*ofs[7]) = v[7]; + } + + + static __forceinline vint8 broadcast64(const long long& a) { return _mm256_set1_epi64x(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const int& operator [](size_t index) const { assert(index < 8); return i[index]; } + __forceinline int& operator [](size_t index) { assert(index < 8); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 asBool(const vint8& a) { return _mm256_castsi256_ps(a); } + + __forceinline vint8 operator +(const vint8& a) { return a; } + __forceinline vint8 operator -(const vint8& a) { return vint8(_mm_sub_epi32(_mm_setzero_si128(), a.vl), _mm_sub_epi32(_mm_setzero_si128(), a.vh)); } + __forceinline vint8 abs (const vint8& a) { return vint8(_mm_abs_epi32(a.vl), _mm_abs_epi32(a.vh)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 operator +(const vint8& a, const vint8& b) { return vint8(_mm_add_epi32(a.vl, b.vl), _mm_add_epi32(a.vh, b.vh)); } + __forceinline vint8 operator +(const vint8& a, int b) { return a + vint8(b); } + __forceinline vint8 operator +(int a, const vint8& b) { return vint8(a) + b; } + + __forceinline vint8 operator -(const vint8& a, const vint8& b) { return vint8(_mm_sub_epi32(a.vl, b.vl), _mm_sub_epi32(a.vh, b.vh)); } + __forceinline vint8 operator -(const vint8& a, int b) { return a - vint8(b); } + __forceinline vint8 operator -(int a, const vint8& b) { return vint8(a) - b; } + + __forceinline vint8 operator *(const vint8& a, const vint8& b) { return vint8(_mm_mullo_epi32(a.vl, b.vl), _mm_mullo_epi32(a.vh, b.vh)); } + __forceinline vint8 operator *(const vint8& a, int b) { return a * vint8(b); } + __forceinline vint8 operator *(int a, const vint8& b) { return vint8(a) * b; } + + __forceinline vint8 operator &(const vint8& a, const vint8& b) { return _mm256_castps_si256(_mm256_and_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vint8 operator &(const vint8& a, int b) { return a & vint8(b); } + __forceinline vint8 operator &(int a, const vint8& b) { return vint8(a) & b; } + + __forceinline vint8 operator |(const vint8& a, const vint8& b) { return _mm256_castps_si256(_mm256_or_ps (_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vint8 operator |(const vint8& a, int b) { return a | vint8(b); } + __forceinline vint8 operator |(int a, const vint8& b) { return vint8(a) | b; } + + __forceinline vint8 operator ^(const vint8& a, const vint8& b) { return _mm256_castps_si256(_mm256_xor_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vint8 operator ^(const vint8& a, int b) { return a ^ vint8(b); } + __forceinline vint8 operator ^(int a, const vint8& b) { return vint8(a) ^ b; } + + __forceinline vint8 operator <<(const vint8& a, int n) { return vint8(_mm_slli_epi32(a.vl, n), _mm_slli_epi32(a.vh, n)); } + __forceinline vint8 operator >>(const vint8& a, int n) { return vint8(_mm_srai_epi32(a.vl, n), _mm_srai_epi32(a.vh, n)); } + + __forceinline vint8 sll (const vint8& a, int b) { return vint8(_mm_slli_epi32(a.vl, b), _mm_slli_epi32(a.vh, b)); } + __forceinline vint8 sra (const vint8& a, int b) { return vint8(_mm_srai_epi32(a.vl, b), _mm_srai_epi32(a.vh, b)); } + __forceinline vint8 srl (const vint8& a, int b) { return vint8(_mm_srli_epi32(a.vl, b), _mm_srli_epi32(a.vh, b)); } + + __forceinline vint8 min(const vint8& a, const vint8& b) { return vint8(_mm_min_epi32(a.vl, b.vl), _mm_min_epi32(a.vh, b.vh)); } + __forceinline vint8 min(const vint8& a, int b) { return min(a,vint8(b)); } + __forceinline vint8 min(int a, const vint8& b) { return min(vint8(a),b); } + + __forceinline vint8 max(const vint8& a, const vint8& b) { return vint8(_mm_max_epi32(a.vl, b.vl), _mm_max_epi32(a.vh, b.vh)); } + __forceinline vint8 max(const vint8& a, int b) { return max(a,vint8(b)); } + __forceinline vint8 max(int a, const vint8& b) { return max(vint8(a),b); } + + __forceinline vint8 umin(const vint8& a, const vint8& b) { return vint8(_mm_min_epu32(a.vl, b.vl), _mm_min_epu32(a.vh, b.vh)); } + __forceinline vint8 umin(const vint8& a, int b) { return umin(a,vint8(b)); } + __forceinline vint8 umin(int a, const vint8& b) { return umin(vint8(a),b); } + + __forceinline vint8 umax(const vint8& a, const vint8& b) { return vint8(_mm_max_epu32(a.vl, b.vl), _mm_max_epu32(a.vh, b.vh)); } + __forceinline vint8 umax(const vint8& a, int b) { return umax(a,vint8(b)); } + __forceinline vint8 umax(int a, const vint8& b) { return umax(vint8(a),b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8& operator +=(vint8& a, const vint8& b) { return a = a + b; } + __forceinline vint8& operator +=(vint8& a, int b) { return a = a + b; } + + __forceinline vint8& operator -=(vint8& a, const vint8& b) { return a = a - b; } + __forceinline vint8& operator -=(vint8& a, int b) { return a = a - b; } + + __forceinline vint8& operator *=(vint8& a, const vint8& b) { return a = a * b; } + __forceinline vint8& operator *=(vint8& a, int b) { return a = a * b; } + + __forceinline vint8& operator &=(vint8& a, const vint8& b) { return a = a & b; } + __forceinline vint8& operator &=(vint8& a, int b) { return a = a & b; } + + __forceinline vint8& operator |=(vint8& a, const vint8& b) { return a = a | b; } + __forceinline vint8& operator |=(vint8& a, int b) { return a = a | b; } + + __forceinline vint8& operator <<=(vint8& a, int b) { return a = a << b; } + __forceinline vint8& operator >>=(vint8& a, int b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator ==(const vint8& a, const vint8& b) { return vboolf8(_mm_castsi128_ps(_mm_cmpeq_epi32 (a.vl, b.vl)), + _mm_castsi128_ps(_mm_cmpeq_epi32 (a.vh, b.vh))); } + __forceinline vboolf8 operator ==(const vint8& a, int b) { return a == vint8(b); } + __forceinline vboolf8 operator ==(int a, const vint8& b) { return vint8(a) == b; } + + __forceinline vboolf8 operator !=(const vint8& a, const vint8& b) { return !(a == b); } + __forceinline vboolf8 operator !=(const vint8& a, int b) { return a != vint8(b); } + __forceinline vboolf8 operator !=(int a, const vint8& b) { return vint8(a) != b; } + + __forceinline vboolf8 operator < (const vint8& a, const vint8& b) { return vboolf8(_mm_castsi128_ps(_mm_cmplt_epi32 (a.vl, b.vl)), + _mm_castsi128_ps(_mm_cmplt_epi32 (a.vh, b.vh))); } + __forceinline vboolf8 operator < (const vint8& a, int b) { return a < vint8(b); } + __forceinline vboolf8 operator < (int a, const vint8& b) { return vint8(a) < b; } + + __forceinline vboolf8 operator >=(const vint8& a, const vint8& b) { return !(a < b); } + __forceinline vboolf8 operator >=(const vint8& a, int b) { return a >= vint8(b); } + __forceinline vboolf8 operator >=(int a, const vint8& b) { return vint8(a) >= b; } + + __forceinline vboolf8 operator > (const vint8& a, const vint8& b) { return vboolf8(_mm_castsi128_ps(_mm_cmpgt_epi32 (a.vl, b.vl)), + _mm_castsi128_ps(_mm_cmpgt_epi32 (a.vh, b.vh))); } + __forceinline vboolf8 operator > (const vint8& a, int b) { return a > vint8(b); } + __forceinline vboolf8 operator > (int a, const vint8& b) { return vint8(a) > b; } + + __forceinline vboolf8 operator <=(const vint8& a, const vint8& b) { return !(a > b); } + __forceinline vboolf8 operator <=(const vint8& a, int b) { return a <= vint8(b); } + __forceinline vboolf8 operator <=(int a, const vint8& b) { return vint8(a) <= b; } + + __forceinline vboolf8 eq(const vint8& a, const vint8& b) { return a == b; } + __forceinline vboolf8 ne(const vint8& a, const vint8& b) { return a != b; } + __forceinline vboolf8 lt(const vint8& a, const vint8& b) { return a < b; } + __forceinline vboolf8 ge(const vint8& a, const vint8& b) { return a >= b; } + __forceinline vboolf8 gt(const vint8& a, const vint8& b) { return a > b; } + __forceinline vboolf8 le(const vint8& a, const vint8& b) { return a <= b; } + + __forceinline vboolf8 eq(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a == b); } + __forceinline vboolf8 ne(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a != b); } + __forceinline vboolf8 lt(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a < b); } + __forceinline vboolf8 ge(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a >= b); } + __forceinline vboolf8 gt(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a > b); } + __forceinline vboolf8 le(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a <= b); } + + __forceinline vint8 select(const vboolf8& m, const vint8& t, const vint8& f) { + return _mm256_castps_si256(_mm256_blendv_ps(_mm256_castsi256_ps(f), _mm256_castsi256_ps(t), m)); + } + + __forceinline vint8 notand(const vboolf8& m, const vint8& f) { + return _mm256_castps_si256(_mm256_andnot_ps(m, _mm256_castsi256_ps(f))); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 unpacklo(const vint8& a, const vint8& b) { return _mm256_castps_si256(_mm256_unpacklo_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vint8 unpackhi(const vint8& a, const vint8& b) { return _mm256_castps_si256(_mm256_unpackhi_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + + template + __forceinline vint8 shuffle(const vint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vint8 shuffle4(const vint8& v) { + return _mm256_permute2f128_si256(v, v, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vint8 shuffle4(const vint8& a, const vint8& b) { + return _mm256_permute2f128_si256(a, b, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vint8 shuffle(const vint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vint8 shuffle(const vint8& a, const vint8& b) { + return _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template<> __forceinline vint8 shuffle<0, 0, 2, 2>(const vint8& v) { return _mm256_castps_si256(_mm256_moveldup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vint8 shuffle<1, 1, 3, 3>(const vint8& v) { return _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vint8 shuffle<0, 1, 0, 1>(const vint8& v) { return _mm256_castps_si256(_mm256_castpd_ps(_mm256_movedup_pd(_mm256_castps_pd(_mm256_castsi256_ps(v))))); } + + __forceinline vint8 broadcast(const int* ptr) { return _mm256_castps_si256(_mm256_broadcast_ss((const float*)ptr)); } + template __forceinline vint8 insert4(const vint8& a, const vint4& b) { return _mm256_insertf128_si256(a, b, i); } + template __forceinline vint4 extract4(const vint8& a) { return _mm256_extractf128_si256(a, i); } + template<> __forceinline vint4 extract4<0>(const vint8& a) { return _mm256_castsi256_si128(a); } + + __forceinline int toScalar(const vint8& v) { return _mm_cvtsi128_si32(_mm256_castsi256_si128(v)); } + + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 vreduce_min2(const vint8& v) { return min(v,shuffle<1,0,3,2>(v)); } + __forceinline vint8 vreduce_min4(const vint8& v) { vint8 v1 = vreduce_min2(v); return min(v1,shuffle<2,3,0,1>(v1)); } + __forceinline vint8 vreduce_min (const vint8& v) { vint8 v1 = vreduce_min4(v); return min(v1,shuffle4<1,0>(v1)); } + + __forceinline vint8 vreduce_max2(const vint8& v) { return max(v,shuffle<1,0,3,2>(v)); } + __forceinline vint8 vreduce_max4(const vint8& v) { vint8 v1 = vreduce_max2(v); return max(v1,shuffle<2,3,0,1>(v1)); } + __forceinline vint8 vreduce_max (const vint8& v) { vint8 v1 = vreduce_max4(v); return max(v1,shuffle4<1,0>(v1)); } + + __forceinline vint8 vreduce_add2(const vint8& v) { return v + shuffle<1,0,3,2>(v); } + __forceinline vint8 vreduce_add4(const vint8& v) { vint8 v1 = vreduce_add2(v); return v1 + shuffle<2,3,0,1>(v1); } + __forceinline vint8 vreduce_add (const vint8& v) { vint8 v1 = vreduce_add4(v); return v1 + shuffle4<1,0>(v1); } + + __forceinline int reduce_min(const vint8& v) { return toScalar(vreduce_min(v)); } + __forceinline int reduce_max(const vint8& v) { return toScalar(vreduce_max(v)); } + __forceinline int reduce_add(const vint8& v) { return toScalar(vreduce_add(v)); } + + __forceinline size_t select_min(const vint8& v) { return bsf(movemask(v == vreduce_min(v))); } + __forceinline size_t select_max(const vint8& v) { return bsf(movemask(v == vreduce_max(v))); } + + __forceinline size_t select_min(const vboolf8& valid, const vint8& v) { const vint8 a = select(valid,v,vint8(pos_inf)); return bsf(movemask(valid & (a == vreduce_min(a)))); } + __forceinline size_t select_max(const vboolf8& valid, const vint8& v) { const vint8 a = select(valid,v,vint8(neg_inf)); return bsf(movemask(valid & (a == vreduce_max(a)))); } + + //////////////////////////////////////////////////////////////////////////////// + /// Sorting networks + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 usort_ascending(const vint8& v) + { + const vint8 a0 = v; + const vint8 b0 = shuffle<1,0,3,2>(a0); + const vint8 c0 = umin(a0,b0); + const vint8 d0 = umax(a0,b0); + const vint8 a1 = select(0x99 /* 0b10011001 */,c0,d0); + const vint8 b1 = shuffle<2,3,0,1>(a1); + const vint8 c1 = umin(a1,b1); + const vint8 d1 = umax(a1,b1); + const vint8 a2 = select(0xc3 /* 0b11000011 */,c1,d1); + const vint8 b2 = shuffle<1,0,3,2>(a2); + const vint8 c2 = umin(a2,b2); + const vint8 d2 = umax(a2,b2); + const vint8 a3 = select(0xa5 /* 0b10100101 */,c2,d2); + const vint8 b3 = shuffle4<1,0>(a3); + const vint8 c3 = umin(a3,b3); + const vint8 d3 = umax(a3,b3); + const vint8 a4 = select(0xf /* 0b00001111 */,c3,d3); + const vint8 b4 = shuffle<2,3,0,1>(a4); + const vint8 c4 = umin(a4,b4); + const vint8 d4 = umax(a4,b4); + const vint8 a5 = select(0x33 /* 0b00110011 */,c4,d4); + const vint8 b5 = shuffle<1,0,3,2>(a5); + const vint8 c5 = umin(a5,b5); + const vint8 d5 = umax(a5,b5); + const vint8 a6 = select(0x55 /* 0b01010101 */,c5,d5); + return a6; + } + + __forceinline vint8 usort_descending(const vint8& v) + { + const vint8 a0 = v; + const vint8 b0 = shuffle<1,0,3,2>(a0); + const vint8 c0 = umax(a0,b0); + const vint8 d0 = umin(a0,b0); + const vint8 a1 = select(0x99 /* 0b10011001 */,c0,d0); + const vint8 b1 = shuffle<2,3,0,1>(a1); + const vint8 c1 = umax(a1,b1); + const vint8 d1 = umin(a1,b1); + const vint8 a2 = select(0xc3 /* 0b11000011 */,c1,d1); + const vint8 b2 = shuffle<1,0,3,2>(a2); + const vint8 c2 = umax(a2,b2); + const vint8 d2 = umin(a2,b2); + const vint8 a3 = select(0xa5 /* 0b10100101 */,c2,d2); + const vint8 b3 = shuffle4<1,0>(a3); + const vint8 c3 = umax(a3,b3); + const vint8 d3 = umin(a3,b3); + const vint8 a4 = select(0xf /* 0b00001111 */,c3,d3); + const vint8 b4 = shuffle<2,3,0,1>(a4); + const vint8 c4 = umax(a4,b4); + const vint8 d4 = umin(a4,b4); + const vint8 a5 = select(0x33 /* 0b00110011 */,c4,d4); + const vint8 b5 = shuffle<1,0,3,2>(a5); + const vint8 c5 = umax(a5,b5); + const vint8 d5 = umin(a5,b5); + const vint8 a6 = select(0x55 /* 0b01010101 */,c5,d5); + return a6; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vint8& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vint8_avx2.h b/thirdparty/embree/common/simd/vint8_avx2.h new file mode 100644 index 000000000000..ea97d3eb346e --- /dev/null +++ b/thirdparty/embree/common/simd/vint8_avx2.h @@ -0,0 +1,505 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX integer type */ + template<> + struct vint<8> + { + ALIGNED_STRUCT_(32); + + typedef vboolf8 Bool; + typedef vint8 Int; + typedef vfloat8 Float; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m256i v; + int i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint() {} + __forceinline vint(const vint8& a) { v = a.v; } + __forceinline vint8& operator =(const vint8& a) { v = a.v; return *this; } + + __forceinline vint(__m256i a) : v(a) {} + __forceinline operator const __m256i&() const { return v; } + __forceinline operator __m256i&() { return v; } + + __forceinline explicit vint(const vint4& a) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),a,1)) {} + __forceinline vint(const vint4& a, const vint4& b) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),b,1)) {} + __forceinline vint(const __m128i& a, const __m128i& b) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),b,1)) {} + + __forceinline explicit vint(const int* a) : v(_mm256_castps_si256(_mm256_loadu_ps((const float*)a))) {} + __forceinline vint(int a) : v(_mm256_set1_epi32(a)) {} + __forceinline vint(int a, int b) : v(_mm256_set_epi32(b, a, b, a, b, a, b, a)) {} + __forceinline vint(int a, int b, int c, int d) : v(_mm256_set_epi32(d, c, b, a, d, c, b, a)) {} + __forceinline vint(int a, int b, int c, int d, int e, int f, int g, int h) : v(_mm256_set_epi32(h, g, f, e, d, c, b, a)) {} + + __forceinline explicit vint(__m256 a) : v(_mm256_cvtps_epi32(a)) {} + +#if defined(__AVX512VL__) + __forceinline explicit vint(const vboolf8& a) : v(_mm256_movm_epi32(a)) {} +#else + __forceinline explicit vint(const vboolf8& a) : v(_mm256_castps_si256((__m256)a)) {} +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint(ZeroTy) : v(_mm256_setzero_si256()) {} + __forceinline vint(OneTy) : v(_mm256_set1_epi32(1)) {} + __forceinline vint(PosInfTy) : v(_mm256_set1_epi32(pos_inf)) {} + __forceinline vint(NegInfTy) : v(_mm256_set1_epi32(neg_inf)) {} + __forceinline vint(StepTy) : v(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0)) {} + __forceinline vint(ReverseStepTy) : v(_mm256_set_epi32(0, 1, 2, 3, 4, 5, 6, 7)) {} + __forceinline vint(UndefinedTy) : v(_mm256_undefined_si256()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vint8 load(const unsigned char* ptr) { return _mm256_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); } + static __forceinline vint8 loadu(const unsigned char* ptr) { return _mm256_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); } + static __forceinline vint8 load(const unsigned short* ptr) { return _mm256_cvtepu16_epi32(_mm_load_si128((__m128i*)ptr)); } + static __forceinline vint8 loadu(const unsigned short* ptr) { return _mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i*)ptr)); } + + static __forceinline vint8 load(const void* ptr) { return _mm256_load_si256((__m256i*)ptr); } + static __forceinline vint8 loadu(const void* ptr) { return _mm256_loadu_si256((__m256i*)ptr); } + + static __forceinline void store (void* ptr, const vint8& v) { _mm256_store_si256((__m256i*)ptr,v); } + static __forceinline void storeu(void* ptr, const vint8& v) { _mm256_storeu_ps((float*)ptr,_mm256_castsi256_ps(v)); } + +#if defined(__AVX512VL__) + + static __forceinline vint8 compact(const vboolf8& mask, vint8 &v) { + return _mm256_mask_compress_epi32(v, mask, v); + } + static __forceinline vint8 compact(const vboolf8& mask, vint8 &a, const vint8& b) { + return _mm256_mask_compress_epi32(a, mask, b); + } + + static __forceinline vint8 load (const vboolf8& mask, const void* ptr) { return _mm256_mask_load_epi32 (_mm256_setzero_si256(),mask,ptr); } + static __forceinline vint8 loadu(const vboolf8& mask, const void* ptr) { return _mm256_mask_loadu_epi32(_mm256_setzero_si256(),mask,ptr); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vint8& v) { _mm256_mask_store_epi32 (ptr,mask,v); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vint8& v) { _mm256_mask_storeu_epi32(ptr,mask,v); } +#else + static __forceinline vint8 load (const vboolf8& mask, const void* ptr) { return _mm256_castps_si256(_mm256_maskload_ps((float*)ptr,mask)); } + static __forceinline vint8 loadu(const vboolf8& mask, const void* ptr) { return _mm256_castps_si256(_mm256_maskload_ps((float*)ptr,mask)); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vint8& v) { _mm256_maskstore_epi32((int*)ptr,mask,v); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vint8& v) { _mm256_maskstore_epi32((int*)ptr,mask,v); } +#endif + + static __forceinline vint8 load_nt(void* ptr) { + return _mm256_stream_load_si256((__m256i*)ptr); + } + + static __forceinline void store_nt(void* ptr, const vint8& v) { + _mm256_stream_ps((float*)ptr,_mm256_castsi256_ps(v)); + } + + static __forceinline void store(unsigned char* ptr, const vint8& i) + { + for (size_t j=0; j<8; j++) + ptr[j] = i[j]; + } + + static __forceinline void store(unsigned short* ptr, const vint8& v) { + for (size_t i=0;i<8;i++) + ptr[i] = (unsigned short)v[i]; + } + + template + static __forceinline vint8 gather(const int *const ptr, const vint8& index) { + return _mm256_i32gather_epi32(ptr, index, scale); + } + + template + static __forceinline vint8 gather(const vboolf8& mask, const int *const ptr, const vint8& index) { + vint8 r = zero; +#if defined(__AVX512VL__) + return _mm256_mmask_i32gather_epi32(r, mask, index, ptr, scale); +#else + return _mm256_mask_i32gather_epi32(r, ptr, index, mask, scale); +#endif + } + + template + static __forceinline void scatter(void* ptr, const vint8& ofs, const vint8& v) + { +#if defined(__AVX512VL__) + _mm256_i32scatter_epi32((int*)ptr, ofs, v, scale); +#else + *(int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + *(int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + *(int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + *(int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + *(int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + *(int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + *(int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + *(int*)(((char*)ptr)+scale*ofs[7]) = v[7]; +#endif + } + + template + static __forceinline void scatter(const vboolf8& mask, void* ptr, const vint8& ofs, const vint8& v) + { +#if defined(__AVX512VL__) + _mm256_mask_i32scatter_epi32((int*)ptr, mask, ofs, v, scale); +#else + if (likely(mask[0])) *(int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + if (likely(mask[1])) *(int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + if (likely(mask[2])) *(int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + if (likely(mask[3])) *(int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + if (likely(mask[4])) *(int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + if (likely(mask[5])) *(int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + if (likely(mask[6])) *(int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + if (likely(mask[7])) *(int*)(((char*)ptr)+scale*ofs[7]) = v[7]; +#endif + } + + static __forceinline vint8 broadcast64(const long long &a) { return _mm256_set1_epi64x(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const int& operator [](size_t index) const { assert(index < 8); return i[index]; } + __forceinline int& operator [](size_t index) { assert(index < 8); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + static __forceinline vboolf8 asBool(const vint8& a) { return _mm256_movepi32_mask(a); } +#else + static __forceinline vboolf8 asBool(const vint8& a) { return _mm256_castsi256_ps(a); } +#endif + + __forceinline vint8 operator +(const vint8& a) { return a; } + __forceinline vint8 operator -(const vint8& a) { return _mm256_sub_epi32(_mm256_setzero_si256(), a); } + __forceinline vint8 abs (const vint8& a) { return _mm256_abs_epi32(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 operator +(const vint8& a, const vint8& b) { return _mm256_add_epi32(a, b); } + __forceinline vint8 operator +(const vint8& a, int b) { return a + vint8(b); } + __forceinline vint8 operator +(int a, const vint8& b) { return vint8(a) + b; } + + __forceinline vint8 operator -(const vint8& a, const vint8& b) { return _mm256_sub_epi32(a, b); } + __forceinline vint8 operator -(const vint8& a, int b) { return a - vint8(b); } + __forceinline vint8 operator -(int a, const vint8& b) { return vint8(a) - b; } + + __forceinline vint8 operator *(const vint8& a, const vint8& b) { return _mm256_mullo_epi32(a, b); } + __forceinline vint8 operator *(const vint8& a, int b) { return a * vint8(b); } + __forceinline vint8 operator *(int a, const vint8& b) { return vint8(a) * b; } + + __forceinline vint8 operator &(const vint8& a, const vint8& b) { return _mm256_and_si256(a, b); } + __forceinline vint8 operator &(const vint8& a, int b) { return a & vint8(b); } + __forceinline vint8 operator &(int a, const vint8& b) { return vint8(a) & b; } + + __forceinline vint8 operator |(const vint8& a, const vint8& b) { return _mm256_or_si256(a, b); } + __forceinline vint8 operator |(const vint8& a, int b) { return a | vint8(b); } + __forceinline vint8 operator |(int a, const vint8& b) { return vint8(a) | b; } + + __forceinline vint8 operator ^(const vint8& a, const vint8& b) { return _mm256_xor_si256(a, b); } + __forceinline vint8 operator ^(const vint8& a, int b) { return a ^ vint8(b); } + __forceinline vint8 operator ^(int a, const vint8& b) { return vint8(a) ^ b; } + + __forceinline vint8 operator <<(const vint8& a, int n) { return _mm256_slli_epi32(a, n); } + __forceinline vint8 operator >>(const vint8& a, int n) { return _mm256_srai_epi32(a, n); } + + __forceinline vint8 operator <<(const vint8& a, const vint8& n) { return _mm256_sllv_epi32(a, n); } + __forceinline vint8 operator >>(const vint8& a, const vint8& n) { return _mm256_srav_epi32(a, n); } + + __forceinline vint8 sll(const vint8& a, int b) { return _mm256_slli_epi32(a, b); } + __forceinline vint8 sra(const vint8& a, int b) { return _mm256_srai_epi32(a, b); } + __forceinline vint8 srl(const vint8& a, int b) { return _mm256_srli_epi32(a, b); } + + __forceinline vint8 sll(const vint8& a, const vint8& b) { return _mm256_sllv_epi32(a, b); } + __forceinline vint8 sra(const vint8& a, const vint8& b) { return _mm256_srav_epi32(a, b); } + __forceinline vint8 srl(const vint8& a, const vint8& b) { return _mm256_srlv_epi32(a, b); } + + __forceinline vint8 min(const vint8& a, const vint8& b) { return _mm256_min_epi32(a, b); } + __forceinline vint8 min(const vint8& a, int b) { return min(a,vint8(b)); } + __forceinline vint8 min(int a, const vint8& b) { return min(vint8(a),b); } + + __forceinline vint8 max(const vint8& a, const vint8& b) { return _mm256_max_epi32(a, b); } + __forceinline vint8 max(const vint8& a, int b) { return max(a,vint8(b)); } + __forceinline vint8 max(int a, const vint8& b) { return max(vint8(a),b); } + + __forceinline vint8 umin(const vint8& a, const vint8& b) { return _mm256_min_epu32(a, b); } + __forceinline vint8 umax(const vint8& a, const vint8& b) { return _mm256_max_epu32(a, b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8& operator +=(vint8& a, const vint8& b) { return a = a + b; } + __forceinline vint8& operator +=(vint8& a, int b) { return a = a + b; } + + __forceinline vint8& operator -=(vint8& a, const vint8& b) { return a = a - b; } + __forceinline vint8& operator -=(vint8& a, int b) { return a = a - b; } + + __forceinline vint8& operator *=(vint8& a, const vint8& b) { return a = a * b; } + __forceinline vint8& operator *=(vint8& a, int b) { return a = a * b; } + + __forceinline vint8& operator &=(vint8& a, const vint8& b) { return a = a & b; } + __forceinline vint8& operator &=(vint8& a, int b) { return a = a & b; } + + __forceinline vint8& operator |=(vint8& a, const vint8& b) { return a = a | b; } + __forceinline vint8& operator |=(vint8& a, int b) { return a = a | b; } + + __forceinline vint8& operator <<=(vint8& a, const int b) { return a = a << b; } + __forceinline vint8& operator >>=(vint8& a, const int b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + static __forceinline vboolf8 operator ==(const vint8& a, const vint8& b) { return _mm256_cmp_epi32_mask(a,b,_MM_CMPINT_EQ); } + static __forceinline vboolf8 operator !=(const vint8& a, const vint8& b) { return _mm256_cmp_epi32_mask(a,b,_MM_CMPINT_NE); } + static __forceinline vboolf8 operator < (const vint8& a, const vint8& b) { return _mm256_cmp_epi32_mask(a,b,_MM_CMPINT_LT); } + static __forceinline vboolf8 operator >=(const vint8& a, const vint8& b) { return _mm256_cmp_epi32_mask(a,b,_MM_CMPINT_GE); } + static __forceinline vboolf8 operator > (const vint8& a, const vint8& b) { return _mm256_cmp_epi32_mask(a,b,_MM_CMPINT_GT); } + static __forceinline vboolf8 operator <=(const vint8& a, const vint8& b) { return _mm256_cmp_epi32_mask(a,b,_MM_CMPINT_LE); } + + static __forceinline vint8 select(const vboolf8& m, const vint8& t, const vint8& f) { + return _mm256_mask_blend_epi32(m, (__m256i)f, (__m256i)t); + } +#else + static __forceinline vboolf8 operator ==(const vint8& a, const vint8& b) { return _mm256_castsi256_ps(_mm256_cmpeq_epi32(a, b)); } + static __forceinline vboolf8 operator !=(const vint8& a, const vint8& b) { return !(a == b); } + static __forceinline vboolf8 operator < (const vint8& a, const vint8& b) { return _mm256_castsi256_ps(_mm256_cmpgt_epi32(b, a)); } + static __forceinline vboolf8 operator >=(const vint8& a, const vint8& b) { return !(a < b); } + static __forceinline vboolf8 operator > (const vint8& a, const vint8& b) { return _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, b)); } + static __forceinline vboolf8 operator <=(const vint8& a, const vint8& b) { return !(a > b); } + + static __forceinline vint8 select(const vboolf8& m, const vint8& t, const vint8& f) { + return _mm256_castps_si256(_mm256_blendv_ps(_mm256_castsi256_ps(f), _mm256_castsi256_ps(t), m)); + } +#endif + + template + __forceinline vint8 select(const vint8& t, const vint8& f) { + return _mm256_blend_epi32(f, t, mask); + } + + __forceinline vboolf8 operator ==(const vint8& a, int b) { return a == vint8(b); } + __forceinline vboolf8 operator ==(int a, const vint8& b) { return vint8(a) == b; } + + __forceinline vboolf8 operator !=(const vint8& a, int b) { return a != vint8(b); } + __forceinline vboolf8 operator !=(int a, const vint8& b) { return vint8(a) != b; } + + __forceinline vboolf8 operator < (const vint8& a, int b) { return a < vint8(b); } + __forceinline vboolf8 operator < (int a, const vint8& b) { return vint8(a) < b; } + + __forceinline vboolf8 operator >=(const vint8& a, int b) { return a >= vint8(b); } + __forceinline vboolf8 operator >=(int a, const vint8& b) { return vint8(a) >= b; } + + __forceinline vboolf8 operator > (const vint8& a, int b) { return a > vint8(b); } + __forceinline vboolf8 operator > (int a, const vint8& b) { return vint8(a) > b; } + + __forceinline vboolf8 operator <=(const vint8& a, int b) { return a <= vint8(b); } + __forceinline vboolf8 operator <=(int a, const vint8& b) { return vint8(a) <= b; } + + __forceinline vboolf8 eq(const vint8& a, const vint8& b) { return a == b; } + __forceinline vboolf8 ne(const vint8& a, const vint8& b) { return a != b; } + __forceinline vboolf8 lt(const vint8& a, const vint8& b) { return a < b; } + __forceinline vboolf8 ge(const vint8& a, const vint8& b) { return a >= b; } + __forceinline vboolf8 gt(const vint8& a, const vint8& b) { return a > b; } + __forceinline vboolf8 le(const vint8& a, const vint8& b) { return a <= b; } + +#if defined(__AVX512VL__) + static __forceinline vboolf8 eq(const vboolf8& mask, const vint8& a, const vint8& b) { return _mm256_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_EQ); } + static __forceinline vboolf8 ne(const vboolf8& mask, const vint8& a, const vint8& b) { return _mm256_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_NE); } + static __forceinline vboolf8 lt(const vboolf8& mask, const vint8& a, const vint8& b) { return _mm256_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_LT); } + static __forceinline vboolf8 ge(const vboolf8& mask, const vint8& a, const vint8& b) { return _mm256_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_GE); } + static __forceinline vboolf8 gt(const vboolf8& mask, const vint8& a, const vint8& b) { return _mm256_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_GT); } + static __forceinline vboolf8 le(const vboolf8& mask, const vint8& a, const vint8& b) { return _mm256_mask_cmp_epi32_mask(mask, a, b, _MM_CMPINT_LE); } +#else + static __forceinline vboolf8 eq(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a == b); } + static __forceinline vboolf8 ne(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a != b); } + static __forceinline vboolf8 lt(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a < b); } + static __forceinline vboolf8 ge(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a >= b); } + static __forceinline vboolf8 gt(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a > b); } + static __forceinline vboolf8 le(const vboolf8& mask, const vint8& a, const vint8& b) { return mask & (a <= b); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 unpacklo(const vint8& a, const vint8& b) { return _mm256_unpacklo_epi32(a, b); } + __forceinline vint8 unpackhi(const vint8& a, const vint8& b) { return _mm256_unpackhi_epi32(a, b); } + + template + __forceinline vint8 shuffle(const vint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vint8 shuffle4(const vint8& v) { + return _mm256_permute2f128_si256(v, v, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vint8 shuffle4(const vint8& a, const vint8& b) { + return _mm256_permute2f128_si256(a, b, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vint8 shuffle(const vint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vint8 shuffle(const vint8& a, const vint8& b) { + return _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template<> __forceinline vint8 shuffle<0, 0, 2, 2>(const vint8& v) { return _mm256_castps_si256(_mm256_moveldup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vint8 shuffle<1, 1, 3, 3>(const vint8& v) { return _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vint8 shuffle<0, 1, 0, 1>(const vint8& v) { return _mm256_castps_si256(_mm256_castpd_ps(_mm256_movedup_pd(_mm256_castps_pd(_mm256_castsi256_ps(v))))); } + + __forceinline vint8 broadcast(const int* ptr) { return _mm256_castps_si256(_mm256_broadcast_ss((const float*)ptr)); } + + template __forceinline vint8 insert4(const vint8& a, const vint4& b) { return _mm256_insertf128_si256(a, b, i); } + template __forceinline vint4 extract4(const vint8& a) { return _mm256_extractf128_si256(a, i); } + template<> __forceinline vint4 extract4<0>(const vint8& a) { return _mm256_castsi256_si128(a); } + + __forceinline int toScalar(const vint8& v) { return _mm_cvtsi128_si32(_mm256_castsi256_si128(v)); } + + __forceinline vint8 permute(const vint8& v, const __m256i& index) { + return _mm256_permutevar8x32_epi32(v, index); + } + + __forceinline vint8 shuffle(const vint8& v, const __m256i& index) { + return _mm256_castps_si256(_mm256_permutevar_ps(_mm256_castsi256_ps(v), index)); + } + + template + static __forceinline vint8 align_shift_right(const vint8& a, const vint8& b) { +#if defined(__AVX512VL__) + return _mm256_alignr_epi32(a, b, i); +#else + return _mm256_alignr_epi8(a, b, 4*i); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 vreduce_min2(const vint8& v) { return min(v,shuffle<1,0,3,2>(v)); } + __forceinline vint8 vreduce_min4(const vint8& v) { vint8 v1 = vreduce_min2(v); return min(v1,shuffle<2,3,0,1>(v1)); } + __forceinline vint8 vreduce_min (const vint8& v) { vint8 v1 = vreduce_min4(v); return min(v1,shuffle4<1,0>(v1)); } + + __forceinline vint8 vreduce_max2(const vint8& v) { return max(v,shuffle<1,0,3,2>(v)); } + __forceinline vint8 vreduce_max4(const vint8& v) { vint8 v1 = vreduce_max2(v); return max(v1,shuffle<2,3,0,1>(v1)); } + __forceinline vint8 vreduce_max (const vint8& v) { vint8 v1 = vreduce_max4(v); return max(v1,shuffle4<1,0>(v1)); } + + __forceinline vint8 vreduce_add2(const vint8& v) { return v + shuffle<1,0,3,2>(v); } + __forceinline vint8 vreduce_add4(const vint8& v) { vint8 v1 = vreduce_add2(v); return v1 + shuffle<2,3,0,1>(v1); } + __forceinline vint8 vreduce_add (const vint8& v) { vint8 v1 = vreduce_add4(v); return v1 + shuffle4<1,0>(v1); } + + __forceinline int reduce_min(const vint8& v) { return toScalar(vreduce_min(v)); } + __forceinline int reduce_max(const vint8& v) { return toScalar(vreduce_max(v)); } + __forceinline int reduce_add(const vint8& v) { return toScalar(vreduce_add(v)); } + + __forceinline size_t select_min(const vint8& v) { return bsf(movemask(v == vreduce_min(v))); } + __forceinline size_t select_max(const vint8& v) { return bsf(movemask(v == vreduce_max(v))); } + + __forceinline size_t select_min(const vboolf8& valid, const vint8& v) { const vint8 a = select(valid,v,vint8(pos_inf)); return bsf(movemask(valid & (a == vreduce_min(a)))); } + __forceinline size_t select_max(const vboolf8& valid, const vint8& v) { const vint8 a = select(valid,v,vint8(neg_inf)); return bsf(movemask(valid & (a == vreduce_max(a)))); } + + + __forceinline vint8 assign(const vint4& a) { return _mm256_castsi128_si256(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Sorting networks + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vint8 usort_ascending(const vint8& v) + { + const vint8 a0 = v; + const vint8 b0 = shuffle<1,0,3,2>(a0); + const vint8 c0 = umin(a0,b0); + const vint8 d0 = umax(a0,b0); + const vint8 a1 = select<0x99 /* 0b10011001 */>(c0,d0); + const vint8 b1 = shuffle<2,3,0,1>(a1); + const vint8 c1 = umin(a1,b1); + const vint8 d1 = umax(a1,b1); + const vint8 a2 = select<0xc3 /* 0b11000011 */>(c1,d1); + const vint8 b2 = shuffle<1,0,3,2>(a2); + const vint8 c2 = umin(a2,b2); + const vint8 d2 = umax(a2,b2); + const vint8 a3 = select<0xa5 /* 0b10100101 */>(c2,d2); + const vint8 b3 = shuffle4<1,0>(a3); + const vint8 c3 = umin(a3,b3); + const vint8 d3 = umax(a3,b3); + const vint8 a4 = select<0xf /* 0b00001111 */>(c3,d3); + const vint8 b4 = shuffle<2,3,0,1>(a4); + const vint8 c4 = umin(a4,b4); + const vint8 d4 = umax(a4,b4); + const vint8 a5 = select<0x33 /* 0b00110011 */>(c4,d4); + const vint8 b5 = shuffle<1,0,3,2>(a5); + const vint8 c5 = umin(a5,b5); + const vint8 d5 = umax(a5,b5); + const vint8 a6 = select<0x55 /* 0b01010101 */>(c5,d5); + return a6; + } + + __forceinline vint8 usort_descending(const vint8& v) + { + const vint8 a0 = v; + const vint8 b0 = shuffle<1,0,3,2>(a0); + const vint8 c0 = umax(a0,b0); + const vint8 d0 = umin(a0,b0); + const vint8 a1 = select<0x99 /* 0b10011001 */>(c0,d0); + const vint8 b1 = shuffle<2,3,0,1>(a1); + const vint8 c1 = umax(a1,b1); + const vint8 d1 = umin(a1,b1); + const vint8 a2 = select<0xc3 /* 0b11000011 */>(c1,d1); + const vint8 b2 = shuffle<1,0,3,2>(a2); + const vint8 c2 = umax(a2,b2); + const vint8 d2 = umin(a2,b2); + const vint8 a3 = select<0xa5 /* 0b10100101 */>(c2,d2); + const vint8 b3 = shuffle4<1,0>(a3); + const vint8 c3 = umax(a3,b3); + const vint8 d3 = umin(a3,b3); + const vint8 a4 = select<0xf /* 0b00001111 */>(c3,d3); + const vint8 b4 = shuffle<2,3,0,1>(a4); + const vint8 c4 = umax(a4,b4); + const vint8 d4 = umin(a4,b4); + const vint8 a5 = select<0x33 /* 0b00110011 */>(c4,d4); + const vint8 b5 = shuffle<1,0,3,2>(a5); + const vint8 c5 = umax(a5,b5); + const vint8 d5 = umin(a5,b5); + const vint8 a6 = select<0x55 /* 0b01010101 */>(c5,d5); + return a6; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vint8& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vllong4_avx2.h b/thirdparty/embree/common/simd/vllong4_avx2.h new file mode 100644 index 000000000000..de3ebc16a728 --- /dev/null +++ b/thirdparty/embree/common/simd/vllong4_avx2.h @@ -0,0 +1,358 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 4-wide AVX2 64-bit long long type */ + template<> + struct vllong<4> + { + ALIGNED_STRUCT_(32); + + typedef vboold4 Bool; + + enum { size = 4 }; // number of SIMD elements + union { // data + __m256i v; + long long i[4]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong() {} + __forceinline vllong(const vllong4& t) { v = t.v; } + __forceinline vllong4& operator =(const vllong4& f) { v = f.v; return *this; } + + __forceinline vllong(const __m256i& t) { v = t; } + __forceinline operator __m256i() const { return v; } + __forceinline operator __m256d() const { return _mm256_castsi256_pd(v); } + + + __forceinline vllong(long long i) { + v = _mm256_set1_epi64x(i); + } + + __forceinline vllong(long long a, long long b, long long c, long long d) { + v = _mm256_set_epi64x(d,c,b,a); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong(ZeroTy) : v(_mm256_setzero_si256()) {} + __forceinline vllong(OneTy) : v(_mm256_set1_epi64x(1)) {} + __forceinline vllong(StepTy) : v(_mm256_set_epi64x(3,2,1,0)) {} + __forceinline vllong(ReverseStepTy) : v(_mm256_set_epi64x(0,1,2,3)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline void store_nt(void* __restrict__ ptr, const vllong4& a) { + _mm256_stream_ps((float*)ptr,_mm256_castsi256_ps(a)); + } + + static __forceinline vllong4 loadu(const void* addr) + { + return _mm256_loadu_si256((__m256i*)addr); + } + + static __forceinline vllong4 load(const vllong4* addr) { + return _mm256_load_si256((__m256i*)addr); + } + + static __forceinline vllong4 load(const long long* addr) { + return _mm256_load_si256((__m256i*)addr); + } + + static __forceinline void store(void* ptr, const vllong4& v) { + _mm256_store_si256((__m256i*)ptr,v); + } + + static __forceinline void storeu(void* ptr, const vllong4& v) { + _mm256_storeu_si256((__m256i*)ptr,v); + } + + static __forceinline void storeu(const vboold4& mask, long long* ptr, const vllong4& f) { +#if defined(__AVX512VL__) + _mm256_mask_storeu_epi64(ptr,mask,f); +#else + _mm256_maskstore_pd((double*)ptr,mask,_mm256_castsi256_pd(f)); +#endif + } + + static __forceinline void store(const vboold4& mask, void* ptr, const vllong4& f) { +#if defined(__AVX512VL__) + _mm256_mask_store_epi64(ptr,mask,f); +#else + _mm256_maskstore_pd((double*)ptr,mask,_mm256_castsi256_pd(f)); +#endif + } + + static __forceinline vllong4 broadcast64bit(size_t v) { + return _mm256_set1_epi64x(v); + } + + static __forceinline size_t extract64bit(const vllong4& v) + { + return _mm_cvtsi128_si64(_mm256_castsi256_si128(v)); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline long long& operator [](size_t index) { assert(index < 4); return i[index]; } + __forceinline const long long& operator [](size_t index) const { assert(index < 4); return i[index]; } + + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong4 select(const vboold4& m, const vllong4& t, const vllong4& f) { + #if defined(__AVX512VL__) + return _mm256_mask_blend_epi64(m, f, t); + #else + return _mm256_castpd_si256(_mm256_blendv_pd(_mm256_castsi256_pd(f), _mm256_castsi256_pd(t), m)); + #endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboold4 asBool(const vllong4& a) { return _mm256_movepi64_mask(a); } +#else + __forceinline vboold4 asBool(const vllong4& a) { return _mm256_castsi256_pd(a); } +#endif + + __forceinline vllong4 operator +(const vllong4& a) { return a; } + __forceinline vllong4 operator -(const vllong4& a) { return _mm256_sub_epi64(_mm256_setzero_si256(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong4 operator +(const vllong4& a, const vllong4& b) { return _mm256_add_epi64(a, b); } + __forceinline vllong4 operator +(const vllong4& a, long long b) { return a + vllong4(b); } + __forceinline vllong4 operator +(long long a, const vllong4& b) { return vllong4(a) + b; } + + __forceinline vllong4 operator -(const vllong4& a, const vllong4& b) { return _mm256_sub_epi64(a, b); } + __forceinline vllong4 operator -(const vllong4& a, long long b) { return a - vllong4(b); } + __forceinline vllong4 operator -(long long a, const vllong4& b) { return vllong4(a) - b; } + + /* only low 32bit part */ + __forceinline vllong4 operator *(const vllong4& a, const vllong4& b) { return _mm256_mul_epi32(a, b); } + __forceinline vllong4 operator *(const vllong4& a, long long b) { return a * vllong4(b); } + __forceinline vllong4 operator *(long long a, const vllong4& b) { return vllong4(a) * b; } + + __forceinline vllong4 operator &(const vllong4& a, const vllong4& b) { return _mm256_and_si256(a, b); } + __forceinline vllong4 operator &(const vllong4& a, long long b) { return a & vllong4(b); } + __forceinline vllong4 operator &(long long a, const vllong4& b) { return vllong4(a) & b; } + + __forceinline vllong4 operator |(const vllong4& a, const vllong4& b) { return _mm256_or_si256(a, b); } + __forceinline vllong4 operator |(const vllong4& a, long long b) { return a | vllong4(b); } + __forceinline vllong4 operator |(long long a, const vllong4& b) { return vllong4(a) | b; } + + __forceinline vllong4 operator ^(const vllong4& a, const vllong4& b) { return _mm256_xor_si256(a, b); } + __forceinline vllong4 operator ^(const vllong4& a, long long b) { return a ^ vllong4(b); } + __forceinline vllong4 operator ^(long long a, const vllong4& b) { return vllong4(a) ^ b; } + + __forceinline vllong4 operator <<(const vllong4& a, long long n) { return _mm256_slli_epi64(a, (int)n); } + //__forceinline vllong4 operator >>(const vllong4& a, long long n) { return _mm256_srai_epi64(a, n); } + + __forceinline vllong4 operator <<(const vllong4& a, const vllong4& n) { return _mm256_sllv_epi64(a, n); } + //__forceinline vllong4 operator >>(const vllong4& a, const vllong4& n) { return _mm256_srav_epi64(a, n); } + //__forceinline vllong4 sra(const vllong4& a, long long b) { return _mm256_srai_epi64(a, b); } + + __forceinline vllong4 srl(const vllong4& a, long long b) { return _mm256_srli_epi64(a, (int)b); } + + //__forceinline vllong4 min(const vllong4& a, const vllong4& b) { return _mm256_min_epi64(a, b); } + //__forceinline vllong4 min(const vllong4& a, long long b) { return min(a,vllong4(b)); } + //__forceinline vllong4 min(long long a, const vllong4& b) { return min(vllong4(a),b); } + + //__forceinline vllong4 max(const vllong4& a, const vllong4& b) { return _mm256_max_epi64(a, b); } + //__forceinline vllong4 max(const vllong4& a, long long b) { return max(a,vllong4(b)); } + //__forceinline vllong4 max(long long a, const vllong4& b) { return max(vllong4(a),b); } + +#if defined(__AVX512VL__) + __forceinline vllong4 mask_and(const vboold4& m, const vllong4& c, const vllong4& a, const vllong4& b) { return _mm256_mask_and_epi64(c,m,a,b); } + __forceinline vllong4 mask_or (const vboold4& m, const vllong4& c, const vllong4& a, const vllong4& b) { return _mm256_mask_or_epi64(c,m,a,b); } +#else + __forceinline vllong4 mask_and(const vboold4& m, const vllong4& c, const vllong4& a, const vllong4& b) { return select(m, a & b, c); } + __forceinline vllong4 mask_or (const vboold4& m, const vllong4& c, const vllong4& a, const vllong4& b) { return select(m, a | b, c); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong4& operator +=(vllong4& a, const vllong4& b) { return a = a + b; } + __forceinline vllong4& operator +=(vllong4& a, long long b) { return a = a + b; } + + __forceinline vllong4& operator -=(vllong4& a, const vllong4& b) { return a = a - b; } + __forceinline vllong4& operator -=(vllong4& a, long long b) { return a = a - b; } + + __forceinline vllong4& operator *=(vllong4& a, const vllong4& b) { return a = a * b; } + __forceinline vllong4& operator *=(vllong4& a, long long b) { return a = a * b; } + + __forceinline vllong4& operator &=(vllong4& a, const vllong4& b) { return a = a & b; } + __forceinline vllong4& operator &=(vllong4& a, long long b) { return a = a & b; } + + __forceinline vllong4& operator |=(vllong4& a, const vllong4& b) { return a = a | b; } + __forceinline vllong4& operator |=(vllong4& a, long long b) { return a = a | b; } + + __forceinline vllong4& operator <<=(vllong4& a, long long b) { return a = a << b; } + //__forceinline vllong4& operator >>=(vllong4& a, long long b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboold4 operator ==(const vllong4& a, const vllong4& b) { return _mm256_cmp_epi64_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboold4 operator !=(const vllong4& a, const vllong4& b) { return _mm256_cmp_epi64_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboold4 operator < (const vllong4& a, const vllong4& b) { return _mm256_cmp_epi64_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboold4 operator >=(const vllong4& a, const vllong4& b) { return _mm256_cmp_epi64_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboold4 operator > (const vllong4& a, const vllong4& b) { return _mm256_cmp_epi64_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboold4 operator <=(const vllong4& a, const vllong4& b) { return _mm256_cmp_epi64_mask(a,b,_MM_CMPINT_LE); } +#else + __forceinline vboold4 operator ==(const vllong4& a, const vllong4& b) { return _mm256_cmpeq_epi64(a,b); } + __forceinline vboold4 operator !=(const vllong4& a, const vllong4& b) { return !(a == b); } + __forceinline vboold4 operator > (const vllong4& a, const vllong4& b) { return _mm256_cmpgt_epi64(a,b); } + __forceinline vboold4 operator < (const vllong4& a, const vllong4& b) { return _mm256_cmpgt_epi64(b,a); } + __forceinline vboold4 operator >=(const vllong4& a, const vllong4& b) { return !(a < b); } + __forceinline vboold4 operator <=(const vllong4& a, const vllong4& b) { return !(a > b); } +#endif + + __forceinline vboold4 operator ==(const vllong4& a, long long b) { return a == vllong4(b); } + __forceinline vboold4 operator ==(long long a, const vllong4& b) { return vllong4(a) == b; } + + __forceinline vboold4 operator !=(const vllong4& a, long long b) { return a != vllong4(b); } + __forceinline vboold4 operator !=(long long a, const vllong4& b) { return vllong4(a) != b; } + + __forceinline vboold4 operator > (const vllong4& a, long long b) { return a > vllong4(b); } + __forceinline vboold4 operator > (long long a, const vllong4& b) { return vllong4(a) > b; } + + __forceinline vboold4 operator < (const vllong4& a, long long b) { return a < vllong4(b); } + __forceinline vboold4 operator < (long long a, const vllong4& b) { return vllong4(a) < b; } + + __forceinline vboold4 operator >=(const vllong4& a, long long b) { return a >= vllong4(b); } + __forceinline vboold4 operator >=(long long a, const vllong4& b) { return vllong4(a) >= b; } + + __forceinline vboold4 operator <=(const vllong4& a, long long b) { return a <= vllong4(b); } + __forceinline vboold4 operator <=(long long a, const vllong4& b) { return vllong4(a) <= b; } + + __forceinline vboold4 eq(const vllong4& a, const vllong4& b) { return a == b; } + __forceinline vboold4 ne(const vllong4& a, const vllong4& b) { return a != b; } + __forceinline vboold4 lt(const vllong4& a, const vllong4& b) { return a < b; } + __forceinline vboold4 ge(const vllong4& a, const vllong4& b) { return a >= b; } + __forceinline vboold4 gt(const vllong4& a, const vllong4& b) { return a > b; } + __forceinline vboold4 le(const vllong4& a, const vllong4& b) { return a <= b; } + +#if defined(__AVX512VL__) + __forceinline vboold4 eq(const vboold4& mask, const vllong4& a, const vllong4& b) { return _mm256_mask_cmp_epi64_mask(mask, a, b, _MM_CMPINT_EQ); } + __forceinline vboold4 ne(const vboold4& mask, const vllong4& a, const vllong4& b) { return _mm256_mask_cmp_epi64_mask(mask, a, b, _MM_CMPINT_NE); } + __forceinline vboold4 lt(const vboold4& mask, const vllong4& a, const vllong4& b) { return _mm256_mask_cmp_epi64_mask(mask, a, b, _MM_CMPINT_LT); } + __forceinline vboold4 ge(const vboold4& mask, const vllong4& a, const vllong4& b) { return _mm256_mask_cmp_epi64_mask(mask, a, b, _MM_CMPINT_GE); } + __forceinline vboold4 gt(const vboold4& mask, const vllong4& a, const vllong4& b) { return _mm256_mask_cmp_epi64_mask(mask, a, b, _MM_CMPINT_GT); } + __forceinline vboold4 le(const vboold4& mask, const vllong4& a, const vllong4& b) { return _mm256_mask_cmp_epi64_mask(mask, a, b, _MM_CMPINT_LE); } +#else + __forceinline vboold4 eq(const vboold4& mask, const vllong4& a, const vllong4& b) { return mask & (a == b); } + __forceinline vboold4 ne(const vboold4& mask, const vllong4& a, const vllong4& b) { return mask & (a != b); } + __forceinline vboold4 lt(const vboold4& mask, const vllong4& a, const vllong4& b) { return mask & (a < b); } + __forceinline vboold4 ge(const vboold4& mask, const vllong4& a, const vllong4& b) { return mask & (a >= b); } + __forceinline vboold4 gt(const vboold4& mask, const vllong4& a, const vllong4& b) { return mask & (a > b); } + __forceinline vboold4 le(const vboold4& mask, const vllong4& a, const vllong4& b) { return mask & (a <= b); } +#endif + + __forceinline void xchg(const vboold4& m, vllong4& a, vllong4& b) { + const vllong4 c = a; a = select(m,b,a); b = select(m,c,b); + } + + __forceinline vboold4 test(const vllong4& a, const vllong4& b) { +#if defined(__AVX512VL__) + return _mm256_test_epi64_mask(a,b); +#else + return _mm256_testz_si256(a,b); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vllong4 shuffle(const vllong4& v) { + return _mm256_castpd_si256(_mm256_permute_pd(_mm256_castsi256_pd(v), (i1 << 3) | (i0 << 2) | (i1 << 1) | i0)); + } + + template + __forceinline vllong4 shuffle(const vllong4& v) { + return shuffle(v); + } + + template + __forceinline vllong4 shuffle2(const vllong4& v) { + return _mm256_castpd_si256(_mm256_permute2f128_pd(_mm256_castsi256_pd(v), _mm256_castsi256_pd(v), (i1 << 4) | i0)); + } + + __forceinline long long toScalar(const vllong4& v) { + return _mm_cvtsi128_si64(_mm256_castsi256_si128(v)); + } + +#if defined(__AVX512VL__) + __forceinline vllong4 permute(const vllong4& a, const __m256i& index) { + // workaround for GCC 7.x +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__) + return _mm256_permutex2var_epi64(a,index,a); +#else + return _mm256_permutexvar_epi64(index,a); +#endif + } + + __forceinline vllong4 permutex2var(const vllong4& index, const vllong4& a, const vllong4& b) { + return _mm256_permutex2var_epi64(a,index,b); + } + +#endif + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + + __forceinline vllong4 vreduce_and2(const vllong4& x) { return x & shuffle<1,0>(x); } + __forceinline vllong4 vreduce_and (const vllong4& y) { const vllong4 x = vreduce_and2(y); return x & shuffle2<1,0>(x); } + + __forceinline vllong4 vreduce_or2(const vllong4& x) { return x | shuffle<1,0>(x); } + __forceinline vllong4 vreduce_or (const vllong4& y) { const vllong4 x = vreduce_or2(y); return x | shuffle2<1,0>(x); } + + __forceinline vllong4 vreduce_add2(const vllong4& x) { return x + shuffle<1,0>(x); } + __forceinline vllong4 vreduce_add (const vllong4& y) { const vllong4 x = vreduce_add2(y); return x + shuffle2<1,0>(x); } + + __forceinline long long reduce_add(const vllong4& a) { return toScalar(vreduce_add(a)); } + __forceinline long long reduce_or (const vllong4& a) { return toScalar(vreduce_or(a)); } + __forceinline long long reduce_and(const vllong4& a) { return toScalar(vreduce_and(a)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vllong4& v) + { + cout << "<" << v[0]; + for (size_t i=1; i<4; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vllong8_avx512.h b/thirdparty/embree/common/simd/vllong8_avx512.h new file mode 100644 index 000000000000..4a724de0621b --- /dev/null +++ b/thirdparty/embree/common/simd/vllong8_avx512.h @@ -0,0 +1,381 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX-512 64-bit long long type */ + template<> + struct vllong<8> + { + ALIGNED_STRUCT_(64); + + typedef vboold8 Bool; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m512i v; + long long i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong() {} + __forceinline vllong(const vllong8& t) { v = t.v; } + __forceinline vllong8& operator =(const vllong8& f) { v = f.v; return *this; } + + __forceinline vllong(const __m512i& t) { v = t; } + __forceinline operator __m512i() const { return v; } + __forceinline operator __m256i() const { return _mm512_castsi512_si256(v); } + + __forceinline vllong(long long i) { + v = _mm512_set1_epi64(i); + } + + __forceinline vllong(long long a, long long b, long long c, long long d) { + v = _mm512_set4_epi64(d,c,b,a); + } + + __forceinline vllong(long long a0, long long a1, long long a2, long long a3, + long long a4, long long a5, long long a6, long long a7) + { + v = _mm512_set_epi64(a7,a6,a5,a4,a3,a2,a1,a0); + } + + __forceinline vllong(const vllong<4>& i) { + v = _mm512_broadcast_i64x4(i); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong(ZeroTy) : v(_mm512_setzero_epi32()) {} + __forceinline vllong(OneTy) : v(_mm512_set1_epi64(1)) {} + __forceinline vllong(StepTy) : v(_mm512_set_epi64(7,6,5,4,3,2,1,0)) {} + __forceinline vllong(ReverseStepTy) : v(_mm512_setr_epi64(7,6,5,4,3,2,1,0)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline void store_nt(void* __restrict__ ptr, const vllong8& a) { + _mm512_stream_si512((__m512i*)ptr,a); + } + + static __forceinline vllong8 loadu(const void* addr) { + return _mm512_loadu_si512(addr); + } + + static __forceinline vllong8 load(const vllong8* addr) { + return _mm512_load_si512(addr); + } + + static __forceinline vllong8 load(const long long* addr) { + return _mm512_load_si512(addr); + } + + static __forceinline vllong8 load(const unsigned char* ptr) { + return _mm512_cvtepu8_epi64(*(__m128i*)ptr); + } + + static __forceinline void store(void* ptr, const vllong8& v) { + _mm512_store_si512(ptr,v); + } + + static __forceinline void storeu(void* ptr, const vllong8& v) { + _mm512_storeu_si512(ptr,v); + } + + static __forceinline void storeu(const vboold8& mask, long long* ptr, const vllong8& f) { + _mm512_mask_storeu_epi64(ptr,mask,f); + } + + static __forceinline void store(const vboold8& mask, void* addr, const vllong8& v2) { + _mm512_mask_store_epi64(addr,mask,v2); + } + + /* pass by value to avoid compiler generating inefficient code */ + static __forceinline void storeu_compact(const vboold8 mask, void* addr, const vllong8& reg) { + _mm512_mask_compressstoreu_epi64(addr,mask,reg); + } + + static __forceinline vllong8 compact64bit(const vboold8& mask, vllong8& v) { + return _mm512_mask_compress_epi64(v,mask,v); + } + + static __forceinline vllong8 compact64bit(const vboold8& mask, vllong8& dest, const vllong8& source) { + return _mm512_mask_compress_epi64(dest,mask,source); + } + + static __forceinline vllong8 compact(const vboold8& mask, vllong8& v) { + return _mm512_mask_compress_epi64(v,mask,v); + } + + static __forceinline vllong8 compact(const vboold8& mask, const vllong8& a, vllong8& b) { + return _mm512_mask_compress_epi64(a,mask,b); + } + + static __forceinline vllong8 expand(const vboold8& mask, const vllong8& a, vllong8& b) { + return _mm512_mask_expand_epi64(b,mask,a); + } + + static __forceinline vllong8 broadcast64bit(size_t v) { + return _mm512_set1_epi64(v); + } + + static __forceinline size_t extract64bit(const vllong8& v) + { + return _mm_cvtsi128_si64(_mm512_castsi512_si128(v)); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline long long& operator [](size_t index) { assert(index < 8); return i[index]; } + __forceinline const long long& operator [](size_t index) const { assert(index < 8); return i[index]; } + + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8 asBool(const vllong8& a) { return _mm512_movepi64_mask(a); } + + __forceinline vllong8 operator +(const vllong8& a) { return a; } + __forceinline vllong8 operator -(const vllong8& a) { return _mm512_sub_epi64(_mm512_setzero_epi32(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong8 operator +(const vllong8& a, const vllong8& b) { return _mm512_add_epi64(a, b); } + __forceinline vllong8 operator +(const vllong8& a, long long b) { return a + vllong8(b); } + __forceinline vllong8 operator +(long long a, const vllong8& b) { return vllong8(a) + b; } + + __forceinline vllong8 operator -(const vllong8& a, const vllong8& b) { return _mm512_sub_epi64(a, b); } + __forceinline vllong8 operator -(const vllong8& a, long long b) { return a - vllong8(b); } + __forceinline vllong8 operator -(long long a, const vllong8& b) { return vllong8(a) - b; } + + __forceinline vllong8 operator *(const vllong8& a, const vllong8& b) { return _mm512_mullo_epi64(a, b); } + __forceinline vllong8 operator *(const vllong8& a, long long b) { return a * vllong8(b); } + __forceinline vllong8 operator *(long long a, const vllong8& b) { return vllong8(a) * b; } + + __forceinline vllong8 operator &(const vllong8& a, const vllong8& b) { return _mm512_and_epi64(a, b); } + __forceinline vllong8 operator &(const vllong8& a, long long b) { return a & vllong8(b); } + __forceinline vllong8 operator &(long long a, const vllong8& b) { return vllong8(a) & b; } + + __forceinline vllong8 operator |(const vllong8& a, const vllong8& b) { return _mm512_or_epi64(a, b); } + __forceinline vllong8 operator |(const vllong8& a, long long b) { return a | vllong8(b); } + __forceinline vllong8 operator |(long long a, const vllong8& b) { return vllong8(a) | b; } + + __forceinline vllong8 operator ^(const vllong8& a, const vllong8& b) { return _mm512_xor_epi64(a, b); } + __forceinline vllong8 operator ^(const vllong8& a, long long b) { return a ^ vllong8(b); } + __forceinline vllong8 operator ^(long long a, const vllong8& b) { return vllong8(a) ^ b; } + + __forceinline vllong8 operator <<(const vllong8& a, long long n) { return _mm512_slli_epi64(a, n); } + __forceinline vllong8 operator >>(const vllong8& a, long long n) { return _mm512_srai_epi64(a, n); } + + __forceinline vllong8 operator <<(const vllong8& a, const vllong8& n) { return _mm512_sllv_epi64(a, n); } + __forceinline vllong8 operator >>(const vllong8& a, const vllong8& n) { return _mm512_srav_epi64(a, n); } + + __forceinline vllong8 sll (const vllong8& a, long long b) { return _mm512_slli_epi64(a, b); } + __forceinline vllong8 sra (const vllong8& a, long long b) { return _mm512_srai_epi64(a, b); } + __forceinline vllong8 srl (const vllong8& a, long long b) { return _mm512_srli_epi64(a, b); } + + __forceinline vllong8 min(const vllong8& a, const vllong8& b) { return _mm512_min_epi64(a, b); } + __forceinline vllong8 min(const vllong8& a, long long b) { return min(a,vllong8(b)); } + __forceinline vllong8 min(long long a, const vllong8& b) { return min(vllong8(a),b); } + + __forceinline vllong8 max(const vllong8& a, const vllong8& b) { return _mm512_max_epi64(a, b); } + __forceinline vllong8 max(const vllong8& a, long long b) { return max(a,vllong8(b)); } + __forceinline vllong8 max(long long a, const vllong8& b) { return max(vllong8(a),b); } + + __forceinline vllong8 mask_add(const vboold8& m, const vllong8& c, const vllong8& a, const vllong8& b) { return _mm512_mask_add_epi64(c,m,a,b); } + __forceinline vllong8 mask_sub(const vboold8& m, const vllong8& c, const vllong8& a, const vllong8& b) { return _mm512_mask_sub_epi64(c,m,a,b); } + + __forceinline vllong8 mask_and(const vboold8& m, const vllong8& c, const vllong8& a, const vllong8& b) { return _mm512_mask_and_epi64(c,m,a,b); } + __forceinline vllong8 mask_or (const vboold8& m, const vllong8& c, const vllong8& a, const vllong8& b) { return _mm512_mask_or_epi64(c,m,a,b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong8& operator +=(vllong8& a, const vllong8& b) { return a = a + b; } + __forceinline vllong8& operator +=(vllong8& a, long long b) { return a = a + b; } + + __forceinline vllong8& operator -=(vllong8& a, const vllong8& b) { return a = a - b; } + __forceinline vllong8& operator -=(vllong8& a, long long b) { return a = a - b; } + + __forceinline vllong8& operator *=(vllong8& a, const vllong8& b) { return a = a * b; } + __forceinline vllong8& operator *=(vllong8& a, long long b) { return a = a * b; } + + __forceinline vllong8& operator &=(vllong8& a, const vllong8& b) { return a = a & b; } + __forceinline vllong8& operator &=(vllong8& a, long long b) { return a = a & b; } + + __forceinline vllong8& operator |=(vllong8& a, const vllong8& b) { return a = a | b; } + __forceinline vllong8& operator |=(vllong8& a, long long b) { return a = a | b; } + + __forceinline vllong8& operator <<=(vllong8& a, long long b) { return a = a << b; } + __forceinline vllong8& operator >>=(vllong8& a, long long b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboold8 operator ==(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboold8 operator ==(const vllong8& a, long long b) { return a == vllong8(b); } + __forceinline vboold8 operator ==(long long a, const vllong8& b) { return vllong8(a) == b; } + + __forceinline vboold8 operator !=(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboold8 operator !=(const vllong8& a, long long b) { return a != vllong8(b); } + __forceinline vboold8 operator !=(long long a, const vllong8& b) { return vllong8(a) != b; } + + __forceinline vboold8 operator < (const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboold8 operator < (const vllong8& a, long long b) { return a < vllong8(b); } + __forceinline vboold8 operator < (long long a, const vllong8& b) { return vllong8(a) < b; } + + __forceinline vboold8 operator >=(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboold8 operator >=(const vllong8& a, long long b) { return a >= vllong8(b); } + __forceinline vboold8 operator >=(long long a, const vllong8& b) { return vllong8(a) >= b; } + + __forceinline vboold8 operator > (const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboold8 operator > (const vllong8& a, long long b) { return a > vllong8(b); } + __forceinline vboold8 operator > (long long a, const vllong8& b) { return vllong8(a) > b; } + + __forceinline vboold8 operator <=(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboold8 operator <=(const vllong8& a, long long b) { return a <= vllong8(b); } + __forceinline vboold8 operator <=(long long a, const vllong8& b) { return vllong8(a) <= b; } + + __forceinline vboold8 eq(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboold8 ne(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboold8 lt(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboold8 ge(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboold8 gt(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboold8 le(const vllong8& a, const vllong8& b) { return _mm512_cmp_epi64_mask(a,b,_MM_CMPINT_LE); } + + __forceinline vboold8 eq(const vboold8 mask, const vllong8& a, const vllong8& b) { return _mm512_mask_cmp_epi64_mask(mask,a,b,_MM_CMPINT_EQ); } + __forceinline vboold8 ne(const vboold8 mask, const vllong8& a, const vllong8& b) { return _mm512_mask_cmp_epi64_mask(mask,a,b,_MM_CMPINT_NE); } + __forceinline vboold8 lt(const vboold8 mask, const vllong8& a, const vllong8& b) { return _mm512_mask_cmp_epi64_mask(mask,a,b,_MM_CMPINT_LT); } + __forceinline vboold8 ge(const vboold8 mask, const vllong8& a, const vllong8& b) { return _mm512_mask_cmp_epi64_mask(mask,a,b,_MM_CMPINT_GE); } + __forceinline vboold8 gt(const vboold8 mask, const vllong8& a, const vllong8& b) { return _mm512_mask_cmp_epi64_mask(mask,a,b,_MM_CMPINT_GT); } + __forceinline vboold8 le(const vboold8 mask, const vllong8& a, const vllong8& b) { return _mm512_mask_cmp_epi64_mask(mask,a,b,_MM_CMPINT_LE); } + + __forceinline vllong8 select(const vboold8& m, const vllong8& t, const vllong8& f) { + return _mm512_mask_or_epi64(f,m,t,t); + } + + __forceinline void xchg(const vboold8& m, vllong8& a, vllong8& b) { + const vllong8 c = a; a = select(m,b,a); b = select(m,c,b); + } + + __forceinline vboold8 test(const vboold8& m, const vllong8& a, const vllong8& b) { + return _mm512_mask_test_epi64_mask(m,a,b); + } + + __forceinline vboold8 test(const vllong8& a, const vllong8& b) { + return _mm512_test_epi64_mask(a,b); + } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vllong8 shuffle(const vllong8& v) { + return _mm512_castpd_si512(_mm512_permute_pd(_mm512_castsi512_pd(v), (i1 << 7) | (i0 << 6) | (i1 << 5) | (i0 << 4) | (i1 << 3) | (i0 << 2) | (i1 << 1) | i0)); + } + + template + __forceinline vllong8 shuffle(const vllong8& v) { + return shuffle(v); + } + + template + __forceinline vllong8 shuffle(const vllong8& v) { + return _mm512_permutex_epi64(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vllong8 shuffle4(const vllong8& v) { + return _mm512_shuffle_i64x2(v, v, _MM_SHUFFLE(i1*2+1, i1*2, i0*2+1, i0*2)); + } + + template + __forceinline vllong8 shuffle4(const vllong8& v) { + return shuffle4(v); + } + + template + __forceinline vllong8 align_shift_right(const vllong8& a, const vllong8& b) { + return _mm512_alignr_epi64(a, b, i); + }; + + __forceinline long long toScalar(const vllong8& v) { + return _mm_cvtsi128_si64(_mm512_castsi512_si128(v)); + } + + __forceinline vllong8 zeroExtend32Bit(const __m512i& a) { + return _mm512_cvtepu32_epi64(_mm512_castsi512_si256(a)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong8 vreduce_min2(vllong8 x) { return min(x, shuffle<1,0,3,2>(x)); } + __forceinline vllong8 vreduce_min4(vllong8 x) { x = vreduce_min2(x); return min(x, shuffle<2,3,0,1>(x)); } + __forceinline vllong8 vreduce_min (vllong8 x) { x = vreduce_min4(x); return min(x, shuffle4<1,0>(x)); } + + __forceinline vllong8 vreduce_max2(vllong8 x) { return max(x, shuffle<1,0,3,2>(x)); } + __forceinline vllong8 vreduce_max4(vllong8 x) { x = vreduce_max2(x); return max(x, shuffle<2,3,0,1>(x)); } + __forceinline vllong8 vreduce_max (vllong8 x) { x = vreduce_max4(x); return max(x, shuffle4<1,0>(x)); } + + __forceinline vllong8 vreduce_and2(vllong8 x) { return x & shuffle<1,0,3,2>(x); } + __forceinline vllong8 vreduce_and4(vllong8 x) { x = vreduce_and2(x); return x & shuffle<2,3,0,1>(x); } + __forceinline vllong8 vreduce_and (vllong8 x) { x = vreduce_and4(x); return x & shuffle4<1,0>(x); } + + __forceinline vllong8 vreduce_or2(vllong8 x) { return x | shuffle<1,0,3,2>(x); } + __forceinline vllong8 vreduce_or4(vllong8 x) { x = vreduce_or2(x); return x | shuffle<2,3,0,1>(x); } + __forceinline vllong8 vreduce_or (vllong8 x) { x = vreduce_or4(x); return x | shuffle4<1,0>(x); } + + __forceinline vllong8 vreduce_add2(vllong8 x) { return x + shuffle<1,0,3,2>(x); } + __forceinline vllong8 vreduce_add4(vllong8 x) { x = vreduce_add2(x); return x + shuffle<2,3,0,1>(x); } + __forceinline vllong8 vreduce_add (vllong8 x) { x = vreduce_add4(x); return x + shuffle4<1,0>(x); } + + __forceinline long long reduce_min(const vllong8& v) { return toScalar(vreduce_min(v)); } + __forceinline long long reduce_max(const vllong8& v) { return toScalar(vreduce_max(v)); } + __forceinline long long reduce_and(const vllong8& v) { return toScalar(vreduce_and(v)); } + __forceinline long long reduce_or (const vllong8& v) { return toScalar(vreduce_or (v)); } + __forceinline long long reduce_add(const vllong8& v) { return toScalar(vreduce_add(v)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Memory load and store operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vllong8 permute(const vllong8& v, const vllong8& index) { + return _mm512_permutexvar_epi64(index,v); + } + + __forceinline vllong8 reverse(const vllong8& a) { + return permute(a,vllong8(reverse_step)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vllong8& v) + { + cout << "<" << v[0]; + for (size_t i=1; i<8; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vuint16_avx512.h b/thirdparty/embree/common/simd/vuint16_avx512.h new file mode 100644 index 000000000000..c5a2bb047840 --- /dev/null +++ b/thirdparty/embree/common/simd/vuint16_avx512.h @@ -0,0 +1,443 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 16-wide AVX-512 unsigned integer type */ + template<> + struct vuint<16> + { + ALIGNED_STRUCT_(64); + + typedef vboolf16 Bool; + typedef vuint16 UInt; + typedef vfloat16 Float; + + enum { size = 16 }; // number of SIMD elements + union { // data + __m512i v; + unsigned int i[16]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint() {} + __forceinline vuint(const vuint16& t) { v = t.v; } + __forceinline vuint16& operator =(const vuint16& f) { v = f.v; return *this; } + + __forceinline vuint(const __m512i& t) { v = t; } + __forceinline operator __m512i() const { return v; } + __forceinline operator __m256i() const { return _mm512_castsi512_si256(v); } + + __forceinline vuint(unsigned int i) { + v = _mm512_set1_epi32(i); + } + + __forceinline vuint(const vuint4& i) { + v = _mm512_broadcast_i32x4(i); + } + + __forceinline vuint(const vuint8& i) { + v = _mm512_castps_si512(_mm512_castpd_ps(_mm512_broadcast_f64x4(_mm256_castsi256_pd(i)))); + } + + __forceinline vuint(unsigned int a, unsigned int b, unsigned int c, unsigned int d) { + v = _mm512_set4_epi32(d,c,b,a); + } + + __forceinline vuint(unsigned int a0 , unsigned int a1 , unsigned int a2 , unsigned int a3, + unsigned int a4 , unsigned int a5 , unsigned int a6 , unsigned int a7, + unsigned int a8 , unsigned int a9 , unsigned int a10, unsigned int a11, + unsigned int a12, unsigned int a13, unsigned int a14, unsigned int a15) + { + v = _mm512_set_epi32(a15,a14,a13,a12,a11,a10,a9,a8,a7,a6,a5,a4,a3,a2,a1,a0); + } + + __forceinline explicit vuint(const __m512& f) { + v = _mm512_cvtps_epu32(f); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint(ZeroTy) : v(_mm512_setzero_epi32()) {} + __forceinline vuint(OneTy) : v(_mm512_set1_epi32(1)) {} + __forceinline vuint(StepTy) : v(_mm512_set_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) {} + __forceinline vuint(ReverseStepTy) : v(_mm512_setr_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline void store_nt(void* __restrict__ ptr, const vuint16& a) { + _mm512_stream_si512((__m512i*)ptr,a); + } + + static __forceinline vuint16 loadu(const void* addr) + { + return _mm512_loadu_si512(addr); + } + + static __forceinline vuint16 loadu(const unsigned char* ptr) { return _mm512_cvtepu8_epi32(_mm_loadu_si128((__m128i*)ptr)); } + static __forceinline vuint16 loadu(const unsigned short* ptr) { return _mm512_cvtepu16_epi32(_mm256_loadu_si256((__m256i*)ptr)); } + + static __forceinline vuint16 load(const vuint16* addr) { + return _mm512_load_si512(addr); + } + + static __forceinline vuint16 load(const unsigned int* addr) { + return _mm512_load_si512(addr); + } + + static __forceinline vuint16 load(unsigned short* ptr) { return _mm512_cvtepu16_epi32(*(__m256i*)ptr); } + + + static __forceinline void store(void* ptr, const vuint16& v) { + _mm512_store_si512(ptr,v); + } + + static __forceinline void storeu(void* ptr, const vuint16& v) { + _mm512_storeu_si512(ptr,v); + } + + static __forceinline void storeu(const vboolf16& mask, void* ptr, const vuint16& f) { + _mm512_mask_storeu_epi32(ptr,mask,f); + } + + static __forceinline void store(const vboolf16& mask, void* addr, const vuint16& v2) { + _mm512_mask_store_epi32(addr,mask,v2); + } + + /* pass by value to avoid compiler generating inefficient code */ + static __forceinline void storeu_compact(const vboolf16 mask, void* addr, const vuint16 reg) { + _mm512_mask_compressstoreu_epi32(addr,mask,reg); + } + + static __forceinline void storeu_compact_single(const vboolf16 mask, void* addr, vuint16 reg) { + //_mm512_mask_compressstoreu_epi32(addr,mask,reg); + *(float*)addr = mm512_cvtss_f32(_mm512_mask_compress_ps(_mm512_castsi512_ps(reg),mask,_mm512_castsi512_ps(reg))); + } + + static __forceinline vuint16 compact64bit(const vboolf16& mask, vuint16& v) { + return _mm512_mask_compress_epi64(v,mask,v); + } + + static __forceinline vuint16 compact(const vboolf16& mask, vuint16& v) { + return _mm512_mask_compress_epi32(v,mask,v); + } + + static __forceinline vuint16 compact(const vboolf16& mask, const vuint16& a, vuint16& b) { + return _mm512_mask_compress_epi32(a,mask,b); + } + + static __forceinline vuint16 expand(const vboolf16& mask, const vuint16& a, vuint16& b) { + return _mm512_mask_expand_epi32(b,mask,a); + } + + template + static __forceinline vuint16 gather(const unsigned int* ptr, const vint16& index) { + return _mm512_i32gather_epi32(index,ptr,scale); + } + + template + static __forceinline vuint16 gather(const vboolf16& mask, const unsigned int* ptr, const vint16& index) { + return _mm512_mask_i32gather_epi32(_mm512_undefined_epi32(),mask,index,ptr,scale); + } + + template + static __forceinline vuint16 gather(const vboolf16& mask, vuint16& dest, const unsigned int* ptr, const vint16& index) { + return _mm512_mask_i32gather_epi32(dest,mask,index,ptr,scale); + } + + template + static __forceinline void scatter(unsigned int* ptr, const vint16& index, const vuint16& v) { + _mm512_i32scatter_epi32((int*)ptr,index,v,scale); + } + + template + static __forceinline void scatter(const vboolf16& mask, unsigned int* ptr, const vint16& index, const vuint16& v) { + _mm512_mask_i32scatter_epi32((int*)ptr,mask,index,v,scale); + } + + static __forceinline vuint16 broadcast64bit(size_t v) { + return _mm512_set1_epi64(v); + } + + static __forceinline size_t extract64bit(const vuint16& v) + { + return _mm_cvtsi128_si64(_mm512_castsi512_si128(v)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline unsigned int& operator [](size_t index) { assert(index < 16); return i[index]; } + __forceinline const unsigned int& operator [](size_t index) const { assert(index < 16); return i[index]; } + + __forceinline unsigned int uint (size_t index) const { assert(index < 16); return ((unsigned int*)i)[index]; } + __forceinline size_t& uint64_t(size_t index) const { assert(index < 8); return ((size_t*)i)[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 asBool(const vuint16& a) { return _mm512_movepi32_mask(a); } + + __forceinline vuint16 operator +(const vuint16& a) { return a; } + __forceinline vuint16 operator -(const vuint16& a) { return _mm512_sub_epi32(_mm512_setzero_epi32(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint16 operator +(const vuint16& a, const vuint16& b) { return _mm512_add_epi32(a, b); } + __forceinline vuint16 operator +(const vuint16& a, unsigned int b) { return a + vuint16(b); } + __forceinline vuint16 operator +(unsigned int a, const vuint16& b) { return vuint16(a) + b; } + + __forceinline vuint16 operator -(const vuint16& a, const vuint16& b) { return _mm512_sub_epi32(a, b); } + __forceinline vuint16 operator -(const vuint16& a, unsigned int b) { return a - vuint16(b); } + __forceinline vuint16 operator -(unsigned int a, const vuint16& b) { return vuint16(a) - b; } + + __forceinline vuint16 operator *(const vuint16& a, const vuint16& b) { return _mm512_mul_epu32(a, b); } + __forceinline vuint16 operator *(const vuint16& a, unsigned int b) { return a * vuint16(b); } + __forceinline vuint16 operator *(unsigned int a, const vuint16& b) { return vuint16(a) * b; } + + __forceinline vuint16 operator &(const vuint16& a, const vuint16& b) { return _mm512_and_epi32(a, b); } + __forceinline vuint16 operator &(const vuint16& a, unsigned int b) { return a & vuint16(b); } + __forceinline vuint16 operator &(unsigned int a, const vuint16& b) { return vuint16(a) & b; } + + __forceinline vuint16 operator |(const vuint16& a, const vuint16& b) { return _mm512_or_epi32(a, b); } + __forceinline vuint16 operator |(const vuint16& a, unsigned int b) { return a | vuint16(b); } + __forceinline vuint16 operator |(unsigned int a, const vuint16& b) { return vuint16(a) | b; } + + __forceinline vuint16 operator ^(const vuint16& a, const vuint16& b) { return _mm512_xor_epi32(a, b); } + __forceinline vuint16 operator ^(const vuint16& a, unsigned int b) { return a ^ vuint16(b); } + __forceinline vuint16 operator ^(unsigned int a, const vuint16& b) { return vuint16(a) ^ b; } + + __forceinline vuint16 operator <<(const vuint16& a, unsigned int n) { return _mm512_slli_epi32(a, n); } + __forceinline vuint16 operator >>(const vuint16& a, unsigned int n) { return _mm512_srli_epi32(a, n); } + + __forceinline vuint16 operator <<(const vuint16& a, const vuint16& n) { return _mm512_sllv_epi32(a, n); } + __forceinline vuint16 operator >>(const vuint16& a, const vuint16& n) { return _mm512_srlv_epi32(a, n); } + + __forceinline vuint16 sll (const vuint16& a, unsigned int b) { return _mm512_slli_epi32(a, b); } + __forceinline vuint16 sra (const vuint16& a, unsigned int b) { return _mm512_srai_epi32(a, b); } + __forceinline vuint16 srl (const vuint16& a, unsigned int b) { return _mm512_srli_epi32(a, b); } + + __forceinline vuint16 min(const vuint16& a, const vuint16& b) { return _mm512_min_epu32(a, b); } + __forceinline vuint16 min(const vuint16& a, unsigned int b) { return min(a,vuint16(b)); } + __forceinline vuint16 min(unsigned int a, const vuint16& b) { return min(vuint16(a),b); } + + __forceinline vuint16 max(const vuint16& a, const vuint16& b) { return _mm512_max_epu32(a, b); } + __forceinline vuint16 max(const vuint16& a, unsigned int b) { return max(a,vuint16(b)); } + __forceinline vuint16 max(unsigned int a, const vuint16& b) { return max(vuint16(a),b); } + + __forceinline vuint16 mask_add(const vboolf16& mask, vuint16& c, const vuint16& a, const vuint16& b) { return _mm512_mask_add_epi32(c,mask,a,b); } + __forceinline vuint16 mask_sub(const vboolf16& mask, vuint16& c, const vuint16& a, const vuint16& b) { return _mm512_mask_sub_epi32(c,mask,a,b); } + + __forceinline vuint16 mask_and(const vboolf16& m, vuint16& c, const vuint16& a, const vuint16& b) { return _mm512_mask_and_epi32(c,m,a,b); } + __forceinline vuint16 mask_or (const vboolf16& m, vuint16& c, const vuint16& a, const vuint16& b) { return _mm512_mask_or_epi32(c,m,a,b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint16& operator +=(vuint16& a, const vuint16& b) { return a = a + b; } + __forceinline vuint16& operator +=(vuint16& a, unsigned int b) { return a = a + b; } + + __forceinline vuint16& operator -=(vuint16& a, const vuint16& b) { return a = a - b; } + __forceinline vuint16& operator -=(vuint16& a, unsigned int b) { return a = a - b; } + + __forceinline vuint16& operator *=(vuint16& a, const vuint16& b) { return a = a * b; } + __forceinline vuint16& operator *=(vuint16& a, unsigned int b) { return a = a * b; } + + __forceinline vuint16& operator &=(vuint16& a, const vuint16& b) { return a = a & b; } + __forceinline vuint16& operator &=(vuint16& a, unsigned int b) { return a = a & b; } + + __forceinline vuint16& operator |=(vuint16& a, const vuint16& b) { return a = a | b; } + __forceinline vuint16& operator |=(vuint16& a, unsigned int b) { return a = a | b; } + + __forceinline vuint16& operator <<=(vuint16& a, unsigned int b) { return a = a << b; } + __forceinline vuint16& operator >>=(vuint16& a, unsigned int b) { return a = a >> b; } + + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf16 operator ==(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 operator ==(const vuint16& a, unsigned int b) { return a == vuint16(b); } + __forceinline vboolf16 operator ==(unsigned int a, const vuint16& b) { return vuint16(a) == b; } + + __forceinline vboolf16 operator !=(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 operator !=(const vuint16& a, unsigned int b) { return a != vuint16(b); } + __forceinline vboolf16 operator !=(unsigned int a, const vuint16& b) { return vuint16(a) != b; } + + __forceinline vboolf16 operator < (const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 operator < (const vuint16& a, unsigned int b) { return a < vuint16(b); } + __forceinline vboolf16 operator < (unsigned int a, const vuint16& b) { return vuint16(a) < b; } + + __forceinline vboolf16 operator >=(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 operator >=(const vuint16& a, unsigned int b) { return a >= vuint16(b); } + __forceinline vboolf16 operator >=(unsigned int a, const vuint16& b) { return vuint16(a) >= b; } + + __forceinline vboolf16 operator > (const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 operator > (const vuint16& a, unsigned int b) { return a > vuint16(b); } + __forceinline vboolf16 operator > (unsigned int a, const vuint16& b) { return vuint16(a) > b; } + + __forceinline vboolf16 operator <=(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_LE); } + __forceinline vboolf16 operator <=(const vuint16& a, unsigned int b) { return a <= vuint16(b); } + __forceinline vboolf16 operator <=(unsigned int a, const vuint16& b) { return vuint16(a) <= b; } + + __forceinline vboolf16 eq(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 ne(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 lt(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 ge(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 gt(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 le(const vuint16& a, const vuint16& b) { return _mm512_cmp_epu32_mask(a,b,_MM_CMPINT_LE); } + + __forceinline vboolf16 eq(const vboolf16 mask, const vuint16& a, const vuint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_EQ); } + __forceinline vboolf16 ne(const vboolf16 mask, const vuint16& a, const vuint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_NE); } + __forceinline vboolf16 lt(const vboolf16 mask, const vuint16& a, const vuint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_LT); } + __forceinline vboolf16 ge(const vboolf16 mask, const vuint16& a, const vuint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_GE); } + __forceinline vboolf16 gt(const vboolf16 mask, const vuint16& a, const vuint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_GT); } + __forceinline vboolf16 le(const vboolf16 mask, const vuint16& a, const vuint16& b) { return _mm512_mask_cmp_epu32_mask(mask,a,b,_MM_CMPINT_LE); } + + + __forceinline vuint16 select(const vboolf16& m, const vuint16& t, const vuint16& f) { + return _mm512_mask_or_epi32(f,m,t,t); + } + + __forceinline void xchg(const vboolf16& m, vuint16& a, vuint16& b) { + const vuint16 c = a; a = select(m,b,a); b = select(m,c,b); + } + + __forceinline vboolf16 test(const vboolf16& m, const vuint16& a, const vuint16& b) { + return _mm512_mask_test_epi32_mask(m,a,b); + } + + __forceinline vboolf16 test(const vuint16& a, const vuint16& b) { + return _mm512_test_epi32_mask(a,b); + } + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vuint16 shuffle(const vuint16& v) { + return _mm512_castps_si512(_mm512_permute_ps(_mm512_castsi512_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vuint16 shuffle(const vuint16& v) { + return _mm512_castps_si512(_mm512_permute_ps(_mm512_castsi512_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vuint16 shuffle4(const vuint16& v) { + return _mm512_castps_si512(_mm512_shuffle_f32x4(_mm512_castsi512_ps(v), _mm512_castsi512_ps(v) ,_MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vuint16 shuffle4(const vuint16& v) { + return _mm512_castps_si512(_mm512_shuffle_f32x4(_mm512_castsi512_ps(v), _mm512_castsi512_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vuint16 align_shift_right(const vuint16& a, const vuint16& b) { + return _mm512_alignr_epi32(a, b, i); + }; + + __forceinline unsigned int toScalar(const vuint16& v) { + return _mm_cvtsi128_si32(_mm512_castsi512_si128(v)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint16 vreduce_min2(vuint16 x) { return min(x, shuffle<1,0,3,2>(x)); } + __forceinline vuint16 vreduce_min4(vuint16 x) { x = vreduce_min2(x); return min(x, shuffle<2,3,0,1>(x)); } + __forceinline vuint16 vreduce_min8(vuint16 x) { x = vreduce_min4(x); return min(x, shuffle4<1,0,3,2>(x)); } + __forceinline vuint16 vreduce_min (vuint16 x) { x = vreduce_min8(x); return min(x, shuffle4<2,3,0,1>(x)); } + + __forceinline vuint16 vreduce_max2(vuint16 x) { return max(x, shuffle<1,0,3,2>(x)); } + __forceinline vuint16 vreduce_max4(vuint16 x) { x = vreduce_max2(x); return max(x, shuffle<2,3,0,1>(x)); } + __forceinline vuint16 vreduce_max8(vuint16 x) { x = vreduce_max4(x); return max(x, shuffle4<1,0,3,2>(x)); } + __forceinline vuint16 vreduce_max (vuint16 x) { x = vreduce_max8(x); return max(x, shuffle4<2,3,0,1>(x)); } + + __forceinline vuint16 vreduce_and2(vuint16 x) { return x & shuffle<1,0,3,2>(x); } + __forceinline vuint16 vreduce_and4(vuint16 x) { x = vreduce_and2(x); return x & shuffle<2,3,0,1>(x); } + __forceinline vuint16 vreduce_and8(vuint16 x) { x = vreduce_and4(x); return x & shuffle4<1,0,3,2>(x); } + __forceinline vuint16 vreduce_and (vuint16 x) { x = vreduce_and8(x); return x & shuffle4<2,3,0,1>(x); } + + __forceinline vuint16 vreduce_or2(vuint16 x) { return x | shuffle<1,0,3,2>(x); } + __forceinline vuint16 vreduce_or4(vuint16 x) { x = vreduce_or2(x); return x | shuffle<2,3,0,1>(x); } + __forceinline vuint16 vreduce_or8(vuint16 x) { x = vreduce_or4(x); return x | shuffle4<1,0,3,2>(x); } + __forceinline vuint16 vreduce_or (vuint16 x) { x = vreduce_or8(x); return x | shuffle4<2,3,0,1>(x); } + + __forceinline vuint16 vreduce_add2(vuint16 x) { return x + shuffle<1,0,3,2>(x); } + __forceinline vuint16 vreduce_add4(vuint16 x) { x = vreduce_add2(x); return x + shuffle<2,3,0,1>(x); } + __forceinline vuint16 vreduce_add8(vuint16 x) { x = vreduce_add4(x); return x + shuffle4<1,0,3,2>(x); } + __forceinline vuint16 vreduce_add (vuint16 x) { x = vreduce_add8(x); return x + shuffle4<2,3,0,1>(x); } + + __forceinline unsigned int reduce_min(const vuint16& v) { return toScalar(vreduce_min(v)); } + __forceinline unsigned int reduce_max(const vuint16& v) { return toScalar(vreduce_max(v)); } + __forceinline unsigned int reduce_and(const vuint16& v) { return toScalar(vreduce_and(v)); } + __forceinline unsigned int reduce_or (const vuint16& v) { return toScalar(vreduce_or (v)); } + __forceinline unsigned int reduce_add(const vuint16& v) { return toScalar(vreduce_add(v)); } + + //////////////////////////////////////////////////////////////////////////////// + /// Memory load and store operations + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint16 permute(vuint16 v, vuint16 index) { + return _mm512_permutexvar_epi32(index,v); + } + + __forceinline vuint16 reverse(const vuint16& a) { + return permute(a,vuint16(reverse_step)); + } + + __forceinline vuint16 prefix_sum(const vuint16& a) + { + const vuint16 z(zero); + vuint16 v = a; + v = v + align_shift_right<16-1>(v,z); + v = v + align_shift_right<16-2>(v,z); + v = v + align_shift_right<16-4>(v,z); + v = v + align_shift_right<16-8>(v,z); + return v; + } + + __forceinline vuint16 reverse_prefix_sum(const vuint16& a) + { + const vuint16 z(zero); + vuint16 v = a; + v = v + align_shift_right<1>(z,v); + v = v + align_shift_right<2>(z,v); + v = v + align_shift_right<4>(z,v); + v = v + align_shift_right<8>(z,v); + return v; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vuint16& v) + { + cout << "<" << v[0]; + for (int i=1; i<16; i++) cout << ", " << v[i]; + cout << ">"; + return cout; + } +} diff --git a/thirdparty/embree/common/simd/vuint4_sse2.h b/thirdparty/embree/common/simd/vuint4_sse2.h new file mode 100644 index 000000000000..396eb45d5dfe --- /dev/null +++ b/thirdparty/embree/common/simd/vuint4_sse2.h @@ -0,0 +1,428 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../math/math.h" + +namespace embree +{ + /* 4-wide SSE integer type */ + template<> + struct vuint<4> + { + ALIGNED_STRUCT_(16); + + typedef vboolf4 Bool; + typedef vuint4 Int; + typedef vfloat4 Float; + + enum { size = 4 }; // number of SIMD elements + union { __m128i v; unsigned int i[4]; }; // data + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint() {} + __forceinline vuint(const vuint4& a) { v = a.v; } + __forceinline vuint4& operator =(const vuint4& a) { v = a.v; return *this; } + + __forceinline vuint(const __m128i a) : v(a) {} + __forceinline operator const __m128i&() const { return v; } + __forceinline operator __m128i&() { return v; } + + + __forceinline vuint(unsigned int a) : v(_mm_set1_epi32(a)) {} + __forceinline vuint(unsigned int a, unsigned int b, unsigned int c, unsigned int d) : v(_mm_set_epi32(d, c, b, a)) {} + +#if defined(__AVX512VL__) + __forceinline explicit vuint(__m128 a) : v(_mm_cvtps_epu32(a)) {} +#endif + +#if defined(__AVX512VL__) + __forceinline explicit vuint(const vboolf4& a) : v(_mm_movm_epi32(a)) {} +#else + __forceinline explicit vuint(const vboolf4& a) : v(_mm_castps_si128((__m128)a)) {} +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint(ZeroTy) : v(_mm_setzero_si128()) {} + __forceinline vuint(OneTy) : v(_mm_set1_epi32(1)) {} + __forceinline vuint(PosInfTy) : v(_mm_set1_epi32(unsigned(pos_inf))) {} + __forceinline vuint(StepTy) : v(_mm_set_epi32(3, 2, 1, 0)) {} + __forceinline vuint(TrueTy) { v = _mm_cmpeq_epi32(v,v); } + __forceinline vuint(UndefinedTy) : v(_mm_castps_si128(_mm_undefined_ps())) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vuint4 load (const void* a) { return _mm_load_si128((__m128i*)a); } + static __forceinline vuint4 loadu(const void* a) { return _mm_loadu_si128((__m128i*)a); } + + static __forceinline void store (void* ptr, const vuint4& v) { _mm_store_si128((__m128i*)ptr,v); } + static __forceinline void storeu(void* ptr, const vuint4& v) { _mm_storeu_si128((__m128i*)ptr,v); } + +#if defined(__AVX512VL__) + static __forceinline vuint4 load (const vboolf4& mask, const void* ptr) { return _mm_mask_load_epi32 (_mm_setzero_si128(),mask,ptr); } + static __forceinline vuint4 loadu(const vboolf4& mask, const void* ptr) { return _mm_mask_loadu_epi32(_mm_setzero_si128(),mask,ptr); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vuint4& v) { _mm_mask_store_epi32 (ptr,mask,v); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vuint4& v) { _mm_mask_storeu_epi32(ptr,mask,v); } +#elif defined(__AVX__) + static __forceinline vuint4 load (const vbool4& mask, const void* a) { return _mm_castps_si128(_mm_maskload_ps((float*)a,mask)); } + static __forceinline vuint4 loadu(const vbool4& mask, const void* a) { return _mm_castps_si128(_mm_maskload_ps((float*)a,mask)); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vuint4& i) { _mm_maskstore_ps((float*)ptr,(__m128i)mask,_mm_castsi128_ps(i)); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vuint4& i) { _mm_maskstore_ps((float*)ptr,(__m128i)mask,_mm_castsi128_ps(i)); } +#else + static __forceinline vuint4 load (const vbool4& mask, const void* a) { return _mm_and_si128(_mm_load_si128 ((__m128i*)a),mask); } + static __forceinline vuint4 loadu(const vbool4& mask, const void* a) { return _mm_and_si128(_mm_loadu_si128((__m128i*)a),mask); } + + static __forceinline void store (const vboolf4& mask, void* ptr, const vuint4& i) { store (ptr,select(mask,i,load (ptr))); } + static __forceinline void storeu(const vboolf4& mask, void* ptr, const vuint4& i) { storeu(ptr,select(mask,i,loadu(ptr))); } +#endif + +#if defined(__SSE4_1__) + static __forceinline vuint4 load(const unsigned char* ptr) { + return _mm_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); + } + + static __forceinline vuint4 loadu(const unsigned char* ptr) { + return _mm_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); + } + +#endif + + static __forceinline vuint4 load(const unsigned short* ptr) { +#if defined (__SSE4_1__) + return _mm_cvtepu16_epi32(_mm_loadu_si128((__m128i*)ptr)); +#else + return vuint4(ptr[0],ptr[1],ptr[2],ptr[3]); +#endif + } + + static __forceinline void store_uchar(unsigned char* ptr, const vuint4& v) { +#if defined(__SSE4_1__) + __m128i x = v; + x = _mm_packus_epi32(x, x); + x = _mm_packus_epi16(x, x); + *(unsigned*)ptr = _mm_cvtsi128_si32(x); +#else + for (size_t i=0;i<4;i++) + ptr[i] = (unsigned char)v[i]; +#endif + } + + static __forceinline void store_uchar(unsigned short* ptr, const vuint4& v) { + for (size_t i=0;i<4;i++) + ptr[i] = (unsigned short)v[i]; + } + + static __forceinline vuint4 load_nt(void* ptr) { +#if defined(__SSE4_1__) + return _mm_stream_load_si128((__m128i*)ptr); +#else + return _mm_load_si128((__m128i*)ptr); +#endif + } + + static __forceinline void store_nt(void* ptr, const vuint4& v) { +#if defined(__SSE4_1__) + _mm_stream_ps((float*)ptr,_mm_castsi128_ps(v)); +#else + _mm_store_si128((__m128i*)ptr,v); +#endif + } + + template + static __forceinline vuint4 gather(const unsigned int* ptr, const vint4& index) { +#if defined(__AVX2__) + return _mm_i32gather_epi32((const int*)ptr, index, scale); +#else + return vuint4( + *(unsigned int*)(((char*)ptr)+scale*index[0]), + *(unsigned int*)(((char*)ptr)+scale*index[1]), + *(unsigned int*)(((char*)ptr)+scale*index[2]), + *(unsigned int*)(((char*)ptr)+scale*index[3])); +#endif + } + + template + static __forceinline vuint4 gather(const vboolf4& mask, const unsigned int* ptr, const vint4& index) { + vuint4 r = zero; +#if defined(__AVX512VL__) + return _mm_mmask_i32gather_epi32(r, mask, index, ptr, scale); +#elif defined(__AVX2__) + return _mm_mask_i32gather_epi32(r, (const int*)ptr, index, mask, scale); +#else + if (likely(mask[0])) r[0] = *(unsigned int*)(((char*)ptr)+scale*index[0]); + if (likely(mask[1])) r[1] = *(unsigned int*)(((char*)ptr)+scale*index[1]); + if (likely(mask[2])) r[2] = *(unsigned int*)(((char*)ptr)+scale*index[2]); + if (likely(mask[3])) r[3] = *(unsigned int*)(((char*)ptr)+scale*index[3]); + return r; +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const unsigned int& operator [](size_t index) const { assert(index < 4); return i[index]; } + __forceinline unsigned int& operator [](size_t index) { assert(index < 4); return i[index]; } + + friend __forceinline vuint4 select(const vboolf4& m, const vuint4& t, const vuint4& f) { +#if defined(__AVX512VL__) + return _mm_mask_blend_epi32(m, (__m128i)f, (__m128i)t); +#elif defined(__SSE4_1__) + return _mm_castps_si128(_mm_blendv_ps(_mm_castsi128_ps(f), _mm_castsi128_ps(t), m)); +#else + return _mm_or_si128(_mm_and_si128(m, t), _mm_andnot_si128(m, f)); +#endif + } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf4 asBool(const vuint4& a) { return _mm_movepi32_mask(a); } +#else + __forceinline vboolf4 asBool(const vuint4& a) { return _mm_castsi128_ps(a); } +#endif + + __forceinline vuint4 operator +(const vuint4& a) { return a; } + __forceinline vuint4 operator -(const vuint4& a) { return _mm_sub_epi32(_mm_setzero_si128(), a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint4 operator +(const vuint4& a, const vuint4& b) { return _mm_add_epi32(a, b); } + __forceinline vuint4 operator +(const vuint4& a, unsigned int b) { return a + vuint4(b); } + __forceinline vuint4 operator +(unsigned int a, const vuint4& b) { return vuint4(a) + b; } + + __forceinline vuint4 operator -(const vuint4& a, const vuint4& b) { return _mm_sub_epi32(a, b); } + __forceinline vuint4 operator -(const vuint4& a, unsigned int b) { return a - vuint4(b); } + __forceinline vuint4 operator -(unsigned int a, const vuint4& b) { return vuint4(a) - b; } + +//#if defined(__SSE4_1__) +// __forceinline vuint4 operator *(const vuint4& a, const vuint4& b) { return _mm_mullo_epu32(a, b); } +//#else +// __forceinline vuint4 operator *(const vuint4& a, const vuint4& b) { return vuint4(a[0]*b[0],a[1]*b[1],a[2]*b[2],a[3]*b[3]); } +//#endif +// __forceinline vuint4 operator *(const vuint4& a, unsigned int b) { return a * vuint4(b); } +// __forceinline vuint4 operator *(unsigned int a, const vuint4& b) { return vuint4(a) * b; } + + __forceinline vuint4 operator &(const vuint4& a, const vuint4& b) { return _mm_and_si128(a, b); } + __forceinline vuint4 operator &(const vuint4& a, unsigned int b) { return a & vuint4(b); } + __forceinline vuint4 operator &(unsigned int a, const vuint4& b) { return vuint4(a) & b; } + + __forceinline vuint4 operator |(const vuint4& a, const vuint4& b) { return _mm_or_si128(a, b); } + __forceinline vuint4 operator |(const vuint4& a, unsigned int b) { return a | vuint4(b); } + __forceinline vuint4 operator |(unsigned int a, const vuint4& b) { return vuint4(a) | b; } + + __forceinline vuint4 operator ^(const vuint4& a, const vuint4& b) { return _mm_xor_si128(a, b); } + __forceinline vuint4 operator ^(const vuint4& a, unsigned int b) { return a ^ vuint4(b); } + __forceinline vuint4 operator ^(unsigned int a, const vuint4& b) { return vuint4(a) ^ b; } + + __forceinline vuint4 operator <<(const vuint4& a, unsigned int n) { return _mm_slli_epi32(a, n); } + __forceinline vuint4 operator >>(const vuint4& a, unsigned int n) { return _mm_srli_epi32(a, n); } + + __forceinline vuint4 sll (const vuint4& a, unsigned int b) { return _mm_slli_epi32(a, b); } + __forceinline vuint4 sra (const vuint4& a, unsigned int b) { return _mm_srai_epi32(a, b); } + __forceinline vuint4 srl (const vuint4& a, unsigned int b) { return _mm_srli_epi32(a, b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint4& operator +=(vuint4& a, const vuint4& b) { return a = a + b; } + __forceinline vuint4& operator +=(vuint4& a, unsigned int b) { return a = a + b; } + + __forceinline vuint4& operator -=(vuint4& a, const vuint4& b) { return a = a - b; } + __forceinline vuint4& operator -=(vuint4& a, unsigned int b) { return a = a - b; } + +//#if defined(__SSE4_1__) +// __forceinline vuint4& operator *=(vuint4& a, const vuint4& b) { return a = a * b; } +// __forceinline vuint4& operator *=(vuint4& a, unsigned int b) { return a = a * b; } +//#endif + + __forceinline vuint4& operator &=(vuint4& a, const vuint4& b) { return a = a & b; } + __forceinline vuint4& operator &=(vuint4& a, unsigned int b) { return a = a & b; } + + __forceinline vuint4& operator |=(vuint4& a, const vuint4& b) { return a = a | b; } + __forceinline vuint4& operator |=(vuint4& a, unsigned int b) { return a = a | b; } + + __forceinline vuint4& operator <<=(vuint4& a, unsigned int b) { return a = a << b; } + __forceinline vuint4& operator >>=(vuint4& a, unsigned int b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf4 operator ==(const vuint4& a, const vuint4& b) { return _mm_cmp_epu32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf4 operator !=(const vuint4& a, const vuint4& b) { return _mm_cmp_epu32_mask(a,b,_MM_CMPINT_NE); } + //__forceinline vboolf4 operator < (const vuint4& a, const vuint4& b) { return _mm_cmp_epu32_mask(a,b,_MM_CMPINT_LT); } + //__forceinline vboolf4 operator >=(const vuint4& a, const vuint4& b) { return _mm_cmp_epu32_mask(a,b,_MM_CMPINT_GE); } + //__forceinline vboolf4 operator > (const vuint4& a, const vuint4& b) { return _mm_cmp_epu32_mask(a,b,_MM_CMPINT_GT); } + //__forceinline vboolf4 operator <=(const vuint4& a, const vuint4& b) { return _mm_cmp_epu32_mask(a,b,_MM_CMPINT_LE); } +#else + __forceinline vboolf4 operator ==(const vuint4& a, const vuint4& b) { return _mm_castsi128_ps(_mm_cmpeq_epi32(a, b)); } + __forceinline vboolf4 operator !=(const vuint4& a, const vuint4& b) { return !(a == b); } + //__forceinline vboolf4 operator < (const vuint4& a, const vuint4& b) { return _mm_castsi128_ps(_mm_cmplt_epu32(a, b)); } + //__forceinline vboolf4 operator >=(const vuint4& a, const vuint4& b) { return !(a < b); } + //__forceinline vboolf4 operator > (const vuint4& a, const vuint4& b) { return _mm_castsi128_ps(_mm_cmpgt_epu32(a, b)); } + //__forceinline vboolf4 operator <=(const vuint4& a, const vuint4& b) { return !(a > b); } +#endif + + __forceinline vboolf4 operator ==(const vuint4& a, unsigned int b) { return a == vuint4(b); } + __forceinline vboolf4 operator ==(unsigned int a, const vuint4& b) { return vuint4(a) == b; } + + __forceinline vboolf4 operator !=(const vuint4& a, unsigned int b) { return a != vuint4(b); } + __forceinline vboolf4 operator !=(unsigned int a, const vuint4& b) { return vuint4(a) != b; } + + //__forceinline vboolf4 operator < (const vuint4& a, unsigned int b) { return a < vuint4(b); } + //__forceinline vboolf4 operator < (unsigned int a, const vuint4& b) { return vuint4(a) < b; } + + //__forceinline vboolf4 operator >=(const vuint4& a, unsigned int b) { return a >= vuint4(b); } + //__forceinline vboolf4 operator >=(unsigned int a, const vuint4& b) { return vuint4(a) >= b; } + + //__forceinline vboolf4 operator > (const vuint4& a, unsigned int b) { return a > vuint4(b); } + //__forceinline vboolf4 operator > (unsigned int a, const vuint4& b) { return vuint4(a) > b; } + + //__forceinline vboolf4 operator <=(const vuint4& a, unsigned int b) { return a <= vuint4(b); } + //__forceinline vboolf4 operator <=(unsigned int a, const vuint4& b) { return vuint4(a) <= b; } + + __forceinline vboolf4 eq(const vuint4& a, const vuint4& b) { return a == b; } + __forceinline vboolf4 ne(const vuint4& a, const vuint4& b) { return a != b; } + //__forceinline vboolf4 lt(const vuint4& a, const vuint4& b) { return a < b; } + //__forceinline vboolf4 ge(const vuint4& a, const vuint4& b) { return a >= b; } + //__forceinline vboolf4 gt(const vuint4& a, const vuint4& b) { return a > b; } + //__forceinline vboolf4 le(const vuint4& a, const vuint4& b) { return a <= b; } + +#if defined(__AVX512VL__) + __forceinline vboolf4 eq(const vboolf4& mask, const vuint4& a, const vuint4& b) { return _mm_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_EQ); } + __forceinline vboolf4 ne(const vboolf4& mask, const vuint4& a, const vuint4& b) { return _mm_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_NE); } + //__forceinline vboolf4 lt(const vboolf4& mask, const vuint4& a, const vuint4& b) { return _mm_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_LT); } + //__forceinline vboolf4 ge(const vboolf4& mask, const vuint4& a, const vuint4& b) { return _mm_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_GE); } + //__forceinline vboolf4 gt(const vboolf4& mask, const vuint4& a, const vuint4& b) { return _mm_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_GT); } + //__forceinline vboolf4 le(const vboolf4& mask, const vuint4& a, const vuint4& b) { return _mm_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_LE); } +#else + __forceinline vboolf4 eq(const vboolf4& mask, const vuint4& a, const vuint4& b) { return mask & (a == b); } + __forceinline vboolf4 ne(const vboolf4& mask, const vuint4& a, const vuint4& b) { return mask & (a != b); } + //__forceinline vboolf4 lt(const vboolf4& mask, const vuint4& a, const vuint4& b) { return mask & (a < b); } + //__forceinline vboolf4 ge(const vboolf4& mask, const vuint4& a, const vuint4& b) { return mask & (a >= b); } + //__forceinline vboolf4 gt(const vboolf4& mask, const vuint4& a, const vuint4& b) { return mask & (a > b); } + //__forceinline vboolf4 le(const vboolf4& mask, const vuint4& a, const vuint4& b) { return mask & (a <= b); } +#endif + + template + __forceinline vuint4 select(const vuint4& t, const vuint4& f) { +#if defined(__SSE4_1__) + return _mm_castps_si128(_mm_blend_ps(_mm_castsi128_ps(f), _mm_castsi128_ps(t), mask)); +#else + return select(vboolf4(mask), t, f); +#endif + } + +/*#if defined(__SSE4_1__) + __forceinline vuint4 min(const vuint4& a, const vuint4& b) { return _mm_min_epu32(a, b); } + __forceinline vuint4 max(const vuint4& a, const vuint4& b) { return _mm_max_epu32(a, b); } + +#else + __forceinline vuint4 min(const vuint4& a, const vuint4& b) { return select(a < b,a,b); } + __forceinline vuint4 max(const vuint4& a, const vuint4& b) { return select(a < b,b,a); } +#endif + + __forceinline vuint4 min(const vuint4& a, unsigned int b) { return min(a,vuint4(b)); } + __forceinline vuint4 min(unsigned int a, const vuint4& b) { return min(vuint4(a),b); } + __forceinline vuint4 max(const vuint4& a, unsigned int b) { return max(a,vuint4(b)); } + __forceinline vuint4 max(unsigned int a, const vuint4& b) { return max(vuint4(a),b); }*/ + + //////////////////////////////////////////////////////////////////////////////// + // Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint4 unpacklo(const vuint4& a, const vuint4& b) { return _mm_castps_si128(_mm_unpacklo_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b))); } + __forceinline vuint4 unpackhi(const vuint4& a, const vuint4& b) { return _mm_castps_si128(_mm_unpackhi_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b))); } + + template + __forceinline vuint4 shuffle(const vuint4& v) { + return _mm_shuffle_epi32(v, _MM_SHUFFLE(i3, i2, i1, i0)); + } + + template + __forceinline vuint4 shuffle(const vuint4& a, const vuint4& b) { + return _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); + } + +#if defined(__SSE3__) + template<> __forceinline vuint4 shuffle<0, 0, 2, 2>(const vuint4& v) { return _mm_castps_si128(_mm_moveldup_ps(_mm_castsi128_ps(v))); } + template<> __forceinline vuint4 shuffle<1, 1, 3, 3>(const vuint4& v) { return _mm_castps_si128(_mm_movehdup_ps(_mm_castsi128_ps(v))); } + template<> __forceinline vuint4 shuffle<0, 1, 0, 1>(const vuint4& v) { return _mm_castpd_si128(_mm_movedup_pd (_mm_castsi128_pd(v))); } +#endif + + template + __forceinline vuint4 shuffle(const vuint4& v) { + return shuffle(v); + } + +#if defined(__SSE4_1__) + template __forceinline unsigned int extract(const vuint4& b) { return _mm_extract_epi32(b, src); } + template __forceinline vuint4 insert(const vuint4& a, const unsigned b) { return _mm_insert_epi32(a, b, dst); } +#else + template __forceinline unsigned int extract(const vuint4& b) { return b[src&3]; } + template __forceinline vuint4 insert(const vuint4& a, const unsigned b) { vuint4 c = a; c[dst&3] = b; return c; } +#endif + + + template<> __forceinline unsigned int extract<0>(const vuint4& b) { return _mm_cvtsi128_si32(b); } + + __forceinline unsigned int toScalar(const vuint4& v) { return _mm_cvtsi128_si32(v); } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + +#if 0 +#if defined(__SSE4_1__) + + __forceinline vuint4 vreduce_min(const vuint4& v) { vuint4 h = min(shuffle<1,0,3,2>(v),v); return min(shuffle<2,3,0,1>(h),h); } + __forceinline vuint4 vreduce_max(const vuint4& v) { vuint4 h = max(shuffle<1,0,3,2>(v),v); return max(shuffle<2,3,0,1>(h),h); } + __forceinline vuint4 vreduce_add(const vuint4& v) { vuint4 h = shuffle<1,0,3,2>(v) + v ; return shuffle<2,3,0,1>(h) + h ; } + + __forceinline unsigned int reduce_min(const vuint4& v) { return toScalar(vreduce_min(v)); } + __forceinline unsigned int reduce_max(const vuint4& v) { return toScalar(vreduce_max(v)); } + __forceinline unsigned int reduce_add(const vuint4& v) { return toScalar(vreduce_add(v)); } + + __forceinline size_t select_min(const vuint4& v) { return bsf(movemask(v == vreduce_min(v))); } + __forceinline size_t select_max(const vuint4& v) { return bsf(movemask(v == vreduce_max(v))); } + + //__forceinline size_t select_min(const vboolf4& valid, const vuint4& v) { const vuint4 a = select(valid,v,vuint4(pos_inf)); return bsf(movemask(valid & (a == vreduce_min(a)))); } + //__forceinline size_t select_max(const vboolf4& valid, const vuint4& v) { const vuint4 a = select(valid,v,vuint4(neg_inf)); return bsf(movemask(valid & (a == vreduce_max(a)))); } + +#else + + __forceinline unsigned int reduce_min(const vuint4& v) { return min(v[0],v[1],v[2],v[3]); } + __forceinline unsigned int reduce_max(const vuint4& v) { return max(v[0],v[1],v[2],v[3]); } + __forceinline unsigned int reduce_add(const vuint4& v) { return v[0]+v[1]+v[2]+v[3]; } + +#endif +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vuint4& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ">"; + } +} + diff --git a/thirdparty/embree/common/simd/vuint8_avx.h b/thirdparty/embree/common/simd/vuint8_avx.h new file mode 100644 index 000000000000..437e73c7fbf3 --- /dev/null +++ b/thirdparty/embree/common/simd/vuint8_avx.h @@ -0,0 +1,375 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX integer type */ + template<> + struct vuint<8> + { + ALIGNED_STRUCT_(32); + + typedef vboolf8 Bool; + typedef vuint8 Int; + typedef vfloat8 Float; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m256i v; + struct { __m128i vl,vh; }; + unsigned int i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint() {} + __forceinline vuint(const vuint8& a) { v = a.v; } + __forceinline vuint8& operator =(const vuint8& a) { v = a.v; return *this; } + + __forceinline vuint(__m256i a) : v(a) {} + __forceinline operator const __m256i&() const { return v; } + __forceinline operator __m256i&() { return v; } + + __forceinline explicit vuint(const vuint4& a) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),a,1)) {} + __forceinline vuint(const vuint4& a, const vuint4& b) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),b,1)) {} + __forceinline vuint(const __m128i& a, const __m128i& b) : vl(a), vh(b) {} + + __forceinline explicit vuint(const unsigned int* a) : v(_mm256_castps_si256(_mm256_loadu_ps((const float*)a))) {} + __forceinline vuint(unsigned int a) : v(_mm256_set1_epi32(a)) {} + __forceinline vuint(unsigned int a, unsigned int b) : v(_mm256_set_epi32(b, a, b, a, b, a, b, a)) {} + __forceinline vuint(unsigned int a, unsigned int b, unsigned int c, unsigned int d) : v(_mm256_set_epi32(d, c, b, a, d, c, b, a)) {} + __forceinline vuint(unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e, unsigned int f, unsigned int g, unsigned int vh) : v(_mm256_set_epi32(vh, g, f, e, d, c, b, a)) {} + + __forceinline explicit vuint(__m256 a) : v(_mm256_cvtps_epi32(a)) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint(ZeroTy) : v(_mm256_setzero_si256()) {} + __forceinline vuint(OneTy) : v(_mm256_set1_epi32(1)) {} + __forceinline vuint(PosInfTy) : v(_mm256_set1_epi32(0xFFFFFFFF)) {} + __forceinline vuint(StepTy) : v(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0)) {} + __forceinline vuint(UndefinedTy) : v(_mm256_undefined_si256()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vuint8 load (const void* a) { return _mm256_castps_si256(_mm256_load_ps((float*)a)); } + static __forceinline vuint8 loadu(const void* a) { return _mm256_castps_si256(_mm256_loadu_ps((float*)a)); } + + static __forceinline vuint8 load (const vboolf8& mask, const void* a) { return _mm256_castps_si256(_mm256_maskload_ps((float*)a,mask)); } + static __forceinline vuint8 loadu(const vboolf8& mask, const void* a) { return _mm256_castps_si256(_mm256_maskload_ps((float*)a,mask)); } + + static __forceinline void store (void* ptr, const vuint8& f) { _mm256_store_ps((float*)ptr,_mm256_castsi256_ps(f)); } + static __forceinline void storeu(void* ptr, const vuint8& f) { _mm256_storeu_ps((float*)ptr,_mm256_castsi256_ps(f)); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vuint8& f) { _mm256_maskstore_ps((float*)ptr,(__m256i)mask,_mm256_castsi256_ps(f)); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vuint8& f) { _mm256_maskstore_ps((float*)ptr,(__m256i)mask,_mm256_castsi256_ps(f)); } + + static __forceinline void store_nt(void* ptr, const vuint8& v) { + _mm256_stream_ps((float*)ptr,_mm256_castsi256_ps(v)); + } + + static __forceinline vuint8 load(const unsigned char* ptr) { + vuint4 il = vuint4::load(ptr+0); + vuint4 ih = vuint4::load(ptr+4); + return vuint8(il,ih); + } + + static __forceinline vuint8 loadu(const unsigned char* ptr) { + vuint4 il = vuint4::loadu(ptr+0); + vuint4 ih = vuint4::loadu(ptr+4); + return vuint8(il,ih); + } + + static __forceinline vuint8 load(const unsigned short* ptr) { + vuint4 il = vuint4::load(ptr+0); + vuint4 ih = vuint4::load(ptr+4); + return vuint8(il,ih); + } + + static __forceinline vuint8 loadu(const unsigned short* ptr) { + vuint4 il = vuint4::loadu(ptr+0); + vuint4 ih = vuint4::loadu(ptr+4); + return vuint8(il,ih); + } + + static __forceinline void store(unsigned char* ptr, const vuint8& i) { + vuint4 il(i.vl); + vuint4 ih(i.vh); + vuint4::store(ptr + 0,il); + vuint4::store(ptr + 4,ih); + } + + static __forceinline void store(unsigned short* ptr, const vuint8& v) { + for (size_t i=0;i<8;i++) + ptr[i] = (unsigned short)v[i]; + } + + template + static __forceinline vuint8 gather(const unsigned int* ptr, const vint8& index) { + return vuint8( + *(unsigned int*)(((char*)ptr)+scale*index[0]), + *(unsigned int*)(((char*)ptr)+scale*index[1]), + *(unsigned int*)(((char*)ptr)+scale*index[2]), + *(unsigned int*)(((char*)ptr)+scale*index[3]), + *(unsigned int*)(((char*)ptr)+scale*index[4]), + *(unsigned int*)(((char*)ptr)+scale*index[5]), + *(unsigned int*)(((char*)ptr)+scale*index[6]), + *(unsigned int*)(((char*)ptr)+scale*index[7])); + } + + template + static __forceinline vuint8 gather(const vboolf8& mask, const unsigned int* ptr, const vint8& index) { + vuint8 r = zero; + if (likely(mask[0])) r[0] = *(unsigned int*)(((char*)ptr)+scale*index[0]); + if (likely(mask[1])) r[1] = *(unsigned int*)(((char*)ptr)+scale*index[1]); + if (likely(mask[2])) r[2] = *(unsigned int*)(((char*)ptr)+scale*index[2]); + if (likely(mask[3])) r[3] = *(unsigned int*)(((char*)ptr)+scale*index[3]); + if (likely(mask[4])) r[4] = *(unsigned int*)(((char*)ptr)+scale*index[4]); + if (likely(mask[5])) r[5] = *(unsigned int*)(((char*)ptr)+scale*index[5]); + if (likely(mask[6])) r[6] = *(unsigned int*)(((char*)ptr)+scale*index[6]); + if (likely(mask[7])) r[7] = *(unsigned int*)(((char*)ptr)+scale*index[7]); + return r; + } + + template + static __forceinline void scatter(void* ptr, const vint8& ofs, const vuint8& v) + { + *(unsigned int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + *(unsigned int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + *(unsigned int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + *(unsigned int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + *(unsigned int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + *(unsigned int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + *(unsigned int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + *(unsigned int*)(((char*)ptr)+scale*ofs[7]) = v[7]; + } + + template + static __forceinline void scatter(const vboolf8& mask, void* ptr, const vint8& ofs, const vuint8& v) + { + if (likely(mask[0])) *(unsigned int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + if (likely(mask[1])) *(unsigned int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + if (likely(mask[2])) *(unsigned int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + if (likely(mask[3])) *(unsigned int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + if (likely(mask[4])) *(unsigned int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + if (likely(mask[5])) *(unsigned int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + if (likely(mask[6])) *(unsigned int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + if (likely(mask[7])) *(unsigned int*)(((char*)ptr)+scale*ofs[7]) = v[7]; + } + + + static __forceinline vuint8 broadcast64(const long long& a) { return _mm256_set1_epi64x(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const unsigned int& operator [](size_t index) const { assert(index < 8); return i[index]; } + __forceinline unsigned int& operator [](size_t index) { assert(index < 8); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 asBool(const vuint8& a) { return _mm256_castsi256_ps(a); } + + __forceinline vuint8 operator +(const vuint8& a) { return a; } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint8 operator +(const vuint8& a, const vuint8& b) { return vuint8(_mm_add_epi32(a.vl, b.vl), _mm_add_epi32(a.vh, b.vh)); } + __forceinline vuint8 operator +(const vuint8& a, unsigned int b) { return a + vuint8(b); } + __forceinline vuint8 operator +(unsigned int a, const vuint8& b) { return vuint8(a) + b; } + + __forceinline vuint8 operator -(const vuint8& a, const vuint8& b) { return vuint8(_mm_sub_epi32(a.vl, b.vl), _mm_sub_epi32(a.vh, b.vh)); } + __forceinline vuint8 operator -(const vuint8& a, unsigned int b) { return a - vuint8(b); } + __forceinline vuint8 operator -(unsigned int a, const vuint8& b) { return vuint8(a) - b; } + + //__forceinline vuint8 operator *(const vuint8& a, const vuint8& b) { return vuint8(_mm_mullo_epu32(a.vl, b.vl), _mm_mullo_epu32(a.vh, b.vh)); } + //__forceinline vuint8 operator *(const vuint8& a, unsigned int b) { return a * vuint8(b); } + //__forceinline vuint8 operator *(unsigned int a, const vuint8& b) { return vuint8(a) * b; } + + __forceinline vuint8 operator &(const vuint8& a, const vuint8& b) { return _mm256_castps_si256(_mm256_and_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vuint8 operator &(const vuint8& a, unsigned int b) { return a & vuint8(b); } + __forceinline vuint8 operator &(unsigned int a, const vuint8& b) { return vuint8(a) & b; } + + __forceinline vuint8 operator |(const vuint8& a, const vuint8& b) { return _mm256_castps_si256(_mm256_or_ps (_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vuint8 operator |(const vuint8& a, unsigned int b) { return a | vuint8(b); } + __forceinline vuint8 operator |(unsigned int a, const vuint8& b) { return vuint8(a) | b; } + + __forceinline vuint8 operator ^(const vuint8& a, const vuint8& b) { return _mm256_castps_si256(_mm256_xor_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vuint8 operator ^(const vuint8& a, unsigned int b) { return a ^ vuint8(b); } + __forceinline vuint8 operator ^(unsigned int a, const vuint8& b) { return vuint8(a) ^ b; } + + __forceinline vuint8 operator <<(const vuint8& a, unsigned int n) { return vuint8(_mm_slli_epi32(a.vl, n), _mm_slli_epi32(a.vh, n)); } + __forceinline vuint8 operator >>(const vuint8& a, unsigned int n) { return vuint8(_mm_srai_epi32(a.vl, n), _mm_srli_epi32(a.vh, n)); } + + __forceinline vuint8 sll (const vuint8& a, unsigned int b) { return vuint8(_mm_slli_epi32(a.vl, b), _mm_slli_epi32(a.vh, b)); } + __forceinline vuint8 sra (const vuint8& a, unsigned int b) { return vuint8(_mm_srai_epi32(a.vl, b), _mm_srai_epi32(a.vh, b)); } + __forceinline vuint8 srl (const vuint8& a, unsigned int b) { return vuint8(_mm_srli_epi32(a.vl, b), _mm_srli_epi32(a.vh, b)); } + + __forceinline vuint8 min(const vuint8& a, const vuint8& b) { return vuint8(_mm_min_epu32(a.vl, b.vl), _mm_min_epu32(a.vh, b.vh)); } + __forceinline vuint8 min(const vuint8& a, unsigned int b) { return min(a,vuint8(b)); } + __forceinline vuint8 min(unsigned int a, const vuint8& b) { return min(vuint8(a),b); } + + __forceinline vuint8 max(const vuint8& a, const vuint8& b) { return vuint8(_mm_max_epu32(a.vl, b.vl), _mm_max_epu32(a.vh, b.vh)); } + __forceinline vuint8 max(const vuint8& a, unsigned int b) { return max(a,vuint8(b)); } + __forceinline vuint8 max(unsigned int a, const vuint8& b) { return max(vuint8(a),b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint8& operator +=(vuint8& a, const vuint8& b) { return a = a + b; } + __forceinline vuint8& operator +=(vuint8& a, unsigned int b) { return a = a + b; } + + __forceinline vuint8& operator -=(vuint8& a, const vuint8& b) { return a = a - b; } + __forceinline vuint8& operator -=(vuint8& a, unsigned int b) { return a = a - b; } + + //__forceinline vuint8& operator *=(vuint8& a, const vuint8& b) { return a = a * b; } + //__forceinline vuint8& operator *=(vuint8& a, unsigned int b) { return a = a * b; } + + __forceinline vuint8& operator &=(vuint8& a, const vuint8& b) { return a = a & b; } + __forceinline vuint8& operator &=(vuint8& a, unsigned int b) { return a = a & b; } + + __forceinline vuint8& operator |=(vuint8& a, const vuint8& b) { return a = a | b; } + __forceinline vuint8& operator |=(vuint8& a, unsigned int b) { return a = a | b; } + + __forceinline vuint8& operator <<=(vuint8& a, unsigned int b) { return a = a << b; } + __forceinline vuint8& operator >>=(vuint8& a, unsigned int b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vboolf8 operator ==(const vuint8& a, const vuint8& b) { return vboolf8(_mm_castsi128_ps(_mm_cmpeq_epi32 (a.vl, b.vl)), + _mm_castsi128_ps(_mm_cmpeq_epi32 (a.vh, b.vh))); } + __forceinline vboolf8 operator ==(const vuint8& a, unsigned int b) { return a == vuint8(b); } + __forceinline vboolf8 operator ==(unsigned int a, const vuint8& b) { return vuint8(a) == b; } + + __forceinline vboolf8 operator !=(const vuint8& a, const vuint8& b) { return !(a == b); } + __forceinline vboolf8 operator !=(const vuint8& a, unsigned int b) { return a != vuint8(b); } + __forceinline vboolf8 operator !=(unsigned int a, const vuint8& b) { return vuint8(a) != b; } + + //__forceinline vboolf8 operator < (const vuint8& a, const vuint8& b) { return vboolf8(_mm_castsi128_ps(_mm_cmplt_epu32 (a.vl, b.vl)), + // _mm_castsi128_ps(_mm_cmplt_epu32 (a.vh, b.vh))); } + //__forceinline vboolf8 operator < (const vuint8& a, unsigned int b) { return a < vuint8(b); } + //__forceinline vboolf8 operator < (unsigned int a, const vuint8& b) { return vuint8(a) < b; } + + //__forceinline vboolf8 operator >=(const vuint8& a, const vuint8& b) { return !(a < b); } + //__forceinline vboolf8 operator >=(const vuint8& a, unsigned int b) { return a >= vuint8(b); } + //__forceinline vboolf8 operator >=(unsigned int a, const vuint8& b) { return vuint8(a) >= b; } + + //__forceinline vboolf8 operator > (const vuint8& a, const vuint8& b) { return vboolf8(_mm_castsi128_ps(_mm_cmpgt_epu32 (a.vl, b.vl)), + // _mm_castsi128_ps(_mm_cmpgt_epu32 (a.vh, b.vh))); } + //__forceinline vboolf8 operator > (const vuint8& a, unsigned int b) { return a > vuint8(b); } + //__forceinline vboolf8 operator > (unsigned int a, const vuint8& b) { return vuint8(a) > b; } + + //__forceinline vboolf8 operator <=(const vuint8& a, const vuint8& b) { return !(a > b); } + //__forceinline vboolf8 operator <=(const vuint8& a, unsigned int b) { return a <= vuint8(b); } + //__forceinline vboolf8 operator <=(unsigned int a, const vuint8& b) { return vuint8(a) <= b; } + + __forceinline vboolf8 eq(const vuint8& a, const vuint8& b) { return a == b; } + __forceinline vboolf8 ne(const vuint8& a, const vuint8& b) { return a != b; } + + __forceinline vboolf8 eq(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a == b); } + __forceinline vboolf8 ne(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a != b); } + + __forceinline vuint8 select(const vboolf8& m, const vuint8& t, const vuint8& f) { + return _mm256_castps_si256(_mm256_blendv_ps(_mm256_castsi256_ps(f), _mm256_castsi256_ps(t), m)); + } + + __forceinline vuint8 notand(const vboolf8& m, const vuint8& f) { + return _mm256_castps_si256(_mm256_andnot_ps(m, _mm256_castsi256_ps(f))); + } + + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint8 unpacklo(const vuint8& a, const vuint8& b) { return _mm256_castps_si256(_mm256_unpacklo_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + __forceinline vuint8 unpackhi(const vuint8& a, const vuint8& b) { return _mm256_castps_si256(_mm256_unpackhi_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + + template + __forceinline vuint8 shuffle(const vuint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vuint8 shuffle4(const vuint8& v) { + return _mm256_permute2f128_si256(v, v, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vuint8 shuffle4(const vuint8& a, const vuint8& b) { + return _mm256_permute2f128_si256(a, b, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vuint8 shuffle(const vuint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vuint8 shuffle(const vuint8& a, const vuint8& b) { + return _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template<> __forceinline vuint8 shuffle<0, 0, 2, 2>(const vuint8& v) { return _mm256_castps_si256(_mm256_moveldup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vuint8 shuffle<1, 1, 3, 3>(const vuint8& v) { return _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vuint8 shuffle<0, 1, 0, 1>(const vuint8& v) { return _mm256_castps_si256(_mm256_castpd_ps(_mm256_movedup_pd(_mm256_castps_pd(_mm256_castsi256_ps(v))))); } + + __forceinline vuint8 broadcast(const unsigned int* ptr) { return _mm256_castps_si256(_mm256_broadcast_ss((const float*)ptr)); } + template __forceinline vuint8 insert4(const vuint8& a, const vuint4& b) { return _mm256_insertf128_si256(a, b, i); } + template __forceinline vuint4 extract4(const vuint8& a) { return _mm256_extractf128_si256(a, i); } + template<> __forceinline vuint4 extract4<0>(const vuint8& a) { return _mm256_castsi256_si128(a); } + + __forceinline int toScalar(const vuint8& v) { return _mm_cvtsi128_si32(_mm256_castsi256_si128(v)); } + + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + //__forceinline vuint8 vreduce_min2(const vuint8& v) { return min(v,shuffle<1,0,3,2>(v)); } + //__forceinline vuint8 vreduce_min4(const vuint8& v) { vuint8 v1 = vreduce_min2(v); return min(v1,shuffle<2,3,0,1>(v1)); } + //__forceinline vuint8 vreduce_min (const vuint8& v) { vuint8 v1 = vreduce_min4(v); return min(v1,shuffle4<1,0>(v1)); } + + //__forceinline vuint8 vreduce_max2(const vuint8& v) { return max(v,shuffle<1,0,3,2>(v)); } + //__forceinline vuint8 vreduce_max4(const vuint8& v) { vuint8 v1 = vreduce_max2(v); return max(v1,shuffle<2,3,0,1>(v1)); } + //__forceinline vuint8 vreduce_max (const vuint8& v) { vuint8 v1 = vreduce_max4(v); return max(v1,shuffle4<1,0>(v1)); } + + __forceinline vuint8 vreduce_add2(const vuint8& v) { return v + shuffle<1,0,3,2>(v); } + __forceinline vuint8 vreduce_add4(const vuint8& v) { vuint8 v1 = vreduce_add2(v); return v1 + shuffle<2,3,0,1>(v1); } + __forceinline vuint8 vreduce_add (const vuint8& v) { vuint8 v1 = vreduce_add4(v); return v1 + shuffle4<1,0>(v1); } + + //__forceinline int reduce_min(const vuint8& v) { return toScalar(vreduce_min(v)); } + //__forceinline int reduce_max(const vuint8& v) { return toScalar(vreduce_max(v)); } + __forceinline int reduce_add(const vuint8& v) { return toScalar(vreduce_add(v)); } + + //__forceinline size_t select_min(const vuint8& v) { return bsf(movemask(v == vreduce_min(v))); } + //__forceinline size_t select_max(const vuint8& v) { return bsf(movemask(v == vreduce_max(v))); } + + //__forceinline size_t select_min(const vboolf8& valid, const vuint8& v) { const vuint8 a = select(valid,v,vuint8(pos_inf)); return bsf(movemask(valid & (a == vreduce_min(a)))); } + //__forceinline size_t select_max(const vboolf8& valid, const vuint8& v) { const vuint8 a = select(valid,v,vuint8(neg_inf)); return bsf(movemask(valid & (a == vreduce_max(a)))); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vuint8& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/simd/vuint8_avx2.h b/thirdparty/embree/common/simd/vuint8_avx2.h new file mode 100644 index 000000000000..ae243ddfb173 --- /dev/null +++ b/thirdparty/embree/common/simd/vuint8_avx2.h @@ -0,0 +1,434 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* 8-wide AVX integer type */ + template<> + struct vuint<8> + { + ALIGNED_STRUCT_(32); + + typedef vboolf8 Bool; + typedef vuint8 Int; + typedef vfloat8 Float; + + enum { size = 8 }; // number of SIMD elements + union { // data + __m256i v; + unsigned int i[8]; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint() {} + __forceinline vuint(const vuint8& a) { v = a.v; } + __forceinline vuint8& operator =(const vuint8& a) { v = a.v; return *this; } + + __forceinline vuint(__m256i a) : v(a) {} + __forceinline operator const __m256i&() const { return v; } + __forceinline operator __m256i&() { return v; } + + __forceinline explicit vuint(const vuint4& a) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),a,1)) {} + __forceinline vuint(const vuint4& a, const vuint4& b) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),b,1)) {} + __forceinline vuint(const __m128i& a, const __m128i& b) : v(_mm256_insertf128_si256(_mm256_castsi128_si256(a),b,1)) {} + + __forceinline explicit vuint(const unsigned int* a) : v(_mm256_castps_si256(_mm256_loadu_ps((const float*)a))) {} + __forceinline vuint(unsigned int a) : v(_mm256_set1_epi32(a)) {} + __forceinline vuint(unsigned int a, unsigned int b) : v(_mm256_set_epi32(b, a, b, a, b, a, b, a)) {} + __forceinline vuint(unsigned int a, unsigned int b, unsigned int c, unsigned int d) : v(_mm256_set_epi32(d, c, b, a, d, c, b, a)) {} + __forceinline vuint(unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e, unsigned int f, unsigned int g, unsigned int h) : v(_mm256_set_epi32(h, g, f, e, d, c, b, a)) {} + + __forceinline explicit vuint(__m256 a) : v(_mm256_cvtps_epi32(a)) {} + +#if defined(__AVX512VL__) + __forceinline explicit vuint(const vboolf8& a) : v(_mm256_movm_epi32(a)) {} +#else + __forceinline explicit vuint(const vboolf8& a) : v(_mm256_castps_si256((__m256)a)) {} +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint(ZeroTy) : v(_mm256_setzero_si256()) {} + __forceinline vuint(OneTy) : v(_mm256_set1_epi32(1)) {} + __forceinline vuint(PosInfTy) : v(_mm256_set1_epi32(pos_inf)) {} + __forceinline vuint(NegInfTy) : v(_mm256_set1_epi32(neg_inf)) {} + __forceinline vuint(StepTy) : v(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0)) {} + __forceinline vuint(UndefinedTy) : v(_mm256_undefined_si256()) {} + + //////////////////////////////////////////////////////////////////////////////// + /// Loads and Stores + //////////////////////////////////////////////////////////////////////////////// + + static __forceinline vuint8 load(const unsigned char* ptr) { return _mm256_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); } + static __forceinline vuint8 loadu(const unsigned char* ptr) { return _mm256_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)ptr)); } + static __forceinline vuint8 load(const unsigned short* ptr) { return _mm256_cvtepu16_epi32(_mm_load_si128((__m128i*)ptr)); } + static __forceinline vuint8 loadu(const unsigned short* ptr) { return _mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i*)ptr)); } + + static __forceinline vuint8 load(const void* ptr) { return _mm256_load_si256((__m256i*)ptr); } + static __forceinline vuint8 loadu(const void* ptr) { return _mm256_loadu_si256((__m256i*)ptr); } + + static __forceinline void store (void* ptr, const vuint8& v) { _mm256_store_si256((__m256i*)ptr,v); } + static __forceinline void storeu(void* ptr, const vuint8& v) { _mm256_storeu_ps((float*)ptr,_mm256_castsi256_ps(v)); } + +#if defined(__AVX512VL__) + + static __forceinline vuint8 compact(const vboolf8& mask, vuint8 &v) { + return _mm256_mask_compress_epi32(v, mask, v); + } + static __forceinline vuint8 compact(const vboolf8& mask, vuint8 &a, const vuint8& b) { + return _mm256_mask_compress_epi32(a, mask, b); + } + + static __forceinline vuint8 load (const vboolf8& mask, const void* ptr) { return _mm256_mask_load_epi32 (_mm256_setzero_si256(),mask,ptr); } + static __forceinline vuint8 loadu(const vboolf8& mask, const void* ptr) { return _mm256_mask_loadu_epi32(_mm256_setzero_si256(),mask,ptr); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vuint8& v) { _mm256_mask_store_epi32 (ptr,mask,v); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vuint8& v) { _mm256_mask_storeu_epi32(ptr,mask,v); } +#else + static __forceinline vuint8 load (const vboolf8& mask, const void* ptr) { return _mm256_castps_si256(_mm256_maskload_ps((float*)ptr,mask)); } + static __forceinline vuint8 loadu(const vboolf8& mask, const void* ptr) { return _mm256_castps_si256(_mm256_maskload_ps((float*)ptr,mask)); } + + static __forceinline void store (const vboolf8& mask, void* ptr, const vuint8& v) { _mm256_maskstore_epi32((int*)ptr,mask,v); } + static __forceinline void storeu(const vboolf8& mask, void* ptr, const vuint8& v) { _mm256_maskstore_epi32((int*)ptr,mask,v); } +#endif + + static __forceinline vuint8 load_nt(void* ptr) { + return _mm256_stream_load_si256((__m256i*)ptr); + } + + static __forceinline void store_nt(void* ptr, const vuint8& v) { + _mm256_stream_ps((float*)ptr,_mm256_castsi256_ps(v)); + } + + static __forceinline void store(unsigned char* ptr, const vuint8& i) + { + for (size_t j=0; j<8; j++) + ptr[j] = i[j]; + } + + static __forceinline void store(unsigned short* ptr, const vuint8& v) { + for (size_t i=0;i<8;i++) + ptr[i] = (unsigned short)v[i]; + } + + template + static __forceinline vuint8 gather(const unsigned int *const ptr, const vint8& index) { + return _mm256_i32gather_epi32((const int*) ptr, index, scale); + } + + template + static __forceinline vuint8 gather(const vboolf8& mask, const unsigned int *const ptr, const vint8& index) { + vuint8 r = zero; +#if defined(__AVX512VL__) + return _mm256_mmask_i32gather_epi32(r, mask, index, (const int*) ptr, scale); +#else + return _mm256_mask_i32gather_epi32(r, (const int*) ptr, index, mask, scale); +#endif + } + + template + static __forceinline void scatter(void* ptr, const vint8& ofs, const vuint8& v) + { +#if defined(__AVX512VL__) + _mm256_i32scatter_epi32((int*)ptr, ofs, v, scale); +#else + *(unsigned int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + *(unsigned int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + *(unsigned int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + *(unsigned int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + *(unsigned int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + *(unsigned int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + *(unsigned int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + *(unsigned int*)(((char*)ptr)+scale*ofs[7]) = v[7]; +#endif + } + + template + static __forceinline void scatter(const vboolf8& mask, void* ptr, const vint8& ofs, const vuint8& v) + { +#if defined(__AVX512VL__) + _mm256_mask_i32scatter_epi32((int*)ptr, mask, ofs, v, scale); +#else + if (likely(mask[0])) *(unsigned int*)(((char*)ptr)+scale*ofs[0]) = v[0]; + if (likely(mask[1])) *(unsigned int*)(((char*)ptr)+scale*ofs[1]) = v[1]; + if (likely(mask[2])) *(unsigned int*)(((char*)ptr)+scale*ofs[2]) = v[2]; + if (likely(mask[3])) *(unsigned int*)(((char*)ptr)+scale*ofs[3]) = v[3]; + if (likely(mask[4])) *(unsigned int*)(((char*)ptr)+scale*ofs[4]) = v[4]; + if (likely(mask[5])) *(unsigned int*)(((char*)ptr)+scale*ofs[5]) = v[5]; + if (likely(mask[6])) *(unsigned int*)(((char*)ptr)+scale*ofs[6]) = v[6]; + if (likely(mask[7])) *(unsigned int*)(((char*)ptr)+scale*ofs[7]) = v[7]; +#endif + } + + static __forceinline vuint8 broadcast64(const long long &a) { return _mm256_set1_epi64x(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Array Access + //////////////////////////////////////////////////////////////////////////////// + + __forceinline const unsigned int& operator [](size_t index) const { assert(index < 8); return i[index]; } + __forceinline unsigned int& operator [](size_t index) { assert(index < 8); return i[index]; } + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Unary Operators + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf8 asBool(const vuint8& a) { return _mm256_movepi32_mask(a); } +#else + __forceinline vboolf8 asBool(const vuint8& a) { return _mm256_castsi256_ps(a); } +#endif + + __forceinline vuint8 operator +(const vuint8& a) { return a; } + + //////////////////////////////////////////////////////////////////////////////// + /// Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint8 operator +(const vuint8& a, const vuint8& b) { return _mm256_add_epi32(a, b); } + __forceinline vuint8 operator +(const vuint8& a, unsigned int b) { return a + vuint8(b); } + __forceinline vuint8 operator +(unsigned int a, const vuint8& b) { return vuint8(a) + b; } + + __forceinline vuint8 operator -(const vuint8& a, const vuint8& b) { return _mm256_sub_epi32(a, b); } + __forceinline vuint8 operator -(const vuint8& a, unsigned int b) { return a - vuint8(b); } + __forceinline vuint8 operator -(unsigned int a, const vuint8& b) { return vuint8(a) - b; } + + //__forceinline vuint8 operator *(const vuint8& a, const vuint8& b) { return _mm256_mullo_epu32(a, b); } + //__forceinline vuint8 operator *(const vuint8& a, unsigned int b) { return a * vuint8(b); } + //__forceinline vuint8 operator *(unsigned int a, const vuint8& b) { return vuint8(a) * b; } + + __forceinline vuint8 operator &(const vuint8& a, const vuint8& b) { return _mm256_and_si256(a, b); } + __forceinline vuint8 operator &(const vuint8& a, unsigned int b) { return a & vuint8(b); } + __forceinline vuint8 operator &(unsigned int a, const vuint8& b) { return vuint8(a) & b; } + + __forceinline vuint8 operator |(const vuint8& a, const vuint8& b) { return _mm256_or_si256(a, b); } + __forceinline vuint8 operator |(const vuint8& a, unsigned int b) { return a | vuint8(b); } + __forceinline vuint8 operator |(unsigned int a, const vuint8& b) { return vuint8(a) | b; } + + __forceinline vuint8 operator ^(const vuint8& a, const vuint8& b) { return _mm256_xor_si256(a, b); } + __forceinline vuint8 operator ^(const vuint8& a, unsigned int b) { return a ^ vuint8(b); } + __forceinline vuint8 operator ^(unsigned int a, const vuint8& b) { return vuint8(a) ^ b; } + + __forceinline vuint8 operator <<(const vuint8& a, unsigned int n) { return _mm256_slli_epi32(a, n); } + __forceinline vuint8 operator >>(const vuint8& a, unsigned int n) { return _mm256_srli_epi32(a, n); } + + __forceinline vuint8 operator <<(const vuint8& a, const vuint8& n) { return _mm256_sllv_epi32(a, n); } + __forceinline vuint8 operator >>(const vuint8& a, const vuint8& n) { return _mm256_srlv_epi32(a, n); } + + __forceinline vuint8 sll(const vuint8& a, unsigned int b) { return _mm256_slli_epi32(a, b); } + __forceinline vuint8 sra(const vuint8& a, unsigned int b) { return _mm256_srai_epi32(a, b); } + __forceinline vuint8 srl(const vuint8& a, unsigned int b) { return _mm256_srli_epi32(a, b); } + + __forceinline vuint8 sll(const vuint8& a, const vuint8& b) { return _mm256_sllv_epi32(a, b); } + __forceinline vuint8 sra(const vuint8& a, const vuint8& b) { return _mm256_srav_epi32(a, b); } + __forceinline vuint8 srl(const vuint8& a, const vuint8& b) { return _mm256_srlv_epi32(a, b); } + + __forceinline vuint8 min(const vuint8& a, const vuint8& b) { return _mm256_min_epu32(a, b); } + __forceinline vuint8 min(const vuint8& a, unsigned int b) { return min(a,vuint8(b)); } + __forceinline vuint8 min(unsigned int a, const vuint8& b) { return min(vuint8(a),b); } + + __forceinline vuint8 max(const vuint8& a, const vuint8& b) { return _mm256_max_epu32(a, b); } + __forceinline vuint8 max(const vuint8& a, unsigned int b) { return max(a,vuint8(b)); } + __forceinline vuint8 max(unsigned int a, const vuint8& b) { return max(vuint8(a),b); } + + //////////////////////////////////////////////////////////////////////////////// + /// Assignment Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint8& operator +=(vuint8& a, const vuint8& b) { return a = a + b; } + __forceinline vuint8& operator +=(vuint8& a, unsigned int b) { return a = a + b; } + + __forceinline vuint8& operator -=(vuint8& a, const vuint8& b) { return a = a - b; } + __forceinline vuint8& operator -=(vuint8& a, unsigned int b) { return a = a - b; } + + //__forceinline vuint8& operator *=(vuint8& a, const vuint8& b) { return a = a * b; } + //__forceinline vuint8& operator *=(vuint8& a, unsigned int b) { return a = a * b; } + + __forceinline vuint8& operator &=(vuint8& a, const vuint8& b) { return a = a & b; } + __forceinline vuint8& operator &=(vuint8& a, unsigned int b) { return a = a & b; } + + __forceinline vuint8& operator |=(vuint8& a, const vuint8& b) { return a = a | b; } + __forceinline vuint8& operator |=(vuint8& a, unsigned int b) { return a = a | b; } + + __forceinline vuint8& operator <<=(vuint8& a, const unsigned int b) { return a = a << b; } + __forceinline vuint8& operator >>=(vuint8& a, const unsigned int b) { return a = a >> b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + Select + //////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVX512VL__) + __forceinline vboolf8 operator ==(const vuint8& a, const vuint8& b) { return _mm256_cmp_epu32_mask(a,b,_MM_CMPINT_EQ); } + __forceinline vboolf8 operator !=(const vuint8& a, const vuint8& b) { return _mm256_cmp_epu32_mask(a,b,_MM_CMPINT_NE); } + __forceinline vboolf8 operator < (const vuint8& a, const vuint8& b) { return _mm256_cmp_epu32_mask(a,b,_MM_CMPINT_LT); } + __forceinline vboolf8 operator >=(const vuint8& a, const vuint8& b) { return _mm256_cmp_epu32_mask(a,b,_MM_CMPINT_GE); } + __forceinline vboolf8 operator > (const vuint8& a, const vuint8& b) { return _mm256_cmp_epu32_mask(a,b,_MM_CMPINT_GT); } + __forceinline vboolf8 operator <=(const vuint8& a, const vuint8& b) { return _mm256_cmp_epu32_mask(a,b,_MM_CMPINT_LE); } + + __forceinline vuint8 select(const vboolf8& m, const vuint8& t, const vuint8& f) { + return _mm256_mask_blend_epi32(m, (__m256i)f, (__m256i)t); + } +#else + __forceinline vboolf8 operator ==(const vuint8& a, const vuint8& b) { return _mm256_castsi256_ps(_mm256_cmpeq_epi32(a, b)); } + __forceinline vboolf8 operator !=(const vuint8& a, const vuint8& b) { return !(a == b); } + //__forceinline vboolf8 operator < (const vuint8& a, const vuint8& b) { return _mm256_castsi256_ps(_mm256_cmpgt_epu32(b, a)); } + //__forceinline vboolf8 operator >=(const vuint8& a, const vuint8& b) { return !(a < b); } + //__forceinline vboolf8 operator > (const vuint8& a, const vuint8& b) { return _mm256_castsi256_ps(_mm256_cmpgt_epu32(a, b)); } + //__forceinline vboolf8 operator <=(const vuint8& a, const vuint8& b) { return !(a > b); } + + __forceinline vuint8 select(const vboolf8& m, const vuint8& t, const vuint8& f) { + return _mm256_castps_si256(_mm256_blendv_ps(_mm256_castsi256_ps(f), _mm256_castsi256_ps(t), m)); + } +#endif + + template + __forceinline vuint8 select(const vuint8& t, const vuint8& f) { + return _mm256_blend_epi32(f, t, mask); + } + + __forceinline vboolf8 operator ==(const vuint8& a, unsigned int b) { return a == vuint8(b); } + __forceinline vboolf8 operator ==(unsigned int a, const vuint8& b) { return vuint8(a) == b; } + + __forceinline vboolf8 operator !=(const vuint8& a, unsigned int b) { return a != vuint8(b); } + __forceinline vboolf8 operator !=(unsigned int a, const vuint8& b) { return vuint8(a) != b; } + + //__forceinline vboolf8 operator < (const vuint8& a, unsigned int b) { return a < vuint8(b); } + //__forceinline vboolf8 operator < (unsigned int a, const vuint8& b) { return vuint8(a) < b; } + + //__forceinline vboolf8 operator >=(const vuint8& a, unsigned int b) { return a >= vuint8(b); } + //__forceinline vboolf8 operator >=(unsigned int a, const vuint8& b) { return vuint8(a) >= b; } + + //__forceinline vboolf8 operator > (const vuint8& a, unsigned int b) { return a > vuint8(b); } + //__forceinline vboolf8 operator > (unsigned int a, const vuint8& b) { return vuint8(a) > b; } + + //__forceinline vboolf8 operator <=(const vuint8& a, unsigned int b) { return a <= vuint8(b); } + //__forceinline vboolf8 operator <=(unsigned int a, const vuint8& b) { return vuint8(a) <= b; } + + __forceinline vboolf8 eq(const vuint8& a, const vuint8& b) { return a == b; } + __forceinline vboolf8 ne(const vuint8& a, const vuint8& b) { return a != b; } + //__forceinline vboolf8 lt(const vuint8& a, const vuint8& b) { return a < b; } + //__forceinline vboolf8 ge(const vuint8& a, const vuint8& b) { return a >= b; } + //__forceinline vboolf8 gt(const vuint8& a, const vuint8& b) { return a > b; } + //__forceinline vboolf8 le(const vuint8& a, const vuint8& b) { return a <= b; } + +#if defined(__AVX512VL__) + __forceinline vboolf8 eq(const vboolf8& mask, const vuint8& a, const vuint8& b) { return _mm256_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_EQ); } + __forceinline vboolf8 ne(const vboolf8& mask, const vuint8& a, const vuint8& b) { return _mm256_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_NE); } + __forceinline vboolf8 lt(const vboolf8& mask, const vuint8& a, const vuint8& b) { return _mm256_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_LT); } + __forceinline vboolf8 ge(const vboolf8& mask, const vuint8& a, const vuint8& b) { return _mm256_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_GE); } + __forceinline vboolf8 gt(const vboolf8& mask, const vuint8& a, const vuint8& b) { return _mm256_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_GT); } + __forceinline vboolf8 le(const vboolf8& mask, const vuint8& a, const vuint8& b) { return _mm256_mask_cmp_epu32_mask(mask, a, b, _MM_CMPINT_LE); } +#else + __forceinline vboolf8 eq(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a == b); } + __forceinline vboolf8 ne(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a != b); } + //__forceinline vboolf8 lt(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a < b); } + //__forceinline vboolf8 ge(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a >= b); } + //__forceinline vboolf8 gt(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a > b); } + //__forceinline vboolf8 le(const vboolf8& mask, const vuint8& a, const vuint8& b) { return mask & (a <= b); } +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Movement/Shifting/Shuffling Functions + //////////////////////////////////////////////////////////////////////////////// + + __forceinline vuint8 unpacklo(const vuint8& a, const vuint8& b) { return _mm256_unpacklo_epi32(a, b); } + __forceinline vuint8 unpackhi(const vuint8& a, const vuint8& b) { return _mm256_unpackhi_epi32(a, b); } + + template + __forceinline vuint8 shuffle(const vuint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i, i, i, i))); + } + + template + __forceinline vuint8 shuffle4(const vuint8& v) { + return _mm256_permute2f128_si256(v, v, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vuint8 shuffle4(const vuint8& a, const vuint8& b) { + return _mm256_permute2f128_si256(a, b, (i1 << 4) | (i0 << 0)); + } + + template + __forceinline vuint8 shuffle(const vuint8& v) { + return _mm256_castps_si256(_mm256_permute_ps(_mm256_castsi256_ps(v), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template + __forceinline vuint8 shuffle(const vuint8& a, const vuint8& b) { + return _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); + } + + template<> __forceinline vuint8 shuffle<0, 0, 2, 2>(const vuint8& v) { return _mm256_castps_si256(_mm256_moveldup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vuint8 shuffle<1, 1, 3, 3>(const vuint8& v) { return _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(v))); } + template<> __forceinline vuint8 shuffle<0, 1, 0, 1>(const vuint8& v) { return _mm256_castps_si256(_mm256_castpd_ps(_mm256_movedup_pd(_mm256_castps_pd(_mm256_castsi256_ps(v))))); } + + __forceinline vuint8 broadcast(const unsigned int* ptr) { return _mm256_castps_si256(_mm256_broadcast_ss((const float*)ptr)); } + + template __forceinline vuint8 insert4(const vuint8& a, const vuint4& b) { return _mm256_insertf128_si256(a, b, i); } + template __forceinline vuint4 extract4(const vuint8& a) { return _mm256_extractf128_si256(a, i); } + template<> __forceinline vuint4 extract4<0>(const vuint8& a) { return _mm256_castsi256_si128(a); } + + __forceinline int toScalar(const vuint8& v) { return _mm_cvtsi128_si32(_mm256_castsi256_si128(v)); } + + __forceinline vuint8 permute(const vuint8& v, const __m256i& index) { + return _mm256_permutevar8x32_epi32(v, index); + } + + __forceinline vuint8 shuffle(const vuint8& v, const __m256i& index) { + return _mm256_castps_si256(_mm256_permutevar_ps(_mm256_castsi256_ps(v), index)); + } + + template + __forceinline vuint8 align_shift_right(const vuint8& a, const vuint8& b) { +#if defined(__AVX512VL__) + return _mm256_alignr_epi32(a, b, i); +#else + return _mm256_alignr_epi8(a, b, 4*i); +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + /// Reductions + //////////////////////////////////////////////////////////////////////////////// + + //__forceinline vuint8 vreduce_min2(const vuint8& v) { return min(v,shuffle<1,0,3,2>(v)); } + //__forceinline vuint8 vreduce_min4(const vuint8& v) { vuint8 v1 = vreduce_min2(v); return min(v1,shuffle<2,3,0,1>(v1)); } + //__forceinline vuint8 vreduce_min (const vuint8& v) { vuint8 v1 = vreduce_min4(v); return min(v1,shuffle4<1,0>(v1)); } + + //__forceinline vuint8 vreduce_max2(const vuint8& v) { return max(v,shuffle<1,0,3,2>(v)); } + //__forceinline vuint8 vreduce_max4(const vuint8& v) { vuint8 v1 = vreduce_max2(v); return max(v1,shuffle<2,3,0,1>(v1)); } + //__forceinline vuint8 vreduce_max (const vuint8& v) { vuint8 v1 = vreduce_max4(v); return max(v1,shuffle4<1,0>(v1)); } + + __forceinline vuint8 vreduce_add2(const vuint8& v) { return v + shuffle<1,0,3,2>(v); } + __forceinline vuint8 vreduce_add4(const vuint8& v) { vuint8 v1 = vreduce_add2(v); return v1 + shuffle<2,3,0,1>(v1); } + __forceinline vuint8 vreduce_add (const vuint8& v) { vuint8 v1 = vreduce_add4(v); return v1 + shuffle4<1,0>(v1); } + + //__forceinline int reduce_min(const vuint8& v) { return toScalar(vreduce_min(v)); } + //__forceinline int reduce_max(const vuint8& v) { return toScalar(vreduce_max(v)); } + __forceinline int reduce_add(const vuint8& v) { return toScalar(vreduce_add(v)); } + + //__forceinline size_t select_min(const vuint8& v) { return bsf(movemask(v == vreduce_min(v))); } + //__forceinline size_t select_max(const vuint8& v) { return bsf(movemask(v == vreduce_max(v))); } + + //__forceinline size_t select_min(const vboolf8& valid, const vuint8& v) { const vuint8 a = select(valid,v,vuint8(pos_inf)); return bsf(movemask(valid & (a == vreduce_min(a)))); } + //__forceinline size_t select_max(const vboolf8& valid, const vuint8& v) { const vuint8 a = select(valid,v,vuint8(neg_inf)); return bsf(movemask(valid & (a == vreduce_max(a)))); } + + __forceinline vuint8 assign(const vuint4& a) { return _mm256_castsi128_si256(a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline embree_ostream operator <<(embree_ostream cout, const vuint8& a) { + return cout << "<" << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << ", " << a[4] << ", " << a[5] << ", " << a[6] << ", " << a[7] << ">"; + } +} diff --git a/thirdparty/embree/common/sys/alloc.cpp b/thirdparty/embree/common/sys/alloc.cpp new file mode 100644 index 000000000000..4e8928242eb8 --- /dev/null +++ b/thirdparty/embree/common/sys/alloc.cpp @@ -0,0 +1,306 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "alloc.h" +#include "intrinsics.h" +#include "sysinfo.h" +#include "mutex.h" + +//////////////////////////////////////////////////////////////////////////////// +/// All Platforms +//////////////////////////////////////////////////////////////////////////////// + +namespace embree +{ + void* alignedMalloc(size_t size, size_t align) + { + if (size == 0) + return nullptr; + + assert((align & (align-1)) == 0); + void* ptr = _mm_malloc(size,align); + + if (size != 0 && ptr == nullptr) + throw std::bad_alloc(); + + return ptr; + } + + void alignedFree(void* ptr) + { + if (ptr) + _mm_free(ptr); + } + + static bool huge_pages_enabled = false; + static MutexSys os_init_mutex; + + __forceinline bool isHugePageCandidate(const size_t bytes) + { + if (!huge_pages_enabled) + return false; + + /* use huge pages only when memory overhead is low */ + const size_t hbytes = (bytes+PAGE_SIZE_2M-1) & ~size_t(PAGE_SIZE_2M-1); + return 66*(hbytes-bytes) < bytes; // at most 1.5% overhead + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Windows Platform +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include +#include + +namespace embree +{ + bool win_enable_selockmemoryprivilege (bool verbose) + { + HANDLE hToken; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) { + if (verbose) std::cout << "WARNING: OpenProcessToken failed while trying to enable SeLockMemoryPrivilege: " << GetLastError() << std::endl; + return false; + } + + TOKEN_PRIVILEGES tp; + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!LookupPrivilegeValueW(nullptr, L"SeLockMemoryPrivilege", &tp.Privileges[0].Luid)) { + if (verbose) std::cout << "WARNING: LookupPrivilegeValue failed while trying to enable SeLockMemoryPrivilege: " << GetLastError() << std::endl; + return false; + } + + SetLastError(ERROR_SUCCESS); + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), nullptr, 0)) { + if (verbose) std::cout << "WARNING: AdjustTokenPrivileges failed while trying to enable SeLockMemoryPrivilege" << std::endl; + return false; + } + + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { + if (verbose) std::cout << "WARNING: AdjustTokenPrivileges failed to enable SeLockMemoryPrivilege: Add SeLockMemoryPrivilege for current user and run process in elevated mode (Run as administrator)." << std::endl; + return false; + } + + return true; + } + + bool os_init(bool hugepages, bool verbose) + { + Lock lock(os_init_mutex); + + if (!hugepages) { + huge_pages_enabled = false; + return true; + } + + if (GetLargePageMinimum() != PAGE_SIZE_2M) { + huge_pages_enabled = false; + return false; + } + + huge_pages_enabled = true; + return true; + } + + void* os_malloc(size_t bytes, bool& hugepages) + { + if (bytes == 0) { + hugepages = false; + return nullptr; + } + + /* try direct huge page allocation first */ + if (isHugePageCandidate(bytes)) + { + int flags = MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES; + char* ptr = (char*) VirtualAlloc(nullptr,bytes,flags,PAGE_READWRITE); + if (ptr != nullptr) { + hugepages = true; + return ptr; + } + } + + /* fall back to 4k pages */ + int flags = MEM_COMMIT | MEM_RESERVE; + char* ptr = (char*) VirtualAlloc(nullptr,bytes,flags,PAGE_READWRITE); + if (ptr == nullptr) throw std::bad_alloc(); + hugepages = false; + return ptr; + } + + size_t os_shrink(void* ptr, size_t bytesNew, size_t bytesOld, bool hugepages) + { + if (hugepages) // decommitting huge pages seems not to work under Windows + return bytesOld; + + const size_t pageSize = hugepages ? PAGE_SIZE_2M : PAGE_SIZE_4K; + bytesNew = (bytesNew+pageSize-1) & ~(pageSize-1); + bytesOld = (bytesOld+pageSize-1) & ~(pageSize-1); + if (bytesNew >= bytesOld) + return bytesOld; + + if (!VirtualFree((char*)ptr+bytesNew,bytesOld-bytesNew,MEM_DECOMMIT)) + throw std::bad_alloc(); + + return bytesNew; + } + + void os_free(void* ptr, size_t bytes, bool hugepages) + { + if (bytes == 0) + return; + + if (!VirtualFree(ptr,0,MEM_RELEASE)) + throw std::bad_alloc(); + } + + void os_advise(void *ptr, size_t bytes) + { + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Unix Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__UNIX__) + +#include +#include +#include +#include +#include + +#if defined(__MACOSX__) +#include +#endif + +namespace embree +{ + bool os_init(bool hugepages, bool verbose) + { + Lock lock(os_init_mutex); + + if (!hugepages) { + huge_pages_enabled = false; + return true; + } + +#if defined(__LINUX__) + + int hugepagesize = 0; + + std::ifstream file; + file.open("/proc/meminfo",std::ios::in); + if (!file.is_open()) { + if (verbose) std::cout << "WARNING: Could not open /proc/meminfo. Huge page support cannot get enabled!" << std::endl; + huge_pages_enabled = false; + return false; + } + + std::string line; + while (getline(file,line)) + { + std::stringstream sline(line); + while (!sline.eof() && sline.peek() == ' ') sline.ignore(); + std::string tag; getline(sline,tag,' '); + while (!sline.eof() && sline.peek() == ' ') sline.ignore(); + std::string val; getline(sline,val,' '); + while (!sline.eof() && sline.peek() == ' ') sline.ignore(); + std::string unit; getline(sline,unit,' '); + if (tag == "Hugepagesize:" && unit == "kB") { + hugepagesize = std::stoi(val)*1024; + break; + } + } + + if (hugepagesize != PAGE_SIZE_2M) + { + if (verbose) std::cout << "WARNING: Only 2MB huge pages supported. Huge page support cannot get enabled!" << std::endl; + huge_pages_enabled = false; + return false; + } +#endif + + huge_pages_enabled = true; + return true; + } + + void* os_malloc(size_t bytes, bool& hugepages) + { + if (bytes == 0) { + hugepages = false; + return nullptr; + } + + /* try direct huge page allocation first */ + if (isHugePageCandidate(bytes)) + { +#if defined(__MACOSX__) + void* ptr = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, VM_FLAGS_SUPERPAGE_SIZE_2MB, 0); + if (ptr != MAP_FAILED) { + hugepages = true; + return ptr; + } +#elif defined(MAP_HUGETLB) + void* ptr = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0); + if (ptr != MAP_FAILED) { + hugepages = true; + return ptr; + } +#endif + } + + /* fallback to 4k pages */ + void* ptr = (char*) mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (ptr == MAP_FAILED) throw std::bad_alloc(); + hugepages = false; + + /* advise huge page hint for THP */ + os_advise(ptr,bytes); + return ptr; + } + + size_t os_shrink(void* ptr, size_t bytesNew, size_t bytesOld, bool hugepages) + { + const size_t pageSize = hugepages ? PAGE_SIZE_2M : PAGE_SIZE_4K; + bytesNew = (bytesNew+pageSize-1) & ~(pageSize-1); + bytesOld = (bytesOld+pageSize-1) & ~(pageSize-1); + if (bytesNew >= bytesOld) + return bytesOld; + + if (munmap((char*)ptr+bytesNew,bytesOld-bytesNew) == -1) + throw std::bad_alloc(); + + return bytesNew; + } + + void os_free(void* ptr, size_t bytes, bool hugepages) + { + if (bytes == 0) + return; + + /* for hugepages we need to also align the size */ + const size_t pageSize = hugepages ? PAGE_SIZE_2M : PAGE_SIZE_4K; + bytes = (bytes+pageSize-1) & ~(pageSize-1); + if (munmap(ptr,bytes) == -1) + throw std::bad_alloc(); + } + + /* hint for transparent huge pages (THP) */ + void os_advise(void* pptr, size_t bytes) + { +#if defined(MADV_HUGEPAGE) + madvise(pptr,bytes,MADV_HUGEPAGE); +#endif + } +} + +#endif diff --git a/thirdparty/embree/common/sys/alloc.h b/thirdparty/embree/common/sys/alloc.h new file mode 100644 index 000000000000..5898ecda7093 --- /dev/null +++ b/thirdparty/embree/common/sys/alloc.h @@ -0,0 +1,164 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" +#include +#include + +namespace embree +{ +#define ALIGNED_STRUCT_(align) \ + void* operator new(size_t size) { return alignedMalloc(size,align); } \ + void operator delete(void* ptr) { alignedFree(ptr); } \ + void* operator new[](size_t size) { return alignedMalloc(size,align); } \ + void operator delete[](void* ptr) { alignedFree(ptr); } + +#define ALIGNED_CLASS_(align) \ + public: \ + ALIGNED_STRUCT_(align) \ + private: + + /*! aligned allocation */ + void* alignedMalloc(size_t size, size_t align); + void alignedFree(void* ptr); + + /*! allocator that performs aligned allocations */ + template + struct aligned_allocator + { + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + __forceinline pointer allocate( size_type n ) { + return (pointer) alignedMalloc(n*sizeof(value_type),alignment); + } + + __forceinline void deallocate( pointer p, size_type n ) { + return alignedFree(p); + } + + __forceinline void construct( pointer p, const_reference val ) { + new (p) T(val); + } + + __forceinline void destroy( pointer p ) { + p->~T(); + } + }; + + /*! allocates pages directly from OS */ + bool win_enable_selockmemoryprivilege(bool verbose); + bool os_init(bool hugepages, bool verbose); + void* os_malloc (size_t bytes, bool& hugepages); + size_t os_shrink (void* ptr, size_t bytesNew, size_t bytesOld, bool hugepages); + void os_free (void* ptr, size_t bytes, bool hugepages); + void os_advise (void* ptr, size_t bytes); + + /*! allocator that performs OS allocations */ + template + struct os_allocator + { + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + __forceinline os_allocator () + : hugepages(false) {} + + __forceinline pointer allocate( size_type n ) { + return (pointer) os_malloc(n*sizeof(value_type),hugepages); + } + + __forceinline void deallocate( pointer p, size_type n ) { + return os_free(p,n*sizeof(value_type),hugepages); + } + + __forceinline void construct( pointer p, const_reference val ) { + new (p) T(val); + } + + __forceinline void destroy( pointer p ) { + p->~T(); + } + + bool hugepages; + }; + + /*! allocator for IDs */ + template + struct IDPool + { + typedef T value_type; + + IDPool () + : nextID(0) {} + + T allocate() + { + /* return ID from list */ + if (!IDs.empty()) + { + T id = *IDs.begin(); + IDs.erase(IDs.begin()); + return id; + } + + /* allocate new ID */ + else + { + if (size_t(nextID)+1 > max_id) + return -1; + + return nextID++; + } + } + + /* adds an ID provided by the user */ + bool add(T id) + { + if (id > max_id) + return false; + + /* check if ID should be in IDs set */ + if (id < nextID) { + auto p = IDs.find(id); + if (p == IDs.end()) return false; + IDs.erase(p); + return true; + } + + /* otherwise increase ID set */ + else + { + for (T i=nextID; i IDs; //!< stores deallocated IDs to be reused + T nextID; //!< next ID to use when IDs vector is empty + }; +} + diff --git a/thirdparty/embree/common/sys/array.h b/thirdparty/embree/common/sys/array.h new file mode 100644 index 000000000000..6f6f98eac816 --- /dev/null +++ b/thirdparty/embree/common/sys/array.h @@ -0,0 +1,222 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" +#include "alloc.h" + +namespace embree +{ + /*! static array with static size */ + template + class array_t + { + public: + + /********************** Iterators ****************************/ + + __forceinline T* begin() const { return items; }; + __forceinline T* end () const { return items+N; }; + + + /********************** Capacity ****************************/ + + __forceinline bool empty () const { return N == 0; } + __forceinline size_t size () const { return N; } + __forceinline size_t max_size () const { return N; } + + + /******************** Element access **************************/ + + __forceinline T& operator[](size_t i) { assert(i < N); return items[i]; } + __forceinline const T& operator[](size_t i) const { assert(i < N); return items[i]; } + + __forceinline T& at(size_t i) { assert(i < N); return items[i]; } + __forceinline const T& at(size_t i) const { assert(i < N); return items[i]; } + + __forceinline T& front() const { assert(N > 0); return items[0]; }; + __forceinline T& back () const { assert(N > 0); return items[N-1]; }; + + __forceinline T* data() { return items; }; + __forceinline const T* data() const { return items; }; + + private: + T items[N]; + }; + + /*! static array with dynamic size */ + template + class darray_t + { + public: + + __forceinline darray_t () : M(0) {} + + __forceinline darray_t (const T& v) : M(0) { + for (size_t i=0; i 0); return items[0]; }; + __forceinline T& back () const { assert(M > 0); return items[M-1]; }; + + __forceinline T* data() { return items; }; + __forceinline const T* data() const { return items; }; + + private: + size_t M; + T items[N]; + }; + + /*! dynamic sized array that is allocated on the stack */ +#define dynamic_large_stack_array(Ty,Name,N,max_stack_bytes) StackArray Name(N) + template + struct __aligned(64) StackArray + { + __forceinline StackArray (const size_t N) + : N(N) + { + if (N*sizeof(Ty) <= max_stack_bytes) + data = &arr[0]; + else + data = (Ty*) alignedMalloc(N*sizeof(Ty),64); + } + + __forceinline ~StackArray () { + if (data != &arr[0]) alignedFree(data); + } + + __forceinline operator Ty* () { return data; } + __forceinline operator const Ty* () const { return data; } + + __forceinline Ty& operator[](const int i) { assert(i>=0 && i=0 && i + struct __aligned(64) DynamicStackArray + { + __forceinline DynamicStackArray () + : data(&arr[0]) {} + + __forceinline ~DynamicStackArray () + { + if (!isStackAllocated()) + delete[] data; + } + + __forceinline bool isStackAllocated() const { + return data == &arr[0]; + } + + __forceinline size_t size() const + { + if (isStackAllocated()) return max_stack_elements; + else return max_total_elements; + } + + __forceinline void resize(size_t M) + { + assert(M <= max_total_elements); + if (likely(M <= max_stack_elements)) return; + if (likely(!isStackAllocated())) return; + + data = new Ty[max_total_elements]; + + for (size_t i=0; i=0 && ioperator[] (i) = other[i]; + } + + DynamicStackArray& operator= (const DynamicStackArray& other) + { + for (size_t i=0; ioperator[] (i) = other[i]; + + return *this; + } + + private: + Ty arr[max_stack_elements]; + Ty* data; + }; +} diff --git a/thirdparty/embree/common/sys/atomic.h b/thirdparty/embree/common/sys/atomic.h new file mode 100644 index 000000000000..ebfb8552c358 --- /dev/null +++ b/thirdparty/embree/common/sys/atomic.h @@ -0,0 +1,59 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "intrinsics.h" + +namespace embree +{ +/* compiler memory barriers */ +#if defined(__INTEL_COMPILER) +//#define __memory_barrier() __memory_barrier() +#elif defined(__GNUC__) || defined(__clang__) +# define __memory_barrier() asm volatile("" ::: "memory") +#elif defined(_MSC_VER) +# define __memory_barrier() _ReadWriteBarrier() +#endif + + template + struct atomic : public std::atomic + { + atomic () {} + + atomic (const T& a) + : std::atomic(a) {} + + atomic (const atomic& a) { + this->store(a.load()); + } + + atomic& operator=(const atomic& other) { + this->store(other.load()); + return *this; + } + }; + + template + __forceinline void atomic_min(std::atomic& aref, const T& bref) + { + const T b = bref.load(); + while (true) { + T a = aref.load(); + if (a <= b) break; + if (aref.compare_exchange_strong(a,b)) break; + } + } + + template + __forceinline void atomic_max(std::atomic& aref, const T& bref) + { + const T b = bref.load(); + while (true) { + T a = aref.load(); + if (a >= b) break; + if (aref.compare_exchange_strong(a,b)) break; + } + } +} diff --git a/thirdparty/embree/common/sys/barrier.cpp b/thirdparty/embree/common/sys/barrier.cpp new file mode 100644 index 000000000000..0061d18db255 --- /dev/null +++ b/thirdparty/embree/common/sys/barrier.cpp @@ -0,0 +1,289 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "barrier.h" +#include "condition.h" +#include "regression.h" +#include "thread.h" + +#if defined (__WIN32__) + +#define WIN32_LEAN_AND_MEAN +#include + +namespace embree +{ + struct BarrierSysImplementation + { + __forceinline BarrierSysImplementation (size_t N) + : i(0), enterCount(0), exitCount(0), barrierSize(0) + { + events[0] = CreateEvent(nullptr, TRUE, FALSE, nullptr); + events[1] = CreateEvent(nullptr, TRUE, FALSE, nullptr); + init(N); + } + + __forceinline ~BarrierSysImplementation () + { + CloseHandle(events[0]); + CloseHandle(events[1]); + } + + __forceinline void init(size_t N) + { + barrierSize = N; + enterCount.store(N); + exitCount.store(N); + } + + __forceinline void wait() + { + /* every thread entering the barrier decrements this count */ + size_t i0 = i; + size_t cnt0 = enterCount--; + + /* all threads except the last one are wait in the barrier */ + if (cnt0 > 1) + { + if (WaitForSingleObject(events[i0], INFINITE) != WAIT_OBJECT_0) + THROW_RUNTIME_ERROR("WaitForSingleObjects failed"); + } + + /* the last thread starts all threads waiting at the barrier */ + else + { + i = 1-i; + enterCount.store(barrierSize); + if (SetEvent(events[i0]) == 0) + THROW_RUNTIME_ERROR("SetEvent failed"); + } + + /* every thread leaving the barrier decrements this count */ + size_t cnt1 = exitCount--; + + /* the last thread that left the barrier resets the event again */ + if (cnt1 == 1) + { + exitCount.store(barrierSize); + if (ResetEvent(events[i0]) == 0) + THROW_RUNTIME_ERROR("ResetEvent failed"); + } + } + + public: + HANDLE events[2]; + atomic i; + atomic enterCount; + atomic exitCount; + size_t barrierSize; + }; +} + +#else + +namespace embree +{ + struct BarrierSysImplementation + { + __forceinline BarrierSysImplementation (size_t N) + : count(0), barrierSize(0) + { + init(N); + } + + __forceinline void init(size_t N) + { + assert(count == 0); + count = 0; + barrierSize = N; + } + + __forceinline void wait() + { + mutex.lock(); + count++; + + if (count == barrierSize) { + count = 0; + cond.notify_all(); + mutex.unlock(); + return; + } + + cond.wait(mutex); + mutex.unlock(); + return; + } + + public: + MutexSys mutex; + ConditionSys cond; + volatile size_t count; + volatile size_t barrierSize; + }; +} + +#endif + +namespace embree +{ + BarrierSys::BarrierSys (size_t N) { + opaque = new BarrierSysImplementation(N); + } + + BarrierSys::~BarrierSys () { + delete (BarrierSysImplementation*) opaque; + } + + void BarrierSys::init(size_t count) { + ((BarrierSysImplementation*) opaque)->init(count); + } + + void BarrierSys::wait() { + ((BarrierSysImplementation*) opaque)->wait(); + } + + LinearBarrierActive::LinearBarrierActive (size_t N) + : count0(nullptr), count1(nullptr), mode(0), flag0(0), flag1(0), threadCount(0) + { + if (N == 0) N = getNumberOfLogicalThreads(); + init(N); + } + + LinearBarrierActive::~LinearBarrierActive() + { + delete[] count0; + delete[] count1; + } + + void LinearBarrierActive::init(size_t N) + { + if (threadCount != N) { + threadCount = N; + if (count0) delete[] count0; count0 = new unsigned char[N]; + if (count1) delete[] count1; count1 = new unsigned char[N]; + } + mode = 0; + flag0 = 0; + flag1 = 0; + for (size_t i=0; i threadID; + std::atomic numFailed; + std::vector threadResults; + + barrier_sys_regression_test() + : RegressionTest("barrier_sys_regression_test"), threadID(0), numFailed(0) + { + registerRegressionTest(this); + } + + static void thread_alloc(barrier_sys_regression_test* This) + { + size_t tid = This->threadID++; + for (size_t j=0; j<1000; j++) + { + This->barrier.wait(); + This->threadResults[tid] = tid; + This->barrier.wait(); + } + } + + bool run () + { + threadID.store(0); + numFailed.store(0); + + size_t numThreads = getNumberOfLogicalThreads(); + threadResults.resize(numThreads); + barrier.init(numThreads+1); + + /* create threads */ + std::vector threads; + for (size_t i=0; i cntr; + }; + + /*! fast active barrier that does not require initialization to some number of threads */ + struct BarrierActiveAutoReset + { + public: + BarrierActiveAutoReset () + : cntr0(0), cntr1(0) {} + + void wait (size_t threadCount) + { + cntr0.fetch_add(1); + while (cntr0 != threadCount) pause_cpu(); + cntr1.fetch_add(1); + while (cntr1 != threadCount) pause_cpu(); + cntr0.fetch_add(-1); + while (cntr0 != 0) pause_cpu(); + cntr1.fetch_add(-1); + while (cntr1 != 0) pause_cpu(); + } + + private: + std::atomic cntr0; + std::atomic cntr1; + }; + + class LinearBarrierActive + { + public: + + /*! construction and destruction */ + LinearBarrierActive (size_t threadCount = 0); + ~LinearBarrierActive(); + + private: + /*! class in non-copyable */ + LinearBarrierActive (const LinearBarrierActive& other) DELETED; // do not implement + LinearBarrierActive& operator= (const LinearBarrierActive& other) DELETED; // do not implement + + public: + /*! intializes the barrier with some number of threads */ + void init(size_t threadCount); + + /*! thread with threadIndex waits in the barrier */ + void wait (const size_t threadIndex); + + private: + volatile unsigned char* count0; + volatile unsigned char* count1; + volatile unsigned int mode; + volatile unsigned int flag0; + volatile unsigned int flag1; + volatile size_t threadCount; + }; +} + diff --git a/thirdparty/embree/common/sys/condition.cpp b/thirdparty/embree/common/sys/condition.cpp new file mode 100644 index 000000000000..0e7ca7af399d --- /dev/null +++ b/thirdparty/embree/common/sys/condition.cpp @@ -0,0 +1,81 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "condition.h" + +#if defined(__WIN32__) && !defined(PTHREADS_WIN32) + +#define WIN32_LEAN_AND_MEAN +#include + +namespace embree +{ + struct ConditionImplementation + { + __forceinline ConditionImplementation () { + InitializeConditionVariable(&cond); + } + + __forceinline ~ConditionImplementation () { + } + + __forceinline void wait(MutexSys& mutex_in) { + SleepConditionVariableCS(&cond, (LPCRITICAL_SECTION)mutex_in.mutex, INFINITE); + } + + __forceinline void notify_all() { + WakeAllConditionVariable(&cond); + } + + public: + CONDITION_VARIABLE cond; + }; +} +#endif + +#if defined(__UNIX__) || defined(PTHREADS_WIN32) +#include +namespace embree +{ + struct ConditionImplementation + { + __forceinline ConditionImplementation () { + pthread_cond_init(&cond,nullptr); + } + + __forceinline ~ConditionImplementation() { + pthread_cond_destroy(&cond); + } + + __forceinline void wait(MutexSys& mutex) { + pthread_cond_wait(&cond, (pthread_mutex_t*)mutex.mutex); + } + + __forceinline void notify_all() { + pthread_cond_broadcast(&cond); + } + + public: + pthread_cond_t cond; + }; +} +#endif + +namespace embree +{ + ConditionSys::ConditionSys () { + cond = new ConditionImplementation; + } + + ConditionSys::~ConditionSys() { + delete (ConditionImplementation*) cond; + } + + void ConditionSys::wait(MutexSys& mutex) { + ((ConditionImplementation*) cond)->wait(mutex); + } + + void ConditionSys::notify_all() { + ((ConditionImplementation*) cond)->notify_all(); + } +} diff --git a/thirdparty/embree/common/sys/condition.h b/thirdparty/embree/common/sys/condition.h new file mode 100644 index 000000000000..7a3a05aa81af --- /dev/null +++ b/thirdparty/embree/common/sys/condition.h @@ -0,0 +1,31 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "mutex.h" + +namespace embree +{ + class ConditionSys + { + public: + ConditionSys(); + ~ConditionSys(); + void wait( class MutexSys& mutex ); + void notify_all(); + + template + __forceinline void wait( class MutexSys& mutex, const Predicate& pred ) + { + while (!pred()) wait(mutex); + } + + private: + ConditionSys (const ConditionSys& other) DELETED; // do not implement + ConditionSys& operator= (const ConditionSys& other) DELETED; // do not implement + + protected: + void* cond; + }; +} diff --git a/thirdparty/embree/common/sys/filename.cpp b/thirdparty/embree/common/sys/filename.cpp new file mode 100644 index 000000000000..86182c1afbbc --- /dev/null +++ b/thirdparty/embree/common/sys/filename.cpp @@ -0,0 +1,138 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "filename.h" +#include "sysinfo.h" + +namespace embree +{ +#ifdef __WIN32__ + const char path_sep = '\\'; +#else + const char path_sep = '/'; +#endif + + /*! create an empty filename */ + FileName::FileName () {} + + /*! create a valid filename from a string */ + FileName::FileName (const char* in) { + filename = in; + for (size_t i=0; i +#endif + +#include + +#if defined(__BMI__) && defined(__GNUC__) && !defined(__INTEL_COMPILER) + #if !defined(_tzcnt_u32) + #define _tzcnt_u32 __tzcnt_u32 + #endif + #if !defined(_tzcnt_u64) + #define _tzcnt_u64 __tzcnt_u64 + #endif +#endif + +#if defined(__LZCNT__) + #if !defined(_lzcnt_u32) + #define _lzcnt_u32 __lzcnt32 + #endif + #if !defined(_lzcnt_u64) + #define _lzcnt_u64 __lzcnt64 + #endif +#endif + +#if defined(__WIN32__) +// -- GODOT start -- +#if !defined(NOMINMAX) +// -- GODOT end -- +#define NOMINMAX +// -- GODOT start -- +#endif +#include "windows.h" +// -- GODOT end -- +#endif + +/* normally defined in pmmintrin.h, but we always need this */ +#if !defined(_MM_SET_DENORMALS_ZERO_MODE) +#define _MM_DENORMALS_ZERO_ON (0x0040) +#define _MM_DENORMALS_ZERO_OFF (0x0000) +#define _MM_DENORMALS_ZERO_MASK (0x0040) +#define _MM_SET_DENORMALS_ZERO_MODE(x) (_mm_setcsr((_mm_getcsr() & ~_MM_DENORMALS_ZERO_MASK) | (x))) +#endif + +namespace embree +{ + +//////////////////////////////////////////////////////////////////////////////// +/// Windows Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__WIN32__) + + __forceinline size_t read_tsc() + { + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + return (size_t)li.QuadPart; + } + + __forceinline int bsf(int v) { +#if defined(__AVX2__) + return _tzcnt_u32(v); +#else + unsigned long r = 0; _BitScanForward(&r,v); return r; +#endif + } + + __forceinline unsigned bsf(unsigned v) { +#if defined(__AVX2__) + return _tzcnt_u32(v); +#else + unsigned long r = 0; _BitScanForward(&r,v); return r; +#endif + } + +#if defined(__X86_64__) + __forceinline size_t bsf(size_t v) { +#if defined(__AVX2__) + return _tzcnt_u64(v); +#else + unsigned long r = 0; _BitScanForward64(&r,v); return r; +#endif + } +#endif + + __forceinline int bscf(int& v) + { + int i = bsf(v); + v &= v-1; + return i; + } + + __forceinline unsigned bscf(unsigned& v) + { + unsigned i = bsf(v); + v &= v-1; + return i; + } + +#if defined(__X86_64__) + __forceinline size_t bscf(size_t& v) + { + size_t i = bsf(v); + v &= v-1; + return i; + } +#endif + + __forceinline int bsr(int v) { +#if defined(__AVX2__) + return 31 - _lzcnt_u32(v); +#else + unsigned long r = 0; _BitScanReverse(&r,v); return r; +#endif + } + + __forceinline unsigned bsr(unsigned v) { +#if defined(__AVX2__) + return 31 - _lzcnt_u32(v); +#else + unsigned long r = 0; _BitScanReverse(&r,v); return r; +#endif + } + +#if defined(__X86_64__) + __forceinline size_t bsr(size_t v) { +#if defined(__AVX2__) + return 63 -_lzcnt_u64(v); +#else + unsigned long r = 0; _BitScanReverse64(&r, v); return r; +#endif + } +#endif + + __forceinline int lzcnt(const int x) + { +#if defined(__AVX2__) + return _lzcnt_u32(x); +#else + if (unlikely(x == 0)) return 32; + return 31 - bsr(x); +#endif + } + + __forceinline int btc(int v, int i) { + long r = v; _bittestandcomplement(&r,i); return r; + } + + __forceinline int bts(int v, int i) { + long r = v; _bittestandset(&r,i); return r; + } + + __forceinline int btr(int v, int i) { + long r = v; _bittestandreset(&r,i); return r; + } + +#if defined(__X86_64__) + + __forceinline size_t btc(size_t v, size_t i) { + size_t r = v; _bittestandcomplement64((__int64*)&r,i); return r; + } + + __forceinline size_t bts(size_t v, size_t i) { + __int64 r = v; _bittestandset64(&r,i); return r; + } + + __forceinline size_t btr(size_t v, size_t i) { + __int64 r = v; _bittestandreset64(&r,i); return r; + } + +#endif + + __forceinline int32_t atomic_cmpxchg(volatile int32_t* p, const int32_t c, const int32_t v) { + return _InterlockedCompareExchange((volatile long*)p,v,c); + } + +//////////////////////////////////////////////////////////////////////////////// +/// Unix Platform +//////////////////////////////////////////////////////////////////////////////// + +#else + +#if defined(__i386__) && defined(__PIC__) + + __forceinline void __cpuid(int out[4], int op) + { + asm volatile ("xchg{l}\t{%%}ebx, %1\n\t" + "cpuid\n\t" + "xchg{l}\t{%%}ebx, %1\n\t" + : "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3]) + : "0"(op)); + } + + __forceinline void __cpuid_count(int out[4], int op1, int op2) + { + asm volatile ("xchg{l}\t{%%}ebx, %1\n\t" + "cpuid\n\t" + "xchg{l}\t{%%}ebx, %1\n\t" + : "=a" (out[0]), "=r" (out[1]), "=c" (out[2]), "=d" (out[3]) + : "0" (op1), "2" (op2)); + } + +#else + + __forceinline void __cpuid(int out[4], int op) { + asm volatile ("cpuid" : "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3]) : "a"(op)); + } + + __forceinline void __cpuid_count(int out[4], int op1, int op2) { + asm volatile ("cpuid" : "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3]) : "a"(op1), "c"(op2)); + } + +#endif + + __forceinline uint64_t read_tsc() { + uint32_t high,low; + asm volatile ("rdtsc" : "=d"(high), "=a"(low)); + return (((uint64_t)high) << 32) + (uint64_t)low; + } + + __forceinline int bsf(int v) { +#if defined(__AVX2__) + return _tzcnt_u32(v); +#else + int r = 0; asm ("bsf %1,%0" : "=r"(r) : "r"(v)); return r; +#endif + } + +#if defined(__X86_64__) + __forceinline unsigned bsf(unsigned v) + { +#if defined(__AVX2__) + return _tzcnt_u32(v); +#else + unsigned r = 0; asm ("bsf %1,%0" : "=r"(r) : "r"(v)); return r; +#endif + } +#endif + + __forceinline size_t bsf(size_t v) { +#if defined(__AVX2__) +#if defined(__X86_64__) + return _tzcnt_u64(v); +#else + return _tzcnt_u32(v); +#endif +#else + size_t r = 0; asm ("bsf %1,%0" : "=r"(r) : "r"(v)); return r; +#endif + } + + __forceinline int bscf(int& v) + { + int i = bsf(v); + v &= v-1; + return i; + } + +#if defined(__X86_64__) + __forceinline unsigned int bscf(unsigned int& v) + { + unsigned int i = bsf(v); + v &= v-1; + return i; + } +#endif + + __forceinline size_t bscf(size_t& v) + { + size_t i = bsf(v); + v &= v-1; + return i; + } + + __forceinline int bsr(int v) { +#if defined(__AVX2__) + return 31 - _lzcnt_u32(v); +#else + int r = 0; asm ("bsr %1,%0" : "=r"(r) : "r"(v)); return r; +#endif + } + +#if defined(__X86_64__) + __forceinline unsigned bsr(unsigned v) { +#if defined(__AVX2__) + return 31 - _lzcnt_u32(v); +#else + unsigned r = 0; asm ("bsr %1,%0" : "=r"(r) : "r"(v)); return r; +#endif + } +#endif + + __forceinline size_t bsr(size_t v) { +#if defined(__AVX2__) +#if defined(__X86_64__) + return 63 - _lzcnt_u64(v); +#else + return 31 - _lzcnt_u32(v); +#endif +#else + size_t r = 0; asm ("bsr %1,%0" : "=r"(r) : "r"(v)); return r; +#endif + } + + __forceinline int lzcnt(const int x) + { +#if defined(__AVX2__) + return _lzcnt_u32(x); +#else + if (unlikely(x == 0)) return 32; + return 31 - bsr(x); +#endif + } + + __forceinline size_t blsr(size_t v) { +#if defined(__AVX2__) +#if defined(__INTEL_COMPILER) + return _blsr_u64(v); +#else +#if defined(__X86_64__) + return __blsr_u64(v); +#else + return __blsr_u32(v); +#endif +#endif +#else + return v & (v-1); +#endif + } + + __forceinline int btc(int v, int i) { + int r = 0; asm ("btc %1,%0" : "=r"(r) : "r"(i), "0"(v) : "flags" ); return r; + } + + __forceinline int bts(int v, int i) { + int r = 0; asm ("bts %1,%0" : "=r"(r) : "r"(i), "0"(v) : "flags"); return r; + } + + __forceinline int btr(int v, int i) { + int r = 0; asm ("btr %1,%0" : "=r"(r) : "r"(i), "0"(v) : "flags"); return r; + } + + __forceinline size_t btc(size_t v, size_t i) { + size_t r = 0; asm ("btc %1,%0" : "=r"(r) : "r"(i), "0"(v) : "flags" ); return r; + } + + __forceinline size_t bts(size_t v, size_t i) { + size_t r = 0; asm ("bts %1,%0" : "=r"(r) : "r"(i), "0"(v) : "flags"); return r; + } + + __forceinline size_t btr(size_t v, size_t i) { + size_t r = 0; asm ("btr %1,%0" : "=r"(r) : "r"(i), "0"(v) : "flags"); return r; + } + + __forceinline int32_t atomic_cmpxchg(int32_t volatile* value, int32_t comparand, const int32_t input) { + return __sync_val_compare_and_swap(value, comparand, input); + } + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// All Platforms +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__clang__) || defined(__GNUC__) +#if !defined(_mm_undefined_ps) + __forceinline __m128 _mm_undefined_ps() { return _mm_setzero_ps(); } +#endif +#if !defined(_mm_undefined_si128) + __forceinline __m128i _mm_undefined_si128() { return _mm_setzero_si128(); } +#endif +#if !defined(_mm256_undefined_ps) && defined(__AVX__) + __forceinline __m256 _mm256_undefined_ps() { return _mm256_setzero_ps(); } +#endif +#if !defined(_mm256_undefined_si256) && defined(__AVX__) + __forceinline __m256i _mm256_undefined_si256() { return _mm256_setzero_si256(); } +#endif +#if !defined(_mm512_undefined_ps) && defined(__AVX512F__) + __forceinline __m512 _mm512_undefined_ps() { return _mm512_setzero_ps(); } +#endif +#if !defined(_mm512_undefined_epi32) && defined(__AVX512F__) + __forceinline __m512i _mm512_undefined_epi32() { return _mm512_setzero_si512(); } +#endif +#endif + +#if defined(__SSE4_2__) + + __forceinline int popcnt(int in) { + return _mm_popcnt_u32(in); + } + + __forceinline unsigned popcnt(unsigned in) { + return _mm_popcnt_u32(in); + } + +#if defined(__X86_64__) + __forceinline size_t popcnt(size_t in) { + return _mm_popcnt_u64(in); + } +#endif + +#endif + + __forceinline uint64_t rdtsc() + { + int dummy[4]; + __cpuid(dummy,0); + uint64_t clock = read_tsc(); + __cpuid(dummy,0); + return clock; + } + + __forceinline void pause_cpu(const size_t N = 8) + { + for (size_t i=0; i + +namespace embree +{ + /* opens a shared library */ + lib_t openLibrary(const std::string& file) + { + std::string fullName = file+".dll"; + FileName executable = getExecutableFileName(); + HANDLE handle = LoadLibrary((executable.path() + fullName).c_str()); + return lib_t(handle); + } + + /* returns address of a symbol from the library */ + void* getSymbol(lib_t lib, const std::string& sym) { + // -- GODOT start -- + return (void*) GetProcAddress(HMODULE(lib),sym.c_str()); + // -- GODOT end -- + } + + /* closes the shared library */ + void closeLibrary(lib_t lib) { + FreeLibrary(HMODULE(lib)); + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Unix Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__UNIX__) + +#include + +namespace embree +{ + /* opens a shared library */ + lib_t openLibrary(const std::string& file) + { +#if defined(__MACOSX__) + std::string fullName = "lib"+file+".dylib"; +#else + std::string fullName = "lib"+file+".so"; +#endif + void* lib = dlopen(fullName.c_str(), RTLD_NOW); + if (lib) return lib_t(lib); + FileName executable = getExecutableFileName(); + lib = dlopen((executable.path() + fullName).c_str(),RTLD_NOW); + if (lib == nullptr) { + const char* error = dlerror(); + if (error) { + THROW_RUNTIME_ERROR(error); + } else { + THROW_RUNTIME_ERROR("could not load library "+executable.str()); + } + } + return lib_t(lib); + } + + /* returns address of a symbol from the library */ + void* getSymbol(lib_t lib, const std::string& sym) { + return dlsym(lib,sym.c_str()); + } + + /* closes the shared library */ + void closeLibrary(lib_t lib) { + dlclose(lib); + } +} +#endif diff --git a/thirdparty/embree/common/sys/library.h b/thirdparty/embree/common/sys/library.h new file mode 100644 index 000000000000..c2164e9fbe02 --- /dev/null +++ b/thirdparty/embree/common/sys/library.h @@ -0,0 +1,21 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" + +namespace embree +{ + /*! type for shared library */ + typedef struct opaque_lib_t* lib_t; + + /*! loads a shared library */ + lib_t openLibrary(const std::string& file); + + /*! returns address of a symbol from the library */ + void* getSymbol(lib_t lib, const std::string& sym); + + /*! unloads a shared library */ + void closeLibrary(lib_t lib); +} diff --git a/thirdparty/embree/common/sys/mutex.cpp b/thirdparty/embree/common/sys/mutex.cpp new file mode 100644 index 000000000000..57ef360981ac --- /dev/null +++ b/thirdparty/embree/common/sys/mutex.cpp @@ -0,0 +1,57 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "mutex.h" +#include "regression.h" + +#if defined(__WIN32__) && !defined(PTHREADS_WIN32) + +#define WIN32_LEAN_AND_MEAN +#include + +namespace embree +{ + MutexSys::MutexSys() { mutex = new CRITICAL_SECTION; InitializeCriticalSection((CRITICAL_SECTION*)mutex); } + MutexSys::~MutexSys() { DeleteCriticalSection((CRITICAL_SECTION*)mutex); delete (CRITICAL_SECTION*)mutex; } + void MutexSys::lock() { EnterCriticalSection((CRITICAL_SECTION*)mutex); } + bool MutexSys::try_lock() { return TryEnterCriticalSection((CRITICAL_SECTION*)mutex) != 0; } + void MutexSys::unlock() { LeaveCriticalSection((CRITICAL_SECTION*)mutex); } +} +#endif + +#if defined(__UNIX__) || defined(PTHREADS_WIN32) +#include +namespace embree +{ + /*! system mutex using pthreads */ + MutexSys::MutexSys() + { + mutex = new pthread_mutex_t; + if (pthread_mutex_init((pthread_mutex_t*)mutex, nullptr) != 0) + THROW_RUNTIME_ERROR("pthread_mutex_init failed"); + } + + MutexSys::~MutexSys() + { + MAYBE_UNUSED bool ok = pthread_mutex_destroy((pthread_mutex_t*)mutex) == 0; + assert(ok); + delete (pthread_mutex_t*)mutex; + } + + void MutexSys::lock() + { + if (pthread_mutex_lock((pthread_mutex_t*)mutex) != 0) + THROW_RUNTIME_ERROR("pthread_mutex_lock failed"); + } + + bool MutexSys::try_lock() { + return pthread_mutex_trylock((pthread_mutex_t*)mutex) == 0; + } + + void MutexSys::unlock() + { + if (pthread_mutex_unlock((pthread_mutex_t*)mutex) != 0) + THROW_RUNTIME_ERROR("pthread_mutex_unlock failed"); + } +}; +#endif diff --git a/thirdparty/embree/common/sys/mutex.h b/thirdparty/embree/common/sys/mutex.h new file mode 100644 index 000000000000..1164210f23aa --- /dev/null +++ b/thirdparty/embree/common/sys/mutex.h @@ -0,0 +1,98 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" +#include "intrinsics.h" +#include "atomic.h" + +namespace embree +{ + /*! system mutex */ + class MutexSys { + friend struct ConditionImplementation; + public: + MutexSys(); + ~MutexSys(); + + private: + MutexSys (const MutexSys& other) DELETED; // do not implement + MutexSys& operator= (const MutexSys& other) DELETED; // do not implement + + public: + void lock(); + bool try_lock(); + void unlock(); + + protected: + void* mutex; + }; + + /*! spinning mutex */ + class SpinLock + { + public: + + SpinLock () + : flag(false) {} + + __forceinline bool isLocked() { + return flag.load(); + } + + __forceinline void lock() + { + while (true) + { + while (flag.load()) + { + _mm_pause(); + _mm_pause(); + } + + bool expected = false; + if (flag.compare_exchange_strong(expected,true,std::memory_order_acquire)) + break; + } + } + + __forceinline bool try_lock() + { + bool expected = false; + if (flag.load() != expected) { + return false; + } + return flag.compare_exchange_strong(expected,true,std::memory_order_acquire); + } + + __forceinline void unlock() { + flag.store(false,std::memory_order_release); + } + + __forceinline void wait_until_unlocked() + { + while(flag.load()) + { + _mm_pause(); + _mm_pause(); + } + } + + public: + atomic flag; + }; + + /*! safe mutex lock and unlock helper */ + template class Lock { + public: + Lock (Mutex& mutex) : mutex(mutex), locked(true) { mutex.lock(); } + Lock (Mutex& mutex, bool locked) : mutex(mutex), locked(locked) {} + ~Lock() { if (locked) mutex.unlock(); } + __forceinline void lock() { assert(!locked); locked = true; mutex.lock(); } + __forceinline bool isLocked() const { return locked; } + protected: + Mutex& mutex; + bool locked; + }; +} diff --git a/thirdparty/embree/common/sys/platform.h b/thirdparty/embree/common/sys/platform.h new file mode 100644 index 000000000000..08617452f48a --- /dev/null +++ b/thirdparty/embree/common/sys/platform.h @@ -0,0 +1,374 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +/// detect platform +//////////////////////////////////////////////////////////////////////////////// + +/* detect 32 or 64 platform */ +#if defined(__x86_64__) || defined(__ia64__) || defined(_M_X64) +#define __X86_64__ +#endif + +/* detect Linux platform */ +#if defined(linux) || defined(__linux__) || defined(__LINUX__) +# if !defined(__LINUX__) +# define __LINUX__ +# endif +# if !defined(__UNIX__) +# define __UNIX__ +# endif +#endif + +/* detect FreeBSD platform */ +#if defined(__FreeBSD__) || defined(__FREEBSD__) +# if !defined(__FREEBSD__) +# define __FREEBSD__ +# endif +# if !defined(__UNIX__) +# define __UNIX__ +# endif +#endif + +/* detect Windows 95/98/NT/2000/XP/Vista/7/8/10 platform */ +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)) && !defined(__CYGWIN__) +# if !defined(__WIN32__) +# define __WIN32__ +# endif +#endif + +/* detect Cygwin platform */ +#if defined(__CYGWIN__) +# if !defined(__UNIX__) +# define __UNIX__ +# endif +#endif + +/* detect MAC OS X platform */ +#if defined(__APPLE__) || defined(MACOSX) || defined(__MACOSX__) +# if !defined(__MACOSX__) +# define __MACOSX__ +# endif +# if !defined(__UNIX__) +# define __UNIX__ +# endif +#endif + +/* try to detect other Unix systems */ +#if defined(__unix__) || defined (unix) || defined(__unix) || defined(_unix) +# if !defined(__UNIX__) +# define __UNIX__ +# endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Macros +//////////////////////////////////////////////////////////////////////////////// + +#ifdef __WIN32__ +#define dll_export __declspec(dllexport) +#define dll_import __declspec(dllimport) +#else +#define dll_export __attribute__ ((visibility ("default"))) +#define dll_import +#endif + +#ifdef __WIN32__ +#if !defined(__noinline) +#define __noinline __declspec(noinline) +#endif +//#define __forceinline __forceinline +//#define __restrict __restrict +#if defined(__INTEL_COMPILER) +#define __restrict__ __restrict +#else +#define __restrict__ //__restrict // causes issues with MSVC +#endif +#if !defined(__thread) +#define __thread __declspec(thread) +#endif +#if !defined(__aligned) +#define __aligned(...) __declspec(align(__VA_ARGS__)) +#endif +//#define __FUNCTION__ __FUNCTION__ +#define debugbreak() __debugbreak() + +#else +#if !defined(__noinline) +#define __noinline __attribute__((noinline)) +#endif +#if !defined(__forceinline) +#define __forceinline inline __attribute__((always_inline)) +#endif +//#define __restrict __restrict +//#define __thread __thread +#if !defined(__aligned) +#define __aligned(...) __attribute__((aligned(__VA_ARGS__))) +#endif +#if !defined(__FUNCTION__) +#define __FUNCTION__ __PRETTY_FUNCTION__ +#endif +#define debugbreak() asm ("int $3") +#endif + +#if defined(__clang__) || defined(__GNUC__) + #define MAYBE_UNUSED __attribute__((unused)) +#else + #define MAYBE_UNUSED +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1900) // before VS2015 deleted functions are not supported properly + #define DELETED +#else + #define DELETED = delete +#endif + +// -- GODOT start -- +#if !defined(likely) +// -- GODOT end -- +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#define likely(expr) (expr) +#define unlikely(expr) (expr) +#else +#define likely(expr) __builtin_expect((bool)(expr),true ) +#define unlikely(expr) __builtin_expect((bool)(expr),false) +#endif +// -- GODOT start -- +#endif +// -- GODOT end -- + +//////////////////////////////////////////////////////////////////////////////// +/// Error handling and debugging +//////////////////////////////////////////////////////////////////////////////// + +/* debug printing macros */ +#define STRING(x) #x +#define TOSTRING(x) STRING(x) +#define PING embree_cout << __FILE__ << " (" << __LINE__ << "): " << __FUNCTION__ << embree_endl +#define PRINT(x) embree_cout << STRING(x) << " = " << (x) << embree_endl +#define PRINT2(x,y) embree_cout << STRING(x) << " = " << (x) << ", " << STRING(y) << " = " << (y) << embree_endl +#define PRINT3(x,y,z) embree_cout << STRING(x) << " = " << (x) << ", " << STRING(y) << " = " << (y) << ", " << STRING(z) << " = " << (z) << embree_endl +#define PRINT4(x,y,z,w) embree_cout << STRING(x) << " = " << (x) << ", " << STRING(y) << " = " << (y) << ", " << STRING(z) << " = " << (z) << ", " << STRING(w) << " = " << (w) << embree_endl + +#if defined(DEBUG) // only report file and line in debug mode + #define THROW_RUNTIME_ERROR(str) \ + throw std::runtime_error(std::string(__FILE__) + " (" + toString(__LINE__) + "): " + std::string(str)); +#else + #define THROW_RUNTIME_ERROR(str) \ + throw std::runtime_error(str); +#endif + +#define FATAL(x) THROW_RUNTIME_ERROR(x) +#define WARNING(x) { std::cerr << "Warning: " << x << embree_endl << std::flush; } + +#define NOT_IMPLEMENTED FATAL(std::string(__FUNCTION__) + " not implemented") + +//////////////////////////////////////////////////////////////////////////////// +/// Basic types +//////////////////////////////////////////////////////////////////////////////// + +/* default floating-point type */ +namespace embree { + typedef float real; +} + +/* windows does not have ssize_t */ +#if defined(__WIN32__) +#if defined(__X86_64__) +typedef int64_t ssize_t; +#else +typedef int32_t ssize_t; +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Basic utility functions +//////////////////////////////////////////////////////////////////////////////// + +__forceinline std::string toString(long long value) { + return std::to_string(value); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Disable some compiler warnings +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__INTEL_COMPILER) +//#pragma warning(disable:265 ) // floating-point operation result is out of range +//#pragma warning(disable:383 ) // value copied to temporary, reference to temporary used +//#pragma warning(disable:869 ) // parameter was never referenced +//#pragma warning(disable:981 ) // operands are evaluated in unspecified order +//#pragma warning(disable:1418) // external function definition with no prior declaration +//#pragma warning(disable:1419) // external declaration in primary source file +//#pragma warning(disable:1572) // floating-point equality and inequality comparisons are unreliable +//#pragma warning(disable:94 ) // the size of an array must be greater than zero +//#pragma warning(disable:1599) // declaration hides parameter +//#pragma warning(disable:424 ) // extra ";" ignored +#pragma warning(disable:2196) // routine is both "inline" and "noinline" +//#pragma warning(disable:177 ) // label was declared but never referenced +//#pragma warning(disable:114 ) // function was referenced but not defined +//#pragma warning(disable:819 ) // template nesting depth does not match the previous declaration of function +#pragma warning(disable:15335) // was not vectorized: vectorization possible but seems inefficient +#endif + +#if defined(_MSC_VER) +//#pragma warning(disable:4200) // nonstandard extension used : zero-sized array in struct/union +#pragma warning(disable:4800) // forcing value to bool 'true' or 'false' (performance warning) +//#pragma warning(disable:4267) // '=' : conversion from 'size_t' to 'unsigned long', possible loss of data +#pragma warning(disable:4244) // 'argument' : conversion from 'ssize_t' to 'unsigned int', possible loss of data +//#pragma warning(disable:4355) // 'this' : used in base member initializer list +//#pragma warning(disable:391 ) // '<=' : signed / unsigned mismatch +//#pragma warning(disable:4018) // '<' : signed / unsigned mismatch +//#pragma warning(disable:4305) // 'initializing' : truncation from 'double' to 'float' +//#pragma warning(disable:4068) // unknown pragma +//#pragma warning(disable:4146) // unary minus operator applied to unsigned type, result still unsigned +//#pragma warning(disable:4838) // conversion from 'unsigned int' to 'const int' requires a narrowing conversion) +//#pragma warning(disable:4227) // anachronism used : qualifiers on reference are ignored +#pragma warning(disable:4503) // decorated name length exceeded, name was truncated +#pragma warning(disable:4180) // qualifier applied to function type has no meaning; ignored +#pragma warning(disable:4258) // definition from the for loop is ignored; the definition from the enclosing scope is used + +# if _MSC_VER < 1910 // prior to Visual studio 2017 (V141) +# pragma warning(disable:4101) // warning C4101: 'x': unreferenced local variable // a compiler bug issues wrong warnings +# pragma warning(disable:4789) // buffer '' of size 8 bytes will be overrun; 32 bytes will be written starting at offset 0 +# endif + +#endif + +#if defined(__clang__) && !defined(__INTEL_COMPILER) +//#pragma clang diagnostic ignored "-Wunknown-pragmas" +//#pragma clang diagnostic ignored "-Wunused-variable" +//#pragma clang diagnostic ignored "-Wreorder" +//#pragma clang diagnostic ignored "-Wmicrosoft" +//#pragma clang diagnostic ignored "-Wunused-private-field" +//#pragma clang diagnostic ignored "-Wunused-local-typedef" +//#pragma clang diagnostic ignored "-Wunused-function" +//#pragma clang diagnostic ignored "-Wnarrowing" +//#pragma clang diagnostic ignored "-Wc++11-narrowing" +//#pragma clang diagnostic ignored "-Wdeprecated-register" +//#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wpragmas" +//#pragma GCC diagnostic ignored "-Wnarrowing" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +//#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +//#pragma GCC diagnostic ignored "-Warray-bounds" +#pragma GCC diagnostic ignored "-Wattributes" +#pragma GCC diagnostic ignored "-Wmisleading-indentation" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#if defined(__clang__) && defined(__WIN32__) +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wmicrosoft-cast" +#pragma clang diagnostic ignored "-Wmicrosoft-enum-value" +#pragma clang diagnostic ignored "-Wmicrosoft-include" +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#endif + +/* disabling deprecated warning, please use only where use of deprecated Embree API functions is desired */ +#if defined(__WIN32__) && defined(__INTEL_COMPILER) +#define DISABLE_DEPRECATED_WARNING __pragma(warning (disable: 1478)) // warning: function was declared deprecated +#define ENABLE_DEPRECATED_WARNING __pragma(warning (enable: 1478)) // warning: function was declared deprecated +#elif defined(__INTEL_COMPILER) +#define DISABLE_DEPRECATED_WARNING _Pragma("warning (disable: 1478)") // warning: function was declared deprecated +#define ENABLE_DEPRECATED_WARNING _Pragma("warning (enable : 1478)") // warning: function was declared deprecated +#elif defined(__clang__) +#define DISABLE_DEPRECATED_WARNING _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") // warning: xxx is deprecated +#define ENABLE_DEPRECATED_WARNING _Pragma("clang diagnostic warning \"-Wdeprecated-declarations\"") // warning: xxx is deprecated +#elif defined(__GNUC__) +#define DISABLE_DEPRECATED_WARNING _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") // warning: xxx is deprecated +#define ENABLE_DEPRECATED_WARNING _Pragma("GCC diagnostic warning \"-Wdeprecated-declarations\"") // warning: xxx is deprecated +#elif defined(_MSC_VER) +#define DISABLE_DEPRECATED_WARNING __pragma(warning (disable: 4996)) // warning: function was declared deprecated +#define ENABLE_DEPRECATED_WARNING __pragma(warning (enable : 4996)) // warning: function was declared deprecated +#endif + +/* embree output stream */ +#define embree_ostream std::ostream& +#define embree_cout std::cout +#define embree_cout_uniform std::cout +#define embree_endl std::endl + +//////////////////////////////////////////////////////////////////////////////// +/// Some macros for static profiling +//////////////////////////////////////////////////////////////////////////////// + +#if defined (__GNUC__) +#define IACA_SSC_MARK( MARK_ID ) \ +__asm__ __volatile__ ( \ + "\n\t movl $"#MARK_ID", %%ebx" \ + "\n\t .byte 0x64, 0x67, 0x90" \ + : : : "memory" ); + +#define IACA_UD_BYTES __asm__ __volatile__ ("\n\t .byte 0x0F, 0x0B"); + +#else +#define IACA_UD_BYTES {__asm _emit 0x0F \ + __asm _emit 0x0B} + +#define IACA_SSC_MARK(x) {__asm mov ebx, x\ + __asm _emit 0x64 \ + __asm _emit 0x67 \ + __asm _emit 0x90 } + +#define IACA_VC64_START __writegsbyte(111, 111); +#define IACA_VC64_END __writegsbyte(222, 222); + +#endif + +#define IACA_START {IACA_UD_BYTES \ + IACA_SSC_MARK(111)} +#define IACA_END {IACA_SSC_MARK(222) \ + IACA_UD_BYTES} + +namespace embree +{ + template + struct OnScopeExitHelper + { + OnScopeExitHelper (const Closure f) : active(true), f(f) {} + ~OnScopeExitHelper() { if (active) f(); } + void deactivate() { active = false; } + bool active; + const Closure f; + }; + + template + OnScopeExitHelper OnScopeExit(const Closure f) { + return OnScopeExitHelper(f); + } + +#define STRING_JOIN2(arg1, arg2) DO_STRING_JOIN2(arg1, arg2) +#define DO_STRING_JOIN2(arg1, arg2) arg1 ## arg2 +#define ON_SCOPE_EXIT(code) \ + auto STRING_JOIN2(on_scope_exit_, __LINE__) = OnScopeExit([&](){code;}) + + template + std::unique_ptr make_unique(Ty* ptr) { + return std::unique_ptr(ptr); + } + +} diff --git a/thirdparty/embree/common/sys/ref.h b/thirdparty/embree/common/sys/ref.h new file mode 100644 index 000000000000..24648e623444 --- /dev/null +++ b/thirdparty/embree/common/sys/ref.h @@ -0,0 +1,122 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "atomic.h" + +namespace embree +{ + struct NullTy { + }; + + extern MAYBE_UNUSED NullTy null; + + class RefCount + { + public: + RefCount(int val = 0) : refCounter(val) {} + virtual ~RefCount() {}; + + virtual RefCount* refInc() { refCounter.fetch_add(1); return this; } + virtual void refDec() { if (refCounter.fetch_add(-1) == 1) delete this; } + private: + std::atomic refCounter; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// Reference to single object + //////////////////////////////////////////////////////////////////////////////// + + template + class Ref + { + public: + Type* ptr; + + //////////////////////////////////////////////////////////////////////////////// + /// Constructors, Assignment & Cast Operators + //////////////////////////////////////////////////////////////////////////////// + + __forceinline Ref() : ptr(nullptr) {} + __forceinline Ref(NullTy) : ptr(nullptr) {} + __forceinline Ref(const Ref& input) : ptr(input.ptr) { if (ptr) ptr->refInc(); } + __forceinline Ref(Ref&& input) : ptr(input.ptr) { input.ptr = nullptr; } + + __forceinline Ref(Type* const input) : ptr(input) + { + if (ptr) + ptr->refInc(); + } + + __forceinline ~Ref() + { + if (ptr) + ptr->refDec(); + } + + __forceinline Ref& operator =(const Ref& input) + { + if (input.ptr) + input.ptr->refInc(); + if (ptr) + ptr->refDec(); + ptr = input.ptr; + return *this; + } + + __forceinline Ref& operator =(Ref&& input) + { + if (ptr) + ptr->refDec(); + ptr = input.ptr; + input.ptr = nullptr; + return *this; + } + + __forceinline Ref& operator =(Type* const input) + { + if (input) + input->refInc(); + if (ptr) + ptr->refDec(); + ptr = input; + return *this; + } + + __forceinline Ref& operator =(NullTy) + { + if (ptr) + ptr->refDec(); + ptr = nullptr; + return *this; + } + + __forceinline operator bool() const { return ptr != nullptr; } + + __forceinline const Type& operator *() const { return *ptr; } + __forceinline Type& operator *() { return *ptr; } + __forceinline const Type* operator ->() const { return ptr; } + __forceinline Type* operator ->() { return ptr; } + + template + __forceinline Ref cast() { return Ref(static_cast(ptr)); } + template + __forceinline const Ref cast() const { return Ref(static_cast(ptr)); } + + template + __forceinline Ref dynamicCast() { return Ref(dynamic_cast(ptr)); } + template + __forceinline const Ref dynamicCast() const { return Ref(dynamic_cast(ptr)); } + }; + + template __forceinline bool operator < (const Ref& a, const Ref& b) { return a.ptr < b.ptr; } + + template __forceinline bool operator ==(const Ref& a, NullTy ) { return a.ptr == nullptr; } + template __forceinline bool operator ==(NullTy , const Ref& b) { return nullptr == b.ptr; } + template __forceinline bool operator ==(const Ref& a, const Ref& b) { return a.ptr == b.ptr; } + + template __forceinline bool operator !=(const Ref& a, NullTy ) { return a.ptr != nullptr; } + template __forceinline bool operator !=(NullTy , const Ref& b) { return nullptr != b.ptr; } + template __forceinline bool operator !=(const Ref& a, const Ref& b) { return a.ptr != b.ptr; } +} diff --git a/thirdparty/embree/common/sys/regression.cpp b/thirdparty/embree/common/sys/regression.cpp new file mode 100644 index 000000000000..d95ff8dfe067 --- /dev/null +++ b/thirdparty/embree/common/sys/regression.cpp @@ -0,0 +1,30 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "regression.h" + +namespace embree +{ + /* registerRegressionTest is invoked from static initializers, thus + * we cannot have the regression_tests variable as global static + * variable due to issues with static variable initialization + * order. */ + std::vector& get_regression_tests() + { + static std::vector regression_tests; + return regression_tests; + } + + void registerRegressionTest(RegressionTest* test) + { + get_regression_tests().push_back(test); + } + + RegressionTest* getRegressionTest(size_t index) + { + if (index >= get_regression_tests().size()) + return nullptr; + + return get_regression_tests()[index]; + } +} diff --git a/thirdparty/embree/common/sys/regression.h b/thirdparty/embree/common/sys/regression.h new file mode 100644 index 000000000000..632f8d92cf57 --- /dev/null +++ b/thirdparty/embree/common/sys/regression.h @@ -0,0 +1,25 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" + +#include + +namespace embree +{ + /*! virtual interface for all regression tests */ + struct RegressionTest + { + RegressionTest (std::string name) : name(name) {} + virtual bool run() = 0; + std::string name; + }; + + /*! registers a regression test */ + void registerRegressionTest(RegressionTest* test); + + /*! run all regression tests */ + RegressionTest* getRegressionTest(size_t index); +} diff --git a/thirdparty/embree/common/sys/string.cpp b/thirdparty/embree/common/sys/string.cpp new file mode 100644 index 000000000000..931244383e05 --- /dev/null +++ b/thirdparty/embree/common/sys/string.cpp @@ -0,0 +1,42 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "string.h" + +#include +#include + +namespace embree +{ + char to_lower(char c) { return char(tolower(int(c))); } + char to_upper(char c) { return char(toupper(int(c))); } + std::string toLowerCase(const std::string& s) { std::string dst(s); std::transform(dst.begin(), dst.end(), dst.begin(), to_lower); return dst; } + std::string toUpperCase(const std::string& s) { std::string dst(s); std::transform(dst.begin(), dst.end(), dst.begin(), to_upper); return dst; } + + Vec2f string_to_Vec2f ( std::string str ) + { + size_t next = 0; + const float x = std::stof(str,&next); str = str.substr(next+1); + const float y = std::stof(str,&next); + return Vec2f(x,y); + } + + Vec3f string_to_Vec3f ( std::string str ) + { + size_t next = 0; + const float x = std::stof(str,&next); str = str.substr(next+1); + const float y = std::stof(str,&next); str = str.substr(next+1); + const float z = std::stof(str,&next); + return Vec3f(x,y,z); + } + + Vec4f string_to_Vec4f ( std::string str ) + { + size_t next = 0; + const float x = std::stof(str,&next); str = str.substr(next+1); + const float y = std::stof(str,&next); str = str.substr(next+1); + const float z = std::stof(str,&next); str = str.substr(next+1); + const float w = std::stof(str,&next); + return Vec4f(x,y,z,w); + } +} diff --git a/thirdparty/embree/common/sys/string.h b/thirdparty/embree/common/sys/string.h new file mode 100644 index 000000000000..2e9b0f88c325 --- /dev/null +++ b/thirdparty/embree/common/sys/string.h @@ -0,0 +1,37 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" +#include "../math/vec2.h" +#include "../math/vec3.h" +#include "../math/vec4.h" + +namespace embree +{ + class IOStreamStateRestorer + { + public: + IOStreamStateRestorer(std::ostream& iostream) + : iostream(iostream), flags(iostream.flags()), precision(iostream.precision()) { + } + + ~IOStreamStateRestorer() { + iostream.flags(flags); + iostream.precision(precision); + } + + private: + std::ostream& iostream; + std::ios::fmtflags flags; + std::streamsize precision; + }; + + std::string toLowerCase(const std::string& s); + std::string toUpperCase(const std::string& s); + + Vec2f string_to_Vec2f ( std::string str ); + Vec3f string_to_Vec3f ( std::string str ); + Vec4f string_to_Vec4f ( std::string str ); +} diff --git a/thirdparty/embree/common/sys/sysinfo.cpp b/thirdparty/embree/common/sys/sysinfo.cpp new file mode 100644 index 000000000000..eb0a10eafa22 --- /dev/null +++ b/thirdparty/embree/common/sys/sysinfo.cpp @@ -0,0 +1,629 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "sysinfo.h" +#include "intrinsics.h" +#include "string.h" +#include "ref.h" +#if defined(__FREEBSD__) +#include +#include +typedef cpuset_t cpu_set_t; +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// All Platforms +//////////////////////////////////////////////////////////////////////////////// + +namespace embree +{ + NullTy null; + + std::string getPlatformName() + { +#if defined(__LINUX__) && !defined(__X86_64__) + return "Linux (32bit)"; +#elif defined(__LINUX__) && defined(__X86_64__) + return "Linux (64bit)"; +#elif defined(__FREEBSD__) && !defined(__X86_64__) + return "FreeBSD (32bit)"; +#elif defined(__FREEBSD__) && defined(__X86_64__) + return "FreeBSD (64bit)"; +#elif defined(__CYGWIN__) && !defined(__X86_64__) + return "Cygwin (32bit)"; +#elif defined(__CYGWIN__) && defined(__X86_64__) + return "Cygwin (64bit)"; +#elif defined(__WIN32__) && !defined(__X86_64__) + return "Windows (32bit)"; +#elif defined(__WIN32__) && defined(__X86_64__) + return "Windows (64bit)"; +#elif defined(__MACOSX__) && !defined(__X86_64__) + return "Mac OS X (32bit)"; +#elif defined(__MACOSX__) && defined(__X86_64__) + return "Mac OS X (64bit)"; +#elif defined(__UNIX__) && !defined(__X86_64__) + return "Unix (32bit)"; +#elif defined(__UNIX__) && defined(__X86_64__) + return "Unix (64bit)"; +#else + return "Unknown"; +#endif + } + + std::string getCompilerName() + { +#if defined(__INTEL_COMPILER) + int icc_mayor = __INTEL_COMPILER / 100 % 100; + int icc_minor = __INTEL_COMPILER % 100; + std::string version = "Intel Compiler "; + version += toString(icc_mayor); + version += "." + toString(icc_minor); +#if defined(__INTEL_COMPILER_UPDATE) + version += "." + toString(__INTEL_COMPILER_UPDATE); +#endif + return version; +#elif defined(__clang__) + return "CLANG " __clang_version__; +#elif defined (__GNUC__) + return "GCC " __VERSION__; +#elif defined(_MSC_VER) + std::string version = toString(_MSC_FULL_VER); + version.insert(4,"."); + version.insert(9,"."); + version.insert(2,"."); + return "Visual C++ Compiler " + version; +#else + return "Unknown Compiler"; +#endif + } + + std::string getCPUVendor() + { + int cpuinfo[4]; + __cpuid (cpuinfo, 0); + int name[4]; + name[0] = cpuinfo[1]; + name[1] = cpuinfo[3]; + name[2] = cpuinfo[2]; + name[3] = 0; + return (char*)name; + } + + CPU getCPUModel() + { + if (getCPUVendor() != "GenuineIntel") + return CPU::UNKNOWN; + + int out[4]; + __cpuid(out, 0); + if (out[0] < 1) return CPU::UNKNOWN; + __cpuid(out, 1); + + /* please see CPUID documentation for these formulas */ + uint32_t family_ID = (out[0] >> 8) & 0x0F; + uint32_t extended_family_ID = (out[0] >> 20) & 0xFF; + + uint32_t model_ID = (out[0] >> 4) & 0x0F; + uint32_t extended_model_ID = (out[0] >> 16) & 0x0F; + + uint32_t DisplayFamily = family_ID; + if (family_ID == 0x0F) + DisplayFamily += extended_family_ID; + + uint32_t DisplayModel = model_ID; + if (family_ID == 0x06 || family_ID == 0x0F) + DisplayModel += extended_model_ID << 4; + + uint32_t DisplayFamily_DisplayModel = (DisplayFamily << 8) + (DisplayModel << 0); + + // Data from Intel® 64 and IA-32 Architectures, Volume 4, Chapter 2, Table 2-1 (CPUID Signature Values of DisplayFamily_DisplayModel) + if (DisplayFamily_DisplayModel == 0x067D) return CPU::CORE_ICE_LAKE; + if (DisplayFamily_DisplayModel == 0x067E) return CPU::CORE_ICE_LAKE; + if (DisplayFamily_DisplayModel == 0x068C) return CPU::CORE_TIGER_LAKE; + if (DisplayFamily_DisplayModel == 0x06A5) return CPU::CORE_COMET_LAKE; + if (DisplayFamily_DisplayModel == 0x06A6) return CPU::CORE_COMET_LAKE; + if (DisplayFamily_DisplayModel == 0x0666) return CPU::CORE_CANNON_LAKE; + if (DisplayFamily_DisplayModel == 0x068E) return CPU::CORE_KABY_LAKE; + if (DisplayFamily_DisplayModel == 0x069E) return CPU::CORE_KABY_LAKE; + if (DisplayFamily_DisplayModel == 0x066A) return CPU::XEON_ICE_LAKE; + if (DisplayFamily_DisplayModel == 0x066C) return CPU::XEON_ICE_LAKE; + if (DisplayFamily_DisplayModel == 0x0655) return CPU::XEON_SKY_LAKE; + if (DisplayFamily_DisplayModel == 0x064E) return CPU::CORE_SKY_LAKE; + if (DisplayFamily_DisplayModel == 0x065E) return CPU::CORE_SKY_LAKE; + if (DisplayFamily_DisplayModel == 0x0656) return CPU::XEON_BROADWELL; + if (DisplayFamily_DisplayModel == 0x064F) return CPU::XEON_BROADWELL; + if (DisplayFamily_DisplayModel == 0x0647) return CPU::CORE_BROADWELL; + if (DisplayFamily_DisplayModel == 0x063D) return CPU::CORE_BROADWELL; + if (DisplayFamily_DisplayModel == 0x063F) return CPU::XEON_HASWELL; + if (DisplayFamily_DisplayModel == 0x063C) return CPU::CORE_HASWELL; + if (DisplayFamily_DisplayModel == 0x0645) return CPU::CORE_HASWELL; + if (DisplayFamily_DisplayModel == 0x0646) return CPU::CORE_HASWELL; + if (DisplayFamily_DisplayModel == 0x063E) return CPU::XEON_IVY_BRIDGE; + if (DisplayFamily_DisplayModel == 0x063A) return CPU::CORE_IVY_BRIDGE; + if (DisplayFamily_DisplayModel == 0x062D) return CPU::SANDY_BRIDGE; + if (DisplayFamily_DisplayModel == 0x062F) return CPU::SANDY_BRIDGE; + if (DisplayFamily_DisplayModel == 0x062A) return CPU::SANDY_BRIDGE; + if (DisplayFamily_DisplayModel == 0x062E) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x0625) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x062C) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x061E) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x061F) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x061A) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x061D) return CPU::NEHALEM; + if (DisplayFamily_DisplayModel == 0x0617) return CPU::CORE2; + if (DisplayFamily_DisplayModel == 0x060F) return CPU::CORE2; + if (DisplayFamily_DisplayModel == 0x060E) return CPU::CORE1; + + if (DisplayFamily_DisplayModel == 0x0685) return CPU::XEON_PHI_KNIGHTS_MILL; + if (DisplayFamily_DisplayModel == 0x0657) return CPU::XEON_PHI_KNIGHTS_LANDING; + + return CPU::UNKNOWN; + } + + std::string stringOfCPUModel(CPU model) + { + switch (model) { + case CPU::XEON_ICE_LAKE : return "Xeon Ice Lake"; + case CPU::CORE_ICE_LAKE : return "Core Ice Lake"; + case CPU::CORE_TIGER_LAKE : return "Core Tiger Lake"; + case CPU::CORE_COMET_LAKE : return "Core Comet Lake"; + case CPU::CORE_CANNON_LAKE : return "Core Cannon Lake"; + case CPU::CORE_KABY_LAKE : return "Core Kaby Lake"; + case CPU::XEON_SKY_LAKE : return "Xeon Sky Lake"; + case CPU::CORE_SKY_LAKE : return "Core Sky Lake"; + case CPU::XEON_PHI_KNIGHTS_MILL : return "Xeon Phi Knights Mill"; + case CPU::XEON_PHI_KNIGHTS_LANDING: return "Xeon Phi Knights Landing"; + case CPU::XEON_BROADWELL : return "Xeon Broadwell"; + case CPU::CORE_BROADWELL : return "Core Broadwell"; + case CPU::XEON_HASWELL : return "Xeon Haswell"; + case CPU::CORE_HASWELL : return "Core Haswell"; + case CPU::XEON_IVY_BRIDGE : return "Xeon Ivy Bridge"; + case CPU::CORE_IVY_BRIDGE : return "Core Ivy Bridge"; + case CPU::SANDY_BRIDGE : return "Sandy Bridge"; + case CPU::NEHALEM : return "Nehalem"; + case CPU::CORE2 : return "Core2"; + case CPU::CORE1 : return "Core"; + case CPU::UNKNOWN : return "Unknown CPU"; + } + return "Unknown CPU (error)"; + } + + /* constants to access destination registers of CPUID instruction */ + static const int EAX = 0; + static const int EBX = 1; + static const int ECX = 2; + static const int EDX = 3; + + /* cpuid[eax=1].ecx */ + static const int CPU_FEATURE_BIT_SSE3 = 1 << 0; + static const int CPU_FEATURE_BIT_SSSE3 = 1 << 9; + static const int CPU_FEATURE_BIT_FMA3 = 1 << 12; + static const int CPU_FEATURE_BIT_SSE4_1 = 1 << 19; + static const int CPU_FEATURE_BIT_SSE4_2 = 1 << 20; + //static const int CPU_FEATURE_BIT_MOVBE = 1 << 22; + static const int CPU_FEATURE_BIT_POPCNT = 1 << 23; + //static const int CPU_FEATURE_BIT_XSAVE = 1 << 26; + static const int CPU_FEATURE_BIT_OXSAVE = 1 << 27; + static const int CPU_FEATURE_BIT_AVX = 1 << 28; + static const int CPU_FEATURE_BIT_F16C = 1 << 29; + static const int CPU_FEATURE_BIT_RDRAND = 1 << 30; + + /* cpuid[eax=1].edx */ + static const int CPU_FEATURE_BIT_SSE = 1 << 25; + static const int CPU_FEATURE_BIT_SSE2 = 1 << 26; + + /* cpuid[eax=0x80000001].ecx */ + static const int CPU_FEATURE_BIT_LZCNT = 1 << 5; + + /* cpuid[eax=7,ecx=0].ebx */ + static const int CPU_FEATURE_BIT_BMI1 = 1 << 3; + static const int CPU_FEATURE_BIT_AVX2 = 1 << 5; + static const int CPU_FEATURE_BIT_BMI2 = 1 << 8; + static const int CPU_FEATURE_BIT_AVX512F = 1 << 16; // AVX512F (foundation) + static const int CPU_FEATURE_BIT_AVX512DQ = 1 << 17; // AVX512DQ (doubleword and quadword instructions) + static const int CPU_FEATURE_BIT_AVX512PF = 1 << 26; // AVX512PF (prefetch gather/scatter instructions) + static const int CPU_FEATURE_BIT_AVX512ER = 1 << 27; // AVX512ER (exponential and reciprocal instructions) + static const int CPU_FEATURE_BIT_AVX512CD = 1 << 28; // AVX512CD (conflict detection instructions) + static const int CPU_FEATURE_BIT_AVX512BW = 1 << 30; // AVX512BW (byte and word instructions) + static const int CPU_FEATURE_BIT_AVX512VL = 1 << 31; // AVX512VL (vector length extensions) + static const int CPU_FEATURE_BIT_AVX512IFMA = 1 << 21; // AVX512IFMA (integer fused multiple-add instructions) + + /* cpuid[eax=7,ecx=0].ecx */ + static const int CPU_FEATURE_BIT_AVX512VBMI = 1 << 1; // AVX512VBMI (vector bit manipulation instructions) + + __noinline int64_t get_xcr0() + { +#if defined (__WIN32__) + int64_t xcr0 = 0; // int64_t is workaround for compiler bug under VS2013, Win32 + xcr0 = _xgetbv(0); + return xcr0; +#else + int xcr0 = 0; + __asm__ ("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx" ); + return xcr0; +#endif + } + + int getCPUFeatures() + { + /* cache CPU features access */ + static int cpu_features = 0; + if (cpu_features) + return cpu_features; + + /* get number of CPUID leaves */ + int cpuid_leaf0[4]; + __cpuid(cpuid_leaf0, 0x00000000); + unsigned nIds = cpuid_leaf0[EAX]; + + /* get number of extended CPUID leaves */ + int cpuid_leafe[4]; + __cpuid(cpuid_leafe, 0x80000000); + unsigned nExIds = cpuid_leafe[EAX]; + + /* get CPUID leaves for EAX = 1,7, and 0x80000001 */ + int cpuid_leaf_1[4] = { 0,0,0,0 }; + int cpuid_leaf_7[4] = { 0,0,0,0 }; + int cpuid_leaf_e1[4] = { 0,0,0,0 }; + if (nIds >= 1) __cpuid (cpuid_leaf_1,0x00000001); +#if _WIN32 +#if _MSC_VER && (_MSC_FULL_VER < 160040219) +#else + if (nIds >= 7) __cpuidex(cpuid_leaf_7,0x00000007,0); +#endif +#else + if (nIds >= 7) __cpuid_count(cpuid_leaf_7,0x00000007,0); +#endif + if (nExIds >= 0x80000001) __cpuid(cpuid_leaf_e1,0x80000001); + + /* detect if OS saves XMM, YMM, and ZMM states */ + bool xmm_enabled = true; + bool ymm_enabled = false; + bool zmm_enabled = false; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_OXSAVE) { + int64_t xcr0 = get_xcr0(); + xmm_enabled = ((xcr0 & 0x02) == 0x02); /* checks if xmm are enabled in XCR0 */ + ymm_enabled = xmm_enabled && ((xcr0 & 0x04) == 0x04); /* checks if ymm state are enabled in XCR0 */ + zmm_enabled = ymm_enabled && ((xcr0 & 0xE0) == 0xE0); /* checks if OPMASK state, upper 256-bit of ZMM0-ZMM15 and ZMM16-ZMM31 state are enabled in XCR0 */ + } + if (xmm_enabled) cpu_features |= CPU_FEATURE_XMM_ENABLED; + if (ymm_enabled) cpu_features |= CPU_FEATURE_YMM_ENABLED; + if (zmm_enabled) cpu_features |= CPU_FEATURE_ZMM_ENABLED; + + if (cpuid_leaf_1[EDX] & CPU_FEATURE_BIT_SSE ) cpu_features |= CPU_FEATURE_SSE; + if (cpuid_leaf_1[EDX] & CPU_FEATURE_BIT_SSE2 ) cpu_features |= CPU_FEATURE_SSE2; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_SSE3 ) cpu_features |= CPU_FEATURE_SSE3; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_SSSE3 ) cpu_features |= CPU_FEATURE_SSSE3; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_SSE4_1) cpu_features |= CPU_FEATURE_SSE41; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_SSE4_2) cpu_features |= CPU_FEATURE_SSE42; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_POPCNT) cpu_features |= CPU_FEATURE_POPCNT; + + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_AVX ) cpu_features |= CPU_FEATURE_AVX; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_F16C ) cpu_features |= CPU_FEATURE_F16C; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_RDRAND) cpu_features |= CPU_FEATURE_RDRAND; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX2 ) cpu_features |= CPU_FEATURE_AVX2; + if (cpuid_leaf_1[ECX] & CPU_FEATURE_BIT_FMA3 ) cpu_features |= CPU_FEATURE_FMA3; + if (cpuid_leaf_e1[ECX] & CPU_FEATURE_BIT_LZCNT) cpu_features |= CPU_FEATURE_LZCNT; + if (cpuid_leaf_7 [EBX] & CPU_FEATURE_BIT_BMI1 ) cpu_features |= CPU_FEATURE_BMI1; + if (cpuid_leaf_7 [EBX] & CPU_FEATURE_BIT_BMI2 ) cpu_features |= CPU_FEATURE_BMI2; + + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512F ) cpu_features |= CPU_FEATURE_AVX512F; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512DQ ) cpu_features |= CPU_FEATURE_AVX512DQ; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512PF ) cpu_features |= CPU_FEATURE_AVX512PF; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512ER ) cpu_features |= CPU_FEATURE_AVX512ER; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512CD ) cpu_features |= CPU_FEATURE_AVX512CD; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512BW ) cpu_features |= CPU_FEATURE_AVX512BW; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512IFMA) cpu_features |= CPU_FEATURE_AVX512IFMA; + if (cpuid_leaf_7[EBX] & CPU_FEATURE_BIT_AVX512VL ) cpu_features |= CPU_FEATURE_AVX512VL; + if (cpuid_leaf_7[ECX] & CPU_FEATURE_BIT_AVX512VBMI) cpu_features |= CPU_FEATURE_AVX512VBMI; + + return cpu_features; + } + + std::string stringOfCPUFeatures(int features) + { + std::string str; + if (features & CPU_FEATURE_XMM_ENABLED) str += "XMM "; + if (features & CPU_FEATURE_YMM_ENABLED) str += "YMM "; + if (features & CPU_FEATURE_ZMM_ENABLED) str += "ZMM "; + if (features & CPU_FEATURE_SSE ) str += "SSE "; + if (features & CPU_FEATURE_SSE2 ) str += "SSE2 "; + if (features & CPU_FEATURE_SSE3 ) str += "SSE3 "; + if (features & CPU_FEATURE_SSSE3 ) str += "SSSE3 "; + if (features & CPU_FEATURE_SSE41 ) str += "SSE4.1 "; + if (features & CPU_FEATURE_SSE42 ) str += "SSE4.2 "; + if (features & CPU_FEATURE_POPCNT) str += "POPCNT "; + if (features & CPU_FEATURE_AVX ) str += "AVX "; + if (features & CPU_FEATURE_F16C ) str += "F16C "; + if (features & CPU_FEATURE_RDRAND) str += "RDRAND "; + if (features & CPU_FEATURE_AVX2 ) str += "AVX2 "; + if (features & CPU_FEATURE_FMA3 ) str += "FMA3 "; + if (features & CPU_FEATURE_LZCNT ) str += "LZCNT "; + if (features & CPU_FEATURE_BMI1 ) str += "BMI1 "; + if (features & CPU_FEATURE_BMI2 ) str += "BMI2 "; + if (features & CPU_FEATURE_AVX512F) str += "AVX512F "; + if (features & CPU_FEATURE_AVX512DQ) str += "AVX512DQ "; + if (features & CPU_FEATURE_AVX512PF) str += "AVX512PF "; + if (features & CPU_FEATURE_AVX512ER) str += "AVX512ER "; + if (features & CPU_FEATURE_AVX512CD) str += "AVX512CD "; + if (features & CPU_FEATURE_AVX512BW) str += "AVX512BW "; + if (features & CPU_FEATURE_AVX512VL) str += "AVX512VL "; + if (features & CPU_FEATURE_AVX512IFMA) str += "AVX512IFMA "; + if (features & CPU_FEATURE_AVX512VBMI) str += "AVX512VBMI "; + return str; + } + + std::string stringOfISA (int isa) + { + if (isa == SSE) return "SSE"; + if (isa == SSE2) return "SSE2"; + if (isa == SSE3) return "SSE3"; + if (isa == SSSE3) return "SSSE3"; + if (isa == SSE41) return "SSE4.1"; + if (isa == SSE42) return "SSE4.2"; + if (isa == AVX) return "AVX"; + if (isa == AVX2) return "AVX2"; + if (isa == AVX512KNL) return "AVX512KNL"; + if (isa == AVX512SKX) return "AVX512SKX"; + return "UNKNOWN"; + } + + bool hasISA(int features, int isa) { + return (features & isa) == isa; + } + + std::string supportedTargetList (int features) + { + std::string v; + if (hasISA(features,SSE)) v += "SSE "; + if (hasISA(features,SSE2)) v += "SSE2 "; + if (hasISA(features,SSE3)) v += "SSE3 "; + if (hasISA(features,SSSE3)) v += "SSSE3 "; + if (hasISA(features,SSE41)) v += "SSE4.1 "; + if (hasISA(features,SSE42)) v += "SSE4.2 "; + if (hasISA(features,AVX)) v += "AVX "; + if (hasISA(features,AVXI)) v += "AVXI "; + if (hasISA(features,AVX2)) v += "AVX2 "; + if (hasISA(features,AVX512KNL)) v += "AVX512KNL "; + if (hasISA(features,AVX512SKX)) v += "AVX512SKX "; + return v; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Windows Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__WIN32__) + +#define WIN32_LEAN_AND_MEAN +#include +#include + +namespace embree +{ + std::string getExecutableFileName() { + char filename[1024]; + if (!GetModuleFileName(nullptr, filename, sizeof(filename))) + return std::string(); + return std::string(filename); + } + + unsigned int getNumberOfLogicalThreads() + { + static int nThreads = -1; + if (nThreads != -1) return nThreads; + + typedef WORD (WINAPI *GetActiveProcessorGroupCountFunc)(); + typedef DWORD (WINAPI *GetActiveProcessorCountFunc)(WORD); + HMODULE hlib = LoadLibrary("Kernel32"); + GetActiveProcessorGroupCountFunc pGetActiveProcessorGroupCount = (GetActiveProcessorGroupCountFunc)GetProcAddress(hlib, "GetActiveProcessorGroupCount"); + GetActiveProcessorCountFunc pGetActiveProcessorCount = (GetActiveProcessorCountFunc) GetProcAddress(hlib, "GetActiveProcessorCount"); + + if (pGetActiveProcessorGroupCount && pGetActiveProcessorCount) + { + int groups = pGetActiveProcessorGroupCount(); + int totalProcessors = 0; + for (int i = 0; i < groups; i++) + totalProcessors += pGetActiveProcessorCount(i); + nThreads = totalProcessors; + } + else + { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + nThreads = sysinfo.dwNumberOfProcessors; + } + assert(nThreads); + return nThreads; + } + + int getTerminalWidth() + { + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (handle == INVALID_HANDLE_VALUE) return 80; + CONSOLE_SCREEN_BUFFER_INFO info; + memset(&info,0,sizeof(info)); + GetConsoleScreenBufferInfo(handle, &info); + return info.dwSize.X; + } + + double getSeconds() + { + LARGE_INTEGER freq, val; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&val); + return (double)val.QuadPart / (double)freq.QuadPart; + } + + void sleepSeconds(double t) { + Sleep(DWORD(1000.0*t)); + } + + size_t getVirtualMemoryBytes() + { + PROCESS_MEMORY_COUNTERS info; + GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) ); + return (size_t)info.QuotaPeakPagedPoolUsage; + } + + size_t getResidentMemoryBytes() + { + PROCESS_MEMORY_COUNTERS info; + GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) ); + return (size_t)info.WorkingSetSize; + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Linux Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__LINUX__) + +#include +#include + +namespace embree +{ + std::string getExecutableFileName() + { + std::string pid = "/proc/" + toString(getpid()) + "/exe"; + char buf[4096]; + memset(buf,0,sizeof(buf)); + if (readlink(pid.c_str(), buf, sizeof(buf)-1) == -1) + return std::string(); + return std::string(buf); + } + + size_t getVirtualMemoryBytes() + { + size_t virt, resident, shared; + std::ifstream buffer("/proc/self/statm"); + buffer >> virt >> resident >> shared; + return virt*sysconf(_SC_PAGE_SIZE); + } + + size_t getResidentMemoryBytes() + { + size_t virt, resident, shared; + std::ifstream buffer("/proc/self/statm"); + buffer >> virt >> resident >> shared; + return resident*sysconf(_SC_PAGE_SIZE); + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// FreeBSD Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined (__FreeBSD__) + +#include + +namespace embree +{ + std::string getExecutableFileName() + { + const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + char buf[4096]; + memset(buf,0,sizeof(buf)); + size_t len = sizeof(buf)-1; + if (sysctl(mib, 4, buf, &len, 0x0, 0) == -1) + return std::string(); + return std::string(buf); + } + + size_t getVirtualMemoryBytes() { + return 0; + } + + size_t getResidentMemoryBytes() { + return 0; + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Mac OS X Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__MACOSX__) + +#include + +namespace embree +{ + std::string getExecutableFileName() + { + char buf[4096]; + uint32_t size = sizeof(buf); + if (_NSGetExecutablePath(buf, &size) != 0) + return std::string(); + return std::string(buf); + } + + size_t getVirtualMemoryBytes() { + return 0; + } + + size_t getResidentMemoryBytes() { + return 0; + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Unix Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__UNIX__) + +#include +#include +#include +#include + +namespace embree +{ + unsigned int getNumberOfLogicalThreads() + { + static int nThreads = -1; + if (nThreads != -1) return nThreads; + +#if defined(__MACOSX__) + nThreads = sysconf(_SC_NPROCESSORS_ONLN); // does not work in Linux LXC container + assert(nThreads); +#else + cpu_set_t set; + if (pthread_getaffinity_np(pthread_self(), sizeof(set), &set) == 0) + nThreads = CPU_COUNT(&set); +#endif + + assert(nThreads); + return nThreads; + } + + int getTerminalWidth() + { + struct winsize info; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &info) < 0) return 80; + return info.ws_col; + } + + double getSeconds() { + struct timeval tp; gettimeofday(&tp,nullptr); + return double(tp.tv_sec) + double(tp.tv_usec)/1E6; + } + + void sleepSeconds(double t) { + usleep(1000000.0*t); + } +} +#endif + diff --git a/thirdparty/embree/common/sys/sysinfo.h b/thirdparty/embree/common/sys/sysinfo.h new file mode 100644 index 000000000000..cee1017ddecf --- /dev/null +++ b/thirdparty/embree/common/sys/sysinfo.h @@ -0,0 +1,182 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define CACHELINE_SIZE 64 + +#if !defined(PAGE_SIZE) + #define PAGE_SIZE 4096 +#endif + +#define PAGE_SIZE_2M (2*1024*1024) +#define PAGE_SIZE_4K (4*1024) + +#include "platform.h" + +/* define isa namespace and ISA bitvector */ +#if defined (__AVX512VL__) +# define isa avx512skx +# define ISA AVX512SKX +# define ISA_STR "AVX512SKX" +#elif defined (__AVX512F__) +# define isa avx512knl +# define ISA AVX512KNL +# define ISA_STR "AVX512KNL" +#elif defined (__AVX2__) +# define isa avx2 +# define ISA AVX2 +# define ISA_STR "AVX2" +#elif defined(__AVXI__) +# define isa avxi +# define ISA AVXI +# define ISA_STR "AVXI" +#elif defined(__AVX__) +# define isa avx +# define ISA AVX +# define ISA_STR "AVX" +#elif defined (__SSE4_2__) +# define isa sse42 +# define ISA SSE42 +# define ISA_STR "SSE4.2" +//#elif defined (__SSE4_1__) // we demote this to SSE2, MacOSX code compiles with SSE41 by default with XCode 11 +//# define isa sse41 +//# define ISA SSE41 +//# define ISA_STR "SSE4.1" +//#elif defined(__SSSE3__) // we demote this to SSE2, MacOSX code compiles with SSSE3 by default with ICC +//# define isa ssse3 +//# define ISA SSSE3 +//# define ISA_STR "SSSE3" +//#elif defined(__SSE3__) // we demote this to SSE2, MacOSX code compiles with SSE3 by default with clang +//# define isa sse3 +//# define ISA SSE3 +//# define ISA_STR "SSE3" +#elif defined(__SSE2__) || defined(__SSE3__) || defined(__SSSE3__) +# define isa sse2 +# define ISA SSE2 +# define ISA_STR "SSE2" +#elif defined(__SSE__) +# define isa sse +# define ISA SSE +# define ISA_STR "SSE" +#else +#error Unknown ISA +#endif + +namespace embree +{ + enum class CPU + { + XEON_ICE_LAKE, + CORE_ICE_LAKE, + CORE_TIGER_LAKE, + CORE_COMET_LAKE, + CORE_CANNON_LAKE, + CORE_KABY_LAKE, + XEON_SKY_LAKE, + CORE_SKY_LAKE, + XEON_PHI_KNIGHTS_MILL, + XEON_PHI_KNIGHTS_LANDING, + XEON_BROADWELL, + CORE_BROADWELL, + XEON_HASWELL, + CORE_HASWELL, + XEON_IVY_BRIDGE, + CORE_IVY_BRIDGE, + SANDY_BRIDGE, + NEHALEM, + CORE2, + CORE1, + UNKNOWN, + }; + + /*! get the full path to the running executable */ + std::string getExecutableFileName(); + + /*! return platform name */ + std::string getPlatformName(); + + /*! get the full name of the compiler */ + std::string getCompilerName(); + + /*! return the name of the CPU */ + std::string getCPUVendor(); + + /*! get microprocessor model */ + CPU getCPUModel(); + + /*! converts CPU model into string */ + std::string stringOfCPUModel(CPU model); + + /*! CPU features */ + static const int CPU_FEATURE_SSE = 1 << 0; + static const int CPU_FEATURE_SSE2 = 1 << 1; + static const int CPU_FEATURE_SSE3 = 1 << 2; + static const int CPU_FEATURE_SSSE3 = 1 << 3; + static const int CPU_FEATURE_SSE41 = 1 << 4; + static const int CPU_FEATURE_SSE42 = 1 << 5; + static const int CPU_FEATURE_POPCNT = 1 << 6; + static const int CPU_FEATURE_AVX = 1 << 7; + static const int CPU_FEATURE_F16C = 1 << 8; + static const int CPU_FEATURE_RDRAND = 1 << 9; + static const int CPU_FEATURE_AVX2 = 1 << 10; + static const int CPU_FEATURE_FMA3 = 1 << 11; + static const int CPU_FEATURE_LZCNT = 1 << 12; + static const int CPU_FEATURE_BMI1 = 1 << 13; + static const int CPU_FEATURE_BMI2 = 1 << 14; + static const int CPU_FEATURE_AVX512F = 1 << 16; + static const int CPU_FEATURE_AVX512DQ = 1 << 17; + static const int CPU_FEATURE_AVX512PF = 1 << 18; + static const int CPU_FEATURE_AVX512ER = 1 << 19; + static const int CPU_FEATURE_AVX512CD = 1 << 20; + static const int CPU_FEATURE_AVX512BW = 1 << 21; + static const int CPU_FEATURE_AVX512VL = 1 << 22; + static const int CPU_FEATURE_AVX512IFMA = 1 << 23; + static const int CPU_FEATURE_AVX512VBMI = 1 << 24; + static const int CPU_FEATURE_XMM_ENABLED = 1 << 25; + static const int CPU_FEATURE_YMM_ENABLED = 1 << 26; + static const int CPU_FEATURE_ZMM_ENABLED = 1 << 27; + + /*! get CPU features */ + int getCPUFeatures(); + + /*! convert CPU features into a string */ + std::string stringOfCPUFeatures(int features); + + /*! creates a string of all supported targets that are supported */ + std::string supportedTargetList (int isa); + + /*! ISAs */ + static const int SSE = CPU_FEATURE_SSE | CPU_FEATURE_XMM_ENABLED; + static const int SSE2 = SSE | CPU_FEATURE_SSE2; + static const int SSE3 = SSE2 | CPU_FEATURE_SSE3; + static const int SSSE3 = SSE3 | CPU_FEATURE_SSSE3; + static const int SSE41 = SSSE3 | CPU_FEATURE_SSE41; + static const int SSE42 = SSE41 | CPU_FEATURE_SSE42 | CPU_FEATURE_POPCNT; + static const int AVX = SSE42 | CPU_FEATURE_AVX | CPU_FEATURE_YMM_ENABLED; + static const int AVXI = AVX | CPU_FEATURE_F16C | CPU_FEATURE_RDRAND; + static const int AVX2 = AVXI | CPU_FEATURE_AVX2 | CPU_FEATURE_FMA3 | CPU_FEATURE_BMI1 | CPU_FEATURE_BMI2 | CPU_FEATURE_LZCNT; + static const int AVX512KNL = AVX2 | CPU_FEATURE_AVX512F | CPU_FEATURE_AVX512PF | CPU_FEATURE_AVX512ER | CPU_FEATURE_AVX512CD | CPU_FEATURE_ZMM_ENABLED; + static const int AVX512SKX = AVX2 | CPU_FEATURE_AVX512F | CPU_FEATURE_AVX512DQ | CPU_FEATURE_AVX512CD | CPU_FEATURE_AVX512BW | CPU_FEATURE_AVX512VL | CPU_FEATURE_ZMM_ENABLED; + + /*! converts ISA bitvector into a string */ + std::string stringOfISA(int features); + + /*! return the number of logical threads of the system */ + unsigned int getNumberOfLogicalThreads(); + + /*! returns the size of the terminal window in characters */ + int getTerminalWidth(); + + /*! returns performance counter in seconds */ + double getSeconds(); + + /*! sleeps the specified number of seconds */ + void sleepSeconds(double t); + + /*! returns virtual address space occupied by process */ + size_t getVirtualMemoryBytes(); + + /*! returns resident memory required by process */ + size_t getResidentMemoryBytes(); +} diff --git a/thirdparty/embree/common/sys/thread.cpp b/thirdparty/embree/common/sys/thread.cpp new file mode 100644 index 000000000000..4d86853c47c2 --- /dev/null +++ b/thirdparty/embree/common/sys/thread.cpp @@ -0,0 +1,425 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "thread.h" +#include "sysinfo.h" +#include "string.h" + +#include +#include + +#if defined(PTHREADS_WIN32) +#pragma comment (lib, "pthreadVC.lib") +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Windows Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__WIN32__) + +#define WIN32_LEAN_AND_MEAN +#include + +namespace embree +{ + /*! set the affinity of a given thread */ + void setAffinity(HANDLE thread, ssize_t affinity) + { + typedef WORD (WINAPI *GetActiveProcessorGroupCountFunc)(); + typedef DWORD (WINAPI *GetActiveProcessorCountFunc)(WORD); + typedef BOOL (WINAPI *SetThreadGroupAffinityFunc)(HANDLE, const GROUP_AFFINITY *, PGROUP_AFFINITY); + typedef BOOL (WINAPI *SetThreadIdealProcessorExFunc)(HANDLE, PPROCESSOR_NUMBER, PPROCESSOR_NUMBER); + HMODULE hlib = LoadLibrary("Kernel32"); + GetActiveProcessorGroupCountFunc pGetActiveProcessorGroupCount = (GetActiveProcessorGroupCountFunc)GetProcAddress(hlib, "GetActiveProcessorGroupCount"); + GetActiveProcessorCountFunc pGetActiveProcessorCount = (GetActiveProcessorCountFunc)GetProcAddress(hlib, "GetActiveProcessorCount"); + SetThreadGroupAffinityFunc pSetThreadGroupAffinity = (SetThreadGroupAffinityFunc)GetProcAddress(hlib, "SetThreadGroupAffinity"); + SetThreadIdealProcessorExFunc pSetThreadIdealProcessorEx = (SetThreadIdealProcessorExFunc)GetProcAddress(hlib, "SetThreadIdealProcessorEx"); + if (pGetActiveProcessorGroupCount && pGetActiveProcessorCount && pSetThreadGroupAffinity && pSetThreadIdealProcessorEx) + { + int groups = pGetActiveProcessorGroupCount(); + int totalProcessors = 0, group = 0, number = 0; + for (int i = 0; i affinity) { + group = i; + number = (int)affinity - totalProcessors; + break; + } + totalProcessors += processors; + } + + GROUP_AFFINITY groupAffinity; + groupAffinity.Group = (WORD)group; + groupAffinity.Mask = (KAFFINITY)(uint64_t(1) << number); + groupAffinity.Reserved[0] = 0; + groupAffinity.Reserved[1] = 0; + groupAffinity.Reserved[2] = 0; + if (!pSetThreadGroupAffinity(thread, &groupAffinity, nullptr)) + WARNING("SetThreadGroupAffinity failed"); // on purpose only a warning + + PROCESSOR_NUMBER processorNumber; + processorNumber.Group = group; + processorNumber.Number = number; + processorNumber.Reserved = 0; + if (!pSetThreadIdealProcessorEx(thread, &processorNumber, nullptr)) + WARNING("SetThreadIdealProcessorEx failed"); // on purpose only a warning + } + else + { + if (!SetThreadAffinityMask(thread, DWORD_PTR(uint64_t(1) << affinity))) + WARNING("SetThreadAffinityMask failed"); // on purpose only a warning + if (SetThreadIdealProcessor(thread, (DWORD)affinity) == (DWORD)-1) + WARNING("SetThreadIdealProcessor failed"); // on purpose only a warning + } + } + + /*! set affinity of the calling thread */ + void setAffinity(ssize_t affinity) { + setAffinity(GetCurrentThread(), affinity); + } + + struct ThreadStartupData + { + public: + ThreadStartupData (thread_func f, void* arg) + : f(f), arg(arg) {} + public: + thread_func f; + void* arg; + }; + + DWORD WINAPI threadStartup(LPVOID ptr) + { + ThreadStartupData* parg = (ThreadStartupData*) ptr; + _mm_setcsr(_mm_getcsr() | /*FTZ:*/ (1<<15) | /*DAZ:*/ (1<<6)); + parg->f(parg->arg); + delete parg; + return 0; + } + +#if !defined(PTHREADS_WIN32) + + /*! creates a hardware thread running on specific core */ + thread_t createThread(thread_func f, void* arg, size_t stack_size, ssize_t threadID) + { + HANDLE thread = CreateThread(nullptr, stack_size, threadStartup, new ThreadStartupData(f,arg), 0, nullptr); + if (thread == nullptr) FATAL("CreateThread failed"); + if (threadID >= 0) setAffinity(thread, threadID); + return thread_t(thread); + } + + /*! the thread calling this function gets yielded */ + void yield() { + SwitchToThread(); + } + + /*! waits until the given thread has terminated */ + void join(thread_t tid) { + WaitForSingleObject(HANDLE(tid), INFINITE); + CloseHandle(HANDLE(tid)); + } + + /*! destroy a hardware thread by its handle */ + void destroyThread(thread_t tid) { + TerminateThread(HANDLE(tid),0); + CloseHandle(HANDLE(tid)); + } + + /*! creates thread local storage */ + tls_t createTls() { + return tls_t(size_t(TlsAlloc())); + } + + /*! set the thread local storage pointer */ + void setTls(tls_t tls, void* const ptr) { + TlsSetValue(DWORD(size_t(tls)), ptr); + } + + /*! return the thread local storage pointer */ + void* getTls(tls_t tls) { + return TlsGetValue(DWORD(size_t(tls))); + } + + /*! destroys thread local storage identifier */ + void destroyTls(tls_t tls) { + TlsFree(DWORD(size_t(tls))); + } +#endif +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Linux Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__LINUX__) + +#include +#include +#include + +namespace embree +{ + static MutexSys mutex; + static std::vector threadIDs; + + /* changes thread ID mapping such that we first fill up all thread on one core */ + size_t mapThreadID(size_t threadID) + { + Lock lock(mutex); + + if (threadIDs.size() == 0) + { + /* parse thread/CPU topology */ + for (size_t cpuID=0;;cpuID++) + { + std::fstream fs; + std::string cpu = std::string("/sys/devices/system/cpu/cpu") + std::to_string((long long)cpuID) + std::string("/topology/thread_siblings_list"); + fs.open (cpu.c_str(), std::fstream::in); + if (fs.fail()) break; + + int i; + while (fs >> i) + { + if (std::none_of(threadIDs.begin(),threadIDs.end(),[&] (int id) { return id == i; })) + threadIDs.push_back(i); + if (fs.peek() == ',') + fs.ignore(); + } + fs.close(); + } + +#if 0 + for (size_t i=0;i " << threadIDs[i] << std::endl; +#endif + + /* verify the mapping and do not use it if the mapping has errors */ + for (size_t i=0;i + +namespace embree +{ + /*! set affinity of the calling thread */ + void setAffinity(ssize_t affinity) + { + cpuset_t cset; + CPU_ZERO(&cset); + CPU_SET(affinity, &cset); + + pthread_setaffinity_np(pthread_self(), sizeof(cset), &cset); + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// MacOSX Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__MACOSX__) + +#include +#include +#include + +namespace embree +{ + /*! set affinity of the calling thread */ + void setAffinity(ssize_t affinity) + { + thread_affinity_policy ap; + ap.affinity_tag = affinity; + if (thread_policy_set(mach_thread_self(),THREAD_AFFINITY_POLICY,(thread_policy_t)&ap,THREAD_AFFINITY_POLICY_COUNT) != KERN_SUCCESS) + WARNING("setting thread affinity failed"); // on purpose only a warning + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +/// Unix Platform +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__UNIX__) || defined(PTHREADS_WIN32) + +#include +#include + +#if defined(__USE_NUMA__) +#include +#endif + +namespace embree +{ + struct ThreadStartupData + { + public: + ThreadStartupData (thread_func f, void* arg, int affinity) + : f(f), arg(arg), affinity(affinity) {} + public: + thread_func f; + void* arg; + ssize_t affinity; + }; + + static void* threadStartup(ThreadStartupData* parg) + { + _mm_setcsr(_mm_getcsr() | /*FTZ:*/ (1<<15) | /*DAZ:*/ (1<<6)); + + /*! Mac OS X does not support setting affinity at thread creation time */ +#if defined(__MACOSX__) + if (parg->affinity >= 0) + setAffinity(parg->affinity); +#endif + + parg->f(parg->arg); + delete parg; + return nullptr; + } + + /*! creates a hardware thread running on specific core */ + thread_t createThread(thread_func f, void* arg, size_t stack_size, ssize_t threadID) + { + /* set stack size */ + pthread_attr_t attr; + pthread_attr_init(&attr); + if (stack_size > 0) pthread_attr_setstacksize (&attr, stack_size); + + /* create thread */ + pthread_t* tid = new pthread_t; + if (pthread_create(tid,&attr,(void*(*)(void*))threadStartup,new ThreadStartupData(f,arg,threadID)) != 0) { + pthread_attr_destroy(&attr); + delete tid; + FATAL("pthread_create failed"); + } + pthread_attr_destroy(&attr); + + /* set affinity */ +#if defined(__LINUX__) + if (threadID >= 0) { + cpu_set_t cset; + CPU_ZERO(&cset); + threadID = mapThreadID(threadID); + CPU_SET(threadID, &cset); + pthread_setaffinity_np(*tid, sizeof(cset), &cset); + } +#elif defined(__FreeBSD__) + if (threadID >= 0) { + cpuset_t cset; + CPU_ZERO(&cset); + CPU_SET(threadID, &cset); + pthread_setaffinity_np(*tid, sizeof(cset), &cset); + } +#endif + + return thread_t(tid); + } + + /*! the thread calling this function gets yielded */ + void yield() { + sched_yield(); + } + + /*! waits until the given thread has terminated */ + void join(thread_t tid) { + if (pthread_join(*(pthread_t*)tid, nullptr) != 0) + FATAL("pthread_join failed"); + delete (pthread_t*)tid; + } + + /*! destroy a hardware thread by its handle */ + void destroyThread(thread_t tid) { + pthread_cancel(*(pthread_t*)tid); + delete (pthread_t*)tid; + } + + /*! creates thread local storage */ + tls_t createTls() + { + pthread_key_t* key = new pthread_key_t; + if (pthread_key_create(key,nullptr) != 0) { + delete key; + FATAL("pthread_key_create failed"); + } + + return tls_t(key); + } + + /*! return the thread local storage pointer */ + void* getTls(tls_t tls) + { + assert(tls); + return pthread_getspecific(*(pthread_key_t*)tls); + } + + /*! set the thread local storage pointer */ + void setTls(tls_t tls, void* const ptr) + { + assert(tls); + if (pthread_setspecific(*(pthread_key_t*)tls, ptr) != 0) + FATAL("pthread_setspecific failed"); + } + + /*! destroys thread local storage identifier */ + void destroyTls(tls_t tls) + { + assert(tls); + if (pthread_key_delete(*(pthread_key_t*)tls) != 0) + FATAL("pthread_key_delete failed"); + delete (pthread_key_t*)tls; + } +} + +#endif diff --git a/thirdparty/embree/common/sys/thread.h b/thirdparty/embree/common/sys/thread.h new file mode 100644 index 000000000000..5261a985eefa --- /dev/null +++ b/thirdparty/embree/common/sys/thread.h @@ -0,0 +1,49 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "platform.h" +#include "mutex.h" +#include "alloc.h" +#include "vector.h" +#include + +namespace embree +{ + /*! type for thread */ + typedef struct opaque_thread_t* thread_t; + + /*! signature of thread start function */ + typedef void (*thread_func)(void*); + + /*! creates a hardware thread running on specific logical thread */ + thread_t createThread(thread_func f, void* arg, size_t stack_size = 0, ssize_t threadID = -1); + + /*! set affinity of the calling thread */ + void setAffinity(ssize_t affinity); + + /*! the thread calling this function gets yielded */ + void yield(); + + /*! waits until the given thread has terminated */ + void join(thread_t tid); + + /*! destroy handle of a thread */ + void destroyThread(thread_t tid); + + /*! type for handle to thread local storage */ + typedef struct opaque_tls_t* tls_t; + + /*! creates thread local storage */ + tls_t createTls(); + + /*! set the thread local storage pointer */ + void setTls(tls_t tls, void* const ptr); + + /*! return the thread local storage pointer */ + void* getTls(tls_t tls); + + /*! destroys thread local storage identifier */ + void destroyTls(tls_t tls); +} diff --git a/thirdparty/embree/common/sys/vector.h b/thirdparty/embree/common/sys/vector.h new file mode 100644 index 000000000000..e41794de7c53 --- /dev/null +++ b/thirdparty/embree/common/sys/vector.h @@ -0,0 +1,242 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "alloc.h" +#include + +namespace embree +{ + template + class vector_t + { + public: + typedef T value_type; + typedef T* iterator; + typedef const T* const_iterator; + + __forceinline vector_t () + : size_active(0), size_alloced(0), items(nullptr) {} + + __forceinline explicit vector_t (size_t sz) + : size_active(0), size_alloced(0), items(nullptr) { internal_resize_init(sz); } + + template + __forceinline explicit vector_t (M alloc, size_t sz) + : alloc(alloc), size_active(0), size_alloced(0), items(nullptr) { internal_resize_init(sz); } + + __forceinline ~vector_t() { + clear(); + } + + __forceinline vector_t (const vector_t& other) + { + size_active = other.size_active; + size_alloced = other.size_alloced; + items = alloc.allocate(size_alloced); + for (size_t i=0; i 0); return items[0]; }; + __forceinline T& back () const { assert(size_active > 0); return items[size_active-1]; }; + + __forceinline T* data() { return items; }; + __forceinline const T* data() const { return items; }; + + + /******************** Modifiers **************************/ + + __forceinline void push_back(const T& nt) + { + const T v = nt; // need local copy as input reference could point to this vector + internal_resize(size_active,internal_grow_size(size_active+1)); + ::new (&items[size_active++]) T(v); + } + + __forceinline void pop_back() + { + assert(!empty()); + size_active--; + alloc.destroy(&items[size_active]); + } + + __forceinline void clear() + { + /* destroy elements */ + for (size_t i=0; i + using vector = vector_t>; + + /*! vector class that performs aligned allocations */ + template + using avector = vector_t::value> >; + + /*! vector class that performs OS allocations */ + template + using ovector = vector_t >; +} diff --git a/thirdparty/embree/common/tasking/taskscheduler.h b/thirdparty/embree/common/tasking/taskscheduler.h new file mode 100644 index 000000000000..298d09255b2c --- /dev/null +++ b/thirdparty/embree/common/tasking/taskscheduler.h @@ -0,0 +1,15 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#if defined(TASKING_INTERNAL) +# include "taskschedulerinternal.h" +#elif defined(TASKING_TBB) +# include "taskschedulertbb.h" +#elif defined(TASKING_PPL) +# include "taskschedulerppl.h" +#else +# error "no tasking system enabled" +#endif + diff --git a/thirdparty/embree/common/tasking/taskschedulerinternal.cpp b/thirdparty/embree/common/tasking/taskschedulerinternal.cpp new file mode 100644 index 000000000000..2152e92f4462 --- /dev/null +++ b/thirdparty/embree/common/tasking/taskschedulerinternal.cpp @@ -0,0 +1,408 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "taskschedulerinternal.h" +#include "../math/math.h" +#include "../sys/sysinfo.h" +#include + +namespace embree +{ + RTC_NAMESPACE_BEGIN + + static MutexSys g_mutex; + size_t TaskScheduler::g_numThreads = 0; + __thread TaskScheduler* TaskScheduler::g_instance = nullptr; + std::vector> g_instance_vector; + __thread TaskScheduler::Thread* TaskScheduler::thread_local_thread = nullptr; + TaskScheduler::ThreadPool* TaskScheduler::threadPool = nullptr; + + template + __forceinline void TaskScheduler::steal_loop(Thread& thread, const Predicate& pred, const Body& body) + { + while (true) + { + /*! some rounds that yield */ + for (size_t i=0; i<32; i++) + { + /*! some spinning rounds */ + const size_t threadCount = thread.threadCount(); + for (size_t j=0; j<1024; j+=threadCount) + { + if (!pred()) return; + if (thread.scheduler->steal_from_other_threads(thread)) { + i=j=0; + body(); + } + } + yield(); + } + } + } + + /*! run this task */ + void TaskScheduler::Task::run_internal (Thread& thread) // FIXME: avoid as many dll_exports as possible + { + /* try to run if not already stolen */ + if (try_switch_state(INITIALIZED,DONE)) + { + Task* prevTask = thread.task; + thread.task = this; + try { + if (thread.scheduler->cancellingException == nullptr) + closure->execute(); + } catch (...) { + if (thread.scheduler->cancellingException == nullptr) + thread.scheduler->cancellingException = std::current_exception(); + } + thread.task = prevTask; + add_dependencies(-1); + } + + /* steal until all dependencies have completed */ + steal_loop(thread, + [&] () { return dependencies>0; }, + [&] () { while (thread.tasks.execute_local_internal(thread,this)); }); + + /* now signal our parent task that we are finished */ + if (parent) + parent->add_dependencies(-1); + } + + /*! run this task */ + dll_export void TaskScheduler::Task::run (Thread& thread) { + run_internal(thread); + } + + bool TaskScheduler::TaskQueue::execute_local_internal(Thread& thread, Task* parent) + { + /* stop if we run out of local tasks or reach the waiting task */ + if (right == 0 || &tasks[right-1] == parent) + return false; + + /* execute task */ + size_t oldRight = right; + tasks[right-1].run_internal(thread); + if (right != oldRight) { + THROW_RUNTIME_ERROR("you have to wait for spawned subtasks"); + } + + /* pop task and closure from stack */ + right--; + if (tasks[right].stackPtr != size_t(-1)) + stackPtr = tasks[right].stackPtr; + + /* also move left pointer */ + if (left >= right) left.store(right.load()); + + return right != 0; + } + + dll_export bool TaskScheduler::TaskQueue::execute_local(Thread& thread, Task* parent) { + return execute_local_internal(thread,parent); + } + + bool TaskScheduler::TaskQueue::steal(Thread& thread) + { + size_t l = left; + size_t r = right; + if (l < r) + { + l = left++; + if (l >= r) + return false; + } + else + return false; + + if (!tasks[l].try_steal(thread.tasks.tasks[thread.tasks.right])) + return false; + + thread.tasks.right++; + return true; + } + + /* we steal from the left */ + size_t TaskScheduler::TaskQueue::getTaskSizeAtLeft() + { + if (left >= right) return 0; + return tasks[left].N; + } + + void threadPoolFunction(std::pair* pair) + { + TaskScheduler::ThreadPool* pool = pair->first; + size_t threadIndex = pair->second; + delete pair; + pool->thread_loop(threadIndex); + } + + TaskScheduler::ThreadPool::ThreadPool(bool set_affinity) + : numThreads(0), numThreadsRunning(0), set_affinity(set_affinity), running(false) {} + + dll_export void TaskScheduler::ThreadPool::startThreads() + { + if (running) return; + setNumThreads(numThreads,true); + } + + void TaskScheduler::ThreadPool::setNumThreads(size_t newNumThreads, bool startThreads) + { + Lock lock(g_mutex); + assert(newNumThreads); + newNumThreads = min(newNumThreads, (size_t) getNumberOfLogicalThreads()); + + numThreads = newNumThreads; + if (!startThreads && !running) return; + running = true; + size_t numThreadsActive = numThreadsRunning; + + mutex.lock(); + numThreadsRunning = newNumThreads; + mutex.unlock(); + condition.notify_all(); + + /* start new threads */ + for (size_t t=numThreadsActive; t(this,t); + threads.push_back(createThread((thread_func)threadPoolFunction,pair,4*1024*1024,set_affinity ? t : -1)); + } + + /* stop some threads if we reduce the number of threads */ + for (ssize_t t=numThreadsActive-1; t>=ssize_t(numThreadsRunning); t--) { + if (t == 0) continue; + embree::join(threads.back()); + threads.pop_back(); + } + } + + TaskScheduler::ThreadPool::~ThreadPool() + { + /* leave all taskschedulers */ + mutex.lock(); + numThreadsRunning = 0; + mutex.unlock(); + condition.notify_all(); + + /* wait for threads to terminate */ + for (size_t i=0; i& scheduler) + { + mutex.lock(); + schedulers.push_back(scheduler); + mutex.unlock(); + condition.notify_all(); + } + + dll_export void TaskScheduler::ThreadPool::remove(const Ref& scheduler) + { + Lock lock(mutex); + for (std::list >::iterator it = schedulers.begin(); it != schedulers.end(); it++) { + if (scheduler == *it) { + schedulers.erase(it); + return; + } + } + } + + void TaskScheduler::ThreadPool::thread_loop(size_t globalThreadIndex) + { + while (globalThreadIndex < numThreadsRunning) + { + Ref scheduler = NULL; + ssize_t threadIndex = -1; + { + Lock lock(mutex); + condition.wait(mutex, [&] () { return globalThreadIndex >= numThreadsRunning || !schedulers.empty(); }); + if (globalThreadIndex >= numThreadsRunning) break; + scheduler = schedulers.front(); + threadIndex = scheduler->allocThreadIndex(); + } + scheduler->thread_loop(threadIndex); + } + } + + TaskScheduler::TaskScheduler() + : threadCounter(0), anyTasksRunning(0), hasRootTask(false) + { + threadLocal.resize(2*getNumberOfLogicalThreads()); // FIXME: this has to be 2x as in the compatibility join mode with rtcCommitScene the worker threads also join. When disallowing rtcCommitScene to join a build we can remove the 2x. + for (size_t i=0; ithreadIndex; + else return 0; + } + + dll_export size_t TaskScheduler::threadIndex() + { + Thread* thread = TaskScheduler::thread(); + if (thread) return thread->threadIndex; + else return 0; + } + + dll_export size_t TaskScheduler::threadCount() { + return threadPool->size(); + } + + dll_export TaskScheduler* TaskScheduler::instance() + { + if (g_instance == NULL) { + Lock lock(g_mutex); + g_instance = new TaskScheduler; + g_instance_vector.push_back(g_instance); + } + return g_instance; + } + + void TaskScheduler::create(size_t numThreads, bool set_affinity, bool start_threads) + { + if (!threadPool) threadPool = new TaskScheduler::ThreadPool(set_affinity); + threadPool->setNumThreads(numThreads,start_threads); + } + + void TaskScheduler::destroy() { + delete threadPool; threadPool = nullptr; + } + + dll_export ssize_t TaskScheduler::allocThreadIndex() + { + size_t threadIndex = threadCounter++; + assert(threadIndex < threadLocal.size()); + return threadIndex; + } + + void TaskScheduler::join() + { + mutex.lock(); + size_t threadIndex = allocThreadIndex(); + condition.wait(mutex, [&] () { return hasRootTask.load(); }); + mutex.unlock(); + std::exception_ptr except = thread_loop(threadIndex); + if (except != nullptr) std::rethrow_exception(except); + } + + void TaskScheduler::reset() { + hasRootTask = false; + } + + void TaskScheduler::wait_for_threads(size_t threadCount) + { + while (threadCounter < threadCount-1) + pause_cpu(); + } + + dll_export TaskScheduler::Thread* TaskScheduler::thread() { + return thread_local_thread; + } + + dll_export TaskScheduler::Thread* TaskScheduler::swapThread(Thread* thread) + { + Thread* old = thread_local_thread; + thread_local_thread = thread; + return old; + } + + dll_export bool TaskScheduler::wait() + { + Thread* thread = TaskScheduler::thread(); + if (thread == nullptr) return true; + while (thread->tasks.execute_local_internal(*thread,thread->task)) {}; + return thread->scheduler->cancellingException == nullptr; + } + + std::exception_ptr TaskScheduler::thread_loop(size_t threadIndex) + { + /* allocate thread structure */ + std::unique_ptr mthread(new Thread(threadIndex,this)); // too large for stack allocation + Thread& thread = *mthread; + threadLocal[threadIndex].store(&thread); + Thread* oldThread = swapThread(&thread); + + /* main thread loop */ + while (anyTasksRunning) + { + steal_loop(thread, + [&] () { return anyTasksRunning > 0; }, + [&] () { + anyTasksRunning++; + while (thread.tasks.execute_local_internal(thread,nullptr)); + anyTasksRunning--; + }); + } + threadLocal[threadIndex].store(nullptr); + swapThread(oldThread); + + /* remember exception to throw */ + std::exception_ptr except = nullptr; + if (cancellingException != nullptr) except = cancellingException; + + /* wait for all threads to terminate */ + threadCounter--; +#if defined(__WIN32__) + size_t loopIndex = 1; +#endif +#define LOOP_YIELD_THRESHOLD (4096) + while (threadCounter > 0) { +#if defined(__WIN32__) + if ((loopIndex % LOOP_YIELD_THRESHOLD) == 0) + yield(); + else + _mm_pause(); + loopIndex++; +#else + yield(); +#endif + } + return except; + } + + bool TaskScheduler::steal_from_other_threads(Thread& thread) + { + const size_t threadIndex = thread.threadIndex; + const size_t threadCount = this->threadCounter; + + for (size_t i=1; i= threadCount) otherThreadIndex -= threadCount; + + Thread* othread = threadLocal[otherThreadIndex].load(); + if (!othread) + continue; + + if (othread->tasks.steal(thread)) + return true; + } + + return false; + } + + dll_export void TaskScheduler::startThreads() { + threadPool->startThreads(); + } + + dll_export void TaskScheduler::addScheduler(const Ref& scheduler) { + threadPool->add(scheduler); + } + + dll_export void TaskScheduler::removeScheduler(const Ref& scheduler) { + threadPool->remove(scheduler); + } + + RTC_NAMESPACE_END +} diff --git a/thirdparty/embree/common/tasking/taskschedulerinternal.h b/thirdparty/embree/common/tasking/taskschedulerinternal.h new file mode 100644 index 000000000000..ef4d65f6fd3f --- /dev/null +++ b/thirdparty/embree/common/tasking/taskschedulerinternal.h @@ -0,0 +1,376 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/alloc.h" +#include "../sys/barrier.h" +#include "../sys/thread.h" +#include "../sys/mutex.h" +#include "../sys/condition.h" +#include "../sys/ref.h" +#include "../sys/atomic.h" +#include "../math/range.h" +#include "../../include/embree3/rtcore.h" + +#include + +namespace embree +{ + + /* The tasking system exports some symbols to be used by the tutorials. Thus we + hide is also in the API namespace when requested. */ + RTC_NAMESPACE_BEGIN + + struct TaskScheduler : public RefCount + { + ALIGNED_STRUCT_(64); + friend class Device; + + static const size_t TASK_STACK_SIZE = 4*1024; //!< task structure stack + static const size_t CLOSURE_STACK_SIZE = 512*1024; //!< stack for task closures + + struct Thread; + + /*! virtual interface for all tasks */ + struct TaskFunction { + virtual void execute() = 0; + }; + + /*! builds a task interface from a closure */ + template + struct ClosureTaskFunction : public TaskFunction + { + Closure closure; + __forceinline ClosureTaskFunction (const Closure& closure) : closure(closure) {} + void execute() { closure(); }; + }; + + struct __aligned(64) Task + { + /*! states a task can be in */ + enum { DONE, INITIALIZED }; + + /*! switch from one state to another */ + __forceinline void switch_state(int from, int to) + { + __memory_barrier(); + MAYBE_UNUSED bool success = state.compare_exchange_strong(from,to); + assert(success); + } + + /*! try to switch from one state to another */ + __forceinline bool try_switch_state(int from, int to) { + __memory_barrier(); + return state.compare_exchange_strong(from,to); + } + + /*! increment/decrement dependency counter */ + void add_dependencies(int n) { + dependencies+=n; + } + + /*! initialize all tasks to DONE state by default */ + __forceinline Task() + : state(DONE) {} + + /*! construction of new task */ + __forceinline Task (TaskFunction* closure, Task* parent, size_t stackPtr, size_t N) + : dependencies(1), stealable(true), closure(closure), parent(parent), stackPtr(stackPtr), N(N) + { + if (parent) parent->add_dependencies(+1); + switch_state(DONE,INITIALIZED); + } + + /*! construction of stolen task, stealing thread will decrement initial dependency */ + __forceinline Task (TaskFunction* closure, Task* parent) + : dependencies(1), stealable(false), closure(closure), parent(parent), stackPtr(-1), N(1) + { + switch_state(DONE,INITIALIZED); + } + + /*! try to steal this task */ + bool try_steal(Task& child) + { + if (!stealable) return false; + if (!try_switch_state(INITIALIZED,DONE)) return false; + new (&child) Task(closure, this); + return true; + } + + /*! run this task */ + dll_export void run(Thread& thread); + + void run_internal(Thread& thread); + + public: + std::atomic state; //!< state this task is in + std::atomic dependencies; //!< dependencies to wait for + std::atomic stealable; //!< true if task can be stolen + TaskFunction* closure; //!< the closure to execute + Task* parent; //!< parent task to signal when we are finished + size_t stackPtr; //!< stack location where closure is stored + size_t N; //!< approximative size of task + }; + + struct TaskQueue + { + TaskQueue () + : left(0), right(0), stackPtr(0) {} + + __forceinline void* alloc(size_t bytes, size_t align = 64) + { + size_t ofs = bytes + ((align - stackPtr) & (align-1)); + if (stackPtr + ofs > CLOSURE_STACK_SIZE) + throw std::runtime_error("closure stack overflow"); + stackPtr += ofs; + return &stack[stackPtr-bytes]; + } + + template + __forceinline void push_right(Thread& thread, const size_t size, const Closure& closure) + { + if (right >= TASK_STACK_SIZE) + throw std::runtime_error("task stack overflow"); + + /* allocate new task on right side of stack */ + size_t oldStackPtr = stackPtr; + TaskFunction* func = new (alloc(sizeof(ClosureTaskFunction))) ClosureTaskFunction(closure); + new (&tasks[right]) Task(func,thread.task,oldStackPtr,size); + right++; + + /* also move left pointer */ + if (left >= right-1) left = right-1; + } + + dll_export bool execute_local(Thread& thread, Task* parent); + bool execute_local_internal(Thread& thread, Task* parent); + bool steal(Thread& thread); + size_t getTaskSizeAtLeft(); + + bool empty() { return right == 0; } + + public: + + /* task stack */ + Task tasks[TASK_STACK_SIZE]; + __aligned(64) std::atomic left; //!< threads steal from left + __aligned(64) std::atomic right; //!< new tasks are added to the right + + /* closure stack */ + __aligned(64) char stack[CLOSURE_STACK_SIZE]; + size_t stackPtr; + }; + + /*! thread local structure for each thread */ + struct Thread + { + ALIGNED_STRUCT_(64); + + Thread (size_t threadIndex, const Ref& scheduler) + : threadIndex(threadIndex), task(nullptr), scheduler(scheduler) {} + + __forceinline size_t threadCount() { + return scheduler->threadCounter; + } + + size_t threadIndex; //!< ID of this thread + TaskQueue tasks; //!< local task queue + Task* task; //!< current active task + Ref scheduler; //!< pointer to task scheduler + }; + + /*! pool of worker threads */ + struct ThreadPool + { + ThreadPool (bool set_affinity); + ~ThreadPool (); + + /*! starts the threads */ + dll_export void startThreads(); + + /*! sets number of threads to use */ + void setNumThreads(size_t numThreads, bool startThreads = false); + + /*! adds a task scheduler object for scheduling */ + dll_export void add(const Ref& scheduler); + + /*! remove the task scheduler object again */ + dll_export void remove(const Ref& scheduler); + + /*! returns number of threads of the thread pool */ + size_t size() const { return numThreads; } + + /*! main loop for all threads */ + void thread_loop(size_t threadIndex); + + private: + std::atomic numThreads; + std::atomic numThreadsRunning; + bool set_affinity; + std::atomic running; + std::vector threads; + + private: + MutexSys mutex; + ConditionSys condition; + std::list > schedulers; + }; + + TaskScheduler (); + ~TaskScheduler (); + + /*! initializes the task scheduler */ + static void create(size_t numThreads, bool set_affinity, bool start_threads); + + /*! destroys the task scheduler again */ + static void destroy(); + + /*! lets new worker threads join the tasking system */ + void join(); + void reset(); + + /*! let a worker thread allocate a thread index */ + dll_export ssize_t allocThreadIndex(); + + /*! wait for some number of threads available (threadCount includes main thread) */ + void wait_for_threads(size_t threadCount); + + /*! thread loop for all worker threads */ + std::exception_ptr thread_loop(size_t threadIndex); + + /*! steals a task from a different thread */ + bool steal_from_other_threads(Thread& thread); + + template + static void steal_loop(Thread& thread, const Predicate& pred, const Body& body); + + /* spawn a new task at the top of the threads task stack */ + template + void spawn_root(const Closure& closure, size_t size = 1, bool useThreadPool = true) + { + if (useThreadPool) startThreads(); + + size_t threadIndex = allocThreadIndex(); + std::unique_ptr mthread(new Thread(threadIndex,this)); // too large for stack allocation + Thread& thread = *mthread; + assert(threadLocal[threadIndex].load() == nullptr); + threadLocal[threadIndex] = &thread; + Thread* oldThread = swapThread(&thread); + thread.tasks.push_right(thread,size,closure); + { + Lock lock(mutex); + anyTasksRunning++; + hasRootTask = true; + condition.notify_all(); + } + + if (useThreadPool) addScheduler(this); + + while (thread.tasks.execute_local(thread,nullptr)); + anyTasksRunning--; + if (useThreadPool) removeScheduler(this); + + threadLocal[threadIndex] = nullptr; + swapThread(oldThread); + + /* remember exception to throw */ + std::exception_ptr except = nullptr; + if (cancellingException != nullptr) except = cancellingException; + + /* wait for all threads to terminate */ + threadCounter--; + while (threadCounter > 0) yield(); + cancellingException = nullptr; + + /* re-throw proper exception */ + if (except != nullptr) + std::rethrow_exception(except); + } + + /* spawn a new task at the top of the threads task stack */ + template + static __forceinline void spawn(size_t size, const Closure& closure) + { + Thread* thread = TaskScheduler::thread(); + if (likely(thread != nullptr)) thread->tasks.push_right(*thread,size,closure); + else instance()->spawn_root(closure,size); + } + + /* spawn a new task at the top of the threads task stack */ + template + static __forceinline void spawn(const Closure& closure) { + spawn(1,closure); + } + + /* spawn a new task set */ + template + static void spawn(const Index begin, const Index end, const Index blockSize, const Closure& closure) + { + spawn(end-begin, [=]() + { + if (end-begin <= blockSize) { + return closure(range(begin,end)); + } + const Index center = (begin+end)/2; + spawn(begin,center,blockSize,closure); + spawn(center,end ,blockSize,closure); + wait(); + }); + } + + /* work on spawned subtasks and wait until all have finished */ + dll_export static bool wait(); + + /* returns the ID of the current thread */ + dll_export static size_t threadID(); + + /* returns the index (0..threadCount-1) of the current thread */ + dll_export static size_t threadIndex(); + + /* returns the total number of threads */ + dll_export static size_t threadCount(); + + private: + + /* returns the thread local task list of this worker thread */ + dll_export static Thread* thread(); + + /* sets the thread local task list of this worker thread */ + dll_export static Thread* swapThread(Thread* thread); + + /*! returns the taskscheduler object to be used by the master thread */ + dll_export static TaskScheduler* instance(); + + /*! starts the threads */ + dll_export static void startThreads(); + + /*! adds a task scheduler object for scheduling */ + dll_export static void addScheduler(const Ref& scheduler); + + /*! remove the task scheduler object again */ + dll_export static void removeScheduler(const Ref& scheduler); + + private: + std::vector> threadLocal; + std::atomic threadCounter; + std::atomic anyTasksRunning; + std::atomic hasRootTask; + std::exception_ptr cancellingException; + MutexSys mutex; + ConditionSys condition; + + private: + static size_t g_numThreads; + static __thread TaskScheduler* g_instance; + static __thread Thread* thread_local_thread; + static ThreadPool* threadPool; + }; + + RTC_NAMESPACE_END + +#if defined(RTC_NAMESPACE) + using RTC_NAMESPACE::TaskScheduler; +#endif +} diff --git a/thirdparty/embree/common/tasking/taskschedulerppl.h b/thirdparty/embree/common/tasking/taskschedulerppl.h new file mode 100644 index 000000000000..776f98cdacd8 --- /dev/null +++ b/thirdparty/embree/common/tasking/taskschedulerppl.h @@ -0,0 +1,46 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/alloc.h" +#include "../sys/barrier.h" +#include "../sys/thread.h" +#include "../sys/mutex.h" +#include "../sys/condition.h" +#include "../sys/ref.h" + +#if !defined(__WIN32__) +#error PPL tasking system only available under windows +#endif + +#include + +namespace embree +{ + struct TaskScheduler + { + /*! initializes the task scheduler */ + static void create(size_t numThreads, bool set_affinity, bool start_threads); + + /*! destroys the task scheduler again */ + static void destroy(); + + /* returns the ID of the current thread */ + static __forceinline size_t threadID() { + return GetCurrentThreadId(); + } + + /* returns the index (0..threadCount-1) of the current thread */ + /* FIXME: threadIndex is NOT supported by PPL! */ + static __forceinline size_t threadIndex() { + return 0; + } + + /* returns the total number of threads */ + static __forceinline size_t threadCount() { + return GetMaximumProcessorCount(ALL_PROCESSOR_GROUPS) + 1; + } + }; +}; diff --git a/thirdparty/embree/common/tasking/taskschedulertbb.h b/thirdparty/embree/common/tasking/taskschedulertbb.h new file mode 100644 index 000000000000..369e5edf076b --- /dev/null +++ b/thirdparty/embree/common/tasking/taskschedulertbb.h @@ -0,0 +1,73 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../sys/platform.h" +#include "../sys/alloc.h" +#include "../sys/barrier.h" +#include "../sys/thread.h" +#include "../sys/mutex.h" +#include "../sys/condition.h" +#include "../sys/ref.h" + +#if defined(__WIN32__) +// -- GODOT start -- +#if !defined(NOMINMAX) +// -- GODOT end -- +# define NOMINMAX +// -- GODOT start -- +#endif +// -- GODOT end -- +#endif + +// We need to define these to avoid implicit linkage against +// tbb_debug.lib under Windows. When removing these lines debug build +// under Windows fails. +#define __TBB_NO_IMPLICIT_LINKAGE 1 +#define __TBBMALLOC_NO_IMPLICIT_LINKAGE 1 +#define TBB_SUPPRESS_DEPRECATED_MESSAGES 1 +#define TBB_PREVIEW_ISOLATED_TASK_GROUP 1 +#include "tbb/tbb.h" +#include "tbb/parallel_sort.h" + +namespace embree +{ + struct TaskScheduler + { + /*! initializes the task scheduler */ + static void create(size_t numThreads, bool set_affinity, bool start_threads); + + /*! destroys the task scheduler again */ + static void destroy(); + + /* returns the ID of the current thread */ + static __forceinline size_t threadID() + { + return threadIndex(); + } + + /* returns the index (0..threadCount-1) of the current thread */ + static __forceinline size_t threadIndex() + { +#if TBB_INTERFACE_VERSION >= 9100 + return tbb::this_task_arena::current_thread_index(); +#elif TBB_INTERFACE_VERSION >= 9000 + return tbb::task_arena::current_thread_index(); +#else + return 0; +#endif + } + + /* returns the total number of threads */ + static __forceinline size_t threadCount() { +#if TBB_INTERFACE_VERSION >= 9100 + return tbb::this_task_arena::max_concurrency(); +#else + return tbb::task_scheduler_init::default_num_threads(); +#endif + } + + }; + +}; diff --git a/thirdparty/embree/include/embree3/rtcore.h b/thirdparty/embree/include/embree3/rtcore.h new file mode 100644 index 000000000000..5830bb58801b --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore.h @@ -0,0 +1,14 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_config.h" +#include "rtcore_common.h" +#include "rtcore_device.h" +#include "rtcore_buffer.h" +#include "rtcore_ray.h" +#include "rtcore_geometry.h" +#include "rtcore_scene.h" +#include "rtcore_builder.h" +#include "rtcore_quaternion.h" diff --git a/thirdparty/embree/include/embree3/rtcore_buffer.h b/thirdparty/embree/include/embree3/rtcore_buffer.h new file mode 100644 index 000000000000..400b604aa53f --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_buffer.h @@ -0,0 +1,51 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_device.h" + +RTC_NAMESPACE_BEGIN + +/* Types of buffers */ +enum RTCBufferType +{ + RTC_BUFFER_TYPE_INDEX = 0, + RTC_BUFFER_TYPE_VERTEX = 1, + RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE = 2, + RTC_BUFFER_TYPE_NORMAL = 3, + RTC_BUFFER_TYPE_TANGENT = 4, + RTC_BUFFER_TYPE_NORMAL_DERIVATIVE = 5, + + RTC_BUFFER_TYPE_GRID = 8, + + RTC_BUFFER_TYPE_FACE = 16, + RTC_BUFFER_TYPE_LEVEL = 17, + RTC_BUFFER_TYPE_EDGE_CREASE_INDEX = 18, + RTC_BUFFER_TYPE_EDGE_CREASE_WEIGHT = 19, + RTC_BUFFER_TYPE_VERTEX_CREASE_INDEX = 20, + RTC_BUFFER_TYPE_VERTEX_CREASE_WEIGHT = 21, + RTC_BUFFER_TYPE_HOLE = 22, + + RTC_BUFFER_TYPE_FLAGS = 32 +}; + +/* Opaque buffer type */ +typedef struct RTCBufferTy* RTCBuffer; + +/* Creates a new buffer. */ +RTC_API RTCBuffer rtcNewBuffer(RTCDevice device, size_t byteSize); + +/* Creates a new shared buffer. */ +RTC_API RTCBuffer rtcNewSharedBuffer(RTCDevice device, void* ptr, size_t byteSize); + +/* Returns a pointer to the buffer data. */ +RTC_API void* rtcGetBufferData(RTCBuffer buffer); + +/* Retains the buffer (increments the reference count). */ +RTC_API void rtcRetainBuffer(RTCBuffer buffer); + +/* Releases the buffer (decrements the reference count). */ +RTC_API void rtcReleaseBuffer(RTCBuffer buffer); + +RTC_NAMESPACE_END diff --git a/thirdparty/embree/include/embree3/rtcore_builder.h b/thirdparty/embree/include/embree3/rtcore_builder.h new file mode 100644 index 000000000000..d62a7f72cc26 --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_builder.h @@ -0,0 +1,125 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_scene.h" + +RTC_NAMESPACE_BEGIN + +/* Opaque BVH type */ +typedef struct RTCBVHTy* RTCBVH; + +/* Input build primitives for the builder */ +struct RTC_ALIGN(32) RTCBuildPrimitive +{ + float lower_x, lower_y, lower_z; + unsigned int geomID; + float upper_x, upper_y, upper_z; + unsigned int primID; +}; + +/* Opaque thread local allocator type */ +typedef struct RTCThreadLocalAllocatorTy* RTCThreadLocalAllocator; + +/* Callback to create a node */ +typedef void* (*RTCCreateNodeFunction) (RTCThreadLocalAllocator allocator, unsigned int childCount, void* userPtr); + +/* Callback to set the pointer to all children */ +typedef void (*RTCSetNodeChildrenFunction) (void* nodePtr, void** children, unsigned int childCount, void* userPtr); + +/* Callback to set the bounds of all children */ +typedef void (*RTCSetNodeBoundsFunction) (void* nodePtr, const struct RTCBounds** bounds, unsigned int childCount, void* userPtr); + +/* Callback to create a leaf node */ +typedef void* (*RTCCreateLeafFunction) (RTCThreadLocalAllocator allocator, const struct RTCBuildPrimitive* primitives, size_t primitiveCount, void* userPtr); + +/* Callback to split a build primitive */ +typedef void (*RTCSplitPrimitiveFunction) (const struct RTCBuildPrimitive* primitive, unsigned int dimension, float position, struct RTCBounds* leftBounds, struct RTCBounds* rightBounds, void* userPtr); + +/* Build flags */ +enum RTCBuildFlags +{ + RTC_BUILD_FLAG_NONE = 0, + RTC_BUILD_FLAG_DYNAMIC = (1 << 0), +}; + +enum RTCBuildConstants +{ + RTC_BUILD_MAX_PRIMITIVES_PER_LEAF = 32 +}; + +/* Input for builders */ +struct RTCBuildArguments +{ + size_t byteSize; + + enum RTCBuildQuality buildQuality; + enum RTCBuildFlags buildFlags; + unsigned int maxBranchingFactor; + unsigned int maxDepth; + unsigned int sahBlockSize; + unsigned int minLeafSize; + unsigned int maxLeafSize; + float traversalCost; + float intersectionCost; + + RTCBVH bvh; + struct RTCBuildPrimitive* primitives; + size_t primitiveCount; + size_t primitiveArrayCapacity; + + RTCCreateNodeFunction createNode; + RTCSetNodeChildrenFunction setNodeChildren; + RTCSetNodeBoundsFunction setNodeBounds; + RTCCreateLeafFunction createLeaf; + RTCSplitPrimitiveFunction splitPrimitive; + RTCProgressMonitorFunction buildProgress; + void* userPtr; +}; + +/* Returns the default build settings. */ +RTC_FORCEINLINE struct RTCBuildArguments rtcDefaultBuildArguments() +{ + struct RTCBuildArguments args; + args.byteSize = sizeof(args); + args.buildQuality = RTC_BUILD_QUALITY_MEDIUM; + args.buildFlags = RTC_BUILD_FLAG_NONE; + args.maxBranchingFactor = 2; + args.maxDepth = 32; + args.sahBlockSize = 1; + args.minLeafSize = 1; + args.maxLeafSize = RTC_BUILD_MAX_PRIMITIVES_PER_LEAF; + args.traversalCost = 1.0f; + args.intersectionCost = 1.0f; + args.bvh = NULL; + args.primitives = NULL; + args.primitiveCount = 0; + args.primitiveArrayCapacity = 0; + args.createNode = NULL; + args.setNodeChildren = NULL; + args.setNodeBounds = NULL; + args.createLeaf = NULL; + args.splitPrimitive = NULL; + args.buildProgress = NULL; + args.userPtr = NULL; + return args; +} + +/* Creates a new BVH. */ +RTC_API RTCBVH rtcNewBVH(RTCDevice device); + +/* Builds a BVH. */ +RTC_API void* rtcBuildBVH(const struct RTCBuildArguments* args); + +/* Allocates memory using the thread local allocator. */ +RTC_API void* rtcThreadLocalAlloc(RTCThreadLocalAllocator allocator, size_t bytes, size_t align); + +/* Retains the BVH (increments reference count). */ +RTC_API void rtcRetainBVH(RTCBVH bvh); + +/* Releases the BVH (decrements reference count). */ +RTC_API void rtcReleaseBVH(RTCBVH bvh); + +RTC_NAMESPACE_END + diff --git a/thirdparty/embree/include/embree3/rtcore_common.h b/thirdparty/embree/include/embree3/rtcore_common.h new file mode 100644 index 000000000000..a516f6bdf1f8 --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_common.h @@ -0,0 +1,326 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "rtcore_config.h" + +RTC_NAMESPACE_BEGIN + +#if defined(_WIN32) +#if defined(_M_X64) +typedef long long ssize_t; +#else +typedef int ssize_t; +#endif +#endif + +#ifdef _WIN32 +# define RTC_ALIGN(...) __declspec(align(__VA_ARGS__)) +#else +# define RTC_ALIGN(...) __attribute__((aligned(__VA_ARGS__))) +#endif + +#if !defined (RTC_DEPRECATED) +#ifdef __GNUC__ + #define RTC_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define RTC_DEPRECATED __declspec(deprecated) +#else + #define RTC_DEPRECATED +#endif +#endif + +#if defined(_WIN32) +# define RTC_FORCEINLINE __forceinline +#else +# define RTC_FORCEINLINE inline __attribute__((always_inline)) +#endif + +/* Invalid geometry ID */ +#define RTC_INVALID_GEOMETRY_ID ((unsigned int)-1) + +/* Maximum number of time steps */ +#define RTC_MAX_TIME_STEP_COUNT 129 + +/* Formats of buffers and other data structures */ +enum RTCFormat +{ + RTC_FORMAT_UNDEFINED = 0, + + /* 8-bit unsigned integer */ + RTC_FORMAT_UCHAR = 0x1001, + RTC_FORMAT_UCHAR2, + RTC_FORMAT_UCHAR3, + RTC_FORMAT_UCHAR4, + + /* 8-bit signed integer */ + RTC_FORMAT_CHAR = 0x2001, + RTC_FORMAT_CHAR2, + RTC_FORMAT_CHAR3, + RTC_FORMAT_CHAR4, + + /* 16-bit unsigned integer */ + RTC_FORMAT_USHORT = 0x3001, + RTC_FORMAT_USHORT2, + RTC_FORMAT_USHORT3, + RTC_FORMAT_USHORT4, + + /* 16-bit signed integer */ + RTC_FORMAT_SHORT = 0x4001, + RTC_FORMAT_SHORT2, + RTC_FORMAT_SHORT3, + RTC_FORMAT_SHORT4, + + /* 32-bit unsigned integer */ + RTC_FORMAT_UINT = 0x5001, + RTC_FORMAT_UINT2, + RTC_FORMAT_UINT3, + RTC_FORMAT_UINT4, + + /* 32-bit signed integer */ + RTC_FORMAT_INT = 0x6001, + RTC_FORMAT_INT2, + RTC_FORMAT_INT3, + RTC_FORMAT_INT4, + + /* 64-bit unsigned integer */ + RTC_FORMAT_ULLONG = 0x7001, + RTC_FORMAT_ULLONG2, + RTC_FORMAT_ULLONG3, + RTC_FORMAT_ULLONG4, + + /* 64-bit signed integer */ + RTC_FORMAT_LLONG = 0x8001, + RTC_FORMAT_LLONG2, + RTC_FORMAT_LLONG3, + RTC_FORMAT_LLONG4, + + /* 32-bit float */ + RTC_FORMAT_FLOAT = 0x9001, + RTC_FORMAT_FLOAT2, + RTC_FORMAT_FLOAT3, + RTC_FORMAT_FLOAT4, + RTC_FORMAT_FLOAT5, + RTC_FORMAT_FLOAT6, + RTC_FORMAT_FLOAT7, + RTC_FORMAT_FLOAT8, + RTC_FORMAT_FLOAT9, + RTC_FORMAT_FLOAT10, + RTC_FORMAT_FLOAT11, + RTC_FORMAT_FLOAT12, + RTC_FORMAT_FLOAT13, + RTC_FORMAT_FLOAT14, + RTC_FORMAT_FLOAT15, + RTC_FORMAT_FLOAT16, + + /* 32-bit float matrix (row-major order) */ + RTC_FORMAT_FLOAT2X2_ROW_MAJOR = 0x9122, + RTC_FORMAT_FLOAT2X3_ROW_MAJOR = 0x9123, + RTC_FORMAT_FLOAT2X4_ROW_MAJOR = 0x9124, + RTC_FORMAT_FLOAT3X2_ROW_MAJOR = 0x9132, + RTC_FORMAT_FLOAT3X3_ROW_MAJOR = 0x9133, + RTC_FORMAT_FLOAT3X4_ROW_MAJOR = 0x9134, + RTC_FORMAT_FLOAT4X2_ROW_MAJOR = 0x9142, + RTC_FORMAT_FLOAT4X3_ROW_MAJOR = 0x9143, + RTC_FORMAT_FLOAT4X4_ROW_MAJOR = 0x9144, + + /* 32-bit float matrix (column-major order) */ + RTC_FORMAT_FLOAT2X2_COLUMN_MAJOR = 0x9222, + RTC_FORMAT_FLOAT2X3_COLUMN_MAJOR = 0x9223, + RTC_FORMAT_FLOAT2X4_COLUMN_MAJOR = 0x9224, + RTC_FORMAT_FLOAT3X2_COLUMN_MAJOR = 0x9232, + RTC_FORMAT_FLOAT3X3_COLUMN_MAJOR = 0x9233, + RTC_FORMAT_FLOAT3X4_COLUMN_MAJOR = 0x9234, + RTC_FORMAT_FLOAT4X2_COLUMN_MAJOR = 0x9242, + RTC_FORMAT_FLOAT4X3_COLUMN_MAJOR = 0x9243, + RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR = 0x9244, + + /* special 12-byte format for grids */ + RTC_FORMAT_GRID = 0xA001 +}; + +/* Build quality levels */ +enum RTCBuildQuality +{ + RTC_BUILD_QUALITY_LOW = 0, + RTC_BUILD_QUALITY_MEDIUM = 1, + RTC_BUILD_QUALITY_HIGH = 2, + RTC_BUILD_QUALITY_REFIT = 3, +}; + +/* Axis-aligned bounding box representation */ +struct RTC_ALIGN(16) RTCBounds +{ + float lower_x, lower_y, lower_z, align0; + float upper_x, upper_y, upper_z, align1; +}; + +/* Linear axis-aligned bounding box representation */ +struct RTC_ALIGN(16) RTCLinearBounds +{ + struct RTCBounds bounds0; + struct RTCBounds bounds1; +}; + +/* Intersection context flags */ +enum RTCIntersectContextFlags +{ + RTC_INTERSECT_CONTEXT_FLAG_NONE = 0, + RTC_INTERSECT_CONTEXT_FLAG_INCOHERENT = (0 << 0), // optimize for incoherent rays + RTC_INTERSECT_CONTEXT_FLAG_COHERENT = (1 << 0) // optimize for coherent rays +}; + +/* Arguments for RTCFilterFunctionN */ +struct RTCFilterFunctionNArguments +{ + int* valid; + void* geometryUserPtr; + struct RTCIntersectContext* context; + struct RTCRayN* ray; + struct RTCHitN* hit; + unsigned int N; +}; + +/* Filter callback function */ +typedef void (*RTCFilterFunctionN)(const struct RTCFilterFunctionNArguments* args); + +/* Intersection context passed to intersect/occluded calls */ +struct RTCIntersectContext +{ + enum RTCIntersectContextFlags flags; // intersection flags + RTCFilterFunctionN filter; // filter function to execute + +#if RTC_MAX_INSTANCE_LEVEL_COUNT > 1 + unsigned int instStackSize; // Number of instances currently on the stack. +#endif + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // The current stack of instance ids. + +#if RTC_MIN_WIDTH + float minWidthDistanceFactor; // curve radius is set to this factor times distance to ray origin +#endif +}; + +/* Initializes an intersection context. */ +RTC_FORCEINLINE void rtcInitIntersectContext(struct RTCIntersectContext* context) +{ + unsigned l = 0; + context->flags = RTC_INTERSECT_CONTEXT_FLAG_INCOHERENT; + context->filter = NULL; + +#if RTC_MAX_INSTANCE_LEVEL_COUNT > 1 + context->instStackSize = 0; +#endif + for (; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + context->instID[l] = RTC_INVALID_GEOMETRY_ID; + +#if RTC_MIN_WIDTH + context->minWidthDistanceFactor = 0.0f; +#endif +} + +/* Point query structure for closest point query */ +struct RTC_ALIGN(16) RTCPointQuery +{ + float x; // x coordinate of the query point + float y; // y coordinate of the query point + float z; // z coordinate of the query point + float time; // time of the point query + float radius; // radius of the point query +}; + +/* Structure of a packet of 4 query points */ +struct RTC_ALIGN(16) RTCPointQuery4 +{ + float x[4]; // x coordinate of the query point + float y[4]; // y coordinate of the query point + float z[4]; // z coordinate of the query point + float time[4]; // time of the point query + float radius[4]; // radius of the point query +}; + +/* Structure of a packet of 8 query points */ +struct RTC_ALIGN(32) RTCPointQuery8 +{ + float x[8]; // x coordinate of the query point + float y[8]; // y coordinate of the query point + float z[8]; // z coordinate of the query point + float time[8]; // time of the point query + float radius[8]; // radius ofr the point query +}; + +/* Structure of a packet of 16 query points */ +struct RTC_ALIGN(64) RTCPointQuery16 +{ + float x[16]; // x coordinate of the query point + float y[16]; // y coordinate of the query point + float z[16]; // z coordinate of the query point + float time[16]; // time of the point quey + float radius[16]; // radius of the point query +}; + +struct RTCPointQueryN; + +struct RTC_ALIGN(16) RTCPointQueryContext +{ + // accumulated 4x4 column major matrices from world space to instance space. + // undefined if size == 0. + float world2inst[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; + + // accumulated 4x4 column major matrices from instance space to world space. + // undefined if size == 0. + float inst2world[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; + + // instance ids. + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; + + // number of instances currently on the stack. + unsigned int instStackSize; +}; + +/* Initializes an intersection context. */ +RTC_FORCEINLINE void rtcInitPointQueryContext(struct RTCPointQueryContext* context) +{ + context->instStackSize = 0; + context->instID[0] = RTC_INVALID_GEOMETRY_ID; +} + +struct RTC_ALIGN(16) RTCPointQueryFunctionArguments +{ + // The (world space) query object that was passed as an argument of rtcPointQuery. The + // radius of the query can be decreased inside the callback to shrink the + // search domain. Increasing the radius or modifying the time or position of + // the query results in undefined behaviour. + struct RTCPointQuery* query; + + // Used for user input/output data. Will not be read or modified internally. + void* userPtr; + + // primitive and geometry ID of primitive + unsigned int primID; + unsigned int geomID; + + // the context with transformation and instance ID stack + struct RTCPointQueryContext* context; + + // If the current instance transform M (= context->world2inst[context->instStackSize]) + // is a similarity matrix, i.e there is a constant factor similarityScale such that, + // for all x,y: dist(Mx, My) = similarityScale * dist(x, y), + // The similarity scale is 0, if the current instance transform is not a + // similarity transform and vice versa. The similarity scale allows to compute + // distance information in instance space and scale the distances into world + // space by dividing with the similarity scale, for example, to update the + // query radius. If the current instance transform is not a similarity + // transform (similarityScale = 0), the distance computation has to be + // performed in world space to ensure correctness. if there is no instance + // transform (context->instStackSize == 0), the similarity scale is 1. + float similarityScale; +}; + +typedef bool (*RTCPointQueryFunction)(struct RTCPointQueryFunctionArguments* args); + +RTC_NAMESPACE_END diff --git a/thirdparty/embree/include/embree3/rtcore_config.h b/thirdparty/embree/include/embree3/rtcore_config.h new file mode 100644 index 000000000000..337d4e948721 --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_config.h @@ -0,0 +1,57 @@ + +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define RTC_VERSION_MAJOR 3 +#define RTC_VERSION_MINOR 12 +#define RTC_VERSION_PATCH 1 +#define RTC_VERSION 31201 +#define RTC_VERSION_STRING "3.12.1" + +#define RTC_MAX_INSTANCE_LEVEL_COUNT 1 + +#define EMBREE_MIN_WIDTH 0 +#define RTC_MIN_WIDTH EMBREE_MIN_WIDTH + +#define EMBREE_STATIC_LIB +/* #undef EMBREE_API_NAMESPACE */ + +#if defined(EMBREE_API_NAMESPACE) +# define RTC_NAMESPACE +# define RTC_NAMESPACE_BEGIN namespace { +# define RTC_NAMESPACE_END } +# define RTC_NAMESPACE_USE using namespace ; +# define RTC_API_EXTERN_C +# undef EMBREE_API_NAMESPACE +#else +# define RTC_NAMESPACE_BEGIN +# define RTC_NAMESPACE_END +# define RTC_NAMESPACE_USE +# if defined(__cplusplus) +# define RTC_API_EXTERN_C extern "C" +# else +# define RTC_API_EXTERN_C +# endif +#endif + +#if defined(ISPC) +# define RTC_API_IMPORT extern "C" unmasked +# define RTC_API_EXPORT extern "C" unmasked +#elif defined(EMBREE_STATIC_LIB) +# define RTC_API_IMPORT RTC_API_EXTERN_C +# define RTC_API_EXPORT RTC_API_EXTERN_C +#elif defined(_WIN32) +# define RTC_API_IMPORT RTC_API_EXTERN_C __declspec(dllimport) +# define RTC_API_EXPORT RTC_API_EXTERN_C __declspec(dllexport) +#else +# define RTC_API_IMPORT RTC_API_EXTERN_C +# define RTC_API_EXPORT RTC_API_EXTERN_C __attribute__ ((visibility ("default"))) +#endif + +#if defined(RTC_EXPORT_API) +# define RTC_API RTC_API_EXPORT +#else +# define RTC_API RTC_API_IMPORT +#endif diff --git a/thirdparty/embree/include/embree3/rtcore_device.h b/thirdparty/embree/include/embree3/rtcore_device.h new file mode 100644 index 000000000000..594e2b755dcd --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_device.h @@ -0,0 +1,87 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_common.h" + +RTC_NAMESPACE_BEGIN + +/* Opaque device type */ +typedef struct RTCDeviceTy* RTCDevice; + +/* Creates a new Embree device. */ +RTC_API RTCDevice rtcNewDevice(const char* config); + +/* Retains the Embree device (increments the reference count). */ +RTC_API void rtcRetainDevice(RTCDevice device); + +/* Releases an Embree device (decrements the reference count). */ +RTC_API void rtcReleaseDevice(RTCDevice device); + +/* Device properties */ +enum RTCDeviceProperty +{ + RTC_DEVICE_PROPERTY_VERSION = 0, + RTC_DEVICE_PROPERTY_VERSION_MAJOR = 1, + RTC_DEVICE_PROPERTY_VERSION_MINOR = 2, + RTC_DEVICE_PROPERTY_VERSION_PATCH = 3, + + RTC_DEVICE_PROPERTY_NATIVE_RAY4_SUPPORTED = 32, + RTC_DEVICE_PROPERTY_NATIVE_RAY8_SUPPORTED = 33, + RTC_DEVICE_PROPERTY_NATIVE_RAY16_SUPPORTED = 34, + RTC_DEVICE_PROPERTY_RAY_STREAM_SUPPORTED = 35, + + RTC_DEVICE_PROPERTY_BACKFACE_CULLING_CURVES_ENABLED = 63, + RTC_DEVICE_PROPERTY_RAY_MASK_SUPPORTED = 64, + RTC_DEVICE_PROPERTY_BACKFACE_CULLING_ENABLED = 65, + RTC_DEVICE_PROPERTY_FILTER_FUNCTION_SUPPORTED = 66, + RTC_DEVICE_PROPERTY_IGNORE_INVALID_RAYS_ENABLED = 67, + RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED = 68, + + RTC_DEVICE_PROPERTY_TRIANGLE_GEOMETRY_SUPPORTED = 96, + RTC_DEVICE_PROPERTY_QUAD_GEOMETRY_SUPPORTED = 97, + RTC_DEVICE_PROPERTY_SUBDIVISION_GEOMETRY_SUPPORTED = 98, + RTC_DEVICE_PROPERTY_CURVE_GEOMETRY_SUPPORTED = 99, + RTC_DEVICE_PROPERTY_USER_GEOMETRY_SUPPORTED = 100, + RTC_DEVICE_PROPERTY_POINT_GEOMETRY_SUPPORTED = 101, + + RTC_DEVICE_PROPERTY_TASKING_SYSTEM = 128, + RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED = 129, + RTC_DEVICE_PROPERTY_PARALLEL_COMMIT_SUPPORTED = 130 +}; + +/* Gets a device property. */ +RTC_API ssize_t rtcGetDeviceProperty(RTCDevice device, enum RTCDeviceProperty prop); + +/* Sets a device property. */ +RTC_API void rtcSetDeviceProperty(RTCDevice device, const enum RTCDeviceProperty prop, ssize_t value); + +/* Error codes */ +enum RTCError +{ + RTC_ERROR_NONE = 0, + RTC_ERROR_UNKNOWN = 1, + RTC_ERROR_INVALID_ARGUMENT = 2, + RTC_ERROR_INVALID_OPERATION = 3, + RTC_ERROR_OUT_OF_MEMORY = 4, + RTC_ERROR_UNSUPPORTED_CPU = 5, + RTC_ERROR_CANCELLED = 6 +}; + +/* Returns the error code. */ +RTC_API enum RTCError rtcGetDeviceError(RTCDevice device); + +/* Error callback function */ +typedef void (*RTCErrorFunction)(void* userPtr, enum RTCError code, const char* str); + +/* Sets the error callback function. */ +RTC_API void rtcSetDeviceErrorFunction(RTCDevice device, RTCErrorFunction error, void* userPtr); + +/* Memory monitor callback function */ +typedef bool (*RTCMemoryMonitorFunction)(void* ptr, ssize_t bytes, bool post); + +/* Sets the memory monitor callback function. */ +RTC_API void rtcSetDeviceMemoryMonitorFunction(RTCDevice device, RTCMemoryMonitorFunction memoryMonitor, void* userPtr); + +RTC_NAMESPACE_END diff --git a/thirdparty/embree/include/embree3/rtcore_geometry.h b/thirdparty/embree/include/embree3/rtcore_geometry.h new file mode 100644 index 000000000000..c70f1b0e5cb3 --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_geometry.h @@ -0,0 +1,383 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_buffer.h" +#include "rtcore_quaternion.h" + +RTC_NAMESPACE_BEGIN + +/* Opaque scene type */ +typedef struct RTCSceneTy* RTCScene; + +/* Opaque geometry type */ +typedef struct RTCGeometryTy* RTCGeometry; + +/* Types of geometries */ +enum RTCGeometryType +{ + RTC_GEOMETRY_TYPE_TRIANGLE = 0, // triangle mesh + RTC_GEOMETRY_TYPE_QUAD = 1, // quad (triangle pair) mesh + RTC_GEOMETRY_TYPE_GRID = 2, // grid mesh + + RTC_GEOMETRY_TYPE_SUBDIVISION = 8, // Catmull-Clark subdivision surface + + RTC_GEOMETRY_TYPE_CONE_LINEAR_CURVE = 15, // Cone linear curves - discontinuous at edge boundaries + RTC_GEOMETRY_TYPE_ROUND_LINEAR_CURVE = 16, // Round (rounded cone like) linear curves + RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE = 17, // flat (ribbon-like) linear curves + + RTC_GEOMETRY_TYPE_ROUND_BEZIER_CURVE = 24, // round (tube-like) Bezier curves + RTC_GEOMETRY_TYPE_FLAT_BEZIER_CURVE = 25, // flat (ribbon-like) Bezier curves + RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BEZIER_CURVE = 26, // flat normal-oriented Bezier curves + + RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE = 32, // round (tube-like) B-spline curves + RTC_GEOMETRY_TYPE_FLAT_BSPLINE_CURVE = 33, // flat (ribbon-like) B-spline curves + RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BSPLINE_CURVE = 34, // flat normal-oriented B-spline curves + + RTC_GEOMETRY_TYPE_ROUND_HERMITE_CURVE = 40, // round (tube-like) Hermite curves + RTC_GEOMETRY_TYPE_FLAT_HERMITE_CURVE = 41, // flat (ribbon-like) Hermite curves + RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_HERMITE_CURVE = 42, // flat normal-oriented Hermite curves + + RTC_GEOMETRY_TYPE_SPHERE_POINT = 50, + RTC_GEOMETRY_TYPE_DISC_POINT = 51, + RTC_GEOMETRY_TYPE_ORIENTED_DISC_POINT = 52, + + RTC_GEOMETRY_TYPE_ROUND_CATMULL_ROM_CURVE = 58, // round (tube-like) Catmull-Rom curves + RTC_GEOMETRY_TYPE_FLAT_CATMULL_ROM_CURVE = 59, // flat (ribbon-like) Catmull-Rom curves + RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_CATMULL_ROM_CURVE = 60, // flat normal-oriented Catmull-Rom curves + + RTC_GEOMETRY_TYPE_USER = 120, // user-defined geometry + RTC_GEOMETRY_TYPE_INSTANCE = 121 // scene instance +}; + +/* Interpolation modes for subdivision surfaces */ +enum RTCSubdivisionMode +{ + RTC_SUBDIVISION_MODE_NO_BOUNDARY = 0, + RTC_SUBDIVISION_MODE_SMOOTH_BOUNDARY = 1, + RTC_SUBDIVISION_MODE_PIN_CORNERS = 2, + RTC_SUBDIVISION_MODE_PIN_BOUNDARY = 3, + RTC_SUBDIVISION_MODE_PIN_ALL = 4, +}; + +/* Curve segment flags */ +enum RTCCurveFlags +{ + RTC_CURVE_FLAG_NEIGHBOR_LEFT = (1 << 0), // left segments exists + RTC_CURVE_FLAG_NEIGHBOR_RIGHT = (1 << 1) // right segment exists +}; + +/* Arguments for RTCBoundsFunction */ +struct RTCBoundsFunctionArguments +{ + void* geometryUserPtr; + unsigned int primID; + unsigned int timeStep; + struct RTCBounds* bounds_o; +}; + +/* Bounding callback function */ +typedef void (*RTCBoundsFunction)(const struct RTCBoundsFunctionArguments* args); + +/* Arguments for RTCIntersectFunctionN */ +struct RTCIntersectFunctionNArguments +{ + int* valid; + void* geometryUserPtr; + unsigned int primID; + struct RTCIntersectContext* context; + struct RTCRayHitN* rayhit; + unsigned int N; + unsigned int geomID; +}; + +/* Intersection callback function */ +typedef void (*RTCIntersectFunctionN)(const struct RTCIntersectFunctionNArguments* args); + +/* Arguments for RTCOccludedFunctionN */ +struct RTCOccludedFunctionNArguments +{ + int* valid; + void* geometryUserPtr; + unsigned int primID; + struct RTCIntersectContext* context; + struct RTCRayN* ray; + unsigned int N; + unsigned int geomID; +}; + +/* Occlusion callback function */ +typedef void (*RTCOccludedFunctionN)(const struct RTCOccludedFunctionNArguments* args); + +/* Arguments for RTCDisplacementFunctionN */ +struct RTCDisplacementFunctionNArguments +{ + void* geometryUserPtr; + RTCGeometry geometry; + unsigned int primID; + unsigned int timeStep; + const float* u; + const float* v; + const float* Ng_x; + const float* Ng_y; + const float* Ng_z; + float* P_x; + float* P_y; + float* P_z; + unsigned int N; +}; + +/* Displacement mapping callback function */ +typedef void (*RTCDisplacementFunctionN)(const struct RTCDisplacementFunctionNArguments* args); + +/* Creates a new geometry of specified type. */ +RTC_API RTCGeometry rtcNewGeometry(RTCDevice device, enum RTCGeometryType type); + +/* Retains the geometry (increments the reference count). */ +RTC_API void rtcRetainGeometry(RTCGeometry geometry); + +/* Releases the geometry (decrements the reference count) */ +RTC_API void rtcReleaseGeometry(RTCGeometry geometry); + +/* Commits the geometry. */ +RTC_API void rtcCommitGeometry(RTCGeometry geometry); + + +/* Enables the geometry. */ +RTC_API void rtcEnableGeometry(RTCGeometry geometry); + +/* Disables the geometry. */ +RTC_API void rtcDisableGeometry(RTCGeometry geometry); + + +/* Sets the number of motion blur time steps of the geometry. */ +RTC_API void rtcSetGeometryTimeStepCount(RTCGeometry geometry, unsigned int timeStepCount); + +/* Sets the motion blur time range of the geometry. */ +RTC_API void rtcSetGeometryTimeRange(RTCGeometry geometry, float startTime, float endTime); + +/* Sets the number of vertex attributes of the geometry. */ +RTC_API void rtcSetGeometryVertexAttributeCount(RTCGeometry geometry, unsigned int vertexAttributeCount); + +/* Sets the ray mask of the geometry. */ +RTC_API void rtcSetGeometryMask(RTCGeometry geometry, unsigned int mask); + +/* Sets the build quality of the geometry. */ +RTC_API void rtcSetGeometryBuildQuality(RTCGeometry geometry, enum RTCBuildQuality quality); + +/* Sets the maximal curve or point radius scale allowed by min-width feature. */ +RTC_API void rtcSetGeometryMaxRadiusScale(RTCGeometry geometry, float maxRadiusScale); + + +/* Sets a geometry buffer. */ +RTC_API void rtcSetGeometryBuffer(RTCGeometry geometry, enum RTCBufferType type, unsigned int slot, enum RTCFormat format, RTCBuffer buffer, size_t byteOffset, size_t byteStride, size_t itemCount); + +/* Sets a shared geometry buffer. */ +RTC_API void rtcSetSharedGeometryBuffer(RTCGeometry geometry, enum RTCBufferType type, unsigned int slot, enum RTCFormat format, const void* ptr, size_t byteOffset, size_t byteStride, size_t itemCount); + +/* Creates and sets a new geometry buffer. */ +RTC_API void* rtcSetNewGeometryBuffer(RTCGeometry geometry, enum RTCBufferType type, unsigned int slot, enum RTCFormat format, size_t byteStride, size_t itemCount); + +/* Returns the pointer to the data of a buffer. */ +RTC_API void* rtcGetGeometryBufferData(RTCGeometry geometry, enum RTCBufferType type, unsigned int slot); + +/* Updates a geometry buffer. */ +RTC_API void rtcUpdateGeometryBuffer(RTCGeometry geometry, enum RTCBufferType type, unsigned int slot); + + +/* Sets the intersection filter callback function of the geometry. */ +RTC_API void rtcSetGeometryIntersectFilterFunction(RTCGeometry geometry, RTCFilterFunctionN filter); + +/* Sets the occlusion filter callback function of the geometry. */ +RTC_API void rtcSetGeometryOccludedFilterFunction(RTCGeometry geometry, RTCFilterFunctionN filter); + +/* Sets the user-defined data pointer of the geometry. */ +RTC_API void rtcSetGeometryUserData(RTCGeometry geometry, void* ptr); + +/* Gets the user-defined data pointer of the geometry. */ +RTC_API void* rtcGetGeometryUserData(RTCGeometry geometry); + +/* Set the point query callback function of a geometry. */ +RTC_API void rtcSetGeometryPointQueryFunction(RTCGeometry geometry, RTCPointQueryFunction pointQuery); + +/* Sets the number of primitives of a user geometry. */ +RTC_API void rtcSetGeometryUserPrimitiveCount(RTCGeometry geometry, unsigned int userPrimitiveCount); + +/* Sets the bounding callback function to calculate bounding boxes for user primitives. */ +RTC_API void rtcSetGeometryBoundsFunction(RTCGeometry geometry, RTCBoundsFunction bounds, void* userPtr); + +/* Set the intersect callback function of a user geometry. */ +RTC_API void rtcSetGeometryIntersectFunction(RTCGeometry geometry, RTCIntersectFunctionN intersect); + +/* Set the occlusion callback function of a user geometry. */ +RTC_API void rtcSetGeometryOccludedFunction(RTCGeometry geometry, RTCOccludedFunctionN occluded); + +/* Invokes the intersection filter from the intersection callback function. */ +RTC_API void rtcFilterIntersection(const struct RTCIntersectFunctionNArguments* args, const struct RTCFilterFunctionNArguments* filterArgs); + +/* Invokes the occlusion filter from the occlusion callback function. */ +RTC_API void rtcFilterOcclusion(const struct RTCOccludedFunctionNArguments* args, const struct RTCFilterFunctionNArguments* filterArgs); + + +/* Sets the instanced scene of an instance geometry. */ +RTC_API void rtcSetGeometryInstancedScene(RTCGeometry geometry, RTCScene scene); + +/* Sets the transformation of an instance for the specified time step. */ +RTC_API void rtcSetGeometryTransform(RTCGeometry geometry, unsigned int timeStep, enum RTCFormat format, const void* xfm); + +/* Sets the transformation quaternion of an instance for the specified time step. */ +RTC_API void rtcSetGeometryTransformQuaternion(RTCGeometry geometry, unsigned int timeStep, const struct RTCQuaternionDecomposition* qd); + +/* Returns the interpolated transformation of an instance for the specified time. */ +RTC_API void rtcGetGeometryTransform(RTCGeometry geometry, float time, enum RTCFormat format, void* xfm); + + +/* Sets the uniform tessellation rate of the geometry. */ +RTC_API void rtcSetGeometryTessellationRate(RTCGeometry geometry, float tessellationRate); + +/* Sets the number of topologies of a subdivision surface. */ +RTC_API void rtcSetGeometryTopologyCount(RTCGeometry geometry, unsigned int topologyCount); + +/* Sets the subdivision interpolation mode. */ +RTC_API void rtcSetGeometrySubdivisionMode(RTCGeometry geometry, unsigned int topologyID, enum RTCSubdivisionMode mode); + +/* Binds a vertex attribute to a topology of the geometry. */ +RTC_API void rtcSetGeometryVertexAttributeTopology(RTCGeometry geometry, unsigned int vertexAttributeID, unsigned int topologyID); + +/* Sets the displacement callback function of a subdivision surface. */ +RTC_API void rtcSetGeometryDisplacementFunction(RTCGeometry geometry, RTCDisplacementFunctionN displacement); + +/* Returns the first half edge of a face. */ +RTC_API unsigned int rtcGetGeometryFirstHalfEdge(RTCGeometry geometry, unsigned int faceID); + +/* Returns the face the half edge belongs to. */ +RTC_API unsigned int rtcGetGeometryFace(RTCGeometry geometry, unsigned int edgeID); + +/* Returns next half edge. */ +RTC_API unsigned int rtcGetGeometryNextHalfEdge(RTCGeometry geometry, unsigned int edgeID); + +/* Returns previous half edge. */ +RTC_API unsigned int rtcGetGeometryPreviousHalfEdge(RTCGeometry geometry, unsigned int edgeID); + +/* Returns opposite half edge. */ +RTC_API unsigned int rtcGetGeometryOppositeHalfEdge(RTCGeometry geometry, unsigned int topologyID, unsigned int edgeID); + + +/* Arguments for rtcInterpolate */ +struct RTCInterpolateArguments +{ + RTCGeometry geometry; + unsigned int primID; + float u; + float v; + enum RTCBufferType bufferType; + unsigned int bufferSlot; + float* P; + float* dPdu; + float* dPdv; + float* ddPdudu; + float* ddPdvdv; + float* ddPdudv; + unsigned int valueCount; +}; + +/* Interpolates vertex data to some u/v location and optionally calculates all derivatives. */ +RTC_API void rtcInterpolate(const struct RTCInterpolateArguments* args); + +/* Interpolates vertex data to some u/v location. */ +RTC_FORCEINLINE void rtcInterpolate0(RTCGeometry geometry, unsigned int primID, float u, float v, enum RTCBufferType bufferType, unsigned int bufferSlot, float* P, unsigned int valueCount) +{ + struct RTCInterpolateArguments args; + args.geometry = geometry; + args.primID = primID; + args.u = u; + args.v = v; + args.bufferType = bufferType; + args.bufferSlot = bufferSlot; + args.P = P; + args.dPdu = NULL; + args.dPdv = NULL; + args.ddPdudu = NULL; + args.ddPdvdv = NULL; + args.ddPdudv = NULL; + args.valueCount = valueCount; + rtcInterpolate(&args); +} + +/* Interpolates vertex data to some u/v location and calculates first order derivatives. */ +RTC_FORCEINLINE void rtcInterpolate1(RTCGeometry geometry, unsigned int primID, float u, float v, enum RTCBufferType bufferType, unsigned int bufferSlot, + float* P, float* dPdu, float* dPdv, unsigned int valueCount) +{ + struct RTCInterpolateArguments args; + args.geometry = geometry; + args.primID = primID; + args.u = u; + args.v = v; + args.bufferType = bufferType; + args.bufferSlot = bufferSlot; + args.P = P; + args.dPdu = dPdu; + args.dPdv = dPdv; + args.ddPdudu = NULL; + args.ddPdvdv = NULL; + args.ddPdudv = NULL; + args.valueCount = valueCount; + rtcInterpolate(&args); +} + +/* Interpolates vertex data to some u/v location and calculates first and second order derivatives. */ +RTC_FORCEINLINE void rtcInterpolate2(RTCGeometry geometry, unsigned int primID, float u, float v, enum RTCBufferType bufferType, unsigned int bufferSlot, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, unsigned int valueCount) +{ + struct RTCInterpolateArguments args; + args.geometry = geometry; + args.primID = primID; + args.u = u; + args.v = v; + args.bufferType = bufferType; + args.bufferSlot = bufferSlot; + args.P = P; + args.dPdu = dPdu; + args.dPdv = dPdv; + args.ddPdudu = ddPdudu; + args.ddPdvdv = ddPdvdv; + args.ddPdudv = ddPdudv; + args.valueCount = valueCount; + rtcInterpolate(&args); +} + +/* Arguments for rtcInterpolateN */ +struct RTCInterpolateNArguments +{ + RTCGeometry geometry; + const void* valid; + const unsigned int* primIDs; + const float* u; + const float* v; + unsigned int N; + enum RTCBufferType bufferType; + unsigned int bufferSlot; + float* P; + float* dPdu; + float* dPdv; + float* ddPdudu; + float* ddPdvdv; + float* ddPdudv; + unsigned int valueCount; +}; + +/* Interpolates vertex data to an array of u/v locations. */ +RTC_API void rtcInterpolateN(const struct RTCInterpolateNArguments* args); + +/* RTCGrid primitive for grid mesh */ +struct RTCGrid +{ + unsigned int startVertexID; + unsigned int stride; + unsigned short width,height; // max is a 32k x 32k grid +}; + +RTC_NAMESPACE_END + + diff --git a/thirdparty/embree/include/embree3/rtcore_quaternion.h b/thirdparty/embree/include/embree3/rtcore_quaternion.h new file mode 100644 index 000000000000..449cdedfdcf6 --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_quaternion.h @@ -0,0 +1,101 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_common.h" + +RTC_NAMESPACE_BEGIN + +/* + * Structure for transformation respresentation as a matrix decomposition using + * a quaternion + */ +struct RTC_ALIGN(16) RTCQuaternionDecomposition +{ + float scale_x; + float scale_y; + float scale_z; + float skew_xy; + float skew_xz; + float skew_yz; + float shift_x; + float shift_y; + float shift_z; + float quaternion_r; + float quaternion_i; + float quaternion_j; + float quaternion_k; + float translation_x; + float translation_y; + float translation_z; +}; + +RTC_FORCEINLINE void rtcInitQuaternionDecomposition(struct RTCQuaternionDecomposition* qdecomp) +{ + qdecomp->scale_x = 1.f; + qdecomp->scale_y = 1.f; + qdecomp->scale_z = 1.f; + qdecomp->skew_xy = 0.f; + qdecomp->skew_xz = 0.f; + qdecomp->skew_yz = 0.f; + qdecomp->shift_x = 0.f; + qdecomp->shift_y = 0.f; + qdecomp->shift_z = 0.f; + qdecomp->quaternion_r = 1.f; + qdecomp->quaternion_i = 0.f; + qdecomp->quaternion_j = 0.f; + qdecomp->quaternion_k = 0.f; + qdecomp->translation_x = 0.f; + qdecomp->translation_y = 0.f; + qdecomp->translation_z = 0.f; +} + +RTC_FORCEINLINE void rtcQuaternionDecompositionSetQuaternion( + struct RTCQuaternionDecomposition* qdecomp, + float r, float i, float j, float k) +{ + qdecomp->quaternion_r = r; + qdecomp->quaternion_i = i; + qdecomp->quaternion_j = j; + qdecomp->quaternion_k = k; +} + +RTC_FORCEINLINE void rtcQuaternionDecompositionSetScale( + struct RTCQuaternionDecomposition* qdecomp, + float scale_x, float scale_y, float scale_z) +{ + qdecomp->scale_x = scale_x; + qdecomp->scale_y = scale_y; + qdecomp->scale_z = scale_z; +} + +RTC_FORCEINLINE void rtcQuaternionDecompositionSetSkew( + struct RTCQuaternionDecomposition* qdecomp, + float skew_xy, float skew_xz, float skew_yz) +{ + qdecomp->skew_xy = skew_xy; + qdecomp->skew_xz = skew_xz; + qdecomp->skew_yz = skew_yz; +} + +RTC_FORCEINLINE void rtcQuaternionDecompositionSetShift( + struct RTCQuaternionDecomposition* qdecomp, + float shift_x, float shift_y, float shift_z) +{ + qdecomp->shift_x = shift_x; + qdecomp->shift_y = shift_y; + qdecomp->shift_z = shift_z; +} + +RTC_FORCEINLINE void rtcQuaternionDecompositionSetTranslation( + struct RTCQuaternionDecomposition* qdecomp, + float translation_x, float translation_y, float translation_z) +{ + qdecomp->translation_x = translation_x; + qdecomp->translation_y = translation_y; + qdecomp->translation_z = translation_z; +} + +RTC_NAMESPACE_END + diff --git a/thirdparty/embree/include/embree3/rtcore_ray.h b/thirdparty/embree/include/embree3/rtcore_ray.h new file mode 100644 index 000000000000..1ae3309ef1f8 --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_ray.h @@ -0,0 +1,378 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_common.h" + +RTC_NAMESPACE_BEGIN + +/* Ray structure for a single ray */ +struct RTC_ALIGN(16) RTCRay +{ + float org_x; // x coordinate of ray origin + float org_y; // y coordinate of ray origin + float org_z; // z coordinate of ray origin + float tnear; // start of ray segment + + float dir_x; // x coordinate of ray direction + float dir_y; // y coordinate of ray direction + float dir_z; // z coordinate of ray direction + float time; // time of this ray for motion blur + + float tfar; // end of ray segment (set to hit distance) + unsigned int mask; // ray mask + unsigned int id; // ray ID + unsigned int flags; // ray flags +}; + +/* Hit structure for a single ray */ +struct RTC_ALIGN(16) RTCHit +{ + float Ng_x; // x coordinate of geometry normal + float Ng_y; // y coordinate of geometry normal + float Ng_z; // z coordinate of geometry normal + + float u; // barycentric u coordinate of hit + float v; // barycentric v coordinate of hit + + unsigned int primID; // primitive ID + unsigned int geomID; // geometry ID + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // instance ID +}; + +/* Combined ray/hit structure for a single ray */ +struct RTCRayHit +{ + struct RTCRay ray; + struct RTCHit hit; +}; + +/* Ray structure for a packet of 4 rays */ +struct RTC_ALIGN(16) RTCRay4 +{ + float org_x[4]; + float org_y[4]; + float org_z[4]; + float tnear[4]; + + float dir_x[4]; + float dir_y[4]; + float dir_z[4]; + float time[4]; + + float tfar[4]; + unsigned int mask[4]; + unsigned int id[4]; + unsigned int flags[4]; +}; + +/* Hit structure for a packet of 4 rays */ +struct RTC_ALIGN(16) RTCHit4 +{ + float Ng_x[4]; + float Ng_y[4]; + float Ng_z[4]; + + float u[4]; + float v[4]; + + unsigned int primID[4]; + unsigned int geomID[4]; + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT][4]; +}; + +/* Combined ray/hit structure for a packet of 4 rays */ +struct RTCRayHit4 +{ + struct RTCRay4 ray; + struct RTCHit4 hit; +}; + +/* Ray structure for a packet of 8 rays */ +struct RTC_ALIGN(32) RTCRay8 +{ + float org_x[8]; + float org_y[8]; + float org_z[8]; + float tnear[8]; + + float dir_x[8]; + float dir_y[8]; + float dir_z[8]; + float time[8]; + + float tfar[8]; + unsigned int mask[8]; + unsigned int id[8]; + unsigned int flags[8]; +}; + +/* Hit structure for a packet of 8 rays */ +struct RTC_ALIGN(32) RTCHit8 +{ + float Ng_x[8]; + float Ng_y[8]; + float Ng_z[8]; + + float u[8]; + float v[8]; + + unsigned int primID[8]; + unsigned int geomID[8]; + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT][8]; +}; + +/* Combined ray/hit structure for a packet of 8 rays */ +struct RTCRayHit8 +{ + struct RTCRay8 ray; + struct RTCHit8 hit; +}; + +/* Ray structure for a packet of 16 rays */ +struct RTC_ALIGN(64) RTCRay16 +{ + float org_x[16]; + float org_y[16]; + float org_z[16]; + float tnear[16]; + + float dir_x[16]; + float dir_y[16]; + float dir_z[16]; + float time[16]; + + float tfar[16]; + unsigned int mask[16]; + unsigned int id[16]; + unsigned int flags[16]; +}; + +/* Hit structure for a packet of 16 rays */ +struct RTC_ALIGN(64) RTCHit16 +{ + float Ng_x[16]; + float Ng_y[16]; + float Ng_z[16]; + + float u[16]; + float v[16]; + + unsigned int primID[16]; + unsigned int geomID[16]; + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; +}; + +/* Combined ray/hit structure for a packet of 16 rays */ +struct RTCRayHit16 +{ + struct RTCRay16 ray; + struct RTCHit16 hit; +}; + +/* Ray structure for a packet/stream of N rays in pointer SOA layout */ +struct RTCRayNp +{ + float* org_x; + float* org_y; + float* org_z; + float* tnear; + + float* dir_x; + float* dir_y; + float* dir_z; + float* time; + + float* tfar; + unsigned int* mask; + unsigned int* id; + unsigned int* flags; +}; + +/* Hit structure for a packet/stream of N rays in pointer SOA layout */ +struct RTCHitNp +{ + float* Ng_x; + float* Ng_y; + float* Ng_z; + + float* u; + float* v; + + unsigned int* primID; + unsigned int* geomID; + unsigned int* instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; +}; + +/* Combined ray/hit structure for a packet/stream of N rays in pointer SOA layout */ +struct RTCRayHitNp +{ + struct RTCRayNp ray; + struct RTCHitNp hit; +}; + +struct RTCRayN; +struct RTCHitN; +struct RTCRayHitN; + +#if defined(__cplusplus) + +/* Helper functions to access ray packets of runtime size N */ +RTC_FORCEINLINE float& RTCRayN_org_x(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[0*N+i]; } +RTC_FORCEINLINE float& RTCRayN_org_y(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[1*N+i]; } +RTC_FORCEINLINE float& RTCRayN_org_z(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[2*N+i]; } +RTC_FORCEINLINE float& RTCRayN_tnear(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[3*N+i]; } + +RTC_FORCEINLINE float& RTCRayN_dir_x(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[4*N+i]; } +RTC_FORCEINLINE float& RTCRayN_dir_y(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[5*N+i]; } +RTC_FORCEINLINE float& RTCRayN_dir_z(RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[6*N+i]; } +RTC_FORCEINLINE float& RTCRayN_time (RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[7*N+i]; } + +RTC_FORCEINLINE float& RTCRayN_tfar (RTCRayN* ray, unsigned int N, unsigned int i) { return ((float*)ray)[8*N+i]; } +RTC_FORCEINLINE unsigned int& RTCRayN_mask (RTCRayN* ray, unsigned int N, unsigned int i) { return ((unsigned*)ray)[9*N+i]; } +RTC_FORCEINLINE unsigned int& RTCRayN_id (RTCRayN* ray, unsigned int N, unsigned int i) { return ((unsigned*)ray)[10*N+i]; } +RTC_FORCEINLINE unsigned int& RTCRayN_flags(RTCRayN* ray, unsigned int N, unsigned int i) { return ((unsigned*)ray)[11*N+i]; } + +/* Helper functions to access hit packets of runtime size N */ +RTC_FORCEINLINE float& RTCHitN_Ng_x(RTCHitN* hit, unsigned int N, unsigned int i) { return ((float*)hit)[0*N+i]; } +RTC_FORCEINLINE float& RTCHitN_Ng_y(RTCHitN* hit, unsigned int N, unsigned int i) { return ((float*)hit)[1*N+i]; } +RTC_FORCEINLINE float& RTCHitN_Ng_z(RTCHitN* hit, unsigned int N, unsigned int i) { return ((float*)hit)[2*N+i]; } + +RTC_FORCEINLINE float& RTCHitN_u(RTCHitN* hit, unsigned int N, unsigned int i) { return ((float*)hit)[3*N+i]; } +RTC_FORCEINLINE float& RTCHitN_v(RTCHitN* hit, unsigned int N, unsigned int i) { return ((float*)hit)[4*N+i]; } + +RTC_FORCEINLINE unsigned int& RTCHitN_primID(RTCHitN* hit, unsigned int N, unsigned int i) { return ((unsigned*)hit)[5*N+i]; } +RTC_FORCEINLINE unsigned int& RTCHitN_geomID(RTCHitN* hit, unsigned int N, unsigned int i) { return ((unsigned*)hit)[6*N+i]; } +RTC_FORCEINLINE unsigned int& RTCHitN_instID(RTCHitN* hit, unsigned int N, unsigned int i, unsigned int l) { return ((unsigned*)hit)[7*N+i+N*l]; } + +/* Helper functions to extract RTCRayN and RTCHitN from RTCRayHitN */ +RTC_FORCEINLINE RTCRayN* RTCRayHitN_RayN(RTCRayHitN* rayhit, unsigned int N) { return (RTCRayN*)&((float*)rayhit)[0*N]; } +RTC_FORCEINLINE RTCHitN* RTCRayHitN_HitN(RTCRayHitN* rayhit, unsigned int N) { return (RTCHitN*)&((float*)rayhit)[12*N]; } + +/* Helper structure for a ray packet of compile-time size N */ +template +struct RTCRayNt +{ + float org_x[N]; + float org_y[N]; + float org_z[N]; + float tnear[N]; + + float dir_x[N]; + float dir_y[N]; + float dir_z[N]; + float time[N]; + + float tfar[N]; + unsigned int mask[N]; + unsigned int id[N]; + unsigned int flags[N]; +}; + +/* Helper structure for a hit packet of compile-time size N */ +template +struct RTCHitNt +{ + float Ng_x[N]; + float Ng_y[N]; + float Ng_z[N]; + + float u[N]; + float v[N]; + + unsigned int primID[N]; + unsigned int geomID[N]; + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT][N]; +}; + +/* Helper structure for a combined ray/hit packet of compile-time size N */ +template +struct RTCRayHitNt +{ + RTCRayNt ray; + RTCHitNt hit; +}; + +RTC_FORCEINLINE RTCRay rtcGetRayFromRayN(RTCRayN* rayN, unsigned int N, unsigned int i) +{ + RTCRay ray; + ray.org_x = RTCRayN_org_x(rayN,N,i); + ray.org_y = RTCRayN_org_y(rayN,N,i); + ray.org_z = RTCRayN_org_z(rayN,N,i); + ray.tnear = RTCRayN_tnear(rayN,N,i); + ray.dir_x = RTCRayN_dir_x(rayN,N,i); + ray.dir_y = RTCRayN_dir_y(rayN,N,i); + ray.dir_z = RTCRayN_dir_z(rayN,N,i); + ray.time = RTCRayN_time(rayN,N,i); + ray.tfar = RTCRayN_tfar(rayN,N,i); + ray.mask = RTCRayN_mask(rayN,N,i); + ray.id = RTCRayN_id(rayN,N,i); + ray.flags = RTCRayN_flags(rayN,N,i); + return ray; +} + +RTC_FORCEINLINE RTCHit rtcGetHitFromHitN(RTCHitN* hitN, unsigned int N, unsigned int i) +{ + RTCHit hit; + hit.Ng_x = RTCHitN_Ng_x(hitN,N,i); + hit.Ng_y = RTCHitN_Ng_y(hitN,N,i); + hit.Ng_z = RTCHitN_Ng_z(hitN,N,i); + hit.u = RTCHitN_u(hitN,N,i); + hit.v = RTCHitN_v(hitN,N,i); + hit.primID = RTCHitN_primID(hitN,N,i); + hit.geomID = RTCHitN_geomID(hitN,N,i); + for (unsigned int l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; l++) + hit.instID[l] = RTCHitN_instID(hitN,N,i,l); + return hit; +} + +RTC_FORCEINLINE void rtcCopyHitToHitN(RTCHitN* hitN, const RTCHit* hit, unsigned int N, unsigned int i) +{ + RTCHitN_Ng_x(hitN,N,i) = hit->Ng_x; + RTCHitN_Ng_y(hitN,N,i) = hit->Ng_y; + RTCHitN_Ng_z(hitN,N,i) = hit->Ng_z; + RTCHitN_u(hitN,N,i) = hit->u; + RTCHitN_v(hitN,N,i) = hit->v; + RTCHitN_primID(hitN,N,i) = hit->primID; + RTCHitN_geomID(hitN,N,i) = hit->geomID; + for (unsigned int l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; l++) + RTCHitN_instID(hitN,N,i,l) = hit->instID[l]; +} + +RTC_FORCEINLINE RTCRayHit rtcGetRayHitFromRayHitN(RTCRayHitN* rayhitN, unsigned int N, unsigned int i) +{ + RTCRayHit rh; + + RTCRayN* ray = RTCRayHitN_RayN(rayhitN,N); + rh.ray.org_x = RTCRayN_org_x(ray,N,i); + rh.ray.org_y = RTCRayN_org_y(ray,N,i); + rh.ray.org_z = RTCRayN_org_z(ray,N,i); + rh.ray.tnear = RTCRayN_tnear(ray,N,i); + rh.ray.dir_x = RTCRayN_dir_x(ray,N,i); + rh.ray.dir_y = RTCRayN_dir_y(ray,N,i); + rh.ray.dir_z = RTCRayN_dir_z(ray,N,i); + rh.ray.time = RTCRayN_time(ray,N,i); + rh.ray.tfar = RTCRayN_tfar(ray,N,i); + rh.ray.mask = RTCRayN_mask(ray,N,i); + rh.ray.id = RTCRayN_id(ray,N,i); + rh.ray.flags = RTCRayN_flags(ray,N,i); + + RTCHitN* hit = RTCRayHitN_HitN(rayhitN,N); + rh.hit.Ng_x = RTCHitN_Ng_x(hit,N,i); + rh.hit.Ng_y = RTCHitN_Ng_y(hit,N,i); + rh.hit.Ng_z = RTCHitN_Ng_z(hit,N,i); + rh.hit.u = RTCHitN_u(hit,N,i); + rh.hit.v = RTCHitN_v(hit,N,i); + rh.hit.primID = RTCHitN_primID(hit,N,i); + rh.hit.geomID = RTCHitN_geomID(hit,N,i); + for (unsigned int l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; l++) + rh.hit.instID[l] = RTCHitN_instID(hit,N,i,l); + + return rh; +} + +#endif + +RTC_NAMESPACE_END + diff --git a/thirdparty/embree/include/embree3/rtcore_scene.h b/thirdparty/embree/include/embree3/rtcore_scene.h new file mode 100644 index 000000000000..0cd64015937e --- /dev/null +++ b/thirdparty/embree/include/embree3/rtcore_scene.h @@ -0,0 +1,160 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore_device.h" + +RTC_NAMESPACE_BEGIN + +/* Forward declarations for ray structures */ +struct RTCRayHit; +struct RTCRayHit4; +struct RTCRayHit8; +struct RTCRayHit16; +struct RTCRayHitNp; + +/* Scene flags */ +enum RTCSceneFlags +{ + RTC_SCENE_FLAG_NONE = 0, + RTC_SCENE_FLAG_DYNAMIC = (1 << 0), + RTC_SCENE_FLAG_COMPACT = (1 << 1), + RTC_SCENE_FLAG_ROBUST = (1 << 2), + RTC_SCENE_FLAG_CONTEXT_FILTER_FUNCTION = (1 << 3) +}; + +/* Creates a new scene. */ +RTC_API RTCScene rtcNewScene(RTCDevice device); + +/* Returns the device the scene got created in. The reference count of + * the device is incremented by this function. */ +RTC_API RTCDevice rtcGetSceneDevice(RTCScene hscene); + +/* Retains the scene (increments the reference count). */ +RTC_API void rtcRetainScene(RTCScene scene); + +/* Releases the scene (decrements the reference count). */ +RTC_API void rtcReleaseScene(RTCScene scene); + + +/* Attaches the geometry to a scene. */ +RTC_API unsigned int rtcAttachGeometry(RTCScene scene, RTCGeometry geometry); + +/* Attaches the geometry to a scene using the specified geometry ID. */ +RTC_API void rtcAttachGeometryByID(RTCScene scene, RTCGeometry geometry, unsigned int geomID); + +/* Detaches the geometry from the scene. */ +RTC_API void rtcDetachGeometry(RTCScene scene, unsigned int geomID); + +/* Gets a geometry handle from the scene. */ +RTC_API RTCGeometry rtcGetGeometry(RTCScene scene, unsigned int geomID); + + +/* Commits the scene. */ +RTC_API void rtcCommitScene(RTCScene scene); + +/* Commits the scene from multiple threads. */ +RTC_API void rtcJoinCommitScene(RTCScene scene); + + +/* Progress monitor callback function */ +typedef bool (*RTCProgressMonitorFunction)(void* ptr, double n); + +/* Sets the progress monitor callback function of the scene. */ +RTC_API void rtcSetSceneProgressMonitorFunction(RTCScene scene, RTCProgressMonitorFunction progress, void* ptr); + +/* Sets the build quality of the scene. */ +RTC_API void rtcSetSceneBuildQuality(RTCScene scene, enum RTCBuildQuality quality); + +/* Sets the scene flags. */ +RTC_API void rtcSetSceneFlags(RTCScene scene, enum RTCSceneFlags flags); + +/* Returns the scene flags. */ +RTC_API enum RTCSceneFlags rtcGetSceneFlags(RTCScene scene); + +/* Returns the axis-aligned bounds of the scene. */ +RTC_API void rtcGetSceneBounds(RTCScene scene, struct RTCBounds* bounds_o); + +/* Returns the linear axis-aligned bounds of the scene. */ +RTC_API void rtcGetSceneLinearBounds(RTCScene scene, struct RTCLinearBounds* bounds_o); + + +/* Perform a closest point query of the scene. */ +RTC_API bool rtcPointQuery(RTCScene scene, struct RTCPointQuery* query, struct RTCPointQueryContext* context, RTCPointQueryFunction queryFunc, void* userPtr); + +/* Perform a closest point query with a packet of 4 points with the scene. */ +RTC_API bool rtcPointQuery4(const int* valid, RTCScene scene, struct RTCPointQuery4* query, struct RTCPointQueryContext* context, RTCPointQueryFunction queryFunc, void** userPtr); + +/* Perform a closest point query with a packet of 4 points with the scene. */ +RTC_API bool rtcPointQuery8(const int* valid, RTCScene scene, struct RTCPointQuery8* query, struct RTCPointQueryContext* context, RTCPointQueryFunction queryFunc, void** userPtr); + +/* Perform a closest point query with a packet of 4 points with the scene. */ +RTC_API bool rtcPointQuery16(const int* valid, RTCScene scene, struct RTCPointQuery16* query, struct RTCPointQueryContext* context, RTCPointQueryFunction queryFunc, void** userPtr); + +/* Intersects a single ray with the scene. */ +RTC_API void rtcIntersect1(RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHit* rayhit); + +/* Intersects a packet of 4 rays with the scene. */ +RTC_API void rtcIntersect4(const int* valid, RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHit4* rayhit); + +/* Intersects a packet of 8 rays with the scene. */ +RTC_API void rtcIntersect8(const int* valid, RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHit8* rayhit); + +/* Intersects a packet of 16 rays with the scene. */ +RTC_API void rtcIntersect16(const int* valid, RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHit16* rayhit); + +/* Intersects a stream of M rays with the scene. */ +RTC_API void rtcIntersect1M(RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHit* rayhit, unsigned int M, size_t byteStride); + +/* Intersects a stream of pointers to M rays with the scene. */ +RTC_API void rtcIntersect1Mp(RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHit** rayhit, unsigned int M); + +/* Intersects a stream of M ray packets of size N in SOA format with the scene. */ +RTC_API void rtcIntersectNM(RTCScene scene, struct RTCIntersectContext* context, struct RTCRayHitN* rayhit, unsigned int N, unsigned int M, size_t byteStride); + +/* Intersects a stream of M ray packets of size N in SOA format with the scene. */ +RTC_API void rtcIntersectNp(RTCScene scene, struct RTCIntersectContext* context, const struct RTCRayHitNp* rayhit, unsigned int N); + +/* Tests a single ray for occlusion with the scene. */ +RTC_API void rtcOccluded1(RTCScene scene, struct RTCIntersectContext* context, struct RTCRay* ray); + +/* Tests a packet of 4 rays for occlusion occluded with the scene. */ +RTC_API void rtcOccluded4(const int* valid, RTCScene scene, struct RTCIntersectContext* context, struct RTCRay4* ray); + +/* Tests a packet of 8 rays for occlusion with the scene. */ +RTC_API void rtcOccluded8(const int* valid, RTCScene scene, struct RTCIntersectContext* context, struct RTCRay8* ray); + +/* Tests a packet of 16 rays for occlusion with the scene. */ +RTC_API void rtcOccluded16(const int* valid, RTCScene scene, struct RTCIntersectContext* context, struct RTCRay16* ray); + +/* Tests a stream of M rays for occlusion with the scene. */ +RTC_API void rtcOccluded1M(RTCScene scene, struct RTCIntersectContext* context, struct RTCRay* ray, unsigned int M, size_t byteStride); + +/* Tests a stream of pointers to M rays for occlusion with the scene. */ +RTC_API void rtcOccluded1Mp(RTCScene scene, struct RTCIntersectContext* context, struct RTCRay** ray, unsigned int M); + +/* Tests a stream of M ray packets of size N in SOA format for occlusion with the scene. */ +RTC_API void rtcOccludedNM(RTCScene scene, struct RTCIntersectContext* context, struct RTCRayN* ray, unsigned int N, unsigned int M, size_t byteStride); + +/* Tests a stream of M ray packets of size N in SOA format for occlusion with the scene. */ +RTC_API void rtcOccludedNp(RTCScene scene, struct RTCIntersectContext* context, const struct RTCRayNp* ray, unsigned int N); + +/*! collision callback */ +struct RTCCollision { unsigned int geomID0; unsigned int primID0; unsigned int geomID1; unsigned int primID1; }; +typedef void (*RTCCollideFunc) (void* userPtr, struct RTCCollision* collisions, unsigned int num_collisions); + +/*! Performs collision detection of two scenes */ +RTC_API void rtcCollide (RTCScene scene0, RTCScene scene1, RTCCollideFunc callback, void* userPtr); + +#if defined(__cplusplus) + +/* Helper for easily combining scene flags */ +inline RTCSceneFlags operator|(RTCSceneFlags a, RTCSceneFlags b) { + return (RTCSceneFlags)((size_t)a | (size_t)b); +} + +#endif + +RTC_NAMESPACE_END + diff --git a/thirdparty/embree/kernels/builders/bvh_builder_hair.h b/thirdparty/embree/kernels/builders/bvh_builder_hair.h new file mode 100644 index 000000000000..755ce255fb47 --- /dev/null +++ b/thirdparty/embree/kernels/builders/bvh_builder_hair.h @@ -0,0 +1,411 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../bvh/bvh.h" +#include "../geometry/primitive.h" +#include "../builders/bvh_builder_sah.h" +#include "../builders/heuristic_binning_array_aligned.h" +#include "../builders/heuristic_binning_array_unaligned.h" +#include "../builders/heuristic_strand_array.h" + +#define NUM_HAIR_OBJECT_BINS 32 + +namespace embree +{ + namespace isa + { + struct BVHBuilderHair + { + /*! settings for builder */ + struct Settings + { + /*! default settings */ + Settings () + : branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(7), finished_range_threshold(inf) {} + + public: + size_t branchingFactor; //!< branching factor of BVH to build + size_t maxDepth; //!< maximum depth of BVH to build + size_t logBlockSize; //!< log2 of blocksize for SAH heuristic + size_t minLeafSize; //!< minimum size of a leaf + size_t maxLeafSize; //!< maximum size of a leaf + size_t finished_range_threshold; //!< finished range threshold + }; + + template + + class BuilderT + { + ALIGNED_CLASS_(16); + friend struct BVHBuilderHair; + + typedef FastAllocator::CachedAllocator Allocator; + typedef HeuristicArrayBinningSAH HeuristicBinningSAH; + typedef UnalignedHeuristicArrayBinningSAH UnalignedHeuristicBinningSAH; + typedef HeuristicStrandSplit HeuristicStrandSplitSAH; + + static const size_t MAX_BRANCHING_FACTOR = 8; //!< maximum supported BVH branching factor + static const size_t MIN_LARGE_LEAF_LEVELS = 8; //!< create balanced tree if we are that many levels before the maximum tree depth + static const size_t SINGLE_THREADED_THRESHOLD = 4096; //!< threshold to switch to single threaded build + + static const size_t travCostAligned = 1; + static const size_t travCostUnaligned = 5; + static const size_t intCost = 6; + + BuilderT (Scene* scene, + PrimRef* prims, + const CreateAllocFunc& createAlloc, + const CreateAABBNodeFunc& createAABBNode, + const SetAABBNodeFunc& setAABBNode, + const CreateOBBNodeFunc& createOBBNode, + const SetOBBNodeFunc& setOBBNode, + const CreateLeafFunc& createLeaf, + const ProgressMonitor& progressMonitor, + const ReportFinishedRangeFunc& reportFinishedRange, + const Settings settings) + + : cfg(settings), + prims(prims), + createAlloc(createAlloc), + createAABBNode(createAABBNode), + setAABBNode(setAABBNode), + createOBBNode(createOBBNode), + setOBBNode(setOBBNode), + createLeaf(createLeaf), + progressMonitor(progressMonitor), + reportFinishedRange(reportFinishedRange), + alignedHeuristic(prims), unalignedHeuristic(scene,prims), strandHeuristic(scene,prims) {} + + /*! checks if all primitives are from the same geometry */ + __forceinline bool sameGeometry(const PrimInfoRange& range) + { + if (range.size() == 0) return true; + unsigned int firstGeomID = prims[range.begin()].geomID(); + for (size_t i=range.begin()+1; i cfg.maxDepth) + throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached"); + + /* create leaf for few primitives */ + if (pinfo.size() <= cfg.maxLeafSize && sameGeometry(pinfo)) + return createLeaf(prims,pinfo,alloc); + + /* fill all children by always splitting the largest one */ + PrimInfoRange children[MAX_BRANCHING_FACTOR]; + unsigned numChildren = 1; + children[0] = pinfo; + + do { + + /* find best child with largest bounding box area */ + int bestChild = -1; + size_t bestSize = 0; + for (unsigned i=0; i bestSize) { + bestSize = children[i].size(); + bestChild = i; + } + } + if (bestChild == -1) break; + + /*! split best child into left and right child */ + __aligned(64) PrimInfoRange left, right; + if (!sameGeometry(children[bestChild])) { + alignedHeuristic.splitByGeometry(children[bestChild],left,right); + } else { + alignedHeuristic.splitFallback(children[bestChild],left,right); + } + + /* add new children left and right */ + children[bestChild] = children[numChildren-1]; + children[numChildren-1] = left; + children[numChildren+0] = right; + numChildren++; + + } while (numChildren < cfg.branchingFactor); + + /* create node */ + auto node = createAABBNode(alloc); + + for (size_t i=0; i> cfg.logBlockSize; + const float leafSAH = intCost*float(blocks)*halfArea(pinfo.geomBounds); + + /* try standard binning in aligned space */ + float alignedObjectSAH = inf; + HeuristicBinningSAH::Split alignedObjectSplit; + if (aligned) { + alignedObjectSplit = alignedHeuristic.find(pinfo,cfg.logBlockSize); + alignedObjectSAH = travCostAligned*halfArea(pinfo.geomBounds) + intCost*alignedObjectSplit.splitSAH(); + bestSAH = min(alignedObjectSAH,bestSAH); + } + + /* try standard binning in unaligned space */ + UnalignedHeuristicBinningSAH::Split unalignedObjectSplit; + LinearSpace3fa uspace; + float unalignedObjectSAH = inf; + if (bestSAH > 0.7f*leafSAH) { + uspace = unalignedHeuristic.computeAlignedSpace(pinfo); + const PrimInfoRange sinfo = unalignedHeuristic.computePrimInfo(pinfo,uspace); + unalignedObjectSplit = unalignedHeuristic.find(sinfo,cfg.logBlockSize,uspace); + unalignedObjectSAH = travCostUnaligned*halfArea(pinfo.geomBounds) + intCost*unalignedObjectSplit.splitSAH(); + bestSAH = min(unalignedObjectSAH,bestSAH); + } + + /* try splitting into two strands */ + HeuristicStrandSplitSAH::Split strandSplit; + float strandSAH = inf; + if (bestSAH > 0.7f*leafSAH && pinfo.size() <= 256) { + strandSplit = strandHeuristic.find(pinfo,cfg.logBlockSize); + strandSAH = travCostUnaligned*halfArea(pinfo.geomBounds) + intCost*strandSplit.splitSAH(); + bestSAH = min(strandSAH,bestSAH); + } + + /* fallback if SAH heuristics failed */ + if (unlikely(!std::isfinite(bestSAH))) + { + alignedHeuristic.deterministic_order(pinfo); + alignedHeuristic.splitFallback(pinfo,linfo,rinfo); + } + + /* perform aligned split if this is best */ + else if (bestSAH == alignedObjectSAH) { + alignedHeuristic.split(alignedObjectSplit,pinfo,linfo,rinfo); + } + + /* perform unaligned split if this is best */ + else if (bestSAH == unalignedObjectSAH) { + unalignedHeuristic.split(unalignedObjectSplit,uspace,pinfo,linfo,rinfo); + aligned = false; + } + + /* perform strand split if this is best */ + else if (bestSAH == strandSAH) { + strandHeuristic.split(strandSplit,pinfo,linfo,rinfo); + aligned = false; + } + + /* can never happen */ + else + assert(false); + } + + /*! recursive build */ + NodeRef recurse(size_t depth, const PrimInfoRange& pinfo, Allocator alloc, bool toplevel, bool alloc_barrier) + { + /* get thread local allocator */ + if (!alloc) + alloc = createAlloc(); + + /* call memory monitor function to signal progress */ + if (toplevel && pinfo.size() <= SINGLE_THREADED_THRESHOLD) + progressMonitor(pinfo.size()); + + PrimInfoRange children[MAX_BRANCHING_FACTOR]; + + /* create leaf node */ + if (depth+MIN_LARGE_LEAF_LEVELS >= cfg.maxDepth || pinfo.size() <= cfg.minLeafSize) { + alignedHeuristic.deterministic_order(pinfo); + return createLargeLeaf(depth,pinfo,alloc); + } + + /* fill all children by always splitting the one with the largest surface area */ + size_t numChildren = 1; + children[0] = pinfo; + bool aligned = true; + + do { + + /* find best child with largest bounding box area */ + ssize_t bestChild = -1; + float bestArea = neg_inf; + for (size_t i=0; i bestArea) { + bestArea = area(children[i].geomBounds); + bestChild = i; + } + } + if (bestChild == -1) break; + + /*! split best child into left and right child */ + PrimInfoRange left, right; + split(children[bestChild],left,right,aligned); + + /* add new children left and right */ + children[bestChild] = children[numChildren-1]; + children[numChildren-1] = left; + children[numChildren+0] = right; + numChildren++; + + } while (numChildren < cfg.branchingFactor); + + NodeRef node; + + /* create aligned node */ + if (aligned) + { + node = createAABBNode(alloc); + + /* spawn tasks or ... */ + if (pinfo.size() > SINGLE_THREADED_THRESHOLD) + { + parallel_for(size_t(0), numChildren, [&] (const range& r) { + for (size_t i=r.begin(); i cfg.finished_range_threshold && children[i].size() <= cfg.finished_range_threshold; + setAABBNode(node,i,recurse(depth+1,children[i],nullptr,true,child_alloc_barrier),children[i].geomBounds); + _mm_mfence(); // to allow non-temporal stores during build + } + }); + } + /* ... continue sequentially */ + else { + for (size_t i=0; i cfg.finished_range_threshold && children[i].size() <= cfg.finished_range_threshold; + setAABBNode(node,i,recurse(depth+1,children[i],alloc,false,child_alloc_barrier),children[i].geomBounds); + } + } + } + + /* create unaligned node */ + else + { + node = createOBBNode(alloc); + + /* spawn tasks or ... */ + if (pinfo.size() > SINGLE_THREADED_THRESHOLD) + { + parallel_for(size_t(0), numChildren, [&] (const range& r) { + for (size_t i=r.begin(); i cfg.finished_range_threshold && children[i].size() <= cfg.finished_range_threshold; + setOBBNode(node,i,recurse(depth+1,children[i],nullptr,true,child_alloc_barrier),obounds); + _mm_mfence(); // to allow non-temporal stores during build + } + }); + } + /* ... continue sequentially */ + else + { + for (size_t i=0; i cfg.finished_range_threshold && children[i].size() <= cfg.finished_range_threshold; + setOBBNode(node,i,recurse(depth+1,children[i],alloc,false,child_alloc_barrier),obounds); + } + } + } + + /* reports a finished range of primrefs */ + if (unlikely(alloc_barrier)) + reportFinishedRange(pinfo); + + return node; + } + + private: + Settings cfg; + PrimRef* prims; + const CreateAllocFunc& createAlloc; + const CreateAABBNodeFunc& createAABBNode; + const SetAABBNodeFunc& setAABBNode; + const CreateOBBNodeFunc& createOBBNode; + const SetOBBNodeFunc& setOBBNode; + const CreateLeafFunc& createLeaf; + const ProgressMonitor& progressMonitor; + const ReportFinishedRangeFunc& reportFinishedRange; + + private: + HeuristicBinningSAH alignedHeuristic; + UnalignedHeuristicBinningSAH unalignedHeuristic; + HeuristicStrandSplitSAH strandHeuristic; + }; + + template + + static NodeRef build (const CreateAllocFunc& createAlloc, + const CreateAABBNodeFunc& createAABBNode, + const SetAABBNodeFunc& setAABBNode, + const CreateOBBNodeFunc& createOBBNode, + const SetOBBNodeFunc& setOBBNode, + const CreateLeafFunc& createLeaf, + const ProgressMonitor& progressMonitor, + const ReportFinishedRangeFunc& reportFinishedRange, + Scene* scene, + PrimRef* prims, + const PrimInfo& pinfo, + const Settings settings) + { + typedef BuilderT Builder; + + Builder builder(scene,prims,createAlloc, + createAABBNode,setAABBNode, + createOBBNode,setOBBNode, + createLeaf,progressMonitor,reportFinishedRange,settings); + + NodeRef root = builder.recurse(1,pinfo,nullptr,true,false); + _mm_mfence(); // to allow non-temporal stores during build + return root; + } + }; + } +} diff --git a/thirdparty/embree/kernels/builders/bvh_builder_morton.h b/thirdparty/embree/kernels/builders/bvh_builder_morton.h new file mode 100644 index 000000000000..92be2f7e65c2 --- /dev/null +++ b/thirdparty/embree/kernels/builders/bvh_builder_morton.h @@ -0,0 +1,501 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/builder.h" +#include "../../common/algorithms/parallel_reduce.h" + +namespace embree +{ + namespace isa + { + struct BVHBuilderMorton + { + static const size_t MAX_BRANCHING_FACTOR = 8; //!< maximum supported BVH branching factor + static const size_t MIN_LARGE_LEAF_LEVELS = 8; //!< create balanced tree of we are that many levels before the maximum tree depth + + /*! settings for morton builder */ + struct Settings + { + /*! default settings */ + Settings () + : branchingFactor(2), maxDepth(32), minLeafSize(1), maxLeafSize(7), singleThreadThreshold(1024) {} + + /*! initialize settings from API settings */ + Settings (const RTCBuildArguments& settings) + : branchingFactor(2), maxDepth(32), minLeafSize(1), maxLeafSize(7), singleThreadThreshold(1024) + { + if (RTC_BUILD_ARGUMENTS_HAS(settings,maxBranchingFactor)) branchingFactor = settings.maxBranchingFactor; + if (RTC_BUILD_ARGUMENTS_HAS(settings,maxDepth )) maxDepth = settings.maxDepth; + if (RTC_BUILD_ARGUMENTS_HAS(settings,minLeafSize )) minLeafSize = settings.minLeafSize; + if (RTC_BUILD_ARGUMENTS_HAS(settings,maxLeafSize )) maxLeafSize = settings.maxLeafSize; + + minLeafSize = min(minLeafSize,maxLeafSize); + } + + Settings (size_t branchingFactor, size_t maxDepth, size_t minLeafSize, size_t maxLeafSize, size_t singleThreadThreshold) + : branchingFactor(branchingFactor), maxDepth(maxDepth), minLeafSize(minLeafSize), maxLeafSize(maxLeafSize), singleThreadThreshold(singleThreadThreshold) + { + minLeafSize = min(minLeafSize,maxLeafSize); + } + + public: + size_t branchingFactor; //!< branching factor of BVH to build + size_t maxDepth; //!< maximum depth of BVH to build + size_t minLeafSize; //!< minimum size of a leaf + size_t maxLeafSize; //!< maximum size of a leaf + size_t singleThreadThreshold; //!< threshold when we switch to single threaded build + }; + + /*! Build primitive consisting of morton code and primitive ID. */ + struct __aligned(8) BuildPrim + { + union { + struct { + unsigned int code; //!< morton code + unsigned int index; //!< i'th primitive + }; + uint64_t t; + }; + + /*! interface for radix sort */ + __forceinline operator unsigned() const { return code; } + + /*! interface for standard sort */ + __forceinline bool operator<(const BuildPrim &m) const { return code < m.code; } + }; + + /*! maps bounding box to morton code */ + struct MortonCodeMapping + { + static const size_t LATTICE_BITS_PER_DIM = 10; + static const size_t LATTICE_SIZE_PER_DIM = size_t(1) << LATTICE_BITS_PER_DIM; + + vfloat4 base; + vfloat4 scale; + + __forceinline MortonCodeMapping(const BBox3fa& bounds) + { + base = (vfloat4)bounds.lower; + const vfloat4 diag = (vfloat4)bounds.upper - (vfloat4)bounds.lower; + scale = select(diag > vfloat4(1E-19f), rcp(diag) * vfloat4(LATTICE_SIZE_PER_DIM * 0.99f),vfloat4(0.0f)); + } + + __forceinline const vint4 bin (const BBox3fa& box) const + { + const vfloat4 lower = (vfloat4)box.lower; + const vfloat4 upper = (vfloat4)box.upper; + const vfloat4 centroid = lower+upper; + return vint4((centroid-base)*scale); + } + + __forceinline unsigned int code (const BBox3fa& box) const + { + const vint4 binID = bin(box); + const unsigned int x = extract<0>(binID); + const unsigned int y = extract<1>(binID); + const unsigned int z = extract<2>(binID); + const unsigned int xyz = bitInterleave(x,y,z); + return xyz; + } + }; + +#if defined (__AVX2__) + + /*! for AVX2 there is a fast scalar bitInterleave */ + struct MortonCodeGenerator + { + __forceinline MortonCodeGenerator(const MortonCodeMapping& mapping, BuildPrim* dest) + : mapping(mapping), dest(dest) {} + + __forceinline void operator() (const BBox3fa& b, const unsigned index) + { + dest->index = index; + dest->code = mapping.code(b); + dest++; + } + + public: + const MortonCodeMapping mapping; + BuildPrim* dest; + size_t currentID; + }; + +#else + + /*! before AVX2 is it better to use the SSE version of bitInterleave */ + struct MortonCodeGenerator + { + __forceinline MortonCodeGenerator(const MortonCodeMapping& mapping, BuildPrim* dest) + : mapping(mapping), dest(dest), currentID(0), slots(0), ax(0), ay(0), az(0), ai(0) {} + + __forceinline ~MortonCodeGenerator() + { + if (slots != 0) + { + const vint4 code = bitInterleave(ax,ay,az); + for (size_t i=0; i(binID); + ay[slots] = extract<1>(binID); + az[slots] = extract<2>(binID); + ai[slots] = index; + slots++; + currentID++; + + if (slots == 4) + { + const vint4 code = bitInterleave(ax,ay,az); + vint4::storeu(&dest[currentID-4],unpacklo(code,ai)); + vint4::storeu(&dest[currentID-2],unpackhi(code,ai)); + slots = 0; + } + } + + public: + const MortonCodeMapping mapping; + BuildPrim* dest; + size_t currentID; + size_t slots; + vint4 ax, ay, az, ai; + }; + +#endif + + template< + typename ReductionTy, + typename Allocator, + typename CreateAllocator, + typename CreateNodeFunc, + typename SetNodeBoundsFunc, + typename CreateLeafFunc, + typename CalculateBounds, + typename ProgressMonitor> + + class BuilderT : private Settings + { + ALIGNED_CLASS_(16); + + public: + + BuilderT (CreateAllocator& createAllocator, + CreateNodeFunc& createNode, + SetNodeBoundsFunc& setBounds, + CreateLeafFunc& createLeaf, + CalculateBounds& calculateBounds, + ProgressMonitor& progressMonitor, + const Settings& settings) + + : Settings(settings), + createAllocator(createAllocator), + createNode(createNode), + setBounds(setBounds), + createLeaf(createLeaf), + calculateBounds(calculateBounds), + progressMonitor(progressMonitor), + morton(nullptr) {} + + ReductionTy createLargeLeaf(size_t depth, const range& current, Allocator alloc) + { + /* this should never occur but is a fatal error */ + if (depth > maxDepth) + throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached"); + + /* create leaf for few primitives */ + if (current.size() <= maxLeafSize) + return createLeaf(current,alloc); + + /* fill all children by always splitting the largest one */ + range children[MAX_BRANCHING_FACTOR]; + size_t numChildren = 1; + children[0] = current; + + do { + + /* find best child with largest number of primitives */ + size_t bestChild = -1; + size_t bestSize = 0; + for (size_t i=0; i bestSize) { + bestSize = children[i].size(); + bestChild = i; + } + } + if (bestChild == size_t(-1)) break; + + /*! split best child into left and right child */ + auto split = children[bestChild].split(); + + /* add new children left and right */ + children[bestChild] = children[numChildren-1]; + children[numChildren-1] = split.first; + children[numChildren+0] = split.second; + numChildren++; + + } while (numChildren < branchingFactor); + + /* create node */ + auto node = createNode(alloc,numChildren); + + /* recurse into each child */ + ReductionTy bounds[MAX_BRANCHING_FACTOR]; + for (size_t i=0; i& current) const + { + /* fast path for small ranges */ + if (likely(current.size() < 1024)) + { + /*! recalculate centroid bounds */ + BBox3fa centBounds(empty); + for (size_t i=current.begin(); i& r ) { + BBox3fa centBounds = empty; + for (size_t i=r.begin(); i& r ) { + for (size_t i=r.begin(); i& current, range& left, range& right) const + { + const unsigned int code_start = morton[current.begin()].code; + const unsigned int code_end = morton[current.end()-1].code; + unsigned int bitpos = lzcnt(code_start^code_end); + + /* if all items mapped to same morton code, then re-create new morton codes for the items */ + if (unlikely(bitpos == 32)) + { + recreateMortonCodes(current); + const unsigned int code_start = morton[current.begin()].code; + const unsigned int code_end = morton[current.end()-1].code; + bitpos = lzcnt(code_start^code_end); + + /* if the morton code is still the same, goto fall back split */ + if (unlikely(bitpos == 32)) { + current.split(left,right); + return; + } + } + + /* split the items at the topmost different morton code bit */ + const unsigned int bitpos_diff = 31-bitpos; + const unsigned int bitmask = 1 << bitpos_diff; + + /* find location where bit differs using binary search */ + unsigned begin = current.begin(); + unsigned end = current.end(); + while (begin + 1 != end) { + const unsigned mid = (begin+end)/2; + const unsigned bit = morton[mid].code & bitmask; + if (bit == 0) begin = mid; else end = mid; + } + unsigned center = end; +#if defined(DEBUG) + for (unsigned int i=begin; i& current, Allocator alloc, bool toplevel) + { + /* get thread local allocator */ + if (!alloc) + alloc = createAllocator(); + + /* call memory monitor function to signal progress */ + if (toplevel && current.size() <= singleThreadThreshold) + progressMonitor(current.size()); + + /* create leaf node */ + if (unlikely(depth+MIN_LARGE_LEAF_LEVELS >= maxDepth || current.size() <= minLeafSize)) + return createLargeLeaf(depth,current,alloc); + + /* fill all children by always splitting the one with the largest surface area */ + range children[MAX_BRANCHING_FACTOR]; + split(current,children[0],children[1]); + size_t numChildren = 2; + + while (numChildren < branchingFactor) + { + /* find best child with largest number of primitives */ + int bestChild = -1; + unsigned bestItems = 0; + for (unsigned int i=0; i bestItems) { + bestItems = children[i].size(); + bestChild = i; + } + } + if (bestChild == -1) break; + + /*! split best child into left and right child */ + range left, right; + split(children[bestChild],left,right); + + /* add new children left and right */ + children[bestChild] = children[numChildren-1]; + children[numChildren-1] = left; + children[numChildren+0] = right; + numChildren++; + } + + /* create leaf node if no split is possible */ + if (unlikely(numChildren == 1)) + return createLeaf(current,alloc); + + /* allocate node */ + auto node = createNode(alloc,numChildren); + + /* process top parts of tree parallel */ + ReductionTy bounds[MAX_BRANCHING_FACTOR]; + if (current.size() > singleThreadThreshold) + { + /*! parallel_for is faster than spawing sub-tasks */ + parallel_for(size_t(0), numChildren, [&] (const range& r) { + for (size_t i=r.begin(); i(0,(unsigned)numPrimitives), nullptr, true); + _mm_mfence(); // to allow non-temporal stores during build + return root; + } + + public: + CreateAllocator& createAllocator; + CreateNodeFunc& createNode; + SetNodeBoundsFunc& setBounds; + CreateLeafFunc& createLeaf; + CalculateBounds& calculateBounds; + ProgressMonitor& progressMonitor; + + public: + BuildPrim* morton; + }; + + + template< + typename ReductionTy, + typename CreateAllocFunc, + typename CreateNodeFunc, + typename SetBoundsFunc, + typename CreateLeafFunc, + typename CalculateBoundsFunc, + typename ProgressMonitor> + + static ReductionTy build(CreateAllocFunc createAllocator, + CreateNodeFunc createNode, + SetBoundsFunc setBounds, + CreateLeafFunc createLeaf, + CalculateBoundsFunc calculateBounds, + ProgressMonitor progressMonitor, + BuildPrim* src, + BuildPrim* tmp, + size_t numPrimitives, + const Settings& settings) + { + typedef BuilderT< + ReductionTy, + decltype(createAllocator()), + CreateAllocFunc, + CreateNodeFunc, + SetBoundsFunc, + CreateLeafFunc, + CalculateBoundsFunc, + ProgressMonitor> Builder; + + Builder builder(createAllocator, + createNode, + setBounds, + createLeaf, + calculateBounds, + progressMonitor, + settings); + + return builder.build(src,tmp,numPrimitives); + } + }; + } +} diff --git a/thirdparty/embree/kernels/builders/bvh_builder_msmblur.h b/thirdparty/embree/kernels/builders/bvh_builder_msmblur.h new file mode 100644 index 000000000000..4c138dacdbd4 --- /dev/null +++ b/thirdparty/embree/kernels/builders/bvh_builder_msmblur.h @@ -0,0 +1,692 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define MBLUR_NUM_TEMPORAL_BINS 2 +#define MBLUR_NUM_OBJECT_BINS 32 + +#include "../bvh/bvh.h" +#include "../common/primref_mb.h" +#include "heuristic_binning_array_aligned.h" +#include "heuristic_timesplit_array.h" + +namespace embree +{ + namespace isa + { + template + struct SharedVector + { + __forceinline SharedVector() {} + + __forceinline SharedVector(T* ptr, size_t refCount = 1) + : prims(ptr), refCount(refCount) {} + + __forceinline void incRef() { + refCount++; + } + + __forceinline void decRef() + { + if (--refCount == 0) + delete prims; + } + + T* prims; + size_t refCount; + }; + + template + struct LocalChildListT + { + typedef SharedVector> SharedPrimRefVector; + + __forceinline LocalChildListT (const BuildRecord& record) + : numChildren(1), numSharedPrimVecs(1) + { + /* the local root will be freed in the ancestor where it was created (thus refCount is 2) */ + children[0] = record; + primvecs[0] = new (&sharedPrimVecs[0]) SharedPrimRefVector(record.prims.prims, 2); + } + + __forceinline ~LocalChildListT() + { + for (size_t i = 0; i < numChildren; i++) + primvecs[i]->decRef(); + } + + __forceinline BuildRecord& operator[] ( const size_t i ) { + return children[i]; + } + + __forceinline size_t size() const { + return numChildren; + } + + __forceinline void split(ssize_t bestChild, const BuildRecord& lrecord, const BuildRecord& rrecord, std::unique_ptr> new_vector) + { + SharedPrimRefVector* bsharedPrimVec = primvecs[bestChild]; + if (lrecord.prims.prims == bsharedPrimVec->prims) { + primvecs[bestChild] = bsharedPrimVec; + bsharedPrimVec->incRef(); + } + else { + primvecs[bestChild] = new (&sharedPrimVecs[numSharedPrimVecs++]) SharedPrimRefVector(lrecord.prims.prims); + } + + if (rrecord.prims.prims == bsharedPrimVec->prims) { + primvecs[numChildren] = bsharedPrimVec; + bsharedPrimVec->incRef(); + } + else { + primvecs[numChildren] = new (&sharedPrimVecs[numSharedPrimVecs++]) SharedPrimRefVector(rrecord.prims.prims); + } + bsharedPrimVec->decRef(); + new_vector.release(); + + children[bestChild] = lrecord; + children[numChildren] = rrecord; + numChildren++; + } + + public: + array_t children; + array_t primvecs; + size_t numChildren; + + array_t sharedPrimVecs; + size_t numSharedPrimVecs; + }; + + template + struct RecalculatePrimRef + { + Scene* scene; + + __forceinline RecalculatePrimRef (Scene* scene) + : scene(scene) {} + + __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range) const + { + const unsigned geomID = prim.geomID(); + const unsigned primID = prim.primID(); + const Mesh* mesh = scene->get(geomID); + const LBBox3fa lbounds = mesh->linearBounds(primID, time_range); + const range tbounds = mesh->timeSegmentRange(time_range); + return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID); + } + + // __noinline is workaround for ICC16 bug under MacOSX + __noinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const + { + const unsigned geomID = prim.geomID(); + const unsigned primID = prim.primID(); + const Mesh* mesh = scene->get(geomID); + const LBBox3fa lbounds = mesh->linearBounds(space, primID, time_range); + const range tbounds = mesh->timeSegmentRange(time_range); + return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID); + } + + __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range) const { + return scene->get(prim.geomID())->linearBounds(prim.primID(), time_range); + } + + // __noinline is workaround for ICC16 bug under MacOSX + __noinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const { + return scene->get(prim.geomID())->linearBounds(space, prim.primID(), time_range); + } + }; + + struct VirtualRecalculatePrimRef + { + Scene* scene; + + __forceinline VirtualRecalculatePrimRef (Scene* scene) + : scene(scene) {} + + __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range) const + { + const unsigned geomID = prim.geomID(); + const unsigned primID = prim.primID(); + const Geometry* mesh = scene->get(geomID); + const LBBox3fa lbounds = mesh->vlinearBounds(primID, time_range); + const range tbounds = mesh->timeSegmentRange(time_range); + return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID); + } + + __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const + { + const unsigned geomID = prim.geomID(); + const unsigned primID = prim.primID(); + const Geometry* mesh = scene->get(geomID); + const LBBox3fa lbounds = mesh->vlinearBounds(space, primID, time_range); + const range tbounds = mesh->timeSegmentRange(time_range); + return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID); + } + + __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range) const { + return scene->get(prim.geomID())->vlinearBounds(prim.primID(), time_range); + } + + __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const { + return scene->get(prim.geomID())->vlinearBounds(space, prim.primID(), time_range); + } + }; + + struct BVHBuilderMSMBlur + { + /*! settings for msmblur builder */ + struct Settings + { + /*! default settings */ + Settings () + : branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(8), + travCost(1.0f), intCost(1.0f), singleLeafTimeSegment(false), + singleThreadThreshold(1024) {} + + + Settings (size_t sahBlockSize, size_t minLeafSize, size_t maxLeafSize, float travCost, float intCost, size_t singleThreadThreshold) + : branchingFactor(2), maxDepth(32), logBlockSize(bsr(sahBlockSize)), minLeafSize(minLeafSize), maxLeafSize(maxLeafSize), + travCost(travCost), intCost(intCost), singleThreadThreshold(singleThreadThreshold) + { + minLeafSize = min(minLeafSize,maxLeafSize); + } + + public: + size_t branchingFactor; //!< branching factor of BVH to build + size_t maxDepth; //!< maximum depth of BVH to build + size_t logBlockSize; //!< log2 of blocksize for SAH heuristic + size_t minLeafSize; //!< minimum size of a leaf + size_t maxLeafSize; //!< maximum size of a leaf + float travCost; //!< estimated cost of one traversal step + float intCost; //!< estimated cost of one primitive intersection + bool singleLeafTimeSegment; //!< split time to single time range + size_t singleThreadThreshold; //!< threshold when we switch to single threaded build + }; + + struct BuildRecord + { + public: + __forceinline BuildRecord () {} + + __forceinline BuildRecord (size_t depth) + : depth(depth) {} + + __forceinline BuildRecord (const SetMB& prims, size_t depth) + : depth(depth), prims(prims) {} + + __forceinline friend bool operator< (const BuildRecord& a, const BuildRecord& b) { + return a.prims.size() < b.prims.size(); + } + + __forceinline size_t size() const { + return prims.size(); + } + + public: + size_t depth; //!< Depth of the root of this subtree. + SetMB prims; //!< The list of primitives. + }; + + struct BuildRecordSplit : public BuildRecord + { + __forceinline BuildRecordSplit () {} + + __forceinline BuildRecordSplit (size_t depth) + : BuildRecord(depth) {} + + __forceinline BuildRecordSplit (const BuildRecord& record, const BinSplit& split) + : BuildRecord(record), split(split) {} + + BinSplit split; + }; + + template< + typename NodeRef, + typename RecalculatePrimRef, + typename Allocator, + typename CreateAllocFunc, + typename CreateNodeFunc, + typename SetNodeFunc, + typename CreateLeafFunc, + typename ProgressMonitor> + + class BuilderT + { + ALIGNED_CLASS_(16); + static const size_t MAX_BRANCHING_FACTOR = 16; //!< maximum supported BVH branching factor + static const size_t MIN_LARGE_LEAF_LEVELS = 8; //!< create balanced tree if we are that many levels before the maximum tree depth + + typedef BVHNodeRecordMB4D NodeRecordMB4D; + typedef BinSplit Split; + typedef mvector* PrimRefVector; + typedef SharedVector> SharedPrimRefVector; + typedef LocalChildListT LocalChildList; + typedef LocalChildListT LocalChildListSplit; + + public: + + BuilderT (MemoryMonitorInterface* device, + const RecalculatePrimRef recalculatePrimRef, + const CreateAllocFunc createAlloc, + const CreateNodeFunc createNode, + const SetNodeFunc setNode, + const CreateLeafFunc createLeaf, + const ProgressMonitor progressMonitor, + const Settings& settings) + : cfg(settings), + heuristicObjectSplit(), + heuristicTemporalSplit(device, recalculatePrimRef), + recalculatePrimRef(recalculatePrimRef), createAlloc(createAlloc), createNode(createNode), setNode(setNode), createLeaf(createLeaf), + progressMonitor(progressMonitor) + { + if (cfg.branchingFactor > MAX_BRANCHING_FACTOR) + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } + + /*! finds the best split */ + const Split find(const SetMB& set) + { + /* first try standard object split */ + const Split object_split = heuristicObjectSplit.find(set,cfg.logBlockSize); + const float object_split_sah = object_split.splitSAH(); + + /* test temporal splits only when object split was bad */ + const float leaf_sah = set.leafSAH(cfg.logBlockSize); + if (object_split_sah < 0.50f*leaf_sah) + return object_split; + + /* do temporal splits only if the the time range is big enough */ + if (set.time_range.size() > 1.01f/float(set.max_num_time_segments)) + { + const Split temporal_split = heuristicTemporalSplit.find(set,cfg.logBlockSize); + const float temporal_split_sah = temporal_split.splitSAH(); + + /* take temporal split if it improved SAH */ + if (temporal_split_sah < object_split_sah) + return temporal_split; + } + + return object_split; + } + + /*! array partitioning */ + __forceinline std::unique_ptr> split(const Split& split, const SetMB& set, SetMB& lset, SetMB& rset) + { + /* perform object split */ + if (likely(split.data == Split::SPLIT_OBJECT)) { + heuristicObjectSplit.split(split,set,lset,rset); + } + /* perform temporal split */ + else if (likely(split.data == Split::SPLIT_TEMPORAL)) { + return heuristicTemporalSplit.split(split,set,lset,rset); + } + /* perform fallback split */ + else if (unlikely(split.data == Split::SPLIT_FALLBACK)) { + set.deterministic_order(); + splitFallback(set,lset,rset); + } + /* split by geometry */ + else if (unlikely(split.data == Split::SPLIT_GEOMID)) { + set.deterministic_order(); + splitByGeometry(set,lset,rset); + } + else + assert(false); + + return std::unique_ptr>(); + } + + /*! finds the best fallback split */ + __noinline Split findFallback(const SetMB& set) + { + /* split if primitives are not from same geometry */ + if (!sameGeometry(set)) + return Split(0.0f,Split::SPLIT_GEOMID); + + /* if a leaf can only hold a single time-segment, we might have to do additional temporal splits */ + if (cfg.singleLeafTimeSegment) + { + /* test if one primitive has more than one time segment in time range, if so split time */ + for (size_t i=set.begin(); i itime_range = prim.timeSegmentRange(set.time_range); + const int localTimeSegments = itime_range.size(); + assert(localTimeSegments > 0); + if (localTimeSegments > 1) { + const int icenter = (itime_range.begin() + itime_range.end())/2; + const float splitTime = prim.timeStep(icenter); + return Split(0.0f,(unsigned)Split::SPLIT_TEMPORAL,0,splitTime); + } + } + } + + /* otherwise return fallback split */ + return Split(0.0f,Split::SPLIT_FALLBACK); + } + + /*! performs fallback split */ + void splitFallback(const SetMB& set, SetMB& lset, SetMB& rset) + { + mvector& prims = *set.prims; + + const size_t begin = set.begin(); + const size_t end = set.end(); + const size_t center = (begin + end)/2; + + PrimInfoMB linfo = empty; + for (size_t i=begin; i(begin,center),set.time_range); + new (&rset) SetMB(rinfo,set.prims,range(center,end ),set.time_range); + } + + /*! checks if all primitives are from the same geometry */ + __forceinline bool sameGeometry(const SetMB& set) + { + if (set.size() == 0) return true; + mvector& prims = *set.prims; + const size_t begin = set.begin(); + const size_t end = set.end(); + unsigned int firstGeomID = prims[begin].geomID(); + for (size_t i=begin+1; i 1); + + mvector& prims = *set.prims; + const size_t begin = set.begin(); + const size_t end = set.end(); + + PrimInfoMB left(empty); + PrimInfoMB right(empty); + unsigned int geomID = prims[begin].geomID(); + size_t center = serial_partitioning(prims.data(),begin,end,left,right, + [&] ( const PrimRefMB& prim ) { return prim.geomID() == geomID; }, + [ ] ( PrimInfoMB& dst, const PrimRefMB& prim ) { dst.add_primref(prim); }); + + new (&lset) SetMB(left, set.prims,range(begin,center),set.time_range); + new (&rset) SetMB(right,set.prims,range(center,end ),set.time_range); + } + + const NodeRecordMB4D createLargeLeaf(const BuildRecord& in, Allocator alloc) + { + /* this should never occur but is a fatal error */ + if (in.depth > cfg.maxDepth) + throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached"); + + /* replace already found split by fallback split */ + const BuildRecordSplit current(BuildRecord(in.prims,in.depth),findFallback(in.prims)); + + /* special case when directly creating leaf without any splits that could shrink time_range */ + bool force_split = false; + if (current.depth == 1 && current.size() > 0) + { + BBox1f c = empty; + BBox1f p = current.prims.time_range; + for (size_t i=current.prims.begin(); i& prims = *current.prims.prims; + c.extend(prims[i].time_range); + } + + force_split = c.lower > p.lower || c.upper < p.upper; + } + + /* create leaf for few primitives */ + if (current.size() <= cfg.maxLeafSize && current.split.data < Split::SPLIT_ENFORCE && !force_split) + return createLeaf(current,alloc); + + /* fill all children by always splitting the largest one */ + bool hasTimeSplits = false; + NodeRecordMB4D values[MAX_BRANCHING_FACTOR]; + LocalChildListSplit children(current); + + do { + /* find best child with largest bounding box area */ + size_t bestChild = -1; + size_t bestSize = 0; + for (size_t i=0; i bestSize) { + bestSize = children[i].size(); + bestChild = i; + } + } + if (bestChild == -1) break; + + /* perform best found split */ + BuildRecordSplit& brecord = children[bestChild]; + BuildRecordSplit lrecord(current.depth+1); + BuildRecordSplit rrecord(current.depth+1); + std::unique_ptr> new_vector = split(brecord.split,brecord.prims,lrecord.prims,rrecord.prims); + hasTimeSplits |= new_vector != nullptr; + + /* find new splits */ + lrecord.split = findFallback(lrecord.prims); + rrecord.split = findFallback(rrecord.prims); + children.split(bestChild,lrecord,rrecord,std::move(new_vector)); + + } while (children.size() < cfg.branchingFactor); + + /* detect time_ranges that have shrunken */ + for (size_t i=0; i p.lower || c.upper < p.upper; + } + + /* create node */ + auto node = createNode(children.children.data(),children.numChildren,alloc,hasTimeSplits); + + /* recurse into each child and perform reduction */ + LBBox3fa gbounds = empty; + for (size_t i=0; i= 0) && (splitSAH >= 0))); + + /*! create a leaf node when threshold reached or SAH tells us to stop */ + if (current.size() <= cfg.minLeafSize || current.depth+MIN_LARGE_LEAF_LEVELS >= cfg.maxDepth || (current.size() <= cfg.maxLeafSize && leafSAH <= splitSAH)) { + current.prims.deterministic_order(); + return createLargeLeaf(current,alloc); + } + + /*! perform initial split */ + SetMB lprims,rprims; + std::unique_ptr> new_vector = split(csplit,current.prims,lprims,rprims); + bool hasTimeSplits = new_vector != nullptr; + NodeRecordMB4D values[MAX_BRANCHING_FACTOR]; + LocalChildList children(current); + { + BuildRecord lrecord(lprims,current.depth+1); + BuildRecord rrecord(rprims,current.depth+1); + children.split(0,lrecord,rrecord,std::move(new_vector)); + } + + /*! split until node is full or SAH tells us to stop */ + while (children.size() < cfg.branchingFactor) + { + /*! find best child to split */ + float bestArea = neg_inf; + ssize_t bestChild = -1; + for (size_t i=0; i bestArea) { + bestChild = i; bestArea = expectedApproxHalfArea(children[i].prims.geomBounds); + } + } + if (bestChild == -1) break; + + /* perform split */ + BuildRecord& brecord = children[bestChild]; + BuildRecord lrecord(current.depth+1); + BuildRecord rrecord(current.depth+1); + Split csplit = find(brecord.prims); + std::unique_ptr> new_vector = split(csplit,brecord.prims,lrecord.prims,rrecord.prims); + hasTimeSplits |= new_vector != nullptr; + children.split(bestChild,lrecord,rrecord,std::move(new_vector)); + } + + /* detect time_ranges that have shrunken */ + for (size_t i=0; i p.lower || c.upper < p.upper; + } + + /* sort buildrecords for simpler shadow ray traversal */ + //std::sort(&children[0],&children[children.size()],std::greater()); // FIXME: reduces traversal performance of bvh8.triangle4 (need to verified) !! + + /*! create an inner node */ + auto node = createNode(children.children.data(), children.numChildren, alloc, hasTimeSplits); + LBBox3fa gbounds = empty; + + /* spawn tasks */ + if (unlikely(current.size() > cfg.singleThreadThreshold)) + { + /*! parallel_for is faster than spawing sub-tasks */ + parallel_for(size_t(0), children.size(), [&] (const range& r) { + for (size_t i=r.begin(); i=0; i--) { + values[i] = recurse(children[i],alloc,false); + gbounds.extend(values[i].lbounds); + } + } + + setNode(current,children.children.data(),node,values,children.numChildren); + + /* calculate geometry bounds of this node */ + if (unlikely(hasTimeSplits)) + return NodeRecordMB4D(node,current.prims.linearBounds(recalculatePrimRef),current.prims.time_range); + else + return NodeRecordMB4D(node,gbounds,current.prims.time_range); + } + + /*! builder entry function */ + __forceinline const NodeRecordMB4D operator() (mvector& prims, const PrimInfoMB& pinfo) + { + const SetMB set(pinfo,&prims); + auto ret = recurse(BuildRecord(set,1),nullptr,true); + _mm_mfence(); // to allow non-temporal stores during build + return ret; + } + + private: + Settings cfg; + HeuristicArrayBinningMB heuristicObjectSplit; + HeuristicMBlurTemporalSplit heuristicTemporalSplit; + const RecalculatePrimRef recalculatePrimRef; + const CreateAllocFunc createAlloc; + const CreateNodeFunc createNode; + const SetNodeFunc setNode; + const CreateLeafFunc createLeaf; + const ProgressMonitor progressMonitor; + }; + + template + + static const BVHNodeRecordMB4D build(mvector& prims, + const PrimInfoMB& pinfo, + MemoryMonitorInterface* device, + const RecalculatePrimRef recalculatePrimRef, + const CreateAllocFunc createAlloc, + const CreateNodeFunc createNode, + const SetNodeFunc setNode, + const CreateLeafFunc createLeaf, + const ProgressMonitorFunc progressMonitor, + const Settings& settings) + { + typedef BuilderT< + NodeRef, + RecalculatePrimRef, + decltype(createAlloc()), + CreateAllocFunc, + CreateNodeFunc, + SetNodeFunc, + CreateLeafFunc, + ProgressMonitorFunc> Builder; + + Builder builder(device, + recalculatePrimRef, + createAlloc, + createNode, + setNode, + createLeaf, + progressMonitor, + settings); + + + return builder(prims,pinfo); + } + }; + } +} diff --git a/thirdparty/embree/kernels/builders/bvh_builder_msmblur_hair.h b/thirdparty/embree/kernels/builders/bvh_builder_msmblur_hair.h new file mode 100644 index 000000000000..e477c313a381 --- /dev/null +++ b/thirdparty/embree/kernels/builders/bvh_builder_msmblur_hair.h @@ -0,0 +1,526 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../bvh/bvh.h" +#include "../geometry/primitive.h" +#include "../builders/bvh_builder_msmblur.h" +#include "../builders/heuristic_binning_array_aligned.h" +#include "../builders/heuristic_binning_array_unaligned.h" +#include "../builders/heuristic_timesplit_array.h" + +namespace embree +{ + namespace isa + { + struct BVHBuilderHairMSMBlur + { + /*! settings for msmblur builder */ + struct Settings + { + /*! default settings */ + Settings () + : branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(8) {} + + public: + size_t branchingFactor; //!< branching factor of BVH to build + size_t maxDepth; //!< maximum depth of BVH to build + size_t logBlockSize; //!< log2 of blocksize for SAH heuristic + size_t minLeafSize; //!< minimum size of a leaf + size_t maxLeafSize; //!< maximum size of a leaf + }; + + struct BuildRecord + { + public: + __forceinline BuildRecord () {} + + __forceinline BuildRecord (size_t depth) + : depth(depth) {} + + __forceinline BuildRecord (const SetMB& prims, size_t depth) + : depth(depth), prims(prims) {} + + __forceinline size_t size() const { + return prims.size(); + } + + public: + size_t depth; //!< depth of the root of this subtree + SetMB prims; //!< the list of primitives + }; + + template + + class BuilderT + { + ALIGNED_CLASS_(16); + + static const size_t MAX_BRANCHING_FACTOR = 8; //!< maximum supported BVH branching factor + static const size_t MIN_LARGE_LEAF_LEVELS = 8; //!< create balanced tree if we are that many levels before the maximum tree depth + static const size_t SINGLE_THREADED_THRESHOLD = 4096; //!< threshold to switch to single threaded build + + typedef BVHNodeRecordMB NodeRecordMB; + typedef BVHNodeRecordMB4D NodeRecordMB4D; + + typedef FastAllocator::CachedAllocator Allocator; + typedef LocalChildListT LocalChildList; + + typedef HeuristicMBlurTemporalSplit HeuristicTemporal; + typedef HeuristicArrayBinningMB HeuristicBinning; + typedef UnalignedHeuristicArrayBinningMB UnalignedHeuristicBinning; + + public: + + BuilderT (Scene* scene, + const RecalculatePrimRef& recalculatePrimRef, + const CreateAllocFunc& createAlloc, + const CreateAABBNodeMBFunc& createAABBNodeMB, + const SetAABBNodeMBFunc& setAABBNodeMB, + const CreateOBBNodeMBFunc& createOBBNodeMB, + const SetOBBNodeMBFunc& setOBBNodeMB, + const CreateLeafFunc& createLeaf, + const ProgressMonitor& progressMonitor, + const Settings settings) + + : cfg(settings), + scene(scene), + recalculatePrimRef(recalculatePrimRef), + createAlloc(createAlloc), + createAABBNodeMB(createAABBNodeMB), setAABBNodeMB(setAABBNodeMB), + createOBBNodeMB(createOBBNodeMB), setOBBNodeMB(setOBBNodeMB), + createLeaf(createLeaf), + progressMonitor(progressMonitor), + unalignedHeuristic(scene), + temporalSplitHeuristic(scene->device,recalculatePrimRef) {} + + private: + + /*! checks if all primitives are from the same geometry */ + __forceinline bool sameGeometry(const SetMB& set) + { + mvector& prims = *set.prims; + unsigned int firstGeomID = prims[set.begin()].geomID(); + for (size_t i=set.begin()+1; i& prims = *set.prims; + + const size_t begin = set.begin(); + const size_t end = set.end(); + const size_t center = (begin + end)/2; + + PrimInfoMB linfo = empty; + for (size_t i=begin; i(begin,center),set.time_range); + new (&rset) SetMB(rinfo,set.prims,range(center,end ),set.time_range); + } + + void splitByGeometry(const SetMB& set, SetMB& lset, SetMB& rset) + { + assert(set.size() > 1); + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfoMB linfo(empty); + PrimInfoMB rinfo(empty); + unsigned int geomID = (*set.prims)[begin].geomID(); + size_t center = serial_partitioning(set.prims->data(),begin,end,linfo,rinfo, + [&] ( const PrimRefMB& prim ) { return prim.geomID() == geomID; }, + [ ] ( PrimInfoMB& a, const PrimRefMB& ref ) { a.add_primref(ref); }); + + new (&lset) SetMB(linfo,set.prims,range(begin,center),set.time_range); + new (&rset) SetMB(rinfo,set.prims,range(center,end ),set.time_range); + } + + /*! creates a large leaf that could be larger than supported by the BVH */ + NodeRecordMB4D createLargeLeaf(BuildRecord& current, Allocator alloc) + { + /* this should never occur but is a fatal error */ + if (current.depth > cfg.maxDepth) + throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached"); + + /* special case when directly creating leaf without any splits that could shrink time_range */ + bool force_split = false; + if (current.depth == 1 && current.size() > 0) + { + BBox1f c = empty; + BBox1f p = current.prims.time_range; + for (size_t i=current.prims.begin(); i& prims = *current.prims.prims; + c.extend(prims[i].time_range); + } + + force_split = c.lower > p.lower || c.upper < p.upper; + } + + /* create leaf for few primitives */ + if (current.size() <= cfg.maxLeafSize && sameGeometry(current.prims) && !force_split) + return createLeaf(current.prims,alloc); + + /* fill all children by always splitting the largest one */ + LocalChildList children(current); + NodeRecordMB4D values[MAX_BRANCHING_FACTOR]; + + do { + + /* find best child with largest bounding box area */ + int bestChild = -1; + size_t bestSize = 0; + for (unsigned i=0; i bestSize) { + bestSize = children[i].size(); + bestChild = i; + } + } + if (bestChild == -1) break; + + /*! split best child into left and right child */ + BuildRecord left(current.depth+1); + BuildRecord right(current.depth+1); + if (!sameGeometry(children[bestChild].prims)) { + splitByGeometry(children[bestChild].prims,left.prims,right.prims); + } else { + splitFallback(children[bestChild].prims,left.prims,right.prims); + } + children.split(bestChild,left,right,std::unique_ptr>()); + + } while (children.size() < cfg.branchingFactor); + + + /* detect time_ranges that have shrunken */ + bool timesplit = false; + for (size_t i=0; i p.lower || c.upper < p.upper; + } + + /* create node */ + NodeRef node = createAABBNodeMB(children.children.data(),children.numChildren,alloc,timesplit); + + LBBox3fa bounds = empty; + for (size_t i=0; i> split(const BuildRecord& current, BuildRecord& lrecord, BuildRecord& rrecord, bool& aligned, bool& timesplit) + { + /* variable to track the SAH of the best splitting approach */ + float bestSAH = inf; + const float leafSAH = current.prims.leafSAH(cfg.logBlockSize); + + /* perform standard binning in aligned space */ + HeuristicBinning::Split alignedObjectSplit = alignedHeuristic.find(current.prims,cfg.logBlockSize); + float alignedObjectSAH = alignedObjectSplit.splitSAH(); + bestSAH = min(alignedObjectSAH,bestSAH); + + /* perform standard binning in unaligned space */ + UnalignedHeuristicBinning::Split unalignedObjectSplit; + LinearSpace3fa uspace; + float unalignedObjectSAH = inf; + if (alignedObjectSAH > 0.7f*leafSAH) { + uspace = unalignedHeuristic.computeAlignedSpaceMB(scene,current.prims); + const SetMB sset = current.prims.primInfo(recalculatePrimRef,uspace); + unalignedObjectSplit = unalignedHeuristic.find(sset,cfg.logBlockSize,uspace); + unalignedObjectSAH = 1.3f*unalignedObjectSplit.splitSAH(); // makes unaligned splits more expensive + bestSAH = min(unalignedObjectSAH,bestSAH); + } + + /* do temporal splits only if previous approaches failed to produce good SAH and the the time range is large enough */ + float temporal_split_sah = inf; + typename HeuristicTemporal::Split temporal_split; + if (bestSAH > 0.5f*leafSAH) { + if (current.prims.time_range.size() > 1.01f/float(current.prims.max_num_time_segments)) { + temporal_split = temporalSplitHeuristic.find(current.prims,cfg.logBlockSize); + temporal_split_sah = temporal_split.splitSAH(); + bestSAH = min(temporal_split_sah,bestSAH); + } + } + + /* perform fallback split if SAH heuristics failed */ + if (unlikely(!std::isfinite(bestSAH))) { + current.prims.deterministic_order(); + splitFallback(current.prims,lrecord.prims,rrecord.prims); + } + /* perform aligned split if this is best */ + else if (likely(bestSAH == alignedObjectSAH)) { + alignedHeuristic.split(alignedObjectSplit,current.prims,lrecord.prims,rrecord.prims); + } + /* perform unaligned split if this is best */ + else if (likely(bestSAH == unalignedObjectSAH)) { + unalignedHeuristic.split(unalignedObjectSplit,uspace,current.prims,lrecord.prims,rrecord.prims); + aligned = false; + } + /* perform temporal split if this is best */ + else if (likely(bestSAH == temporal_split_sah)) { + timesplit = true; + return temporalSplitHeuristic.split(temporal_split,current.prims,lrecord.prims,rrecord.prims); + } + else + assert(false); + + return std::unique_ptr>(); + } + + /*! recursive build */ + NodeRecordMB4D recurse(BuildRecord& current, Allocator alloc, bool toplevel) + { + /* get thread local allocator */ + if (!alloc) + alloc = createAlloc(); + + /* call memory monitor function to signal progress */ + if (toplevel && current.size() <= SINGLE_THREADED_THRESHOLD) + progressMonitor(current.size()); + + /* create leaf node */ + if (current.depth+MIN_LARGE_LEAF_LEVELS >= cfg.maxDepth || current.size() <= cfg.minLeafSize) { + current.prims.deterministic_order(); + return createLargeLeaf(current,alloc); + } + + /* fill all children by always splitting the one with the largest surface area */ + NodeRecordMB4D values[MAX_BRANCHING_FACTOR]; + LocalChildList children(current); + bool aligned = true; + bool timesplit = false; + + do { + + /* find best child with largest bounding box area */ + ssize_t bestChild = -1; + float bestArea = neg_inf; + for (size_t i=0; i bestArea) { + bestArea = children[i].prims.halfArea(); + bestChild = i; + } + } + if (bestChild == -1) break; + + /*! split best child into left and right child */ + BuildRecord left(current.depth+1); + BuildRecord right(current.depth+1); + std::unique_ptr> new_vector = split(children[bestChild],left,right,aligned,timesplit); + children.split(bestChild,left,right,std::move(new_vector)); + + } while (children.size() < cfg.branchingFactor); + + /* detect time_ranges that have shrunken */ + for (size_t i=0; i p.lower || c.upper < p.upper; + } + + /* create time split node */ + if (timesplit) + { + const NodeRef node = createAABBNodeMB(children.children.data(),children.numChildren,alloc,true); + + /* spawn tasks or ... */ + if (current.size() > SINGLE_THREADED_THRESHOLD) + { + parallel_for(size_t(0), children.size(), [&] (const range& r) { + for (size_t i=r.begin(); i SINGLE_THREADED_THRESHOLD) + { + LBBox3fa cbounds[MAX_BRANCHING_FACTOR]; + parallel_for(size_t(0), children.size(), [&] (const range& r) { + for (size_t i=r.begin(); i SINGLE_THREADED_THRESHOLD) + { + parallel_for(size_t(0), children.size(), [&] (const range& r) { + for (size_t i=r.begin(); i& prims, const PrimInfoMB& pinfo) + { + BuildRecord record(SetMB(pinfo,&prims),1); + auto root = recurse(record,nullptr,true); + _mm_mfence(); // to allow non-temporal stores during build + return root; + } + + private: + Settings cfg; + Scene* scene; + const RecalculatePrimRef& recalculatePrimRef; + const CreateAllocFunc& createAlloc; + const CreateAABBNodeMBFunc& createAABBNodeMB; + const SetAABBNodeMBFunc& setAABBNodeMB; + const CreateOBBNodeMBFunc& createOBBNodeMB; + const SetOBBNodeMBFunc& setOBBNodeMB; + const CreateLeafFunc& createLeaf; + const ProgressMonitor& progressMonitor; + + private: + HeuristicBinning alignedHeuristic; + UnalignedHeuristicBinning unalignedHeuristic; + HeuristicTemporal temporalSplitHeuristic; + }; + + template + + static BVHNodeRecordMB4D build (Scene* scene, mvector& prims, const PrimInfoMB& pinfo, + const RecalculatePrimRef& recalculatePrimRef, + const CreateAllocFunc& createAlloc, + const CreateAABBNodeMBFunc& createAABBNodeMB, + const SetAABBNodeMBFunc& setAABBNodeMB, + const CreateOBBNodeMBFunc& createOBBNodeMB, + const SetOBBNodeMBFunc& setOBBNodeMB, + const CreateLeafFunc& createLeaf, + const ProgressMonitor& progressMonitor, + const Settings settings) + { + typedef BuilderT Builder; + + Builder builder(scene,recalculatePrimRef,createAlloc, + createAABBNodeMB,setAABBNodeMB, + createOBBNodeMB,setOBBNodeMB, + createLeaf,progressMonitor,settings); + + return builder(prims,pinfo); + } + }; + } +} diff --git a/thirdparty/embree/kernels/builders/bvh_builder_sah.h b/thirdparty/embree/kernels/builders/bvh_builder_sah.h new file mode 100644 index 000000000000..79ccdf946f5e --- /dev/null +++ b/thirdparty/embree/kernels/builders/bvh_builder_sah.h @@ -0,0 +1,669 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "heuristic_binning_array_aligned.h" +#include "heuristic_spatial_array.h" +#include "heuristic_openmerge_array.h" + +#if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL +# define NUM_OBJECT_BINS 16 +# define NUM_SPATIAL_BINS 16 +#else +# define NUM_OBJECT_BINS 32 +# define NUM_SPATIAL_BINS 16 +#endif + +namespace embree +{ + namespace isa + { + MAYBE_UNUSED static const float travCost = 1.0f; + MAYBE_UNUSED static const size_t DEFAULT_SINGLE_THREAD_THRESHOLD = 1024; + + struct GeneralBVHBuilder + { + static const size_t MAX_BRANCHING_FACTOR = 16; //!< maximum supported BVH branching factor + static const size_t MIN_LARGE_LEAF_LEVELS = 8; //!< create balanced tree of we are that many levels before the maximum tree depth + + + /*! settings for SAH builder */ + struct Settings + { + /*! default settings */ + Settings () + : branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(7), + travCost(1.0f), intCost(1.0f), singleThreadThreshold(1024), primrefarrayalloc(inf) {} + + /*! initialize settings from API settings */ + Settings (const RTCBuildArguments& settings) + : branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(7), + travCost(1.0f), intCost(1.0f), singleThreadThreshold(1024), primrefarrayalloc(inf) + { + if (RTC_BUILD_ARGUMENTS_HAS(settings,maxBranchingFactor)) branchingFactor = settings.maxBranchingFactor; + if (RTC_BUILD_ARGUMENTS_HAS(settings,maxDepth )) maxDepth = settings.maxDepth; + if (RTC_BUILD_ARGUMENTS_HAS(settings,sahBlockSize )) logBlockSize = bsr(settings.sahBlockSize); + if (RTC_BUILD_ARGUMENTS_HAS(settings,minLeafSize )) minLeafSize = settings.minLeafSize; + if (RTC_BUILD_ARGUMENTS_HAS(settings,maxLeafSize )) maxLeafSize = settings.maxLeafSize; + if (RTC_BUILD_ARGUMENTS_HAS(settings,traversalCost )) travCost = settings.traversalCost; + if (RTC_BUILD_ARGUMENTS_HAS(settings,intersectionCost )) intCost = settings.intersectionCost; + + minLeafSize = min(minLeafSize,maxLeafSize); + } + + Settings (size_t sahBlockSize, size_t minLeafSize, size_t maxLeafSize, float travCost, float intCost, size_t singleThreadThreshold, size_t primrefarrayalloc = inf) + : branchingFactor(2), maxDepth(32), logBlockSize(bsr(sahBlockSize)), minLeafSize(minLeafSize), maxLeafSize(maxLeafSize), + travCost(travCost), intCost(intCost), singleThreadThreshold(singleThreadThreshold), primrefarrayalloc(primrefarrayalloc) + { + minLeafSize = min(minLeafSize,maxLeafSize); + } + + public: + size_t branchingFactor; //!< branching factor of BVH to build + size_t maxDepth; //!< maximum depth of BVH to build + size_t logBlockSize; //!< log2 of blocksize for SAH heuristic + size_t minLeafSize; //!< minimum size of a leaf + size_t maxLeafSize; //!< maximum size of a leaf + float travCost; //!< estimated cost of one traversal step + float intCost; //!< estimated cost of one primitive intersection + size_t singleThreadThreshold; //!< threshold when we switch to single threaded build + size_t primrefarrayalloc; //!< builder uses prim ref array to allocate nodes and leaves when a subtree of that size is finished + }; + + /*! recursive state of builder */ + template + struct BuildRecordT + { + public: + __forceinline BuildRecordT () {} + + __forceinline BuildRecordT (size_t depth) + : depth(depth), alloc_barrier(false), prims(empty) {} + + __forceinline BuildRecordT (size_t depth, const Set& prims) + : depth(depth), alloc_barrier(false), prims(prims) {} + + __forceinline BBox3fa bounds() const { return prims.geomBounds; } + + __forceinline friend bool operator< (const BuildRecordT& a, const BuildRecordT& b) { return a.prims.size() < b.prims.size(); } + __forceinline friend bool operator> (const BuildRecordT& a, const BuildRecordT& b) { return a.prims.size() > b.prims.size(); } + + __forceinline size_t size() const { return prims.size(); } + + public: + size_t depth; //!< Depth of the root of this subtree. + bool alloc_barrier; //!< barrier used to reuse primref-array blocks to allocate nodes + Set prims; //!< The list of primitives. + }; + + template + struct DefaultCanCreateLeafFunc + { + __forceinline bool operator()(const PrimRef*, const Set&) const { return true; } + }; + + template + struct DefaultCanCreateLeafSplitFunc + { + __forceinline void operator()(PrimRef*, const Set&, Set&, Set&) const { } + }; + + template + + class BuilderT + { + friend struct GeneralBVHBuilder; + + BuilderT (PrimRef* prims, + Heuristic& heuristic, + const CreateAllocFunc& createAlloc, + const CreateNodeFunc& createNode, + const UpdateNodeFunc& updateNode, + const CreateLeafFunc& createLeaf, + const CanCreateLeafFunc& canCreateLeaf, + const CanCreateLeafSplitFunc& canCreateLeafSplit, + const ProgressMonitor& progressMonitor, + const Settings& settings) : + cfg(settings), + prims(prims), + heuristic(heuristic), + createAlloc(createAlloc), + createNode(createNode), + updateNode(updateNode), + createLeaf(createLeaf), + canCreateLeaf(canCreateLeaf), + canCreateLeafSplit(canCreateLeafSplit), + progressMonitor(progressMonitor) + { + if (cfg.branchingFactor > MAX_BRANCHING_FACTOR) + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } + + const ReductionTy createLargeLeaf(const BuildRecord& current, Allocator alloc) + { + /* this should never occur but is a fatal error */ + if (current.depth > cfg.maxDepth) + throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached"); + + /* create leaf for few primitives */ + if (current.prims.size() <= cfg.maxLeafSize && canCreateLeaf(prims,current.prims)) + return createLeaf(prims,current.prims,alloc); + + /* fill all children by always splitting the largest one */ + ReductionTy values[MAX_BRANCHING_FACTOR]; + BuildRecord children[MAX_BRANCHING_FACTOR]; + size_t numChildren = 1; + children[0] = current; + do { + + /* find best child with largest bounding box area */ + size_t bestChild = -1; + size_t bestSize = 0; + for (size_t i=0; i bestSize) { + bestSize = children[i].prims.size(); + bestChild = i; + } + } + if (bestChild == (size_t)-1) break; + + /*! split best child into left and right child */ + BuildRecord left(current.depth+1); + BuildRecord right(current.depth+1); + if (!canCreateLeaf(prims,children[bestChild].prims)) { + canCreateLeafSplit(prims,children[bestChild].prims,left.prims,right.prims); + } else { + heuristic.splitFallback(children[bestChild].prims,left.prims,right.prims); + } + + /* add new children left and right */ + children[bestChild] = children[numChildren-1]; + children[numChildren-1] = left; + children[numChildren+0] = right; + numChildren++; + + } while (numChildren < cfg.branchingFactor); + + /* set barrier for primrefarrayalloc */ + if (unlikely(current.size() > cfg.primrefarrayalloc)) + for (size_t i=0; i= 0) && (splitSAH >= 0))); + + /*! create a leaf node when threshold reached or SAH tells us to stop */ + if (current.prims.size() <= cfg.minLeafSize || current.depth+MIN_LARGE_LEAF_LEVELS >= cfg.maxDepth || (current.prims.size() <= cfg.maxLeafSize && leafSAH <= splitSAH)) { + heuristic.deterministic_order(current.prims); + return createLargeLeaf(current,alloc); + } + + /*! perform initial split */ + Set lprims,rprims; + heuristic.split(split,current.prims,lprims,rprims); + + /*! initialize child list with initial split */ + ReductionTy values[MAX_BRANCHING_FACTOR]; + BuildRecord children[MAX_BRANCHING_FACTOR]; + children[0] = BuildRecord(current.depth+1,lprims); + children[1] = BuildRecord(current.depth+1,rprims); + size_t numChildren = 2; + + /*! split until node is full or SAH tells us to stop */ + while (numChildren < cfg.branchingFactor) + { + /*! find best child to split */ + float bestArea = neg_inf; + ssize_t bestChild = -1; + for (size_t i=0; i bestArea) { + bestChild = i; + bestArea = halfArea(children[i].prims.geomBounds); + } + } + if (bestChild == -1) break; + + /* perform best found split */ + BuildRecord& brecord = children[bestChild]; + BuildRecord lrecord(current.depth+1); + BuildRecord rrecord(current.depth+1); + auto split = heuristic.find(brecord.prims,cfg.logBlockSize); + heuristic.split(split,brecord.prims,lrecord.prims,rrecord.prims); + children[bestChild ] = lrecord; + children[numChildren] = rrecord; + numChildren++; + } + + /* set barrier for primrefarrayalloc */ + if (unlikely(current.size() > cfg.primrefarrayalloc)) + for (size_t i=0; i()); + + /*! create an inner node */ + auto node = createNode(children,numChildren,alloc); + + /* spawn tasks */ + if (current.size() > cfg.singleThreadThreshold) + { + /*! parallel_for is faster than spawing sub-tasks */ + parallel_for(size_t(0), numChildren, [&] (const range& r) { // FIXME: no range here + for (size_t i=r.begin(); i + + __noinline static ReductionTy build(Heuristic& heuristic, + PrimRef* prims, + const Set& set, + CreateAllocFunc createAlloc, + CreateNodeFunc createNode, UpdateNodeFunc updateNode, + const CreateLeafFunc& createLeaf, + const ProgressMonitor& progressMonitor, + const Settings& settings) + { + typedef BuildRecordT BuildRecord; + + typedef BuilderT< + BuildRecord, + Heuristic, + Set, + PrimRef, + ReductionTy, + decltype(createAlloc()), + CreateAllocFunc, + CreateNodeFunc, + UpdateNodeFunc, + CreateLeafFunc, + DefaultCanCreateLeafFunc, + DefaultCanCreateLeafSplitFunc, + ProgressMonitor> Builder; + + /* instantiate builder */ + Builder builder(prims, + heuristic, + createAlloc, + createNode, + updateNode, + createLeaf, + DefaultCanCreateLeafFunc(), + DefaultCanCreateLeafSplitFunc(), + progressMonitor, + settings); + + /* build hierarchy */ + BuildRecord record(1,set); + const ReductionTy root = builder.recurse(record,nullptr,true); + _mm_mfence(); // to allow non-temporal stores during build + return root; + } + + template< + typename ReductionTy, + typename Heuristic, + typename Set, + typename PrimRef, + typename CreateAllocFunc, + typename CreateNodeFunc, + typename UpdateNodeFunc, + typename CreateLeafFunc, + typename CanCreateLeafFunc, + typename CanCreateLeafSplitFunc, + typename ProgressMonitor> + + __noinline static ReductionTy build(Heuristic& heuristic, + PrimRef* prims, + const Set& set, + CreateAllocFunc createAlloc, + CreateNodeFunc createNode, UpdateNodeFunc updateNode, + const CreateLeafFunc& createLeaf, + const CanCreateLeafFunc& canCreateLeaf, + const CanCreateLeafSplitFunc& canCreateLeafSplit, + const ProgressMonitor& progressMonitor, + const Settings& settings) + { + typedef BuildRecordT BuildRecord; + + typedef BuilderT< + BuildRecord, + Heuristic, + Set, + PrimRef, + ReductionTy, + decltype(createAlloc()), + CreateAllocFunc, + CreateNodeFunc, + UpdateNodeFunc, + CreateLeafFunc, + CanCreateLeafFunc, + CanCreateLeafSplitFunc, + ProgressMonitor> Builder; + + /* instantiate builder */ + Builder builder(prims, + heuristic, + createAlloc, + createNode, + updateNode, + createLeaf, + canCreateLeaf, + canCreateLeafSplit, + progressMonitor, + settings); + + /* build hierarchy */ + BuildRecord record(1,set); + const ReductionTy root = builder.recurse(record,nullptr,true); + _mm_mfence(); // to allow non-temporal stores during build + return root; + } + }; + + /* SAH builder that operates on an array of BuildRecords */ + struct BVHBuilderBinnedSAH + { + typedef PrimInfoRange Set; + typedef HeuristicArrayBinningSAH Heuristic; + typedef GeneralBVHBuilder::BuildRecordT BuildRecord; + typedef GeneralBVHBuilder::Settings Settings; + + /*! special builder that propagates reduction over the tree */ + template< + typename ReductionTy, + typename CreateAllocFunc, + typename CreateNodeFunc, + typename UpdateNodeFunc, + typename CreateLeafFunc, + typename ProgressMonitor> + + static ReductionTy build(CreateAllocFunc createAlloc, + CreateNodeFunc createNode, UpdateNodeFunc updateNode, + const CreateLeafFunc& createLeaf, + const ProgressMonitor& progressMonitor, + PrimRef* prims, const PrimInfo& pinfo, + const Settings& settings) + { + Heuristic heuristic(prims); + return GeneralBVHBuilder::build( + heuristic, + prims, + PrimInfoRange(0,pinfo.size(),pinfo), + createAlloc, + createNode, + updateNode, + createLeaf, + progressMonitor, + settings); + } + + /*! special builder that propagates reduction over the tree */ + template< + typename ReductionTy, + typename CreateAllocFunc, + typename CreateNodeFunc, + typename UpdateNodeFunc, + typename CreateLeafFunc, + typename CanCreateLeafFunc, + typename CanCreateLeafSplitFunc, + typename ProgressMonitor> + + static ReductionTy build(CreateAllocFunc createAlloc, + CreateNodeFunc createNode, UpdateNodeFunc updateNode, + const CreateLeafFunc& createLeaf, + const CanCreateLeafFunc& canCreateLeaf, + const CanCreateLeafSplitFunc& canCreateLeafSplit, + const ProgressMonitor& progressMonitor, + PrimRef* prims, const PrimInfo& pinfo, + const Settings& settings) + { + Heuristic heuristic(prims); + return GeneralBVHBuilder::build( + heuristic, + prims, + PrimInfoRange(0,pinfo.size(),pinfo), + createAlloc, + createNode, + updateNode, + createLeaf, + canCreateLeaf, + canCreateLeafSplit, + progressMonitor, + settings); + } + }; + + /* Spatial SAH builder that operates on an double-buffered array of BuildRecords */ + struct BVHBuilderBinnedFastSpatialSAH + { + typedef PrimInfoExtRange Set; + typedef Split2,SpatialBinSplit > Split; + typedef GeneralBVHBuilder::BuildRecordT BuildRecord; + typedef GeneralBVHBuilder::Settings Settings; + + static const unsigned int GEOMID_MASK = 0xFFFFFFFF >> RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS; + static const unsigned int SPLITS_MASK = 0xFFFFFFFF << (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS); + + template + struct CreateLeafExt + { + __forceinline CreateLeafExt (const UserCreateLeaf userCreateLeaf) + : userCreateLeaf(userCreateLeaf) {} + + // __noinline is workaround for ICC2016 compiler bug + template + __noinline ReductionTy operator() (PrimRef* prims, const range& range, Allocator alloc) const + { + for (size_t i=range.begin(); i + + static ReductionTy build(CreateAllocFunc createAlloc, + CreateNodeFunc createNode, + UpdateNodeFunc updateNode, + const CreateLeafFunc& createLeaf, + SplitPrimitiveFunc splitPrimitive, + ProgressMonitor progressMonitor, + PrimRef* prims, + const size_t extSize, + const PrimInfo& pinfo, + const Settings& settings) + { + typedef HeuristicArraySpatialSAH Heuristic; + Heuristic heuristic(splitPrimitive,prims,pinfo); + + /* calculate total surface area */ // FIXME: this sum is not deterministic + const float A = (float) parallel_reduce(size_t(0),pinfo.size(),0.0, [&] (const range& r) -> double { + + double A = 0.0f; + for (size_t i=r.begin(); i()); + + + /* calculate maximum number of spatial splits per primitive */ + const unsigned int maxSplits = ((size_t)1 << RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)-1; + const float f = 10.0f; + + const float invA = 1.0f / A; + parallel_for( size_t(0), pinfo.size(), [&](const range& r) { + + for (size_t i=r.begin(); i( + heuristic, + prims, + PrimInfoExtRange(0,pinfo.size(),extSize,pinfo), + createAlloc, + createNode, + updateNode, + CreateLeafExt(createLeaf), + progressMonitor, + settings); + } + }; + + /* Open/Merge SAH builder that operates on an array of BuildRecords */ + struct BVHBuilderBinnedOpenMergeSAH + { + static const size_t NUM_OBJECT_BINS_HQ = 32; + typedef PrimInfoExtRange Set; + typedef BinSplit Split; + typedef GeneralBVHBuilder::BuildRecordT BuildRecord; + typedef GeneralBVHBuilder::Settings Settings; + + /*! special builder that propagates reduction over the tree */ + template< + typename ReductionTy, + typename BuildRef, + typename CreateAllocFunc, + typename CreateNodeFunc, + typename UpdateNodeFunc, + typename CreateLeafFunc, + typename NodeOpenerFunc, + typename ProgressMonitor> + + static ReductionTy build(CreateAllocFunc createAlloc, + CreateNodeFunc createNode, + UpdateNodeFunc updateNode, + const CreateLeafFunc& createLeaf, + NodeOpenerFunc nodeOpenerFunc, + ProgressMonitor progressMonitor, + BuildRef* prims, + const size_t extSize, + const PrimInfo& pinfo, + const Settings& settings) + { + typedef HeuristicArrayOpenMergeSAH Heuristic; + Heuristic heuristic(nodeOpenerFunc,prims,settings.branchingFactor); + + return GeneralBVHBuilder::build( + heuristic, + prims, + PrimInfoExtRange(0,pinfo.size(),extSize,pinfo), + createAlloc, + createNode, + updateNode, + createLeaf, + progressMonitor, + settings); + } + }; + } +} diff --git a/thirdparty/embree/kernels/builders/heuristic_binning.h b/thirdparty/embree/kernels/builders/heuristic_binning.h new file mode 100644 index 000000000000..a4d3b68e46a6 --- /dev/null +++ b/thirdparty/embree/kernels/builders/heuristic_binning.h @@ -0,0 +1,972 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "priminfo.h" +#include "../../common/algorithms/parallel_reduce.h" +#include "../../common/algorithms/parallel_partition.h" + +namespace embree +{ + namespace isa + { + /*! mapping into bins */ + template + struct BinMapping + { + public: + __forceinline BinMapping() {} + + /*! calculates the mapping */ + __forceinline BinMapping(size_t N, const BBox3fa& centBounds) + { + num = min(BINS,size_t(4.0f + 0.05f*N)); + assert(num >= 1); + const vfloat4 eps = 1E-34f; + const vfloat4 diag = max(eps, (vfloat4) centBounds.size()); + scale = select(diag > eps,vfloat4(0.99f*num)/diag,vfloat4(0.0f)); + ofs = (vfloat4) centBounds.lower; + } + + /*! calculates the mapping */ + __forceinline BinMapping(const BBox3fa& centBounds) + { + num = BINS; + const vfloat4 eps = 1E-34f; + const vfloat4 diag = max(eps, (vfloat4) centBounds.size()); + scale = select(diag > eps,vfloat4(0.99f*num)/diag,vfloat4(0.0f)); + ofs = (vfloat4) centBounds.lower; + } + + /*! calculates the mapping */ + template + __forceinline BinMapping(const PrimInfo& pinfo) + { + const vfloat4 eps = 1E-34f; + num = min(BINS,size_t(4.0f + 0.05f*pinfo.size())); + const vfloat4 diag = max(eps,(vfloat4) pinfo.centBounds.size()); + scale = select(diag > eps,vfloat4(0.99f*num)/diag,vfloat4(0.0f)); + ofs = (vfloat4) pinfo.centBounds.lower; + } + + /*! returns number of bins */ + __forceinline size_t size() const { return num; } + + /*! slower but safe binning */ + __forceinline Vec3ia bin(const Vec3fa& p) const + { + const vint4 i = floori((vfloat4(p)-ofs)*scale); +#if 1 + assert(i[0] >= 0 && (size_t)i[0] < num); + assert(i[1] >= 0 && (size_t)i[1] < num); + assert(i[2] >= 0 && (size_t)i[2] < num); + return Vec3ia(i); +#else + return Vec3ia(clamp(i,vint4(0),vint4(num-1))); +#endif + } + + /*! faster but unsafe binning */ + __forceinline Vec3ia bin_unsafe(const Vec3fa& p) const { + return Vec3ia(floori((vfloat4(p)-ofs)*scale)); + } + + /*! faster but unsafe binning */ + template + __forceinline Vec3ia bin_unsafe(const PrimRef& p) const { + return bin_unsafe(p.binCenter()); + } + + /*! faster but unsafe binning */ + template + __forceinline Vec3ia bin_unsafe(const PrimRef& p, const BinBoundsAndCenter& binBoundsAndCenter) const { + return bin_unsafe(binBoundsAndCenter.binCenter(p)); + } + + template + __forceinline bool bin_unsafe(const PrimRef& ref, + const vint4& vSplitPos, + const vbool4& splitDimMask) const // FIXME: rename to isLeft + { + return any(((vint4)bin_unsafe(center2(ref.bounds())) < vSplitPos) & splitDimMask); + } + /*! calculates left spatial position of bin */ + __forceinline float pos(const size_t bin, const size_t dim) const { + return madd(float(bin),1.0f / scale[dim],ofs[dim]); + } + + /*! returns true if the mapping is invalid in some dimension */ + __forceinline bool invalid(const size_t dim) const { + return scale[dim] == 0.0f; + } + + /*! stream output */ + friend embree_ostream operator<<(embree_ostream cout, const BinMapping& mapping) { + return cout << "BinMapping { num = " << mapping.num << ", ofs = " << mapping.ofs << ", scale = " << mapping.scale << "}"; + } + + public: + size_t num; + vfloat4 ofs,scale; //!< linear function that maps to bin ID + }; + + /*! stores all information to perform some split */ + template + struct BinSplit + { + enum + { + SPLIT_OBJECT = 0, + SPLIT_FALLBACK = 1, + SPLIT_ENFORCE = 2, // splits with larger ID are enforced in createLargeLeaf even if we could create a leaf already + SPLIT_TEMPORAL = 2, + SPLIT_GEOMID = 3, + }; + + /*! construct an invalid split by default */ + __forceinline BinSplit() + : sah(inf), dim(-1), pos(0), data(0) {} + + __forceinline BinSplit(float sah, unsigned data, int dim = 0, float fpos = 0) + : sah(sah), dim(dim), fpos(fpos), data(data) {} + + /*! constructs specified split */ + __forceinline BinSplit(float sah, int dim, int pos, const BinMapping& mapping) + : sah(sah), dim(dim), pos(pos), data(0), mapping(mapping) {} + + /*! tests if this split is valid */ + __forceinline bool valid() const { return dim != -1; } + + /*! calculates surface area heuristic for performing the split */ + __forceinline float splitSAH() const { return sah; } + + /*! stream output */ + friend embree_ostream operator<<(embree_ostream cout, const BinSplit& split) { + return cout << "BinSplit { sah = " << split.sah << ", dim = " << split.dim << ", pos = " << split.pos << "}"; + } + + public: + float sah; //!< SAH cost of the split + int dim; //!< split dimension + union { int pos; float fpos; }; //!< bin index for splitting + unsigned int data; //!< extra optional split data + BinMapping mapping; //!< mapping into bins + }; + + /*! stores extended information about the split */ + template + struct SplitInfoT + { + + __forceinline SplitInfoT () {} + + __forceinline SplitInfoT (size_t leftCount, const BBox& leftBounds, size_t rightCount, const BBox& rightBounds) + : leftCount(leftCount), rightCount(rightCount), leftBounds(leftBounds), rightBounds(rightBounds) {} + + public: + size_t leftCount,rightCount; + BBox leftBounds,rightBounds; + }; + + typedef SplitInfoT SplitInfo; + typedef SplitInfoT SplitInfo2; + + /*! stores all binning information */ + template + struct __aligned(64) BinInfoT + { + typedef BinSplit Split; + typedef vbool4 vbool; + typedef vint4 vint; + typedef vfloat4 vfloat; + + __forceinline BinInfoT() { + } + + __forceinline BinInfoT(EmptyTy) { + clear(); + } + + /*! bin access function */ + __forceinline BBox &bounds(const size_t binID, const size_t dimID) { return _bounds[binID][dimID]; } + __forceinline const BBox &bounds(const size_t binID, const size_t dimID) const { return _bounds[binID][dimID]; } + + __forceinline unsigned int &counts(const size_t binID, const size_t dimID) { return _counts[binID][dimID]; } + __forceinline const unsigned int &counts(const size_t binID, const size_t dimID) const { return _counts[binID][dimID]; } + + __forceinline vuint4 &counts(const size_t binID) { return _counts[binID]; } + __forceinline const vuint4 &counts(const size_t binID) const { return _counts[binID]; } + + /*! clears the bin info */ + __forceinline void clear() + { + for (size_t i=0; i& mapping) + { + if (unlikely(N == 0)) return; + size_t i; + for (i=0; i(bin0); bounds(b00,0).extend(prim0); + const unsigned int b01 = extract<1>(bin0); bounds(b01,1).extend(prim0); + const unsigned int b02 = extract<2>(bin0); bounds(b02,2).extend(prim0); + const unsigned int s0 = (unsigned int)prims[i+0].size(); + counts(b00,0)+=s0; + counts(b01,1)+=s0; + counts(b02,2)+=s0; + + /*! increase bounds of bins for odd primitive */ + const unsigned int b10 = extract<0>(bin1); bounds(b10,0).extend(prim1); + const unsigned int b11 = extract<1>(bin1); bounds(b11,1).extend(prim1); + const unsigned int b12 = extract<2>(bin1); bounds(b12,2).extend(prim1); + const unsigned int s1 = (unsigned int)prims[i+1].size(); + counts(b10,0)+=s1; + counts(b11,1)+=s1; + counts(b12,2)+=s1; + } + /*! for uneven number of primitives */ + if (i < N) + { + /*! map primitive to bin */ + BBox prim0; Vec3fa center0; + prims[i].binBoundsAndCenter(prim0,center0); + const vint4 bin0 = (vint4)mapping.bin(center0); + + /*! increase bounds of bins */ + const unsigned int s0 = (unsigned int)prims[i].size(); + const int b00 = extract<0>(bin0); counts(b00,0)+=s0; bounds(b00,0).extend(prim0); + const int b01 = extract<1>(bin0); counts(b01,1)+=s0; bounds(b01,1).extend(prim0); + const int b02 = extract<2>(bin0); counts(b02,2)+=s0; bounds(b02,2).extend(prim0); + } + } + + /*! bins an array of primitives */ + template + __forceinline void bin (const PrimRef* prims, size_t N, const BinMapping& mapping, const BinBoundsAndCenter& binBoundsAndCenter) + { + if (N == 0) return; + + size_t i; + for (i=0; i(bin0); counts(b00,0)+=s0; bounds(b00,0).extend(prim0); + const int b01 = extract<1>(bin0); counts(b01,1)+=s0; bounds(b01,1).extend(prim0); + const int b02 = extract<2>(bin0); counts(b02,2)+=s0; bounds(b02,2).extend(prim0); + + /*! increase bounds of bins for odd primitive */ + const unsigned int s1 = prims[i+1].size(); + const int b10 = extract<0>(bin1); counts(b10,0)+=s1; bounds(b10,0).extend(prim1); + const int b11 = extract<1>(bin1); counts(b11,1)+=s1; bounds(b11,1).extend(prim1); + const int b12 = extract<2>(bin1); counts(b12,2)+=s1; bounds(b12,2).extend(prim1); + } + + /*! for uneven number of primitives */ + if (i < N) + { + /*! map primitive to bin */ + BBox prim0; Vec3fa center0; binBoundsAndCenter.binBoundsAndCenter(prims[i+0],prim0,center0); + const vint4 bin0 = (vint4)mapping.bin(center0); + + /*! increase bounds of bins */ + const unsigned int s0 = prims[i+0].size(); + const int b00 = extract<0>(bin0); counts(b00,0)+=s0; bounds(b00,0).extend(prim0); + const int b01 = extract<1>(bin0); counts(b01,1)+=s0; bounds(b01,1).extend(prim0); + const int b02 = extract<2>(bin0); counts(b02,2)+=s0; bounds(b02,2).extend(prim0); + } + } + + __forceinline void bin(const PrimRef* prims, size_t begin, size_t end, const BinMapping& mapping) { + bin(prims+begin,end-begin,mapping); + } + + template + __forceinline void bin(const PrimRef* prims, size_t begin, size_t end, const BinMapping& mapping, const BinBoundsAndCenter& binBoundsAndCenter) { + bin(prims+begin,end-begin,mapping,binBoundsAndCenter); + } + + /*! merges in other binning information */ + __forceinline void merge (const BinInfoT& other, size_t numBins) + { + + for (size_t i=0; i& mapping, const size_t blocks_shift) const + { + /* sweep from right to left and compute parallel prefix of merged bounds */ + vfloat4 rAreas[BINS]; + vuint4 rCounts[BINS]; + vuint4 count = 0; BBox bx = empty; BBox by = empty; BBox bz = empty; + for (size_t i=mapping.size()-1; i>0; i--) + { + count += counts(i); + rCounts[i] = count; + bx.extend(bounds(i,0)); rAreas[i][0] = expectedApproxHalfArea(bx); + by.extend(bounds(i,1)); rAreas[i][1] = expectedApproxHalfArea(by); + bz.extend(bounds(i,2)); rAreas[i][2] = expectedApproxHalfArea(bz); + rAreas[i][3] = 0.0f; + } + /* sweep from left to right and compute SAH */ + vuint4 blocks_add = (1 << blocks_shift)-1; + vuint4 ii = 1; vfloat4 vbestSAH = pos_inf; vuint4 vbestPos = 0; + count = 0; bx = empty; by = empty; bz = empty; + for (size_t i=1; i> (unsigned int)(blocks_shift); // if blocks_shift >=1 then lCount < 4B and could be represented with an vint4, which would allow for faster vfloat4 conversions. + const vuint4 rCount = (rCounts[i]+blocks_add) >> (unsigned int)(blocks_shift); + const vfloat4 sah = madd(lArea,vfloat4(lCount),rArea*vfloat4(rCount)); + //const vfloat4 sah = madd(lArea,vfloat4(vint4(lCount)),rArea*vfloat4(vint4(rCount))); + + vbestPos = select(sah < vbestSAH,ii ,vbestPos); + vbestSAH = select(sah < vbestSAH,sah,vbestSAH); + } + + /* find best dimension */ + float bestSAH = inf; + int bestDim = -1; + int bestPos = 0; + for (int dim=0; dim<3; dim++) + { + /* ignore zero sized dimensions */ + if (unlikely(mapping.invalid(dim))) + continue; + + /* test if this is a better dimension */ + if (vbestSAH[dim] < bestSAH && vbestPos[dim] != 0) { + bestDim = dim; + bestPos = vbestPos[dim]; + bestSAH = vbestSAH[dim]; + } + } + return Split(bestSAH,bestDim,bestPos,mapping); + } + + /*! calculates extended split information */ + __forceinline void getSplitInfo(const BinMapping& mapping, const Split& split, SplitInfoT& info) const + { + if (split.dim == -1) { + new (&info) SplitInfoT(0,empty,0,empty); + return; + } + + size_t leftCount = 0; + BBox leftBounds = empty; + for (size_t i=0; i<(size_t)split.pos; i++) { + leftCount += counts(i,split.dim); + leftBounds.extend(bounds(i,split.dim)); + } + size_t rightCount = 0; + BBox rightBounds = empty; + for (size_t i=split.pos; i(leftCount,leftBounds,rightCount,rightBounds); + } + + /*! gets the number of primitives left of the split */ + __forceinline size_t getLeftCount(const BinMapping& mapping, const Split& split) const + { + if (unlikely(split.dim == -1)) return -1; + + size_t leftCount = 0; + for (size_t i = 0; i < (size_t)split.pos; i++) { + leftCount += counts(i, split.dim); + } + return leftCount; + } + + /*! gets the number of primitives right of the split */ + __forceinline size_t getRightCount(const BinMapping& mapping, const Split& split) const + { + if (unlikely(split.dim == -1)) return -1; + + size_t rightCount = 0; + for (size_t i = (size_t)split.pos; i + struct BinMapping<16> + { + public: + __forceinline BinMapping() {} + + /*! calculates the mapping */ + template + __forceinline BinMapping(const PrimInfo& pinfo) + { + num = 16; + const vfloat4 eps = 1E-34f; + const vfloat4 diag = max(eps,(vfloat4) pinfo.centBounds.size()); + scale = select(diag > eps,vfloat4(0.99f*num)/diag,vfloat4(0.0f)); + ofs = (vfloat4) pinfo.centBounds.lower; + scale16 = scale; + ofs16 = ofs; + } + + /*! returns number of bins */ + __forceinline size_t size() const { return num; } + + __forceinline vint16 bin16(const Vec3fa& p) const { + return vint16(vint4(floori((vfloat4(p)-ofs)*scale))); + } + + __forceinline vint16 bin16(const vfloat16& p) const { + return floori((p-ofs16)*scale16); + } + + __forceinline int bin_unsafe(const PrimRef& ref, + const vint16& vSplitPos, + const vbool16& splitDimMask) const // FIXME: rename to isLeft + { + const vfloat16 lower(*(vfloat4*)&ref.lower); + const vfloat16 upper(*(vfloat4*)&ref.upper); + const vfloat16 p = lower + upper; + const vint16 i = floori((p-ofs16)*scale16); + return lt(splitDimMask,i,vSplitPos); + } + + /*! returns true if the mapping is invalid in some dimension */ + __forceinline bool invalid(const size_t dim) const { + return scale[dim] == 0.0f; + } + + public: + size_t num; + vfloat4 ofs,scale; //!< linear function that maps to bin ID + vfloat16 ofs16,scale16; //!< linear function that maps to bin ID + }; + + /* 16 bins in-register binner */ + template + struct __aligned(64) BinInfoT<16,PrimRef,BBox3fa> + { + typedef BinSplit<16> Split; + typedef vbool16 vbool; + typedef vint16 vint; + typedef vfloat16 vfloat; + + __forceinline BinInfoT() { + } + + __forceinline BinInfoT(EmptyTy) { + clear(); + } + + /*! clears the bin info */ + __forceinline void clear() + { + lower[0] = lower[1] = lower[2] = pos_inf; + upper[0] = upper[1] = upper[2] = neg_inf; + count[0] = count[1] = count[2] = 0; + } + + + static __forceinline vfloat16 prefix_area_rl(const vfloat16 min_x, + const vfloat16 min_y, + const vfloat16 min_z, + const vfloat16 max_x, + const vfloat16 max_y, + const vfloat16 max_z) + { + const vfloat16 r_min_x = reverse_prefix_min(min_x); + const vfloat16 r_min_y = reverse_prefix_min(min_y); + const vfloat16 r_min_z = reverse_prefix_min(min_z); + const vfloat16 r_max_x = reverse_prefix_max(max_x); + const vfloat16 r_max_y = reverse_prefix_max(max_y); + const vfloat16 r_max_z = reverse_prefix_max(max_z); + const vfloat16 dx = r_max_x - r_min_x; + const vfloat16 dy = r_max_y - r_min_y; + const vfloat16 dz = r_max_z - r_min_z; + const vfloat16 area_rl = madd(dx,dy,madd(dx,dz,dy*dz)); + return area_rl; + } + + static __forceinline vfloat16 prefix_area_lr(const vfloat16 min_x, + const vfloat16 min_y, + const vfloat16 min_z, + const vfloat16 max_x, + const vfloat16 max_y, + const vfloat16 max_z) + { + const vfloat16 r_min_x = prefix_min(min_x); + const vfloat16 r_min_y = prefix_min(min_y); + const vfloat16 r_min_z = prefix_min(min_z); + const vfloat16 r_max_x = prefix_max(max_x); + const vfloat16 r_max_y = prefix_max(max_y); + const vfloat16 r_max_z = prefix_max(max_z); + const vfloat16 dx = r_max_x - r_min_x; + const vfloat16 dy = r_max_y - r_min_y; + const vfloat16 dz = r_max_z - r_min_z; + const vfloat16 area_lr = madd(dx,dy,madd(dx,dz,dy*dz)); + return area_lr; + } + + + /*! bins an array of primitives */ + __forceinline void bin (const PrimRef* prims, size_t N, const BinMapping<16>& mapping) + { + if (unlikely(N == 0)) return; + + const vfloat16 init_min(pos_inf); + const vfloat16 init_max(neg_inf); + + vfloat16 min_x0,min_x1,min_x2; + vfloat16 min_y0,min_y1,min_y2; + vfloat16 min_z0,min_z1,min_z2; + vfloat16 max_x0,max_x1,max_x2; + vfloat16 max_y0,max_y1,max_y2; + vfloat16 max_z0,max_z1,max_z2; + vuint16 count0,count1,count2; + + min_x0 = init_min; + min_x1 = init_min; + min_x2 = init_min; + min_y0 = init_min; + min_y1 = init_min; + min_y2 = init_min; + min_z0 = init_min; + min_z1 = init_min; + min_z2 = init_min; + + max_x0 = init_max; + max_x1 = init_max; + max_x2 = init_max; + max_y0 = init_max; + max_y1 = init_max; + max_y2 = init_max; + max_z0 = init_max; + max_z1 = init_max; + max_z2 = init_max; + + count0 = zero; + count1 = zero; + count2 = zero; + + const vint16 step16(step); + size_t i; + for (i=0; i(binA); + const vint16 bin1 = shuffle<1>(binA); + const vint16 bin2 = shuffle<2>(binA); + + const vbool16 m_update_x = step16 == bin0; + const vbool16 m_update_y = step16 == bin1; + const vbool16 m_update_z = step16 == bin2; + + assert(popcnt((size_t)m_update_x) == 1); + assert(popcnt((size_t)m_update_y) == 1); + assert(popcnt((size_t)m_update_z) == 1); + + min_x0 = mask_min(m_update_x,min_x0,min_x0,b_min_x); + min_y0 = mask_min(m_update_x,min_y0,min_y0,b_min_y); + min_z0 = mask_min(m_update_x,min_z0,min_z0,b_min_z); + // ------------------------------------------------------------------------ + max_x0 = mask_max(m_update_x,max_x0,max_x0,b_max_x); + max_y0 = mask_max(m_update_x,max_y0,max_y0,b_max_y); + max_z0 = mask_max(m_update_x,max_z0,max_z0,b_max_z); + // ------------------------------------------------------------------------ + min_x1 = mask_min(m_update_y,min_x1,min_x1,b_min_x); + min_y1 = mask_min(m_update_y,min_y1,min_y1,b_min_y); + min_z1 = mask_min(m_update_y,min_z1,min_z1,b_min_z); + // ------------------------------------------------------------------------ + max_x1 = mask_max(m_update_y,max_x1,max_x1,b_max_x); + max_y1 = mask_max(m_update_y,max_y1,max_y1,b_max_y); + max_z1 = mask_max(m_update_y,max_z1,max_z1,b_max_z); + // ------------------------------------------------------------------------ + min_x2 = mask_min(m_update_z,min_x2,min_x2,b_min_x); + min_y2 = mask_min(m_update_z,min_y2,min_y2,b_min_y); + min_z2 = mask_min(m_update_z,min_z2,min_z2,b_min_z); + // ------------------------------------------------------------------------ + max_x2 = mask_max(m_update_z,max_x2,max_x2,b_max_x); + max_y2 = mask_max(m_update_z,max_y2,max_y2,b_max_y); + max_z2 = mask_max(m_update_z,max_z2,max_z2,b_max_z); + // ------------------------------------------------------------------------ + count0 = mask_add(m_update_x,count0,count0,vuint16(1)); + count1 = mask_add(m_update_y,count1,count1,vuint16(1)); + count2 = mask_add(m_update_z,count2,count2,vuint16(1)); + } + + + /* B */ + { + const vfloat16 b_min_x = prims[i+1].lower.x; + const vfloat16 b_min_y = prims[i+1].lower.y; + const vfloat16 b_min_z = prims[i+1].lower.z; + const vfloat16 b_max_x = prims[i+1].upper.x; + const vfloat16 b_max_y = prims[i+1].upper.y; + const vfloat16 b_max_z = prims[i+1].upper.z; + + const vint16 bin0 = shuffle<0>(binB); + const vint16 bin1 = shuffle<1>(binB); + const vint16 bin2 = shuffle<2>(binB); + + const vbool16 m_update_x = step16 == bin0; + const vbool16 m_update_y = step16 == bin1; + const vbool16 m_update_z = step16 == bin2; + + assert(popcnt((size_t)m_update_x) == 1); + assert(popcnt((size_t)m_update_y) == 1); + assert(popcnt((size_t)m_update_z) == 1); + + min_x0 = mask_min(m_update_x,min_x0,min_x0,b_min_x); + min_y0 = mask_min(m_update_x,min_y0,min_y0,b_min_y); + min_z0 = mask_min(m_update_x,min_z0,min_z0,b_min_z); + // ------------------------------------------------------------------------ + max_x0 = mask_max(m_update_x,max_x0,max_x0,b_max_x); + max_y0 = mask_max(m_update_x,max_y0,max_y0,b_max_y); + max_z0 = mask_max(m_update_x,max_z0,max_z0,b_max_z); + // ------------------------------------------------------------------------ + min_x1 = mask_min(m_update_y,min_x1,min_x1,b_min_x); + min_y1 = mask_min(m_update_y,min_y1,min_y1,b_min_y); + min_z1 = mask_min(m_update_y,min_z1,min_z1,b_min_z); + // ------------------------------------------------------------------------ + max_x1 = mask_max(m_update_y,max_x1,max_x1,b_max_x); + max_y1 = mask_max(m_update_y,max_y1,max_y1,b_max_y); + max_z1 = mask_max(m_update_y,max_z1,max_z1,b_max_z); + // ------------------------------------------------------------------------ + min_x2 = mask_min(m_update_z,min_x2,min_x2,b_min_x); + min_y2 = mask_min(m_update_z,min_y2,min_y2,b_min_y); + min_z2 = mask_min(m_update_z,min_z2,min_z2,b_min_z); + // ------------------------------------------------------------------------ + max_x2 = mask_max(m_update_z,max_x2,max_x2,b_max_x); + max_y2 = mask_max(m_update_z,max_y2,max_y2,b_max_y); + max_z2 = mask_max(m_update_z,max_z2,max_z2,b_max_z); + // ------------------------------------------------------------------------ + count0 = mask_add(m_update_x,count0,count0,vuint16(1)); + count1 = mask_add(m_update_y,count1,count1,vuint16(1)); + count2 = mask_add(m_update_z,count2,count2,vuint16(1)); + } + + } + + if (i < N) + { + const BBox3fa prim0 = prims[i].bounds(); + const vfloat16 center0 = vfloat16((vfloat4)prim0.lower) + vfloat16((vfloat4)prim0.upper); + const vint16 bin = mapping.bin16(center0); + + const vfloat16 b_min_x = prims[i].lower.x; + const vfloat16 b_min_y = prims[i].lower.y; + const vfloat16 b_min_z = prims[i].lower.z; + const vfloat16 b_max_x = prims[i].upper.x; + const vfloat16 b_max_y = prims[i].upper.y; + const vfloat16 b_max_z = prims[i].upper.z; + + const vint16 bin0 = shuffle<0>(bin); + const vint16 bin1 = shuffle<1>(bin); + const vint16 bin2 = shuffle<2>(bin); + + const vbool16 m_update_x = step16 == bin0; + const vbool16 m_update_y = step16 == bin1; + const vbool16 m_update_z = step16 == bin2; + + assert(popcnt((size_t)m_update_x) == 1); + assert(popcnt((size_t)m_update_y) == 1); + assert(popcnt((size_t)m_update_z) == 1); + + min_x0 = mask_min(m_update_x,min_x0,min_x0,b_min_x); + min_y0 = mask_min(m_update_x,min_y0,min_y0,b_min_y); + min_z0 = mask_min(m_update_x,min_z0,min_z0,b_min_z); + // ------------------------------------------------------------------------ + max_x0 = mask_max(m_update_x,max_x0,max_x0,b_max_x); + max_y0 = mask_max(m_update_x,max_y0,max_y0,b_max_y); + max_z0 = mask_max(m_update_x,max_z0,max_z0,b_max_z); + // ------------------------------------------------------------------------ + min_x1 = mask_min(m_update_y,min_x1,min_x1,b_min_x); + min_y1 = mask_min(m_update_y,min_y1,min_y1,b_min_y); + min_z1 = mask_min(m_update_y,min_z1,min_z1,b_min_z); + // ------------------------------------------------------------------------ + max_x1 = mask_max(m_update_y,max_x1,max_x1,b_max_x); + max_y1 = mask_max(m_update_y,max_y1,max_y1,b_max_y); + max_z1 = mask_max(m_update_y,max_z1,max_z1,b_max_z); + // ------------------------------------------------------------------------ + min_x2 = mask_min(m_update_z,min_x2,min_x2,b_min_x); + min_y2 = mask_min(m_update_z,min_y2,min_y2,b_min_y); + min_z2 = mask_min(m_update_z,min_z2,min_z2,b_min_z); + // ------------------------------------------------------------------------ + max_x2 = mask_max(m_update_z,max_x2,max_x2,b_max_x); + max_y2 = mask_max(m_update_z,max_y2,max_y2,b_max_y); + max_z2 = mask_max(m_update_z,max_z2,max_z2,b_max_z); + // ------------------------------------------------------------------------ + count0 = mask_add(m_update_x,count0,count0,vuint16(1)); + count1 = mask_add(m_update_y,count1,count1,vuint16(1)); + count2 = mask_add(m_update_z,count2,count2,vuint16(1)); + } + + lower[0] = Vec3vf16( min_x0, min_y0, min_z0 ); + lower[1] = Vec3vf16( min_x1, min_y1, min_z1 ); + lower[2] = Vec3vf16( min_x2, min_y2, min_z2 ); + + upper[0] = Vec3vf16( max_x0, max_y0, max_z0 ); + upper[1] = Vec3vf16( max_x1, max_y1, max_z1 ); + upper[2] = Vec3vf16( max_x2, max_y2, max_z2 ); + + count[0] = count0; + count[1] = count1; + count[2] = count2; + } + + __forceinline void bin(const PrimRef* prims, size_t begin, size_t end, const BinMapping<16>& mapping) { + bin(prims+begin,end-begin,mapping); + } + + /*! merges in other binning information */ + __forceinline void merge (const BinInfoT& other, size_t numBins) + { + for (size_t i=0; i<3; i++) + { + lower[i] = min(lower[i],other.lower[i]); + upper[i] = max(upper[i],other.upper[i]); + count[i] += other.count[i]; + } + } + + /*! reducesr binning information */ + static __forceinline const BinInfoT reduce (const BinInfoT& a, const BinInfoT& b) + { + BinInfoT c; + for (size_t i=0; i<3; i++) + { + c.counts[i] = a.counts[i] + b.counts[i]; + c.lower[i] = min(a.lower[i],b.lower[i]); + c.upper[i] = max(a.upper[i],b.upper[i]); + } + return c; + } + + /*! finds the best split by scanning binning information */ + __forceinline Split best(const BinMapping<16>& mapping, const size_t blocks_shift) const + { + /* find best dimension */ + float bestSAH = inf; + int bestDim = -1; + int bestPos = 0; + const vuint16 blocks_add = (1 << blocks_shift)-1; + const vfloat16 inf(pos_inf); + for (size_t dim=0; dim<3; dim++) + { + /* ignore zero sized dimensions */ + if (unlikely(mapping.invalid(dim))) + continue; + + const vfloat16 rArea16 = prefix_area_rl(lower[dim].x,lower[dim].y,lower[dim].z, upper[dim].x,upper[dim].y,upper[dim].z); + const vfloat16 lArea16 = prefix_area_lr(lower[dim].x,lower[dim].y,lower[dim].z, upper[dim].x,upper[dim].y,upper[dim].z); + const vuint16 lCount16 = prefix_sum(count[dim]); + const vuint16 rCount16 = reverse_prefix_sum(count[dim]); + + /* compute best split in this dimension */ + const vfloat16 leftArea = lArea16; + const vfloat16 rightArea = align_shift_right<1>(zero,rArea16); + const vuint16 lC = lCount16; + const vuint16 rC = align_shift_right<1>(zero,rCount16); + const vuint16 leftCount = ( lC + blocks_add) >> blocks_shift; + const vuint16 rightCount = ( rC + blocks_add) >> blocks_shift; + const vbool16 valid = (leftArea < inf) & (rightArea < inf) & vbool16(0x7fff); // handles inf entries + const vfloat16 sah = select(valid,madd(leftArea,vfloat16(leftCount),rightArea*vfloat16(rightCount)),vfloat16(pos_inf)); + /* test if this is a better dimension */ + if (any(sah < vfloat16(bestSAH))) + { + const size_t index = select_min(sah); + assert(index < 15); + assert(sah[index] < bestSAH); + bestDim = dim; + bestPos = index+1; + bestSAH = sah[index]; + } + } + + return Split(bestSAH,bestDim,bestPos,mapping); + + } + + /*! calculates extended split information */ + __forceinline void getSplitInfo(const BinMapping<16>& mapping, const Split& split, SplitInfo& info) const + { + if (split.dim == -1) { + new (&info) SplitInfo(0,empty,0,empty); + return; + } + // FIXME: horizontal reduction! + + size_t leftCount = 0; + BBox3fa leftBounds = empty; + for (size_t i=0; i<(size_t)split.pos; i++) { + leftCount += count[split.dim][i]; + Vec3fa bounds_lower(lower[split.dim].x[i],lower[split.dim].y[i],lower[split.dim].z[i]); + Vec3fa bounds_upper(upper[split.dim].x[i],upper[split.dim].y[i],upper[split.dim].z[i]); + leftBounds.extend(BBox3fa(bounds_lower,bounds_upper)); + } + size_t rightCount = 0; + BBox3fa rightBounds = empty; + for (size_t i=split.pos; i& mapping, const Split& split) const + { + if (unlikely(split.dim == -1)) return -1; + + size_t leftCount = 0; + for (size_t i = 0; i < (size_t)split.pos; i++) { + leftCount += count[split.dim][i]; + } + return leftCount; + } + + /*! gets the number of primitives right of the split */ + __forceinline size_t getRightCount(const BinMapping<16>& mapping, const Split& split) const + { + if (unlikely(split.dim == -1)) return -1; + + size_t rightCount = 0; + for (size_t i = (size_t)split.pos; i + __forceinline void bin_parallel(BinInfoT& binner, const PrimRef* prims, size_t begin, size_t end, size_t blockSize, size_t parallelThreshold, const BinMapping& mapping) + { + if (likely(end-begin < parallelThreshold)) { + binner.bin(prims,begin,end,mapping); + } else { + binner = parallel_reduce(begin,end,blockSize,binner, + [&](const range& r) -> BinInfoT { BinInfoT binner(empty); binner.bin(prims + r.begin(), r.size(), mapping); return binner; }, + [&](const BinInfoT& b0, const BinInfoT& b1) -> BinInfoT { BinInfoT r = b0; r.merge(b1, mapping.size()); return r; }); + } + } + + template + __forceinline void bin_parallel(BinInfoT& binner, const PrimRef* prims, size_t begin, size_t end, size_t blockSize, size_t parallelThreshold, const BinMapping& mapping, const BinBoundsAndCenter& binBoundsAndCenter) + { + if (likely(end-begin < parallelThreshold)) { + binner.bin(prims,begin,end,mapping,binBoundsAndCenter); + } else { + binner = parallel_reduce(begin,end,blockSize,binner, + [&](const range& r) -> BinInfoT { BinInfoT binner(empty); binner.bin(prims + r.begin(), r.size(), mapping, binBoundsAndCenter); return binner; }, + [&](const BinInfoT& b0, const BinInfoT& b1) -> BinInfoT { BinInfoT r = b0; r.merge(b1, mapping.size()); return r; }); + } + } + + template + __forceinline void bin_serial_or_parallel(BinInfoT& binner, const PrimRef* prims, size_t begin, size_t end, size_t blockSize, const BinMapping& mapping) + { + if (!parallel) { + binner.bin(prims,begin,end,mapping); + } else { + binner = parallel_reduce(begin,end,blockSize,binner, + [&](const range& r) -> BinInfoT { BinInfoT binner(empty); binner.bin(prims + r.begin(), r.size(), mapping); return binner; }, + [&](const BinInfoT& b0, const BinInfoT& b1) -> BinInfoT { BinInfoT r = b0; r.merge(b1, mapping.size()); return r; }); + } + } + + template + __forceinline void bin_serial_or_parallel(BinInfoT& binner, const PrimRef* prims, size_t begin, size_t end, size_t blockSize, const BinMapping& mapping, const BinBoundsAndCenter& binBoundsAndCenter) + { + if (!parallel) { + binner.bin(prims,begin,end,mapping,binBoundsAndCenter); + } else { + binner = parallel_reduce(begin,end,blockSize,binner, + [&](const range& r) -> BinInfoT { BinInfoT binner(empty); binner.bin(prims + r.begin(), r.size(), mapping, binBoundsAndCenter); return binner; }, + [&](const BinInfoT& b0, const BinInfoT& b1) -> BinInfoT { BinInfoT r = b0; r.merge(b1, mapping.size()); return r; }); + } + } +} diff --git a/thirdparty/embree/kernels/builders/heuristic_binning_array_aligned.h b/thirdparty/embree/kernels/builders/heuristic_binning_array_aligned.h new file mode 100644 index 000000000000..a4c272f01511 --- /dev/null +++ b/thirdparty/embree/kernels/builders/heuristic_binning_array_aligned.h @@ -0,0 +1,205 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "heuristic_binning.h" + +namespace embree +{ + namespace isa + { + struct PrimInfoRange : public CentGeomBBox3fa, public range + { + __forceinline PrimInfoRange () { + } + + __forceinline PrimInfoRange(const PrimInfo& pinfo) + : CentGeomBBox3fa(pinfo), range(pinfo.begin,pinfo.end) {} + + __forceinline PrimInfoRange(EmptyTy) + : CentGeomBBox3fa(EmptyTy()), range(0,0) {} + + __forceinline PrimInfoRange (size_t begin, size_t end, const CentGeomBBox3fa& centGeomBounds) + : CentGeomBBox3fa(centGeomBounds), range(begin,end) {} + + __forceinline float leafSAH() const { + return expectedApproxHalfArea(geomBounds)*float(size()); + } + + __forceinline float leafSAH(size_t block_shift) const { + return expectedApproxHalfArea(geomBounds)*float((size()+(size_t(1)<> block_shift); + } + }; + + /*! Performs standard object binning */ + template + struct HeuristicArrayBinningSAH + { + typedef BinSplit Split; + typedef BinInfoT Binner; + typedef range Set; + +#if defined(__AVX512ER__) // KNL + static const size_t PARALLEL_THRESHOLD = 4*768; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 768; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 768; +#else + static const size_t PARALLEL_THRESHOLD = 3 * 1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 1024; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; +#endif + __forceinline HeuristicArrayBinningSAH () + : prims(nullptr) {} + + /*! remember prim array */ + __forceinline HeuristicArrayBinningSAH (PrimRef* prims) + : prims(prims) {} + + /*! finds the best split */ + __noinline const Split find(const PrimInfoRange& pinfo, const size_t logBlockSize) + { + if (likely(pinfo.size() < PARALLEL_THRESHOLD)) + return find_template(pinfo,logBlockSize); + else + return find_template(pinfo,logBlockSize); + } + + template + __forceinline const Split find_template(const PrimInfoRange& pinfo, const size_t logBlockSize) + { + Binner binner(empty); + const BinMapping mapping(pinfo); + bin_serial_or_parallel(binner,prims,pinfo.begin(),pinfo.end(),PARALLEL_FIND_BLOCK_SIZE,mapping); + return binner.best(mapping,logBlockSize); + } + + /*! array partitioning */ + __forceinline void split(const Split& split, const PrimInfoRange& pinfo, PrimInfoRange& linfo, PrimInfoRange& rinfo) + { + if (likely(pinfo.size() < PARALLEL_THRESHOLD)) + split_template(split,pinfo,linfo,rinfo); + else + split_template(split,pinfo,linfo,rinfo); + } + + template + __forceinline void split_template(const Split& split, const PrimInfoRange& set, PrimInfoRange& lset, PrimInfoRange& rset) + { + if (!split.valid()) { + deterministic_order(set); + return splitFallback(set,lset,rset); + } + + const size_t begin = set.begin(); + const size_t end = set.end(); + CentGeomBBox3fa local_left(empty); + CentGeomBBox3fa local_right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + const typename Binner::vint vSplitPos(splitPos); + const typename Binner::vbool vSplitMask(splitDimMask); + auto isLeft = [&] (const PrimRef &ref) { return split.mapping.bin_unsafe(ref,vSplitPos,vSplitMask); }; + + size_t center = 0; + if (!parallel) + center = serial_partitioning(prims,begin,end,local_left,local_right,isLeft, + [] (CentGeomBBox3fa& pinfo,const PrimRef& ref) { pinfo.extend_center2(ref); }); + else + center = parallel_partitioning( + prims,begin,end,EmptyTy(),local_left,local_right,isLeft, + [] (CentGeomBBox3fa& pinfo,const PrimRef& ref) { pinfo.extend_center2(ref); }, + [] (CentGeomBBox3fa& pinfo0,const CentGeomBBox3fa& pinfo1) { pinfo0.merge(pinfo1); }, + PARALLEL_PARTITION_BLOCK_SIZE); + + new (&lset) PrimInfoRange(begin,center,local_left); + new (&rset) PrimInfoRange(center,end,local_right); + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + } + + void deterministic_order(const PrimInfoRange& pinfo) + { + /* required as parallel partition destroys original primitive order */ + std::sort(&prims[pinfo.begin()],&prims[pinfo.end()]); + } + + void splitFallback(const PrimInfoRange& pinfo, PrimInfoRange& linfo, PrimInfoRange& rinfo) + { + const size_t begin = pinfo.begin(); + const size_t end = pinfo.end(); + const size_t center = (begin + end)/2; + + CentGeomBBox3fa left(empty); + for (size_t i=begin; i& range, PrimInfoRange& linfo, PrimInfoRange& rinfo) + { + assert(range.size() > 1); + CentGeomBBox3fa left(empty); + CentGeomBBox3fa right(empty); + unsigned int geomID = prims[range.begin()].geomID(); + size_t center = serial_partitioning(prims,range.begin(),range.end(),left,right, + [&] ( const PrimRef& prim ) { return prim.geomID() == geomID; }, + [ ] ( CentGeomBBox3fa& a, const PrimRef& ref ) { a.extend_center2(ref); }); + + new (&linfo) PrimInfoRange(range.begin(),center,left); + new (&rinfo) PrimInfoRange(center,range.end(),right); + } + + private: + PrimRef* const prims; + }; + + /*! Performs standard object binning */ + template + struct HeuristicArrayBinningMB + { + typedef BinSplit Split; + typedef typename PrimRefMB::BBox BBox; + typedef BinInfoT ObjectBinner; + static const size_t PARALLEL_THRESHOLD = 3 * 1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 1024; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; + + /*! finds the best split */ + const Split find(const SetMB& set, const size_t logBlockSize) + { + ObjectBinner binner(empty); + const BinMapping mapping(set.size(),set.centBounds); + bin_parallel(binner,set.prims->data(),set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,PARALLEL_THRESHOLD,mapping); + Split osplit = binner.best(mapping,logBlockSize); + osplit.sah *= set.time_range.size(); + if (!osplit.valid()) osplit.data = Split::SPLIT_FALLBACK; // use fallback split + return osplit; + } + + /*! array partitioning */ + __forceinline void split(const Split& split, const SetMB& set, SetMB& lset, SetMB& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfoMB left = empty; + PrimInfoMB right = empty; + const vint4 vSplitPos(split.pos); + const vbool4 vSplitMask(1 << split.dim); + auto isLeft = [&] (const PrimRefMB &ref) { return any(((vint4)split.mapping.bin_unsafe(ref) < vSplitPos) & vSplitMask); }; + auto reduction = [] (PrimInfoMB& pinfo, const PrimRefMB& ref) { pinfo.add_primref(ref); }; + auto reduction2 = [] (PrimInfoMB& pinfo0,const PrimInfoMB& pinfo1) { pinfo0.merge(pinfo1); }; + size_t center = parallel_partitioning(set.prims->data(),begin,end,EmptyTy(),left,right,isLeft,reduction,reduction2,PARALLEL_PARTITION_BLOCK_SIZE,PARALLEL_THRESHOLD); + new (&lset) SetMB(left, set.prims,range(begin,center),set.time_range); + new (&rset) SetMB(right,set.prims,range(center,end ),set.time_range); + } + }; + } +} diff --git a/thirdparty/embree/kernels/builders/heuristic_binning_array_unaligned.h b/thirdparty/embree/kernels/builders/heuristic_binning_array_unaligned.h new file mode 100644 index 000000000000..137024458621 --- /dev/null +++ b/thirdparty/embree/kernels/builders/heuristic_binning_array_unaligned.h @@ -0,0 +1,302 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "heuristic_binning.h" + +namespace embree +{ + namespace isa + { + /*! Performs standard object binning */ + template + struct UnalignedHeuristicArrayBinningSAH + { + typedef BinSplit Split; + typedef BinInfoT Binner; + typedef range Set; + + __forceinline UnalignedHeuristicArrayBinningSAH () // FIXME: required? + : scene(nullptr), prims(nullptr) {} + + /*! remember prim array */ + __forceinline UnalignedHeuristicArrayBinningSAH (Scene* scene, PrimRef* prims) + : scene(scene), prims(prims) {} + + const LinearSpace3fa computeAlignedSpace(const range& set) + { + Vec3fa axis(0,0,1); + uint64_t bestGeomPrimID = -1; + + /*! find curve with minimum ID that defines valid direction */ + for (size_t i=set.begin(); i= bestGeomPrimID) continue; + const Vec3fa axis1 = scene->get(geomID)->computeDirection(primID); + if (sqr_length(axis1) > 1E-18f) { + axis = normalize(axis1); + bestGeomPrimID = geomprimID; + } + } + return frame(axis).transposed(); + } + + const PrimInfo computePrimInfo(const range& set, const LinearSpace3fa& space) + { + auto computeBounds = [&](const range& r) -> CentGeomBBox3fa + { + CentGeomBBox3fa bounds(empty); + for (size_t i=r.begin(); iget(prims[i].geomID()); + bounds.extend(mesh->vbounds(space,prims[i].primID())); + } + return bounds; + }; + + const CentGeomBBox3fa bounds = parallel_reduce(set.begin(), set.end(), size_t(1024), size_t(4096), + CentGeomBBox3fa(empty), computeBounds, CentGeomBBox3fa::merge2); + + return PrimInfo(set.begin(),set.end(),bounds); + } + + struct BinBoundsAndCenter + { + __forceinline BinBoundsAndCenter(Scene* scene, const LinearSpace3fa& space) + : scene(scene), space(space) {} + + /*! returns center for binning */ + __forceinline Vec3fa binCenter(const PrimRef& ref) const + { + Geometry* mesh = (Geometry*) scene->get(ref.geomID()); + BBox3fa bounds = mesh->vbounds(space,ref.primID()); + return embree::center2(bounds); + } + + /*! returns bounds and centroid used for binning */ + __forceinline void binBoundsAndCenter(const PrimRef& ref, BBox3fa& bounds_o, Vec3fa& center_o) const + { + Geometry* mesh = (Geometry*) scene->get(ref.geomID()); + BBox3fa bounds = mesh->vbounds(space,ref.primID()); + bounds_o = bounds; + center_o = embree::center2(bounds); + } + + private: + Scene* scene; + const LinearSpace3fa space; + }; + + /*! finds the best split */ + __forceinline const Split find(const PrimInfoRange& pinfo, const size_t logBlockSize, const LinearSpace3fa& space) + { + if (likely(pinfo.size() < 10000)) + return find_template(pinfo,logBlockSize,space); + else + return find_template(pinfo,logBlockSize,space); + } + + /*! finds the best split */ + template + const Split find_template(const PrimInfoRange& set, const size_t logBlockSize, const LinearSpace3fa& space) + { + Binner binner(empty); + const BinMapping mapping(set); + BinBoundsAndCenter binBoundsAndCenter(scene,space); + bin_serial_or_parallel(binner,prims,set.begin(),set.end(),size_t(4096),mapping,binBoundsAndCenter); + return binner.best(mapping,logBlockSize); + } + + /*! array partitioning */ + __forceinline void split(const Split& split, const LinearSpace3fa& space, const Set& set, PrimInfoRange& lset, PrimInfoRange& rset) + { + if (likely(set.size() < 10000)) + split_template(split,space,set,lset,rset); + else + split_template(split,space,set,lset,rset); + } + + /*! array partitioning */ + template + __forceinline void split_template(const Split& split, const LinearSpace3fa& space, const Set& set, PrimInfoRange& lset, PrimInfoRange& rset) + { + if (!split.valid()) { + deterministic_order(set); + return splitFallback(set,lset,rset); + } + + const size_t begin = set.begin(); + const size_t end = set.end(); + CentGeomBBox3fa local_left(empty); + CentGeomBBox3fa local_right(empty); + const int splitPos = split.pos; + const int splitDim = split.dim; + BinBoundsAndCenter binBoundsAndCenter(scene,space); + + size_t center = 0; + if (likely(set.size() < 10000)) + center = serial_partitioning(prims,begin,end,local_left,local_right, + [&] (const PrimRef& ref) { return split.mapping.bin_unsafe(ref,binBoundsAndCenter)[splitDim] < splitPos; }, + [] (CentGeomBBox3fa& pinfo,const PrimRef& ref) { pinfo.extend_center2(ref); }); + else + center = parallel_partitioning(prims,begin,end,EmptyTy(),local_left,local_right, + [&] (const PrimRef& ref) { return split.mapping.bin_unsafe(ref,binBoundsAndCenter)[splitDim] < splitPos; }, + [] (CentGeomBBox3fa& pinfo,const PrimRef& ref) { pinfo.extend_center2(ref); }, + [] (CentGeomBBox3fa& pinfo0,const CentGeomBBox3fa& pinfo1) { pinfo0.merge(pinfo1); }, + 128); + + new (&lset) PrimInfoRange(begin,center,local_left); + new (&rset) PrimInfoRange(center,end,local_right); + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + } + + void deterministic_order(const range& set) + { + /* required as parallel partition destroys original primitive order */ + std::sort(&prims[set.begin()],&prims[set.end()]); + } + + void splitFallback(const range& set, PrimInfoRange& lset, PrimInfoRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + const size_t center = (begin + end)/2; + + CentGeomBBox3fa left(empty); + for (size_t i=begin; i + struct UnalignedHeuristicArrayBinningMB + { + typedef BinSplit Split; + typedef typename PrimRefMB::BBox BBox; + typedef BinInfoT ObjectBinner; + + static const size_t PARALLEL_THRESHOLD = 3 * 1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 1024; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; + + UnalignedHeuristicArrayBinningMB(Scene* scene) + : scene(scene) {} + + const LinearSpace3fa computeAlignedSpaceMB(Scene* scene, const SetMB& set) + { + Vec3fa axis0(0,0,1); + uint64_t bestGeomPrimID = -1; + + /*! find curve with minimum ID that defines valid direction */ + for (size_t i=set.begin(); i= bestGeomPrimID) continue; + + const Geometry* mesh = scene->get(geomID); + const range tbounds = mesh->timeSegmentRange(set.time_range); + if (tbounds.size() == 0) continue; + + const size_t t = (tbounds.begin()+tbounds.end())/2; + const Vec3fa axis1 = mesh->computeDirection(primID,t); + if (sqr_length(axis1) > 1E-18f) { + axis0 = normalize(axis1); + bestGeomPrimID = geomprimID; + } + } + + return frame(axis0).transposed(); + } + + struct BinBoundsAndCenter + { + __forceinline BinBoundsAndCenter(Scene* scene, BBox1f time_range, const LinearSpace3fa& space) + : scene(scene), time_range(time_range), space(space) {} + + /*! returns center for binning */ + template + __forceinline Vec3fa binCenter(const PrimRef& ref) const + { + Geometry* mesh = scene->get(ref.geomID()); + LBBox3fa lbounds = mesh->vlinearBounds(space,ref.primID(),time_range); + return center2(lbounds.interpolate(0.5f)); + } + + /*! returns bounds and centroid used for binning */ + __noinline void binBoundsAndCenter (const PrimRefMB& ref, BBox3fa& bounds_o, Vec3fa& center_o) const // __noinline is workaround for ICC16 bug under MacOSX + { + Geometry* mesh = scene->get(ref.geomID()); + LBBox3fa lbounds = mesh->vlinearBounds(space,ref.primID(),time_range); + bounds_o = lbounds.interpolate(0.5f); + center_o = center2(bounds_o); + } + + /*! returns bounds and centroid used for binning */ + __noinline void binBoundsAndCenter (const PrimRefMB& ref, LBBox3fa& bounds_o, Vec3fa& center_o) const // __noinline is workaround for ICC16 bug under MacOSX + { + Geometry* mesh = scene->get(ref.geomID()); + LBBox3fa lbounds = mesh->vlinearBounds(space,ref.primID(),time_range); + bounds_o = lbounds; + center_o = center2(lbounds.interpolate(0.5f)); + } + + private: + Scene* scene; + BBox1f time_range; + const LinearSpace3fa space; + }; + + /*! finds the best split */ + const Split find(const SetMB& set, const size_t logBlockSize, const LinearSpace3fa& space) + { + BinBoundsAndCenter binBoundsAndCenter(scene,set.time_range,space); + ObjectBinner binner(empty); + const BinMapping mapping(set.size(),set.centBounds); + bin_parallel(binner,set.prims->data(),set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,PARALLEL_THRESHOLD,mapping,binBoundsAndCenter); + Split osplit = binner.best(mapping,logBlockSize); + osplit.sah *= set.time_range.size(); + if (!osplit.valid()) osplit.data = Split::SPLIT_FALLBACK; // use fallback split + return osplit; + } + + /*! array partitioning */ + __forceinline void split(const Split& split, const LinearSpace3fa& space, const SetMB& set, SetMB& lset, SetMB& rset) + { + BinBoundsAndCenter binBoundsAndCenter(scene,set.time_range,space); + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfoMB left = empty; + PrimInfoMB right = empty; + const vint4 vSplitPos(split.pos); + const vbool4 vSplitMask(1 << split.dim); + auto isLeft = [&] (const PrimRefMB &ref) { return any(((vint4)split.mapping.bin_unsafe(ref,binBoundsAndCenter) < vSplitPos) & vSplitMask); }; + auto reduction = [] (PrimInfoMB& pinfo, const PrimRefMB& ref) { pinfo.add_primref(ref); }; + auto reduction2 = [] (PrimInfoMB& pinfo0,const PrimInfoMB& pinfo1) { pinfo0.merge(pinfo1); }; + size_t center = parallel_partitioning(set.prims->data(),begin,end,EmptyTy(),left,right,isLeft,reduction,reduction2,PARALLEL_PARTITION_BLOCK_SIZE,PARALLEL_THRESHOLD); + new (&lset) SetMB(left,set.prims,range(begin,center),set.time_range); + new (&rset) SetMB(right,set.prims,range(center,end ),set.time_range); + } + + private: + Scene* scene; + }; + } +} diff --git a/thirdparty/embree/kernels/builders/heuristic_openmerge_array.h b/thirdparty/embree/kernels/builders/heuristic_openmerge_array.h new file mode 100644 index 000000000000..21f18c0208f5 --- /dev/null +++ b/thirdparty/embree/kernels/builders/heuristic_openmerge_array.h @@ -0,0 +1,443 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +// TODO: +// - adjust parallel build thresholds +// - openNodesBasedOnExtend should consider max extended size + +#pragma once + +#include "heuristic_binning.h" +#include "heuristic_spatial.h" + +/* stop opening of all bref.geomIDs are the same */ +#define EQUAL_GEOMID_STOP_CRITERIA 1 + +/* 10% spatial extend threshold */ +#define MAX_EXTEND_THRESHOLD 0.1f + +/* maximum is 8 children */ +#define MAX_OPENED_CHILD_NODES 8 + +/* open until all build refs are below threshold size in one step */ +#define USE_LOOP_OPENING 0 + +namespace embree +{ + namespace isa + { + /*! Performs standard object binning */ + template + struct HeuristicArrayOpenMergeSAH + { + typedef BinSplit Split; + typedef BinInfoT Binner; + + static const size_t PARALLEL_THRESHOLD = 1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 512; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; + + static const size_t MOVE_STEP_SIZE = 64; + static const size_t CREATE_SPLITS_STEP_SIZE = 128; + + __forceinline HeuristicArrayOpenMergeSAH () + : prims0(nullptr) {} + + /*! remember prim array */ + __forceinline HeuristicArrayOpenMergeSAH (const NodeOpenerFunc& nodeOpenerFunc, PrimRef* prims0, size_t max_open_size) + : prims0(prims0), nodeOpenerFunc(nodeOpenerFunc), max_open_size(max_open_size) + { + assert(max_open_size <= MAX_OPENED_CHILD_NODES); + } + + struct OpenHeuristic + { + __forceinline OpenHeuristic( const PrimInfoExtRange& pinfo ) + { + const Vec3fa diag = pinfo.geomBounds.size(); + dim = maxDim(diag); + assert(diag[dim] > 0.0f); + inv_max_extend = 1.0f / diag[dim]; + } + + __forceinline bool operator () ( PrimRef& prim ) const { + return !prim.node.isLeaf() && prim.bounds().size()[dim] * inv_max_extend > MAX_EXTEND_THRESHOLD; + } + + private: + size_t dim; + float inv_max_extend; + }; + + /*! compute extended ranges */ + __forceinline void setExtentedRanges(const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset, const size_t lweight, const size_t rweight) + { + assert(set.ext_range_size() > 0); + const float left_factor = (float)lweight / (lweight + rweight); + const size_t ext_range_size = set.ext_range_size(); + const size_t left_ext_range_size = min((size_t)(floorf(left_factor * ext_range_size)),ext_range_size); + const size_t right_ext_range_size = ext_range_size - left_ext_range_size; + lset.set_ext_range(lset.end() + left_ext_range_size); + rset.set_ext_range(rset.end() + right_ext_range_size); + } + + /*! move ranges */ + __forceinline void moveExtentedRange(const PrimInfoExtRange& set, const PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t left_ext_range_size = lset.ext_range_size(); + const size_t right_size = rset.size(); + + /* has the left child an extended range? */ + if (left_ext_range_size > 0) + { + /* left extended range smaller than right range ? */ + if (left_ext_range_size < right_size) + { + /* only move a small part of the beginning of the right range to the end */ + parallel_for( rset.begin(), rset.begin()+left_ext_range_size, MOVE_STEP_SIZE, [&](const range& r) { + for (size_t i=r.begin(); i& r) { + for (size_t i=r.begin(); i getProperties(const PrimInfoExtRange& set) + { + const OpenHeuristic heuristic(set); + const unsigned int geomID = prims0[set.begin()].geomID(); + + auto body = [&] (const range& r) -> std::pair { + bool commonGeomID = true; + size_t opens = 0; + for (size_t i=r.begin(); i(opens,commonGeomID); + }; + auto reduction = [&] (const std::pair& b0, const std::pair& b1) -> std::pair { + return std::pair(b0.first+b1.first,b0.second && b1.second); + }; + return parallel_reduce(set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,PARALLEL_THRESHOLD,std::pair(0,true),body,reduction); + } + + // FIXME: should consider maximum available extended size + __noinline void openNodesBasedOnExtend(PrimInfoExtRange& set) + { + const OpenHeuristic heuristic(set); + const size_t ext_range_start = set.end(); + + if (false && set.size() < PARALLEL_THRESHOLD) + { + size_t extra_elements = 0; + for (size_t i=set.begin(); i ext_elements; + ext_elements.store(0); + PrimInfo info = parallel_reduce( set.begin(), set.end(), CREATE_SPLITS_STEP_SIZE, PrimInfo(empty), [&](const range& r) -> PrimInfo { + PrimInfo info(empty); + for (size_t i=r.begin(); i 0); + + if (unlikely(next_iteration_extra_elements == 0)) break; + } + } + + __noinline const Split find(PrimInfoExtRange& set, const size_t logBlockSize) + { + /* single element */ + if (set.size() <= 1) + return Split(); + + /* disable opening if there is no overlap */ + const size_t D = 4; + if (unlikely(set.has_ext_range() && set.size() <= D)) + { + bool disjoint = true; + for (size_t j=set.begin(); j p(0,false); + + /* disable opening when all primitives are from same geometry */ + if (unlikely(set.has_ext_range())) + { + p = getProperties(set); +#if EQUAL_GEOMID_STOP_CRITERIA == 1 + if (p.second) set.set_ext_range(set.end()); /* disable opening */ +#endif + } + + /* open nodes when we have sufficient space available */ + if (unlikely(set.has_ext_range())) + { +#if USE_LOOP_OPENING == 1 + openNodesBasedOnExtendLoop(set,p.first); +#else + if (p.first <= set.ext_range_size()) + openNodesBasedOnExtend(set); +#endif + + /* disable opening when unsufficient space for opening a node available */ + if (set.ext_range_size() < max_open_size-1) + set.set_ext_range(set.end()); /* disable opening */ + } + + /* find best split */ + return object_find(set,logBlockSize); + } + + + /*! finds the best object split */ + __forceinline const Split object_find(const PrimInfoExtRange& set,const size_t logBlockSize) + { + if (set.size() < PARALLEL_THRESHOLD) return sequential_object_find(set,logBlockSize); + else return parallel_object_find (set,logBlockSize); + } + + /*! finds the best object split */ + __noinline const Split sequential_object_find(const PrimInfoExtRange& set, const size_t logBlockSize) + { + Binner binner(empty); + const BinMapping mapping(set.centBounds); + binner.bin(prims0,set.begin(),set.end(),mapping); + return binner.best(mapping,logBlockSize); + } + + /*! finds the best split */ + __noinline const Split parallel_object_find(const PrimInfoExtRange& set, const size_t logBlockSize) + { + Binner binner(empty); + const BinMapping mapping(set.centBounds); + const BinMapping& _mapping = mapping; // CLANG 3.4 parser bug workaround + auto body = [&] (const range& r) -> Binner { + Binner binner(empty); binner.bin(prims0+r.begin(),r.size(),_mapping); return binner; + }; + auto reduction = [&] (const Binner& b0, const Binner& b1) -> Binner { + Binner r = b0; r.merge(b1,_mapping.size()); return r; + }; + binner = parallel_reduce(set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,binner,body,reduction); + return binner.best(mapping,logBlockSize); + } + + /*! array partitioning */ + __noinline void split(const Split& split, const PrimInfoExtRange& set_i, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + PrimInfoExtRange set = set_i; + + /* valid split */ + if (unlikely(!split.valid())) { + deterministic_order(set); + splitFallback(set,lset,rset); + return; + } + + std::pair ext_weights(0,0); + + /* object split */ + if (likely(set.size() < PARALLEL_THRESHOLD)) + ext_weights = sequential_object_split(split,set,lset,rset); + else + ext_weights = parallel_object_split(split,set,lset,rset); + + /* if we have an extended range, set extended child ranges and move right split range */ + if (unlikely(set.has_ext_range())) + { + setExtentedRanges(set,lset,rset,ext_weights.first,ext_weights.second); + moveExtentedRange(set,lset,rset); + } + } + + /*! array partitioning */ + std::pair sequential_object_split(const Split& split, const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfo local_left(empty); + PrimInfo local_right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + const vint4 vSplitPos(splitPos); + const vbool4 vSplitMask( (int)splitDimMask ); + + size_t center = serial_partitioning(prims0, + begin,end,local_left,local_right, + [&] (const PrimRef& ref) { return split.mapping.bin_unsafe(ref,vSplitPos,vSplitMask); }, + [] (PrimInfo& pinfo,const PrimRef& ref) { pinfo.add_center2(ref); }); + + new (&lset) PrimInfoExtRange(begin,center,center,local_left); + new (&rset) PrimInfoExtRange(center,end,end,local_right); + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + return std::pair(local_left.size(),local_right.size()); + } + + /*! array partitioning */ + __noinline std::pair parallel_object_split(const Split& split, const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfo left(empty); + PrimInfo right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + const vint4 vSplitPos(splitPos); + const vbool4 vSplitMask( (int)splitDimMask ); + auto isLeft = [&] (const PrimRef& ref) { return split.mapping.bin_unsafe(ref,vSplitPos,vSplitMask); }; + + const size_t center = parallel_partitioning( + prims0,begin,end,EmptyTy(),left,right,isLeft, + [] (PrimInfo& pinfo,const PrimRef& ref) { pinfo.add_center2(ref); }, + [] (PrimInfo& pinfo0,const PrimInfo& pinfo1) { pinfo0.merge(pinfo1); }, + PARALLEL_PARTITION_BLOCK_SIZE); + + new (&lset) PrimInfoExtRange(begin,center,center,left); + new (&rset) PrimInfoExtRange(center,end,end,right); + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + + return std::pair(left.size(),right.size()); + } + + void deterministic_order(const extended_range& set) + { + /* required as parallel partition destroys original primitive order */ + std::sort(&prims0[set.begin()],&prims0[set.end()]); + } + + __forceinline void splitFallback(const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + const size_t center = (begin + end)/2; + + PrimInfo left(empty); + for (size_t i=begin; i + struct SpatialBinMapping + { + public: + __forceinline SpatialBinMapping() {} + + /*! calculates the mapping */ + __forceinline SpatialBinMapping(const CentGeomBBox3fa& pinfo) + { + const vfloat4 lower = (vfloat4) pinfo.geomBounds.lower; + const vfloat4 upper = (vfloat4) pinfo.geomBounds.upper; + const vfloat4 eps = 128.0f*vfloat4(ulp)*max(abs(lower),abs(upper)); + const vfloat4 diag = max(eps,(vfloat4) pinfo.geomBounds.size()); + scale = select(upper-lower <= eps,vfloat4(0.0f),vfloat4(BINS)/diag); + ofs = (vfloat4) pinfo.geomBounds.lower; + inv_scale = 1.0f / scale; + } + + /*! slower but safe binning */ + __forceinline vint4 bin(const Vec3fa& p) const + { + const vint4 i = floori((vfloat4(p)-ofs)*scale); + return clamp(i,vint4(0),vint4(BINS-1)); + } + + __forceinline std::pair bin(const BBox3fa& b) const + { +#if defined(__AVX__) + const vfloat8 ofs8(ofs); + const vfloat8 scale8(scale); + const vint8 lu = floori((vfloat8::loadu(&b)-ofs8)*scale8); + const vint8 c_lu = clamp(lu,vint8(zero),vint8(BINS-1)); + return std::pair(extract4<0>(c_lu),extract4<1>(c_lu)); +#else + const vint4 lower = floori((vfloat4(b.lower)-ofs)*scale); + const vint4 upper = floori((vfloat4(b.upper)-ofs)*scale); + const vint4 c_lower = clamp(lower,vint4(0),vint4(BINS-1)); + const vint4 c_upper = clamp(upper,vint4(0),vint4(BINS-1)); + return std::pair(c_lower,c_upper); +#endif + } + + + /*! calculates left spatial position of bin */ + __forceinline float pos(const size_t bin, const size_t dim) const { + return madd(float(bin),inv_scale[dim],ofs[dim]); + } + + /*! calculates left spatial position of bin */ + template + __forceinline vfloat posN(const vfloat bin, const size_t dim) const { + return madd(bin,vfloat(inv_scale[dim]),vfloat(ofs[dim])); + } + + /*! returns true if the mapping is invalid in some dimension */ + __forceinline bool invalid(const size_t dim) const { + return scale[dim] == 0.0f; + } + + public: + vfloat4 ofs,scale,inv_scale; //!< linear function that maps to bin ID + }; + + /*! stores all information required to perform some split */ + template + struct SpatialBinSplit + { + /*! construct an invalid split by default */ + __forceinline SpatialBinSplit() + : sah(inf), dim(-1), pos(0), left(-1), right(-1), factor(1.0f) {} + + /*! constructs specified split */ + __forceinline SpatialBinSplit(float sah, int dim, int pos, const SpatialBinMapping& mapping) + : sah(sah), dim(dim), pos(pos), left(-1), right(-1), factor(1.0f), mapping(mapping) {} + + /*! constructs specified split */ + __forceinline SpatialBinSplit(float sah, int dim, int pos, int left, int right, float factor, const SpatialBinMapping& mapping) + : sah(sah), dim(dim), pos(pos), left(left), right(right), factor(factor), mapping(mapping) {} + + /*! tests if this split is valid */ + __forceinline bool valid() const { return dim != -1; } + + /*! calculates surface area heuristic for performing the split */ + __forceinline float splitSAH() const { return sah; } + + /*! stream output */ + friend embree_ostream operator<<(embree_ostream cout, const SpatialBinSplit& split) { + return cout << "SpatialBinSplit { sah = " << split.sah << ", dim = " << split.dim << ", pos = " << split.pos << ", left = " << split.left << ", right = " << split.right << ", factor = " << split.factor << "}"; + } + + public: + float sah; //!< SAH cost of the split + int dim; //!< split dimension + int pos; //!< split position + int left; //!< number of elements on the left side + int right; //!< number of elements on the right side + float factor; //!< factor splitting the extended range + SpatialBinMapping mapping; //!< mapping into bins + }; + + /*! stores all binning information */ + template + struct __aligned(64) SpatialBinInfo + { + SpatialBinInfo() { + } + + __forceinline SpatialBinInfo(EmptyTy) { + clear(); + } + + /*! clears the bin info */ + __forceinline void clear() + { + for (size_t i=0; i + __forceinline void bin(const SplitPrimitive& splitPrimitive, const PrimRef* prims, size_t N, const SpatialBinMapping& mapping) + { + for (size_t i=0; i> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS); + + if (unlikely(splits == 1)) + { + const vint4 bin = mapping.bin(center(prim.bounds())); + for (size_t dim=0; dim<3; dim++) + { + assert(bin[dim] >= (int)0 && bin[dim] < (int)BINS); + numBegin[bin[dim]][dim]++; + numEnd [bin[dim]][dim]++; + bounds [bin[dim]][dim].extend(prim.bounds()); + } + } + else + { + const vint4 bin0 = mapping.bin(prim.bounds().lower); + const vint4 bin1 = mapping.bin(prim.bounds().upper); + + for (size_t dim=0; dim<3; dim++) + { + size_t bin; + PrimRef rest = prim; + size_t l = bin0[dim]; + size_t r = bin1[dim]; + + // same bin optimization + if (likely(l == r)) + { + numBegin[l][dim]++; + numEnd [l][dim]++; + bounds [l][dim].extend(prim.bounds()); + continue; + } + + for (bin=(size_t)bin0[dim]; bin<(size_t)bin1[dim]; bin++) + { + const float pos = mapping.pos(bin+1,dim); + + PrimRef left,right; + splitPrimitive(rest,(int)dim,pos,left,right); + if (unlikely(left.bounds().empty())) l++; + bounds[bin][dim].extend(left.bounds()); + rest = right; + } + if (unlikely(rest.bounds().empty())) r--; + numBegin[l][dim]++; + numEnd [r][dim]++; + bounds [bin][dim].extend(rest.bounds()); + } + } + } + } + + /*! bins a range of primitives inside an array */ + template + void bin(const SplitPrimitive& splitPrimitive, const PrimRef* prims, size_t begin, size_t end, const SpatialBinMapping& mapping) { + bin(splitPrimitive,prims+begin,end-begin,mapping); + } + + /*! bins an array of primitives */ + template + __forceinline void bin2(const PrimitiveSplitterFactory& splitterFactory, const PrimRef* source, size_t begin, size_t end, const SpatialBinMapping& mapping) + { + for (size_t i=begin; i& mapping) + { + for (size_t i=begin; i best(const SpatialBinMapping& mapping, const size_t blocks_shift) const + { + /* sweep from right to left and compute parallel prefix of merged bounds */ + vfloat4 rAreas[BINS]; + vuint4 rCounts[BINS]; + vuint4 count = 0; BBox3fa bx = empty; BBox3fa by = empty; BBox3fa bz = empty; + for (size_t i=BINS-1; i>0; i--) + { + count += numEnd[i]; + rCounts[i] = count; + bx.extend(bounds[i][0]); rAreas[i][0] = halfArea(bx); + by.extend(bounds[i][1]); rAreas[i][1] = halfArea(by); + bz.extend(bounds[i][2]); rAreas[i][2] = halfArea(bz); + rAreas[i][3] = 0.0f; + } + + /* sweep from left to right and compute SAH */ + vuint4 blocks_add = (1 << blocks_shift)-1; + vuint4 ii = 1; vfloat4 vbestSAH = pos_inf; vuint4 vbestPos = 0; vuint4 vbestlCount = 0; vuint4 vbestrCount = 0; + count = 0; bx = empty; by = empty; bz = empty; + for (size_t i=1; i> (unsigned int)(blocks_shift); + const vuint4 rCount = (rCounts[i]+blocks_add) >> (unsigned int)(blocks_shift); + const vfloat4 sah = madd(lArea,vfloat4(lCount),rArea*vfloat4(rCount)); + // const vfloat4 sah = madd(lArea,vfloat4(vint4(lCount)),rArea*vfloat4(vint4(rCount))); + const vbool4 mask = sah < vbestSAH; + vbestPos = select(mask,ii ,vbestPos); + vbestSAH = select(mask,sah,vbestSAH); + vbestlCount = select(mask,count,vbestlCount); + vbestrCount = select(mask,rCounts[i],vbestrCount); + } + + /* find best dimension */ + float bestSAH = inf; + int bestDim = -1; + int bestPos = 0; + unsigned int bestlCount = 0; + unsigned int bestrCount = 0; + for (int dim=0; dim<3; dim++) + { + /* ignore zero sized dimensions */ + if (unlikely(mapping.invalid(dim))) + continue; + + /* test if this is a better dimension */ + if (vbestSAH[dim] < bestSAH && vbestPos[dim] != 0) { + bestDim = dim; + bestPos = vbestPos[dim]; + bestSAH = vbestSAH[dim]; + bestlCount = vbestlCount[dim]; + bestrCount = vbestrCount[dim]; + } + } + assert(bestSAH >= 0.0f); + + /* return invalid split if no split found */ + if (bestDim == -1) + return SpatialBinSplit(inf,-1,0,mapping); + + /* return best found split */ + return SpatialBinSplit(bestSAH,bestDim,bestPos,bestlCount,bestrCount,1.0f,mapping); + } + + private: + BBox3fa bounds[BINS][3]; //!< geometry bounds for each bin in each dimension + vuint4 numBegin[BINS]; //!< number of primitives starting in bin + vuint4 numEnd[BINS]; //!< number of primitives ending in bin + }; + } +} + diff --git a/thirdparty/embree/kernels/builders/heuristic_spatial_array.h b/thirdparty/embree/kernels/builders/heuristic_spatial_array.h new file mode 100644 index 000000000000..911dcf950cad --- /dev/null +++ b/thirdparty/embree/kernels/builders/heuristic_spatial_array.h @@ -0,0 +1,552 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "heuristic_binning.h" +#include "heuristic_spatial.h" + +namespace embree +{ + namespace isa + { +#if 0 +#define SPATIAL_ASPLIT_OVERLAP_THRESHOLD 0.2f +#define SPATIAL_ASPLIT_SAH_THRESHOLD 0.95f +#define SPATIAL_ASPLIT_AREA_THRESHOLD 0.0f +#else +#define SPATIAL_ASPLIT_OVERLAP_THRESHOLD 0.1f +#define SPATIAL_ASPLIT_SAH_THRESHOLD 0.99f +#define SPATIAL_ASPLIT_AREA_THRESHOLD 0.000005f +#endif + + struct PrimInfoExtRange : public CentGeomBBox3fa, public extended_range + { + __forceinline PrimInfoExtRange() { + } + + __forceinline PrimInfoExtRange(EmptyTy) + : CentGeomBBox3fa(EmptyTy()), extended_range(0,0,0) {} + + __forceinline PrimInfoExtRange(size_t begin, size_t end, size_t ext_end, const CentGeomBBox3fa& centGeomBounds) + : CentGeomBBox3fa(centGeomBounds), extended_range(begin,end,ext_end) {} + + __forceinline float leafSAH() const { + return expectedApproxHalfArea(geomBounds)*float(size()); + } + + __forceinline float leafSAH(size_t block_shift) const { + return expectedApproxHalfArea(geomBounds)*float((size()+(size_t(1)<> block_shift); + } + }; + + template + struct Split2 + { + __forceinline Split2 () {} + + __forceinline Split2 (const Split2& other) + { + spatial = other.spatial; + sah = other.sah; + if (spatial) spatialSplit() = other.spatialSplit(); + else objectSplit() = other.objectSplit(); + } + + __forceinline Split2& operator= (const Split2& other) + { + spatial = other.spatial; + sah = other.sah; + if (spatial) spatialSplit() = other.spatialSplit(); + else objectSplit() = other.objectSplit(); + return *this; + } + + __forceinline ObjectSplit& objectSplit() { return *( ObjectSplit*)data; } + __forceinline const ObjectSplit& objectSplit() const { return *(const ObjectSplit*)data; } + + __forceinline SpatialSplit& spatialSplit() { return *( SpatialSplit*)data; } + __forceinline const SpatialSplit& spatialSplit() const { return *(const SpatialSplit*)data; } + + __forceinline Split2 (const ObjectSplit& objectSplit, float sah) + : spatial(false), sah(sah) + { + new (data) ObjectSplit(objectSplit); + } + + __forceinline Split2 (const SpatialSplit& spatialSplit, float sah) + : spatial(true), sah(sah) + { + new (data) SpatialSplit(spatialSplit); + } + + __forceinline float splitSAH() const { + return sah; + } + + __forceinline bool valid() const { + return sah < float(inf); + } + + public: + __aligned(64) char data[sizeof(ObjectSplit) > sizeof(SpatialSplit) ? sizeof(ObjectSplit) : sizeof(SpatialSplit)]; + bool spatial; + float sah; + }; + + /*! Performs standard object binning */ + template + struct HeuristicArraySpatialSAH + { + typedef BinSplit ObjectSplit; + typedef BinInfoT ObjectBinner; + + typedef SpatialBinSplit SpatialSplit; + typedef SpatialBinInfo SpatialBinner; + + //typedef extended_range Set; + typedef Split2 Split; + +#if defined(__AVX512ER__) // KNL + static const size_t PARALLEL_THRESHOLD = 3*1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 768; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; +#else + static const size_t PARALLEL_THRESHOLD = 3*1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 1024; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; +#endif + + static const size_t MOVE_STEP_SIZE = 64; + static const size_t CREATE_SPLITS_STEP_SIZE = 64; + + __forceinline HeuristicArraySpatialSAH () + : prims0(nullptr) {} + + /*! remember prim array */ + __forceinline HeuristicArraySpatialSAH (const PrimitiveSplitterFactory& splitterFactory, PrimRef* prims0, const CentGeomBBox3fa& root_info) + : prims0(prims0), splitterFactory(splitterFactory), root_info(root_info) {} + + + /*! compute extended ranges */ + __noinline void setExtentedRanges(const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset, const size_t lweight, const size_t rweight) + { + assert(set.ext_range_size() > 0); + const float left_factor = (float)lweight / (lweight + rweight); + const size_t ext_range_size = set.ext_range_size(); + const size_t left_ext_range_size = min((size_t)(floorf(left_factor * ext_range_size)),ext_range_size); + const size_t right_ext_range_size = ext_range_size - left_ext_range_size; + lset.set_ext_range(lset.end() + left_ext_range_size); + rset.set_ext_range(rset.end() + right_ext_range_size); + } + + /*! move ranges */ + __noinline void moveExtentedRange(const PrimInfoExtRange& set, const PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t left_ext_range_size = lset.ext_range_size(); + const size_t right_size = rset.size(); + + /* has the left child an extended range? */ + if (left_ext_range_size > 0) + { + /* left extended range smaller than right range ? */ + if (left_ext_range_size < right_size) + { + /* only move a small part of the beginning of the right range to the end */ + parallel_for( rset.begin(), rset.begin()+left_ext_range_size, MOVE_STEP_SIZE, [&](const range& r) { + for (size_t i=r.begin(); i& r) { + for (size_t i=r.begin(); i= SPATIAL_ASPLIT_AREA_THRESHOLD*safeArea(root_info.geomBounds) && + safeArea(overlap) >= SPATIAL_ASPLIT_OVERLAP_THRESHOLD*safeArea(set.geomBounds)) + { + const SpatialSplit spatial_split = spatial_find(set, logBlockSize); + const float spatial_split_sah = spatial_split.splitSAH(); + + /* valid spatial split, better SAH and number of splits do not exceed extended range */ + if (spatial_split_sah < SPATIAL_ASPLIT_SAH_THRESHOLD*object_split_sah && + spatial_split.left + spatial_split.right - set.size() <= set.ext_range_size()) + { + return Split(spatial_split,spatial_split_sah); + } + } + } + + return Split(object_split,object_split_sah); + } + + /*! finds the best object split */ + __forceinline const ObjectSplit object_find(const PrimInfoExtRange& set, const size_t logBlockSize, SplitInfo &info) + { + if (set.size() < PARALLEL_THRESHOLD) return sequential_object_find(set,logBlockSize,info); + else return parallel_object_find (set,logBlockSize,info); + } + + /*! finds the best object split */ + __noinline const ObjectSplit sequential_object_find(const PrimInfoExtRange& set, const size_t logBlockSize, SplitInfo &info) + { + ObjectBinner binner(empty); + const BinMapping mapping(set); + binner.bin(prims0,set.begin(),set.end(),mapping); + ObjectSplit s = binner.best(mapping,logBlockSize); + binner.getSplitInfo(mapping, s, info); + return s; + } + + /*! finds the best split */ + __noinline const ObjectSplit parallel_object_find(const PrimInfoExtRange& set, const size_t logBlockSize, SplitInfo &info) + { + ObjectBinner binner(empty); + const BinMapping mapping(set); + const BinMapping& _mapping = mapping; // CLANG 3.4 parser bug workaround + binner = parallel_reduce(set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,binner, + [&] (const range& r) -> ObjectBinner { ObjectBinner binner(empty); binner.bin(prims0+r.begin(),r.size(),_mapping); return binner; }, + [&] (const ObjectBinner& b0, const ObjectBinner& b1) -> ObjectBinner { ObjectBinner r = b0; r.merge(b1,_mapping.size()); return r; }); + ObjectSplit s = binner.best(mapping,logBlockSize); + binner.getSplitInfo(mapping, s, info); + return s; + } + + /*! finds the best spatial split */ + __forceinline const SpatialSplit spatial_find(const PrimInfoExtRange& set, const size_t logBlockSize) + { + if (set.size() < PARALLEL_THRESHOLD) return sequential_spatial_find(set, logBlockSize); + else return parallel_spatial_find (set, logBlockSize); + } + + /*! finds the best spatial split */ + __noinline const SpatialSplit sequential_spatial_find(const PrimInfoExtRange& set, const size_t logBlockSize) + { + SpatialBinner binner(empty); + const SpatialBinMapping mapping(set); + binner.bin2(splitterFactory,prims0,set.begin(),set.end(),mapping); + /* todo: best spatial split not exeeding the extended range does not provide any benefit ?*/ + return binner.best(mapping,logBlockSize); //,set.ext_size()); + } + + __noinline const SpatialSplit parallel_spatial_find(const PrimInfoExtRange& set, const size_t logBlockSize) + { + SpatialBinner binner(empty); + const SpatialBinMapping mapping(set); + const SpatialBinMapping& _mapping = mapping; // CLANG 3.4 parser bug workaround + binner = parallel_reduce(set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,binner, + [&] (const range& r) -> SpatialBinner { + SpatialBinner binner(empty); + binner.bin2(splitterFactory,prims0,r.begin(),r.end(),_mapping); + return binner; }, + [&] (const SpatialBinner& b0, const SpatialBinner& b1) -> SpatialBinner { return SpatialBinner::reduce(b0,b1); }); + /* todo: best spatial split not exeeding the extended range does not provide any benefit ?*/ + return binner.best(mapping,logBlockSize); //,set.ext_size()); + } + + + /*! subdivides primitives based on a spatial split */ + __noinline void create_spatial_splits(PrimInfoExtRange& set, const SpatialSplit& split, const SpatialBinMapping &mapping) + { + assert(set.has_ext_range()); + const size_t max_ext_range_size = set.ext_range_size(); + const size_t ext_range_start = set.end(); + + /* atomic counter for number of primref splits */ + std::atomic ext_elements; + ext_elements.store(0); + + const float fpos = split.mapping.pos(split.pos,split.dim); + + const unsigned int mask = 0xFFFFFFFF >> RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS; + + parallel_for( set.begin(), set.end(), CREATE_SPLITS_STEP_SIZE, [&](const range& r) { + for (size_t i=r.begin();i> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS); + + if (likely(splits <= 1)) continue; /* todo: does this ever happen ? */ + + //int bin0 = split.mapping.bin(prims0[i].lower)[split.dim]; + //int bin1 = split.mapping.bin(prims0[i].upper)[split.dim]; + //if (unlikely(bin0 < split.pos && bin1 >= split.pos)) + if (unlikely(prims0[i].lower[split.dim] < fpos && prims0[i].upper[split.dim] > fpos)) + { + assert(splits > 1); + + PrimRef left,right; + const auto splitter = splitterFactory(prims0[i]); + splitter(prims0[i],split.dim,fpos,left,right); + + // no empty splits + if (unlikely(left.bounds().empty() || right.bounds().empty())) continue; + + left.lower.u = (left.lower.u & mask) | ((splits-1) << (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); + right.lower.u = (right.lower.u & mask) | ((splits-1) << (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); + + const size_t ID = ext_elements.fetch_add(1); + + /* break if the number of subdivided elements are greater than the maximum allowed size */ + if (unlikely(ID >= max_ext_range_size)) + break; + + /* only write within the correct bounds */ + assert(ID < max_ext_range_size); + prims0[i] = left; + prims0[ext_range_start+ID] = right; + } + } + }); + + const size_t numExtElements = min(max_ext_range_size,ext_elements.load()); + assert(set.end()+numExtElements<=set.ext_end()); + set._end += numExtElements; + } + + /*! array partitioning */ + void split(const Split& split, const PrimInfoExtRange& set_i, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + PrimInfoExtRange set = set_i; + + /* valid split */ + if (unlikely(!split.valid())) { + deterministic_order(set); + return splitFallback(set,lset,rset); + } + + std::pair ext_weights(0,0); + + if (unlikely(split.spatial)) + { + create_spatial_splits(set,split.spatialSplit(), split.spatialSplit().mapping); + + /* spatial split */ + if (likely(set.size() < PARALLEL_THRESHOLD)) + ext_weights = sequential_spatial_split(split.spatialSplit(),set,lset,rset); + else + ext_weights = parallel_spatial_split(split.spatialSplit(),set,lset,rset); + } + else + { + /* object split */ + if (likely(set.size() < PARALLEL_THRESHOLD)) + ext_weights = sequential_object_split(split.objectSplit(),set,lset,rset); + else + ext_weights = parallel_object_split(split.objectSplit(),set,lset,rset); + } + + /* if we have an extended range, set extended child ranges and move right split range */ + if (unlikely(set.has_ext_range())) + { + setExtentedRanges(set,lset,rset,ext_weights.first,ext_weights.second); + moveExtentedRange(set,lset,rset); + } + } + + /*! array partitioning */ + std::pair sequential_object_split(const ObjectSplit& split, const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfo local_left(empty); + PrimInfo local_right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + const typename ObjectBinner::vint vSplitPos(splitPos); + const typename ObjectBinner::vbool vSplitMask(splitDimMask); + size_t center = serial_partitioning(prims0, + begin,end,local_left,local_right, + [&] (const PrimRef& ref) { + return split.mapping.bin_unsafe(ref,vSplitPos,vSplitMask); + }, + [] (PrimInfo& pinfo,const PrimRef& ref) { pinfo.add_center2(ref,ref.lower.u >> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); }); + const size_t left_weight = local_left.end; + const size_t right_weight = local_right.end; + + new (&lset) PrimInfoExtRange(begin,center,center,local_left); + new (&rset) PrimInfoExtRange(center,end,end,local_right); + + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + return std::pair(left_weight,right_weight); + } + + + /*! array partitioning */ + __noinline std::pair sequential_spatial_split(const SpatialSplit& split, const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfo local_left(empty); + PrimInfo local_right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + /* init spatial mapping */ + const SpatialBinMapping &mapping = split.mapping; + const vint4 vSplitPos(splitPos); + const vbool4 vSplitMask( (int)splitDimMask ); + + size_t center = serial_partitioning(prims0, + begin,end,local_left,local_right, + [&] (const PrimRef& ref) { + const Vec3fa c = ref.bounds().center(); + return any(((vint4)mapping.bin(c) < vSplitPos) & vSplitMask); + }, + [] (PrimInfo& pinfo,const PrimRef& ref) { pinfo.add_center2(ref,ref.lower.u >> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); }); + + const size_t left_weight = local_left.end; + const size_t right_weight = local_right.end; + + new (&lset) PrimInfoExtRange(begin,center,center,local_left); + new (&rset) PrimInfoExtRange(center,end,end,local_right); + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + return std::pair(left_weight,right_weight); + } + + + + /*! array partitioning */ + __noinline std::pair parallel_object_split(const ObjectSplit& split, const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfo left(empty); + PrimInfo right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + const typename ObjectBinner::vint vSplitPos(splitPos); + const typename ObjectBinner::vbool vSplitMask(splitDimMask); + auto isLeft = [&] (const PrimRef &ref) { return split.mapping.bin_unsafe(ref,vSplitPos,vSplitMask); }; + + const size_t center = parallel_partitioning( + prims0,begin,end,EmptyTy(),left,right,isLeft, + [] (PrimInfo &pinfo,const PrimRef &ref) { pinfo.add_center2(ref,ref.lower.u >> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); }, + [] (PrimInfo &pinfo0,const PrimInfo &pinfo1) { pinfo0.merge(pinfo1); }, + PARALLEL_PARTITION_BLOCK_SIZE); + + const size_t left_weight = left.end; + const size_t right_weight = right.end; + + left.begin = begin; left.end = center; + right.begin = center; right.end = end; + + new (&lset) PrimInfoExtRange(begin,center,center,left); + new (&rset) PrimInfoExtRange(center,end,end,right); + + assert(area(left.geomBounds) >= 0.0f); + assert(area(right.geomBounds) >= 0.0f); + return std::pair(left_weight,right_weight); + } + + /*! array partitioning */ + __noinline std::pair parallel_spatial_split(const SpatialSplit& split, const PrimInfoExtRange& set, PrimInfoExtRange& lset, PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + PrimInfo left(empty); + PrimInfo right(empty); + const unsigned int splitPos = split.pos; + const unsigned int splitDim = split.dim; + const unsigned int splitDimMask = (unsigned int)1 << splitDim; + + /* init spatial mapping */ + const SpatialBinMapping& mapping = split.mapping; + const vint4 vSplitPos(splitPos); + const vbool4 vSplitMask( (int)splitDimMask ); + + auto isLeft = [&] (const PrimRef &ref) { + const Vec3fa c = ref.bounds().center(); + return any(((vint4)mapping.bin(c) < vSplitPos) & vSplitMask); }; + + const size_t center = parallel_partitioning( + prims0,begin,end,EmptyTy(),left,right,isLeft, + [] (PrimInfo &pinfo,const PrimRef &ref) { pinfo.add_center2(ref,ref.lower.u >> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); }, + [] (PrimInfo &pinfo0,const PrimInfo &pinfo1) { pinfo0.merge(pinfo1); }, + PARALLEL_PARTITION_BLOCK_SIZE); + + const size_t left_weight = left.end; + const size_t right_weight = right.end; + + left.begin = begin; left.end = center; + right.begin = center; right.end = end; + + new (&lset) PrimInfoExtRange(begin,center,center,left); + new (&rset) PrimInfoExtRange(center,end,end,right); + + assert(area(left.geomBounds) >= 0.0f); + assert(area(right.geomBounds) >= 0.0f); + return std::pair(left_weight,right_weight); + } + + void deterministic_order(const PrimInfoExtRange& set) + { + /* required as parallel partition destroys original primitive order */ + std::sort(&prims0[set.begin()],&prims0[set.end()]); + } + + void splitFallback(const PrimInfoExtRange& set, + PrimInfoExtRange& lset, + PrimInfoExtRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + const size_t center = (begin + end)/2; + + PrimInfo left(empty); + for (size_t i=begin; i> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); + } + const size_t lweight = left.end; + + PrimInfo right(empty); + for (size_t i=center; i> (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)); + } + const size_t rweight = right.end; + + new (&lset) PrimInfoExtRange(begin,center,center,left); + new (&rset) PrimInfoExtRange(center,end,end,right); + + /* if we have an extended range */ + if (set.has_ext_range()) { + setExtentedRanges(set,lset,rset,lweight,rweight); + moveExtentedRange(set,lset,rset); + } + } + + private: + PrimRef* const prims0; + const PrimitiveSplitterFactory& splitterFactory; + const CentGeomBBox3fa& root_info; + }; + } +} diff --git a/thirdparty/embree/kernels/builders/heuristic_strand_array.h b/thirdparty/embree/kernels/builders/heuristic_strand_array.h new file mode 100644 index 000000000000..ede0d04c78ab --- /dev/null +++ b/thirdparty/embree/kernels/builders/heuristic_strand_array.h @@ -0,0 +1,188 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "priminfo.h" +#include "../../common/algorithms/parallel_reduce.h" +#include "../../common/algorithms/parallel_partition.h" + +namespace embree +{ + namespace isa + { + /*! Performs standard object binning */ + struct HeuristicStrandSplit + { + typedef range Set; + + static const size_t PARALLEL_THRESHOLD = 10000; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 4096; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 64; + + /*! stores all information to perform some split */ + struct Split + { + /*! construct an invalid split by default */ + __forceinline Split() + : sah(inf), axis0(zero), axis1(zero) {} + + /*! constructs specified split */ + __forceinline Split(const float sah, const Vec3fa& axis0, const Vec3fa& axis1) + : sah(sah), axis0(axis0), axis1(axis1) {} + + /*! calculates standard surface area heuristic for the split */ + __forceinline float splitSAH() const { return sah; } + + /*! test if this split is valid */ + __forceinline bool valid() const { return sah != float(inf); } + + public: + float sah; //!< SAH cost of the split + Vec3fa axis0, axis1; //!< axis the two strands are aligned into + }; + + __forceinline HeuristicStrandSplit () // FIXME: required? + : scene(nullptr), prims(nullptr) {} + + /*! remember prim array */ + __forceinline HeuristicStrandSplit (Scene* scene, PrimRef* prims) + : scene(scene), prims(prims) {} + + __forceinline const Vec3fa direction(const PrimRef& prim) { + return scene->get(prim.geomID())->computeDirection(prim.primID()); + } + + __forceinline const BBox3fa bounds(const PrimRef& prim) { + return scene->get(prim.geomID())->vbounds(prim.primID()); + } + + __forceinline const BBox3fa bounds(const LinearSpace3fa& space, const PrimRef& prim) { + return scene->get(prim.geomID())->vbounds(space,prim.primID()); + } + + /*! finds the best split */ + const Split find(const range& set, size_t logBlockSize) + { + Vec3fa axis0(0,0,1); + uint64_t bestGeomPrimID = -1; + + /* curve with minimum ID determines first axis */ + for (size_t i=set.begin(); i= bestGeomPrimID) continue; + const Vec3fa axis = direction(prims[i]); + if (sqr_length(axis) > 1E-18f) { + axis0 = normalize(axis); + bestGeomPrimID = geomprimID; + } + } + + /* find 2nd axis that is most misaligned with first axis and has minimum ID */ + float bestCos = 1.0f; + Vec3fa axis1 = axis0; + bestGeomPrimID = -1; + for (size_t i=set.begin(); i cos1) { lnum++; lbounds.extend(bounds(space0,prim)); } + else { rnum++; rbounds.extend(bounds(space1,prim)); } + } + + /*! return an invalid split if we do not partition */ + if (lnum == 0 || rnum == 0) + return Split(inf,axis0,axis1); + + /*! calculate sah for the split */ + const size_t lblocks = (lnum+(1ull<> logBlockSize; + const size_t rblocks = (rnum+(1ull<> logBlockSize; + const float sah = madd(float(lblocks),halfArea(lbounds),float(rblocks)*halfArea(rbounds)); + return Split(sah,axis0,axis1); + } + + /*! array partitioning */ + void split(const Split& split, const PrimInfoRange& set, PrimInfoRange& lset, PrimInfoRange& rset) + { + if (!split.valid()) { + deterministic_order(set); + return splitFallback(set,lset,rset); + } + + const size_t begin = set.begin(); + const size_t end = set.end(); + CentGeomBBox3fa local_left(empty); + CentGeomBBox3fa local_right(empty); + + auto primOnLeftSide = [&] (const PrimRef& prim) -> bool { + const Vec3fa axisi = normalize(direction(prim)); + const float cos0 = abs(dot(axisi,split.axis0)); + const float cos1 = abs(dot(axisi,split.axis1)); + return cos0 > cos1; + }; + + auto mergePrimBounds = [this] (CentGeomBBox3fa& pinfo,const PrimRef& ref) { + pinfo.extend(bounds(ref)); + }; + + size_t center = serial_partitioning(prims,begin,end,local_left,local_right,primOnLeftSide,mergePrimBounds); + + new (&lset) PrimInfoRange(begin,center,local_left); + new (&rset) PrimInfoRange(center,end,local_right); + assert(area(lset.geomBounds) >= 0.0f); + assert(area(rset.geomBounds) >= 0.0f); + } + + void deterministic_order(const Set& set) + { + /* required as parallel partition destroys original primitive order */ + std::sort(&prims[set.begin()],&prims[set.end()]); + } + + void splitFallback(const Set& set, PrimInfoRange& lset, PrimInfoRange& rset) + { + const size_t begin = set.begin(); + const size_t end = set.end(); + const size_t center = (begin + end)/2; + + CentGeomBBox3fa left(empty); + for (size_t i=begin; i + struct HeuristicMBlurTemporalSplit + { + typedef BinSplit Split; + typedef mvector* PrimRefVector; + typedef typename PrimRefMB::BBox BBox; + + static const size_t PARALLEL_THRESHOLD = 3 * 1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 1024; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; + + HeuristicMBlurTemporalSplit (MemoryMonitorInterface* device, const RecalculatePrimRef& recalculatePrimRef) + : device(device), recalculatePrimRef(recalculatePrimRef) {} + + struct TemporalBinInfo + { + __forceinline TemporalBinInfo () { + } + + __forceinline TemporalBinInfo (EmptyTy) + { + for (size_t i=0; i= time_range.upper) continue; + const BBox1f dt0(time_range.lower,center_time); + const BBox1f dt1(center_time,time_range.upper); + + /* find linear bounds for both time segments */ + for (size_t i=begin; i& r) -> TemporalBinInfo { + TemporalBinInfo binner(empty); binner.bin(prims, r.begin(), r.end(), time_range, set, recalculatePrimRef); return binner; + }; + *this = parallel_reduce(begin,end,blockSize,TemporalBinInfo(empty),bin,merge2); + } + } + + /*! merges in other binning information */ + __forceinline void merge (const TemporalBinInfo& other) + { + for (size_t i=0; i= time_range.upper) continue; + const BBox1f dt0(time_range.lower,center_time); + const BBox1f dt1(center_time,time_range.upper); + + /* calculate sah */ + const size_t lCount = (count0[b]+(size_t(1) << logBlockSize)-1) >> int(logBlockSize); + const size_t rCount = (count1[b]+(size_t(1) << logBlockSize)-1) >> int(logBlockSize); + float sah0 = expectedApproxHalfArea(bounds0[b])*float(lCount)*dt0.size(); + float sah1 = expectedApproxHalfArea(bounds1[b])*float(rCount)*dt1.size(); + if (unlikely(lCount == 0)) sah0 = 0.0f; // happens for initial splits when objects not alive over entire shutter time + if (unlikely(rCount == 0)) sah1 = 0.0f; + const float sah = sah0+sah1; + if (sah < bestSAH) { + bestSAH = sah; + bestPos = center_time; + } + } + return Split(bestSAH*MBLUR_TIME_SPLIT_THRESHOLD,(unsigned)Split::SPLIT_TEMPORAL,0,bestPos); + } + + public: + size_t count0[BINS-1]; + size_t count1[BINS-1]; + BBox bounds0[BINS-1]; + BBox bounds1[BINS-1]; + }; + + /*! finds the best split */ + const Split find(const SetMB& set, const size_t logBlockSize) + { + assert(set.size() > 0); + TemporalBinInfo binner(empty); + binner.bin_parallel(set.prims->data(),set.begin(),set.end(),PARALLEL_FIND_BLOCK_SIZE,PARALLEL_THRESHOLD,set.time_range,set,recalculatePrimRef); + Split tsplit = binner.best((int)logBlockSize,set.time_range,set); + if (!tsplit.valid()) tsplit.data = Split::SPLIT_FALLBACK; // use fallback split + return tsplit; + } + + __forceinline std::unique_ptr> split(const Split& tsplit, const SetMB& set, SetMB& lset, SetMB& rset) + { + assert(tsplit.sah != float(inf)); + assert(tsplit.fpos > set.time_range.lower); + assert(tsplit.fpos < set.time_range.upper); + + float center_time = tsplit.fpos; + const BBox1f time_range0(set.time_range.lower,center_time); + const BBox1f time_range1(center_time,set.time_range.upper); + mvector& prims = *set.prims; + + /* calculate primrefs for first time range */ + std::unique_ptr> new_vector(new mvector(device, set.size())); + PrimRefVector lprims = new_vector.get(); + + auto reduction_func0 = [&] (const range& r) { + PrimInfoMB pinfo = empty; + for (size_t i=r.begin(); idata(), size_t(0), set.size(), size_t(1024), + [&](const PrimRefMB& prim) { return prim.time_range_overlap(time_range0); }); + + lset = SetMB(linfo,lprims,time_range0); + + /* calculate primrefs for second time range */ + auto reduction_func1 = [&] (const range& r) { + PrimInfoMB pinfo = empty; + for (size_t i=r.begin(); i(set.begin(), set.begin() + rinfo.size()); + + /* primrefs for second time range are in prims[set.begin() .. set.end()) */ + /* some primitives may need to be filtered out */ + if (rinfo.size() != set.size()) + rinfo.object_range._end = parallel_filter(prims.data(), set.begin(), set.end(), size_t(1024), + [&](const PrimRefMB& prim) { return prim.time_range_overlap(time_range1); }); + + rset = SetMB(rinfo,&prims,time_range1); + + return new_vector; + } + + private: + MemoryMonitorInterface* device; // device to report memory usage to + const RecalculatePrimRef recalculatePrimRef; + }; + } +} diff --git a/thirdparty/embree/kernels/builders/priminfo.h b/thirdparty/embree/kernels/builders/priminfo.h new file mode 100644 index 000000000000..06c1388742a3 --- /dev/null +++ b/thirdparty/embree/kernels/builders/priminfo.h @@ -0,0 +1,362 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "../common/primref.h" +#include "../common/primref_mb.h" + +namespace embree +{ + // FIXME: maybe there's a better place for this util fct + __forceinline float areaProjectedTriangle(const Vec3fa& v0, const Vec3fa& v1, const Vec3fa& v2) + { + const Vec3fa e0 = v1-v0; + const Vec3fa e1 = v2-v0; + const Vec3fa d = cross(e0,e1); + return fabs(d.x) + fabs(d.y) + fabs(d.z); + } + + //namespace isa + //{ + template + class CentGeom + { + public: + __forceinline CentGeom () {} + + __forceinline CentGeom (EmptyTy) + : geomBounds(empty), centBounds(empty) {} + + __forceinline CentGeom (const BBox& geomBounds, const BBox3fa& centBounds) + : geomBounds(geomBounds), centBounds(centBounds) {} + + template + __forceinline void extend_primref(const PrimRef& prim) + { + BBox bounds; Vec3fa center; + prim.binBoundsAndCenter(bounds,center); + geomBounds.extend(bounds); + centBounds.extend(center); + } + + template + __forceinline void extend_center2(const PrimRef& prim) + { + BBox3fa bounds = prim.bounds(); + geomBounds.extend(bounds); + centBounds.extend(bounds.center2()); + } + + __forceinline void extend(const BBox& geomBounds_) { + geomBounds.extend(geomBounds_); + centBounds.extend(center2(geomBounds_)); + } + + __forceinline void merge(const CentGeom& other) + { + geomBounds.extend(other.geomBounds); + centBounds.extend(other.centBounds); + } + + static __forceinline const CentGeom merge2(const CentGeom& a, const CentGeom& b) { + CentGeom r = a; r.merge(b); return r; + } + + public: + BBox geomBounds; //!< geometry bounds of primitives + BBox3fa centBounds; //!< centroid bounds of primitives + }; + + typedef CentGeom CentGeomBBox3fa; + + /*! stores bounding information for a set of primitives */ + template + class PrimInfoT : public CentGeom + { + public: + using CentGeom::geomBounds; + using CentGeom::centBounds; + + __forceinline PrimInfoT () {} + + __forceinline PrimInfoT (EmptyTy) + : CentGeom(empty), begin(0), end(0) {} + + __forceinline PrimInfoT (size_t begin, size_t end, const CentGeomBBox3fa& centGeomBounds) + : CentGeom(centGeomBounds), begin(begin), end(end) {} + + template + __forceinline void add_primref(const PrimRef& prim) + { + CentGeom::extend_primref(prim); + end++; + } + + template + __forceinline void add_center2(const PrimRef& prim) { + CentGeom::extend_center2(prim); + end++; + } + + template + __forceinline void add_center2(const PrimRef& prim, const size_t i) { + CentGeom::extend_center2(prim); + end+=i; + } + + /*__forceinline void add(const BBox& geomBounds_) { + CentGeom::extend(geomBounds_); + end++; + } + + __forceinline void add(const BBox& geomBounds_, const size_t i) { + CentGeom::extend(geomBounds_); + end+=i; + }*/ + + __forceinline void merge(const PrimInfoT& other) + { + CentGeom::merge(other); + begin += other.begin; + end += other.end; + } + + static __forceinline const PrimInfoT merge(const PrimInfoT& a, const PrimInfoT& b) { + PrimInfoT r = a; r.merge(b); return r; + } + + /*! returns the number of primitives */ + __forceinline size_t size() const { + return end-begin; + } + + __forceinline float halfArea() { + return expectedApproxHalfArea(geomBounds); + } + + __forceinline float leafSAH() const { + return expectedApproxHalfArea(geomBounds)*float(size()); + //return halfArea(geomBounds)*blocks(num); + } + + __forceinline float leafSAH(size_t block_shift) const { + return expectedApproxHalfArea(geomBounds)*float((size()+(size_t(1)<> block_shift); + //return halfArea(geomBounds)*float((num+3) >> 2); + //return halfArea(geomBounds)*blocks(num); + } + + /*! stream output */ + friend embree_ostream operator<<(embree_ostream cout, const PrimInfoT& pinfo) { + return cout << "PrimInfo { begin = " << pinfo.begin << ", end = " << pinfo.end << ", geomBounds = " << pinfo.geomBounds << ", centBounds = " << pinfo.centBounds << "}"; + } + + public: + size_t begin,end; //!< number of primitives + }; + + typedef PrimInfoT PrimInfo; + //typedef PrimInfoT PrimInfoMB; + + /*! stores bounding information for a set of primitives */ + template + class PrimInfoMBT : public CentGeom + { + public: + using CentGeom::geomBounds; + using CentGeom::centBounds; + + __forceinline PrimInfoMBT () { + } + + __forceinline PrimInfoMBT (EmptyTy) + : CentGeom(empty), object_range(0,0), num_time_segments(0), max_num_time_segments(0), max_time_range(0.0f,1.0f), time_range(1.0f,0.0f) {} + + __forceinline PrimInfoMBT (size_t begin, size_t end) + : CentGeom(empty), object_range(begin,end), num_time_segments(0), max_num_time_segments(0), max_time_range(0.0f,1.0f), time_range(1.0f,0.0f) {} + + template + __forceinline void add_primref(const PrimRef& prim) + { + CentGeom::extend_primref(prim); + time_range.extend(prim.time_range); + object_range._end++; + num_time_segments += prim.size(); + if (max_num_time_segments < prim.totalTimeSegments()) { + max_num_time_segments = prim.totalTimeSegments(); + max_time_range = prim.time_range; + } + } + + __forceinline void merge(const PrimInfoMBT& other) + { + CentGeom::merge(other); + time_range.extend(other.time_range); + object_range._begin += other.object_range.begin(); + object_range._end += other.object_range.end(); + num_time_segments += other.num_time_segments; + if (max_num_time_segments < other.max_num_time_segments) { + max_num_time_segments = other.max_num_time_segments; + max_time_range = other.max_time_range; + } + } + + static __forceinline const PrimInfoMBT merge2(const PrimInfoMBT& a, const PrimInfoMBT& b) { + PrimInfoMBT r = a; r.merge(b); return r; + } + + __forceinline size_t begin() const { + return object_range.begin(); + } + + __forceinline size_t end() const { + return object_range.end(); + } + + /*! returns the number of primitives */ + __forceinline size_t size() const { + return object_range.size(); + } + + __forceinline float halfArea() const { + return time_range.size()*expectedApproxHalfArea(geomBounds); + } + + __forceinline float leafSAH() const { + return time_range.size()*expectedApproxHalfArea(geomBounds)*float(num_time_segments); + } + + __forceinline float leafSAH(size_t block_shift) const { + return time_range.size()*expectedApproxHalfArea(geomBounds)*float((num_time_segments+(size_t(1)<> block_shift); + } + + __forceinline float align_time(float ct) const + { + //return roundf(ct * float(numTimeSegments)) / float(numTimeSegments); + float t0 = (ct-max_time_range.lower)/max_time_range.size(); + float t1 = roundf(t0 * float(max_num_time_segments)) / float(max_num_time_segments); + return t1*max_time_range.size()+max_time_range.lower; + } + + /*! stream output */ + friend embree_ostream operator<<(embree_ostream cout, const PrimInfoMBT& pinfo) + { + return cout << "PrimInfo { " << + "object_range = " << pinfo.object_range << + ", time_range = " << pinfo.time_range << + ", time_segments = " << pinfo.num_time_segments << + ", geomBounds = " << pinfo.geomBounds << + ", centBounds = " << pinfo.centBounds << + "}"; + } + + public: + range object_range; //!< primitive range + size_t num_time_segments; //!< total number of time segments of all added primrefs + size_t max_num_time_segments; //!< maximum number of time segments of a primitive + BBox1f max_time_range; //!< time range of primitive with max_num_time_segments + BBox1f time_range; //!< merged time range of primitives when merging prims, or additionally clipped with build time range when used in SetMB + }; + + typedef PrimInfoMBT PrimInfoMB; + + struct SetMB : public PrimInfoMB + { + static const size_t PARALLEL_THRESHOLD = 3 * 1024; + static const size_t PARALLEL_FIND_BLOCK_SIZE = 1024; + static const size_t PARALLEL_PARTITION_BLOCK_SIZE = 128; + + typedef mvector* PrimRefVector; + + __forceinline SetMB() {} + + __forceinline SetMB(const PrimInfoMB& pinfo_i, PrimRefVector prims) + : PrimInfoMB(pinfo_i), prims(prims) {} + + __forceinline SetMB(const PrimInfoMB& pinfo_i, PrimRefVector prims, range object_range_in, BBox1f time_range_in) + : PrimInfoMB(pinfo_i), prims(prims) + { + object_range = object_range_in; + time_range = intersect(time_range,time_range_in); + } + + __forceinline SetMB(const PrimInfoMB& pinfo_i, PrimRefVector prims, BBox1f time_range_in) + : PrimInfoMB(pinfo_i), prims(prims) + { + time_range = intersect(time_range,time_range_in); + } + + void deterministic_order() const + { + /* required as parallel partition destroys original primitive order */ + PrimRefMB* prim = prims->data(); + std::sort(&prim[object_range.begin()],&prim[object_range.end()]); + } + + template + __forceinline LBBox3fa linearBounds(const RecalculatePrimRef& recalculatePrimRef) const + { + auto reduce = [&](const range& r) -> LBBox3fa + { + LBBox3fa cbounds(empty); + for (size_t j = r.begin(); j < r.end(); j++) + { + PrimRefMB& ref = (*prims)[j]; + const LBBox3fa bn = recalculatePrimRef.linearBounds(ref, time_range); + cbounds.extend(bn); + }; + return cbounds; + }; + + return parallel_reduce(object_range.begin(), object_range.end(), PARALLEL_FIND_BLOCK_SIZE, PARALLEL_THRESHOLD, LBBox3fa(empty), + reduce, + [&](const LBBox3fa& b0, const LBBox3fa& b1) -> LBBox3fa { return embree::merge(b0, b1); }); + } + + template + __forceinline LBBox3fa linearBounds(const RecalculatePrimRef& recalculatePrimRef, const LinearSpace3fa& space) const + { + auto reduce = [&](const range& r) -> LBBox3fa + { + LBBox3fa cbounds(empty); + for (size_t j = r.begin(); j < r.end(); j++) + { + PrimRefMB& ref = (*prims)[j]; + const LBBox3fa bn = recalculatePrimRef.linearBounds(ref, time_range, space); + cbounds.extend(bn); + }; + return cbounds; + }; + + return parallel_reduce(object_range.begin(), object_range.end(), PARALLEL_FIND_BLOCK_SIZE, PARALLEL_THRESHOLD, LBBox3fa(empty), + reduce, + [&](const LBBox3fa& b0, const LBBox3fa& b1) -> LBBox3fa { return embree::merge(b0, b1); }); + } + + template + const SetMB primInfo(const RecalculatePrimRef& recalculatePrimRef, const LinearSpace3fa& space) const + { + auto computePrimInfo = [&](const range& r) -> PrimInfoMB + { + PrimInfoMB pinfo(empty); + for (size_t j=r.begin(); j& prims, BuildProgressMonitor& progressMonitor) + { + ParallelPrefixSumState pstate; + + /* first try */ + progressMonitor(0); + PrimInfo pinfo = parallel_prefix_sum( pstate, size_t(0), geometry->size(), size_t(1024), PrimInfo(empty), [&](const range& r, const PrimInfo& base) -> PrimInfo { + return geometry->createPrimRefArray(prims,r,r.begin(),geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != prims.size()) + { + progressMonitor(0); + pinfo = parallel_prefix_sum( pstate, size_t(0), geometry->size(), size_t(1024), PrimInfo(empty), [&](const range& r, const PrimInfo& base) -> PrimInfo { + return geometry->createPrimRefArray(prims,r,base.size(),geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + } + return pinfo; + } + + PrimInfo createPrimRefArray(Scene* scene, Geometry::GTypeMask types, bool mblur, mvector& prims, BuildProgressMonitor& progressMonitor) + { + ParallelForForPrefixSumState pstate; + Scene::Iterator2 iter(scene,types,mblur); + + /* first try */ + progressMonitor(0); + pstate.init(iter,size_t(1024)); + PrimInfo pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfo(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID) -> PrimInfo { + return mesh->createPrimRefArray(prims,r,k,(unsigned)geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != prims.size()) + { + progressMonitor(0); + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfo(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID, const PrimInfo& base) -> PrimInfo { + return mesh->createPrimRefArray(prims,r,base.size(),(unsigned)geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + } + return pinfo; + } + + PrimInfo createPrimRefArrayMBlur(Scene* scene, Geometry::GTypeMask types, mvector& prims, BuildProgressMonitor& progressMonitor, size_t itime) + { + ParallelForForPrefixSumState pstate; + Scene::Iterator2 iter(scene,types,true); + + /* first try */ + progressMonitor(0); + pstate.init(iter,size_t(1024)); + PrimInfo pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfo(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID) -> PrimInfo { + return mesh->createPrimRefArrayMB(prims,itime,r,k,(unsigned)geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != prims.size()) + { + progressMonitor(0); + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfo(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID, const PrimInfo& base) -> PrimInfo { + return mesh->createPrimRefArrayMB(prims,itime,r,base.size(),(unsigned)geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + } + return pinfo; + } + + PrimInfoMB createPrimRefArrayMSMBlur(Scene* scene, Geometry::GTypeMask types, mvector& prims, BuildProgressMonitor& progressMonitor, BBox1f t0t1) + { + ParallelForForPrefixSumState pstate; + Scene::Iterator2 iter(scene,types,true); + + /* first try */ + progressMonitor(0); + pstate.init(iter,size_t(1024)); + PrimInfoMB pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfoMB(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID) -> PrimInfoMB { + return mesh->createPrimRefMBArray(prims,t0t1,r,k,(unsigned)geomID); + }, [](const PrimInfoMB& a, const PrimInfoMB& b) -> PrimInfoMB { return PrimInfoMB::merge2(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != prims.size()) + { + progressMonitor(0); + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfoMB(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID, const PrimInfoMB& base) -> PrimInfoMB { + return mesh->createPrimRefMBArray(prims,t0t1,r,base.size(),(unsigned)geomID); + }, [](const PrimInfoMB& a, const PrimInfoMB& b) -> PrimInfoMB { return PrimInfoMB::merge2(a,b); }); + } + + /* the BVH starts with that time range, even though primitives might have smaller/larger time range */ + pinfo.time_range = t0t1; + return pinfo; + } + + template + size_t createMortonCodeArray(Mesh* mesh, mvector& morton, BuildProgressMonitor& progressMonitor) + { + size_t numPrimitives = morton.size(); + + /* compute scene bounds */ + std::pair cb_empty(0,empty); + auto cb = parallel_reduce + ( size_t(0), numPrimitives, size_t(1024), cb_empty, [&](const range& r) -> std::pair + { + size_t num = 0; + BBox3fa bounds = empty; + + for (size_t j=r.begin(); jbuildBounds(j,&prim_bounds))) continue; + bounds.extend(center2(prim_bounds)); + num++; + } + return std::make_pair(num,bounds); + }, [] (const std::pair& a, const std::pair& b) { + return std::make_pair(a.first + b.first,merge(a.second,b.second)); + }); + + + size_t numPrimitivesGen = cb.first; + const BBox3fa centBounds = cb.second; + + /* compute morton codes */ + if (likely(numPrimitivesGen == numPrimitives)) + { + /* fast path if all primitives were valid */ + BVHBuilderMorton::MortonCodeMapping mapping(centBounds); + parallel_for( size_t(0), numPrimitives, size_t(1024), [&](const range& r) -> void { + BVHBuilderMorton::MortonCodeGenerator generator(mapping,&morton.data()[r.begin()]); + for (size_t j=r.begin(); jbounds(j),unsigned(j)); + }); + } + else + { + /* slow path, fallback in case some primitives were invalid */ + ParallelPrefixSumState pstate; + BVHBuilderMorton::MortonCodeMapping mapping(centBounds); + parallel_prefix_sum( pstate, size_t(0), numPrimitives, size_t(1024), size_t(0), [&](const range& r, const size_t base) -> size_t { + size_t num = 0; + BVHBuilderMorton::MortonCodeGenerator generator(mapping,&morton.data()[r.begin()]); + for (size_t j=r.begin(); jbuildBounds(j,&bounds))) continue; + generator(bounds,unsigned(j)); + num++; + } + return num; + }, std::plus()); + + parallel_prefix_sum( pstate, size_t(0), numPrimitives, size_t(1024), size_t(0), [&](const range& r, const size_t base) -> size_t { + size_t num = 0; + BVHBuilderMorton::MortonCodeGenerator generator(mapping,&morton.data()[base]); + for (size_t j=r.begin(); jbuildBounds(j,&bounds)) continue; + generator(bounds,unsigned(j)); + num++; + } + return num; + }, std::plus()); + } + return numPrimitivesGen; + } + + // ==================================================================================================== + // ==================================================================================================== + // ==================================================================================================== + + // template for grid meshes + +#if 0 + template<> + PrimInfo createPrimRefArray(Scene* scene, mvector& prims, BuildProgressMonitor& progressMonitor) + { + PING; + ParallelForForPrefixSumState pstate; + Scene::Iterator iter(scene); + + /* first try */ + progressMonitor(0); + pstate.init(iter,size_t(1024)); + PrimInfo pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfo(empty), [&](GridMesh* mesh, const range& r, size_t k) -> PrimInfo + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jbuildBounds(j,&bounds)) continue; + const PrimRef prim(bounds,mesh->geomID,unsigned(j)); + pinfo.add_center2(prim); + prims[k++] = prim; + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != prims.size()) + { + progressMonitor(0); + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfo(empty), [&](GridMesh* mesh, const range& r, size_t k, const PrimInfo& base) -> PrimInfo + { + k = base.size(); + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jbuildBounds(j,&bounds)) continue; + const PrimRef prim(bounds,mesh->geomID,unsigned(j)); + pinfo.add_center2(prim); + prims[k++] = prim; + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + } + return pinfo; + } +#endif + + // ==================================================================================================== + // ==================================================================================================== + // ==================================================================================================== + + IF_ENABLED_TRIS (template size_t createMortonCodeArray(TriangleMesh* mesh COMMA mvector& morton COMMA BuildProgressMonitor& progressMonitor)); + IF_ENABLED_QUADS(template size_t createMortonCodeArray(QuadMesh* mesh COMMA mvector& morton COMMA BuildProgressMonitor& progressMonitor)); + IF_ENABLED_USER (template size_t createMortonCodeArray(UserGeometry* mesh COMMA mvector& morton COMMA BuildProgressMonitor& progressMonitor)); + IF_ENABLED_INSTANCE (template size_t createMortonCodeArray(Instance* mesh COMMA mvector& morton COMMA BuildProgressMonitor& progressMonitor)); + } +} diff --git a/thirdparty/embree/kernels/builders/primrefgen.h b/thirdparty/embree/kernels/builders/primrefgen.h new file mode 100644 index 000000000000..9919c945c31a --- /dev/null +++ b/thirdparty/embree/kernels/builders/primrefgen.h @@ -0,0 +1,28 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/scene.h" +#include "../common/primref.h" +#include "../common/primref_mb.h" +#include "priminfo.h" +#include "bvh_builder_morton.h" + +namespace embree +{ + namespace isa + { + PrimInfo createPrimRefArray(Geometry* geometry, unsigned int geomID, mvector& prims, BuildProgressMonitor& progressMonitor); + + PrimInfo createPrimRefArray(Scene* scene, Geometry::GTypeMask types, bool mblur, mvector& prims, BuildProgressMonitor& progressMonitor); + + PrimInfo createPrimRefArrayMBlur(Scene* scene, Geometry::GTypeMask types, mvector& prims, BuildProgressMonitor& progressMonitor, size_t itime = 0); + + PrimInfoMB createPrimRefArrayMSMBlur(Scene* scene, Geometry::GTypeMask types, mvector& prims, BuildProgressMonitor& progressMonitor, BBox1f t0t1 = BBox1f(0.0f,1.0f)); + + template + size_t createMortonCodeArray(Mesh* mesh, mvector& morton, BuildProgressMonitor& progressMonitor); + } +} + diff --git a/thirdparty/embree/kernels/builders/primrefgen_presplit.h b/thirdparty/embree/kernels/builders/primrefgen_presplit.h new file mode 100644 index 000000000000..8bdb38b955f7 --- /dev/null +++ b/thirdparty/embree/kernels/builders/primrefgen_presplit.h @@ -0,0 +1,371 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../builders/primrefgen.h" +#include "../builders/heuristic_spatial.h" +#include "../builders/splitter.h" + +#include "../../common/algorithms/parallel_for_for.h" +#include "../../common/algorithms/parallel_for_for_prefix_sum.h" + +#define DBG_PRESPLIT(x) +#define CHECK_PRESPLIT(x) + +#define GRID_SIZE 1024 +#define MAX_PRESPLITS_PER_PRIMITIVE_LOG 5 +#define MAX_PRESPLITS_PER_PRIMITIVE (1<(priority); + } + __forceinline bool operator < (const PresplitItem& item) const + { + return (priority < item.priority); + } + + template + __forceinline static float compute_priority(const PrimRef &ref, Scene *scene, const Vec2i &mc) + { + const unsigned int geomID = ref.geomID(); + const unsigned int primID = ref.primID(); + const float area_aabb = area(ref.bounds()); + const float area_prim = ((Mesh*)scene->get(geomID))->projectedPrimitiveArea(primID); + const unsigned int diff = 31 - lzcnt(mc.x^mc.y); + assert(area_prim <= area_aabb); + //const float priority = powf((area_aabb - area_prim) * powf(PRIORITY_SPLIT_POS_WEIGHT,(float)diff),1.0f/4.0f); + const float priority = sqrtf(sqrtf( (area_aabb - area_prim) * powf(PRIORITY_SPLIT_POS_WEIGHT,(float)diff) )); + assert(priority >= 0.0f && priority < FLT_LARGE); + return priority; + } + + + }; + + inline std::ostream &operator<<(std::ostream &cout, const PresplitItem& item) { + return cout << "index " << item.index << " priority " << item.priority; + }; + + template + void splitPrimitive(SplitterFactory &Splitter, + const PrimRef &prim, + const unsigned int geomID, + const unsigned int primID, + const unsigned int split_level, + const Vec3fa &grid_base, + const float grid_scale, + const float grid_extend, + PrimRef subPrims[MAX_PRESPLITS_PER_PRIMITIVE], + unsigned int& numSubPrims) + { + assert(split_level <= MAX_PRESPLITS_PER_PRIMITIVE_LOG); + if (split_level == 0) + { + assert(numSubPrims < MAX_PRESPLITS_PER_PRIMITIVE); + subPrims[numSubPrims++] = prim; + } + else + { + const Vec3fa lower = prim.lower; + const Vec3fa upper = prim.upper; + const Vec3fa glower = (lower-grid_base)*Vec3fa(grid_scale)+Vec3fa(0.2f); + const Vec3fa gupper = (upper-grid_base)*Vec3fa(grid_scale)-Vec3fa(0.2f); + Vec3ia ilower(floor(glower)); + Vec3ia iupper(floor(gupper)); + + /* this ignores dimensions that are empty */ + iupper = (Vec3ia)(select(vint4(glower) >= vint4(gupper),vint4(ilower),vint4(iupper))); + + /* compute a morton code for the lower and upper grid coordinates. */ + const unsigned int lower_code = bitInterleave(ilower.x,ilower.y,ilower.z); + const unsigned int upper_code = bitInterleave(iupper.x,iupper.y,iupper.z); + + /* if all bits are equal then we cannot split */ + if(unlikely(lower_code == upper_code)) + { + assert(numSubPrims < MAX_PRESPLITS_PER_PRIMITIVE); + subPrims[numSubPrims++] = prim; + return; + } + + /* compute octree level and dimension to perform the split in */ + const unsigned int diff = 31 - lzcnt(lower_code^upper_code); + const unsigned int level = diff / 3; + const unsigned int dim = diff % 3; + + /* now we compute the grid position of the split */ + const unsigned int isplit = iupper[dim] & ~((1<= fsplit); + + /* split primitive */ + const auto splitter = Splitter(prim); + BBox3fa left,right; + splitter(prim.bounds(),dim,fsplit,left,right); + assert(!left.empty()); + assert(!right.empty()); + + + splitPrimitive(Splitter,PrimRef(left ,geomID,primID),geomID,primID,split_level-1,grid_base,grid_scale,grid_extend,subPrims,numSubPrims); + splitPrimitive(Splitter,PrimRef(right,geomID,primID),geomID,primID,split_level-1,grid_base,grid_scale,grid_extend,subPrims,numSubPrims); + } + } + + + template + PrimInfo createPrimRefArray_presplit(Geometry* geometry, unsigned int geomID, size_t numPrimRefs, mvector& prims, BuildProgressMonitor& progressMonitor) + { + ParallelPrefixSumState pstate; + + /* first try */ + progressMonitor(0); + PrimInfo pinfo = parallel_prefix_sum( pstate, size_t(0), geometry->size(), size_t(1024), PrimInfo(empty), [&](const range& r, const PrimInfo& base) -> PrimInfo { + return geometry->createPrimRefArray(prims,r,r.begin(),geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != numPrimRefs) + { + progressMonitor(0); + pinfo = parallel_prefix_sum( pstate, size_t(0), geometry->size(), size_t(1024), PrimInfo(empty), [&](const range& r, const PrimInfo& base) -> PrimInfo { + return geometry->createPrimRefArray(prims,r,base.size(),geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + } + return pinfo; + } + + __forceinline Vec2i computeMC(const Vec3fa &grid_base, const float grid_scale, const PrimRef &ref) + { + const Vec3fa lower = ref.lower; + const Vec3fa upper = ref.upper; + const Vec3fa glower = (lower-grid_base)*Vec3fa(grid_scale)+Vec3fa(0.2f); + const Vec3fa gupper = (upper-grid_base)*Vec3fa(grid_scale)-Vec3fa(0.2f); + Vec3ia ilower(floor(glower)); + Vec3ia iupper(floor(gupper)); + + /* this ignores dimensions that are empty */ + iupper = (Vec3ia)select(vint4(glower) >= vint4(gupper),vint4(ilower),vint4(iupper)); + + /* compute a morton code for the lower and upper grid coordinates. */ + const unsigned int lower_code = bitInterleave(ilower.x,ilower.y,ilower.z); + const unsigned int upper_code = bitInterleave(iupper.x,iupper.y,iupper.z); + return Vec2i(lower_code,upper_code); + } + + template + PrimInfo createPrimRefArray_presplit(Scene* scene, Geometry::GTypeMask types, bool mblur, size_t numPrimRefs, mvector& prims, BuildProgressMonitor& progressMonitor) + { + static const size_t MIN_STEP_SIZE = 128; + + ParallelForForPrefixSumState pstate; + Scene::Iterator2 iter(scene,types,mblur); + + /* first try */ + progressMonitor(0); + pstate.init(iter,size_t(1024)); + PrimInfo pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfo(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID) -> PrimInfo { + return mesh->createPrimRefArray(prims,r,k,(unsigned)geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + /* if we need to filter out geometry, run again */ + if (pinfo.size() != numPrimRefs) + { + progressMonitor(0); + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfo(empty), [&](Geometry* mesh, const range& r, size_t k, size_t geomID, const PrimInfo& base) -> PrimInfo { + return mesh->createPrimRefArray(prims,r,base.size(),(unsigned)geomID); + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + } + + /* use correct number of primitives */ + size_t numPrimitives = pinfo.size(); + const size_t alloc_numPrimitives = prims.size(); + const size_t numSplitPrimitivesBudget = alloc_numPrimitives - numPrimitives; + + /* set up primitive splitter */ + SplitterFactory Splitter(scene); + + + DBG_PRESPLIT( + const size_t org_numPrimitives = pinfo.size(); + PRINT(numPrimitives); + PRINT(alloc_numPrimitives); + PRINT(numSplitPrimitivesBudget); + ); + + /* allocate double buffer presplit items */ + const size_t presplit_allocation_size = sizeof(PresplitItem)*alloc_numPrimitives; + PresplitItem *presplitItem = (PresplitItem*)alignedMalloc(presplit_allocation_size,64); + PresplitItem *tmp_presplitItem = (PresplitItem*)alignedMalloc(presplit_allocation_size,64); + + /* compute grid */ + const Vec3fa grid_base = pinfo.geomBounds.lower; + const Vec3fa grid_diag = pinfo.geomBounds.size(); + const float grid_extend = max(grid_diag.x,max(grid_diag.y,grid_diag.z)); + const float grid_scale = grid_extend == 0.0f ? 0.0f : GRID_SIZE / grid_extend; + + /* init presplit items and get total sum */ + const float psum = parallel_reduce( size_t(0), numPrimitives, size_t(MIN_STEP_SIZE), 0.0f, [&](const range& r) -> float { + float sum = 0.0f; + for (size_t i=r.begin(); i(prims[i],scene,mc) : 0.0f; + /* FIXME: sum undeterministic */ + sum += presplitItem[i].priority; + } + return sum; + },[](const float& a, const float& b) -> float { return a+b; }); + + /* compute number of splits per primitive */ + const float inv_psum = 1.0f / psum; + parallel_for( size_t(0), numPrimitives, size_t(MIN_STEP_SIZE), [&](const range& r) -> void { + for (size_t i=r.begin(); i 0.0f) + { + const float rel_p = (float)numSplitPrimitivesBudget * presplitItem[i].priority * inv_psum; + if (rel_p >= PRIORITY_CUTOFF_THRESHOLD) // need at least a split budget that generates two sub-prims + { + presplitItem[i].priority = max(min(ceilf(logf(rel_p)/logf(2.0f)),(float)MAX_PRESPLITS_PER_PRIMITIVE_LOG),1.0f); + //presplitItem[i].priority = min(floorf(logf(rel_p)/logf(2.0f)),(float)MAX_PRESPLITS_PER_PRIMITIVE_LOG); + assert(presplitItem[i].priority >= 0.0f && presplitItem[i].priority <= (float)MAX_PRESPLITS_PER_PRIMITIVE_LOG); + } + else + presplitItem[i].priority = 0.0f; + } + } + }); + + auto isLeft = [&] (const PresplitItem &ref) { return ref.priority < PRIORITY_CUTOFF_THRESHOLD; }; + size_t center = parallel_partitioning(presplitItem,0,numPrimitives,isLeft,1024); + + /* anything to split ? */ + if (center < numPrimitives) + { + const size_t numPrimitivesToSplit = numPrimitives - center; + assert(presplitItem[center].priority >= 1.0f); + + /* sort presplit items in ascending order */ + radix_sort_u32(presplitItem + center,tmp_presplitItem + center,numPrimitivesToSplit,1024); + + CHECK_PRESPLIT( + parallel_for( size_t(center+1), numPrimitives, size_t(MIN_STEP_SIZE), [&](const range& r) -> void { + for (size_t i=r.begin(); i& t) -> size_t { + size_t sum = 0; + for (size_t i=t.begin(); i= 1.0f); + const unsigned int primrefID = presplitItem[i].index; + const float prio = presplitItem[i].priority; + const unsigned int geomID = prims[primrefID].geomID(); + const unsigned int primID = prims[primrefID].primID(); + const unsigned int split_levels = (unsigned int)prio; + unsigned int numSubPrims = 0; + splitPrimitive(Splitter,prims[primrefID],geomID,primID,split_levels,grid_base,grid_scale,grid_extend,subPrims,numSubPrims); + assert(numSubPrims); + numSubPrims--; // can reuse slot + sum+=numSubPrims; + presplitItem[i].data = (numSubPrims << MAX_PRESPLITS_PER_PRIMITIVE_LOG) | split_levels; + primOffset0[i-center] = numSubPrims; + } + return sum; + },[](const size_t& a, const size_t& b) -> size_t { return a+b; }); + + /* if we are over budget, need to shrink the range */ + if (totalNumSubPrims > numSplitPrimitivesBudget) + { + size_t new_center = numPrimitives-1; + size_t sum = 0; + for (;new_center>=center;new_center--) + { + const unsigned int numSubPrims = presplitItem[new_center].data >> MAX_PRESPLITS_PER_PRIMITIVE_LOG; + if (unlikely(sum + numSubPrims >= numSplitPrimitivesBudget)) break; + sum += numSubPrims; + } + new_center++; + center = new_center; + } + + /* parallel prefix sum to compute offsets for storing sub-primitives */ + const unsigned int offset = parallel_prefix_sum(primOffset0,primOffset1,numPrimitivesToSplit,(unsigned int)0,std::plus()); + + /* iterate over range, and split primitives into sub primitives and append them to prims array */ + parallel_for( size_t(center), numPrimitives, size_t(MIN_STEP_SIZE), [&](const range& rn) -> void { + for (size_t j=rn.begin(); j& r) -> PrimInfo { + PrimInfo p(empty); + for (size_t j=r.begin(); j PrimInfo { return PrimInfo::merge(a,b); }); + + assert(pinfo.size() == numPrimitives); + + /* free double buffer presplit items */ + alignedFree(tmp_presplitItem); + alignedFree(presplitItem); + return pinfo; + } + } +} diff --git a/thirdparty/embree/kernels/builders/splitter.h b/thirdparty/embree/kernels/builders/splitter.h new file mode 100644 index 000000000000..dbd6cf07c7b2 --- /dev/null +++ b/thirdparty/embree/kernels/builders/splitter.h @@ -0,0 +1,169 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/scene.h" +#include "../common/primref.h" + +namespace embree +{ + namespace isa + { + template + __forceinline void splitPolygon(const BBox3fa& bounds, + const size_t dim, + const float pos, + const Vec3fa (&v)[N+1], + const Vec3fa (&inv_length)[N], + BBox3fa& left_o, + BBox3fa& right_o) + { + BBox3fa left = empty, right = empty; + /* clip triangle to left and right box by processing all edges */ + for (size_t i=0; i= pos) right.extend(v0); // this point is on right side + + if ((v0d < pos && pos < v1d) || (v1d < pos && pos < v0d)) // the edge crosses the splitting location + { + assert((v1d-v0d) != 0.0f); + const Vec3fa c = madd(Vec3fa((pos-v0d)*inv_length[i][dim]),v1-v0,v0); + left.extend(c); + right.extend(c); + } + } + + /* clip against current bounds */ + left_o = intersect(left,bounds); + right_o = intersect(right,bounds); + } + + template + __forceinline void splitPolygon(const PrimRef& prim, + const size_t dim, + const float pos, + const Vec3fa (&v)[N+1], + PrimRef& left_o, + PrimRef& right_o) + { + BBox3fa left = empty, right = empty; + for (size_t i=0; i= pos) right.extend(v0); // this point is on right side + + if ((v0d < pos && pos < v1d) || (v1d < pos && pos < v0d)) // the edge crosses the splitting location + { + assert((v1d-v0d) != 0.0f); + const float inv_length = 1.0f/(v1d-v0d); + const Vec3fa c = madd(Vec3fa((pos-v0d)*inv_length),v1-v0,v0); + left.extend(c); + right.extend(c); + } + } + + /* clip against current bounds */ + new (&left_o ) PrimRef(intersect(left ,prim.bounds()),prim.geomID(), prim.primID()); + new (&right_o) PrimRef(intersect(right,prim.bounds()),prim.geomID(), prim.primID()); + } + + struct TriangleSplitter + { + __forceinline TriangleSplitter(const Scene* scene, const PrimRef& prim) + { + const unsigned int mask = 0xFFFFFFFF >> RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS; + const TriangleMesh* mesh = (const TriangleMesh*) scene->get(prim.geomID() & mask ); + TriangleMesh::Triangle tri = mesh->triangle(prim.primID()); + v[0] = mesh->vertex(tri.v[0]); + v[1] = mesh->vertex(tri.v[1]); + v[2] = mesh->vertex(tri.v[2]); + v[3] = mesh->vertex(tri.v[0]); + inv_length[0] = Vec3fa(1.0f) / (v[1]-v[0]); + inv_length[1] = Vec3fa(1.0f) / (v[2]-v[1]); + inv_length[2] = Vec3fa(1.0f) / (v[0]-v[2]); + } + + __forceinline void operator() (const PrimRef& prim, const size_t dim, const float pos, PrimRef& left_o, PrimRef& right_o) const { + splitPolygon<3>(prim,dim,pos,v,left_o,right_o); + } + + __forceinline void operator() (const BBox3fa& prim, const size_t dim, const float pos, BBox3fa& left_o, BBox3fa& right_o) const { + splitPolygon<3>(prim,dim,pos,v,inv_length,left_o,right_o); + } + + private: + Vec3fa v[4]; + Vec3fa inv_length[3]; + }; + + struct TriangleSplitterFactory + { + __forceinline TriangleSplitterFactory(const Scene* scene) + : scene(scene) {} + + __forceinline TriangleSplitter operator() (const PrimRef& prim) const { + return TriangleSplitter(scene,prim); + } + + private: + const Scene* scene; + }; + + struct QuadSplitter + { + __forceinline QuadSplitter(const Scene* scene, const PrimRef& prim) + { + const unsigned int mask = 0xFFFFFFFF >> RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS; + const QuadMesh* mesh = (const QuadMesh*) scene->get(prim.geomID() & mask ); + QuadMesh::Quad quad = mesh->quad(prim.primID()); + v[0] = mesh->vertex(quad.v[0]); + v[1] = mesh->vertex(quad.v[1]); + v[2] = mesh->vertex(quad.v[2]); + v[3] = mesh->vertex(quad.v[3]); + v[4] = mesh->vertex(quad.v[0]); + inv_length[0] = Vec3fa(1.0f) / (v[1]-v[0]); + inv_length[1] = Vec3fa(1.0f) / (v[2]-v[1]); + inv_length[2] = Vec3fa(1.0f) / (v[3]-v[2]); + inv_length[3] = Vec3fa(1.0f) / (v[0]-v[3]); + } + + __forceinline void operator() (const PrimRef& prim, const size_t dim, const float pos, PrimRef& left_o, PrimRef& right_o) const { + splitPolygon<4>(prim,dim,pos,v,left_o,right_o); + } + + __forceinline void operator() (const BBox3fa& prim, const size_t dim, const float pos, BBox3fa& left_o, BBox3fa& right_o) const { + splitPolygon<4>(prim,dim,pos,v,inv_length,left_o,right_o); + } + + private: + Vec3fa v[5]; + Vec3fa inv_length[4]; + }; + + struct QuadSplitterFactory + { + __forceinline QuadSplitterFactory(const Scene* scene) + : scene(scene) {} + + __forceinline QuadSplitter operator() (const PrimRef& prim) const { + return QuadSplitter(scene,prim); + } + + private: + const Scene* scene; + }; + } +} + diff --git a/thirdparty/embree/kernels/bvh/bvh.cpp b/thirdparty/embree/kernels/bvh/bvh.cpp new file mode 100644 index 000000000000..9dbb3bcd732e --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh.cpp @@ -0,0 +1,190 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh.h" +#include "bvh_statistics.h" + +namespace embree +{ + template + BVHN::BVHN (const PrimitiveType& primTy, Scene* scene) + : AccelData((N==4) ? AccelData::TY_BVH4 : (N==8) ? AccelData::TY_BVH8 : AccelData::TY_UNKNOWN), + primTy(&primTy), device(scene->device), scene(scene), + root(emptyNode), alloc(scene->device,scene->isStaticAccel()), numPrimitives(0), numVertices(0) + { + } + + template + BVHN::~BVHN () + { + for (size_t i=0; i + void BVHN::clear() + { + set(BVHN::emptyNode,empty,0); + alloc.clear(); + } + + template + void BVHN::set (NodeRef root, const LBBox3fa& bounds, size_t numPrimitives) + { + this->root = root; + this->bounds = bounds; + this->numPrimitives = numPrimitives; + } + + template + void BVHN::clearBarrier(NodeRef& node) + { + if (node.isBarrier()) + node.clearBarrier(); + else if (!node.isLeaf()) { + BaseNode* n = node.baseNode(); // FIXME: flags should be stored in BVH + for (size_t c=0; cchild(c)); + } + } + + template + void BVHN::layoutLargeNodes(size_t num) + { +#if defined(__X86_64__) // do not use tree rotations on 32 bit platforms, barrier bit in NodeRef will cause issues + struct NodeArea + { + __forceinline NodeArea() {} + + __forceinline NodeArea(NodeRef& node, const BBox3fa& bounds) + : node(&node), A(node.isLeaf() ? float(neg_inf) : area(bounds)) {} + + __forceinline bool operator< (const NodeArea& other) const { + return this->A < other.A; + } + + NodeRef* node; + float A; + }; + std::vector lst; + lst.reserve(num); + lst.push_back(NodeArea(root,empty)); + + while (lst.size() < num) + { + std::pop_heap(lst.begin(), lst.end()); + NodeArea n = lst.back(); lst.pop_back(); + if (!n.node->isAABBNode()) break; + AABBNode* node = n.node->getAABBNode(); + for (size_t i=0; ichild(i) == BVHN::emptyNode) continue; + lst.push_back(NodeArea(node->child(i),node->bounds(i))); + std::push_heap(lst.begin(), lst.end()); + } + } + + for (size_t i=0; isetBarrier(); + + root = layoutLargeNodesRecursion(root,alloc.getCachedAllocator()); +#endif + } + + template + typename BVHN::NodeRef BVHN::layoutLargeNodesRecursion(NodeRef& node, const FastAllocator::CachedAllocator& allocator) + { + if (node.isBarrier()) { + node.clearBarrier(); + return node; + } + else if (node.isAABBNode()) + { + AABBNode* oldnode = node.getAABBNode(); + AABBNode* newnode = (BVHN::AABBNode*) allocator.malloc0(sizeof(BVHN::AABBNode),byteNodeAlignment); + *newnode = *oldnode; + for (size_t c=0; cchild(c) = layoutLargeNodesRecursion(oldnode->child(c),allocator); + return encodeNode(newnode); + } + else return node; + } + + template + double BVHN::preBuild(const std::string& builderName) + { + if (builderName == "") + return inf; + + if (device->verbosity(2)) + { + Lock lock(g_printMutex); + std::cout << "building BVH" << N << (builderName.find("MBlur") != std::string::npos ? "MB" : "") << "<" << primTy->name() << "> using " << builderName << " ..." << std::endl << std::flush; + } + + double t0 = 0.0; + if (device->benchmark || device->verbosity(2)) t0 = getSeconds(); + return t0; + } + + template + void BVHN::postBuild(double t0) + { + if (t0 == double(inf)) + return; + + double dt = 0.0; + if (device->benchmark || device->verbosity(2)) + dt = getSeconds()-t0; + + std::unique_ptr> stat; + + /* print statistics */ + if (device->verbosity(2)) + { + if (!stat) stat.reset(new BVHNStatistics(this)); + const size_t usedBytes = alloc.getUsedBytes(); + Lock lock(g_printMutex); + std::cout << "finished BVH" << N << "<" << primTy->name() << "> : " << 1000.0f*dt << "ms, " << 1E-6*double(numPrimitives)/dt << " Mprim/s, " << 1E-9*double(usedBytes)/dt << " GB/s" << std::endl; + + if (device->verbosity(2)) + std::cout << stat->str(); + + if (device->verbosity(2)) + { + FastAllocator::AllStatistics stat(&alloc); + for (size_t i=0; ialloc); + + stat.print(numPrimitives); + } + + if (device->verbosity(3)) + { + alloc.print_blocks(); + for (size_t i=0; ialloc.print_blocks(); + } + + std::cout << std::flush; + } + + /* benchmark mode */ + if (device->benchmark) + { + if (!stat) stat.reset(new BVHNStatistics(this)); + Lock lock(g_printMutex); + std::cout << "BENCHMARK_BUILD " << dt << " " << double(numPrimitives)/dt << " " << stat->sah() << " " << stat->bytesUsed() << " BVH" << N << "<" << primTy->name() << ">" << std::endl << std::flush; + } + } + +#if defined(__AVX__) + template class BVHN<8>; +#endif + +#if !defined(__AVX__) || !defined(EMBREE_TARGET_SSE2) && !defined(EMBREE_TARGET_SSE42) + template class BVHN<4>; +#endif +} + diff --git a/thirdparty/embree/kernels/bvh/bvh.h b/thirdparty/embree/kernels/bvh/bvh.h new file mode 100644 index 000000000000..7c1a45b63288 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh.h @@ -0,0 +1,235 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +/* include all node types */ +#include "bvh_node_aabb.h" +#include "bvh_node_aabb_mb.h" +#include "bvh_node_aabb_mb4d.h" +#include "bvh_node_obb.h" +#include "bvh_node_obb_mb.h" +#include "bvh_node_qaabb.h" + +namespace embree +{ + /*! flags used to enable specific node types in intersectors */ + enum BVHNodeFlags + { + BVH_FLAG_ALIGNED_NODE = 0x00001, + BVH_FLAG_ALIGNED_NODE_MB = 0x00010, + BVH_FLAG_UNALIGNED_NODE = 0x00100, + BVH_FLAG_UNALIGNED_NODE_MB = 0x01000, + BVH_FLAG_QUANTIZED_NODE = 0x100000, + BVH_FLAG_ALIGNED_NODE_MB4D = 0x1000000, + + /* short versions */ + BVH_AN1 = BVH_FLAG_ALIGNED_NODE, + BVH_AN2 = BVH_FLAG_ALIGNED_NODE_MB, + BVH_AN2_AN4D = BVH_FLAG_ALIGNED_NODE_MB | BVH_FLAG_ALIGNED_NODE_MB4D, + BVH_UN1 = BVH_FLAG_UNALIGNED_NODE, + BVH_UN2 = BVH_FLAG_UNALIGNED_NODE_MB, + BVH_MB = BVH_FLAG_ALIGNED_NODE_MB | BVH_FLAG_UNALIGNED_NODE_MB | BVH_FLAG_ALIGNED_NODE_MB4D, + BVH_AN1_UN1 = BVH_FLAG_ALIGNED_NODE | BVH_FLAG_UNALIGNED_NODE, + BVH_AN2_UN2 = BVH_FLAG_ALIGNED_NODE_MB | BVH_FLAG_UNALIGNED_NODE_MB, + BVH_AN2_AN4D_UN2 = BVH_FLAG_ALIGNED_NODE_MB | BVH_FLAG_ALIGNED_NODE_MB4D | BVH_FLAG_UNALIGNED_NODE_MB, + BVH_QN1 = BVH_FLAG_QUANTIZED_NODE + }; + + /*! Multi BVH with N children. Each node stores the bounding box of + * it's N children as well as N child references. */ + template + class BVHN : public AccelData + { + ALIGNED_CLASS_(16); + public: + + /*! forward declaration of node ref type */ + typedef NodeRefPtr NodeRef; + typedef BaseNode_t BaseNode; + typedef AABBNode_t AABBNode; + typedef AABBNodeMB_t AABBNodeMB; + typedef AABBNodeMB4D_t AABBNodeMB4D; + typedef OBBNode_t OBBNode; + typedef OBBNodeMB_t OBBNodeMB; + typedef QuantizedBaseNode_t QuantizedBaseNode; + typedef QuantizedBaseNodeMB_t QuantizedBaseNodeMB; + typedef QuantizedNode_t QuantizedNode; + + /*! Number of bytes the nodes and primitives are minimally aligned to.*/ + static const size_t byteAlignment = 16; + static const size_t byteNodeAlignment = 4*N; + + /*! Empty node */ + static const size_t emptyNode = NodeRef::emptyNode; + + /*! Invalid node, used as marker in traversal */ + static const size_t invalidNode = NodeRef::invalidNode; + static const size_t popRay = NodeRef::popRay; + + /*! Maximum depth of the BVH. */ + static const size_t maxBuildDepth = 32; + static const size_t maxBuildDepthLeaf = maxBuildDepth+8; + static const size_t maxDepth = 2*maxBuildDepthLeaf; // 2x because of two level builder + + /*! Maximum number of primitive blocks in a leaf. */ + static const size_t maxLeafBlocks = NodeRef::maxLeafBlocks; + + public: + + /*! Builder interface to create allocator */ + struct CreateAlloc : public FastAllocator::Create { + __forceinline CreateAlloc (BVHN* bvh) : FastAllocator::Create(&bvh->alloc) {} + }; + + typedef BVHNodeRecord NodeRecord; + typedef BVHNodeRecordMB NodeRecordMB; + typedef BVHNodeRecordMB4D NodeRecordMB4D; + + public: + + /*! BVHN default constructor. */ + BVHN (const PrimitiveType& primTy, Scene* scene); + + /*! BVHN destruction */ + ~BVHN (); + + /*! clears the acceleration structure */ + void clear(); + + /*! sets BVH members after build */ + void set (NodeRef root, const LBBox3fa& bounds, size_t numPrimitives); + + /*! Clears the barrier bits of a subtree. */ + void clearBarrier(NodeRef& node); + + /*! lays out num large nodes of the BVH */ + void layoutLargeNodes(size_t num); + NodeRef layoutLargeNodesRecursion(NodeRef& node, const FastAllocator::CachedAllocator& allocator); + + /*! called by all builders before build starts */ + double preBuild(const std::string& builderName); + + /*! called by all builders after build ended */ + void postBuild(double t0); + + /*! allocator class */ + struct Allocator { + BVHN* bvh; + Allocator (BVHN* bvh) : bvh(bvh) {} + __forceinline void* operator() (size_t bytes) const { + return bvh->alloc._threadLocal()->malloc(&bvh->alloc,bytes); + } + }; + + /*! post build cleanup */ + void cleanup() { + alloc.cleanup(); + } + + public: + + /*! Encodes a node */ + static __forceinline NodeRef encodeNode(AABBNode* node) { return NodeRef::encodeNode(node); } + static __forceinline NodeRef encodeNode(AABBNodeMB* node) { return NodeRef::encodeNode(node); } + static __forceinline NodeRef encodeNode(AABBNodeMB4D* node) { return NodeRef::encodeNode(node); } + static __forceinline NodeRef encodeNode(OBBNode* node) { return NodeRef::encodeNode(node); } + static __forceinline NodeRef encodeNode(OBBNodeMB* node) { return NodeRef::encodeNode(node); } + static __forceinline NodeRef encodeLeaf(void* tri, size_t num) { return NodeRef::encodeLeaf(tri,num); } + static __forceinline NodeRef encodeTypedLeaf(void* ptr, size_t ty) { return NodeRef::encodeTypedLeaf(ptr,ty); } + + public: + + /*! Prefetches the node this reference points to */ + __forceinline static void prefetch(const NodeRef ref, int types=0) + { +#if defined(__AVX512PF__) // MIC + if (types != BVH_FLAG_QUANTIZED_NODE) { + prefetchL2(((char*)ref.ptr)+0*64); + prefetchL2(((char*)ref.ptr)+1*64); + if ((N >= 8) || (types > BVH_FLAG_ALIGNED_NODE)) { + prefetchL2(((char*)ref.ptr)+2*64); + prefetchL2(((char*)ref.ptr)+3*64); + } + if ((N >= 8) && (types > BVH_FLAG_ALIGNED_NODE)) { + /* KNL still needs L2 prefetches for large nodes */ + prefetchL2(((char*)ref.ptr)+4*64); + prefetchL2(((char*)ref.ptr)+5*64); + prefetchL2(((char*)ref.ptr)+6*64); + prefetchL2(((char*)ref.ptr)+7*64); + } + } + else + { + /* todo: reduce if 32bit offsets are enabled */ + prefetchL2(((char*)ref.ptr)+0*64); + prefetchL2(((char*)ref.ptr)+1*64); + prefetchL2(((char*)ref.ptr)+2*64); + } +#else + if (types != BVH_FLAG_QUANTIZED_NODE) { + prefetchL1(((char*)ref.ptr)+0*64); + prefetchL1(((char*)ref.ptr)+1*64); + if ((N >= 8) || (types > BVH_FLAG_ALIGNED_NODE)) { + prefetchL1(((char*)ref.ptr)+2*64); + prefetchL1(((char*)ref.ptr)+3*64); + } + if ((N >= 8) && (types > BVH_FLAG_ALIGNED_NODE)) { + /* deactivate for large nodes on Xeon, as it introduces regressions */ + //prefetchL1(((char*)ref.ptr)+4*64); + //prefetchL1(((char*)ref.ptr)+5*64); + //prefetchL1(((char*)ref.ptr)+6*64); + //prefetchL1(((char*)ref.ptr)+7*64); + } + } + else + { + /* todo: reduce if 32bit offsets are enabled */ + prefetchL1(((char*)ref.ptr)+0*64); + prefetchL1(((char*)ref.ptr)+1*64); + prefetchL1(((char*)ref.ptr)+2*64); + } +#endif + } + + __forceinline static void prefetchW(const NodeRef ref, int types=0) + { + embree::prefetchEX(((char*)ref.ptr)+0*64); + embree::prefetchEX(((char*)ref.ptr)+1*64); + if ((N >= 8) || (types > BVH_FLAG_ALIGNED_NODE)) { + embree::prefetchEX(((char*)ref.ptr)+2*64); + embree::prefetchEX(((char*)ref.ptr)+3*64); + } + if ((N >= 8) && (types > BVH_FLAG_ALIGNED_NODE)) { + embree::prefetchEX(((char*)ref.ptr)+4*64); + embree::prefetchEX(((char*)ref.ptr)+5*64); + embree::prefetchEX(((char*)ref.ptr)+6*64); + embree::prefetchEX(((char*)ref.ptr)+7*64); + } + } + + /*! bvh type information */ + public: + const PrimitiveType* primTy; //!< primitive type stored in the BVH + + /*! bvh data */ + public: + Device* device; //!< device pointer + Scene* scene; //!< scene pointer + NodeRef root; //!< root node + FastAllocator alloc; //!< allocator used to allocate nodes + + /*! statistics data */ + public: + size_t numPrimitives; //!< number of primitives the BVH is build over + size_t numVertices; //!< number of vertices the BVH references + + /*! data arrays for special builders */ + public: + std::vector objects; + vector_t> subdiv_patches; + }; + + typedef BVHN<4> BVH4; + typedef BVHN<8> BVH8; +} diff --git a/thirdparty/embree/kernels/bvh/bvh4_factory.cpp b/thirdparty/embree/kernels/bvh/bvh4_factory.cpp new file mode 100644 index 000000000000..23f4f63d45f0 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh4_factory.cpp @@ -0,0 +1,1325 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh4_factory.h" +#include "../bvh/bvh.h" + +#include "../geometry/curveNv.h" +#include "../geometry/curveNi.h" +#include "../geometry/curveNi_mb.h" +#include "../geometry/linei.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglev_mb.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/subdivpatch1.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" +#include "../geometry/subgrid.h" +#include "../common/accelinstance.h" + +namespace embree +{ + DECLARE_SYMBOL2(Accel::Collider,BVH4ColliderUserGeom); + + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector4i,void); + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector8i,void); + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector4v,void); + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector8v,void); + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector4iMB,void); + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector8iMB,void); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersector1MB); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersectorRobust1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersectorRobust1MB); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4Intersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4vIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4vMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4vMBIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iMBIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Quad4vIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Quad4iIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Quad4vIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Quad4iIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Quad4iMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4Quad4iMBIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,QBVH4Triangle4iIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,QBVH4Quad4iIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4SubdivPatch1Intersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4SubdivPatch1MBIntersector1); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4VirtualIntersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4VirtualMBIntersector1); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4InstanceIntersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4InstanceMBIntersector1); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH4GridIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4GridMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH4GridIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersector4Hybrid); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersector4HybridMB); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersectorRobust4Hybrid); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersectorRobust4HybridMB); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4Intersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4Intersector4HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4vIntersector4HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4vMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4vMBIntersector4HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iMBIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4vIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4vIntersector4HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4iIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4vIntersector4HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4iIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4iMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4Quad4iMBIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4SubdivPatch1Intersector4); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4SubdivPatch1MBIntersector4); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4VirtualIntersector4Chunk); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4VirtualMBIntersector4Chunk); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4InstanceIntersector4Chunk); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4InstanceMBIntersector4Chunk); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH4GridIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4GridMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH4GridIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersector8Hybrid); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersector8HybridMB); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersectorRobust8Hybrid); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersectorRobust8HybridMB); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4Intersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4Intersector8HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4vIntersector8HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4vMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4vMBIntersector8HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iMBIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4vIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4vIntersector8HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4iIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4vIntersector8HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4iIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4iMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4Quad4iMBIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4SubdivPatch1Intersector8); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4SubdivPatch1MBIntersector8); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4VirtualIntersector8Chunk); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4VirtualMBIntersector8Chunk); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4InstanceIntersector8Chunk); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4InstanceMBIntersector8Chunk); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH4GridIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4GridMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH4GridIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersector16Hybrid); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersector16HybridMB); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersectorRobust16Hybrid); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersectorRobust16HybridMB); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4Intersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4Intersector16HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4vIntersector16HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4vMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4vMBIntersector16HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iMBIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4vIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4vIntersector16HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4iIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4vIntersector16HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4iIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4iMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4Quad4iMBIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4SubdivPatch1Intersector16); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4SubdivPatch1MBIntersector16); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4VirtualIntersector16Chunk); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4VirtualMBIntersector16Chunk); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4InstanceIntersector16Chunk); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4InstanceMBIntersector16Chunk); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH4GridIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4GridMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH4GridIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4IntersectorStreamPacketFallback); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Triangle4IntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Triangle4IntersectorStreamMoellerNoFilter); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Triangle4iIntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Triangle4vIntersectorStreamPluecker); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Triangle4iIntersectorStreamPluecker); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Quad4vIntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Quad4vIntersectorStreamMoellerNoFilter); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Quad4iIntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Quad4vIntersectorStreamPluecker); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4Quad4iIntersectorStreamPluecker); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4VirtualIntersectorStream); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH4InstanceIntersectorStream); + + DECLARE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelTriangle4MeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelTriangle4vMeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelTriangle4iMeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelQuadMeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelVirtualSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelInstanceSAH,void* COMMA Scene* COMMA Geometry::GTypeMask COMMA bool); + + DECLARE_ISA_FUNCTION(Builder*,BVH4Curve4vBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Curve4iBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4OBBCurve4iMBBuilder_OBB,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Curve8iBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4OBBCurve8iMBBuilder_OBB,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4SceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4QuantizedTriangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4QuantizedQuad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4SceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4VirtualSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4VirtualMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4InstanceSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + DECLARE_ISA_FUNCTION(Builder*,BVH4InstanceMBSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + + DECLARE_ISA_FUNCTION(Builder*,BVH4GridSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4GridMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4SubdivPatch1BuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4SubdivPatch1MBBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4MeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vMeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iMeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4vMeshRefitSAH,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4VirtualMeshRefitSAH,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + + BVH4Factory::BVH4Factory(int bfeatures, int ifeatures) + { + SELECT_SYMBOL_DEFAULT_AVX_AVX2(ifeatures,BVH4ColliderUserGeom); + + selectBuilders(bfeatures); + selectIntersectors(ifeatures); + } + + void BVH4Factory::selectBuilders(int features) + { + IF_ENABLED_TRIS (SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4BuilderTwoLevelTriangle4MeshSAH)); + IF_ENABLED_TRIS (SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4BuilderTwoLevelTriangle4iMeshSAH)); + IF_ENABLED_TRIS (SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4BuilderTwoLevelTriangle4vMeshSAH)); + IF_ENABLED_QUADS (SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4BuilderTwoLevelQuadMeshSAH)); + IF_ENABLED_USER (SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4BuilderTwoLevelVirtualSAH)); + IF_ENABLED_INSTANCE (SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4BuilderTwoLevelInstanceSAH)); + + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Curve4vBuilder_OBB_New)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Curve4iBuilder_OBB_New)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4OBBCurve4iMBBuilder_OBB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX(features,BVH4Curve8iBuilder_OBB_New)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX(features,BVH4OBBCurve8iMBBuilder_OBB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4Triangle4SceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4Triangle4vSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4Triangle4iSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Triangle4iMBSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Triangle4vMBSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4QuantizedTriangle4iSceneBuilderSAH)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4Quad4vSceneBuilderSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4Quad4iSceneBuilderSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Quad4iMBSceneBuilderSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4QuantizedQuad4iSceneBuilderSAH)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Triangle4SceneBuilderFastSpatialSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Triangle4vSceneBuilderFastSpatialSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Triangle4iSceneBuilderFastSpatialSAH)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4Quad4vSceneBuilderFastSpatialSAH)); + + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4VirtualSceneBuilderSAH)); + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4VirtualMBSceneBuilderSAH)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4InstanceSceneBuilderSAH)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4InstanceMBSceneBuilderSAH)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4GridSceneBuilderSAH)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_AVX(features,BVH4GridMBSceneBuilderSAH)); + + IF_ENABLED_SUBDIV(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4SubdivPatch1BuilderSAH)); + IF_ENABLED_SUBDIV(SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,BVH4SubdivPatch1MBBuilderSAH)); + } + + void BVH4Factory::selectIntersectors(int features) + { + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector4i)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector8i)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector4v)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector8v)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector4iMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector8iMB)); + + /* select intersectors1 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersector1)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersector1MB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust1)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust1MB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Triangle4Intersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX512SKX(features,BVH4Triangle4iIntersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX512SKX(features,BVH4Triangle4vIntersector1Pluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX512SKX(features,BVH4Triangle4iIntersector1Pluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vMBIntersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iMBIntersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vMBIntersector1Pluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iMBIntersector1Pluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector1Moeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iIntersector1Moeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector1Pluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iIntersector1Pluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iMBIntersector1Pluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iMBIntersector1Moeller)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX512SKX(features,QBVH4Triangle4iIntersector1Pluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX512SKX(features,QBVH4Quad4iIntersector1Pluecker)); + + IF_ENABLED_SUBDIV(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4SubdivPatch1Intersector1)); + IF_ENABLED_SUBDIV(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4SubdivPatch1MBIntersector1)); + + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4VirtualIntersector1)); + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4VirtualMBIntersector1)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4InstanceIntersector1)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4InstanceMBIntersector1)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4GridIntersector1Moeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4GridMBIntersector1Moeller)) + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4GridIntersector1Pluecker)); + +#if defined (EMBREE_RAY_PACKETS) + + /* select intersectors4 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersector4Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersector4HybridMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust4Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust4HybridMB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4Intersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4Intersector4HybridMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iIntersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vIntersector4HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iIntersector4HybridPluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vMBIntersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iMBIntersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vMBIntersector4HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iMBIntersector4HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector4HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector4HybridMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iIntersector4HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector4HybridPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iIntersector4HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iMBIntersector4HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4iMBIntersector4HybridPluecker)); + + IF_ENABLED_SUBDIV(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4SubdivPatch1Intersector4)); + IF_ENABLED_SUBDIV(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4SubdivPatch1MBIntersector4)); + + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4VirtualIntersector4Chunk)); + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4VirtualMBIntersector4Chunk)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4InstanceIntersector4Chunk)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4InstanceMBIntersector4Chunk)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector4HybridMoeller)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4GridIntersector4HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4GridMBIntersector4HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,BVH4GridIntersector4HybridPluecker)); + + /* select intersectors8 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersector8Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersector8HybridMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust8Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust8HybridMB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4Intersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4Intersector8HybridMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iIntersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vIntersector8HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iIntersector8HybridPluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vMBIntersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iMBIntersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4vMBIntersector8HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Triangle4iMBIntersector8HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector8HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector8HybridMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4iIntersector8HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4vIntersector8HybridPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4iIntersector8HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4iMBIntersector8HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4Quad4iMBIntersector8HybridPluecker)); + + IF_ENABLED_SUBDIV(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4SubdivPatch1Intersector8)); + IF_ENABLED_SUBDIV(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4SubdivPatch1MBIntersector8)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4VirtualIntersector8Chunk)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4VirtualMBIntersector8Chunk)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4InstanceIntersector8Chunk)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4InstanceMBIntersector8Chunk)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4GridIntersector8HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4GridMBIntersector8HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH4GridIntersector8HybridPluecker)); + + /* select intersectors16 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4OBBVirtualCurveIntersector16Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4OBBVirtualCurveIntersector16HybridMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust16Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4OBBVirtualCurveIntersectorRobust16HybridMB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4Intersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4Intersector16HybridMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4iIntersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4vIntersector16HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4iIntersector16HybridPluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4vMBIntersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4iMBIntersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4vMBIntersector16HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Triangle4iMBIntersector16HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4vIntersector16HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4vIntersector16HybridMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4iIntersector16HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4vIntersector16HybridPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4iIntersector16HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4iMBIntersector16HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4Quad4iMBIntersector16HybridPluecker)); + + IF_ENABLED_SUBDIV(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4SubdivPatch1Intersector16)); + IF_ENABLED_SUBDIV(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4SubdivPatch1MBIntersector16)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4VirtualIntersector16Chunk)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4VirtualMBIntersector16Chunk)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4InstanceIntersector16Chunk)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4InstanceMBIntersector16Chunk)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4GridIntersector16HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4GridMBIntersector16HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH4GridIntersector16HybridPluecker)); + + /* select stream intersectors */ + SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4IntersectorStreamPacketFallback); + + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Triangle4IntersectorStreamMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Triangle4IntersectorStreamMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Triangle4iIntersectorStreamMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Triangle4vIntersectorStreamPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Triangle4iIntersectorStreamPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Quad4vIntersectorStreamMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Quad4vIntersectorStreamMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Quad4iIntersectorStreamMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Quad4vIntersectorStreamPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4Quad4iIntersectorStreamPluecker)); + + IF_ENABLED_USER(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4VirtualIntersectorStream)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH4InstanceIntersectorStream)); + +#endif + } + + Accel::Intersectors BVH4Factory::BVH4OBBVirtualCurveIntersectors(BVH4* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH4OBBVirtualCurveIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4OBBVirtualCurveIntersector4Hybrid(); + intersectors.intersector8 = BVH4OBBVirtualCurveIntersector8Hybrid(); + intersectors.intersector16 = BVH4OBBVirtualCurveIntersector16Hybrid(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH4OBBVirtualCurveIntersectorRobust1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4OBBVirtualCurveIntersectorRobust4Hybrid(); + intersectors.intersector8 = BVH4OBBVirtualCurveIntersectorRobust8Hybrid(); + intersectors.intersector16 = BVH4OBBVirtualCurveIntersectorRobust16Hybrid(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + default: assert(false); + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4OBBVirtualCurveIntersectorsMB(BVH4* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH4OBBVirtualCurveIntersector1MB(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4OBBVirtualCurveIntersector4HybridMB(); + intersectors.intersector8 = BVH4OBBVirtualCurveIntersector8HybridMB(); + intersectors.intersector16 = BVH4OBBVirtualCurveIntersector16HybridMB(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH4OBBVirtualCurveIntersectorRobust1MB(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4OBBVirtualCurveIntersectorRobust4HybridMB(); + intersectors.intersector8 = BVH4OBBVirtualCurveIntersectorRobust8HybridMB(); + intersectors.intersector16 = BVH4OBBVirtualCurveIntersectorRobust16HybridMB(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + default: assert(false); + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4Triangle4Intersectors(BVH4* bvh, IntersectVariant ivariant) + { + assert(ivariant == IntersectVariant::FAST); + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4Intersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4_filter = BVH4Triangle4Intersector4HybridMoeller(); + intersectors.intersector4_nofilter = BVH4Triangle4Intersector4HybridMoellerNoFilter(); + intersectors.intersector8_filter = BVH4Triangle4Intersector8HybridMoeller(); + intersectors.intersector8_nofilter = BVH4Triangle4Intersector8HybridMoellerNoFilter(); + intersectors.intersector16_filter = BVH4Triangle4Intersector16HybridMoeller(); + intersectors.intersector16_nofilter = BVH4Triangle4Intersector16HybridMoellerNoFilter(); + intersectors.intersectorN_filter = BVH4Triangle4IntersectorStreamMoeller(); + intersectors.intersectorN_nofilter = BVH4Triangle4IntersectorStreamMoellerNoFilter(); +#endif + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4Triangle4vIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + assert(ivariant == IntersectVariant::ROBUST); + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4vIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4vIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Triangle4vIntersector8HybridPluecker(); + intersectors.intersector16 = BVH4Triangle4vIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4Triangle4vIntersectorStreamPluecker(); +#endif + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4Triangle4iIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4iIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4iIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4Triangle4iIntersector8HybridMoeller(); + intersectors.intersector16 = BVH4Triangle4iIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4Triangle4iIntersectorStreamMoeller(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4iIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4iIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Triangle4iIntersector8HybridPluecker(); + intersectors.intersector16 = BVH4Triangle4iIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4Triangle4iIntersectorStreamPluecker(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4Triangle4vMBIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4vMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4vMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4Triangle4vMBIntersector8HybridMoeller(); + intersectors.intersector16 = BVH4Triangle4vMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4vMBIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4vMBIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Triangle4vMBIntersector8HybridPluecker(); + intersectors.intersector16 = BVH4Triangle4vMBIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4Triangle4iMBIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4iMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4iMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4Triangle4iMBIntersector8HybridMoeller(); + intersectors.intersector16 = BVH4Triangle4iMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Triangle4iMBIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Triangle4iMBIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Triangle4iMBIntersector8HybridPluecker(); + intersectors.intersector16 = BVH4Triangle4iMBIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4Quad4vIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Quad4vIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4_filter = BVH4Quad4vIntersector4HybridMoeller(); + intersectors.intersector4_nofilter = BVH4Quad4vIntersector4HybridMoellerNoFilter(); + intersectors.intersector8_filter = BVH4Quad4vIntersector8HybridMoeller(); + intersectors.intersector8_nofilter = BVH4Quad4vIntersector8HybridMoellerNoFilter(); + intersectors.intersector16_filter = BVH4Quad4vIntersector16HybridMoeller(); + intersectors.intersector16_nofilter = BVH4Quad4vIntersector16HybridMoellerNoFilter(); + intersectors.intersectorN_filter = BVH4Quad4vIntersectorStreamMoeller(); + intersectors.intersectorN_nofilter = BVH4Quad4vIntersectorStreamMoellerNoFilter(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Quad4vIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Quad4vIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Quad4vIntersector8HybridPluecker(); + intersectors.intersector16 = BVH4Quad4vIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4Quad4vIntersectorStreamPluecker(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4Quad4iIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Quad4iIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Quad4iIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4Quad4iIntersector8HybridMoeller(); + intersectors.intersector16= BVH4Quad4iIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4Quad4iIntersectorStreamMoeller(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Quad4iIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Quad4iIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Quad4iIntersector8HybridPluecker(); + intersectors.intersector16= BVH4Quad4iIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4Quad4iIntersectorStreamPluecker(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::BVH4Quad4iMBIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Quad4iMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Quad4iMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4Quad4iMBIntersector8HybridMoeller(); + intersectors.intersector16= BVH4Quad4iMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4Quad4iMBIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4Quad4iMBIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4Quad4iMBIntersector8HybridPluecker(); + intersectors.intersector16= BVH4Quad4iMBIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH4Factory::QBVH4Triangle4iIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = QBVH4Triangle4iIntersector1Pluecker(); + return intersectors; + } + + Accel::Intersectors BVH4Factory::QBVH4Quad4iIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = QBVH4Quad4iIntersector1Pluecker(); + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4UserGeometryIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4VirtualIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4VirtualIntersector4Chunk(); + intersectors.intersector8 = BVH4VirtualIntersector8Chunk(); + intersectors.intersector16 = BVH4VirtualIntersector16Chunk(); + intersectors.intersectorN = BVH4VirtualIntersectorStream(); +#endif + intersectors.collider = BVH4ColliderUserGeom(); + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4UserGeometryMBIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4VirtualMBIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4VirtualMBIntersector4Chunk(); + intersectors.intersector8 = BVH4VirtualMBIntersector8Chunk(); + intersectors.intersector16 = BVH4VirtualMBIntersector16Chunk(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4InstanceIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4InstanceIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4InstanceIntersector4Chunk(); + intersectors.intersector8 = BVH4InstanceIntersector8Chunk(); + intersectors.intersector16 = BVH4InstanceIntersector16Chunk(); + intersectors.intersectorN = BVH4InstanceIntersectorStream(); +#endif + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4InstanceMBIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4InstanceMBIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4InstanceMBIntersector4Chunk(); + intersectors.intersector8 = BVH4InstanceMBIntersector8Chunk(); + intersectors.intersector16 = BVH4InstanceMBIntersector16Chunk(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4SubdivPatch1Intersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4SubdivPatch1Intersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4SubdivPatch1Intersector4(); + intersectors.intersector8 = BVH4SubdivPatch1Intersector8(); + intersectors.intersector16 = BVH4SubdivPatch1Intersector16(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4SubdivPatch1MBIntersectors(BVH4* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4SubdivPatch1MBIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4SubdivPatch1MBIntersector4(); + intersectors.intersector8 = BVH4SubdivPatch1MBIntersector8(); + intersectors.intersector16 = BVH4SubdivPatch1MBIntersector16(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel* BVH4Factory::BVH4OBBVirtualCurve4i(Scene* scene, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Curve4i::type,scene); + Accel::Intersectors intersectors = BVH4OBBVirtualCurveIntersectors(accel,VirtualCurveIntersector4i(),ivariant); + + Builder* builder = nullptr; + if (scene->device->hair_builder == "default" ) builder = BVH4Curve4iBuilder_OBB_New(accel,scene,0); + else if (scene->device->hair_builder == "sah" ) builder = BVH4Curve4iBuilder_OBB_New(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->hair_builder+" for BVH4OBB"); + + return new AccelInstance(accel,builder,intersectors); + } + +#if defined(EMBREE_TARGET_SIMD8) + Accel* BVH4Factory::BVH4OBBVirtualCurve8i(Scene* scene, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Curve8i::type,scene); + Accel::Intersectors intersectors = BVH4OBBVirtualCurveIntersectors(accel,VirtualCurveIntersector8i(),ivariant); + + Builder* builder = nullptr; + if (scene->device->hair_builder == "default" ) builder = BVH4Curve8iBuilder_OBB_New(accel,scene,0); + else if (scene->device->hair_builder == "sah" ) builder = BVH4Curve8iBuilder_OBB_New(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->hair_builder+" for BVH4OBB"); + + return new AccelInstance(accel,builder,intersectors); + } +#endif + + Accel* BVH4Factory::BVH4OBBVirtualCurve4v(Scene* scene, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Curve4v::type,scene); + Accel::Intersectors intersectors = BVH4OBBVirtualCurveIntersectors(accel,VirtualCurveIntersector4v(),ivariant); + + Builder* builder = nullptr; + if (scene->device->hair_builder == "default" ) builder = BVH4Curve4vBuilder_OBB_New(accel,scene,0); + else if (scene->device->hair_builder == "sah" ) builder = BVH4Curve4vBuilder_OBB_New(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->hair_builder+" for BVH4OBB"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4OBBVirtualCurve4iMB(Scene* scene, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Curve4iMB::type,scene); + Accel::Intersectors intersectors = BVH4OBBVirtualCurveIntersectorsMB(accel,VirtualCurveIntersector4iMB(),ivariant); + + Builder* builder = nullptr; + if (scene->device->hair_builder == "default" ) builder = BVH4OBBCurve4iMBBuilder_OBB(accel,scene,0); + else if (scene->device->hair_builder == "sah" ) builder = BVH4OBBCurve4iMBBuilder_OBB(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->hair_builder+" for BVH4OBB"); + + return new AccelInstance(accel,builder,intersectors); + } + +#if defined(EMBREE_TARGET_SIMD8) + Accel* BVH4Factory::BVH4OBBVirtualCurve8iMB(Scene* scene, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Curve8iMB::type,scene); + Accel::Intersectors intersectors = BVH4OBBVirtualCurveIntersectorsMB(accel,VirtualCurveIntersector8iMB(), ivariant); + + Builder* builder = nullptr; + if (scene->device->hair_builder == "default" ) builder = BVH4OBBCurve8iMBBuilder_OBB(accel,scene,0); + else if (scene->device->hair_builder == "sah" ) builder = BVH4OBBCurve8iMBBuilder_OBB(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->hair_builder+" for BVH4OBB"); + + return new AccelInstance(accel,builder,intersectors); + } +#endif + + Accel* BVH4Factory::BVH4Triangle4(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Triangle4::type,scene); + + Accel::Intersectors intersectors; + if (scene->device->tri_traverser == "default") intersectors = BVH4Triangle4Intersectors(accel,ivariant); + else if (scene->device->tri_traverser == "fast" ) intersectors = BVH4Triangle4Intersectors(accel,IntersectVariant::FAST); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown traverser "+scene->device->tri_traverser+" for BVH4"); + + Builder* builder = nullptr; + if (scene->device->tri_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Triangle4SceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH4BuilderTwoLevelTriangle4MeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH4Triangle4SceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->tri_builder == "sah" ) builder = BVH4Triangle4SceneBuilderSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_fast_spatial" ) builder = BVH4Triangle4SceneBuilderFastSpatialSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_presplit") builder = BVH4Triangle4SceneBuilderSAH(accel,scene,MODE_HIGH_QUALITY); + else if (scene->device->tri_builder == "dynamic" ) builder = BVH4BuilderTwoLevelTriangle4MeshSAH(accel,scene,false); + else if (scene->device->tri_builder == "morton" ) builder = BVH4BuilderTwoLevelTriangle4MeshSAH(accel,scene,true); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Triangle4v(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Triangle4v::type,scene); + + Accel::Intersectors intersectors; + if (scene->device->tri_traverser == "default") intersectors = BVH4Triangle4vIntersectors(accel,ivariant); + else if (scene->device->tri_traverser == "fast" ) intersectors = BVH4Triangle4vIntersectors(accel,IntersectVariant::FAST); + else if (scene->device->tri_traverser == "robust" ) intersectors = BVH4Triangle4vIntersectors(accel,IntersectVariant::ROBUST); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown traverser "+scene->device->tri_traverser+" for BVH4"); + + Builder* builder = nullptr; + if (scene->device->tri_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Triangle4vSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH4BuilderTwoLevelTriangle4vMeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH4Triangle4vSceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->tri_builder == "sah" ) builder = BVH4Triangle4vSceneBuilderSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_fast_spatial" ) builder = BVH4Triangle4vSceneBuilderFastSpatialSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_presplit") builder = BVH4Triangle4vSceneBuilderSAH(accel,scene,MODE_HIGH_QUALITY); + else if (scene->device->tri_builder == "dynamic" ) builder = BVH4BuilderTwoLevelTriangle4vMeshSAH(accel,scene,false); + else if (scene->device->tri_builder == "morton" ) builder = BVH4BuilderTwoLevelTriangle4vMeshSAH(accel,scene,true); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Triangle4i(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Triangle4i::type,scene); + + Accel::Intersectors intersectors; + if (scene->device->tri_traverser == "default") intersectors = BVH4Triangle4iIntersectors(accel,ivariant); + else if (scene->device->tri_traverser == "fast" ) intersectors = BVH4Triangle4iIntersectors(accel,IntersectVariant::FAST); + else if (scene->device->tri_traverser == "robust" ) intersectors = BVH4Triangle4iIntersectors(accel,IntersectVariant::ROBUST); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown traverser "+scene->device->tri_traverser+" for BVH4"); + + Builder* builder = nullptr; + if (scene->device->tri_builder == "default" ) { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Triangle4iSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH4BuilderTwoLevelTriangle4iMeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH4Triangle4iSceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->tri_builder == "sah" ) builder = BVH4Triangle4iSceneBuilderSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_fast_spatial" ) builder = BVH4Triangle4iSceneBuilderFastSpatialSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_presplit") builder = BVH4Triangle4iSceneBuilderSAH(accel,scene,MODE_HIGH_QUALITY); + else if (scene->device->tri_builder == "dynamic" ) builder = BVH4BuilderTwoLevelTriangle4iMeshSAH(accel,scene,false); + else if (scene->device->tri_builder == "morton" ) builder = BVH4BuilderTwoLevelTriangle4iMeshSAH(accel,scene,true); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Triangle4iMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Triangle4i::type,scene); + + Accel::Intersectors intersectors; + if (scene->device->tri_traverser_mb == "default") intersectors = BVH4Triangle4iMBIntersectors(accel,ivariant); + else if (scene->device->tri_traverser_mb == "fast" ) intersectors = BVH4Triangle4iMBIntersectors(accel,IntersectVariant::FAST); + else if (scene->device->tri_traverser_mb == "robust" ) intersectors = BVH4Triangle4iMBIntersectors(accel,IntersectVariant::ROBUST); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown traverser "+scene->device->tri_traverser_mb+" for BVH4"); + + Builder* builder = nullptr; + if (scene->device->tri_builder_mb == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Triangle4iMBSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->tri_builder_mb == "internal_time_splits") builder = BVH4Triangle4iMBSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder_mb+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Triangle4vMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Triangle4vMB::type,scene); + + Accel::Intersectors intersectors; + if (scene->device->tri_traverser_mb == "default") intersectors = BVH4Triangle4vMBIntersectors(accel,ivariant); + else if (scene->device->tri_traverser_mb == "fast" ) intersectors = BVH4Triangle4vMBIntersectors(accel,IntersectVariant::FAST); + else if (scene->device->tri_traverser_mb == "robust" ) intersectors = BVH4Triangle4vMBIntersectors(accel,IntersectVariant::ROBUST); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown traverser "+scene->device->tri_traverser_mb+" for BVH4"); + + Builder* builder = nullptr; + if (scene->device->tri_builder_mb == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Triangle4vMBSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->tri_builder_mb == "internal_time_splits") builder = BVH4Triangle4vMBSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder_mb+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Quad4v(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Quad4v::type,scene); + Accel::Intersectors intersectors = BVH4Quad4vIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->quad_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Quad4vSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH4BuilderTwoLevelQuadMeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH4Quad4vSceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->quad_builder == "sah" ) builder = BVH4Quad4vSceneBuilderSAH(accel,scene,0); + else if (scene->device->quad_builder == "sah_fast_spatial" ) builder = BVH4Quad4vSceneBuilderFastSpatialSAH(accel,scene,0); + else if (scene->device->quad_builder == "dynamic" ) builder = BVH4BuilderTwoLevelQuadMeshSAH(accel,scene,false); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Quad4i(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Quad4i::type,scene); + Accel::Intersectors intersectors = BVH4Quad4iIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->quad_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Quad4iSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; // FIXME: implement + } + } + else if (scene->device->quad_builder == "sah") builder = BVH4Quad4iSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Quad4iMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(Quad4i::type,scene); + Accel::Intersectors intersectors = BVH4Quad4iMBIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->quad_builder_mb == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4Quad4iMBSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->quad_builder_mb == "sah") builder = BVH4Quad4iMBSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder_mb+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4QuantizedQuad4i(Scene* scene) + { + BVH4* accel = new BVH4(Quad4i::type,scene); + Builder* builder = BVH4QuantizedQuad4iSceneBuilderSAH(accel,scene,0); + Accel::Intersectors intersectors = QBVH4Quad4iIntersectors(accel); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4QuantizedTriangle4i(Scene* scene) + { + BVH4* accel = new BVH4(Triangle4i::type,scene); + Builder* builder = BVH4QuantizedTriangle4iSceneBuilderSAH(accel,scene,0); + Accel::Intersectors intersectors = QBVH4Triangle4iIntersectors(accel); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4SubdivPatch1(Scene* scene) + { + BVH4* accel = new BVH4(SubdivPatch1::type,scene); + Accel::Intersectors intersectors = BVH4SubdivPatch1Intersectors(accel); + Builder* builder = BVH4SubdivPatch1BuilderSAH(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4SubdivPatch1MB(Scene* scene) + { + BVH4* accel = new BVH4(SubdivPatch1::type,scene); + Accel::Intersectors intersectors = BVH4SubdivPatch1MBIntersectors(accel); + Builder* builder = BVH4SubdivPatch1MBBuilderSAH(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4UserGeometry(Scene* scene, BuildVariant bvariant) + { + BVH4* accel = new BVH4(Object::type,scene); + Accel::Intersectors intersectors = BVH4UserGeometryIntersectors(accel); + + Builder* builder = nullptr; + if (scene->device->object_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4VirtualSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH4BuilderTwoLevelVirtualSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->object_builder == "sah") builder = BVH4VirtualSceneBuilderSAH(accel,scene,0); + else if (scene->device->object_builder == "dynamic") builder = BVH4BuilderTwoLevelVirtualSAH(accel,scene,false); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->object_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4UserGeometryMB(Scene* scene) + { + BVH4* accel = new BVH4(Object::type,scene); + Accel::Intersectors intersectors = BVH4UserGeometryMBIntersectors(accel); + Builder* builder = BVH4VirtualMBSceneBuilderSAH(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4Instance(Scene* scene, bool isExpensive, BuildVariant bvariant) + { + BVH4* accel = new BVH4(InstancePrimitive::type,scene); + Accel::Intersectors intersectors = BVH4InstanceIntersectors(accel); + auto gtype = isExpensive ? Geometry::MTY_INSTANCE_EXPENSIVE : Geometry::MTY_INSTANCE_CHEAP; + // Builder* builder = BVH4InstanceSceneBuilderSAH(accel,scene,gtype); + + Builder* builder = nullptr; + if (scene->device->object_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH4InstanceSceneBuilderSAH(accel,scene,gtype); break; + case BuildVariant::DYNAMIC : builder = BVH4BuilderTwoLevelInstanceSAH(accel,scene,gtype,false); break; + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->object_builder == "sah") builder = BVH4InstanceSceneBuilderSAH(accel,scene,gtype); + else if (scene->device->object_builder == "dynamic") builder = BVH4BuilderTwoLevelInstanceSAH(accel,scene,gtype,false); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->object_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4InstanceMB(Scene* scene, bool isExpensive) + { + BVH4* accel = new BVH4(InstancePrimitive::type,scene); + Accel::Intersectors intersectors = BVH4InstanceMBIntersectors(accel); + auto gtype = isExpensive ? Geometry::MTY_INSTANCE_EXPENSIVE : Geometry::MTY_INSTANCE_CHEAP; + Builder* builder = BVH4InstanceMBSceneBuilderSAH(accel,scene,gtype); + return new AccelInstance(accel,builder,intersectors); + } + + Accel::Intersectors BVH4Factory::BVH4GridIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + if (ivariant == IntersectVariant::FAST) + { + intersectors.intersector1 = BVH4GridIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4GridIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4GridIntersector8HybridMoeller(); + intersectors.intersector16 = BVH4GridIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + } + else /* if (ivariant == IntersectVariant::ROBUST) */ + { + intersectors.intersector1 = BVH4GridIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4GridIntersector4HybridPluecker(); + intersectors.intersector8 = BVH4GridIntersector8HybridPluecker(); + intersectors.intersector16 = BVH4GridIntersector16HybridPluecker(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + } + return intersectors; + } + + Accel::Intersectors BVH4Factory::BVH4GridMBIntersectors(BVH4* bvh, IntersectVariant ivariant) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH4GridMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH4GridMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH4GridMBIntersector8HybridMoeller(); + intersectors.intersector16 = BVH4GridMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH4IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel* BVH4Factory::BVH4Grid(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(SubGridQBVH4::type,scene); + Accel::Intersectors intersectors = BVH4GridIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->object_builder == "default") { + builder = BVH4GridSceneBuilderSAH(accel,scene,0); + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->grid_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH4Factory::BVH4GridMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH4* accel = new BVH4(SubGridQBVH4::type,scene); + Accel::Intersectors intersectors = BVH4GridMBIntersectors(accel,ivariant); + Builder* builder = nullptr; + if (scene->device->object_builder == "default") { + builder = BVH4GridMBSceneBuilderSAH(accel,scene,0); + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->grid_builder+" for BVH4MB"); + return new AccelInstance(accel,builder,intersectors); + } + +} diff --git a/thirdparty/embree/kernels/bvh/bvh4_factory.h b/thirdparty/embree/kernels/bvh/bvh4_factory.h new file mode 100644 index 000000000000..a68227b41f80 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh4_factory.h @@ -0,0 +1,316 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_factory.h" + +namespace embree +{ + /*! BVH4 instantiations */ + class BVH4Factory : public BVHFactory + { + public: + BVH4Factory(int bfeatures, int ifeatures); + + public: + Accel* BVH4OBBVirtualCurve4i(Scene* scene, IntersectVariant ivariant); + Accel* BVH4OBBVirtualCurve4v(Scene* scene, IntersectVariant ivariant); + Accel* BVH4OBBVirtualCurve8i(Scene* scene, IntersectVariant ivariant); + Accel* BVH4OBBVirtualCurve4iMB(Scene* scene, IntersectVariant ivariant); + Accel* BVH4OBBVirtualCurve8iMB(Scene* scene, IntersectVariant ivariant); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector4i); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector8i); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector4v); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector8v); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector4iMB); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector8iMB); + + Accel* BVH4Triangle4 (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH4Triangle4v (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::ROBUST); + Accel* BVH4Triangle4i (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH4Triangle4vMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH4Triangle4iMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + + Accel* BVH4Quad4v (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH4Quad4i (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH4Quad4iMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + + Accel* BVH4QuantizedTriangle4i(Scene* scene); + Accel* BVH4QuantizedQuad4i(Scene* scene); + + Accel* BVH4SubdivPatch1(Scene* scene); + Accel* BVH4SubdivPatch1MB(Scene* scene); + + Accel* BVH4UserGeometry(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC); + Accel* BVH4UserGeometryMB(Scene* scene); + + Accel* BVH4Instance(Scene* scene, bool isExpensive, BuildVariant bvariant = BuildVariant::STATIC); + Accel* BVH4InstanceMB(Scene* scene, bool isExpensive); + + Accel* BVH4Grid(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH4GridMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + + private: + void selectBuilders(int features); + void selectIntersectors(int features); + + private: + Accel::Intersectors BVH4OBBVirtualCurveIntersectors(BVH4* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant); + Accel::Intersectors BVH4OBBVirtualCurveIntersectorsMB(BVH4* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant); + + Accel::Intersectors BVH4Triangle4Intersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4Triangle4vIntersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4Triangle4iIntersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4Triangle4iMBIntersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4Triangle4vMBIntersectors(BVH4* bvh, IntersectVariant ivariant); + + Accel::Intersectors BVH4Quad4vIntersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4Quad4iIntersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4Quad4iMBIntersectors(BVH4* bvh, IntersectVariant ivariant); + + Accel::Intersectors QBVH4Quad4iIntersectors(BVH4* bvh); + Accel::Intersectors QBVH4Triangle4iIntersectors(BVH4* bvh); + + Accel::Intersectors BVH4UserGeometryIntersectors(BVH4* bvh); + Accel::Intersectors BVH4UserGeometryMBIntersectors(BVH4* bvh); + + Accel::Intersectors BVH4InstanceIntersectors(BVH4* bvh); + Accel::Intersectors BVH4InstanceMBIntersectors(BVH4* bvh); + + Accel::Intersectors BVH4SubdivPatch1Intersectors(BVH4* bvh); + Accel::Intersectors BVH4SubdivPatch1MBIntersectors(BVH4* bvh); + + Accel::Intersectors BVH4GridIntersectors(BVH4* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH4GridMBIntersectors(BVH4* bvh, IntersectVariant ivariant); + + private: + + DEFINE_SYMBOL2(Accel::Collider,BVH4ColliderUserGeom); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersector1MB); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersectorRobust1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4OBBVirtualCurveIntersectorRobust1MB); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4Intersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4vIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4vMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4vMBIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Triangle4iMBIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Quad4vIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Quad4iIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Quad4vIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Quad4iIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Quad4iMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4Quad4iMBIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,QBVH4Triangle4iIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,QBVH4Quad4iIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4SubdivPatch1Intersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4SubdivPatch1MBIntersector1); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4VirtualIntersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4VirtualMBIntersector1); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4InstanceIntersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4InstanceMBIntersector1); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH4GridIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4GridMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH4GridIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersector4Hybrid); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersector4HybridMB); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersectorRobust4Hybrid); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4OBBVirtualCurveIntersectorRobust4HybridMB); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4Intersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4Intersector4HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4vIntersector4HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4vMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4vMBIntersector4HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Triangle4iMBIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4vIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4vIntersector4HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4iIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4vIntersector4HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4iIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4iMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4Quad4iMBIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4SubdivPatch1Intersector4); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4SubdivPatch1MBIntersector4); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4VirtualIntersector4Chunk); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4VirtualMBIntersector4Chunk); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4InstanceIntersector4Chunk); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4InstanceMBIntersector4Chunk); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH4GridIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4GridMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH4GridIntersector4HybridPluecker); + + // ============== + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersector8Hybrid); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersector8HybridMB); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersectorRobust8Hybrid); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4OBBVirtualCurveIntersectorRobust8HybridMB); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4Intersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4Intersector8HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4vIntersector8HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4vMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4vMBIntersector8HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Triangle4iMBIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4vIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4vIntersector8HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4iIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4vIntersector8HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4iIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4iMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4Quad4iMBIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4SubdivPatch1Intersector8); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4SubdivPatch1MBIntersector8); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4VirtualIntersector8Chunk); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4VirtualMBIntersector8Chunk); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4InstanceIntersector8Chunk); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4InstanceMBIntersector8Chunk); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH4GridIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4GridMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH4GridIntersector8HybridPluecker); + + // ============== + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersector16Hybrid); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersector16HybridMB); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersectorRobust16Hybrid); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4OBBVirtualCurveIntersectorRobust16HybridMB); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4Intersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4Intersector16HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4vIntersector16HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4vMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4vMBIntersector16HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Triangle4iMBIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4vIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4vIntersector16HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4iIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4vIntersector16HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4iIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4iMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4Quad4iMBIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4SubdivPatch1Intersector16); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4SubdivPatch1MBIntersector16); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4VirtualIntersector16Chunk); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4VirtualMBIntersector16Chunk); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4InstanceIntersector16Chunk); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4InstanceMBIntersector16Chunk); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH4GridIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4GridMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH4GridIntersector16HybridPluecker); + + // ============== + + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4IntersectorStreamPacketFallback); + + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Triangle4IntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Triangle4IntersectorStreamMoellerNoFilter); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Triangle4iIntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Triangle4vIntersectorStreamPluecker); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Triangle4iIntersectorStreamPluecker); + + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Quad4vIntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Quad4vIntersectorStreamMoellerNoFilter); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Quad4iIntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Quad4vIntersectorStreamPluecker); + DEFINE_SYMBOL2(Accel::IntersectorN, BVH4Quad4iIntersectorStreamPluecker); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH4VirtualIntersectorStream); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH4InstanceIntersectorStream); + + // SAH scene builders + private: + DEFINE_ISA_FUNCTION(Builder*,BVH4Curve4vBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Curve4iBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4OBBCurve4iMBBuilder_OBB,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Curve8iBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4OBBCurve8iMBBuilder_OBB,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4SceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4vMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4QuantizedTriangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH4Quad4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Quad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Quad4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4QuantizedQuad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH4SubdivPatch1BuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4SubdivPatch1MBBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH4VirtualSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4VirtualMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH4InstanceSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + DEFINE_ISA_FUNCTION(Builder*,BVH4InstanceMBSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + + DEFINE_ISA_FUNCTION(Builder*,BVH4GridSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4GridMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + // spatial scene builder + private: + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4SceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Triangle4iSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH4Quad4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + + // twolevel scene builders + private: + DEFINE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelTriangle4MeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelTriangle4vMeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelTriangle4iMeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelQuadMeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelVirtualSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH4BuilderTwoLevelInstanceSAH,void* COMMA Scene* COMMA Geometry::GTypeMask COMMA bool); + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh8_factory.cpp b/thirdparty/embree/kernels/bvh/bvh8_factory.cpp new file mode 100644 index 000000000000..9fe057c39231 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh8_factory.cpp @@ -0,0 +1,1165 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../common/isa.h" // to define EMBREE_TARGET_SIMD8 + +#if defined (EMBREE_TARGET_SIMD8) + +#include "bvh8_factory.h" +#include "../bvh/bvh.h" + +#include "../geometry/curveNv.h" +#include "../geometry/curveNi.h" +#include "../geometry/curveNi_mb.h" +#include "../geometry/linei.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglev_mb.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/subdivpatch1.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" +#include "../geometry/subgrid.h" +#include "../common/accelinstance.h" + +namespace embree +{ + DECLARE_SYMBOL2(Accel::Collider,BVH8ColliderUserGeom); + + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector8v,void); + DECLARE_ISA_FUNCTION(VirtualCurveIntersector*,VirtualCurveIntersector8iMB,void); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersector1MB); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersectorRobust1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersectorRobust1MB); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4Intersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vIntersector1Woop); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vMBIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iMBIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Quad4vIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Quad4iIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Quad4vIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Quad4iIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Quad4iMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8Quad4iMBIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,QBVH8Triangle4iIntersector1Pluecker); + DECLARE_SYMBOL2(Accel::Intersector1,QBVH8Triangle4Intersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,QBVH8Quad4iIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8VirtualIntersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8VirtualMBIntersector1); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8InstanceIntersector1); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8InstanceMBIntersector1); + + DECLARE_SYMBOL2(Accel::Intersector1,BVH8GridIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8GridMBIntersector1Moeller); + DECLARE_SYMBOL2(Accel::Intersector1,BVH8GridIntersector1Pluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersector4Hybrid); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersector4HybridMB); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersectorRobust4Hybrid); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersectorRobust4HybridMB); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4Intersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4Intersector4HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4vIntersector4HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4vMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4vMBIntersector4HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iMBIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4vIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4vIntersector4HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4iIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4vIntersector4HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4iIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4iMBIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8Quad4iMBIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8VirtualIntersector4Chunk); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8VirtualMBIntersector4Chunk); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8InstanceIntersector4Chunk); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8InstanceMBIntersector4Chunk); + + DECLARE_SYMBOL2(Accel::Intersector4,BVH8GridIntersector4HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector4,BVH8GridIntersector4HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersector8Hybrid); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersector8HybridMB); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersectorRobust8Hybrid); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersectorRobust8HybridMB); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4Intersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4Intersector8HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4vIntersector8HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4vMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4vMBIntersector8HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iMBIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4vIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4vIntersector8HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4iIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4vIntersector8HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4iIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4iMBIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8Quad4iMBIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8VirtualIntersector8Chunk); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8VirtualMBIntersector8Chunk); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8InstanceIntersector8Chunk); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8InstanceMBIntersector8Chunk); + + DECLARE_SYMBOL2(Accel::Intersector8,BVH8GridIntersector8HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector8,BVH8GridIntersector8HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersector16Hybrid); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersector16HybridMB); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersectorRobust16Hybrid); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersectorRobust16HybridMB); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4Intersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4Intersector16HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4vIntersector16HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4vMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4vMBIntersector16HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iMBIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4vIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4vIntersector16HybridMoellerNoFilter); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4iIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4vIntersector16HybridPluecker); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4iIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4iMBIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8Quad4iMBIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8VirtualIntersector16Chunk); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8VirtualMBIntersector16Chunk); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8InstanceIntersector16Chunk); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8InstanceMBIntersector16Chunk); + + DECLARE_SYMBOL2(Accel::Intersector16,BVH8GridIntersector16HybridMoeller); + DECLARE_SYMBOL2(Accel::Intersector16,BVH8GridIntersector16HybridPluecker); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8IntersectorStreamPacketFallback); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4IntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4IntersectorStreamMoellerNoFilter); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4iIntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4vIntersectorStreamPluecker); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4iIntersectorStreamPluecker); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Quad4vIntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Quad4vIntersectorStreamMoellerNoFilter); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Quad4iIntersectorStreamMoeller); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Quad4vIntersectorStreamPluecker); + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8Quad4iIntersectorStreamPluecker); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8VirtualIntersectorStream); + + DECLARE_SYMBOL2(Accel::IntersectorN,BVH8InstanceIntersectorStream); + + DECLARE_ISA_FUNCTION(Builder*,BVH8Curve8vBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8OBBCurve8iMBBuilder_OBB,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4SceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4vMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8QuantizedTriangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8QuantizedTriangle4SceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8QuantizedQuad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH8VirtualSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8VirtualMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH8InstanceSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + DECLARE_ISA_FUNCTION(Builder*,BVH8InstanceMBSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4SceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8GridSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8GridMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DECLARE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelTriangle4MeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelTriangle4vMeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelTriangle4iMeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelQuadMeshSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelVirtualSAH,void* COMMA Scene* COMMA bool); + DECLARE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelInstanceSAH,void* COMMA Scene* COMMA Geometry::GTypeMask COMMA bool); + + BVH8Factory::BVH8Factory(int bfeatures, int ifeatures) + { + SELECT_SYMBOL_INIT_AVX(ifeatures,BVH8ColliderUserGeom); + + selectBuilders(bfeatures); + selectIntersectors(ifeatures); + } + + void BVH8Factory::selectBuilders(int features) + { + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX(features,BVH8Curve8vBuilder_OBB_New)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX(features,BVH8OBBCurve8iMBBuilder_OBB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4SceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4vSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4iSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4iMBSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4vMBSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX(features,BVH8QuantizedTriangle4iSceneBuilderSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX(features,BVH8QuantizedTriangle4SceneBuilderSAH)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Quad4vSceneBuilderSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Quad4iSceneBuilderSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Quad4iMBSceneBuilderSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX(features,BVH8QuantizedQuad4iSceneBuilderSAH)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX(features,BVH8VirtualSceneBuilderSAH)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX(features,BVH8VirtualMBSceneBuilderSAH)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX(features,BVH8InstanceSceneBuilderSAH)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX(features,BVH8InstanceMBSceneBuilderSAH)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX(features,BVH8GridSceneBuilderSAH)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX(features,BVH8GridMBSceneBuilderSAH)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4SceneBuilderFastSpatialSAH)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Triangle4vSceneBuilderFastSpatialSAH)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8Quad4vSceneBuilderFastSpatialSAH)); + + IF_ENABLED_TRIS (SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8BuilderTwoLevelTriangle4MeshSAH)); + IF_ENABLED_TRIS (SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8BuilderTwoLevelTriangle4vMeshSAH)); + IF_ENABLED_TRIS (SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8BuilderTwoLevelTriangle4iMeshSAH)); + IF_ENABLED_QUADS (SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8BuilderTwoLevelQuadMeshSAH)); + IF_ENABLED_USER (SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8BuilderTwoLevelVirtualSAH)); + IF_ENABLED_INSTANCE (SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,BVH8BuilderTwoLevelInstanceSAH)); + } + + void BVH8Factory::selectIntersectors(int features) + { + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector8v)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,VirtualCurveIntersector8iMB)); + + /* select intersectors1 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersector1)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersector1MB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust1)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust1MB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4Intersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4iIntersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4vIntersector1Pluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4iIntersector1Pluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4vIntersector1Woop)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4vMBIntersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4iMBIntersector1Moeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4vMBIntersector1Pluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4iMBIntersector1Pluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersector1Moeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4iIntersector1Moeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersector1Pluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4iIntersector1Pluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4iMBIntersector1Moeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4iMBIntersector1Pluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,QBVH8Triangle4iIntersector1Pluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,QBVH8Triangle4Intersector1Moeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,QBVH8Quad4iIntersector1Pluecker)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8VirtualIntersector1)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8VirtualMBIntersector1)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8InstanceIntersector1)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8InstanceMBIntersector1)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridIntersector1Moeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridMBIntersector1Moeller)) + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridIntersector1Pluecker)); + +#if defined (EMBREE_RAY_PACKETS) + + /* select intersectors4 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersector4Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersector4HybridMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust4Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust4HybridMB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4Intersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4Intersector4HybridMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iIntersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4vIntersector4HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iIntersector4HybridPluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4vMBIntersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iMBIntersector4HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4vMBIntersector4HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iMBIntersector4HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4vIntersector4HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4vIntersector4HybridMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4iIntersector4HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4vIntersector4HybridPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4iIntersector4HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2(features,BVH8Quad4iMBIntersector4HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2(features,BVH8Quad4iMBIntersector4HybridPluecker)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8VirtualIntersector4Chunk)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8VirtualMBIntersector4Chunk)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8InstanceIntersector4Chunk)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8InstanceMBIntersector4Chunk)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridIntersector4HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridIntersector4HybridPluecker)); + + /* select intersectors8 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersector8Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersector8HybridMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust8Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust8HybridMB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4Intersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4Intersector8HybridMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iIntersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4vIntersector8HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iIntersector8HybridPluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4vMBIntersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iMBIntersector8HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4vMBIntersector8HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Triangle4iMBIntersector8HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4vIntersector8HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4vIntersector8HybridMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4iIntersector8HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4vIntersector8HybridPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8Quad4iIntersector8HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2(features,BVH8Quad4iMBIntersector8HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2(features,BVH8Quad4iMBIntersector8HybridPluecker)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8VirtualIntersector8Chunk)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8VirtualMBIntersector8Chunk)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8InstanceIntersector8Chunk)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8InstanceMBIntersector8Chunk)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridIntersector8HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,BVH8GridIntersector8HybridPluecker)); + + /* select intersectors16 */ + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersector16Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersector16HybridMB)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust16Hybrid)); + IF_ENABLED_CURVES_OR_POINTS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8OBBVirtualCurveIntersectorRobust16HybridMB)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4Intersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4Intersector16HybridMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4iIntersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4vIntersector16HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4iIntersector16HybridPluecker)); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4vMBIntersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4iMBIntersector16HybridMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4vMBIntersector16HybridPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Triangle4iMBIntersector16HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersector16HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersector16HybridMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4iIntersector16HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersector16HybridPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4iIntersector16HybridPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4iMBIntersector16HybridMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8Quad4iMBIntersector16HybridPluecker)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8VirtualIntersector16Chunk)); + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8VirtualMBIntersector16Chunk)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8InstanceIntersector16Chunk)); + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8InstanceMBIntersector16Chunk)); + + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8GridIntersector16HybridMoeller)); + IF_ENABLED_GRIDS(SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,BVH8GridIntersector16HybridPluecker)); + + /* select stream intersectors */ + + SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8IntersectorStreamPacketFallback); + + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4IntersectorStreamMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4IntersectorStreamMoellerNoFilter)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4iIntersectorStreamMoeller)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4vIntersectorStreamPluecker)); + IF_ENABLED_TRIS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Triangle4iIntersectorStreamPluecker)); + + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersectorStreamMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersectorStreamMoellerNoFilter)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4iIntersectorStreamMoeller)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4vIntersectorStreamPluecker)); + IF_ENABLED_QUADS(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8Quad4iIntersectorStreamPluecker)); + + IF_ENABLED_USER(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8VirtualIntersectorStream)); + + IF_ENABLED_INSTANCE(SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,BVH8InstanceIntersectorStream)); + +#endif + } + + Accel::Intersectors BVH8Factory::BVH8OBBVirtualCurveIntersectors(BVH8* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH8OBBVirtualCurveIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8OBBVirtualCurveIntersector4Hybrid(); + intersectors.intersector8 = BVH8OBBVirtualCurveIntersector8Hybrid(); + intersectors.intersector16 = BVH8OBBVirtualCurveIntersector16Hybrid(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH8OBBVirtualCurveIntersectorRobust1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8OBBVirtualCurveIntersectorRobust4Hybrid(); + intersectors.intersector8 = BVH8OBBVirtualCurveIntersectorRobust8Hybrid(); + intersectors.intersector16 = BVH8OBBVirtualCurveIntersectorRobust16Hybrid(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + default: assert(false); + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8OBBVirtualCurveIntersectorsMB(BVH8* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH8OBBVirtualCurveIntersector1MB(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8OBBVirtualCurveIntersector4HybridMB(); + intersectors.intersector8 = BVH8OBBVirtualCurveIntersector8HybridMB(); + intersectors.intersector16 = BVH8OBBVirtualCurveIntersector16HybridMB(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.leafIntersector = leafIntersector; + intersectors.intersector1 = BVH8OBBVirtualCurveIntersectorRobust1MB(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8OBBVirtualCurveIntersectorRobust4HybridMB(); + intersectors.intersector8 = BVH8OBBVirtualCurveIntersectorRobust8HybridMB(); + intersectors.intersector16 = BVH8OBBVirtualCurveIntersectorRobust16HybridMB(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + default: assert(false); + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8Triangle4Intersectors(BVH8* bvh, IntersectVariant ivariant) + { + assert(ivariant == IntersectVariant::FAST); + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4Intersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4_filter = BVH8Triangle4Intersector4HybridMoeller(); + intersectors.intersector4_nofilter = BVH8Triangle4Intersector4HybridMoellerNoFilter(); + intersectors.intersector8_filter = BVH8Triangle4Intersector8HybridMoeller(); + intersectors.intersector8_nofilter = BVH8Triangle4Intersector8HybridMoellerNoFilter(); + intersectors.intersector16_filter = BVH8Triangle4Intersector16HybridMoeller(); + intersectors.intersector16_nofilter = BVH8Triangle4Intersector16HybridMoellerNoFilter(); + intersectors.intersectorN_filter = BVH8Triangle4IntersectorStreamMoeller(); + intersectors.intersectorN_nofilter = BVH8Triangle4IntersectorStreamMoellerNoFilter(); +#endif + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8Triangle4vIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; +#define ENABLE_WOOP_TEST 0 +#if ENABLE_WOOP_TEST == 0 + //assert(ivariant == IntersectVariant::ROBUST); + intersectors.intersector1 = BVH8Triangle4vIntersector1Pluecker(); +#else + intersectors.intersector1 = BVH8Triangle4vIntersector1Woop(); +#endif + +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4vIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Triangle4vIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Triangle4vIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8Triangle4vIntersectorStreamPluecker(); +#endif + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8Triangle4iIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4iIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4iIntersector4HybridMoeller(); + intersectors.intersector8 = BVH8Triangle4iIntersector8HybridMoeller(); + intersectors.intersector16 = BVH8Triangle4iIntersector16HybridMoeller(); + intersectors.intersectorN = BVH8Triangle4iIntersectorStreamMoeller(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4iIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4iIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Triangle4iIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Triangle4iIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8Triangle4iIntersectorStreamPluecker(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8Triangle4vMBIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4vMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4vMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH8Triangle4vMBIntersector8HybridMoeller(); + intersectors.intersector16 = BVH8Triangle4vMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4vMBIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4vMBIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Triangle4vMBIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Triangle4vMBIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8Triangle4iMBIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4iMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4iMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH8Triangle4iMBIntersector8HybridMoeller(); + intersectors.intersector16 = BVH8Triangle4iMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Triangle4iMBIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Triangle4iMBIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Triangle4iMBIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Triangle4iMBIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8Quad4vIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Quad4vIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4_filter = BVH8Quad4vIntersector4HybridMoeller(); + intersectors.intersector4_nofilter = BVH8Quad4vIntersector4HybridMoellerNoFilter(); + intersectors.intersector8_filter = BVH8Quad4vIntersector8HybridMoeller(); + intersectors.intersector8_nofilter = BVH8Quad4vIntersector8HybridMoellerNoFilter(); + intersectors.intersector16_filter = BVH8Quad4vIntersector16HybridMoeller(); + intersectors.intersector16_nofilter = BVH8Quad4vIntersector16HybridMoellerNoFilter(); + intersectors.intersectorN_filter = BVH8Quad4vIntersectorStreamMoeller(); + intersectors.intersectorN_nofilter = BVH8Quad4vIntersectorStreamMoellerNoFilter(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Quad4vIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Quad4vIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Quad4vIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Quad4vIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8Quad4vIntersectorStreamPluecker(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8Quad4iIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Quad4iIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Quad4iIntersector4HybridMoeller(); + intersectors.intersector8 = BVH8Quad4iIntersector8HybridMoeller(); + intersectors.intersector16 = BVH8Quad4iIntersector16HybridMoeller(); + intersectors.intersectorN = BVH8Quad4iIntersectorStreamMoeller(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Quad4iIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Quad4iIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Quad4iIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Quad4iIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8Quad4iIntersectorStreamPluecker(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::BVH8Quad4iMBIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + switch (ivariant) { + case IntersectVariant::FAST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Quad4iMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Quad4iMBIntersector4HybridMoeller(); + intersectors.intersector8 = BVH8Quad4iMBIntersector8HybridMoeller(); + intersectors.intersector16 = BVH8Quad4iMBIntersector16HybridMoeller(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + case IntersectVariant::ROBUST: + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8Quad4iMBIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8Quad4iMBIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8Quad4iMBIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8Quad4iMBIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + } + return Accel::Intersectors(); + } + + Accel::Intersectors BVH8Factory::QBVH8Triangle4iIntersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = QBVH8Triangle4iIntersector1Pluecker(); + return intersectors; + } + + Accel::Intersectors BVH8Factory::QBVH8Triangle4Intersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = QBVH8Triangle4Intersector1Moeller(); + return intersectors; + } + + Accel::Intersectors BVH8Factory::QBVH8Quad4iIntersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = QBVH8Quad4iIntersector1Pluecker(); + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8UserGeometryIntersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8VirtualIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8VirtualIntersector4Chunk(); + intersectors.intersector8 = BVH8VirtualIntersector8Chunk(); + intersectors.intersector16 = BVH8VirtualIntersector16Chunk(); + intersectors.intersectorN = BVH8VirtualIntersectorStream(); +#endif + intersectors.collider = BVH8ColliderUserGeom(); + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8UserGeometryMBIntersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8VirtualMBIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8VirtualMBIntersector4Chunk(); + intersectors.intersector8 = BVH8VirtualMBIntersector8Chunk(); + intersectors.intersector16 = BVH8VirtualMBIntersector16Chunk(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8InstanceIntersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8InstanceIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8InstanceIntersector4Chunk(); + intersectors.intersector8 = BVH8InstanceIntersector8Chunk(); + intersectors.intersector16 = BVH8InstanceIntersector16Chunk(); + intersectors.intersectorN = BVH8InstanceIntersectorStream(); +#endif + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8InstanceMBIntersectors(BVH8* bvh) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8InstanceMBIntersector1(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8InstanceMBIntersector4Chunk(); + intersectors.intersector8 = BVH8InstanceMBIntersector8Chunk(); + intersectors.intersector16 = BVH8InstanceMBIntersector16Chunk(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + return intersectors; + } + + Accel* BVH8Factory::BVH8OBBVirtualCurve8v(Scene* scene, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Curve8v::type,scene); + Accel::Intersectors intersectors = BVH8OBBVirtualCurveIntersectors(accel,VirtualCurveIntersector8v(),ivariant); + Builder* builder = BVH8Curve8vBuilder_OBB_New(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8OBBVirtualCurve8iMB(Scene* scene, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Curve8iMB::type,scene); + Accel::Intersectors intersectors = BVH8OBBVirtualCurveIntersectorsMB(accel,VirtualCurveIntersector8iMB(),ivariant); + Builder* builder = BVH8OBBCurve8iMBBuilder_OBB(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Triangle4(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Triangle4::type,scene); + Accel::Intersectors intersectors= BVH8Triangle4Intersectors(accel,ivariant); + Builder* builder = nullptr; + if (scene->device->tri_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Triangle4SceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH8BuilderTwoLevelTriangle4MeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH8Triangle4SceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->tri_builder == "sah" ) builder = BVH8Triangle4SceneBuilderSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_fast_spatial") builder = BVH8Triangle4SceneBuilderFastSpatialSAH(accel,scene,0); + else if (scene->device->tri_builder == "sah_presplit") builder = BVH8Triangle4SceneBuilderSAH(accel,scene,MODE_HIGH_QUALITY); + else if (scene->device->tri_builder == "dynamic" ) builder = BVH8BuilderTwoLevelTriangle4MeshSAH(accel,scene,false); + else if (scene->device->tri_builder == "morton" ) builder = BVH8BuilderTwoLevelTriangle4MeshSAH(accel,scene,true); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Triangle4v(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Triangle4v::type,scene); + Accel::Intersectors intersectors= BVH8Triangle4vIntersectors(accel,ivariant); + Builder* builder = nullptr; + if (scene->device->tri_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Triangle4vSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH8BuilderTwoLevelTriangle4vMeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH8Triangle4vSceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->tri_builder == "sah_fast_spatial") builder = BVH8Triangle4SceneBuilderFastSpatialSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder+" for BVH8"); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Triangle4i(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Triangle4i::type,scene); + Accel::Intersectors intersectors = BVH8Triangle4iIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->tri_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Triangle4iSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH8BuilderTwoLevelTriangle4iMeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: assert(false); break; // FIXME: implement + } + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Triangle4iMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Triangle4i::type,scene); + Accel::Intersectors intersectors = BVH8Triangle4iMBIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->tri_builder_mb == "default") { // FIXME: implement + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Triangle4iMBSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->tri_builder_mb == "internal_time_splits") builder = BVH8Triangle4iMBSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder_mb+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Triangle4vMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Triangle4vMB::type,scene); + Accel::Intersectors intersectors= BVH8Triangle4vMBIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->tri_builder_mb == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Triangle4vMBSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->tri_builder_mb == "internal_time_splits") builder = BVH8Triangle4vMBSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->tri_builder_mb+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8QuantizedTriangle4i(Scene* scene) + { + BVH8* accel = new BVH8(Triangle4i::type,scene); + Accel::Intersectors intersectors = QBVH8Triangle4iIntersectors(accel); + Builder* builder = BVH8QuantizedTriangle4iSceneBuilderSAH(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8QuantizedTriangle4(Scene* scene) + { + BVH8* accel = new BVH8(Triangle4::type,scene); + Accel::Intersectors intersectors = QBVH8Triangle4Intersectors(accel); + Builder* builder = BVH8QuantizedTriangle4SceneBuilderSAH(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Quad4v(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Quad4v::type,scene); + Accel::Intersectors intersectors = BVH8Quad4vIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->quad_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Quad4vSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH8BuilderTwoLevelQuadMeshSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: builder = BVH8Quad4vSceneBuilderFastSpatialSAH(accel,scene,0); break; + } + } + else if (scene->device->quad_builder == "dynamic" ) builder = BVH8BuilderTwoLevelQuadMeshSAH(accel,scene,false); + else if (scene->device->quad_builder == "morton" ) builder = BVH8BuilderTwoLevelQuadMeshSAH(accel,scene,true); + else if (scene->device->quad_builder == "sah_fast_spatial" ) builder = BVH8Quad4vSceneBuilderFastSpatialSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Quad4i(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Quad4i::type,scene); + Accel::Intersectors intersectors = BVH8Quad4iIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->quad_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Quad4iSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; // FIXME: implement + } + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Quad4iMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(Quad4i::type,scene); + Accel::Intersectors intersectors = BVH8Quad4iMBIntersectors(accel,ivariant); + + Builder* builder = nullptr; + if (scene->device->quad_builder_mb == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8Quad4iMBSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : assert(false); break; // FIXME: implement + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder_mb+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8QuantizedQuad4i(Scene* scene) + { + BVH8* accel = new BVH8(Quad4i::type,scene); + Accel::Intersectors intersectors = QBVH8Quad4iIntersectors(accel); + Builder* builder = nullptr; + if (scene->device->quad_builder == "default" ) builder = BVH8QuantizedQuad4iSceneBuilderSAH(accel,scene,0); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->quad_builder+" for QBVH8"); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8UserGeometry(Scene* scene, BuildVariant bvariant) + { + BVH8* accel = new BVH8(Object::type,scene); + Accel::Intersectors intersectors = BVH8UserGeometryIntersectors(accel); + + Builder* builder = nullptr; + if (scene->device->object_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8VirtualSceneBuilderSAH(accel,scene,0); break; + case BuildVariant::DYNAMIC : builder = BVH8BuilderTwoLevelVirtualSAH(accel,scene,false); break; + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->object_builder == "sah") builder = BVH8VirtualSceneBuilderSAH(accel,scene,0); + else if (scene->device->object_builder == "dynamic") builder = BVH8BuilderTwoLevelVirtualSAH(accel,scene,false); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->object_builder+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8UserGeometryMB(Scene* scene) + { + BVH8* accel = new BVH8(Object::type,scene); + Accel::Intersectors intersectors = BVH8UserGeometryMBIntersectors(accel); + Builder* builder = BVH8VirtualMBSceneBuilderSAH(accel,scene,0); + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8Instance(Scene* scene, bool isExpensive, BuildVariant bvariant) + { + BVH8* accel = new BVH8(InstancePrimitive::type,scene); + Accel::Intersectors intersectors = BVH8InstanceIntersectors(accel); + auto gtype = isExpensive ? Geometry::MTY_INSTANCE_EXPENSIVE : Geometry::MTY_INSTANCE; + // Builder* builder = BVH8InstanceSceneBuilderSAH(accel,scene,gtype); + + Builder* builder = nullptr; + if (scene->device->object_builder == "default") { + switch (bvariant) { + case BuildVariant::STATIC : builder = BVH8InstanceSceneBuilderSAH(accel,scene,gtype);; break; + case BuildVariant::DYNAMIC : builder = BVH8BuilderTwoLevelInstanceSAH(accel,scene,gtype,false); break; + case BuildVariant::HIGH_QUALITY: assert(false); break; + } + } + else if (scene->device->object_builder == "sah") builder = BVH8InstanceSceneBuilderSAH(accel,scene,gtype); + else if (scene->device->object_builder == "dynamic") builder = BVH8BuilderTwoLevelInstanceSAH(accel,scene,gtype,false); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->object_builder+" for BVH8"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8InstanceMB(Scene* scene, bool isExpensive) + { + BVH8* accel = new BVH8(InstancePrimitive::type,scene); + Accel::Intersectors intersectors = BVH8InstanceMBIntersectors(accel); + auto gtype = isExpensive ? Geometry::MTY_INSTANCE_EXPENSIVE : Geometry::MTY_INSTANCE; + Builder* builder = BVH8InstanceMBSceneBuilderSAH(accel,scene,gtype); + return new AccelInstance(accel,builder,intersectors); + } + + Accel::Intersectors BVH8Factory::BVH8GridIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + if (ivariant == IntersectVariant::FAST) + { + intersectors.intersector1 = BVH8GridIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8GridIntersector4HybridMoeller(); + intersectors.intersector8 = BVH8GridIntersector8HybridMoeller(); + intersectors.intersector16 = BVH8GridIntersector16HybridMoeller(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + } + else /* if (ivariant == IntersectVariant::ROBUST) */ + { + intersectors.intersector1 = BVH8GridIntersector1Pluecker(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = BVH8GridIntersector4HybridPluecker(); + intersectors.intersector8 = BVH8GridIntersector8HybridPluecker(); + intersectors.intersector16 = BVH8GridIntersector16HybridPluecker(); + intersectors.intersectorN = BVH8IntersectorStreamPacketFallback(); +#endif + } + return intersectors; + } + + Accel::Intersectors BVH8Factory::BVH8GridMBIntersectors(BVH8* bvh, IntersectVariant ivariant) + { + Accel::Intersectors intersectors; + intersectors.ptr = bvh; + intersectors.intersector1 = BVH8GridMBIntersector1Moeller(); +#if defined (EMBREE_RAY_PACKETS) + intersectors.intersector4 = nullptr; + intersectors.intersector8 = nullptr; + intersectors.intersector16 = nullptr; + intersectors.intersectorN = nullptr; +#endif + return intersectors; + } + + Accel* BVH8Factory::BVH8Grid(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(SubGridQBVH8::type,scene); + Accel::Intersectors intersectors = BVH8GridIntersectors(accel,ivariant); + Builder* builder = nullptr; + if (scene->device->grid_builder == "default") { + builder = BVH8GridSceneBuilderSAH(accel,scene,0); + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->object_builder+" for BVH4"); + + return new AccelInstance(accel,builder,intersectors); + } + + Accel* BVH8Factory::BVH8GridMB(Scene* scene, BuildVariant bvariant, IntersectVariant ivariant) + { + BVH8* accel = new BVH8(SubGridQBVH8::type,scene); + Accel::Intersectors intersectors = BVH8GridMBIntersectors(accel,ivariant); + Builder* builder = nullptr; + if (scene->device->grid_builder_mb == "default") { + builder = BVH8GridMBSceneBuilderSAH(accel,scene,0); + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown builder "+scene->device->object_builder+" for BVH8MB"); + return new AccelInstance(accel,builder,intersectors); + } +} + +#endif diff --git a/thirdparty/embree/kernels/bvh/bvh8_factory.h b/thirdparty/embree/kernels/bvh/bvh8_factory.h new file mode 100644 index 000000000000..b92188e7d356 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh8_factory.h @@ -0,0 +1,280 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_factory.h" + +namespace embree +{ + /*! BVH8 instantiations */ + class BVH8Factory : public BVHFactory + { + public: + BVH8Factory(int bfeatures, int ifeatures); + + public: + Accel* BVH8OBBVirtualCurve8v(Scene* scene, IntersectVariant ivariant); + Accel* BVH8OBBVirtualCurve8iMB(Scene* scene, IntersectVariant ivariant); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector8v); + DEFINE_SYMBOL2(VirtualCurveIntersector*,VirtualCurveIntersector8iMB); + + Accel* BVH8Triangle4 (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8Triangle4v (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8Triangle4i (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8Triangle4vMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8Triangle4iMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + + Accel* BVH8Quad4v (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8Quad4i (Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8Quad4iMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + + Accel* BVH8QuantizedTriangle4i(Scene* scene); + Accel* BVH8QuantizedTriangle4(Scene* scene); + Accel* BVH8QuantizedQuad4i(Scene* scene); + + Accel* BVH8UserGeometry(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC); + Accel* BVH8UserGeometryMB(Scene* scene); + + Accel* BVH8Instance(Scene* scene, bool isExpensive, BuildVariant bvariant = BuildVariant::STATIC); + Accel* BVH8InstanceMB(Scene* scene, bool isExpensive); + + Accel* BVH8Grid(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + Accel* BVH8GridMB(Scene* scene, BuildVariant bvariant = BuildVariant::STATIC, IntersectVariant ivariant = IntersectVariant::FAST); + + private: + void selectBuilders(int features); + void selectIntersectors(int features); + + private: + Accel::Intersectors BVH8OBBVirtualCurveIntersectors(BVH8* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant); + Accel::Intersectors BVH8OBBVirtualCurveIntersectorsMB(BVH8* bvh, VirtualCurveIntersector* leafIntersector, IntersectVariant ivariant); + + Accel::Intersectors BVH8Triangle4Intersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8Triangle4vIntersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8Triangle4iIntersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8Triangle4iMBIntersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8Triangle4vMBIntersectors(BVH8* bvh, IntersectVariant ivariant); + + Accel::Intersectors BVH8Quad4vIntersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8Quad4iIntersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8Quad4iMBIntersectors(BVH8* bvh, IntersectVariant ivariant); + + Accel::Intersectors QBVH8Triangle4iIntersectors(BVH8* bvh); + Accel::Intersectors QBVH8Triangle4Intersectors(BVH8* bvh); + Accel::Intersectors QBVH8Quad4iIntersectors(BVH8* bvh); + + Accel::Intersectors BVH8UserGeometryIntersectors(BVH8* bvh); + Accel::Intersectors BVH8UserGeometryMBIntersectors(BVH8* bvh); + + Accel::Intersectors BVH8InstanceIntersectors(BVH8* bvh); + Accel::Intersectors BVH8InstanceMBIntersectors(BVH8* bvh); + + Accel::Intersectors BVH8GridIntersectors(BVH8* bvh, IntersectVariant ivariant); + Accel::Intersectors BVH8GridMBIntersectors(BVH8* bvh, IntersectVariant ivariant); + + private: + DEFINE_SYMBOL2(Accel::Collider,BVH8ColliderUserGeom); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersector1MB); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersectorRobust1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8OBBVirtualCurveIntersectorRobust1MB); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4Intersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vMBIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4iMBIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Triangle4vIntersector1Woop); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Quad4vIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Quad4iIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Quad4vIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Quad4iIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Quad4iMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8Quad4iMBIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,QBVH8Triangle4iIntersector1Pluecker); + DEFINE_SYMBOL2(Accel::Intersector1,QBVH8Triangle4Intersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,QBVH8Quad4iIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8VirtualIntersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8VirtualMBIntersector1); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8InstanceIntersector1); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8InstanceMBIntersector1); + + DEFINE_SYMBOL2(Accel::Intersector1,BVH8GridIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8GridMBIntersector1Moeller); + DEFINE_SYMBOL2(Accel::Intersector1,BVH8GridIntersector1Pluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersector4Hybrid); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersector4HybridMB); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersectorRobust4Hybrid); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8OBBVirtualCurveIntersectorRobust4HybridMB); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4Intersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4Intersector4HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4vIntersector4HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4vMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4vMBIntersector4HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Triangle4iMBIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4vIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4vIntersector4HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4iIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4vIntersector4HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4iIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4iMBIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8Quad4iMBIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8VirtualIntersector4Chunk); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8VirtualMBIntersector4Chunk); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8InstanceIntersector4Chunk); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8InstanceMBIntersector4Chunk); + + DEFINE_SYMBOL2(Accel::Intersector4,BVH8GridIntersector4HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector4,BVH8GridIntersector4HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersector8Hybrid); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersector8HybridMB); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersectorRobust8Hybrid); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8OBBVirtualCurveIntersectorRobust8HybridMB); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4Intersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4Intersector8HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4vIntersector8HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4vMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4vMBIntersector8HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Triangle4iMBIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4vIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4vIntersector8HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4iIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4vIntersector8HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4iIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4iMBIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8Quad4iMBIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8VirtualIntersector8Chunk); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8VirtualMBIntersector8Chunk); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8InstanceIntersector8Chunk); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8InstanceMBIntersector8Chunk); + + DEFINE_SYMBOL2(Accel::Intersector8,BVH8GridIntersector8HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector8,BVH8GridIntersector8HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersector16Hybrid); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersector16HybridMB); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersectorRobust16Hybrid); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8OBBVirtualCurveIntersectorRobust16HybridMB); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4Intersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4Intersector16HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4vIntersector16HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4vMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4vMBIntersector16HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Triangle4iMBIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4vIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4vIntersector16HybridMoellerNoFilter); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4iIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4vIntersector16HybridPluecker); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4iIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4iMBIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8Quad4iMBIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8VirtualIntersector16Chunk); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8VirtualMBIntersector16Chunk); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8InstanceIntersector16Chunk); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8InstanceMBIntersector16Chunk); + + DEFINE_SYMBOL2(Accel::Intersector16,BVH8GridIntersector16HybridMoeller); + DEFINE_SYMBOL2(Accel::Intersector16,BVH8GridIntersector16HybridPluecker); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8IntersectorStreamPacketFallback); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4IntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4IntersectorStreamMoellerNoFilter); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4iIntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4vIntersectorStreamPluecker); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Triangle4iIntersectorStreamPluecker); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Quad4vIntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Quad4vIntersectorStreamMoellerNoFilter); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Quad4iIntersectorStreamMoeller); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Quad4vIntersectorStreamPluecker); + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8Quad4iIntersectorStreamPluecker); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8VirtualIntersectorStream); + + DEFINE_SYMBOL2(Accel::IntersectorN,BVH8InstanceIntersectorStream); + + // SAH scene builders + private: + DEFINE_ISA_FUNCTION(Builder*,BVH8Curve8vBuilder_OBB_New,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8OBBCurve8iMBBuilder_OBB,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4SceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4vMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8QuantizedTriangle4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8QuantizedTriangle4SceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH8Quad4vSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Quad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Quad4iMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8QuantizedQuad4iSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH8VirtualSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8VirtualMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + DEFINE_ISA_FUNCTION(Builder*,BVH8InstanceSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + DEFINE_ISA_FUNCTION(Builder*,BVH8InstanceMBSceneBuilderSAH,void* COMMA Scene* COMMA Geometry::GTypeMask); + + DEFINE_ISA_FUNCTION(Builder*,BVH8GridSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8GridMBSceneBuilderSAH,void* COMMA Scene* COMMA size_t); + + // SAH spatial scene builders + private: + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4SceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Triangle4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + DEFINE_ISA_FUNCTION(Builder*,BVH8Quad4vSceneBuilderFastSpatialSAH,void* COMMA Scene* COMMA size_t); + + // twolevel scene builders + private: + DEFINE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelTriangle4MeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelTriangle4vMeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelTriangle4iMeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelQuadMeshSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelVirtualSAH,void* COMMA Scene* COMMA bool); + DEFINE_ISA_FUNCTION(Builder*,BVH8BuilderTwoLevelInstanceSAH,void* COMMA Scene* COMMA Geometry::GTypeMask COMMA bool); + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder.cpp b/thirdparty/embree/kernels/bvh/bvh_builder.cpp new file mode 100644 index 000000000000..e832537ec570 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder.cpp @@ -0,0 +1,60 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_builder.h" + +namespace embree +{ + namespace isa + { + template + typename BVHN::NodeRef BVHNBuilderVirtual::BVHNBuilderV::build(FastAllocator* allocator, BuildProgressMonitor& progressFunc, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings) + { + auto createLeafFunc = [&] (const PrimRef* prims, const range& set, const Allocator& alloc) -> NodeRef { + return createLeaf(prims,set,alloc); + }; + + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + return BVHBuilderBinnedSAH::build + (FastAllocator::Create(allocator),typename BVH::AABBNode::Create2(),typename BVH::AABBNode::Set3(allocator,prims),createLeafFunc,progressFunc,prims,pinfo,settings); + } + + + template + typename BVHN::NodeRef BVHNBuilderQuantizedVirtual::BVHNBuilderV::build(FastAllocator* allocator, BuildProgressMonitor& progressFunc, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings) + { + auto createLeafFunc = [&] (const PrimRef* prims, const range& set, const Allocator& alloc) -> NodeRef { + return createLeaf(prims,set,alloc); + }; + + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + return BVHBuilderBinnedSAH::build + (FastAllocator::Create(allocator),typename BVH::QuantizedNode::Create2(),typename BVH::QuantizedNode::Set2(),createLeafFunc,progressFunc,prims,pinfo,settings); + } + + template + typename BVHN::NodeRecordMB BVHNBuilderMblurVirtual::BVHNBuilderV::build(FastAllocator* allocator, BuildProgressMonitor& progressFunc, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings, const BBox1f& timeRange) + { + auto createLeafFunc = [&] (const PrimRef* prims, const range& set, const Allocator& alloc) -> NodeRecordMB { + return createLeaf(prims,set,alloc); + }; + + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + return BVHBuilderBinnedSAH::build + (FastAllocator::Create(allocator),typename BVH::AABBNodeMB::Create(),typename BVH::AABBNodeMB::SetTimeRange(timeRange),createLeafFunc,progressFunc,prims,pinfo,settings); + } + + template struct BVHNBuilderVirtual<4>; + template struct BVHNBuilderQuantizedVirtual<4>; + template struct BVHNBuilderMblurVirtual<4>; + +#if defined(__AVX__) + template struct BVHNBuilderVirtual<8>; + template struct BVHNBuilderQuantizedVirtual<8>; + template struct BVHNBuilderMblurVirtual<8>; +#endif + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder.h b/thirdparty/embree/kernels/bvh/bvh_builder.h new file mode 100644 index 000000000000..1b86bb45adac --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder.h @@ -0,0 +1,114 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh.h" +#include "../builders/bvh_builder_sah.h" + +namespace embree +{ + namespace isa + { + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + template + struct BVHNBuilderVirtual + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef FastAllocator::CachedAllocator Allocator; + + struct BVHNBuilderV { + NodeRef build(FastAllocator* allocator, BuildProgressMonitor& progress, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings); + virtual NodeRef createLeaf (const PrimRef* prims, const range& set, const Allocator& alloc) = 0; + }; + + template + struct BVHNBuilderT : public BVHNBuilderV + { + BVHNBuilderT (CreateLeafFunc createLeafFunc) + : createLeafFunc(createLeafFunc) {} + + NodeRef createLeaf (const PrimRef* prims, const range& set, const Allocator& alloc) { + return createLeafFunc(prims,set,alloc); + } + + private: + CreateLeafFunc createLeafFunc; + }; + + template + static NodeRef build(FastAllocator* allocator, CreateLeafFunc createLeaf, BuildProgressMonitor& progress, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings) { + return BVHNBuilderT(createLeaf).build(allocator,progress,prims,pinfo,settings); + } + }; + + template + struct BVHNBuilderQuantizedVirtual + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef FastAllocator::CachedAllocator Allocator; + + struct BVHNBuilderV { + NodeRef build(FastAllocator* allocator, BuildProgressMonitor& progress, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings); + virtual NodeRef createLeaf (const PrimRef* prims, const range& set, const Allocator& alloc) = 0; + }; + + template + struct BVHNBuilderT : public BVHNBuilderV + { + BVHNBuilderT (CreateLeafFunc createLeafFunc) + : createLeafFunc(createLeafFunc) {} + + NodeRef createLeaf (const PrimRef* prims, const range& set, const Allocator& alloc) { + return createLeafFunc(prims,set,alloc); + } + + private: + CreateLeafFunc createLeafFunc; + }; + + template + static NodeRef build(FastAllocator* allocator, CreateLeafFunc createLeaf, BuildProgressMonitor& progress, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings) { + return BVHNBuilderT(createLeaf).build(allocator,progress,prims,pinfo,settings); + } + }; + + template + struct BVHNBuilderMblurVirtual + { + typedef BVHN BVH; + typedef typename BVH::AABBNodeMB AABBNodeMB; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecordMB NodeRecordMB; + typedef FastAllocator::CachedAllocator Allocator; + + struct BVHNBuilderV { + NodeRecordMB build(FastAllocator* allocator, BuildProgressMonitor& progress, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings, const BBox1f& timeRange); + virtual NodeRecordMB createLeaf (const PrimRef* prims, const range& set, const Allocator& alloc) = 0; + }; + + template + struct BVHNBuilderT : public BVHNBuilderV + { + BVHNBuilderT (CreateLeafFunc createLeafFunc) + : createLeafFunc(createLeafFunc) {} + + NodeRecordMB createLeaf (const PrimRef* prims, const range& set, const Allocator& alloc) { + return createLeafFunc(prims,set,alloc); + } + + private: + CreateLeafFunc createLeafFunc; + }; + + template + static NodeRecordMB build(FastAllocator* allocator, CreateLeafFunc createLeaf, BuildProgressMonitor& progress, PrimRef* prims, const PrimInfo& pinfo, GeneralBVHBuilder::Settings settings, const BBox1f& timeRange) { + return BVHNBuilderT(createLeaf).build(allocator,progress,prims,pinfo,settings,timeRange); + } + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_morton.cpp b/thirdparty/embree/kernels/bvh/bvh_builder_morton.cpp new file mode 100644 index 000000000000..fa1710428378 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_morton.cpp @@ -0,0 +1,531 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh.h" +#include "bvh_statistics.h" +#include "bvh_rotate.h" +#include "../common/profile.h" +#include "../../common/algorithms/parallel_prefix_sum.h" + +#include "../builders/primrefgen.h" +#include "../builders/bvh_builder_morton.h" + +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" + +#if defined(__X86_64__) +# define ROTATE_TREE 1 // specifies number of tree rotation rounds to perform +#else +# define ROTATE_TREE 0 // do not use tree rotations on 32 bit platforms, barrier bit in NodeRef will cause issues +#endif + +namespace embree +{ + namespace isa + { + template + struct SetBVHNBounds + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + typedef typename BVH::AABBNode AABBNode; + + BVH* bvh; + __forceinline SetBVHNBounds (BVH* bvh) : bvh(bvh) {} + + __forceinline NodeRecord operator() (NodeRef ref, const NodeRecord* children, size_t num) + { + AABBNode* node = ref.getAABBNode(); + + BBox3fa res = empty; + for (size_t i=0; isetRef(i,children[i].ref); + node->setBounds(i,b); + } + + BBox3fx result = (BBox3fx&)res; +#if ROTATE_TREE + if (N == 4) + { + size_t n = 0; + for (size_t i=0; i= 4096) { + for (size_t i=0; i::rotate(node->child(i)); + node->child(i).setBarrier(); + } + } + } + result.lower.a = unsigned(n); + } +#endif + + return NodeRecord(ref,result); + } + }; + + template + struct CreateMortonLeaf; + + template + struct CreateMortonLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + __forceinline CreateMortonLeaf (TriangleMesh* mesh, unsigned int geomID, BVHBuilderMorton::BuildPrim* morton) + : mesh(mesh), morton(morton), geomID_(geomID) {} + + __noinline NodeRecord operator() (const range& current, const FastAllocator::CachedAllocator& alloc) + { + vfloat4 lower(pos_inf); + vfloat4 upper(neg_inf); + size_t items = current.size(); + size_t start = current.begin(); + assert(items<=4); + + /* allocate leaf node */ + Triangle4* accel = (Triangle4*) alloc.malloc1(sizeof(Triangle4),BVH::byteAlignment); + NodeRef ref = BVH::encodeLeaf((char*)accel,1); + vuint4 vgeomID = -1, vprimID = -1; + Vec3vf4 v0 = zero, v1 = zero, v2 = zero; + const TriangleMesh* __restrict__ const mesh = this->mesh; + + for (size_t i=0; itriangle(primID); + const Vec3fa& p0 = mesh->vertex(tri.v[0]); + const Vec3fa& p1 = mesh->vertex(tri.v[1]); + const Vec3fa& p2 = mesh->vertex(tri.v[2]); + lower = min(lower,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2); + upper = max(upper,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2); + vgeomID [i] = geomID_; + vprimID [i] = primID; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + } + + Triangle4::store_nt(accel,Triangle4(v0,v1,v2,vgeomID,vprimID)); + BBox3fx box_o = BBox3fx((Vec3fx)lower,(Vec3fx)upper); +#if ROTATE_TREE + if (N == 4) + box_o.lower.a = unsigned(current.size()); +#endif + return NodeRecord(ref,box_o); + } + + private: + TriangleMesh* mesh; + BVHBuilderMorton::BuildPrim* morton; + unsigned int geomID_ = std::numeric_limits::max(); + }; + + template + struct CreateMortonLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + __forceinline CreateMortonLeaf (TriangleMesh* mesh, unsigned int geomID, BVHBuilderMorton::BuildPrim* morton) + : mesh(mesh), morton(morton), geomID_(geomID) {} + + __noinline NodeRecord operator() (const range& current, const FastAllocator::CachedAllocator& alloc) + { + vfloat4 lower(pos_inf); + vfloat4 upper(neg_inf); + size_t items = current.size(); + size_t start = current.begin(); + assert(items<=4); + + /* allocate leaf node */ + Triangle4v* accel = (Triangle4v*) alloc.malloc1(sizeof(Triangle4v),BVH::byteAlignment); + NodeRef ref = BVH::encodeLeaf((char*)accel,1); + vuint4 vgeomID = -1, vprimID = -1; + Vec3vf4 v0 = zero, v1 = zero, v2 = zero; + const TriangleMesh* __restrict__ mesh = this->mesh; + + for (size_t i=0; itriangle(primID); + const Vec3fa& p0 = mesh->vertex(tri.v[0]); + const Vec3fa& p1 = mesh->vertex(tri.v[1]); + const Vec3fa& p2 = mesh->vertex(tri.v[2]); + lower = min(lower,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2); + upper = max(upper,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2); + vgeomID [i] = geomID_; + vprimID [i] = primID; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + } + Triangle4v::store_nt(accel,Triangle4v(v0,v1,v2,vgeomID,vprimID)); + BBox3fx box_o = BBox3fx((Vec3fx)lower,(Vec3fx)upper); +#if ROTATE_TREE + if (N == 4) + box_o.lower.a = current.size(); +#endif + return NodeRecord(ref,box_o); + } + private: + TriangleMesh* mesh; + BVHBuilderMorton::BuildPrim* morton; + unsigned int geomID_ = std::numeric_limits::max(); + }; + + template + struct CreateMortonLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + __forceinline CreateMortonLeaf (TriangleMesh* mesh, unsigned int geomID, BVHBuilderMorton::BuildPrim* morton) + : mesh(mesh), morton(morton), geomID_(geomID) {} + + __noinline NodeRecord operator() (const range& current, const FastAllocator::CachedAllocator& alloc) + { + vfloat4 lower(pos_inf); + vfloat4 upper(neg_inf); + size_t items = current.size(); + size_t start = current.begin(); + assert(items<=4); + + /* allocate leaf node */ + Triangle4i* accel = (Triangle4i*) alloc.malloc1(sizeof(Triangle4i),BVH::byteAlignment); + NodeRef ref = BVH::encodeLeaf((char*)accel,1); + + vuint4 v0 = zero, v1 = zero, v2 = zero; + vuint4 vgeomID = -1, vprimID = -1; + const TriangleMesh* __restrict__ const mesh = this->mesh; + + for (size_t i=0; itriangle(primID); + const Vec3fa& p0 = mesh->vertex(tri.v[0]); + const Vec3fa& p1 = mesh->vertex(tri.v[1]); + const Vec3fa& p2 = mesh->vertex(tri.v[2]); + lower = min(lower,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2); + upper = max(upper,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2); + vgeomID[i] = geomID_; + vprimID[i] = primID; + unsigned int int_stride = mesh->vertices0.getStride()/4; + v0[i] = tri.v[0] * int_stride; + v1[i] = tri.v[1] * int_stride; + v2[i] = tri.v[2] * int_stride; + } + + for (size_t i=items; i<4; i++) + { + vgeomID[i] = vgeomID[0]; + vprimID[i] = -1; + v0[i] = 0; + v1[i] = 0; + v2[i] = 0; + } + Triangle4i::store_nt(accel,Triangle4i(v0,v1,v2,vgeomID,vprimID)); + BBox3fx box_o = BBox3fx((Vec3fx)lower,(Vec3fx)upper); +#if ROTATE_TREE + if (N == 4) + box_o.lower.a = current.size(); +#endif + return NodeRecord(ref,box_o); + } + private: + TriangleMesh* mesh; + BVHBuilderMorton::BuildPrim* morton; + unsigned int geomID_ = std::numeric_limits::max(); + }; + + template + struct CreateMortonLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + __forceinline CreateMortonLeaf (QuadMesh* mesh, unsigned int geomID, BVHBuilderMorton::BuildPrim* morton) + : mesh(mesh), morton(morton), geomID_(geomID) {} + + __noinline NodeRecord operator() (const range& current, const FastAllocator::CachedAllocator& alloc) + { + vfloat4 lower(pos_inf); + vfloat4 upper(neg_inf); + size_t items = current.size(); + size_t start = current.begin(); + assert(items<=4); + + /* allocate leaf node */ + Quad4v* accel = (Quad4v*) alloc.malloc1(sizeof(Quad4v),BVH::byteAlignment); + NodeRef ref = BVH::encodeLeaf((char*)accel,1); + + vuint4 vgeomID = -1, vprimID = -1; + Vec3vf4 v0 = zero, v1 = zero, v2 = zero, v3 = zero; + const QuadMesh* __restrict__ mesh = this->mesh; + + for (size_t i=0; iquad(primID); + const Vec3fa& p0 = mesh->vertex(tri.v[0]); + const Vec3fa& p1 = mesh->vertex(tri.v[1]); + const Vec3fa& p2 = mesh->vertex(tri.v[2]); + const Vec3fa& p3 = mesh->vertex(tri.v[3]); + lower = min(lower,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2,(vfloat4)p3); + upper = max(upper,(vfloat4)p0,(vfloat4)p1,(vfloat4)p2,(vfloat4)p3); + vgeomID [i] = geomID_; + vprimID [i] = primID; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + v3.x[i] = p3.x; v3.y[i] = p3.y; v3.z[i] = p3.z; + } + Quad4v::store_nt(accel,Quad4v(v0,v1,v2,v3,vgeomID,vprimID)); + BBox3fx box_o = BBox3fx((Vec3fx)lower,(Vec3fx)upper); +#if ROTATE_TREE + if (N == 4) + box_o.lower.a = current.size(); +#endif + return NodeRecord(ref,box_o); + } + private: + QuadMesh* mesh; + BVHBuilderMorton::BuildPrim* morton; + unsigned int geomID_ = std::numeric_limits::max(); + }; + + template + struct CreateMortonLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + __forceinline CreateMortonLeaf (UserGeometry* mesh, unsigned int geomID, BVHBuilderMorton::BuildPrim* morton) + : mesh(mesh), morton(morton), geomID_(geomID) {} + + __noinline NodeRecord operator() (const range& current, const FastAllocator::CachedAllocator& alloc) + { + vfloat4 lower(pos_inf); + vfloat4 upper(neg_inf); + size_t items = current.size(); + size_t start = current.begin(); + + /* allocate leaf node */ + Object* accel = (Object*) alloc.malloc1(items*sizeof(Object),BVH::byteAlignment); + NodeRef ref = BVH::encodeLeaf((char*)accel,items); + const UserGeometry* mesh = this->mesh; + + BBox3fa bounds = empty; + for (size_t i=0; ibounds(primID)); + new (&accel[i]) Object(geomID_,primID); + } + + BBox3fx box_o = (BBox3fx&)bounds; +#if ROTATE_TREE + if (N == 4) + box_o.lower.a = current.size(); +#endif + return NodeRecord(ref,box_o); + } + private: + UserGeometry* mesh; + BVHBuilderMorton::BuildPrim* morton; + unsigned int geomID_ = std::numeric_limits::max(); + }; + + template + struct CreateMortonLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + __forceinline CreateMortonLeaf (Instance* mesh, unsigned int geomID, BVHBuilderMorton::BuildPrim* morton) + : mesh(mesh), morton(morton), geomID_(geomID) {} + + __noinline NodeRecord operator() (const range& current, const FastAllocator::CachedAllocator& alloc) + { + vfloat4 lower(pos_inf); + vfloat4 upper(neg_inf); + size_t items = current.size(); + size_t start = current.begin(); + assert(items <= 1); + + /* allocate leaf node */ + InstancePrimitive* accel = (InstancePrimitive*) alloc.malloc1(items*sizeof(InstancePrimitive),BVH::byteAlignment); + NodeRef ref = BVH::encodeLeaf((char*)accel,items); + const Instance* instance = this->mesh; + + BBox3fa bounds = empty; + for (size_t i=0; ibounds(primID)); + new (&accel[i]) InstancePrimitive(instance, geomID_); + } + + BBox3fx box_o = (BBox3fx&)bounds; +#if ROTATE_TREE + if (N == 4) + box_o.lower.a = current.size(); +#endif + return NodeRecord(ref,box_o); + } + private: + Instance* mesh; + BVHBuilderMorton::BuildPrim* morton; + unsigned int geomID_ = std::numeric_limits::max(); + }; + + template + struct CalculateMeshBounds + { + __forceinline CalculateMeshBounds (Mesh* mesh) + : mesh(mesh) {} + + __forceinline const BBox3fa operator() (const BVHBuilderMorton::BuildPrim& morton) { + return mesh->bounds(morton.index); + } + + private: + Mesh* mesh; + }; + + template + class BVHNMeshBuilderMorton : public Builder + { + typedef BVHN BVH; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecord NodeRecord; + + public: + + BVHNMeshBuilderMorton (BVH* bvh, Mesh* mesh, unsigned int geomID, const size_t minLeafSize, const size_t maxLeafSize, const size_t singleThreadThreshold = DEFAULT_SINGLE_THREAD_THRESHOLD) + : bvh(bvh), mesh(mesh), morton(bvh->device,0), settings(N,BVH::maxBuildDepth,minLeafSize,min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks),singleThreadThreshold), geomID_(geomID) {} + + /* build function */ + void build() + { + /* we reset the allocator when the mesh size changed */ + if (mesh->numPrimitives != numPreviousPrimitives) { + bvh->alloc.clear(); + morton.clear(); + } + size_t numPrimitives = mesh->size(); + numPreviousPrimitives = numPrimitives; + + /* skip build for empty scene */ + if (numPrimitives == 0) { + bvh->set(BVH::emptyNode,empty,0); + return; + } + + /* preallocate arrays */ + morton.resize(numPrimitives); + size_t bytesEstimated = numPrimitives*sizeof(AABBNode)/(4*N) + size_t(1.2f*Primitive::blocks(numPrimitives)*sizeof(Primitive)); + size_t bytesMortonCodes = numPrimitives*sizeof(BVHBuilderMorton::BuildPrim); + bytesEstimated = max(bytesEstimated,bytesMortonCodes); // the first allocation block is reused to sort the morton codes + bvh->alloc.init(bytesMortonCodes,bytesMortonCodes,bytesEstimated); + + /* create morton code array */ + BVHBuilderMorton::BuildPrim* dest = (BVHBuilderMorton::BuildPrim*) bvh->alloc.specialAlloc(bytesMortonCodes); + size_t numPrimitivesGen = createMortonCodeArray(mesh,morton,bvh->scene->progressInterface); + + /* create BVH */ + SetBVHNBounds setBounds(bvh); + CreateMortonLeaf createLeaf(mesh,geomID_,morton.data()); + CalculateMeshBounds calculateBounds(mesh); + auto root = BVHBuilderMorton::build( + typename BVH::CreateAlloc(bvh), + typename BVH::AABBNode::Create(), + setBounds,createLeaf,calculateBounds,bvh->scene->progressInterface, + morton.data(),dest,numPrimitivesGen,settings); + + bvh->set(root.ref,LBBox3fa(root.bounds),numPrimitives); + +#if ROTATE_TREE + if (N == 4) + { + for (int i=0; i::rotate(bvh->root); + bvh->clearBarrier(bvh->root); + } +#endif + + /* clear temporary data for static geometry */ + if (bvh->scene->isStaticAccel()) { + morton.clear(); + } + bvh->cleanup(); + } + + void clear() { + morton.clear(); + } + + private: + BVH* bvh; + Mesh* mesh; + mvector morton; + BVHBuilderMorton::Settings settings; + unsigned int geomID_ = std::numeric_limits::max(); + unsigned int numPreviousPrimitives = 0; + }; + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + Builder* BVH4Triangle4MeshBuilderMortonGeneral (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<4,TriangleMesh,Triangle4> ((BVH4*)bvh,mesh,geomID,4,4); } + Builder* BVH4Triangle4vMeshBuilderMortonGeneral (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<4,TriangleMesh,Triangle4v>((BVH4*)bvh,mesh,geomID,4,4); } + Builder* BVH4Triangle4iMeshBuilderMortonGeneral (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<4,TriangleMesh,Triangle4i>((BVH4*)bvh,mesh,geomID,4,4); } +#if defined(__AVX__) + Builder* BVH8Triangle4MeshBuilderMortonGeneral (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<8,TriangleMesh,Triangle4> ((BVH8*)bvh,mesh,geomID,4,4); } + Builder* BVH8Triangle4vMeshBuilderMortonGeneral (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<8,TriangleMesh,Triangle4v>((BVH8*)bvh,mesh,geomID,4,4); } + Builder* BVH8Triangle4iMeshBuilderMortonGeneral (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<8,TriangleMesh,Triangle4i>((BVH8*)bvh,mesh,geomID,4,4); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH4Quad4vMeshBuilderMortonGeneral (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<4,QuadMesh,Quad4v>((BVH4*)bvh,mesh,geomID,4,4); } +#if defined(__AVX__) + Builder* BVH8Quad4vMeshBuilderMortonGeneral (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<8,QuadMesh,Quad4v>((BVH8*)bvh,mesh,geomID,4,4); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_USER) + Builder* BVH4VirtualMeshBuilderMortonGeneral (void* bvh, UserGeometry* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<4,UserGeometry,Object>((BVH4*)bvh,mesh,geomID,1,BVH4::maxLeafBlocks); } +#if defined(__AVX__) + Builder* BVH8VirtualMeshBuilderMortonGeneral (void* bvh, UserGeometry* mesh, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<8,UserGeometry,Object>((BVH8*)bvh,mesh,geomID,1,BVH4::maxLeafBlocks); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + Builder* BVH4InstanceMeshBuilderMortonGeneral (void* bvh, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<4,Instance,InstancePrimitive>((BVH4*)bvh,mesh,gtype,geomID,1,BVH4::maxLeafBlocks); } +#if defined(__AVX__) + Builder* BVH8InstanceMeshBuilderMortonGeneral (void* bvh, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode) { return new class BVHNMeshBuilderMorton<8,Instance,InstancePrimitive>((BVH8*)bvh,mesh,gtype,geomID,1,BVH4::maxLeafBlocks); } +#endif +#endif + + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_sah.cpp b/thirdparty/embree/kernels/bvh/bvh_builder_sah.cpp new file mode 100644 index 000000000000..cf5b2eb47f83 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_sah.cpp @@ -0,0 +1,640 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh.h" +#include "bvh_builder.h" +#include "../builders/primrefgen.h" +#include "../builders/splitter.h" + +#include "../geometry/linei.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglev_mb.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" +#include "../geometry/subgrid.h" + +#include "../common/state.h" +#include "../../common/algorithms/parallel_for_for.h" +#include "../../common/algorithms/parallel_for_for_prefix_sum.h" + +#define PROFILE 0 +#define PROFILE_RUNS 20 + +namespace embree +{ + namespace isa + { + template + struct CreateLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + + __forceinline CreateLeaf (BVH* bvh) : bvh(bvh) {} + + __forceinline NodeRef operator() (const PrimRef* prims, const range& set, const FastAllocator::CachedAllocator& alloc) const + { + size_t n = set.size(); + size_t items = Primitive::blocks(n); + size_t start = set.begin(); + Primitive* accel = (Primitive*) alloc.malloc1(items*sizeof(Primitive),BVH::byteAlignment); + typename BVH::NodeRef node = BVH::encodeLeaf((char*)accel,items); + for (size_t i=0; iscene); + } + return node; + } + + BVH* bvh; + }; + + + template + struct CreateLeafQuantized + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + + __forceinline CreateLeafQuantized (BVH* bvh) : bvh(bvh) {} + + __forceinline NodeRef operator() (const PrimRef* prims, const range& set, const FastAllocator::CachedAllocator& alloc) const + { + size_t n = set.size(); + size_t items = Primitive::blocks(n); + size_t start = set.begin(); + Primitive* accel = (Primitive*) alloc.malloc1(items*sizeof(Primitive),BVH::byteAlignment); + typename BVH::NodeRef node = BVH::encodeLeaf((char*)accel,items); + for (size_t i=0; iscene); + } + return node; + } + + BVH* bvh; + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + template + struct BVHNBuilderSAH : public Builder + { + typedef BVHN BVH; + typedef typename BVHN::NodeRef NodeRef; + + BVH* bvh; + Scene* scene; + Geometry* mesh; + mvector prims; + GeneralBVHBuilder::Settings settings; + Geometry::GTypeMask gtype_; + unsigned int geomID_ = std::numeric_limits::max (); + bool primrefarrayalloc; + unsigned int numPreviousPrimitives = 0; + + BVHNBuilderSAH (BVH* bvh, Scene* scene, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, + const Geometry::GTypeMask gtype, bool primrefarrayalloc = false) + : bvh(bvh), scene(scene), mesh(nullptr), prims(scene->device,0), + settings(sahBlockSize, minLeafSize, min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), gtype_(gtype), primrefarrayalloc(primrefarrayalloc) {} + + BVHNBuilderSAH (BVH* bvh, Geometry* mesh, unsigned int geomID, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const Geometry::GTypeMask gtype) + : bvh(bvh), scene(nullptr), mesh(mesh), prims(bvh->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), gtype_(gtype), geomID_(geomID), primrefarrayalloc(false) {} + + // FIXME: shrink bvh->alloc in destructor here and in other builders too + + void build() + { + /* we reset the allocator when the mesh size changed */ + if (mesh && mesh->numPrimitives != numPreviousPrimitives) { + bvh->alloc.clear(); + } + + /* if we use the primrefarray for allocations we have to take it back from the BVH */ + if (settings.primrefarrayalloc != size_t(inf)) + bvh->alloc.unshare(prims); + + /* skip build for empty scene */ + const size_t numPrimitives = mesh ? mesh->size() : scene->getNumPrimitives(gtype_,false); + numPreviousPrimitives = numPrimitives; + if (numPrimitives == 0) { + bvh->clear(); + prims.clear(); + return; + } + + double t0 = bvh->preBuild(mesh ? "" : TOSTRING(isa) "::BVH" + toString(N) + "BuilderSAH"); + +#if PROFILE + profile(2,PROFILE_RUNS,numPrimitives,[&] (ProfileTimer& timer) { +#endif + + /* create primref array */ + if (primrefarrayalloc) { + settings.primrefarrayalloc = numPrimitives/1000; + if (settings.primrefarrayalloc < 1000) + settings.primrefarrayalloc = inf; + } + + /* enable os_malloc for two level build */ + if (mesh) + bvh->alloc.setOSallocation(true); + + /* initialize allocator */ + const size_t node_bytes = numPrimitives*sizeof(typename BVH::AABBNodeMB)/(4*N); + const size_t leaf_bytes = size_t(1.2*Primitive::blocks(numPrimitives)*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,numPrimitives,node_bytes+leaf_bytes); + prims.resize(numPrimitives); + + PrimInfo pinfo = mesh ? + createPrimRefArray(mesh,geomID_,prims,bvh->scene->progressInterface) : + createPrimRefArray(scene,gtype_,false,prims,bvh->scene->progressInterface); + + /* pinfo might has zero size due to invalid geometry */ + if (unlikely(pinfo.size() == 0)) + { + bvh->clear(); + prims.clear(); + return; + } + + /* call BVH builder */ + NodeRef root = BVHNBuilderVirtual::build(&bvh->alloc,CreateLeaf(bvh),bvh->scene->progressInterface,prims.data(),pinfo,settings); + bvh->set(root,LBBox3fa(pinfo.geomBounds),pinfo.size()); + bvh->layoutLargeNodes(size_t(pinfo.size()*0.005f)); + +#if PROFILE + }); +#endif + + /* if we allocated using the primrefarray we have to keep it alive */ + if (settings.primrefarrayalloc != size_t(inf)) + bvh->alloc.share(prims); + + /* for static geometries we can do some cleanups */ + else if (scene && scene->isStaticAccel()) { + prims.clear(); + } + bvh->cleanup(); + bvh->postBuild(t0); + } + + void clear() { + prims.clear(); + } + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + template + struct BVHNBuilderSAHQuantized : public Builder + { + typedef BVHN BVH; + typedef typename BVHN::NodeRef NodeRef; + + BVH* bvh; + Scene* scene; + Geometry* mesh; + mvector prims; + GeneralBVHBuilder::Settings settings; + Geometry::GTypeMask gtype_; + unsigned int geomID_ = std::numeric_limits::max(); + unsigned int numPreviousPrimitives = 0; + + BVHNBuilderSAHQuantized (BVH* bvh, Scene* scene, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const Geometry::GTypeMask gtype) + : bvh(bvh), scene(scene), mesh(nullptr), prims(scene->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), gtype_(gtype) {} + + BVHNBuilderSAHQuantized (BVH* bvh, Geometry* mesh, unsigned int geomID, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const Geometry::GTypeMask gtype) + : bvh(bvh), scene(nullptr), mesh(mesh), prims(bvh->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), gtype_(gtype), geomID_(geomID) {} + + // FIXME: shrink bvh->alloc in destructor here and in other builders too + + void build() + { + /* we reset the allocator when the mesh size changed */ + if (mesh && mesh->numPrimitives != numPreviousPrimitives) { + bvh->alloc.clear(); + } + + /* skip build for empty scene */ + const size_t numPrimitives = mesh ? mesh->size() : scene->getNumPrimitives(gtype_,false); + numPreviousPrimitives = numPrimitives; + if (numPrimitives == 0) { + prims.clear(); + bvh->clear(); + return; + } + + double t0 = bvh->preBuild(mesh ? "" : TOSTRING(isa) "::QBVH" + toString(N) + "BuilderSAH"); + +#if PROFILE + profile(2,PROFILE_RUNS,numPrimitives,[&] (ProfileTimer& timer) { +#endif + /* create primref array */ + prims.resize(numPrimitives); + PrimInfo pinfo = mesh ? + createPrimRefArray(mesh,geomID_,prims,bvh->scene->progressInterface) : + createPrimRefArray(scene,gtype_,false,prims,bvh->scene->progressInterface); + + /* enable os_malloc for two level build */ + if (mesh) + bvh->alloc.setOSallocation(true); + + /* call BVH builder */ + const size_t node_bytes = numPrimitives*sizeof(typename BVH::QuantizedNode)/(4*N); + const size_t leaf_bytes = size_t(1.2*Primitive::blocks(numPrimitives)*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,numPrimitives,node_bytes+leaf_bytes); + NodeRef root = BVHNBuilderQuantizedVirtual::build(&bvh->alloc,CreateLeafQuantized(bvh),bvh->scene->progressInterface,prims.data(),pinfo,settings); + bvh->set(root,LBBox3fa(pinfo.geomBounds),pinfo.size()); + //bvh->layoutLargeNodes(pinfo.size()*0.005f); // FIXME: COPY LAYOUT FOR LARGE NODES !!! +#if PROFILE + }); +#endif + + /* clear temporary data for static geometry */ + if (scene && scene->isStaticAccel()) { + prims.clear(); + } + bvh->cleanup(); + bvh->postBuild(t0); + } + + void clear() { + prims.clear(); + } + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + + template + struct CreateLeafGrid + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + + __forceinline CreateLeafGrid (BVH* bvh, const SubGridBuildData * const sgrids) : bvh(bvh),sgrids(sgrids) {} + + __forceinline NodeRef operator() (const PrimRef* prims, const range& set, const FastAllocator::CachedAllocator& alloc) const + { + const size_t items = set.size(); //Primitive::blocks(n); + const size_t start = set.begin(); + + /* collect all subsets with unique geomIDs */ + assert(items <= N); + unsigned int geomIDs[N]; + unsigned int num_geomIDs = 1; + geomIDs[0] = prims[start].geomID(); + + for (size_t i=1;i* accel = (SubGridQBVHN*) alloc.malloc1(num_geomIDs*sizeof(SubGridQBVHN),BVH::byteAlignment); + typename BVH::NodeRef node = BVH::encodeLeaf((char*)accel,num_geomIDs); + + for (size_t g=0;g(x,y,primID,bounds,geomIDs[g],pos); + } + + return node; + } + + BVH* bvh; + const SubGridBuildData * const sgrids; + }; + + + template + struct BVHNBuilderSAHGrid : public Builder + { + typedef BVHN BVH; + typedef typename BVHN::NodeRef NodeRef; + + BVH* bvh; + Scene* scene; + GridMesh* mesh; + mvector prims; + mvector sgrids; + GeneralBVHBuilder::Settings settings; + unsigned int geomID_ = std::numeric_limits::max(); + unsigned int numPreviousPrimitives = 0; + + BVHNBuilderSAHGrid (BVH* bvh, Scene* scene, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const size_t mode) + : bvh(bvh), scene(scene), mesh(nullptr), prims(scene->device,0), sgrids(scene->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD) {} + + BVHNBuilderSAHGrid (BVH* bvh, GridMesh* mesh, unsigned int geomID, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const size_t mode) + : bvh(bvh), scene(nullptr), mesh(mesh), prims(bvh->device,0), sgrids(scene->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), geomID_(geomID) {} + + void build() + { + /* we reset the allocator when the mesh size changed */ + if (mesh && mesh->numPrimitives != numPreviousPrimitives) { + bvh->alloc.clear(); + } + + /* if we use the primrefarray for allocations we have to take it back from the BVH */ + if (settings.primrefarrayalloc != size_t(inf)) + bvh->alloc.unshare(prims); + + const size_t numGridPrimitives = mesh ? mesh->size() : scene->getNumPrimitives(GridMesh::geom_type,false); + numPreviousPrimitives = numGridPrimitives; + + PrimInfo pinfo(empty); + size_t numPrimitives = 0; + + if (!mesh) + { + /* first run to get #primitives */ + + ParallelForForPrefixSumState pstate; + Scene::Iterator iter(scene); + + pstate.init(iter,size_t(1024)); + + /* iterate over all meshes in the scene */ + pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfo(empty), [&](GridMesh* mesh, const range& r, size_t k, size_t geomID) -> PrimInfo { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jvalid(j)) continue; + BBox3fa bounds = empty; + const PrimRef prim(bounds,(unsigned)geomID,(unsigned)j); + if (!mesh->valid(j)) continue; + pinfo.add_center2(prim,mesh->getNumSubGrids(j)); + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + numPrimitives = pinfo.size(); + + /* resize arrays */ + sgrids.resize(numPrimitives); + prims.resize(numPrimitives); + + /* second run to fill primrefs and SubGridBuildData arrays */ + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfo(empty), [&](GridMesh* mesh, const range& r, size_t k, size_t geomID, const PrimInfo& base) -> PrimInfo { + k = base.size(); + size_t p_index = k; + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jvalid(j)) continue; + const GridMesh::Grid &g = mesh->grid(j); + for (unsigned int y=0; ybuildBounds(g,x,y,bounds)) continue; // get bounds of subgrid + const PrimRef prim(bounds,(unsigned)geomID,(unsigned)p_index); + pinfo.add_center2(prim); + sgrids[p_index] = SubGridBuildData(x | g.get3x3FlagsX(x), y | g.get3x3FlagsY(y), unsigned(j)); + prims[p_index++] = prim; + } + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + assert(pinfo.size() == numPrimitives); + } + else + { + ParallelPrefixSumState pstate; + /* iterate over all grids in a single mesh */ + pinfo = parallel_prefix_sum( pstate, size_t(0), mesh->size(), size_t(1024), PrimInfo(empty), [&](const range& r, const PrimInfo& base) -> PrimInfo + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jvalid(j)) continue; + BBox3fa bounds = empty; + const PrimRef prim(bounds,geomID_,unsigned(j)); + pinfo.add_center2(prim,mesh->getNumSubGrids(j)); + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + numPrimitives = pinfo.size(); + /* resize arrays */ + sgrids.resize(numPrimitives); + prims.resize(numPrimitives); + + /* second run to fill primrefs and SubGridBuildData arrays */ + pinfo = parallel_prefix_sum( pstate, size_t(0), mesh->size(), size_t(1024), PrimInfo(empty), [&](const range& r, const PrimInfo& base) -> PrimInfo + { + + size_t p_index = base.size(); + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jvalid(j)) continue; + const GridMesh::Grid &g = mesh->grid(j); + for (unsigned int y=0; ybuildBounds(g,x,y,bounds)) continue; // get bounds of subgrid + const PrimRef prim(bounds,geomID_,unsigned(p_index)); + pinfo.add_center2(prim); + sgrids[p_index] = SubGridBuildData(x | g.get3x3FlagsX(x), y | g.get3x3FlagsY(y), unsigned(j)); + prims[p_index++] = prim; + } + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + } + + /* no primitives */ + if (numPrimitives == 0) { + bvh->clear(); + prims.clear(); + sgrids.clear(); + return; + } + + double t0 = bvh->preBuild(mesh ? "" : TOSTRING(isa) "::BVH" + toString(N) + "BuilderSAH"); + + /* create primref array */ + settings.primrefarrayalloc = numPrimitives/1000; + if (settings.primrefarrayalloc < 1000) + settings.primrefarrayalloc = inf; + + /* enable os_malloc for two level build */ + if (mesh) + bvh->alloc.setOSallocation(true); + + /* initialize allocator */ + const size_t node_bytes = numPrimitives*sizeof(typename BVH::AABBNodeMB)/(4*N); + const size_t leaf_bytes = size_t(1.2*(float)numPrimitives/N * sizeof(SubGridQBVHN)); + + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,numPrimitives,node_bytes+leaf_bytes); + + /* pinfo might has zero size due to invalid geometry */ + if (unlikely(pinfo.size() == 0)) + { + bvh->clear(); + sgrids.clear(); + prims.clear(); + return; + } + + /* call BVH builder */ + NodeRef root = BVHNBuilderVirtual::build(&bvh->alloc,CreateLeafGrid>(bvh,sgrids.data()),bvh->scene->progressInterface,prims.data(),pinfo,settings); + bvh->set(root,LBBox3fa(pinfo.geomBounds),pinfo.size()); + bvh->layoutLargeNodes(size_t(pinfo.size()*0.005f)); + + /* clear temporary array */ + sgrids.clear(); + + /* if we allocated using the primrefarray we have to keep it alive */ + if (settings.primrefarrayalloc != size_t(inf)) + bvh->alloc.share(prims); + + /* for static geometries we can do some cleanups */ + else if (scene && scene->isStaticAccel()) { + prims.clear(); + } + bvh->cleanup(); + bvh->postBuild(t0); + } + + void clear() { + prims.clear(); + } + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + Builder* BVH4Triangle4MeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<4,Triangle4>((BVH4*)bvh,mesh,geomID,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH4Triangle4vMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<4,Triangle4v>((BVH4*)bvh,mesh,geomID,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH4Triangle4iMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<4,Triangle4i>((BVH4*)bvh,mesh,geomID,4,1.0f,4,inf,TriangleMesh::geom_type); } + + Builder* BVH4Triangle4SceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<4,Triangle4>((BVH4*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH4Triangle4vSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<4,Triangle4v>((BVH4*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH4Triangle4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<4,Triangle4i>((BVH4*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type,true); } + + + Builder* BVH4QuantizedTriangle4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<4,Triangle4i>((BVH4*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } +#if defined(__AVX__) + Builder* BVH8Triangle4MeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<8,Triangle4>((BVH8*)bvh,mesh,geomID,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH8Triangle4vMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<8,Triangle4v>((BVH8*)bvh,mesh,geomID,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH8Triangle4iMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<8,Triangle4i>((BVH8*)bvh,mesh,geomID,4,1.0f,4,inf,TriangleMesh::geom_type); } + + Builder* BVH8Triangle4SceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<8,Triangle4>((BVH8*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH8Triangle4vSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<8,Triangle4v>((BVH8*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH8Triangle4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<8,Triangle4i>((BVH8*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type,true); } + Builder* BVH8QuantizedTriangle4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<8,Triangle4i>((BVH8*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } + Builder* BVH8QuantizedTriangle4SceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<8,Triangle4>((BVH8*)bvh,scene,4,1.0f,4,inf,TriangleMesh::geom_type); } + +#endif +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH4Quad4vMeshBuilderSAH (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<4,Quad4v>((BVH4*)bvh,mesh,geomID,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH4Quad4iMeshBuilderSAH (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<4,Quad4i>((BVH4*)bvh,mesh,geomID,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH4Quad4vSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<4,Quad4v>((BVH4*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH4Quad4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<4,Quad4i>((BVH4*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type,true); } + Builder* BVH4QuantizedQuad4vSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<4,Quad4v>((BVH4*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH4QuantizedQuad4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<4,Quad4i>((BVH4*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type); } + +#if defined(__AVX__) + Builder* BVH8Quad4vSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<8,Quad4v>((BVH8*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH8Quad4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAH<8,Quad4i>((BVH8*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type,true); } + Builder* BVH8QuantizedQuad4vSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<8,Quad4v>((BVH8*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH8QuantizedQuad4iSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHQuantized<8,Quad4i>((BVH8*)bvh,scene,4,1.0f,4,inf,QuadMesh::geom_type); } + Builder* BVH8Quad4vMeshBuilderSAH (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAH<8,Quad4v>((BVH8*)bvh,mesh,geomID,4,1.0f,4,inf,QuadMesh::geom_type); } + +#endif +#endif + +#if defined(EMBREE_GEOMETRY_USER) + + Builder* BVH4VirtualSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { + int minLeafSize = scene->device->object_accel_min_leaf_size; + int maxLeafSize = scene->device->object_accel_max_leaf_size; + return new BVHNBuilderSAH<4,Object>((BVH4*)bvh,scene,4,1.0f,minLeafSize,maxLeafSize,UserGeometry::geom_type); + } + + Builder* BVH4VirtualMeshBuilderSAH (void* bvh, UserGeometry* mesh, unsigned int geomID, size_t mode) { + return new BVHNBuilderSAH<4,Object>((BVH4*)bvh,mesh,geomID,4,1.0f,1,inf,UserGeometry::geom_type); + } +#if defined(__AVX__) + + Builder* BVH8VirtualSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { + int minLeafSize = scene->device->object_accel_min_leaf_size; + int maxLeafSize = scene->device->object_accel_max_leaf_size; + return new BVHNBuilderSAH<8,Object>((BVH8*)bvh,scene,8,1.0f,minLeafSize,maxLeafSize,UserGeometry::geom_type); + } + + Builder* BVH8VirtualMeshBuilderSAH (void* bvh, UserGeometry* mesh, unsigned int geomID, size_t mode) { + return new BVHNBuilderSAH<8,Object>((BVH8*)bvh,mesh,geomID,8,1.0f,1,inf,UserGeometry::geom_type); + } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + Builder* BVH4InstanceSceneBuilderSAH (void* bvh, Scene* scene, Geometry::GTypeMask gtype) { return new BVHNBuilderSAH<4,InstancePrimitive>((BVH4*)bvh,scene,4,1.0f,1,1,gtype); } + Builder* BVH4InstanceMeshBuilderSAH (void* bvh, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode) { + return new BVHNBuilderSAH<4,InstancePrimitive>((BVH4*)bvh,mesh,geomID,4,1.0f,1,inf,gtype); + } +#if defined(__AVX__) + Builder* BVH8InstanceSceneBuilderSAH (void* bvh, Scene* scene, Geometry::GTypeMask gtype) { return new BVHNBuilderSAH<8,InstancePrimitive>((BVH8*)bvh,scene,8,1.0f,1,1,gtype); } + Builder* BVH8InstanceMeshBuilderSAH (void* bvh, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode) { + return new BVHNBuilderSAH<8,InstancePrimitive>((BVH8*)bvh,mesh,geomID,8,1.0f,1,inf,gtype); + } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_GRID) + Builder* BVH4GridMeshBuilderSAH (void* bvh, GridMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAHGrid<4>((BVH4*)bvh,mesh,geomID,4,1.0f,4,4,mode); } + Builder* BVH4GridSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHGrid<4>((BVH4*)bvh,scene,4,1.0f,4,4,mode); } // FIXME: check whether cost factors are correct + +#if defined(__AVX__) + Builder* BVH8GridMeshBuilderSAH (void* bvh, GridMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNBuilderSAHGrid<8>((BVH8*)bvh,mesh,geomID,8,1.0f,8,8,mode); } + Builder* BVH8GridSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderSAHGrid<8>((BVH8*)bvh,scene,8,1.0f,8,8,mode); } // FIXME: check whether cost factors are correct +#endif +#endif + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_sah_mb.cpp b/thirdparty/embree/kernels/bvh/bvh_builder_sah_mb.cpp new file mode 100644 index 000000000000..9c01553ec692 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_sah_mb.cpp @@ -0,0 +1,705 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh.h" +#include "bvh_builder.h" +#include "../builders/bvh_builder_msmblur.h" + +#include "../builders/primrefgen.h" +#include "../builders/splitter.h" + +#include "../geometry/linei.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglev_mb.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" +#include "../geometry/subgrid.h" + +#include "../common/state.h" + +// FIXME: remove after removing BVHNBuilderMBlurRootTimeSplitsSAH +#include "../../common/algorithms/parallel_for_for.h" +#include "../../common/algorithms/parallel_for_for_prefix_sum.h" + + +namespace embree +{ + namespace isa + { + +#if 0 + template + struct CreateMBlurLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecordMB NodeRecordMB; + + __forceinline CreateMBlurLeaf (BVH* bvh, PrimRef* prims, size_t time) : bvh(bvh), prims(prims), time(time) {} + + __forceinline NodeRecordMB operator() (const PrimRef* prims, const range& set, const FastAllocator::CachedAllocator& alloc) const + { + size_t items = Primitive::blocks(set.size()); + size_t start = set.begin(); + for (size_t i=start; iencodeLeaf((char*)accel,items); + + LBBox3fa allBounds = empty; + for (size_t i=0; iscene, time)); + + return NodeRecordMB(node,allBounds); + } + + BVH* bvh; + PrimRef* prims; + size_t time; + }; +#endif + + template + struct CreateMSMBlurLeaf + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecordMB4D NodeRecordMB4D; + + __forceinline CreateMSMBlurLeaf (BVH* bvh) : bvh(bvh) {} + + __forceinline const NodeRecordMB4D operator() (const BVHBuilderMSMBlur::BuildRecord& current, const FastAllocator::CachedAllocator& alloc) const + { + size_t items = Primitive::blocks(current.prims.size()); + size_t start = current.prims.begin(); + size_t end = current.prims.end(); + for (size_t i=start; iencodeLeaf((char*)accel,items); + LBBox3fa allBounds = empty; + for (size_t i=0; idata(), start, current.prims.end(), bvh->scene, current.prims.time_range)); + return NodeRecordMB4D(node,allBounds,current.prims.time_range); + } + + BVH* bvh; + }; + + /* Motion blur BVH with 4D nodes and internal time splits */ + template + struct BVHNBuilderMBlurSAH : public Builder + { + typedef BVHN BVH; + typedef typename BVHN::NodeRef NodeRef; + typedef typename BVHN::NodeRecordMB NodeRecordMB; + typedef typename BVHN::AABBNodeMB AABBNodeMB; + + BVH* bvh; + Scene* scene; + const size_t sahBlockSize; + const float intCost; + const size_t minLeafSize; + const size_t maxLeafSize; + const Geometry::GTypeMask gtype_; + + BVHNBuilderMBlurSAH (BVH* bvh, Scene* scene, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const Geometry::GTypeMask gtype) + : bvh(bvh), scene(scene), sahBlockSize(sahBlockSize), intCost(intCost), minLeafSize(minLeafSize), maxLeafSize(min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks)), gtype_(gtype) {} + + void build() + { + /* skip build for empty scene */ + const size_t numPrimitives = scene->getNumPrimitives(gtype_,true); + if (numPrimitives == 0) { bvh->clear(); return; } + + double t0 = bvh->preBuild(TOSTRING(isa) "::BVH" + toString(N) + "BuilderMBlurSAH"); + +#if PROFILE + profile(2,PROFILE_RUNS,numPrimitives,[&] (ProfileTimer& timer) { +#endif + + //const size_t numTimeSteps = scene->getNumTimeSteps(); + //const size_t numTimeSegments = numTimeSteps-1; assert(numTimeSteps > 1); + + /*if (numTimeSegments == 1) + buildSingleSegment(numPrimitives); + else*/ + buildMultiSegment(numPrimitives); + +#if PROFILE + }); +#endif + + /* clear temporary data for static geometry */ + bvh->cleanup(); + bvh->postBuild(t0); + } + +#if 0 // No longer compatible when time_ranges are present for geometries. Would have to create temporal nodes sometimes, and put only a single geometry into leaf. + void buildSingleSegment(size_t numPrimitives) + { + /* create primref array */ + mvector prims(scene->device,numPrimitives); + const PrimInfo pinfo = createPrimRefArrayMBlur(scene,gtype_,prims,bvh->scene->progressInterface,0); + /* early out if no valid primitives */ + if (pinfo.size() == 0) { bvh->clear(); return; } + /* estimate acceleration structure size */ + const size_t node_bytes = pinfo.size()*sizeof(AABBNodeMB)/(4*N); + const size_t leaf_bytes = size_t(1.2*Primitive::blocks(pinfo.size())*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + + /* settings for BVH build */ + GeneralBVHBuilder::Settings settings; + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + settings.logBlockSize = bsr(sahBlockSize); + settings.minLeafSize = min(minLeafSize,maxLeafSize); + settings.maxLeafSize = maxLeafSize; + settings.travCost = travCost; + settings.intCost = intCost; + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,pinfo.size(),node_bytes+leaf_bytes); + + /* build hierarchy */ + auto root = BVHBuilderBinnedSAH::build + (typename BVH::CreateAlloc(bvh),typename BVH::AABBNodeMB::Create(),typename BVH::AABBNodeMB::Set(), + CreateMBlurLeaf(bvh,prims.data(),0),bvh->scene->progressInterface, + prims.data(),pinfo,settings); + + bvh->set(root.ref,root.lbounds,pinfo.size()); + } +#endif + + void buildMultiSegment(size_t numPrimitives) + { + /* create primref array */ + mvector prims(scene->device,numPrimitives); + PrimInfoMB pinfo = createPrimRefArrayMSMBlur(scene,gtype_,prims,bvh->scene->progressInterface); + + /* early out if no valid primitives */ + if (pinfo.size() == 0) { bvh->clear(); return; } + + /* estimate acceleration structure size */ + const size_t node_bytes = pinfo.num_time_segments*sizeof(AABBNodeMB)/(4*N); + const size_t leaf_bytes = size_t(1.2*Primitive::blocks(pinfo.num_time_segments)*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + + /* settings for BVH build */ + BVHBuilderMSMBlur::Settings settings; + settings.branchingFactor = N; + settings.maxDepth = BVH::maxDepth; + settings.logBlockSize = bsr(sahBlockSize); + settings.minLeafSize = min(minLeafSize,maxLeafSize); + settings.maxLeafSize = maxLeafSize; + settings.travCost = travCost; + settings.intCost = intCost; + settings.singleLeafTimeSegment = Primitive::singleTimeSegment; + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,pinfo.size(),node_bytes+leaf_bytes); + + /* build hierarchy */ + auto root = + BVHBuilderMSMBlur::build(prims,pinfo,scene->device, + RecalculatePrimRef(scene), + typename BVH::CreateAlloc(bvh), + typename BVH::AABBNodeMB4D::Create(), + typename BVH::AABBNodeMB4D::Set(), + CreateMSMBlurLeaf(bvh), + bvh->scene->progressInterface, + settings); + + bvh->set(root.ref,root.lbounds,pinfo.num_time_segments); + } + + void clear() { + } + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + struct GridRecalculatePrimRef + { + Scene* scene; + const SubGridBuildData * const sgrids; + + __forceinline GridRecalculatePrimRef (Scene* scene, const SubGridBuildData * const sgrids) + : scene(scene), sgrids(sgrids) {} + + __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range) const + { + const unsigned int geomID = prim.geomID(); + const GridMesh* mesh = scene->get(geomID); + const unsigned int buildID = prim.primID(); + const SubGridBuildData &subgrid = sgrids[buildID]; + const unsigned int primID = subgrid.primID; + const size_t x = subgrid.x(); + const size_t y = subgrid.y(); + const LBBox3fa lbounds = mesh->linearBounds(mesh->grid(primID),x,y,time_range); + const unsigned num_time_segments = mesh->numTimeSegments(); + const range tbounds = mesh->timeSegmentRange(time_range); + return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, num_time_segments, geomID, buildID); + } + + __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range) const { + const unsigned int geomID = prim.geomID(); + const GridMesh* mesh = scene->get(geomID); + const unsigned int buildID = prim.primID(); + const SubGridBuildData &subgrid = sgrids[buildID]; + const unsigned int primID = subgrid.primID; + const size_t x = subgrid.x(); + const size_t y = subgrid.y(); + return mesh->linearBounds(mesh->grid(primID),x,y,time_range); + } + + }; + + template + struct CreateMSMBlurLeafGrid + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecordMB4D NodeRecordMB4D; + + __forceinline CreateMSMBlurLeafGrid (Scene* scene, BVH* bvh, const SubGridBuildData * const sgrids) : scene(scene), bvh(bvh), sgrids(sgrids) {} + + __forceinline const NodeRecordMB4D operator() (const BVHBuilderMSMBlur::BuildRecord& current, const FastAllocator::CachedAllocator& alloc) const + { + const size_t items = current.prims.size(); + const size_t start = current.prims.begin(); + + const PrimRefMB* prims = current.prims.prims->data(); + /* collect all subsets with unique geomIDs */ + assert(items <= N); + unsigned int geomIDs[N]; + unsigned int num_geomIDs = 1; + geomIDs[0] = prims[start].geomID(); + + for (size_t i=1;i* accel = (SubGridMBQBVHN*) alloc.malloc1(num_geomIDs*sizeof(SubGridMBQBVHN),BVH::byteAlignment); + typename BVH::NodeRef node = bvh->encodeLeaf((char*)accel,num_geomIDs); + + LBBox3fa allBounds = empty; + + for (size_t g=0;gget(geomIDs[g]); + unsigned int x[N]; + unsigned int y[N]; + unsigned int primID[N]; + BBox3fa bounds0[N]; + BBox3fa bounds1[N]; + unsigned int pos = 0; + for (size_t i=0;ilinearBounds(mesh->grid(sgrid_bd.primID),x,y,current.prims.time_range); + allBounds.extend(newBounds); + bounds0[pos] = newBounds.bounds0; + bounds1[pos] = newBounds.bounds1; + pos++; + } + assert(pos <= N); + new (&accel[g]) SubGridMBQBVHN(x,y,primID,bounds0,bounds1,geomIDs[g],current.prims.time_range.lower,1.0f/current.prims.time_range.size(),pos); + } + return NodeRecordMB4D(node,allBounds,current.prims.time_range); + } + + Scene *scene; + BVH* bvh; + const SubGridBuildData * const sgrids; + }; + +#if 0 + template + struct CreateLeafGridMB + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::NodeRecordMB NodeRecordMB; + + __forceinline CreateLeafGridMB (Scene* scene, BVH* bvh, const SubGridBuildData * const sgrids) + : scene(scene), bvh(bvh), sgrids(sgrids) {} + + __forceinline NodeRecordMB operator() (const PrimRef* prims, const range& set, const FastAllocator::CachedAllocator& alloc) const + { + const size_t items = set.size(); + const size_t start = set.begin(); + + /* collect all subsets with unique geomIDs */ + assert(items <= N); + unsigned int geomIDs[N]; + unsigned int num_geomIDs = 1; + geomIDs[0] = prims[start].geomID(); + + for (size_t i=1;i* accel = (SubGridMBQBVHN*) alloc.malloc1(num_geomIDs*sizeof(SubGridMBQBVHN),BVH::byteAlignment); + typename BVH::NodeRef node = bvh->encodeLeaf((char*)accel,num_geomIDs); + + LBBox3fa allBounds = empty; + + for (size_t g=0;gget(geomIDs[g]); + + unsigned int x[N]; + unsigned int y[N]; + unsigned int primID[N]; + BBox3fa bounds0[N]; + BBox3fa bounds1[N]; + unsigned int pos = 0; + for (size_t i=0;ibuildBounds(mesh->grid(sgrid_bd.primID),x,y,0,bounds0[pos]); + bool MAYBE_UNUSED valid1 = mesh->buildBounds(mesh->grid(sgrid_bd.primID),x,y,1,bounds1[pos]); + assert(valid0); + assert(valid1); + allBounds.extend(LBBox3fa(bounds0[pos],bounds1[pos])); + pos++; + } + new (&accel[g]) SubGridMBQBVHN(x,y,primID,bounds0,bounds1,geomIDs[g],0.0f,1.0f,pos); + } + return NodeRecordMB(node,allBounds); + } + + Scene *scene; + BVH* bvh; + const SubGridBuildData * const sgrids; + }; +#endif + + + /* Motion blur BVH with 4D nodes and internal time splits */ + template + struct BVHNBuilderMBlurSAHGrid : public Builder + { + typedef BVHN BVH; + typedef typename BVHN::NodeRef NodeRef; + typedef typename BVHN::NodeRecordMB NodeRecordMB; + typedef typename BVHN::AABBNodeMB AABBNodeMB; + + BVH* bvh; + Scene* scene; + const size_t sahBlockSize; + const float intCost; + const size_t minLeafSize; + const size_t maxLeafSize; + mvector sgrids; + + + BVHNBuilderMBlurSAHGrid (BVH* bvh, Scene* scene, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize) + : bvh(bvh), scene(scene), sahBlockSize(sahBlockSize), intCost(intCost), minLeafSize(minLeafSize), maxLeafSize(min(maxLeafSize,BVH::maxLeafBlocks)), sgrids(scene->device,0) {} + + + PrimInfo createPrimRefArrayMBlurGrid(Scene* scene, mvector& prims, BuildProgressMonitor& progressMonitor, size_t itime) + { + /* first run to get #primitives */ + ParallelForForPrefixSumState pstate; + Scene::Iterator iter(scene); + + pstate.init(iter,size_t(1024)); + + /* iterate over all meshes in the scene */ + PrimInfo pinfo = parallel_for_for_prefix_sum0( pstate, iter, PrimInfo(empty), [&](GridMesh* mesh, const range& r, size_t k, size_t geomID) -> PrimInfo { + + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jvalid(j,range(0,1))) continue; + BBox3fa bounds = empty; + const PrimRef prim(bounds,unsigned(geomID),unsigned(j)); + pinfo.add_center2(prim,mesh->getNumSubGrids(j)); + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + size_t numPrimitives = pinfo.size(); + if (numPrimitives == 0) return pinfo; + + /* resize arrays */ + sgrids.resize(numPrimitives); + prims.resize(numPrimitives); + + /* second run to fill primrefs and SubGridBuildData arrays */ + pinfo = parallel_for_for_prefix_sum1( pstate, iter, PrimInfo(empty), [&](GridMesh* mesh, const range& r, size_t k, size_t geomID, const PrimInfo& base) -> PrimInfo { + + k = base.size(); + size_t p_index = k; + PrimInfo pinfo(empty); + for (size_t j=r.begin(); jgrid(j); + if (!mesh->valid(j,range(0,1))) continue; + + for (unsigned int y=0; ybuildBounds(g,x,y,itime,bounds)) continue; // get bounds of subgrid + const PrimRef prim(bounds,unsigned(geomID),unsigned(p_index)); + pinfo.add_center2(prim); + sgrids[p_index] = SubGridBuildData(x | g.get3x3FlagsX(x), y | g.get3x3FlagsY(y), unsigned(j)); + prims[p_index++] = prim; + } + } + return pinfo; + }, [](const PrimInfo& a, const PrimInfo& b) -> PrimInfo { return PrimInfo::merge(a,b); }); + + assert(pinfo.size() == numPrimitives); + return pinfo; + } + + PrimInfoMB createPrimRefArrayMSMBlurGrid(Scene* scene, mvector& prims, BuildProgressMonitor& progressMonitor, BBox1f t0t1 = BBox1f(0.0f,1.0f)) + { + /* first run to get #primitives */ + ParallelForForPrefixSumState pstate; + Scene::Iterator iter(scene); + + pstate.init(iter,size_t(1024)); + /* iterate over all meshes in the scene */ + PrimInfoMB pinfoMB = parallel_for_for_prefix_sum0( pstate, iter, PrimInfoMB(empty), [&](GridMesh* mesh, const range& r, size_t k, size_t /*geomID*/) -> PrimInfoMB { + + PrimInfoMB pinfoMB(empty); + for (size_t j=r.begin(); jvalid(j, mesh->timeSegmentRange(t0t1))) continue; + LBBox3fa bounds(empty); + PrimInfoMB gridMB(0,mesh->getNumSubGrids(j)); + pinfoMB.merge(gridMB); + } + return pinfoMB; + }, [](const PrimInfoMB& a, const PrimInfoMB& b) -> PrimInfoMB { return PrimInfoMB::merge2(a,b); }); + + size_t numPrimitives = pinfoMB.size(); + if (numPrimitives == 0) return pinfoMB; + + /* resize arrays */ + sgrids.resize(numPrimitives); + prims.resize(numPrimitives); + /* second run to fill primrefs and SubGridBuildData arrays */ + pinfoMB = parallel_for_for_prefix_sum1( pstate, iter, PrimInfoMB(empty), [&](GridMesh* mesh, const range& r, size_t k, size_t geomID, const PrimInfoMB& base) -> PrimInfoMB { + + k = base.size(); + size_t p_index = k; + PrimInfoMB pinfoMB(empty); + for (size_t j=r.begin(); jvalid(j, mesh->timeSegmentRange(t0t1))) continue; + const GridMesh::Grid &g = mesh->grid(j); + + for (unsigned int y=0; ylinearBounds(g,x,y,t0t1),mesh->numTimeSegments(),mesh->time_range,mesh->numTimeSegments(),unsigned(geomID),unsigned(p_index)); + pinfoMB.add_primref(prim); + sgrids[p_index] = SubGridBuildData(x | g.get3x3FlagsX(x), y | g.get3x3FlagsY(y), unsigned(j)); + prims[p_index++] = prim; + } + } + return pinfoMB; + }, [](const PrimInfoMB& a, const PrimInfoMB& b) -> PrimInfoMB { return PrimInfoMB::merge2(a,b); }); + + assert(pinfoMB.size() == numPrimitives); + pinfoMB.time_range = t0t1; + return pinfoMB; + } + + void build() + { + /* skip build for empty scene */ + const size_t numPrimitives = scene->getNumPrimitives(GridMesh::geom_type,true); + if (numPrimitives == 0) { bvh->clear(); return; } + + double t0 = bvh->preBuild(TOSTRING(isa) "::BVH" + toString(N) + "BuilderMBlurSAHGrid"); + + //const size_t numTimeSteps = scene->getNumTimeSteps(); + //const size_t numTimeSegments = numTimeSteps-1; assert(numTimeSteps > 1); + //if (numTimeSegments == 1) + // buildSingleSegment(numPrimitives); + //else + buildMultiSegment(numPrimitives); + + /* clear temporary data for static geometry */ + bvh->cleanup(); + bvh->postBuild(t0); + } + +#if 0 + void buildSingleSegment(size_t numPrimitives) + { + /* create primref array */ + mvector prims(scene->device,numPrimitives); + const PrimInfo pinfo = createPrimRefArrayMBlurGrid(scene,prims,bvh->scene->progressInterface,0); + /* early out if no valid primitives */ + if (pinfo.size() == 0) { bvh->clear(); return; } + + /* estimate acceleration structure size */ + const size_t node_bytes = pinfo.size()*sizeof(AABBNodeMB)/(4*N); + //TODO: check leaf_bytes + const size_t leaf_bytes = size_t(1.2*(float)numPrimitives/N * sizeof(SubGridQBVHN)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + + /* settings for BVH build */ + GeneralBVHBuilder::Settings settings; + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + settings.logBlockSize = bsr(sahBlockSize); + settings.minLeafSize = min(minLeafSize,maxLeafSize); + settings.maxLeafSize = maxLeafSize; + settings.travCost = travCost; + settings.intCost = intCost; + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,pinfo.size(),node_bytes+leaf_bytes); + + /* build hierarchy */ + auto root = BVHBuilderBinnedSAH::build + (typename BVH::CreateAlloc(bvh), + typename BVH::AABBNodeMB::Create(), + typename BVH::AABBNodeMB::Set(), + CreateLeafGridMB(scene,bvh,sgrids.data()), + bvh->scene->progressInterface, + prims.data(),pinfo,settings); + + bvh->set(root.ref,root.lbounds,pinfo.size()); + } +#endif + + void buildMultiSegment(size_t numPrimitives) + { + /* create primref array */ + mvector prims(scene->device,numPrimitives); + PrimInfoMB pinfo = createPrimRefArrayMSMBlurGrid(scene,prims,bvh->scene->progressInterface); + + /* early out if no valid primitives */ + if (pinfo.size() == 0) { bvh->clear(); return; } + + + + GridRecalculatePrimRef recalculatePrimRef(scene,sgrids.data()); + + /* estimate acceleration structure size */ + const size_t node_bytes = pinfo.num_time_segments*sizeof(AABBNodeMB)/(4*N); + //FIXME: check leaf_bytes + //const size_t leaf_bytes = size_t(1.2*Primitive::blocks(pinfo.num_time_segments)*sizeof(SubGridQBVHN)); + const size_t leaf_bytes = size_t(1.2*(float)numPrimitives/N * sizeof(SubGridQBVHN)); + + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + + /* settings for BVH build */ + BVHBuilderMSMBlur::Settings settings; + settings.branchingFactor = N; + settings.maxDepth = BVH::maxDepth; + settings.logBlockSize = bsr(sahBlockSize); + settings.minLeafSize = min(minLeafSize,maxLeafSize); + settings.maxLeafSize = maxLeafSize; + settings.travCost = travCost; + settings.intCost = intCost; + settings.singleLeafTimeSegment = false; + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,pinfo.size(),node_bytes+leaf_bytes); + + /* build hierarchy */ + auto root = + BVHBuilderMSMBlur::build(prims,pinfo,scene->device, + recalculatePrimRef, + typename BVH::CreateAlloc(bvh), + typename BVH::AABBNodeMB4D::Create(), + typename BVH::AABBNodeMB4D::Set(), + CreateMSMBlurLeafGrid(scene,bvh,sgrids.data()), + bvh->scene->progressInterface, + settings); + bvh->set(root.ref,root.lbounds,pinfo.num_time_segments); + } + + void clear() { + } + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + Builder* BVH4Triangle4iMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAH<4,TriangleMesh,Triangle4i>((BVH4*)bvh,scene,4,1.0f,4,inf,Geometry::MTY_TRIANGLE_MESH); } + Builder* BVH4Triangle4vMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAH<4,TriangleMesh,Triangle4vMB>((BVH4*)bvh,scene,4,1.0f,4,inf,Geometry::MTY_TRIANGLE_MESH); } +#if defined(__AVX__) + Builder* BVH8Triangle4iMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAH<8,TriangleMesh,Triangle4i>((BVH8*)bvh,scene,4,1.0f,4,inf,Geometry::MTY_TRIANGLE_MESH); } + Builder* BVH8Triangle4vMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAH<8,TriangleMesh,Triangle4vMB>((BVH8*)bvh,scene,4,1.0f,4,inf,Geometry::MTY_TRIANGLE_MESH); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH4Quad4iMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAH<4,QuadMesh,Quad4i>((BVH4*)bvh,scene,4,1.0f,4,inf,Geometry::MTY_QUAD_MESH); } +#if defined(__AVX__) + Builder* BVH8Quad4iMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAH<8,QuadMesh,Quad4i>((BVH8*)bvh,scene,4,1.0f,4,inf,Geometry::MTY_QUAD_MESH); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_USER) + Builder* BVH4VirtualMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { + int minLeafSize = scene->device->object_accel_mb_min_leaf_size; + int maxLeafSize = scene->device->object_accel_mb_max_leaf_size; + return new BVHNBuilderMBlurSAH<4,UserGeometry,Object>((BVH4*)bvh,scene,4,1.0f,minLeafSize,maxLeafSize,Geometry::MTY_USER_GEOMETRY); + } +#if defined(__AVX__) + Builder* BVH8VirtualMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { + int minLeafSize = scene->device->object_accel_mb_min_leaf_size; + int maxLeafSize = scene->device->object_accel_mb_max_leaf_size; + return new BVHNBuilderMBlurSAH<8,UserGeometry,Object>((BVH8*)bvh,scene,8,1.0f,minLeafSize,maxLeafSize,Geometry::MTY_USER_GEOMETRY); + } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + Builder* BVH4InstanceMBSceneBuilderSAH (void* bvh, Scene* scene, Geometry::GTypeMask gtype) { return new BVHNBuilderMBlurSAH<4,Instance,InstancePrimitive>((BVH4*)bvh,scene,4,1.0f,1,1,gtype); } +#if defined(__AVX__) + Builder* BVH8InstanceMBSceneBuilderSAH (void* bvh, Scene* scene, Geometry::GTypeMask gtype) { return new BVHNBuilderMBlurSAH<8,Instance,InstancePrimitive>((BVH8*)bvh,scene,8,1.0f,1,1,gtype); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_GRID) + Builder* BVH4GridMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAHGrid<4>((BVH4*)bvh,scene,4,1.0f,4,4); } +#if defined(__AVX__) + Builder* BVH8GridMBSceneBuilderSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderMBlurSAHGrid<8>((BVH8*)bvh,scene,8,1.0f,8,8); } +#endif +#endif + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_sah_spatial.cpp b/thirdparty/embree/kernels/bvh/bvh_builder_sah_spatial.cpp new file mode 100644 index 000000000000..285b38c39d6d --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_sah_spatial.cpp @@ -0,0 +1,201 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh.h" +#include "bvh_builder.h" + +#include "../builders/primrefgen.h" +#include "../builders/primrefgen_presplit.h" +#include "../builders/splitter.h" + +#include "../geometry/linei.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglev_mb.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" +#include "../geometry/subgrid.h" + +#include "../common/state.h" + +namespace embree +{ + namespace isa + { + template + struct CreateLeafSpatial + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + + __forceinline CreateLeafSpatial (BVH* bvh) : bvh(bvh) {} + + __forceinline NodeRef operator() (const PrimRef* prims, const range& set, const FastAllocator::CachedAllocator& alloc) const + { + size_t n = set.size(); + size_t items = Primitive::blocks(n); + size_t start = set.begin(); + Primitive* accel = (Primitive*) alloc.malloc1(items*sizeof(Primitive),BVH::byteAlignment); + typename BVH::NodeRef node = BVH::encodeLeaf((char*)accel,items); + for (size_t i=0; iscene); + } + return node; + } + + BVH* bvh; + }; + + template + struct BVHNBuilderFastSpatialSAH : public Builder + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + BVH* bvh; + Scene* scene; + Mesh* mesh; + mvector prims0; + GeneralBVHBuilder::Settings settings; + const float splitFactor; + unsigned int geomID_ = std::numeric_limits::max(); + unsigned int numPreviousPrimitives = 0; + + BVHNBuilderFastSpatialSAH (BVH* bvh, Scene* scene, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const size_t mode) + : bvh(bvh), scene(scene), mesh(nullptr), prims0(scene->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), + splitFactor(scene->device->max_spatial_split_replications) {} + + BVHNBuilderFastSpatialSAH (BVH* bvh, Mesh* mesh, const unsigned int geomID, const size_t sahBlockSize, const float intCost, const size_t minLeafSize, const size_t maxLeafSize, const size_t mode) + : bvh(bvh), scene(nullptr), mesh(mesh), prims0(bvh->device,0), settings(sahBlockSize, minLeafSize, min(maxLeafSize,Primitive::max_size()*BVH::maxLeafBlocks), travCost, intCost, DEFAULT_SINGLE_THREAD_THRESHOLD), + splitFactor(scene->device->max_spatial_split_replications), geomID_(geomID) {} + + // FIXME: shrink bvh->alloc in destructor here and in other builders too + + void build() + { + /* we reset the allocator when the mesh size changed */ + if (mesh && mesh->numPrimitives != numPreviousPrimitives) { + bvh->alloc.clear(); + } + + /* skip build for empty scene */ + const size_t numOriginalPrimitives = mesh ? mesh->size() : scene->getNumPrimitives(Mesh::geom_type,false); + numPreviousPrimitives = numOriginalPrimitives; + if (numOriginalPrimitives == 0) { + prims0.clear(); + bvh->clear(); + return; + } + + const unsigned int maxGeomID = mesh ? geomID_ : scene->getMaxGeomID(); + const bool usePreSplits = scene->device->useSpatialPreSplits || (maxGeomID >= ((unsigned int)1 << (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS))); + double t0 = bvh->preBuild(mesh ? "" : TOSTRING(isa) "::BVH" + toString(N) + (usePreSplits ? "BuilderFastSpatialPresplitSAH" : "BuilderFastSpatialSAH")); + + /* create primref array */ + const size_t numSplitPrimitives = max(numOriginalPrimitives,size_t(splitFactor*numOriginalPrimitives)); + prims0.resize(numSplitPrimitives); + + /* enable os_malloc for two level build */ + if (mesh) + bvh->alloc.setOSallocation(true); + + NodeRef root(0); + PrimInfo pinfo; + + + if (likely(usePreSplits)) + { + /* spatial presplit SAH BVH builder */ + pinfo = mesh ? + createPrimRefArray_presplit(mesh,maxGeomID,numOriginalPrimitives,prims0,bvh->scene->progressInterface) : + createPrimRefArray_presplit(scene,Mesh::geom_type,false,numOriginalPrimitives,prims0,bvh->scene->progressInterface); + + const size_t node_bytes = pinfo.size()*sizeof(typename BVH::AABBNode)/(4*N); + const size_t leaf_bytes = size_t(1.2*Primitive::blocks(pinfo.size())*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,pinfo.size(),node_bytes+leaf_bytes); + + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + + /* call BVH builder */ + root = BVHNBuilderVirtual::build(&bvh->alloc,CreateLeafSpatial(bvh),bvh->scene->progressInterface,prims0.data(),pinfo,settings); + } + else + { + /* standard spatial split SAH BVH builder */ + pinfo = mesh ? + createPrimRefArray(mesh,geomID_,/*numSplitPrimitives,*/prims0,bvh->scene->progressInterface) : + createPrimRefArray(scene,Mesh::geom_type,false,/*numSplitPrimitives,*/prims0,bvh->scene->progressInterface); + + Splitter splitter(scene); + + const size_t node_bytes = pinfo.size()*sizeof(typename BVH::AABBNode)/(4*N); + const size_t leaf_bytes = size_t(1.2*Primitive::blocks(pinfo.size())*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + settings.singleThreadThreshold = bvh->alloc.fixSingleThreadThreshold(N,DEFAULT_SINGLE_THREAD_THRESHOLD,pinfo.size(),node_bytes+leaf_bytes); + + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + + /* call BVH builder */ + root = BVHBuilderBinnedFastSpatialSAH::build( + typename BVH::CreateAlloc(bvh), + typename BVH::AABBNode::Create2(), + typename BVH::AABBNode::Set2(), + CreateLeafSpatial(bvh), + splitter, + bvh->scene->progressInterface, + prims0.data(), + numSplitPrimitives, + pinfo,settings); + + /* ==================== */ + } + + bvh->set(root,LBBox3fa(pinfo.geomBounds),pinfo.size()); + bvh->layoutLargeNodes(size_t(pinfo.size()*0.005f)); + + /* clear temporary data for static geometry */ + if (scene && scene->isStaticAccel()) { + prims0.clear(); + } + bvh->cleanup(); + bvh->postBuild(t0); + } + + void clear() { + prims0.clear(); + } + }; + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + + Builder* BVH4Triangle4SceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<4,TriangleMesh,Triangle4,TriangleSplitterFactory>((BVH4*)bvh,scene,4,1.0f,4,inf,mode); } + Builder* BVH4Triangle4vSceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<4,TriangleMesh,Triangle4v,TriangleSplitterFactory>((BVH4*)bvh,scene,4,1.0f,4,inf,mode); } + Builder* BVH4Triangle4iSceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<4,TriangleMesh,Triangle4i,TriangleSplitterFactory>((BVH4*)bvh,scene,4,1.0f,4,inf,mode); } + +#if defined(__AVX__) + Builder* BVH8Triangle4SceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<8,TriangleMesh,Triangle4,TriangleSplitterFactory>((BVH8*)bvh,scene,4,1.0f,4,inf,mode); } + Builder* BVH8Triangle4vSceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<8,TriangleMesh,Triangle4v,TriangleSplitterFactory>((BVH8*)bvh,scene,4,1.0f,4,inf,mode); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH4Quad4vSceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<4,QuadMesh,Quad4v,QuadSplitterFactory>((BVH4*)bvh,scene,4,1.0f,4,inf,mode); } + +#if defined(__AVX__) + Builder* BVH8Quad4vSceneBuilderFastSpatialSAH (void* bvh, Scene* scene, size_t mode) { return new BVHNBuilderFastSpatialSAH<8,QuadMesh,Quad4v,QuadSplitterFactory>((BVH8*)bvh,scene,4,1.0f,4,inf,mode); } +#endif + +#endif + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_twolevel.cpp b/thirdparty/embree/kernels/bvh/bvh_builder_twolevel.cpp new file mode 100644 index 000000000000..1a78f347ac3d --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_twolevel.cpp @@ -0,0 +1,377 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_builder_twolevel.h" +#include "bvh_statistics.h" +#include "../builders/bvh_builder_sah.h" +#include "../common/scene_line_segments.h" +#include "../common/scene_triangle_mesh.h" +#include "../common/scene_quad_mesh.h" + +#define PROFILE 0 + +namespace embree +{ + namespace isa + { + template + BVHNBuilderTwoLevel::BVHNBuilderTwoLevel (BVH* bvh, Scene* scene, Geometry::GTypeMask gtype, bool useMortonBuilder, const size_t singleThreadThreshold) + : bvh(bvh), scene(scene), refs(scene->device,0), prims(scene->device,0), singleThreadThreshold(singleThreadThreshold), gtype(gtype), useMortonBuilder_(useMortonBuilder) {} + + template + BVHNBuilderTwoLevel::~BVHNBuilderTwoLevel () { + } + + // =========================================================================== + // =========================================================================== + // =========================================================================== + + template + void BVHNBuilderTwoLevel::build() + { + /* delete some objects */ + size_t num = scene->size(); + if (num < bvh->objects.size()) { + parallel_for(num, bvh->objects.size(), [&] (const range& r) { + for (size_t i=r.begin(); iobjects[i]; bvh->objects[i] = nullptr; + } + }); + } + +#if PROFILE + while(1) +#endif + { + /* reset memory allocator */ + bvh->alloc.reset(); + + /* skip build for empty scene */ + const size_t numPrimitives = scene->getNumPrimitives(gtype,false); + + if (numPrimitives == 0) { + prims.resize(0); + bvh->set(BVH::emptyNode,empty,0); + return; + } + + /* calculate the size of the entire BVH */ + const size_t numLeafBlocks = Primitive::blocks(numPrimitives); + const size_t node_bytes = 2*numLeafBlocks*sizeof(typename BVH::AABBNode)/N; + const size_t leaf_bytes = size_t(1.2*numLeafBlocks*sizeof(Primitive)); + bvh->alloc.init_estimate(node_bytes+leaf_bytes); + + double t0 = bvh->preBuild(TOSTRING(isa) "::BVH" + toString(N) + "BuilderTwoLevel"); + + /* resize object array if scene got larger */ + if (bvh->objects.size() < num) bvh->objects.resize(num); + if (builders.size() < num) builders.resize(num); + resizeRefsList (); + nextRef.store(0); + + /* create acceleration structures */ + parallel_for(size_t(0), num, [&] (const range& r) + { + for (size_t objectID=r.begin(); objectIDgetSafe(objectID); + + /* ignore meshes we do not support */ + if (mesh == nullptr || mesh->numTimeSteps != 1) + continue; + + if (isSmallGeometry(mesh)) { + setupSmallBuildRefBuilder (objectID, mesh); + } else { + setupLargeBuildRefBuilder (objectID, mesh); + } + } + }); + + /* parallel build of acceleration structures */ + parallel_for(size_t(0), num, [&] (const range& r) + { + for (size_t objectID=r.begin(); objectIDgetSafe(objectID); + if (mesh == nullptr || !mesh->isEnabled() || mesh->numTimeSteps != 1) + continue; + + builders[objectID]->attachBuildRefs (this); + } + }); + + +#if PROFILE + double d0 = getSeconds(); +#endif + /* fast path for single geometry scenes */ + if (nextRef == 1) { + bvh->set(refs[0].node,LBBox3fa(refs[0].bounds()),numPrimitives); + } + + else + { + /* open all large nodes */ + refs.resize(nextRef); + + /* this probably needs some more tuning */ + const size_t extSize = max(max((size_t)SPLIT_MIN_EXT_SPACE,refs.size()*SPLIT_MEMORY_RESERVE_SCALE),size_t((float)numPrimitives / SPLIT_MEMORY_RESERVE_FACTOR)); + +#if !ENABLE_DIRECT_SAH_MERGE_BUILDER + +#if ENABLE_OPEN_SEQUENTIAL + open_sequential(extSize); +#endif + /* compute PrimRefs */ + prims.resize(refs.size()); +#endif + +#if defined(TASKING_TBB) && defined(__AVX512ER__) && USE_TASK_ARENA // KNL + tbb::task_arena limited(min(32,(int)TaskScheduler::threadCount())); + limited.execute([&] +#endif + { +#if ENABLE_DIRECT_SAH_MERGE_BUILDER + + const PrimInfo pinfo = parallel_reduce(size_t(0), refs.size(), PrimInfo(empty), [&] (const range& r) -> PrimInfo { + + PrimInfo pinfo(empty); + for (size_t i=r.begin(); i& r) -> PrimInfo { + + PrimInfo pinfo(empty); + for (size_t i=r.begin(); iset(BVH::emptyNode,empty,0); + + /* otherwise build toplevel hierarchy */ + else + { + /* settings for BVH build */ + GeneralBVHBuilder::Settings settings; + settings.branchingFactor = N; + settings.maxDepth = BVH::maxBuildDepthLeaf; + settings.logBlockSize = bsr(N); + settings.minLeafSize = 1; + settings.maxLeafSize = 1; + settings.travCost = 1.0f; + settings.intCost = 1.0f; + settings.singleThreadThreshold = singleThreadThreshold; + +#if ENABLE_DIRECT_SAH_MERGE_BUILDER + + refs.resize(extSize); + + NodeRef root = BVHBuilderBinnedOpenMergeSAH::build( + typename BVH::CreateAlloc(bvh), + typename BVH::AABBNode::Create2(), + typename BVH::AABBNode::Set2(), + + [&] (const BuildRef* refs, const range& range, const FastAllocator::CachedAllocator& alloc) -> NodeRef { + assert(range.size() == 1); + return (NodeRef) refs[range.begin()].node; + }, + [&] (BuildRef &bref, BuildRef *refs) -> size_t { + return openBuildRef(bref,refs); + }, + [&] (size_t dn) { bvh->scene->progressMonitor(0); }, + refs.data(),extSize,pinfo,settings); +#else + NodeRef root = BVHBuilderBinnedSAH::build( + typename BVH::CreateAlloc(bvh), + typename BVH::AABBNode::Create2(), + typename BVH::AABBNode::Set2(), + + [&] (const PrimRef* prims, const range& range, const FastAllocator::CachedAllocator& alloc) -> NodeRef { + assert(range.size() == 1); + return (NodeRef) prims[range.begin()].ID(); + }, + [&] (size_t dn) { bvh->scene->progressMonitor(0); }, + prims.data(),pinfo,settings); +#endif + + + bvh->set(root,LBBox3fa(pinfo.geomBounds),numPrimitives); + } + } +#if defined(TASKING_TBB) && defined(__AVX512ER__) && USE_TASK_ARENA // KNL + ); +#endif + + } + + bvh->alloc.cleanup(); + bvh->postBuild(t0); +#if PROFILE + double d1 = getSeconds(); + std::cout << "TOP_LEVEL OPENING/REBUILD TIME " << 1000.0*(d1-d0) << " ms" << std::endl; +#endif + } + + } + + template + void BVHNBuilderTwoLevel::deleteGeometry(size_t geomID) + { + if (geomID >= bvh->objects.size()) return; + if (builders[geomID]) builders[geomID].reset(); + delete bvh->objects [geomID]; bvh->objects [geomID] = nullptr; + } + + template + void BVHNBuilderTwoLevel::clear() + { + for (size_t i=0; iobjects.size(); i++) + if (bvh->objects[i]) bvh->objects[i]->clear(); + + for (size_t i=0; i + void BVHNBuilderTwoLevel::open_sequential(const size_t extSize) + { + if (refs.size() == 0) + return; + + refs.reserve(extSize); + +#if 1 + for (size_t i=0;ichild(i) == BVH::emptyNode) continue; + refs.push_back(BuildRef(node->bounds(i),node->child(i))); + +#if 1 + NodeRef ref_pre = node->child(i); + if (ref_pre.isAABBNode()) + ref_pre.prefetch(); +#endif + std::push_heap (refs.begin(),refs.end()); + } + } + } + + template + void BVHNBuilderTwoLevel::setupSmallBuildRefBuilder (size_t objectID, Mesh const * const /*mesh*/) + { + if (builders[objectID] == nullptr || // new mesh + dynamic_cast(builders[objectID].get()) == nullptr) // size change resulted in large->small change + { + builders[objectID].reset (new RefBuilderSmall(objectID)); + } + } + + template + void BVHNBuilderTwoLevel::setupLargeBuildRefBuilder (size_t objectID, Mesh const * const mesh) + { + if (bvh->objects[objectID] == nullptr || // new mesh + builders[objectID]->meshQualityChanged (mesh->quality) || // changed build quality + dynamic_cast(builders[objectID].get()) == nullptr) // size change resulted in small->large change + { + Builder* builder = nullptr; + delete bvh->objects[objectID]; + createMeshAccel(objectID, builder); + builders[objectID].reset (new RefBuilderLarge(objectID, builder, mesh->quality)); + } + } + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + Builder* BVH4BuilderTwoLevelTriangle4MeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<4,TriangleMesh,Triangle4>((BVH4*)bvh,scene,TriangleMesh::geom_type,useMortonBuilder); + } + Builder* BVH4BuilderTwoLevelTriangle4vMeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<4,TriangleMesh,Triangle4v>((BVH4*)bvh,scene,TriangleMesh::geom_type,useMortonBuilder); + } + Builder* BVH4BuilderTwoLevelTriangle4iMeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<4,TriangleMesh,Triangle4i>((BVH4*)bvh,scene,TriangleMesh::geom_type,useMortonBuilder); + } +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH4BuilderTwoLevelQuadMeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<4,QuadMesh,Quad4v>((BVH4*)bvh,scene,QuadMesh::geom_type,useMortonBuilder); + } +#endif + +#if defined(EMBREE_GEOMETRY_USER) + Builder* BVH4BuilderTwoLevelVirtualSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<4,UserGeometry,Object>((BVH4*)bvh,scene,UserGeometry::geom_type,useMortonBuilder); + } +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + Builder* BVH4BuilderTwoLevelInstanceSAH (void* bvh, Scene* scene, Geometry::GTypeMask gtype, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<4,Instance,InstancePrimitive>((BVH4*)bvh,scene,gtype,useMortonBuilder); + } +#endif + +#if defined(__AVX__) +#if defined(EMBREE_GEOMETRY_TRIANGLE) + Builder* BVH8BuilderTwoLevelTriangle4MeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<8,TriangleMesh,Triangle4>((BVH8*)bvh,scene,TriangleMesh::geom_type,useMortonBuilder); + } + Builder* BVH8BuilderTwoLevelTriangle4vMeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<8,TriangleMesh,Triangle4v>((BVH8*)bvh,scene,TriangleMesh::geom_type,useMortonBuilder); + } + Builder* BVH8BuilderTwoLevelTriangle4iMeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<8,TriangleMesh,Triangle4i>((BVH8*)bvh,scene,TriangleMesh::geom_type,useMortonBuilder); + } +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH8BuilderTwoLevelQuadMeshSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<8,QuadMesh,Quad4v>((BVH8*)bvh,scene,QuadMesh::geom_type,useMortonBuilder); + } +#endif + +#if defined(EMBREE_GEOMETRY_USER) + Builder* BVH8BuilderTwoLevelVirtualSAH (void* bvh, Scene* scene, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<8,UserGeometry,Object>((BVH8*)bvh,scene,UserGeometry::geom_type,useMortonBuilder); + } +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + Builder* BVH8BuilderTwoLevelInstanceSAH (void* bvh, Scene* scene, Geometry::GTypeMask gtype, bool useMortonBuilder) { + return new BVHNBuilderTwoLevel<8,Instance,InstancePrimitive>((BVH8*)bvh,scene,gtype,useMortonBuilder); + } +#endif + +#endif + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_twolevel.h b/thirdparty/embree/kernels/bvh/bvh_builder_twolevel.h new file mode 100644 index 000000000000..8f57c3b40691 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_twolevel.h @@ -0,0 +1,263 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "bvh_builder_twolevel_internal.h" +#include "bvh.h" +#include "../common/primref.h" +#include "../builders/priminfo.h" +#include "../builders/primrefgen.h" + +/* new open/merge builder */ +#define ENABLE_DIRECT_SAH_MERGE_BUILDER 1 +#define ENABLE_OPEN_SEQUENTIAL 0 +#define SPLIT_MEMORY_RESERVE_FACTOR 1000 +#define SPLIT_MEMORY_RESERVE_SCALE 2 +#define SPLIT_MIN_EXT_SPACE 1000 + +namespace embree +{ + namespace isa + { + template + class BVHNBuilderTwoLevel : public Builder + { + typedef BVHN BVH; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::NodeRef NodeRef; + + __forceinline static bool isSmallGeometry(Mesh* mesh) { + return mesh->size() <= 4; + } + + public: + + typedef void (*createMeshAccelTy)(Scene* scene, unsigned int geomID, AccelData*& accel, Builder*& builder); + + struct BuildRef : public PrimRef + { + public: + __forceinline BuildRef () {} + + __forceinline BuildRef (const BBox3fa& bounds, NodeRef node) + : PrimRef(bounds,(size_t)node), node(node) + { + if (node.isLeaf()) + bounds_area = 0.0f; + else + bounds_area = area(this->bounds()); + } + + /* used by the open/merge bvh builder */ + __forceinline BuildRef (const BBox3fa& bounds, NodeRef node, const unsigned int geomID, const unsigned int numPrimitives) + : PrimRef(bounds,geomID,numPrimitives), node(node) + { + /* important for relative buildref ordering */ + if (node.isLeaf()) + bounds_area = 0.0f; + else + bounds_area = area(this->bounds()); + } + + __forceinline size_t size() const { + return primID(); + } + + friend bool operator< (const BuildRef& a, const BuildRef& b) { + return a.bounds_area < b.bounds_area; + } + + friend __forceinline embree_ostream operator<<(embree_ostream cout, const BuildRef& ref) { + return cout << "{ lower = " << ref.lower << ", upper = " << ref.upper << ", center2 = " << ref.center2() << ", geomID = " << ref.geomID() << ", numPrimitives = " << ref.numPrimitives() << ", bounds_area = " << ref.bounds_area << " }"; + } + + __forceinline unsigned int numPrimitives() const { return primID(); } + + public: + NodeRef node; + float bounds_area; + }; + + + __forceinline size_t openBuildRef(BuildRef &bref, BuildRef *const refs) { + if (bref.node.isLeaf()) + { + refs[0] = bref; + return 1; + } + NodeRef ref = bref.node; + unsigned int geomID = bref.geomID(); + unsigned int numPrims = max((unsigned int)bref.numPrimitives() / N,(unsigned int)1); + AABBNode* node = ref.getAABBNode(); + size_t n = 0; + for (size_t i=0; ichild(i) == BVH::emptyNode) continue; + refs[i] = BuildRef(node->bounds(i),node->child(i),geomID,numPrims); + n++; + } + assert(n > 1); + return n; + } + + /*! Constructor. */ + BVHNBuilderTwoLevel (BVH* bvh, Scene* scene, Geometry::GTypeMask gtype = Mesh::geom_type, bool useMortonBuilder = false, const size_t singleThreadThreshold = DEFAULT_SINGLE_THREAD_THRESHOLD); + + /*! Destructor */ + ~BVHNBuilderTwoLevel (); + + /*! builder entry point */ + void build(); + void deleteGeometry(size_t geomID); + void clear(); + + void open_sequential(const size_t extSize); + + private: + + class RefBuilderBase { + public: + virtual ~RefBuilderBase () {} + virtual void attachBuildRefs (BVHNBuilderTwoLevel* builder) = 0; + virtual bool meshQualityChanged (RTCBuildQuality currQuality) = 0; + }; + + class RefBuilderSmall : public RefBuilderBase { + public: + + RefBuilderSmall (size_t objectID) + : objectID_ (objectID) {} + + void attachBuildRefs (BVHNBuilderTwoLevel* topBuilder) { + + Mesh* mesh = topBuilder->scene->template getSafe(objectID_); + size_t meshSize = mesh->size(); + assert(isSmallGeometry(mesh)); + + mvector prefs(topBuilder->scene->device, meshSize); + auto pinfo = createPrimRefArray(mesh,objectID_,prefs,topBuilder->bvh->scene->progressInterface); + + size_t begin=0; + while (begin < pinfo.size()) + { + Primitive* accel = (Primitive*) topBuilder->bvh->alloc.getCachedAllocator().malloc1(sizeof(Primitive),BVH::byteAlignment); + typename BVH::NodeRef node = BVH::encodeLeaf((char*)accel,1); + accel->fill(prefs.data(),begin,pinfo.size(),topBuilder->bvh->scene); + + /* create build primitive */ +#if ENABLE_DIRECT_SAH_MERGE_BUILDER + topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(pinfo.geomBounds,node,(unsigned int)objectID_,1); +#else + topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(pinfo.geomBounds,node); +#endif + } + assert(begin == pinfo.size()); + } + + bool meshQualityChanged (RTCBuildQuality /*currQuality*/) { + return false; + } + + size_t objectID_; + }; + + class RefBuilderLarge : public RefBuilderBase { + public: + + RefBuilderLarge (size_t objectID, const Ref& builder, RTCBuildQuality quality) + : objectID_ (objectID), builder_ (builder), quality_ (quality) {} + + void attachBuildRefs (BVHNBuilderTwoLevel* topBuilder) + { + BVH* object = topBuilder->getBVH(objectID_); assert(object); + + /* build object if it got modified */ + if (topBuilder->isGeometryModified(objectID_)) + builder_->build(); + + /* create build primitive */ + if (!object->getBounds().empty()) + { +#if ENABLE_DIRECT_SAH_MERGE_BUILDER + Mesh* mesh = topBuilder->getMesh(objectID_); + topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(object->getBounds(),object->root,(unsigned int)objectID_,(unsigned int)mesh->size()); +#else + topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(object->getBounds(),object->root); +#endif + } + } + + bool meshQualityChanged (RTCBuildQuality currQuality) { + return currQuality != quality_; + } + + private: + size_t objectID_; + Ref builder_; + RTCBuildQuality quality_; + }; + + void setupLargeBuildRefBuilder (size_t objectID, Mesh const * const mesh); + void setupSmallBuildRefBuilder (size_t objectID, Mesh const * const mesh); + + BVH* getBVH (size_t objectID) { + return this->bvh->objects[objectID]; + } + Mesh* getMesh (size_t objectID) { + return this->scene->template getSafe(objectID); + } + bool isGeometryModified (size_t objectID) { + return this->scene->isGeometryModified(objectID); + } + + void resizeRefsList () + { + size_t num = parallel_reduce (size_t(0), scene->size(), size_t(0), + [this](const range& r)->size_t { + size_t c = 0; + for (auto i=r.begin(); igetSafe(i); + if (mesh == nullptr || mesh->numTimeSteps != 1) + continue; + size_t meshSize = mesh->size(); + c += isSmallGeometry(mesh) ? Primitive::blocks(meshSize) : 1; + } + return c; + }, + std::plus() + ); + + if (refs.size() < num) { + refs.resize(num); + } + } + + void createMeshAccel (size_t geomID, Builder*& builder) + { + bvh->objects[geomID] = new BVH(Primitive::type,scene); + BVH* accel = bvh->objects[geomID]; + auto mesh = scene->getSafe(geomID); + if (nullptr == mesh) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"geomID does not return correct type"); + return; + } + + __internal_two_level_builder__::MeshBuilder()(accel, mesh, geomID, this->gtype, this->useMortonBuilder_, builder); + } + + using BuilderList = std::vector>; + + BuilderList builders; + BVH* bvh; + Scene* scene; + mvector refs; + mvector prims; + std::atomic nextRef; + const size_t singleThreadThreshold; + Geometry::GTypeMask gtype; + bool useMortonBuilder_ = false; + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_builder_twolevel_internal.h b/thirdparty/embree/kernels/bvh/bvh_builder_twolevel_internal.h new file mode 100644 index 000000000000..1c1ae8d6a729 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_builder_twolevel_internal.h @@ -0,0 +1,267 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/quadi.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" + +namespace embree +{ + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4MeshBuilderMortonGeneral,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4MeshBuilderSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4MeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vMeshBuilderMortonGeneral,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vMeshBuilderSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4vMeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iMeshBuilderMortonGeneral,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iMeshBuilderSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Triangle4iMeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4vMeshBuilderMortonGeneral,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4vMeshBuilderSAH,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4Quad4vMeshRefitSAH,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4VirtualMeshBuilderMortonGeneral,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4VirtualMeshBuilderSAH,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4VirtualMeshRefitSAH,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4InstanceMeshBuilderMortonGeneral,void* COMMA Instance* COMMA Geometry::GTypeMask COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4InstanceMeshBuilderSAH,void* COMMA Instance* COMMA Geometry::GTypeMask COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH4InstanceMeshRefitSAH,void* COMMA Instance* COMMA Geometry::GTypeMask COMMA unsigned int COMMA size_t) + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4MeshBuilderMortonGeneral,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4MeshBuilderSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4MeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4vMeshBuilderMortonGeneral,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4vMeshBuilderSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4vMeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4iMeshBuilderMortonGeneral,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4iMeshBuilderSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Triangle4iMeshRefitSAH,void* COMMA TriangleMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4vMeshBuilderMortonGeneral,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4vMeshBuilderSAH,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8Quad4vMeshRefitSAH,void* COMMA QuadMesh* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8VirtualMeshBuilderMortonGeneral,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8VirtualMeshBuilderSAH,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8VirtualMeshRefitSAH,void* COMMA UserGeometry* COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8InstanceMeshBuilderMortonGeneral,void* COMMA Instance* COMMA Geometry::GTypeMask COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8InstanceMeshBuilderSAH,void* COMMA Instance* COMMA Geometry::GTypeMask COMMA unsigned int COMMA size_t); + DECLARE_ISA_FUNCTION(Builder*,BVH8InstanceMeshRefitSAH,void* COMMA Instance* COMMA Geometry::GTypeMask COMMA unsigned int COMMA size_t) + + namespace isa + { + + namespace __internal_two_level_builder__ { + + template + struct MortonBuilder {}; + template<> + struct MortonBuilder<4,TriangleMesh,Triangle4> { + MortonBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4MeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<4,TriangleMesh,Triangle4v> { + MortonBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4vMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<4,TriangleMesh,Triangle4i> { + MortonBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4iMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<4,QuadMesh,Quad4v> { + MortonBuilder () {} + Builder* operator () (void* bvh, QuadMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Quad4vMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<4,UserGeometry,Object> { + MortonBuilder () {} + Builder* operator () (void* bvh, UserGeometry* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4VirtualMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<4,Instance,InstancePrimitive> { + MortonBuilder () {} + Builder* operator () (void* bvh, Instance* mesh, size_t geomID, Geometry::GTypeMask gtype) { return BVH4InstanceMeshBuilderMortonGeneral(bvh,mesh,gtype,geomID,0);} + }; + template<> + struct MortonBuilder<8,TriangleMesh,Triangle4> { + MortonBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4MeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<8,TriangleMesh,Triangle4v> { + MortonBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4vMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<8,TriangleMesh,Triangle4i> { + MortonBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4iMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<8,QuadMesh,Quad4v> { + MortonBuilder () {} + Builder* operator () (void* bvh, QuadMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Quad4vMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<8,UserGeometry,Object> { + MortonBuilder () {} + Builder* operator () (void* bvh, UserGeometry* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8VirtualMeshBuilderMortonGeneral(bvh,mesh,geomID,0);} + }; + template<> + struct MortonBuilder<8,Instance,InstancePrimitive> { + MortonBuilder () {} + Builder* operator () (void* bvh, Instance* mesh, size_t geomID, Geometry::GTypeMask gtype) { return BVH8InstanceMeshBuilderMortonGeneral(bvh,mesh,gtype,geomID,0);} + }; + + template + struct SAHBuilder {}; + template<> + struct SAHBuilder<4,TriangleMesh,Triangle4> { + SAHBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4MeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<4,TriangleMesh,Triangle4v> { + SAHBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4vMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<4,TriangleMesh,Triangle4i> { + SAHBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4iMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<4,QuadMesh,Quad4v> { + SAHBuilder () {} + Builder* operator () (void* bvh, QuadMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Quad4vMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<4,UserGeometry,Object> { + SAHBuilder () {} + Builder* operator () (void* bvh, UserGeometry* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4VirtualMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<4,Instance,InstancePrimitive> { + SAHBuilder () {} + Builder* operator () (void* bvh, Instance* mesh, size_t geomID, Geometry::GTypeMask gtype) { return BVH4InstanceMeshBuilderSAH(bvh,mesh,gtype,geomID,0);} + }; + template<> + struct SAHBuilder<8,TriangleMesh,Triangle4> { + SAHBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4MeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<8,TriangleMesh,Triangle4v> { + SAHBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4vMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<8,TriangleMesh,Triangle4i> { + SAHBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4iMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<8,QuadMesh,Quad4v> { + SAHBuilder () {} + Builder* operator () (void* bvh, QuadMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Quad4vMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<8,UserGeometry,Object> { + SAHBuilder () {} + Builder* operator () (void* bvh, UserGeometry* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8VirtualMeshBuilderSAH(bvh,mesh,geomID,0);} + }; + template<> + struct SAHBuilder<8,Instance,InstancePrimitive> { + SAHBuilder () {} + Builder* operator () (void* bvh, Instance* mesh, size_t geomID, Geometry::GTypeMask gtype) { return BVH8InstanceMeshBuilderSAH(bvh,mesh,gtype,geomID,0);} + }; + + template + struct RefitBuilder {}; + template<> + struct RefitBuilder<4,TriangleMesh,Triangle4> { + RefitBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4MeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<4,TriangleMesh,Triangle4v> { + RefitBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4vMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<4,TriangleMesh,Triangle4i> { + RefitBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Triangle4iMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<4,QuadMesh,Quad4v> { + RefitBuilder () {} + Builder* operator () (void* bvh, QuadMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4Quad4vMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<4,UserGeometry,Object> { + RefitBuilder () {} + Builder* operator () (void* bvh, UserGeometry* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH4VirtualMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<4,Instance,InstancePrimitive> { + RefitBuilder () {} + Builder* operator () (void* bvh, Instance* mesh, size_t geomID, Geometry::GTypeMask gtype) { return BVH4InstanceMeshRefitSAH(bvh,mesh,gtype,geomID,0);} + }; + template<> + struct RefitBuilder<8,TriangleMesh,Triangle4> { + RefitBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4MeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<8,TriangleMesh,Triangle4v> { + RefitBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4vMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<8,TriangleMesh,Triangle4i> { + RefitBuilder () {} + Builder* operator () (void* bvh, TriangleMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Triangle4iMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<8,QuadMesh,Quad4v> { + RefitBuilder () {} + Builder* operator () (void* bvh, QuadMesh* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8Quad4vMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<8,UserGeometry,Object> { + RefitBuilder () {} + Builder* operator () (void* bvh, UserGeometry* mesh, size_t geomID, Geometry::GTypeMask /*gtype*/) { return BVH8VirtualMeshRefitSAH(bvh,mesh,geomID,0);} + }; + template<> + struct RefitBuilder<8,Instance,InstancePrimitive> { + RefitBuilder () {} + Builder* operator () (void* bvh, Instance* mesh, size_t geomID, Geometry::GTypeMask gtype) { return BVH8InstanceMeshRefitSAH(bvh,mesh,gtype,geomID,0);} + }; + + template + struct MeshBuilder { + MeshBuilder () {} + void operator () (void* bvh, Mesh* mesh, size_t geomID, Geometry::GTypeMask gtype, bool useMortonBuilder, Builder*& builder) { + if(useMortonBuilder) { + builder = MortonBuilder()(bvh,mesh,geomID,gtype); + return; + } + switch (mesh->quality) { + case RTC_BUILD_QUALITY_LOW: builder = MortonBuilder()(bvh,mesh,geomID,gtype); break; + case RTC_BUILD_QUALITY_MEDIUM: + case RTC_BUILD_QUALITY_HIGH: builder = SAHBuilder()(bvh,mesh,geomID,gtype); break; + case RTC_BUILD_QUALITY_REFIT: builder = RefitBuilder()(bvh,mesh,geomID,gtype); break; + default: throw_RTCError(RTC_ERROR_UNKNOWN,"invalid build quality"); + } + } + }; + } + } +} \ No newline at end of file diff --git a/thirdparty/embree/kernels/bvh/bvh_collider.cpp b/thirdparty/embree/kernels/bvh/bvh_collider.cpp new file mode 100644 index 000000000000..a27be8bae839 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_collider.cpp @@ -0,0 +1,375 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_collider.h" +#include "../geometry/triangle_triangle_intersector.h" + +namespace embree +{ + namespace isa + { +#define CSTAT(x) + + size_t parallel_depth_threshold = 3; + CSTAT(std::atomic bvh_collide_traversal_steps(0)); + CSTAT(std::atomic bvh_collide_leaf_pairs(0)); + CSTAT(std::atomic bvh_collide_leaf_iterations(0)); + CSTAT(std::atomic bvh_collide_prim_intersections1(0)); + CSTAT(std::atomic bvh_collide_prim_intersections2(0)); + CSTAT(std::atomic bvh_collide_prim_intersections3(0)); + CSTAT(std::atomic bvh_collide_prim_intersections4(0)); + CSTAT(std::atomic bvh_collide_prim_intersections5(0)); + CSTAT(std::atomic bvh_collide_prim_intersections(0)); + + struct Collision + { + __forceinline Collision() {} + + __forceinline Collision (unsigned geomID0, unsigned primID0, unsigned geomID1, unsigned primID1) + : geomID0(geomID0), primID0(primID0), geomID1(geomID1), primID1(primID1) {} + + unsigned geomID0; + unsigned primID0; + unsigned geomID1; + unsigned primID1; + }; + + template + __forceinline size_t overlap(const BBox3fa& box0, const typename BVHN::AABBNode& node1) + { + const vfloat lower_x = max(vfloat(box0.lower.x),node1.lower_x); + const vfloat lower_y = max(vfloat(box0.lower.y),node1.lower_y); + const vfloat lower_z = max(vfloat(box0.lower.z),node1.lower_z); + const vfloat upper_x = min(vfloat(box0.upper.x),node1.upper_x); + const vfloat upper_y = min(vfloat(box0.upper.y),node1.upper_y); + const vfloat upper_z = min(vfloat(box0.upper.z),node1.upper_z); + return movemask((lower_x <= upper_x) & (lower_y <= upper_y) & (lower_z <= upper_z)); + } + + template + __forceinline size_t overlap(const BBox3fa& box0, const BBox>>& box1) + { + const vfloat lower_x = max(vfloat(box0.lower.x),box1.lower.x); + const vfloat lower_y = max(vfloat(box0.lower.y),box1.lower.y); + const vfloat lower_z = max(vfloat(box0.lower.z),box1.lower.z); + const vfloat upper_x = min(vfloat(box0.upper.x),box1.upper.x); + const vfloat upper_y = min(vfloat(box0.upper.y),box1.upper.y); + const vfloat upper_z = min(vfloat(box0.upper.z),box1.upper.z); + return movemask((lower_x <= upper_x) & (lower_y <= upper_y) & (lower_z <= upper_z)); + } + + template + __forceinline size_t overlap(const BBox>>& box0, size_t i, const BBox>>& box1) + { + const vfloat lower_x = max(vfloat(box0.lower.x[i]),box1.lower.x); + const vfloat lower_y = max(vfloat(box0.lower.y[i]),box1.lower.y); + const vfloat lower_z = max(vfloat(box0.lower.z[i]),box1.lower.z); + const vfloat upper_x = min(vfloat(box0.upper.x[i]),box1.upper.x); + const vfloat upper_y = min(vfloat(box0.upper.y[i]),box1.upper.y); + const vfloat upper_z = min(vfloat(box0.upper.z[i]),box1.upper.z); + return movemask((lower_x <= upper_x) & (lower_y <= upper_y) & (lower_z <= upper_z)); + } + + bool intersect_triangle_triangle (Scene* scene0, unsigned geomID0, unsigned primID0, Scene* scene1, unsigned geomID1, unsigned primID1) + { + CSTAT(bvh_collide_prim_intersections1++); + const TriangleMesh* mesh0 = scene0->get(geomID0); + const TriangleMesh* mesh1 = scene1->get(geomID1); + const TriangleMesh::Triangle& tri0 = mesh0->triangle(primID0); + const TriangleMesh::Triangle& tri1 = mesh1->triangle(primID1); + + /* special culling for scene intersection with itself */ + if (scene0 == scene1 && geomID0 == geomID1) + { + /* ignore self intersections */ + if (primID0 == primID1) + return false; + } + CSTAT(bvh_collide_prim_intersections2++); + + if (scene0 == scene1 && geomID0 == geomID1) + { + /* ignore intersection with topological neighbors */ + const vint4 t0(tri0.v[0],tri0.v[1],tri0.v[2],tri0.v[2]); + if (any(vint4(tri1.v[0]) == t0)) return false; + if (any(vint4(tri1.v[1]) == t0)) return false; + if (any(vint4(tri1.v[2]) == t0)) return false; + } + CSTAT(bvh_collide_prim_intersections3++); + + const Vec3fa a0 = mesh0->vertex(tri0.v[0]); + const Vec3fa a1 = mesh0->vertex(tri0.v[1]); + const Vec3fa a2 = mesh0->vertex(tri0.v[2]); + const Vec3fa b0 = mesh1->vertex(tri1.v[0]); + const Vec3fa b1 = mesh1->vertex(tri1.v[1]); + const Vec3fa b2 = mesh1->vertex(tri1.v[2]); + + return TriangleTriangleIntersector::intersect_triangle_triangle(a0,a1,a2,b0,b1,b2); + } + + template + __forceinline void BVHNColliderUserGeom::processLeaf(NodeRef node0, NodeRef node1) + { + Collision collisions[16]; + size_t num_collisions = 0; + + size_t N0; Object* leaf0 = (Object*) node0.leaf(N0); + size_t N1; Object* leaf1 = (Object*) node1.leaf(N1); + for (size_t i=0; iscene0 == this->scene1 && geomID0 == geomID1 && primID0 == primID1) continue; + collisions[num_collisions++] = Collision(geomID0,primID0,geomID1,primID1); + if (num_collisions == 16) { + this->callback(this->userPtr,(RTCCollision*)&collisions,num_collisions); + num_collisions = 0; + } + } + } + if (num_collisions) + this->callback(this->userPtr,(RTCCollision*)&collisions,num_collisions); + } + + template + void BVHNCollider::collide_recurse(NodeRef ref0, const BBox3fa& bounds0, NodeRef ref1, const BBox3fa& bounds1, size_t depth0, size_t depth1) + { + CSTAT(bvh_collide_traversal_steps++); + if (unlikely(ref0.isLeaf())) { + if (unlikely(ref1.isLeaf())) { + CSTAT(bvh_collide_leaf_pairs++); + processLeaf(ref0,ref1); + return; + } else goto recurse_node1; + + } else { + if (unlikely(ref1.isLeaf())) { + goto recurse_node0; + } else { + if (area(bounds0) > area(bounds1)) { + goto recurse_node0; + } + else { + goto recurse_node1; + } + } + } + + { + recurse_node0: + AABBNode* node0 = ref0.getAABBNode(); + size_t mask = overlap(bounds1,*node0); + //for (size_t m=mask, i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) { + //for (size_t i=0; i::prefetch(node0->child(i),BVH_FLAG_ALIGNED_NODE); + collide_recurse(node0->child(i),node0->bounds(i),ref1,bounds1,depth0+1,depth1); + } + }); + } + else +#endif + { + for (size_t m=mask, i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) { + BVHN::prefetch(node0->child(i),BVH_FLAG_ALIGNED_NODE); + collide_recurse(node0->child(i),node0->bounds(i),ref1,bounds1,depth0+1,depth1); + } + } + return; + } + + { + recurse_node1: + AABBNode* node1 = ref1.getAABBNode(); + size_t mask = overlap(bounds0,*node1); + //for (size_t m=mask, i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) { + //for (size_t i=0; i::prefetch(node1->child(i),BVH_FLAG_ALIGNED_NODE); + collide_recurse(ref0,bounds0,node1->child(i),node1->bounds(i),depth0,depth1+1); + } + }); + } + else +#endif + { + for (size_t m=mask, i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) { + BVHN::prefetch(node1->child(i),BVH_FLAG_ALIGNED_NODE); + collide_recurse(ref0,bounds0,node1->child(i),node1->bounds(i),depth0,depth1+1); + } + } + return; + } + } + + template + void BVHNCollider::split(const CollideJob& job, jobvector& jobs) + { + if (unlikely(job.ref0.isLeaf())) { + if (unlikely(job.ref1.isLeaf())) { + jobs.push_back(job); + return; + } else goto recurse_node1; + } else { + if (unlikely(job.ref1.isLeaf())) { + goto recurse_node0; + } else { + if (area(job.bounds0) > area(job.bounds1)) { + goto recurse_node0; + } + else { + goto recurse_node1; + } + } + } + + { + recurse_node0: + const AABBNode* node0 = job.ref0.getAABBNode(); + size_t mask = overlap(job.bounds1,*node0); + for (size_t m=mask, i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) { + jobs.push_back(CollideJob(node0->child(i),node0->bounds(i),job.depth0+1,job.ref1,job.bounds1,job.depth1)); + } + return; + } + + { + recurse_node1: + const AABBNode* node1 = job.ref1.getAABBNode(); + size_t mask = overlap(job.bounds0,*node1); + for (size_t m=mask, i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) { + jobs.push_back(CollideJob(job.ref0,job.bounds0,job.depth0,node1->child(i),node1->bounds(i),job.depth1+1)); + } + return; + } + } + + template + void BVHNCollider::collide_recurse_entry(NodeRef ref0, const BBox3fa& bounds0, NodeRef ref1, const BBox3fa& bounds1) + { + CSTAT(bvh_collide_traversal_steps = 0); + CSTAT(bvh_collide_leaf_pairs = 0); + CSTAT(bvh_collide_leaf_iterations = 0); + CSTAT(bvh_collide_prim_intersections1 = 0); + CSTAT(bvh_collide_prim_intersections2 = 0); + CSTAT(bvh_collide_prim_intersections3 = 0); + CSTAT(bvh_collide_prim_intersections4 = 0); + CSTAT(bvh_collide_prim_intersections5 = 0); + CSTAT(bvh_collide_prim_intersections = 0); +#if 0 + collide_recurse(ref0,bounds0,ref1,bounds1,0,0); +#else + const int M = 2048; + jobvector jobs[2]; + jobs[0].reserve(M); + jobs[1].reserve(M); + jobs[0].push_back(CollideJob(ref0,bounds0,0,ref1,bounds1,0)); + int source = 0; + int target = 1; + + /* try to split job until job list is full */ + while (jobs[source].size()+8 <= M) + { + for (size_t i=0; i M) { + jobs[target].push_back(job); + } else { + split(job,jobs[target]); + } + } + + /* stop splitting jobs if we reached only leaves and cannot make progress anymore */ + if (jobs[target].size() == jobs[source].size()) + break; + + jobs[source].resize(0); + std::swap(source,target); + } + + /* parallel processing of all jobs */ + parallel_for(size_t(jobs[source].size()), [&] ( size_t i ) { + CollideJob& j = jobs[source][i]; + collide_recurse(j.ref0,j.bounds0,j.ref1,j.bounds1,j.depth0,j.depth1); + }); + + +#endif + CSTAT(PRINT(bvh_collide_traversal_steps)); + CSTAT(PRINT(bvh_collide_leaf_pairs)); + CSTAT(PRINT(bvh_collide_leaf_iterations)); + CSTAT(PRINT(bvh_collide_prim_intersections1)); + CSTAT(PRINT(bvh_collide_prim_intersections2)); + CSTAT(PRINT(bvh_collide_prim_intersections3)); + CSTAT(PRINT(bvh_collide_prim_intersections4)); + CSTAT(PRINT(bvh_collide_prim_intersections5)); + CSTAT(PRINT(bvh_collide_prim_intersections)); + } + + template + void BVHNColliderUserGeom::collide(BVH* __restrict__ bvh0, BVH* __restrict__ bvh1, RTCCollideFunc callback, void* userPtr) + { + BVHNColliderUserGeom(bvh0->scene,bvh1->scene,callback,userPtr). + collide_recurse_entry(bvh0->root,bvh0->bounds.bounds(),bvh1->root,bvh1->bounds.bounds()); + } + +#if defined (EMBREE_LOWEST_ISA) + struct collision_regression_test : public RegressionTest + { + collision_regression_test(const char* name) : RegressionTest(name) { + registerRegressionTest(this); + } + + bool run () + { + bool passed = true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(-0.008815f, 0.041848f, -2.49875e-06f), Vec3fa(-0.008276f, 0.053318f, -2.49875e-06f), Vec3fa(0.003023f, 0.048969f, -2.49875e-06f), + Vec3fa(0.00245f, 0.037612f, -2.49875e-06f), Vec3fa(0.01434f, 0.042634f, -2.49875e-06f), Vec3fa(0.013499f, 0.031309f, -2.49875e-06f)) == false; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0,0,1),Vec3fa(1,0,1),Vec3fa(0,1,1)) == false; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0,0,1),Vec3fa(1,0,0),Vec3fa(0,1,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0,0,0),Vec3fa(1,0,1),Vec3fa(0,1,1)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0.1f,0.1f,0),Vec3fa(1,0,1),Vec3fa(0,1,1)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0.1f,0.1f,-0.1f),Vec3fa(1,0,1),Vec3fa(0,1,1)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0,0,0),Vec3fa(0.5f,0,0),Vec3fa(0,0.5f,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0.1f,0.1f,0),Vec3fa(0.5f,0,0),Vec3fa(0,0.5f,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0.1f,0.1f,0),Vec3fa(0.5f,0.1f,0),Vec3fa(0.1f,0.5f,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(0.1f,-0.1f,0),Vec3fa(0.5f,0.1f,0),Vec3fa(0.1f,0.5f,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), Vec3fa(-0.1f,0.1f,0),Vec3fa(0.5f,0.1f,0),Vec3fa(0.1f,0.5f,0)) == true; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), + Vec3fa(-1,1,0) + Vec3fa(0,0,0),Vec3fa(-1,1,0) + Vec3fa(0.1f,0,0),Vec3fa(-1,1,0) + Vec3fa(0,0.1f,0)) == false; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), + Vec3fa( 2,0.5f,0) + Vec3fa(0,0,0),Vec3fa( 2,0.5f,0) + Vec3fa(0.1f,0,0),Vec3fa( 2,0.5f,0) + Vec3fa(0,0.1f,0)) == false; + passed &= TriangleTriangleIntersector::intersect_triangle_triangle (Vec3fa(0,0,0),Vec3fa(1,0,0),Vec3fa(0,1,0), + Vec3fa(0.5f,-2.0f,0) + Vec3fa(0,0,0),Vec3fa(0.5f,-2.0f,0) + Vec3fa(0.1f,0,0),Vec3fa(0.5f,-2.0f,0) + Vec3fa(0,0.1f,0)) == false; + return passed; + } + }; + + collision_regression_test collision_regression("collision_regression_test"); +#endif + + //////////////////////////////////////////////////////////////////////////////// + /// Collider Definitions + //////////////////////////////////////////////////////////////////////////////// + + DEFINE_COLLIDER(BVH4ColliderUserGeom,BVHNColliderUserGeom<4>); + +#if defined(__AVX__) + DEFINE_COLLIDER(BVH8ColliderUserGeom,BVHNColliderUserGeom<8>); +#endif + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_collider.h b/thirdparty/embree/kernels/bvh/bvh_collider.h new file mode 100644 index 000000000000..ac4f99c96a7a --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_collider.h @@ -0,0 +1,72 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include "../geometry/trianglev.h" +#include "../geometry/object.h" + +namespace embree +{ + namespace isa + { + template + class BVHNCollider + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::AABBNode AABBNode; + + struct CollideJob + { + CollideJob () {} + + CollideJob (NodeRef ref0, const BBox3fa& bounds0, size_t depth0, + NodeRef ref1, const BBox3fa& bounds1, size_t depth1) + : ref0(ref0), bounds0(bounds0), depth0(depth0), ref1(ref1), bounds1(bounds1), depth1(depth1) {} + + NodeRef ref0; + BBox3fa bounds0; + size_t depth0; + NodeRef ref1; + BBox3fa bounds1; + size_t depth1; + }; + + typedef vector_t> jobvector; + + void split(const CollideJob& job, jobvector& jobs); + + public: + __forceinline BVHNCollider (Scene* scene0, Scene* scene1, RTCCollideFunc callback, void* userPtr) + : scene0(scene0), scene1(scene1), callback(callback), userPtr(userPtr) {} + + public: + virtual void processLeaf(NodeRef leaf0, NodeRef leaf1) = 0; + void collide_recurse(NodeRef node0, const BBox3fa& bounds0, NodeRef node1, const BBox3fa& bounds1, size_t depth0, size_t depth1); + void collide_recurse_entry(NodeRef node0, const BBox3fa& bounds0, NodeRef node1, const BBox3fa& bounds1); + + protected: + Scene* scene0; + Scene* scene1; + RTCCollideFunc callback; + void* userPtr; + }; + + template + class BVHNColliderUserGeom : public BVHNCollider + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::AABBNode AABBNode; + + __forceinline BVHNColliderUserGeom (Scene* scene0, Scene* scene1, RTCCollideFunc callback, void* userPtr) + : BVHNCollider(scene0,scene1,callback,userPtr) {} + + virtual void processLeaf(NodeRef leaf0, NodeRef leaf1); + public: + static void collide(BVH* __restrict__ bvh0, BVH* __restrict__ bvh1, RTCCollideFunc callback, void* userPtr); + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_factory.h b/thirdparty/embree/kernels/bvh/bvh_factory.h new file mode 100644 index 000000000000..54021ca6eb9f --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_factory.h @@ -0,0 +1,21 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../bvh/bvh.h" +#include "../common/isa.h" +#include "../common/accel.h" +#include "../common/scene.h" +#include "../geometry/curve_intersector_virtual.h" + +namespace embree +{ + /*! BVH instantiations */ + class BVHFactory + { + public: + enum class BuildVariant { STATIC, DYNAMIC, HIGH_QUALITY }; + enum class IntersectVariant { FAST, ROBUST }; + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_intersector1.cpp b/thirdparty/embree/kernels/bvh/bvh_intersector1.cpp new file mode 100644 index 000000000000..ea6adc271746 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_intersector1.cpp @@ -0,0 +1,330 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_intersector1.h" +#include "node_intersector1.h" +#include "bvh_traverser1.h" + +#include "../geometry/intersector_iterators.h" +#include "../geometry/triangle_intersector.h" +#include "../geometry/trianglev_intersector.h" +#include "../geometry/trianglev_mb_intersector.h" +#include "../geometry/trianglei_intersector.h" +#include "../geometry/quadv_intersector.h" +#include "../geometry/quadi_intersector.h" +#include "../geometry/curveNv_intersector.h" +#include "../geometry/curveNi_intersector.h" +#include "../geometry/curveNi_mb_intersector.h" +#include "../geometry/linei_intersector.h" +#include "../geometry/subdivpatch1_intersector.h" +#include "../geometry/object_intersector.h" +#include "../geometry/instance_intersector.h" +#include "../geometry/subgrid_intersector.h" +#include "../geometry/subgrid_mb_intersector.h" +#include "../geometry/curve_intersector_virtual.h" + +namespace embree +{ + namespace isa + { + template + void BVHNIntersector1::intersect(const Accel::Intersectors* __restrict__ This, + RayHit& __restrict__ ray, + IntersectContext* __restrict__ context) + { + const BVH* __restrict__ bvh = (const BVH*)This->ptr; + + /* we may traverse an empty BVH in case all geometry was invalid */ + if (bvh->root == BVH::emptyNode) + return; + + /* perform per ray precalculations required by the primitive intersector */ + Precalculations pre(ray, bvh); + + /* stack state */ + StackItemT stack[stackSize]; // stack of nodes + StackItemT* stackPtr = stack+1; // current stack pointer + StackItemT* stackEnd = stack+stackSize; + stack[0].ptr = bvh->root; + stack[0].dist = neg_inf; + + if (bvh->root == BVH::emptyNode) + return; + + /* filter out invalid rays */ +#if defined(EMBREE_IGNORE_INVALID_RAYS) + if (!ray.valid()) return; +#endif + /* verify correct input */ + assert(ray.valid()); + assert(ray.tnear() >= 0.0f); + assert(!(types & BVH_MB) || (ray.time() >= 0.0f && ray.time() <= 1.0f)); + + /* load the ray into SIMD registers */ + TravRay tray(ray.org, ray.dir, max(ray.tnear(), 0.0f), max(ray.tfar, 0.0f)); + + /* initialize the node traverser */ + BVHNNodeTraverser1Hit nodeTraverser; + + /* pop loop */ + while (true) pop: + { + /* pop next node */ + if (unlikely(stackPtr == stack)) break; + stackPtr--; + NodeRef cur = NodeRef(stackPtr->ptr); + + /* if popped node is too far, pop next one */ +#if defined(__AVX512ER__) + /* much faster on KNL */ + if (unlikely(any(vfloat(*(float*)&stackPtr->dist) > tray.tfar))) + continue; +#else + if (unlikely(*(float*)&stackPtr->dist > ray.tfar)) + continue; +#endif + + /* downtraversal loop */ + while (true) + { + /* intersect node */ + size_t mask; vfloat tNear; + STAT3(normal.trav_nodes,1,1,1); + bool nodeIntersected = BVHNNodeIntersector1::intersect(cur, tray, ray.time(), tNear, mask); + if (unlikely(!nodeIntersected)) { STAT3(normal.trav_nodes,-1,-1,-1); break; } + + /* if no child is hit, pop next node */ + if (unlikely(mask == 0)) + goto pop; + + /* select next child and push other children */ + nodeTraverser.traverseClosestHit(cur, mask, tNear, stackPtr, stackEnd); + } + + /* this is a leaf node */ + assert(cur != BVH::emptyNode); + STAT3(normal.trav_leaves,1,1,1); + size_t num; Primitive* prim = (Primitive*)cur.leaf(num); + size_t lazy_node = 0; + PrimitiveIntersector1::intersect(This, pre, ray, context, prim, num, tray, lazy_node); + tray.tfar = ray.tfar; + + /* push lazy node onto stack */ + if (unlikely(lazy_node)) { + stackPtr->ptr = lazy_node; + stackPtr->dist = neg_inf; + stackPtr++; + } + } + } + + template + void BVHNIntersector1::occluded(const Accel::Intersectors* __restrict__ This, + Ray& __restrict__ ray, + IntersectContext* __restrict__ context) + { + const BVH* __restrict__ bvh = (const BVH*)This->ptr; + + /* we may traverse an empty BVH in case all geometry was invalid */ + if (bvh->root == BVH::emptyNode) + return; + + /* early out for already occluded rays */ + if (unlikely(ray.tfar < 0.0f)) + return; + + /* perform per ray precalculations required by the primitive intersector */ + Precalculations pre(ray, bvh); + + /* stack state */ + NodeRef stack[stackSize]; // stack of nodes that still need to get traversed + NodeRef* stackPtr = stack+1; // current stack pointer + NodeRef* stackEnd = stack+stackSize; + stack[0] = bvh->root; + + /* filter out invalid rays */ +#if defined(EMBREE_IGNORE_INVALID_RAYS) + if (!ray.valid()) return; +#endif + + /* verify correct input */ + assert(ray.valid()); + assert(ray.tnear() >= 0.0f); + assert(!(types & BVH_MB) || (ray.time() >= 0.0f && ray.time() <= 1.0f)); + + /* load the ray into SIMD registers */ + TravRay tray(ray.org, ray.dir, max(ray.tnear(), 0.0f), max(ray.tfar, 0.0f)); + + /* initialize the node traverser */ + BVHNNodeTraverser1Hit nodeTraverser; + + /* pop loop */ + while (true) pop: + { + /* pop next node */ + if (unlikely(stackPtr == stack)) break; + stackPtr--; + NodeRef cur = (NodeRef)*stackPtr; + + /* downtraversal loop */ + while (true) + { + /* intersect node */ + size_t mask; vfloat tNear; + STAT3(shadow.trav_nodes,1,1,1); + bool nodeIntersected = BVHNNodeIntersector1::intersect(cur, tray, ray.time(), tNear, mask); + if (unlikely(!nodeIntersected)) { STAT3(shadow.trav_nodes,-1,-1,-1); break; } + + /* if no child is hit, pop next node */ + if (unlikely(mask == 0)) + goto pop; + + /* select next child and push other children */ + nodeTraverser.traverseAnyHit(cur, mask, tNear, stackPtr, stackEnd); + } + + /* this is a leaf node */ + assert(cur != BVH::emptyNode); + STAT3(shadow.trav_leaves,1,1,1); + size_t num; Primitive* prim = (Primitive*)cur.leaf(num); + size_t lazy_node = 0; + if (PrimitiveIntersector1::occluded(This, pre, ray, context, prim, num, tray, lazy_node)) { + ray.tfar = neg_inf; + break; + } + + /* push lazy node onto stack */ + if (unlikely(lazy_node)) { + *stackPtr = (NodeRef)lazy_node; + stackPtr++; + } + } + } + + template + struct PointQueryDispatch + { + typedef typename PrimitiveIntersector1::Precalculations Precalculations; + typedef typename PrimitiveIntersector1::Primitive Primitive; + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::AABBNodeMB4D AABBNodeMB4D; + + static const size_t stackSize = 1+(N-1)*BVH::maxDepth+3; // +3 due to 16-wide store + + /* right now AVX512KNL SIMD extension only for standard node types */ + static const size_t Nx = (types == BVH_AN1 || types == BVH_QN1) ? vextend::size : N; + + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context) + { + const BVH* __restrict__ bvh = (const BVH*)This->ptr; + + /* we may traverse an empty BVH in case all geometry was invalid */ + if (bvh->root == BVH::emptyNode) + return false; + + /* stack state */ + StackItemT stack[stackSize]; // stack of nodes + StackItemT* stackPtr = stack+1; // current stack pointer + StackItemT* stackEnd = stack+stackSize; + stack[0].ptr = bvh->root; + stack[0].dist = neg_inf; + + /* verify correct input */ + assert(!(types & BVH_MB) || (query->time >= 0.0f && query->time <= 1.0f)); + + /* load the point query into SIMD registers */ + TravPointQuery tquery(query->p, context->query_radius); + + /* initialize the node traverser */ + BVHNNodeTraverser1Hit nodeTraverser; + + bool changed = false; + float cull_radius = context->query_type == POINT_QUERY_TYPE_SPHERE + ? query->radius * query->radius + : dot(context->query_radius, context->query_radius); + + /* pop loop */ + while (true) pop: + { + /* pop next node */ + if (unlikely(stackPtr == stack)) break; + stackPtr--; + NodeRef cur = NodeRef(stackPtr->ptr); + + /* if popped node is too far, pop next one */ + if (unlikely(*(float*)&stackPtr->dist > cull_radius)) + continue; + + /* downtraversal loop */ + while (true) + { + /* intersect node */ + size_t mask; vfloat tNear; + STAT3(point_query.trav_nodes,1,1,1); + bool nodeIntersected; + if (likely(context->query_type == POINT_QUERY_TYPE_SPHERE)) { + nodeIntersected = BVHNNodePointQuerySphere1::pointQuery(cur, tquery, query->time, tNear, mask); + } else { + nodeIntersected = BVHNNodePointQueryAABB1 ::pointQuery(cur, tquery, query->time, tNear, mask); + } + if (unlikely(!nodeIntersected)) { STAT3(point_query.trav_nodes,-1,-1,-1); break; } + + /* if no child is hit, pop next node */ + if (unlikely(mask == 0)) + goto pop; + + /* select next child and push other children */ + nodeTraverser.traverseClosestHit(cur, mask, tNear, stackPtr, stackEnd); + } + + /* this is a leaf node */ + assert(cur != BVH::emptyNode); + STAT3(point_query.trav_leaves,1,1,1); + size_t num; Primitive* prim = (Primitive*)cur.leaf(num); + size_t lazy_node = 0; + if (PrimitiveIntersector1::pointQuery(This, query, context, prim, num, tquery, lazy_node)) + { + changed = true; + tquery.rad = context->query_radius; + cull_radius = context->query_type == POINT_QUERY_TYPE_SPHERE + ? query->radius * query->radius + : dot(context->query_radius, context->query_radius); + } + + /* push lazy node onto stack */ + if (unlikely(lazy_node)) { + stackPtr->ptr = lazy_node; + stackPtr->dist = neg_inf; + stackPtr++; + } + } + return changed; + } + }; + + /* disable point queries for not yet supported geometry types */ + template + struct PointQueryDispatch { + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context) { return false; } + }; + + template + struct PointQueryDispatch { + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context) { return false; } + }; + + template + struct PointQueryDispatch { + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context) { return false; } + }; + + template + bool BVHNIntersector1::pointQuery( + const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context) + { + return PointQueryDispatch::pointQuery(This, query, context); + } + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_intersector1.h b/thirdparty/embree/kernels/bvh/bvh_intersector1.h new file mode 100644 index 000000000000..1a269c319a7b --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_intersector1.h @@ -0,0 +1,37 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include "../common/ray.h" +#include "../common/point_query.h" + +namespace embree +{ + namespace isa + { + /*! BVH single ray intersector. */ + template + class BVHNIntersector1 + { + /* shortcuts for frequently used types */ + typedef typename PrimitiveIntersector1::Precalculations Precalculations; + typedef typename PrimitiveIntersector1::Primitive Primitive; + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::AABBNodeMB4D AABBNodeMB4D; + + static const size_t stackSize = 1+(N-1)*BVH::maxDepth+3; // +3 due to 16-wide store + + /* right now AVX512KNL SIMD extension only for standard node types */ + static const size_t Nx = (types == BVH_AN1 || types == BVH_QN1) ? vextend::size : N; + + public: + static void intersect (const Accel::Intersectors* This, RayHit& ray, IntersectContext* context); + static void occluded (const Accel::Intersectors* This, Ray& ray, IntersectContext* context); + static bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context); + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_intersector1_bvh4.cpp b/thirdparty/embree/kernels/bvh/bvh_intersector1_bvh4.cpp new file mode 100644 index 000000000000..989f7354fda2 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_intersector1_bvh4.cpp @@ -0,0 +1,61 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_intersector1.cpp" + +namespace embree +{ + namespace isa + { + int getISA() { + return VerifyMultiTargetLinking::getISA(); + } + + //////////////////////////////////////////////////////////////////////////////// + /// BVH4Intersector1 Definitions + //////////////////////////////////////////////////////////////////////////////// + + IF_ENABLED_CURVES_OR_POINTS(DEFINE_INTERSECTOR1(BVH4OBBVirtualCurveIntersector1,BVHNIntersector1<4 COMMA BVH_AN1_UN1 COMMA false COMMA VirtualCurveIntersector1 >)); + IF_ENABLED_CURVES_OR_POINTS(DEFINE_INTERSECTOR1(BVH4OBBVirtualCurveIntersector1MB,BVHNIntersector1<4 COMMA BVH_AN2_AN4D_UN2 COMMA false COMMA VirtualCurveIntersector1 >)); + + IF_ENABLED_CURVES_OR_POINTS(DEFINE_INTERSECTOR1(BVH4OBBVirtualCurveIntersectorRobust1,BVHNIntersector1<4 COMMA BVH_AN1_UN1 COMMA true COMMA VirtualCurveIntersector1 >)); + IF_ENABLED_CURVES_OR_POINTS(DEFINE_INTERSECTOR1(BVH4OBBVirtualCurveIntersectorRobust1MB,BVHNIntersector1<4 COMMA BVH_AN2_AN4D_UN2 COMMA true COMMA VirtualCurveIntersector1 >)); + + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4Intersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4iIntersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4vIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN1 COMMA true COMMA ArrayIntersector1 > >)); + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4iIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN1 COMMA true COMMA ArrayIntersector1 > >)); + + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4vMBIntersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4iMBIntersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4vMBIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA true COMMA ArrayIntersector1 > >)); + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(BVH4Triangle4iMBIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA true COMMA ArrayIntersector1 > >)); + + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(BVH4Quad4vIntersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(BVH4Quad4iIntersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(BVH4Quad4vIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN1 COMMA true COMMA ArrayIntersector1 > >)); + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(BVH4Quad4iIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN1 COMMA true COMMA ArrayIntersector1 > >)); + + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(BVH4Quad4iMBIntersector1Moeller, BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(BVH4Quad4iMBIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA true COMMA ArrayIntersector1 > >)); + + IF_ENABLED_SUBDIV(DEFINE_INTERSECTOR1(BVH4SubdivPatch1Intersector1,BVHNIntersector1<4 COMMA BVH_AN1 COMMA true COMMA SubdivPatch1Intersector1>)); + IF_ENABLED_SUBDIV(DEFINE_INTERSECTOR1(BVH4SubdivPatch1MBIntersector1,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA true COMMA SubdivPatch1MBIntersector1>)); + + IF_ENABLED_USER(DEFINE_INTERSECTOR1(BVH4VirtualIntersector1,BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA ArrayIntersector1> >)); + IF_ENABLED_USER(DEFINE_INTERSECTOR1(BVH4VirtualMBIntersector1,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA false COMMA ArrayIntersector1> >)); + + IF_ENABLED_INSTANCE(DEFINE_INTERSECTOR1(BVH4InstanceIntersector1,BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA ArrayIntersector1 >)); + IF_ENABLED_INSTANCE(DEFINE_INTERSECTOR1(BVH4InstanceMBIntersector1,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA false COMMA ArrayIntersector1 >)); + + IF_ENABLED_TRIS(DEFINE_INTERSECTOR1(QBVH4Triangle4iIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_QN1 COMMA false COMMA ArrayIntersector1 > >)); + IF_ENABLED_QUADS(DEFINE_INTERSECTOR1(QBVH4Quad4iIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_QN1 COMMA false COMMA ArrayIntersector1 > >)); + + IF_ENABLED_GRIDS(DEFINE_INTERSECTOR1(BVH4GridIntersector1Moeller,BVHNIntersector1<4 COMMA BVH_AN1 COMMA false COMMA SubGridIntersector1Moeller<4 COMMA true> >)); + IF_ENABLED_GRIDS(DEFINE_INTERSECTOR1(BVH4GridMBIntersector1Moeller,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA true COMMA SubGridMBIntersector1Pluecker<4 COMMA true> >)); + + IF_ENABLED_GRIDS(DEFINE_INTERSECTOR1(BVH4GridIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN1 COMMA true COMMA SubGridIntersector1Pluecker<4 COMMA true> >)); + //IF_ENABLED_GRIDS(DEFINE_INTERSECTOR1(BVH4GridMBIntersector1Pluecker,BVHNIntersector1<4 COMMA BVH_AN2_AN4D COMMA false COMMA SubGridMBIntersector1Pluecker<4 COMMA true> >)); + + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_intersector_hybrid.h b/thirdparty/embree/kernels/bvh/bvh_intersector_hybrid.h new file mode 100644 index 000000000000..d764cc928d7e --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_intersector_hybrid.h @@ -0,0 +1,61 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include "../common/ray.h" +#include "../common/stack_item.h" +#include "node_intersector_frustum.h" + +namespace embree +{ + namespace isa + { + template + struct TravRayK; + + /*! BVH hybrid packet intersector. Switches between packet and single ray traversal (optional). */ + template + class BVHNIntersectorKHybrid + { + /* right now AVX512KNL SIMD extension only for standard node types */ + static const size_t Nx = types == BVH_AN1 ? vextend::size : N; + + /* shortcuts for frequently used types */ + typedef typename PrimitiveIntersectorK::Precalculations Precalculations; + typedef typename PrimitiveIntersectorK::Primitive Primitive; + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::BaseNode BaseNode; + typedef typename BVH::AABBNode AABBNode; + + static const size_t stackSizeSingle = 1+(N-1)*BVH::maxDepth+3; // +3 due to 16-wide store + static const size_t stackSizeChunk = 1+(N-1)*BVH::maxDepth; + + static const size_t switchThresholdIncoherent = \ + (K==4) ? 3 : + (K==8) ? ((N==4) ? 5 : 7) : + (K==16) ? 14 : // 14 seems to work best for KNL due to better ordered chunk traversal + 0; + + private: + static void intersect1(Accel::Intersectors* This, const BVH* bvh, NodeRef root, size_t k, Precalculations& pre, + RayHitK& ray, const TravRayK& tray, IntersectContext* context); + static bool occluded1(Accel::Intersectors* This, const BVH* bvh, NodeRef root, size_t k, Precalculations& pre, + RayK& ray, const TravRayK& tray, IntersectContext* context); + + public: + static void intersect(vint* valid, Accel::Intersectors* This, RayHitK& ray, IntersectContext* context); + static void occluded (vint* valid, Accel::Intersectors* This, RayK& ray, IntersectContext* context); + + static void intersectCoherent(vint* valid, Accel::Intersectors* This, RayHitK& ray, IntersectContext* context); + static void occludedCoherent (vint* valid, Accel::Intersectors* This, RayK& ray, IntersectContext* context); + + }; + + /*! BVH packet intersector. */ + template + class BVHNIntersectorKChunk : public BVHNIntersectorKHybrid {}; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_intersector_stream.h b/thirdparty/embree/kernels/bvh/bvh_intersector_stream.h new file mode 100644 index 000000000000..f5beb6ca9110 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_intersector_stream.h @@ -0,0 +1,284 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "node_intersector_packet_stream.h" +#include "node_intersector_frustum.h" +#include "bvh_traverser_stream.h" + +namespace embree +{ + namespace isa + { + /*! BVH ray stream intersector. */ + template + class BVHNIntersectorStream + { + static const int Nxd = (Nx == N) ? N : Nx/2; + + /* shortcuts for frequently used types */ + template using PrimitiveIntersectorK = typename PrimitiveIntersector::template Type; + template using PrimitiveK = typename PrimitiveIntersectorK::PrimitiveK; + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::BaseNode BaseNode; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::AABBNodeMB AABBNodeMB; + + template + __forceinline static size_t initPacketsAndFrustum(RayK** inputPackets, size_t numOctantRays, + TravRayKStream* packets, Frustum& frustum, bool& commonOctant) + { + const size_t numPackets = (numOctantRays+K-1)/K; + + Vec3vf tmp_min_rdir(pos_inf); + Vec3vf tmp_max_rdir(neg_inf); + Vec3vf tmp_min_org(pos_inf); + Vec3vf tmp_max_org(neg_inf); + vfloat tmp_min_dist(pos_inf); + vfloat tmp_max_dist(neg_inf); + + size_t m_active = 0; + for (size_t i = 0; i < numPackets; i++) + { + const vfloat tnear = inputPackets[i]->tnear(); + const vfloat tfar = inputPackets[i]->tfar; + vbool m_valid = (tnear <= tfar) & (tnear >= 0.0f); + +#if defined(EMBREE_IGNORE_INVALID_RAYS) + m_valid &= inputPackets[i]->valid(); +#endif + + m_active |= (size_t)movemask(m_valid) << (i*K); + + vfloat packet_min_dist = max(tnear, 0.0f); + vfloat packet_max_dist = select(m_valid, tfar, neg_inf); + tmp_min_dist = min(tmp_min_dist, packet_min_dist); + tmp_max_dist = max(tmp_max_dist, packet_max_dist); + + const Vec3vf& org = inputPackets[i]->org; + const Vec3vf& dir = inputPackets[i]->dir; + + new (&packets[i]) TravRayKStream(org, dir, packet_min_dist, packet_max_dist); + + tmp_min_rdir = min(tmp_min_rdir, select(m_valid, packets[i].rdir, Vec3vf(pos_inf))); + tmp_max_rdir = max(tmp_max_rdir, select(m_valid, packets[i].rdir, Vec3vf(neg_inf))); + tmp_min_org = min(tmp_min_org , select(m_valid,org , Vec3vf(pos_inf))); + tmp_max_org = max(tmp_max_org , select(m_valid,org , Vec3vf(neg_inf))); + } + + m_active &= (numOctantRays == (8 * sizeof(size_t))) ? (size_t)-1 : (((size_t)1 << numOctantRays)-1); + + + const Vec3fa reduced_min_rdir(reduce_min(tmp_min_rdir.x), + reduce_min(tmp_min_rdir.y), + reduce_min(tmp_min_rdir.z)); + + const Vec3fa reduced_max_rdir(reduce_max(tmp_max_rdir.x), + reduce_max(tmp_max_rdir.y), + reduce_max(tmp_max_rdir.z)); + + const Vec3fa reduced_min_origin(reduce_min(tmp_min_org.x), + reduce_min(tmp_min_org.y), + reduce_min(tmp_min_org.z)); + + const Vec3fa reduced_max_origin(reduce_max(tmp_max_org.x), + reduce_max(tmp_max_org.y), + reduce_max(tmp_max_org.z)); + + commonOctant = + (reduced_max_rdir.x < 0.0f || reduced_min_rdir.x >= 0.0f) && + (reduced_max_rdir.y < 0.0f || reduced_min_rdir.y >= 0.0f) && + (reduced_max_rdir.z < 0.0f || reduced_min_rdir.z >= 0.0f); + + const float frustum_min_dist = reduce_min(tmp_min_dist); + const float frustum_max_dist = reduce_max(tmp_max_dist); + + frustum.init(reduced_min_origin, reduced_max_origin, + reduced_min_rdir, reduced_max_rdir, + frustum_min_dist, frustum_max_dist, + N); + + return m_active; + } + + template + __forceinline static size_t intersectAABBNodePacket(size_t m_active, + const TravRayKStream* packets, + const AABBNode* __restrict__ node, + size_t boxID, + const NearFarPrecalculations& nf) + { + assert(m_active); + const size_t startPacketID = bsf(m_active) / K; + const size_t endPacketID = bsr(m_active) / K; + size_t m_trav_active = 0; + for (size_t i = startPacketID; i <= endPacketID; i++) + { + const size_t m_hit = intersectNodeK(node, boxID, packets[i], nf); + m_trav_active |= m_hit << (i*K); + } + return m_trav_active; + } + + template + __forceinline static size_t traverseCoherentStream(size_t m_active, + TravRayKStream* packets, + const AABBNode* __restrict__ node, + const Frustum& frustum, + size_t* maskK, + vfloat& dist) + { + size_t m_node_hit = intersectNodeFrustum(node, frustum, dist); + const size_t first_index = bsf(m_active); + const size_t first_packetID = first_index / K; + const size_t first_rayID = first_index % K; + size_t m_first_hit = intersectNode1(node, packets[first_packetID], first_rayID, frustum.nf); + + /* this make traversal independent of the ordering of rays */ + size_t m_node = m_node_hit ^ m_first_hit; + while (unlikely(m_node)) + { + const size_t boxID = bscf(m_node); + const size_t m_current = m_active & intersectAABBNodePacket(m_active, packets, node, boxID, frustum.nf); + m_node_hit ^= m_current ? (size_t)0 : ((size_t)1 << boxID); + maskK[boxID] = m_current; + } + return m_node_hit; + } + + // TODO: explicit 16-wide path for KNL + template + __forceinline static vint traverseIncoherentStream(size_t m_active, + TravRayKStreamFast* __restrict__ packets, + const AABBNode* __restrict__ node, + const NearFarPrecalculations& nf, + const int shiftTable[32]) + { + const vfloat bminX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearX)); + const vfloat bminY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearY)); + const vfloat bminZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearZ)); + const vfloat bmaxX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farX)); + const vfloat bmaxY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farY)); + const vfloat bmaxZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farZ)); + assert(m_active); + vint vmask(zero); + do + { + STAT3(shadow.trav_nodes,1,1,1); + const size_t rayID = bscf(m_active); + assert(rayID < MAX_INTERNAL_STREAM_SIZE); + TravRayKStream &p = packets[rayID / K]; + const size_t i = rayID % K; + const vint bitmask(shiftTable[rayID]); + const vfloat tNearX = msub(bminX, p.rdir.x[i], p.org_rdir.x[i]); + const vfloat tNearY = msub(bminY, p.rdir.y[i], p.org_rdir.y[i]); + const vfloat tNearZ = msub(bminZ, p.rdir.z[i], p.org_rdir.z[i]); + const vfloat tFarX = msub(bmaxX, p.rdir.x[i], p.org_rdir.x[i]); + const vfloat tFarY = msub(bmaxY, p.rdir.y[i], p.org_rdir.y[i]); + const vfloat tFarZ = msub(bmaxZ, p.rdir.z[i], p.org_rdir.z[i]); + const vfloat tNear = maxi(tNearX, tNearY, tNearZ, vfloat(p.tnear[i])); + const vfloat tFar = mini(tFarX , tFarY , tFarZ, vfloat(p.tfar[i])); + +#if defined(__AVX512ER__) + const vboolx m_node((1 << N)-1); + const vbool hit_mask = le(m_node, tNear, tFar); + vmask = mask_or(hit_mask, vmask, vmask, bitmask); +#else + const vbool hit_mask = tNear <= tFar; +#if defined(__AVX2__) + vmask = vmask | (bitmask & vint(hit_mask)); +#else + vmask = select(hit_mask, vmask | bitmask, vmask); +#endif +#endif + } while(m_active); + return vmask; + } + + template + __forceinline static vint traverseIncoherentStream(size_t m_active, + TravRayKStreamRobust* __restrict__ packets, + const AABBNode* __restrict__ node, + const NearFarPrecalculations& nf, + const int shiftTable[32]) + { + const vfloat bminX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearX)); + const vfloat bminY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearY)); + const vfloat bminZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearZ)); + const vfloat bmaxX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farX)); + const vfloat bmaxY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farY)); + const vfloat bmaxZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farZ)); + assert(m_active); + vint vmask(zero); + do + { + STAT3(shadow.trav_nodes,1,1,1); + const size_t rayID = bscf(m_active); + assert(rayID < MAX_INTERNAL_STREAM_SIZE); + TravRayKStream &p = packets[rayID / K]; + const size_t i = rayID % K; + const vint bitmask(shiftTable[rayID]); + const vfloat tNearX = (bminX - p.org.x[i]) * p.rdir.x[i]; + const vfloat tNearY = (bminY - p.org.y[i]) * p.rdir.y[i]; + const vfloat tNearZ = (bminZ - p.org.z[i]) * p.rdir.z[i]; + const vfloat tFarX = (bmaxX - p.org.x[i]) * p.rdir.x[i]; + const vfloat tFarY = (bmaxY - p.org.y[i]) * p.rdir.y[i]; + const vfloat tFarZ = (bmaxZ - p.org.z[i]) * p.rdir.z[i]; + const vfloat tNear = maxi(tNearX, tNearY, tNearZ, vfloat(p.tnear[i])); + const vfloat tFar = mini(tFarX , tFarY , tFarZ, vfloat(p.tfar[i])); + const float round_down = 1.0f-2.0f*float(ulp); + const float round_up = 1.0f+2.0f*float(ulp); +#if defined(__AVX512ER__) + const vboolx m_node((1 << N)-1); + const vbool hit_mask = le(m_node, round_down*tNear, round_up*tFar); + vmask = mask_or(hit_mask, vmask, vmask, bitmask); +#else + const vbool hit_mask = round_down*tNear <= round_up*tFar; +#if defined(__AVX2__) + vmask = vmask | (bitmask & vint(hit_mask)); +#else + vmask = select(hit_mask, vmask | bitmask, vmask); +#endif +#endif + } while(m_active); + return vmask; + } + + + static const size_t stackSizeSingle = 1+(N-1)*BVH::maxDepth; + + public: + static void intersect(Accel::Intersectors* This, RayHitN** inputRays, size_t numRays, IntersectContext* context); + static void occluded (Accel::Intersectors* This, RayN** inputRays, size_t numRays, IntersectContext* context); + + private: + template + static void intersectCoherent(Accel::Intersectors* This, RayHitK** inputRays, size_t numRays, IntersectContext* context); + + template + static void occludedCoherent(Accel::Intersectors* This, RayK** inputRays, size_t numRays, IntersectContext* context); + + template + static void occludedIncoherent(Accel::Intersectors* This, RayK** inputRays, size_t numRays, IntersectContext* context); + }; + + + /*! BVH ray stream intersector with direct fallback to packets. */ + template + class BVHNIntersectorStreamPacketFallback + { + public: + static void intersect(Accel::Intersectors* This, RayHitN** inputRays, size_t numRays, IntersectContext* context); + static void occluded (Accel::Intersectors* This, RayN** inputRays, size_t numRays, IntersectContext* context); + + private: + template + static void intersectK(Accel::Intersectors* This, RayHitK** inputRays, size_t numRays, IntersectContext* context); + + template + static void occludedK(Accel::Intersectors* This, RayK** inputRays, size_t numRays, IntersectContext* context); + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_intersector_stream_filters.h b/thirdparty/embree/kernels/bvh/bvh_intersector_stream_filters.h new file mode 100644 index 000000000000..cdeb9236370e --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_intersector_stream_filters.h @@ -0,0 +1,41 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "../common/ray.h" +#include "../common/scene.h" + +namespace embree +{ + namespace isa + { + class RayStreamFilter + { + public: + static void intersectAOS(Scene* scene, RTCRayHit* rays, size_t N, size_t stride, IntersectContext* context); + static void intersectAOP(Scene* scene, RTCRayHit** rays, size_t N, IntersectContext* context); + static void intersectSOA(Scene* scene, char* rays, size_t N, size_t numPackets, size_t stride, IntersectContext* context); + static void intersectSOP(Scene* scene, const RTCRayHitNp* rays, size_t N, IntersectContext* context); + + static void occludedAOS(Scene* scene, RTCRay* rays, size_t N, size_t stride, IntersectContext* context); + static void occludedAOP(Scene* scene, RTCRay** rays, size_t N, IntersectContext* context); + static void occludedSOA(Scene* scene, char* rays, size_t N, size_t numPackets, size_t stride, IntersectContext* context); + static void occludedSOP(Scene* scene, const RTCRayNp* rays, size_t N, IntersectContext* context); + + private: + template + static void filterAOS(Scene* scene, void* rays, size_t N, size_t stride, IntersectContext* context); + + template + static void filterAOP(Scene* scene, void** rays, size_t N, IntersectContext* context); + + template + static void filterSOA(Scene* scene, char* rays, size_t N, size_t numPackets, size_t stride, IntersectContext* context); + + template + static void filterSOP(Scene* scene, const void* rays, size_t N, IntersectContext* context); + }; + } +}; diff --git a/thirdparty/embree/kernels/bvh/bvh_node_aabb.h b/thirdparty/embree/kernels/bvh/bvh_node_aabb.h new file mode 100644 index 000000000000..baa4a8d80539 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_aabb.h @@ -0,0 +1,213 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_node_base.h" + +namespace embree +{ + /*! BVHN AABBNode */ + template + struct AABBNode_t : public BaseNode_t + { + using BaseNode_t::children; + + struct Create + { + __forceinline NodeRef operator() (const FastAllocator::CachedAllocator& alloc, size_t numChildren = 0) const + { + AABBNode_t* node = (AABBNode_t*) alloc.malloc0(sizeof(AABBNode_t),NodeRef::byteNodeAlignment); node->clear(); + return NodeRef::encodeNode(node); + } + }; + + struct Set + { + __forceinline void operator() (NodeRef node, size_t i, NodeRef child, const BBox3fa& bounds) const { + node.getAABBNode()->setRef(i,child); + node.getAABBNode()->setBounds(i,bounds); + } + }; + + struct Create2 + { + template + __forceinline NodeRef operator() (BuildRecord* children, const size_t num, const FastAllocator::CachedAllocator& alloc) const + { + AABBNode_t* node = (AABBNode_t*) alloc.malloc0(sizeof(AABBNode_t), NodeRef::byteNodeAlignment); node->clear(); + for (size_t i=0; isetBounds(i,children[i].bounds()); + return NodeRef::encodeNode(node); + } + }; + + struct Set2 + { + template + __forceinline NodeRef operator() (const BuildRecord& precord, const BuildRecord* crecords, NodeRef ref, NodeRef* children, const size_t num) const + { + AABBNode_t* node = ref.getAABBNode(); + for (size_t i=0; isetRef(i,children[i]); + return ref; + } + }; + + struct Set3 + { + Set3 (FastAllocator* allocator, PrimRef* prims) + : allocator(allocator), prims(prims) {} + + template + __forceinline NodeRef operator() (const BuildRecord& precord, const BuildRecord* crecords, NodeRef ref, NodeRef* children, const size_t num) const + { + AABBNode_t* node = ref.getAABBNode(); + for (size_t i=0; isetRef(i,children[i]); + + if (unlikely(precord.alloc_barrier)) + { + PrimRef* begin = &prims[precord.prims.begin()]; + PrimRef* end = &prims[precord.prims.end()]; // FIXME: extended end for spatial split builder!!!!! + size_t bytes = (size_t)end - (size_t)begin; + allocator->addBlock(begin,bytes); + } + + return ref; + } + + FastAllocator* const allocator; + PrimRef* const prims; + }; + + /*! Clears the node. */ + __forceinline void clear() { + lower_x = lower_y = lower_z = pos_inf; + upper_x = upper_y = upper_z = neg_inf; + BaseNode_t::clear(); + } + + /*! Sets bounding box and ID of child. */ + __forceinline void setRef(size_t i, const NodeRef& ref) { + assert(i < N); + children[i] = ref; + } + + /*! Sets bounding box of child. */ + __forceinline void setBounds(size_t i, const BBox3fa& bounds) + { + assert(i < N); + lower_x[i] = bounds.lower.x; lower_y[i] = bounds.lower.y; lower_z[i] = bounds.lower.z; + upper_x[i] = bounds.upper.x; upper_y[i] = bounds.upper.y; upper_z[i] = bounds.upper.z; + } + + /*! Sets bounding box and ID of child. */ + __forceinline void set(size_t i, const NodeRef& ref, const BBox3fa& bounds) { + setBounds(i,bounds); + children[i] = ref; + } + + /*! Returns bounds of node. */ + __forceinline BBox3fa bounds() const { + const Vec3fa lower(reduce_min(lower_x),reduce_min(lower_y),reduce_min(lower_z)); + const Vec3fa upper(reduce_max(upper_x),reduce_max(upper_y),reduce_max(upper_z)); + return BBox3fa(lower,upper); + } + + /*! Returns bounds of specified child. */ + __forceinline BBox3fa bounds(size_t i) const + { + assert(i < N); + const Vec3fa lower(lower_x[i],lower_y[i],lower_z[i]); + const Vec3fa upper(upper_x[i],upper_y[i],upper_z[i]); + return BBox3fa(lower,upper); + } + + /*! Returns extent of bounds of specified child. */ + __forceinline Vec3fa extend(size_t i) const { + return bounds(i).size(); + } + + /*! Returns bounds of all children (implemented later as specializations) */ + __forceinline void bounds(BBox& bounds0, BBox& bounds1, BBox& bounds2, BBox& bounds3) const; + + /*! swap two children of the node */ + __forceinline void swap(size_t i, size_t j) + { + assert(ichildren[i],b->children[j]); + std::swap(a->lower_x[i],b->lower_x[j]); + std::swap(a->lower_y[i],b->lower_y[j]); + std::swap(a->lower_z[i],b->lower_z[j]); + std::swap(a->upper_x[i],b->upper_x[j]); + std::swap(a->upper_y[i],b->upper_y[j]); + std::swap(a->upper_z[i],b->upper_z[j]); + } + + /*! compacts a node (moves empty children to the end) */ + __forceinline static void compact(AABBNode_t* a) + { + /* find right most filled node */ + ssize_t j=N; + for (j=j-1; j>=0; j--) + if (a->child(j) != NodeRef::emptyNode) + break; + + /* replace empty nodes with filled nodes */ + for (ssize_t i=0; ichild(i) == NodeRef::emptyNode) { + a->swap(i,j); + for (j=j-1; j>i; j--) + if (a->child(j) != NodeRef::emptyNode) + break; + } + } + } + + /*! Returns reference to specified child */ + __forceinline NodeRef& child(size_t i) { assert(i lower_x; //!< X dimension of lower bounds of all N children. + vfloat upper_x; //!< X dimension of upper bounds of all N children. + vfloat lower_y; //!< Y dimension of lower bounds of all N children. + vfloat upper_y; //!< Y dimension of upper bounds of all N children. + vfloat lower_z; //!< Z dimension of lower bounds of all N children. + vfloat upper_z; //!< Z dimension of upper bounds of all N children. + }; + + template<> + __forceinline void AABBNode_t,4>::bounds(BBox& bounds0, BBox& bounds1, BBox& bounds2, BBox& bounds3) const { + transpose(lower_x,lower_y,lower_z,vfloat4(zero),bounds0.lower,bounds1.lower,bounds2.lower,bounds3.lower); + transpose(upper_x,upper_y,upper_z,vfloat4(zero),bounds0.upper,bounds1.upper,bounds2.upper,bounds3.upper); + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_node_aabb_mb.h b/thirdparty/embree/kernels/bvh/bvh_node_aabb_mb.h new file mode 100644 index 000000000000..501f4bce5bde --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_aabb_mb.h @@ -0,0 +1,247 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_node_base.h" + +namespace embree +{ + /*! Motion Blur AABBNode */ + template + struct AABBNodeMB_t : public BaseNode_t + { + using BaseNode_t::children; + typedef BVHNodeRecord NodeRecord; + typedef BVHNodeRecordMB NodeRecordMB; + typedef BVHNodeRecordMB4D NodeRecordMB4D; + + struct Create + { + template + __forceinline NodeRef operator() (BuildRecord* children, const size_t num, const FastAllocator::CachedAllocator& alloc) const + { + AABBNodeMB_t* node = (AABBNodeMB_t*) alloc.malloc0(sizeof(AABBNodeMB_t),NodeRef::byteNodeAlignment); node->clear(); + return NodeRef::encodeNode(node); + } + }; + + struct Set + { + template + __forceinline NodeRecordMB operator() (const BuildRecord& precord, const BuildRecord* crecords, NodeRef ref, NodeRecordMB* children, const size_t num) const + { + AABBNodeMB_t* node = ref.getAABBNodeMB(); + + LBBox3fa bounds = empty; + for (size_t i=0; isetRef(i,children[i].ref); + node->setBounds(i,children[i].lbounds); + bounds.extend(children[i].lbounds); + } + return NodeRecordMB(ref,bounds); + } + }; + + struct SetTimeRange + { + __forceinline SetTimeRange(BBox1f tbounds) : tbounds(tbounds) {} + + template + __forceinline NodeRecordMB operator() (const BuildRecord& precord, const BuildRecord* crecords, NodeRef ref, NodeRecordMB* children, const size_t num) const + { + AABBNodeMB_t* node = ref.getAABBNodeMB(); + + LBBox3fa bounds = empty; + for (size_t i=0; isetRef(i, children[i].ref); + node->setBounds(i, children[i].lbounds, tbounds); + bounds.extend(children[i].lbounds); + } + return NodeRecordMB(ref,bounds); + } + + BBox1f tbounds; + }; + + /*! Clears the node. */ + __forceinline void clear() { + lower_x = lower_y = lower_z = vfloat(pos_inf); + upper_x = upper_y = upper_z = vfloat(neg_inf); + lower_dx = lower_dy = lower_dz = vfloat(0.0f); + upper_dx = upper_dy = upper_dz = vfloat(0.0f); + BaseNode_t::clear(); + } + + /*! Sets ID of child. */ + __forceinline void setRef(size_t i, NodeRef ref) { + children[i] = ref; + } + + /*! Sets bounding box of child. */ + __forceinline void setBounds(size_t i, const BBox3fa& bounds0_i, const BBox3fa& bounds1_i) + { + /*! for empty bounds we have to avoid inf-inf=nan */ + BBox3fa bounds0(min(bounds0_i.lower,Vec3fa(+FLT_MAX)),max(bounds0_i.upper,Vec3fa(-FLT_MAX))); + BBox3fa bounds1(min(bounds1_i.lower,Vec3fa(+FLT_MAX)),max(bounds1_i.upper,Vec3fa(-FLT_MAX))); + bounds0 = bounds0.enlarge_by(4.0f*float(ulp)); + bounds1 = bounds1.enlarge_by(4.0f*float(ulp)); + Vec3fa dlower = bounds1.lower-bounds0.lower; + Vec3fa dupper = bounds1.upper-bounds0.upper; + + lower_x[i] = bounds0.lower.x; lower_y[i] = bounds0.lower.y; lower_z[i] = bounds0.lower.z; + upper_x[i] = bounds0.upper.x; upper_y[i] = bounds0.upper.y; upper_z[i] = bounds0.upper.z; + + lower_dx[i] = dlower.x; lower_dy[i] = dlower.y; lower_dz[i] = dlower.z; + upper_dx[i] = dupper.x; upper_dy[i] = dupper.y; upper_dz[i] = dupper.z; + } + + /*! Sets bounding box of child. */ + __forceinline void setBounds(size_t i, const LBBox3fa& bounds) { + setBounds(i, bounds.bounds0, bounds.bounds1); + } + + /*! Sets bounding box of child. */ + __forceinline void setBounds(size_t i, const LBBox3fa& bounds, const BBox1f& tbounds) { + setBounds(i, bounds.global(tbounds)); + } + + /*! Sets bounding box and ID of child. */ + __forceinline void set(size_t i, NodeRef ref, const BBox3fa& bounds) { + lower_x[i] = bounds.lower.x; lower_y[i] = bounds.lower.y; lower_z[i] = bounds.lower.z; + upper_x[i] = bounds.upper.x; upper_y[i] = bounds.upper.y; upper_z[i] = bounds.upper.z; + children[i] = ref; + } + + /*! Sets bounding box and ID of child. */ + __forceinline void set(size_t i, const NodeRecordMB4D& child) + { + setRef(i, child.ref); + setBounds(i, child.lbounds, child.dt); + } + + /*! Return bounding box for time 0 */ + __forceinline BBox3fa bounds0(size_t i) const { + return BBox3fa(Vec3fa(lower_x[i],lower_y[i],lower_z[i]), + Vec3fa(upper_x[i],upper_y[i],upper_z[i])); + } + + /*! Return bounding box for time 1 */ + __forceinline BBox3fa bounds1(size_t i) const { + return BBox3fa(Vec3fa(lower_x[i]+lower_dx[i],lower_y[i]+lower_dy[i],lower_z[i]+lower_dz[i]), + Vec3fa(upper_x[i]+upper_dx[i],upper_y[i]+upper_dy[i],upper_z[i]+upper_dz[i])); + } + + /*! Returns bounds of node. */ + __forceinline BBox3fa bounds() const { + return BBox3fa(Vec3fa(reduce_min(min(lower_x,lower_x+lower_dx)), + reduce_min(min(lower_y,lower_y+lower_dy)), + reduce_min(min(lower_z,lower_z+lower_dz))), + Vec3fa(reduce_max(max(upper_x,upper_x+upper_dx)), + reduce_max(max(upper_y,upper_y+upper_dy)), + reduce_max(max(upper_z,upper_z+upper_dz)))); + } + + /*! Return bounding box of child i */ + __forceinline BBox3fa bounds(size_t i) const { + return merge(bounds0(i),bounds1(i)); + } + + /*! Return linear bounding box of child i */ + __forceinline LBBox3fa lbounds(size_t i) const { + return LBBox3fa(bounds0(i),bounds1(i)); + } + + /*! Return bounding box of child i at specified time */ + __forceinline BBox3fa bounds(size_t i, float time) const { + return lerp(bounds0(i),bounds1(i),time); + } + + /*! Returns the expected surface area when randomly sampling the time. */ + __forceinline float expectedHalfArea(size_t i) const { + return lbounds(i).expectedHalfArea(); + } + + /*! Returns the expected surface area when randomly sampling the time. */ + __forceinline float expectedHalfArea(size_t i, const BBox1f& t0t1) const { + return lbounds(i).expectedHalfArea(t0t1); + } + + /*! swap two children of the node */ + __forceinline void swap(size_t i, size_t j) + { + assert(i=0; j--) + if (a->child(j) != NodeRef::emptyNode) + break; + + /* replace empty nodes with filled nodes */ + for (ssize_t i=0; ichild(i) == NodeRef::emptyNode) { + a->swap(i,j); + for (j=j-1; j>i; j--) + if (a->child(j) != NodeRef::emptyNode) + break; + } + } + } + + /*! Returns reference to specified child */ + __forceinline NodeRef& child(size_t i) { assert(i lower_x; //!< X dimension of lower bounds of all N children. + vfloat upper_x; //!< X dimension of upper bounds of all N children. + vfloat lower_y; //!< Y dimension of lower bounds of all N children. + vfloat upper_y; //!< Y dimension of upper bounds of all N children. + vfloat lower_z; //!< Z dimension of lower bounds of all N children. + vfloat upper_z; //!< Z dimension of upper bounds of all N children. + + vfloat lower_dx; //!< X dimension of lower bounds of all N children. + vfloat upper_dx; //!< X dimension of upper bounds of all N children. + vfloat lower_dy; //!< Y dimension of lower bounds of all N children. + vfloat upper_dy; //!< Y dimension of upper bounds of all N children. + vfloat lower_dz; //!< Z dimension of lower bounds of all N children. + vfloat upper_dz; //!< Z dimension of upper bounds of all N children. + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_node_aabb_mb4d.h b/thirdparty/embree/kernels/bvh/bvh_node_aabb_mb4d.h new file mode 100644 index 000000000000..e968bbbc399f --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_aabb_mb4d.h @@ -0,0 +1,107 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_node_aabb_mb.h" + +namespace embree +{ + /*! Aligned 4D Motion Blur Node */ + template + struct AABBNodeMB4D_t : public AABBNodeMB_t + { + using BaseNode_t::children; + using AABBNodeMB_t::set; + + typedef BVHNodeRecord NodeRecord; + typedef BVHNodeRecordMB NodeRecordMB; + typedef BVHNodeRecordMB4D NodeRecordMB4D; + + struct Create + { + template + __forceinline NodeRef operator() (BuildRecord*, const size_t, const FastAllocator::CachedAllocator& alloc, bool hasTimeSplits = true) const + { + if (hasTimeSplits) + { + AABBNodeMB4D_t* node = (AABBNodeMB4D_t*) alloc.malloc0(sizeof(AABBNodeMB4D_t),NodeRef::byteNodeAlignment); node->clear(); + return NodeRef::encodeNode(node); + } + else + { + AABBNodeMB_t* node = (AABBNodeMB_t*) alloc.malloc0(sizeof(AABBNodeMB_t),NodeRef::byteNodeAlignment); node->clear(); + return NodeRef::encodeNode(node); + } + } + }; + + struct Set + { + template + __forceinline void operator() (const BuildRecord&, const BuildRecord*, NodeRef ref, NodeRecordMB4D* children, const size_t num) const + { + if (likely(ref.isAABBNodeMB())) { + for (size_t i=0; iset(i, children[i]); + } else { + for (size_t i=0; iset(i, children[i]); + } + } + }; + + /*! Clears the node. */ + __forceinline void clear() { + lower_t = vfloat(pos_inf); + upper_t = vfloat(neg_inf); + AABBNodeMB_t::clear(); + } + + /*! Sets bounding box of child. */ + __forceinline void setBounds(size_t i, const LBBox3fa& bounds, const BBox1f& tbounds) + { + AABBNodeMB_t::setBounds(i, bounds.global(tbounds)); + lower_t[i] = tbounds.lower; + upper_t[i] = tbounds.upper == 1.0f ? 1.0f+float(ulp) : tbounds.upper; + } + + /*! Sets bounding box and ID of child. */ + __forceinline void set(size_t i, const NodeRecordMB4D& child) { + AABBNodeMB_t::setRef(i,child.ref); + setBounds(i, child.lbounds, child.dt); + } + + /*! Returns the expected surface area when randomly sampling the time. */ + __forceinline float expectedHalfArea(size_t i) const { + return AABBNodeMB_t::lbounds(i).expectedHalfArea(timeRange(i)); + } + + /*! returns time range for specified child */ + __forceinline BBox1f timeRange(size_t i) const { + return BBox1f(lower_t[i],upper_t[i]); + } + + /*! stream output operator */ + friend embree_ostream operator<<(embree_ostream cout, const AABBNodeMB4D_t& n) + { + cout << "AABBNodeMB4D {" << embree_endl; + for (size_t i=0; i lower_t; //!< time dimension of lower bounds of all N children + vfloat upper_t; //!< time dimension of upper bounds of all N children + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_node_base.h b/thirdparty/embree/kernels/bvh/bvh_node_base.h new file mode 100644 index 000000000000..8268f3b93289 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_base.h @@ -0,0 +1,43 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_node_ref.h" + +namespace embree +{ + + /*! BVHN Base Node */ + template + struct BaseNode_t + { + /*! Clears the node. */ + __forceinline void clear() + { + for (size_t i=0; i + struct OBBNode_t : public BaseNode_t + { + using BaseNode_t::children; + + struct Create + { + __forceinline NodeRef operator() (const FastAllocator::CachedAllocator& alloc) const + { + OBBNode_t* node = (OBBNode_t*) alloc.malloc0(sizeof(OBBNode_t),NodeRef::byteNodeAlignment); node->clear(); + return NodeRef::encodeNode(node); + } + }; + + struct Set + { + __forceinline void operator() (NodeRef node, size_t i, NodeRef child, const OBBox3fa& bounds) const { + node.ungetAABBNode()->setRef(i,child); + node.ungetAABBNode()->setBounds(i,bounds); + } + }; + + /*! Clears the node. */ + __forceinline void clear() + { + naabb.l.vx = Vec3fa(nan); + naabb.l.vy = Vec3fa(nan); + naabb.l.vz = Vec3fa(nan); + naabb.p = Vec3fa(nan); + BaseNode_t::clear(); + } + + /*! Sets bounding box. */ + __forceinline void setBounds(size_t i, const OBBox3fa& b) + { + assert(i < N); + + AffineSpace3fa space = b.space; + space.p -= b.bounds.lower; + space = AffineSpace3fa::scale(1.0f/max(Vec3fa(1E-19f),b.bounds.upper-b.bounds.lower))*space; + + naabb.l.vx.x[i] = space.l.vx.x; + naabb.l.vx.y[i] = space.l.vx.y; + naabb.l.vx.z[i] = space.l.vx.z; + + naabb.l.vy.x[i] = space.l.vy.x; + naabb.l.vy.y[i] = space.l.vy.y; + naabb.l.vy.z[i] = space.l.vy.z; + + naabb.l.vz.x[i] = space.l.vz.x; + naabb.l.vz.y[i] = space.l.vz.y; + naabb.l.vz.z[i] = space.l.vz.z; + + naabb.p.x[i] = space.p.x; + naabb.p.y[i] = space.p.y; + naabb.p.z[i] = space.p.z; + } + + /*! Sets ID of child. */ + __forceinline void setRef(size_t i, const NodeRef& ref) { + assert(i < N); + children[i] = ref; + } + + /*! Returns the extent of the bounds of the ith child */ + __forceinline Vec3fa extent(size_t i) const { + assert(i naabb; //!< non-axis aligned bounding boxes (bounds are [0,1] in specified space) + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_node_obb_mb.h b/thirdparty/embree/kernels/bvh/bvh_node_obb_mb.h new file mode 100644 index 000000000000..834cf5ec28b6 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_obb_mb.h @@ -0,0 +1,90 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_node_base.h" + +namespace embree +{ + template + struct OBBNodeMB_t : public BaseNode_t + { + using BaseNode_t::children; + + struct Create + { + __forceinline NodeRef operator() (const FastAllocator::CachedAllocator& alloc) const + { + OBBNodeMB_t* node = (OBBNodeMB_t*) alloc.malloc0(sizeof(OBBNodeMB_t),NodeRef::byteNodeAlignment); node->clear(); + return NodeRef::encodeNode(node); + } + }; + + struct Set + { + __forceinline void operator() (NodeRef node, size_t i, NodeRef child, const LinearSpace3fa& space, const LBBox3fa& lbounds, const BBox1f dt) const { + node.ungetAABBNodeMB()->setRef(i,child); + node.ungetAABBNodeMB()->setBounds(i,space,lbounds.global(dt)); + } + }; + + /*! Clears the node. */ + __forceinline void clear() + { + space0 = one; + //b0.lower = b0.upper = Vec3fa(nan); + b1.lower = b1.upper = Vec3fa(nan); + BaseNode_t::clear(); + } + + /*! Sets space and bounding boxes. */ + __forceinline void setBounds(size_t i, const AffineSpace3fa& space, const LBBox3fa& lbounds) { + setBounds(i,space,lbounds.bounds0,lbounds.bounds1); + } + + /*! Sets space and bounding boxes. */ + __forceinline void setBounds(size_t i, const AffineSpace3fa& s0, const BBox3fa& a, const BBox3fa& c) + { + assert(i < N); + + AffineSpace3fa space = s0; + space.p -= a.lower; + Vec3fa scale = 1.0f/max(Vec3fa(1E-19f),a.upper-a.lower); + space = AffineSpace3fa::scale(scale)*space; + BBox3fa a1((a.lower-a.lower)*scale,(a.upper-a.lower)*scale); + BBox3fa c1((c.lower-a.lower)*scale,(c.upper-a.lower)*scale); + + space0.l.vx.x[i] = space.l.vx.x; space0.l.vx.y[i] = space.l.vx.y; space0.l.vx.z[i] = space.l.vx.z; + space0.l.vy.x[i] = space.l.vy.x; space0.l.vy.y[i] = space.l.vy.y; space0.l.vy.z[i] = space.l.vy.z; + space0.l.vz.x[i] = space.l.vz.x; space0.l.vz.y[i] = space.l.vz.y; space0.l.vz.z[i] = space.l.vz.z; + space0.p .x[i] = space.p .x; space0.p .y[i] = space.p .y; space0.p .z[i] = space.p .z; + + /*b0.lower.x[i] = a1.lower.x; b0.lower.y[i] = a1.lower.y; b0.lower.z[i] = a1.lower.z; + b0.upper.x[i] = a1.upper.x; b0.upper.y[i] = a1.upper.y; b0.upper.z[i] = a1.upper.z;*/ + + b1.lower.x[i] = c1.lower.x; b1.lower.y[i] = c1.lower.y; b1.lower.z[i] = c1.lower.z; + b1.upper.x[i] = c1.upper.x; b1.upper.y[i] = c1.upper.y; b1.upper.z[i] = c1.upper.z; + } + + /*! Sets ID of child. */ + __forceinline void setRef(size_t i, const NodeRef& ref) { + assert(i < N); + children[i] = ref; + } + + /*! Returns the extent of the bounds of the ith child */ + __forceinline Vec3fa extent0(size_t i) const { + assert(i < N); + const Vec3fa vx(space0.l.vx.x[i],space0.l.vx.y[i],space0.l.vx.z[i]); + const Vec3fa vy(space0.l.vy.x[i],space0.l.vy.y[i],space0.l.vy.z[i]); + const Vec3fa vz(space0.l.vz.x[i],space0.l.vz.y[i],space0.l.vz.z[i]); + return rsqrt(vx*vx + vy*vy + vz*vz); + } + + public: + AffineSpace3vf space0; + //BBox3vf b0; // these are the unit bounds + BBox3vf b1; + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_node_qaabb.h b/thirdparty/embree/kernels/bvh/bvh_node_qaabb.h new file mode 100644 index 000000000000..5212821f3fe1 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_qaabb.h @@ -0,0 +1,265 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh_node_base.h" + +namespace embree +{ + /*! BVHN Quantized Node */ + template + struct __aligned(8) QuantizedBaseNode_t + { + typedef unsigned char T; + static const T MIN_QUAN = 0; + static const T MAX_QUAN = 255; + + /*! Clears the node. */ + __forceinline void clear() { + for (size_t i=0; i &lower, + const vfloat &upper, + T lower_quant[N], + T upper_quant[N], + float &start, + float &scale) + { + /* quantize bounds */ + const vbool m_valid = lower != vfloat(pos_inf); + const float minF = reduce_min(lower); + const float maxF = reduce_max(upper); + float diff = (1.0f+2.0f*float(ulp))*(maxF - minF); + float decode_scale = diff / float(MAX_QUAN); + if (decode_scale == 0.0f) decode_scale = 2.0f*FLT_MIN; // result may have been flushed to zero + assert(madd(decode_scale,float(MAX_QUAN),minF) >= maxF); + const float encode_scale = diff > 0 ? (float(MAX_QUAN) / diff) : 0.0f; + vint ilower = max(vint(floor((lower - vfloat(minF))*vfloat(encode_scale))),MIN_QUAN); + vint iupper = min(vint(ceil ((upper - vfloat(minF))*vfloat(encode_scale))),MAX_QUAN); + + /* lower/upper correction */ + vbool m_lower_correction = (madd(vfloat(ilower),decode_scale,minF)) > lower; + vbool m_upper_correction = (madd(vfloat(iupper),decode_scale,minF)) < upper; + ilower = max(select(m_lower_correction,ilower-1,ilower),MIN_QUAN); + iupper = min(select(m_upper_correction,iupper+1,iupper),MAX_QUAN); + + /* disable invalid lanes */ + ilower = select(m_valid,ilower,MAX_QUAN); + iupper = select(m_valid,iupper,MIN_QUAN); + + /* store as uchar to memory */ + vint::store(lower_quant,ilower); + vint::store(upper_quant,iupper); + start = minF; + scale = decode_scale; + +#if defined(DEBUG) + vfloat extract_lower( vint::loadu(lower_quant) ); + vfloat extract_upper( vint::loadu(upper_quant) ); + vfloat final_extract_lower = madd(extract_lower,decode_scale,minF); + vfloat final_extract_upper = madd(extract_upper,decode_scale,minF); + assert( (movemask(final_extract_lower <= lower ) & movemask(m_valid)) == movemask(m_valid)); + assert( (movemask(final_extract_upper >= upper ) & movemask(m_valid)) == movemask(m_valid)); +#endif + } + + __forceinline void init_dim(AABBNode_t,N>& node) + { + init_dim(node.lower_x,node.upper_x,lower_x,upper_x,start.x,scale.x); + init_dim(node.lower_y,node.upper_y,lower_y,upper_y,start.y,scale.y); + init_dim(node.lower_z,node.upper_z,lower_z,upper_z,start.z,scale.z); + } + + __forceinline vbool validMask() const { return vint::loadu(lower_x) <= vint::loadu(upper_x); } + +#if defined(__AVX512F__) // KNL + __forceinline vbool16 validMask16() const { return le(0xff,vint<16>::loadu(lower_x),vint<16>::loadu(upper_x)); } +#endif + __forceinline vfloat dequantizeLowerX() const { return madd(vfloat(vint::loadu(lower_x)),scale.x,vfloat(start.x)); } + + __forceinline vfloat dequantizeUpperX() const { return madd(vfloat(vint::loadu(upper_x)),scale.x,vfloat(start.x)); } + + __forceinline vfloat dequantizeLowerY() const { return madd(vfloat(vint::loadu(lower_y)),scale.y,vfloat(start.y)); } + + __forceinline vfloat dequantizeUpperY() const { return madd(vfloat(vint::loadu(upper_y)),scale.y,vfloat(start.y)); } + + __forceinline vfloat dequantizeLowerZ() const { return madd(vfloat(vint::loadu(lower_z)),scale.z,vfloat(start.z)); } + + __forceinline vfloat dequantizeUpperZ() const { return madd(vfloat(vint::loadu(upper_z)),scale.z,vfloat(start.z)); } + + template + __forceinline vfloat dequantize(const size_t offset) const { return vfloat(vint::loadu(all_planes+offset)); } + +#if defined(__AVX512F__) + __forceinline vfloat16 dequantizeLowerUpperX(const vint16 &p) const { return madd(vfloat16(permute(vint<16>::loadu(lower_x),p)),scale.x,vfloat16(start.x)); } + __forceinline vfloat16 dequantizeLowerUpperY(const vint16 &p) const { return madd(vfloat16(permute(vint<16>::loadu(lower_y),p)),scale.y,vfloat16(start.y)); } + __forceinline vfloat16 dequantizeLowerUpperZ(const vint16 &p) const { return madd(vfloat16(permute(vint<16>::loadu(lower_z),p)),scale.z,vfloat16(start.z)); } +#endif + + union { + struct { + T lower_x[N]; //!< 8bit discretized X dimension of lower bounds of all N children + T upper_x[N]; //!< 8bit discretized X dimension of upper bounds of all N children + T lower_y[N]; //!< 8bit discretized Y dimension of lower bounds of all N children + T upper_y[N]; //!< 8bit discretized Y dimension of upper bounds of all N children + T lower_z[N]; //!< 8bit discretized Z dimension of lower bounds of all N children + T upper_z[N]; //!< 8bit discretized Z dimension of upper bounds of all N children + }; + T all_planes[6*N]; + }; + + Vec3f start; + Vec3f scale; + + friend embree_ostream operator<<(embree_ostream o, const QuantizedBaseNode_t& n) + { + o << "QuantizedBaseNode { " << embree_endl; + o << " start " << n.start << embree_endl; + o << " scale " << n.scale << embree_endl; + o << " lower_x " << vuint::loadu(n.lower_x) << embree_endl; + o << " upper_x " << vuint::loadu(n.upper_x) << embree_endl; + o << " lower_y " << vuint::loadu(n.lower_y) << embree_endl; + o << " upper_y " << vuint::loadu(n.upper_y) << embree_endl; + o << " lower_z " << vuint::loadu(n.lower_z) << embree_endl; + o << " upper_z " << vuint::loadu(n.upper_z) << embree_endl; + o << "}" << embree_endl; + return o; + } + + }; + + template + struct __aligned(8) QuantizedNode_t : public BaseNode_t, QuantizedBaseNode_t + { + using BaseNode_t::children; + using QuantizedBaseNode_t::lower_x; + using QuantizedBaseNode_t::upper_x; + using QuantizedBaseNode_t::lower_y; + using QuantizedBaseNode_t::upper_y; + using QuantizedBaseNode_t::lower_z; + using QuantizedBaseNode_t::upper_z; + using QuantizedBaseNode_t::start; + using QuantizedBaseNode_t::scale; + using QuantizedBaseNode_t::init_dim; + + __forceinline void setRef(size_t i, const NodeRef& ref) { + assert(i < N); + children[i] = ref; + } + + struct Create2 + { + template + __forceinline NodeRef operator() (BuildRecord* children, const size_t n, const FastAllocator::CachedAllocator& alloc) const + { + __aligned(64) AABBNode_t node; + node.clear(); + for (size_t i=0; iinit(node); + + return (size_t)qnode | NodeRef::tyQuantizedNode; + } + }; + + struct Set2 + { + template + __forceinline NodeRef operator() (const BuildRecord& precord, const BuildRecord* crecords, NodeRef ref, NodeRef* children, const size_t num) const + { + QuantizedNode_t* node = ref.quantizedNode(); + for (size_t i=0; isetRef(i,children[i]); + return ref; + } + }; + + __forceinline void init(AABBNode_t& node) + { + for (size_t i=0;i + struct __aligned(8) QuantizedBaseNodeMB_t + { + QuantizedBaseNode_t node0; + QuantizedBaseNode_t node1; + + /*! Clears the node. */ + __forceinline void clear() { + node0.clear(); + node1.clear(); + } + + /*! Returns bounds of specified child. */ + __forceinline BBox3fa bounds(size_t i) const + { + assert(i < N); + BBox3fa bounds0 = node0.bounds(i); + BBox3fa bounds1 = node1.bounds(i); + bounds0.extend(bounds1); + return bounds0; + } + + /*! Returns extent of bounds of specified child. */ + __forceinline Vec3fa extent(size_t i) const { + return bounds(i).size(); + } + + __forceinline vbool validMask() const { return node0.validMask(); } + + template + __forceinline vfloat dequantizeLowerX(const T t) const { return lerp(node0.dequantizeLowerX(),node1.dequantizeLowerX(),t); } + template + __forceinline vfloat dequantizeUpperX(const T t) const { return lerp(node0.dequantizeUpperX(),node1.dequantizeUpperX(),t); } + template + __forceinline vfloat dequantizeLowerY(const T t) const { return lerp(node0.dequantizeLowerY(),node1.dequantizeLowerY(),t); } + template + __forceinline vfloat dequantizeUpperY(const T t) const { return lerp(node0.dequantizeUpperY(),node1.dequantizeUpperY(),t); } + template + __forceinline vfloat dequantizeLowerZ(const T t) const { return lerp(node0.dequantizeLowerZ(),node1.dequantizeLowerZ(),t); } + template + __forceinline vfloat dequantizeUpperZ(const T t) const { return lerp(node0.dequantizeUpperZ(),node1.dequantizeUpperZ(),t); } + + + template + __forceinline vfloat dequantizeLowerX(const size_t i, const vfloat &t) const { return lerp(vfloat(node0.dequantizeLowerX()[i]),vfloat(node1.dequantizeLowerX()[i]),t); } + template + __forceinline vfloat dequantizeUpperX(const size_t i, const vfloat &t) const { return lerp(vfloat(node0.dequantizeUpperX()[i]),vfloat(node1.dequantizeUpperX()[i]),t); } + template + __forceinline vfloat dequantizeLowerY(const size_t i, const vfloat &t) const { return lerp(vfloat(node0.dequantizeLowerY()[i]),vfloat(node1.dequantizeLowerY()[i]),t); } + template + __forceinline vfloat dequantizeUpperY(const size_t i, const vfloat &t) const { return lerp(vfloat(node0.dequantizeUpperY()[i]),vfloat(node1.dequantizeUpperY()[i]),t); } + template + __forceinline vfloat dequantizeLowerZ(const size_t i, const vfloat &t) const { return lerp(vfloat(node0.dequantizeLowerZ()[i]),vfloat(node1.dequantizeLowerZ()[i]),t); } + template + __forceinline vfloat dequantizeUpperZ(const size_t i, const vfloat &t) const { return lerp(vfloat(node0.dequantizeUpperZ()[i]),vfloat(node1.dequantizeUpperZ()[i]),t); } + + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_node_ref.h b/thirdparty/embree/kernels/bvh/bvh_node_ref.h new file mode 100644 index 000000000000..5efc9c72c7c0 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_node_ref.h @@ -0,0 +1,242 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "../common/alloc.h" +#include "../common/accel.h" +#include "../common/device.h" +#include "../common/scene.h" +#include "../geometry/primitive.h" +#include "../common/ray.h" + +namespace embree +{ + /* BVH node reference with bounds */ + template + struct BVHNodeRecord + { + __forceinline BVHNodeRecord() {} + __forceinline BVHNodeRecord(NodeRef ref, const BBox3fa& bounds) : ref(ref), bounds((BBox3fx)bounds) {} + __forceinline BVHNodeRecord(NodeRef ref, const BBox3fx& bounds) : ref(ref), bounds(bounds) {} + + NodeRef ref; + BBox3fx bounds; + }; + + template + struct BVHNodeRecordMB + { + __forceinline BVHNodeRecordMB() {} + __forceinline BVHNodeRecordMB(NodeRef ref, const LBBox3fa& lbounds) : ref(ref), lbounds(lbounds) {} + + NodeRef ref; + LBBox3fa lbounds; + }; + + template + struct BVHNodeRecordMB4D + { + __forceinline BVHNodeRecordMB4D() {} + __forceinline BVHNodeRecordMB4D(NodeRef ref, const LBBox3fa& lbounds, const BBox1f& dt) : ref(ref), lbounds(lbounds), dt(dt) {} + + NodeRef ref; + LBBox3fa lbounds; + BBox1f dt; + }; + + template struct BaseNode_t; + template struct AABBNode_t; + template struct AABBNodeMB_t; + template struct AABBNodeMB4D_t; + template struct OBBNode_t; + template struct OBBNodeMB_t; + template struct QuantizedNode_t; + template struct QuantizedNodeMB_t; + + /*! Pointer that points to a node or a list of primitives */ + template + struct NodeRefPtr + { + //template friend class BVHN; + + /*! Number of bytes the nodes and primitives are minimally aligned to.*/ + static const size_t byteAlignment = 16; + static const size_t byteNodeAlignment = 4*N; + + /*! highest address bit is used as barrier for some algorithms */ + static const size_t barrier_mask = (1LL << (8*sizeof(size_t)-1)); + + /*! Masks the bits that store the number of items per leaf. */ + static const size_t align_mask = byteAlignment-1; + static const size_t items_mask = byteAlignment-1; + + /*! different supported node types */ + static const size_t tyAABBNode = 0; + static const size_t tyAABBNodeMB = 1; + static const size_t tyAABBNodeMB4D = 6; + static const size_t tyOBBNode = 2; + static const size_t tyOBBNodeMB = 3; + static const size_t tyQuantizedNode = 5; + static const size_t tyLeaf = 8; + + /*! Empty node */ + static const size_t emptyNode = tyLeaf; + + /*! Invalid node, used as marker in traversal */ + static const size_t invalidNode = (((size_t)-1) & (~items_mask)) | (tyLeaf+0); + static const size_t popRay = (((size_t)-1) & (~items_mask)) | (tyLeaf+1); + + /*! Maximum number of primitive blocks in a leaf. */ + static const size_t maxLeafBlocks = items_mask-tyLeaf; + + /*! Default constructor */ + __forceinline NodeRefPtr () {} + + /*! Construction from integer */ + __forceinline NodeRefPtr (size_t ptr) : ptr(ptr) {} + + /*! Cast to size_t */ + __forceinline operator size_t() const { return ptr; } + + /*! Sets the barrier bit. */ + __forceinline void setBarrier() { +#if defined(__X86_64__) + assert(!isBarrier()); + ptr |= barrier_mask; +#else + assert(false); +#endif + } + + /*! Clears the barrier bit. */ + __forceinline void clearBarrier() { +#if defined(__X86_64__) + ptr &= ~barrier_mask; +#else + assert(false); +#endif + } + + /*! Checks if this is an barrier. A barrier tells the top level tree rotations how deep to enter the tree. */ + __forceinline bool isBarrier() const { return (ptr & barrier_mask) != 0; } + + /*! checks if this is a leaf */ + __forceinline size_t isLeaf() const { return ptr & tyLeaf; } + + /*! returns node type */ + __forceinline int type() const { return ptr & (size_t)align_mask; } + + /*! checks if this is a node */ + __forceinline int isAABBNode() const { return (ptr & (size_t)align_mask) == tyAABBNode; } + + /*! checks if this is a motion blur node */ + __forceinline int isAABBNodeMB() const { return (ptr & (size_t)align_mask) == tyAABBNodeMB; } + + /*! checks if this is a 4D motion blur node */ + __forceinline int isAABBNodeMB4D() const { return (ptr & (size_t)align_mask) == tyAABBNodeMB4D; } + + /*! checks if this is a node with unaligned bounding boxes */ + __forceinline int isOBBNode() const { return (ptr & (size_t)align_mask) == tyOBBNode; } + + /*! checks if this is a motion blur node with unaligned bounding boxes */ + __forceinline int isOBBNodeMB() const { return (ptr & (size_t)align_mask) == tyOBBNodeMB; } + + /*! checks if this is a quantized node */ + __forceinline int isQuantizedNode() const { return (ptr & (size_t)align_mask) == tyQuantizedNode; } + + /*! Encodes a node */ + static __forceinline NodeRefPtr encodeNode(AABBNode_t* node) { + assert(!((size_t)node & align_mask)); + return NodeRefPtr((size_t) node); + } + + static __forceinline NodeRefPtr encodeNode(AABBNodeMB_t* node) { + assert(!((size_t)node & align_mask)); + return NodeRefPtr((size_t) node | tyAABBNodeMB); + } + + static __forceinline NodeRefPtr encodeNode(AABBNodeMB4D_t* node) { + assert(!((size_t)node & align_mask)); + return NodeRefPtr((size_t) node | tyAABBNodeMB4D); + } + + /*! Encodes an unaligned node */ + static __forceinline NodeRefPtr encodeNode(OBBNode_t* node) { + return NodeRefPtr((size_t) node | tyOBBNode); + } + + /*! Encodes an unaligned motion blur node */ + static __forceinline NodeRefPtr encodeNode(OBBNodeMB_t* node) { + return NodeRefPtr((size_t) node | tyOBBNodeMB); + } + + /*! Encodes a leaf */ + static __forceinline NodeRefPtr encodeLeaf(void* tri, size_t num) { + assert(!((size_t)tri & align_mask)); + assert(num <= maxLeafBlocks); + return NodeRefPtr((size_t)tri | (tyLeaf+min(num,(size_t)maxLeafBlocks))); + } + + /*! Encodes a leaf */ + static __forceinline NodeRefPtr encodeTypedLeaf(void* ptr, size_t ty) { + assert(!((size_t)ptr & align_mask)); + return NodeRefPtr((size_t)ptr | (tyLeaf+ty)); + } + + /*! returns base node pointer */ + __forceinline BaseNode_t* baseNode() + { + assert(!isLeaf()); + return (BaseNode_t*)(ptr & ~(size_t)align_mask); + } + __forceinline const BaseNode_t* baseNode() const + { + assert(!isLeaf()); + return (const BaseNode_t*)(ptr & ~(size_t)align_mask); + } + + /*! returns node pointer */ + __forceinline AABBNode_t* getAABBNode() { assert(isAABBNode()); return ( AABBNode_t*)ptr; } + __forceinline const AABBNode_t* getAABBNode() const { assert(isAABBNode()); return (const AABBNode_t*)ptr; } + + /*! returns motion blur node pointer */ + __forceinline AABBNodeMB_t* getAABBNodeMB() { assert(isAABBNodeMB() || isAABBNodeMB4D()); return ( AABBNodeMB_t*)(ptr & ~(size_t)align_mask); } + __forceinline const AABBNodeMB_t* getAABBNodeMB() const { assert(isAABBNodeMB() || isAABBNodeMB4D()); return (const AABBNodeMB_t*)(ptr & ~(size_t)align_mask); } + + /*! returns 4D motion blur node pointer */ + __forceinline AABBNodeMB4D_t* getAABBNodeMB4D() { assert(isAABBNodeMB4D()); return ( AABBNodeMB4D_t*)(ptr & ~(size_t)align_mask); } + __forceinline const AABBNodeMB4D_t* getAABBNodeMB4D() const { assert(isAABBNodeMB4D()); return (const AABBNodeMB4D_t*)(ptr & ~(size_t)align_mask); } + + /*! returns unaligned node pointer */ + __forceinline OBBNode_t* ungetAABBNode() { assert(isOBBNode()); return ( OBBNode_t*)(ptr & ~(size_t)align_mask); } + __forceinline const OBBNode_t* ungetAABBNode() const { assert(isOBBNode()); return (const OBBNode_t*)(ptr & ~(size_t)align_mask); } + + /*! returns unaligned motion blur node pointer */ + __forceinline OBBNodeMB_t* ungetAABBNodeMB() { assert(isOBBNodeMB()); return ( OBBNodeMB_t*)(ptr & ~(size_t)align_mask); } + __forceinline const OBBNodeMB_t* ungetAABBNodeMB() const { assert(isOBBNodeMB()); return (const OBBNodeMB_t*)(ptr & ~(size_t)align_mask); } + + /*! returns quantized node pointer */ + __forceinline QuantizedNode_t* quantizedNode() { assert(isQuantizedNode()); return ( QuantizedNode_t*)(ptr & ~(size_t)align_mask ); } + __forceinline const QuantizedNode_t* quantizedNode() const { assert(isQuantizedNode()); return (const QuantizedNode_t*)(ptr & ~(size_t)align_mask ); } + + /*! returns leaf pointer */ + __forceinline char* leaf(size_t& num) const { + assert(isLeaf()); + num = (ptr & (size_t)items_mask)-tyLeaf; + return (char*)(ptr & ~(size_t)align_mask); + } + + /*! clear all bit flags */ + __forceinline void clearFlags() { + ptr &= ~(size_t)align_mask; + } + + /*! returns the wideness */ + __forceinline size_t getN() const { return N; } + + public: + size_t ptr; + }; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_refit.cpp b/thirdparty/embree/kernels/bvh/bvh_refit.cpp new file mode 100644 index 000000000000..a273c21e8ba2 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_refit.cpp @@ -0,0 +1,247 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_refit.h" +#include "bvh_statistics.h" + +#include "../geometry/linei.h" +#include "../geometry/triangle.h" +#include "../geometry/trianglev.h" +#include "../geometry/trianglei.h" +#include "../geometry/quadv.h" +#include "../geometry/object.h" +#include "../geometry/instance.h" + +namespace embree +{ + namespace isa + { + static const size_t SINGLE_THREAD_THRESHOLD = 4*1024; + + template + __forceinline bool compare(const typename BVHN::NodeRef* a, const typename BVHN::NodeRef* b) + { + size_t sa = *(size_t*)&a->node()->lower_x; + size_t sb = *(size_t*)&b->node()->lower_x; + return sa < sb; + } + + template + BVHNRefitter::BVHNRefitter (BVH* bvh, const LeafBoundsInterface& leafBounds) + : bvh(bvh), leafBounds(leafBounds), numSubTrees(0) + { + } + + template + void BVHNRefitter::refit() + { + if (bvh->numPrimitives <= SINGLE_THREAD_THRESHOLD) { + bvh->bounds = LBBox3fa(recurse_bottom(bvh->root)); + } + else + { + BBox3fa subTreeBounds[MAX_NUM_SUB_TREES]; + numSubTrees = 0; + gather_subtree_refs(bvh->root,numSubTrees,0); + if (numSubTrees) + parallel_for(size_t(0), numSubTrees, size_t(1), [&](const range& r) { + for (size_t i=r.begin(); ibounds = LBBox3fa(refit_toplevel(bvh->root,numSubTrees,subTreeBounds,0)); + } + } + + template + void BVHNRefitter::gather_subtree_refs(NodeRef& ref, + size_t &subtrees, + const size_t depth) + { + if (depth >= MAX_SUB_TREE_EXTRACTION_DEPTH) + { + assert(subtrees < MAX_NUM_SUB_TREES); + subTrees[subtrees++] = ref; + return; + } + + if (ref.isAABBNode()) + { + AABBNode* node = ref.getAABBNode(); + for (size_t i=0; ichild(i); + if (unlikely(child == BVH::emptyNode)) continue; + gather_subtree_refs(child,subtrees,depth+1); + } + } + } + + template + BBox3fa BVHNRefitter::refit_toplevel(NodeRef& ref, + size_t &subtrees, + const BBox3fa *const subTreeBounds, + const size_t depth) + { + if (depth >= MAX_SUB_TREE_EXTRACTION_DEPTH) + { + assert(subtrees < MAX_NUM_SUB_TREES); + assert(subTrees[subtrees] == ref); + return subTreeBounds[subtrees++]; + } + + if (ref.isAABBNode()) + { + AABBNode* node = ref.getAABBNode(); + BBox3fa bounds[N]; + + for (size_t i=0; ichild(i); + + if (unlikely(child == BVH::emptyNode)) + bounds[i] = BBox3fa(empty); + else + bounds[i] = refit_toplevel(child,subtrees,subTreeBounds,depth+1); + } + + BBox3vf boundsT = transpose(bounds); + + /* set new bounds */ + node->lower_x = boundsT.lower.x; + node->lower_y = boundsT.lower.y; + node->lower_z = boundsT.lower.z; + node->upper_x = boundsT.upper.x; + node->upper_y = boundsT.upper.y; + node->upper_z = boundsT.upper.z; + + return merge(bounds); + } + else + return leafBounds.leafBounds(ref); + } + + // ========================================================= + // ========================================================= + // ========================================================= + + + template + BBox3fa BVHNRefitter::recurse_bottom(NodeRef& ref) + { + /* this is a leaf node */ + if (unlikely(ref.isLeaf())) + return leafBounds.leafBounds(ref); + + /* recurse if this is an internal node */ + AABBNode* node = ref.getAABBNode(); + + /* enable exclusive prefetch for >= AVX platforms */ +#if defined(__AVX__) + BVH::prefetchW(ref); +#endif + BBox3fa bounds[N]; + + for (size_t i=0; ichild(i) == BVH::emptyNode)) + { + bounds[i] = BBox3fa(empty); + } + else + bounds[i] = recurse_bottom(node->child(i)); + + /* AOS to SOA transform */ + BBox3vf boundsT = transpose(bounds); + + /* set new bounds */ + node->lower_x = boundsT.lower.x; + node->lower_y = boundsT.lower.y; + node->lower_z = boundsT.lower.z; + node->upper_x = boundsT.upper.x; + node->upper_y = boundsT.upper.y; + node->upper_z = boundsT.upper.z; + + return merge(bounds); + } + + template + BVHNRefitT::BVHNRefitT (BVH* bvh, Builder* builder, Mesh* mesh, size_t mode) + : bvh(bvh), builder(builder), refitter(new BVHNRefitter(bvh,*(typename BVHNRefitter::LeafBoundsInterface*)this)), mesh(mesh), topologyVersion(0) {} + + template + void BVHNRefitT::clear() + { + if (builder) + builder->clear(); + } + + template + void BVHNRefitT::build() + { + if (mesh->topologyChanged(topologyVersion)) { + topologyVersion = mesh->getTopologyVersion(); + builder->build(); + } + else + refitter->refit(); + } + + template class BVHNRefitter<4>; +#if defined(__AVX__) + template class BVHNRefitter<8>; +#endif + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + Builder* BVH4Triangle4MeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode); + Builder* BVH4Triangle4vMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode); + Builder* BVH4Triangle4iMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode); + + Builder* BVH4Triangle4MeshRefitSAH (void* accel, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<4,TriangleMesh,Triangle4> ((BVH4*)accel,BVH4Triangle4MeshBuilderSAH (accel,mesh,geomID,mode),mesh,mode); } + Builder* BVH4Triangle4vMeshRefitSAH (void* accel, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<4,TriangleMesh,Triangle4v>((BVH4*)accel,BVH4Triangle4vMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } + Builder* BVH4Triangle4iMeshRefitSAH (void* accel, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<4,TriangleMesh,Triangle4i>((BVH4*)accel,BVH4Triangle4iMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } +#if defined(__AVX__) + Builder* BVH8Triangle4MeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode); + Builder* BVH8Triangle4vMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode); + Builder* BVH8Triangle4iMeshBuilderSAH (void* bvh, TriangleMesh* mesh, unsigned int geomID, size_t mode); + + Builder* BVH8Triangle4MeshRefitSAH (void* accel, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<8,TriangleMesh,Triangle4> ((BVH8*)accel,BVH8Triangle4MeshBuilderSAH (accel,mesh,geomID,mode),mesh,mode); } + Builder* BVH8Triangle4vMeshRefitSAH (void* accel, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<8,TriangleMesh,Triangle4v>((BVH8*)accel,BVH8Triangle4vMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } + Builder* BVH8Triangle4iMeshRefitSAH (void* accel, TriangleMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<8,TriangleMesh,Triangle4i>((BVH8*)accel,BVH8Triangle4iMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + Builder* BVH4Quad4vMeshBuilderSAH (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode); + Builder* BVH4Quad4vMeshRefitSAH (void* accel, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<4,QuadMesh,Quad4v>((BVH4*)accel,BVH4Quad4vMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } + +#if defined(__AVX__) + Builder* BVH8Quad4vMeshBuilderSAH (void* bvh, QuadMesh* mesh, unsigned int geomID, size_t mode); + Builder* BVH8Quad4vMeshRefitSAH (void* accel, QuadMesh* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<8,QuadMesh,Quad4v>((BVH8*)accel,BVH8Quad4vMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } +#endif + +#endif + +#if defined(EMBREE_GEOMETRY_USER) + Builder* BVH4VirtualMeshBuilderSAH (void* bvh, UserGeometry* mesh, unsigned int geomID, size_t mode); + Builder* BVH4VirtualMeshRefitSAH (void* accel, UserGeometry* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<4,UserGeometry,Object>((BVH4*)accel,BVH4VirtualMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } + +#if defined(__AVX__) + Builder* BVH8VirtualMeshBuilderSAH (void* bvh, UserGeometry* mesh, unsigned int geomID, size_t mode); + Builder* BVH8VirtualMeshRefitSAH (void* accel, UserGeometry* mesh, unsigned int geomID, size_t mode) { return new BVHNRefitT<8,UserGeometry,Object>((BVH8*)accel,BVH8VirtualMeshBuilderSAH(accel,mesh,geomID,mode),mesh,mode); } +#endif +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + Builder* BVH4InstanceMeshBuilderSAH (void* bvh, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode); + Builder* BVH4InstanceMeshRefitSAH (void* accel, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode) { return new BVHNRefitT<4,Instance,InstancePrimitive>((BVH4*)accel,BVH4InstanceMeshBuilderSAH(accel,mesh,gtype,geomID,mode),mesh,mode); } + +#if defined(__AVX__) + Builder* BVH8InstanceMeshBuilderSAH (void* bvh, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode); + Builder* BVH8InstanceMeshRefitSAH (void* accel, Instance* mesh, Geometry::GTypeMask gtype, unsigned int geomID, size_t mode) { return new BVHNRefitT<8,Instance,InstancePrimitive>((BVH8*)accel,BVH8InstanceMeshBuilderSAH(accel,mesh,gtype,geomID,mode),mesh,mode); } +#endif +#endif + + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_refit.h b/thirdparty/embree/kernels/bvh/bvh_refit.h new file mode 100644 index 000000000000..4aa9bdd7cc89 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_refit.h @@ -0,0 +1,95 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../bvh/bvh.h" + +namespace embree +{ + namespace isa + { + template + class BVHNRefitter + { + public: + + /*! Type shortcuts */ + typedef BVHN BVH; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::NodeRef NodeRef; + + struct LeafBoundsInterface { + virtual const BBox3fa leafBounds(NodeRef& ref) const = 0; + }; + + public: + + /*! Constructor. */ + BVHNRefitter (BVH* bvh, const LeafBoundsInterface& leafBounds); + + /*! refits the BVH */ + void refit(); + + private: + /* single-threaded subtree extraction based on BVH depth */ + void gather_subtree_refs(NodeRef& ref, + size_t &subtrees, + const size_t depth = 0); + + /* single-threaded top-level refit */ + BBox3fa refit_toplevel(NodeRef& ref, + size_t &subtrees, + const BBox3fa *const subTreeBounds, + const size_t depth = 0); + + /* single-threaded subtree refit */ + BBox3fa recurse_bottom(NodeRef& ref); + + public: + BVH* bvh; //!< BVH to refit + const LeafBoundsInterface& leafBounds; //!< calculates bounds of leaves + + static const size_t MAX_SUB_TREE_EXTRACTION_DEPTH = (N==4) ? 4 : (N==8) ? 3 : 3; + static const size_t MAX_NUM_SUB_TREES = (N==4) ? 256 : (N==8) ? 512 : N*N*N; // N ^ MAX_SUB_TREE_EXTRACTION_DEPTH + size_t numSubTrees; + NodeRef subTrees[MAX_NUM_SUB_TREES]; + }; + + template + class BVHNRefitT : public Builder, public BVHNRefitter::LeafBoundsInterface + { + public: + + /*! Type shortcuts */ + typedef BVHN BVH; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::NodeRef NodeRef; + + public: + BVHNRefitT (BVH* bvh, Builder* builder, Mesh* mesh, size_t mode); + + virtual void build(); + + virtual void clear(); + + virtual const BBox3fa leafBounds (NodeRef& ref) const + { + size_t num; char* prim = ref.leaf(num); + if (unlikely(ref == BVH::emptyNode)) return empty; + + BBox3fa bounds = empty; + for (size_t i=0; i builder; + std::unique_ptr> refitter; + Mesh* mesh; + unsigned int topologyVersion; + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_rotate.cpp b/thirdparty/embree/kernels/bvh/bvh_rotate.cpp new file mode 100644 index 000000000000..2bb431bf0e4f --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_rotate.cpp @@ -0,0 +1,127 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_rotate.h" + +namespace embree +{ + namespace isa + { + /*! Computes half surface area of box. */ + __forceinline float halfArea3f(const BBox& box) { + const vfloat4 d = box.size(); + const vfloat4 a = d*shuffle<1,2,0,3>(d); + return a[0]+a[1]+a[2]; + } + + size_t BVHNRotate<4>::rotate(NodeRef parentRef, size_t depth) + { + /*! nothing to rotate if we reached a leaf node. */ + if (parentRef.isBarrier()) return 0; + if (parentRef.isLeaf()) return 0; + AABBNode* parent = parentRef.getAABBNode(); + + /*! rotate all children first */ + vint4 cdepth; + for (size_t c=0; c<4; c++) + cdepth[c] = (int)rotate(parent->child(c),depth+1); + + /* compute current areas of all children */ + vfloat4 sizeX = parent->upper_x-parent->lower_x; + vfloat4 sizeY = parent->upper_y-parent->lower_y; + vfloat4 sizeZ = parent->upper_z-parent->lower_z; + vfloat4 childArea = madd(sizeX,(sizeY + sizeZ),sizeY*sizeZ); + + /*! get node bounds */ + BBox child1_0,child1_1,child1_2,child1_3; + parent->bounds(child1_0,child1_1,child1_2,child1_3); + + /*! Find best rotation. We pick a first child (child1) and a sub-child + (child2child) of a different second child (child2), and swap child1 + and child2child. We perform the best such swap. */ + float bestArea = 0; + size_t bestChild1 = -1, bestChild2 = -1, bestChild2Child = -1; + for (size_t c2=0; c2<4; c2++) + { + /*! ignore leaf nodes as we cannot descent into them */ + if (parent->child(c2).isBarrier()) continue; + if (parent->child(c2).isLeaf()) continue; + AABBNode* child2 = parent->child(c2).getAABBNode(); + + /*! transpose child bounds */ + BBox child2c0,child2c1,child2c2,child2c3; + child2->bounds(child2c0,child2c1,child2c2,child2c3); + + /*! put child1_0 at each child2 position */ + float cost00 = halfArea3f(merge(child1_0,child2c1,child2c2,child2c3)); + float cost01 = halfArea3f(merge(child2c0,child1_0,child2c2,child2c3)); + float cost02 = halfArea3f(merge(child2c0,child2c1,child1_0,child2c3)); + float cost03 = halfArea3f(merge(child2c0,child2c1,child2c2,child1_0)); + vfloat4 cost0 = vfloat4(cost00,cost01,cost02,cost03); + vfloat4 min0 = vreduce_min(cost0); + int pos0 = (int)bsf(movemask(min0 == cost0)); + + /*! put child1_1 at each child2 position */ + float cost10 = halfArea3f(merge(child1_1,child2c1,child2c2,child2c3)); + float cost11 = halfArea3f(merge(child2c0,child1_1,child2c2,child2c3)); + float cost12 = halfArea3f(merge(child2c0,child2c1,child1_1,child2c3)); + float cost13 = halfArea3f(merge(child2c0,child2c1,child2c2,child1_1)); + vfloat4 cost1 = vfloat4(cost10,cost11,cost12,cost13); + vfloat4 min1 = vreduce_min(cost1); + int pos1 = (int)bsf(movemask(min1 == cost1)); + + /*! put child1_2 at each child2 position */ + float cost20 = halfArea3f(merge(child1_2,child2c1,child2c2,child2c3)); + float cost21 = halfArea3f(merge(child2c0,child1_2,child2c2,child2c3)); + float cost22 = halfArea3f(merge(child2c0,child2c1,child1_2,child2c3)); + float cost23 = halfArea3f(merge(child2c0,child2c1,child2c2,child1_2)); + vfloat4 cost2 = vfloat4(cost20,cost21,cost22,cost23); + vfloat4 min2 = vreduce_min(cost2); + int pos2 = (int)bsf(movemask(min2 == cost2)); + + /*! put child1_3 at each child2 position */ + float cost30 = halfArea3f(merge(child1_3,child2c1,child2c2,child2c3)); + float cost31 = halfArea3f(merge(child2c0,child1_3,child2c2,child2c3)); + float cost32 = halfArea3f(merge(child2c0,child2c1,child1_3,child2c3)); + float cost33 = halfArea3f(merge(child2c0,child2c1,child2c2,child1_3)); + vfloat4 cost3 = vfloat4(cost30,cost31,cost32,cost33); + vfloat4 min3 = vreduce_min(cost3); + int pos3 = (int)bsf(movemask(min3 == cost3)); + + /*! find best other child */ + vfloat4 area0123 = vfloat4(extract<0>(min0),extract<0>(min1),extract<0>(min2),extract<0>(min3)) - vfloat4(childArea[c2]); + int pos[4] = { pos0,pos1,pos2,pos3 }; + const size_t mbd = BVH4::maxBuildDepth; + vbool4 valid = vint4(int(depth+1))+cdepth <= vint4(mbd); // only select swaps that fulfill depth constraints + valid &= vint4(int(c2)) != vint4(step); + if (none(valid)) continue; + size_t c1 = select_min(valid,area0123); + float area = area0123[c1]; + if (c1 == c2) continue; // can happen if bounds are NANs + + /*! accept a swap when it reduces cost and is not swapping a node with itself */ + if (area < bestArea) { + bestArea = area; + bestChild1 = c1; + bestChild2 = c2; + bestChild2Child = pos[c1]; + } + } + + /*! if we did not find a swap that improves the SAH then do nothing */ + if (bestChild1 == size_t(-1)) return 1+reduce_max(cdepth); + + /*! perform the best found tree rotation */ + AABBNode* child2 = parent->child(bestChild2).getAABBNode(); + AABBNode::swap(parent,bestChild1,child2,bestChild2Child); + parent->setBounds(bestChild2,child2->bounds()); + AABBNode::compact(parent); + AABBNode::compact(child2); + + /*! This returned depth is conservative as the child that was + * pulled up in the tree could have been on the critical path. */ + cdepth[bestChild1]++; // bestChild1 was pushed down one level + return 1+reduce_max(cdepth); + } + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_rotate.h b/thirdparty/embree/kernels/bvh/bvh_rotate.h new file mode 100644 index 000000000000..009bef339ee1 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_rotate.h @@ -0,0 +1,37 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" + +namespace embree +{ + namespace isa + { + template + class BVHNRotate + { + typedef typename BVHN::NodeRef NodeRef; + + public: + static const bool enabled = false; + + static __forceinline size_t rotate(NodeRef parentRef, size_t depth = 1) { return 0; } + static __forceinline void restructure(NodeRef ref, size_t depth = 1) {} + }; + + /* BVH4 tree rotations */ + template<> + class BVHNRotate<4> + { + typedef BVH4::AABBNode AABBNode; + typedef BVH4::NodeRef NodeRef; + + public: + static const bool enabled = true; + + static size_t rotate(NodeRef parentRef, size_t depth = 1); + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_statistics.cpp b/thirdparty/embree/kernels/bvh/bvh_statistics.cpp new file mode 100644 index 000000000000..05460843af22 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_statistics.cpp @@ -0,0 +1,165 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "bvh_statistics.h" +#include "../../common/algorithms/parallel_reduce.h" + +namespace embree +{ + template + BVHNStatistics::BVHNStatistics (BVH* bvh) : bvh(bvh) + { + double A = max(0.0f,bvh->getLinearBounds().expectedHalfArea()); + stat = statistics(bvh->root,A,BBox1f(0.0f,1.0f)); + } + + template + std::string BVHNStatistics::str() + { + std::ostringstream stream; + stream.setf(std::ios::fixed, std::ios::floatfield); + stream << " primitives = " << bvh->numPrimitives << ", vertices = " << bvh->numVertices << ", depth = " << stat.depth << std::endl; + size_t totalBytes = stat.bytes(bvh); + double totalSAH = stat.sah(bvh); + stream << " total : sah = " << std::setw(7) << std::setprecision(3) << totalSAH << " (100.00%), "; + stream << "#bytes = " << std::setw(7) << std::setprecision(2) << totalBytes/1E6 << " MB (100.00%), "; + stream << "#nodes = " << std::setw(7) << stat.size() << " (" << std::setw(6) << std::setprecision(2) << 100.0*stat.fillRate(bvh) << "% filled), "; + stream << "#bytes/prim = " << std::setw(6) << std::setprecision(2) << double(totalBytes)/double(bvh->numPrimitives) << std::endl; + if (stat.statAABBNodes.numNodes ) stream << " getAABBNodes : " << stat.statAABBNodes.toString(bvh,totalSAH,totalBytes) << std::endl; + if (stat.statOBBNodes.numNodes ) stream << " ungetAABBNodes : " << stat.statOBBNodes.toString(bvh,totalSAH,totalBytes) << std::endl; + if (stat.statAABBNodesMB.numNodes ) stream << " getAABBNodesMB : " << stat.statAABBNodesMB.toString(bvh,totalSAH,totalBytes) << std::endl; + if (stat.statAABBNodesMB4D.numNodes) stream << " getAABBNodesMB4D : " << stat.statAABBNodesMB4D.toString(bvh,totalSAH,totalBytes) << std::endl; + if (stat.statOBBNodesMB.numNodes) stream << " ungetAABBNodesMB : " << stat.statOBBNodesMB.toString(bvh,totalSAH,totalBytes) << std::endl; + if (stat.statQuantizedNodes.numNodes ) stream << " quantizedNodes : " << stat.statQuantizedNodes.toString(bvh,totalSAH,totalBytes) << std::endl; + if (true) stream << " leaves : " << stat.statLeaf.toString(bvh,totalSAH,totalBytes) << std::endl; + if (true) stream << " histogram : " << stat.statLeaf.histToString() << std::endl; + return stream.str(); + } + + template + typename BVHNStatistics::Statistics BVHNStatistics::statistics(NodeRef node, const double A, const BBox1f t0t1) + { + Statistics s; + assert(t0t1.size() > 0.0f); + double dt = max(0.0f,t0t1.size()); + if (node.isAABBNode()) + { + AABBNode* n = node.getAABBNode(); + s = s + parallel_reduce(0,N,Statistics(),[&] ( const int i ) { + if (n->child(i) == BVH::emptyNode) return Statistics(); + const double Ai = max(0.0f,halfArea(n->extend(i))); + Statistics s = statistics(n->child(i),Ai,t0t1); + s.statAABBNodes.numChildren++; + return s; + }, Statistics::add); + s.statAABBNodes.numNodes++; + s.statAABBNodes.nodeSAH += dt*A; + s.depth++; + } + else if (node.isOBBNode()) + { + OBBNode* n = node.ungetAABBNode(); + s = s + parallel_reduce(0,N,Statistics(),[&] ( const int i ) { + if (n->child(i) == BVH::emptyNode) return Statistics(); + const double Ai = max(0.0f,halfArea(n->extent(i))); + Statistics s = statistics(n->child(i),Ai,t0t1); + s.statOBBNodes.numChildren++; + return s; + }, Statistics::add); + s.statOBBNodes.numNodes++; + s.statOBBNodes.nodeSAH += dt*A; + s.depth++; + } + else if (node.isAABBNodeMB()) + { + AABBNodeMB* n = node.getAABBNodeMB(); + s = s + parallel_reduce(0,N,Statistics(),[&] ( const int i ) { + if (n->child(i) == BVH::emptyNode) return Statistics(); + const double Ai = max(0.0f,n->expectedHalfArea(i,t0t1)); + Statistics s = statistics(n->child(i),Ai,t0t1); + s.statAABBNodesMB.numChildren++; + return s; + }, Statistics::add); + s.statAABBNodesMB.numNodes++; + s.statAABBNodesMB.nodeSAH += dt*A; + s.depth++; + } + else if (node.isAABBNodeMB4D()) + { + AABBNodeMB4D* n = node.getAABBNodeMB4D(); + s = s + parallel_reduce(0,N,Statistics(),[&] ( const int i ) { + if (n->child(i) == BVH::emptyNode) return Statistics(); + const BBox1f t0t1i = intersect(t0t1,n->timeRange(i)); + assert(!t0t1i.empty()); + const double Ai = n->AABBNodeMB::expectedHalfArea(i,t0t1i); + Statistics s = statistics(n->child(i),Ai,t0t1i); + s.statAABBNodesMB4D.numChildren++; + return s; + }, Statistics::add); + s.statAABBNodesMB4D.numNodes++; + s.statAABBNodesMB4D.nodeSAH += dt*A; + s.depth++; + } + else if (node.isOBBNodeMB()) + { + OBBNodeMB* n = node.ungetAABBNodeMB(); + s = s + parallel_reduce(0,N,Statistics(),[&] ( const int i ) { + if (n->child(i) == BVH::emptyNode) return Statistics(); + const double Ai = max(0.0f,halfArea(n->extent0(i))); + Statistics s = statistics(n->child(i),Ai,t0t1); + s.statOBBNodesMB.numChildren++; + return s; + }, Statistics::add); + s.statOBBNodesMB.numNodes++; + s.statOBBNodesMB.nodeSAH += dt*A; + s.depth++; + } + else if (node.isQuantizedNode()) + { + QuantizedNode* n = node.quantizedNode(); + s = s + parallel_reduce(0,N,Statistics(),[&] ( const int i ) { + if (n->child(i) == BVH::emptyNode) return Statistics(); + const double Ai = max(0.0f,halfArea(n->extent(i))); + Statistics s = statistics(n->child(i),Ai,t0t1); + s.statQuantizedNodes.numChildren++; + return s; + }, Statistics::add); + s.statQuantizedNodes.numNodes++; + s.statQuantizedNodes.nodeSAH += dt*A; + s.depth++; + } + else if (node.isLeaf()) + { + size_t num; const char* tri = node.leaf(num); + if (num) + { + for (size_t i=0; iprimTy->getBytes(tri); + s.statLeaf.numPrimsActive += bvh->primTy->sizeActive(tri); + s.statLeaf.numPrimsTotal += bvh->primTy->sizeTotal(tri); + s.statLeaf.numBytes += bytes; + tri+=bytes; + } + s.statLeaf.numLeaves++; + s.statLeaf.numPrimBlocks += num; + s.statLeaf.leafSAH += dt*A*num; + if (num-1 < Statistics::LeafStat::NHIST) { + s.statLeaf.numPrimBlocksHistogram[num-1]++; + } + } + } + else { + throw std::runtime_error("not supported node type in bvh_statistics"); + } + return s; + } + +#if defined(__AVX__) + template class BVHNStatistics<8>; +#endif + +#if !defined(__AVX__) || !defined(EMBREE_TARGET_SSE2) && !defined(EMBREE_TARGET_SSE42) + template class BVHNStatistics<4>; +#endif +} diff --git a/thirdparty/embree/kernels/bvh/bvh_statistics.h b/thirdparty/embree/kernels/bvh/bvh_statistics.h new file mode 100644 index 000000000000..73dfc6fbcc0d --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_statistics.h @@ -0,0 +1,285 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include + +namespace embree +{ + template + class BVHNStatistics + { + typedef BVHN BVH; + typedef typename BVH::AABBNode AABBNode; + typedef typename BVH::OBBNode OBBNode; + typedef typename BVH::AABBNodeMB AABBNodeMB; + typedef typename BVH::AABBNodeMB4D AABBNodeMB4D; + typedef typename BVH::OBBNodeMB OBBNodeMB; + typedef typename BVH::QuantizedNode QuantizedNode; + + typedef typename BVH::NodeRef NodeRef; + + struct Statistics + { + template + struct NodeStat + { + NodeStat ( double nodeSAH = 0, + size_t numNodes = 0, + size_t numChildren = 0) + : nodeSAH(nodeSAH), + numNodes(numNodes), + numChildren(numChildren) {} + + double sah(BVH* bvh) const { + return nodeSAH/bvh->getLinearBounds().expectedHalfArea(); + } + + size_t bytes() const { + return numNodes*sizeof(Node); + } + + size_t size() const { + return numNodes; + } + + double fillRateNom () const { return double(numChildren); } + double fillRateDen () const { return double(numNodes*N); } + double fillRate () const { return fillRateNom()/fillRateDen(); } + + __forceinline friend NodeStat operator+ ( const NodeStat& a, const NodeStat& b) + { + return NodeStat(a.nodeSAH + b.nodeSAH, + a.numNodes+b.numNodes, + a.numChildren+b.numChildren); + } + + std::string toString(BVH* bvh, double sahTotal, size_t bytesTotal) const + { + std::ostringstream stream; + stream.setf(std::ios::fixed, std::ios::floatfield); + stream << "sah = " << std::setw(7) << std::setprecision(3) << sah(bvh); + stream << " (" << std::setw(6) << std::setprecision(2) << 100.0*sah(bvh)/sahTotal << "%), "; + stream << "#bytes = " << std::setw(7) << std::setprecision(2) << bytes()/1E6 << " MB "; + stream << "(" << std::setw(6) << std::setprecision(2) << 100.0*double(bytes())/double(bytesTotal) << "%), "; + stream << "#nodes = " << std::setw(7) << numNodes << " (" << std::setw(6) << std::setprecision(2) << 100.0*fillRate() << "% filled), "; + stream << "#bytes/prim = " << std::setw(6) << std::setprecision(2) << double(bytes())/double(bvh->numPrimitives); + return stream.str(); + } + + public: + double nodeSAH; + size_t numNodes; + size_t numChildren; + }; + + struct LeafStat + { + static const int NHIST = 8; + + LeafStat ( double leafSAH = 0.0f, + size_t numLeaves = 0, + size_t numPrimsActive = 0, + size_t numPrimsTotal = 0, + size_t numPrimBlocks = 0, + size_t numBytes = 0) + : leafSAH(leafSAH), + numLeaves(numLeaves), + numPrimsActive(numPrimsActive), + numPrimsTotal(numPrimsTotal), + numPrimBlocks(numPrimBlocks), + numBytes(numBytes) + { + for (size_t i=0; igetLinearBounds().expectedHalfArea(); + } + + size_t bytes(BVH* bvh) const { + return numBytes; + } + + size_t size() const { + return numLeaves; + } + + double fillRateNom (BVH* bvh) const { return double(numPrimsActive); } + double fillRateDen (BVH* bvh) const { return double(numPrimsTotal); } + double fillRate (BVH* bvh) const { return fillRateNom(bvh)/fillRateDen(bvh); } + + __forceinline friend LeafStat operator+ ( const LeafStat& a, const LeafStat& b) + { + LeafStat stat(a.leafSAH + b.leafSAH, + a.numLeaves+b.numLeaves, + a.numPrimsActive+b.numPrimsActive, + a.numPrimsTotal+b.numPrimsTotal, + a.numPrimBlocks+b.numPrimBlocks, + a.numBytes+b.numBytes); + for (size_t i=0; i statAABBNodes = NodeStat(), + NodeStat statOBBNodes = NodeStat(), + NodeStat statAABBNodesMB = NodeStat(), + NodeStat statAABBNodesMB4D = NodeStat(), + NodeStat statOBBNodesMB = NodeStat(), + NodeStat statQuantizedNodes = NodeStat()) + + : depth(depth), + statLeaf(statLeaf), + statAABBNodes(statAABBNodes), + statOBBNodes(statOBBNodes), + statAABBNodesMB(statAABBNodesMB), + statAABBNodesMB4D(statAABBNodesMB4D), + statOBBNodesMB(statOBBNodesMB), + statQuantizedNodes(statQuantizedNodes) {} + + double sah(BVH* bvh) const + { + return statLeaf.sah(bvh) + + statAABBNodes.sah(bvh) + + statOBBNodes.sah(bvh) + + statAABBNodesMB.sah(bvh) + + statAABBNodesMB4D.sah(bvh) + + statOBBNodesMB.sah(bvh) + + statQuantizedNodes.sah(bvh); + } + + size_t bytes(BVH* bvh) const { + return statLeaf.bytes(bvh) + + statAABBNodes.bytes() + + statOBBNodes.bytes() + + statAABBNodesMB.bytes() + + statAABBNodesMB4D.bytes() + + statOBBNodesMB.bytes() + + statQuantizedNodes.bytes(); + } + + size_t size() const + { + return statLeaf.size() + + statAABBNodes.size() + + statOBBNodes.size() + + statAABBNodesMB.size() + + statAABBNodesMB4D.size() + + statOBBNodesMB.size() + + statQuantizedNodes.size(); + } + + double fillRate (BVH* bvh) const + { + double nom = statLeaf.fillRateNom(bvh) + + statAABBNodes.fillRateNom() + + statOBBNodes.fillRateNom() + + statAABBNodesMB.fillRateNom() + + statAABBNodesMB4D.fillRateNom() + + statOBBNodesMB.fillRateNom() + + statQuantizedNodes.fillRateNom(); + double den = statLeaf.fillRateDen(bvh) + + statAABBNodes.fillRateDen() + + statOBBNodes.fillRateDen() + + statAABBNodesMB.fillRateDen() + + statAABBNodesMB4D.fillRateDen() + + statOBBNodesMB.fillRateDen() + + statQuantizedNodes.fillRateDen(); + return nom/den; + } + + friend Statistics operator+ ( const Statistics& a, const Statistics& b ) + { + return Statistics(max(a.depth,b.depth), + a.statLeaf + b.statLeaf, + a.statAABBNodes + b.statAABBNodes, + a.statOBBNodes + b.statOBBNodes, + a.statAABBNodesMB + b.statAABBNodesMB, + a.statAABBNodesMB4D + b.statAABBNodesMB4D, + a.statOBBNodesMB + b.statOBBNodesMB, + a.statQuantizedNodes + b.statQuantizedNodes); + } + + static Statistics add ( const Statistics& a, const Statistics& b ) { + return a+b; + } + + public: + size_t depth; + LeafStat statLeaf; + NodeStat statAABBNodes; + NodeStat statOBBNodes; + NodeStat statAABBNodesMB; + NodeStat statAABBNodesMB4D; + NodeStat statOBBNodesMB; + NodeStat statQuantizedNodes; + }; + + public: + + /* Constructor gathers statistics. */ + BVHNStatistics (BVH* bvh); + + /*! Convert statistics into a string */ + std::string str(); + + double sah() const { + return stat.sah(bvh); + } + + size_t bytesUsed() const { + return stat.bytes(bvh); + } + + private: + Statistics statistics(NodeRef node, const double A, const BBox1f dt); + + private: + BVH* bvh; + Statistics stat; + }; + + typedef BVHNStatistics<4> BVH4Statistics; + typedef BVHNStatistics<8> BVH8Statistics; +} diff --git a/thirdparty/embree/kernels/bvh/bvh_traverser1.h b/thirdparty/embree/kernels/bvh/bvh_traverser1.h new file mode 100644 index 000000000000..7f17084b81d6 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_traverser1.h @@ -0,0 +1,676 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include "node_intersector1.h" +#include "../common/stack_item.h" + +#define NEW_SORTING_CODE 1 + +namespace embree +{ + namespace isa + { + /*! BVH regular node traversal for single rays. */ + template + class BVHNNodeTraverser1Hit; + + /*! Helper functions for fast sorting using AVX512 instructions. */ +#if defined(__AVX512ER__) + + /* KNL code path */ + __forceinline void isort_update(vfloat16 &dist, vllong8 &ptr, const vfloat16 &d, const vllong8 &p) + { + const vfloat16 dist_shift = align_shift_right<15>(dist,dist); + const vllong8 ptr_shift = align_shift_right<7>(ptr,ptr); + const vbool16 m_geq = d >= dist; + const vbool16 m_geq_shift = m_geq << 1; + dist = select(m_geq,d,dist); + ptr = select(vboold8(m_geq),p,ptr); + dist = select(m_geq_shift,dist_shift,dist); + ptr = select(vboold8(m_geq_shift),ptr_shift,ptr); + } + + __forceinline void isort_quick_update(vfloat16 &dist, vllong8 &ptr, const vfloat16 &d, const vllong8 &p) + { + //dist = align_shift_right<15>(dist,d); + //ptr = align_shift_right<7>(ptr,p); + dist = align_shift_right<15>(dist,permute(d,vint16(zero))); + ptr = align_shift_right<7>(ptr,permute(p,vllong8(zero))); + } + + template + __forceinline void traverseClosestHitAVX512(NodeRef& cur, + size_t mask, + const vfloat& tNear, + StackItemT*& stackPtr, + StackItemT* stackEnd) + { + assert(mask != 0); + const BaseNode* node = cur.baseNode(); + + vllong8 children( vllong::loadu((void*)node->children) ); + children = vllong8::compact((int)mask,children); + vfloat16 distance = tNear; + distance = vfloat16::compact((int)mask,distance,tNear); + + cur = toScalar(children); + BVHN::prefetch(cur,types); + + mask &= mask-1; + if (likely(mask == 0)) return; + + /* 2 hits: order A0 B0 */ + const vllong8 c0(children); + const vfloat16 d0(distance); + children = align_shift_right<1>(children,children); + distance = align_shift_right<1>(distance,distance); + const vllong8 c1(children); + const vfloat16 d1(distance); + + cur = toScalar(children); + BVHN::prefetch(cur,types); + + /* a '<' keeps the order for equal distances, scenes like powerplant largely benefit from it */ + const vboolf16 m_dist = d0 < d1; + const vfloat16 dist_A0 = select(m_dist, d0, d1); + const vfloat16 dist_B0 = select(m_dist, d1, d0); + const vllong8 ptr_A0 = select(vboold8(m_dist), c0, c1); + const vllong8 ptr_B0 = select(vboold8(m_dist), c1, c0); + + mask &= mask-1; + if (likely(mask == 0)) { + cur = toScalar(ptr_A0); + stackPtr[0].ptr = toScalar(ptr_B0); + *(float*)&stackPtr[0].dist = toScalar(dist_B0); + stackPtr++; + return; + } + + /* 3 hits: order A1 B1 C1 */ + + children = align_shift_right<1>(children,children); + distance = align_shift_right<1>(distance,distance); + + const vllong8 c2(children); + const vfloat16 d2(distance); + + cur = toScalar(children); + BVHN::prefetch(cur,types); + + const vboolf16 m_dist1 = dist_A0 <= d2; + const vfloat16 dist_tmp_B1 = select(m_dist1, d2, dist_A0); + const vllong8 ptr_A1 = select(vboold8(m_dist1), ptr_A0, c2); + const vllong8 ptr_tmp_B1 = select(vboold8(m_dist1), c2, ptr_A0); + + const vboolf16 m_dist2 = dist_B0 <= dist_tmp_B1; + const vfloat16 dist_B1 = select(m_dist2, dist_B0 , dist_tmp_B1); + const vfloat16 dist_C1 = select(m_dist2, dist_tmp_B1, dist_B0); + const vllong8 ptr_B1 = select(vboold8(m_dist2), ptr_B0, ptr_tmp_B1); + const vllong8 ptr_C1 = select(vboold8(m_dist2), ptr_tmp_B1, ptr_B0); + + mask &= mask-1; + if (likely(mask == 0)) { + cur = toScalar(ptr_A1); + stackPtr[0].ptr = toScalar(ptr_C1); + *(float*)&stackPtr[0].dist = toScalar(dist_C1); + stackPtr[1].ptr = toScalar(ptr_B1); + *(float*)&stackPtr[1].dist = toScalar(dist_B1); + stackPtr+=2; + return; + } + + /* 4 hits: order A2 B2 C2 D2 */ + + const vfloat16 dist_A1 = select(m_dist1, dist_A0, d2); + + children = align_shift_right<1>(children,children); + distance = align_shift_right<1>(distance,distance); + + const vllong8 c3(children); + const vfloat16 d3(distance); + + cur = toScalar(children); + BVHN::prefetch(cur,types); + + const vboolf16 m_dist3 = dist_A1 <= d3; + const vfloat16 dist_tmp_B2 = select(m_dist3, d3, dist_A1); + const vllong8 ptr_A2 = select(vboold8(m_dist3), ptr_A1, c3); + const vllong8 ptr_tmp_B2 = select(vboold8(m_dist3), c3, ptr_A1); + + const vboolf16 m_dist4 = dist_B1 <= dist_tmp_B2; + const vfloat16 dist_B2 = select(m_dist4, dist_B1 , dist_tmp_B2); + const vfloat16 dist_tmp_C2 = select(m_dist4, dist_tmp_B2, dist_B1); + const vllong8 ptr_B2 = select(vboold8(m_dist4), ptr_B1, ptr_tmp_B2); + const vllong8 ptr_tmp_C2 = select(vboold8(m_dist4), ptr_tmp_B2, ptr_B1); + + const vboolf16 m_dist5 = dist_C1 <= dist_tmp_C2; + const vfloat16 dist_C2 = select(m_dist5, dist_C1 , dist_tmp_C2); + const vfloat16 dist_D2 = select(m_dist5, dist_tmp_C2, dist_C1); + const vllong8 ptr_C2 = select(vboold8(m_dist5), ptr_C1, ptr_tmp_C2); + const vllong8 ptr_D2 = select(vboold8(m_dist5), ptr_tmp_C2, ptr_C1); + + mask &= mask-1; + if (likely(mask == 0)) { + cur = toScalar(ptr_A2); + stackPtr[0].ptr = toScalar(ptr_D2); + *(float*)&stackPtr[0].dist = toScalar(dist_D2); + stackPtr[1].ptr = toScalar(ptr_C2); + *(float*)&stackPtr[1].dist = toScalar(dist_C2); + stackPtr[2].ptr = toScalar(ptr_B2); + *(float*)&stackPtr[2].dist = toScalar(dist_B2); + stackPtr+=3; + return; + } + + /* >=5 hits: reverse to descending order for writing to stack */ + + const size_t hits = 4 + popcnt(mask); + const vfloat16 dist_A2 = select(m_dist3, dist_A1, d3); + vfloat16 dist(neg_inf); + vllong8 ptr(zero); + + + isort_quick_update(dist,ptr,dist_A2,ptr_A2); + isort_quick_update(dist,ptr,dist_B2,ptr_B2); + isort_quick_update(dist,ptr,dist_C2,ptr_C2); + isort_quick_update(dist,ptr,dist_D2,ptr_D2); + + do { + + children = align_shift_right<1>(children,children); + distance = align_shift_right<1>(distance,distance); + + cur = toScalar(children); + BVHN::prefetch(cur,types); + + const vfloat16 new_dist(permute(distance,vint16(zero))); + const vllong8 new_ptr(permute(children,vllong8(zero))); + + mask &= mask-1; + isort_update(dist,ptr,new_dist,new_ptr); + + } while(mask); + + const vboold8 m_stack_ptr(0x55); // 10101010 (lsb -> msb) + const vboolf16 m_stack_dist(0x4444); // 0010001000100010 (lsb -> msb) + + /* extract current noderef */ + cur = toScalar(permute(ptr,vllong8(hits-1))); + /* rearrange pointers to beginning of 16 bytes block */ + vllong8 stackElementA0; + stackElementA0 = vllong8::expand(m_stack_ptr,ptr,stackElementA0); + /* put distances in between */ + vuint16 stackElementA1((__m512i)stackElementA0); + stackElementA1 = vuint16::expand(m_stack_dist,asUInt(dist),stackElementA1); + /* write out first 4 x 16 bytes block to stack */ + vuint16::storeu(stackPtr,stackElementA1); + /* get upper half of dist and ptr */ + dist = align_shift_right<4>(dist,dist); + ptr = align_shift_right<4>(ptr,ptr); + /* assemble and write out second block */ + vllong8 stackElementB0; + stackElementB0 = vllong8::expand(m_stack_ptr,ptr,stackElementB0); + vuint16 stackElementB1((__m512i)stackElementB0); + stackElementB1 = vuint16::expand(m_stack_dist,asUInt(dist),stackElementB1); + vuint16::storeu(stackPtr + 4,stackElementB1); + /* increase stack pointer */ + stackPtr += hits-1; + } +#endif + +#if defined(__AVX512VL__) // SKX + + template + __forceinline void isort_update(vint &dist, const vint &d) + { + const vint dist_shift = align_shift_right(dist,dist); + const vboolf m_geq = d >= dist; + const vboolf m_geq_shift = m_geq << 1; + dist = select(m_geq,d,dist); + dist = select(m_geq_shift,dist_shift,dist); + } + + template + __forceinline void isort_quick_update(vint &dist, const vint &d) { + dist = align_shift_right(dist,permute(d,vint(zero))); + } + + __forceinline size_t permuteExtract(const vint8& index, const vllong4& n0, const vllong4& n1) { + return toScalar(permutex2var((__m256i)index,n0,n1)); + } + + __forceinline float permuteExtract(const vint8& index, const vfloat8& n) { + return toScalar(permute(n,index)); + } + +#endif + + /* Specialization for BVH4. */ + template + class BVHNNodeTraverser1Hit<4, Nx, types> + { + typedef BVH4 BVH; + typedef BVH4::NodeRef NodeRef; + typedef BVH4::BaseNode BaseNode; + + + public: + /* Traverses a node with at least one hit child. Optimized for finding the closest hit (intersection). */ + static __forceinline void traverseClosestHit(NodeRef& cur, + size_t mask, + const vfloat& tNear, + StackItemT*& stackPtr, + StackItemT* stackEnd) + { + assert(mask != 0); +#if defined(__AVX512ER__) + traverseClosestHitAVX512<4,Nx,types,NodeRef,BaseNode>(cur,mask,tNear,stackPtr,stackEnd); +#else + const BaseNode* node = cur.baseNode(); + + /*! one child is hit, continue with that child */ + size_t r = bscf(mask); + cur = node->child(r); + BVH::prefetch(cur,types); + if (likely(mask == 0)) { + assert(cur != BVH::emptyNode); + return; + } + + /*! two children are hit, push far child, and continue with closer child */ + NodeRef c0 = cur; + const unsigned int d0 = ((unsigned int*)&tNear)[r]; + r = bscf(mask); + NodeRef c1 = node->child(r); + BVH::prefetch(c1,types); + const unsigned int d1 = ((unsigned int*)&tNear)[r]; + assert(c0 != BVH::emptyNode); + assert(c1 != BVH::emptyNode); + if (likely(mask == 0)) { + assert(stackPtr < stackEnd); + if (d0 < d1) { stackPtr->ptr = c1; stackPtr->dist = d1; stackPtr++; cur = c0; return; } + else { stackPtr->ptr = c0; stackPtr->dist = d0; stackPtr++; cur = c1; return; } + } + +#if NEW_SORTING_CODE == 1 + vint4 s0((size_t)c0,(size_t)d0); + vint4 s1((size_t)c1,(size_t)d1); + r = bscf(mask); + NodeRef c2 = node->child(r); BVH::prefetch(c2,types); unsigned int d2 = ((unsigned int*)&tNear)[r]; + vint4 s2((size_t)c2,(size_t)d2); + /* 3 hits */ + if (likely(mask == 0)) { + StackItemT::sort3(s0,s1,s2); + *(vint4*)&stackPtr[0] = s0; *(vint4*)&stackPtr[1] = s1; + cur = toSizeT(s2); + stackPtr+=2; + return; + } + r = bscf(mask); + NodeRef c3 = node->child(r); BVH::prefetch(c3,types); unsigned int d3 = ((unsigned int*)&tNear)[r]; + vint4 s3((size_t)c3,(size_t)d3); + /* 4 hits */ + StackItemT::sort4(s0,s1,s2,s3); + *(vint4*)&stackPtr[0] = s0; *(vint4*)&stackPtr[1] = s1; *(vint4*)&stackPtr[2] = s2; + cur = toSizeT(s3); + stackPtr+=3; +#else + /*! Here starts the slow path for 3 or 4 hit children. We push + * all nodes onto the stack to sort them there. */ + assert(stackPtr < stackEnd); + stackPtr->ptr = c0; stackPtr->dist = d0; stackPtr++; + assert(stackPtr < stackEnd); + stackPtr->ptr = c1; stackPtr->dist = d1; stackPtr++; + + /*! three children are hit, push all onto stack and sort 3 stack items, continue with closest child */ + assert(stackPtr < stackEnd); + r = bscf(mask); + NodeRef c = node->child(r); BVH::prefetch(c,types); unsigned int d = ((unsigned int*)&tNear)[r]; stackPtr->ptr = c; stackPtr->dist = d; stackPtr++; + assert(c != BVH::emptyNode); + if (likely(mask == 0)) { + sort(stackPtr[-1],stackPtr[-2],stackPtr[-3]); + cur = (NodeRef) stackPtr[-1].ptr; stackPtr--; + return; + } + + /*! four children are hit, push all onto stack and sort 4 stack items, continue with closest child */ + assert(stackPtr < stackEnd); + r = bscf(mask); + c = node->child(r); BVH::prefetch(c,types); d = *(unsigned int*)&tNear[r]; stackPtr->ptr = c; stackPtr->dist = d; stackPtr++; + assert(c != BVH::emptyNode); + sort(stackPtr[-1],stackPtr[-2],stackPtr[-3],stackPtr[-4]); + cur = (NodeRef) stackPtr[-1].ptr; stackPtr--; +#endif +#endif + } + + /* Traverses a node with at least one hit child. Optimized for finding any hit (occlusion). */ + static __forceinline void traverseAnyHit(NodeRef& cur, + size_t mask, + const vfloat& tNear, + NodeRef*& stackPtr, + NodeRef* stackEnd) + { + const BaseNode* node = cur.baseNode(); + + /*! one child is hit, continue with that child */ + size_t r = bscf(mask); + cur = node->child(r); + BVH::prefetch(cur,types); + + /* simpler in sequence traversal order */ + assert(cur != BVH::emptyNode); + if (likely(mask == 0)) return; + assert(stackPtr < stackEnd); + *stackPtr = cur; stackPtr++; + + for (; ;) + { + r = bscf(mask); + cur = node->child(r); BVH::prefetch(cur,types); + assert(cur != BVH::emptyNode); + if (likely(mask == 0)) return; + assert(stackPtr < stackEnd); + *stackPtr = cur; stackPtr++; + } + } + }; + + /* Specialization for BVH8. */ + template + class BVHNNodeTraverser1Hit<8, Nx, types> + { + typedef BVH8 BVH; + typedef BVH8::NodeRef NodeRef; + typedef BVH8::BaseNode BaseNode; + +#if defined(__AVX512VL__) + template + static __forceinline void traverseClosestHitAVX512VL8(NodeRef& cur, + size_t mask, + const vfloat8& tNear, + StackItemT*& stackPtr, + StackItemT* stackEnd) + { + assert(mask != 0); + const BaseNode* node = cur.baseNode(); + const vllong4 n0 = vllong4::loadu((vllong4*)&node->children[0]); + const vllong4 n1 = vllong4::loadu((vllong4*)&node->children[4]); + vint8 distance_i = (asInt(tNear) & 0xfffffff8) | vint8(step); + distance_i = vint8::compact((int)mask,distance_i,distance_i); + cur = permuteExtract(distance_i,n0,n1); + BVH::prefetch(cur,types); + + mask &= mask-1; + if (likely(mask == 0)) return; + + /* 2 hits: order A0 B0 */ + const vint8 d0(distance_i); + const vint8 d1(shuffle<1>(distance_i)); + cur = permuteExtract(d1,n0,n1); + BVH::prefetch(cur,types); + + const vint8 dist_A0 = min(d0, d1); + const vint8 dist_B0 = max(d0, d1); + assert(dist_A0[0] < dist_B0[0]); + + mask &= mask-1; + if (likely(mask == 0)) { + cur = permuteExtract(dist_A0,n0,n1); + stackPtr[0].ptr = permuteExtract(dist_B0,n0,n1); + *(float*)&stackPtr[0].dist = permuteExtract(dist_B0,tNear); + stackPtr++; + return; + } + + /* 3 hits: order A1 B1 C1 */ + + const vint8 d2(shuffle<2>(distance_i)); + cur = permuteExtract(d2,n0,n1); + BVH::prefetch(cur,types); + + const vint8 dist_A1 = min(dist_A0,d2); + const vint8 dist_tmp_B1 = max(dist_A0,d2); + const vint8 dist_B1 = min(dist_B0,dist_tmp_B1); + const vint8 dist_C1 = max(dist_B0,dist_tmp_B1); + assert(dist_A1[0] < dist_B1[0]); + assert(dist_B1[0] < dist_C1[0]); + + mask &= mask-1; + if (likely(mask == 0)) { + cur = permuteExtract(dist_A1,n0,n1); + stackPtr[0].ptr = permuteExtract(dist_C1,n0,n1); + *(float*)&stackPtr[0].dist = permuteExtract(dist_C1,tNear); + stackPtr[1].ptr = permuteExtract(dist_B1,n0,n1); + *(float*)&stackPtr[1].dist = permuteExtract(dist_B1,tNear); + stackPtr+=2; + return; + } + + /* 4 hits: order A2 B2 C2 D2 */ + + const vint8 d3(shuffle<3>(distance_i)); + cur = permuteExtract(d3,n0,n1); + BVH::prefetch(cur,types); + + const vint8 dist_A2 = min(dist_A1,d3); + const vint8 dist_tmp_B2 = max(dist_A1,d3); + const vint8 dist_B2 = min(dist_B1,dist_tmp_B2); + const vint8 dist_tmp_C2 = max(dist_B1,dist_tmp_B2); + const vint8 dist_C2 = min(dist_C1,dist_tmp_C2); + const vint8 dist_D2 = max(dist_C1,dist_tmp_C2); + assert(dist_A2[0] < dist_B2[0]); + assert(dist_B2[0] < dist_C2[0]); + assert(dist_C2[0] < dist_D2[0]); + + mask &= mask-1; + if (likely(mask == 0)) { + cur = permuteExtract(dist_A2,n0,n1); + stackPtr[0].ptr = permuteExtract(dist_D2,n0,n1); + *(float*)&stackPtr[0].dist = permuteExtract(dist_D2,tNear); + stackPtr[1].ptr = permuteExtract(dist_C2,n0,n1); + *(float*)&stackPtr[1].dist = permuteExtract(dist_C2,tNear); + stackPtr[2].ptr = permuteExtract(dist_B2,n0,n1); + *(float*)&stackPtr[2].dist = permuteExtract(dist_B2,tNear); + stackPtr+=3; + return; + } + + /* >=5 hits: reverse to descending order for writing to stack */ + + distance_i = align_shift_right<3>(distance_i,distance_i); + const size_t hits = 4 + popcnt(mask); + vint8 dist(INT_MIN); // this will work with -0.0f (0x80000000) as distance, isort_update uses >= to insert + + isort_quick_update(dist,dist_A2); + isort_quick_update(dist,dist_B2); + isort_quick_update(dist,dist_C2); + isort_quick_update(dist,dist_D2); + + do { + + distance_i = align_shift_right<1>(distance_i,distance_i); + cur = permuteExtract(distance_i,n0,n1); + BVH::prefetch(cur,types); + const vint8 new_dist(permute(distance_i,vint8(zero))); + mask &= mask-1; + isort_update(dist,new_dist); + + } while(mask); + + for (size_t i=0; i<7; i++) + assert(dist[i+0]>=dist[i+1]); + + for (size_t i=0;iptr = permuteExtract(dist,n0,n1); + *(float*)&stackPtr->dist = permuteExtract(dist,tNear); + dist = align_shift_right<1>(dist,dist); + stackPtr++; + } + cur = permuteExtract(dist,n0,n1); + } +#endif + + public: + static __forceinline void traverseClosestHit(NodeRef& cur, + size_t mask, + const vfloat& tNear, + StackItemT*& stackPtr, + StackItemT* stackEnd) + { + assert(mask != 0); +#if defined(__AVX512ER__) + traverseClosestHitAVX512<8,Nx,types,NodeRef,BaseNode>(cur,mask,tNear,stackPtr,stackEnd); +#elif defined(__AVX512VL__) + traverseClosestHitAVX512VL8(cur,mask,tNear,stackPtr,stackEnd); +#else + + const BaseNode* node = cur.baseNode(); + + /*! one child is hit, continue with that child */ + size_t r = bscf(mask); + cur = node->child(r); + BVH::prefetch(cur,types); + if (likely(mask == 0)) { + assert(cur != BVH::emptyNode); + return; + } + + /*! two children are hit, push far child, and continue with closer child */ + NodeRef c0 = cur; + const unsigned int d0 = ((unsigned int*)&tNear)[r]; + r = bscf(mask); + NodeRef c1 = node->child(r); + BVH::prefetch(c1,types); + const unsigned int d1 = ((unsigned int*)&tNear)[r]; + + assert(c0 != BVH::emptyNode); + assert(c1 != BVH::emptyNode); + if (likely(mask == 0)) { + assert(stackPtr < stackEnd); + if (d0 < d1) { stackPtr->ptr = c1; stackPtr->dist = d1; stackPtr++; cur = c0; return; } + else { stackPtr->ptr = c0; stackPtr->dist = d0; stackPtr++; cur = c1; return; } + } +#if NEW_SORTING_CODE == 1 + vint4 s0((size_t)c0,(size_t)d0); + vint4 s1((size_t)c1,(size_t)d1); + + r = bscf(mask); + NodeRef c2 = node->child(r); BVH::prefetch(c2,types); unsigned int d2 = ((unsigned int*)&tNear)[r]; + vint4 s2((size_t)c2,(size_t)d2); + /* 3 hits */ + if (likely(mask == 0)) { + StackItemT::sort3(s0,s1,s2); + *(vint4*)&stackPtr[0] = s0; *(vint4*)&stackPtr[1] = s1; + cur = toSizeT(s2); + stackPtr+=2; + return; + } + r = bscf(mask); + NodeRef c3 = node->child(r); BVH::prefetch(c3,types); unsigned int d3 = ((unsigned int*)&tNear)[r]; + vint4 s3((size_t)c3,(size_t)d3); + /* 4 hits */ + if (likely(mask == 0)) { + StackItemT::sort4(s0,s1,s2,s3); + *(vint4*)&stackPtr[0] = s0; *(vint4*)&stackPtr[1] = s1; *(vint4*)&stackPtr[2] = s2; + cur = toSizeT(s3); + stackPtr+=3; + return; + } + *(vint4*)&stackPtr[0] = s0; *(vint4*)&stackPtr[1] = s1; *(vint4*)&stackPtr[2] = s2; *(vint4*)&stackPtr[3] = s3; + /*! fallback case if more than 4 children are hit */ + StackItemT* stackFirst = stackPtr; + stackPtr+=4; + while (1) + { + assert(stackPtr < stackEnd); + r = bscf(mask); + NodeRef c = node->child(r); BVH::prefetch(c,types); unsigned int d = *(unsigned int*)&tNear[r]; + const vint4 s((size_t)c,(size_t)d); + *(vint4*)stackPtr++ = s; + assert(c != BVH::emptyNode); + if (unlikely(mask == 0)) break; + } + sort(stackFirst,stackPtr); + cur = (NodeRef) stackPtr[-1].ptr; stackPtr--; +#else + /*! Here starts the slow path for 3 or 4 hit children. We push + * all nodes onto the stack to sort them there. */ + assert(stackPtr < stackEnd); + stackPtr->ptr = c0; stackPtr->dist = d0; stackPtr++; + assert(stackPtr < stackEnd); + stackPtr->ptr = c1; stackPtr->dist = d1; stackPtr++; + + /*! three children are hit, push all onto stack and sort 3 stack items, continue with closest child */ + assert(stackPtr < stackEnd); + r = bscf(mask); + NodeRef c = node->child(r); BVH::prefetch(c,types); unsigned int d = ((unsigned int*)&tNear)[r]; stackPtr->ptr = c; stackPtr->dist = d; stackPtr++; + assert(c != BVH::emptyNode); + if (likely(mask == 0)) { + sort(stackPtr[-1],stackPtr[-2],stackPtr[-3]); + cur = (NodeRef) stackPtr[-1].ptr; stackPtr--; + return; + } + + /*! four children are hit, push all onto stack and sort 4 stack items, continue with closest child */ + assert(stackPtr < stackEnd); + r = bscf(mask); + c = node->child(r); BVH::prefetch(c,types); d = *(unsigned int*)&tNear[r]; stackPtr->ptr = c; stackPtr->dist = d; stackPtr++; + assert(c != BVH::emptyNode); + if (likely(mask == 0)) { + sort(stackPtr[-1],stackPtr[-2],stackPtr[-3],stackPtr[-4]); + cur = (NodeRef) stackPtr[-1].ptr; stackPtr--; + return; + } + /*! fallback case if more than 4 children are hit */ + StackItemT* stackFirst = stackPtr-4; + while (1) + { + assert(stackPtr < stackEnd); + r = bscf(mask); + c = node->child(r); BVH::prefetch(c,types); d = *(unsigned int*)&tNear[r]; stackPtr->ptr = c; stackPtr->dist = d; stackPtr++; + assert(c != BVH::emptyNode); + if (unlikely(mask == 0)) break; + } + sort(stackFirst,stackPtr); + cur = (NodeRef) stackPtr[-1].ptr; stackPtr--; +#endif +#endif + } + + static __forceinline void traverseAnyHit(NodeRef& cur, + size_t mask, + const vfloat& tNear, + NodeRef*& stackPtr, + NodeRef* stackEnd) + { + const BaseNode* node = cur.baseNode(); + + /*! one child is hit, continue with that child */ + size_t r = bscf(mask); + cur = node->child(r); + BVH::prefetch(cur,types); + + /* simpler in sequence traversal order */ + assert(cur != BVH::emptyNode); + if (likely(mask == 0)) return; + assert(stackPtr < stackEnd); + *stackPtr = cur; stackPtr++; + + for (; ;) + { + r = bscf(mask); + cur = node->child(r); BVH::prefetch(cur,types); + assert(cur != BVH::emptyNode); + if (likely(mask == 0)) return; + assert(stackPtr < stackEnd); + *stackPtr = cur; stackPtr++; + } + } + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/bvh_traverser_stream.h b/thirdparty/embree/kernels/bvh/bvh_traverser_stream.h new file mode 100644 index 000000000000..9c603babf0a5 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/bvh_traverser_stream.h @@ -0,0 +1,154 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" +#include "../common/ray.h" +#include "../common/stack_item.h" + +namespace embree +{ + namespace isa + { + template + class BVHNNodeTraverserStreamHitCoherent + { + typedef BVHN BVH; + typedef typename BVH::NodeRef NodeRef; + typedef typename BVH::BaseNode BaseNode; + + public: + template + static __forceinline void traverseClosestHit(NodeRef& cur, + size_t& m_trav_active, + const vbool& vmask, + const vfloat& tNear, + const T* const tMask, + StackItemMaskCoherent*& stackPtr) + { + const NodeRef parent = cur; + size_t mask = movemask(vmask); + assert(mask != 0); + const BaseNode* node = cur.baseNode(); + + /*! one child is hit, continue with that child */ + const size_t r0 = bscf(mask); + assert(r0 < 8); + cur = node->child(r0); + BVHN::prefetch(cur,types); + m_trav_active = tMask[r0]; + assert(cur != BVH::emptyNode); + if (unlikely(mask == 0)) return; + + const unsigned int* const tNear_i = (unsigned int*)&tNear; + + /*! two children are hit, push far child, and continue with closer child */ + NodeRef c0 = cur; + unsigned int d0 = tNear_i[r0]; + const size_t r1 = bscf(mask); + assert(r1 < 8); + NodeRef c1 = node->child(r1); + BVHN::prefetch(c1,types); + unsigned int d1 = tNear_i[r1]; + + assert(c0 != BVH::emptyNode); + assert(c1 != BVH::emptyNode); + if (likely(mask == 0)) { + if (d0 < d1) { + assert(tNear[r1] >= 0.0f); + stackPtr->mask = tMask[r1]; + stackPtr->parent = parent; + stackPtr->child = c1; + stackPtr++; + cur = c0; + m_trav_active = tMask[r0]; + return; + } + else { + assert(tNear[r0] >= 0.0f); + stackPtr->mask = tMask[r0]; + stackPtr->parent = parent; + stackPtr->child = c0; + stackPtr++; + cur = c1; + m_trav_active = tMask[r1]; + return; + } + } + + /*! slow path for more than two hits */ + size_t hits = movemask(vmask); + const vint dist_i = select(vmask, (asInt(tNear) & 0xfffffff8) | vint(step), 0); + #if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL + const vint tmp = extractN(dist_i); + const vint dist_i_sorted = usort_descending(tmp); + #else + const vint dist_i_sorted = usort_descending(dist_i); + #endif + const vint sorted_index = dist_i_sorted & 7; + + size_t i = 0; + for (;;) + { + const unsigned int index = sorted_index[i]; + assert(index < 8); + cur = node->child(index); + m_trav_active = tMask[index]; + assert(m_trav_active); + BVHN::prefetch(cur,types); + bscf(hits); + if (unlikely(hits==0)) break; + i++; + assert(cur != BVH::emptyNode); + assert(tNear[index] >= 0.0f); + stackPtr->mask = m_trav_active; + stackPtr->parent = parent; + stackPtr->child = cur; + stackPtr++; + } + } + + template + static __forceinline void traverseAnyHit(NodeRef& cur, + size_t& m_trav_active, + const vbool& vmask, + const T* const tMask, + StackItemMaskCoherent*& stackPtr) + { + const NodeRef parent = cur; + size_t mask = movemask(vmask); + assert(mask != 0); + const BaseNode* node = cur.baseNode(); + + /*! one child is hit, continue with that child */ + size_t r = bscf(mask); + cur = node->child(r); + BVHN::prefetch(cur,types); + m_trav_active = tMask[r]; + + /* simple in order sequence */ + assert(cur != BVH::emptyNode); + if (likely(mask == 0)) return; + stackPtr->mask = m_trav_active; + stackPtr->parent = parent; + stackPtr->child = cur; + stackPtr++; + + for (; ;) + { + r = bscf(mask); + cur = node->child(r); + BVHN::prefetch(cur,types); + m_trav_active = tMask[r]; + assert(cur != BVH::emptyNode); + if (likely(mask == 0)) return; + stackPtr->mask = m_trav_active; + stackPtr->parent = parent; + stackPtr->child = cur; + stackPtr++; + } + } + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/node_intersector.h b/thirdparty/embree/kernels/bvh/node_intersector.h new file mode 100644 index 000000000000..a978c0c45907 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/node_intersector.h @@ -0,0 +1,31 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bvh.h" + +namespace embree +{ + namespace isa + { + struct NearFarPrecalculations + { + size_t nearX, nearY, nearZ; + size_t farX, farY, farZ; + + __forceinline NearFarPrecalculations() {} + + __forceinline NearFarPrecalculations(const Vec3fa& dir, size_t N) + { + const size_t size = sizeof(float)*N; + nearX = (dir.x < 0.0f) ? 1*size : 0*size; + nearY = (dir.y < 0.0f) ? 3*size : 2*size; + nearZ = (dir.z < 0.0f) ? 5*size : 4*size; + farX = nearX ^ size; + farY = nearY ^ size; + farZ = nearZ ^ size; + } + }; + } +} diff --git a/thirdparty/embree/kernels/bvh/node_intersector1.h b/thirdparty/embree/kernels/bvh/node_intersector1.h new file mode 100644 index 000000000000..b1e63ce345ce --- /dev/null +++ b/thirdparty/embree/kernels/bvh/node_intersector1.h @@ -0,0 +1,1695 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "node_intersector.h" + +namespace embree +{ + namespace isa + { + ////////////////////////////////////////////////////////////////////////////////////// + // Ray structure used in single-ray traversal + ////////////////////////////////////////////////////////////////////////////////////// + + template + struct TravRayBase; + + /* Base (without tnear and tfar) */ + template + struct TravRayBase + { + __forceinline TravRayBase() {} + + __forceinline TravRayBase(const Vec3fa& ray_org, const Vec3fa& ray_dir) + : org_xyz(ray_org), dir_xyz(ray_dir) + { + const Vec3fa ray_rdir = rcp_safe(ray_dir); + org = Vec3vf(ray_org.x,ray_org.y,ray_org.z); + dir = Vec3vf(ray_dir.x,ray_dir.y,ray_dir.z); + rdir = Vec3vf(ray_rdir.x,ray_rdir.y,ray_rdir.z); +#if defined(__AVX2__) + const Vec3fa ray_org_rdir = ray_org*ray_rdir; + org_rdir = Vec3vf(ray_org_rdir.x,ray_org_rdir.y,ray_org_rdir.z); +#endif + nearX = ray_rdir.x >= 0.0f ? 0*sizeof(vfloat) : 1*sizeof(vfloat); + nearY = ray_rdir.y >= 0.0f ? 2*sizeof(vfloat) : 3*sizeof(vfloat); + nearZ = ray_rdir.z >= 0.0f ? 4*sizeof(vfloat) : 5*sizeof(vfloat); + farX = nearX ^ sizeof(vfloat); + farY = nearY ^ sizeof(vfloat); + farZ = nearZ ^ sizeof(vfloat); + +#if defined(__AVX512ER__) // KNL+ + /* optimization works only for 8-wide BVHs with 16-wide SIMD */ + const vint<16> id(step); + const vint<16> id2 = align_shift_right<16/2>(id, id); + permX = select(vfloat<16>(dir.x) >= 0.0f, id, id2); + permY = select(vfloat<16>(dir.y) >= 0.0f, id, id2); + permZ = select(vfloat<16>(dir.z) >= 0.0f, id, id2); +#endif + + } + + template + __forceinline TravRayBase(size_t k, const Vec3vf& ray_org, const Vec3vf& ray_dir, + const Vec3vf& ray_rdir, const Vec3vi& nearXYZ, + size_t flip = sizeof(vfloat)) + { + org = Vec3vf(ray_org.x[k], ray_org.y[k], ray_org.z[k]); + dir = Vec3vf(ray_dir.x[k], ray_dir.y[k], ray_dir.z[k]); + rdir = Vec3vf(ray_rdir.x[k], ray_rdir.y[k], ray_rdir.z[k]); +#if defined(__AVX2__) + org_rdir = org*rdir; +#endif + nearX = nearXYZ.x[k]; + nearY = nearXYZ.y[k]; + nearZ = nearXYZ.z[k]; + farX = nearX ^ flip; + farY = nearY ^ flip; + farZ = nearZ ^ flip; + +#if defined(__AVX512ER__) // KNL+ + /* optimization works only for 8-wide BVHs with 16-wide SIMD */ + const vint<16> id(step); + const vint<16> id2 = align_shift_right<16/2>(id, id); + permX = select(vfloat<16>(dir.x) >= 0.0f, id, id2); + permY = select(vfloat<16>(dir.y) >= 0.0f, id, id2); + permZ = select(vfloat<16>(dir.z) >= 0.0f, id, id2); +#endif + } + + Vec3fa org_xyz, dir_xyz; + Vec3vf org, dir, rdir; +#if defined(__AVX2__) + Vec3vf org_rdir; +#endif +#if defined(__AVX512ER__) // KNL+ + vint16 permX, permY, permZ; +#endif + + size_t nearX, nearY, nearZ; + size_t farX, farY, farZ; + }; + + /* Base (without tnear and tfar) */ + template + struct TravRayBase + { + __forceinline TravRayBase() {} + + __forceinline TravRayBase(const Vec3fa& ray_org, const Vec3fa& ray_dir) + : org_xyz(ray_org), dir_xyz(ray_dir) + { + const float round_down = 1.0f-3.0f*float(ulp); + const float round_up = 1.0f+3.0f*float(ulp); + const Vec3fa ray_rdir = 1.0f/zero_fix(ray_dir); + const Vec3fa ray_rdir_near = round_down*ray_rdir; + const Vec3fa ray_rdir_far = round_up *ray_rdir; + org = Vec3vf(ray_org.x,ray_org.y,ray_org.z); + dir = Vec3vf(ray_dir.x,ray_dir.y,ray_dir.z); + rdir_near = Vec3vf(ray_rdir_near.x,ray_rdir_near.y,ray_rdir_near.z); + rdir_far = Vec3vf(ray_rdir_far .x,ray_rdir_far .y,ray_rdir_far .z); + + nearX = ray_rdir_near.x >= 0.0f ? 0*sizeof(vfloat) : 1*sizeof(vfloat); + nearY = ray_rdir_near.y >= 0.0f ? 2*sizeof(vfloat) : 3*sizeof(vfloat); + nearZ = ray_rdir_near.z >= 0.0f ? 4*sizeof(vfloat) : 5*sizeof(vfloat); + farX = nearX ^ sizeof(vfloat); + farY = nearY ^ sizeof(vfloat); + farZ = nearZ ^ sizeof(vfloat); + +#if defined(__AVX512ER__) // KNL+ + /* optimization works only for 8-wide BVHs with 16-wide SIMD */ + const vint<16> id(step); + const vint<16> id2 = align_shift_right<16/2>(id, id); + permX = select(vfloat<16>(dir.x) >= 0.0f, id, id2); + permY = select(vfloat<16>(dir.y) >= 0.0f, id, id2); + permZ = select(vfloat<16>(dir.z) >= 0.0f, id, id2); +#endif + } + + template + __forceinline TravRayBase(size_t k, const Vec3vf& ray_org, const Vec3vf& ray_dir, + const Vec3vf& ray_rdir, const Vec3vi& nearXYZ, + size_t flip = sizeof(vfloat)) + { + const vfloat round_down = 1.0f-3.0f*float(ulp); + const vfloat round_up = 1.0f+3.0f*float(ulp); + org = Vec3vf(ray_org.x[k], ray_org.y[k], ray_org.z[k]); + dir = Vec3vf(ray_dir.x[k], ray_dir.y[k], ray_dir.z[k]); + rdir_near = round_down*Vec3vf(ray_rdir.x[k], ray_rdir.y[k], ray_rdir.z[k]); + rdir_far = round_up *Vec3vf(ray_rdir.x[k], ray_rdir.y[k], ray_rdir.z[k]); + + nearX = nearXYZ.x[k]; + nearY = nearXYZ.y[k]; + nearZ = nearXYZ.z[k]; + farX = nearX ^ flip; + farY = nearY ^ flip; + farZ = nearZ ^ flip; + +#if defined(__AVX512ER__) // KNL+ + /* optimization works only for 8-wide BVHs with 16-wide SIMD */ + const vint<16> id(step); + const vint<16> id2 = align_shift_right<16/2>(id, id); + permX = select(vfloat<16>(dir.x) >= 0.0f, id, id2); + permY = select(vfloat<16>(dir.y) >= 0.0f, id, id2); + permZ = select(vfloat<16>(dir.z) >= 0.0f, id, id2); +#endif + } + + Vec3fa org_xyz, dir_xyz; + Vec3vf org, dir, rdir_near, rdir_far; +#if defined(__AVX512ER__) // KNL+ + vint16 permX, permY, permZ; +#endif + + size_t nearX, nearY, nearZ; + size_t farX, farY, farZ; + }; + + /* Full (with tnear and tfar) */ + template + struct TravRay : TravRayBase + { + __forceinline TravRay() {} + + __forceinline TravRay(const Vec3fa& ray_org, const Vec3fa& ray_dir, float ray_tnear, float ray_tfar) + : TravRayBase(ray_org, ray_dir), + tnear(ray_tnear), tfar(ray_tfar) {} + + template + __forceinline TravRay(size_t k, const Vec3vf& ray_org, const Vec3vf& ray_dir, + const Vec3vf& ray_rdir, const Vec3vi& nearXYZ, + float ray_tnear, float ray_tfar, + size_t flip = sizeof(vfloat)) + : TravRayBase(k, ray_org, ray_dir, ray_rdir, nearXYZ, flip), + tnear(ray_tnear), tfar(ray_tfar) {} + + vfloat tnear; + vfloat tfar; + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // Point Query structure used in single-ray traversal + ////////////////////////////////////////////////////////////////////////////////////// + + template + struct TravPointQuery + { + __forceinline TravPointQuery() {} + + __forceinline TravPointQuery(const Vec3fa& query_org, const Vec3fa& query_rad) + { + org = Vec3vf(query_org.x, query_org.y, query_org.z); + rad = Vec3vf(query_rad.x, query_rad.y, query_rad.z); + } + + __forceinline vfloat const& tfar() const { + return rad.x; + } + + Vec3vf org, rad; + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // point query + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t pointQuerySphereDistAndMask( + const TravPointQuery& query, vfloat& dist, vfloat const& minX, vfloat const& maxX, + vfloat const& minY, vfloat const& maxY, vfloat const& minZ, vfloat const& maxZ) + { + const vfloat vX = min(max(query.org.x, minX), maxX) - query.org.x; + const vfloat vY = min(max(query.org.y, minY), maxY) - query.org.y; + const vfloat vZ = min(max(query.org.z, minZ), maxZ) - query.org.z; + dist = vX * vX + vY * vY + vZ * vZ; + const vbool vmask = dist <= query.tfar()*query.tfar(); + const vbool valid = minX <= maxX; + return movemask(vmask) & movemask(valid); + } + + template + __forceinline size_t pointQueryNodeSphere(const typename BVHN::AABBNode* node, const TravPointQuery& query, vfloat& dist) + { + const vfloat minX = vfloat::load((float*)((const char*)&node->lower_x)); + const vfloat minY = vfloat::load((float*)((const char*)&node->lower_y)); + const vfloat minZ = vfloat::load((float*)((const char*)&node->lower_z)); + const vfloat maxX = vfloat::load((float*)((const char*)&node->upper_x)); + const vfloat maxY = vfloat::load((float*)((const char*)&node->upper_y)); + const vfloat maxZ = vfloat::load((float*)((const char*)&node->upper_z)); + return pointQuerySphereDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ); + } + + template + __forceinline size_t pointQueryNodeSphere(const typename BVHN::AABBNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + const vfloat* pMinX = (const vfloat*)((const char*)&node->lower_x); + const vfloat* pMinY = (const vfloat*)((const char*)&node->lower_y); + const vfloat* pMinZ = (const vfloat*)((const char*)&node->lower_z); + const vfloat* pMaxX = (const vfloat*)((const char*)&node->upper_x); + const vfloat* pMaxY = (const vfloat*)((const char*)&node->upper_y); + const vfloat* pMaxZ = (const vfloat*)((const char*)&node->upper_z); + const vfloat minX = madd(time,pMinX[6],vfloat(pMinX[0])); + const vfloat minY = madd(time,pMinY[6],vfloat(pMinY[0])); + const vfloat minZ = madd(time,pMinZ[6],vfloat(pMinZ[0])); + const vfloat maxX = madd(time,pMaxX[6],vfloat(pMaxX[0])); + const vfloat maxY = madd(time,pMaxY[6],vfloat(pMaxY[0])); + const vfloat maxZ = madd(time,pMaxZ[6],vfloat(pMaxZ[0])); + return pointQuerySphereDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ); + } + + template + __forceinline size_t pointQueryNodeSphereMB4D(const typename BVHN::NodeRef ref, const TravPointQuery& query, const float time, vfloat& dist) + { + const typename BVHN::AABBNodeMB* node = ref.getAABBNodeMB(); + size_t mask = pointQueryNodeSphere(node, query, time, dist); + + if (unlikely(ref.isAABBNodeMB4D())) { + const typename BVHN::AABBNodeMB4D* node1 = (const typename BVHN::AABBNodeMB4D*) node; + const vbool vmask = (node1->lower_t <= time) & (time < node1->upper_t); + mask &= movemask(vmask); + } + + return mask; + } + + template + __forceinline size_t pointQueryNodeSphere(const typename BVHN::QuantizedBaseNode* node, const TravPointQuery& query, vfloat& dist) + { + const vfloat start_x(node->start.x); + const vfloat scale_x(node->scale.x); + const vfloat minX = madd(node->template dequantize((0*sizeof(vfloat)) >> 2),scale_x,start_x); + const vfloat maxX = madd(node->template dequantize((1*sizeof(vfloat)) >> 2),scale_x,start_x); + const vfloat start_y(node->start.y); + const vfloat scale_y(node->scale.y); + const vfloat minY = madd(node->template dequantize((2*sizeof(vfloat)) >> 2),scale_y,start_y); + const vfloat maxY = madd(node->template dequantize((3*sizeof(vfloat)) >> 2),scale_y,start_y); + const vfloat start_z(node->start.z); + const vfloat scale_z(node->scale.z); + const vfloat minZ = madd(node->template dequantize((4*sizeof(vfloat)) >> 2),scale_z,start_z); + const vfloat maxZ = madd(node->template dequantize((5*sizeof(vfloat)) >> 2),scale_z,start_z); + return pointQuerySphereDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ) & movemask(node->validMask()); + } + + template + __forceinline size_t pointQueryNodeSphere(const typename BVHN::QuantizedBaseNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + const vfloat minX = node->dequantizeLowerX(time); + const vfloat maxX = node->dequantizeUpperX(time); + const vfloat minY = node->dequantizeLowerY(time); + const vfloat maxY = node->dequantizeUpperY(time); + const vfloat minZ = node->dequantizeLowerZ(time); + const vfloat maxZ = node->dequantizeUpperZ(time); + return pointQuerySphereDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ) & movemask(node->validMask()); + } + + template + __forceinline size_t pointQueryNodeSphere(const typename BVHN::OBBNode* node, const TravPointQuery& query, vfloat& dist) + { + // TODO: point query - implement + const vbool vmask = vbool(true); + const size_t mask = movemask(vmask) & ((1<(0.0f); + return mask; + } + + template + __forceinline size_t pointQueryNodeSphere(const typename BVHN::OBBNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + // TODO: point query - implement + const vbool vmask = vbool(true); + const size_t mask = movemask(vmask) & ((1<(0.0f); + return mask; + } + + template + __forceinline size_t pointQueryAABBDistAndMask( + const TravPointQuery& query, vfloat& dist, vfloat const& minX, vfloat const& maxX, + vfloat const& minY, vfloat const& maxY, vfloat const& minZ, vfloat const& maxZ) + { + const vfloat vX = min(max(query.org.x, minX), maxX) - query.org.x; + const vfloat vY = min(max(query.org.y, minY), maxY) - query.org.y; + const vfloat vZ = min(max(query.org.z, minZ), maxZ) - query.org.z; + dist = vX * vX + vY * vY + vZ * vZ; + const vbool valid = minX <= maxX; + const vbool vmask = !((maxX < query.org.x - query.rad.x) | (minX > query.org.x + query.rad.x) | + (maxY < query.org.y - query.rad.y) | (minY > query.org.y + query.rad.y) | + (maxZ < query.org.z - query.rad.z) | (minZ > query.org.z + query.rad.z)); + return movemask(vmask) & movemask(valid); + } + + template + __forceinline size_t pointQueryNodeAABB(const typename BVHN::AABBNode* node, const TravPointQuery& query, vfloat& dist) + { + const vfloat minX = vfloat::load((float*)((const char*)&node->lower_x)); + const vfloat minY = vfloat::load((float*)((const char*)&node->lower_y)); + const vfloat minZ = vfloat::load((float*)((const char*)&node->lower_z)); + const vfloat maxX = vfloat::load((float*)((const char*)&node->upper_x)); + const vfloat maxY = vfloat::load((float*)((const char*)&node->upper_y)); + const vfloat maxZ = vfloat::load((float*)((const char*)&node->upper_z)); + return pointQueryAABBDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ); + } + + template + __forceinline size_t pointQueryNodeAABB(const typename BVHN::AABBNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + const vfloat* pMinX = (const vfloat*)((const char*)&node->lower_x); + const vfloat* pMinY = (const vfloat*)((const char*)&node->lower_y); + const vfloat* pMinZ = (const vfloat*)((const char*)&node->lower_z); + const vfloat* pMaxX = (const vfloat*)((const char*)&node->upper_x); + const vfloat* pMaxY = (const vfloat*)((const char*)&node->upper_y); + const vfloat* pMaxZ = (const vfloat*)((const char*)&node->upper_z); + const vfloat minX = madd(time,pMinX[6],vfloat(pMinX[0])); + const vfloat minY = madd(time,pMinY[6],vfloat(pMinY[0])); + const vfloat minZ = madd(time,pMinZ[6],vfloat(pMinZ[0])); + const vfloat maxX = madd(time,pMaxX[6],vfloat(pMaxX[0])); + const vfloat maxY = madd(time,pMaxY[6],vfloat(pMaxY[0])); + const vfloat maxZ = madd(time,pMaxZ[6],vfloat(pMaxZ[0])); + return pointQueryAABBDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ); + } + + template + __forceinline size_t pointQueryNodeAABBMB4D(const typename BVHN::NodeRef ref, const TravPointQuery& query, const float time, vfloat& dist) + { + const typename BVHN::AABBNodeMB* node = ref.getAABBNodeMB(); + size_t mask = pointQueryNodeAABB(node, query, time, dist); + + if (unlikely(ref.isAABBNodeMB4D())) { + const typename BVHN::AABBNodeMB4D* node1 = (const typename BVHN::AABBNodeMB4D*) node; + const vbool vmask = (node1->lower_t <= time) & (time < node1->upper_t); + mask &= movemask(vmask); + } + + return mask; + } + + template + __forceinline size_t pointQueryNodeAABB(const typename BVHN::QuantizedBaseNode* node, const TravPointQuery& query, vfloat& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat start_x(node->start.x); + const vfloat scale_x(node->scale.x); + const vfloat minX = madd(node->template dequantize((0*sizeof(vfloat)) >> 2),scale_x,start_x); + const vfloat maxX = madd(node->template dequantize((1*sizeof(vfloat)) >> 2),scale_x,start_x); + const vfloat start_y(node->start.y); + const vfloat scale_y(node->scale.y); + const vfloat minY = madd(node->template dequantize((2*sizeof(vfloat)) >> 2),scale_y,start_y); + const vfloat maxY = madd(node->template dequantize((3*sizeof(vfloat)) >> 2),scale_y,start_y); + const vfloat start_z(node->start.z); + const vfloat scale_z(node->scale.z); + const vfloat minZ = madd(node->template dequantize((4*sizeof(vfloat)) >> 2),scale_z,start_z); + const vfloat maxZ = madd(node->template dequantize((5*sizeof(vfloat)) >> 2),scale_z,start_z); + return pointQueryAABBDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ) & mvalid; + } + + template + __forceinline size_t pointQueryNodeAABB(const typename BVHN::QuantizedBaseNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat minX = node->dequantizeLowerX(time); + const vfloat maxX = node->dequantizeUpperX(time); + const vfloat minY = node->dequantizeLowerY(time); + const vfloat maxY = node->dequantizeUpperY(time); + const vfloat minZ = node->dequantizeLowerZ(time); + const vfloat maxZ = node->dequantizeUpperZ(time); + return pointQueryAABBDistAndMask(query, dist, minX, maxX, minY, maxY, minZ, maxZ) & mvalid; + } + + template + __forceinline size_t pointQueryNodeAABB(const typename BVHN::OBBNode* node, const TravPointQuery& query, vfloat& dist) + { + // TODO: point query - implement + const vbool vmask = vbool(true); + const size_t mask = movemask(vmask) & ((1<(0.0f); + return mask; + } + + template + __forceinline size_t pointQueryNodeAABB(const typename BVHN::OBBNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + // TODO: point query - implement + const vbool vmask = vbool(true); + const size_t mask = movemask(vmask) & ((1<(0.0f); + return mask; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode(const typename BVHN::AABBNode* node, const TravRay& ray, vfloat& dist); + + template<> + __forceinline size_t intersectNode<4,4>(const typename BVH4::AABBNode* node, const TravRay<4,4,false>& ray, vfloat4& dist) + { +#if defined(__AVX2__) + const vfloat4 tNearX = msub(vfloat4::load((float*)((const char*)&node->lower_x+ray.nearX)), ray.rdir.x, ray.org_rdir.x); + const vfloat4 tNearY = msub(vfloat4::load((float*)((const char*)&node->lower_x+ray.nearY)), ray.rdir.y, ray.org_rdir.y); + const vfloat4 tNearZ = msub(vfloat4::load((float*)((const char*)&node->lower_x+ray.nearZ)), ray.rdir.z, ray.org_rdir.z); + const vfloat4 tFarX = msub(vfloat4::load((float*)((const char*)&node->lower_x+ray.farX )), ray.rdir.x, ray.org_rdir.x); + const vfloat4 tFarY = msub(vfloat4::load((float*)((const char*)&node->lower_x+ray.farY )), ray.rdir.y, ray.org_rdir.y); + const vfloat4 tFarZ = msub(vfloat4::load((float*)((const char*)&node->lower_x+ray.farZ )), ray.rdir.z, ray.org_rdir.z); +#else + const vfloat4 tNearX = (vfloat4::load((float*)((const char*)&node->lower_x+ray.nearX)) - ray.org.x) * ray.rdir.x; + const vfloat4 tNearY = (vfloat4::load((float*)((const char*)&node->lower_x+ray.nearY)) - ray.org.y) * ray.rdir.y; + const vfloat4 tNearZ = (vfloat4::load((float*)((const char*)&node->lower_x+ray.nearZ)) - ray.org.z) * ray.rdir.z; + const vfloat4 tFarX = (vfloat4::load((float*)((const char*)&node->lower_x+ray.farX )) - ray.org.x) * ray.rdir.x; + const vfloat4 tFarY = (vfloat4::load((float*)((const char*)&node->lower_x+ray.farY )) - ray.org.y) * ray.rdir.y; + const vfloat4 tFarZ = (vfloat4::load((float*)((const char*)&node->lower_x+ray.farZ )) - ray.org.z) * ray.rdir.z; +#endif + +#if defined(__SSE4_1__) && !defined(__AVX512F__) // up to HSW + const vfloat4 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = asInt(tNear) > asInt(tFar); + const size_t mask = movemask(vmask) ^ ((1<<4)-1); +#elif defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vfloat4 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = asInt(tNear) <= asInt(tFar); + const size_t mask = movemask(vmask); +#else + const vfloat4 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = tNear <= tFar; + const size_t mask = movemask(vmask); +#endif + dist = tNear; + return mask; + } + +#if defined(__AVX__) + + template<> + __forceinline size_t intersectNode<8,8>(const typename BVH8::AABBNode* node, const TravRay<8,8,false>& ray, vfloat8& dist) + { +#if defined(__AVX2__) + const vfloat8 tNearX = msub(vfloat8::load((float*)((const char*)&node->lower_x+ray.nearX)), ray.rdir.x, ray.org_rdir.x); + const vfloat8 tNearY = msub(vfloat8::load((float*)((const char*)&node->lower_x+ray.nearY)), ray.rdir.y, ray.org_rdir.y); + const vfloat8 tNearZ = msub(vfloat8::load((float*)((const char*)&node->lower_x+ray.nearZ)), ray.rdir.z, ray.org_rdir.z); + const vfloat8 tFarX = msub(vfloat8::load((float*)((const char*)&node->lower_x+ray.farX )), ray.rdir.x, ray.org_rdir.x); + const vfloat8 tFarY = msub(vfloat8::load((float*)((const char*)&node->lower_x+ray.farY )), ray.rdir.y, ray.org_rdir.y); + const vfloat8 tFarZ = msub(vfloat8::load((float*)((const char*)&node->lower_x+ray.farZ )), ray.rdir.z, ray.org_rdir.z); +#else + const vfloat8 tNearX = (vfloat8::load((float*)((const char*)&node->lower_x+ray.nearX)) - ray.org.x) * ray.rdir.x; + const vfloat8 tNearY = (vfloat8::load((float*)((const char*)&node->lower_x+ray.nearY)) - ray.org.y) * ray.rdir.y; + const vfloat8 tNearZ = (vfloat8::load((float*)((const char*)&node->lower_x+ray.nearZ)) - ray.org.z) * ray.rdir.z; + const vfloat8 tFarX = (vfloat8::load((float*)((const char*)&node->lower_x+ray.farX )) - ray.org.x) * ray.rdir.x; + const vfloat8 tFarY = (vfloat8::load((float*)((const char*)&node->lower_x+ray.farY )) - ray.org.y) * ray.rdir.y; + const vfloat8 tFarZ = (vfloat8::load((float*)((const char*)&node->lower_x+ray.farZ )) - ray.org.z) * ray.rdir.z; +#endif + +#if defined(__AVX2__) && !defined(__AVX512F__) // HSW + const vfloat8 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = asInt(tNear) > asInt(tFar); + const size_t mask = movemask(vmask) ^ ((1<<8)-1); +#elif defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vfloat8 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = asInt(tNear) <= asInt(tFar); + const size_t mask = movemask(vmask); +#else + const vfloat8 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = tNear <= tFar; + const size_t mask = movemask(vmask); +#endif + dist = tNear; + return mask; + } + +#endif + +#if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL + + template<> + __forceinline size_t intersectNode<4,16>(const typename BVH4::AABBNode* node, const TravRay<4,16,false>& ray, vfloat16& dist) + { + const vfloat16 tNearX = msub(vfloat16(*(vfloat4*)((const char*)&node->lower_x+ray.nearX)), ray.rdir.x, ray.org_rdir.x); + const vfloat16 tNearY = msub(vfloat16(*(vfloat4*)((const char*)&node->lower_x+ray.nearY)), ray.rdir.y, ray.org_rdir.y); + const vfloat16 tNearZ = msub(vfloat16(*(vfloat4*)((const char*)&node->lower_x+ray.nearZ)), ray.rdir.z, ray.org_rdir.z); + const vfloat16 tFarX = msub(vfloat16(*(vfloat4*)((const char*)&node->lower_x+ray.farX )), ray.rdir.x, ray.org_rdir.x); + const vfloat16 tFarY = msub(vfloat16(*(vfloat4*)((const char*)&node->lower_x+ray.farY )), ray.rdir.y, ray.org_rdir.y); + const vfloat16 tFarZ = msub(vfloat16(*(vfloat4*)((const char*)&node->lower_x+ray.farZ )), ray.rdir.z, ray.org_rdir.z); + const vfloat16 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat16 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool16 vmask = le(vbool16(0xf),tNear,tFar); + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + template<> + __forceinline size_t intersectNode<8,16>(const typename BVH8::AABBNode* node, const TravRay<8,16,false>& ray, vfloat16& dist) + { + const vllong8 invalid((size_t)BVH8::emptyNode); + const vboold8 m_valid(invalid != vllong8::loadu(node->children)); + const vfloat16 bminmaxX = permute(vfloat16::load((const float*)&node->lower_x), ray.permX); + const vfloat16 bminmaxY = permute(vfloat16::load((const float*)&node->lower_y), ray.permY); + const vfloat16 bminmaxZ = permute(vfloat16::load((const float*)&node->lower_z), ray.permZ); + const vfloat16 tNearFarX = msub(bminmaxX, ray.rdir.x, ray.org_rdir.x); + const vfloat16 tNearFarY = msub(bminmaxY, ray.rdir.y, ray.org_rdir.y); + const vfloat16 tNearFarZ = msub(bminmaxZ, ray.rdir.z, ray.org_rdir.z); + const vfloat16 tNear = max(tNearFarX, tNearFarY, tNearFarZ, ray.tnear); + const vfloat16 tFar = min(tNearFarX, tNearFarY, tNearFarZ, ray.tfar); + const vbool16 vmask = le(vboolf16(m_valid),tNear,align_shift_right<8>(tFar, tFar)); + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + +#endif + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNodeRobust(const typename BVHN::AABBNode* node, const TravRay& ray, vfloat& dist) + { + const vfloat tNearX = (vfloat::load((float*)((const char*)&node->lower_x+ray.nearX)) - ray.org.x) * ray.rdir_near.x; + const vfloat tNearY = (vfloat::load((float*)((const char*)&node->lower_x+ray.nearY)) - ray.org.y) * ray.rdir_near.y; + const vfloat tNearZ = (vfloat::load((float*)((const char*)&node->lower_x+ray.nearZ)) - ray.org.z) * ray.rdir_near.z; + const vfloat tFarX = (vfloat::load((float*)((const char*)&node->lower_x+ray.farX )) - ray.org.x) * ray.rdir_far.x; + const vfloat tFarY = (vfloat::load((float*)((const char*)&node->lower_x+ray.farY )) - ray.org.y) * ray.rdir_far.y; + const vfloat tFarZ = (vfloat::load((float*)((const char*)&node->lower_x+ray.farZ )) - ray.org.z) * ray.rdir_far.z; + const vfloat tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool vmask = tNear <= tFar; + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + +#if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL + + template<> + __forceinline size_t intersectNodeRobust<4,16>(const typename BVHN<4>::AABBNode* node, const TravRay<4,16,true>& ray, vfloat<16>& dist) + { + const vfloat16 tNearX = (vfloat16(*(vfloat<4>*)((const char*)&node->lower_x+ray.nearX)) - ray.org.x) * ray.rdir_near.x; + const vfloat16 tNearY = (vfloat16(*(vfloat<4>*)((const char*)&node->lower_x+ray.nearY)) - ray.org.y) * ray.rdir_near.y; + const vfloat16 tNearZ = (vfloat16(*(vfloat<4>*)((const char*)&node->lower_x+ray.nearZ)) - ray.org.z) * ray.rdir_near.z; + const vfloat16 tFarX = (vfloat16(*(vfloat<4>*)((const char*)&node->lower_x+ray.farX )) - ray.org.x) * ray.rdir_far.x; + const vfloat16 tFarY = (vfloat16(*(vfloat<4>*)((const char*)&node->lower_x+ray.farY )) - ray.org.y) * ray.rdir_far.y; + const vfloat16 tFarZ = (vfloat16(*(vfloat<4>*)((const char*)&node->lower_x+ray.farZ )) - ray.org.z) * ray.rdir_far.z; + const vfloat16 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat16 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool16 vmask = le((1 << 4)-1,tNear,tFar); + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + template<> + __forceinline size_t intersectNodeRobust<8,16>(const typename BVHN<8>::AABBNode* node, const TravRay<8,16,true>& ray, vfloat<16>& dist) + { + const vfloat16 tNearX = (vfloat16(*(vfloat<8>*)((const char*)&node->lower_x+ray.nearX)) - ray.org.x) * ray.rdir_near.x; + const vfloat16 tNearY = (vfloat16(*(vfloat<8>*)((const char*)&node->lower_x+ray.nearY)) - ray.org.y) * ray.rdir_near.y; + const vfloat16 tNearZ = (vfloat16(*(vfloat<8>*)((const char*)&node->lower_x+ray.nearZ)) - ray.org.z) * ray.rdir_near.z; + const vfloat16 tFarX = (vfloat16(*(vfloat<8>*)((const char*)&node->lower_x+ray.farX )) - ray.org.x) * ray.rdir_far.x; + const vfloat16 tFarY = (vfloat16(*(vfloat<8>*)((const char*)&node->lower_x+ray.farY )) - ray.org.y) * ray.rdir_far.y; + const vfloat16 tFarZ = (vfloat16(*(vfloat<8>*)((const char*)&node->lower_x+ray.farZ )) - ray.org.z) * ray.rdir_far.z; + const vfloat16 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat16 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool16 vmask = le((1 << 8)-1,tNear,tFar); + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + +#endif + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNodeMB intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode(const typename BVHN::AABBNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + const vfloat* pNearX = (const vfloat*)((const char*)&node->lower_x+ray.nearX); + const vfloat* pNearY = (const vfloat*)((const char*)&node->lower_x+ray.nearY); + const vfloat* pNearZ = (const vfloat*)((const char*)&node->lower_x+ray.nearZ); + const vfloat* pFarX = (const vfloat*)((const char*)&node->lower_x+ray.farX); + const vfloat* pFarY = (const vfloat*)((const char*)&node->lower_x+ray.farY); + const vfloat* pFarZ = (const vfloat*)((const char*)&node->lower_x+ray.farZ); +#if defined(__AVX2__) + const vfloat tNearX = msub(madd(time,pNearX[6],vfloat(pNearX[0])), ray.rdir.x, ray.org_rdir.x); + const vfloat tNearY = msub(madd(time,pNearY[6],vfloat(pNearY[0])), ray.rdir.y, ray.org_rdir.y); + const vfloat tNearZ = msub(madd(time,pNearZ[6],vfloat(pNearZ[0])), ray.rdir.z, ray.org_rdir.z); + const vfloat tFarX = msub(madd(time,pFarX [6],vfloat(pFarX [0])), ray.rdir.x, ray.org_rdir.x); + const vfloat tFarY = msub(madd(time,pFarY [6],vfloat(pFarY [0])), ray.rdir.y, ray.org_rdir.y); + const vfloat tFarZ = msub(madd(time,pFarZ [6],vfloat(pFarZ [0])), ray.rdir.z, ray.org_rdir.z); +#else + const vfloat tNearX = (madd(time,pNearX[6],vfloat(pNearX[0])) - ray.org.x) * ray.rdir.x; + const vfloat tNearY = (madd(time,pNearY[6],vfloat(pNearY[0])) - ray.org.y) * ray.rdir.y; + const vfloat tNearZ = (madd(time,pNearZ[6],vfloat(pNearZ[0])) - ray.org.z) * ray.rdir.z; + const vfloat tFarX = (madd(time,pFarX [6],vfloat(pFarX [0])) - ray.org.x) * ray.rdir.x; + const vfloat tFarY = (madd(time,pFarY [6],vfloat(pFarY [0])) - ray.org.y) * ray.rdir.y; + const vfloat tFarZ = (madd(time,pFarZ [6],vfloat(pFarZ [0])) - ray.org.z) * ray.rdir.z; +#endif +#if defined(__AVX2__) && !defined(__AVX512F__) // HSW + const vfloat tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool vmask = asInt(tNear) > asInt(tFar); + const size_t mask = movemask(vmask) ^ ((1< tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool vmask = asInt(tNear) <= asInt(tFar); + const size_t mask = movemask(vmask); +#else + const vfloat tNear = max(ray.tnear,tNearX,tNearY,tNearZ); + const vfloat tFar = min(ray.tfar, tFarX ,tFarY ,tFarZ ); + const vbool vmask = tNear <= tFar; + const size_t mask = movemask(vmask); +#endif + dist = tNear; + return mask; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNodeMB intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNodeRobust(const typename BVHN::AABBNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + const vfloat* pNearX = (const vfloat*)((const char*)&node->lower_x+ray.nearX); + const vfloat* pNearY = (const vfloat*)((const char*)&node->lower_x+ray.nearY); + const vfloat* pNearZ = (const vfloat*)((const char*)&node->lower_x+ray.nearZ); + const vfloat tNearX = (madd(time,pNearX[6],vfloat(pNearX[0])) - ray.org.x) * ray.rdir_near.x; + const vfloat tNearY = (madd(time,pNearY[6],vfloat(pNearY[0])) - ray.org.y) * ray.rdir_near.y; + const vfloat tNearZ = (madd(time,pNearZ[6],vfloat(pNearZ[0])) - ray.org.z) * ray.rdir_near.z; + const vfloat tNear = max(ray.tnear,tNearX,tNearY,tNearZ); + const vfloat* pFarX = (const vfloat*)((const char*)&node->lower_x+ray.farX); + const vfloat* pFarY = (const vfloat*)((const char*)&node->lower_x+ray.farY); + const vfloat* pFarZ = (const vfloat*)((const char*)&node->lower_x+ray.farZ); + const vfloat tFarX = (madd(time,pFarX[6],vfloat(pFarX[0])) - ray.org.x) * ray.rdir_far.x; + const vfloat tFarY = (madd(time,pFarY[6],vfloat(pFarY[0])) - ray.org.y) * ray.rdir_far.y; + const vfloat tFarZ = (madd(time,pFarZ[6],vfloat(pFarZ[0])) - ray.org.z) * ray.rdir_far.z; + const vfloat tFar = min(ray.tfar,tFarX,tFarY,tFarZ); + const size_t mask = movemask(tNear <= tFar); + dist = tNear; + return mask; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNodeMB4D intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNodeMB4D(const typename BVHN::NodeRef ref, const TravRay& ray, const float time, vfloat& dist) + { + const typename BVHN::AABBNodeMB* node = ref.getAABBNodeMB(); + + const vfloat* pNearX = (const vfloat*)((const char*)&node->lower_x+ray.nearX); + const vfloat* pNearY = (const vfloat*)((const char*)&node->lower_x+ray.nearY); + const vfloat* pNearZ = (const vfloat*)((const char*)&node->lower_x+ray.nearZ); + const vfloat* pFarX = (const vfloat*)((const char*)&node->lower_x+ray.farX); + const vfloat* pFarY = (const vfloat*)((const char*)&node->lower_x+ray.farY); + const vfloat* pFarZ = (const vfloat*)((const char*)&node->lower_x+ray.farZ); +#if defined (__AVX2__) + const vfloat tNearX = msub(madd(time,pNearX[6],vfloat(pNearX[0])), ray.rdir.x, ray.org_rdir.x); + const vfloat tNearY = msub(madd(time,pNearY[6],vfloat(pNearY[0])), ray.rdir.y, ray.org_rdir.y); + const vfloat tNearZ = msub(madd(time,pNearZ[6],vfloat(pNearZ[0])), ray.rdir.z, ray.org_rdir.z); + const vfloat tFarX = msub(madd(time,pFarX [6],vfloat(pFarX [0])), ray.rdir.x, ray.org_rdir.x); + const vfloat tFarY = msub(madd(time,pFarY [6],vfloat(pFarY [0])), ray.rdir.y, ray.org_rdir.y); + const vfloat tFarZ = msub(madd(time,pFarZ [6],vfloat(pFarZ [0])), ray.rdir.z, ray.org_rdir.z); +#else + const vfloat tNearX = (madd(time,pNearX[6],vfloat(pNearX[0])) - ray.org.x) * ray.rdir.x; + const vfloat tNearY = (madd(time,pNearY[6],vfloat(pNearY[0])) - ray.org.y) * ray.rdir.y; + const vfloat tNearZ = (madd(time,pNearZ[6],vfloat(pNearZ[0])) - ray.org.z) * ray.rdir.z; + const vfloat tFarX = (madd(time,pFarX [6],vfloat(pFarX [0])) - ray.org.x) * ray.rdir.x; + const vfloat tFarY = (madd(time,pFarY [6],vfloat(pFarY [0])) - ray.org.y) * ray.rdir.y; + const vfloat tFarZ = (madd(time,pFarZ [6],vfloat(pFarZ [0])) - ray.org.z) * ray.rdir.z; +#endif +#if defined(__AVX2__) && !defined(__AVX512F__) + const vfloat tNear = maxi(maxi(tNearX,tNearY),maxi(tNearZ,ray.tnear)); + const vfloat tFar = mini(mini(tFarX ,tFarY ),mini(tFarZ ,ray.tfar )); +#else + const vfloat tNear = max(ray.tnear,tNearX,tNearY,tNearZ); + const vfloat tFar = min(ray.tfar, tFarX ,tFarY ,tFarZ ); +#endif + vbool vmask = tNear <= tFar; + if (unlikely(ref.isAABBNodeMB4D())) { + const typename BVHN::AABBNodeMB4D* node1 = (const typename BVHN::AABBNodeMB4D*) node; + vmask &= (node1->lower_t <= time) & (time < node1->upper_t); + } + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNodeMB4D intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNodeMB4DRobust(const typename BVHN::NodeRef ref, const TravRay& ray, const float time, vfloat& dist) + { + const typename BVHN::AABBNodeMB* node = ref.getAABBNodeMB(); + + const vfloat* pNearX = (const vfloat*)((const char*)&node->lower_x+ray.nearX); + const vfloat* pNearY = (const vfloat*)((const char*)&node->lower_x+ray.nearY); + const vfloat* pNearZ = (const vfloat*)((const char*)&node->lower_x+ray.nearZ); + const vfloat tNearX = (madd(time,pNearX[6],vfloat(pNearX[0])) - ray.org.x) * ray.rdir_near.x; + const vfloat tNearY = (madd(time,pNearY[6],vfloat(pNearY[0])) - ray.org.y) * ray.rdir_near.y; + const vfloat tNearZ = (madd(time,pNearZ[6],vfloat(pNearZ[0])) - ray.org.z) * ray.rdir_near.z; + const vfloat tNear = max(ray.tnear,tNearX,tNearY,tNearZ); + const vfloat* pFarX = (const vfloat*)((const char*)&node->lower_x+ray.farX); + const vfloat* pFarY = (const vfloat*)((const char*)&node->lower_x+ray.farY); + const vfloat* pFarZ = (const vfloat*)((const char*)&node->lower_x+ray.farZ); + const vfloat tFarX = (madd(time,pFarX[6],vfloat(pFarX[0])) - ray.org.x) * ray.rdir_far.x; + const vfloat tFarY = (madd(time,pFarY[6],vfloat(pFarY[0])) - ray.org.y) * ray.rdir_far.y; + const vfloat tFarZ = (madd(time,pFarZ[6],vfloat(pFarZ[0])) - ray.org.z) * ray.rdir_far.z; + const vfloat tFar = min(ray.tfar,tFarX,tFarY,tFarZ); + vbool vmask = tNear <= tFar; + if (unlikely(ref.isAABBNodeMB4D())) { + const typename BVHN::AABBNodeMB4D* node1 = (const typename BVHN::AABBNodeMB4D*) node; + vmask &= (node1->lower_t <= time) & (time < node1->upper_t); + } + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast QuantizedBaseNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode(const typename BVHN::QuantizedBaseNode* node, const TravRay& ray, vfloat& dist); + + template<> + __forceinline size_t intersectNode<4,4>(const typename BVH4::QuantizedBaseNode* node, const TravRay<4,4,false>& ray, vfloat4& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat4 start_x(node->start.x); + const vfloat4 scale_x(node->scale.x); + const vfloat4 lower_x = madd(node->dequantize<4>(ray.nearX >> 2),scale_x,start_x); + const vfloat4 upper_x = madd(node->dequantize<4>(ray.farX >> 2),scale_x,start_x); + const vfloat4 start_y(node->start.y); + const vfloat4 scale_y(node->scale.y); + const vfloat4 lower_y = madd(node->dequantize<4>(ray.nearY >> 2),scale_y,start_y); + const vfloat4 upper_y = madd(node->dequantize<4>(ray.farY >> 2),scale_y,start_y); + const vfloat4 start_z(node->start.z); + const vfloat4 scale_z(node->scale.z); + const vfloat4 lower_z = madd(node->dequantize<4>(ray.nearZ >> 2),scale_z,start_z); + const vfloat4 upper_z = madd(node->dequantize<4>(ray.farZ >> 2),scale_z,start_z); + +#if defined(__AVX2__) + const vfloat4 tNearX = msub(lower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat4 tNearY = msub(lower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat4 tNearZ = msub(lower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat4 tFarX = msub(upper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat4 tFarY = msub(upper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat4 tFarZ = msub(upper_z, ray.rdir.z, ray.org_rdir.z); +#else + const vfloat4 tNearX = (lower_x - ray.org.x) * ray.rdir.x; + const vfloat4 tNearY = (lower_y - ray.org.y) * ray.rdir.y; + const vfloat4 tNearZ = (lower_z - ray.org.z) * ray.rdir.z; + const vfloat4 tFarX = (upper_x - ray.org.x) * ray.rdir.x; + const vfloat4 tFarY = (upper_y - ray.org.y) * ray.rdir.y; + const vfloat4 tFarZ = (upper_z - ray.org.z) * ray.rdir.z; +#endif + +#if defined(__SSE4_1__) && !defined(__AVX512F__) // up to HSW + const vfloat4 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = asInt(tNear) > asInt(tFar); + const size_t mask = movemask(vmask) ^ ((1<<4)-1); +#elif defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vfloat4 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = asInt(tNear) <= asInt(tFar); + const size_t mask = movemask(vmask); +#else + const vfloat4 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = tNear <= tFar; + const size_t mask = movemask(vmask); +#endif + dist = tNear; + return mask & mvalid; + } + + template<> + __forceinline size_t intersectNode<4,4>(const typename BVH4::QuantizedBaseNode* node, const TravRay<4,4,true>& ray, vfloat4& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat4 start_x(node->start.x); + const vfloat4 scale_x(node->scale.x); + const vfloat4 lower_x = madd(node->dequantize<4>(ray.nearX >> 2),scale_x,start_x); + const vfloat4 upper_x = madd(node->dequantize<4>(ray.farX >> 2),scale_x,start_x); + const vfloat4 start_y(node->start.y); + const vfloat4 scale_y(node->scale.y); + const vfloat4 lower_y = madd(node->dequantize<4>(ray.nearY >> 2),scale_y,start_y); + const vfloat4 upper_y = madd(node->dequantize<4>(ray.farY >> 2),scale_y,start_y); + const vfloat4 start_z(node->start.z); + const vfloat4 scale_z(node->scale.z); + const vfloat4 lower_z = madd(node->dequantize<4>(ray.nearZ >> 2),scale_z,start_z); + const vfloat4 upper_z = madd(node->dequantize<4>(ray.farZ >> 2),scale_z,start_z); + + const vfloat4 tNearX = (lower_x - ray.org.x) * ray.rdir_near.x; + const vfloat4 tNearY = (lower_y - ray.org.y) * ray.rdir_near.y; + const vfloat4 tNearZ = (lower_z - ray.org.z) * ray.rdir_near.z; + const vfloat4 tFarX = (upper_x - ray.org.x) * ray.rdir_far.x; + const vfloat4 tFarY = (upper_y - ray.org.y) * ray.rdir_far.y; + const vfloat4 tFarZ = (upper_z - ray.org.z) * ray.rdir_far.z; + + const vfloat4 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat4 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool4 vmask = tNear <= tFar; + const size_t mask = movemask(vmask); + dist = tNear; + return mask & mvalid; + } + + +#if defined(__AVX__) + + template<> + __forceinline size_t intersectNode<8,8>(const typename BVH8::QuantizedBaseNode* node, const TravRay<8,8,false>& ray, vfloat8& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat8 start_x(node->start.x); + const vfloat8 scale_x(node->scale.x); + const vfloat8 lower_x = madd(node->dequantize<8>(ray.nearX >> 2),scale_x,start_x); + const vfloat8 upper_x = madd(node->dequantize<8>(ray.farX >> 2),scale_x,start_x); + const vfloat8 start_y(node->start.y); + const vfloat8 scale_y(node->scale.y); + const vfloat8 lower_y = madd(node->dequantize<8>(ray.nearY >> 2),scale_y,start_y); + const vfloat8 upper_y = madd(node->dequantize<8>(ray.farY >> 2),scale_y,start_y); + const vfloat8 start_z(node->start.z); + const vfloat8 scale_z(node->scale.z); + const vfloat8 lower_z = madd(node->dequantize<8>(ray.nearZ >> 2),scale_z,start_z); + const vfloat8 upper_z = madd(node->dequantize<8>(ray.farZ >> 2),scale_z,start_z); + +#if defined(__AVX2__) + const vfloat8 tNearX = msub(lower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat8 tNearY = msub(lower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat8 tNearZ = msub(lower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat8 tFarX = msub(upper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat8 tFarY = msub(upper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat8 tFarZ = msub(upper_z, ray.rdir.z, ray.org_rdir.z); +#else + const vfloat8 tNearX = (lower_x - ray.org.x) * ray.rdir.x; + const vfloat8 tNearY = (lower_y - ray.org.y) * ray.rdir.y; + const vfloat8 tNearZ = (lower_z - ray.org.z) * ray.rdir.z; + const vfloat8 tFarX = (upper_x - ray.org.x) * ray.rdir.x; + const vfloat8 tFarY = (upper_y - ray.org.y) * ray.rdir.y; + const vfloat8 tFarZ = (upper_z - ray.org.z) * ray.rdir.z; +#endif + +#if defined(__AVX2__) && !defined(__AVX512F__) // HSW + const vfloat8 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = asInt(tNear) > asInt(tFar); + const size_t mask = movemask(vmask) ^ ((1<<8)-1); +#elif defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vfloat8 tNear = maxi(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = mini(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = asInt(tNear) <= asInt(tFar); + const size_t mask = movemask(vmask); +#else + const vfloat8 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = tNear <= tFar; + const size_t mask = movemask(vmask); +#endif + dist = tNear; + return mask & mvalid; + } + + template<> + __forceinline size_t intersectNode<8,8>(const typename BVH8::QuantizedBaseNode* node, const TravRay<8,8,true>& ray, vfloat8& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat8 start_x(node->start.x); + const vfloat8 scale_x(node->scale.x); + const vfloat8 lower_x = madd(node->dequantize<8>(ray.nearX >> 2),scale_x,start_x); + const vfloat8 upper_x = madd(node->dequantize<8>(ray.farX >> 2),scale_x,start_x); + const vfloat8 start_y(node->start.y); + const vfloat8 scale_y(node->scale.y); + const vfloat8 lower_y = madd(node->dequantize<8>(ray.nearY >> 2),scale_y,start_y); + const vfloat8 upper_y = madd(node->dequantize<8>(ray.farY >> 2),scale_y,start_y); + const vfloat8 start_z(node->start.z); + const vfloat8 scale_z(node->scale.z); + const vfloat8 lower_z = madd(node->dequantize<8>(ray.nearZ >> 2),scale_z,start_z); + const vfloat8 upper_z = madd(node->dequantize<8>(ray.farZ >> 2),scale_z,start_z); + + const vfloat8 tNearX = (lower_x - ray.org.x) * ray.rdir_near.x; + const vfloat8 tNearY = (lower_y - ray.org.y) * ray.rdir_near.y; + const vfloat8 tNearZ = (lower_z - ray.org.z) * ray.rdir_near.z; + const vfloat8 tFarX = (upper_x - ray.org.x) * ray.rdir_far.x; + const vfloat8 tFarY = (upper_y - ray.org.y) * ray.rdir_far.y; + const vfloat8 tFarZ = (upper_z - ray.org.z) * ray.rdir_far.z; + + const vfloat8 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat8 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool8 vmask = tNear <= tFar; + const size_t mask = movemask(vmask); + + dist = tNear; + return mask & mvalid; + } + + +#endif + +#if defined(__AVX512F__) && !defined(__AVX512VL__) // KNL + + template<> + __forceinline size_t intersectNode<4,16>(const typename BVH4::QuantizedBaseNode* node, const TravRay<4,16,false>& ray, vfloat16& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat16 start_x(node->start.x); + const vfloat16 scale_x(node->scale.x); + const vfloat16 lower_x = madd(vfloat16(node->dequantize<4>(ray.nearX >> 2)),scale_x,start_x); + const vfloat16 upper_x = madd(vfloat16(node->dequantize<4>(ray.farX >> 2)),scale_x,start_x); + const vfloat16 start_y(node->start.y); + const vfloat16 scale_y(node->scale.y); + const vfloat16 lower_y = madd(vfloat16(node->dequantize<4>(ray.nearY >> 2)),scale_y,start_y); + const vfloat16 upper_y = madd(vfloat16(node->dequantize<4>(ray.farY >> 2)),scale_y,start_y); + const vfloat16 start_z(node->start.z); + const vfloat16 scale_z(node->scale.z); + const vfloat16 lower_z = madd(vfloat16(node->dequantize<4>(ray.nearZ >> 2)),scale_z,start_z); + const vfloat16 upper_z = madd(vfloat16(node->dequantize<4>(ray.farZ >> 2)),scale_z,start_z); + + const vfloat16 tNearX = msub(lower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat16 tNearY = msub(lower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat16 tNearZ = msub(lower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat16 tFarX = msub(upper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat16 tFarY = msub(upper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat16 tFarZ = msub(upper_z, ray.rdir.z, ray.org_rdir.z); + const vfloat16 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat16 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool16 vmask = le(vbool16(0xf),tNear,tFar); + const size_t mask = movemask(vmask) & mvalid; + dist = tNear; + return mask; + } + + template<> + __forceinline size_t intersectNode<4,16>(const typename BVH4::QuantizedBaseNode* node, const TravRay<4,16,true>& ray, vfloat16& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat16 start_x(node->start.x); + const vfloat16 scale_x(node->scale.x); + const vfloat16 lower_x = madd(vfloat16(node->dequantize<4>(ray.nearX >> 2)),scale_x,start_x); + const vfloat16 upper_x = madd(vfloat16(node->dequantize<4>(ray.farX >> 2)),scale_x,start_x); + const vfloat16 start_y(node->start.y); + const vfloat16 scale_y(node->scale.y); + const vfloat16 lower_y = madd(vfloat16(node->dequantize<4>(ray.nearY >> 2)),scale_y,start_y); + const vfloat16 upper_y = madd(vfloat16(node->dequantize<4>(ray.farY >> 2)),scale_y,start_y); + const vfloat16 start_z(node->start.z); + const vfloat16 scale_z(node->scale.z); + const vfloat16 lower_z = madd(vfloat16(node->dequantize<4>(ray.nearZ >> 2)),scale_z,start_z); + const vfloat16 upper_z = madd(vfloat16(node->dequantize<4>(ray.farZ >> 2)),scale_z,start_z); + + const vfloat16 tNearX = (lower_x - ray.org.x) * ray.rdir_near.x; + const vfloat16 tNearY = (lower_y - ray.org.y) * ray.rdir_near.y; + const vfloat16 tNearZ = (lower_z - ray.org.z) * ray.rdir_near.z; + const vfloat16 tFarX = (upper_x - ray.org.x) * ray.rdir_far.x; + const vfloat16 tFarY = (upper_y - ray.org.y) * ray.rdir_far.y; + const vfloat16 tFarZ = (upper_z - ray.org.z) * ray.rdir_far.z; + + const vfloat16 tNear = max(tNearX,tNearY,tNearZ,ray.tnear); + const vfloat16 tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); + const vbool16 vmask = le(vbool16(0xf),tNear,tFar); + const size_t mask = movemask(vmask) & mvalid; + dist = tNear; + return mask; + } + + template<> + __forceinline size_t intersectNode<8,16>(const typename BVH8::QuantizedBaseNode* node, const TravRay<8,16,false>& ray, vfloat16& dist) + { + const vbool16 m_valid(node->validMask16()); + const vfloat16 bminmaxX = node->dequantizeLowerUpperX(ray.permX); + const vfloat16 bminmaxY = node->dequantizeLowerUpperY(ray.permY); + const vfloat16 bminmaxZ = node->dequantizeLowerUpperZ(ray.permZ); + const vfloat16 tNearFarX = msub(bminmaxX, ray.rdir.x, ray.org_rdir.x); + const vfloat16 tNearFarY = msub(bminmaxY, ray.rdir.y, ray.org_rdir.y); + const vfloat16 tNearFarZ = msub(bminmaxZ, ray.rdir.z, ray.org_rdir.z); + const vfloat16 tNear = max(tNearFarX, tNearFarY, tNearFarZ, ray.tnear); + const vfloat16 tFar = min(tNearFarX, tNearFarY, tNearFarZ, ray.tfar); + const vbool16 vmask = le(m_valid,tNear,align_shift_right<8>(tFar, tFar)); + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + template<> + __forceinline size_t intersectNode<8,16>(const typename BVH8::QuantizedBaseNode* node, const TravRay<8,16,true>& ray, vfloat16& dist) + { + const vbool16 m_valid(node->validMask16()); + const vfloat16 bminmaxX = node->dequantizeLowerUpperX(ray.permX); + const vfloat16 bminmaxY = node->dequantizeLowerUpperY(ray.permY); + const vfloat16 bminmaxZ = node->dequantizeLowerUpperZ(ray.permZ); + const vfloat16 tNearFarX = (bminmaxX - ray.org.x) * ray.rdir_far.x; // FIXME: this is not conservative !!!!!!!!! + const vfloat16 tNearFarY = (bminmaxY - ray.org.y) * ray.rdir_far.y; + const vfloat16 tNearFarZ = (bminmaxZ - ray.org.z) * ray.rdir_far.z; + const vfloat16 tNear = max(tNearFarX, tNearFarY, tNearFarZ, ray.tnear); + const vfloat16 tFar = min(tNearFarX, tNearFarY, tNearFarZ, ray.tfar); + const vbool16 vmask = le(m_valid,tNear,align_shift_right<8>(tFar, tFar)); + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + +#endif + + + template + __forceinline size_t intersectNode(const typename BVHN::QuantizedBaseNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + const vboolf mvalid = node->validMask(); + const vfloat lower_x = node->dequantizeLowerX(time); + const vfloat upper_x = node->dequantizeUpperX(time); + const vfloat lower_y = node->dequantizeLowerY(time); + const vfloat upper_y = node->dequantizeUpperY(time); + const vfloat lower_z = node->dequantizeLowerZ(time); + const vfloat upper_z = node->dequantizeUpperZ(time); +#if defined(__AVX2__) + const vfloat tNearX = msub(lower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat tNearY = msub(lower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat tNearZ = msub(lower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat tFarX = msub(upper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat tFarY = msub(upper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat tFarZ = msub(upper_z, ray.rdir.z, ray.org_rdir.z); +#else + const vfloat tNearX = (lower_x - ray.org.x) * ray.rdir.x; + const vfloat tNearY = (lower_y - ray.org.y) * ray.rdir.y; + const vfloat tNearZ = (lower_z - ray.org.z) * ray.rdir.z; + const vfloat tFarX = (upper_x - ray.org.x) * ray.rdir.x; + const vfloat tFarY = (upper_y - ray.org.y) * ray.rdir.y; + const vfloat tFarZ = (upper_z - ray.org.z) * ray.rdir.z; +#endif + + const vfloat tminX = mini(tNearX,tFarX); + const vfloat tmaxX = maxi(tNearX,tFarX); + const vfloat tminY = mini(tNearY,tFarY); + const vfloat tmaxY = maxi(tNearY,tFarY); + const vfloat tminZ = mini(tNearZ,tFarZ); + const vfloat tmaxZ = maxi(tNearZ,tFarZ); + const vfloat tNear = maxi(tminX,tminY,tminZ,ray.tnear); + const vfloat tFar = mini(tmaxX,tmaxY,tmaxZ,ray.tfar); +#if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vbool vmask = le(mvalid,asInt(tNear),asInt(tFar)); +#else + const vbool vmask = (asInt(tNear) <= asInt(tFar)) & mvalid; +#endif + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + template + __forceinline size_t intersectNode(const typename BVHN::QuantizedBaseNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + const vboolf mvalid = node->validMask(); + const vfloat lower_x = node->dequantizeLowerX(time); + const vfloat upper_x = node->dequantizeUpperX(time); + const vfloat lower_y = node->dequantizeLowerY(time); + const vfloat upper_y = node->dequantizeUpperY(time); + const vfloat lower_z = node->dequantizeLowerZ(time); + const vfloat upper_z = node->dequantizeUpperZ(time); + const vfloat tNearX = (lower_x - ray.org.x) * ray.rdir_near.x; + const vfloat tNearY = (lower_y - ray.org.y) * ray.rdir_near.y; + const vfloat tNearZ = (lower_z - ray.org.z) * ray.rdir_near.z; + const vfloat tFarX = (upper_x - ray.org.x) * ray.rdir_far.x; + const vfloat tFarY = (upper_y - ray.org.y) * ray.rdir_far.y; + const vfloat tFarZ = (upper_z - ray.org.z) * ray.rdir_far.z; + + const vfloat tminX = mini(tNearX,tFarX); + const vfloat tmaxX = maxi(tNearX,tFarX); + const vfloat tminY = mini(tNearY,tFarY); + const vfloat tmaxY = maxi(tNearY,tFarY); + const vfloat tminZ = mini(tNearZ,tFarZ); + const vfloat tmaxZ = maxi(tNearZ,tFarZ); + const vfloat tNear = maxi(tminX,tminY,tminZ,ray.tnear); + const vfloat tFar = mini(tmaxX,tmaxY,tmaxZ,ray.tfar); +#if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vbool vmask = le(mvalid,asInt(tNear),asInt(tFar)); +#else + const vbool vmask = (asInt(tNear) <= asInt(tFar)) & mvalid; +#endif + const size_t mask = movemask(vmask); + dist = tNear; + return mask; + } + + +#if defined(__AVX512ER__) + // for KNL + template<> + __forceinline size_t intersectNode<4,16>(const typename BVHN<4>::QuantizedBaseNodeMB* node, const TravRay<4,16,false>& ray, const float time, vfloat<4>& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat16 lower_x = node->dequantizeLowerX(time); + const vfloat16 upper_x = node->dequantizeUpperX(time); + const vfloat16 lower_y = node->dequantizeLowerY(time); + const vfloat16 upper_y = node->dequantizeUpperY(time); + const vfloat16 lower_z = node->dequantizeLowerZ(time); + const vfloat16 upper_z = node->dequantizeUpperZ(time); + + const vfloat16 tNearX = msub(lower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat16 tNearY = msub(lower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat16 tNearZ = msub(lower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat16 tFarX = msub(upper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat16 tFarY = msub(upper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat16 tFarZ = msub(upper_z, ray.rdir.z, ray.org_rdir.z); + + const vfloat16 tminX = min(tNearX,tFarX); + const vfloat16 tmaxX = max(tNearX,tFarX); + const vfloat16 tminY = min(tNearY,tFarY); + const vfloat16 tmaxY = max(tNearY,tFarY); + const vfloat16 tminZ = min(tNearZ,tFarZ); + const vfloat16 tmaxZ = max(tNearZ,tFarZ); + const vfloat16 tNear = max(tminX,tminY,tminZ,ray.tnear); + const vfloat16 tFar = min(tmaxX,tmaxY,tmaxZ,ray.tfar ); + const vbool16 vmask = tNear <= tFar; + const size_t mask = movemask(vmask) & mvalid; + dist = extractN<4,0>(tNear); + return mask; + } + + + // for KNL + template<> + __forceinline size_t intersectNode<4,16>(const typename BVHN<4>::QuantizedBaseNodeMB* node, const TravRay<4,16,true>& ray, const float time, vfloat<4>& dist) + { + const size_t mvalid = movemask(node->validMask()); + const vfloat16 lower_x = node->dequantizeLowerX(time); + const vfloat16 upper_x = node->dequantizeUpperX(time); + const vfloat16 lower_y = node->dequantizeLowerY(time); + const vfloat16 upper_y = node->dequantizeUpperY(time); + const vfloat16 lower_z = node->dequantizeLowerZ(time); + const vfloat16 upper_z = node->dequantizeUpperZ(time); + + const vfloat16 tNearX = (lower_x - ray.org.x) * ray.rdir_near.x; + const vfloat16 tNearY = (lower_y - ray.org.y) * ray.rdir_near.y; + const vfloat16 tNearZ = (lower_z - ray.org.z) * ray.rdir_near.z; + const vfloat16 tFarX = (upper_x - ray.org.x) * ray.rdir_far.x; + const vfloat16 tFarY = (upper_y - ray.org.y) * ray.rdir_far.y; + const vfloat16 tFarZ = (upper_z - ray.org.z) * ray.rdir_far.z; + + const vfloat16 tminX = min(tNearX,tFarX); + const vfloat16 tmaxX = max(tNearX,tFarX); + const vfloat16 tminY = min(tNearY,tFarY); + const vfloat16 tmaxY = max(tNearY,tFarY); + const vfloat16 tminZ = min(tNearZ,tFarZ); + const vfloat16 tmaxZ = max(tNearZ,tFarZ); + const vfloat16 tNear = max(tminX,tminY,tminZ,ray.tnear); + const vfloat16 tFar = min(tmaxX,tmaxY,tmaxZ,ray.tfar ); + const vbool16 vmask = tNear <= tFar; + const size_t mask = movemask(vmask) & mvalid; + dist = extractN<4,0>(tNear); + return mask; + } + +#endif + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast OBBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode(const typename BVHN::OBBNode* node, const TravRay& ray, vfloat& dist) + { + const Vec3vf dir = xfmVector(node->naabb,ray.dir); + //const Vec3vf nrdir = Vec3vf(vfloat(-1.0f))/dir; + const Vec3vf nrdir = Vec3vf(vfloat(-1.0f))*rcp_safe(dir); + const Vec3vf org = xfmPoint(node->naabb,ray.org); + const Vec3vf tLowerXYZ = org * nrdir; // (Vec3fa(zero) - org) * rdir; + const Vec3vf tUpperXYZ = tLowerXYZ - nrdir; // (Vec3fa(one ) - org) * rdir; + + const vfloat tNearX = mini(tLowerXYZ.x,tUpperXYZ.x); + const vfloat tNearY = mini(tLowerXYZ.y,tUpperXYZ.y); + const vfloat tNearZ = mini(tLowerXYZ.z,tUpperXYZ.z); + const vfloat tFarX = maxi(tLowerXYZ.x,tUpperXYZ.x); + const vfloat tFarY = maxi(tLowerXYZ.y,tUpperXYZ.y); + const vfloat tFarZ = maxi(tLowerXYZ.z,tUpperXYZ.z); + vfloat tNear = max(ray.tnear, tNearX,tNearY,tNearZ); + vfloat tFar = min(ray.tfar, tFarX ,tFarY ,tFarZ ); + if (robust) { + tNear = tNear*vfloat(1.0f-3.0f*float(ulp)); + tFar = tFar *vfloat(1.0f+3.0f*float(ulp)); + } + const vbool vmask = tNear <= tFar; + dist = tNear; + return movemask(vmask); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast OBBNodeMB intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode(const typename BVHN::OBBNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + const AffineSpace3vf xfm = node->space0; + const Vec3vf b0_lower = zero; + const Vec3vf b0_upper = one; + const Vec3vf lower = lerp(b0_lower,node->b1.lower,vfloat(time)); + const Vec3vf upper = lerp(b0_upper,node->b1.upper,vfloat(time)); + + const BBox3vf bounds(lower,upper); + const Vec3vf dir = xfmVector(xfm,ray.dir); + const Vec3vf rdir = rcp_safe(dir); + const Vec3vf org = xfmPoint(xfm,ray.org); + + const Vec3vf tLowerXYZ = (bounds.lower - org) * rdir; + const Vec3vf tUpperXYZ = (bounds.upper - org) * rdir; + + const vfloat tNearX = mini(tLowerXYZ.x,tUpperXYZ.x); + const vfloat tNearY = mini(tLowerXYZ.y,tUpperXYZ.y); + const vfloat tNearZ = mini(tLowerXYZ.z,tUpperXYZ.z); + const vfloat tFarX = maxi(tLowerXYZ.x,tUpperXYZ.x); + const vfloat tFarY = maxi(tLowerXYZ.y,tUpperXYZ.y); + const vfloat tFarZ = maxi(tLowerXYZ.z,tUpperXYZ.z); + vfloat tNear = max(ray.tnear, tNearX,tNearY,tNearZ); + vfloat tFar = min(ray.tfar, tFarX ,tFarY ,tFarZ ); + if (robust) { + tNear = tNear*vfloat(1.0f-3.0f*float(ulp)); + tFar = tFar *vfloat(1.0f+3.0f*float(ulp)); + } + const vbool vmask = tNear <= tFar; + dist = tNear; + return movemask(vmask); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Node intersectors used in point query raversal + ////////////////////////////////////////////////////////////////////////////////////// + + /*! Computes traversal information for N nodes with 1 point query */ + template + struct BVHNNodePointQuerySphere1; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeSphere(node.getAABBNode(), query, dist); + return true; + } + }; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeSphere(node.getAABBNodeMB(), query, time, dist); + return true; + } + }; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeSphereMB4D(node, query, time, dist); + return true; + } + }; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNode())) mask = pointQueryNodeSphere(node.getAABBNode(), query, dist); + else if (unlikely(node.isOBBNode())) mask = pointQueryNodeSphere(node.ungetAABBNode(), query, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNodeMB())) mask = pointQueryNodeSphere(node.getAABBNodeMB(), query, time, dist); + else if (unlikely(node.isOBBNodeMB())) mask = pointQueryNodeSphere(node.ungetAABBNodeMB(), query, time, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + if (unlikely(node.isOBBNodeMB())) mask = pointQueryNodeSphere(node.ungetAABBNodeMB(), query, time, dist); + else mask = pointQueryNodeSphereMB4D(node, query, time, dist); + return true; + } + }; + + template + struct BVHNNodePointQuerySphere1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeSphere((const typename BVHN::QuantizedNode*)node.quantizedNode(), query, dist); + return true; + } + }; + + template + struct BVHNQuantizedBaseNodePointQuerySphere1 + { + static __forceinline size_t pointQuery(const typename BVHN::QuantizedBaseNode* node, const TravPointQuery& query, vfloat& dist) + { + return pointQueryNodeSphere(node,query,dist); + } + + static __forceinline size_t pointQuery(const typename BVHN::QuantizedBaseNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + return pointQueryNodeSphere(node,query,time,dist); + } + }; + + /*! Computes traversal information for N nodes with 1 point query */ + template + struct BVHNNodePointQueryAABB1; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeAABB(node.getAABBNode(), query, dist); + return true; + } + }; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeAABB(node.getAABBNodeMB(), query, time, dist); + return true; + } + }; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeAABBMB4D(node, query, time, dist); + return true; + } + }; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNode())) mask = pointQueryNodeAABB(node.getAABBNode(), query, dist); + else if (unlikely(node.isOBBNode())) mask = pointQueryNodeAABB(node.ungetAABBNode(), query, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNodeMB())) mask = pointQueryNodeAABB(node.getAABBNodeMB(), query, time, dist); + else if (unlikely(node.isOBBNodeMB())) mask = pointQueryNodeAABB(node.ungetAABBNodeMB(), query, time, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + if (unlikely(node.isOBBNodeMB())) mask = pointQueryNodeAABB(node.ungetAABBNodeMB(), query, time, dist); + else mask = pointQueryNodeAABBMB4D(node, query, time, dist); + return true; + } + }; + + template + struct BVHNNodePointQueryAABB1 + { + static __forceinline bool pointQuery(const typename BVHN::NodeRef& node, const TravPointQuery& query, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = pointQueryNodeAABB((const typename BVHN::QuantizedNode*)node.quantizedNode(), query, dist); + return true; + } + }; + + template + struct BVHNQuantizedBaseNodePointQueryAABB1 + { + static __forceinline size_t pointQuery(const typename BVHN::QuantizedBaseNode* node, const TravPointQuery& query, vfloat& dist) + { + return pointQueryNodeAABB(node,query,dist); + } + + static __forceinline size_t pointQuery(const typename BVHN::QuantizedBaseNodeMB* node, const TravPointQuery& query, const float time, vfloat& dist) + { + return pointQueryNodeAABB(node,query,time,dist); + } + }; + + + ////////////////////////////////////////////////////////////////////////////////////// + // Node intersectors used in ray traversal + ////////////////////////////////////////////////////////////////////////////////////// + + /*! Intersects N nodes with 1 ray */ + template + struct BVHNNodeIntersector1; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNode(node.getAABBNode(), ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNodeRobust(node.getAABBNode(), ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNode(node.getAABBNodeMB(), ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNodeRobust(node.getAABBNodeMB(), ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNodeMB4D(node, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNodeMB4DRobust(node, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNode())) mask = intersectNode(node.getAABBNode(), ray, dist); + else if (unlikely(node.isOBBNode())) mask = intersectNode(node.ungetAABBNode(), ray, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNode())) mask = intersectNodeRobust(node.getAABBNode(), ray, dist); + else if (unlikely(node.isOBBNode())) mask = intersectNode(node.ungetAABBNode(), ray, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNodeMB())) mask = intersectNode(node.getAABBNodeMB(), ray, time, dist); + else if (unlikely(node.isOBBNodeMB())) mask = intersectNode(node.ungetAABBNodeMB(), ray, time, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (likely(node.isAABBNodeMB())) mask = intersectNodeRobust(node.getAABBNodeMB(), ray, time, dist); + else if (unlikely(node.isOBBNodeMB())) mask = intersectNode(node.ungetAABBNodeMB(), ray, time, dist); + else return false; + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + if (unlikely(node.isOBBNodeMB())) mask = intersectNode(node.ungetAABBNodeMB(), ray, time, dist); + else mask = intersectNodeMB4D(node, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + if (unlikely(node.isOBBNodeMB())) mask = intersectNode(node.ungetAABBNodeMB(), ray, time, dist); + else mask = intersectNodeMB4DRobust(node, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNode((const typename BVHN::QuantizedNode*)node.quantizedNode(), ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersector1 + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, const TravRay& ray, float time, vfloat& dist, size_t& mask) + { + if (unlikely(node.isLeaf())) return false; + mask = intersectNodeRobust((const typename BVHN::QuantizedNode*)node.quantizedNode(), ray, dist); + return true; + } + }; + + /*! Intersects N nodes with K rays */ + template + struct BVHNQuantizedBaseNodeIntersector1; + + template + struct BVHNQuantizedBaseNodeIntersector1 + { + static __forceinline size_t intersect(const typename BVHN::QuantizedBaseNode* node, const TravRay& ray, vfloat& dist) + { + return intersectNode(node,ray,dist); + } + + static __forceinline size_t intersect(const typename BVHN::QuantizedBaseNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + return intersectNode(node,ray,time,dist); + } + + }; + + template + struct BVHNQuantizedBaseNodeIntersector1 + { + static __forceinline size_t intersect(const typename BVHN::QuantizedBaseNode* node, const TravRay& ray, vfloat& dist) + { + return intersectNode(node,ray,dist); + } + + static __forceinline size_t intersect(const typename BVHN::QuantizedBaseNodeMB* node, const TravRay& ray, const float time, vfloat& dist) + { + return intersectNode(node,ray,time,dist); + } + + }; + + + } +} diff --git a/thirdparty/embree/kernels/bvh/node_intersector_frustum.h b/thirdparty/embree/kernels/bvh/node_intersector_frustum.h new file mode 100644 index 000000000000..dbce46932495 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/node_intersector_frustum.h @@ -0,0 +1,253 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "node_intersector.h" + +namespace embree +{ + namespace isa + { + ////////////////////////////////////////////////////////////////////////////////////// + // Frustum structure used in hybrid and stream traversal + ////////////////////////////////////////////////////////////////////////////////////// + + /* + Optimized frustum test. We calculate t=(p-org)/dir in ray/box + intersection. We assume the rays are split by octant, thus + dir intervals are either positive or negative in each + dimension. + + Case 1: dir.min >= 0 && dir.max >= 0: + t_min = (p_min - org_max) / dir_max = (p_min - org_max)*rdir_min = p_min*rdir_min - org_max*rdir_min + t_max = (p_max - org_min) / dir_min = (p_max - org_min)*rdir_max = p_max*rdir_max - org_min*rdir_max + + Case 2: dir.min < 0 && dir.max < 0: + t_min = (p_max - org_min) / dir_min = (p_max - org_min)*rdir_max = p_max*rdir_max - org_min*rdir_max + t_max = (p_min - org_max) / dir_max = (p_min - org_max)*rdir_min = p_min*rdir_min - org_max*rdir_min + */ + + template + struct Frustum; + + /* Fast variant */ + template<> + struct Frustum + { + __forceinline Frustum() {} + + template + __forceinline Frustum(const vbool& valid, const Vec3vf& org, const Vec3vf& rdir, const vfloat& ray_tnear, const vfloat& ray_tfar, int N) + { + init(valid, org, rdir, ray_tnear, ray_tfar, N); + } + + template + __forceinline void init(const vbool& valid, const Vec3vf& org, const Vec3vf& rdir, const vfloat& ray_tnear, const vfloat& ray_tfar, int N) + { + const Vec3fa reduced_min_org(reduce_min(select(valid, org.x, pos_inf)), + reduce_min(select(valid, org.y, pos_inf)), + reduce_min(select(valid, org.z, pos_inf))); + + const Vec3fa reduced_max_org(reduce_max(select(valid, org.x, neg_inf)), + reduce_max(select(valid, org.y, neg_inf)), + reduce_max(select(valid, org.z, neg_inf))); + + const Vec3fa reduced_min_rdir(reduce_min(select(valid, rdir.x, pos_inf)), + reduce_min(select(valid, rdir.y, pos_inf)), + reduce_min(select(valid, rdir.z, pos_inf))); + + const Vec3fa reduced_max_rdir(reduce_max(select(valid, rdir.x, neg_inf)), + reduce_max(select(valid, rdir.y, neg_inf)), + reduce_max(select(valid, rdir.z, neg_inf))); + + const float reduced_min_dist = reduce_min(select(valid, ray_tnear, vfloat(pos_inf))); + const float reduced_max_dist = reduce_max(select(valid, ray_tfar , vfloat(neg_inf))); + + init(reduced_min_org, reduced_max_org, reduced_min_rdir, reduced_max_rdir, reduced_min_dist, reduced_max_dist, N); + } + + __forceinline void init(const Vec3fa& reduced_min_org, + const Vec3fa& reduced_max_org, + const Vec3fa& reduced_min_rdir, + const Vec3fa& reduced_max_rdir, + float reduced_min_dist, + float reduced_max_dist, + int N) + { + const Vec3ba pos_rdir = ge_mask(reduced_min_rdir, Vec3fa(zero)); + + min_rdir = select(pos_rdir, reduced_min_rdir, reduced_max_rdir); + max_rdir = select(pos_rdir, reduced_max_rdir, reduced_min_rdir); + + min_org_rdir = min_rdir * select(pos_rdir, reduced_max_org, reduced_min_org); + max_org_rdir = max_rdir * select(pos_rdir, reduced_min_org, reduced_max_org); + + min_dist = reduced_min_dist; + max_dist = reduced_max_dist; + + nf = NearFarPrecalculations(min_rdir, N); + } + + template + __forceinline void updateMaxDist(const vfloat& ray_tfar) + { + max_dist = reduce_max(ray_tfar); + } + + NearFarPrecalculations nf; + + Vec3fa min_rdir; + Vec3fa max_rdir; + + Vec3fa min_org_rdir; + Vec3fa max_org_rdir; + + float min_dist; + float max_dist; + }; + + typedef Frustum FrustumFast; + + /* Robust variant */ + template<> + struct Frustum + { + __forceinline Frustum() {} + + template + __forceinline Frustum(const vbool& valid, const Vec3vf& org, const Vec3vf& rdir, const vfloat& ray_tnear, const vfloat& ray_tfar, int N) + { + init(valid, org, rdir, ray_tnear, ray_tfar, N); + } + + template + __forceinline void init(const vbool& valid, const Vec3vf& org, const Vec3vf& rdir, const vfloat& ray_tnear, const vfloat& ray_tfar, int N) + { + const Vec3fa reduced_min_org(reduce_min(select(valid, org.x, pos_inf)), + reduce_min(select(valid, org.y, pos_inf)), + reduce_min(select(valid, org.z, pos_inf))); + + const Vec3fa reduced_max_org(reduce_max(select(valid, org.x, neg_inf)), + reduce_max(select(valid, org.y, neg_inf)), + reduce_max(select(valid, org.z, neg_inf))); + + const Vec3fa reduced_min_rdir(reduce_min(select(valid, rdir.x, pos_inf)), + reduce_min(select(valid, rdir.y, pos_inf)), + reduce_min(select(valid, rdir.z, pos_inf))); + + const Vec3fa reduced_max_rdir(reduce_max(select(valid, rdir.x, neg_inf)), + reduce_max(select(valid, rdir.y, neg_inf)), + reduce_max(select(valid, rdir.z, neg_inf))); + + const float reduced_min_dist = reduce_min(select(valid, ray_tnear, vfloat(pos_inf))); + const float reduced_max_dist = reduce_max(select(valid, ray_tfar , vfloat(neg_inf))); + + init(reduced_min_org, reduced_max_org, reduced_min_rdir, reduced_max_rdir, reduced_min_dist, reduced_max_dist, N); + } + + __forceinline void init(const Vec3fa& reduced_min_org, + const Vec3fa& reduced_max_org, + const Vec3fa& reduced_min_rdir, + const Vec3fa& reduced_max_rdir, + float reduced_min_dist, + float reduced_max_dist, + int N) + { + const Vec3ba pos_rdir = ge_mask(reduced_min_rdir, Vec3fa(zero)); + min_rdir = select(pos_rdir, reduced_min_rdir, reduced_max_rdir); + max_rdir = select(pos_rdir, reduced_max_rdir, reduced_min_rdir); + + min_org = select(pos_rdir, reduced_max_org, reduced_min_org); + max_org = select(pos_rdir, reduced_min_org, reduced_max_org); + + min_dist = reduced_min_dist; + max_dist = reduced_max_dist; + + nf = NearFarPrecalculations(min_rdir, N); + } + + template + __forceinline void updateMaxDist(const vfloat& ray_tfar) + { + max_dist = reduce_max(ray_tfar); + } + + NearFarPrecalculations nf; + + Vec3fa min_rdir; + Vec3fa max_rdir; + + Vec3fa min_org; + Vec3fa max_org; + + float min_dist; + float max_dist; + }; + + typedef Frustum FrustumRobust; + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNodeFrustum(const typename BVHN::AABBNode* __restrict__ node, + const FrustumFast& frustum, vfloat& dist) + { + const vfloat bminX = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.nearX); + const vfloat bminY = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.nearY); + const vfloat bminZ = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.nearZ); + const vfloat bmaxX = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.farX); + const vfloat bmaxY = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.farY); + const vfloat bmaxZ = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.farZ); + + const vfloat fminX = msub(bminX, vfloat(frustum.min_rdir.x), vfloat(frustum.min_org_rdir.x)); + const vfloat fminY = msub(bminY, vfloat(frustum.min_rdir.y), vfloat(frustum.min_org_rdir.y)); + const vfloat fminZ = msub(bminZ, vfloat(frustum.min_rdir.z), vfloat(frustum.min_org_rdir.z)); + const vfloat fmaxX = msub(bmaxX, vfloat(frustum.max_rdir.x), vfloat(frustum.max_org_rdir.x)); + const vfloat fmaxY = msub(bmaxY, vfloat(frustum.max_rdir.y), vfloat(frustum.max_org_rdir.y)); + const vfloat fmaxZ = msub(bmaxZ, vfloat(frustum.max_rdir.z), vfloat(frustum.max_org_rdir.z)); + + const vfloat fmin = maxi(fminX, fminY, fminZ, vfloat(frustum.min_dist)); + dist = fmin; + const vfloat fmax = mini(fmaxX, fmaxY, fmaxZ, vfloat(frustum.max_dist)); + const vbool vmask_node_hit = fmin <= fmax; + size_t m_node = movemask(vmask_node_hit) & (((size_t)1 << N)-1); + return m_node; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNodeFrustum(const typename BVHN::AABBNode* __restrict__ node, + const FrustumRobust& frustum, vfloat& dist) + { + const vfloat bminX = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.nearX); + const vfloat bminY = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.nearY); + const vfloat bminZ = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.nearZ); + const vfloat bmaxX = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.farX); + const vfloat bmaxY = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.farY); + const vfloat bmaxZ = *(const vfloat*)((const char*)&node->lower_x + frustum.nf.farZ); + + const vfloat fminX = (bminX - vfloat(frustum.min_org.x)) * vfloat(frustum.min_rdir.x); + const vfloat fminY = (bminY - vfloat(frustum.min_org.y)) * vfloat(frustum.min_rdir.y); + const vfloat fminZ = (bminZ - vfloat(frustum.min_org.z)) * vfloat(frustum.min_rdir.z); + const vfloat fmaxX = (bmaxX - vfloat(frustum.max_org.x)) * vfloat(frustum.max_rdir.x); + const vfloat fmaxY = (bmaxY - vfloat(frustum.max_org.y)) * vfloat(frustum.max_rdir.y); + const vfloat fmaxZ = (bmaxZ - vfloat(frustum.max_org.z)) * vfloat(frustum.max_rdir.z); + + const float round_down = 1.0f-2.0f*float(ulp); // FIXME: use per instruction rounding for AVX512 + const float round_up = 1.0f+2.0f*float(ulp); + const vfloat fmin = max(fminX, fminY, fminZ, vfloat(frustum.min_dist)); + dist = fmin; + const vfloat fmax = min(fmaxX, fmaxY, fmaxZ, vfloat(frustum.max_dist)); + const vbool vmask_node_hit = (round_down*fmin <= round_up*fmax); + size_t m_node = movemask(vmask_node_hit) & (((size_t)1 << N)-1); + return m_node; + } + } +} diff --git a/thirdparty/embree/kernels/bvh/node_intersector_packet.h b/thirdparty/embree/kernels/bvh/node_intersector_packet.h new file mode 100644 index 000000000000..1cc0d47fabd1 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/node_intersector_packet.h @@ -0,0 +1,805 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "node_intersector.h" + +namespace embree +{ + namespace isa + { + ////////////////////////////////////////////////////////////////////////////////////// + // Ray packet structure used in hybrid traversal + ////////////////////////////////////////////////////////////////////////////////////// + + template + struct TravRayK; + + /* Fast variant */ + template + struct TravRayK + { + __forceinline TravRayK() {} + + __forceinline TravRayK(const Vec3vf& ray_org, const Vec3vf& ray_dir, int N) + { + init(ray_org, ray_dir, N); + } + + __forceinline TravRayK(const Vec3vf& ray_org, const Vec3vf& ray_dir, const vfloat& ray_tnear, const vfloat& ray_tfar, int N) + { + init(ray_org, ray_dir, N); + tnear = ray_tnear; + tfar = ray_tfar; + } + + __forceinline void init(const Vec3vf& ray_org, const Vec3vf& ray_dir, int N) + { + org = ray_org; + dir = ray_dir; + rdir = rcp_safe(ray_dir); +#if defined(__AVX2__) + org_rdir = org * rdir; +#endif + + if (N) + { + const int size = sizeof(float)*N; + nearXYZ.x = select(rdir.x >= 0.0f, vint(0*size), vint(1*size)); + nearXYZ.y = select(rdir.y >= 0.0f, vint(2*size), vint(3*size)); + nearXYZ.z = select(rdir.z >= 0.0f, vint(4*size), vint(5*size)); + } + } + + Vec3vf org; + Vec3vf dir; + Vec3vf rdir; +#if defined(__AVX2__) + Vec3vf org_rdir; +#endif + Vec3vi nearXYZ; + vfloat tnear; + vfloat tfar; + }; + + template + using TravRayKFast = TravRayK; + + /* Robust variant */ + template + struct TravRayK + { + __forceinline TravRayK() {} + + __forceinline TravRayK(const Vec3vf& ray_org, const Vec3vf& ray_dir, int N) + { + init(ray_org, ray_dir, N); + } + + __forceinline TravRayK(const Vec3vf& ray_org, const Vec3vf& ray_dir, const vfloat& ray_tnear, const vfloat& ray_tfar, int N) + { + init(ray_org, ray_dir, N); + tnear = ray_tnear; + tfar = ray_tfar; + } + + __forceinline void init(const Vec3vf& ray_org, const Vec3vf& ray_dir, int N) + { + org = ray_org; + dir = ray_dir; + rdir = vfloat(1.0f)/(zero_fix(ray_dir)); + + if (N) + { + const int size = sizeof(float)*N; + nearXYZ.x = select(rdir.x >= 0.0f, vint(0*size), vint(1*size)); + nearXYZ.y = select(rdir.y >= 0.0f, vint(2*size), vint(3*size)); + nearXYZ.z = select(rdir.z >= 0.0f, vint(4*size), vint(5*size)); + } + } + + Vec3vf org; + Vec3vf dir; + Vec3vf rdir; + Vec3vi nearXYZ; + vfloat tnear; + vfloat tfar; + }; + + template + using TravRayKRobust = TravRayK; + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeK(const typename BVHN::AABBNode* node, size_t i, + const TravRayKFast& ray, vfloat& dist) + + { + #if defined(__AVX2__) + const vfloat lclipMinX = msub(node->lower_x[i], ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMinY = msub(node->lower_y[i], ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMinZ = msub(node->lower_z[i], ray.rdir.z, ray.org_rdir.z); + const vfloat lclipMaxX = msub(node->upper_x[i], ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMaxY = msub(node->upper_y[i], ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMaxZ = msub(node->upper_z[i], ray.rdir.z, ray.org_rdir.z); + #else + const vfloat lclipMinX = (node->lower_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (node->lower_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (node->lower_z[i] - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (node->upper_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (node->upper_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (node->upper_z[i] - ray.org.z) * ray.rdir.z; + #endif + + #if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + if (K == 16) + { + /* use mixed float/int min/max */ + const vfloat lnearP = maxi(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = asInt(maxi(lnearP, ray.tnear)) <= asInt(mini(lfarP, ray.tfar)); + dist = lnearP; + return lhit; + } + else + #endif + { + const vfloat lnearP = maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY), mini(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY), maxi(lclipMinZ, lclipMaxZ)); + #if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vbool lhit = asInt(maxi(lnearP, ray.tnear)) <= asInt(mini(lfarP, ray.tfar)); + #else + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + #endif + dist = lnearP; + return lhit; + } + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeKRobust(const typename BVHN::AABBNode* node, size_t i, + const TravRayKRobust& ray, vfloat& dist) + { + // FIXME: use per instruction rounding for AVX512 + const vfloat lclipMinX = (node->lower_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (node->lower_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (node->lower_z[i] - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (node->upper_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (node->upper_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (node->upper_z[i] - ray.org.z) * ray.rdir.z; + const float round_up = 1.0f+3.0f*float(ulp); + const float round_down = 1.0f-3.0f*float(ulp); + const vfloat lnearP = round_down*max(max(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY)), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = round_up *min(min(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY)), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = max(lnearP, ray.tnear) <= min(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNodeMB intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeK(const typename BVHN::AABBNodeMB* node, const size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist) + { + const vfloat vlower_x = madd(time, vfloat(node->lower_dx[i]), vfloat(node->lower_x[i])); + const vfloat vlower_y = madd(time, vfloat(node->lower_dy[i]), vfloat(node->lower_y[i])); + const vfloat vlower_z = madd(time, vfloat(node->lower_dz[i]), vfloat(node->lower_z[i])); + const vfloat vupper_x = madd(time, vfloat(node->upper_dx[i]), vfloat(node->upper_x[i])); + const vfloat vupper_y = madd(time, vfloat(node->upper_dy[i]), vfloat(node->upper_y[i])); + const vfloat vupper_z = madd(time, vfloat(node->upper_dz[i]), vfloat(node->upper_z[i])); + +#if defined(__AVX2__) + const vfloat lclipMinX = msub(vlower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMinY = msub(vlower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMinZ = msub(vlower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat lclipMaxX = msub(vupper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMaxY = msub(vupper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMaxZ = msub(vupper_z, ray.rdir.z, ray.org_rdir.z); +#else + const vfloat lclipMinX = (vlower_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (vlower_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (vlower_z - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (vupper_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (vupper_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (vupper_z - ray.org.z) * ray.rdir.z; +#endif + +#if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + if (K == 16) + { + /* use mixed float/int min/max */ + const vfloat lnearP = maxi(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = asInt(maxi(lnearP, ray.tnear)) <= asInt(mini(lfarP, ray.tfar)); + dist = lnearP; + return lhit; + } + else +#endif + { + const vfloat lnearP = maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY), mini(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY), maxi(lclipMinZ, lclipMaxZ)); +#if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vbool lhit = asInt(maxi(lnearP, ray.tnear)) <= asInt(mini(lfarP, ray.tfar)); +#else + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); +#endif + dist = lnearP; + return lhit; + } + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNodeMB intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeKRobust(const typename BVHN::AABBNodeMB* node, const size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist) + { + const vfloat vlower_x = madd(time, vfloat(node->lower_dx[i]), vfloat(node->lower_x[i])); + const vfloat vlower_y = madd(time, vfloat(node->lower_dy[i]), vfloat(node->lower_y[i])); + const vfloat vlower_z = madd(time, vfloat(node->lower_dz[i]), vfloat(node->lower_z[i])); + const vfloat vupper_x = madd(time, vfloat(node->upper_dx[i]), vfloat(node->upper_x[i])); + const vfloat vupper_y = madd(time, vfloat(node->upper_dy[i]), vfloat(node->upper_y[i])); + const vfloat vupper_z = madd(time, vfloat(node->upper_dz[i]), vfloat(node->upper_z[i])); + + const vfloat lclipMinX = (vlower_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (vlower_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (vlower_z - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (vupper_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (vupper_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (vupper_z - ray.org.z) * ray.rdir.z; + + const float round_up = 1.0f+3.0f*float(ulp); + const float round_down = 1.0f-3.0f*float(ulp); + +#if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + if (K == 16) + { + const vfloat lnearP = round_down*maxi(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = round_up *mini(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + else +#endif + { + const vfloat lnearP = round_down*maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY), mini(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = round_up *mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY), maxi(lclipMinZ, lclipMaxZ)); + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNodeMB4D intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeKMB4D(const typename BVHN::NodeRef ref, const size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist) + { + const typename BVHN::AABBNodeMB* node = ref.getAABBNodeMB(); + + const vfloat vlower_x = madd(time, vfloat(node->lower_dx[i]), vfloat(node->lower_x[i])); + const vfloat vlower_y = madd(time, vfloat(node->lower_dy[i]), vfloat(node->lower_y[i])); + const vfloat vlower_z = madd(time, vfloat(node->lower_dz[i]), vfloat(node->lower_z[i])); + const vfloat vupper_x = madd(time, vfloat(node->upper_dx[i]), vfloat(node->upper_x[i])); + const vfloat vupper_y = madd(time, vfloat(node->upper_dy[i]), vfloat(node->upper_y[i])); + const vfloat vupper_z = madd(time, vfloat(node->upper_dz[i]), vfloat(node->upper_z[i])); + +#if defined(__AVX2__) + const vfloat lclipMinX = msub(vlower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMinY = msub(vlower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMinZ = msub(vlower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat lclipMaxX = msub(vupper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMaxY = msub(vupper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMaxZ = msub(vupper_z, ray.rdir.z, ray.org_rdir.z); +#else + const vfloat lclipMinX = (vlower_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (vlower_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (vlower_z - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (vupper_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (vupper_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (vupper_z - ray.org.z) * ray.rdir.z; +#endif + + const vfloat lnearP = maxi(maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY)), mini(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY)), maxi(lclipMinZ, lclipMaxZ)); + vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + if (unlikely(ref.isAABBNodeMB4D())) { + const typename BVHN::AABBNodeMB4D* node1 = (const typename BVHN::AABBNodeMB4D*) node; + lhit = lhit & (vfloat(node1->lower_t[i]) <= time) & (time < vfloat(node1->upper_t[i])); + } + dist = lnearP; + return lhit; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNodeMB4D intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeKMB4DRobust(const typename BVHN::NodeRef ref, const size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist) + { + const typename BVHN::AABBNodeMB* node = ref.getAABBNodeMB(); + + const vfloat vlower_x = madd(time, vfloat(node->lower_dx[i]), vfloat(node->lower_x[i])); + const vfloat vlower_y = madd(time, vfloat(node->lower_dy[i]), vfloat(node->lower_y[i])); + const vfloat vlower_z = madd(time, vfloat(node->lower_dz[i]), vfloat(node->lower_z[i])); + const vfloat vupper_x = madd(time, vfloat(node->upper_dx[i]), vfloat(node->upper_x[i])); + const vfloat vupper_y = madd(time, vfloat(node->upper_dy[i]), vfloat(node->upper_y[i])); + const vfloat vupper_z = madd(time, vfloat(node->upper_dz[i]), vfloat(node->upper_z[i])); + + const vfloat lclipMinX = (vlower_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (vlower_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (vlower_z - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (vupper_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (vupper_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (vupper_z - ray.org.z) * ray.rdir.z; + + const float round_up = 1.0f+3.0f*float(ulp); + const float round_down = 1.0f-3.0f*float(ulp); + const vfloat lnearP = round_down*maxi(maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY)), mini(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = round_up *mini(mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY)), maxi(lclipMinZ, lclipMaxZ)); + vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + + if (unlikely(ref.isAABBNodeMB4D())) { + const typename BVHN::AABBNodeMB4D* node1 = (const typename BVHN::AABBNodeMB4D*) node; + lhit = lhit & (vfloat(node1->lower_t[i]) <= time) & (time < vfloat(node1->upper_t[i])); + } + dist = lnearP; + return lhit; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast OBBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeK(const typename BVHN::OBBNode* node, const size_t i, + const TravRayK& ray, vfloat& dist) + { + const AffineSpace3vf naabb(Vec3f(node->naabb.l.vx.x[i], node->naabb.l.vx.y[i], node->naabb.l.vx.z[i]), + Vec3f(node->naabb.l.vy.x[i], node->naabb.l.vy.y[i], node->naabb.l.vy.z[i]), + Vec3f(node->naabb.l.vz.x[i], node->naabb.l.vz.y[i], node->naabb.l.vz.z[i]), + Vec3f(node->naabb.p .x[i], node->naabb.p .y[i], node->naabb.p .z[i])); + + const Vec3vf dir = xfmVector(naabb, ray.dir); + const Vec3vf nrdir = Vec3vf(vfloat(-1.0f)) * rcp_safe(dir); // FIXME: negate instead of mul with -1? + const Vec3vf org = xfmPoint(naabb, ray.org); + + const vfloat lclipMinX = org.x * nrdir.x; // (Vec3fa(zero) - org) * rdir; + const vfloat lclipMinY = org.y * nrdir.y; + const vfloat lclipMinZ = org.z * nrdir.z; + const vfloat lclipMaxX = lclipMinX - nrdir.x; // (Vec3fa(one) - org) * rdir; + const vfloat lclipMaxY = lclipMinY - nrdir.y; + const vfloat lclipMaxZ = lclipMinZ - nrdir.z; + + vfloat lnearP = maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY), mini(lclipMinZ, lclipMaxZ)); + vfloat lfarP = mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY), maxi(lclipMinZ, lclipMaxZ)); + if (robust) { + lnearP = lnearP*vfloat(1.0f-3.0f*float(ulp)); + lfarP = lfarP *vfloat(1.0f+3.0f*float(ulp)); + } + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast OBBNodeMB intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectNodeK(const typename BVHN::OBBNodeMB* node, const size_t i, + const TravRayK& ray, const vfloat& time, vfloat& dist) + { + const AffineSpace3vf xfm(Vec3f(node->space0.l.vx.x[i], node->space0.l.vx.y[i], node->space0.l.vx.z[i]), + Vec3f(node->space0.l.vy.x[i], node->space0.l.vy.y[i], node->space0.l.vy.z[i]), + Vec3f(node->space0.l.vz.x[i], node->space0.l.vz.y[i], node->space0.l.vz.z[i]), + Vec3f(node->space0.p .x[i], node->space0.p .y[i], node->space0.p .z[i])); + + const Vec3vf b0_lower = zero; + const Vec3vf b0_upper = one; + const Vec3vf b1_lower(node->b1.lower.x[i], node->b1.lower.y[i], node->b1.lower.z[i]); + const Vec3vf b1_upper(node->b1.upper.x[i], node->b1.upper.y[i], node->b1.upper.z[i]); + const Vec3vf lower = lerp(b0_lower, b1_lower, time); + const Vec3vf upper = lerp(b0_upper, b1_upper, time); + + const Vec3vf dir = xfmVector(xfm, ray.dir); + const Vec3vf rdir = rcp_safe(dir); + const Vec3vf org = xfmPoint(xfm, ray.org); + + const vfloat lclipMinX = (lower.x - org.x) * rdir.x; + const vfloat lclipMinY = (lower.y - org.y) * rdir.y; + const vfloat lclipMinZ = (lower.z - org.z) * rdir.z; + const vfloat lclipMaxX = (upper.x - org.x) * rdir.x; + const vfloat lclipMaxY = (upper.y - org.y) * rdir.y; + const vfloat lclipMaxZ = (upper.z - org.z) * rdir.z; + + vfloat lnearP = maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY), mini(lclipMinZ, lclipMaxZ)); + vfloat lfarP = mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY), maxi(lclipMinZ, lclipMaxZ)); + if (robust) { + lnearP = lnearP*vfloat(1.0f-3.0f*float(ulp)); + lfarP = lfarP *vfloat(1.0f+3.0f*float(ulp)); + } + + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + + + + ////////////////////////////////////////////////////////////////////////////////////// + // QuantizedBaseNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline vbool intersectQuantizedNodeK(const typename BVHN::QuantizedBaseNode* node, size_t i, + const TravRayK& ray, vfloat& dist) + + { + assert(movemask(node->validMask()) & ((size_t)1 << i)); + const vfloat lower_x = node->dequantizeLowerX(); + const vfloat upper_x = node->dequantizeUpperX(); + const vfloat lower_y = node->dequantizeLowerY(); + const vfloat upper_y = node->dequantizeUpperY(); + const vfloat lower_z = node->dequantizeLowerZ(); + const vfloat upper_z = node->dequantizeUpperZ(); + + #if defined(__AVX2__) + const vfloat lclipMinX = msub(lower_x[i], ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMinY = msub(lower_y[i], ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMinZ = msub(lower_z[i], ray.rdir.z, ray.org_rdir.z); + const vfloat lclipMaxX = msub(upper_x[i], ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMaxY = msub(upper_y[i], ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMaxZ = msub(upper_z[i], ray.rdir.z, ray.org_rdir.z); + #else + const vfloat lclipMinX = (lower_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (lower_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (lower_z[i] - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (upper_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (upper_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (upper_z[i] - ray.org.z) * ray.rdir.z; + #endif + + #if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + if (K == 16) + { + /* use mixed float/int min/max */ + const vfloat lnearP = maxi(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = asInt(maxi(lnearP, ray.tnear)) <= asInt(mini(lfarP, ray.tfar)); + dist = lnearP; + return lhit; + } + else + #endif + { + const vfloat lnearP = maxi(mini(lclipMinX, lclipMaxX), mini(lclipMinY, lclipMaxY), mini(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = mini(maxi(lclipMinX, lclipMaxX), maxi(lclipMinY, lclipMaxY), maxi(lclipMinZ, lclipMaxZ)); + #if defined(__AVX512F__) && !defined(__AVX512ER__) // SKX + const vbool lhit = asInt(maxi(lnearP, ray.tnear)) <= asInt(mini(lfarP, ray.tfar)); + #else + const vbool lhit = maxi(lnearP, ray.tnear) <= mini(lfarP, ray.tfar); + #endif + dist = lnearP; + return lhit; + } + } + + template + __forceinline vbool intersectQuantizedNodeK(const typename BVHN::QuantizedBaseNode* node, size_t i, + const TravRayK& ray, vfloat& dist) + + { + assert(movemask(node->validMask()) & ((size_t)1 << i)); + const vfloat lower_x = node->dequantizeLowerX(); + const vfloat upper_x = node->dequantizeUpperX(); + const vfloat lower_y = node->dequantizeLowerY(); + const vfloat upper_y = node->dequantizeUpperY(); + const vfloat lower_z = node->dequantizeLowerZ(); + const vfloat upper_z = node->dequantizeUpperZ(); + + const vfloat lclipMinX = (lower_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (lower_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (lower_z[i] - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (upper_x[i] - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (upper_y[i] - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (upper_z[i] - ray.org.z) * ray.rdir.z; + + const float round_up = 1.0f+3.0f*float(ulp); + const float round_down = 1.0f-3.0f*float(ulp); + + const vfloat lnearP = round_down*max(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = round_up *min(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = max(lnearP, ray.tnear) <= min(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + + template + __forceinline vbool intersectQuantizedNodeMBK(const typename BVHN::QuantizedBaseNodeMB* node, const size_t i, + const TravRayK& ray, const vfloat& time, vfloat& dist) + + { + assert(movemask(node->validMask()) & ((size_t)1 << i)); + + const vfloat lower_x = node->dequantizeLowerX(i,time); + const vfloat upper_x = node->dequantizeUpperX(i,time); + const vfloat lower_y = node->dequantizeLowerY(i,time); + const vfloat upper_y = node->dequantizeUpperY(i,time); + const vfloat lower_z = node->dequantizeLowerZ(i,time); + const vfloat upper_z = node->dequantizeUpperZ(i,time); + +#if defined(__AVX2__) + const vfloat lclipMinX = msub(lower_x, ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMinY = msub(lower_y, ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMinZ = msub(lower_z, ray.rdir.z, ray.org_rdir.z); + const vfloat lclipMaxX = msub(upper_x, ray.rdir.x, ray.org_rdir.x); + const vfloat lclipMaxY = msub(upper_y, ray.rdir.y, ray.org_rdir.y); + const vfloat lclipMaxZ = msub(upper_z, ray.rdir.z, ray.org_rdir.z); +#else + const vfloat lclipMinX = (lower_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (lower_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (lower_z - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (upper_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (upper_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (upper_z - ray.org.z) * ray.rdir.z; + #endif + const vfloat lnearP = max(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = min(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = max(lnearP, ray.tnear) <= min(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + + + template + __forceinline vbool intersectQuantizedNodeMBK(const typename BVHN::QuantizedBaseNodeMB* node, const size_t i, + const TravRayK& ray, const vfloat& time, vfloat& dist) + + { + assert(movemask(node->validMask()) & ((size_t)1 << i)); + + const vfloat lower_x = node->dequantizeLowerX(i,time); + const vfloat upper_x = node->dequantizeUpperX(i,time); + const vfloat lower_y = node->dequantizeLowerY(i,time); + const vfloat upper_y = node->dequantizeUpperY(i,time); + const vfloat lower_z = node->dequantizeLowerZ(i,time); + const vfloat upper_z = node->dequantizeUpperZ(i,time); + + const vfloat lclipMinX = (lower_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMinY = (lower_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMinZ = (lower_z - ray.org.z) * ray.rdir.z; + const vfloat lclipMaxX = (upper_x - ray.org.x) * ray.rdir.x; + const vfloat lclipMaxY = (upper_y - ray.org.y) * ray.rdir.y; + const vfloat lclipMaxZ = (upper_z - ray.org.z) * ray.rdir.z; + + const float round_up = 1.0f+3.0f*float(ulp); + const float round_down = 1.0f-3.0f*float(ulp); + + const vfloat lnearP = round_down*max(min(lclipMinX, lclipMaxX), min(lclipMinY, lclipMaxY), min(lclipMinZ, lclipMaxZ)); + const vfloat lfarP = round_up *min(max(lclipMinX, lclipMaxX), max(lclipMinY, lclipMaxY), max(lclipMinZ, lclipMaxZ)); + const vbool lhit = max(lnearP, ray.tnear) <= min(lfarP, ray.tfar); + dist = lnearP; + return lhit; + } + + + ////////////////////////////////////////////////////////////////////////////////////// + // Node intersectors used in hybrid traversal + ////////////////////////////////////////////////////////////////////////////////////// + + /*! Intersects N nodes with K rays */ + template + struct BVHNNodeIntersectorK; + + template + struct BVHNNodeIntersectorK + { + /* vmask is both an input and an output parameter! Its initial value should be the parent node + hit mask, which is used for correctly computing the current hit mask. The parent hit mask + is actually required only for motion blur node intersections (because different rays may + have different times), so for regular nodes vmask is simply overwritten. */ + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + vmask = intersectNodeK(node.getAABBNode(), i, ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + vmask = intersectNodeKRobust(node.getAABBNode(), i, ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + vmask = intersectNodeK(node.getAABBNodeMB(), i, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + vmask = intersectNodeKRobust(node.getAABBNodeMB(), i, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + if (likely(node.isAABBNode())) vmask = intersectNodeK(node.getAABBNode(), i, ray, dist); + else /*if (unlikely(node.isOBBNode()))*/ vmask = intersectNodeK(node.ungetAABBNode(), i, ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + if (likely(node.isAABBNode())) vmask = intersectNodeKRobust(node.getAABBNode(), i, ray, dist); + else /*if (unlikely(node.isOBBNode()))*/ vmask = intersectNodeK(node.ungetAABBNode(), i, ray, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + if (likely(node.isAABBNodeMB())) vmask = intersectNodeK(node.getAABBNodeMB(), i, ray, time, dist); + else /*if (unlikely(node.isOBBNodeMB()))*/ vmask = intersectNodeK(node.ungetAABBNodeMB(), i, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + if (likely(node.isAABBNodeMB())) vmask = intersectNodeKRobust(node.getAABBNodeMB(), i, ray, time, dist); + else /*if (unlikely(node.isOBBNodeMB()))*/ vmask = intersectNodeK(node.ungetAABBNodeMB(), i, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + vmask &= intersectNodeKMB4D(node, i, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + vmask &= intersectNodeKMB4DRobust(node, i, ray, time, dist); + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKFast& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + if (likely(node.isAABBNodeMB() || node.isAABBNodeMB4D())) { + vmask &= intersectNodeKMB4D(node, i, ray, time, dist); + } else /*if (unlikely(node.isOBBNodeMB()))*/ { + assert(node.isOBBNodeMB()); + vmask &= intersectNodeK(node.ungetAABBNodeMB(), i, ray, time, dist); + } + return true; + } + }; + + template + struct BVHNNodeIntersectorK + { + static __forceinline bool intersect(const typename BVHN::NodeRef& node, size_t i, + const TravRayKRobust& ray, const vfloat& time, vfloat& dist, vbool& vmask) + { + if (likely(node.isAABBNodeMB() || node.isAABBNodeMB4D())) { + vmask &= intersectNodeKMB4DRobust(node, i, ray, time, dist); + } else /*if (unlikely(node.isOBBNodeMB()))*/ { + assert(node.isOBBNodeMB()); + vmask &= intersectNodeK(node.ungetAABBNodeMB(), i, ray, time, dist); + } + return true; + } + }; + + + /*! Intersects N nodes with K rays */ + template + struct BVHNQuantizedBaseNodeIntersectorK; + + template + struct BVHNQuantizedBaseNodeIntersectorK + { + static __forceinline vbool intersectK(const typename BVHN::QuantizedBaseNode* node, const size_t i, + const TravRayK& ray, vfloat& dist) + { + return intersectQuantizedNodeK(node,i,ray,dist); + } + + static __forceinline vbool intersectK(const typename BVHN::QuantizedBaseNodeMB* node, const size_t i, + const TravRayK& ray, const vfloat& time, vfloat& dist) + { + return intersectQuantizedNodeMBK(node,i,ray,time,dist); + } + + }; + + template + struct BVHNQuantizedBaseNodeIntersectorK + { + static __forceinline vbool intersectK(const typename BVHN::QuantizedBaseNode* node, const size_t i, + const TravRayK& ray, vfloat& dist) + { + return intersectQuantizedNodeK(node,i,ray,dist); + } + + static __forceinline vbool intersectK(const typename BVHN::QuantizedBaseNodeMB* node, const size_t i, + const TravRayK& ray, const vfloat& time, vfloat& dist) + { + return intersectQuantizedNodeMBK(node,i,ray,time,dist); + } + }; + + + } +} diff --git a/thirdparty/embree/kernels/bvh/node_intersector_packet_stream.h b/thirdparty/embree/kernels/bvh/node_intersector_packet_stream.h new file mode 100644 index 000000000000..c2b5b0cb7a47 --- /dev/null +++ b/thirdparty/embree/kernels/bvh/node_intersector_packet_stream.h @@ -0,0 +1,189 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "node_intersector.h" + +namespace embree +{ + namespace isa + { + ////////////////////////////////////////////////////////////////////////////////////// + // Ray packet structure used in stream traversal + ////////////////////////////////////////////////////////////////////////////////////// + + template + struct TravRayKStream; + + /* Fast variant */ + template + struct TravRayKStream + { + __forceinline TravRayKStream() {} + + __forceinline TravRayKStream(const Vec3vf& ray_org, const Vec3vf& ray_dir, const vfloat& ray_tnear, const vfloat& ray_tfar) + { + init(ray_org, ray_dir); + tnear = ray_tnear; + tfar = ray_tfar; + } + + __forceinline void init(const Vec3vf& ray_org, const Vec3vf& ray_dir) + { + rdir = rcp_safe(ray_dir); + org_rdir = ray_org * rdir; + } + + Vec3vf rdir; + Vec3vf org_rdir; + vfloat tnear; + vfloat tfar; + }; + + template + using TravRayKStreamFast = TravRayKStream; + + /* Robust variant */ + template + struct TravRayKStream + { + __forceinline TravRayKStream() {} + + __forceinline TravRayKStream(const Vec3vf& ray_org, const Vec3vf& ray_dir, const vfloat& ray_tnear, const vfloat& ray_tfar) + { + init(ray_org, ray_dir); + tnear = ray_tnear; + tfar = ray_tfar; + } + + __forceinline void init(const Vec3vf& ray_org, const Vec3vf& ray_dir) + { + rdir = vfloat(1.0f)/(zero_fix(ray_dir)); + org = ray_org; + } + + Vec3vf rdir; + Vec3vf org; + vfloat tnear; + vfloat tfar; + }; + + template + using TravRayKStreamRobust = TravRayKStream; + + ////////////////////////////////////////////////////////////////////////////////////// + // Fast AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode1(const typename BVHN::AABBNode* __restrict__ node, + const TravRayKStreamFast& ray, size_t k, const NearFarPrecalculations& nf) + { + const vfloat bminX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearX)); + const vfloat bminY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearY)); + const vfloat bminZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearZ)); + const vfloat bmaxX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farX)); + const vfloat bmaxY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farY)); + const vfloat bmaxZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farZ)); + + const vfloat rminX = msub(bminX, vfloat(ray.rdir.x[k]), vfloat(ray.org_rdir.x[k])); + const vfloat rminY = msub(bminY, vfloat(ray.rdir.y[k]), vfloat(ray.org_rdir.y[k])); + const vfloat rminZ = msub(bminZ, vfloat(ray.rdir.z[k]), vfloat(ray.org_rdir.z[k])); + const vfloat rmaxX = msub(bmaxX, vfloat(ray.rdir.x[k]), vfloat(ray.org_rdir.x[k])); + const vfloat rmaxY = msub(bmaxY, vfloat(ray.rdir.y[k]), vfloat(ray.org_rdir.y[k])); + const vfloat rmaxZ = msub(bmaxZ, vfloat(ray.rdir.z[k]), vfloat(ray.org_rdir.z[k])); + const vfloat rmin = maxi(rminX, rminY, rminZ, vfloat(ray.tnear[k])); + const vfloat rmax = mini(rmaxX, rmaxY, rmaxZ, vfloat(ray.tfar[k])); + + const vbool vmask_first_hit = rmin <= rmax; + + return movemask(vmask_first_hit) & (((size_t)1 << N)-1); + } + + template + __forceinline size_t intersectNodeK(const typename BVHN::AABBNode* __restrict__ node, size_t i, + const TravRayKStreamFast& ray, const NearFarPrecalculations& nf) + { + char* ptr = (char*)&node->lower_x + i*sizeof(float); + const vfloat bminX = *(const float*)(ptr + nf.nearX); + const vfloat bminY = *(const float*)(ptr + nf.nearY); + const vfloat bminZ = *(const float*)(ptr + nf.nearZ); + const vfloat bmaxX = *(const float*)(ptr + nf.farX); + const vfloat bmaxY = *(const float*)(ptr + nf.farY); + const vfloat bmaxZ = *(const float*)(ptr + nf.farZ); + + const vfloat rminX = msub(bminX, ray.rdir.x, ray.org_rdir.x); + const vfloat rminY = msub(bminY, ray.rdir.y, ray.org_rdir.y); + const vfloat rminZ = msub(bminZ, ray.rdir.z, ray.org_rdir.z); + const vfloat rmaxX = msub(bmaxX, ray.rdir.x, ray.org_rdir.x); + const vfloat rmaxY = msub(bmaxY, ray.rdir.y, ray.org_rdir.y); + const vfloat rmaxZ = msub(bmaxZ, ray.rdir.z, ray.org_rdir.z); + + const vfloat rmin = maxi(rminX, rminY, rminZ, ray.tnear); + const vfloat rmax = mini(rmaxX, rmaxY, rmaxZ, ray.tfar); + + const vbool vmask_first_hit = rmin <= rmax; + + return movemask(vmask_first_hit); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // Robust AABBNode intersection + ////////////////////////////////////////////////////////////////////////////////////// + + template + __forceinline size_t intersectNode1(const typename BVHN::AABBNode* __restrict__ node, + const TravRayKStreamRobust& ray, size_t k, const NearFarPrecalculations& nf) + { + const vfloat bminX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearX)); + const vfloat bminY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearY)); + const vfloat bminZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.nearZ)); + const vfloat bmaxX = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farX)); + const vfloat bmaxY = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farY)); + const vfloat bmaxZ = vfloat(*(const vfloat*)((const char*)&node->lower_x + nf.farZ)); + + const vfloat rminX = (bminX - vfloat(ray.org.x[k])) * vfloat(ray.rdir.x[k]); + const vfloat rminY = (bminY - vfloat(ray.org.y[k])) * vfloat(ray.rdir.y[k]); + const vfloat rminZ = (bminZ - vfloat(ray.org.z[k])) * vfloat(ray.rdir.z[k]); + const vfloat rmaxX = (bmaxX - vfloat(ray.org.x[k])) * vfloat(ray.rdir.x[k]); + const vfloat rmaxY = (bmaxY - vfloat(ray.org.y[k])) * vfloat(ray.rdir.y[k]); + const vfloat rmaxZ = (bmaxZ - vfloat(ray.org.z[k])) * vfloat(ray.rdir.z[k]); + const float round_up = 1.0f+3.0f*float(ulp); // FIXME: use per instruction rounding for AVX512 + const vfloat rmin = max(rminX, rminY, rminZ, vfloat(ray.tnear[k])); + const vfloat rmax = round_up *min(rmaxX, rmaxY, rmaxZ, vfloat(ray.tfar[k])); + + const vbool vmask_first_hit = rmin <= rmax; + + return movemask(vmask_first_hit) & (((size_t)1 << N)-1); + } + + template + __forceinline size_t intersectNodeK(const typename BVHN::AABBNode* __restrict__ node, size_t i, + const TravRayKStreamRobust& ray, const NearFarPrecalculations& nf) + { + char *ptr = (char*)&node->lower_x + i*sizeof(float); + const vfloat bminX = *(const float*)(ptr + nf.nearX); + const vfloat bminY = *(const float*)(ptr + nf.nearY); + const vfloat bminZ = *(const float*)(ptr + nf.nearZ); + const vfloat bmaxX = *(const float*)(ptr + nf.farX); + const vfloat bmaxY = *(const float*)(ptr + nf.farY); + const vfloat bmaxZ = *(const float*)(ptr + nf.farZ); + + const vfloat rminX = (bminX - ray.org.x) * ray.rdir.x; + const vfloat rminY = (bminY - ray.org.y) * ray.rdir.y; + const vfloat rminZ = (bminZ - ray.org.z) * ray.rdir.z; + const vfloat rmaxX = (bmaxX - ray.org.x) * ray.rdir.x; + const vfloat rmaxY = (bmaxY - ray.org.y) * ray.rdir.y; + const vfloat rmaxZ = (bmaxZ - ray.org.z) * ray.rdir.z; + + const float round_up = 1.0f+3.0f*float(ulp); + const vfloat rmin = max(rminX, rminY, rminZ, vfloat(ray.tnear)); + const vfloat rmax = round_up * min(rmaxX, rmaxY, rmaxZ, vfloat(ray.tfar)); + + const vbool vmask_first_hit = rmin <= rmax; + + return movemask(vmask_first_hit); + } + } +} diff --git a/thirdparty/embree/kernels/common/accel.h b/thirdparty/embree/kernels/common/accel.h new file mode 100644 index 000000000000..f332d3655516 --- /dev/null +++ b/thirdparty/embree/kernels/common/accel.h @@ -0,0 +1,556 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "ray.h" +#include "point_query.h" +#include "context.h" + +namespace embree +{ + class Scene; + + /*! Base class for the acceleration structure data. */ + class AccelData : public RefCount + { + ALIGNED_CLASS_(16); + public: + enum Type { TY_UNKNOWN = 0, TY_ACCELN = 1, TY_ACCEL_INSTANCE = 2, TY_BVH4 = 3, TY_BVH8 = 4 }; + + public: + AccelData (const Type type) + : bounds(empty), type(type) {} + + /*! notifies the acceleration structure about the deletion of some geometry */ + virtual void deleteGeometry(size_t geomID) {}; + + /*! clears the acceleration structure data */ + virtual void clear() = 0; + + /*! returns normal bounds */ + __forceinline BBox3fa getBounds() const { + return bounds.bounds(); + } + + /*! returns bounds for some time */ + __forceinline BBox3fa getBounds(float t) const { + return bounds.interpolate(t); + } + + /*! returns linear bounds */ + __forceinline LBBox3fa getLinearBounds() const { + return bounds; + } + + /*! checks if acceleration structure is empty */ + __forceinline bool isEmpty() const { + return bounds.bounds0.lower.x == float(pos_inf); + } + + public: + LBBox3fa bounds; // linear bounds + Type type; + }; + + /*! Base class for all intersectable and buildable acceleration structures. */ + class Accel : public AccelData + { + ALIGNED_CLASS_(16); + public: + + struct Intersectors; + + /*! Type of collide function */ + typedef void (*CollideFunc)(void* bvh0, void* bvh1, RTCCollideFunc callback, void* userPtr); + + /*! Type of point query function */ + typedef bool(*PointQueryFunc)(Intersectors* This, /*!< this pointer to accel */ + PointQuery* query, /*!< point query for lookup */ + PointQueryContext* context); /*!< point query context */ + + /*! Type of intersect function pointer for single rays. */ + typedef void (*IntersectFunc)(Intersectors* This, /*!< this pointer to accel */ + RTCRayHit& ray, /*!< ray to intersect */ + IntersectContext* context); + + /*! Type of intersect function pointer for ray packets of size 4. */ + typedef void (*IntersectFunc4)(const void* valid, /*!< pointer to valid mask */ + Intersectors* This, /*!< this pointer to accel */ + RTCRayHit4& ray, /*!< ray packet to intersect */ + IntersectContext* context); + + /*! Type of intersect function pointer for ray packets of size 8. */ + typedef void (*IntersectFunc8)(const void* valid, /*!< pointer to valid mask */ + Intersectors* This, /*!< this pointer to accel */ + RTCRayHit8& ray, /*!< ray packet to intersect */ + IntersectContext* context); + + /*! Type of intersect function pointer for ray packets of size 16. */ + typedef void (*IntersectFunc16)(const void* valid, /*!< pointer to valid mask */ + Intersectors* This, /*!< this pointer to accel */ + RTCRayHit16& ray, /*!< ray packet to intersect */ + IntersectContext* context); + + /*! Type of intersect function pointer for ray packets of size N. */ + typedef void (*IntersectFuncN)(Intersectors* This, /*!< this pointer to accel */ + RTCRayHitN** ray, /*!< ray stream to intersect */ + const size_t N, /*!< number of rays in stream */ + IntersectContext* context /*!< layout flags */); + + + /*! Type of occlusion function pointer for single rays. */ + typedef void (*OccludedFunc) (Intersectors* This, /*!< this pointer to accel */ + RTCRay& ray, /*!< ray to test occlusion */ + IntersectContext* context); + + /*! Type of occlusion function pointer for ray packets of size 4. */ + typedef void (*OccludedFunc4) (const void* valid, /*!< pointer to valid mask */ + Intersectors* This, /*!< this pointer to accel */ + RTCRay4& ray, /*!< ray packet to test occlusion. */ + IntersectContext* context); + + /*! Type of occlusion function pointer for ray packets of size 8. */ + typedef void (*OccludedFunc8) (const void* valid, /*!< pointer to valid mask */ + Intersectors* This, /*!< this pointer to accel */ + RTCRay8& ray, /*!< ray packet to test occlusion. */ + IntersectContext* context); + + /*! Type of occlusion function pointer for ray packets of size 16. */ + typedef void (*OccludedFunc16) (const void* valid, /*!< pointer to valid mask */ + Intersectors* This, /*!< this pointer to accel */ + RTCRay16& ray, /*!< ray packet to test occlusion. */ + IntersectContext* context); + + /*! Type of intersect function pointer for ray packets of size N. */ + typedef void (*OccludedFuncN)(Intersectors* This, /*!< this pointer to accel */ + RTCRayN** ray, /*!< ray stream to test occlusion */ + const size_t N, /*!< number of rays in stream */ + IntersectContext* context /*!< layout flags */); + typedef void (*ErrorFunc) (); + + struct Collider + { + Collider (ErrorFunc error = nullptr) + : collide((CollideFunc)error), name(nullptr) {} + + Collider (CollideFunc collide, const char* name) + : collide(collide), name(name) {} + + operator bool() const { return name; } + + public: + CollideFunc collide; + const char* name; + }; + + struct Intersector1 + { + Intersector1 (ErrorFunc error = nullptr) + : intersect((IntersectFunc)error), occluded((OccludedFunc)error), name(nullptr) {} + + Intersector1 (IntersectFunc intersect, OccludedFunc occluded, const char* name) + : intersect(intersect), occluded(occluded), pointQuery(nullptr), name(name) {} + + Intersector1 (IntersectFunc intersect, OccludedFunc occluded, PointQueryFunc pointQuery, const char* name) + : intersect(intersect), occluded(occluded), pointQuery(pointQuery), name(name) {} + + operator bool() const { return name; } + + public: + static const char* type; + IntersectFunc intersect; + OccludedFunc occluded; + PointQueryFunc pointQuery; + const char* name; + }; + + struct Intersector4 + { + Intersector4 (ErrorFunc error = nullptr) + : intersect((IntersectFunc4)error), occluded((OccludedFunc4)error), name(nullptr) {} + + Intersector4 (IntersectFunc4 intersect, OccludedFunc4 occluded, const char* name) + : intersect(intersect), occluded(occluded), name(name) {} + + operator bool() const { return name; } + + public: + static const char* type; + IntersectFunc4 intersect; + OccludedFunc4 occluded; + const char* name; + }; + + struct Intersector8 + { + Intersector8 (ErrorFunc error = nullptr) + : intersect((IntersectFunc8)error), occluded((OccludedFunc8)error), name(nullptr) {} + + Intersector8 (IntersectFunc8 intersect, OccludedFunc8 occluded, const char* name) + : intersect(intersect), occluded(occluded), name(name) {} + + operator bool() const { return name; } + + public: + static const char* type; + IntersectFunc8 intersect; + OccludedFunc8 occluded; + const char* name; + }; + + struct Intersector16 + { + Intersector16 (ErrorFunc error = nullptr) + : intersect((IntersectFunc16)error), occluded((OccludedFunc16)error), name(nullptr) {} + + Intersector16 (IntersectFunc16 intersect, OccludedFunc16 occluded, const char* name) + : intersect(intersect), occluded(occluded), name(name) {} + + operator bool() const { return name; } + + public: + static const char* type; + IntersectFunc16 intersect; + OccludedFunc16 occluded; + const char* name; + }; + + struct IntersectorN + { + IntersectorN (ErrorFunc error = nullptr) + : intersect((IntersectFuncN)error), occluded((OccludedFuncN)error), name(nullptr) {} + + IntersectorN (IntersectFuncN intersect, OccludedFuncN occluded, const char* name) + : intersect(intersect), occluded(occluded), name(name) {} + + operator bool() const { return name; } + + public: + static const char* type; + IntersectFuncN intersect; + OccludedFuncN occluded; + const char* name; + }; + + struct Intersectors + { + Intersectors() + : ptr(nullptr), leafIntersector(nullptr), collider(nullptr), intersector1(nullptr), intersector4(nullptr), intersector8(nullptr), intersector16(nullptr), intersectorN(nullptr) {} + + Intersectors (ErrorFunc error) + : ptr(nullptr), leafIntersector(nullptr), collider(error), intersector1(error), intersector4(error), intersector8(error), intersector16(error), intersectorN(error) {} + + void print(size_t ident) + { + if (collider.name) { + for (size_t i=0; ibuild(); + bounds = accel->bounds; + } + + void deleteGeometry(size_t geomID) { + if (accel ) accel->deleteGeometry(geomID); + if (builder) builder->deleteGeometry(geomID); + } + + void clear() { + if (accel) accel->clear(); + if (builder) builder->clear(); + } + + private: + std::unique_ptr accel; + std::unique_ptr builder; + }; +} diff --git a/thirdparty/embree/kernels/common/acceln.cpp b/thirdparty/embree/kernels/common/acceln.cpp new file mode 100644 index 000000000000..c9f7e921932e --- /dev/null +++ b/thirdparty/embree/kernels/common/acceln.cpp @@ -0,0 +1,232 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "acceln.h" +#include "ray.h" +#include "../../include/embree3/rtcore_ray.h" +#include "../../common/algorithms/parallel_for.h" + +namespace embree +{ + AccelN::AccelN() + : Accel(AccelData::TY_ACCELN), accels() {} + + AccelN::~AccelN() + { + for (size_t i=0; iptr; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + changed |= This->accels[i]->intersectors.pointQuery(query,context); + return changed; + } + + void AccelN::intersect (Accel::Intersectors* This_in, RTCRayHit& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + This->accels[i]->intersectors.intersect(ray,context); + } + + void AccelN::intersect4 (const void* valid, Accel::Intersectors* This_in, RTCRayHit4& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + This->accels[i]->intersectors.intersect4(valid,ray,context); + } + + void AccelN::intersect8 (const void* valid, Accel::Intersectors* This_in, RTCRayHit8& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + This->accels[i]->intersectors.intersect8(valid,ray,context); + } + + void AccelN::intersect16 (const void* valid, Accel::Intersectors* This_in, RTCRayHit16& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + This->accels[i]->intersectors.intersect16(valid,ray,context); + } + + void AccelN::intersectN (Accel::Intersectors* This_in, RTCRayHitN** ray, const size_t N, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + This->accels[i]->intersectors.intersectN(ray,N,context); + } + + void AccelN::occluded (Accel::Intersectors* This_in, RTCRay& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) { + if (This->accels[i]->isEmpty()) continue; + This->accels[i]->intersectors.occluded(ray,context); + if (ray.tfar < 0.0f) break; + } + } + + void AccelN::occluded4 (const void* valid, Accel::Intersectors* This_in, RTCRay4& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) { + if (This->accels[i]->isEmpty()) continue; + This->accels[i]->intersectors.occluded4(valid,ray,context); +#if defined(__SSE2__) + vbool4 valid0 = asBool(((vint4*)valid)[0]); + vbool4 hit0 = ((vfloat4*)ray.tfar)[0] >= vfloat4(zero); + if (unlikely(none(valid0 & hit0))) break; +#endif + } + } + + void AccelN::occluded8 (const void* valid, Accel::Intersectors* This_in, RTCRay8& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) { + if (This->accels[i]->isEmpty()) continue; + This->accels[i]->intersectors.occluded8(valid,ray,context); +#if defined(__SSE2__) // FIXME: use higher ISA + vbool4 valid0 = asBool(((vint4*)valid)[0]); + vbool4 hit0 = ((vfloat4*)ray.tfar)[0] >= vfloat4(zero); + vbool4 valid1 = asBool(((vint4*)valid)[1]); + vbool4 hit1 = ((vfloat4*)ray.tfar)[1] >= vfloat4(zero); + if (unlikely((none((valid0 & hit0) | (valid1 & hit1))))) break; +#endif + } + } + + void AccelN::occluded16 (const void* valid, Accel::Intersectors* This_in, RTCRay16& ray, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + for (size_t i=0; iaccels.size(); i++) { + if (This->accels[i]->isEmpty()) continue; + This->accels[i]->intersectors.occluded16(valid,ray,context); +#if defined(__SSE2__) // FIXME: use higher ISA + vbool4 valid0 = asBool(((vint4*)valid)[0]); + vbool4 hit0 = ((vfloat4*)ray.tfar)[0] >= vfloat4(zero); + vbool4 valid1 = asBool(((vint4*)valid)[1]); + vbool4 hit1 = ((vfloat4*)ray.tfar)[1] >= vfloat4(zero); + vbool4 valid2 = asBool(((vint4*)valid)[2]); + vbool4 hit2 = ((vfloat4*)ray.tfar)[2] >= vfloat4(zero); + vbool4 valid3 = asBool(((vint4*)valid)[3]); + vbool4 hit3 = ((vfloat4*)ray.tfar)[3] >= vfloat4(zero); + if (unlikely((none((valid0 & hit0) | (valid1 & hit1) | (valid2 & hit2) | (valid3 & hit3))))) break; +#endif + } + } + + void AccelN::occludedN (Accel::Intersectors* This_in, RTCRayN** ray, const size_t N, IntersectContext* context) + { + AccelN* This = (AccelN*)This_in->ptr; + size_t M = N; + for (size_t i=0; iaccels.size(); i++) + if (!This->accels[i]->isEmpty()) + This->accels[i]->intersectors.occludedN(ray,M,context); + } + + void AccelN::accels_print(size_t ident) + { + for (size_t i=0; iintersectors.print(ident+2); + } + } + + void AccelN::accels_immutable() + { + for (size_t i=0; iimmutable(); + } + + void AccelN::accels_build () + { + /* reduce memory consumption */ + accels.shrink_to_fit(); + + /* build all acceleration structures in parallel */ + parallel_for (accels.size(), [&] (size_t i) { + accels[i]->build(); + }); + + /* create list of non-empty acceleration structures */ + bool valid1 = true; + bool valid4 = true; + bool valid8 = true; + bool valid16 = true; + for (size_t i=0; iintersectors.intersector1; + valid4 &= (bool) accels[i]->intersectors.intersector4; + valid8 &= (bool) accels[i]->intersectors.intersector8; + valid16 &= (bool) accels[i]->intersectors.intersector16; + } + + if (accels.size() == 1) { + type = accels[0]->type; // FIXME: should just assign entire Accel + bounds = accels[0]->bounds; + intersectors = accels[0]->intersectors; + } + else + { + type = AccelData::TY_ACCELN; + intersectors.ptr = this; + intersectors.intersector1 = Intersector1(&intersect,&occluded,&pointQuery,valid1 ? "AccelN::intersector1": nullptr); + intersectors.intersector4 = Intersector4(&intersect4,&occluded4,valid4 ? "AccelN::intersector4" : nullptr); + intersectors.intersector8 = Intersector8(&intersect8,&occluded8,valid8 ? "AccelN::intersector8" : nullptr); + intersectors.intersector16 = Intersector16(&intersect16,&occluded16,valid16 ? "AccelN::intersector16": nullptr); + intersectors.intersectorN = IntersectorN(&intersectN,&occludedN,"AccelN::intersectorN"); + + /*! calculate bounds */ + bounds = empty; + for (size_t i=0; ibounds); + } + } + + void AccelN::accels_select(bool filter) + { + for (size_t i=0; iintersectors.select(filter); + } + + void AccelN::accels_deleteGeometry(size_t geomID) + { + for (size_t i=0; ideleteGeometry(geomID); + } + + void AccelN::accels_clear() + { + for (size_t i=0; iclear(); + } + } +} + diff --git a/thirdparty/embree/kernels/common/acceln.h b/thirdparty/embree/kernels/common/acceln.h new file mode 100644 index 000000000000..2edd98f647dd --- /dev/null +++ b/thirdparty/embree/kernels/common/acceln.h @@ -0,0 +1,49 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "accel.h" + +namespace embree +{ + /*! merges N acceleration structures together, by processing them in order */ + class AccelN : public Accel + { + public: + AccelN (); + ~AccelN(); + + public: + void accels_add(Accel* accel); + void accels_init(); + + public: + static bool pointQuery (Accel::Intersectors* This, PointQuery* query, PointQueryContext* context); + + public: + static void intersect (Accel::Intersectors* This, RTCRayHit& ray, IntersectContext* context); + static void intersect4 (const void* valid, Accel::Intersectors* This, RTCRayHit4& ray, IntersectContext* context); + static void intersect8 (const void* valid, Accel::Intersectors* This, RTCRayHit8& ray, IntersectContext* context); + static void intersect16 (const void* valid, Accel::Intersectors* This, RTCRayHit16& ray, IntersectContext* context); + static void intersectN (Accel::Intersectors* This, RTCRayHitN** ray, const size_t N, IntersectContext* context); + + public: + static void occluded (Accel::Intersectors* This, RTCRay& ray, IntersectContext* context); + static void occluded4 (const void* valid, Accel::Intersectors* This, RTCRay4& ray, IntersectContext* context); + static void occluded8 (const void* valid, Accel::Intersectors* This, RTCRay8& ray, IntersectContext* context); + static void occluded16 (const void* valid, Accel::Intersectors* This, RTCRay16& ray, IntersectContext* context); + static void occludedN (Accel::Intersectors* This, RTCRayN** ray, const size_t N, IntersectContext* context); + + public: + void accels_print(size_t ident); + void accels_immutable(); + void accels_build (); + void accels_select(bool filter); + void accels_deleteGeometry(size_t geomID); + void accels_clear (); + + public: + std::vector accels; + }; +} diff --git a/thirdparty/embree/kernels/common/accelset.cpp b/thirdparty/embree/kernels/common/accelset.cpp new file mode 100644 index 000000000000..79be1c4301bb --- /dev/null +++ b/thirdparty/embree/kernels/common/accelset.cpp @@ -0,0 +1,17 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "accelset.h" +#include "scene.h" + +namespace embree +{ + AccelSet::AccelSet (Device* device, Geometry::GType gtype, size_t numItems, size_t numTimeSteps) + : Geometry(device,gtype,(unsigned int)numItems,(unsigned int)numTimeSteps), boundsFunc(nullptr) {} + + AccelSet::IntersectorN::IntersectorN (ErrorFunc error) + : intersect((IntersectFuncN)error), occluded((OccludedFuncN)error), name(nullptr) {} + + AccelSet::IntersectorN::IntersectorN (IntersectFuncN intersect, OccludedFuncN occluded, const char* name) + : intersect(intersect), occluded(occluded), name(name) {} +} diff --git a/thirdparty/embree/kernels/common/accelset.h b/thirdparty/embree/kernels/common/accelset.h new file mode 100644 index 000000000000..3774b2accb08 --- /dev/null +++ b/thirdparty/embree/kernels/common/accelset.h @@ -0,0 +1,248 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "builder.h" +#include "geometry.h" +#include "ray.h" +#include "hit.h" + +namespace embree +{ + struct IntersectFunctionNArguments; + struct OccludedFunctionNArguments; + + typedef void (*ReportIntersectionFunc) (IntersectFunctionNArguments* args, const RTCFilterFunctionNArguments* filter_args); + typedef void (*ReportOcclusionFunc) (OccludedFunctionNArguments* args, const RTCFilterFunctionNArguments* filter_args); + + struct IntersectFunctionNArguments : public RTCIntersectFunctionNArguments + { + IntersectContext* internal_context; + Geometry* geometry; + ReportIntersectionFunc report; + }; + + struct OccludedFunctionNArguments : public RTCOccludedFunctionNArguments + { + IntersectContext* internal_context; + Geometry* geometry; + ReportOcclusionFunc report; + }; + + /*! Base class for set of acceleration structures. */ + class AccelSet : public Geometry + { + public: + typedef RTCIntersectFunctionN IntersectFuncN; + typedef RTCOccludedFunctionN OccludedFuncN; + typedef void (*ErrorFunc) (); + + struct IntersectorN + { + IntersectorN (ErrorFunc error = nullptr) ; + IntersectorN (IntersectFuncN intersect, OccludedFuncN occluded, const char* name); + + operator bool() const { return name; } + + public: + static const char* type; + IntersectFuncN intersect; + OccludedFuncN occluded; + const char* name; + }; + + public: + + /*! construction */ + AccelSet (Device* device, Geometry::GType gtype, size_t items, size_t numTimeSteps); + + /*! makes the acceleration structure immutable */ + virtual void immutable () {} + + /*! build accel */ + virtual void build () = 0; + + /*! check if the i'th primitive is valid between the specified time range */ + __forceinline bool valid(size_t i, const range& itime_range) const + { + for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) + if (!isvalid_non_empty(bounds(i,itime))) return false; + + return true; + } + + /*! Calculates the bounds of an item */ + __forceinline BBox3fa bounds(size_t i, size_t itime = 0) const + { + BBox3fa box; + assert(i < size()); + RTCBoundsFunctionArguments args; + args.geometryUserPtr = userPtr; + args.primID = (unsigned int)i; + args.timeStep = (unsigned int)itime; + args.bounds_o = (RTCBounds*)&box; + boundsFunc(&args); + return box; + } + + /*! calculates the linear bounds of the i'th item at the itime'th time segment */ + __forceinline LBBox3fa linearBounds(size_t i, size_t itime) const + { + BBox3fa box[2]; + assert(i < size()); + RTCBoundsFunctionArguments args; + args.geometryUserPtr = userPtr; + args.primID = (unsigned int)i; + args.timeStep = (unsigned int)(itime+0); + args.bounds_o = (RTCBounds*)&box[0]; + boundsFunc(&args); + args.timeStep = (unsigned int)(itime+1); + args.bounds_o = (RTCBounds*)&box[1]; + boundsFunc(&args); + return LBBox3fa(box[0],box[1]); + } + + /*! calculates the build bounds of the i'th item, if it's valid */ + __forceinline bool buildBounds(size_t i, BBox3fa* bbox = nullptr) const + { + const BBox3fa b = bounds(i); + if (bbox) *bbox = b; + return isvalid_non_empty(b); + } + + /*! calculates the build bounds of the i'th item at the itime'th time segment, if it's valid */ + __forceinline bool buildBounds(size_t i, size_t itime, BBox3fa& bbox) const + { + const LBBox3fa bounds = linearBounds(i,itime); + bbox = bounds.bounds0; // use bounding box of first timestep to build BVH + return isvalid_non_empty(bounds); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(size_t primID, const BBox1f& dt) const { + return LBBox3fa([&] (size_t itime) { return bounds(primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline bool linearBounds(size_t i, const BBox1f& time_range, LBBox3fa& bbox) const { + if (!valid(i, timeSegmentRange(time_range))) return false; + bbox = linearBounds(i, time_range); + return true; + } + + /* gets version info of topology */ + unsigned int getTopologyVersion() const { + return numPrimitives; + } + + /* returns true if topology changed */ + bool topologyChanged(unsigned int otherVersion) const { + return numPrimitives != otherVersion; + } + + public: + + /*! Intersects a single ray with the scene. */ + __forceinline void intersect (RayHit& ray, unsigned int geomID, unsigned int primID, IntersectContext* context, ReportIntersectionFunc report) + { + assert(primID < size()); + assert(intersectorN.intersect); + + int mask = -1; + IntersectFunctionNArguments args; + args.valid = &mask; + args.geometryUserPtr = userPtr; + args.context = context->user; + args.rayhit = (RTCRayHitN*)&ray; + args.N = 1; + args.geomID = geomID; + args.primID = primID; + args.internal_context = context; + args.geometry = this; + args.report = report; + + intersectorN.intersect(&args); + } + + /*! Tests if single ray is occluded by the scene. */ + __forceinline void occluded (Ray& ray, unsigned int geomID, unsigned int primID, IntersectContext* context, ReportOcclusionFunc report) + { + assert(primID < size()); + assert(intersectorN.occluded); + + int mask = -1; + OccludedFunctionNArguments args; + args.valid = &mask; + args.geometryUserPtr = userPtr; + args.context = context->user; + args.ray = (RTCRayN*)&ray; + args.N = 1; + args.geomID = geomID; + args.primID = primID; + args.internal_context = context; + args.geometry = this; + args.report = report; + + intersectorN.occluded(&args); + } + + /*! Intersects a packet of K rays with the scene. */ + template + __forceinline void intersect (const vbool& valid, RayHitK& ray, unsigned int geomID, unsigned int primID, IntersectContext* context, ReportIntersectionFunc report) + { + assert(primID < size()); + assert(intersectorN.intersect); + + vint mask = valid.mask32(); + IntersectFunctionNArguments args; + args.valid = (int*)&mask; + args.geometryUserPtr = userPtr; + args.context = context->user; + args.rayhit = (RTCRayHitN*)&ray; + args.N = K; + args.geomID = geomID; + args.primID = primID; + args.internal_context = context; + args.geometry = this; + args.report = report; + + intersectorN.intersect(&args); + } + + /*! Tests if a packet of K rays is occluded by the scene. */ + template + __forceinline void occluded (const vbool& valid, RayK& ray, unsigned int geomID, unsigned int primID, IntersectContext* context, ReportOcclusionFunc report) + { + assert(primID < size()); + assert(intersectorN.occluded); + + vint mask = valid.mask32(); + OccludedFunctionNArguments args; + args.valid = (int*)&mask; + args.geometryUserPtr = userPtr; + args.context = context->user; + args.ray = (RTCRayN*)&ray; + args.N = K; + args.geomID = geomID; + args.primID = primID; + args.internal_context = context; + args.geometry = this; + args.report = report; + + intersectorN.occluded(&args); + } + + public: + RTCBoundsFunction boundsFunc; + IntersectorN intersectorN; + }; + +#define DEFINE_SET_INTERSECTORN(symbol,intersector) \ + AccelSet::IntersectorN symbol() { \ + return AccelSet::IntersectorN(intersector::intersect, \ + intersector::occluded, \ + TOSTRING(isa) "::" TOSTRING(symbol)); \ + } +} diff --git a/thirdparty/embree/kernels/common/alloc.cpp b/thirdparty/embree/kernels/common/alloc.cpp new file mode 100644 index 000000000000..f958a16f5653 --- /dev/null +++ b/thirdparty/embree/kernels/common/alloc.cpp @@ -0,0 +1,79 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "alloc.h" +#include "../../common/sys/thread.h" + +namespace embree +{ + __thread FastAllocator::ThreadLocal2* FastAllocator::thread_local_allocator2 = nullptr; + SpinLock FastAllocator::s_thread_local_allocators_lock; + std::vector> FastAllocator::s_thread_local_allocators; + + struct fast_allocator_regression_test : public RegressionTest + { + BarrierSys barrier; + std::atomic numFailed; + std::unique_ptr alloc; + + fast_allocator_regression_test() + : RegressionTest("fast_allocator_regression_test"), numFailed(0) + { + registerRegressionTest(this); + } + + static void thread_alloc(fast_allocator_regression_test* This) + { + FastAllocator::CachedAllocator threadalloc = This->alloc->getCachedAllocator(); + + size_t* ptrs[1000]; + for (size_t j=0; j<1000; j++) + { + This->barrier.wait(); + for (size_t i=0; i<1000; i++) { + ptrs[i] = (size_t*) threadalloc.malloc0(sizeof(size_t)+(i%32)); + *ptrs[i] = size_t(threadalloc.talloc0) + i; + } + for (size_t i=0; i<1000; i++) { + if (*ptrs[i] != size_t(threadalloc.talloc0) + i) + This->numFailed++; + } + This->barrier.wait(); + } + } + + bool run () + { + alloc = make_unique(new FastAllocator(nullptr,false)); + numFailed.store(0); + + size_t numThreads = getNumberOfLogicalThreads(); + barrier.init(numThreads+1); + + /* create threads */ + std::vector threads; + for (size_t i=0; ireset(); + barrier.wait(); + barrier.wait(); + } + + /* destroy threads */ + for (size_t i=0; idefaultBlockSize; + } + + /* Allocate aligned memory from the threads memory block. */ + __forceinline void* malloc(FastAllocator* alloc, size_t bytes, size_t align = 16) + { + /* bind the thread local allocator to the proper FastAllocator*/ + parent->bind(alloc); + + assert(align <= maxAlignment); + bytesUsed += bytes; + + /* try to allocate in local block */ + size_t ofs = (align - cur) & (align-1); + cur += bytes + ofs; + if (likely(cur <= end)) { bytesWasted += ofs; return &ptr[cur - bytes]; } + cur -= bytes + ofs; + + /* if allocation is too large allocate with parent allocator */ + if (4*bytes > allocBlockSize) { + return alloc->malloc(bytes,maxAlignment,false); + } + + /* get new partial block if allocation failed */ + size_t blockSize = allocBlockSize; + ptr = (char*) alloc->malloc(blockSize,maxAlignment,true); + bytesWasted += end-cur; + cur = 0; end = blockSize; + + /* retry allocation */ + ofs = (align - cur) & (align-1); + cur += bytes + ofs; + if (likely(cur <= end)) { bytesWasted += ofs; return &ptr[cur - bytes]; } + cur -= bytes + ofs; + + /* get new full block if allocation failed */ + blockSize = allocBlockSize; + ptr = (char*) alloc->malloc(blockSize,maxAlignment,false); + bytesWasted += end-cur; + cur = 0; end = blockSize; + + /* retry allocation */ + ofs = (align - cur) & (align-1); + cur += bytes + ofs; + if (likely(cur <= end)) { bytesWasted += ofs; return &ptr[cur - bytes]; } + cur -= bytes + ofs; + + /* should never happen as large allocations get handled specially above */ + assert(false); + return nullptr; + } + + + /*! returns amount of used bytes */ + __forceinline size_t getUsedBytes() const { return bytesUsed; } + + /*! returns amount of free bytes */ + __forceinline size_t getFreeBytes() const { return end-cur; } + + /*! returns amount of wasted bytes */ + __forceinline size_t getWastedBytes() const { return bytesWasted; } + + private: + ThreadLocal2* parent; + char* ptr; //!< pointer to memory block + size_t cur; //!< current location of the allocator + size_t end; //!< end of the memory block + size_t allocBlockSize; //!< block size for allocations + size_t bytesUsed; //!< number of total bytes allocated + size_t bytesWasted; //!< number of bytes wasted + }; + + /*! Two thread local structures. */ + struct __aligned(64) ThreadLocal2 + { + ALIGNED_CLASS_(64); + public: + + __forceinline ThreadLocal2() + : alloc(nullptr), alloc0(this), alloc1(this) {} + + /*! bind to fast allocator */ + __forceinline void bind(FastAllocator* alloc_i) + { + assert(alloc_i); + if (alloc.load() == alloc_i) return; + Lock lock(mutex); + //if (alloc.load() == alloc_i) return; // not required as only one thread calls bind + if (alloc.load()) { + alloc.load()->bytesUsed += alloc0.getUsedBytes() + alloc1.getUsedBytes(); + alloc.load()->bytesFree += alloc0.getFreeBytes() + alloc1.getFreeBytes(); + alloc.load()->bytesWasted += alloc0.getWastedBytes() + alloc1.getWastedBytes(); + } + alloc0.init(alloc_i); + alloc1.init(alloc_i); + alloc.store(alloc_i); + alloc_i->join(this); + } + + /*! unbind to fast allocator */ + void unbind(FastAllocator* alloc_i) + { + assert(alloc_i); + if (alloc.load() != alloc_i) return; + Lock lock(mutex); + if (alloc.load() != alloc_i) return; // required as a different thread calls unbind + alloc.load()->bytesUsed += alloc0.getUsedBytes() + alloc1.getUsedBytes(); + alloc.load()->bytesFree += alloc0.getFreeBytes() + alloc1.getFreeBytes(); + alloc.load()->bytesWasted += alloc0.getWastedBytes() + alloc1.getWastedBytes(); + alloc0.init(nullptr); + alloc1.init(nullptr); + alloc.store(nullptr); + } + + public: + SpinLock mutex; //!< required as unbind is called from other threads + std::atomic alloc; //!< parent allocator + ThreadLocal alloc0; + ThreadLocal alloc1; + }; + + FastAllocator (Device* device, bool osAllocation) + : device(device), slotMask(0), usedBlocks(nullptr), freeBlocks(nullptr), use_single_mode(false), defaultBlockSize(PAGE_SIZE), estimatedSize(0), + growSize(PAGE_SIZE), maxGrowSize(maxAllocationSize), log2_grow_size_scale(0), bytesUsed(0), bytesFree(0), bytesWasted(0), atype(osAllocation ? OS_MALLOC : ALIGNED_MALLOC), + primrefarray(device,0) + { + for (size_t i=0; i& primrefarray_i) { + primrefarray = std::move(primrefarray_i); + } + + void unshare(mvector& primrefarray_o) + { + reset(); // this removes blocks that are allocated inside the shared primref array + primrefarray_o = std::move(primrefarray); + } + + /*! returns first fast thread local allocator */ + __forceinline ThreadLocal* _threadLocal() { + return &threadLocal2()->alloc0; + } + + void setOSallocation(bool flag) + { + atype = flag ? OS_MALLOC : ALIGNED_MALLOC; + } + + private: + + /*! returns both fast thread local allocators */ + __forceinline ThreadLocal2* threadLocal2() + { + ThreadLocal2* alloc = thread_local_allocator2; + if (alloc == nullptr) { + thread_local_allocator2 = alloc = new ThreadLocal2; + Lock lock(s_thread_local_allocators_lock); + s_thread_local_allocators.push_back(make_unique(alloc)); + } + return alloc; + } + + public: + + __forceinline void join(ThreadLocal2* alloc) + { + Lock lock(thread_local_allocators_lock); + thread_local_allocators.push_back(alloc); + } + + public: + + struct CachedAllocator + { + __forceinline CachedAllocator(void* ptr) + : alloc(nullptr), talloc0(nullptr), talloc1(nullptr) + { + assert(ptr == nullptr); + } + + __forceinline CachedAllocator(FastAllocator* alloc, ThreadLocal2* talloc) + : alloc(alloc), talloc0(&talloc->alloc0), talloc1(alloc->use_single_mode ? &talloc->alloc0 : &talloc->alloc1) {} + + __forceinline operator bool () const { + return alloc != nullptr; + } + + __forceinline void* operator() (size_t bytes, size_t align = 16) const { + return talloc0->malloc(alloc,bytes,align); + } + + __forceinline void* malloc0 (size_t bytes, size_t align = 16) const { + return talloc0->malloc(alloc,bytes,align); + } + + __forceinline void* malloc1 (size_t bytes, size_t align = 16) const { + return talloc1->malloc(alloc,bytes,align); + } + + public: + FastAllocator* alloc; + ThreadLocal* talloc0; + ThreadLocal* talloc1; + }; + + __forceinline CachedAllocator getCachedAllocator() { + return CachedAllocator(this,threadLocal2()); + } + + /*! Builder interface to create thread local allocator */ + struct Create + { + public: + __forceinline Create (FastAllocator* allocator) : allocator(allocator) {} + __forceinline CachedAllocator operator() () const { return allocator->getCachedAllocator(); } + + private: + FastAllocator* allocator; + }; + + void internal_fix_used_blocks() + { + /* move thread local blocks to global block list */ + for (size_t i = 0; i < MAX_THREAD_USED_BLOCK_SLOTS; i++) + { + while (threadBlocks[i].load() != nullptr) { + Block* nextUsedBlock = threadBlocks[i].load()->next; + threadBlocks[i].load()->next = usedBlocks.load(); + usedBlocks = threadBlocks[i].load(); + threadBlocks[i] = nextUsedBlock; + } + threadBlocks[i] = nullptr; + } + } + + static const size_t threadLocalAllocOverhead = 20; //! 20 means 5% parallel allocation overhead through unfilled thread local blocks +#if defined(__AVX512ER__) // KNL + static const size_t mainAllocOverheadStatic = 15; //! 15 means 7.5% allocation overhead through unfilled main alloc blocks +#else + static const size_t mainAllocOverheadStatic = 20; //! 20 means 5% allocation overhead through unfilled main alloc blocks +#endif + static const size_t mainAllocOverheadDynamic = 8; //! 20 means 12.5% allocation overhead through unfilled main alloc blocks + + /* calculates a single threaded threshold for the builders such + * that for small scenes the overhead of partly allocated blocks + * per thread is low */ + size_t fixSingleThreadThreshold(size_t branchingFactor, size_t defaultThreshold, size_t numPrimitives, size_t bytesEstimated) + { + if (numPrimitives == 0 || bytesEstimated == 0) + return defaultThreshold; + + /* calculate block size in bytes to fulfill threadLocalAllocOverhead constraint */ + const size_t single_mode_factor = use_single_mode ? 1 : 2; + const size_t threadCount = TaskScheduler::threadCount(); + const size_t singleThreadBytes = single_mode_factor*threadLocalAllocOverhead*defaultBlockSize; + + /* if we do not have to limit number of threads use optimal thresdhold */ + if ( (bytesEstimated+(singleThreadBytes-1))/singleThreadBytes >= threadCount) + return defaultThreshold; + + /* otherwise limit number of threads by calculating proper single thread threshold */ + else { + double bytesPerPrimitive = double(bytesEstimated)/double(numPrimitives); + return size_t(ceil(branchingFactor*singleThreadBytes/bytesPerPrimitive)); + } + } + + __forceinline size_t alignSize(size_t i) { + return (i+127)/128*128; + } + + /*! initializes the grow size */ + __forceinline void initGrowSizeAndNumSlots(size_t bytesEstimated, bool fast) + { + /* we do not need single thread local allocator mode */ + use_single_mode = false; + + /* calculate growSize such that at most mainAllocationOverhead gets wasted when a block stays unused */ + size_t mainAllocOverhead = fast ? mainAllocOverheadDynamic : mainAllocOverheadStatic; + size_t blockSize = alignSize(bytesEstimated/mainAllocOverhead); + growSize = maxGrowSize = clamp(blockSize,size_t(1024),maxAllocationSize); + + /* if we reached the maxAllocationSize for growSize, we can + * increase the number of allocation slots by still guaranteeing + * the mainAllocationOverhead */ + slotMask = 0x0; + + if (MAX_THREAD_USED_BLOCK_SLOTS >= 2 && bytesEstimated > 2*mainAllocOverhead*growSize) slotMask = 0x1; + if (MAX_THREAD_USED_BLOCK_SLOTS >= 4 && bytesEstimated > 4*mainAllocOverhead*growSize) slotMask = 0x3; + if (MAX_THREAD_USED_BLOCK_SLOTS >= 8 && bytesEstimated > 8*mainAllocOverhead*growSize) slotMask = 0x7; + if (MAX_THREAD_USED_BLOCK_SLOTS >= 8 && bytesEstimated > 16*mainAllocOverhead*growSize) { growSize *= 2; } /* if the overhead is tiny, double the growSize */ + + /* set the thread local alloc block size */ + size_t defaultBlockSizeSwitch = PAGE_SIZE+maxAlignment; + + /* for sufficiently large scene we can increase the defaultBlockSize over the defaultBlockSizeSwitch size */ +#if 0 // we do not do this as a block size of 4160 if for some reason best for KNL + const size_t threadCount = TaskScheduler::threadCount(); + const size_t single_mode_factor = use_single_mode ? 1 : 2; + const size_t singleThreadBytes = single_mode_factor*threadLocalAllocOverhead*defaultBlockSizeSwitch; + if (bytesEstimated+(singleThreadBytes-1))/singleThreadBytes >= threadCount) + defaultBlockSize = min(max(defaultBlockSizeSwitch,bytesEstimated/(single_mode_factor*threadLocalAllocOverhead*threadCount)),growSize); + + /* otherwise we grow the defaultBlockSize up to defaultBlockSizeSwitch */ + else +#endif + defaultBlockSize = clamp(blockSize,size_t(1024),defaultBlockSizeSwitch); + + if (bytesEstimated == 0) { + maxGrowSize = maxAllocationSize; // special mode if builder cannot estimate tree size + defaultBlockSize = defaultBlockSizeSwitch; + } + log2_grow_size_scale = 0; + + if (device->alloc_main_block_size != 0) growSize = device->alloc_main_block_size; + if (device->alloc_num_main_slots >= 1 ) slotMask = 0x0; + if (device->alloc_num_main_slots >= 2 ) slotMask = 0x1; + if (device->alloc_num_main_slots >= 4 ) slotMask = 0x3; + if (device->alloc_num_main_slots >= 8 ) slotMask = 0x7; + if (device->alloc_thread_block_size != 0) defaultBlockSize = device->alloc_thread_block_size; + if (device->alloc_single_thread_alloc != -1) use_single_mode = device->alloc_single_thread_alloc; + } + + /*! initializes the allocator */ + void init(size_t bytesAllocate, size_t bytesReserve, size_t bytesEstimate) + { + internal_fix_used_blocks(); + /* distribute the allocation to multiple thread block slots */ + slotMask = MAX_THREAD_USED_BLOCK_SLOTS-1; // FIXME: remove + if (usedBlocks.load() || freeBlocks.load()) { reset(); return; } + if (bytesReserve == 0) bytesReserve = bytesAllocate; + freeBlocks = Block::create(device,bytesAllocate,bytesReserve,nullptr,atype); + estimatedSize = bytesEstimate; + initGrowSizeAndNumSlots(bytesEstimate,true); + } + + /*! initializes the allocator */ + void init_estimate(size_t bytesEstimate) + { + internal_fix_used_blocks(); + if (usedBlocks.load() || freeBlocks.load()) { reset(); return; } + /* single allocator mode ? */ + estimatedSize = bytesEstimate; + //initGrowSizeAndNumSlots(bytesEstimate,false); + initGrowSizeAndNumSlots(bytesEstimate,false); + + } + + /*! frees state not required after build */ + __forceinline void cleanup() + { + internal_fix_used_blocks(); + + /* unbind all thread local allocators */ + for (auto alloc : thread_local_allocators) alloc->unbind(this); + thread_local_allocators.clear(); + } + + /*! resets the allocator, memory blocks get reused */ + void reset () + { + internal_fix_used_blocks(); + + bytesUsed.store(0); + bytesFree.store(0); + bytesWasted.store(0); + + /* reset all used blocks and move them to begin of free block list */ + while (usedBlocks.load() != nullptr) { + usedBlocks.load()->reset_block(); + Block* nextUsedBlock = usedBlocks.load()->next; + usedBlocks.load()->next = freeBlocks.load(); + freeBlocks = usedBlocks.load(); + usedBlocks = nextUsedBlock; + } + + /* remove all shared blocks as they are re-added during build */ + freeBlocks.store(Block::remove_shared_blocks(freeBlocks.load())); + + for (size_t i=0; iunbind(this); + thread_local_allocators.clear(); + } + + /*! frees all allocated memory */ + __forceinline void clear() + { + cleanup(); + bytesUsed.store(0); + bytesFree.store(0); + bytesWasted.store(0); + if (usedBlocks.load() != nullptr) usedBlocks.load()->clear_list(device); usedBlocks = nullptr; + if (freeBlocks.load() != nullptr) freeBlocks.load()->clear_list(device); freeBlocks = nullptr; + for (size_t i=0; imalloc(device,bytes,align,partial); + if (ptr) return ptr; + } + + /* throw error if allocation is too large */ + if (bytes > maxAllocationSize) + throw_RTCError(RTC_ERROR_UNKNOWN,"allocation is too large"); + + /* parallel block creation in case of no freeBlocks, avoids single global mutex */ + if (likely(freeBlocks.load() == nullptr)) + { + Lock lock(slotMutex[slot]); + if (myUsedBlocks == threadUsedBlocks[slot]) { + const size_t alignedBytes = (bytes+(align-1)) & ~(align-1); + const size_t allocSize = max(min(growSize,maxGrowSize),alignedBytes); + assert(allocSize >= bytes); + threadBlocks[slot] = threadUsedBlocks[slot] = Block::create(device,allocSize,allocSize,threadBlocks[slot],atype); // FIXME: a large allocation might throw away a block here! + // FIXME: a direct allocation should allocate inside the block here, and not in the next loop! a different thread could do some allocation and make the large allocation fail. + } + continue; + } + + /* if this fails allocate new block */ + { + Lock lock(mutex); + if (myUsedBlocks == threadUsedBlocks[slot]) + { + if (freeBlocks.load() != nullptr) { + Block* nextFreeBlock = freeBlocks.load()->next; + freeBlocks.load()->next = usedBlocks; + __memory_barrier(); + usedBlocks = freeBlocks.load(); + threadUsedBlocks[slot] = freeBlocks.load(); + freeBlocks = nextFreeBlock; + } else { + const size_t allocSize = min(growSize*incGrowSizeScale(),maxGrowSize); + usedBlocks = threadUsedBlocks[slot] = Block::create(device,allocSize,allocSize,usedBlocks,atype); // FIXME: a large allocation should get delivered directly, like above! + } + } + } + } + } + + /*! add new block */ + void addBlock(void* ptr, ssize_t bytes) + { + Lock lock(mutex); + const size_t sizeof_Header = offsetof(Block,data[0]); + void* aptr = (void*) ((((size_t)ptr)+maxAlignment-1) & ~(maxAlignment-1)); + size_t ofs = (size_t) aptr - (size_t) ptr; + bytes -= ofs; + if (bytes < 4096) return; // ignore empty or very small blocks + freeBlocks = new (aptr) Block(SHARED,bytes-sizeof_Header,bytes-sizeof_Header,freeBlocks,ofs); + } + + /* special allocation only used from morton builder only a single time for each build */ + void* specialAlloc(size_t bytes) + { + assert(freeBlocks.load() != nullptr && freeBlocks.load()->getBlockAllocatedBytes() >= bytes); + return freeBlocks.load()->ptr(); + } + + struct Statistics + { + Statistics () + : bytesUsed(0), bytesFree(0), bytesWasted(0) {} + + Statistics (size_t bytesUsed, size_t bytesFree, size_t bytesWasted) + : bytesUsed(bytesUsed), bytesFree(bytesFree), bytesWasted(bytesWasted) {} + + Statistics (FastAllocator* alloc, AllocationType atype, bool huge_pages = false) + : bytesUsed(0), bytesFree(0), bytesWasted(0) + { + Block* usedBlocks = alloc->usedBlocks.load(); + Block* freeBlocks = alloc->freeBlocks.load(); + if (usedBlocks) bytesUsed += usedBlocks->getUsedBytes(atype,huge_pages); + if (freeBlocks) bytesFree += freeBlocks->getAllocatedBytes(atype,huge_pages); + if (usedBlocks) bytesFree += usedBlocks->getFreeBytes(atype,huge_pages); + if (freeBlocks) bytesWasted += freeBlocks->getWastedBytes(atype,huge_pages); + if (usedBlocks) bytesWasted += usedBlocks->getWastedBytes(atype,huge_pages); + } + + std::string str(size_t numPrimitives) + { + std::stringstream str; + str.setf(std::ios::fixed, std::ios::floatfield); + str << "used = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesUsed << " MB, " + << "free = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesFree << " MB, " + << "wasted = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesWasted << " MB, " + << "total = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesAllocatedTotal() << " MB, " + << "#bytes/prim = " << std::setw(6) << std::setprecision(2) << double(bytesAllocatedTotal())/double(numPrimitives); + return str.str(); + } + + friend Statistics operator+ ( const Statistics& a, const Statistics& b) + { + return Statistics(a.bytesUsed+b.bytesUsed, + a.bytesFree+b.bytesFree, + a.bytesWasted+b.bytesWasted); + } + + size_t bytesAllocatedTotal() const { + return bytesUsed + bytesFree + bytesWasted; + } + + public: + size_t bytesUsed; + size_t bytesFree; + size_t bytesWasted; + }; + + Statistics getStatistics(AllocationType atype, bool huge_pages = false) { + return Statistics(this,atype,huge_pages); + } + + size_t getUsedBytes() { + return bytesUsed; + } + + size_t getWastedBytes() { + return bytesWasted; + } + + struct AllStatistics + { + AllStatistics (FastAllocator* alloc) + + : bytesUsed(alloc->bytesUsed), + bytesFree(alloc->bytesFree), + bytesWasted(alloc->bytesWasted), + stat_all(alloc,ANY_TYPE), + stat_malloc(alloc,ALIGNED_MALLOC), + stat_4K(alloc,OS_MALLOC,false), + stat_2M(alloc,OS_MALLOC,true), + stat_shared(alloc,SHARED) {} + + AllStatistics (size_t bytesUsed, + size_t bytesFree, + size_t bytesWasted, + Statistics stat_all, + Statistics stat_malloc, + Statistics stat_4K, + Statistics stat_2M, + Statistics stat_shared) + + : bytesUsed(bytesUsed), + bytesFree(bytesFree), + bytesWasted(bytesWasted), + stat_all(stat_all), + stat_malloc(stat_malloc), + stat_4K(stat_4K), + stat_2M(stat_2M), + stat_shared(stat_shared) {} + + friend AllStatistics operator+ (const AllStatistics& a, const AllStatistics& b) + { + return AllStatistics(a.bytesUsed+b.bytesUsed, + a.bytesFree+b.bytesFree, + a.bytesWasted+b.bytesWasted, + a.stat_all + b.stat_all, + a.stat_malloc + b.stat_malloc, + a.stat_4K + b.stat_4K, + a.stat_2M + b.stat_2M, + a.stat_shared + b.stat_shared); + } + + void print(size_t numPrimitives) + { + std::stringstream str0; + str0.setf(std::ios::fixed, std::ios::floatfield); + str0 << " alloc : " + << "used = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesUsed << " MB, " + << " " + << "#bytes/prim = " << std::setw(6) << std::setprecision(2) << double(bytesUsed)/double(numPrimitives); + std::cout << str0.str() << std::endl; + + std::stringstream str1; + str1.setf(std::ios::fixed, std::ios::floatfield); + str1 << " alloc : " + << "used = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesUsed << " MB, " + << "free = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesFree << " MB, " + << "wasted = " << std::setw(7) << std::setprecision(3) << 1E-6f*bytesWasted << " MB, " + << "total = " << std::setw(7) << std::setprecision(3) << 1E-6f*(bytesUsed+bytesFree+bytesWasted) << " MB, " + << "#bytes/prim = " << std::setw(6) << std::setprecision(2) << double(bytesUsed+bytesFree+bytesWasted)/double(numPrimitives); + std::cout << str1.str() << std::endl; + + std::cout << " total : " << stat_all.str(numPrimitives) << std::endl; + std::cout << " 4K : " << stat_4K.str(numPrimitives) << std::endl; + std::cout << " 2M : " << stat_2M.str(numPrimitives) << std::endl; + std::cout << " malloc: " << stat_malloc.str(numPrimitives) << std::endl; + std::cout << " shared: " << stat_shared.str(numPrimitives) << std::endl; + } + + private: + size_t bytesUsed; + size_t bytesFree; + size_t bytesWasted; + Statistics stat_all; + Statistics stat_malloc; + Statistics stat_4K; + Statistics stat_2M; + Statistics stat_shared; + }; + + void print_blocks() + { + std::cout << " estimatedSize = " << estimatedSize << ", slotMask = " << slotMask << ", use_single_mode = " << use_single_mode << ", maxGrowSize = " << maxGrowSize << ", defaultBlockSize = " << defaultBlockSize << std::endl; + + std::cout << " used blocks = "; + if (usedBlocks.load() != nullptr) usedBlocks.load()->print_list(); + std::cout << "[END]" << std::endl; + + std::cout << " free blocks = "; + if (freeBlocks.load() != nullptr) freeBlocks.load()->print_list(); + std::cout << "[END]" << std::endl; + } + + private: + + struct Block + { + static Block* create(MemoryMonitorInterface* device, size_t bytesAllocate, size_t bytesReserve, Block* next, AllocationType atype) + { + /* We avoid using os_malloc for small blocks as this could + * cause a risk of fragmenting the virtual address space and + * reach the limit of vm.max_map_count = 65k under Linux. */ + if (atype == OS_MALLOC && bytesAllocate < maxAllocationSize) + atype = ALIGNED_MALLOC; + + /* we need to additionally allocate some header */ + const size_t sizeof_Header = offsetof(Block,data[0]); + bytesAllocate = sizeof_Header+bytesAllocate; + bytesReserve = sizeof_Header+bytesReserve; + + /* consume full 4k pages with using os_malloc */ + if (atype == OS_MALLOC) { + bytesAllocate = ((bytesAllocate+PAGE_SIZE-1) & ~(PAGE_SIZE-1)); + bytesReserve = ((bytesReserve +PAGE_SIZE-1) & ~(PAGE_SIZE-1)); + } + + /* either use alignedMalloc or os_malloc */ + void *ptr = nullptr; + if (atype == ALIGNED_MALLOC) + { + /* special handling for default block size */ + if (bytesAllocate == (2*PAGE_SIZE_2M)) + { + const size_t alignment = maxAlignment; + if (device) device->memoryMonitor(bytesAllocate+alignment,false); + ptr = alignedMalloc(bytesAllocate,alignment); + + /* give hint to transparently convert these pages to 2MB pages */ + const size_t ptr_aligned_begin = ((size_t)ptr) & ~size_t(PAGE_SIZE_2M-1); + os_advise((void*)(ptr_aligned_begin + 0),PAGE_SIZE_2M); // may fail if no memory mapped before block + os_advise((void*)(ptr_aligned_begin + 1*PAGE_SIZE_2M),PAGE_SIZE_2M); + os_advise((void*)(ptr_aligned_begin + 2*PAGE_SIZE_2M),PAGE_SIZE_2M); // may fail if no memory mapped after block + + return new (ptr) Block(ALIGNED_MALLOC,bytesAllocate-sizeof_Header,bytesAllocate-sizeof_Header,next,alignment); + } + else + { + const size_t alignment = maxAlignment; + if (device) device->memoryMonitor(bytesAllocate+alignment,false); + ptr = alignedMalloc(bytesAllocate,alignment); + return new (ptr) Block(ALIGNED_MALLOC,bytesAllocate-sizeof_Header,bytesAllocate-sizeof_Header,next,alignment); + } + } + else if (atype == OS_MALLOC) + { + if (device) device->memoryMonitor(bytesAllocate,false); + bool huge_pages; ptr = os_malloc(bytesReserve,huge_pages); + return new (ptr) Block(OS_MALLOC,bytesAllocate-sizeof_Header,bytesReserve-sizeof_Header,next,0,huge_pages); + } + else + assert(false); + + return NULL; + } + + Block (AllocationType atype, size_t bytesAllocate, size_t bytesReserve, Block* next, size_t wasted, bool huge_pages = false) + : cur(0), allocEnd(bytesAllocate), reserveEnd(bytesReserve), next(next), wasted(wasted), atype(atype), huge_pages(huge_pages) + { + assert((((size_t)&data[0]) & (maxAlignment-1)) == 0); + } + + static Block* remove_shared_blocks(Block* head) + { + Block** prev_next = &head; + for (Block* block = head; block; block = block->next) { + if (block->atype == SHARED) *prev_next = block->next; + else prev_next = &block->next; + } + return head; + } + + void clear_list(MemoryMonitorInterface* device) + { + Block* block = this; + while (block) { + Block* next = block->next; + block->clear_block(device); + block = next; + } + } + + void clear_block (MemoryMonitorInterface* device) + { + const size_t sizeof_Header = offsetof(Block,data[0]); + const ssize_t sizeof_Alloced = wasted+sizeof_Header+getBlockAllocatedBytes(); + + if (atype == ALIGNED_MALLOC) { + alignedFree(this); + if (device) device->memoryMonitor(-sizeof_Alloced,true); + } + + else if (atype == OS_MALLOC) { + size_t sizeof_This = sizeof_Header+reserveEnd; + os_free(this,sizeof_This,huge_pages); + if (device) device->memoryMonitor(-sizeof_Alloced,true); + } + + else /* if (atype == SHARED) */ { + } + } + + void* malloc(MemoryMonitorInterface* device, size_t& bytes_in, size_t align, bool partial) + { + size_t bytes = bytes_in; + assert(align <= maxAlignment); + bytes = (bytes+(align-1)) & ~(align-1); + if (unlikely(cur+bytes > reserveEnd && !partial)) return nullptr; + const size_t i = cur.fetch_add(bytes); + if (unlikely(i+bytes > reserveEnd && !partial)) return nullptr; + if (unlikely(i > reserveEnd)) return nullptr; + bytes_in = bytes = min(bytes,reserveEnd-i); + + if (i+bytes > allocEnd) { + if (device) device->memoryMonitor(i+bytes-max(i,allocEnd),true); + } + return &data[i]; + } + + void* ptr() { + return &data[cur]; + } + + void reset_block () + { + allocEnd = max(allocEnd,(size_t)cur); + cur = 0; + } + + size_t getBlockUsedBytes() const { + return min(size_t(cur),reserveEnd); + } + + size_t getBlockFreeBytes() const { + return getBlockAllocatedBytes() - getBlockUsedBytes(); + } + + size_t getBlockAllocatedBytes() const { + return min(max(allocEnd,size_t(cur)),reserveEnd); + } + + size_t getBlockWastedBytes() const { + const size_t sizeof_Header = offsetof(Block,data[0]); + return sizeof_Header + wasted; + } + + size_t getBlockReservedBytes() const { + return reserveEnd; + } + + bool hasType(AllocationType atype_i, bool huge_pages_i) const + { + if (atype_i == ANY_TYPE ) return true; + else if (atype == OS_MALLOC) return atype_i == atype && huge_pages_i == huge_pages; + else return atype_i == atype; + } + + size_t getUsedBytes(AllocationType atype, bool huge_pages = false) const { + size_t bytes = 0; + for (const Block* block = this; block; block = block->next) { + if (!block->hasType(atype,huge_pages)) continue; + bytes += block->getBlockUsedBytes(); + } + return bytes; + } + + size_t getFreeBytes(AllocationType atype, bool huge_pages = false) const { + size_t bytes = 0; + for (const Block* block = this; block; block = block->next) { + if (!block->hasType(atype,huge_pages)) continue; + bytes += block->getBlockFreeBytes(); + } + return bytes; + } + + size_t getWastedBytes(AllocationType atype, bool huge_pages = false) const { + size_t bytes = 0; + for (const Block* block = this; block; block = block->next) { + if (!block->hasType(atype,huge_pages)) continue; + bytes += block->getBlockWastedBytes(); + } + return bytes; + } + + size_t getAllocatedBytes(AllocationType atype, bool huge_pages = false) const { + size_t bytes = 0; + for (const Block* block = this; block; block = block->next) { + if (!block->hasType(atype,huge_pages)) continue; + bytes += block->getBlockAllocatedBytes(); + } + return bytes; + } + + void print_list () + { + for (const Block* block = this; block; block = block->next) + block->print_block(); + } + + void print_block() const + { + if (atype == ALIGNED_MALLOC) std::cout << "A"; + else if (atype == OS_MALLOC) std::cout << "O"; + else if (atype == SHARED) std::cout << "S"; + if (huge_pages) std::cout << "H"; + size_t bytesUsed = getBlockUsedBytes(); + size_t bytesFree = getBlockFreeBytes(); + size_t bytesWasted = getBlockWastedBytes(); + std::cout << "[" << bytesUsed << ", " << bytesFree << ", " << bytesWasted << "] "; + } + + public: + std::atomic cur; //!< current location of the allocator + std::atomic allocEnd; //!< end of the allocated memory region + std::atomic reserveEnd; //!< end of the reserved memory region + Block* next; //!< pointer to next block in list + size_t wasted; //!< amount of memory wasted through block alignment + AllocationType atype; //!< allocation mode of the block + bool huge_pages; //!< whether the block uses huge pages + char align[maxAlignment-5*sizeof(size_t)-sizeof(AllocationType)-sizeof(bool)]; //!< align data to maxAlignment + char data[1]; //!< here starts memory to use for allocations + }; + + private: + Device* device; + SpinLock mutex; + size_t slotMask; + std::atomic threadUsedBlocks[MAX_THREAD_USED_BLOCK_SLOTS]; + std::atomic usedBlocks; + std::atomic freeBlocks; + + std::atomic threadBlocks[MAX_THREAD_USED_BLOCK_SLOTS]; + SpinLock slotMutex[MAX_THREAD_USED_BLOCK_SLOTS]; + + bool use_single_mode; + size_t defaultBlockSize; + size_t estimatedSize; + size_t growSize; + size_t maxGrowSize; + std::atomic log2_grow_size_scale; //!< log2 of scaling factor for grow size // FIXME: remove + std::atomic bytesUsed; + std::atomic bytesFree; + std::atomic bytesWasted; + static __thread ThreadLocal2* thread_local_allocator2; + static SpinLock s_thread_local_allocators_lock; + static std::vector> s_thread_local_allocators; + SpinLock thread_local_allocators_lock; + std::vector thread_local_allocators; + AllocationType atype; + mvector primrefarray; //!< primrefarray used to allocate nodes + }; +} diff --git a/thirdparty/embree/kernels/common/buffer.h b/thirdparty/embree/kernels/common/buffer.h new file mode 100644 index 000000000000..02d319c59dd7 --- /dev/null +++ b/thirdparty/embree/kernels/common/buffer.h @@ -0,0 +1,263 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "device.h" + +namespace embree +{ + /*! Implements an API data buffer object. This class may or may not own the data. */ + class Buffer : public RefCount + { + public: + /*! Buffer construction */ + Buffer() + : device(nullptr), ptr(nullptr), numBytes(0), shared(false) {} + + /*! Buffer construction */ + Buffer(Device* device, size_t numBytes_in, void* ptr_in = nullptr) + : device(device), numBytes(numBytes_in) + { + device->refInc(); + + if (ptr_in) + { + shared = true; + ptr = (char*)ptr_in; + } + else + { + shared = false; + alloc(); + } + } + + /*! Buffer destruction */ + ~Buffer() { + free(); + device->refDec(); + } + + /*! this class is not copyable */ + private: + Buffer(const Buffer& other) DELETED; // do not implement + Buffer& operator =(const Buffer& other) DELETED; // do not implement + + public: + /* inits and allocates the buffer */ + void create(Device* device_in, size_t numBytes_in) + { + init(device_in, numBytes_in); + alloc(); + } + + /* inits the buffer */ + void init(Device* device_in, size_t numBytes_in) + { + free(); + device = device_in; + ptr = nullptr; + numBytes = numBytes_in; + shared = false; + } + + /*! sets shared buffer */ + void set(Device* device_in, void* ptr_in, size_t numBytes_in) + { + free(); + device = device_in; + ptr = (char*)ptr_in; + if (numBytes_in != (size_t)-1) + numBytes = numBytes_in; + shared = true; + } + + /*! allocated buffer */ + void alloc() + { + if (device) + device->memoryMonitor(this->bytes(), false); + size_t b = (this->bytes()+15) & ssize_t(-16); + ptr = (char*)alignedMalloc(b,16); + } + + /*! frees the buffer */ + void free() + { + if (shared) return; + alignedFree(ptr); + if (device) + device->memoryMonitor(-ssize_t(this->bytes()), true); + ptr = nullptr; + } + + /*! gets buffer pointer */ + void* data() + { + /* report error if buffer is not existing */ + if (!device) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer specified"); + + /* return buffer */ + return ptr; + } + + /*! returns pointer to first element */ + __forceinline char* getPtr() const { + return ptr; + } + + /*! returns the number of bytes of the buffer */ + __forceinline size_t bytes() const { + return numBytes; + } + + /*! returns true of the buffer is not empty */ + __forceinline operator bool() const { + return ptr; + } + + public: + Device* device; //!< device to report memory usage to + char* ptr; //!< pointer to buffer data + size_t numBytes; //!< number of bytes in the buffer + bool shared; //!< set if memory is shared with application + }; + + /*! An untyped contiguous range of a buffer. This class does not own the buffer content. */ + class RawBufferView + { + public: + /*! Buffer construction */ + RawBufferView() + : ptr_ofs(nullptr), stride(0), num(0), format(RTC_FORMAT_UNDEFINED), modCounter(1), modified(true), userData(0) {} + + public: + /*! sets the buffer view */ + void set(const Ref& buffer_in, size_t offset_in, size_t stride_in, size_t num_in, RTCFormat format_in) + { + if ((offset_in + stride_in * num_in) > (stride_in * buffer_in->numBytes)) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "buffer range out of bounds"); + + ptr_ofs = buffer_in->ptr + offset_in; + stride = stride_in; + num = num_in; + format = format_in; + modCounter++; + modified = true; + buffer = buffer_in; + } + + /*! returns pointer to the first element */ + __forceinline char* getPtr() const { + return ptr_ofs; + } + + /*! returns pointer to the i'th element */ + __forceinline char* getPtr(size_t i) const + { + assert(i otherModCounter; + } + + /*! mark buffer as modified or unmodified */ + __forceinline bool isLocalModified() const { + return modified; + } + + /*! clear local modified flag */ + __forceinline void clearLocalModified() { + modified = false; + } + + /*! returns true of the buffer is not empty */ + __forceinline operator bool() const { + return ptr_ofs; + } + + /*! checks padding to 16 byte check, fails hard */ + __forceinline void checkPadding16() const + { + if (ptr_ofs && num) + volatile int MAYBE_UNUSED w = *((int*)getPtr(size()-1)+3); // FIXME: is failing hard avoidable? + } + + public: + char* ptr_ofs; //!< base pointer plus offset + size_t stride; //!< stride of the buffer in bytes + size_t num; //!< number of elements in the buffer + RTCFormat format; //!< format of the buffer + unsigned int modCounter; //!< version ID of this buffer + bool modified; //!< local modified data + int userData; //!< special data + Ref buffer; //!< reference to the parent buffer + }; + + /*! A typed contiguous range of a buffer. This class does not own the buffer content. */ + template + class BufferView : public RawBufferView + { + public: + typedef T value_type; + + /*! access to the ith element of the buffer */ + __forceinline T& operator [](size_t i) { assert(i + class BufferView : public RawBufferView + { + public: + typedef Vec3fa value_type; + + /*! access to the ith element of the buffer */ + __forceinline const Vec3fa operator [](size_t i) const + { + assert(i + struct ProgressMonitorClosure : BuildProgressMonitor + { + public: + ProgressMonitorClosure (const Closure& closure) : closure(closure) {} + void operator() (size_t dn) const { closure(dn); } + private: + const Closure closure; + }; + template __forceinline const ProgressMonitorClosure BuildProgressMonitorFromClosure(const Closure& closure) { + return ProgressMonitorClosure(closure); + } + + struct LineSegments; + struct TriangleMesh; + struct QuadMesh; + struct UserGeometry; + + class Scene; + + typedef void (*createLineSegmentsAccelTy)(Scene* scene, LineSegments* mesh, AccelData*& accel, Builder*& builder); + typedef void (*createTriangleMeshAccelTy)(Scene* scene, unsigned int geomID, AccelData*& accel, Builder*& builder); + typedef void (*createQuadMeshAccelTy)(Scene* scene, unsigned int geomID, AccelData*& accel, Builder*& builder); + typedef void (*createUserGeometryAccelTy)(Scene* scene, unsigned int geomID, AccelData*& accel, Builder*& builder); + +} diff --git a/thirdparty/embree/kernels/common/context.h b/thirdparty/embree/kernels/common/context.h new file mode 100644 index 000000000000..d0185a74f2e5 --- /dev/null +++ b/thirdparty/embree/kernels/common/context.h @@ -0,0 +1,131 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "rtcore.h" +#include "point_query.h" + +namespace embree +{ + class Scene; + + struct IntersectContext + { + public: + __forceinline IntersectContext(Scene* scene, RTCIntersectContext* user_context) + : scene(scene), user(user_context) {} + + __forceinline bool hasContextFilter() const { + return user->filter != nullptr; + } + + __forceinline bool isCoherent() const { + return embree::isCoherent(user->flags); + } + + __forceinline bool isIncoherent() const { + return embree::isIncoherent(user->flags); + } + + public: + Scene* scene; + RTCIntersectContext* user; + }; + + template + __forceinline Vec4vf enlargeRadiusToMinWidth(const IntersectContext* context, const Geometry* geom, const Vec3vf& ray_org, const Vec4vf& v) + { +#if RTC_MIN_WIDTH + const vfloat d = length(Vec3vf(v) - ray_org); + const vfloat r = clamp(context->user->minWidthDistanceFactor*d, v.w, geom->maxRadiusScale*v.w); + return Vec4vf(v.x,v.y,v.z,r); +#else + return v; +#endif + } + + template + __forceinline Vec3ff enlargeRadiusToMinWidth(const IntersectContext* context, const Geometry* geom, const Vec3fa& ray_org, const Vec3ff& v) + { +#if RTC_MIN_WIDTH + const float d = length(Vec3fa(v) - ray_org); + const float r = clamp(context->user->minWidthDistanceFactor*d, v.w, geom->maxRadiusScale*v.w); + return Vec3ff(v.x,v.y,v.z,r); +#else + return v; +#endif + } + + enum PointQueryType + { + POINT_QUERY_TYPE_UNDEFINED = 0, + POINT_QUERY_TYPE_SPHERE = 1, + POINT_QUERY_TYPE_AABB = 2, + }; + + typedef bool (*PointQueryFunction)(struct RTCPointQueryFunctionArguments* args); + + struct PointQueryContext + { + public: + __forceinline PointQueryContext(Scene* scene, + PointQuery* query_ws, + PointQueryType query_type, + PointQueryFunction func, + RTCPointQueryContext* userContext, + float similarityScale, + void* userPtr) + : scene(scene) + , query_ws(query_ws) + , query_type(query_type) + , func(func) + , userContext(userContext) + , similarityScale(similarityScale) + , userPtr(userPtr) + , primID(RTC_INVALID_GEOMETRY_ID) + , geomID(RTC_INVALID_GEOMETRY_ID) + , query_radius(query_ws->radius) + { + if (query_type == POINT_QUERY_TYPE_AABB) { + assert(similarityScale == 0.f); + updateAABB(); + } + if (userContext->instStackSize == 0) { + assert(similarityScale == 1.f); + } + } + + public: + __forceinline void updateAABB() + { + if (likely(query_ws->radius == (float)inf || userContext->instStackSize == 0)) { + query_radius = Vec3fa(query_ws->radius); + return; + } + + const AffineSpace3fa m = AffineSpace3fa_load_unaligned((AffineSpace3fa*)userContext->world2inst[userContext->instStackSize-1]); + BBox3fa bbox(Vec3fa(-query_ws->radius), Vec3fa(query_ws->radius)); + bbox = xfmBounds(m, bbox); + query_radius = 0.5f * (bbox.upper - bbox.lower); + } + +public: + Scene* scene; + + PointQuery* query_ws; // the original world space point query + PointQueryType query_type; + PointQueryFunction func; + RTCPointQueryContext* userContext; + const float similarityScale; + + void* userPtr; + + unsigned int primID; + unsigned int geomID; + + Vec3fa query_radius; // used if the query is converted to an AABB internally + }; +} + diff --git a/thirdparty/embree/kernels/common/default.h b/thirdparty/embree/kernels/common/default.h new file mode 100644 index 000000000000..3db53413bc57 --- /dev/null +++ b/thirdparty/embree/kernels/common/default.h @@ -0,0 +1,268 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../../common/sys/platform.h" +#include "../../common/sys/sysinfo.h" +#include "../../common/sys/thread.h" +#include "../../common/sys/alloc.h" +#include "../../common/sys/ref.h" +#include "../../common/sys/intrinsics.h" +#include "../../common/sys/atomic.h" +#include "../../common/sys/mutex.h" +#include "../../common/sys/vector.h" +#include "../../common/sys/array.h" +#include "../../common/sys/string.h" +#include "../../common/sys/regression.h" +#include "../../common/sys/vector.h" + +#include "../../common/math/math.h" +#include "../../common/math/transcendental.h" +#include "../../common/simd/simd.h" +#include "../../common/math/vec2.h" +#include "../../common/math/vec3.h" +#include "../../common/math/vec4.h" +#include "../../common/math/vec2fa.h" +#include "../../common/math/vec3fa.h" +#include "../../common/math/interval.h" +#include "../../common/math/bbox.h" +#include "../../common/math/obbox.h" +#include "../../common/math/lbbox.h" +#include "../../common/math/linearspace2.h" +#include "../../common/math/linearspace3.h" +#include "../../common/math/affinespace.h" +#include "../../common/math/range.h" +#include "../../common/lexers/tokenstream.h" + +#include "../../common/tasking/taskscheduler.h" + +#define COMMA , + +#include "../config.h" +#include "isa.h" +#include "stat.h" +#include "profile.h" +#include "rtcore.h" +#include "vector.h" +#include "state.h" +#include "instance_stack.h" + +#include +#include +#include +#include +#include +#include + +namespace embree +{ + //////////////////////////////////////////////////////////////////////////////// + /// Vec2 shortcuts + //////////////////////////////////////////////////////////////////////////////// + + template using Vec2vf = Vec2>; + template using Vec2vd = Vec2>; + template using Vec2vr = Vec2>; + template using Vec2vi = Vec2>; + template using Vec2vl = Vec2>; + template using Vec2vb = Vec2>; + template using Vec2vbf = Vec2>; + template using Vec2vbd = Vec2>; + + typedef Vec2 Vec2vf4; + typedef Vec2 Vec2vd4; + typedef Vec2 Vec2vr4; + typedef Vec2 Vec2vi4; + typedef Vec2 Vec2vl4; + typedef Vec2 Vec2vb4; + typedef Vec2 Vec2vbf4; + typedef Vec2 Vec2vbd4; + + typedef Vec2 Vec2vf8; + typedef Vec2 Vec2vd8; + typedef Vec2 Vec2vr8; + typedef Vec2 Vec2vi8; + typedef Vec2 Vec2vl8; + typedef Vec2 Vec2vb8; + typedef Vec2 Vec2vbf8; + typedef Vec2 Vec2vbd8; + + typedef Vec2 Vec2vf16; + typedef Vec2 Vec2vd16; + typedef Vec2 Vec2vr16; + typedef Vec2 Vec2vi16; + typedef Vec2 Vec2vl16; + typedef Vec2 Vec2vb16; + typedef Vec2 Vec2vbf16; + typedef Vec2 Vec2vbd16; + + typedef Vec2 Vec2vfx; + typedef Vec2 Vec2vdx; + typedef Vec2 Vec2vrx; + typedef Vec2 Vec2vix; + typedef Vec2 Vec2vlx; + typedef Vec2 Vec2vbx; + typedef Vec2 Vec2vbfx; + typedef Vec2 Vec2vbdx; + + //////////////////////////////////////////////////////////////////////////////// + /// Vec3 shortcuts + //////////////////////////////////////////////////////////////////////////////// + + template using Vec3vf = Vec3>; + template using Vec3vd = Vec3>; + template using Vec3vr = Vec3>; + template using Vec3vi = Vec3>; + template using Vec3vl = Vec3>; + template using Vec3vb = Vec3>; + template using Vec3vbf = Vec3>; + template using Vec3vbd = Vec3>; + + typedef Vec3 Vec3vf4; + typedef Vec3 Vec3vd4; + typedef Vec3 Vec3vr4; + typedef Vec3 Vec3vi4; + typedef Vec3 Vec3vl4; + typedef Vec3 Vec3vb4; + typedef Vec3 Vec3vbf4; + typedef Vec3 Vec3vbd4; + + typedef Vec3 Vec3vf8; + typedef Vec3 Vec3vd8; + typedef Vec3 Vec3vr8; + typedef Vec3 Vec3vi8; + typedef Vec3 Vec3vl8; + typedef Vec3 Vec3vb8; + typedef Vec3 Vec3vbf8; + typedef Vec3 Vec3vbd8; + + typedef Vec3 Vec3vf16; + typedef Vec3 Vec3vd16; + typedef Vec3 Vec3vr16; + typedef Vec3 Vec3vi16; + typedef Vec3 Vec3vl16; + typedef Vec3 Vec3vb16; + typedef Vec3 Vec3vbf16; + typedef Vec3 Vec3vbd16; + + typedef Vec3 Vec3vfx; + typedef Vec3 Vec3vdx; + typedef Vec3 Vec3vrx; + typedef Vec3 Vec3vix; + typedef Vec3 Vec3vlx; + typedef Vec3 Vec3vbx; + typedef Vec3 Vec3vbfx; + typedef Vec3 Vec3vbdx; + + //////////////////////////////////////////////////////////////////////////////// + /// Vec4 shortcuts + //////////////////////////////////////////////////////////////////////////////// + + template using Vec4vf = Vec4>; + template using Vec4vd = Vec4>; + template using Vec4vr = Vec4>; + template using Vec4vi = Vec4>; + template using Vec4vl = Vec4>; + template using Vec4vb = Vec4>; + template using Vec4vbf = Vec4>; + template using Vec4vbd = Vec4>; + + typedef Vec4 Vec4vf4; + typedef Vec4 Vec4vd4; + typedef Vec4 Vec4vr4; + typedef Vec4 Vec4vi4; + typedef Vec4 Vec4vl4; + typedef Vec4 Vec4vb4; + typedef Vec4 Vec4vbf4; + typedef Vec4 Vec4vbd4; + + typedef Vec4 Vec4vf8; + typedef Vec4 Vec4vd8; + typedef Vec4 Vec4vr8; + typedef Vec4 Vec4vi8; + typedef Vec4 Vec4vl8; + typedef Vec4 Vec4vb8; + typedef Vec4 Vec4vbf8; + typedef Vec4 Vec4vbd8; + + typedef Vec4 Vec4vf16; + typedef Vec4 Vec4vd16; + typedef Vec4 Vec4vr16; + typedef Vec4 Vec4vi16; + typedef Vec4 Vec4vl16; + typedef Vec4 Vec4vb16; + typedef Vec4 Vec4vbf16; + typedef Vec4 Vec4vbd16; + + typedef Vec4 Vec4vfx; + typedef Vec4 Vec4vdx; + typedef Vec4 Vec4vrx; + typedef Vec4 Vec4vix; + typedef Vec4 Vec4vlx; + typedef Vec4 Vec4vbx; + typedef Vec4 Vec4vbfx; + typedef Vec4 Vec4vbdx; + + //////////////////////////////////////////////////////////////////////////////// + /// Other shortcuts + //////////////////////////////////////////////////////////////////////////////// + + template using BBox3vf = BBox>; + typedef BBox BBox3vf4; + typedef BBox BBox3vf8; + typedef BBox BBox3vf16; + + /* calculate time segment itime and fractional time ftime */ + __forceinline int getTimeSegment(float time, float numTimeSegments, float& ftime) + { + const float timeScaled = time * numTimeSegments; + const float itimef = clamp(floorf(timeScaled), 0.0f, numTimeSegments-1.0f); + ftime = timeScaled - itimef; + return int(itimef); + } + + __forceinline int getTimeSegment(float time, float start_time, float end_time, float numTimeSegments, float& ftime) + { + const float timeScaled = (time-start_time)/(end_time-start_time) * numTimeSegments; + const float itimef = clamp(floorf(timeScaled), 0.0f, numTimeSegments-1.0f); + ftime = timeScaled - itimef; + return int(itimef); + } + + template + __forceinline vint getTimeSegment(const vfloat& time, const vfloat& numTimeSegments, vfloat& ftime) + { + const vfloat timeScaled = time * numTimeSegments; + const vfloat itimef = clamp(floor(timeScaled), vfloat(zero), numTimeSegments-1.0f); + ftime = timeScaled - itimef; + return vint(itimef); + } + + template + __forceinline vint getTimeSegment(const vfloat& time, const vfloat& start_time, const vfloat& end_time, const vfloat& numTimeSegments, vfloat& ftime) + { + const vfloat timeScaled = (time-start_time)/(end_time-start_time) * numTimeSegments; + const vfloat itimef = clamp(floor(timeScaled), vfloat(zero), numTimeSegments-1.0f); + ftime = timeScaled - itimef; + return vint(itimef); + } + + /* calculate overlapping time segment range */ + __forceinline range getTimeSegmentRange(const BBox1f& time_range, float numTimeSegments) + { + const float round_up = 1.0f+2.0f*float(ulp); // corrects inaccuracies to precisely match time step + const float round_down = 1.0f-2.0f*float(ulp); + const int itime_lower = (int)max(floor(round_up *time_range.lower*numTimeSegments), 0.0f); + const int itime_upper = (int)min(ceil (round_down*time_range.upper*numTimeSegments), numTimeSegments); + return make_range(itime_lower, itime_upper); + } + + /* calculate overlapping time segment range */ + __forceinline range getTimeSegmentRange(const BBox1f& range, BBox1f time_range, float numTimeSegments) + { + const float lower = (range.lower-time_range.lower)/time_range.size(); + const float upper = (range.upper-time_range.lower)/time_range.size(); + return getTimeSegmentRange(BBox1f(lower,upper),numTimeSegments); + } +} diff --git a/thirdparty/embree/kernels/common/device.cpp b/thirdparty/embree/kernels/common/device.cpp new file mode 100644 index 000000000000..d8c3d9c748f5 --- /dev/null +++ b/thirdparty/embree/kernels/common/device.cpp @@ -0,0 +1,560 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "device.h" +#include "../hash.h" +#include "scene_triangle_mesh.h" +#include "scene_user_geometry.h" +#include "scene_instance.h" +#include "scene_curves.h" +#include "scene_subdiv_mesh.h" + +#include "../subdiv/tessellation_cache.h" + +#include "acceln.h" +#include "geometry.h" + +#include "../geometry/cylinder.h" + +#include "../bvh/bvh4_factory.h" +#include "../bvh/bvh8_factory.h" + +#include "../../common/tasking/taskscheduler.h" +#include "../../common/sys/alloc.h" + +namespace embree +{ + /*! some global variables that can be set via rtcSetParameter1i for debugging purposes */ + ssize_t Device::debug_int0 = 0; + ssize_t Device::debug_int1 = 0; + ssize_t Device::debug_int2 = 0; + ssize_t Device::debug_int3 = 0; + + DECLARE_SYMBOL2(RayStreamFilterFuncs,rayStreamFilterFuncs); + + static MutexSys g_mutex; + static std::map g_cache_size_map; + static std::map g_num_threads_map; + + Device::Device (const char* cfg) + { + /* check that CPU supports lowest ISA */ + if (!hasISA(ISA)) { + throw_RTCError(RTC_ERROR_UNSUPPORTED_CPU,"CPU does not support " ISA_STR); + } + + /* set default frequency level for detected CPU */ + switch (getCPUModel()) { + case CPU::UNKNOWN: frequency_level = FREQUENCY_SIMD256; break; + case CPU::XEON_ICE_LAKE: frequency_level = FREQUENCY_SIMD256; break; + case CPU::CORE_ICE_LAKE: frequency_level = FREQUENCY_SIMD256; break; + case CPU::CORE_TIGER_LAKE: frequency_level = FREQUENCY_SIMD128; break; + case CPU::CORE_COMET_LAKE: frequency_level = FREQUENCY_SIMD128; break; + case CPU::CORE_CANNON_LAKE:frequency_level = FREQUENCY_SIMD128; break; + case CPU::CORE_KABY_LAKE: frequency_level = FREQUENCY_SIMD128; break; + case CPU::XEON_SKY_LAKE: frequency_level = FREQUENCY_SIMD128; break; + case CPU::CORE_SKY_LAKE: frequency_level = FREQUENCY_SIMD128; break; + case CPU::XEON_BROADWELL: frequency_level = FREQUENCY_SIMD256; break; + case CPU::CORE_BROADWELL: frequency_level = FREQUENCY_SIMD256; break; + case CPU::XEON_HASWELL: frequency_level = FREQUENCY_SIMD256; break; + case CPU::CORE_HASWELL: frequency_level = FREQUENCY_SIMD256; break; + case CPU::XEON_IVY_BRIDGE: frequency_level = FREQUENCY_SIMD256; break; + case CPU::CORE_IVY_BRIDGE: frequency_level = FREQUENCY_SIMD256; break; + case CPU::SANDY_BRIDGE: frequency_level = FREQUENCY_SIMD256; break; + case CPU::NEHALEM: frequency_level = FREQUENCY_SIMD128; break; + case CPU::CORE2: frequency_level = FREQUENCY_SIMD128; break; + case CPU::CORE1: frequency_level = FREQUENCY_SIMD128; break; + } + + /* initialize global state */ +#if defined(EMBREE_CONFIG) + State::parseString(EMBREE_CONFIG); +#endif + State::parseString(cfg); + if (!ignore_config_files && FileName::executableFolder() != FileName("")) + State::parseFile(FileName::executableFolder()+FileName(".embree" TOSTRING(RTC_VERSION_MAJOR))); + if (!ignore_config_files && FileName::homeFolder() != FileName("")) + State::parseFile(FileName::homeFolder()+FileName(".embree" TOSTRING(RTC_VERSION_MAJOR))); + State::verify(); + + /* check whether selected ISA is supported by the HW, as the user could have forced an unsupported ISA */ + if (!checkISASupport()) { + throw_RTCError(RTC_ERROR_UNSUPPORTED_CPU,"CPU does not support selected ISA"); + } + + /*! do some internal tests */ + assert(isa::Cylinder::verify()); + + /*! enable huge page support if desired */ +#if defined(__WIN32__) + if (State::enable_selockmemoryprivilege) + State::hugepages_success &= win_enable_selockmemoryprivilege(State::verbosity(3)); +#endif + State::hugepages_success &= os_init(State::hugepages,State::verbosity(3)); + + /*! set tessellation cache size */ + setCacheSize( State::tessellation_cache_size ); + + /*! enable some floating point exceptions to catch bugs */ + if (State::float_exceptions) + { + int exceptions = _MM_MASK_MASK; + //exceptions &= ~_MM_MASK_INVALID; + exceptions &= ~_MM_MASK_DENORM; + exceptions &= ~_MM_MASK_DIV_ZERO; + //exceptions &= ~_MM_MASK_OVERFLOW; + //exceptions &= ~_MM_MASK_UNDERFLOW; + //exceptions &= ~_MM_MASK_INEXACT; + _MM_SET_EXCEPTION_MASK(exceptions); + } + + /* print info header */ + if (State::verbosity(1)) + print(); + if (State::verbosity(2)) + State::print(); + + /* register all algorithms */ + bvh4_factory = make_unique(new BVH4Factory(enabled_builder_cpu_features, enabled_cpu_features)); + +#if defined(EMBREE_TARGET_SIMD8) + bvh8_factory = make_unique(new BVH8Factory(enabled_builder_cpu_features, enabled_cpu_features)); +#endif + + /* setup tasking system */ + initTaskingSystem(numThreads); + + /* ray stream SOA to AOS conversion */ +#if defined(EMBREE_RAY_PACKETS) + RayStreamFilterFuncsType rayStreamFilterFuncs; + SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(enabled_cpu_features,rayStreamFilterFuncs); + rayStreamFilters = rayStreamFilterFuncs(); +#endif + } + + Device::~Device () + { + setCacheSize(0); + exitTaskingSystem(); + } + + std::string getEnabledTargets() + { + std::string v; +#if defined(EMBREE_TARGET_SSE2) + v += "SSE2 "; +#endif +#if defined(EMBREE_TARGET_SSE42) + v += "SSE4.2 "; +#endif +#if defined(EMBREE_TARGET_AVX) + v += "AVX "; +#endif +#if defined(EMBREE_TARGET_AVX2) + v += "AVX2 "; +#endif +#if defined(EMBREE_TARGET_AVX512KNL) + v += "AVX512KNL "; +#endif +#if defined(EMBREE_TARGET_AVX512SKX) + v += "AVX512SKX "; +#endif + return v; + } + + std::string getEmbreeFeatures() + { + std::string v; +#if defined(EMBREE_RAY_MASK) + v += "raymasks "; +#endif +#if defined (EMBREE_BACKFACE_CULLING) + v += "backfaceculling "; +#endif +#if defined (EMBREE_BACKFACE_CULLING_CURVES) + v += "backfacecullingcurves "; +#endif +#if defined(EMBREE_FILTER_FUNCTION) + v += "intersection_filter "; +#endif +#if defined (EMBREE_COMPACT_POLYS) + v += "compact_polys "; +#endif + return v; + } + + void Device::print() + { + const int cpu_features = getCPUFeatures(); + std::cout << std::endl; + std::cout << "Embree Ray Tracing Kernels " << RTC_VERSION_STRING << " (" << RTC_HASH << ")" << std::endl; + std::cout << " Compiler : " << getCompilerName() << std::endl; + std::cout << " Build : "; +#if defined(DEBUG) + std::cout << "Debug " << std::endl; +#else + std::cout << "Release " << std::endl; +#endif + std::cout << " Platform : " << getPlatformName() << std::endl; + std::cout << " CPU : " << stringOfCPUModel(getCPUModel()) << " (" << getCPUVendor() << ")" << std::endl; + std::cout << " Threads : " << getNumberOfLogicalThreads() << std::endl; + std::cout << " ISA : " << stringOfCPUFeatures(cpu_features) << std::endl; + std::cout << " Targets : " << supportedTargetList(cpu_features) << std::endl; + const bool hasFTZ = _mm_getcsr() & _MM_FLUSH_ZERO_ON; + const bool hasDAZ = _mm_getcsr() & _MM_DENORMALS_ZERO_ON; + std::cout << " MXCSR : " << "FTZ=" << hasFTZ << ", DAZ=" << hasDAZ << std::endl; + std::cout << " Config" << std::endl; + std::cout << " Threads : " << (numThreads ? toString(numThreads) : std::string("default")) << std::endl; + std::cout << " ISA : " << stringOfCPUFeatures(enabled_cpu_features) << std::endl; + std::cout << " Targets : " << supportedTargetList(enabled_cpu_features) << " (supported)" << std::endl; + std::cout << " " << getEnabledTargets() << " (compile time enabled)" << std::endl; + std::cout << " Features: " << getEmbreeFeatures() << std::endl; + std::cout << " Tasking : "; +#if defined(TASKING_TBB) + std::cout << "TBB" << TBB_VERSION_MAJOR << "." << TBB_VERSION_MINOR << " "; + #if TBB_INTERFACE_VERSION >= 12002 + std::cout << "TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << TBB_runtime_interface_version() << " "; + #else + std::cout << "TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << tbb::TBB_runtime_interface_version() << " "; + #endif +#endif +#if defined(TASKING_INTERNAL) + std::cout << "internal_tasking_system "; +#endif +#if defined(TASKING_PPL) + std::cout << "PPL "; +#endif + std::cout << std::endl; + + /* check of FTZ and DAZ flags are set in CSR */ + if (!hasFTZ || !hasDAZ) + { +#if !defined(_DEBUG) + if (State::verbosity(1)) +#endif + { + std::cout << std::endl; + std::cout << "================================================================================" << std::endl; + std::cout << " WARNING: \"Flush to Zero\" or \"Denormals are Zero\" mode not enabled " << std::endl + << " in the MXCSR control and status register. This can have a severe " << std::endl + << " performance impact. Please enable these modes for each application " << std::endl + << " thread the following way:" << std::endl + << std::endl + << " #include \"xmmintrin.h\"" << std::endl + << " #include \"pmmintrin.h\"" << std::endl + << std::endl + << " _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);" << std::endl + << " _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);" << std::endl; + std::cout << "================================================================================" << std::endl; + std::cout << std::endl; + } + } + std::cout << std::endl; + } + + void Device::setDeviceErrorCode(RTCError error) + { + RTCError* stored_error = errorHandler.error(); + if (*stored_error == RTC_ERROR_NONE) + *stored_error = error; + } + + RTCError Device::getDeviceErrorCode() + { + RTCError* stored_error = errorHandler.error(); + RTCError error = *stored_error; + *stored_error = RTC_ERROR_NONE; + return error; + } + + void Device::setThreadErrorCode(RTCError error) + { + RTCError* stored_error = g_errorHandler.error(); + if (*stored_error == RTC_ERROR_NONE) + *stored_error = error; + } + + RTCError Device::getThreadErrorCode() + { + RTCError* stored_error = g_errorHandler.error(); + RTCError error = *stored_error; + *stored_error = RTC_ERROR_NONE; + return error; + } + + void Device::process_error(Device* device, RTCError error, const char* str) + { + /* store global error code when device construction failed */ + if (!device) + return setThreadErrorCode(error); + + /* print error when in verbose mode */ + if (device->verbosity(1)) + { + switch (error) { + case RTC_ERROR_NONE : std::cerr << "Embree: No error"; break; + case RTC_ERROR_UNKNOWN : std::cerr << "Embree: Unknown error"; break; + case RTC_ERROR_INVALID_ARGUMENT : std::cerr << "Embree: Invalid argument"; break; + case RTC_ERROR_INVALID_OPERATION: std::cerr << "Embree: Invalid operation"; break; + case RTC_ERROR_OUT_OF_MEMORY : std::cerr << "Embree: Out of memory"; break; + case RTC_ERROR_UNSUPPORTED_CPU : std::cerr << "Embree: Unsupported CPU"; break; + default : std::cerr << "Embree: Invalid error code"; break; + }; + if (str) std::cerr << ", (" << str << ")"; + std::cerr << std::endl; + } + + /* call user specified error callback */ + if (device->error_function) + device->error_function(device->error_function_userptr,error,str); + + /* record error code */ + device->setDeviceErrorCode(error); + } + + void Device::memoryMonitor(ssize_t bytes, bool post) + { + if (State::memory_monitor_function && bytes != 0) { + if (!State::memory_monitor_function(State::memory_monitor_userptr,bytes,post)) { + if (bytes > 0) { // only throw exception when we allocate memory to never throw inside a destructor + throw_RTCError(RTC_ERROR_OUT_OF_MEMORY,"memory monitor forced termination"); + } + } + } + } + + size_t getMaxNumThreads() + { + size_t maxNumThreads = 0; + for (std::map::iterator i=g_num_threads_map.begin(); i != g_num_threads_map.end(); i++) + maxNumThreads = max(maxNumThreads, (*i).second); + if (maxNumThreads == 0) + maxNumThreads = std::numeric_limits::max(); + return maxNumThreads; + } + + size_t getMaxCacheSize() + { + size_t maxCacheSize = 0; + for (std::map::iterator i=g_cache_size_map.begin(); i!= g_cache_size_map.end(); i++) + maxCacheSize = max(maxCacheSize, (*i).second); + return maxCacheSize; + } + + void Device::setCacheSize(size_t bytes) + { +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + Lock lock(g_mutex); + if (bytes == 0) g_cache_size_map.erase(this); + else g_cache_size_map[this] = bytes; + + size_t maxCacheSize = getMaxCacheSize(); + resizeTessellationCache(maxCacheSize); +#endif + } + + void Device::initTaskingSystem(size_t numThreads) + { + Lock lock(g_mutex); + if (numThreads == 0) + g_num_threads_map[this] = std::numeric_limits::max(); + else + g_num_threads_map[this] = numThreads; + + /* create task scheduler */ + size_t maxNumThreads = getMaxNumThreads(); + TaskScheduler::create(maxNumThreads,State::set_affinity,State::start_threads); +#if USE_TASK_ARENA + const size_t nThreads = min(maxNumThreads,TaskScheduler::threadCount()); + const size_t uThreads = min(max(numUserThreads,(size_t)1),nThreads); + arena = make_unique(new tbb::task_arena((int)nThreads,(unsigned int)uThreads)); +#endif + } + + void Device::exitTaskingSystem() + { + Lock lock(g_mutex); + g_num_threads_map.erase(this); + + /* terminate tasking system */ + if (g_num_threads_map.size() == 0) { + TaskScheduler::destroy(); + } + /* or configure new number of threads */ + else { + size_t maxNumThreads = getMaxNumThreads(); + TaskScheduler::create(maxNumThreads,State::set_affinity,State::start_threads); + } +#if USE_TASK_ARENA + arena.reset(); +#endif + } + + void Device::setProperty(const RTCDeviceProperty prop, ssize_t val) + { + /* hidden internal properties */ + switch ((size_t)prop) + { + case 1000000: debug_int0 = val; return; + case 1000001: debug_int1 = val; return; + case 1000002: debug_int2 = val; return; + case 1000003: debug_int3 = val; return; + } + + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown writable property"); + } + + ssize_t Device::getProperty(const RTCDeviceProperty prop) + { + size_t iprop = (size_t)prop; + + /* get name of internal regression test */ + if (iprop >= 2000000 && iprop < 3000000) + { + RegressionTest* test = getRegressionTest(iprop-2000000); + if (test) return (ssize_t) test->name.c_str(); + else return 0; + } + + /* run internal regression test */ + if (iprop >= 3000000 && iprop < 4000000) + { + RegressionTest* test = getRegressionTest(iprop-3000000); + if (test) return test->run(); + else return 0; + } + + /* documented properties */ + switch (prop) + { + case RTC_DEVICE_PROPERTY_VERSION_MAJOR: return RTC_VERSION_MAJOR; + case RTC_DEVICE_PROPERTY_VERSION_MINOR: return RTC_VERSION_MINOR; + case RTC_DEVICE_PROPERTY_VERSION_PATCH: return RTC_VERSION_PATCH; + case RTC_DEVICE_PROPERTY_VERSION : return RTC_VERSION; + +#if defined(EMBREE_TARGET_SIMD4) && defined(EMBREE_RAY_PACKETS) + case RTC_DEVICE_PROPERTY_NATIVE_RAY4_SUPPORTED: return hasISA(SSE2); +#else + case RTC_DEVICE_PROPERTY_NATIVE_RAY4_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_TARGET_SIMD8) && defined(EMBREE_RAY_PACKETS) + case RTC_DEVICE_PROPERTY_NATIVE_RAY8_SUPPORTED: return hasISA(AVX); +#else + case RTC_DEVICE_PROPERTY_NATIVE_RAY8_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_TARGET_SIMD16) && defined(EMBREE_RAY_PACKETS) + case RTC_DEVICE_PROPERTY_NATIVE_RAY16_SUPPORTED: return hasISA(AVX512KNL) | hasISA(AVX512SKX); +#else + case RTC_DEVICE_PROPERTY_NATIVE_RAY16_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_RAY_PACKETS) + case RTC_DEVICE_PROPERTY_RAY_STREAM_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_RAY_STREAM_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_RAY_MASK) + case RTC_DEVICE_PROPERTY_RAY_MASK_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_RAY_MASK_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_BACKFACE_CULLING) + case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_ENABLED: return 1; +#else + case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_ENABLED: return 0; +#endif + +#if defined(EMBREE_BACKFACE_CULLING_CURVES) + case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_CURVES_ENABLED: return 1; +#else + case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_CURVES_ENABLED: return 0; +#endif + +#if defined(EMBREE_COMPACT_POLYS) + case RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED: return 1; +#else + case RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED: return 0; +#endif + +#if defined(EMBREE_FILTER_FUNCTION) + case RTC_DEVICE_PROPERTY_FILTER_FUNCTION_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_FILTER_FUNCTION_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_IGNORE_INVALID_RAYS) + case RTC_DEVICE_PROPERTY_IGNORE_INVALID_RAYS_ENABLED: return 1; +#else + case RTC_DEVICE_PROPERTY_IGNORE_INVALID_RAYS_ENABLED: return 0; +#endif + +#if defined(TASKING_INTERNAL) + case RTC_DEVICE_PROPERTY_TASKING_SYSTEM: return 0; +#endif + +#if defined(TASKING_TBB) + case RTC_DEVICE_PROPERTY_TASKING_SYSTEM: return 1; +#endif + +#if defined(TASKING_PPL) + case RTC_DEVICE_PROPERTY_TASKING_SYSTEM: return 2; +#endif + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + case RTC_DEVICE_PROPERTY_TRIANGLE_GEOMETRY_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_TRIANGLE_GEOMETRY_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + case RTC_DEVICE_PROPERTY_QUAD_GEOMETRY_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_QUAD_GEOMETRY_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) + case RTC_DEVICE_PROPERTY_CURVE_GEOMETRY_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_CURVE_GEOMETRY_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + case RTC_DEVICE_PROPERTY_SUBDIVISION_GEOMETRY_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_SUBDIVISION_GEOMETRY_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_GEOMETRY_USER) + case RTC_DEVICE_PROPERTY_USER_GEOMETRY_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_USER_GEOMETRY_SUPPORTED: return 0; +#endif + +#if defined(EMBREE_GEOMETRY_POINT) + case RTC_DEVICE_PROPERTY_POINT_GEOMETRY_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_POINT_GEOMETRY_SUPPORTED: return 0; +#endif + +#if defined(TASKING_PPL) + case RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED: return 0; +#elif defined(TASKING_TBB) && (TBB_INTERFACE_VERSION_MAJOR < 8) + case RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED: return 0; +#else + case RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED: return 1; +#endif + +#if defined(TASKING_TBB) && TASKING_TBB_USE_TASK_ISOLATION + case RTC_DEVICE_PROPERTY_PARALLEL_COMMIT_SUPPORTED: return 1; +#else + case RTC_DEVICE_PROPERTY_PARALLEL_COMMIT_SUPPORTED: return 0; +#endif + + default: throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown readable property"); break; + }; + } +} diff --git a/thirdparty/embree/kernels/common/device.h b/thirdparty/embree/kernels/common/device.h new file mode 100644 index 000000000000..e9a81bb1091d --- /dev/null +++ b/thirdparty/embree/kernels/common/device.h @@ -0,0 +1,85 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "state.h" +#include "accel.h" + +namespace embree +{ + class BVH4Factory; + class BVH8Factory; + + class Device : public State, public MemoryMonitorInterface + { + ALIGNED_CLASS_(16); + + public: + + /*! Device construction */ + Device (const char* cfg); + + /*! Device destruction */ + virtual ~Device (); + + /*! prints info about the device */ + void print(); + + /*! sets the error code */ + void setDeviceErrorCode(RTCError error); + + /*! returns and clears the error code */ + RTCError getDeviceErrorCode(); + + /*! sets the error code */ + static void setThreadErrorCode(RTCError error); + + /*! returns and clears the error code */ + static RTCError getThreadErrorCode(); + + /*! processes error codes, do not call directly */ + static void process_error(Device* device, RTCError error, const char* str); + + /*! invokes the memory monitor callback */ + void memoryMonitor(ssize_t bytes, bool post); + + /*! sets the size of the software cache. */ + void setCacheSize(size_t bytes); + + /*! sets a property */ + void setProperty(const RTCDeviceProperty prop, ssize_t val); + + /*! gets a property */ + ssize_t getProperty(const RTCDeviceProperty prop); + + private: + + /*! initializes the tasking system */ + void initTaskingSystem(size_t numThreads); + + /*! shuts down the tasking system */ + void exitTaskingSystem(); + + /*! some variables that can be set via rtcSetParameter1i for debugging purposes */ + public: + static ssize_t debug_int0; + static ssize_t debug_int1; + static ssize_t debug_int2; + static ssize_t debug_int3; + + public: + std::unique_ptr bvh4_factory; +#if defined(EMBREE_TARGET_SIMD8) + std::unique_ptr bvh8_factory; +#endif + +#if USE_TASK_ARENA + std::unique_ptr arena; +#endif + + /* ray streams filter */ + RayStreamFilterFuncs rayStreamFilters; + }; +} diff --git a/thirdparty/embree/kernels/common/geometry.cpp b/thirdparty/embree/kernels/common/geometry.cpp new file mode 100644 index 000000000000..b3aa8e33960d --- /dev/null +++ b/thirdparty/embree/kernels/common/geometry.cpp @@ -0,0 +1,259 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "geometry.h" +#include "scene.h" + +namespace embree +{ + const char* Geometry::gtype_names[Geometry::GTY_END] = + { + "flat_linear_curve", + "round_linear_curve", + "oriented_linear_curve", + "", + "flat_bezier_curve", + "round_bezier_curve", + "oriented_bezier_curve", + "", + "flat_bspline_curve", + "round_bspline_curve", + "oriented_bspline_curve", + "", + "flat_hermite_curve", + "round_hermite_curve", + "oriented_hermite_curve", + "", + "flat_catmull_rom_curve", + "round_catmull_rom_curve", + "oriented_catmull_rom_curve", + "", + "triangles", + "quads", + "grid", + "subdivs", + "", + "sphere", + "disc", + "oriented_disc", + "", + "usergeom", + "instance_cheap", + "instance_expensive", + }; + + Geometry::Geometry (Device* device, GType gtype, unsigned int numPrimitives, unsigned int numTimeSteps) + : device(device), userPtr(nullptr), + numPrimitives(numPrimitives), numTimeSteps(unsigned(numTimeSteps)), fnumTimeSegments(float(numTimeSteps-1)), time_range(0.0f,1.0f), + mask(-1), + gtype(gtype), + gsubtype(GTY_SUBTYPE_DEFAULT), + quality(RTC_BUILD_QUALITY_MEDIUM), + state((unsigned)State::MODIFIED), + enabled(true), + intersectionFilterN(nullptr), occlusionFilterN(nullptr), pointQueryFunc(nullptr) + { + device->refInc(); + } + + Geometry::~Geometry() + { + device->refDec(); + } + + void Geometry::setNumPrimitives(unsigned int numPrimitives_in) + { + if (numPrimitives_in == numPrimitives) return; + + numPrimitives = numPrimitives_in; + + Geometry::update(); + } + + void Geometry::setNumTimeSteps (unsigned int numTimeSteps_in) + { + if (numTimeSteps_in == numTimeSteps) { + return; + } + + numTimeSteps = numTimeSteps_in; + fnumTimeSegments = float(numTimeSteps_in-1); + + Geometry::update(); + } + + void Geometry::setTimeRange (const BBox1f range) + { + time_range = range; + Geometry::update(); + } + + void Geometry::update() + { + ++modCounter_; // FIXME: required? + state = (unsigned)State::MODIFIED; + } + + void Geometry::commit() + { + ++modCounter_; + state = (unsigned)State::COMMITTED; + } + + void Geometry::preCommit() + { + if (State::MODIFIED == (State)state) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"geometry not committed"); + } + + void Geometry::postCommit() + { + } + + void Geometry::enable () + { + if (isEnabled()) + return; + + enabled = true; + ++modCounter_; + } + + void Geometry::disable () + { + if (isDisabled()) + return; + + enabled = false; + ++modCounter_; + } + + void Geometry::setUserData (void* ptr) + { + userPtr = ptr; + } + + void Geometry::setIntersectionFilterFunctionN (RTCFilterFunctionN filter) + { + if (!(getTypeMask() & (MTY_TRIANGLE_MESH | MTY_QUAD_MESH | MTY_CURVES | MTY_SUBDIV_MESH | MTY_USER_GEOMETRY | MTY_GRID_MESH))) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"filter functions not supported for this geometry"); + + intersectionFilterN = filter; + } + + void Geometry::setOcclusionFilterFunctionN (RTCFilterFunctionN filter) + { + if (!(getTypeMask() & (MTY_TRIANGLE_MESH | MTY_QUAD_MESH | MTY_CURVES | MTY_SUBDIV_MESH | MTY_USER_GEOMETRY | MTY_GRID_MESH))) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"filter functions not supported for this geometry"); + + occlusionFilterN = filter; + } + + void Geometry::setPointQueryFunction (RTCPointQueryFunction func) + { + pointQueryFunc = func; + } + + void Geometry::interpolateN(const RTCInterpolateNArguments* const args) + { + const void* valid_i = args->valid; + const unsigned* primIDs = args->primIDs; + const float* u = args->u; + const float* v = args->v; + unsigned int N = args->N; + RTCBufferType bufferType = args->bufferType; + unsigned int bufferSlot = args->bufferSlot; + float* P = args->P; + float* dPdu = args->dPdu; + float* dPdv = args->dPdv; + float* ddPdudu = args->ddPdudu; + float* ddPdvdv = args->ddPdvdv; + float* ddPdudv = args->ddPdudv; + unsigned int valueCount = args->valueCount; + + if (valueCount > 256) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"maximally 256 floating point values can be interpolated per vertex"); + const int* valid = (const int*) valid_i; + + __aligned(64) float P_tmp[256]; + __aligned(64) float dPdu_tmp[256]; + __aligned(64) float dPdv_tmp[256]; + __aligned(64) float ddPdudu_tmp[256]; + __aligned(64) float ddPdvdv_tmp[256]; + __aligned(64) float ddPdudv_tmp[256]; + + float* Pt = P ? P_tmp : nullptr; + float* dPdut = nullptr, *dPdvt = nullptr; + if (dPdu) { dPdut = dPdu_tmp; dPdvt = dPdv_tmp; } + float* ddPdudut = nullptr, *ddPdvdvt = nullptr, *ddPdudvt = nullptr; + if (ddPdudu) { ddPdudut = ddPdudu_tmp; ddPdvdvt = ddPdvdv_tmp; ddPdudvt = ddPdudv_tmp; } + + for (unsigned int i=0; iprimID < size()); + + RTCPointQueryFunctionArguments args; + args.query = (RTCPointQuery*)context->query_ws; + args.userPtr = context->userPtr; + args.primID = context->primID; + args.geomID = context->geomID; + args.context = context->userContext; + args.similarityScale = context->similarityScale; + + bool update = false; + if(context->func) update |= context->func(&args); + if(pointQueryFunc) update |= pointQueryFunc(&args); + + if (update && context->userContext->instStackSize > 0) + { + // update point query + if (context->query_type == POINT_QUERY_TYPE_AABB) { + context->updateAABB(); + } else { + assert(context->similarityScale > 0.f); + query->radius = context->query_ws->radius * context->similarityScale; + } + } + return update; + } +} diff --git a/thirdparty/embree/kernels/common/geometry.h b/thirdparty/embree/kernels/common/geometry.h new file mode 100644 index 000000000000..953974bfd29c --- /dev/null +++ b/thirdparty/embree/kernels/common/geometry.h @@ -0,0 +1,582 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "device.h" +#include "buffer.h" +#include "../common/point_query.h" +#include "../builders/priminfo.h" + +namespace embree +{ + class Scene; + class Geometry; + + struct GeometryCounts + { + __forceinline GeometryCounts() + : numFilterFunctions(0), + numTriangles(0), numMBTriangles(0), + numQuads(0), numMBQuads(0), + numBezierCurves(0), numMBBezierCurves(0), + numLineSegments(0), numMBLineSegments(0), + numSubdivPatches(0), numMBSubdivPatches(0), + numUserGeometries(0), numMBUserGeometries(0), + numInstancesCheap(0), numMBInstancesCheap(0), + numInstancesExpensive(0), numMBInstancesExpensive(0), + numGrids(0), numMBGrids(0), + numPoints(0), numMBPoints(0) {} + + __forceinline size_t size() const { + return numTriangles + numQuads + numBezierCurves + numLineSegments + numSubdivPatches + numUserGeometries + numInstancesCheap + numInstancesExpensive + numGrids + numPoints + + numMBTriangles + numMBQuads + numMBBezierCurves + numMBLineSegments + numMBSubdivPatches + numMBUserGeometries + numMBInstancesCheap + numMBInstancesExpensive + numMBGrids + numMBPoints; + } + + __forceinline unsigned int enabledGeometryTypesMask() const + { + unsigned int mask = 0; + if (numTriangles) mask |= 1 << 0; + if (numQuads) mask |= 1 << 1; + if (numBezierCurves+numLineSegments) mask |= 1 << 2; + if (numSubdivPatches) mask |= 1 << 3; + if (numUserGeometries) mask |= 1 << 4; + if (numInstancesCheap) mask |= 1 << 5; + if (numInstancesExpensive) mask |= 1 << 6; + if (numGrids) mask |= 1 << 7; + if (numPoints) mask |= 1 << 8; + + unsigned int maskMB = 0; + if (numMBTriangles) maskMB |= 1 << 0; + if (numMBQuads) maskMB |= 1 << 1; + if (numMBBezierCurves+numMBLineSegments) maskMB |= 1 << 2; + if (numMBSubdivPatches) maskMB |= 1 << 3; + if (numMBUserGeometries) maskMB |= 1 << 4; + if (numMBInstancesCheap) maskMB |= 1 << 5; + if (numMBInstancesExpensive) maskMB |= 1 << 6; + if (numMBGrids) maskMB |= 1 << 7; + if (numMBPoints) maskMB |= 1 << 8; + + return (mask<<8) + maskMB; + } + + __forceinline GeometryCounts operator+ (GeometryCounts const & rhs) const + { + GeometryCounts ret; + ret.numFilterFunctions = numFilterFunctions + rhs.numFilterFunctions; + ret.numTriangles = numTriangles + rhs.numTriangles; + ret.numMBTriangles = numMBTriangles + rhs.numMBTriangles; + ret.numQuads = numQuads + rhs.numQuads; + ret.numMBQuads = numMBQuads + rhs.numMBQuads; + ret.numBezierCurves = numBezierCurves + rhs.numBezierCurves; + ret.numMBBezierCurves = numMBBezierCurves + rhs.numMBBezierCurves; + ret.numLineSegments = numLineSegments + rhs.numLineSegments; + ret.numMBLineSegments = numMBLineSegments + rhs.numMBLineSegments; + ret.numSubdivPatches = numSubdivPatches + rhs.numSubdivPatches; + ret.numMBSubdivPatches = numMBSubdivPatches + rhs.numMBSubdivPatches; + ret.numUserGeometries = numUserGeometries + rhs.numUserGeometries; + ret.numMBUserGeometries = numMBUserGeometries + rhs.numMBUserGeometries; + ret.numInstancesCheap = numInstancesCheap + rhs.numInstancesCheap; + ret.numMBInstancesCheap = numMBInstancesCheap + rhs.numMBInstancesCheap; + ret.numInstancesExpensive = numInstancesExpensive + rhs.numInstancesExpensive; + ret.numMBInstancesExpensive = numMBInstancesExpensive + rhs.numMBInstancesExpensive; + ret.numGrids = numGrids + rhs.numGrids; + ret.numMBGrids = numMBGrids + rhs.numMBGrids; + ret.numPoints = numPoints + rhs.numPoints; + ret.numMBPoints = numMBPoints + rhs.numMBPoints; + + return ret; + } + + size_t numFilterFunctions; //!< number of geometries with filter functions enabled + size_t numTriangles; //!< number of enabled triangles + size_t numMBTriangles; //!< number of enabled motion blured triangles + size_t numQuads; //!< number of enabled quads + size_t numMBQuads; //!< number of enabled motion blurred quads + size_t numBezierCurves; //!< number of enabled curves + size_t numMBBezierCurves; //!< number of enabled motion blurred curves + size_t numLineSegments; //!< number of enabled line segments + size_t numMBLineSegments; //!< number of enabled line motion blurred segments + size_t numSubdivPatches; //!< number of enabled subdivision patches + size_t numMBSubdivPatches; //!< number of enabled motion blured subdivision patches + size_t numUserGeometries; //!< number of enabled user geometries + size_t numMBUserGeometries; //!< number of enabled motion blurred user geometries + size_t numInstancesCheap; //!< number of enabled cheap instances + size_t numMBInstancesCheap; //!< number of enabled motion blurred cheap instances + size_t numInstancesExpensive; //!< number of enabled expensive instances + size_t numMBInstancesExpensive; //!< number of enabled motion blurred expensive instances + size_t numGrids; //!< number of enabled grid geometries + size_t numMBGrids; //!< number of enabled motion blurred grid geometries + size_t numPoints; //!< number of enabled points + size_t numMBPoints; //!< number of enabled motion blurred points + }; + + /*! Base class all geometries are derived from */ + class Geometry : public RefCount + { + friend class Scene; + public: + + /*! type of geometry */ + enum GType + { + GTY_FLAT_LINEAR_CURVE = 0, + GTY_ROUND_LINEAR_CURVE = 1, + GTY_ORIENTED_LINEAR_CURVE = 2, + GTY_CONE_LINEAR_CURVE = 3, + + GTY_FLAT_BEZIER_CURVE = 4, + GTY_ROUND_BEZIER_CURVE = 5, + GTY_ORIENTED_BEZIER_CURVE = 6, + + GTY_FLAT_BSPLINE_CURVE = 8, + GTY_ROUND_BSPLINE_CURVE = 9, + GTY_ORIENTED_BSPLINE_CURVE = 10, + + GTY_FLAT_HERMITE_CURVE = 12, + GTY_ROUND_HERMITE_CURVE = 13, + GTY_ORIENTED_HERMITE_CURVE = 14, + + GTY_FLAT_CATMULL_ROM_CURVE = 16, + GTY_ROUND_CATMULL_ROM_CURVE = 17, + GTY_ORIENTED_CATMULL_ROM_CURVE = 18, + + GTY_TRIANGLE_MESH = 20, + GTY_QUAD_MESH = 21, + GTY_GRID_MESH = 22, + GTY_SUBDIV_MESH = 23, + + GTY_SPHERE_POINT = 25, + GTY_DISC_POINT = 26, + GTY_ORIENTED_DISC_POINT = 27, + + GTY_USER_GEOMETRY = 29, + GTY_INSTANCE_CHEAP = 30, + GTY_INSTANCE_EXPENSIVE = 31, + GTY_END = 32, + + GTY_BASIS_LINEAR = 0, + GTY_BASIS_BEZIER = 4, + GTY_BASIS_BSPLINE = 8, + GTY_BASIS_HERMITE = 12, + GTY_BASIS_CATMULL_ROM = 16, + GTY_BASIS_MASK = 28, + + GTY_SUBTYPE_FLAT_CURVE = 0, + GTY_SUBTYPE_ROUND_CURVE = 1, + GTY_SUBTYPE_ORIENTED_CURVE = 2, + GTY_SUBTYPE_MASK = 3, + }; + + enum GSubType + { + GTY_SUBTYPE_DEFAULT= 0, + GTY_SUBTYPE_INSTANCE_LINEAR = 0, + GTY_SUBTYPE_INSTANCE_QUATERNION = 1 + }; + + enum GTypeMask + { + MTY_FLAT_LINEAR_CURVE = 1ul << GTY_FLAT_LINEAR_CURVE, + MTY_ROUND_LINEAR_CURVE = 1ul << GTY_ROUND_LINEAR_CURVE, + MTY_CONE_LINEAR_CURVE = 1ul << GTY_CONE_LINEAR_CURVE, + MTY_ORIENTED_LINEAR_CURVE = 1ul << GTY_ORIENTED_LINEAR_CURVE, + + MTY_FLAT_BEZIER_CURVE = 1ul << GTY_FLAT_BEZIER_CURVE, + MTY_ROUND_BEZIER_CURVE = 1ul << GTY_ROUND_BEZIER_CURVE, + MTY_ORIENTED_BEZIER_CURVE = 1ul << GTY_ORIENTED_BEZIER_CURVE, + + MTY_FLAT_BSPLINE_CURVE = 1ul << GTY_FLAT_BSPLINE_CURVE, + MTY_ROUND_BSPLINE_CURVE = 1ul << GTY_ROUND_BSPLINE_CURVE, + MTY_ORIENTED_BSPLINE_CURVE = 1ul << GTY_ORIENTED_BSPLINE_CURVE, + + MTY_FLAT_HERMITE_CURVE = 1ul << GTY_FLAT_HERMITE_CURVE, + MTY_ROUND_HERMITE_CURVE = 1ul << GTY_ROUND_HERMITE_CURVE, + MTY_ORIENTED_HERMITE_CURVE = 1ul << GTY_ORIENTED_HERMITE_CURVE, + + MTY_FLAT_CATMULL_ROM_CURVE = 1ul << GTY_FLAT_CATMULL_ROM_CURVE, + MTY_ROUND_CATMULL_ROM_CURVE = 1ul << GTY_ROUND_CATMULL_ROM_CURVE, + MTY_ORIENTED_CATMULL_ROM_CURVE = 1ul << GTY_ORIENTED_CATMULL_ROM_CURVE, + + MTY_CURVE2 = MTY_FLAT_LINEAR_CURVE | MTY_ROUND_LINEAR_CURVE | MTY_CONE_LINEAR_CURVE | MTY_ORIENTED_LINEAR_CURVE, + + MTY_CURVE4 = MTY_FLAT_BEZIER_CURVE | MTY_ROUND_BEZIER_CURVE | MTY_ORIENTED_BEZIER_CURVE | + MTY_FLAT_BSPLINE_CURVE | MTY_ROUND_BSPLINE_CURVE | MTY_ORIENTED_BSPLINE_CURVE | + MTY_FLAT_HERMITE_CURVE | MTY_ROUND_HERMITE_CURVE | MTY_ORIENTED_HERMITE_CURVE | + MTY_FLAT_CATMULL_ROM_CURVE | MTY_ROUND_CATMULL_ROM_CURVE | MTY_ORIENTED_CATMULL_ROM_CURVE, + + MTY_SPHERE_POINT = 1ul << GTY_SPHERE_POINT, + MTY_DISC_POINT = 1ul << GTY_DISC_POINT, + MTY_ORIENTED_DISC_POINT = 1ul << GTY_ORIENTED_DISC_POINT, + + MTY_POINTS = MTY_SPHERE_POINT | MTY_DISC_POINT | MTY_ORIENTED_DISC_POINT, + + MTY_CURVES = MTY_CURVE2 | MTY_CURVE4 | MTY_POINTS, + + MTY_TRIANGLE_MESH = 1ul << GTY_TRIANGLE_MESH, + MTY_QUAD_MESH = 1ul << GTY_QUAD_MESH, + MTY_GRID_MESH = 1ul << GTY_GRID_MESH, + MTY_SUBDIV_MESH = 1ul << GTY_SUBDIV_MESH, + MTY_USER_GEOMETRY = 1ul << GTY_USER_GEOMETRY, + + MTY_INSTANCE_CHEAP = 1ul << GTY_INSTANCE_CHEAP, + MTY_INSTANCE_EXPENSIVE = 1ul << GTY_INSTANCE_EXPENSIVE, + MTY_INSTANCE = MTY_INSTANCE_CHEAP | MTY_INSTANCE_EXPENSIVE + }; + + static const char* gtype_names[GTY_END]; + + enum class State : unsigned { + MODIFIED = 0, + COMMITTED = 1, + }; + + public: + + /*! Geometry constructor */ + Geometry (Device* device, GType gtype, unsigned int numPrimitives, unsigned int numTimeSteps); + + /*! Geometry destructor */ + virtual ~Geometry(); + + public: + + /*! tests if geometry is enabled */ + __forceinline bool isEnabled() const { return enabled; } + + /*! tests if geometry is disabled */ + __forceinline bool isDisabled() const { return !isEnabled(); } + + /*! tests if that geometry has some filter function set */ + __forceinline bool hasFilterFunctions () const { + return (intersectionFilterN != nullptr) || (occlusionFilterN != nullptr); + } + + /*! returns geometry type */ + __forceinline GType getType() const { return gtype; } + + /*! returns curve type */ + __forceinline GType getCurveType() const { return (GType)(gtype & GTY_SUBTYPE_MASK); } + + /*! returns curve basis */ + __forceinline GType getCurveBasis() const { return (GType)(gtype & GTY_BASIS_MASK); } + + /*! returns geometry type mask */ + __forceinline GTypeMask getTypeMask() const { return (GTypeMask)(1 << gtype); } + + /*! returns number of primitives */ + __forceinline size_t size() const { return numPrimitives; } + + /*! sets the number of primitives */ + virtual void setNumPrimitives(unsigned int numPrimitives_in); + + /*! sets number of time steps */ + virtual void setNumTimeSteps (unsigned int numTimeSteps_in); + + /*! sets motion blur time range */ + void setTimeRange (const BBox1f range); + + /*! sets number of vertex attributes */ + virtual void setVertexAttributeCount (unsigned int N) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! sets number of topologies */ + virtual void setTopologyCount (unsigned int N) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! sets the build quality */ + void setBuildQuality(RTCBuildQuality quality_in) + { + this->quality = quality_in; + Geometry::update(); + } + + /* calculate time segment itime and fractional time ftime */ + __forceinline int timeSegment(float time, float& ftime) const { + return getTimeSegment(time,time_range.lower,time_range.upper,fnumTimeSegments,ftime); + } + + template + __forceinline vint timeSegment(const vfloat& time, vfloat& ftime) const { + return getTimeSegment(time,vfloat(time_range.lower),vfloat(time_range.upper),vfloat(fnumTimeSegments),ftime); + } + + /* calculate overlapping time segment range */ + __forceinline range timeSegmentRange(const BBox1f& range) const { + return getTimeSegmentRange(range,time_range,fnumTimeSegments); + } + + /* returns time that corresponds to time step */ + __forceinline float timeStep(const int i) const { + assert(i>=0 && i<(int)numTimeSteps); + return time_range.lower + time_range.size()*float(i)/fnumTimeSegments; + } + + /*! for all geometries */ + public: + + /*! Enable geometry. */ + virtual void enable(); + + /*! Update geometry. */ + void update(); + + /*! commit of geometry */ + virtual void commit(); + + /*! Update geometry buffer. */ + virtual void updateBuffer(RTCBufferType type, unsigned int slot) { + update(); // update everything for geometries not supporting this call + } + + /*! Disable geometry. */ + virtual void disable(); + + /*! Verify the geometry */ + virtual bool verify() { return true; } + + /*! called before every build */ + virtual void preCommit(); + + /*! called after every build */ + virtual void postCommit(); + + virtual void addElementsToCount (GeometryCounts & counts) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + }; + + /*! sets constant tessellation rate for the geometry */ + virtual void setTessellationRate(float N) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Sets the maximal curve radius scale allowed by min-width feature. */ + virtual void setMaxRadiusScale(float s) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Set user data pointer. */ + virtual void setUserData(void* ptr); + + /*! Get user data pointer. */ + __forceinline void* getUserData() const { + return userPtr; + } + + /*! interpolates user data to the specified u/v location */ + virtual void interpolate(const RTCInterpolateArguments* const args) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! interpolates user data to the specified u/v locations */ + virtual void interpolateN(const RTCInterpolateNArguments* const args); + + /* point query api */ + bool pointQuery(PointQuery* query, PointQueryContext* context); + + /*! for subdivision surfaces only */ + public: + virtual void setSubdivisionMode (unsigned topologyID, RTCSubdivisionMode mode) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + virtual void setVertexAttributeTopology(unsigned int vertexBufferSlot, unsigned int indexBufferSlot) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Set displacement function. */ + virtual void setDisplacementFunction (RTCDisplacementFunctionN filter) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + virtual unsigned int getFirstHalfEdge(unsigned int faceID) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + virtual unsigned int getFace(unsigned int edgeID) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + virtual unsigned int getNextHalfEdge(unsigned int edgeID) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + virtual unsigned int getPreviousHalfEdge(unsigned int edgeID) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + virtual unsigned int getOppositeHalfEdge(unsigned int topologyID, unsigned int edgeID) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! get fast access to first vertex buffer if applicable */ + virtual float * getCompactVertexArray () const { + return nullptr; + } + + /*! Returns the modified counter - how many times the geo has been modified */ + __forceinline unsigned int getModCounter () const { + return modCounter_; + } + + /*! for triangle meshes and bezier curves only */ + public: + + + /*! Sets ray mask. */ + virtual void setMask(unsigned mask) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Sets specified buffer. */ + virtual void setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Gets specified buffer. */ + virtual void* getBuffer(RTCBufferType type, unsigned int slot) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Set intersection filter function for ray packets of size N. */ + virtual void setIntersectionFilterFunctionN (RTCFilterFunctionN filterN); + + /*! Set occlusion filter function for ray packets of size N. */ + virtual void setOcclusionFilterFunctionN (RTCFilterFunctionN filterN); + + /*! for instances only */ + public: + + /*! Sets the instanced scene */ + virtual void setInstancedScene(const Ref& scene) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Sets transformation of the instance */ + virtual void setTransform(const AffineSpace3fa& transform, unsigned int timeStep) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Sets transformation of the instance */ + virtual void setQuaternionDecomposition(const AffineSpace3ff& qd, unsigned int timeStep) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Returns the transformation of the instance */ + virtual AffineSpace3fa getTransform(float time) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! for user geometries only */ + public: + + /*! Set bounds function. */ + virtual void setBoundsFunction (RTCBoundsFunction bounds, void* userPtr) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Set intersect function for ray packets of size N. */ + virtual void setIntersectFunctionN (RTCIntersectFunctionN intersect) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Set occlusion function for ray packets of size N. */ + virtual void setOccludedFunctionN (RTCOccludedFunctionN occluded) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation not supported for this geometry"); + } + + /*! Set point query function. */ + void setPointQueryFunction(RTCPointQueryFunction func); + + /*! returns number of time segments */ + __forceinline unsigned numTimeSegments () const { + return numTimeSteps-1; + } + + public: + + virtual PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"createPrimRefArray not implemented for this geometry"); + } + + virtual PrimInfo createPrimRefArrayMB(mvector& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"createPrimRefMBArray not implemented for this geometry"); + } + + virtual PrimInfoMB createPrimRefMBArray(mvector& prims, const BBox1f& t0t1, const range& r, size_t k, unsigned int geomID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"createPrimRefMBArray not implemented for this geometry"); + } + + virtual LinearSpace3fa computeAlignedSpace(const size_t primID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"computeAlignedSpace not implemented for this geometry"); + } + + virtual LinearSpace3fa computeAlignedSpaceMB(const size_t primID, const BBox1f time_range) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"computeAlignedSpace not implemented for this geometry"); + } + + virtual Vec3fa computeDirection(unsigned int primID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"computeDirection not implemented for this geometry"); + } + + virtual Vec3fa computeDirection(unsigned int primID, size_t time) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"computeDirection not implemented for this geometry"); + } + + virtual BBox3fa vbounds(size_t primID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"vbounds not implemented for this geometry"); + } + + virtual BBox3fa vbounds(const LinearSpace3fa& space, size_t primID) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"vbounds not implemented for this geometry"); + } + + virtual BBox3fa vbounds(const Vec3fa& ofs, const float scale, const float r_scale0, const LinearSpace3fa& space, size_t i, size_t itime = 0) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"vbounds not implemented for this geometry"); + } + + virtual LBBox3fa vlinearBounds(size_t primID, const BBox1f& time_range) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"vlinearBounds not implemented for this geometry"); + } + + virtual LBBox3fa vlinearBounds(const LinearSpace3fa& space, size_t primID, const BBox1f& time_range) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"vlinearBounds not implemented for this geometry"); + } + + virtual LBBox3fa vlinearBounds(const Vec3fa& ofs, const float scale, const float r_scale0, const LinearSpace3fa& space, size_t primID, const BBox1f& time_range) const { + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"vlinearBounds not implemented for this geometry"); + } + + public: + __forceinline bool hasIntersectionFilter() const { return intersectionFilterN != nullptr; } + __forceinline bool hasOcclusionFilter() const { return occlusionFilterN != nullptr; } + + public: + Device* device; //!< device this geometry belongs to + + void* userPtr; //!< user pointer + unsigned int numPrimitives; //!< number of primitives of this geometry + + unsigned int numTimeSteps; //!< number of time steps + float fnumTimeSegments; //!< number of time segments (precalculation) + BBox1f time_range; //!< motion blur time range + + unsigned int mask; //!< for masking out geometry + unsigned int modCounter_ = 1; //!< counter for every modification - used to rebuild scenes when geo is modified + + struct { + GType gtype : 8; //!< geometry type + GSubType gsubtype : 8; //!< geometry subtype + RTCBuildQuality quality : 3; //!< build quality for geometry + unsigned state : 2; + bool enabled : 1; //!< true if geometry is enabled + }; + + RTCFilterFunctionN intersectionFilterN; + RTCFilterFunctionN occlusionFilterN; + RTCPointQueryFunction pointQueryFunc; + }; +} diff --git a/thirdparty/embree/kernels/common/hit.h b/thirdparty/embree/kernels/common/hit.h new file mode 100644 index 000000000000..32a198cdfe4f --- /dev/null +++ b/thirdparty/embree/kernels/common/hit.h @@ -0,0 +1,114 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "ray.h" +#include "instance_stack.h" + +namespace embree +{ + /* Hit structure for K hits */ + template + struct HitK + { + /* Default construction does nothing */ + __forceinline HitK() {} + + /* Constructs a hit */ + __forceinline HitK(const RTCIntersectContext* context, const vuint& geomID, const vuint& primID, const vfloat& u, const vfloat& v, const Vec3vf& Ng) + : Ng(Ng), u(u), v(v), primID(primID), geomID(geomID) + { + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + instID[l] = RTC_INVALID_GEOMETRY_ID; + instance_id_stack::copy(context->instID, instID); + } + + /* Returns the size of the hit */ + static __forceinline size_t size() { return K; } + + public: + Vec3vf Ng; // geometry normal + vfloat u; // barycentric u coordinate of hit + vfloat v; // barycentric v coordinate of hit + vuint primID; // primitive ID + vuint geomID; // geometry ID + vuint instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // instance ID + }; + + /* Specialization for a single hit */ + template<> + struct __aligned(16) HitK<1> + { + /* Default construction does nothing */ + __forceinline HitK() {} + + /* Constructs a hit */ + __forceinline HitK(const RTCIntersectContext* context, unsigned int geomID, unsigned int primID, float u, float v, const Vec3fa& Ng) + : Ng(Ng.x,Ng.y,Ng.z), u(u), v(v), primID(primID), geomID(geomID) + { + instance_id_stack::copy(context->instID, instID); + } + + /* Returns the size of the hit */ + static __forceinline size_t size() { return 1; } + + public: + Vec3 Ng; // geometry normal + float u; // barycentric u coordinate of hit + float v; // barycentric v coordinate of hit + unsigned int primID; // primitive ID + unsigned int geomID; // geometry ID + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // instance ID + }; + + /* Shortcuts */ + typedef HitK<1> Hit; + typedef HitK<4> Hit4; + typedef HitK<8> Hit8; + typedef HitK<16> Hit16; + + /* Outputs hit to stream */ + template + __forceinline embree_ostream operator<<(embree_ostream cout, const HitK& ray) + { + cout << "{ " << embree_endl + << " Ng = " << ray.Ng << embree_endl + << " u = " << ray.u << embree_endl + << " v = " << ray.v << embree_endl + << " primID = " << ray.primID << embree_endl + << " geomID = " << ray.geomID << embree_endl + << " instID ="; + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + { + cout << " " << ray.instID[l]; + } + cout << embree_endl; + return cout << "}"; + } + + template + __forceinline void copyHitToRay(RayHit& ray, const Hit& hit) + { + ray.Ng = hit.Ng; + ray.u = hit.u; + ray.v = hit.v; + ray.primID = hit.primID; + ray.geomID = hit.geomID; + instance_id_stack::copy(hit.instID, ray.instID); + } + + template + __forceinline void copyHitToRay(const vbool &mask, RayHitK &ray, const HitK &hit) + { + vfloat::storeu(mask,&ray.Ng.x, hit.Ng.x); + vfloat::storeu(mask,&ray.Ng.y, hit.Ng.y); + vfloat::storeu(mask,&ray.Ng.z, hit.Ng.z); + vfloat::storeu(mask,&ray.u, hit.u); + vfloat::storeu(mask,&ray.v, hit.v); + vuint::storeu(mask,&ray.primID, hit.primID); + vuint::storeu(mask,&ray.geomID, hit.geomID); + instance_id_stack::copy(hit.instID, ray.instID, mask); + } +} diff --git a/thirdparty/embree/kernels/common/instance_stack.h b/thirdparty/embree/kernels/common/instance_stack.h new file mode 100644 index 000000000000..d7e3637f7b2b --- /dev/null +++ b/thirdparty/embree/kernels/common/instance_stack.h @@ -0,0 +1,199 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rtcore.h" + +namespace embree { +namespace instance_id_stack { + +static_assert(RTC_MAX_INSTANCE_LEVEL_COUNT > 0, + "RTC_MAX_INSTANCE_LEVEL_COUNT must be greater than 0."); + +/******************************************************************************* + * Instance ID stack manipulation. + * This is used from the instance intersector. + ******************************************************************************/ + +/* + * Push an instance to the stack. + */ +RTC_FORCEINLINE bool push(RTCIntersectContext* context, + unsigned instanceId) +{ +#if RTC_MAX_INSTANCE_LEVEL_COUNT > 1 + const bool spaceAvailable = context->instStackSize < RTC_MAX_INSTANCE_LEVEL_COUNT; + /* We assert here because instances are silently dropped when the stack is full. + This might be quite hard to find in production. */ + assert(spaceAvailable); + if (likely(spaceAvailable)) + context->instID[context->instStackSize++] = instanceId; + return spaceAvailable; +#else + const bool spaceAvailable = (context->instID[0] == RTC_INVALID_GEOMETRY_ID); + assert(spaceAvailable); + if (likely(spaceAvailable)) + context->instID[0] = instanceId; + return spaceAvailable; +#endif +} + + +/* + * Pop the last instance pushed to the stack. + * Do not call on an empty stack. + */ +RTC_FORCEINLINE void pop(RTCIntersectContext* context) +{ + assert(context); +#if RTC_MAX_INSTANCE_LEVEL_COUNT > 1 + assert(context->instStackSize > 0); + context->instID[--context->instStackSize] = RTC_INVALID_GEOMETRY_ID; +#else + assert(context->instID[0] != RTC_INVALID_GEOMETRY_ID); + context->instID[0] = RTC_INVALID_GEOMETRY_ID; +#endif +} + +/******************************************************************************* + * Optimized instance id stack copy. + * The copy() function at the bottom of this block will either copy full + * stacks or copy only until the last valid element has been copied, depending + * on RTC_MAX_INSTANCE_LEVEL_COUNT. + ******************************************************************************/ + +/* + * Plain array assignment. This works for scalar->scalar, + * scalar->vector, and vector->vector. + */ +template +RTC_FORCEINLINE void level_copy(unsigned level, Src* src, Tgt* tgt) +{ + tgt[level] = src[level]; +} + +/* + * Masked SIMD vector->vector store. + */ +template +RTC_FORCEINLINE void level_copy(unsigned level, const vuint* src, vuint* tgt, const vbool& mask) +{ + vuint::storeu(mask, tgt + level, src[level]); +} + +/* + * Masked scalar->SIMD vector store. + */ +template +RTC_FORCEINLINE void level_copy(unsigned level, const unsigned* src, vuint* tgt, const vbool& mask) +{ + vuint::store(mask, tgt + level, src[level]); +} + +/* + * Indexed assign from vector to scalar. + */ +template +RTC_FORCEINLINE void level_copy(unsigned level, const vuint* src, unsigned* tgt, const size_t& idx) +{ + tgt[level] = src[level][idx]; +} + +/* + * Indexed assign from scalar to vector. + */ +template +RTC_FORCEINLINE void level_copy(unsigned level, const unsigned* src, vuint* tgt, const size_t& idx) +{ + tgt[level][idx] = src[level]; +} + +/* + * Indexed assign from vector to vector. + */ +template +RTC_FORCEINLINE void level_copy(unsigned level, const vuint* src, vuint* tgt, const size_t& i, const size_t& j) +{ + tgt[level][j] = src[level][i]; +} + +/* + * Check if the given stack level is valid. + * These are only used for large max stack sizes. + */ +RTC_FORCEINLINE bool level_valid(unsigned level, const unsigned* stack) +{ + return stack[level] != RTC_INVALID_GEOMETRY_ID; +} +RTC_FORCEINLINE bool level_valid(unsigned level, const unsigned* stack, const size_t& /*i*/) +{ + return stack[level] != RTC_INVALID_GEOMETRY_ID; +} +template +RTC_FORCEINLINE bool level_valid(unsigned level, const unsigned* stack, const vbool& /*mask*/) +{ + return stack[level] != RTC_INVALID_GEOMETRY_ID; +} + +template +RTC_FORCEINLINE bool level_valid(unsigned level, const vuint* stack) +{ + return any(stack[level] != RTC_INVALID_GEOMETRY_ID); +} +template +RTC_FORCEINLINE bool level_valid(unsigned level, const vuint* stack, const vbool& mask) +{ + return any(mask & (stack[level] != RTC_INVALID_GEOMETRY_ID)); +} + +template +RTC_FORCEINLINE bool level_valid(unsigned level, const vuint* stack, const size_t& i) +{ + return stack[level][i] != RTC_INVALID_GEOMETRY_ID; +} +template +RTC_FORCEINLINE bool level_valid(unsigned level, const vuint* stack, const size_t& i, const size_t& /*j*/) +{ + return stack[level][i] != RTC_INVALID_GEOMETRY_ID; +} + +/* + * Copy an instance ID stack. + * + * This function automatically selects a LevelFunctor from the above Assign + * structs. + */ +template +RTC_FORCEINLINE void copy(Src src, Tgt tgt, Args&&... args) +{ +#if (RTC_MAX_INSTANCE_LEVEL_COUNT == 1) + /* + * Avoid all loops for only one level. + */ + level_copy(0, src, tgt, std::forward(args)...); + +#elif (RTC_MAX_INSTANCE_LEVEL_COUNT <= 4) + /* + * It is faster to avoid the valid test for low level counts. + * Just copy the whole stack. + */ + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + level_copy(l, src, tgt, std::forward(args)...); + +#else + /* + * For general stack sizes, it pays off to test for validity. + */ + bool valid = true; + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT && valid; ++l) + { + level_copy(l, src, tgt, std::forward(args)...); + valid = level_valid(l, src, std::forward(args)...); + } +#endif +} + +} // namespace instance_id_stack +} // namespace embree + diff --git a/thirdparty/embree/kernels/common/isa.h b/thirdparty/embree/kernels/common/isa.h new file mode 100644 index 000000000000..9fd1ea58b749 --- /dev/null +++ b/thirdparty/embree/kernels/common/isa.h @@ -0,0 +1,271 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../../common/sys/platform.h" +#include "../../common/sys/sysinfo.h" + +namespace embree +{ +#define DEFINE_SYMBOL2(type,name) \ + typedef type (*name##Func)(); \ + name##Func name; + +#define DECLARE_SYMBOL2(type,name) \ + namespace sse2 { extern type name(); } \ + namespace sse42 { extern type name(); } \ + namespace avx { extern type name(); } \ + namespace avx2 { extern type name(); } \ + namespace avx512knl { extern type name(); } \ + namespace avx512skx { extern type name(); } \ + void name##_error2() { throw_RTCError(RTC_ERROR_UNKNOWN,"internal error in ISA selection for " TOSTRING(name)); } \ + type name##_error() { return type(name##_error2); } \ + type name##_zero() { return type(nullptr); } + +#define DECLARE_ISA_FUNCTION(type,symbol,args) \ + namespace sse2 { extern type symbol(args); } \ + namespace sse42 { extern type symbol(args); } \ + namespace avx { extern type symbol(args); } \ + namespace avx2 { extern type symbol(args); } \ + namespace avx512knl { extern type symbol(args); } \ + namespace avx512skx { extern type symbol(args); } \ + inline type symbol##_error(args) { throw_RTCError(RTC_ERROR_UNSUPPORTED_CPU,"function " TOSTRING(symbol) " not supported by your CPU"); } \ + typedef type (*symbol##Ty)(args); \ + +#define DEFINE_ISA_FUNCTION(type,symbol,args) \ + typedef type (*symbol##Func)(args); \ + symbol##Func symbol; + +#define ZERO_SYMBOL(features,intersector) \ + intersector = intersector##_zero; + +#define INIT_SYMBOL(features,intersector) \ + intersector = decltype(intersector)(intersector##_error); + +#define SELECT_SYMBOL_DEFAULT(features,intersector) \ + intersector = isa::intersector; + +#if defined(__SSE__) +#if !defined(EMBREE_TARGET_SIMD4) +#define EMBREE_TARGET_SIMD4 +#endif +#endif + +#if defined(EMBREE_TARGET_SSE42) +#define SELECT_SYMBOL_SSE42(features,intersector) \ + if ((features & SSE42) == SSE42) intersector = sse42::intersector; +#else +#define SELECT_SYMBOL_SSE42(features,intersector) +#endif + +#if defined(EMBREE_TARGET_AVX) || defined(__AVX__) +#if !defined(EMBREE_TARGET_SIMD8) +#define EMBREE_TARGET_SIMD8 +#endif +#if defined(__AVX__) // if default ISA is >= AVX we treat AVX target as default target +#define SELECT_SYMBOL_AVX(features,intersector) \ + if ((features & ISA) == ISA) intersector = isa::intersector; +#else +#define SELECT_SYMBOL_AVX(features,intersector) \ + if ((features & AVX) == AVX) intersector = avx::intersector; +#endif +#else +#define SELECT_SYMBOL_AVX(features,intersector) +#endif + +#if defined(EMBREE_TARGET_AVX2) +#if !defined(EMBREE_TARGET_SIMD8) +#define EMBREE_TARGET_SIMD8 +#endif +#define SELECT_SYMBOL_AVX2(features,intersector) \ + if ((features & AVX2) == AVX2) intersector = avx2::intersector; +#else +#define SELECT_SYMBOL_AVX2(features,intersector) +#endif + +#if defined(EMBREE_TARGET_AVX512KNL) +#if !defined(EMBREE_TARGET_SIMD16) +#define EMBREE_TARGET_SIMD16 +#endif +#define SELECT_SYMBOL_AVX512KNL(features,intersector) \ + if ((features & AVX512KNL) == AVX512KNL) intersector = avx512knl::intersector; +#else +#define SELECT_SYMBOL_AVX512KNL(features,intersector) +#endif + +#if defined(EMBREE_TARGET_AVX512SKX) +#if !defined(EMBREE_TARGET_SIMD16) +#define EMBREE_TARGET_SIMD16 +#endif +#define SELECT_SYMBOL_AVX512SKX(features,intersector) \ + if ((features & AVX512SKX) == AVX512SKX) intersector = avx512skx::intersector; +#else +#define SELECT_SYMBOL_AVX512SKX(features,intersector) +#endif + +#define SELECT_SYMBOL_DEFAULT_SSE42(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_SSE42_AVX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX2(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX512KNL_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX_AVX2(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX_AVX2_AVX512SKX(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_INIT_SSE42_AVX_AVX2(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX_AVX512KNL(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX_AVX512KNL_AVX512SKX(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX_AVX2_AVX512KNL_AVX512SKX(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_INIT_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_ZERO_SSE42_AVX_AVX2_AVX512KNL_AVX512SKX(features,intersector) \ + ZERO_SYMBOL(features,intersector); \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(features,intersector) \ + SELECT_SYMBOL_DEFAULT(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_INIT_AVX512KNL_AVX512SKX(features,intersector) \ + INIT_SYMBOL(features,intersector); \ + SELECT_SYMBOL_AVX512KNL(features,intersector); \ + SELECT_SYMBOL_AVX512SKX(features,intersector); + +#define SELECT_SYMBOL_SSE42_AVX_AVX2(features,intersector) \ + SELECT_SYMBOL_SSE42(features,intersector); \ + SELECT_SYMBOL_AVX(features,intersector); \ + SELECT_SYMBOL_AVX2(features,intersector); + + struct VerifyMultiTargetLinking { + static __noinline int getISA(int depth = 5) { + if (depth == 0) return ISA; + else return getISA(depth-1); + } + }; + namespace sse2 { int getISA(); }; + namespace sse42 { int getISA(); }; + namespace avx { int getISA(); }; + namespace avx2 { int getISA(); }; + namespace avx512knl { int getISA(); }; + namespace avx512skx { int getISA(); }; +} diff --git a/thirdparty/embree/kernels/common/motion_derivative.h b/thirdparty/embree/kernels/common/motion_derivative.h new file mode 100644 index 000000000000..82953f0e89a4 --- /dev/null +++ b/thirdparty/embree/kernels/common/motion_derivative.h @@ -0,0 +1,325 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../../common/math/affinespace.h" +#include "../../common/math/interval.h" + +#include + +namespace embree { + +#define MOTION_DERIVATIVE_ROOT_EPSILON 1e-4f + +static void motion_derivative_coefficients(const float *p, float *coeff); + +struct MotionDerivativeCoefficients +{ + float theta; + float coeffs[3*8*7]; + + MotionDerivativeCoefficients() {} + + // xfm0 and xfm1 are interpret as quaternion decomposition + MotionDerivativeCoefficients(AffineSpace3ff const& xfm0, AffineSpace3ff const& xfm1) + { + // cosTheta of the two quaternions + const float cosTheta = min(1.f, max(-1.f, + xfm0.l.vx.w * xfm1.l.vx.w + + xfm0.l.vy.w * xfm1.l.vy.w + + xfm0.l.vz.w * xfm1.l.vz.w + + xfm0.p.w * xfm1.p.w)); + + theta = std::acos(cosTheta); + Vec4f qperp(xfm1.p.w, xfm1.l.vx.w, xfm1.l.vy.w, xfm1.l.vz.w); + if (cosTheta < 0.995f) { + // compute perpendicular quaternion + qperp.x = xfm1.p.w - cosTheta * xfm0.p.w; + qperp.y = xfm1.l.vx.w - cosTheta * xfm0.l.vx.w; + qperp.z = xfm1.l.vy.w - cosTheta * xfm0.l.vy.w; + qperp.w = xfm1.l.vz.w - cosTheta * xfm0.l.vz.w; + qperp = normalize(qperp); + } + const float p[33] = { + theta, + xfm0.l.vx.y, xfm0.l.vx.z, xfm0.l.vy.z, // translation component of xfm0 + xfm1.l.vx.y, xfm1.l.vx.z, xfm1.l.vy.z, // translation component of xfm1 + xfm0.p.w, xfm0.l.vx.w, xfm0.l.vy.w, xfm0.l.vz.w, // quaternion of xfm0 + qperp.x, qperp.y, qperp.z, qperp.w, + xfm0.l.vx.x, xfm0.l.vy.x, xfm0.l.vz.x, xfm0.p.x, // scale/skew component of xfm0 + xfm0.l.vy.y, xfm0.l.vz.y, xfm0.p.y, + xfm0.l.vz.z, xfm0.p.z, + xfm1.l.vx.x, xfm1.l.vy.x, xfm1.l.vz.x, xfm1.p.x, // scale/skew component of xfm1 + xfm1.l.vy.y, xfm1.l.vz.y, xfm1.p.y, + xfm1.l.vz.z, xfm1.p.z + }; + motion_derivative_coefficients(p, coeffs); + } +}; + +struct MotionDerivative +{ + float twoTheta; + float c[8]; + + MotionDerivative(MotionDerivativeCoefficients const& mdc, + int dim, Vec3fa const& p0, Vec3fa const& p1) + : twoTheta(2.f*mdc.theta) + { + const float p[7] = { 1, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z }; + for (int i = 0; i < 8; ++i) { + c[i] = 0; + for (int j = 0; j < 7; ++j) { + c[i] += mdc.coeffs[8*7*dim + i*7 + j] * p[j]; + } + } + } + + template + struct EvalMotionDerivative + { + MotionDerivative const& md; + float offset; + + EvalMotionDerivative(MotionDerivative const& md, float offset) : md(md), offset(offset) {} + + T operator()(T const& time) const { + return md.c[0] + md.c[1] * time + + (md.c[2] + md.c[3] * time + md.c[4] * time * time) * cos(md.twoTheta * time) + + (md.c[5] + md.c[6] * time + md.c[7] * time * time) * sin(md.twoTheta * time) + + offset; + } + }; + + unsigned int findRoots( + Interval1f const& interval, + float offset, + float* roots, + unsigned int maxNumRoots) + { + unsigned int numRoots = 0; + EvalMotionDerivative eval(*this, offset); + findRoots(eval, interval, numRoots, roots, maxNumRoots); + return numRoots; + } + + template + static void findRoots( + + Eval const& eval, + Interval1f const& interval, + unsigned int& numRoots, + float* roots, + unsigned int maxNumRoots) + { + Interval1f range = eval(interval); + if (range.lower > 0 || range.upper < 0 || range.lower >= range.upper) return; + + const float split = 0.5f * (interval.upper + interval.lower); + if (interval.upper-interval.lower < 1e-7f || abs(split-interval.lower) < 1e-7f || abs(split-interval.upper) < 1e-7f) + { + // check if the root already exists + for (unsigned int k = 0; k < numRoots && k < maxNumRoots; ++k) { + if (abs(roots[k]-split) < MOTION_DERIVATIVE_ROOT_EPSILON) + return; + } + if (numRoots < maxNumRoots) { + roots[numRoots++] = split; + } + if (numRoots > maxNumRoots) { + printf("error: more roots than expected\n"); // FIXME: workaround for ICC2019.4 compiler bug under macOS + return; + } + return; + } + + findRoots(eval, Interval1f(interval.lower, split), numRoots, roots, maxNumRoots); + findRoots(eval, Interval1f(split, interval.upper), numRoots, roots, maxNumRoots); + } +}; + +/****************************************************************************** + * Code generated with sympy 1.4 * + * See http://www.sympy.org/ for more information. * + * * + * see * + * * + * scripts/generate_motion_derivative_coefficients.py * + * * + * for how this code is generated * + * * + ******************************************************************************/ +static void motion_derivative_coefficients(const float *p, float *coeff) +{ + coeff[0] = -p[1] + p[4] - p[7]*p[9]*p[23] + p[7]*p[9]*p[32] + p[7]*p[10]*p[21] - p[7]*p[10]*p[30] - p[8]*p[9]*p[21] + p[8]*p[9]*p[30] - p[8]*p[10]*p[23] + p[8]*p[10]*p[32] + p[9]*p[9]*p[18] - p[9]*p[9]*p[27] + p[10]*p[10]*p[18] - p[10]*p[10]*p[27] - p[11]*p[13]*p[23] + p[11]*p[13]*p[32] + p[11]*p[14]*p[21] - p[11]*p[14]*p[30] - p[12]*p[13]*p[21] + p[12]*p[13]*p[30] - p[12]*p[14]*p[23] + p[12]*p[14]*p[32] + p[13]*p[13]*p[18] - p[13]*p[13]*p[27] + p[14]*p[14]*p[18] - p[14]*p[14]*p[27] - p[18] + p[27]; + coeff[1] = 2*p[9]*p[9]*p[15] - p[9]*p[9]*p[24] + 2*p[10]*p[10]*p[15] - p[10]*p[10]*p[24] + 2*p[13]*p[13]*p[15] - p[13]*p[13]*p[24] + 2*p[14]*p[14]*p[15] - p[14]*p[14]*p[24] - 2*p[15] + p[24]; + coeff[2] = 2*p[7]*p[10]*p[19] - p[7]*p[10]*p[28] - 2*p[8]*p[9]*p[19] + p[8]*p[9]*p[28] + 2*p[9]*p[9]*p[16] - p[9]*p[9]*p[25] + 2*p[10]*p[10]*p[16] - p[10]*p[10]*p[25] + 2*p[11]*p[14]*p[19] - p[11]*p[14]*p[28] - 2*p[12]*p[13]*p[19] + p[12]*p[13]*p[28] + 2*p[13]*p[13]*p[16] - p[13]*p[13]*p[25] + 2*p[14]*p[14]*p[16] - p[14]*p[14]*p[25] - 2*p[16] + p[25]; + coeff[3] = -2*p[7]*p[9]*p[22] + p[7]*p[9]*p[31] + 2*p[7]*p[10]*p[20] - p[7]*p[10]*p[29] - 2*p[8]*p[9]*p[20] + p[8]*p[9]*p[29] - 2*p[8]*p[10]*p[22] + p[8]*p[10]*p[31] + 2*p[9]*p[9]*p[17] - p[9]*p[9]*p[26] + 2*p[10]*p[10]*p[17] - p[10]*p[10]*p[26] - 2*p[11]*p[13]*p[22] + p[11]*p[13]*p[31] + 2*p[11]*p[14]*p[20] - p[11]*p[14]*p[29] - 2*p[12]*p[13]*p[20] + p[12]*p[13]*p[29] - 2*p[12]*p[14]*p[22] + p[12]*p[14]*p[31] + 2*p[13]*p[13]*p[17] - p[13]*p[13]*p[26] + 2*p[14]*p[14]*p[17] - p[14]*p[14]*p[26] - 2*p[17] + p[26]; + coeff[4] = (-p[9]*p[9] - p[10]*p[10] - p[13]*p[13] - p[14]*p[14] + 1)*p[15]; + coeff[5] = -p[7]*p[10]*p[19] + p[8]*p[9]*p[19] - p[9]*p[9]*p[16] - p[10]*p[10]*p[16] - p[11]*p[14]*p[19] + p[12]*p[13]*p[19] - p[13]*p[13]*p[16] - p[14]*p[14]*p[16] + p[16]; + coeff[6] = p[7]*p[9]*p[22] - p[7]*p[10]*p[20] + p[8]*p[9]*p[20] + p[8]*p[10]*p[22] - p[9]*p[9]*p[17] - p[10]*p[10]*p[17] + p[11]*p[13]*p[22] - p[11]*p[14]*p[20] + p[12]*p[13]*p[20] + p[12]*p[14]*p[22] - p[13]*p[13]*p[17] - p[14]*p[14]*p[17] + p[17]; + coeff[7] = 0; + coeff[8] = -2*p[9]*p[9]*p[15] + 2*p[9]*p[9]*p[24] - 2*p[10]*p[10]*p[15] + 2*p[10]*p[10]*p[24] - 2*p[13]*p[13]*p[15] + 2*p[13]*p[13]*p[24] - 2*p[14]*p[14]*p[15] + 2*p[14]*p[14]*p[24] + 2*p[15] - 2*p[24]; + coeff[9] = -2*p[7]*p[10]*p[19] + 2*p[7]*p[10]*p[28] + 2*p[8]*p[9]*p[19] - 2*p[8]*p[9]*p[28] - 2*p[9]*p[9]*p[16] + 2*p[9]*p[9]*p[25] - 2*p[10]*p[10]*p[16] + 2*p[10]*p[10]*p[25] - 2*p[11]*p[14]*p[19] + 2*p[11]*p[14]*p[28] + 2*p[12]*p[13]*p[19] - 2*p[12]*p[13]*p[28] - 2*p[13]*p[13]*p[16] + 2*p[13]*p[13]*p[25] - 2*p[14]*p[14]*p[16] + 2*p[14]*p[14]*p[25] + 2*p[16] - 2*p[25]; + coeff[10] = 2*p[7]*p[9]*p[22] - 2*p[7]*p[9]*p[31] - 2*p[7]*p[10]*p[20] + 2*p[7]*p[10]*p[29] + 2*p[8]*p[9]*p[20] - 2*p[8]*p[9]*p[29] + 2*p[8]*p[10]*p[22] - 2*p[8]*p[10]*p[31] - 2*p[9]*p[9]*p[17] + 2*p[9]*p[9]*p[26] - 2*p[10]*p[10]*p[17] + 2*p[10]*p[10]*p[26] + 2*p[11]*p[13]*p[22] - 2*p[11]*p[13]*p[31] - 2*p[11]*p[14]*p[20] + 2*p[11]*p[14]*p[29] + 2*p[12]*p[13]*p[20] - 2*p[12]*p[13]*p[29] + 2*p[12]*p[14]*p[22] - 2*p[12]*p[14]*p[31] - 2*p[13]*p[13]*p[17] + 2*p[13]*p[13]*p[26] - 2*p[14]*p[14]*p[17] + 2*p[14]*p[14]*p[26] + 2*p[17] - 2*p[26]; + coeff[11] = 2*p[9]*p[9]*p[15] - 2*p[9]*p[9]*p[24] + 2*p[10]*p[10]*p[15] - 2*p[10]*p[10]*p[24] + 2*p[13]*p[13]*p[15] - 2*p[13]*p[13]*p[24] + 2*p[14]*p[14]*p[15] - 2*p[14]*p[14]*p[24] - 2*p[15] + 2*p[24]; + coeff[12] = 2*p[7]*p[10]*p[19] - 2*p[7]*p[10]*p[28] - 2*p[8]*p[9]*p[19] + 2*p[8]*p[9]*p[28] + 2*p[9]*p[9]*p[16] - 2*p[9]*p[9]*p[25] + 2*p[10]*p[10]*p[16] - 2*p[10]*p[10]*p[25] + 2*p[11]*p[14]*p[19] - 2*p[11]*p[14]*p[28] - 2*p[12]*p[13]*p[19] + 2*p[12]*p[13]*p[28] + 2*p[13]*p[13]*p[16] - 2*p[13]*p[13]*p[25] + 2*p[14]*p[14]*p[16] - 2*p[14]*p[14]*p[25] - 2*p[16] + 2*p[25]; + coeff[13] = -2*p[7]*p[9]*p[22] + 2*p[7]*p[9]*p[31] + 2*p[7]*p[10]*p[20] - 2*p[7]*p[10]*p[29] - 2*p[8]*p[9]*p[20] + 2*p[8]*p[9]*p[29] - 2*p[8]*p[10]*p[22] + 2*p[8]*p[10]*p[31] + 2*p[9]*p[9]*p[17] - 2*p[9]*p[9]*p[26] + 2*p[10]*p[10]*p[17] - 2*p[10]*p[10]*p[26] - 2*p[11]*p[13]*p[22] + 2*p[11]*p[13]*p[31] + 2*p[11]*p[14]*p[20] - 2*p[11]*p[14]*p[29] - 2*p[12]*p[13]*p[20] + 2*p[12]*p[13]*p[29] - 2*p[12]*p[14]*p[22] + 2*p[12]*p[14]*p[31] + 2*p[13]*p[13]*p[17] - 2*p[13]*p[13]*p[26] + 2*p[14]*p[14]*p[17] - 2*p[14]*p[14]*p[26] - 2*p[17] + 2*p[26]; + coeff[14] = 2*p[0]*p[7]*p[11]*p[18] + 2*p[0]*p[7]*p[13]*p[23] - 2*p[0]*p[7]*p[14]*p[21] + 2*p[0]*p[8]*p[12]*p[18] + 2*p[0]*p[8]*p[13]*p[21] + 2*p[0]*p[8]*p[14]*p[23] + 2*p[0]*p[9]*p[11]*p[23] + 2*p[0]*p[9]*p[12]*p[21] - 2*p[0]*p[9]*p[13]*p[18] - 2*p[0]*p[10]*p[11]*p[21] + 2*p[0]*p[10]*p[12]*p[23] - 2*p[0]*p[10]*p[14]*p[18] - p[7]*p[9]*p[23] + p[7]*p[9]*p[32] + p[7]*p[10]*p[21] - p[7]*p[10]*p[30] - p[8]*p[9]*p[21] + p[8]*p[9]*p[30] - p[8]*p[10]*p[23] + p[8]*p[10]*p[32] + p[9]*p[9]*p[18] - p[9]*p[9]*p[27] + p[10]*p[10]*p[18] - p[10]*p[10]*p[27] + p[11]*p[13]*p[23] - p[11]*p[13]*p[32] - p[11]*p[14]*p[21] + p[11]*p[14]*p[30] + p[12]*p[13]*p[21] - p[12]*p[13]*p[30] + p[12]*p[14]*p[23] - p[12]*p[14]*p[32] - p[13]*p[13]*p[18] + p[13]*p[13]*p[27] - p[14]*p[14]*p[18] + p[14]*p[14]*p[27]; + coeff[15] = 2*p[0]*p[7]*p[11]*p[15] + 2*p[0]*p[8]*p[12]*p[15] - 2*p[0]*p[9]*p[13]*p[15] - 2*p[0]*p[10]*p[14]*p[15] + 2*p[9]*p[9]*p[15] - p[9]*p[9]*p[24] + 2*p[10]*p[10]*p[15] - p[10]*p[10]*p[24] - 2*p[13]*p[13]*p[15] + p[13]*p[13]*p[24] - 2*p[14]*p[14]*p[15] + p[14]*p[14]*p[24]; + coeff[16] = 2*p[0]*p[7]*p[11]*p[16] - 2*p[0]*p[7]*p[14]*p[19] + 2*p[0]*p[8]*p[12]*p[16] + 2*p[0]*p[8]*p[13]*p[19] + 2*p[0]*p[9]*p[12]*p[19] - 2*p[0]*p[9]*p[13]*p[16] - 2*p[0]*p[10]*p[11]*p[19] - 2*p[0]*p[10]*p[14]*p[16] + 2*p[7]*p[10]*p[19] - p[7]*p[10]*p[28] - 2*p[8]*p[9]*p[19] + p[8]*p[9]*p[28] + 2*p[9]*p[9]*p[16] - p[9]*p[9]*p[25] + 2*p[10]*p[10]*p[16] - p[10]*p[10]*p[25] - 2*p[11]*p[14]*p[19] + p[11]*p[14]*p[28] + 2*p[12]*p[13]*p[19] - p[12]*p[13]*p[28] - 2*p[13]*p[13]*p[16] + p[13]*p[13]*p[25] - 2*p[14]*p[14]*p[16] + p[14]*p[14]*p[25]; + coeff[17] = 2*p[0]*p[7]*p[11]*p[17] + 2*p[0]*p[7]*p[13]*p[22] - 2*p[0]*p[7]*p[14]*p[20] + 2*p[0]*p[8]*p[12]*p[17] + 2*p[0]*p[8]*p[13]*p[20] + 2*p[0]*p[8]*p[14]*p[22] + 2*p[0]*p[9]*p[11]*p[22] + 2*p[0]*p[9]*p[12]*p[20] - 2*p[0]*p[9]*p[13]*p[17] - 2*p[0]*p[10]*p[11]*p[20] + 2*p[0]*p[10]*p[12]*p[22] - 2*p[0]*p[10]*p[14]*p[17] - 2*p[7]*p[9]*p[22] + p[7]*p[9]*p[31] + 2*p[7]*p[10]*p[20] - p[7]*p[10]*p[29] - 2*p[8]*p[9]*p[20] + p[8]*p[9]*p[29] - 2*p[8]*p[10]*p[22] + p[8]*p[10]*p[31] + 2*p[9]*p[9]*p[17] - p[9]*p[9]*p[26] + 2*p[10]*p[10]*p[17] - p[10]*p[10]*p[26] + 2*p[11]*p[13]*p[22] - p[11]*p[13]*p[31] - 2*p[11]*p[14]*p[20] + p[11]*p[14]*p[29] + 2*p[12]*p[13]*p[20] - p[12]*p[13]*p[29] + 2*p[12]*p[14]*p[22] - p[12]*p[14]*p[31] - 2*p[13]*p[13]*p[17] + p[13]*p[13]*p[26] - 2*p[14]*p[14]*p[17] + p[14]*p[14]*p[26]; + coeff[18] = (-p[9]*p[9] - p[10]*p[10] + p[13]*p[13] + p[14]*p[14])*p[15]; + coeff[19] = -p[7]*p[10]*p[19] + p[8]*p[9]*p[19] - p[9]*p[9]*p[16] - p[10]*p[10]*p[16] + p[11]*p[14]*p[19] - p[12]*p[13]*p[19] + p[13]*p[13]*p[16] + p[14]*p[14]*p[16]; + coeff[20] = p[7]*p[9]*p[22] - p[7]*p[10]*p[20] + p[8]*p[9]*p[20] + p[8]*p[10]*p[22] - p[9]*p[9]*p[17] - p[10]*p[10]*p[17] - p[11]*p[13]*p[22] + p[11]*p[14]*p[20] - p[12]*p[13]*p[20] - p[12]*p[14]*p[22] + p[13]*p[13]*p[17] + p[14]*p[14]*p[17]; + coeff[21] = 2*(-p[7]*p[11]*p[18] + p[7]*p[11]*p[27] - p[7]*p[13]*p[23] + p[7]*p[13]*p[32] + p[7]*p[14]*p[21] - p[7]*p[14]*p[30] - p[8]*p[12]*p[18] + p[8]*p[12]*p[27] - p[8]*p[13]*p[21] + p[8]*p[13]*p[30] - p[8]*p[14]*p[23] + p[8]*p[14]*p[32] - p[9]*p[11]*p[23] + p[9]*p[11]*p[32] - p[9]*p[12]*p[21] + p[9]*p[12]*p[30] + p[9]*p[13]*p[18] - p[9]*p[13]*p[27] + p[10]*p[11]*p[21] - p[10]*p[11]*p[30] - p[10]*p[12]*p[23] + p[10]*p[12]*p[32] + p[10]*p[14]*p[18] - p[10]*p[14]*p[27])*p[0]; + coeff[22] = -4*p[0]*p[7]*p[11]*p[15] + 2*p[0]*p[7]*p[11]*p[24] - 4*p[0]*p[8]*p[12]*p[15] + 2*p[0]*p[8]*p[12]*p[24] + 4*p[0]*p[9]*p[13]*p[15] - 2*p[0]*p[9]*p[13]*p[24] + 4*p[0]*p[10]*p[14]*p[15] - 2*p[0]*p[10]*p[14]*p[24] - 2*p[9]*p[9]*p[15] + 2*p[9]*p[9]*p[24] - 2*p[10]*p[10]*p[15] + 2*p[10]*p[10]*p[24] + 2*p[13]*p[13]*p[15] - 2*p[13]*p[13]*p[24] + 2*p[14]*p[14]*p[15] - 2*p[14]*p[14]*p[24]; + coeff[23] = -4*p[0]*p[7]*p[11]*p[16] + 2*p[0]*p[7]*p[11]*p[25] + 4*p[0]*p[7]*p[14]*p[19] - 2*p[0]*p[7]*p[14]*p[28] - 4*p[0]*p[8]*p[12]*p[16] + 2*p[0]*p[8]*p[12]*p[25] - 4*p[0]*p[8]*p[13]*p[19] + 2*p[0]*p[8]*p[13]*p[28] - 4*p[0]*p[9]*p[12]*p[19] + 2*p[0]*p[9]*p[12]*p[28] + 4*p[0]*p[9]*p[13]*p[16] - 2*p[0]*p[9]*p[13]*p[25] + 4*p[0]*p[10]*p[11]*p[19] - 2*p[0]*p[10]*p[11]*p[28] + 4*p[0]*p[10]*p[14]*p[16] - 2*p[0]*p[10]*p[14]*p[25] - 2*p[7]*p[10]*p[19] + 2*p[7]*p[10]*p[28] + 2*p[8]*p[9]*p[19] - 2*p[8]*p[9]*p[28] - 2*p[9]*p[9]*p[16] + 2*p[9]*p[9]*p[25] - 2*p[10]*p[10]*p[16] + 2*p[10]*p[10]*p[25] + 2*p[11]*p[14]*p[19] - 2*p[11]*p[14]*p[28] - 2*p[12]*p[13]*p[19] + 2*p[12]*p[13]*p[28] + 2*p[13]*p[13]*p[16] - 2*p[13]*p[13]*p[25] + 2*p[14]*p[14]*p[16] - 2*p[14]*p[14]*p[25]; + coeff[24] = -4*p[0]*p[7]*p[11]*p[17] + 2*p[0]*p[7]*p[11]*p[26] - 4*p[0]*p[7]*p[13]*p[22] + 2*p[0]*p[7]*p[13]*p[31] + 4*p[0]*p[7]*p[14]*p[20] - 2*p[0]*p[7]*p[14]*p[29] - 4*p[0]*p[8]*p[12]*p[17] + 2*p[0]*p[8]*p[12]*p[26] - 4*p[0]*p[8]*p[13]*p[20] + 2*p[0]*p[8]*p[13]*p[29] - 4*p[0]*p[8]*p[14]*p[22] + 2*p[0]*p[8]*p[14]*p[31] - 4*p[0]*p[9]*p[11]*p[22] + 2*p[0]*p[9]*p[11]*p[31] - 4*p[0]*p[9]*p[12]*p[20] + 2*p[0]*p[9]*p[12]*p[29] + 4*p[0]*p[9]*p[13]*p[17] - 2*p[0]*p[9]*p[13]*p[26] + 4*p[0]*p[10]*p[11]*p[20] - 2*p[0]*p[10]*p[11]*p[29] - 4*p[0]*p[10]*p[12]*p[22] + 2*p[0]*p[10]*p[12]*p[31] + 4*p[0]*p[10]*p[14]*p[17] - 2*p[0]*p[10]*p[14]*p[26] + 2*p[7]*p[9]*p[22] - 2*p[7]*p[9]*p[31] - 2*p[7]*p[10]*p[20] + 2*p[7]*p[10]*p[29] + 2*p[8]*p[9]*p[20] - 2*p[8]*p[9]*p[29] + 2*p[8]*p[10]*p[22] - 2*p[8]*p[10]*p[31] - 2*p[9]*p[9]*p[17] + 2*p[9]*p[9]*p[26] - 2*p[10]*p[10]*p[17] + 2*p[10]*p[10]*p[26] - 2*p[11]*p[13]*p[22] + 2*p[11]*p[13]*p[31] + 2*p[11]*p[14]*p[20] - 2*p[11]*p[14]*p[29] - 2*p[12]*p[13]*p[20] + 2*p[12]*p[13]*p[29] - 2*p[12]*p[14]*p[22] + 2*p[12]*p[14]*p[31] + 2*p[13]*p[13]*p[17] - 2*p[13]*p[13]*p[26] + 2*p[14]*p[14]*p[17] - 2*p[14]*p[14]*p[26]; + coeff[25] = 2*p[0]*p[7]*p[11]*p[15] + 2*p[0]*p[8]*p[12]*p[15] - 2*p[0]*p[9]*p[13]*p[15] - 2*p[0]*p[10]*p[14]*p[15] + 2*p[9]*p[9]*p[15] - 2*p[9]*p[9]*p[24] + 2*p[10]*p[10]*p[15] - 2*p[10]*p[10]*p[24] - 2*p[13]*p[13]*p[15] + 2*p[13]*p[13]*p[24] - 2*p[14]*p[14]*p[15] + 2*p[14]*p[14]*p[24]; + coeff[26] = 2*p[0]*p[7]*p[11]*p[16] - 2*p[0]*p[7]*p[14]*p[19] + 2*p[0]*p[8]*p[12]*p[16] + 2*p[0]*p[8]*p[13]*p[19] + 2*p[0]*p[9]*p[12]*p[19] - 2*p[0]*p[9]*p[13]*p[16] - 2*p[0]*p[10]*p[11]*p[19] - 2*p[0]*p[10]*p[14]*p[16] + 2*p[7]*p[10]*p[19] - 2*p[7]*p[10]*p[28] - 2*p[8]*p[9]*p[19] + 2*p[8]*p[9]*p[28] + 2*p[9]*p[9]*p[16] - 2*p[9]*p[9]*p[25] + 2*p[10]*p[10]*p[16] - 2*p[10]*p[10]*p[25] - 2*p[11]*p[14]*p[19] + 2*p[11]*p[14]*p[28] + 2*p[12]*p[13]*p[19] - 2*p[12]*p[13]*p[28] - 2*p[13]*p[13]*p[16] + 2*p[13]*p[13]*p[25] - 2*p[14]*p[14]*p[16] + 2*p[14]*p[14]*p[25]; + coeff[27] = 2*p[0]*p[7]*p[11]*p[17] + 2*p[0]*p[7]*p[13]*p[22] - 2*p[0]*p[7]*p[14]*p[20] + 2*p[0]*p[8]*p[12]*p[17] + 2*p[0]*p[8]*p[13]*p[20] + 2*p[0]*p[8]*p[14]*p[22] + 2*p[0]*p[9]*p[11]*p[22] + 2*p[0]*p[9]*p[12]*p[20] - 2*p[0]*p[9]*p[13]*p[17] - 2*p[0]*p[10]*p[11]*p[20] + 2*p[0]*p[10]*p[12]*p[22] - 2*p[0]*p[10]*p[14]*p[17] - 2*p[7]*p[9]*p[22] + 2*p[7]*p[9]*p[31] + 2*p[7]*p[10]*p[20] - 2*p[7]*p[10]*p[29] - 2*p[8]*p[9]*p[20] + 2*p[8]*p[9]*p[29] - 2*p[8]*p[10]*p[22] + 2*p[8]*p[10]*p[31] + 2*p[9]*p[9]*p[17] - 2*p[9]*p[9]*p[26] + 2*p[10]*p[10]*p[17] - 2*p[10]*p[10]*p[26] + 2*p[11]*p[13]*p[22] - 2*p[11]*p[13]*p[31] - 2*p[11]*p[14]*p[20] + 2*p[11]*p[14]*p[29] + 2*p[12]*p[13]*p[20] - 2*p[12]*p[13]*p[29] + 2*p[12]*p[14]*p[22] - 2*p[12]*p[14]*p[31] - 2*p[13]*p[13]*p[17] + 2*p[13]*p[13]*p[26] - 2*p[14]*p[14]*p[17] + 2*p[14]*p[14]*p[26]; + coeff[28] = 0; + coeff[29] = 2*(p[7]*p[11]*p[15] - p[7]*p[11]*p[24] + p[8]*p[12]*p[15] - p[8]*p[12]*p[24] - p[9]*p[13]*p[15] + p[9]*p[13]*p[24] - p[10]*p[14]*p[15] + p[10]*p[14]*p[24])*p[0]; + coeff[30] = 2*(p[7]*p[11]*p[16] - p[7]*p[11]*p[25] - p[7]*p[14]*p[19] + p[7]*p[14]*p[28] + p[8]*p[12]*p[16] - p[8]*p[12]*p[25] + p[8]*p[13]*p[19] - p[8]*p[13]*p[28] + p[9]*p[12]*p[19] - p[9]*p[12]*p[28] - p[9]*p[13]*p[16] + p[9]*p[13]*p[25] - p[10]*p[11]*p[19] + p[10]*p[11]*p[28] - p[10]*p[14]*p[16] + p[10]*p[14]*p[25])*p[0]; + coeff[31] = 2*(p[7]*p[11]*p[17] - p[7]*p[11]*p[26] + p[7]*p[13]*p[22] - p[7]*p[13]*p[31] - p[7]*p[14]*p[20] + p[7]*p[14]*p[29] + p[8]*p[12]*p[17] - p[8]*p[12]*p[26] + p[8]*p[13]*p[20] - p[8]*p[13]*p[29] + p[8]*p[14]*p[22] - p[8]*p[14]*p[31] + p[9]*p[11]*p[22] - p[9]*p[11]*p[31] + p[9]*p[12]*p[20] - p[9]*p[12]*p[29] - p[9]*p[13]*p[17] + p[9]*p[13]*p[26] - p[10]*p[11]*p[20] + p[10]*p[11]*p[29] + p[10]*p[12]*p[22] - p[10]*p[12]*p[31] - p[10]*p[14]*p[17] + p[10]*p[14]*p[26])*p[0]; + coeff[32] = 2*(-p[7]*p[11]*p[15] + p[7]*p[11]*p[24] - p[8]*p[12]*p[15] + p[8]*p[12]*p[24] + p[9]*p[13]*p[15] - p[9]*p[13]*p[24] + p[10]*p[14]*p[15] - p[10]*p[14]*p[24])*p[0]; + coeff[33] = 2*(-p[7]*p[11]*p[16] + p[7]*p[11]*p[25] + p[7]*p[14]*p[19] - p[7]*p[14]*p[28] - p[8]*p[12]*p[16] + p[8]*p[12]*p[25] - p[8]*p[13]*p[19] + p[8]*p[13]*p[28] - p[9]*p[12]*p[19] + p[9]*p[12]*p[28] + p[9]*p[13]*p[16] - p[9]*p[13]*p[25] + p[10]*p[11]*p[19] - p[10]*p[11]*p[28] + p[10]*p[14]*p[16] - p[10]*p[14]*p[25])*p[0]; + coeff[34] = 2*(-p[7]*p[11]*p[17] + p[7]*p[11]*p[26] - p[7]*p[13]*p[22] + p[7]*p[13]*p[31] + p[7]*p[14]*p[20] - p[7]*p[14]*p[29] - p[8]*p[12]*p[17] + p[8]*p[12]*p[26] - p[8]*p[13]*p[20] + p[8]*p[13]*p[29] - p[8]*p[14]*p[22] + p[8]*p[14]*p[31] - p[9]*p[11]*p[22] + p[9]*p[11]*p[31] - p[9]*p[12]*p[20] + p[9]*p[12]*p[29] + p[9]*p[13]*p[17] - p[9]*p[13]*p[26] + p[10]*p[11]*p[20] - p[10]*p[11]*p[29] - p[10]*p[12]*p[22] + p[10]*p[12]*p[31] + p[10]*p[14]*p[17] - p[10]*p[14]*p[26])*p[0]; + coeff[35] = -2*p[0]*p[7]*p[9]*p[23] + 2*p[0]*p[7]*p[10]*p[21] - 2*p[0]*p[8]*p[9]*p[21] - 2*p[0]*p[8]*p[10]*p[23] + 2*p[0]*p[9]*p[9]*p[18] + 2*p[0]*p[10]*p[10]*p[18] + 2*p[0]*p[11]*p[13]*p[23] - 2*p[0]*p[11]*p[14]*p[21] + 2*p[0]*p[12]*p[13]*p[21] + 2*p[0]*p[12]*p[14]*p[23] - 2*p[0]*p[13]*p[13]*p[18] - 2*p[0]*p[14]*p[14]*p[18] - p[7]*p[11]*p[18] + p[7]*p[11]*p[27] - p[7]*p[13]*p[23] + p[7]*p[13]*p[32] + p[7]*p[14]*p[21] - p[7]*p[14]*p[30] - p[8]*p[12]*p[18] + p[8]*p[12]*p[27] - p[8]*p[13]*p[21] + p[8]*p[13]*p[30] - p[8]*p[14]*p[23] + p[8]*p[14]*p[32] - p[9]*p[11]*p[23] + p[9]*p[11]*p[32] - p[9]*p[12]*p[21] + p[9]*p[12]*p[30] + p[9]*p[13]*p[18] - p[9]*p[13]*p[27] + p[10]*p[11]*p[21] - p[10]*p[11]*p[30] - p[10]*p[12]*p[23] + p[10]*p[12]*p[32] + p[10]*p[14]*p[18] - p[10]*p[14]*p[27]; + coeff[36] = 2*p[0]*p[9]*p[9]*p[15] + 2*p[0]*p[10]*p[10]*p[15] - 2*p[0]*p[13]*p[13]*p[15] - 2*p[0]*p[14]*p[14]*p[15] - 2*p[7]*p[11]*p[15] + p[7]*p[11]*p[24] - 2*p[8]*p[12]*p[15] + p[8]*p[12]*p[24] + 2*p[9]*p[13]*p[15] - p[9]*p[13]*p[24] + 2*p[10]*p[14]*p[15] - p[10]*p[14]*p[24]; + coeff[37] = 2*p[0]*p[7]*p[10]*p[19] - 2*p[0]*p[8]*p[9]*p[19] + 2*p[0]*p[9]*p[9]*p[16] + 2*p[0]*p[10]*p[10]*p[16] - 2*p[0]*p[11]*p[14]*p[19] + 2*p[0]*p[12]*p[13]*p[19] - 2*p[0]*p[13]*p[13]*p[16] - 2*p[0]*p[14]*p[14]*p[16] - 2*p[7]*p[11]*p[16] + p[7]*p[11]*p[25] + 2*p[7]*p[14]*p[19] - p[7]*p[14]*p[28] - 2*p[8]*p[12]*p[16] + p[8]*p[12]*p[25] - 2*p[8]*p[13]*p[19] + p[8]*p[13]*p[28] - 2*p[9]*p[12]*p[19] + p[9]*p[12]*p[28] + 2*p[9]*p[13]*p[16] - p[9]*p[13]*p[25] + 2*p[10]*p[11]*p[19] - p[10]*p[11]*p[28] + 2*p[10]*p[14]*p[16] - p[10]*p[14]*p[25]; + coeff[38] = -2*p[0]*p[7]*p[9]*p[22] + 2*p[0]*p[7]*p[10]*p[20] - 2*p[0]*p[8]*p[9]*p[20] - 2*p[0]*p[8]*p[10]*p[22] + 2*p[0]*p[9]*p[9]*p[17] + 2*p[0]*p[10]*p[10]*p[17] + 2*p[0]*p[11]*p[13]*p[22] - 2*p[0]*p[11]*p[14]*p[20] + 2*p[0]*p[12]*p[13]*p[20] + 2*p[0]*p[12]*p[14]*p[22] - 2*p[0]*p[13]*p[13]*p[17] - 2*p[0]*p[14]*p[14]*p[17] - 2*p[7]*p[11]*p[17] + p[7]*p[11]*p[26] - 2*p[7]*p[13]*p[22] + p[7]*p[13]*p[31] + 2*p[7]*p[14]*p[20] - p[7]*p[14]*p[29] - 2*p[8]*p[12]*p[17] + p[8]*p[12]*p[26] - 2*p[8]*p[13]*p[20] + p[8]*p[13]*p[29] - 2*p[8]*p[14]*p[22] + p[8]*p[14]*p[31] - 2*p[9]*p[11]*p[22] + p[9]*p[11]*p[31] - 2*p[9]*p[12]*p[20] + p[9]*p[12]*p[29] + 2*p[9]*p[13]*p[17] - p[9]*p[13]*p[26] + 2*p[10]*p[11]*p[20] - p[10]*p[11]*p[29] - 2*p[10]*p[12]*p[22] + p[10]*p[12]*p[31] + 2*p[10]*p[14]*p[17] - p[10]*p[14]*p[26]; + coeff[39] = (p[7]*p[11] + p[8]*p[12] - p[9]*p[13] - p[10]*p[14])*p[15]; + coeff[40] = p[7]*p[11]*p[16] - p[7]*p[14]*p[19] + p[8]*p[12]*p[16] + p[8]*p[13]*p[19] + p[9]*p[12]*p[19] - p[9]*p[13]*p[16] - p[10]*p[11]*p[19] - p[10]*p[14]*p[16]; + coeff[41] = p[7]*p[11]*p[17] + p[7]*p[13]*p[22] - p[7]*p[14]*p[20] + p[8]*p[12]*p[17] + p[8]*p[13]*p[20] + p[8]*p[14]*p[22] + p[9]*p[11]*p[22] + p[9]*p[12]*p[20] - p[9]*p[13]*p[17] - p[10]*p[11]*p[20] + p[10]*p[12]*p[22] - p[10]*p[14]*p[17]; + coeff[42] = 2*(p[7]*p[9]*p[23] - p[7]*p[9]*p[32] - p[7]*p[10]*p[21] + p[7]*p[10]*p[30] + p[8]*p[9]*p[21] - p[8]*p[9]*p[30] + p[8]*p[10]*p[23] - p[8]*p[10]*p[32] - p[9]*p[9]*p[18] + p[9]*p[9]*p[27] - p[10]*p[10]*p[18] + p[10]*p[10]*p[27] - p[11]*p[13]*p[23] + p[11]*p[13]*p[32] + p[11]*p[14]*p[21] - p[11]*p[14]*p[30] - p[12]*p[13]*p[21] + p[12]*p[13]*p[30] - p[12]*p[14]*p[23] + p[12]*p[14]*p[32] + p[13]*p[13]*p[18] - p[13]*p[13]*p[27] + p[14]*p[14]*p[18] - p[14]*p[14]*p[27])*p[0]; + coeff[43] = -4*p[0]*p[9]*p[9]*p[15] + 2*p[0]*p[9]*p[9]*p[24] - 4*p[0]*p[10]*p[10]*p[15] + 2*p[0]*p[10]*p[10]*p[24] + 4*p[0]*p[13]*p[13]*p[15] - 2*p[0]*p[13]*p[13]*p[24] + 4*p[0]*p[14]*p[14]*p[15] - 2*p[0]*p[14]*p[14]*p[24] + 2*p[7]*p[11]*p[15] - 2*p[7]*p[11]*p[24] + 2*p[8]*p[12]*p[15] - 2*p[8]*p[12]*p[24] - 2*p[9]*p[13]*p[15] + 2*p[9]*p[13]*p[24] - 2*p[10]*p[14]*p[15] + 2*p[10]*p[14]*p[24]; + coeff[44] = -4*p[0]*p[7]*p[10]*p[19] + 2*p[0]*p[7]*p[10]*p[28] + 4*p[0]*p[8]*p[9]*p[19] - 2*p[0]*p[8]*p[9]*p[28] - 4*p[0]*p[9]*p[9]*p[16] + 2*p[0]*p[9]*p[9]*p[25] - 4*p[0]*p[10]*p[10]*p[16] + 2*p[0]*p[10]*p[10]*p[25] + 4*p[0]*p[11]*p[14]*p[19] - 2*p[0]*p[11]*p[14]*p[28] - 4*p[0]*p[12]*p[13]*p[19] + 2*p[0]*p[12]*p[13]*p[28] + 4*p[0]*p[13]*p[13]*p[16] - 2*p[0]*p[13]*p[13]*p[25] + 4*p[0]*p[14]*p[14]*p[16] - 2*p[0]*p[14]*p[14]*p[25] + 2*p[7]*p[11]*p[16] - 2*p[7]*p[11]*p[25] - 2*p[7]*p[14]*p[19] + 2*p[7]*p[14]*p[28] + 2*p[8]*p[12]*p[16] - 2*p[8]*p[12]*p[25] + 2*p[8]*p[13]*p[19] - 2*p[8]*p[13]*p[28] + 2*p[9]*p[12]*p[19] - 2*p[9]*p[12]*p[28] - 2*p[9]*p[13]*p[16] + 2*p[9]*p[13]*p[25] - 2*p[10]*p[11]*p[19] + 2*p[10]*p[11]*p[28] - 2*p[10]*p[14]*p[16] + 2*p[10]*p[14]*p[25]; + coeff[45] = 4*p[0]*p[7]*p[9]*p[22] - 2*p[0]*p[7]*p[9]*p[31] - 4*p[0]*p[7]*p[10]*p[20] + 2*p[0]*p[7]*p[10]*p[29] + 4*p[0]*p[8]*p[9]*p[20] - 2*p[0]*p[8]*p[9]*p[29] + 4*p[0]*p[8]*p[10]*p[22] - 2*p[0]*p[8]*p[10]*p[31] - 4*p[0]*p[9]*p[9]*p[17] + 2*p[0]*p[9]*p[9]*p[26] - 4*p[0]*p[10]*p[10]*p[17] + 2*p[0]*p[10]*p[10]*p[26] - 4*p[0]*p[11]*p[13]*p[22] + 2*p[0]*p[11]*p[13]*p[31] + 4*p[0]*p[11]*p[14]*p[20] - 2*p[0]*p[11]*p[14]*p[29] - 4*p[0]*p[12]*p[13]*p[20] + 2*p[0]*p[12]*p[13]*p[29] - 4*p[0]*p[12]*p[14]*p[22] + 2*p[0]*p[12]*p[14]*p[31] + 4*p[0]*p[13]*p[13]*p[17] - 2*p[0]*p[13]*p[13]*p[26] + 4*p[0]*p[14]*p[14]*p[17] - 2*p[0]*p[14]*p[14]*p[26] + 2*p[7]*p[11]*p[17] - 2*p[7]*p[11]*p[26] + 2*p[7]*p[13]*p[22] - 2*p[7]*p[13]*p[31] - 2*p[7]*p[14]*p[20] + 2*p[7]*p[14]*p[29] + 2*p[8]*p[12]*p[17] - 2*p[8]*p[12]*p[26] + 2*p[8]*p[13]*p[20] - 2*p[8]*p[13]*p[29] + 2*p[8]*p[14]*p[22] - 2*p[8]*p[14]*p[31] + 2*p[9]*p[11]*p[22] - 2*p[9]*p[11]*p[31] + 2*p[9]*p[12]*p[20] - 2*p[9]*p[12]*p[29] - 2*p[9]*p[13]*p[17] + 2*p[9]*p[13]*p[26] - 2*p[10]*p[11]*p[20] + 2*p[10]*p[11]*p[29] + 2*p[10]*p[12]*p[22] - 2*p[10]*p[12]*p[31] - 2*p[10]*p[14]*p[17] + 2*p[10]*p[14]*p[26]; + coeff[46] = 2*p[0]*p[9]*p[9]*p[15] + 2*p[0]*p[10]*p[10]*p[15] - 2*p[0]*p[13]*p[13]*p[15] - 2*p[0]*p[14]*p[14]*p[15] - 2*p[7]*p[11]*p[15] + 2*p[7]*p[11]*p[24] - 2*p[8]*p[12]*p[15] + 2*p[8]*p[12]*p[24] + 2*p[9]*p[13]*p[15] - 2*p[9]*p[13]*p[24] + 2*p[10]*p[14]*p[15] - 2*p[10]*p[14]*p[24]; + coeff[47] = 2*p[0]*p[7]*p[10]*p[19] - 2*p[0]*p[8]*p[9]*p[19] + 2*p[0]*p[9]*p[9]*p[16] + 2*p[0]*p[10]*p[10]*p[16] - 2*p[0]*p[11]*p[14]*p[19] + 2*p[0]*p[12]*p[13]*p[19] - 2*p[0]*p[13]*p[13]*p[16] - 2*p[0]*p[14]*p[14]*p[16] - 2*p[7]*p[11]*p[16] + 2*p[7]*p[11]*p[25] + 2*p[7]*p[14]*p[19] - 2*p[7]*p[14]*p[28] - 2*p[8]*p[12]*p[16] + 2*p[8]*p[12]*p[25] - 2*p[8]*p[13]*p[19] + 2*p[8]*p[13]*p[28] - 2*p[9]*p[12]*p[19] + 2*p[9]*p[12]*p[28] + 2*p[9]*p[13]*p[16] - 2*p[9]*p[13]*p[25] + 2*p[10]*p[11]*p[19] - 2*p[10]*p[11]*p[28] + 2*p[10]*p[14]*p[16] - 2*p[10]*p[14]*p[25]; + coeff[48] = -2*p[0]*p[7]*p[9]*p[22] + 2*p[0]*p[7]*p[10]*p[20] - 2*p[0]*p[8]*p[9]*p[20] - 2*p[0]*p[8]*p[10]*p[22] + 2*p[0]*p[9]*p[9]*p[17] + 2*p[0]*p[10]*p[10]*p[17] + 2*p[0]*p[11]*p[13]*p[22] - 2*p[0]*p[11]*p[14]*p[20] + 2*p[0]*p[12]*p[13]*p[20] + 2*p[0]*p[12]*p[14]*p[22] - 2*p[0]*p[13]*p[13]*p[17] - 2*p[0]*p[14]*p[14]*p[17] - 2*p[7]*p[11]*p[17] + 2*p[7]*p[11]*p[26] - 2*p[7]*p[13]*p[22] + 2*p[7]*p[13]*p[31] + 2*p[7]*p[14]*p[20] - 2*p[7]*p[14]*p[29] - 2*p[8]*p[12]*p[17] + 2*p[8]*p[12]*p[26] - 2*p[8]*p[13]*p[20] + 2*p[8]*p[13]*p[29] - 2*p[8]*p[14]*p[22] + 2*p[8]*p[14]*p[31] - 2*p[9]*p[11]*p[22] + 2*p[9]*p[11]*p[31] - 2*p[9]*p[12]*p[20] + 2*p[9]*p[12]*p[29] + 2*p[9]*p[13]*p[17] - 2*p[9]*p[13]*p[26] + 2*p[10]*p[11]*p[20] - 2*p[10]*p[11]*p[29] - 2*p[10]*p[12]*p[22] + 2*p[10]*p[12]*p[31] + 2*p[10]*p[14]*p[17] - 2*p[10]*p[14]*p[26]; + coeff[49] = 0; + coeff[50] = 2*(p[9]*p[9]*p[15] - p[9]*p[9]*p[24] + p[10]*p[10]*p[15] - p[10]*p[10]*p[24] - p[13]*p[13]*p[15] + p[13]*p[13]*p[24] - p[14]*p[14]*p[15] + p[14]*p[14]*p[24])*p[0]; + coeff[51] = 2*(p[7]*p[10]*p[19] - p[7]*p[10]*p[28] - p[8]*p[9]*p[19] + p[8]*p[9]*p[28] + p[9]*p[9]*p[16] - p[9]*p[9]*p[25] + p[10]*p[10]*p[16] - p[10]*p[10]*p[25] - p[11]*p[14]*p[19] + p[11]*p[14]*p[28] + p[12]*p[13]*p[19] - p[12]*p[13]*p[28] - p[13]*p[13]*p[16] + p[13]*p[13]*p[25] - p[14]*p[14]*p[16] + p[14]*p[14]*p[25])*p[0]; + coeff[52] = 2*(-p[7]*p[9]*p[22] + p[7]*p[9]*p[31] + p[7]*p[10]*p[20] - p[7]*p[10]*p[29] - p[8]*p[9]*p[20] + p[8]*p[9]*p[29] - p[8]*p[10]*p[22] + p[8]*p[10]*p[31] + p[9]*p[9]*p[17] - p[9]*p[9]*p[26] + p[10]*p[10]*p[17] - p[10]*p[10]*p[26] + p[11]*p[13]*p[22] - p[11]*p[13]*p[31] - p[11]*p[14]*p[20] + p[11]*p[14]*p[29] + p[12]*p[13]*p[20] - p[12]*p[13]*p[29] + p[12]*p[14]*p[22] - p[12]*p[14]*p[31] - p[13]*p[13]*p[17] + p[13]*p[13]*p[26] - p[14]*p[14]*p[17] + p[14]*p[14]*p[26])*p[0]; + coeff[53] = 2*(-p[9]*p[9]*p[15] + p[9]*p[9]*p[24] - p[10]*p[10]*p[15] + p[10]*p[10]*p[24] + p[13]*p[13]*p[15] - p[13]*p[13]*p[24] + p[14]*p[14]*p[15] - p[14]*p[14]*p[24])*p[0]; + coeff[54] = 2*(-p[7]*p[10]*p[19] + p[7]*p[10]*p[28] + p[8]*p[9]*p[19] - p[8]*p[9]*p[28] - p[9]*p[9]*p[16] + p[9]*p[9]*p[25] - p[10]*p[10]*p[16] + p[10]*p[10]*p[25] + p[11]*p[14]*p[19] - p[11]*p[14]*p[28] - p[12]*p[13]*p[19] + p[12]*p[13]*p[28] + p[13]*p[13]*p[16] - p[13]*p[13]*p[25] + p[14]*p[14]*p[16] - p[14]*p[14]*p[25])*p[0]; + coeff[55] = 2*(p[7]*p[9]*p[22] - p[7]*p[9]*p[31] - p[7]*p[10]*p[20] + p[7]*p[10]*p[29] + p[8]*p[9]*p[20] - p[8]*p[9]*p[29] + p[8]*p[10]*p[22] - p[8]*p[10]*p[31] - p[9]*p[9]*p[17] + p[9]*p[9]*p[26] - p[10]*p[10]*p[17] + p[10]*p[10]*p[26] - p[11]*p[13]*p[22] + p[11]*p[13]*p[31] + p[11]*p[14]*p[20] - p[11]*p[14]*p[29] - p[12]*p[13]*p[20] + p[12]*p[13]*p[29] - p[12]*p[14]*p[22] + p[12]*p[14]*p[31] + p[13]*p[13]*p[17] - p[13]*p[13]*p[26] + p[14]*p[14]*p[17] - p[14]*p[14]*p[26])*p[0]; + coeff[56] = -p[2] + p[5] + p[7]*p[8]*p[23] - p[7]*p[8]*p[32] - p[7]*p[10]*p[18] + p[7]*p[10]*p[27] + p[8]*p[8]*p[21] - p[8]*p[8]*p[30] - p[8]*p[9]*p[18] + p[8]*p[9]*p[27] - p[9]*p[10]*p[23] + p[9]*p[10]*p[32] + p[10]*p[10]*p[21] - p[10]*p[10]*p[30] + p[11]*p[12]*p[23] - p[11]*p[12]*p[32] - p[11]*p[14]*p[18] + p[11]*p[14]*p[27] + p[12]*p[12]*p[21] - p[12]*p[12]*p[30] - p[12]*p[13]*p[18] + p[12]*p[13]*p[27] - p[13]*p[14]*p[23] + p[13]*p[14]*p[32] + p[14]*p[14]*p[21] - p[14]*p[14]*p[30] - p[21] + p[30]; + coeff[57] = -2*p[7]*p[10]*p[15] + p[7]*p[10]*p[24] - 2*p[8]*p[9]*p[15] + p[8]*p[9]*p[24] - 2*p[11]*p[14]*p[15] + p[11]*p[14]*p[24] - 2*p[12]*p[13]*p[15] + p[12]*p[13]*p[24]; + coeff[58] = -2*p[7]*p[10]*p[16] + p[7]*p[10]*p[25] + 2*p[8]*p[8]*p[19] - p[8]*p[8]*p[28] - 2*p[8]*p[9]*p[16] + p[8]*p[9]*p[25] + 2*p[10]*p[10]*p[19] - p[10]*p[10]*p[28] - 2*p[11]*p[14]*p[16] + p[11]*p[14]*p[25] + 2*p[12]*p[12]*p[19] - p[12]*p[12]*p[28] - 2*p[12]*p[13]*p[16] + p[12]*p[13]*p[25] + 2*p[14]*p[14]*p[19] - p[14]*p[14]*p[28] - 2*p[19] + p[28]; + coeff[59] = 2*p[7]*p[8]*p[22] - p[7]*p[8]*p[31] - 2*p[7]*p[10]*p[17] + p[7]*p[10]*p[26] + 2*p[8]*p[8]*p[20] - p[8]*p[8]*p[29] - 2*p[8]*p[9]*p[17] + p[8]*p[9]*p[26] - 2*p[9]*p[10]*p[22] + p[9]*p[10]*p[31] + 2*p[10]*p[10]*p[20] - p[10]*p[10]*p[29] + 2*p[11]*p[12]*p[22] - p[11]*p[12]*p[31] - 2*p[11]*p[14]*p[17] + p[11]*p[14]*p[26] + 2*p[12]*p[12]*p[20] - p[12]*p[12]*p[29] - 2*p[12]*p[13]*p[17] + p[12]*p[13]*p[26] - 2*p[13]*p[14]*p[22] + p[13]*p[14]*p[31] + 2*p[14]*p[14]*p[20] - p[14]*p[14]*p[29] - 2*p[20] + p[29]; + coeff[60] = (p[7]*p[10] + p[8]*p[9] + p[11]*p[14] + p[12]*p[13])*p[15]; + coeff[61] = p[7]*p[10]*p[16] - p[8]*p[8]*p[19] + p[8]*p[9]*p[16] - p[10]*p[10]*p[19] + p[11]*p[14]*p[16] - p[12]*p[12]*p[19] + p[12]*p[13]*p[16] - p[14]*p[14]*p[19] + p[19]; + coeff[62] = -p[7]*p[8]*p[22] + p[7]*p[10]*p[17] - p[8]*p[8]*p[20] + p[8]*p[9]*p[17] + p[9]*p[10]*p[22] - p[10]*p[10]*p[20] - p[11]*p[12]*p[22] + p[11]*p[14]*p[17] - p[12]*p[12]*p[20] + p[12]*p[13]*p[17] + p[13]*p[14]*p[22] - p[14]*p[14]*p[20] + p[20]; + coeff[63] = 0; + coeff[64] = 2*p[7]*p[10]*p[15] - 2*p[7]*p[10]*p[24] + 2*p[8]*p[9]*p[15] - 2*p[8]*p[9]*p[24] + 2*p[11]*p[14]*p[15] - 2*p[11]*p[14]*p[24] + 2*p[12]*p[13]*p[15] - 2*p[12]*p[13]*p[24]; + coeff[65] = 2*p[7]*p[10]*p[16] - 2*p[7]*p[10]*p[25] - 2*p[8]*p[8]*p[19] + 2*p[8]*p[8]*p[28] + 2*p[8]*p[9]*p[16] - 2*p[8]*p[9]*p[25] - 2*p[10]*p[10]*p[19] + 2*p[10]*p[10]*p[28] + 2*p[11]*p[14]*p[16] - 2*p[11]*p[14]*p[25] - 2*p[12]*p[12]*p[19] + 2*p[12]*p[12]*p[28] + 2*p[12]*p[13]*p[16] - 2*p[12]*p[13]*p[25] - 2*p[14]*p[14]*p[19] + 2*p[14]*p[14]*p[28] + 2*p[19] - 2*p[28]; + coeff[66] = -2*p[7]*p[8]*p[22] + 2*p[7]*p[8]*p[31] + 2*p[7]*p[10]*p[17] - 2*p[7]*p[10]*p[26] - 2*p[8]*p[8]*p[20] + 2*p[8]*p[8]*p[29] + 2*p[8]*p[9]*p[17] - 2*p[8]*p[9]*p[26] + 2*p[9]*p[10]*p[22] - 2*p[9]*p[10]*p[31] - 2*p[10]*p[10]*p[20] + 2*p[10]*p[10]*p[29] - 2*p[11]*p[12]*p[22] + 2*p[11]*p[12]*p[31] + 2*p[11]*p[14]*p[17] - 2*p[11]*p[14]*p[26] - 2*p[12]*p[12]*p[20] + 2*p[12]*p[12]*p[29] + 2*p[12]*p[13]*p[17] - 2*p[12]*p[13]*p[26] + 2*p[13]*p[14]*p[22] - 2*p[13]*p[14]*p[31] - 2*p[14]*p[14]*p[20] + 2*p[14]*p[14]*p[29] + 2*p[20] - 2*p[29]; + coeff[67] = -2*p[7]*p[10]*p[15] + 2*p[7]*p[10]*p[24] - 2*p[8]*p[9]*p[15] + 2*p[8]*p[9]*p[24] - 2*p[11]*p[14]*p[15] + 2*p[11]*p[14]*p[24] - 2*p[12]*p[13]*p[15] + 2*p[12]*p[13]*p[24]; + coeff[68] = -2*p[7]*p[10]*p[16] + 2*p[7]*p[10]*p[25] + 2*p[8]*p[8]*p[19] - 2*p[8]*p[8]*p[28] - 2*p[8]*p[9]*p[16] + 2*p[8]*p[9]*p[25] + 2*p[10]*p[10]*p[19] - 2*p[10]*p[10]*p[28] - 2*p[11]*p[14]*p[16] + 2*p[11]*p[14]*p[25] + 2*p[12]*p[12]*p[19] - 2*p[12]*p[12]*p[28] - 2*p[12]*p[13]*p[16] + 2*p[12]*p[13]*p[25] + 2*p[14]*p[14]*p[19] - 2*p[14]*p[14]*p[28] - 2*p[19] + 2*p[28]; + coeff[69] = 2*p[7]*p[8]*p[22] - 2*p[7]*p[8]*p[31] - 2*p[7]*p[10]*p[17] + 2*p[7]*p[10]*p[26] + 2*p[8]*p[8]*p[20] - 2*p[8]*p[8]*p[29] - 2*p[8]*p[9]*p[17] + 2*p[8]*p[9]*p[26] - 2*p[9]*p[10]*p[22] + 2*p[9]*p[10]*p[31] + 2*p[10]*p[10]*p[20] - 2*p[10]*p[10]*p[29] + 2*p[11]*p[12]*p[22] - 2*p[11]*p[12]*p[31] - 2*p[11]*p[14]*p[17] + 2*p[11]*p[14]*p[26] + 2*p[12]*p[12]*p[20] - 2*p[12]*p[12]*p[29] - 2*p[12]*p[13]*p[17] + 2*p[12]*p[13]*p[26] - 2*p[13]*p[14]*p[22] + 2*p[13]*p[14]*p[31] + 2*p[14]*p[14]*p[20] - 2*p[14]*p[14]*p[29] - 2*p[20] + 2*p[29]; + coeff[70] = 2*p[0]*p[7]*p[11]*p[21] - 2*p[0]*p[7]*p[12]*p[23] + 2*p[0]*p[7]*p[14]*p[18] - 2*p[0]*p[8]*p[11]*p[23] - 2*p[0]*p[8]*p[12]*p[21] + 2*p[0]*p[8]*p[13]*p[18] + 2*p[0]*p[9]*p[12]*p[18] + 2*p[0]*p[9]*p[13]*p[21] + 2*p[0]*p[9]*p[14]*p[23] + 2*p[0]*p[10]*p[11]*p[18] + 2*p[0]*p[10]*p[13]*p[23] - 2*p[0]*p[10]*p[14]*p[21] + p[7]*p[8]*p[23] - p[7]*p[8]*p[32] - p[7]*p[10]*p[18] + p[7]*p[10]*p[27] + p[8]*p[8]*p[21] - p[8]*p[8]*p[30] - p[8]*p[9]*p[18] + p[8]*p[9]*p[27] - p[9]*p[10]*p[23] + p[9]*p[10]*p[32] + p[10]*p[10]*p[21] - p[10]*p[10]*p[30] - p[11]*p[12]*p[23] + p[11]*p[12]*p[32] + p[11]*p[14]*p[18] - p[11]*p[14]*p[27] - p[12]*p[12]*p[21] + p[12]*p[12]*p[30] + p[12]*p[13]*p[18] - p[12]*p[13]*p[27] + p[13]*p[14]*p[23] - p[13]*p[14]*p[32] - p[14]*p[14]*p[21] + p[14]*p[14]*p[30]; + coeff[71] = 2*p[0]*p[7]*p[14]*p[15] + 2*p[0]*p[8]*p[13]*p[15] + 2*p[0]*p[9]*p[12]*p[15] + 2*p[0]*p[10]*p[11]*p[15] - 2*p[7]*p[10]*p[15] + p[7]*p[10]*p[24] - 2*p[8]*p[9]*p[15] + p[8]*p[9]*p[24] + 2*p[11]*p[14]*p[15] - p[11]*p[14]*p[24] + 2*p[12]*p[13]*p[15] - p[12]*p[13]*p[24]; + coeff[72] = 2*p[0]*p[7]*p[11]*p[19] + 2*p[0]*p[7]*p[14]*p[16] - 2*p[0]*p[8]*p[12]*p[19] + 2*p[0]*p[8]*p[13]*p[16] + 2*p[0]*p[9]*p[12]*p[16] + 2*p[0]*p[9]*p[13]*p[19] + 2*p[0]*p[10]*p[11]*p[16] - 2*p[0]*p[10]*p[14]*p[19] - 2*p[7]*p[10]*p[16] + p[7]*p[10]*p[25] + 2*p[8]*p[8]*p[19] - p[8]*p[8]*p[28] - 2*p[8]*p[9]*p[16] + p[8]*p[9]*p[25] + 2*p[10]*p[10]*p[19] - p[10]*p[10]*p[28] + 2*p[11]*p[14]*p[16] - p[11]*p[14]*p[25] - 2*p[12]*p[12]*p[19] + p[12]*p[12]*p[28] + 2*p[12]*p[13]*p[16] - p[12]*p[13]*p[25] - 2*p[14]*p[14]*p[19] + p[14]*p[14]*p[28]; + coeff[73] = 2*p[0]*p[7]*p[11]*p[20] - 2*p[0]*p[7]*p[12]*p[22] + 2*p[0]*p[7]*p[14]*p[17] - 2*p[0]*p[8]*p[11]*p[22] - 2*p[0]*p[8]*p[12]*p[20] + 2*p[0]*p[8]*p[13]*p[17] + 2*p[0]*p[9]*p[12]*p[17] + 2*p[0]*p[9]*p[13]*p[20] + 2*p[0]*p[9]*p[14]*p[22] + 2*p[0]*p[10]*p[11]*p[17] + 2*p[0]*p[10]*p[13]*p[22] - 2*p[0]*p[10]*p[14]*p[20] + 2*p[7]*p[8]*p[22] - p[7]*p[8]*p[31] - 2*p[7]*p[10]*p[17] + p[7]*p[10]*p[26] + 2*p[8]*p[8]*p[20] - p[8]*p[8]*p[29] - 2*p[8]*p[9]*p[17] + p[8]*p[9]*p[26] - 2*p[9]*p[10]*p[22] + p[9]*p[10]*p[31] + 2*p[10]*p[10]*p[20] - p[10]*p[10]*p[29] - 2*p[11]*p[12]*p[22] + p[11]*p[12]*p[31] + 2*p[11]*p[14]*p[17] - p[11]*p[14]*p[26] - 2*p[12]*p[12]*p[20] + p[12]*p[12]*p[29] + 2*p[12]*p[13]*p[17] - p[12]*p[13]*p[26] + 2*p[13]*p[14]*p[22] - p[13]*p[14]*p[31] - 2*p[14]*p[14]*p[20] + p[14]*p[14]*p[29]; + coeff[74] = (p[7]*p[10] + p[8]*p[9] - p[11]*p[14] - p[12]*p[13])*p[15]; + coeff[75] = p[7]*p[10]*p[16] - p[8]*p[8]*p[19] + p[8]*p[9]*p[16] - p[10]*p[10]*p[19] - p[11]*p[14]*p[16] + p[12]*p[12]*p[19] - p[12]*p[13]*p[16] + p[14]*p[14]*p[19]; + coeff[76] = -p[7]*p[8]*p[22] + p[7]*p[10]*p[17] - p[8]*p[8]*p[20] + p[8]*p[9]*p[17] + p[9]*p[10]*p[22] - p[10]*p[10]*p[20] + p[11]*p[12]*p[22] - p[11]*p[14]*p[17] + p[12]*p[12]*p[20] - p[12]*p[13]*p[17] - p[13]*p[14]*p[22] + p[14]*p[14]*p[20]; + coeff[77] = 2*(-p[7]*p[11]*p[21] + p[7]*p[11]*p[30] + p[7]*p[12]*p[23] - p[7]*p[12]*p[32] - p[7]*p[14]*p[18] + p[7]*p[14]*p[27] + p[8]*p[11]*p[23] - p[8]*p[11]*p[32] + p[8]*p[12]*p[21] - p[8]*p[12]*p[30] - p[8]*p[13]*p[18] + p[8]*p[13]*p[27] - p[9]*p[12]*p[18] + p[9]*p[12]*p[27] - p[9]*p[13]*p[21] + p[9]*p[13]*p[30] - p[9]*p[14]*p[23] + p[9]*p[14]*p[32] - p[10]*p[11]*p[18] + p[10]*p[11]*p[27] - p[10]*p[13]*p[23] + p[10]*p[13]*p[32] + p[10]*p[14]*p[21] - p[10]*p[14]*p[30])*p[0]; + coeff[78] = -4*p[0]*p[7]*p[14]*p[15] + 2*p[0]*p[7]*p[14]*p[24] - 4*p[0]*p[8]*p[13]*p[15] + 2*p[0]*p[8]*p[13]*p[24] - 4*p[0]*p[9]*p[12]*p[15] + 2*p[0]*p[9]*p[12]*p[24] - 4*p[0]*p[10]*p[11]*p[15] + 2*p[0]*p[10]*p[11]*p[24] + 2*p[7]*p[10]*p[15] - 2*p[7]*p[10]*p[24] + 2*p[8]*p[9]*p[15] - 2*p[8]*p[9]*p[24] - 2*p[11]*p[14]*p[15] + 2*p[11]*p[14]*p[24] - 2*p[12]*p[13]*p[15] + 2*p[12]*p[13]*p[24]; + coeff[79] = -4*p[0]*p[7]*p[11]*p[19] + 2*p[0]*p[7]*p[11]*p[28] - 4*p[0]*p[7]*p[14]*p[16] + 2*p[0]*p[7]*p[14]*p[25] + 4*p[0]*p[8]*p[12]*p[19] - 2*p[0]*p[8]*p[12]*p[28] - 4*p[0]*p[8]*p[13]*p[16] + 2*p[0]*p[8]*p[13]*p[25] - 4*p[0]*p[9]*p[12]*p[16] + 2*p[0]*p[9]*p[12]*p[25] - 4*p[0]*p[9]*p[13]*p[19] + 2*p[0]*p[9]*p[13]*p[28] - 4*p[0]*p[10]*p[11]*p[16] + 2*p[0]*p[10]*p[11]*p[25] + 4*p[0]*p[10]*p[14]*p[19] - 2*p[0]*p[10]*p[14]*p[28] + 2*p[7]*p[10]*p[16] - 2*p[7]*p[10]*p[25] - 2*p[8]*p[8]*p[19] + 2*p[8]*p[8]*p[28] + 2*p[8]*p[9]*p[16] - 2*p[8]*p[9]*p[25] - 2*p[10]*p[10]*p[19] + 2*p[10]*p[10]*p[28] - 2*p[11]*p[14]*p[16] + 2*p[11]*p[14]*p[25] + 2*p[12]*p[12]*p[19] - 2*p[12]*p[12]*p[28] - 2*p[12]*p[13]*p[16] + 2*p[12]*p[13]*p[25] + 2*p[14]*p[14]*p[19] - 2*p[14]*p[14]*p[28]; + coeff[80] = -4*p[0]*p[7]*p[11]*p[20] + 2*p[0]*p[7]*p[11]*p[29] + 4*p[0]*p[7]*p[12]*p[22] - 2*p[0]*p[7]*p[12]*p[31] - 4*p[0]*p[7]*p[14]*p[17] + 2*p[0]*p[7]*p[14]*p[26] + 4*p[0]*p[8]*p[11]*p[22] - 2*p[0]*p[8]*p[11]*p[31] + 4*p[0]*p[8]*p[12]*p[20] - 2*p[0]*p[8]*p[12]*p[29] - 4*p[0]*p[8]*p[13]*p[17] + 2*p[0]*p[8]*p[13]*p[26] - 4*p[0]*p[9]*p[12]*p[17] + 2*p[0]*p[9]*p[12]*p[26] - 4*p[0]*p[9]*p[13]*p[20] + 2*p[0]*p[9]*p[13]*p[29] - 4*p[0]*p[9]*p[14]*p[22] + 2*p[0]*p[9]*p[14]*p[31] - 4*p[0]*p[10]*p[11]*p[17] + 2*p[0]*p[10]*p[11]*p[26] - 4*p[0]*p[10]*p[13]*p[22] + 2*p[0]*p[10]*p[13]*p[31] + 4*p[0]*p[10]*p[14]*p[20] - 2*p[0]*p[10]*p[14]*p[29] - 2*p[7]*p[8]*p[22] + 2*p[7]*p[8]*p[31] + 2*p[7]*p[10]*p[17] - 2*p[7]*p[10]*p[26] - 2*p[8]*p[8]*p[20] + 2*p[8]*p[8]*p[29] + 2*p[8]*p[9]*p[17] - 2*p[8]*p[9]*p[26] + 2*p[9]*p[10]*p[22] - 2*p[9]*p[10]*p[31] - 2*p[10]*p[10]*p[20] + 2*p[10]*p[10]*p[29] + 2*p[11]*p[12]*p[22] - 2*p[11]*p[12]*p[31] - 2*p[11]*p[14]*p[17] + 2*p[11]*p[14]*p[26] + 2*p[12]*p[12]*p[20] - 2*p[12]*p[12]*p[29] - 2*p[12]*p[13]*p[17] + 2*p[12]*p[13]*p[26] - 2*p[13]*p[14]*p[22] + 2*p[13]*p[14]*p[31] + 2*p[14]*p[14]*p[20] - 2*p[14]*p[14]*p[29]; + coeff[81] = 2*p[0]*p[7]*p[14]*p[15] + 2*p[0]*p[8]*p[13]*p[15] + 2*p[0]*p[9]*p[12]*p[15] + 2*p[0]*p[10]*p[11]*p[15] - 2*p[7]*p[10]*p[15] + 2*p[7]*p[10]*p[24] - 2*p[8]*p[9]*p[15] + 2*p[8]*p[9]*p[24] + 2*p[11]*p[14]*p[15] - 2*p[11]*p[14]*p[24] + 2*p[12]*p[13]*p[15] - 2*p[12]*p[13]*p[24]; + coeff[82] = 2*p[0]*p[7]*p[11]*p[19] + 2*p[0]*p[7]*p[14]*p[16] - 2*p[0]*p[8]*p[12]*p[19] + 2*p[0]*p[8]*p[13]*p[16] + 2*p[0]*p[9]*p[12]*p[16] + 2*p[0]*p[9]*p[13]*p[19] + 2*p[0]*p[10]*p[11]*p[16] - 2*p[0]*p[10]*p[14]*p[19] - 2*p[7]*p[10]*p[16] + 2*p[7]*p[10]*p[25] + 2*p[8]*p[8]*p[19] - 2*p[8]*p[8]*p[28] - 2*p[8]*p[9]*p[16] + 2*p[8]*p[9]*p[25] + 2*p[10]*p[10]*p[19] - 2*p[10]*p[10]*p[28] + 2*p[11]*p[14]*p[16] - 2*p[11]*p[14]*p[25] - 2*p[12]*p[12]*p[19] + 2*p[12]*p[12]*p[28] + 2*p[12]*p[13]*p[16] - 2*p[12]*p[13]*p[25] - 2*p[14]*p[14]*p[19] + 2*p[14]*p[14]*p[28]; + coeff[83] = 2*p[0]*p[7]*p[11]*p[20] - 2*p[0]*p[7]*p[12]*p[22] + 2*p[0]*p[7]*p[14]*p[17] - 2*p[0]*p[8]*p[11]*p[22] - 2*p[0]*p[8]*p[12]*p[20] + 2*p[0]*p[8]*p[13]*p[17] + 2*p[0]*p[9]*p[12]*p[17] + 2*p[0]*p[9]*p[13]*p[20] + 2*p[0]*p[9]*p[14]*p[22] + 2*p[0]*p[10]*p[11]*p[17] + 2*p[0]*p[10]*p[13]*p[22] - 2*p[0]*p[10]*p[14]*p[20] + 2*p[7]*p[8]*p[22] - 2*p[7]*p[8]*p[31] - 2*p[7]*p[10]*p[17] + 2*p[7]*p[10]*p[26] + 2*p[8]*p[8]*p[20] - 2*p[8]*p[8]*p[29] - 2*p[8]*p[9]*p[17] + 2*p[8]*p[9]*p[26] - 2*p[9]*p[10]*p[22] + 2*p[9]*p[10]*p[31] + 2*p[10]*p[10]*p[20] - 2*p[10]*p[10]*p[29] - 2*p[11]*p[12]*p[22] + 2*p[11]*p[12]*p[31] + 2*p[11]*p[14]*p[17] - 2*p[11]*p[14]*p[26] - 2*p[12]*p[12]*p[20] + 2*p[12]*p[12]*p[29] + 2*p[12]*p[13]*p[17] - 2*p[12]*p[13]*p[26] + 2*p[13]*p[14]*p[22] - 2*p[13]*p[14]*p[31] - 2*p[14]*p[14]*p[20] + 2*p[14]*p[14]*p[29]; + coeff[84] = 0; + coeff[85] = 2*(p[7]*p[14]*p[15] - p[7]*p[14]*p[24] + p[8]*p[13]*p[15] - p[8]*p[13]*p[24] + p[9]*p[12]*p[15] - p[9]*p[12]*p[24] + p[10]*p[11]*p[15] - p[10]*p[11]*p[24])*p[0]; + coeff[86] = 2*(p[7]*p[11]*p[19] - p[7]*p[11]*p[28] + p[7]*p[14]*p[16] - p[7]*p[14]*p[25] - p[8]*p[12]*p[19] + p[8]*p[12]*p[28] + p[8]*p[13]*p[16] - p[8]*p[13]*p[25] + p[9]*p[12]*p[16] - p[9]*p[12]*p[25] + p[9]*p[13]*p[19] - p[9]*p[13]*p[28] + p[10]*p[11]*p[16] - p[10]*p[11]*p[25] - p[10]*p[14]*p[19] + p[10]*p[14]*p[28])*p[0]; + coeff[87] = 2*(p[7]*p[11]*p[20] - p[7]*p[11]*p[29] - p[7]*p[12]*p[22] + p[7]*p[12]*p[31] + p[7]*p[14]*p[17] - p[7]*p[14]*p[26] - p[8]*p[11]*p[22] + p[8]*p[11]*p[31] - p[8]*p[12]*p[20] + p[8]*p[12]*p[29] + p[8]*p[13]*p[17] - p[8]*p[13]*p[26] + p[9]*p[12]*p[17] - p[9]*p[12]*p[26] + p[9]*p[13]*p[20] - p[9]*p[13]*p[29] + p[9]*p[14]*p[22] - p[9]*p[14]*p[31] + p[10]*p[11]*p[17] - p[10]*p[11]*p[26] + p[10]*p[13]*p[22] - p[10]*p[13]*p[31] - p[10]*p[14]*p[20] + p[10]*p[14]*p[29])*p[0]; + coeff[88] = 2*(-p[7]*p[14]*p[15] + p[7]*p[14]*p[24] - p[8]*p[13]*p[15] + p[8]*p[13]*p[24] - p[9]*p[12]*p[15] + p[9]*p[12]*p[24] - p[10]*p[11]*p[15] + p[10]*p[11]*p[24])*p[0]; + coeff[89] = 2*(-p[7]*p[11]*p[19] + p[7]*p[11]*p[28] - p[7]*p[14]*p[16] + p[7]*p[14]*p[25] + p[8]*p[12]*p[19] - p[8]*p[12]*p[28] - p[8]*p[13]*p[16] + p[8]*p[13]*p[25] - p[9]*p[12]*p[16] + p[9]*p[12]*p[25] - p[9]*p[13]*p[19] + p[9]*p[13]*p[28] - p[10]*p[11]*p[16] + p[10]*p[11]*p[25] + p[10]*p[14]*p[19] - p[10]*p[14]*p[28])*p[0]; + coeff[90] = 2*(-p[7]*p[11]*p[20] + p[7]*p[11]*p[29] + p[7]*p[12]*p[22] - p[7]*p[12]*p[31] - p[7]*p[14]*p[17] + p[7]*p[14]*p[26] + p[8]*p[11]*p[22] - p[8]*p[11]*p[31] + p[8]*p[12]*p[20] - p[8]*p[12]*p[29] - p[8]*p[13]*p[17] + p[8]*p[13]*p[26] - p[9]*p[12]*p[17] + p[9]*p[12]*p[26] - p[9]*p[13]*p[20] + p[9]*p[13]*p[29] - p[9]*p[14]*p[22] + p[9]*p[14]*p[31] - p[10]*p[11]*p[17] + p[10]*p[11]*p[26] - p[10]*p[13]*p[22] + p[10]*p[13]*p[31] + p[10]*p[14]*p[20] - p[10]*p[14]*p[29])*p[0]; + coeff[91] = 2*p[0]*p[7]*p[8]*p[23] - 2*p[0]*p[7]*p[10]*p[18] + 2*p[0]*p[8]*p[8]*p[21] - 2*p[0]*p[8]*p[9]*p[18] - 2*p[0]*p[9]*p[10]*p[23] + 2*p[0]*p[10]*p[10]*p[21] - 2*p[0]*p[11]*p[12]*p[23] + 2*p[0]*p[11]*p[14]*p[18] - 2*p[0]*p[12]*p[12]*p[21] + 2*p[0]*p[12]*p[13]*p[18] + 2*p[0]*p[13]*p[14]*p[23] - 2*p[0]*p[14]*p[14]*p[21] - p[7]*p[11]*p[21] + p[7]*p[11]*p[30] + p[7]*p[12]*p[23] - p[7]*p[12]*p[32] - p[7]*p[14]*p[18] + p[7]*p[14]*p[27] + p[8]*p[11]*p[23] - p[8]*p[11]*p[32] + p[8]*p[12]*p[21] - p[8]*p[12]*p[30] - p[8]*p[13]*p[18] + p[8]*p[13]*p[27] - p[9]*p[12]*p[18] + p[9]*p[12]*p[27] - p[9]*p[13]*p[21] + p[9]*p[13]*p[30] - p[9]*p[14]*p[23] + p[9]*p[14]*p[32] - p[10]*p[11]*p[18] + p[10]*p[11]*p[27] - p[10]*p[13]*p[23] + p[10]*p[13]*p[32] + p[10]*p[14]*p[21] - p[10]*p[14]*p[30]; + coeff[92] = -2*p[0]*p[7]*p[10]*p[15] - 2*p[0]*p[8]*p[9]*p[15] + 2*p[0]*p[11]*p[14]*p[15] + 2*p[0]*p[12]*p[13]*p[15] - 2*p[7]*p[14]*p[15] + p[7]*p[14]*p[24] - 2*p[8]*p[13]*p[15] + p[8]*p[13]*p[24] - 2*p[9]*p[12]*p[15] + p[9]*p[12]*p[24] - 2*p[10]*p[11]*p[15] + p[10]*p[11]*p[24]; + coeff[93] = -2*p[0]*p[7]*p[10]*p[16] + 2*p[0]*p[8]*p[8]*p[19] - 2*p[0]*p[8]*p[9]*p[16] + 2*p[0]*p[10]*p[10]*p[19] + 2*p[0]*p[11]*p[14]*p[16] - 2*p[0]*p[12]*p[12]*p[19] + 2*p[0]*p[12]*p[13]*p[16] - 2*p[0]*p[14]*p[14]*p[19] - 2*p[7]*p[11]*p[19] + p[7]*p[11]*p[28] - 2*p[7]*p[14]*p[16] + p[7]*p[14]*p[25] + 2*p[8]*p[12]*p[19] - p[8]*p[12]*p[28] - 2*p[8]*p[13]*p[16] + p[8]*p[13]*p[25] - 2*p[9]*p[12]*p[16] + p[9]*p[12]*p[25] - 2*p[9]*p[13]*p[19] + p[9]*p[13]*p[28] - 2*p[10]*p[11]*p[16] + p[10]*p[11]*p[25] + 2*p[10]*p[14]*p[19] - p[10]*p[14]*p[28]; + coeff[94] = 2*p[0]*p[7]*p[8]*p[22] - 2*p[0]*p[7]*p[10]*p[17] + 2*p[0]*p[8]*p[8]*p[20] - 2*p[0]*p[8]*p[9]*p[17] - 2*p[0]*p[9]*p[10]*p[22] + 2*p[0]*p[10]*p[10]*p[20] - 2*p[0]*p[11]*p[12]*p[22] + 2*p[0]*p[11]*p[14]*p[17] - 2*p[0]*p[12]*p[12]*p[20] + 2*p[0]*p[12]*p[13]*p[17] + 2*p[0]*p[13]*p[14]*p[22] - 2*p[0]*p[14]*p[14]*p[20] - 2*p[7]*p[11]*p[20] + p[7]*p[11]*p[29] + 2*p[7]*p[12]*p[22] - p[7]*p[12]*p[31] - 2*p[7]*p[14]*p[17] + p[7]*p[14]*p[26] + 2*p[8]*p[11]*p[22] - p[8]*p[11]*p[31] + 2*p[8]*p[12]*p[20] - p[8]*p[12]*p[29] - 2*p[8]*p[13]*p[17] + p[8]*p[13]*p[26] - 2*p[9]*p[12]*p[17] + p[9]*p[12]*p[26] - 2*p[9]*p[13]*p[20] + p[9]*p[13]*p[29] - 2*p[9]*p[14]*p[22] + p[9]*p[14]*p[31] - 2*p[10]*p[11]*p[17] + p[10]*p[11]*p[26] - 2*p[10]*p[13]*p[22] + p[10]*p[13]*p[31] + 2*p[10]*p[14]*p[20] - p[10]*p[14]*p[29]; + coeff[95] = (p[7]*p[14] + p[8]*p[13] + p[9]*p[12] + p[10]*p[11])*p[15]; + coeff[96] = p[7]*p[11]*p[19] + p[7]*p[14]*p[16] - p[8]*p[12]*p[19] + p[8]*p[13]*p[16] + p[9]*p[12]*p[16] + p[9]*p[13]*p[19] + p[10]*p[11]*p[16] - p[10]*p[14]*p[19]; + coeff[97] = p[7]*p[11]*p[20] - p[7]*p[12]*p[22] + p[7]*p[14]*p[17] - p[8]*p[11]*p[22] - p[8]*p[12]*p[20] + p[8]*p[13]*p[17] + p[9]*p[12]*p[17] + p[9]*p[13]*p[20] + p[9]*p[14]*p[22] + p[10]*p[11]*p[17] + p[10]*p[13]*p[22] - p[10]*p[14]*p[20]; + coeff[98] = 2*(-p[7]*p[8]*p[23] + p[7]*p[8]*p[32] + p[7]*p[10]*p[18] - p[7]*p[10]*p[27] - p[8]*p[8]*p[21] + p[8]*p[8]*p[30] + p[8]*p[9]*p[18] - p[8]*p[9]*p[27] + p[9]*p[10]*p[23] - p[9]*p[10]*p[32] - p[10]*p[10]*p[21] + p[10]*p[10]*p[30] + p[11]*p[12]*p[23] - p[11]*p[12]*p[32] - p[11]*p[14]*p[18] + p[11]*p[14]*p[27] + p[12]*p[12]*p[21] - p[12]*p[12]*p[30] - p[12]*p[13]*p[18] + p[12]*p[13]*p[27] - p[13]*p[14]*p[23] + p[13]*p[14]*p[32] + p[14]*p[14]*p[21] - p[14]*p[14]*p[30])*p[0]; + coeff[99] = 4*p[0]*p[7]*p[10]*p[15] - 2*p[0]*p[7]*p[10]*p[24] + 4*p[0]*p[8]*p[9]*p[15] - 2*p[0]*p[8]*p[9]*p[24] - 4*p[0]*p[11]*p[14]*p[15] + 2*p[0]*p[11]*p[14]*p[24] - 4*p[0]*p[12]*p[13]*p[15] + 2*p[0]*p[12]*p[13]*p[24] + 2*p[7]*p[14]*p[15] - 2*p[7]*p[14]*p[24] + 2*p[8]*p[13]*p[15] - 2*p[8]*p[13]*p[24] + 2*p[9]*p[12]*p[15] - 2*p[9]*p[12]*p[24] + 2*p[10]*p[11]*p[15] - 2*p[10]*p[11]*p[24]; + coeff[100] = 4*p[0]*p[7]*p[10]*p[16] - 2*p[0]*p[7]*p[10]*p[25] - 4*p[0]*p[8]*p[8]*p[19] + 2*p[0]*p[8]*p[8]*p[28] + 4*p[0]*p[8]*p[9]*p[16] - 2*p[0]*p[8]*p[9]*p[25] - 4*p[0]*p[10]*p[10]*p[19] + 2*p[0]*p[10]*p[10]*p[28] - 4*p[0]*p[11]*p[14]*p[16] + 2*p[0]*p[11]*p[14]*p[25] + 4*p[0]*p[12]*p[12]*p[19] - 2*p[0]*p[12]*p[12]*p[28] - 4*p[0]*p[12]*p[13]*p[16] + 2*p[0]*p[12]*p[13]*p[25] + 4*p[0]*p[14]*p[14]*p[19] - 2*p[0]*p[14]*p[14]*p[28] + 2*p[7]*p[11]*p[19] - 2*p[7]*p[11]*p[28] + 2*p[7]*p[14]*p[16] - 2*p[7]*p[14]*p[25] - 2*p[8]*p[12]*p[19] + 2*p[8]*p[12]*p[28] + 2*p[8]*p[13]*p[16] - 2*p[8]*p[13]*p[25] + 2*p[9]*p[12]*p[16] - 2*p[9]*p[12]*p[25] + 2*p[9]*p[13]*p[19] - 2*p[9]*p[13]*p[28] + 2*p[10]*p[11]*p[16] - 2*p[10]*p[11]*p[25] - 2*p[10]*p[14]*p[19] + 2*p[10]*p[14]*p[28]; + coeff[101] = -4*p[0]*p[7]*p[8]*p[22] + 2*p[0]*p[7]*p[8]*p[31] + 4*p[0]*p[7]*p[10]*p[17] - 2*p[0]*p[7]*p[10]*p[26] - 4*p[0]*p[8]*p[8]*p[20] + 2*p[0]*p[8]*p[8]*p[29] + 4*p[0]*p[8]*p[9]*p[17] - 2*p[0]*p[8]*p[9]*p[26] + 4*p[0]*p[9]*p[10]*p[22] - 2*p[0]*p[9]*p[10]*p[31] - 4*p[0]*p[10]*p[10]*p[20] + 2*p[0]*p[10]*p[10]*p[29] + 4*p[0]*p[11]*p[12]*p[22] - 2*p[0]*p[11]*p[12]*p[31] - 4*p[0]*p[11]*p[14]*p[17] + 2*p[0]*p[11]*p[14]*p[26] + 4*p[0]*p[12]*p[12]*p[20] - 2*p[0]*p[12]*p[12]*p[29] - 4*p[0]*p[12]*p[13]*p[17] + 2*p[0]*p[12]*p[13]*p[26] - 4*p[0]*p[13]*p[14]*p[22] + 2*p[0]*p[13]*p[14]*p[31] + 4*p[0]*p[14]*p[14]*p[20] - 2*p[0]*p[14]*p[14]*p[29] + 2*p[7]*p[11]*p[20] - 2*p[7]*p[11]*p[29] - 2*p[7]*p[12]*p[22] + 2*p[7]*p[12]*p[31] + 2*p[7]*p[14]*p[17] - 2*p[7]*p[14]*p[26] - 2*p[8]*p[11]*p[22] + 2*p[8]*p[11]*p[31] - 2*p[8]*p[12]*p[20] + 2*p[8]*p[12]*p[29] + 2*p[8]*p[13]*p[17] - 2*p[8]*p[13]*p[26] + 2*p[9]*p[12]*p[17] - 2*p[9]*p[12]*p[26] + 2*p[9]*p[13]*p[20] - 2*p[9]*p[13]*p[29] + 2*p[9]*p[14]*p[22] - 2*p[9]*p[14]*p[31] + 2*p[10]*p[11]*p[17] - 2*p[10]*p[11]*p[26] + 2*p[10]*p[13]*p[22] - 2*p[10]*p[13]*p[31] - 2*p[10]*p[14]*p[20] + 2*p[10]*p[14]*p[29]; + coeff[102] = -2*p[0]*p[7]*p[10]*p[15] - 2*p[0]*p[8]*p[9]*p[15] + 2*p[0]*p[11]*p[14]*p[15] + 2*p[0]*p[12]*p[13]*p[15] - 2*p[7]*p[14]*p[15] + 2*p[7]*p[14]*p[24] - 2*p[8]*p[13]*p[15] + 2*p[8]*p[13]*p[24] - 2*p[9]*p[12]*p[15] + 2*p[9]*p[12]*p[24] - 2*p[10]*p[11]*p[15] + 2*p[10]*p[11]*p[24]; + coeff[103] = -2*p[0]*p[7]*p[10]*p[16] + 2*p[0]*p[8]*p[8]*p[19] - 2*p[0]*p[8]*p[9]*p[16] + 2*p[0]*p[10]*p[10]*p[19] + 2*p[0]*p[11]*p[14]*p[16] - 2*p[0]*p[12]*p[12]*p[19] + 2*p[0]*p[12]*p[13]*p[16] - 2*p[0]*p[14]*p[14]*p[19] - 2*p[7]*p[11]*p[19] + 2*p[7]*p[11]*p[28] - 2*p[7]*p[14]*p[16] + 2*p[7]*p[14]*p[25] + 2*p[8]*p[12]*p[19] - 2*p[8]*p[12]*p[28] - 2*p[8]*p[13]*p[16] + 2*p[8]*p[13]*p[25] - 2*p[9]*p[12]*p[16] + 2*p[9]*p[12]*p[25] - 2*p[9]*p[13]*p[19] + 2*p[9]*p[13]*p[28] - 2*p[10]*p[11]*p[16] + 2*p[10]*p[11]*p[25] + 2*p[10]*p[14]*p[19] - 2*p[10]*p[14]*p[28]; + coeff[104] = 2*p[0]*p[7]*p[8]*p[22] - 2*p[0]*p[7]*p[10]*p[17] + 2*p[0]*p[8]*p[8]*p[20] - 2*p[0]*p[8]*p[9]*p[17] - 2*p[0]*p[9]*p[10]*p[22] + 2*p[0]*p[10]*p[10]*p[20] - 2*p[0]*p[11]*p[12]*p[22] + 2*p[0]*p[11]*p[14]*p[17] - 2*p[0]*p[12]*p[12]*p[20] + 2*p[0]*p[12]*p[13]*p[17] + 2*p[0]*p[13]*p[14]*p[22] - 2*p[0]*p[14]*p[14]*p[20] - 2*p[7]*p[11]*p[20] + 2*p[7]*p[11]*p[29] + 2*p[7]*p[12]*p[22] - 2*p[7]*p[12]*p[31] - 2*p[7]*p[14]*p[17] + 2*p[7]*p[14]*p[26] + 2*p[8]*p[11]*p[22] - 2*p[8]*p[11]*p[31] + 2*p[8]*p[12]*p[20] - 2*p[8]*p[12]*p[29] - 2*p[8]*p[13]*p[17] + 2*p[8]*p[13]*p[26] - 2*p[9]*p[12]*p[17] + 2*p[9]*p[12]*p[26] - 2*p[9]*p[13]*p[20] + 2*p[9]*p[13]*p[29] - 2*p[9]*p[14]*p[22] + 2*p[9]*p[14]*p[31] - 2*p[10]*p[11]*p[17] + 2*p[10]*p[11]*p[26] - 2*p[10]*p[13]*p[22] + 2*p[10]*p[13]*p[31] + 2*p[10]*p[14]*p[20] - 2*p[10]*p[14]*p[29]; + coeff[105] = 0; + coeff[106] = 2*(-p[7]*p[10]*p[15] + p[7]*p[10]*p[24] - p[8]*p[9]*p[15] + p[8]*p[9]*p[24] + p[11]*p[14]*p[15] - p[11]*p[14]*p[24] + p[12]*p[13]*p[15] - p[12]*p[13]*p[24])*p[0]; + coeff[107] = 2*(-p[7]*p[10]*p[16] + p[7]*p[10]*p[25] + p[8]*p[8]*p[19] - p[8]*p[8]*p[28] - p[8]*p[9]*p[16] + p[8]*p[9]*p[25] + p[10]*p[10]*p[19] - p[10]*p[10]*p[28] + p[11]*p[14]*p[16] - p[11]*p[14]*p[25] - p[12]*p[12]*p[19] + p[12]*p[12]*p[28] + p[12]*p[13]*p[16] - p[12]*p[13]*p[25] - p[14]*p[14]*p[19] + p[14]*p[14]*p[28])*p[0]; + coeff[108] = 2*(p[7]*p[8]*p[22] - p[7]*p[8]*p[31] - p[7]*p[10]*p[17] + p[7]*p[10]*p[26] + p[8]*p[8]*p[20] - p[8]*p[8]*p[29] - p[8]*p[9]*p[17] + p[8]*p[9]*p[26] - p[9]*p[10]*p[22] + p[9]*p[10]*p[31] + p[10]*p[10]*p[20] - p[10]*p[10]*p[29] - p[11]*p[12]*p[22] + p[11]*p[12]*p[31] + p[11]*p[14]*p[17] - p[11]*p[14]*p[26] - p[12]*p[12]*p[20] + p[12]*p[12]*p[29] + p[12]*p[13]*p[17] - p[12]*p[13]*p[26] + p[13]*p[14]*p[22] - p[13]*p[14]*p[31] - p[14]*p[14]*p[20] + p[14]*p[14]*p[29])*p[0]; + coeff[109] = 2*(p[7]*p[10]*p[15] - p[7]*p[10]*p[24] + p[8]*p[9]*p[15] - p[8]*p[9]*p[24] - p[11]*p[14]*p[15] + p[11]*p[14]*p[24] - p[12]*p[13]*p[15] + p[12]*p[13]*p[24])*p[0]; + coeff[110] = 2*(p[7]*p[10]*p[16] - p[7]*p[10]*p[25] - p[8]*p[8]*p[19] + p[8]*p[8]*p[28] + p[8]*p[9]*p[16] - p[8]*p[9]*p[25] - p[10]*p[10]*p[19] + p[10]*p[10]*p[28] - p[11]*p[14]*p[16] + p[11]*p[14]*p[25] + p[12]*p[12]*p[19] - p[12]*p[12]*p[28] - p[12]*p[13]*p[16] + p[12]*p[13]*p[25] + p[14]*p[14]*p[19] - p[14]*p[14]*p[28])*p[0]; + coeff[111] = 2*(-p[7]*p[8]*p[22] + p[7]*p[8]*p[31] + p[7]*p[10]*p[17] - p[7]*p[10]*p[26] - p[8]*p[8]*p[20] + p[8]*p[8]*p[29] + p[8]*p[9]*p[17] - p[8]*p[9]*p[26] + p[9]*p[10]*p[22] - p[9]*p[10]*p[31] - p[10]*p[10]*p[20] + p[10]*p[10]*p[29] + p[11]*p[12]*p[22] - p[11]*p[12]*p[31] - p[11]*p[14]*p[17] + p[11]*p[14]*p[26] + p[12]*p[12]*p[20] - p[12]*p[12]*p[29] - p[12]*p[13]*p[17] + p[12]*p[13]*p[26] - p[13]*p[14]*p[22] + p[13]*p[14]*p[31] + p[14]*p[14]*p[20] - p[14]*p[14]*p[29])*p[0]; + coeff[112] = -p[3] + p[6] - p[7]*p[8]*p[21] + p[7]*p[8]*p[30] + p[7]*p[9]*p[18] - p[7]*p[9]*p[27] + p[8]*p[8]*p[23] - p[8]*p[8]*p[32] - p[8]*p[10]*p[18] + p[8]*p[10]*p[27] + p[9]*p[9]*p[23] - p[9]*p[9]*p[32] - p[9]*p[10]*p[21] + p[9]*p[10]*p[30] - p[11]*p[12]*p[21] + p[11]*p[12]*p[30] + p[11]*p[13]*p[18] - p[11]*p[13]*p[27] + p[12]*p[12]*p[23] - p[12]*p[12]*p[32] - p[12]*p[14]*p[18] + p[12]*p[14]*p[27] + p[13]*p[13]*p[23] - p[13]*p[13]*p[32] - p[13]*p[14]*p[21] + p[13]*p[14]*p[30] - p[23] + p[32]; + coeff[113] = 2*p[7]*p[9]*p[15] - p[7]*p[9]*p[24] - 2*p[8]*p[10]*p[15] + p[8]*p[10]*p[24] + 2*p[11]*p[13]*p[15] - p[11]*p[13]*p[24] - 2*p[12]*p[14]*p[15] + p[12]*p[14]*p[24]; + coeff[114] = -2*p[7]*p[8]*p[19] + p[7]*p[8]*p[28] + 2*p[7]*p[9]*p[16] - p[7]*p[9]*p[25] - 2*p[8]*p[10]*p[16] + p[8]*p[10]*p[25] - 2*p[9]*p[10]*p[19] + p[9]*p[10]*p[28] - 2*p[11]*p[12]*p[19] + p[11]*p[12]*p[28] + 2*p[11]*p[13]*p[16] - p[11]*p[13]*p[25] - 2*p[12]*p[14]*p[16] + p[12]*p[14]*p[25] - 2*p[13]*p[14]*p[19] + p[13]*p[14]*p[28]; + coeff[115] = -2*p[7]*p[8]*p[20] + p[7]*p[8]*p[29] + 2*p[7]*p[9]*p[17] - p[7]*p[9]*p[26] + 2*p[8]*p[8]*p[22] - p[8]*p[8]*p[31] - 2*p[8]*p[10]*p[17] + p[8]*p[10]*p[26] + 2*p[9]*p[9]*p[22] - p[9]*p[9]*p[31] - 2*p[9]*p[10]*p[20] + p[9]*p[10]*p[29] - 2*p[11]*p[12]*p[20] + p[11]*p[12]*p[29] + 2*p[11]*p[13]*p[17] - p[11]*p[13]*p[26] + 2*p[12]*p[12]*p[22] - p[12]*p[12]*p[31] - 2*p[12]*p[14]*p[17] + p[12]*p[14]*p[26] + 2*p[13]*p[13]*p[22] - p[13]*p[13]*p[31] - 2*p[13]*p[14]*p[20] + p[13]*p[14]*p[29] - 2*p[22] + p[31]; + coeff[116] = (-p[7]*p[9] + p[8]*p[10] - p[11]*p[13] + p[12]*p[14])*p[15]; + coeff[117] = p[7]*p[8]*p[19] - p[7]*p[9]*p[16] + p[8]*p[10]*p[16] + p[9]*p[10]*p[19] + p[11]*p[12]*p[19] - p[11]*p[13]*p[16] + p[12]*p[14]*p[16] + p[13]*p[14]*p[19]; + coeff[118] = p[7]*p[8]*p[20] - p[7]*p[9]*p[17] - p[8]*p[8]*p[22] + p[8]*p[10]*p[17] - p[9]*p[9]*p[22] + p[9]*p[10]*p[20] + p[11]*p[12]*p[20] - p[11]*p[13]*p[17] - p[12]*p[12]*p[22] + p[12]*p[14]*p[17] - p[13]*p[13]*p[22] + p[13]*p[14]*p[20] + p[22]; + coeff[119] = 0; + coeff[120] = -2*p[7]*p[9]*p[15] + 2*p[7]*p[9]*p[24] + 2*p[8]*p[10]*p[15] - 2*p[8]*p[10]*p[24] - 2*p[11]*p[13]*p[15] + 2*p[11]*p[13]*p[24] + 2*p[12]*p[14]*p[15] - 2*p[12]*p[14]*p[24]; + coeff[121] = 2*p[7]*p[8]*p[19] - 2*p[7]*p[8]*p[28] - 2*p[7]*p[9]*p[16] + 2*p[7]*p[9]*p[25] + 2*p[8]*p[10]*p[16] - 2*p[8]*p[10]*p[25] + 2*p[9]*p[10]*p[19] - 2*p[9]*p[10]*p[28] + 2*p[11]*p[12]*p[19] - 2*p[11]*p[12]*p[28] - 2*p[11]*p[13]*p[16] + 2*p[11]*p[13]*p[25] + 2*p[12]*p[14]*p[16] - 2*p[12]*p[14]*p[25] + 2*p[13]*p[14]*p[19] - 2*p[13]*p[14]*p[28]; + coeff[122] = 2*p[7]*p[8]*p[20] - 2*p[7]*p[8]*p[29] - 2*p[7]*p[9]*p[17] + 2*p[7]*p[9]*p[26] - 2*p[8]*p[8]*p[22] + 2*p[8]*p[8]*p[31] + 2*p[8]*p[10]*p[17] - 2*p[8]*p[10]*p[26] - 2*p[9]*p[9]*p[22] + 2*p[9]*p[9]*p[31] + 2*p[9]*p[10]*p[20] - 2*p[9]*p[10]*p[29] + 2*p[11]*p[12]*p[20] - 2*p[11]*p[12]*p[29] - 2*p[11]*p[13]*p[17] + 2*p[11]*p[13]*p[26] - 2*p[12]*p[12]*p[22] + 2*p[12]*p[12]*p[31] + 2*p[12]*p[14]*p[17] - 2*p[12]*p[14]*p[26] - 2*p[13]*p[13]*p[22] + 2*p[13]*p[13]*p[31] + 2*p[13]*p[14]*p[20] - 2*p[13]*p[14]*p[29] + 2*p[22] - 2*p[31]; + coeff[123] = 2*p[7]*p[9]*p[15] - 2*p[7]*p[9]*p[24] - 2*p[8]*p[10]*p[15] + 2*p[8]*p[10]*p[24] + 2*p[11]*p[13]*p[15] - 2*p[11]*p[13]*p[24] - 2*p[12]*p[14]*p[15] + 2*p[12]*p[14]*p[24]; + coeff[124] = -2*p[7]*p[8]*p[19] + 2*p[7]*p[8]*p[28] + 2*p[7]*p[9]*p[16] - 2*p[7]*p[9]*p[25] - 2*p[8]*p[10]*p[16] + 2*p[8]*p[10]*p[25] - 2*p[9]*p[10]*p[19] + 2*p[9]*p[10]*p[28] - 2*p[11]*p[12]*p[19] + 2*p[11]*p[12]*p[28] + 2*p[11]*p[13]*p[16] - 2*p[11]*p[13]*p[25] - 2*p[12]*p[14]*p[16] + 2*p[12]*p[14]*p[25] - 2*p[13]*p[14]*p[19] + 2*p[13]*p[14]*p[28]; + coeff[125] = -2*p[7]*p[8]*p[20] + 2*p[7]*p[8]*p[29] + 2*p[7]*p[9]*p[17] - 2*p[7]*p[9]*p[26] + 2*p[8]*p[8]*p[22] - 2*p[8]*p[8]*p[31] - 2*p[8]*p[10]*p[17] + 2*p[8]*p[10]*p[26] + 2*p[9]*p[9]*p[22] - 2*p[9]*p[9]*p[31] - 2*p[9]*p[10]*p[20] + 2*p[9]*p[10]*p[29] - 2*p[11]*p[12]*p[20] + 2*p[11]*p[12]*p[29] + 2*p[11]*p[13]*p[17] - 2*p[11]*p[13]*p[26] + 2*p[12]*p[12]*p[22] - 2*p[12]*p[12]*p[31] - 2*p[12]*p[14]*p[17] + 2*p[12]*p[14]*p[26] + 2*p[13]*p[13]*p[22] - 2*p[13]*p[13]*p[31] - 2*p[13]*p[14]*p[20] + 2*p[13]*p[14]*p[29] - 2*p[22] + 2*p[31]; + coeff[126] = 2*p[0]*p[7]*p[11]*p[23] + 2*p[0]*p[7]*p[12]*p[21] - 2*p[0]*p[7]*p[13]*p[18] + 2*p[0]*p[8]*p[11]*p[21] - 2*p[0]*p[8]*p[12]*p[23] + 2*p[0]*p[8]*p[14]*p[18] - 2*p[0]*p[9]*p[11]*p[18] - 2*p[0]*p[9]*p[13]*p[23] + 2*p[0]*p[9]*p[14]*p[21] + 2*p[0]*p[10]*p[12]*p[18] + 2*p[0]*p[10]*p[13]*p[21] + 2*p[0]*p[10]*p[14]*p[23] - p[7]*p[8]*p[21] + p[7]*p[8]*p[30] + p[7]*p[9]*p[18] - p[7]*p[9]*p[27] + p[8]*p[8]*p[23] - p[8]*p[8]*p[32] - p[8]*p[10]*p[18] + p[8]*p[10]*p[27] + p[9]*p[9]*p[23] - p[9]*p[9]*p[32] - p[9]*p[10]*p[21] + p[9]*p[10]*p[30] + p[11]*p[12]*p[21] - p[11]*p[12]*p[30] - p[11]*p[13]*p[18] + p[11]*p[13]*p[27] - p[12]*p[12]*p[23] + p[12]*p[12]*p[32] + p[12]*p[14]*p[18] - p[12]*p[14]*p[27] - p[13]*p[13]*p[23] + p[13]*p[13]*p[32] + p[13]*p[14]*p[21] - p[13]*p[14]*p[30]; + coeff[127] = -2*p[0]*p[7]*p[13]*p[15] + 2*p[0]*p[8]*p[14]*p[15] - 2*p[0]*p[9]*p[11]*p[15] + 2*p[0]*p[10]*p[12]*p[15] + 2*p[7]*p[9]*p[15] - p[7]*p[9]*p[24] - 2*p[8]*p[10]*p[15] + p[8]*p[10]*p[24] - 2*p[11]*p[13]*p[15] + p[11]*p[13]*p[24] + 2*p[12]*p[14]*p[15] - p[12]*p[14]*p[24]; + coeff[128] = 2*p[0]*p[7]*p[12]*p[19] - 2*p[0]*p[7]*p[13]*p[16] + 2*p[0]*p[8]*p[11]*p[19] + 2*p[0]*p[8]*p[14]*p[16] - 2*p[0]*p[9]*p[11]*p[16] + 2*p[0]*p[9]*p[14]*p[19] + 2*p[0]*p[10]*p[12]*p[16] + 2*p[0]*p[10]*p[13]*p[19] - 2*p[7]*p[8]*p[19] + p[7]*p[8]*p[28] + 2*p[7]*p[9]*p[16] - p[7]*p[9]*p[25] - 2*p[8]*p[10]*p[16] + p[8]*p[10]*p[25] - 2*p[9]*p[10]*p[19] + p[9]*p[10]*p[28] + 2*p[11]*p[12]*p[19] - p[11]*p[12]*p[28] - 2*p[11]*p[13]*p[16] + p[11]*p[13]*p[25] + 2*p[12]*p[14]*p[16] - p[12]*p[14]*p[25] + 2*p[13]*p[14]*p[19] - p[13]*p[14]*p[28]; + coeff[129] = 2*p[0]*p[7]*p[11]*p[22] + 2*p[0]*p[7]*p[12]*p[20] - 2*p[0]*p[7]*p[13]*p[17] + 2*p[0]*p[8]*p[11]*p[20] - 2*p[0]*p[8]*p[12]*p[22] + 2*p[0]*p[8]*p[14]*p[17] - 2*p[0]*p[9]*p[11]*p[17] - 2*p[0]*p[9]*p[13]*p[22] + 2*p[0]*p[9]*p[14]*p[20] + 2*p[0]*p[10]*p[12]*p[17] + 2*p[0]*p[10]*p[13]*p[20] + 2*p[0]*p[10]*p[14]*p[22] - 2*p[7]*p[8]*p[20] + p[7]*p[8]*p[29] + 2*p[7]*p[9]*p[17] - p[7]*p[9]*p[26] + 2*p[8]*p[8]*p[22] - p[8]*p[8]*p[31] - 2*p[8]*p[10]*p[17] + p[8]*p[10]*p[26] + 2*p[9]*p[9]*p[22] - p[9]*p[9]*p[31] - 2*p[9]*p[10]*p[20] + p[9]*p[10]*p[29] + 2*p[11]*p[12]*p[20] - p[11]*p[12]*p[29] - 2*p[11]*p[13]*p[17] + p[11]*p[13]*p[26] - 2*p[12]*p[12]*p[22] + p[12]*p[12]*p[31] + 2*p[12]*p[14]*p[17] - p[12]*p[14]*p[26] - 2*p[13]*p[13]*p[22] + p[13]*p[13]*p[31] + 2*p[13]*p[14]*p[20] - p[13]*p[14]*p[29]; + coeff[130] = (-p[7]*p[9] + p[8]*p[10] + p[11]*p[13] - p[12]*p[14])*p[15]; + coeff[131] = p[7]*p[8]*p[19] - p[7]*p[9]*p[16] + p[8]*p[10]*p[16] + p[9]*p[10]*p[19] - p[11]*p[12]*p[19] + p[11]*p[13]*p[16] - p[12]*p[14]*p[16] - p[13]*p[14]*p[19]; + coeff[132] = p[7]*p[8]*p[20] - p[7]*p[9]*p[17] - p[8]*p[8]*p[22] + p[8]*p[10]*p[17] - p[9]*p[9]*p[22] + p[9]*p[10]*p[20] - p[11]*p[12]*p[20] + p[11]*p[13]*p[17] + p[12]*p[12]*p[22] - p[12]*p[14]*p[17] + p[13]*p[13]*p[22] - p[13]*p[14]*p[20]; + coeff[133] = 2*(-p[7]*p[11]*p[23] + p[7]*p[11]*p[32] - p[7]*p[12]*p[21] + p[7]*p[12]*p[30] + p[7]*p[13]*p[18] - p[7]*p[13]*p[27] - p[8]*p[11]*p[21] + p[8]*p[11]*p[30] + p[8]*p[12]*p[23] - p[8]*p[12]*p[32] - p[8]*p[14]*p[18] + p[8]*p[14]*p[27] + p[9]*p[11]*p[18] - p[9]*p[11]*p[27] + p[9]*p[13]*p[23] - p[9]*p[13]*p[32] - p[9]*p[14]*p[21] + p[9]*p[14]*p[30] - p[10]*p[12]*p[18] + p[10]*p[12]*p[27] - p[10]*p[13]*p[21] + p[10]*p[13]*p[30] - p[10]*p[14]*p[23] + p[10]*p[14]*p[32])*p[0]; + coeff[134] = 4*p[0]*p[7]*p[13]*p[15] - 2*p[0]*p[7]*p[13]*p[24] - 4*p[0]*p[8]*p[14]*p[15] + 2*p[0]*p[8]*p[14]*p[24] + 4*p[0]*p[9]*p[11]*p[15] - 2*p[0]*p[9]*p[11]*p[24] - 4*p[0]*p[10]*p[12]*p[15] + 2*p[0]*p[10]*p[12]*p[24] - 2*p[7]*p[9]*p[15] + 2*p[7]*p[9]*p[24] + 2*p[8]*p[10]*p[15] - 2*p[8]*p[10]*p[24] + 2*p[11]*p[13]*p[15] - 2*p[11]*p[13]*p[24] - 2*p[12]*p[14]*p[15] + 2*p[12]*p[14]*p[24]; + coeff[135] = -4*p[0]*p[7]*p[12]*p[19] + 2*p[0]*p[7]*p[12]*p[28] + 4*p[0]*p[7]*p[13]*p[16] - 2*p[0]*p[7]*p[13]*p[25] - 4*p[0]*p[8]*p[11]*p[19] + 2*p[0]*p[8]*p[11]*p[28] - 4*p[0]*p[8]*p[14]*p[16] + 2*p[0]*p[8]*p[14]*p[25] + 4*p[0]*p[9]*p[11]*p[16] - 2*p[0]*p[9]*p[11]*p[25] - 4*p[0]*p[9]*p[14]*p[19] + 2*p[0]*p[9]*p[14]*p[28] - 4*p[0]*p[10]*p[12]*p[16] + 2*p[0]*p[10]*p[12]*p[25] - 4*p[0]*p[10]*p[13]*p[19] + 2*p[0]*p[10]*p[13]*p[28] + 2*p[7]*p[8]*p[19] - 2*p[7]*p[8]*p[28] - 2*p[7]*p[9]*p[16] + 2*p[7]*p[9]*p[25] + 2*p[8]*p[10]*p[16] - 2*p[8]*p[10]*p[25] + 2*p[9]*p[10]*p[19] - 2*p[9]*p[10]*p[28] - 2*p[11]*p[12]*p[19] + 2*p[11]*p[12]*p[28] + 2*p[11]*p[13]*p[16] - 2*p[11]*p[13]*p[25] - 2*p[12]*p[14]*p[16] + 2*p[12]*p[14]*p[25] - 2*p[13]*p[14]*p[19] + 2*p[13]*p[14]*p[28]; + coeff[136] = -4*p[0]*p[7]*p[11]*p[22] + 2*p[0]*p[7]*p[11]*p[31] - 4*p[0]*p[7]*p[12]*p[20] + 2*p[0]*p[7]*p[12]*p[29] + 4*p[0]*p[7]*p[13]*p[17] - 2*p[0]*p[7]*p[13]*p[26] - 4*p[0]*p[8]*p[11]*p[20] + 2*p[0]*p[8]*p[11]*p[29] + 4*p[0]*p[8]*p[12]*p[22] - 2*p[0]*p[8]*p[12]*p[31] - 4*p[0]*p[8]*p[14]*p[17] + 2*p[0]*p[8]*p[14]*p[26] + 4*p[0]*p[9]*p[11]*p[17] - 2*p[0]*p[9]*p[11]*p[26] + 4*p[0]*p[9]*p[13]*p[22] - 2*p[0]*p[9]*p[13]*p[31] - 4*p[0]*p[9]*p[14]*p[20] + 2*p[0]*p[9]*p[14]*p[29] - 4*p[0]*p[10]*p[12]*p[17] + 2*p[0]*p[10]*p[12]*p[26] - 4*p[0]*p[10]*p[13]*p[20] + 2*p[0]*p[10]*p[13]*p[29] - 4*p[0]*p[10]*p[14]*p[22] + 2*p[0]*p[10]*p[14]*p[31] + 2*p[7]*p[8]*p[20] - 2*p[7]*p[8]*p[29] - 2*p[7]*p[9]*p[17] + 2*p[7]*p[9]*p[26] - 2*p[8]*p[8]*p[22] + 2*p[8]*p[8]*p[31] + 2*p[8]*p[10]*p[17] - 2*p[8]*p[10]*p[26] - 2*p[9]*p[9]*p[22] + 2*p[9]*p[9]*p[31] + 2*p[9]*p[10]*p[20] - 2*p[9]*p[10]*p[29] - 2*p[11]*p[12]*p[20] + 2*p[11]*p[12]*p[29] + 2*p[11]*p[13]*p[17] - 2*p[11]*p[13]*p[26] + 2*p[12]*p[12]*p[22] - 2*p[12]*p[12]*p[31] - 2*p[12]*p[14]*p[17] + 2*p[12]*p[14]*p[26] + 2*p[13]*p[13]*p[22] - 2*p[13]*p[13]*p[31] - 2*p[13]*p[14]*p[20] + 2*p[13]*p[14]*p[29]; + coeff[137] = -2*p[0]*p[7]*p[13]*p[15] + 2*p[0]*p[8]*p[14]*p[15] - 2*p[0]*p[9]*p[11]*p[15] + 2*p[0]*p[10]*p[12]*p[15] + 2*p[7]*p[9]*p[15] - 2*p[7]*p[9]*p[24] - 2*p[8]*p[10]*p[15] + 2*p[8]*p[10]*p[24] - 2*p[11]*p[13]*p[15] + 2*p[11]*p[13]*p[24] + 2*p[12]*p[14]*p[15] - 2*p[12]*p[14]*p[24]; + coeff[138] = 2*p[0]*p[7]*p[12]*p[19] - 2*p[0]*p[7]*p[13]*p[16] + 2*p[0]*p[8]*p[11]*p[19] + 2*p[0]*p[8]*p[14]*p[16] - 2*p[0]*p[9]*p[11]*p[16] + 2*p[0]*p[9]*p[14]*p[19] + 2*p[0]*p[10]*p[12]*p[16] + 2*p[0]*p[10]*p[13]*p[19] - 2*p[7]*p[8]*p[19] + 2*p[7]*p[8]*p[28] + 2*p[7]*p[9]*p[16] - 2*p[7]*p[9]*p[25] - 2*p[8]*p[10]*p[16] + 2*p[8]*p[10]*p[25] - 2*p[9]*p[10]*p[19] + 2*p[9]*p[10]*p[28] + 2*p[11]*p[12]*p[19] - 2*p[11]*p[12]*p[28] - 2*p[11]*p[13]*p[16] + 2*p[11]*p[13]*p[25] + 2*p[12]*p[14]*p[16] - 2*p[12]*p[14]*p[25] + 2*p[13]*p[14]*p[19] - 2*p[13]*p[14]*p[28]; + coeff[139] = 2*p[0]*p[7]*p[11]*p[22] + 2*p[0]*p[7]*p[12]*p[20] - 2*p[0]*p[7]*p[13]*p[17] + 2*p[0]*p[8]*p[11]*p[20] - 2*p[0]*p[8]*p[12]*p[22] + 2*p[0]*p[8]*p[14]*p[17] - 2*p[0]*p[9]*p[11]*p[17] - 2*p[0]*p[9]*p[13]*p[22] + 2*p[0]*p[9]*p[14]*p[20] + 2*p[0]*p[10]*p[12]*p[17] + 2*p[0]*p[10]*p[13]*p[20] + 2*p[0]*p[10]*p[14]*p[22] - 2*p[7]*p[8]*p[20] + 2*p[7]*p[8]*p[29] + 2*p[7]*p[9]*p[17] - 2*p[7]*p[9]*p[26] + 2*p[8]*p[8]*p[22] - 2*p[8]*p[8]*p[31] - 2*p[8]*p[10]*p[17] + 2*p[8]*p[10]*p[26] + 2*p[9]*p[9]*p[22] - 2*p[9]*p[9]*p[31] - 2*p[9]*p[10]*p[20] + 2*p[9]*p[10]*p[29] + 2*p[11]*p[12]*p[20] - 2*p[11]*p[12]*p[29] - 2*p[11]*p[13]*p[17] + 2*p[11]*p[13]*p[26] - 2*p[12]*p[12]*p[22] + 2*p[12]*p[12]*p[31] + 2*p[12]*p[14]*p[17] - 2*p[12]*p[14]*p[26] - 2*p[13]*p[13]*p[22] + 2*p[13]*p[13]*p[31] + 2*p[13]*p[14]*p[20] - 2*p[13]*p[14]*p[29]; + coeff[140] = 0; + coeff[141] = 2*(-p[7]*p[13]*p[15] + p[7]*p[13]*p[24] + p[8]*p[14]*p[15] - p[8]*p[14]*p[24] - p[9]*p[11]*p[15] + p[9]*p[11]*p[24] + p[10]*p[12]*p[15] - p[10]*p[12]*p[24])*p[0]; + coeff[142] = 2*(p[7]*p[12]*p[19] - p[7]*p[12]*p[28] - p[7]*p[13]*p[16] + p[7]*p[13]*p[25] + p[8]*p[11]*p[19] - p[8]*p[11]*p[28] + p[8]*p[14]*p[16] - p[8]*p[14]*p[25] - p[9]*p[11]*p[16] + p[9]*p[11]*p[25] + p[9]*p[14]*p[19] - p[9]*p[14]*p[28] + p[10]*p[12]*p[16] - p[10]*p[12]*p[25] + p[10]*p[13]*p[19] - p[10]*p[13]*p[28])*p[0]; + coeff[143] = 2*(p[7]*p[11]*p[22] - p[7]*p[11]*p[31] + p[7]*p[12]*p[20] - p[7]*p[12]*p[29] - p[7]*p[13]*p[17] + p[7]*p[13]*p[26] + p[8]*p[11]*p[20] - p[8]*p[11]*p[29] - p[8]*p[12]*p[22] + p[8]*p[12]*p[31] + p[8]*p[14]*p[17] - p[8]*p[14]*p[26] - p[9]*p[11]*p[17] + p[9]*p[11]*p[26] - p[9]*p[13]*p[22] + p[9]*p[13]*p[31] + p[9]*p[14]*p[20] - p[9]*p[14]*p[29] + p[10]*p[12]*p[17] - p[10]*p[12]*p[26] + p[10]*p[13]*p[20] - p[10]*p[13]*p[29] + p[10]*p[14]*p[22] - p[10]*p[14]*p[31])*p[0]; + coeff[144] = 2*(p[7]*p[13]*p[15] - p[7]*p[13]*p[24] - p[8]*p[14]*p[15] + p[8]*p[14]*p[24] + p[9]*p[11]*p[15] - p[9]*p[11]*p[24] - p[10]*p[12]*p[15] + p[10]*p[12]*p[24])*p[0]; + coeff[145] = 2*(-p[7]*p[12]*p[19] + p[7]*p[12]*p[28] + p[7]*p[13]*p[16] - p[7]*p[13]*p[25] - p[8]*p[11]*p[19] + p[8]*p[11]*p[28] - p[8]*p[14]*p[16] + p[8]*p[14]*p[25] + p[9]*p[11]*p[16] - p[9]*p[11]*p[25] - p[9]*p[14]*p[19] + p[9]*p[14]*p[28] - p[10]*p[12]*p[16] + p[10]*p[12]*p[25] - p[10]*p[13]*p[19] + p[10]*p[13]*p[28])*p[0]; + coeff[146] = 2*(-p[7]*p[11]*p[22] + p[7]*p[11]*p[31] - p[7]*p[12]*p[20] + p[7]*p[12]*p[29] + p[7]*p[13]*p[17] - p[7]*p[13]*p[26] - p[8]*p[11]*p[20] + p[8]*p[11]*p[29] + p[8]*p[12]*p[22] - p[8]*p[12]*p[31] - p[8]*p[14]*p[17] + p[8]*p[14]*p[26] + p[9]*p[11]*p[17] - p[9]*p[11]*p[26] + p[9]*p[13]*p[22] - p[9]*p[13]*p[31] - p[9]*p[14]*p[20] + p[9]*p[14]*p[29] - p[10]*p[12]*p[17] + p[10]*p[12]*p[26] - p[10]*p[13]*p[20] + p[10]*p[13]*p[29] - p[10]*p[14]*p[22] + p[10]*p[14]*p[31])*p[0]; + coeff[147] = -2*p[0]*p[7]*p[8]*p[21] + 2*p[0]*p[7]*p[9]*p[18] + 2*p[0]*p[8]*p[8]*p[23] - 2*p[0]*p[8]*p[10]*p[18] + 2*p[0]*p[9]*p[9]*p[23] - 2*p[0]*p[9]*p[10]*p[21] + 2*p[0]*p[11]*p[12]*p[21] - 2*p[0]*p[11]*p[13]*p[18] - 2*p[0]*p[12]*p[12]*p[23] + 2*p[0]*p[12]*p[14]*p[18] - 2*p[0]*p[13]*p[13]*p[23] + 2*p[0]*p[13]*p[14]*p[21] - p[7]*p[11]*p[23] + p[7]*p[11]*p[32] - p[7]*p[12]*p[21] + p[7]*p[12]*p[30] + p[7]*p[13]*p[18] - p[7]*p[13]*p[27] - p[8]*p[11]*p[21] + p[8]*p[11]*p[30] + p[8]*p[12]*p[23] - p[8]*p[12]*p[32] - p[8]*p[14]*p[18] + p[8]*p[14]*p[27] + p[9]*p[11]*p[18] - p[9]*p[11]*p[27] + p[9]*p[13]*p[23] - p[9]*p[13]*p[32] - p[9]*p[14]*p[21] + p[9]*p[14]*p[30] - p[10]*p[12]*p[18] + p[10]*p[12]*p[27] - p[10]*p[13]*p[21] + p[10]*p[13]*p[30] - p[10]*p[14]*p[23] + p[10]*p[14]*p[32]; + coeff[148] = 2*p[0]*p[7]*p[9]*p[15] - 2*p[0]*p[8]*p[10]*p[15] - 2*p[0]*p[11]*p[13]*p[15] + 2*p[0]*p[12]*p[14]*p[15] + 2*p[7]*p[13]*p[15] - p[7]*p[13]*p[24] - 2*p[8]*p[14]*p[15] + p[8]*p[14]*p[24] + 2*p[9]*p[11]*p[15] - p[9]*p[11]*p[24] - 2*p[10]*p[12]*p[15] + p[10]*p[12]*p[24]; + coeff[149] = -2*p[0]*p[7]*p[8]*p[19] + 2*p[0]*p[7]*p[9]*p[16] - 2*p[0]*p[8]*p[10]*p[16] - 2*p[0]*p[9]*p[10]*p[19] + 2*p[0]*p[11]*p[12]*p[19] - 2*p[0]*p[11]*p[13]*p[16] + 2*p[0]*p[12]*p[14]*p[16] + 2*p[0]*p[13]*p[14]*p[19] - 2*p[7]*p[12]*p[19] + p[7]*p[12]*p[28] + 2*p[7]*p[13]*p[16] - p[7]*p[13]*p[25] - 2*p[8]*p[11]*p[19] + p[8]*p[11]*p[28] - 2*p[8]*p[14]*p[16] + p[8]*p[14]*p[25] + 2*p[9]*p[11]*p[16] - p[9]*p[11]*p[25] - 2*p[9]*p[14]*p[19] + p[9]*p[14]*p[28] - 2*p[10]*p[12]*p[16] + p[10]*p[12]*p[25] - 2*p[10]*p[13]*p[19] + p[10]*p[13]*p[28]; + coeff[150] = -2*p[0]*p[7]*p[8]*p[20] + 2*p[0]*p[7]*p[9]*p[17] + 2*p[0]*p[8]*p[8]*p[22] - 2*p[0]*p[8]*p[10]*p[17] + 2*p[0]*p[9]*p[9]*p[22] - 2*p[0]*p[9]*p[10]*p[20] + 2*p[0]*p[11]*p[12]*p[20] - 2*p[0]*p[11]*p[13]*p[17] - 2*p[0]*p[12]*p[12]*p[22] + 2*p[0]*p[12]*p[14]*p[17] - 2*p[0]*p[13]*p[13]*p[22] + 2*p[0]*p[13]*p[14]*p[20] - 2*p[7]*p[11]*p[22] + p[7]*p[11]*p[31] - 2*p[7]*p[12]*p[20] + p[7]*p[12]*p[29] + 2*p[7]*p[13]*p[17] - p[7]*p[13]*p[26] - 2*p[8]*p[11]*p[20] + p[8]*p[11]*p[29] + 2*p[8]*p[12]*p[22] - p[8]*p[12]*p[31] - 2*p[8]*p[14]*p[17] + p[8]*p[14]*p[26] + 2*p[9]*p[11]*p[17] - p[9]*p[11]*p[26] + 2*p[9]*p[13]*p[22] - p[9]*p[13]*p[31] - 2*p[9]*p[14]*p[20] + p[9]*p[14]*p[29] - 2*p[10]*p[12]*p[17] + p[10]*p[12]*p[26] - 2*p[10]*p[13]*p[20] + p[10]*p[13]*p[29] - 2*p[10]*p[14]*p[22] + p[10]*p[14]*p[31]; + coeff[151] = (-p[7]*p[13] + p[8]*p[14] - p[9]*p[11] + p[10]*p[12])*p[15]; + coeff[152] = p[7]*p[12]*p[19] - p[7]*p[13]*p[16] + p[8]*p[11]*p[19] + p[8]*p[14]*p[16] - p[9]*p[11]*p[16] + p[9]*p[14]*p[19] + p[10]*p[12]*p[16] + p[10]*p[13]*p[19]; + coeff[153] = p[7]*p[11]*p[22] + p[7]*p[12]*p[20] - p[7]*p[13]*p[17] + p[8]*p[11]*p[20] - p[8]*p[12]*p[22] + p[8]*p[14]*p[17] - p[9]*p[11]*p[17] - p[9]*p[13]*p[22] + p[9]*p[14]*p[20] + p[10]*p[12]*p[17] + p[10]*p[13]*p[20] + p[10]*p[14]*p[22]; + coeff[154] = 2*(p[7]*p[8]*p[21] - p[7]*p[8]*p[30] - p[7]*p[9]*p[18] + p[7]*p[9]*p[27] - p[8]*p[8]*p[23] + p[8]*p[8]*p[32] + p[8]*p[10]*p[18] - p[8]*p[10]*p[27] - p[9]*p[9]*p[23] + p[9]*p[9]*p[32] + p[9]*p[10]*p[21] - p[9]*p[10]*p[30] - p[11]*p[12]*p[21] + p[11]*p[12]*p[30] + p[11]*p[13]*p[18] - p[11]*p[13]*p[27] + p[12]*p[12]*p[23] - p[12]*p[12]*p[32] - p[12]*p[14]*p[18] + p[12]*p[14]*p[27] + p[13]*p[13]*p[23] - p[13]*p[13]*p[32] - p[13]*p[14]*p[21] + p[13]*p[14]*p[30])*p[0]; + coeff[155] = -4*p[0]*p[7]*p[9]*p[15] + 2*p[0]*p[7]*p[9]*p[24] + 4*p[0]*p[8]*p[10]*p[15] - 2*p[0]*p[8]*p[10]*p[24] + 4*p[0]*p[11]*p[13]*p[15] - 2*p[0]*p[11]*p[13]*p[24] - 4*p[0]*p[12]*p[14]*p[15] + 2*p[0]*p[12]*p[14]*p[24] - 2*p[7]*p[13]*p[15] + 2*p[7]*p[13]*p[24] + 2*p[8]*p[14]*p[15] - 2*p[8]*p[14]*p[24] - 2*p[9]*p[11]*p[15] + 2*p[9]*p[11]*p[24] + 2*p[10]*p[12]*p[15] - 2*p[10]*p[12]*p[24]; + coeff[156] = 4*p[0]*p[7]*p[8]*p[19] - 2*p[0]*p[7]*p[8]*p[28] - 4*p[0]*p[7]*p[9]*p[16] + 2*p[0]*p[7]*p[9]*p[25] + 4*p[0]*p[8]*p[10]*p[16] - 2*p[0]*p[8]*p[10]*p[25] + 4*p[0]*p[9]*p[10]*p[19] - 2*p[0]*p[9]*p[10]*p[28] - 4*p[0]*p[11]*p[12]*p[19] + 2*p[0]*p[11]*p[12]*p[28] + 4*p[0]*p[11]*p[13]*p[16] - 2*p[0]*p[11]*p[13]*p[25] - 4*p[0]*p[12]*p[14]*p[16] + 2*p[0]*p[12]*p[14]*p[25] - 4*p[0]*p[13]*p[14]*p[19] + 2*p[0]*p[13]*p[14]*p[28] + 2*p[7]*p[12]*p[19] - 2*p[7]*p[12]*p[28] - 2*p[7]*p[13]*p[16] + 2*p[7]*p[13]*p[25] + 2*p[8]*p[11]*p[19] - 2*p[8]*p[11]*p[28] + 2*p[8]*p[14]*p[16] - 2*p[8]*p[14]*p[25] - 2*p[9]*p[11]*p[16] + 2*p[9]*p[11]*p[25] + 2*p[9]*p[14]*p[19] - 2*p[9]*p[14]*p[28] + 2*p[10]*p[12]*p[16] - 2*p[10]*p[12]*p[25] + 2*p[10]*p[13]*p[19] - 2*p[10]*p[13]*p[28]; + coeff[157] = 4*p[0]*p[7]*p[8]*p[20] - 2*p[0]*p[7]*p[8]*p[29] - 4*p[0]*p[7]*p[9]*p[17] + 2*p[0]*p[7]*p[9]*p[26] - 4*p[0]*p[8]*p[8]*p[22] + 2*p[0]*p[8]*p[8]*p[31] + 4*p[0]*p[8]*p[10]*p[17] - 2*p[0]*p[8]*p[10]*p[26] - 4*p[0]*p[9]*p[9]*p[22] + 2*p[0]*p[9]*p[9]*p[31] + 4*p[0]*p[9]*p[10]*p[20] - 2*p[0]*p[9]*p[10]*p[29] - 4*p[0]*p[11]*p[12]*p[20] + 2*p[0]*p[11]*p[12]*p[29] + 4*p[0]*p[11]*p[13]*p[17] - 2*p[0]*p[11]*p[13]*p[26] + 4*p[0]*p[12]*p[12]*p[22] - 2*p[0]*p[12]*p[12]*p[31] - 4*p[0]*p[12]*p[14]*p[17] + 2*p[0]*p[12]*p[14]*p[26] + 4*p[0]*p[13]*p[13]*p[22] - 2*p[0]*p[13]*p[13]*p[31] - 4*p[0]*p[13]*p[14]*p[20] + 2*p[0]*p[13]*p[14]*p[29] + 2*p[7]*p[11]*p[22] - 2*p[7]*p[11]*p[31] + 2*p[7]*p[12]*p[20] - 2*p[7]*p[12]*p[29] - 2*p[7]*p[13]*p[17] + 2*p[7]*p[13]*p[26] + 2*p[8]*p[11]*p[20] - 2*p[8]*p[11]*p[29] - 2*p[8]*p[12]*p[22] + 2*p[8]*p[12]*p[31] + 2*p[8]*p[14]*p[17] - 2*p[8]*p[14]*p[26] - 2*p[9]*p[11]*p[17] + 2*p[9]*p[11]*p[26] - 2*p[9]*p[13]*p[22] + 2*p[9]*p[13]*p[31] + 2*p[9]*p[14]*p[20] - 2*p[9]*p[14]*p[29] + 2*p[10]*p[12]*p[17] - 2*p[10]*p[12]*p[26] + 2*p[10]*p[13]*p[20] - 2*p[10]*p[13]*p[29] + 2*p[10]*p[14]*p[22] - 2*p[10]*p[14]*p[31]; + coeff[158] = 2*p[0]*p[7]*p[9]*p[15] - 2*p[0]*p[8]*p[10]*p[15] - 2*p[0]*p[11]*p[13]*p[15] + 2*p[0]*p[12]*p[14]*p[15] + 2*p[7]*p[13]*p[15] - 2*p[7]*p[13]*p[24] - 2*p[8]*p[14]*p[15] + 2*p[8]*p[14]*p[24] + 2*p[9]*p[11]*p[15] - 2*p[9]*p[11]*p[24] - 2*p[10]*p[12]*p[15] + 2*p[10]*p[12]*p[24]; + coeff[159] = -2*p[0]*p[7]*p[8]*p[19] + 2*p[0]*p[7]*p[9]*p[16] - 2*p[0]*p[8]*p[10]*p[16] - 2*p[0]*p[9]*p[10]*p[19] + 2*p[0]*p[11]*p[12]*p[19] - 2*p[0]*p[11]*p[13]*p[16] + 2*p[0]*p[12]*p[14]*p[16] + 2*p[0]*p[13]*p[14]*p[19] - 2*p[7]*p[12]*p[19] + 2*p[7]*p[12]*p[28] + 2*p[7]*p[13]*p[16] - 2*p[7]*p[13]*p[25] - 2*p[8]*p[11]*p[19] + 2*p[8]*p[11]*p[28] - 2*p[8]*p[14]*p[16] + 2*p[8]*p[14]*p[25] + 2*p[9]*p[11]*p[16] - 2*p[9]*p[11]*p[25] - 2*p[9]*p[14]*p[19] + 2*p[9]*p[14]*p[28] - 2*p[10]*p[12]*p[16] + 2*p[10]*p[12]*p[25] - 2*p[10]*p[13]*p[19] + 2*p[10]*p[13]*p[28]; + coeff[160] = -2*p[0]*p[7]*p[8]*p[20] + 2*p[0]*p[7]*p[9]*p[17] + 2*p[0]*p[8]*p[8]*p[22] - 2*p[0]*p[8]*p[10]*p[17] + 2*p[0]*p[9]*p[9]*p[22] - 2*p[0]*p[9]*p[10]*p[20] + 2*p[0]*p[11]*p[12]*p[20] - 2*p[0]*p[11]*p[13]*p[17] - 2*p[0]*p[12]*p[12]*p[22] + 2*p[0]*p[12]*p[14]*p[17] - 2*p[0]*p[13]*p[13]*p[22] + 2*p[0]*p[13]*p[14]*p[20] - 2*p[7]*p[11]*p[22] + 2*p[7]*p[11]*p[31] - 2*p[7]*p[12]*p[20] + 2*p[7]*p[12]*p[29] + 2*p[7]*p[13]*p[17] - 2*p[7]*p[13]*p[26] - 2*p[8]*p[11]*p[20] + 2*p[8]*p[11]*p[29] + 2*p[8]*p[12]*p[22] - 2*p[8]*p[12]*p[31] - 2*p[8]*p[14]*p[17] + 2*p[8]*p[14]*p[26] + 2*p[9]*p[11]*p[17] - 2*p[9]*p[11]*p[26] + 2*p[9]*p[13]*p[22] - 2*p[9]*p[13]*p[31] - 2*p[9]*p[14]*p[20] + 2*p[9]*p[14]*p[29] - 2*p[10]*p[12]*p[17] + 2*p[10]*p[12]*p[26] - 2*p[10]*p[13]*p[20] + 2*p[10]*p[13]*p[29] - 2*p[10]*p[14]*p[22] + 2*p[10]*p[14]*p[31]; + coeff[161] = 0; + coeff[162] = 2*(p[7]*p[9]*p[15] - p[7]*p[9]*p[24] - p[8]*p[10]*p[15] + p[8]*p[10]*p[24] - p[11]*p[13]*p[15] + p[11]*p[13]*p[24] + p[12]*p[14]*p[15] - p[12]*p[14]*p[24])*p[0]; + coeff[163] = 2*(-p[7]*p[8]*p[19] + p[7]*p[8]*p[28] + p[7]*p[9]*p[16] - p[7]*p[9]*p[25] - p[8]*p[10]*p[16] + p[8]*p[10]*p[25] - p[9]*p[10]*p[19] + p[9]*p[10]*p[28] + p[11]*p[12]*p[19] - p[11]*p[12]*p[28] - p[11]*p[13]*p[16] + p[11]*p[13]*p[25] + p[12]*p[14]*p[16] - p[12]*p[14]*p[25] + p[13]*p[14]*p[19] - p[13]*p[14]*p[28])*p[0]; + coeff[164] = 2*(-p[7]*p[8]*p[20] + p[7]*p[8]*p[29] + p[7]*p[9]*p[17] - p[7]*p[9]*p[26] + p[8]*p[8]*p[22] - p[8]*p[8]*p[31] - p[8]*p[10]*p[17] + p[8]*p[10]*p[26] + p[9]*p[9]*p[22] - p[9]*p[9]*p[31] - p[9]*p[10]*p[20] + p[9]*p[10]*p[29] + p[11]*p[12]*p[20] - p[11]*p[12]*p[29] - p[11]*p[13]*p[17] + p[11]*p[13]*p[26] - p[12]*p[12]*p[22] + p[12]*p[12]*p[31] + p[12]*p[14]*p[17] - p[12]*p[14]*p[26] - p[13]*p[13]*p[22] + p[13]*p[13]*p[31] + p[13]*p[14]*p[20] - p[13]*p[14]*p[29])*p[0]; + coeff[165] = 2*(-p[7]*p[9]*p[15] + p[7]*p[9]*p[24] + p[8]*p[10]*p[15] - p[8]*p[10]*p[24] + p[11]*p[13]*p[15] - p[11]*p[13]*p[24] - p[12]*p[14]*p[15] + p[12]*p[14]*p[24])*p[0]; + coeff[166] = 2*(p[7]*p[8]*p[19] - p[7]*p[8]*p[28] - p[7]*p[9]*p[16] + p[7]*p[9]*p[25] + p[8]*p[10]*p[16] - p[8]*p[10]*p[25] + p[9]*p[10]*p[19] - p[9]*p[10]*p[28] - p[11]*p[12]*p[19] + p[11]*p[12]*p[28] + p[11]*p[13]*p[16] - p[11]*p[13]*p[25] - p[12]*p[14]*p[16] + p[12]*p[14]*p[25] - p[13]*p[14]*p[19] + p[13]*p[14]*p[28])*p[0]; + coeff[167] = 2*(p[7]*p[8]*p[20] - p[7]*p[8]*p[29] - p[7]*p[9]*p[17] + p[7]*p[9]*p[26] - p[8]*p[8]*p[22] + p[8]*p[8]*p[31] + p[8]*p[10]*p[17] - p[8]*p[10]*p[26] - p[9]*p[9]*p[22] + p[9]*p[9]*p[31] + p[9]*p[10]*p[20] - p[9]*p[10]*p[29] - p[11]*p[12]*p[20] + p[11]*p[12]*p[29] + p[11]*p[13]*p[17] - p[11]*p[13]*p[26] + p[12]*p[12]*p[22] - p[12]*p[12]*p[31] - p[12]*p[14]*p[17] + p[12]*p[14]*p[26] + p[13]*p[13]*p[22] - p[13]*p[13]*p[31] - p[13]*p[14]*p[20] + p[13]*p[14]*p[29])*p[0]; +} + +} // namespace embree diff --git a/thirdparty/embree/kernels/common/point_query.h b/thirdparty/embree/kernels/common/point_query.h new file mode 100644 index 000000000000..27d158ca3ab0 --- /dev/null +++ b/thirdparty/embree/kernels/common/point_query.h @@ -0,0 +1,136 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" + +namespace embree +{ + /* Point query structure for closest point query */ + template + struct RTC_ALIGN(16) PointQueryK + { + /* Default construction does nothing */ + __forceinline PointQueryK() {} + + /* Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far */ + __forceinline PointQueryK(const Vec3vf& p, const vfloat& radius = inf, const vfloat& time = zero) + : p(p), time(time), radius(radius) {} + + /* Returns the size of the ray */ + static __forceinline size_t size() { return K; } + + /* Calculates if this is a valid ray that does not cause issues during traversal */ + __forceinline vbool valid() const + { + const vbool vx = (abs(p.x) <= vfloat(FLT_LARGE)); + const vbool vy = (abs(p.y) <= vfloat(FLT_LARGE)); + const vbool vz = (abs(p.z) <= vfloat(FLT_LARGE)); + const vbool vn = radius >= vfloat(0); + const vbool vf = abs(time) < vfloat(inf); + return vx & vy & vz & vn & vf; + } + + __forceinline void get(PointQueryK<1>* ray) const; + __forceinline void get(size_t i, PointQueryK<1>& ray) const; + __forceinline void set(const PointQueryK<1>* ray); + __forceinline void set(size_t i, const PointQueryK<1>& ray); + + Vec3vf p; // location of the query point + vfloat time; // time for motion blur + vfloat radius; // radius for the point query + }; + + /* Specialization for a single point query */ + template<> + struct RTC_ALIGN(16) PointQueryK<1> + { + /* Default construction does nothing */ + __forceinline PointQueryK() {} + + /* Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far */ + __forceinline PointQueryK(const Vec3fa& p, float radius = inf, float time = zero) + : p(p), time(time), radius(radius) {} + + /* Calculates if this is a valid ray that does not cause issues during traversal */ + __forceinline bool valid() const { + return all(le_mask(abs(Vec3fa(p)), Vec3fa(FLT_LARGE)) & le_mask(Vec3fa(0.f), Vec3fa(radius))) && abs(time) < float(inf); + } + + Vec3f p; + float time; + float radius; + }; + + /* Converts point query packet to single point query */ + template + __forceinline void PointQueryK::get(PointQueryK<1>* query) const + { + for (size_t i = 0; i < K; i++) // FIXME: use SIMD transpose + { + query[i].p.x = p.x[i]; + query[i].p.y = p.y[i]; + query[i].p.z = p.z[i]; + query[i].time = time[i]; + query[i].radius = radius[i]; + } + } + + /* Extracts a single point query out of a point query packet*/ + template + __forceinline void PointQueryK::get(size_t i, PointQueryK<1>& query) const + { + query.p.x = p.x[i]; + query.p.y = p.y[i]; + query.p.z = p.z[i]; + query.radius = radius[i]; + query.time = time[i]; + } + + /* Converts single point query to point query packet */ + template + __forceinline void PointQueryK::set(const PointQueryK<1>* query) + { + for (size_t i = 0; i < K; i++) + { + p.x[i] = query[i].p.x; + p.y[i] = query[i].p.y; + p.z[i] = query[i].p.z; + radius[i] = query[i].radius; + time[i] = query[i].time; + } + } + + /* inserts a single point query into a point query packet element */ + template + __forceinline void PointQueryK::set(size_t i, const PointQueryK<1>& query) + { + p.x[i] = query.p.x; + p.y[i] = query.p.y; + p.z[i] = query.p.z; + radius[i] = query.radius; + time[i] = query.time; + } + + /* Shortcuts */ + typedef PointQueryK<1> PointQuery; + typedef PointQueryK<4> PointQuery4; + typedef PointQueryK<8> PointQuery8; + typedef PointQueryK<16> PointQuery16; + struct PointQueryN; + + /* Outputs point query to stream */ + template + __forceinline embree_ostream operator <<(embree_ostream cout, const PointQueryK& query) + { + cout << "{ " << embree_endl + << " p = " << query.p << embree_endl + << " r = " << query.radius << embree_endl + << " time = " << query.time << embree_endl + << "}"; + return cout; + } +} diff --git a/thirdparty/embree/kernels/common/primref.h b/thirdparty/embree/kernels/common/primref.h new file mode 100644 index 000000000000..3d4f9c0d44d8 --- /dev/null +++ b/thirdparty/embree/kernels/common/primref.h @@ -0,0 +1,138 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" + +namespace embree +{ + /*! A primitive reference stores the bounds of the primitive and its ID. */ + struct __aligned(32) PrimRef + { + __forceinline PrimRef () {} + +#if defined(__AVX__) + __forceinline PrimRef(const PrimRef& v) { + vfloat8::store((float*)this,vfloat8::load((float*)&v)); + } + __forceinline PrimRef& operator=(const PrimRef& v) { + vfloat8::store((float*)this,vfloat8::load((float*)&v)); return *this; + } +#endif + + __forceinline PrimRef (const BBox3fa& bounds, unsigned int geomID, unsigned int primID) + { + lower = Vec3fx(bounds.lower, geomID); + upper = Vec3fx(bounds.upper, primID); + } + + __forceinline PrimRef (const BBox3fa& bounds, size_t id) + { +#if defined(__X86_64__) + lower = Vec3fx(bounds.lower, (unsigned)(id & 0xFFFFFFFF)); + upper = Vec3fx(bounds.upper, (unsigned)((id >> 32) & 0xFFFFFFFF)); +#else + lower = Vec3fx(bounds.lower, (unsigned)id); + upper = Vec3fx(bounds.upper, (unsigned)0); +#endif + } + + /*! calculates twice the center of the primitive */ + __forceinline const Vec3fa center2() const { + return lower+upper; + } + + /*! return the bounding box of the primitive */ + __forceinline const BBox3fa bounds() const { + return BBox3fa(lower,upper); + } + + /*! size for bin heuristic is 1 */ + __forceinline unsigned size() const { + return 1; + } + + /*! returns bounds and centroid used for binning */ + __forceinline void binBoundsAndCenter(BBox3fa& bounds_o, Vec3fa& center_o) const + { + bounds_o = bounds(); + center_o = embree::center2(bounds_o); + } + + __forceinline unsigned& geomIDref() { // FIXME: remove !!!!!!! + return lower.u; + } + __forceinline unsigned& primIDref() { // FIXME: remove !!!!!!! + return upper.u; + } + + /*! returns the geometry ID */ + __forceinline unsigned geomID() const { + return lower.a; + } + + /*! returns the primitive ID */ + __forceinline unsigned primID() const { + return upper.a; + } + + /*! returns an size_t sized ID */ + __forceinline size_t ID() const { +#if defined(__X86_64__) + return size_t(lower.u) + (size_t(upper.u) << 32); +#else + return size_t(lower.u); +#endif + } + + /*! special function for operator< */ + __forceinline uint64_t ID64() const { + return (((uint64_t)primID()) << 32) + (uint64_t)geomID(); + } + + /*! allows sorting the primrefs by ID */ + friend __forceinline bool operator<(const PrimRef& p0, const PrimRef& p1) { + return p0.ID64() < p1.ID64(); + } + + /*! Outputs primitive reference to a stream. */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const PrimRef& ref) { + return cout << "{ lower = " << ref.lower << ", upper = " << ref.upper << ", geomID = " << ref.geomID() << ", primID = " << ref.primID() << " }"; + } + + public: + Vec3fx lower; //!< lower bounds and geomID + Vec3fx upper; //!< upper bounds and primID + }; + + /*! fast exchange for PrimRefs */ + __forceinline void xchg(PrimRef& a, PrimRef& b) + { +#if defined(__AVX__) + const vfloat8 aa = vfloat8::load((float*)&a); + const vfloat8 bb = vfloat8::load((float*)&b); + vfloat8::store((float*)&a,bb); + vfloat8::store((float*)&b,aa); +#else + std::swap(a,b); +#endif + } + + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + /************************************************************************************/ + + struct SubGridBuildData { + unsigned short sx,sy; + unsigned int primID; + + __forceinline SubGridBuildData() {}; + __forceinline SubGridBuildData(const unsigned int sx, const unsigned int sy, const unsigned int primID) : sx(sx), sy(sy), primID(primID) {}; + + __forceinline size_t x() const { return (size_t)sx & 0x7fff; } + __forceinline size_t y() const { return (size_t)sy & 0x7fff; } + + }; +} diff --git a/thirdparty/embree/kernels/common/primref_mb.h b/thirdparty/embree/kernels/common/primref_mb.h new file mode 100644 index 000000000000..97a3fbe4e6b7 --- /dev/null +++ b/thirdparty/embree/kernels/common/primref_mb.h @@ -0,0 +1,262 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" + +#define MBLUR_BIN_LBBOX 1 + +namespace embree +{ +#if MBLUR_BIN_LBBOX + + /*! A primitive reference stores the bounds of the primitive and its ID. */ + struct PrimRefMB + { + typedef LBBox3fa BBox; + + __forceinline PrimRefMB () {} + + __forceinline PrimRefMB (const LBBox3fa& lbounds_i, unsigned int activeTimeSegments, BBox1f time_range, unsigned int totalTimeSegments, unsigned int geomID, unsigned int primID) + : lbounds((LBBox3fx)lbounds_i), time_range(time_range) + { + assert(activeTimeSegments > 0); + lbounds.bounds0.lower.a = geomID; + lbounds.bounds0.upper.a = primID; + lbounds.bounds1.lower.a = activeTimeSegments; + lbounds.bounds1.upper.a = totalTimeSegments; + } + + __forceinline PrimRefMB (EmptyTy empty, const LBBox3fa& lbounds_i, unsigned int activeTimeSegments, BBox1f time_range, unsigned int totalTimeSegments, size_t id) + : lbounds((LBBox3fx)lbounds_i), time_range(time_range) + { + assert(activeTimeSegments > 0); +#if defined(__X86_64__) + lbounds.bounds0.lower.a = id & 0xFFFFFFFF; + lbounds.bounds0.upper.a = (id >> 32) & 0xFFFFFFFF; +#else + lbounds.bounds0.lower.a = id; + lbounds.bounds0.upper.a = 0; +#endif + lbounds.bounds1.lower.a = activeTimeSegments; + lbounds.bounds1.upper.a = totalTimeSegments; + } + + __forceinline PrimRefMB (const LBBox3fa& lbounds_i, unsigned int activeTimeSegments, BBox1f time_range, unsigned int totalTimeSegments, size_t id) + : lbounds((LBBox3fx)lbounds_i), time_range(time_range) + { + assert(activeTimeSegments > 0); +#if defined(__X86_64__) + lbounds.bounds0.lower.u = id & 0xFFFFFFFF; + lbounds.bounds0.upper.u = (id >> 32) & 0xFFFFFFFF; +#else + lbounds.bounds0.lower.u = id; + lbounds.bounds0.upper.u = 0; +#endif + lbounds.bounds1.lower.a = activeTimeSegments; + lbounds.bounds1.upper.a = totalTimeSegments; + } + + /*! returns bounds for binning */ + __forceinline LBBox3fa bounds() const { + return lbounds; + } + + /*! returns the number of time segments of this primref */ + __forceinline unsigned size() const { + return lbounds.bounds1.lower.a; + } + + __forceinline unsigned totalTimeSegments() const { + return lbounds.bounds1.upper.a; + } + + /* calculate overlapping time segment range */ + __forceinline range timeSegmentRange(const BBox1f& range) const { + return getTimeSegmentRange(range,time_range,float(totalTimeSegments())); + } + + /* returns time that corresponds to time step */ + __forceinline float timeStep(const int i) const { + assert(i>=0 && i<=(int)totalTimeSegments()); + return time_range.lower + time_range.size()*float(i)/float(totalTimeSegments()); + } + + /*! checks if time range overlaps */ + __forceinline bool time_range_overlap(const BBox1f& range) const + { + if (0.9999f*time_range.upper <= range.lower) return false; + if (1.0001f*time_range.lower >= range.upper) return false; + return true; + } + + /*! returns center for binning */ + __forceinline Vec3fa binCenter() const { + return center2(lbounds.interpolate(0.5f)); + } + + /*! returns bounds and centroid used for binning */ + __forceinline void binBoundsAndCenter(LBBox3fa& bounds_o, Vec3fa& center_o) const + { + bounds_o = bounds(); + center_o = binCenter(); + } + + /*! returns the geometry ID */ + __forceinline unsigned geomID() const { + return lbounds.bounds0.lower.a; + } + + /*! returns the primitive ID */ + __forceinline unsigned primID() const { + return lbounds.bounds0.upper.a; + } + + /*! returns an size_t sized ID */ + __forceinline size_t ID() const { +#if defined(__X86_64__) + return size_t(lbounds.bounds0.lower.u) + (size_t(lbounds.bounds0.upper.u) << 32); +#else + return size_t(lbounds.bounds0.lower.u); +#endif + } + + /*! special function for operator< */ + __forceinline uint64_t ID64() const { + return (((uint64_t)primID()) << 32) + (uint64_t)geomID(); + } + + /*! allows sorting the primrefs by ID */ + friend __forceinline bool operator<(const PrimRefMB& p0, const PrimRefMB& p1) { + return p0.ID64() < p1.ID64(); + } + + /*! Outputs primitive reference to a stream. */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const PrimRefMB& ref) { + return cout << "{ time_range = " << ref.time_range << ", bounds = " << ref.bounds() << ", geomID = " << ref.geomID() << ", primID = " << ref.primID() << ", active_segments = " << ref.size() << ", total_segments = " << ref.totalTimeSegments() << " }"; + } + + public: + LBBox3fx lbounds; + BBox1f time_range; // entire geometry time range + }; + +#else + + /*! A primitive reference stores the bounds of the primitive and its ID. */ + struct __aligned(16) PrimRefMB + { + typedef BBox3fa BBox; + + __forceinline PrimRefMB () {} + + __forceinline PrimRefMB (const LBBox3fa& bounds, unsigned int activeTimeSegments, BBox1f time_range, unsigned int totalTimeSegments, unsigned int geomID, unsigned int primID) + : bbox(bounds.interpolate(0.5f)), _activeTimeSegments(activeTimeSegments), _totalTimeSegments(totalTimeSegments), time_range(time_range) + { + assert(activeTimeSegments > 0); + bbox.lower.a = geomID; + bbox.upper.a = primID; + } + + __forceinline PrimRefMB (EmptyTy empty, const LBBox3fa& bounds, unsigned int activeTimeSegments, BBox1f time_range, unsigned int totalTimeSegments, size_t id) + : bbox(bounds.interpolate(0.5f)), _activeTimeSegments(activeTimeSegments), _totalTimeSegments(totalTimeSegments), time_range(time_range) + { + assert(activeTimeSegments > 0); +#if defined(__X86_64__) + bbox.lower.u = id & 0xFFFFFFFF; + bbox.upper.u = (id >> 32) & 0xFFFFFFFF; +#else + bbox.lower.u = id; + bbox.upper.u = 0; +#endif + } + + /*! returns bounds for binning */ + __forceinline BBox3fa bounds() const { + return bbox; + } + + /*! returns the number of time segments of this primref */ + __forceinline unsigned int size() const { + return _activeTimeSegments; + } + + __forceinline unsigned int totalTimeSegments() const { + return _totalTimeSegments; + } + + /* calculate overlapping time segment range */ + __forceinline range timeSegmentRange(const BBox1f& range) const { + return getTimeSegmentRange(range,time_range,float(_totalTimeSegments)); + } + + /* returns time that corresponds to time step */ + __forceinline float timeStep(const int i) const { + assert(i>=0 && i<=(int)_totalTimeSegments); + return time_range.lower + time_range.size()*float(i)/float(_totalTimeSegments); + } + + /*! checks if time range overlaps */ + __forceinline bool time_range_overlap(const BBox1f& range) const + { + if (0.9999f*time_range.upper <= range.lower) return false; + if (1.0001f*time_range.lower >= range.upper) return false; + return true; + } + + /*! returns center for binning */ + __forceinline Vec3fa binCenter() const { + return center2(bounds()); + } + + /*! returns bounds and centroid used for binning */ + __forceinline void binBoundsAndCenter(BBox3fa& bounds_o, Vec3fa& center_o) const + { + bounds_o = bounds(); + center_o = center2(bounds()); + } + + /*! returns the geometry ID */ + __forceinline unsigned int geomID() const { + return bbox.lower.a; + } + + /*! returns the primitive ID */ + __forceinline unsigned int primID() const { + return bbox.upper.a; + } + + /*! returns an size_t sized ID */ + __forceinline size_t ID() const { +#if defined(__X86_64__) + return size_t(bbox.lower.u) + (size_t(bbox.upper.u) << 32); +#else + return size_t(bbox.lower.u); +#endif + } + + /*! special function for operator< */ + __forceinline uint64_t ID64() const { + return (((uint64_t)primID()) << 32) + (uint64_t)geomID(); + } + + /*! allows sorting the primrefs by ID */ + friend __forceinline bool operator<(const PrimRefMB& p0, const PrimRefMB& p1) { + return p0.ID64() < p1.ID64(); + } + + /*! Outputs primitive reference to a stream. */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const PrimRefMB& ref) { + return cout << "{ bounds = " << ref.bounds() << ", geomID = " << ref.geomID() << ", primID = " << ref.primID() << ", active_segments = " << ref.size() << ", total_segments = " << ref.totalTimeSegments() << " }"; + } + + public: + BBox3fa bbox; // bounds, geomID, primID + unsigned int _activeTimeSegments; + unsigned int _totalTimeSegments; + BBox1f time_range; // entire geometry time range + }; + +#endif +} diff --git a/thirdparty/embree/kernels/common/profile.h b/thirdparty/embree/kernels/common/profile.h new file mode 100644 index 000000000000..a7de36414de2 --- /dev/null +++ b/thirdparty/embree/kernels/common/profile.h @@ -0,0 +1,159 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" + +namespace embree +{ + /*! helper structure for the implementation of the profile functions below */ + struct ProfileTimer + { + static const size_t N = 20; + + ProfileTimer () {} + + ProfileTimer (const size_t numSkip) : i(0), j(0), maxJ(0), numSkip(numSkip), t0(0) + { + for (size_t i=0; i=numSkip) { + dt_min[j] = min(dt_min[j],dt); + dt_avg[j] = dt_avg[j] + dt; + dt_max[j] = max(dt_max[j],dt); + } + j++; + maxJ = max(maxJ,j); + } + + __forceinline void relative (const char* name) + { + const double t1 = getSeconds(); + const double dt = t1-tj; + tj = t1; + assert(names[j] == nullptr || names[j] == name); + names[j] = name; + if (i == 0) dt_fst[j] = dt; + if (i>=numSkip) { + dt_min[j] = min(dt_min[j],dt); + dt_avg[j] = dt_avg[j] + dt; + dt_max[j] = max(dt_max[j],dt); + } + j++; + maxJ = max(maxJ,j); + } + + void print(size_t numElements) + { + for (size_t k=0; k + void profile(const size_t numSkip, const size_t numIter, const size_t numElements, const Closure& closure) + { + ProfileTimer timer(numSkip); + + for (size_t i=0; i + void profile(ProfileTimer& timer, const size_t numSkip, const size_t numIter, const size_t numElements, const Closure& closure) + { + timer = ProfileTimer(numSkip); + + for (size_t i=0; i + struct RayK + { + /* Default construction does nothing */ + __forceinline RayK() {} + + /* Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far */ + __forceinline RayK(const Vec3vf& org, const Vec3vf& dir, + const vfloat& tnear = zero, const vfloat& tfar = inf, + const vfloat& time = zero, const vint& mask = -1, const vint& id = 0, const vint& flags = 0) + : org(org), dir(dir), _tnear(tnear), tfar(tfar), _time(time), mask(mask), id(id), flags(flags) {} + + /* Returns the size of the ray */ + static __forceinline size_t size() { return K; } + + /* Calculates if this is a valid ray that does not cause issues during traversal */ + __forceinline vbool valid() const + { + const vbool vx = (abs(org.x) <= vfloat(FLT_LARGE)) & (abs(dir.x) <= vfloat(FLT_LARGE)); + const vbool vy = (abs(org.y) <= vfloat(FLT_LARGE)) & (abs(dir.y) <= vfloat(FLT_LARGE)); + const vbool vz = (abs(org.z) <= vfloat(FLT_LARGE)) & (abs(dir.z) <= vfloat(FLT_LARGE)); + const vbool vn = abs(tnear()) <= vfloat(inf); + const vbool vf = abs(tfar) <= vfloat(inf); + return vx & vy & vz & vn & vf; + } + + __forceinline void get(RayK<1>* ray) const; + __forceinline void get(size_t i, RayK<1>& ray) const; + __forceinline void set(const RayK<1>* ray); + __forceinline void set(size_t i, const RayK<1>& ray); + + __forceinline void copy(size_t dest, size_t source); + + __forceinline vint octant() const + { + return select(dir.x < 0.0f, vint(1), vint(zero)) | + select(dir.y < 0.0f, vint(2), vint(zero)) | + select(dir.z < 0.0f, vint(4), vint(zero)); + } + + /* Ray data */ + Vec3vf org; // ray origin + vfloat _tnear; // start of ray segment + Vec3vf dir; // ray direction + vfloat _time; // time of this ray for motion blur + vfloat tfar; // end of ray segment + vint mask; // used to mask out objects during traversal + vint id; + vint flags; + + __forceinline vfloat& tnear() { return _tnear; } + __forceinline vfloat& time() { return _time; } + __forceinline const vfloat& tnear() const { return _tnear; } + __forceinline const vfloat& time() const { return _time; } + }; + + /* Ray+hit structure for K rays */ + template + struct RayHitK : RayK + { + using RayK::org; + using RayK::_tnear; + using RayK::dir; + using RayK::_time; + using RayK::tfar; + using RayK::mask; + using RayK::id; + using RayK::flags; + + using RayK::tnear; + using RayK::time; + + /* Default construction does nothing */ + __forceinline RayHitK() {} + + /* Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far */ + __forceinline RayHitK(const Vec3vf& org, const Vec3vf& dir, + const vfloat& tnear = zero, const vfloat& tfar = inf, + const vfloat& time = zero, const vint& mask = -1, const vint& id = 0, const vint& flags = 0) + : RayK(org, dir, tnear, tfar, time, mask, id, flags), + geomID(RTC_INVALID_GEOMETRY_ID) + { + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + instID[l] = RTC_INVALID_GEOMETRY_ID; + } + + __forceinline RayHitK(const RayK& ray) + : RayK(ray), + geomID(RTC_INVALID_GEOMETRY_ID) + { + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + instID[l] = RTC_INVALID_GEOMETRY_ID; + } + + __forceinline RayHitK& operator =(const RayK& ray) + { + org = ray.org; + _tnear = ray._tnear; + dir = ray.dir; + _time = ray._time; + tfar = ray.tfar; + mask = ray.mask; + id = ray.id; + flags = ray.flags; + + geomID = RTC_INVALID_GEOMETRY_ID; + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + instID[l] = RTC_INVALID_GEOMETRY_ID; + + return *this; + } + + /* Calculates if the hit is valid */ + __forceinline void verifyHit(const vbool& valid0) const + { + vbool valid = valid0 & geomID != vuint(RTC_INVALID_GEOMETRY_ID); + const vbool vt = (abs(tfar) <= vfloat(FLT_LARGE)) | (tfar == vfloat(neg_inf)); + const vbool vu = (abs(u) <= vfloat(FLT_LARGE)); + const vbool vv = (abs(u) <= vfloat(FLT_LARGE)); + const vbool vnx = abs(Ng.x) <= vfloat(FLT_LARGE); + const vbool vny = abs(Ng.y) <= vfloat(FLT_LARGE); + const vbool vnz = abs(Ng.z) <= vfloat(FLT_LARGE); + if (any(valid & !vt)) throw_RTCError(RTC_ERROR_UNKNOWN,"invalid t"); + if (any(valid & !vu)) throw_RTCError(RTC_ERROR_UNKNOWN,"invalid u"); + if (any(valid & !vv)) throw_RTCError(RTC_ERROR_UNKNOWN,"invalid v"); + if (any(valid & !vnx)) throw_RTCError(RTC_ERROR_UNKNOWN,"invalid Ng.x"); + if (any(valid & !vny)) throw_RTCError(RTC_ERROR_UNKNOWN,"invalid Ng.y"); + if (any(valid & !vnz)) throw_RTCError(RTC_ERROR_UNKNOWN,"invalid Ng.z"); + } + + __forceinline void get(RayHitK<1>* ray) const; + __forceinline void get(size_t i, RayHitK<1>& ray) const; + __forceinline void set(const RayHitK<1>* ray); + __forceinline void set(size_t i, const RayHitK<1>& ray); + + __forceinline void copy(size_t dest, size_t source); + + /* Hit data */ + Vec3vf Ng; // geometry normal + vfloat u; // barycentric u coordinate of hit + vfloat v; // barycentric v coordinate of hit + vuint primID; // primitive ID + vuint geomID; // geometry ID + vuint instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // instance ID + }; + + /* Specialization for a single ray */ + template<> + struct RayK<1> + { + /* Default construction does nothing */ + __forceinline RayK() {} + + /* Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far */ + __forceinline RayK(const Vec3fa& org, const Vec3fa& dir, float tnear = zero, float tfar = inf, float time = zero, int mask = -1, int id = 0, int flags = 0) + : org(org,tnear), dir(dir,time), tfar(tfar), mask(mask), id(id), flags(flags) {} + + /* Calculates if this is a valid ray that does not cause issues during traversal */ + __forceinline bool valid() const { + return all(le_mask(abs(Vec3fa(org)), Vec3fa(FLT_LARGE)) & le_mask(abs(Vec3fa(dir)), Vec3fa(FLT_LARGE))) && abs(tnear()) <= float(inf) && abs(tfar) <= float(inf); + } + + /* Ray data */ + Vec3ff org; // 3 floats for ray origin, 1 float for tnear + //float tnear; // start of ray segment + Vec3ff dir; // 3 floats for ray direction, 1 float for time + // float time; + float tfar; // end of ray segment + int mask; // used to mask out objects during traversal + int id; // ray ID + int flags; // ray flags + + __forceinline float& tnear() { return org.w; }; + __forceinline const float& tnear() const { return org.w; }; + + __forceinline float& time() { return dir.w; }; + __forceinline const float& time() const { return dir.w; }; + + }; + + template<> + struct RayHitK<1> : RayK<1> + { + /* Default construction does nothing */ + __forceinline RayHitK() {} + + /* Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far */ + __forceinline RayHitK(const Vec3fa& org, const Vec3fa& dir, float tnear = zero, float tfar = inf, float time = zero, int mask = -1, int id = 0, int flags = 0) + : RayK<1>(org, dir, tnear, tfar, time, mask, id, flags), + geomID(RTC_INVALID_GEOMETRY_ID) {} + + __forceinline RayHitK(const RayK<1>& ray) + : RayK<1>(ray), + geomID(RTC_INVALID_GEOMETRY_ID) {} + + __forceinline RayHitK<1>& operator =(const RayK<1>& ray) + { + org = ray.org; + dir = ray.dir; + tfar = ray.tfar; + mask = ray.mask; + id = ray.id; + flags = ray.flags; + + geomID = RTC_INVALID_GEOMETRY_ID; + + return *this; + } + + /* Calculates if the hit is valid */ + __forceinline void verifyHit() const + { + if (geomID == RTC_INVALID_GEOMETRY_ID) return; + const bool vt = (abs(tfar) <= FLT_LARGE) || (tfar == float(neg_inf)); + const bool vu = (abs(u) <= FLT_LARGE); + const bool vv = (abs(u) <= FLT_LARGE); + const bool vnx = abs(Ng.x) <= FLT_LARGE; + const bool vny = abs(Ng.y) <= FLT_LARGE; + const bool vnz = abs(Ng.z) <= FLT_LARGE; + if (!vt) throw_RTCError(RTC_ERROR_UNKNOWN, "invalid t"); + if (!vu) throw_RTCError(RTC_ERROR_UNKNOWN, "invalid u"); + if (!vv) throw_RTCError(RTC_ERROR_UNKNOWN, "invalid v"); + if (!vnx) throw_RTCError(RTC_ERROR_UNKNOWN, "invalid Ng.x"); + if (!vny) throw_RTCError(RTC_ERROR_UNKNOWN, "invalid Ng.y"); + if (!vnz) throw_RTCError(RTC_ERROR_UNKNOWN, "invalid Ng.z"); + } + + /* Hit data */ + Vec3f Ng; // not normalized geometry normal + float u; // barycentric u coordinate of hit + float v; // barycentric v coordinate of hit + unsigned int primID; // primitive ID + unsigned int geomID; // geometry ID + unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // instance ID + }; + + /* Converts ray packet to single rays */ + template + __forceinline void RayK::get(RayK<1>* ray) const + { + for (size_t i = 0; i < K; i++) // FIXME: use SIMD transpose + { + ray[i].org.x = org.x[i]; ray[i].org.y = org.y[i]; ray[i].org.z = org.z[i]; ray[i].tnear() = tnear()[i]; + ray[i].dir.x = dir.x[i]; ray[i].dir.y = dir.y[i]; ray[i].dir.z = dir.z[i]; ray[i].time() = time()[i]; + ray[i].tfar = tfar[i]; ray[i].mask = mask[i]; ray[i].id = id[i]; ray[i].flags = flags[i]; + } + } + + template + __forceinline void RayHitK::get(RayHitK<1>* ray) const + { + // FIXME: use SIMD transpose + for (size_t i = 0; i < K; i++) + get(i, ray[i]); + } + + /* Extracts a single ray out of a ray packet*/ + template + __forceinline void RayK::get(size_t i, RayK<1>& ray) const + { + ray.org.x = org.x[i]; ray.org.y = org.y[i]; ray.org.z = org.z[i]; ray.tnear() = tnear()[i]; + ray.dir.x = dir.x[i]; ray.dir.y = dir.y[i]; ray.dir.z = dir.z[i]; ray.time() = time()[i]; + ray.tfar = tfar[i]; ray.mask = mask[i]; ray.id = id[i]; ray.flags = flags[i]; + } + + template + __forceinline void RayHitK::get(size_t i, RayHitK<1>& ray) const + { + ray.org.x = org.x[i]; ray.org.y = org.y[i]; ray.org.z = org.z[i]; ray.tnear() = tnear()[i]; + ray.dir.x = dir.x[i]; ray.dir.y = dir.y[i]; ray.dir.z = dir.z[i]; ray.tfar = tfar[i]; ray.time() = time()[i]; + ray.mask = mask[i]; ray.id = id[i]; ray.flags = flags[i]; + ray.Ng.x = Ng.x[i]; ray.Ng.y = Ng.y[i]; ray.Ng.z = Ng.z[i]; + ray.u = u[i]; ray.v = v[i]; + ray.primID = primID[i]; ray.geomID = geomID[i]; + + instance_id_stack::copy(instID, ray.instID, i); + } + + /* Converts single rays to ray packet */ + template + __forceinline void RayK::set(const RayK<1>* ray) + { + // FIXME: use SIMD transpose + for (size_t i = 0; i < K; i++) + set(i, ray[i]); + } + + template + __forceinline void RayHitK::set(const RayHitK<1>* ray) + { + // FIXME: use SIMD transpose + for (size_t i = 0; i < K; i++) + set(i, ray[i]); + } + + /* inserts a single ray into a ray packet element */ + template + __forceinline void RayK::set(size_t i, const RayK<1>& ray) + { + org.x[i] = ray.org.x; org.y[i] = ray.org.y; org.z[i] = ray.org.z; tnear()[i] = ray.tnear(); + dir.x[i] = ray.dir.x; dir.y[i] = ray.dir.y; dir.z[i] = ray.dir.z; time()[i] = ray.time(); + tfar[i] = ray.tfar; mask[i] = ray.mask; id[i] = ray.id; flags[i] = ray.flags; + } + + template + __forceinline void RayHitK::set(size_t i, const RayHitK<1>& ray) + { + org.x[i] = ray.org.x; org.y[i] = ray.org.y; org.z[i] = ray.org.z; tnear()[i] = ray.tnear(); + dir.x[i] = ray.dir.x; dir.y[i] = ray.dir.y; dir.z[i] = ray.dir.z; time()[i] = ray.time(); + tfar[i] = ray.tfar; mask[i] = ray.mask; id[i] = ray.id; flags[i] = ray.flags; + Ng.x[i] = ray.Ng.x; Ng.y[i] = ray.Ng.y; Ng.z[i] = ray.Ng.z; + u[i] = ray.u; v[i] = ray.v; + primID[i] = ray.primID; geomID[i] = ray.geomID; + + instance_id_stack::copy(ray.instID, instID, i); + } + + /* copies a ray packet element into another element*/ + template + __forceinline void RayK::copy(size_t dest, size_t source) + { + org.x[dest] = org.x[source]; org.y[dest] = org.y[source]; org.z[dest] = org.z[source]; tnear()[dest] = tnear()[source]; + dir.x[dest] = dir.x[source]; dir.y[dest] = dir.y[source]; dir.z[dest] = dir.z[source]; time()[dest] = time()[source]; + tfar [dest] = tfar[source]; mask[dest] = mask[source]; id[dest] = id[source]; flags[dest] = flags[source]; + } + + template + __forceinline void RayHitK::copy(size_t dest, size_t source) + { + org.x[dest] = org.x[source]; org.y[dest] = org.y[source]; org.z[dest] = org.z[source]; tnear()[dest] = tnear()[source]; + dir.x[dest] = dir.x[source]; dir.y[dest] = dir.y[source]; dir.z[dest] = dir.z[source]; time()[dest] = time()[source]; + tfar [dest] = tfar[source]; mask[dest] = mask[source]; id[dest] = id[source]; flags[dest] = flags[source]; + Ng.x[dest] = Ng.x[source]; Ng.y[dest] = Ng.y[source]; Ng.z[dest] = Ng.z[source]; + u[dest] = u[source]; v[dest] = v[source]; + primID[dest] = primID[source]; geomID[dest] = geomID[source]; + + instance_id_stack::copy(instID, instID, source, dest); + } + + /* Shortcuts */ + typedef RayK<1> Ray; + typedef RayK<4> Ray4; + typedef RayK<8> Ray8; + typedef RayK<16> Ray16; + struct RayN; + + typedef RayHitK<1> RayHit; + typedef RayHitK<4> RayHit4; + typedef RayHitK<8> RayHit8; + typedef RayHitK<16> RayHit16; + struct RayHitN; + + template + struct RayTypeHelper; + + template + struct RayTypeHelper + { + typedef RayHitK Ty; + }; + + template + struct RayTypeHelper + { + typedef RayK Ty; + }; + + template + using RayType = typename RayTypeHelper<1, intersect>::Ty; + + template + using RayTypeK = typename RayTypeHelper::Ty; + + /* Outputs ray to stream */ + template + __forceinline embree_ostream operator <<(embree_ostream cout, const RayK& ray) + { + return cout << "{ " << embree_endl + << " org = " << ray.org << embree_endl + << " dir = " << ray.dir << embree_endl + << " near = " << ray.tnear() << embree_endl + << " far = " << ray.tfar << embree_endl + << " time = " << ray.time() << embree_endl + << " mask = " << ray.mask << embree_endl + << " id = " << ray.id << embree_endl + << " flags = " << ray.flags << embree_endl + << "}"; + } + + template + __forceinline embree_ostream operator <<(embree_ostream cout, const RayHitK& ray) + { + cout << "{ " << embree_endl + << " org = " << ray.org << embree_endl + << " dir = " << ray.dir << embree_endl + << " near = " << ray.tnear() << embree_endl + << " far = " << ray.tfar << embree_endl + << " time = " << ray.time() << embree_endl + << " mask = " << ray.mask << embree_endl + << " id = " << ray.id << embree_endl + << " flags = " << ray.flags << embree_endl + << " Ng = " << ray.Ng + << " u = " << ray.u << embree_endl + << " v = " << ray.v << embree_endl + << " primID = " << ray.primID << embree_endl + << " geomID = " << ray.geomID << embree_endl + << " instID ="; + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + { + cout << " " << ray.instID[l]; + } + cout << embree_endl; + return cout << "}"; + } + + struct RayStreamSOA + { + __forceinline RayStreamSOA(void* rays, size_t N) + : ptr((char*)rays), N(N) {} + + /* ray data access functions */ + __forceinline float* org_x(size_t offset = 0) { return (float*)&ptr[0*4*N+offset]; } // x coordinate of ray origin + __forceinline float* org_y(size_t offset = 0) { return (float*)&ptr[1*4*N+offset]; } // y coordinate of ray origin + __forceinline float* org_z(size_t offset = 0) { return (float*)&ptr[2*4*N+offset]; }; // z coordinate of ray origin + __forceinline float* tnear(size_t offset = 0) { return (float*)&ptr[3*4*N+offset]; }; // start of ray segment + + __forceinline float* dir_x(size_t offset = 0) { return (float*)&ptr[4*4*N+offset]; }; // x coordinate of ray direction + __forceinline float* dir_y(size_t offset = 0) { return (float*)&ptr[5*4*N+offset]; }; // y coordinate of ray direction + __forceinline float* dir_z(size_t offset = 0) { return (float*)&ptr[6*4*N+offset]; }; // z coordinate of ray direction + __forceinline float* time (size_t offset = 0) { return (float*)&ptr[7*4*N+offset]; }; // time of this ray for motion blur + + __forceinline float* tfar (size_t offset = 0) { return (float*)&ptr[8*4*N+offset]; }; // end of ray segment (set to hit distance) + __forceinline int* mask (size_t offset = 0) { return (int*)&ptr[9*4*N+offset]; }; // used to mask out objects during traversal (optional) + __forceinline int* id (size_t offset = 0) { return (int*)&ptr[10*4*N+offset]; }; // id + __forceinline int* flags(size_t offset = 0) { return (int*)&ptr[11*4*N+offset]; }; // flags + + /* hit data access functions */ + __forceinline float* Ng_x(size_t offset = 0) { return (float*)&ptr[12*4*N+offset]; }; // x coordinate of geometry normal + __forceinline float* Ng_y(size_t offset = 0) { return (float*)&ptr[13*4*N+offset]; }; // y coordinate of geometry normal + __forceinline float* Ng_z(size_t offset = 0) { return (float*)&ptr[14*4*N+offset]; }; // z coordinate of geometry normal + + __forceinline float* u(size_t offset = 0) { return (float*)&ptr[15*4*N+offset]; }; // barycentric u coordinate of hit + __forceinline float* v(size_t offset = 0) { return (float*)&ptr[16*4*N+offset]; }; // barycentric v coordinate of hit + + __forceinline unsigned int* primID(size_t offset = 0) { return (unsigned int*)&ptr[17*4*N+offset]; }; // primitive ID + __forceinline unsigned int* geomID(size_t offset = 0) { return (unsigned int*)&ptr[18*4*N+offset]; }; // geometry ID + __forceinline unsigned int* instID(size_t level, size_t offset = 0) { return (unsigned int*)&ptr[19*4*N+level*4*N+offset]; }; // instance ID + + __forceinline Ray getRayByOffset(size_t offset) + { + Ray ray; + ray.org.x = org_x(offset)[0]; + ray.org.y = org_y(offset)[0]; + ray.org.z = org_z(offset)[0]; + ray.tnear() = tnear(offset)[0]; + ray.dir.x = dir_x(offset)[0]; + ray.dir.y = dir_y(offset)[0]; + ray.dir.z = dir_z(offset)[0]; + ray.time() = time(offset)[0]; + ray.tfar = tfar(offset)[0]; + ray.mask = mask(offset)[0]; + ray.id = id(offset)[0]; + ray.flags = flags(offset)[0]; + return ray; + } + + template + __forceinline RayK getRayByOffset(size_t offset) + { + RayK ray; + ray.org.x = vfloat::loadu(org_x(offset)); + ray.org.y = vfloat::loadu(org_y(offset)); + ray.org.z = vfloat::loadu(org_z(offset)); + ray.tnear = vfloat::loadu(tnear(offset)); + ray.dir.x = vfloat::loadu(dir_x(offset)); + ray.dir.y = vfloat::loadu(dir_y(offset)); + ray.dir.z = vfloat::loadu(dir_z(offset)); + ray.time = vfloat::loadu(time(offset)); + ray.tfar = vfloat::loadu(tfar(offset)); + ray.mask = vint::loadu(mask(offset)); + ray.id = vint::loadu(id(offset)); + ray.flags = vint::loadu(flags(offset)); + return ray; + } + + template + __forceinline RayK getRayByOffset(const vbool& valid, size_t offset) + { + RayK ray; + ray.org.x = vfloat::loadu(valid, org_x(offset)); + ray.org.y = vfloat::loadu(valid, org_y(offset)); + ray.org.z = vfloat::loadu(valid, org_z(offset)); + ray.tnear() = vfloat::loadu(valid, tnear(offset)); + ray.dir.x = vfloat::loadu(valid, dir_x(offset)); + ray.dir.y = vfloat::loadu(valid, dir_y(offset)); + ray.dir.z = vfloat::loadu(valid, dir_z(offset)); + ray.time() = vfloat::loadu(valid, time(offset)); + ray.tfar = vfloat::loadu(valid, tfar(offset)); + +#if !defined(__AVX__) + /* SSE: some ray members must be loaded with scalar instructions to ensure that we don't cause memory faults, + because the SSE masked loads always access the entire vector */ + if (unlikely(!all(valid))) + { + ray.mask = zero; + ray.id = zero; + ray.flags = zero; + + for (size_t k = 0; k < K; k++) + { + if (likely(valid[k])) + { + ray.mask[k] = mask(offset)[k]; + ray.id[k] = id(offset)[k]; + ray.flags[k] = flags(offset)[k]; + } + } + } + else +#endif + { + ray.mask = vint::loadu(valid, mask(offset)); + ray.id = vint::loadu(valid, id(offset)); + ray.flags = vint::loadu(valid, flags(offset)); + } + + return ray; + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, size_t offset, const RayHitK& ray) + { + /* + * valid_i: stores which of the input rays exist (do not access nonexistent rays!) + * valid: stores which of the rays actually hit something. + */ + vbool valid = valid_i; + valid &= (ray.geomID != RTC_INVALID_GEOMETRY_ID); + + if (likely(any(valid))) + { + vfloat::storeu(valid, tfar(offset), ray.tfar); + vfloat::storeu(valid, Ng_x(offset), ray.Ng.x); + vfloat::storeu(valid, Ng_y(offset), ray.Ng.y); + vfloat::storeu(valid, Ng_z(offset), ray.Ng.z); + vfloat::storeu(valid, u(offset), ray.u); + vfloat::storeu(valid, v(offset), ray.v); + +#if !defined(__AVX__) + /* SSE: some ray members must be stored with scalar instructions to ensure that we don't cause memory faults, + because the SSE masked stores always access the entire vector */ + if (unlikely(!all(valid_i))) + { + for (size_t k = 0; k < K; k++) + { + if (likely(valid[k])) + { + primID(offset)[k] = ray.primID[k]; + geomID(offset)[k] = ray.geomID[k]; + + instID(0, offset)[k] = ray.instID[0][k]; +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && ray.instID[l-1][k] != RTC_INVALID_GEOMETRY_ID; ++l) + instID(l, offset)[k] = ray.instID[l][k]; +#endif + } + } + } + else +#endif + { + vuint::storeu(valid, primID(offset), ray.primID); + vuint::storeu(valid, geomID(offset), ray.geomID); + + vuint::storeu(valid, instID(0, offset), ray.instID[0]); +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && any(valid & (ray.instID[l-1] != RTC_INVALID_GEOMETRY_ID)); ++l) + vuint::storeu(valid, instID(l, offset), ray.instID[l]); +#endif + } + } + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, size_t offset, const RayK& ray) + { + vbool valid = valid_i; + valid &= (ray.tfar < 0.0f); + + if (likely(any(valid))) + vfloat::storeu(valid, tfar(offset), ray.tfar); + } + + __forceinline size_t getOctantByOffset(size_t offset) + { + const float dx = dir_x(offset)[0]; + const float dy = dir_y(offset)[0]; + const float dz = dir_z(offset)[0]; + const size_t octantID = (dx < 0.0f ? 1 : 0) + (dy < 0.0f ? 2 : 0) + (dz < 0.0f ? 4 : 0); + return octantID; + } + + __forceinline bool isValidByOffset(size_t offset) + { + const float nnear = tnear(offset)[0]; + const float ffar = tfar(offset)[0]; + return nnear <= ffar; + } + + template + __forceinline RayK getRayByOffset(const vbool& valid, const vint& offset) + { + RayK ray; + +#if defined(__AVX2__) + ray.org.x = vfloat::template gather<1>(valid, org_x(), offset); + ray.org.y = vfloat::template gather<1>(valid, org_y(), offset); + ray.org.z = vfloat::template gather<1>(valid, org_z(), offset); + ray.tnear() = vfloat::template gather<1>(valid, tnear(), offset); + ray.dir.x = vfloat::template gather<1>(valid, dir_x(), offset); + ray.dir.y = vfloat::template gather<1>(valid, dir_y(), offset); + ray.dir.z = vfloat::template gather<1>(valid, dir_z(), offset); + ray.time() = vfloat::template gather<1>(valid, time(), offset); + ray.tfar = vfloat::template gather<1>(valid, tfar(), offset); + ray.mask = vint::template gather<1>(valid, mask(), offset); + ray.id = vint::template gather<1>(valid, id(), offset); + ray.flags = vint::template gather<1>(valid, flags(), offset); +#else + ray.org = zero; + ray.tnear() = zero; + ray.dir = zero; + ray.time() = zero; + ray.tfar = zero; + ray.mask = zero; + ray.id = zero; + ray.flags = zero; + + for (size_t k = 0; k < K; k++) + { + if (likely(valid[k])) + { + const size_t ofs = offset[k]; + + ray.org.x[k] = *org_x(ofs); + ray.org.y[k] = *org_y(ofs); + ray.org.z[k] = *org_z(ofs); + ray.tnear()[k] = *tnear(ofs); + ray.dir.x[k] = *dir_x(ofs); + ray.dir.y[k] = *dir_y(ofs); + ray.dir.z[k] = *dir_z(ofs); + ray.time()[k] = *time(ofs); + ray.tfar[k] = *tfar(ofs); + ray.mask[k] = *mask(ofs); + ray.id[k] = *id(ofs); + ray.flags[k] = *flags(ofs); + } + } +#endif + + return ray; + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, const vint& offset, const RayHitK& ray) + { + vbool valid = valid_i; + valid &= (ray.geomID != RTC_INVALID_GEOMETRY_ID); + + if (likely(any(valid))) + { +#if defined(__AVX512F__) + vfloat::template scatter<1>(valid, tfar(), offset, ray.tfar); + vfloat::template scatter<1>(valid, Ng_x(), offset, ray.Ng.x); + vfloat::template scatter<1>(valid, Ng_y(), offset, ray.Ng.y); + vfloat::template scatter<1>(valid, Ng_z(), offset, ray.Ng.z); + vfloat::template scatter<1>(valid, u(), offset, ray.u); + vfloat::template scatter<1>(valid, v(), offset, ray.v); + vuint::template scatter<1>(valid, primID(), offset, ray.primID); + vuint::template scatter<1>(valid, geomID(), offset, ray.geomID); + + vuint::template scatter<1>(valid, instID(0), offset, ray.instID[0]); +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && any(valid & (ray.instID[l-1] != RTC_INVALID_GEOMETRY_ID)); ++l) + vuint::template scatter<1>(valid, instID(l), offset, ray.instID[l]); +#endif +#else + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + const size_t ofs = offset[k]; + + *tfar(ofs) = ray.tfar[k]; + + *Ng_x(ofs) = ray.Ng.x[k]; + *Ng_y(ofs) = ray.Ng.y[k]; + *Ng_z(ofs) = ray.Ng.z[k]; + *u(ofs) = ray.u[k]; + *v(ofs) = ray.v[k]; + *primID(ofs) = ray.primID[k]; + *geomID(ofs) = ray.geomID[k]; + + *instID(0, ofs) = ray.instID[0][k]; +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && ray.instID[l-1][k] != RTC_INVALID_GEOMETRY_ID; ++l) + *instID(l, ofs) = ray.instID[l][k]; +#endif + } +#endif + } + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, const vint& offset, const RayK& ray) + { + vbool valid = valid_i; + valid &= (ray.tfar < 0.0f); + + if (likely(any(valid))) + { +#if defined(__AVX512F__) + vfloat::template scatter<1>(valid, tfar(), offset, ray.tfar); +#else + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + const size_t ofs = offset[k]; + + *tfar(ofs) = ray.tfar[k]; + } +#endif + } + } + + char* __restrict__ ptr; + size_t N; + }; + + template + struct StackRayStreamSOA : public RayStreamSOA + { + __forceinline StackRayStreamSOA(size_t K) + : RayStreamSOA(data, K) { assert(K <= MAX_K); } + + char data[MAX_K / 4 * sizeof(RayHit4)]; + }; + + + struct RayStreamSOP + { + template + __forceinline void init(T& t) + { + org_x = (float*)&t.org.x; + org_y = (float*)&t.org.y; + org_z = (float*)&t.org.z; + tnear = (float*)&t.tnear; + dir_x = (float*)&t.dir.x; + dir_y = (float*)&t.dir.y; + dir_z = (float*)&t.dir.z; + time = (float*)&t.time; + tfar = (float*)&t.tfar; + mask = (unsigned int*)&t.mask; + id = (unsigned int*)&t.id; + flags = (unsigned int*)&t.flags; + + Ng_x = (float*)&t.Ng.x; + Ng_y = (float*)&t.Ng.y; + Ng_z = (float*)&t.Ng.z; + u = (float*)&t.u; + v = (float*)&t.v; + primID = (unsigned int*)&t.primID; + geomID = (unsigned int*)&t.geomID; + + for (unsigned l = 0; l < RTC_MAX_INSTANCE_LEVEL_COUNT; ++l) + instID[l] = (unsigned int*)&t.instID[l]; + } + + __forceinline Ray getRayByOffset(size_t offset) + { + Ray ray; + ray.org.x = *(float* __restrict__)((char*)org_x + offset); + ray.org.y = *(float* __restrict__)((char*)org_y + offset); + ray.org.z = *(float* __restrict__)((char*)org_z + offset); + ray.dir.x = *(float* __restrict__)((char*)dir_x + offset); + ray.dir.y = *(float* __restrict__)((char*)dir_y + offset); + ray.dir.z = *(float* __restrict__)((char*)dir_z + offset); + ray.tfar = *(float* __restrict__)((char*)tfar + offset); + ray.tnear() = tnear ? *(float* __restrict__)((char*)tnear + offset) : 0.0f; + ray.time() = time ? *(float* __restrict__)((char*)time + offset) : 0.0f; + ray.mask = mask ? *(unsigned int* __restrict__)((char*)mask + offset) : -1; + ray.id = id ? *(unsigned int* __restrict__)((char*)id + offset) : -1; + ray.flags = flags ? *(unsigned int* __restrict__)((char*)flags + offset) : -1; + return ray; + } + + template + __forceinline RayK getRayByOffset(const vbool& valid, size_t offset) + { + RayK ray; + ray.org.x = vfloat::loadu(valid, (float* __restrict__)((char*)org_x + offset)); + ray.org.y = vfloat::loadu(valid, (float* __restrict__)((char*)org_y + offset)); + ray.org.z = vfloat::loadu(valid, (float* __restrict__)((char*)org_z + offset)); + ray.dir.x = vfloat::loadu(valid, (float* __restrict__)((char*)dir_x + offset)); + ray.dir.y = vfloat::loadu(valid, (float* __restrict__)((char*)dir_y + offset)); + ray.dir.z = vfloat::loadu(valid, (float* __restrict__)((char*)dir_z + offset)); + ray.tfar = vfloat::loadu(valid, (float* __restrict__)((char*)tfar + offset)); + ray.tnear() = tnear ? vfloat::loadu(valid, (float* __restrict__)((char*)tnear + offset)) : 0.0f; + ray.time() = time ? vfloat::loadu(valid, (float* __restrict__)((char*)time + offset)) : 0.0f; + ray.mask = mask ? vint::loadu(valid, (const void* __restrict__)((char*)mask + offset)) : -1; + ray.id = id ? vint::loadu(valid, (const void* __restrict__)((char*)id + offset)) : -1; + ray.flags = flags ? vint::loadu(valid, (const void* __restrict__)((char*)flags + offset)) : -1; + return ray; + } + + template + __forceinline Vec3vf getDirByOffset(const vbool& valid, size_t offset) + { + Vec3vf dir; + dir.x = vfloat::loadu(valid, (float* __restrict__)((char*)dir_x + offset)); + dir.y = vfloat::loadu(valid, (float* __restrict__)((char*)dir_y + offset)); + dir.z = vfloat::loadu(valid, (float* __restrict__)((char*)dir_z + offset)); + return dir; + } + + __forceinline void setHitByOffset(size_t offset, const RayHit& ray) + { + if (ray.geomID != RTC_INVALID_GEOMETRY_ID) + { + *(float* __restrict__)((char*)tfar + offset) = ray.tfar; + + if (likely(Ng_x)) *(float* __restrict__)((char*)Ng_x + offset) = ray.Ng.x; + if (likely(Ng_y)) *(float* __restrict__)((char*)Ng_y + offset) = ray.Ng.y; + if (likely(Ng_z)) *(float* __restrict__)((char*)Ng_z + offset) = ray.Ng.z; + *(float* __restrict__)((char*)u + offset) = ray.u; + *(float* __restrict__)((char*)v + offset) = ray.v; + *(unsigned int* __restrict__)((char*)geomID + offset) = ray.geomID; + *(unsigned int* __restrict__)((char*)primID + offset) = ray.primID; + + if (likely(instID[0])) { + *(unsigned int* __restrict__)((char*)instID[0] + offset) = ray.instID[0]; +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && ray.instID[l-1] != RTC_INVALID_GEOMETRY_ID; ++l) + *(unsigned int* __restrict__)((char*)instID[l] + offset) = ray.instID[l]; +#endif + } + } + } + + __forceinline void setHitByOffset(size_t offset, const Ray& ray) + { + *(float* __restrict__)((char*)tfar + offset) = ray.tfar; + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, size_t offset, const RayHitK& ray) + { + vbool valid = valid_i; + valid &= (ray.geomID != RTC_INVALID_GEOMETRY_ID); + + if (likely(any(valid))) + { + vfloat::storeu(valid, (float* __restrict__)((char*)tfar + offset), ray.tfar); + + if (likely(Ng_x)) vfloat::storeu(valid, (float* __restrict__)((char*)Ng_x + offset), ray.Ng.x); + if (likely(Ng_y)) vfloat::storeu(valid, (float* __restrict__)((char*)Ng_y + offset), ray.Ng.y); + if (likely(Ng_z)) vfloat::storeu(valid, (float* __restrict__)((char*)Ng_z + offset), ray.Ng.z); + vfloat::storeu(valid, (float* __restrict__)((char*)u + offset), ray.u); + vfloat::storeu(valid, (float* __restrict__)((char*)v + offset), ray.v); + vuint::storeu(valid, (unsigned int* __restrict__)((char*)primID + offset), ray.primID); + vuint::storeu(valid, (unsigned int* __restrict__)((char*)geomID + offset), ray.geomID); + + if (likely(instID[0])) { + vuint::storeu(valid, (unsigned int* __restrict__)((char*)instID[0] + offset), ray.instID[0]); +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && any(valid & (ray.instID[l-1] != RTC_INVALID_GEOMETRY_ID)); ++l) + vuint::storeu(valid, (unsigned int* __restrict__)((char*)instID[l] + offset), ray.instID[l]); +#endif + } + } + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, size_t offset, const RayK& ray) + { + vbool valid = valid_i; + valid &= (ray.tfar < 0.0f); + + if (likely(any(valid))) + vfloat::storeu(valid, (float* __restrict__)((char*)tfar + offset), ray.tfar); + } + + __forceinline size_t getOctantByOffset(size_t offset) + { + const float dx = *(float* __restrict__)((char*)dir_x + offset); + const float dy = *(float* __restrict__)((char*)dir_y + offset); + const float dz = *(float* __restrict__)((char*)dir_z + offset); + const size_t octantID = (dx < 0.0f ? 1 : 0) + (dy < 0.0f ? 2 : 0) + (dz < 0.0f ? 4 : 0); + return octantID; + } + + __forceinline bool isValidByOffset(size_t offset) + { + const float nnear = tnear ? *(float* __restrict__)((char*)tnear + offset) : 0.0f; + const float ffar = *(float* __restrict__)((char*)tfar + offset); + return nnear <= ffar; + } + + template + __forceinline vbool isValidByOffset(const vbool& valid, size_t offset) + { + const vfloat nnear = tnear ? vfloat::loadu(valid, (float* __restrict__)((char*)tnear + offset)) : 0.0f; + const vfloat ffar = vfloat::loadu(valid, (float* __restrict__)((char*)tfar + offset)); + return nnear <= ffar; + } + + template + __forceinline RayK getRayByOffset(const vbool& valid, const vint& offset) + { + RayK ray; + +#if defined(__AVX2__) + ray.org.x = vfloat::template gather<1>(valid, org_x, offset); + ray.org.y = vfloat::template gather<1>(valid, org_y, offset); + ray.org.z = vfloat::template gather<1>(valid, org_z, offset); + ray.dir.x = vfloat::template gather<1>(valid, dir_x, offset); + ray.dir.y = vfloat::template gather<1>(valid, dir_y, offset); + ray.dir.z = vfloat::template gather<1>(valid, dir_z, offset); + ray.tfar = vfloat::template gather<1>(valid, tfar, offset); + ray.tnear() = tnear ? vfloat::template gather<1>(valid, tnear, offset) : vfloat(zero); + ray.time() = time ? vfloat::template gather<1>(valid, time, offset) : vfloat(zero); + ray.mask = mask ? vint::template gather<1>(valid, (int*)mask, offset) : vint(-1); + ray.id = id ? vint::template gather<1>(valid, (int*)id, offset) : vint(-1); + ray.flags = flags ? vint::template gather<1>(valid, (int*)flags, offset) : vint(-1); +#else + ray.org = zero; + ray.tnear() = zero; + ray.dir = zero; + ray.tfar = zero; + ray.time() = zero; + ray.mask = zero; + ray.id = zero; + ray.flags = zero; + + for (size_t k = 0; k < K; k++) + { + if (likely(valid[k])) + { + const size_t ofs = offset[k]; + + ray.org.x[k] = *(float* __restrict__)((char*)org_x + ofs); + ray.org.y[k] = *(float* __restrict__)((char*)org_y + ofs); + ray.org.z[k] = *(float* __restrict__)((char*)org_z + ofs); + ray.dir.x[k] = *(float* __restrict__)((char*)dir_x + ofs); + ray.dir.y[k] = *(float* __restrict__)((char*)dir_y + ofs); + ray.dir.z[k] = *(float* __restrict__)((char*)dir_z + ofs); + ray.tfar[k] = *(float* __restrict__)((char*)tfar + ofs); + ray.tnear()[k] = tnear ? *(float* __restrict__)((char*)tnear + ofs) : 0.0f; + ray.time()[k] = time ? *(float* __restrict__)((char*)time + ofs) : 0.0f; + ray.mask[k] = mask ? *(int* __restrict__)((char*)mask + ofs) : -1; + ray.id[k] = id ? *(int* __restrict__)((char*)id + ofs) : -1; + ray.flags[k] = flags ? *(int* __restrict__)((char*)flags + ofs) : -1; + } + } +#endif + + return ray; + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, const vint& offset, const RayHitK& ray) + { + vbool valid = valid_i; + valid &= (ray.geomID != RTC_INVALID_GEOMETRY_ID); + + if (likely(any(valid))) + { +#if defined(__AVX512F__) + vfloat::template scatter<1>(valid, tfar, offset, ray.tfar); + + if (likely(Ng_x)) vfloat::template scatter<1>(valid, Ng_x, offset, ray.Ng.x); + if (likely(Ng_y)) vfloat::template scatter<1>(valid, Ng_y, offset, ray.Ng.y); + if (likely(Ng_z)) vfloat::template scatter<1>(valid, Ng_z, offset, ray.Ng.z); + vfloat::template scatter<1>(valid, u, offset, ray.u); + vfloat::template scatter<1>(valid, v, offset, ray.v); + vuint::template scatter<1>(valid, (unsigned int*)geomID, offset, ray.geomID); + vuint::template scatter<1>(valid, (unsigned int*)primID, offset, ray.primID); + + if (likely(instID[0])) { + vuint::template scatter<1>(valid, (unsigned int*)instID[0], offset, ray.instID[0]); +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && any(valid & (ray.instID[l-1] != RTC_INVALID_GEOMETRY_ID)); ++l) + vuint::template scatter<1>(valid, (unsigned int*)instID[l], offset, ray.instID[l]); +#endif + } +#else + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + const size_t ofs = offset[k]; + + *(float* __restrict__)((char*)tfar + ofs) = ray.tfar[k]; + + if (likely(Ng_x)) *(float* __restrict__)((char*)Ng_x + ofs) = ray.Ng.x[k]; + if (likely(Ng_y)) *(float* __restrict__)((char*)Ng_y + ofs) = ray.Ng.y[k]; + if (likely(Ng_z)) *(float* __restrict__)((char*)Ng_z + ofs) = ray.Ng.z[k]; + *(float* __restrict__)((char*)u + ofs) = ray.u[k]; + *(float* __restrict__)((char*)v + ofs) = ray.v[k]; + *(unsigned int* __restrict__)((char*)primID + ofs) = ray.primID[k]; + *(unsigned int* __restrict__)((char*)geomID + ofs) = ray.geomID[k]; + + if (likely(instID[0])) { + *(unsigned int* __restrict__)((char*)instID[0] + ofs) = ray.instID[0][k]; +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && ray.instID[l-1][k] != RTC_INVALID_GEOMETRY_ID; ++l) + *(unsigned int* __restrict__)((char*)instID[l] + ofs) = ray.instID[l][k]; +#endif + } + } +#endif + } + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, const vint& offset, const RayK& ray) + { + vbool valid = valid_i; + valid &= (ray.tfar < 0.0f); + + if (likely(any(valid))) + { +#if defined(__AVX512F__) + vfloat::template scatter<1>(valid, tfar, offset, ray.tfar); +#else + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + const size_t ofs = offset[k]; + + *(float* __restrict__)((char*)tfar + ofs) = ray.tfar[k]; + } +#endif + } + } + + /* ray data */ + float* __restrict__ org_x; // x coordinate of ray origin + float* __restrict__ org_y; // y coordinate of ray origin + float* __restrict__ org_z; // z coordinate of ray origin + float* __restrict__ tnear; // start of ray segment (optional) + + float* __restrict__ dir_x; // x coordinate of ray direction + float* __restrict__ dir_y; // y coordinate of ray direction + float* __restrict__ dir_z; // z coordinate of ray direction + float* __restrict__ time; // time of this ray for motion blur (optional) + + float* __restrict__ tfar; // end of ray segment (set to hit distance) + unsigned int* __restrict__ mask; // used to mask out objects during traversal (optional) + unsigned int* __restrict__ id; // ray ID + unsigned int* __restrict__ flags; // ray flags + + /* hit data */ + float* __restrict__ Ng_x; // x coordinate of geometry normal (optional) + float* __restrict__ Ng_y; // y coordinate of geometry normal (optional) + float* __restrict__ Ng_z; // z coordinate of geometry normal (optional) + + float* __restrict__ u; // barycentric u coordinate of hit + float* __restrict__ v; // barycentric v coordinate of hit + + unsigned int* __restrict__ primID; // primitive ID + unsigned int* __restrict__ geomID; // geometry ID + unsigned int* __restrict__ instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; // instance ID (optional) + }; + + + struct RayStreamAOS + { + __forceinline RayStreamAOS(void* rays) + : ptr((Ray*)rays) {} + + __forceinline Ray& getRayByOffset(size_t offset) + { + return *(Ray*)((char*)ptr + offset); + } + + template + __forceinline RayK getRayByOffset(const vint& offset); + + template + __forceinline RayK getRayByOffset(const vbool& valid, const vint& offset) + { + const vint valid_offset = select(valid, offset, vintx(zero)); + return getRayByOffset(valid_offset); + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, const vint& offset, const RayHitK& ray) + { + vbool valid = valid_i; + valid &= (ray.geomID != RTC_INVALID_GEOMETRY_ID); + + if (likely(any(valid))) + { +#if defined(__AVX512F__) + vfloat::template scatter<1>(valid, &ptr->tfar, offset, ray.tfar); + vfloat::template scatter<1>(valid, &((RayHit*)ptr)->Ng.x, offset, ray.Ng.x); + vfloat::template scatter<1>(valid, &((RayHit*)ptr)->Ng.y, offset, ray.Ng.y); + vfloat::template scatter<1>(valid, &((RayHit*)ptr)->Ng.z, offset, ray.Ng.z); + vfloat::template scatter<1>(valid, &((RayHit*)ptr)->u, offset, ray.u); + vfloat::template scatter<1>(valid, &((RayHit*)ptr)->v, offset, ray.v); + vuint::template scatter<1>(valid, (unsigned int*)&((RayHit*)ptr)->primID, offset, ray.primID); + vuint::template scatter<1>(valid, (unsigned int*)&((RayHit*)ptr)->geomID, offset, ray.geomID); + + vuint::template scatter<1>(valid, (unsigned int*)&((RayHit*)ptr)->instID[0], offset, ray.instID[0]); +#if (RTC_MAX_INSTANCE_LEVEL_COUNT > 1) + for (unsigned l = 1; l < RTC_MAX_INSTANCE_LEVEL_COUNT && any(valid & (ray.instID[l-1] != RTC_INVALID_GEOMETRY_ID)); ++l) + vuint::template scatter<1>(valid, (unsigned int*)&((RayHit*)ptr)->instID[l], offset, ray.instID[l]); +#endif +#else + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + RayHit* __restrict__ ray_k = (RayHit*)((char*)ptr + offset[k]); + ray_k->tfar = ray.tfar[k]; + ray_k->Ng.x = ray.Ng.x[k]; + ray_k->Ng.y = ray.Ng.y[k]; + ray_k->Ng.z = ray.Ng.z[k]; + ray_k->u = ray.u[k]; + ray_k->v = ray.v[k]; + ray_k->primID = ray.primID[k]; + ray_k->geomID = ray.geomID[k]; + + instance_id_stack::copy(ray.instID, ray_k->instID, k); + } +#endif + } + } + + template + __forceinline void setHitByOffset(const vbool& valid_i, const vint& offset, const RayK& ray) + { + vbool valid = valid_i; + valid &= (ray.tfar < 0.0f); + + if (likely(any(valid))) + { +#if defined(__AVX512F__) + vfloat::template scatter<1>(valid, &ptr->tfar, offset, ray.tfar); +#else + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + Ray* __restrict__ ray_k = (Ray*)((char*)ptr + offset[k]); + ray_k->tfar = ray.tfar[k]; + } +#endif + } + } + + Ray* __restrict__ ptr; + }; + + template<> + __forceinline Ray4 RayStreamAOS::getRayByOffset(const vint4& offset) + { + Ray4 ray; + + /* load and transpose: org.x, org.y, org.z, tnear */ + const vfloat4 a0 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[0]))->org); + const vfloat4 a1 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[1]))->org); + const vfloat4 a2 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[2]))->org); + const vfloat4 a3 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[3]))->org); + + transpose(a0,a1,a2,a3, ray.org.x, ray.org.y, ray.org.z, ray.tnear()); + + /* load and transpose: dir.x, dir.y, dir.z, time */ + const vfloat4 b0 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[0]))->dir); + const vfloat4 b1 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[1]))->dir); + const vfloat4 b2 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[2]))->dir); + const vfloat4 b3 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[3]))->dir); + + transpose(b0,b1,b2,b3, ray.dir.x, ray.dir.y, ray.dir.z, ray.time()); + + /* load and transpose: tfar, mask, id, flags */ + const vfloat4 c0 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[0]))->tfar); + const vfloat4 c1 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[1]))->tfar); + const vfloat4 c2 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[2]))->tfar); + const vfloat4 c3 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[3]))->tfar); + + vfloat4 maskf, idf, flagsf; + transpose(c0,c1,c2,c3, ray.tfar, maskf, idf, flagsf); + ray.mask = asInt(maskf); + ray.id = asInt(idf); + ray.flags = asInt(flagsf); + + return ray; + } + +#if defined(__AVX__) + template<> + __forceinline Ray8 RayStreamAOS::getRayByOffset(const vint8& offset) + { + Ray8 ray; + + /* load and transpose: org.x, org.y, org.z, tnear, dir.x, dir.y, dir.z, time */ + const vfloat8 ab0 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[0]))->org); + const vfloat8 ab1 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[1]))->org); + const vfloat8 ab2 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[2]))->org); + const vfloat8 ab3 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[3]))->org); + const vfloat8 ab4 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[4]))->org); + const vfloat8 ab5 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[5]))->org); + const vfloat8 ab6 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[6]))->org); + const vfloat8 ab7 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[7]))->org); + + transpose(ab0,ab1,ab2,ab3,ab4,ab5,ab6,ab7, ray.org.x, ray.org.y, ray.org.z, ray.tnear(), ray.dir.x, ray.dir.y, ray.dir.z, ray.time()); + + /* load and transpose: tfar, mask, id, flags */ + const vfloat4 c0 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[0]))->tfar); + const vfloat4 c1 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[1]))->tfar); + const vfloat4 c2 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[2]))->tfar); + const vfloat4 c3 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[3]))->tfar); + const vfloat4 c4 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[4]))->tfar); + const vfloat4 c5 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[5]))->tfar); + const vfloat4 c6 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[6]))->tfar); + const vfloat4 c7 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[7]))->tfar); + + vfloat8 maskf, idf, flagsf; + transpose(c0,c1,c2,c3,c4,c5,c6,c7, ray.tfar, maskf, idf, flagsf); + ray.mask = asInt(maskf); + ray.id = asInt(idf); + ray.flags = asInt(flagsf); + + return ray; + } +#endif + +#if defined(__AVX512F__) + template<> + __forceinline Ray16 RayStreamAOS::getRayByOffset(const vint16& offset) + { + Ray16 ray; + + /* load and transpose: org.x, org.y, org.z, tnear, dir.x, dir.y, dir.z, time */ + const vfloat8 ab0 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 0]))->org); + const vfloat8 ab1 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 1]))->org); + const vfloat8 ab2 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 2]))->org); + const vfloat8 ab3 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 3]))->org); + const vfloat8 ab4 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 4]))->org); + const vfloat8 ab5 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 5]))->org); + const vfloat8 ab6 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 6]))->org); + const vfloat8 ab7 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 7]))->org); + const vfloat8 ab8 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 8]))->org); + const vfloat8 ab9 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[ 9]))->org); + const vfloat8 ab10 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[10]))->org); + const vfloat8 ab11 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[11]))->org); + const vfloat8 ab12 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[12]))->org); + const vfloat8 ab13 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[13]))->org); + const vfloat8 ab14 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[14]))->org); + const vfloat8 ab15 = vfloat8::loadu(&((Ray*)((char*)ptr + offset[15]))->org); + + transpose(ab0,ab1,ab2,ab3,ab4,ab5,ab6,ab7,ab8,ab9,ab10,ab11,ab12,ab13,ab14,ab15, + ray.org.x, ray.org.y, ray.org.z, ray.tnear(), ray.dir.x, ray.dir.y, ray.dir.z, ray.time()); + + /* load and transpose: tfar, mask, id, flags */ + const vfloat4 c0 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 0]))->tfar); + const vfloat4 c1 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 1]))->tfar); + const vfloat4 c2 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 2]))->tfar); + const vfloat4 c3 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 3]))->tfar); + const vfloat4 c4 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 4]))->tfar); + const vfloat4 c5 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 5]))->tfar); + const vfloat4 c6 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 6]))->tfar); + const vfloat4 c7 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 7]))->tfar); + const vfloat4 c8 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 8]))->tfar); + const vfloat4 c9 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[ 9]))->tfar); + const vfloat4 c10 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[10]))->tfar); + const vfloat4 c11 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[11]))->tfar); + const vfloat4 c12 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[12]))->tfar); + const vfloat4 c13 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[13]))->tfar); + const vfloat4 c14 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[14]))->tfar); + const vfloat4 c15 = vfloat4::loadu(&((Ray*)((char*)ptr + offset[15]))->tfar); + + vfloat16 maskf, idf, flagsf; + transpose(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15, + ray.tfar, maskf, idf, flagsf); + ray.mask = asInt(maskf); + ray.id = asInt(idf); + ray.flags = asInt(flagsf); + + return ray; + } +#endif + + + struct RayStreamAOP + { + __forceinline RayStreamAOP(void* rays) + : ptr((Ray**)rays) {} + + __forceinline Ray& getRayByIndex(size_t index) + { + return *ptr[index]; + } + + template + __forceinline RayK getRayByIndex(const vint& index); + + template + __forceinline RayK getRayByIndex(const vbool& valid, const vint& index) + { + const vint valid_index = select(valid, index, vintx(zero)); + return getRayByIndex(valid_index); + } + + template + __forceinline void setHitByIndex(const vbool& valid_i, const vint& index, const RayHitK& ray) + { + vbool valid = valid_i; + valid &= (ray.geomID != RTC_INVALID_GEOMETRY_ID); + + if (likely(any(valid))) + { + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + RayHit* __restrict__ ray_k = (RayHit*)ptr[index[k]]; + + ray_k->tfar = ray.tfar[k]; + ray_k->Ng.x = ray.Ng.x[k]; + ray_k->Ng.y = ray.Ng.y[k]; + ray_k->Ng.z = ray.Ng.z[k]; + ray_k->u = ray.u[k]; + ray_k->v = ray.v[k]; + ray_k->primID = ray.primID[k]; + ray_k->geomID = ray.geomID[k]; + instance_id_stack::copy(ray.instID, ray_k->instID, k); + } + } + } + + template + __forceinline void setHitByIndex(const vbool& valid_i, const vint& index, const RayK& ray) + { + vbool valid = valid_i; + valid &= (ray.tfar < 0.0f); + + if (likely(any(valid))) + { + size_t valid_bits = movemask(valid); + while (valid_bits != 0) + { + const size_t k = bscf(valid_bits); + Ray* __restrict__ ray_k = ptr[index[k]]; + + ray_k->tfar = ray.tfar[k]; + } + } + } + + Ray** __restrict__ ptr; + }; + + template<> + __forceinline Ray4 RayStreamAOP::getRayByIndex(const vint4& index) + { + Ray4 ray; + + /* load and transpose: org.x, org.y, org.z, tnear */ + const vfloat4 a0 = vfloat4::loadu(&ptr[index[0]]->org); + const vfloat4 a1 = vfloat4::loadu(&ptr[index[1]]->org); + const vfloat4 a2 = vfloat4::loadu(&ptr[index[2]]->org); + const vfloat4 a3 = vfloat4::loadu(&ptr[index[3]]->org); + + transpose(a0,a1,a2,a3, ray.org.x, ray.org.y, ray.org.z, ray.tnear()); + + /* load and transpose: dir.x, dir.y, dir.z, time */ + const vfloat4 b0 = vfloat4::loadu(&ptr[index[0]]->dir); + const vfloat4 b1 = vfloat4::loadu(&ptr[index[1]]->dir); + const vfloat4 b2 = vfloat4::loadu(&ptr[index[2]]->dir); + const vfloat4 b3 = vfloat4::loadu(&ptr[index[3]]->dir); + + transpose(b0,b1,b2,b3, ray.dir.x, ray.dir.y, ray.dir.z, ray.time()); + + /* load and transpose: tfar, mask, id, flags */ + const vfloat4 c0 = vfloat4::loadu(&ptr[index[0]]->tfar); + const vfloat4 c1 = vfloat4::loadu(&ptr[index[1]]->tfar); + const vfloat4 c2 = vfloat4::loadu(&ptr[index[2]]->tfar); + const vfloat4 c3 = vfloat4::loadu(&ptr[index[3]]->tfar); + + vfloat4 maskf, idf, flagsf; + transpose(c0,c1,c2,c3, ray.tfar, maskf, idf, flagsf); + ray.mask = asInt(maskf); + ray.id = asInt(idf); + ray.flags = asInt(flagsf); + + return ray; + } + +#if defined(__AVX__) + template<> + __forceinline Ray8 RayStreamAOP::getRayByIndex(const vint8& index) + { + Ray8 ray; + + /* load and transpose: org.x, org.y, org.z, tnear, dir.x, dir.y, dir.z, time */ + const vfloat8 ab0 = vfloat8::loadu(&ptr[index[0]]->org); + const vfloat8 ab1 = vfloat8::loadu(&ptr[index[1]]->org); + const vfloat8 ab2 = vfloat8::loadu(&ptr[index[2]]->org); + const vfloat8 ab3 = vfloat8::loadu(&ptr[index[3]]->org); + const vfloat8 ab4 = vfloat8::loadu(&ptr[index[4]]->org); + const vfloat8 ab5 = vfloat8::loadu(&ptr[index[5]]->org); + const vfloat8 ab6 = vfloat8::loadu(&ptr[index[6]]->org); + const vfloat8 ab7 = vfloat8::loadu(&ptr[index[7]]->org); + + transpose(ab0,ab1,ab2,ab3,ab4,ab5,ab6,ab7, ray.org.x, ray.org.y, ray.org.z, ray.tnear(), ray.dir.x, ray.dir.y, ray.dir.z, ray.time()); + + /* load and transpose: tfar, mask, id, flags */ + const vfloat4 c0 = vfloat4::loadu(&ptr[index[0]]->tfar); + const vfloat4 c1 = vfloat4::loadu(&ptr[index[1]]->tfar); + const vfloat4 c2 = vfloat4::loadu(&ptr[index[2]]->tfar); + const vfloat4 c3 = vfloat4::loadu(&ptr[index[3]]->tfar); + const vfloat4 c4 = vfloat4::loadu(&ptr[index[4]]->tfar); + const vfloat4 c5 = vfloat4::loadu(&ptr[index[5]]->tfar); + const vfloat4 c6 = vfloat4::loadu(&ptr[index[6]]->tfar); + const vfloat4 c7 = vfloat4::loadu(&ptr[index[7]]->tfar); + + vfloat8 maskf, idf, flagsf; + transpose(c0,c1,c2,c3,c4,c5,c6,c7, ray.tfar, maskf, idf, flagsf); + ray.mask = asInt(maskf); + ray.id = asInt(idf); + ray.flags = asInt(flagsf); + + return ray; + } +#endif + +#if defined(__AVX512F__) + template<> + __forceinline Ray16 RayStreamAOP::getRayByIndex(const vint16& index) + { + Ray16 ray; + + /* load and transpose: org.x, org.y, org.z, tnear, dir.x, dir.y, dir.z, time */ + const vfloat8 ab0 = vfloat8::loadu(&ptr[index[0]]->org); + const vfloat8 ab1 = vfloat8::loadu(&ptr[index[1]]->org); + const vfloat8 ab2 = vfloat8::loadu(&ptr[index[2]]->org); + const vfloat8 ab3 = vfloat8::loadu(&ptr[index[3]]->org); + const vfloat8 ab4 = vfloat8::loadu(&ptr[index[4]]->org); + const vfloat8 ab5 = vfloat8::loadu(&ptr[index[5]]->org); + const vfloat8 ab6 = vfloat8::loadu(&ptr[index[6]]->org); + const vfloat8 ab7 = vfloat8::loadu(&ptr[index[7]]->org); + const vfloat8 ab8 = vfloat8::loadu(&ptr[index[8]]->org); + const vfloat8 ab9 = vfloat8::loadu(&ptr[index[9]]->org); + const vfloat8 ab10 = vfloat8::loadu(&ptr[index[10]]->org); + const vfloat8 ab11 = vfloat8::loadu(&ptr[index[11]]->org); + const vfloat8 ab12 = vfloat8::loadu(&ptr[index[12]]->org); + const vfloat8 ab13 = vfloat8::loadu(&ptr[index[13]]->org); + const vfloat8 ab14 = vfloat8::loadu(&ptr[index[14]]->org); + const vfloat8 ab15 = vfloat8::loadu(&ptr[index[15]]->org); + + transpose(ab0,ab1,ab2,ab3,ab4,ab5,ab6,ab7,ab8,ab9,ab10,ab11,ab12,ab13,ab14,ab15, + ray.org.x, ray.org.y, ray.org.z, ray.tnear(), ray.dir.x, ray.dir.y, ray.dir.z, ray.time()); + + /* load and transpose: tfar, mask, id, flags */ + const vfloat4 c0 = vfloat4::loadu(&ptr[index[0]]->tfar); + const vfloat4 c1 = vfloat4::loadu(&ptr[index[1]]->tfar); + const vfloat4 c2 = vfloat4::loadu(&ptr[index[2]]->tfar); + const vfloat4 c3 = vfloat4::loadu(&ptr[index[3]]->tfar); + const vfloat4 c4 = vfloat4::loadu(&ptr[index[4]]->tfar); + const vfloat4 c5 = vfloat4::loadu(&ptr[index[5]]->tfar); + const vfloat4 c6 = vfloat4::loadu(&ptr[index[6]]->tfar); + const vfloat4 c7 = vfloat4::loadu(&ptr[index[7]]->tfar); + const vfloat4 c8 = vfloat4::loadu(&ptr[index[8]]->tfar); + const vfloat4 c9 = vfloat4::loadu(&ptr[index[9]]->tfar); + const vfloat4 c10 = vfloat4::loadu(&ptr[index[10]]->tfar); + const vfloat4 c11 = vfloat4::loadu(&ptr[index[11]]->tfar); + const vfloat4 c12 = vfloat4::loadu(&ptr[index[12]]->tfar); + const vfloat4 c13 = vfloat4::loadu(&ptr[index[13]]->tfar); + const vfloat4 c14 = vfloat4::loadu(&ptr[index[14]]->tfar); + const vfloat4 c15 = vfloat4::loadu(&ptr[index[15]]->tfar); + + vfloat16 maskf, idf, flagsf; + transpose(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15, + ray.tfar, maskf, idf, flagsf); + + ray.mask = asInt(maskf); + ray.id = asInt(idf); + ray.flags = asInt(flagsf); + + return ray; + } +#endif +} diff --git a/thirdparty/embree/kernels/common/rtcore.cpp b/thirdparty/embree/kernels/common/rtcore.cpp new file mode 100644 index 000000000000..2cfb466a60c5 --- /dev/null +++ b/thirdparty/embree/kernels/common/rtcore.cpp @@ -0,0 +1,1760 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#define RTC_EXPORT_API + +#include "default.h" +#include "device.h" +#include "scene.h" +#include "context.h" +#include "../../include/embree3/rtcore_ray.h" +using namespace embree; + +RTC_NAMESPACE_BEGIN; + + /* mutex to make API thread safe */ + static MutexSys g_mutex; + + RTC_API RTCDevice rtcNewDevice(const char* config) + { + RTC_CATCH_BEGIN; + RTC_TRACE(rtcNewDevice); + Lock lock(g_mutex); + Device* device = new Device(config); + return (RTCDevice) device->refInc(); + RTC_CATCH_END(nullptr); + return (RTCDevice) nullptr; + } + + RTC_API void rtcRetainDevice(RTCDevice hdevice) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcRetainDevice); + RTC_VERIFY_HANDLE(hdevice); + Lock lock(g_mutex); + device->refInc(); + RTC_CATCH_END(nullptr); + } + + RTC_API void rtcReleaseDevice(RTCDevice hdevice) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcReleaseDevice); + RTC_VERIFY_HANDLE(hdevice); + Lock lock(g_mutex); + device->refDec(); + RTC_CATCH_END(nullptr); + } + + RTC_API ssize_t rtcGetDeviceProperty(RTCDevice hdevice, RTCDeviceProperty prop) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetDeviceProperty); + RTC_VERIFY_HANDLE(hdevice); + Lock lock(g_mutex); + return device->getProperty(prop); + RTC_CATCH_END(device); + return 0; + } + + RTC_API void rtcSetDeviceProperty(RTCDevice hdevice, const RTCDeviceProperty prop, ssize_t val) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetDeviceProperty); + const bool internal_prop = (size_t)prop >= 1000000 && (size_t)prop < 1000004; + if (!internal_prop) RTC_VERIFY_HANDLE(hdevice); // allow NULL device for special internal settings + Lock lock(g_mutex); + device->setProperty(prop,val); + RTC_CATCH_END(device); + } + + RTC_API RTCError rtcGetDeviceError(RTCDevice hdevice) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetDeviceError); + if (device == nullptr) return Device::getThreadErrorCode(); + else return device->getDeviceErrorCode(); + RTC_CATCH_END(device); + return RTC_ERROR_UNKNOWN; + } + + RTC_API void rtcSetDeviceErrorFunction(RTCDevice hdevice, RTCErrorFunction error, void* userPtr) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetDeviceErrorFunction); + RTC_VERIFY_HANDLE(hdevice); + device->setErrorFunction(error, userPtr); + RTC_CATCH_END(device); + } + + RTC_API void rtcSetDeviceMemoryMonitorFunction(RTCDevice hdevice, RTCMemoryMonitorFunction memoryMonitor, void* userPtr) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetDeviceMemoryMonitorFunction); + device->setMemoryMonitorFunction(memoryMonitor, userPtr); + RTC_CATCH_END(device); + } + + RTC_API RTCBuffer rtcNewBuffer(RTCDevice hdevice, size_t byteSize) + { + RTC_CATCH_BEGIN; + RTC_TRACE(rtcNewBuffer); + RTC_VERIFY_HANDLE(hdevice); + Buffer* buffer = new Buffer((Device*)hdevice, byteSize); + return (RTCBuffer)buffer->refInc(); + RTC_CATCH_END((Device*)hdevice); + return nullptr; + } + + RTC_API RTCBuffer rtcNewSharedBuffer(RTCDevice hdevice, void* ptr, size_t byteSize) + { + RTC_CATCH_BEGIN; + RTC_TRACE(rtcNewSharedBuffer); + RTC_VERIFY_HANDLE(hdevice); + Buffer* buffer = new Buffer((Device*)hdevice, byteSize, ptr); + return (RTCBuffer)buffer->refInc(); + RTC_CATCH_END((Device*)hdevice); + return nullptr; + } + + RTC_API void* rtcGetBufferData(RTCBuffer hbuffer) + { + Buffer* buffer = (Buffer*)hbuffer; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetBufferData); + RTC_VERIFY_HANDLE(hbuffer); + return buffer->data(); + RTC_CATCH_END2(buffer); + return nullptr; + } + + RTC_API void rtcRetainBuffer(RTCBuffer hbuffer) + { + Buffer* buffer = (Buffer*)hbuffer; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcRetainBuffer); + RTC_VERIFY_HANDLE(hbuffer); + buffer->refInc(); + RTC_CATCH_END2(buffer); + } + + RTC_API void rtcReleaseBuffer(RTCBuffer hbuffer) + { + Buffer* buffer = (Buffer*)hbuffer; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcReleaseBuffer); + RTC_VERIFY_HANDLE(hbuffer); + buffer->refDec(); + RTC_CATCH_END2(buffer); + } + + RTC_API RTCScene rtcNewScene (RTCDevice hdevice) + { + RTC_CATCH_BEGIN; + RTC_TRACE(rtcNewScene); + RTC_VERIFY_HANDLE(hdevice); + Scene* scene = new Scene((Device*)hdevice); + return (RTCScene) scene->refInc(); + RTC_CATCH_END((Device*)hdevice); + return nullptr; + } + + RTC_API RTCDevice rtcGetSceneDevice(RTCScene hscene) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetSceneDevice); + RTC_VERIFY_HANDLE(hscene); + return (RTCDevice)scene->device->refInc(); // user will own one additional device reference + RTC_CATCH_END2(scene); + return (RTCDevice)nullptr; + } + + RTC_API void rtcSetSceneProgressMonitorFunction(RTCScene hscene, RTCProgressMonitorFunction progress, void* ptr) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetSceneProgressMonitorFunction); + RTC_VERIFY_HANDLE(hscene); + Lock lock(g_mutex); + scene->setProgressMonitorFunction(progress,ptr); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcSetSceneBuildQuality (RTCScene hscene, RTCBuildQuality quality) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetSceneBuildQuality); + RTC_VERIFY_HANDLE(hscene); + if (quality != RTC_BUILD_QUALITY_LOW && + quality != RTC_BUILD_QUALITY_MEDIUM && + quality != RTC_BUILD_QUALITY_HIGH) + throw std::runtime_error("invalid build quality"); + scene->setBuildQuality(quality); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcSetSceneFlags (RTCScene hscene, RTCSceneFlags flags) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetSceneFlags); + RTC_VERIFY_HANDLE(hscene); + scene->setSceneFlags(flags); + RTC_CATCH_END2(scene); + } + + RTC_API RTCSceneFlags rtcGetSceneFlags(RTCScene hscene) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetSceneFlags); + RTC_VERIFY_HANDLE(hscene); + return scene->getSceneFlags(); + RTC_CATCH_END2(scene); + return RTC_SCENE_FLAG_NONE; + } + + RTC_API void rtcCommitScene (RTCScene hscene) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcCommitScene); + RTC_VERIFY_HANDLE(hscene); + scene->commit(false); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcJoinCommitScene (RTCScene hscene) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcJoinCommitScene); + RTC_VERIFY_HANDLE(hscene); + scene->commit(true); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcGetSceneBounds(RTCScene hscene, RTCBounds* bounds_o) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetSceneBounds); + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + BBox3fa bounds = scene->bounds.bounds(); + bounds_o->lower_x = bounds.lower.x; + bounds_o->lower_y = bounds.lower.y; + bounds_o->lower_z = bounds.lower.z; + bounds_o->align0 = 0; + bounds_o->upper_x = bounds.upper.x; + bounds_o->upper_y = bounds.upper.y; + bounds_o->upper_z = bounds.upper.z; + bounds_o->align1 = 0; + RTC_CATCH_END2(scene); + } + + RTC_API void rtcGetSceneLinearBounds(RTCScene hscene, RTCLinearBounds* bounds_o) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetSceneBounds); + RTC_VERIFY_HANDLE(hscene); + if (bounds_o == nullptr) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"invalid destination pointer"); + if (scene->isModified()) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + + bounds_o->bounds0.lower_x = scene->bounds.bounds0.lower.x; + bounds_o->bounds0.lower_y = scene->bounds.bounds0.lower.y; + bounds_o->bounds0.lower_z = scene->bounds.bounds0.lower.z; + bounds_o->bounds0.align0 = 0; + bounds_o->bounds0.upper_x = scene->bounds.bounds0.upper.x; + bounds_o->bounds0.upper_y = scene->bounds.bounds0.upper.y; + bounds_o->bounds0.upper_z = scene->bounds.bounds0.upper.z; + bounds_o->bounds0.align1 = 0; + bounds_o->bounds1.lower_x = scene->bounds.bounds1.lower.x; + bounds_o->bounds1.lower_y = scene->bounds.bounds1.lower.y; + bounds_o->bounds1.lower_z = scene->bounds.bounds1.lower.z; + bounds_o->bounds1.align0 = 0; + bounds_o->bounds1.upper_x = scene->bounds.bounds1.upper.x; + bounds_o->bounds1.upper_y = scene->bounds.bounds1.upper.y; + bounds_o->bounds1.upper_z = scene->bounds.bounds1.upper.z; + bounds_o->bounds1.align1 = 0; + RTC_CATCH_END2(scene); + } + + RTC_API void rtcCollide (RTCScene hscene0, RTCScene hscene1, RTCCollideFunc callback, void* userPtr) + { + Scene* scene0 = (Scene*) hscene0; + Scene* scene1 = (Scene*) hscene1; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcCollide); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene0); + RTC_VERIFY_HANDLE(hscene1); + if (scene0->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene got not committed"); + if (scene1->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene got not committed"); + if (scene0->device != scene1->device) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scenes are from different devices"); + auto nUserPrims0 = scene0->getNumPrimitives (Geometry::MTY_USER_GEOMETRY, false); + auto nUserPrims1 = scene1->getNumPrimitives (Geometry::MTY_USER_GEOMETRY, false); + if (scene0->numPrimitives() != nUserPrims0 && scene1->numPrimitives() != nUserPrims1) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scenes must only contain user geometries with a single timestep"); +#endif + scene0->intersectors.collide(scene0,scene1,callback,userPtr); + RTC_CATCH_END(scene0->device); + } + + inline bool pointQuery(Scene* scene, RTCPointQuery* query, RTCPointQueryContext* userContext, RTCPointQueryFunction queryFunc, void* userPtr) + { + bool changed = false; + if (userContext->instStackSize > 0) + { + const AffineSpace3fa transform = AffineSpace3fa_load_unaligned((AffineSpace3fa*)userContext->world2inst[userContext->instStackSize-1]); + + float similarityScale = 0.f; + const bool similtude = similarityTransform(transform, &similarityScale); + assert((similtude && similarityScale > 0) || (!similtude && similarityScale == 0.f)); + + PointQuery query_inst; + query_inst.p = xfmPoint(transform, Vec3fa(query->x, query->y, query->z)); + query_inst.radius = query->radius * similarityScale; + query_inst.time = query->time; + + PointQueryContext context_inst(scene, (PointQuery*)query, + similtude ? POINT_QUERY_TYPE_SPHERE : POINT_QUERY_TYPE_AABB, + queryFunc, userContext, similarityScale, userPtr); + changed = scene->intersectors.pointQuery((PointQuery*)&query_inst, &context_inst); + } + else + { + PointQueryContext context(scene, (PointQuery*)query, + POINT_QUERY_TYPE_SPHERE, queryFunc, userContext, 1.f, userPtr); + changed = scene->intersectors.pointQuery((PointQuery*)query, &context); + } + return changed; + } + + RTC_API bool rtcPointQuery(RTCScene hscene, RTCPointQuery* query, RTCPointQueryContext* userContext, RTCPointQueryFunction queryFunc, void* userPtr) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcPointQuery); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + RTC_VERIFY_HANDLE(userContext); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene got not committed"); + if (((size_t)query) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "query not aligned to 16 bytes"); + if (((size_t)userContext) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "context not aligned to 16 bytes"); +#endif + + return pointQuery(scene, query, userContext, queryFunc, userPtr); + RTC_CATCH_END2_FALSE(scene); + } + + RTC_API bool rtcPointQuery4 (const int* valid, RTCScene hscene, RTCPointQuery4* query, struct RTCPointQueryContext* userContext, RTCPointQueryFunction queryFunc, void** userPtrN) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcPointQuery4); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene got not committed"); + if (((size_t)valid) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 16 bytes"); + if (((size_t)query) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "query not aligned to 16 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<4; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(point_query.travs,cnt,cnt,cnt); + + bool changed = false; + PointQuery4* query4 = (PointQuery4*)query; + PointQuery query1; + for (size_t i=0; i<4; i++) { + if (!valid[i]) continue; + query4->get(i,query1); + changed |= pointQuery(scene, (RTCPointQuery*)&query1, userContext, queryFunc, userPtrN?userPtrN[i]:NULL); + query4->set(i,query1); + } + return changed; + RTC_CATCH_END2_FALSE(scene); + } + + RTC_API bool rtcPointQuery8 (const int* valid, RTCScene hscene, RTCPointQuery8* query, struct RTCPointQueryContext* userContext, RTCPointQueryFunction queryFunc, void** userPtrN) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcPointQuery8); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene got not committed"); + if (((size_t)valid) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 16 bytes"); + if (((size_t)query) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "query not aligned to 16 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<4; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(point_query.travs,cnt,cnt,cnt); + + bool changed = false; + PointQuery8* query8 = (PointQuery8*)query; + PointQuery query1; + for (size_t i=0; i<8; i++) { + if (!valid[i]) continue; + query8->get(i,query1); + changed |= pointQuery(scene, (RTCPointQuery*)&query1, userContext, queryFunc, userPtrN?userPtrN[i]:NULL); + query8->set(i,query1); + } + return changed; + RTC_CATCH_END2_FALSE(scene); + } + + RTC_API bool rtcPointQuery16 (const int* valid, RTCScene hscene, RTCPointQuery16* query, struct RTCPointQueryContext* userContext, RTCPointQueryFunction queryFunc, void** userPtrN) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcPointQuery16); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene got not committed"); + if (((size_t)valid) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 16 bytes"); + if (((size_t)query) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "query not aligned to 16 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<4; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(point_query.travs,cnt,cnt,cnt); + + bool changed = false; + PointQuery16* query16 = (PointQuery16*)query; + PointQuery query1; + for (size_t i=0; i<16; i++) { + if (!valid[i]) continue; + PointQuery query1; query16->get(i,query1); + changed |= pointQuery(scene, (RTCPointQuery*)&query1, userContext, queryFunc, userPtrN?userPtrN[i]:NULL); + query16->set(i,query1); + } + return changed; + RTC_CATCH_END2_FALSE(scene); + } + + RTC_API void rtcIntersect1 (RTCScene hscene, RTCIntersectContext* user_context, RTCRayHit* rayhit) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersect1); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)rayhit) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 16 bytes"); +#endif + STAT3(normal.travs,1,1,1); + IntersectContext context(scene,user_context); + scene->intersectors.intersect(*rayhit,&context); +#if defined(DEBUG) + ((RayHit*)rayhit)->verifyHit(); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersect4 (const int* valid, RTCScene hscene, RTCIntersectContext* user_context, RTCRayHit4* rayhit) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersect4); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)valid) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 16 bytes"); + if (((size_t)rayhit) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit not aligned to 16 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<4; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(normal.travs,cnt,cnt,cnt); + + IntersectContext context(scene,user_context); +#if !defined(EMBREE_RAY_PACKETS) + Ray4* ray4 = (Ray4*) rayhit; + for (size_t i=0; i<4; i++) { + if (!valid[i]) continue; + RayHit ray1; ray4->get(i,ray1); + scene->intersectors.intersect((RTCRayHit&)ray1,&context); + ray4->set(i,ray1); + } +#else + scene->intersectors.intersect4(valid,*rayhit,&context); +#endif + + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersect8 (const int* valid, RTCScene hscene, RTCIntersectContext* user_context, RTCRayHit8* rayhit) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersect8); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)valid) & 0x1F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 32 bytes"); + if (((size_t)rayhit) & 0x1F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit not aligned to 32 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<8; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(normal.travs,cnt,cnt,cnt); + + IntersectContext context(scene,user_context); +#if !defined(EMBREE_RAY_PACKETS) + Ray8* ray8 = (Ray8*) rayhit; + for (size_t i=0; i<8; i++) { + if (!valid[i]) continue; + RayHit ray1; ray8->get(i,ray1); + scene->intersectors.intersect((RTCRayHit&)ray1,&context); + ray8->set(i,ray1); + } +#else + if (likely(scene->intersectors.intersector8)) + scene->intersectors.intersect8(valid,*rayhit,&context); + else + scene->device->rayStreamFilters.intersectSOA(scene,(char*)rayhit,8,1,sizeof(RTCRayHit8),&context); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersect16 (const int* valid, RTCScene hscene, RTCIntersectContext* user_context, RTCRayHit16* rayhit) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersect16); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)valid) & 0x3F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 64 bytes"); + if (((size_t)rayhit) & 0x3F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit not aligned to 64 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<16; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(normal.travs,cnt,cnt,cnt); + + IntersectContext context(scene,user_context); +#if !defined(EMBREE_RAY_PACKETS) + Ray16* ray16 = (Ray16*) rayhit; + for (size_t i=0; i<16; i++) { + if (!valid[i]) continue; + RayHit ray1; ray16->get(i,ray1); + scene->intersectors.intersect((RTCRayHit&)ray1,&context); + ray16->set(i,ray1); + } +#else + if (likely(scene->intersectors.intersector16)) + scene->intersectors.intersect16(valid,*rayhit,&context); + else + scene->device->rayStreamFilters.intersectSOA(scene,(char*)rayhit,16,1,sizeof(RTCRayHit16),&context); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersect1M (RTCScene hscene, RTCIntersectContext* user_context, RTCRayHit* rayhit, unsigned int M, size_t byteStride) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersect1M); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)rayhit ) & 0x03) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 4 bytes"); +#endif + STAT3(normal.travs,M,M,M); + IntersectContext context(scene,user_context); + + /* fast codepath for single rays */ + if (likely(M == 1)) { + if (likely(rayhit->ray.tnear <= rayhit->ray.tfar)) + scene->intersectors.intersect(*rayhit,&context); + } + + /* codepath for streams */ + else { + scene->device->rayStreamFilters.intersectAOS(scene,rayhit,M,byteStride,&context); + } +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersect1M not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersect1Mp (RTCScene hscene, RTCIntersectContext* user_context, RTCRayHit** rn, unsigned int M) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersect1Mp); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)rn) & 0x03) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 4 bytes"); +#endif + STAT3(normal.travs,M,M,M); + IntersectContext context(scene,user_context); + + /* fast codepath for single rays */ + if (likely(M == 1)) { + if (likely(rn[0]->ray.tnear <= rn[0]->ray.tfar)) + scene->intersectors.intersect(*rn[0],&context); + } + + /* codepath for streams */ + else { + scene->device->rayStreamFilters.intersectAOP(scene,rn,M,&context); + } +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersect1Mp not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersectNM (RTCScene hscene, RTCIntersectContext* user_context, struct RTCRayHitN* rayhit, unsigned int N, unsigned int M, size_t byteStride) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersectNM); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)rayhit) & 0x03) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 4 bytes"); +#endif + STAT3(normal.travs,N*M,N*M,N*M); + IntersectContext context(scene,user_context); + + /* code path for single ray streams */ + if (likely(N == 1)) + { + /* fast code path for streams of size 1 */ + if (likely(M == 1)) { + if (likely(((RTCRayHit*)rayhit)->ray.tnear <= ((RTCRayHit*)rayhit)->ray.tfar)) + scene->intersectors.intersect(*(RTCRayHit*)rayhit,&context); + } + /* normal codepath for single ray streams */ + else { + scene->device->rayStreamFilters.intersectAOS(scene,(RTCRayHit*)rayhit,M,byteStride,&context); + } + } + /* code path for ray packet streams */ + else { + scene->device->rayStreamFilters.intersectSOA(scene,(char*)rayhit,N,M,byteStride,&context); + } +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersectNM not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcIntersectNp (RTCScene hscene, RTCIntersectContext* user_context, const RTCRayHitNp* rayhit, unsigned int N) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcIntersectNp); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)rayhit->ray.org_x ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.org_x not aligned to 4 bytes"); + if (((size_t)rayhit->ray.org_y ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.org_y not aligned to 4 bytes"); + if (((size_t)rayhit->ray.org_z ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.org_z not aligned to 4 bytes"); + if (((size_t)rayhit->ray.dir_x ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.dir_x not aligned to 4 bytes"); + if (((size_t)rayhit->ray.dir_y ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.dir_y not aligned to 4 bytes"); + if (((size_t)rayhit->ray.dir_z ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.dir_z not aligned to 4 bytes"); + if (((size_t)rayhit->ray.tnear ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.dir_x not aligned to 4 bytes"); + if (((size_t)rayhit->ray.tfar ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.tnear not aligned to 4 bytes"); + if (((size_t)rayhit->ray.time ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.time not aligned to 4 bytes"); + if (((size_t)rayhit->ray.mask ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->ray.mask not aligned to 4 bytes"); + if (((size_t)rayhit->hit.Ng_x ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.Ng_x not aligned to 4 bytes"); + if (((size_t)rayhit->hit.Ng_y ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.Ng_y not aligned to 4 bytes"); + if (((size_t)rayhit->hit.Ng_z ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.Ng_z not aligned to 4 bytes"); + if (((size_t)rayhit->hit.u ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.u not aligned to 4 bytes"); + if (((size_t)rayhit->hit.v ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.v not aligned to 4 bytes"); + if (((size_t)rayhit->hit.geomID) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.geomID not aligned to 4 bytes"); + if (((size_t)rayhit->hit.primID) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.primID not aligned to 4 bytes"); + if (((size_t)rayhit->hit.instID) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "rayhit->hit.instID not aligned to 4 bytes"); +#endif + STAT3(normal.travs,N,N,N); + IntersectContext context(scene,user_context); + scene->device->rayStreamFilters.intersectSOP(scene,rayhit,N,&context); +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersectNp not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccluded1 (RTCScene hscene, RTCIntersectContext* user_context, RTCRay* ray) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccluded1); + STAT3(shadow.travs,1,1,1); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)ray) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 16 bytes"); +#endif + IntersectContext context(scene,user_context); + scene->intersectors.occluded(*ray,&context); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccluded4 (const int* valid, RTCScene hscene, RTCIntersectContext* user_context, RTCRay4* ray) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccluded4); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)valid) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 16 bytes"); + if (((size_t)ray) & 0x0F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 16 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<4; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(shadow.travs,cnt,cnt,cnt); + + IntersectContext context(scene,user_context); +#if !defined(EMBREE_RAY_PACKETS) + RayHit4* ray4 = (RayHit4*) ray; + for (size_t i=0; i<4; i++) { + if (!valid[i]) continue; + RayHit ray1; ray4->get(i,ray1); + scene->intersectors.occluded((RTCRay&)ray1,&context); + ray4->geomID[i] = ray1.geomID; + } +#else + scene->intersectors.occluded4(valid,*ray,&context); +#endif + + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccluded8 (const int* valid, RTCScene hscene, RTCIntersectContext* user_context, RTCRay8* ray) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccluded8); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)valid) & 0x1F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 32 bytes"); + if (((size_t)ray) & 0x1F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 32 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<8; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(shadow.travs,cnt,cnt,cnt); + + IntersectContext context(scene,user_context); +#if !defined(EMBREE_RAY_PACKETS) + RayHit8* ray8 = (RayHit8*) ray; + for (size_t i=0; i<8; i++) { + if (!valid[i]) continue; + RayHit ray1; ray8->get(i,ray1); + scene->intersectors.occluded((RTCRay&)ray1,&context); + ray8->set(i,ray1); + } +#else + if (likely(scene->intersectors.intersector8)) + scene->intersectors.occluded8(valid,*ray,&context); + else + scene->device->rayStreamFilters.occludedSOA(scene,(char*)ray,8,1,sizeof(RTCRay8),&context); +#endif + + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccluded16 (const int* valid, RTCScene hscene, RTCIntersectContext* user_context, RTCRay16* ray) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccluded16); + +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)valid) & 0x3F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 64 bytes"); + if (((size_t)ray) & 0x3F) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 64 bytes"); +#endif + STAT(size_t cnt=0; for (size_t i=0; i<16; i++) cnt += ((int*)valid)[i] == -1;); + STAT3(shadow.travs,cnt,cnt,cnt); + + IntersectContext context(scene,user_context); +#if !defined(EMBREE_RAY_PACKETS) + RayHit16* ray16 = (RayHit16*) ray; + for (size_t i=0; i<16; i++) { + if (!valid[i]) continue; + RayHit ray1; ray16->get(i,ray1); + scene->intersectors.occluded((RTCRay&)ray1,&context); + ray16->set(i,ray1); + } +#else + if (likely(scene->intersectors.intersector16)) + scene->intersectors.occluded16(valid,*ray,&context); + else + scene->device->rayStreamFilters.occludedSOA(scene,(char*)ray,16,1,sizeof(RTCRay16),&context); +#endif + + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccluded1M(RTCScene hscene, RTCIntersectContext* user_context, RTCRay* ray, unsigned int M, size_t byteStride) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccluded1M); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)ray) & 0x03) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 4 bytes"); +#endif + STAT3(shadow.travs,M,M,M); + IntersectContext context(scene,user_context); + /* fast codepath for streams of size 1 */ + if (likely(M == 1)) { + if (likely(ray->tnear <= ray->tfar)) + scene->intersectors.occluded (*ray,&context); + } + /* codepath for normal streams */ + else { + scene->device->rayStreamFilters.occludedAOS(scene,ray,M,byteStride,&context); + } +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcOccluded1M not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccluded1Mp(RTCScene hscene, RTCIntersectContext* user_context, RTCRay** ray, unsigned int M) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccluded1Mp); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)ray) & 0x03) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 4 bytes"); +#endif + STAT3(shadow.travs,M,M,M); + IntersectContext context(scene,user_context); + + /* fast codepath for streams of size 1 */ + if (likely(M == 1)) { + if (likely(ray[0]->tnear <= ray[0]->tfar)) + scene->intersectors.occluded (*ray[0],&context); + } + /* codepath for normal streams */ + else { + scene->device->rayStreamFilters.occludedAOP(scene,ray,M,&context); + } +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcOccluded1Mp not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccludedNM(RTCScene hscene, RTCIntersectContext* user_context, RTCRayN* ray, unsigned int N, unsigned int M, size_t byteStride) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccludedNM); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (byteStride < sizeof(RTCRayHit)) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"byteStride too small"); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)ray) & 0x03) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "ray not aligned to 4 bytes"); +#endif + STAT3(shadow.travs,N*M,N*N,N*N); + IntersectContext context(scene,user_context); + + /* codepath for single rays */ + if (likely(N == 1)) + { + /* fast path for streams of size 1 */ + if (likely(M == 1)) { + if (likely(((RTCRay*)ray)->tnear <= ((RTCRay*)ray)->tfar)) + scene->intersectors.occluded (*(RTCRay*)ray,&context); + } + /* codepath for normal ray streams */ + else { + scene->device->rayStreamFilters.occludedAOS(scene,(RTCRay*)ray,M,byteStride,&context); + } + } + /* code path for ray packet streams */ + else { + scene->device->rayStreamFilters.occludedSOA(scene,(char*)ray,N,M,byteStride,&context); + } +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcOccludedNM not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcOccludedNp(RTCScene hscene, RTCIntersectContext* user_context, const RTCRayNp* ray, unsigned int N) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcOccludedNp); + +#if defined (EMBREE_RAY_PACKETS) +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + if (scene->isModified()) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); + if (((size_t)ray->org_x ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "org_x not aligned to 4 bytes"); + if (((size_t)ray->org_y ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "org_y not aligned to 4 bytes"); + if (((size_t)ray->org_z ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "org_z not aligned to 4 bytes"); + if (((size_t)ray->dir_x ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "dir_x not aligned to 4 bytes"); + if (((size_t)ray->dir_y ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "dir_y not aligned to 4 bytes"); + if (((size_t)ray->dir_z ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "dir_z not aligned to 4 bytes"); + if (((size_t)ray->tnear ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "dir_x not aligned to 4 bytes"); + if (((size_t)ray->tfar ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "tnear not aligned to 4 bytes"); + if (((size_t)ray->time ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "time not aligned to 4 bytes"); + if (((size_t)ray->mask ) & 0x03 ) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "mask not aligned to 4 bytes"); +#endif + STAT3(shadow.travs,N,N,N); + IntersectContext context(scene,user_context); + scene->device->rayStreamFilters.occludedSOP(scene,ray,N,&context); +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcOccludedNp not supported"); +#endif + RTC_CATCH_END2(scene); + } + + RTC_API void rtcRetainScene (RTCScene hscene) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcRetainScene); + RTC_VERIFY_HANDLE(hscene); + scene->refInc(); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcReleaseScene (RTCScene hscene) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcReleaseScene); + RTC_VERIFY_HANDLE(hscene); + scene->refDec(); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcSetGeometryInstancedScene(RTCGeometry hgeometry, RTCScene hscene) + { + Geometry* geometry = (Geometry*) hgeometry; + Ref scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryInstancedScene); + RTC_VERIFY_HANDLE(hgeometry); + RTC_VERIFY_HANDLE(hscene); + geometry->setInstancedScene(scene); + RTC_CATCH_END2(geometry); + } + + AffineSpace3fa loadTransform(RTCFormat format, const float* xfm) + { + AffineSpace3fa space = one; + switch (format) + { + case RTC_FORMAT_FLOAT3X4_ROW_MAJOR: + space = AffineSpace3fa(Vec3fa(xfm[ 0], xfm[ 4], xfm[ 8]), + Vec3fa(xfm[ 1], xfm[ 5], xfm[ 9]), + Vec3fa(xfm[ 2], xfm[ 6], xfm[10]), + Vec3fa(xfm[ 3], xfm[ 7], xfm[11])); + break; + + case RTC_FORMAT_FLOAT3X4_COLUMN_MAJOR: + space = AffineSpace3fa(Vec3fa(xfm[ 0], xfm[ 1], xfm[ 2]), + Vec3fa(xfm[ 3], xfm[ 4], xfm[ 5]), + Vec3fa(xfm[ 6], xfm[ 7], xfm[ 8]), + Vec3fa(xfm[ 9], xfm[10], xfm[11])); + break; + + case RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR: + space = AffineSpace3fa(Vec3fa(xfm[ 0], xfm[ 1], xfm[ 2]), + Vec3fa(xfm[ 4], xfm[ 5], xfm[ 6]), + Vec3fa(xfm[ 8], xfm[ 9], xfm[10]), + Vec3fa(xfm[12], xfm[13], xfm[14])); + break; + + default: + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid matrix format"); + break; + } + return space; + } + + void storeTransform(const AffineSpace3fa& space, RTCFormat format, float* xfm) + { + switch (format) + { + case RTC_FORMAT_FLOAT3X4_ROW_MAJOR: + xfm[ 0] = space.l.vx.x; xfm[ 1] = space.l.vy.x; xfm[ 2] = space.l.vz.x; xfm[ 3] = space.p.x; + xfm[ 4] = space.l.vx.y; xfm[ 5] = space.l.vy.y; xfm[ 6] = space.l.vz.y; xfm[ 7] = space.p.y; + xfm[ 8] = space.l.vx.z; xfm[ 9] = space.l.vy.z; xfm[10] = space.l.vz.z; xfm[11] = space.p.z; + break; + + case RTC_FORMAT_FLOAT3X4_COLUMN_MAJOR: + xfm[ 0] = space.l.vx.x; xfm[ 1] = space.l.vx.y; xfm[ 2] = space.l.vx.z; + xfm[ 3] = space.l.vy.x; xfm[ 4] = space.l.vy.y; xfm[ 5] = space.l.vy.z; + xfm[ 6] = space.l.vz.x; xfm[ 7] = space.l.vz.y; xfm[ 8] = space.l.vz.z; + xfm[ 9] = space.p.x; xfm[10] = space.p.y; xfm[11] = space.p.z; + break; + + case RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR: + xfm[ 0] = space.l.vx.x; xfm[ 1] = space.l.vx.y; xfm[ 2] = space.l.vx.z; xfm[ 3] = 0.f; + xfm[ 4] = space.l.vy.x; xfm[ 5] = space.l.vy.y; xfm[ 6] = space.l.vy.z; xfm[ 7] = 0.f; + xfm[ 8] = space.l.vz.x; xfm[ 9] = space.l.vz.y; xfm[10] = space.l.vz.z; xfm[11] = 0.f; + xfm[12] = space.p.x; xfm[13] = space.p.y; xfm[14] = space.p.z; xfm[15] = 1.f; + break; + + default: + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid matrix format"); + break; + } + } + + RTC_API void rtcSetGeometryTransform(RTCGeometry hgeometry, unsigned int timeStep, RTCFormat format, const void* xfm) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryTransform); + RTC_VERIFY_HANDLE(hgeometry); + RTC_VERIFY_HANDLE(xfm); + const AffineSpace3fa transform = loadTransform(format, (const float*)xfm); + geometry->setTransform(transform, timeStep); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryTransformQuaternion(RTCGeometry hgeometry, unsigned int timeStep, const RTCQuaternionDecomposition* qd) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryTransformQuaternion); + RTC_VERIFY_HANDLE(hgeometry); + RTC_VERIFY_HANDLE(qd); + + AffineSpace3fx transform; + transform.l.vx.x = qd->scale_x; + transform.l.vy.y = qd->scale_y; + transform.l.vz.z = qd->scale_z; + transform.l.vy.x = qd->skew_xy; + transform.l.vz.x = qd->skew_xz; + transform.l.vz.y = qd->skew_yz; + transform.l.vx.y = qd->translation_x; + transform.l.vx.z = qd->translation_y; + transform.l.vy.z = qd->translation_z; + transform.p.x = qd->shift_x; + transform.p.y = qd->shift_y; + transform.p.z = qd->shift_z; + + // normalize quaternion + Quaternion3f q(qd->quaternion_r, qd->quaternion_i, qd->quaternion_j, qd->quaternion_k); + q = normalize(q); + transform.l.vx.w = q.i; + transform.l.vy.w = q.j; + transform.l.vz.w = q.k; + transform.p.w = q.r; + + geometry->setQuaternionDecomposition(transform, timeStep); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcGetGeometryTransform(RTCGeometry hgeometry, float time, RTCFormat format, void* xfm) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryTransform); + const AffineSpace3fa transform = geometry->getTransform(time); + storeTransform(transform, format, (float*)xfm); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcFilterIntersection(const struct RTCIntersectFunctionNArguments* const args_i, const struct RTCFilterFunctionNArguments* filter_args) + { + IntersectFunctionNArguments* args = (IntersectFunctionNArguments*) args_i; + args->report(args,filter_args); + } + + RTC_API void rtcFilterOcclusion(const struct RTCOccludedFunctionNArguments* const args_i, const struct RTCFilterFunctionNArguments* filter_args) + { + OccludedFunctionNArguments* args = (OccludedFunctionNArguments*) args_i; + args->report(args,filter_args); + } + + RTC_API RTCGeometry rtcNewGeometry (RTCDevice hdevice, RTCGeometryType type) + { + Device* device = (Device*) hdevice; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcNewGeometry); + RTC_VERIFY_HANDLE(hdevice); + + switch (type) + { + case RTC_GEOMETRY_TYPE_TRIANGLE: + { +#if defined(EMBREE_GEOMETRY_TRIANGLE) + createTriangleMeshTy createTriangleMesh = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createTriangleMesh); + Geometry* geom = createTriangleMesh(device); + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_TRIANGLE is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_QUAD: + { +#if defined(EMBREE_GEOMETRY_QUAD) + createQuadMeshTy createQuadMesh = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createQuadMesh); + Geometry* geom = createQuadMesh(device); + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_QUAD is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_SPHERE_POINT: + case RTC_GEOMETRY_TYPE_DISC_POINT: + case RTC_GEOMETRY_TYPE_ORIENTED_DISC_POINT: + { +#if defined(EMBREE_GEOMETRY_POINT) + createPointsTy createPoints = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_builder_cpu_features, createPoints); + + Geometry *geom; + switch(type) { + case RTC_GEOMETRY_TYPE_SPHERE_POINT: + geom = createPoints(device, Geometry::GTY_SPHERE_POINT); + break; + case RTC_GEOMETRY_TYPE_DISC_POINT: + geom = createPoints(device, Geometry::GTY_DISC_POINT); + break; + case RTC_GEOMETRY_TYPE_ORIENTED_DISC_POINT: + geom = createPoints(device, Geometry::GTY_ORIENTED_DISC_POINT); + break; + default: + geom = nullptr; + break; + } + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_POINT is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_CONE_LINEAR_CURVE: + case RTC_GEOMETRY_TYPE_ROUND_LINEAR_CURVE: + case RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE: + + case RTC_GEOMETRY_TYPE_ROUND_BEZIER_CURVE: + case RTC_GEOMETRY_TYPE_FLAT_BEZIER_CURVE: + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BEZIER_CURVE: + + case RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE: + case RTC_GEOMETRY_TYPE_FLAT_BSPLINE_CURVE: + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BSPLINE_CURVE: + + case RTC_GEOMETRY_TYPE_ROUND_HERMITE_CURVE: + case RTC_GEOMETRY_TYPE_FLAT_HERMITE_CURVE: + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_HERMITE_CURVE: + + case RTC_GEOMETRY_TYPE_ROUND_CATMULL_ROM_CURVE: + case RTC_GEOMETRY_TYPE_FLAT_CATMULL_ROM_CURVE: + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_CATMULL_ROM_CURVE: + { +#if defined(EMBREE_GEOMETRY_CURVE) + createLineSegmentsTy createLineSegments = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createLineSegments); + createCurvesTy createCurves = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createCurves); + + Geometry* geom; + switch (type) { + case RTC_GEOMETRY_TYPE_CONE_LINEAR_CURVE : geom = createLineSegments (device,Geometry::GTY_CONE_LINEAR_CURVE); break; + case RTC_GEOMETRY_TYPE_ROUND_LINEAR_CURVE : geom = createLineSegments (device,Geometry::GTY_ROUND_LINEAR_CURVE); break; + case RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE : geom = createLineSegments (device,Geometry::GTY_FLAT_LINEAR_CURVE); break; + //case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_LINEAR_CURVE : geom = createLineSegments (device,Geometry::GTY_ORIENTED_LINEAR_CURVE); break; + + case RTC_GEOMETRY_TYPE_ROUND_BEZIER_CURVE : geom = createCurves(device,Geometry::GTY_ROUND_BEZIER_CURVE); break; + case RTC_GEOMETRY_TYPE_FLAT_BEZIER_CURVE : geom = createCurves(device,Geometry::GTY_FLAT_BEZIER_CURVE); break; + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BEZIER_CURVE : geom = createCurves(device,Geometry::GTY_ORIENTED_BEZIER_CURVE); break; + + case RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE : geom = createCurves(device,Geometry::GTY_ROUND_BSPLINE_CURVE); break; + case RTC_GEOMETRY_TYPE_FLAT_BSPLINE_CURVE : geom = createCurves(device,Geometry::GTY_FLAT_BSPLINE_CURVE); break; + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BSPLINE_CURVE : geom = createCurves(device,Geometry::GTY_ORIENTED_BSPLINE_CURVE); break; + + case RTC_GEOMETRY_TYPE_ROUND_HERMITE_CURVE : geom = createCurves(device,Geometry::GTY_ROUND_HERMITE_CURVE); break; + case RTC_GEOMETRY_TYPE_FLAT_HERMITE_CURVE : geom = createCurves(device,Geometry::GTY_FLAT_HERMITE_CURVE); break; + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_HERMITE_CURVE : geom = createCurves(device,Geometry::GTY_ORIENTED_HERMITE_CURVE); break; + + case RTC_GEOMETRY_TYPE_ROUND_CATMULL_ROM_CURVE : geom = createCurves(device,Geometry::GTY_ROUND_CATMULL_ROM_CURVE); break; + case RTC_GEOMETRY_TYPE_FLAT_CATMULL_ROM_CURVE : geom = createCurves(device,Geometry::GTY_FLAT_CATMULL_ROM_CURVE); break; + case RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_CATMULL_ROM_CURVE : geom = createCurves(device,Geometry::GTY_ORIENTED_CATMULL_ROM_CURVE); break; + default: geom = nullptr; break; + } + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_CURVE is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_SUBDIVISION: + { +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + createSubdivMeshTy createSubdivMesh = nullptr; + SELECT_SYMBOL_DEFAULT_AVX(device->enabled_cpu_features,createSubdivMesh); + //SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createSubdivMesh); // FIXME: this does not work for some reason? + Geometry* geom = createSubdivMesh(device); + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_SUBDIVISION is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_USER: + { +#if defined(EMBREE_GEOMETRY_USER) + createUserGeometryTy createUserGeometry = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createUserGeometry); + Geometry* geom = createUserGeometry(device); + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_USER is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_INSTANCE: + { +#if defined(EMBREE_GEOMETRY_INSTANCE) + createInstanceTy createInstance = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createInstance); + Geometry* geom = createInstance(device); + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_INSTANCE is not supported"); +#endif + } + + case RTC_GEOMETRY_TYPE_GRID: + { +#if defined(EMBREE_GEOMETRY_GRID) + createGridMeshTy createGridMesh = nullptr; + SELECT_SYMBOL_DEFAULT_AVX_AVX2_AVX512KNL_AVX512SKX(device->enabled_cpu_features,createGridMesh); + Geometry* geom = createGridMesh(device); + return (RTCGeometry) geom->refInc(); +#else + throw_RTCError(RTC_ERROR_UNKNOWN,"RTC_GEOMETRY_TYPE_GRID is not supported"); +#endif + } + + default: + throw_RTCError(RTC_ERROR_UNKNOWN,"invalid geometry type"); + } + + RTC_CATCH_END(device); + return nullptr; + } + + RTC_API void rtcSetGeometryUserPrimitiveCount(RTCGeometry hgeometry, unsigned int userPrimitiveCount) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryUserPrimitiveCount); + RTC_VERIFY_HANDLE(hgeometry); + + if (unlikely(geometry->getType() != Geometry::GTY_USER_GEOMETRY)) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"operation only allowed for user geometries"); + + geometry->setNumPrimitives(userPrimitiveCount); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryTimeStepCount(RTCGeometry hgeometry, unsigned int timeStepCount) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryTimeStepCount); + RTC_VERIFY_HANDLE(hgeometry); + + if (timeStepCount > RTC_MAX_TIME_STEP_COUNT) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"number of time steps is out of range"); + + geometry->setNumTimeSteps(timeStepCount); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryTimeRange(RTCGeometry hgeometry, float startTime, float endTime) + { + Ref geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryTimeRange); + RTC_VERIFY_HANDLE(hgeometry); + + if (startTime > endTime) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"startTime has to be smaller or equal to the endTime"); + + geometry->setTimeRange(BBox1f(startTime,endTime)); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryVertexAttributeCount(RTCGeometry hgeometry, unsigned int N) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryVertexAttributeCount); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setVertexAttributeCount(N); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryTopologyCount(RTCGeometry hgeometry, unsigned int N) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryTopologyCount); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setTopologyCount(N); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryBuildQuality (RTCGeometry hgeometry, RTCBuildQuality quality) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryBuildQuality); + RTC_VERIFY_HANDLE(hgeometry); + if (quality != RTC_BUILD_QUALITY_LOW && + quality != RTC_BUILD_QUALITY_MEDIUM && + quality != RTC_BUILD_QUALITY_HIGH && + quality != RTC_BUILD_QUALITY_REFIT) + throw std::runtime_error("invalid build quality"); + geometry->setBuildQuality(quality); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryMaxRadiusScale(RTCGeometry hgeometry, float maxRadiusScale) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryMaxRadiusScale); + RTC_VERIFY_HANDLE(hgeometry); +#if RTC_MIN_WIDTH + if (maxRadiusScale < 1.0f) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"maximal radius scale has to be larger or equal to 1"); + geometry->setMaxRadiusScale(maxRadiusScale); +#else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"min-width feature is not enabled"); +#endif + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryMask (RTCGeometry hgeometry, unsigned int mask) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryMask); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setMask(mask); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometrySubdivisionMode (RTCGeometry hgeometry, unsigned topologyID, RTCSubdivisionMode mode) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometrySubdivisionMode); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setSubdivisionMode(topologyID,mode); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryVertexAttributeTopology(RTCGeometry hgeometry, unsigned int vertexAttributeID, unsigned int topologyID) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryVertexAttributeTopology); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setVertexAttributeTopology(vertexAttributeID, topologyID); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryBuffer(RTCGeometry hgeometry, RTCBufferType type, unsigned int slot, RTCFormat format, RTCBuffer hbuffer, size_t byteOffset, size_t byteStride, size_t itemCount) + { + Geometry* geometry = (Geometry*) hgeometry; + Ref buffer = (Buffer*)hbuffer; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryBuffer); + RTC_VERIFY_HANDLE(hgeometry); + RTC_VERIFY_HANDLE(hbuffer); + + if (geometry->device != buffer->device) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"inputs are from different devices"); + + if (itemCount > 0xFFFFFFFFu) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"buffer too large"); + + geometry->setBuffer(type, slot, format, buffer, byteOffset, byteStride, (unsigned int)itemCount); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetSharedGeometryBuffer(RTCGeometry hgeometry, RTCBufferType type, unsigned int slot, RTCFormat format, const void* ptr, size_t byteOffset, size_t byteStride, size_t itemCount) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetSharedGeometryBuffer); + RTC_VERIFY_HANDLE(hgeometry); + + if (itemCount > 0xFFFFFFFFu) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"buffer too large"); + + Ref buffer = new Buffer(geometry->device, itemCount*byteStride, (char*)ptr + byteOffset); + geometry->setBuffer(type, slot, format, buffer, 0, byteStride, (unsigned int)itemCount); + RTC_CATCH_END2(geometry); + } + + RTC_API void* rtcSetNewGeometryBuffer(RTCGeometry hgeometry, RTCBufferType type, unsigned int slot, RTCFormat format, size_t byteStride, size_t itemCount) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetNewGeometryBuffer); + RTC_VERIFY_HANDLE(hgeometry); + + if (itemCount > 0xFFFFFFFFu) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"buffer too large"); + + /* vertex buffers need to get overallocated slightly as elements are accessed using SSE loads */ + size_t bytes = itemCount*byteStride; + if (type == RTC_BUFFER_TYPE_VERTEX || type == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE) + bytes += (16 - (byteStride%16))%16; + + Ref buffer = new Buffer(geometry->device, bytes); + geometry->setBuffer(type, slot, format, buffer, 0, byteStride, (unsigned int)itemCount); + return buffer->data(); + RTC_CATCH_END2(geometry); + return nullptr; + } + + RTC_API void* rtcGetGeometryBufferData(RTCGeometry hgeometry, RTCBufferType type, unsigned int slot) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryBufferData); + RTC_VERIFY_HANDLE(hgeometry); + return geometry->getBuffer(type, slot); + RTC_CATCH_END2(geometry); + return nullptr; + } + + RTC_API void rtcEnableGeometry (RTCGeometry hgeometry) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcEnableGeometry); + RTC_VERIFY_HANDLE(hgeometry); + geometry->enable(); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcUpdateGeometryBuffer (RTCGeometry hgeometry, RTCBufferType type, unsigned int slot) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcUpdateGeometryBuffer); + RTC_VERIFY_HANDLE(hgeometry); + geometry->updateBuffer(type, slot); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcDisableGeometry (RTCGeometry hgeometry) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcDisableGeometry); + RTC_VERIFY_HANDLE(hgeometry); + geometry->disable(); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryTessellationRate (RTCGeometry hgeometry, float tessellationRate) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryTessellationRate); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setTessellationRate(tessellationRate); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryUserData (RTCGeometry hgeometry, void* ptr) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryUserData); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setUserData(ptr); + RTC_CATCH_END2(geometry); + } + + RTC_API void* rtcGetGeometryUserData (RTCGeometry hgeometry) + { + Geometry* geometry = (Geometry*) hgeometry; // no ref counting here! + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryUserData); + RTC_VERIFY_HANDLE(hgeometry); + return geometry->getUserData(); + RTC_CATCH_END2(geometry); + return nullptr; + } + + RTC_API void rtcSetGeometryBoundsFunction (RTCGeometry hgeometry, RTCBoundsFunction bounds, void* userPtr) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryBoundsFunction); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setBoundsFunction(bounds,userPtr); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryDisplacementFunction (RTCGeometry hgeometry, RTCDisplacementFunctionN displacement) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryDisplacementFunction); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setDisplacementFunction(displacement); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryIntersectFunction (RTCGeometry hgeometry, RTCIntersectFunctionN intersect) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryIntersectFunction); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setIntersectFunctionN(intersect); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryPointQueryFunction(RTCGeometry hgeometry, RTCPointQueryFunction pointQuery) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryPointQueryFunction); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setPointQueryFunction(pointQuery); + RTC_CATCH_END2(geometry); + } + + RTC_API unsigned int rtcGetGeometryFirstHalfEdge(RTCGeometry hgeometry, unsigned int faceID) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryFirstHalfEdge); + return geometry->getFirstHalfEdge(faceID); + RTC_CATCH_END2(geometry); + return -1; + } + + RTC_API unsigned int rtcGetGeometryFace(RTCGeometry hgeometry, unsigned int edgeID) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryFace); + return geometry->getFace(edgeID); + RTC_CATCH_END2(geometry); + return -1; + } + + RTC_API unsigned int rtcGetGeometryNextHalfEdge(RTCGeometry hgeometry, unsigned int edgeID) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryNextHalfEdge); + return geometry->getNextHalfEdge(edgeID); + RTC_CATCH_END2(geometry); + return -1; + } + + RTC_API unsigned int rtcGetGeometryPreviousHalfEdge(RTCGeometry hgeometry, unsigned int edgeID) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryPreviousHalfEdge); + return geometry->getPreviousHalfEdge(edgeID); + RTC_CATCH_END2(geometry); + return -1; + } + + RTC_API unsigned int rtcGetGeometryOppositeHalfEdge(RTCGeometry hgeometry, unsigned int topologyID, unsigned int edgeID) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometryOppositeHalfEdge); + return geometry->getOppositeHalfEdge(topologyID,edgeID); + RTC_CATCH_END2(geometry); + return -1; + } + + RTC_API void rtcSetGeometryOccludedFunction (RTCGeometry hgeometry, RTCOccludedFunctionN occluded) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetOccludedFunctionN); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setOccludedFunctionN(occluded); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryIntersectFilterFunction (RTCGeometry hgeometry, RTCFilterFunctionN filter) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryIntersectFilterFunction); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setIntersectionFilterFunctionN(filter); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcSetGeometryOccludedFilterFunction (RTCGeometry hgeometry, RTCFilterFunctionN filter) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcSetGeometryOccludedFilterFunction); + RTC_VERIFY_HANDLE(hgeometry); + geometry->setOcclusionFilterFunctionN(filter); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcInterpolate(const RTCInterpolateArguments* const args) + { + Geometry* geometry = (Geometry*) args->geometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcInterpolate); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(args->geometry); +#endif + geometry->interpolate(args); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcInterpolateN(const RTCInterpolateNArguments* const args) + { + Geometry* geometry = (Geometry*) args->geometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcInterpolateN); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(args->geometry); +#endif + geometry->interpolateN(args); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcCommitGeometry (RTCGeometry hgeometry) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcCommitGeometry); + RTC_VERIFY_HANDLE(hgeometry); + return geometry->commit(); + RTC_CATCH_END2(geometry); + } + + RTC_API unsigned int rtcAttachGeometry (RTCScene hscene, RTCGeometry hgeometry) + { + Scene* scene = (Scene*) hscene; + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcAttachGeometry); + RTC_VERIFY_HANDLE(hscene); + RTC_VERIFY_HANDLE(hgeometry); + if (scene->device != geometry->device) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"inputs are from different devices"); + return scene->bind(RTC_INVALID_GEOMETRY_ID,geometry); + RTC_CATCH_END2(scene); + return -1; + } + + RTC_API void rtcAttachGeometryByID (RTCScene hscene, RTCGeometry hgeometry, unsigned int geomID) + { + Scene* scene = (Scene*) hscene; + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcAttachGeometryByID); + RTC_VERIFY_HANDLE(hscene); + RTC_VERIFY_HANDLE(hgeometry); + RTC_VERIFY_GEOMID(geomID); + if (scene->device != geometry->device) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"inputs are from different devices"); + scene->bind(geomID,geometry); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcDetachGeometry (RTCScene hscene, unsigned int geomID) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcDetachGeometry); + RTC_VERIFY_HANDLE(hscene); + RTC_VERIFY_GEOMID(geomID); + scene->detachGeometry(geomID); + RTC_CATCH_END2(scene); + } + + RTC_API void rtcRetainGeometry (RTCGeometry hgeometry) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcRetainGeometry); + RTC_VERIFY_HANDLE(hgeometry); + geometry->refInc(); + RTC_CATCH_END2(geometry); + } + + RTC_API void rtcReleaseGeometry (RTCGeometry hgeometry) + { + Geometry* geometry = (Geometry*) hgeometry; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcReleaseGeometry); + RTC_VERIFY_HANDLE(hgeometry); + geometry->refDec(); + RTC_CATCH_END2(geometry); + } + + RTC_API RTCGeometry rtcGetGeometry (RTCScene hscene, unsigned int geomID) + { + Scene* scene = (Scene*) hscene; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcGetGeometry); +#if defined(DEBUG) + RTC_VERIFY_HANDLE(hscene); + RTC_VERIFY_GEOMID(geomID); +#endif + return (RTCGeometry) scene->get(geomID); + RTC_CATCH_END2(scene); + return nullptr; + } + +RTC_NAMESPACE_END diff --git a/thirdparty/embree/kernels/common/rtcore.h b/thirdparty/embree/kernels/common/rtcore.h new file mode 100644 index 000000000000..6583d12d57cc --- /dev/null +++ b/thirdparty/embree/kernels/common/rtcore.h @@ -0,0 +1,126 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../../include/embree3/rtcore.h" +RTC_NAMESPACE_USE + +namespace embree +{ + /*! decoding of intersection flags */ + __forceinline bool isCoherent (RTCIntersectContextFlags flags) { return (flags & RTC_INTERSECT_CONTEXT_FLAG_COHERENT) == RTC_INTERSECT_CONTEXT_FLAG_COHERENT; } + __forceinline bool isIncoherent(RTCIntersectContextFlags flags) { return (flags & RTC_INTERSECT_CONTEXT_FLAG_COHERENT) == RTC_INTERSECT_CONTEXT_FLAG_INCOHERENT; } + +#if defined(TASKING_TBB) && (TBB_INTERFACE_VERSION_MAJOR >= 8) +# define USE_TASK_ARENA 1 +#else +# define USE_TASK_ARENA 0 +#endif + +#if defined(TASKING_TBB) && (TBB_INTERFACE_VERSION >= 11009) // TBB 2019 Update 9 +# define TASKING_TBB_USE_TASK_ISOLATION 1 +#else +# define TASKING_TBB_USE_TASK_ISOLATION 0 +#endif + +/*! Macros used in the rtcore API implementation */ +#define RTC_CATCH_BEGIN try { + +#define RTC_CATCH_END(device) \ + } catch (std::bad_alloc&) { \ + Device::process_error(device,RTC_ERROR_OUT_OF_MEMORY,"out of memory"); \ + } catch (rtcore_error& e) { \ + Device::process_error(device,e.error,e.what()); \ + } catch (std::exception& e) { \ + Device::process_error(device,RTC_ERROR_UNKNOWN,e.what()); \ + } catch (...) { \ + Device::process_error(device,RTC_ERROR_UNKNOWN,"unknown exception caught"); \ + } + +#define RTC_CATCH_END2(scene) \ + } catch (std::bad_alloc&) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,RTC_ERROR_OUT_OF_MEMORY,"out of memory"); \ + } catch (rtcore_error& e) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,e.error,e.what()); \ + } catch (std::exception& e) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,RTC_ERROR_UNKNOWN,e.what()); \ + } catch (...) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,RTC_ERROR_UNKNOWN,"unknown exception caught"); \ + } + +#define RTC_CATCH_END2_FALSE(scene) \ + } catch (std::bad_alloc&) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,RTC_ERROR_OUT_OF_MEMORY,"out of memory"); \ + return false; \ + } catch (rtcore_error& e) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,e.error,e.what()); \ + return false; \ + } catch (std::exception& e) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,RTC_ERROR_UNKNOWN,e.what()); \ + return false; \ + } catch (...) { \ + Device* device = scene ? scene->device : nullptr; \ + Device::process_error(device,RTC_ERROR_UNKNOWN,"unknown exception caught"); \ + return false; \ + } + +#define RTC_VERIFY_HANDLE(handle) \ + if (handle == nullptr) { \ + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"invalid argument"); \ + } + +#define RTC_VERIFY_GEOMID(id) \ + if (id == RTC_INVALID_GEOMETRY_ID) { \ + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"invalid argument"); \ + } + +#define RTC_VERIFY_UPPER(id,upper) \ + if (id > upper) { \ + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"invalid argument"); \ + } + +#define RTC_VERIFY_RANGE(id,lower,upper) \ + if (id < lower || id > upper) \ + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"argument out of bounds"); + +#if 0 // enable to debug print all API calls +#define RTC_TRACE(x) std::cout << #x << std::endl; +#else +#define RTC_TRACE(x) +#endif + + /*! used to throw embree API errors */ + struct rtcore_error : public std::exception + { + __forceinline rtcore_error(RTCError error, const std::string& str) + : error(error), str(str) {} + + ~rtcore_error() throw() {} + + const char* what () const throw () { + return str.c_str(); + } + + RTCError error; + std::string str; + }; + +#if defined(DEBUG) // only report file and line in debug mode + #define throw_RTCError(error,str) \ + throw rtcore_error(error,std::string(__FILE__) + " (" + toString(__LINE__) + "): " + std::string(str)); +#else + #define throw_RTCError(error,str) \ + throw rtcore_error(error,str); +#endif + +#define RTC_BUILD_ARGUMENTS_HAS(settings,member) \ + (settings.byteSize > (offsetof(RTCBuildArguments,member)+sizeof(settings.member))) +} diff --git a/thirdparty/embree/kernels/common/rtcore_builder.cpp b/thirdparty/embree/kernels/common/rtcore_builder.cpp new file mode 100644 index 000000000000..6bb96bba077f --- /dev/null +++ b/thirdparty/embree/kernels/common/rtcore_builder.cpp @@ -0,0 +1,442 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#define RTC_EXPORT_API + +#include "default.h" +#include "device.h" +#include "scene.h" +#include "context.h" +#include "alloc.h" + +#include "../builders/bvh_builder_sah.h" +#include "../builders/bvh_builder_morton.h" + +namespace embree +{ + namespace isa // FIXME: support more ISAs for builders + { + struct BVH : public RefCount + { + BVH (Device* device) + : device(device), allocator(device,true), morton_src(device,0), morton_tmp(device,0) + { + device->refInc(); + } + + ~BVH() { + device->refDec(); + } + + public: + Device* device; + FastAllocator allocator; + mvector morton_src; + mvector morton_tmp; + }; + + void* rtcBuildBVHMorton(const RTCBuildArguments* arguments) + { + BVH* bvh = (BVH*) arguments->bvh; + RTCBuildPrimitive* prims_i = arguments->primitives; + size_t primitiveCount = arguments->primitiveCount; + RTCCreateNodeFunction createNode = arguments->createNode; + RTCSetNodeChildrenFunction setNodeChildren = arguments->setNodeChildren; + RTCSetNodeBoundsFunction setNodeBounds = arguments->setNodeBounds; + RTCCreateLeafFunction createLeaf = arguments->createLeaf; + RTCProgressMonitorFunction buildProgress = arguments->buildProgress; + void* userPtr = arguments->userPtr; + + std::atomic progress(0); + + /* initialize temporary arrays for morton builder */ + PrimRef* prims = (PrimRef*) prims_i; + mvector& morton_src = bvh->morton_src; + mvector& morton_tmp = bvh->morton_tmp; + morton_src.resize(primitiveCount); + morton_tmp.resize(primitiveCount); + + /* compute centroid bounds */ + const BBox3fa centBounds = parallel_reduce ( size_t(0), primitiveCount, BBox3fa(empty), [&](const range& r) -> BBox3fa { + + BBox3fa bounds(empty); + for (size_t i=r.begin(); i& r) { + BVHBuilderMorton::MortonCodeGenerator generator(mapping,&morton_src[r.begin()]); + for (size_t i=r.begin(); i root = BVHBuilderMorton::build>( + + /* thread local allocator for fast allocations */ + [&] () -> FastAllocator::CachedAllocator { + return bvh->allocator.getCachedAllocator(); + }, + + /* lambda function that allocates BVH nodes */ + [&] ( const FastAllocator::CachedAllocator& alloc, size_t N ) -> void* { + return createNode((RTCThreadLocalAllocator)&alloc, (unsigned int)N,userPtr); + }, + + /* lambda function that sets bounds */ + [&] (void* node, const std::pair* children, size_t N) -> std::pair + { + BBox3fa bounds = empty; + void* childptrs[BVHBuilderMorton::MAX_BRANCHING_FACTOR]; + const RTCBounds* cbounds[BVHBuilderMorton::MAX_BRANCHING_FACTOR]; + for (size_t i=0; i& current, const FastAllocator::CachedAllocator& alloc) -> std::pair + { + RTCBuildPrimitive localBuildPrims[RTC_BUILD_MAX_PRIMITIVES_PER_LEAF]; + BBox3fa bounds = empty; + for (size_t i=0;i BBox3fa { + return prims[morton.index].bounds(); + }, + + /* progress monitor function */ + [&] (size_t dn) { + if (!buildProgress) return true; + const size_t n = progress.fetch_add(dn)+dn; + const double f = std::min(1.0,double(n)/double(primitiveCount)); + return buildProgress(userPtr,f); + }, + + morton_src.data(),morton_tmp.data(),primitiveCount, + *arguments); + + bvh->allocator.cleanup(); + return root.first; + } + + void* rtcBuildBVHBinnedSAH(const RTCBuildArguments* arguments) + { + BVH* bvh = (BVH*) arguments->bvh; + RTCBuildPrimitive* prims = arguments->primitives; + size_t primitiveCount = arguments->primitiveCount; + RTCCreateNodeFunction createNode = arguments->createNode; + RTCSetNodeChildrenFunction setNodeChildren = arguments->setNodeChildren; + RTCSetNodeBoundsFunction setNodeBounds = arguments->setNodeBounds; + RTCCreateLeafFunction createLeaf = arguments->createLeaf; + RTCProgressMonitorFunction buildProgress = arguments->buildProgress; + void* userPtr = arguments->userPtr; + + std::atomic progress(0); + + /* calculate priminfo */ + auto computeBounds = [&](const range& r) -> CentGeomBBox3fa + { + CentGeomBBox3fa bounds(empty); + for (size_t j=r.begin(); j( + + /* thread local allocator for fast allocations */ + [&] () -> FastAllocator::CachedAllocator { + return bvh->allocator.getCachedAllocator(); + }, + + /* lambda function that creates BVH nodes */ + [&](BVHBuilderBinnedSAH::BuildRecord* children, const size_t N, const FastAllocator::CachedAllocator& alloc) -> void* + { + void* node = createNode((RTCThreadLocalAllocator)&alloc, (unsigned int)N,userPtr); + const RTCBounds* cbounds[GeneralBVHBuilder::MAX_BRANCHING_FACTOR]; + for (size_t i=0; i void* { + setNodeChildren(node,children, (unsigned int)N,userPtr); + return node; + }, + + /* lambda function that creates BVH leaves */ + [&](const PrimRef* prims, const range& range, const FastAllocator::CachedAllocator& alloc) -> void* { + return createLeaf((RTCThreadLocalAllocator)&alloc,(RTCBuildPrimitive*)(prims+range.begin()),range.size(),userPtr); + }, + + /* progress monitor function */ + [&] (size_t dn) { + if (!buildProgress) return true; + const size_t n = progress.fetch_add(dn)+dn; + const double f = std::min(1.0,double(n)/double(primitiveCount)); + return buildProgress(userPtr,f); + }, + + (PrimRef*)prims,pinfo,*arguments); + + bvh->allocator.cleanup(); + return root; + } + + static __forceinline const std::pair mergePair(const std::pair& a, const std::pair& b) { + CentGeomBBox3fa centBounds = CentGeomBBox3fa::merge2(a.first,b.first); + unsigned int maxGeomID = max(a.second,b.second); + return std::pair(centBounds,maxGeomID); + } + + void* rtcBuildBVHSpatialSAH(const RTCBuildArguments* arguments) + { + BVH* bvh = (BVH*) arguments->bvh; + RTCBuildPrimitive* prims = arguments->primitives; + size_t primitiveCount = arguments->primitiveCount; + RTCCreateNodeFunction createNode = arguments->createNode; + RTCSetNodeChildrenFunction setNodeChildren = arguments->setNodeChildren; + RTCSetNodeBoundsFunction setNodeBounds = arguments->setNodeBounds; + RTCCreateLeafFunction createLeaf = arguments->createLeaf; + RTCSplitPrimitiveFunction splitPrimitive = arguments->splitPrimitive; + RTCProgressMonitorFunction buildProgress = arguments->buildProgress; + void* userPtr = arguments->userPtr; + + std::atomic progress(0); + + /* calculate priminfo */ + + auto computeBounds = [&](const range& r) -> std::pair + { + CentGeomBBox3fa bounds(empty); + unsigned maxGeomID = 0; + for (size_t j=r.begin(); j(bounds,maxGeomID); + }; + + + const std::pair pair = + parallel_reduce(size_t(0),primitiveCount,size_t(1024),size_t(1024),std::pair(CentGeomBBox3fa(empty),0), computeBounds, mergePair); + + CentGeomBBox3fa bounds = pair.first; + const unsigned int maxGeomID = pair.second; + + if (unlikely(maxGeomID >= ((unsigned int)1 << (32-RESERVED_NUM_SPATIAL_SPLITS_GEOMID_BITS)))) + { + /* fallback code for max geomID larger than threshold */ + return rtcBuildBVHBinnedSAH(arguments); + } + + const PrimInfo pinfo(0,primitiveCount,bounds); + + /* function that splits a build primitive */ + struct Splitter + { + Splitter (RTCSplitPrimitiveFunction splitPrimitive, unsigned geomID, unsigned primID, void* userPtr) + : splitPrimitive(splitPrimitive), geomID(geomID), primID(primID), userPtr(userPtr) {} + + __forceinline void operator() (PrimRef& prim, const size_t dim, const float pos, PrimRef& left_o, PrimRef& right_o) const + { + prim.geomIDref() &= BVHBuilderBinnedFastSpatialSAH::GEOMID_MASK; + splitPrimitive((RTCBuildPrimitive*)&prim,(unsigned)dim,pos,(RTCBounds*)&left_o,(RTCBounds*)&right_o,userPtr); + left_o.geomIDref() = geomID; left_o.primIDref() = primID; + right_o.geomIDref() = geomID; right_o.primIDref() = primID; + } + + __forceinline void operator() (const BBox3fa& box, const size_t dim, const float pos, BBox3fa& left_o, BBox3fa& right_o) const + { + PrimRef prim(box,geomID & BVHBuilderBinnedFastSpatialSAH::GEOMID_MASK,primID); + splitPrimitive((RTCBuildPrimitive*)&prim,(unsigned)dim,pos,(RTCBounds*)&left_o,(RTCBounds*)&right_o,userPtr); + } + + RTCSplitPrimitiveFunction splitPrimitive; + unsigned geomID; + unsigned primID; + void* userPtr; + }; + + /* build BVH */ + void* root = BVHBuilderBinnedFastSpatialSAH::build( + + /* thread local allocator for fast allocations */ + [&] () -> FastAllocator::CachedAllocator { + return bvh->allocator.getCachedAllocator(); + }, + + /* lambda function that creates BVH nodes */ + [&] (BVHBuilderBinnedFastSpatialSAH::BuildRecord* children, const size_t N, const FastAllocator::CachedAllocator& alloc) -> void* + { + void* node = createNode((RTCThreadLocalAllocator)&alloc, (unsigned int)N,userPtr); + const RTCBounds* cbounds[GeneralBVHBuilder::MAX_BRANCHING_FACTOR]; + for (size_t i=0; i void* { + setNodeChildren(node,children, (unsigned int)N,userPtr); + return node; + }, + + /* lambda function that creates BVH leaves */ + [&] (const PrimRef* prims, const range& range, const FastAllocator::CachedAllocator& alloc) -> void* { + return createLeaf((RTCThreadLocalAllocator)&alloc,(RTCBuildPrimitive*)(prims+range.begin()),range.size(),userPtr); + }, + + /* returns the splitter */ + [&] ( const PrimRef& prim ) -> Splitter { + return Splitter(splitPrimitive,prim.geomID(),prim.primID(),userPtr); + }, + + /* progress monitor function */ + [&] (size_t dn) { + if (!buildProgress) return true; + const size_t n = progress.fetch_add(dn)+dn; + const double f = std::min(1.0,double(n)/double(primitiveCount)); + return buildProgress(userPtr,f); + }, + + (PrimRef*)prims, + arguments->primitiveArrayCapacity, + pinfo,*arguments); + + bvh->allocator.cleanup(); + return root; + } + } +} + +using namespace embree; +using namespace embree::isa; + +RTC_NAMESPACE_BEGIN + + RTC_API RTCBVH rtcNewBVH(RTCDevice device) + { + RTC_CATCH_BEGIN; + RTC_TRACE(rtcNewAllocator); + RTC_VERIFY_HANDLE(device); + BVH* bvh = new BVH((Device*)device); + return (RTCBVH) bvh->refInc(); + RTC_CATCH_END((Device*)device); + return nullptr; + } + + RTC_API void* rtcBuildBVH(const RTCBuildArguments* arguments) + { + BVH* bvh = (BVH*) arguments->bvh; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcBuildBVH); + RTC_VERIFY_HANDLE(bvh); + RTC_VERIFY_HANDLE(arguments); + RTC_VERIFY_HANDLE(arguments->createNode); + RTC_VERIFY_HANDLE(arguments->setNodeChildren); + RTC_VERIFY_HANDLE(arguments->setNodeBounds); + RTC_VERIFY_HANDLE(arguments->createLeaf); + + if (arguments->primitiveArrayCapacity < arguments->primitiveCount) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"primitiveArrayCapacity must be greater or equal to primitiveCount") + + /* initialize the allocator */ + bvh->allocator.init_estimate(arguments->primitiveCount*sizeof(BBox3fa)); + bvh->allocator.reset(); + + /* switch between differnet builders based on quality level */ + if (arguments->buildQuality == RTC_BUILD_QUALITY_LOW) + return rtcBuildBVHMorton(arguments); + else if (arguments->buildQuality == RTC_BUILD_QUALITY_MEDIUM) + return rtcBuildBVHBinnedSAH(arguments); + else if (arguments->buildQuality == RTC_BUILD_QUALITY_HIGH) { + if (arguments->splitPrimitive == nullptr || arguments->primitiveArrayCapacity <= arguments->primitiveCount) + return rtcBuildBVHBinnedSAH(arguments); + else + return rtcBuildBVHSpatialSAH(arguments); + } + else + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"invalid build quality"); + + /* if we are in dynamic mode, then do not clear temporary data */ + if (!(arguments->buildFlags & RTC_BUILD_FLAG_DYNAMIC)) + { + bvh->morton_src.clear(); + bvh->morton_tmp.clear(); + } + + RTC_CATCH_END(bvh->device); + return nullptr; + } + + RTC_API void* rtcThreadLocalAlloc(RTCThreadLocalAllocator localAllocator, size_t bytes, size_t align) + { + FastAllocator::CachedAllocator* alloc = (FastAllocator::CachedAllocator*) localAllocator; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcThreadLocalAlloc); + return alloc->malloc0(bytes,align); + RTC_CATCH_END(alloc->alloc->getDevice()); + return nullptr; + } + + RTC_API void rtcMakeStaticBVH(RTCBVH hbvh) + { + BVH* bvh = (BVH*) hbvh; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcStaticBVH); + RTC_VERIFY_HANDLE(hbvh); + bvh->morton_src.clear(); + bvh->morton_tmp.clear(); + RTC_CATCH_END(bvh->device); + } + + RTC_API void rtcRetainBVH(RTCBVH hbvh) + { + BVH* bvh = (BVH*) hbvh; + Device* device = bvh ? bvh->device : nullptr; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcRetainBVH); + RTC_VERIFY_HANDLE(hbvh); + bvh->refInc(); + RTC_CATCH_END(device); + } + + RTC_API void rtcReleaseBVH(RTCBVH hbvh) + { + BVH* bvh = (BVH*) hbvh; + Device* device = bvh ? bvh->device : nullptr; + RTC_CATCH_BEGIN; + RTC_TRACE(rtcReleaseBVH); + RTC_VERIFY_HANDLE(hbvh); + bvh->refDec(); + RTC_CATCH_END(device); + } + +RTC_NAMESPACE_END diff --git a/thirdparty/embree/kernels/common/scene.cpp b/thirdparty/embree/kernels/common/scene.cpp new file mode 100644 index 000000000000..b0d1a723d12e --- /dev/null +++ b/thirdparty/embree/kernels/common/scene.cpp @@ -0,0 +1,953 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "scene.h" + +#include "../bvh/bvh4_factory.h" +#include "../bvh/bvh8_factory.h" +#include "../../common/algorithms/parallel_reduce.h" + +namespace embree +{ + /* error raising rtcIntersect and rtcOccluded functions */ + void missing_rtcCommit() { throw_RTCError(RTC_ERROR_INVALID_OPERATION,"scene not committed"); } + void invalid_rtcIntersect1() { throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersect and rtcOccluded not enabled"); } + void invalid_rtcIntersect4() { throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersect4 and rtcOccluded4 not enabled"); } + void invalid_rtcIntersect8() { throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersect8 and rtcOccluded8 not enabled"); } + void invalid_rtcIntersect16() { throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersect16 and rtcOccluded16 not enabled"); } + void invalid_rtcIntersectN() { throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcIntersectN and rtcOccludedN not enabled"); } + + Scene::Scene (Device* device) + : device(device), + flags_modified(true), enabled_geometry_types(0), + scene_flags(RTC_SCENE_FLAG_NONE), + quality_flags(RTC_BUILD_QUALITY_MEDIUM), + is_build(false), modified(true), + progressInterface(this), progress_monitor_function(nullptr), progress_monitor_ptr(nullptr), progress_monitor_counter(0) + { + device->refInc(); + + intersectors = Accel::Intersectors(missing_rtcCommit); + + /* one can overwrite flags through device for debugging */ + if (device->quality_flags != -1) + quality_flags = (RTCBuildQuality) device->quality_flags; + if (device->scene_flags != -1) + scene_flags = (RTCSceneFlags) device->scene_flags; + } + + Scene::~Scene() noexcept + { + device->refDec(); + } + + void Scene::printStatistics() + { + /* calculate maximum number of time segments */ + unsigned max_time_steps = 0; + for (size_t i=0; inumTimeSteps); + } + + /* initialize vectors*/ + std::vector statistics[Geometry::GTY_END]; + for (size_t i=0; igetType(); + assert(tynumTimeSegments(); + assert((unsigned int)timesegments < max_time_steps); + statistics[ty][timesegments] += get(i)->size(); + } + + /* print statistics */ + std::cout << std::setw(23) << "segments" << ": "; + for (size_t t=0; ttri_accel == "default") + { + if (quality_flags != RTC_BUILD_QUALITY_LOW) + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + { + if (quality_flags == RTC_BUILD_QUALITY_HIGH) + accels_add(device->bvh8_factory->BVH8Triangle4(this,BVHFactory::BuildVariant::HIGH_QUALITY,BVHFactory::IntersectVariant::FAST)); + else + accels_add(device->bvh8_factory->BVH8Triangle4(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); + } + else +#endif + { + if (quality_flags == RTC_BUILD_QUALITY_HIGH) + accels_add(device->bvh4_factory->BVH4Triangle4(this,BVHFactory::BuildVariant::HIGH_QUALITY,BVHFactory::IntersectVariant::FAST)); + else + accels_add(device->bvh4_factory->BVH4Triangle4(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); + } + break; + + case /*0b01*/ 1: +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + accels_add(device->bvh8_factory->BVH8Triangle4v(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); + else +#endif + accels_add(device->bvh4_factory->BVH4Triangle4v(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); + + break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Triangle4i(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Triangle4i(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else /* dynamic */ + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh8_factory->BVH8Triangle4 (this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b01*/ 1: accels_add(device->bvh8_factory->BVH8Triangle4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Triangle4i(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Triangle4i(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else +#endif + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh4_factory->BVH4Triangle4 (this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b01*/ 1: accels_add(device->bvh4_factory->BVH4Triangle4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Triangle4i(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Triangle4i(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + } + } + else if (device->tri_accel == "bvh4.triangle4") accels_add(device->bvh4_factory->BVH4Triangle4 (this)); + else if (device->tri_accel == "bvh4.triangle4v") accels_add(device->bvh4_factory->BVH4Triangle4v(this)); + else if (device->tri_accel == "bvh4.triangle4i") accels_add(device->bvh4_factory->BVH4Triangle4i(this)); + else if (device->tri_accel == "qbvh4.triangle4i") accels_add(device->bvh4_factory->BVH4QuantizedTriangle4i(this)); + +#if defined (EMBREE_TARGET_SIMD8) + else if (device->tri_accel == "bvh8.triangle4") accels_add(device->bvh8_factory->BVH8Triangle4 (this)); + else if (device->tri_accel == "bvh8.triangle4v") accels_add(device->bvh8_factory->BVH8Triangle4v(this)); + else if (device->tri_accel == "bvh8.triangle4i") accels_add(device->bvh8_factory->BVH8Triangle4i(this)); + else if (device->tri_accel == "qbvh8.triangle4i") accels_add(device->bvh8_factory->BVH8QuantizedTriangle4i(this)); + else if (device->tri_accel == "qbvh8.triangle4") accels_add(device->bvh8_factory->BVH8QuantizedTriangle4(this)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown triangle acceleration structure "+device->tri_accel); +#endif + } + + void Scene::createTriangleMBAccel() + { +#if defined(EMBREE_GEOMETRY_TRIANGLE) + if (device->tri_accel_mb == "default") + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX2()) // BVH8 reduces performance on AVX only-machines + { + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh8_factory->BVH8Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b01*/ 1: accels_add(device->bvh8_factory->BVH8Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else +#endif + { + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh4_factory->BVH4Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b01*/ 1: accels_add(device->bvh4_factory->BVH4Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Triangle4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + } + else if (device->tri_accel_mb == "bvh4.triangle4imb") accels_add(device->bvh4_factory->BVH4Triangle4iMB(this)); + else if (device->tri_accel_mb == "bvh4.triangle4vmb") accels_add(device->bvh4_factory->BVH4Triangle4vMB(this)); +#if defined (EMBREE_TARGET_SIMD8) + else if (device->tri_accel_mb == "bvh8.triangle4imb") accels_add(device->bvh8_factory->BVH8Triangle4iMB(this)); + else if (device->tri_accel_mb == "bvh8.triangle4vmb") accels_add(device->bvh8_factory->BVH8Triangle4vMB(this)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown motion blur triangle acceleration structure "+device->tri_accel_mb); +#endif + } + + void Scene::createQuadAccel() + { +#if defined(EMBREE_GEOMETRY_QUAD) + if (device->quad_accel == "default") + { + if (quality_flags != RTC_BUILD_QUALITY_LOW) + { + /* static */ + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + { + if (quality_flags == RTC_BUILD_QUALITY_HIGH) + accels_add(device->bvh8_factory->BVH8Quad4v(this,BVHFactory::BuildVariant::HIGH_QUALITY,BVHFactory::IntersectVariant::FAST)); + else + accels_add(device->bvh8_factory->BVH8Quad4v(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); + } + else +#endif + { + if (quality_flags == RTC_BUILD_QUALITY_HIGH) + accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::HIGH_QUALITY,BVHFactory::IntersectVariant::FAST)); + else + accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); + } + break; + + case /*0b01*/ 1: +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + accels_add(device->bvh8_factory->BVH8Quad4v(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); + else +#endif + accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); + break; + + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Quad4i(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Quad4i(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else /* dynamic */ + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh8_factory->BVH8Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST)); break; + case /*0b01*/ 1: accels_add(device->bvh8_factory->BVH8Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST)); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else +#endif + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST)); break; + case /*0b01*/ 1: accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::FAST)); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Quad4v(this,BVHFactory::BuildVariant::DYNAMIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + } + } + else if (device->quad_accel == "bvh4.quad4v") accels_add(device->bvh4_factory->BVH4Quad4v(this)); + else if (device->quad_accel == "bvh4.quad4i") accels_add(device->bvh4_factory->BVH4Quad4i(this)); + else if (device->quad_accel == "qbvh4.quad4i") accels_add(device->bvh4_factory->BVH4QuantizedQuad4i(this)); + +#if defined (EMBREE_TARGET_SIMD8) + else if (device->quad_accel == "bvh8.quad4v") accels_add(device->bvh8_factory->BVH8Quad4v(this)); + else if (device->quad_accel == "bvh8.quad4i") accels_add(device->bvh8_factory->BVH8Quad4i(this)); + else if (device->quad_accel == "qbvh8.quad4i") accels_add(device->bvh8_factory->BVH8QuantizedQuad4i(this)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown quad acceleration structure "+device->quad_accel); +#endif + } + + void Scene::createQuadMBAccel() + { +#if defined(EMBREE_GEOMETRY_QUAD) + if (device->quad_accel_mb == "default") + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); + switch (mode) { + case /*0b00*/ 0: +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + accels_add(device->bvh8_factory->BVH8Quad4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); + else +#endif + accels_add(device->bvh4_factory->BVH4Quad4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST)); + break; + + case /*0b01*/ 1: +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX()) + accels_add(device->bvh8_factory->BVH8Quad4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); + else +#endif + accels_add(device->bvh4_factory->BVH4Quad4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); + break; + + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4Quad4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::FAST )); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4Quad4iMB(this,BVHFactory::BuildVariant::STATIC,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else if (device->quad_accel_mb == "bvh4.quad4imb") accels_add(device->bvh4_factory->BVH4Quad4iMB(this)); +#if defined (EMBREE_TARGET_SIMD8) + else if (device->quad_accel_mb == "bvh8.quad4imb") accels_add(device->bvh8_factory->BVH8Quad4iMB(this)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown quad motion blur acceleration structure "+device->quad_accel_mb); +#endif + } + + void Scene::createHairAccel() + { +#if defined(EMBREE_GEOMETRY_CURVE) || defined(EMBREE_GEOMETRY_POINT) + if (device->hair_accel == "default") + { + int mode = 2*(int)isCompactAccel() + 1*(int)isRobustAccel(); +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX2()) // only enable on HSW machines, for SNB this codepath is slower + { + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh8_factory->BVH8OBBVirtualCurve8v(this,BVHFactory::IntersectVariant::FAST)); break; + case /*0b01*/ 1: accels_add(device->bvh8_factory->BVH8OBBVirtualCurve8v(this,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4OBBVirtualCurve8i(this,BVHFactory::IntersectVariant::FAST)); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4OBBVirtualCurve8i(this,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + else +#endif + { + switch (mode) { + case /*0b00*/ 0: accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4v(this,BVHFactory::IntersectVariant::FAST)); break; + case /*0b01*/ 1: accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4v(this,BVHFactory::IntersectVariant::ROBUST)); break; + case /*0b10*/ 2: accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4i(this,BVHFactory::IntersectVariant::FAST)); break; + case /*0b11*/ 3: accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4i(this,BVHFactory::IntersectVariant::ROBUST)); break; + } + } + } + else if (device->hair_accel == "bvh4obb.virtualcurve4v" ) accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4v(this,BVHFactory::IntersectVariant::FAST)); + else if (device->hair_accel == "bvh4obb.virtualcurve4i" ) accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4i(this,BVHFactory::IntersectVariant::FAST)); +#if defined (EMBREE_TARGET_SIMD8) + else if (device->hair_accel == "bvh8obb.virtualcurve8v" ) accels_add(device->bvh8_factory->BVH8OBBVirtualCurve8v(this,BVHFactory::IntersectVariant::FAST)); + else if (device->hair_accel == "bvh4obb.virtualcurve8i" ) accels_add(device->bvh4_factory->BVH4OBBVirtualCurve8i(this,BVHFactory::IntersectVariant::FAST)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown hair acceleration structure "+device->hair_accel); +#endif + } + + void Scene::createHairMBAccel() + { +#if defined(EMBREE_GEOMETRY_CURVE) || defined(EMBREE_GEOMETRY_POINT) + if (device->hair_accel_mb == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX2()) // only enable on HSW machines, on SNB this codepath is slower + { + if (isRobustAccel()) accels_add(device->bvh8_factory->BVH8OBBVirtualCurve8iMB(this,BVHFactory::IntersectVariant::ROBUST)); + else accels_add(device->bvh8_factory->BVH8OBBVirtualCurve8iMB(this,BVHFactory::IntersectVariant::FAST)); + } + else +#endif + { + if (isRobustAccel()) accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4iMB(this,BVHFactory::IntersectVariant::ROBUST)); + else accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4iMB(this,BVHFactory::IntersectVariant::FAST)); + } + } + else if (device->hair_accel_mb == "bvh4.virtualcurve4imb") accels_add(device->bvh4_factory->BVH4OBBVirtualCurve4iMB(this,BVHFactory::IntersectVariant::FAST)); + +#if defined (EMBREE_TARGET_SIMD8) + else if (device->hair_accel_mb == "bvh4.virtualcurve8imb") accels_add(device->bvh4_factory->BVH4OBBVirtualCurve8iMB(this,BVHFactory::IntersectVariant::FAST)); + else if (device->hair_accel_mb == "bvh8.virtualcurve8imb") accels_add(device->bvh8_factory->BVH8OBBVirtualCurve8iMB(this,BVHFactory::IntersectVariant::FAST)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown motion blur hair acceleration structure "+device->hair_accel_mb); +#endif + } + + void Scene::createSubdivAccel() + { +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + if (device->subdiv_accel == "default") { + accels_add(device->bvh4_factory->BVH4SubdivPatch1(this)); + } + else if (device->subdiv_accel == "bvh4.grid.eager" ) accels_add(device->bvh4_factory->BVH4SubdivPatch1(this)); + else if (device->subdiv_accel == "bvh4.subdivpatch1eager" ) accels_add(device->bvh4_factory->BVH4SubdivPatch1(this)); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown subdiv accel "+device->subdiv_accel); +#endif + } + + void Scene::createSubdivMBAccel() + { +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + if (device->subdiv_accel_mb == "default") { + accels_add(device->bvh4_factory->BVH4SubdivPatch1MB(this)); + } + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown subdiv mblur accel "+device->subdiv_accel_mb); +#endif + } + + void Scene::createUserGeometryAccel() + { +#if defined(EMBREE_GEOMETRY_USER) + if (device->object_accel == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) + { + if (quality_flags != RTC_BUILD_QUALITY_LOW) { + accels_add(device->bvh8_factory->BVH8UserGeometry(this,BVHFactory::BuildVariant::STATIC)); + } else { + accels_add(device->bvh8_factory->BVH8UserGeometry(this,BVHFactory::BuildVariant::DYNAMIC)); + } + } + else +#endif + { + if (quality_flags != RTC_BUILD_QUALITY_LOW) { + accels_add(device->bvh4_factory->BVH4UserGeometry(this,BVHFactory::BuildVariant::STATIC)); + } else { + accels_add(device->bvh4_factory->BVH4UserGeometry(this,BVHFactory::BuildVariant::DYNAMIC)); + } + } + } + else if (device->object_accel == "bvh4.object") accels_add(device->bvh4_factory->BVH4UserGeometry(this)); +#if defined (EMBREE_TARGET_SIMD8) + else if (device->object_accel == "bvh8.object") accels_add(device->bvh8_factory->BVH8UserGeometry(this)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown user geometry accel "+device->object_accel); +#endif + } + + void Scene::createUserGeometryMBAccel() + { +#if defined(EMBREE_GEOMETRY_USER) + if (device->object_accel_mb == "default" ) { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) + accels_add(device->bvh8_factory->BVH8UserGeometryMB(this)); + else +#endif + accels_add(device->bvh4_factory->BVH4UserGeometryMB(this)); + } + else if (device->object_accel_mb == "bvh4.object") accels_add(device->bvh4_factory->BVH4UserGeometryMB(this)); +#if defined (EMBREE_TARGET_SIMD8) + else if (device->object_accel_mb == "bvh8.object") accels_add(device->bvh8_factory->BVH8UserGeometryMB(this)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown user geometry mblur accel "+device->object_accel_mb); +#endif + } + + void Scene::createInstanceAccel() + { +#if defined(EMBREE_GEOMETRY_INSTANCE) + // if (device->object_accel == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) { + if (quality_flags != RTC_BUILD_QUALITY_LOW) { + accels_add(device->bvh8_factory->BVH8Instance(this, false, BVHFactory::BuildVariant::STATIC)); + } else { + accels_add(device->bvh8_factory->BVH8Instance(this, false, BVHFactory::BuildVariant::DYNAMIC)); + } + } + else +#endif + { + if (quality_flags != RTC_BUILD_QUALITY_LOW) { + accels_add(device->bvh4_factory->BVH4Instance(this, false, BVHFactory::BuildVariant::STATIC)); + } else { + accels_add(device->bvh4_factory->BVH4Instance(this, false, BVHFactory::BuildVariant::DYNAMIC)); + } + } + } + // else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown instance accel "+device->instance_accel); +#endif + } + + void Scene::createInstanceMBAccel() + { +#if defined(EMBREE_GEOMETRY_INSTANCE) + //if (device->instance_accel_mb == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) + accels_add(device->bvh8_factory->BVH8InstanceMB(this, false)); + else +#endif + accels_add(device->bvh4_factory->BVH4InstanceMB(this, false)); + } + //else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown instance mblur accel "+device->instance_accel_mb); +#endif + } + + void Scene::createInstanceExpensiveAccel() + { +#if defined(EMBREE_GEOMETRY_INSTANCE) + // if (device->object_accel == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) { + if (quality_flags != RTC_BUILD_QUALITY_LOW) { + accels_add(device->bvh8_factory->BVH8Instance(this, true, BVHFactory::BuildVariant::STATIC)); + } else { + accels_add(device->bvh8_factory->BVH8Instance(this, true, BVHFactory::BuildVariant::DYNAMIC)); + } + } + else +#endif + { + if (quality_flags != RTC_BUILD_QUALITY_LOW) { + accels_add(device->bvh4_factory->BVH4Instance(this, true, BVHFactory::BuildVariant::STATIC)); + } else { + accels_add(device->bvh4_factory->BVH4Instance(this, true, BVHFactory::BuildVariant::DYNAMIC)); + } + } + } + // else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown instance accel "+device->instance_accel); +#endif + } + + void Scene::createInstanceExpensiveMBAccel() + { +#if defined(EMBREE_GEOMETRY_INSTANCE) + //if (device->instance_accel_mb == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) + accels_add(device->bvh8_factory->BVH8InstanceMB(this, true)); + else +#endif + accels_add(device->bvh4_factory->BVH4InstanceMB(this, true)); + } + //else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown instance mblur accel "+device->instance_accel_mb); +#endif + } + + void Scene::createGridAccel() + { + BVHFactory::IntersectVariant ivariant = isRobustAccel() ? BVHFactory::IntersectVariant::ROBUST : BVHFactory::IntersectVariant::FAST; +#if defined(EMBREE_GEOMETRY_GRID) + if (device->grid_accel == "default") + { +#if defined (EMBREE_TARGET_SIMD8) + if (device->canUseAVX() && !isCompactAccel()) + { + accels_add(device->bvh8_factory->BVH8Grid(this,BVHFactory::BuildVariant::STATIC,ivariant)); + } + else +#endif + { + accels_add(device->bvh4_factory->BVH4Grid(this,BVHFactory::BuildVariant::STATIC,ivariant)); + } + } + else if (device->grid_accel == "bvh4.grid") accels_add(device->bvh4_factory->BVH4Grid(this,BVHFactory::BuildVariant::STATIC,ivariant)); +#if defined (EMBREE_TARGET_SIMD8) + else if (device->grid_accel == "bvh8.grid") accels_add(device->bvh8_factory->BVH8Grid(this,BVHFactory::BuildVariant::STATIC,ivariant)); +#endif + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown grid accel "+device->grid_accel); +#endif + + } + + void Scene::createGridMBAccel() + { +#if defined(EMBREE_GEOMETRY_GRID) + if (device->grid_accel_mb == "default") + { + accels_add(device->bvh4_factory->BVH4GridMB(this,BVHFactory::BuildVariant::STATIC)); + } + else if (device->grid_accel_mb == "bvh4mb.grid") accels_add(device->bvh4_factory->BVH4GridMB(this)); + else throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"unknown grid mb accel "+device->grid_accel); +#endif + + } + + void Scene::clear() { + } + + unsigned Scene::bind(unsigned geomID, Ref geometry) + { + Lock lock(geometriesMutex); + if (geomID == RTC_INVALID_GEOMETRY_ID) { + geomID = id_pool.allocate(); + if (geomID == RTC_INVALID_GEOMETRY_ID) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"too many geometries inside scene"); + } + else + { + if (!id_pool.add(geomID)) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"invalid geometry ID provided"); + } + if (geomID >= geometries.size()) { + geometries.resize(geomID+1); + vertices.resize(geomID+1); + geometryModCounters_.resize(geomID+1); + } + geometries[geomID] = geometry; + geometryModCounters_[geomID] = 0; + if (geometry->isEnabled()) { + setModified (); + } + return geomID; + } + + void Scene::detachGeometry(size_t geomID) + { + Lock lock(geometriesMutex); + + if (geomID >= geometries.size()) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"invalid geometry ID"); + + Ref& geometry = geometries[geomID]; + if (geometry == null) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"invalid geometry"); + + if (geometry->isEnabled()) { + setModified (); + } + accels_deleteGeometry(unsigned(geomID)); + id_pool.deallocate((unsigned)geomID); + geometries[geomID] = null; + vertices[geomID] = nullptr; + geometryModCounters_[geomID] = 0; + } + + void Scene::updateInterface() + { + is_build = true; + } + + void Scene::commit_task () + { + checkIfModifiedAndSet (); + if (!isModified()) { + return; + } + + /* print scene statistics */ + if (device->verbosity(2)) + printStatistics(); + + progress_monitor_counter = 0; + + /* gather scene stats and call preCommit function of each geometry */ + this->world = parallel_reduce (size_t(0), geometries.size(), GeometryCounts (), + [this](const range& r)->GeometryCounts + { + GeometryCounts c; + for (auto i=r.begin(); iisEnabled()) + { + geometries[i]->preCommit(); + geometries[i]->addElementsToCount (c); + c.numFilterFunctions += (int) geometries[i]->hasFilterFunctions(); + } + } + return c; + }, + std::plus() + ); + + /* select acceleration structures to build */ + unsigned int new_enabled_geometry_types = world.enabledGeometryTypesMask(); + if (flags_modified || new_enabled_geometry_types != enabled_geometry_types) + { + accels_init(); + + /* we need to make all geometries modified, otherwise two level builder will + not rebuild currently not modified geometries */ + parallel_for(geometryModCounters_.size(), [&] ( const size_t i ) { + geometryModCounters_[i] = 0; + }); + + if (getNumPrimitives(TriangleMesh::geom_type,false)) createTriangleAccel(); + if (getNumPrimitives(TriangleMesh::geom_type,true)) createTriangleMBAccel(); + if (getNumPrimitives(QuadMesh::geom_type,false)) createQuadAccel(); + if (getNumPrimitives(QuadMesh::geom_type,true)) createQuadMBAccel(); + if (getNumPrimitives(GridMesh::geom_type,false)) createGridAccel(); + if (getNumPrimitives(GridMesh::geom_type,true)) createGridMBAccel(); + if (getNumPrimitives(SubdivMesh::geom_type,false)) createSubdivAccel(); + if (getNumPrimitives(SubdivMesh::geom_type,true)) createSubdivMBAccel(); + if (getNumPrimitives(Geometry::MTY_CURVES,false)) createHairAccel(); + if (getNumPrimitives(Geometry::MTY_CURVES,true)) createHairMBAccel(); + if (getNumPrimitives(UserGeometry::geom_type,false)) createUserGeometryAccel(); + if (getNumPrimitives(UserGeometry::geom_type,true)) createUserGeometryMBAccel(); + if (getNumPrimitives(Geometry::MTY_INSTANCE_CHEAP,false)) createInstanceAccel(); + if (getNumPrimitives(Geometry::MTY_INSTANCE_CHEAP,true)) createInstanceMBAccel(); + if (getNumPrimitives(Geometry::MTY_INSTANCE_EXPENSIVE,false)) createInstanceExpensiveAccel(); + if (getNumPrimitives(Geometry::MTY_INSTANCE_EXPENSIVE,true)) createInstanceExpensiveMBAccel(); + + flags_modified = false; + enabled_geometry_types = new_enabled_geometry_types; + } + + /* select fast code path if no filter function is present */ + accels_select(hasFilterFunction()); + + /* build all hierarchies of this scene */ + accels_build(); + + /* make static geometry immutable */ + if (!isDynamicAccel()) { + accels_immutable(); + flags_modified = true; // in non-dynamic mode we have to re-create accels + } + + /* call postCommit function of each geometry */ + parallel_for(geometries.size(), [&] ( const size_t i ) { + if (geometries[i] && geometries[i]->isEnabled()) { + geometries[i]->postCommit(); + vertices[i] = geometries[i]->getCompactVertexArray(); + geometryModCounters_[i] = geometries[i]->getModCounter(); + } + }); + + updateInterface(); + + if (device->verbosity(2)) { + std::cout << "created scene intersector" << std::endl; + accels_print(2); + std::cout << "selected scene intersector" << std::endl; + intersectors.print(2); + } + + setModified(false); + } + + void Scene::setBuildQuality(RTCBuildQuality quality_flags_i) + { + if (quality_flags == quality_flags_i) return; + quality_flags = quality_flags_i; + flags_modified = true; + } + + RTCBuildQuality Scene::getBuildQuality() const { + return quality_flags; + } + + void Scene::setSceneFlags(RTCSceneFlags scene_flags_i) + { + if (scene_flags == scene_flags_i) return; + scene_flags = scene_flags_i; + flags_modified = true; + } + + RTCSceneFlags Scene::getSceneFlags() const { + return scene_flags; + } + +#if defined(TASKING_INTERNAL) + + void Scene::commit (bool join) + { + Lock buildLock(buildMutex,false); + + /* allocates own taskscheduler for each build */ + Ref scheduler = nullptr; + { + Lock lock(schedulerMutex); + scheduler = this->scheduler; + if (scheduler == null) { + buildLock.lock(); + this->scheduler = scheduler = new TaskScheduler; + } + } + + /* worker threads join build */ + if (!buildLock.isLocked()) + { + if (!join) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"use rtcJoinCommitScene to join a build operation"); + + scheduler->join(); + return; + } + + /* initiate build */ + try { + scheduler->spawn_root([&]() { commit_task(); Lock lock(schedulerMutex); this->scheduler = nullptr; }, 1, !join); + } + catch (...) { + accels_clear(); + updateInterface(); + Lock lock(schedulerMutex); + this->scheduler = nullptr; + throw; + } + } + +#endif + +#if defined(TASKING_TBB) + + void Scene::commit (bool join) + { +#if defined(TASKING_TBB) && (TBB_INTERFACE_VERSION_MAJOR < 8) + if (join) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcJoinCommitScene not supported with this TBB version"); +#endif + + /* try to obtain build lock */ + Lock lock(buildMutex,buildMutex.try_lock()); + + /* join hierarchy build */ + if (!lock.isLocked()) + { +#if !TASKING_TBB_USE_TASK_ISOLATION + if (!join) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"invoking rtcCommitScene from multiple threads is not supported with this TBB version"); +#endif + + do { + +#if USE_TASK_ARENA + if (join) { + device->arena->execute([&]{ group.wait(); }); + } + else +#endif + { + group.wait(); + } + + pause_cpu(); + yield(); + } while (!buildMutex.try_lock()); + + buildMutex.unlock(); + return; + } + + /* for best performance set FTZ and DAZ flags in the MXCSR control and status register */ + const unsigned int mxcsr = _mm_getcsr(); + _mm_setcsr(mxcsr | /* FTZ */ (1<<15) | /* DAZ */ (1<<6)); + + try { +#if TBB_INTERFACE_VERSION_MAJOR < 8 + tbb::task_group_context ctx( tbb::task_group_context::isolated, tbb::task_group_context::default_traits); +#else + tbb::task_group_context ctx( tbb::task_group_context::isolated, tbb::task_group_context::default_traits | tbb::task_group_context::fp_settings ); +#endif + //ctx.set_priority(tbb::priority_high); + +#if USE_TASK_ARENA + if (join) + { + device->arena->execute([&]{ + group.run([&]{ + tbb::parallel_for (size_t(0), size_t(1), size_t(1), [&] (size_t) { commit_task(); }, ctx); + }); + group.wait(); + }); + } + else +#endif + { + group.run([&]{ + tbb::parallel_for (size_t(0), size_t(1), size_t(1), [&] (size_t) { commit_task(); }, ctx); + }); + group.wait(); + } + + /* reset MXCSR register again */ + _mm_setcsr(mxcsr); + } + catch (...) + { + /* reset MXCSR register again */ + _mm_setcsr(mxcsr); + + accels_clear(); + updateInterface(); + throw; + } + } +#endif + +#if defined(TASKING_PPL) + + void Scene::commit (bool join) + { +#if defined(TASKING_PPL) + if (join) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"rtcJoinCommitScene not supported with PPL"); +#endif + + /* try to obtain build lock */ + Lock lock(buildMutex); + + checkIfModifiedAndSet (); + if (!isModified()) { + return; + } + + /* for best performance set FTZ and DAZ flags in the MXCSR control and status register */ + const unsigned int mxcsr = _mm_getcsr(); + _mm_setcsr(mxcsr | /* FTZ */ (1<<15) | /* DAZ */ (1<<6)); + + try { + + group.run([&]{ + concurrency::parallel_for(size_t(0), size_t(1), size_t(1), [&](size_t) { commit_task(); }); + }); + group.wait(); + + /* reset MXCSR register again */ + _mm_setcsr(mxcsr); + } + catch (...) + { + /* reset MXCSR register again */ + _mm_setcsr(mxcsr); + + accels_clear(); + updateInterface(); + throw; + } + } +#endif + + void Scene::setProgressMonitorFunction(RTCProgressMonitorFunction func, void* ptr) + { + progress_monitor_function = func; + progress_monitor_ptr = ptr; + } + + void Scene::progressMonitor(double dn) + { + if (progress_monitor_function) { + size_t n = size_t(dn) + progress_monitor_counter.fetch_add(size_t(dn)); + if (!progress_monitor_function(progress_monitor_ptr, n / (double(numPrimitives())))) { + throw_RTCError(RTC_ERROR_CANCELLED,"progress monitor forced termination"); + } + } + } +} diff --git a/thirdparty/embree/kernels/common/scene.h b/thirdparty/embree/kernels/common/scene.h new file mode 100644 index 000000000000..b41c6cde9134 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene.h @@ -0,0 +1,390 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "device.h" +#include "builder.h" +#include "../../common/algorithms/parallel_any_of.h" +#include "scene_triangle_mesh.h" +#include "scene_quad_mesh.h" +#include "scene_user_geometry.h" +#include "scene_instance.h" +#include "scene_curves.h" +#include "scene_line_segments.h" +#include "scene_subdiv_mesh.h" +#include "scene_grid_mesh.h" +#include "scene_points.h" +#include "../subdiv/tessellation_cache.h" + +#include "acceln.h" +#include "geometry.h" + +namespace embree +{ + /*! Base class all scenes are derived from */ + class Scene : public AccelN + { + ALIGNED_CLASS_(std::alignment_of::value); + + public: + template + class Iterator + { + public: + Iterator () {} + + Iterator (Scene* scene, bool all = false) + : scene(scene), all(all) {} + + __forceinline Ty* at(const size_t i) + { + Geometry* geom = scene->geometries[i].ptr; + if (geom == nullptr) return nullptr; + if (!all && !geom->isEnabled()) return nullptr; + const size_t mask = geom->getTypeMask() & Ty::geom_type; + if (!(mask)) return nullptr; + if ((geom->numTimeSteps != 1) != mblur) return nullptr; + return (Ty*) geom; + } + + __forceinline Ty* operator[] (const size_t i) { + return at(i); + } + + __forceinline size_t size() const { + return scene->size(); + } + + __forceinline size_t numPrimitives() const { + return scene->getNumPrimitives(Ty::geom_type,mblur); + } + + __forceinline size_t maxPrimitivesPerGeometry() + { + size_t ret = 0; + for (size_t i=0; isize(); i++) { + Ty* mesh = at(i); + if (mesh == nullptr) continue; + ret = max(ret,mesh->size()); + } + return ret; + } + + __forceinline unsigned int maxGeomID() + { + unsigned int ret = 0; + for (size_t i=0; isize(); i++) { + Ty* mesh = at(i); + if (mesh == nullptr) continue; + ret = max(ret,(unsigned int)i); + } + return ret; + } + + __forceinline unsigned maxTimeStepsPerGeometry() + { + unsigned ret = 0; + for (size_t i=0; isize(); i++) { + Ty* mesh = at(i); + if (mesh == nullptr) continue; + ret = max(ret,mesh->numTimeSteps); + } + return ret; + } + + private: + Scene* scene; + bool all; + }; + + class Iterator2 + { + public: + Iterator2 () {} + + Iterator2 (Scene* scene, Geometry::GTypeMask typemask, bool mblur) + : scene(scene), typemask(typemask), mblur(mblur) {} + + __forceinline Geometry* at(const size_t i) + { + Geometry* geom = scene->geometries[i].ptr; + if (geom == nullptr) return nullptr; + if (!geom->isEnabled()) return nullptr; + if (!(geom->getTypeMask() & typemask)) return nullptr; + if ((geom->numTimeSteps != 1) != mblur) return nullptr; + return geom; + } + + __forceinline Geometry* operator[] (const size_t i) { + return at(i); + } + + __forceinline size_t size() const { + return scene->size(); + } + + private: + Scene* scene; + Geometry::GTypeMask typemask; + bool mblur; + }; + + public: + + /*! Scene construction */ + Scene (Device* device); + + /*! Scene destruction */ + ~Scene () noexcept; + + private: + /*! class is non-copyable */ + Scene (const Scene& other) DELETED; // do not implement + Scene& operator= (const Scene& other) DELETED; // do not implement + + public: + void createTriangleAccel(); + void createTriangleMBAccel(); + void createQuadAccel(); + void createQuadMBAccel(); + void createHairAccel(); + void createHairMBAccel(); + void createSubdivAccel(); + void createSubdivMBAccel(); + void createUserGeometryAccel(); + void createUserGeometryMBAccel(); + void createInstanceAccel(); + void createInstanceMBAccel(); + void createInstanceExpensiveAccel(); + void createInstanceExpensiveMBAccel(); + void createGridAccel(); + void createGridMBAccel(); + + /*! prints statistics about the scene */ + void printStatistics(); + + /*! clears the scene */ + void clear(); + + /*! detaches some geometry */ + void detachGeometry(size_t geomID); + + void setBuildQuality(RTCBuildQuality quality_flags); + RTCBuildQuality getBuildQuality() const; + + void setSceneFlags(RTCSceneFlags scene_flags); + RTCSceneFlags getSceneFlags() const; + + void commit (bool join); + void commit_task (); + void build () {} + + void updateInterface(); + + /* return number of geometries */ + __forceinline size_t size() const { return geometries.size(); } + + /* bind geometry to the scene */ + unsigned int bind (unsigned geomID, Ref geometry); + + /* determines if scene is modified */ + __forceinline bool isModified() const { return modified; } + + /* sets modified flag */ + __forceinline void setModified(bool f = true) { + modified = f; + } + + __forceinline bool isGeometryModified(size_t geomID) + { + Ref& g = geometries[geomID]; + if (!g) return false; + return g->getModCounter() > geometryModCounters_[geomID]; + } + + protected: + + __forceinline void checkIfModifiedAndSet () + { + if (isModified ()) return; + + auto geometryIsModified = [this](size_t geomID)->bool { + return isGeometryModified(geomID); + }; + + if (parallel_any_of (size_t(0), geometries.size (), geometryIsModified)) { + setModified (); + } + } + + public: + + /* get mesh by ID */ + __forceinline Geometry* get(size_t i) { assert(i < geometries.size()); return geometries[i].ptr; } + __forceinline const Geometry* get(size_t i) const { assert(i < geometries.size()); return geometries[i].ptr; } + + template + __forceinline Mesh* get(size_t i) { + assert(i < geometries.size()); + assert(geometries[i]->getTypeMask() & Mesh::geom_type); + return (Mesh*)geometries[i].ptr; + } + template + __forceinline const Mesh* get(size_t i) const { + assert(i < geometries.size()); + assert(geometries[i]->getTypeMask() & Mesh::geom_type); + return (Mesh*)geometries[i].ptr; + } + + template + __forceinline Mesh* getSafe(size_t i) { + assert(i < geometries.size()); + if (geometries[i] == null) return nullptr; + if (!(geometries[i]->getTypeMask() & Mesh::geom_type)) return nullptr; + else return (Mesh*) geometries[i].ptr; + } + + __forceinline Ref get_locked(size_t i) { + Lock lock(geometriesMutex); + assert(i < geometries.size()); + return geometries[i]; + } + + /* flag decoding */ + __forceinline bool isFastAccel() const { return !isCompactAccel() && !isRobustAccel(); } + __forceinline bool isCompactAccel() const { return scene_flags & RTC_SCENE_FLAG_COMPACT; } + __forceinline bool isRobustAccel() const { return scene_flags & RTC_SCENE_FLAG_ROBUST; } + __forceinline bool isStaticAccel() const { return !(scene_flags & RTC_SCENE_FLAG_DYNAMIC); } + __forceinline bool isDynamicAccel() const { return scene_flags & RTC_SCENE_FLAG_DYNAMIC; } + + __forceinline bool hasContextFilterFunction() const { + return scene_flags & RTC_SCENE_FLAG_CONTEXT_FILTER_FUNCTION; + } + + __forceinline bool hasGeometryFilterFunction() { + return world.numFilterFunctions != 0; + } + + __forceinline bool hasFilterFunction() { + return hasContextFilterFunction() || hasGeometryFilterFunction(); + } + + /* test if scene got already build */ + __forceinline bool isBuild() const { return is_build; } + + public: + IDPool id_pool; + vector> geometries; //!< list of all user geometries + vector geometryModCounters_; + vector vertices; + + public: + Device* device; + + /* these are to detect if we need to recreate the acceleration structures */ + bool flags_modified; + unsigned int enabled_geometry_types; + + RTCSceneFlags scene_flags; + RTCBuildQuality quality_flags; + MutexSys buildMutex; + SpinLock geometriesMutex; + bool is_build; + private: + bool modified; //!< true if scene got modified + + public: + + /*! global lock step task scheduler */ +#if defined(TASKING_INTERNAL) + MutexSys schedulerMutex; + Ref scheduler; +#elif defined(TASKING_TBB) && TASKING_TBB_USE_TASK_ISOLATION + tbb::isolated_task_group group; +#elif defined(TASKING_TBB) + tbb::task_group group; +#elif defined(TASKING_PPL) + concurrency::task_group group; +#endif + + public: + struct BuildProgressMonitorInterface : public BuildProgressMonitor { + BuildProgressMonitorInterface(Scene* scene) + : scene(scene) {} + void operator() (size_t dn) const { scene->progressMonitor(double(dn)); } + private: + Scene* scene; + }; + BuildProgressMonitorInterface progressInterface; + RTCProgressMonitorFunction progress_monitor_function; + void* progress_monitor_ptr; + std::atomic progress_monitor_counter; + void progressMonitor(double nprims); + void setProgressMonitorFunction(RTCProgressMonitorFunction func, void* ptr); + + private: + GeometryCounts world; //!< counts for geometry + + public: + + __forceinline size_t numPrimitives() const { + return world.size(); + } + + __forceinline size_t getNumPrimitives(Geometry::GTypeMask mask, bool mblur) const + { + size_t count = 0; + + if (mask & Geometry::MTY_TRIANGLE_MESH) + count += mblur ? world.numMBTriangles : world.numTriangles; + + if (mask & Geometry::MTY_QUAD_MESH) + count += mblur ? world.numMBQuads : world.numQuads; + + if (mask & Geometry::MTY_CURVE2) + count += mblur ? world.numMBLineSegments : world.numLineSegments; + + if (mask & Geometry::MTY_CURVE4) + count += mblur ? world.numMBBezierCurves : world.numBezierCurves; + + if (mask & Geometry::MTY_POINTS) + count += mblur ? world.numMBPoints : world.numPoints; + + if (mask & Geometry::MTY_SUBDIV_MESH) + count += mblur ? world.numMBSubdivPatches : world.numSubdivPatches; + + if (mask & Geometry::MTY_USER_GEOMETRY) + count += mblur ? world.numMBUserGeometries : world.numUserGeometries; + + if (mask & Geometry::MTY_INSTANCE_CHEAP) + count += mblur ? world.numMBInstancesCheap : world.numInstancesCheap; + + if (mask & Geometry::MTY_INSTANCE_EXPENSIVE) + count += mblur ? world.numMBInstancesExpensive : world.numInstancesExpensive; + + if (mask & Geometry::MTY_GRID_MESH) + count += mblur ? world.numMBGrids : world.numGrids; + + return count; + } + + template + __forceinline unsigned getNumTimeSteps() + { + if (!mblur) + return 1; + + Scene::Iterator iter(this); + return iter.maxTimeStepsPerGeometry(); + } + + template + __forceinline unsigned int getMaxGeomID() + { + Scene::Iterator iter(this); + return iter.maxGeomID(); + } + }; +} diff --git a/thirdparty/embree/kernels/common/scene_curves.h b/thirdparty/embree/kernels/common/scene_curves.h new file mode 100644 index 000000000000..2649ab0e3e87 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_curves.h @@ -0,0 +1,341 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "geometry.h" +#include "buffer.h" + +namespace embree +{ + /*! represents an array of bicubic bezier curves */ + struct CurveGeometry : public Geometry + { + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_CURVE4; + + public: + + /*! bezier curve construction */ + CurveGeometry (Device* device, Geometry::GType gtype); + + public: + void setMask(unsigned mask); + void setNumTimeSteps (unsigned int numTimeSteps); + void setVertexAttributeCount (unsigned int N); + void setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void commit(); + bool verify(); + void setTessellationRate(float N); + void setMaxRadiusScale(float s); + void addElementsToCount (GeometryCounts & counts) const; + + public: + + /*! returns the number of vertices */ + __forceinline size_t numVertices() const { + return vertices[0].size(); + } + + /*! returns the i'th curve */ + __forceinline const unsigned int& curve(size_t i) const { + return curves[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline Vec3ff vertex(size_t i) const { + return vertices0[i]; + } + + /*! returns i'th normal of the first time step */ + __forceinline Vec3fa normal(size_t i) const { + return normals0[i]; + } + + /*! returns i'th tangent of the first time step */ + __forceinline Vec3ff tangent(size_t i) const { + return tangents0[i]; + } + + /*! returns i'th normal derivative of the first time step */ + __forceinline Vec3fa dnormal(size_t i) const { + return dnormals0[i]; + } + + /*! returns i'th radius of the first time step */ + __forceinline float radius(size_t i) const { + return vertices0[i].w; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline Vec3ff vertex(size_t i, size_t itime) const { + return vertices[itime][i]; + } + + /*! returns i'th normal of itime'th timestep */ + __forceinline Vec3fa normal(size_t i, size_t itime) const { + return normals[itime][i]; + } + + /*! returns i'th tangent of itime'th timestep */ + __forceinline Vec3ff tangent(size_t i, size_t itime) const { + return tangents[itime][i]; + } + + /*! returns i'th normal derivative of itime'th timestep */ + __forceinline Vec3fa dnormal(size_t i, size_t itime) const { + return dnormals[itime][i]; + } + + /*! returns i'th radius of itime'th timestep */ + __forceinline float radius(size_t i, size_t itime) const { + return vertices[itime][i].w; + } + + /*! gathers the curve starting with i'th vertex */ + __forceinline void gather(Vec3ff& p0, Vec3ff& p1, Vec3ff& p2, Vec3ff& p3, size_t i) const + { + p0 = vertex(i+0); + p1 = vertex(i+1); + p2 = vertex(i+2); + p3 = vertex(i+3); + } + + /*! gathers the curve starting with i'th vertex of itime'th timestep */ + __forceinline void gather(Vec3ff& p0, Vec3ff& p1, Vec3ff& p2, Vec3ff& p3, size_t i, size_t itime) const + { + p0 = vertex(i+0,itime); + p1 = vertex(i+1,itime); + p2 = vertex(i+2,itime); + p3 = vertex(i+3,itime); + } + + /*! gathers the curve starting with i'th vertex */ + __forceinline void gather(Vec3ff& p0, Vec3ff& p1, Vec3ff& p2, Vec3ff& p3, Vec3fa& n0, Vec3fa& n1, Vec3fa& n2, Vec3fa& n3, size_t i) const + { + p0 = vertex(i+0); + p1 = vertex(i+1); + p2 = vertex(i+2); + p3 = vertex(i+3); + n0 = normal(i+0); + n1 = normal(i+1); + n2 = normal(i+2); + n3 = normal(i+3); + } + + /*! gathers the curve starting with i'th vertex of itime'th timestep */ + __forceinline void gather(Vec3ff& p0, Vec3ff& p1, Vec3ff& p2, Vec3ff& p3, Vec3fa& n0, Vec3fa& n1, Vec3fa& n2, Vec3fa& n3, size_t i, size_t itime) const + { + p0 = vertex(i+0,itime); + p1 = vertex(i+1,itime); + p2 = vertex(i+2,itime); + p3 = vertex(i+3,itime); + n0 = normal(i+0,itime); + n1 = normal(i+1,itime); + n2 = normal(i+2,itime); + n3 = normal(i+3,itime); + } + + /*! prefetches the curve starting with i'th vertex of itime'th timestep */ + __forceinline void prefetchL1_vertices(size_t i) const + { + prefetchL1(vertices0.getPtr(i)+0); + prefetchL1(vertices0.getPtr(i)+64); + } + + /*! prefetches the curve starting with i'th vertex of itime'th timestep */ + __forceinline void prefetchL2_vertices(size_t i) const + { + prefetchL2(vertices0.getPtr(i)+0); + prefetchL2(vertices0.getPtr(i)+64); + } + + /*! loads curve vertices for specified time */ + __forceinline void gather(Vec3ff& p0, Vec3ff& p1, Vec3ff& p2, Vec3ff& p3, size_t i, float time) const + { + float ftime; + const size_t itime = timeSegment(time, ftime); + + const float t0 = 1.0f - ftime; + const float t1 = ftime; + Vec3ff a0,a1,a2,a3; + gather(a0,a1,a2,a3,i,itime); + Vec3ff b0,b1,b2,b3; + gather(b0,b1,b2,b3,i,itime+1); + p0 = madd(Vec3ff(t0),a0,t1*b0); + p1 = madd(Vec3ff(t0),a1,t1*b1); + p2 = madd(Vec3ff(t0),a2,t1*b2); + p3 = madd(Vec3ff(t0),a3,t1*b3); + } + + /*! loads curve vertices for specified time */ + __forceinline void gather(Vec3ff& p0, Vec3ff& p1, Vec3ff& p2, Vec3ff& p3, Vec3fa& n0, Vec3fa& n1, Vec3fa& n2, Vec3fa& n3, size_t i, float time) const + { + float ftime; + const size_t itime = timeSegment(time, ftime); + + const float t0 = 1.0f - ftime; + const float t1 = ftime; + Vec3ff a0,a1,a2,a3; Vec3fa an0,an1,an2,an3; + gather(a0,a1,a2,a3,an0,an1,an2,an3,i,itime); + Vec3ff b0,b1,b2,b3; Vec3fa bn0,bn1,bn2,bn3; + gather(b0,b1,b2,b3,bn0,bn1,bn2,bn3,i,itime+1); + p0 = madd(Vec3ff(t0),a0,t1*b0); + p1 = madd(Vec3ff(t0),a1,t1*b1); + p2 = madd(Vec3ff(t0),a2,t1*b2); + p3 = madd(Vec3ff(t0),a3,t1*b3); + n0 = madd(Vec3ff(t0),an0,t1*bn0); + n1 = madd(Vec3ff(t0),an1,t1*bn1); + n2 = madd(Vec3ff(t0),an2,t1*bn2); + n3 = madd(Vec3ff(t0),an3,t1*bn3); + } + + template + __forceinline TensorLinearCubicBezierSurface3fa getNormalOrientedCurve(IntersectContext* context, const Vec3fa& ray_org, const unsigned int primID, const size_t itime) const + { + Vec3ff v0,v1,v2,v3; Vec3fa n0,n1,n2,n3; + unsigned int vertexID = curve(primID); + gather(v0,v1,v2,v3,n0,n1,n2,n3,vertexID,itime); + SourceCurve3ff ccurve(v0,v1,v2,v3); + SourceCurve3fa ncurve(n0,n1,n2,n3); + ccurve = enlargeRadiusToMinWidth(context,this,ray_org,ccurve); + return TensorLinearCubicBezierSurface3fa::fromCenterAndNormalCurve(ccurve,ncurve); + } + + template + __forceinline TensorLinearCubicBezierSurface3fa getNormalOrientedCurve(IntersectContext* context, const Vec3fa& ray_org, const unsigned int primID, const float time) const + { + float ftime; + const size_t itime = timeSegment(time, ftime); + const TensorLinearCubicBezierSurface3fa curve0 = getNormalOrientedCurve(context,ray_org,primID,itime+0); + const TensorLinearCubicBezierSurface3fa curve1 = getNormalOrientedCurve(context,ray_org,primID,itime+1); + return clerp(curve0,curve1,ftime); + } + + /*! gathers the hermite curve starting with i'th vertex */ + __forceinline void gather_hermite(Vec3ff& p0, Vec3ff& t0, Vec3ff& p1, Vec3ff& t1, size_t i) const + { + p0 = vertex (i+0); + p1 = vertex (i+1); + t0 = tangent(i+0); + t1 = tangent(i+1); + } + + /*! gathers the hermite curve starting with i'th vertex of itime'th timestep */ + __forceinline void gather_hermite(Vec3ff& p0, Vec3ff& t0, Vec3ff& p1, Vec3ff& t1, size_t i, size_t itime) const + { + p0 = vertex (i+0,itime); + p1 = vertex (i+1,itime); + t0 = tangent(i+0,itime); + t1 = tangent(i+1,itime); + } + + /*! loads curve vertices for specified time */ + __forceinline void gather_hermite(Vec3ff& p0, Vec3ff& t0, Vec3ff& p1, Vec3ff& t1, size_t i, float time) const + { + float ftime; + const size_t itime = timeSegment(time, ftime); + const float f0 = 1.0f - ftime, f1 = ftime; + Vec3ff ap0,at0,ap1,at1; + gather_hermite(ap0,at0,ap1,at1,i,itime); + Vec3ff bp0,bt0,bp1,bt1; + gather_hermite(bp0,bt0,bp1,bt1,i,itime+1); + p0 = madd(Vec3ff(f0),ap0,f1*bp0); + t0 = madd(Vec3ff(f0),at0,f1*bt0); + p1 = madd(Vec3ff(f0),ap1,f1*bp1); + t1 = madd(Vec3ff(f0),at1,f1*bt1); + } + + /*! gathers the hermite curve starting with i'th vertex */ + __forceinline void gather_hermite(Vec3ff& p0, Vec3ff& t0, Vec3fa& n0, Vec3fa& dn0, Vec3ff& p1, Vec3ff& t1, Vec3fa& n1, Vec3fa& dn1, size_t i) const + { + p0 = vertex (i+0); + p1 = vertex (i+1); + t0 = tangent(i+0); + t1 = tangent(i+1); + n0 = normal(i+0); + n1 = normal(i+1); + dn0 = dnormal(i+0); + dn1 = dnormal(i+1); + } + + /*! gathers the hermite curve starting with i'th vertex of itime'th timestep */ + __forceinline void gather_hermite(Vec3ff& p0, Vec3ff& t0, Vec3fa& n0, Vec3fa& dn0, Vec3ff& p1, Vec3ff& t1, Vec3fa& n1, Vec3fa& dn1, size_t i, size_t itime) const + { + p0 = vertex (i+0,itime); + p1 = vertex (i+1,itime); + t0 = tangent(i+0,itime); + t1 = tangent(i+1,itime); + n0 = normal(i+0,itime); + n1 = normal(i+1,itime); + dn0 = dnormal(i+0,itime); + dn1 = dnormal(i+1,itime); + } + + /*! loads curve vertices for specified time */ + __forceinline void gather_hermite(Vec3ff& p0, Vec3fa& t0, Vec3fa& n0, Vec3fa& dn0, Vec3ff& p1, Vec3fa& t1, Vec3fa& n1, Vec3fa& dn1, size_t i, float time) const + { + float ftime; + const size_t itime = timeSegment(time, ftime); + const float f0 = 1.0f - ftime, f1 = ftime; + Vec3ff ap0,at0,ap1,at1; Vec3fa an0,adn0,an1,adn1; + gather_hermite(ap0,at0,an0,adn0,ap1,at1,an1,adn1,i,itime); + Vec3ff bp0,bt0,bp1,bt1; Vec3fa bn0,bdn0,bn1,bdn1; + gather_hermite(bp0,bt0,bn0,bdn0,bp1,bt1,bn1,bdn1,i,itime+1); + p0 = madd(Vec3ff(f0),ap0,f1*bp0); + t0 = madd(Vec3ff(f0),at0,f1*bt0); + n0 = madd(Vec3ff(f0),an0,f1*bn0); + dn0= madd(Vec3ff(f0),adn0,f1*bdn0); + p1 = madd(Vec3ff(f0),ap1,f1*bp1); + t1 = madd(Vec3ff(f0),at1,f1*bt1); + n1 = madd(Vec3ff(f0),an1,f1*bn1); + dn1= madd(Vec3ff(f0),adn1,f1*bdn1); + } + + template + __forceinline TensorLinearCubicBezierSurface3fa getNormalOrientedHermiteCurve(IntersectContext* context, const Vec3fa& ray_org, const unsigned int primID, const size_t itime) const + { + Vec3ff v0,t0,v1,t1; Vec3fa n0,dn0,n1,dn1; + unsigned int vertexID = curve(primID); + gather_hermite(v0,t0,n0,dn0,v1,t1,n1,dn1,vertexID,itime); + + SourceCurve3ff ccurve(v0,t0,v1,t1); + SourceCurve3fa ncurve(n0,dn0,n1,dn1); + ccurve = enlargeRadiusToMinWidth(context,this,ray_org,ccurve); + return TensorLinearCubicBezierSurface3fa::fromCenterAndNormalCurve(ccurve,ncurve); + } + + template + __forceinline TensorLinearCubicBezierSurface3fa getNormalOrientedHermiteCurve(IntersectContext* context, const Vec3fa& ray_org, const unsigned int primID, const float time) const + { + float ftime; + const size_t itime = timeSegment(time, ftime); + const TensorLinearCubicBezierSurface3fa curve0 = getNormalOrientedHermiteCurve(context, ray_org, primID,itime+0); + const TensorLinearCubicBezierSurface3fa curve1 = getNormalOrientedHermiteCurve(context, ray_org, primID,itime+1); + return clerp(curve0,curve1,ftime); + } + + private: + void resizeBuffers(unsigned int numSteps); + + public: + BufferView curves; //!< array of curve indices + BufferView vertices0; //!< fast access to first vertex buffer + BufferView normals0; //!< fast access to first normal buffer + BufferView tangents0; //!< fast access to first tangent buffer + BufferView dnormals0; //!< fast access to first normal derivative buffer + vector> vertices; //!< vertex array for each timestep + vector> normals; //!< normal array for each timestep + vector> tangents; //!< tangent array for each timestep + vector> dnormals; //!< normal derivative array for each timestep + BufferView flags; //!< start, end flag per segment + vector> vertexAttribs; //!< user buffers + int tessellationRate; //!< tessellation rate for flat curve + float maxRadiusScale = 1.0; //!< maximal min-width scaling of curve radii + }; + + DECLARE_ISA_FUNCTION(CurveGeometry*, createCurves, Device* COMMA Geometry::GType); +} diff --git a/thirdparty/embree/kernels/common/scene_grid_mesh.h b/thirdparty/embree/kernels/common/scene_grid_mesh.h new file mode 100644 index 000000000000..c08658466a26 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_grid_mesh.h @@ -0,0 +1,215 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "geometry.h" +#include "buffer.h" + +namespace embree +{ + /*! Grid Mesh */ + struct GridMesh : public Geometry + { + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_GRID_MESH; + + /*! grid */ + struct Grid + { + unsigned int startVtxID; + unsigned int lineVtxOffset; + unsigned short resX,resY; + + /* border flags due to 3x3 vertex pattern */ + __forceinline unsigned int get3x3FlagsX(const unsigned int x) const + { + return (x + 2 >= (unsigned int)resX) ? (1<<15) : 0; + } + + /* border flags due to 3x3 vertex pattern */ + __forceinline unsigned int get3x3FlagsY(const unsigned int y) const + { + return (y + 2 >= (unsigned int)resY) ? (1<<15) : 0; + } + + /*! outputs grid structure */ + __forceinline friend embree_ostream operator<<(embree_ostream cout, const Grid& t) { + return cout << "Grid { startVtxID " << t.startVtxID << ", lineVtxOffset " << t.lineVtxOffset << ", resX " << t.resX << ", resY " << t.resY << " }"; + } + }; + + public: + + /*! grid mesh construction */ + GridMesh (Device* device); + + /* geometry interface */ + public: + void setMask(unsigned mask); + void setNumTimeSteps (unsigned int numTimeSteps); + void setVertexAttributeCount (unsigned int N); + void setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void commit(); + bool verify(); + void interpolate(const RTCInterpolateArguments* const args); + void addElementsToCount (GeometryCounts & counts) const; + + __forceinline unsigned int getNumSubGrids(const size_t gridID) + { + const Grid &g = grid(gridID); + return max((unsigned int)1,((unsigned int)g.resX >> 1) * ((unsigned int)g.resY >> 1)); + } + + /*! get fast access to first vertex buffer */ + __forceinline float * getCompactVertexArray () const { + return (float*) vertices0.getPtr(); + } + + public: + + /*! returns number of vertices */ + __forceinline size_t numVertices() const { + return vertices[0].size(); + } + + /*! returns i'th grid*/ + __forceinline const Grid& grid(size_t i) const { + return grids[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline const Vec3fa vertex(size_t i) const { // FIXME: check if this does a unaligned load + return vertices0[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline const char* vertexPtr(size_t i) const { + return vertices0.getPtr(i); + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const Vec3fa vertex(size_t i, size_t itime) const { + return vertices[itime][i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const char* vertexPtr(size_t i, size_t itime) const { + return vertices[itime].getPtr(i); + } + + /*! returns i'th vertex of the first timestep */ + __forceinline size_t grid_vertex_index(const Grid& g, size_t x, size_t y) const { + assert(x < (size_t)g.resX); + assert(y < (size_t)g.resY); + return g.startVtxID + x + y * g.lineVtxOffset; + } + + /*! returns i'th vertex of the first timestep */ + __forceinline const Vec3fa grid_vertex(const Grid& g, size_t x, size_t y) const { + const size_t index = grid_vertex_index(g,x,y); + return vertex(index); + } + + /*! returns i'th vertex of the itime'th timestep */ + __forceinline const Vec3fa grid_vertex(const Grid& g, size_t x, size_t y, size_t itime) const { + const size_t index = grid_vertex_index(g,x,y); + return vertex(index,itime); + } + + /*! calculates the build bounds of the i'th primitive, if it's valid */ + __forceinline bool buildBounds(const Grid& g, size_t sx, size_t sy, BBox3fa& bbox) const + { + BBox3fa b(empty); + for (size_t t=0; t& itime_range) const + { + if (unlikely(gridID >= grids.size())) return false; + const Grid &g = grid(gridID); + if (unlikely(g.startVtxID + 0 >= vertices0.size())) return false; + if (unlikely(g.startVtxID + (g.resY-1)*g.lineVtxOffset + g.resX-1 >= vertices0.size())) return false; + + for (size_t y=0;y grids; //!< array of triangles + BufferView vertices0; //!< fast access to first vertex buffer + vector> vertices; //!< vertex array for each timestep + vector vertexAttribs; //!< vertex attributes + }; + + namespace isa + { + struct GridMeshISA : public GridMesh + { + GridMeshISA (Device* device) + : GridMesh(device) {} + }; + } + + DECLARE_ISA_FUNCTION(GridMesh*, createGridMesh, Device*); +} diff --git a/thirdparty/embree/kernels/common/scene_instance.h b/thirdparty/embree/kernels/common/scene_instance.h new file mode 100644 index 000000000000..7ff82a4fb8dc --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_instance.h @@ -0,0 +1,272 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "geometry.h" +#include "accel.h" + +namespace embree +{ + struct MotionDerivativeCoefficients; + + /*! Instanced acceleration structure */ + struct Instance : public Geometry + { + ALIGNED_STRUCT_(16); + static const Geometry::GTypeMask geom_type = Geometry::MTY_INSTANCE; + + public: + Instance (Device* device, Accel* object = nullptr, unsigned int numTimeSteps = 1); + ~Instance(); + + private: + Instance (const Instance& other) DELETED; // do not implement + Instance& operator= (const Instance& other) DELETED; // do not implement + + private: + LBBox3fa nonlinearBounds(const BBox1f& time_range_in, + const BBox1f& geom_time_range, + float geom_time_segments) const; + + BBox3fa boundSegment(size_t itime, + BBox3fa const& obbox0, BBox3fa const& obbox1, + BBox3fa const& bbox0, BBox3fa const& bbox1, + float t_min, float t_max) const; + + /* calculates the (correct) interpolated bounds */ + __forceinline BBox3fa bounds(size_t itime0, size_t itime1, float f) const + { + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) + return xfmBounds(slerp(local2world[itime0], local2world[itime1], f), + lerp(getObjectBounds(itime0), getObjectBounds(itime1), f)); + return xfmBounds(lerp(local2world[itime0], local2world[itime1], f), + lerp(getObjectBounds(itime0), getObjectBounds(itime1), f)); + } + + public: + virtual void setNumTimeSteps (unsigned int numTimeSteps) override; + virtual void setInstancedScene(const Ref& scene) override; + virtual void setTransform(const AffineSpace3fa& local2world, unsigned int timeStep) override; + virtual void setQuaternionDecomposition(const AffineSpace3ff& qd, unsigned int timeStep) override; + virtual AffineSpace3fa getTransform(float time) override; + virtual void setMask (unsigned mask) override; + virtual void build() {} + virtual void addElementsToCount (GeometryCounts & counts) const override; + virtual void commit() override; + + public: + + /*! calculates the bounds of instance */ + __forceinline BBox3fa bounds(size_t i) const { + assert(i == 0); + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) + return xfmBounds(quaternionDecompositionToAffineSpace(local2world[0]),object->bounds.bounds()); + return xfmBounds(local2world[0],object->bounds.bounds()); + } + + /*! gets the bounds of the instanced scene */ + __forceinline BBox3fa getObjectBounds(size_t itime) const { + return object->getBounds(timeStep(itime)); + } + + /*! calculates the bounds of instance */ + __forceinline BBox3fa bounds(size_t i, size_t itime) const { + assert(i == 0); + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) + return xfmBounds(quaternionDecompositionToAffineSpace(local2world[itime]),getObjectBounds(itime)); + return xfmBounds(local2world[itime],getObjectBounds(itime)); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(size_t i, const BBox1f& dt) const { + assert(i == 0); + LBBox3fa lbbox = nonlinearBounds(dt, time_range, fnumTimeSegments); + return lbbox; + } + + /*! calculates the build bounds of the i'th item, if it's valid */ + __forceinline bool buildBounds(size_t i, BBox3fa* bbox = nullptr) const + { + assert(i==0); + const BBox3fa b = bounds(i); + if (bbox) *bbox = b; + return isvalid(b); + } + + /*! calculates the build bounds of the i'th item at the itime'th time segment, if it's valid */ + __forceinline bool buildBounds(size_t i, size_t itime, BBox3fa& bbox) const + { + assert(i==0); + const LBBox3fa bounds = linearBounds(i,itime); + bbox = bounds.bounds (); + return isvalid(bounds); + } + + /* gets version info of topology */ + unsigned int getTopologyVersion() const { + return numPrimitives; + } + + /* returns true if topology changed */ + bool topologyChanged(unsigned int otherVersion) const { + return numPrimitives != otherVersion; + } + + /*! check if the i'th primitive is valid between the specified time range */ + __forceinline bool valid(size_t i, const range& itime_range) const + { + assert(i == 0); + for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) + if (!isvalid(bounds(i,itime))) return false; + + return true; + } + + __forceinline AffineSpace3fa getLocal2World() const + { + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) + return quaternionDecompositionToAffineSpace(local2world[0]); + return local2world[0]; + } + + __forceinline AffineSpace3fa getLocal2World(float t) const + { + float ftime; const unsigned int itime = timeSegment(t, ftime); + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) + return slerp(local2world[itime+0],local2world[itime+1],ftime); + return lerp(local2world[itime+0],local2world[itime+1],ftime); + } + + __forceinline AffineSpace3fa getWorld2Local() const { + return world2local0; + } + + __forceinline AffineSpace3fa getWorld2Local(float t) const { + return rcp(getLocal2World(t)); + } + + template + __forceinline AffineSpace3vf getWorld2Local(const vbool& valid, const vfloat& t) const + { + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) + return getWorld2LocalSlerp(valid, t); + return getWorld2LocalLerp(valid, t); + } + + private: + + template + __forceinline AffineSpace3vf getWorld2LocalSlerp(const vbool& valid, const vfloat& t) const + { + vfloat ftime; + const vint itime_k = timeSegment(t, ftime); + assert(any(valid)); + const size_t index = bsf(movemask(valid)); + const int itime = itime_k[index]; + if (likely(all(valid, itime_k == vint(itime)))) { + return rcp(slerp(AffineSpace3vff(local2world[itime+0]), + AffineSpace3vff(local2world[itime+1]), + ftime)); + } + else { + AffineSpace3vff space0,space1; + vbool valid1 = valid; + while (any(valid1)) { + vbool valid2; + const int itime = next_unique(valid1, itime_k, valid2); + space0 = select(valid2, AffineSpace3vff(local2world[itime+0]), space0); + space1 = select(valid2, AffineSpace3vff(local2world[itime+1]), space1); + } + return rcp(slerp(space0, space1, ftime)); + } + } + + template + __forceinline AffineSpace3vf getWorld2LocalLerp(const vbool& valid, const vfloat& t) const + { + vfloat ftime; + const vint itime_k = timeSegment(t, ftime); + assert(any(valid)); + const size_t index = bsf(movemask(valid)); + const int itime = itime_k[index]; + if (likely(all(valid, itime_k == vint(itime)))) { + return rcp(lerp(AffineSpace3vf((AffineSpace3fa)local2world[itime+0]), + AffineSpace3vf((AffineSpace3fa)local2world[itime+1]), + ftime)); + } else { + AffineSpace3vf space0,space1; + vbool valid1 = valid; + while (any(valid1)) { + vbool valid2; + const int itime = next_unique(valid1, itime_k, valid2); + space0 = select(valid2, AffineSpace3vf((AffineSpace3fa)local2world[itime+0]), space0); + space1 = select(valid2, AffineSpace3vf((AffineSpace3fa)local2world[itime+1]), space1); + } + return rcp(lerp(space0, space1, ftime)); + } + } + + public: + Accel* object; //!< pointer to instanced acceleration structure + AffineSpace3ff* local2world; //!< transformation from local space to world space for each timestep (either normal matrix or quaternion decomposition) + AffineSpace3fa world2local0; //!< transformation from world space to local space for timestep 0 + }; + + namespace isa + { + struct InstanceISA : public Instance + { + InstanceISA (Device* device) + : Instance(device) {} + + PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const + { + assert(r.begin() == 0); + assert(r.end() == 1); + + PrimInfo pinfo(empty); + BBox3fa b = empty; + if (!buildBounds(0,&b)) return pinfo; + // const BBox3fa b = bounds(0); + // if (!isvalid(b)) return pinfo; + + const PrimRef prim(b,geomID,unsigned(0)); + pinfo.add_center2(prim); + prims[k++] = prim; + return pinfo; + } + + PrimInfo createPrimRefArrayMB(mvector& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const + { + assert(r.begin() == 0); + assert(r.end() == 1); + + PrimInfo pinfo(empty); + BBox3fa b = empty; + if (!buildBounds(0,&b)) return pinfo; + // if (!valid(0,range(itime))) return pinfo; + // const PrimRef prim(linearBounds(0,itime).bounds(),geomID,unsigned(0)); + const PrimRef prim(b,geomID,unsigned(0)); + pinfo.add_center2(prim); + prims[k++] = prim; + return pinfo; + } + + PrimInfoMB createPrimRefMBArray(mvector& prims, const BBox1f& t0t1, const range& r, size_t k, unsigned int geomID) const + { + assert(r.begin() == 0); + assert(r.end() == 1); + + PrimInfoMB pinfo(empty); + if (!valid(0, timeSegmentRange(t0t1))) return pinfo; + const PrimRefMB prim(linearBounds(0,t0t1),this->numTimeSegments(),this->time_range,this->numTimeSegments(),geomID,unsigned(0)); + pinfo.add_primref(prim); + prims[k++] = prim; + return pinfo; + } + }; + } + + DECLARE_ISA_FUNCTION(Instance*, createInstance, Device*); +} diff --git a/thirdparty/embree/kernels/common/scene_line_segments.h b/thirdparty/embree/kernels/common/scene_line_segments.h new file mode 100644 index 000000000000..c0f9ee8f7796 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_line_segments.h @@ -0,0 +1,307 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" +#include "geometry.h" +#include "buffer.h" + +namespace embree +{ + /*! represents an array of line segments */ + struct LineSegments : public Geometry + { + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_CURVE2; + + public: + + /*! line segments construction */ + LineSegments (Device* device, Geometry::GType gtype); + + public: + void setMask (unsigned mask); + void setNumTimeSteps (unsigned int numTimeSteps); + void setVertexAttributeCount (unsigned int N); + void setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void commit(); + bool verify (); + void interpolate(const RTCInterpolateArguments* const args); + void setTessellationRate(float N); + void setMaxRadiusScale(float s); + void addElementsToCount (GeometryCounts & counts) const; + + public: + + /*! returns the number of vertices */ + __forceinline size_t numVertices() const { + return vertices[0].size(); + } + + /*! returns the i'th segment */ + __forceinline const unsigned int& segment(size_t i) const { + return segments[i]; + } + + /*! returns the segment to the left of the i'th segment */ + __forceinline bool segmentLeftExists(size_t i) const { + assert (flags); + return (flags[i] & RTC_CURVE_FLAG_NEIGHBOR_LEFT) != 0; + } + + /*! returns the segment to the right of the i'th segment */ + __forceinline bool segmentRightExists(size_t i) const { + assert (flags); + return (flags[i] & RTC_CURVE_FLAG_NEIGHBOR_RIGHT) != 0; + } + + /*! returns i'th vertex of the first time step */ + __forceinline Vec3ff vertex(size_t i) const { + return vertices0[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline const char* vertexPtr(size_t i) const { + return vertices0.getPtr(i); + } + + /*! returns i'th normal of the first time step */ + __forceinline Vec3fa normal(size_t i) const { + return normals0[i]; + } + + /*! returns i'th radius of the first time step */ + __forceinline float radius(size_t i) const { + return vertices0[i].w; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline Vec3ff vertex(size_t i, size_t itime) const { + return vertices[itime][i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const char* vertexPtr(size_t i, size_t itime) const { + return vertices[itime].getPtr(i); + } + + /*! returns i'th normal of itime'th timestep */ + __forceinline Vec3fa normal(size_t i, size_t itime) const { + return normals[itime][i]; + } + + /*! returns i'th radius of itime'th timestep */ + __forceinline float radius(size_t i, size_t itime) const { + return vertices[itime][i].w; + } + + /*! calculates bounding box of i'th line segment */ + __forceinline BBox3fa bounds(const Vec3ff& v0, const Vec3ff& v1) const + { + const BBox3ff b = merge(BBox3ff(v0),BBox3ff(v1)); + return enlarge((BBox3fa)b,maxRadiusScale*Vec3fa(max(v0.w,v1.w))); + } + + /*! calculates bounding box of i'th line segment */ + __forceinline BBox3fa bounds(size_t i) const + { + const unsigned int index = segment(i); + const Vec3ff v0 = vertex(index+0); + const Vec3ff v1 = vertex(index+1); + return bounds(v0,v1); + } + + /*! calculates bounding box of i'th line segment for the itime'th time step */ + __forceinline BBox3fa bounds(size_t i, size_t itime) const + { + const unsigned int index = segment(i); + const Vec3ff v0 = vertex(index+0,itime); + const Vec3ff v1 = vertex(index+1,itime); + return bounds(v0,v1); + } + + /*! calculates bounding box of i'th line segment */ + __forceinline BBox3fa bounds(const LinearSpace3fa& space, size_t i) const + { + const unsigned int index = segment(i); + const Vec3ff v0 = vertex(index+0); + const Vec3ff v1 = vertex(index+1); + const Vec3ff w0(xfmVector(space,(Vec3fa)v0),v0.w); + const Vec3ff w1(xfmVector(space,(Vec3fa)v1),v1.w); + return bounds(w0,w1); + } + + /*! calculates bounding box of i'th line segment for the itime'th time step */ + __forceinline BBox3fa bounds(const LinearSpace3fa& space, size_t i, size_t itime) const + { + const unsigned int index = segment(i); + const Vec3ff v0 = vertex(index+0,itime); + const Vec3ff v1 = vertex(index+1,itime); + const Vec3ff w0(xfmVector(space,(Vec3fa)v0),v0.w); + const Vec3ff w1(xfmVector(space,(Vec3fa)v1),v1.w); + return bounds(w0,w1); + } + + /*! check if the i'th primitive is valid at the itime'th timestep */ + __forceinline bool valid(size_t i, size_t itime) const { + return valid(i, make_range(itime, itime)); + } + + /*! check if the i'th primitive is valid between the specified time range */ + __forceinline bool valid(size_t i, const range& itime_range) const + { + const unsigned int index = segment(i); + if (index+1 >= numVertices()) return false; + + for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) + { + const Vec3ff v0 = vertex(index+0,itime); if (unlikely(!isvalid4(v0))) return false; + const Vec3ff v1 = vertex(index+1,itime); if (unlikely(!isvalid4(v1))) return false; + if (min(v0.w,v1.w) < 0.0f) return false; + } + return true; + } + + /*! calculates the linear bounds of the i'th primitive at the itimeGlobal'th time segment */ + __forceinline LBBox3fa linearBounds(size_t i, size_t itime) const { + return LBBox3fa(bounds(i,itime+0),bounds(i,itime+1)); + } + + /*! calculates the build bounds of the i'th primitive, if it's valid */ + __forceinline bool buildBounds(size_t i, BBox3fa* bbox) const + { + if (!valid(i,0)) return false; + *bbox = bounds(i); + return true; + } + + /*! calculates the build bounds of the i'th primitive at the itime'th time segment, if it's valid */ + __forceinline bool buildBounds(size_t i, size_t itime, BBox3fa& bbox) const + { + if (!valid(i,itime+0) || !valid(i,itime+1)) return false; + bbox = bounds(i,itime); // use bounds of first time step in builder + return true; + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(size_t primID, const BBox1f& dt) const { + return LBBox3fa([&] (size_t itime) { return bounds(primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(const LinearSpace3fa& space, size_t primID, const BBox1f& dt) const { + return LBBox3fa([&] (size_t itime) { return bounds(space, primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline bool linearBounds(size_t i, const BBox1f& time_range, LBBox3fa& bbox) const + { + if (!valid(i, timeSegmentRange(time_range))) return false; + bbox = linearBounds(i, time_range); + return true; + } + + /*! get fast access to first vertex buffer */ + __forceinline float * getCompactVertexArray () const { + return (float*) vertices0.getPtr(); + } + + public: + BufferView segments; //!< array of line segment indices + BufferView vertices0; //!< fast access to first vertex buffer + BufferView normals0; //!< fast access to first normal buffer + BufferView flags; //!< start, end flag per segment + vector> vertices; //!< vertex array for each timestep + vector> normals; //!< normal array for each timestep + vector> vertexAttribs; //!< user buffers + int tessellationRate; //!< tessellation rate for bezier curve + float maxRadiusScale = 1.0; //!< maximal min-width scaling of curve radii + }; + + namespace isa + { + struct LineSegmentsISA : public LineSegments + { + LineSegmentsISA (Device* device, Geometry::GType gtype) + : LineSegments(device,gtype) {} + + Vec3fa computeDirection(unsigned int primID) const + { + const unsigned vtxID = segment(primID); + const Vec3fa v0 = vertex(vtxID+0); + const Vec3fa v1 = vertex(vtxID+1); + return v1-v0; + } + + Vec3fa computeDirection(unsigned int primID, size_t time) const + { + const unsigned vtxID = segment(primID); + const Vec3fa v0 = vertex(vtxID+0,time); + const Vec3fa v1 = vertex(vtxID+1,time); + return v1-v0; + } + + PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, const BBox1f& t0t1, const range& r, size_t k, unsigned int geomID) const + { + PrimInfoMB pinfo(empty); + for (size_t j=r.begin(); jnumTimeSegments(),this->time_range,this->numTimeSegments(),geomID,unsigned(j)); + pinfo.add_primref(prim); + prims[k++] = prim; + } + return pinfo; + } + + BBox3fa vbounds(size_t i) const { + return bounds(i); + } + + BBox3fa vbounds(const LinearSpace3fa& space, size_t i) const { + return bounds(space,i); + } + + LBBox3fa vlinearBounds(size_t primID, const BBox1f& time_range) const { + return linearBounds(primID,time_range); + } + + LBBox3fa vlinearBounds(const LinearSpace3fa& space, size_t primID, const BBox1f& time_range) const { + return linearBounds(space,primID,time_range); + } + }; + } + + DECLARE_ISA_FUNCTION(LineSegments*, createLineSegments, Device* COMMA Geometry::GType); +} diff --git a/thirdparty/embree/kernels/common/scene_points.h b/thirdparty/embree/kernels/common/scene_points.h new file mode 100644 index 000000000000..1d39ed07baa2 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_points.h @@ -0,0 +1,282 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "buffer.h" +#include "default.h" +#include "geometry.h" + +namespace embree +{ + /*! represents an array of points */ + struct Points : public Geometry + { + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_POINTS; + + public: + /*! line segments construction */ + Points(Device* device, Geometry::GType gtype); + + public: + void setMask(unsigned mask); + void setNumTimeSteps(unsigned int numTimeSteps); + void setVertexAttributeCount(unsigned int N); + void setBuffer(RTCBufferType type, + unsigned int slot, + RTCFormat format, + const Ref& buffer, + size_t offset, + size_t stride, + unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void commit(); + bool verify(); + void setMaxRadiusScale(float s); + void addElementsToCount (GeometryCounts & counts) const; + + public: + /*! returns the number of vertices */ + __forceinline size_t numVertices() const { + return vertices[0].size(); + } + + /*! returns i'th vertex of the first time step */ + __forceinline Vec3ff vertex(size_t i) const { + return vertices0[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline const char* vertexPtr(size_t i) const { + return vertices0.getPtr(i); + } + + /*! returns i'th normal of the first time step */ + __forceinline Vec3fa normal(size_t i) const { + return normals0[i]; + } + + /*! returns i'th radius of the first time step */ + __forceinline float radius(size_t i) const { + return vertices0[i].w; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline Vec3ff vertex(size_t i, size_t itime) const { + return vertices[itime][i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const char* vertexPtr(size_t i, size_t itime) const { + return vertices[itime].getPtr(i); + } + + /*! returns i'th normal of itime'th timestep */ + __forceinline Vec3fa normal(size_t i, size_t itime) const { + return normals[itime][i]; + } + + /*! returns i'th radius of itime'th timestep */ + __forceinline float radius(size_t i, size_t itime) const { + return vertices[itime][i].w; + } + + /*! calculates bounding box of i'th line segment */ + __forceinline BBox3fa bounds(const Vec3ff& v0) const { + return enlarge(BBox3fa(v0), maxRadiusScale*Vec3fa(v0.w)); + } + + /*! calculates bounding box of i'th line segment */ + __forceinline BBox3fa bounds(size_t i) const + { + const Vec3ff v0 = vertex(i); + return bounds(v0); + } + + /*! calculates bounding box of i'th line segment for the itime'th time step */ + __forceinline BBox3fa bounds(size_t i, size_t itime) const + { + const Vec3ff v0 = vertex(i, itime); + return bounds(v0); + } + + /*! calculates bounding box of i'th line segment */ + __forceinline BBox3fa bounds(const LinearSpace3fa& space, size_t i) const + { + const Vec3ff v0 = vertex(i); + const Vec3ff w0(xfmVector(space, (Vec3fa)v0), v0.w); + return bounds(w0); + } + + /*! calculates bounding box of i'th line segment for the itime'th time step */ + __forceinline BBox3fa bounds(const LinearSpace3fa& space, size_t i, size_t itime) const + { + const Vec3ff v0 = vertex(i, itime); + const Vec3ff w0(xfmVector(space, (Vec3fa)v0), v0.w); + return bounds(w0); + } + + /*! check if the i'th primitive is valid at the itime'th timestep */ + __forceinline bool valid(size_t i, size_t itime) const { + return valid(i, make_range(itime, itime)); + } + + /*! check if the i'th primitive is valid between the specified time range */ + __forceinline bool valid(size_t i, const range& itime_range) const + { + const unsigned int index = (unsigned int)i; + if (index >= numVertices()) + return false; + + for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) { + const Vec3ff v0 = vertex(index + 0, itime); + if (unlikely(!isvalid4(v0))) + return false; + if (v0.w < 0.0f) + return false; + } + return true; + } + + /*! calculates the linear bounds of the i'th primitive at the itimeGlobal'th time segment */ + __forceinline LBBox3fa linearBounds(size_t i, size_t itime) const { + return LBBox3fa(bounds(i, itime + 0), bounds(i, itime + 1)); + } + + /*! calculates the build bounds of the i'th primitive, if it's valid */ + __forceinline bool buildBounds(size_t i, BBox3fa* bbox) const + { + if (!valid(i, 0)) + return false; + *bbox = bounds(i); + return true; + } + + /*! calculates the build bounds of the i'th primitive at the itime'th time segment, if it's valid */ + __forceinline bool buildBounds(size_t i, size_t itime, BBox3fa& bbox) const + { + if (!valid(i, itime + 0) || !valid(i, itime + 1)) + return false; + bbox = bounds(i, itime); // use bounds of first time step in builder + return true; + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(size_t primID, const BBox1f& dt) const { + return LBBox3fa([&](size_t itime) { return bounds(primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(const LinearSpace3fa& space, size_t primID, const BBox1f& dt) const { + return LBBox3fa([&](size_t itime) { return bounds(space, primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline bool linearBounds(size_t i, const BBox1f& time_range, LBBox3fa& bbox) const + { + if (!valid(i, timeSegmentRange(time_range))) return false; + bbox = linearBounds(i, time_range); + return true; + } + + /*! get fast access to first vertex buffer */ + __forceinline float * getCompactVertexArray () const { + return (float*) vertices0.getPtr(); + } + + public: + BufferView vertices0; //!< fast access to first vertex buffer + BufferView normals0; //!< fast access to first normal buffer + vector> vertices; //!< vertex array for each timestep + vector> normals; //!< normal array for each timestep + vector> vertexAttribs; //!< user buffers + float maxRadiusScale = 1.0; //!< maximal min-width scaling of curve radii + }; + + namespace isa + { + struct PointsISA : public Points + { + PointsISA(Device* device, Geometry::GType gtype) : Points(device, gtype) {} + + Vec3fa computeDirection(unsigned int primID) const + { + return Vec3fa(1, 0, 0); + } + + Vec3fa computeDirection(unsigned int primID, size_t time) const + { + return Vec3fa(1, 0, 0); + } + + PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j = r.begin(); j < r.end(); j++) { + BBox3fa bounds = empty; + if (!buildBounds(j, &bounds)) + continue; + const PrimRef prim(bounds, geomID, unsigned(j)); + pinfo.add_center2(prim); + prims[k++] = prim; + } + return pinfo; + } + + PrimInfo createPrimRefArrayMB(mvector& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j = r.begin(); j < r.end(); j++) { + BBox3fa bounds = empty; + if (!buildBounds(j, itime, bounds)) + continue; + const PrimRef prim(bounds, geomID, unsigned(j)); + pinfo.add_center2(prim); + prims[k++] = prim; + } + return pinfo; + } + + PrimInfoMB createPrimRefMBArray(mvector& prims, + const BBox1f& t0t1, + const range& r, + size_t k, + unsigned int geomID) const + { + PrimInfoMB pinfo(empty); + for (size_t j = r.begin(); j < r.end(); j++) { + if (!valid(j, timeSegmentRange(t0t1))) + continue; + const PrimRefMB prim(linearBounds(j, t0t1), this->numTimeSegments(), this->time_range, this->numTimeSegments(), geomID, unsigned(j)); + pinfo.add_primref(prim); + prims[k++] = prim; + } + return pinfo; + } + + BBox3fa vbounds(size_t i) const + { + return bounds(i); + } + + BBox3fa vbounds(const LinearSpace3fa& space, size_t i) const + { + return bounds(space, i); + } + + LBBox3fa vlinearBounds(size_t primID, const BBox1f& time_range) const + { + return linearBounds(primID, time_range); + } + + LBBox3fa vlinearBounds(const LinearSpace3fa& space, size_t primID, const BBox1f& time_range) const + { + return linearBounds(space, primID, time_range); + } + }; + } // namespace isa + + DECLARE_ISA_FUNCTION(Points*, createPoints, Device* COMMA Geometry::GType); +} // namespace embree diff --git a/thirdparty/embree/kernels/common/scene_quad_mesh.h b/thirdparty/embree/kernels/common/scene_quad_mesh.h new file mode 100644 index 000000000000..d5bb054b14a1 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_quad_mesh.h @@ -0,0 +1,277 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "geometry.h" +#include "buffer.h" + +namespace embree +{ + /*! Quad Mesh */ + struct QuadMesh : public Geometry + { + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_QUAD_MESH; + + /*! triangle indices */ + struct Quad + { + uint32_t v[4]; + + /*! outputs triangle indices */ + __forceinline friend embree_ostream operator<<(embree_ostream cout, const Quad& q) { + return cout << "Quad {" << q.v[0] << ", " << q.v[1] << ", " << q.v[2] << ", " << q.v[3] << " }"; + } + }; + + public: + + /*! quad mesh construction */ + QuadMesh (Device* device); + + /* geometry interface */ + public: + void setMask(unsigned mask); + void setNumTimeSteps (unsigned int numTimeSteps); + void setVertexAttributeCount (unsigned int N); + void setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void commit(); + bool verify(); + void interpolate(const RTCInterpolateArguments* const args); + void addElementsToCount (GeometryCounts & counts) const; + + public: + + /*! returns number of vertices */ + __forceinline size_t numVertices() const { + return vertices[0].size(); + } + + /*! returns i'th quad */ + __forceinline const Quad& quad(size_t i) const { + return quads[i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const Vec3fa vertex(size_t i) const { + return vertices0[i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const char* vertexPtr(size_t i) const { + return vertices0.getPtr(i); + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const Vec3fa vertex(size_t i, size_t itime) const { + return vertices[itime][i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const char* vertexPtr(size_t i, size_t itime) const { + return vertices[itime].getPtr(i); + } + + /*! calculates the bounds of the i'th quad */ + __forceinline BBox3fa bounds(size_t i) const + { + const Quad& q = quad(i); + const Vec3fa v0 = vertex(q.v[0]); + const Vec3fa v1 = vertex(q.v[1]); + const Vec3fa v2 = vertex(q.v[2]); + const Vec3fa v3 = vertex(q.v[3]); + return BBox3fa(min(v0,v1,v2,v3),max(v0,v1,v2,v3)); + } + + /*! calculates the bounds of the i'th quad at the itime'th timestep */ + __forceinline BBox3fa bounds(size_t i, size_t itime) const + { + const Quad& q = quad(i); + const Vec3fa v0 = vertex(q.v[0],itime); + const Vec3fa v1 = vertex(q.v[1],itime); + const Vec3fa v2 = vertex(q.v[2],itime); + const Vec3fa v3 = vertex(q.v[3],itime); + return BBox3fa(min(v0,v1,v2,v3),max(v0,v1,v2,v3)); + } + + /*! check if the i'th primitive is valid at the itime'th timestep */ + __forceinline bool valid(size_t i, size_t itime) const { + return valid(i, make_range(itime, itime)); + } + + /*! check if the i'th primitive is valid between the specified time range */ + __forceinline bool valid(size_t i, const range& itime_range) const + { + const Quad& q = quad(i); + if (unlikely(q.v[0] >= numVertices())) return false; + if (unlikely(q.v[1] >= numVertices())) return false; + if (unlikely(q.v[2] >= numVertices())) return false; + if (unlikely(q.v[3] >= numVertices())) return false; + + for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) + { + if (!isvalid(vertex(q.v[0],itime))) return false; + if (!isvalid(vertex(q.v[1],itime))) return false; + if (!isvalid(vertex(q.v[2],itime))) return false; + if (!isvalid(vertex(q.v[3],itime))) return false; + } + + return true; + } + + /*! calculates the linear bounds of the i'th quad at the itimeGlobal'th time segment */ + __forceinline LBBox3fa linearBounds(size_t i, size_t itime) const { + return LBBox3fa(bounds(i,itime+0),bounds(i,itime+1)); + } + + /*! calculates the build bounds of the i'th primitive, if it's valid */ + __forceinline bool buildBounds(size_t i, BBox3fa* bbox = nullptr) const + { + const Quad& q = quad(i); + if (q.v[0] >= numVertices()) return false; + if (q.v[1] >= numVertices()) return false; + if (q.v[2] >= numVertices()) return false; + if (q.v[3] >= numVertices()) return false; + + for (unsigned int t=0; t= numVertices())) return false; + if (unlikely(q.v[1] >= numVertices())) return false; + if (unlikely(q.v[2] >= numVertices())) return false; + if (unlikely(q.v[3] >= numVertices())) return false; + + assert(itime+1 < numTimeSteps); + const Vec3fa a0 = vertex(q.v[0],itime+0); if (unlikely(!isvalid(a0))) return false; + const Vec3fa a1 = vertex(q.v[1],itime+0); if (unlikely(!isvalid(a1))) return false; + const Vec3fa a2 = vertex(q.v[2],itime+0); if (unlikely(!isvalid(a2))) return false; + const Vec3fa a3 = vertex(q.v[3],itime+0); if (unlikely(!isvalid(a3))) return false; + const Vec3fa b0 = vertex(q.v[0],itime+1); if (unlikely(!isvalid(b0))) return false; + const Vec3fa b1 = vertex(q.v[1],itime+1); if (unlikely(!isvalid(b1))) return false; + const Vec3fa b2 = vertex(q.v[2],itime+1); if (unlikely(!isvalid(b2))) return false; + const Vec3fa b3 = vertex(q.v[3],itime+1); if (unlikely(!isvalid(b3))) return false; + + /* use bounds of first time step in builder */ + bbox = BBox3fa(min(a0,a1,a2,a3),max(a0,a1,a2,a3)); + return true; + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(size_t primID, const BBox1f& dt) const { + return LBBox3fa([&] (size_t itime) { return bounds(primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline bool linearBounds(size_t i, const BBox1f& dt, LBBox3fa& bbox) const + { + if (!valid(i, timeSegmentRange(dt))) return false; + bbox = linearBounds(i, dt); + return true; + } + + /*! get fast access to first vertex buffer */ + __forceinline float * getCompactVertexArray () const { + return (float*) vertices0.getPtr(); + } + + /* gets version info of topology */ + unsigned int getTopologyVersion() const { + return quads.modCounter; + } + + /* returns true if topology changed */ + bool topologyChanged(unsigned int otherVersion) const { + return quads.isModified(otherVersion); // || numPrimitivesChanged; + } + + /* returns the projected area */ + __forceinline float projectedPrimitiveArea(const size_t i) const { + const Quad& q = quad(i); + const Vec3fa v0 = vertex(q.v[0]); + const Vec3fa v1 = vertex(q.v[1]); + const Vec3fa v2 = vertex(q.v[2]); + const Vec3fa v3 = vertex(q.v[3]); + return areaProjectedTriangle(v0,v1,v3) + + areaProjectedTriangle(v1,v2,v3); + } + + public: + BufferView quads; //!< array of quads + BufferView vertices0; //!< fast access to first vertex buffer + vector> vertices; //!< vertex array for each timestep + vector> vertexAttribs; //!< vertex attribute buffers + }; + + namespace isa + { + struct QuadMeshISA : public QuadMesh + { + QuadMeshISA (Device* device) + : QuadMesh(device) {} + + PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, const BBox1f& t0t1, const range& r, size_t k, unsigned int geomID) const + { + PrimInfoMB pinfo(empty); + for (size_t j=r.begin(); jnumTimeSegments(),this->time_range,this->numTimeSegments(),geomID,unsigned(j)); + pinfo.add_primref(prim); + prims[k++] = prim; + } + return pinfo; + } + }; + } + + DECLARE_ISA_FUNCTION(QuadMesh*, createQuadMesh, Device*); +} diff --git a/thirdparty/embree/kernels/common/scene_subdiv_mesh.h b/thirdparty/embree/kernels/common/scene_subdiv_mesh.h new file mode 100644 index 000000000000..25ee8e8efade --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_subdiv_mesh.h @@ -0,0 +1,326 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "geometry.h" +#include "buffer.h" +#include "../subdiv/half_edge.h" +#include "../subdiv/tessellation_cache.h" +#include "../subdiv/catmullclark_coefficients.h" +#include "../subdiv/patch.h" +#include "../../common/algorithms/parallel_map.h" +#include "../../common/algorithms/parallel_set.h" + +namespace embree +{ + class SubdivMesh : public Geometry + { + ALIGNED_CLASS_(16); + public: + + typedef HalfEdge::Edge Edge; + + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_SUBDIV_MESH; + + /*! structure used to sort half edges using radix sort by their key */ + struct KeyHalfEdge + { + KeyHalfEdge() {} + + KeyHalfEdge (uint64_t key, HalfEdge* edge) + : key(key), edge(edge) {} + + __forceinline operator uint64_t() const { + return key; + } + + friend __forceinline bool operator<(const KeyHalfEdge& e0, const KeyHalfEdge& e1) { + return e0.key < e1.key; + } + + public: + uint64_t key; + HalfEdge* edge; + }; + + public: + + /*! subdiv mesh construction */ + SubdivMesh(Device* device); + + public: + void setMask (unsigned mask); + void setSubdivisionMode (unsigned int topologyID, RTCSubdivisionMode mode); + void setVertexAttributeTopology(unsigned int vertexAttribID, unsigned int topologyID); + void setNumTimeSteps (unsigned int numTimeSteps); + void setVertexAttributeCount (unsigned int N); + void setTopologyCount (unsigned int N); + void setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void setTessellationRate(float N); + bool verify(); + void commit(); + void addElementsToCount (GeometryCounts & counts) const; + void setDisplacementFunction (RTCDisplacementFunctionN func); + unsigned int getFirstHalfEdge(unsigned int faceID); + unsigned int getFace(unsigned int edgeID); + unsigned int getNextHalfEdge(unsigned int edgeID); + unsigned int getPreviousHalfEdge(unsigned int edgeID); + unsigned int getOppositeHalfEdge(unsigned int topologyID, unsigned int edgeID); + + public: + + /*! return the number of faces */ + size_t numFaces() const { + return faceVertices.size(); + } + + /*! return the number of edges */ + size_t numEdges() const { + return topology[0].vertexIndices.size(); + } + + /*! return the number of vertices */ + size_t numVertices() const { + return vertices[0].size(); + } + + /*! calculates the bounds of the i'th subdivision patch at the j'th timestep */ + __forceinline BBox3fa bounds(size_t i, size_t j = 0) const { + return topology[0].getHalfEdge(i)->bounds(vertices[j]); + } + + /*! check if the i'th primitive is valid */ + __forceinline bool valid(size_t i) const { + return topology[0].valid(i) && !invalidFace(i); + } + + /*! check if the i'th primitive is valid for the j'th time range */ + __forceinline bool valid(size_t i, size_t j) const { + return topology[0].valid(i) && !invalidFace(i,j); + } + + /*! prints some statistics */ + void printStatistics(); + + /*! initializes the half edge data structure */ + void initializeHalfEdgeStructures (); + + public: + + /*! returns the vertex buffer for some time step */ + __forceinline const BufferView& getVertexBuffer( const size_t t = 0 ) const { + return vertices[t]; + } + + /* returns tessellation level of edge */ + __forceinline float getEdgeLevel(const size_t i) const + { + if (levels) return clamp(levels[i],1.0f,4096.0f); // FIXME: do we want to limit edge level? + else return clamp(tessellationRate,1.0f,4096.0f); // FIXME: do we want to limit edge level? + } + + public: + RTCDisplacementFunctionN displFunc; //!< displacement function + + /*! all buffers in this section are provided by the application */ + public: + + /*! the topology contains all data that may differ when + * interpolating different user data buffers */ + struct Topology + { + public: + + /*! Default topology construction */ + Topology () : halfEdges(nullptr,0) {} + + /*! Topology initialization */ + Topology (SubdivMesh* mesh); + + /*! make the class movable */ + public: + Topology (Topology&& other) // FIXME: this is only required to workaround compilation issues under Windows + : mesh(std::move(other.mesh)), + vertexIndices(std::move(other.vertexIndices)), + subdiv_mode(std::move(other.subdiv_mode)), + halfEdges(std::move(other.halfEdges)), + halfEdges0(std::move(other.halfEdges0)), + halfEdges1(std::move(other.halfEdges1)) {} + + Topology& operator= (Topology&& other) // FIXME: this is only required to workaround compilation issues under Windows + { + mesh = std::move(other.mesh); + vertexIndices = std::move(other.vertexIndices); + subdiv_mode = std::move(other.subdiv_mode); + halfEdges = std::move(other.halfEdges); + halfEdges0 = std::move(other.halfEdges0); + halfEdges1 = std::move(other.halfEdges1); + return *this; + } + + public: + /*! check if the i'th primitive is valid in this topology */ + __forceinline bool valid(size_t i) const + { + if (unlikely(subdiv_mode == RTC_SUBDIVISION_MODE_NO_BOUNDARY)) { + if (getHalfEdge(i)->faceHasBorder()) return false; + } + return true; + } + + /*! updates the interpolation mode for the topology */ + void setSubdivisionMode (RTCSubdivisionMode mode); + + /*! marks all buffers as modified */ + void update (); + + /*! verifies index array */ + bool verify (size_t numVertices); + + /*! initializes the half edge data structure */ + void initializeHalfEdgeStructures (); + + private: + + /*! recalculates the half edges */ + void calculateHalfEdges(); + + /*! updates half edges when recalculation is not necessary */ + void updateHalfEdges(); + + /*! user input data */ + public: + + SubdivMesh* mesh; + + /*! indices of the vertices composing each face */ + BufferView vertexIndices; + + /*! subdiv interpolation mode */ + RTCSubdivisionMode subdiv_mode; + + /*! generated data */ + public: + + /*! returns the start half edge for face f */ + __forceinline const HalfEdge* getHalfEdge ( const size_t f ) const { + return &halfEdges[mesh->faceStartEdge[f]]; + } + + /*! Half edge structure, generated by initHalfEdgeStructures */ + mvector halfEdges; + + /*! the following data is only required during construction of the + * half edge structure and can be cleared for static scenes */ + private: + + /*! two arrays used to sort the half edges */ + std::vector halfEdges0; + std::vector halfEdges1; + }; + + /*! returns the start half edge for topology t and face f */ + __forceinline const HalfEdge* getHalfEdge ( const size_t t , const size_t f ) const { + return topology[t].getHalfEdge(f); + } + + /*! buffer containing the number of vertices for each face */ + BufferView faceVertices; + + /*! array of topologies */ + vector topology; + + /*! vertex buffer (one buffer for each time step) */ + vector> vertices; + + /*! user data buffers */ + vector vertexAttribs; + + /*! edge crease buffer containing edges (pairs of vertices) that carry edge crease weights */ + BufferView edge_creases; + + /*! edge crease weights for each edge of the edge_creases buffer */ + BufferView edge_crease_weights; + + /*! vertex crease buffer containing all vertices that carry vertex crease weights */ + BufferView vertex_creases; + + /*! vertex crease weights for each vertex of the vertex_creases buffer */ + BufferView vertex_crease_weights; + + /*! subdivision level for each half edge of the vertexIndices buffer */ + BufferView levels; + float tessellationRate; // constant rate that is used when levels is not set + + /*! buffer that marks specific faces as holes */ + BufferView holes; + + /*! all data in this section is generated by initializeHalfEdgeStructures function */ + private: + + /*! number of half edges used by faces */ + size_t numHalfEdges; + + /*! fast lookup table to find the first half edge for some face */ + mvector faceStartEdge; + + /*! fast lookup table to find the face for some half edge */ + mvector halfEdgeFace; + + /*! set with all holes */ + parallel_set holeSet; + + /*! fast lookup table to detect invalid faces */ + mvector invalid_face; + + /*! test if face i is invalid in timestep j */ + __forceinline char& invalidFace(size_t i, size_t j = 0) { return invalid_face[i*numTimeSteps+j]; } + __forceinline const char& invalidFace(size_t i, size_t j = 0) const { return invalid_face[i*numTimeSteps+j]; } + + /*! interpolation cache */ + public: + static __forceinline size_t numInterpolationSlots4(size_t stride) { return (stride+15)/16; } + static __forceinline size_t numInterpolationSlots8(size_t stride) { return (stride+31)/32; } + static __forceinline size_t interpolationSlot(size_t prim, size_t slot, size_t stride) { + const size_t slots = numInterpolationSlots4(stride); + assert(slot < slots); + return slots*prim+slot; + } + std::vector> vertex_buffer_tags; + std::vector> vertex_attrib_buffer_tags; + std::vector patch_eval_trees; + + /*! the following data is only required during construction of the + * half edge structure and can be cleared for static scenes */ + private: + + /*! map with all vertex creases */ + parallel_map vertexCreaseMap; + + /*! map with all edge creases */ + parallel_map edgeCreaseMap; + + protected: + + /*! counts number of geometry commits */ + size_t commitCounter; + }; + + namespace isa + { + struct SubdivMeshISA : public SubdivMesh + { + SubdivMeshISA (Device* device) + : SubdivMesh(device) {} + + void interpolate(const RTCInterpolateArguments* const args); + void interpolateN(const RTCInterpolateNArguments* const args); + }; + } + + DECLARE_ISA_FUNCTION(SubdivMesh*, createSubdivMesh, Device*); +}; diff --git a/thirdparty/embree/kernels/common/scene_triangle_mesh.cpp b/thirdparty/embree/kernels/common/scene_triangle_mesh.cpp new file mode 100644 index 000000000000..d1c2750f14a1 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_triangle_mesh.cpp @@ -0,0 +1,243 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "scene_triangle_mesh.h" +#include "scene.h" + +namespace embree +{ +#if defined(EMBREE_LOWEST_ISA) + + TriangleMesh::TriangleMesh (Device* device) + : Geometry(device,GTY_TRIANGLE_MESH,0,1) + { + vertices.resize(numTimeSteps); + } + + void TriangleMesh::setMask (unsigned mask) + { + this->mask = mask; + Geometry::update(); + } + + void TriangleMesh::setNumTimeSteps (unsigned int numTimeSteps) + { + vertices.resize(numTimeSteps); + Geometry::setNumTimeSteps(numTimeSteps); + } + + void TriangleMesh::setVertexAttributeCount (unsigned int N) + { + vertexAttribs.resize(N); + Geometry::update(); + } + + void TriangleMesh::setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num) + { + /* verify that all accesses are 4 bytes aligned */ + if (((size_t(buffer->getPtr()) + offset) & 0x3) || (stride & 0x3)) + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "data must be 4 bytes aligned"); + + if (type == RTC_BUFFER_TYPE_VERTEX) + { + if (format != RTC_FORMAT_FLOAT3) + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid vertex buffer format"); + + /* if buffer is larger than 16GB the premultiplied index optimization does not work */ + if (stride*num > 16ll*1024ll*1024ll*1024ll) + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "vertex buffer can be at most 16GB large"); + + if (slot >= vertices.size()) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid vertex buffer slot"); + + vertices[slot].set(buffer, offset, stride, num, format); + vertices[slot].checkPadding16(); + vertices0 = vertices[0]; + } + else if (type == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE) + { + if (format < RTC_FORMAT_FLOAT || format > RTC_FORMAT_FLOAT16) + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid vertex attribute buffer format"); + + if (slot >= vertexAttribs.size()) + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid vertex attribute buffer slot"); + + vertexAttribs[slot].set(buffer, offset, stride, num, format); + vertexAttribs[slot].checkPadding16(); + } + else if (type == RTC_BUFFER_TYPE_INDEX) + { + if (slot != 0) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + if (format != RTC_FORMAT_UINT3) + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid index buffer format"); + + triangles.set(buffer, offset, stride, num, format); + setNumPrimitives(num); + } + else + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown buffer type"); + } + + void* TriangleMesh::getBuffer(RTCBufferType type, unsigned int slot) + { + if (type == RTC_BUFFER_TYPE_INDEX) + { + if (slot != 0) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + return triangles.getPtr(); + } + else if (type == RTC_BUFFER_TYPE_VERTEX) + { + if (slot >= vertices.size()) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + return vertices[slot].getPtr(); + } + else if (type == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE) + { + if (slot >= vertexAttribs.size()) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + return vertexAttribs[slot].getPtr(); + } + else + { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown buffer type"); + return nullptr; + } + } + + void TriangleMesh::updateBuffer(RTCBufferType type, unsigned int slot) + { + if (type == RTC_BUFFER_TYPE_INDEX) + { + if (slot != 0) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + triangles.setModified(); + } + else if (type == RTC_BUFFER_TYPE_VERTEX) + { + if (slot >= vertices.size()) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + vertices[slot].setModified(); + } + else if (type == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE) + { + if (slot >= vertexAttribs.size()) + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid buffer slot"); + vertexAttribs[slot].setModified(); + } + else + { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown buffer type"); + } + + Geometry::update(); + } + + void TriangleMesh::commit() + { + /* verify that stride of all time steps are identical */ + for (unsigned int t=0; t= numVertices()) return false; + if (triangles[i].v[1] >= numVertices()) return false; + if (triangles[i].v[2] >= numVertices()) return false; + } + + /*! verify vertices */ + for (const auto& buffer : vertices) + for (size_t i=0; iprimID; + float u = args->u; + float v = args->v; + RTCBufferType bufferType = args->bufferType; + unsigned int bufferSlot = args->bufferSlot; + float* P = args->P; + float* dPdu = args->dPdu; + float* dPdv = args->dPdv; + float* ddPdudu = args->ddPdudu; + float* ddPdvdv = args->ddPdvdv; + float* ddPdudv = args->ddPdudv; + unsigned int valueCount = args->valueCount; + + /* calculate base pointer and stride */ + assert((bufferType == RTC_BUFFER_TYPE_VERTEX && bufferSlot < numTimeSteps) || + (bufferType == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE && bufferSlot <= vertexAttribs.size())); + const char* src = nullptr; + size_t stride = 0; + if (bufferType == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE) { + src = vertexAttribs[bufferSlot].getPtr(); + stride = vertexAttribs[bufferSlot].getStride(); + } else { + src = vertices[bufferSlot].getPtr(); + stride = vertices[bufferSlot].getStride(); + } + + for (unsigned int i=0; i& buffer, size_t offset, size_t stride, unsigned int num); + void* getBuffer(RTCBufferType type, unsigned int slot); + void updateBuffer(RTCBufferType type, unsigned int slot); + void commit(); + bool verify(); + void interpolate(const RTCInterpolateArguments* const args); + void addElementsToCount (GeometryCounts & counts) const; + + public: + + /*! returns number of vertices */ + __forceinline size_t numVertices() const { + return vertices[0].size(); + } + + /*! returns i'th triangle*/ + __forceinline const Triangle& triangle(size_t i) const { + return triangles[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline const Vec3fa vertex(size_t i) const { + return vertices0[i]; + } + + /*! returns i'th vertex of the first time step */ + __forceinline const char* vertexPtr(size_t i) const { + return vertices0.getPtr(i); + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const Vec3fa vertex(size_t i, size_t itime) const { + return vertices[itime][i]; + } + + /*! returns i'th vertex of itime'th timestep */ + __forceinline const char* vertexPtr(size_t i, size_t itime) const { + return vertices[itime].getPtr(i); + } + + /*! calculates the bounds of the i'th triangle */ + __forceinline BBox3fa bounds(size_t i) const + { + const Triangle& tri = triangle(i); + const Vec3fa v0 = vertex(tri.v[0]); + const Vec3fa v1 = vertex(tri.v[1]); + const Vec3fa v2 = vertex(tri.v[2]); + return BBox3fa(min(v0,v1,v2),max(v0,v1,v2)); + } + + /*! calculates the bounds of the i'th triangle at the itime'th timestep */ + __forceinline BBox3fa bounds(size_t i, size_t itime) const + { + const Triangle& tri = triangle(i); + const Vec3fa v0 = vertex(tri.v[0],itime); + const Vec3fa v1 = vertex(tri.v[1],itime); + const Vec3fa v2 = vertex(tri.v[2],itime); + return BBox3fa(min(v0,v1,v2),max(v0,v1,v2)); + } + + /*! check if the i'th primitive is valid at the itime'th timestep */ + __forceinline bool valid(size_t i, size_t itime) const { + return valid(i, make_range(itime, itime)); + } + + /*! check if the i'th primitive is valid between the specified time range */ + __forceinline bool valid(size_t i, const range& itime_range) const + { + const Triangle& tri = triangle(i); + if (unlikely(tri.v[0] >= numVertices())) return false; + if (unlikely(tri.v[1] >= numVertices())) return false; + if (unlikely(tri.v[2] >= numVertices())) return false; + + for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) + { + if (!isvalid(vertex(tri.v[0],itime))) return false; + if (!isvalid(vertex(tri.v[1],itime))) return false; + if (!isvalid(vertex(tri.v[2],itime))) return false; + } + + return true; + } + + /*! calculates the linear bounds of the i'th primitive at the itimeGlobal'th time segment */ + __forceinline LBBox3fa linearBounds(size_t i, size_t itime) const { + return LBBox3fa(bounds(i,itime+0),bounds(i,itime+1)); + } + + /*! calculates the build bounds of the i'th primitive, if it's valid */ + __forceinline bool buildBounds(size_t i, BBox3fa* bbox = nullptr) const + { + const Triangle& tri = triangle(i); + if (unlikely(tri.v[0] >= numVertices())) return false; + if (unlikely(tri.v[1] >= numVertices())) return false; + if (unlikely(tri.v[2] >= numVertices())) return false; + + for (size_t t=0; t= numVertices())) return false; + if (unlikely(tri.v[1] >= numVertices())) return false; + if (unlikely(tri.v[2] >= numVertices())) return false; + + assert(itime+1 < numTimeSteps); + const Vec3fa a0 = vertex(tri.v[0],itime+0); if (unlikely(!isvalid(a0))) return false; + const Vec3fa a1 = vertex(tri.v[1],itime+0); if (unlikely(!isvalid(a1))) return false; + const Vec3fa a2 = vertex(tri.v[2],itime+0); if (unlikely(!isvalid(a2))) return false; + const Vec3fa b0 = vertex(tri.v[0],itime+1); if (unlikely(!isvalid(b0))) return false; + const Vec3fa b1 = vertex(tri.v[1],itime+1); if (unlikely(!isvalid(b1))) return false; + const Vec3fa b2 = vertex(tri.v[2],itime+1); if (unlikely(!isvalid(b2))) return false; + + /* use bounds of first time step in builder */ + bbox = BBox3fa(min(a0,a1,a2),max(a0,a1,a2)); + return true; + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline LBBox3fa linearBounds(size_t primID, const BBox1f& dt) const { + return LBBox3fa([&] (size_t itime) { return bounds(primID, itime); }, dt, time_range, fnumTimeSegments); + } + + /*! calculates the linear bounds of the i'th primitive for the specified time range */ + __forceinline bool linearBounds(size_t i, const BBox1f& dt, LBBox3fa& bbox) const { + if (!valid(i, timeSegmentRange(dt))) return false; + bbox = linearBounds(i, dt); + return true; + } + + /*! get fast access to first vertex buffer */ + __forceinline float * getCompactVertexArray () const { + return (float*) vertices0.getPtr(); + } + + /* gets version info of topology */ + unsigned int getTopologyVersion() const { + return triangles.modCounter; + } + + /* returns true if topology changed */ + bool topologyChanged(unsigned int otherVersion) const { + return triangles.isModified(otherVersion); // || numPrimitivesChanged; + } + + /* returns the projected area */ + __forceinline float projectedPrimitiveArea(const size_t i) const { + const Triangle& tri = triangle(i); + const Vec3fa v0 = vertex(tri.v[0]); + const Vec3fa v1 = vertex(tri.v[1]); + const Vec3fa v2 = vertex(tri.v[2]); + return areaProjectedTriangle(v0,v1,v2); + } + + public: + BufferView triangles; //!< array of triangles + BufferView vertices0; //!< fast access to first vertex buffer + vector> vertices; //!< vertex array for each timestep + vector vertexAttribs; //!< vertex attributes + }; + + namespace isa + { + struct TriangleMeshISA : public TriangleMesh + { + TriangleMeshISA (Device* device) + : TriangleMesh(device) {} + + PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, const BBox1f& t0t1, const range& r, size_t k, unsigned int geomID) const + { + PrimInfoMB pinfo(empty); + for (size_t j=r.begin(); jnumTimeSegments(),this->time_range,this->numTimeSegments(),geomID,unsigned(j)); + pinfo.add_primref(prim); + prims[k++] = prim; + } + return pinfo; + } + }; + } + + DECLARE_ISA_FUNCTION(TriangleMesh*, createTriangleMesh, Device*); +} diff --git a/thirdparty/embree/kernels/common/scene_user_geometry.h b/thirdparty/embree/kernels/common/scene_user_geometry.h new file mode 100644 index 000000000000..8d11ed698605 --- /dev/null +++ b/thirdparty/embree/kernels/common/scene_user_geometry.h @@ -0,0 +1,77 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "accelset.h" + +namespace embree +{ + /*! User geometry with user defined intersection functions */ + struct UserGeometry : public AccelSet + { + /*! type of this geometry */ + static const Geometry::GTypeMask geom_type = Geometry::MTY_USER_GEOMETRY; + + public: + UserGeometry (Device* device, unsigned int items = 0, unsigned int numTimeSteps = 1); + virtual void setMask (unsigned mask); + virtual void setBoundsFunction (RTCBoundsFunction bounds, void* userPtr); + virtual void setIntersectFunctionN (RTCIntersectFunctionN intersect); + virtual void setOccludedFunctionN (RTCOccludedFunctionN occluded); + virtual void build() {} + virtual void addElementsToCount (GeometryCounts & counts) const; + }; + + namespace isa + { + struct UserGeometryISA : public UserGeometry + { + UserGeometryISA (Device* device) + : UserGeometry(device) {} + + PrimInfo createPrimRefArray(mvector& prims, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, size_t itime, const range& r, size_t k, unsigned int geomID) const + { + PrimInfo pinfo(empty); + for (size_t j=r.begin(); j& prims, const BBox1f& t0t1, const range& r, size_t k, unsigned int geomID) const + { + PrimInfoMB pinfo(empty); + for (size_t j=r.begin(); jnumTimeSegments(),this->time_range,this->numTimeSegments(),geomID,unsigned(j)); + pinfo.add_primref(prim); + prims[k++] = prim; + } + return pinfo; + } + }; + } + + DECLARE_ISA_FUNCTION(UserGeometry*, createUserGeometry, Device*); +} diff --git a/thirdparty/embree/kernels/common/stack_item.h b/thirdparty/embree/kernels/common/stack_item.h new file mode 100644 index 000000000000..533c3853652e --- /dev/null +++ b/thirdparty/embree/kernels/common/stack_item.h @@ -0,0 +1,125 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" + +namespace embree +{ + /*! An item on the stack holds the node ID and distance of that node. */ + template + struct __aligned(16) StackItemT + { + /*! assert that the xchg function works */ + static_assert(sizeof(T) <= 12, "sizeof(T) <= 12 failed"); + + __forceinline StackItemT() {} + + __forceinline StackItemT(T &ptr, unsigned &dist) : ptr(ptr), dist(dist) {} + + /*! use SSE instructions to swap stack items */ + __forceinline static void xchg(StackItemT& a, StackItemT& b) + { + const vfloat4 sse_a = vfloat4::load((float*)&a); + const vfloat4 sse_b = vfloat4::load((float*)&b); + vfloat4::store(&a,sse_b); + vfloat4::store(&b,sse_a); + } + + /*! Sort 2 stack items. */ + __forceinline friend void sort(StackItemT& s1, StackItemT& s2) { + if (s2.dist < s1.dist) xchg(s2,s1); + } + + /*! Sort 3 stack items. */ + __forceinline friend void sort(StackItemT& s1, StackItemT& s2, StackItemT& s3) + { + if (s2.dist < s1.dist) xchg(s2,s1); + if (s3.dist < s2.dist) xchg(s3,s2); + if (s2.dist < s1.dist) xchg(s2,s1); + } + + /*! Sort 4 stack items. */ + __forceinline friend void sort(StackItemT& s1, StackItemT& s2, StackItemT& s3, StackItemT& s4) + { + if (s2.dist < s1.dist) xchg(s2,s1); + if (s4.dist < s3.dist) xchg(s4,s3); + if (s3.dist < s1.dist) xchg(s3,s1); + if (s4.dist < s2.dist) xchg(s4,s2); + if (s3.dist < s2.dist) xchg(s3,s2); + } + + /*! use SSE instructions to swap stack items */ + __forceinline static void cmp_xchg(vint4& a, vint4& b) + { +#if defined(__AVX512VL__) + const vboolf4 mask(shuffle<2,2,2,2>(b) < shuffle<2,2,2,2>(a)); +#else + const vboolf4 mask0(b < a); + const vboolf4 mask(shuffle<2,2,2,2>(mask0)); +#endif + const vint4 c = select(mask,b,a); + const vint4 d = select(mask,a,b); + a = c; + b = d; + } + + /*! Sort 3 stack items. */ + __forceinline static void sort3(vint4& s1, vint4& s2, vint4& s3) + { + cmp_xchg(s2,s1); + cmp_xchg(s3,s2); + cmp_xchg(s2,s1); + } + + /*! Sort 4 stack items. */ + __forceinline static void sort4(vint4& s1, vint4& s2, vint4& s3, vint4& s4) + { + cmp_xchg(s2,s1); + cmp_xchg(s4,s3); + cmp_xchg(s3,s1); + cmp_xchg(s4,s2); + cmp_xchg(s3,s2); + } + + + /*! Sort N stack items. */ + __forceinline friend void sort(StackItemT* begin, StackItemT* end) + { + for (StackItemT* i = begin+1; i != end; ++i) + { + const vfloat4 item = vfloat4::load((float*)i); + const unsigned dist = i->dist; + StackItemT* j = i; + + while ((j != begin) && ((j-1)->dist < dist)) + { + vfloat4::store(j, vfloat4::load((float*)(j-1))); + --j; + } + + vfloat4::store(j, item); + } + } + + public: + T ptr; + unsigned dist; + }; + + /*! An item on the stack holds the node ID and active ray mask. */ + template + struct __aligned(8) StackItemMaskT + { + T ptr; + size_t mask; + }; + + struct __aligned(8) StackItemMaskCoherent + { + size_t mask; + size_t parent; + size_t child; + }; +} diff --git a/thirdparty/embree/kernels/common/stat.cpp b/thirdparty/embree/kernels/common/stat.cpp new file mode 100644 index 000000000000..b73c3a8c7663 --- /dev/null +++ b/thirdparty/embree/kernels/common/stat.cpp @@ -0,0 +1,128 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "stat.h" + +namespace embree +{ + Stat Stat::instance; + + Stat::Stat () { + } + + Stat::~Stat () + { +#ifdef EMBREE_STAT_COUNTERS + Stat::print(std::cout); +#endif + } + + void Stat::print(std::ostream& cout) + { + Counters& cntrs = instance.cntrs; + Counters::Data& data = instance.cntrs.code; + //Counters::Data& data = instance.cntrs.active; + + /* print absolute numbers */ + cout << "--------- ABSOLUTE ---------" << std::endl; + cout << " #normal_travs = " << float(data.normal.travs )*1E-6 << "M" << std::endl; + cout << " #nodes = " << float(data.normal.trav_nodes )*1E-6 << "M" << std::endl; + cout << " #nodes_xfm = " << float(data.normal.trav_xfm_nodes )*1E-6 << "M" << std::endl; + cout << " #leaves = " << float(data.normal.trav_leaves )*1E-6 << "M" << std::endl; + cout << " #prims = " << float(data.normal.trav_prims )*1E-6 << "M" << std::endl; + cout << " #prim_hits = " << float(data.normal.trav_prim_hits )*1E-6 << "M" << std::endl; + + cout << " #stack nodes = " << float(data.normal.trav_stack_nodes )*1E-6 << "M" << std::endl; + cout << " #stack pop = " << float(data.normal.trav_stack_pop )*1E-6 << "M" << std::endl; + + size_t normal_box_hits = 0; + size_t weighted_box_hits = 0; + for (size_t i=0;i travs; + std::atomic trav_nodes; + std::atomic trav_leaves; + std::atomic trav_prims; + std::atomic trav_prim_hits; + std::atomic trav_hit_boxes[SIZE_HISTOGRAM+1]; + std::atomic trav_stack_pop; + std::atomic trav_stack_nodes; + std::atomic trav_xfm_nodes; + + } normal, shadow, point_query; + } all, active, code; + + std::atomic user[10]; + }; + + public: + + static __forceinline Counters& get() { + return instance.cntrs; + } + + static void clear() { + instance.cntrs.clear(); + } + + static void print(embree_ostream cout); + + private: + Counters cntrs; + + private: + static Stat instance; + }; +} diff --git a/thirdparty/embree/kernels/common/state.cpp b/thirdparty/embree/kernels/common/state.cpp new file mode 100644 index 000000000000..e9a912d7667d --- /dev/null +++ b/thirdparty/embree/kernels/common/state.cpp @@ -0,0 +1,528 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "state.h" +#include "../../common/lexers/streamfilters.h" + +namespace embree +{ + MutexSys g_printMutex; + + State::ErrorHandler State::g_errorHandler; + + State::ErrorHandler::ErrorHandler() + : thread_error(createTls()) {} + + State::ErrorHandler::~ErrorHandler() + { + Lock lock(errors_mutex); + for (size_t i=0; i lock(errors_mutex); + stored_error = new RTCError(RTC_ERROR_NONE); + thread_errors.push_back(stored_error); + setTls(thread_error,stored_error); + return stored_error; + } + + State::State () + : enabled_cpu_features(getCPUFeatures()), + enabled_builder_cpu_features(enabled_cpu_features), + frequency_level(FREQUENCY_SIMD256) + { + tri_accel = "default"; + tri_builder = "default"; + tri_traverser = "default"; + + tri_accel_mb = "default"; + tri_builder_mb = "default"; + tri_traverser_mb = "default"; + + quad_accel = "default"; + quad_builder = "default"; + quad_traverser = "default"; + + quad_accel_mb = "default"; + quad_builder_mb = "default"; + quad_traverser_mb = "default"; + + line_accel = "default"; + line_builder = "default"; + line_traverser = "default"; + + line_accel_mb = "default"; + line_builder_mb = "default"; + line_traverser_mb = "default"; + + hair_accel = "default"; + hair_builder = "default"; + hair_traverser = "default"; + + hair_accel_mb = "default"; + hair_builder_mb = "default"; + hair_traverser_mb = "default"; + + object_accel = "default"; + object_builder = "default"; + object_accel_min_leaf_size = 1; + object_accel_max_leaf_size = 1; + + object_accel_mb = "default"; + object_builder_mb = "default"; + object_accel_mb_min_leaf_size = 1; + object_accel_mb_max_leaf_size = 1; + + max_spatial_split_replications = 1.2f; + useSpatialPreSplits = false; + + tessellation_cache_size = 128*1024*1024; + + subdiv_accel = "default"; + subdiv_accel_mb = "default"; + + grid_accel = "default"; + grid_builder = "default"; + grid_accel_mb = "default"; + grid_builder_mb = "default"; + + instancing_open_min = 0; + instancing_block_size = 0; + instancing_open_factor = 8.0f; + instancing_open_max_depth = 32; + instancing_open_max = 50000000; + + ignore_config_files = false; + float_exceptions = false; + quality_flags = -1; + scene_flags = -1; + verbose = 0; + benchmark = 0; + + numThreads = 0; + numUserThreads = 0; + +#if TASKING_INTERNAL + set_affinity = true; +#else + set_affinity = false; +#endif + /* per default enable affinity on KNL */ + if (hasISA(AVX512KNL)) set_affinity = true; + + start_threads = false; + enable_selockmemoryprivilege = false; +#if defined(__LINUX__) + hugepages = true; +#else + hugepages = false; +#endif + hugepages_success = true; + + alloc_main_block_size = 0; + alloc_num_main_slots = 0; + alloc_thread_block_size = 0; + alloc_single_thread_alloc = -1; + + error_function = nullptr; + error_function_userptr = nullptr; + + memory_monitor_function = nullptr; + memory_monitor_userptr = nullptr; + } + + State::~State() { + } + + bool State::hasISA(const int isa) { + return (enabled_cpu_features & isa) == isa; + } + + bool State::checkISASupport() { + return (getCPUFeatures() & enabled_cpu_features) == enabled_cpu_features; + } + + void State::verify() + { + /* verify that calculations stay in range */ + assert(rcp(min_rcp_input)*FLT_LARGE+FLT_LARGE < 0.01f*FLT_MAX); + + /* here we verify that CPP files compiled for a specific ISA only + * call that same or lower ISA version of non-inlined class member + * functions */ +#if defined(DEBUG) +#if defined(EMBREE_TARGET_SSE2) + assert(sse2::getISA() <= SSE2); +#endif +#if defined(EMBREE_TARGET_SSE42) + assert(sse42::getISA() <= SSE42); +#endif +#if defined(EMBREE_TARGET_AVX) + assert(avx::getISA() <= AVX); +#endif +#if defined(EMBREE_TARGET_AVX2) + assert(avx2::getISA() <= AVX2); +#endif +#if defined (EMBREE_TARGET_AVX512KNL) + assert(avx512knl::getISA() <= AVX512KNL); +#endif +#if defined (EMBREE_TARGET_AVX512SKX) + assert(avx512skx::getISA() <= AVX512SKX); +#endif +#endif + } + + const char* symbols[3] = { "=", ",", "|" }; + + bool State::parseFile(const FileName& fileName) + { + FILE* f = fopen(fileName.c_str(),"r"); + if (!f) return false; + Ref > file = new FileStream(f,fileName); + + std::vector syms; + for (size_t i=0; i cin = new TokenStream(new LineCommentFilter(file,"#"), + TokenStream::alpha+TokenStream::ALPHA+TokenStream::numbers+"_.", + TokenStream::separators,syms); + parse(cin); + return true; + } + + void State::parseString(const char* cfg) + { + if (cfg == nullptr) return; + + std::vector syms; + for (size_t i=0; i cin = new TokenStream(new StrStream(cfg), + TokenStream::alpha+TokenStream::ALPHA+TokenStream::numbers+"_.", + TokenStream::separators,syms); + parse(cin); + } + + int string_to_cpufeatures(const std::string& isa) + { + if (isa == "sse" ) return SSE; + else if (isa == "sse2") return SSE2; + else if (isa == "sse3") return SSE3; + else if (isa == "ssse3") return SSSE3; + else if (isa == "sse41") return SSE41; + else if (isa == "sse4.1") return SSE41; + else if (isa == "sse42") return SSE42; + else if (isa == "sse4.2") return SSE42; + else if (isa == "avx") return AVX; + else if (isa == "avxi") return AVXI; + else if (isa == "avx2") return AVX2; + else if (isa == "avx512knl") return AVX512KNL; + else if (isa == "avx512skx") return AVX512SKX; + else return SSE2; + } + + void State::parse(Ref cin) + { + /* parse until end of stream */ + while (cin->peek() != Token::Eof()) + { + const Token tok = cin->get(); + + if (tok == Token::Id("threads") && cin->trySymbol("=")) + numThreads = cin->get().Int(); + + else if (tok == Token::Id("user_threads")&& cin->trySymbol("=")) + numUserThreads = cin->get().Int(); + + else if (tok == Token::Id("set_affinity")&& cin->trySymbol("=")) + set_affinity = cin->get().Int(); + + else if (tok == Token::Id("affinity")&& cin->trySymbol("=")) + set_affinity = cin->get().Int(); + + else if (tok == Token::Id("start_threads")&& cin->trySymbol("=")) + start_threads = cin->get().Int(); + + else if (tok == Token::Id("isa") && cin->trySymbol("=")) { + std::string isa = toLowerCase(cin->get().Identifier()); + enabled_cpu_features = string_to_cpufeatures(isa); + enabled_builder_cpu_features = enabled_cpu_features; + } + + else if (tok == Token::Id("max_isa") && cin->trySymbol("=")) { + std::string isa = toLowerCase(cin->get().Identifier()); + enabled_cpu_features &= string_to_cpufeatures(isa); + enabled_builder_cpu_features &= enabled_cpu_features; + } + + else if (tok == Token::Id("max_builder_isa") && cin->trySymbol("=")) { + std::string isa = toLowerCase(cin->get().Identifier()); + enabled_builder_cpu_features &= string_to_cpufeatures(isa); + } + + else if (tok == Token::Id("frequency_level") && cin->trySymbol("=")) { + std::string freq = cin->get().Identifier(); + if (freq == "simd128") frequency_level = FREQUENCY_SIMD128; + else if (freq == "simd256") frequency_level = FREQUENCY_SIMD256; + else if (freq == "simd512") frequency_level = FREQUENCY_SIMD512; + } + + else if (tok == Token::Id("enable_selockmemoryprivilege") && cin->trySymbol("=")) { + enable_selockmemoryprivilege = cin->get().Int(); + } + else if (tok == Token::Id("hugepages") && cin->trySymbol("=")) { + hugepages = cin->get().Int(); + } + + else if (tok == Token::Id("ignore_config_files") && cin->trySymbol("=")) + ignore_config_files = cin->get().Int(); + else if (tok == Token::Id("float_exceptions") && cin->trySymbol("=")) + float_exceptions = cin->get().Int(); + + else if ((tok == Token::Id("tri_accel") || tok == Token::Id("accel")) && cin->trySymbol("=")) + tri_accel = cin->get().Identifier(); + else if ((tok == Token::Id("tri_builder") || tok == Token::Id("builder")) && cin->trySymbol("=")) + tri_builder = cin->get().Identifier(); + else if ((tok == Token::Id("tri_traverser") || tok == Token::Id("traverser")) && cin->trySymbol("=")) + tri_traverser = cin->get().Identifier(); + + else if ((tok == Token::Id("tri_accel_mb") || tok == Token::Id("accel_mb")) && cin->trySymbol("=")) + tri_accel_mb = cin->get().Identifier(); + else if ((tok == Token::Id("tri_builder_mb") || tok == Token::Id("builder_mb")) && cin->trySymbol("=")) + tri_builder_mb = cin->get().Identifier(); + else if ((tok == Token::Id("tri_traverser_mb") || tok == Token::Id("traverser_mb")) && cin->trySymbol("=")) + tri_traverser_mb = cin->get().Identifier(); + + else if ((tok == Token::Id("quad_accel")) && cin->trySymbol("=")) + quad_accel = cin->get().Identifier(); + else if ((tok == Token::Id("quad_builder")) && cin->trySymbol("=")) + quad_builder = cin->get().Identifier(); + else if ((tok == Token::Id("quad_traverser")) && cin->trySymbol("=")) + quad_traverser = cin->get().Identifier(); + + else if ((tok == Token::Id("quad_accel_mb")) && cin->trySymbol("=")) + quad_accel_mb = cin->get().Identifier(); + else if ((tok == Token::Id("quad_builder_mb")) && cin->trySymbol("=")) + quad_builder_mb = cin->get().Identifier(); + else if ((tok == Token::Id("quad_traverser_mb")) && cin->trySymbol("=")) + quad_traverser_mb = cin->get().Identifier(); + + else if ((tok == Token::Id("line_accel")) && cin->trySymbol("=")) + line_accel = cin->get().Identifier(); + else if ((tok == Token::Id("line_builder")) && cin->trySymbol("=")) + line_builder = cin->get().Identifier(); + else if ((tok == Token::Id("line_traverser")) && cin->trySymbol("=")) + line_traverser = cin->get().Identifier(); + + else if ((tok == Token::Id("line_accel_mb")) && cin->trySymbol("=")) + line_accel_mb = cin->get().Identifier(); + else if ((tok == Token::Id("line_builder_mb")) && cin->trySymbol("=")) + line_builder_mb = cin->get().Identifier(); + else if ((tok == Token::Id("line_traverser_mb")) && cin->trySymbol("=")) + line_traverser_mb = cin->get().Identifier(); + + else if (tok == Token::Id("hair_accel") && cin->trySymbol("=")) + hair_accel = cin->get().Identifier(); + else if (tok == Token::Id("hair_builder") && cin->trySymbol("=")) + hair_builder = cin->get().Identifier(); + else if (tok == Token::Id("hair_traverser") && cin->trySymbol("=")) + hair_traverser = cin->get().Identifier(); + + else if (tok == Token::Id("hair_accel_mb") && cin->trySymbol("=")) + hair_accel_mb = cin->get().Identifier(); + else if (tok == Token::Id("hair_builder_mb") && cin->trySymbol("=")) + hair_builder_mb = cin->get().Identifier(); + else if (tok == Token::Id("hair_traverser_mb") && cin->trySymbol("=")) + hair_traverser_mb = cin->get().Identifier(); + + else if (tok == Token::Id("object_accel") && cin->trySymbol("=")) + object_accel = cin->get().Identifier(); + else if (tok == Token::Id("object_builder") && cin->trySymbol("=")) + object_builder = cin->get().Identifier(); + else if (tok == Token::Id("object_accel_min_leaf_size") && cin->trySymbol("=")) + object_accel_min_leaf_size = cin->get().Int(); + else if (tok == Token::Id("object_accel_max_leaf_size") && cin->trySymbol("=")) + object_accel_max_leaf_size = cin->get().Int(); + + else if (tok == Token::Id("object_accel_mb") && cin->trySymbol("=")) + object_accel_mb = cin->get().Identifier(); + else if (tok == Token::Id("object_builder_mb") && cin->trySymbol("=")) + object_builder_mb = cin->get().Identifier(); + else if (tok == Token::Id("object_accel_mb_min_leaf_size") && cin->trySymbol("=")) + object_accel_mb_min_leaf_size = cin->get().Int(); + else if (tok == Token::Id("object_accel_mb_max_leaf_size") && cin->trySymbol("=")) + object_accel_mb_max_leaf_size = cin->get().Int(); + + else if (tok == Token::Id("instancing_open_min") && cin->trySymbol("=")) + instancing_open_min = cin->get().Int(); + else if (tok == Token::Id("instancing_block_size") && cin->trySymbol("=")) { + instancing_block_size = cin->get().Int(); + instancing_open_factor = 0.0f; + } + else if (tok == Token::Id("instancing_open_max_depth") && cin->trySymbol("=")) + instancing_open_max_depth = cin->get().Int(); + else if (tok == Token::Id("instancing_open_factor") && cin->trySymbol("=")) { + instancing_block_size = 0; + instancing_open_factor = cin->get().Float(); + } + else if (tok == Token::Id("instancing_open_max") && cin->trySymbol("=")) + instancing_open_max = cin->get().Int(); + + else if (tok == Token::Id("subdiv_accel") && cin->trySymbol("=")) + subdiv_accel = cin->get().Identifier(); + else if (tok == Token::Id("subdiv_accel_mb") && cin->trySymbol("=")) + subdiv_accel_mb = cin->get().Identifier(); + + else if (tok == Token::Id("grid_accel") && cin->trySymbol("=")) + grid_accel = cin->get().Identifier(); + else if (tok == Token::Id("grid_accel_mb") && cin->trySymbol("=")) + grid_accel_mb = cin->get().Identifier(); + + else if (tok == Token::Id("verbose") && cin->trySymbol("=")) + verbose = cin->get().Int(); + else if (tok == Token::Id("benchmark") && cin->trySymbol("=")) + benchmark = cin->get().Int(); + + else if (tok == Token::Id("quality")) { + if (cin->trySymbol("=")) { + Token flag = cin->get(); + if (flag == Token::Id("low")) quality_flags = RTC_BUILD_QUALITY_LOW; + else if (flag == Token::Id("medium")) quality_flags = RTC_BUILD_QUALITY_MEDIUM; + else if (flag == Token::Id("high")) quality_flags = RTC_BUILD_QUALITY_HIGH; + } + } + + else if (tok == Token::Id("scene_flags")) { + scene_flags = 0; + if (cin->trySymbol("=")) { + do { + Token flag = cin->get(); + if (flag == Token::Id("dynamic") ) scene_flags |= RTC_SCENE_FLAG_DYNAMIC; + else if (flag == Token::Id("compact")) scene_flags |= RTC_SCENE_FLAG_COMPACT; + else if (flag == Token::Id("robust")) scene_flags |= RTC_SCENE_FLAG_ROBUST; + } while (cin->trySymbol("|")); + } + } + + else if (tok == Token::Id("max_spatial_split_replications") && cin->trySymbol("=")) + max_spatial_split_replications = cin->get().Float(); + + else if (tok == Token::Id("presplits") && cin->trySymbol("=")) + useSpatialPreSplits = cin->get().Int() != 0 ? true : false; + + else if (tok == Token::Id("tessellation_cache_size") && cin->trySymbol("=")) + tessellation_cache_size = size_t(cin->get().Float()*1024.0f*1024.0f); + else if (tok == Token::Id("cache_size") && cin->trySymbol("=")) + tessellation_cache_size = size_t(cin->get().Float()*1024.0f*1024.0f); + + else if (tok == Token::Id("alloc_main_block_size") && cin->trySymbol("=")) + alloc_main_block_size = cin->get().Int(); + else if (tok == Token::Id("alloc_num_main_slots") && cin->trySymbol("=")) + alloc_num_main_slots = cin->get().Int(); + else if (tok == Token::Id("alloc_thread_block_size") && cin->trySymbol("=")) + alloc_thread_block_size = cin->get().Int(); + else if (tok == Token::Id("alloc_single_thread_alloc") && cin->trySymbol("=")) + alloc_single_thread_alloc = cin->get().Int(); + + cin->trySymbol(","); // optional , separator + } + } + + bool State::verbosity(size_t N) { + return N <= verbose; + } + + void State::print() + { + std::cout << "general:" << std::endl; + std::cout << " build threads = " << numThreads << std::endl; + std::cout << " build user threads = " << numUserThreads << std::endl; + std::cout << " start_threads = " << start_threads << std::endl; + std::cout << " affinity = " << set_affinity << std::endl; + std::cout << " frequency_level = "; + switch (frequency_level) { + case FREQUENCY_SIMD128: std::cout << "simd128" << std::endl; break; + case FREQUENCY_SIMD256: std::cout << "simd256" << std::endl; break; + case FREQUENCY_SIMD512: std::cout << "simd512" << std::endl; break; + default: std::cout << "error" << std::endl; break; + } + + std::cout << " hugepages = "; + if (!hugepages) std::cout << "disabled" << std::endl; + else if (hugepages_success) std::cout << "enabled" << std::endl; + else std::cout << "failed" << std::endl; + + std::cout << " verbosity = " << verbose << std::endl; + std::cout << " cache_size = " << float(tessellation_cache_size)*1E-6 << " MB" << std::endl; + std::cout << " max_spatial_split_replications = " << max_spatial_split_replications << std::endl; + + std::cout << "triangles:" << std::endl; + std::cout << " accel = " << tri_accel << std::endl; + std::cout << " builder = " << tri_builder << std::endl; + std::cout << " traverser = " << tri_traverser << std::endl; + + std::cout << "motion blur triangles:" << std::endl; + std::cout << " accel = " << tri_accel_mb << std::endl; + std::cout << " builder = " << tri_builder_mb << std::endl; + std::cout << " traverser = " << tri_traverser_mb << std::endl; + + std::cout << "quads:" << std::endl; + std::cout << " accel = " << quad_accel << std::endl; + std::cout << " builder = " << quad_builder << std::endl; + std::cout << " traverser = " << quad_traverser << std::endl; + + std::cout << "motion blur quads:" << std::endl; + std::cout << " accel = " << quad_accel_mb << std::endl; + std::cout << " builder = " << quad_builder_mb << std::endl; + std::cout << " traverser = " << quad_traverser_mb << std::endl; + + std::cout << "line segments:" << std::endl; + std::cout << " accel = " << line_accel << std::endl; + std::cout << " builder = " << line_builder << std::endl; + std::cout << " traverser = " << line_traverser << std::endl; + + std::cout << "motion blur line segments:" << std::endl; + std::cout << " accel = " << line_accel_mb << std::endl; + std::cout << " builder = " << line_builder_mb << std::endl; + std::cout << " traverser = " << line_traverser_mb << std::endl; + + std::cout << "hair:" << std::endl; + std::cout << " accel = " << hair_accel << std::endl; + std::cout << " builder = " << hair_builder << std::endl; + std::cout << " traverser = " << hair_traverser << std::endl; + + std::cout << "motion blur hair:" << std::endl; + std::cout << " accel = " << hair_accel_mb << std::endl; + std::cout << " builder = " << hair_builder_mb << std::endl; + std::cout << " traverser = " << hair_traverser_mb << std::endl; + + std::cout << "subdivision surfaces:" << std::endl; + std::cout << " accel = " << subdiv_accel << std::endl; + + std::cout << "grids:" << std::endl; + std::cout << " accel = " << grid_accel << std::endl; + std::cout << " builder = " << grid_builder << std::endl; + + std::cout << "motion blur grids:" << std::endl; + std::cout << " accel = " << grid_accel_mb << std::endl; + std::cout << " builder = " << grid_builder_mb << std::endl; + + std::cout << "object_accel:" << std::endl; + std::cout << " min_leaf_size = " << object_accel_min_leaf_size << std::endl; + std::cout << " max_leaf_size = " << object_accel_max_leaf_size << std::endl; + + std::cout << "object_accel_mb:" << std::endl; + std::cout << " min_leaf_size = " << object_accel_mb_min_leaf_size << std::endl; + std::cout << " max_leaf_size = " << object_accel_mb_max_leaf_size << std::endl; + } +} diff --git a/thirdparty/embree/kernels/common/state.h b/thirdparty/embree/kernels/common/state.h new file mode 100644 index 000000000000..d0fccc023f8a --- /dev/null +++ b/thirdparty/embree/kernels/common/state.h @@ -0,0 +1,197 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "default.h" + +namespace embree +{ + /* mutex to make printing to cout thread safe */ + extern MutexSys g_printMutex; + + struct State : public RefCount + { + public: + /*! state construction */ + State (); + + /*! state destruction */ + ~State(); + + /*! verifies that state is correct */ + void verify(); + + /*! parses state from a configuration file */ + bool parseFile(const FileName& fileName); + + /*! parses the state from a string */ + void parseString(const char* cfg); + + /*! parses the state from a stream */ + void parse(Ref cin); + + /*! prints the state */ + void print(); + + /*! checks if verbosity level is at least N */ + bool verbosity(size_t N); + + /*! checks if some particular ISA is enabled */ + bool hasISA(const int isa); + + /*! check whether selected ISA is supported by the HW */ + bool checkISASupport(); + + public: + std::string tri_accel; //!< acceleration structure to use for triangles + std::string tri_builder; //!< builder to use for triangles + std::string tri_traverser; //!< traverser to use for triangles + + public: + std::string tri_accel_mb; //!< acceleration structure to use for motion blur triangles + std::string tri_builder_mb; //!< builder to use for motion blur triangles + std::string tri_traverser_mb; //!< traverser to use for triangles + + public: + std::string quad_accel; //!< acceleration structure to use for quads + std::string quad_builder; //!< builder to use for quads + std::string quad_traverser; //!< traverser to use for quads + + public: + std::string quad_accel_mb; //!< acceleration structure to use for motion blur quads + std::string quad_builder_mb; //!< builder to use for motion blur quads + std::string quad_traverser_mb; //!< traverser to use for motion blur quads + + public: + std::string line_accel; //!< acceleration structure to use for line segments + std::string line_builder; //!< builder to use for line segments + std::string line_traverser; //!< traverser to use for line segments + + public: + std::string line_accel_mb; //!< acceleration structure to use for motion blur line segments + std::string line_builder_mb; //!< builder to use for motion blur line segments + std::string line_traverser_mb; //!< traverser to use for motion blur line segments + + public: + std::string hair_accel; //!< hair acceleration structure to use + std::string hair_builder; //!< builder to use for hair + std::string hair_traverser; //!< traverser to use for hair + + public: + std::string hair_accel_mb; //!< acceleration structure to use for motion blur hair + std::string hair_builder_mb; //!< builder to use for motion blur hair + std::string hair_traverser_mb; //!< traverser to use for motion blur hair + + public: + std::string object_accel; //!< acceleration structure for user geometries + std::string object_builder; //!< builder for user geometries + int object_accel_min_leaf_size; //!< minimum leaf size for object acceleration structure + int object_accel_max_leaf_size; //!< maximum leaf size for object acceleration structure + + public: + std::string object_accel_mb; //!< acceleration structure for user geometries + std::string object_builder_mb; //!< builder for user geometries + int object_accel_mb_min_leaf_size; //!< minimum leaf size for mblur object acceleration structure + int object_accel_mb_max_leaf_size; //!< maximum leaf size for mblur object acceleration structure + + public: + std::string subdiv_accel; //!< acceleration structure to use for subdivision surfaces + std::string subdiv_accel_mb; //!< acceleration structure to use for subdivision surfaces + + public: + std::string grid_accel; //!< acceleration structure to use for grids + std::string grid_builder; //!< builder for grids + std::string grid_accel_mb; //!< acceleration structure to use for motion blur grids + std::string grid_builder_mb; //!< builder for motion blur grids + + public: + float max_spatial_split_replications; //!< maximally replications*N many primitives in accel for spatial splits + bool useSpatialPreSplits; //!< use spatial pre-splits instead of the full spatial split builder + size_t tessellation_cache_size; //!< size of the shared tessellation cache + + public: + size_t instancing_open_min; //!< instancing opens tree to minimally that number of subtrees + size_t instancing_block_size; //!< instancing opens tree up to average block size of primitives + float instancing_open_factor; //!< instancing opens tree up to x times the number of instances + size_t instancing_open_max_depth; //!< maximum open depth for geometries + size_t instancing_open_max; //!< instancing opens tree to maximally that number of subtrees + + public: + bool ignore_config_files; //!< if true no more config files get parse + bool float_exceptions; //!< enable floating point exceptions + int quality_flags; + int scene_flags; + size_t verbose; //!< verbosity of output + size_t benchmark; //!< true + + public: + size_t numThreads; //!< number of threads to use in builders + size_t numUserThreads; //!< number of user provided threads to use in builders + bool set_affinity; //!< sets affinity for worker threads + bool start_threads; //!< true when threads should be started at device creation time + int enabled_cpu_features; //!< CPU ISA features to use + int enabled_builder_cpu_features; //!< CPU ISA features to use for builders only + enum FREQUENCY_LEVEL { + FREQUENCY_SIMD128, + FREQUENCY_SIMD256, + FREQUENCY_SIMD512 + } frequency_level; //!< frequency level the app wants to run on (default is SIMD256) + bool enable_selockmemoryprivilege; //!< configures the SeLockMemoryPrivilege under Windows to enable huge pages + bool hugepages; //!< true if huge pages should get used + bool hugepages_success; //!< status for enabling huge pages + + public: + size_t alloc_main_block_size; //!< main allocation block size (shared between threads) + int alloc_num_main_slots; //!< number of such shared blocks to be used to allocate + size_t alloc_thread_block_size; //!< size of thread local allocator block size + int alloc_single_thread_alloc; //!< in single mode nodes and leaves use same thread local allocator + + public: + + /*! checks if we can use AVX */ + bool canUseAVX() { + return hasISA(AVX) && frequency_level != FREQUENCY_SIMD128; + } + + /*! checks if we can use AVX2 */ + bool canUseAVX2() { + return hasISA(AVX2) && frequency_level != FREQUENCY_SIMD128; + } + + struct ErrorHandler + { + public: + ErrorHandler(); + ~ErrorHandler(); + RTCError* error(); + + public: + tls_t thread_error; + std::vector thread_errors; + MutexSys errors_mutex; + }; + ErrorHandler errorHandler; + static ErrorHandler g_errorHandler; + + public: + void setErrorFunction(RTCErrorFunction fptr, void* uptr) + { + error_function = fptr; + error_function_userptr = uptr; + } + + RTCErrorFunction error_function; + void* error_function_userptr; + + public: + void setMemoryMonitorFunction(RTCMemoryMonitorFunction fptr, void* uptr) + { + memory_monitor_function = fptr; + memory_monitor_userptr = uptr; + } + + RTCMemoryMonitorFunction memory_monitor_function; + void* memory_monitor_userptr; + }; +} diff --git a/thirdparty/embree/kernels/common/vector.h b/thirdparty/embree/kernels/common/vector.h new file mode 100644 index 000000000000..b47876224034 --- /dev/null +++ b/thirdparty/embree/kernels/common/vector.h @@ -0,0 +1,76 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "default.h" + +namespace embree +{ + /*! invokes the memory monitor callback */ + struct MemoryMonitorInterface { + virtual void memoryMonitor(ssize_t bytes, bool post) = 0; + }; + + /*! allocator that performs aligned monitored allocations */ + template + struct aligned_monitored_allocator + { + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + __forceinline aligned_monitored_allocator(MemoryMonitorInterface* device) + : device(device), hugepages(false) {} + + __forceinline pointer allocate( size_type n ) + { + if (n) { + assert(device); + device->memoryMonitor(n*sizeof(T),false); + } + if (n*sizeof(value_type) >= 14 * PAGE_SIZE_2M) + { + pointer p = (pointer) os_malloc(n*sizeof(value_type),hugepages); + assert(p); + return p; + } + return (pointer) alignedMalloc(n*sizeof(value_type),alignment); + } + + __forceinline void deallocate( pointer p, size_type n ) + { + if (p) + { + if (n*sizeof(value_type) >= 14 * PAGE_SIZE_2M) + os_free(p,n*sizeof(value_type),hugepages); + else + alignedFree(p); + } + else assert(n == 0); + + if (n) { + assert(device); + device->memoryMonitor(-ssize_t(n)*sizeof(T),true); + } + } + + __forceinline void construct( pointer p, const_reference val ) { + new (p) T(val); + } + + __forceinline void destroy( pointer p ) { + p->~T(); + } + + private: + MemoryMonitorInterface* device; + bool hugepages; + }; + + /*! monitored vector */ + template + using mvector = vector_t::value> >; +} diff --git a/thirdparty/embree/kernels/config.h b/thirdparty/embree/kernels/config.h new file mode 100644 index 000000000000..80a8ab2a564d --- /dev/null +++ b/thirdparty/embree/kernels/config.h @@ -0,0 +1,76 @@ + +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +/* #undef EMBREE_RAY_MASK */ +/* #undef EMBREE_STAT_COUNTERS */ +/* #undef EMBREE_BACKFACE_CULLING */ +/* #undef EMBREE_BACKFACE_CULLING_CURVES */ +#define EMBREE_FILTER_FUNCTION +/* #undef EMBREE_IGNORE_INVALID_RAYS */ +#define EMBREE_GEOMETRY_TRIANGLE +/* #undef EMBREE_GEOMETRY_QUAD */ +/* #undef EMBREE_GEOMETRY_CURVE */ +/* #undef EMBREE_GEOMETRY_SUBDIVISION */ +/* #undef EMBREE_GEOMETRY_USER */ +/* #undef EMBREE_GEOMETRY_INSTANCE */ +/* #undef EMBREE_GEOMETRY_GRID */ +/* #undef EMBREE_GEOMETRY_POINT */ +/* #undef EMBREE_RAY_PACKETS */ +/* #undef EMBREE_COMPACT_POLYS */ + +#define EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR 2.0 + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + #define IF_ENABLED_TRIS(x) x +#else + #define IF_ENABLED_TRIS(x) +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + #define IF_ENABLED_QUADS(x) x +#else + #define IF_ENABLED_QUADS(x) +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) || defined(EMBREE_GEOMETRY_POINT) + #define IF_ENABLED_CURVES_OR_POINTS(x) x +#else + #define IF_ENABLED_CURVES_OR_POINTS(x) +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) + #define IF_ENABLED_CURVES(x) x +#else + #define IF_ENABLED_CURVES(x) +#endif + +#if defined(EMBREE_GEOMETRY_POINT) + #define IF_ENABLED_POINTS(x) x +#else + #define IF_ENABLED_POINTS(x) +#endif + +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + #define IF_ENABLED_SUBDIV(x) x +#else + #define IF_ENABLED_SUBDIV(x) +#endif + +#if defined(EMBREE_GEOMETRY_USER) + #define IF_ENABLED_USER(x) x +#else + #define IF_ENABLED_USER(x) +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + #define IF_ENABLED_INSTANCE(x) x +#else + #define IF_ENABLED_INSTANCE(x) +#endif + +#if defined(EMBREE_GEOMETRY_GRID) + #define IF_ENABLED_GRIDS(x) x +#else + #define IF_ENABLED_GRIDS(x) +#endif diff --git a/thirdparty/embree/kernels/geometry/cone.h b/thirdparty/embree/kernels/geometry/cone.h new file mode 100644 index 000000000000..961ef86160aa --- /dev/null +++ b/thirdparty/embree/kernels/geometry/cone.h @@ -0,0 +1,321 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" + +namespace embree +{ + namespace isa + { + struct Cone + { + const Vec3fa p0; //!< start position of cone + const Vec3fa p1; //!< end position of cone + const float r0; //!< start radius of cone + const float r1; //!< end radius of cone + + __forceinline Cone(const Vec3fa& p0, const float r0, const Vec3fa& p1, const float r1) + : p0(p0), p1(p1), r0(r0), r1(r1) {} + + __forceinline bool intersect(const Vec3fa& org, const Vec3fa& dir, + BBox1f& t_o, + float& u0_o, Vec3fa& Ng0_o, + float& u1_o, Vec3fa& Ng1_o) const + { + /* calculate quadratic equation to solve */ + const Vec3fa v0 = p0-org; + const Vec3fa v1 = p1-org; + + const float rl = rcp_length(v1-v0); + const Vec3fa P0 = v0, dP = (v1-v0)*rl; + const float dr = (r1-r0)*rl; + const Vec3fa O = -P0, dO = dir; + + const float dOdO = dot(dO,dO); + const float OdO = dot(dO,O); + const float OO = dot(O,O); + const float dOz = dot(dP,dO); + const float Oz = dot(dP,O); + + const float R = r0 + Oz*dr; + const float A = dOdO - sqr(dOz) * (1.0f+sqr(dr)); + const float B = 2.0f * (OdO - dOz*(Oz + R*dr)); + const float C = OO - (sqr(Oz) + sqr(R)); + + /* we miss the cone if determinant is smaller than zero */ + const float D = B*B - 4.0f*A*C; + if (D < 0.0f) return false; + + /* special case for rays that are "parallel" to the cone */ + const float eps = float(1<<8)*float(ulp)*max(abs(dOdO),abs(sqr(dOz))); + if (unlikely(abs(A) < eps)) + { + /* cylinder case */ + if (abs(dr) < 16.0f*float(ulp)) { + if (C <= 0.0f) { t_o = BBox1f(neg_inf,pos_inf); return true; } + else { t_o = BBox1f(pos_inf,neg_inf); return false; } + } + + /* cone case */ + else + { + /* if we hit the negative cone there cannot be a hit */ + const float t = -C/B; + const float z0 = Oz+t*dOz; + const float z0r = r0+z0*dr; + if (z0r < 0.0f) return false; + + /* test if we start inside or outside the cone */ + if (dOz*dr > 0.0f) t_o = BBox1f(t,pos_inf); + else t_o = BBox1f(neg_inf,t); + } + } + + /* standard case for "non-parallel" rays */ + else + { + const float Q = sqrt(D); + const float rcp_2A = rcp(2.0f*A); + t_o.lower = (-B-Q)*rcp_2A; + t_o.upper = (-B+Q)*rcp_2A; + + /* standard case where both hits are on same cone */ + if (likely(A > 0.0f)) { + const float z0 = Oz+t_o.lower*dOz; + const float z0r = r0+z0*dr; + if (z0r < 0.0f) return false; + } + + /* special case where the hits are on the positive and negative cone */ + else + { + /* depending on the ray direction and the open direction + * of the cone we have a hit from inside or outside the + * cone */ + if (dOz*dr > 0) t_o.upper = pos_inf; + else t_o.lower = neg_inf; + } + } + + /* calculates u and Ng for near hit */ + { + u0_o = (Oz+t_o.lower*dOz)*rl; + const Vec3fa Pr = t_o.lower*dir; + const Vec3fa Pl = v0 + u0_o*(v1-v0); + const Vec3fa R = normalize(Pr-Pl); + const Vec3fa U = (p1-p0)+(r1-r0)*R; + const Vec3fa V = cross(p1-p0,R); + Ng0_o = cross(V,U); + } + + /* calculates u and Ng for far hit */ + { + u1_o = (Oz+t_o.upper*dOz)*rl; + const Vec3fa Pr = t_o.upper*dir; + const Vec3fa Pl = v0 + u1_o*(v1-v0); + const Vec3fa R = normalize(Pr-Pl); + const Vec3fa U = (p1-p0)+(r1-r0)*R; + const Vec3fa V = cross(p1-p0,R); + Ng1_o = cross(V,U); + } + return true; + } + + __forceinline bool intersect(const Vec3fa& org, const Vec3fa& dir, BBox1f& t_o) const + { + float u0_o; Vec3fa Ng0_o; float u1_o; Vec3fa Ng1_o; + return intersect(org,dir,t_o,u0_o,Ng0_o,u1_o,Ng1_o); + } + + static bool verify(const size_t id, const Cone& cone, const Ray& ray, bool shouldhit, const float t0, const float t1) + { + float eps = 0.001f; + BBox1f t; bool hit; + hit = cone.intersect(ray.org,ray.dir,t); + + bool failed = hit != shouldhit; + if (shouldhit) failed |= std::isinf(t0) ? t0 != t.lower : (t0 == -1E6) ? t.lower > -1E6f : abs(t0-t.lower) > eps; + if (shouldhit) failed |= std::isinf(t1) ? t1 != t.upper : (t1 == +1E6) ? t.upper < +1E6f : abs(t1-t.upper) > eps; + if (!failed) return true; + embree_cout << "Cone test " << id << " failed: cone = " << cone << ", ray = " << ray << ", hit = " << hit << ", t = " << t << embree_endl; + return false; + } + + /* verify cone class */ + static bool verify() + { + bool passed = true; + const Cone cone0(Vec3fa(0.0f,0.0f,0.0f),0.0f,Vec3fa(1.0f,0.0f,0.0f),1.0f); + passed &= verify(0,cone0,Ray(Vec3fa(-2.0f,1.0f,0.0f),Vec3fa(+1.0f,+0.0f,+0.0f),0.0f,float(inf)),true,3.0f,pos_inf); + passed &= verify(1,cone0,Ray(Vec3fa(+2.0f,1.0f,0.0f),Vec3fa(-1.0f,+0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,1.0f); + passed &= verify(2,cone0,Ray(Vec3fa(-1.0f,0.0f,2.0f),Vec3fa(+0.0f,+0.0f,-1.0f),0.0f,float(inf)),false,0.0f,0.0f); + passed &= verify(3,cone0,Ray(Vec3fa(+1.0f,0.0f,2.0f),Vec3fa(+0.0f,+0.0f,-1.0f),0.0f,float(inf)),true,1.0f,3.0f); + passed &= verify(4,cone0,Ray(Vec3fa(-1.0f,0.0f,0.0f),Vec3fa(+1.0f,+0.0f,+0.0f),0.0f,float(inf)),true,1.0f,pos_inf); + passed &= verify(5,cone0,Ray(Vec3fa(+1.0f,0.0f,0.0f),Vec3fa(-1.0f,+0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,1.0f); + passed &= verify(6,cone0,Ray(Vec3fa(+0.0f,0.0f,1.0f),Vec3fa(+0.0f,+0.0f,-1.0f),0.0f,float(inf)),true,1.0f,1.0f); + passed &= verify(7,cone0,Ray(Vec3fa(+0.0f,1.0f,0.0f),Vec3fa(-1.0f,-1.0f,+0.0f),0.0f,float(inf)),false,0.0f,0.0f); + passed &= verify(8,cone0,Ray(Vec3fa(+0.0f,1.0f,0.0f),Vec3fa(+1.0f,-1.0f,+0.0f),0.0f,float(inf)),true,0.5f,+1E6); + passed &= verify(9,cone0,Ray(Vec3fa(+0.0f,1.0f,0.0f),Vec3fa(-1.0f,+1.0f,+0.0f),0.0f,float(inf)),true,-1E6,-0.5f); + const Cone cone1(Vec3fa(0.0f,0.0f,0.0f),1.0f,Vec3fa(1.0f,0.0f,0.0f),0.0f); + passed &= verify(10,cone1,Ray(Vec3fa(-2.0f,1.0f,0.0f),Vec3fa(+1.0f,+0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,2.0f); + passed &= verify(11,cone1,Ray(Vec3fa(-1.0f,0.0f,2.0f),Vec3fa(+0.0f,+0.0f,-1.0f),0.0f,float(inf)),true,0.0f,4.0f); + const Cone cylinder(Vec3fa(0.0f,0.0f,0.0f),1.0f,Vec3fa(1.0f,0.0f,0.0f),1.0f); + passed &= verify(12,cylinder,Ray(Vec3fa(-2.0f,1.0f,0.0f),Vec3fa( 0.0f,-1.0f,+0.0f),0.0f,float(inf)),true,0.0f,2.0f); + passed &= verify(13,cylinder,Ray(Vec3fa(+2.0f,1.0f,0.0f),Vec3fa( 0.0f,-1.0f,+0.0f),0.0f,float(inf)),true,0.0f,2.0f); + passed &= verify(14,cylinder,Ray(Vec3fa(+2.0f,1.0f,2.0f),Vec3fa( 0.0f,-1.0f,+0.0f),0.0f,float(inf)),false,0.0f,0.0f); + passed &= verify(15,cylinder,Ray(Vec3fa(+0.0f,0.0f,0.0f),Vec3fa( 1.0f, 0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,pos_inf); + passed &= verify(16,cylinder,Ray(Vec3fa(+0.0f,0.0f,0.0f),Vec3fa(-1.0f, 0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,pos_inf); + passed &= verify(17,cylinder,Ray(Vec3fa(+0.0f,2.0f,0.0f),Vec3fa( 1.0f, 0.0f,+0.0f),0.0f,float(inf)),false,pos_inf,neg_inf); + passed &= verify(18,cylinder,Ray(Vec3fa(+0.0f,2.0f,0.0f),Vec3fa(-1.0f, 0.0f,+0.0f),0.0f,float(inf)),false,pos_inf,neg_inf); + return passed; + } + + /*! output operator */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const Cone& c) { + return cout << "Cone { p0 = " << c.p0 << ", r0 = " << c.r0 << ", p1 = " << c.p1 << ", r1 = " << c.r1 << "}"; + } + }; + + template + struct ConeN + { + typedef Vec3> Vec3vfN; + + const Vec3vfN p0; //!< start position of cone + const Vec3vfN p1; //!< end position of cone + const vfloat r0; //!< start radius of cone + const vfloat r1; //!< end radius of cone + + __forceinline ConeN(const Vec3vfN& p0, const vfloat& r0, const Vec3vfN& p1, const vfloat& r1) + : p0(p0), p1(p1), r0(r0), r1(r1) {} + + __forceinline Cone operator[] (const size_t i) const + { + assert(i intersect(const Vec3fa& org, const Vec3fa& dir, + BBox>& t_o, + vfloat& u0_o, Vec3vfN& Ng0_o, + vfloat& u1_o, Vec3vfN& Ng1_o) const + { + /* calculate quadratic equation to solve */ + const Vec3vfN v0 = p0-Vec3vfN(org); + const Vec3vfN v1 = p1-Vec3vfN(org); + + const vfloat rl = rcp_length(v1-v0); + const Vec3vfN P0 = v0, dP = (v1-v0)*rl; + const vfloat dr = (r1-r0)*rl; + const Vec3vfN O = -P0, dO = dir; + + const vfloat dOdO = dot(dO,dO); + const vfloat OdO = dot(dO,O); + const vfloat OO = dot(O,O); + const vfloat dOz = dot(dP,dO); + const vfloat Oz = dot(dP,O); + + const vfloat R = r0 + Oz*dr; + const vfloat A = dOdO - sqr(dOz) * (vfloat(1.0f)+sqr(dr)); + const vfloat B = 2.0f * (OdO - dOz*(Oz + R*dr)); + const vfloat C = OO - (sqr(Oz) + sqr(R)); + + /* we miss the cone if determinant is smaller than zero */ + const vfloat D = B*B - 4.0f*A*C; + vbool valid = D >= 0.0f; + if (none(valid)) return valid; + + /* special case for rays that are "parallel" to the cone */ + const vfloat eps = float(1<<8)*float(ulp)*max(abs(dOdO),abs(sqr(dOz))); + const vbool validt = valid & (abs(A) < eps); + const vbool validf = valid & !(abs(A) < eps); + if (unlikely(any(validt))) + { + const vboolx validtt = validt & (abs(dr) < 16.0f*float(ulp)); + const vboolx validtf = validt & (abs(dr) >= 16.0f*float(ulp)); + + /* cylinder case */ + if (unlikely(any(validtt))) + { + t_o.lower = select(validtt, select(C <= 0.0f, vfloat(neg_inf), vfloat(pos_inf)), t_o.lower); + t_o.upper = select(validtt, select(C <= 0.0f, vfloat(pos_inf), vfloat(neg_inf)), t_o.upper); + valid &= !validtt | C <= 0.0f; + } + + /* cone case */ + if (any(validtf)) + { + /* if we hit the negative cone there cannot be a hit */ + const vfloat t = -C/B; + const vfloat z0 = Oz+t*dOz; + const vfloat z0r = r0+z0*dr; + valid &= !validtf | z0r >= 0.0f; + + /* test if we start inside or outside the cone */ + t_o.lower = select(validtf, select(dOz*dr > 0.0f, t, vfloat(neg_inf)), t_o.lower); + t_o.upper = select(validtf, select(dOz*dr > 0.0f, vfloat(pos_inf), t), t_o.upper); + } + } + + /* standard case for "non-parallel" rays */ + if (likely(any(validf))) + { + const vfloat Q = sqrt(D); + const vfloat rcp_2A = 0.5f*rcp(A); + t_o.lower = select(validf, (-B-Q)*rcp_2A, t_o.lower); + t_o.upper = select(validf, (-B+Q)*rcp_2A, t_o.upper); + + /* standard case where both hits are on same cone */ + const vbool validft = validf & A>0.0f; + const vbool validff = validf & !(A>0.0f); + if (any(validft)) { + const vfloat z0 = Oz+t_o.lower*dOz; + const vfloat z0r = r0+z0*dr; + valid &= !validft | z0r >= 0.0f; + } + + /* special case where the hits are on the positive and negative cone */ + if (any(validff)) { + /* depending on the ray direction and the open direction + * of the cone we have a hit from inside or outside the + * cone */ + t_o.lower = select(validff, select(dOz*dr > 0.0f, t_o.lower, float(neg_inf)), t_o.lower); + t_o.upper = select(validff, select(dOz*dr > 0.0f, float(pos_inf), t_o.upper), t_o.upper); + } + } + + /* calculates u and Ng for near hit */ + { + u0_o = (Oz+t_o.lower*dOz)*rl; + const Vec3vfN Pr = t_o.lower*Vec3vfN(dir); + const Vec3vfN Pl = v0 + u0_o*(v1-v0); + const Vec3vfN R = normalize(Pr-Pl); + const Vec3vfN U = (p1-p0)+(r1-r0)*R; + const Vec3vfN V = cross(p1-p0,R); + Ng0_o = cross(V,U); + } + + /* calculates u and Ng for far hit */ + { + u1_o = (Oz+t_o.upper*dOz)*rl; + const Vec3vfN Pr = t_o.lower*Vec3vfN(dir); + const Vec3vfN Pl = v0 + u1_o*(v1-v0); + const Vec3vfN R = normalize(Pr-Pl); + const Vec3vfN U = (p1-p0)+(r1-r0)*R; + const Vec3vfN V = cross(p1-p0,R); + Ng1_o = cross(V,U); + } + return valid; + } + + __forceinline vbool intersect(const Vec3fa& org, const Vec3fa& dir, BBox>& t_o) const + { + vfloat u0_o; Vec3vfN Ng0_o; vfloat u1_o; Vec3vfN Ng1_o; + return intersect(org,dir,t_o,u0_o,Ng0_o,u1_o,Ng1_o); + } + }; + } +} + diff --git a/thirdparty/embree/kernels/geometry/coneline_intersector.h b/thirdparty/embree/kernels/geometry/coneline_intersector.h new file mode 100644 index 000000000000..0902baff7db0 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/coneline_intersector.h @@ -0,0 +1,209 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + namespace isa + { + namespace __coneline_internal + { + template + static __forceinline bool intersectCone(const vbool& valid_i, + const Vec3vf& ray_org_in, const Vec3vf& ray_dir, + const vfloat& ray_tnear, const ray_tfar_func& ray_tfar, + const Vec4vf& v0, const Vec4vf& v1, + const vbool& cL, const vbool& cR, + const Epilog& epilog) + { + vbool valid = valid_i; + + /* move ray origin closer to make calculations numerically stable */ + const vfloat dOdO = sqr(ray_dir); + const vfloat rcp_dOdO = rcp(dOdO); + const Vec3vf center = vfloat(0.5f)*(v0.xyz()+v1.xyz()); + const vfloat dt = dot(center-ray_org_in,ray_dir)*rcp_dOdO; + const Vec3vf ray_org = ray_org_in + dt*ray_dir; + + const Vec3vf dP = v1.xyz() - v0.xyz(); + const Vec3vf p0 = ray_org - v0.xyz(); + const Vec3vf p1 = ray_org - v1.xyz(); + + const vfloat dPdP = sqr(dP); + const vfloat dP0 = dot(p0,dP); + const vfloat dP1 = dot(p1,dP); + const vfloat dOdP = dot(ray_dir,dP); + + // intersect cone body + const vfloat dr = v0.w - v1.w; + const vfloat hy = dPdP + sqr(dr); + const vfloat dO0 = dot(ray_dir,p0); + const vfloat OO = sqr(p0); + const vfloat dPdP2 = sqr(dPdP); + const vfloat dPdPr0 = dPdP*v0.w; + + const vfloat A = dPdP2 - sqr(dOdP)*hy; + const vfloat B = dPdP2*dO0 - dP0*dOdP*hy + dPdPr0*(dr*dOdP); + const vfloat C = dPdP2*OO - sqr(dP0)*hy + dPdPr0*(2.0f*dr*dP0 - dPdPr0); + + const vfloat D = B*B - A*C; + valid &= D >= 0.0f; + if (unlikely(none(valid))) { + return false; + } + + /* standard case for "non-parallel" rays */ + const vfloat Q = sqrt(D); + const vfloat rcp_A = rcp(A); + /* special case for rays that are "parallel" to the cone - assume miss */ + const vbool isParallel = abs(A) <= min_rcp_input; + + vfloat t_cone_lower = select (isParallel, neg_inf, (-B-Q)*rcp_A); + vfloat t_cone_upper = select (isParallel, pos_inf, (-B+Q)*rcp_A); + const vfloat y_lower = dP0 + t_cone_lower*dOdP; + const vfloat y_upper = dP0 + t_cone_upper*dOdP; + t_cone_lower = select(valid & y_lower > 0.0f & y_lower < dPdP, t_cone_lower, pos_inf); + t_cone_upper = select(valid & y_upper > 0.0f & y_upper < dPdP, t_cone_upper, neg_inf); + + const vbool hitDisk0 = valid & cL; + const vbool hitDisk1 = valid & cR; + const vfloat rcp_dOdP = rcp(dOdP); + const vfloat t_disk0 = select (hitDisk0, select (sqr(p0*dOdP-ray_dir*dP0)<(sqr(v0.w)*sqr(dOdP)), -dP0*rcp_dOdP, pos_inf), pos_inf); + const vfloat t_disk1 = select (hitDisk1, select (sqr(p1*dOdP-ray_dir*dP1)<(sqr(v1.w)*sqr(dOdP)), -dP1*rcp_dOdP, pos_inf), pos_inf); + const vfloat t_disk_lower = min(t_disk0, t_disk1); + const vfloat t_disk_upper = max(t_disk0, t_disk1); + + const vfloat t_lower = min(t_cone_lower, t_disk_lower); + const vfloat t_upper = max(t_cone_upper, select(t_lower==t_disk_lower, + select(t_disk_upper==vfloat(pos_inf),neg_inf,t_disk_upper), + select(t_disk_lower==vfloat(pos_inf),neg_inf,t_disk_lower))); + + const vbool valid_lower = valid & ray_tnear <= dt+t_lower & dt+t_lower <= ray_tfar() & t_lower != vfloat(pos_inf); + const vbool valid_upper = valid & ray_tnear <= dt+t_upper & dt+t_upper <= ray_tfar() & t_upper != vfloat(neg_inf); + + const vbool valid_first = valid_lower | valid_upper; + if (unlikely(none(valid_first))) + return false; + + const vfloat t_first = select(valid_lower, t_lower, t_upper); + const vfloat y_first = select(valid_lower, y_lower, y_upper); + + const vfloat rcp_dPdP = rcp(dPdP); + const Vec3vf dP2drr0dP = dPdP*dr*v0.w*dP; + const Vec3vf dPhy = dP*hy; + const vbool cone_hit_first = valid & (t_first == t_cone_lower | t_first == t_cone_upper); + const vbool disk0_hit_first = valid & (t_first == t_disk0); + const Vec3vf Ng_first = select(cone_hit_first, dPdP2*(p0+t_first*ray_dir)+dP2drr0dP-dPhy*y_first, select(disk0_hit_first, -dP, dP)); + const vfloat u_first = select(cone_hit_first, y_first*rcp_dPdP, select(disk0_hit_first, vfloat(zero), vfloat(one))); + + /* invoke intersection filter for first hit */ + RoundLineIntersectorHitM hit(u_first,zero,dt+t_first,Ng_first); + const bool is_hit_first = epilog(valid_first, hit); + + /* check for possible second hits before potentially accepted hit */ + const vfloat t_second = t_upper; + const vfloat y_second = y_upper; + const vbool valid_second = valid_lower & valid_upper & (dt+t_upper <= ray_tfar()); + if (unlikely(none(valid_second))) + return is_hit_first; + + /* invoke intersection filter for second hit */ + const vbool cone_hit_second = t_second == t_cone_lower | t_second == t_cone_upper; + const vbool disk0_hit_second = t_second == t_disk0; + const Vec3vf Ng_second = select(cone_hit_second, dPdP2*(p0+t_second*ray_dir)+dP2drr0dP-dPhy*y_second, select(disk0_hit_second, -dP, dP)); + const vfloat u_second = select(cone_hit_second, y_second*rcp_dPdP, select(disk0_hit_first, vfloat(zero), vfloat(one))); + + hit = RoundLineIntersectorHitM(u_second,zero,dt+t_second,Ng_second); + const bool is_hit_second = epilog(valid_second, hit); + + return is_hit_first | is_hit_second; + } + } + + template + struct ConeLineIntersectorHitM + { + __forceinline ConeLineIntersectorHitM() {} + + __forceinline ConeLineIntersectorHitM(const vfloat& u, const vfloat& v, const vfloat& t, const Vec3vf& Ng) + : vu(u), vv(v), vt(t), vNg(Ng) {} + + __forceinline void finalize() {} + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + public: + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + struct ConeCurveIntersector1 + { + typedef CurvePrecalculations1 Precalculations; + + struct ray_tfar { + Ray& ray; + __forceinline ray_tfar(Ray& ray) : ray(ray) {} + __forceinline vfloat operator() () const { return ray.tfar; }; + }; + + template + static __forceinline bool intersect(const vbool& valid_i, + Ray& ray, + IntersectContext* context, + const LineSegments* geom, + const Precalculations& pre, + const Vec4vf& v0i, const Vec4vf& v1i, + const vbool& cL, const vbool& cR, + const Epilog& epilog) + { + const Vec3vf ray_org(ray.org.x, ray.org.y, ray.org.z); + const Vec3vf ray_dir(ray.dir.x, ray.dir.y, ray.dir.z); + const vfloat ray_tnear(ray.tnear()); + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec4vf v1 = enlargeRadiusToMinWidth(context,geom,ray_org,v1i); + return __coneline_internal::intersectCone(valid_i,ray_org,ray_dir,ray_tnear,ray_tfar(ray),v0,v1,cL,cR,epilog); + } + }; + + template + struct ConeCurveIntersectorK + { + typedef CurvePrecalculationsK Precalculations; + + struct ray_tfar { + RayK& ray; + size_t k; + __forceinline ray_tfar(RayK& ray, size_t k) : ray(ray), k(k) {} + __forceinline vfloat operator() () const { return ray.tfar[k]; }; + }; + + template + static __forceinline bool intersect(const vbool& valid_i, + RayK& ray, size_t k, + IntersectContext* context, + const LineSegments* geom, + const Precalculations& pre, + const Vec4vf& v0i, const Vec4vf& v1i, + const vbool& cL, const vbool& cR, + const Epilog& epilog) + { + const Vec3vf ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const Vec3vf ray_dir(ray.dir.x[k], ray.dir.y[k], ray.dir.z[k]); + const vfloat ray_tnear = ray.tnear()[k]; + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec4vf v1 = enlargeRadiusToMinWidth(context,geom,ray_org,v1i); + return __coneline_internal::intersectCone(valid_i,ray_org,ray_dir,ray_tnear,ray_tfar(ray,k),v0,v1,cL,cR,epilog); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/conelinei_intersector.h b/thirdparty/embree/kernels/geometry/conelinei_intersector.h new file mode 100644 index 000000000000..d47218eb8b78 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/conelinei_intersector.h @@ -0,0 +1,141 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "coneline_intersector.h" +#include "intersector_epilog.h" + +namespace embree +{ + namespace isa + { + template + struct ConeCurveMiIntersector1 + { + typedef LineMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom); + const vbool valid = line.template valid(); + ConeCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,cL,cR,Intersect1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom); + const vbool valid = line.template valid(); + return ConeCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,cL,cR,Occluded1EpilogM(ray,context,line.geomID(),line.primID())); + return false; + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& line) + { + return PrimitivePointQuery1::pointQuery(query, context, line); + } + }; + + template + struct ConeCurveMiMBIntersector1 + { + typedef LineMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom,ray.time()); + const vbool valid = line.template valid(); + ConeCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,cL,cR,Intersect1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom,ray.time()); + const vbool valid = line.template valid(); + return ConeCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,cL,cR,Occluded1EpilogM(ray,context,line.geomID(),line.primID())); + return false; + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& line) + { + return PrimitivePointQuery1::pointQuery(query, context, line); + } + }; + + template + struct ConeCurveMiIntersectorK + { + typedef LineMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom); + const vbool valid = line.template valid(); + ConeCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,cL,cR,Intersect1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom); + const vbool valid = line.template valid(); + return ConeCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,cL,cR,Occluded1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + }; + + template + struct ConeCurveMiMBIntersectorK + { + typedef LineMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom,ray.time()[k]); + const vbool valid = line.template valid(); + ConeCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,cL,cR,Intersect1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; + vbool cL,cR; + line.gather(v0,v1,cL,cR,geom,ray.time()[k]); + const vbool valid = line.template valid(); + return ConeCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,cL,cR,Occluded1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curveNi.h b/thirdparty/embree/kernels/geometry/curveNi.h new file mode 100644 index 000000000000..00ca9b8b6503 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curveNi.h @@ -0,0 +1,222 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + template + struct CurveNi + { + struct Type : public PrimitiveType { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* Returns maximum number of stored primitives */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+M-1)/M; } + + static __forceinline size_t bytes(size_t N) + { + const size_t f = N/M, r = N%M; + static_assert(sizeof(CurveNi) == 22+25*M, "internal data layout issue"); + return f*sizeof(CurveNi) + (r!=0)*(22 + 25*r); + } + + public: + + /*! Default constructor. */ + __forceinline CurveNi () {} + + /*! fill curve from curve list */ + __forceinline void fill(const PrimRef* prims, size_t& begin, size_t _end, Scene* scene) + { + size_t end = min(begin+M,_end); + N = (unsigned char)(end-begin); + const unsigned int geomID0 = prims[begin].geomID(); + this->geomID(N) = geomID0; + ty = (unsigned char) scene->get(geomID0)->getType(); + + /* encode all primitives */ + BBox3fa bounds = empty; + for (size_t i=0; iget(geomID)->vbounds(primID)); + } + + /* calculate offset and scale */ + Vec3fa loffset = bounds.lower; + float lscale = reduce_min(256.0f/(bounds.size()*sqrt(3.0f))); + if (bounds.size() == Vec3fa(zero)) lscale = 0.0f; + *this->offset(N) = loffset; + *this->scale(N) = lscale; + + /* encode all primitives */ + for (size_t i=0; iget(geomID)->computeAlignedSpace(primID); + + const LinearSpace3fa space3(trunc(126.0f*space2.vx),trunc(126.0f*space2.vy),trunc(126.0f*space2.vz)); + const BBox3fa bounds = scene->get(geomID)->vbounds(loffset,lscale,max(length(space3.vx),length(space3.vy),length(space3.vz)),space3.transposed(),primID); + + bounds_vx_x(N)[i] = (char) space3.vx.x; + bounds_vx_y(N)[i] = (char) space3.vx.y; + bounds_vx_z(N)[i] = (char) space3.vx.z; + bounds_vx_lower(N)[i] = (short) clamp(floor(bounds.lower.x),-32767.0f,32767.0f); + bounds_vx_upper(N)[i] = (short) clamp(ceil (bounds.upper.x),-32767.0f,32767.0f); + assert(-32767.0f <= floor(bounds.lower.x) && floor(bounds.lower.x) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.upper.x) && ceil (bounds.upper.x) <= 32767.0f); + + bounds_vy_x(N)[i] = (char) space3.vy.x; + bounds_vy_y(N)[i] = (char) space3.vy.y; + bounds_vy_z(N)[i] = (char) space3.vy.z; + bounds_vy_lower(N)[i] = (short) clamp(floor(bounds.lower.y),-32767.0f,32767.0f); + bounds_vy_upper(N)[i] = (short) clamp(ceil (bounds.upper.y),-32767.0f,32767.0f); + assert(-32767.0f <= floor(bounds.lower.y) && floor(bounds.lower.y) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.upper.y) && ceil (bounds.upper.y) <= 32767.0f); + + bounds_vz_x(N)[i] = (char) space3.vz.x; + bounds_vz_y(N)[i] = (char) space3.vz.y; + bounds_vz_z(N)[i] = (char) space3.vz.z; + bounds_vz_lower(N)[i] = (short) clamp(floor(bounds.lower.z),-32767.0f,32767.0f); + bounds_vz_upper(N)[i] = (short) clamp(ceil (bounds.upper.z),-32767.0f,32767.0f); + assert(-32767.0f <= floor(bounds.lower.z) && floor(bounds.lower.z) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.upper.z) && ceil (bounds.upper.z) <= 32767.0f); + + this->primID(N)[i] = primID; + } + } + + template + __forceinline static typename BVH::NodeRef createLeaf (BVH* bvh, const PrimRef* prims, const range& set, const Allocator& alloc) + { + size_t start = set.begin(); + size_t items = CurveNi::blocks(set.size()); + size_t numbytes = CurveNi::bytes(set.size()); + CurveNi* accel = (CurveNi*) alloc.malloc1(numbytes,BVH::byteAlignment); + for (size_t i=0; iscene); + } + return bvh->encodeLeaf((char*)accel,items); + }; + + public: + + // 27.6 - 46 bytes per primitive + unsigned char ty; + unsigned char N; + unsigned char data[4+25*M+16]; + + /* + struct Layout + { + unsigned int geomID; + unsigned int primID[N]; + + char bounds_vx_x[N]; + char bounds_vx_y[N]; + char bounds_vx_z[N]; + short bounds_vx_lower[N]; + short bounds_vx_upper[N]; + + char bounds_vy_x[N]; + char bounds_vy_y[N]; + char bounds_vy_z[N]; + short bounds_vy_lower[N]; + short bounds_vy_upper[N]; + + char bounds_vz_x[N]; + char bounds_vz_y[N]; + char bounds_vz_z[N]; + short bounds_vz_lower[N]; + short bounds_vz_upper[N]; + + Vec3f offset; + float scale; + }; + */ + + __forceinline unsigned int& geomID(size_t N) { return *(unsigned int*)((char*)this+2); } + __forceinline const unsigned int& geomID(size_t N) const { return *(unsigned int*)((char*)this+2); } + + __forceinline unsigned int* primID(size_t N) { return (unsigned int*)((char*)this+6); } + __forceinline const unsigned int* primID(size_t N) const { return (unsigned int*)((char*)this+6); } + + __forceinline char* bounds_vx_x(size_t N) { return (char*)((char*)this+6+4*N); } + __forceinline const char* bounds_vx_x(size_t N) const { return (char*)((char*)this+6+4*N); } + + __forceinline char* bounds_vx_y(size_t N) { return (char*)((char*)this+6+5*N); } + __forceinline const char* bounds_vx_y(size_t N) const { return (char*)((char*)this+6+5*N); } + + __forceinline char* bounds_vx_z(size_t N) { return (char*)((char*)this+6+6*N); } + __forceinline const char* bounds_vx_z(size_t N) const { return (char*)((char*)this+6+6*N); } + + __forceinline short* bounds_vx_lower(size_t N) { return (short*)((char*)this+6+7*N); } + __forceinline const short* bounds_vx_lower(size_t N) const { return (short*)((char*)this+6+7*N); } + + __forceinline short* bounds_vx_upper(size_t N) { return (short*)((char*)this+6+9*N); } + __forceinline const short* bounds_vx_upper(size_t N) const { return (short*)((char*)this+6+9*N); } + + __forceinline char* bounds_vy_x(size_t N) { return (char*)((char*)this+6+11*N); } + __forceinline const char* bounds_vy_x(size_t N) const { return (char*)((char*)this+6+11*N); } + + __forceinline char* bounds_vy_y(size_t N) { return (char*)((char*)this+6+12*N); } + __forceinline const char* bounds_vy_y(size_t N) const { return (char*)((char*)this+6+12*N); } + + __forceinline char* bounds_vy_z(size_t N) { return (char*)((char*)this+6+13*N); } + __forceinline const char* bounds_vy_z(size_t N) const { return (char*)((char*)this+6+13*N); } + + __forceinline short* bounds_vy_lower(size_t N) { return (short*)((char*)this+6+14*N); } + __forceinline const short* bounds_vy_lower(size_t N) const { return (short*)((char*)this+6+14*N); } + + __forceinline short* bounds_vy_upper(size_t N) { return (short*)((char*)this+6+16*N); } + __forceinline const short* bounds_vy_upper(size_t N) const { return (short*)((char*)this+6+16*N); } + + __forceinline char* bounds_vz_x(size_t N) { return (char*)((char*)this+6+18*N); } + __forceinline const char* bounds_vz_x(size_t N) const { return (char*)((char*)this+6+18*N); } + + __forceinline char* bounds_vz_y(size_t N) { return (char*)((char*)this+6+19*N); } + __forceinline const char* bounds_vz_y(size_t N) const { return (char*)((char*)this+6+19*N); } + + __forceinline char* bounds_vz_z(size_t N) { return (char*)((char*)this+6+20*N); } + __forceinline const char* bounds_vz_z(size_t N) const { return (char*)((char*)this+6+20*N); } + + __forceinline short* bounds_vz_lower(size_t N) { return (short*)((char*)this+6+21*N); } + __forceinline const short* bounds_vz_lower(size_t N) const { return (short*)((char*)this+6+21*N); } + + __forceinline short* bounds_vz_upper(size_t N) { return (short*)((char*)this+6+23*N); } + __forceinline const short* bounds_vz_upper(size_t N) const { return (short*)((char*)this+6+23*N); } + + __forceinline Vec3f* offset(size_t N) { return (Vec3f*)((char*)this+6+25*N); } + __forceinline const Vec3f* offset(size_t N) const { return (Vec3f*)((char*)this+6+25*N); } + + __forceinline float* scale(size_t N) { return (float*)((char*)this+6+25*N+12); } + __forceinline const float* scale(size_t N) const { return (float*)((char*)this+6+25*N+12); } + + __forceinline char* end(size_t N) { return (char*)this+6+25*N+16; } + __forceinline const char* end(size_t N) const { return (char*)this+6+25*N+16; } + }; + + template + typename CurveNi::Type CurveNi::type; + + typedef CurveNi<4> Curve4i; + typedef CurveNi<8> Curve8i; +} diff --git a/thirdparty/embree/kernels/geometry/curveNi_intersector.h b/thirdparty/embree/kernels/geometry/curveNi_intersector.h new file mode 100644 index 000000000000..0f9038c9fc96 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curveNi_intersector.h @@ -0,0 +1,569 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "curveNi.h" + +namespace embree +{ + namespace isa + { + template + struct CurveNiIntersector1 + { + typedef CurveNi Primitive; + typedef Vec3vf Vec3vfM; + typedef LinearSpace3LinearSpace3vfM; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline vbool intersect(Ray& ray, const Primitive& prim, vfloat& tNear_o) + { + const size_t N = prim.N; + const vfloat4 offset_scale = vfloat4::loadu(prim.offset(N)); + const Vec3fa offset = Vec3fa(offset_scale); + const Vec3fa scale = Vec3fa(shuffle<3,3,3,3>(offset_scale)); + const Vec3fa org1 = (ray.org-offset)*scale; + const Vec3fa dir1 = ray.dir*scale; + + const LinearSpace3vfM space(vfloat::load(prim.bounds_vx_x(N)), vfloat::load(prim.bounds_vx_y(N)), vfloat::load(prim.bounds_vx_z(N)), + vfloat::load(prim.bounds_vy_x(N)), vfloat::load(prim.bounds_vy_y(N)), vfloat::load(prim.bounds_vy_z(N)), + vfloat::load(prim.bounds_vz_x(N)), vfloat::load(prim.bounds_vz_y(N)), vfloat::load(prim.bounds_vz_z(N))); + + const Vec3vfM dir2 = xfmVector(space,Vec3vfM(dir1)); + const Vec3vfM org2 = xfmPoint (space,Vec3vfM(org1)); + const Vec3vfM rcp_dir2 = rcp_safe(dir2); + + const vfloat t_lower_x = (vfloat::load(prim.bounds_vx_lower(N))-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_upper_x = (vfloat::load(prim.bounds_vx_upper(N))-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_lower_y = (vfloat::load(prim.bounds_vy_lower(N))-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_upper_y = (vfloat::load(prim.bounds_vy_upper(N))-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_lower_z = (vfloat::load(prim.bounds_vz_lower(N))-vfloat(org2.z))*vfloat(rcp_dir2.z); + const vfloat t_upper_z = (vfloat::load(prim.bounds_vz_upper(N))-vfloat(org2.z))*vfloat(rcp_dir2.z); + + const vfloat round_up (1.0f+3.0f*float(ulp)); + const vfloat round_down(1.0f-3.0f*float(ulp)); + const vfloat tNear = round_down*max(mini(t_lower_x,t_upper_x),mini(t_lower_y,t_upper_y),mini(t_lower_z,t_upper_z),vfloat(ray.tnear())); + const vfloat tFar = round_up *min(maxi(t_lower_x,t_upper_x),maxi(t_lower_y,t_upper_y),maxi(t_lower_z,t_upper_z),vfloat(ray.tfar)); + tNear_o = tNear; + return (vint(step) < vint(prim.N)) & (tNear <= tFar); + } + + template + static __forceinline void intersect_t(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID)); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_t(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID)); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + if (Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + + template + static __forceinline void intersect_n(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + + unsigned int vertexID = geom->curve(primID); + Vec3ff a0,a1,a2,a3; Vec3fa n0,n1,n2,n3; geom->gather(a0,a1,a2,a3,n0,n1,n2,n3,vertexID); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,n0,n1,n2,n3,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_n(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + + unsigned int vertexID = geom->curve(primID); + Vec3ff a0,a1,a2,a3; Vec3fa n0,n1,n2,n3; geom->gather(a0,a1,a2,a3,n0,n1,n2,n3,vertexID); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + if (Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,n0,n1,n2,n3,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + + template + static __forceinline void intersect_h(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID)); + Intersector().intersect(pre,ray,context,geom,primID,p0,t0,p1,t1,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_h(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID)); + if (Intersector().intersect(pre,ray,context,geom,primID,p0,t0,p1,t1,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + + template + static __forceinline void intersect_hn(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; Vec3fa n0,dn0,n1,dn1; geom->gather_hermite(p0,t0,n0,dn0,p1,t1,n1,dn1,geom->curve(primID)); + Intersector().intersect(pre,ray,context,geom,primID,p0,t0,p1,t1,n0,dn0,n1,dn1,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_hn(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; Vec3fa n0,dn0,n1,dn1; geom->gather_hermite(p0,t0,n0,dn0,p1,t1,n1,dn1,geom->curve(primID)); + if (Intersector().intersect(pre,ray,context,geom,primID,p0,t0,p1,t1,n0,dn0,n1,dn1,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + }; + + template + struct CurveNiIntersectorK + { + typedef CurveNi Primitive; + typedef Vec3vf Vec3vfM; + typedef LinearSpace3LinearSpace3vfM; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline vbool intersect(RayK& ray, const size_t k, const Primitive& prim, vfloat& tNear_o) + { + const size_t N = prim.N; + const vfloat4 offset_scale = vfloat4::loadu(prim.offset(N)); + const Vec3fa offset = Vec3fa(offset_scale); + const Vec3fa scale = Vec3fa(shuffle<3,3,3,3>(offset_scale)); + + const Vec3fa ray_org(ray.org.x[k],ray.org.y[k],ray.org.z[k]); + const Vec3fa ray_dir(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]); + const Vec3fa org1 = (ray_org-offset)*scale; + const Vec3fa dir1 = ray_dir*scale; + + const LinearSpace3vfM space(vfloat::load(prim.bounds_vx_x(N)), vfloat::load(prim.bounds_vx_y(N)), vfloat::load(prim.bounds_vx_z(N)), + vfloat::load(prim.bounds_vy_x(N)), vfloat::load(prim.bounds_vy_y(N)), vfloat::load(prim.bounds_vy_z(N)), + vfloat::load(prim.bounds_vz_x(N)), vfloat::load(prim.bounds_vz_y(N)), vfloat::load(prim.bounds_vz_z(N))); + + const Vec3vfM dir2 = xfmVector(space,Vec3vfM(dir1)); + const Vec3vfM org2 = xfmPoint (space,Vec3vfM(org1)); + const Vec3vfM rcp_dir2 = rcp_safe(dir2); + + const vfloat t_lower_x = (vfloat::load(prim.bounds_vx_lower(N))-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_upper_x = (vfloat::load(prim.bounds_vx_upper(N))-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_lower_y = (vfloat::load(prim.bounds_vy_lower(N))-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_upper_y = (vfloat::load(prim.bounds_vy_upper(N))-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_lower_z = (vfloat::load(prim.bounds_vz_lower(N))-vfloat(org2.z))*vfloat(rcp_dir2.z); + const vfloat t_upper_z = (vfloat::load(prim.bounds_vz_upper(N))-vfloat(org2.z))*vfloat(rcp_dir2.z); + + const vfloat round_up (1.0f+3.0f*float(ulp)); + const vfloat round_down(1.0f-3.0f*float(ulp)); + const vfloat tNear = round_down*max(mini(t_lower_x,t_upper_x),mini(t_lower_y,t_upper_y),mini(t_lower_z,t_upper_z),vfloat(ray.tnear()[k])); + const vfloat tFar = round_up *min(maxi(t_lower_x,t_upper_x),maxi(t_lower_y,t_upper_y),maxi(t_lower_z,t_upper_z),vfloat(ray.tfar[k])); + tNear_o = tNear; + return (vint(step) < vint(prim.N)) & (tNear <= tFar); + } + + template + static __forceinline void intersect_t(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID)); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_t(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID)); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + if (Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + + template + static __forceinline void intersect_n(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + + unsigned int vertexID = geom->curve(primID); + Vec3ff a0,a1,a2,a3; Vec3fa n0,n1,n2,n3; geom->gather(a0,a1,a2,a3,n0,n1,n2,n3,vertexID); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,n0,n1,n2,n3,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_n(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + + unsigned int vertexID = geom->curve(primID); + Vec3ff a0,a1,a2,a3; Vec3fa n0,n1,n2,n3; geom->gather(a0,a1,a2,a3,n0,n1,n2,n3,vertexID); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + const unsigned int primID1 = prim.primID(N)[i1]; + geom->prefetchL1_vertices(geom->curve(primID1)); + if (mask1) { + const size_t i2 = bsf(mask1); + const unsigned int primID2 = prim.primID(N)[i2]; + geom->prefetchL2_vertices(geom->curve(primID2)); + } + } + + if (Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,n0,n1,n2,n3,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + + template + static __forceinline void intersect_h(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID)); + Intersector().intersect(pre,ray,k,context,geom,primID,p0,t0,p1,t1,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_h(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID)); + if (Intersector().intersect(pre,ray,k,context,geom,primID,p0,t0,p1,t1,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + + template + static __forceinline void intersect_hn(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; Vec3fa n0,dn0,n1,dn1; geom->gather_hermite(p0,t0,n0,dn0,p1,t1,n1,dn1,geom->curve(primID)); + Intersector().intersect(pre,ray,k,context,geom,primID,p0,t0,p1,t1,n0,dn0,n1,dn1,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_hn(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; Vec3fa n0,dn0,n1,dn1; geom->gather_hermite(p0,t0,n0,dn0,p1,t1,n1,dn1,geom->curve(primID)); + if (Intersector().intersect(pre,ray,k,context,geom,primID,p0,t0,p1,t1,n0,dn0,n1,dn1,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curveNi_mb.h b/thirdparty/embree/kernels/geometry/curveNi_mb.h new file mode 100644 index 000000000000..d2e1926220f2 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curveNi_mb.h @@ -0,0 +1,278 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + template + struct CurveNiMB + { + struct Type : public PrimitiveType { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* Returns maximum number of stored primitives */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+M-1)/M; } + + static __forceinline size_t bytes(size_t N) + { + const size_t f = N/M, r = N%M; + static_assert(sizeof(CurveNiMB) == 6+37*M+24, "internal data layout issue"); + return f*sizeof(CurveNiMB) + (r!=0)*(6+37*r+24); + } + + public: + + /*! Default constructor. */ + __forceinline CurveNiMB () {} + + /*! fill curve from curve list */ + __forceinline LBBox3fa fillMB(const PrimRefMB* prims, size_t& begin, size_t _end, Scene* scene, const BBox1f time_range) + { + size_t end = min(begin+M,_end); + N = (unsigned char)(end-begin); + const unsigned int geomID0 = prims[begin].geomID(); + this->geomID(N) = geomID0; + ty = (unsigned char) scene->get(geomID0)->getType(); + + /* encode all primitives */ + LBBox3fa lbounds = empty; + for (size_t i=0; iget(geomID)->vlinearBounds(primID,time_range)); + } + BBox3fa bounds = lbounds.bounds(); + + /* calculate offset and scale */ + Vec3fa loffset = bounds.lower; + float lscale = reduce_min(256.0f/(bounds.size()*sqrt(3.0f))); + if (bounds.size() == Vec3fa(zero)) lscale = 0.0f; + *this->offset(N) = loffset; + *this->scale(N) = lscale; + this->time_offset(N) = time_range.lower; + this->time_scale(N) = 1.0f/time_range.size(); + + /* encode all primitives */ + for (size_t i=0; iget(geomID)->computeAlignedSpaceMB(primID,time_range); + + const LinearSpace3fa space3(trunc(126.0f*space2.vx),trunc(126.0f*space2.vy),trunc(126.0f*space2.vz)); + const LBBox3fa bounds = scene->get(geomID)->vlinearBounds(loffset,lscale,max(length(space3.vx),length(space3.vy),length(space3.vz)),space3.transposed(),primID,time_range); + + // NOTE: this weird (char) (short) cast works around VS2015 Win32 compiler bug + bounds_vx_x(N)[i] = (char) (short) space3.vx.x; + bounds_vx_y(N)[i] = (char) (short) space3.vx.y; + bounds_vx_z(N)[i] = (char) (short) space3.vx.z; + bounds_vx_lower0(N)[i] = (short) clamp(floor(bounds.bounds0.lower.x),-32767.0f,32767.0f); + bounds_vx_upper0(N)[i] = (short) clamp(ceil (bounds.bounds0.upper.x),-32767.0f,32767.0f); + bounds_vx_lower1(N)[i] = (short) clamp(floor(bounds.bounds1.lower.x),-32767.0f,32767.0f); + bounds_vx_upper1(N)[i] = (short) clamp(ceil (bounds.bounds1.upper.x),-32767.0f,32767.0f); + assert(-32767.0f <= floor(bounds.bounds0.lower.x) && floor(bounds.bounds0.lower.x) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.bounds0.upper.x) && ceil (bounds.bounds0.upper.x) <= 32767.0f); + assert(-32767.0f <= floor(bounds.bounds1.lower.x) && floor(bounds.bounds1.lower.x) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.bounds1.upper.x) && ceil (bounds.bounds1.upper.x) <= 32767.0f); + + bounds_vy_x(N)[i] = (char) (short) space3.vy.x; + bounds_vy_y(N)[i] = (char) (short) space3.vy.y; + bounds_vy_z(N)[i] = (char) (short) space3.vy.z; + bounds_vy_lower0(N)[i] = (short) clamp(floor(bounds.bounds0.lower.y),-32767.0f,32767.0f); + bounds_vy_upper0(N)[i] = (short) clamp(ceil (bounds.bounds0.upper.y),-32767.0f,32767.0f); + bounds_vy_lower1(N)[i] = (short) clamp(floor(bounds.bounds1.lower.y),-32767.0f,32767.0f); + bounds_vy_upper1(N)[i] = (short) clamp(ceil (bounds.bounds1.upper.y),-32767.0f,32767.0f); + assert(-32767.0f <= floor(bounds.bounds0.lower.y) && floor(bounds.bounds0.lower.y) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.bounds0.upper.y) && ceil (bounds.bounds0.upper.y) <= 32767.0f); + assert(-32767.0f <= floor(bounds.bounds1.lower.y) && floor(bounds.bounds1.lower.y) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.bounds1.upper.y) && ceil (bounds.bounds1.upper.y) <= 32767.0f); + + bounds_vz_x(N)[i] = (char) (short) space3.vz.x; + bounds_vz_y(N)[i] = (char) (short) space3.vz.y; + bounds_vz_z(N)[i] = (char) (short) space3.vz.z; + bounds_vz_lower0(N)[i] = (short) clamp(floor(bounds.bounds0.lower.z),-32767.0f,32767.0f); + bounds_vz_upper0(N)[i] = (short) clamp(ceil (bounds.bounds0.upper.z),-32767.0f,32767.0f); + bounds_vz_lower1(N)[i] = (short) clamp(floor(bounds.bounds1.lower.z),-32767.0f,32767.0f); + bounds_vz_upper1(N)[i] = (short) clamp(ceil (bounds.bounds1.upper.z),-32767.0f,32767.0f); + assert(-32767.0f <= floor(bounds.bounds0.lower.z) && floor(bounds.bounds0.lower.z) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.bounds0.upper.z) && ceil (bounds.bounds0.upper.z) <= 32767.0f); + assert(-32767.0f <= floor(bounds.bounds1.lower.z) && floor(bounds.bounds1.lower.z) <= 32767.0f); + assert(-32767.0f <= ceil (bounds.bounds1.upper.z) && ceil (bounds.bounds1.upper.z) <= 32767.0f); + + this->primID(N)[i] = primID; + } + + return lbounds; + } + + template + __forceinline static typename BVH::NodeRecordMB4D createLeafMB(BVH* bvh, const SetMB& prims, const Allocator& alloc) + { + size_t start = prims.begin(); + size_t end = prims.end(); + size_t items = CurveNiMB::blocks(prims.size()); + size_t numbytes = CurveNiMB::bytes(prims.size()); + CurveNiMB* accel = (CurveNiMB*) alloc.malloc1(numbytes,BVH::byteAlignment); + const typename BVH::NodeRef node = bvh->encodeLeaf((char*)accel,items); + + LBBox3fa bounds = empty; + for (size_t i=0; idata(),start,end,bvh->scene,prims.time_range)); + + return typename BVH::NodeRecordMB4D(node,bounds,prims.time_range); + }; + + + public: + + // 27.6 - 46 bytes per primitive + unsigned char ty; + unsigned char N; + unsigned char data[4+37*M+24]; + + /* + struct Layout + { + unsigned int geomID; + unsigned int primID[N]; + + char bounds_vx_x[N]; + char bounds_vx_y[N]; + char bounds_vx_z[N]; + short bounds_vx_lower0[N]; + short bounds_vx_upper0[N]; + short bounds_vx_lower1[N]; + short bounds_vx_upper1[N]; + + char bounds_vy_x[N]; + char bounds_vy_y[N]; + char bounds_vy_z[N]; + short bounds_vy_lower0[N]; + short bounds_vy_upper0[N]; + short bounds_vy_lower1[N]; + short bounds_vy_upper1[N]; + + char bounds_vz_x[N]; + char bounds_vz_y[N]; + char bounds_vz_z[N]; + short bounds_vz_lower0[N]; + short bounds_vz_upper0[N]; + short bounds_vz_lower1[N]; + short bounds_vz_upper1[N]; + + Vec3f offset; + float scale; + + float time_offset; + float time_scale; + }; + */ + + __forceinline unsigned int& geomID(size_t N) { return *(unsigned int*)((char*)this+2); } + __forceinline const unsigned int& geomID(size_t N) const { return *(unsigned int*)((char*)this+2); } + + __forceinline unsigned int* primID(size_t N) { return (unsigned int*)((char*)this+6); } + __forceinline const unsigned int* primID(size_t N) const { return (unsigned int*)((char*)this+6); } + + __forceinline char* bounds_vx_x(size_t N) { return (char*)((char*)this+6+4*N); } + __forceinline const char* bounds_vx_x(size_t N) const { return (char*)((char*)this+6+4*N); } + + __forceinline char* bounds_vx_y(size_t N) { return (char*)((char*)this+6+5*N); } + __forceinline const char* bounds_vx_y(size_t N) const { return (char*)((char*)this+6+5*N); } + + __forceinline char* bounds_vx_z(size_t N) { return (char*)((char*)this+6+6*N); } + __forceinline const char* bounds_vx_z(size_t N) const { return (char*)((char*)this+6+6*N); } + + __forceinline short* bounds_vx_lower0(size_t N) { return (short*)((char*)this+6+7*N); } + __forceinline const short* bounds_vx_lower0(size_t N) const { return (short*)((char*)this+6+7*N); } + + __forceinline short* bounds_vx_upper0(size_t N) { return (short*)((char*)this+6+9*N); } + __forceinline const short* bounds_vx_upper0(size_t N) const { return (short*)((char*)this+6+9*N); } + + __forceinline short* bounds_vx_lower1(size_t N) { return (short*)((char*)this+6+11*N); } + __forceinline const short* bounds_vx_lower1(size_t N) const { return (short*)((char*)this+6+11*N); } + + __forceinline short* bounds_vx_upper1(size_t N) { return (short*)((char*)this+6+13*N); } + __forceinline const short* bounds_vx_upper1(size_t N) const { return (short*)((char*)this+6+13*N); } + + __forceinline char* bounds_vy_x(size_t N) { return (char*)((char*)this+6+15*N); } + __forceinline const char* bounds_vy_x(size_t N) const { return (char*)((char*)this+6+15*N); } + + __forceinline char* bounds_vy_y(size_t N) { return (char*)((char*)this+6+16*N); } + __forceinline const char* bounds_vy_y(size_t N) const { return (char*)((char*)this+6+16*N); } + + __forceinline char* bounds_vy_z(size_t N) { return (char*)((char*)this+6+17*N); } + __forceinline const char* bounds_vy_z(size_t N) const { return (char*)((char*)this+6+17*N); } + + __forceinline short* bounds_vy_lower0(size_t N) { return (short*)((char*)this+6+18*N); } + __forceinline const short* bounds_vy_lower0(size_t N) const { return (short*)((char*)this+6+18*N); } + + __forceinline short* bounds_vy_upper0(size_t N) { return (short*)((char*)this+6+20*N); } + __forceinline const short* bounds_vy_upper0(size_t N) const { return (short*)((char*)this+6+20*N); } + + __forceinline short* bounds_vy_lower1(size_t N) { return (short*)((char*)this+6+22*N); } + __forceinline const short* bounds_vy_lower1(size_t N) const { return (short*)((char*)this+6+22*N); } + + __forceinline short* bounds_vy_upper1(size_t N) { return (short*)((char*)this+6+24*N); } + __forceinline const short* bounds_vy_upper1(size_t N) const { return (short*)((char*)this+6+24*N); } + + __forceinline char* bounds_vz_x(size_t N) { return (char*)((char*)this+6+26*N); } + __forceinline const char* bounds_vz_x(size_t N) const { return (char*)((char*)this+6+26*N); } + + __forceinline char* bounds_vz_y(size_t N) { return (char*)((char*)this+6+27*N); } + __forceinline const char* bounds_vz_y(size_t N) const { return (char*)((char*)this+6+27*N); } + + __forceinline char* bounds_vz_z(size_t N) { return (char*)((char*)this+6+28*N); } + __forceinline const char* bounds_vz_z(size_t N) const { return (char*)((char*)this+6+28*N); } + + __forceinline short* bounds_vz_lower0(size_t N) { return (short*)((char*)this+6+29*N); } + __forceinline const short* bounds_vz_lower0(size_t N) const { return (short*)((char*)this+6+29*N); } + + __forceinline short* bounds_vz_upper0(size_t N) { return (short*)((char*)this+6+31*N); } + __forceinline const short* bounds_vz_upper0(size_t N) const { return (short*)((char*)this+6+31*N); } + + __forceinline short* bounds_vz_lower1(size_t N) { return (short*)((char*)this+6+33*N); } + __forceinline const short* bounds_vz_lower1(size_t N) const { return (short*)((char*)this+6+33*N); } + + __forceinline short* bounds_vz_upper1(size_t N) { return (short*)((char*)this+6+35*N); } + __forceinline const short* bounds_vz_upper1(size_t N) const { return (short*)((char*)this+6+35*N); } + + __forceinline Vec3f* offset(size_t N) { return (Vec3f*)((char*)this+6+37*N); } + __forceinline const Vec3f* offset(size_t N) const { return (Vec3f*)((char*)this+6+37*N); } + + __forceinline float* scale(size_t N) { return (float*)((char*)this+6+37*N+12); } + __forceinline const float* scale(size_t N) const { return (float*)((char*)this+6+37*N+12); } + + __forceinline float& time_offset(size_t N) { return *(float*)((char*)this+6+37*N+16); } + __forceinline const float& time_offset(size_t N) const { return *(float*)((char*)this+6+37*N+16); } + + __forceinline float& time_scale(size_t N) { return *(float*)((char*)this+6+37*N+20); } + __forceinline const float& time_scale(size_t N) const { return *(float*)((char*)this+6+37*N+20); } + + __forceinline char* end(size_t N) { return (char*)this+6+37*N+24; } + __forceinline const char* end(size_t N) const { return (char*)this+6+37*N+24; } + }; + + template + typename CurveNiMB::Type CurveNiMB::type; + + typedef CurveNiMB<4> Curve4iMB; + typedef CurveNiMB<8> Curve8iMB; +} diff --git a/thirdparty/embree/kernels/geometry/curveNi_mb_intersector.h b/thirdparty/embree/kernels/geometry/curveNi_mb_intersector.h new file mode 100644 index 000000000000..0cbc76466834 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curveNi_mb_intersector.h @@ -0,0 +1,516 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "curveNi_mb.h" +#include "../subdiv/linear_bezier_patch.h" + +namespace embree +{ + namespace isa + { + template + struct CurveNiMBIntersector1 + { + typedef CurveNiMB Primitive; + typedef Vec3vf Vec3vfM; + typedef LinearSpace3LinearSpace3vfM; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline vbool intersect(Ray& ray, const Primitive& prim, vfloat& tNear_o) + { + const size_t N = prim.N; + const vfloat4 offset_scale = vfloat4::loadu(prim.offset(N)); + const Vec3fa offset = Vec3fa(offset_scale); + const Vec3fa scale = Vec3fa(shuffle<3,3,3,3>(offset_scale)); + const Vec3fa org1 = (ray.org-offset)*scale; + const Vec3fa dir1 = ray.dir*scale; + + const LinearSpace3vfM space(vfloat::load(prim.bounds_vx_x(N)), vfloat::load(prim.bounds_vx_y(N)), vfloat::load(prim.bounds_vx_z(N)), + vfloat::load(prim.bounds_vy_x(N)), vfloat::load(prim.bounds_vy_y(N)), vfloat::load(prim.bounds_vy_z(N)), + vfloat::load(prim.bounds_vz_x(N)), vfloat::load(prim.bounds_vz_y(N)), vfloat::load(prim.bounds_vz_z(N))); + + const Vec3vfM dir2 = xfmVector(space,Vec3vfM(dir1)); + const Vec3vfM org2 = xfmPoint (space,Vec3vfM(org1)); + const Vec3vfM rcp_dir2 = rcp_safe(dir2); + + const vfloat ltime = (ray.time()-prim.time_offset(N))*prim.time_scale(N); + const vfloat vx_lower0 = vfloat::load(prim.bounds_vx_lower0(N)); + const vfloat vx_lower1 = vfloat::load(prim.bounds_vx_lower1(N)); + const vfloat vx_lower = madd(ltime,vx_lower1-vx_lower0,vx_lower0); + const vfloat vx_upper0 = vfloat::load(prim.bounds_vx_upper0(N)); + const vfloat vx_upper1 = vfloat::load(prim.bounds_vx_upper1(N)); + const vfloat vx_upper = madd(ltime,vx_upper1-vx_upper0,vx_upper0); + + const vfloat vy_lower0 = vfloat::load(prim.bounds_vy_lower0(N)); + const vfloat vy_lower1 = vfloat::load(prim.bounds_vy_lower1(N)); + const vfloat vy_lower = madd(ltime,vy_lower1-vy_lower0,vy_lower0); + const vfloat vy_upper0 = vfloat::load(prim.bounds_vy_upper0(N)); + const vfloat vy_upper1 = vfloat::load(prim.bounds_vy_upper1(N)); + const vfloat vy_upper = madd(ltime,vy_upper1-vy_upper0,vy_upper0); + + const vfloat vz_lower0 = vfloat::load(prim.bounds_vz_lower0(N)); + const vfloat vz_lower1 = vfloat::load(prim.bounds_vz_lower1(N)); + const vfloat vz_lower = madd(ltime,vz_lower1-vz_lower0,vz_lower0); + const vfloat vz_upper0 = vfloat::load(prim.bounds_vz_upper0(N)); + const vfloat vz_upper1 = vfloat::load(prim.bounds_vz_upper1(N)); + const vfloat vz_upper = madd(ltime,vz_upper1-vz_upper0,vz_upper0); + + const vfloat t_lower_x = (vx_lower-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_upper_x = (vx_upper-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_lower_y = (vy_lower-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_upper_y = (vy_upper-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_lower_z = (vz_lower-vfloat(org2.z))*vfloat(rcp_dir2.z); + const vfloat t_upper_z = (vz_upper-vfloat(org2.z))*vfloat(rcp_dir2.z); + + const vfloat round_up (1.0f+3.0f*float(ulp)); + const vfloat round_down(1.0f-3.0f*float(ulp)); + const vfloat tNear = round_down*max(mini(t_lower_x,t_upper_x),mini(t_lower_y,t_upper_y),mini(t_lower_z,t_upper_z),vfloat(ray.tnear())); + const vfloat tFar = round_up *min(maxi(t_lower_x,t_upper_x),maxi(t_lower_y,t_upper_y),maxi(t_lower_z,t_upper_z),vfloat(ray.tfar)); + tNear_o = tNear; + return (vint(step) < vint(prim.N)) & (tNear <= tFar); + } + + template + static __forceinline void intersect_t(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID),ray.time()); + + Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_t(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID),ray.time()); + + if (Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + + template + static __forceinline void intersect_n(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedCurve(context, ray.org, primID,ray.time()); + Intersector().intersect(pre,ray,context,geom,primID,curve,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_n(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedCurve(context, ray.org, primID,ray.time()); + + if (Intersector().intersect(pre,ray,context,geom,primID,curve,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + + template + static __forceinline void intersect_h(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID),ray.time()); + Intersector().intersect(pre,ray,context,geom,primID,p0,t0,p1,t1,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_h(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID),ray.time()); + if (Intersector().intersect(pre,ray,context,geom,primID,p0,t0,p1,t1,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + + template + static __forceinline void intersect_hn(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedHermiteCurve(context, ray.org, primID,ray.time()); + Intersector().intersect(pre,ray,context,geom,primID,curve,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_hn(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedHermiteCurve(context, ray.org, primID,ray.time()); + if (Intersector().intersect(pre,ray,context,geom,primID,curve,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + }; + + template + struct CurveNiMBIntersectorK + { + typedef CurveNiMB Primitive; + typedef Vec3vf Vec3vfM; + typedef LinearSpace3LinearSpace3vfM; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline vbool intersect(RayK& ray, const size_t k, const Primitive& prim, vfloat& tNear_o) + { + const size_t N = prim.N; + const vfloat4 offset_scale = vfloat4::loadu(prim.offset(N)); + const Vec3fa offset = Vec3fa(offset_scale); + const Vec3fa scale = Vec3fa(shuffle<3,3,3,3>(offset_scale)); + + const Vec3fa ray_org(ray.org.x[k],ray.org.y[k],ray.org.z[k]); + const Vec3fa ray_dir(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]); + const Vec3fa org1 = (ray_org-offset)*scale; + const Vec3fa dir1 = ray_dir*scale; + + const LinearSpace3vfM space(vfloat::load(prim.bounds_vx_x(N)), vfloat::load(prim.bounds_vx_y(N)), vfloat::load(prim.bounds_vx_z(N)), + vfloat::load(prim.bounds_vy_x(N)), vfloat::load(prim.bounds_vy_y(N)), vfloat::load(prim.bounds_vy_z(N)), + vfloat::load(prim.bounds_vz_x(N)), vfloat::load(prim.bounds_vz_y(N)), vfloat::load(prim.bounds_vz_z(N))); + + const Vec3vfM dir2 = xfmVector(space,Vec3vfM(dir1)); + const Vec3vfM org2 = xfmPoint (space,Vec3vfM(org1)); + const Vec3vfM rcp_dir2 = rcp_safe(dir2); + + const vfloat ltime = (ray.time()[k]-prim.time_offset(N))*prim.time_scale(N); + const vfloat vx_lower0 = vfloat::load(prim.bounds_vx_lower0(N)); + const vfloat vx_lower1 = vfloat::load(prim.bounds_vx_lower1(N)); + const vfloat vx_lower = madd(ltime,vx_lower1-vx_lower0,vx_lower0); + const vfloat vx_upper0 = vfloat::load(prim.bounds_vx_upper0(N)); + const vfloat vx_upper1 = vfloat::load(prim.bounds_vx_upper1(N)); + const vfloat vx_upper = madd(ltime,vx_upper1-vx_upper0,vx_upper0); + + const vfloat vy_lower0 = vfloat::load(prim.bounds_vy_lower0(N)); + const vfloat vy_lower1 = vfloat::load(prim.bounds_vy_lower1(N)); + const vfloat vy_lower = madd(ltime,vy_lower1-vy_lower0,vy_lower0); + const vfloat vy_upper0 = vfloat::load(prim.bounds_vy_upper0(N)); + const vfloat vy_upper1 = vfloat::load(prim.bounds_vy_upper1(N)); + const vfloat vy_upper = madd(ltime,vy_upper1-vy_upper0,vy_upper0); + + const vfloat vz_lower0 = vfloat::load(prim.bounds_vz_lower0(N)); + const vfloat vz_lower1 = vfloat::load(prim.bounds_vz_lower1(N)); + const vfloat vz_lower = madd(ltime,vz_lower1-vz_lower0,vz_lower0); + const vfloat vz_upper0 = vfloat::load(prim.bounds_vz_upper0(N)); + const vfloat vz_upper1 = vfloat::load(prim.bounds_vz_upper1(N)); + const vfloat vz_upper = madd(ltime,vz_upper1-vz_upper0,vz_upper0); + + const vfloat t_lower_x = (vx_lower-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_upper_x = (vx_upper-vfloat(org2.x))*vfloat(rcp_dir2.x); + const vfloat t_lower_y = (vy_lower-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_upper_y = (vy_upper-vfloat(org2.y))*vfloat(rcp_dir2.y); + const vfloat t_lower_z = (vz_lower-vfloat(org2.z))*vfloat(rcp_dir2.z); + const vfloat t_upper_z = (vz_upper-vfloat(org2.z))*vfloat(rcp_dir2.z); + + const vfloat round_up (1.0f+3.0f*float(ulp)); + const vfloat round_down(1.0f-3.0f*float(ulp)); + const vfloat tNear = round_down*max(mini(t_lower_x,t_upper_x),mini(t_lower_y,t_upper_y),mini(t_lower_z,t_upper_z),vfloat(ray.tnear()[k])); + const vfloat tFar = round_up *min(maxi(t_lower_x,t_upper_x),maxi(t_lower_y,t_upper_y),maxi(t_lower_z,t_upper_z),vfloat(ray.tfar[k])); + tNear_o = tNear; + return (vint(step) < vint(prim.N)) & (tNear <= tFar); + } + + template + static __forceinline void intersect_t(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID),ray.time()[k]); + + Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_t(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff a0,a1,a2,a3; geom->gather(a0,a1,a2,a3,geom->curve(primID),ray.time()[k]); + + if (Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + + template + static __forceinline void intersect_n(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const Vec3fa ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedCurve(context, ray_org, primID,ray.time()[k]); + Intersector().intersect(pre,ray,k,context,geom,primID,curve,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_n(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const Vec3fa ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedCurve(context, ray_org, primID,ray.time()[k]); + + if (Intersector().intersect(pre,ray,k,context,geom,primID,curve,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + + template + static __forceinline void intersect_h(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID),ray.time()[k]); + Intersector().intersect(pre,ray,k,context,geom,primID,p0,t0,p1,t1,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_h(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + Vec3ff p0,t0,p1,t1; geom->gather_hermite(p0,t0,p1,t1,geom->curve(primID),ray.time()[k]); + if (Intersector().intersect(pre,ray,k,context,geom,primID,p0,t0,p1,t1,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + + template + static __forceinline void intersect_hn(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const Vec3fa ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedHermiteCurve(context, ray_org, primID,ray.time()[k]); + Intersector().intersect(pre,ray,k,context,geom,primID,curve,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_hn(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = context->scene->get(geomID); + const Vec3fa ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const TensorLinearCubicBezierSurface3fa curve = geom->getNormalOrientedHermiteCurve(context, ray_org, primID,ray.time()[k]); + if (Intersector().intersect(pre,ray,k,context,geom,primID,curve,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curveNv.h b/thirdparty/embree/kernels/geometry/curveNv.h new file mode 100644 index 000000000000..6eb5e30b39aa --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curveNv.h @@ -0,0 +1,101 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "curveNi.h" + +namespace embree +{ + template + struct CurveNv : public CurveNi + { + using CurveNi::N; + + struct Type : public PrimitiveType { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* Returns maximum number of stored primitives */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+M-1)/M; } + + static __forceinline size_t bytes(size_t N) + { + const size_t f = N/M, r = N%M; + static_assert(sizeof(CurveNv) == 22+25*M+4*16*M, "internal data layout issue"); + return f*sizeof(CurveNv) + (r!=0)*(22 + 25*r + 4*16*r); + } + + public: + + /*! Default constructor. */ + __forceinline CurveNv () {} + + /*! fill curve from curve list */ + __forceinline void fill(const PrimRef* prims, size_t& begin, size_t _end, Scene* scene) + { + size_t end = min(begin+M,_end); + size_t N = end-begin; + + /* encode all primitives */ + for (size_t i=0; iget(geomID); + const unsigned vtxID = mesh->curve(primID); + Vec3fa::storeu(&this->vertices(i,N)[0],mesh->vertex(vtxID+0)); + Vec3fa::storeu(&this->vertices(i,N)[1],mesh->vertex(vtxID+1)); + Vec3fa::storeu(&this->vertices(i,N)[2],mesh->vertex(vtxID+2)); + Vec3fa::storeu(&this->vertices(i,N)[3],mesh->vertex(vtxID+3)); + } + } + + template + __forceinline static typename BVH::NodeRef createLeaf (BVH* bvh, const PrimRef* prims, const range& set, const Allocator& alloc) + { + if (set.size() == 0) + return BVH::emptyNode; + + /* fall back to CurveNi for oriented curves */ + unsigned int geomID = prims[set.begin()].geomID(); + if (bvh->scene->get(geomID)->getCurveType() == Geometry::GTY_SUBTYPE_ORIENTED_CURVE) { + return CurveNi::createLeaf(bvh,prims,set,alloc); + } + if (bvh->scene->get(geomID)->getCurveBasis() == Geometry::GTY_BASIS_HERMITE) { + return CurveNi::createLeaf(bvh,prims,set,alloc); + } + + size_t start = set.begin(); + size_t items = CurveNv::blocks(set.size()); + size_t numbytes = CurveNv::bytes(set.size()); + CurveNv* accel = (CurveNv*) alloc.malloc1(numbytes,BVH::byteAlignment); + for (size_t i=0; i::fill(prims,start,set.end(),bvh->scene); + accel[i].CurveNi::fill(prims,start,set.end(),bvh->scene); + } + return bvh->encodeLeaf((char*)accel,items); + }; + + public: + unsigned char data[4*16*M]; + __forceinline Vec3fa* vertices(size_t i, size_t N) { return (Vec3fa*)CurveNi::end(N)+4*i; } + __forceinline const Vec3fa* vertices(size_t i, size_t N) const { return (Vec3fa*)CurveNi::end(N)+4*i; } + }; + + template + typename CurveNv::Type CurveNv::type; + + typedef CurveNv<4> Curve4v; + typedef CurveNv<8> Curve8v; +} diff --git a/thirdparty/embree/kernels/geometry/curveNv_intersector.h b/thirdparty/embree/kernels/geometry/curveNv_intersector.h new file mode 100644 index 000000000000..e20da2882efd --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curveNv_intersector.h @@ -0,0 +1,181 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "curveNv.h" +#include "curveNi_intersector.h" + +namespace embree +{ + namespace isa + { + template + struct CurveNvIntersector1 : public CurveNiIntersector1 + { + typedef CurveNv Primitive; + typedef CurvePrecalculations1 Precalculations; + + template + static __forceinline void intersect_t(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = CurveNiIntersector1::intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = (CurveGeometry*) context->scene->get(geomID); + const Vec3ff a0 = Vec3ff::loadu(&prim.vertices(i,N)[0]); + const Vec3ff a1 = Vec3ff::loadu(&prim.vertices(i,N)[1]); + const Vec3ff a2 = Vec3ff::loadu(&prim.vertices(i,N)[2]); + const Vec3ff a3 = Vec3ff::loadu(&prim.vertices(i,N)[3]); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + prefetchL1(&prim.vertices(i1,N)[0]); + prefetchL1(&prim.vertices(i1,N)[4]); + if (mask1) { + const size_t i2 = bsf(mask1); + prefetchL2(&prim.vertices(i2,N)[0]); + prefetchL2(&prim.vertices(i2,N)[4]); + } + } + + Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,Epilog(ray,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + } + + template + static __forceinline bool occluded_t(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = CurveNiIntersector1::intersect(ray,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = (CurveGeometry*) context->scene->get(geomID); + const Vec3ff a0 = Vec3ff::loadu(&prim.vertices(i,N)[0]); + const Vec3ff a1 = Vec3ff::loadu(&prim.vertices(i,N)[1]); + const Vec3ff a2 = Vec3ff::loadu(&prim.vertices(i,N)[2]); + const Vec3ff a3 = Vec3ff::loadu(&prim.vertices(i,N)[3]); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + prefetchL1(&prim.vertices(i1,N)[0]); + prefetchL1(&prim.vertices(i1,N)[4]); + if (mask1) { + const size_t i2 = bsf(mask1); + prefetchL2(&prim.vertices(i2,N)[0]); + prefetchL2(&prim.vertices(i2,N)[4]); + } + } + + if (Intersector().intersect(pre,ray,context,geom,primID,a0,a1,a2,a3,Epilog(ray,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar)); + } + return false; + } + }; + + template + struct CurveNvIntersectorK : public CurveNiIntersectorK + { + typedef CurveNv Primitive; + typedef CurvePrecalculationsK Precalculations; + + template + static __forceinline void intersect_t(Precalculations& pre, RayHitK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = CurveNiIntersectorK::intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(normal.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = (CurveGeometry*) context->scene->get(geomID); + const Vec3ff a0 = Vec3ff::loadu(&prim.vertices(i,N)[0]); + const Vec3ff a1 = Vec3ff::loadu(&prim.vertices(i,N)[1]); + const Vec3ff a2 = Vec3ff::loadu(&prim.vertices(i,N)[2]); + const Vec3ff a3 = Vec3ff::loadu(&prim.vertices(i,N)[3]); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + prefetchL1(&prim.vertices(i1,N)[0]); + prefetchL1(&prim.vertices(i1,N)[4]); + if (mask1) { + const size_t i2 = bsf(mask1); + prefetchL2(&prim.vertices(i2,N)[0]); + prefetchL2(&prim.vertices(i2,N)[4]); + } + } + + Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,Epilog(ray,k,context,geomID,primID)); + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + } + + template + static __forceinline bool occluded_t(Precalculations& pre, RayK& ray, const size_t k, IntersectContext* context, const Primitive& prim) + { + vfloat tNear; + vbool valid = CurveNiIntersectorK::intersect(ray,k,prim,tNear); + + const size_t N = prim.N; + size_t mask = movemask(valid); + while (mask) + { + const size_t i = bscf(mask); + STAT3(shadow.trav_prims,1,1,1); + const unsigned int geomID = prim.geomID(N); + const unsigned int primID = prim.primID(N)[i]; + const CurveGeometry* geom = (CurveGeometry*) context->scene->get(geomID); + const Vec3ff a0 = Vec3ff::loadu(&prim.vertices(i,N)[0]); + const Vec3ff a1 = Vec3ff::loadu(&prim.vertices(i,N)[1]); + const Vec3ff a2 = Vec3ff::loadu(&prim.vertices(i,N)[2]); + const Vec3ff a3 = Vec3ff::loadu(&prim.vertices(i,N)[3]); + + size_t mask1 = mask; + const size_t i1 = bscf(mask1); + if (mask) { + prefetchL1(&prim.vertices(i1,N)[0]); + prefetchL1(&prim.vertices(i1,N)[4]); + if (mask1) { + const size_t i2 = bsf(mask1); + prefetchL2(&prim.vertices(i2,N)[0]); + prefetchL2(&prim.vertices(i2,N)[4]); + } + } + + if (Intersector().intersect(pre,ray,k,context,geom,primID,a0,a1,a2,a3,Epilog(ray,k,context,geomID,primID))) + return true; + + mask &= movemask(tNear <= vfloat(ray.tfar[k])); + } + return false; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector.h b/thirdparty/embree/kernels/geometry/curve_intersector.h new file mode 100644 index 000000000000..204958f7cc36 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector.h @@ -0,0 +1,98 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "../subdiv/bezier_curve.h" +#include "../common/primref.h" +#include "bezier_hair_intersector.h" +#include "bezier_ribbon_intersector.h" +#include "bezier_curve_intersector.h" +#include "oriented_curve_intersector.h" +#include "../bvh/node_intersector1.h" + +// FIXME: this file seems replicate of curve_intersector_virtual.h + +namespace embree +{ + namespace isa + { + struct VirtualCurveIntersector1 + { + typedef unsigned char Primitive; + typedef CurvePrecalculations1 Precalculations; + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurvePrimitive::Intersectors& leafIntersector = ((VirtualCurvePrimitive*) This->leafIntersector)->vtbl[ty]; + leafIntersector.intersect<1>(&pre,&ray,context,prim); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurvePrimitive::Intersectors& leafIntersector = ((VirtualCurvePrimitive*) This->leafIntersector)->vtbl[ty]; + return leafIntersector.occluded<1>(&pre,&ray,context,prim); + } + }; + + template + struct VirtualCurveIntersectorK + { + typedef unsigned char Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const vbool& valid_i, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurvePrimitive::Intersectors& leafIntersector = ((VirtualCurvePrimitive*) This->leafIntersector)->vtbl[ty]; + size_t mask = movemask(valid_i); + while (mask) leafIntersector.intersect(&pre,&ray,bscf(mask),context,prim); + } + + static __forceinline vbool occluded(const vbool& valid_i, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurvePrimitive::Intersectors& leafIntersector = ((VirtualCurvePrimitive*) This->leafIntersector)->vtbl[ty]; + vbool valid_o = false; + size_t mask = movemask(valid_i); + while (mask) { + size_t k = bscf(mask); + if (leafIntersector.occluded(&pre,&ray,k,context,prim)) + set(valid_o, k); + } + return valid_o; + } + + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurvePrimitive::Intersectors& leafIntersector = ((VirtualCurvePrimitive*) This->leafIntersector)->vtbl[ty]; + leafIntersector.intersect(&pre,&ray,k,context,prim); + } + + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurvePrimitive::Intersectors& leafIntersector = ((VirtualCurvePrimitive*) This->leafIntersector)->vtbl[ty]; + return leafIntersector.occluded(&pre,&ray,k,context,prim); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector_distance.h b/thirdparty/embree/kernels/geometry/curve_intersector_distance.h new file mode 100644 index 000000000000..343cc8ff28f0 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector_distance.h @@ -0,0 +1,129 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + namespace isa + { + template + struct DistanceCurveHit + { + __forceinline DistanceCurveHit() {} + + __forceinline DistanceCurveHit(const vbool& valid, const vfloat& U, const vfloat& V, const vfloat& T, const int i, const int N, + const NativeCurve3fa& curve3D) + : U(U), V(V), T(T), i(i), N(N), curve3D(curve3D), valid(valid) {} + + __forceinline void finalize() + { + vu = (vfloat(step)+U+vfloat(float(i)))*(1.0f/float(N)); + vv = V; + vt = T; + } + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { + return curve3D.eval_du(vu[i]); + } + + public: + vfloat U; + vfloat V; + vfloat T; + int i, N; + NativeCurve3fa curve3D; + + public: + vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + }; + + template + struct DistanceCurve1Intersector1 + { + template + __forceinline bool intersect(const CurvePrecalculations1& pre,Ray& ray, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3fa& v0, const Vec3fa& v1, const Vec3fa& v2, const Vec3fa& v3, + const Epilog& epilog) + { + const int N = geom->tessellationRate; + + /* transform control points into ray space */ + const NativeCurve3fa curve3Di(v0,v1,v2,v3); + const NativeCurve3fa curve3D = enlargeRadiusToMinWidth(context,geom,ray.org,curve3Di); + const NativeCurve3fa curve2D = curve3D.xfm_pr(pre.ray_space,ray.org); + + /* evaluate the bezier curve */ + vboolx valid = vfloatx(step) < vfloatx(float(N)); + const Vec4vfx p0 = curve2D.template eval0(0,N); + const Vec4vfx p1 = curve2D.template eval1(0,N); + + /* approximative intersection with cone */ + const Vec4vfx v = p1-p0; + const Vec4vfx w = -p0; + const vfloatx d0 = madd(w.x,v.x,w.y*v.y); + const vfloatx d1 = madd(v.x,v.x,v.y*v.y); + const vfloatx u = clamp(d0*rcp(d1),vfloatx(zero),vfloatx(one)); + const Vec4vfx p = madd(u,v,p0); + const vfloatx t = p.z*pre.depth_scale; + const vfloatx d2 = madd(p.x,p.x,p.y*p.y); + const vfloatx r = p.w; + const vfloatx r2 = r*r; + valid &= (d2 <= r2) & (vfloatx(ray.tnear()) <= t) & (t <= vfloatx(ray.tfar)); + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) + valid &= t > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR)*r*pre.depth_scale; // ignore self intersections + + /* update hit information */ + bool ishit = false; + if (unlikely(any(valid))) { + DistanceCurveHit hit(valid,u,0.0f,t,0,N,curve3D); + ishit = ishit | epilog(valid,hit); + } + + if (unlikely(VSIZEX < N)) + { + /* process SIMD-size many segments per iteration */ + for (int i=VSIZEX; i(i,N); + const Vec4vfx p1 = curve2D.template eval1(i,N); + + /* approximative intersection with cone */ + const Vec4vfx v = p1-p0; + const Vec4vfx w = -p0; + const vfloatx d0 = madd(w.x,v.x,w.y*v.y); + const vfloatx d1 = madd(v.x,v.x,v.y*v.y); + const vfloatx u = clamp(d0*rcp(d1),vfloatx(zero),vfloatx(one)); + const Vec4vfx p = madd(u,v,p0); + const vfloatx t = p.z*pre.depth_scale; + const vfloatx d2 = madd(p.x,p.x,p.y*p.y); + const vfloatx r = p.w; + const vfloatx r2 = r*r; + valid &= (d2 <= r2) & (vfloatx(ray.tnear()) <= t) & (t <= vfloatx(ray.tfar)); + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) + valid &= t > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR)*r*pre.depth_scale; // ignore self intersections + + /* update hit information */ + if (unlikely(any(valid))) { + DistanceCurveHit hit(valid,u,0.0f,t,i,N,curve3D); + ishit = ishit | epilog(valid,hit); + } + } + } + return ishit; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector_oriented.h b/thirdparty/embree/kernels/geometry/curve_intersector_oriented.h new file mode 100644 index 000000000000..47531027fc3b --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector_oriented.h @@ -0,0 +1,417 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "curve_intersector_precalculations.h" +#include "curve_intersector_sweep.h" +#include "../subdiv/linear_bezier_patch.h" + +#define DBG(x) + +namespace embree +{ + namespace isa + { + template + struct TensorLinearCubicBezierSurfaceIntersector + { + const LinearSpace3fa& ray_space; + Ray& ray; + TensorLinearCubicBezierSurface3fa curve3d; + TensorLinearCubicBezierSurface2fa curve2d; + float eps; + const Epilog& epilog; + bool isHit; + + __forceinline TensorLinearCubicBezierSurfaceIntersector (const LinearSpace3fa& ray_space, Ray& ray, const TensorLinearCubicBezierSurface3fa& curve3d, const Epilog& epilog) + : ray_space(ray_space), ray(ray), curve3d(curve3d), epilog(epilog), isHit(false) + { + const TensorLinearCubicBezierSurface3fa curve3dray = curve3d.xfm(ray_space,ray.org); + curve2d = TensorLinearCubicBezierSurface2fa(CubicBezierCurve2fa(curve3dray.L),CubicBezierCurve2fa(curve3dray.R)); + const BBox2fa b2 = curve2d.bounds(); + eps = 8.0f*float(ulp)*reduce_max(max(abs(b2.lower),abs(b2.upper))); + } + + __forceinline Interval1f solve_linear(const float u0, const float u1, const float& p0, const float& p1) + { + if (p1 == p0) { + if (p0 == 0.0f) return Interval1f(u0,u1); + else return Interval1f(empty); + } + const float t = -p0/(p1-p0); + const float tt = lerp(u0,u1,t); + return Interval1f(tt); + } + + __forceinline void solve_linear(const float u0, const float u1, const Interval1f& p0, const Interval1f& p1, Interval1f& u) + { + if (sign(p0.lower) != sign(p0.upper)) u.extend(u0); + if (sign(p0.lower) != sign(p1.lower)) u.extend(solve_linear(u0,u1,p0.lower,p1.lower)); + if (sign(p0.upper) != sign(p1.upper)) u.extend(solve_linear(u0,u1,p0.upper,p1.upper)); + if (sign(p1.lower) != sign(p1.upper)) u.extend(u1); + } + + __forceinline Interval1f bezier_clipping(const CubicBezierCurve& curve) + { + Interval1f u = empty; + solve_linear(0.0f/3.0f,1.0f/3.0f,curve.v0,curve.v1,u); + solve_linear(0.0f/3.0f,2.0f/3.0f,curve.v0,curve.v2,u); + solve_linear(0.0f/3.0f,3.0f/3.0f,curve.v0,curve.v3,u); + solve_linear(1.0f/3.0f,2.0f/3.0f,curve.v1,curve.v2,u); + solve_linear(1.0f/3.0f,3.0f/3.0f,curve.v1,curve.v3,u); + solve_linear(2.0f/3.0f,3.0f/3.0f,curve.v2,curve.v3,u); + return intersect(u,Interval1f(0.0f,1.0f)); + } + + __forceinline Interval1f bezier_clipping(const LinearBezierCurve& curve) + { + Interval1f v = empty; + solve_linear(0.0f,1.0f,curve.v0,curve.v1,v); + return intersect(v,Interval1f(0.0f,1.0f)); + } + + __forceinline void solve_bezier_clipping(BBox1f cu, BBox1f cv, const TensorLinearCubicBezierSurface2fa& curve2) + { + BBox2fa bounds = curve2.bounds(); + if (bounds.upper.x < 0.0f) return; + if (bounds.upper.y < 0.0f) return; + if (bounds.lower.x > 0.0f) return; + if (bounds.lower.y > 0.0f) return; + + if (max(cu.size(),cv.size()) < 1E-4f) + { + const float u = cu.center(); + const float v = cv.center(); + TensorLinearCubicBezierSurface1f curve_z = curve3d.xfm(ray_space.row2(),ray.org); + const float t = curve_z.eval(u,v); + if (ray.tnear() <= t && t <= ray.tfar) { + const Vec3fa Ng = cross(curve3d.eval_du(u,v),curve3d.eval_dv(u,v)); + BezierCurveHit hit(t,u,v,Ng); + isHit |= epilog(hit); + } + return; + } + + const Vec2fa dv = curve2.axis_v(); + const TensorLinearCubicBezierSurface1f curve1v = curve2.xfm(dv); + LinearBezierCurve curve0v = curve1v.reduce_u(); + if (!curve0v.hasRoot()) return; + + const Interval1f v = bezier_clipping(curve0v); + if (isEmpty(v)) return; + TensorLinearCubicBezierSurface2fa curve2a = curve2.clip_v(v); + cv = BBox1f(lerp(cv.lower,cv.upper,v.lower),lerp(cv.lower,cv.upper,v.upper)); + + const Vec2fa du = curve2.axis_u(); + const TensorLinearCubicBezierSurface1f curve1u = curve2a.xfm(du); + CubicBezierCurve curve0u = curve1u.reduce_v(); + int roots = curve0u.maxRoots(); + if (roots == 0) return; + + if (roots == 1) + { + const Interval1f u = bezier_clipping(curve0u); + if (isEmpty(u)) return; + TensorLinearCubicBezierSurface2fa curve2b = curve2a.clip_u(u); + cu = BBox1f(lerp(cu.lower,cu.upper,u.lower),lerp(cu.lower,cu.upper,u.upper)); + solve_bezier_clipping(cu,cv,curve2b); + return; + } + + TensorLinearCubicBezierSurface2fa curve2l, curve2r; + curve2a.split_u(curve2l,curve2r); + solve_bezier_clipping(BBox1f(cu.lower,cu.center()),cv,curve2l); + solve_bezier_clipping(BBox1f(cu.center(),cu.upper),cv,curve2r); + } + + __forceinline bool solve_bezier_clipping() + { + solve_bezier_clipping(BBox1f(0.0f,1.0f),BBox1f(0.0f,1.0f),curve2d); + return isHit; + } + + __forceinline void solve_newton_raphson(BBox1f cu, BBox1f cv) + { + Vec2fa uv(cu.center(),cv.center()); + const Vec2fa dfdu = curve2d.eval_du(uv.x,uv.y); + const Vec2fa dfdv = curve2d.eval_dv(uv.x,uv.y); + const LinearSpace2fa rcp_J = rcp(LinearSpace2fa(dfdu,dfdv)); + solve_newton_raphson_loop(cu,cv,uv,dfdu,dfdv,rcp_J); + } + + __forceinline void solve_newton_raphson_loop(BBox1f cu, BBox1f cv, const Vec2fa& uv_in, const Vec2fa& dfdu, const Vec2fa& dfdv, const LinearSpace2fa& rcp_J) + { + Vec2fa uv = uv_in; + + for (size_t i=0; i<200; i++) + { + const Vec2fa f = curve2d.eval(uv.x,uv.y); + const Vec2fa duv = rcp_J*f; + uv -= duv; + + if (max(abs(f.x),abs(f.y)) < eps) + { + const float u = uv.x; + const float v = uv.y; + if (!(u >= 0.0f && u <= 1.0f)) return; // rejects NaNs + if (!(v >= 0.0f && v <= 1.0f)) return; // rejects NaNs + const TensorLinearCubicBezierSurface1f curve_z = curve3d.xfm(ray_space.row2(),ray.org); + const float t = curve_z.eval(u,v); + if (!(ray.tnear() <= t && t <= ray.tfar)) return; // rejects NaNs + const Vec3fa Ng = cross(curve3d.eval_du(u,v),curve3d.eval_dv(u,v)); + BezierCurveHit hit(t,u,v,Ng); + isHit |= epilog(hit); + return; + } + } + } + + __forceinline bool clip_v(BBox1f& cu, BBox1f& cv) + { + const Vec2fa dv = curve2d.eval_dv(cu.lower,cv.lower); + const TensorLinearCubicBezierSurface1f curve1v = curve2d.xfm(dv).clip(cu,cv); + LinearBezierCurve curve0v = curve1v.reduce_u(); + if (!curve0v.hasRoot()) return false; + Interval1f v = bezier_clipping(curve0v); + if (isEmpty(v)) return false; + v = intersect(v + Interval1f(-0.1f,+0.1f),Interval1f(0.0f,1.0f)); + cv = BBox1f(lerp(cv.lower,cv.upper,v.lower),lerp(cv.lower,cv.upper,v.upper)); + return true; + } + + __forceinline bool solve_krawczyk(bool very_small, BBox1f& cu, BBox1f& cv) + { + /* perform bezier clipping in v-direction to get tight v-bounds */ + TensorLinearCubicBezierSurface2fa curve2 = curve2d.clip(cu,cv); + const Vec2fa dv = curve2.axis_v(); + const TensorLinearCubicBezierSurface1f curve1v = curve2.xfm(dv); + LinearBezierCurve curve0v = curve1v.reduce_u(); + if (unlikely(!curve0v.hasRoot())) return true; + Interval1f v = bezier_clipping(curve0v); + if (unlikely(isEmpty(v))) return true; + v = intersect(v + Interval1f(-0.1f,+0.1f),Interval1f(0.0f,1.0f)); + curve2 = curve2.clip_v(v); + cv = BBox1f(lerp(cv.lower,cv.upper,v.lower),lerp(cv.lower,cv.upper,v.upper)); + + /* perform one newton raphson iteration */ + Vec2fa c(cu.center(),cv.center()); + Vec2fa f,dfdu,dfdv; curve2d.eval(c.x,c.y,f,dfdu,dfdv); + const LinearSpace2fa rcp_J = rcp(LinearSpace2fa(dfdu,dfdv)); + const Vec2fa c1 = c - rcp_J*f; + + /* calculate bounds of derivatives */ + const BBox2fa bounds_du = (1.0f/cu.size())*curve2.derivative_u().bounds(); + const BBox2fa bounds_dv = (1.0f/cv.size())*curve2.derivative_v().bounds(); + + /* calculate krawczyk test */ + LinearSpace2> I(Interval1f(1.0f), Interval1f(0.0f), + Interval1f(0.0f), Interval1f(1.0f)); + + LinearSpace2> G(Interval1f(bounds_du.lower.x,bounds_du.upper.x), Interval1f(bounds_dv.lower.x,bounds_dv.upper.x), + Interval1f(bounds_du.lower.y,bounds_du.upper.y), Interval1f(bounds_dv.lower.y,bounds_dv.upper.y)); + + const LinearSpace2 rcp_J2(rcp_J); + const LinearSpace2> rcp_Ji(rcp_J2); + + const Vec2 x(cu,cv); + const Vec2 K = Vec2(Vec2f(c1)) + (I - rcp_Ji*G)*(x-Vec2(Vec2f(c))); + + /* test if there is no solution */ + const Vec2 KK = intersect(K,x); + if (unlikely(isEmpty(KK.x) || isEmpty(KK.y))) return true; + + /* exit if convergence cannot get proven, but terminate if we are very small */ + if (unlikely(!subset(K,x) && !very_small)) return false; + + /* solve using newton raphson iteration of convergence is guarenteed */ + solve_newton_raphson_loop(cu,cv,c1,dfdu,dfdv,rcp_J); + return true; + } + + __forceinline void solve_newton_raphson_no_recursion(BBox1f cu, BBox1f cv) + { + if (!clip_v(cu,cv)) return; + return solve_newton_raphson(cu,cv); + } + + __forceinline void solve_newton_raphson_recursion(BBox1f cu, BBox1f cv) + { + unsigned int sptr = 0; + const unsigned int stack_size = 4; + unsigned int mask_stack[stack_size]; + BBox1f cu_stack[stack_size]; + BBox1f cv_stack[stack_size]; + goto entry; + + /* terminate if stack is empty */ + while (sptr) + { + /* pop from stack */ + { + sptr--; + size_t mask = mask_stack[sptr]; + cu = cu_stack[sptr]; + cv = cv_stack[sptr]; + const size_t i = bscf(mask); + mask_stack[sptr] = mask; + if (mask) sptr++; // there are still items on the stack + + /* process next element recurse into each hit curve segment */ + const float u0 = float(i+0)*(1.0f/(VSIZEX-1)); + const float u1 = float(i+1)*(1.0f/(VSIZEX-1)); + const BBox1f cui(lerp(cu.lower,cu.upper,u0),lerp(cu.lower,cu.upper,u1)); + cu = cui; + } + +#if 0 + solve_newton_raphson_no_recursion(cu,cv); + continue; + +#else + /* we assume convergence for small u ranges and verify using krawczyk */ + if (cu.size() < 1.0f/6.0f) { + const bool very_small = cu.size() < 0.001f || sptr >= stack_size; + if (solve_krawczyk(very_small,cu,cv)) { + continue; + } + } +#endif + + entry: + + /* split the curve into VSIZEX-1 segments in u-direction */ + vboolx valid = true; + TensorLinearCubicBezierSurface subcurves = curve2d.clip_v(cv).vsplit_u(valid,cu); + + /* slabs test in u-direction */ + Vec2vfx ndv = cross(subcurves.axis_v()); + BBox boundsv = subcurves.vxfm(ndv).bounds(); + valid &= boundsv.lower <= eps; + valid &= boundsv.upper >= -eps; + if (none(valid)) continue; + + /* slabs test in v-direction */ + Vec2vfx ndu = cross(subcurves.axis_u()); + BBox boundsu = subcurves.vxfm(ndu).bounds(); + valid &= boundsu.lower <= eps; + valid &= boundsu.upper >= -eps; + if (none(valid)) continue; + + /* push valid segments to stack */ + assert(sptr < stack_size); + mask_stack [sptr] = movemask(valid); + cu_stack [sptr] = cu; + cv_stack [sptr] = cv; + sptr++; + } + } + + __forceinline bool solve_newton_raphson_main() + { + BBox1f vu(0.0f,1.0f); + BBox1f vv(0.0f,1.0f); + solve_newton_raphson_recursion(vu,vv); + return isHit; + } + }; + + + template class SourceCurve> + struct OrientedCurve1Intersector1 + { + //template using Curve = SourceCurve; + typedef SourceCurve SourceCurve3ff; + typedef SourceCurve SourceCurve3fa; + + __forceinline OrientedCurve1Intersector1() {} + + __forceinline OrientedCurve1Intersector1(const Ray& ray, const void* ptr) {} + + template + __noinline bool intersect(const CurvePrecalculations1& pre, Ray& ray, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3ff& v0i, const Vec3ff& v1i, const Vec3ff& v2i, const Vec3ff& v3i, + const Vec3fa& n0i, const Vec3fa& n1i, const Vec3fa& n2i, const Vec3fa& n3i, + const Epilog& epilog) const + { + STAT3(normal.trav_prims,1,1,1); + + SourceCurve3ff ccurve(v0i,v1i,v2i,v3i); + SourceCurve3fa ncurve(n0i,n1i,n2i,n3i); + ccurve = enlargeRadiusToMinWidth(context,geom,ray.org,ccurve); + TensorLinearCubicBezierSurface3fa curve = TensorLinearCubicBezierSurface3fa::fromCenterAndNormalCurve(ccurve,ncurve); + //return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space,ray,curve,epilog).solve_bezier_clipping(); + return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space,ray,curve,epilog).solve_newton_raphson_main(); + } + + template + __noinline bool intersect(const CurvePrecalculations1& pre, Ray& ray, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const TensorLinearCubicBezierSurface3fa& curve, const Epilog& epilog) const + { + STAT3(normal.trav_prims,1,1,1); + //return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space,ray,curve,epilog).solve_bezier_clipping(); + return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space,ray,curve,epilog).solve_newton_raphson_main(); + } + }; + + template class SourceCurve, int K> + struct OrientedCurve1IntersectorK + { + //template using Curve = SourceCurve; + typedef SourceCurve SourceCurve3ff; + typedef SourceCurve SourceCurve3fa; + + struct Ray1 + { + __forceinline Ray1(RayK& ray, size_t k) + : org(ray.org.x[k],ray.org.y[k],ray.org.z[k]), dir(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]), _tnear(ray.tnear()[k]), tfar(ray.tfar[k]) {} + + Vec3fa org; + Vec3fa dir; + float _tnear; + float& tfar; + + __forceinline float& tnear() { return _tnear; } + //__forceinline float& tfar() { return _tfar; } + __forceinline const float& tnear() const { return _tnear; } + //__forceinline const float& tfar() const { return _tfar; } + }; + + template + __forceinline bool intersect(const CurvePrecalculationsK& pre, RayK& vray, size_t k, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3ff& v0i, const Vec3ff& v1i, const Vec3ff& v2i, const Vec3ff& v3i, + const Vec3fa& n0i, const Vec3fa& n1i, const Vec3fa& n2i, const Vec3fa& n3i, + const Epilog& epilog) + { + STAT3(normal.trav_prims,1,1,1); + Ray1 ray(vray,k); + SourceCurve3ff ccurve(v0i,v1i,v2i,v3i); + SourceCurve3fa ncurve(n0i,n1i,n2i,n3i); + ccurve = enlargeRadiusToMinWidth(context,geom,ray.org,ccurve); + TensorLinearCubicBezierSurface3fa curve = TensorLinearCubicBezierSurface3fa::fromCenterAndNormalCurve(ccurve,ncurve); + //return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space[k],ray,curve,epilog).solve_bezier_clipping(); + return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space[k],ray,curve,epilog).solve_newton_raphson_main(); + } + + template + __forceinline bool intersect(const CurvePrecalculationsK& pre, RayK& vray, size_t k, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const TensorLinearCubicBezierSurface3fa& curve, + const Epilog& epilog) + { + STAT3(normal.trav_prims,1,1,1); + Ray1 ray(vray,k); + //return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space[k],ray,curve,epilog).solve_bezier_clipping(); + return TensorLinearCubicBezierSurfaceIntersector(pre.ray_space[k],ray,curve,epilog).solve_newton_raphson_main(); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector_precalculations.h b/thirdparty/embree/kernels/geometry/curve_intersector_precalculations.h new file mode 100644 index 000000000000..6e9fc9192575 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector_precalculations.h @@ -0,0 +1,49 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "../common/geometry.h" + +namespace embree +{ + namespace isa + { + struct CurvePrecalculations1 + { + float depth_scale; + LinearSpace3fa ray_space; + + __forceinline CurvePrecalculations1() {} + + __forceinline CurvePrecalculations1(const Ray& ray, const void* ptr) + { + depth_scale = rsqrt(dot(ray.dir,ray.dir)); + LinearSpace3fa space = frame(depth_scale*ray.dir); + space.vz *= depth_scale; + ray_space = space.transposed(); + } + }; + + template + struct CurvePrecalculationsK + { + vfloat depth_scale; + LinearSpace3fa ray_space[K]; + + __forceinline CurvePrecalculationsK(const vbool& valid, const RayK& ray) + { + size_t mask = movemask(valid); + depth_scale = rsqrt(dot(ray.dir,ray.dir)); + while (mask) { + size_t k = bscf(mask); + Vec3fa ray_dir_k = Vec3fa(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]); + LinearSpace3fa ray_space_k = frame(depth_scale[k]*ray_dir_k); + ray_space_k.vz *= depth_scale[k]; + ray_space[k] = ray_space_k.transposed(); + } + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector_ribbon.h b/thirdparty/embree/kernels/geometry/curve_intersector_ribbon.h new file mode 100644 index 000000000000..a99cf99d56c5 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector_ribbon.h @@ -0,0 +1,214 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "quad_intersector.h" +#include "curve_intersector_precalculations.h" + +#define Bezier1Intersector1 RibbonCurve1Intersector1 +#define Bezier1IntersectorK RibbonCurve1IntersectorK + +namespace embree +{ + namespace isa + { + template + struct RibbonHit + { + __forceinline RibbonHit() {} + + __forceinline RibbonHit(const vbool& valid, const vfloat& U, const vfloat& V, const vfloat& T, const int i, const int N, + const NativeCurve3ff& curve3D) + : U(U), V(V), T(T), i(i), N(N), curve3D(curve3D), valid(valid) {} + + __forceinline void finalize() + { + vu = (vfloat(step)+U+vfloat(float(i)))*(1.0f/float(N)); + vv = V; + vt = T; + } + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { + return curve3D.eval_du(vu[i]); + } + + public: + vfloat U; + vfloat V; + vfloat T; + int i, N; + NativeCurve3ff curve3D; + + public: + vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + }; + + /* calculate squared distance of point p0 to line p1->p2 */ + __forceinline std::pair sqr_point_line_distance(const Vec2vfx& p0, const Vec2vfx& p1, const Vec2vfx& p2) + { + const vfloatx num = det(p2-p1,p1-p0); + const vfloatx den2 = dot(p2-p1,p2-p1); + return std::make_pair(num*num,den2); + } + + /* performs culling against a cylinder */ + __forceinline vboolx cylinder_culling_test(const Vec2vfx& p0, const Vec2vfx& p1, const Vec2vfx& p2, const vfloatx& r) + { + const std::pair d = sqr_point_line_distance(p0,p1,p2); + return d.first <= r*r*d.second; + } + + template + __forceinline bool intersect_ribbon(const Vec3fa& ray_org, const Vec3fa& ray_dir, const float ray_tnear, const float& ray_tfar, + const LinearSpace3fa& ray_space, const float& depth_scale, + const NativeCurve3ff& curve3D, const int N, + const Epilog& epilog) + { + /* transform control points into ray space */ + const NativeCurve3ff curve2D = curve3D.xfm_pr(ray_space,ray_org); + float eps = 4.0f*float(ulp)*reduce_max(max(abs(curve2D.v0),abs(curve2D.v1),abs(curve2D.v2),abs(curve2D.v3))); + + /* evaluate the bezier curve */ + bool ishit = false; + vboolx valid = vfloatx(step) < vfloatx(float(N)); + const Vec4vfx p0 = curve2D.template eval0(0,N); + const Vec4vfx p1 = curve2D.template eval1(0,N); + valid &= cylinder_culling_test(zero,Vec2vfx(p0.x,p0.y),Vec2vfx(p1.x,p1.y),max(p0.w,p1.w)); + + if (any(valid)) + { + Vec3vfx dp0dt = curve2D.template derivative0(0,N); + Vec3vfx dp1dt = curve2D.template derivative1(0,N); + dp0dt = select(reduce_max(abs(dp0dt)) < vfloatx(eps),Vec3vfx(p1-p0),dp0dt); + dp1dt = select(reduce_max(abs(dp1dt)) < vfloatx(eps),Vec3vfx(p1-p0),dp1dt); + const Vec3vfx n0(dp0dt.y,-dp0dt.x,0.0f); + const Vec3vfx n1(dp1dt.y,-dp1dt.x,0.0f); + const Vec3vfx nn0 = normalize(n0); + const Vec3vfx nn1 = normalize(n1); + const Vec3vfx lp0 = madd(p0.w,nn0,Vec3vfx(p0)); + const Vec3vfx lp1 = madd(p1.w,nn1,Vec3vfx(p1)); + const Vec3vfx up0 = nmadd(p0.w,nn0,Vec3vfx(p0)); + const Vec3vfx up1 = nmadd(p1.w,nn1,Vec3vfx(p1)); + + vfloatx vu,vv,vt; + vboolx valid0 = intersect_quad_backface_culling(valid,zero,Vec3fa(0,0,1),ray_tnear,ray_tfar,lp0,lp1,up1,up0,vu,vv,vt); + + if (any(valid0)) + { + /* ignore self intersections */ + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) { + vfloatx r = lerp(p0.w, p1.w, vu); + valid0 &= vt > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR)*r*depth_scale; + } + + if (any(valid0)) + { + vv = madd(2.0f,vv,vfloatx(-1.0f)); + RibbonHit bhit(valid0,vu,vv,vt,0,N,curve3D); + ishit |= epilog(bhit.valid,bhit); + } + } + } + + if (unlikely(VSIZEX < N)) + { + /* process SIMD-size many segments per iteration */ + for (int i=VSIZEX; i(i,N); + const Vec4vfx p1 = curve2D.template eval1(i,N); + valid &= cylinder_culling_test(zero,Vec2vfx(p0.x,p0.y),Vec2vfx(p1.x,p1.y),max(p0.w,p1.w)); + if (none(valid)) continue; + + Vec3vfx dp0dt = curve2D.template derivative0(i,N); + Vec3vfx dp1dt = curve2D.template derivative1(i,N); + dp0dt = select(reduce_max(abs(dp0dt)) < vfloatx(eps),Vec3vfx(p1-p0),dp0dt); + dp1dt = select(reduce_max(abs(dp1dt)) < vfloatx(eps),Vec3vfx(p1-p0),dp1dt); + const Vec3vfx n0(dp0dt.y,-dp0dt.x,0.0f); + const Vec3vfx n1(dp1dt.y,-dp1dt.x,0.0f); + const Vec3vfx nn0 = normalize(n0); + const Vec3vfx nn1 = normalize(n1); + const Vec3vfx lp0 = madd(p0.w,nn0,Vec3vfx(p0)); + const Vec3vfx lp1 = madd(p1.w,nn1,Vec3vfx(p1)); + const Vec3vfx up0 = nmadd(p0.w,nn0,Vec3vfx(p0)); + const Vec3vfx up1 = nmadd(p1.w,nn1,Vec3vfx(p1)); + + vfloatx vu,vv,vt; + vboolx valid0 = intersect_quad_backface_culling(valid,zero,Vec3fa(0,0,1),ray_tnear,ray_tfar,lp0,lp1,up1,up0,vu,vv,vt); + + if (any(valid0)) + { + /* ignore self intersections */ + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) { + vfloatx r = lerp(p0.w, p1.w, vu); + valid0 &= vt > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR)*r*depth_scale; + } + + if (any(valid0)) + { + vv = madd(2.0f,vv,vfloatx(-1.0f)); + RibbonHit bhit(valid0,vu,vv,vt,i,N,curve3D); + ishit |= epilog(bhit.valid,bhit); + } + } + } + } + return ishit; + } + + template class NativeCurve> + struct RibbonCurve1Intersector1 + { + typedef NativeCurve NativeCurve3ff; + + template + __forceinline bool intersect(const CurvePrecalculations1& pre, Ray& ray, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3ff& v0, const Vec3ff& v1, const Vec3ff& v2, const Vec3ff& v3, + const Epilog& epilog) + { + const int N = geom->tessellationRate; + NativeCurve3ff curve(v0,v1,v2,v3); + curve = enlargeRadiusToMinWidth(context,geom,ray.org,curve); + return intersect_ribbon(ray.org,ray.dir,ray.tnear(),ray.tfar, + pre.ray_space,pre.depth_scale, + curve,N, + epilog); + } + }; + + template class NativeCurve, int K> + struct RibbonCurve1IntersectorK + { + typedef NativeCurve NativeCurve3ff; + + template + __forceinline bool intersect(const CurvePrecalculationsK& pre, RayK& ray, size_t k, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3ff& v0, const Vec3ff& v1, const Vec3ff& v2, const Vec3ff& v3, + const Epilog& epilog) + { + const int N = geom->tessellationRate; + const Vec3fa ray_org(ray.org.x[k],ray.org.y[k],ray.org.z[k]); + const Vec3fa ray_dir(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]); + NativeCurve3ff curve(v0,v1,v2,v3); + curve = enlargeRadiusToMinWidth(context,geom,ray_org,curve); + return intersect_ribbon(ray_org,ray_dir,ray.tnear()[k],ray.tfar[k], + pre.ray_space[k],pre.depth_scale[k], + curve,N, + epilog); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector_sweep.h b/thirdparty/embree/kernels/geometry/curve_intersector_sweep.h new file mode 100644 index 000000000000..883cedc3d2a4 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector_sweep.h @@ -0,0 +1,362 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "cylinder.h" +#include "plane.h" +#include "line_intersector.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + namespace isa + { + static const size_t numJacobianIterations = 5; +#if defined(__AVX__) + static const size_t numBezierSubdivisions = 2; +#else + static const size_t numBezierSubdivisions = 3; +#endif + + struct BezierCurveHit + { + __forceinline BezierCurveHit() {} + + __forceinline BezierCurveHit(const float t, const float u, const Vec3fa& Ng) + : t(t), u(u), v(0.0f), Ng(Ng) {} + + __forceinline BezierCurveHit(const float t, const float u, const float v, const Vec3fa& Ng) + : t(t), u(u), v(v), Ng(Ng) {} + + __forceinline void finalize() {} + + public: + float t; + float u; + float v; + Vec3fa Ng; + }; + + template + __forceinline bool intersect_bezier_iterative_debug(const Ray& ray, const float dt, const NativeCurve3ff& curve, size_t i, + const vfloatx& u, const BBox& tp, const BBox& h0, const BBox& h1, + const Vec3vfx& Ng, const Vec4vfx& dP0du, const Vec4vfx& dP3du, + const Epilog& epilog) + { + if (tp.lower[i]+dt > ray.tfar) return false; + Vec3fa Ng_o = Vec3fa(Ng.x[i],Ng.y[i],Ng.z[i]); + if (h0.lower[i] == tp.lower[i]) Ng_o = -Vec3fa(dP0du.x[i],dP0du.y[i],dP0du.z[i]); + if (h1.lower[i] == tp.lower[i]) Ng_o = +Vec3fa(dP3du.x[i],dP3du.y[i],dP3du.z[i]); + BezierCurveHit hit(tp.lower[i]+dt,u[i],Ng_o); + return epilog(hit); + } + + template + __forceinline bool intersect_bezier_iterative_jacobian(const Ray& ray, const float dt, const NativeCurve3ff& curve, float u, float t, const Epilog& epilog) + { + const Vec3fa org = zero; + const Vec3fa dir = ray.dir; + const float length_ray_dir = length(dir); + + /* error of curve evaluations is propertional to largest coordinate */ + const BBox3ff box = curve.bounds(); + const float P_err = 16.0f*float(ulp)*reduce_max(max(abs(box.lower),abs(box.upper))); + + for (size_t i=0; i= 0.0f && u <= 1.0f)) return false; // rejects NaNs + const Vec3fa R = normalize(Q-P); + const Vec3fa U = madd(Vec3fa(dPdu.w),R,dPdu); + const Vec3fa V = cross(dPdu,R); + BezierCurveHit hit(t,u,cross(V,U)); + return epilog(hit); + } + } + return false; + } + + template + bool intersect_bezier_recursive_jacobian(const Ray& ray, const float dt, const NativeCurve3ff& curve, + float u0, float u1, unsigned int depth, const Epilog& epilog) + { +#if defined(__AVX__) + typedef vbool8 vboolx; // maximally 8-wide to work around KNL issues + typedef vint8 vintx; + typedef vfloat8 vfloatx; +#else + typedef vbool4 vboolx; + typedef vint4 vintx; + typedef vfloat4 vfloatx; +#endif + typedef Vec3 Vec3vfx; + typedef Vec4 Vec4vfx; + + unsigned int maxDepth = numBezierSubdivisions; + bool found = false; + const Vec3fa org = zero; + const Vec3fa dir = ray.dir; + + unsigned int sptr = 0; + const unsigned int stack_size = numBezierSubdivisions+1; // +1 because of unstable workaround below + struct StackEntry { + vboolx valid; + vfloatx tlower; + float u0; + float u1; + unsigned int depth; + }; + StackEntry stack[stack_size]; + goto entry; + + /* terminate if stack is empty */ + while (sptr) + { + /* pop from stack */ + { + sptr--; + vboolx valid = stack[sptr].valid; + const vfloatx tlower = stack[sptr].tlower; + valid &= tlower+dt <= ray.tfar; + if (none(valid)) continue; + u0 = stack[sptr].u0; + u1 = stack[sptr].u1; + depth = stack[sptr].depth; + const size_t i = select_min(valid,tlower); clear(valid,i); + stack[sptr].valid = valid; + if (any(valid)) sptr++; // there are still items on the stack + + /* process next segment */ + const vfloatx vu0 = lerp(u0,u1,vfloatx(step)*(1.0f/(vfloatx::size-1))); + u0 = vu0[i+0]; + u1 = vu0[i+1]; + } + entry: + + /* subdivide curve */ + const float dscale = (u1-u0)*(1.0f/(3.0f*(vfloatx::size-1))); + const vfloatx vu0 = lerp(u0,u1,vfloatx(step)*(1.0f/(vfloatx::size-1))); + Vec4vfx P0, dP0du; curve.veval(vu0,P0,dP0du); dP0du = dP0du * Vec4vfx(dscale); + const Vec4vfx P3 = shift_right_1(P0); + const Vec4vfx dP3du = shift_right_1(dP0du); + const Vec4vfx P1 = P0 + dP0du; + const Vec4vfx P2 = P3 - dP3du; + + /* calculate bounding cylinders */ + const vfloatx rr1 = sqr_point_to_line_distance(Vec3vfx(dP0du),Vec3vfx(P3-P0)); + const vfloatx rr2 = sqr_point_to_line_distance(Vec3vfx(dP3du),Vec3vfx(P3-P0)); + const vfloatx maxr12 = sqrt(max(rr1,rr2)); + const vfloatx one_plus_ulp = 1.0f+2.0f*float(ulp); + const vfloatx one_minus_ulp = 1.0f-2.0f*float(ulp); + vfloatx r_outer = max(P0.w,P1.w,P2.w,P3.w)+maxr12; + vfloatx r_inner = min(P0.w,P1.w,P2.w,P3.w)-maxr12; + r_outer = one_plus_ulp*r_outer; + r_inner = max(0.0f,one_minus_ulp*r_inner); + const CylinderN cylinder_outer(Vec3vfx(P0),Vec3vfx(P3),r_outer); + const CylinderN cylinder_inner(Vec3vfx(P0),Vec3vfx(P3),r_inner); + vboolx valid = true; clear(valid,vfloatx::size-1); + + /* intersect with outer cylinder */ + BBox tc_outer; vfloatx u_outer0; Vec3vfx Ng_outer0; vfloatx u_outer1; Vec3vfx Ng_outer1; + valid &= cylinder_outer.intersect(org,dir,tc_outer,u_outer0,Ng_outer0,u_outer1,Ng_outer1); + if (none(valid)) continue; + + /* intersect with cap-planes */ + BBox tp(ray.tnear()-dt,ray.tfar-dt); + tp = embree::intersect(tp,tc_outer); + BBox h0 = HalfPlaneN(Vec3vfx(P0),+Vec3vfx(dP0du)).intersect(org,dir); + tp = embree::intersect(tp,h0); + BBox h1 = HalfPlaneN(Vec3vfx(P3),-Vec3vfx(dP3du)).intersect(org,dir); + tp = embree::intersect(tp,h1); + valid &= tp.lower <= tp.upper; + if (none(valid)) continue; + + /* clamp and correct u parameter */ + u_outer0 = clamp(u_outer0,vfloatx(0.0f),vfloatx(1.0f)); + u_outer1 = clamp(u_outer1,vfloatx(0.0f),vfloatx(1.0f)); + u_outer0 = lerp(u0,u1,(vfloatx(step)+u_outer0)*(1.0f/float(vfloatx::size))); + u_outer1 = lerp(u0,u1,(vfloatx(step)+u_outer1)*(1.0f/float(vfloatx::size))); + + /* intersect with inner cylinder */ + BBox tc_inner; + vfloatx u_inner0 = zero; Vec3vfx Ng_inner0 = zero; vfloatx u_inner1 = zero; Vec3vfx Ng_inner1 = zero; + const vboolx valid_inner = cylinder_inner.intersect(org,dir,tc_inner,u_inner0,Ng_inner0,u_inner1,Ng_inner1); + + /* at the unstable area we subdivide deeper */ + const vboolx unstable0 = (!valid_inner) | (abs(dot(Vec3vfx(Vec3fa(ray.dir)),Ng_inner0)) < 0.3f); + const vboolx unstable1 = (!valid_inner) | (abs(dot(Vec3vfx(Vec3fa(ray.dir)),Ng_inner1)) < 0.3f); + + /* subtract the inner interval from the current hit interval */ + BBox tp0, tp1; + subtract(tp,tc_inner,tp0,tp1); + vboolx valid0 = valid & (tp0.lower <= tp0.upper); + vboolx valid1 = valid & (tp1.lower <= tp1.upper); + if (none(valid0 | valid1)) continue; + + /* iterate over all first hits front to back */ + const vintx termDepth0 = select(unstable0,vintx(maxDepth+1),vintx(maxDepth)); + vboolx recursion_valid0 = valid0 & (depth < termDepth0); + valid0 &= depth >= termDepth0; + + while (any(valid0)) + { + const size_t i = select_min(valid0,tp0.lower); clear(valid0,i); + found = found | intersect_bezier_iterative_jacobian(ray,dt,curve,u_outer0[i],tp0.lower[i],epilog); + //found = found | intersect_bezier_iterative_debug (ray,dt,curve,i,u_outer0,tp0,h0,h1,Ng_outer0,dP0du,dP3du,epilog); + valid0 &= tp0.lower+dt <= ray.tfar; + } + valid1 &= tp1.lower+dt <= ray.tfar; + + /* iterate over all second hits front to back */ + const vintx termDepth1 = select(unstable1,vintx(maxDepth+1),vintx(maxDepth)); + vboolx recursion_valid1 = valid1 & (depth < termDepth1); + valid1 &= depth >= termDepth1; + while (any(valid1)) + { + const size_t i = select_min(valid1,tp1.lower); clear(valid1,i); + found = found | intersect_bezier_iterative_jacobian(ray,dt,curve,u_outer1[i],tp1.upper[i],epilog); + //found = found | intersect_bezier_iterative_debug (ray,dt,curve,i,u_outer1,tp1,h0,h1,Ng_outer1,dP0du,dP3du,epilog); + valid1 &= tp1.lower+dt <= ray.tfar; + } + + /* push valid segments to stack */ + recursion_valid0 &= tp0.lower+dt <= ray.tfar; + recursion_valid1 &= tp1.lower+dt <= ray.tfar; + const vboolx recursion_valid = recursion_valid0 | recursion_valid1; + if (any(recursion_valid)) + { + assert(sptr < stack_size); + stack[sptr].valid = recursion_valid; + stack[sptr].tlower = select(recursion_valid0,tp0.lower,tp1.lower); + stack[sptr].u0 = u0; + stack[sptr].u1 = u1; + stack[sptr].depth = depth+1; + sptr++; + } + } + return found; + } + + template class NativeCurve> + struct SweepCurve1Intersector1 + { + typedef NativeCurve NativeCurve3ff; + + template + __noinline bool intersect(const CurvePrecalculations1& pre, Ray& ray, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3ff& v0, const Vec3ff& v1, const Vec3ff& v2, const Vec3ff& v3, + const Epilog& epilog) + { + STAT3(normal.trav_prims,1,1,1); + + /* move ray closer to make intersection stable */ + NativeCurve3ff curve0(v0,v1,v2,v3); + curve0 = enlargeRadiusToMinWidth(context,geom,ray.org,curve0); + const float dt = dot(curve0.center()-ray.org,ray.dir)*rcp(dot(ray.dir,ray.dir)); + const Vec3ff ref(madd(Vec3fa(dt),ray.dir,ray.org),0.0f); + const NativeCurve3ff curve1 = curve0-ref; + return intersect_bezier_recursive_jacobian(ray,dt,curve1,0.0f,1.0f,1,epilog); + } + }; + + template class NativeCurve, int K> + struct SweepCurve1IntersectorK + { + typedef NativeCurve NativeCurve3ff; + + struct Ray1 + { + __forceinline Ray1(RayK& ray, size_t k) + : org(ray.org.x[k],ray.org.y[k],ray.org.z[k]), dir(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]), _tnear(ray.tnear()[k]), tfar(ray.tfar[k]) {} + + Vec3fa org; + Vec3fa dir; + float _tnear; + float& tfar; + + __forceinline float& tnear() { return _tnear; } + //__forceinline float& tfar() { return _tfar; } + __forceinline const float& tnear() const { return _tnear; } + //__forceinline const float& tfar() const { return _tfar; } + + }; + + template + __forceinline bool intersect(const CurvePrecalculationsK& pre, RayK& vray, size_t k, + IntersectContext* context, + const CurveGeometry* geom, const unsigned int primID, + const Vec3ff& v0, const Vec3ff& v1, const Vec3ff& v2, const Vec3ff& v3, + const Epilog& epilog) + { + STAT3(normal.trav_prims,1,1,1); + Ray1 ray(vray,k); + + /* move ray closer to make intersection stable */ + NativeCurve3ff curve0(v0,v1,v2,v3); + curve0 = enlargeRadiusToMinWidth(context,geom,ray.org,curve0); + const float dt = dot(curve0.center()-ray.org,ray.dir)*rcp(dot(ray.dir,ray.dir)); + const Vec3ff ref(madd(Vec3fa(dt),ray.dir,ray.org),0.0f); + const NativeCurve3ff curve1 = curve0-ref; + return intersect_bezier_recursive_jacobian(ray,dt,curve1,0.0f,1.0f,1,epilog); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/curve_intersector_virtual.h b/thirdparty/embree/kernels/geometry/curve_intersector_virtual.h new file mode 100644 index 000000000000..e1f42381306e --- /dev/null +++ b/thirdparty/embree/kernels/geometry/curve_intersector_virtual.h @@ -0,0 +1,671 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "../subdiv/bezier_curve.h" +#include "../common/primref.h" +#include "curve_intersector_precalculations.h" +#include "../bvh/node_intersector1.h" +#include "../bvh/node_intersector_packet.h" + +#include "intersector_epilog.h" + +#include "../subdiv/bezier_curve.h" +#include "../subdiv/bspline_curve.h" +#include "../subdiv/hermite_curve.h" +#include "../subdiv/catmullrom_curve.h" + +#include "spherei_intersector.h" +#include "disci_intersector.h" + +#include "linei_intersector.h" +#include "roundlinei_intersector.h" +#include "conelinei_intersector.h" + +#include "curveNi_intersector.h" +#include "curveNv_intersector.h" +#include "curveNi_mb_intersector.h" + +#include "curve_intersector_distance.h" +#include "curve_intersector_ribbon.h" +#include "curve_intersector_oriented.h" +#include "curve_intersector_sweep.h" + +namespace embree +{ + struct VirtualCurveIntersector + { + typedef void (*Intersect1Ty)(void* pre, void* ray, IntersectContext* context, const void* primitive); + typedef bool (*Occluded1Ty )(void* pre, void* ray, IntersectContext* context, const void* primitive); + + typedef void (*Intersect4Ty)(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + typedef bool (*Occluded4Ty) (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + + typedef void (*Intersect8Ty)(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + typedef bool (*Occluded8Ty) (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + + typedef void (*Intersect16Ty)(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + typedef bool (*Occluded16Ty) (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + + public: + struct Intersectors + { + Intersectors() {} // WARNING: Do not zero initialize this, as we otherwise get problems with thread unsafe local static variable initialization (e.g. on VS2013) in curve_intersector_virtual.cpp. + + template void intersect(void* pre, void* ray, IntersectContext* context, const void* primitive); + template bool occluded (void* pre, void* ray, IntersectContext* context, const void* primitive); + + template void intersect(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + template bool occluded (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive); + + public: + Intersect1Ty intersect1; + Occluded1Ty occluded1; + Intersect4Ty intersect4; + Occluded4Ty occluded4; + Intersect8Ty intersect8; + Occluded8Ty occluded8; + Intersect16Ty intersect16; + Occluded16Ty occluded16; + }; + + Intersectors vtbl[Geometry::GTY_END]; + }; + + template<> __forceinline void VirtualCurveIntersector::Intersectors::intersect<1> (void* pre, void* ray, IntersectContext* context, const void* primitive) { assert(intersect1); intersect1(pre,ray,context,primitive); } + template<> __forceinline bool VirtualCurveIntersector::Intersectors::occluded<1> (void* pre, void* ray, IntersectContext* context, const void* primitive) { assert(occluded1); return occluded1(pre,ray,context,primitive); } + + template<> __forceinline void VirtualCurveIntersector::Intersectors::intersect<4>(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive) { assert(intersect4); intersect4(pre,ray,k,context,primitive); } + template<> __forceinline bool VirtualCurveIntersector::Intersectors::occluded<4> (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive) { assert(occluded4); return occluded4(pre,ray,k,context,primitive); } + +#if defined(__AVX__) + template<> __forceinline void VirtualCurveIntersector::Intersectors::intersect<8>(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive) { assert(intersect8); intersect8(pre,ray,k,context,primitive); } + template<> __forceinline bool VirtualCurveIntersector::Intersectors::occluded<8> (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive) { assert(occluded8); return occluded8(pre,ray,k,context,primitive); } +#endif + +#if defined(__AVX512F__) + template<> __forceinline void VirtualCurveIntersector::Intersectors::intersect<16>(void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive) { assert(intersect16); intersect16(pre,ray,k,context,primitive); } + template<> __forceinline bool VirtualCurveIntersector::Intersectors::occluded<16> (void* pre, void* ray, size_t k, IntersectContext* context, const void* primitive) { assert(occluded16); return occluded16(pre,ray,k,context,primitive); } +#endif + + namespace isa + { + struct VirtualCurveIntersector1 + { + typedef unsigned char Primitive; + typedef CurvePrecalculations1 Precalculations; + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurveIntersector::Intersectors& leafIntersector = ((VirtualCurveIntersector*) This->leafIntersector)->vtbl[ty]; + leafIntersector.intersect<1>(&pre,&ray,context,prim); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurveIntersector::Intersectors& leafIntersector = ((VirtualCurveIntersector*) This->leafIntersector)->vtbl[ty]; + return leafIntersector.occluded<1>(&pre,&ray,context,prim); + } + }; + + template + struct VirtualCurveIntersectorK + { + typedef unsigned char Primitive; + typedef CurvePrecalculationsK Precalculations; + + template + static __forceinline void intersect(const vbool& valid_i, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurveIntersector::Intersectors& leafIntersector = ((VirtualCurveIntersector*) This->leafIntersector)->vtbl[ty]; + size_t mask = movemask(valid_i); + while (mask) leafIntersector.intersect(&pre,&ray,bscf(mask),context,prim); + } + + template + static __forceinline vbool occluded(const vbool& valid_i, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurveIntersector::Intersectors& leafIntersector = ((VirtualCurveIntersector*) This->leafIntersector)->vtbl[ty]; + vbool valid_o = false; + size_t mask = movemask(valid_i); + while (mask) { + size_t k = bscf(mask); + if (leafIntersector.occluded(&pre,&ray,k,context,prim)) + set(valid_o, k); + } + return valid_o; + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurveIntersector::Intersectors& leafIntersector = ((VirtualCurveIntersector*) This->leafIntersector)->vtbl[ty]; + leafIntersector.intersect(&pre,&ray,k,context,prim); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + assert(num == 1); + RTCGeometryType ty = (RTCGeometryType)(*prim); + assert(This->leafIntersector); + VirtualCurveIntersector::Intersectors& leafIntersector = ((VirtualCurveIntersector*) This->leafIntersector)->vtbl[ty]; + return leafIntersector.occluded(&pre,&ray,k,context,prim); + } + }; + + template + static VirtualCurveIntersector::Intersectors LinearRoundConeNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &RoundLinearCurveMiIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &RoundLinearCurveMiIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &RoundLinearCurveMiIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &RoundLinearCurveMiIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&RoundLinearCurveMiIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &RoundLinearCurveMiIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&RoundLinearCurveMiIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &RoundLinearCurveMiIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors LinearConeNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &ConeCurveMiIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &ConeCurveMiIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &ConeCurveMiIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &ConeCurveMiIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&ConeCurveMiIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &ConeCurveMiIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&ConeCurveMiIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &ConeCurveMiIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors LinearRoundConeNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &RoundLinearCurveMiMBIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &RoundLinearCurveMiMBIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &RoundLinearCurveMiMBIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &RoundLinearCurveMiMBIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&RoundLinearCurveMiMBIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &RoundLinearCurveMiMBIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&RoundLinearCurveMiMBIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &RoundLinearCurveMiMBIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors LinearConeNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &ConeCurveMiMBIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &ConeCurveMiMBIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &ConeCurveMiMBIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &ConeCurveMiMBIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&ConeCurveMiMBIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &ConeCurveMiMBIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&ConeCurveMiMBIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &ConeCurveMiMBIntersectorK::occluded; +#endif + return intersectors; + } + + + template + static VirtualCurveIntersector::Intersectors LinearRibbonNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &FlatLinearCurveMiIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &FlatLinearCurveMiIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &FlatLinearCurveMiIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &FlatLinearCurveMiIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&FlatLinearCurveMiIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &FlatLinearCurveMiIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&FlatLinearCurveMiIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &FlatLinearCurveMiIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors LinearRibbonNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &FlatLinearCurveMiMBIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &FlatLinearCurveMiMBIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &FlatLinearCurveMiMBIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &FlatLinearCurveMiMBIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&FlatLinearCurveMiMBIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &FlatLinearCurveMiMBIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&FlatLinearCurveMiMBIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &FlatLinearCurveMiMBIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors SphereNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &SphereMiIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &SphereMiIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &SphereMiIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &SphereMiIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&SphereMiIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &SphereMiIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&SphereMiIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &SphereMiIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors SphereNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &SphereMiMBIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &SphereMiMBIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &SphereMiMBIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &SphereMiMBIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&SphereMiMBIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &SphereMiMBIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&SphereMiMBIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &SphereMiMBIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors DiscNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &DiscMiIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &DiscMiIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &DiscMiIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &DiscMiIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&DiscMiIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &DiscMiIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&DiscMiIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &DiscMiIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors DiscNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &DiscMiMBIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &DiscMiMBIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &DiscMiMBIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &DiscMiMBIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&DiscMiMBIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &DiscMiMBIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&DiscMiMBIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &DiscMiMBIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors OrientedDiscNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &OrientedDiscMiIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &OrientedDiscMiIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &OrientedDiscMiIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &OrientedDiscMiIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&OrientedDiscMiIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &OrientedDiscMiIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&OrientedDiscMiIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &OrientedDiscMiIntersectorK::occluded; +#endif + return intersectors; + } + + template + static VirtualCurveIntersector::Intersectors OrientedDiscNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &OrientedDiscMiMBIntersector1::intersect; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &OrientedDiscMiMBIntersector1::occluded; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &OrientedDiscMiMBIntersectorK::intersect; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &OrientedDiscMiMBIntersectorK::occluded; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&OrientedDiscMiMBIntersectorK::intersect; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &OrientedDiscMiMBIntersectorK::occluded; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&OrientedDiscMiMBIntersectorK::intersect; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &OrientedDiscMiMBIntersectorK::occluded; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors RibbonNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiIntersector1::template intersect_t, Intersect1EpilogMU >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiIntersector1::template occluded_t , Occluded1EpilogMU >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &CurveNiIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors RibbonNvIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNvIntersector1::template intersect_t, Intersect1EpilogMU >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNvIntersector1::template occluded_t , Occluded1EpilogMU >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &CurveNvIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNvIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNvIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNvIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNvIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNvIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors RibbonNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiMBIntersector1::template intersect_t, Intersect1EpilogMU >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiMBIntersector1::template occluded_t , Occluded1EpilogMU >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty) &CurveNiMBIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiMBIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiMBIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiMBIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiMBIntersectorK::template intersect_t, Intersect1KEpilogMU >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiMBIntersectorK::template occluded_t , Occluded1KEpilogMU >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors CurveNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiIntersector1::template intersect_t, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiIntersector1::template occluded_t , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiIntersectorK::template intersect_t, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiIntersectorK::template occluded_t , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiIntersectorK::template intersect_t, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiIntersectorK::template occluded_t , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiIntersectorK::template intersect_t, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiIntersectorK::template occluded_t , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors CurveNvIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNvIntersector1::template intersect_t, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNvIntersector1::template occluded_t , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNvIntersectorK::template intersect_t, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNvIntersectorK::template occluded_t , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNvIntersectorK::template intersect_t, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNvIntersectorK::template occluded_t , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNvIntersectorK::template intersect_t, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNvIntersectorK::template occluded_t , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors CurveNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiMBIntersector1::template intersect_t, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiMBIntersector1::template occluded_t , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiMBIntersectorK::template intersect_t, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiMBIntersectorK::template occluded_t , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiMBIntersectorK::template intersect_t, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiMBIntersectorK::template occluded_t , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiMBIntersectorK::template intersect_t, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiMBIntersectorK::template occluded_t , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors OrientedCurveNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiIntersector1::template intersect_n, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiIntersector1::template occluded_n , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiIntersectorK::template intersect_n, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiIntersectorK::template occluded_n , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiIntersectorK::template intersect_n, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiIntersectorK::template occluded_n , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiIntersectorK::template intersect_n, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiIntersectorK::template occluded_n , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors OrientedCurveNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiMBIntersector1::template intersect_n, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiMBIntersector1::template occluded_n , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiMBIntersectorK::template intersect_n, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiMBIntersectorK::template occluded_n , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiMBIntersectorK::template intersect_n, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiMBIntersectorK::template occluded_n , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiMBIntersectorK::template intersect_n, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiMBIntersectorK::template occluded_n , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors HermiteRibbonNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiIntersector1::template intersect_h, Intersect1EpilogMU >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiIntersector1::template occluded_h , Occluded1EpilogMU >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiIntersectorK::template intersect_h, Intersect1KEpilogMU >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiIntersectorK::template occluded_h , Occluded1KEpilogMU >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiIntersectorK::template intersect_h, Intersect1KEpilogMU >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiIntersectorK::template occluded_h , Occluded1KEpilogMU >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiIntersectorK::template intersect_h, Intersect1KEpilogMU >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiIntersectorK::template occluded_h , Occluded1KEpilogMU >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors HermiteRibbonNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiMBIntersector1::template intersect_h, Intersect1EpilogMU >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiMBIntersector1::template occluded_h , Occluded1EpilogMU >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiMBIntersectorK::template intersect_h, Intersect1KEpilogMU >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiMBIntersectorK::template occluded_h , Occluded1KEpilogMU >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiMBIntersectorK::template intersect_h, Intersect1KEpilogMU >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiMBIntersectorK::template occluded_h , Occluded1KEpilogMU >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiMBIntersectorK::template intersect_h, Intersect1KEpilogMU >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiMBIntersectorK::template occluded_h , Occluded1KEpilogMU >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors HermiteCurveNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiIntersector1::template intersect_h, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiIntersector1::template occluded_h , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiIntersectorK::template intersect_h, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiIntersectorK::template occluded_h , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiIntersectorK::template intersect_h, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiIntersectorK::template occluded_h , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiIntersectorK::template intersect_h, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiIntersectorK::template occluded_h , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors HermiteCurveNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiMBIntersector1::template intersect_h, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiMBIntersector1::template occluded_h , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiMBIntersectorK::template intersect_h, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiMBIntersectorK::template occluded_h , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiMBIntersectorK::template intersect_h, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiMBIntersectorK::template occluded_h , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiMBIntersectorK::template intersect_h, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiMBIntersectorK::template occluded_h , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors HermiteOrientedCurveNiIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiIntersector1::template intersect_hn, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiIntersector1::template occluded_hn , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiIntersectorK::template intersect_hn, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiIntersectorK::template occluded_hn , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiIntersectorK::template intersect_hn, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiIntersectorK::template occluded_hn , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiIntersectorK::template intersect_hn, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiIntersectorK::template occluded_hn , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + + template class Curve, int N> + static VirtualCurveIntersector::Intersectors HermiteOrientedCurveNiMBIntersectors() + { + VirtualCurveIntersector::Intersectors intersectors; + intersectors.intersect1 = (VirtualCurveIntersector::Intersect1Ty) &CurveNiMBIntersector1::template intersect_hn, Intersect1Epilog1 >; + intersectors.occluded1 = (VirtualCurveIntersector::Occluded1Ty) &CurveNiMBIntersector1::template occluded_hn , Occluded1Epilog1 >; + intersectors.intersect4 = (VirtualCurveIntersector::Intersect4Ty)&CurveNiMBIntersectorK::template intersect_hn, Intersect1KEpilog1<4,true> >; + intersectors.occluded4 = (VirtualCurveIntersector::Occluded4Ty) &CurveNiMBIntersectorK::template occluded_hn , Occluded1KEpilog1<4,true> >; +#if defined(__AVX__) + intersectors.intersect8 = (VirtualCurveIntersector::Intersect8Ty)&CurveNiMBIntersectorK::template intersect_hn, Intersect1KEpilog1<8,true> >; + intersectors.occluded8 = (VirtualCurveIntersector::Occluded8Ty) &CurveNiMBIntersectorK::template occluded_hn , Occluded1KEpilog1<8,true> >; +#endif +#if defined(__AVX512F__) + intersectors.intersect16 = (VirtualCurveIntersector::Intersect16Ty)&CurveNiMBIntersectorK::template intersect_hn, Intersect1KEpilog1<16,true> >; + intersectors.occluded16 = (VirtualCurveIntersector::Occluded16Ty) &CurveNiMBIntersectorK::template occluded_hn , Occluded1KEpilog1<16,true> >; +#endif + return intersectors; + } + } +} diff --git a/thirdparty/embree/kernels/geometry/cylinder.h b/thirdparty/embree/kernels/geometry/cylinder.h new file mode 100644 index 000000000000..39a582864c52 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/cylinder.h @@ -0,0 +1,223 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" + +namespace embree +{ + namespace isa + { + struct Cylinder + { + const Vec3fa p0; //!< start location + const Vec3fa p1; //!< end position + const float rr; //!< squared radius of cylinder + + __forceinline Cylinder(const Vec3fa& p0, const Vec3fa& p1, const float r) + : p0(p0), p1(p1), rr(sqr(r)) {} + + __forceinline Cylinder(const Vec3fa& p0, const Vec3fa& p1, const float rr, bool) + : p0(p0), p1(p1), rr(rr) {} + + __forceinline bool intersect(const Vec3fa& org, + const Vec3fa& dir, + BBox1f& t_o, + float& u0_o, Vec3fa& Ng0_o, + float& u1_o, Vec3fa& Ng1_o) const + { + /* calculate quadratic equation to solve */ + const float rl = rcp_length(p1-p0); + const Vec3fa P0 = p0, dP = (p1-p0)*rl; + const Vec3fa O = org-P0, dO = dir; + + const float dOdO = dot(dO,dO); + const float OdO = dot(dO,O); + const float OO = dot(O,O); + const float dOz = dot(dP,dO); + const float Oz = dot(dP,O); + + const float A = dOdO - sqr(dOz); + const float B = 2.0f * (OdO - dOz*Oz); + const float C = OO - sqr(Oz) - rr; + + /* we miss the cylinder if determinant is smaller than zero */ + const float D = B*B - 4.0f*A*C; + if (D < 0.0f) { + t_o = BBox1f(pos_inf,neg_inf); + return false; + } + + /* special case for rays that are parallel to the cylinder */ + const float eps = 16.0f*float(ulp)*max(abs(dOdO),abs(sqr(dOz))); + if (abs(A) < eps) + { + if (C <= 0.0f) { + t_o = BBox1f(neg_inf,pos_inf); + return true; + } else { + t_o = BBox1f(pos_inf,neg_inf); + return false; + } + } + + /* standard case for rays that are not parallel to the cylinder */ + const float Q = sqrt(D); + const float rcp_2A = rcp(2.0f*A); + const float t0 = (-B-Q)*rcp_2A; + const float t1 = (-B+Q)*rcp_2A; + + /* calculates u and Ng for near hit */ + { + u0_o = madd(t0,dOz,Oz)*rl; + const Vec3fa Pr = t0*dir; + const Vec3fa Pl = madd(u0_o,p1-p0,p0); + Ng0_o = Pr-Pl; + } + + /* calculates u and Ng for far hit */ + { + u1_o = madd(t1,dOz,Oz)*rl; + const Vec3fa Pr = t1*dir; + const Vec3fa Pl = madd(u1_o,p1-p0,p0); + Ng1_o = Pr-Pl; + } + + t_o.lower = t0; + t_o.upper = t1; + return true; + } + + __forceinline bool intersect(const Vec3fa& org_i, const Vec3fa& dir, BBox1f& t_o) const + { + float u0_o; Vec3fa Ng0_o; + float u1_o; Vec3fa Ng1_o; + return intersect(org_i,dir,t_o,u0_o,Ng0_o,u1_o,Ng1_o); + } + + static bool verify(const size_t id, const Cylinder& cylinder, const RayHit& ray, bool shouldhit, const float t0, const float t1) + { + float eps = 0.001f; + BBox1f t; bool hit; + hit = cylinder.intersect(ray.org,ray.dir,t); + + bool failed = hit != shouldhit; + if (shouldhit) failed |= std::isinf(t0) ? t0 != t.lower : abs(t0-t.lower) > eps; + if (shouldhit) failed |= std::isinf(t1) ? t1 != t.upper : abs(t1-t.upper) > eps; + if (!failed) return true; + embree_cout << "Cylinder test " << id << " failed: cylinder = " << cylinder << ", ray = " << ray << ", hit = " << hit << ", t = " << t << embree_endl; + return false; + } + + /* verify cylinder class */ + static bool verify() + { + bool passed = true; + const Cylinder cylinder(Vec3fa(0.0f,0.0f,0.0f),Vec3fa(1.0f,0.0f,0.0f),1.0f); + passed &= verify(0,cylinder,RayHit(Vec3fa(-2.0f,1.0f,0.0f),Vec3fa( 0.0f,-1.0f,+0.0f),0.0f,float(inf)),true,0.0f,2.0f); + passed &= verify(1,cylinder,RayHit(Vec3fa(+2.0f,1.0f,0.0f),Vec3fa( 0.0f,-1.0f,+0.0f),0.0f,float(inf)),true,0.0f,2.0f); + passed &= verify(2,cylinder,RayHit(Vec3fa(+2.0f,1.0f,2.0f),Vec3fa( 0.0f,-1.0f,+0.0f),0.0f,float(inf)),false,0.0f,0.0f); + passed &= verify(3,cylinder,RayHit(Vec3fa(+0.0f,0.0f,0.0f),Vec3fa( 1.0f, 0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,pos_inf); + passed &= verify(4,cylinder,RayHit(Vec3fa(+0.0f,0.0f,0.0f),Vec3fa(-1.0f, 0.0f,+0.0f),0.0f,float(inf)),true,neg_inf,pos_inf); + passed &= verify(5,cylinder,RayHit(Vec3fa(+0.0f,2.0f,0.0f),Vec3fa( 1.0f, 0.0f,+0.0f),0.0f,float(inf)),false,pos_inf,neg_inf); + passed &= verify(6,cylinder,RayHit(Vec3fa(+0.0f,2.0f,0.0f),Vec3fa(-1.0f, 0.0f,+0.0f),0.0f,float(inf)),false,pos_inf,neg_inf); + return passed; + } + + /*! output operator */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const Cylinder& c) { + return cout << "Cylinder { p0 = " << c.p0 << ", p1 = " << c.p1 << ", r = " << sqrtf(c.rr) << "}"; + } + }; + + template + struct CylinderN + { + const Vec3vf p0; //!< start location + const Vec3vf p1; //!< end position + const vfloat rr; //!< squared radius of cylinder + + __forceinline CylinderN(const Vec3vf& p0, const Vec3vf& p1, const vfloat& r) + : p0(p0), p1(p1), rr(sqr(r)) {} + + __forceinline CylinderN(const Vec3vf& p0, const Vec3vf& p1, const vfloat& rr, bool) + : p0(p0), p1(p1), rr(rr) {} + + + __forceinline vbool intersect(const Vec3fa& org, const Vec3fa& dir, + BBox>& t_o, + vfloat& u0_o, Vec3vf& Ng0_o, + vfloat& u1_o, Vec3vf& Ng1_o) const + { + /* calculate quadratic equation to solve */ + const vfloat rl = rcp_length(p1-p0); + const Vec3vf P0 = p0, dP = (p1-p0)*rl; + const Vec3vf O = Vec3vf(org)-P0, dO = dir; + + const vfloat dOdO = dot(dO,dO); + const vfloat OdO = dot(dO,O); + const vfloat OO = dot(O,O); + const vfloat dOz = dot(dP,dO); + const vfloat Oz = dot(dP,O); + + const vfloat A = dOdO - sqr(dOz); + const vfloat B = 2.0f * (OdO - dOz*Oz); + const vfloat C = OO - sqr(Oz) - rr; + + /* we miss the cylinder if determinant is smaller than zero */ + const vfloat D = B*B - 4.0f*A*C; + vbool valid = D >= 0.0f; + if (none(valid)) { + t_o = BBox>(empty); + return valid; + } + + /* standard case for rays that are not parallel to the cylinder */ + const vfloat Q = sqrt(D); + const vfloat rcp_2A = rcp(2.0f*A); + const vfloat t0 = (-B-Q)*rcp_2A; + const vfloat t1 = (-B+Q)*rcp_2A; + + /* calculates u and Ng for near hit */ + { + u0_o = madd(t0,dOz,Oz)*rl; + const Vec3vf Pr = t0*Vec3vf(dir); + const Vec3vf Pl = madd(u0_o,p1-p0,p0); + Ng0_o = Pr-Pl; + } + + /* calculates u and Ng for far hit */ + { + u1_o = madd(t1,dOz,Oz)*rl; + const Vec3vf Pr = t1*Vec3vf(dir); + const Vec3vf Pl = madd(u1_o,p1-p0,p0); + Ng1_o = Pr-Pl; + } + + t_o.lower = select(valid, t0, vfloat(pos_inf)); + t_o.upper = select(valid, t1, vfloat(neg_inf)); + + /* special case for rays that are parallel to the cylinder */ + const vfloat eps = 16.0f*float(ulp)*max(abs(dOdO),abs(sqr(dOz))); + vbool validt = valid & (abs(A) < eps); + if (unlikely(any(validt))) + { + vbool inside = C <= 0.0f; + t_o.lower = select(validt,select(inside,vfloat(neg_inf),vfloat(pos_inf)),t_o.lower); + t_o.upper = select(validt,select(inside,vfloat(pos_inf),vfloat(neg_inf)),t_o.upper); + valid &= !validt | inside; + } + return valid; + } + + __forceinline vbool intersect(const Vec3fa& org_i, const Vec3fa& dir, BBox>& t_o) const + { + vfloat u0_o; Vec3vf Ng0_o; + vfloat u1_o; Vec3vf Ng1_o; + return intersect(org_i,dir,t_o,u0_o,Ng0_o,u1_o,Ng1_o); + } + }; + } +} + diff --git a/thirdparty/embree/kernels/geometry/disc_intersector.h b/thirdparty/embree/kernels/geometry/disc_intersector.h new file mode 100644 index 000000000000..e8305780e58e --- /dev/null +++ b/thirdparty/embree/kernels/geometry/disc_intersector.h @@ -0,0 +1,216 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "../common/scene_points.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + namespace isa + { + template + struct DiscIntersectorHitM + { + __forceinline DiscIntersectorHitM() {} + + __forceinline DiscIntersectorHitM(const vfloat& u, const vfloat& v, const vfloat& t, const Vec3vf& Ng) + : vu(u), vv(v), vt(t), vNg(Ng) + { + } + + __forceinline void finalize() {} + + __forceinline Vec2f uv(const size_t i) const + { + return Vec2f(vu[i], vv[i]); + } + __forceinline float t(const size_t i) const + { + return vt[i]; + } + __forceinline Vec3fa Ng(const size_t i) const + { + return Vec3fa(vNg.x[i], vNg.y[i], vNg.z[i]); + } + + public: + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + struct DiscIntersector1 + { + typedef CurvePrecalculations1 Precalculations; + + template + static __forceinline bool intersect( + const vbool& valid_i, + Ray& ray, + IntersectContext* context, + const Points* geom, + const Precalculations& pre, + const Vec4vf& v0i, + const Epilog& epilog) + { + vbool valid = valid_i; + + const Vec3vf ray_org(ray.org.x, ray.org.y, ray.org.z); + const Vec3vf ray_dir(ray.dir.x, ray.dir.y, ray.dir.z); + const vfloat rd2 = rcp(dot(ray_dir, ray_dir)); + + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec3vf center = v0.xyz(); + const vfloat radius = v0.w; + + const Vec3vf c0 = center - ray_org; + const vfloat projC0 = dot(c0, ray_dir) * rd2; + + valid &= (vfloat(ray.tnear()) <= projC0) & (projC0 <= vfloat(ray.tfar)); + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) + valid &= projC0 > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR) * radius * pre.depth_scale; // ignore self intersections + if (unlikely(none(valid))) + return false; + + const Vec3vf perp = c0 - projC0 * ray_dir; + const vfloat l2 = dot(perp, perp); + const vfloat r2 = radius * radius; + valid &= (l2 <= r2); + if (unlikely(none(valid))) + return false; + + DiscIntersectorHitM hit(zero, zero, projC0, -ray_dir); + return epilog(valid, hit); + } + + template + static __forceinline bool intersect(const vbool& valid_i, + Ray& ray, + IntersectContext* context, + const Points* geom, + const Precalculations& pre, + const Vec4vf& v0i, + const Vec3vf& normal, + const Epilog& epilog) + { + vbool valid = valid_i; + const Vec3vf ray_org(ray.org.x, ray.org.y, ray.org.z); + + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec3vf center = v0.xyz(); + const vfloat radius = v0.w; + + vfloat divisor = dot(Vec3vf((Vec3fa)ray.dir), normal); + const vbool parallel = divisor == vfloat(0.f); + valid &= !parallel; + divisor = select(parallel, 1.f, divisor); // prevent divide by zero + + vfloat t = dot(center - Vec3vf((Vec3fa)ray.org), Vec3vf(normal)) / divisor; + + valid &= (vfloat(ray.tnear()) <= t) & (t <= vfloat(ray.tfar)); + if (unlikely(none(valid))) + return false; + + Vec3vf intersection = Vec3vf((Vec3fa)ray.org) + Vec3vf((Vec3fa)ray.dir) * t; + vfloat dist2 = dot(intersection - center, intersection - center); + valid &= dist2 < radius * radius; + if (unlikely(none(valid))) + return false; + + DiscIntersectorHitM hit(zero, zero, t, normal); + return epilog(valid, hit); + } + }; + + template + struct DiscIntersectorK + { + typedef CurvePrecalculationsK Precalculations; + + template + static __forceinline bool intersect(const vbool& valid_i, + RayK& ray, + size_t k, + IntersectContext* context, + const Points* geom, + const Precalculations& pre, + const Vec4vf& v0i, + const Epilog& epilog) + { + vbool valid = valid_i; + + const Vec3vf ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const Vec3vf ray_dir(ray.dir.x[k], ray.dir.y[k], ray.dir.z[k]); + const vfloat rd2 = rcp(dot(ray_dir, ray_dir)); + + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec3vf center = v0.xyz(); + const vfloat radius = v0.w; + + const Vec3vf c0 = center - ray_org; + const vfloat projC0 = dot(c0, ray_dir) * rd2; + + valid &= (vfloat(ray.tnear()[k]) <= projC0) & (projC0 <= vfloat(ray.tfar[k])); + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) + valid &= projC0 > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR) * radius * pre.depth_scale[k]; // ignore self intersections + if (unlikely(none(valid))) + return false; + + const Vec3vf perp = c0 - projC0 * ray_dir; + const vfloat l2 = dot(perp, perp); + const vfloat r2 = radius * radius; + valid &= (l2 <= r2); + if (unlikely(none(valid))) + return false; + + DiscIntersectorHitM hit(zero, zero, projC0, -ray_dir); + return epilog(valid, hit); + } + + template + static __forceinline bool intersect(const vbool& valid_i, + RayK& ray, + size_t k, + IntersectContext* context, + const Points* geom, + const Precalculations& pre, + const Vec4vf& v0i, + const Vec3vf& normal, + const Epilog& epilog) + { + vbool valid = valid_i; + const Vec3vf ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const Vec3vf ray_dir(ray.dir.x[k], ray.dir.y[k], ray.dir.z[k]); + + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec3vf center = v0.xyz(); + const vfloat radius = v0.w; + + vfloat divisor = dot(Vec3vf(ray_dir), normal); + const vbool parallel = divisor == vfloat(0.f); + valid &= !parallel; + divisor = select(parallel, 1.f, divisor); // prevent divide by zero + + vfloat t = dot(center - Vec3vf(ray_org), Vec3vf(normal)) / divisor; + + valid &= (vfloat(ray.tnear()[k]) <= t) & (t <= vfloat(ray.tfar[k])); + if (unlikely(none(valid))) + return false; + + Vec3vf intersection = Vec3vf(ray_org) + Vec3vf(ray_dir) * t; + vfloat dist2 = dot(intersection - center, intersection - center); + valid &= dist2 < radius * radius; + if (unlikely(none(valid))) + return false; + + DiscIntersectorHitM hit(zero, zero, t, normal); + return epilog(valid, hit); + } + }; + } // namespace isa +} // namespace embree diff --git a/thirdparty/embree/kernels/geometry/disci_intersector.h b/thirdparty/embree/kernels/geometry/disci_intersector.h new file mode 100644 index 000000000000..e1dc3aa98ec9 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/disci_intersector.h @@ -0,0 +1,277 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "disc_intersector.h" +#include "intersector_epilog.h" +#include "pointi.h" + +namespace embree +{ + namespace isa + { + template + struct DiscMiIntersector1 + { + typedef PointMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, + RayHit& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom); + const vbool valid = Disc.template valid(); + DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, Intersect1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, + Ray& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom); + const vbool valid = Disc.template valid(); + return DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, Occluded1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct DiscMiMBIntersector1 + { + typedef PointMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, + RayHit& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom, ray.time()); + const vbool valid = Disc.template valid(); + DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, Intersect1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, + Ray& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom, ray.time()); + const vbool valid = Disc.template valid(); + return DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, Occluded1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct DiscMiIntersectorK + { + typedef PointMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect( + const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom); + const vbool valid = Disc.template valid(); + DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Intersect1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded( + const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom); + const vbool valid = Disc.template valid(); + return DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Occluded1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct DiscMiMBIntersectorK + { + typedef PointMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect( + const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom, ray.time()[k]); + const vbool valid = Disc.template valid(); + DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Intersect1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded( + const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Disc.gather(v0, geom, ray.time()[k]); + const vbool valid = Disc.template valid(); + return DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, Occluded1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct OrientedDiscMiIntersector1 + { + typedef PointMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, + RayHit& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom); + const vbool valid = Disc.template valid(); + DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, n0, Intersect1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, + Ray& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom); + const vbool valid = Disc.template valid(); + return DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, n0, Occluded1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct OrientedDiscMiMBIntersector1 + { + typedef PointMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, + RayHit& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom, ray.time()); + const vbool valid = Disc.template valid(); + DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, n0, Intersect1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, + Ray& ray, + IntersectContext* context, + const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom, ray.time()); + const vbool valid = Disc.template valid(); + return DiscIntersector1::intersect( + valid, ray, context, geom, pre, v0, n0, Occluded1EpilogM(ray, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct OrientedDiscMiIntersectorK + { + typedef PointMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect( + const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom); + const vbool valid = Disc.template valid(); + DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, n0, + Intersect1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded( + const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom); + const vbool valid = Disc.template valid(); + return DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, n0, + Occluded1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + }; + + template + struct OrientedDiscMiMBIntersectorK + { + typedef PointMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect( + const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom, ray.time()[k]); + const vbool valid = Disc.template valid(); + DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, n0, + Intersect1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + + static __forceinline bool occluded( + const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& Disc) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(Disc.geomID()); + Vec4vf v0; Vec3vf n0; + Disc.gather(v0, n0, geom, ray.time()[k]); + const vbool valid = Disc.template valid(); + return DiscIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, n0, + Occluded1KEpilogM(ray, k, context, Disc.geomID(), Disc.primID())); + } + }; + } // namespace isa +} // namespace embree diff --git a/thirdparty/embree/kernels/geometry/filter.h b/thirdparty/embree/kernels/geometry/filter.h new file mode 100644 index 000000000000..4cdf7a395a49 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/filter.h @@ -0,0 +1,204 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/geometry.h" +#include "../common/ray.h" +#include "../common/hit.h" +#include "../common/context.h" + +namespace embree +{ + namespace isa + { + __forceinline bool runIntersectionFilter1Helper(RTCFilterFunctionNArguments* args, const Geometry* const geometry, IntersectContext* context) + { + if (geometry->intersectionFilterN) + { + assert(context->scene->hasGeometryFilterFunction()); + geometry->intersectionFilterN(args); + + if (args->valid[0] == 0) + return false; + } + + if (context->user->filter) { + assert(context->scene->hasContextFilterFunction()); + context->user->filter(args); + + if (args->valid[0] == 0) + return false; + } + + copyHitToRay(*(RayHit*)args->ray,*(Hit*)args->hit); + return true; + } + + __forceinline bool runIntersectionFilter1(const Geometry* const geometry, RayHit& ray, IntersectContext* context, Hit& hit) + { + RTCFilterFunctionNArguments args; + int mask = -1; + args.valid = &mask; + args.geometryUserPtr = geometry->userPtr; + args.context = context->user; + args.ray = (RTCRayN*)&ray; + args.hit = (RTCHitN*)&hit; + args.N = 1; + return runIntersectionFilter1Helper(&args,geometry,context); + } + + __forceinline void reportIntersection1(IntersectFunctionNArguments* args, const RTCFilterFunctionNArguments* filter_args) + { +#if defined(EMBREE_FILTER_FUNCTION) + IntersectContext* MAYBE_UNUSED context = args->internal_context; + const Geometry* const geometry = args->geometry; + if (geometry->intersectionFilterN) { + assert(context->scene->hasGeometryFilterFunction()); + geometry->intersectionFilterN(filter_args); + } + + //if (args->valid[0] == 0) + // return; + + if (context->user->filter) { + assert(context->scene->hasContextFilterFunction()); + context->user->filter(filter_args); + } +#endif + } + + __forceinline bool runOcclusionFilter1Helper(RTCFilterFunctionNArguments* args, const Geometry* const geometry, IntersectContext* context) + { + if (geometry->occlusionFilterN) + { + assert(context->scene->hasGeometryFilterFunction()); + geometry->occlusionFilterN(args); + + if (args->valid[0] == 0) + return false; + } + + if (context->user->filter) { + assert(context->scene->hasContextFilterFunction()); + context->user->filter(args); + + if (args->valid[0] == 0) + return false; + } + return true; + } + + __forceinline bool runOcclusionFilter1(const Geometry* const geometry, Ray& ray, IntersectContext* context, Hit& hit) + { + RTCFilterFunctionNArguments args; + int mask = -1; + args.valid = &mask; + args.geometryUserPtr = geometry->userPtr; + args.context = context->user; + args.ray = (RTCRayN*)&ray; + args.hit = (RTCHitN*)&hit; + args.N = 1; + return runOcclusionFilter1Helper(&args,geometry,context); + } + + __forceinline void reportOcclusion1(OccludedFunctionNArguments* args, const RTCFilterFunctionNArguments* filter_args) + { +#if defined(EMBREE_FILTER_FUNCTION) + IntersectContext* MAYBE_UNUSED context = args->internal_context; + const Geometry* const geometry = args->geometry; + if (geometry->occlusionFilterN) { + assert(context->scene->hasGeometryFilterFunction()); + geometry->occlusionFilterN(filter_args); + } + + //if (args->valid[0] == 0) + // return false; + + if (context->user->filter) { + assert(context->scene->hasContextFilterFunction()); + context->user->filter(filter_args); + } +#endif + } + + template + __forceinline vbool runIntersectionFilterHelper(RTCFilterFunctionNArguments* args, const Geometry* const geometry, IntersectContext* context) + { + vint* mask = (vint*) args->valid; + if (geometry->intersectionFilterN) + { + assert(context->scene->hasGeometryFilterFunction()); + geometry->intersectionFilterN(args); + } + + vbool valid_o = *mask != vint(zero); + if (none(valid_o)) return valid_o; + + if (context->user->filter) { + assert(context->scene->hasContextFilterFunction()); + context->user->filter(args); + } + + valid_o = *mask != vint(zero); + if (none(valid_o)) return valid_o; + + copyHitToRay(valid_o,*(RayHitK*)args->ray,*(HitK*)args->hit); + return valid_o; + } + + template + __forceinline vbool runIntersectionFilter(const vbool& valid, const Geometry* const geometry, RayHitK& ray, IntersectContext* context, HitK& hit) + { + RTCFilterFunctionNArguments args; + vint mask = valid.mask32(); + args.valid = (int*)&mask; + args.geometryUserPtr = geometry->userPtr; + args.context = context->user; + args.ray = (RTCRayN*)&ray; + args.hit = (RTCHitN*)&hit; + args.N = K; + return runIntersectionFilterHelper(&args,geometry,context); + } + + template + __forceinline vbool runOcclusionFilterHelper(RTCFilterFunctionNArguments* args, const Geometry* const geometry, IntersectContext* context) + { + vint* mask = (vint*) args->valid; + if (geometry->occlusionFilterN) + { + assert(context->scene->hasGeometryFilterFunction()); + geometry->occlusionFilterN(args); + } + + vbool valid_o = *mask != vint(zero); + + if (none(valid_o)) return valid_o; + + if (context->user->filter) { + assert(context->scene->hasContextFilterFunction()); + context->user->filter(args); + } + + valid_o = *mask != vint(zero); + + RayK* ray = (RayK*) args->ray; + ray->tfar = select(valid_o, vfloat(neg_inf), ray->tfar); + return valid_o; + } + + template + __forceinline vbool runOcclusionFilter(const vbool& valid, const Geometry* const geometry, RayK& ray, IntersectContext* context, HitK& hit) + { + RTCFilterFunctionNArguments args; + vint mask = valid.mask32(); + args.valid = (int*)&mask; + args.geometryUserPtr = geometry->userPtr; + args.context = context->user; + args.ray = (RTCRayN*)&ray; + args.hit = (RTCHitN*)&hit; + args.N = K; + return runOcclusionFilterHelper(&args,geometry,context); + } + } +} diff --git a/thirdparty/embree/kernels/geometry/grid_intersector.h b/thirdparty/embree/kernels/geometry/grid_intersector.h new file mode 100644 index 000000000000..46a0af08279c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/grid_intersector.h @@ -0,0 +1,99 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "grid_soa.h" +#include "grid_soa_intersector1.h" +#include "grid_soa_intersector_packet.h" +#include "../common/ray.h" + +namespace embree +{ + namespace isa + { + template + class SubdivPatch1Precalculations : public T + { + public: + __forceinline SubdivPatch1Precalculations (const Ray& ray, const void* ptr) + : T(ray,ptr) {} + }; + + template + class SubdivPatch1PrecalculationsK : public T + { + public: + __forceinline SubdivPatch1PrecalculationsK (const vbool& valid, RayK& ray) + : T(valid,ray) {} + }; + + class Grid1Intersector1 + { + public: + typedef GridSOA Primitive; + typedef Grid1Precalculations Precalculations; + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) + { + GridSOAIntersector1::intersect(pre,ray,context,prim,lazy_node); + } + static __forceinline void intersect(Precalculations& pre, RayHit& ray, IntersectContext* context, size_t ty0, const Primitive* prim, size_t ty, size_t& lazy_node) { + intersect(pre,ray,context,prim,ty,lazy_node); + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline bool occluded(Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) + { + GridSOAIntersector1::occluded(pre,ray,context,prim,lazy_node); + } + static __forceinline bool occluded(Precalculations& pre, Ray& ray, IntersectContext* context, size_t ty0, const Primitive* prim, size_t ty, size_t& lazy_node) { + return occluded(pre,ray,context,prim,ty,lazy_node); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) { + assert(false && "not implemented"); + return false; + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, size_t ty0, const Primitive* prim, size_t ty, size_t& lazy_node) { + assert(false && "not implemented"); + return false; + } + }; + + template + struct GridIntersectorK + { + typedef GridSOA Primitive; + typedef SubdivPatch1PrecalculationsK::Precalculations> Precalculations; + + + static __forceinline void intersect(const vbool& valid, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) + { + GridSOAIntersectorK::intersect(valid,pre,ray,context,prim,lazy_node); + } + + static __forceinline vbool occluded(const vbool& valid, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) + { + GridSOAIntersectorK::occluded(valid,pre,ray,context,prim,lazy_node); + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) + { + GridSOAIntersectorK::intersect(pre,ray,k,context,prim,lazy_node); + } + + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t ty, size_t& lazy_node) + { + GridSOAIntersectorK::occluded(pre,ray,k,context,prim,lazy_node); + } + }; + + typedef Grid1IntersectorK<4> SubdivPatch1Intersector4; + typedef Grid1IntersectorK<8> SubdivPatch1Intersector8; + typedef Grid1IntersectorK<16> SubdivPatch1Intersector16; + + } +} diff --git a/thirdparty/embree/kernels/geometry/grid_soa.h b/thirdparty/embree/kernels/geometry/grid_soa.h new file mode 100644 index 000000000000..02edbbed5e1d --- /dev/null +++ b/thirdparty/embree/kernels/geometry/grid_soa.h @@ -0,0 +1,275 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "../common/scene_subdiv_mesh.h" +#include "../bvh/bvh.h" +#include "../subdiv/tessellation.h" +#include "../subdiv/tessellation_cache.h" +#include "subdivpatch1.h" + +namespace embree +{ + namespace isa + { + class GridSOA + { + public: + + /*! GridSOA constructor */ + GridSOA(const SubdivPatch1Base* patches, const unsigned time_steps, + const unsigned x0, const unsigned x1, const unsigned y0, const unsigned y1, const unsigned swidth, const unsigned sheight, + const SubdivMesh* const geom, const size_t totalBvhBytes, const size_t gridBytes, BBox3fa* bounds_o = nullptr); + + /*! Subgrid creation */ + template + static GridSOA* create(const SubdivPatch1Base* patches, const unsigned time_steps, + unsigned x0, unsigned x1, unsigned y0, unsigned y1, + const Scene* scene, Allocator& alloc, BBox3fa* bounds_o = nullptr) + { + const unsigned width = x1-x0+1; + const unsigned height = y1-y0+1; + const GridRange range(0,width-1,0,height-1); + size_t bvhBytes = 0; + if (time_steps == 1) + bvhBytes = getBVHBytes(range,sizeof(BVH4::AABBNode),0); + else { + bvhBytes = (time_steps-1)*getBVHBytes(range,sizeof(BVH4::AABBNodeMB),0); + bvhBytes += getTemporalBVHBytes(make_range(0,int(time_steps-1)),sizeof(BVH4::AABBNodeMB4D)); + } + const size_t gridBytes = 4*size_t(width)*size_t(height)*sizeof(float); + size_t rootBytes = time_steps*sizeof(BVH4::NodeRef); +#if !defined(__X86_64__) + rootBytes += 4; // We read 2 elements behind the grid. As we store at least 8 root bytes after the grid we are fine in 64 bit mode. But in 32 bit mode we have to do additional padding. +#endif + void* data = alloc(offsetof(GridSOA,data)+bvhBytes+time_steps*gridBytes+rootBytes); + assert(data); + return new (data) GridSOA(patches,time_steps,x0,x1,y0,y1,patches->grid_u_res,patches->grid_v_res,scene->get(patches->geomID()),bvhBytes,gridBytes,bounds_o); + } + + /*! Grid creation */ + template + static GridSOA* create(const SubdivPatch1Base* const patches, const unsigned time_steps, + const Scene* scene, const Allocator& alloc, BBox3fa* bounds_o = nullptr) + { + return create(patches,time_steps,0,patches->grid_u_res-1,0,patches->grid_v_res-1,scene,alloc,bounds_o); + } + + /*! returns reference to root */ + __forceinline BVH4::NodeRef& root(size_t t = 0) { return (BVH4::NodeRef&)data[rootOffset + t*sizeof(BVH4::NodeRef)]; } + __forceinline const BVH4::NodeRef& root(size_t t = 0) const { return (BVH4::NodeRef&)data[rootOffset + t*sizeof(BVH4::NodeRef)]; } + + /*! returns pointer to BVH array */ + __forceinline char* bvhData() { return &data[0]; } + __forceinline const char* bvhData() const { return &data[0]; } + + /*! returns pointer to Grid array */ + __forceinline float* gridData(size_t t = 0) { return (float*) &data[gridOffset + t*gridBytes]; } + __forceinline const float* gridData(size_t t = 0) const { return (float*) &data[gridOffset + t*gridBytes]; } + + __forceinline void* encodeLeaf(size_t u, size_t v) { + return (void*) (16*(v * width + u + 1)); // +1 to not create empty leaf + } + __forceinline float* decodeLeaf(size_t t, const void* ptr) { + return gridData(t) + (((size_t) (ptr) >> 4) - 1); + } + + /*! returns the size of the BVH over the grid in bytes */ + static size_t getBVHBytes(const GridRange& range, const size_t nodeBytes, const size_t leafBytes); + + /*! returns the size of the temporal BVH over the time range BVHs */ + static size_t getTemporalBVHBytes(const range time_range, const size_t nodeBytes); + + /*! calculates bounding box of grid range */ + __forceinline BBox3fa calculateBounds(size_t time, const GridRange& range) const + { + const float* const grid_array = gridData(time); + const float* const grid_x_array = grid_array + 0 * dim_offset; + const float* const grid_y_array = grid_array + 1 * dim_offset; + const float* const grid_z_array = grid_array + 2 * dim_offset; + + /* compute the bounds just for the range! */ + BBox3fa bounds( empty ); + for (unsigned v = range.v_start; v<=range.v_end; v++) + { + for (unsigned u = range.u_start; u<=range.u_end; u++) + { + const float x = grid_x_array[ v * width + u]; + const float y = grid_y_array[ v * width + u]; + const float z = grid_z_array[ v * width + u]; + bounds.extend( Vec3fa(x,y,z) ); + } + } + assert(is_finite(bounds)); + return bounds; + } + + /*! Evaluates grid over patch and builds BVH4 tree over the grid. */ + std::pair buildBVH(BBox3fa* bounds_o); + + /*! Create BVH4 tree over grid. */ + std::pair buildBVH(const GridRange& range, size_t& allocator); + + /*! Evaluates grid over patch and builds MSMBlur BVH4 tree over the grid. */ + std::pair buildMSMBlurBVH(const range time_range, BBox3fa* bounds_o); + + /*! Create MBlur BVH4 tree over grid. */ + std::pair buildMBlurBVH(size_t time, const GridRange& range, size_t& allocator); + + /*! Create MSMBlur BVH4 tree over grid. */ + std::pair buildMSMBlurBVH(const range time_range, size_t& allocator, BBox3fa* bounds_o); + + template + struct MapUV + { + typedef typename Loader::vfloat vfloat; + const float* const grid_uv; + size_t line_offset; + size_t lines; + + __forceinline MapUV(const float* const grid_uv, size_t line_offset, const size_t lines) + : grid_uv(grid_uv), line_offset(line_offset), lines(lines) {} + + __forceinline void operator() (vfloat& u, vfloat& v) const { + const Vec3 tri_v012_uv = Loader::gather(grid_uv,line_offset,lines); + const Vec2 uv0 = GridSOA::decodeUV(tri_v012_uv[0]); + const Vec2 uv1 = GridSOA::decodeUV(tri_v012_uv[1]); + const Vec2 uv2 = GridSOA::decodeUV(tri_v012_uv[2]); + const Vec2 uv = u * uv1 + v * uv2 + (1.0f-u-v) * uv0; + u = uv[0];v = uv[1]; + } + }; + + struct Gather2x3 + { + enum { M = 4 }; + typedef vbool4 vbool; + typedef vint4 vint; + typedef vfloat4 vfloat; + + static __forceinline const Vec3vf4 gather(const float* const grid, const size_t line_offset, const size_t lines) + { + vfloat4 r0 = vfloat4::loadu(grid + 0*line_offset); + vfloat4 r1 = vfloat4::loadu(grid + 1*line_offset); // this accesses 2 elements too much in case of 2x2 grid, but this is ok as we ensure enough padding after the grid + if (unlikely(line_offset == 2)) + { + r0 = shuffle<0,1,1,1>(r0); + r1 = shuffle<0,1,1,1>(r1); + } + return Vec3vf4(unpacklo(r0,r1), // r00, r10, r01, r11 + shuffle<1,1,2,2>(r0), // r01, r01, r02, r02 + shuffle<0,1,1,2>(r1)); // r10, r11, r11, r12 + } + + static __forceinline void gather(const float* const grid_x, + const float* const grid_y, + const float* const grid_z, + const size_t line_offset, + const size_t lines, + Vec3vf4& v0_o, + Vec3vf4& v1_o, + Vec3vf4& v2_o) + { + const Vec3vf4 tri_v012_x = gather(grid_x,line_offset,lines); + const Vec3vf4 tri_v012_y = gather(grid_y,line_offset,lines); + const Vec3vf4 tri_v012_z = gather(grid_z,line_offset,lines); + v0_o = Vec3vf4(tri_v012_x[0],tri_v012_y[0],tri_v012_z[0]); + v1_o = Vec3vf4(tri_v012_x[1],tri_v012_y[1],tri_v012_z[1]); + v2_o = Vec3vf4(tri_v012_x[2],tri_v012_y[2],tri_v012_z[2]); + } + }; + +#if defined (__AVX__) + struct Gather3x3 + { + enum { M = 8 }; + typedef vbool8 vbool; + typedef vint8 vint; + typedef vfloat8 vfloat; + + static __forceinline const Vec3vf8 gather(const float* const grid, const size_t line_offset, const size_t lines) + { + vfloat4 ra = vfloat4::loadu(grid + 0*line_offset); + vfloat4 rb = vfloat4::loadu(grid + 1*line_offset); // this accesses 2 elements too much in case of 2x2 grid, but this is ok as we ensure enough padding after the grid + vfloat4 rc; + if (likely(lines > 2)) + rc = vfloat4::loadu(grid + 2*line_offset); + else + rc = rb; + + if (unlikely(line_offset == 2)) + { + ra = shuffle<0,1,1,1>(ra); + rb = shuffle<0,1,1,1>(rb); + rc = shuffle<0,1,1,1>(rc); + } + + const vfloat8 r0 = vfloat8(ra,rb); + const vfloat8 r1 = vfloat8(rb,rc); + return Vec3vf8(unpacklo(r0,r1), // r00, r10, r01, r11, r10, r20, r11, r21 + shuffle<1,1,2,2>(r0), // r01, r01, r02, r02, r11, r11, r12, r12 + shuffle<0,1,1,2>(r1)); // r10, r11, r11, r12, r20, r21, r21, r22 + } + + static __forceinline void gather(const float* const grid_x, + const float* const grid_y, + const float* const grid_z, + const size_t line_offset, + const size_t lines, + Vec3vf8& v0_o, + Vec3vf8& v1_o, + Vec3vf8& v2_o) + { + const Vec3vf8 tri_v012_x = gather(grid_x,line_offset,lines); + const Vec3vf8 tri_v012_y = gather(grid_y,line_offset,lines); + const Vec3vf8 tri_v012_z = gather(grid_z,line_offset,lines); + v0_o = Vec3vf8(tri_v012_x[0],tri_v012_y[0],tri_v012_z[0]); + v1_o = Vec3vf8(tri_v012_x[1],tri_v012_y[1],tri_v012_z[1]); + v2_o = Vec3vf8(tri_v012_x[2],tri_v012_y[2],tri_v012_z[2]); + } + }; +#endif + + template + static __forceinline Vec2 decodeUV(const vfloat& uv) + { + typedef typename vfloat::Int vint; + const vint iu = asInt(uv) & 0xffff; + const vint iv = srl(asInt(uv),16); + const vfloat u = (vfloat)iu * vfloat(8.0f/0x10000); + const vfloat v = (vfloat)iv * vfloat(8.0f/0x10000); + return Vec2(u,v); + } + + __forceinline unsigned int geomID() const { + return _geomID; + } + + __forceinline unsigned int primID() const { + return _primID; + } + + public: + BVH4::NodeRef troot; +#if !defined(__X86_64__) + unsigned align1; +#endif + unsigned time_steps; + unsigned width; + + unsigned height; + unsigned dim_offset; + unsigned _geomID; + unsigned _primID; + + unsigned align2; + unsigned gridOffset; + unsigned gridBytes; + unsigned rootOffset; + + char data[1]; //!< after the struct we first store the BVH, then the grid, and finally the roots + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/grid_soa_intersector1.h b/thirdparty/embree/kernels/geometry/grid_soa_intersector1.h new file mode 100644 index 000000000000..2ed922a5aeb3 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/grid_soa_intersector1.h @@ -0,0 +1,207 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "grid_soa.h" +#include "../common/ray.h" +#include "triangle_intersector_pluecker.h" + +namespace embree +{ + namespace isa + { + class GridSOAIntersector1 + { + public: + typedef void Primitive; + + class Precalculations + { + public: + __forceinline Precalculations (const Ray& ray, const void* ptr) + : grid(nullptr) {} + + public: + GridSOA* grid; + int itime; + float ftime; + }; + + template + static __forceinline void intersect(RayHit& ray, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t dim_offset = pre.grid->dim_offset; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + Vec3 v0, v1, v2; + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); + GridSOA::MapUV mapUV(grid_uv,line_offset,lines); + PlueckerIntersector1 intersector(ray,nullptr); + intersector.intersect(ray,v0,v1,v2,mapUV,Intersect1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); + }; + + template + static __forceinline bool occluded(Ray& ray, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t dim_offset = pre.grid->dim_offset; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + Vec3 v0, v1, v2; + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); + + GridSOA::MapUV mapUV(grid_uv,line_offset,lines); + PlueckerIntersector1 intersector(ray,nullptr); + return intersector.intersect(ray,v0,v1,v2,mapUV,Occluded1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); + } + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(0,prim); + +#if defined(__AVX__) + intersect( ray, context, grid_x, line_offset, lines, pre); +#else + intersect(ray, context, grid_x , line_offset, lines, pre); + if (likely(lines > 2)) + intersect(ray, context, grid_x+line_offset, line_offset, lines, pre); +#endif + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline bool occluded(Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(0,prim); + +#if defined(__AVX__) + return occluded( ray, context, grid_x, line_offset, lines, pre); +#else + if (occluded(ray, context, grid_x , line_offset, lines, pre)) return true; + if (likely(lines > 2)) + if (occluded(ray, context, grid_x+line_offset, line_offset, lines, pre)) return true; +#endif + return false; + } + }; + + class GridSOAMBIntersector1 + { + public: + typedef void Primitive; + typedef GridSOAIntersector1::Precalculations Precalculations; + + template + static __forceinline void intersect(RayHit& ray, const float ftime, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t dim_offset = pre.grid->dim_offset; + const size_t grid_offset = pre.grid->gridBytes >> 2; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + Vec3 a0, a1, a2; + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,a0,a1,a2); + + Vec3 b0, b1, b2; + Loader::gather(grid_x+grid_offset,grid_y+grid_offset,grid_z+grid_offset,line_offset,lines,b0,b1,b2); + + Vec3 v0 = lerp(a0,b0,vfloat(ftime)); + Vec3 v1 = lerp(a1,b1,vfloat(ftime)); + Vec3 v2 = lerp(a2,b2,vfloat(ftime)); + + GridSOA::MapUV mapUV(grid_uv,line_offset,lines); + PlueckerIntersector1 intersector(ray,nullptr); + intersector.intersect(ray,v0,v1,v2,mapUV,Intersect1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); + }; + + template + static __forceinline bool occluded(Ray& ray, const float ftime, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t dim_offset = pre.grid->dim_offset; + const size_t grid_offset = pre.grid->gridBytes >> 2; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + Vec3 a0, a1, a2; + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,a0,a1,a2); + + Vec3 b0, b1, b2; + Loader::gather(grid_x+grid_offset,grid_y+grid_offset,grid_z+grid_offset,line_offset,lines,b0,b1,b2); + + Vec3 v0 = lerp(a0,b0,vfloat(ftime)); + Vec3 v1 = lerp(a1,b1,vfloat(ftime)); + Vec3 v2 = lerp(a2,b2,vfloat(ftime)); + + GridSOA::MapUV mapUV(grid_uv,line_offset,lines); + PlueckerIntersector1 intersector(ray,nullptr); + return intersector.intersect(ray,v0,v1,v2,mapUV,Occluded1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); + } + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(pre.itime,prim); + +#if defined(__AVX__) + intersect( ray, pre.ftime, context, grid_x, line_offset, lines, pre); +#else + intersect(ray, pre.ftime, context, grid_x, line_offset, lines, pre); + if (likely(lines > 2)) + intersect(ray, pre.ftime, context, grid_x+line_offset, line_offset, lines, pre); +#endif + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline bool occluded(Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(pre.itime,prim); + +#if defined(__AVX__) + return occluded( ray, pre.ftime, context, grid_x, line_offset, lines, pre); +#else + if (occluded(ray, pre.ftime, context, grid_x , line_offset, lines, pre)) return true; + if (likely(lines > 2)) + if (occluded(ray, pre.ftime, context, grid_x+line_offset, line_offset, lines, pre)) return true; +#endif + return false; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/grid_soa_intersector_packet.h b/thirdparty/embree/kernels/geometry/grid_soa_intersector_packet.h new file mode 100644 index 000000000000..41d66e1e285b --- /dev/null +++ b/thirdparty/embree/kernels/geometry/grid_soa_intersector_packet.h @@ -0,0 +1,445 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "grid_soa.h" +#include "../common/ray.h" +#include "triangle_intersector_pluecker.h" + +namespace embree +{ + namespace isa + { + template + struct MapUV0 + { + const float* const grid_uv; + size_t ofs00, ofs01, ofs10, ofs11; + + __forceinline MapUV0(const float* const grid_uv, size_t ofs00, size_t ofs01, size_t ofs10, size_t ofs11) + : grid_uv(grid_uv), ofs00(ofs00), ofs01(ofs01), ofs10(ofs10), ofs11(ofs11) {} + + __forceinline void operator() (vfloat& u, vfloat& v) const { + const vfloat uv00(grid_uv[ofs00]); + const vfloat uv01(grid_uv[ofs01]); + const vfloat uv10(grid_uv[ofs10]); + const vfloat uv11(grid_uv[ofs11]); + const Vec2vf uv0 = GridSOA::decodeUV(uv00); + const Vec2vf uv1 = GridSOA::decodeUV(uv01); + const Vec2vf uv2 = GridSOA::decodeUV(uv10); + const Vec2vf uv = madd(u,uv1,madd(v,uv2,(1.0f-u-v)*uv0)); + u = uv[0]; v = uv[1]; + } + }; + + template + struct MapUV1 + { + const float* const grid_uv; + size_t ofs00, ofs01, ofs10, ofs11; + + __forceinline MapUV1(const float* const grid_uv, size_t ofs00, size_t ofs01, size_t ofs10, size_t ofs11) + : grid_uv(grid_uv), ofs00(ofs00), ofs01(ofs01), ofs10(ofs10), ofs11(ofs11) {} + + __forceinline void operator() (vfloat& u, vfloat& v) const { + const vfloat uv00(grid_uv[ofs00]); + const vfloat uv01(grid_uv[ofs01]); + const vfloat uv10(grid_uv[ofs10]); + const vfloat uv11(grid_uv[ofs11]); + const Vec2vf uv0 = GridSOA::decodeUV(uv10); + const Vec2vf uv1 = GridSOA::decodeUV(uv01); + const Vec2vf uv2 = GridSOA::decodeUV(uv11); + const Vec2vf uv = madd(u,uv1,madd(v,uv2,(1.0f-u-v)*uv0)); + u = uv[0]; v = uv[1]; + } + }; + + template + class GridSOAIntersectorK + { + public: + typedef void Primitive; + + class Precalculations + { +#if defined(__AVX__) + static const int M = 8; +#else + static const int M = 4; +#endif + + public: + __forceinline Precalculations (const vbool& valid, const RayK& ray) + : grid(nullptr), intersector(valid,ray) {} + + public: + GridSOA* grid; + PlueckerIntersectorK intersector; // FIXME: use quad intersector + }; + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t dim_offset = pre.grid->dim_offset; + const size_t line_offset = pre.grid->width; + const float* const grid_x = pre.grid->decodeLeaf(0,prim); + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + const size_t max_x = pre.grid->width == 2 ? 1 : 2; + const size_t max_y = pre.grid->height == 2 ? 1 : 2; + for (size_t y=0; y p00(grid_x[ofs00],grid_y[ofs00],grid_z[ofs00]); + const Vec3vf p01(grid_x[ofs01],grid_y[ofs01],grid_z[ofs01]); + const Vec3vf p10(grid_x[ofs10],grid_y[ofs10],grid_z[ofs10]); + const Vec3vf p11(grid_x[ofs11],grid_y[ofs11],grid_z[ofs11]); + + pre.intersector.intersectK(valid_i,ray,p00,p01,p10,MapUV0(grid_uv,ofs00,ofs01,ofs10,ofs11),IntersectKEpilogMU<1,K,true>(ray,context,pre.grid->geomID(),pre.grid->primID())); + pre.intersector.intersectK(valid_i,ray,p10,p01,p11,MapUV1(grid_uv,ofs00,ofs01,ofs10,ofs11),IntersectKEpilogMU<1,K,true>(ray,context,pre.grid->geomID(),pre.grid->primID())); + } + } + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t dim_offset = pre.grid->dim_offset; + const size_t line_offset = pre.grid->width; + const float* const grid_x = pre.grid->decodeLeaf(0,prim); + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + vbool valid = valid_i; + const size_t max_x = pre.grid->width == 2 ? 1 : 2; + const size_t max_y = pre.grid->height == 2 ? 1 : 2; + for (size_t y=0; y p00(grid_x[ofs00],grid_y[ofs00],grid_z[ofs00]); + const Vec3vf p01(grid_x[ofs01],grid_y[ofs01],grid_z[ofs01]); + const Vec3vf p10(grid_x[ofs10],grid_y[ofs10],grid_z[ofs10]); + const Vec3vf p11(grid_x[ofs11],grid_y[ofs11],grid_z[ofs11]); + + pre.intersector.intersectK(valid,ray,p00,p01,p10,MapUV0(grid_uv,ofs00,ofs01,ofs10,ofs11),OccludedKEpilogMU<1,K,true>(valid,ray,context,pre.grid->geomID(),pre.grid->primID())); + if (none(valid)) break; + pre.intersector.intersectK(valid,ray,p10,p01,p11,MapUV1(grid_uv,ofs00,ofs01,ofs10,ofs11),OccludedKEpilogMU<1,K,true>(valid,ray,context,pre.grid->geomID(),pre.grid->primID())); + if (none(valid)) break; + } + } + return !valid; + } + + template + static __forceinline void intersect(RayHitK& ray, size_t k, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t dim_offset = pre.grid->dim_offset; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + Vec3 v0, v1, v2; Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); + pre.intersector.intersect(ray,k,v0,v1,v2,GridSOA::MapUV(grid_uv,line_offset,lines),Intersect1KEpilogMU(ray,k,context,pre.grid->geomID(),pre.grid->primID())); + }; + + template + static __forceinline bool occluded(RayK& ray, size_t k, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t dim_offset = pre.grid->dim_offset; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + Vec3 v0, v1, v2; Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); + return pre.intersector.intersect(ray,k,v0,v1,v2,GridSOA::MapUV(grid_uv,line_offset,lines),Occluded1KEpilogMU(ray,k,context,pre.grid->geomID(),pre.grid->primID())); + } + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(0,prim); +#if defined(__AVX__) + intersect( ray, k, context, grid_x, line_offset, lines, pre); +#else + intersect(ray, k, context, grid_x , line_offset, lines, pre); + if (likely(lines > 2)) + intersect(ray, k, context, grid_x+line_offset, line_offset, lines, pre); +#endif + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(0,prim); + +#if defined(__AVX__) + return occluded( ray, k, context, grid_x, line_offset, lines, pre); +#else + if (occluded(ray, k, context, grid_x , line_offset, lines, pre)) return true; + if (likely(lines > 2)) + if (occluded(ray, k, context, grid_x+line_offset, line_offset, lines, pre)) return true; +#endif + return false; + } + }; + + template + class GridSOAMBIntersectorK + { + public: + typedef void Primitive; + typedef typename GridSOAIntersectorK::Precalculations Precalculations; + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + vfloat vftime; + vint vitime = getTimeSegment(ray.time(), vfloat((float)(pre.grid->time_steps-1)), vftime); + + vbool valid1 = valid_i; + while (any(valid1)) { + const size_t j = bsf(movemask(valid1)); + const int itime = vitime[j]; + const vbool valid2 = valid1 & (itime == vitime); + valid1 = valid1 & !valid2; + intersect(valid2,pre,ray,vftime,itime,context,prim,lazy_node); + } + } + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, const vfloat& ftime, int itime, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t grid_offset = pre.grid->gridBytes >> 2; + const size_t dim_offset = pre.grid->dim_offset; + const size_t line_offset = pre.grid->width; + const float* const grid_x = pre.grid->decodeLeaf(itime,prim); + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + const size_t max_x = pre.grid->width == 2 ? 1 : 2; + const size_t max_y = pre.grid->height == 2 ? 1 : 2; + for (size_t y=0; y a00(grid_x[ofs00],grid_y[ofs00],grid_z[ofs00]); + const Vec3vf a01(grid_x[ofs01],grid_y[ofs01],grid_z[ofs01]); + const Vec3vf a10(grid_x[ofs10],grid_y[ofs10],grid_z[ofs10]); + const Vec3vf a11(grid_x[ofs11],grid_y[ofs11],grid_z[ofs11]); + ofs00 += grid_offset; + ofs01 += grid_offset; + ofs10 += grid_offset; + ofs11 += grid_offset; + const Vec3vf b00(grid_x[ofs00],grid_y[ofs00],grid_z[ofs00]); + const Vec3vf b01(grid_x[ofs01],grid_y[ofs01],grid_z[ofs01]); + const Vec3vf b10(grid_x[ofs10],grid_y[ofs10],grid_z[ofs10]); + const Vec3vf b11(grid_x[ofs11],grid_y[ofs11],grid_z[ofs11]); + const Vec3vf p00 = lerp(a00,b00,ftime); + const Vec3vf p01 = lerp(a01,b01,ftime); + const Vec3vf p10 = lerp(a10,b10,ftime); + const Vec3vf p11 = lerp(a11,b11,ftime); + + pre.intersector.intersectK(valid_i,ray,p00,p01,p10,MapUV0(grid_uv,ofs00,ofs01,ofs10,ofs11),IntersectKEpilogMU<1,K,true>(ray,context,pre.grid->geomID(),pre.grid->primID())); + pre.intersector.intersectK(valid_i,ray,p10,p01,p11,MapUV1(grid_uv,ofs00,ofs01,ofs10,ofs11),IntersectKEpilogMU<1,K,true>(ray,context,pre.grid->geomID(),pre.grid->primID())); + } + } + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + vfloat vftime; + vint vitime = getTimeSegment(ray.time(), vfloat((float)(pre.grid->time_steps-1)), vftime); + + vbool valid_o = valid_i; + vbool valid1 = valid_i; + while (any(valid1)) { + const int j = int(bsf(movemask(valid1))); + const int itime = vitime[j]; + const vbool valid2 = valid1 & (itime == vitime); + valid1 = valid1 & !valid2; + valid_o &= !valid2 | occluded(valid2,pre,ray,vftime,itime,context,prim,lazy_node); + } + return !valid_o; + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, const vfloat& ftime, int itime, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + const size_t grid_offset = pre.grid->gridBytes >> 2; + const size_t dim_offset = pre.grid->dim_offset; + const size_t line_offset = pre.grid->width; + const float* const grid_x = pre.grid->decodeLeaf(itime,prim); + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + vbool valid = valid_i; + const size_t max_x = pre.grid->width == 2 ? 1 : 2; + const size_t max_y = pre.grid->height == 2 ? 1 : 2; + for (size_t y=0; y a00(grid_x[ofs00],grid_y[ofs00],grid_z[ofs00]); + const Vec3vf a01(grid_x[ofs01],grid_y[ofs01],grid_z[ofs01]); + const Vec3vf a10(grid_x[ofs10],grid_y[ofs10],grid_z[ofs10]); + const Vec3vf a11(grid_x[ofs11],grid_y[ofs11],grid_z[ofs11]); + ofs00 += grid_offset; + ofs01 += grid_offset; + ofs10 += grid_offset; + ofs11 += grid_offset; + const Vec3vf b00(grid_x[ofs00],grid_y[ofs00],grid_z[ofs00]); + const Vec3vf b01(grid_x[ofs01],grid_y[ofs01],grid_z[ofs01]); + const Vec3vf b10(grid_x[ofs10],grid_y[ofs10],grid_z[ofs10]); + const Vec3vf b11(grid_x[ofs11],grid_y[ofs11],grid_z[ofs11]); + const Vec3vf p00 = lerp(a00,b00,ftime); + const Vec3vf p01 = lerp(a01,b01,ftime); + const Vec3vf p10 = lerp(a10,b10,ftime); + const Vec3vf p11 = lerp(a11,b11,ftime); + + pre.intersector.intersectK(valid,ray,p00,p01,p10,MapUV0(grid_uv,ofs00,ofs01,ofs10,ofs11),OccludedKEpilogMU<1,K,true>(valid,ray,context,pre.grid->geomID(),pre.grid->primID())); + if (none(valid)) break; + pre.intersector.intersectK(valid,ray,p10,p01,p11,MapUV1(grid_uv,ofs00,ofs01,ofs10,ofs11),OccludedKEpilogMU<1,K,true>(valid,ray,context,pre.grid->geomID(),pre.grid->primID())); + if (none(valid)) break; + } + } + return valid; + } + + template + static __forceinline void intersect(RayHitK& ray, size_t k, + const float ftime, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t grid_offset = pre.grid->gridBytes >> 2; + const size_t dim_offset = pre.grid->dim_offset; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + Vec3 a0, a1, a2; + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,a0,a1,a2); + + Vec3 b0, b1, b2; + Loader::gather(grid_x+grid_offset,grid_y+grid_offset,grid_z+grid_offset,line_offset,lines,b0,b1,b2); + + Vec3 v0 = lerp(a0,b0,vfloat(ftime)); + Vec3 v1 = lerp(a1,b1,vfloat(ftime)); + Vec3 v2 = lerp(a2,b2,vfloat(ftime)); + + pre.intersector.intersect(ray,k,v0,v1,v2,GridSOA::MapUV(grid_uv,line_offset,lines),Intersect1KEpilogMU(ray,k,context,pre.grid->geomID(),pre.grid->primID())); + }; + + template + static __forceinline bool occluded(RayK& ray, size_t k, + const float ftime, + IntersectContext* context, + const float* const grid_x, + const size_t line_offset, + const size_t lines, + Precalculations& pre) + { + typedef typename Loader::vfloat vfloat; + const size_t grid_offset = pre.grid->gridBytes >> 2; + const size_t dim_offset = pre.grid->dim_offset; + const float* const grid_y = grid_x + 1 * dim_offset; + const float* const grid_z = grid_x + 2 * dim_offset; + const float* const grid_uv = grid_x + 3 * dim_offset; + + Vec3 a0, a1, a2; + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,a0,a1,a2); + + Vec3 b0, b1, b2; + Loader::gather(grid_x+grid_offset,grid_y+grid_offset,grid_z+grid_offset,line_offset,lines,b0,b1,b2); + + Vec3 v0 = lerp(a0,b0,vfloat(ftime)); + Vec3 v1 = lerp(a1,b1,vfloat(ftime)); + Vec3 v2 = lerp(a2,b2,vfloat(ftime)); + + return pre.intersector.intersect(ray,k,v0,v1,v2,GridSOA::MapUV(grid_uv,line_offset,lines),Occluded1KEpilogMU(ray,k,context,pre.grid->geomID(),pre.grid->primID())); + } + + /*! Intersect a ray with the primitive. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + float ftime; + int itime = getTimeSegment(ray.time()[k], float(pre.grid->time_steps-1), ftime); + + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(itime,prim); + +#if defined(__AVX__) + intersect( ray, k, ftime, context, grid_x, line_offset, lines, pre); +#else + intersect(ray, k, ftime, context, grid_x, line_offset, lines, pre); + if (likely(lines > 2)) + intersect(ray, k, ftime, context, grid_x+line_offset, line_offset, lines, pre); +#endif + } + + /*! Test if the ray is occluded by the primitive */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + float ftime; + int itime = getTimeSegment(ray.time()[k], float(pre.grid->time_steps-1), ftime); + + const size_t line_offset = pre.grid->width; + const size_t lines = pre.grid->height; + const float* const grid_x = pre.grid->decodeLeaf(itime,prim); + +#if defined(__AVX__) + return occluded( ray, k, ftime, context, grid_x, line_offset, lines, pre); +#else + if (occluded(ray, k, ftime, context, grid_x, line_offset, lines, pre)) return true; + if (likely(lines > 2)) + if (occluded(ray, k, ftime, context, grid_x+line_offset, line_offset, lines, pre)) return true; +#endif + return false; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/instance.h b/thirdparty/embree/kernels/geometry/instance.h new file mode 100644 index 000000000000..66893d581f0b --- /dev/null +++ b/thirdparty/embree/kernels/geometry/instance.h @@ -0,0 +1,78 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "../common/scene_instance.h" + +namespace embree +{ + struct InstancePrimitive + { + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored primitives */ + static __forceinline size_t max_size() { return 1; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return N; } + + public: + + InstancePrimitive (const Instance* instance, unsigned int instID) + : instance(instance) + , instID_(instID) + {} + + __forceinline void fill(const PrimRef* prims, size_t& i, size_t end, Scene* scene) + { + assert(end-i == 1); + const PrimRef& prim = prims[i]; i++; + const unsigned int geomID = prim.geomID(); + const Instance* instance = scene->get(geomID); + new (this) InstancePrimitive(instance, geomID); + } + + __forceinline LBBox3fa fillMB(const PrimRef* prims, size_t& i, size_t end, Scene* scene, size_t itime) + { + assert(end-i == 1); + const PrimRef& prim = prims[i]; i++; + const unsigned int geomID = prim.geomID(); + const Instance* instance = scene->get(geomID); + new (this) InstancePrimitive(instance,geomID); + return instance->linearBounds(0,itime); + } + + __forceinline LBBox3fa fillMB(const PrimRefMB* prims, size_t& i, size_t end, Scene* scene, const BBox1f time_range) + { + assert(end-i == 1); + const PrimRefMB& prim = prims[i]; i++; + const unsigned int geomID = prim.geomID(); + const Instance* instance = scene->get(geomID); + new (this) InstancePrimitive(instance,geomID); + return instance->linearBounds(0,time_range); + } + + /* Updates the primitive */ + __forceinline BBox3fa update(Instance* instance) { + return instance->bounds(0); + } + + public: + const Instance* instance; + const unsigned int instID_ = std::numeric_limits::max (); + }; +} diff --git a/thirdparty/embree/kernels/geometry/instance_intersector.h b/thirdparty/embree/kernels/geometry/instance_intersector.h new file mode 100644 index 000000000000..91731a39c5a8 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/instance_intersector.h @@ -0,0 +1,84 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "instance.h" +#include "../common/ray.h" +#include "../common/point_query.h" + +namespace embree +{ + namespace isa + { + struct InstanceIntersector1 + { + typedef InstancePrimitive Primitive; + + struct Precalculations { + __forceinline Precalculations (const Ray& ray, const void *ptr) {} + }; + + static void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim); + static bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim); + static bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& prim); + }; + + struct InstanceIntersector1MB + { + typedef InstancePrimitive Primitive; + + struct Precalculations { + __forceinline Precalculations (const Ray& ray, const void *ptr) {} + }; + + static void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim); + static bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim); + static bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& prim); + }; + + template + struct InstanceIntersectorK + { + typedef InstancePrimitive Primitive; + + struct Precalculations { + __forceinline Precalculations (const vbool& valid, const RayK& ray) {} + }; + + static void intersect(const vbool& valid_i, const Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& prim); + static vbool occluded(const vbool& valid_i, const Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& prim); + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& prim) { + intersect(vbool(1<& ray, size_t k, IntersectContext* context, const Primitive& prim) { + occluded(vbool(1< + struct InstanceIntersectorKMB + { + typedef InstancePrimitive Primitive; + + struct Precalculations { + __forceinline Precalculations (const vbool& valid, const RayK& ray) {} + }; + + static void intersect(const vbool& valid_i, const Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& prim); + static vbool occluded(const vbool& valid_i, const Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& prim); + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& prim) { + intersect(vbool(1<& ray, size_t k, IntersectContext* context, const Primitive& prim) { + occluded(vbool(1< + struct UVIdentity { + __forceinline void operator() (vfloat& u, vfloat& v) const {} + }; + + + template + struct Intersect1Epilog1 + { + RayHit& ray; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Intersect1Epilog1(RayHit& ray, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (Hit& hit) const + { + /* ray mask test */ + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + if ((geometry->mask & ray.mask) == 0) return false; +#endif + hit.finalize(); + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + HitK<1> h(context->user,geomID,primID,hit.u,hit.v,hit.Ng); + const float old_t = ray.tfar; + ray.tfar = hit.t; + bool found = runIntersectionFilter1(geometry,ray,context,h); + if (!found) ray.tfar = old_t; + return found; + } + } +#endif + + /* update hit information */ + ray.tfar = hit.t; + ray.Ng = hit.Ng; + ray.u = hit.u; + ray.v = hit.v; + ray.primID = primID; + ray.geomID = geomID; + instance_id_stack::copy(context->user->instID, ray.instID); + return true; + } + }; + + template + struct Occluded1Epilog1 + { + Ray& ray; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Occluded1Epilog1(Ray& ray, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (Hit& hit) const + { + /* ray mask test */ + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); + + +#if defined(EMBREE_RAY_MASK) + if ((geometry->mask & ray.mask) == 0) return false; +#endif + hit.finalize(); + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) { + HitK<1> h(context->user,geomID,primID,hit.u,hit.v,hit.Ng); + const float old_t = ray.tfar; + ray.tfar = hit.t; + const bool found = runOcclusionFilter1(geometry,ray,context,h); + if (!found) ray.tfar = old_t; + return found; + } + } +#endif + return true; + } + }; + + template + struct Intersect1KEpilog1 + { + RayHitK& ray; + size_t k; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Intersect1KEpilog1(RayHitK& ray, size_t k, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), k(k), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (Hit& hit) const + { + /* ray mask test */ + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + if ((geometry->mask & ray.mask[k]) == 0) + return false; +#endif + hit.finalize(); + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + HitK h(context->user,geomID,primID,hit.u,hit.v,hit.Ng); + const float old_t = ray.tfar[k]; + ray.tfar[k] = hit.t; + const bool found = any(runIntersectionFilter(vbool(1<*, const size_t&>(context->user->instID, ray.instID, k); + return true; + } + }; + + template + struct Occluded1KEpilog1 + { + RayK& ray; + size_t k; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Occluded1KEpilog1(RayK& ray, size_t k, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), k(k), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (Hit& hit) const + { + /* ray mask test */ + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + if ((geometry->mask & ray.mask[k]) == 0) + return false; +#endif + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) { + hit.finalize(); + HitK h(context->user,geomID,primID,hit.u,hit.v,hit.Ng); + const float old_t = ray.tfar[k]; + ray.tfar[k] = hit.t; + const bool found = any(runOcclusionFilter(vbool(1< + struct Intersect1EpilogM + { + RayHit& ray; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + + __forceinline Intersect1EpilogM(RayHit& ray, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs) + : ray(ray), context(context), geomIDs(geomIDs), primIDs(primIDs) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + vbool valid = valid_i; + if (Mx > M) valid &= (1<get(geomID); + +#if defined(EMBREE_RAY_MASK) + /* goto next hit if mask test fails */ + if ((geometry->mask & ray.mask) == 0) { + clear(valid,i); + continue; + } +#endif + +#if defined(EMBREE_FILTER_FUNCTION) + /* call intersection filter function */ + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + const Vec2f uv = hit.uv(i); + HitK<1> h(context->user,geomID,primIDs[i],uv.x,uv.y,hit.Ng(i)); + const float old_t = ray.tfar; + ray.tfar = hit.t(i); + const bool found = runIntersectionFilter1(geometry,ray,context,h); + if (!found) ray.tfar = old_t; + foundhit |= found; + clear(valid,i); + valid &= hit.vt <= ray.tfar; // intersection filters may modify tfar value + continue; + } + } +#endif + break; + } +#endif + + /* update hit information */ + const Vec2f uv = hit.uv(i); + ray.tfar = hit.vt[i]; + ray.Ng.x = hit.vNg.x[i]; + ray.Ng.y = hit.vNg.y[i]; + ray.Ng.z = hit.vNg.z[i]; + ray.u = uv.x; + ray.v = uv.y; + ray.primID = primIDs[i]; + ray.geomID = geomID; + instance_id_stack::copy(context->user->instID, ray.instID); + return true; + + } + }; + +#if 0 && defined(__AVX512F__) // do not enable, this reduced frequency for BVH4 + template + struct Intersect1EpilogM + { + static const size_t Mx = 16; + RayHit& ray; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + + __forceinline Intersect1EpilogM(RayHit& ray, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs) + : ray(ray), context(context), geomIDs(geomIDs), primIDs(primIDs) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* MAYBE_UNUSED scene = context->scene; + vbool valid = valid_i; + if (Mx > M) valid &= (1<get(geomID); + +#if defined(EMBREE_RAY_MASK) + /* goto next hit if mask test fails */ + if ((geometry->mask & ray.mask) == 0) { + clear(valid,i); + continue; + } +#endif + +#if defined(EMBREE_FILTER_FUNCTION) + /* call intersection filter function */ + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + const Vec2f uv = hit.uv(i); + HitK<1> h(context->user,geomID,primIDs[i],uv.x,uv.y,hit.Ng(i)); + const float old_t = ray.tfar; + ray.tfar = hit.t(i); + const bool found = runIntersectionFilter1(geometry,ray,context,h); + if (!found) ray.tfar = old_t; + foundhit |= found; + clear(valid,i); + valid &= hit.vt <= ray.tfar; // intersection filters may modify tfar value + continue; + } + } +#endif + break; + } +#endif + + vbool finalMask(((unsigned int)1 << i)); + ray.update(finalMask,hit.vt,hit.vu,hit.vv,hit.vNg.x,hit.vNg.y,hit.vNg.z,geomID,primIDs); + instance_id_stack::foreach([&](unsigned level) + { + ray.instID[level] = context->user->instID[level]; + return (context->user->instID[level] != RTC_INVALID_GEOMETRY_ID); + }); + return true; + + } + }; +#endif + + template + struct Occluded1EpilogM + { + Ray& ray; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + + __forceinline Occluded1EpilogM(Ray& ray, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs) + : ray(ray), context(context), geomIDs(geomIDs), primIDs(primIDs) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) || defined(EMBREE_RAY_MASK) + if (unlikely(filter)) + hit.finalize(); /* called only once */ + + vbool valid = valid_i; + if (Mx > M) valid &= (1<get(geomID); + +#if defined(EMBREE_RAY_MASK) + /* goto next hit if mask test fails */ + if ((geometry->mask & ray.mask) == 0) { + m=btc(m,i); + continue; + } +#endif + +#if defined(EMBREE_FILTER_FUNCTION) + /* if we have no filter then the test passed */ + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) + { + const Vec2f uv = hit.uv(i); + HitK<1> h(context->user,geomID,primIDs[i],uv.x,uv.y,hit.Ng(i)); + const float old_t = ray.tfar; + ray.tfar = hit.t(i); + if (runOcclusionFilter1(geometry,ray,context,h)) return true; + ray.tfar = old_t; + m=btc(m,i); + continue; + } + } +#endif + break; + } +#endif + + return true; + } + }; + + template + struct Intersect1EpilogMU + { + RayHit& ray; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Intersect1EpilogMU(RayHit& ray, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + /* ray mask test */ + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + if ((geometry->mask & ray.mask) == 0) return false; +#endif + + vbool valid = valid_i; + hit.finalize(); + + size_t i = select_min(valid,hit.vt); + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) + { + bool foundhit = false; + while (true) + { + /* call intersection filter function */ + Vec2f uv = hit.uv(i); + const float old_t = ray.tfar; + ray.tfar = hit.t(i); + HitK<1> h(context->user,geomID,primID,uv.x,uv.y,hit.Ng(i)); + const bool found = runIntersectionFilter1(geometry,ray,context,h); + if (!found) ray.tfar = old_t; + foundhit |= found; + clear(valid,i); + valid &= hit.vt <= ray.tfar; // intersection filters may modify tfar value + if (unlikely(none(valid))) break; + i = select_min(valid,hit.vt); + } + return foundhit; + } +#endif + + /* update hit information */ + const Vec2f uv = hit.uv(i); + const Vec3fa Ng = hit.Ng(i); + ray.tfar = hit.t(i); + ray.Ng.x = Ng.x; + ray.Ng.y = Ng.y; + ray.Ng.z = Ng.z; + ray.u = uv.x; + ray.v = uv.y; + ray.primID = primID; + ray.geomID = geomID; + instance_id_stack::copy(context->user->instID, ray.instID); + return true; + } + }; + + template + struct Occluded1EpilogMU + { + Ray& ray; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Occluded1EpilogMU(Ray& ray, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (const vbool& valid, Hit& hit) const + { + /* ray mask test */ + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + if ((geometry->mask & ray.mask) == 0) return false; +#endif + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) + { + hit.finalize(); + for (size_t m=movemask(valid), i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) + { + const Vec2f uv = hit.uv(i); + const float old_t = ray.tfar; + ray.tfar = hit.t(i); + HitK<1> h(context->user,geomID,primID,uv.x,uv.y,hit.Ng(i)); + if (runOcclusionFilter1(geometry,ray,context,h)) return true; + ray.tfar = old_t; + } + return false; + } +#endif + return true; + } + }; + + template + struct IntersectKEpilogM + { + RayHitK& ray; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + const size_t i; + + __forceinline IntersectKEpilogM(RayHitK& ray, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs, + size_t i) + : ray(ray), context(context), geomIDs(geomIDs), primIDs(primIDs), i(i) {} + + template + __forceinline vbool operator() (const vbool& valid_i, const Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + + vfloat u, v, t; + Vec3vf Ng; + vbool valid = valid_i; + + std::tie(u,v,t,Ng) = hit(); + + const unsigned int geomID = geomIDs[i]; + const unsigned int primID = primIDs[i]; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); + + /* ray masking test */ +#if defined(EMBREE_RAY_MASK) + valid &= (geometry->mask & ray.mask) != 0; + if (unlikely(none(valid))) return false; +#endif + + /* occlusion filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + HitK h(context->user,geomID,primID,u,v,Ng); + const vfloat old_t = ray.tfar; + ray.tfar = select(valid,t,ray.tfar); + const vbool m_accept = runIntersectionFilter(valid,geometry,ray,context,h); + ray.tfar = select(m_accept,ray.tfar,old_t); + return m_accept; + } + } +#endif + + /* update hit information */ + vfloat::store(valid,&ray.tfar,t); + vfloat::store(valid,&ray.Ng.x,Ng.x); + vfloat::store(valid,&ray.Ng.y,Ng.y); + vfloat::store(valid,&ray.Ng.z,Ng.z); + vfloat::store(valid,&ray.u,u); + vfloat::store(valid,&ray.v,v); + vuint::store(valid,&ray.primID,primID); + vuint::store(valid,&ray.geomID,geomID); + instance_id_stack::copy*, const vbool&>(context->user->instID, ray.instID, valid); + return valid; + } + }; + + template + struct OccludedKEpilogM + { + vbool& valid0; + RayK& ray; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + const size_t i; + + __forceinline OccludedKEpilogM(vbool& valid0, + RayK& ray, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs, + size_t i) + : valid0(valid0), ray(ray), context(context), geomIDs(geomIDs), primIDs(primIDs), i(i) {} + + template + __forceinline vbool operator() (const vbool& valid_i, const Hit& hit) const + { + vbool valid = valid_i; + + /* ray masking test */ + Scene* scene MAYBE_UNUSED = context->scene; + const unsigned int geomID = geomIDs[i]; + const unsigned int primID = primIDs[i]; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + valid &= (geometry->mask & ray.mask) != 0; + if (unlikely(none(valid))) return valid; +#endif + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) + { + vfloat u, v, t; + Vec3vf Ng; + std::tie(u,v,t,Ng) = hit(); + HitK h(context->user,geomID,primID,u,v,Ng); + const vfloat old_t = ray.tfar; + ray.tfar = select(valid,t,ray.tfar); + valid = runOcclusionFilter(valid,geometry,ray,context,h); + ray.tfar = select(valid,ray.tfar,old_t); + } + } +#endif + + /* update occlusion */ + valid0 = valid0 & !valid; + return valid; + } + }; + + template + struct IntersectKEpilogMU + { + RayHitK& ray; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline IntersectKEpilogMU(RayHitK& ray, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline vbool operator() (const vbool& valid_org, const Hit& hit) const + { + vbool valid = valid_org; + vfloat u, v, t; + Vec3vf Ng; + std::tie(u,v,t,Ng) = hit(); + + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); + + /* ray masking test */ +#if defined(EMBREE_RAY_MASK) + valid &= (geometry->mask & ray.mask) != 0; + if (unlikely(none(valid))) return false; +#endif + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + HitK h(context->user,geomID,primID,u,v,Ng); + const vfloat old_t = ray.tfar; + ray.tfar = select(valid,t,ray.tfar); + const vbool m_accept = runIntersectionFilter(valid,geometry,ray,context,h); + ray.tfar = select(m_accept,ray.tfar,old_t); + return m_accept; + } + } +#endif + + /* update hit information */ + vfloat::store(valid,&ray.tfar,t); + vfloat::store(valid,&ray.Ng.x,Ng.x); + vfloat::store(valid,&ray.Ng.y,Ng.y); + vfloat::store(valid,&ray.Ng.z,Ng.z); + vfloat::store(valid,&ray.u,u); + vfloat::store(valid,&ray.v,v); + vuint::store(valid,&ray.primID,primID); + vuint::store(valid,&ray.geomID,geomID); + instance_id_stack::copy*, const vbool&>(context->user->instID, ray.instID, valid); + + return valid; + } + }; + + template + struct OccludedKEpilogMU + { + vbool& valid0; + RayK& ray; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline OccludedKEpilogMU(vbool& valid0, + RayK& ray, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : valid0(valid0), ray(ray), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline vbool operator() (const vbool& valid_i, const Hit& hit) const + { + vbool valid = valid_i; + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); + +#if defined(EMBREE_RAY_MASK) + valid &= (geometry->mask & ray.mask) != 0; + if (unlikely(none(valid))) return false; +#endif + + /* occlusion filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) + { + vfloat u, v, t; + Vec3vf Ng; + std::tie(u,v,t,Ng) = hit(); + HitK h(context->user,geomID,primID,u,v,Ng); + const vfloat old_t = ray.tfar; + ray.tfar = select(valid,t,ray.tfar); + valid = runOcclusionFilter(valid,geometry,ray,context,h); + ray.tfar = select(valid,ray.tfar,old_t); + } + } +#endif + + /* update occlusion */ + valid0 = valid0 & !valid; + return valid; + } + }; + + template + struct Intersect1KEpilogM + { + RayHitK& ray; + size_t k; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + + __forceinline Intersect1KEpilogM(RayHitK& ray, size_t k, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs) + : ray(ray), k(k), context(context), geomIDs(geomIDs), primIDs(primIDs) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + vbool valid = valid_i; + hit.finalize(); + if (Mx > M) valid &= (1<get(geomID); + +#if defined(EMBREE_RAY_MASK) + /* goto next hit if mask test fails */ + if ((geometry->mask & ray.mask[k]) == 0) { + clear(valid,i); + continue; + } +#endif + +#if defined(EMBREE_FILTER_FUNCTION) + /* call intersection filter function */ + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) { + assert(i h(context->user,geomID,primIDs[i],uv.x,uv.y,hit.Ng(i)); + const float old_t = ray.tfar[k]; + ray.tfar[k] = hit.t(i); + const bool found = any(runIntersectionFilter(vbool(1<(hit.vNg.x),vfloat(hit.vNg.y),vfloat(hit.vNg.z),geomID,vuint(primIDs)); +#else + const Vec2f uv = hit.uv(i); + ray.tfar[k] = hit.t(i); + ray.Ng.x[k] = hit.vNg.x[i]; + ray.Ng.y[k] = hit.vNg.y[i]; + ray.Ng.z[k] = hit.vNg.z[i]; + ray.u[k] = uv.x; + ray.v[k] = uv.y; + ray.primID[k] = primIDs[i]; + ray.geomID[k] = geomID; + instance_id_stack::copy*, const size_t&>(context->user->instID, ray.instID, k); +#endif + return true; + } + }; + + template + struct Occluded1KEpilogM + { + RayK& ray; + size_t k; + IntersectContext* context; + const vuint& geomIDs; + const vuint& primIDs; + + __forceinline Occluded1KEpilogM(RayK& ray, size_t k, + IntersectContext* context, + const vuint& geomIDs, + const vuint& primIDs) + : ray(ray), k(k), context(context), geomIDs(geomIDs), primIDs(primIDs) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) || defined(EMBREE_RAY_MASK) + if (unlikely(filter)) + hit.finalize(); /* called only once */ + + vbool valid = valid_i; + if (Mx > M) valid &= (1<get(geomID); + +#if defined(EMBREE_RAY_MASK) + /* goto next hit if mask test fails */ + if ((geometry->mask & ray.mask[k]) == 0) { + m=btc(m,i); + continue; + } +#endif + +#if defined(EMBREE_FILTER_FUNCTION) + /* execute occlusion filer */ + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) + { + const Vec2f uv = hit.uv(i); + const float old_t = ray.tfar[k]; + ray.tfar[k] = hit.t(i); + HitK h(context->user,geomID,primIDs[i],uv.x,uv.y,hit.Ng(i)); + if (any(runOcclusionFilter(vbool(1< + struct Intersect1KEpilogMU + { + RayHitK& ray; + size_t k; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Intersect1KEpilogMU(RayHitK& ray, size_t k, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), k(k), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + /* ray mask test */ + if ((geometry->mask & ray.mask[k]) == 0) + return false; +#endif + + /* finalize hit calculation */ + vbool valid = valid_i; + hit.finalize(); + size_t i = select_min(valid,hit.vt); + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasIntersectionFilter())) + { + bool foundhit = false; + while (true) + { + const Vec2f uv = hit.uv(i); + const float old_t = ray.tfar[k]; + ray.tfar[k] = hit.t(i); + HitK h(context->user,geomID,primID,uv.x,uv.y,hit.Ng(i)); + const bool found = any(runIntersectionFilter(vbool(1<(Ng.x),vfloat(Ng.y),vfloat(Ng.z),geomID,vuint(primID)); +#else + const Vec2f uv = hit.uv(i); + const Vec3fa Ng = hit.Ng(i); + ray.tfar[k] = hit.t(i); + ray.Ng.x[k] = Ng.x; + ray.Ng.y[k] = Ng.y; + ray.Ng.z[k] = Ng.z; + ray.u[k] = uv.x; + ray.v[k] = uv.y; + ray.primID[k] = primID; + ray.geomID[k] = geomID; + instance_id_stack::copy*, const size_t&>(context->user->instID, ray.instID, k); +#endif + return true; + } + }; + + template + struct Occluded1KEpilogMU + { + RayK& ray; + size_t k; + IntersectContext* context; + const unsigned int geomID; + const unsigned int primID; + + __forceinline Occluded1KEpilogMU(RayK& ray, size_t k, + IntersectContext* context, + const unsigned int geomID, + const unsigned int primID) + : ray(ray), k(k), context(context), geomID(geomID), primID(primID) {} + + template + __forceinline bool operator() (const vbool& valid_i, Hit& hit) const + { + Scene* scene MAYBE_UNUSED = context->scene; + Geometry* geometry MAYBE_UNUSED = scene->get(geomID); +#if defined(EMBREE_RAY_MASK) + /* ray mask test */ + if ((geometry->mask & ray.mask[k]) == 0) + return false; +#endif + + /* intersection filter test */ +#if defined(EMBREE_FILTER_FUNCTION) + if (filter) { + if (unlikely(context->hasContextFilter() || geometry->hasOcclusionFilter())) + { + hit.finalize(); + for (size_t m=movemask(valid_i), i=bsf(m); m!=0; m=btc(m,i), i=bsf(m)) + { + const Vec2f uv = hit.uv(i); + const float old_t = ray.tfar[k]; + ray.tfar[k] = hit.t(i); + HitK h(context->user,geomID,primID,uv.x,uv.y,hit.Ng(i)); + if (any(runOcclusionFilter(vbool(1< + struct ArrayIntersector1 + { + typedef typename Intersector::Primitive Primitive; + typedef typename Intersector::Precalculations Precalculations; + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + for (size_t i=0; i + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + for (size_t i=0; i + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t num, const TravPointQuery &tquery, size_t& lazy_node) + { + bool changed = false; + for (size_t i=0; i + static __forceinline void intersectK(const vbool& valid, /* PrecalculationsK& pre, */ RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + } + + template + static __forceinline vbool occludedK(const vbool& valid, /* PrecalculationsK& pre, */ RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + return valid; + } + }; + + template + struct ArrayIntersectorK_1 + { + typedef typename Intersector::Primitive Primitive; + typedef typename Intersector::Precalculations Precalculations; + + template + static __forceinline void intersect(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + for (size_t i=0; i + static __forceinline vbool occluded(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + vbool valid0 = valid; + for (size_t i=0; i + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + for (size_t i=0; i + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + for (size_t i=0; i + struct ArrayIntersectorKStream + { + typedef typename IntersectorK::Primitive PrimitiveK; + typedef typename IntersectorK::Precalculations PrecalculationsK; + + static __forceinline void intersectK(const vbool& valid, const Accel::Intersectors* This, /* PrecalculationsK& pre, */ RayHitK& ray, IntersectContext* context, const PrimitiveK* prim, size_t num, size_t& lazy_node) + { + PrecalculationsK pre(valid,ray); // FIXME: might cause trouble + + for (size_t i=0; i occludedK(const vbool& valid, const Accel::Intersectors* This, /* PrecalculationsK& pre, */ RayK& ray, IntersectContext* context, const PrimitiveK* prim, size_t num, size_t& lazy_node) + { + PrecalculationsK pre(valid,ray); // FIXME: might cause trouble + vbool valid0 = valid; + for (size_t i=0; i& ray, size_t k, IntersectContext* context, const PrimitiveK* prim, size_t num, size_t& lazy_node) + { + PrecalculationsK pre(ray.tnear() <= ray.tfar,ray); // FIXME: might cause trouble + for (size_t i=0; i& ray, size_t k, IntersectContext* context, const PrimitiveK* prim, size_t num, size_t& lazy_node) + { + PrecalculationsK pre(ray.tnear() <= ray.tfar,ray); // FIXME: might cause trouble + for (size_t i=0; i** __restrict__ inputPackets, IntersectContext* context, const PrimitiveK* prim, size_t num, size_t& lazy_node) + { + size_t m_occluded = 0; + for (size_t i=0; i &ray = *inputPackets[rayID / K]; + const size_t k = rayID % K; + PrecalculationsK pre(ray.tnear() <= ray.tfar,ray); // FIXME: might cause trouble + if (IntersectorK::occluded(pre,ray,k,context,prim[i])) + { + m_occluded |= (size_t)1 << rayID; + ray.tfar[k] = neg_inf; + } + } + } + return m_occluded; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/line_intersector.h b/thirdparty/embree/kernels/geometry/line_intersector.h new file mode 100644 index 000000000000..eef5b0b1fd1c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/line_intersector.h @@ -0,0 +1,141 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + namespace isa + { + template + struct LineIntersectorHitM + { + __forceinline LineIntersectorHitM() {} + + __forceinline LineIntersectorHitM(const vfloat& u, const vfloat& v, const vfloat& t, const Vec3vf& Ng) + : vu(u), vv(v), vt(t), vNg(Ng) {} + + __forceinline void finalize() {} + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + public: + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + struct FlatLinearCurveIntersector1 + { + typedef CurvePrecalculations1 Precalculations; + + template + static __forceinline bool intersect(const vbool& valid_i, + Ray& ray, + IntersectContext* context, + const LineSegments* geom, + const Precalculations& pre, + const Vec4vf& v0i, const Vec4vf& v1i, + const Epilog& epilog) + { + /* transform end points into ray space */ + vbool valid = valid_i; + vfloat depth_scale = pre.depth_scale; + LinearSpace3> ray_space = pre.ray_space; + + const Vec3vf ray_org ((Vec3fa)ray.org); + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec4vf v1 = enlargeRadiusToMinWidth(context,geom,ray_org,v1i); + + Vec4vf p0(xfmVector(ray_space,v0.xyz()-ray_org), v0.w); + Vec4vf p1(xfmVector(ray_space,v1.xyz()-ray_org), v1.w); + + /* approximative intersection with cone */ + const Vec4vf v = p1-p0; + const Vec4vf w = -p0; + const vfloat d0 = madd(w.x,v.x,w.y*v.y); + const vfloat d1 = madd(v.x,v.x,v.y*v.y); + const vfloat u = clamp(d0*rcp(d1),vfloat(zero),vfloat(one)); + const Vec4vf p = madd(u,v,p0); + const vfloat t = p.z; + const vfloat d2 = madd(p.x,p.x,p.y*p.y); + const vfloat r = p.w; + const vfloat r2 = r*r; + valid &= (d2 <= r2) & (vfloat(ray.tnear()) <= t) & (t <= vfloat(ray.tfar)); + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) + valid &= t > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR)*r*depth_scale; // ignore self intersections + if (unlikely(none(valid))) return false; + + /* ignore denormalized segments */ + const Vec3vf T = v1.xyz()-v0.xyz(); + valid &= (T.x != vfloat(zero)) | (T.y != vfloat(zero)) | (T.z != vfloat(zero)); + if (unlikely(none(valid))) return false; + + /* update hit information */ + LineIntersectorHitM hit(u,zero,t,T); + return epilog(valid,hit); + } + }; + + template + struct FlatLinearCurveIntersectorK + { + typedef CurvePrecalculationsK Precalculations; + + template + static __forceinline bool intersect(const vbool& valid_i, + RayK& ray, size_t k, + IntersectContext* context, + const LineSegments* geom, + const Precalculations& pre, + const Vec4vf& v0i, const Vec4vf& v1i, + const Epilog& epilog) + { + /* transform end points into ray space */ + vbool valid = valid_i; + vfloat depth_scale = pre.depth_scale[k]; + LinearSpace3> ray_space = pre.ray_space[k]; + const Vec3vf ray_org(ray.org.x[k],ray.org.y[k],ray.org.z[k]); + const Vec3vf ray_dir(ray.dir.x[k],ray.dir.y[k],ray.dir.z[k]); + + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec4vf v1 = enlargeRadiusToMinWidth(context,geom,ray_org,v1i); + + Vec4vf p0(xfmVector(ray_space,v0.xyz()-ray_org), v0.w); + Vec4vf p1(xfmVector(ray_space,v1.xyz()-ray_org), v1.w); + + /* approximative intersection with cone */ + const Vec4vf v = p1-p0; + const Vec4vf w = -p0; + const vfloat d0 = madd(w.x,v.x,w.y*v.y); + const vfloat d1 = madd(v.x,v.x,v.y*v.y); + const vfloat u = clamp(d0*rcp(d1),vfloat(zero),vfloat(one)); + const Vec4vf p = madd(u,v,p0); + const vfloat t = p.z; + const vfloat d2 = madd(p.x,p.x,p.y*p.y); + const vfloat r = p.w; + const vfloat r2 = r*r; + valid &= (d2 <= r2) & (vfloat(ray.tnear()[k]) <= t) & (t <= vfloat(ray.tfar[k])); + if (EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR != 0.0f) + valid &= t > float(EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR)*r*depth_scale; // ignore self intersections + if (unlikely(none(valid))) return false; + + /* ignore denormalized segments */ + const Vec3vf T = v1.xyz()-v0.xyz(); + valid &= (T.x != vfloat(zero)) | (T.y != vfloat(zero)) | (T.z != vfloat(zero)); + if (unlikely(none(valid))) return false; + + /* update hit information */ + LineIntersectorHitM hit(u,zero,t,T); + return epilog(valid,hit); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/linei.h b/thirdparty/embree/kernels/geometry/linei.h new file mode 100644 index 000000000000..a72029ca53d7 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/linei.h @@ -0,0 +1,709 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + template + struct LineMi + { + /* Virtual interface to query information about the line segment type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored line segments */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N line segments */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + /* Returns required number of bytes for N line segments */ + static __forceinline size_t bytes(size_t N) { return blocks(N)*sizeof(LineMi); } + + public: + + /* Default constructor */ + __forceinline LineMi() { } + + /* Construction from vertices and IDs */ + __forceinline LineMi(const vuint& v0, unsigned short leftExists, unsigned short rightExists, const vuint& geomIDs, const vuint& primIDs, Geometry::GType gtype) + : gtype((unsigned char)gtype), m((unsigned char)popcnt(vuint(primIDs) != vuint(-1))), sharedGeomID(geomIDs[0]), leftExists (leftExists), rightExists(rightExists), v0(v0), primIDs(primIDs) + { + assert(all(vuint(geomID()) == geomIDs)); + } + + /* Returns a mask that tells which line segments are valid */ + __forceinline vbool valid() const { return primIDs != vuint(-1); } + + /* Returns a mask that tells which line segments are valid */ + template + __forceinline vbool valid() const { return vuint(primIDs) != vuint(-1); } + + /* Returns if the specified line segment is valid */ + __forceinline bool valid(const size_t i) const { assert(i + //static __forceinline T unmask(T &index) { return index & 0x3fffffff; } + + __forceinline unsigned int geomID(unsigned int i = 0) const { return sharedGeomID; } + //__forceinline vuint geomID() { return unmask(geomIDs); } + //__forceinline const vuint geomID() const { return unmask(geomIDs); } + //__forceinline unsigned int geomID(const size_t i) const { assert(i& primID() { return primIDs; } + __forceinline const vuint& primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(i& p0, + Vec4vf& p1, + const LineSegments* geom) const; + + __forceinline void gatheri(Vec4vf& p0, + Vec4vf& p1, + const LineSegments* geom, + const int itime) const; + + __forceinline void gather(Vec4vf& p0, + Vec4vf& p1, + const LineSegments* geom, + float time) const; + + /* gather the line segments with lateral info */ + __forceinline void gather(Vec4vf& p0, + Vec4vf& p1, + Vec4vf& pL, + Vec4vf& pR, + const LineSegments* geom) const; + + __forceinline void gatheri(Vec4vf& p0, + Vec4vf& p1, + Vec4vf& pL, + Vec4vf& pR, + const LineSegments* geom, + const int itime) const; + + __forceinline void gather(Vec4vf& p0, + Vec4vf& p1, + Vec4vf& pL, + Vec4vf& pR, + const LineSegments* geom, + float time) const; + + __forceinline void gather(Vec4vf& p0, + Vec4vf& p1, + vbool& cL, + vbool& cR, + const LineSegments* geom) const; + + __forceinline void gatheri(Vec4vf& p0, + Vec4vf& p1, + vbool& cL, + vbool& cR, + const LineSegments* geom, + const int itime) const; + + __forceinline void gather(Vec4vf& p0, + Vec4vf& p1, + vbool& cL, + vbool& cR, + const LineSegments* geom, + float time) const; + + /* Calculate the bounds of the line segments */ + __forceinline const BBox3fa bounds(const Scene* scene, size_t itime = 0) const + { + BBox3fa bounds = empty; + for (size_t i=0; iget(geomID(i)); + const Vec3ff& p0 = geom->vertex(v0[i]+0,itime); + const Vec3ff& p1 = geom->vertex(v0[i]+1,itime); + BBox3fa b = merge(BBox3fa(p0),BBox3fa(p1)); + b = enlarge(b,Vec3fa(max(p0.w,p1.w))); + bounds.extend(b); + } + return bounds; + } + + /* Calculate the linear bounds of the primitive */ + __forceinline LBBox3fa linearBounds(const Scene* scene, size_t itime) { + return LBBox3fa(bounds(scene,itime+0), bounds(scene,itime+1)); + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, size_t itime, size_t numTimeSteps) { + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID(i)); + allBounds.extend(geom->linearBounds(primID(i), itime, numTimeSteps)); + } + return allBounds; + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, const BBox1f time_range) + { + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID((unsigned int)i)); + allBounds.extend(geom->linearBounds(primID(i), time_range)); + } + return allBounds; + } + + /* Fill line segment from line segment list */ + template + __forceinline void fill(const PrimRefT* prims, size_t& begin, size_t end, Scene* scene) + { + Geometry::GType gty = scene->get(prims[begin].geomID())->getType(); + vuint geomID, primID; + vuint v0; + unsigned short leftExists = 0; + unsigned short rightExists = 0; + const PrimRefT* prim = &prims[begin]; + + for (size_t i=0; iget(prim->geomID()); + if (begingeomID(); + primID[i] = prim->primID(); + v0[i] = geom->segment(prim->primID()); + leftExists |= geom->segmentLeftExists(primID[i]) << i; + rightExists |= geom->segmentRightExists(primID[i]) << i; + begin++; + } else { + assert(i); + if (i>0) { + geomID[i] = geomID[i-1]; + primID[i] = -1; + v0[i] = v0[i-1]; + } + } + if (begin + __forceinline static typename BVH::NodeRef createLeaf (BVH* bvh, const PrimRef* prims, const range& set, const Allocator& alloc) + { + size_t start = set.begin(); + size_t items = LineMi::blocks(set.size()); + size_t numbytes = LineMi::bytes(set.size()); + LineMi* accel = (LineMi*) alloc.malloc1(numbytes,M*sizeof(float)); + for (size_t i=0; iscene); + } + return bvh->encodeLeaf((char*)accel,items); + }; + + __forceinline LBBox3fa fillMB(const PrimRef* prims, size_t& begin, size_t end, Scene* scene, size_t itime) + { + fill(prims,begin,end,scene); + return linearBounds(scene,itime); + } + + __forceinline LBBox3fa fillMB(const PrimRefMB* prims, size_t& begin, size_t end, Scene* scene, const BBox1f time_range) + { + fill(prims,begin,end,scene); + return linearBounds(scene,time_range); + } + + template + __forceinline static typename BVH::NodeRecordMB4D createLeafMB(BVH* bvh, const SetMB& prims, const Allocator& alloc) + { + size_t start = prims.begin(); + size_t end = prims.end(); + size_t items = LineMi::blocks(prims.size()); + size_t numbytes = LineMi::bytes(prims.size()); + LineMi* accel = (LineMi*) alloc.malloc1(numbytes,M*sizeof(float)); + const typename BVH::NodeRef node = bvh->encodeLeaf((char*)accel,items); + + LBBox3fa bounds = empty; + for (size_t i=0; idata(),start,end,bvh->scene,prims.time_range)); + + return typename BVH::NodeRecordMB4D(node,bounds,prims.time_range); + }; + + /* Updates the primitive */ + __forceinline BBox3fa update(LineSegments* geom) + { + BBox3fa bounds = empty; + for (size_t i=0; ivertex(v0[i]+0); + const Vec3ff& p1 = geom->vertex(v0[i]+1); + BBox3fa b = merge(BBox3fa(p0),BBox3fa(p1)); + b = enlarge(b,Vec3fa(max(p0.w,p1.w))); + bounds.extend(b); + } + return bounds; + } + + /*! output operator */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const LineMi& line) { + return cout << "Line" << M << "i {" << line.v0 << ", " << line.geomID() << ", " << line.primID() << "}"; + } + + public: + unsigned char gtype; + unsigned char m; + unsigned int sharedGeomID; + unsigned short leftExists, rightExists; + vuint v0; // index of start vertex + private: + vuint primIDs; // primitive ID + }; + + template<> + __forceinline void LineMi<4>::gather(Vec4vf4& p0, + Vec4vf4& p1, + const LineSegments* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0])); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1])); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2])); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3])); + transpose(a0,a1,a2,a3,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1)); + transpose(b0,b1,b2,b3,p1.x,p1.y,p1.z,p1.w); + } + + template<> + __forceinline void LineMi<4>::gatheri(Vec4vf4& p0, + Vec4vf4& p1, + const LineSegments* geom, + const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0],itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1],itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2],itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3],itime)); + transpose(a0,a1,a2,a3,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1,itime)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1,itime)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1,itime)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1,itime)); + transpose(b0,b1,b2,b3,p1.x,p1.y,p1.z,p1.w); + } + + template<> + __forceinline void LineMi<4>::gather(Vec4vf4& p0, + Vec4vf4& p1, + const LineSegments* geom, + float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf4 a0,a1; + gatheri(a0,a1,geom,itime); + Vec4vf4 b0,b1; + gatheri(b0,b1,geom,itime+1); + p0 = lerp(a0,b0,vfloat4(ftime)); + p1 = lerp(a1,b1,vfloat4(ftime)); + } + + template<> + __forceinline void LineMi<4>::gather(Vec4vf4& p0, + Vec4vf4& p1, + vbool4& cL, + vbool4& cR, + const LineSegments* geom) const + { + gather(p0,p1,geom); + cL = !vbool4(leftExists); + cR = !vbool4(rightExists); + } + + template<> + __forceinline void LineMi<4>::gatheri(Vec4vf4& p0, + Vec4vf4& p1, + vbool4& cL, + vbool4& cR, + const LineSegments* geom, + const int itime) const + { + gatheri(p0,p1,geom,itime); + cL = !vbool4(leftExists); + cR = !vbool4(rightExists); + } + + template<> + __forceinline void LineMi<4>::gather(Vec4vf4& p0, + Vec4vf4& p1, + vbool4& cL, + vbool4& cR, + const LineSegments* geom, + float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf4 a0,a1; + gatheri(a0,a1,geom,itime); + Vec4vf4 b0,b1; + gatheri(b0,b1,geom,itime+1); + p0 = lerp(a0,b0,vfloat4(ftime)); + p1 = lerp(a1,b1,vfloat4(ftime)); + cL = !vbool4(leftExists); + cR = !vbool4(rightExists); + } + + template<> + __forceinline void LineMi<4>::gather(Vec4vf4& p0, + Vec4vf4& p1, + Vec4vf4& pL, + Vec4vf4& pR, + const LineSegments* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0])); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1])); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2])); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3])); + transpose(a0,a1,a2,a3,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1)); + transpose(b0,b1,b2,b3,p1.x,p1.y,p1.z,p1.w); + + const vfloat4 l0 = (leftExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]-1)) : vfloat4(inf); + const vfloat4 l1 = (leftExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]-1)) : vfloat4(inf); + const vfloat4 l2 = (leftExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]-1)) : vfloat4(inf); + const vfloat4 l3 = (leftExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]-1)) : vfloat4(inf); + transpose(l0,l1,l2,l3,pL.x,pL.y,pL.z,pL.w); + + const vfloat4 r0 = (rightExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]+2)) : vfloat4(inf); + const vfloat4 r1 = (rightExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]+2)) : vfloat4(inf); + const vfloat4 r2 = (rightExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]+2)) : vfloat4(inf); + const vfloat4 r3 = (rightExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]+2)) : vfloat4(inf); + transpose(r0,r1,r2,r3,pR.x,pR.y,pR.z,pR.w); + } + + template<> + __forceinline void LineMi<4>::gatheri(Vec4vf4& p0, + Vec4vf4& p1, + Vec4vf4& pL, + Vec4vf4& pR, + const LineSegments* geom, + const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0],itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1],itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2],itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3],itime)); + transpose(a0,a1,a2,a3,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1,itime)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1,itime)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1,itime)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1,itime)); + transpose(b0,b1,b2,b3,p1.x,p1.y,p1.z,p1.w); + + const vfloat4 l0 = (leftExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]-1,itime)) : vfloat4(inf); + const vfloat4 l1 = (leftExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]-1,itime)) : vfloat4(inf); + const vfloat4 l2 = (leftExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]-1,itime)) : vfloat4(inf); + const vfloat4 l3 = (leftExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]-1,itime)) : vfloat4(inf); + transpose(l0,l1,l2,l3,pL.x,pL.y,pL.z,pL.w); + + const vfloat4 r0 = (rightExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]+2,itime)) : vfloat4(inf); + const vfloat4 r1 = (rightExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]+2,itime)) : vfloat4(inf); + const vfloat4 r2 = (rightExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]+2,itime)) : vfloat4(inf); + const vfloat4 r3 = (rightExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]+2,itime)) : vfloat4(inf); + transpose(r0,r1,r2,r3,pR.x,pR.y,pR.z,pR.w); + } + + template<> + __forceinline void LineMi<4>::gather(Vec4vf4& p0, + Vec4vf4& p1, + Vec4vf4& pL, + Vec4vf4& pR, + const LineSegments* geom, + float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf4 a0,a1,aL,aR; + gatheri(a0,a1,aL,aR,geom,itime); + Vec4vf4 b0,b1,bL,bR; + gatheri(b0,b1,bL,bR,geom,itime+1); + p0 = lerp(a0,b0,vfloat4(ftime)); + p1 = lerp(a1,b1,vfloat4(ftime)); + pL = lerp(aL,bL,vfloat4(ftime)); + pR = lerp(aR,bR,vfloat4(ftime)); + } + +#if defined(__AVX__) + + template<> + __forceinline void LineMi<8>::gather(Vec4vf8& p0, + Vec4vf8& p1, + const LineSegments* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0])); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1])); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2])); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3])); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(v0[4])); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(v0[5])); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(v0[6])); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(v0[7])); + transpose(a0,a1,a2,a3,a4,a5,a6,a7,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1)); + const vfloat4 b4 = vfloat4::loadu(geom->vertexPtr(v0[4]+1)); + const vfloat4 b5 = vfloat4::loadu(geom->vertexPtr(v0[5]+1)); + const vfloat4 b6 = vfloat4::loadu(geom->vertexPtr(v0[6]+1)); + const vfloat4 b7 = vfloat4::loadu(geom->vertexPtr(v0[7]+1)); + transpose(b0,b1,b2,b3,b4,b5,b6,b7,p1.x,p1.y,p1.z,p1.w); + } + + template<> + __forceinline void LineMi<8>::gatheri(Vec4vf8& p0, + Vec4vf8& p1, + const LineSegments* geom, + const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0],itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1],itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2],itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3],itime)); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(v0[4],itime)); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(v0[5],itime)); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(v0[6],itime)); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(v0[7],itime)); + transpose(a0,a1,a2,a3,a4,a5,a6,a7,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1,itime)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1,itime)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1,itime)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1,itime)); + const vfloat4 b4 = vfloat4::loadu(geom->vertexPtr(v0[4]+1,itime)); + const vfloat4 b5 = vfloat4::loadu(geom->vertexPtr(v0[5]+1,itime)); + const vfloat4 b6 = vfloat4::loadu(geom->vertexPtr(v0[6]+1,itime)); + const vfloat4 b7 = vfloat4::loadu(geom->vertexPtr(v0[7]+1,itime)); + transpose(b0,b1,b2,b3,b4,b5,b6,b7,p1.x,p1.y,p1.z,p1.w); + } + + template<> + __forceinline void LineMi<8>::gather(Vec4vf8& p0, + Vec4vf8& p1, + const LineSegments* geom, + float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf8 a0,a1; + gatheri(a0,a1,geom,itime); + Vec4vf8 b0,b1; + gatheri(b0,b1,geom,itime+1); + p0 = lerp(a0,b0,vfloat8(ftime)); + p1 = lerp(a1,b1,vfloat8(ftime)); + } + + template<> + __forceinline void LineMi<8>::gather(Vec4vf8& p0, + Vec4vf8& p1, + Vec4vf8& pL, + Vec4vf8& pR, + const LineSegments* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0])); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1])); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2])); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3])); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(v0[4])); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(v0[5])); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(v0[6])); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(v0[7])); + transpose(a0,a1,a2,a3,a4,a5,a6,a7,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1)); + const vfloat4 b4 = vfloat4::loadu(geom->vertexPtr(v0[4]+1)); + const vfloat4 b5 = vfloat4::loadu(geom->vertexPtr(v0[5]+1)); + const vfloat4 b6 = vfloat4::loadu(geom->vertexPtr(v0[6]+1)); + const vfloat4 b7 = vfloat4::loadu(geom->vertexPtr(v0[7]+1)); + transpose(b0,b1,b2,b3,b4,b5,b6,b7,p1.x,p1.y,p1.z,p1.w); + + const vfloat4 l0 = (leftExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]-1)) : vfloat4(inf); + const vfloat4 l1 = (leftExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]-1)) : vfloat4(inf); + const vfloat4 l2 = (leftExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]-1)) : vfloat4(inf); + const vfloat4 l3 = (leftExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]-1)) : vfloat4(inf); + const vfloat4 l4 = (leftExists & (1<<4)) ? vfloat4::loadu(geom->vertexPtr(v0[4]-1)) : vfloat4(inf); + const vfloat4 l5 = (leftExists & (1<<5)) ? vfloat4::loadu(geom->vertexPtr(v0[5]-1)) : vfloat4(inf); + const vfloat4 l6 = (leftExists & (1<<6)) ? vfloat4::loadu(geom->vertexPtr(v0[6]-1)) : vfloat4(inf); + const vfloat4 l7 = (leftExists & (1<<7)) ? vfloat4::loadu(geom->vertexPtr(v0[7]-1)) : vfloat4(inf); + transpose(l0,l1,l2,l3,l4,l5,l6,l7,pL.x,pL.y,pL.z,pL.w); + + const vfloat4 r0 = (rightExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]+2)) : vfloat4(inf); + const vfloat4 r1 = (rightExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]+2)) : vfloat4(inf); + const vfloat4 r2 = (rightExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]+2)) : vfloat4(inf); + const vfloat4 r3 = (rightExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]+2)) : vfloat4(inf); + const vfloat4 r4 = (rightExists & (1<<4)) ? vfloat4::loadu(geom->vertexPtr(v0[4]+2)) : vfloat4(inf); + const vfloat4 r5 = (rightExists & (1<<5)) ? vfloat4::loadu(geom->vertexPtr(v0[5]+2)) : vfloat4(inf); + const vfloat4 r6 = (rightExists & (1<<6)) ? vfloat4::loadu(geom->vertexPtr(v0[6]+2)) : vfloat4(inf); + const vfloat4 r7 = (rightExists & (1<<7)) ? vfloat4::loadu(geom->vertexPtr(v0[7]+2)) : vfloat4(inf); + transpose(r0,r1,r2,r3,r4,r5,r6,r7,pR.x,pR.y,pR.z,pR.w); + } + + template<> + __forceinline void LineMi<8>::gatheri(Vec4vf8& p0, + Vec4vf8& p1, + Vec4vf8& pL, + Vec4vf8& pR, + const LineSegments* geom, + const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(v0[0],itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(v0[1],itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(v0[2],itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(v0[3],itime)); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(v0[4],itime)); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(v0[5],itime)); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(v0[6],itime)); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(v0[7],itime)); + transpose(a0,a1,a2,a3,a4,a5,a6,a7,p0.x,p0.y,p0.z,p0.w); + + const vfloat4 b0 = vfloat4::loadu(geom->vertexPtr(v0[0]+1,itime)); + const vfloat4 b1 = vfloat4::loadu(geom->vertexPtr(v0[1]+1,itime)); + const vfloat4 b2 = vfloat4::loadu(geom->vertexPtr(v0[2]+1,itime)); + const vfloat4 b3 = vfloat4::loadu(geom->vertexPtr(v0[3]+1,itime)); + const vfloat4 b4 = vfloat4::loadu(geom->vertexPtr(v0[4]+1,itime)); + const vfloat4 b5 = vfloat4::loadu(geom->vertexPtr(v0[5]+1,itime)); + const vfloat4 b6 = vfloat4::loadu(geom->vertexPtr(v0[6]+1,itime)); + const vfloat4 b7 = vfloat4::loadu(geom->vertexPtr(v0[7]+1,itime)); + transpose(b0,b1,b2,b3,b4,b5,b6,b7,p1.x,p1.y,p1.z,p1.w); + + const vfloat4 l0 = (leftExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]-1,itime)) : vfloat4(inf); + const vfloat4 l1 = (leftExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]-1,itime)) : vfloat4(inf); + const vfloat4 l2 = (leftExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]-1,itime)) : vfloat4(inf); + const vfloat4 l3 = (leftExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]-1,itime)) : vfloat4(inf); + const vfloat4 l4 = (leftExists & (1<<4)) ? vfloat4::loadu(geom->vertexPtr(v0[4]-1,itime)) : vfloat4(inf); + const vfloat4 l5 = (leftExists & (1<<5)) ? vfloat4::loadu(geom->vertexPtr(v0[5]-1,itime)) : vfloat4(inf); + const vfloat4 l6 = (leftExists & (1<<6)) ? vfloat4::loadu(geom->vertexPtr(v0[6]-1,itime)) : vfloat4(inf); + const vfloat4 l7 = (leftExists & (1<<7)) ? vfloat4::loadu(geom->vertexPtr(v0[7]-1,itime)) : vfloat4(inf); + transpose(l0,l1,l2,l3,l4,l5,l6,l7,pL.x,pL.y,pL.z,pL.w); + + const vfloat4 r0 = (rightExists & (1<<0)) ? vfloat4::loadu(geom->vertexPtr(v0[0]+2,itime)) : vfloat4(inf); + const vfloat4 r1 = (rightExists & (1<<1)) ? vfloat4::loadu(geom->vertexPtr(v0[1]+2,itime)) : vfloat4(inf); + const vfloat4 r2 = (rightExists & (1<<2)) ? vfloat4::loadu(geom->vertexPtr(v0[2]+2,itime)) : vfloat4(inf); + const vfloat4 r3 = (rightExists & (1<<3)) ? vfloat4::loadu(geom->vertexPtr(v0[3]+2,itime)) : vfloat4(inf); + const vfloat4 r4 = (rightExists & (1<<4)) ? vfloat4::loadu(geom->vertexPtr(v0[4]+2,itime)) : vfloat4(inf); + const vfloat4 r5 = (rightExists & (1<<5)) ? vfloat4::loadu(geom->vertexPtr(v0[5]+2,itime)) : vfloat4(inf); + const vfloat4 r6 = (rightExists & (1<<6)) ? vfloat4::loadu(geom->vertexPtr(v0[6]+2,itime)) : vfloat4(inf); + const vfloat4 r7 = (rightExists & (1<<7)) ? vfloat4::loadu(geom->vertexPtr(v0[7]+2,itime)) : vfloat4(inf); + transpose(r0,r1,r2,r3,r4,r5,r6,r7,pR.x,pR.y,pR.z,pR.w); + } + + template<> + __forceinline void LineMi<8>::gather(Vec4vf8& p0, + Vec4vf8& p1, + Vec4vf8& pL, + Vec4vf8& pR, + const LineSegments* geom, + float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf8 a0,a1,aL,aR; + gatheri(a0,a1,aL,aR,geom,itime); + Vec4vf8 b0,b1,bL,bR; + gatheri(b0,b1,bL,bR,geom,itime+1); + p0 = lerp(a0,b0,vfloat8(ftime)); + p1 = lerp(a1,b1,vfloat8(ftime)); + pL = lerp(aL,bL,vfloat8(ftime)); + pR = lerp(aR,bR,vfloat8(ftime)); + } + + template<> + __forceinline void LineMi<8>::gather(Vec4vf8& p0, + Vec4vf8& p1, + vbool8& cL, + vbool8& cR, + const LineSegments* geom) const + { + gather(p0,p1,geom); + cL = !vbool8(leftExists); + cR = !vbool8(rightExists); + } + + template<> + __forceinline void LineMi<8>::gatheri(Vec4vf8& p0, + Vec4vf8& p1, + vbool8& cL, + vbool8& cR, + const LineSegments* geom, + const int itime) const + { + gatheri(p0,p1,geom,itime); + cL = !vbool8(leftExists); + cR = !vbool8(rightExists); + } + + template<> + __forceinline void LineMi<8>::gather(Vec4vf8& p0, + Vec4vf8& p1, + vbool8& cL, + vbool8& cR, + const LineSegments* geom, + float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf8 a0,a1; + gatheri(a0,a1,geom,itime); + Vec4vf8 b0,b1; + gatheri(b0,b1,geom,itime+1); + p0 = lerp(a0,b0,vfloat8(ftime)); + p1 = lerp(a1,b1,vfloat8(ftime)); + cL = !vbool8(leftExists); + cR = !vbool8(rightExists); + } + +#endif + + template + typename LineMi::Type LineMi::type; + + typedef LineMi<4> Line4i; + typedef LineMi<8> Line8i; +} diff --git a/thirdparty/embree/kernels/geometry/linei_intersector.h b/thirdparty/embree/kernels/geometry/linei_intersector.h new file mode 100644 index 000000000000..a431796a88d5 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/linei_intersector.h @@ -0,0 +1,124 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "linei.h" +#include "line_intersector.h" +#include "intersector_epilog.h" + +namespace embree +{ + namespace isa + { + template + struct FlatLinearCurveMiIntersector1 + { + typedef LineMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom); + const vbool valid = line.template valid(); + FlatLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,Intersect1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom); + const vbool valid = line.template valid(); + return FlatLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,Occluded1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& line) + { + return PrimitivePointQuery1::pointQuery(query, context, line); + } + }; + + template + struct FlatLinearCurveMiMBIntersector1 + { + typedef LineMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom,ray.time()); + const vbool valid = line.template valid(); + FlatLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,Intersect1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom,ray.time()); + const vbool valid = line.template valid(); + return FlatLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,Occluded1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& line) + { + return PrimitivePointQuery1::pointQuery(query, context, line); + } + }; + + template + struct FlatLinearCurveMiIntersectorK + { + typedef LineMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom); + const vbool valid = line.template valid(); + FlatLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,Intersect1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom); + const vbool valid = line.template valid(); + return FlatLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,Occluded1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + }; + + template + struct FlatLinearCurveMiMBIntersectorK + { + typedef LineMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom,ray.time()[k]); + const vbool valid = line.template valid(); + FlatLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,Intersect1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1; line.gather(v0,v1,geom,ray.time()[k]); + const vbool valid = line.template valid(); + return FlatLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,Occluded1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/object.h b/thirdparty/embree/kernels/geometry/object.h new file mode 100644 index 000000000000..f26391de5239 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/object.h @@ -0,0 +1,84 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + struct Object + { + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored primitives */ + static __forceinline size_t max_size() { return 1; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return N; } + + public: + + /*! constructs a virtual object */ + Object (unsigned geomID, unsigned primID) + : _geomID(geomID), _primID(primID) {} + + __forceinline unsigned geomID() const { + return _geomID; + } + + __forceinline unsigned primID() const { + return _primID; + } + + /*! fill triangle from triangle list */ + __forceinline void fill(const PrimRef* prims, size_t& i, size_t end, Scene* scene) + { + const PrimRef& prim = prims[i]; i++; + new (this) Object(prim.geomID(), prim.primID()); + } + + /*! fill triangle from triangle list */ + __forceinline LBBox3fa fillMB(const PrimRef* prims, size_t& i, size_t end, Scene* scene, size_t itime) + { + const PrimRef& prim = prims[i]; i++; + const unsigned geomID = prim.geomID(); + const unsigned primID = prim.primID(); + new (this) Object(geomID, primID); + AccelSet* accel = (AccelSet*) scene->get(geomID); + return accel->linearBounds(primID,itime); + } + + /*! fill triangle from triangle list */ + __forceinline LBBox3fa fillMB(const PrimRefMB* prims, size_t& i, size_t end, Scene* scene, const BBox1f time_range) + { + const PrimRefMB& prim = prims[i]; i++; + const unsigned geomID = prim.geomID(); + const unsigned primID = prim.primID(); + new (this) Object(geomID, primID); + AccelSet* accel = (AccelSet*) scene->get(geomID); + return accel->linearBounds(primID,time_range); + } + + /* Updates the primitive */ + __forceinline BBox3fa update(AccelSet* mesh) { + return mesh->bounds(primID()); + } + + private: + unsigned int _geomID; //!< geometry ID + unsigned int _primID; //!< primitive ID + }; +} diff --git a/thirdparty/embree/kernels/geometry/object_intersector.h b/thirdparty/embree/kernels/geometry/object_intersector.h new file mode 100644 index 000000000000..97882e0e599e --- /dev/null +++ b/thirdparty/embree/kernels/geometry/object_intersector.h @@ -0,0 +1,127 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "object.h" +#include "../common/ray.h" + +namespace embree +{ + namespace isa + { + template + struct ObjectIntersector1 + { + typedef Object Primitive; + + static const bool validIntersectorK = false; + + struct Precalculations { + __forceinline Precalculations() {} + __forceinline Precalculations (const Ray& ray, const void *ptr) {} + }; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& prim) + { + AccelSet* accel = (AccelSet*) context->scene->get(prim.geomID()); + + /* perform ray mask test */ +#if defined(EMBREE_RAY_MASK) + if ((ray.mask & accel->mask) == 0) + return; +#endif + + accel->intersect(ray,prim.geomID(),prim.primID(),context,reportIntersection1); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& prim) + { + AccelSet* accel = (AccelSet*) context->scene->get(prim.geomID()); + /* perform ray mask test */ +#if defined(EMBREE_RAY_MASK) + if ((ray.mask & accel->mask) == 0) + return false; +#endif + + accel->occluded(ray,prim.geomID(),prim.primID(),context,&reportOcclusion1); + return ray.tfar < 0.0f; + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& prim) + { + AccelSet* accel = (AccelSet*)context->scene->get(prim.geomID()); + context->geomID = prim.geomID(); + context->primID = prim.primID(); + return accel->pointQuery(query, context); + } + + template + static __forceinline void intersectK(const vbool& valid, /* PrecalculationsK& pre, */ RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + assert(false); + } + + template + static __forceinline vbool occludedK(const vbool& valid, /* PrecalculationsK& pre, */ RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, size_t& lazy_node) + { + assert(false); + return valid; + } + }; + + template + struct ObjectIntersectorK + { + typedef Object Primitive; + + struct Precalculations { + __forceinline Precalculations (const vbool& valid, const RayK& ray) {} + }; + + static __forceinline void intersect(const vbool& valid_i, const Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& prim) + { + vbool valid = valid_i; + AccelSet* accel = (AccelSet*) context->scene->get(prim.geomID()); + + /* perform ray mask test */ +#if defined(EMBREE_RAY_MASK) + valid &= (ray.mask & accel->mask) != 0; + if (none(valid)) return; +#endif + accel->intersect(valid,ray,prim.geomID(),prim.primID(),context,&reportIntersection1); + } + + static __forceinline vbool occluded(const vbool& valid_i, const Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& prim) + { + vbool valid = valid_i; + AccelSet* accel = (AccelSet*) context->scene->get(prim.geomID()); + + /* perform ray mask test */ +#if defined(EMBREE_RAY_MASK) + valid &= (ray.mask & accel->mask) != 0; + if (none(valid)) return false; +#endif + accel->occluded(valid,ray,prim.geomID(),prim.primID(),context,&reportOcclusion1); + return ray.tfar < 0.0f; + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& prim) { + intersect(vbool(1<& ray, size_t k, IntersectContext* context, const Primitive& prim) { + occluded(vbool(1< ObjectIntersector4; + typedef ObjectIntersectorK<8,false> ObjectIntersector8; + typedef ObjectIntersectorK<16,false> ObjectIntersector16; + + typedef ObjectIntersectorK<4,true> ObjectIntersector4MB; + typedef ObjectIntersectorK<8,true> ObjectIntersector8MB; + typedef ObjectIntersectorK<16,true> ObjectIntersector16MB; + } +} diff --git a/thirdparty/embree/kernels/geometry/plane.h b/thirdparty/embree/kernels/geometry/plane.h new file mode 100644 index 000000000000..ebe45db55850 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/plane.h @@ -0,0 +1,57 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" + +namespace embree +{ + namespace isa + { + struct HalfPlane + { + const Vec3fa P; //!< plane origin + const Vec3fa N; //!< plane normal + + __forceinline HalfPlane(const Vec3fa& P, const Vec3fa& N) + : P(P), N(N) {} + + __forceinline BBox1f intersect(const Vec3fa& ray_org, const Vec3fa& ray_dir) const + { + Vec3fa O = Vec3fa(ray_org) - P; + Vec3fa D = Vec3fa(ray_dir); + float ON = dot(O,N); + float DN = dot(D,N); + bool eps = abs(DN) < min_rcp_input; + float t = -ON*rcp(DN); + float lower = select(eps || DN < 0.0f, float(neg_inf), t); + float upper = select(eps || DN > 0.0f, float(pos_inf), t); + return BBox1f(lower,upper); + } + }; + + template + struct HalfPlaneN + { + const Vec3vf P; //!< plane origin + const Vec3vf N; //!< plane normal + + __forceinline HalfPlaneN(const Vec3vf& P, const Vec3vf& N) + : P(P), N(N) {} + + __forceinline BBox> intersect(const Vec3fa& ray_org, const Vec3fa& ray_dir) const + { + Vec3vf O = Vec3vf((Vec3fa)ray_org) - P; + Vec3vf D = Vec3vf((Vec3fa)ray_dir); + vfloat ON = dot(O,N); + vfloat DN = dot(D,N); + vbool eps = abs(DN) < min_rcp_input; + vfloat t = -ON*rcp(DN); + vfloat lower = select(eps | DN < 0.0f, vfloat(neg_inf), t); + vfloat upper = select(eps | DN > 0.0f, vfloat(pos_inf), t); + return BBox>(lower,upper); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/pointi.h b/thirdparty/embree/kernels/geometry/pointi.h new file mode 100644 index 000000000000..4ba298e86b68 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/pointi.h @@ -0,0 +1,417 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + template + struct PointMi + { + /* Virtual interface to query information about the line segment type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored line segments */ + static __forceinline size_t max_size() + { + return M; + } + + /* Returns required number of primitive blocks for N line segments */ + static __forceinline size_t blocks(size_t N) + { + return (N + max_size() - 1) / max_size(); + } + + /* Returns required number of bytes for N line segments */ + static __forceinline size_t bytes(size_t N) + { + return blocks(N) * sizeof(PointMi); + } + + public: + /* Default constructor */ + __forceinline PointMi() {} + + /* Construction from vertices and IDs */ + __forceinline PointMi(const vuint& geomIDs, const vuint& primIDs, Geometry::GType gtype, uint32_t numPrimitives) + : gtype((unsigned char)gtype), + numPrimitives(numPrimitives), + sharedGeomID(geomIDs[0]), + primIDs(primIDs) + { + assert(all(vuint(geomID()) == geomIDs)); + } + + /* Returns a mask that tells which line segments are valid */ + __forceinline vbool valid() const { + return vint(step) < vint(numPrimitives); + } + + /* Returns a mask that tells which line segments are valid */ + template __forceinline vbool valid() const { + return vint(step) < vint(numPrimitives); + } + + /* Returns if the specified line segment is valid */ + __forceinline bool valid(const size_t i) const + { + assert(i < M); + return i < numPrimitives; + } + + /* Returns the number of stored line segments */ + __forceinline size_t size() const { + return numPrimitives; + } + + __forceinline unsigned int geomID(unsigned int i = 0) const { + return sharedGeomID; + } + + __forceinline vuint& primID() { + return primIDs; + } + __forceinline const vuint& primID() const { + return primIDs; + } + __forceinline unsigned int primID(const size_t i) const { + assert(i < M); + return primIDs[i]; + } + + /* gather the line segments */ + __forceinline void gather(Vec4vf& p0, const Points* geom) const; + __forceinline void gather(Vec4vf& p0, Vec3vf& n0, const Points* geom) const; + + __forceinline void gatheri(Vec4vf& p0, const Points* geom, const int itime) const; + __forceinline void gatheri(Vec4vf& p0, Vec3vf& n0, const Points* geom, const int itime) const; + + __forceinline void gather(Vec4vf& p0, const Points* geom, float time) const; + __forceinline void gather(Vec4vf& p0, Vec3vf& n0, const Points* geom, float time) const; + + /* Calculate the bounds of the line segments */ + __forceinline const BBox3fa bounds(const Scene* scene, size_t itime = 0) const + { + BBox3fa bounds = empty; + for (size_t i = 0; i < M && valid(i); i++) { + const Points* geom = scene->get(geomID(i)); + bounds.extend(geom->bounds(primID(i),itime)); + } + return bounds; + } + + /* Calculate the linear bounds of the primitive */ + __forceinline LBBox3fa linearBounds(const Scene* scene, size_t itime) { + return LBBox3fa(bounds(scene, itime + 0), bounds(scene, itime + 1)); + } + + __forceinline LBBox3fa linearBounds(const Scene* const scene, size_t itime, size_t numTimeSteps) + { + LBBox3fa allBounds = empty; + for (size_t i = 0; i < M && valid(i); i++) { + const Points* geom = scene->get(geomID(i)); + allBounds.extend(geom->linearBounds(primID(i), itime, numTimeSteps)); + } + return allBounds; + } + + __forceinline LBBox3fa linearBounds(const Scene* const scene, const BBox1f time_range) + { + LBBox3fa allBounds = empty; + for (size_t i = 0; i < M && valid(i); i++) { + const Points* geom = scene->get(geomID((unsigned int)i)); + allBounds.extend(geom->linearBounds(primID(i), time_range)); + } + return allBounds; + } + + /* Fill line segment from line segment list */ + template + __forceinline void fill(const PrimRefT* prims, size_t& begin, size_t end, Scene* scene) + { + Geometry::GType gty = scene->get(prims[begin].geomID())->getType(); + vuint geomID, primID; + vuint v0; + const PrimRefT* prim = &prims[begin]; + + int numPrimitives = 0; + for (size_t i = 0; i < M; i++) { + if (begin < end) { + geomID[i] = prim->geomID(); + primID[i] = prim->primID(); + begin++; + numPrimitives++; + } else { + assert(i); + if (i > 0) { + geomID[i] = geomID[i - 1]; + primID[i] = primID[i - 1]; + } + } + if (begin < end) + prim = &prims[begin]; // FIXME: remove this line + } + new (this) PointMi(geomID, primID, gty, numPrimitives); // FIXME: use non temporal store + } + + template + __forceinline static typename BVH::NodeRef createLeaf(BVH* bvh, + const PrimRef* prims, + const range& set, + const Allocator& alloc) + { + size_t start = set.begin(); + size_t items = PointMi::blocks(set.size()); + size_t numbytes = PointMi::bytes(set.size()); + PointMi* accel = (PointMi*)alloc.malloc1(numbytes, M * sizeof(float)); + for (size_t i = 0; i < items; i++) { + accel[i].fill(prims, start, set.end(), bvh->scene); + } + return bvh->encodeLeaf((char*)accel, items); + }; + + __forceinline LBBox3fa fillMB(const PrimRef* prims, size_t& begin, size_t end, Scene* scene, size_t itime) + { + fill(prims, begin, end, scene); + return linearBounds(scene, itime); + } + + __forceinline LBBox3fa fillMB( + const PrimRefMB* prims, size_t& begin, size_t end, Scene* scene, const BBox1f time_range) + { + fill(prims, begin, end, scene); + return linearBounds(scene, time_range); + } + + template + __forceinline static typename BVH::NodeRecordMB4D createLeafMB(BVH* bvh, const SetMB& prims, const Allocator& alloc) + { + size_t start = prims.object_range.begin(); + size_t end = prims.object_range.end(); + size_t items = PointMi::blocks(prims.object_range.size()); + size_t numbytes = PointMi::bytes(prims.object_range.size()); + PointMi* accel = (PointMi*)alloc.malloc1(numbytes, M * sizeof(float)); + const typename BVH::NodeRef node = bvh->encodeLeaf((char*)accel, items); + + LBBox3fa bounds = empty; + for (size_t i = 0; i < items; i++) + bounds.extend(accel[i].fillMB(prims.prims->data(), start, end, bvh->scene, prims.time_range)); + + return typename BVH::NodeRecordMB4D(node, bounds, prims.time_range); + }; + + /*! output operator */ + friend __forceinline embree_ostream operator<<(embree_ostream cout, const PointMi& line) + { + return cout << "Line" << M << "i {" << line.v0 << ", " << line.geomID() << ", " << line.primID() << "}"; + } + + public: + unsigned char gtype; + unsigned char numPrimitives; + unsigned int sharedGeomID; + + private: + vuint primIDs; // primitive ID + }; + + template<> + __forceinline void PointMi<4>::gather(Vec4vf4& p0, const Points* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0))); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1))); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2))); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3))); + transpose(a0, a1, a2, a3, p0.x, p0.y, p0.z, p0.w); + } + + template<> + __forceinline void PointMi<4>::gather(Vec4vf4& p0, Vec3vf4& n0, const Points* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0))); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1))); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2))); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3))); + transpose(a0, a1, a2, a3, p0.x, p0.y, p0.z, p0.w); + const vfloat4 b0 = vfloat4(geom->normal(primID(0))); + const vfloat4 b1 = vfloat4(geom->normal(primID(1))); + const vfloat4 b2 = vfloat4(geom->normal(primID(2))); + const vfloat4 b3 = vfloat4(geom->normal(primID(3))); + transpose(b0, b1, b2, b3, n0.x, n0.y, n0.z); + } + + template<> + __forceinline void PointMi<4>::gatheri(Vec4vf4& p0, const Points* geom, const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0), itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1), itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2), itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3), itime)); + transpose(a0, a1, a2, a3, p0.x, p0.y, p0.z, p0.w); + } + + template<> + __forceinline void PointMi<4>::gatheri(Vec4vf4& p0, Vec3vf4& n0, const Points* geom, const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0), itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1), itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2), itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3), itime)); + transpose(a0, a1, a2, a3, p0.x, p0.y, p0.z, p0.w); + const vfloat4 b0 = vfloat4(geom->normal(primID(0), itime)); + const vfloat4 b1 = vfloat4(geom->normal(primID(1), itime)); + const vfloat4 b2 = vfloat4(geom->normal(primID(2), itime)); + const vfloat4 b3 = vfloat4(geom->normal(primID(3), itime)); + transpose(b0, b1, b2, b3, n0.x, n0.y, n0.z); + } + + template<> + __forceinline void PointMi<4>::gather(Vec4vf4& p0, const Points* geom, float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf4 a0; gatheri(a0, geom, itime); + Vec4vf4 b0; gatheri(b0, geom, itime + 1); + p0 = lerp(a0, b0, vfloat4(ftime)); + } + + template<> + __forceinline void PointMi<4>::gather(Vec4vf4& p0, Vec3vf4& n0, const Points* geom, float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf4 a0, b0; + Vec3vf4 norm0, norm1; + gatheri(a0, norm0, geom, itime); + gatheri(b0, norm1, geom, itime + 1); + p0 = lerp(a0, b0, vfloat4(ftime)); + n0 = lerp(norm0, norm1, vfloat4(ftime)); + } + +#if defined(__AVX__) + + template<> + __forceinline void PointMi<8>::gather(Vec4vf8& p0, const Points* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0))); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1))); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2))); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3))); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(primID(4))); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(primID(5))); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(primID(6))); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(primID(7))); + transpose(a0, a1, a2, a3, a4, a5, a6, a7, p0.x, p0.y, p0.z, p0.w); + } + + template<> + __forceinline void PointMi<8>::gather(Vec4vf8& p0, Vec3vf8& n0, const Points* geom) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0))); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1))); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2))); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3))); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(primID(4))); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(primID(5))); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(primID(6))); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(primID(7))); + transpose(a0, a1, a2, a3, a4, a5, a6, a7, p0.x, p0.y, p0.z, p0.w); + const vfloat4 b0 = vfloat4(geom->normal(primID(0))); + const vfloat4 b1 = vfloat4(geom->normal(primID(1))); + const vfloat4 b2 = vfloat4(geom->normal(primID(2))); + const vfloat4 b3 = vfloat4(geom->normal(primID(3))); + const vfloat4 b4 = vfloat4(geom->normal(primID(4))); + const vfloat4 b5 = vfloat4(geom->normal(primID(5))); + const vfloat4 b6 = vfloat4(geom->normal(primID(6))); + const vfloat4 b7 = vfloat4(geom->normal(primID(7))); + transpose(b0, b1, b2, b3, b4, b5, b6, b7, n0.x, n0.y, n0.z); + } + + template<> + __forceinline void PointMi<8>::gatheri(Vec4vf8& p0, const Points* geom, const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0), itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1), itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2), itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3), itime)); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(primID(4), itime)); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(primID(5), itime)); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(primID(6), itime)); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(primID(7), itime)); + transpose(a0, a1, a2, a3, a4, a5, a6, a7, p0.x, p0.y, p0.z, p0.w); + } + + template<> + __forceinline void PointMi<8>::gatheri(Vec4vf8& p0, Vec3vf8& n0, const Points* geom, const int itime) const + { + const vfloat4 a0 = vfloat4::loadu(geom->vertexPtr(primID(0), itime)); + const vfloat4 a1 = vfloat4::loadu(geom->vertexPtr(primID(1), itime)); + const vfloat4 a2 = vfloat4::loadu(geom->vertexPtr(primID(2), itime)); + const vfloat4 a3 = vfloat4::loadu(geom->vertexPtr(primID(3), itime)); + const vfloat4 a4 = vfloat4::loadu(geom->vertexPtr(primID(4), itime)); + const vfloat4 a5 = vfloat4::loadu(geom->vertexPtr(primID(5), itime)); + const vfloat4 a6 = vfloat4::loadu(geom->vertexPtr(primID(6), itime)); + const vfloat4 a7 = vfloat4::loadu(geom->vertexPtr(primID(7), itime)); + transpose(a0, a1, a2, a3, a4, a5, a6, a7, p0.x, p0.y, p0.z, p0.w); + const vfloat4 b0 = vfloat4(geom->normal(primID(0), itime)); + const vfloat4 b1 = vfloat4(geom->normal(primID(1), itime)); + const vfloat4 b2 = vfloat4(geom->normal(primID(2), itime)); + const vfloat4 b3 = vfloat4(geom->normal(primID(3), itime)); + const vfloat4 b4 = vfloat4(geom->normal(primID(4), itime)); + const vfloat4 b5 = vfloat4(geom->normal(primID(5), itime)); + const vfloat4 b6 = vfloat4(geom->normal(primID(6), itime)); + const vfloat4 b7 = vfloat4(geom->normal(primID(7), itime)); + transpose(b0, b1, b2, b3, b4, b5, b6, b7, n0.x, n0.y, n0.z); + } + + template<> + __forceinline void PointMi<8>::gather(Vec4vf8& p0, const Points* geom, float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf8 a0; + gatheri(a0, geom, itime); + Vec4vf8 b0; + gatheri(b0, geom, itime + 1); + p0 = lerp(a0, b0, vfloat8(ftime)); + } + + template<> + __forceinline void PointMi<8>::gather(Vec4vf8& p0, Vec3vf8& n0, const Points* geom, float time) const + { + float ftime; + const int itime = geom->timeSegment(time, ftime); + + Vec4vf8 a0, b0; + Vec3vf8 norm0, norm1; + gatheri(a0, norm0, geom, itime); + gatheri(b0, norm1, geom, itime + 1); + p0 = lerp(a0, b0, vfloat8(ftime)); + n0 = lerp(norm0, norm1, vfloat8(ftime)); + } +#endif + + template + typename PointMi::Type PointMi::type; + + typedef PointMi<4> Point4i; + typedef PointMi<8> Point8i; + +} // namespace embree diff --git a/thirdparty/embree/kernels/geometry/primitive.h b/thirdparty/embree/kernels/geometry/primitive.h new file mode 100644 index 000000000000..41e5b2b3042c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/primitive.h @@ -0,0 +1,49 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "../common/scene.h" +#include "../../common/simd/simd.h" +#include "../common/primref.h" +#include "../common/primref_mb.h" + +namespace embree +{ + struct PrimitiveType + { + /*! returns name of this primitive type */ + virtual const char* name() const = 0; + + /*! Returns the number of stored active primitives in a block. */ + virtual size_t sizeActive(const char* This) const = 0; + + /*! Returns the number of stored active and inactive primitives in a block. */ + virtual size_t sizeTotal(const char* This) const = 0; + + /*! Returns the number of bytes of block. */ + virtual size_t getBytes(const char* This) const = 0; + }; + + template + struct PrimitivePointQuery1 + { + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& prim) + { + bool changed = false; + for (size_t i = 0; i < Primitive::max_size(); i++) + { + if (!prim.valid(i)) break; + STAT3(point_query.trav_prims,1,1,1); + AccelSet* accel = (AccelSet*)context->scene->get(prim.geomID(i)); + context->geomID = prim.geomID(i); + context->primID = prim.primID(i); + changed |= accel->pointQuery(query, context); + } + return changed; + } + + static __forceinline void pointQueryNoop(PointQuery* query, PointQueryContext* context, const Primitive& prim) { } + }; +} diff --git a/thirdparty/embree/kernels/geometry/primitive4.cpp b/thirdparty/embree/kernels/geometry/primitive4.cpp new file mode 100644 index 000000000000..f93574c9c8d0 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/primitive4.cpp @@ -0,0 +1,379 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "primitive.h" +#include "curveNv.h" +#include "curveNi.h" +#include "curveNi_mb.h" +#include "linei.h" +#include "triangle.h" +#include "trianglev.h" +#include "trianglev_mb.h" +#include "trianglei.h" +#include "quadv.h" +#include "quadi.h" +#include "subdivpatch1.h" +#include "object.h" +#include "instance.h" +#include "subgrid.h" + +namespace embree +{ + /********************** Curve4v **************************/ + + template<> + const char* Curve4v::Type::name () const { + return "curve4v"; + } + + template<> + size_t Curve4v::Type::sizeActive(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return ((Line4i*)This)->size(); + else + return ((Curve4v*)This)->N; + } + + template<> + size_t Curve4v::Type::sizeTotal(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return 4; + else + return ((Curve4v*)This)->N; + } + + template<> + size_t Curve4v::Type::getBytes(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return Line4i::bytes(sizeActive(This)); + else + return Curve4v::bytes(sizeActive(This)); + } + + /********************** Curve4i **************************/ + + template<> + const char* Curve4i::Type::name () const { + return "curve4i"; + } + + template<> + size_t Curve4i::Type::sizeActive(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return ((Line4i*)This)->size(); + else + return ((Curve4i*)This)->N; + } + + template<> + size_t Curve4i::Type::sizeTotal(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return 4; + else + return ((Curve4i*)This)->N; + } + + template<> + size_t Curve4i::Type::getBytes(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return Line4i::bytes(sizeActive(This)); + else + return Curve4i::bytes(sizeActive(This)); + } + + /********************** Curve4iMB **************************/ + + template<> + const char* Curve4iMB::Type::name () const { + return "curve4imb"; + } + + template<> + size_t Curve4iMB::Type::sizeActive(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return ((Line4i*)This)->size(); + else + return ((Curve4iMB*)This)->N; + } + + template<> + size_t Curve4iMB::Type::sizeTotal(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return 4; + else + return ((Curve4iMB*)This)->N; + } + + template<> + size_t Curve4iMB::Type::getBytes(const char* This) const + { + if ((*This & Geometry::GType::GTY_BASIS_MASK) == Geometry::GType::GTY_BASIS_LINEAR) + return Line4i::bytes(sizeActive(This)); + else + return Curve4iMB::bytes(sizeActive(This)); + } + + /********************** Line4i **************************/ + + template<> + const char* Line4i::Type::name () const { + return "line4i"; + } + + template<> + size_t Line4i::Type::sizeActive(const char* This) const { + return ((Line4i*)This)->size(); + } + + template<> + size_t Line4i::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Line4i::Type::getBytes(const char* This) const { + return sizeof(Line4i); + } + + /********************** Triangle4 **************************/ + + template<> + const char* Triangle4::Type::name () const { + return "triangle4"; + } + + template<> + size_t Triangle4::Type::sizeActive(const char* This) const { + return ((Triangle4*)This)->size(); + } + + template<> + size_t Triangle4::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Triangle4::Type::getBytes(const char* This) const { + return sizeof(Triangle4); + } + + /********************** Triangle4v **************************/ + + template<> + const char* Triangle4v::Type::name () const { + return "triangle4v"; + } + + template<> + size_t Triangle4v::Type::sizeActive(const char* This) const { + return ((Triangle4v*)This)->size(); + } + + template<> + size_t Triangle4v::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Triangle4v::Type::getBytes(const char* This) const { + return sizeof(Triangle4v); + } + + /********************** Triangle4i **************************/ + + template<> + const char* Triangle4i::Type::name () const { + return "triangle4i"; + } + + template<> + size_t Triangle4i::Type::sizeActive(const char* This) const { + return ((Triangle4i*)This)->size(); + } + + template<> + size_t Triangle4i::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Triangle4i::Type::getBytes(const char* This) const { + return sizeof(Triangle4i); + } + + /********************** Triangle4vMB **************************/ + + template<> + const char* Triangle4vMB::Type::name () const { + return "triangle4vmb"; + } + + template<> + size_t Triangle4vMB::Type::sizeActive(const char* This) const { + return ((Triangle4vMB*)This)->size(); + } + + template<> + size_t Triangle4vMB::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Triangle4vMB::Type::getBytes(const char* This) const { + return sizeof(Triangle4vMB); + } + + /********************** Quad4v **************************/ + + template<> + const char* Quad4v::Type::name () const { + return "quad4v"; + } + + template<> + size_t Quad4v::Type::sizeActive(const char* This) const { + return ((Quad4v*)This)->size(); + } + + template<> + size_t Quad4v::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Quad4v::Type::getBytes(const char* This) const { + return sizeof(Quad4v); + } + + /********************** Quad4i **************************/ + + template<> + const char* Quad4i::Type::name () const { + return "quad4i"; + } + + template<> + size_t Quad4i::Type::sizeActive(const char* This) const { + return ((Quad4i*)This)->size(); + } + + template<> + size_t Quad4i::Type::sizeTotal(const char* This) const { + return 4; + } + + template<> + size_t Quad4i::Type::getBytes(const char* This) const { + return sizeof(Quad4i); + } + + /********************** SubdivPatch1 **************************/ + + const char* SubdivPatch1::Type::name () const { + return "subdivpatch1"; + } + + size_t SubdivPatch1::Type::sizeActive(const char* This) const { + return 1; + } + + size_t SubdivPatch1::Type::sizeTotal(const char* This) const { + return 1; + } + + size_t SubdivPatch1::Type::getBytes(const char* This) const { + return sizeof(SubdivPatch1); + } + + SubdivPatch1::Type SubdivPatch1::type; + + /********************** Virtual Object **************************/ + + const char* Object::Type::name () const { + return "object"; + } + + size_t Object::Type::sizeActive(const char* This) const { + return 1; + } + + size_t Object::Type::sizeTotal(const char* This) const { + return 1; + } + + size_t Object::Type::getBytes(const char* This) const { + return sizeof(Object); + } + + Object::Type Object::type; + + /********************** Instance **************************/ + + const char* InstancePrimitive::Type::name () const { + return "instance"; + } + + size_t InstancePrimitive::Type::sizeActive(const char* This) const { + return 1; + } + + size_t InstancePrimitive::Type::sizeTotal(const char* This) const { + return 1; + } + + size_t InstancePrimitive::Type::getBytes(const char* This) const { + return sizeof(InstancePrimitive); + } + + InstancePrimitive::Type InstancePrimitive::type; + + /********************** SubGrid **************************/ + + const char* SubGrid::Type::name () const { + return "subgrid"; + } + + size_t SubGrid::Type::sizeActive(const char* This) const { + return 1; + } + + size_t SubGrid::Type::sizeTotal(const char* This) const { + return 1; + } + + size_t SubGrid::Type::getBytes(const char* This) const { + return sizeof(SubGrid); + } + + SubGrid::Type SubGrid::type; + + /********************** SubGridQBVH4 **************************/ + + template<> + const char* SubGridQBVH4::Type::name () const { + return "SubGridQBVH4"; + } + + template<> + size_t SubGridQBVH4::Type::sizeActive(const char* This) const { + return 1; + } + + template<> + size_t SubGridQBVH4::Type::sizeTotal(const char* This) const { + return 1; + } + + template<> + size_t SubGridQBVH4::Type::getBytes(const char* This) const { + return sizeof(SubGridQBVH4); + } +} diff --git a/thirdparty/embree/kernels/geometry/quad_intersector.h b/thirdparty/embree/kernels/geometry/quad_intersector.h new file mode 100644 index 000000000000..57ff4e60e5f5 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quad_intersector.h @@ -0,0 +1,76 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + namespace isa + { + /*! Intersects a ray with a quad with backface culling + * enabled. The quad v0,v1,v2,v3 is split into two triangles + * v0,v1,v3 and v2,v3,v1. The edge v1,v2 decides which of the two + * triangles gets intersected. */ + template + __forceinline vbool intersect_quad_backface_culling(const vbool& valid0, + const Vec3fa& ray_org, + const Vec3fa& ray_dir, + const float ray_tnear, + const float ray_tfar, + const Vec3vf& quad_v0, + const Vec3vf& quad_v1, + const Vec3vf& quad_v2, + const Vec3vf& quad_v3, + vfloat& u_o, + vfloat& v_o, + vfloat& t_o) + { + /* calculate vertices relative to ray origin */ + vbool valid = valid0; + const Vec3vf O = Vec3vf(ray_org); + const Vec3vf D = Vec3vf(ray_dir); + const Vec3vf va = quad_v0-O; + const Vec3vf vb = quad_v1-O; + const Vec3vf vc = quad_v2-O; + const Vec3vf vd = quad_v3-O; + + const Vec3vf edb = vb-vd; + const vfloat WW = dot(cross(vd,edb),D); + const Vec3vf v0 = select(WW <= 0.0f,va,vc); + const Vec3vf v1 = select(WW <= 0.0f,vb,vd); + const Vec3vf v2 = select(WW <= 0.0f,vd,vb); + + /* calculate edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + + /* perform edge tests */ + const vfloat U = dot(cross(v0,e0),D); + const vfloat V = dot(cross(v1,e1),D); + valid &= max(U,V) <= 0.0f; + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = cross(e1,e0); + const vfloat den = dot(Ng,D); + const vfloat rcpDen = rcp(den); + + /* perform depth test */ + const vfloat t = rcpDen*dot(v0,Ng); + valid &= vfloat(ray_tnear) <= t & t <= vfloat(ray_tfar); + if (unlikely(none(valid))) return false; + + /* avoid division by 0 */ + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + t_o = t; + u_o = U * rcpDen; + v_o = V * rcpDen; + u_o = select(WW <= 0.0f,u_o,1.0f-u_o); + v_o = select(WW <= 0.0f,v_o,1.0f-v_o); + return valid; + } + } +} diff --git a/thirdparty/embree/kernels/geometry/quad_intersector_moeller.h b/thirdparty/embree/kernels/geometry/quad_intersector_moeller.h new file mode 100644 index 000000000000..74e8c7720c4b --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quad_intersector_moeller.h @@ -0,0 +1,566 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "quadv.h" +#include "triangle_intersector_moeller.h" + +namespace embree +{ + namespace isa + { + template + struct QuadHitM + { + __forceinline QuadHitM() {} + + __forceinline QuadHitM(const vbool& valid, + const vfloat& U, + const vfloat& V, + const vfloat& T, + const vfloat& absDen, + const Vec3vf& Ng, + const vbool& flags) + : U(U), V(V), T(T), absDen(absDen), tri_Ng(Ng), valid(valid), flags(flags) {} + + __forceinline void finalize() + { + const vfloat rcpAbsDen = rcp(absDen); + vt = T * rcpAbsDen; + const vfloat u = min(U * rcpAbsDen,1.0f); + const vfloat v = min(V * rcpAbsDen,1.0f); + const vfloat u1 = vfloat(1.0f) - u; + const vfloat v1 = vfloat(1.0f) - v; +#if !defined(__AVX__) || defined(EMBREE_BACKFACE_CULLING) + vu = select(flags,u1,u); + vv = select(flags,v1,v); + vNg = Vec3vf(tri_Ng.x,tri_Ng.y,tri_Ng.z); +#else + const vfloat flip = select(flags,vfloat(-1.0f),vfloat(1.0f)); + vv = select(flags,u1,v); + vu = select(flags,v1,u); + vNg = Vec3vf(flip*tri_Ng.x,flip*tri_Ng.y,flip*tri_Ng.z); +#endif + } + + __forceinline Vec2f uv(const size_t i) + { + const float u = vu[i]; + const float v = vv[i]; + return Vec2f(u,v); + } + + __forceinline float t(const size_t i) { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + private: + vfloat U; + vfloat V; + vfloat T; + vfloat absDen; + Vec3vf tri_Ng; + + public: + vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + + public: + const vbool flags; + }; + + template + struct QuadHitK + { + __forceinline QuadHitK(const vfloat& U, + const vfloat& V, + const vfloat& T, + const vfloat& absDen, + const Vec3vf& Ng, + const vbool& flags) + : U(U), V(V), T(T), absDen(absDen), flags(flags), tri_Ng(Ng) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vfloat rcpAbsDen = rcp(absDen); + const vfloat t = T * rcpAbsDen; + const vfloat u0 = min(U * rcpAbsDen,1.0f); + const vfloat v0 = min(V * rcpAbsDen,1.0f); + const vfloat u1 = vfloat(1.0f) - u0; + const vfloat v1 = vfloat(1.0f) - v0; + const vfloat u = select(flags,u1,u0); + const vfloat v = select(flags,v1,v0); + const Vec3vf Ng(tri_Ng.x,tri_Ng.y,tri_Ng.z); + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat T; + const vfloat absDen; + const vbool flags; + const Vec3vf tri_Ng; + }; + + /* ----------------------------- */ + /* -- single ray intersectors -- */ + /* ----------------------------- */ + + + template + struct QuadMIntersector1MoellerTrumbore; + + /*! Intersects M quads with 1 ray */ + template + struct QuadMIntersector1MoellerTrumbore + { + __forceinline QuadMIntersector1MoellerTrumbore() {} + + __forceinline QuadMIntersector1MoellerTrumbore(const Ray& ray, const void* ptr) {} + + __forceinline void intersect(RayHit& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + MoellerTrumboreHitM hit; + MoellerTrumboreIntersector1 intersector(ray,nullptr); + Intersect1EpilogM epilog(ray,context,geomID,primID); + + /* intersect first triangle */ + if (intersector.intersect(ray,v0,v1,v3,hit)) + epilog(hit.valid,hit); + + /* intersect second triangle */ + if (intersector.intersect(ray,v2,v3,v1,hit)) + { + hit.U = hit.absDen - hit.U; + hit.V = hit.absDen - hit.V; + epilog(hit.valid,hit); + } + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + MoellerTrumboreHitM hit; + MoellerTrumboreIntersector1 intersector(ray,nullptr); + Occluded1EpilogM epilog(ray,context,geomID,primID); + + /* intersect first triangle */ + if (intersector.intersect(ray,v0,v1,v3,hit)) + { + if (epilog(hit.valid,hit)) + return true; + } + + /* intersect second triangle */ + if (intersector.intersect(ray,v2,v3,v1,hit)) + { + hit.U = hit.absDen - hit.U; + hit.V = hit.absDen - hit.V; + if (epilog(hit.valid,hit)) + return true; + } + return false; + } + }; + +#if defined(__AVX512ER__) // KNL + + /*! Intersects 4 quads with 1 ray using AVX512 */ + template + struct QuadMIntersector1MoellerTrumbore<4,filter> + { + __forceinline QuadMIntersector1MoellerTrumbore() {} + + __forceinline QuadMIntersector1MoellerTrumbore(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf16 vtx0(select(0x0f0f,vfloat16(v0.x),vfloat16(v2.x)), + select(0x0f0f,vfloat16(v0.y),vfloat16(v2.y)), + select(0x0f0f,vfloat16(v0.z),vfloat16(v2.z))); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf16 vtx1(vfloat16(v1.x),vfloat16(v1.y),vfloat16(v1.z)); + const Vec3vf16 vtx2(vfloat16(v3.x),vfloat16(v3.y),vfloat16(v3.z)); +#else + const Vec3vf16 vtx1(select(0x0f0f,vfloat16(v1.x),vfloat16(v3.x)), + select(0x0f0f,vfloat16(v1.y),vfloat16(v3.y)), + select(0x0f0f,vfloat16(v1.z),vfloat16(v3.z))); + const Vec3vf16 vtx2(select(0x0f0f,vfloat16(v3.x),vfloat16(v1.x)), + select(0x0f0f,vfloat16(v3.y),vfloat16(v1.y)), + select(0x0f0f,vfloat16(v3.z),vfloat16(v1.z))); +#endif + const vbool16 flags(0xf0f0); + + MoellerTrumboreHitM<16> hit; + MoellerTrumboreIntersector1<16> intersector(ray,nullptr); + if (unlikely(intersector.intersect(ray,vtx0,vtx1,vtx2,hit))) + { + vfloat16 U = hit.U, V = hit.V, absDen = hit.absDen; +#if !defined(EMBREE_BACKFACE_CULLING) + hit.U = select(flags,absDen-V,U); + hit.V = select(flags,absDen-U,V); + hit.vNg *= select(flags,vfloat16(-1.0f),vfloat16(1.0f)); // FIXME: use XOR +#else + hit.U = select(flags,absDen-U,U); + hit.V = select(flags,absDen-V,V); +#endif + if (likely(epilog(hit.valid,hit))) + return true; + } + return false; + } + + __forceinline bool intersect(RayHit& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Intersect1EpilogM<8,16,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Occluded1EpilogM<8,16,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + }; + +#elif defined(__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct QuadMIntersector1MoellerTrumbore<4,filter> + { + __forceinline QuadMIntersector1MoellerTrumbore() {} + + __forceinline QuadMIntersector1MoellerTrumbore(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + MoellerTrumboreHitM<8> hit; + MoellerTrumboreIntersector1<8> intersector(ray,nullptr); + const vbool8 flags(0,0,0,0,1,1,1,1); + if (unlikely(intersector.intersect(ray,vtx0,vtx1,vtx2,hit))) + { + vfloat8 U = hit.U, V = hit.V, absDen = hit.absDen; + +#if !defined(EMBREE_BACKFACE_CULLING) + hit.U = select(flags,absDen-V,U); + hit.V = select(flags,absDen-U,V); + hit.vNg *= select(flags,vfloat8(-1.0f),vfloat8(1.0f)); // FIXME: use XOR +#else + hit.U = select(flags,absDen-U,U); + hit.V = select(flags,absDen-V,V); +#endif + if (unlikely(epilog(hit.valid,hit))) + return true; + } + return false; + } + + __forceinline bool intersect(RayHit& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Intersect1EpilogM<8,8,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Occluded1EpilogM<8,8,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + }; + +#endif + + /* ----------------------------- */ + /* -- ray packet intersectors -- */ + /* ----------------------------- */ + + + struct MoellerTrumboreIntersector1KTriangleM + { + /*! Intersect k'th ray from ray packet of size K with M triangles. */ + template + static __forceinline bool intersect(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + const vbool& flags, + const Epilog& epilog) + { + /* calculate denominator */ + const Vec3vf O = broadcast>(ray.org,k); + const Vec3vf D = broadcast>(ray.dir,k); + const Vec3vf C = Vec3vf(tri_v0) - O; + const Vec3vf R = cross(C,D); + const vfloat den = dot(Vec3vf(tri_Ng),D); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* perform edge tests */ + const vfloat U = dot(R,Vec3vf(tri_e2)) ^ sgnDen; + const vfloat V = dot(R,Vec3vf(tri_e1)) ^ sgnDen; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = (den < vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#else + vbool valid = (den != vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#endif + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(Vec3vf(tri_Ng),C) ^ sgnDen; + valid &= (absDen*vfloat(ray.tnear()[k]) < T) & (T <= absDen*vfloat(ray.tfar[k])); + if (likely(none(valid))) return false; + + /* calculate hit information */ + QuadHitM hit(valid,U,V,T,absDen,tri_Ng,flags); + return epilog(valid,hit); + } + + template + static __forceinline bool intersect1(RayK& ray, + size_t k, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const vbool& flags, + const Epilog& epilog) + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + const Vec3vf Ng = cross(e2,e1); + return intersect(ray,k,v0,e1,e2,Ng,flags,epilog); + } + }; + + template + struct QuadMIntersectorKMoellerTrumboreBase + { + __forceinline QuadMIntersectorKMoellerTrumboreBase(const vbool& valid, const RayK& ray) {} + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + const vbool& flags, + const Epilog& epilog) const + { + /* calculate denominator */ + vbool valid = valid0; + const Vec3vf C = tri_v0 - ray.org; + const Vec3vf R = cross(C,ray.dir); + const vfloat den = dot(tri_Ng,ray.dir); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* test against edge p2 p0 */ + const vfloat U = dot(R,tri_e2) ^ sgnDen; + valid &= U >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p0 p1 */ + const vfloat V = dot(R,tri_e1) ^ sgnDen; + valid &= V >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p1 p2 */ + const vfloat W = absDen-U-V; + valid &= W >= 0.0f; + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(tri_Ng,C) ^ sgnDen; + valid &= (absDen*ray.tnear() < T) & (T <= absDen*ray.tfar); + if (unlikely(none(valid))) return false; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + valid &= den < vfloat(zero); + if (unlikely(none(valid))) return false; +#else + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; +#endif + + /* calculate hit information */ + QuadHitK hit(U,V,T,absDen,tri_Ng,flags); + return epilog(valid,hit); + } + + /*! Intersects K rays with one of M quads. */ + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const vbool& flags, + const Epilog& epilog) const + { + const Vec3vf e1 = tri_v0-tri_v1; + const Vec3vf e2 = tri_v2-tri_v0; + const Vec3vf Ng = cross(e2,e1); + return intersectK(valid0,ray,tri_v0,e1,e2,Ng,flags,epilog); + } + + /*! Intersects K rays with one of M quads. */ + template + __forceinline bool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Vec3vf& v3, + const Epilog& epilog) const + { + intersectK(valid0,ray,v0,v1,v3,vbool(false),epilog); + if (none(valid0)) return true; + intersectK(valid0,ray,v2,v3,v1,vbool(true ),epilog); + return none(valid0); + } + }; + + template + struct QuadMIntersectorKMoellerTrumbore : public QuadMIntersectorKMoellerTrumboreBase + { + __forceinline QuadMIntersectorKMoellerTrumbore(const vbool& valid, const RayK& ray) + : QuadMIntersectorKMoellerTrumboreBase(valid,ray) {} + + __forceinline void intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + Intersect1KEpilogM epilog(ray,k,context,geomID,primID); + MoellerTrumboreIntersector1KTriangleM::intersect1(ray,k,v0,v1,v3,vbool(false),epilog); + MoellerTrumboreIntersector1KTriangleM::intersect1(ray,k,v2,v3,v1,vbool(true ),epilog); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + Occluded1KEpilogM epilog(ray,k,context,geomID,primID); + if (MoellerTrumboreIntersector1KTriangleM::intersect1(ray,k,v0,v1,v3,vbool(false),epilog)) return true; + if (MoellerTrumboreIntersector1KTriangleM::intersect1(ray,k,v2,v3,v1,vbool(true ),epilog)) return true; + return false; + } + }; + + +#if defined(__AVX512ER__) // KNL + + /*! Intersects 4 quads with 1 ray using AVX512 */ + template + struct QuadMIntersectorKMoellerTrumbore<4,K,filter> : public QuadMIntersectorKMoellerTrumboreBase<4,K,filter> + { + __forceinline QuadMIntersectorKMoellerTrumbore(const vbool& valid, const RayK& ray) + : QuadMIntersectorKMoellerTrumboreBase<4,K,filter>(valid,ray) {} + + template + __forceinline bool intersect1(RayK& ray, size_t k, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf16 vtx0(select(0x0f0f,vfloat16(v0.x),vfloat16(v2.x)), + select(0x0f0f,vfloat16(v0.y),vfloat16(v2.y)), + select(0x0f0f,vfloat16(v0.z),vfloat16(v2.z))); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf16 vtx1(vfloat16(v1.x),vfloat16(v1.y),vfloat16(v1.z)); + const Vec3vf16 vtx2(vfloat16(v3.x),vfloat16(v3.y),vfloat16(v3.z)); +#else + const Vec3vf16 vtx1(select(0x0f0f,vfloat16(v1.x),vfloat16(v3.x)), + select(0x0f0f,vfloat16(v1.y),vfloat16(v3.y)), + select(0x0f0f,vfloat16(v1.z),vfloat16(v3.z))); + const Vec3vf16 vtx2(select(0x0f0f,vfloat16(v3.x),vfloat16(v1.x)), + select(0x0f0f,vfloat16(v3.y),vfloat16(v1.y)), + select(0x0f0f,vfloat16(v3.z),vfloat16(v1.z))); +#endif + const vbool16 flags(0xf0f0); + return MoellerTrumboreIntersector1KTriangleM::intersect1(ray,k,vtx0,vtx1,vtx2,flags,epilog); + } + + __forceinline bool intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Intersect1KEpilogM<8,16,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Occluded1KEpilogM<8,16,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + }; + +#elif defined(__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct QuadMIntersectorKMoellerTrumbore<4,K,filter> : public QuadMIntersectorKMoellerTrumboreBase<4,K,filter> + { + __forceinline QuadMIntersectorKMoellerTrumbore(const vbool& valid, const RayK& ray) + : QuadMIntersectorKMoellerTrumboreBase<4,K,filter>(valid,ray) {} + + template + __forceinline bool intersect1(RayK& ray, size_t k, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + const vbool8 flags(0,0,0,0,1,1,1,1); + return MoellerTrumboreIntersector1KTriangleM::intersect1(ray,k,vtx0,vtx1,vtx2,flags,epilog); + } + + __forceinline bool intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Intersect1KEpilogM<8,8,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Occluded1KEpilogM<8,8,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + }; + +#endif + } +} diff --git a/thirdparty/embree/kernels/geometry/quad_intersector_pluecker.h b/thirdparty/embree/kernels/geometry/quad_intersector_pluecker.h new file mode 100644 index 000000000000..7ca3aed0a040 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quad_intersector_pluecker.h @@ -0,0 +1,529 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "quad_intersector_moeller.h" + +/*! Modified Pluecker ray/triangle intersector. The test first shifts + * the ray origin into the origin of the coordinate system and then + * uses Pluecker coordinates for the intersection. Due to the shift, + * the Pluecker coordinate calculation simplifies and the tests get + * numerically stable. The edge equations are watertight along the + * edge for neighboring triangles. */ + +namespace embree +{ + namespace isa + { + template + struct QuadHitPlueckerM + { + __forceinline QuadHitPlueckerM() {} + + __forceinline QuadHitPlueckerM(const vbool& valid, + const vfloat& U, + const vfloat& V, + const vfloat& UVW, + const vfloat& t, + const Vec3vf& Ng, + const vbool& flags) + : U(U), V(V), UVW(UVW), tri_Ng(Ng), valid(valid), vt(t), flags(flags) {} + + __forceinline void finalize() + { + const vbool invalid = abs(UVW) < min_rcp_input; + const vfloat rcpUVW = select(invalid,vfloat(0.0f),rcp(UVW)); + const vfloat u = min(U * rcpUVW,1.0f); + const vfloat v = min(V * rcpUVW,1.0f); + const vfloat u1 = vfloat(1.0f) - u; + const vfloat v1 = vfloat(1.0f) - v; +#if !defined(__AVX__) || defined(EMBREE_BACKFACE_CULLING) + vu = select(flags,u1,u); + vv = select(flags,v1,v); + vNg = Vec3vf(tri_Ng.x,tri_Ng.y,tri_Ng.z); +#else + const vfloat flip = select(flags,vfloat(-1.0f),vfloat(1.0f)); + vv = select(flags,u1,v); + vu = select(flags,v1,u); + vNg = Vec3vf(flip*tri_Ng.x,flip*tri_Ng.y,flip*tri_Ng.z); +#endif + } + + __forceinline Vec2f uv(const size_t i) + { + const float u = vu[i]; + const float v = vv[i]; + return Vec2f(u,v); + } + + __forceinline float t(const size_t i) { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + private: + vfloat U; + vfloat V; + vfloat UVW; + Vec3vf tri_Ng; + + public: + vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + + public: + const vbool flags; + }; + + template + struct QuadHitPlueckerK + { + __forceinline QuadHitPlueckerK(const vfloat& U, + const vfloat& V, + const vfloat& UVW, + const vfloat& t, + const Vec3vf& Ng, + const vbool& flags) + : U(U), V(V), UVW(UVW), t(t), flags(flags), tri_Ng(Ng) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vbool invalid = abs(UVW) < min_rcp_input; + const vfloat rcpUVW = select(invalid,vfloat(0.0f),rcp(UVW)); + const vfloat u0 = min(U * rcpUVW,1.0f); + const vfloat v0 = min(V * rcpUVW,1.0f); + const vfloat u1 = vfloat(1.0f) - u0; + const vfloat v1 = vfloat(1.0f) - v0; + const vfloat u = select(flags,u1,u0); + const vfloat v = select(flags,v1,v0); + const Vec3vf Ng(tri_Ng.x,tri_Ng.y,tri_Ng.z); + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat UVW; + const vfloat t; + const vbool flags; + const Vec3vf tri_Ng; + }; + + struct PlueckerIntersectorTriangle1 + { + template + static __forceinline bool intersect(Ray& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const vbool& flags, + const Epilog& epilog) + { + /* calculate vertices relative to ray origin */ + const Vec3vf O = Vec3vf((Vec3fa)ray.org); + const Vec3vf D = Vec3vf((Vec3fa)ray.dir); + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(cross(e0,v2+v0),D); + const vfloat V = dot(cross(e1,v0+v1),D); + const vfloat W = dot(cross(e2,v1+v2),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = max(U,V,W) <= eps; +#else + vbool valid = (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Ng,D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Ng)); + const vfloat t = rcp(den)*T; + valid &= vfloat(ray.tnear()) <= t & t <= vfloat(ray.tfar); + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + QuadHitPlueckerM hit(valid,U,V,UVW,t,Ng,flags); + return epilog(valid,hit); + } + }; + + /*! Intersects M quads with 1 ray */ + template + struct QuadMIntersector1Pluecker + { + __forceinline QuadMIntersector1Pluecker() {} + + __forceinline QuadMIntersector1Pluecker(const Ray& ray, const void* ptr) {} + + __forceinline void intersect(RayHit& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + Intersect1EpilogM epilog(ray,context,geomID,primID); + PlueckerIntersectorTriangle1::intersect(ray,v0,v1,v3,vbool(false),epilog); + PlueckerIntersectorTriangle1::intersect(ray,v2,v3,v1,vbool(true),epilog); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + Occluded1EpilogM epilog(ray,context,geomID,primID); + if (PlueckerIntersectorTriangle1::intersect(ray,v0,v1,v3,vbool(false),epilog)) return true; + if (PlueckerIntersectorTriangle1::intersect(ray,v2,v3,v1,vbool(true ),epilog)) return true; + return false; + } + }; + +#if defined(__AVX512ER__) // KNL + + /*! Intersects 4 quads with 1 ray using AVX512 */ + template + struct QuadMIntersector1Pluecker<4,filter> + { + __forceinline QuadMIntersector1Pluecker() {} + + __forceinline QuadMIntersector1Pluecker(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf16 vtx0(select(0x0f0f,vfloat16(v0.x),vfloat16(v2.x)), + select(0x0f0f,vfloat16(v0.y),vfloat16(v2.y)), + select(0x0f0f,vfloat16(v0.z),vfloat16(v2.z))); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf16 vtx1(vfloat16(v1.x),vfloat16(v1.y),vfloat16(v1.z)); + const Vec3vf16 vtx2(vfloat16(v3.x),vfloat16(v3.y),vfloat16(v3.z)); +#else + const Vec3vf16 vtx1(select(0x0f0f,vfloat16(v1.x),vfloat16(v3.x)), + select(0x0f0f,vfloat16(v1.y),vfloat16(v3.y)), + select(0x0f0f,vfloat16(v1.z),vfloat16(v3.z))); + const Vec3vf16 vtx2(select(0x0f0f,vfloat16(v3.x),vfloat16(v1.x)), + select(0x0f0f,vfloat16(v3.y),vfloat16(v1.y)), + select(0x0f0f,vfloat16(v3.z),vfloat16(v1.z))); +#endif + const vbool16 flags(0xf0f0); + return PlueckerIntersectorTriangle1::intersect(ray,vtx0,vtx1,vtx2,flags,epilog); + } + + __forceinline bool intersect(RayHit& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Intersect1EpilogM<8,16,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Occluded1EpilogM<8,16,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + }; + +#elif defined(__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct QuadMIntersector1Pluecker<4,filter> + { + __forceinline QuadMIntersector1Pluecker() {} + + __forceinline QuadMIntersector1Pluecker(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + const vbool8 flags(0,0,0,0,1,1,1,1); + return PlueckerIntersectorTriangle1::intersect(ray,vtx0,vtx1,vtx2,flags,epilog); + } + + __forceinline bool intersect(RayHit& ray, IntersectContext* context, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Intersect1EpilogM<8,8,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect(ray,v0,v1,v2,v3,Occluded1EpilogM<8,8,filter>(ray,context,vuint8(geomID),vuint8(primID))); + } + }; + +#endif + + + /* ----------------------------- */ + /* -- ray packet intersectors -- */ + /* ----------------------------- */ + + struct PlueckerIntersector1KTriangleM + { + /*! Intersect k'th ray from ray packet of size K with M triangles. */ + template + static __forceinline bool intersect1(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const vbool& flags, + const Epilog& epilog) + { + /* calculate vertices relative to ray origin */ + const Vec3vf O = broadcast>(ray.org,k); + const Vec3vf D = broadcast>(ray.dir,k); + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(cross(e0,v2+v0),D); + const vfloat V = dot(cross(e1,v0+v1),D); + const vfloat W = dot(cross(e2,v1+v2),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = max(U,V,W) <= eps; +#else + vbool valid = (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Ng,D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Ng)); + const vfloat t = rcp(den)*T; + valid &= vfloat(ray.tnear()[k]) <= t & t <= vfloat(ray.tfar[k]); + if (unlikely(none(valid))) return false; + + /* avoid division by 0 */ + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + QuadHitPlueckerM hit(valid,U,V,UVW,t,Ng,flags); + return epilog(valid,hit); + } + }; + + template + struct QuadMIntersectorKPlueckerBase + { + __forceinline QuadMIntersectorKPlueckerBase(const vbool& valid, const RayK& ray) {} + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const vbool& flags, + const Epilog& epilog) const + { + /* calculate vertices relative to ray origin */ + vbool valid = valid0; + const Vec3vf O = ray.org; + const Vec3vf D = ray.dir; + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(Vec3vf(cross(e0,v2+v0)),D); + const vfloat V = dot(Vec3vf(cross(e1,v0+v1)),D); + const vfloat W = dot(Vec3vf(cross(e2,v1+v2)),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + valid &= max(U,V,W) <= eps; +#else + valid &= (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Vec3vf(Ng),D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Vec3vf(Ng))); + const vfloat t = rcp(den)*T; + valid &= ray.tnear() <= t & t <= ray.tfar; + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* calculate hit information */ + QuadHitPlueckerK hit(U,V,UVW,t,Ng,flags); + return epilog(valid,hit); + } + + /*! Intersects K rays with one of M quads. */ + template + __forceinline bool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Vec3vf& v3, + const Epilog& epilog) const + { + intersectK(valid0,ray,v0,v1,v3,vbool(false),epilog); + if (none(valid0)) return true; + intersectK(valid0,ray,v2,v3,v1,vbool(true ),epilog); + return none(valid0); + } + }; + + template + struct QuadMIntersectorKPluecker : public QuadMIntersectorKPlueckerBase + { + __forceinline QuadMIntersectorKPluecker(const vbool& valid, const RayK& ray) + : QuadMIntersectorKPlueckerBase(valid,ray) {} + + __forceinline void intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + Intersect1KEpilogM epilog(ray,k,context,geomID,primID); + PlueckerIntersector1KTriangleM::intersect1(ray,k,v0,v1,v3,vbool(false),epilog); + PlueckerIntersector1KTriangleM::intersect1(ray,k,v2,v3,v1,vbool(true ),epilog); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const vuint& geomID, const vuint& primID) const + { + Occluded1KEpilogM epilog(ray,k,context,geomID,primID); + if (PlueckerIntersector1KTriangleM::intersect1(ray,k,v0,v1,v3,vbool(false),epilog)) return true; + if (PlueckerIntersector1KTriangleM::intersect1(ray,k,v2,v3,v1,vbool(true ),epilog)) return true; + return false; + } + }; + +#if defined(__AVX512ER__) // KNL + + /*! Intersects 4 quads with 1 ray using AVX512 */ + template + struct QuadMIntersectorKPluecker<4,K,filter> : public QuadMIntersectorKPlueckerBase<4,K,filter> + { + __forceinline QuadMIntersectorKPluecker(const vbool& valid, const RayK& ray) + : QuadMIntersectorKPlueckerBase<4,K,filter>(valid,ray) {} + + template + __forceinline bool intersect1(RayK& ray, size_t k, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf16 vtx0(select(0x0f0f,vfloat16(v0.x),vfloat16(v2.x)), + select(0x0f0f,vfloat16(v0.y),vfloat16(v2.y)), + select(0x0f0f,vfloat16(v0.z),vfloat16(v2.z))); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf16 vtx1(vfloat16(v1.x),vfloat16(v1.y),vfloat16(v1.z)); + const Vec3vf16 vtx2(vfloat16(v3.x),vfloat16(v3.y),vfloat16(v3.z)); +#else + const Vec3vf16 vtx1(select(0x0f0f,vfloat16(v1.x),vfloat16(v3.x)), + select(0x0f0f,vfloat16(v1.y),vfloat16(v3.y)), + select(0x0f0f,vfloat16(v1.z),vfloat16(v3.z))); + const Vec3vf16 vtx2(select(0x0f0f,vfloat16(v3.x),vfloat16(v1.x)), + select(0x0f0f,vfloat16(v3.y),vfloat16(v1.y)), + select(0x0f0f,vfloat16(v3.z),vfloat16(v1.z))); +#endif + + const vbool16 flags(0xf0f0); + return PlueckerIntersector1KTriangleM::intersect1(ray,k,vtx0,vtx1,vtx2,flags,epilog); + } + + __forceinline bool intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Intersect1KEpilogM<8,16,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Occluded1KEpilogM<8,16,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + }; + +#elif defined(__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct QuadMIntersectorKPluecker<4,K,filter> : public QuadMIntersectorKPlueckerBase<4,K,filter> + { + __forceinline QuadMIntersectorKPluecker(const vbool& valid, const RayK& ray) + : QuadMIntersectorKPlueckerBase<4,K,filter>(valid,ray) {} + + template + __forceinline bool intersect1(RayK& ray, size_t k, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); + const vbool8 flags(0,0,0,0,1,1,1,1); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + return PlueckerIntersector1KTriangleM::intersect1(ray,k,vtx0,vtx1,vtx2,flags,epilog); + } + + __forceinline bool intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Intersect1KEpilogM<8,8,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const vuint4& geomID, const vuint4& primID) const + { + return intersect1(ray,k,v0,v1,v2,v3,Occluded1KEpilogM<8,8,K,filter>(ray,k,context,vuint8(geomID),vuint8(primID))); + } + }; + +#endif + } +} diff --git a/thirdparty/embree/kernels/geometry/quadi.h b/thirdparty/embree/kernels/geometry/quadi.h new file mode 100644 index 000000000000..741ec519ab4c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quadi.h @@ -0,0 +1,483 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "../common/scene.h" + +namespace embree +{ + /* Stores M quads from an indexed face set */ + template + struct QuadMi + { + /* Virtual interface to query information about the quad type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored quads */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline QuadMi() { } + + /* Construction from vertices and IDs */ + __forceinline QuadMi(const vuint& v0, + const vuint& v1, + const vuint& v2, + const vuint& v3, + const vuint& geomIDs, + const vuint& primIDs) +#if defined(EMBREE_COMPACT_POLYS) + : geomIDs(geomIDs), primIDs(primIDs) {} +#else + : v0_(v0),v1_(v1), v2_(v2), v3_(v3), geomIDs(geomIDs), primIDs(primIDs) {} +#endif + + /* Returns a mask that tells which quads are valid */ + __forceinline vbool valid() const { return primIDs != vuint(-1); } + + /* Returns if the specified quad is valid */ + __forceinline bool valid(const size_t i) const { assert(i& geomID() { return geomIDs; } + __forceinline const vuint& geomID() const { return geomIDs; } + __forceinline unsigned int geomID(const size_t i) const { assert(i& primID() { return primIDs; } + __forceinline const vuint& primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(iget(geomID(i)); + bounds.extend(mesh->bounds(primID(i),itime)); + } + return bounds; + } + + /* Calculate the linear bounds of the primitive */ + __forceinline LBBox3fa linearBounds(const Scene* const scene, const size_t itime) { + return LBBox3fa(bounds(scene,itime+0),bounds(scene,itime+1)); + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, size_t itime, size_t numTimeSteps) + { + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID(i)); + allBounds.extend(mesh->linearBounds(primID(i), itime, numTimeSteps)); + } + return allBounds; + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, const BBox1f time_range) + { + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID(i)); + allBounds.extend(mesh->linearBounds(primID(i), time_range)); + } + return allBounds; + } + + /* Fill quad from quad list */ + template + __forceinline void fill(const PrimRefT* prims, size_t& begin, size_t end, Scene* scene) + { + vuint geomID = -1, primID = -1; + const PrimRefT* prim = &prims[begin]; + vuint v0 = zero, v1 = zero, v2 = zero, v3 = zero; + + for (size_t i=0; igeomID(); + primID[i] = prim->primID(); +#if !defined(EMBREE_COMPACT_POLYS) + const QuadMesh* mesh = scene->get(prim->geomID()); + const QuadMesh::Quad& q = mesh->quad(prim->primID()); + unsigned int_stride = mesh->vertices0.getStride()/4; + v0[i] = q.v[0] * int_stride; + v1[i] = q.v[1] * int_stride; + v2[i] = q.v[2] * int_stride; + v3[i] = q.v[3] * int_stride; +#endif + begin++; + } else { + assert(i); + if (likely(i > 0)) { + geomID[i] = geomID[0]; // always valid geomIDs + primID[i] = -1; // indicates invalid data + v0[i] = v0[0]; + v1[i] = v0[0]; + v2[i] = v0[0]; + v3[i] = v0[0]; + } + } + if (begin( " +#if !defined(EMBREE_COMPACT_POLYS) + << "v0 = " << quad.v0_ << ", v1 = " << quad.v1_ << ", v2 = " << quad.v2_ << ", v3 = " << quad.v3_ << ", " +#endif + << "geomID = " << quad.geomIDs << ", primID = " << quad.primIDs << " )"; + } + + protected: +#if !defined(EMBREE_COMPACT_POLYS) + vuint v0_; // 4 byte offset of 1st vertex + vuint v1_; // 4 byte offset of 2nd vertex + vuint v2_; // 4 byte offset of 3rd vertex + vuint v3_; // 4 byte offset of 4th vertex +#endif + vuint geomIDs; // geometry ID of mesh + vuint primIDs; // primitive ID of primitive inside mesh + }; + + namespace isa + { + + template + struct QuadMi : public embree::QuadMi + { +#if !defined(EMBREE_COMPACT_POLYS) + using embree::QuadMi::v0_; + using embree::QuadMi::v1_; + using embree::QuadMi::v2_; + using embree::QuadMi::v3_; +#endif + using embree::QuadMi::geomIDs; + using embree::QuadMi::primIDs; + using embree::QuadMi::geomID; + using embree::QuadMi::primID; + using embree::QuadMi::valid; + + template + __forceinline Vec3f getVertex(const size_t index, const Scene *const scene) const + { +#if defined(EMBREE_COMPACT_POLYS) + const QuadMesh* mesh = scene->get(geomID(index)); + const QuadMesh::Quad& quad = mesh->quad(primID(index)); + return (Vec3f) mesh->vertices[0][quad.v[vid]]; +#else + const vuint& v = getVertexOffset(); + const float* vertices = scene->vertices[geomID(index)]; + return (Vec3f&) vertices[v[index]]; +#endif + } + + template + __forceinline Vec3 getVertex(const size_t index, const Scene *const scene, const size_t itime, const T& ftime) const + { +#if defined(EMBREE_COMPACT_POLYS) + const QuadMesh* mesh = scene->get(geomID(index)); + const QuadMesh::Quad& quad = mesh->quad(primID(index)); + const Vec3fa v0 = mesh->vertices[itime+0][quad.v[vid]]; + const Vec3fa v1 = mesh->vertices[itime+1][quad.v[vid]]; +#else + const vuint& v = getVertexOffset(); + const QuadMesh* mesh = scene->get(geomID(index)); + const float* vertices0 = (const float*) mesh->vertexPtr(0,itime+0); + const float* vertices1 = (const float*) mesh->vertexPtr(0,itime+1); + const Vec3fa v0 = Vec3fa::loadu(vertices0+v[index]); + const Vec3fa v1 = Vec3fa::loadu(vertices1+v[index]); +#endif + const Vec3 p0(v0.x,v0.y,v0.z); + const Vec3 p1(v1.x,v1.y,v1.z); + return lerp(p0,p1,ftime); + } + + template + __forceinline Vec3 getVertex(const vbool& valid, const size_t index, const Scene *const scene, const vint& itime, const T& ftime) const + { + Vec3 p0, p1; + const QuadMesh* mesh = scene->get(geomID(index)); + + for (size_t mask=movemask(valid), i=bsf(mask); mask; mask=btc(mask,i), i=bsf(mask)) + { +#if defined(EMBREE_COMPACT_POLYS) + const QuadMesh::Quad& quad = mesh->quad(primID(index)); + const Vec3fa v0 = mesh->vertices[itime[i]+0][quad.v[vid]]; + const Vec3fa v1 = mesh->vertices[itime[i]+1][quad.v[vid]]; +#else + const vuint& v = getVertexOffset(); + const float* vertices0 = (const float*) mesh->vertexPtr(0,itime[i]+0); + const float* vertices1 = (const float*) mesh->vertexPtr(0,itime[i]+1); + const Vec3fa v0 = Vec3fa::loadu(vertices0+v[index]); + const Vec3fa v1 = Vec3fa::loadu(vertices1+v[index]); +#endif + p0.x[i] = v0.x; p0.y[i] = v0.y; p0.z[i] = v0.z; + p1.x[i] = v1.x; p1.y[i] = v1.y; p1.z[i] = v1.z; + } + return (T(one)-ftime)*p0 + ftime*p1; + } + + struct Quad { + vfloat4 v0,v1,v2,v3; + }; + +#if defined(EMBREE_COMPACT_POLYS) + + __forceinline Quad loadQuad(const int i, const Scene* const scene) const + { + const unsigned int geomID = geomIDs[i]; + const unsigned int primID = primIDs[i]; + if (unlikely(primID == -1)) return { zero, zero, zero, zero }; + const QuadMesh* mesh = scene->get(geomID); + const QuadMesh::Quad& quad = mesh->quad(primID); + const vfloat4 v0 = (vfloat4) mesh->vertices0[quad.v[0]]; + const vfloat4 v1 = (vfloat4) mesh->vertices0[quad.v[1]]; + const vfloat4 v2 = (vfloat4) mesh->vertices0[quad.v[2]]; + const vfloat4 v3 = (vfloat4) mesh->vertices0[quad.v[3]]; + return { v0, v1, v2, v3 }; + } + + __forceinline Quad loadQuad(const int i, const int itime, const Scene* const scene) const + { + const unsigned int geomID = geomIDs[i]; + const unsigned int primID = primIDs[i]; + if (unlikely(primID == -1)) return { zero, zero, zero, zero }; + const QuadMesh* mesh = scene->get(geomID); + const QuadMesh::Quad& quad = mesh->quad(primID); + const vfloat4 v0 = (vfloat4) mesh->vertices[itime][quad.v[0]]; + const vfloat4 v1 = (vfloat4) mesh->vertices[itime][quad.v[1]]; + const vfloat4 v2 = (vfloat4) mesh->vertices[itime][quad.v[2]]; + const vfloat4 v3 = (vfloat4) mesh->vertices[itime][quad.v[3]]; + return { v0, v1, v2, v3 }; + } + +#else + + __forceinline Quad loadQuad(const int i, const Scene* const scene) const + { + const float* vertices = scene->vertices[geomID(i)]; + const vfloat4 v0 = vfloat4::loadu(vertices + v0_[i]); + const vfloat4 v1 = vfloat4::loadu(vertices + v1_[i]); + const vfloat4 v2 = vfloat4::loadu(vertices + v2_[i]); + const vfloat4 v3 = vfloat4::loadu(vertices + v3_[i]); + return { v0, v1, v2, v3 }; + } + + __forceinline Quad loadQuad(const int i, const int itime, const Scene* const scene) const + { + const unsigned int geomID = geomIDs[i]; + const QuadMesh* mesh = scene->get(geomID); + const float* vertices = (const float*) mesh->vertexPtr(0,itime); + const vfloat4 v0 = vfloat4::loadu(vertices + v0_[i]); + const vfloat4 v1 = vfloat4::loadu(vertices + v1_[i]); + const vfloat4 v2 = vfloat4::loadu(vertices + v2_[i]); + const vfloat4 v3 = vfloat4::loadu(vertices + v3_[i]); + return { v0, v1, v2, v3 }; + } + +#endif + + /* Gather the quads */ + __forceinline void gather(Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + Vec3vf& p3, + const Scene *const scene) const; + +#if defined(__AVX512F__) + __forceinline void gather(Vec3vf16& p0, + Vec3vf16& p1, + Vec3vf16& p2, + Vec3vf16& p3, + const Scene *const scene) const; +#endif + + template +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER < 2000) // workaround for compiler bug in ICC 2019 + __noinline +#else + __forceinline +#endif + void gather(const vbool& valid, + Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + Vec3vf& p3, + const size_t index, + const Scene* const scene, + const vfloat& time) const + { + const QuadMesh* mesh = scene->get(geomID(index)); + + vfloat ftime; + const vint itime = mesh->timeSegment(time, ftime); + + const size_t first = bsf(movemask(valid)); + if (likely(all(valid,itime[first] == itime))) + { + p0 = getVertex<0>(index, scene, itime[first], ftime); + p1 = getVertex<1>(index, scene, itime[first], ftime); + p2 = getVertex<2>(index, scene, itime[first], ftime); + p3 = getVertex<3>(index, scene, itime[first], ftime); + } + else + { + p0 = getVertex<0>(valid, index, scene, itime, ftime); + p1 = getVertex<1>(valid, index, scene, itime, ftime); + p2 = getVertex<2>(valid, index, scene, itime, ftime); + p3 = getVertex<3>(valid, index, scene, itime, ftime); + } + } + + __forceinline void gather(Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + Vec3vf& p3, + const QuadMesh* mesh, + const Scene *const scene, + const int itime) const; + + __forceinline void gather(Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + Vec3vf& p3, + const Scene *const scene, + const float time) const; + + /* Updates the primitive */ + __forceinline BBox3fa update(QuadMesh* mesh) + { + BBox3fa bounds = empty; + for (size_t i=0; iquad(primId); + const Vec3fa p0 = mesh->vertex(q.v[0]); + const Vec3fa p1 = mesh->vertex(q.v[1]); + const Vec3fa p2 = mesh->vertex(q.v[2]); + const Vec3fa p3 = mesh->vertex(q.v[3]); + bounds.extend(merge(BBox3fa(p0),BBox3fa(p1),BBox3fa(p2),BBox3fa(p3))); + } + return bounds; + } + + private: +#if !defined(EMBREE_COMPACT_POLYS) + template const vuint& getVertexOffset() const; +#endif + }; + +#if !defined(EMBREE_COMPACT_POLYS) + template<> template<> __forceinline const vuint<4>& QuadMi<4>::getVertexOffset<0>() const { return v0_; } + template<> template<> __forceinline const vuint<4>& QuadMi<4>::getVertexOffset<1>() const { return v1_; } + template<> template<> __forceinline const vuint<4>& QuadMi<4>::getVertexOffset<2>() const { return v2_; } + template<> template<> __forceinline const vuint<4>& QuadMi<4>::getVertexOffset<3>() const { return v3_; } +#endif + + template<> + __forceinline void QuadMi<4>::gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const Scene *const scene) const + { + prefetchL1(((char*)this)+0*64); + prefetchL1(((char*)this)+1*64); + const Quad tri0 = loadQuad(0,scene); + const Quad tri1 = loadQuad(1,scene); + const Quad tri2 = loadQuad(2,scene); + const Quad tri3 = loadQuad(3,scene); + transpose(tri0.v0,tri1.v0,tri2.v0,tri3.v0,p0.x,p0.y,p0.z); + transpose(tri0.v1,tri1.v1,tri2.v1,tri3.v1,p1.x,p1.y,p1.z); + transpose(tri0.v2,tri1.v2,tri2.v2,tri3.v2,p2.x,p2.y,p2.z); + transpose(tri0.v3,tri1.v3,tri2.v3,tri3.v3,p3.x,p3.y,p3.z); + } + + template<> + __forceinline void QuadMi<4>::gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const QuadMesh* mesh, + const Scene *const scene, + const int itime) const + { + // FIXME: for trianglei there all geometries are identical, is this the case here too? + + const Quad tri0 = loadQuad(0,itime,scene); + const Quad tri1 = loadQuad(1,itime,scene); + const Quad tri2 = loadQuad(2,itime,scene); + const Quad tri3 = loadQuad(3,itime,scene); + transpose(tri0.v0,tri1.v0,tri2.v0,tri3.v0,p0.x,p0.y,p0.z); + transpose(tri0.v1,tri1.v1,tri2.v1,tri3.v1,p1.x,p1.y,p1.z); + transpose(tri0.v2,tri1.v2,tri2.v2,tri3.v2,p2.x,p2.y,p2.z); + transpose(tri0.v3,tri1.v3,tri2.v3,tri3.v3,p3.x,p3.y,p3.z); + } + + template<> + __forceinline void QuadMi<4>::gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const Scene *const scene, + const float time) const + { + const QuadMesh* mesh = scene->get(geomID(0)); // in mblur mode all geometries are identical + + float ftime; + const int itime = mesh->timeSegment(time, ftime); + + Vec3vf4 a0,a1,a2,a3; gather(a0,a1,a2,a3,mesh,scene,itime); + Vec3vf4 b0,b1,b2,b3; gather(b0,b1,b2,b3,mesh,scene,itime+1); + p0 = lerp(a0,b0,vfloat4(ftime)); + p1 = lerp(a1,b1,vfloat4(ftime)); + p2 = lerp(a2,b2,vfloat4(ftime)); + p3 = lerp(a3,b3,vfloat4(ftime)); + } + } + + template + typename QuadMi::Type QuadMi::type; + + typedef QuadMi<4> Quad4i; +} diff --git a/thirdparty/embree/kernels/geometry/quadi_intersector.h b/thirdparty/embree/kernels/geometry/quadi_intersector.h new file mode 100644 index 000000000000..96cf7f1ca2d3 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quadi_intersector.h @@ -0,0 +1,350 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "quadi.h" +#include "quad_intersector_moeller.h" +#include "quad_intersector_pluecker.h" + +namespace embree +{ + namespace isa + { + /*! Intersects M quads with 1 ray */ + template + struct QuadMiIntersector1Moeller + { + typedef QuadMi Primitive; + typedef QuadMIntersector1MoellerTrumbore Precalculations; + + /*! Intersect a ray with the M quads and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + pre.intersect(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of M quads. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + return pre.occluded(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& quad) + { + return PrimitivePointQuery1::pointQuery(query, context, quad); + } + }; + + /*! Intersects M triangles with K rays. */ + template + struct QuadMiIntersectorKMoeller + { + typedef QuadMi Primitive; + typedef QuadMIntersectorKMoellerTrumbore Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const QuadMi& quad) + { + Scene* scene = context->scene; + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf p0 = quad.template getVertex<0>(i,scene); + const Vec3vf p1 = quad.template getVertex<1>(i,scene); + const Vec3vf p2 = quad.template getVertex<2>(i,scene); + const Vec3vf p3 = quad.template getVertex<3>(i,scene); + pre.intersectK(valid_i,ray,p0,p1,p2,p3,IntersectKEpilogM(ray,context,quad.geomID(),quad.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const QuadMi& quad) + { + Scene* scene = context->scene; + vbool valid0 = valid_i; + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf p0 = quad.template getVertex<0>(i,scene); + const Vec3vf p1 = quad.template getVertex<1>(i,scene); + const Vec3vf p2 = quad.template getVertex<2>(i,scene); + const Vec3vf p3 = quad.template getVertex<3>(i,scene); + if (pre.intersectK(valid0,ray,p0,p1,p2,p3,OccludedKEpilogM(valid0,ray,context,quad.geomID(),quad.primID(),i))) + break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf4 v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + pre.intersect1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf4 v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + }; + + /*! Intersects M quads with 1 ray */ + template + struct QuadMiIntersector1Pluecker + { + typedef QuadMi Primitive; + typedef QuadMIntersector1Pluecker Precalculations; + + /*! Intersect a ray with the M quads and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + pre.intersect(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of M quads. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + return pre.occluded(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& quad) + { + return PrimitivePointQuery1::pointQuery(query, context, quad); + } + }; + + /*! Intersects M triangles with K rays. */ + template + struct QuadMiIntersectorKPluecker + { + typedef QuadMi Primitive; + typedef QuadMIntersectorKPluecker Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const QuadMi& quad) + { + Scene* scene = context->scene; + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf p0 = quad.template getVertex<0>(i,scene); + const Vec3vf p1 = quad.template getVertex<1>(i,scene); + const Vec3vf p2 = quad.template getVertex<2>(i,scene); + const Vec3vf p3 = quad.template getVertex<3>(i,scene); + pre.intersectK(valid_i,ray,p0,p1,p2,p3,IntersectKEpilogM(ray,context,quad.geomID(),quad.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const QuadMi& quad) + { + Scene* scene = context->scene; + vbool valid0 = valid_i; + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf p0 = quad.template getVertex<0>(i,scene); + const Vec3vf p1 = quad.template getVertex<1>(i,scene); + const Vec3vf p2 = quad.template getVertex<2>(i,scene); + const Vec3vf p3 = quad.template getVertex<3>(i,scene); + if (pre.intersectK(valid0,ray,p0,p1,p2,p3,OccludedKEpilogM(valid0,ray,context,quad.geomID(),quad.primID(),i))) + break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf4 v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + pre.intersect1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf4 v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + }; + + /*! Intersects M motion blur quads with 1 ray */ + template + struct QuadMiMBIntersector1Moeller + { + typedef QuadMi Primitive; + typedef QuadMIntersector1MoellerTrumbore Precalculations; + + /*! Intersect a ray with the M quads and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()); + pre.intersect(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of M quads. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()); + return pre.occluded(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& quad) + { + return PrimitivePointQuery1::pointQuery(query, context, quad); + } + }; + + /*! Intersects M motion blur quads with K rays. */ + template + struct QuadMiMBIntersectorKMoeller + { + typedef QuadMi Primitive; + typedef QuadMIntersectorKMoellerTrumbore Precalculations; + + /*! Intersects K rays with M quads. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const QuadMi& quad) + { + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + Vec3vf v0,v1,v2,v3; quad.gather(valid_i,v0,v1,v2,v3,i,context->scene,ray.time()); + pre.intersectK(valid_i,ray,v0,v1,v2,v3,IntersectKEpilogM(ray,context,quad.geomID(),quad.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M quads. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const QuadMi& quad) + { + vbool valid0 = valid_i; + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + Vec3vf v0,v1,v2,v3; quad.gather(valid_i,v0,v1,v2,v3,i,context->scene,ray.time()); + if (pre.intersectK(valid0,ray,v0,v1,v2,v3,OccludedKEpilogM(valid0,ray,context,quad.geomID(),quad.primID(),i))) + break; + } + return !valid0; + } + + /*! Intersect a ray with M quads and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()[k]); + pre.intersect1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of the M quads. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()[k]); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + }; + + /*! Intersects M motion blur quads with 1 ray */ + template + struct QuadMiMBIntersector1Pluecker + { + typedef QuadMi Primitive; + typedef QuadMIntersector1Pluecker Precalculations; + + /*! Intersect a ray with the M quads and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()); + pre.intersect(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of M quads. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()); + return pre.occluded(ray,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& quad) + { + return PrimitivePointQuery1::pointQuery(query, context, quad); + } + }; + + /*! Intersects M motion blur quads with K rays. */ + template + struct QuadMiMBIntersectorKPluecker + { + typedef QuadMi Primitive; + typedef QuadMIntersectorKPluecker Precalculations; + + /*! Intersects K rays with M quads. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const QuadMi& quad) + { + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + Vec3vf v0,v1,v2,v3; quad.gather(valid_i,v0,v1,v2,v3,i,context->scene,ray.time()); + pre.intersectK(valid_i,ray,v0,v1,v2,v3,IntersectKEpilogM(ray,context,quad.geomID(),quad.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M quads. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const QuadMi& quad) + { + vbool valid0 = valid_i; + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + Vec3vf v0,v1,v2,v3; quad.gather(valid_i,v0,v1,v2,v3,i,context->scene,ray.time()); + if (pre.intersectK(valid0,ray,v0,v1,v2,v3,OccludedKEpilogM(valid0,ray,context,quad.geomID(),quad.primID(),i))) + break; + } + return !valid0; + } + + /*! Intersect a ray with M quads and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()[k]); + pre.intersect1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of the M quads. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const QuadMi& quad) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2,v3; quad.gather(v0,v1,v2,v3,context->scene,ray.time()[k]); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,quad.geomID(),quad.primID()); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/quadv.h b/thirdparty/embree/kernels/geometry/quadv.h new file mode 100644 index 000000000000..0a1fe4d128a7 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quadv.h @@ -0,0 +1,165 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + /* Stores the vertices of M quads in struct of array layout */ + template + struct QuadMv + { + public: + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* Returns maximum number of stored quads */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline QuadMv() {} + + /* Construction from vertices and IDs */ + __forceinline QuadMv(const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, const vuint& geomIDs, const vuint& primIDs) + : v0(v0), v1(v1), v2(v2), v3(v3), geomIDs(geomIDs), primIDs(primIDs) {} + + /* Returns a mask that tells which quads are valid */ + __forceinline vbool valid() const { return geomIDs != vuint(-1); } + + /* Returns true if the specified quad is valid */ + __forceinline bool valid(const size_t i) const { assert(i& geomID() { return geomIDs; } + __forceinline const vuint& geomID() const { return geomIDs; } + __forceinline unsigned int geomID(const size_t i) const { assert(i primID() { return primIDs; } + __forceinline const vuint primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(i lower = min(v0,v1,v2,v3); + Vec3vf upper = max(v0,v1,v2,v3); + vbool mask = valid(); + lower.x = select(mask,lower.x,vfloat(pos_inf)); + lower.y = select(mask,lower.y,vfloat(pos_inf)); + lower.z = select(mask,lower.z,vfloat(pos_inf)); + upper.x = select(mask,upper.x,vfloat(neg_inf)); + upper.y = select(mask,upper.y,vfloat(neg_inf)); + upper.z = select(mask,upper.z,vfloat(neg_inf)); + return BBox3fa(Vec3fa(reduce_min(lower.x),reduce_min(lower.y),reduce_min(lower.z)), + Vec3fa(reduce_max(upper.x),reduce_max(upper.y),reduce_max(upper.z))); + } + + /* Non temporal store */ + __forceinline static void store_nt(QuadMv* dst, const QuadMv& src) + { + vfloat::store_nt(&dst->v0.x,src.v0.x); + vfloat::store_nt(&dst->v0.y,src.v0.y); + vfloat::store_nt(&dst->v0.z,src.v0.z); + vfloat::store_nt(&dst->v1.x,src.v1.x); + vfloat::store_nt(&dst->v1.y,src.v1.y); + vfloat::store_nt(&dst->v1.z,src.v1.z); + vfloat::store_nt(&dst->v2.x,src.v2.x); + vfloat::store_nt(&dst->v2.y,src.v2.y); + vfloat::store_nt(&dst->v2.z,src.v2.z); + vfloat::store_nt(&dst->v3.x,src.v3.x); + vfloat::store_nt(&dst->v3.y,src.v3.y); + vfloat::store_nt(&dst->v3.z,src.v3.z); + vuint::store_nt(&dst->geomIDs,src.geomIDs); + vuint::store_nt(&dst->primIDs,src.primIDs); + } + + /* Fill quad from quad list */ + __forceinline void fill(const PrimRef* prims, size_t& begin, size_t end, Scene* scene) + { + vuint vgeomID = -1, vprimID = -1; + Vec3vf v0 = zero, v1 = zero, v2 = zero, v3 = zero; + + for (size_t i=0; iget(geomID); + const QuadMesh::Quad& quad = mesh->quad(primID); + const Vec3fa& p0 = mesh->vertex(quad.v[0]); + const Vec3fa& p1 = mesh->vertex(quad.v[1]); + const Vec3fa& p2 = mesh->vertex(quad.v[2]); + const Vec3fa& p3 = mesh->vertex(quad.v[3]); + vgeomID [i] = geomID; + vprimID [i] = primID; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + v3.x[i] = p3.x; v3.y[i] = p3.y; v3.z[i] = p3.z; + } + QuadMv::store_nt(this,QuadMv(v0,v1,v2,v3,vgeomID,vprimID)); + } + + /* Updates the primitive */ + __forceinline BBox3fa update(QuadMesh* mesh) + { + BBox3fa bounds = empty; + vuint vgeomID = -1, vprimID = -1; + Vec3vf v0 = zero, v1 = zero, v2 = zero; + + for (size_t i=0; iquad(primId); + const Vec3fa p0 = mesh->vertex(quad.v[0]); + const Vec3fa p1 = mesh->vertex(quad.v[1]); + const Vec3fa p2 = mesh->vertex(quad.v[2]); + const Vec3fa p3 = mesh->vertex(quad.v[3]); + bounds.extend(merge(BBox3fa(p0),BBox3fa(p1),BBox3fa(p2),BBox3fa(p3))); + vgeomID [i] = geomId; + vprimID [i] = primId; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + v3.x[i] = p3.x; v3.y[i] = p3.y; v3.z[i] = p3.z; + } + new (this) QuadMv(v0,v1,v2,v3,vgeomID,vprimID); + return bounds; + } + + public: + Vec3vf v0; // 1st vertex of the quads + Vec3vf v1; // 2nd vertex of the quads + Vec3vf v2; // 3rd vertex of the quads + Vec3vf v3; // 4rd vertex of the quads + private: + vuint geomIDs; // geometry ID + vuint primIDs; // primitive ID + }; + + template + typename QuadMv::Type QuadMv::type; + + typedef QuadMv<4> Quad4v; +} diff --git a/thirdparty/embree/kernels/geometry/quadv_intersector.h b/thirdparty/embree/kernels/geometry/quadv_intersector.h new file mode 100644 index 000000000000..30a24b291a7c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/quadv_intersector.h @@ -0,0 +1,181 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "quadv.h" +#include "quad_intersector_moeller.h" +#include "quad_intersector_pluecker.h" + +namespace embree +{ + namespace isa + { + /*! Intersects M quads with 1 ray */ + template + struct QuadMvIntersector1Moeller + { + typedef QuadMv Primitive; + typedef QuadMIntersector1MoellerTrumbore Precalculations; + + /*! Intersect a ray with the M quads and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect(ray,context,quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of M quads. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.occluded(ray,context, quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& quad) + { + return PrimitivePointQuery1::pointQuery(query, context, quad); + } + }; + + /*! Intersects M triangles with K rays. */ + template + struct QuadMvIntersectorKMoeller + { + typedef QuadMv Primitive; + typedef QuadMIntersectorKMoellerTrumbore Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const QuadMv& quad) + { + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf p0 = broadcast>(quad.v0,i); + const Vec3vf p1 = broadcast>(quad.v1,i); + const Vec3vf p2 = broadcast>(quad.v2,i); + const Vec3vf p3 = broadcast>(quad.v3,i); + pre.intersectK(valid_i,ray,p0,p1,p2,p3,IntersectKEpilogM(ray,context,quad.geomID(),quad.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const QuadMv& quad) + { + vbool valid0 = valid_i; + + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf p0 = broadcast>(quad.v0,i); + const Vec3vf p1 = broadcast>(quad.v1,i); + const Vec3vf p2 = broadcast>(quad.v2,i); + const Vec3vf p3 = broadcast>(quad.v3,i); + if (pre.intersectK(valid0,ray,p0,p1,p2,p3,OccludedKEpilogM(valid0,ray,context,quad.geomID(),quad.primID(),i))) + break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const QuadMv& quad) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect1(ray,k,context,quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const QuadMv& quad) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.occluded1(ray,k,context,quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + }; + + /*! Intersects M quads with 1 ray */ + template + struct QuadMvIntersector1Pluecker + { + typedef QuadMv Primitive; + typedef QuadMIntersector1Pluecker Precalculations; + + /*! Intersect a ray with the M quads and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect(ray,context,quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of M quads. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& quad) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.occluded(ray,context, quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& quad) + { + return PrimitivePointQuery1::pointQuery(query, context, quad); + } + }; + + /*! Intersects M triangles with K rays. */ + template + struct QuadMvIntersectorKPluecker + { + typedef QuadMv Primitive; + typedef QuadMIntersectorKPluecker Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const QuadMv& quad) + { + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf p0 = broadcast>(quad.v0,i); + const Vec3vf p1 = broadcast>(quad.v1,i); + const Vec3vf p2 = broadcast>(quad.v2,i); + const Vec3vf p3 = broadcast>(quad.v3,i); + pre.intersectK(valid_i,ray,p0,p1,p2,p3,IntersectKEpilogM(ray,context,quad.geomID(),quad.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const QuadMv& quad) + { + vbool valid0 = valid_i; + + for (size_t i=0; i::max_size(); i++) + { + if (!quad.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf p0 = broadcast>(quad.v0,i); + const Vec3vf p1 = broadcast>(quad.v1,i); + const Vec3vf p2 = broadcast>(quad.v2,i); + const Vec3vf p3 = broadcast>(quad.v3,i); + if (pre.intersectK(valid0,ray,p0,p1,p2,p3,OccludedKEpilogM(valid0,ray,context,quad.geomID(),quad.primID(),i))) + break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const QuadMv& quad) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect1(ray,k,context,quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const QuadMv& quad) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.occluded1(ray,k,context,quad.v0,quad.v1,quad.v2,quad.v3,quad.geomID(),quad.primID()); + } + }; + } +} + diff --git a/thirdparty/embree/kernels/geometry/roundline_intersector.h b/thirdparty/embree/kernels/geometry/roundline_intersector.h new file mode 100644 index 000000000000..cdf68f486bb7 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/roundline_intersector.h @@ -0,0 +1,710 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "curve_intersector_precalculations.h" + + +/* + + This file implements the intersection of a ray with a round linear + curve segment. We define the geometry of such a round linear curve + segment from point p0 with radius r0 to point p1 with radius r1 + using the cone that touches spheres p0/r0 and p1/r1 tangentially + plus the sphere p1/r1. We denote the tangentially touching cone from + p0/r0 to p1/r1 with cone(p0,r0,p1,r1) and the cone plus the ending + sphere with cone_sphere(p0,r0,p1,r1). + + For multiple connected round linear curve segments this construction + yield a proper shape when viewed from the outside. Using the + following CSG we can also handle the interiour in most common cases: + + round_linear_curve(pl,rl,p0,r0,p1,r1,pr,rr) = + cone_sphere(p0,r0,p1,r1) - cone(pl,rl,p0,r0) - cone(p1,r1,pr,rr) + + Thus by subtracting the neighboring cone geometries, we cut away + parts of the center cone_sphere surface which lie inside the + combined curve. This approach works as long as geometry of the + current cone_sphere penetrates into direct neighbor segments only, + and not into segments further away. + + To construct a cone that touches two spheres at p0 and p1 with r0 + and r1, one has to increase the cone radius at r0 and r1 to obtain + larger radii w0 and w1, such that the infinite cone properly touches + the spheres. From the paper "Ray Tracing Generalized Tube + Primitives: Method and Applications" + (https://www.researchgate.net/publication/334378683_Ray_Tracing_Generalized_Tube_Primitives_Method_and_Applications) + one can derive the following equations for these increased + radii: + + sr = 1.0f / sqrt(1-sqr(dr)/sqr(p1-p0)) + w0 = sr*r0 + w1 = sr*r1 + + Further, we want the cone to start where it touches the sphere at p0 + and to end where it touches sphere at p1. Therefore, we need to + construct clipping locations y0 and y1 for the start and end of the + cone. These start and end clipping location of the cone can get + calculated as: + + Y0 = - r0 * (r1-r0) / length(p1-p0) + Y1 = length(p1-p0) - r1 * (r1-r0) / length(p1-p0) + + Where the cone starts a distance Y0 and ends a distance Y1 away of + point p0 along the cone center. The distance between Y1-Y0 can get + calculated as: + + dY = length(p1-p0) - (r1-r0)^2 / length(p1-p0) + + In the code below, Y will always be scaled by length(p1-p0) to + obtain y and you will find the terms r0*(r1-r0) and + (p1-p0)^2-(r1-r0)^2. + + */ + +namespace embree +{ + namespace isa + { + template + struct RoundLineIntersectorHitM + { + __forceinline RoundLineIntersectorHitM() {} + + __forceinline RoundLineIntersectorHitM(const vfloat& u, const vfloat& v, const vfloat& t, const Vec3vf& Ng) + : vu(u), vv(v), vt(t), vNg(Ng) {} + + __forceinline void finalize() {} + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + public: + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + namespace __roundline_internal + { + template + struct ConeGeometry + { + ConeGeometry (const Vec4vf& a, const Vec4vf& b) + : p0(a.xyz()), p1(b.xyz()), dP(p1-p0), dPdP(dot(dP,dP)), r0(a.w), sqr_r0(sqr(r0)), r1(b.w), dr(r1-r0), drdr(dr*dr), r0dr (r0*dr), g(dPdP - drdr) {} + + /* + + This function tests if a point is accepted by first cone + clipping plane. + + First, we need to project the point onto the line p0->p1: + + Y = (p-p0)*(p1-p0)/length(p1-p0) + + This value y is the distance to the projection point from + p0. The clip distances are calculated as: + + Y0 = - r0 * (r1-r0) / length(p1-p0) + Y1 = length(p1-p0) - r1 * (r1-r0) / length(p1-p0) + + Thus to test if the point p is accepted by the first + clipping plane we need to test Y > Y0 and to test if it + is accepted by the second clipping plane we need to test + Y < Y1. + + By multiplying the calculations with length(p1-p0) these + calculation can get simplied to: + + y = (p-p0)*(p1-p0) + y0 = - r0 * (r1-r0) + y1 = (p1-p0)^2 - r1 * (r1-r0) + + and the test y > y0 and y < y1. + + */ + + __forceinline vbool isClippedByPlane (const vbool& valid_i, const Vec3vf& p) const + { + const Vec3vf p0p = p - p0; + const vfloat y = dot(p0p,dP); + const vfloat cap0 = -r0dr; + const vbool inside_cone = y > cap0; + return valid_i & (p0.x != vfloat(inf)) & (p1.x != vfloat(inf)) & inside_cone; + } + + /* + + This function tests whether a point lies inside the capped cone + tangential to its ending spheres. + + Therefore one has to check if the point is inside the + region defined by the cone clipping planes, which is + performed similar as in the previous function. + + To perform the inside cone test we need to project the + point onto the line p0->p1: + + dP = p1-p0 + Y = (p-p0)*dP/length(dP) + + This value Y is the distance to the projection point from + p0. To obtain a parameter value u going from 0 to 1 along + the line p0->p1 we calculate: + + U = Y/length(dP) + + The radii to use at points p0 and p1 are: + + w0 = sr * r0 + w1 = sr * r1 + dw = w1-w0 + + Using these radii and u one can directly test if the point + lies inside the cone using the formula dP*dP < wy*wy with: + + wy = w0 + u*dw + py = p0 + u*dP - p + + By multiplying the calculations with length(p1-p0) and + inserting the definition of w can obtain simpler equations: + + y = (p-p0)*dP + ry = r0 + y/dP^2 * dr + wy = sr*ry + py = p0 + y/dP^2*dP - p + y0 = - r0 * dr + y1 = dP^2 - r1 * dr + + Thus for the in-cone test we get: + + py^2 < wy^2 + <=> py^2 < sr^2 * ry^2 + <=> py^2 * ( dP^2 - dr^2 ) < dP^2 * ry^2 + + This can further get simplified to: + + (p0-p)^2 * (dP^2 - dr^2) - y^2 < dP^2 * r0^2 + 2.0f*r0*dr*y; + + */ + + __forceinline vbool isInsideCappedCone (const vbool& valid_i, const Vec3vf& p) const + { + const Vec3vf p0p = p - p0; + const vfloat y = dot(p0p,dP); + const vfloat cap0 = -r0dr+vfloat(ulp); + const vfloat cap1 = -r1*dr + dPdP; + + vbool inside_cone = valid_i & (p0.x != vfloat(inf)) & (p1.x != vfloat(inf)); + inside_cone &= y > cap0; // start clipping plane + inside_cone &= y < cap1; // end clipping plane + inside_cone &= sqr(p0p)*g - sqr(y) < dPdP * sqr_r0 + 2.0f*r0dr*y; // in cone test + return inside_cone; + } + + protected: + Vec3vf p0; + Vec3vf p1; + Vec3vf dP; + vfloat dPdP; + vfloat r0; + vfloat sqr_r0; + vfloat r1; + vfloat dr; + vfloat drdr; + vfloat r0dr; + vfloat g; + }; + + template + struct ConeGeometryIntersector : public ConeGeometry + { + using ConeGeometry::p0; + using ConeGeometry::p1; + using ConeGeometry::dP; + using ConeGeometry::dPdP; + using ConeGeometry::r0; + using ConeGeometry::sqr_r0; + using ConeGeometry::r1; + using ConeGeometry::dr; + using ConeGeometry::r0dr; + using ConeGeometry::g; + + ConeGeometryIntersector (const Vec3vf& ray_org, const Vec3vf& ray_dir, const vfloat& dOdO, const vfloat& rcp_dOdO, const Vec4vf& a, const Vec4vf& b) + : ConeGeometry(a,b), org(ray_org), O(ray_org-p0), dO(ray_dir), dOdO(dOdO), rcp_dOdO(rcp_dOdO), OdP(dot(dP,O)), dOdP(dot(dP,dO)), yp(OdP + r0dr) {} + + /* + + This function intersects a ray with a cone that touches a + start sphere p0/r0 and end sphere p1/r1. + + To find this ray/cone intersections one could just + calculate radii w0 and w1 as described above and use a + standard ray/cone intersection routine with these + radii. However, it turns out that calculations can get + simplified when deriving a specialized ray/cone + intersection for this special case. We perform + calculations relative to the cone origin p0 and define: + + O = ray_org - p0 + dO = ray_dir + dP = p1-p0 + dr = r1-r0 + dw = w1-w0 + + For some t we can compute the potential hit point h = O + t*dO and + project it onto the cone vector dP to obtain u = (h*dP)/(dP*dP). In + case of an intersection, the squared distance from the hit point + projected onto the cone center line to the hit point should be equal + to the squared cone radius at u: + + (u*dP - h)^2 = (w0 + u*dw)^2 + + Inserting the definition of h, u, w0, and dw into this formula, then + factoring out all terms, and sorting by t^2, t^1, and t^0 terms + yields a quadratic equation to solve. + + Inserting u: + ( (h*dP)*dP/dP^2 - h )^2 = ( w0 + (h*dP)*dw/dP^2 )^2 + + Multiplying by dP^4: + ( (h*dP)*dP - h*dP^2 )^2 = ( w0*dP^2 + (h*dP)*dw )^2 + + Inserting w0 and dw: + ( (h*dP)*dP - h*dP^2 )^2 = ( r0*dP^2 + (h*dP)*dr )^2 / (1-dr^2/dP^2) + ( (h*dP)*dP - h*dP^2 )^2 *(dP^2 - dr^2) = dP^2 * ( r0*dP^2 + (h*dP)*dr )^2 + + Now one can insert the definition of h, factor out, and presort by t: + ( ((O + t*dO)*dP)*dP - (O + t*dO)*dP^2 )^2 *(dP^2 - dr^2) = dP^2 * ( r0*dP^2 + ((O + t*dO)*dP)*dr )^2 + ( (O*dP)*dP-O*dP^2 + t*( (dO*dP)*dP - dO*dP^2 ) )^2 *(dP^2 - dr^2) = dP^2 * ( r0*dP^2 + (O*dP)*dr + t*(dO*dP)*dr )^2 + + Factoring out further and sorting by t^2, t^1 and t^0 yields: + + 0 = t^2 * [ ((dO*dP)*dP - dO-dP^2)^2 * (dP^2 - dr^2) - dP^2*(dO*dP)^2*dr^2 ] + + 2*t^1 * [ ((O*dP)*dP - O*dP^2) * ((dO*dP)*dP - dO*dP^2) * (dP^2 - dr^2) - dP^2*(r0*dP^2 + (O*dP)*dr)*(dO*dP)*dr ] + + t^0 * [ ( (O*dP)*dP - O*dP^2)^2 * (dP^2-dr^2) - dP^2*(r0*dP^2 + (O*dP)*dr)^2 ] + + This can be simplified to: + + 0 = t^2 * [ (dP^2 - dr^2)*dO^2 - (dO*dP)^2 ] + + 2*t^1 * [ (dP^2 - dr^2)*(O*dO) - (dO*dP)*(O*dP + r0*dr) ] + + t^0 * [ (dP^2 - dr^2)*O^2 - (O*dP)^2 - r0^2*dP^2 - 2.0f*r0*dr*(O*dP) ] + + Solving this quadratic equation yields the values for t at which the + ray intersects the cone. + + */ + + __forceinline bool intersectCone(vbool& valid, vfloat& lower, vfloat& upper) + { + /* return no hit by default */ + lower = pos_inf; + upper = neg_inf; + + /* compute quadratic equation A*t^2 + B*t + C = 0 */ + const vfloat OO = dot(O,O); + const vfloat OdO = dot(dO,O); + const vfloat A = g * dOdO - sqr(dOdP); + const vfloat B = 2.0f * (g*OdO - dOdP*yp); + const vfloat C = g*OO - sqr(OdP) - sqr_r0*dPdP - 2.0f*r0dr*OdP; + + /* we miss the cone if determinant is smaller than zero */ + const vfloat D = B*B - 4.0f*A*C; + valid &= (D >= 0.0f & g > 0.0f); // if g <= 0 then the cone is inside a sphere end + + /* When rays are parallel to the cone surface, then the + * ray may be inside or outside the cone. We just assume a + * miss in that case, which is fine as rays inside the + * cone would anyway hit the ending spheres in that + * case. */ + valid &= abs(A) > min_rcp_input; + if (unlikely(none(valid))) { + return false; + } + + /* compute distance to front and back hit */ + const vfloat Q = sqrt(D); + const vfloat rcp_2A = rcp(2.0f*A); + t_cone_front = (-B-Q)*rcp_2A; + y_cone_front = yp + t_cone_front*dOdP; + lower = select( (y_cone_front > -(float)ulp) & (y_cone_front <= g) & (g > 0.0f), t_cone_front, vfloat(pos_inf)); +#if !defined (EMBREE_BACKFACE_CULLING_CURVES) + t_cone_back = (-B+Q)*rcp_2A; + y_cone_back = yp + t_cone_back *dOdP; + upper = select( (y_cone_back > -(float)ulp) & (y_cone_back <= g) & (g > 0.0f), t_cone_back , vfloat(neg_inf)); +#endif + return true; + } + + /* + This function intersects the ray with the end sphere at + p1. We already clip away hits that are inside the + neighboring cone segment. + + */ + + __forceinline void intersectEndSphere(vbool& valid, + const ConeGeometry& coneR, + vfloat& lower, vfloat& upper) + { + /* calculate front and back hit with end sphere */ + const Vec3vf O1 = org - p1; + const vfloat O1dO = dot(O1,dO); + const vfloat h2 = sqr(O1dO) - dOdO*(sqr(O1) - sqr(r1)); + const vfloat rhs1 = select( h2 >= 0.0f, sqrt(h2), vfloat(neg_inf) ); + + /* clip away front hit if it is inside next cone segment */ + t_sph1_front = (-O1dO - rhs1)*rcp_dOdO; + const Vec3vf hit_front = org + t_sph1_front*dO; + vbool valid_sph1_front = h2 >= 0.0f & yp + t_sph1_front*dOdP > g & !coneR.isClippedByPlane (valid, hit_front); + lower = select(valid_sph1_front, t_sph1_front, vfloat(pos_inf)); + +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + /* clip away back hit if it is inside next cone segment */ + t_sph1_back = (-O1dO + rhs1)*rcp_dOdO; + const Vec3vf hit_back = org + t_sph1_back*dO; + vbool valid_sph1_back = h2 >= 0.0f & yp + t_sph1_back*dOdP > g & !coneR.isClippedByPlane (valid, hit_back); + upper = select(valid_sph1_back, t_sph1_back, vfloat(neg_inf)); +#else + upper = vfloat(neg_inf); +#endif + } + + __forceinline void intersectBeginSphere(const vbool& valid, + vfloat& lower, vfloat& upper) + { + /* calculate front and back hit with end sphere */ + const Vec3vf O1 = org - p0; + const vfloat O1dO = dot(O1,dO); + const vfloat h2 = sqr(O1dO) - dOdO*(sqr(O1) - sqr(r0)); + const vfloat rhs1 = select( h2 >= 0.0f, sqrt(h2), vfloat(neg_inf) ); + + /* clip away front hit if it is inside next cone segment */ + t_sph0_front = (-O1dO - rhs1)*rcp_dOdO; + vbool valid_sph1_front = valid & h2 >= 0.0f & yp + t_sph0_front*dOdP < 0; + lower = select(valid_sph1_front, t_sph0_front, vfloat(pos_inf)); + +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + /* clip away back hit if it is inside next cone segment */ + t_sph0_back = (-O1dO + rhs1)*rcp_dOdO; + vbool valid_sph1_back = valid & h2 >= 0.0f & yp + t_sph0_back*dOdP < 0; + upper = select(valid_sph1_back, t_sph0_back, vfloat(neg_inf)); +#else + upper = vfloat(neg_inf); +#endif + } + + /* + + This function calculates the geometry normal of some cone hit. + + For a given hit point h (relative to p0) with a cone + starting at p0 with radius w0 and ending at p1 with + radius w1 one normally calculates the geometry normal by + first calculating the parmetric u hit location along the + cone: + + u = dot(h,dP)/dP^2 + + Using this value one can now directly calculate the + geometry normal by bending the connection vector (h-u*dP) + from hit to projected hit with some cone dependent value + dw/sqrt(dP^2) * normalize(dP): + + Ng = normalize(h-u*dP) - dw/length(dP) * normalize(dP) + + The length of the vector (h-u*dP) can also get calculated + by interpolating the radii as w0+u*dw which yields: + + Ng = (h-u*dP)/(w0+u*dw) - dw/dP^2 * dP + + Multiplying with (w0+u*dw) yield a scaled Ng': + + Ng' = (h-u*dP) - (w0+u*dw)*dw/dP^2*dP + + Inserting the definition of w0 and dw and refactoring + yield a furhter scaled Ng'': + + Ng'' = (dP^2 - dr^2) (h-q) - (r0+u*dr)*dr*dP + + Now inserting the definition of u gives and multiplying + with the denominator yields: + + Ng''' = (dP^2-dr^2)*(dP^2*h-dot(h,dP)*dP) - (dP^2*r0+dot(h,dP)*dr)*dr*dP + + Factoring out, cancelling terms, dividing by dP^2, and + factoring again yields finally: + + Ng'''' = (dP^2-dr^2)*h - dP*(dot(h,dP) + r0*dr) + + */ + + __forceinline Vec3vf Ng_cone(const vbool& front_hit) const + { +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + const vfloat y = select(front_hit, y_cone_front, y_cone_back); + const vfloat t = select(front_hit, t_cone_front, t_cone_back); + const Vec3vf h = O + t*dO; + return g*h-dP*y; +#else + const Vec3vf h = O + t_cone_front*dO; + return g*h-dP*y_cone_front; +#endif + } + + /* compute geometry normal of sphere hit as the difference + * vector from hit point to sphere center */ + + __forceinline Vec3vf Ng_sphere1(const vbool& front_hit) const + { +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + const vfloat t_sph1 = select(front_hit, t_sph1_front, t_sph1_back); + return org+t_sph1*dO-p1; +#else + return org+t_sph1_front*dO-p1; +#endif + } + + __forceinline Vec3vf Ng_sphere0(const vbool& front_hit) const + { +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + const vfloat t_sph0 = select(front_hit, t_sph0_front, t_sph0_back); + return org+t_sph0*dO-p0; +#else + return org+t_sph0_front*dO-p0; +#endif + } + + /* + This function calculates the u coordinate of a + hit. Therefore we use the hit distance y (which is zero + at the first cone clipping plane) and divide by distance + g between the clipping planes. + + */ + + __forceinline vfloat u_cone(const vbool& front_hit) const + { +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + const vfloat y = select(front_hit, y_cone_front, y_cone_back); + return clamp(y*rcp(g)); +#else + return clamp(y_cone_front*rcp(g)); +#endif + } + + private: + Vec3vf org; + Vec3vf O; + Vec3vf dO; + vfloat dOdO; + vfloat rcp_dOdO; + vfloat OdP; + vfloat dOdP; + + /* for ray/cone intersection */ + private: + vfloat yp; + vfloat y_cone_front; + vfloat t_cone_front; +#if !defined (EMBREE_BACKFACE_CULLING_CURVES) + vfloat y_cone_back; + vfloat t_cone_back; +#endif + + /* for ray/sphere intersection */ + private: + vfloat t_sph1_front; + vfloat t_sph0_front; +#if !defined (EMBREE_BACKFACE_CULLING_CURVES) + vfloat t_sph1_back; + vfloat t_sph0_back; +#endif + }; + + + template + static __forceinline bool intersectConeSphere(const vbool& valid_i, + const Vec3vf& ray_org_in, const Vec3vf& ray_dir, + const vfloat& ray_tnear, const ray_tfar_func& ray_tfar, + const Vec4vf& v0, const Vec4vf& v1, + const Vec4vf& vL, const Vec4vf& vR, + const Epilog& epilog) + { + vbool valid = valid_i; + + /* move ray origin closer to make calculations numerically stable */ + const vfloat dOdO = sqr(ray_dir); + const vfloat rcp_dOdO = rcp(dOdO); + const Vec3vf center = vfloat(0.5f)*(v0.xyz()+v1.xyz()); + const vfloat dt = dot(center-ray_org_in,ray_dir)*rcp_dOdO; + const Vec3vf ray_org = ray_org_in + dt*ray_dir; + + /* intersect with cone from v0 to v1 */ + vfloat t_cone_lower, t_cone_upper; + ConeGeometryIntersector cone (ray_org, ray_dir, dOdO, rcp_dOdO, v0, v1); + vbool validCone = valid; + cone.intersectCone(validCone, t_cone_lower, t_cone_upper); + + valid &= (validCone | (cone.g <= 0.0f)); // if cone is entirely in sphere end - check sphere + if (unlikely(none(valid))) + return false; + + /* cone hits inside the neighboring capped cones are inside the geometry and thus ignored */ + const ConeGeometry coneL (v0, vL); + const ConeGeometry coneR (v1, vR); +#if !defined(EMBREE_BACKFACE_CULLING_CURVES) + const Vec3vf hit_lower = ray_org + t_cone_lower*ray_dir; + const Vec3vf hit_upper = ray_org + t_cone_upper*ray_dir; + t_cone_lower = select (!coneL.isInsideCappedCone (validCone, hit_lower) & !coneR.isInsideCappedCone (validCone, hit_lower), t_cone_lower, vfloat(pos_inf)); + t_cone_upper = select (!coneL.isInsideCappedCone (validCone, hit_upper) & !coneR.isInsideCappedCone (validCone, hit_upper), t_cone_upper, vfloat(neg_inf)); +#endif + + /* intersect ending sphere */ + vfloat t_sph1_lower, t_sph1_upper; + vfloat t_sph0_lower = vfloat(pos_inf); + vfloat t_sph0_upper = vfloat(neg_inf); + cone.intersectEndSphere(valid, coneR, t_sph1_lower, t_sph1_upper); + + const vbool isBeginPoint = valid & (vL[0] == vfloat(pos_inf)); + if (unlikely(any(isBeginPoint))) { + cone.intersectBeginSphere (isBeginPoint, t_sph0_lower, t_sph0_upper); + } + + /* CSG union of cone and end sphere */ + vfloat t_sph_lower = min(t_sph0_lower, t_sph1_lower); + vfloat t_cone_sphere_lower = min(t_cone_lower, t_sph_lower); +#if !defined (EMBREE_BACKFACE_CULLING_CURVES) + vfloat t_sph_upper = max(t_sph0_upper, t_sph1_upper); + vfloat t_cone_sphere_upper = max(t_cone_upper, t_sph_upper); + + /* filter out hits that are not in tnear/tfar range */ + const vbool valid_lower = valid & ray_tnear <= dt+t_cone_sphere_lower & dt+t_cone_sphere_lower <= ray_tfar() & t_cone_sphere_lower != vfloat(pos_inf); + const vbool valid_upper = valid & ray_tnear <= dt+t_cone_sphere_upper & dt+t_cone_sphere_upper <= ray_tfar() & t_cone_sphere_upper != vfloat(neg_inf); + + /* check if there is a first hit */ + const vbool valid_first = valid_lower | valid_upper; + if (unlikely(none(valid_first))) + return false; + + /* construct first hit */ + const vfloat t_first = select(valid_lower, t_cone_sphere_lower, t_cone_sphere_upper); + const vbool cone_hit_first = t_first == t_cone_lower | t_first == t_cone_upper; + const vbool sph0_hit_first = t_first == t_sph0_lower | t_first == t_sph0_upper; + const Vec3vf Ng_first = select(cone_hit_first, cone.Ng_cone(valid_lower), select (sph0_hit_first, cone.Ng_sphere0(valid_lower), cone.Ng_sphere1(valid_lower))); + const vfloat u_first = select(cone_hit_first, cone.u_cone(valid_lower), select (sph0_hit_first, vfloat(zero), vfloat(one))); + + /* invoke intersection filter for first hit */ + RoundLineIntersectorHitM hit(u_first,zero,dt+t_first,Ng_first); + const bool is_hit_first = epilog(valid_first, hit); + + /* check for possible second hits before potentially accepted hit */ + const vfloat t_second = t_cone_sphere_upper; + const vbool valid_second = valid_lower & valid_upper & (dt+t_cone_sphere_upper <= ray_tfar()); + if (unlikely(none(valid_second))) + return is_hit_first; + + /* invoke intersection filter for second hit */ + const vbool cone_hit_second = t_second == t_cone_lower | t_second == t_cone_upper; + const vbool sph0_hit_second = t_second == t_sph0_lower | t_second == t_sph0_upper; + const Vec3vf Ng_second = select(cone_hit_second, cone.Ng_cone(false), select (sph0_hit_second, cone.Ng_sphere0(false), cone.Ng_sphere1(false))); + const vfloat u_second = select(cone_hit_second, cone.u_cone(false), select (sph0_hit_second, vfloat(zero), vfloat(one))); + + hit = RoundLineIntersectorHitM(u_second,zero,dt+t_second,Ng_second); + const bool is_hit_second = epilog(valid_second, hit); + + return is_hit_first | is_hit_second; +#else + /* filter out hits that are not in tnear/tfar range */ + const vbool valid_lower = valid & ray_tnear <= dt+t_cone_sphere_lower & dt+t_cone_sphere_lower <= ray_tfar() & t_cone_sphere_lower != vfloat(pos_inf); + + /* check if there is a valid hit */ + if (unlikely(none(valid_lower))) + return false; + + /* construct first hit */ + const vbool cone_hit_first = t_cone_sphere_lower == t_cone_lower | t_cone_sphere_lower == t_cone_upper; + const vbool sph0_hit_first = t_cone_sphere_lower == t_sph0_lower | t_cone_sphere_lower == t_sph0_upper; + const Vec3vf Ng_first = select(cone_hit_first, cone.Ng_cone(valid_lower), select (sph0_hit_first, cone.Ng_sphere0(valid_lower), cone.Ng_sphere1(valid_lower))); + const vfloat u_first = select(cone_hit_first, cone.u_cone(valid_lower), select (sph0_hit_first, vfloat(zero), vfloat(one))); + + /* invoke intersection filter for first hit */ + RoundLineIntersectorHitM hit(u_first,zero,dt+t_cone_sphere_lower,Ng_first); + const bool is_hit_first = epilog(valid_lower, hit); + + return is_hit_first; +#endif + } + + } // end namespace __roundline_internal + + template + struct RoundLinearCurveIntersector1 + { + typedef CurvePrecalculations1 Precalculations; + + struct ray_tfar { + Ray& ray; + __forceinline ray_tfar(Ray& ray) : ray(ray) {} + __forceinline vfloat operator() () const { return ray.tfar; }; + }; + + template + static __forceinline bool intersect(const vbool& valid_i, + Ray& ray, + IntersectContext* context, + const LineSegments* geom, + const Precalculations& pre, + const Vec4vf& v0i, const Vec4vf& v1i, + const Vec4vf& vLi, const Vec4vf& vRi, + const Epilog& epilog) + { + const Vec3vf ray_org(ray.org.x, ray.org.y, ray.org.z); + const Vec3vf ray_dir(ray.dir.x, ray.dir.y, ray.dir.z); + const vfloat ray_tnear(ray.tnear()); + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec4vf v1 = enlargeRadiusToMinWidth(context,geom,ray_org,v1i); + const Vec4vf vL = enlargeRadiusToMinWidth(context,geom,ray_org,vLi); + const Vec4vf vR = enlargeRadiusToMinWidth(context,geom,ray_org,vRi); + return __roundline_internal::intersectConeSphere(valid_i,ray_org,ray_dir,ray_tnear,ray_tfar(ray),v0,v1,vL,vR,epilog); + } + }; + + template + struct RoundLinearCurveIntersectorK + { + typedef CurvePrecalculationsK Precalculations; + + struct ray_tfar { + RayK& ray; + size_t k; + __forceinline ray_tfar(RayK& ray, size_t k) : ray(ray), k(k) {} + __forceinline vfloat operator() () const { return ray.tfar[k]; }; + }; + + template + static __forceinline bool intersect(const vbool& valid_i, + RayK& ray, size_t k, + IntersectContext* context, + const LineSegments* geom, + const Precalculations& pre, + const Vec4vf& v0i, const Vec4vf& v1i, + const Vec4vf& vLi, const Vec4vf& vRi, + const Epilog& epilog) + { + const Vec3vf ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const Vec3vf ray_dir(ray.dir.x[k], ray.dir.y[k], ray.dir.z[k]); + const vfloat ray_tnear = ray.tnear()[k]; + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec4vf v1 = enlargeRadiusToMinWidth(context,geom,ray_org,v1i); + const Vec4vf vL = enlargeRadiusToMinWidth(context,geom,ray_org,vLi); + const Vec4vf vR = enlargeRadiusToMinWidth(context,geom,ray_org,vRi); + return __roundline_internal::intersectConeSphere(valid_i,ray_org,ray_dir,ray_tnear,ray_tfar(ray,k),v0,v1,vL,vR,epilog); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/roundlinei_intersector.h b/thirdparty/embree/kernels/geometry/roundlinei_intersector.h new file mode 100644 index 000000000000..079817335ebf --- /dev/null +++ b/thirdparty/embree/kernels/geometry/roundlinei_intersector.h @@ -0,0 +1,136 @@ +// ======================================================================== // +// Copyright 2009-2020 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#pragma once + +#include "roundline_intersector.h" +#include "intersector_epilog.h" + +namespace embree +{ + namespace isa + { + template + struct RoundLinearCurveMiIntersector1 + { + typedef LineMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom); + const vbool valid = line.template valid(); + RoundLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,vL,vR,Intersect1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom); + const vbool valid = line.template valid(); + return RoundLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,vL,vR,Occluded1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& line) + { + return PrimitivePointQuery1::pointQuery(query, context, line); + } + }; + + template + struct RoundLinearCurveMiMBIntersector1 + { + typedef LineMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom,ray.time()); + const vbool valid = line.template valid(); + RoundLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,vL,vR,Intersect1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom,ray.time()); + const vbool valid = line.template valid(); + return RoundLinearCurveIntersector1::intersect(valid,ray,context,geom,pre,v0,v1,vL,vR,Occluded1EpilogM(ray,context,line.geomID(),line.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& line) + { + return PrimitivePointQuery1::pointQuery(query, context, line); + } + }; + + template + struct RoundLinearCurveMiIntersectorK + { + typedef LineMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom); + const vbool valid = line.template valid(); + RoundLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,vL,vR,Intersect1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom); + const vbool valid = line.template valid(); + return RoundLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,vL,vR,Occluded1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + }; + + template + struct RoundLinearCurveMiMBIntersectorK + { + typedef LineMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(normal.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom,ray.time()[k]); + const vbool valid = line.template valid(); + RoundLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,vL,vR,Intersect1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& line) + { + STAT3(shadow.trav_prims,1,1,1); + const LineSegments* geom = context->scene->get(line.geomID()); + Vec4vf v0,v1,vL,vR; line.gather(v0,v1,vL,vR,geom,ray.time()[k]); + const vbool valid = line.template valid(); + return RoundLinearCurveIntersectorK::intersect(valid,ray,k,context,geom,pre,v0,v1,vL,vR,Occluded1KEpilogM(ray,k,context,line.geomID(),line.primID())); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/sphere_intersector.h b/thirdparty/embree/kernels/geometry/sphere_intersector.h new file mode 100644 index 000000000000..3ab90c29ef71 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/sphere_intersector.h @@ -0,0 +1,183 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "../common/scene_points.h" +#include "curve_intersector_precalculations.h" + +namespace embree +{ + namespace isa + { + template + struct SphereIntersectorHitM + { + __forceinline SphereIntersectorHitM() {} + + __forceinline SphereIntersectorHitM(const vfloat& t, const Vec3vf& Ng) + : vt(t), vNg(Ng) {} + + __forceinline void finalize() {} + + __forceinline Vec2f uv(const size_t i) const { + return Vec2f(0.0f, 0.0f); + } + __forceinline float t(const size_t i) const { + return vt[i]; + } + __forceinline Vec3fa Ng(const size_t i) const { + return Vec3fa(vNg.x[i], vNg.y[i], vNg.z[i]); + } + + public: + vfloat vt; + Vec3vf vNg; + }; + + template + struct SphereIntersector1 + { + typedef CurvePrecalculations1 Precalculations; + + template + static __forceinline bool intersect( + const vbool& valid_i, Ray& ray, + const Precalculations& pre, const Vec4vf& v0, const Epilog& epilog) + { + vbool valid = valid_i; + + const vfloat rd2 = rcp(dot(ray.dir, ray.dir)); + const Vec3vf ray_org(ray.org.x, ray.org.y, ray.org.z); + const Vec3vf ray_dir(ray.dir.x, ray.dir.y, ray.dir.z); + const Vec3vf center = v0.xyz(); + const vfloat radius = v0.w; + + const Vec3vf c0 = center - ray_org; + const vfloat projC0 = dot(c0, ray_dir) * rd2; + const Vec3vf perp = c0 - projC0 * ray_dir; + const vfloat l2 = dot(perp, perp); + const vfloat r2 = radius * radius; + valid &= (l2 <= r2); + if (unlikely(none(valid))) + return false; + + const vfloat td = sqrt((r2 - l2) * rd2); + const vfloat t_front = projC0 - td; + const vfloat t_back = projC0 + td; + + const vbool valid_front = valid & (ray.tnear() <= t_front) & (t_front <= ray.tfar); + const vbool valid_back = valid & (ray.tnear() <= t_back ) & (t_back <= ray.tfar); + + /* check if there is a first hit */ + const vbool valid_first = valid_front | valid_back; + if (unlikely(none(valid_first))) + return false; + + /* construct first hit */ + const vfloat td_front = -td; + const vfloat td_back = +td; + const vfloat t_first = select(valid_front, t_front, t_back); + const Vec3vf Ng_first = select(valid_front, td_front, td_back) * ray_dir - perp; + SphereIntersectorHitM hit(t_first, Ng_first); + + /* invoke intersection filter for first hit */ + const bool is_hit_first = epilog(valid_first, hit); + + /* check for possible second hits before potentially accepted hit */ + const vfloat t_second = t_back; + const vbool valid_second = valid_front & valid_back & (t_second <= ray.tfar); + if (unlikely(none(valid_second))) + return is_hit_first; + + /* invoke intersection filter for second hit */ + const Vec3vf Ng_second = td_back * ray_dir - perp; + hit = SphereIntersectorHitM (t_second, Ng_second); + const bool is_hit_second = epilog(valid_second, hit); + + return is_hit_first | is_hit_second; + } + + template + static __forceinline bool intersect( + const vbool& valid_i, Ray& ray, IntersectContext* context, const Points* geom, + const Precalculations& pre, const Vec4vf& v0i, const Epilog& epilog) + { + const Vec3vf ray_org(ray.org.x, ray.org.y, ray.org.z); + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + return intersect(valid_i,ray,pre,v0,epilog); + } + }; + + template + struct SphereIntersectorK + { + typedef CurvePrecalculationsK Precalculations; + + template + static __forceinline bool intersect(const vbool& valid_i, + RayK& ray, size_t k, + IntersectContext* context, + const Points* geom, + const Precalculations& pre, + const Vec4vf& v0i, + const Epilog& epilog) + { + vbool valid = valid_i; + + const Vec3vf ray_org(ray.org.x[k], ray.org.y[k], ray.org.z[k]); + const Vec3vf ray_dir(ray.dir.x[k], ray.dir.y[k], ray.dir.z[k]); + const vfloat rd2 = rcp(dot(ray_dir, ray_dir)); + + const Vec4vf v0 = enlargeRadiusToMinWidth(context,geom,ray_org,v0i); + const Vec3vf center = v0.xyz(); + const vfloat radius = v0.w; + + const Vec3vf c0 = center - ray_org; + const vfloat projC0 = dot(c0, ray_dir) * rd2; + const Vec3vf perp = c0 - projC0 * ray_dir; + const vfloat l2 = dot(perp, perp); + const vfloat r2 = radius * radius; + valid &= (l2 <= r2); + if (unlikely(none(valid))) + return false; + + const vfloat td = sqrt((r2 - l2) * rd2); + const vfloat t_front = projC0 - td; + const vfloat t_back = projC0 + td; + + const vbool valid_front = valid & (ray.tnear()[k] <= t_front) & (t_front <= ray.tfar[k]); + const vbool valid_back = valid & (ray.tnear()[k] <= t_back ) & (t_back <= ray.tfar[k]); + + /* check if there is a first hit */ + const vbool valid_first = valid_front | valid_back; + if (unlikely(none(valid_first))) + return false; + + /* construct first hit */ + const vfloat td_front = -td; + const vfloat td_back = +td; + const vfloat t_first = select(valid_front, t_front, t_back); + const Vec3vf Ng_first = select(valid_front, td_front, td_back) * ray_dir - perp; + SphereIntersectorHitM hit(t_first, Ng_first); + + /* invoke intersection filter for first hit */ + const bool is_hit_first = epilog(valid_first, hit); + + /* check for possible second hits before potentially accepted hit */ + const vfloat t_second = t_back; + const vbool valid_second = valid_front & valid_back & (t_second <= ray.tfar[k]); + if (unlikely(none(valid_second))) + return is_hit_first; + + /* invoke intersection filter for second hit */ + const Vec3vf Ng_second = td_back * ray_dir - perp; + hit = SphereIntersectorHitM (t_second, Ng_second); + const bool is_hit_second = epilog(valid_second, hit); + + return is_hit_first | is_hit_second; + } + }; + } // namespace isa +} // namespace embree diff --git a/thirdparty/embree/kernels/geometry/spherei_intersector.h b/thirdparty/embree/kernels/geometry/spherei_intersector.h new file mode 100644 index 000000000000..11468476022d --- /dev/null +++ b/thirdparty/embree/kernels/geometry/spherei_intersector.h @@ -0,0 +1,156 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "intersector_epilog.h" +#include "pointi.h" +#include "sphere_intersector.h" + +namespace embree +{ + namespace isa + { + template + struct SphereMiIntersector1 + { + typedef PointMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, + RayHit& ray, + IntersectContext* context, + const Primitive& sphere) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom); + const vbool valid = sphere.template valid(); + SphereIntersector1::intersect( + valid, ray, context, geom, pre, v0, Intersect1EpilogM(ray, context, sphere.geomID(), sphere.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, + Ray& ray, + IntersectContext* context, + const Primitive& sphere) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom); + const vbool valid = sphere.template valid(); + return SphereIntersector1::intersect( + valid, ray, context, geom, pre, v0, Occluded1EpilogM(ray, context, sphere.geomID(), sphere.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, + PointQueryContext* context, + const Primitive& sphere) + { + return PrimitivePointQuery1::pointQuery(query, context, sphere); + } + }; + + template + struct SphereMiMBIntersector1 + { + typedef PointMi Primitive; + typedef CurvePrecalculations1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, + RayHit& ray, + IntersectContext* context, + const Primitive& sphere) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom, ray.time()); + const vbool valid = sphere.template valid(); + SphereIntersector1::intersect( + valid, ray, context, geom, pre, v0, Intersect1EpilogM(ray, context, sphere.geomID(), sphere.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, + Ray& ray, + IntersectContext* context, + const Primitive& sphere) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom, ray.time()); + const vbool valid = sphere.template valid(); + return SphereIntersector1::intersect( + valid, ray, context, geom, pre, v0, Occluded1EpilogM(ray, context, sphere.geomID(), sphere.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, + PointQueryContext* context, + const Primitive& sphere) + { + return PrimitivePointQuery1::pointQuery(query, context, sphere); + } + }; + + template + struct SphereMiIntersectorK + { + typedef PointMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect( + const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& sphere) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom); + const vbool valid = sphere.template valid(); + SphereIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Intersect1KEpilogM(ray, k, context, sphere.geomID(), sphere.primID())); + } + + static __forceinline bool occluded( + const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& sphere) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom); + const vbool valid = sphere.template valid(); + return SphereIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Occluded1KEpilogM(ray, k, context, sphere.geomID(), sphere.primID())); + } + }; + + template + struct SphereMiMBIntersectorK + { + typedef PointMi Primitive; + typedef CurvePrecalculationsK Precalculations; + + static __forceinline void intersect( + const Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& sphere) + { + STAT3(normal.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom, ray.time()[k]); + const vbool valid = sphere.template valid(); + SphereIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Intersect1KEpilogM(ray, k, context, sphere.geomID(), sphere.primID())); + } + + static __forceinline bool occluded( + const Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& sphere) + { + STAT3(shadow.trav_prims, 1, 1, 1); + const Points* geom = context->scene->get(sphere.geomID()); + Vec4vf v0; sphere.gather(v0, geom, ray.time()[k]); + const vbool valid = sphere.template valid(); + return SphereIntersectorK::intersect( + valid, ray, k, context, geom, pre, v0, + Occluded1KEpilogM(ray, k, context, sphere.geomID(), sphere.primID())); + } + }; + } // namespace isa +} // namespace embree diff --git a/thirdparty/embree/kernels/geometry/subdivpatch1.h b/thirdparty/embree/kernels/geometry/subdivpatch1.h new file mode 100644 index 000000000000..94ad46ad87ad --- /dev/null +++ b/thirdparty/embree/kernels/geometry/subdivpatch1.h @@ -0,0 +1,38 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../geometry/primitive.h" +#include "../subdiv/subdivpatch1base.h" + +namespace embree +{ + + struct __aligned(64) SubdivPatch1 : public SubdivPatch1Base + { + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + + static Type type; + + public: + + /*! constructor for cached subdiv patch */ + SubdivPatch1 (const unsigned int gID, + const unsigned int pID, + const unsigned int subPatch, + const SubdivMesh *const mesh, + const size_t time, + const Vec2f uv[4], + const float edge_level[4], + const int subdiv[4], + const int simd_width) + : SubdivPatch1Base(gID,pID,subPatch,mesh,time,uv,edge_level,subdiv,simd_width) {} + }; +} diff --git a/thirdparty/embree/kernels/geometry/subdivpatch1_intersector.h b/thirdparty/embree/kernels/geometry/subdivpatch1_intersector.h new file mode 100644 index 000000000000..74ec1de25868 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/subdivpatch1_intersector.h @@ -0,0 +1,237 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "subdivpatch1.h" +#include "grid_soa.h" +#include "grid_soa_intersector1.h" +#include "grid_soa_intersector_packet.h" +#include "../common/ray.h" + +namespace embree +{ + namespace isa + { + template + class SubdivPatch1Precalculations : public T + { + public: + __forceinline SubdivPatch1Precalculations (const Ray& ray, const void* ptr) + : T(ray,ptr) {} + }; + + template + class SubdivPatch1PrecalculationsK : public T + { + public: + __forceinline SubdivPatch1PrecalculationsK (const vbool& valid, RayK& ray) + : T(valid,ray) {} + }; + + class SubdivPatch1Intersector1 + { + public: + typedef GridSOA Primitive; + typedef SubdivPatch1Precalculations Precalculations; + + static __forceinline bool processLazyNode(Precalculations& pre, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + lazy_node = prim->root(0); + pre.grid = (Primitive*)prim; + return false; + } + + /*! Intersect a ray with the primitive. */ + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) GridSOAIntersector1::intersect(pre,ray,context,prim,lazy_node); + else processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, size_t ty0, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) { + intersect(This,pre,ray,context,prim,ty,tray,lazy_node); + } + + /*! Test if the ray is occluded by the primitive */ + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) return GridSOAIntersector1::occluded(pre,ray,context,prim,lazy_node); + else return processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, size_t ty0, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) { + return occluded(This,pre,ray,context,prim,ty,tray,lazy_node); + } + + template + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t ty, const TravPointQuery &tquery, size_t& lazy_node) + { + // TODO: PointQuery implement + assert(false && "not implemented"); + return false; + } + + template + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, size_t ty0, const Primitive* prim, size_t ty, const TravPointQuery &tquery, size_t& lazy_node) { + return pointQuery(This,query,context,prim,ty,tquery,lazy_node); + } + }; + + class SubdivPatch1MBIntersector1 + { + public: + typedef SubdivPatch1 Primitive; + typedef GridSOAMBIntersector1::Precalculations Precalculations; + + static __forceinline bool processLazyNode(Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim_i, size_t& lazy_node) + { + Primitive* prim = (Primitive*) prim_i; + GridSOA* grid = nullptr; + grid = (GridSOA*) prim->root_ref.get(); + pre.itime = getTimeSegment(ray.time(), float(grid->time_steps-1), pre.ftime); + lazy_node = grid->root(pre.itime); + pre.grid = grid; + return false; + } + + /*! Intersect a ray with the primitive. */ + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) GridSOAMBIntersector1::intersect(pre,ray,context,prim,lazy_node); + else processLazyNode(pre,ray,context,prim,lazy_node); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, size_t ty0, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) { + intersect(This,pre,ray,context,prim,ty,tray,lazy_node); + } + + /*! Test if the ray is occluded by the primitive */ + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) return GridSOAMBIntersector1::occluded(pre,ray,context,prim,lazy_node); + else return processLazyNode(pre,ray,context,prim,lazy_node); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, size_t ty0, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) { + return occluded(This,pre,ray,context,prim,ty,tray,lazy_node); + } + + template + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t ty, const TravPointQuery &tquery, size_t& lazy_node) + { + // TODO: PointQuery implement + assert(false && "not implemented"); + return false; + } + + template + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, size_t ty0, const Primitive* prim, size_t ty, const TravPointQuery &tquery, size_t& lazy_node) { + return pointQuery(This,query,context,prim,ty,tquery,lazy_node); + } + }; + + template + struct SubdivPatch1IntersectorK + { + typedef GridSOA Primitive; + typedef SubdivPatch1PrecalculationsK::Precalculations> Precalculations; + + static __forceinline bool processLazyNode(Precalculations& pre, IntersectContext* context, const Primitive* prim, size_t& lazy_node) + { + lazy_node = prim->root(0); + pre.grid = (Primitive*)prim; + return false; + } + + template + static __forceinline void intersect(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRayK &tray, size_t& lazy_node) + { + if (likely(ty == 0)) GridSOAIntersectorK::intersect(valid,pre,ray,context,prim,lazy_node); + else processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline vbool occluded(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRayK &tray, size_t& lazy_node) + { + if (likely(ty == 0)) return GridSOAIntersectorK::occluded(valid,pre,ray,context,prim,lazy_node); + else return processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) GridSOAIntersectorK::intersect(pre,ray,k,context,prim,lazy_node); + else processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) return GridSOAIntersectorK::occluded(pre,ray,k,context,prim,lazy_node); + else return processLazyNode(pre,context,prim,lazy_node); + } + }; + + typedef SubdivPatch1IntersectorK<4> SubdivPatch1Intersector4; + typedef SubdivPatch1IntersectorK<8> SubdivPatch1Intersector8; + typedef SubdivPatch1IntersectorK<16> SubdivPatch1Intersector16; + + template + struct SubdivPatch1MBIntersectorK + { + typedef SubdivPatch1 Primitive; + //typedef GridSOAMBIntersectorK::Precalculations Precalculations; + typedef SubdivPatch1PrecalculationsK::Precalculations> Precalculations; + + static __forceinline bool processLazyNode(Precalculations& pre, IntersectContext* context, const Primitive* prim_i, size_t& lazy_node) + { + Primitive* prim = (Primitive*) prim_i; + GridSOA* grid = (GridSOA*) prim->root_ref.get(); + lazy_node = grid->troot; + pre.grid = grid; + return false; + } + + template + static __forceinline void intersect(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRayK &tray, size_t& lazy_node) + { + if (likely(ty == 0)) GridSOAMBIntersectorK::intersect(valid,pre,ray,context,prim,lazy_node); + else processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline vbool occluded(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t ty, const TravRayK &tray, size_t& lazy_node) + { + if (likely(ty == 0)) return GridSOAMBIntersectorK::occluded(valid,pre,ray,context,prim,lazy_node); + else return processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) GridSOAMBIntersectorK::intersect(pre,ray,k,context,prim,lazy_node); + else processLazyNode(pre,context,prim,lazy_node); + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t ty, const TravRay &tray, size_t& lazy_node) + { + if (likely(ty == 0)) return GridSOAMBIntersectorK::occluded(pre,ray,k,context,prim,lazy_node); + else return processLazyNode(pre,context,prim,lazy_node); + } + }; + + typedef SubdivPatch1MBIntersectorK<4> SubdivPatch1MBIntersector4; + typedef SubdivPatch1MBIntersectorK<8> SubdivPatch1MBIntersector8; + typedef SubdivPatch1MBIntersectorK<16> SubdivPatch1MBIntersector16; + } +} diff --git a/thirdparty/embree/kernels/geometry/subgrid.h b/thirdparty/embree/kernels/geometry/subgrid.h new file mode 100644 index 000000000000..39fa6fb0f048 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/subgrid.h @@ -0,0 +1,517 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/ray.h" +#include "../common/scene_grid_mesh.h" +#include "../bvh/bvh.h" + +namespace embree +{ + /* Stores M quads from an indexed face set */ + struct SubGrid + { + /* Virtual interface to query information about the quad type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored quads */ + static __forceinline size_t max_size() { return 1; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline SubGrid() { } + + /* Construction from vertices and IDs */ + __forceinline SubGrid(const unsigned int x, + const unsigned int y, + const unsigned int geomID, + const unsigned int primID) + : _x(x), _y(y), _geomID(geomID), _primID(primID) + { + } + + __forceinline bool invalid3x3X() const { return (unsigned int)_x & (1<<15); } + __forceinline bool invalid3x3Y() const { return (unsigned int)_y & (1<<15); } + + /* Gather the quads */ + __forceinline void gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const GridMesh* const mesh, + const GridMesh::Grid &g) const + { + /* first quad always valid */ + const size_t vtxID00 = g.startVtxID + x() + y() * g.lineVtxOffset; + const size_t vtxID01 = vtxID00 + 1; + const vfloat4 vtx00 = vfloat4::loadu(mesh->vertexPtr(vtxID00)); + const vfloat4 vtx01 = vfloat4::loadu(mesh->vertexPtr(vtxID01)); + const size_t vtxID10 = vtxID00 + g.lineVtxOffset; + const size_t vtxID11 = vtxID01 + g.lineVtxOffset; + const vfloat4 vtx10 = vfloat4::loadu(mesh->vertexPtr(vtxID10)); + const vfloat4 vtx11 = vfloat4::loadu(mesh->vertexPtr(vtxID11)); + + /* deltaX => vtx02, vtx12 */ + const size_t deltaX = invalid3x3X() ? 0 : 1; + const size_t vtxID02 = vtxID01 + deltaX; + const vfloat4 vtx02 = vfloat4::loadu(mesh->vertexPtr(vtxID02)); + const size_t vtxID12 = vtxID11 + deltaX; + const vfloat4 vtx12 = vfloat4::loadu(mesh->vertexPtr(vtxID12)); + + /* deltaY => vtx20, vtx21 */ + const size_t deltaY = invalid3x3Y() ? 0 : g.lineVtxOffset; + const size_t vtxID20 = vtxID10 + deltaY; + const size_t vtxID21 = vtxID11 + deltaY; + const vfloat4 vtx20 = vfloat4::loadu(mesh->vertexPtr(vtxID20)); + const vfloat4 vtx21 = vfloat4::loadu(mesh->vertexPtr(vtxID21)); + + /* deltaX/deltaY => vtx22 */ + const size_t vtxID22 = vtxID11 + deltaX + deltaY; + const vfloat4 vtx22 = vfloat4::loadu(mesh->vertexPtr(vtxID22)); + + transpose(vtx00,vtx01,vtx11,vtx10,p0.x,p0.y,p0.z); + transpose(vtx01,vtx02,vtx12,vtx11,p1.x,p1.y,p1.z); + transpose(vtx11,vtx12,vtx22,vtx21,p2.x,p2.y,p2.z); + transpose(vtx10,vtx11,vtx21,vtx20,p3.x,p3.y,p3.z); + } + + template + __forceinline vfloat4 getVertexMB(const GridMesh* const mesh, const size_t offset, const size_t itime, const float ftime) const + { + const T v0 = T::loadu(mesh->vertexPtr(offset,itime+0)); + const T v1 = T::loadu(mesh->vertexPtr(offset,itime+1)); + return lerp(v0,v1,ftime); + } + + /* Gather the quads */ + __forceinline void gatherMB(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const GridMesh* const mesh, + const GridMesh::Grid &g, + const size_t itime, + const float ftime) const + { + /* first quad always valid */ + const size_t vtxID00 = g.startVtxID + x() + y() * g.lineVtxOffset; + const size_t vtxID01 = vtxID00 + 1; + const vfloat4 vtx00 = getVertexMB(mesh,vtxID00,itime,ftime); + const vfloat4 vtx01 = getVertexMB(mesh,vtxID01,itime,ftime); + const size_t vtxID10 = vtxID00 + g.lineVtxOffset; + const size_t vtxID11 = vtxID01 + g.lineVtxOffset; + const vfloat4 vtx10 = getVertexMB(mesh,vtxID10,itime,ftime); + const vfloat4 vtx11 = getVertexMB(mesh,vtxID11,itime,ftime); + + /* deltaX => vtx02, vtx12 */ + const size_t deltaX = invalid3x3X() ? 0 : 1; + const size_t vtxID02 = vtxID01 + deltaX; + const vfloat4 vtx02 = getVertexMB(mesh,vtxID02,itime,ftime); + const size_t vtxID12 = vtxID11 + deltaX; + const vfloat4 vtx12 = getVertexMB(mesh,vtxID12,itime,ftime); + + /* deltaY => vtx20, vtx21 */ + const size_t deltaY = invalid3x3Y() ? 0 : g.lineVtxOffset; + const size_t vtxID20 = vtxID10 + deltaY; + const size_t vtxID21 = vtxID11 + deltaY; + const vfloat4 vtx20 = getVertexMB(mesh,vtxID20,itime,ftime); + const vfloat4 vtx21 = getVertexMB(mesh,vtxID21,itime,ftime); + + /* deltaX/deltaY => vtx22 */ + const size_t vtxID22 = vtxID11 + deltaX + deltaY; + const vfloat4 vtx22 = getVertexMB(mesh,vtxID22,itime,ftime); + + transpose(vtx00,vtx01,vtx11,vtx10,p0.x,p0.y,p0.z); + transpose(vtx01,vtx02,vtx12,vtx11,p1.x,p1.y,p1.z); + transpose(vtx11,vtx12,vtx22,vtx21,p2.x,p2.y,p2.z); + transpose(vtx10,vtx11,vtx21,vtx20,p3.x,p3.y,p3.z); + } + + + + /* Gather the quads */ + __forceinline void gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const Scene *const scene) const + { + const GridMesh* const mesh = scene->get(geomID()); + const GridMesh::Grid &g = mesh->grid(primID()); + gather(p0,p1,p2,p3,mesh,g); + } + + /* Gather the quads in the motion blur case */ + __forceinline void gatherMB(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + Vec3vf4& p3, + const Scene *const scene, + const size_t itime, + const float ftime) const + { + const GridMesh* const mesh = scene->get(geomID()); + const GridMesh::Grid &g = mesh->grid(primID()); + gatherMB(p0,p1,p2,p3,mesh,g,itime,ftime); + } + + /* Gather the quads */ + __forceinline void gather(Vec3fa vtx[16], const Scene *const scene) const + { + const GridMesh* mesh = scene->get(geomID()); + const GridMesh::Grid &g = mesh->grid(primID()); + + /* first quad always valid */ + const size_t vtxID00 = g.startVtxID + x() + y() * g.lineVtxOffset; + const size_t vtxID01 = vtxID00 + 1; + const Vec3fa vtx00 = Vec3fa::loadu(mesh->vertexPtr(vtxID00)); + const Vec3fa vtx01 = Vec3fa::loadu(mesh->vertexPtr(vtxID01)); + const size_t vtxID10 = vtxID00 + g.lineVtxOffset; + const size_t vtxID11 = vtxID01 + g.lineVtxOffset; + const Vec3fa vtx10 = Vec3fa::loadu(mesh->vertexPtr(vtxID10)); + const Vec3fa vtx11 = Vec3fa::loadu(mesh->vertexPtr(vtxID11)); + + /* deltaX => vtx02, vtx12 */ + const size_t deltaX = invalid3x3X() ? 0 : 1; + const size_t vtxID02 = vtxID01 + deltaX; + const Vec3fa vtx02 = Vec3fa::loadu(mesh->vertexPtr(vtxID02)); + const size_t vtxID12 = vtxID11 + deltaX; + const Vec3fa vtx12 = Vec3fa::loadu(mesh->vertexPtr(vtxID12)); + + /* deltaY => vtx20, vtx21 */ + const size_t deltaY = invalid3x3Y() ? 0 : g.lineVtxOffset; + const size_t vtxID20 = vtxID10 + deltaY; + const size_t vtxID21 = vtxID11 + deltaY; + const Vec3fa vtx20 = Vec3fa::loadu(mesh->vertexPtr(vtxID20)); + const Vec3fa vtx21 = Vec3fa::loadu(mesh->vertexPtr(vtxID21)); + + /* deltaX/deltaY => vtx22 */ + const size_t vtxID22 = vtxID11 + deltaX + deltaY; + const Vec3fa vtx22 = Vec3fa::loadu(mesh->vertexPtr(vtxID22)); + + vtx[ 0] = vtx00; vtx[ 1] = vtx01; vtx[ 2] = vtx11; vtx[ 3] = vtx10; + vtx[ 4] = vtx01; vtx[ 5] = vtx02; vtx[ 6] = vtx12; vtx[ 7] = vtx11; + vtx[ 8] = vtx10; vtx[ 9] = vtx11; vtx[10] = vtx21; vtx[11] = vtx20; + vtx[12] = vtx11; vtx[13] = vtx12; vtx[14] = vtx22; vtx[15] = vtx21; + } + + /* Gather the quads */ + __forceinline void gatherMB(vfloat4 vtx[16], const Scene *const scene, const size_t itime, const float ftime) const + { + const GridMesh* mesh = scene->get(geomID()); + const GridMesh::Grid &g = mesh->grid(primID()); + + /* first quad always valid */ + const size_t vtxID00 = g.startVtxID + x() + y() * g.lineVtxOffset; + const size_t vtxID01 = vtxID00 + 1; + const vfloat4 vtx00 = getVertexMB(mesh,vtxID00,itime,ftime); + const vfloat4 vtx01 = getVertexMB(mesh,vtxID01,itime,ftime); + const size_t vtxID10 = vtxID00 + g.lineVtxOffset; + const size_t vtxID11 = vtxID01 + g.lineVtxOffset; + const vfloat4 vtx10 = getVertexMB(mesh,vtxID10,itime,ftime); + const vfloat4 vtx11 = getVertexMB(mesh,vtxID11,itime,ftime); + + /* deltaX => vtx02, vtx12 */ + const size_t deltaX = invalid3x3X() ? 0 : 1; + const size_t vtxID02 = vtxID01 + deltaX; + const vfloat4 vtx02 = getVertexMB(mesh,vtxID02,itime,ftime); + const size_t vtxID12 = vtxID11 + deltaX; + const vfloat4 vtx12 = getVertexMB(mesh,vtxID12,itime,ftime); + + /* deltaY => vtx20, vtx21 */ + const size_t deltaY = invalid3x3Y() ? 0 : g.lineVtxOffset; + const size_t vtxID20 = vtxID10 + deltaY; + const size_t vtxID21 = vtxID11 + deltaY; + const vfloat4 vtx20 = getVertexMB(mesh,vtxID20,itime,ftime); + const vfloat4 vtx21 = getVertexMB(mesh,vtxID21,itime,ftime); + + /* deltaX/deltaY => vtx22 */ + const size_t vtxID22 = vtxID11 + deltaX + deltaY; + const vfloat4 vtx22 = getVertexMB(mesh,vtxID22,itime,ftime); + + vtx[ 0] = vtx00; vtx[ 1] = vtx01; vtx[ 2] = vtx11; vtx[ 3] = vtx10; + vtx[ 4] = vtx01; vtx[ 5] = vtx02; vtx[ 6] = vtx12; vtx[ 7] = vtx11; + vtx[ 8] = vtx10; vtx[ 9] = vtx11; vtx[10] = vtx21; vtx[11] = vtx20; + vtx[12] = vtx11; vtx[13] = vtx12; vtx[14] = vtx22; vtx[15] = vtx21; + } + + + /* Calculate the bounds of the subgrid */ + __forceinline const BBox3fa bounds(const Scene *const scene, const size_t itime=0) const + { + BBox3fa bounds = empty; + FATAL("not implemented yet"); + return bounds; + } + + /* Calculate the linear bounds of the primitive */ + __forceinline LBBox3fa linearBounds(const Scene* const scene, const size_t itime) + { + return LBBox3fa(bounds(scene,itime+0),bounds(scene,itime+1)); + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, size_t itime, size_t numTimeSteps) + { + LBBox3fa allBounds = empty; + FATAL("not implemented yet"); + return allBounds; + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, const BBox1f time_range) + { + LBBox3fa allBounds = empty; + FATAL("not implemented yet"); + return allBounds; + } + + + friend embree_ostream operator<<(embree_ostream cout, const SubGrid& sg) { + return cout << "SubGrid " << " ( x " << sg.x() << ", y = " << sg.y() << ", geomID = " << sg.geomID() << ", primID = " << sg.primID() << " )"; + } + + __forceinline unsigned int geomID() const { return _geomID; } + __forceinline unsigned int primID() const { return _primID; } + __forceinline unsigned int x() const { return (unsigned int)_x & 0x7fff; } + __forceinline unsigned int y() const { return (unsigned int)_y & 0x7fff; } + + private: + unsigned short _x; + unsigned short _y; + unsigned int _geomID; // geometry ID of mesh + unsigned int _primID; // primitive ID of primitive inside mesh + }; + + struct SubGridID { + unsigned short x; + unsigned short y; + unsigned int primID; + + __forceinline SubGridID() {} + __forceinline SubGridID(const unsigned int x, const unsigned int y, const unsigned int primID) : + x(x), y(y), primID(primID) {} + }; + + /* QuantizedBaseNode as large subgrid leaf */ + template + struct SubGridQBVHN + { + /* Virtual interface to query information about the quad type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + __forceinline size_t size() const + { + for (size_t i=0;i::AABBNode node; + node.clear(); + for (size_t i=0;i::QuantizedBaseNode qnode; + + unsigned int _geomID; // geometry ID of mesh + + + friend embree_ostream operator<<(embree_ostream cout, const SubGridQBVHN& sg) { + cout << "SubGridQBVHN " << embree_endl; + for (size_t i=0;i + typename SubGridQBVHN::Type SubGridQBVHN::type; + + typedef SubGridQBVHN<4> SubGridQBVH4; + typedef SubGridQBVHN<8> SubGridQBVH8; + + + /* QuantizedBaseNode as large subgrid leaf */ + template + struct SubGridMBQBVHN + { + /* Virtual interface to query information about the quad type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + __forceinline size_t size() const + { + for (size_t i=0;i::AABBNode node0,node1; + node0.clear(); + node1.clear(); + for (size_t i=0;i + __forceinline vfloat adjustTime(const vfloat &t) const { return time_scale * (t-time_offset); } + + public: + SubGridID subgridIDs[N]; + + typename BVHN::QuantizedBaseNodeMB qnode; + + float time_offset; + float time_scale; + unsigned int _geomID; // geometry ID of mesh + + + friend embree_ostream operator<<(embree_ostream cout, const SubGridMBQBVHN& sg) { + cout << "SubGridMBQBVHN " << embree_endl; + for (size_t i=0;i + struct SubGridIntersector1Moeller + { + typedef SubGridQBVHN Primitive; + typedef SubGridQuadMIntersector1MoellerTrumbore<4,filter> Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(normal.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + pre.intersect(ray,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(shadow.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + return pre.occluded(ray,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const SubGrid& subgrid) + { + STAT3(point_query.trav_prims,1,1,1); + AccelSet* accel = (AccelSet*)context->scene->get(subgrid.geomID()); + assert(accel); + context->geomID = subgrid.geomID(); + context->primID = subgrid.primID(); + return accel->pointQuery(query, context); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); +#if defined(__AVX__) + STAT3(normal.trav_hit_boxes[popcnt(mask)],1,1,1); +#endif + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (unlikely(dist[ID] > ray.tfar)) continue; + intersect(pre,ray,context,prim[i].subgrid(ID)); + } + } + } + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (occluded(pre,ray,context,prim[i].subgrid(ID))) + return true; + } + } + return false; + } + + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t num, const TravPointQuery &tquery, size_t& lazy_node) + { + bool changed = false; + for (size_t i=0;i dist; + size_t mask; + if (likely(context->query_type == POINT_QUERY_TYPE_SPHERE)) { + mask = BVHNQuantizedBaseNodePointQuerySphere1::pointQuery(&prim[i].qnode,tquery,dist); + } else { + mask = BVHNQuantizedBaseNodePointQueryAABB1::pointQuery(&prim[i].qnode,tquery,dist); + } + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + changed |= pointQuery(query, context, prim[i].subgrid(ID)); + } + } + return changed; + } + }; + + template + struct SubGridIntersector1Pluecker + { + typedef SubGridQBVHN Primitive; + typedef SubGridQuadMIntersector1Pluecker<4,filter> Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(normal.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + pre.intersect(ray,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(shadow.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + return pre.occluded(ray,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const SubGrid& subgrid) + { + STAT3(point_query.trav_prims,1,1,1); + AccelSet* accel = (AccelSet*)context->scene->get(subgrid.geomID()); + context->geomID = subgrid.geomID(); + context->primID = subgrid.primID(); + return accel->pointQuery(query, context); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); +#if defined(__AVX__) + STAT3(normal.trav_hit_boxes[popcnt(mask)],1,1,1); +#endif + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (unlikely(dist[ID] > ray.tfar)) continue; + intersect(pre,ray,context,prim[i].subgrid(ID)); + } + } + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (occluded(pre,ray,context,prim[i].subgrid(ID))) + return true; + } + } + return false; + } + + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t num, const TravPointQuery &tquery, size_t& lazy_node) + { + bool changed = false; + for (size_t i=0;i dist; + size_t mask; + if (likely(context->query_type == POINT_QUERY_TYPE_SPHERE)) { + mask = BVHNQuantizedBaseNodePointQuerySphere1::pointQuery(&prim[i].qnode,tquery,dist); + } else { + mask = BVHNQuantizedBaseNodePointQueryAABB1::pointQuery(&prim[i].qnode,tquery,dist); + } +#if defined(__AVX__) + STAT3(point_query.trav_hit_boxes[popcnt(mask)],1,1,1); +#endif + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + changed |= pointQuery(query, context, prim[i].subgrid(ID)); + } + } + return changed; + } + }; + + template + struct SubGridIntersectorKMoeller + { + typedef SubGridQBVHN Primitive; + typedef SubGridQuadMIntersectorKMoellerTrumbore<4,K,filter> Precalculations; + + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const SubGrid& subgrid) + { + Vec3fa vtx[16]; + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + subgrid.gather(vtx,context->scene); + for (unsigned int i=0; i<4; i++) + { + const Vec3vf p0 = vtx[i*4+0]; + const Vec3vf p1 = vtx[i*4+1]; + const Vec3vf p2 = vtx[i*4+2]; + const Vec3vf p3 = vtx[i*4+3]; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + pre.intersectK(valid_i,ray,p0,p1,p2,p3,g,subgrid,i,IntersectKEpilogM<4,K,filter>(ray,context,subgrid.geomID(),subgrid.primID(),i)); + } + } + + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const SubGrid& subgrid) + { + vbool valid0 = valid_i; + Vec3fa vtx[16]; + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + subgrid.gather(vtx,context->scene); + for (unsigned int i=0; i<4; i++) + { + const Vec3vf p0 = vtx[i*4+0]; + const Vec3vf p1 = vtx[i*4+1]; + const Vec3vf p2 = vtx[i*4+2]; + const Vec3vf p3 = vtx[i*4+3]; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + if (pre.intersectK(valid0,ray,p0,p1,p2,p3,g,subgrid,i,OccludedKEpilogM<4,K,filter>(valid0,ray,context,subgrid.geomID(),subgrid.primID(),i))) + break; + } + return !valid0; + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(normal.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + pre.intersect1(ray,k,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(shadow.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,g,subgrid); + } + + template + static __forceinline void intersect(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersectorK isecK; + for (size_t j=0;j dist; + while(m_valid) + { + const size_t i = bscf(m_valid); + if (none(valid & isecK.intersectK(&prim[j].qnode,i,tray,dist))) continue; + intersect(valid,pre,ray,context,prim[j].subgrid(i)); + } + } + } + + template + static __forceinline vbool occluded(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersectorK isecK; + vbool valid0 = valid; + for (size_t j=0;j dist; + while(m_valid) + { + const size_t i = bscf(m_valid); + if (none(valid0 & isecK.intersectK(&prim[j].qnode,i,tray,dist))) continue; + valid0 &= !occluded(valid0,pre,ray,context,prim[j].subgrid(i)); + if (none(valid0)) break; + } + } + return !valid0; + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (unlikely(dist[ID] > ray.tfar[k])) continue; + intersect(pre,ray,k,context,prim[i].subgrid(ID)); + } + } + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (occluded(pre,ray,k,context,prim[i].subgrid(ID))) + return true; + } + } + return false; + } + }; + + + template + struct SubGridIntersectorKPluecker + { + typedef SubGridQBVHN Primitive; + typedef SubGridQuadMIntersectorKPluecker<4,K,filter> Precalculations; + + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const SubGrid& subgrid) + { + Vec3fa vtx[16]; + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + subgrid.gather(vtx,context->scene); + for (unsigned int i=0; i<4; i++) + { + const Vec3vf p0 = vtx[i*4+0]; + const Vec3vf p1 = vtx[i*4+1]; + const Vec3vf p2 = vtx[i*4+2]; + const Vec3vf p3 = vtx[i*4+3]; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + pre.intersectK(valid_i,ray,p0,p1,p2,p3,g,subgrid,i,IntersectKEpilogM<4,K,filter>(ray,context,subgrid.geomID(),subgrid.primID(),i)); + } + } + + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const SubGrid& subgrid) + { + vbool valid0 = valid_i; + Vec3fa vtx[16]; + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + subgrid.gather(vtx,context->scene); + for (unsigned int i=0; i<4; i++) + { + const Vec3vf p0 = vtx[i*4+0]; + const Vec3vf p1 = vtx[i*4+1]; + const Vec3vf p2 = vtx[i*4+2]; + const Vec3vf p3 = vtx[i*4+3]; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + if (pre.intersectK(valid0,ray,p0,p1,p2,p3,g,subgrid,i,OccludedKEpilogM<4,K,filter>(valid0,ray,context,subgrid.geomID(),subgrid.primID(),i))) + break; + } + return !valid0; + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(normal.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + pre.intersect1(ray,k,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(shadow.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + Vec3vf4 v0,v1,v2,v3; subgrid.gather(v0,v1,v2,v3,context->scene); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,g,subgrid); + } + + template + static __forceinline void intersect(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersectorK isecK; + for (size_t j=0;j dist; + while(m_valid) + { + const size_t i = bscf(m_valid); + if (none(valid & isecK.intersectK(&prim[j].qnode,i,tray,dist))) continue; + intersect(valid,pre,ray,context,prim[j].subgrid(i)); + } + } + } + + template + static __forceinline vbool occluded(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersectorK isecK; + vbool valid0 = valid; + for (size_t j=0;j dist; + while(m_valid) + { + const size_t i = bscf(m_valid); + if (none(valid0 & isecK.intersectK(&prim[j].qnode,i,tray,dist))) continue; + valid0 &= !occluded(valid0,pre,ray,context,prim[j].subgrid(i)); + if (none(valid0)) break; + } + } + return !valid0; + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (unlikely(dist[ID] > ray.tfar[k])) continue; + intersect(pre,ray,k,context,prim[i].subgrid(ID)); + } + } + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + assert(((size_t)1 << ID) & movemask(prim[i].qnode.validMask())); + + if (occluded(pre,ray,k,context,prim[i].subgrid(ID))) + return true; + } + } + return false; + } + }; + + + + } +} diff --git a/thirdparty/embree/kernels/geometry/subgrid_intersector_moeller.h b/thirdparty/embree/kernels/geometry/subgrid_intersector_moeller.h new file mode 100644 index 000000000000..f65b4abf61c9 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/subgrid_intersector_moeller.h @@ -0,0 +1,493 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "subgrid.h" +#include "quad_intersector_moeller.h" + +namespace embree +{ + namespace isa + { + + /* ----------------------------- */ + /* -- single ray intersectors -- */ + /* ----------------------------- */ + + template + __forceinline void interpolateUV(MoellerTrumboreHitM &hit,const GridMesh::Grid &g, const SubGrid& subgrid) + { + /* correct U,V interpolation across the entire grid */ + const vint sx((int)subgrid.x()); + const vint sy((int)subgrid.y()); + const vint sxM(sx + vint(0,1,1,0)); + const vint syM(sy + vint(0,0,1,1)); + const float inv_resX = rcp((float)((int)g.resX-1)); + const float inv_resY = rcp((float)((int)g.resY-1)); + hit.U = (hit.U + (vfloat)sxM * hit.absDen) * inv_resX; + hit.V = (hit.V + (vfloat)syM * hit.absDen) * inv_resY; + } + + template + struct SubGridQuadMIntersector1MoellerTrumbore; + + template + struct SubGridQuadMIntersector1MoellerTrumbore + { + __forceinline SubGridQuadMIntersector1MoellerTrumbore() {} + + __forceinline SubGridQuadMIntersector1MoellerTrumbore(const Ray& ray, const void* ptr) {} + + __forceinline void intersect(RayHit& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + MoellerTrumboreHitM hit; + MoellerTrumboreIntersector1 intersector(ray,nullptr); + Intersect1EpilogMU epilog(ray,context,subgrid.geomID(),subgrid.primID()); + + /* intersect first triangle */ + if (intersector.intersect(ray,v0,v1,v3,hit)) + { + interpolateUV(hit,g,subgrid); + epilog(hit.valid,hit); + } + + /* intersect second triangle */ + if (intersector.intersect(ray,v2,v3,v1,hit)) + { + hit.U = hit.absDen - hit.U; + hit.V = hit.absDen - hit.V; + interpolateUV(hit,g,subgrid); + epilog(hit.valid,hit); + } + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + MoellerTrumboreHitM hit; + MoellerTrumboreIntersector1 intersector(ray,nullptr); + Occluded1EpilogMU epilog(ray,context,subgrid.geomID(),subgrid.primID()); + + /* intersect first triangle */ + if (intersector.intersect(ray,v0,v1,v3,hit)) + { + interpolateUV(hit,g,subgrid); + if (epilog(hit.valid,hit)) + return true; + } + + /* intersect second triangle */ + if (intersector.intersect(ray,v2,v3,v1,hit)) + { + hit.U = hit.absDen - hit.U; + hit.V = hit.absDen - hit.V; + interpolateUV(hit,g,subgrid); + if (epilog(hit.valid,hit)) + return true; + } + return false; + } + }; + +#if defined (__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct SubGridQuadMIntersector1MoellerTrumbore<4,filter> + { + __forceinline SubGridQuadMIntersector1MoellerTrumbore() {} + + __forceinline SubGridQuadMIntersector1MoellerTrumbore(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const GridMesh::Grid &g, const SubGrid& subgrid, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + MoellerTrumboreHitM<8> hit; + MoellerTrumboreIntersector1<8> intersector(ray,nullptr); + const vbool8 flags(0,0,0,0,1,1,1,1); + if (unlikely(intersector.intersect(ray,vtx0,vtx1,vtx2,hit))) + { + vfloat8 U = hit.U, V = hit.V, absDen = hit.absDen; + +#if !defined(EMBREE_BACKFACE_CULLING) + hit.U = select(flags,absDen-V,U); + hit.V = select(flags,absDen-U,V); + hit.vNg *= select(flags,vfloat8(-1.0f),vfloat8(1.0f)); +#else + hit.U = select(flags,absDen-U,U); + hit.V = select(flags,absDen-V,V); +#endif + /* correct U,V interpolation across the entire grid */ + const vint8 sx((int)subgrid.x()); + const vint8 sy((int)subgrid.y()); + const vint8 sx8(sx + vint8(0,1,1,0,0,1,1,0)); + const vint8 sy8(sy + vint8(0,0,1,1,0,0,1,1)); + const float inv_resX = rcp((float)((int)g.resX-1)); + const float inv_resY = rcp((float)((int)g.resY-1)); + hit.U = (hit.U + (vfloat8)sx8 * absDen) * inv_resX; + hit.V = (hit.V + (vfloat8)sy8 * absDen) * inv_resY; + + if (unlikely(epilog(hit.valid,hit))) + return true; + } + return false; + } + + __forceinline bool intersect(RayHit& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + return intersect(ray,v0,v1,v2,v3,g,subgrid,Intersect1EpilogMU<8,filter>(ray,context,subgrid.geomID(),subgrid.primID())); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + return intersect(ray,v0,v1,v2,v3,g,subgrid,Occluded1EpilogMU<8,filter>(ray,context,subgrid.geomID(),subgrid.primID())); + } + }; + +#endif + + // ============================================================================================================================ + // ============================================================================================================================ + // ============================================================================================================================ + + + /* ----------------------------- */ + /* -- ray packet intersectors -- */ + /* ----------------------------- */ + + template + struct SubGridQuadHitK + { + __forceinline SubGridQuadHitK(const vfloat& U, + const vfloat& V, + const vfloat& T, + const vfloat& absDen, + const Vec3vf& Ng, + const vbool& flags, + const GridMesh::Grid &g, + const SubGrid& subgrid, + const unsigned int i) + : U(U), V(V), T(T), absDen(absDen), flags(flags), tri_Ng(Ng), g(g), subgrid(subgrid), i(i) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vfloat rcpAbsDen = rcp(absDen); + const vfloat t = T * rcpAbsDen; + const vfloat u0 = min(U * rcpAbsDen,1.0f); + const vfloat v0 = min(V * rcpAbsDen,1.0f); + const vfloat u1 = vfloat(1.0f) - u0; + const vfloat v1 = vfloat(1.0f) - v0; + const vfloat uu = select(flags,u1,u0); + const vfloat vv = select(flags,v1,v0); + const unsigned int sx = subgrid.x() + (unsigned int)(i % 2); + const unsigned int sy = subgrid.y() + (unsigned int)(i >>1); + const float inv_resX = rcp((float)(int)(g.resX-1)); + const float inv_resY = rcp((float)(int)(g.resY-1)); + const vfloat u = (uu + (float)(int)sx) * inv_resX; + const vfloat v = (vv + (float)(int)sy) * inv_resY; + const Vec3vf Ng(tri_Ng.x,tri_Ng.y,tri_Ng.z); + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat T; + const vfloat absDen; + const vbool flags; + const Vec3vf tri_Ng; + + const GridMesh::Grid &g; + const SubGrid& subgrid; + const size_t i; + }; + + template + struct SubGridQuadMIntersectorKMoellerTrumboreBase + { + __forceinline SubGridQuadMIntersectorKMoellerTrumboreBase(const vbool& valid, const RayK& ray) {} + + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + const vbool& flags, + const GridMesh::Grid &g, + const SubGrid &subgrid, + const unsigned int i, + const Epilog& epilog) const + { + /* calculate denominator */ + vbool valid = valid0; + const Vec3vf C = tri_v0 - ray.org; + const Vec3vf R = cross(C,ray.dir); + const vfloat den = dot(tri_Ng,ray.dir); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* test against edge p2 p0 */ + const vfloat U = dot(R,tri_e2) ^ sgnDen; + valid &= U >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p0 p1 */ + const vfloat V = dot(R,tri_e1) ^ sgnDen; + valid &= V >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p1 p2 */ + const vfloat W = absDen-U-V; + valid &= W >= 0.0f; + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(tri_Ng,C) ^ sgnDen; + valid &= (absDen*ray.tnear() < T) & (T <= absDen*ray.tfar); + if (unlikely(none(valid))) return false; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + valid &= den < vfloat(zero); + if (unlikely(none(valid))) return false; +#else + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; +#endif + + /* calculate hit information */ + SubGridQuadHitK hit(U,V,T,absDen,tri_Ng,flags,g,subgrid,i); + return epilog(valid,hit); + } + + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const vbool& flags, + const GridMesh::Grid &g, + const SubGrid &subgrid, + const unsigned int i, + const Epilog& epilog) const + { + const Vec3vf e1 = tri_v0-tri_v1; + const Vec3vf e2 = tri_v2-tri_v0; + const Vec3vf Ng = cross(e2,e1); + return intersectK(valid0,ray,tri_v0,e1,e2,Ng,flags,g,subgrid,i,epilog); + } + + template + __forceinline bool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Vec3vf& v3, + const GridMesh::Grid &g, + const SubGrid &subgrid, + const unsigned int i, + const Epilog& epilog) const + { + intersectK(valid0,ray,v0,v1,v3,vbool(false),g,subgrid,i,epilog); + if (none(valid0)) return true; + intersectK(valid0,ray,v2,v3,v1,vbool(true ),g,subgrid,i,epilog); + return none(valid0); + } + + static __forceinline bool intersect1(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + MoellerTrumboreHitM &hit) + { + /* calculate denominator */ + const Vec3vf O = broadcast>(ray.org,k); + const Vec3vf D = broadcast>(ray.dir,k); + const Vec3vf C = Vec3vf(tri_v0) - O; + const Vec3vf R = cross(C,D); + const vfloat den = dot(Vec3vf(tri_Ng),D); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* perform edge tests */ + const vfloat U = dot(R,Vec3vf(tri_e2)) ^ sgnDen; + const vfloat V = dot(R,Vec3vf(tri_e1)) ^ sgnDen; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = (den < vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#else + vbool valid = (den != vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#endif + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(Vec3vf(tri_Ng),C) ^ sgnDen; + valid &= (absDen*vfloat(ray.tnear()[k]) < T) & (T <= absDen*vfloat(ray.tfar[k])); + if (likely(none(valid))) return false; + + /* calculate hit information */ + new (&hit) MoellerTrumboreHitM(valid,U,V,T,absDen,tri_Ng); + return true; + } + + static __forceinline bool intersect1(RayK& ray, + size_t k, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + MoellerTrumboreHitM &hit) + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + const Vec3vf Ng = cross(e2,e1); + return intersect1(ray,k,v0,e1,e2,Ng,hit); + } + + }; + + template + struct SubGridQuadMIntersectorKMoellerTrumbore : public SubGridQuadMIntersectorKMoellerTrumboreBase + { + __forceinline SubGridQuadMIntersectorKMoellerTrumbore(const vbool& valid, const RayK& ray) + : SubGridQuadMIntersectorKMoellerTrumboreBase(valid,ray) {} + + __forceinline void intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, const GridMesh::Grid &g, const SubGrid &subgrid) const + { + Intersect1KEpilogMU epilog(ray,k,context,subgrid.geomID(),subgrid.primID()); + + MoellerTrumboreHitM<4> hit; + if (SubGridQuadMIntersectorKMoellerTrumboreBase<4,K,filter>::intersect1(ray,k,v0,v1,v3,hit)) + { + interpolateUV(hit,g,subgrid); + epilog(hit.valid,hit); + } + + if (SubGridQuadMIntersectorKMoellerTrumboreBase<4,K,filter>::intersect1(ray,k,v2,v3,v1,hit)) + { + hit.U = hit.absDen - hit.U; + hit.V = hit.absDen - hit.V; + interpolateUV(hit,g,subgrid); + epilog(hit.valid,hit); + } + + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, const GridMesh::Grid &g, const SubGrid &subgrid) const + { + Occluded1KEpilogMU epilog(ray,k,context,subgrid.geomID(),subgrid.primID()); + + MoellerTrumboreHitM<4> hit; + if (SubGridQuadMIntersectorKMoellerTrumboreBase<4,K,filter>::intersect1(ray,k,v0,v1,v3,hit)) + { + interpolateUV(hit,g,subgrid); + if (epilog(hit.valid,hit)) return true; + } + + if (SubGridQuadMIntersectorKMoellerTrumboreBase<4,K,filter>::intersect1(ray,k,v2,v3,v1,hit)) + { + hit.U = hit.absDen - hit.U; + hit.V = hit.absDen - hit.V; + interpolateUV(hit,g,subgrid); + if (epilog(hit.valid,hit)) return true; + } + return false; + } + }; + + +#if defined (__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct SubGridQuadMIntersectorKMoellerTrumbore<4,K,filter> : public SubGridQuadMIntersectorKMoellerTrumboreBase<4,K,filter> + { + __forceinline SubGridQuadMIntersectorKMoellerTrumbore(const vbool& valid, const RayK& ray) + : SubGridQuadMIntersectorKMoellerTrumboreBase<4,K,filter>(valid,ray) {} + + template + __forceinline bool intersect1(RayK& ray, size_t k,const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const GridMesh::Grid &g, const SubGrid &subgrid, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + const vbool8 flags(0,0,0,0,1,1,1,1); + + MoellerTrumboreHitM<8> hit; + if (SubGridQuadMIntersectorKMoellerTrumboreBase<8,K,filter>::intersect1(ray,k,vtx0,vtx1,vtx2,hit)) + { + vfloat8 U = hit.U, V = hit.V, absDen = hit.absDen; +#if !defined(EMBREE_BACKFACE_CULLING) + hit.U = select(flags,absDen-V,U); + hit.V = select(flags,absDen-U,V); + hit.vNg *= select(flags,vfloat8(-1.0f),vfloat8(1.0f)); +#else + hit.U = select(flags,absDen-U,U); + hit.V = select(flags,absDen-V,V); +#endif + + /* correct U,V interpolation across the entire grid */ + const vint8 sx((int)subgrid.x()); + const vint8 sy((int)subgrid.y()); + const vint8 sx8(sx + vint8(0,1,1,0,0,1,1,0)); + const vint8 sy8(sy + vint8(0,0,1,1,0,0,1,1)); + const float inv_resX = rcp((float)((int)g.resX-1)); + const float inv_resY = rcp((float)((int)g.resY-1)); + hit.U = (hit.U + (vfloat8)sx8 * absDen) * inv_resX; + hit.V = (hit.V + (vfloat8)sy8 * absDen) * inv_resY; + if (unlikely(epilog(hit.valid,hit))) + return true; + + } + return false; + } + + __forceinline bool intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const GridMesh::Grid &g, const SubGrid &subgrid) const + { + return intersect1(ray,k,v0,v1,v2,v3,g,subgrid,Intersect1KEpilogMU<8,K,filter>(ray,k,context,subgrid.geomID(),subgrid.primID())); + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const GridMesh::Grid &g, const SubGrid &subgrid) const + { + return intersect1(ray,k,v0,v1,v2,v3,g,subgrid,Occluded1KEpilogMU<8,K,filter>(ray,k,context,subgrid.geomID(),subgrid.primID())); + } + }; + +#endif + + + + } +} diff --git a/thirdparty/embree/kernels/geometry/subgrid_intersector_pluecker.h b/thirdparty/embree/kernels/geometry/subgrid_intersector_pluecker.h new file mode 100644 index 000000000000..1cd88aa799e7 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/subgrid_intersector_pluecker.h @@ -0,0 +1,508 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "subgrid.h" +#include "quad_intersector_moeller.h" +#include "quad_intersector_pluecker.h" + +namespace embree +{ + namespace isa + { + + template + struct SubGridQuadHitPlueckerM + { + __forceinline SubGridQuadHitPlueckerM() {} + + __forceinline SubGridQuadHitPlueckerM(const vbool& valid, + const vfloat& U, + const vfloat& V, + const vfloat& UVW, + const vfloat& t, + const Vec3vf& Ng, + const vbool& flags) : valid(valid), vt(t) + { + const vbool invalid = abs(UVW) < min_rcp_input; + const vfloat rcpUVW = select(invalid,vfloat(0.0f),rcp(UVW)); + const vfloat u = min(U * rcpUVW,1.0f); + const vfloat v = min(V * rcpUVW,1.0f); + const vfloat u1 = vfloat(1.0f) - u; + const vfloat v1 = vfloat(1.0f) - v; +#if !defined(__AVX__) || defined(EMBREE_BACKFACE_CULLING) + vu = select(flags,u1,u); + vv = select(flags,v1,v); + vNg = Vec3vf(Ng.x,Ng.y,Ng.z); +#else + const vfloat flip = select(flags,vfloat(-1.0f),vfloat(1.0f)); + vv = select(flags,u1,v); + vu = select(flags,v1,u); + vNg = Vec3vf(flip*Ng.x,flip*Ng.y,flip*Ng.z); +#endif + } + + __forceinline void finalize() + { + } + + __forceinline Vec2f uv(const size_t i) + { + const float u = vu[i]; + const float v = vv[i]; + return Vec2f(u,v); + } + + __forceinline float t(const size_t i) { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + public: + vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + __forceinline void interpolateUV(SubGridQuadHitPlueckerM &hit,const GridMesh::Grid &g, const SubGrid& subgrid, const vint &stepX, const vint &stepY) + { + /* correct U,V interpolation across the entire grid */ + const vint sx((int)subgrid.x()); + const vint sy((int)subgrid.y()); + const vint sxM(sx + stepX); + const vint syM(sy + stepY); + const float inv_resX = rcp((float)((int)g.resX-1)); + const float inv_resY = rcp((float)((int)g.resY-1)); + hit.vu = (hit.vu + vfloat(sxM)) * inv_resX; + hit.vv = (hit.vv + vfloat(syM)) * inv_resY; + } + + template + __forceinline static bool intersectPluecker(Ray& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const vbool& flags, + SubGridQuadHitPlueckerM &hit) + { + /* calculate vertices relative to ray origin */ + const Vec3vf O = Vec3vf((Vec3fa)ray.org); + const Vec3vf D = Vec3vf((Vec3fa)ray.dir); + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(cross(e0,v2+v0),D); + const vfloat V = dot(cross(e1,v0+v1),D); + const vfloat W = dot(cross(e2,v1+v2),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = max(U,V,W) <= eps; +#else + vbool valid = (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Ng,D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Ng)); + const vfloat t = rcp(den)*T; + valid &= vfloat(ray.tnear()) <= t & t <= vfloat(ray.tfar); + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + new (&hit) SubGridQuadHitPlueckerM(valid,U,V,UVW,t,Ng,flags); + return true; + } + + template + struct SubGridQuadMIntersector1Pluecker; + + template + struct SubGridQuadMIntersector1Pluecker + { + __forceinline SubGridQuadMIntersector1Pluecker() {} + + __forceinline SubGridQuadMIntersector1Pluecker(const Ray& ray, const void* ptr) {} + + __forceinline void intersect(RayHit& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + SubGridQuadHitPlueckerM hit; + Intersect1EpilogMU epilog(ray,context,subgrid.geomID(),subgrid.primID()); + + /* intersect first triangle */ + if (intersectPluecker(ray,v0,v1,v3,vbool(false),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + epilog(hit.valid,hit); + } + + /* intersect second triangle */ + if (intersectPluecker(ray,v2,v3,v1,vbool(true),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + epilog(hit.valid,hit); + } + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + SubGridQuadHitPlueckerM hit; + Occluded1EpilogMU epilog(ray,context,subgrid.geomID(),subgrid.primID()); + + /* intersect first triangle */ + if (intersectPluecker(ray,v0,v1,v3,vbool(false),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + if (epilog(hit.valid,hit)) + return true; + } + + /* intersect second triangle */ + if (intersectPluecker(ray,v2,v3,v1,vbool(true),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + if (epilog(hit.valid,hit)) + return true; + } + + return false; + } + }; + +#if defined (__AVX__) + + /*! Intersects 4 quads with 1 ray using AVX */ + template + struct SubGridQuadMIntersector1Pluecker<4,filter> + { + __forceinline SubGridQuadMIntersector1Pluecker() {} + + __forceinline SubGridQuadMIntersector1Pluecker(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, const GridMesh::Grid &g, const SubGrid& subgrid, const Epilog& epilog) const + { + const Vec3vf8 vtx0(vfloat8(v0.x,v2.x),vfloat8(v0.y,v2.y),vfloat8(v0.z,v2.z)); +#if !defined(EMBREE_BACKFACE_CULLING) + const Vec3vf8 vtx1(vfloat8(v1.x),vfloat8(v1.y),vfloat8(v1.z)); + const Vec3vf8 vtx2(vfloat8(v3.x),vfloat8(v3.y),vfloat8(v3.z)); +#else + const Vec3vf8 vtx1(vfloat8(v1.x,v3.x),vfloat8(v1.y,v3.y),vfloat8(v1.z,v3.z)); + const Vec3vf8 vtx2(vfloat8(v3.x,v1.x),vfloat8(v3.y,v1.y),vfloat8(v3.z,v1.z)); +#endif + SubGridQuadHitPlueckerM<8> hit; + const vbool8 flags(0,0,0,0,1,1,1,1); + if (unlikely(intersectPluecker(ray,vtx0,vtx1,vtx2,flags,hit))) + { + /* correct U,V interpolation across the entire grid */ + interpolateUV<8>(hit,g,subgrid,vint<8>(0,1,1,0,0,1,1,0),vint<8>(0,0,1,1,0,0,1,1)); + if (unlikely(epilog(hit.valid,hit))) + return true; + } + return false; + } + + __forceinline bool intersect(RayHit& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + return intersect(ray,v0,v1,v2,v3,g,subgrid,Intersect1EpilogMU<8,filter>(ray,context,subgrid.geomID(),subgrid.primID())); + } + + __forceinline bool occluded(Ray& ray, IntersectContext* context, + const Vec3vf4& v0, const Vec3vf4& v1, const Vec3vf4& v2, const Vec3vf4& v3, + const GridMesh::Grid &g, const SubGrid& subgrid) const + { + return intersect(ray,v0,v1,v2,v3,g,subgrid,Occluded1EpilogMU<8,filter>(ray,context,subgrid.geomID(),subgrid.primID())); + } + }; + +#endif + + + /* ----------------------------- */ + /* -- ray packet intersectors -- */ + /* ----------------------------- */ + + template + struct SubGridQuadHitPlueckerK + { + __forceinline SubGridQuadHitPlueckerK(const vfloat& U, + const vfloat& V, + const vfloat& UVW, + const vfloat& t, + const Vec3vf& Ng, + const vbool& flags, + const GridMesh::Grid &g, + const SubGrid& subgrid, + const unsigned int i) + : U(U), V(V), UVW(UVW), t(t), flags(flags), tri_Ng(Ng), g(g), subgrid(subgrid), i(i) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vbool invalid = abs(UVW) < min_rcp_input; + const vfloat rcpUVW = select(invalid,vfloat(0.0f),rcp(UVW)); + const vfloat u0 = min(U * rcpUVW,1.0f); + const vfloat v0 = min(V * rcpUVW,1.0f); + const vfloat u1 = vfloat(1.0f) - u0; + const vfloat v1 = vfloat(1.0f) - v0; + const vfloat uu = select(flags,u1,u0); + const vfloat vv = select(flags,v1,v0); + const unsigned int sx = subgrid.x() + (unsigned int)(i % 2); + const unsigned int sy = subgrid.y() + (unsigned int)(i >>1); + const float inv_resX = rcp((float)(int)(g.resX-1)); + const float inv_resY = rcp((float)(int)(g.resY-1)); + const vfloat u = (uu + (float)(int)sx) * inv_resX; + const vfloat v = (vv + (float)(int)sy) * inv_resY; + const Vec3vf Ng(tri_Ng.x,tri_Ng.y,tri_Ng.z); + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat UVW; + const vfloat t; + const vfloat absDen; + const vbool flags; + const Vec3vf tri_Ng; + + const GridMesh::Grid &g; + const SubGrid& subgrid; + const size_t i; + }; + + + template + struct SubGridQuadMIntersectorKPlueckerBase + { + __forceinline SubGridQuadMIntersectorKPlueckerBase(const vbool& valid, const RayK& ray) {} + + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const Vec3vf& tri_Ng, + const vbool& flags, + const GridMesh::Grid &g, + const SubGrid &subgrid, + const unsigned int i, + const Epilog& epilog) const + { + /* calculate denominator */ + /* calculate vertices relative to ray origin */ + vbool valid = valid0; + const Vec3vf O = ray.org; + const Vec3vf D = ray.dir; + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(Vec3vf(cross(e0,v2+v0)),D); + const vfloat V = dot(Vec3vf(cross(e1,v0+v1)),D); + const vfloat W = dot(Vec3vf(cross(e2,v1+v2)),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + valid &= max(U,V,W) <= eps; +#else + valid &= (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Vec3vf(Ng),D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Vec3vf(Ng))); + const vfloat t = rcp(den)*T; + valid &= ray.tnear() <= t & t <= ray.tfar; + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* calculate hit information */ + SubGridQuadHitPlueckerK hit(U,V,UVW,t,tri_Ng,flags,g,subgrid,i); + return epilog(valid,hit); + } + + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const vbool& flags, + const GridMesh::Grid &g, + const SubGrid &subgrid, + const unsigned int i, + const Epilog& epilog) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + const Vec3vf Ng = cross(e2,e1); + return intersectK(valid0,ray,v0,v1,v2,Ng,flags,g,subgrid,i,epilog); + } + + template + __forceinline bool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Vec3vf& v3, + const GridMesh::Grid &g, + const SubGrid &subgrid, + const unsigned int i, + const Epilog& epilog) const + { + intersectK(valid0,ray,v0,v1,v3,vbool(false),g,subgrid,i,epilog); + if (none(valid0)) return true; + intersectK(valid0,ray,v2,v3,v1,vbool(true ),g,subgrid,i,epilog); + return none(valid0); + } + + static __forceinline bool intersect1(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const Vec3vf& tri_Ng, + const vbool& flags, + SubGridQuadHitPlueckerM &hit) + { + /* calculate vertices relative to ray origin */ + const Vec3vf O = broadcast>(ray.org,k); + const Vec3vf D = broadcast>(ray.dir,k); + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(cross(e0,v2+v0),D); + const vfloat V = dot(cross(e1,v0+v1),D); + const vfloat W = dot(cross(e2,v1+v2),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = max(U,V,W) <= eps ; +#else + vbool valid = (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Ng,D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Ng)); + const vfloat t = rcp(den)*T; + valid &= vfloat(ray.tnear()[k]) <= t & t <= vfloat(ray.tfar[k]); + if (unlikely(none(valid))) return false; + + /* avoid division by 0 */ + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + new (&hit) SubGridQuadHitPlueckerM(valid,U,V,UVW,t,tri_Ng,flags); + return true; + } + + static __forceinline bool intersect1(RayK& ray, + size_t k, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const vbool& flags, + SubGridQuadHitPlueckerM &hit) + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + const Vec3vf Ng = cross(e2,e1); // FIXME: optimize!!! + return intersect1(ray,k,v0,v1,v2,Ng,flags,hit); + } + + }; + + template + struct SubGridQuadMIntersectorKPluecker : public SubGridQuadMIntersectorKPlueckerBase + { + __forceinline SubGridQuadMIntersectorKPluecker(const vbool& valid, const RayK& ray) + : SubGridQuadMIntersectorKPlueckerBase(valid,ray) {} + + __forceinline void intersect1(RayHitK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, const GridMesh::Grid &g, const SubGrid &subgrid) const + { + Intersect1KEpilogMU epilog(ray,k,context,subgrid.geomID(),subgrid.primID()); + + SubGridQuadHitPlueckerM<4> hit; + if (SubGridQuadMIntersectorKPlueckerBase<4,K,filter>::intersect1(ray,k,v0,v1,v3,vboolf4(false),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + epilog(hit.valid,hit); + } + + if (SubGridQuadMIntersectorKPlueckerBase<4,K,filter>::intersect1(ray,k,v2,v3,v1,vboolf4(true),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + epilog(hit.valid,hit); + } + + } + + __forceinline bool occluded1(RayK& ray, size_t k, IntersectContext* context, + const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const Vec3vf& v3, const GridMesh::Grid &g, const SubGrid &subgrid) const + { + Occluded1KEpilogMU epilog(ray,k,context,subgrid.geomID(),subgrid.primID()); + + SubGridQuadHitPlueckerM<4> hit; + if (SubGridQuadMIntersectorKPlueckerBase<4,K,filter>::intersect1(ray,k,v0,v1,v3,vboolf4(false),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + if (epilog(hit.valid,hit)) return true; + } + + if (SubGridQuadMIntersectorKPlueckerBase<4,K,filter>::intersect1(ray,k,v2,v3,v1,vboolf4(true),hit)) + { + interpolateUV(hit,g,subgrid,vint(0,1,1,0),vint(0,0,1,1)); + if (epilog(hit.valid,hit)) return true; + } + return false; + } + }; + + } +} diff --git a/thirdparty/embree/kernels/geometry/subgrid_mb_intersector.h b/thirdparty/embree/kernels/geometry/subgrid_mb_intersector.h new file mode 100644 index 000000000000..400a88b985a7 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/subgrid_mb_intersector.h @@ -0,0 +1,236 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "subgrid_intersector.h" + +namespace embree +{ + namespace isa + { + template + struct SubGridMBIntersector1Pluecker + { + typedef SubGridMBQBVHN Primitive; + typedef SubGridQuadMIntersector1Pluecker<4,filter> Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(normal.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + float ftime; + const int itime = mesh->timeSegment(ray.time(), ftime); + Vec3vf4 v0,v1,v2,v3; subgrid.gatherMB(v0,v1,v2,v3,context->scene,itime,ftime); + pre.intersect(ray,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(shadow.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + float ftime; + const int itime = mesh->timeSegment(ray.time(), ftime); + + Vec3vf4 v0,v1,v2,v3; subgrid.gatherMB(v0,v1,v2,v3,context->scene,itime,ftime); + return pre.occluded(ray,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const SubGrid& subgrid) + { + return PrimitivePointQuery1::pointQuery(query, context, subgrid); + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + for (size_t i=0;i dist; + const float time = prim[i].adjustTime(ray.time()); + + assert(time <= 1.0f); + size_t mask = isec1.intersect(&prim[i].qnode,tray,time,dist); +#if defined(__AVX__) + STAT3(normal.trav_hit_boxes[popcnt(mask)],1,1,1); +#endif + while(mask != 0) + { + const size_t ID = bscf(mask); + if (unlikely(dist[ID] > ray.tfar)) continue; + intersect(pre,ray,context,prim[i].subgrid(ID)); + } + } + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + for (size_t i=0;i dist; + size_t mask = isec1.intersect(&prim[i].qnode,tray,time,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + if (occluded(pre,ray,context,prim[i].subgrid(ID))) + return true; + } + } + return false; + } + + static __forceinline bool pointQuery(const Accel::Intersectors* This, PointQuery* query, PointQueryContext* context, const Primitive* prim, size_t num, const TravPointQuery &tquery, size_t& lazy_node) + { + assert(false && "not implemented"); + return false; + } + }; + + + template + struct SubGridMBIntersectorKPluecker + { + typedef SubGridMBQBVHN Primitive; + typedef SubGridQuadMIntersectorKPluecker<4,K,filter> Precalculations; + + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const SubGrid& subgrid) + { + size_t m_valid = movemask(valid_i); + while(m_valid) + { + size_t ID = bscf(m_valid); + intersect(pre,ray,ID,context,subgrid); + } + } + + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const SubGrid& subgrid) + { + vbool valid0 = valid_i; + size_t m_valid = movemask(valid_i); + while(m_valid) + { + size_t ID = bscf(m_valid); + if (occluded(pre,ray,ID,context,subgrid)) + clear(valid0,ID); + } + return !valid0; + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(normal.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + vfloat ftime; + const vint itime = mesh->timeSegment(ray.time(), ftime); + Vec3vf4 v0,v1,v2,v3; subgrid.gatherMB(v0,v1,v2,v3,context->scene,itime[k],ftime[k]); + pre.intersect1(ray,k,context,v0,v1,v2,v3,g,subgrid); + } + + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const SubGrid& subgrid) + { + STAT3(shadow.trav_prims,1,1,1); + const GridMesh* mesh = context->scene->get(subgrid.geomID()); + const GridMesh::Grid &g = mesh->grid(subgrid.primID()); + + vfloat ftime; + const vint itime = mesh->timeSegment(ray.time(), ftime); + Vec3vf4 v0,v1,v2,v3; subgrid.gatherMB(v0,v1,v2,v3,context->scene,itime[k],ftime[k]); + return pre.occluded1(ray,k,context,v0,v1,v2,v3,g,subgrid); + } + + template + static __forceinline void intersect(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersectorK isecK; + for (size_t j=0;j time = prim[j].adjustTime(ray.time()); + + vfloat dist; + while(m_valid) + { + const size_t i = bscf(m_valid); + if (none(valid & isecK.intersectK(&prim[j].qnode,i,tray,time,dist))) continue; + intersect(valid,pre,ray,context,prim[j].subgrid(i)); + } + } + } + + template + static __forceinline vbool occluded(const vbool& valid, const Accel::Intersectors* This, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive* prim, size_t num, const TravRayK &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersectorK isecK; + + vbool valid0 = valid; + for (size_t j=0;j time = prim[j].adjustTime(ray.time()); + vfloat dist; + while(m_valid) + { + const size_t i = bscf(m_valid); + if (none(valid0 & isecK.intersectK(&prim[j].qnode,i,tray,time,dist))) continue; + valid0 &= !occluded(valid0,pre,ray,context,prim[j].subgrid(i)); + if (none(valid0)) break; + } + } + return !valid0; + } + + template + static __forceinline void intersect(const Accel::Intersectors* This, Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + for (size_t i=0;i dist; + const float time = prim[i].adjustTime(ray.time()[k]); + assert(time <= 1.0f); + + size_t mask = isec1.intersect(&prim[i].qnode,tray,time,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + if (unlikely(dist[ID] > ray.tfar[k])) continue; + intersect(pre,ray,k,context,prim[i].subgrid(ID)); + } + } + } + + template + static __forceinline bool occluded(const Accel::Intersectors* This, Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive* prim, size_t num, const TravRay &tray, size_t& lazy_node) + { + BVHNQuantizedBaseNodeIntersector1 isec1; + + for (size_t i=0;i dist; + const float time = prim[i].adjustTime(ray.time()[k]); + assert(time <= 1.0f); + + size_t mask = isec1.intersect(&prim[i].qnode,tray,time,dist); + while(mask != 0) + { + const size_t ID = bscf(mask); + if (occluded(pre,ray,k,context,prim[i].subgrid(ID))) + return true; + } + } + return false; + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/triangle.h b/thirdparty/embree/kernels/geometry/triangle.h new file mode 100644 index 000000000000..0dedf6dc4c13 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/triangle.h @@ -0,0 +1,162 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + /* Precalculated representation for M triangles. Stores for each + triangle a base vertex, two edges, and the geometry normal to + speed up intersection calculations */ + template + struct TriangleM + { + public: + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* Returns maximum number of stored triangles */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline TriangleM() {} + + /* Construction from vertices and IDs */ + __forceinline TriangleM(const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const vuint& geomIDs, const vuint& primIDs) + : v0(v0), e1(v0-v1), e2(v2-v0), geomIDs(geomIDs), primIDs(primIDs) {} + + /* Returns a mask that tells which triangles are valid */ + __forceinline vbool valid() const { return geomIDs != vuint(-1); } + + /* Returns true if the specified triangle is valid */ + __forceinline bool valid(const size_t i) const { assert(i& geomID() { return geomIDs; } + __forceinline const vuint& geomID() const { return geomIDs; } + __forceinline unsigned int geomID(const size_t i) const { assert(i& primID() { return primIDs; } + __forceinline const vuint& primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(i p0 = v0; + Vec3vf p1 = v0-e1; + Vec3vf p2 = v0+e2; + Vec3vf lower = min(p0,p1,p2); + Vec3vf upper = max(p0,p1,p2); + vbool mask = valid(); + lower.x = select(mask,lower.x,vfloat(pos_inf)); + lower.y = select(mask,lower.y,vfloat(pos_inf)); + lower.z = select(mask,lower.z,vfloat(pos_inf)); + upper.x = select(mask,upper.x,vfloat(neg_inf)); + upper.y = select(mask,upper.y,vfloat(neg_inf)); + upper.z = select(mask,upper.z,vfloat(neg_inf)); + return BBox3fa(Vec3fa(reduce_min(lower.x),reduce_min(lower.y),reduce_min(lower.z)), + Vec3fa(reduce_max(upper.x),reduce_max(upper.y),reduce_max(upper.z))); + } + + /* Non temporal store */ + __forceinline static void store_nt(TriangleM* dst, const TriangleM& src) + { + vfloat::store_nt(&dst->v0.x,src.v0.x); + vfloat::store_nt(&dst->v0.y,src.v0.y); + vfloat::store_nt(&dst->v0.z,src.v0.z); + vfloat::store_nt(&dst->e1.x,src.e1.x); + vfloat::store_nt(&dst->e1.y,src.e1.y); + vfloat::store_nt(&dst->e1.z,src.e1.z); + vfloat::store_nt(&dst->e2.x,src.e2.x); + vfloat::store_nt(&dst->e2.y,src.e2.y); + vfloat::store_nt(&dst->e2.z,src.e2.z); + vuint::store_nt(&dst->geomIDs,src.geomIDs); + vuint::store_nt(&dst->primIDs,src.primIDs); + } + + /* Fill triangle from triangle list */ + __forceinline void fill(const PrimRef* prims, size_t& begin, size_t end, Scene* scene) + { + vuint vgeomID = -1, vprimID = -1; + Vec3vf v0 = zero, v1 = zero, v2 = zero; + + for (size_t i=0; iget(geomID); + const TriangleMesh::Triangle& tri = mesh->triangle(primID); + const Vec3fa& p0 = mesh->vertex(tri.v[0]); + const Vec3fa& p1 = mesh->vertex(tri.v[1]); + const Vec3fa& p2 = mesh->vertex(tri.v[2]); + vgeomID [i] = geomID; + vprimID [i] = primID; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + } + TriangleM::store_nt(this,TriangleM(v0,v1,v2,vgeomID,vprimID)); + } + + /* Updates the primitive */ + __forceinline BBox3fa update(TriangleMesh* mesh) + { + BBox3fa bounds = empty; + vuint vgeomID = -1, vprimID = -1; + Vec3vf v0 = zero, v1 = zero, v2 = zero; + + for (size_t i=0; itriangle(primId); + const Vec3fa p0 = mesh->vertex(tri.v[0]); + const Vec3fa p1 = mesh->vertex(tri.v[1]); + const Vec3fa p2 = mesh->vertex(tri.v[2]); + bounds.extend(merge(BBox3fa(p0),BBox3fa(p1),BBox3fa(p2))); + vgeomID [i] = geomId; + vprimID [i] = primId; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + } + TriangleM::store_nt(this,TriangleM(v0,v1,v2,vgeomID,vprimID)); + return bounds; + } + + public: + Vec3vf v0; // base vertex of the triangles + Vec3vf e1; // 1st edge of the triangles (v0-v1) + Vec3vf e2; // 2nd edge of the triangles (v2-v0) + private: + vuint geomIDs; // geometry IDs + vuint primIDs; // primitive IDs + }; + + template + typename TriangleM::Type TriangleM::type; + + typedef TriangleM<4> Triangle4; +} diff --git a/thirdparty/embree/kernels/geometry/triangle_intersector.h b/thirdparty/embree/kernels/geometry/triangle_intersector.h new file mode 100644 index 000000000000..125a42c5fee0 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/triangle_intersector.h @@ -0,0 +1,96 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "triangle.h" +#include "triangle_intersector_moeller.h" + +namespace embree +{ + namespace isa + { + /*! Intersects M triangles with 1 ray */ + template + struct TriangleMIntersector1Moeller + { + typedef TriangleM Primitive; + typedef MoellerTrumboreIntersector1 Precalculations; + + /*! Intersect a ray with the M triangles and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const TriangleM& tri) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersectEdge(ray,tri.v0,tri.e1,tri.e2,Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const TriangleM& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.intersectEdge(ray,tri.v0,tri.e1,tri.e2,Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + + }; + + /*! Intersects M triangles with K rays. */ + template + struct TriangleMIntersectorKMoeller + { + typedef TriangleM Primitive; + typedef MoellerTrumboreIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const TriangleM& tri) + { + STAT_USER(0,TriangleM::max_size()); + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf p0 = broadcast>(tri.v0,i); + const Vec3vf e1 = broadcast>(tri.e1,i); + const Vec3vf e2 = broadcast>(tri.e2,i); + pre.intersectEdgeK(valid_i,ray,p0,e1,e2,IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const TriangleM& tri) + { + vbool valid0 = valid_i; + + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf p0 = broadcast>(tri.v0,i); + const Vec3vf e1 = broadcast>(tri.e1,i); + const Vec3vf e2 = broadcast>(tri.e2,i); + pre.intersectEdgeK(valid0,ray,p0,e1,e2,OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const TriangleM& tri) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersectEdge(ray,k,tri.v0,tri.e1,tri.e2,Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const TriangleM& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.intersectEdge(ray,k,tri.v0,tri.e1,tri.e2,Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/triangle_intersector_moeller.h b/thirdparty/embree/kernels/geometry/triangle_intersector_moeller.h new file mode 100644 index 000000000000..b5a8519236da --- /dev/null +++ b/thirdparty/embree/kernels/geometry/triangle_intersector_moeller.h @@ -0,0 +1,403 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "triangle.h" +#include "intersector_epilog.h" + +/*! This intersector implements a modified version of the Moeller + * Trumbore intersector from the paper "Fast, Minimum Storage + * Ray-Triangle Intersection". In contrast to the paper we + * precalculate some factors and factor the calculations differently + * to allow precalculating the cross product e1 x e2. The resulting + * algorithm is similar to the fastest one of the paper "Optimizing + * Ray-Triangle Intersection via Automated Search". */ + +namespace embree +{ + namespace isa + { + template + struct MoellerTrumboreHitM + { + __forceinline MoellerTrumboreHitM() {} + + __forceinline MoellerTrumboreHitM(const vbool& valid, const vfloat& U, const vfloat& V, const vfloat& T, const vfloat& absDen, const Vec3vf& Ng) + : U(U), V(V), T(T), absDen(absDen), valid(valid), vNg(Ng) {} + + __forceinline void finalize() + { + const vfloat rcpAbsDen = rcp(absDen); + vt = T * rcpAbsDen; + vu = U * rcpAbsDen; + vv = V * rcpAbsDen; + } + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + public: + vfloat U; + vfloat V; + vfloat T; + vfloat absDen; + + public: + vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + struct MoellerTrumboreIntersector1 + { + __forceinline MoellerTrumboreIntersector1() {} + + __forceinline MoellerTrumboreIntersector1(const Ray& ray, const void* ptr) {} + + __forceinline bool intersect(const vbool& valid0, + Ray& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + MoellerTrumboreHitM& hit) const + { + /* calculate denominator */ + vbool valid = valid0; + const Vec3vf O = Vec3vf((Vec3fa)ray.org); + const Vec3vf D = Vec3vf((Vec3fa)ray.dir); + const Vec3vf C = Vec3vf(tri_v0) - O; + const Vec3vf R = cross(C,D); + const vfloat den = dot(Vec3vf(tri_Ng),D); + + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* perform edge tests */ + const vfloat U = dot(R,Vec3vf(tri_e2)) ^ sgnDen; + const vfloat V = dot(R,Vec3vf(tri_e1)) ^ sgnDen; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + valid &= (den < vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#else + valid &= (den != vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#endif + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(Vec3vf(tri_Ng),C) ^ sgnDen; + valid &= (absDen*vfloat(ray.tnear()) < T) & (T <= absDen*vfloat(ray.tfar)); + if (likely(none(valid))) return false; + + + /* update hit information */ + new (&hit) MoellerTrumboreHitM(valid,U,V,T,absDen,tri_Ng); + + return true; + } + + __forceinline bool intersectEdge(Ray& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + MoellerTrumboreHitM& hit) const + { + vbool valid = true; + const Vec3> tri_Ng = cross(tri_e2,tri_e1); + return intersect(valid,ray,tri_v0,tri_e1,tri_e2,tri_Ng,hit); + } + + __forceinline bool intersect(Ray& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + MoellerTrumboreHitM& hit) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + return intersectEdge(ray,v0,e1,e2,hit); + } + + __forceinline bool intersect(const vbool& valid, + Ray& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + MoellerTrumboreHitM& hit) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + return intersectEdge(valid,ray,v0,e1,e2,hit); + } + + template + __forceinline bool intersectEdge(Ray& ray, + const Vec3vf& v0, + const Vec3vf& e1, + const Vec3vf& e2, + const Epilog& epilog) const + { + MoellerTrumboreHitM hit; + if (likely(intersectEdge(ray,v0,e1,e2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + __forceinline bool intersect(Ray& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) const + { + MoellerTrumboreHitM hit; + if (likely(intersect(ray,v0,v1,v2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + __forceinline bool intersect(const vbool& valid, + Ray& ray, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) const + { + MoellerTrumboreHitM hit; + if (likely(intersect(valid,ray,v0,v1,v2,hit))) return epilog(hit.valid,hit); + return false; + } + }; + + template + struct MoellerTrumboreHitK + { + __forceinline MoellerTrumboreHitK(const vfloat& U, const vfloat& V, const vfloat& T, const vfloat& absDen, const Vec3vf& Ng) + : U(U), V(V), T(T), absDen(absDen), Ng(Ng) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vfloat rcpAbsDen = rcp(absDen); + const vfloat t = T * rcpAbsDen; + const vfloat u = U * rcpAbsDen; + const vfloat v = V * rcpAbsDen; + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat T; + const vfloat absDen; + const Vec3vf Ng; + }; + + template + struct MoellerTrumboreIntersectorK + { + __forceinline MoellerTrumboreIntersectorK(const vbool& valid, const RayK& ray) {} + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + //RayK& ray, + const Vec3vf& ray_org, + const Vec3vf& ray_dir, + const vfloat& ray_tnear, + const vfloat& ray_tfar, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + const Epilog& epilog) const + { + /* calculate denominator */ + vbool valid = valid0; + const Vec3vf C = tri_v0 - ray_org; + const Vec3vf R = cross(C,ray_dir); + const vfloat den = dot(tri_Ng,ray_dir); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* test against edge p2 p0 */ + const vfloat U = dot(tri_e2,R) ^ sgnDen; + valid &= U >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p0 p1 */ + const vfloat V = dot(tri_e1,R) ^ sgnDen; + valid &= V >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p1 p2 */ + const vfloat W = absDen-U-V; + valid &= W >= 0.0f; + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(tri_Ng,C) ^ sgnDen; + valid &= (absDen*ray_tnear < T) & (T <= absDen*ray_tfar); + if (unlikely(none(valid))) return false; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + valid &= den < vfloat(zero); + if (unlikely(none(valid))) return false; +#else + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; +#endif + + /* calculate hit information */ + MoellerTrumboreHitK hit(U,V,T,absDen,tri_Ng); + return epilog(valid,hit); + } + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const Epilog& epilog) const + { + const Vec3vf e1 = tri_v0-tri_v1; + const Vec3vf e2 = tri_v2-tri_v0; + const Vec3vf Ng = cross(e2,e1); + return intersectK(valid0,ray.org,ray.dir,ray.tnear(),ray.tfar,tri_v0,e1,e2,Ng,epilog); + } + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectEdgeK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Epilog& epilog) const + { + const Vec3vf tri_Ng = cross(tri_e2,tri_e1); + return intersectK(valid0,ray.org,ray.dir,ray.tnear(),ray.tfar,tri_v0,tri_e1,tri_e2,tri_Ng,epilog); + } + + /*! Intersect k'th ray from ray packet of size K with M triangles. */ + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + MoellerTrumboreHitM& hit) const + { + /* calculate denominator */ + typedef Vec3vf Vec3vfM; + const Vec3vf tri_Ng = cross(tri_e2,tri_e1); + + const Vec3vfM O = broadcast>(ray.org,k); + const Vec3vfM D = broadcast>(ray.dir,k); + const Vec3vfM C = Vec3vfM(tri_v0) - O; + const Vec3vfM R = cross(C,D); + const vfloat den = dot(Vec3vfM(tri_Ng),D); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* perform edge tests */ + const vfloat U = dot(Vec3vf(tri_e2),R) ^ sgnDen; + const vfloat V = dot(Vec3vf(tri_e1),R) ^ sgnDen; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = (den < vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#else + vbool valid = (den != vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#endif + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(Vec3vf(tri_Ng),C) ^ sgnDen; + valid &= (absDen*vfloat(ray.tnear()[k]) < T) & (T <= absDen*vfloat(ray.tfar[k])); + if (likely(none(valid))) return false; + + /* calculate hit information */ + new (&hit) MoellerTrumboreHitM(valid,U,V,T,absDen,tri_Ng); + return true; + } + + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const BBox>& time_range, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + MoellerTrumboreHitM& hit) const + { + if (likely(intersect(ray,k,tri_v0,tri_e1,tri_e2,hit))) + { + hit.valid &= time_range.lower <= vfloat(ray.time[k]); + hit.valid &= vfloat(ray.time[k]) < time_range.upper; + return any(hit.valid); + } + return false; + } + + template + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Epilog& epilog) const + { + MoellerTrumboreHitM hit; + if (likely(intersectEdge(ray,k,tri_v0,tri_e1,tri_e2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const BBox>& time_range, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Epilog& epilog) const + { + MoellerTrumboreHitM hit; + if (likely(intersectEdge(ray,k,time_range,tri_v0,tri_e1,tri_e2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + __forceinline bool intersect(RayK& ray, + size_t k, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + return intersectEdge(ray,k,v0,e1,e2,epilog); + } + + template + __forceinline bool intersect(RayK& ray, + size_t k, + const BBox>& time_range, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + return intersectEdge(ray,k,time_range,v0,e1,e2,epilog); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/triangle_intersector_pluecker.h b/thirdparty/embree/kernels/geometry/triangle_intersector_pluecker.h new file mode 100644 index 000000000000..f1de99d2087c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/triangle_intersector_pluecker.h @@ -0,0 +1,247 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "triangle.h" +#include "trianglev.h" +#include "trianglev_mb.h" +#include "intersector_epilog.h" + +/*! Modified Pluecker ray/triangle intersector. The test first shifts + * the ray origin into the origin of the coordinate system and then + * uses Pluecker coordinates for the intersection. Due to the shift, + * the Pluecker coordinate calculation simplifies and the tests get + * numerically stable. The edge equations are watertight along the + * edge for neighboring triangles. */ + +namespace embree +{ + namespace isa + { + template + struct PlueckerHitM + { + __forceinline PlueckerHitM(const vfloat& U, const vfloat& V, const vfloat& UVW, const vfloat& t, const Vec3vf& Ng, const UVMapper& mapUV) + : U(U), V(V), UVW(UVW), mapUV(mapUV), vt(t), vNg(Ng) {} + + __forceinline void finalize() + { + const vbool invalid = abs(UVW) < min_rcp_input; + const vfloat rcpUVW = select(invalid,vfloat(0.0f),rcp(UVW)); + vu = U * rcpUVW; + vv = V * rcpUVW; + mapUV(vu,vv); + } + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + private: + const vfloat U; + const vfloat V; + const vfloat UVW; + const UVMapper& mapUV; + + public: + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + struct PlueckerIntersector1 + { + __forceinline PlueckerIntersector1() {} + + __forceinline PlueckerIntersector1(const Ray& ray, const void* ptr) {} + + template + __forceinline bool intersect(Ray& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const UVMapper& mapUV, + const Epilog& epilog) const + { + /* calculate vertices relative to ray origin */ + const Vec3vf O = Vec3vf((Vec3fa)ray.org); + const Vec3vf D = Vec3vf((Vec3fa)ray.dir); + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(cross(e0,v2+v0),D); + const vfloat V = dot(cross(e1,v0+v1),D); + const vfloat W = dot(cross(e2,v1+v2),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = max(U,V,W) <= eps; +#else + vbool valid = (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Ng,D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Ng)); + const vfloat t = rcp(den)*T; + valid &= vfloat(ray.tnear()) <= t & t <= vfloat(ray.tfar); + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + PlueckerHitM hit(U,V,UVW,t,Ng,mapUV); + return epilog(valid,hit); + } + }; + + template + struct PlueckerHitK + { + __forceinline PlueckerHitK(const vfloat& U, const vfloat& V, const vfloat& UVW, const vfloat& t, const Vec3vf& Ng, const UVMapper& mapUV) + : U(U), V(V), UVW(UVW), t(t), Ng(Ng), mapUV(mapUV) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vbool invalid = abs(UVW) < min_rcp_input; + const vfloat rcpUVW = select(invalid,vfloat(0.0f),rcp(UVW)); + vfloat u = U * rcpUVW; + vfloat v = V * rcpUVW; + mapUV(u,v); + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat UVW; + const vfloat t; + const Vec3vf Ng; + const UVMapper& mapUV; + }; + + template + struct PlueckerIntersectorK + { + __forceinline PlueckerIntersectorK(const vbool& valid, const RayK& ray) {} + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const UVMapper& mapUV, + const Epilog& epilog) const + { + /* calculate vertices relative to ray origin */ + vbool valid = valid0; + const Vec3vf O = ray.org; + const Vec3vf D = ray.dir; + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(Vec3vf(cross(e0,v2+v0)),D); + const vfloat V = dot(Vec3vf(cross(e1,v0+v1)),D); + const vfloat W = dot(Vec3vf(cross(e2,v1+v2)),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + valid &= max(U,V,W) <= eps; +#else + valid &= (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Vec3vf(Ng),D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Vec3vf(Ng))); + const vfloat t = rcp(den)*T; + valid &= ray.tnear() <= t & t <= ray.tfar; + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* calculate hit information */ + PlueckerHitK hit(U,V,UVW,t,Ng,mapUV); + return epilog(valid,hit); + } + + /*! Intersect k'th ray from ray packet of size K with M triangles. */ + template + __forceinline bool intersect(RayK& ray, size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const UVMapper& mapUV, + const Epilog& epilog) const + { + /* calculate vertices relative to ray origin */ + const Vec3vf O = broadcast>(ray.org,k); + const Vec3vf D = broadcast>(ray.dir,k); + const Vec3vf v0 = tri_v0-O; + const Vec3vf v1 = tri_v1-O; + const Vec3vf v2 = tri_v2-O; + + /* calculate triangle edges */ + const Vec3vf e0 = v2-v0; + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v1-v2; + + /* perform edge tests */ + const vfloat U = dot(cross(e0,v2+v0),D); + const vfloat V = dot(cross(e1,v0+v1),D); + const vfloat W = dot(cross(e2,v1+v2),D); + const vfloat UVW = U+V+W; + const vfloat eps = float(ulp)*abs(UVW); +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = max(U,V,W) <= eps; +#else + vbool valid = (min(U,V,W) >= -eps) | (max(U,V,W) <= eps); +#endif + if (unlikely(none(valid))) return false; + + /* calculate geometry normal and denominator */ + const Vec3vf Ng = stable_triangle_normal(e0,e1,e2); + const vfloat den = twice(dot(Ng,D)); + + /* perform depth test */ + const vfloat T = twice(dot(v0,Ng)); + const vfloat t = rcp(den)*T; + valid &= vfloat(ray.tnear()[k]) <= t & t <= vfloat(ray.tfar[k]); + if (unlikely(none(valid))) return false; + + /* avoid division by 0 */ + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; + + /* update hit information */ + PlueckerHitM hit(U,V,UVW,t,Ng,mapUV); + return epilog(valid,hit); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/triangle_intersector_woop.h b/thirdparty/embree/kernels/geometry/triangle_intersector_woop.h new file mode 100644 index 000000000000..63e649d8fb6e --- /dev/null +++ b/thirdparty/embree/kernels/geometry/triangle_intersector_woop.h @@ -0,0 +1,418 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "triangle.h" +#include "intersector_epilog.h" + +/*! This intersector implements a modified version of the Woop's ray-triangle intersection test */ + +namespace embree +{ + namespace isa + { + template + struct WoopHitM + { + __forceinline WoopHitM() {} + + __forceinline WoopHitM(const vbool& valid, + const vfloat& U, + const vfloat& V, + const vfloat& T, + const vfloat& inv_det, + const Vec3vf& Ng) + : U(U), V(V), T(T), inv_det(inv_det), valid(valid), vNg(Ng) {} + + __forceinline void finalize() + { + vt = T; + vu = U*inv_det; + vv = V*inv_det; + } + + __forceinline Vec2f uv (const size_t i) const { return Vec2f(vu[i],vv[i]); } + __forceinline float t (const size_t i) const { return vt[i]; } + __forceinline Vec3fa Ng(const size_t i) const { return Vec3fa(vNg.x[i],vNg.y[i],vNg.z[i]); } + + private: + const vfloat U; + const vfloat V; + const vfloat T; + const vfloat inv_det; + + public: + const vbool valid; + vfloat vu; + vfloat vv; + vfloat vt; + Vec3vf vNg; + }; + + template + struct WoopPrecalculations1 + { + unsigned int kx,ky,kz; + Vec3vf org; + Vec3fa S; + __forceinline WoopPrecalculations1() {} + + __forceinline WoopPrecalculations1(const Ray& ray, const void* ptr) + { + kz = maxDim(abs(ray.dir)); + kx = (kz+1) % 3; + ky = (kx+1) % 3; + const float inv_dir_kz = rcp(ray.dir[kz]); + if (ray.dir[kz]) std::swap(kx,ky); + S.x = ray.dir[kx] * inv_dir_kz; + S.y = ray.dir[ky] * inv_dir_kz; + S.z = inv_dir_kz; + org = Vec3vf(ray.org[kx],ray.org[ky],ray.org[kz]); + } + }; + + + template + struct WoopIntersector1 + { + + typedef WoopPrecalculations1 Precalculations; + + __forceinline WoopIntersector1() {} + + __forceinline WoopIntersector1(const Ray& ray, const void* ptr) {} + + static __forceinline bool intersect(const vbool& valid0, + Ray& ray, + const Precalculations& pre, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + WoopHitM& hit) + { + vbool valid = valid0; + + /* vertices relative to ray origin */ + const Vec3vf org = Vec3vf(pre.org.x,pre.org.y,pre.org.z); + const Vec3vf A = Vec3vf(tri_v0[pre.kx],tri_v0[pre.ky],tri_v0[pre.kz]) - org; + const Vec3vf B = Vec3vf(tri_v1[pre.kx],tri_v1[pre.ky],tri_v1[pre.kz]) - org; + const Vec3vf C = Vec3vf(tri_v2[pre.kx],tri_v2[pre.ky],tri_v2[pre.kz]) - org; + + /* shear and scale vertices */ + const vfloat Ax = nmadd(A.z,pre.S.x,A.x); + const vfloat Ay = nmadd(A.z,pre.S.y,A.y); + const vfloat Bx = nmadd(B.z,pre.S.x,B.x); + const vfloat By = nmadd(B.z,pre.S.y,B.y); + const vfloat Cx = nmadd(C.z,pre.S.x,C.x); + const vfloat Cy = nmadd(C.z,pre.S.y,C.y); + + /* scaled barycentric */ + const vfloat U0 = Cx*By; + const vfloat U1 = Cy*Bx; + const vfloat V0 = Ax*Cy; + const vfloat V1 = Ay*Cx; + const vfloat W0 = Bx*Ay; + const vfloat W1 = By*Ax; +#if !defined(__AVX512F__) + valid &= (U0 >= U1) & (V0 >= V1) & (W0 >= W1) | + (U0 <= U1) & (V0 <= V1) & (W0 <= W1); +#else + valid &= ge(ge(U0 >= U1,V0,V1),W0,W1) | le(le(U0 <= U1,V0,V1),W0,W1); +#endif + + if (likely(none(valid))) return false; + const vfloat U = U0-U1; + const vfloat V = V0-V1; + const vfloat W = W0-W1; + + const vfloat det = U+V+W; + + valid &= det != 0.0f; + const vfloat inv_det = rcp(det); + + const vfloat Az = pre.S.z * A.z; + const vfloat Bz = pre.S.z * B.z; + const vfloat Cz = pre.S.z * C.z; + const vfloat T = madd(U,Az,madd(V,Bz,W*Cz)); + const vfloat t = T * inv_det; + /* perform depth test */ + valid &= (vfloat(ray.tnear()) < t) & (t <= vfloat(ray.tfar)); + if (likely(none(valid))) return false; + + const Vec3vf tri_Ng = cross(tri_v2-tri_v0,tri_v0-tri_v1); + + /* update hit information */ + new (&hit) WoopHitM(valid,U,V,t,inv_det,tri_Ng); + return true; + } + + static __forceinline bool intersect(Ray& ray, + const Precalculations& pre, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + WoopHitM& hit) + { + vbool valid = true; + return intersect(valid,ray,pre,v0,v1,v2,hit); + } + + + template + static __forceinline bool intersect(Ray& ray, + const Precalculations& pre, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) + { + WoopHitM hit; + if (likely(intersect(ray,pre,v0,v1,v2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + static __forceinline bool intersect(const vbool& valid, + Ray& ray, + const Precalculations& pre, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) + { + WoopHitM hit; + if (likely(intersect(valid,ray,pre,v0,v1,v2,hit))) return epilog(hit.valid,hit); + return false; + } + }; + +#if 0 + template + struct WoopHitK + { + __forceinline WoopHitK(const vfloat& U, const vfloat& V, const vfloat& T, const vfloat& absDen, const Vec3vf& Ng) + : U(U), V(V), T(T), absDen(absDen), Ng(Ng) {} + + __forceinline std::tuple,vfloat,vfloat,Vec3vf> operator() () const + { + const vfloat rcpAbsDen = rcp(absDen); + const vfloat t = T * rcpAbsDen; + const vfloat u = U * rcpAbsDen; + const vfloat v = V * rcpAbsDen; + return std::make_tuple(u,v,t,Ng); + } + + private: + const vfloat U; + const vfloat V; + const vfloat T; + const vfloat absDen; + const Vec3vf Ng; + }; + + template + struct WoopIntersectorK + { + __forceinline WoopIntersectorK(const vbool& valid, const RayK& ray) {} + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + //RayK& ray, + const Vec3vf& ray_org, + const Vec3vf& ray_dir, + const vfloat& ray_tnear, + const vfloat& ray_tfar, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Vec3vf& tri_Ng, + const Epilog& epilog) const + { + /* calculate denominator */ + vbool valid = valid0; + const Vec3vf C = tri_v0 - ray_org; + const Vec3vf R = cross(C,ray_dir); + const vfloat den = dot(tri_Ng,ray_dir); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* test against edge p2 p0 */ + const vfloat U = dot(tri_e2,R) ^ sgnDen; + valid &= U >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p0 p1 */ + const vfloat V = dot(tri_e1,R) ^ sgnDen; + valid &= V >= 0.0f; + if (likely(none(valid))) return false; + + /* test against edge p1 p2 */ + const vfloat W = absDen-U-V; + valid &= W >= 0.0f; + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(tri_Ng,C) ^ sgnDen; + valid &= (absDen*ray_tnear < T) & (T <= absDen*ray_tfar); + if (unlikely(none(valid))) return false; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + valid &= den < vfloat(zero); + if (unlikely(none(valid))) return false; +#else + valid &= den != vfloat(zero); + if (unlikely(none(valid))) return false; +#endif + + /* calculate hit information */ + WoopHitK hit(U,V,T,absDen,tri_Ng); + return epilog(valid,hit); + } + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_v1, + const Vec3vf& tri_v2, + const Epilog& epilog) const + { + const Vec3vf e1 = tri_v0-tri_v1; + const Vec3vf e2 = tri_v2-tri_v0; + const Vec3vf Ng = cross(e2,e1); + return intersectK(valid0,ray.org,ray.dir,ray.tnear(),ray.tfar,tri_v0,e1,e2,Ng,epilog); + } + + /*! Intersects K rays with one of M triangles. */ + template + __forceinline vbool intersectEdgeK(const vbool& valid0, + RayK& ray, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Epilog& epilog) const + { + const Vec3vf tri_Ng = cross(tri_e2,tri_e1); + return intersectK(valid0,ray.org,ray.dir,ray.tnear(),ray.tfar,tri_v0,tri_e1,tri_e2,tri_Ng,epilog); + } + + /*! Intersect k'th ray from ray packet of size K with M triangles. */ + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + WoopHitM& hit) const + { + /* calculate denominator */ + typedef Vec3vf Vec3vfM; + const Vec3vf tri_Ng = cross(tri_e2,tri_e1); + + const Vec3vfM O = broadcast>(ray.org,k); + const Vec3vfM D = broadcast>(ray.dir,k); + const Vec3vfM C = Vec3vfM(tri_v0) - O; + const Vec3vfM R = cross(C,D); + const vfloat den = dot(Vec3vfM(tri_Ng),D); + const vfloat absDen = abs(den); + const vfloat sgnDen = signmsk(den); + + /* perform edge tests */ + const vfloat U = dot(Vec3vf(tri_e2),R) ^ sgnDen; + const vfloat V = dot(Vec3vf(tri_e1),R) ^ sgnDen; + + /* perform backface culling */ +#if defined(EMBREE_BACKFACE_CULLING) + vbool valid = (den < vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#else + vbool valid = (den != vfloat(zero)) & (U >= 0.0f) & (V >= 0.0f) & (U+V<=absDen); +#endif + if (likely(none(valid))) return false; + + /* perform depth test */ + const vfloat T = dot(Vec3vf(tri_Ng),C) ^ sgnDen; + valid &= (absDen*vfloat(ray.tnear()[k]) < T) & (T <= absDen*vfloat(ray.tfar[k])); + if (likely(none(valid))) return false; + + /* calculate hit information */ + new (&hit) WoopHitM(valid,U,V,T,absDen,tri_Ng); + return true; + } + + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const BBox>& time_range, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + WoopHitM& hit) const + { + if (likely(intersect(ray,k,tri_v0,tri_e1,tri_e2,hit))) + { + hit.valid &= time_range.lower <= vfloat(ray.time[k]); + hit.valid &= vfloat(ray.time[k]) < time_range.upper; + return any(hit.valid); + } + return false; + } + + template + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Epilog& epilog) const + { + WoopHitM hit; + if (likely(intersectEdge(ray,k,tri_v0,tri_e1,tri_e2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + __forceinline bool intersectEdge(RayK& ray, + size_t k, + const BBox>& time_range, + const Vec3vf& tri_v0, + const Vec3vf& tri_e1, + const Vec3vf& tri_e2, + const Epilog& epilog) const + { + WoopHitM hit; + if (likely(intersectEdge(ray,k,time_range,tri_v0,tri_e1,tri_e2,hit))) return epilog(hit.valid,hit); + return false; + } + + template + __forceinline bool intersect(RayK& ray, + size_t k, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + return intersectEdge(ray,k,v0,e1,e2,epilog); + } + + template + __forceinline bool intersect(RayK& ray, + size_t k, + const BBox>& time_range, + const Vec3vf& v0, + const Vec3vf& v1, + const Vec3vf& v2, + const Epilog& epilog) const + { + const Vec3vf e1 = v0-v1; + const Vec3vf e2 = v2-v0; + return intersectEdge(ray,k,time_range,v0,e1,e2,epilog); + } + }; +#endif + } +} diff --git a/thirdparty/embree/kernels/geometry/triangle_triangle_intersector.h b/thirdparty/embree/kernels/geometry/triangle_triangle_intersector.h new file mode 100644 index 000000000000..91b35c36f396 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/triangle_triangle_intersector.h @@ -0,0 +1,132 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "primitive.h" + +namespace embree +{ + namespace isa + { + struct TriangleTriangleIntersector + { + __forceinline static float T(float pa0, float pa1, float da0, float da1) { + return pa0 + (pa1-pa0)*da0/(da0-da1); + } + + __forceinline static bool point_line_side(const Vec2f& p, const Vec2f& a0, const Vec2f& a1) { + return det(p-a0,a0-a1) >= 0.0f; + } + + __forceinline static bool point_inside_triangle(const Vec2f& p, const Vec2f& a, const Vec2f& b, const Vec2f& c) + { + const bool pab = point_line_side(p,a,b); + const bool pbc = point_line_side(p,b,c); + const bool pca = point_line_side(p,c,a); + return pab == pbc && pab == pca; + } + + __forceinline static bool intersect_line_line(const Vec2f& a0, const Vec2f& a1, const Vec2f& b0, const Vec2f& b1) + { + const bool different_sides0 = point_line_side(b0,a0,a1) != point_line_side(b1,a0,a1); + const bool different_sides1 = point_line_side(a0,b0,b1) != point_line_side(a1,b0,b1); + return different_sides0 && different_sides1; + } + + __forceinline static bool intersect_triangle_triangle (const Vec2f& a0, const Vec2f& a1, const Vec2f& a2, + const Vec2f& b0, const Vec2f& b1, const Vec2f& b2) + { + const bool a01_b01 = intersect_line_line(a0,a1,b0,b1); + if (a01_b01) return true; + const bool a01_b12 = intersect_line_line(a0,a1,b1,b2); + if (a01_b12) return true; + const bool a01_b20 = intersect_line_line(a0,a1,b2,b0); + if (a01_b20) return true; + const bool a12_b01 = intersect_line_line(a1,a2,b0,b1); + if (a12_b01) return true; + const bool a12_b12 = intersect_line_line(a1,a2,b1,b2); + if (a12_b12) return true; + const bool a12_b20 = intersect_line_line(a1,a2,b2,b0); + if (a12_b20) return true; + const bool a20_b01 = intersect_line_line(a2,a0,b0,b1); + if (a20_b01) return true; + const bool a20_b12 = intersect_line_line(a2,a0,b1,b2); + if (a20_b12) return true; + const bool a20_b20 = intersect_line_line(a2,a0,b2,b0); + if (a20_b20) return true; + + bool a_in_b = point_inside_triangle(a0,b0,b1,b2) && point_inside_triangle(a1,b0,b1,b2) && point_inside_triangle(a2,b0,b1,b2); + if (a_in_b) return true; + + bool b_in_a = point_inside_triangle(b0,a0,a1,a2) && point_inside_triangle(b1,a0,a1,a2) && point_inside_triangle(b2,a0,a1,a2); + if (b_in_a) return true; + + return false; + } + + static bool intersect_triangle_triangle (const Vec3fa& a0, const Vec3fa& a1, const Vec3fa& a2, + const Vec3fa& b0, const Vec3fa& b1, const Vec3fa& b2) + { + const float eps = 1E-5f; + + /* calculate triangle planes */ + const Vec3fa Na = cross(a1-a0,a2-a0); + const float Ca = dot(Na,a0); + const Vec3fa Nb = cross(b1-b0,b2-b0); + const float Cb = dot(Nb,b0); + + /* project triangle A onto plane B */ + const float da0 = dot(Nb,a0)-Cb; + const float da1 = dot(Nb,a1)-Cb; + const float da2 = dot(Nb,a2)-Cb; + if (max(da0,da1,da2) < -eps) return false; + if (min(da0,da1,da2) > +eps) return false; + //CSTAT(bvh_collide_prim_intersections4++); + + /* project triangle B onto plane A */ + const float db0 = dot(Na,b0)-Ca; + const float db1 = dot(Na,b1)-Ca; + const float db2 = dot(Na,b2)-Ca; + if (max(db0,db1,db2) < -eps) return false; + if (min(db0,db1,db2) > +eps) return false; + //CSTAT(bvh_collide_prim_intersections5++); + + if (unlikely((std::fabs(da0) < eps && std::fabs(da1) < eps && std::fabs(da2) < eps) || + (std::fabs(db0) < eps && std::fabs(db1) < eps && std::fabs(db2) < eps))) + { + const size_t dz = maxDim(Na); + const size_t dx = (dz+1)%3; + const size_t dy = (dx+1)%3; + const Vec2f A0(a0[dx],a0[dy]); + const Vec2f A1(a1[dx],a1[dy]); + const Vec2f A2(a2[dx],a2[dy]); + const Vec2f B0(b0[dx],b0[dy]); + const Vec2f B1(b1[dx],b1[dy]); + const Vec2f B2(b2[dx],b2[dy]); + return intersect_triangle_triangle(A0,A1,A2,B0,B1,B2); + } + + const Vec3fa D = cross(Na,Nb); + const float pa0 = dot(D,a0); + const float pa1 = dot(D,a1); + const float pa2 = dot(D,a2); + const float pb0 = dot(D,b0); + const float pb1 = dot(D,b1); + const float pb2 = dot(D,b2); + + BBox1f ba = empty; + if (min(da0,da1) <= 0.0f && max(da0,da1) >= 0.0f && abs(da0-da1) > 0.0f) ba.extend(T(pa0,pa1,da0,da1)); + if (min(da1,da2) <= 0.0f && max(da1,da2) >= 0.0f && abs(da1-da2) > 0.0f) ba.extend(T(pa1,pa2,da1,da2)); + if (min(da2,da0) <= 0.0f && max(da2,da0) >= 0.0f && abs(da2-da0) > 0.0f) ba.extend(T(pa2,pa0,da2,da0)); + + BBox1f bb = empty; + if (min(db0,db1) <= 0.0f && max(db0,db1) >= 0.0f && abs(db0-db1) > 0.0f) bb.extend(T(pb0,pb1,db0,db1)); + if (min(db1,db2) <= 0.0f && max(db1,db2) >= 0.0f && abs(db1-db2) > 0.0f) bb.extend(T(pb1,pb2,db1,db2)); + if (min(db2,db0) <= 0.0f && max(db2,db0) >= 0.0f && abs(db2-db0) > 0.0f) bb.extend(T(pb2,pb0,db2,db0)); + + return conjoint(ba,bb); + } + }; + } +} + + diff --git a/thirdparty/embree/kernels/geometry/trianglei.h b/thirdparty/embree/kernels/geometry/trianglei.h new file mode 100644 index 000000000000..4f3118cc0ccc --- /dev/null +++ b/thirdparty/embree/kernels/geometry/trianglei.h @@ -0,0 +1,442 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" +#include "../common/scene.h" + +namespace embree +{ + /* Stores M triangles from an indexed face set */ + template + struct TriangleMi + { + /* Virtual interface to query information about the triangle type */ + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* primitive supports multiple time segments */ + static const bool singleTimeSegment = false; + + /* Returns maximum number of stored triangles */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline TriangleMi() { } + + /* Construction from vertices and IDs */ + __forceinline TriangleMi(const vuint& v0, + const vuint& v1, + const vuint& v2, + const vuint& geomIDs, + const vuint& primIDs) +#if defined(EMBREE_COMPACT_POLYS) + : geomIDs(geomIDs), primIDs(primIDs) {} +#else + : v0_(v0), v1_(v1), v2_(v2), geomIDs(geomIDs), primIDs(primIDs) {} +#endif + + /* Returns a mask that tells which triangles are valid */ + __forceinline vbool valid() const { return primIDs != vuint(-1); } + + /* Returns if the specified triangle is valid */ + __forceinline bool valid(const size_t i) const { assert(i geomID() const { return geomIDs; } + __forceinline unsigned int geomID(const size_t i) const { assert(i primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(iget(geomID(i)); + bounds.extend(mesh->bounds(primID(i),itime)); + } + return bounds; + } + + /* Calculate the linear bounds of the primitive */ + __forceinline LBBox3fa linearBounds(const Scene *const scene, size_t itime) { + return LBBox3fa(bounds(scene,itime+0),bounds(scene,itime+1)); + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, size_t itime, size_t numTimeSteps) + { + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID(i)); + allBounds.extend(mesh->linearBounds(primID(i), itime, numTimeSteps)); + } + return allBounds; + } + + __forceinline LBBox3fa linearBounds(const Scene *const scene, const BBox1f time_range) + { + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID(i)); + allBounds.extend(mesh->linearBounds(primID(i), time_range)); + } + return allBounds; + } + + /* Non-temporal store */ + __forceinline static void store_nt(TriangleMi* dst, const TriangleMi& src) + { +#if !defined(EMBREE_COMPACT_POLYS) + vuint::store_nt(&dst->v0_,src.v0_); + vuint::store_nt(&dst->v1_,src.v1_); + vuint::store_nt(&dst->v2_,src.v2_); +#endif + vuint::store_nt(&dst->geomIDs,src.geomIDs); + vuint::store_nt(&dst->primIDs,src.primIDs); + } + + /* Fill triangle from triangle list */ + template + __forceinline void fill(const PrimRefT* prims, size_t& begin, size_t end, Scene* scene) + { + vuint v0 = zero, v1 = zero, v2 = zero; + vuint geomID = -1, primID = -1; + const PrimRefT* prim = &prims[begin]; + + for (size_t i=0; igeomID(); + primID[i] = prim->primID(); +#if !defined(EMBREE_COMPACT_POLYS) + const TriangleMesh* mesh = scene->get(prim->geomID()); + const TriangleMesh::Triangle& tri = mesh->triangle(prim->primID()); + unsigned int int_stride = mesh->vertices0.getStride()/4; + v0[i] = tri.v[0] * int_stride; + v1[i] = tri.v[1] * int_stride; + v2[i] = tri.v[2] * int_stride; +#endif + begin++; + } else { + assert(i); + if (likely(i > 0)) { + geomID[i] = geomID[0]; + primID[i] = -1; + v0[i] = v0[0]; + v1[i] = v0[0]; + v2[i] = v0[0]; + } + } + if (begintriangle(primId); + const Vec3fa p0 = mesh->vertex(tri.v[0]); + const Vec3fa p1 = mesh->vertex(tri.v[1]); + const Vec3fa p2 = mesh->vertex(tri.v[2]); + bounds.extend(merge(BBox3fa(p0),BBox3fa(p1),BBox3fa(p2))); + } + return bounds; + } + + protected: +#if !defined(EMBREE_COMPACT_POLYS) + vuint v0_; // 4 byte offset of 1st vertex + vuint v1_; // 4 byte offset of 2nd vertex + vuint v2_; // 4 byte offset of 3rd vertex +#endif + vuint geomIDs; // geometry ID of mesh + vuint primIDs; // primitive ID of primitive inside mesh + }; + + namespace isa + { + + template + struct TriangleMi : public embree::TriangleMi + { +#if !defined(EMBREE_COMPACT_POLYS) + using embree::TriangleMi::v0_; + using embree::TriangleMi::v1_; + using embree::TriangleMi::v2_; +#endif + using embree::TriangleMi::geomIDs; + using embree::TriangleMi::primIDs; + using embree::TriangleMi::geomID; + using embree::TriangleMi::primID; + using embree::TriangleMi::valid; + + /* loads a single vertex */ + template + __forceinline Vec3f getVertex(const size_t index, const Scene *const scene) const + { +#if defined(EMBREE_COMPACT_POLYS) + const TriangleMesh* mesh = scene->get(geomID(index)); + const TriangleMesh::Triangle& tri = mesh->triangle(primID(index)); + return (Vec3f) mesh->vertices[0][tri.v[vid]]; +#else + const vuint& v = getVertexOffset(); + const float* vertices = scene->vertices[geomID(index)]; + return (Vec3f&) vertices[v[index]]; +#endif + } + + template + __forceinline Vec3 getVertex(const size_t index, const Scene *const scene, const size_t itime, const T& ftime) const + { +#if defined(EMBREE_COMPACT_POLYS) + const TriangleMesh* mesh = scene->get(geomID(index)); + const TriangleMesh::Triangle& tri = mesh->triangle(primID(index)); + const Vec3fa v0 = mesh->vertices[itime+0][tri.v[vid]]; + const Vec3fa v1 = mesh->vertices[itime+1][tri.v[vid]]; +#else + const vuint& v = getVertexOffset(); + const TriangleMesh* mesh = scene->get(geomID(index)); + const float* vertices0 = (const float*) mesh->vertexPtr(0,itime+0); + const float* vertices1 = (const float*) mesh->vertexPtr(0,itime+1); + const Vec3fa v0 = Vec3fa::loadu(vertices0+v[index]); + const Vec3fa v1 = Vec3fa::loadu(vertices1+v[index]); +#endif + const Vec3 p0(v0.x,v0.y,v0.z); + const Vec3 p1(v1.x,v1.y,v1.z); + return lerp(p0,p1,ftime); + } + + template + __forceinline Vec3 getVertex(const vbool& valid, const size_t index, const Scene *const scene, const vint& itime, const T& ftime) const + { + Vec3 p0, p1; + const TriangleMesh* mesh = scene->get(geomID(index)); + + for (size_t mask=movemask(valid), i=bsf(mask); mask; mask=btc(mask,i), i=bsf(mask)) + { +#if defined(EMBREE_COMPACT_POLYS) + const TriangleMesh::Triangle& tri = mesh->triangle(primID(index)); + const Vec3fa v0 = mesh->vertices[itime[i]+0][tri.v[vid]]; + const Vec3fa v1 = mesh->vertices[itime[i]+1][tri.v[vid]]; +#else + const vuint& v = getVertexOffset(); + const float* vertices0 = (const float*) mesh->vertexPtr(0,itime[i]+0); + const float* vertices1 = (const float*) mesh->vertexPtr(0,itime[i]+1); + const Vec3fa v0 = Vec3fa::loadu(vertices0+v[index]); + const Vec3fa v1 = Vec3fa::loadu(vertices1+v[index]); +#endif + p0.x[i] = v0.x; p0.y[i] = v0.y; p0.z[i] = v0.z; + p1.x[i] = v1.x; p1.y[i] = v1.y; p1.z[i] = v1.z; + } + return (T(one)-ftime)*p0 + ftime*p1; + } + + struct Triangle { + vfloat4 v0,v1,v2; + }; + +#if defined(EMBREE_COMPACT_POLYS) + + __forceinline Triangle loadTriangle(const int i, const Scene* const scene) const + { + const unsigned int geomID = geomIDs[i]; + const unsigned int primID = primIDs[i]; + if (unlikely(primID == -1)) return { zero, zero, zero }; + const TriangleMesh* mesh = scene->get(geomID); + const TriangleMesh::Triangle& tri = mesh->triangle(primID); + const vfloat4 v0 = (vfloat4) mesh->vertices0[tri.v[0]]; + const vfloat4 v1 = (vfloat4) mesh->vertices0[tri.v[1]]; + const vfloat4 v2 = (vfloat4) mesh->vertices0[tri.v[2]]; + return { v0, v1, v2 }; + } + + __forceinline Triangle loadTriangle(const int i, const int itime, const TriangleMesh* const mesh) const + { + const unsigned int primID = primIDs[i]; + if (unlikely(primID == -1)) return { zero, zero, zero }; + const TriangleMesh::Triangle& tri = mesh->triangle(primID); + const vfloat4 v0 = (vfloat4) mesh->vertices[itime][tri.v[0]]; + const vfloat4 v1 = (vfloat4) mesh->vertices[itime][tri.v[1]]; + const vfloat4 v2 = (vfloat4) mesh->vertices[itime][tri.v[2]]; + return { v0, v1, v2 }; + } + +#else + + __forceinline Triangle loadTriangle(const int i, const Scene* const scene) const + { + const float* vertices = scene->vertices[geomID(i)]; + const vfloat4 v0 = vfloat4::loadu(vertices + v0_[i]); + const vfloat4 v1 = vfloat4::loadu(vertices + v1_[i]); + const vfloat4 v2 = vfloat4::loadu(vertices + v2_[i]); + return { v0, v1, v2 }; + } + + __forceinline Triangle loadTriangle(const int i, const int itime, const TriangleMesh* const mesh) const + { + const float* vertices = (const float*) mesh->vertexPtr(0,itime); + const vfloat4 v0 = vfloat4::loadu(vertices + v0_[i]); + const vfloat4 v1 = vfloat4::loadu(vertices + v1_[i]); + const vfloat4 v2 = vfloat4::loadu(vertices + v2_[i]); + return { v0, v1, v2 }; + } + +#endif + + /* Gather the triangles */ + __forceinline void gather(Vec3vf& p0, Vec3vf& p1, Vec3vf& p2, const Scene* const scene) const; + + template +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER < 2000) // workaround for compiler bug in ICC 2019 + __noinline +#else + __forceinline +#endif + void gather(const vbool& valid, + Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + const size_t index, + const Scene* const scene, + const vfloat& time) const + { + const TriangleMesh* mesh = scene->get(geomID(index)); + + vfloat ftime; + const vint itime = mesh->timeSegment(time, ftime); + + const size_t first = bsf(movemask(valid)); + if (likely(all(valid,itime[first] == itime))) + { + p0 = getVertex<0>(index, scene, itime[first], ftime); + p1 = getVertex<1>(index, scene, itime[first], ftime); + p2 = getVertex<2>(index, scene, itime[first], ftime); + } else { + p0 = getVertex<0>(valid, index, scene, itime, ftime); + p1 = getVertex<1>(valid, index, scene, itime, ftime); + p2 = getVertex<2>(valid, index, scene, itime, ftime); + } + } + + __forceinline void gather(Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + const TriangleMesh* mesh, + const Scene *const scene, + const int itime) const; + + __forceinline void gather(Vec3vf& p0, + Vec3vf& p1, + Vec3vf& p2, + const Scene *const scene, + const float time) const; + + +#if !defined(EMBREE_COMPACT_POLYS) + template const vuint& getVertexOffset() const; +#endif + }; + +#if !defined(EMBREE_COMPACT_POLYS) + template<> template<> __forceinline const vuint<4>& TriangleMi<4>::getVertexOffset<0>() const { return v0_; } + template<> template<> __forceinline const vuint<4>& TriangleMi<4>::getVertexOffset<1>() const { return v1_; } + template<> template<> __forceinline const vuint<4>& TriangleMi<4>::getVertexOffset<2>() const { return v2_; } +#endif + + template<> + __forceinline void TriangleMi<4>::gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + const Scene* const scene) const + { + const Triangle tri0 = loadTriangle(0,scene); + const Triangle tri1 = loadTriangle(1,scene); + const Triangle tri2 = loadTriangle(2,scene); + const Triangle tri3 = loadTriangle(3,scene); + transpose(tri0.v0,tri1.v0,tri2.v0,tri3.v0,p0.x,p0.y,p0.z); + transpose(tri0.v1,tri1.v1,tri2.v1,tri3.v1,p1.x,p1.y,p1.z); + transpose(tri0.v2,tri1.v2,tri2.v2,tri3.v2,p2.x,p2.y,p2.z); + } + + template<> + __forceinline void TriangleMi<4>::gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + const TriangleMesh* mesh, + const Scene *const scene, + const int itime) const + { + const Triangle tri0 = loadTriangle(0,itime,mesh); + const Triangle tri1 = loadTriangle(1,itime,mesh); + const Triangle tri2 = loadTriangle(2,itime,mesh); + const Triangle tri3 = loadTriangle(3,itime,mesh); + transpose(tri0.v0,tri1.v0,tri2.v0,tri3.v0,p0.x,p0.y,p0.z); + transpose(tri0.v1,tri1.v1,tri2.v1,tri3.v1,p1.x,p1.y,p1.z); + transpose(tri0.v2,tri1.v2,tri2.v2,tri3.v2,p2.x,p2.y,p2.z); + } + + template<> + __forceinline void TriangleMi<4>::gather(Vec3vf4& p0, + Vec3vf4& p1, + Vec3vf4& p2, + const Scene *const scene, + const float time) const + { + const TriangleMesh* mesh = scene->get(geomID(0)); // in mblur mode all geometries are identical + + float ftime; + const int itime = mesh->timeSegment(time, ftime); + + Vec3vf4 a0,a1,a2; gather(a0,a1,a2,mesh,scene,itime); + Vec3vf4 b0,b1,b2; gather(b0,b1,b2,mesh,scene,itime+1); + p0 = lerp(a0,b0,vfloat4(ftime)); + p1 = lerp(a1,b1,vfloat4(ftime)); + p2 = lerp(a2,b2,vfloat4(ftime)); + } + } + + template + typename TriangleMi::Type TriangleMi::type; + + typedef TriangleMi<4> Triangle4i; +} diff --git a/thirdparty/embree/kernels/geometry/trianglei_intersector.h b/thirdparty/embree/kernels/geometry/trianglei_intersector.h new file mode 100644 index 000000000000..e2f106a62cd8 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/trianglei_intersector.h @@ -0,0 +1,336 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "trianglei.h" +#include "triangle_intersector_moeller.h" +#include "triangle_intersector_pluecker.h" + +namespace embree +{ + namespace isa + { + /*! Intersects M triangles with 1 ray */ + template + struct TriangleMiIntersector1Moeller + { + typedef TriangleMi Primitive; + typedef MoellerTrumboreIntersector1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + pre.intersect(ray,v0,v1,v2,/*UVIdentity(),*/Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + return pre.intersect(ray,v0,v1,v2,/*UVIdentity(),*/Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M triangles with K rays */ + template + struct TriangleMiIntersectorKMoeller + { + typedef TriangleMi Primitive; + typedef MoellerTrumboreIntersectorK Precalculations; + + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& tri) + { + const Scene* scene = context->scene; + for (size_t i=0; i::size()); + const Vec3vf v0 = tri.template getVertex<0>(i,scene); + const Vec3vf v1 = tri.template getVertex<1>(i,scene); + const Vec3vf v2 = tri.template getVertex<2>(i,scene); + pre.intersectK(valid_i,ray,v0,v1,v2,/*UVIdentity(),*/IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& tri) + { + vbool valid0 = valid_i; + const Scene* scene = context->scene; + + for (size_t i=0; i::size()); + const Vec3vf v0 = tri.template getVertex<0>(i,scene); + const Vec3vf v1 = tri.template getVertex<1>(i,scene); + const Vec3vf v2 = tri.template getVertex<2>(i,scene); + pre.intersectK(valid0,ray,v0,v1,v2,/*UVIdentity(),*/OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + pre.intersect(ray,k,v0,v1,v2,/*UVIdentity(),*/Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + return pre.intersect(ray,k,v0,v1,v2,/*UVIdentity(),*/Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + + /*! Intersects M triangles with 1 ray */ + template + struct TriangleMiIntersector1Pluecker + { + typedef TriangleMi Primitive; + typedef PlueckerIntersector1 Precalculations; + + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + pre.intersect(ray,v0,v1,v2,UVIdentity(),Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + return pre.intersect(ray,v0,v1,v2,UVIdentity(),Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M triangles with K rays */ + template + struct TriangleMiIntersectorKPluecker + { + typedef TriangleMi Primitive; + typedef PlueckerIntersectorK Precalculations; + + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& tri) + { + const Scene* scene = context->scene; + for (size_t i=0; i::size()); + const Vec3vf v0 = tri.template getVertex<0>(i,scene); + const Vec3vf v1 = tri.template getVertex<1>(i,scene); + const Vec3vf v2 = tri.template getVertex<2>(i,scene); + pre.intersectK(valid_i,ray,v0,v1,v2,UVIdentity(),IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& tri) + { + vbool valid0 = valid_i; + const Scene* scene = context->scene; + + for (size_t i=0; i::size()); + const Vec3vf v0 = tri.template getVertex<0>(i,scene); + const Vec3vf v1 = tri.template getVertex<1>(i,scene); + const Vec3vf v2 = tri.template getVertex<2>(i,scene); + pre.intersectK(valid0,ray,v0,v1,v2,UVIdentity(),OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + pre.intersect(ray,k,v0,v1,v2,UVIdentity(),Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0, v1, v2; tri.gather(v0,v1,v2,context->scene); + return pre.intersect(ray,k,v0,v1,v2,UVIdentity(),Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + + /*! Intersects M motion blur triangles with 1 ray */ + template + struct TriangleMiMBIntersector1Moeller + { + typedef TriangleMi Primitive; + typedef MoellerTrumboreIntersector1 Precalculations; + + /*! Intersect a ray with the M triangles and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()); + pre.intersect(ray,v0,v1,v2,/*UVIdentity(),*/Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()); + return pre.intersect(ray,v0,v1,v2,/*UVIdentity(),*/Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M motion blur triangles with K rays. */ + template + struct TriangleMiMBIntersectorKMoeller + { + typedef TriangleMi Primitive; + typedef MoellerTrumboreIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const TriangleMi& tri) + { + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + Vec3vf v0,v1,v2; tri.gather(valid_i,v0,v1,v2,i,context->scene,ray.time()); + pre.intersectK(valid_i,ray,v0,v1,v2,/*UVIdentity(),*/IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const TriangleMi& tri) + { + vbool valid0 = valid_i; + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + Vec3vf v0,v1,v2; tri.gather(valid_i,v0,v1,v2,i,context->scene,ray.time()); + pre.intersectK(valid0,ray,v0,v1,v2,/*UVIdentity(),*/OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const TriangleMi& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()[k]); + pre.intersect(ray,k,v0,v1,v2,/*UVIdentity(),*/Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const TriangleMi& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()[k]); + return pre.intersect(ray,k,v0,v1,v2,/*UVIdentity(),*/Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + + /*! Intersects M motion blur triangles with 1 ray */ + template + struct TriangleMiMBIntersector1Pluecker + { + typedef TriangleMi Primitive; + typedef PlueckerIntersector1 Precalculations; + + /*! Intersect a ray with the M triangles and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()); + pre.intersect(ray,v0,v1,v2,UVIdentity(),Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()); + return pre.intersect(ray,v0,v1,v2,UVIdentity(),Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M motion blur triangles with K rays. */ + template + struct TriangleMiMBIntersectorKPluecker + { + typedef TriangleMi Primitive; + typedef PlueckerIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const TriangleMi& tri) + { + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + Vec3vf v0,v1,v2; tri.gather(valid_i,v0,v1,v2,i,context->scene,ray.time()); + pre.intersectK(valid_i,ray,v0,v1,v2,UVIdentity(),IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const TriangleMi& tri) + { + vbool valid0 = valid_i; + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + Vec3vf v0,v1,v2; tri.gather(valid_i,v0,v1,v2,i,context->scene,ray.time()); + pre.intersectK(valid0,ray,v0,v1,v2,UVIdentity(),OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const TriangleMi& tri) + { + STAT3(normal.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()[k]); + pre.intersect(ray,k,v0,v1,v2,UVIdentity(),Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const TriangleMi& tri) + { + STAT3(shadow.trav_prims,1,1,1); + Vec3vf v0,v1,v2; tri.gather(v0,v1,v2,context->scene,ray.time()[k]); + return pre.intersect(ray,k,v0,v1,v2,UVIdentity(),Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/trianglev.h b/thirdparty/embree/kernels/geometry/trianglev.h new file mode 100644 index 000000000000..19af389e73e4 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/trianglev.h @@ -0,0 +1,157 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + /* Stores the vertices of M triangles in struct of array layout */ + template + struct TriangleMv + { + public: + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + static Type type; + + public: + + /* Returns maximum number of stored triangles */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline TriangleMv() {} + + /* Construction from vertices and IDs */ + __forceinline TriangleMv(const Vec3vf& v0, const Vec3vf& v1, const Vec3vf& v2, const vuint& geomIDs, const vuint& primIDs) + : v0(v0), v1(v1), v2(v2), geomIDs(geomIDs), primIDs(primIDs) {} + + /* Returns a mask that tells which triangles are valid */ + __forceinline vbool valid() const { return geomIDs != vuint(-1); } + + /* Returns true if the specified triangle is valid */ + __forceinline bool valid(const size_t i) const { assert(i& geomID() { return geomIDs; } + __forceinline const vuint& geomID() const { return geomIDs; } + __forceinline unsigned int geomID(const size_t i) const { assert(i& primID() { return primIDs; } + __forceinline const vuint& primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(i lower = min(v0,v1,v2); + Vec3vf upper = max(v0,v1,v2); + vbool mask = valid(); + lower.x = select(mask,lower.x,vfloat(pos_inf)); + lower.y = select(mask,lower.y,vfloat(pos_inf)); + lower.z = select(mask,lower.z,vfloat(pos_inf)); + upper.x = select(mask,upper.x,vfloat(neg_inf)); + upper.y = select(mask,upper.y,vfloat(neg_inf)); + upper.z = select(mask,upper.z,vfloat(neg_inf)); + return BBox3fa(Vec3fa(reduce_min(lower.x),reduce_min(lower.y),reduce_min(lower.z)), + Vec3fa(reduce_max(upper.x),reduce_max(upper.y),reduce_max(upper.z))); + } + + /* Non temporal store */ + __forceinline static void store_nt(TriangleMv* dst, const TriangleMv& src) + { + vfloat::store_nt(&dst->v0.x,src.v0.x); + vfloat::store_nt(&dst->v0.y,src.v0.y); + vfloat::store_nt(&dst->v0.z,src.v0.z); + vfloat::store_nt(&dst->v1.x,src.v1.x); + vfloat::store_nt(&dst->v1.y,src.v1.y); + vfloat::store_nt(&dst->v1.z,src.v1.z); + vfloat::store_nt(&dst->v2.x,src.v2.x); + vfloat::store_nt(&dst->v2.y,src.v2.y); + vfloat::store_nt(&dst->v2.z,src.v2.z); + vuint::store_nt(&dst->geomIDs,src.geomIDs); + vuint::store_nt(&dst->primIDs,src.primIDs); + } + + /* Fill triangle from triangle list */ + __forceinline void fill(const PrimRef* prims, size_t& begin, size_t end, Scene* scene) + { + vuint vgeomID = -1, vprimID = -1; + Vec3vf v0 = zero, v1 = zero, v2 = zero; + + for (size_t i=0; iget(geomID); + const TriangleMesh::Triangle& tri = mesh->triangle(primID); + const Vec3fa& p0 = mesh->vertex(tri.v[0]); + const Vec3fa& p1 = mesh->vertex(tri.v[1]); + const Vec3fa& p2 = mesh->vertex(tri.v[2]); + vgeomID [i] = geomID; + vprimID [i] = primID; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + } + TriangleMv::store_nt(this,TriangleMv(v0,v1,v2,vgeomID,vprimID)); + } + + /* Updates the primitive */ + __forceinline BBox3fa update(TriangleMesh* mesh) + { + BBox3fa bounds = empty; + vuint vgeomID = -1, vprimID = -1; + Vec3vf v0 = zero, v1 = zero, v2 = zero; + + for (size_t i=0; itriangle(primId); + const Vec3fa p0 = mesh->vertex(tri.v[0]); + const Vec3fa p1 = mesh->vertex(tri.v[1]); + const Vec3fa p2 = mesh->vertex(tri.v[2]); + bounds.extend(merge(BBox3fa(p0),BBox3fa(p1),BBox3fa(p2))); + vgeomID [i] = geomId; + vprimID [i] = primId; + v0.x[i] = p0.x; v0.y[i] = p0.y; v0.z[i] = p0.z; + v1.x[i] = p1.x; v1.y[i] = p1.y; v1.z[i] = p1.z; + v2.x[i] = p2.x; v2.y[i] = p2.y; v2.z[i] = p2.z; + } + new (this) TriangleMv(v0,v1,v2,vgeomID,vprimID); + return bounds; + } + + public: + Vec3vf v0; // 1st vertex of the triangles + Vec3vf v1; // 2nd vertex of the triangles + Vec3vf v2; // 3rd vertex of the triangles + private: + vuint geomIDs; // geometry ID + vuint primIDs; // primitive ID + }; + + template + typename TriangleMv::Type TriangleMv::type; + + typedef TriangleMv<4> Triangle4v; +} diff --git a/thirdparty/embree/kernels/geometry/trianglev_intersector.h b/thirdparty/embree/kernels/geometry/trianglev_intersector.h new file mode 100644 index 000000000000..6af0d5a11c09 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/trianglev_intersector.h @@ -0,0 +1,206 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "triangle.h" +#include "triangle_intersector_pluecker.h" +#include "triangle_intersector_moeller.h" +#include "triangle_intersector_woop.h" + +namespace embree +{ + namespace isa + { + /*! Intersects M triangles with 1 ray */ + template + struct TriangleMvIntersector1Moeller + { + typedef TriangleMv Primitive; + typedef MoellerTrumboreIntersector1 Precalculations; + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect(ray,tri.v0,tri.v1,tri.v2,/*UVIdentity(),*/Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.intersect(ray,tri.v0,tri.v1,tri.v2,/*UVIdentity(),*/Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + + template + struct TriangleMvIntersector1Woop + { + typedef TriangleMv Primitive; + typedef WoopIntersector1 intersec; + typedef WoopPrecalculations1 Precalculations; + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + intersec::intersect(ray,pre,tri.v0,tri.v1,tri.v2,Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return intersec::intersect(ray,pre,tri.v0,tri.v1,tri.v2,Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + + /*! Intersects M triangles with K rays */ + template + struct TriangleMvIntersectorKMoeller + { + typedef TriangleMv Primitive; + typedef MoellerTrumboreIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& tri) + { + for (size_t i=0; i v0 = broadcast>(tri.v0,i); + const Vec3vf v1 = broadcast>(tri.v1,i); + const Vec3vf v2 = broadcast>(tri.v2,i); + pre.intersectK(valid_i,ray,v0,v1,v2,/*UVIdentity(),*/IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& tri) + { + vbool valid0 = valid_i; + + for (size_t i=0; i v0 = broadcast>(tri.v0,i); + const Vec3vf v1 = broadcast>(tri.v1,i); + const Vec3vf v2 = broadcast>(tri.v2,i); + pre.intersectK(valid0,ray,v0,v1,v2,/*UVIdentity(),*/OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect(ray,k,tri.v0,tri.v1,tri.v2,/*UVIdentity(),*/Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); //FIXME: M,Mx + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.intersect(ray,k,tri.v0,tri.v1,tri.v2,/*UVIdentity(),*/Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); //FIXME: M,Mx + } + }; + + /*! Intersects M triangles with 1 ray */ + template + struct TriangleMvIntersector1Pluecker + { + typedef TriangleMv Primitive; + typedef PlueckerIntersector1 Precalculations; + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHit& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect(ray,tri.v0,tri.v1,tri.v2,UVIdentity(),Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.intersect(ray,tri.v0,tri.v1,tri.v2,UVIdentity(),Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M triangles with K rays */ + template + struct TriangleMvIntersectorKPluecker + { + typedef TriangleMv Primitive; + typedef PlueckerIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const Primitive& tri) + { + for (size_t i=0; i v0 = broadcast>(tri.v0,i); + const Vec3vf v1 = broadcast>(tri.v1,i); + const Vec3vf v2 = broadcast>(tri.v2,i); + pre.intersectK(valid_i,ray,v0,v1,v2,UVIdentity(),IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const Primitive& tri) + { + vbool valid0 = valid_i; + + for (size_t i=0; i v0 = broadcast>(tri.v0,i); + const Vec3vf v1 = broadcast>(tri.v1,i); + const Vec3vf v2 = broadcast>(tri.v2,i); + pre.intersectK(valid0,ray,v0,v1,v2,UVIdentity(),OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(normal.trav_prims,1,1,1); + pre.intersect(ray,k,tri.v0,tri.v1,tri.v2,UVIdentity(),Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); //FIXME: M,Mx + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const Primitive& tri) + { + STAT3(shadow.trav_prims,1,1,1); + return pre.intersect(ray,k,tri.v0,tri.v1,tri.v2,UVIdentity(),Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); //FIXME: M,Mx + } + }; + } +} diff --git a/thirdparty/embree/kernels/geometry/trianglev_mb.h b/thirdparty/embree/kernels/geometry/trianglev_mb.h new file mode 100644 index 000000000000..63137aee166c --- /dev/null +++ b/thirdparty/embree/kernels/geometry/trianglev_mb.h @@ -0,0 +1,201 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "primitive.h" + +namespace embree +{ + /* Stores the vertices of M triangles in struct of array layout */ + template + struct TriangleMvMB + { + public: + struct Type : public PrimitiveType + { + const char* name() const; + size_t sizeActive(const char* This) const; + size_t sizeTotal(const char* This) const; + size_t getBytes(const char* This) const; + }; + + static Type type; + + public: + + /* primitive supports single time segments */ + static const bool singleTimeSegment = true; + + /* Returns maximum number of stored triangles */ + static __forceinline size_t max_size() { return M; } + + /* Returns required number of primitive blocks for N primitives */ + static __forceinline size_t blocks(size_t N) { return (N+max_size()-1)/max_size(); } + + public: + + /* Default constructor */ + __forceinline TriangleMvMB() {} + + /* Construction from vertices and IDs */ + __forceinline TriangleMvMB(const Vec3vf& a0, const Vec3vf& a1, + const Vec3vf& b0, const Vec3vf& b1, + const Vec3vf& c0, const Vec3vf& c1, + const vuint& geomIDs, const vuint& primIDs) + : v0(a0), v1(b0), v2(c0), dv0(a1-a0), dv1(b1-b0), dv2(c1-c0), geomIDs(geomIDs), primIDs(primIDs) {} + + /* Returns a mask that tells which triangles are valid */ + __forceinline vbool valid() const { return geomIDs != vuint(-1); } + + /* Returns if the specified triangle is valid */ + __forceinline bool valid(const size_t i) const { assert(i& geomID() { return geomIDs; } + __forceinline const vuint& geomID() const { return geomIDs; } + __forceinline unsigned int geomID(const size_t i) const { assert(i& primID() { return primIDs; } + __forceinline const vuint& primID() const { return primIDs; } + __forceinline unsigned int primID(const size_t i) const { assert(i lower = min(v0,v1,v2); + Vec3vf upper = max(v0,v1,v2); + const vbool mask = valid(); + lower.x = select(mask,lower.x,vfloat(pos_inf)); + lower.y = select(mask,lower.y,vfloat(pos_inf)); + lower.z = select(mask,lower.z,vfloat(pos_inf)); + upper.x = select(mask,upper.x,vfloat(neg_inf)); + upper.y = select(mask,upper.y,vfloat(neg_inf)); + upper.z = select(mask,upper.z,vfloat(neg_inf)); + return BBox3fa(Vec3fa(reduce_min(lower.x),reduce_min(lower.y),reduce_min(lower.z)), + Vec3fa(reduce_max(upper.x),reduce_max(upper.y),reduce_max(upper.z))); + } + + /* Calculate the bounds of the triangles at t1 */ + __forceinline BBox3fa bounds1() const + { + const Vec3vf p0 = v0+dv0; + const Vec3vf p1 = v1+dv1; + const Vec3vf p2 = v2+dv2; + Vec3vf lower = min(p0,p1,p2); + Vec3vf upper = max(p0,p1,p2); + const vbool mask = valid(); + lower.x = select(mask,lower.x,vfloat(pos_inf)); + lower.y = select(mask,lower.y,vfloat(pos_inf)); + lower.z = select(mask,lower.z,vfloat(pos_inf)); + upper.x = select(mask,upper.x,vfloat(neg_inf)); + upper.y = select(mask,upper.y,vfloat(neg_inf)); + upper.z = select(mask,upper.z,vfloat(neg_inf)); + return BBox3fa(Vec3fa(reduce_min(lower.x),reduce_min(lower.y),reduce_min(lower.z)), + Vec3fa(reduce_max(upper.x),reduce_max(upper.y),reduce_max(upper.z))); + } + + /* Calculate the linear bounds of the primitive */ + __forceinline LBBox3fa linearBounds() const { + return LBBox3fa(bounds0(),bounds1()); + } + + /* Fill triangle from triangle list */ + __forceinline LBBox3fa fillMB(const PrimRef* prims, size_t& begin, size_t end, Scene* scene, size_t itime) + { + vuint vgeomID = -1, vprimID = -1; + Vec3vf va0 = zero, vb0 = zero, vc0 = zero; + Vec3vf va1 = zero, vb1 = zero, vc1 = zero; + + BBox3fa bounds0 = empty; + BBox3fa bounds1 = empty; + + for (size_t i=0; iget(geomID); + const TriangleMesh::Triangle& tri = mesh->triangle(primID); + const Vec3fa& a0 = mesh->vertex(tri.v[0],itime+0); bounds0.extend(a0); + const Vec3fa& a1 = mesh->vertex(tri.v[0],itime+1); bounds1.extend(a1); + const Vec3fa& b0 = mesh->vertex(tri.v[1],itime+0); bounds0.extend(b0); + const Vec3fa& b1 = mesh->vertex(tri.v[1],itime+1); bounds1.extend(b1); + const Vec3fa& c0 = mesh->vertex(tri.v[2],itime+0); bounds0.extend(c0); + const Vec3fa& c1 = mesh->vertex(tri.v[2],itime+1); bounds1.extend(c1); + vgeomID [i] = geomID; + vprimID [i] = primID; + va0.x[i] = a0.x; va0.y[i] = a0.y; va0.z[i] = a0.z; + va1.x[i] = a1.x; va1.y[i] = a1.y; va1.z[i] = a1.z; + vb0.x[i] = b0.x; vb0.y[i] = b0.y; vb0.z[i] = b0.z; + vb1.x[i] = b1.x; vb1.y[i] = b1.y; vb1.z[i] = b1.z; + vc0.x[i] = c0.x; vc0.y[i] = c0.y; vc0.z[i] = c0.z; + vc1.x[i] = c1.x; vc1.y[i] = c1.y; vc1.z[i] = c1.z; + } + new (this) TriangleMvMB(va0,va1,vb0,vb1,vc0,vc1,vgeomID,vprimID); + return LBBox3fa(bounds0,bounds1); + } + + /* Fill triangle from triangle list */ + __forceinline LBBox3fa fillMB(const PrimRefMB* prims, size_t& begin, size_t end, Scene* scene, const BBox1f time_range) + { + vuint vgeomID = -1, vprimID = -1; + Vec3vf va0 = zero, vb0 = zero, vc0 = zero; + Vec3vf va1 = zero, vb1 = zero, vc1 = zero; + + LBBox3fa allBounds = empty; + for (size_t i=0; iget(geomID); + const range itime_range = mesh->timeSegmentRange(time_range); + assert(itime_range.size() == 1); + const int ilower = itime_range.begin(); + const TriangleMesh::Triangle& tri = mesh->triangle(primID); + allBounds.extend(mesh->linearBounds(primID, time_range)); + const Vec3fa& a0 = mesh->vertex(tri.v[0],ilower+0); + const Vec3fa& a1 = mesh->vertex(tri.v[0],ilower+1); + const Vec3fa& b0 = mesh->vertex(tri.v[1],ilower+0); + const Vec3fa& b1 = mesh->vertex(tri.v[1],ilower+1); + const Vec3fa& c0 = mesh->vertex(tri.v[2],ilower+0); + const Vec3fa& c1 = mesh->vertex(tri.v[2],ilower+1); + const BBox1f time_range_v(mesh->timeStep(ilower+0),mesh->timeStep(ilower+1)); + auto a01 = globalLinear(std::make_pair(a0,a1),time_range_v); + auto b01 = globalLinear(std::make_pair(b0,b1),time_range_v); + auto c01 = globalLinear(std::make_pair(c0,c1),time_range_v); + vgeomID [i] = geomID; + vprimID [i] = primID; + va0.x[i] = a01.first .x; va0.y[i] = a01.first .y; va0.z[i] = a01.first .z; + va1.x[i] = a01.second.x; va1.y[i] = a01.second.y; va1.z[i] = a01.second.z; + vb0.x[i] = b01.first .x; vb0.y[i] = b01.first .y; vb0.z[i] = b01.first .z; + vb1.x[i] = b01.second.x; vb1.y[i] = b01.second.y; vb1.z[i] = b01.second.z; + vc0.x[i] = c01.first .x; vc0.y[i] = c01.first .y; vc0.z[i] = c01.first .z; + vc1.x[i] = c01.second.x; vc1.y[i] = c01.second.y; vc1.z[i] = c01.second.z; + } + new (this) TriangleMvMB(va0,va1,vb0,vb1,vc0,vc1,vgeomID,vprimID); + return allBounds; + } + + public: + Vec3vf v0; // 1st vertex of the triangles + Vec3vf v1; // 2nd vertex of the triangles + Vec3vf v2; // 3rd vertex of the triangles + Vec3vf dv0; // difference vector between time steps t0 and t1 for first vertex + Vec3vf dv1; // difference vector between time steps t0 and t1 for second vertex + Vec3vf dv2; // difference vector between time steps t0 and t1 for third vertex + private: + vuint geomIDs; // geometry ID + vuint primIDs; // primitive ID + }; + + template + typename TriangleMvMB::Type TriangleMvMB::type; + + typedef TriangleMvMB<4> Triangle4vMB; +} diff --git a/thirdparty/embree/kernels/geometry/trianglev_mb_intersector.h b/thirdparty/embree/kernels/geometry/trianglev_mb_intersector.h new file mode 100644 index 000000000000..35a260d826e9 --- /dev/null +++ b/thirdparty/embree/kernels/geometry/trianglev_mb_intersector.h @@ -0,0 +1,211 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "triangle.h" +#include "intersector_epilog.h" + +namespace embree +{ + namespace isa + { + /*! Intersects M motion blur triangles with 1 ray */ + template + struct TriangleMvMBIntersector1Moeller + { + typedef TriangleMvMB Primitive; + typedef MoellerTrumboreIntersector1 Precalculations; + + /*! Intersect a ray with the M triangles and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(normal.trav_prims,1,1,1); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + pre.intersect(ray,v0,v1,v2,Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(shadow.trav_prims,1,1,1); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + return pre.intersect(ray,v0,v1,v2,Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M motion blur triangles with K rays. */ + template + struct TriangleMvMBIntersectorKMoeller + { + typedef TriangleMvMB Primitive; + typedef MoellerTrumboreIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const TriangleMvMB& tri) + { + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,broadcast>(tri.dv0,i),broadcast>(tri.v0,i)); + const Vec3vf v1 = madd(time,broadcast>(tri.dv1,i),broadcast>(tri.v1,i)); + const Vec3vf v2 = madd(time,broadcast>(tri.dv2,i),broadcast>(tri.v2,i)); + pre.intersectK(valid_i,ray,v0,v1,v2,IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const TriangleMvMB& tri) + { + vbool valid0 = valid_i; + + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,broadcast>(tri.dv0,i),broadcast>(tri.v0,i)); + const Vec3vf v1 = madd(time,broadcast>(tri.dv1,i),broadcast>(tri.v1,i)); + const Vec3vf v2 = madd(time,broadcast>(tri.dv2,i),broadcast>(tri.v2,i)); + pre.intersectK(valid0,ray,v0,v1,v2,OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(normal.trav_prims,1,1,1); + const Vec3vf time(ray.time()[k]); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + pre.intersect(ray,k,v0,v1,v2,Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(shadow.trav_prims,1,1,1); + const Vec3vf time(ray.time()[k]); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + return pre.intersect(ray,k,v0,v1,v2,Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + + /*! Intersects M motion blur triangles with 1 ray */ + template + struct TriangleMvMBIntersector1Pluecker + { + typedef TriangleMvMB Primitive; + typedef PlueckerIntersector1 Precalculations; + + /*! Intersect a ray with the M triangles and updates the hit. */ + static __forceinline void intersect(const Precalculations& pre, RayHit& ray, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(normal.trav_prims,1,1,1); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + pre.intersect(ray,v0,v1,v2,UVIdentity(),Intersect1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of M triangles. */ + static __forceinline bool occluded(const Precalculations& pre, Ray& ray, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(shadow.trav_prims,1,1,1); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + return pre.intersect(ray,v0,v1,v2,UVIdentity(),Occluded1EpilogM(ray,context,tri.geomID(),tri.primID())); + } + + static __forceinline bool pointQuery(PointQuery* query, PointQueryContext* context, const Primitive& tri) + { + return PrimitivePointQuery1::pointQuery(query, context, tri); + } + }; + + /*! Intersects M motion blur triangles with K rays. */ + template + struct TriangleMvMBIntersectorKPluecker + { + typedef TriangleMvMB Primitive; + typedef PlueckerIntersectorK Precalculations; + + /*! Intersects K rays with M triangles. */ + static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, IntersectContext* context, const TriangleMvMB& tri) + { + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(normal.trav_prims,1,popcnt(valid_i),K); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,broadcast>(tri.dv0,i),broadcast>(tri.v0,i)); + const Vec3vf v1 = madd(time,broadcast>(tri.dv1,i),broadcast>(tri.v1,i)); + const Vec3vf v2 = madd(time,broadcast>(tri.dv2,i),broadcast>(tri.v2,i)); + pre.intersectK(valid_i,ray,v0,v1,v2,UVIdentity(),IntersectKEpilogM(ray,context,tri.geomID(),tri.primID(),i)); + } + } + + /*! Test for K rays if they are occluded by any of the M triangles. */ + static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, IntersectContext* context, const TriangleMvMB& tri) + { + vbool valid0 = valid_i; + + for (size_t i=0; i::max_size(); i++) + { + if (!tri.valid(i)) break; + STAT3(shadow.trav_prims,1,popcnt(valid0),K); + const Vec3vf time(ray.time()); + const Vec3vf v0 = madd(time,broadcast>(tri.dv0,i),broadcast>(tri.v0,i)); + const Vec3vf v1 = madd(time,broadcast>(tri.dv1,i),broadcast>(tri.v1,i)); + const Vec3vf v2 = madd(time,broadcast>(tri.dv2,i),broadcast>(tri.v2,i)); + pre.intersectK(valid0,ray,v0,v1,v2,UVIdentity(),OccludedKEpilogM(valid0,ray,context,tri.geomID(),tri.primID(),i)); + if (none(valid0)) break; + } + return !valid0; + } + + /*! Intersect a ray with M triangles and updates the hit. */ + static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(normal.trav_prims,1,1,1); + const Vec3vf time(ray.time()[k]); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + pre.intersect(ray,k,v0,v1,v2,UVIdentity(),Intersect1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + + /*! Test if the ray is occluded by one of the M triangles. */ + static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, IntersectContext* context, const TriangleMvMB& tri) + { + STAT3(shadow.trav_prims,1,1,1); + const Vec3vf time(ray.time()[k]); + const Vec3vf v0 = madd(time,Vec3vf(tri.dv0),Vec3vf(tri.v0)); + const Vec3vf v1 = madd(time,Vec3vf(tri.dv1),Vec3vf(tri.v1)); + const Vec3vf v2 = madd(time,Vec3vf(tri.dv2),Vec3vf(tri.v2)); + return pre.intersect(ray,k,v0,v1,v2,UVIdentity(),Occluded1KEpilogM(ray,k,context,tri.geomID(),tri.primID())); + } + }; + } +} diff --git a/thirdparty/embree/kernels/hash.h b/thirdparty/embree/kernels/hash.h new file mode 100644 index 000000000000..f11598b7ab89 --- /dev/null +++ b/thirdparty/embree/kernels/hash.h @@ -0,0 +1,5 @@ + +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#define RTC_HASH "69bd4c272f1ed608494f233ecfff3feec516880b" diff --git a/thirdparty/embree/kernels/subdiv/bezier_curve.h b/thirdparty/embree/kernels/subdiv/bezier_curve.h new file mode 100644 index 000000000000..c0e78820f8ec --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/bezier_curve.h @@ -0,0 +1,669 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "../common/scene_curves.h" + +namespace embree +{ + class BezierBasis + { + public: + + template + static __forceinline Vec4 eval(const T& u) + { + const T t1 = u; + const T t0 = 1.0f-t1; + const T B0 = t0 * t0 * t0; + const T B1 = 3.0f * t1 * (t0 * t0); + const T B2 = 3.0f * (t1 * t1) * t0; + const T B3 = t1 * t1 * t1; + return Vec4(B0,B1,B2,B3); + } + + template + static __forceinline Vec4 derivative(const T& u) + { + const T t1 = u; + const T t0 = 1.0f-t1; + const T B0 = -(t0*t0); + const T B1 = madd(-2.0f,t0*t1,t0*t0); + const T B2 = msub(+2.0f,t0*t1,t1*t1); + const T B3 = +(t1*t1); + return T(3.0f)*Vec4(B0,B1,B2,B3); + } + + template + static __forceinline Vec4 derivative2(const T& u) + { + const T t1 = u; + const T t0 = 1.0f-t1; + const T B0 = t0; + const T B1 = madd(-2.0f,t0,t1); + const T B2 = madd(-2.0f,t1,t0); + const T B3 = t1; + return T(6.0f)*Vec4(B0,B1,B2,B3); + } + }; + + struct PrecomputedBezierBasis + { + enum { N = 16 }; + public: + PrecomputedBezierBasis() {} + PrecomputedBezierBasis(int shift); + + /* basis for bezier evaluation */ + public: + float c0[N+1][N+1]; + float c1[N+1][N+1]; + float c2[N+1][N+1]; + float c3[N+1][N+1]; + + /* basis for bezier derivative evaluation */ + public: + float d0[N+1][N+1]; + float d1[N+1][N+1]; + float d2[N+1][N+1]; + float d3[N+1][N+1]; + }; + extern PrecomputedBezierBasis bezier_basis0; + extern PrecomputedBezierBasis bezier_basis1; + + + template + struct LinearBezierCurve + { + V v0,v1; + + __forceinline LinearBezierCurve () {} + + __forceinline LinearBezierCurve (const LinearBezierCurve& other) + : v0(other.v0), v1(other.v1) {} + + __forceinline LinearBezierCurve& operator= (const LinearBezierCurve& other) { + v0 = other.v0; v1 = other.v1; return *this; + } + + __forceinline LinearBezierCurve (const V& v0, const V& v1) + : v0(v0), v1(v1) {} + + __forceinline V begin() const { return v0; } + __forceinline V end () const { return v1; } + + bool hasRoot() const; + + friend embree_ostream operator<<(embree_ostream cout, const LinearBezierCurve& a) { + return cout << "LinearBezierCurve (" << a.v0 << ", " << a.v1 << ")"; + } + }; + + template<> __forceinline bool LinearBezierCurve::hasRoot() const { + return numRoots(v0,v1); + } + + template + struct QuadraticBezierCurve + { + V v0,v1,v2; + + __forceinline QuadraticBezierCurve () {} + + __forceinline QuadraticBezierCurve (const QuadraticBezierCurve& other) + : v0(other.v0), v1(other.v1), v2(other.v2) {} + + __forceinline QuadraticBezierCurve& operator= (const QuadraticBezierCurve& other) { + v0 = other.v0; v1 = other.v1; v2 = other.v2; return *this; + } + + __forceinline QuadraticBezierCurve (const V& v0, const V& v1, const V& v2) + : v0(v0), v1(v1), v2(v2) {} + + __forceinline V begin() const { return v0; } + __forceinline V end () const { return v2; } + + __forceinline V interval() const { + return merge(v0,v1,v2); + } + + __forceinline BBox bounds() const { + return merge(BBox(v0),BBox(v1),BBox(v2)); + } + + friend embree_ostream operator<<(embree_ostream cout, const QuadraticBezierCurve& a) { + return cout << "QuadraticBezierCurve ( (" << a.u.lower << ", " << a.u.upper << "), " << a.v0 << ", " << a.v1 << ", " << a.v2 << ")"; + } + }; + + + typedef QuadraticBezierCurve QuadraticBezierCurve1f; + typedef QuadraticBezierCurve QuadraticBezierCurve2fa; + typedef QuadraticBezierCurve QuadraticBezierCurve3fa; + + template + struct CubicBezierCurve + { + Vertex v0,v1,v2,v3; + + __forceinline CubicBezierCurve() {} + + template + __forceinline CubicBezierCurve (const CubicBezierCurve& other) + : v0(other.v0), v1(other.v1), v2(other.v2), v3(other.v3) {} + + __forceinline CubicBezierCurve& operator= (const CubicBezierCurve& other) { + v0 = other.v0; v1 = other.v1; v2 = other.v2; v3 = other.v3; return *this; + } + + __forceinline CubicBezierCurve(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3) + : v0(v0), v1(v1), v2(v2), v3(v3) {} + + __forceinline Vertex begin() const { + return v0; + } + + __forceinline Vertex end() const { + return v3; + } + + __forceinline Vertex center() const { + return 0.25f*(v0+v1+v2+v3); + } + + __forceinline Vertex begin_direction() const { + return v1-v0; + } + + __forceinline Vertex end_direction() const { + return v3-v2; + } + + __forceinline CubicBezierCurve xfm(const Vertex& dx) const { + return CubicBezierCurve(dot(v0,dx),dot(v1,dx),dot(v2,dx),dot(v3,dx)); + } + + __forceinline CubicBezierCurve vxfm(const Vertex& dx) const { + return CubicBezierCurve(dot(v0,dx),dot(v1,dx),dot(v2,dx),dot(v3,dx)); + } + + __forceinline CubicBezierCurve xfm(const Vertex& dx, const Vertex& p) const { + return CubicBezierCurve(dot(v0-p,dx),dot(v1-p,dx),dot(v2-p,dx),dot(v3-p,dx)); + } + + __forceinline CubicBezierCurve xfm(const LinearSpace3fa& space) const + { + const Vec3fa q0 = xfmVector(space,v0); + const Vec3fa q1 = xfmVector(space,v1); + const Vec3fa q2 = xfmVector(space,v2); + const Vec3fa q3 = xfmVector(space,v3); + return CubicBezierCurve(q0,q1,q2,q3); + } + + __forceinline CubicBezierCurve xfm(const LinearSpace3fa& space, const Vec3fa& p) const + { + const Vec3fa q0 = xfmVector(space,v0-p); + const Vec3fa q1 = xfmVector(space,v1-p); + const Vec3fa q2 = xfmVector(space,v2-p); + const Vec3fa q3 = xfmVector(space,v3-p); + return CubicBezierCurve(q0,q1,q2,q3); + } + + __forceinline CubicBezierCurve xfm_pr(const LinearSpace3fa& space, const Vec3fa& p) const + { + const Vec3ff q0(xfmVector(space,(Vec3fa)v0-p), v0.w); + const Vec3ff q1(xfmVector(space,(Vec3fa)v1-p), v1.w); + const Vec3ff q2(xfmVector(space,(Vec3fa)v2-p), v2.w); + const Vec3ff q3(xfmVector(space,(Vec3fa)v3-p), v3.w); + return CubicBezierCurve(q0,q1,q2,q3); + } + + __forceinline CubicBezierCurve xfm(const LinearSpace3fa& space, const Vec3fa& p, const float s) const + { + const Vec3fa q0 = xfmVector(space,s*(v0-p)); + const Vec3fa q1 = xfmVector(space,s*(v1-p)); + const Vec3fa q2 = xfmVector(space,s*(v2-p)); + const Vec3fa q3 = xfmVector(space,s*(v3-p)); + return CubicBezierCurve(q0,q1,q2,q3); + } + + __forceinline int maxRoots() const; + + __forceinline BBox bounds() const { + return merge(BBox(v0),BBox(v1),BBox(v2),BBox(v3)); + } + + __forceinline friend CubicBezierCurve operator +( const CubicBezierCurve& a, const CubicBezierCurve& b ) { + return CubicBezierCurve(a.v0+b.v0,a.v1+b.v1,a.v2+b.v2,a.v3+b.v3); + } + + __forceinline friend CubicBezierCurve operator -( const CubicBezierCurve& a, const CubicBezierCurve& b ) { + return CubicBezierCurve(a.v0-b.v0,a.v1-b.v1,a.v2-b.v2,a.v3-b.v3); + } + + __forceinline friend CubicBezierCurve operator -( const CubicBezierCurve& a, const Vertex& b ) { + return CubicBezierCurve(a.v0-b,a.v1-b,a.v2-b,a.v3-b); + } + + __forceinline friend CubicBezierCurve operator *( const Vertex& a, const CubicBezierCurve& b ) { + return CubicBezierCurve(a*b.v0,a*b.v1,a*b.v2,a*b.v3); + } + + __forceinline friend CubicBezierCurve cmadd( const Vertex& a, const CubicBezierCurve& b, const CubicBezierCurve& c) { + return CubicBezierCurve(madd(a,b.v0,c.v0),madd(a,b.v1,c.v1),madd(a,b.v2,c.v2),madd(a,b.v3,c.v3)); + } + + __forceinline friend CubicBezierCurve clerp ( const CubicBezierCurve& a, const CubicBezierCurve& b, const Vertex& t ) { + return cmadd((Vertex(1.0f)-t),a,t*b); + } + + __forceinline friend CubicBezierCurve merge ( const CubicBezierCurve& a, const CubicBezierCurve& b ) { + return CubicBezierCurve(merge(a.v0,b.v0),merge(a.v1,b.v1),merge(a.v2,b.v2),merge(a.v3,b.v3)); + } + + __forceinline void split(CubicBezierCurve& left, CubicBezierCurve& right, const float t = 0.5f) const + { + const Vertex p00 = v0; + const Vertex p01 = v1; + const Vertex p02 = v2; + const Vertex p03 = v3; + + const Vertex p10 = lerp(p00,p01,t); + const Vertex p11 = lerp(p01,p02,t); + const Vertex p12 = lerp(p02,p03,t); + const Vertex p20 = lerp(p10,p11,t); + const Vertex p21 = lerp(p11,p12,t); + const Vertex p30 = lerp(p20,p21,t); + + new (&left ) CubicBezierCurve(p00,p10,p20,p30); + new (&right) CubicBezierCurve(p30,p21,p12,p03); + } + + __forceinline CubicBezierCurve split() const + { + const float u0 = 0.0f, u1 = 1.0f; + const float dscale = (u1-u0)*(1.0f/(3.0f*(VSIZEX-1))); + const vfloatx vu0 = lerp(u0,u1,vfloatx(step)*(1.0f/(VSIZEX-1))); + Vec2vfx P0, dP0du; evalN(vu0,P0,dP0du); dP0du = dP0du * Vec2vfx(dscale); + const Vec2vfx P3 = shift_right_1(P0); + const Vec2vfx dP3du = shift_right_1(dP0du); + const Vec2vfx P1 = P0 + dP0du; + const Vec2vfx P2 = P3 - dP3du; + return CubicBezierCurve(P0,P1,P2,P3); + } + + __forceinline CubicBezierCurve split(const BBox1f& u) const + { + const float u0 = u.lower, u1 = u.upper; + const float dscale = (u1-u0)*(1.0f/(3.0f*(VSIZEX-1))); + const vfloatx vu0 = lerp(u0,u1,vfloatx(step)*(1.0f/(VSIZEX-1))); + Vec2vfx P0, dP0du; evalN(vu0,P0,dP0du); dP0du = dP0du * Vec2vfx(dscale); + const Vec2vfx P3 = shift_right_1(P0); + const Vec2vfx dP3du = shift_right_1(dP0du); + const Vec2vfx P1 = P0 + dP0du; + const Vec2vfx P2 = P3 - dP3du; + return CubicBezierCurve(P0,P1,P2,P3); + } + + __forceinline void eval(float t, Vertex& p, Vertex& dp) const + { + const Vertex p00 = v0; + const Vertex p01 = v1; + const Vertex p02 = v2; + const Vertex p03 = v3; + + const Vertex p10 = lerp(p00,p01,t); + const Vertex p11 = lerp(p01,p02,t); + const Vertex p12 = lerp(p02,p03,t); + const Vertex p20 = lerp(p10,p11,t); + const Vertex p21 = lerp(p11,p12,t); + const Vertex p30 = lerp(p20,p21,t); + + p = p30; + dp = Vertex(3.0f)*(p21-p20); + } + +#if 0 + __forceinline Vertex eval(float t) const + { + const Vertex p00 = v0; + const Vertex p01 = v1; + const Vertex p02 = v2; + const Vertex p03 = v3; + + const Vertex p10 = lerp(p00,p01,t); + const Vertex p11 = lerp(p01,p02,t); + const Vertex p12 = lerp(p02,p03,t); + const Vertex p20 = lerp(p10,p11,t); + const Vertex p21 = lerp(p11,p12,t); + const Vertex p30 = lerp(p20,p21,t); + + return p30; + } +#else + __forceinline Vertex eval(const float t) const + { + const Vec4 b = BezierBasis::eval(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } +#endif + + __forceinline Vertex eval_dt(float t) const + { + const Vertex p00 = v1-v0; + const Vertex p01 = v2-v1; + const Vertex p02 = v3-v2; + const Vertex p10 = lerp(p00,p01,t); + const Vertex p11 = lerp(p01,p02,t); + const Vertex p20 = lerp(p10,p11,t); + return Vertex(3.0f)*p20; + } + + __forceinline Vertex eval_du(const float t) const + { + const Vec4 b = BezierBasis::derivative(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline Vertex eval_dudu(const float t) const + { + const Vec4 b = BezierBasis::derivative2(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline void evalN(const vfloatx& t, Vec2vfx& p, Vec2vfx& dp) const + { + const Vec2vfx p00 = v0; + const Vec2vfx p01 = v1; + const Vec2vfx p02 = v2; + const Vec2vfx p03 = v3; + + const Vec2vfx p10 = lerp(p00,p01,t); + const Vec2vfx p11 = lerp(p01,p02,t); + const Vec2vfx p12 = lerp(p02,p03,t); + + const Vec2vfx p20 = lerp(p10,p11,t); + const Vec2vfx p21 = lerp(p11,p12,t); + + const Vec2vfx p30 = lerp(p20,p21,t); + + p = p30; + dp = vfloatx(3.0f)*(p21-p20); + } + + __forceinline void eval(const float t, Vertex& p, Vertex& dp, Vertex& ddp) const + { + const Vertex p00 = v0; + const Vertex p01 = v1; + const Vertex p02 = v2; + const Vertex p03 = v3; + const Vertex p10 = lerp(p00,p01,t); + const Vertex p11 = lerp(p01,p02,t); + const Vertex p12 = lerp(p02,p03,t); + const Vertex p20 = lerp(p10,p11,t); + const Vertex p21 = lerp(p11,p12,t); + const Vertex p30 = lerp(p20,p21,t); + p = p30; + dp = 3.0f*(p21-p20); + ddp = eval_dudu(t); + } + + __forceinline CubicBezierCurve clip(const Interval1f& u1) const + { + Vertex f0,df0; eval(u1.lower,f0,df0); + Vertex f1,df1; eval(u1.upper,f1,df1); + float s = u1.upper-u1.lower; + return CubicBezierCurve(f0,f0+s*(1.0f/3.0f)*df0,f1-s*(1.0f/3.0f)*df1,f1); + } + + __forceinline QuadraticBezierCurve derivative() const + { + const Vertex q0 = 3.0f*(v1-v0); + const Vertex q1 = 3.0f*(v2-v1); + const Vertex q2 = 3.0f*(v3-v2); + return QuadraticBezierCurve(q0,q1,q2); + } + + __forceinline BBox derivative_bounds(const Interval1f& u1) const + { + Vertex f0,df0; eval(u1.lower,f0,df0); + Vertex f3,df3; eval(u1.upper,f3,df3); + const float s = u1.upper-u1.lower; + const Vertex f1 = f0+s*(1.0f/3.0f)*df0; + const Vertex f2 = f3-s*(1.0f/3.0f)*df3; + const Vertex q0 = s*df0; + const Vertex q1 = 3.0f*(f2-f1); + const Vertex q2 = s*df3; + return merge(BBox(q0),BBox(q1),BBox(q2)); + } + + template + __forceinline Vec4vf veval(const vfloat& t) const + { + const Vec4vf b = BezierBasis::eval(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf veval_du(const vfloat& t) const + { + const Vec4vf b = BezierBasis::derivative(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf veval_dudu(const vfloat& t) const + { + const Vec4vf b = BezierBasis::derivative2(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline void veval(const vfloat& t, Vec4vf& p, Vec4vf& dp) const + { + const Vec4vf p00 = v0; + const Vec4vf p01 = v1; + const Vec4vf p02 = v2; + const Vec4vf p03 = v3; + + const Vec4vf p10 = lerp(p00,p01,t); + const Vec4vf p11 = lerp(p01,p02,t); + const Vec4vf p12 = lerp(p02,p03,t); + const Vec4vf p20 = lerp(p10,p11,t); + const Vec4vf p21 = lerp(p11,p12,t); + const Vec4vf p30 = lerp(p20,p21,t); + + p = p30; + dp = vfloat(3.0f)*(p21-p20); + } + + template> + __forceinline Vec eval0(const int ofs, const int size) const + { + assert(size <= PrecomputedBezierBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bezier_basis0.c0[size][ofs]), Vec(v0), + madd(vfloat::loadu(&bezier_basis0.c1[size][ofs]), Vec(v1), + madd(vfloat::loadu(&bezier_basis0.c2[size][ofs]), Vec(v2), + vfloat::loadu(&bezier_basis0.c3[size][ofs]) * Vec(v3)))); + } + + template> + __forceinline Vec eval1(const int ofs, const int size) const + { + assert(size <= PrecomputedBezierBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bezier_basis1.c0[size][ofs]), Vec(v0), + madd(vfloat::loadu(&bezier_basis1.c1[size][ofs]), Vec(v1), + madd(vfloat::loadu(&bezier_basis1.c2[size][ofs]), Vec(v2), + vfloat::loadu(&bezier_basis1.c3[size][ofs]) * Vec(v3)))); + } + + template> + __forceinline Vec derivative0(const int ofs, const int size) const + { + assert(size <= PrecomputedBezierBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bezier_basis0.d0[size][ofs]), Vec(v0), + madd(vfloat::loadu(&bezier_basis0.d1[size][ofs]), Vec(v1), + madd(vfloat::loadu(&bezier_basis0.d2[size][ofs]), Vec(v2), + vfloat::loadu(&bezier_basis0.d3[size][ofs]) * Vec(v3)))); + } + + template> + __forceinline Vec derivative1(const int ofs, const int size) const + { + assert(size <= PrecomputedBezierBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bezier_basis1.d0[size][ofs]), Vec(v0), + madd(vfloat::loadu(&bezier_basis1.d1[size][ofs]), Vec(v1), + madd(vfloat::loadu(&bezier_basis1.d2[size][ofs]), Vec(v2), + vfloat::loadu(&bezier_basis1.d3[size][ofs]) * Vec(v3)))); + } + + /* calculates bounds of bezier curve geometry */ + __forceinline BBox3fa accurateBounds() const + { + const int N = 7; + const float scale = 1.0f/(3.0f*(N-1)); + Vec3vfx pl(pos_inf), pu(neg_inf); + for (int i=0; i<=N; i+=VSIZEX) + { + vintx vi = vintx(i)+vintx(step); + vboolx valid = vi <= vintx(N); + const Vec3vfx p = eval0>(i,N); + const Vec3vfx dp = derivative0>(i,N); + const Vec3vfx pm = p-Vec3vfx(scale)*select(vi!=vintx(0),dp,Vec3vfx(zero)); + const Vec3vfx pp = p+Vec3vfx(scale)*select(vi!=vintx(N),dp,Vec3vfx(zero)); + pl = select(valid,min(pl,p,pm,pp),pl); // FIXME: use masked min + pu = select(valid,max(pu,p,pm,pp),pu); // FIXME: use masked min + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + return BBox3fa(lower,upper); + } + + /* calculates bounds of bezier curve geometry */ + __forceinline BBox3fa accurateRoundBounds() const + { + const int N = 7; + const float scale = 1.0f/(3.0f*(N-1)); + Vec4vfx pl(pos_inf), pu(neg_inf); + for (int i=0; i<=N; i+=VSIZEX) + { + vintx vi = vintx(i)+vintx(step); + vboolx valid = vi <= vintx(N); + const Vec4vfx p = eval0(i,N); + const Vec4vfx dp = derivative0(i,N); + const Vec4vfx pm = p-Vec4vfx(scale)*select(vi!=vintx(0),dp,Vec4vfx(zero)); + const Vec4vfx pp = p+Vec4vfx(scale)*select(vi!=vintx(N),dp,Vec4vfx(zero)); + pl = select(valid,min(pl,p,pm,pp),pl); // FIXME: use masked min + pu = select(valid,max(pu,p,pm,pp),pu); // FIXME: use masked min + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + const float r_min = reduce_min(pl.w); + const float r_max = reduce_max(pu.w); + const Vec3fa upper_r = Vec3fa(max(abs(r_min),abs(r_max))); + return enlarge(BBox3fa(lower,upper),upper_r); + } + + /* calculates bounds when tessellated into N line segments */ + __forceinline BBox3fa accurateFlatBounds(int N) const + { + if (likely(N == 4)) + { + const Vec4vf4 pi = eval0<4>(0,4); + const Vec3fa lower(reduce_min(pi.x),reduce_min(pi.y),reduce_min(pi.z)); + const Vec3fa upper(reduce_max(pi.x),reduce_max(pi.y),reduce_max(pi.z)); + const Vec3fa upper_r = Vec3fa(reduce_max(abs(pi.w))); + return enlarge(BBox3fa(min(lower,v3),max(upper,v3)),max(upper_r,Vec3fa(abs(v3.w)))); + } + else + { + Vec3vfx pl(pos_inf), pu(neg_inf); vfloatx ru(0.0f); + for (int i=0; i(i,N); + + pl.x = select(valid,min(pl.x,pi.x),pl.x); // FIXME: use masked min + pl.y = select(valid,min(pl.y,pi.y),pl.y); + pl.z = select(valid,min(pl.z,pi.z),pl.z); + + pu.x = select(valid,max(pu.x,pi.x),pu.x); // FIXME: use masked min + pu.y = select(valid,max(pu.y,pi.y),pu.y); + pu.z = select(valid,max(pu.z,pi.z),pu.z); + + ru = select(valid,max(ru,abs(pi.w)),ru); + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + const Vec3fa upper_r(reduce_max(ru)); + return enlarge(BBox3fa(min(lower,v3),max(upper,v3)),max(upper_r,Vec3fa(abs(v3.w)))); + } + } + + friend __forceinline embree_ostream operator<<(embree_ostream cout, const CubicBezierCurve& curve) { + return cout << "CubicBezierCurve { v0 = " << curve.v0 << ", v1 = " << curve.v1 << ", v2 = " << curve.v2 << ", v3 = " << curve.v3 << " }"; + } + }; + +#if defined(__AVX__) + template<> + __forceinline CubicBezierCurve CubicBezierCurve::clip(const Interval1f& u1) const + { + const vfloat8 p00 = vfloat8(v0); + const vfloat8 p01 = vfloat8(v1); + const vfloat8 p02 = vfloat8(v2); + const vfloat8 p03 = vfloat8(v3); + + const vfloat8 t(vfloat4(u1.lower),vfloat4(u1.upper)); + const vfloat8 p10 = lerp(p00,p01,t); + const vfloat8 p11 = lerp(p01,p02,t); + const vfloat8 p12 = lerp(p02,p03,t); + const vfloat8 p20 = lerp(p10,p11,t); + const vfloat8 p21 = lerp(p11,p12,t); + const vfloat8 p30 = lerp(p20,p21,t); + + const vfloat8 f01 = p30; + const vfloat8 df01 = vfloat8(3.0f)*(p21-p20); + + const vfloat4 f0 = extract4<0>(f01), f1 = extract4<1>(f01); + const vfloat4 df0 = extract4<0>(df01), df1 = extract4<1>(df01); + const float s = u1.upper-u1.lower; + return CubicBezierCurve(f0,f0+s*(1.0f/3.0f)*df0,f1-s*(1.0f/3.0f)*df1,f1); + } +#endif + + template using BezierCurveT = CubicBezierCurve; + + typedef CubicBezierCurve CubicBezierCurve1f; + typedef CubicBezierCurve CubicBezierCurve2fa; + typedef CubicBezierCurve CubicBezierCurve3fa; + typedef CubicBezierCurve BezierCurve3fa; + + template<> __forceinline int CubicBezierCurve::maxRoots() const + { + float eps = 1E-4f; + bool neg0 = v0 <= 0.0f; bool zero0 = fabs(v0) < eps; + bool neg1 = v1 <= 0.0f; bool zero1 = fabs(v1) < eps; + bool neg2 = v2 <= 0.0f; bool zero2 = fabs(v2) < eps; + bool neg3 = v3 <= 0.0f; bool zero3 = fabs(v3) < eps; + return (neg0 != neg1 || zero0) + (neg1 != neg2 || zero1) + (neg2 != neg3 || zero2 || zero3); + } + + template<> __forceinline int CubicBezierCurve::maxRoots() const { + return numRoots(v0,v1) + numRoots(v1,v2) + numRoots(v2,v3); + } + + __forceinline CubicBezierCurve enlargeRadiusToMinWidth(const IntersectContext* context, const CurveGeometry* geom, const Vec3fa& ray_org, const CubicBezierCurve& curve) + { + return CubicBezierCurve(enlargeRadiusToMinWidth(context,geom,ray_org,curve.v0), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v1), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v2), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v3)); + } +} diff --git a/thirdparty/embree/kernels/subdiv/bezier_patch.h b/thirdparty/embree/kernels/subdiv/bezier_patch.h new file mode 100644 index 000000000000..d87ed41ccb1f --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/bezier_patch.h @@ -0,0 +1,372 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_patch.h" +#include "bezier_curve.h" + +namespace embree +{ + template + static __forceinline T deCasteljau(const S& uu, const T& v0, const T& v1, const T& v2, const T& v3) + { + const T v0_1 = lerp(v0,v1,uu); + const T v1_1 = lerp(v1,v2,uu); + const T v2_1 = lerp(v2,v3,uu); + const T v0_2 = lerp(v0_1,v1_1,uu); + const T v1_2 = lerp(v1_1,v2_1,uu); + const T v0_3 = lerp(v0_2,v1_2,uu); + return v0_3; + } + + template + static __forceinline T deCasteljau_tangent(const S& uu, const T& v0, const T& v1, const T& v2, const T& v3) + { + const T v0_1 = lerp(v0,v1,uu); + const T v1_1 = lerp(v1,v2,uu); + const T v2_1 = lerp(v2,v3,uu); + const T v0_2 = lerp(v0_1,v1_1,uu); + const T v1_2 = lerp(v1_1,v2_1,uu); + return S(3.0f)*(v1_2-v0_2); + } + + template + __forceinline Vertex computeInnerBezierControlPoint(const Vertex v[4][4], const size_t y, const size_t x) { + return 1.0f / 36.0f * (16.0f * v[y][x] + 4.0f * (v[y-1][x] + v[y+1][x] + v[y][x-1] + v[y][x+1]) + (v[y-1][x-1] + v[y+1][x+1] + v[y-1][x+1] + v[y+1][x-1])); + } + + template + __forceinline Vertex computeTopEdgeBezierControlPoint(const Vertex v[4][4], const size_t y, const size_t x) { + return 1.0f / 18.0f * (8.0f * v[y][x] + 4.0f * v[y-1][x] + 2.0f * (v[y][x-1] + v[y][x+1]) + (v[y-1][x-1] + v[y-1][x+1])); + } + + template + __forceinline Vertex computeBottomEdgeBezierControlPoint(const Vertex v[4][4], const size_t y, const size_t x) { + return 1.0f / 18.0f * (8.0f * v[y][x] + 4.0f * v[y+1][x] + 2.0f * (v[y][x-1] + v[y][x+1]) + v[y+1][x-1] + v[y+1][x+1]); + } + + template + __forceinline Vertex computeLeftEdgeBezierControlPoint(const Vertex v[4][4], const size_t y, const size_t x) { + return 1.0f / 18.0f * (8.0f * v[y][x] + 4.0f * v[y][x-1] + 2.0f * (v[y-1][x] + v[y+1][x]) + v[y-1][x-1] + v[y+1][x-1]); + } + + template + __forceinline Vertex computeRightEdgeBezierControlPoint(const Vertex v[4][4], const size_t y, const size_t x) { + return 1.0f / 18.0f * (8.0f * v[y][x] + 4.0f * v[y][x+1] + 2.0f * (v[y-1][x] + v[y+1][x]) + v[y-1][x+1] + v[y+1][x+1]); + } + + template + __forceinline Vertex computeCornerBezierControlPoint(const Vertex v[4][4], const size_t y, const size_t x, const ssize_t delta_y, const ssize_t delta_x) + { + return 1.0f / 9.0f * (4.0f * v[y][x] + 2.0f * (v[y+delta_y][x] + v[y][x+delta_x]) + v[y+delta_y][x+delta_x]); + } + + template + class __aligned(64) BezierPatchT + { + public: + Vertex matrix[4][4]; + + public: + + __forceinline BezierPatchT() {} + + __forceinline BezierPatchT (const HalfEdge* edge, const char* vertices, size_t stride); + + __forceinline BezierPatchT(const CatmullClarkPatchT& patch); + + __forceinline BezierPatchT(const CatmullClarkPatchT& patch, + const BezierCurveT* border0, + const BezierCurveT* border1, + const BezierCurveT* border2, + const BezierCurveT* border3); + + __forceinline BezierPatchT(const BSplinePatchT& source) + { + /* compute inner bezier control points */ + matrix[0][0] = computeInnerBezierControlPoint(source.v,1,1); + matrix[0][3] = computeInnerBezierControlPoint(source.v,1,2); + matrix[3][3] = computeInnerBezierControlPoint(source.v,2,2); + matrix[3][0] = computeInnerBezierControlPoint(source.v,2,1); + + /* compute top edge control points */ + matrix[0][1] = computeRightEdgeBezierControlPoint(source.v,1,1); + matrix[0][2] = computeLeftEdgeBezierControlPoint(source.v,1,2); + + /* compute buttom edge control points */ + matrix[3][1] = computeRightEdgeBezierControlPoint(source.v,2,1); + matrix[3][2] = computeLeftEdgeBezierControlPoint(source.v,2,2); + + /* compute left edge control points */ + matrix[1][0] = computeBottomEdgeBezierControlPoint(source.v,1,1); + matrix[2][0] = computeTopEdgeBezierControlPoint(source.v,2,1); + + /* compute right edge control points */ + matrix[1][3] = computeBottomEdgeBezierControlPoint(source.v,1,2); + matrix[2][3] = computeTopEdgeBezierControlPoint(source.v,2,2); + + /* compute corner control points */ + matrix[1][1] = computeCornerBezierControlPoint(source.v,1,1, 1, 1); + matrix[1][2] = computeCornerBezierControlPoint(source.v,1,2, 1,-1); + matrix[2][2] = computeCornerBezierControlPoint(source.v,2,2,-1,-1); + matrix[2][1] = computeCornerBezierControlPoint(source.v,2,1,-1, 1); + } + + static __forceinline Vertex_t bilinear(const Vec4f Bu, const Vertex matrix[4][4], const Vec4f Bv) + { + const Vertex_t M0 = madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))); + const Vertex_t M1 = madd(Bu.x,matrix[1][0],madd(Bu.y,matrix[1][1],madd(Bu.z,matrix[1][2],Bu.w * matrix[1][3]))); + const Vertex_t M2 = madd(Bu.x,matrix[2][0],madd(Bu.y,matrix[2][1],madd(Bu.z,matrix[2][2],Bu.w * matrix[2][3]))); + const Vertex_t M3 = madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3]))); + return madd(Bv.x,M0,madd(Bv.y,M1,madd(Bv.z,M2,Bv.w*M3))); + } + + static __forceinline Vertex_t eval(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vec4f Bu = BezierBasis::eval(uu); + const Vec4f Bv = BezierBasis::eval(vv); + return bilinear(Bu,matrix,Bv); + } + + static __forceinline Vertex_t eval_du(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vec4f Bu = BezierBasis::derivative(uu); + const Vec4f Bv = BezierBasis::eval(vv); + return bilinear(Bu,matrix,Bv); + } + + static __forceinline Vertex_t eval_dv(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vec4f Bu = BezierBasis::eval(uu); + const Vec4f Bv = BezierBasis::derivative(vv); + return bilinear(Bu,matrix,Bv); + } + + static __forceinline Vertex_t eval_dudu(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vec4f Bu = BezierBasis::derivative2(uu); + const Vec4f Bv = BezierBasis::eval(vv); + return bilinear(Bu,matrix,Bv); + } + + static __forceinline Vertex_t eval_dvdv(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vec4f Bu = BezierBasis::eval(uu); + const Vec4f Bv = BezierBasis::derivative2(vv); + return bilinear(Bu,matrix,Bv); + } + + static __forceinline Vertex_t eval_dudv(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vec4f Bu = BezierBasis::derivative(uu); + const Vec4f Bv = BezierBasis::derivative(vv); + return bilinear(Bu,matrix,Bv); + } + + static __forceinline Vertex_t normal(const Vertex matrix[4][4], const float uu, const float vv) + { + const Vertex_t dPdu = eval_du(matrix,uu,vv); + const Vertex_t dPdv = eval_dv(matrix,uu,vv); + return cross(dPdu,dPdv); + } + + __forceinline Vertex_t normal(const float uu, const float vv) + { + const Vertex_t dPdu = eval_du(matrix,uu,vv); + const Vertex_t dPdv = eval_dv(matrix,uu,vv); + return cross(dPdu,dPdv); + } + + __forceinline Vertex_t eval(const float uu, const float vv) const { + return eval(matrix,uu,vv); + } + + __forceinline Vertex_t eval_du(const float uu, const float vv) const { + return eval_du(matrix,uu,vv); + } + + __forceinline Vertex_t eval_dv(const float uu, const float vv) const { + return eval_dv(matrix,uu,vv); + } + + __forceinline Vertex_t eval_dudu(const float uu, const float vv) const { + return eval_dudu(matrix,uu,vv); + } + + __forceinline Vertex_t eval_dvdv(const float uu, const float vv) const { + return eval_dvdv(matrix,uu,vv); + } + + __forceinline Vertex_t eval_dudv(const float uu, const float vv) const { + return eval_dudv(matrix,uu,vv); + } + + __forceinline void eval(const float u, const float v, Vertex* P, Vertex* dPdu, Vertex* dPdv, Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv, const float dscale = 1.0f) const + { + if (P) { + *P = eval(u,v); + } + if (dPdu) { + assert(dPdu); *dPdu = eval_du(u,v)*dscale; + assert(dPdv); *dPdv = eval_dv(u,v)*dscale; + } + if (ddPdudu) { + assert(ddPdudu); *ddPdudu = eval_dudu(u,v)*sqr(dscale); + assert(ddPdvdv); *ddPdvdv = eval_dvdv(u,v)*sqr(dscale); + assert(ddPdudv); *ddPdudv = eval_dudv(u,v)*sqr(dscale); + } + } + + template + __forceinline vfloat eval(const size_t i, const vfloat& uu, const vfloat& vv, const Vec4& u_n, const Vec4& v_n) const + { + const vfloat curve0_x = v_n[0] * vfloat(matrix[0][0][i]) + v_n[1] * vfloat(matrix[1][0][i]) + v_n[2] * vfloat(matrix[2][0][i]) + v_n[3] * vfloat(matrix[3][0][i]); + const vfloat curve1_x = v_n[0] * vfloat(matrix[0][1][i]) + v_n[1] * vfloat(matrix[1][1][i]) + v_n[2] * vfloat(matrix[2][1][i]) + v_n[3] * vfloat(matrix[3][1][i]); + const vfloat curve2_x = v_n[0] * vfloat(matrix[0][2][i]) + v_n[1] * vfloat(matrix[1][2][i]) + v_n[2] * vfloat(matrix[2][2][i]) + v_n[3] * vfloat(matrix[3][2][i]); + const vfloat curve3_x = v_n[0] * vfloat(matrix[0][3][i]) + v_n[1] * vfloat(matrix[1][3][i]) + v_n[2] * vfloat(matrix[2][3][i]) + v_n[3] * vfloat(matrix[3][3][i]); + return u_n[0] * curve0_x + u_n[1] * curve1_x + u_n[2] * curve2_x + u_n[3] * curve3_x; + } + + template + __forceinline void eval(const vbool& valid, const vfloat& uu, const vfloat& vv, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, + const float dscale, const size_t dstride, const size_t N) const + { + if (P) { + const Vec4 u_n = BezierBasis::eval(uu); + const Vec4 v_n = BezierBasis::eval(vv); + for (size_t i=0; i u_n = BezierBasis::derivative(uu); + const Vec4 v_n = BezierBasis::eval(vv); + for (size_t i=0; i u_n = BezierBasis::eval(uu); + const Vec4 v_n = BezierBasis::derivative(vv); + for (size_t i=0; i u_n = BezierBasis::derivative2(uu); + const Vec4 v_n = BezierBasis::eval(vv); + for (size_t i=0; i u_n = BezierBasis::eval(uu); + const Vec4 v_n = BezierBasis::derivative2(vv); + for (size_t i=0; i u_n = BezierBasis::derivative(uu); + const Vec4 v_n = BezierBasis::derivative(vv); + for (size_t i=0; i + static __forceinline Vec3 eval(const Vertex matrix[4][4], const T& uu, const T& vv) + { + const T one_minus_uu = 1.0f - uu; + const T one_minus_vv = 1.0f - vv; + + const T B0_u = one_minus_uu * one_minus_uu * one_minus_uu; + const T B0_v = one_minus_vv * one_minus_vv * one_minus_vv; + const T B1_u = 3.0f * (one_minus_uu * uu * one_minus_uu); + const T B1_v = 3.0f * (one_minus_vv * vv * one_minus_vv); + const T B2_u = 3.0f * (uu * one_minus_uu * uu); + const T B2_v = 3.0f * (vv * one_minus_vv * vv); + const T B3_u = uu * uu * uu; + const T B3_v = vv * vv * vv; + + const T x = + madd(B0_v,madd(B0_u,matrix[0][0].x,madd(B1_u,matrix[0][1].x,madd(B2_u,matrix[0][2].x,B3_u*matrix[0][3].x))), + madd(B1_v,madd(B0_u,matrix[1][0].x,madd(B1_u,matrix[1][1].x,madd(B2_u,matrix[1][2].x,B3_u*matrix[1][3].x))), + madd(B2_v,madd(B0_u,matrix[2][0].x,madd(B1_u,matrix[2][1].x,madd(B2_u,matrix[2][2].x,B3_u*matrix[2][3].x))), + B3_v*madd(B0_u,matrix[3][0].x,madd(B1_u,matrix[3][1].x,madd(B2_u,matrix[3][2].x,B3_u*matrix[3][3].x)))))); + + const T y = + madd(B0_v,madd(B0_u,matrix[0][0].y,madd(B1_u,matrix[0][1].y,madd(B2_u,matrix[0][2].y,B3_u*matrix[0][3].y))), + madd(B1_v,madd(B0_u,matrix[1][0].y,madd(B1_u,matrix[1][1].y,madd(B2_u,matrix[1][2].y,B3_u*matrix[1][3].y))), + madd(B2_v,madd(B0_u,matrix[2][0].y,madd(B1_u,matrix[2][1].y,madd(B2_u,matrix[2][2].y,B3_u*matrix[2][3].y))), + B3_v*madd(B0_u,matrix[3][0].y,madd(B1_u,matrix[3][1].y,madd(B2_u,matrix[3][2].y,B3_u*matrix[3][3].y)))))); + + const T z = + madd(B0_v,madd(B0_u,matrix[0][0].z,madd(B1_u,matrix[0][1].z,madd(B2_u,matrix[0][2].z,B3_u*matrix[0][3].z))), + madd(B1_v,madd(B0_u,matrix[1][0].z,madd(B1_u,matrix[1][1].z,madd(B2_u,matrix[1][2].z,B3_u*matrix[1][3].z))), + madd(B2_v,madd(B0_u,matrix[2][0].z,madd(B1_u,matrix[2][1].z,madd(B2_u,matrix[2][2].z,B3_u*matrix[2][3].z))), + B3_v*madd(B0_u,matrix[3][0].z,madd(B1_u,matrix[3][1].z,madd(B2_u,matrix[3][2].z,B3_u*matrix[3][3].z)))))); + + return Vec3(x,y,z); + } + + template + __forceinline Vec3 eval(const vfloat& uu, const vfloat& vv) const { + return eval(matrix,uu,vv); + } + + template + static __forceinline Vec3 normal(const Vertex matrix[4][4], const T& uu, const T& vv) + { + + const Vec3 matrix_00 = Vec3(matrix[0][0].x,matrix[0][0].y,matrix[0][0].z); + const Vec3 matrix_01 = Vec3(matrix[0][1].x,matrix[0][1].y,matrix[0][1].z); + const Vec3 matrix_02 = Vec3(matrix[0][2].x,matrix[0][2].y,matrix[0][2].z); + const Vec3 matrix_03 = Vec3(matrix[0][3].x,matrix[0][3].y,matrix[0][3].z); + + const Vec3 matrix_10 = Vec3(matrix[1][0].x,matrix[1][0].y,matrix[1][0].z); + const Vec3 matrix_11 = Vec3(matrix[1][1].x,matrix[1][1].y,matrix[1][1].z); + const Vec3 matrix_12 = Vec3(matrix[1][2].x,matrix[1][2].y,matrix[1][2].z); + const Vec3 matrix_13 = Vec3(matrix[1][3].x,matrix[1][3].y,matrix[1][3].z); + + const Vec3 matrix_20 = Vec3(matrix[2][0].x,matrix[2][0].y,matrix[2][0].z); + const Vec3 matrix_21 = Vec3(matrix[2][1].x,matrix[2][1].y,matrix[2][1].z); + const Vec3 matrix_22 = Vec3(matrix[2][2].x,matrix[2][2].y,matrix[2][2].z); + const Vec3 matrix_23 = Vec3(matrix[2][3].x,matrix[2][3].y,matrix[2][3].z); + + const Vec3 matrix_30 = Vec3(matrix[3][0].x,matrix[3][0].y,matrix[3][0].z); + const Vec3 matrix_31 = Vec3(matrix[3][1].x,matrix[3][1].y,matrix[3][1].z); + const Vec3 matrix_32 = Vec3(matrix[3][2].x,matrix[3][2].y,matrix[3][2].z); + const Vec3 matrix_33 = Vec3(matrix[3][3].x,matrix[3][3].y,matrix[3][3].z); + + /* tangentU */ + const Vec3 col0 = deCasteljau(vv, matrix_00, matrix_10, matrix_20, matrix_30); + const Vec3 col1 = deCasteljau(vv, matrix_01, matrix_11, matrix_21, matrix_31); + const Vec3 col2 = deCasteljau(vv, matrix_02, matrix_12, matrix_22, matrix_32); + const Vec3 col3 = deCasteljau(vv, matrix_03, matrix_13, matrix_23, matrix_33); + + const Vec3 tangentU = deCasteljau_tangent(uu, col0, col1, col2, col3); + + /* tangentV */ + const Vec3 row0 = deCasteljau(uu, matrix_00, matrix_01, matrix_02, matrix_03); + const Vec3 row1 = deCasteljau(uu, matrix_10, matrix_11, matrix_12, matrix_13); + const Vec3 row2 = deCasteljau(uu, matrix_20, matrix_21, matrix_22, matrix_23); + const Vec3 row3 = deCasteljau(uu, matrix_30, matrix_31, matrix_32, matrix_33); + + const Vec3 tangentV = deCasteljau_tangent(vv, row0, row1, row2, row3); + + /* normal = tangentU x tangentV */ + const Vec3 n = cross(tangentU,tangentV); + return n; + } + + template + __forceinline Vec3 normal(const vfloat& uu, const vfloat& vv) const { + return normal(matrix,uu,vv); + } + }; + + typedef BezierPatchT BezierPatch3fa; +} diff --git a/thirdparty/embree/kernels/subdiv/bilinear_patch.h b/thirdparty/embree/kernels/subdiv/bilinear_patch.h new file mode 100644 index 000000000000..35748754bd02 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/bilinear_patch.h @@ -0,0 +1,191 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_patch.h" +#include "bezier_curve.h" + +namespace embree +{ + template + class __aligned(64) BilinearPatchT + { + typedef CatmullClark1RingT CatmullClarkRing; + typedef CatmullClarkPatchT CatmullClarkPatch; + + public: + Vertex v[4]; + + public: + + __forceinline BilinearPatchT () {} + + __forceinline BilinearPatchT (const HalfEdge* edge, const BufferView& vertices) { + init(edge,vertices.getPtr(),vertices.getStride()); + } + + __forceinline BilinearPatchT (const HalfEdge* edge, const char* vertices, size_t stride) { + init(edge,vertices,stride); + } + + __forceinline void init (const HalfEdge* edge, const char* vertices, size_t stride) + { + v[0] = Vertex::loadu(vertices+edge->getStartVertexIndex()*stride); edge = edge->next(); + v[1] = Vertex::loadu(vertices+edge->getStartVertexIndex()*stride); edge = edge->next(); + v[2] = Vertex::loadu(vertices+edge->getStartVertexIndex()*stride); edge = edge->next(); + v[3] = Vertex::loadu(vertices+edge->getStartVertexIndex()*stride); edge = edge->next(); + } + + __forceinline BilinearPatchT (const CatmullClarkPatch& patch) + { + v[0] = patch.ring[0].getLimitVertex(); + v[1] = patch.ring[1].getLimitVertex(); + v[2] = patch.ring[2].getLimitVertex(); + v[3] = patch.ring[3].getLimitVertex(); + } + + __forceinline BBox bounds() const + { + + BBox bounds (v[0]); + bounds.extend(v[1]); + bounds.extend(v[2]); + bounds.extend(v[3]); + return bounds; + } + + __forceinline Vertex eval(const float uu, const float vv) const { + return lerp(lerp(v[0],v[1],uu),lerp(v[3],v[2],uu),vv); + } + + __forceinline Vertex eval_du(const float uu, const float vv) const { + return lerp(v[1]-v[0],v[2]-v[3],vv); + } + + __forceinline Vertex eval_dv(const float uu, const float vv) const { + return lerp(v[3]-v[0],v[2]-v[1],uu); + } + + __forceinline Vertex eval_dudu(const float uu, const float vv) const { + return Vertex(zero); + } + + __forceinline Vertex eval_dvdv(const float uu, const float vv) const { + return Vertex(zero); + } + + __forceinline Vertex eval_dudv(const float uu, const float vv) const { + return (v[2]-v[3]) - (v[1]-v[0]); + } + + __forceinline Vertex normal(const float uu, const float vv) const { + return cross(eval_du(uu,vv),eval_dv(uu,vv)); + } + + __forceinline void eval(const float u, const float v, + Vertex* P, Vertex* dPdu, Vertex* dPdv, Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv, + const float dscale = 1.0f) const + { + if (P) { + *P = eval(u,v); + } + if (dPdu) { + assert(dPdu); *dPdu = eval_du(u,v)*dscale; + assert(dPdv); *dPdv = eval_dv(u,v)*dscale; + } + if (ddPdudu) { + assert(ddPdudu); *ddPdudu = eval_dudu(u,v)*sqr(dscale); + assert(ddPdvdv); *ddPdvdv = eval_dvdv(u,v)*sqr(dscale); + assert(ddPdudv); *ddPdudv = eval_dudv(u,v)*sqr(dscale); + } + } + + template + __forceinline Vec3 eval(const vfloat& uu, const vfloat& vv) const + { + const vfloat x = lerp(lerp(v[0].x,v[1].x,uu),lerp(v[3].x,v[2].x,uu),vv); + const vfloat y = lerp(lerp(v[0].y,v[1].y,uu),lerp(v[3].y,v[2].y,uu),vv); + const vfloat z = lerp(lerp(v[0].z,v[1].z,uu),lerp(v[3].z,v[2].z,uu),vv); + return Vec3(x,y,z); + } + + template + __forceinline Vec3 eval_du(const vfloat& uu, const vfloat& vv) const + { + const vfloat x = lerp(v[1].x-v[0].x,v[2].x-v[3].x,vv); + const vfloat y = lerp(v[1].y-v[0].y,v[2].y-v[3].y,vv); + const vfloat z = lerp(v[1].z-v[0].z,v[2].z-v[3].z,vv); + return Vec3(x,y,z); + } + + template + __forceinline Vec3 eval_dv(const vfloat& uu, const vfloat& vv) const + { + const vfloat x = lerp(v[3].x-v[0].x,v[2].x-v[1].x,uu); + const vfloat y = lerp(v[3].y-v[0].y,v[2].y-v[1].y,uu); + const vfloat z = lerp(v[3].z-v[0].z,v[2].z-v[1].z,uu); + return Vec3(x,y,z); + } + + template + __forceinline Vec3 normal(const vfloat& uu, const vfloat& vv) const { + return cross(eval_du(uu,vv),eval_dv(uu,vv)); + } + + template + __forceinline vfloat eval(const size_t i, const vfloat& uu, const vfloat& vv) const { + return lerp(lerp(v[0][i],v[1][i],uu),lerp(v[3][i],v[2][i],uu),vv); + } + + template + __forceinline vfloat eval_du(const size_t i, const vfloat& uu, const vfloat& vv) const { + return lerp(v[1][i]-v[0][i],v[2][i]-v[3][i],vv); + } + + template + __forceinline vfloat eval_dv(const size_t i, const vfloat& uu, const vfloat& vv) const { + return lerp(v[3][i]-v[0][i],v[2][i]-v[1][i],uu); + } + + template + __forceinline vfloat eval_dudu(const size_t i, const vfloat& uu, const vfloat& vv) const { + return vfloat(zero); + } + + template + __forceinline vfloat eval_dvdv(const size_t i, const vfloat& uu, const vfloat& vv) const { + return vfloat(zero); + } + + template + __forceinline vfloat eval_dudv(const size_t i, const vfloat& uu, const vfloat& vv) const { + return (v[2][i]-v[3][i]) - (v[1][i]-v[0][i]); + } + + template + __forceinline void eval(const vbool& valid, const vfloat& uu, const vfloat& vv, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, + const float dscale, const size_t dstride, const size_t N) const + { + if (P) { + for (size_t i=0; i BilinearPatch3fa; +} diff --git a/thirdparty/embree/kernels/subdiv/bspline_curve.h b/thirdparty/embree/kernels/subdiv/bspline_curve.h new file mode 100644 index 000000000000..a3256673282f --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/bspline_curve.h @@ -0,0 +1,319 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "bezier_curve.h" + +namespace embree +{ + class BSplineBasis + { + public: + + template + static __forceinline Vec4 eval(const T& u) + { + const T t = u; + const T s = T(1.0f) - u; + const T n0 = s*s*s; + const T n1 = (4.0f*(s*s*s)+(t*t*t)) + (12.0f*((s*t)*s) + 6.0f*((t*s)*t)); + const T n2 = (4.0f*(t*t*t)+(s*s*s)) + (12.0f*((t*s)*t) + 6.0f*((s*t)*s)); + const T n3 = t*t*t; + return T(1.0f/6.0f)*Vec4(n0,n1,n2,n3); + } + + template + static __forceinline Vec4 derivative(const T& u) + { + const T t = u; + const T s = 1.0f - u; + const T n0 = -s*s; + const T n1 = -t*t - 4.0f*(t*s); + const T n2 = s*s + 4.0f*(s*t); + const T n3 = t*t; + return T(0.5f)*Vec4(n0,n1,n2,n3); + } + + template + static __forceinline Vec4 derivative2(const T& u) + { + const T t = u; + const T s = 1.0f - u; + const T n0 = s; + const T n1 = t - 2.0f*s; + const T n2 = s - 2.0f*t; + const T n3 = t; + return Vec4(n0,n1,n2,n3); + } + }; + + struct PrecomputedBSplineBasis + { + enum { N = 16 }; + public: + PrecomputedBSplineBasis() {} + PrecomputedBSplineBasis(int shift); + + /* basis for bspline evaluation */ + public: + float c0[N+1][N+1]; + float c1[N+1][N+1]; + float c2[N+1][N+1]; + float c3[N+1][N+1]; + + /* basis for bspline derivative evaluation */ + public: + float d0[N+1][N+1]; + float d1[N+1][N+1]; + float d2[N+1][N+1]; + float d3[N+1][N+1]; + }; + extern PrecomputedBSplineBasis bspline_basis0; + extern PrecomputedBSplineBasis bspline_basis1; + + template + struct BSplineCurveT + { + Vertex v0,v1,v2,v3; + + __forceinline BSplineCurveT() {} + + __forceinline BSplineCurveT(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3) + : v0(v0), v1(v1), v2(v2), v3(v3) {} + + __forceinline Vertex begin() const { + return madd(1.0f/6.0f,v0,madd(2.0f/3.0f,v1,1.0f/6.0f*v2)); + } + + __forceinline Vertex end() const { + return madd(1.0f/6.0f,v1,madd(2.0f/3.0f,v2,1.0f/6.0f*v3)); + } + + __forceinline Vertex center() const { + return 0.25f*(v0+v1+v2+v3); + } + + __forceinline BBox bounds() const { + return merge(BBox(v0),BBox(v1),BBox(v2),BBox(v3)); + } + + __forceinline friend BSplineCurveT operator -( const BSplineCurveT& a, const Vertex& b ) { + return BSplineCurveT(a.v0-b,a.v1-b,a.v2-b,a.v3-b); + } + + __forceinline BSplineCurveT xfm_pr(const LinearSpace3fa& space, const Vec3fa& p) const + { + const Vec3ff q0(xfmVector(space,(Vec3fa)v0-p), v0.w); + const Vec3ff q1(xfmVector(space,(Vec3fa)v1-p), v1.w); + const Vec3ff q2(xfmVector(space,(Vec3fa)v2-p), v2.w); + const Vec3ff q3(xfmVector(space,(Vec3fa)v3-p), v3.w); + return BSplineCurveT(q0,q1,q2,q3); + } + + __forceinline Vertex eval(const float t) const + { + const Vec4 b = BSplineBasis::eval(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline Vertex eval_du(const float t) const + { + const Vec4 b = BSplineBasis::derivative(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline Vertex eval_dudu(const float t) const + { + const Vec4 b = BSplineBasis::derivative2(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline void eval(const float t, Vertex& p, Vertex& dp, Vertex& ddp) const + { + p = eval(t); + dp = eval_du(t); + ddp = eval_dudu(t); + } + + template + __forceinline Vec4vf veval(const vfloat& t) const + { + const Vec4vf b = BSplineBasis::eval(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf veval_du(const vfloat& t) const + { + const Vec4vf b = BSplineBasis::derivative(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf veval_dudu(const vfloat& t) const + { + const Vec4vf b = BSplineBasis::derivative2(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline void veval(const vfloat& t, Vec4vf& p, Vec4vf& dp) const + { + p = veval(t); + dp = veval_du(t); + } + + template + __forceinline Vec4vf eval0(const int ofs, const int size) const + { + assert(size <= PrecomputedBSplineBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bspline_basis0.c0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&bspline_basis0.c1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&bspline_basis0.c2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&bspline_basis0.c3[size][ofs]) * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf eval1(const int ofs, const int size) const + { + assert(size <= PrecomputedBSplineBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bspline_basis1.c0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&bspline_basis1.c1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&bspline_basis1.c2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&bspline_basis1.c3[size][ofs]) * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf derivative0(const int ofs, const int size) const + { + assert(size <= PrecomputedBSplineBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bspline_basis0.d0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&bspline_basis0.d1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&bspline_basis0.d2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&bspline_basis0.d3[size][ofs]) * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf derivative1(const int ofs, const int size) const + { + assert(size <= PrecomputedBSplineBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&bspline_basis1.d0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&bspline_basis1.d1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&bspline_basis1.d2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&bspline_basis1.d3[size][ofs]) * Vec4vf(v3)))); + } + + /* calculates bounds of bspline curve geometry */ + __forceinline BBox3fa accurateRoundBounds() const + { + const int N = 7; + const float scale = 1.0f/(3.0f*(N-1)); + Vec4vfx pl(pos_inf), pu(neg_inf); + for (int i=0; i<=N; i+=VSIZEX) + { + vintx vi = vintx(i)+vintx(step); + vboolx valid = vi <= vintx(N); + const Vec4vfx p = eval0(i,N); + const Vec4vfx dp = derivative0(i,N); + const Vec4vfx pm = p-Vec4vfx(scale)*select(vi!=vintx(0),dp,Vec4vfx(zero)); + const Vec4vfx pp = p+Vec4vfx(scale)*select(vi!=vintx(N),dp,Vec4vfx(zero)); + pl = select(valid,min(pl,p,pm,pp),pl); // FIXME: use masked min + pu = select(valid,max(pu,p,pm,pp),pu); // FIXME: use masked min + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + const float r_min = reduce_min(pl.w); + const float r_max = reduce_max(pu.w); + const Vec3fa upper_r = Vec3fa(max(abs(r_min),abs(r_max))); + return enlarge(BBox3fa(lower,upper),upper_r); + } + + /* calculates bounds when tessellated into N line segments */ + __forceinline BBox3fa accurateFlatBounds(int N) const + { + if (likely(N == 4)) + { + const Vec4vf4 pi = eval0<4>(0,4); + const Vec3fa lower(reduce_min(pi.x),reduce_min(pi.y),reduce_min(pi.z)); + const Vec3fa upper(reduce_max(pi.x),reduce_max(pi.y),reduce_max(pi.z)); + const Vec3fa upper_r = Vec3fa(reduce_max(abs(pi.w))); + const Vec3ff pe = end(); + return enlarge(BBox3fa(min(lower,pe),max(upper,pe)),max(upper_r,Vec3fa(abs(pe.w)))); + } + else + { + Vec3vfx pl(pos_inf), pu(neg_inf); vfloatx ru(0.0f); + for (int i=0; i<=N; i+=VSIZEX) + { + vboolx valid = vintx(i)+vintx(step) <= vintx(N); + const Vec4vfx pi = eval0(i,N); + + pl.x = select(valid,min(pl.x,pi.x),pl.x); // FIXME: use masked min + pl.y = select(valid,min(pl.y,pi.y),pl.y); + pl.z = select(valid,min(pl.z,pi.z),pl.z); + + pu.x = select(valid,max(pu.x,pi.x),pu.x); // FIXME: use masked min + pu.y = select(valid,max(pu.y,pi.y),pu.y); + pu.z = select(valid,max(pu.z,pi.z),pu.z); + + ru = select(valid,max(ru,abs(pi.w)),ru); + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + const Vec3fa upper_r(reduce_max(ru)); + return enlarge(BBox3fa(lower,upper),upper_r); + } + } + + friend __forceinline embree_ostream operator<<(embree_ostream cout, const BSplineCurveT& curve) { + return cout << "BSplineCurve { v0 = " << curve.v0 << ", v1 = " << curve.v1 << ", v2 = " << curve.v2 << ", v3 = " << curve.v3 << " }"; + } + }; + + template + __forceinline void convert(const BezierCurveT& icurve, BezierCurveT& ocurve) { + ocurve = icurve; + } + + template + __forceinline void convert(const BSplineCurveT& icurve, BSplineCurveT& ocurve) { + ocurve = icurve; + } + + template + __forceinline void convert(const BezierCurveT& icurve, BSplineCurveT& ocurve) + { + const Vertex v0 = madd(6.0f,icurve.v0,madd(-7.0f,icurve.v1,2.0f*icurve.v2)); + const Vertex v1 = msub(2.0f,icurve.v1,icurve.v2); + const Vertex v2 = msub(2.0f,icurve.v2,icurve.v1); + const Vertex v3 = madd(2.0f,icurve.v1,madd(-7.0f,icurve.v2,6.0f*icurve.v3)); + ocurve = BSplineCurveT(v0,v1,v2,v3); + } + + template + __forceinline void convert(const BSplineCurveT& icurve, BezierCurveT& ocurve) + { + const Vertex v0 = madd(1.0f/6.0f,icurve.v0,madd(2.0f/3.0f,icurve.v1,1.0f/6.0f*icurve.v2)); + const Vertex v1 = madd(2.0f/3.0f,icurve.v1,1.0f/3.0f*icurve.v2); + const Vertex v2 = madd(1.0f/3.0f,icurve.v1,2.0f/3.0f*icurve.v2); + const Vertex v3 = madd(1.0f/6.0f,icurve.v1,madd(2.0f/3.0f,icurve.v2,1.0f/6.0f*icurve.v3)); + ocurve = BezierCurveT(v0,v1,v2,v3); + } + + __forceinline BSplineCurveT enlargeRadiusToMinWidth(const IntersectContext* context, const CurveGeometry* geom, const Vec3fa& ray_org, const BSplineCurveT& curve) + { + return BSplineCurveT(enlargeRadiusToMinWidth(context,geom,ray_org,curve.v0), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v1), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v2), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v3)); + } + + typedef BSplineCurveT BSplineCurve3fa; +} + diff --git a/thirdparty/embree/kernels/subdiv/bspline_patch.h b/thirdparty/embree/kernels/subdiv/bspline_patch.h new file mode 100644 index 000000000000..9769bc17bdfe --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/bspline_patch.h @@ -0,0 +1,449 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_patch.h" +#include "bspline_curve.h" + +namespace embree +{ + template + class __aligned(64) BSplinePatchT + { + typedef CatmullClark1RingT CatmullClarkRing; + typedef CatmullClarkPatchT CatmullClarkPatch; + + public: + + __forceinline BSplinePatchT () {} + + __forceinline BSplinePatchT (const CatmullClarkPatch& patch) { + init(patch); + } + + __forceinline BSplinePatchT(const CatmullClarkPatch& patch, + const BezierCurveT* border0, + const BezierCurveT* border1, + const BezierCurveT* border2, + const BezierCurveT* border3) + { + init(patch); + } + + __forceinline BSplinePatchT (const HalfEdge* edge, const char* vertices, size_t stride) { + init(edge,vertices,stride); + } + + __forceinline Vertex hard_corner(const Vertex& v01, const Vertex& v02, + const Vertex& v10, const Vertex& v11, const Vertex& v12, + const Vertex& v20, const Vertex& v21, const Vertex& v22) + { + return 4.0f*v11 - 2.0f*(v12+v21) + v22; + } + + __forceinline Vertex soft_convex_corner( const Vertex& v01, const Vertex& v02, + const Vertex& v10, const Vertex& v11, const Vertex& v12, + const Vertex& v20, const Vertex& v21, const Vertex& v22) + { + return -8.0f*v11 + 4.0f*(v12+v21) + v22; + } + + __forceinline Vertex convex_corner(const float vertex_crease_weight, + const Vertex& v01, const Vertex& v02, + const Vertex& v10, const Vertex& v11, const Vertex& v12, + const Vertex& v20, const Vertex& v21, const Vertex& v22) + { + if (std::isinf(vertex_crease_weight)) return hard_corner(v01,v02,v10,v11,v12,v20,v21,v22); + else return soft_convex_corner(v01,v02,v10,v11,v12,v20,v21,v22); + } + + __forceinline Vertex load(const HalfEdge* edge, const char* vertices, size_t stride) { + return Vertex_t::loadu(vertices+edge->getStartVertexIndex()*stride); + } + + __forceinline void init_border(const CatmullClarkRing& edge0, + Vertex& v01, Vertex& v02, + const Vertex& v11, const Vertex& v12, + const Vertex& v21, const Vertex& v22) + { + if (likely(edge0.has_opposite_back(0))) + { + v01 = edge0.back(2); + v02 = edge0.back(1); + } else { + v01 = 2.0f*v11-v21; + v02 = 2.0f*v12-v22; + } + } + + __forceinline void init_corner(const CatmullClarkRing& edge0, + Vertex& v00, const Vertex& v01, const Vertex& v02, + const Vertex& v10, const Vertex& v11, const Vertex& v12, + const Vertex& v20, const Vertex& v21, const Vertex& v22) + { + const bool MAYBE_UNUSED has_back1 = edge0.has_opposite_back(1); + const bool has_back0 = edge0.has_opposite_back(0); + const bool has_front1 = edge0.has_opposite_front(1); + const bool MAYBE_UNUSED has_front2 = edge0.has_opposite_front(2); + + if (likely(has_back0)) { + if (likely(has_front1)) { assert(has_back1 && has_front2); v00 = edge0.back(3); } + else { assert(!has_back1); v00 = 2.0f*v01-v02; } + } + else { + if (likely(has_front1)) { assert(!has_front2); v00 = 2.0f*v10-v20; } + else v00 = convex_corner(edge0.vertex_crease_weight,v01,v02,v10,v11,v12,v20,v21,v22); + } + } + + void init(const CatmullClarkPatch& patch) + { + /* fill inner vertices */ + const Vertex v11 = v[1][1] = patch.ring[0].vtx; + const Vertex v12 = v[1][2] = patch.ring[1].vtx; + const Vertex v22 = v[2][2] = patch.ring[2].vtx; + const Vertex v21 = v[2][1] = patch.ring[3].vtx; + + /* fill border vertices */ + init_border(patch.ring[0],v[0][1],v[0][2],v11,v12,v21,v22); + init_border(patch.ring[1],v[1][3],v[2][3],v12,v22,v11,v21); + init_border(patch.ring[2],v[3][2],v[3][1],v22,v21,v12,v11); + init_border(patch.ring[3],v[2][0],v[1][0],v21,v11,v22,v12); + + /* fill corner vertices */ + init_corner(patch.ring[0],v[0][0],v[0][1],v[0][2],v[1][0],v11,v12,v[2][0],v21,v22); + init_corner(patch.ring[1],v[0][3],v[1][3],v[2][3],v[0][2],v12,v22,v[0][1],v11,v21); + init_corner(patch.ring[2],v[3][3],v[3][2],v[3][1],v[2][3],v22,v21,v[1][3],v12,v11); + init_corner(patch.ring[3],v[3][0],v[2][0],v[1][0],v[3][1],v21,v11,v[3][2],v22,v12); + } + + void init_border(const HalfEdge* edge0, const char* vertices, size_t stride, + Vertex& v01, Vertex& v02, + const Vertex& v11, const Vertex& v12, + const Vertex& v21, const Vertex& v22) + { + if (likely(edge0->hasOpposite())) + { + const HalfEdge* e = edge0->opposite()->next()->next(); + v01 = load(e,vertices,stride); + v02 = load(e->next(),vertices,stride); + } else { + v01 = 2.0f*v11-v21; + v02 = 2.0f*v12-v22; + } + } + + void init_corner(const HalfEdge* edge0, const char* vertices, size_t stride, + Vertex& v00, const Vertex& v01, const Vertex& v02, + const Vertex& v10, const Vertex& v11, const Vertex& v12, + const Vertex& v20, const Vertex& v21, const Vertex& v22) + { + const bool has_back0 = edge0->hasOpposite(); + const bool has_front1 = edge0->prev()->hasOpposite(); + + if (likely(has_back0)) + { + const HalfEdge* e = edge0->opposite()->next(); + if (likely(has_front1)) + { + assert(e->hasOpposite()); + assert(edge0->prev()->opposite()->prev()->hasOpposite()); + v00 = load(e->opposite()->prev(),vertices,stride); + } + else { + assert(!e->hasOpposite()); + v00 = 2.0f*v01-v02; + } + } + else + { + if (likely(has_front1)) { + assert(!edge0->prev()->opposite()->prev()->hasOpposite()); + v00 = 2.0f*v10-v20; + } + else { + assert(edge0->vertex_crease_weight == 0.0f || std::isinf(edge0->vertex_crease_weight)); + v00 = convex_corner(edge0->vertex_crease_weight,v01,v02,v10,v11,v12,v20,v21,v22); + } + } + } + + void init(const HalfEdge* edge0, const char* vertices, size_t stride) + { + assert( edge0->isRegularFace() ); + + /* fill inner vertices */ + const Vertex v11 = v[1][1] = load(edge0,vertices,stride); const HalfEdge* edge1 = edge0->next(); + const Vertex v12 = v[1][2] = load(edge1,vertices,stride); const HalfEdge* edge2 = edge1->next(); + const Vertex v22 = v[2][2] = load(edge2,vertices,stride); const HalfEdge* edge3 = edge2->next(); + const Vertex v21 = v[2][1] = load(edge3,vertices,stride); assert(edge0 == edge3->next()); + + /* fill border vertices */ + init_border(edge0,vertices,stride,v[0][1],v[0][2],v11,v12,v21,v22); + init_border(edge1,vertices,stride,v[1][3],v[2][3],v12,v22,v11,v21); + init_border(edge2,vertices,stride,v[3][2],v[3][1],v22,v21,v12,v11); + init_border(edge3,vertices,stride,v[2][0],v[1][0],v21,v11,v22,v12); + + /* fill corner vertices */ + init_corner(edge0,vertices,stride,v[0][0],v[0][1],v[0][2],v[1][0],v11,v12,v[2][0],v21,v22); + init_corner(edge1,vertices,stride,v[0][3],v[1][3],v[2][3],v[0][2],v12,v22,v[0][1],v11,v21); + init_corner(edge2,vertices,stride,v[3][3],v[3][2],v[3][1],v[2][3],v22,v21,v[1][3],v12,v11); + init_corner(edge3,vertices,stride,v[3][0],v[2][0],v[1][0],v[3][1],v21,v11,v[3][2],v22,v12); + } + + __forceinline BBox bounds() const + { + const Vertex* const cv = &v[0][0]; + BBox bounds (cv[0]); + for (size_t i=1; i<16 ; i++) + bounds.extend( cv[i] ); + return bounds; + } + + __forceinline Vertex eval(const float uu, const float vv) const + { + const Vec4f v_n = BSplineBasis::eval(vv); + const Vertex_t curve0 = madd(v_n[0],v[0][0],madd(v_n[1],v[1][0],madd(v_n[2],v[2][0],v_n[3] * v[3][0]))); + const Vertex_t curve1 = madd(v_n[0],v[0][1],madd(v_n[1],v[1][1],madd(v_n[2],v[2][1],v_n[3] * v[3][1]))); + const Vertex_t curve2 = madd(v_n[0],v[0][2],madd(v_n[1],v[1][2],madd(v_n[2],v[2][2],v_n[3] * v[3][2]))); + const Vertex_t curve3 = madd(v_n[0],v[0][3],madd(v_n[1],v[1][3],madd(v_n[2],v[2][3],v_n[3] * v[3][3]))); + + const Vec4f u_n = BSplineBasis::eval(uu); + return madd(u_n[0],curve0,madd(u_n[1],curve1,madd(u_n[2],curve2,u_n[3] * curve3))); + } + + __forceinline Vertex eval_du(const float uu, const float vv) const + { + const Vec4f v_n = BSplineBasis::eval(vv); + const Vertex_t curve0 = madd(v_n[0],v[0][0],madd(v_n[1],v[1][0],madd(v_n[2],v[2][0],v_n[3] * v[3][0]))); + const Vertex_t curve1 = madd(v_n[0],v[0][1],madd(v_n[1],v[1][1],madd(v_n[2],v[2][1],v_n[3] * v[3][1]))); + const Vertex_t curve2 = madd(v_n[0],v[0][2],madd(v_n[1],v[1][2],madd(v_n[2],v[2][2],v_n[3] * v[3][2]))); + const Vertex_t curve3 = madd(v_n[0],v[0][3],madd(v_n[1],v[1][3],madd(v_n[2],v[2][3],v_n[3] * v[3][3]))); + + const Vec4f u_n = BSplineBasis::derivative(uu); + return madd(u_n[0],curve0,madd(u_n[1],curve1,madd(u_n[2],curve2,u_n[3] * curve3))); + } + + __forceinline Vertex eval_dv(const float uu, const float vv) const + { + const Vec4f v_n = BSplineBasis::derivative(vv); + const Vertex_t curve0 = madd(v_n[0],v[0][0],madd(v_n[1],v[1][0],madd(v_n[2],v[2][0],v_n[3] * v[3][0]))); + const Vertex_t curve1 = madd(v_n[0],v[0][1],madd(v_n[1],v[1][1],madd(v_n[2],v[2][1],v_n[3] * v[3][1]))); + const Vertex_t curve2 = madd(v_n[0],v[0][2],madd(v_n[1],v[1][2],madd(v_n[2],v[2][2],v_n[3] * v[3][2]))); + const Vertex_t curve3 = madd(v_n[0],v[0][3],madd(v_n[1],v[1][3],madd(v_n[2],v[2][3],v_n[3] * v[3][3]))); + + const Vec4f u_n = BSplineBasis::eval(uu); + return madd(u_n[0],curve0,madd(u_n[1],curve1,madd(u_n[2],curve2,u_n[3] * curve3))); + } + + __forceinline Vertex eval_dudu(const float uu, const float vv) const + { + const Vec4f v_n = BSplineBasis::eval(vv); + const Vertex_t curve0 = madd(v_n[0],v[0][0],madd(v_n[1],v[1][0],madd(v_n[2],v[2][0],v_n[3] * v[3][0]))); + const Vertex_t curve1 = madd(v_n[0],v[0][1],madd(v_n[1],v[1][1],madd(v_n[2],v[2][1],v_n[3] * v[3][1]))); + const Vertex_t curve2 = madd(v_n[0],v[0][2],madd(v_n[1],v[1][2],madd(v_n[2],v[2][2],v_n[3] * v[3][2]))); + const Vertex_t curve3 = madd(v_n[0],v[0][3],madd(v_n[1],v[1][3],madd(v_n[2],v[2][3],v_n[3] * v[3][3]))); + + const Vec4f u_n = BSplineBasis::derivative2(uu); + return madd(u_n[0],curve0,madd(u_n[1],curve1,madd(u_n[2],curve2,u_n[3] * curve3))); + } + + __forceinline Vertex eval_dvdv(const float uu, const float vv) const + { + const Vec4f v_n = BSplineBasis::derivative2(vv); + const Vertex_t curve0 = madd(v_n[0],v[0][0],madd(v_n[1],v[1][0],madd(v_n[2],v[2][0],v_n[3] * v[3][0]))); + const Vertex_t curve1 = madd(v_n[0],v[0][1],madd(v_n[1],v[1][1],madd(v_n[2],v[2][1],v_n[3] * v[3][1]))); + const Vertex_t curve2 = madd(v_n[0],v[0][2],madd(v_n[1],v[1][2],madd(v_n[2],v[2][2],v_n[3] * v[3][2]))); + const Vertex_t curve3 = madd(v_n[0],v[0][3],madd(v_n[1],v[1][3],madd(v_n[2],v[2][3],v_n[3] * v[3][3]))); + + const Vec4f u_n = BSplineBasis::eval(uu); + return madd(u_n[0],curve0,madd(u_n[1],curve1,madd(u_n[2],curve2,u_n[3] * curve3))); + } + + __forceinline Vertex eval_dudv(const float uu, const float vv) const + { + const Vec4f v_n = BSplineBasis::derivative(vv); + const Vertex_t curve0 = madd(v_n[0],v[0][0],madd(v_n[1],v[1][0],madd(v_n[2],v[2][0],v_n[3] * v[3][0]))); + const Vertex_t curve1 = madd(v_n[0],v[0][1],madd(v_n[1],v[1][1],madd(v_n[2],v[2][1],v_n[3] * v[3][1]))); + const Vertex_t curve2 = madd(v_n[0],v[0][2],madd(v_n[1],v[1][2],madd(v_n[2],v[2][2],v_n[3] * v[3][2]))); + const Vertex_t curve3 = madd(v_n[0],v[0][3],madd(v_n[1],v[1][3],madd(v_n[2],v[2][3],v_n[3] * v[3][3]))); + + const Vec4f u_n = BSplineBasis::derivative(uu); + return madd(u_n[0],curve0,madd(u_n[1],curve1,madd(u_n[2],curve2,u_n[3] * curve3))); + } + + __forceinline Vertex normal(const float uu, const float vv) const + { + const Vertex tu = eval_du(uu,vv); + const Vertex tv = eval_dv(uu,vv); + return cross(tu,tv); + } + + template + __forceinline Vec3 eval(const T& uu, const T& vv, const Vec4& u_n, const Vec4& v_n) const + { + const T curve0_x = madd(v_n[0],T(v[0][0].x),madd(v_n[1],T(v[1][0].x),madd(v_n[2],T(v[2][0].x),v_n[3] * T(v[3][0].x)))); + const T curve1_x = madd(v_n[0],T(v[0][1].x),madd(v_n[1],T(v[1][1].x),madd(v_n[2],T(v[2][1].x),v_n[3] * T(v[3][1].x)))); + const T curve2_x = madd(v_n[0],T(v[0][2].x),madd(v_n[1],T(v[1][2].x),madd(v_n[2],T(v[2][2].x),v_n[3] * T(v[3][2].x)))); + const T curve3_x = madd(v_n[0],T(v[0][3].x),madd(v_n[1],T(v[1][3].x),madd(v_n[2],T(v[2][3].x),v_n[3] * T(v[3][3].x)))); + const T x = madd(u_n[0],curve0_x,madd(u_n[1],curve1_x,madd(u_n[2],curve2_x,u_n[3] * curve3_x))); + + const T curve0_y = madd(v_n[0],T(v[0][0].y),madd(v_n[1],T(v[1][0].y),madd(v_n[2],T(v[2][0].y),v_n[3] * T(v[3][0].y)))); + const T curve1_y = madd(v_n[0],T(v[0][1].y),madd(v_n[1],T(v[1][1].y),madd(v_n[2],T(v[2][1].y),v_n[3] * T(v[3][1].y)))); + const T curve2_y = madd(v_n[0],T(v[0][2].y),madd(v_n[1],T(v[1][2].y),madd(v_n[2],T(v[2][2].y),v_n[3] * T(v[3][2].y)))); + const T curve3_y = madd(v_n[0],T(v[0][3].y),madd(v_n[1],T(v[1][3].y),madd(v_n[2],T(v[2][3].y),v_n[3] * T(v[3][3].y)))); + const T y = madd(u_n[0],curve0_y,madd(u_n[1],curve1_y,madd(u_n[2],curve2_y,u_n[3] * curve3_y))); + + const T curve0_z = madd(v_n[0],T(v[0][0].z),madd(v_n[1],T(v[1][0].z),madd(v_n[2],T(v[2][0].z),v_n[3] * T(v[3][0].z)))); + const T curve1_z = madd(v_n[0],T(v[0][1].z),madd(v_n[1],T(v[1][1].z),madd(v_n[2],T(v[2][1].z),v_n[3] * T(v[3][1].z)))); + const T curve2_z = madd(v_n[0],T(v[0][2].z),madd(v_n[1],T(v[1][2].z),madd(v_n[2],T(v[2][2].z),v_n[3] * T(v[3][2].z)))); + const T curve3_z = madd(v_n[0],T(v[0][3].z),madd(v_n[1],T(v[1][3].z),madd(v_n[2],T(v[2][3].z),v_n[3] * T(v[3][3].z)))); + const T z = madd(u_n[0],curve0_z,madd(u_n[1],curve1_z,madd(u_n[2],curve2_z,u_n[3] * curve3_z))); + + return Vec3(x,y,z); + } + + template + __forceinline Vec3 eval(const T& uu, const T& vv) const + { + const Vec4 u_n = BSplineBasis::eval(uu); + const Vec4 v_n = BSplineBasis::eval(vv); + return eval(uu,vv,u_n,v_n); + } + + template + __forceinline Vec3 eval_du(const T& uu, const T& vv) const + { + const Vec4 u_n = BSplineBasis::derivative(uu); + const Vec4 v_n = BSplineBasis::eval(vv); + return eval(uu,vv,u_n,v_n); + } + + template + __forceinline Vec3 eval_dv(const T& uu, const T& vv) const + { + const Vec4 u_n = BSplineBasis::eval(uu); + const Vec4 v_n = BSplineBasis::derivative(vv); + return eval(uu,vv,u_n,v_n); + } + + template + __forceinline Vec3 eval_dudu(const T& uu, const T& vv) const + { + const Vec4 u_n = BSplineBasis::derivative2(uu); + const Vec4 v_n = BSplineBasis::eval(vv); + return eval(uu,vv,u_n,v_n); + } + + template + __forceinline Vec3 eval_dvdv(const T& uu, const T& vv) const + { + const Vec4 u_n = BSplineBasis::eval(uu); + const Vec4 v_n = BSplineBasis::derivative2(vv); + return eval(uu,vv,u_n,v_n); + } + + template + __forceinline Vec3 eval_dudv(const T& uu, const T& vv) const + { + const Vec4 u_n = BSplineBasis::derivative(uu); + const Vec4 v_n = BSplineBasis::derivative(vv); + return eval(uu,vv,u_n,v_n); + } + + template + __forceinline Vec3 normal(const T& uu, const T& vv) const { + return cross(eval_du(uu,vv),eval_dv(uu,vv)); + } + + void eval(const float u, const float v, + Vertex* P, Vertex* dPdu, Vertex* dPdv, Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv, + const float dscale = 1.0f) const + { + if (P) { + *P = eval(u,v); + } + if (dPdu) { + assert(dPdu); *dPdu = eval_du(u,v)*dscale; + assert(dPdv); *dPdv = eval_dv(u,v)*dscale; + } + if (ddPdudu) { + assert(ddPdudu); *ddPdudu = eval_dudu(u,v)*sqr(dscale); + assert(ddPdvdv); *ddPdvdv = eval_dvdv(u,v)*sqr(dscale); + assert(ddPdudv); *ddPdudv = eval_dudv(u,v)*sqr(dscale); + } + } + + template + __forceinline vfloat eval(const size_t i, const vfloat& uu, const vfloat& vv, const Vec4& u_n, const Vec4& v_n) const + { + const vfloat curve0_x = madd(v_n[0],vfloat(v[0][0][i]),madd(v_n[1],vfloat(v[1][0][i]),madd(v_n[2],vfloat(v[2][0][i]),v_n[3] * vfloat(v[3][0][i])))); + const vfloat curve1_x = madd(v_n[0],vfloat(v[0][1][i]),madd(v_n[1],vfloat(v[1][1][i]),madd(v_n[2],vfloat(v[2][1][i]),v_n[3] * vfloat(v[3][1][i])))); + const vfloat curve2_x = madd(v_n[0],vfloat(v[0][2][i]),madd(v_n[1],vfloat(v[1][2][i]),madd(v_n[2],vfloat(v[2][2][i]),v_n[3] * vfloat(v[3][2][i])))); + const vfloat curve3_x = madd(v_n[0],vfloat(v[0][3][i]),madd(v_n[1],vfloat(v[1][3][i]),madd(v_n[2],vfloat(v[2][3][i]),v_n[3] * vfloat(v[3][3][i])))); + return madd(u_n[0],curve0_x,madd(u_n[1],curve1_x,madd(u_n[2],curve2_x,u_n[3] * curve3_x))); + } + + template + void eval(const vbool& valid, const vfloat& uu, const vfloat& vv, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, + const float dscale, const size_t dstride, const size_t N) const + { + if (P) { + const Vec4 u_n = BSplineBasis::eval(uu); + const Vec4 v_n = BSplineBasis::eval(vv); + for (size_t i=0; i u_n = BSplineBasis::derivative(uu); + const Vec4 v_n = BSplineBasis::eval(vv); + for (size_t i=0; i u_n = BSplineBasis::eval(uu); + const Vec4 v_n = BSplineBasis::derivative(vv); + for (size_t i=0; i u_n = BSplineBasis::derivative2(uu); + const Vec4 v_n = BSplineBasis::eval(vv); + for (size_t i=0; i u_n = BSplineBasis::eval(uu); + const Vec4 v_n = BSplineBasis::derivative2(vv); + for (size_t i=0; i u_n = BSplineBasis::derivative(uu); + const Vec4 v_n = BSplineBasis::derivative(vv); + for (size_t i=0; i BSplinePatch3fa; +} diff --git a/thirdparty/embree/kernels/subdiv/catmullclark_coefficients.h b/thirdparty/embree/kernels/subdiv/catmullclark_coefficients.h new file mode 100644 index 000000000000..05031cf6b9ae --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/catmullclark_coefficients.h @@ -0,0 +1,85 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/geometry.h" + +namespace embree +{ + static const size_t MAX_PATCH_VALENCE = 16; //!< maximum number of vertices of a patch + static const size_t MAX_RING_FACE_VALENCE = 64; //!< maximum number of faces per ring + static const size_t MAX_RING_EDGE_VALENCE = 2*64; //!< maximum number of edges per ring + + class CatmullClarkPrecomputedCoefficients + { + private: + + float table_cos_2PI_div_n[MAX_RING_FACE_VALENCE+1]; + + float* table_limittangent_a[MAX_RING_FACE_VALENCE+1]; + float* table_limittangent_b[MAX_RING_FACE_VALENCE+1]; + float table_limittangent_c[MAX_RING_FACE_VALENCE+1]; + + __forceinline float set_cos_2PI_div_n(const size_t n) { + if (unlikely(n == 0)) return 1.0f; + return cosf(2.0f*float(pi)/(float)n); + } + + __forceinline float set_limittangent_a(const size_t i, const size_t n) + { + if (unlikely(n == 0)) return 1.0f; + const float c0 = 1.0f/(float)n * 1.0f / sqrtf(4.0f + cosf(float(pi)/(float)n)*cosf(float(pi)/(float)n)); + const float c1 = (1.0f/(float)n + cosf(float(pi)/(float)n) * c0); + return cosf(2.0f*float(pi)*(float)i/(float)n) * c1; + } + + __forceinline float set_limittangent_b(const size_t i, const size_t n) + { + if (unlikely(n == 0)) return 1.0f; + const float c0 = 1.0f/(float)n * 1.0f / sqrtf(4.0f + cosf(float(pi)/(float)n)*cosf(float(pi)/(float)n)); + return cosf((2.0f*float(pi)*i+float(pi))/(float)n) * c0; + } + + __forceinline float set_limittangent_c(const size_t n) + { + if (unlikely(n == 0)) return 1.0f; + return 2.0f/16.0f * (5.0f + cosf(2.0f*float(pi)/(float)n) + cosf(float(pi)/(float)n) * sqrtf(18.0f+2.0f*cosf(2.0f*float(pi)/(float)n))); + } + + public: + + __forceinline float cos_2PI_div_n(const size_t n) + { + if (likely(n <= MAX_RING_FACE_VALENCE)) + return table_cos_2PI_div_n[n]; + else + return set_cos_2PI_div_n(n); + } + + __forceinline float limittangent_a(const size_t i, const size_t n) + { + assert(n <= MAX_RING_FACE_VALENCE); + assert(i < n); + return table_limittangent_a[n][i]; + } + + __forceinline float limittangent_b(const size_t i, const size_t n) + { + assert(n <= MAX_RING_FACE_VALENCE); + assert(i < n); + return table_limittangent_b[n][i]; + } + + __forceinline float limittangent_c(const size_t n) + { + assert(n <= MAX_RING_FACE_VALENCE); + return table_limittangent_c[n]; + } + + static CatmullClarkPrecomputedCoefficients table; + + CatmullClarkPrecomputedCoefficients(); + ~CatmullClarkPrecomputedCoefficients(); + }; +} diff --git a/thirdparty/embree/kernels/subdiv/catmullclark_patch.h b/thirdparty/embree/kernels/subdiv/catmullclark_patch.h new file mode 100644 index 000000000000..ab1d63594a06 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/catmullclark_patch.h @@ -0,0 +1,562 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_ring.h" +#include "bezier_curve.h" + +namespace embree +{ + template + class __aligned(64) CatmullClarkPatchT + { + public: + typedef CatmullClark1RingT CatmullClark1Ring; + typedef typename CatmullClark1Ring::Type Type; + + array_t,4> ring; + + public: + __forceinline CatmullClarkPatchT () {} + + __forceinline CatmullClarkPatchT (const HalfEdge* first_half_edge, const char* vertices, size_t stride) { + init(first_half_edge,vertices,stride); + } + + __forceinline CatmullClarkPatchT (const HalfEdge* first_half_edge, const BufferView& vertices) { + init(first_half_edge,vertices.getPtr(),vertices.getStride()); + } + + __forceinline void init (const HalfEdge* first_half_edge, const char* vertices, size_t stride) + { + for (unsigned i=0; i<4; i++) + ring[i].init(first_half_edge+i,vertices,stride); + + assert(verify()); + } + + __forceinline size_t bytes() const { + return ring[0].bytes()+ring[1].bytes()+ring[2].bytes()+ring[3].bytes(); + } + + __forceinline void serialize(void* ptr, size_t& ofs) const + { + for (size_t i=0; i<4; i++) + ring[i].serialize((char*)ptr,ofs); + } + + __forceinline void deserialize(void* ptr) + { + size_t ofs = 0; + for (size_t i=0; i<4; i++) + ring[i].deserialize((char*)ptr,ofs); + } + + __forceinline BBox3fa bounds() const + { + BBox3fa bounds (ring[0].bounds()); + for (size_t i=1; i<4; i++) + bounds.extend(ring[i].bounds()); + return bounds; + } + + __forceinline Type type() const + { + const int ty0 = ring[0].type() ^ CatmullClark1Ring::TYPE_CREASES; + const int ty1 = ring[1].type() ^ CatmullClark1Ring::TYPE_CREASES; + const int ty2 = ring[2].type() ^ CatmullClark1Ring::TYPE_CREASES; + const int ty3 = ring[3].type() ^ CatmullClark1Ring::TYPE_CREASES; + return (Type) ((ty0 & ty1 & ty2 & ty3) ^ CatmullClark1Ring::TYPE_CREASES); + } + + __forceinline bool isFinalResolution(float res) const { + return ring[0].isFinalResolution(res) && ring[1].isFinalResolution(res) && ring[2].isFinalResolution(res) && ring[3].isFinalResolution(res); + } + + static __forceinline void init_regular(const CatmullClark1RingT& p0, + const CatmullClark1RingT& p1, + CatmullClark1RingT& dest0, + CatmullClark1RingT& dest1) + { + assert(p1.face_valence > 2); + dest1.vertex_level = dest0.vertex_level = p0.edge_level; + dest1.face_valence = dest0.face_valence = 4; + dest1.edge_valence = dest0.edge_valence = 8; + dest1.border_index = dest0.border_index = -1; + dest1.vtx = dest0.vtx = (Vertex_t)p0.ring[0]; + dest1.vertex_crease_weight = dest0.vertex_crease_weight = 0.0f; + + dest1.ring[2] = dest0.ring[0] = (Vertex_t)p0.ring[1]; + dest1.ring[1] = dest0.ring[7] = (Vertex_t)p1.ring[0]; + dest1.ring[0] = dest0.ring[6] = (Vertex_t)p1.vtx; + dest1.ring[7] = dest0.ring[5] = (Vertex_t)p1.ring[4]; + dest1.ring[6] = dest0.ring[4] = (Vertex_t)p0.ring[p0.edge_valence-1]; + dest1.ring[5] = dest0.ring[3] = (Vertex_t)p0.ring[p0.edge_valence-2]; + dest1.ring[4] = dest0.ring[2] = (Vertex_t)p0.vtx; + dest1.ring[3] = dest0.ring[1] = (Vertex_t)p0.ring[2]; + + dest1.crease_weight[1] = dest0.crease_weight[0] = 0.0f; + dest1.crease_weight[0] = dest0.crease_weight[3] = p1.crease_weight[1]; + dest1.crease_weight[3] = dest0.crease_weight[2] = 0.0f; + dest1.crease_weight[2] = dest0.crease_weight[1] = p0.crease_weight[0]; + + if (p0.eval_unique_identifier <= p1.eval_unique_identifier) + { + dest0.eval_start_index = 3; + dest1.eval_start_index = 0; + dest0.eval_unique_identifier = p0.eval_unique_identifier; + dest1.eval_unique_identifier = p0.eval_unique_identifier; + } + else + { + dest0.eval_start_index = 1; + dest1.eval_start_index = 2; + dest0.eval_unique_identifier = p1.eval_unique_identifier; + dest1.eval_unique_identifier = p1.eval_unique_identifier; + } + } + + static __forceinline void init_border(const CatmullClark1RingT &p0, + const CatmullClark1RingT &p1, + CatmullClark1RingT &dest0, + CatmullClark1RingT &dest1) + { + dest1.vertex_level = dest0.vertex_level = p0.edge_level; + dest1.face_valence = dest0.face_valence = 3; + dest1.edge_valence = dest0.edge_valence = 6; + dest0.border_index = 2; + dest1.border_index = 4; + dest1.vtx = dest0.vtx = (Vertex_t)p0.ring[0]; + dest1.vertex_crease_weight = dest0.vertex_crease_weight = 0.0f; + + dest1.ring[2] = dest0.ring[0] = (Vertex_t)p0.ring[1]; + dest1.ring[1] = dest0.ring[5] = (Vertex_t)p1.ring[0]; + dest1.ring[0] = dest0.ring[4] = (Vertex_t)p1.vtx; + dest1.ring[5] = dest0.ring[3] = (Vertex_t)p0.ring[p0.border_index+1]; // dummy + dest1.ring[4] = dest0.ring[2] = (Vertex_t)p0.vtx; + dest1.ring[3] = dest0.ring[1] = (Vertex_t)p0.ring[2]; + + dest1.crease_weight[1] = dest0.crease_weight[0] = 0.0f; + dest1.crease_weight[0] = dest0.crease_weight[2] = p1.crease_weight[1]; + dest1.crease_weight[2] = dest0.crease_weight[1] = p0.crease_weight[0]; + + if (p0.eval_unique_identifier <= p1.eval_unique_identifier) + { + dest0.eval_start_index = 1; + dest1.eval_start_index = 2; + dest0.eval_unique_identifier = p0.eval_unique_identifier; + dest1.eval_unique_identifier = p0.eval_unique_identifier; + } + else + { + dest0.eval_start_index = 2; + dest1.eval_start_index = 0; + dest0.eval_unique_identifier = p1.eval_unique_identifier; + dest1.eval_unique_identifier = p1.eval_unique_identifier; + } + } + + static __forceinline void init_regular(const Vertex_t ¢er, const Vertex_t center_ring[8], const unsigned int offset, CatmullClark1RingT &dest) + { + dest.vertex_level = 0.0f; + dest.face_valence = 4; + dest.edge_valence = 8; + dest.border_index = -1; + dest.vtx = (Vertex_t)center; + dest.vertex_crease_weight = 0.0f; + for (size_t i=0; i<8; i++) + dest.ring[i] = (Vertex_t)center_ring[(offset+i)%8]; + for (size_t i=0; i<4; i++) + dest.crease_weight[i] = 0.0f; + + dest.eval_start_index = (8-offset)>>1; + if (dest.eval_start_index >= dest.face_valence) dest.eval_start_index -= dest.face_valence; + assert( dest.eval_start_index < dest.face_valence ); + dest.eval_unique_identifier = 0; + } + + __noinline void subdivide(array_t& patch) const + { + ring[0].subdivide(patch[0].ring[0]); + ring[1].subdivide(patch[1].ring[1]); + ring[2].subdivide(patch[2].ring[2]); + ring[3].subdivide(patch[3].ring[3]); + + patch[0].ring[0].edge_level = 0.5f*ring[0].edge_level; + patch[0].ring[1].edge_level = 0.25f*(ring[1].edge_level+ring[3].edge_level); + patch[0].ring[2].edge_level = 0.25f*(ring[0].edge_level+ring[2].edge_level); + patch[0].ring[3].edge_level = 0.5f*ring[3].edge_level; + + patch[1].ring[0].edge_level = 0.5f*ring[0].edge_level; + patch[1].ring[1].edge_level = 0.5f*ring[1].edge_level; + patch[1].ring[2].edge_level = 0.25f*(ring[0].edge_level+ring[2].edge_level); + patch[1].ring[3].edge_level = 0.25f*(ring[1].edge_level+ring[3].edge_level); + + patch[2].ring[0].edge_level = 0.25f*(ring[0].edge_level+ring[2].edge_level); + patch[2].ring[1].edge_level = 0.5f*ring[1].edge_level; + patch[2].ring[2].edge_level = 0.5f*ring[2].edge_level; + patch[2].ring[3].edge_level = 0.25f*(ring[1].edge_level+ring[3].edge_level); + + patch[3].ring[0].edge_level = 0.25f*(ring[0].edge_level+ring[2].edge_level); + patch[3].ring[1].edge_level = 0.25f*(ring[1].edge_level+ring[3].edge_level); + patch[3].ring[2].edge_level = 0.5f*ring[2].edge_level; + patch[3].ring[3].edge_level = 0.5f*ring[3].edge_level; + + const bool regular0 = ring[0].has_last_face() && ring[1].face_valence > 2; + if (likely(regular0)) + init_regular(patch[0].ring[0],patch[1].ring[1],patch[0].ring[1],patch[1].ring[0]); + else + init_border(patch[0].ring[0],patch[1].ring[1],patch[0].ring[1],patch[1].ring[0]); + + const bool regular1 = ring[1].has_last_face() && ring[2].face_valence > 2; + if (likely(regular1)) + init_regular(patch[1].ring[1],patch[2].ring[2],patch[1].ring[2],patch[2].ring[1]); + else + init_border(patch[1].ring[1],patch[2].ring[2],patch[1].ring[2],patch[2].ring[1]); + + const bool regular2 = ring[2].has_last_face() && ring[3].face_valence > 2; + if (likely(regular2)) + init_regular(patch[2].ring[2],patch[3].ring[3],patch[2].ring[3],patch[3].ring[2]); + else + init_border(patch[2].ring[2],patch[3].ring[3],patch[2].ring[3],patch[3].ring[2]); + + const bool regular3 = ring[3].has_last_face() && ring[0].face_valence > 2; + if (likely(regular3)) + init_regular(patch[3].ring[3],patch[0].ring[0],patch[3].ring[0],patch[0].ring[3]); + else + init_border(patch[3].ring[3],patch[0].ring[0],patch[3].ring[0],patch[0].ring[3]); + + Vertex_t center = (ring[0].vtx + ring[1].vtx + ring[2].vtx + ring[3].vtx) * 0.25f; + + Vertex_t center_ring[8]; + center_ring[0] = (Vertex_t)patch[3].ring[3].ring[0]; + center_ring[7] = (Vertex_t)patch[3].ring[3].vtx; + center_ring[6] = (Vertex_t)patch[2].ring[2].ring[0]; + center_ring[5] = (Vertex_t)patch[2].ring[2].vtx; + center_ring[4] = (Vertex_t)patch[1].ring[1].ring[0]; + center_ring[3] = (Vertex_t)patch[1].ring[1].vtx; + center_ring[2] = (Vertex_t)patch[0].ring[0].ring[0]; + center_ring[1] = (Vertex_t)patch[0].ring[0].vtx; + + init_regular(center,center_ring,0,patch[0].ring[2]); + init_regular(center,center_ring,2,patch[1].ring[3]); + init_regular(center,center_ring,4,patch[2].ring[0]); + init_regular(center,center_ring,6,patch[3].ring[1]); + + assert(patch[0].verify()); + assert(patch[1].verify()); + assert(patch[2].verify()); + assert(patch[3].verify()); + } + + bool verify() const { + return ring[0].hasValidPositions() && ring[1].hasValidPositions() && ring[2].hasValidPositions() && ring[3].hasValidPositions(); + } + + __forceinline void init( FinalQuad& quad ) const + { + quad.vtx[0] = (Vertex_t)ring[0].vtx; + quad.vtx[1] = (Vertex_t)ring[1].vtx; + quad.vtx[2] = (Vertex_t)ring[2].vtx; + quad.vtx[3] = (Vertex_t)ring[3].vtx; + }; + + friend __forceinline embree_ostream operator<<(embree_ostream o, const CatmullClarkPatchT &p) + { + o << "CatmullClarkPatch { " << embree_endl; + for (size_t i=0; i<4; i++) + o << "ring" << i << ": " << p.ring[i] << embree_endl; + o << "}" << embree_endl; + return o; + } + }; + + typedef CatmullClarkPatchT CatmullClarkPatch3fa; + + template + class __aligned(64) GeneralCatmullClarkPatchT + { + public: + typedef CatmullClarkPatchT CatmullClarkPatch; + typedef CatmullClark1RingT CatmullClark1Ring; + typedef BezierCurveT BezierCurve; + + static const unsigned SIZE = MAX_PATCH_VALENCE; + DynamicStackArray,8,SIZE> ring; + unsigned N; + + __forceinline GeneralCatmullClarkPatchT () + : N(0) {} + + GeneralCatmullClarkPatchT (const HalfEdge* h, const char* vertices, size_t stride) { + init(h,vertices,stride); + } + + __forceinline GeneralCatmullClarkPatchT (const HalfEdge* first_half_edge, const BufferView& vertices) { + init(first_half_edge,vertices.getPtr(),vertices.getStride()); + } + + __forceinline void init (const HalfEdge* h, const char* vertices, size_t stride) + { + unsigned int i = 0; + const HalfEdge* edge = h; + do { + ring[i].init(edge,vertices,stride); + edge = edge->next(); + i++; + } while ((edge != h) && (i < SIZE)); + N = i; + } + + __forceinline unsigned size() const { + return N; + } + + __forceinline bool isQuadPatch() const { + return (N == 4) && ring[0].only_quads && ring[1].only_quads && ring[2].only_quads && ring[3].only_quads; + } + + static __forceinline void init_regular(const CatmullClark1RingT& p0, + const CatmullClark1RingT& p1, + CatmullClark1RingT& dest0, + CatmullClark1RingT& dest1) + { + assert(p1.face_valence > 2); + dest1.vertex_level = dest0.vertex_level = p0.edge_level; + dest1.face_valence = dest0.face_valence = 4; + dest1.edge_valence = dest0.edge_valence = 8; + dest1.border_index = dest0.border_index = -1; + dest1.vtx = dest0.vtx = (Vertex_t)p0.ring[0]; + dest1.vertex_crease_weight = dest0.vertex_crease_weight = 0.0f; + + dest1.ring[2] = dest0.ring[0] = (Vertex_t)p0.ring[1]; + dest1.ring[1] = dest0.ring[7] = (Vertex_t)p1.ring[0]; + dest1.ring[0] = dest0.ring[6] = (Vertex_t)p1.vtx; + dest1.ring[7] = dest0.ring[5] = (Vertex_t)p1.ring[4]; + dest1.ring[6] = dest0.ring[4] = (Vertex_t)p0.ring[p0.edge_valence-1]; + dest1.ring[5] = dest0.ring[3] = (Vertex_t)p0.ring[p0.edge_valence-2]; + dest1.ring[4] = dest0.ring[2] = (Vertex_t)p0.vtx; + dest1.ring[3] = dest0.ring[1] = (Vertex_t)p0.ring[2]; + + dest1.crease_weight[1] = dest0.crease_weight[0] = 0.0f; + dest1.crease_weight[0] = dest0.crease_weight[3] = p1.crease_weight[1]; + dest1.crease_weight[3] = dest0.crease_weight[2] = 0.0f; + dest1.crease_weight[2] = dest0.crease_weight[1] = p0.crease_weight[0]; + + if (p0.eval_unique_identifier <= p1.eval_unique_identifier) + { + dest0.eval_start_index = 3; + dest1.eval_start_index = 0; + dest0.eval_unique_identifier = p0.eval_unique_identifier; + dest1.eval_unique_identifier = p0.eval_unique_identifier; + } + else + { + dest0.eval_start_index = 1; + dest1.eval_start_index = 2; + dest0.eval_unique_identifier = p1.eval_unique_identifier; + dest1.eval_unique_identifier = p1.eval_unique_identifier; + } + } + + + static __forceinline void init_border(const CatmullClark1RingT &p0, + const CatmullClark1RingT &p1, + CatmullClark1RingT &dest0, + CatmullClark1RingT &dest1) + { + dest1.vertex_level = dest0.vertex_level = p0.edge_level; + dest1.face_valence = dest0.face_valence = 3; + dest1.edge_valence = dest0.edge_valence = 6; + dest0.border_index = 2; + dest1.border_index = 4; + dest1.vtx = dest0.vtx = (Vertex_t)p0.ring[0]; + dest1.vertex_crease_weight = dest0.vertex_crease_weight = 0.0f; + + dest1.ring[2] = dest0.ring[0] = (Vertex_t)p0.ring[1]; + dest1.ring[1] = dest0.ring[5] = (Vertex_t)p1.ring[0]; + dest1.ring[0] = dest0.ring[4] = (Vertex_t)p1.vtx; + dest1.ring[5] = dest0.ring[3] = (Vertex_t)p0.ring[p0.border_index+1]; // dummy + dest1.ring[4] = dest0.ring[2] = (Vertex_t)p0.vtx; + dest1.ring[3] = dest0.ring[1] = (Vertex_t)p0.ring[2]; + + dest1.crease_weight[1] = dest0.crease_weight[0] = 0.0f; + dest1.crease_weight[0] = dest0.crease_weight[2] = p1.crease_weight[1]; + dest1.crease_weight[2] = dest0.crease_weight[1] = p0.crease_weight[0]; + + if (p0.eval_unique_identifier <= p1.eval_unique_identifier) + { + dest0.eval_start_index = 1; + dest1.eval_start_index = 2; + dest0.eval_unique_identifier = p0.eval_unique_identifier; + dest1.eval_unique_identifier = p0.eval_unique_identifier; + } + else + { + dest0.eval_start_index = 2; + dest1.eval_start_index = 0; + dest0.eval_unique_identifier = p1.eval_unique_identifier; + dest1.eval_unique_identifier = p1.eval_unique_identifier; + } + } + + static __forceinline void init_regular(const Vertex_t ¢er, const array_t& center_ring, const float vertex_level, const unsigned int N, const unsigned int offset, CatmullClark1RingT &dest) + { + assert(N<(MAX_RING_FACE_VALENCE)); + assert(2*N<(MAX_RING_EDGE_VALENCE)); + dest.vertex_level = vertex_level; + dest.face_valence = N; + dest.edge_valence = 2*N; + dest.border_index = -1; + dest.vtx = (Vertex_t)center; + dest.vertex_crease_weight = 0.0f; + for (unsigned i=0; i<2*N; i++) { + dest.ring[i] = (Vertex_t)center_ring[(2*N+offset+i-1)%(2*N)]; + assert(isvalid(dest.ring[i])); + } + for (unsigned i=0; i>1; + if (dest.eval_start_index >= dest.face_valence) dest.eval_start_index -= dest.face_valence; + + assert( dest.eval_start_index < dest.face_valence ); + dest.eval_unique_identifier = 0; + } + + __noinline void subdivide(array_t& patch, unsigned& N_o) const + { + N_o = N; + assert( N ); + for (unsigned i=0; i center_ring; + float center_vertex_level = 2.0f; // guarantees that irregular vertices get always isolated also for non-quads + + for (unsigned i=0; i 2; + if (likely(regular)) init_regular(patch[i].ring[0],patch[ip1].ring[0],patch[i].ring[1],patch[ip1].ring[3]); + else init_border (patch[i].ring[0],patch[ip1].ring[0],patch[i].ring[1],patch[ip1].ring[3]); + + assert( patch[i].ring[1].hasValidPositions() ); + assert( patch[ip1].ring[3].hasValidPositions() ); + + float level = 0.25f*(ring[im1].edge_level+ring[ip1].edge_level); + patch[i].ring[1].edge_level = patch[ip1].ring[2].edge_level = level; + center_vertex_level = max(center_vertex_level,level); + + center += ring[i].vtx; + center_ring[2*i+0] = (Vertex_t)patch[i].ring[0].vtx; + center_ring[2*i+1] = (Vertex_t)patch[i].ring[0].ring[0]; + } + center /= float(N); + + for (unsigned int i=0; i& patches) + { + CatmullClark1Ring patches1ring1 = patches[1].ring[1]; + patches[1].ring[1] = patches[1].ring[0]; // FIXME: optimize these assignments + patches[1].ring[0] = patches[1].ring[3]; + patches[1].ring[3] = patches[1].ring[2]; + patches[1].ring[2] = patches1ring1; + + CatmullClark1Ring patches2ring2 = patches[2].ring[2]; + patches[2].ring[2] = patches[2].ring[0]; + patches[2].ring[0] = patches2ring2; + CatmullClark1Ring patches2ring3 = patches[2].ring[3]; + patches[2].ring[3] = patches[2].ring[1]; + patches[2].ring[1] = patches2ring3; + + CatmullClark1Ring patches3ring3 = patches[3].ring[3]; + patches[3].ring[3] = patches[3].ring[0]; + patches[3].ring[0] = patches[3].ring[1]; + patches[3].ring[1] = patches[3].ring[2]; + patches[3].ring[2] = patches3ring3; + } + + __forceinline void getLimitBorder(BezierCurve curves[GeneralCatmullClarkPatchT::SIZE]) const + { + Vertex P0 = ring[0].getLimitVertex(); + for (unsigned i=0; i GeneralCatmullClarkPatch3fa; +} diff --git a/thirdparty/embree/kernels/subdiv/catmullclark_ring.h b/thirdparty/embree/kernels/subdiv/catmullclark_ring.h new file mode 100644 index 000000000000..73b41fd4ff46 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/catmullclark_ring.h @@ -0,0 +1,826 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/geometry.h" +#include "../common/buffer.h" +#include "half_edge.h" +#include "catmullclark_coefficients.h" + +namespace embree +{ + struct __aligned(64) FinalQuad { + Vec3fa vtx[4]; + }; + + template + struct __aligned(64) CatmullClark1RingT + { + ALIGNED_STRUCT_(64); + + int border_index; //!< edge index where border starts + unsigned int face_valence; //!< number of adjacent quad faces + unsigned int edge_valence; //!< number of adjacent edges (2*face_valence) + float vertex_crease_weight; //!< weight of vertex crease (0 if no vertex crease) + DynamicStackArray crease_weight; //!< edge crease weights for each adjacent edge + float vertex_level; //!< maximum level of all adjacent edges + float edge_level; //!< level of first edge + unsigned int eval_start_index; //!< topology dependent index to start evaluation + unsigned int eval_unique_identifier; //!< topology dependent unique identifier for this ring + Vertex vtx; //!< center vertex + DynamicStackArray ring; //!< ring of neighboring vertices + + public: + CatmullClark1RingT () + : eval_start_index(0), eval_unique_identifier(0) {} // FIXME: default constructor should be empty + + /*! calculates number of bytes required to serialize this structure */ + __forceinline size_t bytes() const + { + size_t ofs = 0; + ofs += sizeof(border_index); + ofs += sizeof(face_valence); + assert(2*face_valence == edge_valence); + ofs += sizeof(vertex_crease_weight); + ofs += face_valence*sizeof(float); + ofs += sizeof(vertex_level); + ofs += sizeof(edge_level); + ofs += sizeof(eval_start_index); + ofs += sizeof(eval_unique_identifier); + ofs += sizeof(vtx); + ofs += edge_valence*sizeof(Vertex); + return ofs; + } + + template + static __forceinline void store(char* ptr, size_t& ofs, const Ty& v) { + *(Ty*)&ptr[ofs] = v; ofs += sizeof(Ty); + } + + template + static __forceinline void load(char* ptr, size_t& ofs, Ty& v) { + v = *(Ty*)&ptr[ofs]; ofs += sizeof(Ty); + } + + /*! serializes the ring to some memory location */ + __forceinline void serialize(char* ptr, size_t& ofs) const + { + store(ptr,ofs,border_index); + store(ptr,ofs,face_valence); + store(ptr,ofs,vertex_crease_weight); + for (size_t i=0; ii); + return ring[i]; + } + + __forceinline const Vertex& back(size_t i) const { + assert(edge_valence>=i); + return ring[edge_valence-i]; + } + + __forceinline bool has_last_face() const { + return (size_t)border_index != (size_t)edge_valence-2; + } + + __forceinline bool has_opposite_front(size_t i) const { + return (size_t)border_index != 2*i; + } + + __forceinline bool has_opposite_back(size_t i) const { + return (size_t)border_index != ((size_t)edge_valence-2-2*i); + } + + __forceinline BBox3fa bounds() const + { + BBox3fa bounds ( vtx ); + for (size_t i = 0; igetStartVertexIndex()*stride); + vertex_crease_weight = h->vertex_crease_weight; + + HalfEdge* p = (HalfEdge*) h; + + unsigned i=0; + unsigned min_vertex_index = (unsigned)-1; + unsigned min_vertex_index_face = (unsigned)-1; + edge_level = p->edge_level; + vertex_level = 0.0f; + + do + { + vertex_level = max(vertex_level,p->edge_level); + crease_weight[i/2] = p->edge_crease_weight; + assert(p->hasOpposite() || p->edge_crease_weight == float(inf)); + + /* store first two vertices of face */ + p = p->next(); + const unsigned index0 = p->getStartVertexIndex(); + ring[i++] = Vertex_t::loadu(vertices+index0*stride); + if (index0 < min_vertex_index) { min_vertex_index = index0; min_vertex_index_face = i>>1; } + p = p->next(); + + const unsigned index1 = p->getStartVertexIndex(); + ring[i++] = Vertex_t::loadu(vertices+index1*stride); + p = p->next(); + + /* continue with next face */ + if (likely(p->hasOpposite())) + p = p->opposite(); + + /* if there is no opposite go the long way to the other side of the border */ + else + { + /* find minimum start vertex */ + const unsigned index0 = p->getStartVertexIndex(); + if (index0 < min_vertex_index) { min_vertex_index = index0; min_vertex_index_face = i>>1; } + + /*! mark first border edge and store dummy vertex for face between the two border edges */ + border_index = i; + crease_weight[i/2] = inf; + ring[i++] = Vertex_t::loadu(vertices+index0*stride); + ring[i++] = vtx; // dummy vertex + + /*! goto other side of border */ + p = (HalfEdge*) h; + while (p->hasOpposite()) + p = p->opposite()->next(); + } + + } while (p != h); + + edge_valence = i; + face_valence = i >> 1; + eval_unique_identifier = min_vertex_index; + eval_start_index = min_vertex_index_face; + + assert( hasValidPositions() ); + } + + __forceinline void subdivide(CatmullClark1RingT& dest) const + { + dest.edge_level = 0.5f*edge_level; + dest.vertex_level = 0.5f*vertex_level; + dest.face_valence = face_valence; + dest.edge_valence = edge_valence; + dest.border_index = border_index; + dest.vertex_crease_weight = max(0.0f,vertex_crease_weight-1.0f); + dest.eval_start_index = eval_start_index; + dest.eval_unique_identifier = eval_unique_identifier; + + /* calculate face points */ + Vertex_t S = Vertex_t(0.0f); + for (size_t i=0; i= face_valence) face_index -= face_valence; assert(face_index < face_valence); + size_t index0 = 2*face_index+0; if (index0 >= edge_valence) index0 -= edge_valence; assert(index0 < edge_valence); + size_t index1 = 2*face_index+1; if (index1 >= edge_valence) index1 -= edge_valence; assert(index1 < edge_valence); + size_t index2 = 2*face_index+2; if (index2 >= edge_valence) index2 -= edge_valence; assert(index2 < edge_valence); + S += dest.ring[index1] = ((vtx + ring[index1]) + (ring[index0] + ring[index2])) * 0.25f; + } + + /* calculate new edge points */ + size_t num_creases = 0; + array_t crease_id; + + for (size_t i=0; i= face_valence) face_index -= face_valence; + const float edge_crease = crease_weight[face_index]; + dest.crease_weight[face_index] = max(edge_crease-1.0f,0.0f); + + size_t index = 2*face_index; + size_t prev_index = face_index == 0 ? edge_valence-1 : 2*face_index-1; + size_t next_index = 2*face_index+1; + + const Vertex_t v = vtx + ring[index]; + const Vertex_t f = dest.ring[prev_index] + dest.ring[next_index]; + S += ring[index]; + + /* fast path for regular edge points */ + if (likely(edge_crease <= 0.0f)) { + dest.ring[index] = (v+f) * 0.25f; + } + + /* slower path for hard edge rule */ + else { + crease_id[num_creases++] = face_index; + dest.ring[index] = v*0.5f; + + /* even slower path for blended edge rule */ + if (unlikely(edge_crease < 1.0f)) { + dest.ring[index] = lerp((v+f)*0.25f,v*0.5f,edge_crease); + } + } + } + + /* compute new vertex using smooth rule */ + const float inv_face_valence = 1.0f / (float)face_valence; + const Vertex_t v_smooth = (Vertex_t) madd(inv_face_valence,S,(float(face_valence)-2.0f)*vtx)*inv_face_valence; + dest.vtx = v_smooth; + + /* compute new vertex using vertex_crease_weight rule */ + if (unlikely(vertex_crease_weight > 0.0f)) + { + if (vertex_crease_weight >= 1.0f) { + dest.vtx = vtx; + } else { + dest.vtx = lerp(v_smooth,vtx,vertex_crease_weight); + } + return; + } + + /* no edge crease rule and dart rule */ + if (likely(num_creases <= 1)) + return; + + /* compute new vertex using crease rule */ + if (likely(num_creases == 2)) + { + /* update vertex using crease rule */ + const size_t crease0 = crease_id[0], crease1 = crease_id[1]; + const Vertex_t v_sharp = (Vertex_t)(ring[2*crease0] + 6.0f*vtx + ring[2*crease1]) * (1.0f / 8.0f); + dest.vtx = v_sharp; + + /* update crease_weights using chaikin rule */ + const float crease_weight0 = crease_weight[crease0], crease_weight1 = crease_weight[crease1]; + dest.crease_weight[crease0] = max(0.25f*(3.0f*crease_weight0 + crease_weight1)-1.0f,0.0f); + dest.crease_weight[crease1] = max(0.25f*(3.0f*crease_weight1 + crease_weight0)-1.0f,0.0f); + + /* interpolate between sharp and smooth rule */ + const float v_blend = 0.5f*(crease_weight0+crease_weight1); + if (unlikely(v_blend < 1.0f)) { + dest.vtx = lerp(v_smooth,v_sharp,v_blend); + } + } + + /* compute new vertex using corner rule */ + else { + dest.vtx = vtx; + } + } + + __forceinline bool isRegular1() const + { + if (border_index == -1) { + if (face_valence == 4) return true; + } else { + if (face_valence < 4) return true; + } + return false; + } + + __forceinline size_t numEdgeCreases() const + { + ssize_t numCreases = 0; + for (size_t i=0; i 0.0f; + } + return numCreases; + } + + enum Type { + TYPE_NONE = 0, //!< invalid type + TYPE_REGULAR = 1, //!< regular patch when ignoring creases + TYPE_REGULAR_CREASES = 2, //!< regular patch when considering creases + TYPE_GREGORY = 4, //!< gregory patch when ignoring creases + TYPE_GREGORY_CREASES = 8, //!< gregory patch when considering creases + TYPE_CREASES = 16 //!< patch has crease features + }; + + __forceinline Type type() const + { + /* check if there is an edge crease anywhere */ + const size_t numCreases = numEdgeCreases(); + const bool noInnerCreases = hasBorder() ? numCreases == 2 : numCreases == 0; + + Type crease_mask = (Type) (TYPE_REGULAR | TYPE_GREGORY); + if (noInnerCreases ) crease_mask = (Type) (crease_mask | TYPE_REGULAR_CREASES | TYPE_GREGORY_CREASES); + if (numCreases != 0) crease_mask = (Type) (crease_mask | TYPE_CREASES); + + /* calculate if this vertex is regular */ + bool hasBorder = border_index != -1; + if (face_valence == 2 && hasBorder) { + if (vertex_crease_weight == 0.0f ) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); + else if (vertex_crease_weight == float(inf)) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); + else return TYPE_CREASES; + } + else if (vertex_crease_weight != 0.0f) return TYPE_CREASES; + else if (face_valence == 3 && hasBorder) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); + else if (face_valence == 4 && !hasBorder) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); + else return (Type) (crease_mask & (TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); + } + + __forceinline bool isFinalResolution(float res) const { + return vertex_level <= res; + } + + /* computes the limit vertex */ + __forceinline Vertex getLimitVertex() const + { + /* return hard corner */ + if (unlikely(std::isinf(vertex_crease_weight))) + return vtx; + + /* border vertex rule */ + if (unlikely(border_index != -1)) + { + const unsigned int second_border_index = border_index+2 >= int(edge_valence) ? 0 : border_index+2; + return (4.0f * vtx + (ring[border_index] + ring[second_border_index])) * 1.0f/6.0f; + } + + Vertex_t F( 0.0f ); + Vertex_t E( 0.0f ); + + assert(eval_start_index < face_valence); + + for (size_t i=0; i= face_valence) index -= face_valence; + F += ring[2*index+1]; + E += ring[2*index]; + } + + const float n = (float)face_valence; + return (Vertex_t)(n*n*vtx+4.0f*E+F) / ((n+5.0f)*n); + } + + /* gets limit tangent in the direction of egde vtx -> ring[0] */ + __forceinline Vertex getLimitTangent() const + { + if (unlikely(std::isinf(vertex_crease_weight))) + return ring[0] - vtx; + + /* border vertex rule */ + if (unlikely(border_index != -1)) + { + if (border_index != (int)edge_valence-2 ) { + return ring[0] - vtx; + } + else + { + const unsigned int second_border_index = border_index+2 >= int(edge_valence) ? 0 : border_index+2; + return (ring[second_border_index] - ring[border_index]) * 0.5f; + } + } + + Vertex_t alpha( 0.0f ); + Vertex_t beta ( 0.0f ); + + const size_t n = face_valence; + + assert(eval_start_index < face_valence); + + Vertex_t q( 0.0f ); + for (size_t i=0; i= face_valence) index -= face_valence; + const float a = CatmullClarkPrecomputedCoefficients::table.limittangent_a(index,n); + const float b = CatmullClarkPrecomputedCoefficients::table.limittangent_b(index,n); + alpha += a * ring[2*index]; + beta += b * ring[2*index+1]; + } + + const float sigma = CatmullClarkPrecomputedCoefficients::table.limittangent_c(n); + return sigma * (alpha + beta); + } + + /* gets limit tangent in the direction of egde vtx -> ring[edge_valence-2] */ + __forceinline Vertex getSecondLimitTangent() const + { + if (unlikely(std::isinf(vertex_crease_weight))) + return ring[2] - vtx; + + /* border vertex rule */ + if (unlikely(border_index != -1)) + { + if (border_index != 2) { + return ring[2] - vtx; + } + else { + const unsigned int second_border_index = border_index+2 >= int(edge_valence) ? 0 : border_index+2; + return (ring[border_index] - ring[second_border_index]) * 0.5f; + } + } + + Vertex_t alpha( 0.0f ); + Vertex_t beta ( 0.0f ); + + const size_t n = face_valence; + + assert(eval_start_index < face_valence); + + for (size_t i=0; i= face_valence) index -= face_valence; + + size_t prev_index = index == 0 ? face_valence-1 : index-1; // need to be bit-wise exact in cosf eval + const float a = CatmullClarkPrecomputedCoefficients::table.limittangent_a(prev_index,n); + const float b = CatmullClarkPrecomputedCoefficients::table.limittangent_b(prev_index,n); + alpha += a * ring[2*index]; + beta += b * ring[2*index+1]; + } + + const float sigma = CatmullClarkPrecomputedCoefficients::table.limittangent_c(n); + return sigma* (alpha + beta); + } + + /* gets surface normal */ + const Vertex getNormal() const { + return cross(getLimitTangent(),getSecondLimitTangent()); + } + + /* returns center of the n-th quad in the 1-ring */ + __forceinline Vertex getQuadCenter(const size_t index) const + { + const Vertex_t &p0 = vtx; + const Vertex_t &p1 = ring[2*index+0]; + const Vertex_t &p2 = ring[2*index+1]; + const Vertex_t &p3 = index == face_valence-1 ? ring[0] : ring[2*index+2]; + const Vertex p = (p0+p1+p2+p3) * 0.25f; + return p; + } + + /* returns center of the n-th edge in the 1-ring */ + __forceinline Vertex getEdgeCenter(const size_t index) const { + return (vtx + ring[index*2]) * 0.5f; + } + + bool hasValidPositions() const + { + for (size_t i=0; i " << c.ring[i]; + if (i % 2 == 0) o << " crease = " << c.crease_weight[i/2]; + o << embree_endl; + } + return o; + } + }; + + typedef CatmullClark1RingT CatmullClark1Ring3fa; + + template + struct __aligned(64) GeneralCatmullClark1RingT + { + ALIGNED_STRUCT_(64); + + typedef CatmullClark1RingT CatmullClark1Ring; + + struct Face + { + __forceinline Face() {} + __forceinline Face (int size, float crease_weight) + : size(size), crease_weight(crease_weight) {} + + // FIXME: add member that returns total number of vertices + + int size; // number of vertices-2 of nth face in ring + float crease_weight; + }; + + Vertex vtx; + DynamicStackArray ring; + DynamicStackArray faces; + unsigned int face_valence; + unsigned int edge_valence; + int border_face; + float vertex_crease_weight; + float vertex_level; //!< maximum level of adjacent edges + float edge_level; // level of first edge + bool only_quads; // true if all faces are quads + unsigned int eval_start_face_index; + unsigned int eval_start_vertex_index; + unsigned int eval_unique_identifier; + + public: + GeneralCatmullClark1RingT() + : eval_start_face_index(0), eval_start_vertex_index(0), eval_unique_identifier(0) {} + + __forceinline bool isRegular() const + { + if (border_face == -1 && face_valence == 4) return true; + return false; + } + + __forceinline bool has_last_face() const { + return border_face != (int)face_valence-1; + } + + __forceinline bool has_second_face() const { + return (border_face == -1) || (border_face >= 2); + } + + bool hasValidPositions() const + { + for (size_t i=0; igetStartVertexIndex()*stride); + vertex_crease_weight = h->vertex_crease_weight; + HalfEdge* p = (HalfEdge*) h; + + unsigned int e=0, f=0; + unsigned min_vertex_index = (unsigned)-1; + unsigned min_vertex_index_face = (unsigned)-1; + unsigned min_vertex_index_vertex = (unsigned)-1; + edge_level = p->edge_level; + vertex_level = 0.0f; + do + { + HalfEdge* p_prev = p->prev(); + HalfEdge* p_next = p->next(); + const float crease_weight = p->edge_crease_weight; + assert(p->hasOpposite() || p->edge_crease_weight == float(inf)); + vertex_level = max(vertex_level,p->edge_level); + + /* find minimum start vertex */ + unsigned vertex_index = p_next->getStartVertexIndex(); + if (vertex_index < min_vertex_index) { min_vertex_index = vertex_index; min_vertex_index_face = f; min_vertex_index_vertex = e; } + + /* store first N-2 vertices of face */ + unsigned int vn = 0; + for (p = p_next; p!=p_prev; p=p->next()) { + ring[e++] = Vertex_t::loadu(vertices+p->getStartVertexIndex()*stride); + vn++; + } + faces[f++] = Face(vn,crease_weight); + only_quads &= (vn == 2); + + /* continue with next face */ + if (likely(p->hasOpposite())) + p = p->opposite(); + + /* if there is no opposite go the long way to the other side of the border */ + else + { + /* find minimum start vertex */ + unsigned vertex_index = p->getStartVertexIndex(); + if (vertex_index < min_vertex_index) { min_vertex_index = vertex_index; min_vertex_index_face = f; min_vertex_index_vertex = e; } + + /*! mark first border edge and store dummy vertex for face between the two border edges */ + border_face = f; + faces[f++] = Face(2,inf); + ring[e++] = Vertex_t::loadu(vertices+p->getStartVertexIndex()*stride); + ring[e++] = vtx; // dummy vertex + + /*! goto other side of border */ + p = (HalfEdge*) h; + while (p->hasOpposite()) + p = p->opposite()->next(); + } + + } while (p != h); + + edge_valence = e; + face_valence = f; + eval_unique_identifier = min_vertex_index; + eval_start_face_index = min_vertex_index_face; + eval_start_vertex_index = min_vertex_index_vertex; + + assert( hasValidPositions() ); + } + + __forceinline void subdivide(CatmullClark1Ring& dest) const + { + dest.edge_level = 0.5f*edge_level; + dest.vertex_level = 0.5f*vertex_level; + dest.face_valence = face_valence; + dest.edge_valence = 2*face_valence; + dest.border_index = border_face == -1 ? -1 : 2*border_face; // FIXME: + dest.vertex_crease_weight = max(0.0f,vertex_crease_weight-1.0f); + dest.eval_start_index = eval_start_face_index; + dest.eval_unique_identifier = eval_unique_identifier; + assert(dest.face_valence <= MAX_RING_FACE_VALENCE); + + /* calculate face points */ + Vertex_t S = Vertex_t(0.0f); + for (size_t face=0, v=eval_start_vertex_index; face crease_id; + Vertex_t C = Vertex_t(0.0f); + for (size_t face=0, j=eval_start_vertex_index; face 0.0f)) + { + if (vertex_crease_weight >= 1.0f) { + dest.vtx = vtx; + } else { + dest.vtx = lerp(vtx,v_smooth,vertex_crease_weight); + } + return; + } + + if (likely(num_creases <= 1)) + return; + + /* compute new vertex using crease rule */ + if (likely(num_creases == 2)) { + const Vertex_t v_sharp = (Vertex_t)(C + 6.0f * vtx) * (1.0f / 8.0f); + const float crease_weight0 = faces[crease_id[0]].crease_weight; + const float crease_weight1 = faces[crease_id[1]].crease_weight; + dest.vtx = v_sharp; + dest.crease_weight[crease_id[0]] = max(0.25f*(3.0f*crease_weight0 + crease_weight1)-1.0f,0.0f); + dest.crease_weight[crease_id[1]] = max(0.25f*(3.0f*crease_weight1 + crease_weight0)-1.0f,0.0f); + const float v_blend = 0.5f*(crease_weight0+crease_weight1); + if (unlikely(v_blend < 1.0f)) { + dest.vtx = lerp(v_sharp,v_smooth,v_blend); + } + } + + /* compute new vertex using corner rule */ + else { + dest.vtx = vtx; + } + } + + void convert(CatmullClark1Ring& dst) const + { + dst.edge_level = edge_level; + dst.vertex_level = vertex_level; + dst.vtx = vtx; + dst.face_valence = face_valence; + dst.edge_valence = 2*face_valence; + dst.border_index = border_face == -1 ? -1 : 2*border_face; + for (size_t i=0; i ring[0] */ + __forceinline Vertex getLimitTangent() const + { + CatmullClark1Ring cc_vtx; + + /* fast path for quad only rings */ + if (only_quads) + { + convert(cc_vtx); + return cc_vtx.getLimitTangent(); + } + + subdivide(cc_vtx); + return 2.0f * cc_vtx.getLimitTangent(); + } + + /* gets limit tangent in the direction of egde vtx -> ring[edge_valence-2] */ + __forceinline Vertex getSecondLimitTangent() const + { + CatmullClark1Ring cc_vtx; + + /* fast path for quad only rings */ + if (only_quads) + { + convert(cc_vtx); + return cc_vtx.getSecondLimitTangent(); + } + + subdivide(cc_vtx); + return 2.0f * cc_vtx.getSecondLimitTangent(); + } + + + /* gets limit vertex */ + __forceinline Vertex getLimitVertex() const + { + CatmullClark1Ring cc_vtx; + + /* fast path for quad only rings */ + if (only_quads) + convert(cc_vtx); + else + subdivide(cc_vtx); + return cc_vtx.getLimitVertex(); + } + + friend __forceinline embree_ostream operator<<(embree_ostream o, const GeneralCatmullClark1RingT &c) + { + o << "vtx " << c.vtx << " size = " << c.edge_valence << ", border_face = " << c.border_face << ", " << " face_valence = " << c.face_valence << + ", edge_level = " << c.edge_level << ", vertex_level = " << c.vertex_level << ", ring: " << embree_endl; + for (size_t v=0, f=0; f " << c.ring[i]; + if (i == v) o << " crease = " << c.faces[f].crease_weight; + o << embree_endl; + } + } + return o; + } + }; +} diff --git a/thirdparty/embree/kernels/subdiv/catmullrom_curve.h b/thirdparty/embree/kernels/subdiv/catmullrom_curve.h new file mode 100644 index 000000000000..b244af481cb1 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/catmullrom_curve.h @@ -0,0 +1,296 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "../common/scene_curves.h" + +/* + + Implements Catmul Rom curves with control points p0, p1, p2, p3. At + t=0 the curve goes through p1, with tangent (p2-p0)/3, and for t=1 + the curve goes through p2 with tangent (p3-p2)/2. + + */ + +namespace embree +{ + class CatmullRomBasis + { + public: + + template + static __forceinline Vec4 eval(const T& u) + { + const T t = u; + const T s = T(1.0f) - u; + const T n0 = - t * s * s; + const T n1 = 2.0f + t * t * (3.0f * t - 5.0f); + const T n2 = 2.0f + s * s * (3.0f * s - 5.0f); + const T n3 = - s * t * t; + return T(0.5f) * Vec4(n0, n1, n2, n3); + } + + template + static __forceinline Vec4 derivative(const T& u) + { + const T t = u; + const T s = 1.0f - u; + const T n0 = - s * s + 2.0f * s * t; + const T n1 = 2.0f * t * (3.0f * t - 5.0f) + 3.0f * t * t; + const T n2 = 2.0f * s * (3.0f * t + 2.0f) - 3.0f * s * s; + const T n3 = -2.0f * s * t + t * t; + return T(0.5f) * Vec4(n0, n1, n2, n3); + } + + template + static __forceinline Vec4 derivative2(const T& u) + { + const T t = u; + const T n0 = -3.0f * t + 2.0f; + const T n1 = 9.0f * t - 5.0f; + const T n2 = -9.0f * t + 4.0f; + const T n3 = 3.0f * t - 1.0f; + return Vec4(n0, n1, n2, n3); + } + }; + + struct PrecomputedCatmullRomBasis + { + enum { N = 16 }; + public: + PrecomputedCatmullRomBasis() {} + PrecomputedCatmullRomBasis(int shift); + + /* basis for bspline evaluation */ + public: + float c0[N+1][N+1]; + float c1[N+1][N+1]; + float c2[N+1][N+1]; + float c3[N+1][N+1]; + + /* basis for bspline derivative evaluation */ + public: + float d0[N+1][N+1]; + float d1[N+1][N+1]; + float d2[N+1][N+1]; + float d3[N+1][N+1]; + }; + extern PrecomputedCatmullRomBasis catmullrom_basis0; + extern PrecomputedCatmullRomBasis catmullrom_basis1; + + template + struct CatmullRomCurveT + { + Vertex v0,v1,v2,v3; + + __forceinline CatmullRomCurveT() {} + + __forceinline CatmullRomCurveT(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3) + : v0(v0), v1(v1), v2(v2), v3(v3) {} + + __forceinline Vertex begin() const { + return madd(1.0f/6.0f,v0,madd(2.0f/3.0f,v1,1.0f/6.0f*v2)); + } + + __forceinline Vertex end() const { + return madd(1.0f/6.0f,v1,madd(2.0f/3.0f,v2,1.0f/6.0f*v3)); + } + + __forceinline Vertex center() const { + return 0.25f*(v0+v1+v2+v3); + } + + __forceinline BBox bounds() const { + return merge(BBox(v0),BBox(v1),BBox(v2),BBox(v3)); + } + + __forceinline friend CatmullRomCurveT operator -( const CatmullRomCurveT& a, const Vertex& b ) { + return CatmullRomCurveT(a.v0-b,a.v1-b,a.v2-b,a.v3-b); + } + + __forceinline CatmullRomCurveT xfm_pr(const LinearSpace3fa& space, const Vec3fa& p) const + { + const Vec3ff q0(xfmVector(space,v0-p), v0.w); + const Vec3ff q1(xfmVector(space,v1-p), v1.w); + const Vec3ff q2(xfmVector(space,v2-p), v2.w); + const Vec3ff q3(xfmVector(space,v3-p), v3.w); + return CatmullRomCurveT(q0,q1,q2,q3); + } + + __forceinline Vertex eval(const float t) const + { + const Vec4 b = CatmullRomBasis::eval(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline Vertex eval_du(const float t) const + { + const Vec4 b = CatmullRomBasis::derivative(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline Vertex eval_dudu(const float t) const + { + const Vec4 b = CatmullRomBasis::derivative2(t); + return madd(b.x,v0,madd(b.y,v1,madd(b.z,v2,b.w*v3))); + } + + __forceinline void eval(const float t, Vertex& p, Vertex& dp, Vertex& ddp) const + { + p = eval(t); + dp = eval_du(t); + ddp = eval_dudu(t); + } + + template + __forceinline Vec4vf veval(const vfloat& t) const + { + const Vec4vf b = CatmullRomBasis::eval(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf veval_du(const vfloat& t) const + { + const Vec4vf b = CatmullRomBasis::derivative(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf veval_dudu(const vfloat& t) const + { + const Vec4vf b = CatmullRomBasis::derivative2(t); + return madd(b.x, Vec4vf(v0), madd(b.y, Vec4vf(v1), madd(b.z, Vec4vf(v2), b.w * Vec4vf(v3)))); + } + + template + __forceinline void veval(const vfloat& t, Vec4vf& p, Vec4vf& dp) const + { + p = veval(t); + dp = veval_du(t); + } + + template + __forceinline Vec4vf eval0(const int ofs, const int size) const + { + assert(size <= PrecomputedCatmullRomBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&catmullrom_basis0.c0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&catmullrom_basis0.c1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&catmullrom_basis0.c2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&catmullrom_basis0.c3[size][ofs]) * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf eval1(const int ofs, const int size) const + { + assert(size <= PrecomputedCatmullRomBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&catmullrom_basis1.c0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&catmullrom_basis1.c1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&catmullrom_basis1.c2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&catmullrom_basis1.c3[size][ofs]) * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf derivative0(const int ofs, const int size) const + { + assert(size <= PrecomputedCatmullRomBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&catmullrom_basis0.d0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&catmullrom_basis0.d1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&catmullrom_basis0.d2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&catmullrom_basis0.d3[size][ofs]) * Vec4vf(v3)))); + } + + template + __forceinline Vec4vf derivative1(const int ofs, const int size) const + { + assert(size <= PrecomputedCatmullRomBasis::N); + assert(ofs <= size); + return madd(vfloat::loadu(&catmullrom_basis1.d0[size][ofs]), Vec4vf(v0), + madd(vfloat::loadu(&catmullrom_basis1.d1[size][ofs]), Vec4vf(v1), + madd(vfloat::loadu(&catmullrom_basis1.d2[size][ofs]), Vec4vf(v2), + vfloat::loadu(&catmullrom_basis1.d3[size][ofs]) * Vec4vf(v3)))); + } + + /* calculates bounds of catmull-rom curve geometry */ + __forceinline BBox3fa accurateRoundBounds() const + { + const int N = 7; + const float scale = 1.0f/(3.0f*(N-1)); + Vec4vfx pl(pos_inf), pu(neg_inf); + for (int i=0; i<=N; i+=VSIZEX) + { + vintx vi = vintx(i)+vintx(step); + vboolx valid = vi <= vintx(N); + const Vec4vfx p = eval0(i,N); + const Vec4vfx dp = derivative0(i,N); + const Vec4vfx pm = p-Vec4vfx(scale)*select(vi!=vintx(0),dp,Vec4vfx(zero)); + const Vec4vfx pp = p+Vec4vfx(scale)*select(vi!=vintx(N),dp,Vec4vfx(zero)); + pl = select(valid,min(pl,p,pm,pp),pl); // FIXME: use masked min + pu = select(valid,max(pu,p,pm,pp),pu); // FIXME: use masked min + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + const float r_min = reduce_min(pl.w); + const float r_max = reduce_max(pu.w); + const Vec3fa upper_r = Vec3fa(max(abs(r_min),abs(r_max))); + return enlarge(BBox3fa(lower,upper),upper_r); + } + + /* calculates bounds when tessellated into N line segments */ + __forceinline BBox3fa accurateFlatBounds(int N) const + { + if (likely(N == 4)) + { + const Vec4vf4 pi = eval0<4>(0,4); + const Vec3fa lower(reduce_min(pi.x),reduce_min(pi.y),reduce_min(pi.z)); + const Vec3fa upper(reduce_max(pi.x),reduce_max(pi.y),reduce_max(pi.z)); + const Vec3fa upper_r = Vec3fa(reduce_max(abs(pi.w))); + const Vec3ff pe = end(); + return enlarge(BBox3fa(min(lower,pe),max(upper,pe)),max(upper_r,Vec3fa(abs(pe.w)))); + } + else + { + Vec3vfx pl(pos_inf), pu(neg_inf); vfloatx ru(0.0f); + for (int i=0; i<=N; i+=VSIZEX) + { + vboolx valid = vintx(i)+vintx(step) <= vintx(N); + const Vec4vfx pi = eval0(i,N); + + pl.x = select(valid,min(pl.x,pi.x),pl.x); // FIXME: use masked min + pl.y = select(valid,min(pl.y,pi.y),pl.y); + pl.z = select(valid,min(pl.z,pi.z),pl.z); + + pu.x = select(valid,max(pu.x,pi.x),pu.x); // FIXME: use masked min + pu.y = select(valid,max(pu.y,pi.y),pu.y); + pu.z = select(valid,max(pu.z,pi.z),pu.z); + + ru = select(valid,max(ru,abs(pi.w)),ru); + } + const Vec3fa lower(reduce_min(pl.x),reduce_min(pl.y),reduce_min(pl.z)); + const Vec3fa upper(reduce_max(pu.x),reduce_max(pu.y),reduce_max(pu.z)); + const Vec3fa upper_r(reduce_max(ru)); + return enlarge(BBox3fa(lower,upper),upper_r); + } + } + + friend __forceinline embree_ostream operator<<(embree_ostream cout, const CatmullRomCurveT& curve) { + return cout << "CatmullRomCurve { v0 = " << curve.v0 << ", v1 = " << curve.v1 << ", v2 = " << curve.v2 << ", v3 = " << curve.v3 << " }"; + } + }; + + __forceinline CatmullRomCurveT enlargeRadiusToMinWidth(const IntersectContext* context, const CurveGeometry* geom, const Vec3fa& ray_org, const CatmullRomCurveT& curve) + { + return CatmullRomCurveT(enlargeRadiusToMinWidth(context,geom,ray_org,curve.v0), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v1), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v2), + enlargeRadiusToMinWidth(context,geom,ray_org,curve.v3)); + } + + typedef CatmullRomCurveT CatmullRomCurve3fa; +} + diff --git a/thirdparty/embree/kernels/subdiv/feature_adaptive_eval.h b/thirdparty/embree/kernels/subdiv/feature_adaptive_eval.h new file mode 100644 index 000000000000..23f24c360c8d --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/feature_adaptive_eval.h @@ -0,0 +1,226 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "patch.h" + +namespace embree +{ + namespace isa + { + template + struct FeatureAdaptiveEval + { + public: + + typedef PatchT Patch; + typedef typename Patch::Ref Ref; + typedef GeneralCatmullClarkPatchT GeneralCatmullClarkPatch; + typedef CatmullClark1RingT CatmullClarkRing; + typedef CatmullClarkPatchT CatmullClarkPatch; + typedef BSplinePatchT BSplinePatch; + typedef BezierPatchT BezierPatch; + typedef GregoryPatchT GregoryPatch; + typedef BilinearPatchT BilinearPatch; + typedef BezierCurveT BezierCurve; + + public: + + FeatureAdaptiveEval (const HalfEdge* edge, const char* vertices, size_t stride, const float u, const float v, + Vertex* P, Vertex* dPdu, Vertex* dPdv, Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv) + : P(P), dPdu(dPdu), dPdv(dPdv), ddPdudu(ddPdudu), ddPdvdv(ddPdvdv), ddPdudv(ddPdudv) + { + switch (edge->patch_type) { + case HalfEdge::BILINEAR_PATCH: BilinearPatch(edge,vertices,stride).eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,1.0f); break; + case HalfEdge::REGULAR_QUAD_PATCH: RegularPatchT(edge,vertices,stride).eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,1.0f); break; +#if PATCH_USE_GREGORY == 2 + case HalfEdge::IRREGULAR_QUAD_PATCH: GregoryPatch(edge,vertices,stride).eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,1.0f); break; +#endif + default: { + GeneralCatmullClarkPatch patch(edge,vertices,stride); + eval(patch,Vec2f(u,v),0); + break; + } + } + } + + FeatureAdaptiveEval (CatmullClarkPatch& patch, const float u, const float v, float dscale, size_t depth, + Vertex* P, Vertex* dPdu, Vertex* dPdv, Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv) + : P(P), dPdu(dPdu), dPdv(dPdv), ddPdudu(ddPdudu), ddPdvdv(ddPdvdv), ddPdudv(ddPdudv) + { + eval(patch,Vec2f(u,v),dscale,depth); + } + + void eval_general_quad(const GeneralCatmullClarkPatch& patch, array_t& patches, const Vec2f& uv, size_t depth) + { + float u = uv.x, v = uv.y; + if (v < 0.5f) { + if (u < 0.5f) { +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[2]; patch.getLimitBorder(borders,0); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border2l,border2r; borders[1].subdivide(border2l,border2r); + eval(patches[0],Vec2f(2.0f*u,2.0f*v),2.0f,depth+1, &border0l, nullptr, nullptr, &border2r); +#else + eval(patches[0],Vec2f(2.0f*u,2.0f*v),2.0f,depth+1); +#endif + if (dPdu && dPdv) { + const Vertex dpdx = *dPdu, dpdy = *dPdv; + *dPdu = dpdx; *dPdv = dpdy; + } + } + else { +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[2]; patch.getLimitBorder(borders,1); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border2l,border2r; borders[1].subdivide(border2l,border2r); + eval(patches[1],Vec2f(2.0f*v,2.0f-2.0f*u),2.0f,depth+1, &border0l, nullptr, nullptr, &border2r); +#else + eval(patches[1],Vec2f(2.0f*v,2.0f-2.0f*u),2.0f,depth+1); +#endif + if (dPdu && dPdv) { + const Vertex dpdx = *dPdu, dpdy = *dPdv; + *dPdu = -dpdy; *dPdv = dpdx; + } + } + } else { + if (u > 0.5f) { +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[2]; patch.getLimitBorder(borders,2); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border2l,border2r; borders[1].subdivide(border2l,border2r); + eval(patches[2],Vec2f(2.0f-2.0f*u,2.0f-2.0f*v),2.0f,depth+1, &border0l, nullptr, nullptr, &border2r); +#else + eval(patches[2],Vec2f(2.0f-2.0f*u,2.0f-2.0f*v),2.0f,depth+1); +#endif + if (dPdu && dPdv) { + const Vertex dpdx = *dPdu, dpdy = *dPdv; + *dPdu = -dpdx; *dPdv = -dpdy; + } + } + else { +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[2]; patch.getLimitBorder(borders,3); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border2l,border2r; borders[1].subdivide(border2l,border2r); + eval(patches[3],Vec2f(2.0f-2.0f*v,2.0f*u),2.0f,depth+1, &border0l, nullptr, nullptr, &border2r); +#else + eval(patches[3],Vec2f(2.0f-2.0f*v,2.0f*u),2.0f,depth+1); +#endif + if (dPdu && dPdv) { + const Vertex dpdx = *dPdu, dpdy = *dPdv; + *dPdu = dpdy; *dPdv = -dpdx; + } + } + } + } + + __forceinline bool final(const CatmullClarkPatch& patch, const typename CatmullClarkRing::Type type, size_t depth) + { + const int max_eval_depth = (type & CatmullClarkRing::TYPE_CREASES) ? PATCH_MAX_EVAL_DEPTH_CREASE : PATCH_MAX_EVAL_DEPTH_IRREGULAR; +//#if PATCH_MIN_RESOLUTION +// return patch.isFinalResolution(PATCH_MIN_RESOLUTION) || depth>=(size_t)max_eval_depth; +//#else + return depth>=(size_t)max_eval_depth; +//#endif + } + + void eval(CatmullClarkPatch& patch, Vec2f uv, float dscale, size_t depth, + BezierCurve* border0 = nullptr, BezierCurve* border1 = nullptr, BezierCurve* border2 = nullptr, BezierCurve* border3 = nullptr) + { + while (true) + { + typename CatmullClarkPatch::Type ty = patch.type(); + + if (unlikely(final(patch,ty,depth))) + { + if (ty & CatmullClarkRing::TYPE_REGULAR) { + RegularPatch(patch,border0,border1,border2,border3).eval(uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(234423,c,c,-1); + return; + } else { + IrregularFillPatch(patch,border0,border1,border2,border3).eval(uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(34534,c,-1,c); + return; + } + } + else if (ty & CatmullClarkRing::TYPE_REGULAR_CREASES) { + assert(depth > 0); + RegularPatch(patch,border0,border1,border2,border3).eval(uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(43524,c,c,-1); + return; + } +#if PATCH_USE_GREGORY == 2 + else if (ty & CatmullClarkRing::TYPE_GREGORY_CREASES) { + assert(depth > 0); + GregoryPatch(patch,border0,border1,border2,border3).eval(uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(23498,c,-1,c); + return; + } +#endif + else + { + array_t patches; + patch.subdivide(patches); // FIXME: only have to generate one of the patches + + const float u = uv.x, v = uv.y; + if (v < 0.5f) { + if (u < 0.5f) { patch = patches[0]; uv = Vec2f(2.0f*u,2.0f*v); dscale *= 2.0f; } + else { patch = patches[1]; uv = Vec2f(2.0f*u-1.0f,2.0f*v); dscale *= 2.0f; } + } else { + if (u > 0.5f) { patch = patches[2]; uv = Vec2f(2.0f*u-1.0f,2.0f*v-1.0f); dscale *= 2.0f; } + else { patch = patches[3]; uv = Vec2f(2.0f*u,2.0f*v-1.0f); dscale *= 2.0f; } + } + depth++; + } + } + } + + void eval(const GeneralCatmullClarkPatch& patch, const Vec2f& uv, const size_t depth) + { + /* convert into standard quad patch if possible */ + if (likely(patch.isQuadPatch())) + { + CatmullClarkPatch qpatch; patch.init(qpatch); + return eval(qpatch,uv,1.0f,depth); + } + + /* subdivide patch */ + unsigned N; + array_t patches; + patch.subdivide(patches,N); // FIXME: only have to generate one of the patches + + /* parametrization for quads */ + if (N == 4) + eval_general_quad(patch,patches,uv,depth); + + /* parametrization for arbitrary polygons */ + else + { + const unsigned l = (unsigned) floor(0.5f*uv.x); const float u = 2.0f*frac(0.5f*uv.x)-0.5f; + const unsigned h = (unsigned) floor(0.5f*uv.y); const float v = 2.0f*frac(0.5f*uv.y)-0.5f; + const unsigned i = 4*h+l; assert(i= N) return; + +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[2]; patch.getLimitBorder(borders,i); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border2l,border2r; borders[1].subdivide(border2l,border2r); + eval(patches[i],Vec2f(u,v),1.0f,depth+1, &border0l, nullptr, nullptr, &border2r); +#else + eval(patches[i],Vec2f(u,v),1.0f,depth+1); +#endif + } + } + + private: + Vertex* const P; + Vertex* const dPdu; + Vertex* const dPdv; + Vertex* const ddPdudu; + Vertex* const ddPdvdv; + Vertex* const ddPdudv; + }; + } +} diff --git a/thirdparty/embree/kernels/subdiv/feature_adaptive_eval_grid.h b/thirdparty/embree/kernels/subdiv/feature_adaptive_eval_grid.h new file mode 100644 index 000000000000..76583b2e5dce --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/feature_adaptive_eval_grid.h @@ -0,0 +1,359 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "patch.h" +#include "catmullclark_patch.h" +#include "bspline_patch.h" +#include "gregory_patch.h" +#include "tessellation.h" + +namespace embree +{ + namespace isa + { + struct FeatureAdaptiveEvalGrid + { + typedef CatmullClark1Ring3fa CatmullClarkRing; + typedef CatmullClarkPatch3fa CatmullClarkPatch; + typedef BilinearPatch3fa BilinearPatch; + typedef BSplinePatch3fa BSplinePatch; + typedef BezierPatch3fa BezierPatch; + typedef GregoryPatch3fa GregoryPatch; + + private: + const unsigned x0,x1; + const unsigned y0,y1; + const unsigned swidth,sheight; + const float rcp_swidth, rcp_sheight; + float* const Px; + float* const Py; + float* const Pz; + float* const U; + float* const V; + float* const Nx; + float* const Ny; + float* const Nz; + const unsigned dwidth; + //const unsigned dheight; + unsigned count; + + + public: + FeatureAdaptiveEvalGrid (const GeneralCatmullClarkPatch3fa& patch, unsigned subPatch, + const unsigned x0, const unsigned x1, const unsigned y0, const unsigned y1, const unsigned swidth, const unsigned sheight, + float* Px, float* Py, float* Pz, float* U, float* V, + float* Nx, float* Ny, float* Nz, + const unsigned dwidth, const unsigned dheight) + : x0(x0), x1(x1), y0(y0), y1(y1), swidth(swidth), sheight(sheight), rcp_swidth(1.0f/(swidth-1.0f)), rcp_sheight(1.0f/(sheight-1.0f)), + Px(Px), Py(Py), Pz(Pz), U(U), V(V), Nx(Nx), Ny(Ny), Nz(Nz), dwidth(dwidth), /*dheight(dheight),*/ count(0) + { + assert(swidth < (2<<20) && sheight < (2<<20)); + const BBox2f srange(Vec2f(0.0f,0.0f),Vec2f(float(swidth-1),float(sheight-1))); + const BBox2f erange(Vec2f((float)x0,(float)y0),Vec2f((float)x1,(float)y1)); + + /* convert into standard quad patch if possible */ + if (likely(patch.isQuadPatch())) + { + CatmullClarkPatch3fa qpatch; patch.init(qpatch); + eval(qpatch, srange, erange, 0); + assert(count == (x1-x0+1)*(y1-y0+1)); + return; + } + + /* subdivide patch */ + unsigned N; + array_t patches; + patch.subdivide(patches,N); + + if (N == 4) + { + const Vec2f c = srange.center(); + const BBox2f srange0(srange.lower,c); + const BBox2f srange1(Vec2f(c.x,srange.lower.y),Vec2f(srange.upper.x,c.y)); + const BBox2f srange2(c,srange.upper); + const BBox2f srange3(Vec2f(srange.lower.x,c.y),Vec2f(c.x,srange.upper.y)); + +#if PATCH_USE_GREGORY == 2 + BezierCurve3fa borders[GeneralCatmullClarkPatch3fa::SIZE]; patch.getLimitBorder(borders); + BezierCurve3fa border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve3fa border1l,border1r; borders[1].subdivide(border1l,border1r); + BezierCurve3fa border2l,border2r; borders[2].subdivide(border2l,border2r); + BezierCurve3fa border3l,border3r; borders[3].subdivide(border3l,border3r); + GeneralCatmullClarkPatch3fa::fix_quad_ring_order(patches); + eval(patches[0],srange0,intersect(srange0,erange),1,&border0l,nullptr,nullptr,&border3r); + eval(patches[1],srange1,intersect(srange1,erange),1,&border0r,&border1l,nullptr,nullptr); + eval(patches[2],srange2,intersect(srange2,erange),1,nullptr,&border1r,&border2l,nullptr); + eval(patches[3],srange3,intersect(srange3,erange),1,nullptr,nullptr,&border2r,&border3l); +#else + GeneralCatmullClarkPatch3fa::fix_quad_ring_order(patches); + eval(patches[0],srange0,intersect(srange0,erange),1); + eval(patches[1],srange1,intersect(srange1,erange),1); + eval(patches[2],srange2,intersect(srange2,erange),1); + eval(patches[3],srange3,intersect(srange3,erange),1); +#endif + } + else + { + assert(subPatch < N); + +#if PATCH_USE_GREGORY == 2 + BezierCurve3fa borders[2]; patch.getLimitBorder(borders,subPatch); + BezierCurve3fa border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve3fa border2l,border2r; borders[1].subdivide(border2l,border2r); + eval(patches[subPatch], srange, erange, 1, &border0l, nullptr, nullptr, &border2r); +#else + eval(patches[subPatch], srange, erange, 1); +#endif + + } + assert(count == (x1-x0+1)*(y1-y0+1)); + } + + FeatureAdaptiveEvalGrid (const CatmullClarkPatch3fa& patch, + const BBox2f& srange, const BBox2f& erange, const unsigned depth, + const unsigned x0, const unsigned x1, const unsigned y0, const unsigned y1, const unsigned swidth, const unsigned sheight, + float* Px, float* Py, float* Pz, float* U, float* V, + float* Nx, float* Ny, float* Nz, + const unsigned dwidth, const unsigned dheight) + : x0(x0), x1(x1), y0(y0), y1(y1), swidth(swidth), sheight(sheight), rcp_swidth(1.0f/(swidth-1.0f)), rcp_sheight(1.0f/(sheight-1.0f)), + Px(Px), Py(Py), Pz(Pz), U(U), V(V), Nx(Nx), Ny(Ny), Nz(Nz), dwidth(dwidth), /*dheight(dheight),*/ count(0) + { + eval(patch,srange,erange,depth); + } + + template + void evalLocalGrid(const Patch& patch, const BBox2f& srange, const int lx0, const int lx1, const int ly0, const int ly1) + { + const float scale_x = rcp(srange.upper.x-srange.lower.x); + const float scale_y = rcp(srange.upper.y-srange.lower.y); + count += (lx1-lx0)*(ly1-ly0); + +#if 0 + for (unsigned iy=ly0; iy=max_eval_depth; +//#else + return depth>=max_eval_depth; +//#endif + } + + void eval(const CatmullClarkPatch3fa& patch, const BBox2f& srange, const BBox2f& erange, const unsigned depth, + const BezierCurve3fa* border0 = nullptr, const BezierCurve3fa* border1 = nullptr, const BezierCurve3fa* border2 = nullptr, const BezierCurve3fa* border3 = nullptr) + { + if (erange.empty()) + return; + + int lx0 = (int) ceilf(erange.lower.x); + int lx1 = (int) ceilf(erange.upper.x) + (erange.upper.x == x1 && (srange.lower.x < erange.upper.x || erange.upper.x == 0)); + int ly0 = (int) ceilf(erange.lower.y); + int ly1 = (int) ceilf(erange.upper.y) + (erange.upper.y == y1 && (srange.lower.y < erange.upper.y || erange.upper.y == 0)); + if (lx0 >= lx1 || ly0 >= ly1) return; + + CatmullClarkPatch::Type ty = patch.type(); + + if (unlikely(final(patch,ty,depth))) + { + if (ty & CatmullClarkRing::TYPE_REGULAR) { + RegularPatch rpatch(patch,border0,border1,border2,border3); + evalLocalGrid(rpatch,srange,lx0,lx1,ly0,ly1); + return; + } else { + IrregularFillPatch ipatch(patch,border0,border1,border2,border3); + evalLocalGrid(ipatch,srange,lx0,lx1,ly0,ly1); + return; + } + } + else if (ty & CatmullClarkRing::TYPE_REGULAR_CREASES) { + assert(depth > 0); + RegularPatch rpatch(patch,border0,border1,border2,border3); + evalLocalGrid(rpatch,srange,lx0,lx1,ly0,ly1); + return; + } +#if PATCH_USE_GREGORY == 2 + else if (ty & CatmullClarkRing::TYPE_GREGORY_CREASES) { + assert(depth > 0); + GregoryPatch gpatch(patch,border0,border1,border2,border3); + evalLocalGrid(gpatch,srange,lx0,lx1,ly0,ly1); + } +#endif + else + { + array_t patches; + patch.subdivide(patches); + + const Vec2f c = srange.center(); + const BBox2f srange0(srange.lower,c); + const BBox2f srange1(Vec2f(c.x,srange.lower.y),Vec2f(srange.upper.x,c.y)); + const BBox2f srange2(c,srange.upper); + const BBox2f srange3(Vec2f(srange.lower.x,c.y),Vec2f(c.x,srange.upper.y)); + + eval(patches[0],srange0,intersect(srange0,erange),depth+1); + eval(patches[1],srange1,intersect(srange1,erange),depth+1); + eval(patches[2],srange2,intersect(srange2,erange),depth+1); + eval(patches[3],srange3,intersect(srange3,erange),depth+1); + } + } + }; + + template + bool stitch_col(const Patch& patch, int subPatch, + const bool right, const unsigned y0, const unsigned y1, const int fine_y, const int coarse_y, + float* Px, float* Py, float* Pz, float* U, float* V, float* Nx, float* Ny, float* Nz, const unsigned dx0, const unsigned dwidth, const unsigned dheight) + { + assert(coarse_y <= fine_y); + if (likely(fine_y == coarse_y)) + return false; + + const unsigned y0s = stitch(y0,fine_y,coarse_y); + const unsigned y1s = stitch(y1,fine_y,coarse_y); + const unsigned M = y1s-y0s+1 + VSIZEX; + + dynamic_large_stack_array(float,px,M,64*sizeof(float)); + dynamic_large_stack_array(float,py,M,64*sizeof(float)); + dynamic_large_stack_array(float,pz,M,64*sizeof(float)); + dynamic_large_stack_array(float,u,M,64*sizeof(float)); + dynamic_large_stack_array(float,v,M,64*sizeof(float)); + dynamic_large_stack_array(float,nx,M,64*sizeof(float)); + dynamic_large_stack_array(float,ny,M,64*sizeof(float)); + dynamic_large_stack_array(float,nz,M,64*sizeof(float)); + const bool has_Nxyz = Nx; assert(!Nx || (Ny && Nz)); + Eval(patch,subPatch, right,right, y0s,y1s, 2,coarse_y+1, px,py,pz,u,v, + has_Nxyz ? (float*)nx : nullptr,has_Nxyz ? (float*)ny : nullptr ,has_Nxyz ? (float*)nz : nullptr, 1,4097); + + for (unsigned y=y0; y<=y1; y++) + { + const unsigned ys = stitch(y,fine_y,coarse_y)-y0s; + Px[(y-y0)*dwidth+dx0] = px[ys]; + Py[(y-y0)*dwidth+dx0] = py[ys]; + Pz[(y-y0)*dwidth+dx0] = pz[ys]; + U [(y-y0)*dwidth+dx0] = u[ys]; + V [(y-y0)*dwidth+dx0] = v[ys]; + if (unlikely(has_Nxyz)) { + Nx[(y-y0)*dwidth+dx0] = nx[ys]; + Ny[(y-y0)*dwidth+dx0] = ny[ys]; + Nz[(y-y0)*dwidth+dx0] = nz[ys]; + } + } + return true; + } + + template + bool stitch_row(const Patch& patch, int subPatch, + const bool bottom, const unsigned x0, const unsigned x1, const int fine_x, const int coarse_x, + float* Px, float* Py, float* Pz, float* U, float* V, float* Nx, float* Ny, float* Nz, const unsigned dy0, const unsigned dwidth, const unsigned dheight) + { + assert(coarse_x <= fine_x); + if (likely(fine_x == coarse_x)) + return false; + + const unsigned x0s = stitch(x0,fine_x,coarse_x); + const unsigned x1s = stitch(x1,fine_x,coarse_x); + const unsigned M = x1s-x0s+1 + VSIZEX; + + dynamic_large_stack_array(float,px,M,32*sizeof(float)); + dynamic_large_stack_array(float,py,M,32*sizeof(float)); + dynamic_large_stack_array(float,pz,M,32*sizeof(float)); + dynamic_large_stack_array(float,u,M,32*sizeof(float)); + dynamic_large_stack_array(float,v,M,32*sizeof(float)); + dynamic_large_stack_array(float,nx,M,32*sizeof(float)); + dynamic_large_stack_array(float,ny,M,32*sizeof(float)); + dynamic_large_stack_array(float,nz,M,32*sizeof(float)); + const bool has_Nxyz = Nx; assert(!Nx || (Ny && Nz)); + Eval(patch,subPatch, x0s,x1s, bottom,bottom, coarse_x+1,2, px,py,pz,u,v, + has_Nxyz ? (float*)nx :nullptr, has_Nxyz ? (float*)ny : nullptr , has_Nxyz ? (float*)nz : nullptr, 4097,1); + + for (unsigned x=x0; x<=x1; x++) + { + const unsigned xs = stitch(x,fine_x,coarse_x)-x0s; + Px[dy0*dwidth+x-x0] = px[xs]; + Py[dy0*dwidth+x-x0] = py[xs]; + Pz[dy0*dwidth+x-x0] = pz[xs]; + U [dy0*dwidth+x-x0] = u[xs]; + V [dy0*dwidth+x-x0] = v[xs]; + if (unlikely(has_Nxyz)) { + Nx[dy0*dwidth+x-x0] = nx[xs]; + Ny[dy0*dwidth+x-x0] = ny[xs]; + Nz[dy0*dwidth+x-x0] = nz[xs]; + } + } + return true; + } + + template + void feature_adaptive_eval_grid (const Patch& patch, unsigned subPatch, const float levels[4], + const unsigned x0, const unsigned x1, const unsigned y0, const unsigned y1, const unsigned swidth, const unsigned sheight, + float* Px, float* Py, float* Pz, float* U, float* V, float* Nx, float* Ny, float* Nz, const unsigned dwidth, const unsigned dheight) + { + bool sl = false, sr = false, st = false, sb = false; + if (levels) { + sl = x0 == 0 && stitch_col(patch,subPatch,0,y0,y1,sheight-1,int(levels[3]), Px,Py,Pz,U,V,Nx,Ny,Nz, 0 ,dwidth,dheight); + sr = x1 == swidth-1 && stitch_col(patch,subPatch,1,y0,y1,sheight-1,int(levels[1]), Px,Py,Pz,U,V,Nx,Ny,Nz, x1-x0,dwidth,dheight); + st = y0 == 0 && stitch_row(patch,subPatch,0,x0,x1,swidth-1,int(levels[0]), Px,Py,Pz,U,V,Nx,Ny,Nz, 0 ,dwidth,dheight); + sb = y1 == sheight-1 && stitch_row(patch,subPatch,1,x0,x1,swidth-1,int(levels[2]), Px,Py,Pz,U,V,Nx,Ny,Nz, y1-y0,dwidth,dheight); + } + const unsigned ofs = st*dwidth+sl; + Eval(patch,subPatch,x0+sl,x1-sr,y0+st,y1-sb, swidth,sheight, Px+ofs,Py+ofs,Pz+ofs,U+ofs,V+ofs,Nx?Nx+ofs:nullptr,Ny?Ny+ofs:nullptr,Nz?Nz+ofs:nullptr, dwidth,dheight); + } + } +} + diff --git a/thirdparty/embree/kernels/subdiv/feature_adaptive_eval_simd.h b/thirdparty/embree/kernels/subdiv/feature_adaptive_eval_simd.h new file mode 100644 index 000000000000..fa3216730f82 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/feature_adaptive_eval_simd.h @@ -0,0 +1,186 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "patch.h" + +namespace embree +{ + namespace isa + { + template + struct FeatureAdaptiveEvalSimd + { + public: + + typedef PatchT Patch; + typedef typename Patch::Ref Ref; + typedef GeneralCatmullClarkPatchT GeneralCatmullClarkPatch; + typedef CatmullClark1RingT CatmullClarkRing; + typedef CatmullClarkPatchT CatmullClarkPatch; + typedef BSplinePatchT BSplinePatch; + typedef BezierPatchT BezierPatch; + typedef GregoryPatchT GregoryPatch; + typedef BilinearPatchT BilinearPatch; + typedef BezierCurveT BezierCurve; + + FeatureAdaptiveEvalSimd (const HalfEdge* edge, const char* vertices, size_t stride, const vbool& valid, const vfloat& u, const vfloat& v, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, const size_t dstride, const size_t N) + : P(P), dPdu(dPdu), dPdv(dPdv), ddPdudu(ddPdudu), ddPdvdv(ddPdvdv), ddPdudv(ddPdudv), dstride(dstride), N(N) + { + switch (edge->patch_type) { + case HalfEdge::BILINEAR_PATCH: BilinearPatch(edge,vertices,stride).eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,1.0f,dstride,N); break; + case HalfEdge::REGULAR_QUAD_PATCH: RegularPatchT(edge,vertices,stride).eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,1.0f,dstride,N); break; +#if PATCH_USE_GREGORY == 2 + case HalfEdge::IRREGULAR_QUAD_PATCH: GregoryPatchT(edge,vertices,stride).eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,1.0f,dstride,N); break; +#endif + default: { + GeneralCatmullClarkPatch patch(edge,vertices,stride); + eval_direct(valid,patch,Vec2(u,v),0); + break; + } + } + } + + FeatureAdaptiveEvalSimd (const CatmullClarkPatch& patch, const vbool& valid, const vfloat& u, const vfloat& v, float dscale, size_t depth, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, const size_t dstride, const size_t N) + : P(P), dPdu(dPdu), dPdv(dPdv), ddPdudu(ddPdudu), ddPdvdv(ddPdvdv), ddPdudv(ddPdudv), dstride(dstride), N(N) + { + eval_direct(valid,patch,Vec2(u,v),dscale,depth); + } + + template + __forceinline void eval_quad_direct(const vbool& valid, array_t& patches, const Vec2& uv, float dscale, size_t depth) + { + const vfloat u = uv.x, v = uv.y; + const vbool u0_mask = u < 0.5f, u1_mask = u >= 0.5f; + const vbool v0_mask = v < 0.5f, v1_mask = v >= 0.5f; + const vbool u0v0_mask = valid & u0_mask & v0_mask; + const vbool u0v1_mask = valid & u0_mask & v1_mask; + const vbool u1v0_mask = valid & u1_mask & v0_mask; + const vbool u1v1_mask = valid & u1_mask & v1_mask; + if (any(u0v0_mask)) eval_direct(u0v0_mask,patches[0],Vec2(2.0f*u,2.0f*v),2.0f*dscale,depth+1); + if (any(u1v0_mask)) eval_direct(u1v0_mask,patches[1],Vec2(2.0f*u-1.0f,2.0f*v),2.0f*dscale,depth+1); + if (any(u1v1_mask)) eval_direct(u1v1_mask,patches[2],Vec2(2.0f*u-1.0f,2.0f*v-1.0f),2.0f*dscale,depth+1); + if (any(u0v1_mask)) eval_direct(u0v1_mask,patches[3],Vec2(2.0f*u,2.0f*v-1.0f),2.0f*dscale,depth+1); + } + + template + __forceinline void eval_general_quad_direct(const vbool& valid, const GeneralCatmullClarkPatch& patch, array_t& patches, const Vec2& uv, float dscale, size_t depth) + { +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[GeneralCatmullClarkPatch::SIZE]; patch.getLimitBorder(borders); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border1l,border1r; borders[1].subdivide(border1l,border1r); + BezierCurve border2l,border2r; borders[2].subdivide(border2l,border2r); + BezierCurve border3l,border3r; borders[3].subdivide(border3l,border3r); +#endif + GeneralCatmullClarkPatch::fix_quad_ring_order(patches); + const vfloat u = uv.x, v = uv.y; + const vbool u0_mask = u < 0.5f, u1_mask = u >= 0.5f; + const vbool v0_mask = v < 0.5f, v1_mask = v >= 0.5f; + const vbool u0v0_mask = valid & u0_mask & v0_mask; + const vbool u0v1_mask = valid & u0_mask & v1_mask; + const vbool u1v0_mask = valid & u1_mask & v0_mask; + const vbool u1v1_mask = valid & u1_mask & v1_mask; +#if PATCH_USE_GREGORY == 2 + if (any(u0v0_mask)) eval_direct(u0v0_mask,patches[0],Vec2(2.0f*u,2.0f*v),2.0f*dscale,depth+1,&border0l,nullptr,nullptr,&border3r); + if (any(u1v0_mask)) eval_direct(u1v0_mask,patches[1],Vec2(2.0f*u-1.0f,2.0f*v),2.0f*dscale,depth+1,&border0r,&border1l,nullptr,nullptr); + if (any(u1v1_mask)) eval_direct(u1v1_mask,patches[2],Vec2(2.0f*u-1.0f,2.0f*v-1.0f),2.0f*dscale,depth+1,nullptr,&border1r,&border2l,nullptr); + if (any(u0v1_mask)) eval_direct(u0v1_mask,patches[3],Vec2(2.0f*u,2.0f*v-1.0f),2.0f*dscale,depth+1,nullptr,nullptr,&border2r,&border3l); +#else + if (any(u0v0_mask)) eval_direct(u0v0_mask,patches[0],Vec2(2.0f*u,2.0f*v),2.0f*dscale,depth+1); + if (any(u1v0_mask)) eval_direct(u1v0_mask,patches[1],Vec2(2.0f*u-1.0f,2.0f*v),2.0f*dscale,depth+1); + if (any(u1v1_mask)) eval_direct(u1v1_mask,patches[2],Vec2(2.0f*u-1.0f,2.0f*v-1.0f),2.0f*dscale,depth+1); + if (any(u0v1_mask)) eval_direct(u0v1_mask,patches[3],Vec2(2.0f*u,2.0f*v-1.0f),2.0f*dscale,depth+1); +#endif + } + + __forceinline bool final(const CatmullClarkPatch& patch, const typename CatmullClarkRing::Type type, size_t depth) + { + const size_t max_eval_depth = (type & CatmullClarkRing::TYPE_CREASES) ? PATCH_MAX_EVAL_DEPTH_CREASE : PATCH_MAX_EVAL_DEPTH_IRREGULAR; +//#if PATCH_MIN_RESOLUTION +// return patch.isFinalResolution(PATCH_MIN_RESOLUTION) || depth>=max_eval_depth; +//#else + return depth>=max_eval_depth; +//#endif + } + + void eval_direct(const vbool& valid, const CatmullClarkPatch& patch, const Vec2& uv, float dscale, size_t depth, + BezierCurve* border0 = nullptr, BezierCurve* border1 = nullptr, BezierCurve* border2 = nullptr, BezierCurve* border3 = nullptr) + { + typename CatmullClarkPatch::Type ty = patch.type(); + + if (unlikely(final(patch,ty,depth))) + { + if (ty & CatmullClarkRing::TYPE_REGULAR) { + RegularPatch(patch,border0,border1,border2,border3).eval(valid,uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + } else { + IrregularFillPatch(patch,border0,border1,border2,border3).eval(valid,uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + } + } + else if (ty & CatmullClarkRing::TYPE_REGULAR_CREASES) { + assert(depth > 0); RegularPatch(patch,border0,border1,border2,border3).eval(valid,uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + } +#if PATCH_USE_GREGORY == 2 + else if (ty & CatmullClarkRing::TYPE_GREGORY_CREASES) { + assert(depth > 0); GregoryPatch(patch,border0,border1,border2,border3).eval(valid,uv.x,uv.y,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + } +#endif + else + { + array_t patches; + patch.subdivide(patches); // FIXME: only have to generate one of the patches + eval_quad_direct(valid,patches,uv,dscale,depth); + } + } + + void eval_direct(const vbool& valid, const GeneralCatmullClarkPatch& patch, const Vec2& uv, const size_t depth) + { + /* convert into standard quad patch if possible */ + if (likely(patch.isQuadPatch())) { + CatmullClarkPatch qpatch; patch.init(qpatch); + return eval_direct(valid,qpatch,uv,1.0f,depth); + } + + /* subdivide patch */ + unsigned Nc; + array_t patches; + patch.subdivide(patches,Nc); // FIXME: only have to generate one of the patches + + /* parametrization for quads */ + if (Nc == 4) + eval_general_quad_direct(valid,patch,patches,uv,1.0f,depth); + + /* parametrization for arbitrary polygons */ + else + { + const vint l = (vint)floor(0.5f*uv.x); const vfloat u = 2.0f*frac(0.5f*uv.x)-0.5f; + const vint h = (vint)floor(0.5f*uv.y); const vfloat v = 2.0f*frac(0.5f*uv.y)-0.5f; + const vint i = (h<<2)+l; assert(all(valid,i(u,v),1.0f,depth+1, &border0l, nullptr, nullptr, &border2r); +#else + eval_direct(valid,patches[i],Vec2(u,v),1.0f,depth+1); +#endif + }); + } + } + + private: + float* const P; + float* const dPdu; + float* const dPdv; + float* const ddPdudu; + float* const ddPdvdv; + float* const ddPdudv; + const size_t dstride; + const size_t N; + }; + } +} diff --git a/thirdparty/embree/kernels/subdiv/gregory_patch.h b/thirdparty/embree/kernels/subdiv/gregory_patch.h new file mode 100644 index 000000000000..2a7c4b1f2c23 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/gregory_patch.h @@ -0,0 +1,893 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_patch.h" +#include "bezier_patch.h" +#include "bezier_curve.h" +#include "catmullclark_coefficients.h" + +namespace embree +{ + template + class __aligned(64) GregoryPatchT + { + typedef CatmullClarkPatchT CatmullClarkPatch; + typedef GeneralCatmullClarkPatchT GeneralCatmullClarkPatch; + typedef CatmullClark1RingT CatmullClark1Ring; + typedef BezierCurveT BezierCurve; + + public: + Vertex v[4][4]; + Vertex f[2][2]; + + __forceinline GregoryPatchT() {} + + __forceinline GregoryPatchT(const CatmullClarkPatch& patch) { + init(patch); + } + + __forceinline GregoryPatchT(const CatmullClarkPatch& patch, + const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) + { + init_crackfix(patch,border0,border1,border2,border3); + } + + __forceinline GregoryPatchT (const HalfEdge* edge, const char* vertices, size_t stride) { + init(CatmullClarkPatch(edge,vertices,stride)); + } + + __forceinline Vertex& p0() { return v[0][0]; } + __forceinline Vertex& p1() { return v[0][3]; } + __forceinline Vertex& p2() { return v[3][3]; } + __forceinline Vertex& p3() { return v[3][0]; } + + __forceinline Vertex& e0_p() { return v[0][1]; } + __forceinline Vertex& e0_m() { return v[1][0]; } + __forceinline Vertex& e1_p() { return v[1][3]; } + __forceinline Vertex& e1_m() { return v[0][2]; } + __forceinline Vertex& e2_p() { return v[3][2]; } + __forceinline Vertex& e2_m() { return v[2][3]; } + __forceinline Vertex& e3_p() { return v[2][0]; } + __forceinline Vertex& e3_m() { return v[3][1]; } + + __forceinline Vertex& f0_p() { return v[1][1]; } + __forceinline Vertex& f1_p() { return v[1][2]; } + __forceinline Vertex& f2_p() { return v[2][2]; } + __forceinline Vertex& f3_p() { return v[2][1]; } + __forceinline Vertex& f0_m() { return f[0][0]; } + __forceinline Vertex& f1_m() { return f[0][1]; } + __forceinline Vertex& f2_m() { return f[1][1]; } + __forceinline Vertex& f3_m() { return f[1][0]; } + + __forceinline const Vertex& p0() const { return v[0][0]; } + __forceinline const Vertex& p1() const { return v[0][3]; } + __forceinline const Vertex& p2() const { return v[3][3]; } + __forceinline const Vertex& p3() const { return v[3][0]; } + + __forceinline const Vertex& e0_p() const { return v[0][1]; } + __forceinline const Vertex& e0_m() const { return v[1][0]; } + __forceinline const Vertex& e1_p() const { return v[1][3]; } + __forceinline const Vertex& e1_m() const { return v[0][2]; } + __forceinline const Vertex& e2_p() const { return v[3][2]; } + __forceinline const Vertex& e2_m() const { return v[2][3]; } + __forceinline const Vertex& e3_p() const { return v[2][0]; } + __forceinline const Vertex& e3_m() const { return v[3][1]; } + + __forceinline const Vertex& f0_p() const { return v[1][1]; } + __forceinline const Vertex& f1_p() const { return v[1][2]; } + __forceinline const Vertex& f2_p() const { return v[2][2]; } + __forceinline const Vertex& f3_p() const { return v[2][1]; } + __forceinline const Vertex& f0_m() const { return f[0][0]; } + __forceinline const Vertex& f1_m() const { return f[0][1]; } + __forceinline const Vertex& f2_m() const { return f[1][1]; } + __forceinline const Vertex& f3_m() const { return f[1][0]; } + + __forceinline Vertex initCornerVertex(const CatmullClarkPatch& irreg_patch, const size_t index) { + return irreg_patch.ring[index].getLimitVertex(); + } + + __forceinline Vertex initPositiveEdgeVertex(const CatmullClarkPatch& irreg_patch, const size_t index, const Vertex& p_vtx) { + return madd(1.0f/3.0f,irreg_patch.ring[index].getLimitTangent(),p_vtx); + } + + __forceinline Vertex initNegativeEdgeVertex(const CatmullClarkPatch& irreg_patch, const size_t index, const Vertex& p_vtx) { + return madd(1.0f/3.0f,irreg_patch.ring[index].getSecondLimitTangent(),p_vtx); + } + + __forceinline Vertex initPositiveEdgeVertex2(const CatmullClarkPatch& irreg_patch, const size_t index, const Vertex& p_vtx) + { + CatmullClark1Ring3fa r0,r1,r2; + irreg_patch.ring[index].subdivide(r0); + r0.subdivide(r1); + r1.subdivide(r2); + return madd(8.0f/3.0f,r2.getLimitTangent(),p_vtx); + } + + __forceinline Vertex initNegativeEdgeVertex2(const CatmullClarkPatch& irreg_patch, const size_t index, const Vertex& p_vtx) + { + CatmullClark1Ring3fa r0,r1,r2; + irreg_patch.ring[index].subdivide(r0); + r0.subdivide(r1); + r1.subdivide(r2); + return madd(8.0f/3.0f,r2.getSecondLimitTangent(),p_vtx); + } + + void initFaceVertex(const CatmullClarkPatch& irreg_patch, + const size_t index, + const Vertex& p_vtx, + const Vertex& e0_p_vtx, + const Vertex& e1_m_vtx, + const unsigned int face_valence_p1, + const Vertex& e0_m_vtx, + const Vertex& e3_p_vtx, + const unsigned int face_valence_p3, + Vertex& f_p_vtx, + Vertex& f_m_vtx) + { + const unsigned int face_valence = irreg_patch.ring[index].face_valence; + const unsigned int edge_valence = irreg_patch.ring[index].edge_valence; + const unsigned int border_index = irreg_patch.ring[index].border_index; + + const Vertex& vtx = irreg_patch.ring[index].vtx; + const Vertex e_i = irreg_patch.ring[index].getEdgeCenter(0); + const Vertex c_i_m_1 = irreg_patch.ring[index].getQuadCenter(0); + const Vertex e_i_m_1 = irreg_patch.ring[index].getEdgeCenter(1); + + Vertex c_i, e_i_p_1; + const bool hasHardEdge0 = + std::isinf(irreg_patch.ring[index].vertex_crease_weight) && + std::isinf(irreg_patch.ring[index].crease_weight[0]); + + if (unlikely((border_index == edge_valence-2) || hasHardEdge0)) + { + /* mirror quad center and edge mid-point */ + c_i = madd(2.0f, e_i - c_i_m_1, c_i_m_1); + e_i_p_1 = madd(2.0f, vtx - e_i_m_1, e_i_m_1); + } + else + { + c_i = irreg_patch.ring[index].getQuadCenter( face_valence-1 ); + e_i_p_1 = irreg_patch.ring[index].getEdgeCenter( face_valence-1 ); + } + + Vertex c_i_m_2, e_i_m_2; + const bool hasHardEdge1 = + std::isinf(irreg_patch.ring[index].vertex_crease_weight) && + std::isinf(irreg_patch.ring[index].crease_weight[1]); + + if (unlikely(border_index == 2 || hasHardEdge1)) + { + /* mirror quad center and edge mid-point */ + c_i_m_2 = madd(2.0f, e_i_m_1 - c_i_m_1, c_i_m_1); + e_i_m_2 = madd(2.0f, vtx - e_i, + e_i); + } + else + { + c_i_m_2 = irreg_patch.ring[index].getQuadCenter( 1 ); + e_i_m_2 = irreg_patch.ring[index].getEdgeCenter( 2 ); + } + + const float d = 3.0f; + //const float c = cosf(2.0f*M_PI/(float)face_valence); + //const float c_e_p = cosf(2.0f*M_PI/(float)face_valence_p1); + //const float c_e_m = cosf(2.0f*M_PI/(float)face_valence_p3); + + const float c = CatmullClarkPrecomputedCoefficients::table.cos_2PI_div_n(face_valence); + const float c_e_p = CatmullClarkPrecomputedCoefficients::table.cos_2PI_div_n(face_valence_p1); + const float c_e_m = CatmullClarkPrecomputedCoefficients::table.cos_2PI_div_n(face_valence_p3); + + const Vertex r_e_p = 1.0f/3.0f * (e_i_m_1 - e_i_p_1) + 2.0f/3.0f * (c_i_m_1 - c_i); + const Vertex r_e_m = 1.0f/3.0f * (e_i - e_i_m_2) + 2.0f/3.0f * (c_i_m_1 - c_i_m_2); + + f_p_vtx = 1.0f / d * (c_e_p * p_vtx + (d - 2.0f*c - c_e_p) * e0_p_vtx + 2.0f*c* e1_m_vtx + r_e_p); + f_m_vtx = 1.0f / d * (c_e_m * p_vtx + (d - 2.0f*c - c_e_m) * e0_m_vtx + 2.0f*c* e3_p_vtx + r_e_m); + } + + __noinline void init(const CatmullClarkPatch& patch) + { + assert( patch.ring[0].hasValidPositions() ); + assert( patch.ring[1].hasValidPositions() ); + assert( patch.ring[2].hasValidPositions() ); + assert( patch.ring[3].hasValidPositions() ); + + p0() = initCornerVertex(patch,0); + p1() = initCornerVertex(patch,1); + p2() = initCornerVertex(patch,2); + p3() = initCornerVertex(patch,3); + + e0_p() = initPositiveEdgeVertex(patch,0, p0()); + e1_p() = initPositiveEdgeVertex(patch,1, p1()); + e2_p() = initPositiveEdgeVertex(patch,2, p2()); + e3_p() = initPositiveEdgeVertex(patch,3, p3()); + + e0_m() = initNegativeEdgeVertex(patch,0, p0()); + e1_m() = initNegativeEdgeVertex(patch,1, p1()); + e2_m() = initNegativeEdgeVertex(patch,2, p2()); + e3_m() = initNegativeEdgeVertex(patch,3, p3()); + + const unsigned int face_valence_p0 = patch.ring[0].face_valence; + const unsigned int face_valence_p1 = patch.ring[1].face_valence; + const unsigned int face_valence_p2 = patch.ring[2].face_valence; + const unsigned int face_valence_p3 = patch.ring[3].face_valence; + + initFaceVertex(patch,0,p0(),e0_p(),e1_m(),face_valence_p1,e0_m(),e3_p(),face_valence_p3,f0_p(),f0_m() ); + initFaceVertex(patch,1,p1(),e1_p(),e2_m(),face_valence_p2,e1_m(),e0_p(),face_valence_p0,f1_p(),f1_m() ); + initFaceVertex(patch,2,p2(),e2_p(),e3_m(),face_valence_p3,e2_m(),e1_p(),face_valence_p1,f2_p(),f2_m() ); + initFaceVertex(patch,3,p3(),e3_p(),e0_m(),face_valence_p0,e3_m(),e2_p(),face_valence_p3,f3_p(),f3_m() ); + + } + + __noinline void init_crackfix(const CatmullClarkPatch& patch, + const BezierCurve* border0, + const BezierCurve* border1, + const BezierCurve* border2, + const BezierCurve* border3) + { + assert( patch.ring[0].hasValidPositions() ); + assert( patch.ring[1].hasValidPositions() ); + assert( patch.ring[2].hasValidPositions() ); + assert( patch.ring[3].hasValidPositions() ); + + p0() = initCornerVertex(patch,0); + p1() = initCornerVertex(patch,1); + p2() = initCornerVertex(patch,2); + p3() = initCornerVertex(patch,3); + + e0_p() = initPositiveEdgeVertex(patch,0, p0()); + e1_p() = initPositiveEdgeVertex(patch,1, p1()); + e2_p() = initPositiveEdgeVertex(patch,2, p2()); + e3_p() = initPositiveEdgeVertex(patch,3, p3()); + + e0_m() = initNegativeEdgeVertex(patch,0, p0()); + e1_m() = initNegativeEdgeVertex(patch,1, p1()); + e2_m() = initNegativeEdgeVertex(patch,2, p2()); + e3_m() = initNegativeEdgeVertex(patch,3, p3()); + + if (unlikely(border0 != nullptr)) + { + p0() = border0->v0; + e0_p() = border0->v1; + e1_m() = border0->v2; + p1() = border0->v3; + } + + if (unlikely(border1 != nullptr)) + { + p1() = border1->v0; + e1_p() = border1->v1; + e2_m() = border1->v2; + p2() = border1->v3; + } + + if (unlikely(border2 != nullptr)) + { + p2() = border2->v0; + e2_p() = border2->v1; + e3_m() = border2->v2; + p3() = border2->v3; + } + + if (unlikely(border3 != nullptr)) + { + p3() = border3->v0; + e3_p() = border3->v1; + e0_m() = border3->v2; + p0() = border3->v3; + } + + const unsigned int face_valence_p0 = patch.ring[0].face_valence; + const unsigned int face_valence_p1 = patch.ring[1].face_valence; + const unsigned int face_valence_p2 = patch.ring[2].face_valence; + const unsigned int face_valence_p3 = patch.ring[3].face_valence; + + initFaceVertex(patch,0,p0(),e0_p(),e1_m(),face_valence_p1,e0_m(),e3_p(),face_valence_p3,f0_p(),f0_m() ); + initFaceVertex(patch,1,p1(),e1_p(),e2_m(),face_valence_p2,e1_m(),e0_p(),face_valence_p0,f1_p(),f1_m() ); + initFaceVertex(patch,2,p2(),e2_p(),e3_m(),face_valence_p3,e2_m(),e1_p(),face_valence_p1,f2_p(),f2_m() ); + initFaceVertex(patch,3,p3(),e3_p(),e0_m(),face_valence_p0,e3_m(),e2_p(),face_valence_p3,f3_p(),f3_m() ); + } + + + void computeGregoryPatchFacePoints(const unsigned int face_valence, + const Vertex& r_e_p, + const Vertex& r_e_m, + const Vertex& p_vtx, + const Vertex& e0_p_vtx, + const Vertex& e1_m_vtx, + const unsigned int face_valence_p1, + const Vertex& e0_m_vtx, + const Vertex& e3_p_vtx, + const unsigned int face_valence_p3, + Vertex& f_p_vtx, + Vertex& f_m_vtx, + const float d = 3.0f) + { + //const float c = cosf(2.0*M_PI/(float)face_valence); + //const float c_e_p = cosf(2.0*M_PI/(float)face_valence_p1); + //const float c_e_m = cosf(2.0*M_PI/(float)face_valence_p3); + + const float c = CatmullClarkPrecomputedCoefficients::table.cos_2PI_div_n(face_valence); + const float c_e_p = CatmullClarkPrecomputedCoefficients::table.cos_2PI_div_n(face_valence_p1); + const float c_e_m = CatmullClarkPrecomputedCoefficients::table.cos_2PI_div_n(face_valence_p3); + + + f_p_vtx = 1.0f / d * (c_e_p * p_vtx + (d - 2.0f*c - c_e_p) * e0_p_vtx + 2.0f*c* e1_m_vtx + r_e_p); + f_m_vtx = 1.0f / d * (c_e_m * p_vtx + (d - 2.0f*c - c_e_m) * e0_m_vtx + 2.0f*c* e3_p_vtx + r_e_m); + f_p_vtx = 1.0f / d * (c_e_p * p_vtx + (d - 2.0f*c - c_e_p) * e0_p_vtx + 2.0f*c* e1_m_vtx + r_e_p); + f_m_vtx = 1.0f / d * (c_e_m * p_vtx + (d - 2.0f*c - c_e_m) * e0_m_vtx + 2.0f*c* e3_p_vtx + r_e_m); + } + + __noinline void init(const GeneralCatmullClarkPatch& patch) + { + assert(patch.size() == 4); +#if 0 + CatmullClarkPatch qpatch; patch.init(qpatch); + init(qpatch); +#else + const float face_valence_p0 = patch.ring[0].face_valence; + const float face_valence_p1 = patch.ring[1].face_valence; + const float face_valence_p2 = patch.ring[2].face_valence; + const float face_valence_p3 = patch.ring[3].face_valence; + + Vertex p0_r_p, p0_r_m; + patch.ring[0].computeGregoryPatchEdgePoints( p0(), e0_p(), e0_m(), p0_r_p, p0_r_m ); + + Vertex p1_r_p, p1_r_m; + patch.ring[1].computeGregoryPatchEdgePoints( p1(), e1_p(), e1_m(), p1_r_p, p1_r_m ); + + Vertex p2_r_p, p2_r_m; + patch.ring[2].computeGregoryPatchEdgePoints( p2(), e2_p(), e2_m(), p2_r_p, p2_r_m ); + + Vertex p3_r_p, p3_r_m; + patch.ring[3].computeGregoryPatchEdgePoints( p3(), e3_p(), e3_m(), p3_r_p, p3_r_m ); + + computeGregoryPatchFacePoints(face_valence_p0, p0_r_p, p0_r_m, p0(), e0_p(), e1_m(), face_valence_p1, e0_m(), e3_p(), face_valence_p3, f0_p(), f0_m() ); + computeGregoryPatchFacePoints(face_valence_p1, p1_r_p, p1_r_m, p1(), e1_p(), e2_m(), face_valence_p2, e1_m(), e0_p(), face_valence_p0, f1_p(), f1_m() ); + computeGregoryPatchFacePoints(face_valence_p2, p2_r_p, p2_r_m, p2(), e2_p(), e3_m(), face_valence_p3, e2_m(), e1_p(), face_valence_p1, f2_p(), f2_m() ); + computeGregoryPatchFacePoints(face_valence_p3, p3_r_p, p3_r_m, p3(), e3_p(), e0_m(), face_valence_p0, e3_m(), e2_p(), face_valence_p3, f3_p(), f3_m() ); + +#endif + } + + + __forceinline void convert_to_bezier() + { + f0_p() = (f0_p() + f0_m()) * 0.5f; + f1_p() = (f1_p() + f1_m()) * 0.5f; + f2_p() = (f2_p() + f2_m()) * 0.5f; + f3_p() = (f3_p() + f3_m()) * 0.5f; + f0_m() = Vertex( zero ); + f1_m() = Vertex( zero ); + f2_m() = Vertex( zero ); + f3_m() = Vertex( zero ); + } + + static __forceinline void computeInnerVertices(const Vertex matrix[4][4], const Vertex f_m[2][2], const float uu, const float vv, + Vertex_t& matrix_11, Vertex_t& matrix_12, Vertex_t& matrix_22, Vertex_t& matrix_21) + { + if (unlikely(uu == 0.0f || uu == 1.0f || vv == 0.0f || vv == 1.0f)) + { + matrix_11 = matrix[1][1]; + matrix_12 = matrix[1][2]; + matrix_22 = matrix[2][2]; + matrix_21 = matrix[2][1]; + } + else + { + const Vertex_t f0_p = matrix[1][1]; + const Vertex_t f1_p = matrix[1][2]; + const Vertex_t f2_p = matrix[2][2]; + const Vertex_t f3_p = matrix[2][1]; + + const Vertex_t f0_m = f_m[0][0]; + const Vertex_t f1_m = f_m[0][1]; + const Vertex_t f2_m = f_m[1][1]; + const Vertex_t f3_m = f_m[1][0]; + + matrix_11 = ( uu * f0_p + vv * f0_m)*rcp(uu+vv); + matrix_12 = ((1.0f-uu) * f1_m + vv * f1_p)*rcp(1.0f-uu+vv); + matrix_22 = ((1.0f-uu) * f2_p + (1.0f-vv) * f2_m)*rcp(2.0f-uu-vv); + matrix_21 = ( uu * f3_m + (1.0f-vv) * f3_p)*rcp(1.0f+uu-vv); + } + } + + template + static __forceinline void computeInnerVertices(const Vertex v[4][4], const Vertex f[2][2], + size_t i, const vfloat& uu, const vfloat& vv, vfloat& matrix_11, vfloat& matrix_12, vfloat& matrix_22, vfloat& matrix_21) + { + const auto m_border = (uu == 0.0f) | (uu == 1.0f) | (vv == 0.0f) | (vv == 1.0f); + + const vfloat f0_p = v[1][1][i]; + const vfloat f1_p = v[1][2][i]; + const vfloat f2_p = v[2][2][i]; + const vfloat f3_p = v[2][1][i]; + + const vfloat f0_m = f[0][0][i]; + const vfloat f1_m = f[0][1][i]; + const vfloat f2_m = f[1][1][i]; + const vfloat f3_m = f[1][0][i]; + + const vfloat one_minus_uu = vfloat(1.0f) - uu; + const vfloat one_minus_vv = vfloat(1.0f) - vv; + + const vfloat f0_i = ( uu * f0_p + vv * f0_m) * rcp(uu+vv); + const vfloat f1_i = (one_minus_uu * f1_m + vv * f1_p) * rcp(one_minus_uu+vv); + const vfloat f2_i = (one_minus_uu * f2_p + one_minus_vv * f2_m) * rcp(one_minus_uu+one_minus_vv); + const vfloat f3_i = ( uu * f3_m + one_minus_vv * f3_p) * rcp(uu+one_minus_vv); + + matrix_11 = select(m_border,f0_p,f0_i); + matrix_12 = select(m_border,f1_p,f1_i); + matrix_22 = select(m_border,f2_p,f2_i); + matrix_21 = select(m_border,f3_p,f3_i); + } + + static __forceinline Vertex eval(const Vertex matrix[4][4], const Vertex f[2][2], const float& uu, const float& vv) + { + Vertex_t v_11, v_12, v_22, v_21; + computeInnerVertices(matrix,f,uu,vv,v_11, v_12, v_22, v_21); + + const Vec4 Bu = BezierBasis::eval(uu); + const Vec4 Bv = BezierBasis::eval(vv); + + return madd(Bv.x,madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))), + madd(Bv.y,madd(Bu.x,matrix[1][0],madd(Bu.y,v_11 ,madd(Bu.z,v_12 ,Bu.w * matrix[1][3]))), + madd(Bv.z,madd(Bu.x,matrix[2][0],madd(Bu.y,v_21 ,madd(Bu.z,v_22 ,Bu.w * matrix[2][3]))), + Bv.w*madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3])))))); + } + + static __forceinline Vertex eval_du(const Vertex matrix[4][4], const Vertex f[2][2], const float uu, const float vv) // approximative derivative + { + Vertex_t v_11, v_12, v_22, v_21; + computeInnerVertices(matrix,f,uu,vv,v_11, v_12, v_22, v_21); + + const Vec4 Bu = BezierBasis::derivative(uu); + const Vec4 Bv = BezierBasis::eval(vv); + + return madd(Bv.x,madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))), + madd(Bv.y,madd(Bu.x,matrix[1][0],madd(Bu.y,v_11 ,madd(Bu.z,v_12 ,Bu.w * matrix[1][3]))), + madd(Bv.z,madd(Bu.x,matrix[2][0],madd(Bu.y,v_21 ,madd(Bu.z,v_22 ,Bu.w * matrix[2][3]))), + Bv.w*madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3])))))); + } + + static __forceinline Vertex eval_dv(const Vertex matrix[4][4], const Vertex f[2][2], const float uu, const float vv) // approximative derivative + { + Vertex_t v_11, v_12, v_22, v_21; + computeInnerVertices(matrix,f,uu,vv,v_11, v_12, v_22, v_21); + + const Vec4 Bu = BezierBasis::eval(uu); + const Vec4 Bv = BezierBasis::derivative(vv); + + return madd(Bv.x,madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))), + madd(Bv.y,madd(Bu.x,matrix[1][0],madd(Bu.y,v_11 ,madd(Bu.z,v_12 ,Bu.w * matrix[1][3]))), + madd(Bv.z,madd(Bu.x,matrix[2][0],madd(Bu.y,v_21 ,madd(Bu.z,v_22 ,Bu.w * matrix[2][3]))), + Bv.w*madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3])))))); + } + + static __forceinline Vertex eval_dudu(const Vertex matrix[4][4], const Vertex f[2][2], const float uu, const float vv) // approximative derivative + { + Vertex_t v_11, v_12, v_22, v_21; + computeInnerVertices(matrix,f,uu,vv,v_11, v_12, v_22, v_21); + + const Vec4 Bu = BezierBasis::derivative2(uu); + const Vec4 Bv = BezierBasis::eval(vv); + + return madd(Bv.x,madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))), + madd(Bv.y,madd(Bu.x,matrix[1][0],madd(Bu.y,v_11 ,madd(Bu.z,v_12 ,Bu.w * matrix[1][3]))), + madd(Bv.z,madd(Bu.x,matrix[2][0],madd(Bu.y,v_21 ,madd(Bu.z,v_22 ,Bu.w * matrix[2][3]))), + Bv.w*madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3])))))); + } + + static __forceinline Vertex eval_dvdv(const Vertex matrix[4][4], const Vertex f[2][2], const float uu, const float vv) // approximative derivative + { + Vertex_t v_11, v_12, v_22, v_21; + computeInnerVertices(matrix,f,uu,vv,v_11, v_12, v_22, v_21); + + const Vec4 Bu = BezierBasis::eval(uu); + const Vec4 Bv = BezierBasis::derivative2(vv); + + return madd(Bv.x,madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))), + madd(Bv.y,madd(Bu.x,matrix[1][0],madd(Bu.y,v_11 ,madd(Bu.z,v_12 ,Bu.w * matrix[1][3]))), + madd(Bv.z,madd(Bu.x,matrix[2][0],madd(Bu.y,v_21 ,madd(Bu.z,v_22 ,Bu.w * matrix[2][3]))), + Bv.w*madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3])))))); + } + + static __forceinline Vertex eval_dudv(const Vertex matrix[4][4], const Vertex f[2][2], const float uu, const float vv) // approximative derivative + { + Vertex_t v_11, v_12, v_22, v_21; + computeInnerVertices(matrix,f,uu,vv,v_11, v_12, v_22, v_21); + + const Vec4 Bu = BezierBasis::derivative(uu); + const Vec4 Bv = BezierBasis::derivative(vv); + + return madd(Bv.x,madd(Bu.x,matrix[0][0],madd(Bu.y,matrix[0][1],madd(Bu.z,matrix[0][2],Bu.w * matrix[0][3]))), + madd(Bv.y,madd(Bu.x,matrix[1][0],madd(Bu.y,v_11 ,madd(Bu.z,v_12 ,Bu.w * matrix[1][3]))), + madd(Bv.z,madd(Bu.x,matrix[2][0],madd(Bu.y,v_21 ,madd(Bu.z,v_22 ,Bu.w * matrix[2][3]))), + Bv.w*madd(Bu.x,matrix[3][0],madd(Bu.y,matrix[3][1],madd(Bu.z,matrix[3][2],Bu.w * matrix[3][3])))))); + } + + __forceinline Vertex eval(const float uu, const float vv) const { + return eval(v,f,uu,vv); + } + + __forceinline Vertex eval_du( const float uu, const float vv) const { + return eval_du(v,f,uu,vv); + } + + __forceinline Vertex eval_dv( const float uu, const float vv) const { + return eval_dv(v,f,uu,vv); + } + + __forceinline Vertex eval_dudu( const float uu, const float vv) const { + return eval_dudu(v,f,uu,vv); + } + + __forceinline Vertex eval_dvdv( const float uu, const float vv) const { + return eval_dvdv(v,f,uu,vv); + } + + __forceinline Vertex eval_dudv( const float uu, const float vv) const { + return eval_dudv(v,f,uu,vv); + } + + static __forceinline Vertex normal(const Vertex matrix[4][4], const Vertex f_m[2][2], const float uu, const float vv) // FIXME: why not using basis functions + { + /* interpolate inner vertices */ + Vertex_t matrix_11, matrix_12, matrix_22, matrix_21; + computeInnerVertices(matrix,f_m,uu,vv,matrix_11, matrix_12, matrix_22, matrix_21); + + /* tangentU */ + const Vertex_t col0 = deCasteljau(vv, (Vertex_t)matrix[0][0], (Vertex_t)matrix[1][0], (Vertex_t)matrix[2][0], (Vertex_t)matrix[3][0]); + const Vertex_t col1 = deCasteljau(vv, (Vertex_t)matrix[0][1], (Vertex_t)matrix_11 , (Vertex_t)matrix_21 , (Vertex_t)matrix[3][1]); + const Vertex_t col2 = deCasteljau(vv, (Vertex_t)matrix[0][2], (Vertex_t)matrix_12 , (Vertex_t)matrix_22 , (Vertex_t)matrix[3][2]); + const Vertex_t col3 = deCasteljau(vv, (Vertex_t)matrix[0][3], (Vertex_t)matrix[1][3], (Vertex_t)matrix[2][3], (Vertex_t)matrix[3][3]); + + const Vertex_t tangentU = deCasteljau_tangent(uu, col0, col1, col2, col3); + + /* tangentV */ + const Vertex_t row0 = deCasteljau(uu, (Vertex_t)matrix[0][0], (Vertex_t)matrix[0][1], (Vertex_t)matrix[0][2], (Vertex_t)matrix[0][3]); + const Vertex_t row1 = deCasteljau(uu, (Vertex_t)matrix[1][0], (Vertex_t)matrix_11 , (Vertex_t)matrix_12 , (Vertex_t)matrix[1][3]); + const Vertex_t row2 = deCasteljau(uu, (Vertex_t)matrix[2][0], (Vertex_t)matrix_21 , (Vertex_t)matrix_22 , (Vertex_t)matrix[2][3]); + const Vertex_t row3 = deCasteljau(uu, (Vertex_t)matrix[3][0], (Vertex_t)matrix[3][1], (Vertex_t)matrix[3][2], (Vertex_t)matrix[3][3]); + + const Vertex_t tangentV = deCasteljau_tangent(vv, row0, row1, row2, row3); + + /* normal = tangentU x tangentV */ + const Vertex_t n = cross(tangentU,tangentV); + + return n; + } + + __forceinline Vertex normal( const float uu, const float vv) const { + return normal(v,f,uu,vv); + } + + __forceinline void eval(const float u, const float v, + Vertex* P, Vertex* dPdu, Vertex* dPdv, + Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv, + const float dscale = 1.0f) const + { + if (P) { + *P = eval(u,v); + } + if (dPdu) { + assert(dPdu); *dPdu = eval_du(u,v)*dscale; + assert(dPdv); *dPdv = eval_dv(u,v)*dscale; + } + if (ddPdudu) { + assert(ddPdudu); *ddPdudu = eval_dudu(u,v)*sqr(dscale); + assert(ddPdvdv); *ddPdvdv = eval_dvdv(u,v)*sqr(dscale); + assert(ddPdudv); *ddPdudv = eval_dudv(u,v)*sqr(dscale); + } + } + + template + static __forceinline vfloat eval(const Vertex v[4][4], const Vertex f[2][2], + const size_t i, const vfloat& uu, const vfloat& vv, const Vec4& u_n, const Vec4& v_n, + vfloat& matrix_11, vfloat& matrix_12, vfloat& matrix_22, vfloat& matrix_21) + { + const vfloat curve0_x = madd(v_n[0],vfloat(v[0][0][i]),madd(v_n[1],vfloat(v[1][0][i]),madd(v_n[2],vfloat(v[2][0][i]),v_n[3] * vfloat(v[3][0][i])))); + const vfloat curve1_x = madd(v_n[0],vfloat(v[0][1][i]),madd(v_n[1],vfloat(matrix_11 ),madd(v_n[2],vfloat(matrix_21 ),v_n[3] * vfloat(v[3][1][i])))); + const vfloat curve2_x = madd(v_n[0],vfloat(v[0][2][i]),madd(v_n[1],vfloat(matrix_12 ),madd(v_n[2],vfloat(matrix_22 ),v_n[3] * vfloat(v[3][2][i])))); + const vfloat curve3_x = madd(v_n[0],vfloat(v[0][3][i]),madd(v_n[1],vfloat(v[1][3][i]),madd(v_n[2],vfloat(v[2][3][i]),v_n[3] * vfloat(v[3][3][i])))); + return madd(u_n[0],curve0_x,madd(u_n[1],curve1_x,madd(u_n[2],curve2_x,u_n[3] * curve3_x))); + } + + template + static __forceinline void eval(const Vertex v[4][4], const Vertex f[2][2], + const vbool& valid, const vfloat& uu, const vfloat& vv, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, + const float dscale, const size_t dstride, const size_t N) + { + if (P) { + const Vec4 u_n = BezierBasis::eval(uu); + const Vec4 v_n = BezierBasis::eval(vv); + for (size_t i=0; i u_n = BezierBasis::derivative(uu); + const Vec4 v_n = BezierBasis::eval(vv); + for (size_t i=0; i u_n = BezierBasis::eval(uu); + const Vec4 v_n = BezierBasis::derivative(vv); + for (size_t i=0; i u_n = BezierBasis::derivative2(uu); + const Vec4 v_n = BezierBasis::eval(vv); + for (size_t i=0; i u_n = BezierBasis::eval(uu); + const Vec4 v_n = BezierBasis::derivative2(vv); + for (size_t i=0; i u_n = BezierBasis::derivative(uu); + const Vec4 v_n = BezierBasis::derivative(vv); + for (size_t i=0; i + __forceinline void eval(const vbool& valid, const vfloat& uu, const vfloat& vv, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, + const float dscale, const size_t dstride, const size_t N) const { + eval(v,f,valid,uu,vv,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + } + + template + static __forceinline Vec3 eval_t(const Vertex matrix[4][4], const Vec3 f[2][2], const T& uu, const T& vv) + { + typedef typename T::Bool M; + const M m_border = (uu == 0.0f) | (uu == 1.0f) | (vv == 0.0f) | (vv == 1.0f); + + const Vec3 f0_p = Vec3(matrix[1][1].x,matrix[1][1].y,matrix[1][1].z); + const Vec3 f1_p = Vec3(matrix[1][2].x,matrix[1][2].y,matrix[1][2].z); + const Vec3 f2_p = Vec3(matrix[2][2].x,matrix[2][2].y,matrix[2][2].z); + const Vec3 f3_p = Vec3(matrix[2][1].x,matrix[2][1].y,matrix[2][1].z); + + const Vec3 f0_m = f[0][0]; + const Vec3 f1_m = f[0][1]; + const Vec3 f2_m = f[1][1]; + const Vec3 f3_m = f[1][0]; + + const T one_minus_uu = T(1.0f) - uu; + const T one_minus_vv = T(1.0f) - vv; + + const Vec3 f0_i = ( uu * f0_p + vv * f0_m) * rcp(uu+vv); + const Vec3 f1_i = (one_minus_uu * f1_m + vv * f1_p) * rcp(one_minus_uu+vv); + const Vec3 f2_i = (one_minus_uu * f2_p + one_minus_vv * f2_m) * rcp(one_minus_uu+one_minus_vv); + const Vec3 f3_i = ( uu * f3_m + one_minus_vv * f3_p) * rcp(uu+one_minus_vv); + + const Vec3 F0( select(m_border,f0_p.x,f0_i.x), select(m_border,f0_p.y,f0_i.y), select(m_border,f0_p.z,f0_i.z) ); + const Vec3 F1( select(m_border,f1_p.x,f1_i.x), select(m_border,f1_p.y,f1_i.y), select(m_border,f1_p.z,f1_i.z) ); + const Vec3 F2( select(m_border,f2_p.x,f2_i.x), select(m_border,f2_p.y,f2_i.y), select(m_border,f2_p.z,f2_i.z) ); + const Vec3 F3( select(m_border,f3_p.x,f3_i.x), select(m_border,f3_p.y,f3_i.y), select(m_border,f3_p.z,f3_i.z) ); + + const T B0_u = one_minus_uu * one_minus_uu * one_minus_uu; + const T B0_v = one_minus_vv * one_minus_vv * one_minus_vv; + const T B1_u = 3.0f * (one_minus_uu * uu * one_minus_uu); + const T B1_v = 3.0f * (one_minus_vv * vv * one_minus_vv); + const T B2_u = 3.0f * (uu * one_minus_uu * uu); + const T B2_v = 3.0f * (vv * one_minus_vv * vv); + const T B3_u = uu * uu * uu; + const T B3_v = vv * vv * vv; + + const T x = madd(B0_v,madd(B0_u,matrix[0][0].x,madd(B1_u,matrix[0][1].x,madd(B2_u,matrix[0][2].x,B3_u * matrix[0][3].x))), + madd(B1_v,madd(B0_u,matrix[1][0].x,madd(B1_u,F0.x ,madd(B2_u,F1.x ,B3_u * matrix[1][3].x))), + madd(B2_v,madd(B0_u,matrix[2][0].x,madd(B1_u,F3.x ,madd(B2_u,F2.x ,B3_u * matrix[2][3].x))), + B3_v*madd(B0_u,matrix[3][0].x,madd(B1_u,matrix[3][1].x,madd(B2_u,matrix[3][2].x,B3_u * matrix[3][3].x)))))); + + const T y = madd(B0_v,madd(B0_u,matrix[0][0].y,madd(B1_u,matrix[0][1].y,madd(B2_u,matrix[0][2].y,B3_u * matrix[0][3].y))), + madd(B1_v,madd(B0_u,matrix[1][0].y,madd(B1_u,F0.y ,madd(B2_u,F1.y ,B3_u * matrix[1][3].y))), + madd(B2_v,madd(B0_u,matrix[2][0].y,madd(B1_u,F3.y ,madd(B2_u,F2.y ,B3_u * matrix[2][3].y))), + B3_v*madd(B0_u,matrix[3][0].y,madd(B1_u,matrix[3][1].y,madd(B2_u,matrix[3][2].y,B3_u * matrix[3][3].y)))))); + + const T z = madd(B0_v,madd(B0_u,matrix[0][0].z,madd(B1_u,matrix[0][1].z,madd(B2_u,matrix[0][2].z,B3_u * matrix[0][3].z))), + madd(B1_v,madd(B0_u,matrix[1][0].z,madd(B1_u,F0.z ,madd(B2_u,F1.z ,B3_u * matrix[1][3].z))), + madd(B2_v,madd(B0_u,matrix[2][0].z,madd(B1_u,F3.z ,madd(B2_u,F2.z ,B3_u * matrix[2][3].z))), + B3_v*madd(B0_u,matrix[3][0].z,madd(B1_u,matrix[3][1].z,madd(B2_u,matrix[3][2].z,B3_u * matrix[3][3].z)))))); + + return Vec3(x,y,z); + } + + template + __forceinline Vec3 eval(const T& uu, const T& vv) const + { + Vec3 ff[2][2]; + ff[0][0] = Vec3(f[0][0]); + ff[0][1] = Vec3(f[0][1]); + ff[1][1] = Vec3(f[1][1]); + ff[1][0] = Vec3(f[1][0]); + return eval_t(v,ff,uu,vv); + } + + template + static __forceinline Vec3 normal_t(const Vertex matrix[4][4], const Vec3 f[2][2], const T& uu, const T& vv) + { + typedef typename T::Bool M; + + const Vec3 f0_p = Vec3(matrix[1][1].x,matrix[1][1].y,matrix[1][1].z); + const Vec3 f1_p = Vec3(matrix[1][2].x,matrix[1][2].y,matrix[1][2].z); + const Vec3 f2_p = Vec3(matrix[2][2].x,matrix[2][2].y,matrix[2][2].z); + const Vec3 f3_p = Vec3(matrix[2][1].x,matrix[2][1].y,matrix[2][1].z); + + const Vec3 f0_m = f[0][0]; + const Vec3 f1_m = f[0][1]; + const Vec3 f2_m = f[1][1]; + const Vec3 f3_m = f[1][0]; + + const T one_minus_uu = T(1.0f) - uu; + const T one_minus_vv = T(1.0f) - vv; + + const Vec3 f0_i = ( uu * f0_p + vv * f0_m) * rcp(uu+vv); + const Vec3 f1_i = (one_minus_uu * f1_m + vv * f1_p) * rcp(one_minus_uu+vv); + const Vec3 f2_i = (one_minus_uu * f2_p + one_minus_vv * f2_m) * rcp(one_minus_uu+one_minus_vv); + const Vec3 f3_i = ( uu * f3_m + one_minus_vv * f3_p) * rcp(uu+one_minus_vv); + +#if 1 + const M m_corner0 = (uu == 0.0f) & (vv == 0.0f); + const M m_corner1 = (uu == 1.0f) & (vv == 0.0f); + const M m_corner2 = (uu == 1.0f) & (vv == 1.0f); + const M m_corner3 = (uu == 0.0f) & (vv == 1.0f); + const Vec3 matrix_11( select(m_corner0,f0_p.x,f0_i.x), select(m_corner0,f0_p.y,f0_i.y), select(m_corner0,f0_p.z,f0_i.z) ); + const Vec3 matrix_12( select(m_corner1,f1_p.x,f1_i.x), select(m_corner1,f1_p.y,f1_i.y), select(m_corner1,f1_p.z,f1_i.z) ); + const Vec3 matrix_22( select(m_corner2,f2_p.x,f2_i.x), select(m_corner2,f2_p.y,f2_i.y), select(m_corner2,f2_p.z,f2_i.z) ); + const Vec3 matrix_21( select(m_corner3,f3_p.x,f3_i.x), select(m_corner3,f3_p.y,f3_i.y), select(m_corner3,f3_p.z,f3_i.z) ); +#else + const M m_border = (uu == 0.0f) | (uu == 1.0f) | (vv == 0.0f) | (vv == 1.0f); + const Vec3 matrix_11( select(m_border,f0_p.x,f0_i.x), select(m_border,f0_p.y,f0_i.y), select(m_border,f0_p.z,f0_i.z) ); + const Vec3 matrix_12( select(m_border,f1_p.x,f1_i.x), select(m_border,f1_p.y,f1_i.y), select(m_border,f1_p.z,f1_i.z) ); + const Vec3 matrix_22( select(m_border,f2_p.x,f2_i.x), select(m_border,f2_p.y,f2_i.y), select(m_border,f2_p.z,f2_i.z) ); + const Vec3 matrix_21( select(m_border,f3_p.x,f3_i.x), select(m_border,f3_p.y,f3_i.y), select(m_border,f3_p.z,f3_i.z) ); +#endif + + const Vec3 matrix_00 = Vec3(matrix[0][0].x,matrix[0][0].y,matrix[0][0].z); + const Vec3 matrix_10 = Vec3(matrix[1][0].x,matrix[1][0].y,matrix[1][0].z); + const Vec3 matrix_20 = Vec3(matrix[2][0].x,matrix[2][0].y,matrix[2][0].z); + const Vec3 matrix_30 = Vec3(matrix[3][0].x,matrix[3][0].y,matrix[3][0].z); + + const Vec3 matrix_01 = Vec3(matrix[0][1].x,matrix[0][1].y,matrix[0][1].z); + const Vec3 matrix_02 = Vec3(matrix[0][2].x,matrix[0][2].y,matrix[0][2].z); + const Vec3 matrix_03 = Vec3(matrix[0][3].x,matrix[0][3].y,matrix[0][3].z); + + const Vec3 matrix_31 = Vec3(matrix[3][1].x,matrix[3][1].y,matrix[3][1].z); + const Vec3 matrix_32 = Vec3(matrix[3][2].x,matrix[3][2].y,matrix[3][2].z); + const Vec3 matrix_33 = Vec3(matrix[3][3].x,matrix[3][3].y,matrix[3][3].z); + + const Vec3 matrix_13 = Vec3(matrix[1][3].x,matrix[1][3].y,matrix[1][3].z); + const Vec3 matrix_23 = Vec3(matrix[2][3].x,matrix[2][3].y,matrix[2][3].z); + + /* tangentU */ + const Vec3 col0 = deCasteljau(vv, matrix_00, matrix_10, matrix_20, matrix_30); + const Vec3 col1 = deCasteljau(vv, matrix_01, matrix_11, matrix_21, matrix_31); + const Vec3 col2 = deCasteljau(vv, matrix_02, matrix_12, matrix_22, matrix_32); + const Vec3 col3 = deCasteljau(vv, matrix_03, matrix_13, matrix_23, matrix_33); + + const Vec3 tangentU = deCasteljau_tangent(uu, col0, col1, col2, col3); + + /* tangentV */ + const Vec3 row0 = deCasteljau(uu, matrix_00, matrix_01, matrix_02, matrix_03); + const Vec3 row1 = deCasteljau(uu, matrix_10, matrix_11, matrix_12, matrix_13); + const Vec3 row2 = deCasteljau(uu, matrix_20, matrix_21, matrix_22, matrix_23); + const Vec3 row3 = deCasteljau(uu, matrix_30, matrix_31, matrix_32, matrix_33); + + const Vec3 tangentV = deCasteljau_tangent(vv, row0, row1, row2, row3); + + /* normal = tangentU x tangentV */ + const Vec3 n = cross(tangentU,tangentV); + return n; + } + + template + __forceinline Vec3 normal(const T& uu, const T& vv) const + { + Vec3 ff[2][2]; + ff[0][0] = Vec3(f[0][0]); + ff[0][1] = Vec3(f[0][1]); + ff[1][1] = Vec3(f[1][1]); + ff[1][0] = Vec3(f[1][0]); + return normal_t(v,ff,uu,vv); + } + + __forceinline BBox bounds() const + { + const Vertex *const cv = &v[0][0]; + BBox bounds (cv[0]); + for (size_t i=1; i<16; i++) + bounds.extend( cv[i] ); + bounds.extend(f[0][0]); + bounds.extend(f[1][0]); + bounds.extend(f[1][1]); + bounds.extend(f[1][1]); + return bounds; + } + + friend embree_ostream operator<<(embree_ostream o, const GregoryPatchT& p) + { + for (size_t y=0; y<4; y++) + for (size_t x=0; x<4; x++) + o << "v[" << y << "][" << x << "] " << p.v[y][x] << embree_endl; + + for (size_t y=0; y<2; y++) + for (size_t x=0; x<2; x++) + o << "f[" << y << "][" << x << "] " << p.f[y][x] << embree_endl; + return o; + } + }; + + typedef GregoryPatchT GregoryPatch3fa; + + template + __forceinline BezierPatchT::BezierPatchT (const HalfEdge* edge, const char* vertices, size_t stride) + { + CatmullClarkPatchT patch(edge,vertices,stride); + GregoryPatchT gpatch(patch); + gpatch.convert_to_bezier(); + for (size_t y=0; y<4; y++) + for (size_t x=0; x<4; x++) + matrix[y][x] = (Vertex_t)gpatch.v[y][x]; + } + + template + __forceinline BezierPatchT::BezierPatchT(const CatmullClarkPatchT& patch) + { + GregoryPatchT gpatch(patch); + gpatch.convert_to_bezier(); + for (size_t y=0; y<4; y++) + for (size_t x=0; x<4; x++) + matrix[y][x] = (Vertex_t)gpatch.v[y][x]; + } + + template + __forceinline BezierPatchT::BezierPatchT(const CatmullClarkPatchT& patch, + const BezierCurveT* border0, + const BezierCurveT* border1, + const BezierCurveT* border2, + const BezierCurveT* border3) + { + GregoryPatchT gpatch(patch,border0,border1,border2,border3); + gpatch.convert_to_bezier(); + for (size_t y=0; y<4; y++) + for (size_t x=0; x<4; x++) + matrix[y][x] = (Vertex_t)gpatch.v[y][x]; + } +} diff --git a/thirdparty/embree/kernels/subdiv/gregory_patch_dense.h b/thirdparty/embree/kernels/subdiv/gregory_patch_dense.h new file mode 100644 index 000000000000..85effd02cfb3 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/gregory_patch_dense.h @@ -0,0 +1,113 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "gregory_patch.h" + +namespace embree +{ + class __aligned(64) DenseGregoryPatch3fa + { + typedef Vec3fa Vec3fa_4x4[4][4]; + public: + + __forceinline DenseGregoryPatch3fa (const GregoryPatch3fa& patch) + { + for (size_t y=0; y<4; y++) + for (size_t x=0; x<4; x++) + matrix[y][x] = Vec3ff(patch.v[y][x], 0.0f); + + matrix[0][0].w = patch.f[0][0].x; + matrix[0][1].w = patch.f[0][0].y; + matrix[0][2].w = patch.f[0][0].z; + matrix[0][3].w = 0.0f; + + matrix[1][0].w = patch.f[0][1].x; + matrix[1][1].w = patch.f[0][1].y; + matrix[1][2].w = patch.f[0][1].z; + matrix[1][3].w = 0.0f; + + matrix[2][0].w = patch.f[1][1].x; + matrix[2][1].w = patch.f[1][1].y; + matrix[2][2].w = patch.f[1][1].z; + matrix[2][3].w = 0.0f; + + matrix[3][0].w = patch.f[1][0].x; + matrix[3][1].w = patch.f[1][0].y; + matrix[3][2].w = patch.f[1][0].z; + matrix[3][3].w = 0.0f; + } + + __forceinline void extract_f_m(Vec3fa f_m[2][2]) const + { + f_m[0][0] = Vec3fa( matrix[0][0].w, matrix[0][1].w, matrix[0][2].w ); + f_m[0][1] = Vec3fa( matrix[1][0].w, matrix[1][1].w, matrix[1][2].w ); + f_m[1][1] = Vec3fa( matrix[2][0].w, matrix[2][1].w, matrix[2][2].w ); + f_m[1][0] = Vec3fa( matrix[3][0].w, matrix[3][1].w, matrix[3][2].w ); + } + + __forceinline Vec3fa eval(const float uu, const float vv) const + { + __aligned(64) Vec3fa f_m[2][2]; extract_f_m(f_m); + return GregoryPatch3fa::eval(*(Vec3fa_4x4*)&matrix,f_m,uu,vv); + } + + __forceinline Vec3fa normal(const float uu, const float vv) const + { + __aligned(64) Vec3fa f_m[2][2]; extract_f_m(f_m); + return GregoryPatch3fa::normal(*(Vec3fa_4x4*)&matrix,f_m,uu,vv); + } + + template + __forceinline Vec3 eval(const T &uu, const T &vv) const + { + Vec3 f_m[2][2]; + f_m[0][0] = Vec3( matrix[0][0].w, matrix[0][1].w, matrix[0][2].w ); + f_m[0][1] = Vec3( matrix[1][0].w, matrix[1][1].w, matrix[1][2].w ); + f_m[1][1] = Vec3( matrix[2][0].w, matrix[2][1].w, matrix[2][2].w ); + f_m[1][0] = Vec3( matrix[3][0].w, matrix[3][1].w, matrix[3][2].w ); + return GregoryPatch3fa::eval_t(*(Vec3fa_4x4*)&matrix,f_m,uu,vv); + } + + template + __forceinline Vec3 normal(const T &uu, const T &vv) const + { + Vec3 f_m[2][2]; + f_m[0][0] = Vec3( matrix[0][0].w, matrix[0][1].w, matrix[0][2].w ); + f_m[0][1] = Vec3( matrix[1][0].w, matrix[1][1].w, matrix[1][2].w ); + f_m[1][1] = Vec3( matrix[2][0].w, matrix[2][1].w, matrix[2][2].w ); + f_m[1][0] = Vec3( matrix[3][0].w, matrix[3][1].w, matrix[3][2].w ); + return GregoryPatch3fa::normal_t(*(Vec3fa_4x4*)&matrix,f_m,uu,vv); + } + + __forceinline void eval(const float u, const float v, + Vec3fa* P, Vec3fa* dPdu, Vec3fa* dPdv, Vec3fa* ddPdudu, Vec3fa* ddPdvdv, Vec3fa* ddPdudv, + const float dscale = 1.0f) const + { + __aligned(64) Vec3fa f_m[2][2]; extract_f_m(f_m); + if (P) { + *P = GregoryPatch3fa::eval(*(Vec3fa_4x4*)&matrix,f_m,u,v); + } + if (dPdu) { + assert(dPdu); *dPdu = GregoryPatch3fa::eval_du(*(Vec3fa_4x4*)&matrix,f_m,u,v)*dscale; + assert(dPdv); *dPdv = GregoryPatch3fa::eval_dv(*(Vec3fa_4x4*)&matrix,f_m,u,v)*dscale; + } + if (ddPdudu) { + assert(ddPdudu); *ddPdudu = GregoryPatch3fa::eval_dudu(*(Vec3fa_4x4*)&matrix,f_m,u,v)*sqr(dscale); + assert(ddPdvdv); *ddPdvdv = GregoryPatch3fa::eval_dvdv(*(Vec3fa_4x4*)&matrix,f_m,u,v)*sqr(dscale); + assert(ddPdudv); *ddPdudv = GregoryPatch3fa::eval_dudv(*(Vec3fa_4x4*)&matrix,f_m,u,v)*sqr(dscale); + } + } + + template + __forceinline void eval(const vbool& valid, const vfloat& uu, const vfloat& vv, float* P, float* dPdu, float* dPdv, const float dscale, const size_t dstride, const size_t N) const + { + __aligned(64) Vec3fa f_m[2][2]; extract_f_m(f_m); + GregoryPatch3fa::eval(matrix,f_m,valid,uu,vv,P,dPdu,dPdv,dscale,dstride,N); + } + + private: + Vec3ff matrix[4][4]; // f_p/m points are stored in 4th component + }; +} diff --git a/thirdparty/embree/kernels/subdiv/gridrange.h b/thirdparty/embree/kernels/subdiv/gridrange.h new file mode 100644 index 000000000000..4fd741c8790a --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/gridrange.h @@ -0,0 +1,96 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" + +namespace embree +{ + struct __aligned(16) GridRange + { + unsigned int u_start; + unsigned int u_end; + unsigned int v_start; + unsigned int v_end; + + __forceinline GridRange() {} + + __forceinline GridRange(unsigned int u_start, unsigned int u_end, unsigned int v_start, unsigned int v_end) + : u_start(u_start), u_end(u_end), v_start(v_start), v_end(v_end) {} + + __forceinline unsigned int width() const { + return u_end-u_start+1; + } + + __forceinline unsigned int height() const { + return v_end-v_start+1; + } + + __forceinline bool hasLeafSize() const + { + const unsigned int u_size = u_end-u_start+1; + const unsigned int v_size = v_end-v_start+1; + assert(u_size >= 1); + assert(v_size >= 1); + return u_size <= 3 && v_size <= 3; + } + + static __forceinline unsigned int split(unsigned int start,unsigned int end) + { + const unsigned int center = (start+end)/2; + assert (center > start); + assert (center < end); + return center; + } + + __forceinline void split(GridRange& r0, GridRange& r1) const + { + assert( hasLeafSize() == false ); + const unsigned int u_size = u_end-u_start+1; + const unsigned int v_size = v_end-v_start+1; + r0 = *this; + r1 = *this; + + if (u_size >= v_size) + { + const unsigned int u_mid = split(u_start,u_end); + r0.u_end = u_mid; + r1.u_start = u_mid; + } + else + { + const unsigned int v_mid = split(v_start,v_end); + r0.v_end = v_mid; + r1.v_start = v_mid; + } + } + + __forceinline unsigned int splitIntoSubRanges(GridRange r[4]) const + { + assert( !hasLeafSize() ); + unsigned int children = 0; + GridRange first,second; + split(first,second); + + if (first.hasLeafSize()) { + r[0] = first; + children++; + } + else { + first.split(r[0],r[1]); + children += 2; + } + + if (second.hasLeafSize()) { + r[children] = second; + children++; + } + else { + second.split(r[children+0],r[children+1]); + children += 2; + } + return children; + } + }; +} diff --git a/thirdparty/embree/kernels/subdiv/half_edge.h b/thirdparty/embree/kernels/subdiv/half_edge.h new file mode 100644 index 000000000000..fb350ca71fe3 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/half_edge.h @@ -0,0 +1,371 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_coefficients.h" + +namespace embree +{ + class __aligned(32) HalfEdge + { + friend class SubdivMesh; + public: + + enum PatchType : char { + BILINEAR_PATCH = 0, //!< a bilinear patch + REGULAR_QUAD_PATCH = 1, //!< a regular quad patch can be represented as a B-Spline + IRREGULAR_QUAD_PATCH = 2, //!< an irregular quad patch can be represented as a Gregory patch + COMPLEX_PATCH = 3 //!< these patches need subdivision and cannot be processed by the above fast code paths + }; + + enum VertexType : char { + REGULAR_VERTEX = 0, //!< regular vertex + NON_MANIFOLD_EDGE_VERTEX = 1, //!< vertex of a non-manifold edge + }; + + __forceinline friend PatchType max( const PatchType& ty0, const PatchType& ty1) { + return (PatchType) max((int)ty0,(int)ty1); + } + + struct Edge + { + /*! edge constructor */ + __forceinline Edge(const uint32_t v0, const uint32_t v1) + : v0(v0), v1(v1) {} + + /*! create an 64 bit identifier that is unique for the not oriented edge */ + __forceinline operator uint64_t() const + { + uint32_t p0 = v0, p1 = v1; + if (p0next(); } + __forceinline const HalfEdge* rotate() const { return opposite()->next(); } + + __forceinline unsigned int getStartVertexIndex() const { return vtx_index; } + __forceinline unsigned int getEndVertexIndex () const { return next()->vtx_index; } + __forceinline Edge getEdge () const { return Edge(getStartVertexIndex(),getEndVertexIndex()); } + + + /*! tests if the start vertex of the edge is regular */ + __forceinline PatchType vertexType() const + { + const HalfEdge* p = this; + size_t face_valence = 0; + bool hasBorder = false; + + do + { + /* we need subdivision to handle edge creases */ + if (p->hasOpposite() && p->edge_crease_weight > 0.0f) + return COMPLEX_PATCH; + + face_valence++; + + /* test for quad */ + const HalfEdge* pp = p; + pp = pp->next(); if (pp == p) return COMPLEX_PATCH; + pp = pp->next(); if (pp == p) return COMPLEX_PATCH; + pp = pp->next(); if (pp == p) return COMPLEX_PATCH; + pp = pp->next(); if (pp != p) return COMPLEX_PATCH; + + /* continue with next face */ + p = p->prev(); + if (likely(p->hasOpposite())) + p = p->opposite(); + + /* if there is no opposite go the long way to the other side of the border */ + else + { + face_valence++; + hasBorder = true; + p = this; + while (p->hasOpposite()) + p = p->rotate(); + } + } while (p != this); + + /* calculate vertex type */ + if (face_valence == 2 && hasBorder) { + if (vertex_crease_weight == 0.0f ) return REGULAR_QUAD_PATCH; + else if (vertex_crease_weight == float(inf)) return REGULAR_QUAD_PATCH; + else return COMPLEX_PATCH; + } + else if (vertex_crease_weight != 0.0f) return COMPLEX_PATCH; + else if (face_valence == 3 && hasBorder) return REGULAR_QUAD_PATCH; + else if (face_valence == 4 && !hasBorder) return REGULAR_QUAD_PATCH; + else return IRREGULAR_QUAD_PATCH; + } + + /*! tests if this edge is part of a bilinear patch */ + __forceinline bool bilinearVertex() const { + return vertex_crease_weight == float(inf) && edge_crease_weight == float(inf); + } + + /*! calculates the type of the patch */ + __forceinline PatchType patchType() const + { + const HalfEdge* p = this; + PatchType ret = REGULAR_QUAD_PATCH; + bool bilinear = true; + + ret = max(ret,p->vertexType()); + bilinear &= p->bilinearVertex(); + if ((p = p->next()) == this) return COMPLEX_PATCH; + + ret = max(ret,p->vertexType()); + bilinear &= p->bilinearVertex(); + if ((p = p->next()) == this) return COMPLEX_PATCH; + + ret = max(ret,p->vertexType()); + bilinear &= p->bilinearVertex(); + if ((p = p->next()) == this) return COMPLEX_PATCH; + + ret = max(ret,p->vertexType()); + bilinear &= p->bilinearVertex(); + if ((p = p->next()) != this) return COMPLEX_PATCH; + + if (bilinear) return BILINEAR_PATCH; + return ret; + } + + /*! tests if the face is a regular b-spline face */ + __forceinline bool isRegularFace() const { + return patch_type == REGULAR_QUAD_PATCH; + } + + /*! tests if the face can be diced (using bspline or gregory patch) */ + __forceinline bool isGregoryFace() const { + return patch_type == IRREGULAR_QUAD_PATCH || patch_type == REGULAR_QUAD_PATCH; + } + + /*! tests if the base vertex of this half edge is a corner vertex */ + __forceinline bool isCorner() const { + return !hasOpposite() && !prev()->hasOpposite(); + } + + /*! tests if the vertex is attached to any border */ + __forceinline bool vertexHasBorder() const + { + const HalfEdge* p = this; + do { + if (!p->hasOpposite()) return true; + p = p->rotate(); + } while (p != this); + return false; + } + + /*! tests if the face this half edge belongs to has some border */ + __forceinline bool faceHasBorder() const + { + const HalfEdge* p = this; + do { + if (p->vertexHasBorder()) return true; + p = p->next(); + } while (p != this); + return false; + } + + /*! calculates conservative bounds of a catmull clark subdivision face */ + __forceinline BBox3fa bounds(const BufferView& vertices) const + { + BBox3fa bounds = this->get1RingBounds(vertices); + for (const HalfEdge* p=this->next(); p!=this; p=p->next()) + bounds.extend(p->get1RingBounds(vertices)); + return bounds; + } + + /*! tests if this is a valid patch */ + __forceinline bool valid(const BufferView& vertices) const + { + size_t N = 1; + if (!this->validRing(vertices)) return false; + for (const HalfEdge* p=this->next(); p!=this; p=p->next(), N++) { + if (!p->validRing(vertices)) return false; + } + return N >= 3 && N <= MAX_PATCH_VALENCE; + } + + /*! counts number of polygon edges */ + __forceinline unsigned int numEdges() const + { + unsigned int N = 1; + for (const HalfEdge* p=this->next(); p!=this; p=p->next(), N++); + return N; + } + + /*! calculates face and edge valence */ + __forceinline void calculateFaceValenceAndEdgeValence(size_t& faceValence, size_t& edgeValence) const + { + faceValence = 0; + edgeValence = 0; + + const HalfEdge* p = this; + do + { + /* calculate bounds of current face */ + unsigned int numEdges = p->numEdges(); + assert(numEdges >= 3); + edgeValence += numEdges-2; + + faceValence++; + p = p->prev(); + + /* continue with next face */ + if (likely(p->hasOpposite())) + p = p->opposite(); + + /* if there is no opposite go the long way to the other side of the border */ + else { + faceValence++; + edgeValence++; + p = this; + while (p->hasOpposite()) + p = p->opposite()->next(); + } + + } while (p != this); + } + + /*! stream output */ + friend __forceinline std::ostream &operator<<(std::ostream &o, const HalfEdge &h) + { + return o << "{ " << + "vertex = " << h.vtx_index << ", " << //" -> " << h.next()->vtx_index << ", " << + "prev = " << h.prev_half_edge_ofs << ", " << + "next = " << h.next_half_edge_ofs << ", " << + "opposite = " << h.opposite_half_edge_ofs << ", " << + "edge_crease = " << h.edge_crease_weight << ", " << + "vertex_crease = " << h.vertex_crease_weight << ", " << + //"edge_level = " << h.edge_level << + " }"; + } + + private: + + /*! calculates the bounds of the face associated with the half-edge */ + __forceinline BBox3fa getFaceBounds(const BufferView& vertices) const + { + BBox3fa b = vertices[getStartVertexIndex()]; + for (const HalfEdge* p = next(); p!=this; p=p->next()) { + b.extend(vertices[p->getStartVertexIndex()]); + } + return b; + } + + /*! calculates the bounds of the 1-ring associated with the vertex of the half-edge */ + __forceinline BBox3fa get1RingBounds(const BufferView& vertices) const + { + BBox3fa bounds = empty; + const HalfEdge* p = this; + do + { + /* calculate bounds of current face */ + bounds.extend(p->getFaceBounds(vertices)); + p = p->prev(); + + /* continue with next face */ + if (likely(p->hasOpposite())) + p = p->opposite(); + + /* if there is no opposite go the long way to the other side of the border */ + else { + p = this; + while (p->hasOpposite()) + p = p->opposite()->next(); + } + + } while (p != this); + + return bounds; + } + + /*! tests if this is a valid face */ + __forceinline bool validFace(const BufferView& vertices, size_t& N) const + { + const Vec3fa v = vertices[getStartVertexIndex()]; + if (!isvalid(v)) return false; + size_t n = 1; + for (const HalfEdge* p = next(); p!=this; p=p->next(), n++) { + const Vec3fa v = vertices[p->getStartVertexIndex()]; + if (!isvalid(v)) return false; + } + N += n-2; + return n >= 3 && n <= MAX_PATCH_VALENCE; + } + + /*! tests if this is a valid ring */ + __forceinline bool validRing(const BufferView& vertices) const + { + size_t faceValence = 0; + size_t edgeValence = 0; + + const HalfEdge* p = this; + do + { + /* calculate bounds of current face */ + if (!p->validFace(vertices,edgeValence)) + return false; + + faceValence++; + p = p->prev(); + + /* continue with next face */ + if (likely(p->hasOpposite())) + p = p->opposite(); + + /* if there is no opposite go the long way to the other side of the border */ + else { + faceValence++; + edgeValence++; + p = this; + while (p->hasOpposite()) + p = p->opposite()->next(); + } + + } while (p != this); + + return faceValence <= MAX_RING_FACE_VALENCE && edgeValence <= MAX_RING_EDGE_VALENCE; + } + + private: + unsigned int vtx_index; //!< index of edge start vertex + int next_half_edge_ofs; //!< relative offset to next half edge of face + int prev_half_edge_ofs; //!< relative offset to previous half edge of face + int opposite_half_edge_ofs; //!< relative offset to opposite half edge + + public: + float edge_crease_weight; //!< crease weight attached to edge + float vertex_crease_weight; //!< crease weight attached to start vertex + float edge_level; //!< subdivision factor for edge + PatchType patch_type; //!< stores type of subdiv patch + VertexType vertex_type; //!< stores type of the start vertex + char align[2]; + }; +} diff --git a/thirdparty/embree/kernels/subdiv/hermite_curve.h b/thirdparty/embree/kernels/subdiv/hermite_curve.h new file mode 100644 index 000000000000..9fab79cf0c7f --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/hermite_curve.h @@ -0,0 +1,38 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../common/default.h" +#include "bezier_curve.h" + +namespace embree +{ + template + struct HermiteCurveT : BezierCurveT + { + __forceinline HermiteCurveT() {} + + __forceinline HermiteCurveT(const BezierCurveT& curve) + : BezierCurveT(curve) {} + + __forceinline HermiteCurveT(const Vertex& v0, const Vertex& t0, const Vertex& v1, const Vertex& t1) + : BezierCurveT(v0,madd(1.0f/3.0f,t0,v0),nmadd(1.0f/3.0f,t1,v1),v1) {} + + __forceinline HermiteCurveT xfm_pr(const LinearSpace3fa& space, const Vec3fa& p) const + { + const Vec3ff q0(xfmVector(space,this->v0-p), this->v0.w); + const Vec3ff q1(xfmVector(space,this->v1-p), this->v1.w); + const Vec3ff q2(xfmVector(space,this->v2-p), this->v2.w); + const Vec3ff q3(xfmVector(space,this->v3-p), this->v3.w); + return BezierCurveT(q0,q1,q2,q3); + } + }; + + __forceinline HermiteCurveT enlargeRadiusToMinWidth(const IntersectContext* context, const CurveGeometry* geom, const Vec3fa& ray_org, const HermiteCurveT& curve) { + return HermiteCurveT(enlargeRadiusToMinWidth(context,geom,ray_org,BezierCurveT(curve))); + } + + typedef HermiteCurveT HermiteCurve3fa; +} + diff --git a/thirdparty/embree/kernels/subdiv/linear_bezier_patch.h b/thirdparty/embree/kernels/subdiv/linear_bezier_patch.h new file mode 100644 index 000000000000..f4a854af7f32 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/linear_bezier_patch.h @@ -0,0 +1,403 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "bezier_curve.h" + +namespace embree +{ + namespace isa + { + template + struct TensorLinearQuadraticBezierSurface + { + QuadraticBezierCurve L; + QuadraticBezierCurve R; + + __forceinline TensorLinearQuadraticBezierSurface() {} + + __forceinline TensorLinearQuadraticBezierSurface(const TensorLinearQuadraticBezierSurface& curve) + : L(curve.L), R(curve.R) {} + + __forceinline TensorLinearQuadraticBezierSurface& operator= (const TensorLinearQuadraticBezierSurface& other) { + L = other.L; R = other.R; return *this; + } + + __forceinline TensorLinearQuadraticBezierSurface(const QuadraticBezierCurve& L, const QuadraticBezierCurve& R) + : L(L), R(R) {} + + __forceinline BBox bounds() const { + return merge(L.bounds(),R.bounds()); + } + }; + + template<> + struct TensorLinearQuadraticBezierSurface + { + QuadraticBezierCurve LR; + + __forceinline TensorLinearQuadraticBezierSurface() {} + + __forceinline TensorLinearQuadraticBezierSurface(const TensorLinearQuadraticBezierSurface& curve) + : LR(curve.LR) {} + + __forceinline TensorLinearQuadraticBezierSurface& operator= (const TensorLinearQuadraticBezierSurface& other) { + LR = other.LR; return *this; + } + + __forceinline TensorLinearQuadraticBezierSurface(const QuadraticBezierCurve& LR) + : LR(LR) {} + + __forceinline BBox bounds() const + { + const BBox b = LR.bounds(); + const BBox bl(Vec2fa(b.lower),Vec2fa(b.upper)); + const BBox br(Vec2fa(shuffle<2,3,2,3>(b.lower)),Vec2fa(shuffle<2,3,2,3>(b.upper))); + return merge(bl,br); + } + }; + + template + struct TensorLinearCubicBezierSurface + { + CubicBezierCurve L; + CubicBezierCurve R; + + __forceinline TensorLinearCubicBezierSurface() {} + + __forceinline TensorLinearCubicBezierSurface(const TensorLinearCubicBezierSurface& curve) + : L(curve.L), R(curve.R) {} + + __forceinline TensorLinearCubicBezierSurface& operator= (const TensorLinearCubicBezierSurface& other) { + L = other.L; R = other.R; return *this; + } + + __forceinline TensorLinearCubicBezierSurface(const CubicBezierCurve& L, const CubicBezierCurve& R) + : L(L), R(R) {} + + template class SourceCurve> + __forceinline static TensorLinearCubicBezierSurface fromCenterAndNormalCurve(const SourceCurve& center, const SourceCurve& normal) + { + SourceCurve vcurve = center; + SourceCurve ncurve = normal; + + /* here we construct a patch which follows the curve l(t) = + * p(t) +/- r(t)*normalize(cross(n(t),dp(t))) */ + + const Vec3ff p0 = vcurve.eval(0.0f); + const Vec3ff dp0 = vcurve.eval_du(0.0f); + const Vec3ff ddp0 = vcurve.eval_dudu(0.0f); + + const Vec3fa n0 = ncurve.eval(0.0f); + const Vec3fa dn0 = ncurve.eval_du(0.0f); + + const Vec3ff p1 = vcurve.eval(1.0f); + const Vec3ff dp1 = vcurve.eval_du(1.0f); + const Vec3ff ddp1 = vcurve.eval_dudu(1.0f); + + const Vec3fa n1 = ncurve.eval(1.0f); + const Vec3fa dn1 = ncurve.eval_du(1.0f); + + const Vec3fa bt0 = cross(n0,dp0); + const Vec3fa dbt0 = cross(dn0,dp0) + cross(n0,ddp0); + + const Vec3fa bt1 = cross(n1,dp1); + const Vec3fa dbt1 = cross(dn1,dp1) + cross(n1,ddp1); + + const Vec3fa k0 = normalize(bt0); + const Vec3fa dk0 = dnormalize(bt0,dbt0); + + const Vec3fa k1 = normalize(bt1); + const Vec3fa dk1 = dnormalize(bt1,dbt1); + + const Vec3fa l0 = p0 - p0.w*k0; + const Vec3fa dl0 = dp0 - (dp0.w*k0 + p0.w*dk0); + + const Vec3fa r0 = p0 + p0.w*k0; + const Vec3fa dr0 = dp0 + (dp0.w*k0 + p0.w*dk0); + + const Vec3fa l1 = p1 - p1.w*k1; + const Vec3fa dl1 = dp1 - (dp1.w*k1 + p1.w*dk1); + + const Vec3fa r1 = p1 + p1.w*k1; + const Vec3fa dr1 = dp1 + (dp1.w*k1 + p1.w*dk1); + + const float scale = 1.0f/3.0f; + CubicBezierCurve L(l0,l0+scale*dl0,l1-scale*dl1,l1); + CubicBezierCurve R(r0,r0+scale*dr0,r1-scale*dr1,r1); + return TensorLinearCubicBezierSurface(L,R); + } + + __forceinline BBox bounds() const { + return merge(L.bounds(),R.bounds()); + } + + __forceinline BBox3fa accurateBounds() const { + return merge(L.accurateBounds(),R.accurateBounds()); + } + + __forceinline CubicBezierCurve reduce_v() const { + return merge(CubicBezierCurve>(L),CubicBezierCurve>(R)); + } + + __forceinline LinearBezierCurve reduce_u() const { + return LinearBezierCurve(L.bounds(),R.bounds()); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const V& dx) const { + return TensorLinearCubicBezierSurface(L.xfm(dx),R.xfm(dx)); + } + + __forceinline TensorLinearCubicBezierSurface vxfm(const V& dx) const { + return TensorLinearCubicBezierSurface(L.vxfm(dx),R.vxfm(dx)); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const V& dx, const V& p) const { + return TensorLinearCubicBezierSurface(L.xfm(dx,p),R.xfm(dx,p)); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const LinearSpace3fa& space) const { + return TensorLinearCubicBezierSurface(L.xfm(space),R.xfm(space)); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const LinearSpace3fa& space, const Vec3fa& p) const { + return TensorLinearCubicBezierSurface(L.xfm(space,p),R.xfm(space,p)); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const LinearSpace3fa& space, const Vec3fa& p, const float s) const { + return TensorLinearCubicBezierSurface(L.xfm(space,p,s),R.xfm(space,p,s)); + } + + __forceinline TensorLinearCubicBezierSurface clip_u(const Interval1f& u) const { + return TensorLinearCubicBezierSurface(L.clip(u),R.clip(u)); + } + + __forceinline TensorLinearCubicBezierSurface clip_v(const Interval1f& v) const { + return TensorLinearCubicBezierSurface(clerp(L,R,V(v.lower)),clerp(L,R,V(v.upper))); + } + + __forceinline TensorLinearCubicBezierSurface clip(const Interval1f& u, const Interval1f& v) const { + return clip_v(v).clip_u(u); + } + + __forceinline void split_u(TensorLinearCubicBezierSurface& left, TensorLinearCubicBezierSurface& right, const float u = 0.5f) const + { + CubicBezierCurve L0,L1; L.split(L0,L1,u); + CubicBezierCurve R0,R1; R.split(R0,R1,u); + new (&left ) TensorLinearCubicBezierSurface(L0,R0); + new (&right) TensorLinearCubicBezierSurface(L1,R1); + } + + __forceinline TensorLinearCubicBezierSurface vsplit_u(vboolx& valid, const BBox1f& u) const { + valid = true; clear(valid,VSIZEX-1); + return TensorLinearCubicBezierSurface(L.split(u),R.split(u)); + } + + __forceinline V eval(const float u, const float v) const { + return clerp(L,R,V(v)).eval(u); + } + + __forceinline V eval_du(const float u, const float v) const { + return clerp(L,R,V(v)).eval_dt(u); + } + + __forceinline V eval_dv(const float u, const float v) const { + return (R-L).eval(u); + } + + __forceinline void eval(const float u, const float v, V& p, V& dpdu, V& dpdv) const + { + V p0, dp0du; L.eval(u,p0,dp0du); + V p1, dp1du; R.eval(u,p1,dp1du); + p = lerp(p0,p1,v); + dpdu = lerp(dp0du,dp1du,v); + dpdv = p1-p0; + } + + __forceinline TensorLinearQuadraticBezierSurface derivative_u() const { + return TensorLinearQuadraticBezierSurface(L.derivative(),R.derivative()); + } + + __forceinline CubicBezierCurve derivative_v() const { + return R-L; + } + + __forceinline V axis_u() const { + return (L.end()-L.begin())+(R.end()-R.begin()); + } + + __forceinline V axis_v() const { + return (R.begin()-L.begin())+(R.end()-L.end()); + } + + friend embree_ostream operator<<(embree_ostream cout, const TensorLinearCubicBezierSurface& a) + { + return cout << "TensorLinearCubicBezierSurface" << embree_endl + << "{" << embree_endl + << " L = " << a.L << ", " << embree_endl + << " R = " << a.R << embree_endl + << "}"; + } + + friend __forceinline TensorLinearCubicBezierSurface clerp(const TensorLinearCubicBezierSurface& a, const TensorLinearCubicBezierSurface& b, const float t) { + return TensorLinearCubicBezierSurface(clerp(a.L,b.L,V(t)), clerp(a.R,b.R,V(t))); + } + }; + + template<> + struct TensorLinearCubicBezierSurface + { + CubicBezierCurve LR; + + __forceinline TensorLinearCubicBezierSurface() {} + + __forceinline TensorLinearCubicBezierSurface(const TensorLinearCubicBezierSurface& curve) + : LR(curve.LR) {} + + __forceinline TensorLinearCubicBezierSurface& operator= (const TensorLinearCubicBezierSurface& other) { + LR = other.LR; return *this; + } + + __forceinline TensorLinearCubicBezierSurface(const CubicBezierCurve& LR) + : LR(LR) {} + + __forceinline TensorLinearCubicBezierSurface(const CubicBezierCurve& L, const CubicBezierCurve& R) + : LR(shuffle<0,1,0,1>(vfloat4(L.v0),vfloat4(R.v0)),shuffle<0,1,0,1>(vfloat4(L.v1),vfloat4(R.v1)),shuffle<0,1,0,1>(vfloat4(L.v2),vfloat4(R.v2)),shuffle<0,1,0,1>(vfloat4(L.v3),vfloat4(R.v3))) {} + + __forceinline CubicBezierCurve getL() const { + return CubicBezierCurve(Vec2fa(LR.v0),Vec2fa(LR.v1),Vec2fa(LR.v2),Vec2fa(LR.v3)); + } + + __forceinline CubicBezierCurve getR() const { + return CubicBezierCurve(Vec2fa(shuffle<2,3,2,3>(LR.v0)),Vec2fa(shuffle<2,3,2,3>(LR.v1)),Vec2fa(shuffle<2,3,2,3>(LR.v2)),Vec2fa(shuffle<2,3,2,3>(LR.v3))); + } + + __forceinline BBox bounds() const + { + const BBox b = LR.bounds(); + const BBox bl(Vec2fa(b.lower),Vec2fa(b.upper)); + const BBox br(Vec2fa(shuffle<2,3,2,3>(b.lower)),Vec2fa(shuffle<2,3,2,3>(b.upper))); + return merge(bl,br); + } + + __forceinline BBox1f bounds(const Vec2fa& axis) const + { + const CubicBezierCurve LRx = LR; + const CubicBezierCurve LRy(shuffle<1,0,3,2>(LR.v0),shuffle<1,0,3,2>(LR.v1),shuffle<1,0,3,2>(LR.v2),shuffle<1,0,3,2>(LR.v3)); + const CubicBezierCurve LRa = cmadd(shuffle<0>(vfloat4(axis)),LRx,shuffle<1>(vfloat4(axis))*LRy); + const BBox Lb = LRa.bounds(); + const BBox Rb(shuffle<3>(Lb.lower),shuffle<3>(Lb.upper)); + const BBox b = merge(Lb,Rb); + return BBox1f(b.lower[0],b.upper[0]); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const Vec2fa& dx) const + { + const CubicBezierCurve LRx = LR; + const CubicBezierCurve LRy(shuffle<1,0,3,2>(LR.v0),shuffle<1,0,3,2>(LR.v1),shuffle<1,0,3,2>(LR.v2),shuffle<1,0,3,2>(LR.v3)); + const CubicBezierCurve LRa = cmadd(shuffle<0>(vfloat4(dx)),LRx,shuffle<1>(vfloat4(dx))*LRy); + return TensorLinearCubicBezierSurface(CubicBezierCurve(LRa.v0[0],LRa.v1[0],LRa.v2[0],LRa.v3[0]), + CubicBezierCurve(LRa.v0[2],LRa.v1[2],LRa.v2[2],LRa.v3[2])); + } + + __forceinline TensorLinearCubicBezierSurface xfm(const Vec2fa& dx, const Vec2fa& p) const + { + const vfloat4 pxyxy = shuffle<0,1,0,1>(vfloat4(p)); + const CubicBezierCurve LRx = LR-pxyxy; + const CubicBezierCurve LRy(shuffle<1,0,3,2>(LR.v0),shuffle<1,0,3,2>(LR.v1),shuffle<1,0,3,2>(LR.v2),shuffle<1,0,3,2>(LR.v3)); + const CubicBezierCurve LRa = cmadd(shuffle<0>(vfloat4(dx)),LRx,shuffle<1>(vfloat4(dx))*LRy); + return TensorLinearCubicBezierSurface(CubicBezierCurve(LRa.v0[0],LRa.v1[0],LRa.v2[0],LRa.v3[0]), + CubicBezierCurve(LRa.v0[2],LRa.v1[2],LRa.v2[2],LRa.v3[2])); + } + + __forceinline TensorLinearCubicBezierSurface clip_u(const Interval1f& u) const { + return TensorLinearCubicBezierSurface(LR.clip(u)); + } + + __forceinline TensorLinearCubicBezierSurface clip_v(const Interval1f& v) const + { + const CubicBezierCurve LL(shuffle<0,1,0,1>(LR.v0),shuffle<0,1,0,1>(LR.v1),shuffle<0,1,0,1>(LR.v2),shuffle<0,1,0,1>(LR.v3)); + const CubicBezierCurve RR(shuffle<2,3,2,3>(LR.v0),shuffle<2,3,2,3>(LR.v1),shuffle<2,3,2,3>(LR.v2),shuffle<2,3,2,3>(LR.v3)); + return TensorLinearCubicBezierSurface(clerp(LL,RR,vfloat4(v.lower,v.lower,v.upper,v.upper))); + } + + __forceinline TensorLinearCubicBezierSurface clip(const Interval1f& u, const Interval1f& v) const { + return clip_v(v).clip_u(u); + } + + __forceinline void split_u(TensorLinearCubicBezierSurface& left, TensorLinearCubicBezierSurface& right, const float u = 0.5f) const + { + CubicBezierCurve LR0,LR1; LR.split(LR0,LR1,u); + new (&left ) TensorLinearCubicBezierSurface(LR0); + new (&right) TensorLinearCubicBezierSurface(LR1); + } + + __forceinline TensorLinearCubicBezierSurface vsplit_u(vboolx& valid, const BBox1f& u) const { + valid = true; clear(valid,VSIZEX-1); + return TensorLinearCubicBezierSurface(getL().split(u),getR().split(u)); + } + + __forceinline Vec2fa eval(const float u, const float v) const + { + const vfloat4 p = LR.eval(u); + return Vec2fa(lerp(shuffle<0,1,0,1>(p),shuffle<2,3,2,3>(p),v)); + } + + __forceinline Vec2fa eval_du(const float u, const float v) const + { + const vfloat4 dpdu = LR.eval_dt(u); + return Vec2fa(lerp(shuffle<0,1,0,1>(dpdu),shuffle<2,3,2,3>(dpdu),v)); + } + + __forceinline Vec2fa eval_dv(const float u, const float v) const + { + const vfloat4 p = LR.eval(u); + return Vec2fa(shuffle<2,3,2,3>(p)-shuffle<0,1,0,1>(p)); + } + + __forceinline void eval(const float u, const float v, Vec2fa& p, Vec2fa& dpdu, Vec2fa& dpdv) const + { + vfloat4 p0, dp0du; LR.eval(u,p0,dp0du); + p = Vec2fa(lerp(shuffle<0,1,0,1>(p0),shuffle<2,3,2,3>(p0),v)); + dpdu = Vec2fa(lerp(shuffle<0,1,0,1>(dp0du),shuffle<2,3,2,3>(dp0du),v)); + dpdv = Vec2fa(shuffle<2,3,2,3>(p0)-shuffle<0,1,0,1>(p0)); + } + + __forceinline TensorLinearQuadraticBezierSurface derivative_u() const { + return TensorLinearQuadraticBezierSurface(LR.derivative()); + } + + __forceinline CubicBezierCurve derivative_v() const { + return getR()-getL(); + } + + __forceinline Vec2fa axis_u() const + { + const CubicBezierCurve L = getL(); + const CubicBezierCurve R = getR(); + return (L.end()-L.begin())+(R.end()-R.begin()); + } + + __forceinline Vec2fa axis_v() const + { + const CubicBezierCurve L = getL(); + const CubicBezierCurve R = getR(); + return (R.begin()-L.begin())+(R.end()-L.end()); + } + + friend embree_ostream operator<<(embree_ostream cout, const TensorLinearCubicBezierSurface& a) + { + return cout << "TensorLinearCubicBezierSurface" << embree_endl + << "{" << embree_endl + << " L = " << a.getL() << ", " << embree_endl + << " R = " << a.getR() << embree_endl + << "}"; + } + }; + + typedef TensorLinearCubicBezierSurface TensorLinearCubicBezierSurface1f; + typedef TensorLinearCubicBezierSurface TensorLinearCubicBezierSurface2fa; + typedef TensorLinearCubicBezierSurface TensorLinearCubicBezierSurface3fa; + } +} diff --git a/thirdparty/embree/kernels/subdiv/patch.h b/thirdparty/embree/kernels/subdiv/patch.h new file mode 100644 index 000000000000..d58241b96d6e --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/patch.h @@ -0,0 +1,371 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "catmullclark_patch.h" +#include "bilinear_patch.h" +#include "bspline_patch.h" +#include "bezier_patch.h" +#include "gregory_patch.h" +#include "tessellation_cache.h" + +#if 1 +#define PATCH_DEBUG_SUBDIVISION(ptr,x,y,z) +#else +#define PATCH_DEBUG_SUBDIVISION(ptr,x,y,z) \ + { \ + size_t hex = (size_t)ptr; \ + for (size_t i=0; i<4; i++) hex = hex ^ (hex >> 8); \ + const float c = (float)(((hex >> 0) ^ (hex >> 4) ^ (hex >> 8) ^ (hex >> 12) ^ (hex >> 16))&0xf)/15.0f; \ + if (P) *P = Vertex(0.5f+0.5f*x,0.5f+0.5f*y,0.5f+0.5f*z,0.0f); \ + } +#endif + +#define PATCH_MAX_CACHE_DEPTH 2 +//#define PATCH_MIN_RESOLUTION 1 // FIXME: not yet completely implemented +#define PATCH_MAX_EVAL_DEPTH_IRREGULAR 10 // maximum evaluation depth at irregular vertices (has to be larger or equal than PATCH_MAX_CACHE_DEPTH) +#define PATCH_MAX_EVAL_DEPTH_CREASE 10 // maximum evaluation depth at crease features (has to be larger or equal than PATCH_MAX_CACHE_DEPTH) +#define PATCH_USE_GREGORY 1 // 0 = no gregory, 1 = fill, 2 = as early as possible + +#if PATCH_USE_GREGORY==2 +#define PATCH_USE_BEZIER_PATCH 1 // enable use of bezier instead of b-spline patches +#else +#define PATCH_USE_BEZIER_PATCH 0 // enable use of bezier instead of b-spline patches +#endif + +#if PATCH_USE_BEZIER_PATCH +# define RegularPatch BezierPatch +# define RegularPatchT BezierPatchT +#else +# define RegularPatch BSplinePatch +# define RegularPatchT BSplinePatchT +#endif + +#if PATCH_USE_GREGORY +#define IrregularFillPatch GregoryPatch +#define IrregularFillPatchT GregoryPatchT +#else +#define IrregularFillPatch BilinearPatch +#define IrregularFillPatchT BilinearPatchT +#endif + +namespace embree +{ + template + struct __aligned(64) PatchT + { + public: + + typedef GeneralCatmullClarkPatchT GeneralCatmullClarkPatch; + typedef CatmullClarkPatchT CatmullClarkPatch; + typedef CatmullClark1RingT CatmullClarkRing; + typedef BezierCurveT BezierCurve; + + enum Type { + INVALID_PATCH = 0, + BILINEAR_PATCH = 1, + BSPLINE_PATCH = 2, + BEZIER_PATCH = 3, + GREGORY_PATCH = 4, + SUBDIVIDED_GENERAL_PATCH = 7, + SUBDIVIDED_QUAD_PATCH = 8, + EVAL_PATCH = 9, + }; + + struct Ref + { + __forceinline Ref(void* p = nullptr) + : ptr((size_t)p) {} + + __forceinline operator bool() const { return ptr != 0; } + __forceinline operator size_t() const { return ptr; } + + __forceinline Ref (Type ty, void* in) + : ptr(((size_t)in)+ty) { assert((((size_t)in) & 0xF) == 0); } + + __forceinline Type type () const { return (Type)(ptr & 0xF); } + __forceinline void* object() const { return (void*) (ptr & ~0xF); } + + size_t ptr; + }; + + struct EvalPatch + { + /* creates EvalPatch from a CatmullClarkPatch */ + template + __noinline static Ref create(const Allocator& alloc, const CatmullClarkPatch& patch) + { + size_t ofs = 0, bytes = patch.bytes(); + void* ptr = alloc(bytes); + patch.serialize(ptr,ofs); + assert(ofs == bytes); + return Ref(EVAL_PATCH, ptr); + } + }; + + struct BilinearPatch + { + /* creates BilinearPatch from a CatmullClarkPatch */ + template + __noinline static Ref create(const Allocator& alloc, const CatmullClarkPatch& patch, + const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) { + return Ref(BILINEAR_PATCH, new (alloc(sizeof(BilinearPatch))) BilinearPatch(patch)); + } + + __forceinline BilinearPatch (const CatmullClarkPatch& patch) + : patch(patch) {} + + /* creates BilinearPatch from 4 vertices */ + template + __noinline static Ref create(const Allocator& alloc, const HalfEdge* edge, const char* vertices, size_t stride) { + return Ref(BILINEAR_PATCH, new (alloc(sizeof(BilinearPatch))) BilinearPatch(edge,vertices,stride)); + } + + __forceinline BilinearPatch (const HalfEdge* edge, const char* vertices, size_t stride) + : patch(edge,vertices,stride) {} + + public: + BilinearPatchT patch; + }; + + struct BSplinePatch + { + /* creates BSplinePatch from a half edge */ + template + __noinline static Ref create(const Allocator& alloc, const HalfEdge* edge, const char* vertices, size_t stride) { + return Ref(BSPLINE_PATCH, new (alloc(sizeof(BSplinePatch))) BSplinePatch(edge,vertices,stride)); + } + + __forceinline BSplinePatch (const HalfEdge* edge, const char* vertices, size_t stride) + : patch(edge,vertices,stride) {} + + /* creates BSplinePatch from a CatmullClarkPatch */ + template + __noinline static Ref create(const Allocator& alloc, const CatmullClarkPatch& patch, + const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) { + return Ref(BSPLINE_PATCH, new (alloc(sizeof(BSplinePatch))) BSplinePatch(patch,border0,border1,border2,border3)); + } + + __forceinline BSplinePatch (const CatmullClarkPatch& patch, const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) + : patch(patch,border0,border1,border2,border3) {} + + public: + BSplinePatchT patch; + }; + + struct BezierPatch + { + /* creates BezierPatch from a half edge */ + template + __noinline static Ref create(const Allocator& alloc, const HalfEdge* edge, const char* vertices, size_t stride) { + return Ref(BEZIER_PATCH, new (alloc(sizeof(BezierPatch))) BezierPatch(edge,vertices,stride)); + } + + __forceinline BezierPatch (const HalfEdge* edge, const char* vertices, size_t stride) + : patch(edge,vertices,stride) {} + + /* creates Bezier from a CatmullClarkPatch */ + template + __noinline static Ref create(const Allocator& alloc, const CatmullClarkPatch& patch, + const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) { + return Ref(BEZIER_PATCH, new (alloc(sizeof(BezierPatch))) BezierPatch(patch,border0,border1,border2,border3)); + } + + __forceinline BezierPatch (const CatmullClarkPatch& patch, const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) + : patch(patch,border0,border1,border2,border3) {} + + public: + BezierPatchT patch; + }; + + struct GregoryPatch + { + /* creates GregoryPatch from half edge */ + template + __noinline static Ref create(const Allocator& alloc, const HalfEdge* edge, const char* vertices, size_t stride) { + return Ref(GREGORY_PATCH, new (alloc(sizeof(GregoryPatch))) GregoryPatch(edge,vertices,stride)); + } + + __forceinline GregoryPatch (const HalfEdge* edge, const char* vertices, size_t stride) + : patch(CatmullClarkPatch(edge,vertices,stride)) {} + + /* creates GregoryPatch from CatmullClarkPatch */ + template + __noinline static Ref create(const Allocator& alloc, const CatmullClarkPatch& patch, + const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) { + return Ref(GREGORY_PATCH, new (alloc(sizeof(GregoryPatch))) GregoryPatch(patch,border0,border1,border2,border3)); + } + + __forceinline GregoryPatch (const CatmullClarkPatch& patch, const BezierCurve* border0, const BezierCurve* border1, const BezierCurve* border2, const BezierCurve* border3) + : patch(patch,border0,border1,border2,border3) {} + + public: + GregoryPatchT patch; + }; + + struct SubdividedQuadPatch + { + template + __noinline static Ref create(const Allocator& alloc, Ref children[4]) { + return Ref(SUBDIVIDED_QUAD_PATCH, new (alloc(sizeof(SubdividedQuadPatch))) SubdividedQuadPatch(children)); + } + + __forceinline SubdividedQuadPatch(Ref children[4]) { + for (size_t i=0; i<4; i++) child[i] = children[i]; + } + + public: + Ref child[4]; + }; + + struct SubdividedGeneralPatch + { + template + __noinline static Ref create(const Allocator& alloc, Ref* children, const unsigned N) { + return Ref(SUBDIVIDED_GENERAL_PATCH, new (alloc(sizeof(SubdividedGeneralPatch))) SubdividedGeneralPatch(children,N)); + } + + __forceinline SubdividedGeneralPatch(Ref* children, const unsigned N) : N(N) { + for (unsigned i=0; i + __noinline static Ref create(const Allocator& alloc, const HalfEdge* edge, const char* vertices, size_t stride) + { + if (PATCH_MAX_CACHE_DEPTH == 0) + return nullptr; + + Ref child(0); + switch (edge->patch_type) { + case HalfEdge::BILINEAR_PATCH: child = BilinearPatch::create(alloc,edge,vertices,stride); break; + case HalfEdge::REGULAR_QUAD_PATCH: child = RegularPatch::create(alloc,edge,vertices,stride); break; +#if PATCH_USE_GREGORY == 2 + case HalfEdge::IRREGULAR_QUAD_PATCH: child = GregoryPatch::create(alloc,edge,vertices,stride); break; +#endif + default: { + GeneralCatmullClarkPatch patch(edge,vertices,stride); + child = PatchT::create(alloc,patch,edge,vertices,stride,0); + } + } + return child; + } + + template + __noinline static Ref create(const Allocator& alloc, GeneralCatmullClarkPatch& patch, const HalfEdge* edge, const char* vertices, size_t stride, size_t depth) + { + /* convert into standard quad patch if possible */ + if (likely(patch.isQuadPatch())) + { + CatmullClarkPatch qpatch; patch.init(qpatch); + return PatchT::create(alloc,qpatch,edge,vertices,stride,depth); + } + + /* do only cache up to some depth */ + if (depth >= PATCH_MAX_CACHE_DEPTH) + return nullptr; + + /* subdivide patch */ + unsigned N; + array_t patches; + patch.subdivide(patches,N); + + if (N == 4) + { + Ref child[4]; +#if PATCH_USE_GREGORY == 2 + BezierCurve borders[GeneralCatmullClarkPatch::SIZE]; patch.getLimitBorder(borders); + BezierCurve border0l,border0r; borders[0].subdivide(border0l,border0r); + BezierCurve border1l,border1r; borders[1].subdivide(border1l,border1r); + BezierCurve border2l,border2r; borders[2].subdivide(border2l,border2r); + BezierCurve border3l,border3r; borders[3].subdivide(border3l,border3r); + GeneralCatmullClarkPatch::fix_quad_ring_order(patches); + child[0] = PatchT::create(alloc,patches[0],edge,vertices,stride,depth+1,&border0l,nullptr,nullptr,&border3r); + child[1] = PatchT::create(alloc,patches[1],edge,vertices,stride,depth+1,&border0r,&border1l,nullptr,nullptr); + child[2] = PatchT::create(alloc,patches[2],edge,vertices,stride,depth+1,nullptr,&border1r,&border2l,nullptr); + child[3] = PatchT::create(alloc,patches[3],edge,vertices,stride,depth+1,nullptr,nullptr,&border2r,&border3l); +#else + GeneralCatmullClarkPatch::fix_quad_ring_order(patches); + for (size_t i=0; i<4; i++) + child[i] = PatchT::create(alloc,patches[i],edge,vertices,stride,depth+1); +#endif + return SubdividedQuadPatch::create(alloc,child); + } + else + { + assert(N=max_eval_depth; +//#else + return depth>=max_eval_depth; +//#endif + } + + template + __noinline static Ref create(const Allocator& alloc, CatmullClarkPatch& patch, const HalfEdge* edge, const char* vertices, size_t stride, size_t depth, + const BezierCurve* border0 = nullptr, const BezierCurve* border1 = nullptr, const BezierCurve* border2 = nullptr, const BezierCurve* border3 = nullptr) + { + const typename CatmullClarkPatch::Type ty = patch.type(); + if (unlikely(final(patch,ty,depth))) { + if (ty & CatmullClarkRing::TYPE_REGULAR) return RegularPatch::create(alloc,patch,border0,border1,border2,border3); + else return IrregularFillPatch::create(alloc,patch,border0,border1,border2,border3); + } + else if (ty & CatmullClarkRing::TYPE_REGULAR_CREASES) { + assert(depth > 0); return RegularPatch::create(alloc,patch,border0,border1,border2,border3); + } +#if PATCH_USE_GREGORY == 2 + else if (ty & CatmullClarkRing::TYPE_GREGORY_CREASES) { + assert(depth > 0); return GregoryPatch::create(alloc,patch,border0,border1,border2,border3); + } +#endif + else if (depth >= PATCH_MAX_CACHE_DEPTH) { + return EvalPatch::create(alloc,patch); + } + + else + { + Ref child[4]; + array_t patches; + patch.subdivide(patches); + + for (size_t i=0; i<4; i++) + child[i] = PatchT::create(alloc,patches[i],edge,vertices,stride,depth+1); + return SubdividedQuadPatch::create(alloc,child); + } + } + }; + + typedef PatchT Patch3fa; +} diff --git a/thirdparty/embree/kernels/subdiv/patch_eval.h b/thirdparty/embree/kernels/subdiv/patch_eval.h new file mode 100644 index 000000000000..482d015fa312 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/patch_eval.h @@ -0,0 +1,129 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "patch.h" +#include "feature_adaptive_eval.h" + +namespace embree +{ + namespace isa + { + template + struct PatchEval + { + public: + + typedef PatchT Patch; + typedef typename Patch::Ref Ref; + typedef CatmullClarkPatchT CatmullClarkPatch; + + PatchEval (SharedLazyTessellationCache::CacheEntry& entry, size_t commitCounter, + const HalfEdge* edge, const char* vertices, size_t stride, const float u, const float v, + Vertex* P, Vertex* dPdu, Vertex* dPdv, Vertex* ddPdudu, Vertex* ddPdvdv, Vertex* ddPdudv) + : P(P), dPdu(dPdu), dPdv(dPdv), ddPdudu(ddPdudu), ddPdvdv(ddPdvdv), ddPdudv(ddPdudv) + { + /* conservative time for the very first allocation */ + auto time = SharedLazyTessellationCache::sharedLazyTessellationCache.getTime(commitCounter); + + Ref patch = SharedLazyTessellationCache::lookup(entry,commitCounter,[&] () { + auto alloc = [&](size_t bytes) { return SharedLazyTessellationCache::malloc(bytes); }; + return Patch::create(alloc,edge,vertices,stride); + },true); + + auto curTime = SharedLazyTessellationCache::sharedLazyTessellationCache.getTime(commitCounter); + const bool allAllocationsValid = SharedLazyTessellationCache::validTime(time,curTime); + + if (patch && allAllocationsValid && eval(patch,u,v,1.0f,0)) { + SharedLazyTessellationCache::unlock(); + return; + } + SharedLazyTessellationCache::unlock(); + FeatureAdaptiveEval(edge,vertices,stride,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv); + PATCH_DEBUG_SUBDIVISION(edge,c,-1,-1); + } + + __forceinline bool eval_quad(const typename Patch::SubdividedQuadPatch* This, const float u, const float v, const float dscale, const size_t depth) + { + if (v < 0.5f) { + if (u < 0.5f) return eval(This->child[0],2.0f*u,2.0f*v,2.0f*dscale,depth+1); + else return eval(This->child[1],2.0f*u-1.0f,2.0f*v,2.0f*dscale,depth+1); + } else { + if (u > 0.5f) return eval(This->child[2],2.0f*u-1.0f,2.0f*v-1.0f,2.0f*dscale,depth+1); + else return eval(This->child[3],2.0f*u,2.0f*v-1.0f,2.0f*dscale,depth+1); + } + } + + bool eval_general(const typename Patch::SubdividedGeneralPatch* This, const float U, const float V, const size_t depth) + { + const unsigned l = (unsigned) floor(0.5f*U); const float u = 2.0f*frac(0.5f*U)-0.5f; + const unsigned h = (unsigned) floor(0.5f*V); const float v = 2.0f*frac(0.5f*V)-0.5f; + const unsigned i = 4*h+l; assert(iN); + return eval(This->child[i],u,v,1.0f,depth+1); + } + + bool eval(Ref This, const float& u, const float& v, const float dscale, const size_t depth) + { + if (!This) return false; + //PRINT(depth); + //PRINT2(u,v); + + switch (This.type()) + { + case Patch::BILINEAR_PATCH: { + //PRINT("bilinear"); + ((typename Patch::BilinearPatch*)This.object())->patch.eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(This,-1,c,c); + return true; + } + case Patch::BSPLINE_PATCH: { + //PRINT("bspline"); + ((typename Patch::BSplinePatch*)This.object())->patch.eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(This,-1,c,-1); + return true; + } + case Patch::BEZIER_PATCH: { + //PRINT("bezier"); + ((typename Patch::BezierPatch*)This.object())->patch.eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(This,-1,c,-1); + return true; + } + case Patch::GREGORY_PATCH: { + //PRINT("gregory"); + ((typename Patch::GregoryPatch*)This.object())->patch.eval(u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale); + PATCH_DEBUG_SUBDIVISION(This,-1,-1,c); + return true; + } + case Patch::SUBDIVIDED_QUAD_PATCH: { + //PRINT("subdivided quad"); + return eval_quad(((typename Patch::SubdividedQuadPatch*)This.object()),u,v,dscale,depth); + } + case Patch::SUBDIVIDED_GENERAL_PATCH: { + //PRINT("general_patch"); + assert(dscale == 1.0f); + return eval_general(((typename Patch::SubdividedGeneralPatch*)This.object()),u,v,depth); + } + case Patch::EVAL_PATCH: { + //PRINT("eval_patch"); + CatmullClarkPatch patch; patch.deserialize(This.object()); + FeatureAdaptiveEval(patch,u,v,dscale,depth,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv); + return true; + } + default: + assert(false); + return false; + } + } + + private: + Vertex* const P; + Vertex* const dPdu; + Vertex* const dPdv; + Vertex* const ddPdudu; + Vertex* const ddPdvdv; + Vertex* const ddPdudv; + }; + } +} + diff --git a/thirdparty/embree/kernels/subdiv/patch_eval_grid.h b/thirdparty/embree/kernels/subdiv/patch_eval_grid.h new file mode 100644 index 000000000000..c05db55f4cde --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/patch_eval_grid.h @@ -0,0 +1,245 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "patch.h" +#include "feature_adaptive_eval_grid.h" + +namespace embree +{ + namespace isa + { + struct PatchEvalGrid + { + typedef Patch3fa Patch; + typedef Patch::Ref Ref; + typedef GeneralCatmullClarkPatch3fa GeneralCatmullClarkPatch; + typedef CatmullClarkPatch3fa CatmullClarkPatch; + typedef BSplinePatch3fa BSplinePatch; + typedef BezierPatch3fa BezierPatch; + typedef GregoryPatch3fa GregoryPatch; + typedef BilinearPatch3fa BilinearPatch; + + private: + const unsigned x0,x1; + const unsigned y0,y1; + const unsigned swidth,sheight; + const float rcp_swidth, rcp_sheight; + float* const Px; + float* const Py; + float* const Pz; + float* const U; + float* const V; + float* const Nx; + float* const Ny; + float* const Nz; + const unsigned dwidth,dheight; + unsigned count; + + public: + + PatchEvalGrid (Ref patch, unsigned subPatch, + const unsigned x0, const unsigned x1, const unsigned y0, const unsigned y1, const unsigned swidth, const unsigned sheight, + float* Px, float* Py, float* Pz, float* U, float* V, + float* Nx, float* Ny, float* Nz, + const unsigned dwidth, const unsigned dheight) + : x0(x0), x1(x1), y0(y0), y1(y1), swidth(swidth), sheight(sheight), rcp_swidth(1.0f/(swidth-1.0f)), rcp_sheight(1.0f/(sheight-1.0f)), + Px(Px), Py(Py), Pz(Pz), U(U), V(V), Nx(Nx), Ny(Ny), Nz(Nz), dwidth(dwidth), dheight(dheight), count(0) + { + assert(swidth < (2<<20) && sheight < (2<<20)); + const BBox2f srange(Vec2f(0.0f,0.0f),Vec2f(float(swidth-1),float(sheight-1))); + const BBox2f erange(Vec2f(float(x0),float(y0)),Vec2f((float)x1,(float)y1)); + bool done MAYBE_UNUSED = eval(patch,subPatch,srange,erange); + assert(done); + assert(count == (x1-x0+1)*(y1-y0+1)); + } + + template + __forceinline void evalLocalGrid(const Patch* patch, const BBox2f& srange, const int lx0, const int lx1, const int ly0, const int ly1) + { + const float scale_x = rcp(srange.upper.x-srange.lower.x); + const float scale_y = rcp(srange.upper.y-srange.lower.y); + count += (lx1-lx0)*(ly1-ly0); + +#if 0 + for (unsigned iy=ly0; iypatch.eval(lu,lv); + const float u = float(ix)*rcp_swidth; + const float v = float(iy)*rcp_sheight; + const int ofs = (iy-y0)*dwidth+(ix-x0); + Px[ofs] = p.x; + Py[ofs] = p.y; + Pz[ofs] = p.z; + U[ofs] = u; + V[ofs] = v; + } + } +#else + foreach2(lx0,lx1,ly0,ly1,[&](const vboolx& valid, const vintx& ix, const vintx& iy) { + const vfloatx lu = select(ix == swidth -1, vfloatx(1.0f), (vfloatx(ix)-srange.lower.x)*scale_x); + const vfloatx lv = select(iy == sheight-1, vfloatx(1.0f), (vfloatx(iy)-srange.lower.y)*scale_y); + const Vec3vfx p = patch->patch.eval(lu,lv); + Vec3vfx n = zero; + if (unlikely(Nx != nullptr)) n = normalize_safe(patch->patch.normal(lu,lv)); + const vfloatx u = vfloatx(ix)*rcp_swidth; + const vfloatx v = vfloatx(iy)*rcp_sheight; + const vintx ofs = (iy-y0)*dwidth+(ix-x0); + if (likely(all(valid)) && all(iy==iy[0])) { + const unsigned ofs2 = ofs[0]; + vfloatx::storeu(Px+ofs2,p.x); + vfloatx::storeu(Py+ofs2,p.y); + vfloatx::storeu(Pz+ofs2,p.z); + vfloatx::storeu(U+ofs2,u); + vfloatx::storeu(V+ofs2,v); + if (unlikely(Nx != nullptr)) { + vfloatx::storeu(Nx+ofs2,n.x); + vfloatx::storeu(Ny+ofs2,n.y); + vfloatx::storeu(Nz+ofs2,n.z); + } + } else { + foreach_unique_index(valid,iy,[&](const vboolx& valid, const int iy0, const int j) { + const unsigned ofs2 = ofs[j]-j; + vfloatx::storeu(valid,Px+ofs2,p.x); + vfloatx::storeu(valid,Py+ofs2,p.y); + vfloatx::storeu(valid,Pz+ofs2,p.z); + vfloatx::storeu(valid,U+ofs2,u); + vfloatx::storeu(valid,V+ofs2,v); + if (unlikely(Nx != nullptr)) { + vfloatx::storeu(valid,Nx+ofs2,n.x); + vfloatx::storeu(valid,Ny+ofs2,n.y); + vfloatx::storeu(valid,Nz+ofs2,n.z); + } + }); + } + }); +#endif + } + + bool eval(Ref This, const BBox2f& srange, const BBox2f& erange, const unsigned depth) + { + if (erange.empty()) + return true; + + const int lx0 = (int) ceilf(erange.lower.x); + const int lx1 = (int) ceilf(erange.upper.x) + (erange.upper.x == x1 && (srange.lower.x < erange.upper.x || erange.upper.x == 0)); + const int ly0 = (int) ceilf(erange.lower.y); + const int ly1 = (int) ceilf(erange.upper.y) + (erange.upper.y == y1 && (srange.lower.y < erange.upper.y || erange.upper.y == 0)); + if (lx0 >= lx1 || ly0 >= ly1) + return true; + + if (!This) + return false; + + switch (This.type()) + { + case Patch::BILINEAR_PATCH: { + evalLocalGrid((Patch::BilinearPatch*)This.object(),srange,lx0,lx1,ly0,ly1); + return true; + } + case Patch::BSPLINE_PATCH: { + evalLocalGrid((Patch::BSplinePatch*)This.object(),srange,lx0,lx1,ly0,ly1); + return true; + } + case Patch::BEZIER_PATCH: { + evalLocalGrid((Patch::BezierPatch*)This.object(),srange,lx0,lx1,ly0,ly1); + return true; + } + case Patch::GREGORY_PATCH: { + evalLocalGrid((Patch::GregoryPatch*)This.object(),srange,lx0,lx1,ly0,ly1); + return true; + } + case Patch::SUBDIVIDED_QUAD_PATCH: + { + const Vec2f c = srange.center(); + const BBox2f srange0(srange.lower,c); + const BBox2f srange1(Vec2f(c.x,srange.lower.y),Vec2f(srange.upper.x,c.y)); + const BBox2f srange2(c,srange.upper); + const BBox2f srange3(Vec2f(srange.lower.x,c.y),Vec2f(c.x,srange.upper.y)); + + Patch::SubdividedQuadPatch* patch = (Patch::SubdividedQuadPatch*)This.object(); + eval(patch->child[0],srange0,intersect(srange0,erange),depth+1); + eval(patch->child[1],srange1,intersect(srange1,erange),depth+1); + eval(patch->child[2],srange2,intersect(srange2,erange),depth+1); + eval(patch->child[3],srange3,intersect(srange3,erange),depth+1); + return true; + } + case Patch::EVAL_PATCH: { + CatmullClarkPatch patch; patch.deserialize(This.object()); + FeatureAdaptiveEvalGrid(patch,srange,erange,depth,x0,x1,y0,y1,swidth,sheight,Px,Py,Pz,U,V,Nx,Ny,Nz,dwidth,dheight); + count += (lx1-lx0)*(ly1-ly0); + return true; + } + default: + assert(false); + return false; + } + } + + bool eval(Ref This, unsigned subPatch, const BBox2f& srange, const BBox2f& erange) + { + if (!This) + return false; + + switch (This.type()) + { + case Patch::SUBDIVIDED_GENERAL_PATCH: { + Patch::SubdividedGeneralPatch* patch = (Patch::SubdividedGeneralPatch*)This.object(); + assert(subPatch < patch->N); + return eval(patch->child[subPatch],srange,erange,1); + } + default: + assert(subPatch == 0); + return eval(This,srange,erange,0); + } + } + }; + + __forceinline unsigned patch_eval_subdivision_count (const HalfEdge* h) + { + const unsigned N = h->numEdges(); + if (N == 4) return 1; + else return N; + } + + template + inline void patch_eval_subdivision (const HalfEdge* h, Tessellator tessellator) + { + const unsigned N = h->numEdges(); + int neighborSubdiv[GeneralCatmullClarkPatch3fa::SIZE]; // FIXME: use array_t + float levels[GeneralCatmullClarkPatch3fa::SIZE]; + for (unsigned i=0; ihasOpposite() ? h->opposite()->numEdges() != 4 : 0; + levels[i] = h->edge_level; + h = h->next(); + } + if (N == 4) + { + const Vec2f uv[4] = { Vec2f(0.0f,0.0f), Vec2f(1.0f,0.0f), Vec2f(1.0f,1.0f), Vec2f(0.0f,1.0f) }; + tessellator(uv,neighborSubdiv,levels,0); + } + else + { + for (unsigned i=0; i 16"); + const int h = (i >> 2) & 3, l = i & 3; + const Vec2f subPatchID((float)l,(float)h); + const Vec2f uv[4] = { 2.0f*subPatchID + (0.5f+Vec2f(0.0f,0.0f)), + 2.0f*subPatchID + (0.5f+Vec2f(1.0f,0.0f)), + 2.0f*subPatchID + (0.5f+Vec2f(1.0f,1.0f)), + 2.0f*subPatchID + (0.5f+Vec2f(0.0f,1.0f)) }; + const int neighborSubdiv1[4] = { 0,0,0,0 }; + const float levels1[4] = { 0.5f*levels[(i+0)%N], 0.5f*levels[(i+0)%N], 0.5f*levels[(i+N-1)%N], 0.5f*levels[(i+N-1)%N] }; + tessellator(uv,neighborSubdiv1,levels1,i); + } + } + } + } +} + diff --git a/thirdparty/embree/kernels/subdiv/patch_eval_simd.h b/thirdparty/embree/kernels/subdiv/patch_eval_simd.h new file mode 100644 index 000000000000..28016d9e20d9 --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/patch_eval_simd.h @@ -0,0 +1,127 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "patch.h" +#include "feature_adaptive_eval_simd.h" + +namespace embree +{ + namespace isa + { + template + struct PatchEvalSimd + { + public: + + typedef PatchT Patch; + typedef typename Patch::Ref Ref; + typedef CatmullClarkPatchT CatmullClarkPatch; + + PatchEvalSimd (SharedLazyTessellationCache::CacheEntry& entry, size_t commitCounter, + const HalfEdge* edge, const char* vertices, size_t stride, const vbool& valid0, const vfloat& u, const vfloat& v, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, const size_t dstride, const size_t N) + : P(P), dPdu(dPdu), dPdv(dPdv), ddPdudu(ddPdudu), ddPdvdv(ddPdvdv), ddPdudv(ddPdudv), dstride(dstride), N(N) + { + /* conservative time for the very first allocation */ + auto time = SharedLazyTessellationCache::sharedLazyTessellationCache.getTime(commitCounter); + + Ref patch = SharedLazyTessellationCache::lookup(entry,commitCounter,[&] () { + auto alloc = [](size_t bytes) { return SharedLazyTessellationCache::malloc(bytes); }; + return Patch::create(alloc,edge,vertices,stride); + }, true); + + auto curTime = SharedLazyTessellationCache::sharedLazyTessellationCache.getTime(commitCounter); + const bool allAllocationsValid = SharedLazyTessellationCache::validTime(time,curTime); + + patch = allAllocationsValid ? patch : nullptr; + + /* use cached data structure for calculations */ + const vbool valid1 = patch ? eval(valid0,patch,u,v,1.0f,0) : vbool(false); + SharedLazyTessellationCache::unlock(); + const vbool valid2 = valid0 & !valid1; + if (any(valid2)) { + FeatureAdaptiveEvalSimd(edge,vertices,stride,valid2,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dstride,N); + } + } + + vbool eval_quad(const vbool& valid, const typename Patch::SubdividedQuadPatch* This, const vfloat& u, const vfloat& v, const float dscale, const size_t depth) + { + vbool ret = false; + const vbool u0_mask = u < 0.5f, u1_mask = u >= 0.5f; + const vbool v0_mask = v < 0.5f, v1_mask = v >= 0.5f; + const vbool u0v0_mask = valid & u0_mask & v0_mask; + const vbool u0v1_mask = valid & u0_mask & v1_mask; + const vbool u1v0_mask = valid & u1_mask & v0_mask; + const vbool u1v1_mask = valid & u1_mask & v1_mask; + if (any(u0v0_mask)) ret |= eval(u0v0_mask,This->child[0],2.0f*u,2.0f*v,2.0f*dscale,depth+1); + if (any(u1v0_mask)) ret |= eval(u1v0_mask,This->child[1],2.0f*u-1.0f,2.0f*v,2.0f*dscale,depth+1); + if (any(u1v1_mask)) ret |= eval(u1v1_mask,This->child[2],2.0f*u-1.0f,2.0f*v-1.0f,2.0f*dscale,depth+1); + if (any(u0v1_mask)) ret |= eval(u0v1_mask,This->child[3],2.0f*u,2.0f*v-1.0f,2.0f*dscale,depth+1); + return ret; + } + + vbool eval_general(const vbool& valid, const typename Patch::SubdividedGeneralPatch* patch, const vfloat& U, const vfloat& V, const size_t depth) + { + vbool ret = false; + const vint l = (vint)floor(0.5f*U); const vfloat u = 2.0f*frac(0.5f*U)-0.5f; + const vint h = (vint)floor(0.5f*V); const vfloat v = 2.0f*frac(0.5f*V)-0.5f; + const vint i = (h<<2)+l; assert(all(valid,iN)); + foreach_unique(valid,i,[&](const vbool& valid, const int i) { + ret |= eval(valid,patch->child[i],u,v,1.0f,depth+1); + }); + return ret; + } + + vbool eval(const vbool& valid, Ref This, const vfloat& u, const vfloat& v, const float dscale, const size_t depth) + { + if (!This) return false; + switch (This.type()) + { + case Patch::BILINEAR_PATCH: { + ((typename Patch::BilinearPatch*)This.object())->patch.eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + return valid; + } + case Patch::BSPLINE_PATCH: { + ((typename Patch::BSplinePatch*)This.object())->patch.eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + return valid; + } + case Patch::BEZIER_PATCH: { + ((typename Patch::BezierPatch*)This.object())->patch.eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + return valid; + } + case Patch::GREGORY_PATCH: { + ((typename Patch::GregoryPatch*)This.object())->patch.eval(valid,u,v,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dscale,dstride,N); + return valid; + } + case Patch::SUBDIVIDED_QUAD_PATCH: { + return eval_quad(valid,((typename Patch::SubdividedQuadPatch*)This.object()),u,v,dscale,depth); + } + case Patch::SUBDIVIDED_GENERAL_PATCH: { + assert(dscale == 1.0f); + return eval_general(valid,((typename Patch::SubdividedGeneralPatch*)This.object()),u,v,depth); + } + case Patch::EVAL_PATCH: { + CatmullClarkPatch patch; patch.deserialize(This.object()); + FeatureAdaptiveEvalSimd(patch,valid,u,v,dscale,depth,P,dPdu,dPdv,ddPdudu,ddPdvdv,ddPdudv,dstride,N); + return valid; + } + default: + assert(false); + return false; + } + } + + private: + float* const P; + float* const dPdu; + float* const dPdv; + float* const ddPdudu; + float* const ddPdvdv; + float* const ddPdudv; + const size_t dstride; + const size_t N; + }; + } +} diff --git a/thirdparty/embree/kernels/subdiv/subdivpatch1base.h b/thirdparty/embree/kernels/subdiv/subdivpatch1base.h new file mode 100644 index 000000000000..d5bc403cca8c --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/subdivpatch1base.h @@ -0,0 +1,156 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../geometry/primitive.h" +#include "bspline_patch.h" +#include "bezier_patch.h" +#include "gregory_patch.h" +#include "gregory_patch_dense.h" +#include "tessellation.h" +#include "tessellation_cache.h" +#include "gridrange.h" +#include "patch_eval_grid.h" +#include "feature_adaptive_eval_grid.h" +#include "../common/scene_subdiv_mesh.h" + +namespace embree +{ + struct __aligned(64) SubdivPatch1Base + { + public: + + enum Type { + INVALID_PATCH = 0, + BSPLINE_PATCH = 1, + BEZIER_PATCH = 2, + GREGORY_PATCH = 3, + EVAL_PATCH = 5, + BILINEAR_PATCH = 6, + }; + + enum Flags { + TRANSITION_PATCH = 16, + }; + + /*! Default constructor. */ + __forceinline SubdivPatch1Base () {} + + SubdivPatch1Base (const unsigned int gID, + const unsigned int pID, + const unsigned int subPatch, + const SubdivMesh *const mesh, + const size_t time, + const Vec2f uv[4], + const float edge_level[4], + const int subdiv[4], + const int simd_width); + + __forceinline bool needsStitching() const { + return flags & TRANSITION_PATCH; + } + + __forceinline Vec2f getUV(const size_t i) const { + return Vec2f((float)u[i],(float)v[i]) * (8.0f/0x10000); + } + + static void computeEdgeLevels(const float edge_level[4], const int subdiv[4], float level[4]); + static Vec2i computeGridSize(const float level[4]); + bool updateEdgeLevels(const float edge_level[4], const int subdiv[4], const SubdivMesh *const mesh, const int simd_width); + + public: + + __forceinline size_t getGridBytes() const { + const size_t grid_size_xyzuv = (grid_size_simd_blocks * VSIZEX) * 4; + return 64*((grid_size_xyzuv+15) / 16); + } + + __forceinline void write_lock() { mtx.lock(); } + __forceinline void write_unlock() { mtx.unlock(); } + __forceinline bool try_write_lock() { return mtx.try_lock(); } + //__forceinline bool try_read_lock() { return mtx.try_read_lock(); } + + __forceinline void resetRootRef() { + //assert( mtx.hasInitialState() ); + root_ref = SharedLazyTessellationCache::Tag(); + } + + __forceinline SharedLazyTessellationCache::CacheEntry& entry() { + return (SharedLazyTessellationCache::CacheEntry&) root_ref; + } + + public: + __forceinline unsigned int geomID() const { + return geom; + } + + __forceinline unsigned int primID() const { + return prim; + } + + public: + SharedLazyTessellationCache::Tag root_ref; + SpinLock mtx; + + unsigned short u[4]; //!< 16bit discretized u,v coordinates + unsigned short v[4]; + float level[4]; + + unsigned char flags; + unsigned char type; + unsigned short grid_u_res; + unsigned int geom; //!< geometry ID of the subdivision mesh this patch belongs to + unsigned int prim; //!< primitive ID of this subdivision patch + unsigned short grid_v_res; + + unsigned short grid_size_simd_blocks; + unsigned int time_; + + struct PatchHalfEdge { + const HalfEdge* edge; + unsigned subPatch; + }; + + Vec3fa patch_v[4][4]; + + const HalfEdge *edge() const { return ((PatchHalfEdge*)patch_v)->edge; } + unsigned time() const { return time_; } + unsigned subPatch() const { return ((PatchHalfEdge*)patch_v)->subPatch; } + + void set_edge(const HalfEdge *h) const { ((PatchHalfEdge*)patch_v)->edge = h; } + void set_subPatch(const unsigned s) const { ((PatchHalfEdge*)patch_v)->subPatch = s; } + }; + + namespace isa + { + Vec3fa patchEval(const SubdivPatch1Base& patch, const float uu, const float vv); + Vec3fa patchNormal(const SubdivPatch1Base& patch, const float uu, const float vv); + + template + Vec3 patchEval(const SubdivPatch1Base& patch, const simdf& uu, const simdf& vv); + + template + Vec3 patchNormal(const SubdivPatch1Base& patch, const simdf& uu, const simdf& vv); + + + /* eval grid over patch and stich edges when required */ + void evalGrid(const SubdivPatch1Base& patch, + const unsigned x0, const unsigned x1, + const unsigned y0, const unsigned y1, + const unsigned swidth, const unsigned sheight, + float *__restrict__ const grid_x, + float *__restrict__ const grid_y, + float *__restrict__ const grid_z, + float *__restrict__ const grid_u, + float *__restrict__ const grid_v, + const SubdivMesh* const geom); + + /* eval grid over patch and stich edges when required */ + BBox3fa evalGridBounds(const SubdivPatch1Base& patch, + const unsigned x0, const unsigned x1, + const unsigned y0, const unsigned y1, + const unsigned swidth, const unsigned sheight, + const SubdivMesh* const geom); + } +} diff --git a/thirdparty/embree/kernels/subdiv/tessellation.h b/thirdparty/embree/kernels/subdiv/tessellation.h new file mode 100644 index 000000000000..bda1e2d5591e --- /dev/null +++ b/thirdparty/embree/kernels/subdiv/tessellation.h @@ -0,0 +1,161 @@ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace embree +{ + /* adjust discret tessellation level for feature-adaptive pre-subdivision */ + __forceinline float adjustTessellationLevel(float l, const size_t sublevel) + { + for (size_t i=0; i= 2); + + const float inv_low_rate = rcp((float)(low_rate-1)); + const unsigned int dy = low_rate - 1; + const unsigned int dx = high_rate - 1; + + int p = 2*dy-dx; + + unsigned int offset = 0; + unsigned int y = 0; + float value = 0.0f; + for(unsigned int x=0;x data; + }; + + static __forceinline size_t extractCommitIndex(const int64_t v) { return v >> SharedLazyTessellationCache::COMMIT_INDEX_SHIFT; } + + struct CacheEntry + { + Tag tag; + SpinLock mutex; + }; + + private: + + float *data; + bool hugepages; + size_t size; + size_t maxBlocks; + ThreadWorkState *threadWorkState; + + __aligned(64) std::atomic localTime; + __aligned(64) std::atomic next_block; + __aligned(64) SpinLock reset_state; + __aligned(64) SpinLock linkedlist_mtx; + __aligned(64) std::atomic switch_block_threshold; + __aligned(64) std::atomic numRenderThreads; + + + public: + + + SharedLazyTessellationCache(); + ~SharedLazyTessellationCache(); + + void getNextRenderThreadWorkState(); + + __forceinline size_t maxAllocSize() const { + return switch_block_threshold; + } + + __forceinline size_t getCurrentIndex() { return localTime.load(); } + __forceinline void addCurrentIndex(const size_t i=1) { localTime.fetch_add(i); } + + __forceinline size_t getTime(const size_t globalTime) { + return localTime.load()+NUM_CACHE_SEGMENTS*globalTime; + } + + + __forceinline size_t lockThread (ThreadWorkState *const t_state, const ssize_t plus=1) { return t_state->counter.fetch_add(plus); } + __forceinline size_t unlockThread(ThreadWorkState *const t_state, const ssize_t plus=-1) { assert(isLocked(t_state)); return t_state->counter.fetch_add(plus); } + + __forceinline bool isLocked(ThreadWorkState *const t_state) { return t_state->counter.load() != 0; } + + static __forceinline void lock () { sharedLazyTessellationCache.lockThread(threadState()); } + static __forceinline void unlock() { sharedLazyTessellationCache.unlockThread(threadState()); } + static __forceinline bool isLocked() { return sharedLazyTessellationCache.isLocked(threadState()); } + static __forceinline size_t getState() { return threadState()->counter.load(); } + static __forceinline void lockThreadLoop() { sharedLazyTessellationCache.lockThreadLoop(threadState()); } + + static __forceinline size_t getTCacheTime(const size_t globalTime) { + return sharedLazyTessellationCache.getTime(globalTime); + } + + /* per thread lock */ + __forceinline void lockThreadLoop (ThreadWorkState *const t_state) + { + while(1) + { + size_t lock = SharedLazyTessellationCache::sharedLazyTessellationCache.lockThread(t_state,1); + if (unlikely(lock >= THREAD_BLOCK_ATOMIC_ADD)) + { + /* lock failed wait until sync phase is over */ + sharedLazyTessellationCache.unlockThread(t_state,-1); + sharedLazyTessellationCache.waitForUsersLessEqual(t_state,0); + } + else + break; + } + } + + static __forceinline void* lookup(CacheEntry& entry, size_t globalTime) + { + const int64_t subdiv_patch_root_ref = entry.tag.get(); + CACHE_STATS(SharedTessellationCacheStats::cache_accesses++); + + if (likely(subdiv_patch_root_ref != 0)) + { + const size_t subdiv_patch_root = (subdiv_patch_root_ref & REF_TAG_MASK) + (size_t)sharedLazyTessellationCache.getDataPtr(); + const size_t subdiv_patch_cache_index = extractCommitIndex(subdiv_patch_root_ref); + + if (likely( sharedLazyTessellationCache.validCacheIndex(subdiv_patch_cache_index,globalTime) )) + { + CACHE_STATS(SharedTessellationCacheStats::cache_hits++); + return (void*) subdiv_patch_root; + } + } + CACHE_STATS(SharedTessellationCacheStats::cache_misses++); + return nullptr; + } + + template + static __forceinline auto lookup (CacheEntry& entry, size_t globalTime, const Constructor constructor, const bool before=false) -> decltype(constructor()) + { + ThreadWorkState *t_state = SharedLazyTessellationCache::threadState(); + + while (true) + { + sharedLazyTessellationCache.lockThreadLoop(t_state); + void* patch = SharedLazyTessellationCache::lookup(entry,globalTime); + if (patch) return (decltype(constructor())) patch; + + if (entry.mutex.try_lock()) + { + if (!validTag(entry.tag,globalTime)) + { + auto timeBefore = sharedLazyTessellationCache.getTime(globalTime); + auto ret = constructor(); // thread is locked here! + assert(ret); + /* this should never return nullptr */ + auto timeAfter = sharedLazyTessellationCache.getTime(globalTime); + auto time = before ? timeBefore : timeAfter; + __memory_barrier(); + entry.tag = SharedLazyTessellationCache::Tag(ret,time); + __memory_barrier(); + entry.mutex.unlock(); + return ret; + } + entry.mutex.unlock(); + } + SharedLazyTessellationCache::sharedLazyTessellationCache.unlockThread(t_state); + } + } + + __forceinline bool validCacheIndex(const size_t i, const size_t globalTime) + { +#if FORCE_SIMPLE_FLUSH == 1 + return i == getTime(globalTime); +#else + return i+(NUM_CACHE_SEGMENTS-1) >= getTime(globalTime); +#endif + } + + static __forceinline bool validTime(const size_t oldtime, const size_t newTime) + { + return oldtime+(NUM_CACHE_SEGMENTS-1) >= newTime; + } + + + static __forceinline bool validTag(const Tag& tag, size_t globalTime) + { + const int64_t subdiv_patch_root_ref = tag.get(); + if (subdiv_patch_root_ref == 0) return false; + const size_t subdiv_patch_cache_index = extractCommitIndex(subdiv_patch_root_ref); + return sharedLazyTessellationCache.validCacheIndex(subdiv_patch_cache_index,globalTime); + } + + void waitForUsersLessEqual(ThreadWorkState *const t_state, + const unsigned int users); + + __forceinline size_t alloc(const size_t blocks) + { + if (unlikely(blocks >= switch_block_threshold)) + throw_RTCError(RTC_ERROR_INVALID_OPERATION,"allocation exceeds size of tessellation cache segment"); + + assert(blocks < switch_block_threshold); + size_t index = next_block.fetch_add(blocks); + if (unlikely(index + blocks >= switch_block_threshold)) return (size_t)-1; + return index; + } + + static __forceinline void* malloc(const size_t bytes) + { + size_t block_index = -1; + ThreadWorkState *const t_state = threadState(); + while (true) + { + block_index = sharedLazyTessellationCache.alloc((bytes+BLOCK_SIZE-1)/BLOCK_SIZE); + if (block_index == (size_t)-1) + { + sharedLazyTessellationCache.unlockThread(t_state); + sharedLazyTessellationCache.allocNextSegment(); + sharedLazyTessellationCache.lockThread(t_state); + continue; + } + break; + } + return sharedLazyTessellationCache.getBlockPtr(block_index); + } + + __forceinline void *getBlockPtr(const size_t block_index) + { + assert(block_index < maxBlocks); + assert(data); + assert(block_index*16 <= size); + return (void*)&data[block_index*16]; + } + + __forceinline void* getDataPtr() { return data; } + __forceinline size_t getNumUsedBytes() { return next_block * BLOCK_SIZE; } + __forceinline size_t getMaxBlocks() { return maxBlocks; } + __forceinline size_t getSize() { return size; } + + void allocNextSegment(); + void realloc(const size_t newSize); + + void reset(); + + static SharedLazyTessellationCache sharedLazyTessellationCache; + }; +} diff --git a/thirdparty/embree/pathces/godot-changes.patch b/thirdparty/embree/pathces/godot-changes.patch new file mode 100644 index 000000000000..32c4380862b6 --- /dev/null +++ b/thirdparty/embree/pathces/godot-changes.patch @@ -0,0 +1,91 @@ +diff --git a/common/math/math.h b/common/math/math.h +index 5af0691a2..1982c27c1 100644 +--- a/common/math/math.h ++++ b/common/math/math.h +@@ -12,7 +12,7 @@ + #include + #include + +-#if defined(__WIN32__) ++#if defined(__WIN32__) && !defined(__MINGW32__) + #if (__MSV_VER <= 1700) + namespace std + { +diff --git a/common/sys/intrinsics.h b/common/sys/intrinsics.h +index 3f0619cac..6948e4a74 100644 +--- a/common/sys/intrinsics.h ++++ b/common/sys/intrinsics.h +@@ -30,8 +30,14 @@ + #endif + + #if defined(__WIN32__) +-# define NOMINMAX +-# include ++// -- GODOT start -- ++#if !defined(NOMINMAX) ++// -- GODOT end -- ++#define NOMINMAX ++// -- GODOT start -- ++#endif ++#include "windows.h" ++// -- GODOT end -- + #endif + + /* normally defined in pmmintrin.h, but we always need this */ +diff --git a/common/sys/library.cpp b/common/sys/library.cpp +index e448b195d..8ec918660 100644 +--- a/common/sys/library.cpp ++++ b/common/sys/library.cpp +@@ -27,7 +27,9 @@ namespace embree + + /* returns address of a symbol from the library */ + void* getSymbol(lib_t lib, const std::string& sym) { +- return GetProcAddress(HMODULE(lib),sym.c_str()); ++ // -- GODOT start -- ++ return (void*) GetProcAddress(HMODULE(lib),sym.c_str()); ++ // -- GODOT end -- + } + + /* closes the shared library */ +diff --git a/common/sys/platform.h b/common/sys/platform.h +index 96f9aab01..08617452f 100644 +--- a/common/sys/platform.h ++++ b/common/sys/platform.h +@@ -141,6 +141,9 @@ + #define DELETED = delete + #endif + ++// -- GODOT start -- ++#if !defined(likely) ++// -- GODOT end -- + #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) + #define likely(expr) (expr) + #define unlikely(expr) (expr) +@@ -148,6 +151,9 @@ + #define likely(expr) __builtin_expect((bool)(expr),true ) + #define unlikely(expr) __builtin_expect((bool)(expr),false) + #endif ++// -- GODOT start -- ++#endif ++// -- GODOT end -- + + //////////////////////////////////////////////////////////////////////////////// + /// Error handling and debugging +diff --git a/common/tasking/taskschedulertbb.h b/common/tasking/taskschedulertbb.h +index 98dba2687..369e5edf0 100644 +--- a/common/tasking/taskschedulertbb.h ++++ b/common/tasking/taskschedulertbb.h +@@ -12,7 +12,13 @@ + #include "../sys/ref.h" + + #if defined(__WIN32__) ++// -- GODOT start -- ++#if !defined(NOMINMAX) ++// -- GODOT end -- + # define NOMINMAX ++// -- GODOT start -- ++#endif ++// -- GODOT end -- + #endif + + // We need to define these to avoid implicit linkage against From 112b4160563811fad1e849e6349f6cbb408fd38a Mon Sep 17 00:00:00 2001 From: JFonS Date: Sat, 19 Dec 2020 18:17:22 +0100 Subject: [PATCH 3/7] Implement new CPU lightmapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completely re-write the lightmap generation code: - Follow the general lightmapper code structure from 4.0. - Use proper path tracing to compute the global illumination. - Use atlassing to merge all lightmaps into a single texture (done by @RandomShaper) - Use OpenImageDenoiser to improve the generated lightmaps. - Take into account alpha transparency in material textures. - Allow baking environment lighting. - Add bicubic lightmap filtering. There is some minor compatibility breakage in some properties and methods in BakedLightmap, but lightmaps generated in previous engine versions should work fine out of the box. The scene importer has been changed to generate `.unwrap_cache` files next to the imported scene files. These files *SHOULD* be added to any version control system as they guarantee there won't be differences when re-importing the scene from other OSes or engine versions. This work started as a Google Summer of Code project; Was later funded by IMVU for a good amount of progress; Was then finished and polished by me on my free time. Co-authored-by: Pedro J. Estébanez --- core/math/geometry.cpp | 35 + core/math/geometry.h | 28 + drivers/gles2/rasterizer_scene_gles2.cpp | 5 + drivers/gles2/rasterizer_storage_gles2.cpp | 1 + drivers/gles2/rasterizer_storage_gles2.h | 1 + drivers/gles2/shaders/scene.glsl | 70 +- drivers/gles3/rasterizer_scene_gles3.cpp | 20 +- drivers/gles3/rasterizer_scene_gles3.h | 9 +- drivers/gles3/rasterizer_storage_gles3.cpp | 2 + drivers/gles3/rasterizer_storage_gles3.h | 1 + drivers/gles3/shaders/scene.glsl | 115 +- .../resource_importer_layered_texture.cpp | 9 +- editor/import/resource_importer_scene.cpp | 125 +- .../plugins/baked_lightmap_editor_plugin.cpp | 86 +- editor/plugins/baked_lightmap_editor_plugin.h | 8 +- editor/progress_dialog.cpp | 8 +- editor/progress_dialog.h | 2 +- modules/denoise/lightmap_denoiser.h | 2 +- modules/lightmapper_cpu/SCsub | 8 + modules/lightmapper_cpu/config.py | 6 + modules/lightmapper_cpu/lightmapper_cpu.cpp | 1621 +++++++++++++++++ modules/lightmapper_cpu/lightmapper_cpu.h | 182 ++ modules/lightmapper_cpu/register_types.cpp | 55 + modules/lightmapper_cpu/register_types.h | 37 + modules/xatlas_unwrap/register_types.cpp | 14 +- scene/3d/baked_lightmap.cpp | 1358 ++++++++++---- scene/3d/baked_lightmap.h | 174 +- scene/3d/lightmapper.cpp | 76 + scene/3d/lightmapper.h | 196 ++ scene/3d/visual_instance.cpp | 35 + scene/3d/visual_instance.h | 20 + scene/3d/voxel_light_baker.cpp | 872 +-------- scene/3d/voxel_light_baker.h | 21 +- scene/resources/mesh.cpp | 261 ++- scene/resources/mesh.h | 1 + scene/resources/sky.cpp | 15 +- scene/resources/sky.h | 3 + scene/resources/texture.cpp | 285 +-- scene/resources/texture.h | 18 +- servers/visual/rasterizer.h | 4 + servers/visual/visual_server_raster.h | 2 +- servers/visual/visual_server_scene.cpp | 10 +- servers/visual/visual_server_scene.h | 2 +- servers/visual/visual_server_wrap_mt.h | 2 +- servers/visual_server.cpp | 5 +- servers/visual_server.h | 2 +- thirdparty/README.md | 4 + thirdparty/stb_rect_pack/stb_rect_pack.h | 629 +++++++ 48 files changed, 4847 insertions(+), 1598 deletions(-) create mode 100644 modules/lightmapper_cpu/SCsub create mode 100644 modules/lightmapper_cpu/config.py create mode 100644 modules/lightmapper_cpu/lightmapper_cpu.cpp create mode 100644 modules/lightmapper_cpu/lightmapper_cpu.h create mode 100644 modules/lightmapper_cpu/register_types.cpp create mode 100644 modules/lightmapper_cpu/register_types.h create mode 100644 scene/3d/lightmapper.cpp create mode 100644 scene/3d/lightmapper.h create mode 100644 thirdparty/stb_rect_pack/stb_rect_pack.h diff --git a/core/math/geometry.cpp b/core/math/geometry.cpp index 2425249dfceb..83f7f67dd883 100644 --- a/core/math/geometry.cpp +++ b/core/math/geometry.cpp @@ -33,6 +33,8 @@ #include "core/print_string.h" #include "thirdparty/misc/clipper.hpp" #include "thirdparty/misc/triangulator.h" +#define STB_RECT_PACK_IMPLEMENTATION +#include "thirdparty/stb_rect_pack/stb_rect_pack.h" #define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON. @@ -1224,3 +1226,36 @@ Vector Geometry::compute_convex_mesh_points(const Plane *p_planes, int return points; } + +Vector Geometry::partial_pack_rects(const Vector &p_sizes, const Size2i &p_atlas_size) { + + Vector nodes; + nodes.resize(p_atlas_size.width); + zeromem(nodes.ptrw(), sizeof(stbrp_node) * nodes.size()); + + stbrp_context context; + stbrp_init_target(&context, p_atlas_size.width, p_atlas_size.height, nodes.ptrw(), p_atlas_size.width); + + Vector rects; + rects.resize(p_sizes.size()); + + for (int i = 0; i < p_sizes.size(); i++) { + rects.write[i].id = i; + rects.write[i].w = p_sizes[i].width; + rects.write[i].h = p_sizes[i].height; + rects.write[i].x = 0; + rects.write[i].y = 0; + rects.write[i].was_packed = 0; + } + + stbrp_pack_rects(&context, rects.ptrw(), rects.size()); + + Vector ret; + ret.resize(p_sizes.size()); + + for (int i = 0; i < p_sizes.size(); i++) { + ret.write[rects[i].id] = { rects[i].x, rects[i].y, static_cast(rects[i].was_packed) }; + } + + return ret; +} diff --git a/core/math/geometry.h b/core/math/geometry.h index 3ab108e8717b..23f4f4fce08e 100644 --- a/core/math/geometry.h +++ b/core/math/geometry.h @@ -502,6 +502,27 @@ class Geometry { return (cn.cross(an) > 0) == orientation; } + static Vector3 barycentric_coordinates_2d(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) { + // http://www.blackpawn.com/texts/pointinpoly/ + Vector2 v0 = c - a; + Vector2 v1 = b - a; + Vector2 v2 = s - a; + + // Compute dot products + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + + // Compute barycentric coordinates + double invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + double b2 = (dot11 * dot02 - dot01 * dot12) * invDenom; + double b1 = (dot00 * dot12 - dot01 * dot02) * invDenom; + double b0 = 1.0f - b2 - b1; + return Vector3(b0, b1, b2); + } + static Vector2 get_closest_point_to_segment_uncapped_2d(const Vector2 &p_point, const Vector2 *p_segment) { Vector2 p = p_point - p_segment[0]; @@ -1014,6 +1035,13 @@ class Geometry { static void make_atlas(const Vector &p_rects, Vector &r_result, Size2i &r_size); + struct PackRectsResult { + int x; + int y; + bool packed; + }; + static Vector partial_pack_rects(const Vector &p_sizes, const Size2i &p_atlas_size); + static Vector compute_convex_mesh_points(const Plane *p_planes, int p_plane_count); private: diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index ed936fefbc9f..d0e4c5a51c67 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -2572,6 +2572,9 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements, if (rebind_lightmap && lightmap) { state.scene_shader.set_uniform(SceneShaderGLES2::LIGHTMAP_ENERGY, lightmap_energy); + if (storage->config.use_lightmap_filter_bicubic) { + state.scene_shader.set_uniform(SceneShaderGLES2::LIGHTMAP_TEXTURE_SIZE, Vector2(lightmap->width, lightmap->height)); + } } state.scene_shader.set_uniform(SceneShaderGLES2::WORLD_TRANSFORM, e->instance->transform); @@ -4053,6 +4056,8 @@ void RasterizerSceneGLES2::initialize() { } void RasterizerSceneGLES2::iteration() { + storage->config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); + state.scene_shader.set_conditional(SceneShaderGLES2::USE_LIGHTMAP_FILTER_BICUBIC, storage->config.use_lightmap_filter_bicubic); shadow_filter_mode = ShadowFilterMode(int(GLOBAL_GET("rendering/quality/shadows/filter_mode"))); } diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 1c94196055d8..4a17f3274d4b 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -6299,6 +6299,7 @@ void RasterizerStorageGLES2::initialize() { config.force_vertex_shading = GLOBAL_GET("rendering/quality/shading/force_vertex_shading"); config.use_fast_texture_filter = GLOBAL_GET("rendering/quality/filters/use_nearest_mipmap_filter"); + config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); } void RasterizerStorageGLES2::finalize() { diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index 675f19c622ed..7cff4d59353c 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -57,6 +57,7 @@ class RasterizerStorageGLES2 : public RasterizerStorage { bool shrink_textures_x2; bool use_fast_texture_filter; bool use_skeleton_software; + bool use_lightmap_filter_bicubic; int max_vertex_texture_image_units; int max_texture_image_units; diff --git a/drivers/gles2/shaders/scene.glsl b/drivers/gles2/shaders/scene.glsl index b55dbec37663..d1f59c0a2a8d 100644 --- a/drivers/gles2/shaders/scene.glsl +++ b/drivers/gles2/shaders/scene.glsl @@ -889,6 +889,74 @@ void reflection_process(samplerCube reflection_map, #ifdef USE_LIGHTMAP uniform mediump sampler2D lightmap; //texunit:-4 uniform mediump float lightmap_energy; + +#ifdef USE_LIGHTMAP_FILTER_BICUBIC +uniform mediump vec2 lightmap_texture_size; + +// w0, w1, w2, and w3 are the four cubic B-spline basis functions +float w0(float a) { + return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0); +} + +float w1(float a) { + return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0); +} + +float w2(float a) { + return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0); +} + +float w3(float a) { + return (1.0 / 6.0) * (a * a * a); +} + +// g0 and g1 are the two amplitude functions +float g0(float a) { + return w0(a) + w1(a); +} + +float g1(float a) { + return w2(a) + w3(a); +} + +// h0 and h1 are the two offset functions +float h0(float a) { + return -1.0 + w1(a) / (w0(a) + w1(a)); +} + +float h1(float a) { + return 1.0 + w3(a) / (w2(a) + w3(a)); +} + +vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { + vec2 texel_size = vec2(1.0) / lightmap_texture_size; + + uv = uv * lightmap_texture_size + vec2(0.5); + + vec2 iuv = floor(uv); + vec2 fuv = fract(uv); + + float g0x = g0(fuv.x); + float g1x = g1(fuv.x); + float h0x = h0(fuv.x); + float h1x = h1(fuv.x); + float h0y = h0(fuv.y); + float h1y = h1(fuv.y); + + vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; + vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; + + return (g0(fuv.y) * (g0x * texture2D(tex, p0) + g1x * texture2D(tex, p1))) + + (g1(fuv.y) * (g0x * texture2D(tex, p2) + g1x * texture2D(tex, p3))); +} +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D_bicubic(m_tex, m_uv) + +#else //!USE_LIGHTMAP_FILTER_BICUBIC +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D(m_tex, m_uv) + +#endif //USE_LIGHTMAP_FILTER_BICUBIC #endif #ifdef USE_LIGHTMAP_CAPTURE @@ -1661,7 +1729,7 @@ FRAGMENT_SHADER_CODE #ifdef USE_LIGHTMAP //ambient light will come entirely from lightmap is lightmap is used - ambient_light = texture2D(lightmap, uv2_interp).rgb * lightmap_energy; + ambient_light = LIGHTMAP_TEXTURE_SAMPLE(lightmap, uv2_interp).rgb * lightmap_energy; #endif #ifdef USE_LIGHTMAP_CAPTURE diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 4e36ba94235b..7313b559c9af 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1949,7 +1949,17 @@ void RasterizerSceneGLES3::_setup_light(RenderList::Element *e, const Transform if (lightmap && capture) { glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 9); - glBindTexture(GL_TEXTURE_2D, lightmap->tex_id); + if (e->instance->lightmap_slice == -1) { + glBindTexture(GL_TEXTURE_2D, lightmap->tex_id); + } else { + glBindTexture(GL_TEXTURE_2D_ARRAY, lightmap->tex_id); + state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_LAYER, e->instance->lightmap_slice); + } + const Rect2 &uvr = e->instance->lightmap_uv_rect; + state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_UV_RECT, Color(uvr.get_position().x, uvr.get_position().y, uvr.get_size().x, uvr.get_size().y)); + if (storage->config.use_lightmap_filter_bicubic) { + state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_TEXTURE_SIZE, Vector2(lightmap->width, lightmap->height)); + } state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_ENERGY, capture->energy); } } @@ -2080,6 +2090,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_RADIANCE_MAP, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false); @@ -2088,6 +2099,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, e->instance->gi_probe_instances.size() > 0); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, e->instance->lightmap.is_valid() && e->instance->gi_probe_instances.size() == 0); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, e->instance->lightmap_slice != -1); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, !e->instance->lightmap_capture_data.empty() && !e->instance->lightmap.is_valid() && e->instance->gi_probe_instances.size() == 0); state.scene_shader.set_conditional(SceneShaderGLES3::SHADELESS, false); @@ -2258,6 +2270,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_VERTEX_LIGHTING, false); @@ -2392,6 +2405,9 @@ void RasterizerSceneGLES3::_add_geometry_with_material(RasterizerStorageGLES3::G if (e->instance->lightmap.is_valid()) { e->sort_key |= SORT_KEY_LIGHTMAP_FLAG; + if (e->instance->lightmap_slice != -1) { + e->sort_key |= SORT_KEY_LIGHTMAP_LAYERED_FLAG; + } } if (!e->instance->lightmap_capture_data.empty()) { @@ -5337,6 +5353,8 @@ void RasterizerSceneGLES3::iteration() { subsurface_scatter_quality = SubSurfaceScatterQuality(int(GLOBAL_GET("rendering/quality/subsurface_scattering/quality"))); subsurface_scatter_size = GLOBAL_GET("rendering/quality/subsurface_scattering/scale"); + storage->config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_FILTER_BICUBIC, storage->config.use_lightmap_filter_bicubic); state.scene_shader.set_conditional(SceneShaderGLES3::VCT_QUALITY_HIGH, GLOBAL_GET("rendering/quality/voxel_cone_tracing/high_quality")); } diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 772d8df02417..3d379a3800c0 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -680,14 +680,15 @@ class RasterizerSceneGLES3 : public RasterizerScene { SORT_KEY_OPAQUE_DEPTH_LAYER_SHIFT = 52, SORT_KEY_OPAQUE_DEPTH_LAYER_MASK = 0xF, //64 bits unsupported in MSVC -#define SORT_KEY_UNSHADED_FLAG (uint64_t(1) << 49) -#define SORT_KEY_NO_DIRECTIONAL_FLAG (uint64_t(1) << 48) -#define SORT_KEY_LIGHTMAP_CAPTURE_FLAG (uint64_t(1) << 47) +#define SORT_KEY_UNSHADED_FLAG (uint64_t(1) << 50) +#define SORT_KEY_NO_DIRECTIONAL_FLAG (uint64_t(1) << 49) +#define SORT_KEY_LIGHTMAP_CAPTURE_FLAG (uint64_t(1) << 48) +#define SORT_KEY_LIGHTMAP_LAYERED_FLAG (uint64_t(1) << 47) #define SORT_KEY_LIGHTMAP_FLAG (uint64_t(1) << 46) #define SORT_KEY_GI_PROBES_FLAG (uint64_t(1) << 45) #define SORT_KEY_VERTEX_LIT_FLAG (uint64_t(1) << 44) SORT_KEY_SHADING_SHIFT = 44, - SORT_KEY_SHADING_MASK = 63, + SORT_KEY_SHADING_MASK = 127, //44-28 material index SORT_KEY_MATERIAL_INDEX_SHIFT = 28, //28-8 geometry index diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 0a72c67497d9..13e7ce333ed9 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -8555,6 +8555,8 @@ void RasterizerStorageGLES3::initialize() { String renderer = (const char *)glGetString(GL_RENDERER); + config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); + config.use_depth_prepass = bool(GLOBAL_GET("rendering/quality/depth_prepass/enable")); if (config.use_depth_prepass) { diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index c351f265ec7b..1101d25ee0c8 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -74,6 +74,7 @@ class RasterizerStorageGLES3 : public RasterizerStorage { bool shrink_textures_x2; bool use_fast_texture_filter; bool use_anisotropic_filter; + bool use_lightmap_filter_bicubic; bool s3tc_supported; bool latc_supported; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index a45ac2eb8a60..ba430b520d7e 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -109,6 +109,10 @@ layout(std140) uniform SceneData { // ubo:0 uniform highp mat4 world_transform; +#ifdef USE_LIGHTMAP +uniform highp vec4 lightmap_uv_rect; +#endif + #ifdef USE_LIGHT_DIRECTIONAL layout(std140) uniform DirectionalLightData { //ubo:3 @@ -346,7 +350,9 @@ void main() { uv_interp = uv_attrib; #endif -#if defined(ENABLE_UV2_INTERP) || defined(USE_LIGHTMAP) +#if defined(USE_LIGHTMAP) + uv2_interp = lightmap_uv_rect.zw * uv2_attrib + lightmap_uv_rect.xy; +#elif defined(ENABLE_UV2_INTERP) uv2_interp = uv2_attrib; #endif @@ -1435,8 +1441,109 @@ void reflection_process(int idx, vec3 vertex, vec3 normal, vec3 binormal, vec3 t } #ifdef USE_LIGHTMAP +#ifdef USE_LIGHTMAP_LAYERED +uniform mediump sampler2DArray lightmap; //texunit:-9 +uniform int lightmap_layer; +#else uniform mediump sampler2D lightmap; //texunit:-9 +#endif + uniform mediump float lightmap_energy; + +#ifdef USE_LIGHTMAP_FILTER_BICUBIC +uniform vec2 lightmap_texture_size; + +// w0, w1, w2, and w3 are the four cubic B-spline basis functions +float w0(float a) { + return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0); +} + +float w1(float a) { + return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0); +} + +float w2(float a) { + return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0); +} + +float w3(float a) { + return (1.0 / 6.0) * (a * a * a); +} + +// g0 and g1 are the two amplitude functions +float g0(float a) { + return w0(a) + w1(a); +} + +float g1(float a) { + return w2(a) + w3(a); +} + +// h0 and h1 are the two offset functions +float h0(float a) { + return -1.0 + w1(a) / (w0(a) + w1(a)); +} + +float h1(float a) { + return 1.0 + w3(a) / (w2(a) + w3(a)); +} + +vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { + vec2 texel_size = vec2(1.0) / lightmap_texture_size; + + uv = uv * lightmap_texture_size + vec2(0.5); + + vec2 iuv = floor(uv); + vec2 fuv = fract(uv); + + float g0x = g0(fuv.x); + float g1x = g1(fuv.x); + float h0x = h0(fuv.x); + float h1x = h1(fuv.x); + float h0y = h0(fuv.y); + float h1y = h1(fuv.y); + + vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; + vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; + + return (g0(fuv.y) * (g0x * texture2D(tex, p0) + g1x * texture2D(tex, p1))) + + (g1(fuv.y) * (g0x * texture2D(tex, p2) + g1x * texture2D(tex, p3))); +} + +vec4 texture_bicubic(sampler2DArray tex, vec3 uv) { + vec2 texel_size = vec2(1.0) / lightmap_texture_size; + + uv.xy = uv.xy * lightmap_texture_size + vec2(0.5); + + vec2 iuv = floor(uv.xy); + vec2 fuv = fract(uv.xy); + + float g0x = g0(fuv.x); + float g1x = g1(fuv.x); + float h0x = h0(fuv.x); + float h1x = h1(fuv.x); + float h0y = h0(fuv.y); + float h1y = h1(fuv.y); + + vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; + vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; + + return (g0(fuv.y) * (g0x * texture(tex, vec3(p0, uv.z)) + g1x * texture(tex, vec3(p1, uv.z)))) + + (g1(fuv.y) * (g0x * texture(tex, vec3(p2, uv.z)) + g1x * texture(tex, vec3(p3, uv.z)))); +} + +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D_bicubic(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) texture_bicubic(m_tex, m_uv) + +#else //!USE_LIGHTMAP_FILTER_BICUBIC +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) texture(m_tex, m_uv) + +#endif //USE_LIGHTMAP_FILTER_BICUBIC #endif #ifdef USE_LIGHTMAP_CAPTURE @@ -1823,7 +1930,11 @@ FRAGMENT_SHADER_CODE #endif #ifdef USE_LIGHTMAP - ambient_light = texture(lightmap, uv2).rgb * lightmap_energy; +#ifdef USE_LIGHTMAP_LAYERED + ambient_light = LIGHTMAP_TEXTURE_LAYERED_SAMPLE(lightmap, vec3(uv2, float(lightmap_layer))).rgb * lightmap_energy; +#else + ambient_light = LIGHTMAP_TEXTURE_SAMPLE(lightmap, uv2).rgb * lightmap_energy; +#endif #endif #ifdef USE_LIGHTMAP_CAPTURE diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index cdbc5b264c67..1b0b4028bac9 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -107,16 +107,17 @@ void ResourceImporterLayeredTexture::_save_tex(const Vector > &p_imag f->store_32(p_images[0]->get_height()); f->store_32(p_images.size()); //depth f->store_32(p_texture_flags); + + if ((p_compress_mode == COMPRESS_LOSSLESS) && p_images[0]->get_format() > Image::FORMAT_RGBA8) { + p_compress_mode = COMPRESS_UNCOMPRESSED; //these can't go as lossy + } + if (p_compress_mode != COMPRESS_VIDEO_RAM) { //vram needs to do a first compression to tell what the format is, for the rest its ok f->store_32(p_images[0]->get_format()); f->store_32(p_compress_mode); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed } - if ((p_compress_mode == COMPRESS_LOSSLESS) && p_images[0]->get_format() > Image::FORMAT_RGBA8) { - p_compress_mode = COMPRESS_UNCOMPRESSED; //these can't go as lossy - } - for (int i = 0; i < p_images.size(); i++) { switch (p_compress_mode) { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index d2012508a51c..589a916e9547 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -931,9 +931,6 @@ static String _make_extname(const String &p_str) { void ResourceImporterScene::_find_meshes(Node *p_node, Map, Transform> &meshes) { - List pi; - p_node->get_property_list(&pi); - MeshInstance *mi = Object::cast_to(p_node); if (mi) { @@ -941,11 +938,11 @@ void ResourceImporterScene::_find_meshes(Node *p_node, Map, Trans Ref mesh = mi->get_mesh(); if (mesh.is_valid() && !meshes.has(mesh)) { - Spatial *s = mi; + Spatial *s = Object::cast_to(mi); Transform transform; while (s) { transform = transform * s->get_transform(); - s = s->get_parent_spatial(); + s = Object::cast_to(s->get_parent()); } meshes[mesh] = transform; @@ -1427,29 +1424,117 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p Map, Transform> meshes; _find_meshes(scene, meshes); - if (light_bake_mode == 2) { + String file_id = src_path.get_file(); + String cache_file_path = base_path.plus_file(file_id + ".unwrap_cache"); + + int *cache_data = nullptr; + unsigned int cache_size = 0; + + if (FileAccess::exists(cache_file_path)) { + Error err2; + FileAccess *file = FileAccess::open(cache_file_path, FileAccess::READ, &err2); + + if (!err2) { + cache_size = file->get_len(); + cache_data = (int *)memalloc(cache_size); + file->get_buffer((unsigned char *)cache_data, cache_size); + } + + if (file) + memdelete(file); + } + + float texel_size = p_options["meshes/lightmap_texel_size"]; + texel_size = MAX(0.001, texel_size); + + Map used_meshes; + + EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size()); + int step = 0; + for (Map, Transform>::Element *E = meshes.front(); E; E = E->next()) { + + Ref mesh = E->key(); + String name = mesh->get_name(); + if (name == "") { //should not happen but.. + name = "Mesh " + itos(step); + } + + progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step); + + int *ret_cache_data = cache_data; + unsigned int ret_cache_size = cache_size; + bool ret_used_cache = true; // Tell the unwrapper to use the cache + Error err2 = mesh->lightmap_unwrap_cached(ret_cache_data, ret_cache_size, ret_used_cache, E->get(), texel_size); - float texel_size = p_options["meshes/lightmap_texel_size"]; - texel_size = MAX(0.001, texel_size); + if (err2 != OK) { + EditorNode::add_io_error("Mesh '" + name + "' failed lightmap generation. Please fix geometry."); + } else { + + String hash = String::md5((unsigned char *)ret_cache_data); + used_meshes.insert(hash, ret_cache_size); + + if (!ret_used_cache) { + // Cache was not used, add the generated entry to the current cache + + unsigned int new_cache_size = cache_size + ret_cache_size + (cache_size == 0 ? 4 : 0); + int *new_cache_data = (int *)memalloc(new_cache_size); + + if (cache_size == 0) { + // Cache was empty + new_cache_data[0] = 0; + cache_size = 4; + } else { + memcpy(new_cache_data, cache_data, cache_size); + memfree(cache_data); + } + + memcpy(&new_cache_data[cache_size / sizeof(int)], ret_cache_data, ret_cache_size); - EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size()); - int step = 0; - for (Map, Transform>::Element *E = meshes.front(); E; E = E->next()) { + cache_data = new_cache_data; + cache_size = new_cache_size; - Ref mesh = E->key(); - String name = mesh->get_name(); - if (name == "") { //should not happen but.. - name = "Mesh " + itos(step); + cache_data[0]++; // Increase entry count } + } + step++; + } - progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step); + Error err2; + FileAccess *file = FileAccess::open(cache_file_path, FileAccess::WRITE, &err2); + + if (err2) { + if (file) + memdelete(file); + } else { - Error err2 = mesh->lightmap_unwrap(E->get(), texel_size); - if (err2 != OK) { - EditorNode::add_io_error("Mesh '" + name + "' failed lightmap generation. Please fix geometry."); + // Store number of entries + file->store_32(used_meshes.size()); + + // Store cache entries + unsigned int r_idx = 1; + for (int i = 0; i < cache_data[0]; ++i) { + unsigned char *entry_start = (unsigned char *)&cache_data[r_idx]; + String entry_hash = String::md5(entry_start); + if (used_meshes.has(entry_hash)) { + unsigned int entry_size = used_meshes[entry_hash]; + file->store_buffer(entry_start, entry_size); } - step++; + + r_idx += 4; // hash + r_idx += 2; // size hint + + int vertex_count = cache_data[r_idx]; + r_idx += 1; // vertex count + r_idx += vertex_count; // vertex + r_idx += vertex_count * 2; // uvs + + int index_count = cache_data[r_idx]; + r_idx += 1; // index count + r_idx += index_count; // indices } + + file->close(); + memfree(cache_data); } } diff --git a/editor/plugins/baked_lightmap_editor_plugin.cpp b/editor/plugins/baked_lightmap_editor_plugin.cpp index 059448eb225b..8f10236e8e7f 100644 --- a/editor/plugins/baked_lightmap_editor_plugin.cpp +++ b/editor/plugins/baked_lightmap_editor_plugin.cpp @@ -30,32 +30,58 @@ #include "baked_lightmap_editor_plugin.h" -void BakedLightmapEditorPlugin::_bake() { - +void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { BakedLightmap::BakeError err; if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) { - err = lightmap->bake(lightmap); + err = lightmap->bake(lightmap, p_file); } else { - err = lightmap->bake(lightmap->get_parent()); + err = lightmap->bake(lightmap->get_parent(), p_file); } + bake_func_end(); + switch (err) { - case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: - EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene (for images to be saved in the same dir), or pick a save path from the BakedLightmap properties.")); - break; + case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: { + String scene_path = lightmap->get_filename(); + if (scene_path == String()) { + scene_path = lightmap->get_owner()->get_filename(); + } + if (scene_path == String()) { + EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene and try again.")); + break; + } + scene_path = scene_path.get_basename() + ".lmbake"; + + file_dialog->set_current_path(scene_path); + file_dialog->popup_centered_ratio(); + + } break; case BakedLightmap::BAKE_ERROR_NO_MESHES: EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); break; case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE: EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); break; + case BakedLightmap::BAKE_ERROR_LIGHTMAP_SIZE: + EditorNode::get_singleton()->show_warning(TTR("Failed determining lightmap size. Maximum lightmap size too small?")); + break; + case BakedLightmap::BAKE_ERROR_INVALID_MESH: + EditorNode::get_singleton()->show_warning(TTR("Some mesh is invalid. Make sure the UV2 channel values are conatined within the [0.0,1.0] square region.")); + break; + case BakedLightmap::BAKE_ERROR_NO_LIGHTMAPPER: + EditorNode::get_singleton()->show_warning(TTR("Godot editor was built without ray tracing support, lightmaps can't be baked.")); + break; default: { } } } } +void BakedLightmapEditorPlugin::_bake() { + _bake_select_file(""); +} + void BakedLightmapEditorPlugin::edit(Object *p_object) { BakedLightmap *s = Object::cast_to(p_object); @@ -81,29 +107,40 @@ void BakedLightmapEditorPlugin::make_visible(bool p_visible) { } EditorProgress *BakedLightmapEditorPlugin::tmp_progress = NULL; +EditorProgress *BakedLightmapEditorPlugin::tmp_subprogress = NULL; -void BakedLightmapEditorPlugin::bake_func_begin(int p_steps) { - - ERR_FAIL_COND(tmp_progress != NULL); - - tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), p_steps, true)); +bool BakedLightmapEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_force_refresh) { + if (!tmp_progress) { + tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), 1000, true)); + ERR_FAIL_COND_V(tmp_progress == nullptr, false); + } + return tmp_progress->step(p_description, p_progress * 1000, p_force_refresh); } -bool BakedLightmapEditorPlugin::bake_func_step(int p_step, const String &p_description) { - - ERR_FAIL_COND_V(tmp_progress == NULL, false); - return tmp_progress->step(p_description, p_step, false); +bool BakedLightmapEditorPlugin::bake_func_substep(float p_progress, const String &p_description, void *, bool p_force_refresh) { + if (!tmp_subprogress) { + tmp_subprogress = memnew(EditorProgress("bake_lightmaps_substep", "", 1000, true)); + ERR_FAIL_COND_V(tmp_subprogress == nullptr, false); + } + return tmp_subprogress->step(p_description, p_progress * 1000, p_force_refresh); } void BakedLightmapEditorPlugin::bake_func_end() { - ERR_FAIL_COND(tmp_progress == NULL); - memdelete(tmp_progress); - tmp_progress = NULL; + if (tmp_progress != nullptr) { + memdelete(tmp_progress); + tmp_progress = nullptr; + } + + if (tmp_subprogress != nullptr) { + memdelete(tmp_subprogress); + tmp_subprogress = nullptr; + } } void BakedLightmapEditorPlugin::_bind_methods() { ClassDB::bind_method("_bake", &BakedLightmapEditorPlugin::_bake); + ClassDB::bind_method("_bake_select_file", &BakedLightmapEditorPlugin::_bake_select_file); } BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) { @@ -114,12 +151,19 @@ BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) { bake->set_text(TTR("Bake Lightmaps")); bake->hide(); bake->connect("pressed", this, "_bake"); + + file_dialog = memnew(EditorFileDialog); + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->add_filter("*.lmbake ; LightMap Bake"); + file_dialog->set_title(TTR("Select lightmap bake file:")); + file_dialog->connect("file_selected", this, "_bake_select_file"); + bake->add_child(file_dialog); + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake); lightmap = NULL; - BakedLightmap::bake_begin_function = bake_func_begin; BakedLightmap::bake_step_function = bake_func_step; - BakedLightmap::bake_end_function = bake_func_end; + BakedLightmap::bake_substep_function = bake_func_substep; } BakedLightmapEditorPlugin::~BakedLightmapEditorPlugin() { diff --git a/editor/plugins/baked_lightmap_editor_plugin.h b/editor/plugins/baked_lightmap_editor_plugin.h index abd39b5ef446..5a4a6189b43b 100644 --- a/editor/plugins/baked_lightmap_editor_plugin.h +++ b/editor/plugins/baked_lightmap_editor_plugin.h @@ -45,11 +45,15 @@ class BakedLightmapEditorPlugin : public EditorPlugin { ToolButton *bake; EditorNode *editor; + EditorFileDialog *file_dialog; static EditorProgress *tmp_progress; - static void bake_func_begin(int p_steps); - static bool bake_func_step(int p_step, const String &p_description); + static EditorProgress *tmp_subprogress; + + static bool bake_func_step(float p_progress, const String &p_description, void *, bool p_force_refresh); + static bool bake_func_substep(float p_progress, const String &p_description, void *, bool p_force_refresh); static void bake_func_end(); + void _bake_select_file(const String &p_file); void _bake(); protected: diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp index 7a09380387db..42aebcb2e04a 100644 --- a/editor/progress_dialog.cpp +++ b/editor/progress_dialog.cpp @@ -180,6 +180,7 @@ void ProgressDialog::add_task(const String &p_task, const String &p_label, int p t.progress = memnew(ProgressBar); t.progress->set_max(p_steps); t.progress->set_value(p_steps); + t.last_progress_tick = 0; vb2->add_child(t.progress); t.state = memnew(Label); t.state->set_clip_text(true); @@ -204,20 +205,20 @@ bool ProgressDialog::task_step(const String &p_task, const String &p_state, int ERR_FAIL_COND_V(!tasks.has(p_task), cancelled); + Task &t = tasks[p_task]; if (!p_force_redraw) { uint64_t tus = OS::get_singleton()->get_ticks_usec(); - if (tus - last_progress_tick < 200000) //200ms + if (tus - t.last_progress_tick < 200000) //200ms return cancelled; } - Task &t = tasks[p_task]; if (p_step < 0) t.progress->set_value(t.progress->get_value() + 1); else t.progress->set_value(p_step); t.state->set_text(p_state); - last_progress_tick = OS::get_singleton()->get_ticks_usec(); + t.last_progress_tick = OS::get_singleton()->get_ticks_usec(); if (cancel_hb->is_visible()) { OS::get_singleton()->force_process_input(); } @@ -254,7 +255,6 @@ ProgressDialog::ProgressDialog() { add_child(main); main->set_anchors_and_margins_preset(Control::PRESET_WIDE); set_exclusive(true); - last_progress_tick = 0; singleton = this; cancel_hb = memnew(HBoxContainer); main->add_child(cancel_hb); diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h index bdd4c0ffd1d2..0a100a2a7f2d 100644 --- a/editor/progress_dialog.h +++ b/editor/progress_dialog.h @@ -77,13 +77,13 @@ class ProgressDialog : public Popup { VBoxContainer *vb; ProgressBar *progress; Label *state; + uint64_t last_progress_tick; }; HBoxContainer *cancel_hb; Button *cancel; Map tasks; VBoxContainer *main; - uint64_t last_progress_tick; static ProgressDialog *singleton; void _popup(); diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h index 74a9d8af8669..e7f5c23637b8 100644 --- a/modules/denoise/lightmap_denoiser.h +++ b/modules/denoise/lightmap_denoiser.h @@ -45,7 +45,7 @@ class LightmapDenoiserOIDN : public LightmapDenoiser { public: static LightmapDenoiser *create_oidn_denoiser(); - Ref denoise_image(const Ref &p_image) override; + Ref denoise_image(const Ref &p_image); static void make_default_denoiser(); diff --git a/modules/lightmapper_cpu/SCsub b/modules/lightmapper_cpu/SCsub new file mode 100644 index 000000000000..4fbb1b6b1dd9 --- /dev/null +++ b/modules/lightmapper_cpu/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_lightmapper_rd = env_modules.Clone() +# Godot source files +env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/lightmapper_cpu/config.py b/modules/lightmapper_cpu/config.py new file mode 100644 index 000000000000..d01c1726dd34 --- /dev/null +++ b/modules/lightmapper_cpu/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env["tools"] and env["module_raycast_enabled"] + + +def configure(env): + pass diff --git a/modules/lightmapper_cpu/lightmapper_cpu.cpp b/modules/lightmapper_cpu/lightmapper_cpu.cpp new file mode 100644 index 000000000000..375d20976e18 --- /dev/null +++ b/modules/lightmapper_cpu/lightmapper_cpu.cpp @@ -0,0 +1,1621 @@ +/*************************************************************************/ +/* lightmapper_cpu.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "lightmapper_cpu.h" +#include "core/math/geometry.h" +#include "core/os/os.h" +#include "core/os/threaded_array_processor.h" +#include "core/project_settings.h" +#include "modules/raycast/lightmap_raycaster.h" + +Error LightmapperCPU::_layout_atlas(int p_max_size, Vector2i *r_atlas_size, int *r_atlas_slices) { + + Vector2i atlas_size; + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + Vector2i size = mesh_instances[i].size; + atlas_size.width = MAX(atlas_size.width, size.width + 2); + atlas_size.height = MAX(atlas_size.height, size.height + 2); + } + } + + int max = nearest_power_of_2_templated(atlas_size.width); + max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); + + if (max > p_max_size) { + return ERR_INVALID_DATA; + } + + Vector2i best_atlas_size; + int best_atlas_slices = 0; + int best_atlas_memory = 0x7FFFFFFF; + float best_atlas_mem_utilization = 0; + Vector best_atlas_offsets; + Vector best_scaled_sizes; + + int first_try_mem_occupied = 0; + int first_try_mem_used = 0; + for (int recovery_percent = 0; recovery_percent <= 100; recovery_percent += 10) { + // These only make sense from the second round of the loop + float recovery_scale = 1; + int target_mem_occupied = 0; + if (recovery_percent != 0) { + target_mem_occupied = first_try_mem_occupied + (first_try_mem_used - first_try_mem_occupied) * recovery_percent * 0.01f; + float new_squared_recovery_scale = static_cast(target_mem_occupied) / first_try_mem_occupied; + if (new_squared_recovery_scale > 1.0f) { + recovery_scale = Math::sqrt(new_squared_recovery_scale); + } + } + + atlas_size = Vector2i(max, max); + while (atlas_size.x <= p_max_size && atlas_size.y <= p_max_size) { + + if (recovery_percent != 0) { + // Find out how much memory is not recoverable (because of lightmaps that can't grow), + // to compute a greater recovery scale for those that can. + int mem_unrecoverable = 0; + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + Vector2i scaled_size = Vector2i( + static_cast(recovery_scale * mesh_instances[i].size.x), + static_cast(recovery_scale * mesh_instances[i].size.y)); + if (scaled_size.x + 2 > atlas_size.x || scaled_size.y + 2 > atlas_size.y) { + mem_unrecoverable += scaled_size.x * scaled_size.y - mesh_instances[i].size.x * mesh_instances[i].size.y; + } + } + } + float new_squared_recovery_scale = static_cast(target_mem_occupied - mem_unrecoverable) / (first_try_mem_occupied - mem_unrecoverable); + if (new_squared_recovery_scale > 1.0f) { + recovery_scale = Math::sqrt(new_squared_recovery_scale); + } + } + + Vector scaled_sizes; + scaled_sizes.resize(mesh_instances.size()); + { + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + if (recovery_percent == 0) { + scaled_sizes.write[i] = mesh_instances[i].size; + } else { + Vector2i scaled_size = Vector2i( + static_cast(recovery_scale * mesh_instances[i].size.x), + static_cast(recovery_scale * mesh_instances[i].size.y)); + if (scaled_size.x + 2 <= atlas_size.x && scaled_size.y + 2 <= atlas_size.y) { + scaled_sizes.write[i] = scaled_size; + } else { + scaled_sizes.write[i] = mesh_instances[i].size; + } + } + } else { + // Don't consider meshes with no generated lightmap here; will compensate later + scaled_sizes.write[i] = Vector2i(); + } + } + } + + Vector source_sizes; + source_sizes.resize(scaled_sizes.size()); + Vector source_indices; + source_indices.resize(scaled_sizes.size()); + for (int i = 0; i < source_sizes.size(); i++) { + source_sizes.write[i] = scaled_sizes[i] + Vector2i(2, 2); // Add padding between lightmaps + source_indices.write[i] = i; + } + + Vector curr_atlas_offsets; + curr_atlas_offsets.resize(source_sizes.size()); + + int slices = 0; + + while (source_sizes.size() > 0) { + + Vector offsets = Geometry::partial_pack_rects(source_sizes, atlas_size); + Vector new_indices; + Vector new_sources; + for (int i = 0; i < offsets.size(); i++) { + Geometry::PackRectsResult ofs = offsets[i]; + int sidx = source_indices[i]; + if (ofs.packed) { + curr_atlas_offsets.write[sidx] = { slices, ofs.x + 1, ofs.y + 1 }; + } else { + new_indices.push_back(sidx); + new_sources.push_back(source_sizes[i]); + } + } + + source_sizes = new_sources; + source_indices = new_indices; + slices++; + } + + int mem_used = atlas_size.x * atlas_size.y * slices; + int mem_occupied = 0; + for (int i = 0; i < curr_atlas_offsets.size(); i++) { + mem_occupied += scaled_sizes[i].x * scaled_sizes[i].y; + } + + float mem_utilization = static_cast(mem_occupied) / mem_used; + if (slices * atlas_size.y < 16384) { // Maximum Image size + if (mem_used < best_atlas_memory || (mem_used == best_atlas_memory && mem_utilization > best_atlas_mem_utilization)) { + best_atlas_size = atlas_size; + best_atlas_offsets = curr_atlas_offsets; + best_atlas_slices = slices; + best_atlas_memory = mem_used; + best_atlas_mem_utilization = mem_utilization; + best_scaled_sizes = scaled_sizes; + } + } + + if (recovery_percent == 0) { + first_try_mem_occupied = mem_occupied; + first_try_mem_used = mem_used; + } + + if (atlas_size.width == atlas_size.height) { + atlas_size.width *= 2; + } else { + atlas_size.height *= 2; + } + } + } + + if (best_atlas_size == Vector2i()) { + return ERR_INVALID_DATA; + } + + *r_atlas_size = best_atlas_size; + *r_atlas_slices = best_atlas_slices; + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (best_scaled_sizes[i] != Vector2i()) { + mesh_instances[i].size = best_scaled_sizes[i]; + mesh_instances[i].offset = Vector2i(best_atlas_offsets[i].x, best_atlas_offsets[i].y); + mesh_instances[i].slice = best_atlas_offsets[i].slice; + } + } + return OK; +} + +void LightmapperCPU::_thread_func_callback(void *p_thread_data) { + ThreadData *thread_data = reinterpret_cast(p_thread_data); + thread_process_array(thread_data->count, thread_data->instance, &LightmapperCPU::_thread_func_wrapper, thread_data); +} + +void LightmapperCPU::_thread_func_wrapper(uint32_t p_idx, ThreadData *p_thread_data) { + + if (thread_cancelled) { + return; + } + + (p_thread_data->instance->*p_thread_data->thread_func)(p_idx, p_thread_data->userdata); + + thread_progress++; +} + +bool LightmapperCPU::_parallel_run(int p_count, const String &p_description, BakeThreadFunc p_thread_func, void *p_userdata, BakeStepFunc p_substep_func) { + + bool cancelled = false; + if (p_substep_func) { + cancelled = p_substep_func(0.0f, vformat("%s (%d/%d)", p_description, 0, p_count), nullptr, false); + } + + thread_progress = 0; + thread_cancelled = false; + +#ifdef NO_THREAD + for (int i = 0; !cancelled && i < p_count; i++) { + (this->*p_thread_func)(i, p_userdata); + float p = float(i) / p_count; + if (p_substep_func) { + cancelled = p_substep_func(p, vformat("%s (%d/%d)", p_description, i + 1, p_count), nullptr, false); + } + } +#else + + if (p_count == 0) { + return cancelled; + } + + ThreadData td; + td.instance = this; + td.count = p_count; + td.thread_func = p_thread_func; + td.userdata = p_userdata; + Thread *runner_thread = Thread::create(_thread_func_callback, &td); + + int progress = thread_progress; + + while (!cancelled && progress < p_count) { + float p = float(progress) / p_count; + if (p_substep_func) { + cancelled = p_substep_func(p, vformat("%s (%d/%d)", p_description, progress + 1, p_count), nullptr, false); + } + progress = thread_progress; + } + thread_cancelled = cancelled; + Thread::wait_to_finish(runner_thread); +#endif + + thread_cancelled = false; + + return cancelled; +} + +void LightmapperCPU::_generate_buffer(uint32_t p_idx, void *p_unused) { + + const Size2i &size = mesh_instances[p_idx].size; + + int buffer_size = size.x * size.y; + + LocalVector &lightmap = scene_lightmaps[p_idx]; + LocalVector &lightmap_indices = scene_lightmap_indices[p_idx]; + + lightmap_indices.resize(buffer_size); + + for (unsigned int i = 0; i < lightmap_indices.size(); i++) { + lightmap_indices[i] = -1; + } + + MeshData &md = mesh_instances[p_idx].data; + + LocalVector > albedo_images; + LocalVector > emission_images; + + for (int surface_id = 0; surface_id < md.albedo.size(); surface_id++) { + albedo_images.push_back(_init_bake_texture(md.albedo[surface_id], albedo_textures, Image::FORMAT_RGBA8)); + emission_images.push_back(_init_bake_texture(md.emission[surface_id], emission_textures, Image::FORMAT_RGBH)); + } + + int surface_id = 0; + int surface_facecount = 0; + const Vector3 *points_ptr = md.points.ptr(); + const Vector3 *normals_ptr = md.normal.ptr(); + const Vector2 *uvs_ptr = md.uv.empty() ? nullptr : md.uv.ptr(); + const Vector2 *uv2s_ptr = md.uv2.ptr(); + + for (int i = 0; i < md.points.size() / 3; i++) { + + Ref albedo = albedo_images[surface_id]; + Ref emission = emission_images[surface_id]; + + albedo->lock(); + emission->lock(); + _plot_triangle(&(uv2s_ptr[i * 3]), &(points_ptr[i * 3]), &(normals_ptr[i * 3]), uvs_ptr ? &(uvs_ptr[i * 3]) : nullptr, albedo, emission, size, lightmap, lightmap_indices); + albedo->unlock(); + emission->unlock(); + + surface_facecount++; + if (surface_facecount == md.surface_facecounts[surface_id]) { + surface_id++; + surface_facecount = 0; + } + } +} + +Ref LightmapperCPU::_init_bake_texture(const MeshData::TextureDef &p_texture_def, const Map > &p_tex_cache, Image::Format p_default_format) { + Ref ret; + if (p_texture_def.tex_rid.is_valid()) { + ret = p_tex_cache[p_texture_def.tex_rid]->duplicate(); + ret->lock(); + for (int j = 0; j < ret->get_height(); j++) { + for (int i = 0; i < ret->get_width(); i++) { + ret->set_pixel(i, j, ret->get_pixel(i, j) * p_texture_def.mul + p_texture_def.add); + } + } + ret->unlock(); + } else { + ret.instance(); + ret->create(8, 8, false, p_default_format); + ret->fill(p_texture_def.add * p_texture_def.mul); + } + return ret; +} + +Color LightmapperCPU::_bilinear_sample(const Ref &p_img, const Vector2 &p_uv, bool p_clamp_x, bool p_clamp_y) { + + int width = p_img->get_width(); + int height = p_img->get_height(); + + Vector2 uv; + uv.x = p_clamp_x ? p_uv.x : Math::fposmod(p_uv.x, 1.0f); + uv.y = p_clamp_y ? p_uv.y : Math::fposmod(p_uv.y, 1.0f); + + float xf = uv.x * width; + float yf = uv.y * height; + + int xi = (int)xf; + int yi = (int)yf; + + Color texels[4]; + for (int i = 0; i < 4; i++) { + int sample_x = xi + i % 2; + int sample_y = yi + i / 2; + + sample_x = CLAMP(sample_x, 0, width - 1); + sample_y = CLAMP(sample_y, 0, height - 1); + + texels[i] = p_img->get_pixel(sample_x, sample_y); + } + + float tx = xf - xi; + float ty = yf - yi; + + Color c = Color(0, 0, 0, 0); + for (int i = 0; i < 4; i++) { + c[i] = Math::lerp(Math::lerp(texels[0][i], texels[1][i], tx), Math::lerp(texels[2][i], texels[3][i], tx), ty); + } + return c; +} + +Vector3 LightmapperCPU::_fix_sample_position(const Vector3 &p_position, const Vector3 &p_texel_center, const Vector3 &p_normal, const Vector3 &p_tangent, const Vector3 &p_bitangent, const Vector2 &p_texel_size) { + + Basis tangent_basis(p_tangent, p_bitangent, p_normal); + tangent_basis.orthonormalize(); + Vector2 half_size = p_texel_size / 2.0f; + Vector3 corrected = p_position; + + for (int i = -1; i <= 1; i += 1) { + for (int j = -1; j <= 1; j += 1) { + if (i == 0 && j == 0) continue; + Vector3 offset = Vector3(half_size.x * i, half_size.y * j, 0.0); + Vector3 rotated_offset = tangent_basis.xform_inv(offset); + Vector3 target = p_texel_center + rotated_offset; + Vector3 ray_vector = target - corrected; + + Vector3 ray_back_offset = -ray_vector.normalized() * parameters.bias; + Vector3 ray_origin = corrected + ray_back_offset; + ray_vector = target - ray_origin; + float ray_length = ray_vector.length(); + LightmapRaycaster::Ray ray(ray_origin + p_normal * parameters.bias, ray_vector.normalized(), 0.0f, ray_length + parameters.bias); + + bool hit = raycaster->intersect(ray); + if (hit) { + ray.normal.normalize(); + if (ray.normal.dot(ray_vector.normalized()) > 0.0f) { + corrected = ray_origin + ray.dir * ray.tfar + ray.normal * (parameters.bias * 2.0f); + } + } + } + } + + return corrected; +} + +void LightmapperCPU::_plot_triangle(const Vector2 *p_vertices, const Vector3 *p_positions, const Vector3 *p_normals, const Vector2 *p_uvs, const Ref &p_albedo, const Ref &p_emission, Vector2i p_size, LocalVector &r_lightmap, LocalVector &r_lightmap_indices) { + Vector2 pv0 = p_vertices[0]; + Vector2 pv1 = p_vertices[1]; + Vector2 pv2 = p_vertices[2]; + + Vector2 v0 = pv0 * p_size; + Vector2 v1 = pv1 * p_size; + Vector2 v2 = pv2 * p_size; + + Vector3 p0 = p_positions[0]; + Vector3 p1 = p_positions[1]; + Vector3 p2 = p_positions[2]; + + Vector3 n0 = p_normals[0]; + Vector3 n1 = p_normals[1]; + Vector3 n2 = p_normals[2]; + + Vector2 uv0 = p_uvs == nullptr ? Vector2(0.5f, 0.5f) : p_uvs[0]; + Vector2 uv1 = p_uvs == nullptr ? Vector2(0.5f, 0.5f) : p_uvs[1]; + Vector2 uv2 = p_uvs == nullptr ? Vector2(0.5f, 0.5f) : p_uvs[2]; + +#define edgeFunction(a, b, c) ((c)[0] - (a)[0]) * ((b)[1] - (a)[1]) - ((c)[1] - (a)[1]) * ((b)[0] - (a)[0]) + + if (edgeFunction(v0, v1, v2) < 0.0) { + SWAP(pv1, pv2); + SWAP(v1, v2); + SWAP(p1, p2); + SWAP(n1, n2); + SWAP(uv1, uv2); + } + + Vector3 edge1 = p1 - p0; + Vector3 edge2 = p2 - p0; + + Vector2 uv_edge1 = pv1 - pv0; + Vector2 uv_edge2 = pv2 - pv0; + + float r = 1.0f / (uv_edge1.x * uv_edge2.y - uv_edge1.y * uv_edge2.x); + + Vector3 tangent = (edge1 * uv_edge2.y - edge2 * uv_edge1.y) * r; + Vector3 bitangent = (edge2 * uv_edge1.x - edge1 * uv_edge2.x) * r; + + tangent.normalize(); + bitangent.normalize(); + + // Compute triangle bounding box + Vector2 bbox_min = Vector2(MIN(v0.x, MIN(v1.x, v2.x)), MIN(v0.y, MIN(v1.y, v2.y))); + Vector2 bbox_max = Vector2(MAX(v0.x, MAX(v1.x, v2.x)), MAX(v0.y, MAX(v1.y, v2.y))); + + bbox_min = bbox_min.floor(); + bbox_max = bbox_max.ceil(); + + uint32_t min_x = MAX(bbox_min.x - 2, 0); + uint32_t min_y = MAX(bbox_min.y - 2, 0); + uint32_t max_x = MIN(bbox_max.x, p_size.x - 1); + uint32_t max_y = MIN(bbox_max.y, p_size.y - 1); + + Vector2 texel_size; + Vector2 centroid = (v0 + v1 + v2) / 3.0f; + Vector3 centroid_pos = (p0 + p1 + p2) / 3.0f; + for (int i = 0; i < 2; i++) { + Vector2 p = centroid; + p[i] += 1; + Vector3 bary = Geometry::barycentric_coordinates_2d(p, v0, v1, v2); + Vector3 pos = p0 * bary[0] + p1 * bary[1] + p2 * bary[2]; + texel_size[i] = centroid_pos.distance_to(pos); + } + + Vector pixel_polygon; + pixel_polygon.resize(4); + static const Vector2 corners[4] = { Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 0) }; + + Vector triangle_polygon; + triangle_polygon.push_back(v0); + triangle_polygon.push_back(v1); + triangle_polygon.push_back(v2); + + for (uint32_t j = min_y; j <= max_y; ++j) { + for (uint32_t i = min_x; i <= max_x; i++) { + + int ofs = j * p_size.x + i; + int texel_idx = r_lightmap_indices[ofs]; + + if (texel_idx >= 0 && r_lightmap[texel_idx].area_coverage >= 0.5f) { + continue; + } + + Vector3 barycentric_coords; + float area_coverage = 0.0f; + bool intersected = false; + + for (int k = 0; k < 4; k++) { + pixel_polygon.write[k] = Vector2(i, j) + corners[k]; + } + + const float max_dist = 0.05; + bool v0eqv1 = v0.distance_squared_to(v1) < max_dist; + bool v1eqv2 = v1.distance_squared_to(v2) < max_dist; + bool v2eqv0 = v2.distance_squared_to(v0) < max_dist; + if (v0eqv1 && v1eqv2 && v2eqv0) { + intersected = true; + barycentric_coords = Vector3(1, 0, 0); + } else if (v0eqv1 || v1eqv2 || v2eqv0) { + + Vector segment; + segment.resize(2); + if (v0eqv1) { + segment.write[0] = v0; + segment.write[1] = v2; + } else if (v1eqv2) { + segment.write[0] = v1; + segment.write[1] = v0; + } else { + segment.write[0] = v0; + segment.write[1] = v1; + } + + Vector > intersected_segments = Geometry::intersect_polyline_with_polygon_2d(segment, pixel_polygon); + ERR_FAIL_COND_MSG(intersected_segments.size() > 1, "[Lightmapper] Itersecting a segment and a convex polygon should give at most one segment."); + if (!intersected_segments.empty()) { + const Vector &intersected_segment = intersected_segments[0]; + ERR_FAIL_COND_MSG(intersected_segment.size() != 2, "[Lightmapper] Itersecting a segment and a convex polygon should give at most one segment."); + Vector2 sample_pos = (intersected_segment[0] + intersected_segment[1]) / 2.0f; + + float u = (segment[0].distance_to(sample_pos)) / (segment[0].distance_to(segment[1])); + float v = (1.0f - u) / 2.0f; + intersected = true; + if (v0eqv1) { + barycentric_coords = Vector3(v, v, u); + } else if (v1eqv2) { + barycentric_coords = Vector3(u, v, v); + } else { + barycentric_coords = Vector3(v, u, v); + } + } + + } else if (edgeFunction(v0, v1, v2) < 0.005) { + Vector2 direction = v0 - v1; + Vector2 perpendicular = Vector2(direction.y, -direction.x); + + Vector line; + int middle_vertex; + + if (SGN(edgeFunction(v0, v0 + perpendicular, v1)) != SGN(edgeFunction(v0, v0 + perpendicular, v2))) { + line.push_back(v1); + line.push_back(v2); + middle_vertex = 0; + } else if (SGN(edgeFunction(v1, v1 + perpendicular, v0)) != SGN(edgeFunction(v1, v1 + perpendicular, v2))) { + line.push_back(v0); + line.push_back(v2); + middle_vertex = 1; + } else { + line.push_back(v0); + line.push_back(v1); + middle_vertex = 2; + } + + Vector > intersected_lines = Geometry::intersect_polyline_with_polygon_2d(line, pixel_polygon); + + ERR_FAIL_COND_MSG(intersected_lines.size() > 1, "[Lightmapper] Itersecting a line and a convex polygon should give at most one line."); + + if (!intersected_lines.empty()) { + intersected = true; + const Vector &intersected_line = intersected_lines[0]; + Vector2 sample_pos = (intersected_line[0] + intersected_line[1]) / 2.0f; + + float line_length = line[0].distance_to(line[1]); + float norm = line[0].distance_to(sample_pos) / line_length; + + if (middle_vertex == 0) { + barycentric_coords = Vector3(0.0f, 1.0f - norm, norm); + } else if (middle_vertex == 1) { + barycentric_coords = Vector3(1.0f - norm, 0.0f, norm); + } else { + barycentric_coords = Vector3(1.0f - norm, norm, 0.0f); + } + } + } else { + + Vector > intersected_polygons = Geometry::intersect_polygons_2d(pixel_polygon, triangle_polygon); + + ERR_FAIL_COND_MSG(intersected_polygons.size() > 1, "[Lightmapper] Itersecting two convex polygons should give at most one polygon."); + + if (!intersected_polygons.empty()) { + const Vector &intersected_polygon = intersected_polygons[0]; + + // do centroid sampling + Vector2 sample_pos = intersected_polygon[0]; + Vector2 area_center = Vector2(i, j) + Vector2(0.5f, 0.5f); + float intersected_area = (intersected_polygon[0] - area_center).cross(intersected_polygon[intersected_polygon.size() - 1] - area_center); + for (int k = 1; k < intersected_polygon.size(); k++) { + sample_pos += intersected_polygon[k]; + intersected_area += (intersected_polygon[k] - area_center).cross(intersected_polygon[k - 1] - area_center); + } + + if (intersected_area != 0.0f) { + sample_pos /= intersected_polygon.size(); + barycentric_coords = Geometry::barycentric_coordinates_2d(sample_pos, v0, v1, v2); + intersected = true; + area_coverage = ABS(intersected_area) / 2.0f; + } + } + + if (!intersected) { + for (int k = 0; k < 4; ++k) { + for (int l = 0; l < 3; ++l) { + Vector2 intersection_point; + if (Geometry::segment_intersects_segment_2d(pixel_polygon[k], pixel_polygon[(k + 1) % 4], triangle_polygon[l], triangle_polygon[(l + 1) % 3], &intersection_point)) { + intersected = true; + barycentric_coords = Geometry::barycentric_coordinates_2d(intersection_point, v0, v1, v2); + break; + } + } + if (intersected) { + break; + } + } + } + } + + if (texel_idx >= 0 && area_coverage < r_lightmap[texel_idx].area_coverage) { + continue; // A previous triangle gives better pixel coverage + } + + Vector2 pixel = Vector2(i, j); + if (!intersected && v0.floor() == pixel) { + intersected = true; + barycentric_coords = Vector3(1, 0, 0); + } + + if (!intersected && v1.floor() == pixel) { + intersected = true; + barycentric_coords = Vector3(0, 1, 0); + } + + if (!intersected && v2.floor() == pixel) { + intersected = true; + barycentric_coords = Vector3(0, 0, 1); + } + + if (!intersected) { + continue; + } + + if (Math::is_nan(barycentric_coords.x) || Math::is_nan(barycentric_coords.y) || Math::is_nan(barycentric_coords.z)) { + continue; + } + + if (Math::is_inf(barycentric_coords.x) || Math::is_inf(barycentric_coords.y) || Math::is_inf(barycentric_coords.z)) { + continue; + } + + r_lightmap_indices[ofs] = r_lightmap.size(); + + Vector3 pos = p0 * barycentric_coords[0] + p1 * barycentric_coords[1] + p2 * barycentric_coords[2]; + Vector3 normal = n0 * barycentric_coords[0] + n1 * barycentric_coords[1] + n2 * barycentric_coords[2]; + + Vector2 uv = uv0 * barycentric_coords[0] + uv1 * barycentric_coords[1] + uv2 * barycentric_coords[2]; + Color c = _bilinear_sample(p_albedo, uv); + Color e = _bilinear_sample(p_emission, uv); + + Vector2 texel_center = Vector2(i, j) + Vector2(0.5f, 0.5f); + Vector3 texel_center_bary = Geometry::barycentric_coordinates_2d(texel_center, v0, v1, v2); + + if (!Math::is_nan(texel_center_bary.x) && !Math::is_nan(texel_center_bary.y) && !Math::is_nan(texel_center_bary.z) && !Math::is_inf(texel_center_bary.x) && !Math::is_inf(texel_center_bary.y) && !Math::is_inf(texel_center_bary.z)) { + Vector3 texel_center_pos = p0 * texel_center_bary[0] + p1 * texel_center_bary[1] + p2 * texel_center_bary[2]; + pos = _fix_sample_position(pos, texel_center_pos, normal, tangent, bitangent, texel_size); + } + + LightmapTexel texel; + texel.normal = normal.normalized(); + texel.pos = pos; + texel.albedo = Vector3(c.r, c.g, c.b); + texel.alpha = c.a; + texel.emission = Vector3(e.r, e.g, e.b); + texel.area_coverage = area_coverage; + r_lightmap.push_back(texel); + } + } +} + +void LightmapperCPU::_compute_direct_light(uint32_t p_idx, void *r_lightmap) { + + LightmapTexel *lightmap = (LightmapTexel *)r_lightmap; + for (unsigned int i = 0; i < lights.size(); ++i) { + + const Light &light = lights[i]; + Vector3 normal = lightmap[p_idx].normal; + Vector3 position = lightmap[p_idx].pos; + Vector3 final_energy; + Color c = light.color; + Vector3 light_energy = Vector3(c.r, c.g, c.b) * light.energy; + + if (light.type == LIGHT_TYPE_OMNI) { + Vector3 light_direction = (position - light.position).normalized(); + if (normal.dot(light_direction) >= 0.0) { + continue; + } + float dist = position.distance_to(light.position); + + if (dist <= light.range) { + LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_direction, parameters.bias, dist - parameters.bias); + if (raycaster->intersect(ray)) { + continue; + } + float att = powf(1.0 - dist / light.range, light.attenuation); + final_energy = light_energy * att * MAX(0, normal.dot(-light_direction)); + } + } + + if (light.type == LIGHT_TYPE_SPOT) { + + Vector3 light_direction = (position - light.position).normalized(); + if (normal.dot(light_direction) >= 0.0) { + continue; + } + + float angle = Math::acos(light.direction.dot(light_direction)); + + if (angle > light.spot_angle) { + continue; + } + + float dist = position.distance_to(light.position); + if (dist > light.range) { + continue; + } + + LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_direction, parameters.bias, dist); + if (raycaster->intersect(ray)) { + continue; + } + + float normalized_dist = dist * (1.0f / MAX(0.001f, light.range)); + float norm_light_attenuation = Math::pow(MAX(1.0f - normalized_dist, 0.001f), light.attenuation); + + float spot_cutoff = Math::cos(light.spot_angle); + float scos = MAX(light_direction.dot(light.direction), spot_cutoff); + float spot_rim = (1.0f - scos) / (1.0f - spot_cutoff); + norm_light_attenuation *= 1.0f - pow(MAX(spot_rim, 0.001f), light.spot_attenuation); + final_energy = light_energy * norm_light_attenuation * MAX(0, normal.dot(-light_direction)); + } + + if (light.type == LIGHT_TYPE_DIRECTIONAL) { + if (normal.dot(light.direction) >= 0.0) { + continue; + } + + LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position + normal * parameters.bias, -light.direction, parameters.bias); + if (raycaster->intersect(ray)) { + continue; + } + + final_energy = light_energy * MAX(0, normal.dot(-light.direction)); + } + + lightmap[p_idx].direct_light += final_energy * light.indirect_multiplier; + if (light.bake_direct) { + lightmap[p_idx].output_light += final_energy; + } + } +} + +_ALWAYS_INLINE_ float uniform_rand() { + /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ + static thread_local uint32_t state = rand(); + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + return float(state) / UINT32_MAX; +} + +void LightmapperCPU::_compute_indirect_light(uint32_t p_idx, void *r_lightmap) { + + LightmapTexel *lightmap = (LightmapTexel *)r_lightmap; + LightmapTexel &texel = lightmap[p_idx]; + + Vector3 accum; + + const Vector3 const_forward = Vector3(0, 0, 1); + const Vector3 const_up = Vector3(0, 1, 0); + + for (int i = 0; i < parameters.samples; i++) { + + Vector3 color; + Vector3 throughput = Vector3(1.0f, 1.0f, 1.0f); + + Vector3 position = texel.pos; + Vector3 normal = texel.normal; + Vector3 direction; + + for (int depth = 0; depth < parameters.bounces; depth++) { + + Vector3 tangent = const_forward.cross(normal); + if (unlikely(tangent.length_squared() < 0.005f)) { + tangent = const_up.cross(normal); + } + tangent.normalize(); + Vector3 bitangent = tangent.cross(normal); + bitangent.normalize(); + + Basis normal_xform = Basis(tangent, bitangent, normal); + normal_xform.transpose(); + + float u1 = uniform_rand(); + float u2 = uniform_rand(); + + float radius = Math::sqrt(u1); + float theta = Math_TAU * u2; + + Vector3 axis = Vector3(radius * Math::cos(theta), radius * Math::sin(theta), Math::sqrt(MAX(0.0f, 1.0f - u1))); + + direction = normal_xform.xform(axis); + + // We can skip multiplying throughput by cos(theta) because de sampling PDF is also cos(theta) and they cancel each other + //float pdf = normal.dot(direction); + //throughput *= normal.dot(direction)/pdf; + + LightmapRaycaster::Ray ray(position, direction, parameters.bias); + bool hit = raycaster->intersect(ray); + + if (!hit) { + if (parameters.environment_panorama.is_valid()) { + direction = parameters.environment_transform.xform_inv(direction); + Vector2 st = Vector2(Math::atan2(direction.z, direction.x), Math::acos(direction.y)); + + if (Math::is_nan(st.y)) { + st.y = direction.y > 0.0 ? 0.0 : Math_PI; + } + + st.x += Math_PI; + st /= Vector2(Math_TAU, Math_PI); + st.x = Math::fmod(st.x + 0.75, 1.0); + Color c = _bilinear_sample(parameters.environment_panorama, st, false, true); + color += throughput * Vector3(c.r, c.g, c.b) * c.a; + } + break; + } + + unsigned int hit_mesh_id = ray.geomID; + const Vector2i &size = mesh_instances[hit_mesh_id].size; + + int x = ray.u * size.x; + int y = ray.v * size.y; + + const int idx = scene_lightmap_indices[hit_mesh_id][y * size.x + x]; + + if (idx < 0) { + break; + } + + const LightmapTexel &sample = scene_lightmaps[hit_mesh_id][idx]; + + if (sample.normal.dot(ray.dir) > 0.0 && !no_shadow_meshes.has(hit_mesh_id)) { + // We hit a back-face + break; + } + + color += throughput * sample.emission; + throughput *= sample.albedo; + color += throughput * sample.direct_light; + + // Russian Roulette + // https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer + const float p = throughput[throughput.max_axis()]; + if (uniform_rand() > p) { + break; + } + throughput *= 1.0f / p; + + position = sample.pos; + normal = sample.normal; + } + accum += color; + } + + texel.output_light += accum / parameters.samples; +} + +void LightmapperCPU::_post_process(uint32_t p_idx, void *r_output) { + + const MeshInstance &mesh = mesh_instances[p_idx]; + + if (!mesh.generate_lightmap) { + return; + } + + LocalVector &indices = scene_lightmap_indices[p_idx]; + LocalVector &lightmap = scene_lightmaps[p_idx]; + Vector3 *output = ((LocalVector *)r_output)[p_idx].ptr(); + Vector2i size = mesh.size; + + // Blit texels to buffer + const int margin = 4; + for (int i = 0; i < size.y; i++) { + for (int j = 0; j < size.x; j++) { + int idx = indices[i * size.x + j]; + if (idx >= 0) { + output[i * size.x + j] = lightmap[idx].output_light; + continue; // filled, skip + } + + int closest_idx = -1; + float closest_dist = 1e20; + + for (int y = i - margin; y <= i + margin; y++) { + for (int x = j - margin; x <= j + margin; x++) { + + if (x == j && y == i) + continue; + if (x < 0 || x >= size.x) + continue; + if (y < 0 || y >= size.y) + continue; + int cell_idx = indices[y * size.x + x]; + if (cell_idx < 0) { + continue; //also ensures that blitted stuff is not reused + } + + float dist = Vector2(i - y, j - x).length_squared(); + if (dist < closest_dist) { + closest_dist = dist; + closest_idx = cell_idx; + } + } + } + + if (closest_idx != -1) { + output[i * size.x + j] = lightmap[closest_idx].output_light; + } + } + } + + lightmap.clear(); + + LocalVector seams; + _compute_seams(mesh, seams); + + _fix_seams(seams, output, size); + _dilate_lightmap(output, indices, size, margin); + + if (parameters.use_denoiser) { + Ref denoiser = LightmapDenoiser::create(); + + if (denoiser.is_valid()) { + int data_size = size.x * size.y * sizeof(Vector3); + Ref current_image; + current_image.instance(); + { + PoolByteArray data; + data.resize(data_size); + PoolByteArray::Write w = data.write(); + copymem(w.ptr(), output, data_size); + current_image->create(size.x, size.y, false, Image::FORMAT_RGBF, data); + } + + Ref denoised_image = denoiser->denoise_image(current_image); + + PoolByteArray denoised_data = denoised_image->get_data(); + denoised_image.unref(); + PoolByteArray::Read r = denoised_data.read(); + copymem(output, r.ptr(), data_size); + } + } + + _dilate_lightmap(output, indices, size, margin); + _fix_seams(seams, output, size); + _dilate_lightmap(output, indices, size, margin); + + indices.clear(); +} + +void LightmapperCPU::_compute_seams(const MeshInstance &p_mesh, LocalVector &r_seams) { + float max_uv_distance = 1.0f / MAX(p_mesh.size.x, p_mesh.size.y); + max_uv_distance *= max_uv_distance; // We use distance_to_squared(), so wee need to square the max distance as well + float max_pos_distance = 0.0005f; + float max_normal_distance = 0.05f; + + const Vector &points = p_mesh.data.points; + const Vector &uv2s = p_mesh.data.uv2; + const Vector &normals = p_mesh.data.normal; + + LocalVector edges; + edges.resize(points.size()); // One edge per vertex + + for (int i = 0; i < points.size(); i += 3) { + Vector3 triangle_vtxs[3] = { points[i + 0], points[i + 1], points[i + 2] }; + Vector2 triangle_uvs[3] = { uv2s[i + 0], uv2s[i + 1], uv2s[i + 2] }; + Vector3 triangle_normals[3] = { normals[i + 0], normals[i + 1], normals[i + 2] }; + + for (int k = 0; k < 3; k++) { + int idx[2]; + idx[0] = k; + idx[1] = (k + 1) % 3; + + if (triangle_vtxs[idx[1]] < triangle_vtxs[idx[0]]) { + SWAP(idx[0], idx[1]); + } + + SeamEdge e; + for (int l = 0; l < 2; ++l) { + e.pos[l] = triangle_vtxs[idx[l]]; + e.uv[l] = triangle_uvs[idx[l]]; + e.normal[l] = triangle_normals[idx[l]]; + } + edges[i + k] = e; + } + } + + edges.sort(); + + for (unsigned int j = 0; j < edges.size(); j++) { + const SeamEdge &edge0 = edges[j]; + for (unsigned int k = j + 1; k < edges.size() && edges[k].pos[0].x < (edge0.pos[0].x + max_pos_distance * 1.1f); k++) { + const SeamEdge &edge1 = edges[k]; + + if (edge0.uv[0].distance_squared_to(edge1.uv[0]) < max_uv_distance && edge0.uv[1].distance_squared_to(edge1.uv[1]) < max_uv_distance) { + continue; + } + + if (edge0.pos[0].distance_squared_to(edge1.pos[0]) > max_pos_distance || edge0.pos[1].distance_squared_to(edge1.pos[1]) > max_pos_distance) { + continue; + } + + if (edge0.normal[0].distance_squared_to(edge1.normal[0]) > max_normal_distance || edge0.normal[1].distance_squared_to(edge1.normal[1]) > max_normal_distance) { + continue; + } + + UVSeam s; + s.edge0[0] = edge0.uv[0]; + s.edge0[1] = edge0.uv[1]; + s.edge1[0] = edge1.uv[0]; + s.edge1[1] = edge1.uv[1]; + r_seams.push_back(s); + } + } +} + +void LightmapperCPU::_fix_seams(const LocalVector &p_seams, Vector3 *r_lightmap, Vector2i p_size) { + LocalVector extra_buffer; + extra_buffer.resize(p_size.x * p_size.y); + + copymem(extra_buffer.ptr(), r_lightmap, p_size.x * p_size.y * sizeof(Vector3)); + + Vector3 *read_ptr = extra_buffer.ptr(); + Vector3 *write_ptr = r_lightmap; + + for (int i = 0; i < 5; i++) { + for (unsigned int j = 0; j < p_seams.size(); j++) { + _fix_seam(p_seams[j].edge0[0], p_seams[j].edge0[1], p_seams[j].edge1[0], p_seams[j].edge1[1], read_ptr, write_ptr, p_size); + _fix_seam(p_seams[j].edge1[0], p_seams[j].edge1[1], p_seams[j].edge0[0], p_seams[j].edge0[1], read_ptr, write_ptr, p_size); + } + copymem(read_ptr, write_ptr, p_size.x * p_size.y * sizeof(Vector3)); + } +} + +void LightmapperCPU::_fix_seam(const Vector2 &p_pos0, const Vector2 &p_pos1, const Vector2 &p_uv0, const Vector2 &p_uv1, const Vector3 *p_read_buffer, Vector3 *r_write_buffer, const Vector2i &p_size) { + + Vector2 line[2]; + line[0] = p_pos0 * p_size; + line[1] = p_pos1 * p_size; + + const Vector2i start_pixel = line[0].floor(); + const Vector2i end_pixel = line[1].floor(); + + Vector2 seam_dir = (line[1] - line[0]).normalized(); + Vector2 t_delta = Vector2(1.0f / Math::abs(seam_dir.x), 1.0f / Math::abs(seam_dir.y)); + Vector2i step = Vector2(seam_dir.x > 0 ? 1 : (seam_dir.x < 0 ? -1 : 0), seam_dir.y > 0 ? 1 : (seam_dir.y < 0 ? -1 : 0)); + + Vector2 t_next = Vector2(Math::fmod(line[0].x, 1.0f), Math::fmod(line[0].y, 1.0f)); + + if (step.x == 1) { + t_next.x = 1.0f - t_next.x; + } + + if (step.y == 1) { + t_next.y = 1.0f - t_next.y; + } + + t_next.x /= Math::abs(seam_dir.x); + t_next.y /= Math::abs(seam_dir.y); + + if (Math::is_nan(t_next.x)) { + t_next.x = 1e20f; + } + + if (Math::is_nan(t_next.y)) { + t_next.y = 1e20f; + } + + Vector2i pixel = start_pixel; + Vector2 start_p = start_pixel; + float line_length = line[0].distance_to(line[1]); + + if (line_length == 0.0f) { + return; + } + + while (start_p.distance_to(pixel) < line_length + 1.0f) { + + Vector2 current_point = Vector2(pixel) + Vector2(0.5f, 0.5f); + current_point = Geometry::get_closest_point_to_segment_2d(current_point, line); + float t = line[0].distance_to(current_point) / line_length; + + Vector2 current_uv = p_uv0 * (1.0 - t) + p_uv1 * t; + Vector2i sampled_point = (current_uv * p_size).floor(); + + Vector3 current_color = r_write_buffer[pixel.y * p_size.x + pixel.x]; + Vector3 sampled_color = p_read_buffer[sampled_point.y * p_size.x + sampled_point.x]; + + r_write_buffer[pixel.y * p_size.x + pixel.x] = current_color * 0.6f + sampled_color * 0.4f; + + if (pixel == end_pixel) { + break; + } + + if (t_next.x < t_next.y) { + pixel.x += step.x; + t_next.x += t_delta.x; + } else { + pixel.y += step.y; + t_next.y += t_delta.y; + } + } +} + +void LightmapperCPU::_dilate_lightmap(Vector3 *r_lightmap, const LocalVector p_indices, Vector2i p_size, int margin) { + for (int i = 0; i < p_size.y; i++) { + for (int j = 0; j < p_size.x; j++) { + int idx = p_indices[i * p_size.x + j]; + if (idx >= 0) { + continue; //filled, skip + } + + Vector2i closest; + float closest_dist = 1e20; + + for (int y = i - margin; y <= i + margin; y++) { + for (int x = j - margin; x <= j + margin; x++) { + + if (x == j && y == i) + continue; + if (x < 0 || x >= p_size.x) + continue; + if (y < 0 || y >= p_size.y) + continue; + int cell_idx = p_indices[y * p_size.x + x]; + if (cell_idx < 0) { + continue; //also ensures that blitted stuff is not reused + } + + float dist = Vector2(i - y, j - x).length_squared(); + if (dist < closest_dist) { + closest_dist = dist; + closest = Vector2(x, y); + } + } + } + + if (closest_dist < 1e20) { + r_lightmap[i * p_size.x + j] = r_lightmap[closest.y * p_size.x + closest.x]; + } + } + } +} + +void LightmapperCPU::_blit_lightmap(const Vector &p_src, const Vector2i &p_size, Ref &p_dst, int p_x, int p_y, bool p_with_padding) { + int padding = p_with_padding ? 1 : 0; + ERR_FAIL_COND(p_x < padding || p_y < padding); + ERR_FAIL_COND(p_x + p_size.x > p_dst->get_width() - padding); + ERR_FAIL_COND(p_y + p_size.y > p_dst->get_height() - padding); + + p_dst->lock(); + for (int y = 0; y < p_size.y; y++) { + const Vector3 *__restrict src = p_src.ptr() + y * p_size.x; + for (int x = 0; x < p_size.x; x++) { + p_dst->set_pixel(p_x + x, p_y + y, Color(src->x, src->y, src->z)); + src++; + } + } + + if (p_with_padding) { + for (int y = -1; y < p_size.y + 1; y++) { + int yy = CLAMP(y, 0, p_size.y - 1); + int idx_left = yy * p_size.x; + int idx_right = idx_left + p_size.x - 1; + p_dst->set_pixel(p_x - 1, p_y + y, Color(p_src[idx_left].x, p_src[idx_left].y, p_src[idx_left].z)); + p_dst->set_pixel(p_x + p_size.x, p_y + y, Color(p_src[idx_right].x, p_src[idx_right].y, p_src[idx_right].z)); + } + + for (int x = -1; x < p_size.x + 1; x++) { + int xx = CLAMP(x, 0, p_size.x - 1); + int idx_top = xx; + int idx_bot = idx_top + (p_size.y - 1) * p_size.x; + p_dst->set_pixel(p_x + x, p_y - 1, Color(p_src[idx_top].x, p_src[idx_top].y, p_src[idx_top].z)); + p_dst->set_pixel(p_x + x, p_y + p_size.y, Color(p_src[idx_bot].x, p_src[idx_bot].y, p_src[idx_bot].z)); + } + } + p_dst->unlock(); +} + +LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, bool p_generate_atlas, int p_max_texture_size, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, BakeStepFunc p_substep_function) { + + if (p_step_function) { + bool cancelled = p_step_function(0.0, TTR("Begin Bake"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + raycaster = LightmapRaycaster::create(); + + ERR_FAIL_COND_V(raycaster.is_null(), BAKE_ERROR_NO_RAYCASTER); + + // Collect parameters + parameters.use_denoiser = p_use_denoiser; + parameters.bias = p_bias; + parameters.bounces = p_bounces; + parameters.environment_transform = p_environment_transform; + parameters.environment_panorama = p_environment_panorama; + + switch (p_quality) { + case BAKE_QUALITY_LOW: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/low_quality_ray_count"); + } break; + case BAKE_QUALITY_MEDIUM: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/medium_quality_ray_count"); + } break; + case BAKE_QUALITY_HIGH: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/high_quality_ray_count"); + } break; + case BAKE_QUALITY_ULTRA: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/ultra_quality_ray_count"); + } break; + } + + bake_textures.clear(); + + if (p_step_function) { + bool cancelled = p_step_function(0.1, TTR("Preparing data structures"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + raycaster->add_mesh(mesh_instances[i].data.points, mesh_instances[i].data.normal, mesh_instances[i].data.uv2, i); + } + raycaster->commit(); + + scene_lightmaps.resize(mesh_instances.size()); + scene_lightmap_indices.resize(mesh_instances.size()); + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].cast_shadows) { + no_shadow_meshes.insert(i); + } + } + + raycaster->set_mesh_filter(no_shadow_meshes); + + Vector2i atlas_size = Vector2i(-1, -1); + int atlas_slices = -1; + if (p_generate_atlas) { + Error err = _layout_atlas(p_max_texture_size, &atlas_size, &atlas_slices); + if (err != OK) { + return BAKE_ERROR_LIGHTMAP_TOO_SMALL; + } + } + + if (p_step_function) { + bool cancelled = p_step_function(0.2, TTR("Generate buffers"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (_parallel_run(mesh_instances.size(), "Rasterizing meshes", &LightmapperCPU::_generate_buffer, nullptr, p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + const Size2i &size = mesh_instances[i].size; + bool has_alpha = false; + PoolVector alpha_data; + alpha_data.resize(size.x * size.y); + { + PoolVector::Write w = alpha_data.write(); + for (unsigned int j = 0; j < scene_lightmap_indices[i].size(); ++j) { + int idx = scene_lightmap_indices[i][j]; + uint8_t alpha = 0; + if (idx >= 0) { + alpha = CLAMP(scene_lightmaps[i][idx].alpha * 255, 0, 255); + if (alpha < 255) { + has_alpha = true; + } + } + w[j] = alpha; + } + } + + if (has_alpha) { + Ref alpha_texture; + alpha_texture.instance(); + alpha_texture->create(size.x, size.y, false, Image::FORMAT_L8, alpha_data); + raycaster->set_mesh_alpha_texture(alpha_texture, i); + } + } + + albedo_textures.clear(); + emission_textures.clear(); + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (p_step_function) { + float p = float(i) / mesh_instances.size(); + bool cancelled = p_step_function(0.2 + p * 0.2, vformat("%s (%d/%d)", TTR("Direct lighting"), i, mesh_instances.size()), p_bake_userdata, false); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (_parallel_run(scene_lightmaps[i].size(), "Computing direct light", &LightmapperCPU::_compute_direct_light, scene_lightmaps[i].ptr(), p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + } + raycaster->clear_mesh_filter(); + + int n_lit_meshes = 0; + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + n_lit_meshes++; + } + } + + if (parameters.environment_panorama.is_valid()) { + parameters.environment_panorama->lock(); + } + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + + if (!mesh_instances[i].generate_lightmap) { + continue; + } + + if (p_step_function) { + float p = float(i) / n_lit_meshes; + bool cancelled = p_step_function(0.4 + p * 0.4, vformat("%s (%d/%d)", TTR("Indirect lighting"), i, mesh_instances.size()), p_bake_userdata, false); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (!scene_lightmaps[i].empty()) { + if (_parallel_run(scene_lightmaps[i].size(), "Computing indirect light", &LightmapperCPU::_compute_indirect_light, scene_lightmaps[i].ptr(), p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + } + } + if (parameters.environment_panorama.is_valid()) { + parameters.environment_panorama->unlock(); + } + + raycaster.unref(); // Not needed anymore, free some memory. + + LocalVector > lightmaps_data; + lightmaps_data.resize(mesh_instances.size()); + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + const Vector2i size = mesh_instances[i].size; + lightmaps_data[i].resize(size.x * size.y); + } + } + + if (p_step_function) { + bool cancelled = p_step_function(0.8, TTR("Post processing"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (_parallel_run(mesh_instances.size(), "Denoise & fix seams", &LightmapperCPU::_post_process, lightmaps_data.ptr(), p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + + if (p_generate_atlas) { + bake_textures.resize(atlas_slices); + + for (int i = 0; i < atlas_slices; i++) { + Ref image; + image.instance(); + image->create(atlas_size.x, atlas_size.y, false, Image::FORMAT_RGBH); + bake_textures[i] = image; + } + } else { + bake_textures.resize(mesh_instances.size()); + + Set used_mesh_names; + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].generate_lightmap) { + continue; + } + + String mesh_name = mesh_instances[i].node_name; + if (mesh_name == "" || mesh_name.find(":") != -1 || mesh_name.find("/") != -1) { + mesh_name = "LightMap"; + } + + if (used_mesh_names.has(mesh_name)) { + int idx = 2; + String base = mesh_name; + while (true) { + mesh_name = base + itos(idx); + if (!used_mesh_names.has(mesh_name)) + break; + idx++; + } + } + used_mesh_names.insert(mesh_name); + + Ref image; + image.instance(); + image->create(mesh_instances[i].size.x, mesh_instances[i].size.y, false, Image::FORMAT_RGBH); + image->set_name(mesh_name); + bake_textures[i] = image; + } + } + + if (p_step_function) { + bool cancelled = p_step_function(0.9, TTR("Plotting lightmaps"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + { + int j = 0; + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].generate_lightmap) { + continue; + } + + if (p_generate_atlas) { + _blit_lightmap(lightmaps_data[i], mesh_instances[i].size, bake_textures[mesh_instances[i].slice], mesh_instances[i].offset.x, mesh_instances[i].offset.y, true); + } else { + _blit_lightmap(lightmaps_data[i], mesh_instances[i].size, bake_textures[j], 0, 0, false); + } + j++; + } + } + + return BAKE_OK; +} + +int LightmapperCPU::get_bake_texture_count() const { + return bake_textures.size(); +} + +Ref LightmapperCPU::get_bake_texture(int p_index) const { + ERR_FAIL_INDEX_V(p_index, (int)bake_textures.size(), Ref()); + return bake_textures[p_index]; +} + +int LightmapperCPU::get_bake_mesh_count() const { + return mesh_instances.size(); +} + +Variant LightmapperCPU::get_bake_mesh_userdata(int p_index) const { + ERR_FAIL_INDEX_V(p_index, (int)mesh_instances.size(), Variant()); + return mesh_instances[p_index].data.userdata; +} + +Rect2 LightmapperCPU::get_bake_mesh_uv_scale(int p_index) const { + ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2()); + Rect2 uv_ofs; + Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height()); + uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size; + uv_ofs.size = Vector2(mesh_instances[p_index].size) / atlas_size; + return uv_ofs; +} + +int LightmapperCPU::get_bake_mesh_texture_slice(int p_index) const { + ERR_FAIL_INDEX_V(p_index, (int)mesh_instances.size(), Variant()); + return mesh_instances[p_index].slice; +} + +void LightmapperCPU::add_albedo_texture(Ref p_texture) { + if (p_texture.is_null()) { + return; + } + + RID texture_rid = p_texture->get_rid(); + if (!texture_rid.is_valid() || albedo_textures.has(texture_rid)) { + return; + } + + Ref texture_data = p_texture->get_data(); + + if (texture_data.is_null()) { + return; + } + + if (texture_data->is_compressed()) { + texture_data->decompress(); + } + + texture_data->convert(Image::FORMAT_RGBA8); + + albedo_textures.insert(texture_rid, texture_data); +} + +void LightmapperCPU::add_emission_texture(Ref p_texture) { + if (p_texture.is_null()) { + return; + } + + RID texture_rid = p_texture->get_rid(); + if (!texture_rid.is_valid() || emission_textures.has(texture_rid)) { + return; + } + + Ref texture_data = p_texture->get_data(); + + if (texture_data.is_null()) { + return; + } + + if (texture_data->is_compressed()) { + texture_data->decompress(); + } + + texture_data->convert(Image::FORMAT_RGBH); + + emission_textures.insert(texture_rid, texture_data); +} + +void LightmapperCPU::add_mesh(const MeshData &p_mesh, Vector2i p_size) { + ERR_FAIL_COND(p_mesh.points.size() == 0); + ERR_FAIL_COND(p_mesh.points.size() != p_mesh.uv2.size()); + ERR_FAIL_COND(p_mesh.points.size() != p_mesh.normal.size()); + ERR_FAIL_COND(!p_mesh.uv.empty() && p_mesh.points.size() != p_mesh.uv.size()); + ERR_FAIL_COND(p_mesh.surface_facecounts.size() != p_mesh.albedo.size()); + ERR_FAIL_COND(p_mesh.surface_facecounts.size() != p_mesh.emission.size()); + + MeshInstance mi; + mi.data = p_mesh; + mi.size = p_size; + mi.generate_lightmap = true; + mi.cast_shadows = true; + mi.node_name = ""; + + Dictionary userdata = p_mesh.userdata; + if (userdata.has("cast_shadows")) { + mi.cast_shadows = userdata["cast_shadows"]; + } + if (userdata.has("generate_lightmap")) { + mi.generate_lightmap = userdata["generate_lightmap"]; + } + if (userdata.has("node_name")) { + mi.node_name = userdata["node_name"]; + } + + mesh_instances.push_back(mi); +} + +void LightmapperCPU::add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier) { + Light l; + l.type = LIGHT_TYPE_DIRECTIONAL; + l.direction = p_direction; + l.color = p_color; + l.energy = p_energy; + l.indirect_multiplier = p_indirect_multiplier; + l.bake_direct = p_bake_direct; + lights.push_back(l); +} + +void LightmapperCPU::add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation) { + Light l; + l.type = LIGHT_TYPE_OMNI; + l.position = p_position; + l.range = p_range; + l.attenuation = p_attenuation; + l.color = p_color; + l.energy = p_energy; + l.indirect_multiplier = p_indirect_multiplier; + l.bake_direct = p_bake_direct; + lights.push_back(l); +} + +void LightmapperCPU::add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation) { + Light l; + l.type = LIGHT_TYPE_SPOT; + l.position = p_position; + l.direction = p_direction; + l.range = p_range; + l.attenuation = p_attenuation; + l.spot_angle = Math::deg2rad(p_spot_angle); + l.spot_attenuation = p_spot_attenuation; + l.color = p_color; + l.energy = p_energy; + l.indirect_multiplier = p_indirect_multiplier; + l.bake_direct = p_bake_direct; + lights.push_back(l); +} + +LightmapperCPU::LightmapperCPU() { + thread_progress = 0; + thread_cancelled = false; +} diff --git a/modules/lightmapper_cpu/lightmapper_cpu.h b/modules/lightmapper_cpu/lightmapper_cpu.h new file mode 100644 index 000000000000..e809240099bb --- /dev/null +++ b/modules/lightmapper_cpu/lightmapper_cpu.h @@ -0,0 +1,182 @@ +/*************************************************************************/ +/* lightmapper_cpu.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LIGHTMAPPER_CPU_H +#define LIGHTMAPPER_CPU_H + +#include "core/local_vector.h" +#include "scene/3d/lightmapper.h" +#include "scene/resources/mesh.h" +#include "scene/resources/surface_tool.h" +#include + +class LightmapperCPU : public Lightmapper { + GDCLASS(LightmapperCPU, Lightmapper) + + struct MeshInstance { + MeshData data; + int slice = 0; + Vector2i offset; + Vector2i size; + bool cast_shadows; + bool generate_lightmap; + String node_name; + }; + + struct Light { + Vector3 position; + uint32_t type = LIGHT_TYPE_DIRECTIONAL; + Vector3 direction; + float energy; + float indirect_multiplier; + Color color; + float range; + float attenuation; + float spot_angle; + float spot_attenuation; + bool bake_direct; + }; + + struct LightmapTexel { + Vector3 albedo; + float alpha; + Vector3 emission; + Vector3 pos; + Vector3 normal; + + Vector3 direct_light; + Vector3 output_light; + + float area_coverage; + }; + + struct BakeParams { + float bias; + int bounces; + int samples; + bool use_denoiser = true; + Ref environment_panorama; + Basis environment_transform; + }; + + struct UVSeam { + Vector2 edge0[2]; + Vector2 edge1[2]; + }; + + struct SeamEdge { + Vector3 pos[2]; + Vector3 normal[2]; + Vector2 uv[2]; + + _FORCE_INLINE_ bool operator<(const SeamEdge &p_edge) const { + return pos[0].x < p_edge.pos[0].x; + } + }; + + struct AtlasOffset { + int slice; + int x; + int y; + }; + + struct ThreadData; + + typedef void (LightmapperCPU::*BakeThreadFunc)(uint32_t, void *); + + struct ThreadData { + LightmapperCPU *instance; + uint32_t count; + BakeThreadFunc thread_func; + void *userdata; + }; + + BakeParams parameters; + + LocalVector > bake_textures; + Map > albedo_textures; + Map > emission_textures; + + LocalVector mesh_instances; + LocalVector lights; + + LocalVector > scene_lightmaps; + LocalVector > scene_lightmap_indices; + Set no_shadow_meshes; + + std::atomic thread_progress; + std::atomic thread_cancelled; + + Ref raycaster; + + Error _layout_atlas(int p_max_size, Vector2i *r_atlas_size, int *r_atlas_slices); + + static void _thread_func_callback(void *p_thread_data); + void _thread_func_wrapper(uint32_t p_idx, ThreadData *p_thread_data); + bool _parallel_run(int p_count, const String &p_description, BakeThreadFunc p_thread_func, void *p_userdata, BakeStepFunc p_substep_func = nullptr); + + void _generate_buffer(uint32_t p_idx, void *p_unused); + Ref _init_bake_texture(const MeshData::TextureDef &p_texture_def, const Map > &p_tex_cache, Image::Format p_default_format); + Color _bilinear_sample(const Ref &p_img, const Vector2 &p_uv, bool p_clamp_x = false, bool p_clamp_y = false); + Vector3 _fix_sample_position(const Vector3 &p_position, const Vector3 &p_texel_center, const Vector3 &p_normal, const Vector3 &p_tangent, const Vector3 &p_bitangent, const Vector2 &p_texel_size); + void _plot_triangle(const Vector2 *p_vertices, const Vector3 *p_positions, const Vector3 *p_normals, const Vector2 *p_uvs, const Ref &p_albedo_texture, const Ref &p_emission_texture, Vector2i p_size, LocalVector &r_texels, LocalVector &r_lightmap_indices); + + void _compute_direct_light(uint32_t p_idx, void *r_lightmap); + + void _compute_indirect_light(uint32_t p_idx, void *r_lightmap); + + void _post_process(uint32_t p_idx, void *r_output); + void _compute_seams(const MeshInstance &p_mesh, LocalVector &r_seams); + void _fix_seams(const LocalVector &p_seams, Vector3 *r_lightmap, Vector2i p_size); + void _fix_seam(const Vector2 &p_pos0, const Vector2 &p_pos1, const Vector2 &p_uv0, const Vector2 &p_uv1, const Vector3 *p_read_buffer, Vector3 *r_write_buffer, const Vector2i &p_size); + void _dilate_lightmap(Vector3 *r_lightmap, const LocalVector p_indices, Vector2i p_size, int margin); + + void _blit_lightmap(const Vector &p_src, const Vector2i &p_size, Ref &p_dst, int p_x, int p_y, bool p_with_padding); + +public: + virtual void add_albedo_texture(Ref p_texture); + virtual void add_emission_texture(Ref p_texture); + virtual void add_mesh(const MeshData &p_mesh, Vector2i p_size); + virtual void add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier); + virtual void add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation); + virtual void add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation); + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, bool p_generate_atlas, int p_max_texture_size, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, BakeStepFunc p_substep_function = nullptr); + + int get_bake_texture_count() const; + Ref get_bake_texture(int p_index) const; + int get_bake_mesh_count() const; + Variant get_bake_mesh_userdata(int p_index) const; + Rect2 get_bake_mesh_uv_scale(int p_index) const; + int get_bake_mesh_texture_slice(int p_index) const; + + LightmapperCPU(); +}; + +#endif // LIGHTMAPPER_H diff --git a/modules/lightmapper_cpu/register_types.cpp b/modules/lightmapper_cpu/register_types.cpp new file mode 100644 index 000000000000..ccf443669f0e --- /dev/null +++ b/modules/lightmapper_cpu/register_types.cpp @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" + +#include "core/project_settings.h" +#include "lightmapper_cpu.h" +#include "scene/3d/lightmapper.h" + +#ifndef _3D_DISABLED +static Lightmapper *create_lightmapper_cpu() { + return memnew(LightmapperCPU); +} +#endif + +void register_lightmapper_cpu_types() { + GLOBAL_DEF("rendering/cpu_lightmapper/quality/low_quality_ray_count", 64); + GLOBAL_DEF("rendering/cpu_lightmapper/quality/medium_quality_ray_count", 256); + GLOBAL_DEF("rendering/cpu_lightmapper/quality/high_quality_ray_count", 512); + GLOBAL_DEF("rendering/cpu_lightmapper/quality/ultra_quality_ray_count", 1024); +#ifndef _3D_DISABLED + ClassDB::register_class(); + Lightmapper::create_cpu = create_lightmapper_cpu; +#endif +} + +void unregister_lightmapper_cpu_types() { +} diff --git a/modules/lightmapper_cpu/register_types.h b/modules/lightmapper_cpu/register_types.h new file mode 100644 index 000000000000..11594184c6b4 --- /dev/null +++ b/modules/lightmapper_cpu/register_types.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LIGHTMAPPER_CPU_REGISTER_TYPES_H +#define LIGHTMAPPER_CPU_REGISTER_TYPES_H + +void register_lightmapper_cpu_types(); +void unregister_lightmapper_cpu_types(); + +#endif // LIGHTMAPPER_CPU_REGISTER_TYPES_H diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 95339277e2ab..72026a2a4118 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -41,7 +41,7 @@ extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const flo bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) { - //set up input mesh + // set up input mesh xatlas::MeshDecl input_mesh; input_mesh.indexData = p_indices; input_mesh.indexCount = p_index_count; @@ -56,18 +56,19 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver input_mesh.vertexUvStride = 0; xatlas::ChartOptions chart_options; - xatlas::PackOptions pack_options; + chart_options.fixWinding = true; - pack_options.maxChartSize = 4096; + xatlas::PackOptions pack_options; + pack_options.padding = 1; + pack_options.maxChartSize = 4094; // Lightmap atlassing needs 2 for padding between meshes, so 4096-2 pack_options.blockAlign = true; pack_options.texelsPerUnit = 1.0 / p_texel_size; xatlas::Atlas *atlas = xatlas::Create(); - printf("Adding mesh..\n"); + xatlas::AddMeshError err = xatlas::AddMesh(atlas, input_mesh, 1); ERR_FAIL_COND_V_MSG(err != xatlas::AddMeshError::Success, false, xatlas::StringForEnum(err)); - printf("Generate..\n"); xatlas::Generate(atlas, chart_options, pack_options); *r_size_hint_x = atlas->width; @@ -96,7 +97,6 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver max_y = MAX(max_y, output.vertexArray[i].uv[1]); } - printf("Final texture size: %f,%f - max %f,%f\n", w, h, max_x, max_y); *r_vertex_count = output.vertexCount; for (uint32_t i = 0; i < output.indexCount; i++) { @@ -106,7 +106,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver *r_index_count = output.indexCount; xatlas::Destroy(atlas); - printf("Done\n"); + return true; } diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index 5fbec7b3b254..642f1cb0465a 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -31,6 +31,7 @@ #include "baked_lightmap.h" #include "core/io/config_file.h" #include "core/io/resource_saver.h" +#include "core/math/math_defs.h" #include "core/os/dir_access.h" #include "core/os/os.h" #include "voxel_light_baker.h" @@ -86,12 +87,21 @@ float BakedLightmapData::get_energy() const { return energy; } -void BakedLightmapData::add_user(const NodePath &p_path, const Ref &p_lightmap, int p_instance) { +void BakedLightmapData::add_user(const NodePath &p_path, const Ref &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance) { ERR_FAIL_COND_MSG(p_lightmap.is_null(), "It's not a reference to a valid Texture object."); + ERR_FAIL_COND(p_lightmap_slice == -1 && !Object::cast_to(p_lightmap.ptr())); + ERR_FAIL_COND(p_lightmap_slice != -1 && !Object::cast_to(p_lightmap.ptr())); + User user; user.path = p_path; - user.lightmap = p_lightmap; + if (p_lightmap_slice == -1) { + user.lightmap.single = p_lightmap; + } else { + user.lightmap.layered = p_lightmap; + } + user.lightmap_slice = p_lightmap_slice; + user.lightmap_uv_rect = p_lightmap_uv_rect; user.instance_index = p_instance; users.push_back(user); } @@ -105,10 +115,26 @@ NodePath BakedLightmapData::get_user_path(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), NodePath()); return users[p_user].path; } -Ref BakedLightmapData::get_user_lightmap(int p_user) const { +Ref BakedLightmapData::get_user_lightmap(int p_user) const { - ERR_FAIL_INDEX_V(p_user, users.size(), Ref()); - return users[p_user].lightmap; + ERR_FAIL_INDEX_V(p_user, users.size(), Ref()); + if (users[p_user].lightmap_slice == -1) { + return users[p_user].lightmap.single; + } else { + return users[p_user].lightmap.layered; + } +} + +int BakedLightmapData::get_user_lightmap_slice(int p_user) const { + + ERR_FAIL_INDEX_V(p_user, users.size(), -1); + return users[p_user].lightmap_slice; +} + +Rect2 BakedLightmapData::get_user_lightmap_uv_rect(int p_user) const { + + ERR_FAIL_INDEX_V(p_user, users.size(), Rect2(0, 0, 1, 1)); + return users[p_user].lightmap_uv_rect; } int BakedLightmapData::get_user_instance(int p_user) const { @@ -123,10 +149,39 @@ void BakedLightmapData::clear_users() { void BakedLightmapData::_set_user_data(const Array &p_data) { - ERR_FAIL_COND((p_data.size() % 3) != 0); + // Detect old lightmapper format + if (p_data.size() % 3 == 0) { + bool is_old_format = true; + for (int i = 0; i < p_data.size(); i += 3) { + is_old_format = is_old_format && p_data[i + 0].get_type() == Variant::NODE_PATH; + is_old_format = is_old_format && p_data[i + 1].is_ref(); + is_old_format = is_old_format && p_data[i + 2].get_type() == Variant::INT; + if (!is_old_format) { + break; + } + } + if (is_old_format) { +#ifdef DEBUG_ENABLED + WARN_PRINTS("Geometry at path " + String(p_data[0]) + " is using old lightmapper data. Please re-bake."); +#endif + Array adapted_data; + adapted_data.resize((p_data.size() / 3) * 5); + for (int i = 0; i < p_data.size() / 3; i++) { + adapted_data[i * 5 + 0] = p_data[i * 3 + 0]; + adapted_data[i * 5 + 1] = p_data[i * 3 + 1]; + adapted_data[i * 5 + 2] = -1; + adapted_data[i * 5 + 3] = Rect2(0, 0, 1, 1); + adapted_data[i * 5 + 4] = p_data[i * 3 + 2]; + } + _set_user_data(adapted_data); + return; + } + } + + ERR_FAIL_COND((p_data.size() % 5) != 0); - for (int i = 0; i < p_data.size(); i += 3) { - add_user(p_data[i], p_data[i + 1], p_data[i + 2]); + for (int i = 0; i < p_data.size(); i += 5) { + add_user(p_data[i], p_data[i + 1], p_data[i + 2], p_data[i + 3], p_data[i + 4]); } } @@ -135,7 +190,9 @@ Array BakedLightmapData::_get_user_data() const { Array ret; for (int i = 0; i < users.size(); i++) { ret.push_back(users[i].path); - ret.push_back(users[i].lightmap); + ret.push_back(users[i].lightmap_slice == -1 ? Ref(users[i].lightmap.single) : Ref(users[i].lightmap.layered)); + ret.push_back(users[i].lightmap_slice); + ret.push_back(users[i].lightmap_uv_rect); ret.push_back(users[i].instance_index); } return ret; @@ -164,7 +221,7 @@ void BakedLightmapData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmapData::set_energy); ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmapData::get_energy); - ClassDB::bind_method(D_METHOD("add_user", "path", "lightmap", "instance"), &BakedLightmapData::add_user); + ClassDB::bind_method(D_METHOD("add_user", "path", "lightmap", "lightmap_slice", "lightmap_uv_rect", "instance"), &BakedLightmapData::add_user); ClassDB::bind_method(D_METHOD("get_user_count"), &BakedLightmapData::get_user_count); ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &BakedLightmapData::get_user_path); ClassDB::bind_method(D_METHOD("get_user_lightmap", "user_idx"), &BakedLightmapData::get_user_lightmap); @@ -192,79 +249,117 @@ BakedLightmapData::~BakedLightmapData() { /////////////////////////// -BakedLightmap::BakeBeginFunc BakedLightmap::bake_begin_function = NULL; -BakedLightmap::BakeStepFunc BakedLightmap::bake_step_function = NULL; -BakedLightmap::BakeEndFunc BakedLightmap::bake_end_function = NULL; - -void BakedLightmap::set_bake_cell_size(float p_cell_size) { - bake_cell_size = p_cell_size; -} - -float BakedLightmap::get_bake_cell_size() const { - return bake_cell_size; -} +Lightmapper::BakeStepFunc BakedLightmap::bake_step_function; +Lightmapper::BakeStepFunc BakedLightmap::bake_substep_function; + +Size2i BakedLightmap::_compute_lightmap_size(const MeshesFound &p_mesh) { + double area = 0; + double uv_area = 0; + for (int i = 0; i < p_mesh.mesh->get_surface_count(); i++) { + Array arrays = p_mesh.mesh->surface_get_arrays(i); + PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; + PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; + PoolVector indices = arrays[Mesh::ARRAY_INDEX]; + + ERR_FAIL_COND_V(vertices.size() == 0, Vector2()); + ERR_FAIL_COND_V(uv2.size() == 0, Vector2()); + + int vc = vertices.size(); + PoolVector::Read vr = vertices.read(); + PoolVector::Read u2r = uv2.read(); + PoolVector::Read ir; + int ic = 0; + + if (indices.size()) { + ic = indices.size(); + ir = indices.read(); + } -void BakedLightmap::set_capture_cell_size(float p_cell_size) { - capture_cell_size = p_cell_size; -} + int faces = ic ? ic / 3 : vc / 3; + for (int j = 0; j < faces; j++) { + Vector3 vertex[3]; + Vector2 uv[3]; -float BakedLightmap::get_capture_cell_size() const { - return capture_cell_size; -} - -void BakedLightmap::set_extents(const Vector3 &p_extents) { - extents = p_extents; - update_gizmo(); - _change_notify("bake_extents"); -} + for (int k = 0; k < 3; k++) { + int idx = ic ? ir[j * 3 + k] : j * 3 + k; + vertex[k] = p_mesh.xform.xform(vr[idx]); + uv[k] = u2r[idx]; + } -Vector3 BakedLightmap::get_extents() const { - return extents; -} + Vector3 p1 = vertex[0]; + Vector3 p2 = vertex[1]; + Vector3 p3 = vertex[2]; + double a = p1.distance_to(p2); + double b = p2.distance_to(p3); + double c = p3.distance_to(p1); + double halfPerimeter = (a + b + c) / 2.0; + area += sqrt(halfPerimeter * (halfPerimeter - a) * (halfPerimeter - b) * (halfPerimeter - c)); + + Vector2 uv_p1 = uv[0]; + Vector2 uv_p2 = uv[1]; + Vector2 uv_p3 = uv[2]; + double uv_a = uv_p1.distance_to(uv_p2); + double uv_b = uv_p2.distance_to(uv_p3); + double uv_c = uv_p3.distance_to(uv_p1); + double uv_halfPerimeter = (uv_a + uv_b + uv_c) / 2.0; + uv_area += sqrt( + uv_halfPerimeter * (uv_halfPerimeter - uv_a) * (uv_halfPerimeter - uv_b) * (uv_halfPerimeter - uv_c)); + } + } -void BakedLightmap::set_bake_default_texels_per_unit(const float &p_bake_texels_per_unit) { - bake_default_texels_per_unit = p_bake_texels_per_unit; - update_gizmo(); -} + if (uv_area < 0.0001f) { + uv_area = 1.0; + } -float BakedLightmap::get_bake_default_texels_per_unit() const { - return bake_default_texels_per_unit; + int pixels = Math::round(ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit))); + int size = CLAMP(pixels, 2, 4096); + return Vector2i(size, size); } -void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List &plot_meshes, List &plot_lights) { - +void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, Vector &meshes, Vector &lights) { MeshInstance *mi = Object::cast_to(p_at_node); if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) { Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { - - bool all_have_uv2 = true; + bool all_have_uv2_and_normal = true; + bool surfaces_found = false; for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_TEX_UV2)) { - all_have_uv2 = false; + all_have_uv2_and_normal = false; break; } + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_NORMAL)) { + all_have_uv2_and_normal = false; + break; + } + surfaces_found = true; } - if (all_have_uv2) { - //READY TO BAKE! size hint could be computed if not found, actually.. - - AABB aabb = mesh->get_aabb(); - - Transform xf = get_global_transform().affine_inverse() * mi->get_global_transform(); - - if (AABB(-extents, extents * 2).intersects(xf.xform(aabb))) { - PlotMesh pm; - pm.local_xform = xf; - pm.mesh = mesh; - pm.path = get_path_to(mi); - pm.instance_idx = -1; - for (int i = 0; i < mesh->get_surface_count(); i++) { - pm.instance_materials.push_back(mi->get_surface_material(i)); + if (surfaces_found && all_have_uv2_and_normal) { + MeshesFound mf; + mf.cast_shadows = mi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF; + mf.generate_lightmap = mi->get_generate_lightmap(); + mf.xform = get_global_transform().affine_inverse() * mi->get_global_transform(); + mf.node_path = get_path_to(mi); + mf.subindex = -1; + mf.mesh = mesh; + + static const int lightmap_scale[4] = { 1, 2, 4, 8 }; //GeometryInstance3D::LIGHTMAP_SCALE_MAX = { 1, 2, 4, 8 }; + mf.lightmap_scale = lightmap_scale[mi->get_lightmap_scale()]; + + Ref all_override = mi->get_material_override(); + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (all_override.is_valid()) { + mf.overrides.push_back(all_override); + } else { + mf.overrides.push_back(mi->get_surface_material(i)); } - pm.override_material = mi->get_material_override(); - plot_meshes.push_back(pm); } + + meshes.push_back(mf); } } } @@ -272,19 +367,25 @@ void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List &plo Spatial *s = Object::cast_to(p_at_node); if (!mi && s) { - Array meshes = p_at_node->call("get_bake_meshes"); - if (meshes.size() && (meshes.size() & 1) == 0) { + Array bmeshes = p_at_node->call("get_bake_meshes"); + if (bmeshes.size() && (bmeshes.size() & 1) == 0) { Transform xf = get_global_transform().affine_inverse() * s->get_global_transform(); - for (int i = 0; i < meshes.size(); i += 2) { - PlotMesh pm; - Transform mesh_xf = meshes[i + 1]; - pm.local_xform = xf * mesh_xf; - pm.mesh = meshes[i]; - pm.instance_idx = i / 2; - if (!pm.mesh.is_valid()) + for (int i = 0; i < bmeshes.size(); i += 2) { + Ref mesh = bmeshes[i]; + if (!mesh.is_valid()) { continue; - pm.path = get_path_to(s); - plot_meshes.push_back(pm); + } + + MeshesFound mf; + + Transform mesh_xf = bmeshes[i + 1]; + mf.xform = xf * mesh_xf; + mf.node_path = get_path_to(s); + mf.subindex = i / 2; + mf.lightmap_scale = 1; + mf.mesh = mesh; + + meshes.push_back(mf); } } } @@ -292,290 +393,588 @@ void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List &plo Light *light = Object::cast_to(p_at_node); if (light && light->get_bake_mode() != Light::BAKE_DISABLED) { - PlotLight pl; - Transform xf = get_global_transform().affine_inverse() * light->get_global_transform(); - - pl.local_xform = xf; - pl.light = light; - plot_lights.push_back(pl); + LightsFound lf; + lf.xform = get_global_transform().affine_inverse() * light->get_global_transform(); + lf.light = light; + lights.push_back(lf); } - for (int i = 0; i < p_at_node->get_child_count(); i++) { + for (int i = 0; i < p_at_node->get_child_count(); i++) { Node *child = p_at_node->get_child(i); - if (!child->get_owner()) + if (!child->get_owner()) { continue; //maybe a helper + } - _find_meshes_and_lights(child, plot_meshes, plot_lights); + _find_meshes_and_lights(child, meshes, lights); } } -void BakedLightmap::set_hdr(bool p_enable) { - hdr = p_enable; -} +void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightmapper::MeshData &r_mesh_data, Vector > &r_albedo_textures, Vector > &r_emission_textures) { -bool BakedLightmap::is_hdr() const { - return hdr; -} + for (int i = 0; i < p_found_mesh.mesh->get_surface_count(); ++i) { + Ref mat = p_found_mesh.overrides[i]; + + if (mat.is_null()) { + mat = p_found_mesh.mesh->surface_get_material(i); + } + + Ref albedo_texture; + Color albedo_add = Color(0, 0, 0, 0); + Color albedo_mul = Color(1, 1, 1, 1); + + Ref emission_texture; + Color emission_add = Color(0, 0, 0, 0); + Color emission_mul = Color(1, 1, 1, 1); + + if (mat.is_valid()) { + + albedo_texture = mat->get_texture(SpatialMaterial::TEXTURE_ALBEDO); + + if (albedo_texture.is_valid()) { + albedo_mul = mat->get_albedo(); + } else { + albedo_add = mat->get_albedo(); + } -bool BakedLightmap::_bake_time(void *ud, float p_secs, float p_progress) { + emission_texture = mat->get_texture(SpatialMaterial::TEXTURE_EMISSION); + Color emission_color = mat->get_emission(); + float emission_energy = mat->get_emission_energy(); - uint64_t time = OS::get_singleton()->get_ticks_usec(); - BakeTimeData *btd = (BakeTimeData *)ud; + if (mat->get_emission_operator() == SpatialMaterial::EMISSION_OP_ADD) { + emission_mul = Color(1, 1, 1) * emission_energy; + emission_add = emission_color * emission_energy; + } else { + emission_mul = emission_color * emission_energy; + emission_add = Color(0, 0, 0); + } + } + + Lightmapper::MeshData::TextureDef albedo; + albedo.mul = albedo_mul; + albedo.add = albedo_add; + + if (albedo_texture.is_valid()) { + albedo.tex_rid = albedo_texture->get_rid(); + r_albedo_textures.push_back(albedo_texture); + } + + r_mesh_data.albedo.push_back(albedo); - if (time - btd->last_step > 1000000) { + Lightmapper::MeshData::TextureDef emission; + emission.mul = emission_mul; + emission.add = emission_add; - int mins_left = p_secs / 60; - int secs_left = Math::fmod(p_secs, 60.0f); - int percent = p_progress * 100; - bool abort = bake_step_function(btd->pass + percent, btd->text + " " + vformat(RTR("%d%%"), percent) + " " + vformat(RTR("(Time Left: %d:%02d s)"), mins_left, secs_left)); - btd->last_step = time; - if (abort) - return true; + if (emission_texture.is_valid()) { + emission.tex_rid = emission_texture->get_rid(); + r_emission_textures.push_back(emission_texture); + } + r_mesh_data.emission.push_back(emission); } +} - return false; +bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) { + BakeStepUD *bsud = (BakeStepUD *)ud; + bool ret = false; + if (bsud->func) { + ret = bsud->func(bsud->from_percent + p_completion * (bsud->to_percent - bsud->from_percent), p_text, bsud->ud, p_refresh); + } + return ret; } -BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, bool p_create_visual_debug) { +BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_save_path) { - String save_path; + bool no_save_path = false; + if (p_data_save_path == "" && (get_light_data().is_null() || !get_light_data()->get_path().is_resource_file())) { + no_save_path = true; + } - if (image_path.begins_with("res://")) { - save_path = image_path; - } else { - if (get_filename() != "") { - save_path = get_filename().get_base_dir(); - } else if (get_owner() && get_owner()->get_filename() != "") { - save_path = get_owner()->get_filename().get_base_dir(); + if (p_data_save_path == "") { + if (get_light_data().is_null()) { + no_save_path = true; + } else { + p_data_save_path = get_light_data()->get_path(); + if (!p_data_save_path.is_resource_file()) { + no_save_path = true; + } } + } - if (save_path == "") { + if (no_save_path) { + + if (image_path == "") { return BAKE_ERROR_NO_SAVE_PATH; + } else { + p_data_save_path = image_path; } - if (image_path != "") { - save_path.plus_file(image_path); - } + + WARN_PRINT("Using the deprecated property \"image_path\" as a save path, consider providing a better save path via the \"data_save_path\" parameter"); + p_data_save_path = image_path.plus_file("BakedLightmap.lmbake"); } + { //check for valid save path - DirAccessRef d = DirAccess::open(save_path); + DirAccessRef d = DirAccess::open(p_data_save_path.get_base_dir()); if (!d) { - ERR_PRINTS("Invalid Save Path '" + save_path + "'."); - return BAKE_ERROR_NO_SAVE_PATH; + ERR_FAIL_V_MSG(BAKE_ERROR_NO_SAVE_PATH, "Invalid save path '" + p_data_save_path + "'."); } } - Ref new_light_data; - new_light_data.instance(); + if (bake_step_function) { + bool cancelled = bake_step_function(0.0, TTR("Finding meshes and lights"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } - VoxelLightBaker baker; + Ref lightmapper = Lightmapper::create(); + ERR_FAIL_COND_V(lightmapper.is_null(), BAKE_ERROR_NO_LIGHTMAPPER); - int bake_subdiv; - int capture_subdiv; - AABB bake_bounds; - { - bake_bounds = AABB(-extents, extents * 2.0); - int subdiv = nearest_power_of_2_templated(int(bake_bounds.get_longest_axis_size() / bake_cell_size)); - bake_bounds.size[bake_bounds.get_longest_axis_index()] = subdiv * bake_cell_size; - bake_subdiv = nearest_shift(subdiv) + 1; + Vector lights_found; + Vector meshes_found; - capture_subdiv = bake_subdiv; - float css = bake_cell_size; - while (css < capture_cell_size && capture_subdiv > 2) { - capture_subdiv--; - css *= 2.0; - } + _find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), meshes_found, lights_found); + + if (meshes_found.size() == 0) { + return BAKE_ERROR_NO_MESHES; } - baker.begin_bake(bake_subdiv, bake_bounds); + for (int m_i = 0; m_i < meshes_found.size(); m_i++) { + if (bake_step_function) { + float p = (float)(m_i) / meshes_found.size(); + bool cancelled = bake_step_function(p * 0.05, vformat(TTR("Preparing geometry (%d/%d)"), m_i + 1, meshes_found.size()), nullptr, false); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } - List mesh_list; - List light_list; + MeshesFound &mf = meshes_found.write[m_i]; - _find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), mesh_list, light_list); + Size2i lightmap_size = mf.mesh->get_lightmap_size_hint(); - if (bake_begin_function) { - bake_begin_function(mesh_list.size() + light_list.size() + 1 + mesh_list.size() * 100); - } + if (lightmap_size == Vector2i(0, 0)) { + lightmap_size = _compute_lightmap_size(mf); + } + lightmap_size *= mf.lightmap_scale; - int step = 0; + Lightmapper::MeshData md; - int pmc = 0; + { + Dictionary d; + d["path"] = mf.node_path; + if (mf.subindex >= 0) { + d["subindex"] = mf.subindex; + } + d["cast_shadows"] = mf.cast_shadows; + d["generate_lightmap"] = mf.generate_lightmap; + d["node_name"] = mf.node_path.get_name(mf.node_path.get_name_count() - 1); + md.userdata = d; + } - for (List::Element *E = mesh_list.front(); E; E = E->next()) { + Basis normal_xform = mf.xform.basis.inverse().transposed(); - if (bake_step_function) { - bake_step_function(step++, RTR("Plotting Meshes: ") + " (" + itos(pmc + 1) + "/" + itos(mesh_list.size()) + ")"); + for (int i = 0; i < mf.mesh->get_surface_count(); i++) { + if (mf.mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + Array a = mf.mesh->surface_get_arrays(i); + + Vector vertices = a[Mesh::ARRAY_VERTEX]; + const Vector3 *vr = vertices.ptr(); + Vector uv2 = a[Mesh::ARRAY_TEX_UV2]; + const Vector2 *uv2r = nullptr; + Vector uv = a[Mesh::ARRAY_TEX_UV]; + const Vector2 *uvr = nullptr; + Vector normals = a[Mesh::ARRAY_NORMAL]; + const Vector3 *nr = nullptr; + Vector index = a[Mesh::ARRAY_INDEX]; + + ERR_CONTINUE(uv2.size() == 0); + ERR_CONTINUE(normals.size() == 0); + + if (!uv.empty()) { + uvr = uv.ptr(); + } + + uv2r = uv2.ptr(); + nr = normals.ptr(); + + int facecount; + const int *ir = nullptr; + + if (index.size()) { + facecount = index.size() / 3; + ir = index.ptr(); + } else { + facecount = vertices.size() / 3; + } + + md.surface_facecounts.push_back(facecount); + + for (int j = 0; j < facecount; j++) { + uint32_t vidx[3]; + + if (ir) { + for (int k = 0; k < 3; k++) { + vidx[k] = ir[j * 3 + k]; + } + } else { + for (int k = 0; k < 3; k++) { + vidx[k] = j * 3 + k; + } + } + + for (int k = 0; k < 3; k++) { + Vector3 v = mf.xform.xform(vr[vidx[k]]); + md.points.push_back(v); + + md.uv2.push_back(uv2r[vidx[k]]); + md.normal.push_back(normal_xform.xform(nr[vidx[k]]).normalized()); + + if (uvr != nullptr) { + md.uv.push_back(uvr[vidx[k]]); + } + } + } } - pmc++; - baker.plot_mesh(E->get().local_xform, E->get().mesh, E->get().instance_materials, E->get().override_material); + Vector > albedo_textures; + Vector > emission_textures; + + _get_material_images(mf, md, albedo_textures, emission_textures); + + for (int j = 0; j < albedo_textures.size(); j++) { + lightmapper->add_albedo_texture(albedo_textures[j]); + } + + for (int j = 0; j < emission_textures.size(); j++) { + lightmapper->add_emission_texture(emission_textures[j]); + } + + lightmapper->add_mesh(md, lightmap_size); } - pmc = 0; - baker.begin_bake_light(VoxelLightBaker::BakeQuality(bake_quality), VoxelLightBaker::BakeMode(bake_mode), propagation, energy); + for (int i = 0; i < lights_found.size(); i++) { + Light *light = lights_found[i].light; + Transform xf = lights_found[i].xform; + + if (Object::cast_to(light)) { + DirectionalLight *l = Object::cast_to(light); + lightmapper->add_directional_light(light->get_bake_mode() == Light::BAKE_ALL, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY)); + } else if (Object::cast_to(light)) { + OmniLight *l = Object::cast_to(light); + lightmapper->add_omni_light(light->get_bake_mode() == Light::BAKE_ALL, xf.origin, l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY), l->get_param(Light::PARAM_RANGE), l->get_param(Light::PARAM_ATTENUATION)); + } else if (Object::cast_to(light)) { + SpotLight *l = Object::cast_to(light); + lightmapper->add_spot_light(light->get_bake_mode() == Light::BAKE_ALL, xf.origin, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY), l->get_param(Light::PARAM_RANGE), l->get_param(Light::PARAM_ATTENUATION), l->get_param(Light::PARAM_SPOT_ANGLE), l->get_param(Light::PARAM_SPOT_ATTENUATION)); + } + } - for (List::Element *E = light_list.front(); E; E = E->next()) { + Ref environment_image; + Basis environment_xform; + if (environment_mode != ENVIRONMENT_MODE_DISABLED) { if (bake_step_function) { - bake_step_function(step++, RTR("Plotting Lights:") + " (" + itos(pmc + 1) + "/" + itos(light_list.size()) + ")"); + bake_step_function(0.1, TTR("Preparing environment"), nullptr, true); } - pmc++; - PlotLight pl = E->get(); - switch (pl.light->get_light_type()) { - case VS::LIGHT_DIRECTIONAL: { - baker.plot_light_directional(-pl.local_xform.basis.get_axis(2), pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_bake_mode() == Light::BAKE_ALL); + switch (environment_mode) { + case ENVIRONMENT_MODE_DISABLED: { + //nothing } break; - case VS::LIGHT_OMNI: { - baker.plot_light_omni(pl.local_xform.origin, pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_param(Light::PARAM_RANGE), pl.light->get_param(Light::PARAM_ATTENUATION), pl.light->get_bake_mode() == Light::BAKE_ALL); + case ENVIRONMENT_MODE_SCENE: { + Ref world = get_world(); + if (world.is_valid()) { + Ref env = world->get_environment(); + if (env.is_null()) { + env = world->get_fallback_environment(); + } + + if (env.is_valid()) { + environment_image = _get_irradiance_map(env, Vector2i(128, 64)); + environment_xform = get_global_transform().affine_inverse().basis * env->get_sky_orientation(); + } + } + } break; + case ENVIRONMENT_MODE_CUSTOM_SKY: { + if (environment_custom_sky.is_valid()) { + environment_image = _get_irradiance_from_sky(environment_custom_sky, Vector2i(128, 64)); + print_line(vformat("env -> %s", environment_custom_sky_rotation_degrees)); + environment_xform.set_euler(environment_custom_sky_rotation_degrees * Math_PI / 180.0); + } + } break; - case VS::LIGHT_SPOT: { - baker.plot_light_spot(pl.local_xform.origin, pl.local_xform.basis.get_axis(2), pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_param(Light::PARAM_RANGE), pl.light->get_param(Light::PARAM_ATTENUATION), pl.light->get_param(Light::PARAM_SPOT_ANGLE), pl.light->get_param(Light::PARAM_SPOT_ATTENUATION), pl.light->get_bake_mode() == Light::BAKE_ALL); + case ENVIRONMENT_MODE_CUSTOM_COLOR: { + environment_image.instance(); + environment_image->create(128, 64, false, Image::FORMAT_RGBF); + Color c = environment_custom_color; + c.r *= environment_custom_energy; + c.g *= environment_custom_energy; + c.b *= environment_custom_energy; + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 64; j++) { + environment_image->set_pixel(i, j, c); + } + } } break; } } - /*if (bake_step_function) { - bake_step_function(pmc++, RTR("Finishing Plot")); - }*/ - baker.end_bake(); + BakeStepUD bsud; + bsud.func = bake_step_function; + bsud.ud = nullptr; + bsud.from_percent = 0.1; + bsud.to_percent = 0.9; - Set used_mesh_names; + Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, generate_atlas, max_atlas_size, environment_image, environment_xform, _lightmap_bake_step_function, &bsud, bake_substep_function); - pmc = 0; - for (List::Element *E = mesh_list.front(); E; E = E->next()) { + if (bake_err != Lightmapper::BAKE_OK) { + switch (bake_err) { + case Lightmapper::BAKE_ERROR_USER_ABORTED: { + return BAKE_ERROR_USER_ABORTED; + } + case Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { + return BAKE_ERROR_LIGHTMAP_SIZE; + } + case Lightmapper::BAKE_ERROR_NO_MESHES: { + return BAKE_ERROR_NO_MESHES; + } + default: { + } + } + return BAKE_ERROR_NO_LIGHTMAPPER; + } - String mesh_name = E->get().mesh->get_name(); - if (mesh_name == "" || mesh_name.find(":") != -1 || mesh_name.find("/") != -1) { - mesh_name = "LightMap"; + Ref data; + if (get_light_data().is_valid()) { + data = get_light_data(); + set_light_data(Ref()); //clear + data->clear_users(); + } else { + data.instance(); + } + + if (capture_enabled) { + + if (bake_step_function) { + bool cancelled = bake_step_function(0.85, TTR("Generating capture"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } } - if (used_mesh_names.has(mesh_name)) { - int idx = 2; - String base = mesh_name; - while (true) { - mesh_name = base + itos(idx); - if (!used_mesh_names.has(mesh_name)) - break; - idx++; + VoxelLightBaker voxel_baker; + + int bake_subdiv; + int capture_subdiv; + AABB bake_bounds; + { + bake_bounds = AABB(-extents, extents * 2.0); + int subdiv = nearest_power_of_2_templated(int(bake_bounds.get_longest_axis_size() / capture_cell_size)); + bake_bounds.size[bake_bounds.get_longest_axis_index()] = subdiv * capture_cell_size; + bake_subdiv = nearest_shift(subdiv) + 1; + + capture_subdiv = bake_subdiv; + float css = capture_cell_size; + while (css < capture_cell_size && capture_subdiv > 2) { + capture_subdiv--; + css *= 2.0; } } - used_mesh_names.insert(mesh_name); - pmc++; - VoxelLightBaker::LightMapData lm; + voxel_baker.begin_bake(capture_subdiv + 1, bake_bounds); - Error err; - if (bake_step_function) { - BakeTimeData btd; - btd.text = RTR("Lighting Meshes: ") + mesh_name + " (" + itos(pmc) + "/" + itos(mesh_list.size()) + ")"; - btd.pass = step; - btd.last_step = 0; - err = baker.make_lightmap(E->get().local_xform, E->get().mesh, bake_default_texels_per_unit, lm, _bake_time, &btd); - if (err != OK) { - bake_end_function(); - if (err == ERR_SKIP) - return BAKE_ERROR_USER_ABORTED; - return BAKE_ERROR_CANT_CREATE_IMAGE; - } - step += 100; - } else { + for (int mesh_id = 0; mesh_id < meshes_found.size(); mesh_id++) { + MeshesFound &mf = meshes_found.write[mesh_id]; + voxel_baker.plot_mesh(mf.xform, mf.mesh, mf.overrides, Ref()); + } - err = baker.make_lightmap(E->get().local_xform, E->get().mesh, bake_default_texels_per_unit, lm); + VoxelLightBaker::BakeQuality capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_HIGH; + if (capture_quality == BakedLightmap::BakeQuality::BAKE_QUALITY_LOW) { + capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_LOW; + } else if (capture_quality == BakedLightmap::BakeQuality::BAKE_QUALITY_MEDIUM) { + capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_MEDIUM; } - if (err == OK) { + voxel_baker.begin_bake_light(capt_quality, capture_propagation); + + for (int i = 0; i < lights_found.size(); i++) { + LightsFound &lf = lights_found.write[i]; + switch (lf.light->get_light_type()) { + case VS::LIGHT_DIRECTIONAL: { + voxel_baker.plot_light_directional(-lf.xform.basis.get_axis(2), lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_bake_mode() == Light::BAKE_ALL); + } break; + case VS::LIGHT_OMNI: { + voxel_baker.plot_light_omni(lf.xform.origin, lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_param(Light::PARAM_RANGE), lf.light->get_param(Light::PARAM_ATTENUATION), lf.light->get_bake_mode() == Light::BAKE_ALL); + } break; + case VS::LIGHT_SPOT: { + voxel_baker.plot_light_spot(lf.xform.origin, lf.xform.basis.get_axis(2), lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_param(Light::PARAM_RANGE), lf.light->get_param(Light::PARAM_ATTENUATION), lf.light->get_param(Light::PARAM_SPOT_ANGLE), lf.light->get_param(Light::PARAM_SPOT_ATTENUATION), lf.light->get_bake_mode() == Light::BAKE_ALL); + + } break; + } + } - Ref image; - image.instance(); + voxel_baker.end_bake(); - uint32_t tex_flags = Texture::FLAGS_DEFAULT; - if (hdr) { + AABB bounds = AABB(-extents, extents * 2); + data->set_cell_subdiv(capture_subdiv); + data->set_bounds(bounds); + data->set_octree(voxel_baker.create_capture_octree(capture_subdiv)); - //just save a regular image - PoolVector data; - int s = lm.light.size(); - data.resize(lm.light.size() * 2); - { + { + float bake_bound_size = bake_bounds.get_longest_axis_size(); + Transform to_bounds; + to_bounds.basis.scale(Vector3(bake_bound_size, bake_bound_size, bake_bound_size)); + to_bounds.origin = bounds.position; - PoolVector::Write w = data.write(); - PoolVector::Read r = lm.light.read(); - uint16_t *hfw = (uint16_t *)w.ptr(); - for (int i = 0; i < s; i++) { - hfw[i] = Math::make_half_float(r[i]); - } - } + Transform to_grid; + to_grid.basis.scale(Vector3(1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1))); + + Transform to_cell_space = to_grid * to_bounds.affine_inverse(); + data->set_cell_space_transform(to_cell_space); + } + } + + if (bake_step_function) { + bool cancelled = bake_step_function(0.9, TTR("Saving lightmaps"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + Vector > images; + for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { + images.push_back(lightmapper->get_bake_texture(i)); + } + + if (generate_atlas) { + + Ref large_image; + large_image.instance(); + large_image->create(images[0]->get_width(), images[0]->get_height() * images.size(), false, images[0]->get_format()); + for (int i = 0; i < images.size(); i++) { + large_image->blit_rect(images[i], Rect2(0, 0, images[0]->get_width(), images[0]->get_height()), Point2(0, images[0]->get_height() * i)); + } + + Ref texture; + String base_path = p_data_save_path.get_basename(); - image->create(lm.width, lm.height, false, Image::FORMAT_RGBH, data); + if (ResourceLoader::import) { + base_path += ".exr"; + String relative_path = base_path; + if (relative_path.begins_with("res://")) { + relative_path = relative_path.substr(6, relative_path.length()); + } + large_image->save_exr(relative_path, false); + + Ref config; + config.instance(); + if (FileAccess::exists(base_path + ".import")) { + config->load(base_path + ".import"); } else { + // Set only if settings don't exist, to keep user choice + config->set_value("params", "compress/mode", 0); + } + config->set_value("remap", "importer", "texture_array"); + config->set_value("remap", "type", "TextureArray"); + config->set_value("params", "detect_3d", false); + config->set_value("params", "flags/repeat", false); + config->set_value("params", "flags/filter", true); + config->set_value("params", "flags/mipmaps", false); + config->set_value("params", "flags/srgb", false); + config->set_value("params", "slices/horizontal", 1); + config->set_value("params", "slices/vertical", images.size()); + config->save(base_path + ".import"); + + ResourceLoader::import(base_path); + texture = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? + } else { - //just save a regular image - PoolVector data; - int s = lm.light.size(); - data.resize(lm.light.size()); - { - - PoolVector::Write w = data.write(); - PoolVector::Read r = lm.light.read(); - for (int i = 0; i < s; i += 3) { - Color c(r[i + 0], r[i + 1], r[i + 2]); - c = c.to_srgb(); - w[i + 0] = CLAMP(c.r * 255, 0, 255); - w[i + 1] = CLAMP(c.g * 255, 0, 255); - w[i + 2] = CLAMP(c.b * 255, 0, 255); - } + base_path += ".texarr"; + Ref tex; + bool set_path = true; + if (ResourceCache::has(base_path)) { + tex = Ref((Resource *)ResourceCache::get(base_path)); + set_path = false; + } + + if (!tex.is_valid()) { + tex.instance(); + } + + tex->create(images[0]->get_width(), images[0]->get_height(), images.size(), images[0]->get_format(), Texture::FLAGS_DEFAULT); + for (int i = 0; i < images.size(); i++) { + tex->set_layer_data(images[i], i); + } + + ResourceSaver::save(base_path, tex, ResourceSaver::FLAG_CHANGE_PATH); + if (set_path) { + tex->set_path(base_path); + } + texture = tex; + } + + for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { + if (meshes_found[i].generate_lightmap) { + Dictionary d = lightmapper->get_bake_mesh_userdata(i); + NodePath np = d["path"]; + int32_t subindex = -1; + if (d.has("subindex")) { + subindex = d["subindex"]; } - image->create(lm.width, lm.height, false, Image::FORMAT_RGB8, data); + Rect2 uv_rect = lightmapper->get_bake_mesh_uv_scale(i); + int slice_index = lightmapper->get_bake_mesh_texture_slice(i); + data->add_user(np, texture, slice_index, uv_rect, subindex); + } + } + } else { + for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { - //This texture is saved to SRGB for two reasons: - // 1) first is so it looks better when doing the LINEAR->SRGB conversion (more accurate) - // 2) So it can be used in the GLES2 backend, which does not support linkear workflow - tex_flags |= Texture::FLAG_CONVERT_TO_LINEAR; + if (!meshes_found[i].generate_lightmap) { + continue; } - String image_path = save_path.plus_file(mesh_name); Ref texture; + String base_path = p_data_save_path.get_base_dir().plus_file(images[i]->get_name()); if (ResourceLoader::import) { - bool srgb = false; - if (false && hdr) { - //save hdr - } else { - image_path += ".png"; - print_line("image path saving png: " + image_path); - image->save_png(image_path); - srgb = true; + base_path += ".exr"; + String relative_path = base_path; + if (relative_path.begins_with("res://")) { + relative_path = relative_path.substr(6, relative_path.length()); } + images[i]->save_exr(relative_path, false); - if (!FileAccess::exists(image_path + ".import")) { - Ref config; - config.instance(); - config->set_value("remap", "importer", "texture"); - config->set_value("remap", "type", "StreamTexture"); - config->set_value("params", "compress/mode", 2); - config->set_value("params", "detect_3d", false); - config->set_value("params", "flags/repeat", false); - config->set_value("params", "flags/filter", true); - config->set_value("params", "flags/mipmaps", false); - config->set_value("params", "flags/srgb", srgb); - - config->save(image_path + ".import"); + Ref config; + config.instance(); + if (FileAccess::exists(base_path + ".import")) { + config->load(base_path + ".import"); + } else { + // Set only if settings don't exist, to keep user choice + config->set_value("params", "compress/mode", 0); } - - ResourceLoader::import(image_path); - texture = ResourceLoader::load(image_path); //if already loaded, it will be updated on refocus? + config->set_value("remap", "importer", "texture"); + config->set_value("remap", "type", "StreamTexture"); + config->set_value("params", "detect_3d", false); + config->set_value("params", "flags/repeat", false); + config->set_value("params", "flags/filter", true); + config->set_value("params", "flags/mipmaps", false); + config->set_value("params", "flags/srgb", false); + + config->save(base_path + ".import"); + + ResourceLoader::import(base_path); + texture = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? } else { - image_path += ".text"; + base_path += ".tex"; Ref tex; bool set_path = true; - if (ResourceCache::has(image_path)) { - tex = Ref((Resource *)ResourceCache::get(image_path)); + if (ResourceCache::has(base_path)) { + tex = Ref((Resource *)ResourceCache::get(base_path)); set_path = false; } @@ -583,67 +982,80 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, bool p_create_vi tex.instance(); } - tex->create_from_image(image, tex_flags); + tex->create_from_image(images[i], Texture::FLAGS_DEFAULT); - err = ResourceSaver::save(image_path, tex, ResourceSaver::FLAG_CHANGE_PATH); + ResourceSaver::save(base_path, tex, ResourceSaver::FLAG_CHANGE_PATH); if (set_path) { - tex->set_path(image_path); + tex->set_path(base_path); } texture = tex; } - if (err != OK) { - if (bake_end_function) { - bake_end_function(); - } - ERR_FAIL_COND_V(err != OK, BAKE_ERROR_CANT_CREATE_IMAGE); + + Dictionary d = lightmapper->get_bake_mesh_userdata(i); + NodePath np = d["path"]; + int32_t subindex = -1; + if (d.has("subindex")) { + subindex = d["subindex"]; } - new_light_data->add_user(E->get().path, texture, E->get().instance_idx); + Rect2 uv_rect = Rect2(0, 0, 1, 1); + int slice_index = -1; + data->add_user(np, texture, slice_index, uv_rect, subindex); } } - AABB bounds = AABB(-extents, extents * 2); - new_light_data->set_cell_subdiv(capture_subdiv); - new_light_data->set_bounds(bounds); - new_light_data->set_octree(baker.create_capture_octree(capture_subdiv)); - { - - float bake_bound_size = bake_bounds.get_longest_axis_size(); - Transform to_bounds; - to_bounds.basis.scale(Vector3(bake_bound_size, bake_bound_size, bake_bound_size)); - to_bounds.origin = bounds.position; + if (bake_step_function) { + bool cancelled = bake_step_function(1.0, TTR("Done"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } - Transform to_grid; - to_grid.basis.scale(Vector3(1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1))); + Error err = ResourceSaver::save(p_data_save_path, data); + data->set_path(p_data_save_path); - Transform to_cell_space = to_grid * to_bounds.affine_inverse(); - new_light_data->set_cell_space_transform(to_cell_space); + if (err != OK) { + return BAKE_ERROR_CANT_CREATE_IMAGE; } - if (bake_end_function) { - bake_end_function(); - } + set_light_data(data); - //create the data for visual server + return BAKE_ERROR_OK; +} - if (p_create_visual_debug) { - MultiMeshInstance *mmi = memnew(MultiMeshInstance); - mmi->set_multimesh(baker.create_debug_multimesh(VoxelLightBaker::DEBUG_LIGHT)); - add_child(mmi); -#ifdef TOOLS_ENABLED - if (get_tree()->get_edited_scene_root() == this) { - mmi->set_owner(this); - } else { - mmi->set_owner(get_owner()); - } -#else - mmi->set_owner(get_owner()); -#endif - } +void BakedLightmap::set_capture_cell_size(float p_cell_size) { + capture_cell_size = MAX(0.1, p_cell_size); +} - set_light_data(new_light_data); +float BakedLightmap::get_capture_cell_size() const { + return capture_cell_size; +} - return BAKE_ERROR_OK; +void BakedLightmap::set_extents(const Vector3 &p_extents) { + extents = p_extents; + update_gizmo(); + _change_notify("bake_extents"); +} + +Vector3 BakedLightmap::get_extents() const { + return extents; +} + +void BakedLightmap::set_default_texels_per_unit(const float &p_bake_texels_per_unit) { + default_texels_per_unit = MAX(0.0, p_bake_texels_per_unit); +} + +float BakedLightmap::get_default_texels_per_unit() const { + return default_texels_per_unit; +} + +void BakedLightmap::set_capture_enabled(bool p_enable) { + capture_enabled = p_enable; + _change_notify(); +} + +bool BakedLightmap::get_capture_enabled() const { + return capture_enabled; } void BakedLightmap::_notification(int p_what) { @@ -668,20 +1080,21 @@ void BakedLightmap::_assign_lightmaps() { ERR_FAIL_COND(!light_data.is_valid()); for (int i = 0; i < light_data->get_user_count(); i++) { - Ref lightmap = light_data->get_user_lightmap(i); + Ref lightmap = light_data->get_user_lightmap(i); ERR_CONTINUE(!lightmap.is_valid()); + ERR_CONTINUE(!Object::cast_to(lightmap.ptr()) && !Object::cast_to(lightmap.ptr())); Node *node = get_node(light_data->get_user_path(i)); int instance_idx = light_data->get_user_instance(i); if (instance_idx >= 0) { RID instance = node->call("get_bake_mesh_instance", instance_idx); if (instance.is_valid()) { - VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid()); + VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); - VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid()); + VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); } } } @@ -694,14 +1107,63 @@ void BakedLightmap::_clear_lightmaps() { if (instance_idx >= 0) { RID instance = node->call("get_bake_mesh_instance", instance_idx); if (instance.is_valid()) { - VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), RID()); + VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), RID(), -1, Rect2(0, 0, 1, 1)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); - VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), RID()); + VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), RID(), -1, Rect2(0, 0, 1, 1)); + } + } +} + +Ref BakedLightmap::_get_irradiance_from_sky(Ref p_sky, Vector2i p_size) { + if (p_sky.is_null()) { + return Ref(); + } + + Ref sky_image; + Ref panorama = p_sky; + if (panorama.is_valid()) { + sky_image = panorama->get_panorama()->get_data(); + } + Ref procedural = p_sky; + if (procedural.is_valid()) { + sky_image = procedural->get_panorama(); + } + + if (sky_image.is_null()) { + return Ref(); + } + + sky_image->convert(Image::FORMAT_RGBF); + sky_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_CUBIC); + return sky_image; +} + +Ref BakedLightmap::_get_irradiance_map(Ref p_env, Vector2i p_size) { + Environment::BGMode bg_mode = p_env->get_background(); + switch (bg_mode) { + case Environment::BG_SKY: { + return _get_irradiance_from_sky(p_env->get_sky(), Vector2i(128, 64)); + } + case Environment::BG_CLEAR_COLOR: + case Environment::BG_COLOR: { + Color c = bg_mode == Environment::BG_CLEAR_COLOR ? Color(GLOBAL_GET("rendering/environment/default_clear_color")) : p_env->get_bg_color(); + c.r *= p_env->get_bg_energy(); + c.g *= p_env->get_bg_energy(); + c.b *= p_env->get_bg_energy(); + + Ref ret; + ret.instance(); + ret->create(p_size.x, p_size.y, false, Image::FORMAT_RGBF); + ret->fill(c); + return ret; + } + default: { } } + return Ref(); } void BakedLightmap::set_light_data(const Ref &p_data) { @@ -713,6 +1175,7 @@ void BakedLightmap::set_light_data(const Ref &p_data) { set_base(RID()); } light_data = p_data; + _change_notify(); if (light_data.is_valid()) { set_base(light_data->get_rid()); @@ -727,44 +1190,50 @@ Ref BakedLightmap::get_light_data() const { return light_data; } -void BakedLightmap::_debug_bake() { - bake(get_parent(), true); +void BakedLightmap::set_capture_propagation(float p_propagation) { + capture_propagation = p_propagation; +} + +float BakedLightmap::get_capture_propagation() const { + + return capture_propagation; } -void BakedLightmap::set_propagation(float p_propagation) { - propagation = p_propagation; +void BakedLightmap::set_capture_quality(BakeQuality p_quality) { + capture_quality = p_quality; } -float BakedLightmap::get_propagation() const { +BakedLightmap::BakeQuality BakedLightmap::get_capture_quality() const { + return capture_quality; +} - return propagation; +void BakedLightmap::set_generate_atlas(bool p_enabled) { + generate_atlas = p_enabled; } -void BakedLightmap::set_energy(float p_energy) { - energy = p_energy; +bool BakedLightmap::is_generate_atlas_enabled() const { + return generate_atlas; } -float BakedLightmap::get_energy() const { +void BakedLightmap::set_max_atlas_size(int p_size) { + ERR_FAIL_COND(p_size < 2048); + max_atlas_size = p_size; +} - return energy; +int BakedLightmap::get_max_atlas_size() const { + return max_atlas_size; } void BakedLightmap::set_bake_quality(BakeQuality p_quality) { bake_quality = p_quality; + _change_notify(); } BakedLightmap::BakeQuality BakedLightmap::get_bake_quality() const { return bake_quality; } -void BakedLightmap::set_bake_mode(BakeMode p_mode) { - bake_mode = p_mode; -} - -BakedLightmap::BakeMode BakedLightmap::get_bake_mode() const { - return bake_mode; -} - +#ifndef DISABLE_DEPRECATED void BakedLightmap::set_image_path(const String &p_path) { image_path = p_path; } @@ -772,6 +1241,74 @@ void BakedLightmap::set_image_path(const String &p_path) { String BakedLightmap::get_image_path() const { return image_path; } +#endif + +void BakedLightmap::set_use_denoiser(bool p_enable) { + + use_denoiser = p_enable; +} + +bool BakedLightmap::is_using_denoiser() const { + + return use_denoiser; +} + +void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) { + environment_mode = p_mode; + _change_notify(); +} + +BakedLightmap::EnvironmentMode BakedLightmap::get_environment_mode() const { + return environment_mode; +} + +void BakedLightmap::set_environment_custom_sky(const Ref &p_sky) { + environment_custom_sky = p_sky; +} + +Ref BakedLightmap::get_environment_custom_sky() const { + return environment_custom_sky; +} + +void BakedLightmap::set_environment_custom_sky_rotation_degrees(const Vector3 &p_rotation) { + environment_custom_sky_rotation_degrees = p_rotation; +} + +Vector3 BakedLightmap::get_environment_custom_sky_rotation_degrees() const { + return environment_custom_sky_rotation_degrees; +} + +void BakedLightmap::set_environment_custom_color(const Color &p_color) { + environment_custom_color = p_color; +} +Color BakedLightmap::get_environment_custom_color() const { + return environment_custom_color; +} + +void BakedLightmap::set_environment_custom_energy(float p_energy) { + environment_custom_energy = p_energy; +} +float BakedLightmap::get_environment_custom_energy() const { + return environment_custom_energy; +} + +void BakedLightmap::set_bounces(int p_bounces) { + ERR_FAIL_COND(p_bounces < 0 || p_bounces > 16); + bounces = p_bounces; +} + +int BakedLightmap::get_bounces() const { + return bounces; +} + +void BakedLightmap::set_bias(float p_bias) { + ERR_FAIL_COND(p_bias < 0.00001); + bias = p_bias; +} + +float BakedLightmap::get_bias() const { + return bias; +} AABB BakedLightmap::get_aabb() const { return AABB(-extents, extents * 2); @@ -780,85 +1317,160 @@ PoolVector BakedLightmap::get_faces(uint32_t p_usage_flags) const { return PoolVector(); } +void BakedLightmap::_validate_property(PropertyInfo &property) const { + + if (property.name.begins_with("environment_custom_sky") && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) { + property.usage = 0; + } + + if (property.name == "environment_custom_color" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR) { + property.usage = 0; + } + + if (property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) { + property.usage = 0; + } + + if (property.name.begins_with("atlas") && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } + + if (property.name.begins_with("capture") && property.name != "capture_enabled" && !capture_enabled) { + property.usage = 0; + } +} + void BakedLightmap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_light_data", "data"), &BakedLightmap::set_light_data); ClassDB::bind_method(D_METHOD("get_light_data"), &BakedLightmap::get_light_data); - ClassDB::bind_method(D_METHOD("set_bake_cell_size", "bake_cell_size"), &BakedLightmap::set_bake_cell_size); - ClassDB::bind_method(D_METHOD("get_bake_cell_size"), &BakedLightmap::get_bake_cell_size); + ClassDB::bind_method(D_METHOD("set_bake_quality", "quality"), &BakedLightmap::set_bake_quality); + ClassDB::bind_method(D_METHOD("get_bake_quality"), &BakedLightmap::get_bake_quality); - ClassDB::bind_method(D_METHOD("set_capture_cell_size", "capture_cell_size"), &BakedLightmap::set_capture_cell_size); - ClassDB::bind_method(D_METHOD("get_capture_cell_size"), &BakedLightmap::get_capture_cell_size); + ClassDB::bind_method(D_METHOD("set_bounces", "bounces"), &BakedLightmap::set_bounces); + ClassDB::bind_method(D_METHOD("get_bounces"), &BakedLightmap::get_bounces); - ClassDB::bind_method(D_METHOD("set_bake_quality", "bake_quality"), &BakedLightmap::set_bake_quality); - ClassDB::bind_method(D_METHOD("get_bake_quality"), &BakedLightmap::get_bake_quality); + ClassDB::bind_method(D_METHOD("set_bias", "bias"), &BakedLightmap::set_bias); + ClassDB::bind_method(D_METHOD("get_bias"), &BakedLightmap::get_bias); + + ClassDB::bind_method(D_METHOD("set_environment_mode", "mode"), &BakedLightmap::set_environment_mode); + ClassDB::bind_method(D_METHOD("get_environment_mode"), &BakedLightmap::get_environment_mode); + + ClassDB::bind_method(D_METHOD("set_environment_custom_sky", "sky"), &BakedLightmap::set_environment_custom_sky); + ClassDB::bind_method(D_METHOD("get_environment_custom_sky"), &BakedLightmap::get_environment_custom_sky); + + ClassDB::bind_method(D_METHOD("set_environment_custom_sky_rotation_degrees", "rotation"), &BakedLightmap::set_environment_custom_sky_rotation_degrees); + ClassDB::bind_method(D_METHOD("get_environment_custom_sky_rotation_degrees"), &BakedLightmap::get_environment_custom_sky_rotation_degrees); + + ClassDB::bind_method(D_METHOD("set_environment_custom_color", "color"), &BakedLightmap::set_environment_custom_color); + ClassDB::bind_method(D_METHOD("get_environment_custom_color"), &BakedLightmap::get_environment_custom_color); + + ClassDB::bind_method(D_METHOD("set_environment_custom_energy", "energy"), &BakedLightmap::set_environment_custom_energy); + ClassDB::bind_method(D_METHOD("get_environment_custom_energy"), &BakedLightmap::get_environment_custom_energy); - ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &BakedLightmap::set_bake_mode); - ClassDB::bind_method(D_METHOD("get_bake_mode"), &BakedLightmap::get_bake_mode); + ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser); + ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser); + + ClassDB::bind_method(D_METHOD("set_generate_atlas", "enabled"), &BakedLightmap::set_generate_atlas); + ClassDB::bind_method(D_METHOD("is_generate_atlas_enabled"), &BakedLightmap::is_generate_atlas_enabled); + + ClassDB::bind_method(D_METHOD("set_max_atlas_size", "max_atlas_size"), &BakedLightmap::set_max_atlas_size); + ClassDB::bind_method(D_METHOD("get_max_atlas_size"), &BakedLightmap::get_max_atlas_size); + + ClassDB::bind_method(D_METHOD("set_capture_quality", "capture_quality"), &BakedLightmap::set_capture_quality); + ClassDB::bind_method(D_METHOD("get_capture_quality"), &BakedLightmap::get_capture_quality); ClassDB::bind_method(D_METHOD("set_extents", "extents"), &BakedLightmap::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &BakedLightmap::get_extents); - ClassDB::bind_method(D_METHOD("set_bake_default_texels_per_unit", "texels"), &BakedLightmap::set_bake_default_texels_per_unit); - ClassDB::bind_method(D_METHOD("get_bake_default_texels_per_unit"), &BakedLightmap::get_bake_default_texels_per_unit); + ClassDB::bind_method(D_METHOD("set_default_texels_per_unit", "texels"), &BakedLightmap::set_default_texels_per_unit); + ClassDB::bind_method(D_METHOD("get_default_texels_per_unit"), &BakedLightmap::get_default_texels_per_unit); - ClassDB::bind_method(D_METHOD("set_propagation", "propagation"), &BakedLightmap::set_propagation); - ClassDB::bind_method(D_METHOD("get_propagation"), &BakedLightmap::get_propagation); + ClassDB::bind_method(D_METHOD("set_capture_propagation", "propagation"), &BakedLightmap::set_capture_propagation); + ClassDB::bind_method(D_METHOD("get_capture_propagation"), &BakedLightmap::get_capture_propagation); - ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmap::set_energy); - ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmap::get_energy); - - ClassDB::bind_method(D_METHOD("set_hdr", "hdr"), &BakedLightmap::set_hdr); - ClassDB::bind_method(D_METHOD("is_hdr"), &BakedLightmap::is_hdr); + ClassDB::bind_method(D_METHOD("set_capture_enabled", "enabled"), &BakedLightmap::set_capture_enabled); + ClassDB::bind_method(D_METHOD("get_capture_enabled"), &BakedLightmap::get_capture_enabled); + ClassDB::bind_method(D_METHOD("set_capture_cell_size", "capture_cell_size"), &BakedLightmap::set_capture_cell_size); + ClassDB::bind_method(D_METHOD("get_capture_cell_size"), &BakedLightmap::get_capture_cell_size); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_image_path", "image_path"), &BakedLightmap::set_image_path); ClassDB::bind_method(D_METHOD("get_image_path"), &BakedLightmap::get_image_path); +#endif + ClassDB::bind_method(D_METHOD("bake", "from_node", "data_save_path"), &BakedLightmap::bake, DEFVAL(Variant()), DEFVAL("")); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); + + ADD_GROUP("Tweaks", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_texels_per_unit", PROPERTY_HINT_RANGE, "0.0,64.0,0.01,or_greater"), "set_default_texels_per_unit", "get_default_texels_per_unit"); + + ADD_GROUP("Atlas", "atlas_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "atlas_generate"), "set_generate_atlas", "is_generate_atlas_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "atlas_max_size"), "set_max_atlas_size", "get_max_atlas_size"); + + ADD_GROUP("Environment", "environment_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "environment_mode", PROPERTY_HINT_ENUM, "Disabled,Scene,Custom Sky,Custom Color"), "set_environment_mode", "get_environment_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment_custom_sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_environment_custom_sky", "get_environment_custom_sky"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "environment_custom_sky_rotation_degrees", PROPERTY_HINT_NONE), "set_environment_custom_sky_rotation_degrees", "get_environment_custom_sky_rotation_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "environment_custom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_environment_custom_color", "get_environment_custom_color"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "environment_custom_energy", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_environment_custom_energy", "get_environment_custom_energy"); - ClassDB::bind_method(D_METHOD("bake", "from_node", "create_visual_debug"), &BakedLightmap::bake, DEFVAL(Variant()), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("debug_bake"), &BakedLightmap::_debug_bake); - ClassDB::set_method_flags(get_class_static(), _scs_create("debug_bake"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); - - ADD_GROUP("Bake", "bake_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_bake_cell_size", "get_bake_cell_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_bake_quality", "get_bake_quality"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mode", PROPERTY_HINT_ENUM, "ConeTrace,RayTrace"), "set_bake_mode", "get_bake_mode"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_propagation", "get_propagation"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_energy", PROPERTY_HINT_RANGE, "0,32,0.01"), "set_energy", "get_energy"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_hdr"), "set_hdr", "is_hdr"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "bake_extents"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_default_texels_per_unit"), "set_bake_default_texels_per_unit", "get_bake_default_texels_per_unit"); ADD_GROUP("Capture", "capture_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_capture_cell_size", "get_capture_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_enabled"), "set_capture_enabled", "get_capture_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_cell_size", PROPERTY_HINT_RANGE, "0.25,2.0,0.05,or_greater"), "set_capture_cell_size", "get_capture_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "capture_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_capture_quality", "get_capture_quality"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_capture_propagation", "get_capture_propagation"); + ADD_GROUP("Data", ""); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_path", PROPERTY_HINT_DIR), "set_image_path", "get_image_path"); +#ifndef DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_path", PROPERTY_HINT_DIR, "", 0), "set_image_path", "get_image_path"); +#endif ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_data", PROPERTY_HINT_RESOURCE_TYPE, "BakedLightmapData"), "set_light_data", "get_light_data"); BIND_ENUM_CONSTANT(BAKE_QUALITY_LOW); BIND_ENUM_CONSTANT(BAKE_QUALITY_MEDIUM); BIND_ENUM_CONSTANT(BAKE_QUALITY_HIGH); - BIND_ENUM_CONSTANT(BAKE_MODE_CONE_TRACE); - BIND_ENUM_CONSTANT(BAKE_MODE_RAY_TRACE); + BIND_ENUM_CONSTANT(BAKE_QUALITY_ULTRA); BIND_ENUM_CONSTANT(BAKE_ERROR_OK); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_SAVE_PATH); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_MESHES); BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE); BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED); + + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED); + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE); + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_CUSTOM_SKY); + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_CUSTOM_COLOR); } BakedLightmap::BakedLightmap() { extents = Vector3(10, 10, 10); - bake_default_texels_per_unit = 20; - bake_cell_size = 0.25; - capture_cell_size = 0.5; + default_texels_per_unit = 16.0f; bake_quality = BAKE_QUALITY_MEDIUM; - bake_mode = BAKE_MODE_CONE_TRACE; - energy = 1; - propagation = 1; - hdr = false; - image_path = "."; + capture_quality = BAKE_QUALITY_MEDIUM; + capture_propagation = 1; + capture_enabled = true; + bounces = 3; + image_path = ""; set_disable_scale(true); + capture_cell_size = 0.5; + + environment_mode = ENVIRONMENT_MODE_DISABLED; + environment_custom_color = Color(0.2, 0.7, 1.0); + environment_custom_energy = 1.0; + + use_denoiser = true; + bias = 0.005; + + generate_atlas = true; + max_atlas_size = 4096; } diff --git a/scene/3d/baked_lightmap.h b/scene/3d/baked_lightmap.h index 28f6196dee75..33f9a62a57ca 100644 --- a/scene/3d/baked_lightmap.h +++ b/scene/3d/baked_lightmap.h @@ -31,12 +31,15 @@ #ifndef BAKED_INDIRECT_LIGHT_H #define BAKED_INDIRECT_LIGHT_H +#include "core/local_vector.h" #include "multimesh_instance.h" #include "scene/3d/light.h" +#include "scene/3d/lightmapper.h" #include "scene/3d/visual_instance.h" class BakedLightmapData : public Resource { GDCLASS(BakedLightmapData, Resource); + RES_BASE_EXTENSION("lmbake") RID baked_light; AABB bounds; @@ -47,7 +50,12 @@ class BakedLightmapData : public Resource { struct User { NodePath path; - Ref lightmap; + struct { + Ref single; + Ref layered; + } lightmap; + int lightmap_slice; + Rect2 lightmap_uv_rect; int instance_index; }; @@ -75,10 +83,12 @@ class BakedLightmapData : public Resource { void set_energy(float p_energy); float get_energy() const; - void add_user(const NodePath &p_path, const Ref &p_lightmap, int p_instance = -1); + void add_user(const NodePath &p_path, const Ref &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance); int get_user_count() const; NodePath get_user_path(int p_user) const; - Ref get_user_lightmap(int p_user) const; + Ref get_user_lightmap(int p_user) const; + int get_user_lightmap_slice(int p_user) const; + Rect2 get_user_lightmap_uv_rect(int p_user) const; int get_user_instance(int p_user) const; void clear_users(); @@ -94,12 +104,8 @@ class BakedLightmap : public VisualInstance { enum BakeQuality { BAKE_QUALITY_LOW, BAKE_QUALITY_MEDIUM, - BAKE_QUALITY_HIGH - }; - - enum BakeMode { - BAKE_MODE_CONE_TRACE, - BAKE_MODE_RAY_TRACE, + BAKE_QUALITY_HIGH, + BAKE_QUALITY_ULTRA }; enum BakeError { @@ -107,108 +113,154 @@ class BakedLightmap : public VisualInstance { BAKE_ERROR_NO_SAVE_PATH, BAKE_ERROR_NO_MESHES, BAKE_ERROR_CANT_CREATE_IMAGE, - BAKE_ERROR_USER_ABORTED + BAKE_ERROR_LIGHTMAP_SIZE, + BAKE_ERROR_INVALID_MESH, + BAKE_ERROR_USER_ABORTED, + BAKE_ERROR_NO_LIGHTMAPPER }; - typedef void (*BakeBeginFunc)(int); - typedef bool (*BakeStepFunc)(int, const String &); - typedef void (*BakeEndFunc)(); + enum EnvironmentMode { + ENVIRONMENT_MODE_DISABLED, + ENVIRONMENT_MODE_SCENE, + ENVIRONMENT_MODE_CUSTOM_SKY, + ENVIRONMENT_MODE_CUSTOM_COLOR + }; -private: - float bake_cell_size; - float capture_cell_size; - Vector3 extents; - float bake_default_texels_per_unit; - float propagation; - float energy; - BakeQuality bake_quality; - BakeMode bake_mode; - bool hdr; - String image_path; + struct BakeStepUD { + Lightmapper::BakeStepFunc func; + void *ud; + float from_percent; + float to_percent; + }; - Ref light_data; + struct LightsFound { + Transform xform; + Light *light; + }; - struct PlotMesh { - Ref override_material; - Vector > instance_materials; + struct MeshesFound { + Transform xform; + NodePath node_path; + int32_t subindex; Ref mesh; - Transform local_xform; - NodePath path; - int instance_idx; + int32_t lightmap_scale; + Vector > overrides; + bool cast_shadows; + bool generate_lightmap; }; - struct PlotLight { - Light *light; - Transform local_xform; - }; +private: + Vector3 extents; + float default_texels_per_unit; + float bias; + BakeQuality bake_quality; + bool generate_atlas; + int max_atlas_size; + bool capture_enabled; + int bounces; + bool use_denoiser; + + EnvironmentMode environment_mode; + Ref environment_custom_sky; + Vector3 environment_custom_sky_rotation_degrees; + Color environment_custom_color; + float environment_custom_energy; + + BakeQuality capture_quality; + float capture_propagation; + float capture_cell_size; - void _find_meshes_and_lights(Node *p_at_node, List &plot_meshes, List &plot_lights); + String image_path; // (Deprecated property) - void _debug_bake(); + Ref light_data; void _assign_lightmaps(); void _clear_lightmaps(); - static bool _bake_time(void *ud, float p_secs, float p_progress); + void _get_material_images(const MeshesFound &p_found_mesh, Lightmapper::MeshData &r_mesh_data, Vector > &r_albedo_textures, Vector > &r_emission_textures); + Ref _get_irradiance_from_sky(Ref p_sky, Vector2i p_size); + Ref _get_irradiance_map(Ref p_env, Vector2i p_size); + void _find_meshes_and_lights(Node *p_at_node, Vector &meshes, Vector &lights); + Vector2i _compute_lightmap_size(const MeshesFound &p_mesh); - struct BakeTimeData { - String text; - int pass; - uint64_t last_step; - }; + static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh); protected: static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; void _notification(int p_what); public: - static BakeBeginFunc bake_begin_function; - static BakeStepFunc bake_step_function; - static BakeEndFunc bake_end_function; + static Lightmapper::BakeStepFunc bake_step_function; + static Lightmapper::BakeStepFunc bake_substep_function; void set_light_data(const Ref &p_data); Ref get_light_data() const; - void set_bake_cell_size(float p_cell_size); - float get_bake_cell_size() const; - void set_capture_cell_size(float p_cell_size); float get_capture_cell_size() const; void set_extents(const Vector3 &p_extents); Vector3 get_extents() const; - void set_bake_default_texels_per_unit(const float &p_bake_texels_per_unit); - float get_bake_default_texels_per_unit() const; + void set_default_texels_per_unit(const float &p_extents); + float get_default_texels_per_unit() const; - void set_propagation(float p_propagation); - float get_propagation() const; + void set_capture_propagation(float p_propagation); + float get_capture_propagation() const; - void set_energy(float p_energy); - float get_energy() const; + void set_capture_quality(BakeQuality p_quality); + BakeQuality get_capture_quality() const; void set_bake_quality(BakeQuality p_quality); BakeQuality get_bake_quality() const; - void set_bake_mode(BakeMode p_mode); - BakeMode get_bake_mode() const; + void set_generate_atlas(bool p_enabled); + bool is_generate_atlas_enabled() const; + + void set_max_atlas_size(int p_size); + int get_max_atlas_size() const; - void set_hdr(bool p_enable); - bool is_hdr() const; + void set_capture_enabled(bool p_enable); + bool get_capture_enabled() const; void set_image_path(const String &p_path); String get_image_path() const; + void set_environment_mode(EnvironmentMode p_mode); + EnvironmentMode get_environment_mode() const; + + void set_environment_custom_sky(const Ref &p_sky); + Ref get_environment_custom_sky() const; + + void set_environment_custom_sky_rotation_degrees(const Vector3 &p_rotation); + Vector3 get_environment_custom_sky_rotation_degrees() const; + + void set_environment_custom_color(const Color &p_color); + Color get_environment_custom_color() const; + + void set_environment_custom_energy(float p_energy); + float get_environment_custom_energy() const; + + void set_use_denoiser(bool p_enable); + bool is_using_denoiser() const; + + void set_bounces(int p_bounces); + int get_bounces() const; + + void set_bias(float p_bias); + float get_bias() const; + AABB get_aabb() const; PoolVector get_faces(uint32_t p_usage_flags) const; - BakeError bake(Node *p_from_node, bool p_create_visual_debug = false); + BakeError bake(Node *p_from_node, String p_data_save_path = ""); BakedLightmap(); }; VARIANT_ENUM_CAST(BakedLightmap::BakeQuality); -VARIANT_ENUM_CAST(BakedLightmap::BakeMode); VARIANT_ENUM_CAST(BakedLightmap::BakeError); +VARIANT_ENUM_CAST(BakedLightmap::EnvironmentMode); #endif // BAKED_INDIRECT_LIGHT_H diff --git a/scene/3d/lightmapper.cpp b/scene/3d/lightmapper.cpp new file mode 100644 index 000000000000..839186299b93 --- /dev/null +++ b/scene/3d/lightmapper.cpp @@ -0,0 +1,76 @@ +/*************************************************************************/ +/* lightmapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "lightmapper.h" + +LightmapDenoiser *(*LightmapDenoiser::create_function)() = nullptr; + +Ref LightmapDenoiser::create() { + if (create_function) { + return Ref(create_function()); + } + return Ref(); +} + +LightmapRaycaster *(*LightmapRaycaster::create_function)() = nullptr; + +Ref LightmapRaycaster::create() { + if (create_function) { + return Ref(create_function()); + } + return Ref(); +} + +Lightmapper::CreateFunc Lightmapper::create_custom = nullptr; +Lightmapper::CreateFunc Lightmapper::create_gpu = nullptr; +Lightmapper::CreateFunc Lightmapper::create_cpu = nullptr; + +Ref Lightmapper::create() { + Lightmapper *lm = nullptr; + if (create_custom) { + lm = create_custom(); + } + + if (!lm && create_gpu) { + lm = create_gpu(); + } + + if (!lm && create_cpu) { + lm = create_cpu(); + } + if (!lm) { + return Ref(); + } else { + return Ref(lm); + } +} + +Lightmapper::Lightmapper() { +} diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h new file mode 100644 index 000000000000..ccb08e4630d8 --- /dev/null +++ b/scene/3d/lightmapper.h @@ -0,0 +1,196 @@ +/*************************************************************************/ +/* lightmapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LIGHTMAPPER_H +#define LIGHTMAPPER_H + +#include "scene/resources/mesh.h" + +#if !defined(__aligned) + +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)) && !defined(__CYGWIN__) +#define __aligned(...) __declspec(align(__VA_ARGS__)) +#else +#define __aligned(...) __attribute__((aligned(__VA_ARGS__))) +#endif + +#endif + +class LightmapDenoiser : public Reference { + GDCLASS(LightmapDenoiser, Reference) +protected: + static LightmapDenoiser *(*create_function)(); + +public: + virtual Ref denoise_image(const Ref &p_image) = 0; + static Ref create(); +}; + +class LightmapRaycaster : public Reference { + GDCLASS(LightmapRaycaster, Reference) +protected: + static LightmapRaycaster *(*create_function)(); + +public: + // compatible with embree3 rays + struct __aligned(16) Ray { + const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h + + /*! Default construction does nothing. */ + _FORCE_INLINE_ Ray() : + geomID(INVALID_GEOMETRY_ID) {} + + /*! Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far. */ + _FORCE_INLINE_ Ray(const Vector3 &org, + const Vector3 &dir, + float tnear = 0.0f, + float tfar = INFINITY) : + org(org), + tnear(tnear), + dir(dir), + time(0.0f), + tfar(tfar), + mask(-1), + u(0.0), + v(0.0), + primID(INVALID_GEOMETRY_ID), + geomID(INVALID_GEOMETRY_ID), + instID(INVALID_GEOMETRY_ID) {} + + /*! Tests if we hit something. */ + _FORCE_INLINE_ explicit operator bool() const { return geomID != INVALID_GEOMETRY_ID; } + + public: + Vector3 org; //!< Ray origin + tnear + float tnear; //!< Start of ray segment + Vector3 dir; //!< Ray direction + tfar + float time; //!< Time of this ray for motion blur. + float tfar; //!< End of ray segment + unsigned int mask; //!< used to mask out objects during traversal + unsigned int id; //!< ray ID + unsigned int flags; //!< ray flags + + Vector3 normal; //!< Not normalized geometry normal + float u; //!< Barycentric u coordinate of hit + float v; //!< Barycentric v coordinate of hit + unsigned int primID; //!< primitive ID + unsigned int geomID; //!< geometry ID + unsigned int instID; //!< instance ID + }; + + virtual bool intersect(Ray &p_ray) = 0; + + virtual void intersect(Vector &r_rays) = 0; + + virtual void add_mesh(const Vector &p_vertices, const Vector &p_normals, const Vector &p_uv2s, unsigned int p_id) = 0; + virtual void set_mesh_alpha_texture(Ref p_alpha_texture, unsigned int p_id) = 0; + virtual void commit() = 0; + + virtual void set_mesh_filter(const Set &p_mesh_ids) = 0; + virtual void clear_mesh_filter() = 0; + + static Ref create(); +}; + +class Lightmapper : public Reference { + GDCLASS(Lightmapper, Reference) +public: + enum LightType { + LIGHT_TYPE_DIRECTIONAL, + LIGHT_TYPE_OMNI, + LIGHT_TYPE_SPOT + }; + + enum BakeError { + BAKE_ERROR_LIGHTMAP_TOO_SMALL, + BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, + BAKE_ERROR_NO_MESHES, + BAKE_ERROR_USER_ABORTED, + BAKE_ERROR_NO_RAYCASTER, + BAKE_OK + }; + + enum BakeQuality { + BAKE_QUALITY_LOW, + BAKE_QUALITY_MEDIUM, + BAKE_QUALITY_HIGH, + BAKE_QUALITY_ULTRA, + }; + + typedef Lightmapper *(*CreateFunc)(); + + static CreateFunc create_custom; + static CreateFunc create_gpu; + static CreateFunc create_cpu; + +protected: +public: + typedef bool (*BakeStepFunc)(float, const String &, void *, bool); //progress, step description, userdata, force refresh + + struct MeshData { + struct TextureDef { + RID tex_rid; + Color mul; + Color add; + }; + + //triangle data + Vector points; + Vector uv; + Vector uv2; + Vector normal; + Vector albedo; + Vector emission; + Vector surface_facecounts; + Variant userdata; + }; + + virtual void add_albedo_texture(Ref p_texture) = 0; + virtual void add_emission_texture(Ref p_texture) = 0; + virtual void add_mesh(const MeshData &p_mesh, Vector2i p_size) = 0; + virtual void add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier) = 0; + virtual void add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation) = 0; + virtual void add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation) = 0; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, bool p_generate_atlas, int p_max_texture_size, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, BakeStepFunc p_substep_function = nullptr) = 0; + + virtual int get_bake_texture_count() const = 0; + virtual Ref get_bake_texture(int p_index) const = 0; + virtual int get_bake_mesh_count() const = 0; + virtual Variant get_bake_mesh_userdata(int p_index) const = 0; + virtual Rect2 get_bake_mesh_uv_scale(int p_index) const = 0; + virtual int get_bake_mesh_texture_slice(int p_index) const = 0; + + static Ref create(); + + Lightmapper(); +}; + +#endif // LIGHTMAPPER_H diff --git a/scene/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index a66b18e09867..7bea31af214a 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -170,6 +170,23 @@ Ref GeometryInstance::get_material_override() const { return material_override; } +void GeometryInstance::set_generate_lightmap(bool p_enabled) { + generate_lightmap = p_enabled; +} + +bool GeometryInstance::get_generate_lightmap() { + return generate_lightmap; +} + +void GeometryInstance::set_lightmap_scale(LightmapScale p_scale) { + ERR_FAIL_INDEX(p_scale, LIGHTMAP_SCALE_MAX); + lightmap_scale = p_scale; +} + +GeometryInstance::LightmapScale GeometryInstance::get_lightmap_scale() const { + return lightmap_scale; +} + void GeometryInstance::set_lod_min_distance(float p_dist) { lod_min_distance = p_dist; @@ -274,6 +291,12 @@ void GeometryInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cast_shadows_setting", "shadow_casting_setting"), &GeometryInstance::set_cast_shadows_setting); ClassDB::bind_method(D_METHOD("get_cast_shadows_setting"), &GeometryInstance::get_cast_shadows_setting); + ClassDB::bind_method(D_METHOD("set_generate_lightmap", "enabled"), &GeometryInstance::set_generate_lightmap); + ClassDB::bind_method(D_METHOD("get_generate_lightmap"), &GeometryInstance::get_generate_lightmap); + + ClassDB::bind_method(D_METHOD("set_lightmap_scale", "scale"), &GeometryInstance::set_lightmap_scale); + ClassDB::bind_method(D_METHOD("get_lightmap_scale"), &GeometryInstance::get_lightmap_scale); + ClassDB::bind_method(D_METHOD("set_lod_max_hysteresis", "mode"), &GeometryInstance::set_lod_max_hysteresis); ClassDB::bind_method(D_METHOD("get_lod_max_hysteresis"), &GeometryInstance::get_lod_max_hysteresis); @@ -297,7 +320,11 @@ void GeometryInstance::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial"), "set_material_override", "get_material_override"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0.01"), "set_extra_cull_margin", "get_extra_cull_margin"); + + ADD_GROUP("Baked Light", ""); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_in_baked_light"), "set_flag", "get_flag", FLAG_USE_BAKED_LIGHT); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_lightmap"), "set_generate_lightmap", "get_generate_lightmap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "lightmap_scale", PROPERTY_HINT_ENUM, "1x,2x,4x,8x"), "set_lightmap_scale", "get_lightmap_scale"); ADD_GROUP("LOD", "lod_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_min_distance", PROPERTY_HINT_RANGE, "0,32768,0.01"), "set_lod_min_distance", "get_lod_min_distance"); @@ -307,6 +334,12 @@ void GeometryInstance::_bind_methods() { //ADD_SIGNAL( MethodInfo("visibility_changed")); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_1X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_2X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_4X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_8X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_MAX); + BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_OFF); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_ON); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_DOUBLE_SIDED); @@ -329,5 +362,7 @@ GeometryInstance::GeometryInstance() { shadow_casting_setting = SHADOW_CASTING_SETTING_ON; extra_cull_margin = 0; + generate_lightmap = true; + lightmap_scale = LightmapScale::LIGHTMAP_SCALE_1X; //VS::get_singleton()->instance_geometry_set_baked_light_texture_index(get_instance(),0); } diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h index ce67b4dd6a3a..6c12ce5b2a23 100644 --- a/scene/3d/visual_instance.h +++ b/scene/3d/visual_instance.h @@ -91,6 +91,14 @@ class GeometryInstance : public VisualInstance { FLAG_MAX = VS::INSTANCE_FLAG_MAX, }; + enum LightmapScale { + LIGHTMAP_SCALE_1X, + LIGHTMAP_SCALE_2X, + LIGHTMAP_SCALE_4X, + LIGHTMAP_SCALE_8X, + LIGHTMAP_SCALE_MAX, + }; + enum ShadowCastingSetting { SHADOW_CASTING_SETTING_OFF = VS::SHADOW_CASTING_SETTING_OFF, SHADOW_CASTING_SETTING_ON = VS::SHADOW_CASTING_SETTING_ON, @@ -100,6 +108,8 @@ class GeometryInstance : public VisualInstance { private: bool flags[FLAG_MAX]; + bool generate_lightmap; + LightmapScale lightmap_scale; ShadowCastingSetting shadow_casting_setting; Ref material_override; float lod_min_distance; @@ -120,6 +130,15 @@ class GeometryInstance : public VisualInstance { void set_cast_shadows_setting(ShadowCastingSetting p_shadow_casting_setting); ShadowCastingSetting get_cast_shadows_setting() const; + void set_bake_cast_shadows(bool p_enabled); + bool get_bake_cast_shadows(); + + void set_generate_lightmap(bool p_enabled); + bool get_generate_lightmap(); + + void set_lightmap_scale(LightmapScale p_scale); + LightmapScale get_lightmap_scale() const; + void set_lod_min_distance(float p_dist); float get_lod_min_distance() const; @@ -144,6 +163,7 @@ class GeometryInstance : public VisualInstance { }; VARIANT_ENUM_CAST(GeometryInstance::Flags); +VARIANT_ENUM_CAST(GeometryInstance::LightmapScale); VARIANT_ENUM_CAST(GeometryInstance::ShadowCastingSetting); #endif diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index 48d722ff2ad7..0af8cbf20c18 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -718,12 +718,10 @@ void VoxelLightBaker::_init_light_plot(int p_idx, int p_level, int p_x, int p_y, } } -void VoxelLightBaker::begin_bake_light(BakeQuality p_quality, BakeMode p_bake_mode, float p_propagation, float p_energy) { +void VoxelLightBaker::begin_bake_light(BakeQuality p_quality, float p_propagation) { _check_init_light(); propagation = p_propagation; bake_quality = p_quality; - bake_mode = p_bake_mode; - energy = p_energy; } void VoxelLightBaker::_check_init_light() { @@ -733,7 +731,6 @@ void VoxelLightBaker::_check_init_light() { leaf_voxel_count = 0; _fixup_plot(0, 0); //pre fixup, so normal, albedo, emission, etc. work for lighting. bake_light.resize(bake_cells.size()); - print_line("bake light size: " + itos(bake_light.size())); //zeromem(bake_light.ptrw(), bake_light.size() * sizeof(Light)); first_leaf = -1; _init_light_plot(0, 0, 0, 0, 0, CHILD_EMPTY); @@ -1289,872 +1286,6 @@ void VoxelLightBaker::_fixup_plot(int p_idx, int p_level) { } } -//make sure any cell (save for the root) has an empty cell previous to it, so it can be interpolated into - -void VoxelLightBaker::_plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height) { - - int x[3]; - int y[3]; - - for (int j = 0; j < 3; j++) { - - x[j] = vertices[j].x * width; - y[j] = vertices[j].y * height; - //x[j] = CLAMP(x[j], 0, bt.width - 1); - //y[j] = CLAMP(y[j], 0, bt.height - 1); - } - - // sort the points vertically - if (y[1] > y[2]) { - SWAP(x[1], x[2]); - SWAP(y[1], y[2]); - SWAP(positions[1], positions[2]); - SWAP(normals[1], normals[2]); - } - if (y[0] > y[1]) { - SWAP(x[0], x[1]); - SWAP(y[0], y[1]); - SWAP(positions[0], positions[1]); - SWAP(normals[0], normals[1]); - } - if (y[1] > y[2]) { - SWAP(x[1], x[2]); - SWAP(y[1], y[2]); - SWAP(positions[1], positions[2]); - SWAP(normals[1], normals[2]); - } - - double dx_far = double(x[2] - x[0]) / (y[2] - y[0] + 1); - double dx_upper = double(x[1] - x[0]) / (y[1] - y[0] + 1); - double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1); - double xf = x[0]; - double xt = x[0] + dx_upper; // if y[0] == y[1], special case - for (int yi = y[0]; yi <= (y[2] > height - 1 ? height - 1 : y[2]); yi++) { - if (yi >= 0) { - for (int xi = (xf > 0 ? int(xf) : 0); xi <= (xt < width ? xt : width - 1); xi++) { - //pixels[int(x + y * width)] = color; - - Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]); - Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]); - //vertices[2] - vertices[0]; - Vector2 v2 = Vector2(xi - x[0], yi - y[0]); - float d00 = v0.dot(v0); - float d01 = v0.dot(v1); - float d11 = v1.dot(v1); - float d20 = v2.dot(v0); - float d21 = v2.dot(v1); - float denom = (d00 * d11 - d01 * d01); - Vector3 pos; - Vector3 normal; - if (denom == 0) { - pos = positions[0]; - normal = normals[0]; - } else { - float v = (d11 * d20 - d01 * d21) / denom; - float w = (d00 * d21 - d01 * d20) / denom; - float u = 1.0f - v - w; - pos = positions[0] * u + positions[1] * v + positions[2] * w; - normal = normals[0] * u + normals[1] * v + normals[2] * w; - } - - int ofs = yi * width + xi; - pixels[ofs].normal = normal; - pixels[ofs].pos = pos; - } - - for (int xi = (xf < width ? int(xf) : width - 1); xi >= (xt > 0 ? xt : 0); xi--) { - //pixels[int(x + y * width)] = color; - Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]); - Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]); - //vertices[2] - vertices[0]; - Vector2 v2 = Vector2(xi - x[0], yi - y[0]); - float d00 = v0.dot(v0); - float d01 = v0.dot(v1); - float d11 = v1.dot(v1); - float d20 = v2.dot(v0); - float d21 = v2.dot(v1); - float denom = (d00 * d11 - d01 * d01); - Vector3 pos; - Vector3 normal; - if (denom == 0) { - pos = positions[0]; - normal = normals[0]; - } else { - float v = (d11 * d20 - d01 * d21) / denom; - float w = (d00 * d21 - d01 * d20) / denom; - float u = 1.0f - v - w; - pos = positions[0] * u + positions[1] * v + positions[2] * w; - normal = normals[0] * u + normals[1] * v + normals[2] * w; - } - - int ofs = yi * width + xi; - pixels[ofs].normal = normal; - pixels[ofs].pos = pos; - } - } - xf += dx_far; - if (yi < y[1]) - xt += dx_upper; - else - xt += dx_low; - } -} - -void VoxelLightBaker::_sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha) { - - int size = 1 << (cell_subdiv - 1); - - int clamp_v = size - 1; - //first of all, clamp - Vector3 pos; - pos.x = CLAMP(p_posf.x, 0, clamp_v); - pos.y = CLAMP(p_posf.y, 0, clamp_v); - pos.z = CLAMP(p_posf.z, 0, clamp_v); - - float level = (cell_subdiv - 1) - p_level; - - int target_level; - float level_filter; - if (level <= 0.0) { - level_filter = 0; - target_level = 0; - } else { - target_level = Math::ceil(level); - level_filter = target_level - level; - } - - const Cell *cells = bake_cells.ptr(); - const Light *light = bake_light.ptr(); - - Vector3 color[2][8]; - float alpha[2][8]; - zeromem(alpha, sizeof(float) * 2 * 8); - - //find cell at given level first - - for (int c = 0; c < 2; c++) { - - int current_level = MAX(0, target_level - c); - int level_cell_size = (1 << (cell_subdiv - 1)) >> current_level; - - for (int n = 0; n < 8; n++) { - - int x = int(pos.x); - int y = int(pos.y); - int z = int(pos.z); - - if (n & 1) - x += level_cell_size; - if (n & 2) - y += level_cell_size; - if (n & 4) - z += level_cell_size; - - int ofs_x = 0; - int ofs_y = 0; - int ofs_z = 0; - - x = CLAMP(x, 0, clamp_v); - y = CLAMP(y, 0, clamp_v); - z = CLAMP(z, 0, clamp_v); - - int half = size / 2; - uint32_t cell = 0; - for (int i = 0; i < current_level; i++) { - - const Cell *bc = &cells[cell]; - - int child = 0; - if (x >= ofs_x + half) { - child |= 1; - ofs_x += half; - } - if (y >= ofs_y + half) { - child |= 2; - ofs_y += half; - } - if (z >= ofs_z + half) { - child |= 4; - ofs_z += half; - } - - cell = bc->children[child]; - if (cell == CHILD_EMPTY) - break; - - half >>= 1; - } - - if (cell == CHILD_EMPTY) { - alpha[c][n] = 0; - } else { - alpha[c][n] = cells[cell].alpha; - - for (int i = 0; i < 6; i++) { - //anisotropic read light - float amount = p_direction.dot(aniso_normal[i]); - if (amount < 0) - amount = 0; - color[c][n].x += light[cell].accum[i][0] * amount; - color[c][n].y += light[cell].accum[i][1] * amount; - color[c][n].z += light[cell].accum[i][2] * amount; - } - - color[c][n].x += cells[cell].emission[0]; - color[c][n].y += cells[cell].emission[1]; - color[c][n].z += cells[cell].emission[2]; - } - } - } - - float target_level_size = size >> target_level; - Vector3 pos_fract[2]; - - pos_fract[0].x = Math::fmod(pos.x, target_level_size) / target_level_size; - pos_fract[0].y = Math::fmod(pos.y, target_level_size) / target_level_size; - pos_fract[0].z = Math::fmod(pos.z, target_level_size) / target_level_size; - - target_level_size = size >> MAX(0, target_level - 1); - - pos_fract[1].x = Math::fmod(pos.x, target_level_size) / target_level_size; - pos_fract[1].y = Math::fmod(pos.y, target_level_size) / target_level_size; - pos_fract[1].z = Math::fmod(pos.z, target_level_size) / target_level_size; - - float alpha_interp[2]; - Vector3 color_interp[2]; - - for (int i = 0; i < 2; i++) { - - Vector3 color_x00 = color[i][0].linear_interpolate(color[i][1], pos_fract[i].x); - Vector3 color_xy0 = color[i][2].linear_interpolate(color[i][3], pos_fract[i].x); - Vector3 blend_z0 = color_x00.linear_interpolate(color_xy0, pos_fract[i].y); - - Vector3 color_x0z = color[i][4].linear_interpolate(color[i][5], pos_fract[i].x); - Vector3 color_xyz = color[i][6].linear_interpolate(color[i][7], pos_fract[i].x); - Vector3 blend_z1 = color_x0z.linear_interpolate(color_xyz, pos_fract[i].y); - - color_interp[i] = blend_z0.linear_interpolate(blend_z1, pos_fract[i].z); - - float alpha_x00 = Math::lerp(alpha[i][0], alpha[i][1], pos_fract[i].x); - float alpha_xy0 = Math::lerp(alpha[i][2], alpha[i][3], pos_fract[i].x); - float alpha_z0 = Math::lerp(alpha_x00, alpha_xy0, pos_fract[i].y); - - float alpha_x0z = Math::lerp(alpha[i][4], alpha[i][5], pos_fract[i].x); - float alpha_xyz = Math::lerp(alpha[i][6], alpha[i][7], pos_fract[i].x); - float alpha_z1 = Math::lerp(alpha_x0z, alpha_xyz, pos_fract[i].y); - - alpha_interp[i] = Math::lerp(alpha_z0, alpha_z1, pos_fract[i].z); - } - - r_color = color_interp[0].linear_interpolate(color_interp[1], level_filter); - r_alpha = Math::lerp(alpha_interp[0], alpha_interp[1], level_filter); -} - -Vector3 VoxelLightBaker::_voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture) { - - float bias = 2.5; - float max_distance = (Vector3(1, 1, 1) * (1 << (cell_subdiv - 1))).length(); - - float dist = bias; - float alpha = 0.0; - Vector3 color; - - Vector3 scolor; - float salpha; - - while (dist < max_distance && alpha < 0.95) { - float diameter = MAX(1.0, 2.0 * p_aperture * dist); - _sample_baked_octree_filtered_and_anisotropic(p_pos + dist * p_normal, p_normal, log2(diameter), scolor, salpha); - float a = (1.0 - alpha); - color += scolor * a; - alpha += a * salpha; - dist += diameter * 0.5; - } - - /*if (blend_ambient) { - color.rgb = mix(ambient,color.rgb,min(1.0,alpha/0.95)); - }*/ - - return color; -} - -Vector3 VoxelLightBaker::_compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) { - - //find arbitrary tangent and bitangent, then build a matrix - Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0); - Vector3 tangent = v0.cross(p_normal).normalized(); - Vector3 bitangent = tangent.cross(p_normal).normalized(); - Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed(); - - const Vector3 *cone_dirs = NULL; - const float *cone_weights = NULL; - int cone_dir_count = 0; - float cone_aperture = 0; - - switch (bake_quality) { - case BAKE_QUALITY_LOW: { - //default quality - static const Vector3 dirs[4] = { - Vector3(Math_SQRT12, 0, Math_SQRT12), - Vector3(0, Math_SQRT12, Math_SQRT12), - Vector3(-Math_SQRT12, 0, Math_SQRT12), - Vector3(0, -Math_SQRT12, Math_SQRT12) - }; - - static const float weights[4] = { 0.25, 0.25, 0.25, 0.25 }; - - cone_dirs = dirs; - cone_dir_count = 4; - cone_aperture = 1.0; // tan(angle) 90 degrees - cone_weights = weights; - } break; - case BAKE_QUALITY_MEDIUM: { - //default quality - static const Vector3 dirs[6] = { - Vector3(0, 0, 1), - Vector3(0.866025, 0, 0.5), - Vector3(0.267617, 0.823639, 0.5), - Vector3(-0.700629, 0.509037, 0.5), - Vector3(-0.700629, -0.509037, 0.5), - Vector3(0.267617, -0.823639, 0.5) - }; - static const float weights[6] = { 0.25f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f }; - // - cone_dirs = dirs; - cone_dir_count = 6; - cone_aperture = 0.577; // tan(angle) 60 degrees - cone_weights = weights; - } break; - case BAKE_QUALITY_HIGH: { - - //high qualily - static const Vector3 dirs[10] = { - Vector3(0.8781648411741658, 0.0, 0.478358141694643), - Vector3(0.5369754325592234, 0.6794204427701518, 0.5000452447267606), - Vector3(-0.19849436573466497, 0.8429904390140635, 0.49996710542041645), - Vector3(-0.7856196499811189, 0.3639120321329737, 0.5003696617825604), - Vector3(-0.7856196499811189, -0.3639120321329737, 0.5003696617825604), - Vector3(-0.19849436573466497, -0.8429904390140635, 0.49996710542041645), - Vector3(0.5369754325592234, -0.6794204427701518, 0.5000452447267606), - Vector3(-0.4451656858129485, 0.0, 0.8954482185892644), - Vector3(0.19124006749743122, 0.39355745585016605, 0.8991883926788214), - Vector3(0.19124006749743122, -0.39355745585016605, 0.8991883926788214), - }; - static const float weights[10] = { 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.133333f, 0.133333f, 0.13333f }; - cone_dirs = dirs; - cone_dir_count = 10; - cone_aperture = 0.404; // tan(angle) 45 degrees - cone_weights = weights; - } break; - } - - Vector3 accum; - - for (int i = 0; i < cone_dir_count; i++) { - Vector3 dir = normal_xform.xform(cone_dirs[i]).normalized(); //normal may not completely correct when transformed to cell - accum += _voxel_cone_trace(p_pos, dir, cone_aperture) * cone_weights[i]; - } - - return accum; -} - -_ALWAYS_INLINE_ uint32_t xorshift32(uint32_t *state) { - /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ - uint32_t x = *state; - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - *state = x; - return x; -} - -Vector3 VoxelLightBaker::_compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) { - - int samples_per_quality[3] = { 48, 128, 512 }; - - int samples = samples_per_quality[bake_quality]; - - //create a basis in Z - Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0); - Vector3 tangent = v0.cross(p_normal).normalized(); - Vector3 bitangent = tangent.cross(p_normal).normalized(); - Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed(); - - float bias = 1.5; - int max_level = cell_subdiv - 1; - int size = 1 << max_level; - - Vector3 accum; - float spread = Math::deg2rad(80.0); - - const Light *light = bake_light.ptr(); - const Cell *cells = bake_cells.ptr(); - - uint32_t local_rng_state = rand(); //needs to be fixed again - - for (int i = 0; i < samples; i++) { - - float random_angle1 = (((xorshift32(&local_rng_state) % 65535) / 65535.0) * 2.0 - 1.0) * spread; - Vector3 axis(0, sin(random_angle1), cos(random_angle1)); - float random_angle2 = ((xorshift32(&local_rng_state) % 65535) / 65535.0) * Math_PI * 2.0; - Basis rot(Vector3(0, 0, 1), random_angle2); - axis = rot.xform(axis); - - Vector3 direction = normal_xform.xform(axis).normalized(); - - Vector3 advance = direction * _get_normal_advance(direction); - - Vector3 pos = p_pos /*+ Vector3(0.5, 0.5, 0.5)*/ + advance * bias; - - uint32_t cell = CHILD_EMPTY; - - while (cell == CHILD_EMPTY) { - - int x = int(pos.x); - int y = int(pos.y); - int z = int(pos.z); - - int ofs_x = 0; - int ofs_y = 0; - int ofs_z = 0; - int half = size / 2; - - if (x < 0 || x >= size) - break; - if (y < 0 || y >= size) - break; - if (z < 0 || z >= size) - break; - - //int level_limit = max_level; - - cell = 0; //start from root - for (int j = 0; j < max_level; j++) { - - const Cell *bc = &cells[cell]; - - int child = 0; - if (x >= ofs_x + half) { - child |= 1; - ofs_x += half; - } - if (y >= ofs_y + half) { - child |= 2; - ofs_y += half; - } - if (z >= ofs_z + half) { - child |= 4; - ofs_z += half; - } - - cell = bc->children[child]; - if (unlikely(cell == CHILD_EMPTY)) - break; - - half >>= 1; - } - - pos += advance; - } - - if (unlikely(cell != CHILD_EMPTY)) { - for (int j = 0; j < 6; j++) { - //anisotropic read light - float amount = direction.dot(aniso_normal[j]); - if (amount <= 0) - continue; - accum.x += light[cell].accum[j][0] * amount; - accum.y += light[cell].accum[j][1] * amount; - accum.z += light[cell].accum[j][2] * amount; - } - accum.x += cells[cell].emission[0]; - accum.y += cells[cell].emission[1]; - accum.z += cells[cell].emission[2]; - } - } - - // Make sure we don't reset this thread's RNG state - - return accum / samples; -} - -void VoxelLightBaker::_lightmap_bake_point(uint32_t p_x, LightMap *p_line) { - - LightMap *pixel = &p_line[p_x]; - if (pixel->pos == Vector3()) - return; - switch (bake_mode) { - case BAKE_MODE_CONE_TRACE: { - pixel->light = _compute_pixel_light_at_pos(pixel->pos, pixel->normal) * energy; - } break; - case BAKE_MODE_RAY_TRACE: { - pixel->light = _compute_ray_trace_at_pos(pixel->pos, pixel->normal) * energy; - } break; - } -} - -Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float), void *p_bake_time_ud) { - - //transfer light information to a lightmap - Ref mesh = p_mesh; - - //step 1 - create lightmap - int width; - int height; - Vector lightmap; - Transform xform = to_cell_space * p_xform; - if (mesh->get_lightmap_size_hint() == Size2()) { - double area = 0; - double uv_area = 0; - for (int i = 0; i < mesh->get_surface_count(); i++) { - Array arrays = mesh->surface_get_arrays(i); - PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; - PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; - PoolVector indices = arrays[Mesh::ARRAY_INDEX]; - - ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER); - - int vc = vertices.size(); - PoolVector::Read vr = vertices.read(); - PoolVector::Read u2r = uv2.read(); - PoolVector::Read ir; - int ic = 0; - - if (indices.size()) { - ic = indices.size(); - ir = indices.read(); - } - - int faces = ic ? ic / 3 : vc / 3; - for (int j = 0; j < faces; j++) { - Vector3 vertex[3]; - Vector2 uv[3]; - - for (int k = 0; k < 3; k++) { - int idx = ic ? ir[j * 3 + k] : j * 3 + k; - vertex[k] = xform.xform(vr[idx]); - uv[k] = u2r[idx]; - } - - Vector3 p1 = vertex[0]; - Vector3 p2 = vertex[1]; - Vector3 p3 = vertex[2]; - double a = p1.distance_to(p2); - double b = p2.distance_to(p3); - double c = p3.distance_to(p1); - double halfPerimeter = (a + b + c) / 2.0; - area += sqrt(halfPerimeter * (halfPerimeter - a) * (halfPerimeter - b) * (halfPerimeter - c)); - - Vector2 uv_p1 = uv[0]; - Vector2 uv_p2 = uv[1]; - Vector2 uv_p3 = uv[2]; - double uv_a = uv_p1.distance_to(uv_p2); - double uv_b = uv_p2.distance_to(uv_p3); - double uv_c = uv_p3.distance_to(uv_p1); - double uv_halfPerimeter = (uv_a + uv_b + uv_c) / 2.0; - uv_area += sqrt(uv_halfPerimeter * (uv_halfPerimeter - uv_a) * (uv_halfPerimeter - uv_b) * (uv_halfPerimeter - uv_c)); - } - } - - if (uv_area < 0.0001f) { - uv_area = 1.0; - } - - int pixels = (ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit))); - width = height = CLAMP(pixels, 2, 4096); - } else { - width = mesh->get_lightmap_size_hint().x; - height = mesh->get_lightmap_size_hint().y; - } - - lightmap.resize(width * height); - - //step 2 plot faces to lightmap - for (int i = 0; i < mesh->get_surface_count(); i++) { - Array arrays = mesh->surface_get_arrays(i); - PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; - PoolVector normals = arrays[Mesh::ARRAY_NORMAL]; - PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; - PoolVector indices = arrays[Mesh::ARRAY_INDEX]; - - ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(normals.size() == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER); - - int vc = vertices.size(); - PoolVector::Read vr = vertices.read(); - PoolVector::Read nr = normals.read(); - PoolVector::Read u2r = uv2.read(); - PoolVector::Read ir; - int ic = 0; - - if (indices.size()) { - ic = indices.size(); - ir = indices.read(); - } - - int faces = ic ? ic / 3 : vc / 3; - for (int j = 0; j < faces; j++) { - Vector3 vertex[3]; - Vector3 normal[3]; - Vector2 uv[3]; - - for (int k = 0; k < 3; k++) { - int idx = ic ? ir[j * 3 + k] : j * 3 + k; - vertex[k] = xform.xform(vr[idx]); - normal[k] = xform.basis.xform(nr[idx]).normalized(); - uv[k] = u2r[idx]; - } - - _plot_triangle(uv, vertex, normal, lightmap.ptrw(), width, height); - } - } - - //step 3 perform voxel cone trace on lightmap pixels - { - LightMap *lightmap_ptr = lightmap.ptrw(); - uint64_t begin_time = OS::get_singleton()->get_ticks_usec(); - volatile int lines = 0; - - // make sure our OS-level rng is seeded - - for (int i = 0; i < height; i++) { - - thread_process_array(width, this, &VoxelLightBaker::_lightmap_bake_point, &lightmap_ptr[i * width]); - - lines = MAX(lines, i); //for multithread - if (p_bake_time_func) { - uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - begin_time; - float elapsed_sec = double(elapsed) / 1000000.0; - float remaining = lines < 1 ? 0 : (elapsed_sec / lines) * (height - lines - 1); - if (p_bake_time_func(p_bake_time_ud, remaining, lines / float(height))) { - return ERR_SKIP; - } - } - } - - if (bake_mode == BAKE_MODE_RAY_TRACE) { - //blur - //gauss kernel, 7 step sigma 2 - static const float gauss_kernel[4] = { 0.214607f, 0.189879f, 0.131514f, 0.071303f }; - //horizontal pass - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (lightmap_ptr[i * width + j].normal == Vector3()) { - continue; //empty - } - float gauss_sum = gauss_kernel[0]; - Vector3 accum = lightmap_ptr[i * width + j].light * gauss_kernel[0]; - for (int k = 1; k < 4; k++) { - int new_x = j + k; - if (new_x >= width || lightmap_ptr[i * width + new_x].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k]; - } - for (int k = 1; k < 4; k++) { - int new_x = j - k; - if (new_x < 0 || lightmap_ptr[i * width + new_x].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k]; - } - - lightmap_ptr[i * width + j].pos = accum /= gauss_sum; - } - } - //vertical pass - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (lightmap_ptr[i * width + j].normal == Vector3()) - continue; //empty, don't write over it anyway - float gauss_sum = gauss_kernel[0]; - Vector3 accum = lightmap_ptr[i * width + j].pos * gauss_kernel[0]; - for (int k = 1; k < 4; k++) { - int new_y = i + k; - if (new_y >= height || lightmap_ptr[new_y * width + j].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k]; - } - for (int k = 1; k < 4; k++) { - int new_y = i - k; - if (new_y < 0 || lightmap_ptr[new_y * width + j].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k]; - } - - lightmap_ptr[i * width + j].light = accum /= gauss_sum; - } - } - } - - //add directional light (do this after blur) - { - const Cell *cells = bake_cells.ptr(); - const Light *light = bake_light.ptr(); -#ifdef _OPENMP -#pragma omp parallel -#endif - for (int i = 0; i < height; i++) { -#ifdef _OPENMP -#pragma omp parallel for schedule(dynamic, 1) -#endif - for (int j = 0; j < width; j++) { - - //if (i == 125 && j == 280) { - - LightMap *pixel = &lightmap_ptr[i * width + j]; - if (pixel->pos == Vector3()) - continue; //unused, skipe - - int x = int(pixel->pos.x) - 1; - int y = int(pixel->pos.y) - 1; - int z = int(pixel->pos.z) - 1; - Color accum; - int size = 1 << (cell_subdiv - 1); - - int found = 0; - - for (int k = 0; k < 8; k++) { - - int ofs_x = x; - int ofs_y = y; - int ofs_z = z; - - if (k & 1) - ofs_x++; - if (k & 2) - ofs_y++; - if (k & 4) - ofs_z++; - - if (x < 0 || x >= size) - continue; - if (y < 0 || y >= size) - continue; - if (z < 0 || z >= size) - continue; - - uint32_t cell = _find_cell_at_pos(cells, ofs_x, ofs_y, ofs_z); - - if (cell == CHILD_EMPTY) - continue; - for (int l = 0; l < 6; l++) { - float s = pixel->normal.dot(aniso_normal[l]); - if (s < 0) - s = 0; - accum.r += light[cell].direct_accum[l][0] * s; - accum.g += light[cell].direct_accum[l][1] * s; - accum.b += light[cell].direct_accum[l][2] * s; - } - found++; - } - if (found) { - accum /= found; - pixel->light.x += accum.r; - pixel->light.y += accum.g; - pixel->light.z += accum.b; - } - } - } - } - - { - //fill gaps with neighbour vertices to avoid filter fades to black on edges - - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (lightmap_ptr[i * width + j].normal != Vector3()) { - continue; //filled, skip - } - - //this can't be made separatable.. - - int closest_i = -1, closest_j = 1; - float closest_dist = 1e20; - - const int margin = 3; - for (int y = i - margin; y <= i + margin; y++) { - for (int x = j - margin; x <= j + margin; x++) { - - if (x == j && y == i) - continue; - if (x < 0 || x >= width) - continue; - if (y < 0 || y >= height) - continue; - if (lightmap_ptr[y * width + x].normal == Vector3()) - continue; //also ensures that blitted stuff is not reused - - float dist = Vector2(i - y, j - x).length(); - if (dist > closest_dist) - continue; - - closest_dist = dist; - closest_i = y; - closest_j = x; - } - } - - if (closest_i != -1) { - lightmap_ptr[i * width + j].light = lightmap_ptr[closest_i * width + closest_j].light; - } - } - } - } - - { - //fill the lightmap data - r_lightmap.width = width; - r_lightmap.height = height; - r_lightmap.light.resize(lightmap.size() * 3); - PoolVector::Write w = r_lightmap.light.write(); - for (int i = 0; i < lightmap.size(); i++) { - w[i * 3 + 0] = lightmap[i].light.x; - w[i * 3 + 1] = lightmap[i].light.y; - w[i * 3 + 2] = lightmap[i].light.z; - } - } - -#if 0 // Enable for debugging. - { - PoolVector img; - int ls = lightmap.size(); - img.resize(ls * 3); - { - PoolVector::Write w = img.write(); - for (int i = 0; i < ls; i++) { - w[i * 3 + 0] = CLAMP(lightmap_ptr[i].light.x * 255, 0, 255); - w[i * 3 + 1] = CLAMP(lightmap_ptr[i].light.y * 255, 0, 255); - w[i * 3 + 2] = CLAMP(lightmap_ptr[i].light.z * 255, 0, 255); - //w[i * 3 + 0] = CLAMP(lightmap_ptr[i].normal.x * 255, 0, 255); - //w[i * 3 + 1] = CLAMP(lightmap_ptr[i].normal.y * 255, 0, 255); - //w[i * 3 + 2] = CLAMP(lightmap_ptr[i].normal.z * 255, 0, 255); - //w[i * 3 + 0] = CLAMP(lightmap_ptr[i].pos.x / (1 << (cell_subdiv - 1)) * 255, 0, 255); - //w[i * 3 + 1] = CLAMP(lightmap_ptr[i].pos.y / (1 << (cell_subdiv - 1)) * 255, 0, 255); - //w[i * 3 + 2] = CLAMP(lightmap_ptr[i].pos.z / (1 << (cell_subdiv - 1)) * 255, 0, 255); - } - } - - Ref image; - image.instance(); - image->create(width, height, false, Image::FORMAT_RGB8, img); - - String name = p_mesh->get_name(); - if (name == "") { - name = "Mesh" + itos(p_mesh->get_instance_id()); - } - image->save_png(name + ".png"); - } -#endif - } - - return OK; -} - void VoxelLightBaker::begin_bake(int p_subdiv, const AABB &p_bounds) { original_bounds = p_bounds; @@ -2482,5 +1613,4 @@ VoxelLightBaker::VoxelLightBaker() { color_scan_cell_width = 4; bake_texture_size = 128; propagation = 0.85; - energy = 1.0; } diff --git a/scene/3d/voxel_light_baker.h b/scene/3d/voxel_light_baker.h index 135fae2ac819..9f7258caeb31 100644 --- a/scene/3d/voxel_light_baker.h +++ b/scene/3d/voxel_light_baker.h @@ -128,10 +128,8 @@ class VoxelLightBaker { int bake_texture_size; float cell_size; float propagation; - float energy; BakeQuality bake_quality; - BakeMode bake_mode; int max_original_cells; @@ -147,25 +145,10 @@ class VoxelLightBaker { uint32_t _find_cell_at_pos(const Cell *cells, int x, int y, int z); - struct LightMap { - Vector3 light; - Vector3 pos; - Vector3 normal; - }; - - void _plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height); - - _FORCE_INLINE_ void _sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha); - _FORCE_INLINE_ Vector3 _voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture); - _FORCE_INLINE_ Vector3 _compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal); - _FORCE_INLINE_ Vector3 _compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal); - - void _lightmap_bake_point(uint32_t p_x, LightMap *p_line); - public: void begin_bake(int p_subdiv, const AABB &p_bounds); void plot_mesh(const Transform &p_xform, Ref &p_mesh, const Vector > &p_materials, const Ref &p_override_material); - void begin_bake_light(BakeQuality p_quality = BAKE_QUALITY_MEDIUM, BakeMode p_bake_mode = BAKE_MODE_CONE_TRACE, float p_propagation = 0.85, float p_energy = 1); + void begin_bake_light(BakeQuality p_quality = BAKE_QUALITY_MEDIUM, float p_propagation = 0.85); void plot_light_directional(const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, bool p_direct); void plot_light_omni(const Vector3 &p_pos, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, bool p_direct); void plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axis, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, float p_spot_angle, float p_spot_attenuation, bool p_direct); @@ -177,8 +160,6 @@ class VoxelLightBaker { PoolVector light; }; - Error make_lightmap(const Transform &p_xform, Ref &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float) = NULL, void *p_bake_time_ud = NULL); - PoolVector create_gi_probe_data(); Ref create_debug_multimesh(DebugMode p_mode = DEBUG_ALBEDO); PoolVector create_capture_octree(int p_subdiv); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index fb0595e58d26..57df051a9f61 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -30,11 +30,14 @@ #include "mesh.h" +#include "core/local_vector.h" #include "core/pair.h" #include "scene/resources/concave_polygon_shape.h" #include "scene/resources/convex_polygon_shape.h" #include "surface_tool.h" +#include "core/crypto/crypto_core.h" + #include Mesh::ConvexDecompositionFunc Mesh::convex_composition_function = NULL; @@ -1108,18 +1111,35 @@ struct ArrayMeshLightmapSurface { }; Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texel_size) { + int *cache_data = nullptr; + unsigned int cache_size = 0; + bool use_cache = false; // Don't use cache + return lightmap_unwrap_cached(cache_data, cache_size, use_cache, p_base_transform, p_texel_size); +} + +Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size) { ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); - Vector vertices; - Vector normals; - Vector indices; - Vector face_materials; - Vector uv; - Vector > uv_index; + LocalVector vertices; + LocalVector normals; + LocalVector indices; + LocalVector face_materials; + LocalVector uv; + LocalVector > uv_indices; + + Vector lightmap_surfaces; + + // Keep only the scale + Basis basis = p_base_transform.get_basis(); + Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); + + Transform transform; + transform.scale(scale); + + Basis normal_basis = transform.basis.inverse().transposed(); - Vector surfaces; for (int i = 0; i < get_surface_count(); i++) { ArrayMeshLightmapSurface s; s.primitive = surface_get_primitive_type(i); @@ -1143,30 +1163,36 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe vertices.resize((vertex_ofs + vc) * 3); normals.resize((vertex_ofs + vc) * 3); - uv_index.resize(vertex_ofs + vc); + uv_indices.resize(vertex_ofs + vc); for (int j = 0; j < vc; j++) { - Vector3 v = p_base_transform.xform(r[j]); - Vector3 n = p_base_transform.basis.xform(rn[j]).normalized(); + Vector3 v = transform.xform(r[j]); + Vector3 n = normal_basis.xform(rn[j]).normalized(); - vertices.write[(j + vertex_ofs) * 3 + 0] = v.x; - vertices.write[(j + vertex_ofs) * 3 + 1] = v.y; - vertices.write[(j + vertex_ofs) * 3 + 2] = v.z; - normals.write[(j + vertex_ofs) * 3 + 0] = n.x; - normals.write[(j + vertex_ofs) * 3 + 1] = n.y; - normals.write[(j + vertex_ofs) * 3 + 2] = n.z; - uv_index.write[j + vertex_ofs] = Pair(i, j); + vertices[(j + vertex_ofs) * 3 + 0] = v.x; + vertices[(j + vertex_ofs) * 3 + 1] = v.y; + vertices[(j + vertex_ofs) * 3 + 2] = v.z; + normals[(j + vertex_ofs) * 3 + 0] = n.x; + normals[(j + vertex_ofs) * 3 + 1] = n.y; + normals[(j + vertex_ofs) * 3 + 2] = n.z; + uv_indices[j + vertex_ofs] = Pair(i, j); } PoolVector rindices = arrays[Mesh::ARRAY_INDEX]; int ic = rindices.size(); + float eps = 1.19209290e-7F; // Taken from xatlas.h if (ic == 0) { for (int j = 0; j < vc / 3; j++) { - if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate()) + Vector3 p0 = transform.xform(r[j * 3 + 0]); + Vector3 p1 = transform.xform(r[j * 3 + 1]); + Vector3 p2 = transform.xform(r[j * 3 + 2]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { continue; + } indices.push_back(vertex_ofs + j * 3 + 0); indices.push_back(vertex_ofs + j * 3 + 1); @@ -1178,8 +1204,14 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe PoolVector::Read ri = rindices.read(); for (int j = 0; j < ic / 3; j++) { - if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate()) + Vector3 p0 = transform.xform(r[ri[j * 3 + 0]]); + Vector3 p1 = transform.xform(r[ri[j * 3 + 1]]); + Vector3 p2 = transform.xform(r[ri[j * 3 + 2]]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { continue; + } + indices.push_back(vertex_ofs + ri[j * 3 + 0]); indices.push_back(vertex_ofs + ri[j * 3 + 1]); indices.push_back(vertex_ofs + ri[j * 3 + 2]); @@ -1187,7 +1219,49 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe } } - surfaces.push_back(s); + lightmap_surfaces.push_back(s); + } + + CryptoCore::MD5Context ctx; + ctx.start(); + + ctx.update((unsigned char *)&p_texel_size, sizeof(float)); + ctx.update((unsigned char *)indices.ptr(), sizeof(int) * indices.size()); + ctx.update((unsigned char *)face_materials.ptr(), sizeof(int) * face_materials.size()); + ctx.update((unsigned char *)vertices.ptr(), sizeof(float) * vertices.size()); + ctx.update((unsigned char *)normals.ptr(), sizeof(float) * normals.size()); + + unsigned char hash[16]; + ctx.finish(hash); + + bool cached = false; + unsigned int cache_idx = 0; + + if (r_used_cache && r_cache_data) { + //Check if hash is in cache data + + int *cache_data = r_cache_data; + int n_entries = cache_data[0]; + unsigned int r_idx = 1; + for (int i = 0; i < n_entries; ++i) { + if (memcmp(&cache_data[r_idx], hash, 16) == 0) { + cached = true; + cache_idx = r_idx; + break; + } + + r_idx += 4; // hash + r_idx += 2; // size hint + + int vertex_count = cache_data[r_idx]; + r_idx += 1; // vertex count + r_idx += vertex_count; // vertex + r_idx += vertex_count * 2; // uvs + + int index_count = cache_data[r_idx]; + r_idx += 1; // index count + r_idx += index_count; // indices + } } //unwrap @@ -1200,10 +1274,86 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe int size_x; int size_y; - bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), face_materials.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); + if (r_used_cache && cached) { + int *cache_data = r_cache_data; + + // Return cache data pointer to the caller + r_cache_data = &cache_data[cache_idx]; + + cache_idx += 4; - if (!ok) { - return ERR_CANT_CREATE; + // Load size + size_x = ((int *)cache_data)[cache_idx]; + size_y = ((int *)cache_data)[cache_idx + 1]; + cache_idx += 2; + + // Load vertices + gen_vertex_count = cache_data[cache_idx]; + cache_idx++; + gen_vertices = &cache_data[cache_idx]; + cache_idx += gen_vertex_count; + + // Load UVs + gen_uvs = (float *)&cache_data[cache_idx]; + cache_idx += gen_vertex_count * 2; + + // Load indices + gen_index_count = cache_data[cache_idx]; + cache_idx++; + gen_indices = &cache_data[cache_idx]; + + // Return cache data size to the caller + r_cache_size = sizeof(int) * (4 + 2 + 1 + gen_vertex_count + (gen_vertex_count * 2) + 1 + gen_index_count); // hash + size hint + vertex_count + vertices + uvs + index_count + indices + r_used_cache = true; + } + + if (!cached) { + bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), face_materials.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); + + if (!ok) { + return ERR_CANT_CREATE; + } + + if (r_used_cache) { + unsigned int new_cache_size = 4 + 2 + 1 + gen_vertex_count + (gen_vertex_count * 2) + 1 + gen_index_count; // hash + size hint + vertex_count + vertices + uvs + index_count + indices + new_cache_size *= sizeof(int); + int *new_cache_data = (int *)memalloc(new_cache_size); + unsigned int new_cache_idx = 0; + + // hash + memcpy(&new_cache_data[new_cache_idx], hash, 16); + new_cache_idx += 4; + + // size hint + new_cache_data[new_cache_idx] = size_x; + new_cache_data[new_cache_idx + 1] = size_y; + new_cache_idx += 2; + + // vertex count + new_cache_data[new_cache_idx] = gen_vertex_count; + new_cache_idx++; + + // vertices + memcpy(&new_cache_data[new_cache_idx], gen_vertices, sizeof(int) * gen_vertex_count); + new_cache_idx += gen_vertex_count; + + // uvs + memcpy(&new_cache_data[new_cache_idx], gen_uvs, sizeof(float) * gen_vertex_count * 2); + new_cache_idx += gen_vertex_count * 2; + + // index count + new_cache_data[new_cache_idx] = gen_index_count; + new_cache_idx++; + + // indices + memcpy(&new_cache_data[new_cache_idx], gen_indices, sizeof(int) * gen_index_count); + new_cache_idx += gen_index_count; + + // Return cache data to the caller + r_cache_data = new_cache_data; + r_cache_size = new_cache_size; + r_used_cache = false; + } } //remove surfaces @@ -1212,13 +1362,13 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe } //create surfacetools for each surface.. - Vector > surfaces_tools; + LocalVector > surfaces_tools; - for (int i = 0; i < surfaces.size(); i++) { + for (int i = 0; i < lightmap_surfaces.size(); i++) { Ref st; st.instance(); st->begin(Mesh::PRIMITIVE_TRIANGLES); - st->set_material(surfaces[i].material); + st->set_material(lightmap_surfaces[i].material); surfaces_tools.push_back(st); //stay there } @@ -1226,61 +1376,62 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe //go through all indices for (int i = 0; i < gen_index_count; i += 3) { - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], uv_index.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_index.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_index.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG); - ERR_FAIL_COND_V(uv_index[gen_vertices[gen_indices[i + 0]]].first != uv_index[gen_vertices[gen_indices[i + 1]]].first || uv_index[gen_vertices[gen_indices[i + 0]]].first != uv_index[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); + ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); - int surface = uv_index[gen_vertices[gen_indices[i + 0]]].first; + int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first; for (int j = 0; j < 3; j++) { - SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second]; + SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; - if (surfaces[surface].format & ARRAY_FORMAT_COLOR) { - surfaces_tools.write[surface]->add_color(v.color); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_COLOR) { + surfaces_tools[surface]->add_color(v.color); } - if (surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { - surfaces_tools.write[surface]->add_uv(v.uv); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { + surfaces_tools[surface]->add_uv(v.uv); } - if (surfaces[surface].format & ARRAY_FORMAT_NORMAL) { - surfaces_tools.write[surface]->add_normal(v.normal); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_NORMAL) { + surfaces_tools[surface]->add_normal(v.normal); } - if (surfaces[surface].format & ARRAY_FORMAT_TANGENT) { + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TANGENT) { Plane t; t.normal = v.tangent; t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; - surfaces_tools.write[surface]->add_tangent(t); + surfaces_tools[surface]->add_tangent(t); } - if (surfaces[surface].format & ARRAY_FORMAT_BONES) { - surfaces_tools.write[surface]->add_bones(v.bones); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_BONES) { + surfaces_tools[surface]->add_bones(v.bones); } - if (surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { - surfaces_tools.write[surface]->add_weights(v.weights); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { + surfaces_tools[surface]->add_weights(v.weights); } Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); - surfaces_tools.write[surface]->add_uv2(uv2); + surfaces_tools[surface]->add_uv2(uv2); - surfaces_tools.write[surface]->add_vertex(v.vertex); + surfaces_tools[surface]->add_vertex(v.vertex); } } - //free stuff - ::free(gen_vertices); - ::free(gen_indices); - ::free(gen_uvs); - //generate surfaces - - for (int i = 0; i < surfaces_tools.size(); i++) { - surfaces_tools.write[i]->index(); - surfaces_tools.write[i]->commit(Ref((ArrayMesh *)this), surfaces[i].format); + for (unsigned int i = 0; i < surfaces_tools.size(); i++) { + surfaces_tools[i]->index(); + surfaces_tools[i]->commit(Ref((ArrayMesh *)this), lightmap_surfaces[i].format); } set_lightmap_size_hint(Size2(size_x, size_y)); + if (!cached) { + //free stuff + ::free(gen_vertices); + ::free(gen_indices); + ::free(gen_uvs); + } + return OK; } diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index f4f86ff4598b..3c4cbb570e51 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -231,6 +231,7 @@ class ArrayMesh : public Mesh { void regen_normalmaps(); Error lightmap_unwrap(const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); + Error lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); virtual void reload_from_file(); diff --git a/scene/resources/sky.cpp b/scene/resources/sky.cpp index a724b435d6c6..5bd96bac0e60 100644 --- a/scene/resources/sky.cpp +++ b/scene/resources/sky.cpp @@ -390,6 +390,10 @@ ProceduralSky::TextureSize ProceduralSky::get_texture_size() const { return texture_size; } +Ref ProceduralSky::get_panorama() const { + return panorama; +} + RID ProceduralSky::get_rid() const { return sky; } @@ -414,9 +418,9 @@ void ProceduralSky::_update_sky() { } } else { - Ref image = _generate_sky(); - VS::get_singleton()->texture_allocate(texture, image->get_width(), image->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); - VS::get_singleton()->texture_set_data(texture, image); + panorama = _generate_sky(); + VS::get_singleton()->texture_allocate(texture, panorama->get_width(), panorama->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); + VS::get_singleton()->texture_set_data(texture, panorama); _radiance_changed(); } } @@ -432,8 +436,9 @@ void ProceduralSky::_queue_update() { void ProceduralSky::_thread_done(const Ref &p_image) { - VS::get_singleton()->texture_allocate(texture, p_image->get_width(), p_image->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); - VS::get_singleton()->texture_set_data(texture, p_image); + panorama = p_image; + VS::get_singleton()->texture_allocate(texture, panorama->get_width(), panorama->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); + VS::get_singleton()->texture_set_data(texture, panorama); _radiance_changed(); Thread::wait_to_finish(sky_thread); memdelete(sky_thread); diff --git a/scene/resources/sky.h b/scene/resources/sky.h index 5d763cbdbb91..503b23976f12 100644 --- a/scene/resources/sky.h +++ b/scene/resources/sky.h @@ -122,6 +122,7 @@ class ProceduralSky : public Sky { RID sky; RID texture; + Ref panorama; bool update_queued; bool regen_queued; @@ -189,6 +190,8 @@ class ProceduralSky : public Sky { void set_texture_size(TextureSize p_size); TextureSize get_texture_size() const; + Ref get_panorama() const; + virtual RID get_rid() const; ProceduralSky(bool p_desaturate = false); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 0e94864b8f76..03e5a676d936 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -2250,6 +2250,143 @@ Image::Format TextureLayered::get_format() const { return format; } +Error TextureLayered::load(const String &p_path) { + + Error error; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &error); + ERR_FAIL_COND_V(error, error); + + uint8_t header[5] = { 0, 0, 0, 0, 0 }; + f->get_buffer(header, 4); + + if (header[0] == 'G' && header[1] == 'D' && header[2] == '3' && header[3] == 'T') { + if (!Object::cast_to(this)) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_INVALID_DATA); + } + } else if (header[0] == 'G' && header[1] == 'D' && header[2] == 'A' && header[3] == 'T') { + if (!Object::cast_to(this)) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_INVALID_DATA); + } + } else { + + f->close(); + memdelete(f); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Unrecognized layered texture file format: " + String((const char *)header)); + } + + int tw = f->get_32(); + int th = f->get_32(); + int td = f->get_32(); + int flags = f->get_32(); //texture flags! + Image::Format format = Image::Format(f->get_32()); + uint32_t compression = f->get_32(); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed + + create(tw, th, td, format, flags); + + for (int layer = 0; layer < td; layer++) { + + Ref image; + image.instance(); + + if (compression == COMPRESS_LOSSLESS) { + //look for a PNG file inside + + int mipmaps = f->get_32(); + Vector > mipmap_images; + + for (int i = 0; i < mipmaps; i++) { + uint32_t size = f->get_32(); + + PoolVector pv; + pv.resize(size); + { + PoolVector::Write w = pv.write(); + f->get_buffer(w.ptr(), size); + } + + Ref img = Image::lossless_unpacker(pv); + + if (img.is_null() || img->empty() || format != img->get_format()) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + + mipmap_images.push_back(img); + } + + if (mipmap_images.size() == 1) { + + image = mipmap_images[0]; + + } else { + int total_size = Image::get_image_data_size(tw, th, format, true); + PoolVector img_data; + img_data.resize(total_size); + + { + PoolVector::Write w = img_data.write(); + + int ofs = 0; + for (int i = 0; i < mipmap_images.size(); i++) { + + PoolVector id = mipmap_images[i]->get_data(); + int len = id.size(); + PoolVector::Read r = id.read(); + copymem(&w[ofs], r.ptr(), len); + ofs += len; + } + } + + image->create(tw, th, true, format, img_data); + if (image->empty()) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + } + + } else { + + //look for regular format + bool mipmaps = (flags & Texture::FLAG_MIPMAPS); + int total_size = Image::get_image_data_size(tw, th, format, mipmaps); + + PoolVector img_data; + img_data.resize(total_size); + + { + PoolVector::Write w = img_data.write(); + int bytes = f->get_buffer(w.ptr(), total_size); + if (bytes != total_size) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + } + + image->create(tw, th, mipmaps, format, img_data); + } + + set_layer_data(image, layer); + } + + memdelete(f); + + path_to_file = p_path; + _change_notify(); + return OK; +} + +String TextureLayered::get_load_path() const { + + return path_to_file; +} + uint32_t TextureLayered::get_width() const { return width; } @@ -2262,6 +2399,20 @@ uint32_t TextureLayered::get_depth() const { return depth; } +void TextureLayered::reload_from_file() { + + String path = get_path(); + if (!path.is_resource_file()) + return; + + path = ResourceLoader::path_remap(path); //remap for translation + path = ResourceLoader::import_remap(path); //remap for import + if (!path.is_resource_file()) + return; + + load(path); +} + void TextureLayered::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(!p_data.has("width")); ERR_FAIL_COND(!p_data.has("height")); @@ -2410,139 +2561,11 @@ RES ResourceFormatLoaderTextureLayered::load(const String &p_path, const String ERR_FAIL_V_MSG(RES(), "Unrecognized layered texture extension."); } - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, RES(), "Cannot open file '" + p_path + "'."); - - uint8_t header[5] = { 0, 0, 0, 0, 0 }; - f->get_buffer(header, 4); - - if (header[0] == 'G' && header[1] == 'D' && header[2] == '3' && header[3] == 'T') { - if (tex3d.is_null()) { - f->close(); - memdelete(f); - ERR_FAIL_COND_V(tex3d.is_null(), RES()) - } - } else if (header[0] == 'G' && header[1] == 'D' && header[2] == 'A' && header[3] == 'T') { - if (texarr.is_null()) { - f->close(); - memdelete(f); - ERR_FAIL_COND_V(texarr.is_null(), RES()) - } - } else { - - f->close(); - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unrecognized layered texture file format '" + String((const char *)header) + "'."); - } - - int tw = f->get_32(); - int th = f->get_32(); - int td = f->get_32(); - int flags = f->get_32(); //texture flags! - Image::Format format = Image::Format(f->get_32()); - uint32_t compression = f->get_32(); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed - - lt->create(tw, th, td, format, flags); - - for (int layer = 0; layer < td; layer++) { - - Ref image; - image.instance(); - - if (compression == COMPRESSION_LOSSLESS) { - //look for a PNG file inside - - int mipmaps = f->get_32(); - Vector > mipmap_images; - - for (int i = 0; i < mipmaps; i++) { - uint32_t size = f->get_32(); - - PoolVector pv; - pv.resize(size); - { - PoolVector::Write w = pv.write(); - f->get_buffer(w.ptr(), size); - } - - Ref img = Image::lossless_unpacker(pv); - - if (img.is_null() || img->empty() || format != img->get_format()) { - if (r_error) { - *r_error = ERR_FILE_CORRUPT; - } - f->close(); - memdelete(f); - ERR_FAIL_V(RES()); - } - - mipmap_images.push_back(img); - } - - if (mipmap_images.size() == 1) { - - image = mipmap_images[0]; - - } else { - int total_size = Image::get_image_data_size(tw, th, format, true); - PoolVector img_data; - img_data.resize(total_size); - - { - PoolVector::Write w = img_data.write(); - - int ofs = 0; - for (int i = 0; i < mipmap_images.size(); i++) { - - PoolVector id = mipmap_images[i]->get_data(); - int len = id.size(); - PoolVector::Read r = id.read(); - copymem(&w[ofs], r.ptr(), len); - ofs += len; - } - } - - image->create(tw, th, true, format, img_data); - if (image->empty()) { - if (r_error) { - *r_error = ERR_FILE_CORRUPT; - } - f->close(); - memdelete(f); - ERR_FAIL_V(RES()); - } - } - - } else { - - //look for regular format - bool mipmaps = (flags & Texture::FLAG_MIPMAPS); - int total_size = Image::get_image_data_size(tw, th, format, mipmaps); - - PoolVector img_data; - img_data.resize(total_size); - - { - PoolVector::Write w = img_data.write(); - int bytes = f->get_buffer(w.ptr(), total_size); - if (bytes != total_size) { - if (r_error) { - *r_error = ERR_FILE_CORRUPT; - } - f->close(); - memdelete(f); - ERR_FAIL_V(RES()); - } - } - - image->create(tw, th, mipmaps, format, img_data); - } - - lt->set_layer_data(image, layer); - } - + Error err = lt->load(p_path); if (r_error) *r_error = OK; + if (err != OK) + return RES(); return lt; } diff --git a/scene/resources/texture.h b/scene/resources/texture.h index add9572dffda..9ee35ee13eb8 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -477,7 +477,14 @@ class TextureLayered : public Resource { FLAGS_DEFAULT = FLAG_FILTER, }; + enum CompressMode { + COMPRESS_LOSSLESS, + COMPRESS_VIDEO_RAM, + COMPRESS_UNCOMPRESSED + }; + private: + String path_to_file; bool is_3d; RID texture; Image::Format format; @@ -487,6 +494,8 @@ class TextureLayered : public Resource { int height; int depth; + virtual void reload_from_file(); + void _set_data(const Dictionary &p_data); Dictionary _get_data() const; @@ -498,6 +507,9 @@ class TextureLayered : public Resource { uint32_t get_flags() const; Image::Format get_format() const; + Error load(const String &p_path); + String get_load_path() const; + uint32_t get_width() const; uint32_t get_height() const; uint32_t get_depth() const; @@ -536,12 +548,6 @@ class TextureArray : public TextureLayered { class ResourceFormatLoaderTextureLayered : public ResourceFormatLoader { public: - enum Compression { - COMPRESSION_LOSSLESS, - COMPRESSION_VRAM, - COMPRESSION_UNCOMPRESSED - }; - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List *p_extensions) const; virtual bool handles_type(const String &p_type) const; diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index bf0bd1b4a4c5..894b782f5b38 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -120,6 +120,8 @@ class RasterizerScene { InstanceBase *lightmap_capture; RID lightmap; Vector lightmap_capture_data; //in a array (12 values) to avoid wasting space if unused. Alpha is unused, but needed to send to shader + int lightmap_slice; + Rect2 lightmap_uv_rect; virtual void base_removed() = 0; virtual void base_changed(bool p_aabb, bool p_materials) = 0; @@ -135,6 +137,8 @@ class RasterizerScene { baked_light = false; redraw_if_visible = false; lightmap_capture = NULL; + lightmap_slice = -1; + lightmap_uv_rect = Rect2(0, 0, 1, 1); } }; diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index daae705e1427..cf6d6f7d7298 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -548,7 +548,7 @@ class VisualServerRaster : public VisualServer { BIND3(instance_set_blend_shape_weight, RID, int, float) BIND3(instance_set_surface_material, RID, int, RID) BIND2(instance_set_visible, RID, bool) - BIND3(instance_set_use_lightmap, RID, RID, RID) + BIND5(instance_set_use_lightmap, RID, RID, RID, int, const Rect2 &) BIND2(instance_set_custom_aabb, RID, AABB) diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp index d62ee8791e9a..7c4b201400ab 100644 --- a/servers/visual/visual_server_scene.cpp +++ b/servers/visual/visual_server_scene.cpp @@ -484,7 +484,7 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) { InstanceLightmapCaptureData *lightmap_capture = static_cast(instance->base_data); //erase dependencies, since no longer a lightmap while (lightmap_capture->users.front()) { - instance_set_use_lightmap(lightmap_capture->users.front()->get()->self, RID(), RID()); + instance_set_use_lightmap(lightmap_capture->users.front()->get()->self, RID(), RID(), -1, Rect2(0, 0, 1, 1)); } } break; case VS::INSTANCE_GI_PROBE: { @@ -805,7 +805,7 @@ inline bool is_geometry_instance(VisualServer::InstanceType p_type) { return p_type == VS::INSTANCE_MESH || p_type == VS::INSTANCE_MULTIMESH || p_type == VS::INSTANCE_PARTICLES || p_type == VS::INSTANCE_IMMEDIATE; } -void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap) { +void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect) { Instance *instance = instance_owner.get(p_instance); ERR_FAIL_COND(!instance); @@ -814,6 +814,8 @@ void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap InstanceLightmapCaptureData *lightmap_capture = static_cast(((Instance *)instance->lightmap_capture)->base_data); lightmap_capture->users.erase(instance); instance->lightmap = RID(); + instance->lightmap_slice = -1; + instance->lightmap_uv_rect = Rect2(0, 0, 1, 1); instance->lightmap_capture = NULL; } @@ -826,6 +828,8 @@ void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap InstanceLightmapCaptureData *lightmap_capture = static_cast(((Instance *)instance->lightmap_capture)->base_data); lightmap_capture->users.insert(instance); instance->lightmap = p_lightmap; + instance->lightmap_slice = p_lightmap_slice; + instance->lightmap_uv_rect = p_lightmap_uv_rect; } } @@ -3618,7 +3622,7 @@ bool VisualServerScene::free(RID p_rid) { Instance *instance = instance_owner.get(p_rid); - instance_set_use_lightmap(p_rid, RID(), RID()); + instance_set_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1)); instance_set_scenario(p_rid, RID()); instance_set_base(p_rid, RID()); instance_geometry_set_material_override(p_rid, RID()); diff --git a/servers/visual/visual_server_scene.h b/servers/visual/visual_server_scene.h index c228e2731a3c..a33e9a4e97da 100644 --- a/servers/visual/visual_server_scene.h +++ b/servers/visual/visual_server_scene.h @@ -517,7 +517,7 @@ class VisualServerScene { virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight); virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material); virtual void instance_set_visible(RID p_instance, bool p_visible); - virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap); + virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect); virtual void instance_set_custom_aabb(RID p_instance, AABB p_aabb); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 70052f2bd0ac..0f24d7908f70 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -470,7 +470,7 @@ class VisualServerWrapMT : public VisualServer { FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_surface_material, RID, int, RID) FUNC2(instance_set_visible, RID, bool) - FUNC3(instance_set_use_lightmap, RID, RID, RID) + FUNC5(instance_set_use_lightmap, RID, RID, RID, int, const Rect2 &) FUNC2(instance_set_custom_aabb, RID, AABB) diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index c14a2d9abc7f..d5fcb3f230f0 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -1938,7 +1938,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &VisualServer::instance_set_blend_shape_weight); ClassDB::bind_method(D_METHOD("instance_set_surface_material", "instance", "surface", "material"), &VisualServer::instance_set_surface_material); ClassDB::bind_method(D_METHOD("instance_set_visible", "instance", "visible"), &VisualServer::instance_set_visible); - ClassDB::bind_method(D_METHOD("instance_set_use_lightmap", "instance", "lightmap_instance", "lightmap"), &VisualServer::instance_set_use_lightmap); + ClassDB::bind_method(D_METHOD("instance_set_use_lightmap", "instance", "lightmap_instance", "lightmap", "lightmap_slice", "lightmap_uv_rect"), &VisualServer::instance_set_use_lightmap, DEFVAL(-1), DEFVAL(Rect2(0, 0, 1, 1))); ClassDB::bind_method(D_METHOD("instance_set_custom_aabb", "instance", "aabb"), &VisualServer::instance_set_custom_aabb); ClassDB::bind_method(D_METHOD("instance_attach_skeleton", "instance", "skeleton"), &VisualServer::instance_attach_skeleton); ClassDB::bind_method(D_METHOD("instance_set_exterior", "instance", "enabled"), &VisualServer::instance_set_exterior); @@ -2446,6 +2446,9 @@ VisualServer::VisualServer() { GLOBAL_DEF(sz_balance_render_tree, 0.0f); ProjectSettings::get_singleton()->set_custom_property_info(sz_balance_render_tree, PropertyInfo(Variant::REAL, sz_balance_render_tree, PROPERTY_HINT_RANGE, "0.0,1.0,0.01")); + GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling", true); + GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false); + GLOBAL_DEF("rendering/quality/2d/use_software_skinning", true); GLOBAL_DEF("rendering/quality/2d/ninepatch_mode", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/2d/ninepatch_mode", PropertyInfo(Variant::INT, "rendering/quality/2d/ninepatch_mode", PROPERTY_HINT_ENUM, "Default,Scaling")); diff --git a/servers/visual_server.h b/servers/visual_server.h index 48e73e043809..dee3b8749ef5 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -843,7 +843,7 @@ class VisualServer : public Object { virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0; virtual void instance_set_visible(RID p_instance, bool p_visible) = 0; - virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap) = 0; + virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect) = 0; virtual void instance_set_custom_aabb(RID p_instance, AABB aabb) = 0; diff --git a/thirdparty/README.md b/thirdparty/README.md index 81d87717ba96..0a406db1c5f2 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -350,6 +350,10 @@ Collection of single-file libraries used in Godot components. * Version: git (2f625846a775501fb69456567409a8b12f10ea25, 2012) * License: BSD-3-Clause * Modifications: use `const char*` instead of `char*` for input string +- `stb_rect_pack.h` + * Upstream: https://github.com/nothings/stb + * Version: 1.00 + * License: Public Domain (Unlicense) or MIT - `stb_vorbis.c` * Upstream: https://github.com/nothings/stb * Version: 1.20 (314d0a6f9af5af27e585336eecea333e95c5a2d8, 2020) diff --git a/thirdparty/stb_rect_pack/stb_rect_pack.h b/thirdparty/stb_rect_pack/stb_rect_pack.h new file mode 100644 index 000000000000..3336fe7395bb --- /dev/null +++ b/thirdparty/stb_rect_pack/stb_rect_pack.h @@ -0,0 +1,629 @@ +// stb_rect_pack.h - v1.00 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ + From 089da5a71b2bf9a537fe9a94d52e6e3e1eb0e58b Mon Sep 17 00:00:00 2001 From: JFonS Date: Wed, 30 Dec 2020 14:54:06 +0100 Subject: [PATCH 4/7] Gracefully handle atlassed lightmaps in GLES2. --- scene/3d/baked_lightmap.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index 642f1cb0465a..017de2cd3730 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -726,7 +726,9 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa bsud.from_percent = 0.1; bsud.to_percent = 0.9; - Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, generate_atlas, max_atlas_size, environment_image, environment_xform, _lightmap_bake_step_function, &bsud, bake_substep_function); + bool gen_atlas = OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2 ? false : generate_atlas; + + Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, gen_atlas, max_atlas_size, environment_image, environment_xform, _lightmap_bake_step_function, &bsud, bake_substep_function); if (bake_err != Lightmapper::BAKE_OK) { switch (bake_err) { @@ -847,7 +849,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa images.push_back(lightmapper->get_bake_texture(i)); } - if (generate_atlas) { + if (gen_atlas) { Ref large_image; large_image.instance(); @@ -1079,6 +1081,8 @@ void BakedLightmap::_assign_lightmaps() { ERR_FAIL_COND(!light_data.is_valid()); + bool atlassed_on_gles2 = false; + for (int i = 0; i < light_data->get_user_count(); i++) { Ref lightmap = light_data->get_user_lightmap(i); ERR_CONTINUE(!lightmap.is_valid()); @@ -1089,14 +1093,22 @@ void BakedLightmap::_assign_lightmaps() { if (instance_idx >= 0) { RID instance = node->call("get_bake_mesh_instance", instance_idx); if (instance.is_valid()) { - VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); + int slice = light_data->get_user_lightmap_slice(i); + atlassed_on_gles2 = atlassed_on_gles2 || (slice != -1 && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2); + VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid(), slice, light_data->get_user_lightmap_uv_rect(i)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); - VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); + int slice = light_data->get_user_lightmap_slice(i); + atlassed_on_gles2 = atlassed_on_gles2 || (slice != -1 && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2); + VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid(), slice, light_data->get_user_lightmap_uv_rect(i)); } } + + if (atlassed_on_gles2) { + ERR_PRINT("GLES2 doesn't support layered textures, so lightmap atlassing is not supported. Please re-bake the lightmap or switch to GLES3."); + } } void BakedLightmap::_clear_lightmaps() { From 7d7d7ef16d49db3c3fdf354540a9bd24df8557c7 Mon Sep 17 00:00:00 2001 From: JFonS Date: Wed, 30 Dec 2020 15:36:39 +0100 Subject: [PATCH 5/7] CPU lightmapper formatting and minor fixes. --- core/math/geometry.cpp | 1 + modules/denoise/denoise_wrapper.cpp | 2 -- modules/denoise/lightmap_denoiser.cpp | 1 + modules/denoise/register_types.cpp | 1 + modules/lightmapper_cpu/SCsub | 1 + modules/lightmapper_cpu/lightmapper_cpu.cpp | 1 + modules/lightmapper_cpu/lightmapper_cpu.h | 1 + modules/lightmapper_cpu/register_types.cpp | 1 - modules/raycast/SCsub | 4 ++-- modules/raycast/lightmap_raycaster.cpp | 6 ++++-- modules/raycast/lightmap_raycaster.h | 3 ++- modules/raycast/register_types.cpp | 1 + scene/3d/baked_lightmap.cpp | 3 +++ scene/resources/mesh.cpp | 3 +-- thirdparty/README.md | 3 +++ 15 files changed, 22 insertions(+), 10 deletions(-) diff --git a/core/math/geometry.cpp b/core/math/geometry.cpp index 83f7f67dd883..096bee73ceba 100644 --- a/core/math/geometry.cpp +++ b/core/math/geometry.cpp @@ -31,6 +31,7 @@ #include "geometry.h" #include "core/print_string.h" + #include "thirdparty/misc/clipper.hpp" #include "thirdparty/misc/triangulator.h" #define STB_RECT_PACK_IMPLEMENTATION diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/denoise/denoise_wrapper.cpp index a52ac735a62b..cc0d52d46f0a 100644 --- a/modules/denoise/denoise_wrapper.cpp +++ b/modules/denoise/denoise_wrapper.cpp @@ -48,8 +48,6 @@ bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) { oidnSetSharedFilterImage(filter, "color", input_buffer, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); oidnSetFilter1b(filter, "hdr", true); - //oidnSetFilter1f(filter, "hdrScale", 1.0f); - //oidnSetFilter1i(filter, "verbose", 4); oidnCommitFilter(filter); oidnExecuteFilter(filter); diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/denoise/lightmap_denoiser.cpp index 7726e0dc065f..6978f2a8470a 100644 --- a/modules/denoise/lightmap_denoiser.cpp +++ b/modules/denoise/lightmap_denoiser.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "lightmap_denoiser.h" + #include "denoise_wrapper.h" LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() { diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp index b78734a5318c..c9c90c3408d9 100644 --- a/modules/denoise/register_types.cpp +++ b/modules/denoise/register_types.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "register_types.h" + #include "core/engine.h" #include "lightmap_denoiser.h" diff --git a/modules/lightmapper_cpu/SCsub b/modules/lightmapper_cpu/SCsub index 4fbb1b6b1dd9..e27beafe3dfc 100644 --- a/modules/lightmapper_cpu/SCsub +++ b/modules/lightmapper_cpu/SCsub @@ -5,4 +5,5 @@ Import("env_modules") env_lightmapper_rd = env_modules.Clone() # Godot source files +env_lightmapper_rd.Prepend(CPPPATH=["#thirdparty/embree/include"]) env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/lightmapper_cpu/lightmapper_cpu.cpp b/modules/lightmapper_cpu/lightmapper_cpu.cpp index 375d20976e18..a49652162373 100644 --- a/modules/lightmapper_cpu/lightmapper_cpu.cpp +++ b/modules/lightmapper_cpu/lightmapper_cpu.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "lightmapper_cpu.h" + #include "core/math/geometry.h" #include "core/os/os.h" #include "core/os/threaded_array_processor.h" diff --git a/modules/lightmapper_cpu/lightmapper_cpu.h b/modules/lightmapper_cpu/lightmapper_cpu.h index e809240099bb..f090ccdc2fe2 100644 --- a/modules/lightmapper_cpu/lightmapper_cpu.h +++ b/modules/lightmapper_cpu/lightmapper_cpu.h @@ -35,6 +35,7 @@ #include "scene/3d/lightmapper.h" #include "scene/resources/mesh.h" #include "scene/resources/surface_tool.h" + #include class LightmapperCPU : public Lightmapper { diff --git a/modules/lightmapper_cpu/register_types.cpp b/modules/lightmapper_cpu/register_types.cpp index ccf443669f0e..bd56763e85aa 100644 --- a/modules/lightmapper_cpu/register_types.cpp +++ b/modules/lightmapper_cpu/register_types.cpp @@ -46,7 +46,6 @@ void register_lightmapper_cpu_types() { GLOBAL_DEF("rendering/cpu_lightmapper/quality/high_quality_ray_count", 512); GLOBAL_DEF("rendering/cpu_lightmapper/quality/ultra_quality_ray_count", 1024); #ifndef _3D_DISABLED - ClassDB::register_class(); Lightmapper::create_cpu = create_lightmapper_cpu; #endif } diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub index 8c0f81ea0331..84ab13358818 100644 --- a/modules/raycast/SCsub +++ b/modules/raycast/SCsub @@ -62,7 +62,7 @@ embree_dir = "#thirdparty/embree/" env_embree = env_modules.Clone() embree_sources = [embree_dir + file for file in embree_src] -env_embree.Prepend(CPPPATH=[embree_dir, embree_dir + "include/embree3"]) +env_embree.Prepend(CPPPATH=[embree_dir, embree_dir + "include"]) env_embree.Append( CPPFLAGS=[ "-DEMBREE_TARGET_SSE2", @@ -88,6 +88,6 @@ env_embree.disable_warnings() env_embree.add_source_files(env.modules_sources, embree_sources) env_raycast = env_modules.Clone() -env_raycast.Prepend(CPPPATH=[embree_dir, embree_dir + "include/embree3", embree_dir + "common"]) +env_raycast.Prepend(CPPPATH=[embree_dir, embree_dir + "include", embree_dir + "common"]) env_raycast.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/raycast/lightmap_raycaster.cpp b/modules/raycast/lightmap_raycaster.cpp index b5eb15de4abf..ef0eaa3694c5 100644 --- a/modules/raycast/lightmap_raycaster.cpp +++ b/modules/raycast/lightmap_raycaster.cpp @@ -29,8 +29,10 @@ /*************************************************************************/ #include "lightmap_raycaster.h" -#include "math/vec2.h" -#include "math/vec3.h" + +// From Embree. +#include +#include using namespace embree; diff --git a/modules/raycast/lightmap_raycaster.h b/modules/raycast/lightmap_raycaster.h index 46ed2dfba2be..e04286f40c68 100644 --- a/modules/raycast/lightmap_raycaster.h +++ b/modules/raycast/lightmap_raycaster.h @@ -31,7 +31,8 @@ #include "core/object.h" #include "scene/3d/lightmapper.h" #include "scene/resources/mesh.h" -#include "thirdparty/embree/include/embree3/rtcore.h" + +#include class LightmapRaycasterEmbree : public LightmapRaycaster { GDCLASS(LightmapRaycasterEmbree, LightmapRaycaster); diff --git a/modules/raycast/register_types.cpp b/modules/raycast/register_types.cpp index 8009fbbbeccd..8af3c467ab24 100644 --- a/modules/raycast/register_types.cpp +++ b/modules/raycast/register_types.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "register_types.h" + #include "lightmap_raycaster.h" void register_raycast_types() { diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index 017de2cd3730..ecd0f5a85e7a 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -1454,7 +1454,10 @@ void BakedLightmap::_bind_methods() { BIND_ENUM_CONSTANT(BAKE_ERROR_NO_SAVE_PATH); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_MESHES); BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE); + BIND_ENUM_CONSTANT(BAKE_ERROR_LIGHTMAP_SIZE); + BIND_ENUM_CONSTANT(BAKE_ERROR_INVALID_MESH); BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED); + BIND_ENUM_CONSTANT(BAKE_ERROR_NO_LIGHTMAPPER); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 57df051a9f61..f997929ed3f5 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -30,14 +30,13 @@ #include "mesh.h" +#include "core/crypto/crypto_core.h" #include "core/local_vector.h" #include "core/pair.h" #include "scene/resources/concave_polygon_shape.h" #include "scene/resources/convex_polygon_shape.h" #include "surface_tool.h" -#include "core/crypto/crypto_core.h" - #include Mesh::ConvexDecompositionFunc Mesh::convex_composition_function = NULL; diff --git a/thirdparty/README.md b/thirdparty/README.md index 0a406db1c5f2..9ff0bf6e6f9d 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -50,6 +50,9 @@ Files extracted from upstream: - All cpp files listed in `modules/raytrace/godot_update_embree.py` - All header files in the directories listed in `modules/raytrace/godot_update_embree.py` +The `modules/raytrace/godot_update_embree.py`script can be used to pull the +relevant files from the latest Embree release and apply some automatic changes. + Some minor changes have been made in order to fix build errors. They are marked with `// -- GODOT start --` and `// -- GODOT end --` comments. Apply the patches in the `patches/` folder when syncing on newer upstream From d909592fc508b32c4c3a74d2e5e310ed200c1822 Mon Sep 17 00:00:00 2001 From: JFonS Date: Wed, 30 Dec 2020 17:12:41 +0100 Subject: [PATCH 6/7] Add documentation for new CPU lightmapper. --- doc/classes/BakedLightmap.xml | 106 ++++++++++++++++++++---------- doc/classes/BakedLightmapData.xml | 10 ++- doc/classes/GeometryInstance.xml | 20 ++++++ doc/classes/Mesh.xml | 2 +- doc/classes/ProjectSettings.xml | 18 +++++ doc/classes/VisualServer.xml | 4 ++ 6 files changed, 120 insertions(+), 40 deletions(-) diff --git a/doc/classes/BakedLightmap.xml b/doc/classes/BakedLightmap.xml index 71f22d186440..c93902da98d9 100644 --- a/doc/classes/BakedLightmap.xml +++ b/doc/classes/BakedLightmap.xml @@ -16,54 +16,71 @@ - + - Bakes the lightmaps within the currently edited scene. Returns a [enum BakeError] to signify if the bake was successful, or if unsuccessful, how the bake failed. - - - - - - - Executes a dry run bake of lightmaps within the currently edited scene. + Bakes the lightmap, scanning from the given [code]from_node[/code] root and saves the resulting [BakedLightmapData] in [code]data_save_path[/code]. If no save path is provided it will try to match the path from the current [member light_data]. - - Grid subdivision size for lightmapper calculation. The default value will work for most cases. Increase for better lighting on small details or if your scene is very large. + + When enabled, the lightmapper will merge the textures for all meshes into a single large layered texture. Not supported in GLES2. - - If a [member Mesh.lightmap_size_hint] isn't specified, the lightmap baker will dynamically set the lightmap size using this value. This value is measured in texels per world unit. The maximum lightmap texture size is 4096x4096. + + Maximum size of each lightmap layer, only used when [member atlas_generate] is enabled. - - Multiplies the light sources' intensity by this value. For instance, if the value is set to 2, lights will be twice as bright. If the value is set to 0.5, lights will be half as bright. + + Raycasting bias used during baking to avoid floating point precission issues. - - The size of the affected area. + + Number of light bounces that are taken into account during baking. - - If [code]true[/code], the lightmap can capture light values greater than [code]1.0[/code]. Turning this off will result in a smaller file size. + + Grid size used for real-time capture information on dynamic objects. - - Lightmapping mode. See [enum BakeMode]. + + When enabled, an octree containing the scene's lighting information will be computed. This octree will then be used to light dynamic objects in the scene. - - Defines how far the light will travel before it is no longer effective. The higher the number, the farther the light will travel. For instance, if the value is set to 2, the light will go twice as far. If the value is set to 0.5, the light will only go half as far. + + Bias value to reduce the amount of light proagation in the captured octree. - - Three quality modes are available. Higher quality requires more rendering time. See [enum BakeQuality]. + + Bake quality of the capture data. - - Grid size used for real-time capture information on dynamic objects. Cannot be larger than [member bake_cell_size]. + + If a baked mesh doesn't have a UV2 size hint, this value will be used to roughly compute a suitable lightmap size. + + + The environment color when [member environment_mode] is set to [constant ENVIRONMENT_MODE_CUSTOM_COLOR]. + + + The energy scaling factor when when [member environment_mode] is set to [constant ENVIRONMENT_MODE_CUSTOM_COLOR] or [constant ENVIRONMENT_MODE_CUSTOM_SKY]. + + + The [Sky] resource to use when [member environment_mode] is set o [constant ENVIRONMENT_MODE_CUSTOM_SKY]. + + + The rotation of the baked custom sky. + + + Decides which environment to use during baking. - - The location where lightmaps will be saved. + + Size of the baked lightmap. Only meshes inside this region will be included in the baked lightmap, also used as the bounds of the captured region for dynamic lighting. + + + Deprecated, in previous versions it determined the location where lightmaps were be saved. The calculated light data. + + Determines the amount of samples per texel used in indrect light baking. The amount of samples for each quality level can be configured in the project settings. + + + When enabled, a lightmap denoiser will be used to reduce the noise inherent to Monte Carlo based global illumination. + @@ -73,13 +90,10 @@ The default bake quality mode. - The highest bake quality mode. Takes longer to calculate. + A higher bake quality mode. Takes longer to calculate. - - Less precise but faster bake mode. - - - More precise bake mode but can take considerably longer to bake. + + The highest bake quality mode. Takes the longest to calculate. Baking was successful. @@ -93,8 +107,28 @@ Returns when the baker cannot save per-mesh textures to file. - + + The size of the generated lightmaps is too large. + + + Some mesh contains UV2 values outside the [code][0,1][/code] range. + + Returns if user cancels baking. + + + + No environment is used during baking. + + + The baked environment is automatically picked from the current scene. + + + A custom sky is used as environment during baking. + + + A custom solid color is used as environment during baking. + diff --git a/doc/classes/BakedLightmapData.xml b/doc/classes/BakedLightmapData.xml index bc44646018ec..72b4d68b7e05 100644 --- a/doc/classes/BakedLightmapData.xml +++ b/doc/classes/BakedLightmapData.xml @@ -12,9 +12,13 @@ - + - + + + + + @@ -32,7 +36,7 @@ - + diff --git a/doc/classes/GeometryInstance.xml b/doc/classes/GeometryInstance.xml index 4545d88121e6..0d5a85ea8a72 100644 --- a/doc/classes/GeometryInstance.xml +++ b/doc/classes/GeometryInstance.xml @@ -46,6 +46,12 @@ The extra distance added to the GeometryInstance's bounding box ([AABB]) to increase its cull box. + + When disabled, the mesh will be taken into account when computing indirect lighting, but the resulting lightmap will not be saved. Useful for emissive only materials or shadow casters. + + + Scale factor for the generated baked lightmap. Useful for adding detail to certain mesh instances. + The GeometryInstance's max LOD distance. [b]Note:[/b] This property currently has no effect. @@ -71,6 +77,20 @@ + + The generated lightmap texture will have the original size. + + + The generated lightmap texture will be twice as large, on each axis. + + + The generated lightmap texture will be 4 times as large, on each axis. + + + The generated lightmap texture will be 8 times as large, on each axis. + + + Will not cast any shadows. diff --git a/doc/classes/Mesh.xml b/doc/classes/Mesh.xml index d843b6192c62..54ccb0a82871 100644 --- a/doc/classes/Mesh.xml +++ b/doc/classes/Mesh.xml @@ -107,7 +107,7 @@ - Sets a hint to be used for lightmap resolution in [BakedLightmap]. Overrides [member BakedLightmap.bake_default_texels_per_unit]. + Sets a hint to be used for lightmap resolution in [BakedLightmap]. Overrides [member BakedLightmap.default_texels_per_unit]. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 32060afb1dee..1992cef09bc3 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1057,6 +1057,18 @@ The amount of UV contraction. This figure is divided by 1000000, and is a proportion of the total texture dimensions, where the width and height are both ranged from 0.0 to 1.0. Use the default unless correcting for a problem on particular hardware. + + Amount of light samples taken when using [constant BakedLightmap.BAKE_QUALITY_HIGH]. + + + Amount of light samples taken when using [constant BakedLightmap.BAKE_QUALITY_LOW]. + + + Amount of light samples taken when using [constant BakedLightmap.BAKE_QUALITY_MEDIUM]. + + + Amount of light samples taken when using [constant BakedLightmap.BAKE_QUALITY_ULTRA]. + Default background clear color. Overridable per [Viewport] using its [Environment]. See [member Environment.background_mode] and [member Environment.background_color] in particular. To change this default color programmatically, use [method VisualServer.set_default_clear_color]. @@ -1169,6 +1181,12 @@ Lower-end override for [member rendering/quality/intended_usage/framebuffer_allocation] on mobile devices, due to performance concerns or driver support. + + Enable usage of bicubic sampling in baked lightmaps. This results in smoother looking lighting at the expense of more bandwidth usage. + + + Lower-end override for [member rendering/quality/lightmapping/use_bicubic_sampling] on mobile devices, in order to reduce bandwidth usage. + Size of the atlas used by reflection probes. A larger size can result in higher visual quality, while a smaller size will be faster and take up less memory. diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml index ead815e13535..8aae7d98a4bc 100644 --- a/doc/classes/VisualServer.xml +++ b/doc/classes/VisualServer.xml @@ -2045,6 +2045,10 @@ + + + + Sets the lightmap to use with this instance. From b1ca82c43a09e95b0072ef9cd67a0cdb4d416507 Mon Sep 17 00:00:00 2001 From: JFonS Date: Mon, 11 Jan 2021 20:20:22 +0100 Subject: [PATCH 7/7] CPU lightmapper fixes. - Fix Embree runtime when using MinGW (patch by @RandomShaper). - Fix baking of lightmaps on GridMaps. - Fix some GLSL errors. - Fix overflow in the number of shader variants (GLES2). --- doc/classes/ProjectSettings.xml | 2 +- drivers/gles2/rasterizer_scene_gles2.cpp | 6 +- drivers/gles2/rasterizer_storage_gles2.cpp | 2 + drivers/gles2/shader_compiler_gles2.h | 2 +- drivers/gles2/shaders/scene.glsl | 15 ++- drivers/gles3/rasterizer_storage_gles3.cpp | 2 + drivers/gles3/shaders/scene.glsl | 14 +-- modules/denoise/denoise_wrapper.cpp | 4 +- modules/denoise/denoise_wrapper.h | 4 +- modules/denoise/lightmap_denoiser.cpp | 4 +- modules/denoise/lightmap_denoiser.h | 4 +- modules/denoise/register_types.cpp | 4 +- modules/denoise/register_types.h | 4 +- modules/lightmapper_cpu/lightmapper_cpu.cpp | 4 +- modules/lightmapper_cpu/lightmapper_cpu.h | 4 +- modules/lightmapper_cpu/register_types.cpp | 4 +- modules/lightmapper_cpu/register_types.h | 4 +- modules/raycast/lightmap_raycaster.cpp | 4 +- modules/raycast/lightmap_raycaster.h | 4 +- modules/raycast/register_types.cpp | 4 +- modules/raycast/register_types.h | 4 +- scene/3d/baked_lightmap.cpp | 19 ++++ scene/3d/lightmapper.cpp | 4 +- scene/3d/lightmapper.h | 4 +- servers/visual_server.cpp | 3 - thirdparty/embree/common/sys/intrinsics.h | 14 +++ thirdparty/embree/common/sys/mutex.h | 16 +++ thirdparty/embree/common/sys/sysinfo.cpp | 2 +- .../common/tasking/taskschedulerinternal.cpp | 8 ++ thirdparty/embree/pathces/godot-changes.patch | 105 +++++++++++++++++- 30 files changed, 217 insertions(+), 57 deletions(-) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 1992cef09bc3..2ef5b17d5606 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1182,7 +1182,7 @@ Lower-end override for [member rendering/quality/intended_usage/framebuffer_allocation] on mobile devices, due to performance concerns or driver support. - Enable usage of bicubic sampling in baked lightmaps. This results in smoother looking lighting at the expense of more bandwidth usage. + Enable usage of bicubic sampling in baked lightmaps. This results in smoother looking lighting at the expense of more bandwidth usage. On GLES2, changes to this setting will only be applied upon restarting the application. Lower-end override for [member rendering/quality/lightmapping/use_bicubic_sampling] on mobile devices, in order to reduce bandwidth usage. diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index d0e4c5a51c67..ff27f113338d 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -4050,14 +4050,16 @@ void RasterizerSceneGLES2::initialize() { } } + if (storage->config.use_lightmap_filter_bicubic) { + state.scene_shader.add_custom_define("#define USE_LIGHTMAP_FILTER_BICUBIC\n"); + } + shadow_filter_mode = SHADOW_FILTER_NEAREST; glFrontFace(GL_CW); } void RasterizerSceneGLES2::iteration() { - storage->config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); - state.scene_shader.set_conditional(SceneShaderGLES2::USE_LIGHTMAP_FILTER_BICUBIC, storage->config.use_lightmap_filter_bicubic); shadow_filter_mode = ShadowFilterMode(int(GLOBAL_GET("rendering/quality/shadows/filter_mode"))); } diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 4a17f3274d4b..03ff9b560102 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -6299,6 +6299,8 @@ void RasterizerStorageGLES2::initialize() { config.force_vertex_shading = GLOBAL_GET("rendering/quality/shading/force_vertex_shading"); config.use_fast_texture_filter = GLOBAL_GET("rendering/quality/filters/use_nearest_mipmap_filter"); + GLOBAL_DEF_RST("rendering/quality/lightmapping/use_bicubic_sampling", true); + GLOBAL_DEF_RST("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false); config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); } diff --git a/drivers/gles2/shader_compiler_gles2.h b/drivers/gles2/shader_compiler_gles2.h index f302001da593..c4877d23b0e0 100644 --- a/drivers/gles2/shader_compiler_gles2.h +++ b/drivers/gles2/shader_compiler_gles2.h @@ -98,4 +98,4 @@ class ShaderCompilerGLES2 { ShaderCompilerGLES2(); }; -#endif // SHADERCOMPILERGLES3_H +#endif // SHADERCOMPILERGLES2_H diff --git a/drivers/gles2/shaders/scene.glsl b/drivers/gles2/shaders/scene.glsl index d1f59c0a2a8d..9b2521c2c142 100644 --- a/drivers/gles2/shaders/scene.glsl +++ b/drivers/gles2/shaders/scene.glsl @@ -890,7 +890,7 @@ void reflection_process(samplerCube reflection_map, uniform mediump sampler2D lightmap; //texunit:-4 uniform mediump float lightmap_energy; -#ifdef USE_LIGHTMAP_FILTER_BICUBIC +#if defined(USE_LIGHTMAP_FILTER_BICUBIC) uniform mediump vec2 lightmap_texture_size; // w0, w1, w2, and w3 are the four cubic B-spline basis functions @@ -951,11 +951,6 @@ vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { return (g0(fuv.y) * (g0x * texture2D(tex, p0) + g1x * texture2D(tex, p1))) + (g1(fuv.y) * (g0x * texture2D(tex, p2) + g1x * texture2D(tex, p3))); } -#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D_bicubic(m_tex, m_uv) - -#else //!USE_LIGHTMAP_FILTER_BICUBIC -#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D(m_tex, m_uv) - #endif //USE_LIGHTMAP_FILTER_BICUBIC #endif @@ -1728,8 +1723,12 @@ FRAGMENT_SHADER_CODE } #ifdef USE_LIGHTMAP - //ambient light will come entirely from lightmap is lightmap is used - ambient_light = LIGHTMAP_TEXTURE_SAMPLE(lightmap, uv2_interp).rgb * lightmap_energy; +//ambient light will come entirely from lightmap is lightmap is used +#if defined(USE_LIGHTMAP_FILTER_BICUBIC) + ambient_light = texture2D_bicubic(lightmap, uv2_interp).rgb * lightmap_energy; +#else + ambient_light = texture2D(lightmap, uv2_interp).rgb * lightmap_energy; +#endif #endif #ifdef USE_LIGHTMAP_CAPTURE diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 13e7ce333ed9..6984e3f2e82c 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -8555,6 +8555,8 @@ void RasterizerStorageGLES3::initialize() { String renderer = (const char *)glGetString(GL_RENDERER); + GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling", true); + GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false); config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); config.use_depth_prepass = bool(GLOBAL_GET("rendering/quality/depth_prepass/enable")); diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index ba430b520d7e..78071ea51b04 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -1488,7 +1488,7 @@ float h1(float a) { return 1.0 + w3(a) / (w2(a) + w3(a)); } -vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { +vec4 texture_bicubic(sampler2D tex, vec2 uv) { vec2 texel_size = vec2(1.0) / lightmap_texture_size; uv = uv * lightmap_texture_size + vec2(0.5); @@ -1508,11 +1508,11 @@ vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; - return (g0(fuv.y) * (g0x * texture2D(tex, p0) + g1x * texture2D(tex, p1))) + - (g1(fuv.y) * (g0x * texture2D(tex, p2) + g1x * texture2D(tex, p3))); + return (g0(fuv.y) * (g0x * texture(tex, p0) + g1x * texture(tex, p1))) + + (g1(fuv.y) * (g0x * texture(tex, p2) + g1x * texture(tex, p3))); } -vec4 texture_bicubic(sampler2DArray tex, vec3 uv) { +vec4 textureArray_bicubic(sampler2DArray tex, vec3 uv) { vec2 texel_size = vec2(1.0) / lightmap_texture_size; uv.xy = uv.xy * lightmap_texture_size + vec2(0.5); @@ -1536,11 +1536,11 @@ vec4 texture_bicubic(sampler2DArray tex, vec3 uv) { (g1(fuv.y) * (g0x * texture(tex, vec3(p2, uv.z)) + g1x * texture(tex, vec3(p3, uv.z)))); } -#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D_bicubic(m_tex, m_uv) -#define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) texture_bicubic(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture_bicubic(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) textureArray_bicubic(m_tex, m_uv) #else //!USE_LIGHTMAP_FILTER_BICUBIC -#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture(m_tex, m_uv) #define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) texture(m_tex, m_uv) #endif //USE_LIGHTMAP_FILTER_BICUBIC diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/denoise/denoise_wrapper.cpp index cc0d52d46f0a..0ef33e7ba522 100644 --- a/modules/denoise/denoise_wrapper.cpp +++ b/modules/denoise/denoise_wrapper.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h index 2107df09c174..25e342bc93f1 100644 --- a/modules/denoise/denoise_wrapper.h +++ b/modules/denoise/denoise_wrapper.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/denoise/lightmap_denoiser.cpp index 6978f2a8470a..6023ebf05a2d 100644 --- a/modules/denoise/lightmap_denoiser.cpp +++ b/modules/denoise/lightmap_denoiser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h index e7f5c23637b8..21d5ca3f328d 100644 --- a/modules/denoise/lightmap_denoiser.h +++ b/modules/denoise/lightmap_denoiser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp index c9c90c3408d9..5fe835a365ec 100644 --- a/modules/denoise/register_types.cpp +++ b/modules/denoise/register_types.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h index f0f1f44bfec2..516a91b134a5 100644 --- a/modules/denoise/register_types.h +++ b/modules/denoise/register_types.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/lightmapper_cpu/lightmapper_cpu.cpp b/modules/lightmapper_cpu/lightmapper_cpu.cpp index a49652162373..3cbdc7dc9efa 100644 --- a/modules/lightmapper_cpu/lightmapper_cpu.cpp +++ b/modules/lightmapper_cpu/lightmapper_cpu.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/lightmapper_cpu/lightmapper_cpu.h b/modules/lightmapper_cpu/lightmapper_cpu.h index f090ccdc2fe2..eb9a3a33ea09 100644 --- a/modules/lightmapper_cpu/lightmapper_cpu.h +++ b/modules/lightmapper_cpu/lightmapper_cpu.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/lightmapper_cpu/register_types.cpp b/modules/lightmapper_cpu/register_types.cpp index bd56763e85aa..f5861311b928 100644 --- a/modules/lightmapper_cpu/register_types.cpp +++ b/modules/lightmapper_cpu/register_types.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/lightmapper_cpu/register_types.h b/modules/lightmapper_cpu/register_types.h index 11594184c6b4..3033e838bee2 100644 --- a/modules/lightmapper_cpu/register_types.h +++ b/modules/lightmapper_cpu/register_types.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/raycast/lightmap_raycaster.cpp b/modules/raycast/lightmap_raycaster.cpp index ef0eaa3694c5..2a0a216f7694 100644 --- a/modules/raycast/lightmap_raycaster.cpp +++ b/modules/raycast/lightmap_raycaster.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/raycast/lightmap_raycaster.h b/modules/raycast/lightmap_raycaster.h index e04286f40c68..b116cf6466bc 100644 --- a/modules/raycast/lightmap_raycaster.h +++ b/modules/raycast/lightmap_raycaster.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/raycast/register_types.cpp b/modules/raycast/register_types.cpp index 8af3c467ab24..b6799ce62969 100644 --- a/modules/raycast/register_types.cpp +++ b/modules/raycast/register_types.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/raycast/register_types.h b/modules/raycast/register_types.h index 341359b23708..789604a4917d 100644 --- a/modules/raycast/register_types.h +++ b/modules/raycast/register_types.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index ecd0f5a85e7a..425857dde42e 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -370,6 +370,13 @@ void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, Vector Array bmeshes = p_at_node->call("get_bake_meshes"); if (bmeshes.size() && (bmeshes.size() & 1) == 0) { Transform xf = get_global_transform().affine_inverse() * s->get_global_transform(); + Ref all_override; + + GeometryInstance *gi = Object::cast_to(p_at_node); + if (gi) { + all_override = mi->get_material_override(); + } + for (int i = 0; i < bmeshes.size(); i += 2) { Ref mesh = bmeshes[i]; if (!mesh.is_valid()) { @@ -385,6 +392,18 @@ void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, Vector mf.lightmap_scale = 1; mf.mesh = mesh; + if (gi) { + mf.cast_shadows = mi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF; + mf.generate_lightmap = mi->get_generate_lightmap(); + } else { + mf.cast_shadows = true; + mf.generate_lightmap = true; + } + + for (int j = 0; j < mesh->get_surface_count(); j++) { + mf.overrides.push_back(all_override); + } + meshes.push_back(mf); } } diff --git a/scene/3d/lightmapper.cpp b/scene/3d/lightmapper.cpp index 839186299b93..9e5078ba95e0 100644 --- a/scene/3d/lightmapper.cpp +++ b/scene/3d/lightmapper.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index ccb08e4630d8..720f95a86460 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index d5fcb3f230f0..7d6dda10bf02 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2446,9 +2446,6 @@ VisualServer::VisualServer() { GLOBAL_DEF(sz_balance_render_tree, 0.0f); ProjectSettings::get_singleton()->set_custom_property_info(sz_balance_render_tree, PropertyInfo(Variant::REAL, sz_balance_render_tree, PROPERTY_HINT_RANGE, "0.0,1.0,0.01")); - GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling", true); - GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false); - GLOBAL_DEF("rendering/quality/2d/use_software_skinning", true); GLOBAL_DEF("rendering/quality/2d/ninepatch_mode", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/2d/ninepatch_mode", PropertyInfo(Variant::INT, "rendering/quality/2d/ninepatch_mode", PROPERTY_HINT_ENUM, "Default,Scaling")); diff --git a/thirdparty/embree/common/sys/intrinsics.h b/thirdparty/embree/common/sys/intrinsics.h index 6948e4a74212..58f5c3bb4e65 100644 --- a/thirdparty/embree/common/sys/intrinsics.h +++ b/thirdparty/embree/common/sys/intrinsics.h @@ -11,6 +11,12 @@ #include +// -- GODOT start -- +#if defined(__WIN32__) && defined(__MINGW32__) +#include +#endif +// -- GODOT end -- + #if defined(__BMI__) && defined(__GNUC__) && !defined(__INTEL_COMPILER) #if !defined(_tzcnt_u32) #define _tzcnt_u32 __tzcnt_u32 @@ -419,8 +425,16 @@ namespace embree __forceinline void pause_cpu(const size_t N = 8) { +// -- GODOT start -- for (size_t i=0; i + ++// -- GODOT start -- ++#if defined(__WIN32__) && defined(__MINGW32__) ++#include ++#endif ++// -- GODOT end -- ++ + #if defined(__BMI__) && defined(__GNUC__) && !defined(__INTEL_COMPILER) + #if !defined(_tzcnt_u32) + #define _tzcnt_u32 __tzcnt_u32 +@@ -30,8 +36,14 @@ #endif #if defined(__WIN32__) @@ -32,6 +45,23 @@ index 3f0619cac..6948e4a74 100644 #endif /* normally defined in pmmintrin.h, but we always need this */ +@@ -413,8 +425,16 @@ namespace embree + + __forceinline void pause_cpu(const size_t N = 8) + { ++// -- GODOT start -- + for (size_t i=0; i

      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

    pXCh$$Ffz(N3_|=_4H2sADBwsVo>= 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

    DltK%oCPF3?ZacO6qT;c1hwBz+|&1J-12s7 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

    MzekM1rttbF^yaS{0-m2Wb}Qe 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

    oyFN{EBJ zEW2~CkUa1m!`_&z&hqYcWO-*AYDp$RM%^{2>HChN182y&c3(JPA%mM-Ie7X-*ty-5 z=EI$9(d6$tl6bKN!v(&pM0pYJ*YE;`P6f70c^K>VND-7|mUH7B2Z?s!RroabIn3Sn z9k|)$`00-Zc~Eo$H7i;m@vJ9xJAECMTI*5nVw>cXD;Pu4f`he3XR|$`vVsqe@nVYj(~21=lJrb z0dMiP9##BBSZqI^eU|Kq#$MIBHDeg2TxS4B;pMXU; zhyNDx7VEubd7s@Z^XrWz?pLZMi|QU>QAs%XSjEAL&W$u<&O)%?Gn|#}@q@c_&8U6+ z1IQb4BAd5}aMP_Lpge9Z33aS0RUKwa!n#X@F3eic6|&85e|~|s?oqhVU*Hy}7_gUK zC83vl2N_*8NG3%2U_-VU&iN_^&XJ>V=X^7W(mKFD^!-b=&-e(s(;2=Wx8cFWLR#Ia z4QbL|Lhxwxpa?VDW|<`1zr`N z>2-hp!|}Zv1y18=)KXZ1$6m<@9FF6}spJV0``VU!HUDk-?R7KA?V@2cc?l(2a~;w8 z;1K*6uK`7N32-iT1JTtt=5g~dUic(hzENK=E63n&x`tD_8jO#U!%dCzJn1199}>P% z62y0VW9k$)KH^Oz)Qk$oSwiRX=EI}dYPk!RTcwe=i!V{ltZBIAY(Cc6N21ZSAk5!z z7A;eXQ6|O(@4j-xlI8K7Yob1#cx64_-wOCoU?6@9pfvi-JZyICCIy+NIJKmDX0P5v z5-6NIdmPc`7RziCnExs>23OX#u@%ioI3&V{sHiRO;|~T0Pufgi7iea(R-KRZvH+T z>t8pMeH{<+r_=<&G5ku@HoHx<=EN4b^1TE8JQrb?{0->)%|rQUc_{u_hwCN(_rPA2=&Krf<)L5cm+O%8u@W>BX$e3_~$Ls zbvX|*{-!eXAFW3%`NJe!XFmiUtb`m-ITSnFO!dv@!FrVzx;{o9uDU9*S?!6Cw8xx2 zoz@8dow*IwrJZ!@3P-5YQ%4=0ML23>ae3Z-JNk0fX)CCnW!JU0(NAk)L5!N6uK zEScF2=Is;N$_8^gsjo77^qcFH`u(P8>gJ3t_8wieK!}Y?~mgbh3N3( zibMF%G>-fHV-~MxK98SpT8(wSBb-Bj1@IoitYO+&1vX-(7Tf7N$b9^rOG_%Ng-pjt za?9oldV8qx?(1?fVRQ#^iJgpXZXdXAOG<|V)%lfQb(pa?O}Lt6r|?XBAnDxA(GB7G zR9ih5hZ|K3nUz_%aIYzp%_<{}^*N$#&cUF+|1KnNHs#Iz%^|z=0yKnb@&-Gc;OzZh zOx~ja{94gMJ_X%|C6`9R+{|RSvj07lBrJ{F7WfQ%n!W<}w~Q z3nx}N^I22UY0R}UY6Fg_xI-U2OtNTsV;NOFa)d_x9L+nl9L5``#)G|?2Jt8{EcKNCR8D8hq1VN6t^4OqWRgqBARd zNw3jbP&(aD?lk3+Q^wQaqUT%Mt^SQX*|r(hFKZK}8UKP}eh_vJFfhvKFfqINNKhkW zk?E%uAb-y(=EvLzVD94qlf$>MEB(aSx%W73r`JhHI`9JGs*Ui!S=} zH@S;Z3+arYVsIEy4Yh8km~r1vg8zm*7%Q<5rxvZpr-^;A=*BOa9GgwvKM_Z*+n33y zi+#jOtQF3D)PkV(X%Kbv0`+7|$P@2}a9SmejNKaxua=Kzi=(c9;)2xjEC1PmVb2&a zT{Dl>O5MTA_EKmlxeaA`axnUD8hsvU2wSBECcnXEP}k0&Up2NsjCdjpsO}JUw0f}o zOBL0=z5#yN?1XWb_QH-cPNrXZC$KoGLe0&D?7q<^SS)o5X8v2pwYo|2*F(4AZ&r~% zuX+;Gs#W;LQzrZ;UkxZMKaH6?SMjRXbYR|r`!FhhH?63VfiJ7(@btV1ze|{HFQ28% zYl}O>yDCX?Y2h|L^t>#;StTY&D+;Qez!3gxkQHbE31b`3O2B9b2y2-V+OlRvp{F&%ktYF z(qQD%57cjdDSiD}g;o?Oa_(o<`BK3f5Z<%~u0}V&&dIkhq$`q(-Mt=fEZqTc#|K)b zEQK#OGU2g>HoLV^nw4(xCf4f;sDHv;ZeFk!4rm54oAp_cwyY&?nPIG7*9=zIT0}k$ z9meM}4di^f4t2fnO9!kXh{@L(kg{8Z+vDWQN$M7AZ+(-xE)J!N_NVFdL7`u=WhKe6 z@e-IMI?%WGA5-j{BC0wREieOHm;r?nu(y9Cbk2@I#(+9L8T5qQ<}dK(21oZ-9HI`g zyD>3Hi`UqV`29~LCR|J+OEfAiecKaJUaXz`3hX7xn~#CWMgoVgpGHDr+UPT>yC`!&jnh;qUhb^{+Y9|{{0V;e$q7ZXkjk=OxTZ`Dqdpyq-zkF_FiD0B*LGE8dS7* zkWTmjWV}NRItZMw$A!Of;qMXbl|OTE^%)hcl<|Q|^)8$+D+6y-AAx|(SuD2~$!isL zTKGqS-F*H4+#4;B@>4va&vi5(Q?&sqQx)L)0&x_jc~E<+We^uwhd<4Kp!wqns=o3T z8r*Q>R_vD*IO{4jeOm-RZjit`Y3^k1wApxJ&pn)SRFe1iGJ&p$N_uSbA#jxnBAhva zqRY+Z+dbEqAIfzX0!-4@W<-rSO=k!jR7Y z&`&K%6mv}i8yb1hH}`&~Ud{mCIoWYadrh#RvQgABxDr49QlyH9UC60SisEYPxasZs z^!kS;5_(0R{xr>EWH!H}dD~R!p)$eop>r1xt{zWw!+(-dPENR2=t2&t6u{wAgjB7! z!Do6oqSowwS|KVTgDvv3xMDBK?otE==@J^&_>md6;Wr)KvJF0552jt_U-8T>EzEa0 zj#1v*5=%vnv`K%lcNV|h~ z<5=!R@mr!@@&KNkaHdYHJMqETG)CdVaolWp0w2%4NtJv1iD6m~RDR`PhP)~N%4ZV4 z?)yi~lF>&4>3H&AR5ZD5aF&QWjpZ&!+Crn-1Rf<+sm84#GB?5x*N&G4jfKNt#J@6p zDVfi}zaYB9`5`fy)=jtg24m(N7OY>a1Pcp+VVRo*qdv*8X9w!w(b2_tcU2?$8owpJ zr8nXD{)xokNExUFoWP>#SIE`z&$-N(wRjodmTMqRxsCS`;*iTD4Ec_dnGD zbNZ>A#~AkFFkQHrr3k$3N03y%#x%{2U_2!M!mYe zr<@=uQX7iJ!tle9ccek#DRsMXgDf9uN|yKfqiA3)O08K6z42)%Hq)5ijEKg*ZACQt zD^E-7t#C^AZ1DG5h+NHh6MR^Qrc8tK)eNu2!%nGEdEXZ~yf}Gx80dc<9p?J49oUGSk;~PyNq(dB9 zKV5>$(d%KMWHvdsAPjC3S$r(&pt0}WAt@~lqK5=dpw?wbmbM0q`6cis%n+QOYqAc7 z&GhWqmyGU!BAvz0r0H@~m?tVZbc6d*u4>SPwCa4N0dx#McK&>nKK6qq)vNH|CgxJx z)EngEqp`e^#eM4eEF4yi>!$iUQ=l|^7CU&Q4m%3+Fjv=@_zUL;`{H8idFKa_6PMrv zc^jIMGLet>jllcwzHm2l0re||UGsKXx@7eWqGa(4Bjsc1oxe8JZ~F#;f7w93{8hqH zS{ns_z(ee94W#ET%!J`H&%viX=l(B>AwwGyx!yZ{Waw@e%szX9?hMo8e2ALqEgqT9-R1-4Zfj;nGJZrq{tK?#l9=_)$; zJ&l-pC7^EDHBs&eUDh-F8EpG@1nA%_{I6{kd|*}Z2Z^9cYpYSKNEfD^=wp&j4uiAt z572SCA-=UehWdV=xSrL3P6dkmm)^^;N6LxhbTy&hQGtmUmWC-?%;8s>CAr@dh^Kds zfgW2~yg9j^m<)D9=S)TZt$Yn;Y!LF_|2FVHs=f=J3O9JDt10Q_E=zf5VJ}p z$=m}W5D;mH50Z5SMsEf7X5`AG+yvcP(qpX2 zzSnRd`hCOLfW$UxEcjC_x3c7r!6o#sn1e~zO2}2IJ-BAOJ+L`%z;jqIWKbWX<@=l{ z&wC+vqj*9y&H!%ZL0hewrODRo^6=gTZsu*UC{P# z8vJz3CB;)763@6vRHYzSGu9)R+n#SpX?!e~7^lu=nBRa;4#sGiTtIeh9?jbK zIe}WRBKzaqJop`X2Yj0>S%Yb>L0jMjOmGNI(_3x!4f&T_k^wUpFPW?xNqr0LDU`~ zY3KuQ4L^BiZBhQgrDVpe58jYP@A)Hxa7|k&rMlbj_kqWBIALS}&Rj(BG?~;evk4L}( z$xBdFT>u-NzNL{WLxj%|W?M!Z@XrJx)7IBT?~xr~xc?ewcX%6B_trpfO%s?|-h*BX zyNR-n3_c3nfVXxGBYkfosLAB%`~uHMRK4N^9?R~a?&FB(}D?#q_3^=}OJ8v;_2fZHiip+6a!)K3_CtK8A;A_fCUhUZl7$^P~?rXGhE3=2V zJj*Up|KKzs51NIZ#a_m7ZX!AOJD-lUZPw2QLFU4aHL;*=p>vbca)JpW6- zst3|*QU27uj3Ki^GQiboAw0+*2HSF#*^|@6$zX^T{#y18<~H497u~deAJ;w!j z?=Grnqy}bbp|HBxfph;_4L^mh*(v{p#5U1`I-I`^vOAEn@=r;WWFTxmqX;usJ|jcT z`aE?hW}NnoW$$*_@bfChVAs-inEKrp!`d$3RlicWEu}=|fgVfotPxwuH;*$eXeDJ4vq*67E%DjGs;>Wgwquf?r!hmRBZcDr}r{9hdr zQxkfAzqVknP9Jghsx6;u+z2r`92i$P;w!5>tQ%_qtDhu8v&2@qm#riRmE~a|W;XN1 zV=#MVK~x{>*EUZ+#M(V1A$U z30&mMi5-w+WC>SoIqFy@1*Mmq;Y7*^CR`yN4hcEsQ^FlvirWqzpOsy99k!C|ddjy-^zKPwnNen+^ z3ipB5&!qK%9(B^Gqj{q>(8YH-@00AppEz`azFZ!KoNf{pe_BIk?42QU8g~GtzC0(2 zTYp05_8K_4S8&<>C(McKkHUp`Q%Dk6gIiu1GxM1=@YpBL$Ft|~M_L*6-z~#6FUbOL z7jaVGz7bdXkHF+*A7QEd2Yl#v8Fj>s$=<&&>GzUsnq45v`bI=^CS@a_-ZUP%FWU1b z#mjJ|mp1-rT+A#zA?P#LIm4z8S-@?0$cUX0=514x*xK~v>}`K{I2|C&TmnhQe&yqwH~T|SP}Pl{N?`Kze9eCEO2GYbeK9$o9~%2 z2A!6@Bw^_u#3d$>u5+4=yR;^OpK}JOes9i1<=;TvqCL>7J{&+@=zMw$9h11#*dAJq zf6}KSQ}-XNe(AtpiU>h>@)i`W^wFX)7^m5eWK#scr}>n>U?h(ud!q;U$i6~ zvm$SS%WFy$+)`o1VWGDeT0$qi-^GOF<)FfJH~!7cBzotU4{3_Cz!CpC=(;7xF#Aa? z{Nh`oDX|?#qL9nW4uTc-qj<&Xqp6lzI^+g!VZio|;Bk=?tvk2~``0C6T)+vs=es6s zSrJS+)|`ffLNl!WBZQXXTIh{Gp=3;hM!8PHZv2$uPTs!=6Yf`|p_UfVS|!9&s%GTZ zOGEzH#xAnhRs~I@FBAX!k@Um$P_%hoL__ivNzsEm>Rua+a$yFX(uzGe&^VtY)EJOu zI&HK`lt(fnrg8iIa+y)`#<r;L+=_wPTSG@uiXmcckrxk4HwH29M%ZbEGSEEGFgMZU8I7Gi41M+aW-167P3C7BqFb;7Vi*4B0H^ zm&hmK$-@uO{kxQBZtuHgoXPExDld-!Kl6yCeIn%}$Cir?-i zxXXpkJKfM_dc3xYTr&G$I{#l!`E@P`mrCv8{WLoNiNq&?mG#Ms<#H1D|_!I$NH^)&!?HnkcIhAV^ zUkA2V;_=?Lw`6z2YM7__7B1M|f&_Mes4f=HFE<26QJXVd6*Hof(GGo82g;Q)4@2*c zN}B%WAWAwIVgGd{ba|Wwp*{21B@RMHHA{jG5prvrl>^Xbp5O~OpMb_6cH_UjQBN1N|a&MN;%riO=A5#;e#8?~ZuJ#jV_f{m1ih z_Nwi$Y|cIWZ_@*aJYGtw$6m(xSrgdmM?0}OT8R&6Jqt}=ERfOYrm9cR(xgdAv@Mbzlg=sPYXb5?lA1WZoz1( z*K*0f6Y$6tZS?JLWiDJPC6iu?;q>iuL}4*Xm?C;j1w4-FQ*9R*QFjLX6I#iT(Ma~- z_cp2_%y*Bk{fI4NQ!!M6lFvkqedMtU-j|($;Q_X!_E-gd{is0j9jxImTO0DvqO+*G z=K(rHUL4;Q)YD@h-0--fW7)(4c@nxPTXbILGI_f(7TvqFQ2S#(=i)}G!?qu$EtSFC zZ_SCgX>AvlR?0$WmIFRNJq=Tj9s#uucQB2Whpd4;L?JPR+J0wQ!4A)^^Gc;}502&k zo}Y5UjPFg|S;FLS~Knd1gT|}Sz8khV0+s0Xp&%x@^yRkg9Psm+} zu@8a@L8ZnVE^D?@O*g>@xaK#>G5C+(U7ehi1| zqgk;BKI~lO@38XWJ8%mg4I$dn{Mfo|IF;DM`I>vt{s<+0UhF}>U@6Bf7-PV0TX7wN zrf&nAwot0$c?PDBNJG`ir5LmFA>vdaYqD$*Ym*AmKB1g+e~{(RR|*XCPYdw7Oc-BO zV9a~zoy6_=J9+spIt#IIz|Tt!|CyFIiKJAGeB|#b~it+PdMF8eu={ zJrOO^tHA@_1P7&8;pJopkbZgv+A?yXuJUK;4GlNk(K?2T1uHP!HL6&p89-jxpTgf| zJ#cM1;rFU8=g<8rAs1H{a48j5m|i>#r7a|Af$tA)$0Hxywl5BiUVCG*OdB;}He#)i zLw;scM{YW2VqK;zUl`Ru;!l);#QRLx9GL+9rcHF-sy%q*#b>%!WgXsXR7X&BqMMdZ zf!l?$6n7iqM_*ywQeq3~#U{wSZK5rAC=>eb7%JX6!r8o>2M-QUp;lvM`85#&3o*U} zJ{dm8TZhKb#g`%n0cT2Pg19{tzuMXyENO$3k2nIs3wH_b>ermk zSr-U!e2#5(&eVg`g1a7$a6rg3ejWFo9IBSYMJ96m1p|fM~fSWkG_Gu-oOv!;W%28-Obv3W#9L8JS z6XP%4mSBH-MnH4tZ^lPYov5sv1sY)qTXmfh&*oCP0O=d|}+ zmc$hl8|Eug_I^SKo5c9aCyv*eMx=?%z&{!C*nAZzs#q3_lbXv2k!E$;^giC zw`GFhWqE!RH}y6`$-qiHSg*kwS-r*u4R@fP&7eci7c;rrWqC&l8U9)JEchQqXW~xP z+l65=W}XrXkxBzeDeQNh3~3+=p-2>I(1=u0#*9gZGG;0vl|+U8t{oZ_QIaO9)K4X( zQW`XT`}+f2*Kr*0+3#Aa?wlCB)hNk1<_ow3`hW5AKp_Ma48y;l*GQgXA#s^KPS9oI08w@mATKQr z0yi3hp}ZXC{VGD|*it$oG6UQjpVFs6CW4CcZ}_T@=bJj;C%!Yy(Wb+K)?YA&nRojk zu;B(-CTO7_B~)<2cm;fRXD<$zB(sa1bIGF8a6Ife8$a2~0sZ9*nj_)ZJebbUCb-`(irY|=Q#KnFAr}0$6?p1 zE12xUzt&foaP@K?{SmVn|CpV|4X=xF(u{eW3IF`@V_hT_(YT0P45#3)r=OrcOcD&u zK7ps1E!ULqz{Ob(WB$tNoM2of$+TAI79TDEF~&eJxpFsjb-#iaMz0|PzF}X04w$Ug zz)72D;MEi2+ab6ovxz~ngRFe-sn_noe|2eS^t%8*eZ#37k-lE79wQfokpCN zN$_Y=0I6A|iYm{K^1I*FxWK@ZYD^nPviYu^e_k59Ve1kw_Gh6Ht>BE|2l8TA1S0ue zriW`F-cI3jb)kB&e`^r&(rRNI7DdcBF2*ZH9OJLApfzk{t3SK;h&eOi+!hL2v2qfe)7 z#5+@-pzFCyIBoJ%^oz(urNyea`O}ZOlz@}?cH20vWBE@Uu{8o&zJF2j<}%aO-~{cZ zqJr?v2jS7k3*5O>k(;ah2gTP(Ae2sro7}+!5H+?C$L$y6R;C$p!k?$o8`>+dUZ@-)rwCNcvvG37 zthzPcL0rHICGKzIdXDiZqGm4-fqirpZc%vxxCG@*RfNBZH*ljT4Oh z>&AO7CevTPMcF&18F+N^Ir?bo6KM8NrOx$EXv^m{>^D9oS&1t!!z~N_m%8CJ`LQU! z?KJFIy@+a+{h?+(^8^#O9HF|i-q9xI6tcTYl)HGh3liV%pi@=nP+i>sJlvS3i!>b?0As~7lQLKghmmXhkHskrBLDf@({Q7rwUPK>qXK!=|d_JlH|EGnL~ z|J(pS-w1>Kff=lFqZ%|m{zN8(A0P=4^<>iBZYKG%0dj}_(8iH=JSek>Iez9O`RFNx z8XGjB`|C`i5O*9mM&G3>#@6^_Sr0}J#=xYCP_QUVpyj@fuvJ=2Ff%!u^fXzZ%Xw9< zb=q6(VWq%*x+iUo@gteBo|s+IK_*^!gH@B-(RZFT=1&_T=_9{!-OdiIKVgD}F_PTP zrP^GSayiP}&cp*BZRobZUF5uE1IB&ah>~9pl9My_uzt@9oWwIh-`7iWMOV(zBf{rF zK2EWD?HwHKth z7p>z^VNWj&mh(bcmu2{8=O)yzEu9jR~1hme1NC)`p{zMRo=A|fVrjL=!lRxXJDnm&CK3xbc$H!wI&)JM#bQ(g# z^1#+X0w&K9gRAB~5VSb~PJPd%mv22I^KUuw4uu!|9$*GEm`;YlX{E4Yp(f1o_oe~U zCg3c^V>H;~W!;q6Q&f4Q8RQlmflyBioN1SchOXKe92J8n*MG*_J6LqtFGX)oyF_&6 zH_^4WQrzOjSII!)6O#6=1|=@UQt=5v)I2+i++zK+xOkJf@EXv^Qx-jD zd0?onD%Yi-1f|s(q(8C+`bzoTJMRNatI{V0k4s?JX$e&G9ffC4&B6NZbtX})8nXPh z!__tXgj|95Jr~Q+vJINVf#*&gA36*_m3D%6-#v2dcqp`%b`tGtXX(40S!9`!Jl|Wl zgln^3)2o-yu$qRFGwDJt~e zC7eHU9-FWJ#o|0i-h<-B9N%Mz^Y#@|yrf4iMh9=ZEze3PG*-PI?%B4r$<&LV_^`rubm0E&5mAWR>&Ik8c>2mUVnFF&xY6bbUWENSbn1m8F@|d=?mfEYc z?3w?fcu)EoOr4#>Wo8mfxqZ%4&&ap)6Rc&1GgkUQa~- z3BiKQSO_|33zDsBVB46??3CF-&*dJ*nSFde(^?g~qq4BDW;4!SD#V!t-DJn5&A}i4 z61k0ASL5NhOQ@wK$*oGWpf49JpbOKxsZ!Y?c3+<_yeg=LPCaw+GV;x9EQc^>|rR8Ol7bqOQSK-Zfv0*BuK;648U(xx%PAFojw?@q?HRmzm{t z@i0l`0#UDPBBNJ1@Mxce32#{PX;(4omrdiHYZ`)T*;Y37Qykp3@(_eg?87HPrleK$ z012IS8Xwgq&>08r;FkVwh%xB~-O8!3V%cJxqb$Hp5iaD?x>qRk?jrV^=HtuRkHJnp zl)f5eQS{w;l#-U_3Y+F}O<~2D{q-`sCSK;xP%ECbZ3Kn&V)W^pIou&>ACY>qo?%LaQR1R_?&&1c>Fxc8m@Mxa#4B+Z2aOJ(4!mUTGiY$ch$FbU4Z`M_QI z$@H|%HBi6UL4793fmP@x^fr)#1cTe==Th8>$zdb%WyPkgS=p&dod>Myp7q*KLMBW4n`CXv2%G3 zS2k(DcYBga{23j%EWHivw9~OQ;Rj~koPrvWQAAhoD5fUW@foC*=7%bJa5q1j+vL_s zZw!v1e$!L%(<2GAEYj!dzlfrks|mi{)xd-vi@|%%i^##B>9l3hDja#?!JMeHjOtayCP8#;5-hkEr3}Akh5SPe5uP+#GrkeNXVQq{g zjyK!}Yf490UE^o;@}?>@JekFuHA6b9;w-g1x)>~uXOWs^l5A_3I_H;Pjo)Ya@L2^D zoG{G_eOs!63`Gn0nv<|o3By?5|*&{j+{wLmBBU@TZ;O}P8((Ut5h7W zRRlHH`$*%VLAYGI0Co&naY@%IC~^7@aW?N@urrqa?OhI6WMsi`>kKH44&%?QdT3B} z0JmKhT($d0yzp2Siu0~u-)I-xZ?zj+f2zZ?Az?w^q5taE*AC(NGv7#5%|90SdvUit z->Xnj<3dhkW3@Nm9q+jSwp=G%zVFHXw;jPJ?GL=ZC@r`;`WcJ-Uy!!CGPN&}x=cH6YSeNrVB9bk_%x6?x4Xc1iYdM_okFl$7Io*>fLF_JqQ7G~ zu2Qf=!}SbRiLiv|-t{OEHbBImYd{Q-?>D95T<#bC4*Kmn9-0aGW?wSIoi*UjteMnEd}@T> zw%ek2{$y@D!!r@n!_ncy6i(q6KQG>=1!jJE7{YF+PA0{8aMv|+Tq?&U)GBikI=pwn z$DWH;dxS+YdE{uj0ggC!;7dtYs1kbvx6Pkm-Ck?BQ<5Z@UV4Y}5=Zjf_Y&{{eke^W z1CbjkP&KE9;_4D|dI{hq$isx|5kyY&80PUDFu}(~A6B)xgajt+fdfHl=%VwU424w@wS+*Z%dH`L4q4=#?lS7F zlED6oO~#%VuSsvQ5ZAdp6*raZa8A0@>P*hA!RKE2)PEY!@K8AnlVBZauY3er|4C4% zC6W;NH4`mDY?kU4O^Ir93_E<%Gq8`|HoZ_`F8ElT1a*qe*1ujt6kzN(sIECmwc4dw}%5Kv=Q8yzcpb*3`Dyn;LI@OiPxe(1Fc` zSgtvjRna*??Yh!%o}@Q6OLo->wZt=)FRtKtvl!fzQ$}6m!nk#^(cGWMqMYq)-b0@B z4V!&#(bloY=QdoI3ks>j_W4k&kL zK4+P~lnoZ!LcSehLD^`9;P$htuzvxOi5^Q%tu+f3>19r zWQFSEP^5VfH_w|)yH`!e%!1v>b<9WeYkj1Dh8N1;7VtTcH$>w4FL2zF!nRGB#Kv8i zg6~Tz=otxB)HF`QpoS>S3!Vlt?ZN^X#~QMyVI_5qWk_w5Cik`65VhZg@^dJTG5chX zb1qx~&mTrGuW|+7mtwGCpDp8+@{=fR(#PfUJFvZ9i!=NrPTyUYK{s1P!Ei5Pn`t%u zbEX>&>qlw2`8B%YUIrd~q7EBr5^dxAA-zidB>LqRs^jsHxpQX?Qr#%*ZrFo4L9;=6 zdn6Tw>-2_;tiXi0qR8ML6v+$1qx%=b%vbj@w6O$zX&k=t-cMuKrO@f4D!Bb$4n7-y z3)17j?;i;X#)g}4Yx!P(wR#9?{4E1si!vbD@HR~rK1nCL9fmc6qoiY3E@fm+ zv5~#a5SS{BlRqWlso*5KUd)t^;~fHXBQ)S}!7g^>b`>W6E{6oqxq|qDmOA+{d{;{F zAJytrfji{^IAv`Pp-Z*6eW4m$QR_VVS}dE++4q?}Gj<~U+BglD-HAb;yAqV#FC>*C z8e}-V4>u;qasQ->=>+2{G{_-1GI)4c| zY>r}+rngg}AQg1x5`lBw0=H+@P}_gbY=!w9(zR(0PCkBuQxaPx&@)yOcpb1o5%Wey zVb4)0UsKLiG%d!U!g^LDHx^A&^1ye0BELV9Clb7Y#Yp-y57;|L1)3I_v6}O% z=}6gjw(d?i-Z(rTR#@iY$fUQl{$My*%rA%GM|GsV*qpocIYS`#U0!fdA_Etk?}Wh! zpzAk(#l#9{ZjJjXc0-I3ceiaJGN(Ub?}haZEF2;&ulTr6WynBV#|j46uh$CpXOg{D7FV3IZK5FSyO;RvX5& zz@8rwoWA~CApBVq{I(I;Cu?xT<_$gkucEH)d#K=Ax)*ysHJ`Q#Z28XoIXK>Sj>zh# z*PS|b1A?DUq)nGcK>oh3z;fOm9G`L5Tyc*FQJ?2XlUK%r(wuHo?CPTj$E3h+Pl^i6 zdc30R%01!t@zT4KvFWou=R)K;u_S^&h#^jzlnCK>E|JA6k&;94>^HY*wEAu}{T^FI zePT|N`Mh8BP^T}>M;p3ysyix+n4s+FMRLV6hbXLdLWOHRc)cM3QalZjvy+6KUcgm5 zlw;q!v9Pqd1Q(}N;A8V;xLCstW}Xd%t^^&@UaZhPE%~_d_-khWbBE+Tr@gzC*j^S?2dj1k-+1Piq_qH%-+xx;j{@>v>%*{Rlo-tFwo12V|hN=Wyw!qFZUQW{E5fq zy~_ll9~=bNj=BiS3nF2bP#NjSpN7AuF6P$$Sxojhm~x%hirFen6D$fh!$W4f@WeXyc-RJr?22>QYHGUUY()arF^>`@N8EA1EWM zPP?Mmj3DZrv6NR?nBEvs@EA~_2Klk*!$0r-xa3PS!)D`{!2tYTIZW?Z ztK%2BQxL12O1@+{f=|mFI&$(MyRY^&UE`)rm12e9`A&PhFY%72>Lo!!vj%<+)ke9* zHKgrX?X4@$Kk!w;Z_ z3##UzNi5G_eYgN~T+g!oLD9H%UNd@kh~U51!e%}F)x^sT$#ugf=2N-`q)xp^a-vGG zqhK!8IxGQA=DS$lR8ORM?&Tq4V|>(nlbO@rM$TCr!t;p)@^{&jt-Ql=o7-s`e|HWs zKI1_LhX(16p=x&Us}qLpk6@06#1dueHJIfWPK#j;4 zQYaPHMIRrDK*hEwY&&U-CHIceS>t5!+eBq(5Gf|@3nOsahA=|D>_)$J+fYAa8V)Eu zrsg+gpz+Rp82i+3KN?Xac!DwW@nNP5%~z;XMVy4=@O;DWq5RM;evngxAzuY_wMv-$(s z*HT11-xx#b@I)G!P(}}bQljnmlF|OR7M?vd35R}fBpw-obP>idCyhdg0tPU;}#cWyC0R7f{gxD@~ME}bg$iD2P@hz(O zB65J)8}pKB8ks^&k4QsN^e)^UDvW3H;%U~M<)CJLg7ywY;#ZzK;WuYLz(~)E^I;>9U3=EGQ_f&fP2a8yH~66 z+t~dGzhv>#+XXms<&l~Bk2HYxLM)xzWFGz)z&<#KDD6)tsYDq5@x3CS&Io+9;5;dv zBuBHHc9IV_9l>CQBe;d#XO_sVBk?Xz=>D5J$hoA_*O{+s5BW&r`!+3VsWltjjWe+0 znj5-4zDMg%XkmWC1iElWJDPvhBV2hU>L>DZ#;s@Qr`M^tUN42*wwi)z7j0!{1#j*yFzw;P5_V5Au z;JpgxjtitAOIwL%t~L5i55S^TLnJpL1~%iuU0KBo{VhOsmD)6A(UDB2KD z!WXoW)J2y3yi*7-iu#ign>@P0^93W@DFW5Z_Q1}71WdpeDl?lVNm zsZzA2ZadCMaUv%@i)hq+0b!T!$LrQv=0oq)P~=zvz4AAWO;W8V8P{%nG+Uo zWV+2((gJg3h&N@~i_N#0u9rJ;ZCW`AZ41Vu3nWSDt!>oFcM{t8>Je3$RnG%`>|fR+MHPz2Ws>{Vo%ASCM~y9;iNk@dsClG- zw3!|-k9qQweRk(Mb;(bqc0I>Yh&7?xViah|W*yRBF^RNW@SittLpn8Vko`4P8nbpc zGqvC2@kzTSdiYe*YyUMxtl|9sUM1?H>PZYM9exu`~r$AuqU#dHBhS}krO{QE9Us|MtL+(L=tC}t<>2R#7tDu&QsmywW)@%GiHFu-VEW6i8n{~hFXeBruKtUX2k;#hRJx=>8?5JU`|f2{sBNdX_;{yZs4ik$pst z?S4RB%P)gx6?2L4U?TH#V>mv}%cdf{m?@yd(ER;%L+pR@f{yb0hGA`SyuNM|{*c{{ zCvx}DuhRRl+G{h}UGBvSf4@%NUQCDi@mcWG<1+7uo=j<1D%QTt#oZGY(B)f?;l`2| zbYxv5F57$ppMTB5cZ==F;P2n8(%~_jNrVeN(G=mV!{V@C0b zncErs#OG5K$|&Bfl@E!c=~=ojc6Z=)635f~-ghPPgB#Y+{0JkN3mtrZ3&e8Y6| z$!SjA{Y)cfhg=8KtLIFTRikjBjWK^e(<0YPz7dt?V3d#yq5Za+D19Iqe+A#bD~Fr; z{B$d8c~lg%texns5J$p_7}JMtH<BV!Eq?W9~(<$4rVb&@3 zvHIJm;uZk2o6Jo;BZRV-4eD3_v$Sf^~V;9vMoK^gvBexad^=!G(F;nv*e%Awi*Uqny!)Y zJ~p`Hzh|_zr-4p(tR`Y#O397$!Z2f!3`Dm_<1C)*7@=WNr^NH3>=vxX11pM3OT9iLRD@WpoUyC$=iUDY-;aT>rei=sb%chjp3Z-mvPi9K0{Ji75UnTV@rguTICfzs zjF+Np-TGr_J1ds&QWxO+;e~Y$b6(Nw*XH8>tqDX*#~)*E>f)eDFFi5u7c)9j6&-_A zh>6ux_I0@q)$H=2Z!0h3nl1mxqM8gk;r9c2VDA_5jaIRn`xxw4SIpK|h@lEUoAvNm zOYvX@nCSM>jeUk>R)-xa-YJIb<@GSeU_HBQt~+{1?gTe4O?cOk!i>%IqUy3D)WR{0 zmL<7^Tv{kEZC+FEE`4Km_@J`^if{^E{)l&jD2Pk>C9^0 zt@1gVI{);;vPm1zAnXAR>+r(nNgC)Vv=`e_E3o14XY)CUI+&xv@4%lc(S?&EnFG%) zcn6(0Jf3zGm)-;%Um}IXub;${8nU+YE2(lafX(V)zOQo=8dR)+B9~uva#nAsZks;M zvq?wq)CllSx!BvY7!=I zg#j!19Qh72#3du_FIPS%ZqHKdJ!gseu|aakC!XA!FUG#us)lac)Z7kCMB)}1%ou2o}){`{pT|-D^tm(EM;)%N@v~eKhsxLRy5tQo>WTB!sBgr zAY!KsvcC1q1XF*soYuyqgf7AJ!s7U0<^ZD|u$P*}YvOLLQd*y@L{^x8qFQ@3VTRl( zs+nbp!*-_ZDt85>y{U}zK@S#;GI5vhQgk?w4q^HOjCrA%`LBngbs_%N?E6_i+5Qiv z;PZAITyXrxvqfuID_Lpy8=DNhYF2cH+jwGJD1`Q%{PX@!Z_*&xOV5^-(N$;Km}QI> z{%t%;T9kb)DV8=Z?LMjM!A$)$Ap{yXvyv zpPgi2f?7P2|7~CqEWCQA=BtYpX1OYXvvCYn;LM2f)d;BiJs%uT6_CTZpV{Nb)p6?N zG2ngj1(DdgjzlSEGB)!FJruVZ^!Qx5e6bk2<kNKH0j~P72-9-(@sP%T+@7nAhFb&i{$69u4_OXb3#4dJjRjapA1Bs-){+I4 zzGQCjcK%L$ig^=pjNFP!s8g`lg|9l{uybITnm-qXK5(5OPyN5|hEFqgfx6>ks=IF@5xg8n(rza~ z)#zLD%cz?%BCWJ7+ZzSE$6Y9EFVQKGg`)*&_};h)!`1k`?XVFBJI}}ABO7tCNCXzn z?!sdu<8i)W8|j~r&iMRLCp0sgh{;Rizm0ck&(jiCU1cMP4dv4Hf-&@(ZaQ0bAO?dY zyU_WO96LC*lPOxIj&oi2;EBzaXs}!aT!&wgHKUDm;=&s=rS%Efuk()g8t`|VdS{H^ zE)LyxBIN8lZ&cf)N*j%&pj=Fh5!Fc{KZNp$ui`D*{!fl%e{_Ph*gBfTv!h=-ju#B9 z^7LvQQcl0fvK>)N{5Nxj8EjvRf?hyWAu4{@e|!Rj`V+FL=O4Ej)zZ4@EFzc3dM5 z-Yi8~s|4b8LmbvFTFD+YS%$ygJ)ko93Vj!c!mRgWVfRH-!bgZ%sg6gC=GlBKIw(oE zd5F;yGdO&5!p8imN+h{$wvV1xAA>a+u{8Ok1RgZhAV1FfVOF|3xoNPHXK~unPS>YY zzJDR)Y6gtcU$c4q-OBDa-%==eZn z-2_l3TLL?6cA?xr8Zp0Vh@GovGwHkw*0UrXooC%3KQ}xv^UK_Xq7%9ao2icBYtBr7i5Qx)Y%C;6 z3E^PVQZi-{A&Flfu@}>qv1>cSK(*+kxrWAjDw-Wm1&?*nHOB-yBHYkrTLe|pk%h2( zK)2nvYaV804x5f-(IhdR{cZG#UK8ymkqKXFpB?NVvp?6HyBwSh`Yx4Jh&-lhcYI;u ztxKe+{2p_1cn+o+95y#!FT}3S;@z)9Qrvo>i@5y~zk5s_!aFv+*PQIe%=1stuxAMC z#dRQkrv&v=E+YA#y4ho^Vrorg-62DG5>9L8dGv24QJpDI@%xr=+ER5BZ_nrN;Zt^^ zN$XfHWZ47U_~Rc9byVi+-86A@ZW%QUE+dx052#?17TBEoMUFYO)7$sD_&bp}+%2Dm z8b6FtOVbP&7s{J=tUN}q*rt)IaSvH9a}n(Fn+GdgH<3=>L-_t`H|uQ~hqF^2lKa|T z)HgGOK6E+CEN`8FODY>ka$_`(%zw$8e5hz%_9q4c^ms3PTp06yW**q3)Wd~NGhFbW z7%aYF1%Lgn(w{wql*~F!jbE;$!$smCweu=favKZTKW@;oA%B>WABMEgx&Jhi7?Ym5v?tHvddL%5?FB)o<2K`8JK^ zxfBImsWjQ9oQiA4(Wd;(~L|E5qzv9Kr)@m(&)xVqEb7ZcF;$0O!+`wa4`x0t$oxrmFusX+WeC8Rd`wA*tG zv`M;Btv_4QtwI?qgVI^y^>ay|=6H}%n#9%bIgY1RP~6!yoAdU4jcxhyw9yy1X*Q?1 z@0(4y>pPV>$Bmg-_OpWh=9dfVJ4`WfdpO(-{0MY<8%@s4MuiRk$X;#~ryT6XhliHp zJkgIlS2z>wO(qJ?k6DTRPH*W`y$rH^!bTS6r8F= zN8N79iM62en@$|OQi+e(Wnh>7Rr-AYYI5lV{~GVcFe;vBK*Bc|Z*S3t4MIL3y1E&M z&7;tBayS?_f2k9Q>2l+CPU8HU=W`vmv)ESgRq$;vjm8Fc5gZgD2LrV+Vv!kl zKA?#(ZQ-y;`30=W*JI8l@(ecJLh!#<2}g%t(?jRgL0-fKJ}|FI<)T(nD_lY2#vkJY zaNQ708TuxEA8DH30u5&i=m$$9m^*WXNod>(^+V!tb(JB#G}sDW4|_m!njHitq>!Lx z7s;!?)iA@ApM%Uk22KXL0Ulqu;Jf#tFUi?m{80*ILW7h&B z+{WimHcny~Bd=28>L^Br_&M$=rEYpW^bADpaVKg4!}yYC*lYAj;@kBV=(hO|N=(VX z%&u4X>B)J}x?)5-=J1{yvK*hBpNi|N`I%heFcb6526YQ|GwV;NvSIi0@Lf+GyWmMN z?pF%Ks%ZCSwgcx8E4@DhFWy*HEHxn(ZO|CdkG6~&0;5>4Edt%+mObm5YWDBhi)fsblt z2>gyjz?Ky*Wb@`|yk%fdJ)-r=>k0KJb6k{rV>6jevA@iXe$D5eXTQQ3qOM%I!&)vt zNt`pbb>ybnwUq3^o&>7|W#$WPCDqCQkc;};2|Ug8CFskxD?)3GUJdGQvk zpIuG0=A_e^S-a@rp)XAIg(PNI**&JV@iHSm(Gog!{?L+w1bk%>Kw|r>amwK`RJvjd zPXZ(aH`iNnBqN9JFpTF}B-UKpkP2e8a`7Kyh}gM@G31*?qCxv?^jAP-2>X;(od=EdwNt)iTk#;5Z$yBu$iAl?d@2C6*ESm=S&FnkvP(9 zD1jXs-`Mr445$xV6 ziVGaP@XWO)BDb&rl55U`wpa_r-?+lbSBZ7`_q8r}2#5|tnGg>4{rnb$kZXxGQ}M8D1xbln%hsLnX> zZVX3RIVBVvNW||kmDv3ZxQ>`}_^6Zx@evQ|>|G0!L@z?1p%dLC6^%13SJG{}w=%7! zQ@QTX0kEI%&TlNa0xuHxaXS+3;FCUnFaD|=(#o9#J7I|+Emj>fHyow9cfIKHuyLrZ zcZ*iZM-VY15j=1Egx*j*iF5un(fl%|CfMN;d45(w5K-|5bZ$y9qF?U9iAPF;!jKRs z|1Ae)dt5;JtTd)qHK37~wKa2jK2S9gNlC3^IwCLwfwyqS~Fku-igJu;*g}c6gP7$H@!O$9pWv zbq(GDejohHTMtRMXFW;WeZq?V z%Et{F`WX9t5&inN0{*DafXgzc$c)Kj1@0lO#HalNWF;npl2kRg%?*ch>0MYc&W(5P ze5D866NqScn7MjDB3cH%$DAY`E@0_m&SgXkpUAq<`78Qx&9E?@+VTQJc+OSa;Uav& z=X7U`4@1pYreGj#L0$Fu-EW8`tgQ$Fo#ASpL+=NEhQG<6Vlj0*Uqly;Y{P-w)?}Z_ zZTcT9!2Z=^u=?OEDy%dSn73o;&e#gP+|WY~Nhx5g!hE{uvlO1$?MEfoUB^HVp2epb z$T=_dhQm>Quu&w7+2US6&i5Iz9!0ZBdhIO|B`btC5A*!sKYCo{>J-%Cdy5rE8C0u| zM&ECv^yf@ZO!JK=v$krX*ZK%5Tbjw9tW|*+-qX24)*9FT_Z+DD6s!!k0-pmL1R{J_ zM%-e6k=s3ly6(a>U;GHxx9o!z_SeB*D<8)MjL?dNP)Mqo3&&iYG5ozOw0ChhWZH&5 zcCNwnn|`SOvmEdLF=iX%JW%5DF1GH!pD=iT4e8ilggWlCfd>MkNCQ8MzEnUQ2Uz+( ze>&K>Y{0m$AKACtksNnuW$3Z9`01u1PBmOe#%nHsLH;bYJ%5yb(BQjNCZ~yS@oB6T zzspRGIBdRbViIn+;R*U%?cmGfSG50OG)?dlLh*(Rs8Ki*?_OMpYD#Be^QJa<8Zs6= z9OMMiH;DX@|c_+umA^_C@>wjgRSokgO%&TaN_u@kQXM*^TFqXp3h~NsdSlb z-*y&^@+Q!A12=j0q$C-C^%a3!Neq8vimub9lUEaCNzl@_#MyWU=ihpb=YIdhy-|wX zLAlS=@sbGlsA@VLaS(xZ#csHKV-;SIQ|DAR&E(eS--NAlUtv%|jJa554xL3^Y?`nI zUJOYKAN+B)MP2ej*Ff0()}#)jnw0 zuL|Zbk7YZi`0c_)I$ZxX|#g$a%YINxJmcG46&^TB3RYCD zhZd)IbpZ$dN6~r5WA(mq+@@@@6(LGdR>irmr_w-$q@q2)sT3tGGb@rA4HB{nC6x7? z`#N^0R7%n?8d|i6w%_^v?XSn{dCs}->-v1&?{`3xgHc-Bh8X8JLu`2t@7uH@;a4Ou*4eQpW)Vs`{HA9RCMSskm~)RLjufv`xpO%OD{(YE87he?696bwai` zlBg_^M}L`koVIN$z8sXJI5`%xzYpWy#QF3`uNHN8>cqOQpK-oKDjaeTM)}-hINRES z_>N5l8M$eo-p&B(QJ8t*5bj)Mi7&?3;r6q+@Jy6I(2i!N^PCL*pdCT}DT?5mJGu1d z^8L)O*5&YWCJU!8G})DwmXP<86Y#`?8JJlZNQNa&vez$qK-mW;cKT;6_>dRP%sO=x z8t%$-s_*}@aq+eI!!nuc_u0VNZ{5LpZB3*vEe-JXb2nVR)Q0A(y@KFPi^!!d33$|h zlpr~8Exs7N7Ni(O=nMcTd@7!7Q0vz-dptQ06r?c zhJ6t#P%gTOa9j7HPHzZ=w-cC0bSPa;&{5TiwP_oL1>2F}hxcx2xwu6Ond zTnqE7_qjltdmJO8x>4xS&5%K(IaGFcHhZC98TReG!sJLjCu-x9B(Z{Yl?m(9tS<g?D3Xak{s>11aOflI?f(3z;lC5C20 z&B+sR^inv;+qeiNw#*lNFVPfas?O$0!%PH!!lnxN?hR__|0B6-=G>c)dZ-yM#)(-O zarx0YoaMDCG(f32mHBjTaCHA#!meHVi`R1{tf%~ zd4lu9Rl8Vv^Oc z3qKqhjosN1pphF!vOKbx?g3XEN-CnduczY<6>-e?z;96VSyapw;p9Iz3HJ`AQX{oc zXxU^YFu!}7u9%sJ@*OAamgQAJ(-nSZVVH|Y#m>W)MauNp^JXH&XY?&4@*wGzEO#%q zo4IvCnih)e6y|o6;(P8gdC&7~m#p{<=eF;InWKZr({~qvyK#&(S00Bs4!hA{%4O7L z&wy%z6L{_XMOI(FLf)<&CMdrRVkh5*j~BuqB6ltr{)t9l+sJnb85~LTz^ymZK%4KX z30I{+)_Wt2PgQ^f-j}_pbQM`H_+v-jFjRRz&+*|&vo@=c`s7(-ic~(NtTBaix2-^2 z?lb&osl&*Juff8&jrq2!4xB9apnkgoWy6i|Z{!qQbYLs>u`MJk=K0f!Teg$YyUx)W zJQs3JfiAaibSQTrQJwqS)`nT?R@|u`Gj7?tpBVXkH+rPb!KzbyrulC)J`YCh8c`;< za<@^>`>}lakD(%FhVXUA2L8T(4W2FePvE;qQJ`}A9g3{n2dc9S1p%#DP|BLZ@SRk+ z9xNxg=ibWhdpZHW9JGLjRWpI3;xz4N7}aUZ!sHwkfWt|syOu?-sP}Z#UVG+nMg>jX zH6M=LdBNXJ+o<cb;;s(vP@%@#8C{7fRweGW7&Sc35{-Z-F)?@{2da{L$`K*uky+ygS=IwCKEfNhR>QQNtK6C_xFp6Tgac}BZa6agU z&8l-j+o%{aWaR`Koz2)B-EtD`*oD3WgCvyiU-oY(p?uT;*B?w}=C+ojU3)zpTpop) z6P598(paKo*$>4}`=P>m5#6&t1I90#M3$GBaBdEg_&(!5Ow4v86PGNe22Ra%@w^`F z4>!a9;c`}?;S%Swu9W;Ye-_FMPE*+p=3w)+nA~moOn;c|p+WVXR95#=_5aUUD*kWj zgrQ9OmSCB}X`1E~g4$l4c9z0axVZBR z>;Lx*Nu7}a2HY?#JgN+Nic##q(QG=``4UqIi|K(Q^_cyvA5DghxIf{I=yJLemAVS) zPO&eT!S}|VYD?n<3q?VrV7XA%Qy+H>$qRNLIKaevG|?lnjVR6UO?^*YBL*8vNqCnc z(&AV)+lSxZJ2~QkbV)cir%Iq+{R>okM$r|n`|6o=0fXc;ugy+^$WQ9yAaqh~7n3Wl5w{aIbHGHER@+d8K zI)_aCe5~K^Ah=vTR^XrI4ZC@F%h4un4#{WgD?5lOzC*ZV)p=(9_+4DxD?3i@kTDc% z4bvS&59_~X!RLnp+|6CY?d>IWn@%Qp)Cn2S9TVWggihM?glDZqD`5`LWBKhY3!WD( z$QEWa>YkfKLQg)ymWj2P`QKHPpKL{MFK;04VuPub=~4Q#Faz$-OT&uPK=#bz7BqSw z%Qf-rjWe=iIO*5vGj0Y6sh@=;Cn1u^)bh?Zwat^U(8-y2@t&miYI}UO-xef-GGLaN z7~lQjXFg3c;q|Rb_T{xp_`E<2AHDg2b}G7p-XcBNALwFt;@54u{_J>=^*xGOf%bT_ zZGv5BRwUlvx{O+6zae2V!Cc!@SMGgnB4aYbXVf$*@uJoe>bxc$4Cy$*8aHi0ok%zo zuh0+}4qt(M_Z?_|Oc6!04H(yAFQOrChdZX1(>XWx6LFpeJJ>F;?{BsDgBnX@b>Z;{>^H0_3Dsd2B$K0IcGS202jZ~<_@f~b-}O z2X&ygcp@k@^;6~7+Vok7D_wZ>Zu1Fd%Y!Uz z<)0I8Zi(Z4r4%r?k;BQ$qA_TyI2WCN5+c_=r?o3bW5fK#Xz)mz#3(tCw)jZyzM48Q z3vpuK$8F-AW!stS)#vH*H?jilX`etV<0xC8PPj?ka@f9LEVTXSjUPh7d8WloGBG!f zIG5=`)Kmos@Q{X)0VE&wD`4@z2l%DyB?L58;n+Y)!MTT97`xm^2&wErA6q5fMH2!a z26a&U4Tlyl-;?s&?pVO@Cgzl=U_|sLu5y|lIo!30(OsH@-#$DcU;K6PzQ#c~xMUEi zjs*RkD#6Xp{f2ToI&po;NzCcFNCnXZ_Zw(%%^nlE4FRI`ylXJ=v|fZOt}*atToo=I zCnX4ebPV>}U&Tr1liA}@6VbSSJC;U%z~DIpWavZyZWp_bJ2*G=tvZdDZB`0)m&iiE zmOdOiFBji5XOZHiiO`-Gi5GMZ!u-v{Fnw}9(O>U~Gcz?g?Gvfosq2SPC3y!vuiMG( zcGToz_AZ7;qcZSIwko%4KZim!H_qzsF3z%T3U`}l0jzY)r-nv{;rFref}zOiFlozJ zZkeGD)wa3=;`AKNeUXAj!JasMK{FFtHx9MS`2FIe(cGy{-rt)zinBU(7PGe4BX*u9 zomQ$`nhAk>;_g`UNRr=6jpD{ur*P=a^Wz+|v3lzR>~HXfzw2f3h4)%eX*ohq@w^h7 z=|iyn$OIIQ%)_|_bJ60C2^SD|mfNLu56!l*uNq07CMn3C zQv^GWENH%{wxD6*3DM$OvIJ zRuSi#2tqyoz`)vU>Z_SSFDX&_?)WaMTU|h}B`1*`t|e$aD2Xa%W_a071*4|Tz$)WZ zJLND3C^y$(So^oExBY4mx%QcrL`U%8f3MZf+i-(@1qmA6foXgR-v0ChcF7k9e82P@ zx)>J|xs^dE41Q{7+Atl3F&{Bnxf*Reo|EanSHX=-(*QS$!b`mu?7FxX^w@U=v(s0D z*Lpe5>1QDdT|D{<{nA1Hn51JxhoCnL%J!yxy)MS%N0_Z$^{3Y5d;(1RilILgRa-u+O6t ze6%dc*|XyW7Y`H>?{zKoPu^O(s5Tj9s+ckN_K$+UlCt#G&~%)BV3=wC>Os4^wDAfw zk>t_pAQxi|f`^rMbiWVGbm4o9ZR6;x-LvT&CLh=Rm`i^J@LB3{NAb9UHBl6~MC=O> zgV*teI6itSl&c3AG+* z;&6>K?Hgl?p1YkeWZ?v`Jd;WP#X7^0arwl!8=?5$6SDou7)apHAkyE8*y@s-@M>Zm z+&Vv>Y1y#^v{S~wwXgE6S+n~3)FX4sy~XI(C*L3ya6pzV+| zEV(&>Ie9RSKVz(g?X8LepEZ-nIJZV}x_mM$j9vj-SD9gY3||SdPr;K*48ZwFKAYL# zOX_YFQm>s!q_ETpcU9UzQsF+LJm*bKh+_^lIeUuE2we*Ojywl-b_i9Nm{0oUJn8+e zvsC#}00e(XA$n5$uHiS&4OU%D#(Yv|*Xy2w_%TPJD9%>+=G7cJz0aCk_}w4H>?&Yu zxD_6g{z6R6OW;#nF2?G&((wNtu{yCP?CBXN=+r;m{5fen?icrjMGa{fIBFF&CE|j# zMmJ)R=#M{xmO$FlKEz918kE4z1aSH#txIz+_<- zJ%ZaEukv@MtBmY>p75&m8fCZpp~1i9bmx{TdO740{xW^V7&QP9l}trLu}P%2BAVD( z{-V+|x0xPo^L7>94m?h(yb73ZK1;f}Sr+?k8;HmW zX&f#`wC? z==F0C@0Gm;e+E6kX|+H=|12O{Q{3>a?{bp#q>fe&uO%yc+rZ0Z9OOd-)m-$Rl&srB zMUDXUD4!*v=3`JRRF5buYC%)8Ci>rNXS6!Eim5TGB$}fK$W9|qnDfAj?0lC=+7vF6 z;ij7;JSCj0x_J)WY_8*rB@gJjjRaFZt;BwN-XS762EQ%pq(8%qaeDW8-0(IGjeSny zsX9N~Ba?zqQRynG=%*vTxoOwtyB(*gS-|z#6=ddG9jq%@h+T(bSuw9_viRN&#`#<( zy>-`++GjnZp$_gerTZUk@+!p^R)8&H^7xDIn(fyU6D(CP1dpZKxc<}$I)jy=3oq6} zn#z6pHT@MaKWc;R%|`H>_tr?95Q7q{B``1k7jcyq(yrXEbkVVV@*qSNF2-x(0h<&i zVZvVAuwyEUT;X{&<#sf{gwJd8&J3v0MESsK_WQbCVo@1O>N_TZWBo=rrL_>PTc=a8 zb$dzw9}`CHMKqdR%)@pYIj&|<8k5vjaFqL4^s2cA8sAM}v(^h*VY`Sr+=|2R8_&`1 zZ|W#~X^)W#CG>2NGZZ}iOszLf!tp9bAg;a)mOt&Hk4D<)zIj*angDYgsCA`A|Mbc3 z%R*wn_eyrfpCf)9?QEH)1^KJOcbBJ@()me?;GUcr{r#hvF7;}q3{Jquxa^5Z0~HP;)-$Lbe8{Ly2QY-ab}PoFd9mh>0pn85f$N_ zaN*r7Vs52FB##`&UO}zh!Z|tgW2F?g%WEc{@zli+-;UyxMR`QQDhVbH9mIe;$MM6N zV!Pa^DJfW zrazS4n~%Pt&ly>bbeLIPL+U(x=~`(O%nrItO+$l-F+)^~?s8W~g>ypA)liM`d>J1}zSb$elf{~b)vTuG>;?J?Vc>IEZ zZn=?-sYyPh;Kn(s(>adZ`r%4t+=|F}<;`rA%yBY#k_gW$=q8R0I|XM#@|dj!5_~Sr zTCj}o)jXb^O`goS4W`qUL+Y##WYWSRqI3Ku)5hlk-Tk-W=t6J!FWnlZx;o$~$$Yx| z^+_Vpb%^9C4$)hh_h_5AG|dbBNORhb^A3!OMC+I>6MH9`y>2-l$f$TYqH+c1ZqWwm zl}|xq9S`#laR-NygJ`22fR0xl&}i=i_{@C;CWwv2DF%FpqJ9qszIbaV(k~_0y3q-q z_2$uY+lpw+yg(dh@QUax`GiHG5#;5%)9}*t7kwx8jp_H+1WSKLxM%M%ENL~ze)AJ_ z-Wp>(eqs(xbBV%0UwOQdRZfb3P+an5geZL$f~9W)zV10kbZm@au~`~r%)+Txp##~X z^ppLRzMgW0?quo%1-56F1Z}?BL{ts)g&VuGU`E7BvPaO0Z=@zOKTDP1laS|`$#VGc zv^NfE6~jbJ-eDBv0xq+y1%bQ|`V_y%zIEyqR352d)~x@n7;>=uKYZ0Ez_{?|^!dF6Ed6RuQjL?Tz{di&NM%hb?Dvv@izG7Dl1tUYs5XJN1(s;Tel@_p@ zNW_Ny5F5D*4v84j^UYDHZreh+Sqx+(jz_=z2Ku_Bnz%pQPkenu?TnVkGQqBvaP4j? zIVY{os5dfDXOPBD2uh*%bjLA45q3~CCLT_u@5TcQcSG6k7o=3$9hFKC(_@ofl1J;3 z$l)!gv0;KG9LO_)GtUjs*}V^+4i#glgB;GDkw;dEH^B4br$~3-UptRU=ZHbvJ*f4( zh4xdGa6wWs=$1%uA9uFVkvTuv(1SWSsKU=R%)is8U-(}2wf~5P)@;(6ScCZm=b7$% z7umTD=TQw_x@P0x2Ww3fx1$iF36VcTU9}Ss6rf)JS88uAY1;lnfg3V~72)FK%nZg#MB zK{0Un&jVV{9ieA$OVXVRCs96~s>tQIq(^?GE z$JxW&w@5=9HskrgOz?3&K?7zifv(V6_~YhJOiqSUcI}0lwV!Sf>&%H%pj}Ux?i{## zq8ykKMEMwBui4>HV^!{ug1oPE@6ZeOUr8ce&Lx2U zr9iA4T1^9P%q535XVGjSgE2avVf>Qmbm00S+qhySRBB72)yo1{t7aJ*nP)bLnT8 zJkgbOPhvDiA1w#>cRS&r$pf-g?E^C{@jJE5I)z7vL)c0DGNURW1^Yat8C5w=s0|y3 za^_>WC_bNMO^fVYeGj3U1n()kn^|+ewg5VV{J@mk$7D*621iL2Z@gx4WR@8y3eJP> z%028Gr#7^dmjZ6f30Qh&5>=??fibsmj+{94uXskV zXDk#-TA=o02|@bR8Fa;~Nth~r5mrU7Ln*zPs2j{e0Y49V)v%Bg)wP8!u9eK@X*W=< zS(jUWL6R$Kdx+z`7vjS@S*TMe#2cfMATw5hs1~SjGRsR*cUBS@7EQv*%xFQxjgxex z+jz)EZMyg04BS0G0}q?=yxR%As6TleH~I58&XVsYlwbeOJol>tEx+@uNSYuDt&E94 zRlvWypC-f9v2$R_CpmOdp9EgzGoex}5hiusqh)e)xn&oQlSNCfpn+aJ76qPRKYuJk z6W2ZD)a3v=O=}DUPF&1wy!8&4d(-go`v)i{Uk=&smobF?z_}~3ApN@(D}L$&?$}^~ zZ|zgD$WovCF*1fLJ5)gjDk%Ds1E`ok1()TXW2LWLr4b*k(EMQuI^2-PSIWomZbmP; zV-du6Wk=|yhW}71fx|2J3&`M8{%rG33I@7{P|mc9Ne-3cCO%zB4JNC?s@QF`rqCJO zj!2W&Pu0+O-BK{T`H9A)c+;_}_B>~3FYjF22dBr55qw@U#QXjh2-ajvQ_+zDVT4a2 zeA&(4qt5?e`V5k6tPW z&=@6nmt+jbE^EV8$5RmgqXlkN-Jw^04r5wnId);pB$zxZsTvx7V_i9&$Q@ zO}B3|&pmCiu`LR>wfv*S?nm(N>OOYJc`e+4OYqsW12IdZGz~iVTL^0NnO&Hl9L{#>G7H*Uyme%p2pK%c9U{QAm}wS2F6107UdP$)LK7U5?Rc3|cNr>N~enrDPWn zn#mCFg-@Bs0w2EHBntE0cjH-&a5!}L3GHvaNqQv-RF=yMLap;bAv_9lxL4@`CZNW}K;Bd32%6c`O`1R7N(+)?!lM(@`U zOn6mCZdpnQ<`}$yS3FZVDKs3H<=kXNayJok#tI^~-yk5Ji)ZDIqwcX>G}&njj`5F4 zt>%8Z(LRy&c@~2cqpj(c#vW3wSPIggli`@xIV7_r1ZkPqQ0|{BH@?_RkQlbkIF4nR2<2r~v)5Ua!C(!gMv#GAqC)OtaJnQXHf|A;&@Jq%X_^!+F-knnD zH>qGue1_>P-8^&(I|FZz^BIsZAxcN(ksmk5;Tahb+;QZlUE_QOtSk`Nebj9u3dfGn zcS2q2mi!8e+RV@|*^zUZ6~;T;HqcW&5ApQ2mqa7&AyGZ~g!&)d&gAp?>fYu5p>1gf z1dTn50*e&-xqUTF7`R6q4sGCh-sQOc+yJf5euVamn@CITBT~O(fQE#Lz~o!Y;J#!P z)aR@u9Uej&mf%io_tw(kEI;!3*DCTmCm8nfeVu>y$)K&!PR`F9BD3a&!D!q-Y&%^r z^Ybqn8BtC0OA9dXdOZE*5pGv&>A-n8&A`x_leF*m5LRg@kb8lCSh(XFc1p;?!h1VN zflU&IR^`weue%vFQ{Xx;MkBcsh08WXpv@zWNqoEk64P%$`n_Qy{22!UN)tdi%?O2t6Mj3Uk6KrhImg<0LSBzYL$XbA*`#aa-^yaFi;&<7yqs|K;4k_! zqZC7aMPr5X3HHQ6c`n%KBHstONnO^R5l%>wLSBaz8|4580nKz%F8@CPn-}-}5q&L>` z&-;W+^SL+sHq)u!rZX3Io#A)*OHoU*8b&Oaz+dhCcp%9 zVzp5!=mzMov=l`CnMfBIIAaQZhSivkof}QK#h+YhH=mX6xtPtIZMsk9j~yiTca7j* zi5Wfju$!H*)&XZs6DO{(@<@w81*$){#?fWp@y)Le9QDD3+E0|?u3MfUezqo9=CBb> z#oo}fb(Gm3HJg(zktSL%@1c5!1$UK>p}RhQp(n#vadqep{_(ZM;lEa_P&5>{cA9ZZ zRCCcytIM zS~}_&b{`P$B$0#?j=T-D1LX^Ipl+o*l)a3Cxr?RhWhk@kC2-+jtN?D^l@-aV~!U>PVw=)bUz{HDvDkLF(P6 zA=jcug6Ay;ttUmqFtQz#gEqsY7uguJ;}5C+*N4xgO}I<)Car zBbz_#gR*BX1fP0Lbj=iDiM1F<>yM*@S25n*XpIT8#&Aa_+@n)}90yCiIdJRm8)CBM zBRTJI0nhfIL%+cs9MpP93mT8$l?j*dQ^st(ST}-+2FLN*jZjiyG7j6q(n+3CEPFM% z8My*Ya^BC3B=yJO@?ZK~Z0`x$ZnhUJEp@mRp4s@;Ad05A_hQ_84{X-5M{Z~)-Q$;l zPko-yx#mmAtewNewk87f(j7=}sR6v`oDOy=f9dFZme@YyC0^DS(zL|S=%ysaX-)?2*#NF6wRa4aeG?7q4*G-2yOVUvsZ45T z`;;!<69X&8xj=sWOne_{OAQ+IAmvOZv_3mUHOv0P!*U*Eq_K@!z1>59kM^dLoex=q z55v$r!39*u+mfdtXKkqiOC80efyd~RM5U!5o0&^~YNRo})lIZcClPKsRnun&6e0V= z2PU~amom+s&?e@LnrgeK+q1>!XqO2tQaj<3Ya@-np#YaNkAO(WBC5Epf{eSZ3O_bH zfc9&(bYNj3D|c=N%-S`IN}D&5a-GqrF-jJiN_Fw@Nk`IsU?+Rt%7rX<)}?VecFdF~ z?Ih^v2R4%DQyW=qgsIhMKvYl!>sEY(_AA{G&3nFV%x=QEk|^-t=fN_+ok+hoG@afq(+ro!Dzaw=q)3x>Z8DZ}?cB}0ZJU-z$mfn@))-&on$MiCLKlCE5Qv8FaCh4rAwhH9N zW)a^9Z>jdT>0Hd^*TOFc7ej*PA6z3Z!KH`ty=8}q#8A{3mId+7bJ|Nsb!{RUv!7C{ z5LI?plrpLC<(wsSw7NS#=SPWCyeg_T>F4hDKXf~b7kh` zy+m(|TAl&yfrb_HaoTM|;n&}exbo8(YSC$oH4@Tzr-jeE#3qv2%k~Smu6u?vlYihP zuQXIEtHX@F5lo8iC{DP|fD3)|5wi$~^Hq9aOzu^9v$+x$Eh~oGhwEuw*?MxNcLEt# zbqF*RXF*}~MvUlm#=rv+>}ev7R($5TN_`Q}0p3WLU6Doqrhjbt@Q$aWR@aZPDmQ9y*@6b_`HZ+j_YrnY>PN-P9k_YFE@zcfj8|JPqPaKFv-WgS%ZLutrUZpFzIG;r+ic zc1J0;o-M%<`J=S3VE{Sv=Xh&+GtZIeL`9QNxL}qg7b>w3yUVP&*Nb?LGSAg6R(XUG zzmCuW;SE+Yr3_PJ(lP012zodd>XgXm_zLBq+h#3$Y*iSf`Q?CZs2MprYaZ_Ulu3K` zoM*}J2c)_`gjs8{kj*np=H2GCxM!O=SN3EHC-Wo{yE;GetnDfE+-w7kA7@56VKnCQP1C<`I&H@tF8R%BB;8*2)znZ@WQ;^=;M4D z(|(Vl6^Co^*)iwKRYe}k&C(rHX(A_i}HOVy_=hE{$~(bN|# z%zG_GALUrl*86wp({U@ocY_)T=JC7ca%q9|U>j6USAr1H5O~6O%IcDgT7Raz&0e{wbl9z=}xYc+az8|lNht}UCj}Coicif8t z+apd;w8sWs%)Lpq^uJLxzSA<{KsGH)O=0DIBJj%T8;t1qVDOxpNQCdkg52}t^kzgE zZOD#8bBhLS{aT6E%cgJ!n*1DMlo2j`oQjI>r|7}fX=vOWfsw6M%*&lF=$ON^*JHlZ zT~E5OvEPcmRWqj>TDF07{5*Uhu>tJ96SVy}9zSh5!Ujx`CC=4R+=VB=ZN0pWYd*e^ zyTZ2M#yO(ghm0Q7`@RT2mTH5~M~eC<9N_0*7%Yp~${C+gK+&;h?T+5ti{eK!LEi2g z1SGcO;C2yCIrI%~d-jHGs^`(xxCtWvtNs?HP6`K^H9BUt?HTC7zwFWBWCa z=jA&!@J`iqs9H1~7W|81Uqp8hv&mjydO4bT`KJnm4*xP5RWW-(fDlzs-(@O)@J{L@6HS{L0C<)~(!+ z*0r3!hCkPKFBCQEe~|Y+Qe5q(Bbffc6eS{WBJ;Hsf5nR-8}XFHo-81GM;9<1YPMh# zvK)RNv}C69oZ}Lr%l=rUfa`e9W1o^VnVx@`-QE*Snw?qZ zZS`r4++K|}RY!6BWhwTyQw;MV_&r|z(L>&06e@o*;9Rxi`QJ}3an`87Dgy@yyy*!N zD-OZo^Wpe-$6d_ZSk6qUevj>9mH6(*dI;s;eZ`Wi(N_KiP=2PV?>Ps~cYa6sT?BXk zs}R2WxQXK~sG!e@f4HI7o#Wn~#OrAXA%yQlWrYtChf`8)`hX)u4qDON)dh6p&$HMV zq{>x!x!~3VgCwM^6>Yke=#;HDiIU_QydXCp`>tkTzMnS)*zBXd8@H0+1qW!vsqrv+ zQ6M~fH=g$V>7owWVlZ*0Azp9&g&P)5#8LatlU|W(sxSQoC;fR25`XT%y2E#%#Zdv2 zR8}*ybym~06PjVn;S`vAavua+Z3Ok>kLii@SaLpFlv7``lUw}WlUvI-!X=YUrm5tlVvbs>O5Tkrki*i*Wjiv=)}=-a@;2{!k0@n z!A|Q$8kO3E#fzttpoLAOHTN>M2HeH8Q5(?w;7^PQzt7B>|C31OTk;&yYcNzaN}&CE zKUnS_3s%Y*;1eu?_PJen)e`akfAu8B;331A)WF&^;gH64L+F{qIG$(Ay!;+Xog98r zLt2YBriWAK;X7pazpMD?*K|l+(|yyUN*#M6Sn%}?ZQ%$y*Cl!uaBdn zq_3h+dIZIE{MxI)dDRJ*AMR0Qvi{(}n|C5+r{ zJ7gZ`GZQjPV1FS9jK6<^tJNY{kv&cD3yp;Gih(3#!gxW9!E5L_K1Oic;W0?oeSz~G za?rH237iwe1f@U41$!HRz_F7jiKBfb7M#q0;}wzk zbqqLm!N3zg2)ZSNcfJ!U@i}DsYwNG>o|$4&(k_2BXt@ z(DL&#orW@4cKsMWENr0$GvDI@(=gCOWfUF}k~we|tY1�y5mV2n`#%fvbxI7UM&3 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