diff --git a/.github/workflows/build-test-windows.yml b/.github/workflows/build-test-windows.yml index 32b6a32904a0..6884b727994c 100644 --- a/.github/workflows/build-test-windows.yml +++ b/.github/workflows/build-test-windows.yml @@ -45,15 +45,16 @@ jobs: actions: read # This is required for wait-for-job env: - BUILD_C_SHARP: ${{ matrix.cxx_compiler == 'msvc-2026' && matrix.build_system == 'MSBuild' }} + # The C# bindings are generated and tested on the clang-cl jobs, which also produce the NuGet + # payload (the native DLLs, MRDotNet2.dll and its XML) + BUILD_C_SHARP: ${{ matrix.cxx_compiler == 'clang-cl' }} PYTEST_C_SHARP_ARGS: --run-c-sharp-samples --csharp-sample-dir=..\source\${{ matrix.arch || 'x64' }}\${{ matrix.config }} VCPKG_DEFAULT_TRIPLET: ${{ matrix.vcpkg_triplet || 'x64-windows-meshlib' }} # Upload a developer distributive per Visual Studio toolchain and target # architecture so update-win-version can repackage each into # MeshLibDistVS{19,22,26}[-arm64].zip. - # VS2019/VS2022 ship the CMake build; VS2026 ships the MSBuild build - # (which also produces the C#/.NET components). VS_TAG keeps the archive - # names distinct. + # VS2019/VS2022 ship the CMake build; VS2026 ships the MSBuild build. + # VS_TAG keeps the archive names distinct. job_upload_artifact: ${{ inputs.upload_artifacts && ( matrix.cxx_compiler == 'msvc-2026' && matrix.build_system == 'MSBuild' || matrix.cxx_compiler != 'msvc-2026' && matrix.cxx_compiler != 'clang-cl' && matrix.build_system == 'CMake' ) }} # Only VS2026 has an arm64 leg, so the suffix separates it from the x64 VS26 archive VS_TAG: ${{ format('{0}{1}', fromJSON('{"msvc-2019":"VS19","msvc-2022":"VS22"}')[matrix.cxx_compiler] || 'VS26', matrix.arch == 'arm64' && '-arm64' || '') }} @@ -489,7 +490,7 @@ jobs: compression-level: 0 - name: Create and fix fake Wheel for NuGet - if: ${{ inputs.nuget_build && matrix.cxx_compiler == 'msvc-2026' && matrix.config == 'Release' && env.BUILD_C_SHARP == 'true' }} + if: ${{ inputs.nuget_build && matrix.cxx_compiler == 'clang-cl' && matrix.config == 'Release' && env.BUILD_C_SHARP == 'true' }} run: | py -3 -m venv wheel_venv wheel_venv\Scripts\Activate @@ -497,7 +498,7 @@ jobs: py -3 ./scripts/nuget_patch/patch_library_deps.py ./patched_content/ ./source/${{ env.MR_ARCH }}/Release/MeshLibC2.dll ./source/${{ env.MR_ARCH }}/Release/MeshLibC2Cuda.dll - name: Upload NuGet files to Artifacts - if: ${{ inputs.nuget_build && matrix.cxx_compiler == 'msvc-2026' && matrix.config == 'Release' && env.BUILD_C_SHARP == 'true' }} + if: ${{ inputs.nuget_build && matrix.cxx_compiler == 'clang-cl' && matrix.config == 'Release' && env.BUILD_C_SHARP == 'true' }} uses: actions/upload-artifact@v7 with: name: DotNetPatchArchiveWindows-${{ env.MR_ARCH }} @@ -508,7 +509,7 @@ jobs: - name: Upload NuGet library DLL and XML to Artifacts # These are netstandard2.0 and carry no architecture, so take them from the x64 # leg only; the arm64 one would only overwrite them with equivalent assemblies. - if: ${{ inputs.nuget_build && matrix.cxx_compiler == 'msvc-2026' && matrix.config == 'Release' && env.BUILD_C_SHARP == 'true' && matrix.arch != 'arm64' }} + if: ${{ inputs.nuget_build && matrix.cxx_compiler == 'clang-cl' && matrix.config == 'Release' && env.BUILD_C_SHARP == 'true' && matrix.arch != 'arm64' }} uses: actions/upload-artifact@v7 with: name: DotNetDllXml diff --git a/.github/workflows/pip-build.yml b/.github/workflows/pip-build.yml index f17829848f83..855ab1c4b708 100644 --- a/.github/workflows/pip-build.yml +++ b/.github/workflows/pip-build.yml @@ -462,11 +462,12 @@ jobs: matrix: platform: [x64, arm64] include: + # Both build with the image's clang-cl through CMake, as the clang-cl jobs of build-test-windows.yml: + # x64 on the 14.4 (VS2022) toolset, which CUDA 12 needs, arm64 on VS2026. - platform: x64 runner: windows-2025 - vcpkg-triplet: x64-windows-meshlib - msbuild-platform: x64 - msbuild-architecture: x86 + vcpkg-triplet: x64-windows-vs2022-meshlib + vcvars-ver: '14.4' vs-dev-args: -arch=amd64 -host_arch=x86 plat-name: win-amd64 msys2-env: clang64 @@ -475,12 +476,10 @@ jobs: cuda-version: '12.0.1' cuda-major: '12' cuda-minor: '0' - cuda-platform-toolset: v143 - platform: arm64 runner: windows-11-vs2026-arm vcpkg-triplet: arm64-windows-meshlib - msbuild-platform: ARM64 - msbuild-architecture: arm64 + vcvars-ver: '14.5' vs-dev-args: -arch=arm64 -host_arch=arm64 plat-name: win-arm64 msys2-env: clangarm64 @@ -489,7 +488,6 @@ jobs: cuda-version: '13.4.1' cuda-major: '13' cuda-minor: '4' - cuda-platform-toolset: '' permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout @@ -499,9 +497,8 @@ jobs: CUDA-MAJOR: ${{ matrix.cuda-major }} CUDA-MINOR: ${{ matrix.cuda-minor }} VCPKG_DEFAULT_TRIPLET: ${{ matrix.vcpkg-triplet }} - VCVARS_VER: '14.5' - PLATFORM_TOOLSET: v145 - CUDA_PLATFORM_TOOLSET: ${{ matrix.cuda-platform-toolset }} + VCVARS_VER: ${{ matrix.vcvars-ver }} + CMAKE_COMPILER_ARGS: -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_LINKER_TYPE=LLD MSYS2_ENV: ${{ matrix.msys2-env }} steps: - name: Checkout @@ -543,16 +540,7 @@ jobs: with: cuda-major: ${{ env.CUDA-MAJOR }} cuda-minor: ${{ env.CUDA-MINOR }} - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 - with: - # an x86 MSBuild would make the VC props pick an x86 host compiler, whose address - # space is too small for our PCH when targeting ARM64 - msbuild-architecture: ${{ matrix.msbuild-architecture }} - - - name: Build - run: msbuild -m source\MeshLib.sln -p:Configuration=Release -p:Platform=${{ matrix.msbuild-platform }} -p:VcpkgTriplet=${{ env.VCPKG_DEFAULT_TRIPLET }} -p:PlatformToolset=${{ env.PLATFORM_TOOLSET }} -p:MRCudaPlatformToolset=${{ env.CUDA_PLATFORM_TOOLSET }} -p:MR_PCH_USE_EXTRA_HEADERS=ON -p:MRVOXELS_UNITY_BUILD=ON -p:MESHLIB_BUILD_MCP=OFF + install-vs-integration: false - name: Install MSYS2 for MRBind uses: ./.github/actions/install-msys2-mrbind @@ -567,6 +555,27 @@ jobs: extra-cache-key: ${{ hashFiles(format('scripts/mrbind/msys2_package_hashes{0}.txt', matrix.msys2-pkgs)) }} msys2-env: ${{ matrix.msys2-env }} + - name: Build + shell: cmd + env: + GETTEXT_ROOT: C:\msys64\clang64 + VCPKG_ROOT: C:\vcpkg + VCPKG_TARGET_TRIPLET: ${{ matrix.vcpkg-triplet }} + # The CMake branch of the Build step of build-test-windows.yml, Release only; see the notes there + # on `|| exit /b 1` and on the serial z-applocal pass. VsDevCmd.bat replaces VCPKG_ROOT with the + # VS-bundled vcpkg, whose path has a space, so the project's one is kept aside for the loop. + run: | + set "PROJ_VCPKG_ROOT=%VCPKG_ROOT%" + call "${{ env.VC_PATH }}\Common7\Tools\VsDevCmd.bat" ${{ matrix.vs-dev-args }} -vcvars_ver=${{ env.VCVARS_VER }} || exit /b 1 + cmake --version || exit /b 1 + cmake -B source\TempOutput -GNinja -DCMAKE_BUILD_TYPE=Release %CMAKE_COMPILER_ARGS% -DVCPKG_TARGET_TRIPLET=%VCPKG_TARGET_TRIPLET% -DVCPKG_APPLOCAL_DEPS=OFF -DMESHLIB_BUILD_GENERATED_C_BINDINGS=OFF -DMESHLIB_BUILD_MRCUDA=${{ matrix.cuda-version != '' && 'ON' || 'OFF' }} -DMESHLIB_BUILD_MCP=OFF -DMR_PCH_USE_EXTRA_HEADERS=ON -DMRVOXELS_UNITY_BUILD=ON || exit /b 1 + cmake --build source\TempOutput -j || exit /b 1 + for %%b in (source\TempOutput\bin\*.exe source\TempOutput\bin\*.dll) do ( + "%PROJ_VCPKG_ROOT%\vcpkg.exe" z-applocal --target-binary="%%b" --installed-bin-dir="%PROJ_VCPKG_ROOT%\installed\%VCPKG_TARGET_TRIPLET%\bin" >nul || exit 1 + ) + if not exist source\${{ matrix.platform }} mkdir source\${{ matrix.platform }} || exit /b 1 + xcopy source\TempOutput\bin\* source\${{ matrix.platform }}\Release\* /s /e /i /Y || exit /b 1 + - name: Generate and build MRBind bindings shell: cmd env: diff --git a/source/MRDotNet2Test/src/ICPTests.cs b/source/MRDotNet2Test/src/ICPTests.cs index 490e4e0fabb7..540806007ee5 100644 --- a/source/MRDotNet2Test/src/ICPTests.cs +++ b/source/MRDotNet2Test/src/ICPTests.cs @@ -33,14 +33,15 @@ public void TestMultiwayICP() icp.setParams(iCPProperties); icp.updateAllPointPairs(); - Assert.That(icp.getNumActivePairs(), Is.EqualTo((nuint)1748)); - Assert.That(icp.getNumSamples(), Is.EqualTo((nuint)1748)); + // 1744 pairs with FMA instructions (clang-cl on arm64), 1748 without + Assert.That((long)icp.getNumActivePairs(), Is.InRange(1744, 1748)); + Assert.That((long)icp.getNumSamples(), Is.InRange(1744, 1748)); Assert.That(icp.getMeanSqDistToPoint(), Is.EqualTo(0.00254).Within(1e-5)); Console.WriteLine("Calculating transformations..."); var xfs = icp.calculateTransformations(); - Assert.That(icp.getNumActivePairs(), Is.EqualTo((nuint)1748)); - Assert.That(icp.getNumSamples(), Is.EqualTo((nuint)1748)); + Assert.That((long)icp.getNumActivePairs(), Is.InRange(1744, 1748)); + Assert.That((long)icp.getNumSamples(), Is.InRange(1744, 1748)); Assert.That(icp.getMeanSqDistToPoint(), Is.EqualTo(0).Within(1e-5)); } diff --git a/source/MRDotNet2Test/src/MeshCollideTests.cs b/source/MRDotNet2Test/src/MeshCollideTests.cs index 251194d30b2d..08562cec4058 100644 --- a/source/MRDotNet2Test/src/MeshCollideTests.cs +++ b/source/MRDotNet2Test/src/MeshCollideTests.cs @@ -26,16 +26,17 @@ public void TestMeshCollidePrecise() else edgeBTriACount++; } - Assert.That(edgeATriBCount, Is.EqualTo(80)); - Assert.That(edgeBTriACount, Is.EqualTo(72)); + // the second values are with FMA instructions (clang-cl on arm64), as in the C++ test of MRMeshBooleanTests.cpp + Assert.That(edgeATriBCount, Is.EqualTo(80).Or.EqualTo(76)); + Assert.That(edgeBTriACount, Is.EqualTo(72).Or.EqualTo(76)); var contours = orderIntersectionContours(meshA.topology, meshB.topology, intersections); Assert.That(contours.size(), Is.EqualTo((nuint)4)); - Assert.That(contours[0].size(), Is.EqualTo((nuint)71)); - Assert.That(contours[1].size(), Is.EqualTo((nuint)7)); - Assert.That(contours[2].size(), Is.EqualTo((nuint)69)); - Assert.That(contours[3].size(), Is.EqualTo((nuint)9)); + Assert.That(contours[0].size(), Is.EqualTo((nuint)71).Or.EqualTo((nuint)69)); + Assert.That(contours[1].size(), Is.EqualTo((nuint)7).Or.EqualTo((nuint)9)); + Assert.That(contours[2].size(), Is.EqualTo((nuint)69).Or.EqualTo((nuint)71)); + Assert.That(contours[3].size(), Is.EqualTo((nuint)9).Or.EqualTo((nuint)7)); var aConts = new Std.Vector_MROneMeshContour(); getOneMeshIntersectionContours(meshA, meshB, contours, aConts, null, conv); Assert.That(aConts.size(), Is.EqualTo((nuint)4)); diff --git a/source/MRDotNet2Test/src/MeshTests.cs b/source/MRDotNet2Test/src/MeshTests.cs index 383d6f8e8cf4..567d60b8deaa 100644 --- a/source/MRDotNet2Test/src/MeshTests.cs +++ b/source/MRDotNet2Test/src/MeshTests.cs @@ -321,9 +321,12 @@ public void TestProjection() Assert.That(projRes.proj.point.y, Is.EqualTo(0.507).Within(1e-3)); Assert.That(projRes.proj.point.z, Is.EqualTo(0.803).Within(1e-3)); - Assert.That(projRes.mtp.e.id, Is.EqualTo(1640)); - Assert.That(projRes.mtp.bary.a, Is.EqualTo(0.053).Within(1e-3)); - Assert.That(projRes.mtp.bary.b, Is.EqualTo(0.946).Within(1e-3)); + // which edge of the triangle represents the point differs with FMA instructions (clang-cl on arm64) + Assert.That(mesh.topology.left(projRes.mtp.e).id, Is.EqualTo(904)); + var mtpPoint = mesh.triPoint(projRes.mtp); + Assert.That(mtpPoint.x, Is.EqualTo(0.310).Within(1e-3)); + Assert.That(mtpPoint.y, Is.EqualTo(0.507).Within(1e-3)); + Assert.That(mtpPoint.z, Is.EqualTo(0.803).Within(1e-3)); var xf = AffineXf3f.translation(Vector3f.diagonal(1.0f)); projRes = findProjection(p, mesh, float.MaxValue, xf); @@ -333,9 +336,10 @@ public void TestProjection() Assert.That(projRes.proj.point.y, Is.EqualTo(1.439).Within(1e-3)); Assert.That(projRes.proj.point.z, Is.EqualTo(1.895).Within(1e-3)); - Assert.That(projRes.mtp.e.id, Is.EqualTo(1898)); - Assert.That(projRes.mtp.bary.a, Is.EqualTo(0.5).Within(1e-3)); - Assert.That(projRes.mtp.bary.b, Is.EqualTo(0.0).Within(1e-3)); + mtpPoint = mesh.triPoint(projRes.mtp); // in mesh space, before xf + Assert.That(mtpPoint.x, Is.EqualTo(0.000).Within(1e-3)); + Assert.That(mtpPoint.y, Is.EqualTo(0.439).Within(1e-3)); + Assert.That(mtpPoint.z, Is.EqualTo(0.895).Within(1e-3)); } [Test] diff --git a/source/MRVoxels/CMakeLists.txt b/source/MRVoxels/CMakeLists.txt index 578baa0a9c09..4a122e7758ec 100644 --- a/source/MRVoxels/CMakeLists.txt +++ b/source/MRVoxels/CMakeLists.txt @@ -39,7 +39,7 @@ ENDIF() IF(NOT MRVOXELS_NO_DICOM) find_package(GDCM CONFIG REQUIRED) - target_link_libraries(${PROJECT_NAME} PRIVATE gdcmIOD gdcmDICT gdcmDSED gdcmMEXD gdcmMSFF) + target_link_libraries(${PROJECT_NAME} PRIVATE gdcmIOD gdcmDICT gdcmDSED gdcmMSFF) ENDIF() IF(MR_PCH)