Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f4ce3dc
Move CUDA interop behind extension target
AnastaZIuk May 6, 2026
78845ae
Address CUDA interop review cleanup
AnastaZIuk May 6, 2026
ab9a7e5
Simplify CUDA interop smoke CMake
AnastaZIuk May 6, 2026
bf8eeb3
Clean CUDA interop smoke usage requirements
AnastaZIuk May 6, 2026
f701ac6
Export CUDA interop package target
AnastaZIuk May 6, 2026
a520d57
Use CUDAToolkit package targets
AnastaZIuk May 6, 2026
4bddc57
Require CUDA version via CMake
AnastaZIuk May 6, 2026
6f68e66
Split CUDA interop native surface
AnastaZIuk May 6, 2026
49bcb2c
Add native CUDA accessor overloads
AnastaZIuk May 6, 2026
d85657e
Document CUDA interop target split
AnastaZIuk May 6, 2026
6e8c4f9
Trim CUDA interop README wording
AnastaZIuk May 6, 2026
881e9b8
Move CUDA interop into Nabla
AnastaZIuk May 6, 2026
5dd1134
Document CUDA interop accessor model
AnastaZIuk May 7, 2026
e514df7
Inline CUDA interop stubs
AnastaZIuk May 7, 2026
e53c838
Refine CUDA interop boundary
AnastaZIuk May 7, 2026
1417905
Add CUDA interop runtime header discovery
AnastaZIuk May 7, 2026
045432e
Tighten CUDA interop native helpers
AnastaZIuk May 7, 2026
8a119dd
Hide CUDA interop native state construction
AnastaZIuk May 7, 2026
e018545
Clean up CUDA runtime header discovery
AnastaZIuk May 7, 2026
c6ef6ee
Move CUDA interop API back into video
AnastaZIuk May 7, 2026
d559a2c
Move smart pointer helpers into core
AnastaZIuk May 7, 2026
38705b9
Use CUDA interop accessors
AnastaZIuk May 7, 2026
23e6ef5
Use explicit CUDA compile log
AnastaZIuk May 7, 2026
a640183
Trim CUDA interop API surface
AnastaZIuk May 7, 2026
5bf0e2d
Keep CUDA SDK layouts private
AnastaZIuk May 7, 2026
d745421
Simplify CUDA interop helper
AnastaZIuk May 7, 2026
ffba3d4
Update CUDA interop examples pointer
AnastaZIuk May 7, 2026
745f1b9
Use opaque CUDA interop boundary
AnastaZIuk May 8, 2026
ec259cb
Clean CUDA interop boundary
AnastaZIuk May 9, 2026
fce838b
Polish CUDA interop cleanup
AnastaZIuk May 9, 2026
9f2d5fe
Simplify CUDA interop native boundary
AnastaZIuk May 9, 2026
ed8a1d6
Refine CUDA interop boundary
AnastaZIuk May 10, 2026
f2f62ce
Polish CUDA interop review feedback
AnastaZIuk May 10, 2026
9c504a1
Polish CUDA interop native header
AnastaZIuk May 10, 2026
0df7507
Use opaque CUDA interop handles
AnastaZIuk May 10, 2026
21d3b7c
Accept CUDA handler pointers in assert helper
AnastaZIuk May 10, 2026
dfca17e
Consolidate CUDA native handle declarations
AnastaZIuk May 10, 2026
525315e
Tighten CUDA native output bridges
AnastaZIuk May 10, 2026
d8d4c3b
Centralize CUDA output bridge
AnastaZIuk May 11, 2026
fe3fd66
Document CUDA interop handles
AnastaZIuk May 11, 2026
d5dfade
Make CUDA PTX compile log optional
AnastaZIuk May 11, 2026
2d53e9a
Enable CUDA in Windows CI
AnastaZIuk May 11, 2026
0243ed0
Fix CUDA cache path in CI
AnastaZIuk May 11, 2026
4ea20f7
Seed CUDA cache on Windows 2025
AnastaZIuk May 11, 2026
85fbf7f
Use Choco for CUDA cache seed
AnastaZIuk May 11, 2026
6008285
Update CUDA interop examples pointer
AnastaZIuk May 11, 2026
82d82a2
Update CUDA interop examples pointer
AnastaZIuk May 11, 2026
828211c
Retry CI image pull
AnastaZIuk May 11, 2026
e913518
Deduplicate CUDA CI setup
AnastaZIuk May 12, 2026
f74efe8
Simplify CUDA CI cache handling
AnastaZIuk May 12, 2026
920f2ef
Keep CUDA CI paths configurable
AnastaZIuk May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/scripts/ci_cuda_toolkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3

