diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ab5dd8..21224d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,8 +8,24 @@ project(tiny_bvh LANGUAGES CXX) if (APPLE) find_library(COCOA_LIBRARY Cocoa) -elseif (UNIX) +elseif (UNIX AND NOT EMSCRIPTEN) find_package(X11) +elseif (EMSCRIPTEN) + # To auto generate test pages + set(CMAKE_EXECUTABLE_SUFFIX ".html") + + # Enable clang's Address Sanitizer for WASM + option(enable_asan "Enable ASAN on the build (it'll slow down the app)" OFF) + + # Enable wasm SIMD instructions + set(emcc_simd "fixed" CACHE STRING "SIMD type to use in the build (none, fixed, relaxed)") + set_property(CACHE emcc_simd PROPERTY STRINGS none fixed relaxed) + + # Enable thread support for WASM (note that you need special http headers now https://emscripten.org/docs/porting/pthreads.html ) + option(emcc_threads "Enable Threads in WASM (note that means you need to change your http headers)" OFF) + + # Enable C/C++ main thread as a proxy thread to avoid blocking DOM/UI thread https://emscripten.org/docs/porting/pthreads.html + option(emcc_proxy_main "Enable C/C++ main thread as a proxy thread to avoid blocking DOM/UI thread" ${emcc_threads}) endif() enable_testing() @@ -17,29 +33,101 @@ enable_testing() add_executable(tiny_bvh_test tiny_bvh_test.cpp) add_executable(tiny_bvh_renderer tiny_bvh_renderer.cpp) add_executable(tiny_bvh_speedtest tiny_bvh_speedtest.cpp) -add_executable(tiny_bvh_fenster tiny_bvh_fenster.cpp) +if (NOT EMSCRIPTEN) + # EMSCRIPTEN doesn't render anything by default (you would need WebGL/WebGPU) + add_executable(tiny_bvh_fenster tiny_bvh_fenster.cpp) +endif() if (NOT MSVC) + # Produce debug symbols and set optimization level set(common_cxx_flags - -g -march=native + $<$,$>:-g> $<$,$>:-O3> ) + + set(common_link_flags + $<$,$>:-g> + ) + + if (NOT EMSCRIPTEN) + # EMSCRIPTEN doesn't know about archs + set(common_cxx_flags + ${common_cxx_flags} -march=native + ) + else() + # EMSCRIPTEN flags: + # -sALLOW_MEMORY_GROWTH (allow runtime allocations) (for some reason cmake passes this as a linker flag when in theory it's a compiler one) + set(common_link_flags ${common_link_flags} -sALLOW_MEMORY_GROWTH=1) + + # -fsanitize=address (enable ASAN) + # More at https://emscripten.org/docs/debugging/Sanitizers.html#address-sanitizer + if (enable_asan) + set(common_cxx_flags ${common_cxx_flags} -fsanitize=address) + set(common_link_flags ${common_link_flags} -fsanitize=address) + endif() + + # Enable SIMD instructions and AVX intrinsics + string(TOLOWER "${emcc_simd}" emcc_simd_lower) + if (emcc_simd_lower STREQUAL fixed) + # Supported by all browsers https://webassembly.org/features/ + set(common_cxx_flags ${common_cxx_flags} -msimd128 -mavx) + elseif (emcc_simd_lower STREQUAL relaxed) + # Supported some browsers https://webassembly.org/features/ + set(common_cxx_flags ${common_cxx_flags} -mrelaxed-simd -mavx) + endif() + + endif() + target_compile_options(tiny_bvh_test PRIVATE ${common_cxx_flags}) + target_link_options(tiny_bvh_test PRIVATE ${common_link_flags}) + target_compile_options(tiny_bvh_renderer PRIVATE ${common_cxx_flags}) - if (NOT APPLE) - target_compile_options(tiny_bvh_speedtest PRIVATE ${common_cxx_flags} -fopenmp) - target_link_options(tiny_bvh_speedtest PRIVATE -fopenmp) - else() - # No openmp support in default compiler - target_compile_options(tiny_bvh_speedtest PRIVATE ${common_cxx_flags}) - target_link_options(tiny_bvh_speedtest PRIVATE) + target_link_options(tiny_bvh_renderer PRIVATE ${common_link_flags}) + + # No openmp support in default compiler + set(tiny_bvh_speedtest_cxx_flags ${common_cxx_flags}) + set(tiny_bvh_speedtest_link_flags ${common_link_flags}) + + if (NOT APPLE AND NOT EMSCRIPTEN) + set(tiny_bvh_speedtest_cxx_flags ${tiny_bvh_speedtest_cxx_flags} -fopenmp) + set(tiny_bvh_speedtest_link_flags ${tiny_bvh_speedtest_link_flags} -fopenmp) + elseif (EMSCRIPTEN) + if (emcc_threads) + set(tiny_bvh_speedtest_cxx_flags ${tiny_bvh_speedtest_cxx_flags} -pthread) + set(tiny_bvh_speedtest_link_flags ${tiny_bvh_speedtest_link_flags} -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency) + + if (FALSE) + # From: + # - https://github.com/llvm/llvm-project/pull/95169 + # - https://github.com/llvm/llvm-project/pull/71297 + # - https://reviews.llvm.org/D142593?id=492403 + # llvm's and clang's OpenMP has been ported to WASM around August 2024 + # Sadly emcc's clang doesn't include a copy of the lib + # So you would need to do compile it yourself https://github.com/abrown/wasm-openmp-examples/blob/main/emscripten.sh + # Then link it here + set(tiny_bvh_speedtest_cxx_flags ${tiny_bvh_speedtest_cxx_flags} -fopenmp) + set(tiny_bvh_speedtest_link_flags ${tiny_bvh_speedtest_link_flags} -fopenmp) + endif() + + if (emcc_proxy_main) + set(tiny_bvh_speedtest_link_flags ${tiny_bvh_speedtest_link_flags} -sPROXY_TO_PTHREAD=1) + endif() + endif() endif() - target_compile_options(tiny_bvh_fenster PRIVATE ${common_cxx_flags}) - if (WIN32) - target_link_libraries(tiny_bvh_fenster -mwindows) - elseif (APPLE) - target_link_libraries(tiny_bvh_fenster ${COCOA_LIBRARY}) - elseif (X11_FOUND) - target_link_libraries(tiny_bvh_fenster ${X11_LIBRARIES}) + target_compile_options(tiny_bvh_speedtest PRIVATE ${tiny_bvh_speedtest_cxx_flags}) + target_link_options(tiny_bvh_speedtest PRIVATE ${tiny_bvh_speedtest_link_flags}) + + if (TARGET tiny_bvh_fenster) + # Not all platforms support all targets. E. g.: + # - EMSCRIPTEN doesn't render anything by default (you would need WebGL/WebGPU) + target_compile_options(tiny_bvh_fenster PRIVATE ${common_cxx_flags}) + target_link_options(tiny_bvh_fenster PRIVATE ${common_link_flags}) + if (WIN32) + target_link_libraries(tiny_bvh_fenster -mwindows) + elseif (APPLE) + target_link_libraries(tiny_bvh_fenster ${COCOA_LIBRARY}) + elseif (X11_FOUND) + target_link_libraries(tiny_bvh_fenster ${X11_LIBRARIES}) + endif() endif() else() if (WIN32) diff --git a/tiny_bvh.h b/tiny_bvh.h index b0d6ce0..4be0c6f 100644 --- a/tiny_bvh.h +++ b/tiny_bvh.h @@ -71,7 +71,7 @@ THE SOFTWARE. #define BVHBINS 8 // include fast AVX BVH builder -#if defined(__x86_64__) || defined(_M_X64) +#if defined(__x86_64__) || defined(_M_X64) || defined(__wasm_simd128__) || defined(__wasm_relaxed_simd__) #define BVH_USEAVX #endif @@ -95,6 +95,24 @@ THE SOFTWARE. #define ALIGNED( x ) __declspec( align( x ) ) #define ALIGNED_MALLOC( x ) ( ( x ) == 0 ? 0 : _aligned_malloc( ( x ), 64 ) ) #define ALIGNED_FREE( x ) _aligned_free( x ) +#elif defined(__EMSCRIPTEN__) +// EMSCRIPTEN (needs to be before gcc and clang to avoid misdetection) +#include +#include +#include +#define ALIGNED( x ) __attribute__( ( aligned( x ) ) ) +#if defined(__wasm_simd128__) || defined(__wasm_relaxed_simd__) +// https://emscripten.org/docs/porting/simd.html +#include +#define ALIGNED_MALLOC( x ) ( ( x ) == 0 ? 0 : _mm_malloc( ( x ), 64 ) ) +#define ALIGNED_FREE( x ) _mm_free( x ) +#else +// Size needs to be a multiple of Alignment in the standard (in EMSCRIPTEN this is enforced) +// See https://en.cppreference.com/w/c/memory/aligned_alloc +#define MAKE_MULIPLE_64( x ) ( ( ( x ) + 63 ) & ( ~0x3f ) ) +#define ALIGNED_MALLOC( x ) ( ( x ) == 0 ? 0 : aligned_alloc( 64, MAKE_MULIPLE_64( x ) ) ) +#define ALIGNED_FREE( x ) free( x ) +#endif #else // gcc / clang #include @@ -2578,6 +2596,8 @@ static unsigned __bfind( unsigned x ) // https://github.com/mackron/refcode/blob { #if defined(_MSC_VER) && !defined(__clang__) return 31 - __lzcnt( x ); +#elif defined(__EMSCRIPTEN__) + return 31 - __builtin_clz(x); #elif defined(__GNUC__) || defined(__clang__) unsigned r; __asm__ __volatile__( "lzcnt{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" ); diff --git a/tiny_bvh_speedtest.cpp b/tiny_bvh_speedtest.cpp index 79efb00..4c96425 100644 --- a/tiny_bvh_speedtest.cpp +++ b/tiny_bvh_speedtest.cpp @@ -9,6 +9,9 @@ #ifdef _WIN32 #include // for __cpuidex #endif +#ifdef __EMSCRIPTEN__ +#include // for __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__ +#endif // 'screen resolution': see tiny_bvh_fenster.cpp; this program traces the // same rays, but without visualization - just performance statistics. @@ -122,6 +125,9 @@ int main() // determine compiler #ifdef _MSC_VER printf( "(MSVC %i build)\n", _MSC_VER ); +#elif defined __EMSCRIPTEN__ + // EMSCRIPTEN needs to be before clang or gcc + printf("(emcc %i.%i build)\n", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__); #elif defined __clang__ printf( "(clang %i.%i build)\n", __clang_major__, __clang_minor__ ); #elif defined __GNUC__ @@ -405,6 +411,6 @@ int main() #endif - // all done. + printf( "all done." ); return 0; }