import argparse
import os
import platform
import subprocess
from pathlib import Path


REQUIRED_HEADERS = (
"cuda.h",
"nvrtc.h",
"cuda_fp16.h",
"vector_types.h",
)


def cuda_version() -> str:
version = os.environ.get("CUDA_VERSION", "").strip()
if not version:
raise SystemExit("CUDA_VERSION is not set.")
parts = version.split(".")
if len(parts) < 2 or not all(part.isdigit() for part in parts[:2]):
raise SystemExit(f"CUDA_VERSION must start with major.minor, got: {version}")
return version


def major_minor(version: str) -> str:
major, minor, *_ = version.split(".")
return f"{major}.{minor}"


def cache_base() -> Path:
base = os.environ.get("CUDA_CACHE_BASE", "").strip()
if not base:
raise SystemExit("CUDA_CACHE_BASE is not set.")
return Path(base)


def cache_root(version: str) -> str:
return str(cache_base() / f"v{major_minor(version)}")


def cache_key(version: str) -> str:
if platform.system() == "Windows":
return f"cuda-toolkit-{version}-windows-2025-x64-v2"
return f"cuda-toolkit-{version}-{platform.system().lower()}-x64-v1"


def cache_restore_key(version: str) -> str:
if platform.system() == "Windows":
return f"cuda-toolkit-{version}-windows-2025-x64-"
return f"cuda-toolkit-{version}-{platform.system().lower()}-x64-"


def emit_outputs() -> None:
version = cuda_version()
lines = (
f"cache_root={cache_root(version)}",
f"cache_key={cache_key(version)}",
f"cache_restore_key={cache_restore_key(version)}",
)
output = os.environ.get("GITHUB_OUTPUT")
if output:
with open(output, "a", encoding="utf-8") as file:
file.write("\n".join(lines))
file.write("\n")
else:
print("\n".join(lines))


def nvcc_path(root: Path) -> Path:
executable = "nvcc.exe" if platform.system() == "Windows" else "nvcc"
return root / "bin" / executable


def run(command: list[str], **kwargs) -> subprocess.CompletedProcess:
print("+", " ".join(command))
return subprocess.run(command, check=False, text=True, **kwargs)


def verify_toolkit(root: Path, version: str) -> bool:
missing = [str(nvcc_path(root))]
missing.extend(str(root / "include" / header) for header in REQUIRED_HEADERS)
missing = [path for path in missing if not Path(path).exists()]
if missing:
print(f"CUDA Toolkit cache is incomplete at {root}.")
for path in missing:
print(f"missing: {path}")
return False

result = run([str(nvcc_path(root)), "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
print(result.stdout)
expected = f"release {major_minor(version)}"
if result.returncode != 0 or expected not in result.stdout:
print(f"Expected CUDA Toolkit {major_minor(version)} at {root}.")
return False
return True


def verify() -> None:
version = cuda_version()
root = Path(os.environ.get("CUDA_TOOLKIT_ROOT", cache_root(version)))
if not verify_toolkit(root, version):
raise SystemExit(1)


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=("outputs", "verify"))
args = parser.parse_args()

if args.command == "outputs":
emit_outputs()
elif args.command == "verify":
verify()


if __name__ == "__main__":
main()
92 changes: 90 additions & 2 deletions .github/workflows/build-nabla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ permissions:
contents: read
actions: read

env:
CUDA_VERSION: '13.2.1'
CUDA_CACHE_BASE: 'C:\nabla-ci\cuda'
CUDA_CONTAINER_ROOT: 'C:\cuda'

concurrency:
group: push-lock-${{ github.ref }}
cancel-in-progress: true
Expand Down Expand Up @@ -47,9 +52,36 @@ jobs:
}
& $rgExe --version

prepare-host-cuda:
name: Prepare host CUDA
runs-on: windows-2025

steps:
- name: Checkout CUDA CI helper
uses: actions/checkout@v6
with:
fetch-depth: 1
sparse-checkout: |
.github/scripts

- name: CUDA Toolkit paths
id: cuda
run: python .github/scripts/ci_cuda_toolkit.py outputs

- name: Restore CUDA Toolkit
id: cache-cuda
uses: actions/cache@v5
with:
path: ${{ steps.cuda.outputs.cache_root }}
key: ${{ steps.cuda.outputs.cache_key }}
restore-keys: ${{ steps.cuda.outputs.cache_restore_key }}

- name: Verify CUDA Toolkit
run: python .github/scripts/ci_cuda_toolkit.py verify

build-windows:
name: Nabla (${{ matrix.os }}, ${{ matrix.vendor }}-${{ matrix.tag }}, ${{ matrix.config }})
needs: prepare-host-rg
needs: [prepare-host-rg, prepare-host-cuda]
runs-on: ${{ matrix.os }}

env:
Expand Down Expand Up @@ -165,6 +197,10 @@ jobs:
with:
submodules: 'recursive'

- name: CUDA Toolkit paths
id: cuda
run: python .github/scripts/ci_cuda_toolkit.py outputs

- name: Restore ripgrep host tool
id: cache-rg
uses: actions/cache@v5
Expand All @@ -183,9 +219,29 @@ jobs:
$rgDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
& $rgExe --version

- name: Restore CUDA Toolkit
id: cache-cuda
uses: actions/cache@v5
with:
path: ${{ steps.cuda.outputs.cache_root }}
key: ${{ steps.cuda.outputs.cache_key }}
restore-keys: ${{ steps.cuda.outputs.cache_restore_key }}

- name: Verify CUDA Toolkit
run: python .github/scripts/ci_cuda_toolkit.py verify

- name: Pull Image
run: |
docker pull "${{ env.image }}:${{ matrix.tag }}"
$image = "${{ env.image }}:${{ matrix.tag }}"
for ($attempt = 1; $attempt -le 3; $attempt++) {
docker pull $image
if ($LASTEXITCODE -eq 0) {
exit 0
}
Write-Warning "docker pull failed for $image on attempt $attempt."
Start-Sleep -Seconds (15 * $attempt)
}
exit $LASTEXITCODE

- name: Run Container
run: |
Expand All @@ -199,8 +255,10 @@ jobs:
--env-file .\docker\ci-windows.env `
--env-file .\docker\ninja.env `
--env "NSC_IMAGE_NAME=${{ steps.set-prefix.outputs.nscTargetTaggedImage }}" `
--env "CUDA_PATH=${{ env.CUDA_CONTAINER_ROOT }}" `
--name orphan --network docker_default `
-v "${{ github.workspace }}:${{ env.mount }}" `
-v "${{ steps.cuda.outputs.cache_root }}:${{ env.CUDA_CONTAINER_ROOT }}" `
-v "${pipeHost}:\\.\pipe\dockerd" -e "DOCKER_HOST=npipe:////./pipe/dockerd" `
-w "${{ env.mount }}" `
"${{ env.image }}:${{ matrix.tag }}" `
Expand All @@ -222,6 +280,7 @@ jobs:
${{ env.entry }} ${{ env.cmd }} -Command cmake `
--preset ci-configure-dynamic-${{ matrix.vendor }} `
-DCMAKE_INSTALL_PREFIX:PATH=C:/mount/nabla/build-ct/install `
-DNBL_CUDA_TOOLKIT_ROOT:PATH=${{ env.CUDA_CONTAINER_ROOT }} `
--profiling-output=profiling/cmake-profiling.json `
--profiling-format=google-trace

Expand Down Expand Up @@ -635,7 +694,13 @@ jobs:
with:
fetch-depth: 1
sparse-checkout: |
.github/scripts
smoke
src/nbl/ext/CUDAInterop/smoke

- name: CUDA Toolkit paths
id: cuda
run: python .github/scripts/ci_cuda_toolkit.py outputs

- name: Download VulkanSDK
uses: Devsh-Graphics-Programming/install-vulkan-sdk-action@v1.4.0-devsh.1
Expand All @@ -646,6 +711,17 @@ jobs:
install_lavapipe: true
github_token: ${{ github.token }}

- name: Restore CUDA Toolkit
id: cache-cuda
uses: actions/cache@v5
with:
path: ${{ steps.cuda.outputs.cache_root }}
key: ${{ steps.cuda.outputs.cache_key }}
restore-keys: ${{ steps.cuda.outputs.cache_restore_key }}

- name: Verify CUDA Toolkit
run: python .github/scripts/ci_cuda_toolkit.py verify

- name: Download Nabla install artifact
uses: actions/download-artifact@v8
with:
Expand All @@ -668,3 +744,15 @@ jobs:

- name: Smoke Flow BUILD_ONLY
run: cmake -D FLOW=BUILD_ONLY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake

- name: Build CUDA interop package smoke
shell: pwsh
run: |
cmake `
-S src/nbl/ext/CUDAInterop/smoke `
-B smoke/cuda-interop-smoke `
-D "CMAKE_PREFIX_PATH=${{ github.workspace }}\smoke\build-ct\install\cmake" `
-D "NBL_CUDA_INTEROP_SMOKE_WITH_NATIVE=ON" `
-D "Nabla_CUDA_TOOLKIT_ROOT=${{ steps.cuda.outputs.cache_root }}"

cmake --build smoke/cuda-interop-smoke --config ${{ matrix.config }}
22 changes: 11 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ else()
message(STATUS "Vulkan SDK is not found")
endif()

option(NBL_COMPILE_WITH_CUDA "Compile with CUDA interop?" OFF)
option(NBL_COMPILE_WITH_CUDA "Build CUDA interop support?" OFF)
set(NBL_CUDA_TOOLKIT_ROOT "" CACHE PATH "Optional CUDA Toolkit root used when NBL_COMPILE_WITH_CUDA is ON")

if(NBL_COMPILE_WITH_CUDA)
find_package(CUDAToolkit REQUIRED)
if(${CUDAToolkit_VERSION} VERSION_GREATER_EQUAL "13.0")
message(STATUS "CUDA version ${CUDAToolkit_VERSION} found!")
else()
message(FATAL_ERROR "CUDA version 13.0+ needed for C++14 support!")
if(NBL_CUDA_TOOLKIT_ROOT)
set(CUDAToolkit_ROOT "${NBL_CUDA_TOOLKIT_ROOT}")
endif()
find_package(CUDAToolkit 13.0 REQUIRED)
message(STATUS "CUDA version ${CUDAToolkit_VERSION} found!")
endif()

get_filename_component(NBL_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
Expand Down Expand Up @@ -183,13 +183,12 @@ option(NBL_BUILD_IMGUI "Enable nbl::ext::ImGui?" ON)
option(NBL_BUILD_DEBUG_DRAW "Enable Nabla Debug Draw extension?" ON)

option(NBL_BUILD_OPTIX "Enable nbl::ext::OptiX?" OFF)
if(NBL_COMPILE_WITH_CUDA)
find_package(OPTIX REQUIRED)
message(STATUS "CUDA enabled and OptiX found!")
else()
if(NBL_BUILD_OPTIX)
if(NBL_BUILD_OPTIX)
if(NOT NBL_COMPILE_WITH_CUDA)
message(FATAL_ERROR "You cannot build Optix without enabled CUDA! NBL_COMPILE_WITH_CUDA must be ON!")
endif()
find_package(OPTIX REQUIRED)
message(STATUS "CUDA enabled and OptiX found!")
endif()

option(NBL_BUILD_BULLET "Enable Bullet Physics building and integration?" OFF)
Expand Down Expand Up @@ -313,6 +312,7 @@ if(NBL_ENABLE_CONFIG_INSTALL)
set(_NBL_NABLA_CONFIG_FILES
"${CMAKE_CURRENT_BINARY_DIR}/NablaConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/NablaConfigVersion.cmake"
"${CMAKE_CURRENT_LIST_DIR}/cmake/NablaCUDAInteropHelpers.cmake"
)

install(EXPORT NablaExportTargets
Expand Down
2 changes: 1 addition & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"NBL_EMBED_BUILTIN_RESOURCES": "ON",
"NBL_NSC_MODE": "SOURCE",
"NBL_UPDATE_GIT_SUBMODULE": "OFF",
"NBL_COMPILE_WITH_CUDA": "OFF",
"NBL_COMPILE_WITH_CUDA": "ON",
"NBL_BUILD_OPTIX": "OFF",
"NBL_BUILD_MITSUBA_LOADER": "ON",
"NBL_BUILD_RADEON_RAYS": "OFF",
Expand Down
4 changes: 3 additions & 1 deletion cmake/FindZLIB.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ endif()

set(ZLIB_FOUND TRUE)
set(ZLIB_LIBRARY ZLIB::ZLIB)
set(ZLIB_INCLUDE_DIR "${THIRD_PARTY_SOURCE_DIR}/zlib;${THIRD_PARTY_BINARY_DIR}/zlib")
set(ZLIB_LIBRARIES ZLIB::ZLIB)
set(ZLIB_INCLUDE_DIR "${THIRD_PARTY_SOURCE_DIR}/zlib;${THIRD_PARTY_BINARY_DIR}/zlib")
set(ZLIB_INCLUDE_DIRS "${ZLIB_INCLUDE_DIR}")
28 changes: 28 additions & 0 deletions cmake/NablaCUDAInteropHelpers.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
function(nbl_target_link_cuda_interop TARGET_NAME SCOPE)
if(NOT SCOPE MATCHES "^(PRIVATE|PUBLIC|INTERFACE)$")
set(SCOPE PRIVATE)
endif()
cmake_parse_arguments(_NBL_CUDA_INTEROP "" "RUNTIME_JSON" "INCLUDE_DIRS" ${ARGN})
target_link_libraries("${TARGET_NAME}" ${SCOPE} Nabla::ext::CUDAInterop)
set(_include_dir_entries "")
foreach(_include_dir IN LISTS _NBL_CUDA_INTEROP_INCLUDE_DIRS CUDAToolkit_INCLUDE_DIRS)
if(_include_dir)
file(TO_CMAKE_PATH "${_include_dir}" _include_dir)
list(APPEND _include_dir_entries " \"${_include_dir}\"")
endif()
endforeach()
list(JOIN _include_dir_entries "," _include_dirs_json)
set(_runtime_json [=[
{
"cudaRuntimeIncludeDirs": [
@_include_dirs_json@
]
}
]=])
string(CONFIGURE "${_runtime_json}" _runtime_json @ONLY)
set(_runtime_json_path "$<TARGET_FILE_DIR:${TARGET_NAME}>/nbl_cuda_interop_runtime.json")
if(_NBL_CUDA_INTEROP_RUNTIME_JSON)
set(_runtime_json_path "${_NBL_CUDA_INTEROP_RUNTIME_JSON}")
endif()
file(GENERATE OUTPUT "${_runtime_json_path}" CONTENT "${_runtime_json}" TARGET "${TARGET_NAME}")
endfunction()
Loading
Loading