From b56e90c4d2bd4403e12f1f8ce8f4f6543c0e8ff9 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 08:09:48 +1300 Subject: [PATCH 001/100] wip --- crates/optix/Cargo.toml | 8 +- crates/optix/build.rs | 98 ++ crates/{optix_sys => optix}/optix_stubs.c | 1 + .../optix.rs => optix/optix_wrapper.rs} | 915 ++++++++---------- crates/optix/src/context.rs | 2 +- crates/optix/src/denoiser.rs | 4 +- crates/optix/src/lib.rs | 3 +- crates/optix/src/module.rs | 25 + crates/optix/src/optix_wrapper.h | 27 + crates/optix/src/sys.rs | 64 ++ crates/optix_sys/Cargo.toml | 11 - crates/optix_sys/build.rs | 32 - crates/optix_sys/src/lib.rs | 10 - 13 files changed, 636 insertions(+), 564 deletions(-) create mode 100644 crates/optix/build.rs rename crates/{optix_sys => optix}/optix_stubs.c (99%) rename crates/{optix_sys/optix.rs => optix/optix_wrapper.rs} (60%) create mode 100644 crates/optix/src/module.rs create mode 100644 crates/optix/src/optix_wrapper.h create mode 100644 crates/optix/src/sys.rs delete mode 100644 crates/optix_sys/Cargo.toml delete mode 100644 crates/optix_sys/build.rs delete mode 100644 crates/optix_sys/src/lib.rs diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index a6ea88c8..7bf0ce70 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -4,5 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] -optix_sys = { version = "0.1", path = "../optix_sys" } cust = { version = "0.1", path = "../cust" } +cust_raw = { version = "0.11.2", path = "../cust_raw" } + +[build-dependencies] +bindgen = "0.55" +cc = "1.0.71" +find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } + diff --git a/crates/optix/build.rs b/crates/optix/build.rs new file mode 100644 index 00000000..d4ad5e20 --- /dev/null +++ b/crates/optix/build.rs @@ -0,0 +1,98 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; +use std::env; +use std::path::{Path, PathBuf}; + +// OptiX is a bit exotic in how it provides its functions. It uses a function table +// approach, a function table struct holds function pointers to every optix function. Then +// the Optix driver dll is loaded at runtime and the function table is loaded from that. +// OptiX provides this logic inside optix_stubs.h in the include dir, so we need to compile that +// to a lib and link it in so that we have the initialization and C function logic. +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + cuda_include = cuda_include.join("include"); + + bindgen_optix(&optix_include, &cuda_include); + + cc::Build::new() + .file("./optix_stubs.c") + .include(optix_include) + .include(cuda_include) + .cpp(false) + .compile("optix_stubs"); + + println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-lib=static=optix_stubs"); +} + +fn bindgen_optix(optix_include: &Path, cuda_include: &Path) { + let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("optix_wrapper.rs"); + + let header_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src") + .join("optix_wrapper.h"); + + let this_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("build.rs"); + + println!("cargo:rerun-if-changed={}", header_path.display()); + println!("cargo:rerun-if-changed={}", this_path.display()); + + let bindings = bindgen::Builder::default() + .header("src/optix_wrapper.h") + .clang_arg(format!("-I{}", optix_include.display())) + .clang_arg(format!("-I{}", cuda_include.display())) + .whitelist_recursively(false) + .whitelist_type("Optix.*") + .whitelist_type("RaygenRecord") + .whitelist_type("MissRecord") + .whitelist_type("HitgroupRecord") + .blacklist_type("OptixBuildInput") + .whitelist_function("optix.*") + .whitelist_var("OptixSbtRecordHeaderSize") + .whitelist_var("OptixSbtRecordAlignment") + .whitelist_var("OptixAccelBufferByteAlignment") + .whitelist_var("OptixInstanceByteAlignment") + .whitelist_var("OptixAabbBufferByteAlignment") + .whitelist_var("OptixGeometryTransformByteAlignment") + .whitelist_var("OptixTransformByteAlignment") + .whitelist_var("OptixVersion") + .whitelist_var("OptixBuildInputSize") + .layout_tests(false) + .generate_comments(false) + .newtype_enum("OptixResult") + .constified_enum_module("OptixCompileOptimizationLevel") + .constified_enum_module("OptixCompileDebugLevel") + .constified_enum_module("OptixTraversableGraphFlags") + .constified_enum_module("OptixExceptionFlags") + .constified_enum_module("OptixProgramGroupKind") + .constified_enum_module("OptixDeviceProperty") + .constified_enum_module("OptixPixelFormat") + .constified_enum_module("OptixDenoiserModelKind") + .rustified_enum("GeometryFlags") + .rustified_enum("OptixGeometryFlags") + .constified_enum("OptixVertexFormat") + .constified_enum("OptixIndicesFormat") + .rust_target(bindgen::RustTarget::Nightly) + .rustfmt_bindings(true) + .generate() + .expect("Unable to generate optix bindings"); + + let dbg_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + bindings + .write_to_file(dbg_path.join("optix_wrapper.rs")) + .expect("Couldn't write bindings!"); + + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} diff --git a/crates/optix_sys/optix_stubs.c b/crates/optix/optix_stubs.c similarity index 99% rename from crates/optix_sys/optix_stubs.c rename to crates/optix/optix_stubs.c index 01529541..3325d867 100644 --- a/crates/optix_sys/optix_stubs.c +++ b/crates/optix/optix_stubs.c @@ -603,3 +603,4 @@ extern "C" #ifdef __cplusplus } #endif + diff --git a/crates/optix_sys/optix.rs b/crates/optix/optix_wrapper.rs similarity index 60% rename from crates/optix_sys/optix.rs rename to crates/optix/optix_wrapper.rs index 2643576e..60ae295c 100644 --- a/crates/optix_sys/optix.rs +++ b/crates/optix/optix_wrapper.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.58.1 */ +/* automatically generated by rust-bindgen 0.55.1 */ #[repr(C)] pub struct __BindgenUnionField(::std::marker::PhantomData); @@ -43,16 +43,6 @@ impl ::std::cmp::PartialEq for __BindgenUnionField { } } impl ::std::cmp::Eq for __BindgenUnionField {} -pub const OPTIX_VERSION: u32 = 70300; -pub const OPTIX_SBT_RECORD_ALIGNMENT: u32 = 16; -pub const OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT: u32 = 128; -pub const OPTIX_INSTANCE_BYTE_ALIGNMENT: u32 = 16; -pub const OPTIX_AABB_BUFFER_BYTE_ALIGNMENT: u32 = 8; -pub const OPTIX_GEOMETRY_TRANSFORM_BYTE_ALIGNMENT: u32 = 16; -pub const OPTIX_TRANSFORM_BYTE_ALIGNMENT: u32 = 64; -pub const OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT: u32 = 0; -pub const OPTIX_COMPILE_DEFAULT_MAX_PAYLOAD_VALUE_COUNT: u32 = 8; -pub const OPTIX_ABI_VERSION: u32 = 47; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct OptixDeviceContext_t { @@ -85,59 +75,131 @@ pub struct OptixDenoiser_t { pub type OptixDenoiser = *mut OptixDenoiser_t; pub type OptixTraversableHandle = ::std::os::raw::c_ulonglong; pub type OptixVisibilityMask = ::std::os::raw::c_uint; -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixResult { - OPTIX_SUCCESS = 0, - OPTIX_ERROR_INVALID_VALUE = 7001, - OPTIX_ERROR_HOST_OUT_OF_MEMORY = 7002, - OPTIX_ERROR_INVALID_OPERATION = 7003, - OPTIX_ERROR_FILE_IO_ERROR = 7004, - OPTIX_ERROR_INVALID_FILE_FORMAT = 7005, - OPTIX_ERROR_DISK_CACHE_INVALID_PATH = 7010, - OPTIX_ERROR_DISK_CACHE_PERMISSION_ERROR = 7011, - OPTIX_ERROR_DISK_CACHE_DATABASE_ERROR = 7012, - OPTIX_ERROR_DISK_CACHE_INVALID_DATA = 7013, - OPTIX_ERROR_LAUNCH_FAILURE = 7050, - OPTIX_ERROR_INVALID_DEVICE_CONTEXT = 7051, - OPTIX_ERROR_CUDA_NOT_INITIALIZED = 7052, - OPTIX_ERROR_VALIDATION_FAILURE = 7053, - OPTIX_ERROR_INVALID_PTX = 7200, - OPTIX_ERROR_INVALID_LAUNCH_PARAMETER = 7201, - OPTIX_ERROR_INVALID_PAYLOAD_ACCESS = 7202, - OPTIX_ERROR_INVALID_ATTRIBUTE_ACCESS = 7203, - OPTIX_ERROR_INVALID_FUNCTION_USE = 7204, - OPTIX_ERROR_INVALID_FUNCTION_ARGUMENTS = 7205, - OPTIX_ERROR_PIPELINE_OUT_OF_CONSTANT_MEMORY = 7250, - OPTIX_ERROR_PIPELINE_LINK_ERROR = 7251, - OPTIX_ERROR_ILLEGAL_DURING_TASK_EXECUTE = 7270, - OPTIX_ERROR_INTERNAL_COMPILER_ERROR = 7299, - OPTIX_ERROR_DENOISER_MODEL_NOT_SET = 7300, - OPTIX_ERROR_DENOISER_NOT_INITIALIZED = 7301, - OPTIX_ERROR_ACCEL_NOT_COMPATIBLE = 7400, - OPTIX_ERROR_NOT_SUPPORTED = 7800, - OPTIX_ERROR_UNSUPPORTED_ABI_VERSION = 7801, - OPTIX_ERROR_FUNCTION_TABLE_SIZE_MISMATCH = 7802, - OPTIX_ERROR_INVALID_ENTRY_FUNCTION_OPTIONS = 7803, - OPTIX_ERROR_LIBRARY_NOT_FOUND = 7804, - OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND = 7805, - OPTIX_ERROR_LIBRARY_UNLOAD_FAILURE = 7806, - OPTIX_ERROR_CUDA_ERROR = 7900, - OPTIX_ERROR_INTERNAL_ERROR = 7990, - OPTIX_ERROR_UNKNOWN = 7999, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixDeviceProperty { - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH = 8193, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH = 8194, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS = 8195, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS = 8196, - OPTIX_DEVICE_PROPERTY_RTCORE_VERSION = 8197, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID = 8198, - OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK = 8199, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS = 8200, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET = 8201, +impl OptixResult { + pub const OPTIX_SUCCESS: OptixResult = OptixResult(0); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_VALUE: OptixResult = OptixResult(7001); +} +impl OptixResult { + pub const OPTIX_ERROR_HOST_OUT_OF_MEMORY: OptixResult = OptixResult(7002); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_OPERATION: OptixResult = OptixResult(7003); +} +impl OptixResult { + pub const OPTIX_ERROR_FILE_IO_ERROR: OptixResult = OptixResult(7004); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_FILE_FORMAT: OptixResult = OptixResult(7005); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_INVALID_PATH: OptixResult = OptixResult(7010); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_PERMISSION_ERROR: OptixResult = OptixResult(7011); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_DATABASE_ERROR: OptixResult = OptixResult(7012); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_INVALID_DATA: OptixResult = OptixResult(7013); +} +impl OptixResult { + pub const OPTIX_ERROR_LAUNCH_FAILURE: OptixResult = OptixResult(7050); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_DEVICE_CONTEXT: OptixResult = OptixResult(7051); +} +impl OptixResult { + pub const OPTIX_ERROR_CUDA_NOT_INITIALIZED: OptixResult = OptixResult(7052); +} +impl OptixResult { + pub const OPTIX_ERROR_VALIDATION_FAILURE: OptixResult = OptixResult(7053); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_PTX: OptixResult = OptixResult(7200); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_LAUNCH_PARAMETER: OptixResult = OptixResult(7201); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_PAYLOAD_ACCESS: OptixResult = OptixResult(7202); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_ATTRIBUTE_ACCESS: OptixResult = OptixResult(7203); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_FUNCTION_USE: OptixResult = OptixResult(7204); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_FUNCTION_ARGUMENTS: OptixResult = OptixResult(7205); +} +impl OptixResult { + pub const OPTIX_ERROR_PIPELINE_OUT_OF_CONSTANT_MEMORY: OptixResult = OptixResult(7250); +} +impl OptixResult { + pub const OPTIX_ERROR_PIPELINE_LINK_ERROR: OptixResult = OptixResult(7251); +} +impl OptixResult { + pub const OPTIX_ERROR_ILLEGAL_DURING_TASK_EXECUTE: OptixResult = OptixResult(7270); +} +impl OptixResult { + pub const OPTIX_ERROR_INTERNAL_COMPILER_ERROR: OptixResult = OptixResult(7299); +} +impl OptixResult { + pub const OPTIX_ERROR_DENOISER_MODEL_NOT_SET: OptixResult = OptixResult(7300); +} +impl OptixResult { + pub const OPTIX_ERROR_DENOISER_NOT_INITIALIZED: OptixResult = OptixResult(7301); +} +impl OptixResult { + pub const OPTIX_ERROR_ACCEL_NOT_COMPATIBLE: OptixResult = OptixResult(7400); +} +impl OptixResult { + pub const OPTIX_ERROR_NOT_SUPPORTED: OptixResult = OptixResult(7800); +} +impl OptixResult { + pub const OPTIX_ERROR_UNSUPPORTED_ABI_VERSION: OptixResult = OptixResult(7801); +} +impl OptixResult { + pub const OPTIX_ERROR_FUNCTION_TABLE_SIZE_MISMATCH: OptixResult = OptixResult(7802); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_ENTRY_FUNCTION_OPTIONS: OptixResult = OptixResult(7803); +} +impl OptixResult { + pub const OPTIX_ERROR_LIBRARY_NOT_FOUND: OptixResult = OptixResult(7804); +} +impl OptixResult { + pub const OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND: OptixResult = OptixResult(7805); +} +impl OptixResult { + pub const OPTIX_ERROR_LIBRARY_UNLOAD_FAILURE: OptixResult = OptixResult(7806); +} +impl OptixResult { + pub const OPTIX_ERROR_CUDA_ERROR: OptixResult = OptixResult(7900); +} +impl OptixResult { + pub const OPTIX_ERROR_INTERNAL_ERROR: OptixResult = OptixResult(7990); +} +impl OptixResult { + pub const OPTIX_ERROR_UNKNOWN: OptixResult = OptixResult(7999); +} +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct OptixResult(pub ::std::os::raw::c_uint); +pub mod OptixDeviceProperty { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH: Type = 8193; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH: Type = 8194; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS: Type = 8195; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS: Type = 8196; + pub const OPTIX_DEVICE_PROPERTY_RTCORE_VERSION: Type = 8197; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID: Type = 8198; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK: Type = 8199; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS: Type = 8200; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET: Type = 8201; } pub type OptixLogCallback = ::std::option::Option< unsafe extern "C" fn( @@ -147,62 +209,37 @@ pub type OptixLogCallback = ::std::option::Option< cbdata: *mut ::std::os::raw::c_void, ), >; -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixDeviceContextValidationMode { - OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_OFF = 0, - OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL = -1, -} +pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_OFF: + OptixDeviceContextValidationMode = 0; +pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL: + OptixDeviceContextValidationMode = 4294967295; +pub type OptixDeviceContextValidationMode = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixDeviceContextOptions { pub logCallbackFunction: OptixLogCallback, pub logCallbackData: *mut ::std::os::raw::c_void, pub logCallbackLevel: ::std::os::raw::c_int, pub validationMode: OptixDeviceContextValidationMode, } -impl Default for OptixDeviceContextOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixGeometryFlags { - OPTIX_GEOMETRY_FLAG_NONE = 0, - OPTIX_GEOMETRY_FLAG_DISABLE_ANYHIT = 1, - OPTIX_GEOMETRY_FLAG_REQUIRE_SINGLE_ANYHIT_CALL = 2, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixHitKind { - OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE = 254, - OPTIX_HIT_KIND_TRIANGLE_BACK_FACE = 255, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixIndicesFormat { - OPTIX_INDICES_FORMAT_NONE = 0, - OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3 = 8450, - OPTIX_INDICES_FORMAT_UNSIGNED_INT3 = 8451, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixVertexFormat { - OPTIX_VERTEX_FORMAT_NONE = 0, - OPTIX_VERTEX_FORMAT_FLOAT3 = 8481, - OPTIX_VERTEX_FORMAT_FLOAT2 = 8482, - OPTIX_VERTEX_FORMAT_HALF3 = 8483, - OPTIX_VERTEX_FORMAT_HALF2 = 8484, - OPTIX_VERTEX_FORMAT_SNORM16_3 = 8485, - OPTIX_VERTEX_FORMAT_SNORM16_2 = 8486, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTransformFormat { - OPTIX_TRANSFORM_FORMAT_NONE = 0, - OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 = 8673, -} +pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE: OptixHitKind = 254; +pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_BACK_FACE: OptixHitKind = 255; +pub type OptixHitKind = ::std::os::raw::c_uint; +pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE: OptixIndicesFormat = 0; +pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3: OptixIndicesFormat = 8450; +pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3: OptixIndicesFormat = 8451; +pub type OptixIndicesFormat = ::std::os::raw::c_uint; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_NONE: OptixVertexFormat = 0; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT3: OptixVertexFormat = 8481; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT2: OptixVertexFormat = 8482; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF3: OptixVertexFormat = 8483; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF2: OptixVertexFormat = 8484; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_3: OptixVertexFormat = 8485; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_2: OptixVertexFormat = 8486; +pub type OptixVertexFormat = ::std::os::raw::c_uint; +pub const OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE: OptixTransformFormat = 0; +pub const OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12: OptixTransformFormat = 8673; +pub type OptixTransformFormat = ::std::os::raw::c_uint; #[repr(C)] pub struct OptixBuildInputTriangleArray { pub vertexBuffers: *const CUdeviceptr, @@ -222,29 +259,23 @@ pub struct OptixBuildInputTriangleArray { pub primitiveIndexOffset: ::std::os::raw::c_uint, pub transformFormat: OptixTransformFormat, } -impl Default for OptixBuildInputTriangleArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixPrimitiveType { - OPTIX_PRIMITIVE_TYPE_CUSTOM = 9472, - OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE = 9473, - OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE = 9474, - OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR = 9475, - OPTIX_PRIMITIVE_TYPE_TRIANGLE = 9521, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixPrimitiveTypeFlags { - OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM = 1, - OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE = 2, - OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE = 4, - OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR = 8, - OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE = -2147483648, -} +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_CUSTOM: OptixPrimitiveType = 9472; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE: OptixPrimitiveType = + 9473; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE: OptixPrimitiveType = 9474; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR: OptixPrimitiveType = 9475; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_TRIANGLE: OptixPrimitiveType = 9521; +pub type OptixPrimitiveType = ::std::os::raw::c_uint; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM: OptixPrimitiveTypeFlags = 1; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE: + OptixPrimitiveTypeFlags = 2; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE: + OptixPrimitiveTypeFlags = 4; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR: OptixPrimitiveTypeFlags = + 8; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE: OptixPrimitiveTypeFlags = + -2147483648; +pub type OptixPrimitiveTypeFlags = ::std::os::raw::c_int; #[repr(C)] pub struct OptixBuildInputCurveArray { pub curveType: OptixPrimitiveType, @@ -261,13 +292,8 @@ pub struct OptixBuildInputCurveArray { pub flag: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } -impl Default for OptixBuildInputCurveArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixAabb { pub minX: f32, pub minY: f32, @@ -288,35 +314,17 @@ pub struct OptixBuildInputCustomPrimitiveArray { pub sbtIndexOffsetStrideInBytes: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } -impl Default for OptixBuildInputCustomPrimitiveArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] pub struct OptixBuildInputInstanceArray { pub instances: CUdeviceptr, pub numInstances: ::std::os::raw::c_uint, } -impl Default for OptixBuildInputInstanceArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixBuildInputType { - OPTIX_BUILD_INPUT_TYPE_TRIANGLES = 8513, - OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES = 8514, - OPTIX_BUILD_INPUT_TYPE_INSTANCES = 8515, - OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS = 8516, - OPTIX_BUILD_INPUT_TYPE_CURVES = 8517, -} -#[repr(C)] -pub struct OptixBuildInput { - pub type_: OptixBuildInputType, - pub __bindgen_anon_1: OptixBuildInput__bindgen_ty_1, -} +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES: OptixBuildInputType = 8513; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES: OptixBuildInputType = 8514; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES: OptixBuildInputType = 8515; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS: OptixBuildInputType = 8516; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CURVES: OptixBuildInputType = 8517; +pub type OptixBuildInputType = ::std::os::raw::c_uint; #[repr(C)] pub struct OptixBuildInput__bindgen_ty_1 { pub triangleArray: __BindgenUnionField, @@ -326,28 +334,16 @@ pub struct OptixBuildInput__bindgen_ty_1 { pub pad: __BindgenUnionField<[::std::os::raw::c_char; 1024usize]>, pub bindgen_union_field: [u64; 128usize], } -impl Default for OptixBuildInput__bindgen_ty_1 { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -impl Default for OptixBuildInput { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixInstanceFlags { - OPTIX_INSTANCE_FLAG_NONE = 0, - OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING = 1, - OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING = 2, - OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT = 4, - OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT = 8, - OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM = 64, -} +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE: OptixInstanceFlags = 0; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING: OptixInstanceFlags = + 1; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING: OptixInstanceFlags = 2; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT: OptixInstanceFlags = 4; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT: OptixInstanceFlags = 8; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM: OptixInstanceFlags = 64; +pub type OptixInstanceFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixInstance { pub transform: [f32; 12usize], pub instanceId: ::std::os::raw::c_uint, @@ -357,32 +353,23 @@ pub struct OptixInstance { pub traversableHandle: OptixTraversableHandle, pub pad: [::std::os::raw::c_uint; 2usize], } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixBuildFlags { - OPTIX_BUILD_FLAG_NONE = 0, - OPTIX_BUILD_FLAG_ALLOW_UPDATE = 1, - OPTIX_BUILD_FLAG_ALLOW_COMPACTION = 2, - OPTIX_BUILD_FLAG_PREFER_FAST_TRACE = 4, - OPTIX_BUILD_FLAG_PREFER_FAST_BUILD = 8, - OPTIX_BUILD_FLAG_ALLOW_RANDOM_VERTEX_ACCESS = 16, - OPTIX_BUILD_FLAG_ALLOW_RANDOM_INSTANCE_ACCESS = 32, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixBuildOperation { - OPTIX_BUILD_OPERATION_BUILD = 8545, - OPTIX_BUILD_OPERATION_UPDATE = 8546, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixMotionFlags { - OPTIX_MOTION_FLAG_NONE = 0, - OPTIX_MOTION_FLAG_START_VANISH = 1, - OPTIX_MOTION_FLAG_END_VANISH = 2, -} +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_NONE: OptixBuildFlags = 0; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_UPDATE: OptixBuildFlags = 1; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_COMPACTION: OptixBuildFlags = 2; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_TRACE: OptixBuildFlags = 4; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_BUILD: OptixBuildFlags = 8; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_VERTEX_ACCESS: OptixBuildFlags = 16; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_INSTANCE_ACCESS: OptixBuildFlags = 32; +pub type OptixBuildFlags = ::std::os::raw::c_uint; +pub const OptixBuildOperation_OPTIX_BUILD_OPERATION_BUILD: OptixBuildOperation = 8545; +pub const OptixBuildOperation_OPTIX_BUILD_OPERATION_UPDATE: OptixBuildOperation = 8546; +pub type OptixBuildOperation = ::std::os::raw::c_uint; +pub const OptixMotionFlags_OPTIX_MOTION_FLAG_NONE: OptixMotionFlags = 0; +pub const OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH: OptixMotionFlags = 1; +pub const OptixMotionFlags_OPTIX_MOTION_FLAG_END_VANISH: OptixMotionFlags = 2; +pub type OptixMotionFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixMotionOptions { pub numKeys: ::std::os::raw::c_ushort, pub flags: ::std::os::raw::c_ushort, @@ -390,47 +377,33 @@ pub struct OptixMotionOptions { pub timeEnd: f32, } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixAccelBuildOptions { pub buildFlags: ::std::os::raw::c_uint, pub operation: OptixBuildOperation, pub motionOptions: OptixMotionOptions, } -impl Default for OptixAccelBuildOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] pub struct OptixAccelBufferSizes { - pub outputSizeInBytes: usize, - pub tempSizeInBytes: usize, - pub tempUpdateSizeInBytes: usize, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixAccelPropertyType { - OPTIX_PROPERTY_TYPE_COMPACTED_SIZE = 8577, - OPTIX_PROPERTY_TYPE_AABBS = 8578, + pub outputSizeInBytes: size_t, + pub tempSizeInBytes: size_t, + pub tempUpdateSizeInBytes: size_t, } +pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE: OptixAccelPropertyType = 8577; +pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS: OptixAccelPropertyType = 8578; +pub type OptixAccelPropertyType = ::std::os::raw::c_uint; #[repr(C)] pub struct OptixAccelEmitDesc { pub result: CUdeviceptr, pub type_: OptixAccelPropertyType, } -impl Default for OptixAccelEmitDesc { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixAccelRelocationInfo { pub info: [::std::os::raw::c_ulonglong; 4usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixStaticTransform { pub child: OptixTraversableHandle, pub pad: [::std::os::raw::c_uint; 2usize], @@ -438,7 +411,7 @@ pub struct OptixStaticTransform { pub invTransform: [f32; 12usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixMatrixMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, @@ -446,7 +419,7 @@ pub struct OptixMatrixMotionTransform { pub transform: [[f32; 12usize]; 2usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixSRTData { pub sx: f32, pub a: f32, @@ -466,31 +439,29 @@ pub struct OptixSRTData { pub tz: f32, } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixSRTMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, pub pad: [::std::os::raw::c_uint; 3usize], pub srtData: [OptixSRTData; 2usize], } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTraversableType { - OPTIX_TRAVERSABLE_TYPE_STATIC_TRANSFORM = 8641, - OPTIX_TRAVERSABLE_TYPE_MATRIX_MOTION_TRANSFORM = 8642, - OPTIX_TRAVERSABLE_TYPE_SRT_MOTION_TRANSFORM = 8643, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixPixelFormat { - OPTIX_PIXEL_FORMAT_HALF2 = 8711, - OPTIX_PIXEL_FORMAT_HALF3 = 8705, - OPTIX_PIXEL_FORMAT_HALF4 = 8706, - OPTIX_PIXEL_FORMAT_FLOAT2 = 8712, - OPTIX_PIXEL_FORMAT_FLOAT3 = 8707, - OPTIX_PIXEL_FORMAT_FLOAT4 = 8708, - OPTIX_PIXEL_FORMAT_UCHAR3 = 8709, - OPTIX_PIXEL_FORMAT_UCHAR4 = 8710, +pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_STATIC_TRANSFORM: OptixTraversableType = 8641; +pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_MATRIX_MOTION_TRANSFORM: + OptixTraversableType = 8642; +pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_SRT_MOTION_TRANSFORM: OptixTraversableType = + 8643; +pub type OptixTraversableType = ::std::os::raw::c_uint; +pub mod OptixPixelFormat { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_PIXEL_FORMAT_HALF2: Type = 8711; + pub const OPTIX_PIXEL_FORMAT_HALF3: Type = 8705; + pub const OPTIX_PIXEL_FORMAT_HALF4: Type = 8706; + pub const OPTIX_PIXEL_FORMAT_FLOAT2: Type = 8712; + pub const OPTIX_PIXEL_FORMAT_FLOAT3: Type = 8707; + pub const OPTIX_PIXEL_FORMAT_FLOAT4: Type = 8708; + pub const OPTIX_PIXEL_FORMAT_UCHAR3: Type = 8709; + pub const OPTIX_PIXEL_FORMAT_UCHAR4: Type = 8710; } #[repr(C)] pub struct OptixImage2D { @@ -499,23 +470,17 @@ pub struct OptixImage2D { pub height: ::std::os::raw::c_uint, pub rowStrideInBytes: ::std::os::raw::c_uint, pub pixelStrideInBytes: ::std::os::raw::c_uint, - pub format: OptixPixelFormat, + pub format: OptixPixelFormat::Type, } -impl Default for OptixImage2D { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixDenoiserModelKind { - OPTIX_DENOISER_MODEL_KIND_LDR = 8994, - OPTIX_DENOISER_MODEL_KIND_HDR = 8995, - OPTIX_DENOISER_MODEL_KIND_AOV = 8996, - OPTIX_DENOISER_MODEL_KIND_TEMPORAL = 8997, +pub mod OptixDenoiserModelKind { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_DENOISER_MODEL_KIND_LDR: Type = 8994; + pub const OPTIX_DENOISER_MODEL_KIND_HDR: Type = 8995; + pub const OPTIX_DENOISER_MODEL_KIND_AOV: Type = 8996; + pub const OPTIX_DENOISER_MODEL_KIND_TEMPORAL: Type = 8997; } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixDenoiserOptions { pub guideAlbedo: ::std::os::raw::c_uint, pub guideNormal: ::std::os::raw::c_uint, @@ -526,22 +491,12 @@ pub struct OptixDenoiserGuideLayer { pub normal: OptixImage2D, pub flow: OptixImage2D, } -impl Default for OptixDenoiserGuideLayer { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] pub struct OptixDenoiserLayer { pub input: OptixImage2D, pub previousOutput: OptixImage2D, pub output: OptixImage2D, } -impl Default for OptixDenoiserLayer { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] pub struct OptixDenoiserParams { pub denoiseAlpha: ::std::os::raw::c_uint, @@ -549,119 +504,84 @@ pub struct OptixDenoiserParams { pub blendFactor: f32, pub hdrAverageColor: CUdeviceptr, } -impl Default for OptixDenoiserParams { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] pub struct OptixDenoiserSizes { - pub stateSizeInBytes: usize, - pub withOverlapScratchSizeInBytes: usize, - pub withoutOverlapScratchSizeInBytes: usize, + pub stateSizeInBytes: size_t, + pub withOverlapScratchSizeInBytes: size_t, + pub withoutOverlapScratchSizeInBytes: size_t, pub overlapWindowSizeInPixels: ::std::os::raw::c_uint, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixRayFlags { - OPTIX_RAY_FLAG_NONE = 0, - OPTIX_RAY_FLAG_DISABLE_ANYHIT = 1, - OPTIX_RAY_FLAG_ENFORCE_ANYHIT = 2, - OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT = 4, - OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT = 8, - OPTIX_RAY_FLAG_CULL_BACK_FACING_TRIANGLES = 16, - OPTIX_RAY_FLAG_CULL_FRONT_FACING_TRIANGLES = 32, - OPTIX_RAY_FLAG_CULL_DISABLED_ANYHIT = 64, - OPTIX_RAY_FLAG_CULL_ENFORCED_ANYHIT = 128, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTransformType { - OPTIX_TRANSFORM_TYPE_NONE = 0, - OPTIX_TRANSFORM_TYPE_STATIC_TRANSFORM = 1, - OPTIX_TRANSFORM_TYPE_MATRIX_MOTION_TRANSFORM = 2, - OPTIX_TRANSFORM_TYPE_SRT_MOTION_TRANSFORM = 3, - OPTIX_TRANSFORM_TYPE_INSTANCE = 4, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTraversableGraphFlags { - OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY = 0, - OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS = 1, - OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING = 2, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixCompileOptimizationLevel { - OPTIX_COMPILE_OPTIMIZATION_DEFAULT = 0, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_0 = 9024, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_1 = 9025, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_2 = 9026, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_3 = 9027, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixCompileDebugLevel { - OPTIX_COMPILE_DEBUG_LEVEL_DEFAULT = 0, - OPTIX_COMPILE_DEBUG_LEVEL_NONE = 9040, - OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO = 9041, - OPTIX_COMPILE_DEBUG_LEVEL_FULL = 9042, +pub const OptixRayFlags_OPTIX_RAY_FLAG_NONE: OptixRayFlags = 0; +pub const OptixRayFlags_OPTIX_RAY_FLAG_DISABLE_ANYHIT: OptixRayFlags = 1; +pub const OptixRayFlags_OPTIX_RAY_FLAG_ENFORCE_ANYHIT: OptixRayFlags = 2; +pub const OptixRayFlags_OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT: OptixRayFlags = 4; +pub const OptixRayFlags_OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT: OptixRayFlags = 8; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_BACK_FACING_TRIANGLES: OptixRayFlags = 16; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_FRONT_FACING_TRIANGLES: OptixRayFlags = 32; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_DISABLED_ANYHIT: OptixRayFlags = 64; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_ENFORCED_ANYHIT: OptixRayFlags = 128; +pub type OptixRayFlags = ::std::os::raw::c_uint; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_NONE: OptixTransformType = 0; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_STATIC_TRANSFORM: OptixTransformType = 1; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_MATRIX_MOTION_TRANSFORM: OptixTransformType = 2; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_SRT_MOTION_TRANSFORM: OptixTransformType = 3; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_INSTANCE: OptixTransformType = 4; +pub type OptixTransformType = ::std::os::raw::c_uint; +pub mod OptixTraversableGraphFlags { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY: Type = 0; + pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS: Type = 1; + pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING: Type = 2; +} +pub mod OptixCompileOptimizationLevel { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_COMPILE_OPTIMIZATION_DEFAULT: Type = 0; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_0: Type = 9024; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_1: Type = 9025; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_2: Type = 9026; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_3: Type = 9027; +} +pub mod OptixCompileDebugLevel { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_COMPILE_DEBUG_LEVEL_DEFAULT: Type = 0; + pub const OPTIX_COMPILE_DEBUG_LEVEL_NONE: Type = 9040; + pub const OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO: Type = 9041; + pub const OPTIX_COMPILE_DEBUG_LEVEL_FULL: Type = 9042; } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct OptixModuleCompileBoundValueEntry { - pub pipelineParamOffsetInBytes: usize, - pub sizeInBytes: usize, + pub pipelineParamOffsetInBytes: size_t, + pub sizeInBytes: size_t, pub boundValuePtr: *const ::std::os::raw::c_void, pub annotation: *const ::std::os::raw::c_char, } -impl Default for OptixModuleCompileBoundValueEntry { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixModuleCompileOptions { pub maxRegisterCount: ::std::os::raw::c_int, - pub optLevel: OptixCompileOptimizationLevel, - pub debugLevel: OptixCompileDebugLevel, + pub optLevel: OptixCompileOptimizationLevel::Type, + pub debugLevel: OptixCompileDebugLevel::Type, pub boundValues: *const OptixModuleCompileBoundValueEntry, pub numBoundValues: ::std::os::raw::c_uint, } -impl Default for OptixModuleCompileOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixProgramGroupKind { - OPTIX_PROGRAM_GROUP_KIND_RAYGEN = 9249, - OPTIX_PROGRAM_GROUP_KIND_MISS = 9250, - OPTIX_PROGRAM_GROUP_KIND_EXCEPTION = 9251, - OPTIX_PROGRAM_GROUP_KIND_HITGROUP = 9252, - OPTIX_PROGRAM_GROUP_KIND_CALLABLES = 9253, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixProgramGroupFlags { - OPTIX_PROGRAM_GROUP_FLAGS_NONE = 0, +pub mod OptixProgramGroupKind { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_PROGRAM_GROUP_KIND_RAYGEN: Type = 9249; + pub const OPTIX_PROGRAM_GROUP_KIND_MISS: Type = 9250; + pub const OPTIX_PROGRAM_GROUP_KIND_EXCEPTION: Type = 9251; + pub const OPTIX_PROGRAM_GROUP_KIND_HITGROUP: Type = 9252; + pub const OPTIX_PROGRAM_GROUP_KIND_CALLABLES: Type = 9253; } +pub const OptixProgramGroupFlags_OPTIX_PROGRAM_GROUP_FLAGS_NONE: OptixProgramGroupFlags = 0; +pub type OptixProgramGroupFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupSingleModule { pub module: OptixModule, pub entryFunctionName: *const ::std::os::raw::c_char, } -impl Default for OptixProgramGroupSingleModule { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupHitgroup { pub moduleCH: OptixModule, pub entryFunctionNameCH: *const ::std::os::raw::c_char, @@ -670,28 +590,18 @@ pub struct OptixProgramGroupHitgroup { pub moduleIS: OptixModule, pub entryFunctionNameIS: *const ::std::os::raw::c_char, } -impl Default for OptixProgramGroupHitgroup { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupCallables { pub moduleDC: OptixModule, pub entryFunctionNameDC: *const ::std::os::raw::c_char, pub moduleCC: OptixModule, pub entryFunctionNameCC: *const ::std::os::raw::c_char, } -impl Default for OptixProgramGroupCallables { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] #[derive(Copy, Clone)] pub struct OptixProgramGroupDesc { - pub kind: OptixProgramGroupKind, + pub kind: OptixProgramGroupKind::Type, pub flags: ::std::os::raw::c_uint, pub __bindgen_anon_1: OptixProgramGroupDesc__bindgen_ty_1, } @@ -703,55 +613,54 @@ pub union OptixProgramGroupDesc__bindgen_ty_1 { pub exception: OptixProgramGroupSingleModule, pub callables: OptixProgramGroupCallables, pub hitgroup: OptixProgramGroupHitgroup, -} -impl Default for OptixProgramGroupDesc__bindgen_ty_1 { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -impl Default for OptixProgramGroupDesc { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } + _bindgen_union_align: [u64; 6usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupOptions { pub reserved: ::std::os::raw::c_int, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixExceptionCodes { - OPTIX_EXCEPTION_CODE_STACK_OVERFLOW = -1, - OPTIX_EXCEPTION_CODE_TRACE_DEPTH_EXCEEDED = -2, - OPTIX_EXCEPTION_CODE_TRAVERSAL_DEPTH_EXCEEDED = -3, - OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_TRAVERSABLE = -5, - OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_MISS_SBT = -6, - OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_HIT_SBT = -7, - OPTIX_EXCEPTION_CODE_UNSUPPORTED_PRIMITIVE_TYPE = -8, - OPTIX_EXCEPTION_CODE_INVALID_RAY = -9, - OPTIX_EXCEPTION_CODE_CALLABLE_PARAMETER_MISMATCH = -10, - OPTIX_EXCEPTION_CODE_BUILTIN_IS_MISMATCH = -11, - OPTIX_EXCEPTION_CODE_CALLABLE_INVALID_SBT = -12, - OPTIX_EXCEPTION_CODE_CALLABLE_NO_DC_SBT_RECORD = -13, - OPTIX_EXCEPTION_CODE_CALLABLE_NO_CC_SBT_RECORD = -14, - OPTIX_EXCEPTION_CODE_UNSUPPORTED_SINGLE_LEVEL_GAS = -15, - OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_0 = -16, - OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_1 = -17, - OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_2 = -18, - OPTIX_EXCEPTION_CODE_UNSUPPORTED_DATA_ACCESS = -32, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixExceptionFlags { - OPTIX_EXCEPTION_FLAG_NONE = 0, - OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW = 1, - OPTIX_EXCEPTION_FLAG_TRACE_DEPTH = 2, - OPTIX_EXCEPTION_FLAG_USER = 4, - OPTIX_EXCEPTION_FLAG_DEBUG = 8, +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_STACK_OVERFLOW: OptixExceptionCodes = -1; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRACE_DEPTH_EXCEEDED: OptixExceptionCodes = -2; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_DEPTH_EXCEEDED: OptixExceptionCodes = + -3; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_TRAVERSABLE: + OptixExceptionCodes = -5; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_MISS_SBT: OptixExceptionCodes = + -6; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_HIT_SBT: OptixExceptionCodes = + -7; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_PRIMITIVE_TYPE: OptixExceptionCodes = + -8; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_RAY: OptixExceptionCodes = -9; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_PARAMETER_MISMATCH: + OptixExceptionCodes = -10; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_BUILTIN_IS_MISMATCH: OptixExceptionCodes = -11; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_INVALID_SBT: OptixExceptionCodes = -12; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_NO_DC_SBT_RECORD: OptixExceptionCodes = + -13; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_NO_CC_SBT_RECORD: OptixExceptionCodes = + -14; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_SINGLE_LEVEL_GAS: + OptixExceptionCodes = -15; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_0: OptixExceptionCodes = + -16; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_1: OptixExceptionCodes = + -17; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_2: OptixExceptionCodes = + -18; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_DATA_ACCESS: OptixExceptionCodes = + -32; +pub type OptixExceptionCodes = ::std::os::raw::c_int; +pub mod OptixExceptionFlags { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_EXCEPTION_FLAG_NONE: Type = 0; + pub const OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW: Type = 1; + pub const OPTIX_EXCEPTION_FLAG_TRACE_DEPTH: Type = 2; + pub const OPTIX_EXCEPTION_FLAG_USER: Type = 4; + pub const OPTIX_EXCEPTION_FLAG_DEBUG: Type = 8; } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct OptixPipelineCompileOptions { pub usesMotionBlur: ::std::os::raw::c_int, pub traversableGraphFlags: ::std::os::raw::c_uint, @@ -761,23 +670,13 @@ pub struct OptixPipelineCompileOptions { pub pipelineLaunchParamsVariableName: *const ::std::os::raw::c_char, pub usesPrimitiveTypeFlags: ::std::os::raw::c_uint, pub reserved: ::std::os::raw::c_uint, - pub reserved2: usize, -} -impl Default for OptixPipelineCompileOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } + pub reserved2: size_t, } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixPipelineLinkOptions { pub maxTraceDepth: ::std::os::raw::c_uint, - pub debugLevel: OptixCompileDebugLevel, -} -impl Default for OptixPipelineLinkOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } + pub debugLevel: OptixCompileDebugLevel::Type, } #[repr(C)] pub struct OptixShaderBindingTable { @@ -793,13 +692,8 @@ pub struct OptixShaderBindingTable { pub callablesRecordStrideInBytes: ::std::os::raw::c_uint, pub callablesRecordCount: ::std::os::raw::c_uint, } -impl Default for OptixShaderBindingTable { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixStackSizes { pub cssRG: ::std::os::raw::c_uint, pub cssMS: ::std::os::raw::c_uint, @@ -809,11 +703,9 @@ pub struct OptixStackSizes { pub cssCC: ::std::os::raw::c_uint, pub dssDC: ::std::os::raw::c_uint, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixQueryFunctionTableOptions { - OPTIX_QUERY_FUNCTION_TABLE_OPTION_DUMMY = 0, -} +pub const OptixQueryFunctionTableOptions_OPTIX_QUERY_FUNCTION_TABLE_OPTION_DUMMY: + OptixQueryFunctionTableOptions = 0; +pub type OptixQueryFunctionTableOptions = ::std::os::raw::c_uint; pub type OptixQueryFunctionTable_t = ::std::option::Option< unsafe extern "C" fn( abiId: ::std::os::raw::c_int, @@ -821,20 +713,15 @@ pub type OptixQueryFunctionTable_t = ::std::option::Option< arg1: *mut OptixQueryFunctionTableOptions, arg2: *mut *const ::std::os::raw::c_void, functionTable: *mut ::std::os::raw::c_void, - sizeOfTable: usize, + sizeOfTable: size_t, ) -> OptixResult, >; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixBuiltinISOptions { pub builtinISModuleType: OptixPrimitiveType, pub usesMotionBlur: ::std::os::raw::c_int, } -impl Default for OptixBuiltinISOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} extern "C" { pub fn optixGetErrorName(result: OptixResult) -> *const ::std::os::raw::c_char; } @@ -854,9 +741,9 @@ extern "C" { extern "C" { pub fn optixDeviceContextGetProperty( context: OptixDeviceContext, - property: OptixDeviceProperty, + property: OptixDeviceProperty::Type, value: *mut ::std::os::raw::c_void, - sizeInBytes: usize, + sizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -882,8 +769,8 @@ extern "C" { extern "C" { pub fn optixDeviceContextSetCacheDatabaseSizes( context: OptixDeviceContext, - lowWaterMark: usize, - highWaterMark: usize, + lowWaterMark: size_t, + highWaterMark: size_t, ) -> OptixResult; } extern "C" { @@ -896,14 +783,14 @@ extern "C" { pub fn optixDeviceContextGetCacheLocation( context: OptixDeviceContext, location: *mut ::std::os::raw::c_char, - locationSize: usize, + locationSize: size_t, ) -> OptixResult; } extern "C" { pub fn optixDeviceContextGetCacheDatabaseSizes( context: OptixDeviceContext, - lowWaterMark: *mut usize, - highWaterMark: *mut usize, + lowWaterMark: *mut size_t, + highWaterMark: *mut size_t, ) -> OptixResult; } extern "C" { @@ -914,7 +801,7 @@ extern "C" { programGroups: *const OptixProgramGroup, numProgramGroups: ::std::os::raw::c_uint, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, pipeline: *mut OptixPipeline, ) -> OptixResult; } @@ -936,9 +823,9 @@ extern "C" { moduleCompileOptions: *const OptixModuleCompileOptions, pipelineCompileOptions: *const OptixPipelineCompileOptions, PTX: *const ::std::os::raw::c_char, - PTXsize: usize, + PTXsize: size_t, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, module: *mut OptixModule, ) -> OptixResult; } @@ -967,7 +854,7 @@ extern "C" { numProgramGroups: ::std::os::raw::c_uint, options: *const OptixProgramGroupOptions, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, programGroups: *mut OptixProgramGroup, ) -> OptixResult; } @@ -979,7 +866,7 @@ extern "C" { pipeline: OptixPipeline, stream: CUstream, pipelineParams: CUdeviceptr, - pipelineParamsSize: usize, + pipelineParamsSize: size_t, sbt: *const OptixShaderBindingTable, width: ::std::os::raw::c_uint, height: ::std::os::raw::c_uint, @@ -1009,9 +896,9 @@ extern "C" { buildInputs: *const OptixBuildInput, numBuildInputs: ::std::os::raw::c_uint, tempBuffer: CUdeviceptr, - tempBufferSizeInBytes: usize, + tempBufferSizeInBytes: size_t, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, emittedProperties: *const OptixAccelEmitDesc, numEmittedProperties: ::std::os::raw::c_uint, @@ -1037,9 +924,9 @@ extern "C" { stream: CUstream, info: *const OptixAccelRelocationInfo, instanceTraversableHandles: CUdeviceptr, - numInstanceTraversableHandles: usize, + numInstanceTraversableHandles: size_t, targetAccel: CUdeviceptr, - targetAccelSizeInBytes: usize, + targetAccelSizeInBytes: size_t, targetHandle: *mut OptixTraversableHandle, ) -> OptixResult; } @@ -1049,7 +936,7 @@ extern "C" { stream: CUstream, inputHandle: OptixTraversableHandle, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, ) -> OptixResult; } @@ -1064,7 +951,7 @@ extern "C" { extern "C" { pub fn optixDenoiserCreate( context: OptixDeviceContext, - modelKind: OptixDenoiserModelKind, + modelKind: OptixDenoiserModelKind::Type, options: *const OptixDenoiserOptions, denoiser: *mut OptixDenoiser, ) -> OptixResult; @@ -1073,7 +960,7 @@ extern "C" { pub fn optixDenoiserCreateWithUserModel( context: OptixDeviceContext, userData: *const ::std::os::raw::c_void, - userDataSizeInBytes: usize, + userDataSizeInBytes: size_t, denoiser: *mut OptixDenoiser, ) -> OptixResult; } @@ -1095,9 +982,9 @@ extern "C" { inputWidth: ::std::os::raw::c_uint, inputHeight: ::std::os::raw::c_uint, denoiserState: CUdeviceptr, - denoiserStateSizeInBytes: usize, + denoiserStateSizeInBytes: size_t, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -1106,14 +993,14 @@ extern "C" { stream: CUstream, params: *const OptixDenoiserParams, denoiserState: CUdeviceptr, - denoiserStateSizeInBytes: usize, + denoiserStateSizeInBytes: size_t, guideLayer: *const OptixDenoiserGuideLayer, layers: *const OptixDenoiserLayer, numLayers: ::std::os::raw::c_uint, inputOffsetX: ::std::os::raw::c_uint, inputOffsetY: ::std::os::raw::c_uint, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -1123,7 +1010,7 @@ extern "C" { inputImage: *const OptixImage2D, outputIntensity: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -1133,11 +1020,11 @@ extern "C" { inputImage: *const OptixImage2D, outputAverageColor: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixFunctionTable { pub optixGetErrorName: ::std::option::Option< unsafe extern "C" fn(result: OptixResult) -> *const ::std::os::raw::c_char, @@ -1157,9 +1044,9 @@ pub struct OptixFunctionTable { pub optixDeviceContextGetProperty: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - property: OptixDeviceProperty, + property: OptixDeviceProperty::Type, value: *mut ::std::os::raw::c_void, - sizeInBytes: usize, + sizeInBytes: size_t, ) -> OptixResult, >, pub optixDeviceContextSetLogCallback: ::std::option::Option< @@ -1185,8 +1072,8 @@ pub struct OptixFunctionTable { pub optixDeviceContextSetCacheDatabaseSizes: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - lowWaterMark: usize, - highWaterMark: usize, + lowWaterMark: size_t, + highWaterMark: size_t, ) -> OptixResult, >, pub optixDeviceContextGetCacheEnabled: ::std::option::Option< @@ -1199,14 +1086,14 @@ pub struct OptixFunctionTable { unsafe extern "C" fn( context: OptixDeviceContext, location: *mut ::std::os::raw::c_char, - locationSize: usize, + locationSize: size_t, ) -> OptixResult, >, pub optixDeviceContextGetCacheDatabaseSizes: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - lowWaterMark: *mut usize, - highWaterMark: *mut usize, + lowWaterMark: *mut size_t, + highWaterMark: *mut size_t, ) -> OptixResult, >, pub optixModuleCreateFromPTX: ::std::option::Option< @@ -1215,9 +1102,9 @@ pub struct OptixFunctionTable { moduleCompileOptions: *const OptixModuleCompileOptions, pipelineCompileOptions: *const OptixPipelineCompileOptions, PTX: *const ::std::os::raw::c_char, - PTXsize: usize, + PTXsize: size_t, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, module: *mut OptixModule, ) -> OptixResult, >, @@ -1239,7 +1126,7 @@ pub struct OptixFunctionTable { numProgramGroups: ::std::os::raw::c_uint, options: *const OptixProgramGroupOptions, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, programGroups: *mut OptixProgramGroup, ) -> OptixResult, >, @@ -1259,7 +1146,7 @@ pub struct OptixFunctionTable { programGroups: *const OptixProgramGroup, numProgramGroups: ::std::os::raw::c_uint, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, pipeline: *mut OptixPipeline, ) -> OptixResult, >, @@ -1291,9 +1178,9 @@ pub struct OptixFunctionTable { buildInputs: *const OptixBuildInput, numBuildInputs: ::std::os::raw::c_uint, tempBuffer: CUdeviceptr, - tempBufferSizeInBytes: usize, + tempBufferSizeInBytes: size_t, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, emittedProperties: *const OptixAccelEmitDesc, numEmittedProperties: ::std::os::raw::c_uint, @@ -1319,9 +1206,9 @@ pub struct OptixFunctionTable { stream: CUstream, info: *const OptixAccelRelocationInfo, instanceTraversableHandles: CUdeviceptr, - numInstanceTraversableHandles: usize, + numInstanceTraversableHandles: size_t, targetAccel: CUdeviceptr, - targetAccelSizeInBytes: usize, + targetAccelSizeInBytes: size_t, targetHandle: *mut OptixTraversableHandle, ) -> OptixResult, >, @@ -1331,7 +1218,7 @@ pub struct OptixFunctionTable { stream: CUstream, inputHandle: OptixTraversableHandle, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, ) -> OptixResult, >, @@ -1354,7 +1241,7 @@ pub struct OptixFunctionTable { pipeline: OptixPipeline, stream: CUstream, pipelineParams: CUdeviceptr, - pipelineParamsSize: usize, + pipelineParamsSize: size_t, sbt: *const OptixShaderBindingTable, width: ::std::os::raw::c_uint, height: ::std::os::raw::c_uint, @@ -1364,7 +1251,7 @@ pub struct OptixFunctionTable { pub optixDenoiserCreate: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - modelKind: OptixDenoiserModelKind, + modelKind: OptixDenoiserModelKind::Type, options: *const OptixDenoiserOptions, returnHandle: *mut OptixDenoiser, ) -> OptixResult, @@ -1386,9 +1273,9 @@ pub struct OptixFunctionTable { inputWidth: ::std::os::raw::c_uint, inputHeight: ::std::os::raw::c_uint, state: CUdeviceptr, - stateSizeInBytes: usize, + stateSizeInBytes: size_t, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserInvoke: ::std::option::Option< @@ -1397,14 +1284,14 @@ pub struct OptixFunctionTable { stream: CUstream, params: *const OptixDenoiserParams, denoiserState: CUdeviceptr, - denoiserStateSizeInBytes: usize, + denoiserStateSizeInBytes: size_t, guideLayer: *const OptixDenoiserGuideLayer, layers: *const OptixDenoiserLayer, numLayers: ::std::os::raw::c_uint, inputOffsetX: ::std::os::raw::c_uint, inputOffsetY: ::std::os::raw::c_uint, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserComputeIntensity: ::std::option::Option< @@ -1414,7 +1301,7 @@ pub struct OptixFunctionTable { inputImage: *const OptixImage2D, outputIntensity: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserComputeAverageColor: ::std::option::Option< @@ -1424,15 +1311,31 @@ pub struct OptixFunctionTable { inputImage: *const OptixImage2D, outputAverageColor: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserCreateWithUserModel: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, data: *const ::std::os::raw::c_void, - dataSizeInBytes: usize, + dataSizeInBytes: size_t, returnHandle: *mut OptixDenoiser, ) -> OptixResult, >, } +pub const OptixSbtRecordHeaderSize: size_t = 32; +pub const OptixSbtRecordAlignment: size_t = 16; +pub const OptixAccelBufferByteAlignment: size_t = 128; +pub const OptixInstanceByteAlignment: size_t = 16; +pub const OptixAabbBufferByteAlignment: size_t = 8; +pub const OptixGeometryTransformByteAlignment: size_t = 16; +pub const OptixTransformByteAlignment: size_t = 64; +pub const OptixVersion: size_t = 70300; +pub const OptixBuildInputSize: size_t = 1032; +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum OptixGeometryFlags { + None = 0, + DisableAnyHit = 1, + RequireSingleAnyHitCall = 2, +} diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index a95ff086..15697d18 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -34,7 +34,7 @@ pub enum OptixDeviceProperty { impl OptixDeviceProperty { // we could repr this the same as the sys version, but for better compatability // and safety in the future, we just match. - pub fn to_raw(self) -> sys::OptixDeviceProperty { + pub fn to_raw(self) -> sys::OptixDeviceProperty::Type { use OptixDeviceProperty::*; match self { MaxTraceDepth => sys::OptixDeviceProperty::OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH, diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 3ee0d595..139a8d5a 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -42,7 +42,7 @@ pub enum DenoiserModelKind { impl DenoiserModelKind { /// Converts this model kind to its raw counterpart. - pub fn to_raw(self) -> sys::OptixDenoiserModelKind { + pub fn to_raw(self) -> sys::OptixDenoiserModelKind::Type { match self { Self::Ldr => sys::OptixDenoiserModelKind::OPTIX_DENOISER_MODEL_KIND_LDR, Self::Hdr => sys::OptixDenoiserModelKind::OPTIX_DENOISER_MODEL_KIND_HDR, @@ -499,7 +499,7 @@ pub enum ImageFormat { } impl ImageFormat { - pub fn to_raw(self) -> sys::OptixPixelFormat { + pub fn to_raw(self) -> sys::OptixPixelFormat::Type { use ImageFormat::*; match self { diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 7f47e537..a2700b0a 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -1,10 +1,11 @@ pub mod context; pub mod denoiser; pub mod error; +pub mod module; +pub mod sys; pub use cust; use error::{OptixResult, ToResult}; -pub use optix_sys as sys; /// Initializes the OptiX library. This must be called before using any OptiX function. It may /// be called before or after initializing CUDA. diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs new file mode 100644 index 00000000..9e1e5325 --- /dev/null +++ b/crates/optix/src/module.rs @@ -0,0 +1,25 @@ +use crate::{error::OptixResult, optix_call, sys}; + +#[derive(Clone)] +#[repr(transparent)] +pub struct Module { + pub(crate) raw: sys::OptixModule, +} + +#[repr(u32)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub enum CompileOptimizationLevel { + Default = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_DEFAULT, + Level0 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_0, + Level1 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_1, + Level2 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_2, + Level3 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_3, +} + +#[repr(u32)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub enum CompileDebugLevel { + None = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_NONE, + LineInfo = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO, + Full = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_FULL, +} diff --git a/crates/optix/src/optix_wrapper.h b/crates/optix/src/optix_wrapper.h new file mode 100644 index 00000000..fe7c4469 --- /dev/null +++ b/crates/optix/src/optix_wrapper.h @@ -0,0 +1,27 @@ +#include +#include + +static const size_t OptixSbtRecordHeaderSize = OPTIX_SBT_RECORD_HEADER_SIZE; +static const size_t OptixSbtRecordAlignment = OPTIX_SBT_RECORD_ALIGNMENT; +static const size_t OptixAccelBufferByteAlignment = + OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT; +static const size_t OptixInstanceByteAlignment = OPTIX_INSTANCE_BYTE_ALIGNMENT; +static const size_t OptixAabbBufferByteAlignment = + OPTIX_AABB_BUFFER_BYTE_ALIGNMENT; +static const size_t OptixGeometryTransformByteAlignment = + OPTIX_GEOMETRY_TRANSFORM_BYTE_ALIGNMENT; +static const size_t OptixTransformByteAlignment = + OPTIX_TRANSFORM_BYTE_ALIGNMENT; + +static const size_t OptixVersion = OPTIX_VERSION; + +static const size_t OptixBuildInputSize = sizeof(OptixBuildInput); + +/** + *
+ */ +enum GeometryFlags { + None = OPTIX_GEOMETRY_FLAG_NONE, + DisableAnyHit = OPTIX_GEOMETRY_FLAG_DISABLE_ANYHIT, + RequireSingleAnyHitCall = OPTIX_GEOMETRY_FLAG_REQUIRE_SINGLE_ANYHIT_CALL +}; diff --git a/crates/optix/src/sys.rs b/crates/optix/src/sys.rs new file mode 100644 index 00000000..dfb5caf3 --- /dev/null +++ b/crates/optix/src/sys.rs @@ -0,0 +1,64 @@ +use cust_raw::*; + +use std::mem::ManuallyDrop; + +type size_t = usize; + +include!(concat!(env!("OUT_DIR"), "/optix_wrapper.rs")); + +extern "C" { + pub fn optixInit() -> OptixResult; +} + +// The SBT record header is an opaque blob used by optix +#[repr(C)] +pub struct SbtRecordHeader { + header: [u8; OptixSbtRecordHeaderSize as usize], +} + +impl SbtRecordHeader { + pub fn as_mut_ptr(&mut self) -> *mut std::os::raw::c_void { + self.header.as_mut_ptr() as *mut std::os::raw::c_void + } +} + +impl Default for SbtRecordHeader { + fn default() -> SbtRecordHeader { + SbtRecordHeader { + header: [0u8; OptixSbtRecordHeaderSize as usize], + } + } +} + +// Manually define the build input union as the bindgen is pretty nasty +#[repr(C)] +pub union OptixBuildInputUnion { + pub triangle_array: ManuallyDrop, + pub curve_array: ManuallyDrop, + pub custom_primitive_array: ManuallyDrop, + pub instance_array: ManuallyDrop, + pad: [std::os::raw::c_char; 1024], +} + +impl Default for OptixBuildInputUnion { + fn default() -> OptixBuildInputUnion { + OptixBuildInputUnion { pad: [0i8; 1024] } + } +} + +#[repr(C)] +pub struct OptixBuildInput { + pub type_: OptixBuildInputType, + pub input: OptixBuildInputUnion, +} + +// Sanity check that the size of this union we're defining matches the one in +// optix header so we don't get any nasty surprises +fn _size_check() { + unsafe { + std::mem::transmute::(OptixBuildInput { + type_: OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: { OptixBuildInputUnion { pad: [0; 1024] } }, + }); + } +} diff --git a/crates/optix_sys/Cargo.toml b/crates/optix_sys/Cargo.toml deleted file mode 100644 index 6b4d5a51..00000000 --- a/crates/optix_sys/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "optix_sys" -version = "0.1.0" -edition = "2021" - -[dependencies] -cust_raw = { version = "0.11.2", path = "../cust_raw" } - -[build-dependencies] -cc = "1.0.71" -find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } diff --git a/crates/optix_sys/build.rs b/crates/optix_sys/build.rs deleted file mode 100644 index dd88b7cf..00000000 --- a/crates/optix_sys/build.rs +++ /dev/null @@ -1,32 +0,0 @@ -use find_cuda_helper::{find_cuda_root, find_optix_root}; -use std::env; - -// OptiX is a bit exotic in how it provides its functions. It uses a function table -// approach, a function table struct holds function pointers to every optix function. Then -// the Optix driver dll is loaded at runtime and the function table is loaded from that. -// OptiX provides this logic inside optix_stubs.h in the include dir, so we need to compile that -// to a lib and link it in so that we have the initialization and C function logic. -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - let mut optix_include = find_optix_root().expect( - "Unable to find the OptiX SDK, make sure you installed it and - that OPTIX_ROOT or OPTIX_ROOT_DIR are set", - ); - optix_include = optix_include.join("include"); - - let mut cuda_include = find_cuda_root().expect( - "Unable to find the CUDA Toolkit, make sure you installed it and - that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", - ); - cuda_include = cuda_include.join("include"); - - cc::Build::new() - .file("./optix_stubs.c") - .include(optix_include) - .include(cuda_include) - .cpp(false) - .compile("optix_stubs"); - - println!("cargo:rustc-link-search=native={}", out_dir); - println!("cargo:rustc-link-lib=static=optix_stubs"); -} diff --git a/crates/optix_sys/src/lib.rs b/crates/optix_sys/src/lib.rs deleted file mode 100644 index 088cb334..00000000 --- a/crates/optix_sys/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Raw bindings to the OptiX 7.3 SDK. - -#![allow(warnings)] - -use cust_raw::*; -include!("../optix.rs"); - -extern "C" { - pub fn optixInit() -> OptixResult; -} From ff04700131a916f7ab7914e0a86065271545b866 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 12:40:01 +1300 Subject: [PATCH 002/100] bootstrap enough optix to get ex02 working --- Cargo.toml | 5 + crates/optix/Cargo.toml | 11 +- crates/optix/build.rs | 35 +- .../optix/examples/common/gdt/CMakeLists.txt | 35 ++ .../examples/common/gdt/cmake/FindOptiX.cmake | 189 +++++++++ .../examples/common/gdt/cmake/FindTBB.cmake | 154 +++++++ .../gdt/cmake/configure_build_type.cmake | 41 ++ .../common/gdt/cmake/configure_glut.cmake | 20 + .../common/gdt/cmake/configure_optix.cmake | 68 +++ .../common/gdt/cmake/configure_tbb.cmake | 21 + crates/optix/examples/common/gdt/gdt/gdt.cpp | 20 + crates/optix/examples/common/gdt/gdt/gdt.h | 233 ++++++++++ .../common/gdt/gdt/math/AffineSpace.h | 183 ++++++++ .../common/gdt/gdt/math/LinearSpace.h | 341 +++++++++++++++ .../examples/common/gdt/gdt/math/Quaternion.h | 227 ++++++++++ .../optix/examples/common/gdt/gdt/math/box.h | 223 ++++++++++ .../examples/common/gdt/gdt/math/constants.h | 185 ++++++++ .../examples/common/gdt/gdt/math/fixedpoint.h | 36 ++ .../optix/examples/common/gdt/gdt/math/vec.h | 400 ++++++++++++++++++ .../common/gdt/gdt/math/vec/compare.h | 59 +++ .../common/gdt/gdt/math/vec/functors.h | 364 ++++++++++++++++ .../examples/common/gdt/gdt/math/vec/rotate.h | 40 ++ .../examples/common/gdt/gdt/random/random.h | 91 ++++ .../optix/examples/ex02_pipeline/Cargo.toml | 15 + crates/optix/examples/ex02_pipeline/build.rs | 49 +++ .../ex02_pipeline/src/ex02_pipeline.cu | 105 +++++ .../ex02_pipeline/src/launch_params.h | 6 + .../optix/examples/ex02_pipeline/src/main.rs | 8 + .../examples/ex02_pipeline/src/renderer.rs | 240 +++++++++++ crates/optix/optix_wrapper.rs | 273 ++++++++++-- crates/optix/src/context.rs | 14 +- crates/optix/src/denoiser.rs | 13 +- crates/optix/src/error.rs | 54 ++- crates/optix/src/lib.rs | 42 +- crates/optix/src/module.rs | 259 +++++++++++- crates/optix/src/optix_wrapper.h | 1 + crates/optix/src/pipeline.rs | 131 ++++++ crates/optix/src/program_group.rs | 400 ++++++++++++++++++ crates/optix/src/shader_binding_table.rs | 140 ++++++ crates/optix/src/sys.rs | 11 +- 40 files changed, 4673 insertions(+), 69 deletions(-) create mode 100644 crates/optix/examples/common/gdt/CMakeLists.txt create mode 100644 crates/optix/examples/common/gdt/cmake/FindOptiX.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/FindTBB.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_build_type.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_glut.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_optix.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_tbb.cmake create mode 100644 crates/optix/examples/common/gdt/gdt/gdt.cpp create mode 100644 crates/optix/examples/common/gdt/gdt/gdt.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/AffineSpace.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/LinearSpace.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/Quaternion.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/box.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/constants.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/fixedpoint.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec/compare.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec/functors.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec/rotate.h create mode 100644 crates/optix/examples/common/gdt/gdt/random/random.h create mode 100644 crates/optix/examples/ex02_pipeline/Cargo.toml create mode 100644 crates/optix/examples/ex02_pipeline/build.rs create mode 100644 crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu create mode 100644 crates/optix/examples/ex02_pipeline/src/launch_params.h create mode 100644 crates/optix/examples/ex02_pipeline/src/main.rs create mode 100644 crates/optix/examples/ex02_pipeline/src/renderer.rs create mode 100644 crates/optix/src/pipeline.rs create mode 100644 crates/optix/src/program_group.rs create mode 100644 crates/optix/src/shader_binding_table.rs diff --git a/Cargo.toml b/Cargo.toml index dac834a3..8bfc12cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,14 @@ [workspace] members = [ "crates/*", + "crates/optix/examples/ex*", "xtask", "examples/optix/*" ] +exclude = [ + "crates/optix/examples/*" +] + [profile.dev.package.rustc_codegen_nvvm] opt-level = 3 diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 7bf0ce70..821101c7 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -3,12 +3,21 @@ name = "optix" version = "0.1.0" edition = "2021" +[features] +optix71 = [] +optix72 = [] +optix73 = [] +default=["optix73"] + [dependencies] cust = { version = "0.1", path = "../cust" } cust_raw = { version = "0.11.2", path = "../cust_raw" } +cfg-if = "1.0.0" +bitflags = "1.3.2" +ustr = "0.8.1" [build-dependencies] -bindgen = "0.55" +bindgen = "0.59" cc = "1.0.71" find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } diff --git a/crates/optix/build.rs b/crates/optix/build.rs index d4ad5e20..c48dcdd2 100644 --- a/crates/optix/build.rs +++ b/crates/optix/build.rs @@ -51,22 +51,23 @@ fn bindgen_optix(optix_include: &Path, cuda_include: &Path) { .header("src/optix_wrapper.h") .clang_arg(format!("-I{}", optix_include.display())) .clang_arg(format!("-I{}", cuda_include.display())) - .whitelist_recursively(false) - .whitelist_type("Optix.*") - .whitelist_type("RaygenRecord") - .whitelist_type("MissRecord") - .whitelist_type("HitgroupRecord") - .blacklist_type("OptixBuildInput") - .whitelist_function("optix.*") - .whitelist_var("OptixSbtRecordHeaderSize") - .whitelist_var("OptixSbtRecordAlignment") - .whitelist_var("OptixAccelBufferByteAlignment") - .whitelist_var("OptixInstanceByteAlignment") - .whitelist_var("OptixAabbBufferByteAlignment") - .whitelist_var("OptixGeometryTransformByteAlignment") - .whitelist_var("OptixTransformByteAlignment") - .whitelist_var("OptixVersion") - .whitelist_var("OptixBuildInputSize") + .allowlist_recursively(false) + .allowlist_type("Optix.*") + .allowlist_type("RaygenRecord") + .allowlist_type("MissRecord") + .allowlist_type("HitgroupRecord") + .blocklist_type("OptixBuildInput") + .allowlist_function("optix.*") + .allowlist_var("OptixSbtRecordHeaderSize") + .allowlist_var("OptixSbtRecordAlignment") + .allowlist_var("OptixAccelBufferByteAlignment") + .allowlist_var("OptixInstanceByteAlignment") + .allowlist_var("OptixAabbBufferByteAlignment") + .allowlist_var("OptixGeometryTransformByteAlignment") + .allowlist_var("OptixTransformByteAlignment") + .allowlist_var("OptixVersion") + .allowlist_var("OptixBuildInputSize") + .allowlist_var("OptixShaderBindingTableSize") .layout_tests(false) .generate_comments(false) .newtype_enum("OptixResult") @@ -83,6 +84,8 @@ fn bindgen_optix(optix_include: &Path, cuda_include: &Path) { .constified_enum("OptixVertexFormat") .constified_enum("OptixIndicesFormat") .rust_target(bindgen::RustTarget::Nightly) + .derive_default(true) + .derive_partialeq(true) .rustfmt_bindings(true) .generate() .expect("Unable to generate optix bindings"); diff --git a/crates/optix/examples/common/gdt/CMakeLists.txt b/crates/optix/examples/common/gdt/CMakeLists.txt new file mode 100644 index 00000000..bc14ffc0 --- /dev/null +++ b/crates/optix/examples/common/gdt/CMakeLists.txt @@ -0,0 +1,35 @@ +# ======================================================================== # +# Copyright 2018-2019 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +project(GPU_Development_Tools) +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(gdt + cmake/configure_build_type.cmake + cmake/configure_optix.cmake + cmake/FindOptiX.cmake + + gdt/gdt.h + gdt/math/LinearSpace.h + gdt/math/AffineSpace.h + + gdt/gdt.cpp + ) + diff --git a/crates/optix/examples/common/gdt/cmake/FindOptiX.cmake b/crates/optix/examples/common/gdt/cmake/FindOptiX.cmake new file mode 100644 index 00000000..17578042 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/FindOptiX.cmake @@ -0,0 +1,189 @@ +# +# Copyright (c) 2018 NVIDIA 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: +# * 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 NVIDIA 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 ``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. +# + +# Locate the OptiX distribution. Search relative to the SDK first, then look in the system. + +# Our initial guess will be within the SDK. + +if (WIN32) +# set(OptiX_INSTALL_DIR "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.1.0" CACHE PATH "Path to OptiX installed location.") + find_path(searched_OptiX_INSTALL_DIR + NAME include/optix.h + PATHS + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 7.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 6.5.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 6.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.1.1" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.1.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.0.1" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK *" + ) + mark_as_advanced(searched_OptiX_INSTALL_DIR) + set(OptiX_INSTALL_DIR ${searched_OptiX_INSTALL_DIR} CACHE PATH "Path to OptiX installed location.") +else() + set(OptiX_INSTALL_DIR $ENV{OptiX_INSTALL_DIR} CACHE PATH "Path to OptiX installed location.") +endif() +# The distribution contains both 32 and 64 bit libraries. Adjust the library +# search path based on the bit-ness of the build. (i.e. 64: bin64, lib64; 32: +# bin, lib). Note that on Mac, the OptiX library is a universal binary, so we +# only need to look in lib and not lib64 for 64 bit builds. +if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT APPLE) + set(bit_dest "64") +else() + set(bit_dest "") +endif() + +macro(OPTIX_find_api_library name version) + find_library(${name}_LIBRARY + NAMES ${name}.${version} ${name} + PATHS "${OptiX_INSTALL_DIR}/lib${bit_dest}" + NO_DEFAULT_PATH + ) + find_library(${name}_LIBRARY + NAMES ${name}.${version} ${name} + ) + if(WIN32) + find_file(${name}_DLL + NAMES ${name}.${version}.dll + PATHS "${OptiX_INSTALL_DIR}/bin${bit_dest}" + NO_DEFAULT_PATH + ) + find_file(${name}_DLL + NAMES ${name}.${version}.dll + ) + endif() +endmacro() + +#OPTIX_find_api_library(optix 7.0.0) +#OPTIX_find_api_library(optixu 7.0.0) +#OPTIX_find_api_library(optix_prime 7.0.0) + +# Include +find_path(OptiX_INCLUDE + NAMES optix.h + PATHS "${OptiX_INSTALL_DIR}/include" + NO_DEFAULT_PATH + ) +find_path(OptiX_INCLUDE + NAMES optix.h + ) + +# Check to make sure we found what we were looking for +function(OptiX_report_error error_message required) + if(OptiX_FIND_REQUIRED AND required) + message(FATAL_ERROR "${error_message}") + else() + if(NOT OptiX_FIND_QUIETLY) + message(STATUS "${error_message}") + endif(NOT OptiX_FIND_QUIETLY) + endif() +endfunction() + +#if(NOT optix_LIBRARY) +# OptiX_report_error("optix library not found. Please locate before proceeding." TRUE) +#endif() +if(NOT OptiX_INCLUDE) + OptiX_report_error("OptiX headers (optix.h and friends) not found. Please locate before proceeding." TRUE) +endif() +#if(NOT optix_prime_LIBRARY) +# OptiX_report_error("optix Prime library not found. Please locate before proceeding." FALSE) +#endif() + +# Macro for setting up dummy targets +function(OptiX_add_imported_library name lib_location dll_lib dependent_libs) + set(CMAKE_IMPORT_FILE_VERSION 1) + + # Create imported target + add_library(${name} SHARED IMPORTED) + + # Import target "optix" for configuration "Debug" + if(WIN32) + set_target_properties(${name} PROPERTIES + IMPORTED_IMPLIB "${lib_location}" + #IMPORTED_LINK_INTERFACE_LIBRARIES "glu32;opengl32" + IMPORTED_LOCATION "${dll_lib}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" + ) + elseif(UNIX) + set_target_properties(${name} PROPERTIES + #IMPORTED_LINK_INTERFACE_LIBRARIES "glu32;opengl32" + IMPORTED_LOCATION "${lib_location}" + # We don't have versioned filenames for now, and it may not even matter. + #IMPORTED_SONAME "${optix_soname}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" + ) + else() + # Unknown system, but at least try and provide the minimum required + # information. + set_target_properties(${name} PROPERTIES + IMPORTED_LOCATION "${lib_location}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" + ) + endif() + + # Commands beyond this point should not need to know the version. + set(CMAKE_IMPORT_FILE_VERSION) +endfunction() + +# Sets up a dummy target +#OptiX_add_imported_library(optix "${optix_LIBRARY}" "${optix_DLL}" "${OPENGL_LIBRARIES}") +#OptiX_add_imported_library(optixu "${optixu_LIBRARY}" "${optixu_DLL}" "") +#OptiX_add_imported_library(optix_prime "${optix_prime_LIBRARY}" "${optix_prime_DLL}" "") + +macro(OptiX_check_same_path libA libB) + if(_optix_path_to_${libA}) + if(NOT _optix_path_to_${libA} STREQUAL _optix_path_to_${libB}) + # ${libA} and ${libB} are in different paths. Make sure there isn't a ${libA} next + # to the ${libB}. + get_filename_component(_optix_name_of_${libA} "${${libA}_LIBRARY}" NAME) + if(EXISTS "${_optix_path_to_${libB}}/${_optix_name_of_${libA}}") + message(WARNING " ${libA} library found next to ${libB} library that is not being used. Due to the way we are using rpath, the copy of ${libA} next to ${libB} will be used during loading instead of the one you intended. Consider putting the libraries in the same directory or moving ${_optix_path_to_${libB}}/${_optix_name_of_${libA} out of the way.") + endif() + endif() + set( _${libA}_rpath "-Wl,-rpath,${_optix_path_to_${libA}}" ) + endif() +endmacro() + +# Since liboptix.1.dylib is built with an install name of @rpath, we need to +# compile our samples with the rpath set to where optix exists. +if(APPLE) + get_filename_component(_optix_path_to_optix "${optix_LIBRARY}" PATH) + if(_optix_path_to_optix) + set( _optix_rpath "-Wl,-rpath,${_optix_path_to_optix}" ) + endif() + get_filename_component(_optix_path_to_optixu "${optixu_LIBRARY}" PATH) + OptiX_check_same_path(optixu optix) + get_filename_component(_optix_path_to_optix_prime "${optix_prime_LIBRARY}" PATH) + OptiX_check_same_path(optix_prime optix) + OptiX_check_same_path(optix_prime optixu) + + set( optix_rpath ${_optix_rpath} ${_optixu_rpath} ${_optix_prime_rpath} ) + list(REMOVE_DUPLICATES optix_rpath) +endif() + diff --git a/crates/optix/examples/common/gdt/cmake/FindTBB.cmake b/crates/optix/examples/common/gdt/cmake/FindTBB.cmake new file mode 100644 index 00000000..a7c4f465 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/FindTBB.cmake @@ -0,0 +1,154 @@ +## ======================================================================== ## +## 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. ## +## ======================================================================== ## + +SET(TBB_VERSION_REQUIRED "3.0") + +IF (NOT TBB_ROOT_PATH) + SET(TBB_ROOT_PATH $ENV{TBB_ROOT_PATH}) +ENDIF() +IF (NOT TBB_ROOT_PATH) + SET(TBB_ROOT_PATH $ENV{TBBROOT}) +ENDIF() + +# detect changed TBB_ROOT_PATH +IF (NOT TBB_ROOT_PATH STREQUAL TBB_ROOT_PATH_LAST) + UNSET(TBB_INCLUDE_DIR CACHE) + UNSET(TBB_LIBRARY CACHE) + UNSET(TBB_LIBRARY_DEBUG CACHE) + UNSET(TBB_LIBRARY_MALLOC CACHE) + UNSET(TBB_LIBRARY_MALLOC_DEBUG CACHE) +ENDIF() + +IF (WIN32) + # workaround for parentheses in variable name / CMP0053 + SET(PROGRAMFILESx86 "PROGRAMFILES(x86)") + SET(PROGRAMFILES32 "$ENV{${PROGRAMFILESx86}}") + IF (NOT PROGRAMFILES32) + SET(PROGRAMFILES32 "$ENV{PROGRAMFILES}") + ENDIF() + IF (NOT PROGRAMFILES32) + SET(PROGRAMFILES32 "C:/Program Files (x86)") + ENDIF() + FIND_PATH(TBB_ROOT_PATH include/tbb/task_scheduler_init.h + DOC "Root of TBB installation" + HINTS ${TBB_ROOT_PATH} + PATHS + ${PROJECT_SOURCE_DIR}/tbb + ${PROJECT_SOURCE_DIR}/../tbb + "${PROGRAMFILES32}/IntelSWTools/compilers_and_libraries/windows/tbb" + "${PROGRAMFILES32}/Intel/Composer XE/tbb" + "${PROGRAMFILES32}/Intel/compilers_and_libraries/windows/tbb" + ) + + IF (CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(TBB_ARCH intel64) + ELSE() + SET(TBB_ARCH ia32) + ENDIF() + + SET(TBB_LIBDIR ${TBB_ROOT_PATH}/lib) + + FIND_PATH(TBB_INCLUDE_DIR tbb/task_scheduler_init.h PATHS ${TBB_ROOT_PATH}/include NO_DEFAULT_PATH) + SET(TBB_LIB_HINTS + PATHS + ${TBB_LIBDIR}/${TBB_ARCH}/vc14 + ${TBB_LIBDIR} + NO_DEFAULT_PATH + ) + FIND_LIBRARY(TBB_LIBRARY tbb ${TBB_LIB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_DEBUG tbb_debug ${TBB_LIB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC tbbmalloc ${TBB_LIB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC_DEBUG tbbmalloc_debug ${TBB_LIB_HINTS}) + +ELSE () + + FIND_PATH(TBB_ROOT_PATH include/tbb/task_scheduler_init.h + DOC "Root of TBB installation" + HINTS ${TBB_ROOT_PATH} + PATHS + ${PROJECT_SOURCE_DIR}/tbb + /opt/intel/composerxe/tbb + /opt/intel/compilers_and_libraries/tbb + /opt/intel/tbb + ) + + IF (APPLE) + FIND_PATH(TBB_INCLUDE_DIR tbb/task_scheduler_init.h PATHS ${TBB_ROOT_PATH}/include NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY tbb PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY_DEBUG tbb_debug PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY_MALLOC tbbmalloc PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY_MALLOC_DEBUG tbbmalloc_debug PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + ELSE() + FIND_PATH(TBB_INCLUDE_DIR tbb/task_scheduler_init.h PATHS ${TBB_ROOT_PATH}/include NO_DEFAULT_PATH) + SET(TBB_HINTS HINTS ${TBB_ROOT_PATH}/lib/intel64/gcc4.7 ${TBB_ROOT_PATH}/lib/intel64/gcc4.4 ${TBB_ROOT_PATH}/lib ${TBB_ROOT_PATH}/lib64 PATHS /usr/libx86_64-linux-gnu/) + FIND_LIBRARY(TBB_LIBRARY libtbb.so.2 ${TBB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_DEBUG libtbb_debug.so.2 ${TBB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC libtbbmalloc.so.2 ${TBB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC_DEBUG libtbbmalloc_debug.so.2 ${TBB_HINTS}) + ENDIF() +ENDIF() + +SET(TBB_ROOT_PATH_LAST ${TBB_ROOT_PATH} CACHE INTERNAL "Last value of TBB_ROOT_PATH to detect changes") + +SET(TBB_ERROR_MESSAGE + "Threading Building Blocks (TBB) with minimum version ${TBB_VERSION_REQUIRED} not found. +OSPRay uses TBB as default tasking system. Please make sure you have the TBB headers installed as well (the package is typically named 'libtbb-dev' or 'tbb-devel') and/or hint the location of TBB in TBB_ROOT_PATH. +Alternatively, you can try to use OpenMP as tasking system by setting OSPRAY_TASKING_SYSTEM=OpenMP") + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(TBB + ${TBB_ERROR_MESSAGE} + TBB_INCLUDE_DIR TBB_LIBRARY TBB_LIBRARY_MALLOC +) + +# check version +IF (TBB_INCLUDE_DIR) + FILE(READ ${TBB_INCLUDE_DIR}/tbb/tbb_stddef.h TBB_STDDEF_H) + + STRING(REGEX MATCH "#define TBB_VERSION_MAJOR ([0-9]+)" DUMMY "${TBB_STDDEF_H}") + SET(TBB_VERSION_MAJOR ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "#define TBB_VERSION_MINOR ([0-9]+)" DUMMY "${TBB_STDDEF_H}") + SET(TBB_VERSION "${TBB_VERSION_MAJOR}.${CMAKE_MATCH_1}") + + IF (TBB_VERSION VERSION_LESS TBB_VERSION_REQUIRED) + MESSAGE(FATAL_ERROR ${TBB_ERROR_MESSAGE}) + ENDIF() + + SET(TBB_VERSION ${TBB_VERSION} CACHE STRING "TBB Version") + MARK_AS_ADVANCED(TBB_VERSION) +ENDIF() + +IF (TBB_FOUND) + SET(TBB_INCLUDE_DIRS ${TBB_INCLUDE_DIR}) + # NOTE(jda) - TBB found in CentOS 6/7 package manager does not have debug + # versions of the library...silently fall-back to using only the + # libraries which we actually found. + IF (NOT TBB_LIBRARY_DEBUG) + SET(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_LIBRARY_MALLOC}) + ELSE () + SET(TBB_LIBRARIES + optimized ${TBB_LIBRARY} optimized ${TBB_LIBRARY_MALLOC} + debug ${TBB_LIBRARY_DEBUG} debug ${TBB_LIBRARY_MALLOC_DEBUG} + ) + ENDIF() +ENDIF() + +MARK_AS_ADVANCED(TBB_INCLUDE_DIR) +MARK_AS_ADVANCED(TBB_LIBRARY) +MARK_AS_ADVANCED(TBB_LIBRARY_DEBUG) +MARK_AS_ADVANCED(TBB_LIBRARY_MALLOC) +MARK_AS_ADVANCED(TBB_LIBRARY_MALLOC_DEBUG) diff --git a/crates/optix/examples/common/gdt/cmake/configure_build_type.cmake b/crates/optix/examples/common/gdt/cmake/configure_build_type.cmake new file mode 100644 index 00000000..e34b964d --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_build_type.cmake @@ -0,0 +1,41 @@ +# ======================================================================== # +# Copyright 2018-2020 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +# This helper script sets up default build targets for Release/Debug, etc, +# something which each project I worked on seems to need, eventually, so +# having it in one place arguably makes sense. + +if(NOT SET_UP_CONFIGURATIONS_DONE) + set(SET_UP_CONFIGURATIONS_DONE 1) + + # No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator + # Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator. + if(CMAKE_CONFIGURATION_TYPES) # multiconfig generator? + set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) + else() + if(NOT CMAKE_BUILD_TYPE) +# message("Defaulting to release build.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) + endif() + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build") + # set the valid options for cmake-gui drop-down list + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release") + endif() +endif() + +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) \ No newline at end of file diff --git a/crates/optix/examples/common/gdt/cmake/configure_glut.cmake b/crates/optix/examples/common/gdt/cmake/configure_glut.cmake new file mode 100644 index 00000000..8e3a64b4 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_glut.cmake @@ -0,0 +1,20 @@ +# helper script that finds GLUT, either from the system install (linux), or from the included, precompiled binaries (windows) +# Note we *intentionally* do not use the file name of "FindGLUT.cmake" because we want to call the system-provided FindGLUT later on, we just set up some paths, where required + +# legacy gl vs glvnd/glx +if (POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) +endif() + +if (WIN32) + # The default cmake-FindGLUT.cmake script will automatically search in + # - ${GLUT_ROOT_PATH}/Release (fro the lib) + # - ${GLUT_ROOT_PATH}/include + # ... ie, setting this search path _should_ make the default script find the + # right stuff, and set the right variables + set(GLUT_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../3rdParty/freeglut") +endif() + + +find_package(OpenGL REQUIRED) +find_package(GLUT REQUIRED) diff --git a/crates/optix/examples/common/gdt/cmake/configure_optix.cmake b/crates/optix/examples/common/gdt/cmake/configure_optix.cmake new file mode 100644 index 00000000..4023f30b --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_optix.cmake @@ -0,0 +1,68 @@ +# ======================================================================== # +# Copyright 2018 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +set(CMAKE_MODULE_PATH + "${PROJECT_SOURCE_DIR}/cmake" +# "${CMAKE_CURRENT_SOURCE_DIR}/../cmake" + ${CMAKE_MODULE_PATH} + ) + +find_package(CUDA REQUIRED) +find_package(OptiX REQUIRED VERSION 7.0) + +#include_directories(${CUDA_TOOLKIT_INCLUDE}) +if (CUDA_TOOLKIT_ROOT_DIR) + include_directories(${CUDA_TOOLKIT_ROOT_DIR}/include) +endif() +include_directories(${OptiX_INCLUDE}) + +if (WIN32) + add_definitions(-DNOMINMAX) +endif() + +find_program(BIN2C bin2c + DOC "Path to the cuda-sdk bin2c executable.") + +# this macro defines cmake rules that execute the following four steps: +# 1) compile the given cuda file ${cuda_file} to an intermediary PTX file +# 2) use the 'bin2c' tool (that comes with CUDA) to +# create a second intermediary (.c-)file which defines a const string variable +# (named '${c_var_name}') whose (constant) value is the PTX output +# from the previous step. +# 3) compile the given .c file to an intermediary object file (why thus has +# that PTX string 'embedded' as a global constant. +# 4) assign the name of the intermediary .o file to the cmake variable +# 'output_var', which can then be added to cmake targets. +macro(cuda_compile_and_embed output_var cuda_file) + set(c_var_name ${output_var}) + cuda_compile_ptx(ptx_files ${cuda_file}) + list(GET ptx_files 0 ptx_file) + set(embedded_file ${ptx_file}_embedded.c) +# message("adding rule to compile and embed ${cuda_file} to \"const char ${var_name}[];\"") + add_custom_command( + OUTPUT ${embedded_file} + COMMAND ${BIN2C} -c --padd 0 --type char --name ${c_var_name} ${ptx_file} > ${embedded_file} + DEPENDS ${ptx_file} + COMMENT "compiling (and embedding ptx from) ${cuda_file}" + ) + set(${output_var} ${embedded_file}) +endmacro() + +include_directories(${OptiX_INCLUDE}) + +add_definitions(-D__CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__=1) + + diff --git a/crates/optix/examples/common/gdt/cmake/configure_tbb.cmake b/crates/optix/examples/common/gdt/cmake/configure_tbb.cmake new file mode 100644 index 00000000..1aa45852 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_tbb.cmake @@ -0,0 +1,21 @@ +# ======================================================================== # +# Copyright 2018-2019 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +find_package(TBB REQUIRED) +if (TBB_FOUND) + include_directories(${TBB_INCLUDE_DIR}) +endif() + diff --git a/crates/optix/examples/common/gdt/gdt/gdt.cpp b/crates/optix/examples/common/gdt/gdt/gdt.cpp new file mode 100644 index 00000000..0dcbffb4 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/gdt.cpp @@ -0,0 +1,20 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt.h" +#include "math/LinearSpace.h" +#include "math/AffineSpace.h" + diff --git a/crates/optix/examples/common/gdt/gdt/gdt.h b/crates/optix/examples/common/gdt/gdt/gdt.h new file mode 100644 index 00000000..b4eaa48d --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/gdt.h @@ -0,0 +1,233 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 +#include +#include +#include +#ifdef __CUDA_ARCH__ +# include +#else +# include +#endif +#include +#ifdef __GNUC__ +# include +# include +#endif +#include + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #ifdef min + #undef min + #endif + #ifdef max + #undef max + #endif +#endif + + +#if defined(_MSC_VER) +# define GDT_DLL_EXPORT __declspec(dllexport) +# define GDT_DLL_IMPORT __declspec(dllimport) +#elif defined(__clang__) || defined(__GNUC__) +# define GDT_DLL_EXPORT __attribute__((visibility("default"))) +# define GDT_DLL_IMPORT __attribute__((visibility("default"))) +#else +# define GDT_DLL_EXPORT +# define GDT_DLL_IMPORT +#endif + +#if 1 +# define GDT_INTERFACE /* nothing */ +#else +//#if defined(GDT_DLL_INTERFACE) +# ifdef gdt_EXPORTS +# define GDT_INTERFACE GDT_DLL_EXPORT +# else +# define GDT_INTERFACE GDT_DLL_IMPORT +# endif +//#else +//# define GDT_INTERFACE /*static lib*/ +//#endif +#endif + +#ifndef PRINT +# define PRINT(var) std::cout << #var << "=" << var << std::endl; +# define PING std::cout << __FILE__ << "::" << __LINE__ << ": " << __FUNCTION__ << std::endl; +#endif + +#if defined(__CUDACC__) +# define __gdt_device __device__ +# define __gdt_host __host__ +#else +# define __gdt_device /* ignore */ +# define __gdt_host /* ignore */ +#endif + +# define __both__ __gdt_host __gdt_device + + +#ifdef __GNUC__ + #define MAYBE_UNUSED __attribute__((unused)) +#else + #define MAYBE_UNUSED +#endif + + + +#define GDT_NOTIMPLEMENTED throw std::runtime_error(std::string(__PRETTY_FUNCTION__)+" not implemented") + +#define GDT_TERMINAL_RED "\033[1;31m" +#define GDT_TERMINAL_GREEN "\033[1;32m" +#define GDT_TERMINAL_YELLOW "\033[1;33m" +#define GDT_TERMINAL_BLUE "\033[1;34m" +#define GDT_TERMINAL_RESET "\033[0m" +#define GDT_TERMINAL_DEFAULT GDT_TERMINAL_RESET +#define GDT_TERMINAL_BOLD "\033[1;1m" + + + + + +/*! \namespace gdt GPU Developer Toolbox */ +namespace gdt { + +#ifdef __CUDACC__ + using ::min; + using ::max; + // inline __both__ float abs(float f) { return fabsf(f); } + // inline __both__ double abs(double f) { return fabs(f); } + using std::abs; + // inline __both__ float sin(float f) { return ::sinf(f); } + // inline __both__ double sin(double f) { return ::sin(f); } + // inline __both__ float cos(float f) { return ::cosf(f); } + // inline __both__ double cos(double f) { return ::cos(f); } + + using ::saturate; +#else + using std::min; + using std::max; + using std::abs; + // inline __both__ double sin(double f) { return ::sin(f); } + inline __both__ float saturate(const float &f) { return min(1.f,max(0.f,f)); } +#endif + + // inline __both__ float abs(float f) { return fabsf(f); } + // inline __both__ double abs(double f) { return fabs(f); } + inline __both__ float rcp(float f) { return 1.f/f; } + inline __both__ double rcp(double d) { return 1./d; } + + inline __both__ int32_t divRoundUp(int32_t a, int32_t b) { return (a+b-1)/b; } + inline __both__ uint32_t divRoundUp(uint32_t a, uint32_t b) { return (a+b-1)/b; } + inline __both__ int64_t divRoundUp(int64_t a, int64_t b) { return (a+b-1)/b; } + inline __both__ uint64_t divRoundUp(uint64_t a, uint64_t b) { return (a+b-1)/b; } + +#ifdef __CUDACC__ + using ::sin; // this is the double version + // inline __both__ float sin(float f) { return ::sinf(f); } + using ::cos; // this is the double version + // inline __both__ float cos(float f) { return ::cosf(f); } +#else + using ::sin; // this is the double version + using ::cos; // this is the double version +#endif + +// #ifdef __CUDA_ARCH__ + // using ::sqrt; + // using ::sqrtf; +// #else + namespace overloaded { + /* move all those in a special namespace so they will never get + included - and thus, conflict with, the default namesapce */ + inline __both__ float sqrt(const float f) { return ::sqrtf(f); } + inline __both__ double sqrt(const double d) { return ::sqrt(d); } + } +// #endif +// inline __both__ float rsqrt(const float f) { return 1.f/sqrtf(f); } +// inline __both__ double rsqrt(const double d) { return 1./sqrt(d); } + +#ifdef __WIN32__ +# define osp_snprintf sprintf_s +#else +# define osp_snprintf snprintf +#endif + + /*! added pretty-print function for large numbers, printing 10000000 as "10M" instead */ + inline std::string prettyDouble(const double val) { + const double absVal = abs(val); + char result[1000]; + + if (absVal >= 1e+18f) osp_snprintf(result,1000,"%.1f%c",val/1e18f,'E'); + else if (absVal >= 1e+15f) osp_snprintf(result,1000,"%.1f%c",val/1e15f,'P'); + else if (absVal >= 1e+12f) osp_snprintf(result,1000,"%.1f%c",val/1e12f,'T'); + else if (absVal >= 1e+09f) osp_snprintf(result,1000,"%.1f%c",val/1e09f,'G'); + else if (absVal >= 1e+06f) osp_snprintf(result,1000,"%.1f%c",val/1e06f,'M'); + else if (absVal >= 1e+03f) osp_snprintf(result,1000,"%.1f%c",val/1e03f,'k'); + else if (absVal <= 1e-12f) osp_snprintf(result,1000,"%.1f%c",val*1e15f,'f'); + else if (absVal <= 1e-09f) osp_snprintf(result,1000,"%.1f%c",val*1e12f,'p'); + else if (absVal <= 1e-06f) osp_snprintf(result,1000,"%.1f%c",val*1e09f,'n'); + else if (absVal <= 1e-03f) osp_snprintf(result,1000,"%.1f%c",val*1e06f,'u'); + else if (absVal <= 1e-00f) osp_snprintf(result,1000,"%.1f%c",val*1e03f,'m'); + else osp_snprintf(result,1000,"%f",(float)val); + + return result; + } + + + + inline std::string prettyNumber(const size_t s) + { + char buf[1000]; + if (s >= (1024LL*1024LL*1024LL*1024LL)) { + osp_snprintf(buf, 1000,"%.2fT",s/(1024.f*1024.f*1024.f*1024.f)); + } else if (s >= (1024LL*1024LL*1024LL)) { + osp_snprintf(buf, 1000, "%.2fG",s/(1024.f*1024.f*1024.f)); + } else if (s >= (1024LL*1024LL)) { + osp_snprintf(buf, 1000, "%.2fM",s/(1024.f*1024.f)); + } else if (s >= (1024LL)) { + osp_snprintf(buf, 1000, "%.2fK",s/(1024.f)); + } else { + osp_snprintf(buf,1000,"%zi",s); + } + return buf; + } + + inline double getCurrentTime() + { +#ifdef _WIN32 + SYSTEMTIME tp; GetSystemTime(&tp); + return double(tp.wSecond) + double(tp.wMilliseconds) / 1E3; +#else + struct timeval tp; gettimeofday(&tp,nullptr); + return double(tp.tv_sec) + double(tp.tv_usec)/1E6; +#endif + } + + inline bool hasSuffix(const std::string &s, const std::string &suffix) + { + return s.substr(s.size()-suffix.size()) == suffix; + } +} diff --git a/crates/optix/examples/common/gdt/gdt/math/AffineSpace.h b/crates/optix/examples/common/gdt/gdt/math/AffineSpace.h new file mode 100644 index 00000000..80a306c5 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/AffineSpace.h @@ -0,0 +1,183 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* originally taken (and adapted) from ospray, under following license */ + +// ======================================================================== // +// Copyright 2009-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. // +// ======================================================================== // + +#pragma once + +#include "LinearSpace.h" +#include "box.h" + +namespace gdt { + +#define VectorT typename L::vector_t +#define ScalarT typename L::vector_t::scalar_t + + //////////////////////////////////////////////////////////////////////////////// + // Affine Space + //////////////////////////////////////////////////////////////////////////////// + + template + struct GDT_INTERFACE AffineSpaceT + { + L l; /*< linear part of affine space */ + VectorT p; /*< affine part of affine space */ + + //////////////////////////////////////////////////////////////////////////////// + // Constructors, Assignment, Cast, Copy Operations + //////////////////////////////////////////////////////////////////////////////// + + inline AffineSpaceT ( ) = default; + inline AffineSpaceT ( const AffineSpaceT& other ) { l = other.l; p = other.p; } + inline AffineSpaceT ( const L & other ) { l = other ; p = VectorT(zero); } + inline AffineSpaceT& operator=( const AffineSpaceT& other ) { l = other.l; p = other.p; return *this; } + + inline AffineSpaceT( const VectorT& vx, const VectorT& vy, const VectorT& vz, const VectorT& p ) : l(vx,vy,vz), p(p) {} + inline AffineSpaceT( const L& l, const VectorT& p ) : l(l), p(p) {} + + template inline AffineSpaceT( const AffineSpaceT& s ) : l(s.l), p(s.p) {} + + //////////////////////////////////////////////////////////////////////////////// + // Constants + //////////////////////////////////////////////////////////////////////////////// + + inline AffineSpaceT( ZeroTy ) : l(zero), p(zero) {} + inline AffineSpaceT( OneTy ) : l(one), p(zero) {} + + /*! return matrix for scaling */ + static inline AffineSpaceT scale(const VectorT& s) { return L::scale(s); } + + /*! return matrix for translation */ + static inline AffineSpaceT translate(const VectorT& p) { return AffineSpaceT(one,p); } + + /*! return matrix for rotation, only in 2D */ + static inline AffineSpaceT rotate(const ScalarT& r) { return L::rotate(r); } + + /*! return matrix for rotation around arbitrary point (2D) or axis (3D) */ + static inline 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 inline 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; right-handed coordinate system */ + static inline AffineSpaceT lookat(const VectorT& eye, const VectorT& point, const VectorT& up) { + VectorT Z = normalize(point-eye); + VectorT U = normalize(cross(Z,up)); + VectorT V = cross(U,Z); + return AffineSpaceT(L(U,V,Z),eye); + } + + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline AffineSpaceT operator -( const AffineSpaceT& a ) { return AffineSpaceT(-a.l,-a.p); } + template inline AffineSpaceT operator +( const AffineSpaceT& a ) { return AffineSpaceT(+a.l,+a.p); } + template inline AffineSpaceT rcp( const AffineSpaceT& a ) { L il = rcp(a.l); return AffineSpaceT(il,-(il*a.p)); } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline AffineSpaceT operator +( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l+b.l,a.p+b.p); } + template inline AffineSpaceT operator -( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l-b.l,a.p-b.p); } + + template inline AffineSpaceT operator *( const ScalarT & a, const AffineSpaceT& b ) { return AffineSpaceT(a*b.l,a*b.p); } + template inline AffineSpaceT operator *( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l*b.l,a.l*b.p+a.p); } + template inline AffineSpaceT operator /( const AffineSpaceT& a, const AffineSpaceT& b ) { return a * rcp(b); } + template inline AffineSpaceT operator /( const AffineSpaceT& a, const ScalarT & b ) { return a * rcp(b); } + + template inline AffineSpaceT& operator *=( AffineSpaceT& a, const AffineSpaceT& b ) { return a = a * b; } + template inline AffineSpaceT& operator *=( AffineSpaceT& a, const ScalarT & b ) { return a = a * b; } + template inline AffineSpaceT& operator /=( AffineSpaceT& a, const AffineSpaceT& b ) { return a = a / b; } + template inline AffineSpaceT& operator /=( AffineSpaceT& a, const ScalarT & b ) { return a = a / b; } + + template inline __both__ const 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 inline __both__ const VectorT xfmVector(const AffineSpaceT& m, const VectorT& v) { return xfmVector(m.l,v); } + template inline __both__ const VectorT xfmNormal(const AffineSpaceT& m, const VectorT& n) { return xfmNormal(m.l,n); } + + + // template + // inline const box_t + // xfmBounds(const AffineSpaceT>> &m, + // const box_t &b) + // { + // box_t dst = empty; + // const vec_t p0(b.lower.x,b.lower.y,b.lower.z); dst.extend(xfmPoint(m,p0)); + // const vec_t p1(b.lower.x,b.lower.y,b.upper.z); dst.extend(xfmPoint(m,p1)); + // const vec_t p2(b.lower.x,b.upper.y,b.lower.z); dst.extend(xfmPoint(m,p2)); + // const vec_t p3(b.lower.x,b.upper.y,b.upper.z); dst.extend(xfmPoint(m,p3)); + // const vec_t p4(b.upper.x,b.lower.y,b.lower.z); dst.extend(xfmPoint(m,p4)); + // const vec_t p5(b.upper.x,b.lower.y,b.upper.z); dst.extend(xfmPoint(m,p5)); + // const vec_t p6(b.upper.x,b.upper.y,b.lower.z); dst.extend(xfmPoint(m,p6)); + // const vec_t p7(b.upper.x,b.upper.y,b.upper.z); dst.extend(xfmPoint(m,p7)); + // return dst; + // } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline bool operator ==( const AffineSpaceT& a, const AffineSpaceT& b ) { return a.l == b.l && a.p == b.p; } + template inline bool operator !=( const AffineSpaceT& a, const AffineSpaceT& b ) { return a.l != b.l || a.p != b.p; } + + //////////////////////////////////////////////////////////////////////////////// + // Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline std::ostream& operator<<(std::ostream& cout, const AffineSpaceT& m) { + return cout << "{ l = " << m.l << ", p = " << m.p << " }"; + } + + //////////////////////////////////////////////////////////////////////////////// + // Type Aliases + //////////////////////////////////////////////////////////////////////////////// + + using AffineSpace2f = AffineSpaceT; + using AffineSpace3f = AffineSpaceT; + using AffineSpace3fa = AffineSpaceT; + using OrthonormalSpace3f = AffineSpaceT; + + using affine2f = AffineSpace2f; + using affine3f = AffineSpace3f; + + //////////////////////////////////////////////////////////////////////////////// + /*! Template Specialization for 2D: return matrix for rotation around point (rotation around arbitrarty vector is not meaningful in 2D) */ + template<> inline AffineSpace2f AffineSpace2f::rotate(const vec2f& p, const float& r) + { return translate(+p) * AffineSpace2f(LinearSpace2f::rotate(r)) * translate(-p); } + +#undef VectorT +#undef ScalarT + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/LinearSpace.h b/crates/optix/examples/common/gdt/gdt/math/LinearSpace.h new file mode 100644 index 00000000..50bd4066 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/LinearSpace.h @@ -0,0 +1,341 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* originally taken (and adapted) from ospray, under following license */ + +// ======================================================================== // +// Copyright 2009-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. // +// ======================================================================== // + +#pragma once + +#include "vec.h" +#include "Quaternion.h" + +namespace gdt { + + //////////////////////////////////////////////////////////////////////////////// + /// 2D Linear Transform (2x2 Matrix) + //////////////////////////////////////////////////////////////////////////////// + + template struct GDT_INTERFACE LinearSpace2 + { + using vector_t = T; + // using Scalar = typename T::scalar_t; + // using vector_t = T; + using scalar_t = typename T::scalar_t; + + /*! default matrix constructor */ + inline LinearSpace2 ( ) = default; + inline LinearSpace2 ( const LinearSpace2& other ) { vx = other.vx; vy = other.vy; } + inline LinearSpace2& operator=( const LinearSpace2& other ) { vx = other.vx; vy = other.vy; return *this; } + + template inline LinearSpace2( const LinearSpace2& s ) : vx(s.vx), vy(s.vy) {} + + /*! matrix construction from column vectors */ + inline __both__ LinearSpace2(const vector_t& vx, const vector_t& vy) + : vx(vx), vy(vy) {} + + /*! matrix construction from row mayor data */ + inline __both__ LinearSpace2(const scalar_t& m00, const scalar_t& m01, + const scalar_t& m10, const scalar_t& m11) + : vx(m00,m10), vy(m01,m11) {} + + /*! compute the determinant of the matrix */ + inline __both__ const scalar_t det() const { return vx.x*vy.y - vx.y*vy.x; } + + /*! compute adjoint matrix */ + inline __both__ const LinearSpace2 adjoint() const { return LinearSpace2(vy.y,-vy.x,-vx.y,vx.x); } + + /*! compute inverse matrix */ + inline __both__ const LinearSpace2 inverse() const { return adjoint()/det(); } + + /*! compute transposed matrix */ + inline __both__ const LinearSpace2 transposed() const { return LinearSpace2(vx.x,vx.y,vy.x,vy.y); } + + /*! returns first row of matrix */ + inline const vector_t row0() const { return vector_t(vx.x,vy.x); } + + /*! returns second row of matrix */ + inline const vector_t row1() const { return vector_t(vx.y,vy.y); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + inline LinearSpace2( ZeroTy ) : vx(zero), vy(zero) {} + inline LinearSpace2( OneTy ) : vx(one, zero), vy(zero, one) {} + + /*! return matrix for scaling */ + static inline LinearSpace2 scale(const vector_t& s) { + return LinearSpace2(s.x, 0, + 0 , s.y); + } + + /*! return matrix for rotation */ + static inline LinearSpace2 rotate(const scalar_t& r) { + scalar_t 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_t mirror(one); + if (m.det() < scalar_t(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_t vx,vy; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __both__ inline LinearSpace2 operator -( const LinearSpace2& a ) { return LinearSpace2(-a.vx,-a.vy); } + template __both__ inline LinearSpace2 operator +( const LinearSpace2& a ) { return LinearSpace2(+a.vx,+a.vy); } + template __both__ inline LinearSpace2 rcp ( const LinearSpace2& a ) { return a.inverse(); } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline LinearSpace2 operator +( const LinearSpace2& a, const LinearSpace2& b ) { return LinearSpace2(a.vx+b.vx,a.vy+b.vy); } + template inline LinearSpace2 operator -( const LinearSpace2& a, const LinearSpace2& b ) { return LinearSpace2(a.vx-b.vx,a.vy-b.vy); } + + template inline LinearSpace2 operator*(const typename T::scalar_t & a, const LinearSpace2& b) { return LinearSpace2(a*b.vx, a*b.vy); } + template inline T operator*(const LinearSpace2& a, const T & b) { return b.x*a.vx + b.y*a.vy; } + template inline LinearSpace2 operator*(const LinearSpace2& a, const LinearSpace2& b) { return LinearSpace2(a*b.vx, a*b.vy); } + + template inline LinearSpace2 operator/(const LinearSpace2& a, const typename T::scalar_t & b) { return LinearSpace2(a.vx/b, a.vy/b); } + template inline LinearSpace2 operator/(const LinearSpace2& a, const LinearSpace2& b) { return a * rcp(b); } + + template inline LinearSpace2& operator *=( LinearSpace2& a, const LinearSpace2& b ) { return a = a * b; } + template inline LinearSpace2& operator /=( LinearSpace2& a, const LinearSpace2& b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline bool operator ==( const LinearSpace2& a, const LinearSpace2& b ) { return a.vx == b.vx && a.vy == b.vy; } + template inline bool operator !=( const LinearSpace2& a, const LinearSpace2& b ) { return a.vx != b.vx || a.vy != b.vy; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template static std::ostream& operator<<(std::ostream& cout, const LinearSpace2& m) { + return cout << "{ vx = " << m.vx << ", vy = " << m.vy << "}"; + } + + //////////////////////////////////////////////////////////////////////////////// + /// 3D Linear Transform (3x3 Matrix) + //////////////////////////////////////////////////////////////////////////////// + + template + struct GDT_INTERFACE LinearSpace3 + { + // using vector_t = T; + using scalar_t = typename T::scalar_t; + using vector_t = T; + // using scalar_t = typename T::scalar_t; + + /*! default matrix constructor */ + inline LinearSpace3 ( ) = default; + inline __both__ LinearSpace3 ( const LinearSpace3& other ) { vx = other.vx; vy = other.vy; vz = other.vz; } + inline __both__ LinearSpace3& operator=( const LinearSpace3& other ) { vx = other.vx; vy = other.vy; vz = other.vz; return *this; } + + template inline __both__ LinearSpace3( const LinearSpace3& s ) : vx(s.vx), vy(s.vy), vz(s.vz) {} + + /*! matrix construction from column vectors */ + inline __both__ LinearSpace3(const vector_t& vx, const vector_t& vy, const vector_t& vz) + : vx(vx), vy(vy), vz(vz) {} + + /*! construction from quaternion */ + inline __both__ 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 */ + inline __both__ LinearSpace3(const scalar_t& m00, const scalar_t& m01, const scalar_t& m02, + const scalar_t& m10, const scalar_t& m11, const scalar_t& m12, + const scalar_t& m20, const scalar_t& m21, const scalar_t& m22) + : vx(m00,m10,m20), vy(m01,m11,m21), vz(m02,m12,m22) {} + + /*! compute the determinant of the matrix */ + inline __both__ const scalar_t det() const { return dot(vx,cross(vy,vz)); } + + /*! compute adjoint matrix */ + inline __both__ const LinearSpace3 adjoint() const { return LinearSpace3(cross(vy,vz),cross(vz,vx),cross(vx,vy)).transposed(); } + + /*! compute inverse matrix */ + inline __both__ const LinearSpace3 inverse() const { return adjoint()/det(); } + + /*! compute transposed matrix */ + inline __both__ 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 */ + inline __both__ const vector_t row0() const { return vector_t(vx.x,vy.x,vz.x); } + + /*! returns second row of matrix */ + inline __both__ const vector_t row1() const { return vector_t(vx.y,vy.y,vz.y); } + + /*! returns third row of matrix */ + inline __both__ const vector_t row2() const { return vector_t(vx.z,vy.z,vz.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + inline __both__ LinearSpace3( ZeroTy ) : vx(zero), vy(zero), vz(zero) {} + inline __both__ LinearSpace3( OneTy ) : vx(one, zero, zero), vy(zero, one, zero), vz(zero, zero, one) {} + + /*! return matrix for scaling */ + static inline __both__ LinearSpace3 scale(const vector_t& s) { + return LinearSpace3(s.x, 0, 0, + 0 , s.y, 0, + 0 , 0, s.z); + } + + /*! return matrix for rotation around arbitrary axis */ + static inline __both__ LinearSpace3 rotate(const vector_t& _u, const scalar_t& r) { + vector_t u = normalize(_u); + scalar_t 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 */ + T vx,vy,vz; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline __both__ LinearSpace3 operator -( const LinearSpace3& a ) { return LinearSpace3(-a.vx,-a.vy,-a.vz); } + template inline __both__ LinearSpace3 operator +( const LinearSpace3& a ) { return LinearSpace3(+a.vx,+a.vy,+a.vz); } + template inline __both__ LinearSpace3 rcp ( const LinearSpace3& a ) { return a.inverse(); } + + /* constructs a coordinate frame form a normalized normal */ + template inline __both__ LinearSpace3 frame(const T& N) + { + const T dx0 = cross(T(one,zero,zero),N); + const T dx1 = cross(T(zero,one,zero),N); + 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 inline __both__ 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 inline __both__ 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 inline __both__ LinearSpace3 operator +( const LinearSpace3& a, const LinearSpace3& b ) { return LinearSpace3(a.vx+b.vx,a.vy+b.vy,a.vz+b.vz); } + template inline __both__ LinearSpace3 operator -( const LinearSpace3& a, const LinearSpace3& b ) { return LinearSpace3(a.vx-b.vx,a.vy-b.vy,a.vz-b.vz); } + + template inline __both__ LinearSpace3 operator*(const typename T::scalar_t & a, const LinearSpace3& b) { return LinearSpace3(a*b.vx, a*b.vy, a*b.vz); } + template inline T operator*(const LinearSpace3& a, const T & b) { return b.x*a.vx + b.y*a.vy + b.z*a.vz; } + template inline __both__ LinearSpace3 operator*(const LinearSpace3& a, const LinearSpace3& b) { return LinearSpace3(a*b.vx, a*b.vy, a*b.vz); } + + template __both__ inline LinearSpace3 operator/(const LinearSpace3& a, const typename T::scalar_t & b) { return LinearSpace3(a.vx/b, a.vy/b, a.vz/b); } + + template __both__ inline LinearSpace3 operator/(const LinearSpace3& a, const LinearSpace3& b) { return a * rcp(b); } + + template inline LinearSpace3& operator *=( LinearSpace3& a, const LinearSpace3& b ) { return a = a * b; } + template inline LinearSpace3& operator /=( LinearSpace3& a, const LinearSpace3& b ) { return a = a / b; } + + template inline __both__ 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 inline __both__ 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 inline __both__ T xfmNormal(const LinearSpace3& s, const T& a) { return xfmVector(s.inverse().transposed(),a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline bool operator ==( const LinearSpace3& a, const LinearSpace3& b ) { return a.vx == b.vx && a.vy == b.vy && a.vz == b.vz; } + template inline bool operator !=( const LinearSpace3& a, const LinearSpace3& b ) { return a.vx != b.vx || a.vy != b.vy || a.vz != b.vz; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline std::ostream& operator<<(std::ostream& cout, const LinearSpace3& m) { + return cout << "{ vx = " << m.vx << ", vy = " << m.vy << ", vz = " << m.vz << "}"; + } + + /*! Shortcuts for common linear spaces. */ + using LinearSpace2f = LinearSpace2 ; + using LinearSpace3f = LinearSpace3 ; + using LinearSpace3fa = LinearSpace3; + + using linear2f = LinearSpace2f; + using linear3f = LinearSpace3f; +} // ::ospcommon diff --git a/crates/optix/examples/common/gdt/gdt/math/Quaternion.h b/crates/optix/examples/common/gdt/gdt/math/Quaternion.h new file mode 100644 index 00000000..ed62e924 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/Quaternion.h @@ -0,0 +1,227 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* originally taken (and adapted) from ospray, under following license */ + +// ======================================================================== // +// Copyright 2009-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. // +// ======================================================================== // + +#pragma once + +#include "vec.h" + +namespace gdt +{ + //////////////////////////////////////////////////////////////// + // Quaternion Struct + //////////////////////////////////////////////////////////////// + + template + struct QuaternionT + { + typedef vec_t Vector; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __both__ QuaternionT ( void ) { } + __both__ QuaternionT ( const QuaternionT& other ) { r = other.r; i = other.i; j = other.j; k = other.k; } + __both__ QuaternionT& operator=( const QuaternionT& other ) { r = other.r; i = other.i; j = other.j; k = other.k; return *this; } + + __both__ QuaternionT( const T& r ) : r(r), i(zero), j(zero), k(zero) {} + __both__ explicit QuaternionT( const Vector& v ) : r(zero), i(v.x), j(v.y), k(v.z) {} + __both__ QuaternionT( const T& r, const T& i, const T& j, const T& k ) : r(r), i(i), j(j), k(k) {} + __both__ QuaternionT( const T& r, const Vector& v ) : r(r), i(v.x), j(v.y), k(v.z) {} + + __inline QuaternionT( const Vector& vx, const Vector& vy, const Vector& vz ); + __inline QuaternionT( const T& yaw, const T& pitch, const T& roll ); + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __both__ QuaternionT( ZeroTy ) : r(zero), i(zero), j(zero), k(zero) {} + __both__ QuaternionT( OneTy ) : r( one), i(zero), j(zero), k(zero) {} + + /*! return quaternion for rotation around arbitrary axis */ + static __both__ QuaternionT rotate(const Vector& 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 */ + __both__ const Vector v( ) const { return Vector(i, j, k); } + + public: + T r, i, j, k; + }; + + template __both__ QuaternionT operator *( const T & a, const QuaternionT& b ) { return QuaternionT(a * b.r, a * b.i, a * b.j, a * b.k); } + template __both__ 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 __both__ QuaternionT operator +( const QuaternionT& a ) { return QuaternionT(+a.r, +a.i, +a.j, +a.k); } + template __both__ QuaternionT operator -( const QuaternionT& a ) { return QuaternionT(-a.r, -a.i, -a.j, -a.k); } + template __both__ QuaternionT conj ( const QuaternionT& a ) { return QuaternionT(a.r, -a.i, -a.j, -a.k); } + template __both__ T abs ( const QuaternionT& a ) { return sqrt(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + template __both__ 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 __both__ QuaternionT normalize ( const QuaternionT& a ) { return a*rsqrt(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + + //////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////// + + template __both__ QuaternionT operator +( const T & a, const QuaternionT& b ) { return QuaternionT(a + b.r, b.i, b.j, b.k); } + template __both__ QuaternionT operator +( const QuaternionT& a, const T & b ) { return QuaternionT(a.r + b, a.i, a.j, a.k); } + template __both__ 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 __both__ QuaternionT operator -( const T & a, const QuaternionT& b ) { return QuaternionT(a - b.r, -b.i, -b.j, -b.k); } + template __both__ QuaternionT operator -( const QuaternionT& a, const T & b ) { return QuaternionT(a.r - b, a.i, a.j, a.k); } + template __both__ 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 __both__ typename QuaternionT::Vector operator *( const QuaternionT& a, const typename QuaternionT::Vector & b ) { return (a*QuaternionT(b)*conj(a)).v(); } + template __both__ 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 __both__ QuaternionT operator /( const T & a, const QuaternionT& b ) { return a*rcp(b); } + template __both__ QuaternionT operator /( const QuaternionT& a, const T & b ) { return a*rcp(b); } + template __both__ QuaternionT operator /( const QuaternionT& a, const QuaternionT& b ) { return a*rcp(b); } + + template __both__ QuaternionT& operator +=( QuaternionT& a, const T & b ) { return a = a+b; } + template __both__ QuaternionT& operator +=( QuaternionT& a, const QuaternionT& b ) { return a = a+b; } + template __both__ QuaternionT& operator -=( QuaternionT& a, const T & b ) { return a = a-b; } + template __both__ QuaternionT& operator -=( QuaternionT& a, const QuaternionT& b ) { return a = a-b; } + template __both__ QuaternionT& operator *=( QuaternionT& a, const T & b ) { return a = a*b; } + template __both__ QuaternionT& operator *=( QuaternionT& a, const QuaternionT& b ) { return a = a*b; } + template __both__ QuaternionT& operator /=( QuaternionT& a, const T & b ) { return a = a*rcp(b); } + template __both__ QuaternionT& operator /=( QuaternionT& a, const QuaternionT& b ) { return a = a*rcp(b); } + + template __both__ typename QuaternionT::Vector + xfmPoint ( const QuaternionT& a, + const typename QuaternionT::Vector& b ) + { return (a*QuaternionT(b)*conj(a)).v(); } + + template __both__ typename QuaternionT::Vector + xfmQuaternion( const QuaternionT& a, + const typename QuaternionT::Vector& b ) + { return (a*QuaternionT(b)*conj(a)).v(); } + + + template __both__ typename QuaternionT::Vector + xfmNormal( const QuaternionT& a, + const typename QuaternionT::Vector& b ) + { return (a*QuaternionT(b)*conj(a)).v(); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __both__ 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 __both__ 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 typename QuaternionT::Vector& vx, + const typename QuaternionT::Vector& vy, + const typename QuaternionT::Vector& 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 std::ostream& operator<<(std::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; +} diff --git a/crates/optix/examples/common/gdt/gdt/math/box.h b/crates/optix/examples/common/gdt/gdt/math/box.h new file mode 100644 index 00000000..def74c79 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/box.h @@ -0,0 +1,223 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/math/vec.h" + +namespace gdt { + + template + struct interval { + typedef T scalar_t; + inline __both__ interval() + : lower(gdt::empty_bounds_lower()), + upper(gdt::empty_bounds_upper()) + {} + inline __both__ interval(T begin, T end) : begin(begin), end(end) {} + + union { + T begin; + T lower; + T lo; + }; + union { + T end; + T upper; + T hi; + }; + + inline __both__ bool contains(const T &t) const { return t >= lower && t <= upper; } + inline __both__ bool is_empty() const { return begin > end; } + inline __both__ T center() const { return (begin+end)/2; } + inline __both__ T span() const { return end - begin; } + inline __both__ T diagonal() const { return end - begin; } + inline __both__ interval &extend(const T &t) + { lower = min(lower,t); upper = max(upper,t); return *this; } + inline __both__ interval &extend(const interval &t) + { lower = min(lower,t.lower); upper = max(upper,t.upper); return *this; } + + static inline __both__ interval positive() + { + return interval(0.f,gdt::open_range_upper()); + } + }; + + template + inline __both__ std::ostream &operator<<(std::ostream &o, const interval &b) + { +#ifndef __CUDACC__ + o << "[" << b.lower << ":" << b.upper << "]"; +#endif + return o; + } + + template + inline __both__ interval build_interval(const T &a, const T &b) + { return interval(min(a,b),max(a,b)); } + + template + inline __both__ interval intersect(const interval &a, const interval &b) + { return interval(max(a.lower,b.lower),min(a.upper,b.upper)); } + + template + inline __both__ interval operator-(const interval &a, const T &b) + { return interval(a.lower-b,a.upper-b); } + + template + inline __both__ interval operator*(const interval &a, const T &b) + { return build_interval(a.lower*b,a.upper*b); } + + template + inline __both__ bool operator==(const interval &a, const interval &b) + { return a.lower == b.lower && a.upper == b.upper; } + + template + inline __both__ bool operator!=(const interval &a, const interval &b) + { return !(a == b); } + + + + template + struct box_t { + typedef T vec_t; + typedef typename T::scalar_t scalar_t; + enum { dims = T::dims }; + + inline __both__ box_t() + : lower(gdt::empty_bounds_lower()), + upper(gdt::empty_bounds_upper()) + {} + + // /*! construct a new, origin-oriented box of given size */ + // explicit inline __both__ box_t(const vec_t &box_size) + // : lower(vec_t(0)), + // upper(box_size) + // {} + /*! construct a new box around a single point */ + explicit inline __both__ box_t(const vec_t &v) + : lower(v), + upper(v) + {} + + /*! construct a new, origin-oriented box of given size */ + inline __both__ box_t(const vec_t &lo, const vec_t &hi) + : lower(lo), + upper(hi) + {} + + /*! returns new box including both ourselves _and_ the given point */ + inline __both__ box_t including(const vec_t &other) const + { return box_t(min(lower,other),max(upper,other)); } + + + /*! returns new box including both ourselves _and_ the given point */ + inline __both__ box_t &extend(const vec_t &other) + { lower = min(lower,other); upper = max(upper,other); return *this; } + /*! returns new box including both ourselves _and_ the given point */ + inline __both__ box_t &extend(const box_t &other) + { lower = min(lower,other.lower); upper = max(upper,other.upper); return *this; } + + + /*! get the d-th dimensional slab (lo[dim]..hi[dim] */ + inline __both__ interval get_slab(const uint32_t dim) + { + return interval(lower[dim],upper[dim]); + } + + inline __both__ bool contains(const vec_t &point) const + { return !(any_less_than(point,lower) || any_greater_than(point,upper)); } + + inline __both__ bool overlaps(const box_t &other) const + { return !(any_less_than(other.upper,lower) || any_greater_than(other.lower,upper)); } + + inline __both__ vec_t center() const { return (lower+upper)/(typename vec_t::scalar_t)2; } + inline __both__ vec_t span() const { return upper-lower; } + inline __both__ vec_t size() const { return upper-lower; } + + inline __both__ typename long_type_of::type volume() const + { return gdt::volume(size()); } + + inline __both__ bool empty() const { return any_less_than(upper,lower); } + + vec_t lower, upper; + }; + + // ======================================================= + // default functions + // ======================================================= + + template + inline __both__ typename long_type_of::type area(const box_t> &b) + { return area(b.upper - b.lower); } + + template + inline __both__ typename long_type_of::type area(const box_t> &b) + { + const vec_t diag = b.upper - b.lower; + return 2.f*(area(vec_t(diag.x,diag.y))+ + area(vec_t(diag.y,diag.z))+ + area(vec_t(diag.z,diag.x))); + } + + template + inline __both__ typename long_type_of::type volume(const box_t> &b) + { + const vec_t diag = b.upper - b.lower; + return diag.x*diag.y*diag.z; + } + + template + inline __both__ std::ostream &operator<<(std::ostream &o, const box_t &b) + { +#ifndef __CUDACC__ + o << "[" << b.lower << ":" << b.upper << "]"; +#endif + return o; + } + + template + inline __both__ box_t intersection(const box_t &a, const box_t &b) + { return box_t(max(a.lower,b.lower),min(a.upper,b.upper)); } + + template + inline __both__ bool operator==(const box_t &a, const box_t &b) + { return a.lower == b.lower && a.upper == b.upper; } + + template + inline __both__ bool operator!=(const box_t &a, const box_t &b) + { return !(a == b); } + + + + + // ======================================================= + // default instantiations + // ======================================================= + +#define _define_box_types(T,t) \ + typedef box_t> box2##t; \ + typedef box_t> box3##t; \ + typedef box_t> box4##t; \ + typedef box_t> box3##t##a; \ + + _define_box_types(int,i); + _define_box_types(unsigned int,ui); + _define_box_types(float,f); + +#undef _define_box_types + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/constants.h b/crates/optix/examples/common/gdt/gdt/math/constants.h new file mode 100644 index 00000000..bbacb22a --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/constants.h @@ -0,0 +1,185 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/math/vec.h" +#ifdef __CUDA_ARCH__ +#include +#else +#include +#endif + +#ifndef M_PI + #define M_PI 3.141593f +#endif + +namespace gdt { + + static struct ZeroTy + { + __both__ operator double ( ) const { return 0; } + __both__ operator float ( ) const { return 0; } + __both__ operator long long( ) const { return 0; } + __both__ operator unsigned long long( ) const { return 0; } + __both__ operator long ( ) const { return 0; } + __both__ operator unsigned long ( ) const { return 0; } + __both__ operator int ( ) const { return 0; } + __both__ operator unsigned int ( ) const { return 0; } + __both__ operator short ( ) const { return 0; } + __both__ operator unsigned short ( ) const { return 0; } + __both__ operator char ( ) const { return 0; } + __both__ operator unsigned char ( ) const { return 0; } + } zero MAYBE_UNUSED; + + static struct OneTy + { + __both__ operator double ( ) const { return 1; } + __both__ operator float ( ) const { return 1; } + __both__ operator long long( ) const { return 1; } + __both__ operator unsigned long long( ) const { return 1; } + __both__ operator long ( ) const { return 1; } + __both__ operator unsigned long ( ) const { return 1; } + __both__ operator int ( ) const { return 1; } + __both__ operator unsigned int ( ) const { return 1; } + __both__ operator short ( ) const { return 1; } + __both__ operator unsigned short ( ) const { return 1; } + __both__ operator char ( ) const { return 1; } + __both__ operator unsigned char ( ) const { return 1; } + } one MAYBE_UNUSED; + + static struct NegInfTy + { +#ifdef __CUDA_ARCH__ + __device__ operator double ( ) const { return -CUDART_INF; } + __device__ operator float ( ) const { return -CUDART_INF_F; } +#else + __both__ operator double ( ) const { return -std::numeric_limits::infinity(); } + __both__ operator float ( ) const { return -std::numeric_limits::infinity(); } + __both__ operator long long( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned long long( ) const { return std::numeric_limits::min(); } + __both__ operator long ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned long ( ) const { return std::numeric_limits::min(); } + __both__ operator int ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned int ( ) const { return std::numeric_limits::min(); } + __both__ operator short ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned short ( ) const { return std::numeric_limits::min(); } + __both__ operator char ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned char ( ) const { return std::numeric_limits::min(); } +#endif + } neg_inf MAYBE_UNUSED; + + inline __both__ float infty() { +#ifdef __CUDA_ARCH__ + return CUDART_INF_F; +#else + return std::numeric_limits::infinity(); +#endif + } + + static struct PosInfTy + { +#ifdef __CUDA_ARCH__ + __device__ operator double ( ) const { return CUDART_INF; } + __device__ operator float ( ) const { return CUDART_INF_F; } +#else + __both__ operator double ( ) const { return std::numeric_limits::infinity(); } + __both__ operator float ( ) const { return std::numeric_limits::infinity(); } + __both__ operator long long( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned long long( ) const { return std::numeric_limits::max(); } + __both__ operator long ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned long ( ) const { return std::numeric_limits::max(); } + __both__ operator int ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned int ( ) const { return std::numeric_limits::max(); } + __both__ operator short ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned short ( ) const { return std::numeric_limits::max(); } + __both__ operator char ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned char ( ) const { return std::numeric_limits::max(); } +#endif + } inf MAYBE_UNUSED, pos_inf MAYBE_UNUSED; + + static struct NaNTy + { +#ifdef __CUDA_ARCH__ + __device__ operator double( ) const { return CUDART_NAN_F; } + __device__ operator float ( ) const { return CUDART_NAN; } +#else + __both__ operator double( ) const { return std::numeric_limits::quiet_NaN(); } + __both__ operator float ( ) const { return std::numeric_limits::quiet_NaN(); } +#endif + } nan MAYBE_UNUSED; + + static struct UlpTy + { +#ifdef __CUDACC__ + // todo +#else + __both__ operator double( ) const { return std::numeric_limits::epsilon(); } + __both__ operator float ( ) const { return std::numeric_limits::epsilon(); } +#endif + } ulp MAYBE_UNUSED; + + + + template + struct limits_traits; + + template<> struct limits_traits { + template static inline __both__ T value_limits_lower(T) { return std::numeric_limits::min(); } + template static inline __both__ T value_limits_upper(T) { return std::numeric_limits::max(); } + }; + template<> struct limits_traits { + template static inline __both__ T value_limits_lower(T) { return (T)NegInfTy(); }//{ return -std::numeric_limits::infinity(); } + template static inline __both__ T value_limits_upper(T) { return (T)PosInfTy(); }//{ return +std::numeric_limits::infinity(); } + }; + + /*! lower value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_bounds_lower() + { + return limits_traits::is_integer>::value_limits_upper(T()); + } + + /*! upper value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_bounds_upper() + { + return limits_traits::is_integer>::value_limits_lower(T()); + } + + /*! lower value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_range_lower() + { + return limits_traits::is_integer>::value_limits_upper(T()); + } + + /*! upper value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_range_upper() + { + return limits_traits::is_integer>::value_limits_lower(T()); + } + + /*! lower value of a completely open range [-inf..+inf] */ + template inline __both__ T open_range_lower() + { + return limits_traits::is_integer>::value_limits_lower(T()); + } + + /*! upper value of a completely open range [-inf..+inf] */ + template inline __both__ T open_range_upper() + { + return limits_traits::is_integer>::value_limits_upper(T()); + } + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/fixedpoint.h b/crates/optix/examples/common/gdt/gdt/math/fixedpoint.h new file mode 100644 index 00000000..069be114 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/fixedpoint.h @@ -0,0 +1,36 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/gdt.h" +#include "gdt/math/constants.h" +#include + +namespace gdt { + + /*! a n-bit fixed-point float in the [0..1] region */ + template + struct FixedPoint { + FixedPoint(); + + float operator float() const { + return bits / float((1ULL << Nbits)-1); + } + storageT bits; + }; +} + diff --git a/crates/optix/examples/common/gdt/gdt/math/vec.h b/crates/optix/examples/common/gdt/gdt/math/vec.h new file mode 100644 index 00000000..72e3cf4a --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec.h @@ -0,0 +1,400 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/gdt.h" +#include "gdt/math/constants.h" +#include +#include +#include + +#ifndef __CUDACC__ +// define builtins for IDE +struct float2 { + float x; + float y; +}; +struct float3 { + float x; + float y; + float z; +}; +struct float4 { + float x; + float y; + float z; + float w; +}; +#endif + +namespace gdt { + + template struct long_type_of { typedef T type; }; + template<> struct long_type_of { typedef int64_t type; }; + template<> struct long_type_of { typedef uint64_t type; }; + + template + struct GDT_INTERFACE vec_t { T t[N]; }; + + + template struct BinaryOpResultType; + + // Binary Result type: scalar type with itself always returns same type + template + struct BinaryOpResultType { typedef ScalarType type; }; + + template<> struct BinaryOpResultType { typedef float type; }; + template<> struct BinaryOpResultType { typedef float type; }; + template<> struct BinaryOpResultType { typedef float type; }; + template<> struct BinaryOpResultType { typedef float type; }; + + template<> struct BinaryOpResultType { typedef double type; }; + template<> struct BinaryOpResultType { typedef double type; }; + template<> struct BinaryOpResultType { typedef double type; }; + template<> struct BinaryOpResultType { typedef double type; }; + + // ------------------------------------------------------------------ + // vec1 - not really a vector, but makes a scalar look like a + // vector, so we can use it in, say, box1f + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 1 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + inline __both__ vec_t(const T &v) : v(v) {} + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->v = other.v; + return *this; + } + + /*! construct 2-vector from 2-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) : v(o.v) {} + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + union { + T v; + T x; //!< just to allow all vec types to use x,y,z,w,... + }; + }; + + // ------------------------------------------------------------------ + // vec2 + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 2 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + inline __both__ vec_t(const T &t) : x(t), y(t) {} + inline __both__ vec_t(const T &x, const T &y) : x(x), y(y) {} +#ifdef __CUDACC__ + inline __both__ vec_t(const float2 v) : x(v.x), y(v.y) {} + inline __both__ vec_t(const int2 v) : x(v.x), y(v.y) {} + inline __both__ vec_t(const uint2 v) : x(v.x), y(v.y) {} + + inline __both__ operator float2() const { return make_float2(x,y); } + inline __both__ operator int2() const { return make_int2(x,y); } + inline __both__ operator uint2() const { return make_uint2(x,y); } + // inline __both__ vec_t(const size_t2 v) : x(v.x), y(v.y), z(v.z) {} + // inline __both__ operator size_t2() { return make_size_t2(x,y); } +#endif + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->x = other.x; + this->y = other.y; + return *this; + } + + /*! construct 2-vector from 2-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) : x((T)o.x), y((T)o.y) {} + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + union { + struct { T x, y; }; + struct { T s, t; }; + struct { T u, v; }; + }; + }; + + // ------------------------------------------------------------------ + // vec3 + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 3 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + inline __both__ vec_t(const T &t) : x(t), y(t), z(t) {} + inline __both__ vec_t(const T &_x, const T &_y, const T &_z) : x(_x), y(_y), z(_z) {} +#ifdef __CUDACC__ + inline __both__ vec_t(const int3 &v) : x(v.x), y(v.y), z(v.z) {} + inline __both__ vec_t(const uint3 &v) : x(v.x), y(v.y), z(v.z) {} + inline __both__ vec_t(const float3 &v) : x(v.x), y(v.y), z(v.z) {} + inline __both__ operator float3() const { return make_float3(x,y,z); } + inline __both__ operator int3() const { return make_int3(x,y,z); } + inline __both__ operator uint3() const { return make_uint3(x,y,z); } +#endif + inline __both__ explicit vec_t(const vec_t &v); + /*! construct 3-vector from 3-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) : x((T)o.x), y((T)o.y), z((T)o.z) {} + + /*! swizzle ... */ + inline __both__ vec_t yzx() const { return vec_t(y,z,x); } + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->x = other.x; + this->y = other.y; + this->z = other.z; + return *this; + } + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + template + static inline __both__ vec_t make_from(const vec_t &v, const Lambda &lambda) + { return vec_t(lambda(v.x),lambda(v.y),lambda(v.z)); } + + union { + struct { T x, y, z; }; + struct { T r, s, t; }; + struct { T u, v, w; }; + }; + }; + + // ------------------------------------------------------------------ + // vec3a + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec3a_t : public vec_t { + inline vec3a_t() {} + inline vec3a_t(const T &t) : vec_t(t) {} + inline vec3a_t(const T &x, const T &y, const T &z) : vec_t(x,y,z) {} + + template + inline vec3a_t(const vec_t &v) : vec_t(v.x,v.y,v.z) {} + + T a; + }; + + // ------------------------------------------------------------------ + // vec4 + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 4 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + + inline __both__ vec_t(const T &t) + : x(t), y(t), z(t), w(t) + {} + inline __both__ vec_t(const vec_t &xyz, const T &_w) + : x(xyz.x), y(xyz.y), z(xyz.z), w(_w) + {} + inline __both__ vec_t(const T &_x, const T &_y, const T &_z, const T &_w) + : x(_x), y(_y), z(_z), w(_w) + {} + +#ifdef __CUDACC__ + inline __both__ vec_t(const float4 &v) + : x(v.x), y(v.y), z(v.z), w(v.w) + {} + inline __both__ vec_t(const int4 &v) + : x(v.x), y(v.y), z(v.z), w(v.w) + {} + inline __both__ operator float4() const { return make_float4(x,y,z,w); } + inline __both__ operator int4() const { return make_int4(x,y,z,w); } +#endif + /*! construct 3-vector from 3-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) + : x(o.x), y(o.y), z(o.z), w(o.w) + {} + inline __both__ vec_t(const vec_t &o) : x(o.x), y(o.y), z(o.z), w(o.w) {} + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->x = other.x; + this->y = other.y; + this->z = other.z; + this->w = other.w; + return *this; + } + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + template + static inline __both__ vec_t make_from(const vec_t &v, + const Lambda &lambda) + { return vec_t(lambda(v.x),lambda(v.y),lambda(v.z),lambda(v.w)); } + + T x, y, z, w; + }; + + template + inline __both__ vec_t::vec_t(const vec_t &v) + : x(v.x), y(v.y), z(v.z) + {} + + // ======================================================= + // default functions + // ======================================================= + + template + inline __both__ typename long_type_of::type area(const vec_t &v) + { return (typename long_type_of::type)(v.x)*(typename long_type_of::type)(v.y); } + + + template + inline __both__ typename long_type_of::type volume(const vec_t &v) + { return + (typename long_type_of::type)(v.x)* + (typename long_type_of::type)(v.y)* + (typename long_type_of::type)(v.z); + } + + template + inline __both__ typename long_type_of::type volume(const vec_t &v) + { return + (typename long_type_of::type)(v.x)* + (typename long_type_of::type)(v.y)* + (typename long_type_of::type)(v.z)* + (typename long_type_of::type)(v.w); + } + + template + inline __both__ typename long_type_of::type area(const vec_t &v) + { return + T(2)*((typename long_type_of::type)(v.x)*v.y+ + (typename long_type_of::type)(v.y)*v.z+ + (typename long_type_of::type)(v.z)*v.x); + } + + + + /*! vector cross product */ + template + inline __both__ vec_t cross(const vec_t &a, const vec_t &b) + { + return vec_t(a.y*b.z-b.y*a.z, + a.z*b.x-b.z*a.x, + a.x*b.y-b.x*a.y); + } + + /*! vector cross product */ + template + inline __both__ T dot(const vec_t &a, const vec_t &b) + { + return a.x*b.x + a.y*b.y + a.z*b.z; + } + + /*! vector cross product */ + template + inline __both__ vec_t normalize(const vec_t &v) + { + return v * 1.f/gdt::overloaded::sqrt(dot(v,v)); + } + + /*! vector cross product */ + template + inline __both__ T length(const vec_t &v) + { + return gdt::overloaded::sqrt(dot(v,v)); + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << ")"; + return o; + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << "," << v.y << ")"; + return o; + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << "," << v.y << "," << v.z << ")"; + return o; + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << "," << v.y << "," << v.z << "," << v.w << ")"; + return o; + } + + // ======================================================= + // default instantiations + // ======================================================= + +#define _define_vec_types(T,t) \ +using vec2##t = vec_t; \ +using vec3##t = vec_t; \ +using vec4##t = vec_t; \ +using vec3##t##a = vec3a_t; \ + +//#define _define_vec_types(T,t) \ +// typedef vec_t vec2##t; \ +// typedef vec_t vec3##t; \ +// typedef vec_t vec4##t; \ +// typedef vec3a_t vec3##t##a; \ + + _define_vec_types(int8_t ,c); + _define_vec_types(int16_t ,s); + _define_vec_types(int32_t ,i); + _define_vec_types(int64_t ,l); + _define_vec_types(uint8_t ,uc); + _define_vec_types(uint16_t,us); + _define_vec_types(uint32_t,ui); + _define_vec_types(uint64_t,ul); + _define_vec_types(float,f); + _define_vec_types(double,d); + +#undef _define_vec_types + +} // ::gdt + + +#include "vec/functors.h" +// comparison operators +#include "vec/compare.h" +#include "vec/rotate.h" diff --git a/crates/optix/examples/common/gdt/gdt/math/vec/compare.h b/crates/optix/examples/common/gdt/gdt/math/vec/compare.h new file mode 100644 index 00000000..0e50fdba --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec/compare.h @@ -0,0 +1,59 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace gdt { + + // ------------------------------------------------------------------ + // == + // ------------------------------------------------------------------ + +#if __CUDACC__ + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return (a.x==b.x) & (a.y==b.y); } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return (a.x==b.x) & (a.y==b.y) & (a.z==b.z); } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return (a.x==b.x) & (a.y==b.y) & (a.z==b.z) & (a.w==b.w); } +#else + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return a.x==b.x && a.y==b.y; } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return a.x==b.x && a.y==b.y && a.z==b.z; } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return a.x==b.x && a.y==b.y && a.z==b.z && a.w==b.w; } +#endif + + // ------------------------------------------------------------------ + // != + // ------------------------------------------------------------------ + + template + inline __both__ bool operator!=(const vec_t &a, const vec_t &b) + { return !(a==b); } + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/vec/functors.h b/crates/optix/examples/common/gdt/gdt/math/vec/functors.h new file mode 100644 index 00000000..a2c8e741 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec/functors.h @@ -0,0 +1,364 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace gdt { + + // inline __both__ float min(float x, float y) { return fminf(x,y); } + // inline __both__ float max(float x, float y) { return fmaxf(x,y); } + + // ======================================================= + // scalar functors + // ======================================================= + + template + inline __both__ T divRoundUp(const T &a, const T &b) + { //causes issues on ubuntu16-gcc: static_assert(std::numeric_limits::is_integer); + return T((a+b-1)/b); } + + // ======================================================= + // vector specializations of those scalar functors + // ======================================================= + + template inline __both__ + bool any_less_than(const vec_t &a, const vec_t &b) + { for (int i=0;i inline __both__ + bool any_greater_than(const vec_t &a, const vec_t &b) + { for (int i=0;i b[i]) return true; return false; } + + // ------------------------------------------------------- + // unary functors + // ------------------------------------------------------- + + template + inline __both__ T clamp(const T &val, const T &lo, const T &hi) + { return min(hi,max(lo,val)); } + + template + inline __both__ T clamp(const T &val, const T &hi) + { return clamp(val,(T)0,hi); } + +#define _define_float_functor(func) \ + template inline __both__ vec_t func(const vec_t &v) \ + { return vec_t(func(v.x),func(v.y)); } \ + \ + template inline __both__ vec_t func(const vec_t &v) \ + { return vec_t(func(v.x),func(v.y),func(v.z)); } \ + \ + template inline __both__ vec_t func(const vec_t &v) \ + { return vec_t(func(v.x),func(v.y),func(v.z),func(v.w)); } \ + + _define_float_functor(rcp) + _define_float_functor(sin) + _define_float_functor(cos) + _define_float_functor(abs) + _define_float_functor(saturate) + +#undef _define_float_functor + + // ------------------------------------------------------- + // binary functors + // ------------------------------------------------------- + // template + // __both__ vec_t divRoundUp(const vec_t &a, const vec_t &b) + // { return vec_t(divRoundUp(a.x,b.x),divRoundUp(a.y,b.y)); } + + // template + // __both__ vec_t divRoundUp(const vec_t &a, const vec_t &b) + // { return vec_t(divRoundUp(a.x,b.x),divRoundUp(a.y,b.y),divRoundUp(a.z,b.z)); } + +#define _define_binary_functor(fct) \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x)); \ + } \ + \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x), \ + fct(a.y,b.y)); \ + } \ + \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x), \ + fct(a.y,b.y), \ + fct(a.z,b.z)); \ + } \ + \ + template \ + __both__ vec_t::type,3> \ + fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t::type,3> \ + (fct(a.x,b.x), \ + fct(a.y,b.y), \ + fct(a.z,b.z)); \ + } \ + \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x), \ + fct(a.y,b.y), \ + fct(a.z,b.z), \ + fct(a.w,b.w)); \ + } \ + + + _define_binary_functor(divRoundUp) + _define_binary_functor(min) + _define_binary_functor(max) +#undef _define_binary_functor + + + + + + + // ------------------------------------------------------- + // binary operators + // ------------------------------------------------------- +#define _define_operator(op) \ + /* vec op vec */ \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const vec_t &b) \ + { return vec_t(a.x op b.x, a.y op b.y); } \ + \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const vec_t &b) \ + { return vec_t(a.x op b.x, a.y op b.y, a.z op b.z); } \ + \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const vec_t &b) \ + { return vec_t(a.x op b.x,a.y op b.y,a.z op b.z,a.w op b.w); } \ + \ + /* vec op scalar */ \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const T &b) \ + { return vec_t(a.x op b, a.y op b); } \ + \ + template \ + inline __both__ vec_t::type,3> \ + operator op(const vec_t &a, const T2 &b) \ + { return vec_t::type,3> \ + (a.x op b, a.y op b, a.z op b); \ + } \ + \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const T &b) \ + { return vec_t(a.x op b, a.y op b, a.z op b, a.w op b); } \ + \ + /* scalar op vec */ \ + template \ + inline __both__ vec_t operator op(const T &a, \ + const vec_t &b) \ + { return vec_t(a op b.x, a op b.y); } \ + \ + template \ + inline __both__ vec_t operator op(const T &a, \ + const vec_t &b) \ + { return vec_t(a op b.x, a op b.y, a op b.z); } \ + \ + template \ + inline __both__ vec_t operator op(const T &a, \ + const vec_t &b) \ + { return vec_t(a op b.x, a op b.y, a op b.z, a op b.w); } \ + \ + \ + + _define_operator(*); + _define_operator(/); + _define_operator(+); + _define_operator(-); + +#undef _define_operator + + + + + // ------------------------------------------------------- + // unary operators + // ------------------------------------------------------- + + template + inline __both__ vec_t operator-(const vec_t &v) + { return vec_t(-v.x, -v.y); } + + template + inline __both__ vec_t operator+(const vec_t &v) + { return vec_t(v.x, v.y); } + + template + inline __both__ vec_t operator-(const vec_t &v) + { return vec_t(-v.x, -v.y, -v.z); } + + template + inline __both__ vec_t operator+(const vec_t &v) + { return vec_t(v.x, v.y, v.z); } + + + + // ------------------------------------------------------- + // binary op-assign operators + // ------------------------------------------------------- +#define _define_op_assign_operator(operator_op,op) \ + /* vec op vec */ \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const vec_t &b) \ + { \ + a.x op (T)b.x; \ + a.y op (T)b.y; \ + return a; \ + } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const vec_t &b) \ + { \ + a.x op (T)b.x; \ + a.y op (T)b.y; \ + a.z op (T)b.z; \ + return a; \ + } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const vec_t &b) \ + { \ + a.x op (T)b.x; \ + a.y op (T)b.y; \ + a.z op (T)b.z; \ + a.w op (T)b.w; \ + return a; \ + } \ + \ + /* vec op scalar */ \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const OT &b) \ + { a.x op (T)b; a.y op (T)b; return a; } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const OT &b) \ + { a.x op (T)b; a.y op (T)b; a.z op (T)b; return a; } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const OT &b) \ + { a.x op (T)b; a.y op (T)b; a.z op (T)b; a.w op (T)b; return a; } \ + + _define_op_assign_operator(operator*=,*=); + _define_op_assign_operator(operator/=,/=); + _define_op_assign_operator(operator+=,+=); + _define_op_assign_operator(operator-=,-=); + +#undef _define_op_assign_operator + + + template + __both__ T reduce_min(const vec_t &v) { return v.x; } + template + __both__ T reduce_min(const vec_t &v) { return min(v.x,v.y); } + template + __both__ T reduce_min(const vec_t &v) { return min(min(v.x,v.y),v.z); } + template + __both__ T reduce_min(const vec_t &v) { return min(min(v.x,v.y),min(v.z,v.w)); } + template + __both__ T reduce_max(const vec_t &v) { return max(v.x,v.y); } + template + __both__ T reduce_max(const vec_t &v) { return max(max(v.x,v.y),v.z); } + template + __both__ T reduce_max(const vec_t &v) { return max(max(v.x,v.y),max(v.z,v.w)); } + + + template + __both__ vec_t madd(const vec_t &a, const vec_t &b, const vec_t &c) + { + return a*b + c; + } + + + template + __both__ int arg_max(const vec_t &v) + { + int biggestDim = 0; + for (int i=1;i abs(v[biggestDim])) biggestDim = i; + return biggestDim; + } + + + // less, for std::set, std::map, etc + template + __both__ bool operator<(const vec_t &a, const vec_t &b) + { + if (a.x < b.x) return true; + if (a.x == b.x && a.y < b.y) return true; + if (a.x == b.x && a.y == b.y && a.z < b.z) return true; + return false; + // return + // (a.x < b.x) | + // ((a.x == b.x) & ((a.y < b.y) | + // ((a.y == b.y) & (a.z < b.z)))); + } + + /*! helper function that creates a semi-random color from an ID */ + inline __both__ vec3f randomColor(int i) + { + int r = unsigned(i)*13*17 + 0x234235; + int g = unsigned(i)*7*3*5 + 0x773477; + int b = unsigned(i)*11*19 + 0x223766; + return vec3f((r&255)/255.f, + (g&255)/255.f, + (b&255)/255.f); + } + + /*! helper function that creates a semi-random color from an ID */ + inline __both__ vec3f randomColor(size_t idx) + { + unsigned int r = (unsigned int)(idx*13*17 + 0x234235); + unsigned int g = (unsigned int)(idx*7*3*5 + 0x773477); + unsigned int b = (unsigned int)(idx*11*19 + 0x223766); + return vec3f((r&255)/255.f, + (g&255)/255.f, + (b&255)/255.f); + } + + /*! helper function that creates a semi-random color from an ID */ + template + inline __both__ vec3f randomColor(const T *ptr) + { + return randomColor((size_t)ptr); + } + + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/vec/rotate.h b/crates/optix/examples/common/gdt/gdt/math/vec/rotate.h new file mode 100644 index 00000000..2a7ba29a --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec/rotate.h @@ -0,0 +1,40 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace gdt { + + /*! perform 'rotation' of float a by amount b. Both a and b must be + in 0,1 range, the result will be (a+1) clamped to that same + range (ie, it is the value a shifted by the amount b to the + right, and re-entering the [0,1) range on the left if it + "rotates" out on the right */ + inline __both__ float rotate(const float a, const float b) + { + float sum = a+b; + return (sum-1.f)<0.f?sum:(sum-1.f); + } + + /*! perform 'rotation' of float a by amount b. Both a and b must be + in 0,1 range, the result will be (a+1) clamped to that same + range (ie, it is the value a shifted by the amount b to the + right, and re-entering the [0,1) range on the left if it + "rotates" out on the right */ + inline __both__ vec2f rotate(const vec2f a, const vec2f b) + { return vec2f(rotate(a.x,b.x),rotate(a.y,b.y)); } + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/random/random.h b/crates/optix/examples/common/gdt/gdt/random/random.h new file mode 100644 index 00000000..e2f77465 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/random/random.h @@ -0,0 +1,91 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* pieces originally taken from optixPathTracer/random.h example, + under following license */ + +/* + * Copyright (c) 2018 NVIDIA 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: + * * 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 NVIDIA 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 ``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 + +#include "gdt/gdt.h" + +namespace gdt { + + /*! simple 24-bit linear congruence generator */ + template + struct LCG { + + inline __both__ LCG() + { /* intentionally empty so we can use it in device vars that + don't allow dynamic initialization (ie, PRD) */ + } + inline __both__ LCG(unsigned int val0, unsigned int val1) + { init(val0,val1); } + + inline __both__ void init(unsigned int val0, unsigned int val1) + { + unsigned int v0 = val0; + unsigned int v1 = val1; + unsigned int s0 = 0; + + for (unsigned int n = 0; n < N; n++) { + s0 += 0x9e3779b9; + v0 += ((v1<<4)+0xa341316c)^(v1+s0)^((v1>>5)+0xc8013ea4); + v1 += ((v0<<4)+0xad90777d)^(v0+s0)^((v0>>5)+0x7e95761e); + } + state = v0; + } + + // Generate random unsigned int in [0, 2^24) + inline __both__ float operator() () + { + const uint32_t LCG_A = 1664525u; + const uint32_t LCG_C = 1013904223u; + state = (LCG_A * state + LCG_C); + return (state & 0x00FFFFFF) / (float) 0x01000000; + } + + uint32_t state; + }; + +} // ::gdt diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml new file mode 100644 index 00000000..6c589340 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ex02_pipeline" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +optix = {path = "../../"} +cust = {path = "../../../cust"} +anyhow = "1.0.44" +ustr = "0.8.1" + +[build-dependencies] +find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs new file mode 100644 index 00000000..64d4aca8 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -0,0 +1,49 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; + +fn main() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + + cuda_include = cuda_include.join("include"); + + let args = vec![ + format!("-I{}", optix_include.display()), + format!("-I{}/../common/gdt", manifest_dir), + ]; + + compile_to_ptx("src/ex02_pipeline.cu", &args); +} + +fn compile_to_ptx(cu_path: &str, args: &[String]) { + println!("cargo:rerun-if-changed={}", cu_path); + + let full_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(cu_path); + + let mut ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(cu_path); + ptx_path.set_extension("ptx"); + std::fs::create_dir_all(ptx_path.parent().unwrap()).unwrap(); + + let output = std::process::Command::new("nvcc") + .arg("-ptx") + .arg(&full_path) + .arg("-o") + .arg(&ptx_path) + .args(args) + .output() + .expect("failed to fun nvcc"); + + if !output.status.success() { + panic!("{}", unsafe { String::from_utf8_unchecked(output.stderr) }); + } +} diff --git a/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu b/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu new file mode 100644 index 00000000..26e3c43b --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu @@ -0,0 +1,105 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace osc { + +using namespace gdt; + +struct LaunchParams { + int frameID{0}; + uint32_t* colorBuffer; + vec2i fbSize; +}; + +/*! launch parameters in constant memory, filled in by optix upon + optixLaunch (this gets filled in from the buffer we pass to + optixLaunch) */ +extern "C" __constant__ LaunchParams PARAMS; + +//------------------------------------------------------------------------------ +// closest hit and anyhit programs for radiance-type rays. +// +// Note eventually we will have to create one pair of those for each +// ray type and each geometry type we want to render; but this +// simple example doesn't use any actual geometries yet, so we only +// create a single, dummy, set of them (we do have to have at least +// one group of them to set up the SBT) +//------------------------------------------------------------------------------ + +extern "C" __global__ void +__closesthit__radiance() { /*! for this simple example, this will remain empty + */ +} + +extern "C" __global__ void +__anyhit__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// miss program that gets called for any ray that did not have a +// valid intersection +// +// as with the anyhit/closest hit programs, in this example we only +// need to have _some_ dummy function to set up a valid SBT +// ------------------------------------------------------------------------------ + +extern "C" __global__ void +__miss__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// ray gen program - the actual rendering happens in here +//------------------------------------------------------------------------------ +extern "C" __global__ void __raygen__renderFrame() { + if (PARAMS.frameID == 0 && optixGetLaunchIndex().x == 0 && + optixGetLaunchIndex().y == 0) { + // we could of course also have used optixGetLaunchDims to query + // the launch size, but accessing the PARAMS here + // makes sure they're not getting optimized away (because + // otherwise they'd not get used) + printf("############################################\n"); + printf("Hello world from OptiX 7 raygen program!\n(within a " + "%ix%i-sized launch)\n", + PARAMS.fbSize.x, PARAMS.fbSize.y); + printf("############################################\n"); + } + + // ------------------------------------------------------------------ + // for this example, produce a simple test pattern: + // ------------------------------------------------------------------ + + // compute a test pattern based on pixel ID + const int ix = optixGetLaunchIndex().x; + const int iy = optixGetLaunchIndex().y; + + const int r = (ix % 256); + const int g = (iy % 256); + const int b = ((ix + iy) % 256); + + // convert to 32-bit rgba value (we explicitly set alpha to 0xff + // to make stb_image_write happy ... + const uint32_t rgba = 0xff000000 | (r << 0) | (g << 8) | (b << 16); + + // and write to frame buffer ... + const uint32_t fbIndex = ix + iy * PARAMS.fbSize.x; + PARAMS.colorBuffer[fbIndex] = rgba; +} + +} // namespace osc diff --git a/crates/optix/examples/ex02_pipeline/src/launch_params.h b/crates/optix/examples/ex02_pipeline/src/launch_params.h new file mode 100644 index 00000000..e4364e45 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/launch_params.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +namespace osc { +} // namespace osc diff --git a/crates/optix/examples/ex02_pipeline/src/main.rs b/crates/optix/examples/ex02_pipeline/src/main.rs new file mode 100644 index 00000000..00f2ba65 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/main.rs @@ -0,0 +1,8 @@ +mod renderer; +use renderer::Renderer; + +fn main() -> Result<(), Box> { + let mut renderer = Renderer::new(256, 128)?; + renderer.render()?; + Ok(()) +} \ No newline at end of file diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs new file mode 100644 index 00000000..bff1e1d1 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -0,0 +1,240 @@ +use anyhow::{Context, Result}; +use cust::context::{Context as CuContext, ContextFlags}; +use cust::device::{Device, DeviceAttribute}; +use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::stream::{Stream, StreamFlags}; +use cust::CudaFlags; +use optix::{ + context::DeviceContext, + module::{ + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + PipelineCompileOptions, TraversableGraphFlags, + }, + pipeline::{Pipeline, PipelineLinkOptions}, + program_group::ProgramGroupDesc, + shader_binding_table::{SbtRecord, ShaderBindingTable}, +}; + +use ustr::ustr; + +pub struct Renderer { + ctx: DeviceContext, + cuda_context: CuContext, + stream: Stream, + launch_params: LaunchParams, + buf_launch_params: DBox, + buf_raygen: DBuffer, + buf_hitgroup: DBuffer, + buf_miss: DBuffer, + sbt: optix::sys::OptixShaderBindingTable, + pipeline: Pipeline, + color_buffer: DBuffer, +} + +impl Renderer { + pub fn new(width: usize, height: usize) -> Result> { + init_optix()?; + + // create CUDA and OptiX contexts + let device = Device::get_device(0)?; + let tex_align = device.get_attribute(DeviceAttribute::TextureAlignment)?; + let srf_align = device.get_attribute(DeviceAttribute::SurfaceAlignment)?; + println!("tex align: {}\nsrf align: {}", tex_align, srf_align); + + let cuda_context = + CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let stream = Stream::new(StreamFlags::DEFAULT, None)?; + + let mut ctx = DeviceContext::new(&cuda_context)?; + // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + + // create module + let module_compile_options = ModuleCompileOptions { + max_register_count: 50, + opt_level: CompileOptimizationLevel::Default, + debug_level: CompileDebugLevel::None, + }; + + let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + .uses_motion_blur(false) + .num_attribute_values(2) + .num_payload_values(2) + .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) + .exception_flags(ExceptionFlags::NONE); + + let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); + + let (module, _log) = ctx + .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) + .context("Create module")?; + + // create raygen program + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + + let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + + // create miss program + let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + + let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + + let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( + Some((&module, ustr("__closesthit__radiance"))), + Some((&module, ustr("__anyhit__radiance"))), + None, + ); + + // create hitgroup programs + let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.iter().cloned()); + program_groups.extend(pg_miss.iter().cloned()); + program_groups.extend(pg_hitgroup.iter().cloned()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = ctx.pipeline_create( + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + + // create SBT + let rec_raygen: Vec<_> = pg_raygen + .iter() + .map(|pg| RaygenRecord::pack(0, pg).expect("failed to pack raygen record")) + .collect(); + + let rec_miss: Vec<_> = pg_miss + .iter() + .map(|pg| MissRecord::pack(0, pg).expect("failed to pack miss record")) + .collect(); + + let num_objects = 1; + let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); + + let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + + let sbt = ShaderBindingTable::new(&mut buf_raygen) + .miss(&mut buf_miss) + .hitgroup(&mut buf_hitgroup) + .build(); + + let mut color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; + + let launch_params = LaunchParams { + frame_id: 0, + color_buffer: color_buffer.as_device_ptr(), + fb_size: Point2i { + x: width as i32, + y: height as i32, + }, + }; + + let buf_launch_params = DBox::new(&launch_params)?; + + Ok(Renderer { + ctx, + cuda_context, + stream, + launch_params, + buf_launch_params, + buf_raygen, + buf_hitgroup, + buf_miss, + sbt, + pipeline, + color_buffer, + }) + } + + pub fn resize( + &mut self, + width: usize, + height: usize, + ) -> Result<(), Box> { + self.color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; + self.launch_params.fb_size.x = width as i32; + self.launch_params.fb_size.y = height as i32; + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + Ok(()) + } + + pub fn render(&mut self) -> Result<(), Box> { + self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.frame_id += 1; + + unsafe { + optix::launch( + &self.pipeline, + &self.stream, + &mut self.buf_launch_params, + &self.sbt, + self.launch_params.fb_size.x as u32, + self.launch_params.fb_size.y as u32, + 1, + )?; + } + + self.stream.synchronize()?; + + Ok(()) + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Point2i { + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct LaunchParams { + pub frame_id: i32, + pub color_buffer: DevicePointer, + pub fb_size: Point2i, +} + +unsafe impl DeviceCopy for LaunchParams {} + +type RaygenRecord = SbtRecord; +type MissRecord = SbtRecord; + +#[derive(Copy, Clone, Default)] +struct HitgroupSbtData { + object_id: u32, +} +unsafe impl DeviceCopy for HitgroupSbtData {} +type HitgroupRecord = SbtRecord; + +fn init_optix() -> Result<(), Box> { + cust::init(CudaFlags::empty())?; + let device_count = Device::num_devices()?; + if device_count == 0 { + panic!("No CUDA devices found!"); + } + + optix::init()?; + Ok(()) +} diff --git a/crates/optix/optix_wrapper.rs b/crates/optix/optix_wrapper.rs index 60ae295c..45b3934e 100644 --- a/crates/optix/optix_wrapper.rs +++ b/crates/optix/optix_wrapper.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.55.1 */ +/* automatically generated by rust-bindgen 0.59.1 */ #[repr(C)] pub struct __BindgenUnionField(::std::marker::PhantomData); @@ -187,7 +187,7 @@ impl OptixResult { pub const OPTIX_ERROR_UNKNOWN: OptixResult = OptixResult(7999); } #[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct OptixResult(pub ::std::os::raw::c_uint); pub mod OptixDeviceProperty { pub type Type = ::std::os::raw::c_uint; @@ -215,13 +215,22 @@ pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ OptixDeviceContextValidationMode = 4294967295; pub type OptixDeviceContextValidationMode = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixDeviceContextOptions { pub logCallbackFunction: OptixLogCallback, pub logCallbackData: *mut ::std::os::raw::c_void, pub logCallbackLevel: ::std::os::raw::c_int, pub validationMode: OptixDeviceContextValidationMode, } +impl Default for OptixDeviceContextOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE: OptixHitKind = 254; pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_BACK_FACE: OptixHitKind = 255; pub type OptixHitKind = ::std::os::raw::c_uint; @@ -259,6 +268,15 @@ pub struct OptixBuildInputTriangleArray { pub primitiveIndexOffset: ::std::os::raw::c_uint, pub transformFormat: OptixTransformFormat, } +impl Default for OptixBuildInputTriangleArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_CUSTOM: OptixPrimitiveType = 9472; pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE: OptixPrimitiveType = 9473; @@ -292,8 +310,17 @@ pub struct OptixBuildInputCurveArray { pub flag: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } +impl Default for OptixBuildInputCurveArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixAabb { pub minX: f32, pub minY: f32, @@ -314,11 +341,29 @@ pub struct OptixBuildInputCustomPrimitiveArray { pub sbtIndexOffsetStrideInBytes: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } +impl Default for OptixBuildInputCustomPrimitiveArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixBuildInputInstanceArray { pub instances: CUdeviceptr, pub numInstances: ::std::os::raw::c_uint, } +impl Default for OptixBuildInputInstanceArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES: OptixBuildInputType = 8513; pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES: OptixBuildInputType = 8514; pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES: OptixBuildInputType = 8515; @@ -334,6 +379,15 @@ pub struct OptixBuildInput__bindgen_ty_1 { pub pad: __BindgenUnionField<[::std::os::raw::c_char; 1024usize]>, pub bindgen_union_field: [u64; 128usize], } +impl Default for OptixBuildInput__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE: OptixInstanceFlags = 0; pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING: OptixInstanceFlags = 1; @@ -343,7 +397,7 @@ pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT: OptixInstanceFl pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM: OptixInstanceFlags = 64; pub type OptixInstanceFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixInstance { pub transform: [f32; 12usize], pub instanceId: ::std::os::raw::c_uint, @@ -369,7 +423,7 @@ pub const OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH: OptixMotionFlags = 1; pub const OptixMotionFlags_OPTIX_MOTION_FLAG_END_VANISH: OptixMotionFlags = 2; pub type OptixMotionFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixMotionOptions { pub numKeys: ::std::os::raw::c_ushort, pub flags: ::std::os::raw::c_ushort, @@ -377,18 +431,36 @@ pub struct OptixMotionOptions { pub timeEnd: f32, } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixAccelBuildOptions { pub buildFlags: ::std::os::raw::c_uint, pub operation: OptixBuildOperation, pub motionOptions: OptixMotionOptions, } +impl Default for OptixAccelBuildOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixAccelBufferSizes { pub outputSizeInBytes: size_t, pub tempSizeInBytes: size_t, pub tempUpdateSizeInBytes: size_t, } +impl Default for OptixAccelBufferSizes { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE: OptixAccelPropertyType = 8577; pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS: OptixAccelPropertyType = 8578; pub type OptixAccelPropertyType = ::std::os::raw::c_uint; @@ -397,13 +469,22 @@ pub struct OptixAccelEmitDesc { pub result: CUdeviceptr, pub type_: OptixAccelPropertyType, } +impl Default for OptixAccelEmitDesc { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixAccelRelocationInfo { pub info: [::std::os::raw::c_ulonglong; 4usize], } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixStaticTransform { pub child: OptixTraversableHandle, pub pad: [::std::os::raw::c_uint; 2usize], @@ -411,7 +492,7 @@ pub struct OptixStaticTransform { pub invTransform: [f32; 12usize], } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixMatrixMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, @@ -419,7 +500,7 @@ pub struct OptixMatrixMotionTransform { pub transform: [[f32; 12usize]; 2usize], } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixSRTData { pub sx: f32, pub a: f32, @@ -439,7 +520,7 @@ pub struct OptixSRTData { pub tz: f32, } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixSRTMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, @@ -472,6 +553,15 @@ pub struct OptixImage2D { pub pixelStrideInBytes: ::std::os::raw::c_uint, pub format: OptixPixelFormat::Type, } +impl Default for OptixImage2D { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub mod OptixDenoiserModelKind { pub type Type = ::std::os::raw::c_uint; pub const OPTIX_DENOISER_MODEL_KIND_LDR: Type = 8994; @@ -480,7 +570,7 @@ pub mod OptixDenoiserModelKind { pub const OPTIX_DENOISER_MODEL_KIND_TEMPORAL: Type = 8997; } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixDenoiserOptions { pub guideAlbedo: ::std::os::raw::c_uint, pub guideNormal: ::std::os::raw::c_uint, @@ -491,12 +581,30 @@ pub struct OptixDenoiserGuideLayer { pub normal: OptixImage2D, pub flow: OptixImage2D, } +impl Default for OptixDenoiserGuideLayer { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixDenoiserLayer { pub input: OptixImage2D, pub previousOutput: OptixImage2D, pub output: OptixImage2D, } +impl Default for OptixDenoiserLayer { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixDenoiserParams { pub denoiseAlpha: ::std::os::raw::c_uint, @@ -504,6 +612,15 @@ pub struct OptixDenoiserParams { pub blendFactor: f32, pub hdrAverageColor: CUdeviceptr, } +impl Default for OptixDenoiserParams { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixDenoiserSizes { pub stateSizeInBytes: size_t, @@ -511,6 +628,15 @@ pub struct OptixDenoiserSizes { pub withoutOverlapScratchSizeInBytes: size_t, pub overlapWindowSizeInPixels: ::std::os::raw::c_uint, } +impl Default for OptixDenoiserSizes { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixRayFlags_OPTIX_RAY_FLAG_NONE: OptixRayFlags = 0; pub const OptixRayFlags_OPTIX_RAY_FLAG_DISABLE_ANYHIT: OptixRayFlags = 1; pub const OptixRayFlags_OPTIX_RAY_FLAG_ENFORCE_ANYHIT: OptixRayFlags = 2; @@ -555,8 +681,17 @@ pub struct OptixModuleCompileBoundValueEntry { pub boundValuePtr: *const ::std::os::raw::c_void, pub annotation: *const ::std::os::raw::c_char, } +impl Default for OptixModuleCompileBoundValueEntry { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixModuleCompileOptions { pub maxRegisterCount: ::std::os::raw::c_int, pub optLevel: OptixCompileOptimizationLevel::Type, @@ -564,6 +699,15 @@ pub struct OptixModuleCompileOptions { pub boundValues: *const OptixModuleCompileBoundValueEntry, pub numBoundValues: ::std::os::raw::c_uint, } +impl Default for OptixModuleCompileOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub mod OptixProgramGroupKind { pub type Type = ::std::os::raw::c_uint; pub const OPTIX_PROGRAM_GROUP_KIND_RAYGEN: Type = 9249; @@ -575,13 +719,22 @@ pub mod OptixProgramGroupKind { pub const OptixProgramGroupFlags_OPTIX_PROGRAM_GROUP_FLAGS_NONE: OptixProgramGroupFlags = 0; pub type OptixProgramGroupFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupSingleModule { pub module: OptixModule, pub entryFunctionName: *const ::std::os::raw::c_char, } +impl Default for OptixProgramGroupSingleModule { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupHitgroup { pub moduleCH: OptixModule, pub entryFunctionNameCH: *const ::std::os::raw::c_char, @@ -590,14 +743,32 @@ pub struct OptixProgramGroupHitgroup { pub moduleIS: OptixModule, pub entryFunctionNameIS: *const ::std::os::raw::c_char, } +impl Default for OptixProgramGroupHitgroup { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupCallables { pub moduleDC: OptixModule, pub entryFunctionNameDC: *const ::std::os::raw::c_char, pub moduleCC: OptixModule, pub entryFunctionNameCC: *const ::std::os::raw::c_char, } +impl Default for OptixProgramGroupCallables { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] #[derive(Copy, Clone)] pub struct OptixProgramGroupDesc { @@ -613,10 +784,27 @@ pub union OptixProgramGroupDesc__bindgen_ty_1 { pub exception: OptixProgramGroupSingleModule, pub callables: OptixProgramGroupCallables, pub hitgroup: OptixProgramGroupHitgroup, - _bindgen_union_align: [u64; 6usize], +} +impl Default for OptixProgramGroupDesc__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +impl Default for OptixProgramGroupDesc { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixProgramGroupOptions { pub reserved: ::std::os::raw::c_int, } @@ -672,12 +860,30 @@ pub struct OptixPipelineCompileOptions { pub reserved: ::std::os::raw::c_uint, pub reserved2: size_t, } +impl Default for OptixPipelineCompileOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixPipelineLinkOptions { pub maxTraceDepth: ::std::os::raw::c_uint, pub debugLevel: OptixCompileDebugLevel::Type, } +impl Default for OptixPipelineLinkOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixShaderBindingTable { pub raygenRecord: CUdeviceptr, @@ -692,8 +898,17 @@ pub struct OptixShaderBindingTable { pub callablesRecordStrideInBytes: ::std::os::raw::c_uint, pub callablesRecordCount: ::std::os::raw::c_uint, } +impl Default for OptixShaderBindingTable { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixStackSizes { pub cssRG: ::std::os::raw::c_uint, pub cssMS: ::std::os::raw::c_uint, @@ -717,11 +932,20 @@ pub type OptixQueryFunctionTable_t = ::std::option::Option< ) -> OptixResult, >; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixBuiltinISOptions { pub builtinISModuleType: OptixPrimitiveType, pub usesMotionBlur: ::std::os::raw::c_int, } +impl Default for OptixBuiltinISOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} extern "C" { pub fn optixGetErrorName(result: OptixResult) -> *const ::std::os::raw::c_char; } @@ -1024,7 +1248,7 @@ extern "C" { ) -> OptixResult; } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixFunctionTable { pub optixGetErrorName: ::std::option::Option< unsafe extern "C" fn(result: OptixResult) -> *const ::std::os::raw::c_char, @@ -1332,8 +1556,9 @@ pub const OptixGeometryTransformByteAlignment: size_t = 16; pub const OptixTransformByteAlignment: size_t = 64; pub const OptixVersion: size_t = 70300; pub const OptixBuildInputSize: size_t = 1032; +pub const OptixShaderBindingTableSize: size_t = 64; #[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum OptixGeometryFlags { None = 0, DisableAnyHit = 1, diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 15697d18..2c30693a 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -4,7 +4,8 @@ use std::{ffi::c_void, mem::MaybeUninit, ptr}; use cust::context::ContextHandle; -use crate::{error::OptixResult, optix_call, sys}; +use crate::{error::Error, optix_call, sys}; +type Result = std::result::Result; /// A certain property belonging to an OptiX device. #[non_exhaustive] @@ -51,11 +52,12 @@ impl OptixDeviceProperty { } #[derive(Debug)] -pub struct OptixContext { +#[repr(transparent)] +pub struct DeviceContext { pub(crate) raw: sys::OptixDeviceContext, } -impl Drop for OptixContext { +impl Drop for DeviceContext { fn drop(&mut self) { unsafe { sys::optixDeviceContextDestroy(self.raw); @@ -63,11 +65,11 @@ impl Drop for OptixContext { } } -impl OptixContext { +impl DeviceContext { // TODO(RDambrosio016): expose device context options /// Creates a new [`OptixContext`] from a cust CUDA context. - pub fn new(cuda_ctx: &impl ContextHandle) -> OptixResult { + pub fn new(cuda_ctx: &impl ContextHandle) -> Result { let mut raw = MaybeUninit::uninit(); unsafe { optix_call!(optixDeviceContextCreate( @@ -81,7 +83,7 @@ impl OptixContext { } } - pub fn get_property(&self, property: OptixDeviceProperty) -> OptixResult { + pub fn get_property(&self, property: OptixDeviceProperty) -> Result { let raw_prop = property.to_raw(); unsafe { let mut value = 0u32; diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 139a8d5a..1da1c4e0 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -12,7 +12,8 @@ use cust::{ prelude::Stream, }; -use crate::{context::OptixContext, error::OptixResult, optix_call, sys}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; +type Result = std::result::Result; // can't zero initialize, OptixPixelFormat is not zero-initializable. fn null_optix_image() -> sys::OptixImage2D { @@ -129,10 +130,10 @@ impl Drop for Denoiser { impl Denoiser { /// Create a new [`Denoiser`] with a model kind and some options. pub fn new( - ctx: &OptixContext, + ctx: &DeviceContext, kind: DenoiserModelKind, options: DenoiserOptions, - ) -> OptixResult { + ) -> Result { let mut raw = MaybeUninit::uninit(); unsafe { let ctx = ctx.raw; @@ -157,7 +158,7 @@ impl Denoiser { /// /// If tiling is being used, `width` and `height` should not contain the overlap size. Tiling requires /// extra overlap areas which is why there is scratch memory with and without tiling requirements. - pub fn required_gpu_memory(&self, width: u32, height: u32) -> OptixResult { + pub fn required_gpu_memory(&self, width: u32, height: u32) -> Result { let mut sizes = MaybeUninit::uninit(); unsafe { optix_call!(optixDenoiserComputeMemoryResources( @@ -190,7 +191,7 @@ impl Denoiser { mut width: u32, mut height: u32, tiled: bool, - ) -> OptixResult<()> { + ) -> Result<()> { // first, find out how much memory we need to allocate let sizes = self.required_gpu_memory(width, height)?; let original_width = width; @@ -262,7 +263,7 @@ impl Denoiser { input_image: Image, parameters: DenoiserParams, out_buffer: &mut impl GpuBuffer, - ) -> OptixResult<()> { + ) -> Result<()> { let state_lock = self.state.lock().unwrap(); let state = state_lock.as_ref().expect( "State was not initialized before invoking the denoiser, call Denoiser::setup_state first" diff --git a/crates/optix/src/error.rs b/crates/optix/src/error.rs index 80165baf..38d9609c 100644 --- a/crates/optix/src/error.rs +++ b/crates/optix/src/error.rs @@ -131,14 +131,14 @@ impl Display for OptixError { impl std::error::Error for OptixError {} -pub type OptixResult = Result; +// pub type OptixResult = Result; pub trait ToResult { - fn to_result(self) -> OptixResult<()>; + fn to_result(self) -> Result<(), OptixError>; } impl ToResult for sys::OptixResult { - fn to_result(self) -> OptixResult<()> { + fn to_result(self) -> Result<(), OptixError> { use OptixError::*; Err(match self { @@ -183,6 +183,54 @@ impl ToResult for sys::OptixResult { sys::OptixResult::OPTIX_ERROR_CUDA_ERROR => CudaError, sys::OptixResult::OPTIX_ERROR_INTERNAL_ERROR => InternalError, sys::OptixResult::OPTIX_ERROR_UNKNOWN => Unknown, + value @ _ => panic!("Unhandled OptixResult value {:?}", value), }) } } + +#[derive(Debug)] +pub enum Error { + Optix(OptixError), + Cuda(CudaError), + ModuleCreation { source: OptixError, log: String }, + ProgramGroupCreation { source: OptixError, log: String }, + PipelineCreation { source: OptixError, log: String }, +} + +impl From for Error { + fn from(o: OptixError) -> Self { + Self::Optix(o) + } +} + +impl From for Error { + fn from(e: CudaError) -> Self { + Self::Cuda(e) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Optix(e) => Some(e), + Self::Cuda(e) => Some(e), + Self::ModuleCreation { source, .. } => Some(source), + Self::ProgramGroupCreation { source, .. } => Some(source), + Self::PipelineCreation { source, .. } => Some(source), + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Optix(_) => write!(f, "OptiX error"), + Self::Cuda(_) => write!(f, "CUDA error"), + Self::ModuleCreation { log, .. } => write!(f, "Module creation error: {}", log), + Self::ProgramGroupCreation { log, .. } => { + write!(f, "Program group creation error: {}", log) + } + Self::PipelineCreation { log, .. } => write!(f, "Pipeline creation error: {}", log), + } + } +} diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index a2700b0a..e09bb604 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -2,14 +2,18 @@ pub mod context; pub mod denoiser; pub mod error; pub mod module; +pub mod pipeline; +pub mod program_group; +pub mod shader_binding_table; pub mod sys; pub use cust; -use error::{OptixResult, ToResult}; +use error::{Error, ToResult}; +type Result = std::result::Result; /// Initializes the OptiX library. This must be called before using any OptiX function. It may /// be called before or after initializing CUDA. -pub fn init() -> OptixResult<()> { +pub fn init() -> Result<()> { // avoid initializing multiple times because that will try to load the dll every time. if !optix_is_initialized() { init_cold() @@ -20,8 +24,8 @@ pub fn init() -> OptixResult<()> { #[cold] #[inline(never)] -fn init_cold() -> OptixResult<()> { - unsafe { sys::optixInit().to_result() } +fn init_cold() -> Result<()> { + unsafe { Ok(sys::optixInit().to_result()?) } } /// Whether OptiX is initialized. If you are calling raw [`sys`] functions you must make sure @@ -51,3 +55,33 @@ macro_rules! optix_call { } }}; } + +/// Launch the given [Pipeline] on the given [Stream](cu::Stream). +/// +/// # Safety +/// You must ensure that: +/// - Any [ProgramGroup]s reference by the [Pipeline] are still alive +/// - Any [DevicePtr]s contained in `buf_launch_params` point to valid, +/// correctly aligned memory +/// - Any [SbtRecord]s and associated data referenced by the +/// [OptixShaderBindingTable] are alive and valid +pub unsafe fn launch( + pipeline: &crate::pipeline::Pipeline, + stream: &cust::stream::Stream, + buf_launch_params: &mut cust::memory::DBox

, + sbt: &sys::OptixShaderBindingTable, + width: u32, + height: u32, + depth: u32, +) -> Result<()> { + Ok(optix_call!(optixLaunch( + pipeline.inner, + stream.as_inner(), + buf_launch_params.as_device_ptr().as_raw() as u64, + std::mem::size_of::

(), + sbt, + width, + height, + depth, + ))?) +} diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 9e1e5325..5ebb680b 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -1,4 +1,13 @@ -use crate::{error::OptixResult, optix_call, sys}; +use crate::{ + context::DeviceContext, + error::{Error, ToResult}, + optix_call, sys, +}; +type Result = std::result::Result; + +use std::ffi::{CStr, CString}; + +use ustr::Ustr; #[derive(Clone)] #[repr(transparent)] @@ -6,6 +15,7 @@ pub struct Module { pub(crate) raw: sys::OptixModule, } +/// Module compilation optimization level #[repr(u32)] #[derive(Debug, Hash, PartialEq, Copy, Clone)] pub enum CompileOptimizationLevel { @@ -16,6 +26,7 @@ pub enum CompileOptimizationLevel { Level3 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_3, } +/// Module compilation debug level #[repr(u32)] #[derive(Debug, Hash, PartialEq, Copy, Clone)] pub enum CompileDebugLevel { @@ -23,3 +34,249 @@ pub enum CompileDebugLevel { LineInfo = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO, Full = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_FULL, } + +cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + #[repr(C)] + #[derive(Debug, Hash, PartialEq, Copy, Clone)] + pub struct ModuleCompileOptions { + pub max_register_count: i32, + pub opt_level: CompileOptimizationLevel, + pub debug_level: CompileDebugLevel, + } + + impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { + fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { + sys::OptixModuleCompileOptions { + maxRegisterCount: o.max_register_count, + optLevel: o.opt_level as u32, + debugLevel: o.debug_level as u32, + boundValues: std::ptr::null(), + numBoundValues: 0, + } + } + } + } else { + #[repr(C)] + #[derive(Debug, Hash, PartialEq, Copy, Clone)] + pub struct ModuleCompileOptions { + pub max_register_count: i32, + pub opt_level: CompileOptimizationLevel, + pub debug_level: CompileDebugLevel, + } + + impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { + fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { + sys::OptixModuleCompileOptions { + maxRegisterCount: o.max_register_count, + optLevel: o.opt_level as u32, + debugLevel: o.debug_level as u32, + } + } + } + } +} + +bitflags::bitflags! { + pub struct TraversableGraphFlags: u32 { + const ALLOW_ANY = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY; + const ALLOW_SINGLE_GAS = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS; + const ALLOW_SINGLE_LEVEL_INSTANCING = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; + } +} + +bitflags::bitflags! { + pub struct ExceptionFlags: u32 { + const NONE = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_NONE; + const STACK_OVERFLOW = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW; + const TRACE_DEPTH = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_TRACE_DEPTH; + const USER = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_USER; + const DEBUG = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_DEBUG; + } +} + +bitflags::bitflags! { + pub struct PrimitiveTypeFlags: i32 { + const DEFAULT = 0; + const CUSTOM = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM; + const ROUND_QUADRATIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE; + const ROUND_CUBIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE; + const ROUND_LINEAR = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR; + const TRIANGLE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; + } +} + +#[repr(u32)] +pub enum PrimitiveType { + RoundQuadraticBspline = + sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE as u32, + RoundCubicBspline = + sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE as u32, + RoundLinear = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR as u32, + Triangle = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE as u32, +} + +#[derive(Debug, Hash, PartialEq, Clone)] +pub struct PipelineCompileOptions { + uses_motion_blur: bool, + traversable_graph_flags: TraversableGraphFlags, + num_payload_values: i32, + num_attribute_values: i32, + exception_flags: ExceptionFlags, + pipeline_launch_params_variable_name: Ustr, + primitive_type_flags: PrimitiveTypeFlags, +} + +impl PipelineCompileOptions { + pub fn new>(launch_params_variable_name: S) -> PipelineCompileOptions { + PipelineCompileOptions { + uses_motion_blur: false, + traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, + num_payload_values: 0, + num_attribute_values: 0, + exception_flags: ExceptionFlags::NONE, + pipeline_launch_params_variable_name: launch_params_variable_name.into(), + primitive_type_flags: PrimitiveTypeFlags::DEFAULT, + } + } + + pub fn build(&self) -> sys::OptixPipelineCompileOptions { + cfg_if::cfg_if! { + if #[cfg(feature="optix73")] { + sys::OptixPipelineCompileOptions { + usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, + traversableGraphFlags: self.traversable_graph_flags.bits(), + numPayloadValues: self.num_payload_values, + numAttributeValues: self.num_attribute_values, + exceptionFlags: self.exception_flags.bits(), + pipelineLaunchParamsVariableName: self + .pipeline_launch_params_variable_name + .as_char_ptr(), + usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, + reserved: 0, + reserved2: 0, + } + } else { + sys::OptixPipelineCompileOptions { + usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, + traversableGraphFlags: self.traversable_graph_flags.bits(), + numPayloadValues: self.num_payload_values, + numAttributeValues: self.num_attribute_values, + exceptionFlags: self.exception_flags.bits(), + pipelineLaunchParamsVariableName: self + .pipeline_launch_params_variable_name + .as_char_ptr(), + usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, + } + } + } + } + + pub fn uses_motion_blur(mut self, umb: bool) -> Self { + self.uses_motion_blur = umb; + self + } + + pub fn traversable_graph_flags(mut self, tgf: TraversableGraphFlags) -> Self { + self.traversable_graph_flags = tgf; + self + } + + pub fn num_payload_values(mut self, npv: i32) -> Self { + self.num_payload_values = npv; + self + } + + pub fn num_attribute_values(mut self, nav: i32) -> Self { + self.num_attribute_values = nav; + self + } + + pub fn exception_flags(mut self, ef: ExceptionFlags) -> Self { + self.exception_flags = ef; + self + } + + pub fn pipeline_launch_params_variable_name(mut self, name: ustr::Ustr) -> Self { + self.pipeline_launch_params_variable_name = name; + self + } +} + +/// # Creating and destroying `Module`s +impl DeviceContext { + pub fn module_create_from_ptx( + &mut self, + module_compile_options: &ModuleCompileOptions, + pipeline_compile_options: &PipelineCompileOptions, + ptx: &str, + ) -> Result<(Module, String)> { + let cptx = CString::new(ptx).unwrap(); + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let mopt = module_compile_options.into(); + let popt = pipeline_compile_options.build(); + + let mut raw = std::ptr::null_mut(); + let res = unsafe { + optix_call!(optixModuleCreateFromPTX( + self.raw, + &mopt as *const _, + &popt, + cptx.as_ptr(), + cptx.as_bytes().len(), + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut raw, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((Module { raw }, log)), + Err(source) => Err(Error::ModuleCreation { source, log }), + } + } + + pub fn builtin_is_module_get( + &self, + module_compile_options: &ModuleCompileOptions, + pipeline_compile_options: &PipelineCompileOptions, + builtin_is_module_type: PrimitiveType, + uses_motion_blur: bool, + ) -> Result { + let is_options = sys::OptixBuiltinISOptions { + builtinISModuleType: builtin_is_module_type as u32, + usesMotionBlur: if uses_motion_blur { 1 } else { 0 }, + }; + + let mut raw = std::ptr::null_mut(); + + unsafe { + optix_call!(optixBuiltinISModuleGet( + self.raw, + module_compile_options as *const _ as *const _, + pipeline_compile_options as *const _ as *const _, + &is_options as *const _, + &mut raw, + )) + .map(|_| Module { raw }) + .map_err(|e| Error::from(e)) + } + } + + /// Destroy a module created with [DeviceContext::module_create_from_ptx()] + /// # Safety + /// Modules must not be destroyed while they are still used by any program + /// group. + /// A Module must not be destroyed while it is + /// still in use by concurrent API calls in other threads. + pub fn module_destroy(&mut self, module: Module) -> Result<()> { + unsafe { Ok(optix_call!(optixModuleDestroy(module.raw))?) } + } +} diff --git a/crates/optix/src/optix_wrapper.h b/crates/optix/src/optix_wrapper.h index fe7c4469..bd9a4d7a 100644 --- a/crates/optix/src/optix_wrapper.h +++ b/crates/optix/src/optix_wrapper.h @@ -16,6 +16,7 @@ static const size_t OptixTransformByteAlignment = static const size_t OptixVersion = OPTIX_VERSION; static const size_t OptixBuildInputSize = sizeof(OptixBuildInput); +static const size_t OptixShaderBindingTableSize = sizeof(OptixShaderBindingTable); /** *

diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs new file mode 100644 index 00000000..0f191623 --- /dev/null +++ b/crates/optix/src/pipeline.rs @@ -0,0 +1,131 @@ +use crate::{ + context::DeviceContext, + error::Error, + module::{CompileDebugLevel, PipelineCompileOptions}, + optix_call, + program_group::ProgramGroup, + sys, +}; +type Result = std::result::Result; + +use std::ffi::CStr; + +#[repr(transparent)] +pub struct Pipeline { + pub(crate) inner: sys::OptixPipeline, +} + +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub struct PipelineLinkOptions { + pub max_trace_depth: u32, + pub debug_level: CompileDebugLevel, +} + +impl From for sys::OptixPipelineLinkOptions { + fn from(o: PipelineLinkOptions) -> Self { + sys::OptixPipelineLinkOptions { + maxTraceDepth: o.max_trace_depth, + debugLevel: o.debug_level as u32, + } + } +} + +/// # Creating and destroying `Pipeline`s +impl DeviceContext { + pub fn pipeline_create( + &mut self, + pipeline_compile_options: &PipelineCompileOptions, + link_options: PipelineLinkOptions, + program_groups: &[ProgramGroup], + ) -> Result<(Pipeline, String)> { + let popt = pipeline_compile_options.build(); + + let link_options: sys::OptixPipelineLinkOptions = link_options.into(); + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let mut inner: sys::OptixPipeline = std::ptr::null_mut(); + + let res = unsafe { + optix_call!(optixPipelineCreate( + self.raw, + &popt, + &link_options, + program_groups.as_ptr() as *const _, + program_groups.len() as u32, + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut inner, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((Pipeline { inner }, log)), + Err(source) => Err(Error::PipelineCreation { source, log }), + } + } + + /// Destroys the pipeline + /// # Safety + /// Thread safety: A pipeline must not be destroyed while it is still in use + /// by concurrent API calls in other threads. + pub fn pipeline_destroy(&mut self, pipeline: Pipeline) -> Result<()> { + unsafe { Ok(optix_call!(optixPipelineDestroy(pipeline.inner))?) } + } +} + +impl Pipeline { + /// Sets the stack sizes for a pipeline. + /// + /// Users are encouraged to see the programming guide and the + /// implementations of the helper functions to understand how to + /// construct the stack sizes based on their particular needs. + /// If this method is not used, an internal default implementation is used. + /// The default implementation is correct (but not necessarily optimal) as + /// long as the maximum depth of call trees of CC and DC programs is at most + /// 2 and no motion transforms are used. + /// The maxTraversableGraphDepth responds to the maximal number of + /// traversables visited when calling trace. Every acceleration structure + /// and motion transform count as one level of traversal. E.g., for a simple + /// IAS (instance acceleration structure) -> GAS (geometry acceleration + /// structure) traversal graph, the maxTraversableGraphDepth is two. For + /// IAS -> MT (motion transform) -> GAS, the maxTraversableGraphDepth is + /// three. Note that it does not matter whether a IAS or GAS has motion + /// or not, it always counts as one. Launching optix with exceptions + /// turned on (see OPTIX_EXCEPTION_FLAG_TRACE_DEPTH) will throw an + /// exception if the specified maxTraversableGraphDepth is too small. + /// + /// # Arguments + /// * `direct_callable_stack_size_from_traversable` - The direct stack size + /// requirement for direct callables invoked from IS or AH + /// * `direct_callable_stack_size_from_state` - The direct stack size + /// requirement for direct callables invoked from RG, MS, or CH. + /// * `continuation_stack_size` - The continuation stack requirement. + /// * `max_traversable_graph_depth` - The maximum depth of a traversable + /// graph + /// passed to trace + pub fn set_stack_size( + &self, + direct_callable_stack_size_from_traversable: u32, + direct_callable_stack_size_from_state: u32, + continuation_stack_size: u32, + max_traversable_graph_depth: u32, + ) -> Result<()> { + unsafe { + Ok(optix_call!(optixPipelineSetStackSize( + self.inner, + direct_callable_stack_size_from_traversable, + direct_callable_stack_size_from_state, + continuation_stack_size, + max_traversable_graph_depth, + ))?) + } + } +} diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs new file mode 100644 index 00000000..3c7a128a --- /dev/null +++ b/crates/optix/src/program_group.rs @@ -0,0 +1,400 @@ +use crate::{context::DeviceContext, error::Error, module::Module, optix_call, sys}; +type Result = std::result::Result; + +use ustr::Ustr; + +use std::ffi::CStr; + +#[derive(Clone)] +pub struct ProgramGroupModule<'m> { + pub module: &'m Module, + pub entry_function_name: Ustr, +} + +pub enum ProgramGroupDesc<'m> { + Raygen(ProgramGroupModule<'m>), + Miss(ProgramGroupModule<'m>), + Exception(ProgramGroupModule<'m>), + Hitgroup { + ch: Option>, + ah: Option>, + is: Option>, + }, + Callables { + dc: Option>, + cc: Option>, + }, +} + +impl<'m> ProgramGroupDesc<'m> { + pub fn raygen(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) + } + + pub fn miss(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) + } + + pub fn exception(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) + } + + pub fn hitgroup( + ch: Option<(&'m Module, Ustr)>, + ah: Option<(&'m Module, Ustr)>, + is: Option<(&'m Module, Ustr)>, + ) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Hitgroup { + ch: ch.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name, + }), + ah: ah.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name, + }), + is: is.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name, + }), + } + } +} + +#[repr(transparent)] +#[derive(Clone)] +/// Modules can contain more than one program. The program in the module is +/// designated by its entry function name as part of the [ProgramGroupDesc] +/// struct passed to [DeviceContext::program_group_create()] and +/// [DeviceContext::program_group_create_single()], or specified directly in the +/// case of [DeviceContext::program_group_raygen()], +/// [DeviceContext::program_group_miss()] and +/// [DeviceContext::program_group_hitgroup()] +/// +/// Four program groups can contain only a single program; only hitgroups can +/// designate up to three programs for the closest-hit, any-hit, and +/// intersection programs. +/// +/// Programs from modules can be used in any number of [ProgramGroup] objects. +/// The resulting program groups can be used to fill in any number of +/// SBT records. Program groups can also be used across pipelines as long as the +/// compilation options match. +/// +/// A hit group specifies the intersection program used to test whether a ray +/// intersects a primitive, together with the hit shaders to be executed when a +/// ray does intersect the primitive. For built-in primitive types, a built-in +/// intersection program should be obtained from +/// [DeviceContext::builtin_is_module_get()] and used in the hit group. As a +/// special case, the intersection program is not required – and is ignored – +/// for triangle primitives. +/// +/// # Safety +/// The lifetime of a module must extend to the lifetime of any +/// OptixProgramGroup that references that module. +pub struct ProgramGroup { + pub(crate) raw: sys::OptixProgramGroup, +} + +impl ProgramGroup { + /// Use this information to calculate the total required stack sizes for a + /// particular call graph of NVIDIA OptiX programs. + /// + /// To set the stack sizes for a particular pipeline, use + /// [Pipeline::set_stack_size()](crate::Pipeline::set_stack_size()). + pub fn get_stack_size(&self) -> Result { + let mut stack_sizes = StackSizes::default(); + unsafe { + Ok(optix_call!(optixProgramGroupGetStackSize( + self.raw, + &mut stack_sizes as *mut _ as *mut _ + )) + .map(|_| stack_sizes)?) + } + } +} + +impl PartialEq for ProgramGroup { + fn eq(&self, rhs: &ProgramGroup) -> bool { + self.raw == rhs.raw + } +} + +/// # Creating and destroying `ProgramGroup`s +impl DeviceContext { + /// Create a [ProgramGroup] for each of the [ProgramGroupDesc] objects in + /// `desc`. + pub fn program_group_create( + &mut self, + desc: &[ProgramGroupDesc], + ) -> Result<(Vec, String)> { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix73"))] { + let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; + } else { + let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; + } + } + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let pg_desc: Vec = desc.iter().map(|d| d.into()).collect(); + + let mut raws = vec![std::ptr::null_mut(); pg_desc.len()]; + + let res = unsafe { + optix_call!(optixProgramGroupCreate( + self.raw, + pg_desc.as_ptr(), + pg_desc.len() as u32, + &pg_options, + log.as_mut_ptr() as *mut i8, + &mut log_len, + raws.as_mut_ptr(), + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok(( + raws.iter().map(|raw| ProgramGroup { raw: *raw }).collect(), + log, + )), + Err(source) => Err(Error::ProgramGroupCreation { source, log }), + } + } + + /// Create a single [ProgramGroup] specified by `desc`. + pub fn program_group_create_single( + &mut self, + desc: &ProgramGroupDesc, + ) -> Result<(ProgramGroup, String)> { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix73"))] { + let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; + } else { + let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; + } + } + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let pg_desc: sys::OptixProgramGroupDesc = desc.into(); + + let mut raw = std::ptr::null_mut(); + + let res = unsafe { + optix_call!(optixProgramGroupCreate( + self.raw, + &pg_desc, + 1, + &pg_options, + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut raw, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((ProgramGroup { raw }, log)), + Err(source) => Err(Error::ProgramGroupCreation { source, log }), + } + } + + /// Create a raygen [ProgramGroup] from `entry_function_name` in `module`. + pub fn program_group_raygen( + &mut self, + module: &Module, + entry_function_name: Ustr, + ) -> Result { + let desc = ProgramGroupDesc::raygen(module, entry_function_name); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Create a miss [ProgramGroup] from `entry_function_name` in `module`. + pub fn program_group_miss( + &mut self, + module: &Module, + entry_function_name: Ustr, + ) -> Result { + let desc = ProgramGroupDesc::miss(module, entry_function_name); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Create an exception [ProgramGroup] from `entry_function_name` in `module`. + pub fn program_group_exception( + &mut self, + module: &Module, + entry_function_name: Ustr, + ) -> Result { + let desc = ProgramGroupDesc::exception(module, entry_function_name); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Create a hitgroup [ProgramGroup] from any combination of + /// `(module, entry_function_name)` pairs. + pub fn program_group_hitgroup( + &mut self, + closest_hit: Option<(&Module, Ustr)>, + any_hit: Option<(&Module, Ustr)>, + intersection: Option<(&Module, Ustr)>, + ) -> Result { + let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Destroy `program_group` + /// + /// # Safety + /// Thread safety: A program group must not be destroyed while it is still + /// in use by concurrent API calls in other threads. + pub fn program_group_destroy(&mut self, program_group: ProgramGroup) -> Result<()> { + unsafe { Ok(optix_call!(optixProgramGroupDestroy(program_group.raw))?) } + } +} + +impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { + fn from(desc: &ProgramGroupDesc<'m>) -> sys::OptixProgramGroupDesc { + unsafe { + match &desc { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + raygen: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_char_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_char_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_char_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Hitgroup { ch, ah, is } => { + let mut efn_ch_ptr = std::ptr::null(); + let mut efn_ah_ptr = std::ptr::null(); + let mut efn_is_ptr = std::ptr::null(); + + let module_ch = if let Some(pg_ch) = &ch { + efn_ch_ptr = pg_ch.entry_function_name.as_char_ptr(); + pg_ch.module.raw + } else { + std::ptr::null_mut() + }; + + let module_ah = if let Some(pg_ah) = &ah { + efn_ah_ptr = pg_ah.entry_function_name.as_char_ptr(); + pg_ah.module.raw + } else { + std::ptr::null_mut() + }; + + let module_is = if let Some(pg_is) = &is { + efn_is_ptr = pg_is.entry_function_name.as_char_ptr(); + pg_is.module.raw + } else { + std::ptr::null_mut() + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + hitgroup: sys::OptixProgramGroupHitgroup { + moduleCH: module_ch, + entryFunctionNameCH: efn_ch_ptr, + moduleAH: module_ah, + entryFunctionNameAH: efn_ah_ptr, + moduleIS: module_is, + entryFunctionNameIS: efn_is_ptr, + }, + }, + flags: 0, + } + } + ProgramGroupDesc::Callables { dc, cc } => { + let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { + (pg_dc.module.raw, pg_dc.entry_function_name.as_char_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { + (pg_cc.module.raw, pg_cc.entry_function_name.as_char_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + callables: sys::OptixProgramGroupCallables { + moduleDC: module_dc, + entryFunctionNameDC: efn_dc, + moduleCC: module_cc, + entryFunctionNameCC: efn_cc, + }, + }, + flags: 0, + } + } + } + } + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct StackSizes { + pub css_rg: u32, + pub css_mg: u32, + pub css_ch: u32, + pub css_ah: u32, + pub css_is: u32, + pub css_cc: u32, + pub css_dc: u32, +} diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs new file mode 100644 index 00000000..7f98319f --- /dev/null +++ b/crates/optix/src/shader_binding_table.rs @@ -0,0 +1,140 @@ +use crate::{ + context::DeviceContext, + error::{Error, ToResult}, + optix_call, + program_group::ProgramGroup, + sys, +}; + +use cust::memory::{DSlice, DeviceCopy}; +use cust_raw::CUdeviceptr; + +type Result = std::result::Result; + +#[repr(C)] +#[repr(align(16))] +#[derive(Copy, Clone)] +pub struct SbtRecord +where + T: Copy, +{ + header: sys::SbtRecordHeader, + data: T, +} + +// impl Copy for SbtRecord where T: Copy {} + +impl SbtRecord +where + T: Copy, +{ + pub fn pack(data: T, program_group: &ProgramGroup) -> Result> { + let mut rec = SbtRecord { + header: sys::SbtRecordHeader::default(), + data, + }; + + unsafe { + Ok(optix_call!(optixSbtRecordPackHeader( + program_group.raw, + &mut rec as *mut _ as *mut std::os::raw::c_void, + )) + .map(|_| rec)?) + } + } +} + +unsafe impl DeviceCopy for SbtRecord {} + +#[repr(C)] +pub struct ShaderBindingTable { + raygen_record: CUdeviceptr, + exception_record: CUdeviceptr, + miss_record_base: CUdeviceptr, + miss_record_stride_in_bytes: u32, + miss_record_count: u32, + hitgroup_record_base: CUdeviceptr, + hitgroup_record_stride_in_bytes: u32, + hitgroup_record_count: u32, + callables_record_base: CUdeviceptr, + callables_record_stride_in_bytes: u32, + callables_record_count: u32, +} + +impl ShaderBindingTable { + pub fn new(buf_raygen_record: &mut DSlice>) -> Self { + let raygen_record = buf_raygen_record.as_device_ptr().as_raw() as u64; + ShaderBindingTable { + raygen_record, + exception_record: 0, + miss_record_base: 0, + miss_record_stride_in_bytes: 0, + miss_record_count: 0, + hitgroup_record_base: 0, + hitgroup_record_stride_in_bytes: 0, + hitgroup_record_count: 0, + callables_record_base: 0, + callables_record_stride_in_bytes: 0, + callables_record_count: 0, + } + } + + pub fn build(self) -> sys::OptixShaderBindingTable { + unsafe { std::mem::transmute::(self) } + } + + pub fn exception( + mut self, + buf_exception_record: &mut DSlice>, + ) -> Self { + if buf_exception_record.len() != 1 { + panic!("SBT not psased single exception record",); + } + self.exception_record = buf_exception_record.as_device_ptr().as_raw() as u64; + self + } + + pub fn miss(mut self, buf_miss_records: &mut DSlice>) -> Self { + if buf_miss_records.len() == 0 { + panic!("SBT passed empty miss records"); + } + self.miss_record_base = buf_miss_records.as_device_ptr().as_raw() as u64; + self.miss_record_stride_in_bytes = std::mem::size_of::>() as u32; + self.miss_record_count = buf_miss_records.len() as u32; + self + } + + pub fn hitgroup( + mut self, + buf_hitgroup_records: &mut DSlice>, + ) -> Self { + if buf_hitgroup_records.len() == 0 { + panic!("SBT passed empty hitgroup records"); + } + self.hitgroup_record_base = buf_hitgroup_records.as_device_ptr().as_raw() as u64; + self.hitgroup_record_stride_in_bytes = std::mem::size_of::>() as u32; + self.hitgroup_record_count = buf_hitgroup_records.len() as u32; + self + } + + pub fn callables( + mut self, + buf_callables_records: &mut DSlice>, + ) -> Self { + if buf_callables_records.len() == 0 { + panic!("SBT passed empty callables records"); + } + self.callables_record_base = buf_callables_records.as_device_ptr().as_raw() as u64; + self.callables_record_stride_in_bytes = std::mem::size_of::>() as u32; + self.callables_record_count = buf_callables_records.len() as u32; + self + } +} + +// Sanity check that the size of this union we're defining matches the one in +// optix header so we don't get any nasty surprises +fn _size_check() { + unsafe { + std::mem::transmute::(panic!()); + } +} diff --git a/crates/optix/src/sys.rs b/crates/optix/src/sys.rs index dfb5caf3..7243625a 100644 --- a/crates/optix/src/sys.rs +++ b/crates/optix/src/sys.rs @@ -1,3 +1,5 @@ +#![allow(warnings)] + use cust_raw::*; use std::mem::ManuallyDrop; @@ -12,6 +14,7 @@ extern "C" { // The SBT record header is an opaque blob used by optix #[repr(C)] +#[derive(Default, Clone, Copy)] pub struct SbtRecordHeader { header: [u8; OptixSbtRecordHeaderSize as usize], } @@ -22,14 +25,6 @@ impl SbtRecordHeader { } } -impl Default for SbtRecordHeader { - fn default() -> SbtRecordHeader { - SbtRecordHeader { - header: [0u8; OptixSbtRecordHeaderSize as usize], - } - } -} - // Manually define the build input union as the bindgen is pretty nasty #[repr(C)] pub union OptixBuildInputUnion { From 860941373b34f8b292ab6d1d9842b2845f7bb57a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 13:56:55 +1300 Subject: [PATCH 003/100] Add example 03 Generate an animated pattern in the raygen and display it in a window using glfw --- crates/optix/examples/ex03_window/Cargo.toml | 18 + crates/optix/examples/ex03_window/build.rs | 49 ++ .../examples/ex03_window/src/ex03_window.cu | 103 ++++ .../optix/examples/ex03_window/src/gl_util.rs | 582 ++++++++++++++++++ crates/optix/examples/ex03_window/src/main.rs | 86 +++ .../examples/ex03_window/src/renderer.rs | 243 ++++++++ .../optix/examples/ex03_window/src/vector.rs | 301 +++++++++ 7 files changed, 1382 insertions(+) create mode 100644 crates/optix/examples/ex03_window/Cargo.toml create mode 100644 crates/optix/examples/ex03_window/build.rs create mode 100644 crates/optix/examples/ex03_window/src/ex03_window.cu create mode 100644 crates/optix/examples/ex03_window/src/gl_util.rs create mode 100644 crates/optix/examples/ex03_window/src/main.rs create mode 100644 crates/optix/examples/ex03_window/src/renderer.rs create mode 100644 crates/optix/examples/ex03_window/src/vector.rs diff --git a/crates/optix/examples/ex03_window/Cargo.toml b/crates/optix/examples/ex03_window/Cargo.toml new file mode 100644 index 00000000..38373a2f --- /dev/null +++ b/crates/optix/examples/ex03_window/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ex03_window" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +optix = {path = "../../"} +cust = {path = "../../../cust"} +anyhow = "1.0.44" +ustr = "0.8.1" +glfw = "0.42.0" +gl = "0.14.0" +num-traits = "0.2.14" + +[build-dependencies] +find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex03_window/build.rs b/crates/optix/examples/ex03_window/build.rs new file mode 100644 index 00000000..1dadfe69 --- /dev/null +++ b/crates/optix/examples/ex03_window/build.rs @@ -0,0 +1,49 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; + +fn main() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + + cuda_include = cuda_include.join("include"); + + let args = vec![ + format!("-I{}", optix_include.display()), + format!("-I{}/../common/gdt", manifest_dir), + ]; + + compile_to_ptx("src/ex03_window.cu", &args); +} + +fn compile_to_ptx(cu_path: &str, args: &[String]) { + println!("cargo:rerun-if-changed={}", cu_path); + + let full_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(cu_path); + + let mut ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(cu_path); + ptx_path.set_extension("ptx"); + std::fs::create_dir_all(ptx_path.parent().unwrap()).unwrap(); + + let output = std::process::Command::new("nvcc") + .arg("-ptx") + .arg(&full_path) + .arg("-o") + .arg(&ptx_path) + .args(args) + .output() + .expect("failed to fun nvcc"); + + if !output.status.success() { + panic!("{}", unsafe { String::from_utf8_unchecked(output.stderr) }); + } +} diff --git a/crates/optix/examples/ex03_window/src/ex03_window.cu b/crates/optix/examples/ex03_window/src/ex03_window.cu new file mode 100644 index 00000000..b35e319c --- /dev/null +++ b/crates/optix/examples/ex03_window/src/ex03_window.cu @@ -0,0 +1,103 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace osc { + +using namespace gdt; +struct LaunchParams { + float4* color_buffer; + vec2i fb_size; + int frame_id{0}; +}; + +/*! launch parameters in constant memory, filled in by optix upon + optixLaunch (this gets filled in from the buffer we pass to + optixLaunch) */ +extern "C" __constant__ LaunchParams PARAMS; + +//------------------------------------------------------------------------------ +// closest hit and anyhit programs for radiance-type rays. +// +// Note eventually we will have to create one pair of those for each +// ray type and each geometry type we want to render; but this +// simple example doesn't use any actual geometries yet, so we only +// create a single, dummy, set of them (we do have to have at least +// one group of them to set up the SBT) +//------------------------------------------------------------------------------ + +extern "C" __global__ void +__closesthit__radiance() { /*! for this simple example, this will remain empty + */ +} + +extern "C" __global__ void +__anyhit__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// miss program that gets called for any ray that did not have a +// valid intersection +// +// as with the anyhit/closest hit programs, in this example we only +// need to have _some_ dummy function to set up a valid SBT +// ------------------------------------------------------------------------------ + +extern "C" __global__ void +__miss__radiance() { /*! for this simple example, this will remain empty */ +} + + +//------------------------------------------------------------------------------ +// ray gen program - the actual rendering happens in here +//------------------------------------------------------------------------------ +extern "C" __global__ void __raygen__renderFrame() { + if (PARAMS.frame_id == 0 && optixGetLaunchIndex().x == 0 && + optixGetLaunchIndex().y == 0) { + // we could of course also have used optixGetLaunchDims to query + // the launch size, but accessing the PARAMS here + // makes sure they're not getting optimized away (because + // otherwise they'd not get used) + printf("############################################\n"); + printf("Hello world from OptiX 7 raygen program!\n(within a " + "%ix%i-sized launch)\n", + PARAMS.fb_size.x, PARAMS.fb_size.y); + printf("############################################\n"); + } + + // ------------------------------------------------------------------ + // for this example, produce a simple test pattern: + // ------------------------------------------------------------------ + + // compute a test pattern based on pixel ID + const int ix = optixGetLaunchIndex().x; + const int iy = optixGetLaunchIndex().y; + + int frameID = PARAMS.frame_id; + + const float r = float((ix + frameID) % 256) / 255.0f; + const float g = float((iy + frameID) % 256) / 255.0f; + const float b = float((ix + iy + frameID) % 256) / 255.0f; + + // and write to frame buffer ... + const unsigned fb_index = ix + iy * PARAMS.fb_size.x; + PARAMS.color_buffer[fb_index] = make_float4(r, g, b, 1.0f); +} + +} // namespace osc diff --git a/crates/optix/examples/ex03_window/src/gl_util.rs b/crates/optix/examples/ex03_window/src/gl_util.rs new file mode 100644 index 00000000..c8b39caa --- /dev/null +++ b/crates/optix/examples/ex03_window/src/gl_util.rs @@ -0,0 +1,582 @@ +use gl; +use gl::types::{GLchar, GLenum, GLint, GLsizeiptr, GLuint, GLvoid}; +use std::ffi::{CStr, CString}; + +use crate::vector::*; + +pub struct Shader { + id: GLuint, +} + +impl Shader { + pub fn from_source( + source: &CStr, + shader_type: GLenum, + ) -> Result { + let id = unsafe { gl::CreateShader(shader_type) }; + + unsafe { + gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null()); + gl::CompileShader(id); + } + + let mut success: GLint = 1; + unsafe { + gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut success); + } + + if success == 0 { + let mut len: GLint = 0; + unsafe { + gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + let error = create_whitespace_cstring(len as usize); + unsafe { + gl::GetShaderInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut GLchar, + ); + } + Err(error.to_string_lossy().into_owned()) + } else { + Ok(Shader { id }) + } + } + + pub fn vertex_from_source(source: &CStr) -> Result { + Shader::from_source(source, gl::VERTEX_SHADER) + } + + pub fn fragment_from_source(source: &CStr) -> Result { + Shader::from_source(source, gl::FRAGMENT_SHADER) + } + + pub fn id(&self) -> GLuint { + self.id + } +} + +impl Drop for Shader { + fn drop(&mut self) { + unsafe { gl::DeleteShader(self.id) }; + } +} + +pub struct Program { + id: GLuint, +} + +impl Program { + pub fn from_shaders(shaders: &[Shader]) -> Result { + let id = unsafe { gl::CreateProgram() }; + + for shader in shaders { + unsafe { gl::AttachShader(id, shader.id()) }; + } + + unsafe { gl::LinkProgram(id) }; + + let mut success: GLint = 1; + unsafe { + gl::GetProgramiv(id, gl::LINK_STATUS, &mut success); + } + + if success == 0 { + let mut len: GLint = 0; + unsafe { + gl::GetProgramiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + let error = create_whitespace_cstring(len as usize); + unsafe { + gl::GetProgramInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut GLchar, + ); + } + return Err(error.to_string_lossy().into_owned()); + } + + for shader in shaders { + unsafe { gl::DetachShader(id, shader.id()) } + } + + Ok(Program { id }) + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn use_program(&self) { + unsafe { + gl::UseProgram(self.id); + } + } + + pub fn get_location(&self, name: &str) -> Result { + let cname = CString::new(name).unwrap(); + let loc = unsafe { + gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) + }; + + if loc != -1 { + Ok(loc) + } else { + Err("Could not get location".to_owned()) + } + } + + pub fn set_uniform(&self, loc: GLint, v: i32) { + unsafe { + gl::ProgramUniform1i(self.id, loc, v); + } + } +} + +fn create_whitespace_cstring(len: usize) -> CString { + let mut buffer: Vec = Vec::with_capacity(len as usize + 1); + buffer.extend([b' '].iter().cycle().take(len as usize)); + unsafe { CString::from_vec_unchecked(buffer) } +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum BufferType { + ArrayBuffer = gl::ARRAY_BUFFER, +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum BufferUsage { + StaticDraw = gl::STATIC_DRAW, + StreamDraw = gl::STREAM_DRAW, +} + +pub struct Buffer { + id: GLuint, + buffer_type: BufferType, + _phantom: std::marker::PhantomData, +} + +impl Buffer { + pub fn new(buffer_type: BufferType) -> Buffer { + let mut id: GLuint = 0; + unsafe { + gl::GenBuffers(1, &mut id); + } + Buffer { + id, + buffer_type, + _phantom: std::marker::PhantomData, + } + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn buffer_data(&self, data: &[T], usage: BufferUsage) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, self.id); + gl::BufferData( + self.buffer_type as GLuint, + (data.len() * std::mem::size_of::()) as GLsizeiptr, + data.as_ptr() as *const GLvoid, + usage as GLenum, + ); + gl::BindBuffer(self.buffer_type as GLuint, 0); + } + } + + pub fn bind(&self) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, self.id); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, 0); + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.id as *const GLuint); + } + } +} + +pub struct VertexArray { + id: GLuint, +} + +impl VertexArray { + pub fn new() -> VertexArray { + let mut id: GLuint = 0; + unsafe { + gl::GenVertexArrays(1, &mut id); + } + + VertexArray { id } + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn bind(&self) { + unsafe { + gl::BindVertexArray(self.id); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindVertexArray(0); + } + } +} + +impl Drop for VertexArray { + fn drop(&mut self) { + unsafe { + gl::DeleteVertexArrays(1, &self.id as *const GLuint); + } + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x2 { + x: f32, + y: f32, +} + +impl f32x2 { + pub fn new(x: f32, y: f32) -> f32x2 { + f32x2 { x, y } + } + + pub fn num_components() -> usize { + 2 + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x3 { + x: f32, + y: f32, + z: f32, +} + +impl f32x3 { + pub fn new(x: f32, y: f32, z: f32) -> f32x3 { + f32x3 { x, y, z } + } + + pub fn num_components() -> usize { + 3 + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x4 { + x: f32, + y: f32, + z: f32, + w: f32, +} + +impl f32x4 { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> f32x4 { + f32x4 { x, y, z, w } + } + + pub fn zero() -> f32x4 { + f32x4::new(0.0, 0.0, 0.0, 0.0) + } + + pub fn set(&mut self, x: f32, y: f32, z: f32, w: f32) { + self.x = x; + self.y = y; + self.z = z; + self.w = w; + } + + pub fn num_components() -> usize { + 4 + } +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Vertex { + p: f32x3, + st: f32x2, +} +impl Vertex { + pub fn new(p: f32x3, st: f32x2) -> Vertex { + Vertex { p, st } + } + + unsafe fn vertex_attrib_pointer( + num_components: usize, + stride: usize, + location: usize, + offset: usize, + ) { + gl::EnableVertexAttribArray(location as gl::types::GLuint); // location(0) + gl::VertexAttribPointer( + location as gl::types::GLuint, // index of the vertex attribute + num_components as gl::types::GLint, /* number of components per + * vertex attrib */ + gl::FLOAT, + gl::FALSE, // normalized (int-to-float conversion), + stride as gl::types::GLint, /* byte stride between + * successive elements */ + offset as *const gl::types::GLvoid, /* offset of the first + * element */ + ); + } + + pub fn vertex_attrib_pointers() { + let stride = std::mem::size_of::(); + + let location = 0; + let offset = 0; + + // and configure the vertex array + unsafe { + Vertex::vertex_attrib_pointer( + f32x3::num_components(), + stride, + location, + offset, + ); + } + + let location = location + 1; + let offset = offset + std::mem::size_of::(); + + // and configure the st array + unsafe { + Vertex::vertex_attrib_pointer( + f32x2::num_components(), + stride, + location, + offset, + ); + } + } +} + +pub struct FullscreenQuad { + width: u32, + height: u32, + vertex_array: VertexArray, + program: Program, + texture_id: GLuint, + loc_progression: GLint, +} + +impl FullscreenQuad { + pub fn new(width: u32, height: u32) -> Result { + let vert_shader = Shader::vertex_from_source( + CStr::from_bytes_with_nul( + b" + #version 330 core + layout (location = 0) in vec3 _p; + layout (location = 1) in vec2 _st; + out vec2 st; + void main() { + gl_Position = vec4(_p, 1.0); + st = _st; + } + \0", + ) + .unwrap(), + )?; + + let frag_shader = Shader::fragment_from_source( + CStr::from_bytes_with_nul( + b" + #version 330 core + in vec2 st; + out vec4 Color; + + uniform sampler2D smp2d_0; + uniform int progression; + + void main() { + vec4 col = texture(smp2d_0, st); + col.r = pow(col.r / progression, 1/2.2); + col.g = pow(col.g / progression, 1/2.2); + col.b = pow(col.b / progression, 1/2.2); + Color = col; + } + \0", + ) + .unwrap(), + )?; + + let program = Program::from_shaders(&[vert_shader, frag_shader])?; + program.use_program(); + let loc_progression = program.get_location("progression")?; + + let vertices: Vec = vec![ + Vertex::new(f32x3::new(-1.0, -1.0, 0.0), f32x2::new(0.0, 0.0)), + Vertex::new(f32x3::new(1.0, -1.0, 0.0), f32x2::new(1.0, 0.0)), + Vertex::new(f32x3::new(1.0, 1.0, 0.0), f32x2::new(1.0, 1.0)), + Vertex::new(f32x3::new(-1.0, -1.0, 0.0), f32x2::new(0.0, 0.0)), + Vertex::new(f32x3::new(1.0, 1.0, 0.0), f32x2::new(1.0, 1.0)), + Vertex::new(f32x3::new(-1.0, 1.0, 0.0), f32x2::new(0.0, 1.0)), + ]; + let vertex_buffer = Buffer::::new(BufferType::ArrayBuffer); + vertex_buffer.buffer_data(&vertices, BufferUsage::StaticDraw); + + // Generate and bind the VAO + let vertex_array = VertexArray::new(); + + vertex_array.bind(); + // Re-bind the VBO to associate the two. We could just have left it + // bound earlier and let the association happen when we + // configure the VAO but this way at least makes the connection + // between the two seem more explicit, despite the magical + // state machine hiding in OpenGL + vertex_buffer.bind(); + + // Set up the vertex attribute pointers for all locations + Vertex::vertex_attrib_pointers(); + + // now unbind both the vbo and vao to keep everything cleaner + vertex_buffer.unbind(); + vertex_array.unbind(); + + // generate test texture data using the image width rather than the + // framebuffer width + let mut tex_data = Vec::with_capacity((width * height) as usize); + for y in 0..height { + for x in 0..width { + tex_data.push(f32x4::new( + (x as f32) / width as f32, + (y as f32) / height as f32, + 1.0, + 0.0, + )); + } + } + + // generate the texture for the quad + let mut texture_id: gl::types::GLuint = 0; + unsafe { + gl::GenTextures(1, &mut texture_id); + gl::ActiveTexture(gl::TEXTURE0); + gl::Enable(gl::TEXTURE_2D); + gl::BindTexture(gl::TEXTURE_2D, texture_id); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_BORDER as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_BORDER as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::NEAREST as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + gl::NEAREST as gl::types::GLint, + ); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA32F as gl::types::GLint, + width as gl::types::GLint, + height as gl::types::GLint, + 0, + gl::RGBA, + gl::FLOAT, + tex_data.as_ptr() as *const gl::types::GLvoid, + ); + } + + Ok(FullscreenQuad { + width, + height, + vertex_array, + program, + texture_id, + loc_progression, + }) + } + + pub fn draw(&self) { + self.program.use_program(); + self.vertex_array.bind(); + unsafe { + gl::DrawArrays( + gl::TRIANGLES, + 0, // starting index in the enabled array + 6, // number of indices to draw + ) + } + self.vertex_array.unbind(); + } + + pub fn update_texture(&self, data: &[V4f32]) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.texture_id); + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, + 0, + 0, + self.width as GLint, + self.height as GLint, + gl::RGBA, + gl::FLOAT, + data.as_ptr() as *const GLvoid, + ); + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + unsafe { + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA32F as gl::types::GLint, + width as gl::types::GLint, + height as gl::types::GLint, + 0, + gl::RGBA, + gl::FLOAT, + std::ptr::null(), + ); + } + self.width = width; + self.height = height; + } + + pub fn set_progression(&self, progression: i32) { + self.program.set_uniform(self.loc_progression, progression); + } +} diff --git a/crates/optix/examples/ex03_window/src/main.rs b/crates/optix/examples/ex03_window/src/main.rs new file mode 100644 index 00000000..39072e60 --- /dev/null +++ b/crates/optix/examples/ex03_window/src/main.rs @@ -0,0 +1,86 @@ +mod renderer; +use renderer::Renderer; + +mod vector; +pub use vector::*; +mod gl_util; +use gl_util::FullscreenQuad; +use glfw::{Action, Context, Key}; + +fn main() -> Result<(), Box> { + let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap(); + glfw.window_hint(glfw::WindowHint::ContextVersion(4, 1)); + glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true)); + glfw.window_hint(glfw::WindowHint::OpenGlProfile( + glfw::OpenGlProfileHint::Core, + )); + + let mut width = 960u32; + let mut height = 540u32; + + let mut renderer = Renderer::new(width, height)?; + + let (mut window, events) = glfw + .create_window( + width, + height, + "Example 03: in window", + glfw::WindowMode::Windowed, + ) + .expect("failed to create glfw window"); + + window.set_key_polling(true); + window.make_current(); + + // retina displays will return a higher res for the framebuffer + // which we need to use for the viewport + let (fb_width, fb_height) = window.get_framebuffer_size(); + + gl::load_with(|s| glfw.get_proc_address_raw(s) as *const std::os::raw::c_void); + + let mut fsq = FullscreenQuad::new(width, height).unwrap(); + + let mut image_data = vec![v4f32(0.0, 0.0, 0.0, 0.0); (width * height) as usize]; + + unsafe { + gl::Viewport(0, 0, fb_width, fb_height); + }; + + while !window.should_close() { + glfw.poll_events(); + for (_, event) in glfw::flush_messages(&events) { + handle_window_event(&mut window, event); + } + + let (w, h) = window.get_framebuffer_size(); + let w = w as u32; + let h = h as u32; + if w != width || h != height { + fsq.resize(w, h); + renderer.resize(w, h)?; + width = w; + height = h; + image_data.resize((width * height) as usize, v4f32(0.0, 0.0, 0.0, 0.0)); + } + + renderer.render()?; + renderer.download_pixels(&mut image_data)?; + fsq.update_texture(&image_data); + fsq.set_progression(1); + + // draw the quad + fsq.draw(); + + window.swap_buffers(); + } + + renderer.render()?; + Ok(()) +} + +fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) { + match event { + glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => window.set_should_close(true), + _ => {} + } +} diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs new file mode 100644 index 00000000..446c353b --- /dev/null +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -0,0 +1,243 @@ +use anyhow::{Context, Result}; +use cust::context::{Context as CuContext, ContextFlags}; +use cust::device::{Device, DeviceAttribute}; +use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::stream::{Stream, StreamFlags}; +use cust::CudaFlags; +use optix::{ + context::DeviceContext, + module::{ + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + PipelineCompileOptions, TraversableGraphFlags, + }, + pipeline::{Pipeline, PipelineLinkOptions}, + program_group::ProgramGroupDesc, + shader_binding_table::{SbtRecord, ShaderBindingTable}, +}; + +use ustr::ustr; + +use crate::vector::V4f32; + +pub struct Renderer { + ctx: DeviceContext, + cuda_context: CuContext, + stream: Stream, + launch_params: LaunchParams, + buf_launch_params: DBox, + buf_raygen: DBuffer, + buf_hitgroup: DBuffer, + buf_miss: DBuffer, + sbt: optix::sys::OptixShaderBindingTable, + pipeline: Pipeline, + color_buffer: DBuffer, +} + +impl Renderer { + pub fn new(width: u32, height: u32) -> Result> { + init_optix()?; + + // create CUDA and OptiX contexts + let device = Device::get_device(0)?; + let tex_align = device.get_attribute(DeviceAttribute::TextureAlignment)?; + let srf_align = device.get_attribute(DeviceAttribute::SurfaceAlignment)?; + println!("tex align: {}\nsrf align: {}", tex_align, srf_align); + + let cuda_context = + CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let stream = Stream::new(StreamFlags::DEFAULT, None)?; + + let mut ctx = DeviceContext::new(&cuda_context)?; + // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + + // create module + let module_compile_options = ModuleCompileOptions { + max_register_count: 50, + opt_level: CompileOptimizationLevel::Default, + debug_level: CompileDebugLevel::None, + }; + + let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + .uses_motion_blur(false) + .num_attribute_values(2) + .num_payload_values(2) + .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) + .exception_flags(ExceptionFlags::NONE); + + let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex03_window.ptx")); + + let (module, _log) = ctx + .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) + .context("Create module")?; + + // create raygen program + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + + let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + + // create miss program + let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + + let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + + let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( + Some((&module, ustr("__closesthit__radiance"))), + Some((&module, ustr("__anyhit__radiance"))), + None, + ); + + // create hitgroup programs + let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.iter().cloned()); + program_groups.extend(pg_miss.iter().cloned()); + program_groups.extend(pg_hitgroup.iter().cloned()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = ctx.pipeline_create( + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + + // create SBT + let rec_raygen: Vec<_> = pg_raygen + .iter() + .map(|pg| RaygenRecord::pack(0, pg).expect("failed to pack raygen record")) + .collect(); + + let rec_miss: Vec<_> = pg_miss + .iter() + .map(|pg| MissRecord::pack(0, pg).expect("failed to pack miss record")) + .collect(); + + let num_objects = 1; + let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); + + let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + + let sbt = ShaderBindingTable::new(&mut buf_raygen) + .miss(&mut buf_miss) + .hitgroup(&mut buf_hitgroup) + .build(); + + let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; + + let launch_params = LaunchParams { + frame_id: 0, + color_buffer: color_buffer.as_device_ptr(), + fb_size: Point2i { + x: width as i32, + y: height as i32, + }, + }; + + let buf_launch_params = DBox::new(&launch_params)?; + + Ok(Renderer { + ctx, + cuda_context, + stream, + launch_params, + buf_launch_params, + buf_raygen, + buf_hitgroup, + buf_miss, + sbt, + pipeline, + color_buffer, + }) + } + + pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Box> { + self.color_buffer = unsafe { DBuffer::uninitialized((width * height) as usize)? }; + self.launch_params.fb_size.x = width as i32; + self.launch_params.fb_size.y = height as i32; + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + Ok(()) + } + + pub fn render(&mut self) -> Result<(), Box> { + self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.frame_id += 1; + + unsafe { + optix::launch( + &self.pipeline, + &self.stream, + &mut self.buf_launch_params, + &self.sbt, + self.launch_params.fb_size.x as u32, + self.launch_params.fb_size.y as u32, + 1, + )?; + } + + self.stream.synchronize()?; + + Ok(()) + } + + pub fn download_pixels(&self, slice: &mut [V4f32]) -> Result<(), Box> { + self.color_buffer.copy_to(slice)?; + Ok(()) + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Point2i { + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct LaunchParams { + pub color_buffer: DevicePointer, + pub fb_size: Point2i, + pub frame_id: i32, +} + +unsafe impl DeviceCopy for LaunchParams {} + +type RaygenRecord = SbtRecord; +type MissRecord = SbtRecord; + +#[derive(Copy, Clone, Default)] +struct HitgroupSbtData { + object_id: u32, +} +unsafe impl DeviceCopy for HitgroupSbtData {} +type HitgroupRecord = SbtRecord; + +fn init_optix() -> Result<(), Box> { + cust::init(CudaFlags::empty())?; + let device_count = Device::num_devices()?; + if device_count == 0 { + panic!("No CUDA devices found!"); + } + + optix::init()?; + Ok(()) +} diff --git a/crates/optix/examples/ex03_window/src/vector.rs b/crates/optix/examples/ex03_window/src/vector.rs new file mode 100644 index 00000000..589a4134 --- /dev/null +++ b/crates/optix/examples/ex03_window/src/vector.rs @@ -0,0 +1,301 @@ +use core::ops; +pub use num_traits::{One, Zero}; + +pub trait Scalar: num_traits::One + num_traits::Zero {} + +impl Scalar for i8 {} +impl Scalar for i16 {} +impl Scalar for i32 {} +impl Scalar for i64 {} +impl Scalar for f32 {} +impl Scalar for f64 {} + +pub trait Vector { + type Component: Scalar; + + fn dot(&self, v: &Self) -> Self::Component; + + #[inline] + fn length2(&self) -> Self::Component { + self.dot(&self) + } +} + +macro_rules! vec_impl { + ($name:ident: $t:ty, $sc:ident, $align:expr, ($($c:ident),+)) => { + #[repr(C)] + #[derive(Clone, Copy, Default, PartialEq, Debug)] + pub struct $name + { + $( + pub $c: $t, + )+ + } + + impl $name + { + pub fn new($($c: $t),+) -> Self + { + Self { + $( + $c, + )+ + } + } + } + + impl Vector for $name + { + type Component = $t; + + #[inline] + fn dot(&self, v: &Self) -> $t + { + <$t>::zero() $( + + self.$c * v.$c + )+ + } + } + + impl From<$t> for $name + { + fn from(x: $t) -> Self + { + Self { + $( + $c: x, + )+ + } + } + } + + impl ops::Neg for $name + { + type Output = Self; + + fn neg(self) -> Self + { + Self { + $( + $c: -self.$c, + )+ + } + } + } + + impl ops::Add for $name + { + type Output = Self; + + #[inline] + fn add(self, v: Self) -> Self + { + Self { + $( + $c: self.$c + v.$c, + )+ + } + } + } + + impl ops::AddAssign for $name + { + #[inline] + fn add_assign(&mut self, v: Self) + { + $( + self.$c += v.$c; + )+ + } + } + + impl ops::Sub for $name + { + type Output = Self; + + #[inline] + fn sub(self, v: Self) -> Self + { + Self { + $( + $c: self.$c - v.$c, + )+ + } + } + } + + impl ops::SubAssign for $name + { + #[inline] + fn sub_assign(&mut self, v: Self) + { + $( + self.$c -= v.$c; + )+ + } + } + + impl ops::Mul for $name + { + type Output = Self; + + #[inline] + fn mul(self, v: Self) -> Self + { + Self { + $( + $c: self.$c * v.$c, + )+ + } + } + } + + impl ops::MulAssign for $name + { + #[inline] + fn mul_assign(&mut self, v: Self) + { + $( + self.$c *= v.$c; + )+ + } + } + + impl ops::Mul<$t> for $name + { + type Output = Self; + + #[inline] + fn mul(self, v: $t) -> Self + { + Self { + $( + $c: self.$c * v, + )+ + } + } + } + + impl ops::MulAssign<$t> for $name + { + #[inline] + fn mul_assign(&mut self, v: $t) + { + $( + self.$c *= v; + )+ + } + } + + impl ops::Div<$t> for $name + { + type Output = Self; + + #[inline] + fn div(self, v: $t) -> Self + { + Self { + $( + $c: self.$c / v, + )+ + } + } + } + + impl ops::DivAssign<$t> for $name + { + #[inline] + fn div_assign(&mut self, v: $t) + { + $( + self.$c /= v; + )+ + } + } + + impl ops::Mul<$name> for $t + { + type Output = $name; + + #[inline] + fn mul(self, v: $name) -> $name + { + $name { + $( + $c: self * v.$c, + )+ + } + } + } + + impl ops::Div<$name> for $t + { + type Output = $name; + + #[inline] + fn div(self, v: $name) -> $name + { + $name { + $( + $c: self / v.$c, + )+ + } + } + } + + pub fn $sc($($c: $t),+) -> $name + { + $name { + $( + $c, + )+ + } + } + + unsafe impl cust::memory::DeviceCopy for $name { + // fn device_align() -> usize { + // $align + // } + } + }; + +} + +vec_impl!(V2i8: i8, v2i8, 1, (x, y)); +vec_impl!(V2i16: i16, v2i16, 2, (x, y)); +vec_impl!(V2i32: i32, v2i32, 8, (x, y)); +vec_impl!(V2i64: i64, v2i64, 8, (x, y)); +vec_impl!(V3i8: i8, v3i8, 1, (x, y, z)); +vec_impl!(V3i16: i16, v3i16, 2, (x, y, z)); +vec_impl!(V3i32: i32, v3i32, 4, (x, y, z)); +vec_impl!(V3i64: i64, v3i64, 8, (x, y, z)); +vec_impl!(V4i8: i8, v4i8, 1, (x, y, z, w)); +vec_impl!(V4i16: i16, v4i16, 2, (x, y, z, w)); +vec_impl!(V4i32: i32, v4i32, 16, (x, y, z, w)); +vec_impl!(V4i64: i64, v4i64, 8, (x, y, z, w)); + +vec_impl!(V2f32: f32, v2f32, 8, (x, y)); +vec_impl!(V2f64: f64, v2f64, 8, (x, y)); +vec_impl!(V3f32: f32, v3f32, 4, (x, y, z)); +vec_impl!(V3f64: f64, v3f64, 8, (x, y, z)); +vec_impl!(V4f32: f32, v4f32, 16, (x, y, z, w)); +vec_impl!(V4f64: f64, v4f64, 8, (x, y, z, w)); + +vec_impl!(P2f32: f32, p2f32, 8, (x, y)); +vec_impl!(P2f64: f64, p2f64, 8, (x, y)); +vec_impl!(P3f32: f32, p3f32, 4, (x, y, z)); +vec_impl!(P3f64: f64, p3f64, 8, (x, y, z)); +vec_impl!(P4f32: f32, p4f32, 16, (x, y, z, w)); +vec_impl!(P4f64: f64, p4f64, 8, (x, y, z, w)); + +vec_impl!(N2f32: f32, n2f32, 8, (x, y)); +vec_impl!(N2f64: f64, n2f64, 8, (x, y)); +vec_impl!(N3f32: f32, n3f32, 4, (x, y, z)); +vec_impl!(N3f64: f64, n3f64, 8, (x, y, z)); +vec_impl!(N4f32: f32, n4f32, 16, (x, y, z, w)); +vec_impl!(N4f64: f64, n4f64, 8, (x, y, z, w)); + +#[inline] +pub fn dot(a: &T, b: &T) -> T::Component { + a.dot(b) +} From 3856ed5fa62cdea52e723c4a036a74118b2a0cc1 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 14:05:10 +1300 Subject: [PATCH 004/100] add logging callback --- .../examples/ex02_pipeline/src/renderer.rs | 2 +- .../examples/ex03_window/src/renderer.rs | 5 +- crates/optix/src/context.rs | 81 ++++++++++++++++++- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index bff1e1d1..2d38f9a0 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -46,7 +46,7 @@ impl Renderer { let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 446c353b..93ea4f5d 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -39,16 +39,13 @@ impl Renderer { // create CUDA and OptiX contexts let device = Device::get_device(0)?; - let tex_align = device.get_attribute(DeviceAttribute::TextureAlignment)?; - let srf_align = device.get_attribute(DeviceAttribute::SurfaceAlignment)?; - println!("tex align: {}\nsrf align: {}", tex_align, srf_align); let cuda_context = CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 2c30693a..8f36ebf2 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -1,6 +1,11 @@ //! OptiX Device Context handling. -use std::{ffi::c_void, mem::MaybeUninit, ptr}; +use std::os::raw::{c_char, c_uint}; +use std::{ + ffi::{c_void, CStr}, + mem::MaybeUninit, + ptr, +}; use cust::context::ContextHandle; @@ -100,4 +105,78 @@ impl DeviceContext { pub fn as_raw(&self) -> sys::OptixDeviceContext { self.raw } + + /// Sets the current log callback method. + /// + /// The following log levels are defined. + /// * 0 - disable: Setting the callback level will disable all messages. The + /// callback function will not be called in this case. + /// * 1 - fatal: A non-recoverable error. The context and/or OptiX itself + /// might + /// no longer be in a usable state. + /// * 2 - error: A recoverable error, e.g., when passing invalid call + /// parameters. + /// * 3 - warning: Hints that OptiX might not behave exactly as requested by + /// the user or may perform slower than expected. + /// * 4 - print: Status or progress messages. + /// Higher levels might occur. + /// + /// # Safety + /// Note that the callback must live longer than the DeviceContext which + /// it's set for. This is because OptiX will fire messages when the + /// underlying OptixDeviceContext is destroyed. In order to do ensure + /// this we leak the closure `cb`. So don't go setting a new closure + /// every frame. + pub fn set_log_callback(&mut self, cb: F, level: u32) + where + F: FnMut(u32, &str, &str) + 'static, + { + let (closure, trampoline) = unsafe { unpack_closure(cb) }; + let res = unsafe { + sys::optixDeviceContextSetLogCallback(self.raw, Some(trampoline), closure, level) + }; + if res != sys::OptixResult::OPTIX_SUCCESS { + panic!("Failed to set log callback"); + } + } +} + +type LogCallback = extern "C" fn(c_uint, *const c_char, *const c_char, *mut c_void); + +/// Unpack a Rust closure, extracting a `void*` pointer to the data and a +/// trampoline function which can be used to invoke it. +/// +/// # Safety +/// +/// It is the user's responsibility to ensure the closure outlives the returned +/// `void*` pointer. +/// +/// Calling the trampoline function with anything except the `void*` pointer +/// will result in *Undefined Behaviour*. +/// +/// The closure should guarantee that it never panics, seeing as panicking +/// across the FFI barrier is *Undefined Behaviour*. You may find +/// `std::panic::catch_unwind()` useful. +unsafe fn unpack_closure(closure: F) -> (*mut c_void, LogCallback) +where + F: FnMut(u32, &str, &str), +{ + extern "C" fn trampoline( + level: c_uint, + tag: *const c_char, + msg: *const c_char, + data: *mut c_void, + ) where + F: FnMut(u32, &str, &str), + { + let tag = unsafe { CStr::from_ptr(tag).to_string_lossy().into_owned() }; + let msg = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; + let closure: &mut F = unsafe { &mut *(data as *mut F) }; + (*closure)(level, &tag, &msg); + } + + let cb = Box::new(closure); + let cb = Box::leak(cb); + + (cb as *mut F as *mut c_void, trampoline::) } From c4346eff41f14c073395ab9dc7089091106d5c0e Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 14:35:23 +1300 Subject: [PATCH 005/100] remove ustr --- crates/optix/Cargo.toml | 1 - .../optix/examples/ex02_pipeline/Cargo.toml | 1 - .../examples/ex02_pipeline/src/renderer.rs | 10 ++-- crates/optix/examples/ex03_window/Cargo.toml | 1 - .../examples/ex03_window/src/renderer.rs | 18 +++--- crates/optix/src/module.rs | 15 +++-- crates/optix/src/program_group.rs | 58 +++++++++---------- 7 files changed, 47 insertions(+), 57 deletions(-) diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 821101c7..7cdb8e5f 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -14,7 +14,6 @@ cust = { version = "0.1", path = "../cust" } cust_raw = { version = "0.11.2", path = "../cust_raw" } cfg-if = "1.0.0" bitflags = "1.3.2" -ustr = "0.8.1" [build-dependencies] bindgen = "0.59" diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml index 6c589340..189cb991 100644 --- a/crates/optix/examples/ex02_pipeline/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" optix = {path = "../../"} cust = {path = "../../../cust"} anyhow = "1.0.44" -ustr = "0.8.1" [build-dependencies] find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 2d38f9a0..813cd6e9 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -15,8 +15,6 @@ use optix::{ shader_binding_table::{SbtRecord, ShaderBindingTable}, }; -use ustr::ustr; - pub struct Renderer { ctx: DeviceContext, cuda_context: CuContext, @@ -69,18 +67,18 @@ impl Renderer { .context("Create module")?; // create raygen program - let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; // create miss program - let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( - Some((&module, ustr("__closesthit__radiance"))), - Some((&module, ustr("__anyhit__radiance"))), + Some((&module, "__closesthit__radiance")), + Some((&module, "__anyhit__radiance")), None, ); diff --git a/crates/optix/examples/ex03_window/Cargo.toml b/crates/optix/examples/ex03_window/Cargo.toml index 38373a2f..19a7646c 100644 --- a/crates/optix/examples/ex03_window/Cargo.toml +++ b/crates/optix/examples/ex03_window/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" optix = {path = "../../"} cust = {path = "../../../cust"} anyhow = "1.0.44" -ustr = "0.8.1" glfw = "0.42.0" gl = "0.14.0" num-traits = "0.2.14" diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 93ea4f5d..ae6334d1 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -15,22 +15,20 @@ use optix::{ shader_binding_table::{SbtRecord, ShaderBindingTable}, }; -use ustr::ustr; - use crate::vector::V4f32; pub struct Renderer { - ctx: DeviceContext, - cuda_context: CuContext, - stream: Stream, launch_params: LaunchParams, buf_launch_params: DBox, + sbt: optix::sys::OptixShaderBindingTable, buf_raygen: DBuffer, buf_hitgroup: DBuffer, buf_miss: DBuffer, - sbt: optix::sys::OptixShaderBindingTable, pipeline: Pipeline, color_buffer: DBuffer, + ctx: DeviceContext, + stream: Stream, + cuda_context: CuContext, } impl Renderer { @@ -68,18 +66,18 @@ impl Renderer { .context("Create module")?; // create raygen program - let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; // create miss program - let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( - Some((&module, ustr("__closesthit__radiance"))), - Some((&module, ustr("__anyhit__radiance"))), + Some((&module, "__closesthit__radiance")), + Some((&module, "__anyhit__radiance")), None, ); diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 5ebb680b..df74665e 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -7,8 +7,6 @@ type Result = std::result::Result; use std::ffi::{CStr, CString}; -use ustr::Ustr; - #[derive(Clone)] #[repr(transparent)] pub struct Module { @@ -123,19 +121,20 @@ pub struct PipelineCompileOptions { num_payload_values: i32, num_attribute_values: i32, exception_flags: ExceptionFlags, - pipeline_launch_params_variable_name: Ustr, + pipeline_launch_params_variable_name: CString, primitive_type_flags: PrimitiveTypeFlags, } impl PipelineCompileOptions { - pub fn new>(launch_params_variable_name: S) -> PipelineCompileOptions { + pub fn new(launch_params_variable_name: &str) -> PipelineCompileOptions { PipelineCompileOptions { uses_motion_blur: false, traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, num_payload_values: 0, num_attribute_values: 0, exception_flags: ExceptionFlags::NONE, - pipeline_launch_params_variable_name: launch_params_variable_name.into(), + pipeline_launch_params_variable_name: CString::new(launch_params_variable_name) + .expect("Invalid string"), primitive_type_flags: PrimitiveTypeFlags::DEFAULT, } } @@ -151,7 +150,7 @@ impl PipelineCompileOptions { exceptionFlags: self.exception_flags.bits(), pipelineLaunchParamsVariableName: self .pipeline_launch_params_variable_name - .as_char_ptr(), + .as_ptr(), usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, reserved: 0, reserved2: 0, @@ -197,8 +196,8 @@ impl PipelineCompileOptions { self } - pub fn pipeline_launch_params_variable_name(mut self, name: ustr::Ustr) -> Self { - self.pipeline_launch_params_variable_name = name; + pub fn pipeline_launch_params_variable_name(mut self, name: &str) -> Self { + self.pipeline_launch_params_variable_name = CString::new(name).expect("Invalid string"); self } } diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index 3c7a128a..c6e08dc7 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -1,14 +1,12 @@ use crate::{context::DeviceContext, error::Error, module::Module, optix_call, sys}; type Result = std::result::Result; -use ustr::Ustr; - -use std::ffi::CStr; +use std::ffi::{CStr, CString}; #[derive(Clone)] pub struct ProgramGroupModule<'m> { pub module: &'m Module, - pub entry_function_name: Ustr, + pub entry_function_name: CString, } pub enum ProgramGroupDesc<'m> { @@ -27,44 +25,44 @@ pub enum ProgramGroupDesc<'m> { } impl<'m> ProgramGroupDesc<'m> { - pub fn raygen(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + pub fn raygen(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Raygen(ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }) } - pub fn miss(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + pub fn miss(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Miss(ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }) } - pub fn exception(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + pub fn exception(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Exception(ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }) } pub fn hitgroup( - ch: Option<(&'m Module, Ustr)>, - ah: Option<(&'m Module, Ustr)>, - is: Option<(&'m Module, Ustr)>, + ch: Option<(&'m Module, &str)>, + ah: Option<(&'m Module, &str)>, + is: Option<(&'m Module, &str)>, ) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Hitgroup { ch: ch.map(|(module, entry_function_name)| ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }), ah: ah.map(|(module, entry_function_name)| ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }), is: is.map(|(module, entry_function_name)| ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }), } } @@ -224,7 +222,7 @@ impl DeviceContext { pub fn program_group_raygen( &mut self, module: &Module, - entry_function_name: Ustr, + entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::raygen(module, entry_function_name); Ok(self.program_group_create_single(&desc)?.0) @@ -234,7 +232,7 @@ impl DeviceContext { pub fn program_group_miss( &mut self, module: &Module, - entry_function_name: Ustr, + entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::miss(module, entry_function_name); Ok(self.program_group_create_single(&desc)?.0) @@ -244,7 +242,7 @@ impl DeviceContext { pub fn program_group_exception( &mut self, module: &Module, - entry_function_name: Ustr, + entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::exception(module, entry_function_name); Ok(self.program_group_create_single(&desc)?.0) @@ -254,9 +252,9 @@ impl DeviceContext { /// `(module, entry_function_name)` pairs. pub fn program_group_hitgroup( &mut self, - closest_hit: Option<(&Module, Ustr)>, - any_hit: Option<(&Module, Ustr)>, - intersection: Option<(&Module, Ustr)>, + closest_hit: Option<(&Module, &str)>, + any_hit: Option<(&Module, &str)>, + intersection: Option<(&Module, &str)>, ) -> Result { let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); Ok(self.program_group_create_single(&desc)?.0) @@ -284,7 +282,7 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { raygen: sys::OptixProgramGroupSingleModule { module: module.raw, - entryFunctionName: entry_function_name.as_char_ptr(), + entryFunctionName: entry_function_name.as_ptr(), }, }, flags: 0, @@ -297,7 +295,7 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { miss: sys::OptixProgramGroupSingleModule { module: module.raw, - entryFunctionName: entry_function_name.as_char_ptr(), + entryFunctionName: entry_function_name.as_ptr(), }, }, flags: 0, @@ -310,7 +308,7 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { miss: sys::OptixProgramGroupSingleModule { module: module.raw, - entryFunctionName: entry_function_name.as_char_ptr(), + entryFunctionName: entry_function_name.as_ptr(), }, }, flags: 0, @@ -321,21 +319,21 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { let mut efn_is_ptr = std::ptr::null(); let module_ch = if let Some(pg_ch) = &ch { - efn_ch_ptr = pg_ch.entry_function_name.as_char_ptr(); + efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); pg_ch.module.raw } else { std::ptr::null_mut() }; let module_ah = if let Some(pg_ah) = &ah { - efn_ah_ptr = pg_ah.entry_function_name.as_char_ptr(); + efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); pg_ah.module.raw } else { std::ptr::null_mut() }; let module_is = if let Some(pg_is) = &is { - efn_is_ptr = pg_is.entry_function_name.as_char_ptr(); + efn_is_ptr = pg_is.entry_function_name.as_ptr(); pg_is.module.raw } else { std::ptr::null_mut() @@ -358,13 +356,13 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { } ProgramGroupDesc::Callables { dc, cc } => { let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { - (pg_dc.module.raw, pg_dc.entry_function_name.as_char_ptr()) + (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) } else { (std::ptr::null_mut(), std::ptr::null()) }; let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { - (pg_cc.module.raw, pg_cc.entry_function_name.as_char_ptr()) + (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) } else { (std::ptr::null_mut(), std::ptr::null()) }; From b43664875d1a509c0882cf93a13254893d742abb Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 16:00:27 +1300 Subject: [PATCH 006/100] Manually create OptixShaderBindingTable field-by-field instead of transmute --- crates/optix/src/shader_binding_table.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 7f98319f..9ff5af61 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -46,7 +46,6 @@ where unsafe impl DeviceCopy for SbtRecord {} -#[repr(C)] pub struct ShaderBindingTable { raygen_record: CUdeviceptr, exception_record: CUdeviceptr, @@ -80,7 +79,19 @@ impl ShaderBindingTable { } pub fn build(self) -> sys::OptixShaderBindingTable { - unsafe { std::mem::transmute::(self) } + sys::OptixShaderBindingTable { + raygenRecord: self.raygen_record, + exceptionRecord: self.exception_record, + missRecordBase: self.miss_record_base, + missRecordStrideInBytes: self.miss_record_stride_in_bytes, + missRecordCount: self.miss_record_count, + hitgroupRecordBase: self.hitgroup_record_base, + hitgroupRecordStrideInBytes: self.hitgroup_record_stride_in_bytes, + hitgroupRecordCount: self.hitgroup_record_count, + callablesRecordBase: self.callables_record_base, + callablesRecordStrideInBytes: self.callables_record_stride_in_bytes, + callablesRecordCount: self.callables_record_count, + } } pub fn exception( From e21e5a7f457ba8dc9239b83e37444dcf1532f767 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 16:01:23 +1300 Subject: [PATCH 007/100] Switch Module and Pipeline methods to their structs Instead of having them on DeviceContext --- .../examples/ex02_pipeline/src/renderer.rs | 15 ++++++++----- .../examples/ex03_window/src/renderer.rs | 15 ++++++++----- crates/optix/src/lib.rs | 2 +- crates/optix/src/module.rs | 22 ++++++++----------- crates/optix/src/pipeline.rs | 22 +++++++++---------- 5 files changed, 41 insertions(+), 35 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 813cd6e9..07bf1758 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -7,7 +7,7 @@ use cust::CudaFlags; use optix::{ context::DeviceContext, module::{ - CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, @@ -62,9 +62,13 @@ impl Renderer { let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); - let (module, _log) = ctx - .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) - .context("Create module")?; + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); @@ -96,7 +100,8 @@ impl Renderer { debug_level: CompileDebugLevel::LineInfo, }; - let (pipeline, _log) = ctx.pipeline_create( + let (pipeline, _log) = Pipeline::new( + &mut ctx, &pipeline_compile_options, pipeline_link_options, &program_groups, diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index ae6334d1..55213c8b 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -7,7 +7,7 @@ use cust::CudaFlags; use optix::{ context::DeviceContext, module::{ - CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, @@ -61,9 +61,13 @@ impl Renderer { let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex03_window.ptx")); - let (module, _log) = ctx - .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) - .context("Create module")?; + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); @@ -95,7 +99,8 @@ impl Renderer { debug_level: CompileDebugLevel::LineInfo, }; - let (pipeline, _log) = ctx.pipeline_create( + let (pipeline, _log) = Pipeline::new( + &mut ctx, &pipeline_compile_options, pipeline_link_options, &program_groups, diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index e09bb604..9a7d7563 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -75,7 +75,7 @@ pub unsafe fn launch( depth: u32, ) -> Result<()> { Ok(optix_call!(optixLaunch( - pipeline.inner, + pipeline.raw, stream.as_inner(), buf_launch_params.as_device_ptr().as_raw() as u64, std::mem::size_of::

(), diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index df74665e..1636f868 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -1,8 +1,4 @@ -use crate::{ - context::DeviceContext, - error::{Error, ToResult}, - optix_call, sys, -}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; type Result = std::result::Result; use std::ffi::{CStr, CString}; @@ -203,9 +199,9 @@ impl PipelineCompileOptions { } /// # Creating and destroying `Module`s -impl DeviceContext { - pub fn module_create_from_ptx( - &mut self, +impl Module { + pub fn new( + ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, pipeline_compile_options: &PipelineCompileOptions, ptx: &str, @@ -220,7 +216,7 @@ impl DeviceContext { let mut raw = std::ptr::null_mut(); let res = unsafe { optix_call!(optixModuleCreateFromPTX( - self.raw, + ctx.raw, &mopt as *const _, &popt, cptx.as_ptr(), @@ -243,7 +239,7 @@ impl DeviceContext { } pub fn builtin_is_module_get( - &self, + ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, pipeline_compile_options: &PipelineCompileOptions, builtin_is_module_type: PrimitiveType, @@ -258,7 +254,7 @@ impl DeviceContext { unsafe { optix_call!(optixBuiltinISModuleGet( - self.raw, + ctx.raw, module_compile_options as *const _ as *const _, pipeline_compile_options as *const _ as *const _, &is_options as *const _, @@ -275,7 +271,7 @@ impl DeviceContext { /// group. /// A Module must not be destroyed while it is /// still in use by concurrent API calls in other threads. - pub fn module_destroy(&mut self, module: Module) -> Result<()> { - unsafe { Ok(optix_call!(optixModuleDestroy(module.raw))?) } + pub fn module_destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixModuleDestroy(self.raw))?) } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 0f191623..11549c37 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -12,7 +12,7 @@ use std::ffi::CStr; #[repr(transparent)] pub struct Pipeline { - pub(crate) inner: sys::OptixPipeline, + pub(crate) raw: sys::OptixPipeline, } #[repr(C)] @@ -32,9 +32,9 @@ impl From for sys::OptixPipelineLinkOptions { } /// # Creating and destroying `Pipeline`s -impl DeviceContext { - pub fn pipeline_create( - &mut self, +impl Pipeline { + pub fn new( + ctx: &mut DeviceContext, pipeline_compile_options: &PipelineCompileOptions, link_options: PipelineLinkOptions, program_groups: &[ProgramGroup], @@ -46,18 +46,18 @@ impl DeviceContext { let mut log = [0u8; 4096]; let mut log_len = log.len(); - let mut inner: sys::OptixPipeline = std::ptr::null_mut(); + let mut raw: sys::OptixPipeline = std::ptr::null_mut(); let res = unsafe { optix_call!(optixPipelineCreate( - self.raw, + ctx.raw, &popt, &link_options, program_groups.as_ptr() as *const _, program_groups.len() as u32, log.as_mut_ptr() as *mut i8, &mut log_len, - &mut inner, + &mut raw, )) }; @@ -67,7 +67,7 @@ impl DeviceContext { .into_owned(); match res { - Ok(()) => Ok((Pipeline { inner }, log)), + Ok(()) => Ok((Pipeline { raw }, log)), Err(source) => Err(Error::PipelineCreation { source, log }), } } @@ -76,8 +76,8 @@ impl DeviceContext { /// # Safety /// Thread safety: A pipeline must not be destroyed while it is still in use /// by concurrent API calls in other threads. - pub fn pipeline_destroy(&mut self, pipeline: Pipeline) -> Result<()> { - unsafe { Ok(optix_call!(optixPipelineDestroy(pipeline.inner))?) } + pub fn destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixPipelineDestroy(self.raw))?) } } } @@ -120,7 +120,7 @@ impl Pipeline { ) -> Result<()> { unsafe { Ok(optix_call!(optixPipelineSetStackSize( - self.inner, + self.raw, direct_callable_stack_size_from_traversable, direct_callable_stack_size_from_state, continuation_stack_size, From 73ad982685f09d95d2b0e01d6c2b7ec3afdeb686 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 16:01:23 +1300 Subject: [PATCH 008/100] Switch Module, Pipeline, ProgramGroup methods to their structs Instead of having them on DeviceContext --- .../examples/ex02_pipeline/src/renderer.rs | 23 +- .../examples/ex03_window/src/renderer.rs | 25 +- crates/optix/src/lib.rs | 2 +- crates/optix/src/module.rs | 22 +- crates/optix/src/pipeline.rs | 22 +- crates/optix/src/program_group.rs | 234 +++++++++--------- 6 files changed, 166 insertions(+), 162 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 813cd6e9..e075108d 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -7,11 +7,11 @@ use cust::CudaFlags; use optix::{ context::DeviceContext, module::{ - CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, - program_group::ProgramGroupDesc, + program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; @@ -62,19 +62,23 @@ impl Renderer { let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); - let (module, _log) = ctx - .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) - .context("Create module")?; + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); - let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + let (pg_raygen, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_raygen])?; // create miss program let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); - let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + let (pg_miss, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( Some((&module, "__closesthit__radiance")), @@ -83,7 +87,7 @@ impl Renderer { ); // create hitgroup programs - let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; // create pipeline let mut program_groups = Vec::new(); @@ -96,7 +100,8 @@ impl Renderer { debug_level: CompileDebugLevel::LineInfo, }; - let (pipeline, _log) = ctx.pipeline_create( + let (pipeline, _log) = Pipeline::new( + &mut ctx, &pipeline_compile_options, pipeline_link_options, &program_groups, diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index ae6334d1..f62bc795 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -1,17 +1,17 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; -use cust::device::{Device, DeviceAttribute}; +use cust::device::Device; use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; use optix::{ context::DeviceContext, module::{ - CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, - program_group::ProgramGroupDesc, + program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; @@ -61,19 +61,23 @@ impl Renderer { let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex03_window.ptx")); - let (module, _log) = ctx - .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) - .context("Create module")?; + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); - let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + let (pg_raygen, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_raygen])?; // create miss program let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); - let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + let (pg_miss, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( Some((&module, "__closesthit__radiance")), @@ -82,7 +86,7 @@ impl Renderer { ); // create hitgroup programs - let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; // create pipeline let mut program_groups = Vec::new(); @@ -95,7 +99,8 @@ impl Renderer { debug_level: CompileDebugLevel::LineInfo, }; - let (pipeline, _log) = ctx.pipeline_create( + let (pipeline, _log) = Pipeline::new( + &mut ctx, &pipeline_compile_options, pipeline_link_options, &program_groups, diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index e09bb604..9a7d7563 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -75,7 +75,7 @@ pub unsafe fn launch( depth: u32, ) -> Result<()> { Ok(optix_call!(optixLaunch( - pipeline.inner, + pipeline.raw, stream.as_inner(), buf_launch_params.as_device_ptr().as_raw() as u64, std::mem::size_of::

(), diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index df74665e..1636f868 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -1,8 +1,4 @@ -use crate::{ - context::DeviceContext, - error::{Error, ToResult}, - optix_call, sys, -}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; type Result = std::result::Result; use std::ffi::{CStr, CString}; @@ -203,9 +199,9 @@ impl PipelineCompileOptions { } /// # Creating and destroying `Module`s -impl DeviceContext { - pub fn module_create_from_ptx( - &mut self, +impl Module { + pub fn new( + ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, pipeline_compile_options: &PipelineCompileOptions, ptx: &str, @@ -220,7 +216,7 @@ impl DeviceContext { let mut raw = std::ptr::null_mut(); let res = unsafe { optix_call!(optixModuleCreateFromPTX( - self.raw, + ctx.raw, &mopt as *const _, &popt, cptx.as_ptr(), @@ -243,7 +239,7 @@ impl DeviceContext { } pub fn builtin_is_module_get( - &self, + ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, pipeline_compile_options: &PipelineCompileOptions, builtin_is_module_type: PrimitiveType, @@ -258,7 +254,7 @@ impl DeviceContext { unsafe { optix_call!(optixBuiltinISModuleGet( - self.raw, + ctx.raw, module_compile_options as *const _ as *const _, pipeline_compile_options as *const _ as *const _, &is_options as *const _, @@ -275,7 +271,7 @@ impl DeviceContext { /// group. /// A Module must not be destroyed while it is /// still in use by concurrent API calls in other threads. - pub fn module_destroy(&mut self, module: Module) -> Result<()> { - unsafe { Ok(optix_call!(optixModuleDestroy(module.raw))?) } + pub fn module_destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixModuleDestroy(self.raw))?) } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 0f191623..11549c37 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -12,7 +12,7 @@ use std::ffi::CStr; #[repr(transparent)] pub struct Pipeline { - pub(crate) inner: sys::OptixPipeline, + pub(crate) raw: sys::OptixPipeline, } #[repr(C)] @@ -32,9 +32,9 @@ impl From for sys::OptixPipelineLinkOptions { } /// # Creating and destroying `Pipeline`s -impl DeviceContext { - pub fn pipeline_create( - &mut self, +impl Pipeline { + pub fn new( + ctx: &mut DeviceContext, pipeline_compile_options: &PipelineCompileOptions, link_options: PipelineLinkOptions, program_groups: &[ProgramGroup], @@ -46,18 +46,18 @@ impl DeviceContext { let mut log = [0u8; 4096]; let mut log_len = log.len(); - let mut inner: sys::OptixPipeline = std::ptr::null_mut(); + let mut raw: sys::OptixPipeline = std::ptr::null_mut(); let res = unsafe { optix_call!(optixPipelineCreate( - self.raw, + ctx.raw, &popt, &link_options, program_groups.as_ptr() as *const _, program_groups.len() as u32, log.as_mut_ptr() as *mut i8, &mut log_len, - &mut inner, + &mut raw, )) }; @@ -67,7 +67,7 @@ impl DeviceContext { .into_owned(); match res { - Ok(()) => Ok((Pipeline { inner }, log)), + Ok(()) => Ok((Pipeline { raw }, log)), Err(source) => Err(Error::PipelineCreation { source, log }), } } @@ -76,8 +76,8 @@ impl DeviceContext { /// # Safety /// Thread safety: A pipeline must not be destroyed while it is still in use /// by concurrent API calls in other threads. - pub fn pipeline_destroy(&mut self, pipeline: Pipeline) -> Result<()> { - unsafe { Ok(optix_call!(optixPipelineDestroy(pipeline.inner))?) } + pub fn destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixPipelineDestroy(self.raw))?) } } } @@ -120,7 +120,7 @@ impl Pipeline { ) -> Result<()> { unsafe { Ok(optix_call!(optixPipelineSetStackSize( - self.inner, + self.raw, direct_callable_stack_size_from_traversable, direct_callable_stack_size_from_state, continuation_stack_size, diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index c6e08dc7..da6ad792 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -127,11 +127,11 @@ impl PartialEq for ProgramGroup { } /// # Creating and destroying `ProgramGroup`s -impl DeviceContext { +impl ProgramGroup { /// Create a [ProgramGroup] for each of the [ProgramGroupDesc] objects in /// `desc`. - pub fn program_group_create( - &mut self, + pub fn new( + ctx: &mut DeviceContext, desc: &[ProgramGroupDesc], ) -> Result<(Vec, String)> { cfg_if::cfg_if! { @@ -151,7 +151,7 @@ impl DeviceContext { let res = unsafe { optix_call!(optixProgramGroupCreate( - self.raw, + ctx.raw, pg_desc.as_ptr(), pg_desc.len() as u32, &pg_options, @@ -176,8 +176,8 @@ impl DeviceContext { } /// Create a single [ProgramGroup] specified by `desc`. - pub fn program_group_create_single( - &mut self, + pub fn new_single( + ctx: &mut DeviceContext, desc: &ProgramGroupDesc, ) -> Result<(ProgramGroup, String)> { cfg_if::cfg_if! { @@ -197,7 +197,7 @@ impl DeviceContext { let res = unsafe { optix_call!(optixProgramGroupCreate( - self.raw, + ctx.raw, &pg_desc, 1, &pg_options, @@ -219,45 +219,45 @@ impl DeviceContext { } /// Create a raygen [ProgramGroup] from `entry_function_name` in `module`. - pub fn program_group_raygen( - &mut self, + pub fn raygen( + ctx: &mut DeviceContext, module: &Module, entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::raygen(module, entry_function_name); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Create a miss [ProgramGroup] from `entry_function_name` in `module`. - pub fn program_group_miss( - &mut self, + pub fn miss( + ctx: &mut DeviceContext, module: &Module, entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::miss(module, entry_function_name); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Create an exception [ProgramGroup] from `entry_function_name` in `module`. - pub fn program_group_exception( - &mut self, + pub fn exception( + ctx: &mut DeviceContext, module: &Module, entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::exception(module, entry_function_name); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Create a hitgroup [ProgramGroup] from any combination of /// `(module, entry_function_name)` pairs. - pub fn program_group_hitgroup( - &mut self, + pub fn hitgroup( + ctx: &mut DeviceContext, closest_hit: Option<(&Module, &str)>, any_hit: Option<(&Module, &str)>, intersection: Option<(&Module, &str)>, ) -> Result { let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Destroy `program_group` @@ -265,120 +265,118 @@ impl DeviceContext { /// # Safety /// Thread safety: A program group must not be destroyed while it is still /// in use by concurrent API calls in other threads. - pub fn program_group_destroy(&mut self, program_group: ProgramGroup) -> Result<()> { - unsafe { Ok(optix_call!(optixProgramGroupDestroy(program_group.raw))?) } + pub fn destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixProgramGroupDestroy(self.raw))?) } } } impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { fn from(desc: &ProgramGroupDesc<'m>) -> sys::OptixProgramGroupDesc { - unsafe { - match &desc { - ProgramGroupDesc::Raygen(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - raygen: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, + match &desc { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + raygen: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), }, - flags: 0, }, - ProgramGroupDesc::Miss(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - miss: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, + flags: 0, + }, + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), }, - flags: 0, }, - ProgramGroupDesc::Exception(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + flags: 0, + }, + ProgramGroupDesc::Hitgroup { ch, ah, is } => { + let mut efn_ch_ptr = std::ptr::null(); + let mut efn_ah_ptr = std::ptr::null(); + let mut efn_is_ptr = std::ptr::null(); + + let module_ch = if let Some(pg_ch) = &ch { + efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); + pg_ch.module.raw + } else { + std::ptr::null_mut() + }; + + let module_ah = if let Some(pg_ah) = &ah { + efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); + pg_ah.module.raw + } else { + std::ptr::null_mut() + }; + + let module_is = if let Some(pg_is) = &is { + efn_is_ptr = pg_is.entry_function_name.as_ptr(); + pg_is.module.raw + } else { + std::ptr::null_mut() + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - miss: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), + hitgroup: sys::OptixProgramGroupHitgroup { + moduleCH: module_ch, + entryFunctionNameCH: efn_ch_ptr, + moduleAH: module_ah, + entryFunctionNameAH: efn_ah_ptr, + moduleIS: module_is, + entryFunctionNameIS: efn_is_ptr, }, }, flags: 0, - }, - ProgramGroupDesc::Hitgroup { ch, ah, is } => { - let mut efn_ch_ptr = std::ptr::null(); - let mut efn_ah_ptr = std::ptr::null(); - let mut efn_is_ptr = std::ptr::null(); - - let module_ch = if let Some(pg_ch) = &ch { - efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); - pg_ch.module.raw - } else { - std::ptr::null_mut() - }; - - let module_ah = if let Some(pg_ah) = &ah { - efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); - pg_ah.module.raw - } else { - std::ptr::null_mut() - }; - - let module_is = if let Some(pg_is) = &is { - efn_is_ptr = pg_is.entry_function_name.as_ptr(); - pg_is.module.raw - } else { - std::ptr::null_mut() - }; - - sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - hitgroup: sys::OptixProgramGroupHitgroup { - moduleCH: module_ch, - entryFunctionNameCH: efn_ch_ptr, - moduleAH: module_ah, - entryFunctionNameAH: efn_ah_ptr, - moduleIS: module_is, - entryFunctionNameIS: efn_is_ptr, - }, - }, - flags: 0, - } } - ProgramGroupDesc::Callables { dc, cc } => { - let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { - (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) - } else { - (std::ptr::null_mut(), std::ptr::null()) - }; - - let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { - (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) - } else { - (std::ptr::null_mut(), std::ptr::null()) - }; - - sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - callables: sys::OptixProgramGroupCallables { - moduleDC: module_dc, - entryFunctionNameDC: efn_dc, - moduleCC: module_cc, - entryFunctionNameCC: efn_cc, - }, + } + ProgramGroupDesc::Callables { dc, cc } => { + let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { + (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { + (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + callables: sys::OptixProgramGroupCallables { + moduleDC: module_dc, + entryFunctionNameDC: efn_dc, + moduleCC: module_cc, + entryFunctionNameCC: efn_cc, }, - flags: 0, - } + }, + flags: 0, } } } From 0e242887806207d5638701132d1f9faeaaba006f Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Sun, 31 Oct 2021 12:36:33 -0400 Subject: [PATCH 009/100] Refactor: remove dead imports --- crates/optix/src/shader_binding_table.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 9ff5af61..3205620b 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -1,10 +1,4 @@ -use crate::{ - context::DeviceContext, - error::{Error, ToResult}, - optix_call, - program_group::ProgramGroup, - sys, -}; +use crate::{error::Error, optix_call, program_group::ProgramGroup, sys}; use cust::memory::{DSlice, DeviceCopy}; use cust_raw::CUdeviceptr; From f76b1d9bb10cc651f69039adb0850e1d546fd584 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:23:30 +1300 Subject: [PATCH 010/100] derive DeviceCopy --- crates/optix/examples/ex02_pipeline/src/renderer.rs | 10 ++++------ crates/optix/examples/ex03_window/src/renderer.rs | 11 ++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index e075108d..c01d4ed5 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -4,6 +4,7 @@ use cust::device::{Device, DeviceAttribute}; use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; +use cust::DeviceCopy; use optix::{ context::DeviceContext, module::{ @@ -205,30 +206,27 @@ impl Renderer { } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct Point2i { pub x: i32, pub y: i32, } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct LaunchParams { pub frame_id: i32, pub color_buffer: DevicePointer, pub fb_size: Point2i, } -unsafe impl DeviceCopy for LaunchParams {} - type RaygenRecord = SbtRecord; type MissRecord = SbtRecord; -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, DeviceCopy)] struct HitgroupSbtData { object_id: u32, } -unsafe impl DeviceCopy for HitgroupSbtData {} type HitgroupRecord = SbtRecord; fn init_optix() -> Result<(), Box> { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index f62bc795..b5e91b6f 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -3,7 +3,7 @@ use cust::context::{Context as CuContext, ContextFlags}; use cust::device::Device; use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; -use cust::CudaFlags; +use cust::{CudaFlags, DeviceCopy}; use optix::{ context::DeviceContext, module::{ @@ -205,30 +205,27 @@ impl Renderer { } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct Point2i { pub x: i32, pub y: i32, } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct LaunchParams { pub color_buffer: DevicePointer, pub fb_size: Point2i, pub frame_id: i32, } -unsafe impl DeviceCopy for LaunchParams {} - type RaygenRecord = SbtRecord; type MissRecord = SbtRecord; -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, DeviceCopy)] struct HitgroupSbtData { object_id: u32, } -unsafe impl DeviceCopy for HitgroupSbtData {} type HitgroupRecord = SbtRecord; fn init_optix() -> Result<(), Box> { From deea117e5aec7f88e63b3c1f14af76ec9e702f4b Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:23:39 +1300 Subject: [PATCH 011/100] typo --- crates/optix/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 9a7d7563..3eb209de 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -60,7 +60,7 @@ macro_rules! optix_call { /// /// # Safety /// You must ensure that: -/// - Any [ProgramGroup]s reference by the [Pipeline] are still alive +/// - Any [ProgramGroup]s referenced by the [Pipeline] are still alive /// - Any [DevicePtr]s contained in `buf_launch_params` point to valid, /// correctly aligned memory /// - Any [SbtRecord]s and associated data referenced by the From f945be34c6b1bf840ba47caf94d40e860ccffa28 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:23:56 +1300 Subject: [PATCH 012/100] Better error message --- crates/optix/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 1636f868..af726aac 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -130,7 +130,7 @@ impl PipelineCompileOptions { num_attribute_values: 0, exception_flags: ExceptionFlags::NONE, pipeline_launch_params_variable_name: CString::new(launch_params_variable_name) - .expect("Invalid string"), + .expect("launch_params_variable_name contains a nul byte"), primitive_type_flags: PrimitiveTypeFlags::DEFAULT, } } From 6b0011f8a35f6ff134a89703f0a405d388145242 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:24:25 +1300 Subject: [PATCH 013/100] Move destroy to Drop impl --- crates/optix/src/module.rs | 12 ++++-------- crates/optix/src/pipeline.rs | 10 ++++------ crates/optix/src/program_group.rs | 18 +++++++++--------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index af726aac..2c279174 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -264,14 +264,10 @@ impl Module { .map_err(|e| Error::from(e)) } } +} - /// Destroy a module created with [DeviceContext::module_create_from_ptx()] - /// # Safety - /// Modules must not be destroyed while they are still used by any program - /// group. - /// A Module must not be destroyed while it is - /// still in use by concurrent API calls in other threads. - pub fn module_destroy(&mut self) -> Result<()> { - unsafe { Ok(optix_call!(optixModuleDestroy(self.raw))?) } +impl Drop for Module { + fn drop(&mut self) { + unsafe { optix_call!(optixModuleDestroy(self.raw)).expect("optixModuleDestroy failed") } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 11549c37..0b14dcbf 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -71,13 +71,11 @@ impl Pipeline { Err(source) => Err(Error::PipelineCreation { source, log }), } } +} - /// Destroys the pipeline - /// # Safety - /// Thread safety: A pipeline must not be destroyed while it is still in use - /// by concurrent API calls in other threads. - pub fn destroy(&mut self) -> Result<()> { - unsafe { Ok(optix_call!(optixPipelineDestroy(self.raw))?) } +impl Drop for Pipeline { + fn drop(&mut self) { + unsafe { optix_call!(optixPipelineDestroy(self.raw)).expect("optixPipelineDestroy failed") } } } diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index da6ad792..54bb6a73 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -68,8 +68,6 @@ impl<'m> ProgramGroupDesc<'m> { } } -#[repr(transparent)] -#[derive(Clone)] /// Modules can contain more than one program. The program in the module is /// designated by its entry function name as part of the [ProgramGroupDesc] /// struct passed to [DeviceContext::program_group_create()] and @@ -98,6 +96,8 @@ impl<'m> ProgramGroupDesc<'m> { /// # Safety /// The lifetime of a module must extend to the lifetime of any /// OptixProgramGroup that references that module. +#[repr(transparent)] +#[derive(Clone)] pub struct ProgramGroup { pub(crate) raw: sys::OptixProgramGroup, } @@ -259,14 +259,14 @@ impl ProgramGroup { let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); Ok(ProgramGroup::new_single(ctx, &desc)?.0) } +} - /// Destroy `program_group` - /// - /// # Safety - /// Thread safety: A program group must not be destroyed while it is still - /// in use by concurrent API calls in other threads. - pub fn destroy(&mut self) -> Result<()> { - unsafe { Ok(optix_call!(optixProgramGroupDestroy(self.raw))?) } +impl Drop for ProgramGroup { + fn drop(&mut self) { + unsafe { + optix_call!(optixProgramGroupDestroy(self.raw)) + .expect("optixProgramGroupDestroy failed") + } } } From 7c9c686f941e43b28a0968de30e568ec26281ef0 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:24:31 +1300 Subject: [PATCH 014/100] typo --- crates/optix/src/shader_binding_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 9ff5af61..a14d47a5 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -99,7 +99,7 @@ impl ShaderBindingTable { buf_exception_record: &mut DSlice>, ) -> Self { if buf_exception_record.len() != 1 { - panic!("SBT not psased single exception record",); + panic!("SBT not passed single exception record",); } self.exception_record = buf_exception_record.as_device_ptr().as_raw() as u64; self From 51cbc041b71531d9cf5ea237c0c7a9dfc858b99f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:24:53 +1300 Subject: [PATCH 015/100] rename OptixContext to DeviceContext --- examples/optix/denoiser/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/optix/denoiser/src/main.rs b/examples/optix/denoiser/src/main.rs index e457a72c..c01f8f02 100644 --- a/examples/optix/denoiser/src/main.rs +++ b/examples/optix/denoiser/src/main.rs @@ -3,7 +3,7 @@ use cust::prelude::{Stream, StreamFlags}; use cust::util::SliceExt; use cust::vek::{Clamp, Vec3}; use image::io::Reader; -use optix::context::OptixContext; +use optix::context::DeviceContext; use optix::denoiser::{Denoiser, DenoiserModelKind, DenoiserParams, Image, ImageFormat}; use std::error::Error; use std::path::PathBuf; @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { // set up CUDA and OptiX then make the needed structs/contexts. let cuda_ctx = cust::quick_init()?; optix::init()?; - let optix_ctx = OptixContext::new(&cuda_ctx)?; + let optix_ctx = DeviceContext::new(&cuda_ctx)?; let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?; From 234aa938c1a07784ad65fe6877459474e5a424c4 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:38:25 +1300 Subject: [PATCH 016/100] Make launch params variable name optional --- .../examples/ex02_pipeline/src/renderer.rs | 3 +- .../examples/ex03_window/src/renderer.rs | 3 +- crates/optix/src/module.rs | 29 ++++++++++++------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index c01d4ed5..fb4ef7cd 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -54,7 +54,8 @@ impl Renderer { debug_level: CompileDebugLevel::None, }; - let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + let pipeline_compile_options = PipelineCompileOptions::new() + .pipeline_launch_params_variable_name("PARAMS") .uses_motion_blur(false) .num_attribute_values(2) .num_payload_values(2) diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index b5e91b6f..57247b96 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -52,7 +52,8 @@ impl Renderer { debug_level: CompileDebugLevel::None, }; - let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + let pipeline_compile_options = PipelineCompileOptions::new() + .pipeline_launch_params_variable_name("PARAMS") .uses_motion_blur(false) .num_attribute_values(2) .num_payload_values(2) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 2c279174..3e39dddf 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -117,20 +117,19 @@ pub struct PipelineCompileOptions { num_payload_values: i32, num_attribute_values: i32, exception_flags: ExceptionFlags, - pipeline_launch_params_variable_name: CString, + pipeline_launch_params_variable_name: Option, primitive_type_flags: PrimitiveTypeFlags, } impl PipelineCompileOptions { - pub fn new(launch_params_variable_name: &str) -> PipelineCompileOptions { + pub fn new() -> PipelineCompileOptions { PipelineCompileOptions { uses_motion_blur: false, traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, num_payload_values: 0, num_attribute_values: 0, exception_flags: ExceptionFlags::NONE, - pipeline_launch_params_variable_name: CString::new(launch_params_variable_name) - .expect("launch_params_variable_name contains a nul byte"), + pipeline_launch_params_variable_name: None, primitive_type_flags: PrimitiveTypeFlags::DEFAULT, } } @@ -144,9 +143,12 @@ impl PipelineCompileOptions { numPayloadValues: self.num_payload_values, numAttributeValues: self.num_attribute_values, exceptionFlags: self.exception_flags.bits(), - pipelineLaunchParamsVariableName: self - .pipeline_launch_params_variable_name - .as_ptr(), + pipelineLaunchParamsVariableName: if let Some(ref name) = self + .pipeline_launch_params_variable_name { + name.as_ptr() + } else { + std::ptr::null() + }, usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, reserved: 0, reserved2: 0, @@ -158,9 +160,12 @@ impl PipelineCompileOptions { numPayloadValues: self.num_payload_values, numAttributeValues: self.num_attribute_values, exceptionFlags: self.exception_flags.bits(), - pipelineLaunchParamsVariableName: self - .pipeline_launch_params_variable_name - .as_char_ptr(), + pipelineLaunchParamsVariableName: if let Some(ref name) = self + .pipeline_launch_params_variable_name { + name.as_ptr() + } else { + std::ptr::null() + }, usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, } } @@ -193,7 +198,9 @@ impl PipelineCompileOptions { } pub fn pipeline_launch_params_variable_name(mut self, name: &str) -> Self { - self.pipeline_launch_params_variable_name = CString::new(name).expect("Invalid string"); + self.pipeline_launch_params_variable_name = Some( + CString::new(name).expect("pipeline launch params variable name contains nul bytes"), + ); self } } From b1f88be6e05eb12cb69d1e386f992b8a4719eeb6 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:38:53 +1300 Subject: [PATCH 017/100] Remove Clone from Module and ProgramGroup --- .../examples/ex02_pipeline/src/renderer.rs | 40 +++++++++---------- .../examples/ex03_window/src/renderer.rs | 40 +++++++++---------- crates/optix/src/module.rs | 1 - crates/optix/src/program_group.rs | 1 - 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index fb4ef7cd..8bfa9b2c 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -91,26 +91,6 @@ impl Renderer { // create hitgroup programs let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; - // create pipeline - let mut program_groups = Vec::new(); - program_groups.extend(pg_raygen.iter().cloned()); - program_groups.extend(pg_miss.iter().cloned()); - program_groups.extend(pg_hitgroup.iter().cloned()); - - let pipeline_link_options = PipelineLinkOptions { - max_trace_depth: 2, - debug_level: CompileDebugLevel::LineInfo, - }; - - let (pipeline, _log) = Pipeline::new( - &mut ctx, - &pipeline_compile_options, - pipeline_link_options, - &program_groups, - )?; - - pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - // create SBT let rec_raygen: Vec<_> = pg_raygen .iter() @@ -144,6 +124,26 @@ impl Renderer { .hitgroup(&mut buf_hitgroup) .build(); + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.into_iter()); + program_groups.extend(pg_miss.into_iter()); + program_groups.extend(pg_hitgroup.into_iter()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = Pipeline::new( + &mut ctx, + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + let mut color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; let launch_params = LaunchParams { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 57247b96..646ff181 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -89,26 +89,6 @@ impl Renderer { // create hitgroup programs let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; - // create pipeline - let mut program_groups = Vec::new(); - program_groups.extend(pg_raygen.iter().cloned()); - program_groups.extend(pg_miss.iter().cloned()); - program_groups.extend(pg_hitgroup.iter().cloned()); - - let pipeline_link_options = PipelineLinkOptions { - max_trace_depth: 2, - debug_level: CompileDebugLevel::LineInfo, - }; - - let (pipeline, _log) = Pipeline::new( - &mut ctx, - &pipeline_compile_options, - pipeline_link_options, - &program_groups, - )?; - - pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - // create SBT let rec_raygen: Vec<_> = pg_raygen .iter() @@ -142,6 +122,26 @@ impl Renderer { .hitgroup(&mut buf_hitgroup) .build(); + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.into_iter()); + program_groups.extend(pg_miss.into_iter()); + program_groups.extend(pg_hitgroup.into_iter()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = Pipeline::new( + &mut ctx, + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; let launch_params = LaunchParams { diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 3e39dddf..1d46aee1 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -3,7 +3,6 @@ type Result = std::result::Result; use std::ffi::{CStr, CString}; -#[derive(Clone)] #[repr(transparent)] pub struct Module { pub(crate) raw: sys::OptixModule, diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index 54bb6a73..a8aa1c88 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -97,7 +97,6 @@ impl<'m> ProgramGroupDesc<'m> { /// The lifetime of a module must extend to the lifetime of any /// OptixProgramGroup that references that module. #[repr(transparent)] -#[derive(Clone)] pub struct ProgramGroup { pub(crate) raw: sys::OptixProgramGroup, } From d16856b5e1ddc7e8c3022e41b129c5b8968d3ade Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:52:01 +1300 Subject: [PATCH 018/100] Make log callback safe User catch_unwind to guard against panic in C. Remove note about lifetime of closure since it's 'static anyway. Have set_log_callback return a Result instead of panicking on error. --- .../examples/ex02_pipeline/src/renderer.rs | 2 +- .../examples/ex03_window/src/renderer.rs | 2 +- crates/optix/src/context.rs | 38 +++++++++---------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 8bfa9b2c..42c4ec97 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -45,7 +45,7 @@ impl Renderer { let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 646ff181..b4c1a529 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -43,7 +43,7 @@ impl Renderer { let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 8f36ebf2..19b5cd1e 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -120,23 +120,18 @@ impl DeviceContext { /// the user or may perform slower than expected. /// * 4 - print: Status or progress messages. /// Higher levels might occur. - /// - /// # Safety - /// Note that the callback must live longer than the DeviceContext which - /// it's set for. This is because OptiX will fire messages when the - /// underlying OptixDeviceContext is destroyed. In order to do ensure - /// this we leak the closure `cb`. So don't go setting a new closure - /// every frame. - pub fn set_log_callback(&mut self, cb: F, level: u32) + pub fn set_log_callback(&mut self, cb: F, level: u32) -> Result<()> where F: FnMut(u32, &str, &str) + 'static, { let (closure, trampoline) = unsafe { unpack_closure(cb) }; - let res = unsafe { - sys::optixDeviceContextSetLogCallback(self.raw, Some(trampoline), closure, level) - }; - if res != sys::OptixResult::OPTIX_SUCCESS { - panic!("Failed to set log callback"); + unsafe { + Ok(optix_call!(optixDeviceContextSetLogCallback( + self.raw, + Some(trampoline), + closure, + level + ))?) } } } @@ -153,10 +148,6 @@ type LogCallback = extern "C" fn(c_uint, *const c_char, *const c_char, *mut c_vo /// /// Calling the trampoline function with anything except the `void*` pointer /// will result in *Undefined Behaviour*. -/// -/// The closure should guarantee that it never panics, seeing as panicking -/// across the FFI barrier is *Undefined Behaviour*. You may find -/// `std::panic::catch_unwind()` useful. unsafe fn unpack_closure(closure: F) -> (*mut c_void, LogCallback) where F: FnMut(u32, &str, &str), @@ -169,10 +160,15 @@ where ) where F: FnMut(u32, &str, &str), { - let tag = unsafe { CStr::from_ptr(tag).to_string_lossy().into_owned() }; - let msg = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; - let closure: &mut F = unsafe { &mut *(data as *mut F) }; - (*closure)(level, &tag, &msg); + if let Err(e) = std::panic::catch_unwind(|| { + let tag = unsafe { CStr::from_ptr(tag).to_string_lossy().into_owned() }; + let msg = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; + let closure: &mut F = unsafe { &mut *(data as *mut F) }; + + (*closure)(level, &tag, &msg); + }) { + eprintln!("Caught a panic calling log closure: {:?}", e); + } } let cb = Box::new(closure); From e0e00921ddfe8bb7adf5bde36ffbb94a67d554c1 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 09:13:06 +1300 Subject: [PATCH 019/100] add wip glam support --- crates/cust/Cargo.toml | 3 ++- crates/cust/src/memory/mod.rs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index cb7c00a7..b410782d 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -10,9 +10,10 @@ cust_raw = { path = "../cust_raw", version = "0.11.2"} bitflags = "1.2" cust_derive = { path = "../cust_derive", version = "0.1" } vek = { version = "0.15.1", optional = true } +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } [features] -default-features = ["vek"] +default-features = ["vek", "glam"] [build-dependencies] find_cuda_helper = { path = "../find_cuda_helper", version = "0.1" } diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 5ad71424..1e4095cd 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -297,6 +297,14 @@ macro_rules! impl_device_copy_vek { } } +macro_rules! impl_device_copy_glam { + ($($strukt:ty),* $(,)?) => { + $( + unsafe impl DeviceCopy for $strukt {} + )* + } +} + #[cfg(feature = "vek")] use vek::*; @@ -307,3 +315,8 @@ impl_device_copy_vek! { CubicBezier2, CubicBezier3, Quaternion, } + +#[cfg(feature = "glam")] +impl_device_copy_glam! { + glam::Vec2, glam::Vec3, glam::Vec4, glam::IVec2, glam::IVec3, glam::IVec4, +} From 87208bf6bee4f3adfe8530193199e4448e2b16d0 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 09:13:45 +1300 Subject: [PATCH 020/100] dont panic in drop --- crates/optix/src/module.rs | 4 +++- crates/optix/src/pipeline.rs | 4 +++- crates/optix/src/program_group.rs | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 1d46aee1..3716c916 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -274,6 +274,8 @@ impl Module { impl Drop for Module { fn drop(&mut self) { - unsafe { optix_call!(optixModuleDestroy(self.raw)).expect("optixModuleDestroy failed") } + unsafe { + sys::optixModuleDestroy(self.raw); + } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 0b14dcbf..848fca9b 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -75,7 +75,9 @@ impl Pipeline { impl Drop for Pipeline { fn drop(&mut self) { - unsafe { optix_call!(optixPipelineDestroy(self.raw)).expect("optixPipelineDestroy failed") } + unsafe { + sys::optixPipelineDestroy(self.raw); + } } } diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index a8aa1c88..a29600f6 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -263,8 +263,7 @@ impl ProgramGroup { impl Drop for ProgramGroup { fn drop(&mut self) { unsafe { - optix_call!(optixProgramGroupDestroy(self.raw)) - .expect("optixProgramGroupDestroy failed") + sys::optixProgramGroupDestroy(self.raw); } } } From a05270eb3350e3495ceac692ae4a286d921a642b Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 12:59:19 +1300 Subject: [PATCH 021/100] Rework DevicePointer on top of CUdeviceptr This switches out *T for CUdeviceptr in DevicePointer. This has the knock-on effect of removing a lot of "pretend we're a CPU pointer" stuff from downstream types like DeviceSlice. --- crates/cust/src/memory/device/device_box.rs | 134 +++---- .../cust/src/memory/device/device_buffer.rs | 162 +++------ crates/cust/src/memory/device/device_slice.rs | 343 ++++-------------- crates/cust/src/memory/malloc.rs | 17 +- crates/cust/src/memory/mod.rs | 24 +- crates/cust/src/memory/pointer.rs | 161 +++----- crates/cust/src/module.rs | 8 +- crates/cust/src/prelude.rs | 2 +- crates/cust/src/util.rs | 18 +- crates/optix/src/denoiser.rs | 24 +- examples/optix/denoiser/src/main.rs | 4 +- 11 files changed, 252 insertions(+), 645 deletions(-) diff --git a/crates/cust/src/memory/device/device_box.rs b/crates/cust/src/memory/device/device_box.rs index 5e11ce8c..db56d7d8 100644 --- a/crates/cust/src/memory/device/device_box.rs +++ b/crates/cust/src/memory/device/device_box.rs @@ -15,10 +15,11 @@ use std::os::raw::c_void; /// /// See the [`module-level documentation`](../memory/index.html) for more information on device memory. #[derive(Debug)] -pub struct DBox { +pub struct DeviceBox { pub(crate) ptr: DevicePointer, } -impl DBox { + +impl DeviceBox { /// Allocate device memory and place val into it. /// /// This doesn't actually allocate if `T` is zero-sized. @@ -35,13 +36,13 @@ impl DBox { /// let five = DBox::new(&5).unwrap(); /// ``` pub fn new(val: &T) -> CudaResult { - let mut dev_box = unsafe { DBox::uninitialized()? }; + let mut dev_box = unsafe { DeviceBox::uninitialized()? }; dev_box.copy_from(val)?; Ok(dev_box) } } -impl DBox { +impl DeviceBox { /// Read the data back from the GPU into host memory. pub fn as_host_value(&self) -> CudaResult { let mut val = T::default(); @@ -50,7 +51,7 @@ impl DBox { } } -impl DBox { +impl DeviceBox { /// Allocate device memory, but do not initialize it. /// /// This doesn't actually allocate if `T` is zero-sized. @@ -71,12 +72,12 @@ impl DBox { /// ``` pub unsafe fn uninitialized() -> CudaResult { if mem::size_of::() == 0 { - Ok(DBox { + Ok(DeviceBox { ptr: DevicePointer::null(), }) } else { let ptr = cuda_malloc(1)?; - Ok(DBox { ptr }) + Ok(DeviceBox { ptr }) } } @@ -101,14 +102,10 @@ impl DBox { /// assert_eq!(0, value); /// ``` pub unsafe fn zeroed() -> CudaResult { - let mut new_box = DBox::uninitialized()?; + let mut new_box = DeviceBox::uninitialized()?; if mem::size_of::() != 0 { - cuda::cuMemsetD8_v2( - new_box.as_device_ptr().as_raw_mut() as u64, - 0, - mem::size_of::(), - ) - .to_result()?; + cuda::cuMemsetD8_v2(new_box.as_device_ptr().as_raw(), 0, mem::size_of::()) + .to_result()?; } Ok(new_box) } @@ -135,9 +132,9 @@ impl DBox { /// let ptr = DBox::into_device(x).as_raw_mut(); /// let x = unsafe { DBox::from_raw(ptr) }; /// ``` - pub unsafe fn from_raw(ptr: *mut T) -> Self { - DBox { - ptr: DevicePointer::wrap(ptr), + pub unsafe fn from_raw(ptr: cust_raw::CUdeviceptr) -> Self { + DeviceBox { + ptr: DevicePointer::from_raw(ptr), } } @@ -164,7 +161,7 @@ impl DBox { /// let x = unsafe { DBox::from_device(ptr) }; /// ``` pub unsafe fn from_device(ptr: DevicePointer) -> Self { - DBox { ptr } + DeviceBox { ptr } } /// Consumes the DBox, returning the wrapped DevicePointer. @@ -187,7 +184,7 @@ impl DBox { /// # unsafe { DBox::from_device(ptr) }; /// ``` #[allow(clippy::wrong_self_convention)] - pub fn into_device(mut b: DBox) -> DevicePointer { + pub fn into_device(mut b: DeviceBox) -> DevicePointer { let ptr = mem::replace(&mut b.ptr, DevicePointer::null()); mem::forget(b); ptr @@ -229,7 +226,7 @@ impl DBox { /// }, /// } /// ``` - pub fn drop(mut dev_box: DBox) -> DropResult> { + pub fn drop(mut dev_box: DeviceBox) -> DropResult> { if dev_box.ptr.is_null() { return Ok(()); } @@ -241,12 +238,12 @@ impl DBox { mem::forget(dev_box); Ok(()) } - Err(e) => Err((e, DBox { ptr })), + Err(e) => Err((e, DeviceBox { ptr })), } } } } -impl Drop for DBox { +impl Drop for DeviceBox { fn drop(&mut self) { if self.ptr.is_null() { return; @@ -258,23 +255,22 @@ impl Drop for DBox { } } } -impl Pointer for DBox { + +impl Pointer for DeviceBox { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Pointer::fmt(&self.ptr, f) + let ptr = self.ptr.as_raw() as *const c_void; + fmt::Pointer::fmt(&ptr, f) } } -impl crate::private::Sealed for DBox {} -impl CopyDestination for DBox { + +impl crate::private::Sealed for DeviceBox {} +impl CopyDestination for DeviceBox { fn copy_from(&mut self, val: &T) -> CudaResult<()> { let size = mem::size_of::(); if size != 0 { unsafe { - cuda::cuMemcpyHtoD_v2( - self.ptr.as_raw_mut() as u64, - val as *const T as *const c_void, - size, - ) - .to_result()? + cuda::cuMemcpyHtoD_v2(self.ptr.as_raw(), val as *const T as *const c_void, size) + .to_result()? } } Ok(()) @@ -295,54 +291,38 @@ impl CopyDestination for DBox { Ok(()) } } -impl CopyDestination> for DBox { - fn copy_from(&mut self, val: &DBox) -> CudaResult<()> { +impl CopyDestination> for DeviceBox { + fn copy_from(&mut self, val: &DeviceBox) -> CudaResult<()> { let size = mem::size_of::(); if size != 0 { - unsafe { - cuda::cuMemcpyDtoD_v2(self.ptr.as_raw_mut() as u64, val.ptr.as_raw() as u64, size) - .to_result()? - } + unsafe { cuda::cuMemcpyDtoD_v2(self.ptr.as_raw(), val.ptr.as_raw(), size).to_result()? } } Ok(()) } - fn copy_to(&self, val: &mut DBox) -> CudaResult<()> { + fn copy_to(&self, val: &mut DeviceBox) -> CudaResult<()> { let size = mem::size_of::(); if size != 0 { - unsafe { - cuda::cuMemcpyDtoD_v2(val.ptr.as_raw_mut() as u64, self.ptr.as_raw() as u64, size) - .to_result()? - } + unsafe { cuda::cuMemcpyDtoD_v2(val.ptr.as_raw(), self.ptr.as_raw(), size).to_result()? } } Ok(()) } } -impl AsyncCopyDestination> for DBox { - unsafe fn async_copy_from(&mut self, val: &DBox, stream: &Stream) -> CudaResult<()> { +impl AsyncCopyDestination> for DeviceBox { + unsafe fn async_copy_from(&mut self, val: &DeviceBox, stream: &Stream) -> CudaResult<()> { let size = mem::size_of::(); if size != 0 { - cuda::cuMemcpyDtoDAsync_v2( - self.ptr.as_raw_mut() as u64, - val.ptr.as_raw() as u64, - size, - stream.as_inner(), - ) - .to_result()? + cuda::cuMemcpyDtoDAsync_v2(self.ptr.as_raw(), val.ptr.as_raw(), size, stream.as_inner()) + .to_result()? } Ok(()) } - unsafe fn async_copy_to(&self, val: &mut DBox, stream: &Stream) -> CudaResult<()> { + unsafe fn async_copy_to(&self, val: &mut DeviceBox, stream: &Stream) -> CudaResult<()> { let size = mem::size_of::(); if size != 0 { - cuda::cuMemcpyDtoDAsync_v2( - val.ptr.as_raw_mut() as u64, - self.ptr.as_raw() as u64, - size, - stream.as_inner(), - ) - .to_result()? + cuda::cuMemcpyDtoDAsync_v2(val.ptr.as_raw(), self.ptr.as_raw(), size, stream.as_inner()) + .to_result()? } Ok(()) } @@ -359,41 +339,41 @@ mod test_device_box { #[test] fn test_allocate_and_free_device_box() { let _context = crate::quick_init().unwrap(); - let x = DBox::new(&5u64).unwrap(); + let x = DeviceBox::new(&5u64).unwrap(); drop(x); } #[test] fn test_device_box_allocates_for_non_zst() { let _context = crate::quick_init().unwrap(); - let x = DBox::new(&5u64).unwrap(); - let ptr = DBox::into_device(x); + let x = DeviceBox::new(&5u64).unwrap(); + let ptr = DeviceBox::into_device(x); assert!(!ptr.is_null()); - let _ = unsafe { DBox::from_device(ptr) }; + let _ = unsafe { DeviceBox::from_device(ptr) }; } #[test] fn test_device_box_doesnt_allocate_for_zero_sized_type() { let _context = crate::quick_init().unwrap(); - let x = DBox::new(&ZeroSizedType).unwrap(); - let ptr = DBox::into_device(x); + let x = DeviceBox::new(&ZeroSizedType).unwrap(); + let ptr = DeviceBox::into_device(x); assert!(ptr.is_null()); - let _ = unsafe { DBox::from_device(ptr) }; + let _ = unsafe { DeviceBox::from_device(ptr) }; } #[test] fn test_into_from_device() { let _context = crate::quick_init().unwrap(); - let x = DBox::new(&5u64).unwrap(); - let ptr = DBox::into_device(x); - let _ = unsafe { DBox::from_device(ptr) }; + let x = DeviceBox::new(&5u64).unwrap(); + let ptr = DeviceBox::into_device(x); + let _ = unsafe { DeviceBox::from_device(ptr) }; } #[test] fn test_copy_host_to_device() { let _context = crate::quick_init().unwrap(); let y = 5u64; - let mut x = DBox::new(&0u64).unwrap(); + let mut x = DeviceBox::new(&0u64).unwrap(); x.copy_from(&y).unwrap(); let mut z = 10u64; x.copy_to(&mut z).unwrap(); @@ -403,7 +383,7 @@ mod test_device_box { #[test] fn test_copy_device_to_host() { let _context = crate::quick_init().unwrap(); - let x = DBox::new(&5u64).unwrap(); + let x = DeviceBox::new(&5u64).unwrap(); let mut y = 0u64; x.copy_to(&mut y).unwrap(); assert_eq!(5, y); @@ -412,9 +392,9 @@ mod test_device_box { #[test] fn test_copy_device_to_device() { let _context = crate::quick_init().unwrap(); - let x = DBox::new(&5u64).unwrap(); - let mut y = DBox::new(&0u64).unwrap(); - let mut z = DBox::new(&0u64).unwrap(); + let x = DeviceBox::new(&5u64).unwrap(); + let mut y = DeviceBox::new(&0u64).unwrap(); + let mut z = DeviceBox::new(&0u64).unwrap(); x.copy_to(&mut y).unwrap(); z.copy_from(&y).unwrap(); @@ -426,8 +406,8 @@ mod test_device_box { #[test] fn test_device_pointer_implements_traits_safely() { let _context = crate::quick_init().unwrap(); - let mut x = DBox::new(&5u64).unwrap(); - let mut y = DBox::new(&0u64).unwrap(); + let mut x = DeviceBox::new(&5u64).unwrap(); + let mut y = DeviceBox::new(&0u64).unwrap(); // If the impls dereference the pointer, this should segfault. let _ = Ord::cmp(&x.as_device_ptr(), &y.as_device_ptr()); diff --git a/crates/cust/src/memory/device/device_buffer.rs b/crates/cust/src/memory/device/device_buffer.rs index 9fa6b435..4d6af7cb 100644 --- a/crates/cust/src/memory/device/device_buffer.rs +++ b/crates/cust/src/memory/device/device_buffer.rs @@ -1,5 +1,5 @@ use crate::error::{CudaResult, DropResult, ToResult}; -use crate::memory::device::{AsyncCopyDestination, CopyDestination, DSlice}; +use crate::memory::device::{AsyncCopyDestination, CopyDestination, DeviceSlice}; use crate::memory::malloc::{cuda_free, cuda_malloc}; use crate::memory::DeviceCopy; use crate::memory::DevicePointer; @@ -8,15 +8,15 @@ use crate::sys as cuda; use std::mem; use std::ops::{Deref, DerefMut}; -use std::ptr; - /// Fixed-size device-side buffer. Provides basic access to device memory. #[derive(Debug)] -pub struct DBuffer { +#[repr(C)] +pub struct DeviceBuffer { buf: DevicePointer, capacity: usize, } -impl DBuffer { + +impl DeviceBuffer { /// Allocate a new device buffer large enough to hold `size` `T`'s, but without /// initializing the contents. /// @@ -42,9 +42,10 @@ impl DBuffer { let ptr = if size > 0 && mem::size_of::() > 0 { cuda_malloc(size)? } else { - DevicePointer::wrap(ptr::NonNull::dangling().as_ptr() as *mut T) + // FIXME (AL): Do we /really/ want to allow creating an invalid buffer? + DevicePointer::null() }; - Ok(DBuffer { + Ok(DeviceBuffer { buf: ptr, capacity: size, }) @@ -76,14 +77,14 @@ impl DBuffer { /// ``` pub unsafe fn zeroed(size: usize) -> CudaResult { let ptr = if size > 0 && mem::size_of::() > 0 { - let mut ptr = cuda_malloc(size)?; - cuda::cuMemsetD8_v2(ptr.as_raw_mut() as u64, 0, size * mem::size_of::()) - .to_result()?; + let ptr = cuda_malloc(size)?; + cuda::cuMemsetD8_v2(ptr.as_raw(), 0, size * mem::size_of::()).to_result()?; ptr } else { - DevicePointer::wrap(ptr::NonNull::dangling().as_ptr() as *mut T) + // FIXME (AL): Do we /really/ want to allow creating an invalid buffer? + DevicePointer::null() }; - Ok(DBuffer { + Ok(DeviceBuffer { buf: ptr, capacity: size, }) @@ -125,8 +126,8 @@ impl DBuffer { /// /// let buffer = unsafe { DeviceBuffer::from_raw_parts(ptr, size) }; /// ``` - pub unsafe fn from_raw_parts(ptr: DevicePointer, capacity: usize) -> DBuffer { - DBuffer { buf: ptr, capacity } + pub unsafe fn from_raw_parts(ptr: DevicePointer, capacity: usize) -> DeviceBuffer { + DeviceBuffer { buf: ptr, capacity } } /// Destroy a `DeviceBuffer`, returning an error. @@ -148,7 +149,7 @@ impl DBuffer { /// }, /// } /// ``` - pub fn drop(mut dev_buf: DBuffer) -> DropResult> { + pub fn drop(mut dev_buf: DeviceBuffer) -> DropResult> { if dev_buf.buf.is_null() { return Ok(()); } @@ -162,7 +163,7 @@ impl DBuffer { mem::forget(dev_buf); Ok(()) } - Err(e) => Err((e, DBuffer::from_raw_parts(ptr, capacity))), + Err(e) => Err((e, DeviceBuffer::from_raw_parts(ptr, capacity))), } } } else { @@ -170,7 +171,7 @@ impl DBuffer { } } } -impl DBuffer { +impl DeviceBuffer { /// Allocate a new device buffer of the same size as `slice`, initialized with a clone of /// the data in `slice`. /// @@ -188,7 +189,7 @@ impl DBuffer { /// ``` pub fn from_slice(slice: &[T]) -> CudaResult { unsafe { - let mut uninit = DBuffer::uninitialized(slice.len())?; + let mut uninit = DeviceBuffer::uninitialized(slice.len())?; uninit.copy_from(slice)?; Ok(uninit) } @@ -221,32 +222,26 @@ impl DBuffer { /// } /// ``` pub unsafe fn from_slice_async(slice: &[T], stream: &Stream) -> CudaResult { - let mut uninit = DBuffer::uninitialized(slice.len())?; + let mut uninit = DeviceBuffer::uninitialized(slice.len())?; uninit.async_copy_from(slice, stream)?; Ok(uninit) } } -impl Deref for DBuffer { - type Target = DSlice; +impl Deref for DeviceBuffer { + type Target = DeviceSlice; - fn deref(&self) -> &DSlice { - unsafe { - DSlice::from_slice(::std::slice::from_raw_parts( - self.buf.as_raw(), - self.capacity, - )) - } + fn deref(&self) -> &DeviceSlice { + unsafe { &*(self as *const _ as *const DeviceSlice) } } } -impl DerefMut for DBuffer { - fn deref_mut(&mut self) -> &mut DSlice { - unsafe { - &mut *(::std::slice::from_raw_parts_mut(self.buf.as_raw_mut(), self.capacity) - as *mut [T] as *mut DSlice) - } + +impl DerefMut for DeviceBuffer { + fn deref_mut(&mut self) -> &mut DeviceSlice { + unsafe { &mut *(self as *mut _ as *mut DeviceSlice) } } } -impl Drop for DBuffer { + +impl Drop for DeviceBuffer { fn drop(&mut self) { if self.buf.is_null() { return; @@ -265,7 +260,6 @@ impl Drop for DBuffer { #[cfg(test)] mod test_device_buffer { use super::*; - use crate::memory::device::DBox; use crate::stream::{Stream, StreamFlags}; #[derive(Clone, Copy, Debug)] @@ -275,7 +269,7 @@ mod test_device_buffer { #[test] fn test_from_slice_drop() { let _context = crate::quick_init().unwrap(); - let buf = DBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); + let buf = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); drop(buf); } @@ -284,7 +278,7 @@ mod test_device_buffer { let _context = crate::quick_init().unwrap(); let start = [0u64, 1, 2, 3, 4, 5]; let mut end = [0u64, 0, 0, 0, 0, 0]; - let buf = DBuffer::from_slice(&start).unwrap(); + let buf = DeviceBuffer::from_slice(&start).unwrap(); buf.copy_to(&mut end).unwrap(); assert_eq!(start, end); } @@ -296,44 +290,18 @@ mod test_device_buffer { let start = [0u64, 1, 2, 3, 4, 5]; let mut end = [0u64, 0, 0, 0, 0, 0]; unsafe { - let buf = DBuffer::from_slice_async(&start, &stream).unwrap(); + let buf = DeviceBuffer::from_slice_async(&start, &stream).unwrap(); buf.async_copy_to(&mut end, &stream).unwrap(); } stream.synchronize().unwrap(); assert_eq!(start, end); } - #[test] - fn test_slice() { - let _context = crate::quick_init().unwrap(); - let start = [0u64, 1, 2, 3, 4, 5]; - let mut end = [0u64, 0]; - let mut buf = DBuffer::from_slice(&[0u64, 0, 0, 0]).unwrap(); - buf.copy_from(&start[0..4]).unwrap(); - buf[0..2].copy_to(&mut end).unwrap(); - assert_eq!(start[0..2], end); - } - - #[test] - fn test_async_slice() { - let _context = crate::quick_init().unwrap(); - let stream = Stream::new(StreamFlags::NON_BLOCKING, None).unwrap(); - let start = [0u64, 1, 2, 3, 4, 5]; - let mut end = [0u64, 0]; - unsafe { - let mut buf = DBuffer::from_slice_async(&[0u64, 0, 0, 0], &stream).unwrap(); - buf.async_copy_from(&start[0..4], &stream).unwrap(); - buf[0..2].async_copy_to(&mut end, &stream).unwrap(); - stream.synchronize().unwrap(); - assert_eq!(start[0..2], end); - } - } - #[test] #[should_panic] fn test_copy_to_d2h_wrong_size() { let _context = crate::quick_init().unwrap(); - let buf = DBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); + let buf = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); let mut end = [0u64, 1, 2, 3, 4]; let _ = buf.copy_to(&mut end); } @@ -344,7 +312,7 @@ mod test_device_buffer { let _context = crate::quick_init().unwrap(); let stream = Stream::new(StreamFlags::NON_BLOCKING, None).unwrap(); unsafe { - let buf = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); + let buf = DeviceBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); let mut end = [0u64, 1, 2, 3, 4]; let _ = buf.async_copy_to(&mut end, &stream); } @@ -355,7 +323,7 @@ mod test_device_buffer { fn test_copy_from_h2d_wrong_size() { let _context = crate::quick_init().unwrap(); let start = [0u64, 1, 2, 3, 4]; - let mut buf = DBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); + let mut buf = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); let _ = buf.copy_from(&start); } @@ -366,47 +334,17 @@ mod test_device_buffer { let stream = Stream::new(StreamFlags::NON_BLOCKING, None).unwrap(); let start = [0u64, 1, 2, 3, 4]; unsafe { - let mut buf = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); + let mut buf = DeviceBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); let _ = buf.async_copy_from(&start, &stream); } } - #[test] - fn test_copy_device_slice_to_device() { - let _context = crate::quick_init().unwrap(); - let start = DBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); - let mut mid = DBuffer::from_slice(&[0u64, 0, 0, 0]).unwrap(); - let mut end = DBuffer::from_slice(&[0u64, 0]).unwrap(); - let mut host_end = [0u64, 0]; - start[1..5].copy_to(&mut mid).unwrap(); - end.copy_from(&mid[1..3]).unwrap(); - end.copy_to(&mut host_end).unwrap(); - assert_eq!([2u64, 3], host_end); - } - - #[test] - fn test_async_copy_device_slice_to_device() { - let _context = crate::quick_init().unwrap(); - let stream = Stream::new(StreamFlags::NON_BLOCKING, None).unwrap(); - unsafe { - let start = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); - let mut mid = DBuffer::from_slice_async(&[0u64, 0, 0, 0], &stream).unwrap(); - let mut end = DBuffer::from_slice_async(&[0u64, 0], &stream).unwrap(); - let mut host_end = [0u64, 0]; - start[1..5].async_copy_to(&mut mid, &stream).unwrap(); - end.async_copy_from(&mid[1..3], &stream).unwrap(); - end.async_copy_to(&mut host_end, &stream).unwrap(); - stream.synchronize().unwrap(); - assert_eq!([2u64, 3], host_end); - } - } - #[test] #[should_panic] fn test_copy_to_d2d_wrong_size() { let _context = crate::quick_init().unwrap(); - let buf = DBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); - let mut end = DBuffer::from_slice(&[0u64, 1, 2, 3, 4]).unwrap(); + let buf = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); + let mut end = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4]).unwrap(); let _ = buf.copy_to(&mut end); } @@ -416,8 +354,8 @@ mod test_device_buffer { let _context = crate::quick_init().unwrap(); let stream = Stream::new(StreamFlags::NON_BLOCKING, None).unwrap(); unsafe { - let buf = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); - let mut end = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4], &stream).unwrap(); + let buf = DeviceBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); + let mut end = DeviceBuffer::from_slice_async(&[0u64, 1, 2, 3, 4], &stream).unwrap(); let _ = buf.async_copy_to(&mut end, &stream); } } @@ -426,8 +364,8 @@ mod test_device_buffer { #[should_panic] fn test_copy_from_d2d_wrong_size() { let _context = crate::quick_init().unwrap(); - let mut buf = DBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); - let start = DBuffer::from_slice(&[0u64, 1, 2, 3, 4]).unwrap(); + let mut buf = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4, 5]).unwrap(); + let start = DeviceBuffer::from_slice(&[0u64, 1, 2, 3, 4]).unwrap(); let _ = buf.copy_from(&start); } @@ -437,19 +375,9 @@ mod test_device_buffer { let _context = crate::quick_init().unwrap(); let stream = Stream::new(StreamFlags::NON_BLOCKING, None).unwrap(); unsafe { - let mut buf = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); - let start = DBuffer::from_slice_async(&[0u64, 1, 2, 3, 4], &stream).unwrap(); + let mut buf = DeviceBuffer::from_slice_async(&[0u64, 1, 2, 3, 4, 5], &stream).unwrap(); + let start = DeviceBuffer::from_slice_async(&[0u64, 1, 2, 3, 4], &stream).unwrap(); let _ = buf.async_copy_from(&start, &stream); } } - - #[test] - fn test_can_create_uninitialized_non_devicecopy_buffers() { - let _context = crate::quick_init().unwrap(); - unsafe { - let _box: DBox> = DBox::uninitialized().unwrap(); - let buffer: DBuffer> = DBuffer::uninitialized(10).unwrap(); - let _slice = &buffer[0..5]; - } - } } diff --git a/crates/cust/src/memory/device/device_slice.rs b/crates/cust/src/memory/device/device_slice.rs index d1a4a7e6..4a281706 100644 --- a/crates/cust/src/memory/device/device_slice.rs +++ b/crates/cust/src/memory/device/device_slice.rs @@ -1,25 +1,25 @@ +use cuda::CUdeviceptr; + use crate::error::{CudaResult, ToResult}; use crate::memory::device::AsyncCopyDestination; -use crate::memory::device::{CopyDestination, DBuffer}; +use crate::memory::device::{CopyDestination, DeviceBuffer}; use crate::memory::DeviceCopy; use crate::memory::DevicePointer; use crate::stream::Stream; use crate::sys as cuda; -use std::iter::{ExactSizeIterator, FusedIterator}; -use std::mem::{self}; -use std::ops::{ - Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, -}; +use std::mem; use std::os::raw::c_void; -use std::slice::{self, Chunks, ChunksMut}; /// Fixed-size device-side slice. #[derive(Debug)] #[repr(C)] -pub struct DSlice([T]); +pub struct DeviceSlice { + ptr: DevicePointer, + len: usize, +} -impl DSlice { +impl DeviceSlice { pub fn as_host_vec(&self) -> CudaResult> { let mut vec = vec![T::default(); self.len()]; self.copy_to(&mut vec)?; @@ -31,7 +31,7 @@ impl DSlice { // I have no idea if this is safe or not. Probably not, though I can't imagine how the compiler // could possibly know that the pointer is not de-referenceable. I'm banking that we get proper // Dynamicaly-sized Types before the compiler authors break this assumption. -impl DSlice { +impl DeviceSlice { /// Returns the number of elements in the slice. /// /// # Examples @@ -43,7 +43,7 @@ impl DSlice { /// assert_eq!(a.len(), 3); /// ``` pub fn len(&self) -> usize { - self.0.len() + self.len } /// Returns `true` if the slice has a length of 0. @@ -57,7 +57,7 @@ impl DSlice { /// assert!(a.is_empty()); /// ``` pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.len == 0 } /// Return a raw device-pointer to the slice's buffer. @@ -74,28 +74,11 @@ impl DSlice { /// let a = DeviceBuffer::from_slice(&[1, 2, 3]).unwrap(); /// println!("{:p}", a.as_ptr()); /// ``` - pub fn as_ptr(&self) -> *const T { - self.0.as_ptr() - } - - /// Returns an unsafe mutable device-pointer to the slice's buffer. - /// - /// The caller must ensure that the slice outlives the pointer this function returns, or else - /// it will end up pointing to garbage. The caller must also ensure that the pointer is not - /// dereferenced by the CPU. - /// - /// Examples: - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// let mut a = DeviceBuffer::from_slice(&[1, 2, 3]).unwrap(); - /// println!("{:p}", a.as_mut_ptr()); - /// ``` - pub fn as_mut_ptr(&mut self) -> *mut T { - self.0.as_mut_ptr() + pub fn as_device_ptr(&self) -> CUdeviceptr { + self.ptr.as_raw() } + /* TODO (AL): keep these? /// Divides one DeviceSlice into two at a given index. /// /// The first will contain all indices from `[0, mid)` (excluding the index `mid` itself) and @@ -156,85 +139,18 @@ impl DSlice { let (left, right) = self.0.split_at_mut(mid); unsafe { (DSlice::from_slice_mut(left), DSlice::from_slice_mut(right)) } } + */ - /// Returns an iterator over `chunk_size` elements of the slice at a time. The chunks are device - /// slices and do not overlap. If `chunk_size` does not divide the length of the slice, then the - /// last chunk will not have length `chunk_size`. - /// - /// See `exact_chunks` for a variant of this iterator that returns chunks of always exactly - /// `chunk_size` elements. - /// - /// # Panics - /// - /// Panics if `chunk_size` is 0. - /// - /// # Examples - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// let slice = DeviceBuffer::from_slice(&[1u64, 2, 3, 4, 5]).unwrap(); - /// let mut iter = slice.chunks(2); - /// - /// assert_eq!(iter.next().unwrap().len(), 2); - /// - /// let mut host_buf = [0u64, 0]; - /// iter.next().unwrap().copy_to(&mut host_buf).unwrap(); - /// assert_eq!([3, 4], host_buf); - /// - /// assert_eq!(iter.next().unwrap().len(), 1); - /// - /// ``` - pub fn chunks(&self, chunk_size: usize) -> DeviceChunks { - DeviceChunks(self.0.chunks(chunk_size)) - } - - /// Returns an iterator over `chunk_size` elements of the slice at a time. The chunks are - /// mutable device slices and do not overlap. If `chunk_size` does not divide the length of the - /// slice, then the last chunk will not have length `chunk_size`. - /// - /// See `exact_chunks` for a variant of this iterator that returns chunks of always exactly - /// `chunk_size` elements. - /// - /// # Panics - /// - /// Panics if `chunk_size` is 0. - /// - /// # Examples - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// let mut slice = DeviceBuffer::from_slice(&[0u64, 0, 0, 0, 0]).unwrap(); - /// { - /// let mut iter = slice.chunks_mut(2); - /// - /// assert_eq!(iter.next().unwrap().len(), 2); - /// - /// let host_buf = [2u64, 3]; - /// iter.next().unwrap().copy_from(&host_buf).unwrap(); + /// Returns a `DevicePointer` to the buffer. /// - /// assert_eq!(iter.next().unwrap().len(), 1); - /// } + /// The caller must ensure that the buffer outlives the returned pointer, or it will end up + /// pointing to garbage. /// - /// let mut host_buf = [0u64, 0, 0, 0, 0]; - /// slice.copy_to(&mut host_buf).unwrap(); - /// assert_eq!([0u64, 0, 2, 3, 0], host_buf); - /// ``` - pub fn chunks_mut(&mut self, chunk_size: usize) -> DeviceChunksMut { - DeviceChunksMut(self.0.chunks_mut(chunk_size)) - } - - /// Private function used to transmute a CPU slice (which must have the device pointer as it's - /// buffer pointer) to a DeviceSlice. Completely unsafe. - pub(super) unsafe fn from_slice(slice: &[T]) -> &DSlice { - &*(slice as *const [T] as *const DSlice) - } - - /// Private function used to transmute a mutable CPU slice (which must have the device pointer - /// as it's buffer pointer) to a mutable DeviceSlice. Completely unsafe. - pub(super) unsafe fn from_slice_mut(slice: &mut [T]) -> &mut DSlice { - &mut *(slice as *mut [T] as *mut DSlice) + /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers + /// cannot be invalidated in that manner, but other types may be added in the future which can + /// reallocate. + pub fn as_ptr(&self) -> DevicePointer { + self.ptr } /// Returns a `DevicePointer` to the buffer. @@ -245,8 +161,8 @@ impl DSlice { /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers /// cannot be invalidated in that manner, but other types may be added in the future which can /// reallocate. - pub fn as_device_ptr(&mut self) -> DevicePointer { - unsafe { DevicePointer::wrap(self.0.as_mut_ptr()) } + pub fn as_mut_ptr(&mut self) -> DevicePointer { + self.ptr } /// Forms a slice from a `DevicePointer` and a length. @@ -279,8 +195,8 @@ impl DSlice { /// assert_eq!([1u64, 2], host_buf); /// ``` #[allow(clippy::needless_pass_by_value)] - pub unsafe fn from_raw_parts<'a>(data: DevicePointer, len: usize) -> &'a DSlice { - DSlice::from_slice(slice::from_raw_parts(data.as_raw(), len)) + pub unsafe fn from_raw_parts(ptr: DevicePointer, len: usize) -> DeviceSlice { + DeviceSlice { ptr, len } } /// Performs the same functionality as `from_raw_parts`, except that a @@ -298,145 +214,13 @@ impl DSlice { /// slices as with `from_raw_parts`. /// /// See the documentation of `from_raw_parts` for more details. - pub unsafe fn from_raw_parts_mut<'a>( - mut data: DevicePointer, - len: usize, - ) -> &'a mut DSlice { - DSlice::from_slice_mut(slice::from_raw_parts_mut(data.as_raw_mut(), len)) - } -} - -/// An iterator over a [`DeviceSlice`](struct.DeviceSlice.html) in (non-overlapping) chunks -/// (`chunk_size` elements at a time). -/// -/// When the slice len is not evenly divided by the chunk size, the last slice of the iteration will -/// be the remainder. -/// -/// This struct is created by the `chunks` method on `DeviceSlices`. -#[derive(Debug, Clone)] -pub struct DeviceChunks<'a, T: 'a>(Chunks<'a, T>); -impl<'a, T> Iterator for DeviceChunks<'a, T> { - type Item = &'a DSlice; - - fn next(&mut self) -> Option<&'a DSlice> { - self.0 - .next() - .map(|slice| unsafe { DSlice::from_slice(slice) }) - } - - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } - - fn count(self) -> usize { - self.0.len() - } - - fn nth(&mut self, n: usize) -> Option { - self.0 - .nth(n) - .map(|slice| unsafe { DSlice::from_slice(slice) }) - } - - #[inline] - fn last(self) -> Option { - self.0 - .last() - .map(|slice| unsafe { DSlice::from_slice(slice) }) - } -} -impl<'a, T> DoubleEndedIterator for DeviceChunks<'a, T> { - #[inline] - fn next_back(&mut self) -> Option<&'a DSlice> { - self.0 - .next_back() - .map(|slice| unsafe { DSlice::from_slice(slice) }) + pub unsafe fn from_raw_parts_mut(ptr: DevicePointer, len: usize) -> DeviceSlice { + DeviceSlice { ptr, len } } } -impl<'a, T> ExactSizeIterator for DeviceChunks<'a, T> {} -impl<'a, T> FusedIterator for DeviceChunks<'a, T> {} -/// An iterator over a [`DeviceSlice`](struct.DeviceSlice.html) in (non-overlapping) mutable chunks -/// (`chunk_size` elements at a time). -/// -/// When the slice len is not evenly divided by the chunk size, the last slice of the iteration will -/// be the remainder. -/// -/// This struct is created by the `chunks` method on `DeviceSlices`. -#[derive(Debug)] -pub struct DeviceChunksMut<'a, T: 'a>(ChunksMut<'a, T>); -impl<'a, T> Iterator for DeviceChunksMut<'a, T> { - type Item = &'a mut DSlice; - - fn next(&mut self) -> Option<&'a mut DSlice> { - self.0 - .next() - .map(|slice| unsafe { DSlice::from_slice_mut(slice) }) - } - - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } - - fn count(self) -> usize { - self.0.len() - } - - fn nth(&mut self, n: usize) -> Option { - self.0 - .nth(n) - .map(|slice| unsafe { DSlice::from_slice_mut(slice) }) - } - - #[inline] - fn last(self) -> Option { - self.0 - .last() - .map(|slice| unsafe { DSlice::from_slice_mut(slice) }) - } -} -impl<'a, T> DoubleEndedIterator for DeviceChunksMut<'a, T> { - #[inline] - fn next_back(&mut self) -> Option<&'a mut DSlice> { - self.0 - .next_back() - .map(|slice| unsafe { DSlice::from_slice_mut(slice) }) - } -} -impl<'a, T> ExactSizeIterator for DeviceChunksMut<'a, T> {} -impl<'a, T> FusedIterator for DeviceChunksMut<'a, T> {} - -macro_rules! impl_index { - ($($t:ty)*) => { - $( - impl Index<$t> for DSlice - { - type Output = DSlice; - - fn index(&self, index: $t) -> &Self { - unsafe { DSlice::from_slice(self.0.index(index)) } - } - } - - impl IndexMut<$t> for DSlice - { - fn index_mut(&mut self, index: $t) -> &mut Self { - unsafe { DSlice::from_slice_mut( self.0.index_mut(index)) } - } - } - )* - } -} -impl_index! { - Range - RangeFull - RangeFrom - RangeInclusive - RangeTo - RangeToInclusive -} -impl crate::private::Sealed for DSlice {} -impl + AsMut<[T]> + ?Sized> CopyDestination for DSlice { +impl crate::private::Sealed for DeviceSlice {} +impl + AsMut<[T]> + ?Sized> CopyDestination for DeviceSlice { fn copy_from(&mut self, val: &I) -> CudaResult<()> { let val = val.as_ref(); assert!( @@ -446,12 +230,8 @@ impl + AsMut<[T]> + ?Sized> CopyDestination for let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyHtoD_v2( - self.0.as_mut_ptr() as u64, - val.as_ptr() as *const c_void, - size, - ) - .to_result()? + cuda::cuMemcpyHtoD_v2(self.ptr.as_raw(), val.as_ptr() as *const c_void, size) + .to_result()? } } Ok(()) @@ -466,15 +246,15 @@ impl + AsMut<[T]> + ?Sized> CopyDestination for let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyDtoH_v2(val.as_mut_ptr() as *mut c_void, self.as_ptr() as u64, size) + cuda::cuMemcpyDtoH_v2(val.as_mut_ptr() as *mut c_void, self.as_device_ptr(), size) .to_result()? } } Ok(()) } } -impl CopyDestination> for DSlice { - fn copy_from(&mut self, val: &DSlice) -> CudaResult<()> { +impl CopyDestination> for DeviceSlice { + fn copy_from(&mut self, val: &DeviceSlice) -> CudaResult<()> { assert!( self.len() == val.len(), "destination and source slices have different lengths" @@ -482,14 +262,13 @@ impl CopyDestination> for DSlice { let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyDtoD_v2(self.0.as_mut_ptr() as u64, val.as_ptr() as u64, size) - .to_result()? + cuda::cuMemcpyDtoD_v2(self.ptr.as_raw(), val.as_device_ptr(), size).to_result()? } } Ok(()) } - fn copy_to(&self, val: &mut DSlice) -> CudaResult<()> { + fn copy_to(&self, val: &mut DeviceSlice) -> CudaResult<()> { assert!( self.len() == val.len(), "destination and source slices have different lengths" @@ -497,23 +276,25 @@ impl CopyDestination> for DSlice { let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyDtoD_v2(val.as_mut_ptr() as u64, self.as_ptr() as u64, size) + cuda::cuMemcpyDtoD_v2(val.as_device_ptr(), self.as_device_ptr(), size) .to_result()? } } Ok(()) } } -impl CopyDestination> for DSlice { - fn copy_from(&mut self, val: &DBuffer) -> CudaResult<()> { - self.copy_from(val as &DSlice) +impl CopyDestination> for DeviceSlice { + fn copy_from(&mut self, val: &DeviceBuffer) -> CudaResult<()> { + self.copy_from(val as &DeviceSlice) } - fn copy_to(&self, val: &mut DBuffer) -> CudaResult<()> { - self.copy_to(val as &mut DSlice) + fn copy_to(&self, val: &mut DeviceBuffer) -> CudaResult<()> { + self.copy_to(val as &mut DeviceSlice) } } -impl + AsMut<[T]> + ?Sized> AsyncCopyDestination for DSlice { +impl + AsMut<[T]> + ?Sized> AsyncCopyDestination + for DeviceSlice +{ unsafe fn async_copy_from(&mut self, val: &I, stream: &Stream) -> CudaResult<()> { let val = val.as_ref(); assert!( @@ -523,7 +304,7 @@ impl + AsMut<[T]> + ?Sized> AsyncCopyDestination let size = mem::size_of::() * self.len(); if size != 0 { cuda::cuMemcpyHtoDAsync_v2( - self.0.as_mut_ptr() as u64, + self.ptr.as_raw(), val.as_ptr() as *const c_void, size, stream.as_inner(), @@ -543,7 +324,7 @@ impl + AsMut<[T]> + ?Sized> AsyncCopyDestination if size != 0 { cuda::cuMemcpyDtoHAsync_v2( val.as_mut_ptr() as *mut c_void, - self.as_ptr() as u64, + self.as_device_ptr(), size, stream.as_inner(), ) @@ -552,8 +333,8 @@ impl + AsMut<[T]> + ?Sized> AsyncCopyDestination Ok(()) } } -impl AsyncCopyDestination> for DSlice { - unsafe fn async_copy_from(&mut self, val: &DSlice, stream: &Stream) -> CudaResult<()> { +impl AsyncCopyDestination> for DeviceSlice { + unsafe fn async_copy_from(&mut self, val: &DeviceSlice, stream: &Stream) -> CudaResult<()> { assert!( self.len() == val.len(), "destination and source slices have different lengths" @@ -561,8 +342,8 @@ impl AsyncCopyDestination> for DSlice { let size = mem::size_of::() * self.len(); if size != 0 { cuda::cuMemcpyDtoDAsync_v2( - self.0.as_mut_ptr() as u64, - val.as_ptr() as u64, + self.as_device_ptr(), + val.as_device_ptr(), size, stream.as_inner(), ) @@ -571,7 +352,7 @@ impl AsyncCopyDestination> for DSlice { Ok(()) } - unsafe fn async_copy_to(&self, val: &mut DSlice, stream: &Stream) -> CudaResult<()> { + unsafe fn async_copy_to(&self, val: &mut DeviceSlice, stream: &Stream) -> CudaResult<()> { assert!( self.len() == val.len(), "destination and source slices have different lengths" @@ -579,8 +360,8 @@ impl AsyncCopyDestination> for DSlice { let size = mem::size_of::() * self.len(); if size != 0 { cuda::cuMemcpyDtoDAsync_v2( - val.as_mut_ptr() as u64, - self.as_ptr() as u64, + val.as_device_ptr(), + self.as_device_ptr(), size, stream.as_inner(), ) @@ -589,12 +370,12 @@ impl AsyncCopyDestination> for DSlice { Ok(()) } } -impl AsyncCopyDestination> for DSlice { - unsafe fn async_copy_from(&mut self, val: &DBuffer, stream: &Stream) -> CudaResult<()> { - self.async_copy_from(val as &DSlice, stream) +impl AsyncCopyDestination> for DeviceSlice { + unsafe fn async_copy_from(&mut self, val: &DeviceBuffer, stream: &Stream) -> CudaResult<()> { + self.async_copy_from(val as &DeviceSlice, stream) } - unsafe fn async_copy_to(&self, val: &mut DBuffer, stream: &Stream) -> CudaResult<()> { - self.async_copy_to(val as &mut DSlice, stream) + unsafe fn async_copy_to(&self, val: &mut DeviceBuffer, stream: &Stream) -> CudaResult<()> { + self.async_copy_to(val as &mut DeviceSlice, stream) } } diff --git a/crates/cust/src/memory/malloc.rs b/crates/cust/src/memory/malloc.rs index b99fa3ae..29ecccbb 100644 --- a/crates/cust/src/memory/malloc.rs +++ b/crates/cust/src/memory/malloc.rs @@ -38,16 +38,15 @@ use std::ptr; /// cuda_free(device_buffer).unwrap(); /// } /// ``` -pub unsafe fn cuda_malloc(count: usize) -> CudaResult> { +pub unsafe fn cuda_malloc(count: usize) -> CudaResult> { let size = count.checked_mul(mem::size_of::()).unwrap_or(0); if size == 0 { return Err(CudaError::InvalidMemoryAllocation); } - let mut ptr: *mut c_void = ptr::null_mut(); - cuda::cuMemAlloc_v2(&mut ptr as *mut *mut c_void as *mut u64, size).to_result()?; - let ptr = ptr as *mut T; - Ok(DevicePointer::wrap(ptr as *mut T)) + let mut ptr = 0; + cuda::cuMemAlloc_v2(&mut ptr, size).to_result()?; + Ok(DevicePointer::from_raw(ptr)) } /// Unsafe wrapper around the `cuMemAllocManaged` function, which allocates some unified memory and @@ -123,13 +122,12 @@ pub unsafe fn cuda_malloc_unified(count: usize) -> CudaResult(mut p: DevicePointer) -> CudaResult<()> { - let ptr = p.as_raw_mut(); +pub unsafe fn cuda_free(ptr: DevicePointer) -> CudaResult<()> { if ptr.is_null() { return Err(CudaError::InvalidMemoryAllocation); } - cuda::cuMemFree_v2(ptr as u64).to_result()?; + cuda::cuMemFree_v2(ptr.as_raw()).to_result()?; Ok(()) } @@ -342,11 +340,10 @@ mod test { #[test] fn test_cuda_free_null() { let _context = crate::quick_init().unwrap(); - let null = ::std::ptr::null_mut::(); unsafe { assert_eq!( CudaError::InvalidMemoryAllocation, - cuda_free(DevicePointer::wrap(null)).unwrap_err() + cuda_free(DevicePointer::::null()).unwrap_err() ); } } diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 5ad71424..21845a18 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -101,13 +101,13 @@ pub trait GpuBuffer: private::Sealed { fn len(&self) -> usize; } -impl GpuBuffer for DBuffer { +impl GpuBuffer for DeviceBuffer { unsafe fn as_device_ptr(&self) -> DevicePointer { - DevicePointer::wrap((**self).as_ptr() as *mut _) + self.as_ptr() } fn as_device_ptr_mut(&mut self) -> DevicePointer { - (**self).as_device_ptr() + self.as_mut_ptr() } fn len(&self) -> usize { @@ -117,12 +117,12 @@ impl GpuBuffer for DBuffer { impl GpuBuffer for UnifiedBuffer { unsafe fn as_device_ptr(&self) -> DevicePointer { - DevicePointer::wrap(self.as_ptr() as *mut _) + DevicePointer::from_raw(self.as_ptr() as u64) } fn as_device_ptr_mut(&mut self) -> DevicePointer { // SAFETY: unified pointers can be dereferenced from the gpu. - unsafe { DevicePointer::wrap(self.as_ptr() as *mut _) } + DevicePointer::from_raw(self.as_ptr() as u64) } fn len(&self) -> usize { @@ -139,35 +139,35 @@ pub trait GpuBox: private::Sealed { fn as_device_ptr_mut(&mut self) -> DevicePointer; } -impl GpuBox for DBox { +impl GpuBox for DeviceBox { unsafe fn as_device_ptr(&self) -> DevicePointer { self.ptr } fn as_device_ptr_mut(&mut self) -> DevicePointer { - DBox::as_device_ptr(self) + DeviceBox::as_device_ptr(self) } } impl GpuBox for UnifiedBox { unsafe fn as_device_ptr(&self) -> DevicePointer { - DevicePointer::wrap(self.ptr.as_raw() as *mut _) + DevicePointer::from_raw(self.ptr.as_raw() as u64) } fn as_device_ptr_mut(&mut self) -> DevicePointer { // SAFETY: unified pointers can be dereferenced from the gpu. - unsafe { DevicePointer::wrap(self.ptr.as_raw() as *mut _) } + DevicePointer::from_raw(self.ptr.as_raw() as u64) } } mod private { - use super::{DBox, DBuffer, DeviceCopy, UnifiedBox, UnifiedBuffer}; + use super::{DeviceBox, DeviceBuffer, DeviceCopy, UnifiedBox, UnifiedBuffer}; pub trait Sealed {} impl Sealed for UnifiedBuffer {} - impl Sealed for DBuffer {} + impl Sealed for DeviceBuffer {} impl Sealed for UnifiedBox {} - impl Sealed for DBox {} + impl Sealed for DeviceBox {} } /// Marker trait for types which can safely be copied to or from a CUDA device. diff --git a/crates/cust/src/memory/pointer.rs b/crates/cust/src/memory/pointer.rs index e36762ca..0de4fb40 100644 --- a/crates/cust/src/memory/pointer.rs +++ b/crates/cust/src/memory/pointer.rs @@ -1,60 +1,13 @@ use crate::memory::DeviceCopy; +use cust_raw::CUdeviceptr; use core::{ - cmp::Ordering, fmt::{self, Debug, Pointer}, - hash::{Hash, Hasher}, + hash::Hash, ptr, }; - -macro_rules! derive_traits { - ( $( $Ptr:ty )* ) => ($( - impl Debug for $Ptr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Debug::fmt(&self.0, f) - } - } - impl Pointer for $Ptr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Pointer::fmt(&self.0, f) - } - } - - impl Hash for $Ptr { - fn hash(&self, h: &mut H) { - Hash::hash(&self.0, h); - } - } - - impl PartialEq for $Ptr { - fn eq(&self, other: &$Ptr) -> bool { - PartialEq::eq(&self.0, &other.0) - } - } - - impl Eq for $Ptr {} - - impl PartialOrd for $Ptr { - fn partial_cmp(&self, other: &$Ptr) -> Option { - PartialOrd::partial_cmp(&self.0, &other.0) - } - } - - impl Ord for $Ptr { - fn cmp(&self, other: &$Ptr) -> Ordering { - Ord::cmp(&self.0, &other.0) - } - } - - impl Clone for $Ptr { - fn clone(&self) -> Self { - Self(self.0) - } - } - impl Copy for $Ptr {} - )*) -} -derive_traits!(DevicePointer UnifiedPointer); +use std::ffi::c_void; +use std::marker::PhantomData; /// A pointer to device memory. /// @@ -68,68 +21,33 @@ derive_traits!(DevicePointer UnifiedPointer); /// the other side of that boundary does not attempt to dereference the pointer on the CPU. It is /// thus possible to pass a `DevicePointer` to a CUDA kernel written in C. #[repr(transparent)] -pub struct DevicePointer(*mut T); +#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct DevicePointer { + ptr: CUdeviceptr, + marker: PhantomData<*mut T>, +} -unsafe impl DeviceCopy for DevicePointer {} +unsafe impl DeviceCopy for DevicePointer {} -impl DevicePointer { - /// Wrap the given raw pointer in a DevicePointer. The given pointer is assumed to be a valid, - /// device pointer or null. - /// - /// # Safety - /// - /// The given pointer must have been allocated with [`cuda_malloc`](fn.cuda_malloc.html) or - /// be null. - /// - /// # Examples - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// use std::ptr; - /// unsafe { - /// let null : *mut u64 = ptr::null_mut(); - /// assert!(DevicePointer::wrap(null).is_null()); - /// } - /// ``` - pub unsafe fn wrap(ptr: *mut T) -> Self { - DevicePointer(ptr) +impl Pointer for DevicePointer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let ptr = self.ptr as *const c_void; + fmt::Pointer::fmt(&ptr, f) } +} - /// Returns the contained pointer as a raw pointer. The returned pointer is not valid on the CPU - /// and must not be dereferenced. - /// - /// # Examples - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// unsafe { - /// let dev_ptr = cuda_malloc::(1).unwrap(); - /// let ptr: *const u64 = dev_ptr.as_raw(); - /// cuda_free(dev_ptr); - /// } - /// ``` - pub fn as_raw(self) -> *const T { - self.0 +impl DevicePointer { + /// Returns the contained CUdeviceptr. + pub fn as_raw(&self) -> CUdeviceptr { + self.ptr } - /// Returns the contained pointer as a mutable raw pointer. The returned pointer is not valid on the CPU - /// and must not be dereferenced. - /// - /// # Examples - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// unsafe { - /// let mut dev_ptr = cuda_malloc::(1).unwrap(); - /// let ptr: *mut u64 = dev_ptr.as_raw_mut(); - /// cuda_free(dev_ptr); - /// } - /// ``` - pub fn as_raw_mut(&mut self) -> *mut T { - self.0 + /// Create a DevicePointer from a raw CUDA pointer + pub fn from_raw(ptr: CUdeviceptr) -> Self { + Self { + ptr, + marker: PhantomData, + } } /// Returns true if the pointer is null. @@ -145,26 +63,23 @@ impl DevicePointer { /// } /// ``` pub fn is_null(self) -> bool { - self.0.is_null() + self.ptr == 0 } /// Returns a null device pointer. /// - /// # Examples: - /// - /// ``` - /// # let _context = cust::quick_init().unwrap(); - /// use cust::memory::*; - /// let ptr : DevicePointer = DevicePointer::null(); - /// assert!(ptr.is_null()); - /// ``` + // TODO (AL): do we even want this? pub fn null() -> Self where T: Sized, { - unsafe { Self::wrap(ptr::null_mut()) } + Self { + ptr: 0, + marker: PhantomData, + } } + /* TODO (AL): see if we need to reimplement these /// Calculates the offset from a device pointer. /// /// `count` is in units of T; eg. a `count` of 3 represents a pointer offset of @@ -387,6 +302,7 @@ impl DevicePointer { { self.wrapping_offset((count as isize).wrapping_neg()) } + */ } /// A pointer to unified memory. @@ -400,11 +316,18 @@ impl DevicePointer { /// `UnifiedPointer` through an FFI boundary to C code expecting a `*mut T`. It is /// thus possible to pass a `UnifiedPointer` to a CUDA kernel written in C. #[repr(transparent)] -pub struct UnifiedPointer(*mut T); +#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct UnifiedPointer(*mut T); unsafe impl DeviceCopy for UnifiedPointer {} -impl UnifiedPointer { +impl Pointer for UnifiedPointer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Pointer::fmt(&self.0, f) + } +} + +impl UnifiedPointer { /// Wrap the given raw pointer in a UnifiedPointer. The given pointer is assumed to be a valid, /// unified-memory pointer or null. /// diff --git a/crates/cust/src/module.rs b/crates/cust/src/module.rs index aee17a9d..2400b3b4 100644 --- a/crates/cust/src/module.rs +++ b/crates/cust/src/module.rs @@ -266,12 +266,8 @@ impl<'a, T: DeviceCopy> CopyDestination for Symbol<'a, T> { let size = mem::size_of::(); if size != 0 { unsafe { - cuda::cuMemcpyHtoD_v2( - self.ptr.as_raw_mut() as u64, - val as *const T as *const c_void, - size, - ) - .to_result()? + cuda::cuMemcpyHtoD_v2(self.ptr.as_raw(), val as *const T as *const c_void, size) + .to_result()? } } Ok(()) diff --git a/crates/cust/src/prelude.rs b/crates/cust/src/prelude.rs index 85301945..83579de2 100644 --- a/crates/cust/src/prelude.rs +++ b/crates/cust/src/prelude.rs @@ -6,7 +6,7 @@ pub use crate::context::{Context, ContextFlags}; pub use crate::device::Device; pub use crate::launch; -pub use crate::memory::{CopyDestination, DBuffer, UnifiedBuffer}; +pub use crate::memory::{CopyDestination, DeviceBuffer, UnifiedBuffer}; pub use crate::module::Module; pub use crate::stream::{Stream, StreamFlags}; pub use crate::util::*; diff --git a/crates/cust/src/util.rs b/crates/cust/src/util.rs index 50d48856..91e3f5fc 100644 --- a/crates/cust/src/util.rs +++ b/crates/cust/src/util.rs @@ -2,17 +2,17 @@ use crate::{ error::CudaResult, memory::{ array::{ArrayObject, ArrayPrimitive}, - DBox, DeviceCopy, UnifiedBuffer, + DeviceBox, DeviceCopy, UnifiedBuffer, }, - prelude::DBuffer, + prelude::DeviceBuffer, surface::Surface, texture::Texture, }; pub trait DeviceCopyExt: DeviceCopy { /// Makes a new [`DBox`] from this value. - fn as_dbox(&self) -> CudaResult> { - DBox::new(self) + fn as_dbox(&self) -> CudaResult> { + DeviceBox::new(self) } } @@ -21,7 +21,7 @@ impl DeviceCopyExt for T {} /// Utilities for slices and slice-like things such as arrays. pub trait SliceExt { /// Allocate memory on the GPU and convert this slice into a DBuffer. - fn as_dbuf(&self) -> CudaResult>; + fn as_dbuf(&self) -> CudaResult>; /// Convert this slice to a [`UnifiedBuffer`] which can be accessed from the CPU and GPU /// without conversions back and forth. fn as_unified_buf(&self) -> CudaResult>; @@ -64,8 +64,8 @@ pub trait SliceExt { } impl SliceExt for &[T] { - fn as_dbuf(&self) -> CudaResult> { - DBuffer::from_slice(*self) + fn as_dbuf(&self) -> CudaResult> { + DeviceBuffer::from_slice(*self) } fn as_unified_buf(&self) -> CudaResult> { @@ -92,8 +92,8 @@ impl SliceExt for &[T] { } impl SliceExt for [T; N] { - fn as_dbuf(&self) -> CudaResult> { - DBuffer::from_slice(self) + fn as_dbuf(&self) -> CudaResult> { + DeviceBuffer::from_slice(self) } fn as_unified_buf(&self) -> CudaResult> { diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 3ee0d595..8ff3be3d 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -8,7 +8,9 @@ use std::{ use cust::{ error::CudaResult, - memory::{DBox, DBuffer, DeviceCopy, DevicePointer, GpuBox, GpuBuffer, UnifiedBuffer}, + memory::{ + DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer, GpuBox, GpuBuffer, UnifiedBuffer, + }, prelude::Stream, }; @@ -98,13 +100,13 @@ impl DenoiserSizes { // we keep track of state we allocated to safety-check invocations of the denoiser. #[derive(Debug)] struct InternalDenoiserState { - state: DBuffer, + state: DeviceBuffer, width: u32, height: u32, _tiled: bool, // we handle scratch memory internally currently, so its fine // to drop it when we are done. - scratch: DBuffer, + scratch: DeviceBuffer, } /// High level wrapper for OptiX's GPU-accelerated AI image denoiser. @@ -208,12 +210,12 @@ impl Denoiser { }; // SAFETY: OptiX will write to this and we never read it or expose the buffer. - let scratch = unsafe { DBuffer::::uninitialized(scratch_size) }?; + let scratch = unsafe { DeviceBuffer::::uninitialized(scratch_size) }?; let state_size = sizes.state_size_in_bytes; // SAFETY: OptiX will write into this, its just temporary alloc. - let state = unsafe { DBuffer::::uninitialized(state_size) }?; + let state = unsafe { DeviceBuffer::::uninitialized(state_size) }?; unsafe { optix_call!(optixDenoiserSetup( @@ -372,7 +374,7 @@ impl Denoiser { let raw_params = parameters.to_raw(); let mut out = input_image.to_raw(); - out.data = out_buffer.as_device_ptr_mut().as_raw_mut() as u64; + out.data = out_buffer.as_device_ptr_mut().as_raw() as u64; let layer = sys::OptixDenoiserLayer { input: input_image.to_raw(), @@ -387,14 +389,14 @@ impl Denoiser { self.raw, stream.as_inner(), &raw_params as *const _, - state.state.as_device_ptr().as_raw_mut() as u64, + state.state.as_device_ptr().as_raw() as u64, state.state.len(), &cloned as *const _, &layer as *const _, 1, // num-layers 0, // offsetX 0, // offsetY - state.scratch.as_device_ptr().as_raw_mut() as u64, + state.scratch.as_device_ptr().as_raw() as u64, state.scratch.len() ))?; } @@ -410,13 +412,13 @@ pub struct DenoiserParams<'a> { pub denoise_alpha: bool, /// Average log intensity of the input image. If `None`, then denoised results will not be /// optimal for very dark or bright input images. - pub hdr_intensity: Option<&'a DBox>, + pub hdr_intensity: Option<&'a DeviceBox>, /// How much of the denoised image to blend into the final image. If set to `1.0`, then the output /// image will be composed of 100% the noisy output. If set to `0.0`, the output will be 100% of the denoised input. /// Linearly interpolates for other values. pub blend_factor: f32, /// Used for AOV models, the average log color of the input image, separate for RGB channels. - pub hdr_average_color: Option<&'a DBox<[f32; 3]>>, + pub hdr_average_color: Option<&'a DeviceBox<[f32; 3]>>, } impl DenoiserParams<'_> { @@ -561,7 +563,7 @@ impl<'a> Image<'a> { buffer: unsafe { // SAFETY: this buffer is never written to for the duration of this image being alive. // And we know the buffer is large enough to be reinterpreted as a buffer of bytes. - DevicePointer::wrap(buffer.as_device_ptr().as_raw_mut() as *mut u8) + DevicePointer::from_raw(buffer.as_device_ptr().as_raw()) }, buffer_size: buffer.len() * std::mem::size_of::(), format, diff --git a/examples/optix/denoiser/src/main.rs b/examples/optix/denoiser/src/main.rs index e457a72c..59c860f8 100644 --- a/examples/optix/denoiser/src/main.rs +++ b/examples/optix/denoiser/src/main.rs @@ -1,4 +1,4 @@ -use cust::memory::DBuffer; +use cust::memory::DeviceBuffer; use cust::prelude::{Stream, StreamFlags}; use cust::util::SliceExt; use cust::vek::{Clamp, Vec3}; @@ -63,7 +63,7 @@ fn main() -> Result<(), Box> { // Currently zeroed is unsafe, but in the future we will probably expose a safe way to do it // using bytemuck - let mut out_buf = unsafe { DBuffer::>::zeroed((width * height) as usize)? }; + let mut out_buf = unsafe { DeviceBuffer::>::zeroed((width * height) as usize)? }; // make an image to tell OptiX about how our image buffer is represented let input_image = Image::new(&in_buf, ImageFormat::Float3, width, height); From 970af09e8c406dd700ec4155482d755260423e73 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 08:09:48 +1300 Subject: [PATCH 022/100] wip --- crates/optix/Cargo.toml | 8 +- crates/optix/build.rs | 98 ++ crates/{optix_sys => optix}/optix_stubs.c | 1 + .../optix.rs => optix/optix_wrapper.rs} | 915 ++++++++---------- crates/optix/src/context.rs | 2 +- crates/optix/src/denoiser.rs | 4 +- crates/optix/src/lib.rs | 3 +- crates/optix/src/module.rs | 25 + crates/optix/src/optix_wrapper.h | 27 + crates/optix/src/sys.rs | 64 ++ crates/optix_sys/Cargo.toml | 11 - crates/optix_sys/build.rs | 32 - crates/optix_sys/src/lib.rs | 10 - 13 files changed, 636 insertions(+), 564 deletions(-) create mode 100644 crates/optix/build.rs rename crates/{optix_sys => optix}/optix_stubs.c (99%) rename crates/{optix_sys/optix.rs => optix/optix_wrapper.rs} (60%) create mode 100644 crates/optix/src/module.rs create mode 100644 crates/optix/src/optix_wrapper.h create mode 100644 crates/optix/src/sys.rs delete mode 100644 crates/optix_sys/Cargo.toml delete mode 100644 crates/optix_sys/build.rs delete mode 100644 crates/optix_sys/src/lib.rs diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index a6ea88c8..7bf0ce70 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -4,5 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] -optix_sys = { version = "0.1", path = "../optix_sys" } cust = { version = "0.1", path = "../cust" } +cust_raw = { version = "0.11.2", path = "../cust_raw" } + +[build-dependencies] +bindgen = "0.55" +cc = "1.0.71" +find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } + diff --git a/crates/optix/build.rs b/crates/optix/build.rs new file mode 100644 index 00000000..d4ad5e20 --- /dev/null +++ b/crates/optix/build.rs @@ -0,0 +1,98 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; +use std::env; +use std::path::{Path, PathBuf}; + +// OptiX is a bit exotic in how it provides its functions. It uses a function table +// approach, a function table struct holds function pointers to every optix function. Then +// the Optix driver dll is loaded at runtime and the function table is loaded from that. +// OptiX provides this logic inside optix_stubs.h in the include dir, so we need to compile that +// to a lib and link it in so that we have the initialization and C function logic. +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + cuda_include = cuda_include.join("include"); + + bindgen_optix(&optix_include, &cuda_include); + + cc::Build::new() + .file("./optix_stubs.c") + .include(optix_include) + .include(cuda_include) + .cpp(false) + .compile("optix_stubs"); + + println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-lib=static=optix_stubs"); +} + +fn bindgen_optix(optix_include: &Path, cuda_include: &Path) { + let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("optix_wrapper.rs"); + + let header_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src") + .join("optix_wrapper.h"); + + let this_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("build.rs"); + + println!("cargo:rerun-if-changed={}", header_path.display()); + println!("cargo:rerun-if-changed={}", this_path.display()); + + let bindings = bindgen::Builder::default() + .header("src/optix_wrapper.h") + .clang_arg(format!("-I{}", optix_include.display())) + .clang_arg(format!("-I{}", cuda_include.display())) + .whitelist_recursively(false) + .whitelist_type("Optix.*") + .whitelist_type("RaygenRecord") + .whitelist_type("MissRecord") + .whitelist_type("HitgroupRecord") + .blacklist_type("OptixBuildInput") + .whitelist_function("optix.*") + .whitelist_var("OptixSbtRecordHeaderSize") + .whitelist_var("OptixSbtRecordAlignment") + .whitelist_var("OptixAccelBufferByteAlignment") + .whitelist_var("OptixInstanceByteAlignment") + .whitelist_var("OptixAabbBufferByteAlignment") + .whitelist_var("OptixGeometryTransformByteAlignment") + .whitelist_var("OptixTransformByteAlignment") + .whitelist_var("OptixVersion") + .whitelist_var("OptixBuildInputSize") + .layout_tests(false) + .generate_comments(false) + .newtype_enum("OptixResult") + .constified_enum_module("OptixCompileOptimizationLevel") + .constified_enum_module("OptixCompileDebugLevel") + .constified_enum_module("OptixTraversableGraphFlags") + .constified_enum_module("OptixExceptionFlags") + .constified_enum_module("OptixProgramGroupKind") + .constified_enum_module("OptixDeviceProperty") + .constified_enum_module("OptixPixelFormat") + .constified_enum_module("OptixDenoiserModelKind") + .rustified_enum("GeometryFlags") + .rustified_enum("OptixGeometryFlags") + .constified_enum("OptixVertexFormat") + .constified_enum("OptixIndicesFormat") + .rust_target(bindgen::RustTarget::Nightly) + .rustfmt_bindings(true) + .generate() + .expect("Unable to generate optix bindings"); + + let dbg_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + bindings + .write_to_file(dbg_path.join("optix_wrapper.rs")) + .expect("Couldn't write bindings!"); + + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} diff --git a/crates/optix_sys/optix_stubs.c b/crates/optix/optix_stubs.c similarity index 99% rename from crates/optix_sys/optix_stubs.c rename to crates/optix/optix_stubs.c index 01529541..3325d867 100644 --- a/crates/optix_sys/optix_stubs.c +++ b/crates/optix/optix_stubs.c @@ -603,3 +603,4 @@ extern "C" #ifdef __cplusplus } #endif + diff --git a/crates/optix_sys/optix.rs b/crates/optix/optix_wrapper.rs similarity index 60% rename from crates/optix_sys/optix.rs rename to crates/optix/optix_wrapper.rs index 2643576e..60ae295c 100644 --- a/crates/optix_sys/optix.rs +++ b/crates/optix/optix_wrapper.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.58.1 */ +/* automatically generated by rust-bindgen 0.55.1 */ #[repr(C)] pub struct __BindgenUnionField(::std::marker::PhantomData); @@ -43,16 +43,6 @@ impl ::std::cmp::PartialEq for __BindgenUnionField { } } impl ::std::cmp::Eq for __BindgenUnionField {} -pub const OPTIX_VERSION: u32 = 70300; -pub const OPTIX_SBT_RECORD_ALIGNMENT: u32 = 16; -pub const OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT: u32 = 128; -pub const OPTIX_INSTANCE_BYTE_ALIGNMENT: u32 = 16; -pub const OPTIX_AABB_BUFFER_BYTE_ALIGNMENT: u32 = 8; -pub const OPTIX_GEOMETRY_TRANSFORM_BYTE_ALIGNMENT: u32 = 16; -pub const OPTIX_TRANSFORM_BYTE_ALIGNMENT: u32 = 64; -pub const OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT: u32 = 0; -pub const OPTIX_COMPILE_DEFAULT_MAX_PAYLOAD_VALUE_COUNT: u32 = 8; -pub const OPTIX_ABI_VERSION: u32 = 47; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct OptixDeviceContext_t { @@ -85,59 +75,131 @@ pub struct OptixDenoiser_t { pub type OptixDenoiser = *mut OptixDenoiser_t; pub type OptixTraversableHandle = ::std::os::raw::c_ulonglong; pub type OptixVisibilityMask = ::std::os::raw::c_uint; -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixResult { - OPTIX_SUCCESS = 0, - OPTIX_ERROR_INVALID_VALUE = 7001, - OPTIX_ERROR_HOST_OUT_OF_MEMORY = 7002, - OPTIX_ERROR_INVALID_OPERATION = 7003, - OPTIX_ERROR_FILE_IO_ERROR = 7004, - OPTIX_ERROR_INVALID_FILE_FORMAT = 7005, - OPTIX_ERROR_DISK_CACHE_INVALID_PATH = 7010, - OPTIX_ERROR_DISK_CACHE_PERMISSION_ERROR = 7011, - OPTIX_ERROR_DISK_CACHE_DATABASE_ERROR = 7012, - OPTIX_ERROR_DISK_CACHE_INVALID_DATA = 7013, - OPTIX_ERROR_LAUNCH_FAILURE = 7050, - OPTIX_ERROR_INVALID_DEVICE_CONTEXT = 7051, - OPTIX_ERROR_CUDA_NOT_INITIALIZED = 7052, - OPTIX_ERROR_VALIDATION_FAILURE = 7053, - OPTIX_ERROR_INVALID_PTX = 7200, - OPTIX_ERROR_INVALID_LAUNCH_PARAMETER = 7201, - OPTIX_ERROR_INVALID_PAYLOAD_ACCESS = 7202, - OPTIX_ERROR_INVALID_ATTRIBUTE_ACCESS = 7203, - OPTIX_ERROR_INVALID_FUNCTION_USE = 7204, - OPTIX_ERROR_INVALID_FUNCTION_ARGUMENTS = 7205, - OPTIX_ERROR_PIPELINE_OUT_OF_CONSTANT_MEMORY = 7250, - OPTIX_ERROR_PIPELINE_LINK_ERROR = 7251, - OPTIX_ERROR_ILLEGAL_DURING_TASK_EXECUTE = 7270, - OPTIX_ERROR_INTERNAL_COMPILER_ERROR = 7299, - OPTIX_ERROR_DENOISER_MODEL_NOT_SET = 7300, - OPTIX_ERROR_DENOISER_NOT_INITIALIZED = 7301, - OPTIX_ERROR_ACCEL_NOT_COMPATIBLE = 7400, - OPTIX_ERROR_NOT_SUPPORTED = 7800, - OPTIX_ERROR_UNSUPPORTED_ABI_VERSION = 7801, - OPTIX_ERROR_FUNCTION_TABLE_SIZE_MISMATCH = 7802, - OPTIX_ERROR_INVALID_ENTRY_FUNCTION_OPTIONS = 7803, - OPTIX_ERROR_LIBRARY_NOT_FOUND = 7804, - OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND = 7805, - OPTIX_ERROR_LIBRARY_UNLOAD_FAILURE = 7806, - OPTIX_ERROR_CUDA_ERROR = 7900, - OPTIX_ERROR_INTERNAL_ERROR = 7990, - OPTIX_ERROR_UNKNOWN = 7999, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixDeviceProperty { - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH = 8193, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH = 8194, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS = 8195, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS = 8196, - OPTIX_DEVICE_PROPERTY_RTCORE_VERSION = 8197, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID = 8198, - OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK = 8199, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS = 8200, - OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET = 8201, +impl OptixResult { + pub const OPTIX_SUCCESS: OptixResult = OptixResult(0); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_VALUE: OptixResult = OptixResult(7001); +} +impl OptixResult { + pub const OPTIX_ERROR_HOST_OUT_OF_MEMORY: OptixResult = OptixResult(7002); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_OPERATION: OptixResult = OptixResult(7003); +} +impl OptixResult { + pub const OPTIX_ERROR_FILE_IO_ERROR: OptixResult = OptixResult(7004); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_FILE_FORMAT: OptixResult = OptixResult(7005); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_INVALID_PATH: OptixResult = OptixResult(7010); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_PERMISSION_ERROR: OptixResult = OptixResult(7011); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_DATABASE_ERROR: OptixResult = OptixResult(7012); +} +impl OptixResult { + pub const OPTIX_ERROR_DISK_CACHE_INVALID_DATA: OptixResult = OptixResult(7013); +} +impl OptixResult { + pub const OPTIX_ERROR_LAUNCH_FAILURE: OptixResult = OptixResult(7050); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_DEVICE_CONTEXT: OptixResult = OptixResult(7051); +} +impl OptixResult { + pub const OPTIX_ERROR_CUDA_NOT_INITIALIZED: OptixResult = OptixResult(7052); +} +impl OptixResult { + pub const OPTIX_ERROR_VALIDATION_FAILURE: OptixResult = OptixResult(7053); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_PTX: OptixResult = OptixResult(7200); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_LAUNCH_PARAMETER: OptixResult = OptixResult(7201); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_PAYLOAD_ACCESS: OptixResult = OptixResult(7202); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_ATTRIBUTE_ACCESS: OptixResult = OptixResult(7203); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_FUNCTION_USE: OptixResult = OptixResult(7204); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_FUNCTION_ARGUMENTS: OptixResult = OptixResult(7205); +} +impl OptixResult { + pub const OPTIX_ERROR_PIPELINE_OUT_OF_CONSTANT_MEMORY: OptixResult = OptixResult(7250); +} +impl OptixResult { + pub const OPTIX_ERROR_PIPELINE_LINK_ERROR: OptixResult = OptixResult(7251); +} +impl OptixResult { + pub const OPTIX_ERROR_ILLEGAL_DURING_TASK_EXECUTE: OptixResult = OptixResult(7270); +} +impl OptixResult { + pub const OPTIX_ERROR_INTERNAL_COMPILER_ERROR: OptixResult = OptixResult(7299); +} +impl OptixResult { + pub const OPTIX_ERROR_DENOISER_MODEL_NOT_SET: OptixResult = OptixResult(7300); +} +impl OptixResult { + pub const OPTIX_ERROR_DENOISER_NOT_INITIALIZED: OptixResult = OptixResult(7301); +} +impl OptixResult { + pub const OPTIX_ERROR_ACCEL_NOT_COMPATIBLE: OptixResult = OptixResult(7400); +} +impl OptixResult { + pub const OPTIX_ERROR_NOT_SUPPORTED: OptixResult = OptixResult(7800); +} +impl OptixResult { + pub const OPTIX_ERROR_UNSUPPORTED_ABI_VERSION: OptixResult = OptixResult(7801); +} +impl OptixResult { + pub const OPTIX_ERROR_FUNCTION_TABLE_SIZE_MISMATCH: OptixResult = OptixResult(7802); +} +impl OptixResult { + pub const OPTIX_ERROR_INVALID_ENTRY_FUNCTION_OPTIONS: OptixResult = OptixResult(7803); +} +impl OptixResult { + pub const OPTIX_ERROR_LIBRARY_NOT_FOUND: OptixResult = OptixResult(7804); +} +impl OptixResult { + pub const OPTIX_ERROR_ENTRY_SYMBOL_NOT_FOUND: OptixResult = OptixResult(7805); +} +impl OptixResult { + pub const OPTIX_ERROR_LIBRARY_UNLOAD_FAILURE: OptixResult = OptixResult(7806); +} +impl OptixResult { + pub const OPTIX_ERROR_CUDA_ERROR: OptixResult = OptixResult(7900); +} +impl OptixResult { + pub const OPTIX_ERROR_INTERNAL_ERROR: OptixResult = OptixResult(7990); +} +impl OptixResult { + pub const OPTIX_ERROR_UNKNOWN: OptixResult = OptixResult(7999); +} +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct OptixResult(pub ::std::os::raw::c_uint); +pub mod OptixDeviceProperty { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH: Type = 8193; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH: Type = 8194; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS: Type = 8195; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCES_PER_IAS: Type = 8196; + pub const OPTIX_DEVICE_PROPERTY_RTCORE_VERSION: Type = 8197; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_INSTANCE_ID: Type = 8198; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_NUM_BITS_INSTANCE_VISIBILITY_MASK: Type = 8199; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_RECORDS_PER_GAS: Type = 8200; + pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_SBT_OFFSET: Type = 8201; } pub type OptixLogCallback = ::std::option::Option< unsafe extern "C" fn( @@ -147,62 +209,37 @@ pub type OptixLogCallback = ::std::option::Option< cbdata: *mut ::std::os::raw::c_void, ), >; -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixDeviceContextValidationMode { - OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_OFF = 0, - OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL = -1, -} +pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_OFF: + OptixDeviceContextValidationMode = 0; +pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL: + OptixDeviceContextValidationMode = 4294967295; +pub type OptixDeviceContextValidationMode = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixDeviceContextOptions { pub logCallbackFunction: OptixLogCallback, pub logCallbackData: *mut ::std::os::raw::c_void, pub logCallbackLevel: ::std::os::raw::c_int, pub validationMode: OptixDeviceContextValidationMode, } -impl Default for OptixDeviceContextOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixGeometryFlags { - OPTIX_GEOMETRY_FLAG_NONE = 0, - OPTIX_GEOMETRY_FLAG_DISABLE_ANYHIT = 1, - OPTIX_GEOMETRY_FLAG_REQUIRE_SINGLE_ANYHIT_CALL = 2, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixHitKind { - OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE = 254, - OPTIX_HIT_KIND_TRIANGLE_BACK_FACE = 255, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixIndicesFormat { - OPTIX_INDICES_FORMAT_NONE = 0, - OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3 = 8450, - OPTIX_INDICES_FORMAT_UNSIGNED_INT3 = 8451, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixVertexFormat { - OPTIX_VERTEX_FORMAT_NONE = 0, - OPTIX_VERTEX_FORMAT_FLOAT3 = 8481, - OPTIX_VERTEX_FORMAT_FLOAT2 = 8482, - OPTIX_VERTEX_FORMAT_HALF3 = 8483, - OPTIX_VERTEX_FORMAT_HALF2 = 8484, - OPTIX_VERTEX_FORMAT_SNORM16_3 = 8485, - OPTIX_VERTEX_FORMAT_SNORM16_2 = 8486, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTransformFormat { - OPTIX_TRANSFORM_FORMAT_NONE = 0, - OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 = 8673, -} +pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE: OptixHitKind = 254; +pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_BACK_FACE: OptixHitKind = 255; +pub type OptixHitKind = ::std::os::raw::c_uint; +pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE: OptixIndicesFormat = 0; +pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3: OptixIndicesFormat = 8450; +pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3: OptixIndicesFormat = 8451; +pub type OptixIndicesFormat = ::std::os::raw::c_uint; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_NONE: OptixVertexFormat = 0; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT3: OptixVertexFormat = 8481; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT2: OptixVertexFormat = 8482; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF3: OptixVertexFormat = 8483; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF2: OptixVertexFormat = 8484; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_3: OptixVertexFormat = 8485; +pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_2: OptixVertexFormat = 8486; +pub type OptixVertexFormat = ::std::os::raw::c_uint; +pub const OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE: OptixTransformFormat = 0; +pub const OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12: OptixTransformFormat = 8673; +pub type OptixTransformFormat = ::std::os::raw::c_uint; #[repr(C)] pub struct OptixBuildInputTriangleArray { pub vertexBuffers: *const CUdeviceptr, @@ -222,29 +259,23 @@ pub struct OptixBuildInputTriangleArray { pub primitiveIndexOffset: ::std::os::raw::c_uint, pub transformFormat: OptixTransformFormat, } -impl Default for OptixBuildInputTriangleArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixPrimitiveType { - OPTIX_PRIMITIVE_TYPE_CUSTOM = 9472, - OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE = 9473, - OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE = 9474, - OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR = 9475, - OPTIX_PRIMITIVE_TYPE_TRIANGLE = 9521, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixPrimitiveTypeFlags { - OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM = 1, - OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE = 2, - OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE = 4, - OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR = 8, - OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE = -2147483648, -} +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_CUSTOM: OptixPrimitiveType = 9472; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE: OptixPrimitiveType = + 9473; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE: OptixPrimitiveType = 9474; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR: OptixPrimitiveType = 9475; +pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_TRIANGLE: OptixPrimitiveType = 9521; +pub type OptixPrimitiveType = ::std::os::raw::c_uint; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM: OptixPrimitiveTypeFlags = 1; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE: + OptixPrimitiveTypeFlags = 2; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE: + OptixPrimitiveTypeFlags = 4; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR: OptixPrimitiveTypeFlags = + 8; +pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE: OptixPrimitiveTypeFlags = + -2147483648; +pub type OptixPrimitiveTypeFlags = ::std::os::raw::c_int; #[repr(C)] pub struct OptixBuildInputCurveArray { pub curveType: OptixPrimitiveType, @@ -261,13 +292,8 @@ pub struct OptixBuildInputCurveArray { pub flag: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } -impl Default for OptixBuildInputCurveArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixAabb { pub minX: f32, pub minY: f32, @@ -288,35 +314,17 @@ pub struct OptixBuildInputCustomPrimitiveArray { pub sbtIndexOffsetStrideInBytes: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } -impl Default for OptixBuildInputCustomPrimitiveArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] pub struct OptixBuildInputInstanceArray { pub instances: CUdeviceptr, pub numInstances: ::std::os::raw::c_uint, } -impl Default for OptixBuildInputInstanceArray { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixBuildInputType { - OPTIX_BUILD_INPUT_TYPE_TRIANGLES = 8513, - OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES = 8514, - OPTIX_BUILD_INPUT_TYPE_INSTANCES = 8515, - OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS = 8516, - OPTIX_BUILD_INPUT_TYPE_CURVES = 8517, -} -#[repr(C)] -pub struct OptixBuildInput { - pub type_: OptixBuildInputType, - pub __bindgen_anon_1: OptixBuildInput__bindgen_ty_1, -} +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES: OptixBuildInputType = 8513; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES: OptixBuildInputType = 8514; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES: OptixBuildInputType = 8515; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS: OptixBuildInputType = 8516; +pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CURVES: OptixBuildInputType = 8517; +pub type OptixBuildInputType = ::std::os::raw::c_uint; #[repr(C)] pub struct OptixBuildInput__bindgen_ty_1 { pub triangleArray: __BindgenUnionField, @@ -326,28 +334,16 @@ pub struct OptixBuildInput__bindgen_ty_1 { pub pad: __BindgenUnionField<[::std::os::raw::c_char; 1024usize]>, pub bindgen_union_field: [u64; 128usize], } -impl Default for OptixBuildInput__bindgen_ty_1 { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -impl Default for OptixBuildInput { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixInstanceFlags { - OPTIX_INSTANCE_FLAG_NONE = 0, - OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING = 1, - OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING = 2, - OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT = 4, - OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT = 8, - OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM = 64, -} +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE: OptixInstanceFlags = 0; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING: OptixInstanceFlags = + 1; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING: OptixInstanceFlags = 2; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT: OptixInstanceFlags = 4; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT: OptixInstanceFlags = 8; +pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM: OptixInstanceFlags = 64; +pub type OptixInstanceFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixInstance { pub transform: [f32; 12usize], pub instanceId: ::std::os::raw::c_uint, @@ -357,32 +353,23 @@ pub struct OptixInstance { pub traversableHandle: OptixTraversableHandle, pub pad: [::std::os::raw::c_uint; 2usize], } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixBuildFlags { - OPTIX_BUILD_FLAG_NONE = 0, - OPTIX_BUILD_FLAG_ALLOW_UPDATE = 1, - OPTIX_BUILD_FLAG_ALLOW_COMPACTION = 2, - OPTIX_BUILD_FLAG_PREFER_FAST_TRACE = 4, - OPTIX_BUILD_FLAG_PREFER_FAST_BUILD = 8, - OPTIX_BUILD_FLAG_ALLOW_RANDOM_VERTEX_ACCESS = 16, - OPTIX_BUILD_FLAG_ALLOW_RANDOM_INSTANCE_ACCESS = 32, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixBuildOperation { - OPTIX_BUILD_OPERATION_BUILD = 8545, - OPTIX_BUILD_OPERATION_UPDATE = 8546, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixMotionFlags { - OPTIX_MOTION_FLAG_NONE = 0, - OPTIX_MOTION_FLAG_START_VANISH = 1, - OPTIX_MOTION_FLAG_END_VANISH = 2, -} +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_NONE: OptixBuildFlags = 0; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_UPDATE: OptixBuildFlags = 1; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_COMPACTION: OptixBuildFlags = 2; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_TRACE: OptixBuildFlags = 4; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_BUILD: OptixBuildFlags = 8; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_VERTEX_ACCESS: OptixBuildFlags = 16; +pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_INSTANCE_ACCESS: OptixBuildFlags = 32; +pub type OptixBuildFlags = ::std::os::raw::c_uint; +pub const OptixBuildOperation_OPTIX_BUILD_OPERATION_BUILD: OptixBuildOperation = 8545; +pub const OptixBuildOperation_OPTIX_BUILD_OPERATION_UPDATE: OptixBuildOperation = 8546; +pub type OptixBuildOperation = ::std::os::raw::c_uint; +pub const OptixMotionFlags_OPTIX_MOTION_FLAG_NONE: OptixMotionFlags = 0; +pub const OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH: OptixMotionFlags = 1; +pub const OptixMotionFlags_OPTIX_MOTION_FLAG_END_VANISH: OptixMotionFlags = 2; +pub type OptixMotionFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixMotionOptions { pub numKeys: ::std::os::raw::c_ushort, pub flags: ::std::os::raw::c_ushort, @@ -390,47 +377,33 @@ pub struct OptixMotionOptions { pub timeEnd: f32, } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixAccelBuildOptions { pub buildFlags: ::std::os::raw::c_uint, pub operation: OptixBuildOperation, pub motionOptions: OptixMotionOptions, } -impl Default for OptixAccelBuildOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] pub struct OptixAccelBufferSizes { - pub outputSizeInBytes: usize, - pub tempSizeInBytes: usize, - pub tempUpdateSizeInBytes: usize, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixAccelPropertyType { - OPTIX_PROPERTY_TYPE_COMPACTED_SIZE = 8577, - OPTIX_PROPERTY_TYPE_AABBS = 8578, + pub outputSizeInBytes: size_t, + pub tempSizeInBytes: size_t, + pub tempUpdateSizeInBytes: size_t, } +pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE: OptixAccelPropertyType = 8577; +pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS: OptixAccelPropertyType = 8578; +pub type OptixAccelPropertyType = ::std::os::raw::c_uint; #[repr(C)] pub struct OptixAccelEmitDesc { pub result: CUdeviceptr, pub type_: OptixAccelPropertyType, } -impl Default for OptixAccelEmitDesc { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixAccelRelocationInfo { pub info: [::std::os::raw::c_ulonglong; 4usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixStaticTransform { pub child: OptixTraversableHandle, pub pad: [::std::os::raw::c_uint; 2usize], @@ -438,7 +411,7 @@ pub struct OptixStaticTransform { pub invTransform: [f32; 12usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixMatrixMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, @@ -446,7 +419,7 @@ pub struct OptixMatrixMotionTransform { pub transform: [[f32; 12usize]; 2usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixSRTData { pub sx: f32, pub a: f32, @@ -466,31 +439,29 @@ pub struct OptixSRTData { pub tz: f32, } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct OptixSRTMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, pub pad: [::std::os::raw::c_uint; 3usize], pub srtData: [OptixSRTData; 2usize], } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTraversableType { - OPTIX_TRAVERSABLE_TYPE_STATIC_TRANSFORM = 8641, - OPTIX_TRAVERSABLE_TYPE_MATRIX_MOTION_TRANSFORM = 8642, - OPTIX_TRAVERSABLE_TYPE_SRT_MOTION_TRANSFORM = 8643, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixPixelFormat { - OPTIX_PIXEL_FORMAT_HALF2 = 8711, - OPTIX_PIXEL_FORMAT_HALF3 = 8705, - OPTIX_PIXEL_FORMAT_HALF4 = 8706, - OPTIX_PIXEL_FORMAT_FLOAT2 = 8712, - OPTIX_PIXEL_FORMAT_FLOAT3 = 8707, - OPTIX_PIXEL_FORMAT_FLOAT4 = 8708, - OPTIX_PIXEL_FORMAT_UCHAR3 = 8709, - OPTIX_PIXEL_FORMAT_UCHAR4 = 8710, +pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_STATIC_TRANSFORM: OptixTraversableType = 8641; +pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_MATRIX_MOTION_TRANSFORM: + OptixTraversableType = 8642; +pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_SRT_MOTION_TRANSFORM: OptixTraversableType = + 8643; +pub type OptixTraversableType = ::std::os::raw::c_uint; +pub mod OptixPixelFormat { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_PIXEL_FORMAT_HALF2: Type = 8711; + pub const OPTIX_PIXEL_FORMAT_HALF3: Type = 8705; + pub const OPTIX_PIXEL_FORMAT_HALF4: Type = 8706; + pub const OPTIX_PIXEL_FORMAT_FLOAT2: Type = 8712; + pub const OPTIX_PIXEL_FORMAT_FLOAT3: Type = 8707; + pub const OPTIX_PIXEL_FORMAT_FLOAT4: Type = 8708; + pub const OPTIX_PIXEL_FORMAT_UCHAR3: Type = 8709; + pub const OPTIX_PIXEL_FORMAT_UCHAR4: Type = 8710; } #[repr(C)] pub struct OptixImage2D { @@ -499,23 +470,17 @@ pub struct OptixImage2D { pub height: ::std::os::raw::c_uint, pub rowStrideInBytes: ::std::os::raw::c_uint, pub pixelStrideInBytes: ::std::os::raw::c_uint, - pub format: OptixPixelFormat, + pub format: OptixPixelFormat::Type, } -impl Default for OptixImage2D { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixDenoiserModelKind { - OPTIX_DENOISER_MODEL_KIND_LDR = 8994, - OPTIX_DENOISER_MODEL_KIND_HDR = 8995, - OPTIX_DENOISER_MODEL_KIND_AOV = 8996, - OPTIX_DENOISER_MODEL_KIND_TEMPORAL = 8997, +pub mod OptixDenoiserModelKind { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_DENOISER_MODEL_KIND_LDR: Type = 8994; + pub const OPTIX_DENOISER_MODEL_KIND_HDR: Type = 8995; + pub const OPTIX_DENOISER_MODEL_KIND_AOV: Type = 8996; + pub const OPTIX_DENOISER_MODEL_KIND_TEMPORAL: Type = 8997; } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixDenoiserOptions { pub guideAlbedo: ::std::os::raw::c_uint, pub guideNormal: ::std::os::raw::c_uint, @@ -526,22 +491,12 @@ pub struct OptixDenoiserGuideLayer { pub normal: OptixImage2D, pub flow: OptixImage2D, } -impl Default for OptixDenoiserGuideLayer { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] pub struct OptixDenoiserLayer { pub input: OptixImage2D, pub previousOutput: OptixImage2D, pub output: OptixImage2D, } -impl Default for OptixDenoiserLayer { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] pub struct OptixDenoiserParams { pub denoiseAlpha: ::std::os::raw::c_uint, @@ -549,119 +504,84 @@ pub struct OptixDenoiserParams { pub blendFactor: f32, pub hdrAverageColor: CUdeviceptr, } -impl Default for OptixDenoiserParams { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] pub struct OptixDenoiserSizes { - pub stateSizeInBytes: usize, - pub withOverlapScratchSizeInBytes: usize, - pub withoutOverlapScratchSizeInBytes: usize, + pub stateSizeInBytes: size_t, + pub withOverlapScratchSizeInBytes: size_t, + pub withoutOverlapScratchSizeInBytes: size_t, pub overlapWindowSizeInPixels: ::std::os::raw::c_uint, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixRayFlags { - OPTIX_RAY_FLAG_NONE = 0, - OPTIX_RAY_FLAG_DISABLE_ANYHIT = 1, - OPTIX_RAY_FLAG_ENFORCE_ANYHIT = 2, - OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT = 4, - OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT = 8, - OPTIX_RAY_FLAG_CULL_BACK_FACING_TRIANGLES = 16, - OPTIX_RAY_FLAG_CULL_FRONT_FACING_TRIANGLES = 32, - OPTIX_RAY_FLAG_CULL_DISABLED_ANYHIT = 64, - OPTIX_RAY_FLAG_CULL_ENFORCED_ANYHIT = 128, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTransformType { - OPTIX_TRANSFORM_TYPE_NONE = 0, - OPTIX_TRANSFORM_TYPE_STATIC_TRANSFORM = 1, - OPTIX_TRANSFORM_TYPE_MATRIX_MOTION_TRANSFORM = 2, - OPTIX_TRANSFORM_TYPE_SRT_MOTION_TRANSFORM = 3, - OPTIX_TRANSFORM_TYPE_INSTANCE = 4, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixTraversableGraphFlags { - OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY = 0, - OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS = 1, - OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING = 2, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixCompileOptimizationLevel { - OPTIX_COMPILE_OPTIMIZATION_DEFAULT = 0, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_0 = 9024, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_1 = 9025, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_2 = 9026, - OPTIX_COMPILE_OPTIMIZATION_LEVEL_3 = 9027, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixCompileDebugLevel { - OPTIX_COMPILE_DEBUG_LEVEL_DEFAULT = 0, - OPTIX_COMPILE_DEBUG_LEVEL_NONE = 9040, - OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO = 9041, - OPTIX_COMPILE_DEBUG_LEVEL_FULL = 9042, +pub const OptixRayFlags_OPTIX_RAY_FLAG_NONE: OptixRayFlags = 0; +pub const OptixRayFlags_OPTIX_RAY_FLAG_DISABLE_ANYHIT: OptixRayFlags = 1; +pub const OptixRayFlags_OPTIX_RAY_FLAG_ENFORCE_ANYHIT: OptixRayFlags = 2; +pub const OptixRayFlags_OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT: OptixRayFlags = 4; +pub const OptixRayFlags_OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT: OptixRayFlags = 8; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_BACK_FACING_TRIANGLES: OptixRayFlags = 16; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_FRONT_FACING_TRIANGLES: OptixRayFlags = 32; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_DISABLED_ANYHIT: OptixRayFlags = 64; +pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_ENFORCED_ANYHIT: OptixRayFlags = 128; +pub type OptixRayFlags = ::std::os::raw::c_uint; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_NONE: OptixTransformType = 0; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_STATIC_TRANSFORM: OptixTransformType = 1; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_MATRIX_MOTION_TRANSFORM: OptixTransformType = 2; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_SRT_MOTION_TRANSFORM: OptixTransformType = 3; +pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_INSTANCE: OptixTransformType = 4; +pub type OptixTransformType = ::std::os::raw::c_uint; +pub mod OptixTraversableGraphFlags { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY: Type = 0; + pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS: Type = 1; + pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING: Type = 2; +} +pub mod OptixCompileOptimizationLevel { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_COMPILE_OPTIMIZATION_DEFAULT: Type = 0; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_0: Type = 9024; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_1: Type = 9025; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_2: Type = 9026; + pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_3: Type = 9027; +} +pub mod OptixCompileDebugLevel { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_COMPILE_DEBUG_LEVEL_DEFAULT: Type = 0; + pub const OPTIX_COMPILE_DEBUG_LEVEL_NONE: Type = 9040; + pub const OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO: Type = 9041; + pub const OPTIX_COMPILE_DEBUG_LEVEL_FULL: Type = 9042; } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct OptixModuleCompileBoundValueEntry { - pub pipelineParamOffsetInBytes: usize, - pub sizeInBytes: usize, + pub pipelineParamOffsetInBytes: size_t, + pub sizeInBytes: size_t, pub boundValuePtr: *const ::std::os::raw::c_void, pub annotation: *const ::std::os::raw::c_char, } -impl Default for OptixModuleCompileBoundValueEntry { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixModuleCompileOptions { pub maxRegisterCount: ::std::os::raw::c_int, - pub optLevel: OptixCompileOptimizationLevel, - pub debugLevel: OptixCompileDebugLevel, + pub optLevel: OptixCompileOptimizationLevel::Type, + pub debugLevel: OptixCompileDebugLevel::Type, pub boundValues: *const OptixModuleCompileBoundValueEntry, pub numBoundValues: ::std::os::raw::c_uint, } -impl Default for OptixModuleCompileOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixProgramGroupKind { - OPTIX_PROGRAM_GROUP_KIND_RAYGEN = 9249, - OPTIX_PROGRAM_GROUP_KIND_MISS = 9250, - OPTIX_PROGRAM_GROUP_KIND_EXCEPTION = 9251, - OPTIX_PROGRAM_GROUP_KIND_HITGROUP = 9252, - OPTIX_PROGRAM_GROUP_KIND_CALLABLES = 9253, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixProgramGroupFlags { - OPTIX_PROGRAM_GROUP_FLAGS_NONE = 0, +pub mod OptixProgramGroupKind { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_PROGRAM_GROUP_KIND_RAYGEN: Type = 9249; + pub const OPTIX_PROGRAM_GROUP_KIND_MISS: Type = 9250; + pub const OPTIX_PROGRAM_GROUP_KIND_EXCEPTION: Type = 9251; + pub const OPTIX_PROGRAM_GROUP_KIND_HITGROUP: Type = 9252; + pub const OPTIX_PROGRAM_GROUP_KIND_CALLABLES: Type = 9253; } +pub const OptixProgramGroupFlags_OPTIX_PROGRAM_GROUP_FLAGS_NONE: OptixProgramGroupFlags = 0; +pub type OptixProgramGroupFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupSingleModule { pub module: OptixModule, pub entryFunctionName: *const ::std::os::raw::c_char, } -impl Default for OptixProgramGroupSingleModule { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupHitgroup { pub moduleCH: OptixModule, pub entryFunctionNameCH: *const ::std::os::raw::c_char, @@ -670,28 +590,18 @@ pub struct OptixProgramGroupHitgroup { pub moduleIS: OptixModule, pub entryFunctionNameIS: *const ::std::os::raw::c_char, } -impl Default for OptixProgramGroupHitgroup { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupCallables { pub moduleDC: OptixModule, pub entryFunctionNameDC: *const ::std::os::raw::c_char, pub moduleCC: OptixModule, pub entryFunctionNameCC: *const ::std::os::raw::c_char, } -impl Default for OptixProgramGroupCallables { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] #[derive(Copy, Clone)] pub struct OptixProgramGroupDesc { - pub kind: OptixProgramGroupKind, + pub kind: OptixProgramGroupKind::Type, pub flags: ::std::os::raw::c_uint, pub __bindgen_anon_1: OptixProgramGroupDesc__bindgen_ty_1, } @@ -703,55 +613,54 @@ pub union OptixProgramGroupDesc__bindgen_ty_1 { pub exception: OptixProgramGroupSingleModule, pub callables: OptixProgramGroupCallables, pub hitgroup: OptixProgramGroupHitgroup, -} -impl Default for OptixProgramGroupDesc__bindgen_ty_1 { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -impl Default for OptixProgramGroupDesc { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } + _bindgen_union_align: [u64; 6usize], } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixProgramGroupOptions { pub reserved: ::std::os::raw::c_int, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixExceptionCodes { - OPTIX_EXCEPTION_CODE_STACK_OVERFLOW = -1, - OPTIX_EXCEPTION_CODE_TRACE_DEPTH_EXCEEDED = -2, - OPTIX_EXCEPTION_CODE_TRAVERSAL_DEPTH_EXCEEDED = -3, - OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_TRAVERSABLE = -5, - OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_MISS_SBT = -6, - OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_HIT_SBT = -7, - OPTIX_EXCEPTION_CODE_UNSUPPORTED_PRIMITIVE_TYPE = -8, - OPTIX_EXCEPTION_CODE_INVALID_RAY = -9, - OPTIX_EXCEPTION_CODE_CALLABLE_PARAMETER_MISMATCH = -10, - OPTIX_EXCEPTION_CODE_BUILTIN_IS_MISMATCH = -11, - OPTIX_EXCEPTION_CODE_CALLABLE_INVALID_SBT = -12, - OPTIX_EXCEPTION_CODE_CALLABLE_NO_DC_SBT_RECORD = -13, - OPTIX_EXCEPTION_CODE_CALLABLE_NO_CC_SBT_RECORD = -14, - OPTIX_EXCEPTION_CODE_UNSUPPORTED_SINGLE_LEVEL_GAS = -15, - OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_0 = -16, - OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_1 = -17, - OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_2 = -18, - OPTIX_EXCEPTION_CODE_UNSUPPORTED_DATA_ACCESS = -32, -} -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixExceptionFlags { - OPTIX_EXCEPTION_FLAG_NONE = 0, - OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW = 1, - OPTIX_EXCEPTION_FLAG_TRACE_DEPTH = 2, - OPTIX_EXCEPTION_FLAG_USER = 4, - OPTIX_EXCEPTION_FLAG_DEBUG = 8, +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_STACK_OVERFLOW: OptixExceptionCodes = -1; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRACE_DEPTH_EXCEEDED: OptixExceptionCodes = -2; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_DEPTH_EXCEEDED: OptixExceptionCodes = + -3; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_TRAVERSABLE: + OptixExceptionCodes = -5; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_MISS_SBT: OptixExceptionCodes = + -6; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_TRAVERSAL_INVALID_HIT_SBT: OptixExceptionCodes = + -7; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_PRIMITIVE_TYPE: OptixExceptionCodes = + -8; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_RAY: OptixExceptionCodes = -9; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_PARAMETER_MISMATCH: + OptixExceptionCodes = -10; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_BUILTIN_IS_MISMATCH: OptixExceptionCodes = -11; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_INVALID_SBT: OptixExceptionCodes = -12; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_NO_DC_SBT_RECORD: OptixExceptionCodes = + -13; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_CALLABLE_NO_CC_SBT_RECORD: OptixExceptionCodes = + -14; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_SINGLE_LEVEL_GAS: + OptixExceptionCodes = -15; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_0: OptixExceptionCodes = + -16; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_1: OptixExceptionCodes = + -17; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_INVALID_VALUE_ARGUMENT_2: OptixExceptionCodes = + -18; +pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_DATA_ACCESS: OptixExceptionCodes = + -32; +pub type OptixExceptionCodes = ::std::os::raw::c_int; +pub mod OptixExceptionFlags { + pub type Type = ::std::os::raw::c_uint; + pub const OPTIX_EXCEPTION_FLAG_NONE: Type = 0; + pub const OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW: Type = 1; + pub const OPTIX_EXCEPTION_FLAG_TRACE_DEPTH: Type = 2; + pub const OPTIX_EXCEPTION_FLAG_USER: Type = 4; + pub const OPTIX_EXCEPTION_FLAG_DEBUG: Type = 8; } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct OptixPipelineCompileOptions { pub usesMotionBlur: ::std::os::raw::c_int, pub traversableGraphFlags: ::std::os::raw::c_uint, @@ -761,23 +670,13 @@ pub struct OptixPipelineCompileOptions { pub pipelineLaunchParamsVariableName: *const ::std::os::raw::c_char, pub usesPrimitiveTypeFlags: ::std::os::raw::c_uint, pub reserved: ::std::os::raw::c_uint, - pub reserved2: usize, -} -impl Default for OptixPipelineCompileOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } + pub reserved2: size_t, } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixPipelineLinkOptions { pub maxTraceDepth: ::std::os::raw::c_uint, - pub debugLevel: OptixCompileDebugLevel, -} -impl Default for OptixPipelineLinkOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } + pub debugLevel: OptixCompileDebugLevel::Type, } #[repr(C)] pub struct OptixShaderBindingTable { @@ -793,13 +692,8 @@ pub struct OptixShaderBindingTable { pub callablesRecordStrideInBytes: ::std::os::raw::c_uint, pub callablesRecordCount: ::std::os::raw::c_uint, } -impl Default for OptixShaderBindingTable { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixStackSizes { pub cssRG: ::std::os::raw::c_uint, pub cssMS: ::std::os::raw::c_uint, @@ -809,11 +703,9 @@ pub struct OptixStackSizes { pub cssCC: ::std::os::raw::c_uint, pub dssDC: ::std::os::raw::c_uint, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum OptixQueryFunctionTableOptions { - OPTIX_QUERY_FUNCTION_TABLE_OPTION_DUMMY = 0, -} +pub const OptixQueryFunctionTableOptions_OPTIX_QUERY_FUNCTION_TABLE_OPTION_DUMMY: + OptixQueryFunctionTableOptions = 0; +pub type OptixQueryFunctionTableOptions = ::std::os::raw::c_uint; pub type OptixQueryFunctionTable_t = ::std::option::Option< unsafe extern "C" fn( abiId: ::std::os::raw::c_int, @@ -821,20 +713,15 @@ pub type OptixQueryFunctionTable_t = ::std::option::Option< arg1: *mut OptixQueryFunctionTableOptions, arg2: *mut *const ::std::os::raw::c_void, functionTable: *mut ::std::os::raw::c_void, - sizeOfTable: usize, + sizeOfTable: size_t, ) -> OptixResult, >; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixBuiltinISOptions { pub builtinISModuleType: OptixPrimitiveType, pub usesMotionBlur: ::std::os::raw::c_int, } -impl Default for OptixBuiltinISOptions { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} extern "C" { pub fn optixGetErrorName(result: OptixResult) -> *const ::std::os::raw::c_char; } @@ -854,9 +741,9 @@ extern "C" { extern "C" { pub fn optixDeviceContextGetProperty( context: OptixDeviceContext, - property: OptixDeviceProperty, + property: OptixDeviceProperty::Type, value: *mut ::std::os::raw::c_void, - sizeInBytes: usize, + sizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -882,8 +769,8 @@ extern "C" { extern "C" { pub fn optixDeviceContextSetCacheDatabaseSizes( context: OptixDeviceContext, - lowWaterMark: usize, - highWaterMark: usize, + lowWaterMark: size_t, + highWaterMark: size_t, ) -> OptixResult; } extern "C" { @@ -896,14 +783,14 @@ extern "C" { pub fn optixDeviceContextGetCacheLocation( context: OptixDeviceContext, location: *mut ::std::os::raw::c_char, - locationSize: usize, + locationSize: size_t, ) -> OptixResult; } extern "C" { pub fn optixDeviceContextGetCacheDatabaseSizes( context: OptixDeviceContext, - lowWaterMark: *mut usize, - highWaterMark: *mut usize, + lowWaterMark: *mut size_t, + highWaterMark: *mut size_t, ) -> OptixResult; } extern "C" { @@ -914,7 +801,7 @@ extern "C" { programGroups: *const OptixProgramGroup, numProgramGroups: ::std::os::raw::c_uint, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, pipeline: *mut OptixPipeline, ) -> OptixResult; } @@ -936,9 +823,9 @@ extern "C" { moduleCompileOptions: *const OptixModuleCompileOptions, pipelineCompileOptions: *const OptixPipelineCompileOptions, PTX: *const ::std::os::raw::c_char, - PTXsize: usize, + PTXsize: size_t, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, module: *mut OptixModule, ) -> OptixResult; } @@ -967,7 +854,7 @@ extern "C" { numProgramGroups: ::std::os::raw::c_uint, options: *const OptixProgramGroupOptions, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, programGroups: *mut OptixProgramGroup, ) -> OptixResult; } @@ -979,7 +866,7 @@ extern "C" { pipeline: OptixPipeline, stream: CUstream, pipelineParams: CUdeviceptr, - pipelineParamsSize: usize, + pipelineParamsSize: size_t, sbt: *const OptixShaderBindingTable, width: ::std::os::raw::c_uint, height: ::std::os::raw::c_uint, @@ -1009,9 +896,9 @@ extern "C" { buildInputs: *const OptixBuildInput, numBuildInputs: ::std::os::raw::c_uint, tempBuffer: CUdeviceptr, - tempBufferSizeInBytes: usize, + tempBufferSizeInBytes: size_t, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, emittedProperties: *const OptixAccelEmitDesc, numEmittedProperties: ::std::os::raw::c_uint, @@ -1037,9 +924,9 @@ extern "C" { stream: CUstream, info: *const OptixAccelRelocationInfo, instanceTraversableHandles: CUdeviceptr, - numInstanceTraversableHandles: usize, + numInstanceTraversableHandles: size_t, targetAccel: CUdeviceptr, - targetAccelSizeInBytes: usize, + targetAccelSizeInBytes: size_t, targetHandle: *mut OptixTraversableHandle, ) -> OptixResult; } @@ -1049,7 +936,7 @@ extern "C" { stream: CUstream, inputHandle: OptixTraversableHandle, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, ) -> OptixResult; } @@ -1064,7 +951,7 @@ extern "C" { extern "C" { pub fn optixDenoiserCreate( context: OptixDeviceContext, - modelKind: OptixDenoiserModelKind, + modelKind: OptixDenoiserModelKind::Type, options: *const OptixDenoiserOptions, denoiser: *mut OptixDenoiser, ) -> OptixResult; @@ -1073,7 +960,7 @@ extern "C" { pub fn optixDenoiserCreateWithUserModel( context: OptixDeviceContext, userData: *const ::std::os::raw::c_void, - userDataSizeInBytes: usize, + userDataSizeInBytes: size_t, denoiser: *mut OptixDenoiser, ) -> OptixResult; } @@ -1095,9 +982,9 @@ extern "C" { inputWidth: ::std::os::raw::c_uint, inputHeight: ::std::os::raw::c_uint, denoiserState: CUdeviceptr, - denoiserStateSizeInBytes: usize, + denoiserStateSizeInBytes: size_t, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -1106,14 +993,14 @@ extern "C" { stream: CUstream, params: *const OptixDenoiserParams, denoiserState: CUdeviceptr, - denoiserStateSizeInBytes: usize, + denoiserStateSizeInBytes: size_t, guideLayer: *const OptixDenoiserGuideLayer, layers: *const OptixDenoiserLayer, numLayers: ::std::os::raw::c_uint, inputOffsetX: ::std::os::raw::c_uint, inputOffsetY: ::std::os::raw::c_uint, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -1123,7 +1010,7 @@ extern "C" { inputImage: *const OptixImage2D, outputIntensity: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } extern "C" { @@ -1133,11 +1020,11 @@ extern "C" { inputImage: *const OptixImage2D, outputAverageColor: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult; } #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct OptixFunctionTable { pub optixGetErrorName: ::std::option::Option< unsafe extern "C" fn(result: OptixResult) -> *const ::std::os::raw::c_char, @@ -1157,9 +1044,9 @@ pub struct OptixFunctionTable { pub optixDeviceContextGetProperty: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - property: OptixDeviceProperty, + property: OptixDeviceProperty::Type, value: *mut ::std::os::raw::c_void, - sizeInBytes: usize, + sizeInBytes: size_t, ) -> OptixResult, >, pub optixDeviceContextSetLogCallback: ::std::option::Option< @@ -1185,8 +1072,8 @@ pub struct OptixFunctionTable { pub optixDeviceContextSetCacheDatabaseSizes: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - lowWaterMark: usize, - highWaterMark: usize, + lowWaterMark: size_t, + highWaterMark: size_t, ) -> OptixResult, >, pub optixDeviceContextGetCacheEnabled: ::std::option::Option< @@ -1199,14 +1086,14 @@ pub struct OptixFunctionTable { unsafe extern "C" fn( context: OptixDeviceContext, location: *mut ::std::os::raw::c_char, - locationSize: usize, + locationSize: size_t, ) -> OptixResult, >, pub optixDeviceContextGetCacheDatabaseSizes: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - lowWaterMark: *mut usize, - highWaterMark: *mut usize, + lowWaterMark: *mut size_t, + highWaterMark: *mut size_t, ) -> OptixResult, >, pub optixModuleCreateFromPTX: ::std::option::Option< @@ -1215,9 +1102,9 @@ pub struct OptixFunctionTable { moduleCompileOptions: *const OptixModuleCompileOptions, pipelineCompileOptions: *const OptixPipelineCompileOptions, PTX: *const ::std::os::raw::c_char, - PTXsize: usize, + PTXsize: size_t, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, module: *mut OptixModule, ) -> OptixResult, >, @@ -1239,7 +1126,7 @@ pub struct OptixFunctionTable { numProgramGroups: ::std::os::raw::c_uint, options: *const OptixProgramGroupOptions, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, programGroups: *mut OptixProgramGroup, ) -> OptixResult, >, @@ -1259,7 +1146,7 @@ pub struct OptixFunctionTable { programGroups: *const OptixProgramGroup, numProgramGroups: ::std::os::raw::c_uint, logString: *mut ::std::os::raw::c_char, - logStringSize: *mut usize, + logStringSize: *mut size_t, pipeline: *mut OptixPipeline, ) -> OptixResult, >, @@ -1291,9 +1178,9 @@ pub struct OptixFunctionTable { buildInputs: *const OptixBuildInput, numBuildInputs: ::std::os::raw::c_uint, tempBuffer: CUdeviceptr, - tempBufferSizeInBytes: usize, + tempBufferSizeInBytes: size_t, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, emittedProperties: *const OptixAccelEmitDesc, numEmittedProperties: ::std::os::raw::c_uint, @@ -1319,9 +1206,9 @@ pub struct OptixFunctionTable { stream: CUstream, info: *const OptixAccelRelocationInfo, instanceTraversableHandles: CUdeviceptr, - numInstanceTraversableHandles: usize, + numInstanceTraversableHandles: size_t, targetAccel: CUdeviceptr, - targetAccelSizeInBytes: usize, + targetAccelSizeInBytes: size_t, targetHandle: *mut OptixTraversableHandle, ) -> OptixResult, >, @@ -1331,7 +1218,7 @@ pub struct OptixFunctionTable { stream: CUstream, inputHandle: OptixTraversableHandle, outputBuffer: CUdeviceptr, - outputBufferSizeInBytes: usize, + outputBufferSizeInBytes: size_t, outputHandle: *mut OptixTraversableHandle, ) -> OptixResult, >, @@ -1354,7 +1241,7 @@ pub struct OptixFunctionTable { pipeline: OptixPipeline, stream: CUstream, pipelineParams: CUdeviceptr, - pipelineParamsSize: usize, + pipelineParamsSize: size_t, sbt: *const OptixShaderBindingTable, width: ::std::os::raw::c_uint, height: ::std::os::raw::c_uint, @@ -1364,7 +1251,7 @@ pub struct OptixFunctionTable { pub optixDenoiserCreate: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, - modelKind: OptixDenoiserModelKind, + modelKind: OptixDenoiserModelKind::Type, options: *const OptixDenoiserOptions, returnHandle: *mut OptixDenoiser, ) -> OptixResult, @@ -1386,9 +1273,9 @@ pub struct OptixFunctionTable { inputWidth: ::std::os::raw::c_uint, inputHeight: ::std::os::raw::c_uint, state: CUdeviceptr, - stateSizeInBytes: usize, + stateSizeInBytes: size_t, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserInvoke: ::std::option::Option< @@ -1397,14 +1284,14 @@ pub struct OptixFunctionTable { stream: CUstream, params: *const OptixDenoiserParams, denoiserState: CUdeviceptr, - denoiserStateSizeInBytes: usize, + denoiserStateSizeInBytes: size_t, guideLayer: *const OptixDenoiserGuideLayer, layers: *const OptixDenoiserLayer, numLayers: ::std::os::raw::c_uint, inputOffsetX: ::std::os::raw::c_uint, inputOffsetY: ::std::os::raw::c_uint, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserComputeIntensity: ::std::option::Option< @@ -1414,7 +1301,7 @@ pub struct OptixFunctionTable { inputImage: *const OptixImage2D, outputIntensity: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserComputeAverageColor: ::std::option::Option< @@ -1424,15 +1311,31 @@ pub struct OptixFunctionTable { inputImage: *const OptixImage2D, outputAverageColor: CUdeviceptr, scratch: CUdeviceptr, - scratchSizeInBytes: usize, + scratchSizeInBytes: size_t, ) -> OptixResult, >, pub optixDenoiserCreateWithUserModel: ::std::option::Option< unsafe extern "C" fn( context: OptixDeviceContext, data: *const ::std::os::raw::c_void, - dataSizeInBytes: usize, + dataSizeInBytes: size_t, returnHandle: *mut OptixDenoiser, ) -> OptixResult, >, } +pub const OptixSbtRecordHeaderSize: size_t = 32; +pub const OptixSbtRecordAlignment: size_t = 16; +pub const OptixAccelBufferByteAlignment: size_t = 128; +pub const OptixInstanceByteAlignment: size_t = 16; +pub const OptixAabbBufferByteAlignment: size_t = 8; +pub const OptixGeometryTransformByteAlignment: size_t = 16; +pub const OptixTransformByteAlignment: size_t = 64; +pub const OptixVersion: size_t = 70300; +pub const OptixBuildInputSize: size_t = 1032; +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum OptixGeometryFlags { + None = 0, + DisableAnyHit = 1, + RequireSingleAnyHitCall = 2, +} diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index a95ff086..15697d18 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -34,7 +34,7 @@ pub enum OptixDeviceProperty { impl OptixDeviceProperty { // we could repr this the same as the sys version, but for better compatability // and safety in the future, we just match. - pub fn to_raw(self) -> sys::OptixDeviceProperty { + pub fn to_raw(self) -> sys::OptixDeviceProperty::Type { use OptixDeviceProperty::*; match self { MaxTraceDepth => sys::OptixDeviceProperty::OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH, diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 8ff3be3d..6cf08da6 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -44,7 +44,7 @@ pub enum DenoiserModelKind { impl DenoiserModelKind { /// Converts this model kind to its raw counterpart. - pub fn to_raw(self) -> sys::OptixDenoiserModelKind { + pub fn to_raw(self) -> sys::OptixDenoiserModelKind::Type { match self { Self::Ldr => sys::OptixDenoiserModelKind::OPTIX_DENOISER_MODEL_KIND_LDR, Self::Hdr => sys::OptixDenoiserModelKind::OPTIX_DENOISER_MODEL_KIND_HDR, @@ -501,7 +501,7 @@ pub enum ImageFormat { } impl ImageFormat { - pub fn to_raw(self) -> sys::OptixPixelFormat { + pub fn to_raw(self) -> sys::OptixPixelFormat::Type { use ImageFormat::*; match self { diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 7f47e537..a2700b0a 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -1,10 +1,11 @@ pub mod context; pub mod denoiser; pub mod error; +pub mod module; +pub mod sys; pub use cust; use error::{OptixResult, ToResult}; -pub use optix_sys as sys; /// Initializes the OptiX library. This must be called before using any OptiX function. It may /// be called before or after initializing CUDA. diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs new file mode 100644 index 00000000..9e1e5325 --- /dev/null +++ b/crates/optix/src/module.rs @@ -0,0 +1,25 @@ +use crate::{error::OptixResult, optix_call, sys}; + +#[derive(Clone)] +#[repr(transparent)] +pub struct Module { + pub(crate) raw: sys::OptixModule, +} + +#[repr(u32)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub enum CompileOptimizationLevel { + Default = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_DEFAULT, + Level0 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_0, + Level1 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_1, + Level2 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_2, + Level3 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_3, +} + +#[repr(u32)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub enum CompileDebugLevel { + None = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_NONE, + LineInfo = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO, + Full = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_FULL, +} diff --git a/crates/optix/src/optix_wrapper.h b/crates/optix/src/optix_wrapper.h new file mode 100644 index 00000000..fe7c4469 --- /dev/null +++ b/crates/optix/src/optix_wrapper.h @@ -0,0 +1,27 @@ +#include +#include + +static const size_t OptixSbtRecordHeaderSize = OPTIX_SBT_RECORD_HEADER_SIZE; +static const size_t OptixSbtRecordAlignment = OPTIX_SBT_RECORD_ALIGNMENT; +static const size_t OptixAccelBufferByteAlignment = + OPTIX_ACCEL_BUFFER_BYTE_ALIGNMENT; +static const size_t OptixInstanceByteAlignment = OPTIX_INSTANCE_BYTE_ALIGNMENT; +static const size_t OptixAabbBufferByteAlignment = + OPTIX_AABB_BUFFER_BYTE_ALIGNMENT; +static const size_t OptixGeometryTransformByteAlignment = + OPTIX_GEOMETRY_TRANSFORM_BYTE_ALIGNMENT; +static const size_t OptixTransformByteAlignment = + OPTIX_TRANSFORM_BYTE_ALIGNMENT; + +static const size_t OptixVersion = OPTIX_VERSION; + +static const size_t OptixBuildInputSize = sizeof(OptixBuildInput); + +/** + *

+ */ +enum GeometryFlags { + None = OPTIX_GEOMETRY_FLAG_NONE, + DisableAnyHit = OPTIX_GEOMETRY_FLAG_DISABLE_ANYHIT, + RequireSingleAnyHitCall = OPTIX_GEOMETRY_FLAG_REQUIRE_SINGLE_ANYHIT_CALL +}; diff --git a/crates/optix/src/sys.rs b/crates/optix/src/sys.rs new file mode 100644 index 00000000..dfb5caf3 --- /dev/null +++ b/crates/optix/src/sys.rs @@ -0,0 +1,64 @@ +use cust_raw::*; + +use std::mem::ManuallyDrop; + +type size_t = usize; + +include!(concat!(env!("OUT_DIR"), "/optix_wrapper.rs")); + +extern "C" { + pub fn optixInit() -> OptixResult; +} + +// The SBT record header is an opaque blob used by optix +#[repr(C)] +pub struct SbtRecordHeader { + header: [u8; OptixSbtRecordHeaderSize as usize], +} + +impl SbtRecordHeader { + pub fn as_mut_ptr(&mut self) -> *mut std::os::raw::c_void { + self.header.as_mut_ptr() as *mut std::os::raw::c_void + } +} + +impl Default for SbtRecordHeader { + fn default() -> SbtRecordHeader { + SbtRecordHeader { + header: [0u8; OptixSbtRecordHeaderSize as usize], + } + } +} + +// Manually define the build input union as the bindgen is pretty nasty +#[repr(C)] +pub union OptixBuildInputUnion { + pub triangle_array: ManuallyDrop, + pub curve_array: ManuallyDrop, + pub custom_primitive_array: ManuallyDrop, + pub instance_array: ManuallyDrop, + pad: [std::os::raw::c_char; 1024], +} + +impl Default for OptixBuildInputUnion { + fn default() -> OptixBuildInputUnion { + OptixBuildInputUnion { pad: [0i8; 1024] } + } +} + +#[repr(C)] +pub struct OptixBuildInput { + pub type_: OptixBuildInputType, + pub input: OptixBuildInputUnion, +} + +// Sanity check that the size of this union we're defining matches the one in +// optix header so we don't get any nasty surprises +fn _size_check() { + unsafe { + std::mem::transmute::(OptixBuildInput { + type_: OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: { OptixBuildInputUnion { pad: [0; 1024] } }, + }); + } +} diff --git a/crates/optix_sys/Cargo.toml b/crates/optix_sys/Cargo.toml deleted file mode 100644 index 6b4d5a51..00000000 --- a/crates/optix_sys/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "optix_sys" -version = "0.1.0" -edition = "2021" - -[dependencies] -cust_raw = { version = "0.11.2", path = "../cust_raw" } - -[build-dependencies] -cc = "1.0.71" -find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } diff --git a/crates/optix_sys/build.rs b/crates/optix_sys/build.rs deleted file mode 100644 index dd88b7cf..00000000 --- a/crates/optix_sys/build.rs +++ /dev/null @@ -1,32 +0,0 @@ -use find_cuda_helper::{find_cuda_root, find_optix_root}; -use std::env; - -// OptiX is a bit exotic in how it provides its functions. It uses a function table -// approach, a function table struct holds function pointers to every optix function. Then -// the Optix driver dll is loaded at runtime and the function table is loaded from that. -// OptiX provides this logic inside optix_stubs.h in the include dir, so we need to compile that -// to a lib and link it in so that we have the initialization and C function logic. -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - let mut optix_include = find_optix_root().expect( - "Unable to find the OptiX SDK, make sure you installed it and - that OPTIX_ROOT or OPTIX_ROOT_DIR are set", - ); - optix_include = optix_include.join("include"); - - let mut cuda_include = find_cuda_root().expect( - "Unable to find the CUDA Toolkit, make sure you installed it and - that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", - ); - cuda_include = cuda_include.join("include"); - - cc::Build::new() - .file("./optix_stubs.c") - .include(optix_include) - .include(cuda_include) - .cpp(false) - .compile("optix_stubs"); - - println!("cargo:rustc-link-search=native={}", out_dir); - println!("cargo:rustc-link-lib=static=optix_stubs"); -} diff --git a/crates/optix_sys/src/lib.rs b/crates/optix_sys/src/lib.rs deleted file mode 100644 index 088cb334..00000000 --- a/crates/optix_sys/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Raw bindings to the OptiX 7.3 SDK. - -#![allow(warnings)] - -use cust_raw::*; -include!("../optix.rs"); - -extern "C" { - pub fn optixInit() -> OptixResult; -} From 2527adaeec14f22bb899c8289793d74922b1f120 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 12:40:01 +1300 Subject: [PATCH 023/100] bootstrap enough optix to get ex02 working --- Cargo.toml | 5 + crates/optix/Cargo.toml | 11 +- crates/optix/build.rs | 35 +- .../optix/examples/common/gdt/CMakeLists.txt | 35 ++ .../examples/common/gdt/cmake/FindOptiX.cmake | 189 +++++++++ .../examples/common/gdt/cmake/FindTBB.cmake | 154 +++++++ .../gdt/cmake/configure_build_type.cmake | 41 ++ .../common/gdt/cmake/configure_glut.cmake | 20 + .../common/gdt/cmake/configure_optix.cmake | 68 +++ .../common/gdt/cmake/configure_tbb.cmake | 21 + crates/optix/examples/common/gdt/gdt/gdt.cpp | 20 + crates/optix/examples/common/gdt/gdt/gdt.h | 233 ++++++++++ .../common/gdt/gdt/math/AffineSpace.h | 183 ++++++++ .../common/gdt/gdt/math/LinearSpace.h | 341 +++++++++++++++ .../examples/common/gdt/gdt/math/Quaternion.h | 227 ++++++++++ .../optix/examples/common/gdt/gdt/math/box.h | 223 ++++++++++ .../examples/common/gdt/gdt/math/constants.h | 185 ++++++++ .../examples/common/gdt/gdt/math/fixedpoint.h | 36 ++ .../optix/examples/common/gdt/gdt/math/vec.h | 400 ++++++++++++++++++ .../common/gdt/gdt/math/vec/compare.h | 59 +++ .../common/gdt/gdt/math/vec/functors.h | 364 ++++++++++++++++ .../examples/common/gdt/gdt/math/vec/rotate.h | 40 ++ .../examples/common/gdt/gdt/random/random.h | 91 ++++ .../optix/examples/ex02_pipeline/Cargo.toml | 15 + crates/optix/examples/ex02_pipeline/build.rs | 49 +++ .../ex02_pipeline/src/ex02_pipeline.cu | 105 +++++ .../ex02_pipeline/src/launch_params.h | 6 + .../optix/examples/ex02_pipeline/src/main.rs | 8 + .../examples/ex02_pipeline/src/renderer.rs | 240 +++++++++++ crates/optix/optix_wrapper.rs | 273 ++++++++++-- crates/optix/src/context.rs | 14 +- crates/optix/src/denoiser.rs | 13 +- crates/optix/src/error.rs | 54 ++- crates/optix/src/lib.rs | 42 +- crates/optix/src/module.rs | 259 +++++++++++- crates/optix/src/optix_wrapper.h | 1 + crates/optix/src/pipeline.rs | 131 ++++++ crates/optix/src/program_group.rs | 400 ++++++++++++++++++ crates/optix/src/shader_binding_table.rs | 140 ++++++ crates/optix/src/sys.rs | 11 +- 40 files changed, 4673 insertions(+), 69 deletions(-) create mode 100644 crates/optix/examples/common/gdt/CMakeLists.txt create mode 100644 crates/optix/examples/common/gdt/cmake/FindOptiX.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/FindTBB.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_build_type.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_glut.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_optix.cmake create mode 100644 crates/optix/examples/common/gdt/cmake/configure_tbb.cmake create mode 100644 crates/optix/examples/common/gdt/gdt/gdt.cpp create mode 100644 crates/optix/examples/common/gdt/gdt/gdt.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/AffineSpace.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/LinearSpace.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/Quaternion.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/box.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/constants.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/fixedpoint.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec/compare.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec/functors.h create mode 100644 crates/optix/examples/common/gdt/gdt/math/vec/rotate.h create mode 100644 crates/optix/examples/common/gdt/gdt/random/random.h create mode 100644 crates/optix/examples/ex02_pipeline/Cargo.toml create mode 100644 crates/optix/examples/ex02_pipeline/build.rs create mode 100644 crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu create mode 100644 crates/optix/examples/ex02_pipeline/src/launch_params.h create mode 100644 crates/optix/examples/ex02_pipeline/src/main.rs create mode 100644 crates/optix/examples/ex02_pipeline/src/renderer.rs create mode 100644 crates/optix/src/pipeline.rs create mode 100644 crates/optix/src/program_group.rs create mode 100644 crates/optix/src/shader_binding_table.rs diff --git a/Cargo.toml b/Cargo.toml index dac834a3..8bfc12cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,14 @@ [workspace] members = [ "crates/*", + "crates/optix/examples/ex*", "xtask", "examples/optix/*" ] +exclude = [ + "crates/optix/examples/*" +] + [profile.dev.package.rustc_codegen_nvvm] opt-level = 3 diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 7bf0ce70..821101c7 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -3,12 +3,21 @@ name = "optix" version = "0.1.0" edition = "2021" +[features] +optix71 = [] +optix72 = [] +optix73 = [] +default=["optix73"] + [dependencies] cust = { version = "0.1", path = "../cust" } cust_raw = { version = "0.11.2", path = "../cust_raw" } +cfg-if = "1.0.0" +bitflags = "1.3.2" +ustr = "0.8.1" [build-dependencies] -bindgen = "0.55" +bindgen = "0.59" cc = "1.0.71" find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } diff --git a/crates/optix/build.rs b/crates/optix/build.rs index d4ad5e20..c48dcdd2 100644 --- a/crates/optix/build.rs +++ b/crates/optix/build.rs @@ -51,22 +51,23 @@ fn bindgen_optix(optix_include: &Path, cuda_include: &Path) { .header("src/optix_wrapper.h") .clang_arg(format!("-I{}", optix_include.display())) .clang_arg(format!("-I{}", cuda_include.display())) - .whitelist_recursively(false) - .whitelist_type("Optix.*") - .whitelist_type("RaygenRecord") - .whitelist_type("MissRecord") - .whitelist_type("HitgroupRecord") - .blacklist_type("OptixBuildInput") - .whitelist_function("optix.*") - .whitelist_var("OptixSbtRecordHeaderSize") - .whitelist_var("OptixSbtRecordAlignment") - .whitelist_var("OptixAccelBufferByteAlignment") - .whitelist_var("OptixInstanceByteAlignment") - .whitelist_var("OptixAabbBufferByteAlignment") - .whitelist_var("OptixGeometryTransformByteAlignment") - .whitelist_var("OptixTransformByteAlignment") - .whitelist_var("OptixVersion") - .whitelist_var("OptixBuildInputSize") + .allowlist_recursively(false) + .allowlist_type("Optix.*") + .allowlist_type("RaygenRecord") + .allowlist_type("MissRecord") + .allowlist_type("HitgroupRecord") + .blocklist_type("OptixBuildInput") + .allowlist_function("optix.*") + .allowlist_var("OptixSbtRecordHeaderSize") + .allowlist_var("OptixSbtRecordAlignment") + .allowlist_var("OptixAccelBufferByteAlignment") + .allowlist_var("OptixInstanceByteAlignment") + .allowlist_var("OptixAabbBufferByteAlignment") + .allowlist_var("OptixGeometryTransformByteAlignment") + .allowlist_var("OptixTransformByteAlignment") + .allowlist_var("OptixVersion") + .allowlist_var("OptixBuildInputSize") + .allowlist_var("OptixShaderBindingTableSize") .layout_tests(false) .generate_comments(false) .newtype_enum("OptixResult") @@ -83,6 +84,8 @@ fn bindgen_optix(optix_include: &Path, cuda_include: &Path) { .constified_enum("OptixVertexFormat") .constified_enum("OptixIndicesFormat") .rust_target(bindgen::RustTarget::Nightly) + .derive_default(true) + .derive_partialeq(true) .rustfmt_bindings(true) .generate() .expect("Unable to generate optix bindings"); diff --git a/crates/optix/examples/common/gdt/CMakeLists.txt b/crates/optix/examples/common/gdt/CMakeLists.txt new file mode 100644 index 00000000..bc14ffc0 --- /dev/null +++ b/crates/optix/examples/common/gdt/CMakeLists.txt @@ -0,0 +1,35 @@ +# ======================================================================== # +# Copyright 2018-2019 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +project(GPU_Development_Tools) +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(gdt + cmake/configure_build_type.cmake + cmake/configure_optix.cmake + cmake/FindOptiX.cmake + + gdt/gdt.h + gdt/math/LinearSpace.h + gdt/math/AffineSpace.h + + gdt/gdt.cpp + ) + diff --git a/crates/optix/examples/common/gdt/cmake/FindOptiX.cmake b/crates/optix/examples/common/gdt/cmake/FindOptiX.cmake new file mode 100644 index 00000000..17578042 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/FindOptiX.cmake @@ -0,0 +1,189 @@ +# +# Copyright (c) 2018 NVIDIA 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: +# * 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 NVIDIA 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 ``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. +# + +# Locate the OptiX distribution. Search relative to the SDK first, then look in the system. + +# Our initial guess will be within the SDK. + +if (WIN32) +# set(OptiX_INSTALL_DIR "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.1.0" CACHE PATH "Path to OptiX installed location.") + find_path(searched_OptiX_INSTALL_DIR + NAME include/optix.h + PATHS + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 7.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 6.5.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 6.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.1.1" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.1.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.0.1" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 5.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK *" + ) + mark_as_advanced(searched_OptiX_INSTALL_DIR) + set(OptiX_INSTALL_DIR ${searched_OptiX_INSTALL_DIR} CACHE PATH "Path to OptiX installed location.") +else() + set(OptiX_INSTALL_DIR $ENV{OptiX_INSTALL_DIR} CACHE PATH "Path to OptiX installed location.") +endif() +# The distribution contains both 32 and 64 bit libraries. Adjust the library +# search path based on the bit-ness of the build. (i.e. 64: bin64, lib64; 32: +# bin, lib). Note that on Mac, the OptiX library is a universal binary, so we +# only need to look in lib and not lib64 for 64 bit builds. +if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT APPLE) + set(bit_dest "64") +else() + set(bit_dest "") +endif() + +macro(OPTIX_find_api_library name version) + find_library(${name}_LIBRARY + NAMES ${name}.${version} ${name} + PATHS "${OptiX_INSTALL_DIR}/lib${bit_dest}" + NO_DEFAULT_PATH + ) + find_library(${name}_LIBRARY + NAMES ${name}.${version} ${name} + ) + if(WIN32) + find_file(${name}_DLL + NAMES ${name}.${version}.dll + PATHS "${OptiX_INSTALL_DIR}/bin${bit_dest}" + NO_DEFAULT_PATH + ) + find_file(${name}_DLL + NAMES ${name}.${version}.dll + ) + endif() +endmacro() + +#OPTIX_find_api_library(optix 7.0.0) +#OPTIX_find_api_library(optixu 7.0.0) +#OPTIX_find_api_library(optix_prime 7.0.0) + +# Include +find_path(OptiX_INCLUDE + NAMES optix.h + PATHS "${OptiX_INSTALL_DIR}/include" + NO_DEFAULT_PATH + ) +find_path(OptiX_INCLUDE + NAMES optix.h + ) + +# Check to make sure we found what we were looking for +function(OptiX_report_error error_message required) + if(OptiX_FIND_REQUIRED AND required) + message(FATAL_ERROR "${error_message}") + else() + if(NOT OptiX_FIND_QUIETLY) + message(STATUS "${error_message}") + endif(NOT OptiX_FIND_QUIETLY) + endif() +endfunction() + +#if(NOT optix_LIBRARY) +# OptiX_report_error("optix library not found. Please locate before proceeding." TRUE) +#endif() +if(NOT OptiX_INCLUDE) + OptiX_report_error("OptiX headers (optix.h and friends) not found. Please locate before proceeding." TRUE) +endif() +#if(NOT optix_prime_LIBRARY) +# OptiX_report_error("optix Prime library not found. Please locate before proceeding." FALSE) +#endif() + +# Macro for setting up dummy targets +function(OptiX_add_imported_library name lib_location dll_lib dependent_libs) + set(CMAKE_IMPORT_FILE_VERSION 1) + + # Create imported target + add_library(${name} SHARED IMPORTED) + + # Import target "optix" for configuration "Debug" + if(WIN32) + set_target_properties(${name} PROPERTIES + IMPORTED_IMPLIB "${lib_location}" + #IMPORTED_LINK_INTERFACE_LIBRARIES "glu32;opengl32" + IMPORTED_LOCATION "${dll_lib}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" + ) + elseif(UNIX) + set_target_properties(${name} PROPERTIES + #IMPORTED_LINK_INTERFACE_LIBRARIES "glu32;opengl32" + IMPORTED_LOCATION "${lib_location}" + # We don't have versioned filenames for now, and it may not even matter. + #IMPORTED_SONAME "${optix_soname}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" + ) + else() + # Unknown system, but at least try and provide the minimum required + # information. + set_target_properties(${name} PROPERTIES + IMPORTED_LOCATION "${lib_location}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" + ) + endif() + + # Commands beyond this point should not need to know the version. + set(CMAKE_IMPORT_FILE_VERSION) +endfunction() + +# Sets up a dummy target +#OptiX_add_imported_library(optix "${optix_LIBRARY}" "${optix_DLL}" "${OPENGL_LIBRARIES}") +#OptiX_add_imported_library(optixu "${optixu_LIBRARY}" "${optixu_DLL}" "") +#OptiX_add_imported_library(optix_prime "${optix_prime_LIBRARY}" "${optix_prime_DLL}" "") + +macro(OptiX_check_same_path libA libB) + if(_optix_path_to_${libA}) + if(NOT _optix_path_to_${libA} STREQUAL _optix_path_to_${libB}) + # ${libA} and ${libB} are in different paths. Make sure there isn't a ${libA} next + # to the ${libB}. + get_filename_component(_optix_name_of_${libA} "${${libA}_LIBRARY}" NAME) + if(EXISTS "${_optix_path_to_${libB}}/${_optix_name_of_${libA}}") + message(WARNING " ${libA} library found next to ${libB} library that is not being used. Due to the way we are using rpath, the copy of ${libA} next to ${libB} will be used during loading instead of the one you intended. Consider putting the libraries in the same directory or moving ${_optix_path_to_${libB}}/${_optix_name_of_${libA} out of the way.") + endif() + endif() + set( _${libA}_rpath "-Wl,-rpath,${_optix_path_to_${libA}}" ) + endif() +endmacro() + +# Since liboptix.1.dylib is built with an install name of @rpath, we need to +# compile our samples with the rpath set to where optix exists. +if(APPLE) + get_filename_component(_optix_path_to_optix "${optix_LIBRARY}" PATH) + if(_optix_path_to_optix) + set( _optix_rpath "-Wl,-rpath,${_optix_path_to_optix}" ) + endif() + get_filename_component(_optix_path_to_optixu "${optixu_LIBRARY}" PATH) + OptiX_check_same_path(optixu optix) + get_filename_component(_optix_path_to_optix_prime "${optix_prime_LIBRARY}" PATH) + OptiX_check_same_path(optix_prime optix) + OptiX_check_same_path(optix_prime optixu) + + set( optix_rpath ${_optix_rpath} ${_optixu_rpath} ${_optix_prime_rpath} ) + list(REMOVE_DUPLICATES optix_rpath) +endif() + diff --git a/crates/optix/examples/common/gdt/cmake/FindTBB.cmake b/crates/optix/examples/common/gdt/cmake/FindTBB.cmake new file mode 100644 index 00000000..a7c4f465 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/FindTBB.cmake @@ -0,0 +1,154 @@ +## ======================================================================== ## +## 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. ## +## ======================================================================== ## + +SET(TBB_VERSION_REQUIRED "3.0") + +IF (NOT TBB_ROOT_PATH) + SET(TBB_ROOT_PATH $ENV{TBB_ROOT_PATH}) +ENDIF() +IF (NOT TBB_ROOT_PATH) + SET(TBB_ROOT_PATH $ENV{TBBROOT}) +ENDIF() + +# detect changed TBB_ROOT_PATH +IF (NOT TBB_ROOT_PATH STREQUAL TBB_ROOT_PATH_LAST) + UNSET(TBB_INCLUDE_DIR CACHE) + UNSET(TBB_LIBRARY CACHE) + UNSET(TBB_LIBRARY_DEBUG CACHE) + UNSET(TBB_LIBRARY_MALLOC CACHE) + UNSET(TBB_LIBRARY_MALLOC_DEBUG CACHE) +ENDIF() + +IF (WIN32) + # workaround for parentheses in variable name / CMP0053 + SET(PROGRAMFILESx86 "PROGRAMFILES(x86)") + SET(PROGRAMFILES32 "$ENV{${PROGRAMFILESx86}}") + IF (NOT PROGRAMFILES32) + SET(PROGRAMFILES32 "$ENV{PROGRAMFILES}") + ENDIF() + IF (NOT PROGRAMFILES32) + SET(PROGRAMFILES32 "C:/Program Files (x86)") + ENDIF() + FIND_PATH(TBB_ROOT_PATH include/tbb/task_scheduler_init.h + DOC "Root of TBB installation" + HINTS ${TBB_ROOT_PATH} + PATHS + ${PROJECT_SOURCE_DIR}/tbb + ${PROJECT_SOURCE_DIR}/../tbb + "${PROGRAMFILES32}/IntelSWTools/compilers_and_libraries/windows/tbb" + "${PROGRAMFILES32}/Intel/Composer XE/tbb" + "${PROGRAMFILES32}/Intel/compilers_and_libraries/windows/tbb" + ) + + IF (CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(TBB_ARCH intel64) + ELSE() + SET(TBB_ARCH ia32) + ENDIF() + + SET(TBB_LIBDIR ${TBB_ROOT_PATH}/lib) + + FIND_PATH(TBB_INCLUDE_DIR tbb/task_scheduler_init.h PATHS ${TBB_ROOT_PATH}/include NO_DEFAULT_PATH) + SET(TBB_LIB_HINTS + PATHS + ${TBB_LIBDIR}/${TBB_ARCH}/vc14 + ${TBB_LIBDIR} + NO_DEFAULT_PATH + ) + FIND_LIBRARY(TBB_LIBRARY tbb ${TBB_LIB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_DEBUG tbb_debug ${TBB_LIB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC tbbmalloc ${TBB_LIB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC_DEBUG tbbmalloc_debug ${TBB_LIB_HINTS}) + +ELSE () + + FIND_PATH(TBB_ROOT_PATH include/tbb/task_scheduler_init.h + DOC "Root of TBB installation" + HINTS ${TBB_ROOT_PATH} + PATHS + ${PROJECT_SOURCE_DIR}/tbb + /opt/intel/composerxe/tbb + /opt/intel/compilers_and_libraries/tbb + /opt/intel/tbb + ) + + IF (APPLE) + FIND_PATH(TBB_INCLUDE_DIR tbb/task_scheduler_init.h PATHS ${TBB_ROOT_PATH}/include NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY tbb PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY_DEBUG tbb_debug PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY_MALLOC tbbmalloc PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + FIND_LIBRARY(TBB_LIBRARY_MALLOC_DEBUG tbbmalloc_debug PATHS ${TBB_ROOT_PATH}/lib NO_DEFAULT_PATH) + ELSE() + FIND_PATH(TBB_INCLUDE_DIR tbb/task_scheduler_init.h PATHS ${TBB_ROOT_PATH}/include NO_DEFAULT_PATH) + SET(TBB_HINTS HINTS ${TBB_ROOT_PATH}/lib/intel64/gcc4.7 ${TBB_ROOT_PATH}/lib/intel64/gcc4.4 ${TBB_ROOT_PATH}/lib ${TBB_ROOT_PATH}/lib64 PATHS /usr/libx86_64-linux-gnu/) + FIND_LIBRARY(TBB_LIBRARY libtbb.so.2 ${TBB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_DEBUG libtbb_debug.so.2 ${TBB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC libtbbmalloc.so.2 ${TBB_HINTS}) + FIND_LIBRARY(TBB_LIBRARY_MALLOC_DEBUG libtbbmalloc_debug.so.2 ${TBB_HINTS}) + ENDIF() +ENDIF() + +SET(TBB_ROOT_PATH_LAST ${TBB_ROOT_PATH} CACHE INTERNAL "Last value of TBB_ROOT_PATH to detect changes") + +SET(TBB_ERROR_MESSAGE + "Threading Building Blocks (TBB) with minimum version ${TBB_VERSION_REQUIRED} not found. +OSPRay uses TBB as default tasking system. Please make sure you have the TBB headers installed as well (the package is typically named 'libtbb-dev' or 'tbb-devel') and/or hint the location of TBB in TBB_ROOT_PATH. +Alternatively, you can try to use OpenMP as tasking system by setting OSPRAY_TASKING_SYSTEM=OpenMP") + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(TBB + ${TBB_ERROR_MESSAGE} + TBB_INCLUDE_DIR TBB_LIBRARY TBB_LIBRARY_MALLOC +) + +# check version +IF (TBB_INCLUDE_DIR) + FILE(READ ${TBB_INCLUDE_DIR}/tbb/tbb_stddef.h TBB_STDDEF_H) + + STRING(REGEX MATCH "#define TBB_VERSION_MAJOR ([0-9]+)" DUMMY "${TBB_STDDEF_H}") + SET(TBB_VERSION_MAJOR ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "#define TBB_VERSION_MINOR ([0-9]+)" DUMMY "${TBB_STDDEF_H}") + SET(TBB_VERSION "${TBB_VERSION_MAJOR}.${CMAKE_MATCH_1}") + + IF (TBB_VERSION VERSION_LESS TBB_VERSION_REQUIRED) + MESSAGE(FATAL_ERROR ${TBB_ERROR_MESSAGE}) + ENDIF() + + SET(TBB_VERSION ${TBB_VERSION} CACHE STRING "TBB Version") + MARK_AS_ADVANCED(TBB_VERSION) +ENDIF() + +IF (TBB_FOUND) + SET(TBB_INCLUDE_DIRS ${TBB_INCLUDE_DIR}) + # NOTE(jda) - TBB found in CentOS 6/7 package manager does not have debug + # versions of the library...silently fall-back to using only the + # libraries which we actually found. + IF (NOT TBB_LIBRARY_DEBUG) + SET(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_LIBRARY_MALLOC}) + ELSE () + SET(TBB_LIBRARIES + optimized ${TBB_LIBRARY} optimized ${TBB_LIBRARY_MALLOC} + debug ${TBB_LIBRARY_DEBUG} debug ${TBB_LIBRARY_MALLOC_DEBUG} + ) + ENDIF() +ENDIF() + +MARK_AS_ADVANCED(TBB_INCLUDE_DIR) +MARK_AS_ADVANCED(TBB_LIBRARY) +MARK_AS_ADVANCED(TBB_LIBRARY_DEBUG) +MARK_AS_ADVANCED(TBB_LIBRARY_MALLOC) +MARK_AS_ADVANCED(TBB_LIBRARY_MALLOC_DEBUG) diff --git a/crates/optix/examples/common/gdt/cmake/configure_build_type.cmake b/crates/optix/examples/common/gdt/cmake/configure_build_type.cmake new file mode 100644 index 00000000..e34b964d --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_build_type.cmake @@ -0,0 +1,41 @@ +# ======================================================================== # +# Copyright 2018-2020 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +# This helper script sets up default build targets for Release/Debug, etc, +# something which each project I worked on seems to need, eventually, so +# having it in one place arguably makes sense. + +if(NOT SET_UP_CONFIGURATIONS_DONE) + set(SET_UP_CONFIGURATIONS_DONE 1) + + # No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator + # Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator. + if(CMAKE_CONFIGURATION_TYPES) # multiconfig generator? + set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) + else() + if(NOT CMAKE_BUILD_TYPE) +# message("Defaulting to release build.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) + endif() + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build") + # set the valid options for cmake-gui drop-down list + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release") + endif() +endif() + +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) \ No newline at end of file diff --git a/crates/optix/examples/common/gdt/cmake/configure_glut.cmake b/crates/optix/examples/common/gdt/cmake/configure_glut.cmake new file mode 100644 index 00000000..8e3a64b4 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_glut.cmake @@ -0,0 +1,20 @@ +# helper script that finds GLUT, either from the system install (linux), or from the included, precompiled binaries (windows) +# Note we *intentionally* do not use the file name of "FindGLUT.cmake" because we want to call the system-provided FindGLUT later on, we just set up some paths, where required + +# legacy gl vs glvnd/glx +if (POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) +endif() + +if (WIN32) + # The default cmake-FindGLUT.cmake script will automatically search in + # - ${GLUT_ROOT_PATH}/Release (fro the lib) + # - ${GLUT_ROOT_PATH}/include + # ... ie, setting this search path _should_ make the default script find the + # right stuff, and set the right variables + set(GLUT_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../3rdParty/freeglut") +endif() + + +find_package(OpenGL REQUIRED) +find_package(GLUT REQUIRED) diff --git a/crates/optix/examples/common/gdt/cmake/configure_optix.cmake b/crates/optix/examples/common/gdt/cmake/configure_optix.cmake new file mode 100644 index 00000000..4023f30b --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_optix.cmake @@ -0,0 +1,68 @@ +# ======================================================================== # +# Copyright 2018 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +set(CMAKE_MODULE_PATH + "${PROJECT_SOURCE_DIR}/cmake" +# "${CMAKE_CURRENT_SOURCE_DIR}/../cmake" + ${CMAKE_MODULE_PATH} + ) + +find_package(CUDA REQUIRED) +find_package(OptiX REQUIRED VERSION 7.0) + +#include_directories(${CUDA_TOOLKIT_INCLUDE}) +if (CUDA_TOOLKIT_ROOT_DIR) + include_directories(${CUDA_TOOLKIT_ROOT_DIR}/include) +endif() +include_directories(${OptiX_INCLUDE}) + +if (WIN32) + add_definitions(-DNOMINMAX) +endif() + +find_program(BIN2C bin2c + DOC "Path to the cuda-sdk bin2c executable.") + +# this macro defines cmake rules that execute the following four steps: +# 1) compile the given cuda file ${cuda_file} to an intermediary PTX file +# 2) use the 'bin2c' tool (that comes with CUDA) to +# create a second intermediary (.c-)file which defines a const string variable +# (named '${c_var_name}') whose (constant) value is the PTX output +# from the previous step. +# 3) compile the given .c file to an intermediary object file (why thus has +# that PTX string 'embedded' as a global constant. +# 4) assign the name of the intermediary .o file to the cmake variable +# 'output_var', which can then be added to cmake targets. +macro(cuda_compile_and_embed output_var cuda_file) + set(c_var_name ${output_var}) + cuda_compile_ptx(ptx_files ${cuda_file}) + list(GET ptx_files 0 ptx_file) + set(embedded_file ${ptx_file}_embedded.c) +# message("adding rule to compile and embed ${cuda_file} to \"const char ${var_name}[];\"") + add_custom_command( + OUTPUT ${embedded_file} + COMMAND ${BIN2C} -c --padd 0 --type char --name ${c_var_name} ${ptx_file} > ${embedded_file} + DEPENDS ${ptx_file} + COMMENT "compiling (and embedding ptx from) ${cuda_file}" + ) + set(${output_var} ${embedded_file}) +endmacro() + +include_directories(${OptiX_INCLUDE}) + +add_definitions(-D__CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__=1) + + diff --git a/crates/optix/examples/common/gdt/cmake/configure_tbb.cmake b/crates/optix/examples/common/gdt/cmake/configure_tbb.cmake new file mode 100644 index 00000000..1aa45852 --- /dev/null +++ b/crates/optix/examples/common/gdt/cmake/configure_tbb.cmake @@ -0,0 +1,21 @@ +# ======================================================================== # +# Copyright 2018-2019 Ingo Wald # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ======================================================================== # + +find_package(TBB REQUIRED) +if (TBB_FOUND) + include_directories(${TBB_INCLUDE_DIR}) +endif() + diff --git a/crates/optix/examples/common/gdt/gdt/gdt.cpp b/crates/optix/examples/common/gdt/gdt/gdt.cpp new file mode 100644 index 00000000..0dcbffb4 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/gdt.cpp @@ -0,0 +1,20 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt.h" +#include "math/LinearSpace.h" +#include "math/AffineSpace.h" + diff --git a/crates/optix/examples/common/gdt/gdt/gdt.h b/crates/optix/examples/common/gdt/gdt/gdt.h new file mode 100644 index 00000000..b4eaa48d --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/gdt.h @@ -0,0 +1,233 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 +#include +#include +#include +#ifdef __CUDA_ARCH__ +# include +#else +# include +#endif +#include +#ifdef __GNUC__ +# include +# include +#endif +#include + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #ifdef min + #undef min + #endif + #ifdef max + #undef max + #endif +#endif + + +#if defined(_MSC_VER) +# define GDT_DLL_EXPORT __declspec(dllexport) +# define GDT_DLL_IMPORT __declspec(dllimport) +#elif defined(__clang__) || defined(__GNUC__) +# define GDT_DLL_EXPORT __attribute__((visibility("default"))) +# define GDT_DLL_IMPORT __attribute__((visibility("default"))) +#else +# define GDT_DLL_EXPORT +# define GDT_DLL_IMPORT +#endif + +#if 1 +# define GDT_INTERFACE /* nothing */ +#else +//#if defined(GDT_DLL_INTERFACE) +# ifdef gdt_EXPORTS +# define GDT_INTERFACE GDT_DLL_EXPORT +# else +# define GDT_INTERFACE GDT_DLL_IMPORT +# endif +//#else +//# define GDT_INTERFACE /*static lib*/ +//#endif +#endif + +#ifndef PRINT +# define PRINT(var) std::cout << #var << "=" << var << std::endl; +# define PING std::cout << __FILE__ << "::" << __LINE__ << ": " << __FUNCTION__ << std::endl; +#endif + +#if defined(__CUDACC__) +# define __gdt_device __device__ +# define __gdt_host __host__ +#else +# define __gdt_device /* ignore */ +# define __gdt_host /* ignore */ +#endif + +# define __both__ __gdt_host __gdt_device + + +#ifdef __GNUC__ + #define MAYBE_UNUSED __attribute__((unused)) +#else + #define MAYBE_UNUSED +#endif + + + +#define GDT_NOTIMPLEMENTED throw std::runtime_error(std::string(__PRETTY_FUNCTION__)+" not implemented") + +#define GDT_TERMINAL_RED "\033[1;31m" +#define GDT_TERMINAL_GREEN "\033[1;32m" +#define GDT_TERMINAL_YELLOW "\033[1;33m" +#define GDT_TERMINAL_BLUE "\033[1;34m" +#define GDT_TERMINAL_RESET "\033[0m" +#define GDT_TERMINAL_DEFAULT GDT_TERMINAL_RESET +#define GDT_TERMINAL_BOLD "\033[1;1m" + + + + + +/*! \namespace gdt GPU Developer Toolbox */ +namespace gdt { + +#ifdef __CUDACC__ + using ::min; + using ::max; + // inline __both__ float abs(float f) { return fabsf(f); } + // inline __both__ double abs(double f) { return fabs(f); } + using std::abs; + // inline __both__ float sin(float f) { return ::sinf(f); } + // inline __both__ double sin(double f) { return ::sin(f); } + // inline __both__ float cos(float f) { return ::cosf(f); } + // inline __both__ double cos(double f) { return ::cos(f); } + + using ::saturate; +#else + using std::min; + using std::max; + using std::abs; + // inline __both__ double sin(double f) { return ::sin(f); } + inline __both__ float saturate(const float &f) { return min(1.f,max(0.f,f)); } +#endif + + // inline __both__ float abs(float f) { return fabsf(f); } + // inline __both__ double abs(double f) { return fabs(f); } + inline __both__ float rcp(float f) { return 1.f/f; } + inline __both__ double rcp(double d) { return 1./d; } + + inline __both__ int32_t divRoundUp(int32_t a, int32_t b) { return (a+b-1)/b; } + inline __both__ uint32_t divRoundUp(uint32_t a, uint32_t b) { return (a+b-1)/b; } + inline __both__ int64_t divRoundUp(int64_t a, int64_t b) { return (a+b-1)/b; } + inline __both__ uint64_t divRoundUp(uint64_t a, uint64_t b) { return (a+b-1)/b; } + +#ifdef __CUDACC__ + using ::sin; // this is the double version + // inline __both__ float sin(float f) { return ::sinf(f); } + using ::cos; // this is the double version + // inline __both__ float cos(float f) { return ::cosf(f); } +#else + using ::sin; // this is the double version + using ::cos; // this is the double version +#endif + +// #ifdef __CUDA_ARCH__ + // using ::sqrt; + // using ::sqrtf; +// #else + namespace overloaded { + /* move all those in a special namespace so they will never get + included - and thus, conflict with, the default namesapce */ + inline __both__ float sqrt(const float f) { return ::sqrtf(f); } + inline __both__ double sqrt(const double d) { return ::sqrt(d); } + } +// #endif +// inline __both__ float rsqrt(const float f) { return 1.f/sqrtf(f); } +// inline __both__ double rsqrt(const double d) { return 1./sqrt(d); } + +#ifdef __WIN32__ +# define osp_snprintf sprintf_s +#else +# define osp_snprintf snprintf +#endif + + /*! added pretty-print function for large numbers, printing 10000000 as "10M" instead */ + inline std::string prettyDouble(const double val) { + const double absVal = abs(val); + char result[1000]; + + if (absVal >= 1e+18f) osp_snprintf(result,1000,"%.1f%c",val/1e18f,'E'); + else if (absVal >= 1e+15f) osp_snprintf(result,1000,"%.1f%c",val/1e15f,'P'); + else if (absVal >= 1e+12f) osp_snprintf(result,1000,"%.1f%c",val/1e12f,'T'); + else if (absVal >= 1e+09f) osp_snprintf(result,1000,"%.1f%c",val/1e09f,'G'); + else if (absVal >= 1e+06f) osp_snprintf(result,1000,"%.1f%c",val/1e06f,'M'); + else if (absVal >= 1e+03f) osp_snprintf(result,1000,"%.1f%c",val/1e03f,'k'); + else if (absVal <= 1e-12f) osp_snprintf(result,1000,"%.1f%c",val*1e15f,'f'); + else if (absVal <= 1e-09f) osp_snprintf(result,1000,"%.1f%c",val*1e12f,'p'); + else if (absVal <= 1e-06f) osp_snprintf(result,1000,"%.1f%c",val*1e09f,'n'); + else if (absVal <= 1e-03f) osp_snprintf(result,1000,"%.1f%c",val*1e06f,'u'); + else if (absVal <= 1e-00f) osp_snprintf(result,1000,"%.1f%c",val*1e03f,'m'); + else osp_snprintf(result,1000,"%f",(float)val); + + return result; + } + + + + inline std::string prettyNumber(const size_t s) + { + char buf[1000]; + if (s >= (1024LL*1024LL*1024LL*1024LL)) { + osp_snprintf(buf, 1000,"%.2fT",s/(1024.f*1024.f*1024.f*1024.f)); + } else if (s >= (1024LL*1024LL*1024LL)) { + osp_snprintf(buf, 1000, "%.2fG",s/(1024.f*1024.f*1024.f)); + } else if (s >= (1024LL*1024LL)) { + osp_snprintf(buf, 1000, "%.2fM",s/(1024.f*1024.f)); + } else if (s >= (1024LL)) { + osp_snprintf(buf, 1000, "%.2fK",s/(1024.f)); + } else { + osp_snprintf(buf,1000,"%zi",s); + } + return buf; + } + + inline double getCurrentTime() + { +#ifdef _WIN32 + SYSTEMTIME tp; GetSystemTime(&tp); + return double(tp.wSecond) + double(tp.wMilliseconds) / 1E3; +#else + struct timeval tp; gettimeofday(&tp,nullptr); + return double(tp.tv_sec) + double(tp.tv_usec)/1E6; +#endif + } + + inline bool hasSuffix(const std::string &s, const std::string &suffix) + { + return s.substr(s.size()-suffix.size()) == suffix; + } +} diff --git a/crates/optix/examples/common/gdt/gdt/math/AffineSpace.h b/crates/optix/examples/common/gdt/gdt/math/AffineSpace.h new file mode 100644 index 00000000..80a306c5 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/AffineSpace.h @@ -0,0 +1,183 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* originally taken (and adapted) from ospray, under following license */ + +// ======================================================================== // +// Copyright 2009-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. // +// ======================================================================== // + +#pragma once + +#include "LinearSpace.h" +#include "box.h" + +namespace gdt { + +#define VectorT typename L::vector_t +#define ScalarT typename L::vector_t::scalar_t + + //////////////////////////////////////////////////////////////////////////////// + // Affine Space + //////////////////////////////////////////////////////////////////////////////// + + template + struct GDT_INTERFACE AffineSpaceT + { + L l; /*< linear part of affine space */ + VectorT p; /*< affine part of affine space */ + + //////////////////////////////////////////////////////////////////////////////// + // Constructors, Assignment, Cast, Copy Operations + //////////////////////////////////////////////////////////////////////////////// + + inline AffineSpaceT ( ) = default; + inline AffineSpaceT ( const AffineSpaceT& other ) { l = other.l; p = other.p; } + inline AffineSpaceT ( const L & other ) { l = other ; p = VectorT(zero); } + inline AffineSpaceT& operator=( const AffineSpaceT& other ) { l = other.l; p = other.p; return *this; } + + inline AffineSpaceT( const VectorT& vx, const VectorT& vy, const VectorT& vz, const VectorT& p ) : l(vx,vy,vz), p(p) {} + inline AffineSpaceT( const L& l, const VectorT& p ) : l(l), p(p) {} + + template inline AffineSpaceT( const AffineSpaceT& s ) : l(s.l), p(s.p) {} + + //////////////////////////////////////////////////////////////////////////////// + // Constants + //////////////////////////////////////////////////////////////////////////////// + + inline AffineSpaceT( ZeroTy ) : l(zero), p(zero) {} + inline AffineSpaceT( OneTy ) : l(one), p(zero) {} + + /*! return matrix for scaling */ + static inline AffineSpaceT scale(const VectorT& s) { return L::scale(s); } + + /*! return matrix for translation */ + static inline AffineSpaceT translate(const VectorT& p) { return AffineSpaceT(one,p); } + + /*! return matrix for rotation, only in 2D */ + static inline AffineSpaceT rotate(const ScalarT& r) { return L::rotate(r); } + + /*! return matrix for rotation around arbitrary point (2D) or axis (3D) */ + static inline 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 inline 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; right-handed coordinate system */ + static inline AffineSpaceT lookat(const VectorT& eye, const VectorT& point, const VectorT& up) { + VectorT Z = normalize(point-eye); + VectorT U = normalize(cross(Z,up)); + VectorT V = cross(U,Z); + return AffineSpaceT(L(U,V,Z),eye); + } + + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline AffineSpaceT operator -( const AffineSpaceT& a ) { return AffineSpaceT(-a.l,-a.p); } + template inline AffineSpaceT operator +( const AffineSpaceT& a ) { return AffineSpaceT(+a.l,+a.p); } + template inline AffineSpaceT rcp( const AffineSpaceT& a ) { L il = rcp(a.l); return AffineSpaceT(il,-(il*a.p)); } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline AffineSpaceT operator +( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l+b.l,a.p+b.p); } + template inline AffineSpaceT operator -( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l-b.l,a.p-b.p); } + + template inline AffineSpaceT operator *( const ScalarT & a, const AffineSpaceT& b ) { return AffineSpaceT(a*b.l,a*b.p); } + template inline AffineSpaceT operator *( const AffineSpaceT& a, const AffineSpaceT& b ) { return AffineSpaceT(a.l*b.l,a.l*b.p+a.p); } + template inline AffineSpaceT operator /( const AffineSpaceT& a, const AffineSpaceT& b ) { return a * rcp(b); } + template inline AffineSpaceT operator /( const AffineSpaceT& a, const ScalarT & b ) { return a * rcp(b); } + + template inline AffineSpaceT& operator *=( AffineSpaceT& a, const AffineSpaceT& b ) { return a = a * b; } + template inline AffineSpaceT& operator *=( AffineSpaceT& a, const ScalarT & b ) { return a = a * b; } + template inline AffineSpaceT& operator /=( AffineSpaceT& a, const AffineSpaceT& b ) { return a = a / b; } + template inline AffineSpaceT& operator /=( AffineSpaceT& a, const ScalarT & b ) { return a = a / b; } + + template inline __both__ const 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 inline __both__ const VectorT xfmVector(const AffineSpaceT& m, const VectorT& v) { return xfmVector(m.l,v); } + template inline __both__ const VectorT xfmNormal(const AffineSpaceT& m, const VectorT& n) { return xfmNormal(m.l,n); } + + + // template + // inline const box_t + // xfmBounds(const AffineSpaceT>> &m, + // const box_t &b) + // { + // box_t dst = empty; + // const vec_t p0(b.lower.x,b.lower.y,b.lower.z); dst.extend(xfmPoint(m,p0)); + // const vec_t p1(b.lower.x,b.lower.y,b.upper.z); dst.extend(xfmPoint(m,p1)); + // const vec_t p2(b.lower.x,b.upper.y,b.lower.z); dst.extend(xfmPoint(m,p2)); + // const vec_t p3(b.lower.x,b.upper.y,b.upper.z); dst.extend(xfmPoint(m,p3)); + // const vec_t p4(b.upper.x,b.lower.y,b.lower.z); dst.extend(xfmPoint(m,p4)); + // const vec_t p5(b.upper.x,b.lower.y,b.upper.z); dst.extend(xfmPoint(m,p5)); + // const vec_t p6(b.upper.x,b.upper.y,b.lower.z); dst.extend(xfmPoint(m,p6)); + // const vec_t p7(b.upper.x,b.upper.y,b.upper.z); dst.extend(xfmPoint(m,p7)); + // return dst; + // } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline bool operator ==( const AffineSpaceT& a, const AffineSpaceT& b ) { return a.l == b.l && a.p == b.p; } + template inline bool operator !=( const AffineSpaceT& a, const AffineSpaceT& b ) { return a.l != b.l || a.p != b.p; } + + //////////////////////////////////////////////////////////////////////////////// + // Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline std::ostream& operator<<(std::ostream& cout, const AffineSpaceT& m) { + return cout << "{ l = " << m.l << ", p = " << m.p << " }"; + } + + //////////////////////////////////////////////////////////////////////////////// + // Type Aliases + //////////////////////////////////////////////////////////////////////////////// + + using AffineSpace2f = AffineSpaceT; + using AffineSpace3f = AffineSpaceT; + using AffineSpace3fa = AffineSpaceT; + using OrthonormalSpace3f = AffineSpaceT; + + using affine2f = AffineSpace2f; + using affine3f = AffineSpace3f; + + //////////////////////////////////////////////////////////////////////////////// + /*! Template Specialization for 2D: return matrix for rotation around point (rotation around arbitrarty vector is not meaningful in 2D) */ + template<> inline AffineSpace2f AffineSpace2f::rotate(const vec2f& p, const float& r) + { return translate(+p) * AffineSpace2f(LinearSpace2f::rotate(r)) * translate(-p); } + +#undef VectorT +#undef ScalarT + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/LinearSpace.h b/crates/optix/examples/common/gdt/gdt/math/LinearSpace.h new file mode 100644 index 00000000..50bd4066 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/LinearSpace.h @@ -0,0 +1,341 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* originally taken (and adapted) from ospray, under following license */ + +// ======================================================================== // +// Copyright 2009-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. // +// ======================================================================== // + +#pragma once + +#include "vec.h" +#include "Quaternion.h" + +namespace gdt { + + //////////////////////////////////////////////////////////////////////////////// + /// 2D Linear Transform (2x2 Matrix) + //////////////////////////////////////////////////////////////////////////////// + + template struct GDT_INTERFACE LinearSpace2 + { + using vector_t = T; + // using Scalar = typename T::scalar_t; + // using vector_t = T; + using scalar_t = typename T::scalar_t; + + /*! default matrix constructor */ + inline LinearSpace2 ( ) = default; + inline LinearSpace2 ( const LinearSpace2& other ) { vx = other.vx; vy = other.vy; } + inline LinearSpace2& operator=( const LinearSpace2& other ) { vx = other.vx; vy = other.vy; return *this; } + + template inline LinearSpace2( const LinearSpace2& s ) : vx(s.vx), vy(s.vy) {} + + /*! matrix construction from column vectors */ + inline __both__ LinearSpace2(const vector_t& vx, const vector_t& vy) + : vx(vx), vy(vy) {} + + /*! matrix construction from row mayor data */ + inline __both__ LinearSpace2(const scalar_t& m00, const scalar_t& m01, + const scalar_t& m10, const scalar_t& m11) + : vx(m00,m10), vy(m01,m11) {} + + /*! compute the determinant of the matrix */ + inline __both__ const scalar_t det() const { return vx.x*vy.y - vx.y*vy.x; } + + /*! compute adjoint matrix */ + inline __both__ const LinearSpace2 adjoint() const { return LinearSpace2(vy.y,-vy.x,-vx.y,vx.x); } + + /*! compute inverse matrix */ + inline __both__ const LinearSpace2 inverse() const { return adjoint()/det(); } + + /*! compute transposed matrix */ + inline __both__ const LinearSpace2 transposed() const { return LinearSpace2(vx.x,vx.y,vy.x,vy.y); } + + /*! returns first row of matrix */ + inline const vector_t row0() const { return vector_t(vx.x,vy.x); } + + /*! returns second row of matrix */ + inline const vector_t row1() const { return vector_t(vx.y,vy.y); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + inline LinearSpace2( ZeroTy ) : vx(zero), vy(zero) {} + inline LinearSpace2( OneTy ) : vx(one, zero), vy(zero, one) {} + + /*! return matrix for scaling */ + static inline LinearSpace2 scale(const vector_t& s) { + return LinearSpace2(s.x, 0, + 0 , s.y); + } + + /*! return matrix for rotation */ + static inline LinearSpace2 rotate(const scalar_t& r) { + scalar_t 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_t mirror(one); + if (m.det() < scalar_t(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_t vx,vy; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template __both__ inline LinearSpace2 operator -( const LinearSpace2& a ) { return LinearSpace2(-a.vx,-a.vy); } + template __both__ inline LinearSpace2 operator +( const LinearSpace2& a ) { return LinearSpace2(+a.vx,+a.vy); } + template __both__ inline LinearSpace2 rcp ( const LinearSpace2& a ) { return a.inverse(); } + + //////////////////////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline LinearSpace2 operator +( const LinearSpace2& a, const LinearSpace2& b ) { return LinearSpace2(a.vx+b.vx,a.vy+b.vy); } + template inline LinearSpace2 operator -( const LinearSpace2& a, const LinearSpace2& b ) { return LinearSpace2(a.vx-b.vx,a.vy-b.vy); } + + template inline LinearSpace2 operator*(const typename T::scalar_t & a, const LinearSpace2& b) { return LinearSpace2(a*b.vx, a*b.vy); } + template inline T operator*(const LinearSpace2& a, const T & b) { return b.x*a.vx + b.y*a.vy; } + template inline LinearSpace2 operator*(const LinearSpace2& a, const LinearSpace2& b) { return LinearSpace2(a*b.vx, a*b.vy); } + + template inline LinearSpace2 operator/(const LinearSpace2& a, const typename T::scalar_t & b) { return LinearSpace2(a.vx/b, a.vy/b); } + template inline LinearSpace2 operator/(const LinearSpace2& a, const LinearSpace2& b) { return a * rcp(b); } + + template inline LinearSpace2& operator *=( LinearSpace2& a, const LinearSpace2& b ) { return a = a * b; } + template inline LinearSpace2& operator /=( LinearSpace2& a, const LinearSpace2& b ) { return a = a / b; } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline bool operator ==( const LinearSpace2& a, const LinearSpace2& b ) { return a.vx == b.vx && a.vy == b.vy; } + template inline bool operator !=( const LinearSpace2& a, const LinearSpace2& b ) { return a.vx != b.vx || a.vy != b.vy; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template static std::ostream& operator<<(std::ostream& cout, const LinearSpace2& m) { + return cout << "{ vx = " << m.vx << ", vy = " << m.vy << "}"; + } + + //////////////////////////////////////////////////////////////////////////////// + /// 3D Linear Transform (3x3 Matrix) + //////////////////////////////////////////////////////////////////////////////// + + template + struct GDT_INTERFACE LinearSpace3 + { + // using vector_t = T; + using scalar_t = typename T::scalar_t; + using vector_t = T; + // using scalar_t = typename T::scalar_t; + + /*! default matrix constructor */ + inline LinearSpace3 ( ) = default; + inline __both__ LinearSpace3 ( const LinearSpace3& other ) { vx = other.vx; vy = other.vy; vz = other.vz; } + inline __both__ LinearSpace3& operator=( const LinearSpace3& other ) { vx = other.vx; vy = other.vy; vz = other.vz; return *this; } + + template inline __both__ LinearSpace3( const LinearSpace3& s ) : vx(s.vx), vy(s.vy), vz(s.vz) {} + + /*! matrix construction from column vectors */ + inline __both__ LinearSpace3(const vector_t& vx, const vector_t& vy, const vector_t& vz) + : vx(vx), vy(vy), vz(vz) {} + + /*! construction from quaternion */ + inline __both__ 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 */ + inline __both__ LinearSpace3(const scalar_t& m00, const scalar_t& m01, const scalar_t& m02, + const scalar_t& m10, const scalar_t& m11, const scalar_t& m12, + const scalar_t& m20, const scalar_t& m21, const scalar_t& m22) + : vx(m00,m10,m20), vy(m01,m11,m21), vz(m02,m12,m22) {} + + /*! compute the determinant of the matrix */ + inline __both__ const scalar_t det() const { return dot(vx,cross(vy,vz)); } + + /*! compute adjoint matrix */ + inline __both__ const LinearSpace3 adjoint() const { return LinearSpace3(cross(vy,vz),cross(vz,vx),cross(vx,vy)).transposed(); } + + /*! compute inverse matrix */ + inline __both__ const LinearSpace3 inverse() const { return adjoint()/det(); } + + /*! compute transposed matrix */ + inline __both__ 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 */ + inline __both__ const vector_t row0() const { return vector_t(vx.x,vy.x,vz.x); } + + /*! returns second row of matrix */ + inline __both__ const vector_t row1() const { return vector_t(vx.y,vy.y,vz.y); } + + /*! returns third row of matrix */ + inline __both__ const vector_t row2() const { return vector_t(vx.z,vy.z,vz.z); } + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + inline __both__ LinearSpace3( ZeroTy ) : vx(zero), vy(zero), vz(zero) {} + inline __both__ LinearSpace3( OneTy ) : vx(one, zero, zero), vy(zero, one, zero), vz(zero, zero, one) {} + + /*! return matrix for scaling */ + static inline __both__ LinearSpace3 scale(const vector_t& s) { + return LinearSpace3(s.x, 0, 0, + 0 , s.y, 0, + 0 , 0, s.z); + } + + /*! return matrix for rotation around arbitrary axis */ + static inline __both__ LinearSpace3 rotate(const vector_t& _u, const scalar_t& r) { + vector_t u = normalize(_u); + scalar_t 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 */ + T vx,vy,vz; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Unary Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline __both__ LinearSpace3 operator -( const LinearSpace3& a ) { return LinearSpace3(-a.vx,-a.vy,-a.vz); } + template inline __both__ LinearSpace3 operator +( const LinearSpace3& a ) { return LinearSpace3(+a.vx,+a.vy,+a.vz); } + template inline __both__ LinearSpace3 rcp ( const LinearSpace3& a ) { return a.inverse(); } + + /* constructs a coordinate frame form a normalized normal */ + template inline __both__ LinearSpace3 frame(const T& N) + { + const T dx0 = cross(T(one,zero,zero),N); + const T dx1 = cross(T(zero,one,zero),N); + 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 inline __both__ 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 inline __both__ 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 inline __both__ LinearSpace3 operator +( const LinearSpace3& a, const LinearSpace3& b ) { return LinearSpace3(a.vx+b.vx,a.vy+b.vy,a.vz+b.vz); } + template inline __both__ LinearSpace3 operator -( const LinearSpace3& a, const LinearSpace3& b ) { return LinearSpace3(a.vx-b.vx,a.vy-b.vy,a.vz-b.vz); } + + template inline __both__ LinearSpace3 operator*(const typename T::scalar_t & a, const LinearSpace3& b) { return LinearSpace3(a*b.vx, a*b.vy, a*b.vz); } + template inline T operator*(const LinearSpace3& a, const T & b) { return b.x*a.vx + b.y*a.vy + b.z*a.vz; } + template inline __both__ LinearSpace3 operator*(const LinearSpace3& a, const LinearSpace3& b) { return LinearSpace3(a*b.vx, a*b.vy, a*b.vz); } + + template __both__ inline LinearSpace3 operator/(const LinearSpace3& a, const typename T::scalar_t & b) { return LinearSpace3(a.vx/b, a.vy/b, a.vz/b); } + + template __both__ inline LinearSpace3 operator/(const LinearSpace3& a, const LinearSpace3& b) { return a * rcp(b); } + + template inline LinearSpace3& operator *=( LinearSpace3& a, const LinearSpace3& b ) { return a = a * b; } + template inline LinearSpace3& operator /=( LinearSpace3& a, const LinearSpace3& b ) { return a = a / b; } + + template inline __both__ 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 inline __both__ 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 inline __both__ T xfmNormal(const LinearSpace3& s, const T& a) { return xfmVector(s.inverse().transposed(),a); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline bool operator ==( const LinearSpace3& a, const LinearSpace3& b ) { return a.vx == b.vx && a.vy == b.vy && a.vz == b.vz; } + template inline bool operator !=( const LinearSpace3& a, const LinearSpace3& b ) { return a.vx != b.vx || a.vy != b.vy || a.vz != b.vz; } + + //////////////////////////////////////////////////////////////////////////////// + /// Output Operators + //////////////////////////////////////////////////////////////////////////////// + + template inline std::ostream& operator<<(std::ostream& cout, const LinearSpace3& m) { + return cout << "{ vx = " << m.vx << ", vy = " << m.vy << ", vz = " << m.vz << "}"; + } + + /*! Shortcuts for common linear spaces. */ + using LinearSpace2f = LinearSpace2 ; + using LinearSpace3f = LinearSpace3 ; + using LinearSpace3fa = LinearSpace3; + + using linear2f = LinearSpace2f; + using linear3f = LinearSpace3f; +} // ::ospcommon diff --git a/crates/optix/examples/common/gdt/gdt/math/Quaternion.h b/crates/optix/examples/common/gdt/gdt/math/Quaternion.h new file mode 100644 index 00000000..ed62e924 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/Quaternion.h @@ -0,0 +1,227 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* originally taken (and adapted) from ospray, under following license */ + +// ======================================================================== // +// Copyright 2009-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. // +// ======================================================================== // + +#pragma once + +#include "vec.h" + +namespace gdt +{ + //////////////////////////////////////////////////////////////// + // Quaternion Struct + //////////////////////////////////////////////////////////////// + + template + struct QuaternionT + { + typedef vec_t Vector; + + //////////////////////////////////////////////////////////////////////////////// + /// Construction + //////////////////////////////////////////////////////////////////////////////// + + __both__ QuaternionT ( void ) { } + __both__ QuaternionT ( const QuaternionT& other ) { r = other.r; i = other.i; j = other.j; k = other.k; } + __both__ QuaternionT& operator=( const QuaternionT& other ) { r = other.r; i = other.i; j = other.j; k = other.k; return *this; } + + __both__ QuaternionT( const T& r ) : r(r), i(zero), j(zero), k(zero) {} + __both__ explicit QuaternionT( const Vector& v ) : r(zero), i(v.x), j(v.y), k(v.z) {} + __both__ QuaternionT( const T& r, const T& i, const T& j, const T& k ) : r(r), i(i), j(j), k(k) {} + __both__ QuaternionT( const T& r, const Vector& v ) : r(r), i(v.x), j(v.y), k(v.z) {} + + __inline QuaternionT( const Vector& vx, const Vector& vy, const Vector& vz ); + __inline QuaternionT( const T& yaw, const T& pitch, const T& roll ); + + //////////////////////////////////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////////////////////////////////// + + __both__ QuaternionT( ZeroTy ) : r(zero), i(zero), j(zero), k(zero) {} + __both__ QuaternionT( OneTy ) : r( one), i(zero), j(zero), k(zero) {} + + /*! return quaternion for rotation around arbitrary axis */ + static __both__ QuaternionT rotate(const Vector& 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 */ + __both__ const Vector v( ) const { return Vector(i, j, k); } + + public: + T r, i, j, k; + }; + + template __both__ QuaternionT operator *( const T & a, const QuaternionT& b ) { return QuaternionT(a * b.r, a * b.i, a * b.j, a * b.k); } + template __both__ 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 __both__ QuaternionT operator +( const QuaternionT& a ) { return QuaternionT(+a.r, +a.i, +a.j, +a.k); } + template __both__ QuaternionT operator -( const QuaternionT& a ) { return QuaternionT(-a.r, -a.i, -a.j, -a.k); } + template __both__ QuaternionT conj ( const QuaternionT& a ) { return QuaternionT(a.r, -a.i, -a.j, -a.k); } + template __both__ T abs ( const QuaternionT& a ) { return sqrt(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + template __both__ 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 __both__ QuaternionT normalize ( const QuaternionT& a ) { return a*rsqrt(a.r*a.r + a.i*a.i + a.j*a.j + a.k*a.k); } + + //////////////////////////////////////////////////////////////// + // Binary Operators + //////////////////////////////////////////////////////////////// + + template __both__ QuaternionT operator +( const T & a, const QuaternionT& b ) { return QuaternionT(a + b.r, b.i, b.j, b.k); } + template __both__ QuaternionT operator +( const QuaternionT& a, const T & b ) { return QuaternionT(a.r + b, a.i, a.j, a.k); } + template __both__ 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 __both__ QuaternionT operator -( const T & a, const QuaternionT& b ) { return QuaternionT(a - b.r, -b.i, -b.j, -b.k); } + template __both__ QuaternionT operator -( const QuaternionT& a, const T & b ) { return QuaternionT(a.r - b, a.i, a.j, a.k); } + template __both__ 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 __both__ typename QuaternionT::Vector operator *( const QuaternionT& a, const typename QuaternionT::Vector & b ) { return (a*QuaternionT(b)*conj(a)).v(); } + template __both__ 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 __both__ QuaternionT operator /( const T & a, const QuaternionT& b ) { return a*rcp(b); } + template __both__ QuaternionT operator /( const QuaternionT& a, const T & b ) { return a*rcp(b); } + template __both__ QuaternionT operator /( const QuaternionT& a, const QuaternionT& b ) { return a*rcp(b); } + + template __both__ QuaternionT& operator +=( QuaternionT& a, const T & b ) { return a = a+b; } + template __both__ QuaternionT& operator +=( QuaternionT& a, const QuaternionT& b ) { return a = a+b; } + template __both__ QuaternionT& operator -=( QuaternionT& a, const T & b ) { return a = a-b; } + template __both__ QuaternionT& operator -=( QuaternionT& a, const QuaternionT& b ) { return a = a-b; } + template __both__ QuaternionT& operator *=( QuaternionT& a, const T & b ) { return a = a*b; } + template __both__ QuaternionT& operator *=( QuaternionT& a, const QuaternionT& b ) { return a = a*b; } + template __both__ QuaternionT& operator /=( QuaternionT& a, const T & b ) { return a = a*rcp(b); } + template __both__ QuaternionT& operator /=( QuaternionT& a, const QuaternionT& b ) { return a = a*rcp(b); } + + template __both__ typename QuaternionT::Vector + xfmPoint ( const QuaternionT& a, + const typename QuaternionT::Vector& b ) + { return (a*QuaternionT(b)*conj(a)).v(); } + + template __both__ typename QuaternionT::Vector + xfmQuaternion( const QuaternionT& a, + const typename QuaternionT::Vector& b ) + { return (a*QuaternionT(b)*conj(a)).v(); } + + + template __both__ typename QuaternionT::Vector + xfmNormal( const QuaternionT& a, + const typename QuaternionT::Vector& b ) + { return (a*QuaternionT(b)*conj(a)).v(); } + + //////////////////////////////////////////////////////////////////////////////// + /// Comparison Operators + //////////////////////////////////////////////////////////////////////////////// + + template __both__ 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 __both__ 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 typename QuaternionT::Vector& vx, + const typename QuaternionT::Vector& vy, + const typename QuaternionT::Vector& 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 std::ostream& operator<<(std::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; +} diff --git a/crates/optix/examples/common/gdt/gdt/math/box.h b/crates/optix/examples/common/gdt/gdt/math/box.h new file mode 100644 index 00000000..def74c79 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/box.h @@ -0,0 +1,223 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/math/vec.h" + +namespace gdt { + + template + struct interval { + typedef T scalar_t; + inline __both__ interval() + : lower(gdt::empty_bounds_lower()), + upper(gdt::empty_bounds_upper()) + {} + inline __both__ interval(T begin, T end) : begin(begin), end(end) {} + + union { + T begin; + T lower; + T lo; + }; + union { + T end; + T upper; + T hi; + }; + + inline __both__ bool contains(const T &t) const { return t >= lower && t <= upper; } + inline __both__ bool is_empty() const { return begin > end; } + inline __both__ T center() const { return (begin+end)/2; } + inline __both__ T span() const { return end - begin; } + inline __both__ T diagonal() const { return end - begin; } + inline __both__ interval &extend(const T &t) + { lower = min(lower,t); upper = max(upper,t); return *this; } + inline __both__ interval &extend(const interval &t) + { lower = min(lower,t.lower); upper = max(upper,t.upper); return *this; } + + static inline __both__ interval positive() + { + return interval(0.f,gdt::open_range_upper()); + } + }; + + template + inline __both__ std::ostream &operator<<(std::ostream &o, const interval &b) + { +#ifndef __CUDACC__ + o << "[" << b.lower << ":" << b.upper << "]"; +#endif + return o; + } + + template + inline __both__ interval build_interval(const T &a, const T &b) + { return interval(min(a,b),max(a,b)); } + + template + inline __both__ interval intersect(const interval &a, const interval &b) + { return interval(max(a.lower,b.lower),min(a.upper,b.upper)); } + + template + inline __both__ interval operator-(const interval &a, const T &b) + { return interval(a.lower-b,a.upper-b); } + + template + inline __both__ interval operator*(const interval &a, const T &b) + { return build_interval(a.lower*b,a.upper*b); } + + template + inline __both__ bool operator==(const interval &a, const interval &b) + { return a.lower == b.lower && a.upper == b.upper; } + + template + inline __both__ bool operator!=(const interval &a, const interval &b) + { return !(a == b); } + + + + template + struct box_t { + typedef T vec_t; + typedef typename T::scalar_t scalar_t; + enum { dims = T::dims }; + + inline __both__ box_t() + : lower(gdt::empty_bounds_lower()), + upper(gdt::empty_bounds_upper()) + {} + + // /*! construct a new, origin-oriented box of given size */ + // explicit inline __both__ box_t(const vec_t &box_size) + // : lower(vec_t(0)), + // upper(box_size) + // {} + /*! construct a new box around a single point */ + explicit inline __both__ box_t(const vec_t &v) + : lower(v), + upper(v) + {} + + /*! construct a new, origin-oriented box of given size */ + inline __both__ box_t(const vec_t &lo, const vec_t &hi) + : lower(lo), + upper(hi) + {} + + /*! returns new box including both ourselves _and_ the given point */ + inline __both__ box_t including(const vec_t &other) const + { return box_t(min(lower,other),max(upper,other)); } + + + /*! returns new box including both ourselves _and_ the given point */ + inline __both__ box_t &extend(const vec_t &other) + { lower = min(lower,other); upper = max(upper,other); return *this; } + /*! returns new box including both ourselves _and_ the given point */ + inline __both__ box_t &extend(const box_t &other) + { lower = min(lower,other.lower); upper = max(upper,other.upper); return *this; } + + + /*! get the d-th dimensional slab (lo[dim]..hi[dim] */ + inline __both__ interval get_slab(const uint32_t dim) + { + return interval(lower[dim],upper[dim]); + } + + inline __both__ bool contains(const vec_t &point) const + { return !(any_less_than(point,lower) || any_greater_than(point,upper)); } + + inline __both__ bool overlaps(const box_t &other) const + { return !(any_less_than(other.upper,lower) || any_greater_than(other.lower,upper)); } + + inline __both__ vec_t center() const { return (lower+upper)/(typename vec_t::scalar_t)2; } + inline __both__ vec_t span() const { return upper-lower; } + inline __both__ vec_t size() const { return upper-lower; } + + inline __both__ typename long_type_of::type volume() const + { return gdt::volume(size()); } + + inline __both__ bool empty() const { return any_less_than(upper,lower); } + + vec_t lower, upper; + }; + + // ======================================================= + // default functions + // ======================================================= + + template + inline __both__ typename long_type_of::type area(const box_t> &b) + { return area(b.upper - b.lower); } + + template + inline __both__ typename long_type_of::type area(const box_t> &b) + { + const vec_t diag = b.upper - b.lower; + return 2.f*(area(vec_t(diag.x,diag.y))+ + area(vec_t(diag.y,diag.z))+ + area(vec_t(diag.z,diag.x))); + } + + template + inline __both__ typename long_type_of::type volume(const box_t> &b) + { + const vec_t diag = b.upper - b.lower; + return diag.x*diag.y*diag.z; + } + + template + inline __both__ std::ostream &operator<<(std::ostream &o, const box_t &b) + { +#ifndef __CUDACC__ + o << "[" << b.lower << ":" << b.upper << "]"; +#endif + return o; + } + + template + inline __both__ box_t intersection(const box_t &a, const box_t &b) + { return box_t(max(a.lower,b.lower),min(a.upper,b.upper)); } + + template + inline __both__ bool operator==(const box_t &a, const box_t &b) + { return a.lower == b.lower && a.upper == b.upper; } + + template + inline __both__ bool operator!=(const box_t &a, const box_t &b) + { return !(a == b); } + + + + + // ======================================================= + // default instantiations + // ======================================================= + +#define _define_box_types(T,t) \ + typedef box_t> box2##t; \ + typedef box_t> box3##t; \ + typedef box_t> box4##t; \ + typedef box_t> box3##t##a; \ + + _define_box_types(int,i); + _define_box_types(unsigned int,ui); + _define_box_types(float,f); + +#undef _define_box_types + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/constants.h b/crates/optix/examples/common/gdt/gdt/math/constants.h new file mode 100644 index 00000000..bbacb22a --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/constants.h @@ -0,0 +1,185 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/math/vec.h" +#ifdef __CUDA_ARCH__ +#include +#else +#include +#endif + +#ifndef M_PI + #define M_PI 3.141593f +#endif + +namespace gdt { + + static struct ZeroTy + { + __both__ operator double ( ) const { return 0; } + __both__ operator float ( ) const { return 0; } + __both__ operator long long( ) const { return 0; } + __both__ operator unsigned long long( ) const { return 0; } + __both__ operator long ( ) const { return 0; } + __both__ operator unsigned long ( ) const { return 0; } + __both__ operator int ( ) const { return 0; } + __both__ operator unsigned int ( ) const { return 0; } + __both__ operator short ( ) const { return 0; } + __both__ operator unsigned short ( ) const { return 0; } + __both__ operator char ( ) const { return 0; } + __both__ operator unsigned char ( ) const { return 0; } + } zero MAYBE_UNUSED; + + static struct OneTy + { + __both__ operator double ( ) const { return 1; } + __both__ operator float ( ) const { return 1; } + __both__ operator long long( ) const { return 1; } + __both__ operator unsigned long long( ) const { return 1; } + __both__ operator long ( ) const { return 1; } + __both__ operator unsigned long ( ) const { return 1; } + __both__ operator int ( ) const { return 1; } + __both__ operator unsigned int ( ) const { return 1; } + __both__ operator short ( ) const { return 1; } + __both__ operator unsigned short ( ) const { return 1; } + __both__ operator char ( ) const { return 1; } + __both__ operator unsigned char ( ) const { return 1; } + } one MAYBE_UNUSED; + + static struct NegInfTy + { +#ifdef __CUDA_ARCH__ + __device__ operator double ( ) const { return -CUDART_INF; } + __device__ operator float ( ) const { return -CUDART_INF_F; } +#else + __both__ operator double ( ) const { return -std::numeric_limits::infinity(); } + __both__ operator float ( ) const { return -std::numeric_limits::infinity(); } + __both__ operator long long( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned long long( ) const { return std::numeric_limits::min(); } + __both__ operator long ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned long ( ) const { return std::numeric_limits::min(); } + __both__ operator int ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned int ( ) const { return std::numeric_limits::min(); } + __both__ operator short ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned short ( ) const { return std::numeric_limits::min(); } + __both__ operator char ( ) const { return std::numeric_limits::min(); } + __both__ operator unsigned char ( ) const { return std::numeric_limits::min(); } +#endif + } neg_inf MAYBE_UNUSED; + + inline __both__ float infty() { +#ifdef __CUDA_ARCH__ + return CUDART_INF_F; +#else + return std::numeric_limits::infinity(); +#endif + } + + static struct PosInfTy + { +#ifdef __CUDA_ARCH__ + __device__ operator double ( ) const { return CUDART_INF; } + __device__ operator float ( ) const { return CUDART_INF_F; } +#else + __both__ operator double ( ) const { return std::numeric_limits::infinity(); } + __both__ operator float ( ) const { return std::numeric_limits::infinity(); } + __both__ operator long long( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned long long( ) const { return std::numeric_limits::max(); } + __both__ operator long ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned long ( ) const { return std::numeric_limits::max(); } + __both__ operator int ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned int ( ) const { return std::numeric_limits::max(); } + __both__ operator short ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned short ( ) const { return std::numeric_limits::max(); } + __both__ operator char ( ) const { return std::numeric_limits::max(); } + __both__ operator unsigned char ( ) const { return std::numeric_limits::max(); } +#endif + } inf MAYBE_UNUSED, pos_inf MAYBE_UNUSED; + + static struct NaNTy + { +#ifdef __CUDA_ARCH__ + __device__ operator double( ) const { return CUDART_NAN_F; } + __device__ operator float ( ) const { return CUDART_NAN; } +#else + __both__ operator double( ) const { return std::numeric_limits::quiet_NaN(); } + __both__ operator float ( ) const { return std::numeric_limits::quiet_NaN(); } +#endif + } nan MAYBE_UNUSED; + + static struct UlpTy + { +#ifdef __CUDACC__ + // todo +#else + __both__ operator double( ) const { return std::numeric_limits::epsilon(); } + __both__ operator float ( ) const { return std::numeric_limits::epsilon(); } +#endif + } ulp MAYBE_UNUSED; + + + + template + struct limits_traits; + + template<> struct limits_traits { + template static inline __both__ T value_limits_lower(T) { return std::numeric_limits::min(); } + template static inline __both__ T value_limits_upper(T) { return std::numeric_limits::max(); } + }; + template<> struct limits_traits { + template static inline __both__ T value_limits_lower(T) { return (T)NegInfTy(); }//{ return -std::numeric_limits::infinity(); } + template static inline __both__ T value_limits_upper(T) { return (T)PosInfTy(); }//{ return +std::numeric_limits::infinity(); } + }; + + /*! lower value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_bounds_lower() + { + return limits_traits::is_integer>::value_limits_upper(T()); + } + + /*! upper value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_bounds_upper() + { + return limits_traits::is_integer>::value_limits_lower(T()); + } + + /*! lower value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_range_lower() + { + return limits_traits::is_integer>::value_limits_upper(T()); + } + + /*! upper value of a completely *empty* range [+inf..-inf] */ + template inline __both__ T empty_range_upper() + { + return limits_traits::is_integer>::value_limits_lower(T()); + } + + /*! lower value of a completely open range [-inf..+inf] */ + template inline __both__ T open_range_lower() + { + return limits_traits::is_integer>::value_limits_lower(T()); + } + + /*! upper value of a completely open range [-inf..+inf] */ + template inline __both__ T open_range_upper() + { + return limits_traits::is_integer>::value_limits_upper(T()); + } + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/fixedpoint.h b/crates/optix/examples/common/gdt/gdt/math/fixedpoint.h new file mode 100644 index 00000000..069be114 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/fixedpoint.h @@ -0,0 +1,36 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/gdt.h" +#include "gdt/math/constants.h" +#include + +namespace gdt { + + /*! a n-bit fixed-point float in the [0..1] region */ + template + struct FixedPoint { + FixedPoint(); + + float operator float() const { + return bits / float((1ULL << Nbits)-1); + } + storageT bits; + }; +} + diff --git a/crates/optix/examples/common/gdt/gdt/math/vec.h b/crates/optix/examples/common/gdt/gdt/math/vec.h new file mode 100644 index 00000000..72e3cf4a --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec.h @@ -0,0 +1,400 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 "gdt/gdt.h" +#include "gdt/math/constants.h" +#include +#include +#include + +#ifndef __CUDACC__ +// define builtins for IDE +struct float2 { + float x; + float y; +}; +struct float3 { + float x; + float y; + float z; +}; +struct float4 { + float x; + float y; + float z; + float w; +}; +#endif + +namespace gdt { + + template struct long_type_of { typedef T type; }; + template<> struct long_type_of { typedef int64_t type; }; + template<> struct long_type_of { typedef uint64_t type; }; + + template + struct GDT_INTERFACE vec_t { T t[N]; }; + + + template struct BinaryOpResultType; + + // Binary Result type: scalar type with itself always returns same type + template + struct BinaryOpResultType { typedef ScalarType type; }; + + template<> struct BinaryOpResultType { typedef float type; }; + template<> struct BinaryOpResultType { typedef float type; }; + template<> struct BinaryOpResultType { typedef float type; }; + template<> struct BinaryOpResultType { typedef float type; }; + + template<> struct BinaryOpResultType { typedef double type; }; + template<> struct BinaryOpResultType { typedef double type; }; + template<> struct BinaryOpResultType { typedef double type; }; + template<> struct BinaryOpResultType { typedef double type; }; + + // ------------------------------------------------------------------ + // vec1 - not really a vector, but makes a scalar look like a + // vector, so we can use it in, say, box1f + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 1 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + inline __both__ vec_t(const T &v) : v(v) {} + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->v = other.v; + return *this; + } + + /*! construct 2-vector from 2-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) : v(o.v) {} + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + union { + T v; + T x; //!< just to allow all vec types to use x,y,z,w,... + }; + }; + + // ------------------------------------------------------------------ + // vec2 + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 2 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + inline __both__ vec_t(const T &t) : x(t), y(t) {} + inline __both__ vec_t(const T &x, const T &y) : x(x), y(y) {} +#ifdef __CUDACC__ + inline __both__ vec_t(const float2 v) : x(v.x), y(v.y) {} + inline __both__ vec_t(const int2 v) : x(v.x), y(v.y) {} + inline __both__ vec_t(const uint2 v) : x(v.x), y(v.y) {} + + inline __both__ operator float2() const { return make_float2(x,y); } + inline __both__ operator int2() const { return make_int2(x,y); } + inline __both__ operator uint2() const { return make_uint2(x,y); } + // inline __both__ vec_t(const size_t2 v) : x(v.x), y(v.y), z(v.z) {} + // inline __both__ operator size_t2() { return make_size_t2(x,y); } +#endif + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->x = other.x; + this->y = other.y; + return *this; + } + + /*! construct 2-vector from 2-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) : x((T)o.x), y((T)o.y) {} + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + union { + struct { T x, y; }; + struct { T s, t; }; + struct { T u, v; }; + }; + }; + + // ------------------------------------------------------------------ + // vec3 + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 3 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + inline __both__ vec_t(const T &t) : x(t), y(t), z(t) {} + inline __both__ vec_t(const T &_x, const T &_y, const T &_z) : x(_x), y(_y), z(_z) {} +#ifdef __CUDACC__ + inline __both__ vec_t(const int3 &v) : x(v.x), y(v.y), z(v.z) {} + inline __both__ vec_t(const uint3 &v) : x(v.x), y(v.y), z(v.z) {} + inline __both__ vec_t(const float3 &v) : x(v.x), y(v.y), z(v.z) {} + inline __both__ operator float3() const { return make_float3(x,y,z); } + inline __both__ operator int3() const { return make_int3(x,y,z); } + inline __both__ operator uint3() const { return make_uint3(x,y,z); } +#endif + inline __both__ explicit vec_t(const vec_t &v); + /*! construct 3-vector from 3-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) : x((T)o.x), y((T)o.y), z((T)o.z) {} + + /*! swizzle ... */ + inline __both__ vec_t yzx() const { return vec_t(y,z,x); } + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->x = other.x; + this->y = other.y; + this->z = other.z; + return *this; + } + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + template + static inline __both__ vec_t make_from(const vec_t &v, const Lambda &lambda) + { return vec_t(lambda(v.x),lambda(v.y),lambda(v.z)); } + + union { + struct { T x, y, z; }; + struct { T r, s, t; }; + struct { T u, v, w; }; + }; + }; + + // ------------------------------------------------------------------ + // vec3a + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec3a_t : public vec_t { + inline vec3a_t() {} + inline vec3a_t(const T &t) : vec_t(t) {} + inline vec3a_t(const T &x, const T &y, const T &z) : vec_t(x,y,z) {} + + template + inline vec3a_t(const vec_t &v) : vec_t(v.x,v.y,v.z) {} + + T a; + }; + + // ------------------------------------------------------------------ + // vec4 + // ------------------------------------------------------------------ + template + struct GDT_INTERFACE vec_t { + enum { dims = 4 }; + typedef T scalar_t; + + inline __both__ vec_t() {} + + inline __both__ vec_t(const T &t) + : x(t), y(t), z(t), w(t) + {} + inline __both__ vec_t(const vec_t &xyz, const T &_w) + : x(xyz.x), y(xyz.y), z(xyz.z), w(_w) + {} + inline __both__ vec_t(const T &_x, const T &_y, const T &_z, const T &_w) + : x(_x), y(_y), z(_z), w(_w) + {} + +#ifdef __CUDACC__ + inline __both__ vec_t(const float4 &v) + : x(v.x), y(v.y), z(v.z), w(v.w) + {} + inline __both__ vec_t(const int4 &v) + : x(v.x), y(v.y), z(v.z), w(v.w) + {} + inline __both__ operator float4() const { return make_float4(x,y,z,w); } + inline __both__ operator int4() const { return make_int4(x,y,z,w); } +#endif + /*! construct 3-vector from 3-vector of another type */ + template + inline __both__ explicit vec_t(const vec_t &o) + : x(o.x), y(o.y), z(o.z), w(o.w) + {} + inline __both__ vec_t(const vec_t &o) : x(o.x), y(o.y), z(o.z), w(o.w) {} + + /*! assignment operator */ + inline __both__ vec_t &operator=(const vec_t &other) { + this->x = other.x; + this->y = other.y; + this->z = other.z; + this->w = other.w; + return *this; + } + + inline __both__ T &operator[](size_t dim) { return (&x)[dim]; } + inline __both__ const T &operator[](size_t dim) const { return (&x)[dim]; } + + template + static inline __both__ vec_t make_from(const vec_t &v, + const Lambda &lambda) + { return vec_t(lambda(v.x),lambda(v.y),lambda(v.z),lambda(v.w)); } + + T x, y, z, w; + }; + + template + inline __both__ vec_t::vec_t(const vec_t &v) + : x(v.x), y(v.y), z(v.z) + {} + + // ======================================================= + // default functions + // ======================================================= + + template + inline __both__ typename long_type_of::type area(const vec_t &v) + { return (typename long_type_of::type)(v.x)*(typename long_type_of::type)(v.y); } + + + template + inline __both__ typename long_type_of::type volume(const vec_t &v) + { return + (typename long_type_of::type)(v.x)* + (typename long_type_of::type)(v.y)* + (typename long_type_of::type)(v.z); + } + + template + inline __both__ typename long_type_of::type volume(const vec_t &v) + { return + (typename long_type_of::type)(v.x)* + (typename long_type_of::type)(v.y)* + (typename long_type_of::type)(v.z)* + (typename long_type_of::type)(v.w); + } + + template + inline __both__ typename long_type_of::type area(const vec_t &v) + { return + T(2)*((typename long_type_of::type)(v.x)*v.y+ + (typename long_type_of::type)(v.y)*v.z+ + (typename long_type_of::type)(v.z)*v.x); + } + + + + /*! vector cross product */ + template + inline __both__ vec_t cross(const vec_t &a, const vec_t &b) + { + return vec_t(a.y*b.z-b.y*a.z, + a.z*b.x-b.z*a.x, + a.x*b.y-b.x*a.y); + } + + /*! vector cross product */ + template + inline __both__ T dot(const vec_t &a, const vec_t &b) + { + return a.x*b.x + a.y*b.y + a.z*b.z; + } + + /*! vector cross product */ + template + inline __both__ vec_t normalize(const vec_t &v) + { + return v * 1.f/gdt::overloaded::sqrt(dot(v,v)); + } + + /*! vector cross product */ + template + inline __both__ T length(const vec_t &v) + { + return gdt::overloaded::sqrt(dot(v,v)); + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << ")"; + return o; + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << "," << v.y << ")"; + return o; + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << "," << v.y << "," << v.z << ")"; + return o; + } + + template + inline __gdt_host std::ostream &operator<<(std::ostream &o, const vec_t &v) + { + o << "(" << v.x << "," << v.y << "," << v.z << "," << v.w << ")"; + return o; + } + + // ======================================================= + // default instantiations + // ======================================================= + +#define _define_vec_types(T,t) \ +using vec2##t = vec_t; \ +using vec3##t = vec_t; \ +using vec4##t = vec_t; \ +using vec3##t##a = vec3a_t; \ + +//#define _define_vec_types(T,t) \ +// typedef vec_t vec2##t; \ +// typedef vec_t vec3##t; \ +// typedef vec_t vec4##t; \ +// typedef vec3a_t vec3##t##a; \ + + _define_vec_types(int8_t ,c); + _define_vec_types(int16_t ,s); + _define_vec_types(int32_t ,i); + _define_vec_types(int64_t ,l); + _define_vec_types(uint8_t ,uc); + _define_vec_types(uint16_t,us); + _define_vec_types(uint32_t,ui); + _define_vec_types(uint64_t,ul); + _define_vec_types(float,f); + _define_vec_types(double,d); + +#undef _define_vec_types + +} // ::gdt + + +#include "vec/functors.h" +// comparison operators +#include "vec/compare.h" +#include "vec/rotate.h" diff --git a/crates/optix/examples/common/gdt/gdt/math/vec/compare.h b/crates/optix/examples/common/gdt/gdt/math/vec/compare.h new file mode 100644 index 00000000..0e50fdba --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec/compare.h @@ -0,0 +1,59 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace gdt { + + // ------------------------------------------------------------------ + // == + // ------------------------------------------------------------------ + +#if __CUDACC__ + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return (a.x==b.x) & (a.y==b.y); } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return (a.x==b.x) & (a.y==b.y) & (a.z==b.z); } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return (a.x==b.x) & (a.y==b.y) & (a.z==b.z) & (a.w==b.w); } +#else + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return a.x==b.x && a.y==b.y; } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return a.x==b.x && a.y==b.y && a.z==b.z; } + + template + inline __both__ bool operator==(const vec_t &a, const vec_t &b) + { return a.x==b.x && a.y==b.y && a.z==b.z && a.w==b.w; } +#endif + + // ------------------------------------------------------------------ + // != + // ------------------------------------------------------------------ + + template + inline __both__ bool operator!=(const vec_t &a, const vec_t &b) + { return !(a==b); } + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/vec/functors.h b/crates/optix/examples/common/gdt/gdt/math/vec/functors.h new file mode 100644 index 00000000..a2c8e741 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec/functors.h @@ -0,0 +1,364 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace gdt { + + // inline __both__ float min(float x, float y) { return fminf(x,y); } + // inline __both__ float max(float x, float y) { return fmaxf(x,y); } + + // ======================================================= + // scalar functors + // ======================================================= + + template + inline __both__ T divRoundUp(const T &a, const T &b) + { //causes issues on ubuntu16-gcc: static_assert(std::numeric_limits::is_integer); + return T((a+b-1)/b); } + + // ======================================================= + // vector specializations of those scalar functors + // ======================================================= + + template inline __both__ + bool any_less_than(const vec_t &a, const vec_t &b) + { for (int i=0;i inline __both__ + bool any_greater_than(const vec_t &a, const vec_t &b) + { for (int i=0;i b[i]) return true; return false; } + + // ------------------------------------------------------- + // unary functors + // ------------------------------------------------------- + + template + inline __both__ T clamp(const T &val, const T &lo, const T &hi) + { return min(hi,max(lo,val)); } + + template + inline __both__ T clamp(const T &val, const T &hi) + { return clamp(val,(T)0,hi); } + +#define _define_float_functor(func) \ + template inline __both__ vec_t func(const vec_t &v) \ + { return vec_t(func(v.x),func(v.y)); } \ + \ + template inline __both__ vec_t func(const vec_t &v) \ + { return vec_t(func(v.x),func(v.y),func(v.z)); } \ + \ + template inline __both__ vec_t func(const vec_t &v) \ + { return vec_t(func(v.x),func(v.y),func(v.z),func(v.w)); } \ + + _define_float_functor(rcp) + _define_float_functor(sin) + _define_float_functor(cos) + _define_float_functor(abs) + _define_float_functor(saturate) + +#undef _define_float_functor + + // ------------------------------------------------------- + // binary functors + // ------------------------------------------------------- + // template + // __both__ vec_t divRoundUp(const vec_t &a, const vec_t &b) + // { return vec_t(divRoundUp(a.x,b.x),divRoundUp(a.y,b.y)); } + + // template + // __both__ vec_t divRoundUp(const vec_t &a, const vec_t &b) + // { return vec_t(divRoundUp(a.x,b.x),divRoundUp(a.y,b.y),divRoundUp(a.z,b.z)); } + +#define _define_binary_functor(fct) \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x)); \ + } \ + \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x), \ + fct(a.y,b.y)); \ + } \ + \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x), \ + fct(a.y,b.y), \ + fct(a.z,b.z)); \ + } \ + \ + template \ + __both__ vec_t::type,3> \ + fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t::type,3> \ + (fct(a.x,b.x), \ + fct(a.y,b.y), \ + fct(a.z,b.z)); \ + } \ + \ + template \ + __both__ vec_t fct(const vec_t &a, const vec_t &b) \ + { \ + return vec_t(fct(a.x,b.x), \ + fct(a.y,b.y), \ + fct(a.z,b.z), \ + fct(a.w,b.w)); \ + } \ + + + _define_binary_functor(divRoundUp) + _define_binary_functor(min) + _define_binary_functor(max) +#undef _define_binary_functor + + + + + + + // ------------------------------------------------------- + // binary operators + // ------------------------------------------------------- +#define _define_operator(op) \ + /* vec op vec */ \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const vec_t &b) \ + { return vec_t(a.x op b.x, a.y op b.y); } \ + \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const vec_t &b) \ + { return vec_t(a.x op b.x, a.y op b.y, a.z op b.z); } \ + \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const vec_t &b) \ + { return vec_t(a.x op b.x,a.y op b.y,a.z op b.z,a.w op b.w); } \ + \ + /* vec op scalar */ \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const T &b) \ + { return vec_t(a.x op b, a.y op b); } \ + \ + template \ + inline __both__ vec_t::type,3> \ + operator op(const vec_t &a, const T2 &b) \ + { return vec_t::type,3> \ + (a.x op b, a.y op b, a.z op b); \ + } \ + \ + template \ + inline __both__ vec_t operator op(const vec_t &a, \ + const T &b) \ + { return vec_t(a.x op b, a.y op b, a.z op b, a.w op b); } \ + \ + /* scalar op vec */ \ + template \ + inline __both__ vec_t operator op(const T &a, \ + const vec_t &b) \ + { return vec_t(a op b.x, a op b.y); } \ + \ + template \ + inline __both__ vec_t operator op(const T &a, \ + const vec_t &b) \ + { return vec_t(a op b.x, a op b.y, a op b.z); } \ + \ + template \ + inline __both__ vec_t operator op(const T &a, \ + const vec_t &b) \ + { return vec_t(a op b.x, a op b.y, a op b.z, a op b.w); } \ + \ + \ + + _define_operator(*); + _define_operator(/); + _define_operator(+); + _define_operator(-); + +#undef _define_operator + + + + + // ------------------------------------------------------- + // unary operators + // ------------------------------------------------------- + + template + inline __both__ vec_t operator-(const vec_t &v) + { return vec_t(-v.x, -v.y); } + + template + inline __both__ vec_t operator+(const vec_t &v) + { return vec_t(v.x, v.y); } + + template + inline __both__ vec_t operator-(const vec_t &v) + { return vec_t(-v.x, -v.y, -v.z); } + + template + inline __both__ vec_t operator+(const vec_t &v) + { return vec_t(v.x, v.y, v.z); } + + + + // ------------------------------------------------------- + // binary op-assign operators + // ------------------------------------------------------- +#define _define_op_assign_operator(operator_op,op) \ + /* vec op vec */ \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const vec_t &b) \ + { \ + a.x op (T)b.x; \ + a.y op (T)b.y; \ + return a; \ + } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const vec_t &b) \ + { \ + a.x op (T)b.x; \ + a.y op (T)b.y; \ + a.z op (T)b.z; \ + return a; \ + } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const vec_t &b) \ + { \ + a.x op (T)b.x; \ + a.y op (T)b.y; \ + a.z op (T)b.z; \ + a.w op (T)b.w; \ + return a; \ + } \ + \ + /* vec op scalar */ \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const OT &b) \ + { a.x op (T)b; a.y op (T)b; return a; } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const OT &b) \ + { a.x op (T)b; a.y op (T)b; a.z op (T)b; return a; } \ + \ + template \ + inline __both__ vec_t &operator_op(vec_t &a, \ + const OT &b) \ + { a.x op (T)b; a.y op (T)b; a.z op (T)b; a.w op (T)b; return a; } \ + + _define_op_assign_operator(operator*=,*=); + _define_op_assign_operator(operator/=,/=); + _define_op_assign_operator(operator+=,+=); + _define_op_assign_operator(operator-=,-=); + +#undef _define_op_assign_operator + + + template + __both__ T reduce_min(const vec_t &v) { return v.x; } + template + __both__ T reduce_min(const vec_t &v) { return min(v.x,v.y); } + template + __both__ T reduce_min(const vec_t &v) { return min(min(v.x,v.y),v.z); } + template + __both__ T reduce_min(const vec_t &v) { return min(min(v.x,v.y),min(v.z,v.w)); } + template + __both__ T reduce_max(const vec_t &v) { return max(v.x,v.y); } + template + __both__ T reduce_max(const vec_t &v) { return max(max(v.x,v.y),v.z); } + template + __both__ T reduce_max(const vec_t &v) { return max(max(v.x,v.y),max(v.z,v.w)); } + + + template + __both__ vec_t madd(const vec_t &a, const vec_t &b, const vec_t &c) + { + return a*b + c; + } + + + template + __both__ int arg_max(const vec_t &v) + { + int biggestDim = 0; + for (int i=1;i abs(v[biggestDim])) biggestDim = i; + return biggestDim; + } + + + // less, for std::set, std::map, etc + template + __both__ bool operator<(const vec_t &a, const vec_t &b) + { + if (a.x < b.x) return true; + if (a.x == b.x && a.y < b.y) return true; + if (a.x == b.x && a.y == b.y && a.z < b.z) return true; + return false; + // return + // (a.x < b.x) | + // ((a.x == b.x) & ((a.y < b.y) | + // ((a.y == b.y) & (a.z < b.z)))); + } + + /*! helper function that creates a semi-random color from an ID */ + inline __both__ vec3f randomColor(int i) + { + int r = unsigned(i)*13*17 + 0x234235; + int g = unsigned(i)*7*3*5 + 0x773477; + int b = unsigned(i)*11*19 + 0x223766; + return vec3f((r&255)/255.f, + (g&255)/255.f, + (b&255)/255.f); + } + + /*! helper function that creates a semi-random color from an ID */ + inline __both__ vec3f randomColor(size_t idx) + { + unsigned int r = (unsigned int)(idx*13*17 + 0x234235); + unsigned int g = (unsigned int)(idx*7*3*5 + 0x773477); + unsigned int b = (unsigned int)(idx*11*19 + 0x223766); + return vec3f((r&255)/255.f, + (g&255)/255.f, + (b&255)/255.f); + } + + /*! helper function that creates a semi-random color from an ID */ + template + inline __both__ vec3f randomColor(const T *ptr) + { + return randomColor((size_t)ptr); + } + + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/math/vec/rotate.h b/crates/optix/examples/common/gdt/gdt/math/vec/rotate.h new file mode 100644 index 00000000..2a7ba29a --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/math/vec/rotate.h @@ -0,0 +1,40 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace gdt { + + /*! perform 'rotation' of float a by amount b. Both a and b must be + in 0,1 range, the result will be (a+1) clamped to that same + range (ie, it is the value a shifted by the amount b to the + right, and re-entering the [0,1) range on the left if it + "rotates" out on the right */ + inline __both__ float rotate(const float a, const float b) + { + float sum = a+b; + return (sum-1.f)<0.f?sum:(sum-1.f); + } + + /*! perform 'rotation' of float a by amount b. Both a and b must be + in 0,1 range, the result will be (a+1) clamped to that same + range (ie, it is the value a shifted by the amount b to the + right, and re-entering the [0,1) range on the left if it + "rotates" out on the right */ + inline __both__ vec2f rotate(const vec2f a, const vec2f b) + { return vec2f(rotate(a.x,b.x),rotate(a.y,b.y)); } + +} // ::gdt diff --git a/crates/optix/examples/common/gdt/gdt/random/random.h b/crates/optix/examples/common/gdt/gdt/random/random.h new file mode 100644 index 00000000..e2f77465 --- /dev/null +++ b/crates/optix/examples/common/gdt/gdt/random/random.h @@ -0,0 +1,91 @@ +// ======================================================================== // +// Copyright 2018 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +/* pieces originally taken from optixPathTracer/random.h example, + under following license */ + +/* + * Copyright (c) 2018 NVIDIA 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: + * * 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 NVIDIA 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 ``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 + +#include "gdt/gdt.h" + +namespace gdt { + + /*! simple 24-bit linear congruence generator */ + template + struct LCG { + + inline __both__ LCG() + { /* intentionally empty so we can use it in device vars that + don't allow dynamic initialization (ie, PRD) */ + } + inline __both__ LCG(unsigned int val0, unsigned int val1) + { init(val0,val1); } + + inline __both__ void init(unsigned int val0, unsigned int val1) + { + unsigned int v0 = val0; + unsigned int v1 = val1; + unsigned int s0 = 0; + + for (unsigned int n = 0; n < N; n++) { + s0 += 0x9e3779b9; + v0 += ((v1<<4)+0xa341316c)^(v1+s0)^((v1>>5)+0xc8013ea4); + v1 += ((v0<<4)+0xad90777d)^(v0+s0)^((v0>>5)+0x7e95761e); + } + state = v0; + } + + // Generate random unsigned int in [0, 2^24) + inline __both__ float operator() () + { + const uint32_t LCG_A = 1664525u; + const uint32_t LCG_C = 1013904223u; + state = (LCG_A * state + LCG_C); + return (state & 0x00FFFFFF) / (float) 0x01000000; + } + + uint32_t state; + }; + +} // ::gdt diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml new file mode 100644 index 00000000..6c589340 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ex02_pipeline" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +optix = {path = "../../"} +cust = {path = "../../../cust"} +anyhow = "1.0.44" +ustr = "0.8.1" + +[build-dependencies] +find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs new file mode 100644 index 00000000..64d4aca8 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -0,0 +1,49 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; + +fn main() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + + cuda_include = cuda_include.join("include"); + + let args = vec![ + format!("-I{}", optix_include.display()), + format!("-I{}/../common/gdt", manifest_dir), + ]; + + compile_to_ptx("src/ex02_pipeline.cu", &args); +} + +fn compile_to_ptx(cu_path: &str, args: &[String]) { + println!("cargo:rerun-if-changed={}", cu_path); + + let full_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(cu_path); + + let mut ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(cu_path); + ptx_path.set_extension("ptx"); + std::fs::create_dir_all(ptx_path.parent().unwrap()).unwrap(); + + let output = std::process::Command::new("nvcc") + .arg("-ptx") + .arg(&full_path) + .arg("-o") + .arg(&ptx_path) + .args(args) + .output() + .expect("failed to fun nvcc"); + + if !output.status.success() { + panic!("{}", unsafe { String::from_utf8_unchecked(output.stderr) }); + } +} diff --git a/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu b/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu new file mode 100644 index 00000000..26e3c43b --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu @@ -0,0 +1,105 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace osc { + +using namespace gdt; + +struct LaunchParams { + int frameID{0}; + uint32_t* colorBuffer; + vec2i fbSize; +}; + +/*! launch parameters in constant memory, filled in by optix upon + optixLaunch (this gets filled in from the buffer we pass to + optixLaunch) */ +extern "C" __constant__ LaunchParams PARAMS; + +//------------------------------------------------------------------------------ +// closest hit and anyhit programs for radiance-type rays. +// +// Note eventually we will have to create one pair of those for each +// ray type and each geometry type we want to render; but this +// simple example doesn't use any actual geometries yet, so we only +// create a single, dummy, set of them (we do have to have at least +// one group of them to set up the SBT) +//------------------------------------------------------------------------------ + +extern "C" __global__ void +__closesthit__radiance() { /*! for this simple example, this will remain empty + */ +} + +extern "C" __global__ void +__anyhit__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// miss program that gets called for any ray that did not have a +// valid intersection +// +// as with the anyhit/closest hit programs, in this example we only +// need to have _some_ dummy function to set up a valid SBT +// ------------------------------------------------------------------------------ + +extern "C" __global__ void +__miss__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// ray gen program - the actual rendering happens in here +//------------------------------------------------------------------------------ +extern "C" __global__ void __raygen__renderFrame() { + if (PARAMS.frameID == 0 && optixGetLaunchIndex().x == 0 && + optixGetLaunchIndex().y == 0) { + // we could of course also have used optixGetLaunchDims to query + // the launch size, but accessing the PARAMS here + // makes sure they're not getting optimized away (because + // otherwise they'd not get used) + printf("############################################\n"); + printf("Hello world from OptiX 7 raygen program!\n(within a " + "%ix%i-sized launch)\n", + PARAMS.fbSize.x, PARAMS.fbSize.y); + printf("############################################\n"); + } + + // ------------------------------------------------------------------ + // for this example, produce a simple test pattern: + // ------------------------------------------------------------------ + + // compute a test pattern based on pixel ID + const int ix = optixGetLaunchIndex().x; + const int iy = optixGetLaunchIndex().y; + + const int r = (ix % 256); + const int g = (iy % 256); + const int b = ((ix + iy) % 256); + + // convert to 32-bit rgba value (we explicitly set alpha to 0xff + // to make stb_image_write happy ... + const uint32_t rgba = 0xff000000 | (r << 0) | (g << 8) | (b << 16); + + // and write to frame buffer ... + const uint32_t fbIndex = ix + iy * PARAMS.fbSize.x; + PARAMS.colorBuffer[fbIndex] = rgba; +} + +} // namespace osc diff --git a/crates/optix/examples/ex02_pipeline/src/launch_params.h b/crates/optix/examples/ex02_pipeline/src/launch_params.h new file mode 100644 index 00000000..e4364e45 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/launch_params.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +namespace osc { +} // namespace osc diff --git a/crates/optix/examples/ex02_pipeline/src/main.rs b/crates/optix/examples/ex02_pipeline/src/main.rs new file mode 100644 index 00000000..00f2ba65 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/main.rs @@ -0,0 +1,8 @@ +mod renderer; +use renderer::Renderer; + +fn main() -> Result<(), Box> { + let mut renderer = Renderer::new(256, 128)?; + renderer.render()?; + Ok(()) +} \ No newline at end of file diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs new file mode 100644 index 00000000..bff1e1d1 --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -0,0 +1,240 @@ +use anyhow::{Context, Result}; +use cust::context::{Context as CuContext, ContextFlags}; +use cust::device::{Device, DeviceAttribute}; +use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::stream::{Stream, StreamFlags}; +use cust::CudaFlags; +use optix::{ + context::DeviceContext, + module::{ + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + PipelineCompileOptions, TraversableGraphFlags, + }, + pipeline::{Pipeline, PipelineLinkOptions}, + program_group::ProgramGroupDesc, + shader_binding_table::{SbtRecord, ShaderBindingTable}, +}; + +use ustr::ustr; + +pub struct Renderer { + ctx: DeviceContext, + cuda_context: CuContext, + stream: Stream, + launch_params: LaunchParams, + buf_launch_params: DBox, + buf_raygen: DBuffer, + buf_hitgroup: DBuffer, + buf_miss: DBuffer, + sbt: optix::sys::OptixShaderBindingTable, + pipeline: Pipeline, + color_buffer: DBuffer, +} + +impl Renderer { + pub fn new(width: usize, height: usize) -> Result> { + init_optix()?; + + // create CUDA and OptiX contexts + let device = Device::get_device(0)?; + let tex_align = device.get_attribute(DeviceAttribute::TextureAlignment)?; + let srf_align = device.get_attribute(DeviceAttribute::SurfaceAlignment)?; + println!("tex align: {}\nsrf align: {}", tex_align, srf_align); + + let cuda_context = + CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let stream = Stream::new(StreamFlags::DEFAULT, None)?; + + let mut ctx = DeviceContext::new(&cuda_context)?; + // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + + // create module + let module_compile_options = ModuleCompileOptions { + max_register_count: 50, + opt_level: CompileOptimizationLevel::Default, + debug_level: CompileDebugLevel::None, + }; + + let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + .uses_motion_blur(false) + .num_attribute_values(2) + .num_payload_values(2) + .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) + .exception_flags(ExceptionFlags::NONE); + + let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); + + let (module, _log) = ctx + .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) + .context("Create module")?; + + // create raygen program + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + + let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + + // create miss program + let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + + let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + + let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( + Some((&module, ustr("__closesthit__radiance"))), + Some((&module, ustr("__anyhit__radiance"))), + None, + ); + + // create hitgroup programs + let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.iter().cloned()); + program_groups.extend(pg_miss.iter().cloned()); + program_groups.extend(pg_hitgroup.iter().cloned()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = ctx.pipeline_create( + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + + // create SBT + let rec_raygen: Vec<_> = pg_raygen + .iter() + .map(|pg| RaygenRecord::pack(0, pg).expect("failed to pack raygen record")) + .collect(); + + let rec_miss: Vec<_> = pg_miss + .iter() + .map(|pg| MissRecord::pack(0, pg).expect("failed to pack miss record")) + .collect(); + + let num_objects = 1; + let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); + + let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + + let sbt = ShaderBindingTable::new(&mut buf_raygen) + .miss(&mut buf_miss) + .hitgroup(&mut buf_hitgroup) + .build(); + + let mut color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; + + let launch_params = LaunchParams { + frame_id: 0, + color_buffer: color_buffer.as_device_ptr(), + fb_size: Point2i { + x: width as i32, + y: height as i32, + }, + }; + + let buf_launch_params = DBox::new(&launch_params)?; + + Ok(Renderer { + ctx, + cuda_context, + stream, + launch_params, + buf_launch_params, + buf_raygen, + buf_hitgroup, + buf_miss, + sbt, + pipeline, + color_buffer, + }) + } + + pub fn resize( + &mut self, + width: usize, + height: usize, + ) -> Result<(), Box> { + self.color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; + self.launch_params.fb_size.x = width as i32; + self.launch_params.fb_size.y = height as i32; + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + Ok(()) + } + + pub fn render(&mut self) -> Result<(), Box> { + self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.frame_id += 1; + + unsafe { + optix::launch( + &self.pipeline, + &self.stream, + &mut self.buf_launch_params, + &self.sbt, + self.launch_params.fb_size.x as u32, + self.launch_params.fb_size.y as u32, + 1, + )?; + } + + self.stream.synchronize()?; + + Ok(()) + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Point2i { + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct LaunchParams { + pub frame_id: i32, + pub color_buffer: DevicePointer, + pub fb_size: Point2i, +} + +unsafe impl DeviceCopy for LaunchParams {} + +type RaygenRecord = SbtRecord; +type MissRecord = SbtRecord; + +#[derive(Copy, Clone, Default)] +struct HitgroupSbtData { + object_id: u32, +} +unsafe impl DeviceCopy for HitgroupSbtData {} +type HitgroupRecord = SbtRecord; + +fn init_optix() -> Result<(), Box> { + cust::init(CudaFlags::empty())?; + let device_count = Device::num_devices()?; + if device_count == 0 { + panic!("No CUDA devices found!"); + } + + optix::init()?; + Ok(()) +} diff --git a/crates/optix/optix_wrapper.rs b/crates/optix/optix_wrapper.rs index 60ae295c..45b3934e 100644 --- a/crates/optix/optix_wrapper.rs +++ b/crates/optix/optix_wrapper.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.55.1 */ +/* automatically generated by rust-bindgen 0.59.1 */ #[repr(C)] pub struct __BindgenUnionField(::std::marker::PhantomData); @@ -187,7 +187,7 @@ impl OptixResult { pub const OPTIX_ERROR_UNKNOWN: OptixResult = OptixResult(7999); } #[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct OptixResult(pub ::std::os::raw::c_uint); pub mod OptixDeviceProperty { pub type Type = ::std::os::raw::c_uint; @@ -215,13 +215,22 @@ pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ OptixDeviceContextValidationMode = 4294967295; pub type OptixDeviceContextValidationMode = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixDeviceContextOptions { pub logCallbackFunction: OptixLogCallback, pub logCallbackData: *mut ::std::os::raw::c_void, pub logCallbackLevel: ::std::os::raw::c_int, pub validationMode: OptixDeviceContextValidationMode, } +impl Default for OptixDeviceContextOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE: OptixHitKind = 254; pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_BACK_FACE: OptixHitKind = 255; pub type OptixHitKind = ::std::os::raw::c_uint; @@ -259,6 +268,15 @@ pub struct OptixBuildInputTriangleArray { pub primitiveIndexOffset: ::std::os::raw::c_uint, pub transformFormat: OptixTransformFormat, } +impl Default for OptixBuildInputTriangleArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_CUSTOM: OptixPrimitiveType = 9472; pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE: OptixPrimitiveType = 9473; @@ -292,8 +310,17 @@ pub struct OptixBuildInputCurveArray { pub flag: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } +impl Default for OptixBuildInputCurveArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixAabb { pub minX: f32, pub minY: f32, @@ -314,11 +341,29 @@ pub struct OptixBuildInputCustomPrimitiveArray { pub sbtIndexOffsetStrideInBytes: ::std::os::raw::c_uint, pub primitiveIndexOffset: ::std::os::raw::c_uint, } +impl Default for OptixBuildInputCustomPrimitiveArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixBuildInputInstanceArray { pub instances: CUdeviceptr, pub numInstances: ::std::os::raw::c_uint, } +impl Default for OptixBuildInputInstanceArray { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES: OptixBuildInputType = 8513; pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES: OptixBuildInputType = 8514; pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES: OptixBuildInputType = 8515; @@ -334,6 +379,15 @@ pub struct OptixBuildInput__bindgen_ty_1 { pub pad: __BindgenUnionField<[::std::os::raw::c_char; 1024usize]>, pub bindgen_union_field: [u64; 128usize], } +impl Default for OptixBuildInput__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE: OptixInstanceFlags = 0; pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING: OptixInstanceFlags = 1; @@ -343,7 +397,7 @@ pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT: OptixInstanceFl pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM: OptixInstanceFlags = 64; pub type OptixInstanceFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixInstance { pub transform: [f32; 12usize], pub instanceId: ::std::os::raw::c_uint, @@ -369,7 +423,7 @@ pub const OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH: OptixMotionFlags = 1; pub const OptixMotionFlags_OPTIX_MOTION_FLAG_END_VANISH: OptixMotionFlags = 2; pub type OptixMotionFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixMotionOptions { pub numKeys: ::std::os::raw::c_ushort, pub flags: ::std::os::raw::c_ushort, @@ -377,18 +431,36 @@ pub struct OptixMotionOptions { pub timeEnd: f32, } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixAccelBuildOptions { pub buildFlags: ::std::os::raw::c_uint, pub operation: OptixBuildOperation, pub motionOptions: OptixMotionOptions, } +impl Default for OptixAccelBuildOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixAccelBufferSizes { pub outputSizeInBytes: size_t, pub tempSizeInBytes: size_t, pub tempUpdateSizeInBytes: size_t, } +impl Default for OptixAccelBufferSizes { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE: OptixAccelPropertyType = 8577; pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS: OptixAccelPropertyType = 8578; pub type OptixAccelPropertyType = ::std::os::raw::c_uint; @@ -397,13 +469,22 @@ pub struct OptixAccelEmitDesc { pub result: CUdeviceptr, pub type_: OptixAccelPropertyType, } +impl Default for OptixAccelEmitDesc { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixAccelRelocationInfo { pub info: [::std::os::raw::c_ulonglong; 4usize], } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixStaticTransform { pub child: OptixTraversableHandle, pub pad: [::std::os::raw::c_uint; 2usize], @@ -411,7 +492,7 @@ pub struct OptixStaticTransform { pub invTransform: [f32; 12usize], } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixMatrixMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, @@ -419,7 +500,7 @@ pub struct OptixMatrixMotionTransform { pub transform: [[f32; 12usize]; 2usize], } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixSRTData { pub sx: f32, pub a: f32, @@ -439,7 +520,7 @@ pub struct OptixSRTData { pub tz: f32, } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixSRTMotionTransform { pub child: OptixTraversableHandle, pub motionOptions: OptixMotionOptions, @@ -472,6 +553,15 @@ pub struct OptixImage2D { pub pixelStrideInBytes: ::std::os::raw::c_uint, pub format: OptixPixelFormat::Type, } +impl Default for OptixImage2D { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub mod OptixDenoiserModelKind { pub type Type = ::std::os::raw::c_uint; pub const OPTIX_DENOISER_MODEL_KIND_LDR: Type = 8994; @@ -480,7 +570,7 @@ pub mod OptixDenoiserModelKind { pub const OPTIX_DENOISER_MODEL_KIND_TEMPORAL: Type = 8997; } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixDenoiserOptions { pub guideAlbedo: ::std::os::raw::c_uint, pub guideNormal: ::std::os::raw::c_uint, @@ -491,12 +581,30 @@ pub struct OptixDenoiserGuideLayer { pub normal: OptixImage2D, pub flow: OptixImage2D, } +impl Default for OptixDenoiserGuideLayer { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixDenoiserLayer { pub input: OptixImage2D, pub previousOutput: OptixImage2D, pub output: OptixImage2D, } +impl Default for OptixDenoiserLayer { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixDenoiserParams { pub denoiseAlpha: ::std::os::raw::c_uint, @@ -504,6 +612,15 @@ pub struct OptixDenoiserParams { pub blendFactor: f32, pub hdrAverageColor: CUdeviceptr, } +impl Default for OptixDenoiserParams { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixDenoiserSizes { pub stateSizeInBytes: size_t, @@ -511,6 +628,15 @@ pub struct OptixDenoiserSizes { pub withoutOverlapScratchSizeInBytes: size_t, pub overlapWindowSizeInPixels: ::std::os::raw::c_uint, } +impl Default for OptixDenoiserSizes { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub const OptixRayFlags_OPTIX_RAY_FLAG_NONE: OptixRayFlags = 0; pub const OptixRayFlags_OPTIX_RAY_FLAG_DISABLE_ANYHIT: OptixRayFlags = 1; pub const OptixRayFlags_OPTIX_RAY_FLAG_ENFORCE_ANYHIT: OptixRayFlags = 2; @@ -555,8 +681,17 @@ pub struct OptixModuleCompileBoundValueEntry { pub boundValuePtr: *const ::std::os::raw::c_void, pub annotation: *const ::std::os::raw::c_char, } +impl Default for OptixModuleCompileBoundValueEntry { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixModuleCompileOptions { pub maxRegisterCount: ::std::os::raw::c_int, pub optLevel: OptixCompileOptimizationLevel::Type, @@ -564,6 +699,15 @@ pub struct OptixModuleCompileOptions { pub boundValues: *const OptixModuleCompileBoundValueEntry, pub numBoundValues: ::std::os::raw::c_uint, } +impl Default for OptixModuleCompileOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} pub mod OptixProgramGroupKind { pub type Type = ::std::os::raw::c_uint; pub const OPTIX_PROGRAM_GROUP_KIND_RAYGEN: Type = 9249; @@ -575,13 +719,22 @@ pub mod OptixProgramGroupKind { pub const OptixProgramGroupFlags_OPTIX_PROGRAM_GROUP_FLAGS_NONE: OptixProgramGroupFlags = 0; pub type OptixProgramGroupFlags = ::std::os::raw::c_uint; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupSingleModule { pub module: OptixModule, pub entryFunctionName: *const ::std::os::raw::c_char, } +impl Default for OptixProgramGroupSingleModule { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupHitgroup { pub moduleCH: OptixModule, pub entryFunctionNameCH: *const ::std::os::raw::c_char, @@ -590,14 +743,32 @@ pub struct OptixProgramGroupHitgroup { pub moduleIS: OptixModule, pub entryFunctionNameIS: *const ::std::os::raw::c_char, } +impl Default for OptixProgramGroupHitgroup { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupCallables { pub moduleDC: OptixModule, pub entryFunctionNameDC: *const ::std::os::raw::c_char, pub moduleCC: OptixModule, pub entryFunctionNameCC: *const ::std::os::raw::c_char, } +impl Default for OptixProgramGroupCallables { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] #[derive(Copy, Clone)] pub struct OptixProgramGroupDesc { @@ -613,10 +784,27 @@ pub union OptixProgramGroupDesc__bindgen_ty_1 { pub exception: OptixProgramGroupSingleModule, pub callables: OptixProgramGroupCallables, pub hitgroup: OptixProgramGroupHitgroup, - _bindgen_union_align: [u64; 6usize], +} +impl Default for OptixProgramGroupDesc__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +impl Default for OptixProgramGroupDesc { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixProgramGroupOptions { pub reserved: ::std::os::raw::c_int, } @@ -672,12 +860,30 @@ pub struct OptixPipelineCompileOptions { pub reserved: ::std::os::raw::c_uint, pub reserved2: size_t, } +impl Default for OptixPipelineCompileOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixPipelineLinkOptions { pub maxTraceDepth: ::std::os::raw::c_uint, pub debugLevel: OptixCompileDebugLevel::Type, } +impl Default for OptixPipelineLinkOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] pub struct OptixShaderBindingTable { pub raygenRecord: CUdeviceptr, @@ -692,8 +898,17 @@ pub struct OptixShaderBindingTable { pub callablesRecordStrideInBytes: ::std::os::raw::c_uint, pub callablesRecordCount: ::std::os::raw::c_uint, } +impl Default for OptixShaderBindingTable { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixStackSizes { pub cssRG: ::std::os::raw::c_uint, pub cssMS: ::std::os::raw::c_uint, @@ -717,11 +932,20 @@ pub type OptixQueryFunctionTable_t = ::std::option::Option< ) -> OptixResult, >; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixBuiltinISOptions { pub builtinISModuleType: OptixPrimitiveType, pub usesMotionBlur: ::std::os::raw::c_int, } +impl Default for OptixBuiltinISOptions { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} extern "C" { pub fn optixGetErrorName(result: OptixResult) -> *const ::std::os::raw::c_char; } @@ -1024,7 +1248,7 @@ extern "C" { ) -> OptixResult; } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixFunctionTable { pub optixGetErrorName: ::std::option::Option< unsafe extern "C" fn(result: OptixResult) -> *const ::std::os::raw::c_char, @@ -1332,8 +1556,9 @@ pub const OptixGeometryTransformByteAlignment: size_t = 16; pub const OptixTransformByteAlignment: size_t = 64; pub const OptixVersion: size_t = 70300; pub const OptixBuildInputSize: size_t = 1032; +pub const OptixShaderBindingTableSize: size_t = 64; #[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum OptixGeometryFlags { None = 0, DisableAnyHit = 1, diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 15697d18..2c30693a 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -4,7 +4,8 @@ use std::{ffi::c_void, mem::MaybeUninit, ptr}; use cust::context::ContextHandle; -use crate::{error::OptixResult, optix_call, sys}; +use crate::{error::Error, optix_call, sys}; +type Result = std::result::Result; /// A certain property belonging to an OptiX device. #[non_exhaustive] @@ -51,11 +52,12 @@ impl OptixDeviceProperty { } #[derive(Debug)] -pub struct OptixContext { +#[repr(transparent)] +pub struct DeviceContext { pub(crate) raw: sys::OptixDeviceContext, } -impl Drop for OptixContext { +impl Drop for DeviceContext { fn drop(&mut self) { unsafe { sys::optixDeviceContextDestroy(self.raw); @@ -63,11 +65,11 @@ impl Drop for OptixContext { } } -impl OptixContext { +impl DeviceContext { // TODO(RDambrosio016): expose device context options /// Creates a new [`OptixContext`] from a cust CUDA context. - pub fn new(cuda_ctx: &impl ContextHandle) -> OptixResult { + pub fn new(cuda_ctx: &impl ContextHandle) -> Result { let mut raw = MaybeUninit::uninit(); unsafe { optix_call!(optixDeviceContextCreate( @@ -81,7 +83,7 @@ impl OptixContext { } } - pub fn get_property(&self, property: OptixDeviceProperty) -> OptixResult { + pub fn get_property(&self, property: OptixDeviceProperty) -> Result { let raw_prop = property.to_raw(); unsafe { let mut value = 0u32; diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 6cf08da6..0bf6ef9e 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -14,7 +14,8 @@ use cust::{ prelude::Stream, }; -use crate::{context::OptixContext, error::OptixResult, optix_call, sys}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; +type Result = std::result::Result; // can't zero initialize, OptixPixelFormat is not zero-initializable. fn null_optix_image() -> sys::OptixImage2D { @@ -131,10 +132,10 @@ impl Drop for Denoiser { impl Denoiser { /// Create a new [`Denoiser`] with a model kind and some options. pub fn new( - ctx: &OptixContext, + ctx: &DeviceContext, kind: DenoiserModelKind, options: DenoiserOptions, - ) -> OptixResult { + ) -> Result { let mut raw = MaybeUninit::uninit(); unsafe { let ctx = ctx.raw; @@ -159,7 +160,7 @@ impl Denoiser { /// /// If tiling is being used, `width` and `height` should not contain the overlap size. Tiling requires /// extra overlap areas which is why there is scratch memory with and without tiling requirements. - pub fn required_gpu_memory(&self, width: u32, height: u32) -> OptixResult { + pub fn required_gpu_memory(&self, width: u32, height: u32) -> Result { let mut sizes = MaybeUninit::uninit(); unsafe { optix_call!(optixDenoiserComputeMemoryResources( @@ -192,7 +193,7 @@ impl Denoiser { mut width: u32, mut height: u32, tiled: bool, - ) -> OptixResult<()> { + ) -> Result<()> { // first, find out how much memory we need to allocate let sizes = self.required_gpu_memory(width, height)?; let original_width = width; @@ -264,7 +265,7 @@ impl Denoiser { input_image: Image, parameters: DenoiserParams, out_buffer: &mut impl GpuBuffer, - ) -> OptixResult<()> { + ) -> Result<()> { let state_lock = self.state.lock().unwrap(); let state = state_lock.as_ref().expect( "State was not initialized before invoking the denoiser, call Denoiser::setup_state first" diff --git a/crates/optix/src/error.rs b/crates/optix/src/error.rs index 80165baf..38d9609c 100644 --- a/crates/optix/src/error.rs +++ b/crates/optix/src/error.rs @@ -131,14 +131,14 @@ impl Display for OptixError { impl std::error::Error for OptixError {} -pub type OptixResult = Result; +// pub type OptixResult = Result; pub trait ToResult { - fn to_result(self) -> OptixResult<()>; + fn to_result(self) -> Result<(), OptixError>; } impl ToResult for sys::OptixResult { - fn to_result(self) -> OptixResult<()> { + fn to_result(self) -> Result<(), OptixError> { use OptixError::*; Err(match self { @@ -183,6 +183,54 @@ impl ToResult for sys::OptixResult { sys::OptixResult::OPTIX_ERROR_CUDA_ERROR => CudaError, sys::OptixResult::OPTIX_ERROR_INTERNAL_ERROR => InternalError, sys::OptixResult::OPTIX_ERROR_UNKNOWN => Unknown, + value @ _ => panic!("Unhandled OptixResult value {:?}", value), }) } } + +#[derive(Debug)] +pub enum Error { + Optix(OptixError), + Cuda(CudaError), + ModuleCreation { source: OptixError, log: String }, + ProgramGroupCreation { source: OptixError, log: String }, + PipelineCreation { source: OptixError, log: String }, +} + +impl From for Error { + fn from(o: OptixError) -> Self { + Self::Optix(o) + } +} + +impl From for Error { + fn from(e: CudaError) -> Self { + Self::Cuda(e) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Optix(e) => Some(e), + Self::Cuda(e) => Some(e), + Self::ModuleCreation { source, .. } => Some(source), + Self::ProgramGroupCreation { source, .. } => Some(source), + Self::PipelineCreation { source, .. } => Some(source), + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Optix(_) => write!(f, "OptiX error"), + Self::Cuda(_) => write!(f, "CUDA error"), + Self::ModuleCreation { log, .. } => write!(f, "Module creation error: {}", log), + Self::ProgramGroupCreation { log, .. } => { + write!(f, "Program group creation error: {}", log) + } + Self::PipelineCreation { log, .. } => write!(f, "Pipeline creation error: {}", log), + } + } +} diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index a2700b0a..e09bb604 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -2,14 +2,18 @@ pub mod context; pub mod denoiser; pub mod error; pub mod module; +pub mod pipeline; +pub mod program_group; +pub mod shader_binding_table; pub mod sys; pub use cust; -use error::{OptixResult, ToResult}; +use error::{Error, ToResult}; +type Result = std::result::Result; /// Initializes the OptiX library. This must be called before using any OptiX function. It may /// be called before or after initializing CUDA. -pub fn init() -> OptixResult<()> { +pub fn init() -> Result<()> { // avoid initializing multiple times because that will try to load the dll every time. if !optix_is_initialized() { init_cold() @@ -20,8 +24,8 @@ pub fn init() -> OptixResult<()> { #[cold] #[inline(never)] -fn init_cold() -> OptixResult<()> { - unsafe { sys::optixInit().to_result() } +fn init_cold() -> Result<()> { + unsafe { Ok(sys::optixInit().to_result()?) } } /// Whether OptiX is initialized. If you are calling raw [`sys`] functions you must make sure @@ -51,3 +55,33 @@ macro_rules! optix_call { } }}; } + +/// Launch the given [Pipeline] on the given [Stream](cu::Stream). +/// +/// # Safety +/// You must ensure that: +/// - Any [ProgramGroup]s reference by the [Pipeline] are still alive +/// - Any [DevicePtr]s contained in `buf_launch_params` point to valid, +/// correctly aligned memory +/// - Any [SbtRecord]s and associated data referenced by the +/// [OptixShaderBindingTable] are alive and valid +pub unsafe fn launch( + pipeline: &crate::pipeline::Pipeline, + stream: &cust::stream::Stream, + buf_launch_params: &mut cust::memory::DBox

, + sbt: &sys::OptixShaderBindingTable, + width: u32, + height: u32, + depth: u32, +) -> Result<()> { + Ok(optix_call!(optixLaunch( + pipeline.inner, + stream.as_inner(), + buf_launch_params.as_device_ptr().as_raw() as u64, + std::mem::size_of::

(), + sbt, + width, + height, + depth, + ))?) +} diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 9e1e5325..5ebb680b 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -1,4 +1,13 @@ -use crate::{error::OptixResult, optix_call, sys}; +use crate::{ + context::DeviceContext, + error::{Error, ToResult}, + optix_call, sys, +}; +type Result = std::result::Result; + +use std::ffi::{CStr, CString}; + +use ustr::Ustr; #[derive(Clone)] #[repr(transparent)] @@ -6,6 +15,7 @@ pub struct Module { pub(crate) raw: sys::OptixModule, } +/// Module compilation optimization level #[repr(u32)] #[derive(Debug, Hash, PartialEq, Copy, Clone)] pub enum CompileOptimizationLevel { @@ -16,6 +26,7 @@ pub enum CompileOptimizationLevel { Level3 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_3, } +/// Module compilation debug level #[repr(u32)] #[derive(Debug, Hash, PartialEq, Copy, Clone)] pub enum CompileDebugLevel { @@ -23,3 +34,249 @@ pub enum CompileDebugLevel { LineInfo = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO, Full = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_FULL, } + +cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + #[repr(C)] + #[derive(Debug, Hash, PartialEq, Copy, Clone)] + pub struct ModuleCompileOptions { + pub max_register_count: i32, + pub opt_level: CompileOptimizationLevel, + pub debug_level: CompileDebugLevel, + } + + impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { + fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { + sys::OptixModuleCompileOptions { + maxRegisterCount: o.max_register_count, + optLevel: o.opt_level as u32, + debugLevel: o.debug_level as u32, + boundValues: std::ptr::null(), + numBoundValues: 0, + } + } + } + } else { + #[repr(C)] + #[derive(Debug, Hash, PartialEq, Copy, Clone)] + pub struct ModuleCompileOptions { + pub max_register_count: i32, + pub opt_level: CompileOptimizationLevel, + pub debug_level: CompileDebugLevel, + } + + impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { + fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { + sys::OptixModuleCompileOptions { + maxRegisterCount: o.max_register_count, + optLevel: o.opt_level as u32, + debugLevel: o.debug_level as u32, + } + } + } + } +} + +bitflags::bitflags! { + pub struct TraversableGraphFlags: u32 { + const ALLOW_ANY = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY; + const ALLOW_SINGLE_GAS = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS; + const ALLOW_SINGLE_LEVEL_INSTANCING = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; + } +} + +bitflags::bitflags! { + pub struct ExceptionFlags: u32 { + const NONE = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_NONE; + const STACK_OVERFLOW = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW; + const TRACE_DEPTH = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_TRACE_DEPTH; + const USER = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_USER; + const DEBUG = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_DEBUG; + } +} + +bitflags::bitflags! { + pub struct PrimitiveTypeFlags: i32 { + const DEFAULT = 0; + const CUSTOM = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM; + const ROUND_QUADRATIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE; + const ROUND_CUBIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE; + const ROUND_LINEAR = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR; + const TRIANGLE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; + } +} + +#[repr(u32)] +pub enum PrimitiveType { + RoundQuadraticBspline = + sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE as u32, + RoundCubicBspline = + sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE as u32, + RoundLinear = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR as u32, + Triangle = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE as u32, +} + +#[derive(Debug, Hash, PartialEq, Clone)] +pub struct PipelineCompileOptions { + uses_motion_blur: bool, + traversable_graph_flags: TraversableGraphFlags, + num_payload_values: i32, + num_attribute_values: i32, + exception_flags: ExceptionFlags, + pipeline_launch_params_variable_name: Ustr, + primitive_type_flags: PrimitiveTypeFlags, +} + +impl PipelineCompileOptions { + pub fn new>(launch_params_variable_name: S) -> PipelineCompileOptions { + PipelineCompileOptions { + uses_motion_blur: false, + traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, + num_payload_values: 0, + num_attribute_values: 0, + exception_flags: ExceptionFlags::NONE, + pipeline_launch_params_variable_name: launch_params_variable_name.into(), + primitive_type_flags: PrimitiveTypeFlags::DEFAULT, + } + } + + pub fn build(&self) -> sys::OptixPipelineCompileOptions { + cfg_if::cfg_if! { + if #[cfg(feature="optix73")] { + sys::OptixPipelineCompileOptions { + usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, + traversableGraphFlags: self.traversable_graph_flags.bits(), + numPayloadValues: self.num_payload_values, + numAttributeValues: self.num_attribute_values, + exceptionFlags: self.exception_flags.bits(), + pipelineLaunchParamsVariableName: self + .pipeline_launch_params_variable_name + .as_char_ptr(), + usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, + reserved: 0, + reserved2: 0, + } + } else { + sys::OptixPipelineCompileOptions { + usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, + traversableGraphFlags: self.traversable_graph_flags.bits(), + numPayloadValues: self.num_payload_values, + numAttributeValues: self.num_attribute_values, + exceptionFlags: self.exception_flags.bits(), + pipelineLaunchParamsVariableName: self + .pipeline_launch_params_variable_name + .as_char_ptr(), + usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, + } + } + } + } + + pub fn uses_motion_blur(mut self, umb: bool) -> Self { + self.uses_motion_blur = umb; + self + } + + pub fn traversable_graph_flags(mut self, tgf: TraversableGraphFlags) -> Self { + self.traversable_graph_flags = tgf; + self + } + + pub fn num_payload_values(mut self, npv: i32) -> Self { + self.num_payload_values = npv; + self + } + + pub fn num_attribute_values(mut self, nav: i32) -> Self { + self.num_attribute_values = nav; + self + } + + pub fn exception_flags(mut self, ef: ExceptionFlags) -> Self { + self.exception_flags = ef; + self + } + + pub fn pipeline_launch_params_variable_name(mut self, name: ustr::Ustr) -> Self { + self.pipeline_launch_params_variable_name = name; + self + } +} + +/// # Creating and destroying `Module`s +impl DeviceContext { + pub fn module_create_from_ptx( + &mut self, + module_compile_options: &ModuleCompileOptions, + pipeline_compile_options: &PipelineCompileOptions, + ptx: &str, + ) -> Result<(Module, String)> { + let cptx = CString::new(ptx).unwrap(); + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let mopt = module_compile_options.into(); + let popt = pipeline_compile_options.build(); + + let mut raw = std::ptr::null_mut(); + let res = unsafe { + optix_call!(optixModuleCreateFromPTX( + self.raw, + &mopt as *const _, + &popt, + cptx.as_ptr(), + cptx.as_bytes().len(), + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut raw, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((Module { raw }, log)), + Err(source) => Err(Error::ModuleCreation { source, log }), + } + } + + pub fn builtin_is_module_get( + &self, + module_compile_options: &ModuleCompileOptions, + pipeline_compile_options: &PipelineCompileOptions, + builtin_is_module_type: PrimitiveType, + uses_motion_blur: bool, + ) -> Result { + let is_options = sys::OptixBuiltinISOptions { + builtinISModuleType: builtin_is_module_type as u32, + usesMotionBlur: if uses_motion_blur { 1 } else { 0 }, + }; + + let mut raw = std::ptr::null_mut(); + + unsafe { + optix_call!(optixBuiltinISModuleGet( + self.raw, + module_compile_options as *const _ as *const _, + pipeline_compile_options as *const _ as *const _, + &is_options as *const _, + &mut raw, + )) + .map(|_| Module { raw }) + .map_err(|e| Error::from(e)) + } + } + + /// Destroy a module created with [DeviceContext::module_create_from_ptx()] + /// # Safety + /// Modules must not be destroyed while they are still used by any program + /// group. + /// A Module must not be destroyed while it is + /// still in use by concurrent API calls in other threads. + pub fn module_destroy(&mut self, module: Module) -> Result<()> { + unsafe { Ok(optix_call!(optixModuleDestroy(module.raw))?) } + } +} diff --git a/crates/optix/src/optix_wrapper.h b/crates/optix/src/optix_wrapper.h index fe7c4469..bd9a4d7a 100644 --- a/crates/optix/src/optix_wrapper.h +++ b/crates/optix/src/optix_wrapper.h @@ -16,6 +16,7 @@ static const size_t OptixTransformByteAlignment = static const size_t OptixVersion = OPTIX_VERSION; static const size_t OptixBuildInputSize = sizeof(OptixBuildInput); +static const size_t OptixShaderBindingTableSize = sizeof(OptixShaderBindingTable); /** *

diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs new file mode 100644 index 00000000..0f191623 --- /dev/null +++ b/crates/optix/src/pipeline.rs @@ -0,0 +1,131 @@ +use crate::{ + context::DeviceContext, + error::Error, + module::{CompileDebugLevel, PipelineCompileOptions}, + optix_call, + program_group::ProgramGroup, + sys, +}; +type Result = std::result::Result; + +use std::ffi::CStr; + +#[repr(transparent)] +pub struct Pipeline { + pub(crate) inner: sys::OptixPipeline, +} + +#[repr(C)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub struct PipelineLinkOptions { + pub max_trace_depth: u32, + pub debug_level: CompileDebugLevel, +} + +impl From for sys::OptixPipelineLinkOptions { + fn from(o: PipelineLinkOptions) -> Self { + sys::OptixPipelineLinkOptions { + maxTraceDepth: o.max_trace_depth, + debugLevel: o.debug_level as u32, + } + } +} + +/// # Creating and destroying `Pipeline`s +impl DeviceContext { + pub fn pipeline_create( + &mut self, + pipeline_compile_options: &PipelineCompileOptions, + link_options: PipelineLinkOptions, + program_groups: &[ProgramGroup], + ) -> Result<(Pipeline, String)> { + let popt = pipeline_compile_options.build(); + + let link_options: sys::OptixPipelineLinkOptions = link_options.into(); + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let mut inner: sys::OptixPipeline = std::ptr::null_mut(); + + let res = unsafe { + optix_call!(optixPipelineCreate( + self.raw, + &popt, + &link_options, + program_groups.as_ptr() as *const _, + program_groups.len() as u32, + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut inner, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((Pipeline { inner }, log)), + Err(source) => Err(Error::PipelineCreation { source, log }), + } + } + + /// Destroys the pipeline + /// # Safety + /// Thread safety: A pipeline must not be destroyed while it is still in use + /// by concurrent API calls in other threads. + pub fn pipeline_destroy(&mut self, pipeline: Pipeline) -> Result<()> { + unsafe { Ok(optix_call!(optixPipelineDestroy(pipeline.inner))?) } + } +} + +impl Pipeline { + /// Sets the stack sizes for a pipeline. + /// + /// Users are encouraged to see the programming guide and the + /// implementations of the helper functions to understand how to + /// construct the stack sizes based on their particular needs. + /// If this method is not used, an internal default implementation is used. + /// The default implementation is correct (but not necessarily optimal) as + /// long as the maximum depth of call trees of CC and DC programs is at most + /// 2 and no motion transforms are used. + /// The maxTraversableGraphDepth responds to the maximal number of + /// traversables visited when calling trace. Every acceleration structure + /// and motion transform count as one level of traversal. E.g., for a simple + /// IAS (instance acceleration structure) -> GAS (geometry acceleration + /// structure) traversal graph, the maxTraversableGraphDepth is two. For + /// IAS -> MT (motion transform) -> GAS, the maxTraversableGraphDepth is + /// three. Note that it does not matter whether a IAS or GAS has motion + /// or not, it always counts as one. Launching optix with exceptions + /// turned on (see OPTIX_EXCEPTION_FLAG_TRACE_DEPTH) will throw an + /// exception if the specified maxTraversableGraphDepth is too small. + /// + /// # Arguments + /// * `direct_callable_stack_size_from_traversable` - The direct stack size + /// requirement for direct callables invoked from IS or AH + /// * `direct_callable_stack_size_from_state` - The direct stack size + /// requirement for direct callables invoked from RG, MS, or CH. + /// * `continuation_stack_size` - The continuation stack requirement. + /// * `max_traversable_graph_depth` - The maximum depth of a traversable + /// graph + /// passed to trace + pub fn set_stack_size( + &self, + direct_callable_stack_size_from_traversable: u32, + direct_callable_stack_size_from_state: u32, + continuation_stack_size: u32, + max_traversable_graph_depth: u32, + ) -> Result<()> { + unsafe { + Ok(optix_call!(optixPipelineSetStackSize( + self.inner, + direct_callable_stack_size_from_traversable, + direct_callable_stack_size_from_state, + continuation_stack_size, + max_traversable_graph_depth, + ))?) + } + } +} diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs new file mode 100644 index 00000000..3c7a128a --- /dev/null +++ b/crates/optix/src/program_group.rs @@ -0,0 +1,400 @@ +use crate::{context::DeviceContext, error::Error, module::Module, optix_call, sys}; +type Result = std::result::Result; + +use ustr::Ustr; + +use std::ffi::CStr; + +#[derive(Clone)] +pub struct ProgramGroupModule<'m> { + pub module: &'m Module, + pub entry_function_name: Ustr, +} + +pub enum ProgramGroupDesc<'m> { + Raygen(ProgramGroupModule<'m>), + Miss(ProgramGroupModule<'m>), + Exception(ProgramGroupModule<'m>), + Hitgroup { + ch: Option>, + ah: Option>, + is: Option>, + }, + Callables { + dc: Option>, + cc: Option>, + }, +} + +impl<'m> ProgramGroupDesc<'m> { + pub fn raygen(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) + } + + pub fn miss(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) + } + + pub fn exception(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) + } + + pub fn hitgroup( + ch: Option<(&'m Module, Ustr)>, + ah: Option<(&'m Module, Ustr)>, + is: Option<(&'m Module, Ustr)>, + ) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Hitgroup { + ch: ch.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name, + }), + ah: ah.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name, + }), + is: is.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name, + }), + } + } +} + +#[repr(transparent)] +#[derive(Clone)] +/// Modules can contain more than one program. The program in the module is +/// designated by its entry function name as part of the [ProgramGroupDesc] +/// struct passed to [DeviceContext::program_group_create()] and +/// [DeviceContext::program_group_create_single()], or specified directly in the +/// case of [DeviceContext::program_group_raygen()], +/// [DeviceContext::program_group_miss()] and +/// [DeviceContext::program_group_hitgroup()] +/// +/// Four program groups can contain only a single program; only hitgroups can +/// designate up to three programs for the closest-hit, any-hit, and +/// intersection programs. +/// +/// Programs from modules can be used in any number of [ProgramGroup] objects. +/// The resulting program groups can be used to fill in any number of +/// SBT records. Program groups can also be used across pipelines as long as the +/// compilation options match. +/// +/// A hit group specifies the intersection program used to test whether a ray +/// intersects a primitive, together with the hit shaders to be executed when a +/// ray does intersect the primitive. For built-in primitive types, a built-in +/// intersection program should be obtained from +/// [DeviceContext::builtin_is_module_get()] and used in the hit group. As a +/// special case, the intersection program is not required – and is ignored – +/// for triangle primitives. +/// +/// # Safety +/// The lifetime of a module must extend to the lifetime of any +/// OptixProgramGroup that references that module. +pub struct ProgramGroup { + pub(crate) raw: sys::OptixProgramGroup, +} + +impl ProgramGroup { + /// Use this information to calculate the total required stack sizes for a + /// particular call graph of NVIDIA OptiX programs. + /// + /// To set the stack sizes for a particular pipeline, use + /// [Pipeline::set_stack_size()](crate::Pipeline::set_stack_size()). + pub fn get_stack_size(&self) -> Result { + let mut stack_sizes = StackSizes::default(); + unsafe { + Ok(optix_call!(optixProgramGroupGetStackSize( + self.raw, + &mut stack_sizes as *mut _ as *mut _ + )) + .map(|_| stack_sizes)?) + } + } +} + +impl PartialEq for ProgramGroup { + fn eq(&self, rhs: &ProgramGroup) -> bool { + self.raw == rhs.raw + } +} + +/// # Creating and destroying `ProgramGroup`s +impl DeviceContext { + /// Create a [ProgramGroup] for each of the [ProgramGroupDesc] objects in + /// `desc`. + pub fn program_group_create( + &mut self, + desc: &[ProgramGroupDesc], + ) -> Result<(Vec, String)> { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix73"))] { + let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; + } else { + let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; + } + } + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let pg_desc: Vec = desc.iter().map(|d| d.into()).collect(); + + let mut raws = vec![std::ptr::null_mut(); pg_desc.len()]; + + let res = unsafe { + optix_call!(optixProgramGroupCreate( + self.raw, + pg_desc.as_ptr(), + pg_desc.len() as u32, + &pg_options, + log.as_mut_ptr() as *mut i8, + &mut log_len, + raws.as_mut_ptr(), + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok(( + raws.iter().map(|raw| ProgramGroup { raw: *raw }).collect(), + log, + )), + Err(source) => Err(Error::ProgramGroupCreation { source, log }), + } + } + + /// Create a single [ProgramGroup] specified by `desc`. + pub fn program_group_create_single( + &mut self, + desc: &ProgramGroupDesc, + ) -> Result<(ProgramGroup, String)> { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix73"))] { + let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; + } else { + let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; + } + } + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let pg_desc: sys::OptixProgramGroupDesc = desc.into(); + + let mut raw = std::ptr::null_mut(); + + let res = unsafe { + optix_call!(optixProgramGroupCreate( + self.raw, + &pg_desc, + 1, + &pg_options, + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut raw, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((ProgramGroup { raw }, log)), + Err(source) => Err(Error::ProgramGroupCreation { source, log }), + } + } + + /// Create a raygen [ProgramGroup] from `entry_function_name` in `module`. + pub fn program_group_raygen( + &mut self, + module: &Module, + entry_function_name: Ustr, + ) -> Result { + let desc = ProgramGroupDesc::raygen(module, entry_function_name); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Create a miss [ProgramGroup] from `entry_function_name` in `module`. + pub fn program_group_miss( + &mut self, + module: &Module, + entry_function_name: Ustr, + ) -> Result { + let desc = ProgramGroupDesc::miss(module, entry_function_name); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Create an exception [ProgramGroup] from `entry_function_name` in `module`. + pub fn program_group_exception( + &mut self, + module: &Module, + entry_function_name: Ustr, + ) -> Result { + let desc = ProgramGroupDesc::exception(module, entry_function_name); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Create a hitgroup [ProgramGroup] from any combination of + /// `(module, entry_function_name)` pairs. + pub fn program_group_hitgroup( + &mut self, + closest_hit: Option<(&Module, Ustr)>, + any_hit: Option<(&Module, Ustr)>, + intersection: Option<(&Module, Ustr)>, + ) -> Result { + let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); + Ok(self.program_group_create_single(&desc)?.0) + } + + /// Destroy `program_group` + /// + /// # Safety + /// Thread safety: A program group must not be destroyed while it is still + /// in use by concurrent API calls in other threads. + pub fn program_group_destroy(&mut self, program_group: ProgramGroup) -> Result<()> { + unsafe { Ok(optix_call!(optixProgramGroupDestroy(program_group.raw))?) } + } +} + +impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { + fn from(desc: &ProgramGroupDesc<'m>) -> sys::OptixProgramGroupDesc { + unsafe { + match &desc { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + raygen: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_char_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_char_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_char_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Hitgroup { ch, ah, is } => { + let mut efn_ch_ptr = std::ptr::null(); + let mut efn_ah_ptr = std::ptr::null(); + let mut efn_is_ptr = std::ptr::null(); + + let module_ch = if let Some(pg_ch) = &ch { + efn_ch_ptr = pg_ch.entry_function_name.as_char_ptr(); + pg_ch.module.raw + } else { + std::ptr::null_mut() + }; + + let module_ah = if let Some(pg_ah) = &ah { + efn_ah_ptr = pg_ah.entry_function_name.as_char_ptr(); + pg_ah.module.raw + } else { + std::ptr::null_mut() + }; + + let module_is = if let Some(pg_is) = &is { + efn_is_ptr = pg_is.entry_function_name.as_char_ptr(); + pg_is.module.raw + } else { + std::ptr::null_mut() + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + hitgroup: sys::OptixProgramGroupHitgroup { + moduleCH: module_ch, + entryFunctionNameCH: efn_ch_ptr, + moduleAH: module_ah, + entryFunctionNameAH: efn_ah_ptr, + moduleIS: module_is, + entryFunctionNameIS: efn_is_ptr, + }, + }, + flags: 0, + } + } + ProgramGroupDesc::Callables { dc, cc } => { + let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { + (pg_dc.module.raw, pg_dc.entry_function_name.as_char_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { + (pg_cc.module.raw, pg_cc.entry_function_name.as_char_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + callables: sys::OptixProgramGroupCallables { + moduleDC: module_dc, + entryFunctionNameDC: efn_dc, + moduleCC: module_cc, + entryFunctionNameCC: efn_cc, + }, + }, + flags: 0, + } + } + } + } + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct StackSizes { + pub css_rg: u32, + pub css_mg: u32, + pub css_ch: u32, + pub css_ah: u32, + pub css_is: u32, + pub css_cc: u32, + pub css_dc: u32, +} diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs new file mode 100644 index 00000000..7f98319f --- /dev/null +++ b/crates/optix/src/shader_binding_table.rs @@ -0,0 +1,140 @@ +use crate::{ + context::DeviceContext, + error::{Error, ToResult}, + optix_call, + program_group::ProgramGroup, + sys, +}; + +use cust::memory::{DSlice, DeviceCopy}; +use cust_raw::CUdeviceptr; + +type Result = std::result::Result; + +#[repr(C)] +#[repr(align(16))] +#[derive(Copy, Clone)] +pub struct SbtRecord +where + T: Copy, +{ + header: sys::SbtRecordHeader, + data: T, +} + +// impl Copy for SbtRecord where T: Copy {} + +impl SbtRecord +where + T: Copy, +{ + pub fn pack(data: T, program_group: &ProgramGroup) -> Result> { + let mut rec = SbtRecord { + header: sys::SbtRecordHeader::default(), + data, + }; + + unsafe { + Ok(optix_call!(optixSbtRecordPackHeader( + program_group.raw, + &mut rec as *mut _ as *mut std::os::raw::c_void, + )) + .map(|_| rec)?) + } + } +} + +unsafe impl DeviceCopy for SbtRecord {} + +#[repr(C)] +pub struct ShaderBindingTable { + raygen_record: CUdeviceptr, + exception_record: CUdeviceptr, + miss_record_base: CUdeviceptr, + miss_record_stride_in_bytes: u32, + miss_record_count: u32, + hitgroup_record_base: CUdeviceptr, + hitgroup_record_stride_in_bytes: u32, + hitgroup_record_count: u32, + callables_record_base: CUdeviceptr, + callables_record_stride_in_bytes: u32, + callables_record_count: u32, +} + +impl ShaderBindingTable { + pub fn new(buf_raygen_record: &mut DSlice>) -> Self { + let raygen_record = buf_raygen_record.as_device_ptr().as_raw() as u64; + ShaderBindingTable { + raygen_record, + exception_record: 0, + miss_record_base: 0, + miss_record_stride_in_bytes: 0, + miss_record_count: 0, + hitgroup_record_base: 0, + hitgroup_record_stride_in_bytes: 0, + hitgroup_record_count: 0, + callables_record_base: 0, + callables_record_stride_in_bytes: 0, + callables_record_count: 0, + } + } + + pub fn build(self) -> sys::OptixShaderBindingTable { + unsafe { std::mem::transmute::(self) } + } + + pub fn exception( + mut self, + buf_exception_record: &mut DSlice>, + ) -> Self { + if buf_exception_record.len() != 1 { + panic!("SBT not psased single exception record",); + } + self.exception_record = buf_exception_record.as_device_ptr().as_raw() as u64; + self + } + + pub fn miss(mut self, buf_miss_records: &mut DSlice>) -> Self { + if buf_miss_records.len() == 0 { + panic!("SBT passed empty miss records"); + } + self.miss_record_base = buf_miss_records.as_device_ptr().as_raw() as u64; + self.miss_record_stride_in_bytes = std::mem::size_of::>() as u32; + self.miss_record_count = buf_miss_records.len() as u32; + self + } + + pub fn hitgroup( + mut self, + buf_hitgroup_records: &mut DSlice>, + ) -> Self { + if buf_hitgroup_records.len() == 0 { + panic!("SBT passed empty hitgroup records"); + } + self.hitgroup_record_base = buf_hitgroup_records.as_device_ptr().as_raw() as u64; + self.hitgroup_record_stride_in_bytes = std::mem::size_of::>() as u32; + self.hitgroup_record_count = buf_hitgroup_records.len() as u32; + self + } + + pub fn callables( + mut self, + buf_callables_records: &mut DSlice>, + ) -> Self { + if buf_callables_records.len() == 0 { + panic!("SBT passed empty callables records"); + } + self.callables_record_base = buf_callables_records.as_device_ptr().as_raw() as u64; + self.callables_record_stride_in_bytes = std::mem::size_of::>() as u32; + self.callables_record_count = buf_callables_records.len() as u32; + self + } +} + +// Sanity check that the size of this union we're defining matches the one in +// optix header so we don't get any nasty surprises +fn _size_check() { + unsafe { + std::mem::transmute::(panic!()); + } +} diff --git a/crates/optix/src/sys.rs b/crates/optix/src/sys.rs index dfb5caf3..7243625a 100644 --- a/crates/optix/src/sys.rs +++ b/crates/optix/src/sys.rs @@ -1,3 +1,5 @@ +#![allow(warnings)] + use cust_raw::*; use std::mem::ManuallyDrop; @@ -12,6 +14,7 @@ extern "C" { // The SBT record header is an opaque blob used by optix #[repr(C)] +#[derive(Default, Clone, Copy)] pub struct SbtRecordHeader { header: [u8; OptixSbtRecordHeaderSize as usize], } @@ -22,14 +25,6 @@ impl SbtRecordHeader { } } -impl Default for SbtRecordHeader { - fn default() -> SbtRecordHeader { - SbtRecordHeader { - header: [0u8; OptixSbtRecordHeaderSize as usize], - } - } -} - // Manually define the build input union as the bindgen is pretty nasty #[repr(C)] pub union OptixBuildInputUnion { From ad863c06bd0797b96accf694db782af530c9066c Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 13:56:55 +1300 Subject: [PATCH 024/100] Add example 03 Generate an animated pattern in the raygen and display it in a window using glfw --- crates/optix/examples/ex03_window/Cargo.toml | 18 + crates/optix/examples/ex03_window/build.rs | 49 ++ .../examples/ex03_window/src/ex03_window.cu | 103 ++++ .../optix/examples/ex03_window/src/gl_util.rs | 582 ++++++++++++++++++ crates/optix/examples/ex03_window/src/main.rs | 86 +++ .../examples/ex03_window/src/renderer.rs | 243 ++++++++ .../optix/examples/ex03_window/src/vector.rs | 301 +++++++++ 7 files changed, 1382 insertions(+) create mode 100644 crates/optix/examples/ex03_window/Cargo.toml create mode 100644 crates/optix/examples/ex03_window/build.rs create mode 100644 crates/optix/examples/ex03_window/src/ex03_window.cu create mode 100644 crates/optix/examples/ex03_window/src/gl_util.rs create mode 100644 crates/optix/examples/ex03_window/src/main.rs create mode 100644 crates/optix/examples/ex03_window/src/renderer.rs create mode 100644 crates/optix/examples/ex03_window/src/vector.rs diff --git a/crates/optix/examples/ex03_window/Cargo.toml b/crates/optix/examples/ex03_window/Cargo.toml new file mode 100644 index 00000000..38373a2f --- /dev/null +++ b/crates/optix/examples/ex03_window/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ex03_window" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +optix = {path = "../../"} +cust = {path = "../../../cust"} +anyhow = "1.0.44" +ustr = "0.8.1" +glfw = "0.42.0" +gl = "0.14.0" +num-traits = "0.2.14" + +[build-dependencies] +find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex03_window/build.rs b/crates/optix/examples/ex03_window/build.rs new file mode 100644 index 00000000..1dadfe69 --- /dev/null +++ b/crates/optix/examples/ex03_window/build.rs @@ -0,0 +1,49 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; + +fn main() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + + cuda_include = cuda_include.join("include"); + + let args = vec![ + format!("-I{}", optix_include.display()), + format!("-I{}/../common/gdt", manifest_dir), + ]; + + compile_to_ptx("src/ex03_window.cu", &args); +} + +fn compile_to_ptx(cu_path: &str, args: &[String]) { + println!("cargo:rerun-if-changed={}", cu_path); + + let full_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(cu_path); + + let mut ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(cu_path); + ptx_path.set_extension("ptx"); + std::fs::create_dir_all(ptx_path.parent().unwrap()).unwrap(); + + let output = std::process::Command::new("nvcc") + .arg("-ptx") + .arg(&full_path) + .arg("-o") + .arg(&ptx_path) + .args(args) + .output() + .expect("failed to fun nvcc"); + + if !output.status.success() { + panic!("{}", unsafe { String::from_utf8_unchecked(output.stderr) }); + } +} diff --git a/crates/optix/examples/ex03_window/src/ex03_window.cu b/crates/optix/examples/ex03_window/src/ex03_window.cu new file mode 100644 index 00000000..b35e319c --- /dev/null +++ b/crates/optix/examples/ex03_window/src/ex03_window.cu @@ -0,0 +1,103 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace osc { + +using namespace gdt; +struct LaunchParams { + float4* color_buffer; + vec2i fb_size; + int frame_id{0}; +}; + +/*! launch parameters in constant memory, filled in by optix upon + optixLaunch (this gets filled in from the buffer we pass to + optixLaunch) */ +extern "C" __constant__ LaunchParams PARAMS; + +//------------------------------------------------------------------------------ +// closest hit and anyhit programs for radiance-type rays. +// +// Note eventually we will have to create one pair of those for each +// ray type and each geometry type we want to render; but this +// simple example doesn't use any actual geometries yet, so we only +// create a single, dummy, set of them (we do have to have at least +// one group of them to set up the SBT) +//------------------------------------------------------------------------------ + +extern "C" __global__ void +__closesthit__radiance() { /*! for this simple example, this will remain empty + */ +} + +extern "C" __global__ void +__anyhit__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// miss program that gets called for any ray that did not have a +// valid intersection +// +// as with the anyhit/closest hit programs, in this example we only +// need to have _some_ dummy function to set up a valid SBT +// ------------------------------------------------------------------------------ + +extern "C" __global__ void +__miss__radiance() { /*! for this simple example, this will remain empty */ +} + + +//------------------------------------------------------------------------------ +// ray gen program - the actual rendering happens in here +//------------------------------------------------------------------------------ +extern "C" __global__ void __raygen__renderFrame() { + if (PARAMS.frame_id == 0 && optixGetLaunchIndex().x == 0 && + optixGetLaunchIndex().y == 0) { + // we could of course also have used optixGetLaunchDims to query + // the launch size, but accessing the PARAMS here + // makes sure they're not getting optimized away (because + // otherwise they'd not get used) + printf("############################################\n"); + printf("Hello world from OptiX 7 raygen program!\n(within a " + "%ix%i-sized launch)\n", + PARAMS.fb_size.x, PARAMS.fb_size.y); + printf("############################################\n"); + } + + // ------------------------------------------------------------------ + // for this example, produce a simple test pattern: + // ------------------------------------------------------------------ + + // compute a test pattern based on pixel ID + const int ix = optixGetLaunchIndex().x; + const int iy = optixGetLaunchIndex().y; + + int frameID = PARAMS.frame_id; + + const float r = float((ix + frameID) % 256) / 255.0f; + const float g = float((iy + frameID) % 256) / 255.0f; + const float b = float((ix + iy + frameID) % 256) / 255.0f; + + // and write to frame buffer ... + const unsigned fb_index = ix + iy * PARAMS.fb_size.x; + PARAMS.color_buffer[fb_index] = make_float4(r, g, b, 1.0f); +} + +} // namespace osc diff --git a/crates/optix/examples/ex03_window/src/gl_util.rs b/crates/optix/examples/ex03_window/src/gl_util.rs new file mode 100644 index 00000000..c8b39caa --- /dev/null +++ b/crates/optix/examples/ex03_window/src/gl_util.rs @@ -0,0 +1,582 @@ +use gl; +use gl::types::{GLchar, GLenum, GLint, GLsizeiptr, GLuint, GLvoid}; +use std::ffi::{CStr, CString}; + +use crate::vector::*; + +pub struct Shader { + id: GLuint, +} + +impl Shader { + pub fn from_source( + source: &CStr, + shader_type: GLenum, + ) -> Result { + let id = unsafe { gl::CreateShader(shader_type) }; + + unsafe { + gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null()); + gl::CompileShader(id); + } + + let mut success: GLint = 1; + unsafe { + gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut success); + } + + if success == 0 { + let mut len: GLint = 0; + unsafe { + gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + let error = create_whitespace_cstring(len as usize); + unsafe { + gl::GetShaderInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut GLchar, + ); + } + Err(error.to_string_lossy().into_owned()) + } else { + Ok(Shader { id }) + } + } + + pub fn vertex_from_source(source: &CStr) -> Result { + Shader::from_source(source, gl::VERTEX_SHADER) + } + + pub fn fragment_from_source(source: &CStr) -> Result { + Shader::from_source(source, gl::FRAGMENT_SHADER) + } + + pub fn id(&self) -> GLuint { + self.id + } +} + +impl Drop for Shader { + fn drop(&mut self) { + unsafe { gl::DeleteShader(self.id) }; + } +} + +pub struct Program { + id: GLuint, +} + +impl Program { + pub fn from_shaders(shaders: &[Shader]) -> Result { + let id = unsafe { gl::CreateProgram() }; + + for shader in shaders { + unsafe { gl::AttachShader(id, shader.id()) }; + } + + unsafe { gl::LinkProgram(id) }; + + let mut success: GLint = 1; + unsafe { + gl::GetProgramiv(id, gl::LINK_STATUS, &mut success); + } + + if success == 0 { + let mut len: GLint = 0; + unsafe { + gl::GetProgramiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + let error = create_whitespace_cstring(len as usize); + unsafe { + gl::GetProgramInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut GLchar, + ); + } + return Err(error.to_string_lossy().into_owned()); + } + + for shader in shaders { + unsafe { gl::DetachShader(id, shader.id()) } + } + + Ok(Program { id }) + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn use_program(&self) { + unsafe { + gl::UseProgram(self.id); + } + } + + pub fn get_location(&self, name: &str) -> Result { + let cname = CString::new(name).unwrap(); + let loc = unsafe { + gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) + }; + + if loc != -1 { + Ok(loc) + } else { + Err("Could not get location".to_owned()) + } + } + + pub fn set_uniform(&self, loc: GLint, v: i32) { + unsafe { + gl::ProgramUniform1i(self.id, loc, v); + } + } +} + +fn create_whitespace_cstring(len: usize) -> CString { + let mut buffer: Vec = Vec::with_capacity(len as usize + 1); + buffer.extend([b' '].iter().cycle().take(len as usize)); + unsafe { CString::from_vec_unchecked(buffer) } +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum BufferType { + ArrayBuffer = gl::ARRAY_BUFFER, +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum BufferUsage { + StaticDraw = gl::STATIC_DRAW, + StreamDraw = gl::STREAM_DRAW, +} + +pub struct Buffer { + id: GLuint, + buffer_type: BufferType, + _phantom: std::marker::PhantomData, +} + +impl Buffer { + pub fn new(buffer_type: BufferType) -> Buffer { + let mut id: GLuint = 0; + unsafe { + gl::GenBuffers(1, &mut id); + } + Buffer { + id, + buffer_type, + _phantom: std::marker::PhantomData, + } + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn buffer_data(&self, data: &[T], usage: BufferUsage) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, self.id); + gl::BufferData( + self.buffer_type as GLuint, + (data.len() * std::mem::size_of::()) as GLsizeiptr, + data.as_ptr() as *const GLvoid, + usage as GLenum, + ); + gl::BindBuffer(self.buffer_type as GLuint, 0); + } + } + + pub fn bind(&self) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, self.id); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, 0); + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.id as *const GLuint); + } + } +} + +pub struct VertexArray { + id: GLuint, +} + +impl VertexArray { + pub fn new() -> VertexArray { + let mut id: GLuint = 0; + unsafe { + gl::GenVertexArrays(1, &mut id); + } + + VertexArray { id } + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn bind(&self) { + unsafe { + gl::BindVertexArray(self.id); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindVertexArray(0); + } + } +} + +impl Drop for VertexArray { + fn drop(&mut self) { + unsafe { + gl::DeleteVertexArrays(1, &self.id as *const GLuint); + } + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x2 { + x: f32, + y: f32, +} + +impl f32x2 { + pub fn new(x: f32, y: f32) -> f32x2 { + f32x2 { x, y } + } + + pub fn num_components() -> usize { + 2 + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x3 { + x: f32, + y: f32, + z: f32, +} + +impl f32x3 { + pub fn new(x: f32, y: f32, z: f32) -> f32x3 { + f32x3 { x, y, z } + } + + pub fn num_components() -> usize { + 3 + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x4 { + x: f32, + y: f32, + z: f32, + w: f32, +} + +impl f32x4 { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> f32x4 { + f32x4 { x, y, z, w } + } + + pub fn zero() -> f32x4 { + f32x4::new(0.0, 0.0, 0.0, 0.0) + } + + pub fn set(&mut self, x: f32, y: f32, z: f32, w: f32) { + self.x = x; + self.y = y; + self.z = z; + self.w = w; + } + + pub fn num_components() -> usize { + 4 + } +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Vertex { + p: f32x3, + st: f32x2, +} +impl Vertex { + pub fn new(p: f32x3, st: f32x2) -> Vertex { + Vertex { p, st } + } + + unsafe fn vertex_attrib_pointer( + num_components: usize, + stride: usize, + location: usize, + offset: usize, + ) { + gl::EnableVertexAttribArray(location as gl::types::GLuint); // location(0) + gl::VertexAttribPointer( + location as gl::types::GLuint, // index of the vertex attribute + num_components as gl::types::GLint, /* number of components per + * vertex attrib */ + gl::FLOAT, + gl::FALSE, // normalized (int-to-float conversion), + stride as gl::types::GLint, /* byte stride between + * successive elements */ + offset as *const gl::types::GLvoid, /* offset of the first + * element */ + ); + } + + pub fn vertex_attrib_pointers() { + let stride = std::mem::size_of::(); + + let location = 0; + let offset = 0; + + // and configure the vertex array + unsafe { + Vertex::vertex_attrib_pointer( + f32x3::num_components(), + stride, + location, + offset, + ); + } + + let location = location + 1; + let offset = offset + std::mem::size_of::(); + + // and configure the st array + unsafe { + Vertex::vertex_attrib_pointer( + f32x2::num_components(), + stride, + location, + offset, + ); + } + } +} + +pub struct FullscreenQuad { + width: u32, + height: u32, + vertex_array: VertexArray, + program: Program, + texture_id: GLuint, + loc_progression: GLint, +} + +impl FullscreenQuad { + pub fn new(width: u32, height: u32) -> Result { + let vert_shader = Shader::vertex_from_source( + CStr::from_bytes_with_nul( + b" + #version 330 core + layout (location = 0) in vec3 _p; + layout (location = 1) in vec2 _st; + out vec2 st; + void main() { + gl_Position = vec4(_p, 1.0); + st = _st; + } + \0", + ) + .unwrap(), + )?; + + let frag_shader = Shader::fragment_from_source( + CStr::from_bytes_with_nul( + b" + #version 330 core + in vec2 st; + out vec4 Color; + + uniform sampler2D smp2d_0; + uniform int progression; + + void main() { + vec4 col = texture(smp2d_0, st); + col.r = pow(col.r / progression, 1/2.2); + col.g = pow(col.g / progression, 1/2.2); + col.b = pow(col.b / progression, 1/2.2); + Color = col; + } + \0", + ) + .unwrap(), + )?; + + let program = Program::from_shaders(&[vert_shader, frag_shader])?; + program.use_program(); + let loc_progression = program.get_location("progression")?; + + let vertices: Vec = vec![ + Vertex::new(f32x3::new(-1.0, -1.0, 0.0), f32x2::new(0.0, 0.0)), + Vertex::new(f32x3::new(1.0, -1.0, 0.0), f32x2::new(1.0, 0.0)), + Vertex::new(f32x3::new(1.0, 1.0, 0.0), f32x2::new(1.0, 1.0)), + Vertex::new(f32x3::new(-1.0, -1.0, 0.0), f32x2::new(0.0, 0.0)), + Vertex::new(f32x3::new(1.0, 1.0, 0.0), f32x2::new(1.0, 1.0)), + Vertex::new(f32x3::new(-1.0, 1.0, 0.0), f32x2::new(0.0, 1.0)), + ]; + let vertex_buffer = Buffer::::new(BufferType::ArrayBuffer); + vertex_buffer.buffer_data(&vertices, BufferUsage::StaticDraw); + + // Generate and bind the VAO + let vertex_array = VertexArray::new(); + + vertex_array.bind(); + // Re-bind the VBO to associate the two. We could just have left it + // bound earlier and let the association happen when we + // configure the VAO but this way at least makes the connection + // between the two seem more explicit, despite the magical + // state machine hiding in OpenGL + vertex_buffer.bind(); + + // Set up the vertex attribute pointers for all locations + Vertex::vertex_attrib_pointers(); + + // now unbind both the vbo and vao to keep everything cleaner + vertex_buffer.unbind(); + vertex_array.unbind(); + + // generate test texture data using the image width rather than the + // framebuffer width + let mut tex_data = Vec::with_capacity((width * height) as usize); + for y in 0..height { + for x in 0..width { + tex_data.push(f32x4::new( + (x as f32) / width as f32, + (y as f32) / height as f32, + 1.0, + 0.0, + )); + } + } + + // generate the texture for the quad + let mut texture_id: gl::types::GLuint = 0; + unsafe { + gl::GenTextures(1, &mut texture_id); + gl::ActiveTexture(gl::TEXTURE0); + gl::Enable(gl::TEXTURE_2D); + gl::BindTexture(gl::TEXTURE_2D, texture_id); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_BORDER as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_BORDER as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::NEAREST as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + gl::NEAREST as gl::types::GLint, + ); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA32F as gl::types::GLint, + width as gl::types::GLint, + height as gl::types::GLint, + 0, + gl::RGBA, + gl::FLOAT, + tex_data.as_ptr() as *const gl::types::GLvoid, + ); + } + + Ok(FullscreenQuad { + width, + height, + vertex_array, + program, + texture_id, + loc_progression, + }) + } + + pub fn draw(&self) { + self.program.use_program(); + self.vertex_array.bind(); + unsafe { + gl::DrawArrays( + gl::TRIANGLES, + 0, // starting index in the enabled array + 6, // number of indices to draw + ) + } + self.vertex_array.unbind(); + } + + pub fn update_texture(&self, data: &[V4f32]) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.texture_id); + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, + 0, + 0, + self.width as GLint, + self.height as GLint, + gl::RGBA, + gl::FLOAT, + data.as_ptr() as *const GLvoid, + ); + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + unsafe { + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA32F as gl::types::GLint, + width as gl::types::GLint, + height as gl::types::GLint, + 0, + gl::RGBA, + gl::FLOAT, + std::ptr::null(), + ); + } + self.width = width; + self.height = height; + } + + pub fn set_progression(&self, progression: i32) { + self.program.set_uniform(self.loc_progression, progression); + } +} diff --git a/crates/optix/examples/ex03_window/src/main.rs b/crates/optix/examples/ex03_window/src/main.rs new file mode 100644 index 00000000..39072e60 --- /dev/null +++ b/crates/optix/examples/ex03_window/src/main.rs @@ -0,0 +1,86 @@ +mod renderer; +use renderer::Renderer; + +mod vector; +pub use vector::*; +mod gl_util; +use gl_util::FullscreenQuad; +use glfw::{Action, Context, Key}; + +fn main() -> Result<(), Box> { + let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap(); + glfw.window_hint(glfw::WindowHint::ContextVersion(4, 1)); + glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true)); + glfw.window_hint(glfw::WindowHint::OpenGlProfile( + glfw::OpenGlProfileHint::Core, + )); + + let mut width = 960u32; + let mut height = 540u32; + + let mut renderer = Renderer::new(width, height)?; + + let (mut window, events) = glfw + .create_window( + width, + height, + "Example 03: in window", + glfw::WindowMode::Windowed, + ) + .expect("failed to create glfw window"); + + window.set_key_polling(true); + window.make_current(); + + // retina displays will return a higher res for the framebuffer + // which we need to use for the viewport + let (fb_width, fb_height) = window.get_framebuffer_size(); + + gl::load_with(|s| glfw.get_proc_address_raw(s) as *const std::os::raw::c_void); + + let mut fsq = FullscreenQuad::new(width, height).unwrap(); + + let mut image_data = vec![v4f32(0.0, 0.0, 0.0, 0.0); (width * height) as usize]; + + unsafe { + gl::Viewport(0, 0, fb_width, fb_height); + }; + + while !window.should_close() { + glfw.poll_events(); + for (_, event) in glfw::flush_messages(&events) { + handle_window_event(&mut window, event); + } + + let (w, h) = window.get_framebuffer_size(); + let w = w as u32; + let h = h as u32; + if w != width || h != height { + fsq.resize(w, h); + renderer.resize(w, h)?; + width = w; + height = h; + image_data.resize((width * height) as usize, v4f32(0.0, 0.0, 0.0, 0.0)); + } + + renderer.render()?; + renderer.download_pixels(&mut image_data)?; + fsq.update_texture(&image_data); + fsq.set_progression(1); + + // draw the quad + fsq.draw(); + + window.swap_buffers(); + } + + renderer.render()?; + Ok(()) +} + +fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) { + match event { + glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => window.set_should_close(true), + _ => {} + } +} diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs new file mode 100644 index 00000000..446c353b --- /dev/null +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -0,0 +1,243 @@ +use anyhow::{Context, Result}; +use cust::context::{Context as CuContext, ContextFlags}; +use cust::device::{Device, DeviceAttribute}; +use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::stream::{Stream, StreamFlags}; +use cust::CudaFlags; +use optix::{ + context::DeviceContext, + module::{ + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + PipelineCompileOptions, TraversableGraphFlags, + }, + pipeline::{Pipeline, PipelineLinkOptions}, + program_group::ProgramGroupDesc, + shader_binding_table::{SbtRecord, ShaderBindingTable}, +}; + +use ustr::ustr; + +use crate::vector::V4f32; + +pub struct Renderer { + ctx: DeviceContext, + cuda_context: CuContext, + stream: Stream, + launch_params: LaunchParams, + buf_launch_params: DBox, + buf_raygen: DBuffer, + buf_hitgroup: DBuffer, + buf_miss: DBuffer, + sbt: optix::sys::OptixShaderBindingTable, + pipeline: Pipeline, + color_buffer: DBuffer, +} + +impl Renderer { + pub fn new(width: u32, height: u32) -> Result> { + init_optix()?; + + // create CUDA and OptiX contexts + let device = Device::get_device(0)?; + let tex_align = device.get_attribute(DeviceAttribute::TextureAlignment)?; + let srf_align = device.get_attribute(DeviceAttribute::SurfaceAlignment)?; + println!("tex align: {}\nsrf align: {}", tex_align, srf_align); + + let cuda_context = + CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let stream = Stream::new(StreamFlags::DEFAULT, None)?; + + let mut ctx = DeviceContext::new(&cuda_context)?; + // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + + // create module + let module_compile_options = ModuleCompileOptions { + max_register_count: 50, + opt_level: CompileOptimizationLevel::Default, + debug_level: CompileDebugLevel::None, + }; + + let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + .uses_motion_blur(false) + .num_attribute_values(2) + .num_payload_values(2) + .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) + .exception_flags(ExceptionFlags::NONE); + + let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex03_window.ptx")); + + let (module, _log) = ctx + .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) + .context("Create module")?; + + // create raygen program + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + + let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + + // create miss program + let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + + let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + + let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( + Some((&module, ustr("__closesthit__radiance"))), + Some((&module, ustr("__anyhit__radiance"))), + None, + ); + + // create hitgroup programs + let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.iter().cloned()); + program_groups.extend(pg_miss.iter().cloned()); + program_groups.extend(pg_hitgroup.iter().cloned()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = ctx.pipeline_create( + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + + // create SBT + let rec_raygen: Vec<_> = pg_raygen + .iter() + .map(|pg| RaygenRecord::pack(0, pg).expect("failed to pack raygen record")) + .collect(); + + let rec_miss: Vec<_> = pg_miss + .iter() + .map(|pg| MissRecord::pack(0, pg).expect("failed to pack miss record")) + .collect(); + + let num_objects = 1; + let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); + + let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + + let sbt = ShaderBindingTable::new(&mut buf_raygen) + .miss(&mut buf_miss) + .hitgroup(&mut buf_hitgroup) + .build(); + + let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; + + let launch_params = LaunchParams { + frame_id: 0, + color_buffer: color_buffer.as_device_ptr(), + fb_size: Point2i { + x: width as i32, + y: height as i32, + }, + }; + + let buf_launch_params = DBox::new(&launch_params)?; + + Ok(Renderer { + ctx, + cuda_context, + stream, + launch_params, + buf_launch_params, + buf_raygen, + buf_hitgroup, + buf_miss, + sbt, + pipeline, + color_buffer, + }) + } + + pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Box> { + self.color_buffer = unsafe { DBuffer::uninitialized((width * height) as usize)? }; + self.launch_params.fb_size.x = width as i32; + self.launch_params.fb_size.y = height as i32; + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + Ok(()) + } + + pub fn render(&mut self) -> Result<(), Box> { + self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.frame_id += 1; + + unsafe { + optix::launch( + &self.pipeline, + &self.stream, + &mut self.buf_launch_params, + &self.sbt, + self.launch_params.fb_size.x as u32, + self.launch_params.fb_size.y as u32, + 1, + )?; + } + + self.stream.synchronize()?; + + Ok(()) + } + + pub fn download_pixels(&self, slice: &mut [V4f32]) -> Result<(), Box> { + self.color_buffer.copy_to(slice)?; + Ok(()) + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Point2i { + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct LaunchParams { + pub color_buffer: DevicePointer, + pub fb_size: Point2i, + pub frame_id: i32, +} + +unsafe impl DeviceCopy for LaunchParams {} + +type RaygenRecord = SbtRecord; +type MissRecord = SbtRecord; + +#[derive(Copy, Clone, Default)] +struct HitgroupSbtData { + object_id: u32, +} +unsafe impl DeviceCopy for HitgroupSbtData {} +type HitgroupRecord = SbtRecord; + +fn init_optix() -> Result<(), Box> { + cust::init(CudaFlags::empty())?; + let device_count = Device::num_devices()?; + if device_count == 0 { + panic!("No CUDA devices found!"); + } + + optix::init()?; + Ok(()) +} diff --git a/crates/optix/examples/ex03_window/src/vector.rs b/crates/optix/examples/ex03_window/src/vector.rs new file mode 100644 index 00000000..589a4134 --- /dev/null +++ b/crates/optix/examples/ex03_window/src/vector.rs @@ -0,0 +1,301 @@ +use core::ops; +pub use num_traits::{One, Zero}; + +pub trait Scalar: num_traits::One + num_traits::Zero {} + +impl Scalar for i8 {} +impl Scalar for i16 {} +impl Scalar for i32 {} +impl Scalar for i64 {} +impl Scalar for f32 {} +impl Scalar for f64 {} + +pub trait Vector { + type Component: Scalar; + + fn dot(&self, v: &Self) -> Self::Component; + + #[inline] + fn length2(&self) -> Self::Component { + self.dot(&self) + } +} + +macro_rules! vec_impl { + ($name:ident: $t:ty, $sc:ident, $align:expr, ($($c:ident),+)) => { + #[repr(C)] + #[derive(Clone, Copy, Default, PartialEq, Debug)] + pub struct $name + { + $( + pub $c: $t, + )+ + } + + impl $name + { + pub fn new($($c: $t),+) -> Self + { + Self { + $( + $c, + )+ + } + } + } + + impl Vector for $name + { + type Component = $t; + + #[inline] + fn dot(&self, v: &Self) -> $t + { + <$t>::zero() $( + + self.$c * v.$c + )+ + } + } + + impl From<$t> for $name + { + fn from(x: $t) -> Self + { + Self { + $( + $c: x, + )+ + } + } + } + + impl ops::Neg for $name + { + type Output = Self; + + fn neg(self) -> Self + { + Self { + $( + $c: -self.$c, + )+ + } + } + } + + impl ops::Add for $name + { + type Output = Self; + + #[inline] + fn add(self, v: Self) -> Self + { + Self { + $( + $c: self.$c + v.$c, + )+ + } + } + } + + impl ops::AddAssign for $name + { + #[inline] + fn add_assign(&mut self, v: Self) + { + $( + self.$c += v.$c; + )+ + } + } + + impl ops::Sub for $name + { + type Output = Self; + + #[inline] + fn sub(self, v: Self) -> Self + { + Self { + $( + $c: self.$c - v.$c, + )+ + } + } + } + + impl ops::SubAssign for $name + { + #[inline] + fn sub_assign(&mut self, v: Self) + { + $( + self.$c -= v.$c; + )+ + } + } + + impl ops::Mul for $name + { + type Output = Self; + + #[inline] + fn mul(self, v: Self) -> Self + { + Self { + $( + $c: self.$c * v.$c, + )+ + } + } + } + + impl ops::MulAssign for $name + { + #[inline] + fn mul_assign(&mut self, v: Self) + { + $( + self.$c *= v.$c; + )+ + } + } + + impl ops::Mul<$t> for $name + { + type Output = Self; + + #[inline] + fn mul(self, v: $t) -> Self + { + Self { + $( + $c: self.$c * v, + )+ + } + } + } + + impl ops::MulAssign<$t> for $name + { + #[inline] + fn mul_assign(&mut self, v: $t) + { + $( + self.$c *= v; + )+ + } + } + + impl ops::Div<$t> for $name + { + type Output = Self; + + #[inline] + fn div(self, v: $t) -> Self + { + Self { + $( + $c: self.$c / v, + )+ + } + } + } + + impl ops::DivAssign<$t> for $name + { + #[inline] + fn div_assign(&mut self, v: $t) + { + $( + self.$c /= v; + )+ + } + } + + impl ops::Mul<$name> for $t + { + type Output = $name; + + #[inline] + fn mul(self, v: $name) -> $name + { + $name { + $( + $c: self * v.$c, + )+ + } + } + } + + impl ops::Div<$name> for $t + { + type Output = $name; + + #[inline] + fn div(self, v: $name) -> $name + { + $name { + $( + $c: self / v.$c, + )+ + } + } + } + + pub fn $sc($($c: $t),+) -> $name + { + $name { + $( + $c, + )+ + } + } + + unsafe impl cust::memory::DeviceCopy for $name { + // fn device_align() -> usize { + // $align + // } + } + }; + +} + +vec_impl!(V2i8: i8, v2i8, 1, (x, y)); +vec_impl!(V2i16: i16, v2i16, 2, (x, y)); +vec_impl!(V2i32: i32, v2i32, 8, (x, y)); +vec_impl!(V2i64: i64, v2i64, 8, (x, y)); +vec_impl!(V3i8: i8, v3i8, 1, (x, y, z)); +vec_impl!(V3i16: i16, v3i16, 2, (x, y, z)); +vec_impl!(V3i32: i32, v3i32, 4, (x, y, z)); +vec_impl!(V3i64: i64, v3i64, 8, (x, y, z)); +vec_impl!(V4i8: i8, v4i8, 1, (x, y, z, w)); +vec_impl!(V4i16: i16, v4i16, 2, (x, y, z, w)); +vec_impl!(V4i32: i32, v4i32, 16, (x, y, z, w)); +vec_impl!(V4i64: i64, v4i64, 8, (x, y, z, w)); + +vec_impl!(V2f32: f32, v2f32, 8, (x, y)); +vec_impl!(V2f64: f64, v2f64, 8, (x, y)); +vec_impl!(V3f32: f32, v3f32, 4, (x, y, z)); +vec_impl!(V3f64: f64, v3f64, 8, (x, y, z)); +vec_impl!(V4f32: f32, v4f32, 16, (x, y, z, w)); +vec_impl!(V4f64: f64, v4f64, 8, (x, y, z, w)); + +vec_impl!(P2f32: f32, p2f32, 8, (x, y)); +vec_impl!(P2f64: f64, p2f64, 8, (x, y)); +vec_impl!(P3f32: f32, p3f32, 4, (x, y, z)); +vec_impl!(P3f64: f64, p3f64, 8, (x, y, z)); +vec_impl!(P4f32: f32, p4f32, 16, (x, y, z, w)); +vec_impl!(P4f64: f64, p4f64, 8, (x, y, z, w)); + +vec_impl!(N2f32: f32, n2f32, 8, (x, y)); +vec_impl!(N2f64: f64, n2f64, 8, (x, y)); +vec_impl!(N3f32: f32, n3f32, 4, (x, y, z)); +vec_impl!(N3f64: f64, n3f64, 8, (x, y, z)); +vec_impl!(N4f32: f32, n4f32, 16, (x, y, z, w)); +vec_impl!(N4f64: f64, n4f64, 8, (x, y, z, w)); + +#[inline] +pub fn dot(a: &T, b: &T) -> T::Component { + a.dot(b) +} From ee5bb1becfe267688924a5c30ba2aa6b16d50f1f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 14:05:10 +1300 Subject: [PATCH 025/100] add logging callback --- .../examples/ex02_pipeline/src/renderer.rs | 2 +- .../examples/ex03_window/src/renderer.rs | 5 +- crates/optix/src/context.rs | 81 ++++++++++++++++++- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index bff1e1d1..2d38f9a0 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -46,7 +46,7 @@ impl Renderer { let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 446c353b..93ea4f5d 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -39,16 +39,13 @@ impl Renderer { // create CUDA and OptiX contexts let device = Device::get_device(0)?; - let tex_align = device.get_attribute(DeviceAttribute::TextureAlignment)?; - let srf_align = device.get_attribute(DeviceAttribute::SurfaceAlignment)?; - println!("tex align: {}\nsrf align: {}", tex_align, srf_align); let cuda_context = CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - // ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 2c30693a..8f36ebf2 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -1,6 +1,11 @@ //! OptiX Device Context handling. -use std::{ffi::c_void, mem::MaybeUninit, ptr}; +use std::os::raw::{c_char, c_uint}; +use std::{ + ffi::{c_void, CStr}, + mem::MaybeUninit, + ptr, +}; use cust::context::ContextHandle; @@ -100,4 +105,78 @@ impl DeviceContext { pub fn as_raw(&self) -> sys::OptixDeviceContext { self.raw } + + /// Sets the current log callback method. + /// + /// The following log levels are defined. + /// * 0 - disable: Setting the callback level will disable all messages. The + /// callback function will not be called in this case. + /// * 1 - fatal: A non-recoverable error. The context and/or OptiX itself + /// might + /// no longer be in a usable state. + /// * 2 - error: A recoverable error, e.g., when passing invalid call + /// parameters. + /// * 3 - warning: Hints that OptiX might not behave exactly as requested by + /// the user or may perform slower than expected. + /// * 4 - print: Status or progress messages. + /// Higher levels might occur. + /// + /// # Safety + /// Note that the callback must live longer than the DeviceContext which + /// it's set for. This is because OptiX will fire messages when the + /// underlying OptixDeviceContext is destroyed. In order to do ensure + /// this we leak the closure `cb`. So don't go setting a new closure + /// every frame. + pub fn set_log_callback(&mut self, cb: F, level: u32) + where + F: FnMut(u32, &str, &str) + 'static, + { + let (closure, trampoline) = unsafe { unpack_closure(cb) }; + let res = unsafe { + sys::optixDeviceContextSetLogCallback(self.raw, Some(trampoline), closure, level) + }; + if res != sys::OptixResult::OPTIX_SUCCESS { + panic!("Failed to set log callback"); + } + } +} + +type LogCallback = extern "C" fn(c_uint, *const c_char, *const c_char, *mut c_void); + +/// Unpack a Rust closure, extracting a `void*` pointer to the data and a +/// trampoline function which can be used to invoke it. +/// +/// # Safety +/// +/// It is the user's responsibility to ensure the closure outlives the returned +/// `void*` pointer. +/// +/// Calling the trampoline function with anything except the `void*` pointer +/// will result in *Undefined Behaviour*. +/// +/// The closure should guarantee that it never panics, seeing as panicking +/// across the FFI barrier is *Undefined Behaviour*. You may find +/// `std::panic::catch_unwind()` useful. +unsafe fn unpack_closure(closure: F) -> (*mut c_void, LogCallback) +where + F: FnMut(u32, &str, &str), +{ + extern "C" fn trampoline( + level: c_uint, + tag: *const c_char, + msg: *const c_char, + data: *mut c_void, + ) where + F: FnMut(u32, &str, &str), + { + let tag = unsafe { CStr::from_ptr(tag).to_string_lossy().into_owned() }; + let msg = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; + let closure: &mut F = unsafe { &mut *(data as *mut F) }; + (*closure)(level, &tag, &msg); + } + + let cb = Box::new(closure); + let cb = Box::leak(cb); + + (cb as *mut F as *mut c_void, trampoline::) } From a50e587281160ee6bdeefe10f9ae26be42cee993 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 14:35:23 +1300 Subject: [PATCH 026/100] remove ustr --- crates/optix/Cargo.toml | 1 - .../optix/examples/ex02_pipeline/Cargo.toml | 1 - .../examples/ex02_pipeline/src/renderer.rs | 10 ++-- crates/optix/examples/ex03_window/Cargo.toml | 1 - .../examples/ex03_window/src/renderer.rs | 18 +++--- crates/optix/src/module.rs | 15 +++-- crates/optix/src/program_group.rs | 58 +++++++++---------- 7 files changed, 47 insertions(+), 57 deletions(-) diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 821101c7..7cdb8e5f 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -14,7 +14,6 @@ cust = { version = "0.1", path = "../cust" } cust_raw = { version = "0.11.2", path = "../cust_raw" } cfg-if = "1.0.0" bitflags = "1.3.2" -ustr = "0.8.1" [build-dependencies] bindgen = "0.59" diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml index 6c589340..189cb991 100644 --- a/crates/optix/examples/ex02_pipeline/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" optix = {path = "../../"} cust = {path = "../../../cust"} anyhow = "1.0.44" -ustr = "0.8.1" [build-dependencies] find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 2d38f9a0..813cd6e9 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -15,8 +15,6 @@ use optix::{ shader_binding_table::{SbtRecord, ShaderBindingTable}, }; -use ustr::ustr; - pub struct Renderer { ctx: DeviceContext, cuda_context: CuContext, @@ -69,18 +67,18 @@ impl Renderer { .context("Create module")?; // create raygen program - let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; // create miss program - let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( - Some((&module, ustr("__closesthit__radiance"))), - Some((&module, ustr("__anyhit__radiance"))), + Some((&module, "__closesthit__radiance")), + Some((&module, "__anyhit__radiance")), None, ); diff --git a/crates/optix/examples/ex03_window/Cargo.toml b/crates/optix/examples/ex03_window/Cargo.toml index 38373a2f..19a7646c 100644 --- a/crates/optix/examples/ex03_window/Cargo.toml +++ b/crates/optix/examples/ex03_window/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" optix = {path = "../../"} cust = {path = "../../../cust"} anyhow = "1.0.44" -ustr = "0.8.1" glfw = "0.42.0" gl = "0.14.0" num-traits = "0.2.14" diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 93ea4f5d..ae6334d1 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -15,22 +15,20 @@ use optix::{ shader_binding_table::{SbtRecord, ShaderBindingTable}, }; -use ustr::ustr; - use crate::vector::V4f32; pub struct Renderer { - ctx: DeviceContext, - cuda_context: CuContext, - stream: Stream, launch_params: LaunchParams, buf_launch_params: DBox, + sbt: optix::sys::OptixShaderBindingTable, buf_raygen: DBuffer, buf_hitgroup: DBuffer, buf_miss: DBuffer, - sbt: optix::sys::OptixShaderBindingTable, pipeline: Pipeline, color_buffer: DBuffer, + ctx: DeviceContext, + stream: Stream, + cuda_context: CuContext, } impl Renderer { @@ -68,18 +66,18 @@ impl Renderer { .context("Create module")?; // create raygen program - let pgdesc_raygen = ProgramGroupDesc::raygen(&module, ustr("__raygen__renderFrame")); + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; // create miss program - let pgdesc_miss = ProgramGroupDesc::miss(&module, ustr("__miss__radiance")); + let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( - Some((&module, ustr("__closesthit__radiance"))), - Some((&module, ustr("__anyhit__radiance"))), + Some((&module, "__closesthit__radiance")), + Some((&module, "__anyhit__radiance")), None, ); diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 5ebb680b..df74665e 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -7,8 +7,6 @@ type Result = std::result::Result; use std::ffi::{CStr, CString}; -use ustr::Ustr; - #[derive(Clone)] #[repr(transparent)] pub struct Module { @@ -123,19 +121,20 @@ pub struct PipelineCompileOptions { num_payload_values: i32, num_attribute_values: i32, exception_flags: ExceptionFlags, - pipeline_launch_params_variable_name: Ustr, + pipeline_launch_params_variable_name: CString, primitive_type_flags: PrimitiveTypeFlags, } impl PipelineCompileOptions { - pub fn new>(launch_params_variable_name: S) -> PipelineCompileOptions { + pub fn new(launch_params_variable_name: &str) -> PipelineCompileOptions { PipelineCompileOptions { uses_motion_blur: false, traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, num_payload_values: 0, num_attribute_values: 0, exception_flags: ExceptionFlags::NONE, - pipeline_launch_params_variable_name: launch_params_variable_name.into(), + pipeline_launch_params_variable_name: CString::new(launch_params_variable_name) + .expect("Invalid string"), primitive_type_flags: PrimitiveTypeFlags::DEFAULT, } } @@ -151,7 +150,7 @@ impl PipelineCompileOptions { exceptionFlags: self.exception_flags.bits(), pipelineLaunchParamsVariableName: self .pipeline_launch_params_variable_name - .as_char_ptr(), + .as_ptr(), usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, reserved: 0, reserved2: 0, @@ -197,8 +196,8 @@ impl PipelineCompileOptions { self } - pub fn pipeline_launch_params_variable_name(mut self, name: ustr::Ustr) -> Self { - self.pipeline_launch_params_variable_name = name; + pub fn pipeline_launch_params_variable_name(mut self, name: &str) -> Self { + self.pipeline_launch_params_variable_name = CString::new(name).expect("Invalid string"); self } } diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index 3c7a128a..c6e08dc7 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -1,14 +1,12 @@ use crate::{context::DeviceContext, error::Error, module::Module, optix_call, sys}; type Result = std::result::Result; -use ustr::Ustr; - -use std::ffi::CStr; +use std::ffi::{CStr, CString}; #[derive(Clone)] pub struct ProgramGroupModule<'m> { pub module: &'m Module, - pub entry_function_name: Ustr, + pub entry_function_name: CString, } pub enum ProgramGroupDesc<'m> { @@ -27,44 +25,44 @@ pub enum ProgramGroupDesc<'m> { } impl<'m> ProgramGroupDesc<'m> { - pub fn raygen(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + pub fn raygen(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Raygen(ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }) } - pub fn miss(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + pub fn miss(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Miss(ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }) } - pub fn exception(module: &'m Module, entry_function_name: Ustr) -> ProgramGroupDesc<'m> { + pub fn exception(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Exception(ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }) } pub fn hitgroup( - ch: Option<(&'m Module, Ustr)>, - ah: Option<(&'m Module, Ustr)>, - is: Option<(&'m Module, Ustr)>, + ch: Option<(&'m Module, &str)>, + ah: Option<(&'m Module, &str)>, + is: Option<(&'m Module, &str)>, ) -> ProgramGroupDesc<'m> { ProgramGroupDesc::Hitgroup { ch: ch.map(|(module, entry_function_name)| ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }), ah: ah.map(|(module, entry_function_name)| ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }), is: is.map(|(module, entry_function_name)| ProgramGroupModule { module, - entry_function_name, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), }), } } @@ -224,7 +222,7 @@ impl DeviceContext { pub fn program_group_raygen( &mut self, module: &Module, - entry_function_name: Ustr, + entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::raygen(module, entry_function_name); Ok(self.program_group_create_single(&desc)?.0) @@ -234,7 +232,7 @@ impl DeviceContext { pub fn program_group_miss( &mut self, module: &Module, - entry_function_name: Ustr, + entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::miss(module, entry_function_name); Ok(self.program_group_create_single(&desc)?.0) @@ -244,7 +242,7 @@ impl DeviceContext { pub fn program_group_exception( &mut self, module: &Module, - entry_function_name: Ustr, + entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::exception(module, entry_function_name); Ok(self.program_group_create_single(&desc)?.0) @@ -254,9 +252,9 @@ impl DeviceContext { /// `(module, entry_function_name)` pairs. pub fn program_group_hitgroup( &mut self, - closest_hit: Option<(&Module, Ustr)>, - any_hit: Option<(&Module, Ustr)>, - intersection: Option<(&Module, Ustr)>, + closest_hit: Option<(&Module, &str)>, + any_hit: Option<(&Module, &str)>, + intersection: Option<(&Module, &str)>, ) -> Result { let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); Ok(self.program_group_create_single(&desc)?.0) @@ -284,7 +282,7 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { raygen: sys::OptixProgramGroupSingleModule { module: module.raw, - entryFunctionName: entry_function_name.as_char_ptr(), + entryFunctionName: entry_function_name.as_ptr(), }, }, flags: 0, @@ -297,7 +295,7 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { miss: sys::OptixProgramGroupSingleModule { module: module.raw, - entryFunctionName: entry_function_name.as_char_ptr(), + entryFunctionName: entry_function_name.as_ptr(), }, }, flags: 0, @@ -310,7 +308,7 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { miss: sys::OptixProgramGroupSingleModule { module: module.raw, - entryFunctionName: entry_function_name.as_char_ptr(), + entryFunctionName: entry_function_name.as_ptr(), }, }, flags: 0, @@ -321,21 +319,21 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { let mut efn_is_ptr = std::ptr::null(); let module_ch = if let Some(pg_ch) = &ch { - efn_ch_ptr = pg_ch.entry_function_name.as_char_ptr(); + efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); pg_ch.module.raw } else { std::ptr::null_mut() }; let module_ah = if let Some(pg_ah) = &ah { - efn_ah_ptr = pg_ah.entry_function_name.as_char_ptr(); + efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); pg_ah.module.raw } else { std::ptr::null_mut() }; let module_is = if let Some(pg_is) = &is { - efn_is_ptr = pg_is.entry_function_name.as_char_ptr(); + efn_is_ptr = pg_is.entry_function_name.as_ptr(); pg_is.module.raw } else { std::ptr::null_mut() @@ -358,13 +356,13 @@ impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { } ProgramGroupDesc::Callables { dc, cc } => { let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { - (pg_dc.module.raw, pg_dc.entry_function_name.as_char_ptr()) + (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) } else { (std::ptr::null_mut(), std::ptr::null()) }; let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { - (pg_cc.module.raw, pg_cc.entry_function_name.as_char_ptr()) + (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) } else { (std::ptr::null_mut(), std::ptr::null()) }; From 2a5b787fce62884fa8f2e571b7d9e964e2f2828c Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 16:00:27 +1300 Subject: [PATCH 027/100] Manually create OptixShaderBindingTable field-by-field instead of transmute --- crates/optix/src/shader_binding_table.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 7f98319f..9ff5af61 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -46,7 +46,6 @@ where unsafe impl DeviceCopy for SbtRecord {} -#[repr(C)] pub struct ShaderBindingTable { raygen_record: CUdeviceptr, exception_record: CUdeviceptr, @@ -80,7 +79,19 @@ impl ShaderBindingTable { } pub fn build(self) -> sys::OptixShaderBindingTable { - unsafe { std::mem::transmute::(self) } + sys::OptixShaderBindingTable { + raygenRecord: self.raygen_record, + exceptionRecord: self.exception_record, + missRecordBase: self.miss_record_base, + missRecordStrideInBytes: self.miss_record_stride_in_bytes, + missRecordCount: self.miss_record_count, + hitgroupRecordBase: self.hitgroup_record_base, + hitgroupRecordStrideInBytes: self.hitgroup_record_stride_in_bytes, + hitgroupRecordCount: self.hitgroup_record_count, + callablesRecordBase: self.callables_record_base, + callablesRecordStrideInBytes: self.callables_record_stride_in_bytes, + callablesRecordCount: self.callables_record_count, + } } pub fn exception( From 22054846d3aaab05ec03139d250a6276cadf247e Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 16:01:23 +1300 Subject: [PATCH 028/100] Switch Module and Pipeline methods to their structs Instead of having them on DeviceContext --- .../examples/ex02_pipeline/src/renderer.rs | 15 ++++++++----- .../examples/ex03_window/src/renderer.rs | 15 ++++++++----- crates/optix/src/lib.rs | 2 +- crates/optix/src/module.rs | 22 ++++++++----------- crates/optix/src/pipeline.rs | 22 +++++++++---------- 5 files changed, 41 insertions(+), 35 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 813cd6e9..07bf1758 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -7,7 +7,7 @@ use cust::CudaFlags; use optix::{ context::DeviceContext, module::{ - CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, @@ -62,9 +62,13 @@ impl Renderer { let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); - let (module, _log) = ctx - .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) - .context("Create module")?; + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); @@ -96,7 +100,8 @@ impl Renderer { debug_level: CompileDebugLevel::LineInfo, }; - let (pipeline, _log) = ctx.pipeline_create( + let (pipeline, _log) = Pipeline::new( + &mut ctx, &pipeline_compile_options, pipeline_link_options, &program_groups, diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index ae6334d1..55213c8b 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -7,7 +7,7 @@ use cust::CudaFlags; use optix::{ context::DeviceContext, module::{ - CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, ModuleCompileOptions, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, @@ -61,9 +61,13 @@ impl Renderer { let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex03_window.ptx")); - let (module, _log) = ctx - .module_create_from_ptx(&module_compile_options, &pipeline_compile_options, ptx) - .context("Create module")?; + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); @@ -95,7 +99,8 @@ impl Renderer { debug_level: CompileDebugLevel::LineInfo, }; - let (pipeline, _log) = ctx.pipeline_create( + let (pipeline, _log) = Pipeline::new( + &mut ctx, &pipeline_compile_options, pipeline_link_options, &program_groups, diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index e09bb604..9a7d7563 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -75,7 +75,7 @@ pub unsafe fn launch( depth: u32, ) -> Result<()> { Ok(optix_call!(optixLaunch( - pipeline.inner, + pipeline.raw, stream.as_inner(), buf_launch_params.as_device_ptr().as_raw() as u64, std::mem::size_of::

(), diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index df74665e..1636f868 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -1,8 +1,4 @@ -use crate::{ - context::DeviceContext, - error::{Error, ToResult}, - optix_call, sys, -}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; type Result = std::result::Result; use std::ffi::{CStr, CString}; @@ -203,9 +199,9 @@ impl PipelineCompileOptions { } /// # Creating and destroying `Module`s -impl DeviceContext { - pub fn module_create_from_ptx( - &mut self, +impl Module { + pub fn new( + ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, pipeline_compile_options: &PipelineCompileOptions, ptx: &str, @@ -220,7 +216,7 @@ impl DeviceContext { let mut raw = std::ptr::null_mut(); let res = unsafe { optix_call!(optixModuleCreateFromPTX( - self.raw, + ctx.raw, &mopt as *const _, &popt, cptx.as_ptr(), @@ -243,7 +239,7 @@ impl DeviceContext { } pub fn builtin_is_module_get( - &self, + ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, pipeline_compile_options: &PipelineCompileOptions, builtin_is_module_type: PrimitiveType, @@ -258,7 +254,7 @@ impl DeviceContext { unsafe { optix_call!(optixBuiltinISModuleGet( - self.raw, + ctx.raw, module_compile_options as *const _ as *const _, pipeline_compile_options as *const _ as *const _, &is_options as *const _, @@ -275,7 +271,7 @@ impl DeviceContext { /// group. /// A Module must not be destroyed while it is /// still in use by concurrent API calls in other threads. - pub fn module_destroy(&mut self, module: Module) -> Result<()> { - unsafe { Ok(optix_call!(optixModuleDestroy(module.raw))?) } + pub fn module_destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixModuleDestroy(self.raw))?) } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 0f191623..11549c37 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -12,7 +12,7 @@ use std::ffi::CStr; #[repr(transparent)] pub struct Pipeline { - pub(crate) inner: sys::OptixPipeline, + pub(crate) raw: sys::OptixPipeline, } #[repr(C)] @@ -32,9 +32,9 @@ impl From for sys::OptixPipelineLinkOptions { } /// # Creating and destroying `Pipeline`s -impl DeviceContext { - pub fn pipeline_create( - &mut self, +impl Pipeline { + pub fn new( + ctx: &mut DeviceContext, pipeline_compile_options: &PipelineCompileOptions, link_options: PipelineLinkOptions, program_groups: &[ProgramGroup], @@ -46,18 +46,18 @@ impl DeviceContext { let mut log = [0u8; 4096]; let mut log_len = log.len(); - let mut inner: sys::OptixPipeline = std::ptr::null_mut(); + let mut raw: sys::OptixPipeline = std::ptr::null_mut(); let res = unsafe { optix_call!(optixPipelineCreate( - self.raw, + ctx.raw, &popt, &link_options, program_groups.as_ptr() as *const _, program_groups.len() as u32, log.as_mut_ptr() as *mut i8, &mut log_len, - &mut inner, + &mut raw, )) }; @@ -67,7 +67,7 @@ impl DeviceContext { .into_owned(); match res { - Ok(()) => Ok((Pipeline { inner }, log)), + Ok(()) => Ok((Pipeline { raw }, log)), Err(source) => Err(Error::PipelineCreation { source, log }), } } @@ -76,8 +76,8 @@ impl DeviceContext { /// # Safety /// Thread safety: A pipeline must not be destroyed while it is still in use /// by concurrent API calls in other threads. - pub fn pipeline_destroy(&mut self, pipeline: Pipeline) -> Result<()> { - unsafe { Ok(optix_call!(optixPipelineDestroy(pipeline.inner))?) } + pub fn destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixPipelineDestroy(self.raw))?) } } } @@ -120,7 +120,7 @@ impl Pipeline { ) -> Result<()> { unsafe { Ok(optix_call!(optixPipelineSetStackSize( - self.inner, + self.raw, direct_callable_stack_size_from_traversable, direct_callable_stack_size_from_state, continuation_stack_size, From 9eb6a0812a31c159d50b1b3f86711ad2505f3a42 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 31 Oct 2021 16:01:23 +1300 Subject: [PATCH 029/100] Switch Module, Pipeline, ProgramGroup methods to their structs Instead of having them on DeviceContext --- .../examples/ex02_pipeline/src/renderer.rs | 8 +- .../examples/ex03_window/src/renderer.rs | 10 +- crates/optix/src/program_group.rs | 234 +++++++++--------- 3 files changed, 125 insertions(+), 127 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 07bf1758..e075108d 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -11,7 +11,7 @@ use optix::{ PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, - program_group::ProgramGroupDesc, + program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; @@ -73,12 +73,12 @@ impl Renderer { // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); - let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + let (pg_raygen, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_raygen])?; // create miss program let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); - let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + let (pg_miss, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( Some((&module, "__closesthit__radiance")), @@ -87,7 +87,7 @@ impl Renderer { ); // create hitgroup programs - let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; // create pipeline let mut program_groups = Vec::new(); diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 55213c8b..f62bc795 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; -use cust::device::{Device, DeviceAttribute}; +use cust::device::Device; use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; @@ -11,7 +11,7 @@ use optix::{ PipelineCompileOptions, TraversableGraphFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, - program_group::ProgramGroupDesc, + program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; @@ -72,12 +72,12 @@ impl Renderer { // create raygen program let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); - let (pg_raygen, _log) = ctx.program_group_create(&[pgdesc_raygen])?; + let (pg_raygen, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_raygen])?; // create miss program let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); - let (pg_miss, _log) = ctx.program_group_create(&[pgdesc_miss])?; + let (pg_miss, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_miss])?; let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( Some((&module, "__closesthit__radiance")), @@ -86,7 +86,7 @@ impl Renderer { ); // create hitgroup programs - let (pg_hitgroup, _log) = ctx.program_group_create(&[pgdesc_hitgroup])?; + let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; // create pipeline let mut program_groups = Vec::new(); diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index c6e08dc7..da6ad792 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -127,11 +127,11 @@ impl PartialEq for ProgramGroup { } /// # Creating and destroying `ProgramGroup`s -impl DeviceContext { +impl ProgramGroup { /// Create a [ProgramGroup] for each of the [ProgramGroupDesc] objects in /// `desc`. - pub fn program_group_create( - &mut self, + pub fn new( + ctx: &mut DeviceContext, desc: &[ProgramGroupDesc], ) -> Result<(Vec, String)> { cfg_if::cfg_if! { @@ -151,7 +151,7 @@ impl DeviceContext { let res = unsafe { optix_call!(optixProgramGroupCreate( - self.raw, + ctx.raw, pg_desc.as_ptr(), pg_desc.len() as u32, &pg_options, @@ -176,8 +176,8 @@ impl DeviceContext { } /// Create a single [ProgramGroup] specified by `desc`. - pub fn program_group_create_single( - &mut self, + pub fn new_single( + ctx: &mut DeviceContext, desc: &ProgramGroupDesc, ) -> Result<(ProgramGroup, String)> { cfg_if::cfg_if! { @@ -197,7 +197,7 @@ impl DeviceContext { let res = unsafe { optix_call!(optixProgramGroupCreate( - self.raw, + ctx.raw, &pg_desc, 1, &pg_options, @@ -219,45 +219,45 @@ impl DeviceContext { } /// Create a raygen [ProgramGroup] from `entry_function_name` in `module`. - pub fn program_group_raygen( - &mut self, + pub fn raygen( + ctx: &mut DeviceContext, module: &Module, entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::raygen(module, entry_function_name); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Create a miss [ProgramGroup] from `entry_function_name` in `module`. - pub fn program_group_miss( - &mut self, + pub fn miss( + ctx: &mut DeviceContext, module: &Module, entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::miss(module, entry_function_name); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Create an exception [ProgramGroup] from `entry_function_name` in `module`. - pub fn program_group_exception( - &mut self, + pub fn exception( + ctx: &mut DeviceContext, module: &Module, entry_function_name: &str, ) -> Result { let desc = ProgramGroupDesc::exception(module, entry_function_name); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Create a hitgroup [ProgramGroup] from any combination of /// `(module, entry_function_name)` pairs. - pub fn program_group_hitgroup( - &mut self, + pub fn hitgroup( + ctx: &mut DeviceContext, closest_hit: Option<(&Module, &str)>, any_hit: Option<(&Module, &str)>, intersection: Option<(&Module, &str)>, ) -> Result { let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); - Ok(self.program_group_create_single(&desc)?.0) + Ok(ProgramGroup::new_single(ctx, &desc)?.0) } /// Destroy `program_group` @@ -265,120 +265,118 @@ impl DeviceContext { /// # Safety /// Thread safety: A program group must not be destroyed while it is still /// in use by concurrent API calls in other threads. - pub fn program_group_destroy(&mut self, program_group: ProgramGroup) -> Result<()> { - unsafe { Ok(optix_call!(optixProgramGroupDestroy(program_group.raw))?) } + pub fn destroy(&mut self) -> Result<()> { + unsafe { Ok(optix_call!(optixProgramGroupDestroy(self.raw))?) } } } impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { fn from(desc: &ProgramGroupDesc<'m>) -> sys::OptixProgramGroupDesc { - unsafe { - match &desc { - ProgramGroupDesc::Raygen(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - raygen: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, + match &desc { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + raygen: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), }, - flags: 0, }, - ProgramGroupDesc::Miss(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - miss: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, + flags: 0, + }, + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), }, - flags: 0, }, - ProgramGroupDesc::Exception(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + flags: 0, + }, + ProgramGroupDesc::Hitgroup { ch, ah, is } => { + let mut efn_ch_ptr = std::ptr::null(); + let mut efn_ah_ptr = std::ptr::null(); + let mut efn_is_ptr = std::ptr::null(); + + let module_ch = if let Some(pg_ch) = &ch { + efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); + pg_ch.module.raw + } else { + std::ptr::null_mut() + }; + + let module_ah = if let Some(pg_ah) = &ah { + efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); + pg_ah.module.raw + } else { + std::ptr::null_mut() + }; + + let module_is = if let Some(pg_is) = &is { + efn_is_ptr = pg_is.entry_function_name.as_ptr(); + pg_is.module.raw + } else { + std::ptr::null_mut() + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - miss: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), + hitgroup: sys::OptixProgramGroupHitgroup { + moduleCH: module_ch, + entryFunctionNameCH: efn_ch_ptr, + moduleAH: module_ah, + entryFunctionNameAH: efn_ah_ptr, + moduleIS: module_is, + entryFunctionNameIS: efn_is_ptr, }, }, flags: 0, - }, - ProgramGroupDesc::Hitgroup { ch, ah, is } => { - let mut efn_ch_ptr = std::ptr::null(); - let mut efn_ah_ptr = std::ptr::null(); - let mut efn_is_ptr = std::ptr::null(); - - let module_ch = if let Some(pg_ch) = &ch { - efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); - pg_ch.module.raw - } else { - std::ptr::null_mut() - }; - - let module_ah = if let Some(pg_ah) = &ah { - efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); - pg_ah.module.raw - } else { - std::ptr::null_mut() - }; - - let module_is = if let Some(pg_is) = &is { - efn_is_ptr = pg_is.entry_function_name.as_ptr(); - pg_is.module.raw - } else { - std::ptr::null_mut() - }; - - sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - hitgroup: sys::OptixProgramGroupHitgroup { - moduleCH: module_ch, - entryFunctionNameCH: efn_ch_ptr, - moduleAH: module_ah, - entryFunctionNameAH: efn_ah_ptr, - moduleIS: module_is, - entryFunctionNameIS: efn_is_ptr, - }, - }, - flags: 0, - } } - ProgramGroupDesc::Callables { dc, cc } => { - let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { - (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) - } else { - (std::ptr::null_mut(), std::ptr::null()) - }; - - let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { - (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) - } else { - (std::ptr::null_mut(), std::ptr::null()) - }; - - sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - callables: sys::OptixProgramGroupCallables { - moduleDC: module_dc, - entryFunctionNameDC: efn_dc, - moduleCC: module_cc, - entryFunctionNameCC: efn_cc, - }, + } + ProgramGroupDesc::Callables { dc, cc } => { + let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { + (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { + (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + callables: sys::OptixProgramGroupCallables { + moduleDC: module_dc, + entryFunctionNameDC: efn_dc, + moduleCC: module_cc, + entryFunctionNameCC: efn_cc, }, - flags: 0, - } + }, + flags: 0, } } } From 1b8059c488bb678b6f86ecc8cdb8f47a7b7c5359 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Sun, 31 Oct 2021 12:36:33 -0400 Subject: [PATCH 030/100] Refactor: remove dead imports --- crates/optix/src/shader_binding_table.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 9ff5af61..3205620b 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -1,10 +1,4 @@ -use crate::{ - context::DeviceContext, - error::{Error, ToResult}, - optix_call, - program_group::ProgramGroup, - sys, -}; +use crate::{error::Error, optix_call, program_group::ProgramGroup, sys}; use cust::memory::{DSlice, DeviceCopy}; use cust_raw::CUdeviceptr; From 28c4b8331a3c2e67babfc8e04e51dcdf06a93f38 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:23:30 +1300 Subject: [PATCH 031/100] derive DeviceCopy --- crates/optix/examples/ex02_pipeline/src/renderer.rs | 10 ++++------ crates/optix/examples/ex03_window/src/renderer.rs | 11 ++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index e075108d..c01d4ed5 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -4,6 +4,7 @@ use cust::device::{Device, DeviceAttribute}; use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; +use cust::DeviceCopy; use optix::{ context::DeviceContext, module::{ @@ -205,30 +206,27 @@ impl Renderer { } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct Point2i { pub x: i32, pub y: i32, } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct LaunchParams { pub frame_id: i32, pub color_buffer: DevicePointer, pub fb_size: Point2i, } -unsafe impl DeviceCopy for LaunchParams {} - type RaygenRecord = SbtRecord; type MissRecord = SbtRecord; -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, DeviceCopy)] struct HitgroupSbtData { object_id: u32, } -unsafe impl DeviceCopy for HitgroupSbtData {} type HitgroupRecord = SbtRecord; fn init_optix() -> Result<(), Box> { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index f62bc795..b5e91b6f 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -3,7 +3,7 @@ use cust::context::{Context as CuContext, ContextFlags}; use cust::device::Device; use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; -use cust::CudaFlags; +use cust::{CudaFlags, DeviceCopy}; use optix::{ context::DeviceContext, module::{ @@ -205,30 +205,27 @@ impl Renderer { } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct Point2i { pub x: i32, pub y: i32, } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, DeviceCopy)] struct LaunchParams { pub color_buffer: DevicePointer, pub fb_size: Point2i, pub frame_id: i32, } -unsafe impl DeviceCopy for LaunchParams {} - type RaygenRecord = SbtRecord; type MissRecord = SbtRecord; -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Default, DeviceCopy)] struct HitgroupSbtData { object_id: u32, } -unsafe impl DeviceCopy for HitgroupSbtData {} type HitgroupRecord = SbtRecord; fn init_optix() -> Result<(), Box> { From ba9f05b1116b6b2a7d045f7addfcc9ad98d0e60a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:23:39 +1300 Subject: [PATCH 032/100] typo --- crates/optix/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 9a7d7563..3eb209de 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -60,7 +60,7 @@ macro_rules! optix_call { /// /// # Safety /// You must ensure that: -/// - Any [ProgramGroup]s reference by the [Pipeline] are still alive +/// - Any [ProgramGroup]s referenced by the [Pipeline] are still alive /// - Any [DevicePtr]s contained in `buf_launch_params` point to valid, /// correctly aligned memory /// - Any [SbtRecord]s and associated data referenced by the From 9be649d35a7599d86166856709ab9a6e29ce3d55 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:23:56 +1300 Subject: [PATCH 033/100] Better error message --- crates/optix/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 1636f868..af726aac 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -130,7 +130,7 @@ impl PipelineCompileOptions { num_attribute_values: 0, exception_flags: ExceptionFlags::NONE, pipeline_launch_params_variable_name: CString::new(launch_params_variable_name) - .expect("Invalid string"), + .expect("launch_params_variable_name contains a nul byte"), primitive_type_flags: PrimitiveTypeFlags::DEFAULT, } } From 35b935b3b5ac32405b6288b6207ecfd653053ff7 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:24:25 +1300 Subject: [PATCH 034/100] Move destroy to Drop impl --- crates/optix/src/module.rs | 12 ++++-------- crates/optix/src/pipeline.rs | 10 ++++------ crates/optix/src/program_group.rs | 18 +++++++++--------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index af726aac..2c279174 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -264,14 +264,10 @@ impl Module { .map_err(|e| Error::from(e)) } } +} - /// Destroy a module created with [DeviceContext::module_create_from_ptx()] - /// # Safety - /// Modules must not be destroyed while they are still used by any program - /// group. - /// A Module must not be destroyed while it is - /// still in use by concurrent API calls in other threads. - pub fn module_destroy(&mut self) -> Result<()> { - unsafe { Ok(optix_call!(optixModuleDestroy(self.raw))?) } +impl Drop for Module { + fn drop(&mut self) { + unsafe { optix_call!(optixModuleDestroy(self.raw)).expect("optixModuleDestroy failed") } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 11549c37..0b14dcbf 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -71,13 +71,11 @@ impl Pipeline { Err(source) => Err(Error::PipelineCreation { source, log }), } } +} - /// Destroys the pipeline - /// # Safety - /// Thread safety: A pipeline must not be destroyed while it is still in use - /// by concurrent API calls in other threads. - pub fn destroy(&mut self) -> Result<()> { - unsafe { Ok(optix_call!(optixPipelineDestroy(self.raw))?) } +impl Drop for Pipeline { + fn drop(&mut self) { + unsafe { optix_call!(optixPipelineDestroy(self.raw)).expect("optixPipelineDestroy failed") } } } diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index da6ad792..54bb6a73 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -68,8 +68,6 @@ impl<'m> ProgramGroupDesc<'m> { } } -#[repr(transparent)] -#[derive(Clone)] /// Modules can contain more than one program. The program in the module is /// designated by its entry function name as part of the [ProgramGroupDesc] /// struct passed to [DeviceContext::program_group_create()] and @@ -98,6 +96,8 @@ impl<'m> ProgramGroupDesc<'m> { /// # Safety /// The lifetime of a module must extend to the lifetime of any /// OptixProgramGroup that references that module. +#[repr(transparent)] +#[derive(Clone)] pub struct ProgramGroup { pub(crate) raw: sys::OptixProgramGroup, } @@ -259,14 +259,14 @@ impl ProgramGroup { let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); Ok(ProgramGroup::new_single(ctx, &desc)?.0) } +} - /// Destroy `program_group` - /// - /// # Safety - /// Thread safety: A program group must not be destroyed while it is still - /// in use by concurrent API calls in other threads. - pub fn destroy(&mut self) -> Result<()> { - unsafe { Ok(optix_call!(optixProgramGroupDestroy(self.raw))?) } +impl Drop for ProgramGroup { + fn drop(&mut self) { + unsafe { + optix_call!(optixProgramGroupDestroy(self.raw)) + .expect("optixProgramGroupDestroy failed") + } } } From ecff765c7acdac54ffac1713d3a0b70611567f1b Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:24:31 +1300 Subject: [PATCH 035/100] typo --- crates/optix/src/shader_binding_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 3205620b..64ab9107 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -93,7 +93,7 @@ impl ShaderBindingTable { buf_exception_record: &mut DSlice>, ) -> Self { if buf_exception_record.len() != 1 { - panic!("SBT not psased single exception record",); + panic!("SBT not passed single exception record",); } self.exception_record = buf_exception_record.as_device_ptr().as_raw() as u64; self From 71f8f3762a6626e1e369b739bda53bfd95333999 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:24:53 +1300 Subject: [PATCH 036/100] rename OptixContext to DeviceContext --- examples/optix/denoiser/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/optix/denoiser/src/main.rs b/examples/optix/denoiser/src/main.rs index 59c860f8..23b75c9b 100644 --- a/examples/optix/denoiser/src/main.rs +++ b/examples/optix/denoiser/src/main.rs @@ -3,7 +3,7 @@ use cust::prelude::{Stream, StreamFlags}; use cust::util::SliceExt; use cust::vek::{Clamp, Vec3}; use image::io::Reader; -use optix::context::OptixContext; +use optix::context::DeviceContext; use optix::denoiser::{Denoiser, DenoiserModelKind, DenoiserParams, Image, ImageFormat}; use std::error::Error; use std::path::PathBuf; @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { // set up CUDA and OptiX then make the needed structs/contexts. let cuda_ctx = cust::quick_init()?; optix::init()?; - let optix_ctx = OptixContext::new(&cuda_ctx)?; + let optix_ctx = DeviceContext::new(&cuda_ctx)?; let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?; From 73cd6013e7ed8a3beb7d22755702ef2f850e6c70 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:38:25 +1300 Subject: [PATCH 037/100] Make launch params variable name optional --- .../examples/ex02_pipeline/src/renderer.rs | 3 +- .../examples/ex03_window/src/renderer.rs | 3 +- crates/optix/src/module.rs | 29 ++++++++++++------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index c01d4ed5..fb4ef7cd 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -54,7 +54,8 @@ impl Renderer { debug_level: CompileDebugLevel::None, }; - let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + let pipeline_compile_options = PipelineCompileOptions::new() + .pipeline_launch_params_variable_name("PARAMS") .uses_motion_blur(false) .num_attribute_values(2) .num_payload_values(2) diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index b5e91b6f..57247b96 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -52,7 +52,8 @@ impl Renderer { debug_level: CompileDebugLevel::None, }; - let pipeline_compile_options = PipelineCompileOptions::new("PARAMS") + let pipeline_compile_options = PipelineCompileOptions::new() + .pipeline_launch_params_variable_name("PARAMS") .uses_motion_blur(false) .num_attribute_values(2) .num_payload_values(2) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 2c279174..3e39dddf 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -117,20 +117,19 @@ pub struct PipelineCompileOptions { num_payload_values: i32, num_attribute_values: i32, exception_flags: ExceptionFlags, - pipeline_launch_params_variable_name: CString, + pipeline_launch_params_variable_name: Option, primitive_type_flags: PrimitiveTypeFlags, } impl PipelineCompileOptions { - pub fn new(launch_params_variable_name: &str) -> PipelineCompileOptions { + pub fn new() -> PipelineCompileOptions { PipelineCompileOptions { uses_motion_blur: false, traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, num_payload_values: 0, num_attribute_values: 0, exception_flags: ExceptionFlags::NONE, - pipeline_launch_params_variable_name: CString::new(launch_params_variable_name) - .expect("launch_params_variable_name contains a nul byte"), + pipeline_launch_params_variable_name: None, primitive_type_flags: PrimitiveTypeFlags::DEFAULT, } } @@ -144,9 +143,12 @@ impl PipelineCompileOptions { numPayloadValues: self.num_payload_values, numAttributeValues: self.num_attribute_values, exceptionFlags: self.exception_flags.bits(), - pipelineLaunchParamsVariableName: self - .pipeline_launch_params_variable_name - .as_ptr(), + pipelineLaunchParamsVariableName: if let Some(ref name) = self + .pipeline_launch_params_variable_name { + name.as_ptr() + } else { + std::ptr::null() + }, usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, reserved: 0, reserved2: 0, @@ -158,9 +160,12 @@ impl PipelineCompileOptions { numPayloadValues: self.num_payload_values, numAttributeValues: self.num_attribute_values, exceptionFlags: self.exception_flags.bits(), - pipelineLaunchParamsVariableName: self - .pipeline_launch_params_variable_name - .as_char_ptr(), + pipelineLaunchParamsVariableName: if let Some(ref name) = self + .pipeline_launch_params_variable_name { + name.as_ptr() + } else { + std::ptr::null() + }, usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, } } @@ -193,7 +198,9 @@ impl PipelineCompileOptions { } pub fn pipeline_launch_params_variable_name(mut self, name: &str) -> Self { - self.pipeline_launch_params_variable_name = CString::new(name).expect("Invalid string"); + self.pipeline_launch_params_variable_name = Some( + CString::new(name).expect("pipeline launch params variable name contains nul bytes"), + ); self } } From e396effee3e706e98f8586e4185666661c7d8069 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:38:53 +1300 Subject: [PATCH 038/100] Remove Clone from Module and ProgramGroup --- .../examples/ex02_pipeline/src/renderer.rs | 40 +++++++++---------- .../examples/ex03_window/src/renderer.rs | 40 +++++++++---------- crates/optix/src/module.rs | 1 - crates/optix/src/program_group.rs | 1 - 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index fb4ef7cd..8bfa9b2c 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -91,26 +91,6 @@ impl Renderer { // create hitgroup programs let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; - // create pipeline - let mut program_groups = Vec::new(); - program_groups.extend(pg_raygen.iter().cloned()); - program_groups.extend(pg_miss.iter().cloned()); - program_groups.extend(pg_hitgroup.iter().cloned()); - - let pipeline_link_options = PipelineLinkOptions { - max_trace_depth: 2, - debug_level: CompileDebugLevel::LineInfo, - }; - - let (pipeline, _log) = Pipeline::new( - &mut ctx, - &pipeline_compile_options, - pipeline_link_options, - &program_groups, - )?; - - pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - // create SBT let rec_raygen: Vec<_> = pg_raygen .iter() @@ -144,6 +124,26 @@ impl Renderer { .hitgroup(&mut buf_hitgroup) .build(); + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.into_iter()); + program_groups.extend(pg_miss.into_iter()); + program_groups.extend(pg_hitgroup.into_iter()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = Pipeline::new( + &mut ctx, + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + let mut color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; let launch_params = LaunchParams { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 57247b96..646ff181 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -89,26 +89,6 @@ impl Renderer { // create hitgroup programs let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; - // create pipeline - let mut program_groups = Vec::new(); - program_groups.extend(pg_raygen.iter().cloned()); - program_groups.extend(pg_miss.iter().cloned()); - program_groups.extend(pg_hitgroup.iter().cloned()); - - let pipeline_link_options = PipelineLinkOptions { - max_trace_depth: 2, - debug_level: CompileDebugLevel::LineInfo, - }; - - let (pipeline, _log) = Pipeline::new( - &mut ctx, - &pipeline_compile_options, - pipeline_link_options, - &program_groups, - )?; - - pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - // create SBT let rec_raygen: Vec<_> = pg_raygen .iter() @@ -142,6 +122,26 @@ impl Renderer { .hitgroup(&mut buf_hitgroup) .build(); + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.into_iter()); + program_groups.extend(pg_miss.into_iter()); + program_groups.extend(pg_hitgroup.into_iter()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = Pipeline::new( + &mut ctx, + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; let launch_params = LaunchParams { diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 3e39dddf..1d46aee1 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -3,7 +3,6 @@ type Result = std::result::Result; use std::ffi::{CStr, CString}; -#[derive(Clone)] #[repr(transparent)] pub struct Module { pub(crate) raw: sys::OptixModule, diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index 54bb6a73..a8aa1c88 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -97,7 +97,6 @@ impl<'m> ProgramGroupDesc<'m> { /// The lifetime of a module must extend to the lifetime of any /// OptixProgramGroup that references that module. #[repr(transparent)] -#[derive(Clone)] pub struct ProgramGroup { pub(crate) raw: sys::OptixProgramGroup, } From aee77abfb259feb98cf36c043cf169ce4cea67d1 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 2 Nov 2021 08:52:01 +1300 Subject: [PATCH 039/100] Make log callback safe User catch_unwind to guard against panic in C. Remove note about lifetime of closure since it's 'static anyway. Have set_log_callback return a Result instead of panicking on error. --- .../examples/ex02_pipeline/src/renderer.rs | 2 +- .../examples/ex03_window/src/renderer.rs | 2 +- crates/optix/src/context.rs | 38 +++++++++---------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 8bfa9b2c..42c4ec97 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -45,7 +45,7 @@ impl Renderer { let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 646ff181..b4c1a529 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -43,7 +43,7 @@ impl Renderer { let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context)?; - ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4); + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module let module_compile_options = ModuleCompileOptions { diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 8f36ebf2..19b5cd1e 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -120,23 +120,18 @@ impl DeviceContext { /// the user or may perform slower than expected. /// * 4 - print: Status or progress messages. /// Higher levels might occur. - /// - /// # Safety - /// Note that the callback must live longer than the DeviceContext which - /// it's set for. This is because OptiX will fire messages when the - /// underlying OptixDeviceContext is destroyed. In order to do ensure - /// this we leak the closure `cb`. So don't go setting a new closure - /// every frame. - pub fn set_log_callback(&mut self, cb: F, level: u32) + pub fn set_log_callback(&mut self, cb: F, level: u32) -> Result<()> where F: FnMut(u32, &str, &str) + 'static, { let (closure, trampoline) = unsafe { unpack_closure(cb) }; - let res = unsafe { - sys::optixDeviceContextSetLogCallback(self.raw, Some(trampoline), closure, level) - }; - if res != sys::OptixResult::OPTIX_SUCCESS { - panic!("Failed to set log callback"); + unsafe { + Ok(optix_call!(optixDeviceContextSetLogCallback( + self.raw, + Some(trampoline), + closure, + level + ))?) } } } @@ -153,10 +148,6 @@ type LogCallback = extern "C" fn(c_uint, *const c_char, *const c_char, *mut c_vo /// /// Calling the trampoline function with anything except the `void*` pointer /// will result in *Undefined Behaviour*. -/// -/// The closure should guarantee that it never panics, seeing as panicking -/// across the FFI barrier is *Undefined Behaviour*. You may find -/// `std::panic::catch_unwind()` useful. unsafe fn unpack_closure(closure: F) -> (*mut c_void, LogCallback) where F: FnMut(u32, &str, &str), @@ -169,10 +160,15 @@ where ) where F: FnMut(u32, &str, &str), { - let tag = unsafe { CStr::from_ptr(tag).to_string_lossy().into_owned() }; - let msg = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; - let closure: &mut F = unsafe { &mut *(data as *mut F) }; - (*closure)(level, &tag, &msg); + if let Err(e) = std::panic::catch_unwind(|| { + let tag = unsafe { CStr::from_ptr(tag).to_string_lossy().into_owned() }; + let msg = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; + let closure: &mut F = unsafe { &mut *(data as *mut F) }; + + (*closure)(level, &tag, &msg); + }) { + eprintln!("Caught a panic calling log closure: {:?}", e); + } } let cb = Box::new(closure); From d1d06f2255a47b019fc0ab0cce4dda08dd21e2ea Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 09:13:06 +1300 Subject: [PATCH 040/100] add wip glam support --- crates/cust/Cargo.toml | 3 ++- crates/cust/src/memory/mod.rs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index cb7c00a7..b410782d 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -10,9 +10,10 @@ cust_raw = { path = "../cust_raw", version = "0.11.2"} bitflags = "1.2" cust_derive = { path = "../cust_derive", version = "0.1" } vek = { version = "0.15.1", optional = true } +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } [features] -default-features = ["vek"] +default-features = ["vek", "glam"] [build-dependencies] find_cuda_helper = { path = "../find_cuda_helper", version = "0.1" } diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 21845a18..a7865619 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -297,6 +297,14 @@ macro_rules! impl_device_copy_vek { } } +macro_rules! impl_device_copy_glam { + ($($strukt:ty),* $(,)?) => { + $( + unsafe impl DeviceCopy for $strukt {} + )* + } +} + #[cfg(feature = "vek")] use vek::*; @@ -307,3 +315,8 @@ impl_device_copy_vek! { CubicBezier2, CubicBezier3, Quaternion, } + +#[cfg(feature = "glam")] +impl_device_copy_glam! { + glam::Vec2, glam::Vec3, glam::Vec4, glam::IVec2, glam::IVec3, glam::IVec4, +} From 9f7788930703fc8519bfbf0e61a5697620bbaca4 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 09:13:45 +1300 Subject: [PATCH 041/100] dont panic in drop --- crates/optix/src/module.rs | 4 +++- crates/optix/src/pipeline.rs | 4 +++- crates/optix/src/program_group.rs | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 1d46aee1..3716c916 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -274,6 +274,8 @@ impl Module { impl Drop for Module { fn drop(&mut self) { - unsafe { optix_call!(optixModuleDestroy(self.raw)).expect("optixModuleDestroy failed") } + unsafe { + sys::optixModuleDestroy(self.raw); + } } } diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 0b14dcbf..848fca9b 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -75,7 +75,9 @@ impl Pipeline { impl Drop for Pipeline { fn drop(&mut self) { - unsafe { optix_call!(optixPipelineDestroy(self.raw)).expect("optixPipelineDestroy failed") } + unsafe { + sys::optixPipelineDestroy(self.raw); + } } } diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs index a8aa1c88..a29600f6 100644 --- a/crates/optix/src/program_group.rs +++ b/crates/optix/src/program_group.rs @@ -263,8 +263,7 @@ impl ProgramGroup { impl Drop for ProgramGroup { fn drop(&mut self) { unsafe { - optix_call!(optixProgramGroupDestroy(self.raw)) - .expect("optixProgramGroupDestroy failed") + sys::optixProgramGroupDestroy(self.raw); } } } From c307b5308e69690f9e4f8460dcbaa1733c32469b Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 09:14:57 +1300 Subject: [PATCH 042/100] wip accel support --- crates/optix/examples/ex04_mesh/Cargo.toml | 18 + crates/optix/examples/ex04_mesh/build.rs | 49 ++ .../optix/examples/ex04_mesh/src/ex04_mesh.cu | 145 +++++ .../optix/examples/ex04_mesh/src/gl_util.rs | 582 ++++++++++++++++++ crates/optix/examples/ex04_mesh/src/main.rs | 86 +++ .../optix/examples/ex04_mesh/src/renderer.rs | 350 +++++++++++ crates/optix/examples/ex04_mesh/src/vector.rs | 301 +++++++++ crates/optix/src/acceleration.rs | 284 +++++++++ crates/optix/src/instance_array.rs | 117 ++++ crates/optix/src/lib.rs | 3 + crates/optix/src/triangle_array.rs | 150 +++++ 11 files changed, 2085 insertions(+) create mode 100644 crates/optix/examples/ex04_mesh/Cargo.toml create mode 100644 crates/optix/examples/ex04_mesh/build.rs create mode 100644 crates/optix/examples/ex04_mesh/src/ex04_mesh.cu create mode 100644 crates/optix/examples/ex04_mesh/src/gl_util.rs create mode 100644 crates/optix/examples/ex04_mesh/src/main.rs create mode 100644 crates/optix/examples/ex04_mesh/src/renderer.rs create mode 100644 crates/optix/examples/ex04_mesh/src/vector.rs create mode 100644 crates/optix/src/acceleration.rs create mode 100644 crates/optix/src/instance_array.rs create mode 100644 crates/optix/src/triangle_array.rs diff --git a/crates/optix/examples/ex04_mesh/Cargo.toml b/crates/optix/examples/ex04_mesh/Cargo.toml new file mode 100644 index 00000000..ab1d0cf2 --- /dev/null +++ b/crates/optix/examples/ex04_mesh/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ex04_mesh" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +optix = {path = "../../"} +cust = {path = "../../../cust", features=["glam"]} +anyhow = "1.0.44" +glfw = "0.42.0" +gl = "0.14.0" +num-traits = "0.2.14" +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"] } + +[build-dependencies] +find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex04_mesh/build.rs b/crates/optix/examples/ex04_mesh/build.rs new file mode 100644 index 00000000..c0e8247b --- /dev/null +++ b/crates/optix/examples/ex04_mesh/build.rs @@ -0,0 +1,49 @@ +use find_cuda_helper::{find_cuda_root, find_optix_root}; + +fn main() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let mut optix_include = find_optix_root().expect( + "Unable to find the OptiX SDK, make sure you installed it and + that OPTIX_ROOT or OPTIX_ROOT_DIR are set", + ); + optix_include = optix_include.join("include"); + + let mut cuda_include = find_cuda_root().expect( + "Unable to find the CUDA Toolkit, make sure you installed it and + that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", + ); + + cuda_include = cuda_include.join("include"); + + let args = vec![ + format!("-I{}", optix_include.display()), + format!("-I{}/../common/gdt", manifest_dir), + ]; + + compile_to_ptx("src/ex04_mesh.cu", &args); +} + +fn compile_to_ptx(cu_path: &str, args: &[String]) { + println!("cargo:rerun-if-changed={}", cu_path); + + let full_path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(cu_path); + + let mut ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(cu_path); + ptx_path.set_extension("ptx"); + std::fs::create_dir_all(ptx_path.parent().unwrap()).unwrap(); + + let output = std::process::Command::new("nvcc") + .arg("-ptx") + .arg(&full_path) + .arg("-o") + .arg(&ptx_path) + .args(args) + .output() + .expect("failed to fun nvcc"); + + if !output.status.success() { + panic!("{}", unsafe { String::from_utf8_unchecked(output.stderr) }); + } +} diff --git a/crates/optix/examples/ex04_mesh/src/ex04_mesh.cu b/crates/optix/examples/ex04_mesh/src/ex04_mesh.cu new file mode 100644 index 00000000..43573084 --- /dev/null +++ b/crates/optix/examples/ex04_mesh/src/ex04_mesh.cu @@ -0,0 +1,145 @@ +// ======================================================================== // +// Copyright 2018-2019 Ingo Wald // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT 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 + +namespace osc { + +using namespace gdt; +struct LaunchParams { + struct { + float4* colorBuffer; + vec2i size; + } frame; + + struct { + vec3f position; + vec3f direction; + vec3f horizontal; + vec3f vertical; + } camera; + + OptixTraversableHandle traversable; +}; + +/*! launch parameters in constant memory, filled in by optix upon + optixLaunch (this gets filled in from the buffer we pass to + optixLaunch) */ +extern "C" __constant__ LaunchParams PARAMS; +// for this simple example, we have a single ray type +enum { SURFACE_RAY_TYPE = 0, RAY_TYPE_COUNT }; + +static __forceinline__ __device__ void* unpackPointer(uint32_t i0, + uint32_t i1) { + const uint64_t uptr = static_cast(i0) << 32 | i1; + void* ptr = reinterpret_cast(uptr); + return ptr; +} + +static __forceinline__ __device__ void packPointer(void* ptr, uint32_t& i0, + uint32_t& i1) { + const uint64_t uptr = reinterpret_cast(ptr); + i0 = uptr >> 32; + i1 = uptr & 0x00000000ffffffff; +} + +template static __forceinline__ __device__ T* getPRD() { + const uint32_t u0 = optixGetPayload_0(); + const uint32_t u1 = optixGetPayload_1(); + return reinterpret_cast(unpackPointer(u0, u1)); +} + +//------------------------------------------------------------------------------ +// closest hit and anyhit programs for radiance-type rays. +// +// Note eventually we will have to create one pair of those for each +// ray type and each geometry type we want to render; but this +// simple example doesn't use any actual geometries yet, so we only +// create a single, dummy, set of them (we do have to have at least +// one group of them to set up the SBT) +//------------------------------------------------------------------------------ + +extern "C" __global__ void __closesthit__radiance() { + const int primID = optixGetPrimitiveIndex(); + vec3f& prd = *(vec3f*)getPRD(); + prd = gdt::randomColor(primID); +} + +extern "C" __global__ void +__anyhit__radiance() { /*! for this simple example, this will remain empty */ +} + +//------------------------------------------------------------------------------ +// miss program that gets called for any ray that did not have a +// valid intersection +// +// as with the anyhit/closest hit programs, in this example we only +// need to have _some_ dummy function to set up a valid SBT +// ------------------------------------------------------------------------------ + +extern "C" __global__ void __miss__radiance() { + vec3f& prd = *(vec3f*)getPRD(); + // set to constant white as background color + prd = vec3f(1.f); +} + +//------------------------------------------------------------------------------ +// ray gen program - the actual rendering happens in here +//------------------------------------------------------------------------------ +extern "C" __global__ void __raygen__renderFrame() { + // compute a test pattern based on pixel ID + const int ix = optixGetLaunchIndex().x; + const int iy = optixGetLaunchIndex().y; + + const auto& camera = PARAMS.camera; + + // our per-ray data for this example. what we initialize it to + // won't matter, since this value will be overwritten by either + // the miss or hit program, anyway + vec3f pixelColorPRD = vec3f(0.f); + + // the values we store the PRD pointer in: + uint32_t u0, u1; + packPointer(&pixelColorPRD, u0, u1); + + // normalized screen plane position, in [0,1]^2 + const vec2f screen(vec2f(ix + .5f, iy + .5f) / vec2f(PARAMS.frame.size)); + + // generate ray direction + vec3f rayDir = + normalize(camera.direction + (screen.x - 0.5f) * camera.horizontal + + (screen.y - 0.5f) * camera.vertical); + + optixTrace(PARAMS.traversable, camera.position, rayDir, + 0.f, // tmin + 1e20f, // tmax + 0.0f, // rayTime + OptixVisibilityMask(255), + OPTIX_RAY_FLAG_DISABLE_ANYHIT, // OPTIX_RAY_FLAG_NONE, + SURFACE_RAY_TYPE, // SBT offset + RAY_TYPE_COUNT, // SBT stride + SURFACE_RAY_TYPE, // missSBTIndex + u0, u1); + + // and write to frame buffer ... + const uint32_t fbIndex = ix + iy * PARAMS.frame.size.x; + PARAMS.frame.colorBuffer[fbIndex] = + make_float4(pixelColorPRD.x, pixelColorPRD.y, pixelColorPRD.z, 1.0f); +} + +} // namespace osc diff --git a/crates/optix/examples/ex04_mesh/src/gl_util.rs b/crates/optix/examples/ex04_mesh/src/gl_util.rs new file mode 100644 index 00000000..c8b39caa --- /dev/null +++ b/crates/optix/examples/ex04_mesh/src/gl_util.rs @@ -0,0 +1,582 @@ +use gl; +use gl::types::{GLchar, GLenum, GLint, GLsizeiptr, GLuint, GLvoid}; +use std::ffi::{CStr, CString}; + +use crate::vector::*; + +pub struct Shader { + id: GLuint, +} + +impl Shader { + pub fn from_source( + source: &CStr, + shader_type: GLenum, + ) -> Result { + let id = unsafe { gl::CreateShader(shader_type) }; + + unsafe { + gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null()); + gl::CompileShader(id); + } + + let mut success: GLint = 1; + unsafe { + gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut success); + } + + if success == 0 { + let mut len: GLint = 0; + unsafe { + gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + let error = create_whitespace_cstring(len as usize); + unsafe { + gl::GetShaderInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut GLchar, + ); + } + Err(error.to_string_lossy().into_owned()) + } else { + Ok(Shader { id }) + } + } + + pub fn vertex_from_source(source: &CStr) -> Result { + Shader::from_source(source, gl::VERTEX_SHADER) + } + + pub fn fragment_from_source(source: &CStr) -> Result { + Shader::from_source(source, gl::FRAGMENT_SHADER) + } + + pub fn id(&self) -> GLuint { + self.id + } +} + +impl Drop for Shader { + fn drop(&mut self) { + unsafe { gl::DeleteShader(self.id) }; + } +} + +pub struct Program { + id: GLuint, +} + +impl Program { + pub fn from_shaders(shaders: &[Shader]) -> Result { + let id = unsafe { gl::CreateProgram() }; + + for shader in shaders { + unsafe { gl::AttachShader(id, shader.id()) }; + } + + unsafe { gl::LinkProgram(id) }; + + let mut success: GLint = 1; + unsafe { + gl::GetProgramiv(id, gl::LINK_STATUS, &mut success); + } + + if success == 0 { + let mut len: GLint = 0; + unsafe { + gl::GetProgramiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + let error = create_whitespace_cstring(len as usize); + unsafe { + gl::GetProgramInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut GLchar, + ); + } + return Err(error.to_string_lossy().into_owned()); + } + + for shader in shaders { + unsafe { gl::DetachShader(id, shader.id()) } + } + + Ok(Program { id }) + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn use_program(&self) { + unsafe { + gl::UseProgram(self.id); + } + } + + pub fn get_location(&self, name: &str) -> Result { + let cname = CString::new(name).unwrap(); + let loc = unsafe { + gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) + }; + + if loc != -1 { + Ok(loc) + } else { + Err("Could not get location".to_owned()) + } + } + + pub fn set_uniform(&self, loc: GLint, v: i32) { + unsafe { + gl::ProgramUniform1i(self.id, loc, v); + } + } +} + +fn create_whitespace_cstring(len: usize) -> CString { + let mut buffer: Vec = Vec::with_capacity(len as usize + 1); + buffer.extend([b' '].iter().cycle().take(len as usize)); + unsafe { CString::from_vec_unchecked(buffer) } +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum BufferType { + ArrayBuffer = gl::ARRAY_BUFFER, +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum BufferUsage { + StaticDraw = gl::STATIC_DRAW, + StreamDraw = gl::STREAM_DRAW, +} + +pub struct Buffer { + id: GLuint, + buffer_type: BufferType, + _phantom: std::marker::PhantomData, +} + +impl Buffer { + pub fn new(buffer_type: BufferType) -> Buffer { + let mut id: GLuint = 0; + unsafe { + gl::GenBuffers(1, &mut id); + } + Buffer { + id, + buffer_type, + _phantom: std::marker::PhantomData, + } + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn buffer_data(&self, data: &[T], usage: BufferUsage) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, self.id); + gl::BufferData( + self.buffer_type as GLuint, + (data.len() * std::mem::size_of::()) as GLsizeiptr, + data.as_ptr() as *const GLvoid, + usage as GLenum, + ); + gl::BindBuffer(self.buffer_type as GLuint, 0); + } + } + + pub fn bind(&self) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, self.id); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindBuffer(self.buffer_type as GLuint, 0); + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.id as *const GLuint); + } + } +} + +pub struct VertexArray { + id: GLuint, +} + +impl VertexArray { + pub fn new() -> VertexArray { + let mut id: GLuint = 0; + unsafe { + gl::GenVertexArrays(1, &mut id); + } + + VertexArray { id } + } + + pub fn id(&self) -> GLuint { + self.id + } + + pub fn bind(&self) { + unsafe { + gl::BindVertexArray(self.id); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindVertexArray(0); + } + } +} + +impl Drop for VertexArray { + fn drop(&mut self) { + unsafe { + gl::DeleteVertexArrays(1, &self.id as *const GLuint); + } + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x2 { + x: f32, + y: f32, +} + +impl f32x2 { + pub fn new(x: f32, y: f32) -> f32x2 { + f32x2 { x, y } + } + + pub fn num_components() -> usize { + 2 + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x3 { + x: f32, + y: f32, + z: f32, +} + +impl f32x3 { + pub fn new(x: f32, y: f32, z: f32) -> f32x3 { + f32x3 { x, y, z } + } + + pub fn num_components() -> usize { + 3 + } +} + +#[allow(non_camel_case_types)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct f32x4 { + x: f32, + y: f32, + z: f32, + w: f32, +} + +impl f32x4 { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> f32x4 { + f32x4 { x, y, z, w } + } + + pub fn zero() -> f32x4 { + f32x4::new(0.0, 0.0, 0.0, 0.0) + } + + pub fn set(&mut self, x: f32, y: f32, z: f32, w: f32) { + self.x = x; + self.y = y; + self.z = z; + self.w = w; + } + + pub fn num_components() -> usize { + 4 + } +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Vertex { + p: f32x3, + st: f32x2, +} +impl Vertex { + pub fn new(p: f32x3, st: f32x2) -> Vertex { + Vertex { p, st } + } + + unsafe fn vertex_attrib_pointer( + num_components: usize, + stride: usize, + location: usize, + offset: usize, + ) { + gl::EnableVertexAttribArray(location as gl::types::GLuint); // location(0) + gl::VertexAttribPointer( + location as gl::types::GLuint, // index of the vertex attribute + num_components as gl::types::GLint, /* number of components per + * vertex attrib */ + gl::FLOAT, + gl::FALSE, // normalized (int-to-float conversion), + stride as gl::types::GLint, /* byte stride between + * successive elements */ + offset as *const gl::types::GLvoid, /* offset of the first + * element */ + ); + } + + pub fn vertex_attrib_pointers() { + let stride = std::mem::size_of::(); + + let location = 0; + let offset = 0; + + // and configure the vertex array + unsafe { + Vertex::vertex_attrib_pointer( + f32x3::num_components(), + stride, + location, + offset, + ); + } + + let location = location + 1; + let offset = offset + std::mem::size_of::(); + + // and configure the st array + unsafe { + Vertex::vertex_attrib_pointer( + f32x2::num_components(), + stride, + location, + offset, + ); + } + } +} + +pub struct FullscreenQuad { + width: u32, + height: u32, + vertex_array: VertexArray, + program: Program, + texture_id: GLuint, + loc_progression: GLint, +} + +impl FullscreenQuad { + pub fn new(width: u32, height: u32) -> Result { + let vert_shader = Shader::vertex_from_source( + CStr::from_bytes_with_nul( + b" + #version 330 core + layout (location = 0) in vec3 _p; + layout (location = 1) in vec2 _st; + out vec2 st; + void main() { + gl_Position = vec4(_p, 1.0); + st = _st; + } + \0", + ) + .unwrap(), + )?; + + let frag_shader = Shader::fragment_from_source( + CStr::from_bytes_with_nul( + b" + #version 330 core + in vec2 st; + out vec4 Color; + + uniform sampler2D smp2d_0; + uniform int progression; + + void main() { + vec4 col = texture(smp2d_0, st); + col.r = pow(col.r / progression, 1/2.2); + col.g = pow(col.g / progression, 1/2.2); + col.b = pow(col.b / progression, 1/2.2); + Color = col; + } + \0", + ) + .unwrap(), + )?; + + let program = Program::from_shaders(&[vert_shader, frag_shader])?; + program.use_program(); + let loc_progression = program.get_location("progression")?; + + let vertices: Vec = vec![ + Vertex::new(f32x3::new(-1.0, -1.0, 0.0), f32x2::new(0.0, 0.0)), + Vertex::new(f32x3::new(1.0, -1.0, 0.0), f32x2::new(1.0, 0.0)), + Vertex::new(f32x3::new(1.0, 1.0, 0.0), f32x2::new(1.0, 1.0)), + Vertex::new(f32x3::new(-1.0, -1.0, 0.0), f32x2::new(0.0, 0.0)), + Vertex::new(f32x3::new(1.0, 1.0, 0.0), f32x2::new(1.0, 1.0)), + Vertex::new(f32x3::new(-1.0, 1.0, 0.0), f32x2::new(0.0, 1.0)), + ]; + let vertex_buffer = Buffer::::new(BufferType::ArrayBuffer); + vertex_buffer.buffer_data(&vertices, BufferUsage::StaticDraw); + + // Generate and bind the VAO + let vertex_array = VertexArray::new(); + + vertex_array.bind(); + // Re-bind the VBO to associate the two. We could just have left it + // bound earlier and let the association happen when we + // configure the VAO but this way at least makes the connection + // between the two seem more explicit, despite the magical + // state machine hiding in OpenGL + vertex_buffer.bind(); + + // Set up the vertex attribute pointers for all locations + Vertex::vertex_attrib_pointers(); + + // now unbind both the vbo and vao to keep everything cleaner + vertex_buffer.unbind(); + vertex_array.unbind(); + + // generate test texture data using the image width rather than the + // framebuffer width + let mut tex_data = Vec::with_capacity((width * height) as usize); + for y in 0..height { + for x in 0..width { + tex_data.push(f32x4::new( + (x as f32) / width as f32, + (y as f32) / height as f32, + 1.0, + 0.0, + )); + } + } + + // generate the texture for the quad + let mut texture_id: gl::types::GLuint = 0; + unsafe { + gl::GenTextures(1, &mut texture_id); + gl::ActiveTexture(gl::TEXTURE0); + gl::Enable(gl::TEXTURE_2D); + gl::BindTexture(gl::TEXTURE_2D, texture_id); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_BORDER as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_BORDER as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::NEAREST as gl::types::GLint, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + gl::NEAREST as gl::types::GLint, + ); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA32F as gl::types::GLint, + width as gl::types::GLint, + height as gl::types::GLint, + 0, + gl::RGBA, + gl::FLOAT, + tex_data.as_ptr() as *const gl::types::GLvoid, + ); + } + + Ok(FullscreenQuad { + width, + height, + vertex_array, + program, + texture_id, + loc_progression, + }) + } + + pub fn draw(&self) { + self.program.use_program(); + self.vertex_array.bind(); + unsafe { + gl::DrawArrays( + gl::TRIANGLES, + 0, // starting index in the enabled array + 6, // number of indices to draw + ) + } + self.vertex_array.unbind(); + } + + pub fn update_texture(&self, data: &[V4f32]) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.texture_id); + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, + 0, + 0, + self.width as GLint, + self.height as GLint, + gl::RGBA, + gl::FLOAT, + data.as_ptr() as *const GLvoid, + ); + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + unsafe { + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA32F as gl::types::GLint, + width as gl::types::GLint, + height as gl::types::GLint, + 0, + gl::RGBA, + gl::FLOAT, + std::ptr::null(), + ); + } + self.width = width; + self.height = height; + } + + pub fn set_progression(&self, progression: i32) { + self.program.set_uniform(self.loc_progression, progression); + } +} diff --git a/crates/optix/examples/ex04_mesh/src/main.rs b/crates/optix/examples/ex04_mesh/src/main.rs new file mode 100644 index 00000000..39072e60 --- /dev/null +++ b/crates/optix/examples/ex04_mesh/src/main.rs @@ -0,0 +1,86 @@ +mod renderer; +use renderer::Renderer; + +mod vector; +pub use vector::*; +mod gl_util; +use gl_util::FullscreenQuad; +use glfw::{Action, Context, Key}; + +fn main() -> Result<(), Box> { + let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap(); + glfw.window_hint(glfw::WindowHint::ContextVersion(4, 1)); + glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true)); + glfw.window_hint(glfw::WindowHint::OpenGlProfile( + glfw::OpenGlProfileHint::Core, + )); + + let mut width = 960u32; + let mut height = 540u32; + + let mut renderer = Renderer::new(width, height)?; + + let (mut window, events) = glfw + .create_window( + width, + height, + "Example 03: in window", + glfw::WindowMode::Windowed, + ) + .expect("failed to create glfw window"); + + window.set_key_polling(true); + window.make_current(); + + // retina displays will return a higher res for the framebuffer + // which we need to use for the viewport + let (fb_width, fb_height) = window.get_framebuffer_size(); + + gl::load_with(|s| glfw.get_proc_address_raw(s) as *const std::os::raw::c_void); + + let mut fsq = FullscreenQuad::new(width, height).unwrap(); + + let mut image_data = vec![v4f32(0.0, 0.0, 0.0, 0.0); (width * height) as usize]; + + unsafe { + gl::Viewport(0, 0, fb_width, fb_height); + }; + + while !window.should_close() { + glfw.poll_events(); + for (_, event) in glfw::flush_messages(&events) { + handle_window_event(&mut window, event); + } + + let (w, h) = window.get_framebuffer_size(); + let w = w as u32; + let h = h as u32; + if w != width || h != height { + fsq.resize(w, h); + renderer.resize(w, h)?; + width = w; + height = h; + image_data.resize((width * height) as usize, v4f32(0.0, 0.0, 0.0, 0.0)); + } + + renderer.render()?; + renderer.download_pixels(&mut image_data)?; + fsq.update_texture(&image_data); + fsq.set_progression(1); + + // draw the quad + fsq.draw(); + + window.swap_buffers(); + } + + renderer.render()?; + Ok(()) +} + +fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) { + match event { + glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => window.set_should_close(true), + _ => {} + } +} diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs new file mode 100644 index 00000000..6053d5b6 --- /dev/null +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -0,0 +1,350 @@ +use anyhow::{Context, Result}; +use cust::context::{Context as CuContext, ContextFlags}; +use cust::device::Device; +use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::stream::{Stream, StreamFlags}; +use cust::{CudaFlags, DeviceCopy}; +use optix::{ + context::DeviceContext, + module::{ + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, + PipelineCompileOptions, TraversableGraphFlags, + }, + pipeline::{Pipeline, PipelineLinkOptions}, + program_group::{ProgramGroup, ProgramGroupDesc}, + shader_binding_table::{SbtRecord, ShaderBindingTable}, +}; + +use glam::{vec3, IVec3, Vec3}; + +use crate::vector::V4f32; + +pub struct Renderer { + launch_params: LaunchParams, + buf_launch_params: DBox, + sbt: optix::sys::OptixShaderBindingTable, + buf_raygen: DBuffer, + buf_hitgroup: DBuffer, + buf_miss: DBuffer, + pipeline: Pipeline, + color_buffer: DBuffer, + ctx: DeviceContext, + stream: Stream, + cuda_context: CuContext, +} + +impl Renderer { + pub fn new(width: u32, height: u32) -> Result> { + init_optix()?; + + // create CUDA and OptiX contexts + let device = Device::get_device(0)?; + + let cuda_context = + CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let stream = Stream::new(StreamFlags::DEFAULT, None)?; + + let mut ctx = DeviceContext::new(&cuda_context)?; + ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; + + // create module + let module_compile_options = ModuleCompileOptions { + max_register_count: 50, + opt_level: CompileOptimizationLevel::Default, + debug_level: CompileDebugLevel::None, + }; + + let pipeline_compile_options = PipelineCompileOptions::new() + .pipeline_launch_params_variable_name("PARAMS") + .uses_motion_blur(false) + .num_attribute_values(2) + .num_payload_values(2) + .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) + .exception_flags(ExceptionFlags::NONE); + + let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex04_mesh.ptx")); + + let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, + ) + .context("Create module")?; + + // create raygen program + let pgdesc_raygen = ProgramGroupDesc::raygen(&module, "__raygen__renderFrame"); + + let (pg_raygen, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_raygen])?; + + // create miss program + let pgdesc_miss = ProgramGroupDesc::miss(&module, "__miss__radiance"); + + let (pg_miss, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_miss])?; + + let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( + Some((&module, "__closesthit__radiance")), + Some((&module, "__anyhit__radiance")), + None, + ); + + // create hitgroup programs + let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; + + // create geometry and accels + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + add_cube( + vec3(0.0, -1.5, 0.0), + vec3(10.0, 0.1, 10.0), + &mut vertices, + &mut indices, + ); + add_cube( + vec3(0.0, 0.0, 0.0), + vec3(2.0, 2.0, 2.0), + &mut vertices, + &mut indices, + ); + + let buf_vertex = DBuffer::from_slice(&vertices)?; + let buf_indices = DBuffer::from_slice(&indices)?; + + let geometry_flags = optix::GeometryFlags::None; + let triangle_input = optix::BuildInput::<_, ()>::TriangleArray( + optix::TriangleArray::new( + smallvec![buf_vertex.as_slice()], + std::slice::from_ref(&geometry_flags), + ) + .index_buffer(buf_indices.as_slice()), + ); + + // blas setup + let accel_options = optix::AccelBuildOptions::new( + optix::BuildFlags::ALLOW_COMPACTION, + optix::BuildOperation::Build, + ); + + let build_inputs = vec![triangle_input]; + + let blas_buffer_sizes = ctx.accel_compute_memory_usage(&[accel_options], &build_inputs)?; + + // prepare compaction + let temp_buffer = optix::Buffer::uninitialized_with_align_in( + blas_buffer_sizes.temp_size_in_bytes, + optix::ACCEL_BUFFER_BYTE_ALIGNMENT, + FrameAlloc, + )?; + + let output_buffer = optix::Buffer::uninitialized_with_align_in( + blas_buffer_sizes.output_size_in_bytes, + optix::ACCEL_BUFFER_BYTE_ALIGNMENT, + FrameAlloc, + )?; + + let compacted_size_buffer = + optix::TypedBuffer::::uninitialized_in(1, FrameAlloc)?; + + let mut properties = vec![optix::AccelEmitDesc::CompactedSize( + compacted_size_buffer.device_ptr(), + )]; + + let as_handle = ctx.accel_build( + &stream, + &[accel_options], + &build_inputs, + &temp_buffer, + &output_buffer, + &mut properties, + )?; + + cu::Context::synchronize()?; + + let mut compacted_size = 0usize; + compacted_size_buffer.download(std::slice::from_mut(&mut compacted_size))?; + + let as_buffer = optix::Buffer::uninitialized_with_align_in( + compacted_size, + optix::ACCEL_BUFFER_BYTE_ALIGNMENT, + FrameAlloc, + )?; + + let as_handle = ctx.accel_compact(&stream, as_handle, &as_buffer)?; + cu::Context::synchronize()?; + + // create SBT + let rec_raygen: Vec<_> = pg_raygen + .iter() + .map(|pg| RaygenRecord::pack(0, pg).expect("failed to pack raygen record")) + .collect(); + + let rec_miss: Vec<_> = pg_miss + .iter() + .map(|pg| MissRecord::pack(0, pg).expect("failed to pack miss record")) + .collect(); + + let num_objects = 1; + let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); + + let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + + let sbt = ShaderBindingTable::new(&mut buf_raygen) + .miss(&mut buf_miss) + .hitgroup(&mut buf_hitgroup) + .build(); + + // create pipeline + let mut program_groups = Vec::new(); + program_groups.extend(pg_raygen.into_iter()); + program_groups.extend(pg_miss.into_iter()); + program_groups.extend(pg_hitgroup.into_iter()); + + let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, + }; + + let (pipeline, _log) = Pipeline::new( + &mut ctx, + &pipeline_compile_options, + pipeline_link_options, + &program_groups, + )?; + + pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; + + let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; + + let launch_params = LaunchParams { + frame_id: 0, + color_buffer: color_buffer.as_device_ptr(), + fb_size: Point2i { + x: width as i32, + y: height as i32, + }, + }; + + let buf_launch_params = DBox::new(&launch_params)?; + + Ok(Renderer { + ctx, + cuda_context, + stream, + launch_params, + buf_launch_params, + buf_raygen, + buf_hitgroup, + buf_miss, + sbt, + pipeline, + color_buffer, + }) + } + + pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Box> { + self.color_buffer = unsafe { DBuffer::uninitialized((width * height) as usize)? }; + self.launch_params.fb_size.x = width as i32; + self.launch_params.fb_size.y = height as i32; + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + Ok(()) + } + + pub fn render(&mut self) -> Result<(), Box> { + self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.frame_id += 1; + + unsafe { + optix::launch( + &self.pipeline, + &self.stream, + &mut self.buf_launch_params, + &self.sbt, + self.launch_params.fb_size.x as u32, + self.launch_params.fb_size.y as u32, + 1, + )?; + } + + self.stream.synchronize()?; + + Ok(()) + } + + pub fn download_pixels(&self, slice: &mut [V4f32]) -> Result<(), Box> { + self.color_buffer.copy_to(slice)?; + Ok(()) + } +} + +#[repr(C)] +#[derive(Copy, Clone, DeviceCopy)] +struct Point2i { + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Copy, Clone, DeviceCopy)] +struct LaunchParams { + pub color_buffer: DevicePointer, + pub fb_size: Point2i, + pub frame_id: i32, +} + +type RaygenRecord = SbtRecord; +type MissRecord = SbtRecord; + +#[derive(Copy, Clone, Default, DeviceCopy)] +struct HitgroupSbtData { + object_id: u32, +} +type HitgroupRecord = SbtRecord; + +fn init_optix() -> Result<(), Box> { + cust::init(CudaFlags::empty())?; + let device_count = Device::num_devices()?; + if device_count == 0 { + panic!("No CUDA devices found!"); + } + + optix::init()?; + Ok(()) +} + +pub fn add_cube(center: Vec3, size: Vec3, vertices: &mut Vec, indices: &mut Vec) { + let start_index = vertices.len() as i32; + + vertices.push((vec3(0.0, 0.0, 0.0)) * size + center - 0.5 * size); + vertices.push((vec3(1.0, 0.0, 0.0)) * size + center - 0.5 * size); + vertices.push((vec3(0.0, 1.0, 0.0)) * size + center - 0.5 * size); + vertices.push((vec3(1.0, 1.0, 0.0)) * size + center - 0.5 * size); + vertices.push((vec3(0.0, 0.0, 1.0)) * size + center - 0.5 * size); + vertices.push((vec3(1.0, 0.0, 1.0)) * size + center - 0.5 * size); + vertices.push((vec3(0.0, 1.0, 1.0)) * size + center - 0.5 * size); + vertices.push((vec3(1.0, 1.0, 1.0)) * size + center - 0.5 * size); + + const idx: [i32; 36] = [ + 0, 1, 3, 2, 3, 0, 5, 7, 6, 5, 6, 4, 0, 4, 5, 0, 5, 1, 2, 3, 7, 2, 7, 6, 1, 5, 6, 1, 7, 3, + 4, 0, 2, 4, 2, 6, + ]; + + for c in idx.chunks(3) { + indices.push(IVec3::new( + c[0] + start_index, + c[1] + start_index, + c[2] + start_index, + )); + } +} diff --git a/crates/optix/examples/ex04_mesh/src/vector.rs b/crates/optix/examples/ex04_mesh/src/vector.rs new file mode 100644 index 00000000..589a4134 --- /dev/null +++ b/crates/optix/examples/ex04_mesh/src/vector.rs @@ -0,0 +1,301 @@ +use core::ops; +pub use num_traits::{One, Zero}; + +pub trait Scalar: num_traits::One + num_traits::Zero {} + +impl Scalar for i8 {} +impl Scalar for i16 {} +impl Scalar for i32 {} +impl Scalar for i64 {} +impl Scalar for f32 {} +impl Scalar for f64 {} + +pub trait Vector { + type Component: Scalar; + + fn dot(&self, v: &Self) -> Self::Component; + + #[inline] + fn length2(&self) -> Self::Component { + self.dot(&self) + } +} + +macro_rules! vec_impl { + ($name:ident: $t:ty, $sc:ident, $align:expr, ($($c:ident),+)) => { + #[repr(C)] + #[derive(Clone, Copy, Default, PartialEq, Debug)] + pub struct $name + { + $( + pub $c: $t, + )+ + } + + impl $name + { + pub fn new($($c: $t),+) -> Self + { + Self { + $( + $c, + )+ + } + } + } + + impl Vector for $name + { + type Component = $t; + + #[inline] + fn dot(&self, v: &Self) -> $t + { + <$t>::zero() $( + + self.$c * v.$c + )+ + } + } + + impl From<$t> for $name + { + fn from(x: $t) -> Self + { + Self { + $( + $c: x, + )+ + } + } + } + + impl ops::Neg for $name + { + type Output = Self; + + fn neg(self) -> Self + { + Self { + $( + $c: -self.$c, + )+ + } + } + } + + impl ops::Add for $name + { + type Output = Self; + + #[inline] + fn add(self, v: Self) -> Self + { + Self { + $( + $c: self.$c + v.$c, + )+ + } + } + } + + impl ops::AddAssign for $name + { + #[inline] + fn add_assign(&mut self, v: Self) + { + $( + self.$c += v.$c; + )+ + } + } + + impl ops::Sub for $name + { + type Output = Self; + + #[inline] + fn sub(self, v: Self) -> Self + { + Self { + $( + $c: self.$c - v.$c, + )+ + } + } + } + + impl ops::SubAssign for $name + { + #[inline] + fn sub_assign(&mut self, v: Self) + { + $( + self.$c -= v.$c; + )+ + } + } + + impl ops::Mul for $name + { + type Output = Self; + + #[inline] + fn mul(self, v: Self) -> Self + { + Self { + $( + $c: self.$c * v.$c, + )+ + } + } + } + + impl ops::MulAssign for $name + { + #[inline] + fn mul_assign(&mut self, v: Self) + { + $( + self.$c *= v.$c; + )+ + } + } + + impl ops::Mul<$t> for $name + { + type Output = Self; + + #[inline] + fn mul(self, v: $t) -> Self + { + Self { + $( + $c: self.$c * v, + )+ + } + } + } + + impl ops::MulAssign<$t> for $name + { + #[inline] + fn mul_assign(&mut self, v: $t) + { + $( + self.$c *= v; + )+ + } + } + + impl ops::Div<$t> for $name + { + type Output = Self; + + #[inline] + fn div(self, v: $t) -> Self + { + Self { + $( + $c: self.$c / v, + )+ + } + } + } + + impl ops::DivAssign<$t> for $name + { + #[inline] + fn div_assign(&mut self, v: $t) + { + $( + self.$c /= v; + )+ + } + } + + impl ops::Mul<$name> for $t + { + type Output = $name; + + #[inline] + fn mul(self, v: $name) -> $name + { + $name { + $( + $c: self * v.$c, + )+ + } + } + } + + impl ops::Div<$name> for $t + { + type Output = $name; + + #[inline] + fn div(self, v: $name) -> $name + { + $name { + $( + $c: self / v.$c, + )+ + } + } + } + + pub fn $sc($($c: $t),+) -> $name + { + $name { + $( + $c, + )+ + } + } + + unsafe impl cust::memory::DeviceCopy for $name { + // fn device_align() -> usize { + // $align + // } + } + }; + +} + +vec_impl!(V2i8: i8, v2i8, 1, (x, y)); +vec_impl!(V2i16: i16, v2i16, 2, (x, y)); +vec_impl!(V2i32: i32, v2i32, 8, (x, y)); +vec_impl!(V2i64: i64, v2i64, 8, (x, y)); +vec_impl!(V3i8: i8, v3i8, 1, (x, y, z)); +vec_impl!(V3i16: i16, v3i16, 2, (x, y, z)); +vec_impl!(V3i32: i32, v3i32, 4, (x, y, z)); +vec_impl!(V3i64: i64, v3i64, 8, (x, y, z)); +vec_impl!(V4i8: i8, v4i8, 1, (x, y, z, w)); +vec_impl!(V4i16: i16, v4i16, 2, (x, y, z, w)); +vec_impl!(V4i32: i32, v4i32, 16, (x, y, z, w)); +vec_impl!(V4i64: i64, v4i64, 8, (x, y, z, w)); + +vec_impl!(V2f32: f32, v2f32, 8, (x, y)); +vec_impl!(V2f64: f64, v2f64, 8, (x, y)); +vec_impl!(V3f32: f32, v3f32, 4, (x, y, z)); +vec_impl!(V3f64: f64, v3f64, 8, (x, y, z)); +vec_impl!(V4f32: f32, v4f32, 16, (x, y, z, w)); +vec_impl!(V4f64: f64, v4f64, 8, (x, y, z, w)); + +vec_impl!(P2f32: f32, p2f32, 8, (x, y)); +vec_impl!(P2f64: f64, p2f64, 8, (x, y)); +vec_impl!(P3f32: f32, p3f32, 4, (x, y, z)); +vec_impl!(P3f64: f64, p3f64, 8, (x, y, z)); +vec_impl!(P4f32: f32, p4f32, 16, (x, y, z, w)); +vec_impl!(P4f64: f64, p4f64, 8, (x, y, z, w)); + +vec_impl!(N2f32: f32, n2f32, 8, (x, y)); +vec_impl!(N2f64: f64, n2f64, 8, (x, y)); +vec_impl!(N3f32: f32, n3f32, 4, (x, y, z)); +vec_impl!(N3f64: f64, n3f64, 8, (x, y, z)); +vec_impl!(N4f32: f32, n4f32, 16, (x, y, z, w)); +vec_impl!(N4f64: f64, n4f64, 8, (x, y, z, w)); + +#[inline] +pub fn dot(a: &T, b: &T) -> T::Component { + a.dot(b) +} diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs new file mode 100644 index 00000000..da781956 --- /dev/null +++ b/crates/optix/src/acceleration.rs @@ -0,0 +1,284 @@ +use crate::{ + context::DeviceContext, error::Error, instance_array::BuildInputInstanceArray, module::Module, + optix_call, sys, triangle_array::BuildInputTriangleArray, +}; +use cust::{ + memory::{DBox, DBuffer, DSlice}, + DeviceCopy, +}; +type Result = std::result::Result; + +/// Opaque handle to a traversable acceleration structure. +/// # Safety +/// You should consider this handle to be a raw pointer, thus you can copy it +/// and it provides no tracking of lifetime or ownership. You are responsible +/// for ensuring that the device memory containing the acceleration structures +/// this handle references are alive if you try to use this handle +#[repr(transparent)] +#[derive(Copy, Clone, Debug, PartialEq, DeviceCopy)] +pub struct TraversableHandle { + pub(crate) inner: u64, +} + +impl DeviceContext { + /// Computes the device memory required for temporary and output buffers + /// when building the acceleration structure. Use the returned sizes to + /// allocate enough memory to pass to `accel_build()` + pub fn accel_compute_memory_usage( + &self, + accel_options: &[AccelBuildOptions], + build_inputs: &[BuildInput], + ) -> Result { + let mut buffer_sizes = AccelBufferSizes::default(); + let build_sys: Vec<_> = build_inputs + .iter() + .map(|b| match b { + BuildInput::TriangleArray(bita) => sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: sys::OptixBuildInputUnion { + triangle_array: std::mem::ManuallyDrop::new(bita.to_sys()), + }, + }, + BuildInput::InstanceArray(biia) => sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(biia.to_sys()), + }, + }, + _ => unimplemented!(), + }) + .collect(); + + unsafe { + Ok(optix_call!(optixAccelComputeMemoryUsage( + self.raw, + accel_options.as_ptr() as *const _, + build_sys.as_ptr(), + build_sys.len() as u32, + &mut buffer_sizes as *mut _ as *mut _, + )) + .map(|_| buffer_sizes)?) + } + } + + /// Builds the acceleration structure. + /// `temp_buffer` and `output_buffer` must be at least as large as the sizes + /// returned by `accel_compute_memory_usage()` + pub fn accel_build( + &self, + stream: &cust::stream::Stream, + accel_options: &[AccelBuildOptions], + build_inputs: &[BuildInput], + temp_buffer: &mut DSlice, + output_buffer: &mut DSlice, + emitted_properties: &mut [AccelEmitDesc], + ) -> Result { + let mut traversable_handle = TraversableHandle { inner: 0 }; + let properties: Vec = + emitted_properties.iter_mut().map(|p| p.into()).collect(); + + let build_sys: Vec<_> = build_inputs + .iter() + .map(|b| match b { + BuildInput::TriangleArray(bita) => sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: sys::OptixBuildInputUnion { + triangle_array: std::mem::ManuallyDrop::new(bita.to_sys()), + }, + }, + BuildInput::InstanceArray(biia) => sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(biia.to_sys()), + }, + }, + _ => unimplemented!(), + }) + .collect(); + + unsafe { + Ok(optix_call!(optixAccelBuild( + self.raw, + stream.as_inner(), + accel_options.as_ptr() as *const _, + build_sys.as_ptr(), + build_sys.len() as u32, + temp_buffer.as_device_ptr().as_raw_mut() as u64, + temp_buffer.len(), + output_buffer.as_device_ptr().as_raw_mut() as u64, + output_buffer.len(), + &mut traversable_handle as *mut _ as *mut _, + properties.as_ptr() as *const _, + properties.len() as u32, + )) + .map(|_| traversable_handle)?) + } + } + + /// Compacts the acceleration structure referenced by `input_handle`, + /// storing the result in `output_buffer` and returning a handle to the + /// newly compacted structure + pub fn accel_compact( + &self, + stream: &cust::stream::Stream, + input_handle: TraversableHandle, + output_buffer: &mut DSlice, + ) -> Result { + let mut traversable_handle = TraversableHandle { inner: 0 }; + unsafe { + Ok(optix_call!(optixAccelCompact( + self.raw, + stream.as_inner(), + input_handle.inner, + output_buffer.as_device_ptr().as_raw_mut() as u64, + output_buffer.len(), + &mut traversable_handle as *mut _ as *mut _, + )) + .map(|_| traversable_handle)?) + } + } +} + +bitflags::bitflags! { + pub struct BuildFlags: u32 { + const NONE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_NONE; + const ALLOW_UPDATE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_UPDATE; + const ALLOW_COMPACTION = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_COMPACTION; + const PREFER_FAST_TRACE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_TRACE; + const PREFER_FAST_BUILD = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_BUILD; + const ALLOW_RANDOM_VERTEX_ACCESS = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_VERTEX_ACCESS; + } +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum BuildOperation { + Build = sys::OptixBuildOperation_OPTIX_BUILD_OPERATION_BUILD, + Update = sys::OptixBuildOperation_OPTIX_BUILD_OPERATION_UPDATE, +} + +bitflags::bitflags! { + pub struct MotionFlags: u16 { + const NONE = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_NONE as u16; + const START_VANISH = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH as u16; + const END_VANISH = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_END_VANISH as u16; + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct MotionOptions { + pub num_keys: u16, + pub flags: MotionFlags, + pub time_begin: f32, + pub time_end: f32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct AccelBuildOptions { + build_flags: BuildFlags, + operation: BuildOperation, + motion_options: MotionOptions, +} + +impl AccelBuildOptions { + pub fn new(build_flags: BuildFlags, operation: BuildOperation) -> Self { + AccelBuildOptions { + build_flags, + operation, + motion_options: MotionOptions { + num_keys: 1, + flags: MotionFlags::NONE, + time_begin: 0.0f32, + time_end: 1.0f32, + }, + } + } + + pub fn num_keys(mut self, num_keys: u16) -> Self { + self.motion_options.num_keys = num_keys; + self + } + + pub fn time_interval(mut self, time_begin: f32, time_end: f32) -> Self { + self.motion_options.time_begin = time_begin; + self.motion_options.time_end = time_end; + self + } + + pub fn motion_flags(mut self, flags: MotionFlags) -> Self { + self.motion_options.flags = flags; + self + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct AccelBufferSizes { + pub output_size_in_bytes: usize, + pub temp_size_in_bytes: usize, + pub temp_update_size_in_bytes: usize, +} + +pub struct TriangleArrayDefault; +impl BuildInputTriangleArray for TriangleArrayDefault { + fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { + unreachable!() + } +} +pub struct InstanceArrayDefault; +impl BuildInputInstanceArray for InstanceArrayDefault { + fn to_sys(&self) -> sys::OptixBuildInputInstanceArray { + unreachable!() + } +} + +pub enum BuildInput< + T: BuildInputTriangleArray = TriangleArrayDefault, + I: BuildInputInstanceArray = InstanceArrayDefault, +> { + TriangleArray(T), + CurveArray, + CustomPrimitiveArray, + InstanceArray(I), +} + +pub enum AccelEmitDesc { + CompactedSize(DBox), + Aabbs(DBuffer), //< FIXME: need to handle OptixAabbBufferByteAlignment here +} + +#[repr(C)] +#[derive(DeviceCopy, Copy, Clone)] +pub struct Aabb { + min_x: f32, + min_y: f32, + min_z: f32, + max_x: f32, + max_y: f32, + max_z: f32, +} + +impl From<&mut AccelEmitDesc> for sys::OptixAccelEmitDesc { + fn from(aed: &mut AccelEmitDesc) -> Self { + match aed { + AccelEmitDesc::CompactedSize(p) => Self { + result: p.as_device_ptr().as_raw() as u64, + type_: sys::OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE, + }, + AccelEmitDesc::Aabbs(p) => Self { + result: p.as_device_ptr().as_raw() as u64, + type_: sys::OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS, + }, + } + } +} + +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum GeometryFlags { + None = sys::OptixGeometryFlags::None as u32, + DisableAnyHit = sys::OptixGeometryFlags::DisableAnyHit as u32, + RequireSingleAnyHitCall = sys::OptixGeometryFlags::RequireSingleAnyHitCall as u32, +} diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs new file mode 100644 index 00000000..0dd15f91 --- /dev/null +++ b/crates/optix/src/instance_array.rs @@ -0,0 +1,117 @@ +use crate::{ + acceleration::{GeometryFlags, TraversableHandle}, context::DeviceContext, error::Error, module::Module, optix_call, + sys, +}; +use cust::{memory::DSlice, DeviceCopy}; +use std::ffi::c_void; + +#[repr(C, align(16))] +#[derive(Debug, DeviceCopy, Copy, Clone)] +pub struct Instance { + transform: [f32; 12], + instance_id: u32, + sbt_offset: u32, + visibility_mask: u32, + flags: InstanceFlags, + traversable_handle: TraversableHandle, + pad: [u32; 2], +} + + +bitflags::bitflags! { + #[derive(DeviceCopy)] + pub struct InstanceFlags: u32 { + const NONE = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE; + const DISABLE_TRIANGLE_FACE_CULLING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING; + const FLIP_TRIANGLE_FACING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING; + const DISABLE_ANYHIT = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT; + const ENFORCE_ANYHIT = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT; + const DISABLE_TRANSFORM = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; + } +} + +impl Instance { + pub fn new(traversable_handle: TraversableHandle) -> Instance { + #[cfg_attr(rustfmt, rustfmt_skip)] + Instance { + transform: [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0], + instance_id: 0, + sbt_offset: 0, + visibility_mask: 255, + flags: InstanceFlags::NONE, + traversable_handle, + pad: [0; 2], + } + } + + pub fn transform(mut self, transform: [f32; 12]) -> Instance { + self.transform = transform; + self + } + + pub fn instance_id(mut self, instance_id: u32) -> Instance { + self.instance_id = instance_id; + self + } + + pub fn sbt_offset(mut self, sbt_offset: u32) -> Instance { + self.sbt_offset = sbt_offset; + self + } + + pub fn visibility_mask(mut self, visibility_mask: u8) -> Instance { + self.visibility_mask = visibility_mask as u32; + self + } + + pub fn flags(mut self, flags: InstanceFlags) -> Instance { + self.flags = flags; + self + } +} + +pub struct InstanceArray<'i> { + instances: &'i DSlice, +} + +impl<'i> InstanceArray<'i> { + pub fn new(instances: &'i DSlice) -> InstanceArray { + InstanceArray { + instances + } + } +} + +pub trait BuildInputInstanceArray { + fn to_sys(&self) -> sys::OptixBuildInputInstanceArray; +} + +impl BuildInputInstanceArray for () { + fn to_sys(&self) -> sys::OptixBuildInputInstanceArray { + unreachable!() + } +} + +impl<'i> BuildInputInstanceArray for InstanceArray<'i> { + fn to_sys(&self) -> sys::OptixBuildInputInstanceArray { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr().as_raw() as u64, + numInstances: self.instances.len() as u32, + } + } else { + sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr().as_raw() as u64, + numInstances: self.instances.len() as u32, + aabbs: 0, + numAabbs: 0, + } + } + } + } +} + diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 3eb209de..5b8f9d6c 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -1,11 +1,14 @@ +pub mod acceleration; pub mod context; pub mod denoiser; pub mod error; +pub mod instance_array; pub mod module; pub mod pipeline; pub mod program_group; pub mod shader_binding_table; pub mod sys; +pub mod triangle_array; pub use cust; use error::{Error, ToResult}; diff --git a/crates/optix/src/triangle_array.rs b/crates/optix/src/triangle_array.rs new file mode 100644 index 00000000..2b4dbb51 --- /dev/null +++ b/crates/optix/src/triangle_array.rs @@ -0,0 +1,150 @@ +use crate::{ + acceleration::GeometryFlags, context::DeviceContext, error::Error, module::Module, optix_call, + sys, +}; +use cust::{memory::DSlice, DeviceCopy}; +use std::ffi::c_void; + +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum VertexFormat { + None = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_NONE as u32, + Float3 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT3 as u32, + Float2 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT2 as u32, + Half3 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF3 as u32, + Half2 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF2 as u32, + SNorm16 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_3 as u32, + SNorm32 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_2 as u32, +} + +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum IndicesFormat { + None = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE as u32, + Short3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3 as u32, + Int3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3 as u32, +} + +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum TransformFormat { + None = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + MatrixFloat12 = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12, +} + +pub trait Vertex: cust::memory::DeviceCopy { + const FORMAT: VertexFormat; + const STRIDE: u32 = 0; +} + +pub trait IndexTriple: cust::memory::DeviceCopy { + const FORMAT: IndicesFormat; + const STRIDE: u32 = 0; +} + +pub trait BuildInputTriangleArray { + fn to_sys(&self) -> sys::OptixBuildInputTriangleArray; +} + +impl BuildInputTriangleArray for () { + fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { + unreachable!() + } +} + +pub struct TriangleArray<'v, 'g, V: Vertex> { + // We hold slices here to make sure the referenced device memory remains + // valid for the lifetime of the build input + vertex_buffers: Vec<&'v DSlice>, + // This is the array of device pointers passed to optix functions + d_vertex_buffers: Vec<*const c_void>, + // per-object geometry flags + geometry_flags: &'g [GeometryFlags], +} + +impl<'v, 'vs, 'g, V: Vertex> BuildInputTriangleArray for TriangleArray<'v, 'g, V> { + fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { + sys::OptixBuildInputTriangleArray { + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, + numVertices: self.vertex_buffers[0].len() as u32, + vertexFormat: V::FORMAT as u32, + vertexStrideInBytes: V::STRIDE, + indexBuffer: 0, + numIndexTriplets: 0, + indexFormat: sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE, + indexStrideInBytes: 0, + preTransform: 0, + flags: self.geometry_flags.as_ptr() as *const _, + numSbtRecords: 1, + sbtIndexOffsetBuffer: 0, + sbtIndexOffsetSizeInBytes: 0, + sbtIndexOffsetStrideInBytes: 0, + primitiveIndexOffset: 0, + transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + } + } +} + +impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { + pub fn new(vertex_buffers: &[&'v DSlice], geometry_flags: &'g [GeometryFlags]) -> Self { + let d_vertex_buffers: Vec<_> = vertex_buffers + .iter() + .map(|b| b.as_device_ptr().as_raw() as *const c_void) + .collect(); + + TriangleArray { + vertex_buffers: vertex_buffers.to_vec(), + d_vertex_buffers, + geometry_flags, + } + } + + pub fn index_buffer<'i, I: IndexTriple>( + self, + index_buffer: &'i DSlice, + ) -> IndexedTriangleArray<'v, 'g, 'i, V, I> { + IndexedTriangleArray { + vertex_buffers: self.vertex_buffers, + d_vertex_buffers: self.d_vertex_buffers, + index_buffer, + geometry_flags: self.geometry_flags, + } + } +} + +#[doc(hidden)] +pub struct IndexedTriangleArray<'v, 'g, 'i, V: Vertex, I: IndexTriple> { + // We hold slices here to make sure the referenced device memory remains + // valid for the lifetime of the build input + vertex_buffers: Vec<&'v DSlice>, + // This is the array of device pointers passed to optix functions + d_vertex_buffers: Vec<*const c_void>, + index_buffer: &'i DSlice, + // per-object geometry flags + geometry_flags: &'g [GeometryFlags], +} + +impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInputTriangleArray + for IndexedTriangleArray<'v, 'i, 'g, V, I> +{ + fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { + sys::OptixBuildInputTriangleArray { + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, + numVertices: self.vertex_buffers[0].len() as u32, + vertexFormat: V::FORMAT as u32, + vertexStrideInBytes: V::STRIDE, + indexBuffer: self.index_buffer.as_device_ptr().as_raw() as u64, + numIndexTriplets: self.index_buffer.len() as u32, + indexFormat: I::FORMAT as u32, + indexStrideInBytes: I::STRIDE, + preTransform: 0, + flags: self.geometry_flags.as_ptr() as *const _, + numSbtRecords: 1, + sbtIndexOffsetBuffer: 0, + sbtIndexOffsetSizeInBytes: 0, + sbtIndexOffsetStrideInBytes: 0, + primitiveIndexOffset: 0, + transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + } + } +} From 2a7d98d6947c2022d684034acf82cba61b9b9aab Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 14:09:10 +1300 Subject: [PATCH 043/100] Add accel wip Enough acceleration structure stuff to get example 04 running, and rebasing on top of deviceptr branch --- crates/optix/Cargo.toml | 6 +- .../examples/ex02_pipeline/src/renderer.rs | 28 +-- .../examples/ex03_window/src/renderer.rs | 29 ++-- .../optix/examples/ex04_mesh/src/gl_util.rs | 41 +---- crates/optix/examples/ex04_mesh/src/main.rs | 10 +- .../optix/examples/ex04_mesh/src/renderer.rs | 161 ++++++++++-------- crates/optix/src/acceleration.rs | 22 +-- crates/optix/src/impl_glam.rs | 9 + crates/optix/src/instance_array.rs | 10 +- crates/optix/src/lib.rs | 5 +- crates/optix/src/shader_binding_table.rs | 22 +-- crates/optix/src/triangle_array.rs | 50 ++++-- 12 files changed, 212 insertions(+), 181 deletions(-) create mode 100644 crates/optix/src/impl_glam.rs diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 7cdb8e5f..d3b0c12b 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -7,13 +7,15 @@ edition = "2021" optix71 = [] optix72 = [] optix73 = [] -default=["optix73"] +default=["optix73", "glam"] [dependencies] -cust = { version = "0.1", path = "../cust" } +cust = { version = "0.1", path = "../cust", features=["glam"] } cust_raw = { version = "0.11.2", path = "../cust_raw" } cfg-if = "1.0.0" bitflags = "1.3.2" +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } +half = { version = "^1.8", optional = true } [build-dependencies] bindgen = "0.59" diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 42c4ec97..d40db7c5 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::{Device, DeviceAttribute}; -use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; use cust::DeviceCopy; @@ -21,13 +21,13 @@ pub struct Renderer { cuda_context: CuContext, stream: Stream, launch_params: LaunchParams, - buf_launch_params: DBox, - buf_raygen: DBuffer, - buf_hitgroup: DBuffer, - buf_miss: DBuffer, + buf_launch_params: DeviceBox, + buf_raygen: DeviceBuffer, + buf_hitgroup: DeviceBuffer, + buf_miss: DeviceBuffer, sbt: optix::sys::OptixShaderBindingTable, pipeline: Pipeline, - color_buffer: DBuffer, + color_buffer: DeviceBuffer, } impl Renderer { @@ -115,9 +115,9 @@ impl Renderer { }) .collect(); - let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; - let mut buf_miss = DBuffer::from_slice(&rec_miss)?; - let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + let mut buf_raygen = DeviceBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DeviceBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DeviceBuffer::from_slice(&rec_hitgroup)?; let sbt = ShaderBindingTable::new(&mut buf_raygen) .miss(&mut buf_miss) @@ -144,18 +144,18 @@ impl Renderer { pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - let mut color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; + let mut color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; let launch_params = LaunchParams { frame_id: 0, - color_buffer: color_buffer.as_device_ptr(), + color_buffer: color_buffer.as_ptr(), fb_size: Point2i { x: width as i32, y: height as i32, }, }; - let buf_launch_params = DBox::new(&launch_params)?; + let buf_launch_params = DeviceBox::new(&launch_params)?; Ok(Renderer { ctx, @@ -177,10 +177,10 @@ impl Renderer { width: usize, height: usize, ) -> Result<(), Box> { - self.color_buffer = unsafe { DBuffer::uninitialized(width * height)? }; + self.color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; self.launch_params.fb_size.x = width as i32; self.launch_params.fb_size.y = height as i32; - self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + self.launch_params.color_buffer = self.color_buffer.as_ptr(); Ok(()) } diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index b4c1a529..9bebc20e 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::Device; -use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; use optix::{ @@ -19,13 +19,13 @@ use crate::vector::V4f32; pub struct Renderer { launch_params: LaunchParams, - buf_launch_params: DBox, + buf_launch_params: DeviceBox, sbt: optix::sys::OptixShaderBindingTable, - buf_raygen: DBuffer, - buf_hitgroup: DBuffer, - buf_miss: DBuffer, + buf_raygen: DeviceBuffer, + buf_hitgroup: DeviceBuffer, + buf_miss: DeviceBuffer, pipeline: Pipeline, - color_buffer: DBuffer, + color_buffer: DeviceBuffer, ctx: DeviceContext, stream: Stream, cuda_context: CuContext, @@ -113,9 +113,9 @@ impl Renderer { }) .collect(); - let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; - let mut buf_miss = DBuffer::from_slice(&rec_miss)?; - let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + let mut buf_raygen = DeviceBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DeviceBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DeviceBuffer::from_slice(&rec_hitgroup)?; let sbt = ShaderBindingTable::new(&mut buf_raygen) .miss(&mut buf_miss) @@ -142,18 +142,19 @@ impl Renderer { pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; + let mut color_buffer = + unsafe { DeviceBuffer::uninitialized(width as usize * height as usize)? }; let launch_params = LaunchParams { frame_id: 0, - color_buffer: color_buffer.as_device_ptr(), + color_buffer: color_buffer.as_ptr(), fb_size: Point2i { x: width as i32, y: height as i32, }, }; - let buf_launch_params = DBox::new(&launch_params)?; + let buf_launch_params = DeviceBox::new(&launch_params)?; Ok(Renderer { ctx, @@ -171,10 +172,10 @@ impl Renderer { } pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Box> { - self.color_buffer = unsafe { DBuffer::uninitialized((width * height) as usize)? }; + self.color_buffer = unsafe { DeviceBuffer::uninitialized((width * height) as usize)? }; self.launch_params.fb_size.x = width as i32; self.launch_params.fb_size.y = height as i32; - self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + self.launch_params.color_buffer = self.color_buffer.as_ptr(); Ok(()) } diff --git a/crates/optix/examples/ex04_mesh/src/gl_util.rs b/crates/optix/examples/ex04_mesh/src/gl_util.rs index c8b39caa..2199181a 100644 --- a/crates/optix/examples/ex04_mesh/src/gl_util.rs +++ b/crates/optix/examples/ex04_mesh/src/gl_util.rs @@ -2,17 +2,14 @@ use gl; use gl::types::{GLchar, GLenum, GLint, GLsizeiptr, GLuint, GLvoid}; use std::ffi::{CStr, CString}; -use crate::vector::*; +use glam::Vec4; pub struct Shader { id: GLuint, } impl Shader { - pub fn from_source( - source: &CStr, - shader_type: GLenum, - ) -> Result { + pub fn from_source(source: &CStr, shader_type: GLenum) -> Result { let id = unsafe { gl::CreateShader(shader_type) }; unsafe { @@ -32,12 +29,7 @@ impl Shader { } let error = create_whitespace_cstring(len as usize); unsafe { - gl::GetShaderInfoLog( - id, - len, - std::ptr::null_mut(), - error.as_ptr() as *mut GLchar, - ); + gl::GetShaderInfoLog(id, len, std::ptr::null_mut(), error.as_ptr() as *mut GLchar); } Err(error.to_string_lossy().into_owned()) } else { @@ -90,12 +82,7 @@ impl Program { } let error = create_whitespace_cstring(len as usize); unsafe { - gl::GetProgramInfoLog( - id, - len, - std::ptr::null_mut(), - error.as_ptr() as *mut GLchar, - ); + gl::GetProgramInfoLog(id, len, std::ptr::null_mut(), error.as_ptr() as *mut GLchar); } return Err(error.to_string_lossy().into_owned()); } @@ -119,9 +106,7 @@ impl Program { pub fn get_location(&self, name: &str) -> Result { let cname = CString::new(name).unwrap(); - let loc = unsafe { - gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) - }; + let loc = unsafe { gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) }; if loc != -1 { Ok(loc) @@ -359,12 +344,7 @@ impl Vertex { // and configure the vertex array unsafe { - Vertex::vertex_attrib_pointer( - f32x3::num_components(), - stride, - location, - offset, - ); + Vertex::vertex_attrib_pointer(f32x3::num_components(), stride, location, offset); } let location = location + 1; @@ -372,12 +352,7 @@ impl Vertex { // and configure the st array unsafe { - Vertex::vertex_attrib_pointer( - f32x2::num_components(), - stride, - location, - offset, - ); + Vertex::vertex_attrib_pointer(f32x2::num_components(), stride, location, offset); } } } @@ -541,7 +516,7 @@ impl FullscreenQuad { self.vertex_array.unbind(); } - pub fn update_texture(&self, data: &[V4f32]) { + pub fn update_texture(&self, data: &[Vec4]) { unsafe { gl::BindTexture(gl::TEXTURE_2D, self.texture_id); gl::TexSubImage2D( diff --git a/crates/optix/examples/ex04_mesh/src/main.rs b/crates/optix/examples/ex04_mesh/src/main.rs index 39072e60..76579f11 100644 --- a/crates/optix/examples/ex04_mesh/src/main.rs +++ b/crates/optix/examples/ex04_mesh/src/main.rs @@ -1,8 +1,8 @@ mod renderer; use renderer::Renderer; -mod vector; -pub use vector::*; +use glam::{vec4, Vec4}; + mod gl_util; use gl_util::FullscreenQuad; use glfw::{Action, Context, Key}; @@ -24,7 +24,7 @@ fn main() -> Result<(), Box> { .create_window( width, height, - "Example 03: in window", + "Example 04: mesh", glfw::WindowMode::Windowed, ) .expect("failed to create glfw window"); @@ -40,7 +40,7 @@ fn main() -> Result<(), Box> { let mut fsq = FullscreenQuad::new(width, height).unwrap(); - let mut image_data = vec![v4f32(0.0, 0.0, 0.0, 0.0); (width * height) as usize]; + let mut image_data = vec![vec4(0.0, 0.0, 0.0, 0.0); (width * height) as usize]; unsafe { gl::Viewport(0, 0, fb_width, fb_height); @@ -60,7 +60,7 @@ fn main() -> Result<(), Box> { renderer.resize(w, h)?; width = w; height = h; - image_data.resize((width * height) as usize, v4f32(0.0, 0.0, 0.0, 0.0)); + image_data.resize((width * height) as usize, vec4(0.0, 0.0, 0.0, 0.0)); } renderer.render()?; diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 6053d5b6..fc37ab70 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -1,10 +1,14 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::Device; -use cust::memory::{CopyDestination, DBox, DBuffer, DeviceCopy, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; use optix::{ + acceleration::{ + AccelBuildOptions, AccelEmitDesc, BuildFlags, BuildInput, BuildOperation, GeometryFlags, + TraversableHandle, + }, context::DeviceContext, module::{ CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, @@ -13,21 +17,22 @@ use optix::{ pipeline::{Pipeline, PipelineLinkOptions}, program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, + triangle_array::TriangleArray, }; -use glam::{vec3, IVec3, Vec3}; - -use crate::vector::V4f32; +use glam::{ivec2, vec3, IVec2, IVec3, Vec3, Vec4}; pub struct Renderer { launch_params: LaunchParams, - buf_launch_params: DBox, + buf_launch_params: DeviceBox, sbt: optix::sys::OptixShaderBindingTable, - buf_raygen: DBuffer, - buf_hitgroup: DBuffer, - buf_miss: DBuffer, + as_handle: TraversableHandle, + as_buffer: DeviceBuffer, + buf_raygen: DeviceBuffer, + buf_hitgroup: DeviceBuffer, + buf_miss: DeviceBuffer, pipeline: Pipeline, - color_buffer: DBuffer, + color_buffer: DeviceBuffer, ctx: DeviceContext, stream: Stream, cuda_context: CuContext, @@ -107,70 +112,56 @@ impl Renderer { &mut indices, ); - let buf_vertex = DBuffer::from_slice(&vertices)?; - let buf_indices = DBuffer::from_slice(&indices)?; + let buf_vertex = DeviceBuffer::from_slice(&vertices)?; + let buf_indices = DeviceBuffer::from_slice(&indices)?; - let geometry_flags = optix::GeometryFlags::None; - let triangle_input = optix::BuildInput::<_, ()>::TriangleArray( - optix::TriangleArray::new( - smallvec![buf_vertex.as_slice()], - std::slice::from_ref(&geometry_flags), - ) - .index_buffer(buf_indices.as_slice()), + let geometry_flags = GeometryFlags::None; + let triangle_input = BuildInput::<_, ()>::TriangleArray( + TriangleArray::new(&[&buf_vertex], std::slice::from_ref(&geometry_flags)) + .index_buffer(&buf_indices), ); // blas setup - let accel_options = optix::AccelBuildOptions::new( - optix::BuildFlags::ALLOW_COMPACTION, - optix::BuildOperation::Build, - ); + let accel_options = + AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); let build_inputs = vec![triangle_input]; let blas_buffer_sizes = ctx.accel_compute_memory_usage(&[accel_options], &build_inputs)?; // prepare compaction - let temp_buffer = optix::Buffer::uninitialized_with_align_in( - blas_buffer_sizes.temp_size_in_bytes, - optix::ACCEL_BUFFER_BYTE_ALIGNMENT, - FrameAlloc, - )?; + let mut temp_buffer = + unsafe { DeviceBuffer::::uninitialized(blas_buffer_sizes.temp_size_in_bytes)? }; - let output_buffer = optix::Buffer::uninitialized_with_align_in( - blas_buffer_sizes.output_size_in_bytes, - optix::ACCEL_BUFFER_BYTE_ALIGNMENT, - FrameAlloc, - )?; + let mut output_buffer = + unsafe { DeviceBuffer::::uninitialized(blas_buffer_sizes.output_size_in_bytes)? }; - let compacted_size_buffer = - optix::TypedBuffer::::uninitialized_in(1, FrameAlloc)?; + let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; - let mut properties = vec![optix::AccelEmitDesc::CompactedSize( - compacted_size_buffer.device_ptr(), + let mut properties = vec![AccelEmitDesc::CompactedSize( + compacted_size_buffer.as_device_ptr(), )]; let as_handle = ctx.accel_build( &stream, &[accel_options], &build_inputs, - &temp_buffer, - &output_buffer, + &mut temp_buffer, + &mut output_buffer, &mut properties, )?; - cu::Context::synchronize()?; + stream.synchronize()?; let mut compacted_size = 0usize; - compacted_size_buffer.download(std::slice::from_mut(&mut compacted_size))?; - let as_buffer = optix::Buffer::uninitialized_with_align_in( - compacted_size, - optix::ACCEL_BUFFER_BYTE_ALIGNMENT, - FrameAlloc, - )?; + compacted_size_buffer.copy_to(&mut compacted_size)?; + + let mut as_buffer = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; + + let as_handle = ctx.accel_compact(&stream, as_handle, &mut as_buffer)?; - let as_handle = ctx.accel_compact(&stream, as_handle, &as_buffer)?; - cu::Context::synchronize()?; + stream.synchronize()?; // create SBT let rec_raygen: Vec<_> = pg_raygen @@ -196,9 +187,9 @@ impl Renderer { }) .collect(); - let mut buf_raygen = DBuffer::from_slice(&rec_raygen)?; - let mut buf_miss = DBuffer::from_slice(&rec_miss)?; - let mut buf_hitgroup = DBuffer::from_slice(&rec_hitgroup)?; + let mut buf_raygen = DeviceBuffer::from_slice(&rec_raygen)?; + let mut buf_miss = DeviceBuffer::from_slice(&rec_miss)?; + let mut buf_hitgroup = DeviceBuffer::from_slice(&rec_hitgroup)?; let sbt = ShaderBindingTable::new(&mut buf_raygen) .miss(&mut buf_miss) @@ -225,24 +216,42 @@ impl Renderer { pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - let mut color_buffer = unsafe { DBuffer::uninitialized(width as usize * height as usize)? }; + let color_buffer = + unsafe { DeviceBuffer::uninitialized(width as usize * height as usize)? }; + + let from = vec3(-10.0, 2.0, -12.0); + let at = vec3(0.0, 0.0, 0.0); + let up = vec3(0.0, 1.0, 0.0); + + let cosfovy = 0.66f32; + let aspect = width as f32 / height as f32; + let direction = (at - from).normalize(); + let horizontal = cosfovy * aspect * direction.cross(up).normalize(); + let vertical = cosfovy * horizontal.cross(direction).normalize(); let launch_params = LaunchParams { - frame_id: 0, - color_buffer: color_buffer.as_device_ptr(), - fb_size: Point2i { - x: width as i32, - y: height as i32, + frame: Frame { + color_buffer: color_buffer.as_ptr(), + size: ivec2(width as i32, height as i32), }, + camera: RenderCamera { + position: from, + direction, + horizontal, + vertical, + }, + traversable: as_handle, }; - let buf_launch_params = DBox::new(&launch_params)?; + let buf_launch_params = DeviceBox::new(&launch_params)?; Ok(Renderer { ctx, cuda_context, stream, launch_params, + as_handle, + as_buffer, buf_launch_params, buf_raygen, buf_hitgroup, @@ -254,16 +263,15 @@ impl Renderer { } pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Box> { - self.color_buffer = unsafe { DBuffer::uninitialized((width * height) as usize)? }; - self.launch_params.fb_size.x = width as i32; - self.launch_params.fb_size.y = height as i32; - self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); + self.color_buffer = unsafe { DeviceBuffer::uninitialized((width * height) as usize)? }; + self.launch_params.frame.size.x = width as i32; + self.launch_params.frame.size.y = height as i32; + self.launch_params.frame.color_buffer = self.color_buffer.as_ptr(); Ok(()) } pub fn render(&mut self) -> Result<(), Box> { self.buf_launch_params.copy_from(&self.launch_params)?; - self.launch_params.frame_id += 1; unsafe { optix::launch( @@ -271,8 +279,8 @@ impl Renderer { &self.stream, &mut self.buf_launch_params, &self.sbt, - self.launch_params.fb_size.x as u32, - self.launch_params.fb_size.y as u32, + self.launch_params.frame.size.x as u32, + self.launch_params.frame.size.y as u32, 1, )?; } @@ -282,7 +290,7 @@ impl Renderer { Ok(()) } - pub fn download_pixels(&self, slice: &mut [V4f32]) -> Result<(), Box> { + pub fn download_pixels(&self, slice: &mut [Vec4]) -> Result<(), Box> { self.color_buffer.copy_to(slice)?; Ok(()) } @@ -290,17 +298,26 @@ impl Renderer { #[repr(C)] #[derive(Copy, Clone, DeviceCopy)] -struct Point2i { - pub x: i32, - pub y: i32, +pub struct Frame { + color_buffer: DevicePointer, + size: IVec2, +} + +#[repr(C)] +#[derive(Copy, Clone, DeviceCopy)] +pub struct RenderCamera { + position: Vec3, + direction: Vec3, + horizontal: Vec3, + vertical: Vec3, } #[repr(C)] #[derive(Copy, Clone, DeviceCopy)] -struct LaunchParams { - pub color_buffer: DevicePointer, - pub fb_size: Point2i, - pub frame_id: i32, +pub struct LaunchParams { + pub frame: Frame, + pub camera: RenderCamera, + pub traversable: TraversableHandle, } type RaygenRecord = SbtRecord; diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index da781956..ea0b59c1 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -3,7 +3,7 @@ use crate::{ optix_call, sys, triangle_array::BuildInputTriangleArray, }; use cust::{ - memory::{DBox, DBuffer, DSlice}, + memory::{DevicePointer, DeviceSlice}, DeviceCopy, }; type Result = std::result::Result; @@ -69,8 +69,8 @@ impl DeviceContext { stream: &cust::stream::Stream, accel_options: &[AccelBuildOptions], build_inputs: &[BuildInput], - temp_buffer: &mut DSlice, - output_buffer: &mut DSlice, + temp_buffer: &mut DeviceSlice, + output_buffer: &mut DeviceSlice, emitted_properties: &mut [AccelEmitDesc], ) -> Result { let mut traversable_handle = TraversableHandle { inner: 0 }; @@ -103,9 +103,9 @@ impl DeviceContext { accel_options.as_ptr() as *const _, build_sys.as_ptr(), build_sys.len() as u32, - temp_buffer.as_device_ptr().as_raw_mut() as u64, + temp_buffer.as_device_ptr(), temp_buffer.len(), - output_buffer.as_device_ptr().as_raw_mut() as u64, + output_buffer.as_device_ptr(), output_buffer.len(), &mut traversable_handle as *mut _ as *mut _, properties.as_ptr() as *const _, @@ -122,7 +122,7 @@ impl DeviceContext { &self, stream: &cust::stream::Stream, input_handle: TraversableHandle, - output_buffer: &mut DSlice, + output_buffer: &mut DeviceSlice, ) -> Result { let mut traversable_handle = TraversableHandle { inner: 0 }; unsafe { @@ -130,7 +130,7 @@ impl DeviceContext { self.raw, stream.as_inner(), input_handle.inner, - output_buffer.as_device_ptr().as_raw_mut() as u64, + output_buffer.as_device_ptr(), output_buffer.len(), &mut traversable_handle as *mut _ as *mut _, )) @@ -245,8 +245,8 @@ pub enum BuildInput< } pub enum AccelEmitDesc { - CompactedSize(DBox), - Aabbs(DBuffer), //< FIXME: need to handle OptixAabbBufferByteAlignment here + CompactedSize(DevicePointer), + Aabbs(DevicePointer), //< FIXME: need to handle OptixAabbBufferByteAlignment here } #[repr(C)] @@ -264,11 +264,11 @@ impl From<&mut AccelEmitDesc> for sys::OptixAccelEmitDesc { fn from(aed: &mut AccelEmitDesc) -> Self { match aed { AccelEmitDesc::CompactedSize(p) => Self { - result: p.as_device_ptr().as_raw() as u64, + result: p.as_raw(), type_: sys::OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE, }, AccelEmitDesc::Aabbs(p) => Self { - result: p.as_device_ptr().as_raw() as u64, + result: p.as_raw(), type_: sys::OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS, }, } diff --git a/crates/optix/src/impl_glam.rs b/crates/optix/src/impl_glam.rs new file mode 100644 index 00000000..20a7084f --- /dev/null +++ b/crates/optix/src/impl_glam.rs @@ -0,0 +1,9 @@ +use crate::triangle_array::{IndexTriple, IndicesFormat, Vertex, VertexFormat}; + +impl Vertex for glam::Vec3 { + const FORMAT: VertexFormat = VertexFormat::Float3; +} + +impl IndexTriple for glam::IVec3 { + const FORMAT: IndicesFormat = IndicesFormat::Int3; +} diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs index 0dd15f91..2a7ca4cf 100644 --- a/crates/optix/src/instance_array.rs +++ b/crates/optix/src/instance_array.rs @@ -2,7 +2,7 @@ use crate::{ acceleration::{GeometryFlags, TraversableHandle}, context::DeviceContext, error::Error, module::Module, optix_call, sys, }; -use cust::{memory::DSlice, DeviceCopy}; +use cust::{memory::DeviceSlice, DeviceCopy}; use std::ffi::c_void; #[repr(C, align(16))] @@ -74,11 +74,11 @@ impl Instance { } pub struct InstanceArray<'i> { - instances: &'i DSlice, + instances: &'i DeviceSlice, } impl<'i> InstanceArray<'i> { - pub fn new(instances: &'i DSlice) -> InstanceArray { + pub fn new(instances: &'i DeviceSlice) -> InstanceArray { InstanceArray { instances } @@ -100,12 +100,12 @@ impl<'i> BuildInputInstanceArray for InstanceArray<'i> { cfg_if::cfg_if! { if #[cfg(any(feature="optix72", feature="optix73"))] { sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr().as_raw() as u64, + instances: self.instances.as_device_ptr(), numInstances: self.instances.len() as u32, } } else { sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr().as_raw() as u64, + instances: self.instances.as_device_ptr(), numInstances: self.instances.len() as u32, aabbs: 0, numAabbs: 0, diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 5b8f9d6c..5b608edd 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -71,7 +71,7 @@ macro_rules! optix_call { pub unsafe fn launch( pipeline: &crate::pipeline::Pipeline, stream: &cust::stream::Stream, - buf_launch_params: &mut cust::memory::DBox

, + buf_launch_params: &mut cust::memory::DeviceBox

, sbt: &sys::OptixShaderBindingTable, width: u32, height: u32, @@ -88,3 +88,6 @@ pub unsafe fn launch( depth, ))?) } + +#[cfg(feature = "glam")] +mod impl_glam; diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 64ab9107..78a07506 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -1,6 +1,6 @@ use crate::{error::Error, optix_call, program_group::ProgramGroup, sys}; -use cust::memory::{DSlice, DeviceCopy}; +use cust::memory::{DeviceCopy, DeviceSlice}; use cust_raw::CUdeviceptr; type Result = std::result::Result; @@ -55,8 +55,8 @@ pub struct ShaderBindingTable { } impl ShaderBindingTable { - pub fn new(buf_raygen_record: &mut DSlice>) -> Self { - let raygen_record = buf_raygen_record.as_device_ptr().as_raw() as u64; + pub fn new(buf_raygen_record: &DeviceSlice>) -> Self { + let raygen_record = buf_raygen_record.as_device_ptr(); ShaderBindingTable { raygen_record, exception_record: 0, @@ -90,20 +90,20 @@ impl ShaderBindingTable { pub fn exception( mut self, - buf_exception_record: &mut DSlice>, + buf_exception_record: &DeviceSlice>, ) -> Self { if buf_exception_record.len() != 1 { panic!("SBT not passed single exception record",); } - self.exception_record = buf_exception_record.as_device_ptr().as_raw() as u64; + self.exception_record = buf_exception_record.as_device_ptr(); self } - pub fn miss(mut self, buf_miss_records: &mut DSlice>) -> Self { + pub fn miss(mut self, buf_miss_records: &DeviceSlice>) -> Self { if buf_miss_records.len() == 0 { panic!("SBT passed empty miss records"); } - self.miss_record_base = buf_miss_records.as_device_ptr().as_raw() as u64; + self.miss_record_base = buf_miss_records.as_device_ptr(); self.miss_record_stride_in_bytes = std::mem::size_of::>() as u32; self.miss_record_count = buf_miss_records.len() as u32; self @@ -111,12 +111,12 @@ impl ShaderBindingTable { pub fn hitgroup( mut self, - buf_hitgroup_records: &mut DSlice>, + buf_hitgroup_records: &DeviceSlice>, ) -> Self { if buf_hitgroup_records.len() == 0 { panic!("SBT passed empty hitgroup records"); } - self.hitgroup_record_base = buf_hitgroup_records.as_device_ptr().as_raw() as u64; + self.hitgroup_record_base = buf_hitgroup_records.as_device_ptr(); self.hitgroup_record_stride_in_bytes = std::mem::size_of::>() as u32; self.hitgroup_record_count = buf_hitgroup_records.len() as u32; self @@ -124,12 +124,12 @@ impl ShaderBindingTable { pub fn callables( mut self, - buf_callables_records: &mut DSlice>, + buf_callables_records: &DeviceSlice>, ) -> Self { if buf_callables_records.len() == 0 { panic!("SBT passed empty callables records"); } - self.callables_record_base = buf_callables_records.as_device_ptr().as_raw() as u64; + self.callables_record_base = buf_callables_records.as_device_ptr(); self.callables_record_stride_in_bytes = std::mem::size_of::>() as u32; self.callables_record_count = buf_callables_records.len() as u32; self diff --git a/crates/optix/src/triangle_array.rs b/crates/optix/src/triangle_array.rs index 2b4dbb51..8b44a93d 100644 --- a/crates/optix/src/triangle_array.rs +++ b/crates/optix/src/triangle_array.rs @@ -2,7 +2,8 @@ use crate::{ acceleration::GeometryFlags, context::DeviceContext, error::Error, module::Module, optix_call, sys, }; -use cust::{memory::DSlice, DeviceCopy}; +use cust::memory::DeviceSlice; +use cust_raw::CUdeviceptr; use std::ffi::c_void; #[repr(u32)] @@ -37,6 +38,32 @@ pub trait Vertex: cust::memory::DeviceCopy { const STRIDE: u32 = 0; } +#[cfg(feature = "half")] +impl Vertex for [half::f16; 2] { + const FORMAT: VertexFormat = VertexFormat::Half2; +} + +#[cfg(feature = "half")] +impl Vertex for [half::f16; 3] { + const FORMAT: VertexFormat = VertexFormat::Half3; +} + +impl Vertex for [f32; 2] { + const FORMAT: VertexFormat = VertexFormat::Float2; +} + +impl Vertex for [f32; 3] { + const FORMAT: VertexFormat = VertexFormat::Float3; +} + +impl Vertex for [i16; 3] { + const FORMAT: VertexFormat = VertexFormat::SNorm16; +} + +impl Vertex for [i32; 3] { + const FORMAT: VertexFormat = VertexFormat::SNorm32; +} + pub trait IndexTriple: cust::memory::DeviceCopy { const FORMAT: IndicesFormat; const STRIDE: u32 = 0; @@ -55,9 +82,9 @@ impl BuildInputTriangleArray for () { pub struct TriangleArray<'v, 'g, V: Vertex> { // We hold slices here to make sure the referenced device memory remains // valid for the lifetime of the build input - vertex_buffers: Vec<&'v DSlice>, + vertex_buffers: Vec<&'v DeviceSlice>, // This is the array of device pointers passed to optix functions - d_vertex_buffers: Vec<*const c_void>, + d_vertex_buffers: Vec, // per-object geometry flags geometry_flags: &'g [GeometryFlags], } @@ -86,11 +113,8 @@ impl<'v, 'vs, 'g, V: Vertex> BuildInputTriangleArray for TriangleArray<'v, 'g, V } impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { - pub fn new(vertex_buffers: &[&'v DSlice], geometry_flags: &'g [GeometryFlags]) -> Self { - let d_vertex_buffers: Vec<_> = vertex_buffers - .iter() - .map(|b| b.as_device_ptr().as_raw() as *const c_void) - .collect(); + pub fn new(vertex_buffers: &[&'v DeviceSlice], geometry_flags: &'g [GeometryFlags]) -> Self { + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); TriangleArray { vertex_buffers: vertex_buffers.to_vec(), @@ -101,7 +125,7 @@ impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { pub fn index_buffer<'i, I: IndexTriple>( self, - index_buffer: &'i DSlice, + index_buffer: &'i DeviceSlice, ) -> IndexedTriangleArray<'v, 'g, 'i, V, I> { IndexedTriangleArray { vertex_buffers: self.vertex_buffers, @@ -116,10 +140,10 @@ impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { pub struct IndexedTriangleArray<'v, 'g, 'i, V: Vertex, I: IndexTriple> { // We hold slices here to make sure the referenced device memory remains // valid for the lifetime of the build input - vertex_buffers: Vec<&'v DSlice>, + vertex_buffers: Vec<&'v DeviceSlice>, // This is the array of device pointers passed to optix functions - d_vertex_buffers: Vec<*const c_void>, - index_buffer: &'i DSlice, + d_vertex_buffers: Vec, + index_buffer: &'i DeviceSlice, // per-object geometry flags geometry_flags: &'g [GeometryFlags], } @@ -133,7 +157,7 @@ impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInputTriangleArray numVertices: self.vertex_buffers[0].len() as u32, vertexFormat: V::FORMAT as u32, vertexStrideInBytes: V::STRIDE, - indexBuffer: self.index_buffer.as_device_ptr().as_raw() as u64, + indexBuffer: self.index_buffer.as_device_ptr(), numIndexTriplets: self.index_buffer.len() as u32, indexFormat: I::FORMAT as u32, indexStrideInBytes: I::STRIDE, From 4607ca381915e32060cd0417008bb15bb5f77ff3 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Wed, 3 Nov 2021 22:11:02 +1300 Subject: [PATCH 044/100] Rework acceleration structure stuff Provide simple internally allocating API for Accel, but also allow creating one from raw parts to let user handle memory allocation. Original API kept as free functions and marked unsafe. Implement all build input types. Add mint support to cust and optix. --- crates/cust/Cargo.toml | 3 +- .../cust/src/memory/device/device_buffer.rs | 1 + crates/cust/src/memory/mod.rs | 9 + crates/optix/Cargo.toml | 4 +- .../optix/examples/ex04_mesh/src/renderer.rs | 55 +-- crates/optix/src/acceleration.rs | 317 ++++++++++-------- crates/optix/src/curve_array.rs | 132 ++++++++ crates/optix/src/custom_primitive_array.rs | 101 ++++++ crates/optix/src/instance_array.rs | 93 +++-- crates/optix/src/lib.rs | 20 ++ crates/optix/src/triangle_array.rs | 186 ++++++---- 11 files changed, 635 insertions(+), 286 deletions(-) create mode 100644 crates/optix/src/curve_array.rs create mode 100644 crates/optix/src/custom_primitive_array.rs diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index b410782d..608f9470 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -11,9 +11,10 @@ bitflags = "1.2" cust_derive = { path = "../cust_derive", version = "0.1" } vek = { version = "0.15.1", optional = true } glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } +mint = { version = "^0.5", optional = true } [features] -default-features = ["vek", "glam"] +default-features = ["vek", "glam", "mint"] [build-dependencies] find_cuda_helper = { path = "../find_cuda_helper", version = "0.1" } diff --git a/crates/cust/src/memory/device/device_buffer.rs b/crates/cust/src/memory/device/device_buffer.rs index 4d6af7cb..e5f7b239 100644 --- a/crates/cust/src/memory/device/device_buffer.rs +++ b/crates/cust/src/memory/device/device_buffer.rs @@ -227,6 +227,7 @@ impl DeviceBuffer { Ok(uninit) } } + impl Deref for DeviceBuffer { type Target = DeviceSlice; diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index a7865619..227e0864 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -320,3 +320,12 @@ impl_device_copy_vek! { impl_device_copy_glam! { glam::Vec2, glam::Vec3, glam::Vec4, glam::IVec2, glam::IVec3, glam::IVec4, } + +#[cfg(feature = "mint")] +impl_device_copy_glam! { + mint::Vector2, mint::Vector2, mint::Vector2, + mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, + mint::Vector4, mint::Vector4, mint::Vector4, + mint::ColumnMatrix2, mint::ColumnMatrix3, mint::ColumnMatrix4, mint::ColumnMatrix3x4, + mint::RowMatrix2, mint::RowMatrix3, mint::RowMatrix4, mint::RowMatrix3x4, +} diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index d3b0c12b..f45eb4ea 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -10,12 +10,14 @@ optix73 = [] default=["optix73", "glam"] [dependencies] -cust = { version = "0.1", path = "../cust", features=["glam"] } +cust = { version = "0.1", path = "../cust", features=["glam", "mint"] } cust_raw = { version = "0.11.2", path = "../cust_raw" } cfg-if = "1.0.0" bitflags = "1.3.2" glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } half = { version = "^1.8", optional = true } +memoffset = "0.6.4" +mint = "0.5.8" [build-dependencies] bindgen = "0.59" diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index fc37ab70..93728fed 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -6,8 +6,7 @@ use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; use optix::{ acceleration::{ - AccelBuildOptions, AccelEmitDesc, BuildFlags, BuildInput, BuildOperation, GeometryFlags, - TraversableHandle, + Accel, AccelBuildOptions, BuildFlags, BuildOperation, GeometryFlags, TraversableHandle, }, context::DeviceContext, module::{ @@ -17,7 +16,7 @@ use optix::{ pipeline::{Pipeline, PipelineLinkOptions}, program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, - triangle_array::TriangleArray, + triangle_array::IndexedTriangleArray, }; use glam::{ivec2, vec3, IVec2, IVec3, Vec3, Vec4}; @@ -26,8 +25,7 @@ pub struct Renderer { launch_params: LaunchParams, buf_launch_params: DeviceBox, sbt: optix::sys::OptixShaderBindingTable, - as_handle: TraversableHandle, - as_buffer: DeviceBuffer, + gas: Accel, buf_raygen: DeviceBuffer, buf_hitgroup: DeviceBuffer, buf_miss: DeviceBuffer, @@ -116,50 +114,18 @@ impl Renderer { let buf_indices = DeviceBuffer::from_slice(&indices)?; let geometry_flags = GeometryFlags::None; - let triangle_input = BuildInput::<_, ()>::TriangleArray( - TriangleArray::new(&[&buf_vertex], std::slice::from_ref(&geometry_flags)) - .index_buffer(&buf_indices), + let triangle_input = IndexedTriangleArray::new( + &[&buf_vertex], + &buf_indices, + std::slice::from_ref(&geometry_flags), ); - // blas setup let accel_options = AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); let build_inputs = vec![triangle_input]; - let blas_buffer_sizes = ctx.accel_compute_memory_usage(&[accel_options], &build_inputs)?; - - // prepare compaction - let mut temp_buffer = - unsafe { DeviceBuffer::::uninitialized(blas_buffer_sizes.temp_size_in_bytes)? }; - - let mut output_buffer = - unsafe { DeviceBuffer::::uninitialized(blas_buffer_sizes.output_size_in_bytes)? }; - - let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; - - let mut properties = vec![AccelEmitDesc::CompactedSize( - compacted_size_buffer.as_device_ptr(), - )]; - - let as_handle = ctx.accel_build( - &stream, - &[accel_options], - &build_inputs, - &mut temp_buffer, - &mut output_buffer, - &mut properties, - )?; - - stream.synchronize()?; - - let mut compacted_size = 0usize; - - compacted_size_buffer.copy_to(&mut compacted_size)?; - - let mut as_buffer = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; - - let as_handle = ctx.accel_compact(&stream, as_handle, &mut as_buffer)?; + let gas = Accel::build(&ctx, &stream, &[accel_options], &build_inputs, true)?; stream.synchronize()?; @@ -240,7 +206,7 @@ impl Renderer { horizontal, vertical, }, - traversable: as_handle, + traversable: gas.handle(), }; let buf_launch_params = DeviceBox::new(&launch_params)?; @@ -250,8 +216,7 @@ impl Renderer { cuda_context, stream, launch_params, - as_handle, - as_buffer, + gas, buf_launch_params, buf_raygen, buf_hitgroup, diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index ea0b59c1..aed23540 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -1,14 +1,84 @@ -use crate::{ - context::DeviceContext, error::Error, instance_array::BuildInputInstanceArray, module::Module, - optix_call, sys, triangle_array::BuildInputTriangleArray, -}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; use cust::{ - memory::{DevicePointer, DeviceSlice}, + memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer, DeviceSlice}, DeviceCopy, }; type Result = std::result::Result; +pub trait BuildInput { + fn to_sys(&self) -> sys::OptixBuildInput; +} + +pub struct Accel { + buf: DeviceBuffer, + hnd: TraversableHandle, +} + +impl Accel { + pub fn handle(&self) -> TraversableHandle { + self.hnd + } + + /// Build and compact the acceleration structure for the given inputs. + pub fn build( + ctx: &DeviceContext, + stream: &cust::stream::Stream, + accel_options: &[AccelBuildOptions], + build_inputs: &[I], + compact: bool, + ) -> Result { + let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; + let mut output_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; + + let mut temp_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; + + let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; + + let mut properties = vec![AccelEmitDesc::CompactedSize( + compacted_size_buffer.as_device_ptr(), + )]; + + let hnd = unsafe { + accel_build( + ctx, + stream, + accel_options, + build_inputs, + &mut temp_buffer, + &mut output_buffer, + &mut properties, + )? + }; + + if compact { + // FIXME (AL): async this + stream.synchronize()?; + + let mut compacted_size = 0usize; + compacted_size_buffer.copy_to(&mut compacted_size)?; + + let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; + + let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; + + Ok(Accel { buf, hnd }) + } else { + Ok(Accel { + buf: output_buffer, + hnd, + }) + } + } + + pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Accel { + Accel { buf, hnd } + } +} + /// Opaque handle to a traversable acceleration structure. +/// /// # Safety /// You should consider this handle to be a raw pointer, thus you can copy it /// and it provides no tracking of lifetime or ownership. You are responsible @@ -20,123 +90,83 @@ pub struct TraversableHandle { pub(crate) inner: u64, } -impl DeviceContext { - /// Computes the device memory required for temporary and output buffers - /// when building the acceleration structure. Use the returned sizes to - /// allocate enough memory to pass to `accel_build()` - pub fn accel_compute_memory_usage( - &self, - accel_options: &[AccelBuildOptions], - build_inputs: &[BuildInput], - ) -> Result { - let mut buffer_sizes = AccelBufferSizes::default(); - let build_sys: Vec<_> = build_inputs - .iter() - .map(|b| match b { - BuildInput::TriangleArray(bita) => sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, - input: sys::OptixBuildInputUnion { - triangle_array: std::mem::ManuallyDrop::new(bita.to_sys()), - }, - }, - BuildInput::InstanceArray(biia) => sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, - input: sys::OptixBuildInputUnion { - instance_array: std::mem::ManuallyDrop::new(biia.to_sys()), - }, - }, - _ => unimplemented!(), - }) - .collect(); - - unsafe { - Ok(optix_call!(optixAccelComputeMemoryUsage( - self.raw, - accel_options.as_ptr() as *const _, - build_sys.as_ptr(), - build_sys.len() as u32, - &mut buffer_sizes as *mut _ as *mut _, - )) - .map(|_| buffer_sizes)?) - } - } +/// Computes the device memory required for temporary and output buffers +/// when building the acceleration structure. Use the returned sizes to +/// allocate enough memory to pass to `accel_build()` +pub fn accel_compute_memory_usage( + ctx: &DeviceContext, + accel_options: &[AccelBuildOptions], + build_inputs: &[I], +) -> Result { + let mut buffer_sizes = AccelBufferSizes::default(); + let build_sys: Vec<_> = build_inputs.iter().map(|b| b.to_sys()).collect(); - /// Builds the acceleration structure. - /// `temp_buffer` and `output_buffer` must be at least as large as the sizes - /// returned by `accel_compute_memory_usage()` - pub fn accel_build( - &self, - stream: &cust::stream::Stream, - accel_options: &[AccelBuildOptions], - build_inputs: &[BuildInput], - temp_buffer: &mut DeviceSlice, - output_buffer: &mut DeviceSlice, - emitted_properties: &mut [AccelEmitDesc], - ) -> Result { - let mut traversable_handle = TraversableHandle { inner: 0 }; - let properties: Vec = - emitted_properties.iter_mut().map(|p| p.into()).collect(); - - let build_sys: Vec<_> = build_inputs - .iter() - .map(|b| match b { - BuildInput::TriangleArray(bita) => sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, - input: sys::OptixBuildInputUnion { - triangle_array: std::mem::ManuallyDrop::new(bita.to_sys()), - }, - }, - BuildInput::InstanceArray(biia) => sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, - input: sys::OptixBuildInputUnion { - instance_array: std::mem::ManuallyDrop::new(biia.to_sys()), - }, - }, - _ => unimplemented!(), - }) - .collect(); - - unsafe { - Ok(optix_call!(optixAccelBuild( - self.raw, - stream.as_inner(), - accel_options.as_ptr() as *const _, - build_sys.as_ptr(), - build_sys.len() as u32, - temp_buffer.as_device_ptr(), - temp_buffer.len(), - output_buffer.as_device_ptr(), - output_buffer.len(), - &mut traversable_handle as *mut _ as *mut _, - properties.as_ptr() as *const _, - properties.len() as u32, - )) - .map(|_| traversable_handle)?) - } + unsafe { + Ok(optix_call!(optixAccelComputeMemoryUsage( + ctx.raw, + accel_options.as_ptr() as *const _, + build_sys.as_ptr(), + build_sys.len() as u32, + &mut buffer_sizes as *mut _ as *mut _, + )) + .map(|_| buffer_sizes)?) } +} - /// Compacts the acceleration structure referenced by `input_handle`, - /// storing the result in `output_buffer` and returning a handle to the - /// newly compacted structure - pub fn accel_compact( - &self, - stream: &cust::stream::Stream, - input_handle: TraversableHandle, - output_buffer: &mut DeviceSlice, - ) -> Result { - let mut traversable_handle = TraversableHandle { inner: 0 }; - unsafe { - Ok(optix_call!(optixAccelCompact( - self.raw, - stream.as_inner(), - input_handle.inner, - output_buffer.as_device_ptr(), - output_buffer.len(), - &mut traversable_handle as *mut _ as *mut _, - )) - .map(|_| traversable_handle)?) - } - } +/// Builds the acceleration structure. +/// `temp_buffer` and `output_buffer` must be at least as large as the sizes +/// returned by `accel_compute_memory_usage()` +pub unsafe fn accel_build( + ctx: &DeviceContext, + stream: &cust::stream::Stream, + accel_options: &[AccelBuildOptions], + build_inputs: &[I], + temp_buffer: &mut DeviceSlice, + output_buffer: &mut DeviceSlice, + emitted_properties: &mut [AccelEmitDesc], +) -> Result { + let mut traversable_handle = TraversableHandle { inner: 0 }; + let properties: Vec = + emitted_properties.iter_mut().map(|p| p.into()).collect(); + + let build_sys: Vec<_> = build_inputs.iter().map(|b| b.to_sys()).collect(); + + Ok(optix_call!(optixAccelBuild( + ctx.raw, + stream.as_inner(), + accel_options.as_ptr() as *const _, + build_sys.as_ptr(), + build_sys.len() as u32, + temp_buffer.as_device_ptr(), + temp_buffer.len(), + output_buffer.as_device_ptr(), + output_buffer.len(), + &mut traversable_handle as *mut _ as *mut _, + properties.as_ptr() as *const _, + properties.len() as u32, + )) + .map(|_| traversable_handle)?) +} + +/// Compacts the acceleration structure referenced by `input_handle`, +/// storing the result in `output_buffer` and returning a handle to the +/// newly compacted structure +pub unsafe fn accel_compact( + ctx: &DeviceContext, + stream: &cust::stream::Stream, + input_handle: TraversableHandle, + output_buffer: &mut DeviceSlice, +) -> Result { + let mut traversable_handle = TraversableHandle { inner: 0 }; + Ok(optix_call!(optixAccelCompact( + ctx.raw, + stream.as_inner(), + input_handle.inner, + output_buffer.as_device_ptr(), + output_buffer.len(), + &mut traversable_handle as *mut _ as *mut _, + )) + .map(|_| traversable_handle)?) } bitflags::bitflags! { @@ -221,32 +251,9 @@ pub struct AccelBufferSizes { pub temp_update_size_in_bytes: usize, } -pub struct TriangleArrayDefault; -impl BuildInputTriangleArray for TriangleArrayDefault { - fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { - unreachable!() - } -} -pub struct InstanceArrayDefault; -impl BuildInputInstanceArray for InstanceArrayDefault { - fn to_sys(&self) -> sys::OptixBuildInputInstanceArray { - unreachable!() - } -} - -pub enum BuildInput< - T: BuildInputTriangleArray = TriangleArrayDefault, - I: BuildInputInstanceArray = InstanceArrayDefault, -> { - TriangleArray(T), - CurveArray, - CustomPrimitiveArray, - InstanceArray(I), -} - pub enum AccelEmitDesc { CompactedSize(DevicePointer), - Aabbs(DevicePointer), //< FIXME: need to handle OptixAabbBufferByteAlignment here + Aabbs(DevicePointer), } #[repr(C)] @@ -282,3 +289,27 @@ pub enum GeometryFlags { DisableAnyHit = sys::OptixGeometryFlags::DisableAnyHit as u32, RequireSingleAnyHitCall = sys::OptixGeometryFlags::RequireSingleAnyHitCall as u32, } + +impl From for sys::OptixGeometryFlags { + fn from(f: GeometryFlags) -> Self { + match f { + GeometryFlags::None => sys::OptixGeometryFlags::None, + GeometryFlags::DisableAnyHit => sys::OptixGeometryFlags::DisableAnyHit, + GeometryFlags::RequireSingleAnyHitCall => { + sys::OptixGeometryFlags::RequireSingleAnyHitCall + } + } + } +} + +impl From for u32 { + fn from(f: GeometryFlags) -> Self { + match f { + GeometryFlags::None => sys::OptixGeometryFlags::None as u32, + GeometryFlags::DisableAnyHit => sys::OptixGeometryFlags::DisableAnyHit as u32, + GeometryFlags::RequireSingleAnyHitCall => { + sys::OptixGeometryFlags::RequireSingleAnyHitCall as u32 + } + } + } +} diff --git a/crates/optix/src/curve_array.rs b/crates/optix/src/curve_array.rs new file mode 100644 index 00000000..607e595e --- /dev/null +++ b/crates/optix/src/curve_array.rs @@ -0,0 +1,132 @@ +use crate::{ + acceleration::{BuildInput, GeometryFlags}, + error::Error, + sys, +}; +use cust::memory::DeviceSlice; +use cust_raw::CUdeviceptr; +type Result = std::result::Result; + +use std::marker::PhantomData; + +pub struct CurveArray<'v, 'w, 'i> { + curve_type: CurveType, + num_primitives: u32, + vertex_buffers: PhantomData<&'v f32>, + num_vertices: u32, + d_vertex_buffers: Vec, + vertex_stride_in_bytes: u32, + width_buffers: PhantomData<&'w f32>, + num_width_buffers: u32, + d_width_buffers: Vec, + width_stride_in_bytes: u32, + index_buffer: &'i DeviceSlice, + index_stride_in_bytes: u32, + flags: GeometryFlags, + primitive_index_offset: u32, +} + +impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { + pub fn new( + curve_type: CurveType, + num_primitives: u32, + vertex_buffers: &[&'v DeviceSlice], + width_buffers: &[&'w DeviceSlice], + index_buffer: &'i DeviceSlice, + ) -> Result> { + // TODO (AL): Do some sanity checking on the values here + let num_vertices = vertex_buffers[0].len() as u32; + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + + let num_width_buffers = width_buffers.len() as u32; + let d_width_buffers: Vec<_> = width_buffers.iter().map(|b| b.as_device_ptr()).collect(); + + Ok(CurveArray { + curve_type, + num_primitives, + vertex_buffers: PhantomData, + num_vertices, + d_vertex_buffers, + vertex_stride_in_bytes: 0, + width_buffers: PhantomData, + num_width_buffers, + d_width_buffers, + width_stride_in_bytes: 0, + index_buffer, + index_stride_in_bytes: 0, + flags: GeometryFlags::None, + primitive_index_offset: 0, + }) + } + + pub fn vertex_stride(mut self, stride_in_bytes: u32) -> Self { + self.vertex_stride_in_bytes = stride_in_bytes; + self + } + + pub fn width_stride(mut self, stride_in_bytes: u32) -> Self { + self.vertex_stride_in_bytes = stride_in_bytes; + self + } + + pub fn index_stride(mut self, stride_in_bytes: u32) -> Self { + self.vertex_stride_in_bytes = stride_in_bytes; + self + } + + pub fn flags(mut self, flags: GeometryFlags) -> Self { + self.flags = flags; + self + } + + pub fn primitive_index_offset(mut self, offset: u32) -> Self { + self.primitive_index_offset = offset; + self + } +} + +impl<'v, 'w, 'i> BuildInput for CurveArray<'v, 'w, 'i> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CURVES, + input: sys::OptixBuildInputUnion { + curve_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputCurveArray { + curveType: self.curve_type.into(), + numPrimitives: self.num_primitives, + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const CUdeviceptr, + numVertices: self.num_vertices, + vertexStrideInBytes: self.vertex_stride_in_bytes, + widthBuffers: self.d_width_buffers.as_ptr() as *const CUdeviceptr, + widthStrideInBytes: self.width_stride_in_bytes, + normalBuffers: std::ptr::null(), + normalStrideInBytes: 0, + indexBuffer: self.index_buffer.as_device_ptr(), + indexStrideInBytes: self.index_stride_in_bytes, + flag: self.flags as u32, + primitiveIndexOffset: self.primitive_index_offset, + }), + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CurveType { + RoundLinear, + RoundQuadraticBSpline, + RoundCubicBSpline, +} + +impl From for sys::OptixPrimitiveType { + fn from(c: CurveType) -> Self { + match c { + CurveType::RoundLinear => sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR, + CurveType::RoundQuadraticBSpline => { + sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE + } + CurveType::RoundCubicBSpline => { + sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE + } + } + } +} diff --git a/crates/optix/src/custom_primitive_array.rs b/crates/optix/src/custom_primitive_array.rs new file mode 100644 index 00000000..4f0a4e29 --- /dev/null +++ b/crates/optix/src/custom_primitive_array.rs @@ -0,0 +1,101 @@ +use crate::{ + acceleration::{Aabb, BuildInput, GeometryFlags}, + error::Error, + sys, +}; +use cust::memory::{DeviceSlice, GpuBox}; +use cust_raw::CUdeviceptr; +type Result = std::result::Result; + +use std::marker::PhantomData; + +pub struct CustomPrimitiveArray<'a, 'g, 's> { + aabb_buffers: Vec, + aabb_buffers_marker: PhantomData<&'a Aabb>, + num_primitives: u32, + stride_in_bytes: u32, + flags: &'g [GeometryFlags], + num_sbt_records: u32, + sbt_index_offset_buffer: Option<&'s DeviceSlice>, + sbt_index_offset_stride_in_bytes: u32, + primitive_index_offset: u32, +} + +impl<'a, 'g, 's> CustomPrimitiveArray<'a, 'g, 's> { + pub fn new( + aabb_buffers: &[&'a DeviceSlice], + flags: &'g [GeometryFlags], + ) -> Result> { + let num_primitives = aabb_buffers.len() as u32; + let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr()).collect(); + + Ok(CustomPrimitiveArray { + aabb_buffers, + aabb_buffers_marker: PhantomData, + num_primitives, + stride_in_bytes: 0, + flags, + num_sbt_records: 1, + sbt_index_offset_buffer: None, + sbt_index_offset_stride_in_bytes: 0, + primitive_index_offset: 0, + }) + } + + pub fn stride(mut self, stride_in_bytes: u32) -> Self { + self.stride_in_bytes = stride_in_bytes; + self + } + + pub fn primitive_index_offset(mut self, offset: u32) -> Self { + self.primitive_index_offset = offset; + self + } + + pub fn num_sbt_records(mut self, num_sbt_records: u32) -> Self { + self.num_sbt_records = num_sbt_records; + self + } + + pub fn sbt_index_offset_buffer( + mut self, + sbt_index_offset_buffer: &'s DeviceSlice, + ) -> Self { + self.sbt_index_offset_buffer = Some(sbt_index_offset_buffer); + self + } + + pub fn sbt_index_offset_buffer_stride(mut self, stride_in_bytes: u32) -> Self { + self.sbt_index_offset_stride_in_bytes = stride_in_bytes; + self + } +} + +impl<'a, 'g, 's> BuildInput for CustomPrimitiveArray<'a, 'g, 's> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES, + input: sys::OptixBuildInputUnion { + custom_primitive_array: std::mem::ManuallyDrop::new( + sys::OptixBuildInputCustomPrimitiveArray { + aabbBuffers: self.aabb_buffers.as_ptr(), + numPrimitives: self.num_primitives, + strideInBytes: self.stride_in_bytes, + flags: self.flags.as_ptr() as *const u32, + numSbtRecords: self.num_sbt_records, + sbtIndexOffsetBuffer: if let Some(sbt_index_offset_buffer) = + self.sbt_index_offset_buffer + { + sbt_index_offset_buffer.as_device_ptr() + } else { + 0 + }, + sbtIndexOffsetSizeInBytes: 4, + sbtIndexOffsetStrideInBytes: self.sbt_index_offset_stride_in_bytes, + primitiveIndexOffset: self.primitive_index_offset, + }, + ), + }, + } + } +} diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs index 2a7ca4cf..75e396fc 100644 --- a/crates/optix/src/instance_array.rs +++ b/crates/optix/src/instance_array.rs @@ -1,14 +1,12 @@ -use crate::{ - acceleration::{GeometryFlags, TraversableHandle}, context::DeviceContext, error::Error, module::Module, optix_call, - sys, -}; +use crate::{acceleration::{BuildInput, TraversableHandle}, const_assert, const_assert_eq, sys}; use cust::{memory::DeviceSlice, DeviceCopy}; -use std::ffi::c_void; +use cust_raw::CUdeviceptr; +use mint::RowMatrix3x4; #[repr(C, align(16))] -#[derive(Debug, DeviceCopy, Copy, Clone)] +#[derive(Debug, Copy, Clone, DeviceCopy)] pub struct Instance { - transform: [f32; 12], + transform: RowMatrix3x4, instance_id: u32, sbt_offset: u32, visibility_mask: u32, @@ -17,6 +15,9 @@ pub struct Instance { pad: [u32; 2], } +const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); +const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + bitflags::bitflags! { #[derive(DeviceCopy)] @@ -37,7 +38,7 @@ impl Instance { transform: [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0], + 0.0, 0.0, 1.0, 0.0].into(), instance_id: 0, sbt_offset: 0, visibility_mask: 255, @@ -47,8 +48,8 @@ impl Instance { } } - pub fn transform(mut self, transform: [f32; 12]) -> Instance { - self.transform = transform; + pub fn transform>>(mut self, transform: T) -> Instance { + self.transform = transform.into(); self } @@ -79,36 +80,74 @@ pub struct InstanceArray<'i> { impl<'i> InstanceArray<'i> { pub fn new(instances: &'i DeviceSlice) -> InstanceArray { - InstanceArray { - instances + InstanceArray { instances } + } +} + +impl<'i> BuildInput for InstanceArray<'i> { + fn to_sys(&self) -> sys::OptixBuildInput { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + }) + } + } + } else { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + aabbs: 0, + numAabbs: 0, + }) + } + } + } } } } -pub trait BuildInputInstanceArray { - fn to_sys(&self) -> sys::OptixBuildInputInstanceArray; +pub struct InstancePointerArray<'i> { + instances: &'i DeviceSlice, } -impl BuildInputInstanceArray for () { - fn to_sys(&self) -> sys::OptixBuildInputInstanceArray { - unreachable!() +impl<'i> InstancePointerArray<'i> { + pub fn new(instances: &'i DeviceSlice) -> InstancePointerArray { + InstancePointerArray { instances } } } -impl<'i> BuildInputInstanceArray for InstanceArray<'i> { - fn to_sys(&self) -> sys::OptixBuildInputInstanceArray { +impl<'i> BuildInput for InstancePointerArray<'i> { + fn to_sys(&self) -> sys::OptixBuildInput { cfg_if::cfg_if! { if #[cfg(any(feature="optix72", feature="optix73"))] { - sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), - numInstances: self.instances.len() as u32, + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + }) + } } } else { - sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), - numInstances: self.instances.len() as u32, - aabbs: 0, - numAabbs: 0, + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + aabbs: 0, + numAabbs: 0, + }) + } } } } diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 5b608edd..1d8e8d6d 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -1,5 +1,7 @@ pub mod acceleration; pub mod context; +pub mod curve_array; +pub mod custom_primitive_array; pub mod denoiser; pub mod error; pub mod instance_array; @@ -91,3 +93,21 @@ pub unsafe fn launch( #[cfg(feature = "glam")] mod impl_glam; + +macro_rules! const_assert { + ($x:expr $(,)?) => { + #[allow(unknown_lints, clippy::eq_op)] + const _: [(); 0 - !{ + const ASSERT: bool = $x; + ASSERT + } as usize] = []; + }; +} +pub(crate) use const_assert; + +macro_rules! const_assert_eq { + ($x:expr, $y:expr $(,)?) => { + const_assert!($x == $y); + }; +} +pub(crate) use const_assert_eq; diff --git a/crates/optix/src/triangle_array.rs b/crates/optix/src/triangle_array.rs index 8b44a93d..c1113003 100644 --- a/crates/optix/src/triangle_array.rs +++ b/crates/optix/src/triangle_array.rs @@ -1,10 +1,11 @@ +use std::marker::PhantomData; + use crate::{ - acceleration::GeometryFlags, context::DeviceContext, error::Error, module::Module, optix_call, + acceleration::{BuildInput, GeometryFlags}, sys, }; use cust::memory::DeviceSlice; use cust_raw::CUdeviceptr; -use std::ffi::c_void; #[repr(u32)] #[derive(Copy, Clone, PartialEq)] @@ -48,6 +49,16 @@ impl Vertex for [half::f16; 3] { const FORMAT: VertexFormat = VertexFormat::Half3; } +#[cfg(feature = "half")] +impl Vertex for mint::Vector2 { + const FORMAT: VertexFormat = VertexFormat::Half2; +} + +#[cfg(feature = "half")] +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::Half3; +} + impl Vertex for [f32; 2] { const FORMAT: VertexFormat = VertexFormat::Float2; } @@ -64,111 +75,148 @@ impl Vertex for [i32; 3] { const FORMAT: VertexFormat = VertexFormat::SNorm32; } +impl Vertex for mint::Vector2 { + const FORMAT: VertexFormat = VertexFormat::Float2; +} + +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::Float3; +} + +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::SNorm16; +} + +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::SNorm32; +} + pub trait IndexTriple: cust::memory::DeviceCopy { const FORMAT: IndicesFormat; const STRIDE: u32 = 0; } -pub trait BuildInputTriangleArray { - fn to_sys(&self) -> sys::OptixBuildInputTriangleArray; +impl IndexTriple for [u16; 3] { + const FORMAT: IndicesFormat = IndicesFormat::Short3; } -impl BuildInputTriangleArray for () { - fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { - unreachable!() - } +impl IndexTriple for [u32; 3] { + const FORMAT: IndicesFormat = IndicesFormat::Int3; +} + +impl IndexTriple for mint::Vector3 { + const FORMAT: IndicesFormat = IndicesFormat::Short3; +} + +impl IndexTriple for mint::Vector3 { + const FORMAT: IndicesFormat = IndicesFormat::Int3; } pub struct TriangleArray<'v, 'g, V: Vertex> { // We hold slices here to make sure the referenced device memory remains // valid for the lifetime of the build input - vertex_buffers: Vec<&'v DeviceSlice>, - // This is the array of device pointers passed to optix functions + vertex_buffers: PhantomData<&'v V>, + num_vertices: u32, d_vertex_buffers: Vec, - // per-object geometry flags + // per-sbt-record geometry flags geometry_flags: &'g [GeometryFlags], } -impl<'v, 'vs, 'g, V: Vertex> BuildInputTriangleArray for TriangleArray<'v, 'g, V> { - fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { - sys::OptixBuildInputTriangleArray { - vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, - numVertices: self.vertex_buffers[0].len() as u32, - vertexFormat: V::FORMAT as u32, - vertexStrideInBytes: V::STRIDE, - indexBuffer: 0, - numIndexTriplets: 0, - indexFormat: sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE, - indexStrideInBytes: 0, - preTransform: 0, - flags: self.geometry_flags.as_ptr() as *const _, - numSbtRecords: 1, - sbtIndexOffsetBuffer: 0, - sbtIndexOffsetSizeInBytes: 0, - sbtIndexOffsetStrideInBytes: 0, - primitiveIndexOffset: 0, - transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, - } - } -} - impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { pub fn new(vertex_buffers: &[&'v DeviceSlice], geometry_flags: &'g [GeometryFlags]) -> Self { + // TODO (AL): do some sanity checking on the slice lengths here + let num_vertices = vertex_buffers[0].len() as u32; let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); - TriangleArray { - vertex_buffers: vertex_buffers.to_vec(), + vertex_buffers: PhantomData, + num_vertices, d_vertex_buffers, geometry_flags, } } +} - pub fn index_buffer<'i, I: IndexTriple>( - self, - index_buffer: &'i DeviceSlice, - ) -> IndexedTriangleArray<'v, 'g, 'i, V, I> { - IndexedTriangleArray { - vertex_buffers: self.vertex_buffers, - d_vertex_buffers: self.d_vertex_buffers, - index_buffer, - geometry_flags: self.geometry_flags, +impl<'v, 'g, V: Vertex> BuildInput for TriangleArray<'v, 'g, V> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: sys::OptixBuildInputUnion { + triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, + numVertices: self.num_vertices, + vertexFormat: V::FORMAT as u32, + vertexStrideInBytes: V::STRIDE, + indexBuffer: 0, + numIndexTriplets: 0, + indexFormat: 0, + indexStrideInBytes: 0, + preTransform: 0, + flags: self.geometry_flags.as_ptr() as *const _, + numSbtRecords: 1, + sbtIndexOffsetBuffer: 0, + sbtIndexOffsetSizeInBytes: 0, + sbtIndexOffsetStrideInBytes: 0, + primitiveIndexOffset: 0, + transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + }), + }, } } } -#[doc(hidden)] pub struct IndexedTriangleArray<'v, 'g, 'i, V: Vertex, I: IndexTriple> { // We hold slices here to make sure the referenced device memory remains // valid for the lifetime of the build input - vertex_buffers: Vec<&'v DeviceSlice>, - // This is the array of device pointers passed to optix functions + vertex_buffers: PhantomData<&'v V>, + num_vertices: u32, d_vertex_buffers: Vec, index_buffer: &'i DeviceSlice, // per-object geometry flags geometry_flags: &'g [GeometryFlags], } -impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInputTriangleArray - for IndexedTriangleArray<'v, 'i, 'g, V, I> -{ - fn to_sys(&self) -> sys::OptixBuildInputTriangleArray { - sys::OptixBuildInputTriangleArray { - vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, - numVertices: self.vertex_buffers[0].len() as u32, - vertexFormat: V::FORMAT as u32, - vertexStrideInBytes: V::STRIDE, - indexBuffer: self.index_buffer.as_device_ptr(), - numIndexTriplets: self.index_buffer.len() as u32, - indexFormat: I::FORMAT as u32, - indexStrideInBytes: I::STRIDE, - preTransform: 0, - flags: self.geometry_flags.as_ptr() as *const _, - numSbtRecords: 1, - sbtIndexOffsetBuffer: 0, - sbtIndexOffsetSizeInBytes: 0, - sbtIndexOffsetStrideInBytes: 0, - primitiveIndexOffset: 0, - transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, +impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'g, 'i, V, I> { + pub fn new( + vertex_buffers: &[&'v DeviceSlice], + index_buffer: &'i DeviceSlice, + geometry_flags: &'g [GeometryFlags], + ) -> Self { + let num_vertices = vertex_buffers[0].len() as u32; + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + IndexedTriangleArray { + vertex_buffers: PhantomData, + num_vertices, + d_vertex_buffers, + geometry_flags, + index_buffer, + } + } +} + +impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, 'g, 'i, V, I> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: sys::OptixBuildInputUnion { + triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, + numVertices: self.num_vertices, + vertexFormat: V::FORMAT as u32, + vertexStrideInBytes: V::STRIDE, + indexBuffer: self.index_buffer.as_device_ptr(), + numIndexTriplets: self.index_buffer.len() as u32, + indexFormat: I::FORMAT as u32, + indexStrideInBytes: I::STRIDE, + preTransform: 0, + flags: self.geometry_flags.as_ptr() as *const _, + numSbtRecords: 1, + sbtIndexOffsetBuffer: 0, + sbtIndexOffsetSizeInBytes: 0, + sbtIndexOffsetStrideInBytes: 0, + primitiveIndexOffset: 0, + transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + }), + }, } } } From 76dc52458278cd75fa4172bf6373cd38b5b6385e Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 08:38:05 +1300 Subject: [PATCH 045/100] add lifetime bound on Instance to referenced Accel --- crates/cust/src/memory/mod.rs | 2 ++ crates/optix/src/instance_array.rs | 34 +++++++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 227e0864..223a2e98 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -248,6 +248,8 @@ impl_device_copy!( unsafe impl DeviceCopy for Option {} unsafe impl DeviceCopy for Result {} unsafe impl DeviceCopy for PhantomData {} +// Allow DeviceCopy for lifetime constraint markers +unsafe impl DeviceCopy for PhantomData<&i32> {} unsafe impl DeviceCopy for Wrapping {} unsafe impl DeviceCopy for [T; N] {} unsafe impl DeviceCopy for () {} diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs index 75e396fc..27236d2a 100644 --- a/crates/optix/src/instance_array.rs +++ b/crates/optix/src/instance_array.rs @@ -1,11 +1,13 @@ -use crate::{acceleration::{BuildInput, TraversableHandle}, const_assert, const_assert_eq, sys}; +use std::marker::PhantomData; + +use crate::{acceleration::{Accel, BuildInput, TraversableHandle}, const_assert, const_assert_eq, sys}; use cust::{memory::DeviceSlice, DeviceCopy}; use cust_raw::CUdeviceptr; use mint::RowMatrix3x4; #[repr(C, align(16))] #[derive(Debug, Copy, Clone, DeviceCopy)] -pub struct Instance { +pub struct Instance<'a> { transform: RowMatrix3x4, instance_id: u32, sbt_offset: u32, @@ -13,6 +15,7 @@ pub struct Instance { flags: InstanceFlags, traversable_handle: TraversableHandle, pad: [u32; 2], + accel: PhantomData<&'a i32>, } const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); @@ -31,8 +34,8 @@ bitflags::bitflags! { } } -impl Instance { - pub fn new(traversable_handle: TraversableHandle) -> Instance { +impl<'a> Instance<'a> { + pub fn new(accel: &'a Accel) -> Instance<'a> { #[cfg_attr(rustfmt, rustfmt_skip)] Instance { transform: [ @@ -43,48 +46,49 @@ impl Instance { sbt_offset: 0, visibility_mask: 255, flags: InstanceFlags::NONE, - traversable_handle, + traversable_handle: accel.handle(), pad: [0; 2], + accel: PhantomData, } } - pub fn transform>>(mut self, transform: T) -> Instance { + pub fn transform>>(mut self, transform: T) -> Instance<'a> { self.transform = transform.into(); self } - pub fn instance_id(mut self, instance_id: u32) -> Instance { + pub fn instance_id(mut self, instance_id: u32) -> Instance<'a> { self.instance_id = instance_id; self } - pub fn sbt_offset(mut self, sbt_offset: u32) -> Instance { + pub fn sbt_offset(mut self, sbt_offset: u32) -> Instance<'a> { self.sbt_offset = sbt_offset; self } - pub fn visibility_mask(mut self, visibility_mask: u8) -> Instance { + pub fn visibility_mask(mut self, visibility_mask: u8) -> Instance<'a> { self.visibility_mask = visibility_mask as u32; self } - pub fn flags(mut self, flags: InstanceFlags) -> Instance { + pub fn flags(mut self, flags: InstanceFlags) -> Instance<'a> { self.flags = flags; self } } -pub struct InstanceArray<'i> { - instances: &'i DeviceSlice, +pub struct InstanceArray<'i, 'a> { + instances: &'i DeviceSlice>, } -impl<'i> InstanceArray<'i> { - pub fn new(instances: &'i DeviceSlice) -> InstanceArray { +impl<'i, 'a> InstanceArray<'i, 'a> { + pub fn new(instances: &'i DeviceSlice>) -> InstanceArray<'i, 'a> { InstanceArray { instances } } } -impl<'i> BuildInput for InstanceArray<'i> { +impl<'i, 'a> BuildInput for InstanceArray<'i, 'a> { fn to_sys(&self) -> sys::OptixBuildInput { cfg_if::cfg_if! { if #[cfg(any(feature="optix72", feature="optix73"))] { From 6cf07ff3af8e1a5e94aca84d1dcc537f8e82678a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 08:42:02 +1300 Subject: [PATCH 046/100] Have DeviceCopy impl for lifetime markers use null type --- crates/cust/src/memory/mod.rs | 2 +- crates/optix/src/instance_array.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 223a2e98..ee14ab21 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -249,7 +249,7 @@ unsafe impl DeviceCopy for Option {} unsafe impl DeviceCopy for Result {} unsafe impl DeviceCopy for PhantomData {} // Allow DeviceCopy for lifetime constraint markers -unsafe impl DeviceCopy for PhantomData<&i32> {} +unsafe impl DeviceCopy for PhantomData<&()> {} unsafe impl DeviceCopy for Wrapping {} unsafe impl DeviceCopy for [T; N] {} unsafe impl DeviceCopy for () {} diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs index 27236d2a..5f183c8a 100644 --- a/crates/optix/src/instance_array.rs +++ b/crates/optix/src/instance_array.rs @@ -15,7 +15,7 @@ pub struct Instance<'a> { flags: InstanceFlags, traversable_handle: TraversableHandle, pad: [u32; 2], - accel: PhantomData<&'a i32>, + accel: PhantomData<&'a ()>, } const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); From e163a25b2ea3db57fcc35d9b2944c1a63c538d5a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 08:42:11 +1300 Subject: [PATCH 047/100] Add unsaafe from_handle ctor --- crates/optix/src/instance_array.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs index 5f183c8a..e2283d5f 100644 --- a/crates/optix/src/instance_array.rs +++ b/crates/optix/src/instance_array.rs @@ -52,6 +52,23 @@ impl<'a> Instance<'a> { } } + pub unsafe fn from_handle(traversable_handle: TraversableHandle) -> Instance<'static> { + #[cfg_attr(rustfmt, rustfmt_skip)] + Instance { + transform: [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0].into(), + instance_id: 0, + sbt_offset: 0, + visibility_mask: 255, + flags: InstanceFlags::NONE, + traversable_handle, + pad: [0; 2], + accel: PhantomData, + } + } + pub fn transform>>(mut self, transform: T) -> Instance<'a> { self.transform = transform.into(); self From 0c13305126e64f3f553e3995e3dd595352ad570b Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 09:07:44 +1300 Subject: [PATCH 048/100] Add update for DynamicAccel --- crates/optix/src/acceleration.rs | 151 ++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index aed23540..28025b9e 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -5,6 +5,8 @@ use cust::{ }; type Result = std::result::Result; +use std::ops::Deref; + pub trait BuildInput { fn to_sys(&self) -> sys::OptixBuildInput; } @@ -53,7 +55,6 @@ impl Accel { }; if compact { - // FIXME (AL): async this stream.synchronize()?; let mut compacted_size = 0usize; @@ -77,6 +78,154 @@ impl Accel { } } +/// Building an acceleration structure can be computationally costly. Applications +/// may choose to update an existing acceleration structure using modified vertex +/// data or bounding boxes. Updating an existing acceleration structure is generally +/// much faster than rebuilding. However, the quality of the acceleration structure +/// may degrade if the data changes too much with an update, for example, through +/// explosions or other chaotic transitions—even if for only parts of the mesh. +/// The degraded acceleration structure may result in slower traversal performance +/// as compared to an acceleration structure built from scratch from the modified +/// input data. +pub struct DynamicAccel { + accel: Accel, +} + +impl Deref for DynamicAccel { + type Target = Accel; + + fn deref(&self) -> &Self::Target { + &self.accel + } +} + +impl DynamicAccel { + /// Build and compact the acceleration structure for the given inputs. + /// + /// This forces the ALLOW_UPDATE flag for the build flags to make sure the + /// resulting accel can be updated + pub fn build( + ctx: &DeviceContext, + stream: &cust::stream::Stream, + accel_options: &mut [AccelBuildOptions], + build_inputs: &[I], + compact: bool, + ) -> Result { + // Force ALLOW_UPDATE + for opt in accel_options.iter_mut() { + opt.build_flags |= BuildFlags::ALLOW_UPDATE; + opt.operation = BuildOperation::Build; + } + + let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; + let mut output_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; + + let mut temp_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; + + let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; + + let mut properties = vec![AccelEmitDesc::CompactedSize( + compacted_size_buffer.as_device_ptr(), + )]; + + let hnd = unsafe { + accel_build( + ctx, + stream, + accel_options, + build_inputs, + &mut temp_buffer, + &mut output_buffer, + &mut properties, + )? + }; + + if compact { + stream.synchronize()?; + + let mut compacted_size = 0usize; + compacted_size_buffer.copy_to(&mut compacted_size)?; + + let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; + + let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; + + Ok(Accel { buf, hnd }) + } else { + Ok(Accel { + buf: output_buffer, + hnd, + }) + } + } + + /// Update the acceleration structure + /// + /// # Safety + /// Note that the `build_inputs` here must match the topology of those that + /// were supplied to [`build`](DynamicAccel::build) or the behaviour is + /// undefined. + /// + /// This forces the build operation to Update. + pub unsafe fn update( + ctx: &DeviceContext, + stream: &cust::stream::Stream, + accel_options: &mut [AccelBuildOptions], + build_inputs: &[I], + compact: bool, + ) -> Result { + for opt in accel_options.iter_mut() { + opt.build_flags |= BuildFlags::ALLOW_UPDATE; + opt.operation = BuildOperation::Update; + } + + let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; + let mut output_buffer = DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)?; + + let mut temp_buffer = DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)?; + + let mut compacted_size_buffer = DeviceBox::::uninitialized()?; + + let mut properties = vec![AccelEmitDesc::CompactedSize( + compacted_size_buffer.as_device_ptr(), + )]; + + let hnd = accel_build( + ctx, + stream, + accel_options, + build_inputs, + &mut temp_buffer, + &mut output_buffer, + &mut properties, + )?; + + if compact { + stream.synchronize()?; + + let mut compacted_size = 0usize; + compacted_size_buffer.copy_to(&mut compacted_size)?; + + let mut buf = DeviceBuffer::::uninitialized(compacted_size)?; + + let hnd = accel_compact(ctx, stream, hnd, &mut buf)?; + + Ok(Accel { buf, hnd }) + } else { + Ok(Accel { + buf: output_buffer, + hnd, + }) + } + } + + pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Accel { + Accel { buf, hnd } + } +} + /// Opaque handle to a traversable acceleration structure. /// /// # Safety From cbd06a8c5ff54ac5b422646812db6f6f92c82e99 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 09:47:00 +1300 Subject: [PATCH 049/100] Hash build inputs to ensure update is sound --- crates/optix/src/acceleration.rs | 94 ++++++++++++---------- crates/optix/src/curve_array.rs | 20 ++++- crates/optix/src/custom_primitive_array.rs | 18 +++++ crates/optix/src/error.rs | 3 + crates/optix/src/instance_array.rs | 14 ++++ crates/optix/src/triangle_array.rs | 18 +++++ 6 files changed, 125 insertions(+), 42 deletions(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index 28025b9e..a0cc21ff 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -6,8 +6,12 @@ use cust::{ type Result = std::result::Result; use std::ops::Deref; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; -pub trait BuildInput { +pub trait BuildInput: std::hash::Hash { fn to_sys(&self) -> sys::OptixBuildInput; } @@ -89,6 +93,7 @@ impl Accel { /// input data. pub struct DynamicAccel { accel: Accel, + hash: u64, } impl Deref for DynamicAccel { @@ -110,7 +115,7 @@ impl DynamicAccel { accel_options: &mut [AccelBuildOptions], build_inputs: &[I], compact: bool, - ) -> Result { + ) -> Result { // Force ALLOW_UPDATE for opt in accel_options.iter_mut() { opt.build_flags |= BuildFlags::ALLOW_UPDATE; @@ -142,6 +147,10 @@ impl DynamicAccel { )? }; + let mut hasher = DefaultHasher::new(); + build_inputs.hash(&mut hasher); + let hash = hasher.finish(); + if compact { stream.synchronize()?; @@ -152,73 +161,76 @@ impl DynamicAccel { let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; - Ok(Accel { buf, hnd }) + Ok(DynamicAccel { + accel: Accel { buf, hnd }, + hash, + }) } else { - Ok(Accel { - buf: output_buffer, - hnd, + Ok(DynamicAccel { + accel: Accel { + buf: output_buffer, + hnd, + }, + hash, }) } } /// Update the acceleration structure /// - /// # Safety /// Note that the `build_inputs` here must match the topology of those that - /// were supplied to [`build`](DynamicAccel::build) or the behaviour is - /// undefined. + /// were supplied to [`build`](DynamicAccel::build) /// /// This forces the build operation to Update. - pub unsafe fn update( + pub fn update( + &mut self, ctx: &DeviceContext, stream: &cust::stream::Stream, accel_options: &mut [AccelBuildOptions], build_inputs: &[I], - compact: bool, - ) -> Result { + ) -> Result<()> { for opt in accel_options.iter_mut() { opt.build_flags |= BuildFlags::ALLOW_UPDATE; opt.operation = BuildOperation::Update; } + let mut hasher = DefaultHasher::new(); + build_inputs.hash(&mut hasher); + let hash = hasher.finish(); + + if hash != self.hash {} + let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; - let mut output_buffer = DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)?; + let mut output_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; - let mut temp_buffer = DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)?; + let mut temp_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; - let mut compacted_size_buffer = DeviceBox::::uninitialized()?; + let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; let mut properties = vec![AccelEmitDesc::CompactedSize( compacted_size_buffer.as_device_ptr(), )]; - let hnd = accel_build( - ctx, - stream, - accel_options, - build_inputs, - &mut temp_buffer, - &mut output_buffer, - &mut properties, - )?; - - if compact { - stream.synchronize()?; - - let mut compacted_size = 0usize; - compacted_size_buffer.copy_to(&mut compacted_size)?; - - let mut buf = DeviceBuffer::::uninitialized(compacted_size)?; + let hnd = unsafe { + accel_build( + ctx, + stream, + accel_options, + build_inputs, + &mut temp_buffer, + &mut output_buffer, + &mut properties, + )? + }; - let hnd = accel_compact(ctx, stream, hnd, &mut buf)?; + self.accel = Accel { + buf: output_buffer, + hnd, + }; - Ok(Accel { buf, hnd }) - } else { - Ok(Accel { - buf: output_buffer, - hnd, - }) - } + Ok(()) } pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Accel { @@ -432,7 +444,7 @@ impl From<&mut AccelEmitDesc> for sys::OptixAccelEmitDesc { } #[repr(u32)] -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Hash)] pub enum GeometryFlags { None = sys::OptixGeometryFlags::None as u32, DisableAnyHit = sys::OptixGeometryFlags::DisableAnyHit as u32, diff --git a/crates/optix/src/curve_array.rs b/crates/optix/src/curve_array.rs index 607e595e..97efdf59 100644 --- a/crates/optix/src/curve_array.rs +++ b/crates/optix/src/curve_array.rs @@ -7,6 +7,7 @@ use cust::memory::DeviceSlice; use cust_raw::CUdeviceptr; type Result = std::result::Result; +use std::hash::Hash; use std::marker::PhantomData; pub struct CurveArray<'v, 'w, 'i> { @@ -26,6 +27,23 @@ pub struct CurveArray<'v, 'w, 'i> { primitive_index_offset: u32, } +impl<'v, 'w, 'i> Hash for CurveArray<'v, 'w, 'i> { + fn hash(&self, state: &mut H) { + self.curve_type.hash(state); + state.write_u32(self.num_primitives); + state.write_u32(self.num_vertices); + state.write_usize(self.d_vertex_buffers.len()); + state.write_u32(self.vertex_stride_in_bytes); + state.write_u32(self.num_vertices); + state.write_usize(self.d_width_buffers.len()); + state.write_u32(self.width_stride_in_bytes); + state.write_usize(self.index_buffer.len()); + state.write_u32(self.index_stride_in_bytes); + self.flags.hash(state); + state.write_u32(self.primitive_index_offset); + } +} + impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { pub fn new( curve_type: CurveType, @@ -110,7 +128,7 @@ impl<'v, 'w, 'i> BuildInput for CurveArray<'v, 'w, 'i> { } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Hash)] pub enum CurveType { RoundLinear, RoundQuadraticBSpline, diff --git a/crates/optix/src/custom_primitive_array.rs b/crates/optix/src/custom_primitive_array.rs index 4f0a4e29..8ecb08b6 100644 --- a/crates/optix/src/custom_primitive_array.rs +++ b/crates/optix/src/custom_primitive_array.rs @@ -7,6 +7,7 @@ use cust::memory::{DeviceSlice, GpuBox}; use cust_raw::CUdeviceptr; type Result = std::result::Result; +use std::hash::Hash; use std::marker::PhantomData; pub struct CustomPrimitiveArray<'a, 'g, 's> { @@ -21,6 +22,23 @@ pub struct CustomPrimitiveArray<'a, 'g, 's> { primitive_index_offset: u32, } +impl<'a, 'g, 's> Hash for CustomPrimitiveArray<'a, 'g, 's> { + fn hash(&self, state: &mut H) { + state.write_usize(self.aabb_buffers.len()); + state.write_u32(self.num_primitives); + state.write_u32(self.stride_in_bytes); + self.flags.hash(state); + state.write_u32(self.num_sbt_records); + if let Some(b) = self.sbt_index_offset_buffer { + state.write_usize(b.len()); + } else { + state.write_usize(0); + } + state.write_u32(self.sbt_index_offset_stride_in_bytes); + state.write_u32(self.primitive_index_offset); + } +} + impl<'a, 'g, 's> CustomPrimitiveArray<'a, 'g, 's> { pub fn new( aabb_buffers: &[&'a DeviceSlice], diff --git a/crates/optix/src/error.rs b/crates/optix/src/error.rs index 38d9609c..83e84bc0 100644 --- a/crates/optix/src/error.rs +++ b/crates/optix/src/error.rs @@ -195,6 +195,7 @@ pub enum Error { ModuleCreation { source: OptixError, log: String }, ProgramGroupCreation { source: OptixError, log: String }, PipelineCreation { source: OptixError, log: String }, + AccelUpdateMismatch, } impl From for Error { @@ -217,6 +218,7 @@ impl std::error::Error for Error { Self::ModuleCreation { source, .. } => Some(source), Self::ProgramGroupCreation { source, .. } => Some(source), Self::PipelineCreation { source, .. } => Some(source), + Self::AccelUpdateMismatch => None, } } } @@ -231,6 +233,7 @@ impl Display for Error { write!(f, "Program group creation error: {}", log) } Self::PipelineCreation { log, .. } => write!(f, "Pipeline creation error: {}", log), + Self::AccelUpdateMismatch => write!(f, "Build inputs passed to DynamicAccel::update do not match the structure of those used to build the accel"), } } } diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs index e2283d5f..52bbadad 100644 --- a/crates/optix/src/instance_array.rs +++ b/crates/optix/src/instance_array.rs @@ -1,4 +1,5 @@ use std::marker::PhantomData; +use std::hash::Hash; use crate::{acceleration::{Accel, BuildInput, TraversableHandle}, const_assert, const_assert_eq, sys}; use cust::{memory::DeviceSlice, DeviceCopy}; @@ -105,6 +106,12 @@ impl<'i, 'a> InstanceArray<'i, 'a> { } } +impl<'i, 'a> Hash for InstanceArray<'i, 'a> { + fn hash(&self, state: &mut H) { + state.write_usize(self.instances.len()); + } +} + impl<'i, 'a> BuildInput for InstanceArray<'i, 'a> { fn to_sys(&self) -> sys::OptixBuildInput { cfg_if::cfg_if! { @@ -145,6 +152,13 @@ impl<'i> InstancePointerArray<'i> { } } +impl<'i> Hash for InstancePointerArray<'i> { + fn hash(&self, state: &mut H) { + state.write_usize(self.instances.len()); + } +} + + impl<'i> BuildInput for InstancePointerArray<'i> { fn to_sys(&self) -> sys::OptixBuildInput { cfg_if::cfg_if! { diff --git a/crates/optix/src/triangle_array.rs b/crates/optix/src/triangle_array.rs index c1113003..10b8307d 100644 --- a/crates/optix/src/triangle_array.rs +++ b/crates/optix/src/triangle_array.rs @@ -1,3 +1,4 @@ +use std::hash::Hash; use std::marker::PhantomData; use crate::{ @@ -136,6 +137,14 @@ impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { } } +impl<'v, 'g, V: Vertex> Hash for TriangleArray<'v, 'g, V> { + fn hash(&self, state: &mut H) { + state.write_u32(self.num_vertices); + state.write_usize(self.d_vertex_buffers.len()); + self.geometry_flags.hash(state); + } +} + impl<'v, 'g, V: Vertex> BuildInput for TriangleArray<'v, 'g, V> { fn to_sys(&self) -> sys::OptixBuildInput { sys::OptixBuildInput { @@ -193,6 +202,15 @@ impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'g, 'i, V, } } +impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> Hash for IndexedTriangleArray<'v, 'g, 'i, V, I> { + fn hash(&self, state: &mut H) { + state.write_u32(self.num_vertices); + state.write_usize(self.d_vertex_buffers.len()); + self.geometry_flags.hash(state); + state.write_usize(self.index_buffer.len()); + } +} + impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, 'g, 'i, V, I> { fn to_sys(&self) -> sys::OptixBuildInput { sys::OptixBuildInput { From afd5ef5465aa1f405620e3b937ef1b7af6aa8a88 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 14:58:37 +1300 Subject: [PATCH 050/100] Add relocation info --- crates/optix/src/acceleration.rs | 56 +++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index a0cc21ff..a4fd2d2b 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -21,11 +21,13 @@ pub struct Accel { } impl Accel { + /// Get the [`TraversableHandle`] that represents this accel. pub fn handle(&self) -> TraversableHandle { self.hnd } - /// Build and compact the acceleration structure for the given inputs. + /// Build and (optionally) compact the acceleration structure for the given + /// `build_inputs`. pub fn build( ctx: &DeviceContext, stream: &cust::stream::Stream, @@ -80,6 +82,30 @@ impl Accel { pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Accel { Accel { buf, hnd } } + + /// Obtain opaque relocation information for this accel in the given [`DeviceContext`]. + /// + /// The location information may be passed to + /// [`check_relocation_compatibility()`](Accel::check_relocation_compatibility) to + /// determine if this acceleration structure can be relocated to a different device's + /// memory space. + /// + /// When used with [`relocate`](Accel::relocate) it provides the data necessary + /// for doing the relocation. + /// + /// If this acceleration structure is copied multiple times, the same + /// [`AccelRelocationInfo`] can also be used on all copies. + pub fn get_relocation_info(&self, ctx: &DeviceContext) -> Result { + let mut inner = sys::OptixAccelRelocationInfo::default(); + unsafe { + Ok(optix_call!(optixAccelGetRelocationInfo( + ctx.raw, + self.hnd.inner, + &mut inner + )) + .map(|_| AccelRelocationInfo { inner })?) + } + } } /// Building an acceleration structure can be computationally costly. Applications @@ -178,10 +204,11 @@ impl DynamicAccel { /// Update the acceleration structure /// - /// Note that the `build_inputs` here must match the topology of those that - /// were supplied to [`build`](DynamicAccel::build) - /// /// This forces the build operation to Update. + /// + /// # Errors + /// * [`Error::AccelUpdateMismatch`] - if the provided `build_inputs` do + /// not match the structure of those provided to [`build()`](DynamicAccel::build) pub fn update( &mut self, ctx: &DeviceContext, @@ -198,7 +225,9 @@ impl DynamicAccel { build_inputs.hash(&mut hasher); let hash = hasher.finish(); - if hash != self.hash {} + if hash != self.hash { + return Err(Error::AccelUpdateMismatch); + } let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; let mut output_buffer = @@ -404,6 +433,23 @@ impl AccelBuildOptions { } } +/// Opaque relocation information for an [`Accel`] in a given [`DeviceContext`]. +/// +/// The location information may be passed to +/// [`check_relocation_compatibility()`](Accel::check_relocation_compatibility) to +/// determine if the associated acceleration structure can be relocated to a different device's +/// memory space. +/// +/// When used with [`relocate`](Accel::relocate) it provides the data necessary +/// for doing the relocation. +/// +/// If the acceleration structure is copied multiple times, the same +/// [`AccelRelocationInfo`] can also be used on all copies. +#[repr(transparent)] +pub struct AccelRelocationInfo { + inner: sys::OptixAccelRelocationInfo, +} + #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct AccelBufferSizes { From e4feec44dbc9d68e2bdddee0f327f30b4a1ac25d Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 14:58:58 +1300 Subject: [PATCH 051/100] Add remaning DeviceContext methods --- crates/optix/src/context.rs | 136 +++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 8 deletions(-) diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 19b5cd1e..5ae6027b 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -2,7 +2,7 @@ use std::os::raw::{c_char, c_uint}; use std::{ - ffi::{c_void, CStr}, + ffi::{c_void, CStr, CString}, mem::MaybeUninit, ptr, }; @@ -15,7 +15,7 @@ type Result = std::result::Result; /// A certain property belonging to an OptiX device. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum OptixDeviceProperty { +pub enum DeviceProperty { /// The maximum value that can be given to the OptiX pipeline's max trace depth. MaxTraceDepth, /// The maximum value that can be given to the OptiX pipeline's stack size method's max traversable @@ -37,11 +37,11 @@ pub enum OptixDeviceProperty { MaxSbtOffset, } -impl OptixDeviceProperty { +impl DeviceProperty { // we could repr this the same as the sys version, but for better compatability // and safety in the future, we just match. pub fn to_raw(self) -> sys::OptixDeviceProperty::Type { - use OptixDeviceProperty::*; + use DeviceProperty::*; match self { MaxTraceDepth => sys::OptixDeviceProperty::OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH, MaxTraversableGraphDepth => sys::OptixDeviceProperty::OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH, @@ -73,7 +73,7 @@ impl Drop for DeviceContext { impl DeviceContext { // TODO(RDambrosio016): expose device context options - /// Creates a new [`OptixContext`] from a cust CUDA context. + /// Creates a new [`DeviceContext`] from a cust CUDA context. pub fn new(cuda_ctx: &impl ContextHandle) -> Result { let mut raw = MaybeUninit::uninit(); unsafe { @@ -88,7 +88,47 @@ impl DeviceContext { } } - pub fn get_property(&self, property: OptixDeviceProperty) -> Result { + /// Returns the low and high water marks, respectively, for disk cache garbage collection. + /// If the cache has been disabled by setting the environment variable + /// OPTIX_CACHE_MAXSIZE=0, this function will return 0 for the low and high water marks. + pub fn get_cache_database_sizes(&self) -> Result<(usize, usize)> { + let mut low = 0; + let mut high = 0; + unsafe { + Ok(optix_call!(optixDeviceContextGetCacheDatabaseSizes( + self.raw, &mut low, &mut high, + )) + .map(|_| (low as usize, high as usize))?) + } + } + + /// Indicated whether the disk cache is enabled + pub fn get_cache_enabled(&self) -> Result { + let result = 0; + unsafe { + Ok( + optix_call!(optixDeviceContextGetCacheEnabled(self.raw, &mut result,)) + .map(|_| result != 0)?, + ) + } + } + + /// Returns the location of the disk cache. If the cache has been disabled + /// by setting the environment variable OPTIX_CACHE_MAXSIZE=0, this function will return an empy string. + pub fn get_cache_location(&self) -> Result { + let mut buf = [0i8; 1024]; + unsafe { + Ok(optix_call!(optixDeviceContextGetCacheLocation( + self.raw, + buf.as_ptr(), + buf.len() as u64 + )) + .map(|_| CStr::from_ptr(buf.as_ptr()).to_string_lossy().to_string())?) + } + } + + /// Query properties of this context. + pub fn get_property(&self, property: DeviceProperty) -> Result { let raw_prop = property.to_raw(); unsafe { let mut value = 0u32; @@ -102,8 +142,83 @@ impl DeviceContext { } } - pub fn as_raw(&self) -> sys::OptixDeviceContext { - self.raw + /// Sets the low and high water marks for disk cache garbage collection. + /// + /// Garbage collection is triggered when a new entry is written to the cache + /// and the current cache data size plus the size of the cache entry that is + /// about to be inserted exceeds the high water mark. Garbage collection proceeds + /// until the size reaches the low water mark. Garbage collection will always + /// free enough space to insert the new entry without exceeding the low water + /// mark. Setting either limit to zero will disable garbage collection. An + /// error will be returned if both limits are non-zero and the high water mark + /// is smaller than the low water mark. + /// + /// Note that garbage collection is performed only on writes to the disk cache. + /// No garbage collection is triggered on disk cache initialization or immediately + /// when calling this function, but on subsequent inserting of data into the + /// database. + /// + /// If the size of a compiled module exceeds the value configured for the high + /// water mark and garbage collection is enabled, the module will not be added + /// to the cache and a warning will be added to the log. + /// + /// The high water mark can be overridden with the environment variable + /// OPTIX_CACHE_MAXSIZE. The environment variable takes precedence over the + /// function parameters. The low water mark will be set to half the value of + /// OPTIX_CACHE_MAXSIZE. Setting OPTIX_CACHE_MAXSIZE to 0 will disable the + /// disk cache, but will not alter the contents of the cache. Negative and + /// non-integer values will be ignored. + pub fn set_cache_database_sizes(&mut self, low: usize, high: usize) -> Result<()> { + unsafe { + Ok(optix_call!(optixDeviceContextSetCacheDatabaseSizes( + self.raw, low, high, + ))?) + } + } + + /// Enables or disables the disk cache. + /// + /// If caching was previously disabled, enabling it will attempt to initialize + /// the disk cache database using the currently configured cache location. + /// An error will be returned if initialization fails. + /// + /// Note that no in-memory cache is used, so no caching behavior will be observed + /// if the disk cache is disabled. + /// + /// The cache can be disabled by setting the environment variable + /// OPTIX_CACHE_MAXSIZE=0. The environment variable takes precedence over this + /// setting. See optixDeviceContextSetCacheDatabaseSizes for additional information. + /// + /// Note that the disk cache can be disabled by the environment variable, but + /// it cannot be enabled via the environment if it is disabled via the API. + pub fn set_cache_enabled(&mut self, enable: bool) -> Result<()> { + unsafe { + Ok(optix_call!(optixDeviceContextSetCacheEnabled( + self.raw, enable + ))?) + } + } + + /// Sets the location of the disk cache. + /// + /// The location is specified by a directory. This directory should not be used for other purposes and will be created if it does not exist. An error will be returned if is not possible to create the disk cache at the specified location for any reason (e.g., the path is invalid or the directory is not writable). Caching will be disabled if the disk cache cannot be initialized in the new location. If caching is disabled, no error will be returned until caching is enabled. If the disk cache is located on a network file share, behavior is undefined. + /// + /// The location of the disk cache can be overridden with the environment variable OPTIX_CACHE_PATH. The environment variable takes precedence over this setting. + /// + /// The default location depends on the operating system: + /// + /// * Windows: `LOCALAPPDATA%\NVIDIA\OptixCache` + /// * Linux: `/var/tmp/OptixCache_` (or `/tmp/OptixCache_` + /// if the first choice is not usable), the underscore and username suffix are omitted if the username cannot be obtained + /// * MacOS X: `/Library/Application Support/NVIDIA/OptixCache` + pub fn set_cache_location(&mut self, location: &str) -> Result<()> { + let location = CString::new(location)?; + unsafe { + Ok(optix_call!(optixDeviceContextSetCacheLocation( + self.raw, + location.as_ptr() + ))?) + } } /// Sets the current log callback method. @@ -134,6 +249,11 @@ impl DeviceContext { ))?) } } + + /// Get the FFI context representation + pub fn as_raw(&self) -> sys::OptixDeviceContext { + self.raw + } } type LogCallback = extern "C" fn(c_uint, *const c_char, *const c_char, *mut c_void); From da817c17ab81dd4b505f9bffe765babab1914b76 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 14:59:13 +1300 Subject: [PATCH 052/100] Correct docstrings --- crates/optix/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 1d8e8d6d..16240819 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -61,15 +61,15 @@ macro_rules! optix_call { }}; } -/// Launch the given [Pipeline] on the given [Stream](cu::Stream). +/// Launch the given [`Pipeline`](pipeline::Pipeline) on the given [`Stream`](cust::stream::Stream). /// /// # Safety /// You must ensure that: -/// - Any [ProgramGroup]s referenced by the [Pipeline] are still alive -/// - Any [DevicePtr]s contained in `buf_launch_params` point to valid, +/// - Any [`ProgramGroup`](program_group::ProgramGroup)s referenced by the [`Pipeline`](pipeline::Pipeline) are still alive +/// - Any device memory referenced in `buf_launch_params` point to valid, /// correctly aligned memory -/// - Any [SbtRecord]s and associated data referenced by the -/// [OptixShaderBindingTable] are alive and valid +/// - Any [`SbtRecord`](shader_binding_table::SbtRecord)s and associated data referenced by the +/// [`ShaderBindingTable`](shader_binding_table::ShaderBindingTable) are alive and valid pub unsafe fn launch( pipeline: &crate::pipeline::Pipeline, stream: &cust::stream::Stream, From 8d1839f46c9948af8b1968be89f700f2b9ba8147 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 14:59:31 +1300 Subject: [PATCH 053/100] Add doc comments --- crates/optix/src/module.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs index 3716c916..a1fe0cfb 100644 --- a/crates/optix/src/module.rs +++ b/crates/optix/src/module.rs @@ -244,6 +244,10 @@ impl Module { } } + /// Returns a module containing the intersection program for the built-in + /// primitive type specified by the builtinISOptions. This module must be used + /// as the moduleIS for the OptixProgramGroupHitgroup in any SBT record for + /// that primitive type. pub fn builtin_is_module_get( ctx: &mut DeviceContext, module_compile_options: &ModuleCompileOptions, From 718bec7b6f3b945096bbb91bf95d90d8c3de94cc Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:07:18 +1300 Subject: [PATCH 054/100] Add a prelude --- crates/optix/src/prelude.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 crates/optix/src/prelude.rs diff --git a/crates/optix/src/prelude.rs b/crates/optix/src/prelude.rs new file mode 100644 index 00000000..d8cdf6f8 --- /dev/null +++ b/crates/optix/src/prelude.rs @@ -0,0 +1,19 @@ +pub use crate::{ + acceleration::{ + Aabb, Accel, AccelBufferSizes, AccelBuildOptions, AccelEmitDesc, AccelRelocationInfo, + BuildFlags, BuildOperation, DynamicAccel, GeometryFlags, + }, + context::{DeviceContext, DeviceProperty}, + curve_array::{CurveArray, CurveType}, + custom_primitive_array::CustomPrimitiveArray, + init, + instance_array::{Instance, InstanceArray, InstanceFlags, InstancePointerArray}, + launch, + module::{ + Module, ModuleCompileOptions, PipelineCompileOptions, PrimitiveType, PrimitiveTypeFlags, + }, + pipeline::{Pipeline, PipelineLinkOptions}, + program_group::{ProgramGroup, ProgramGroupDesc, ProgramGroupModule, StackSizes}, + shader_binding_table::{SbtRecord, ShaderBindingTable}, + triangle_array::{IndexTriple, IndexedTriangleArray, TriangleArray, Vertex}, +}; From 4d36fcad986482cf888ce93c70e952cfe364a949 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:08:09 +1300 Subject: [PATCH 055/100] Own the geometry flags array --- crates/optix/src/custom_primitive_array.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/optix/src/custom_primitive_array.rs b/crates/optix/src/custom_primitive_array.rs index 8ecb08b6..da126b7b 100644 --- a/crates/optix/src/custom_primitive_array.rs +++ b/crates/optix/src/custom_primitive_array.rs @@ -10,19 +10,19 @@ type Result = std::result::Result; use std::hash::Hash; use std::marker::PhantomData; -pub struct CustomPrimitiveArray<'a, 'g, 's> { +pub struct CustomPrimitiveArray<'a, 's> { aabb_buffers: Vec, aabb_buffers_marker: PhantomData<&'a Aabb>, num_primitives: u32, stride_in_bytes: u32, - flags: &'g [GeometryFlags], + flags: Vec, num_sbt_records: u32, sbt_index_offset_buffer: Option<&'s DeviceSlice>, sbt_index_offset_stride_in_bytes: u32, primitive_index_offset: u32, } -impl<'a, 'g, 's> Hash for CustomPrimitiveArray<'a, 'g, 's> { +impl<'a, 'g, 's> Hash for CustomPrimitiveArray<'a, 's> { fn hash(&self, state: &mut H) { state.write_usize(self.aabb_buffers.len()); state.write_u32(self.num_primitives); @@ -39,11 +39,11 @@ impl<'a, 'g, 's> Hash for CustomPrimitiveArray<'a, 'g, 's> { } } -impl<'a, 'g, 's> CustomPrimitiveArray<'a, 'g, 's> { +impl<'a, 's> CustomPrimitiveArray<'a, 's> { pub fn new( aabb_buffers: &[&'a DeviceSlice], - flags: &'g [GeometryFlags], - ) -> Result> { + flags: &[GeometryFlags], + ) -> Result> { let num_primitives = aabb_buffers.len() as u32; let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr()).collect(); @@ -52,7 +52,7 @@ impl<'a, 'g, 's> CustomPrimitiveArray<'a, 'g, 's> { aabb_buffers_marker: PhantomData, num_primitives, stride_in_bytes: 0, - flags, + flags: flags.to_vec(), num_sbt_records: 1, sbt_index_offset_buffer: None, sbt_index_offset_stride_in_bytes: 0, @@ -89,7 +89,7 @@ impl<'a, 'g, 's> CustomPrimitiveArray<'a, 'g, 's> { } } -impl<'a, 'g, 's> BuildInput for CustomPrimitiveArray<'a, 'g, 's> { +impl<'a, 's> BuildInput for CustomPrimitiveArray<'a, 's> { fn to_sys(&self) -> sys::OptixBuildInput { sys::OptixBuildInput { type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES, From 56ce8f05405721ad4010838694d3be200075374a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:08:25 +1300 Subject: [PATCH 056/100] Add prelude --- crates/optix/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 16240819..1a62bf56 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -7,6 +7,7 @@ pub mod error; pub mod instance_array; pub mod module; pub mod pipeline; +pub mod prelude; pub mod program_group; pub mod shader_binding_table; pub mod sys; From 100759ff93519afa7c426af5af9fcf4b61caf04a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:08:54 +1300 Subject: [PATCH 057/100] own the geometry flags array and add support for pre_transform --- crates/optix/src/triangle_array.rs | 54 +++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/crates/optix/src/triangle_array.rs b/crates/optix/src/triangle_array.rs index 10b8307d..f12ee78b 100644 --- a/crates/optix/src/triangle_array.rs +++ b/crates/optix/src/triangle_array.rs @@ -5,7 +5,7 @@ use crate::{ acceleration::{BuildInput, GeometryFlags}, sys, }; -use cust::memory::DeviceSlice; +use cust::memory::{DevicePointer, DeviceSlice}; use cust_raw::CUdeviceptr; #[repr(u32)] @@ -121,6 +121,7 @@ pub struct TriangleArray<'v, 'g, V: Vertex> { d_vertex_buffers: Vec, // per-sbt-record geometry flags geometry_flags: &'g [GeometryFlags], + pre_transform: Option>, } impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { @@ -133,8 +134,14 @@ impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { num_vertices, d_vertex_buffers, geometry_flags, + pre_transform: None, } } + + pub fn pre_transform(mut self, pre_transform: DevicePointer<[f32; 12]>) -> Self { + self.pre_transform = Some(pre_transform); + self + } } impl<'v, 'g, V: Vertex> Hash for TriangleArray<'v, 'g, V> { @@ -159,21 +166,29 @@ impl<'v, 'g, V: Vertex> BuildInput for TriangleArray<'v, 'g, V> { numIndexTriplets: 0, indexFormat: 0, indexStrideInBytes: 0, - preTransform: 0, flags: self.geometry_flags.as_ptr() as *const _, numSbtRecords: 1, sbtIndexOffsetBuffer: 0, sbtIndexOffsetSizeInBytes: 0, sbtIndexOffsetStrideInBytes: 0, primitiveIndexOffset: 0, - transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + preTransform: if let Some(t) = self.pre_transform { + t.as_raw() + } else { + 0 + }, + transformFormat: if self.pre_transform.is_some() { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 + } else { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE + }, }), }, } } } -pub struct IndexedTriangleArray<'v, 'g, 'i, V: Vertex, I: IndexTriple> { +pub struct IndexedTriangleArray<'v, 'i, V: Vertex, I: IndexTriple> { // We hold slices here to make sure the referenced device memory remains // valid for the lifetime of the build input vertex_buffers: PhantomData<&'v V>, @@ -181,14 +196,15 @@ pub struct IndexedTriangleArray<'v, 'g, 'i, V: Vertex, I: IndexTriple> { d_vertex_buffers: Vec, index_buffer: &'i DeviceSlice, // per-object geometry flags - geometry_flags: &'g [GeometryFlags], + geometry_flags: Vec, + pre_transform: Option>, } -impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'g, 'i, V, I> { +impl<'v, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'i, V, I> { pub fn new( vertex_buffers: &[&'v DeviceSlice], index_buffer: &'i DeviceSlice, - geometry_flags: &'g [GeometryFlags], + geometry_flags: &[GeometryFlags], ) -> Self { let num_vertices = vertex_buffers[0].len() as u32; let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); @@ -196,13 +212,19 @@ impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'g, 'i, V, vertex_buffers: PhantomData, num_vertices, d_vertex_buffers, - geometry_flags, + geometry_flags: geometry_flags.to_vec(), index_buffer, + pre_transform: None, } } + + pub fn pre_transform(mut self, pre_transform: DevicePointer<[f32; 12]>) -> Self { + self.pre_transform = Some(pre_transform); + self + } } -impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> Hash for IndexedTriangleArray<'v, 'g, 'i, V, I> { +impl<'v, 'i, V: Vertex, I: IndexTriple> Hash for IndexedTriangleArray<'v, 'i, V, I> { fn hash(&self, state: &mut H) { state.write_u32(self.num_vertices); state.write_usize(self.d_vertex_buffers.len()); @@ -211,7 +233,7 @@ impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> Hash for IndexedTriangleArray<'v, 'g } } -impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, 'g, 'i, V, I> { +impl<'v, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, 'i, V, I> { fn to_sys(&self) -> sys::OptixBuildInput { sys::OptixBuildInput { type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, @@ -225,14 +247,22 @@ impl<'v, 'g, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray< numIndexTriplets: self.index_buffer.len() as u32, indexFormat: I::FORMAT as u32, indexStrideInBytes: I::STRIDE, - preTransform: 0, flags: self.geometry_flags.as_ptr() as *const _, numSbtRecords: 1, sbtIndexOffsetBuffer: 0, sbtIndexOffsetSizeInBytes: 0, sbtIndexOffsetStrideInBytes: 0, primitiveIndexOffset: 0, - transformFormat: sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + preTransform: if let Some(t) = self.pre_transform { + t.as_raw() + } else { + 0 + }, + transformFormat: if self.pre_transform.is_some() { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 + } else { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE + }, }), }, } From 6052229de248a79b5b17299b114d2581ce46591c Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:09:25 +1300 Subject: [PATCH 058/100] Fill out context and add some module docs --- crates/optix/src/context.rs | 186 ++++++++++++++++++++++++++++++++++-- crates/optix/src/error.rs | 3 + 2 files changed, 182 insertions(+), 7 deletions(-) diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 5ae6027b..5f86ee09 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -1,4 +1,163 @@ //! OptiX Device Context handling. +//! +//! A context is created by [`DeviceContext::new()`] and is used to manage a single +//! GPU. The NVIDIA OptiX 7 device context is created by specifying the CUDA +//! context associated with the device. +//! +//! ``` +//! # fn doit() -> Result<(), Box> { +//! use optix::prelude as ox; +//! use cust::prelude as cu; +//! +//! // Initialize cuda and optix +//! cust::init(cu::CudaFlags::empty())?; +//! ox::init()?; +//! +//! // Create a cuda context for the first device +//! let device = cu::Device::get_device(0)?; +//! let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +//! cu::ContextFlags::MAP_HOST, device)?; +//! +//! // Create optix device context +//! let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +//! +//! # Ok(()) +//! # } +//! ``` +//! A small set of context properties exist for determining sizes and limits. These +//! are queried using [`DeviceContext::get_property()`]. Such properties include +//! maximum trace depth, maximum traversable graph depth, maximum primitives per +//! build input, and maximum number of instances per acceleration structure. +//! +//! The context may retain ownership of any GPU resources necessary to launch the +//! ray tracing kernels. Some API objects will retain host memory. These are defined +//! with create/destroy patterns in the API. The context's `Drop` impl will clean +//! up any host or device resources associated with the context. If any other API +//! objects associated with this context still exist when the context is destroyed, +//! they are also destroyed. +//! +//! An application may combine any mixture of supported GPUs as long as the data +//! transfer and synchronization is handled appropriately. Some applications may +//! choose to simplify multi-GPU handling by restricting the variety of these blends, +//! for example, by mixing only GPUs of the same streaming multiprocessor version +//! to simplify data sharing. +//! +//! ## Logging callbacks +//! +//! A logging callback closure can be specified using [`DeviceContext::set_log_callback`]. +//! The closure has the signiature: +//! `F: FnMut(u32, &str, &str) + 'static` +//! +//! The first argument is the log level and indicates the serverity of the message: +//! +//! * 0 - disable: Setting the callback level will disable all messages. The +//! callback function will not be called in this case. +//! * 1 - fatal: A non-recoverable error. The context and/or OptiX itself +//! might +//! no longer be in a usable state. +//! * 2 - error: A recoverable error, e.g., when passing invalid call +//! parameters. +//! * 3 - warning: Hints that OptiX might not behave exactly as requested by +//! the user or may perform slower than expected. +//! * 4 - print: Status or progress messages. +//! Higher levels might occur. +//! The second argument is a message category description (for example, "SCENE STAT") +//! The last argument is the message itself. +//! +//! ## Compilation caching +//! +//! Compilation of input programs will be cached to disk when creating [`Module`](crate::module::Module), +//! [`ProgramGroup`](crate::program_group::ProgramGroup), and +//! [`Pipeline`](crate::pipeline::Pipeline) objects if caching has been enabled. +//! +//! Subsequent compilation can reuse the cached data to improve the time to create +//! these objects. The cache can be shared between multiple [`DeviceContext`] +//! objects, and NVIDIA OptiX 7 will take care of ensuring correct multi-threaded +//! access to the cache. If no sharing between [`DeviceContext`] objects is desired, +//! the path to the cache can be set differently for each [`DeviceContext`]. +//! Caching can be disabled entirely by setting the environment variable +//! `OPTIX_CACHE_MAXSIZE` to 0. Disabling the cache via the environment variable +//! will not affect existing cache files or their contents. +//! +//! The disk cache can be controlled with: +//! +//! ### [`DeviceContext::set_cache_enabled()`] +//! The cache database is initialized when the device context is created and when +//! enabled through this function call. If the database cannot be initialized when +//! the device context is created, caching will be disabled; a message is reported +//! to the log callback if caching is enabled. In this case, the call to +//! [`DeviceContext::new()`] does not return an error. To ensure that cache +//! initialization succeeded on context creation, the status can be queried using +//! [`DeviceContext::get_cache_enabled`]. If caching is disabled, the cache can be +//! reconfigured and then enabled using [`DeviceContext::set_cache_enabled`]. If +//! the cache database cannot be initialized, an error is returned. Garbage +//! collection is performed on the next write to the cache database, not when the +//! cache is enabled. +//! +//! ### [`DeviceContext::set_cache_location`] +//! The disk cache is created in the directory specified by location. The directory +//! is created if it does not exist. +//! +//! The cache database is created immediately if the cache is currently enabled. +//! Otherwise the cache database is created later when the cache is enabled. An +//! error is returned if it is not possible to create the cache database file at +//! the specified location for any reason (for example, if the path is invalid or +//! if the directory is not writable) and caching will be disabled. If the disk +//! cache is located on a network file share, behavior is undefined. +//! +//! The location of the disk cache can be overridden with the environment variable +//! `OPTIX_CACHE_PATH`. This environment variable takes precedence over the value +//! passed to this function when the disk cache is enabled. +//! +//! The default location of the cache depends on the operating system: +//! * Windows - `%LOCALAPPDATA%\NVIDIA\OptixCache` +//! * Linux - `/var/tmp/OptixCache_username`, or `/tmp/OptixCache_username` if the +//! first choice is not usable. The underscore and username suffix are omitted if +//! the username cannot be obtained. +//! +//! ### [`DeviceContext::set_cache_database_sizes()`] +//! Parameters `low` and `high` set the low and high water marks for disk cache +//! garbage collection. Setting either limit to zero disables garbage collection. +//! Garbage collection only happens when the cache database is written. It is +//! triggered whenever the cache data size exceeds the high water mark and proceeding +//! until the size reaches the low water mark. Garbage collection always frees enough +//! space to allow the insertion of the new entry within the boundary of the low +//! water mark. An error is returned if either limit is nonzero and the high water +//! mark is lower than the low water mark. If more than one device context accesses +//! the same cache database with different high and low water mark values, the device +//! context uses its values when writing to the cache database. +//! +//! The high water mark can be overridden with the environment variable +//! `OPTIX_CACHE_MAXSIZE`. Setting `OPTIX_CACHE_MAXSIZE` to 0 will disable the cache. +//! Negative and non-integer values will be ignored. +//! +//! `OPTIX_CACHE_MAXSIZE` takes precedence over the `high` value passed to this +//! function. The low water mark will be set to half the value of +//! `OPTIX_CACHE_MAXSIZE`. +//! +//! Corresponding get* functions are supplied to retrieve the current value of these +//! cache properties. +//! +//! ## Validation Mode +//! The NVIDIA OptiX 7 validation mode can help uncover errors which might otherwise +//! go undetected or which occur only intermittently and are difficult to locate. +//! Validation mode enables additional tests and settings during application +//! execution. This additional processing can reduce performance, so it should only +//! be used during debugging or in the final testing phase of a completed application. +//! +//! Validation mode can be enabled by passing `true` to the `enable_validation` +//! parameter of [`DeviceContext::new()`]. +//! +//! [`OptixError::ValidationFailure`](crate::error::OptixError::ValidationFailure) +//! will be signalled if an error is caught when validation mode is enabled. +//! [`launch()`](crate::launch) will synchronize after the launch and report errors, +//! if any. +//! +//! Among other effects, validation mode implicitly enables all OptiX debug +//! exceptions and provides an exception program if none is provided. The first +//! non-user exception caught inside an exception program will therefore be reported +//! and the launch terminated immediately. This will make exceptions more visible +//! that otherwise might be overlooked. use std::os::raw::{c_char, c_uint}; use std::{ @@ -74,12 +233,24 @@ impl DeviceContext { // TODO(RDambrosio016): expose device context options /// Creates a new [`DeviceContext`] from a cust CUDA context. - pub fn new(cuda_ctx: &impl ContextHandle) -> Result { + /// + /// If `enable_validation` is `true`, then additional tests and settings are + /// enabled during application execution. This additional processing can reduce + /// performance, so it should only be used during debugging or in the final + /// testing phase of a completed application. + pub fn new(cuda_ctx: &impl ContextHandle, enable_validation: bool) -> Result { let mut raw = MaybeUninit::uninit(); + + let mut opt = sys::OptixDeviceContextOptions::default(); + if enable_validation { + opt.validationMode = + sys::OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL; + } + unsafe { optix_call!(optixDeviceContextCreate( cuda_ctx.get_inner(), - ptr::null(), + &opt, raw.as_mut_ptr() ))?; Ok(Self { @@ -104,7 +275,7 @@ impl DeviceContext { /// Indicated whether the disk cache is enabled pub fn get_cache_enabled(&self) -> Result { - let result = 0; + let mut result = 0; unsafe { Ok( optix_call!(optixDeviceContextGetCacheEnabled(self.raw, &mut result,)) @@ -120,8 +291,8 @@ impl DeviceContext { unsafe { Ok(optix_call!(optixDeviceContextGetCacheLocation( self.raw, - buf.as_ptr(), - buf.len() as u64 + buf.as_mut_ptr(), + buf.len(), )) .map(|_| CStr::from_ptr(buf.as_ptr()).to_string_lossy().to_string())?) } @@ -194,7 +365,8 @@ impl DeviceContext { pub fn set_cache_enabled(&mut self, enable: bool) -> Result<()> { unsafe { Ok(optix_call!(optixDeviceContextSetCacheEnabled( - self.raw, enable + self.raw, + if enable { 1 } else { 0 } ))?) } } @@ -212,7 +384,7 @@ impl DeviceContext { /// if the first choice is not usable), the underscore and username suffix are omitted if the username cannot be obtained /// * MacOS X: `/Library/Application Support/NVIDIA/OptixCache` pub fn set_cache_location(&mut self, location: &str) -> Result<()> { - let location = CString::new(location)?; + let location = CString::new(location).map_err(|_| Error::NulBytesInString)?; unsafe { Ok(optix_call!(optixDeviceContextSetCacheLocation( self.raw, diff --git a/crates/optix/src/error.rs b/crates/optix/src/error.rs index 83e84bc0..f29fd386 100644 --- a/crates/optix/src/error.rs +++ b/crates/optix/src/error.rs @@ -196,6 +196,7 @@ pub enum Error { ProgramGroupCreation { source: OptixError, log: String }, PipelineCreation { source: OptixError, log: String }, AccelUpdateMismatch, + NulBytesInString, } impl From for Error { @@ -219,6 +220,7 @@ impl std::error::Error for Error { Self::ProgramGroupCreation { source, .. } => Some(source), Self::PipelineCreation { source, .. } => Some(source), Self::AccelUpdateMismatch => None, + Self::NulBytesInString => None, } } } @@ -234,6 +236,7 @@ impl Display for Error { } Self::PipelineCreation { log, .. } => write!(f, "Pipeline creation error: {}", log), Self::AccelUpdateMismatch => write!(f, "Build inputs passed to DynamicAccel::update do not match the structure of those used to build the accel"), + Self::NulBytesInString => write!(f, "The provided string contained nul bytes"), } } } From 0d7a78f3c97fc7ff778c3db1e8a3467d72c406b1 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:09:50 +1300 Subject: [PATCH 059/100] Add some module docs --- crates/optix/src/acceleration.rs | 270 +++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index a4fd2d2b..b3c47038 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -1,3 +1,273 @@ +//! # Acceleration Structures +//! +//! NVIDIA OptiX 7 provides acceleration structures to optimize the search for the +//! intersection of rays with the geometric data in the scene. Acceleration structures +//! can contain two types of data: geometric primitives (a geometry-AS) or instances +//! (an instance-AS). Acceleration structures are created on the device using a set +//! of functions. These functions enable overlapping and pipelining of acceleration +//! structure creation, called a build. The functions use one or more [`BuildInput`] +//! structs to specify the geometry plus a set of parameters to control the build. +//! +//! Acceleration structures have size limits, listed in “Limits”. For an instance +//! acceleration structure, the number of instances has an upper limit. For a geometry +//! acceleration structure, the number of geometric primitives is limited, +//! specifically the total number of primitives in its build inputs, multiplied by the +//! number of motion keys. +//! +//! The following acceleration structure types are supported: +//! +//! #### Instance acceleration structures +//! - [`InstanceArray`](crate::instance_array::InstanceArray) +//! - [`InstancePointerArray`](crate::instance_array::InstancePointerArray) +//! +//! #### Geometry acceleration structure containing built-in triangles +//! - [`TriangleArray`](crate::triangle_array::TriangleArray) +//! - [`IndexedTriangleArray`](crate::triangle_array::IndexedTriangleArray) +//! +//! #### Geometry acceleration structure containing built-in curves +//! - [`CurveArray`](crate::curve_array::CurveArray) +//! +//! #### Geometry acceleration structure containing custom primitives +//! - [`CustomPrimitiveArray`](crate::custom_primitive_array::CustomPrimitiveArray) +//! +//! ## Building +//! +//! For geometry-AS builds, each build input can specify a set of triangles, a set +//! of curves, or a set of user-defined primitives bounded by specified axis-aligned +//! bounding boxes. Multiple build inputs can be passed as an array to [`accel_build`] +//! to combine different meshes into a single acceleration structure. All build +//! inputs for a single build must agree on the build input type. +//! +//! Instance acceleration structures have a single build input and specify an array +//! of instances. Each [`Instance`](crate::instance_array::Instance) includes a ray transformation and an +//! [`TraversableHandle`] that refers to a geometry-AS, a transform node, or another +//! instance acceleration structure. +//! +//! ### Safe API +//! +//! The easiest way to build an acceleration structure is using [`Accel::build`] +//! to which you just pass a slice of [`BuildInput`]s and the function handles +//! memory allocation and synchronization for you. +//! +//! This is handy for getting something working with the minimum of fuss, but +//! means reallocating temporary storage each time. It also means synchronizing +//! after each build rather than potentially processing many builds on a stream +//! and synchronizing at the end. +//! +//! ```no_run +//! use cust::prelude as cu; +//! use optix::prelude as ox; +//! # fn doit() -> Result<(), Box> { +//! # cust::init(cu::CudaFlags::empty())?; +//! # ox::init()?; +//! # let device = cu::Device::get_device(0)?; +//! # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +//! # cu::ContextFlags::MAP_HOST, device)?; +//! # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +//! # let vertices: Vec<[f32; 3]> = Vec::new(); +//! # let indices: Vec<[u32; 3]> = Vec::new(); +//! # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; +//! +//! let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +//! let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; +//! +//! let geometry_flags = ox::GeometryFlags::None; +//! let triangle_input = +//! ox::IndexedTriangleArray::new( +//! &[&buf_vertex], +//! &buf_indices, +//! &[geometry_flags] +//! ); +//! +//! let accel_options = +//! ox::AccelBuildOptions::new( +//! ox::BuildFlags::ALLOW_COMPACTION, +//! ox::BuildOperation::Build +//! ); +//! +//! let build_inputs = vec![triangle_input]; +//! +//! let gas = ox::Accel::build( +//! &ctx, +//! &stream, +//! &[accel_options], +//! &build_inputs, +//! true +//! )?; +//! +//! stream.synchronize()?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ### Unsafe API +//! +//! As an alternative, you can also use the unsafe functions [`accel_build`], +//! [`accel_compact`], and [`Accel::from_raw_parts`] to handle the memory +//! allocation yourself, meaning you can reuse buffers between accel builds. +//! +//! To prepare for a build, the required memory sizes are queried by passing an +//! initial set of build inputs and parameters to [`accel_compute_memory_usage`]. +//! It returns three different sizes: +//! +//! * `output_size_in_bytes` - Size of the memory region where the resulting +//! acceleration structure is placed. This size is an upper bound and may be +//! substantially larger than the final acceleration structure. (See “Compacting acceleration structures”.) +//! * `temp_size_in_bytes` - Size of the memory region that is temporarily used during +//! the build. +//! * `temp_update_size_in_bytes` - Size of the memory region that is temporarily +//! required to update the acceleration structure. +//! +//! Using these sizes, the application allocates memory for the output and temporary +//! memory buffers on the device. The pointers to these buffers must be aligned to +//! a 128-byte boundary. These buffers are actively used for the duration of the +//! build. For this reason, they cannot be shared with other currently active build +//! requests. +//! +//! Note that [`accel_compute_memory_usage`] does not initiate any activity on the +//! device; pointers to device memory or contents of input buffers are not required to point to allocated memory. +//! +//! The function [`accel_build`] takes the same array of [`BuildInput`] structs as +//! [`accel_compute_memory_usage`] and builds a single acceleration structure from +//! these inputs. This acceleration structure can contain either geometry or +//! instances, depending on the inputs to the build. +//! +//! The build operation is executed on the device in the specified CUDA stream and +//! runs asynchronously on the device, similar to CUDA kernel launches. The +//! application may choose to block the host-side thread or synchronize with other +//! CUDA streams by using available CUDA synchronization functionality such as +//! [`Stream::synchronize()`](cust::stream::Stream::synchronize) or CUDA events. +//! The traversable handle returned is computed on the host and is returned from +//! the function immediately, without waiting for the build to finish. By producing +//! handles at acceleration time, custom handles can also be generated based on +//! input to the builder. +//! +//! The acceleration structure constructed by [`accel_build`] does not reference +//! any of the device buffers referenced in the build inputs. All relevant data +//! is copied from these buffers into the acceleration output buffer, possibly in +//! a different format. +//! +//! The application is free to release this memory after the build without +//! invalidating the acceleration structure. However, instance-AS builds will +//! continue to refer to other instance-AS and geometry-AS instances and transform +//! nodes. +//! +//! ## Primitive Build Inputs +//! The [`accel_build`] function accepts multiple build inputs per call, but they +//! must be all triangle inputs, all curve inputs, or all AABB inputs. Mixing build +//! input types in a single geometry-AS is not allowed. +//! +//! Each build input maps to one or more consecutive records in the shader binding +//! table (SBT), which controls program dispatch. (See “Shader binding table”.) If +//! multiple records in the SBT are required, the application needs to provide a +//! device buffer with per-primitive SBT record indices for that build input. If +//! only a single SBT record is requested, all primitives reference this same unique +//! SBT record. Note that there is a limit to the number of referenced SBT records +//! per geometry-AS. (Limits are discussed in “Limits”.) +//! +//! Each build input also specifies an array of OptixGeometryFlags, one for each SBT +//! record. The flags for one record apply to all primitives mapped to this SBT record. +//! +//! The following flags are supported: +//! +//! * [`GeometryFlags::None`] - Applies the default behavior when calling the any-hit +//! program, possibly multiple times, allowing the acceleration-structure builder +//! to apply all optimizations. +//! * [`GeometryFlags::RequireSingleAnyHitCall`] - Disables some optimizations +//! specific to acceleration-structure builders. By default, traversal may call +//! the any-hit program more than once for each intersected primitive. Setting +//! the flag ensures that the any-hit program is called only once for a hit with a +//! primitive. However, setting this flag may change traversal performance. The +//! usage of this flag may be required for correctness of some rendering algorithms; +//! for example, in cases where opacity or transparency information is accumulated +//! in an any-hit program. +//! * [`GeometryFlags::DisableAnyHit`] - Indicates that traversal should not call +//! the any-hit program for this primitive even if the corresponding SBT record +//! contains an any-hit program. Setting this flag usually improves performance +//! even if no any-hit program is present in the SBT. +//! +//! Primitives inside a build input are indexed starting from zero. This primitive +//! index is accessible inside the intersection, any-hit, and closest-hit programs. +//! If the application chooses to offset this index for all primitives in a build +//! input, there is no overhead at runtime. This can be particularly useful when +//! data for consecutive build inputs is stored consecutively in device memory. +//! The `primitive_index_offset` value is only used when reporting the intersection +//! primitive. +//! +//! ## Build Flags +//! +//! An acceleration structure build can be controlled using the values of the +//! [`BuildFlags`] enum. To enable random vertex access on an acceleration structure, +//! use [`BuildFlags::ALLOW_RANDOM_VERTEX_ACCESS`]. (See “Vertex random access”.) +//! To steer trade-offs between build performance, runtime traversal performance +//! and acceleration structure memory usage, use [`BuildFlags::PREFER_FAST_TRACE`] +//! and [`BuildFlags::PREFER_FAST_BUILD`]. For curve primitives in particular, +//! these flags control splitting; see “Splitting curve segments”. +//! +//! The flags [`BuildFlags::PREFER_FAST_TRACE`] and [`BuildFlags::PREFER_FAST_BUILD`] +//! are mutually exclusive. To combine multiple flags that are not mutually exclusive, +//! use the logical “or” operator. +//! +//! ## Dynamic Updates +//! +//! Building an acceleration structure can be computationally costly. Applications +//! may choose to update an existing acceleration structure using modified vertex +//! data or bounding boxes. Updating an existing acceleration structure is generally +//! much faster than rebuilding. However, the quality of the acceleration structure +//! may degrade if the data changes too much with an update, for example, through +//! explosions or other chaotic transitions—even if for only parts of the mesh. +//! The degraded acceleration structure may result in slower traversal performance +//! as compared to an acceleration structure built from scratch from the modified +//! input data. +//! +//! ### Safe API +//! +//! The simplest way to use dynamic updates is with the [`DynamicAccel`] structure. +//! Simply call [`DynamicAccel::new()`] as you would with [`Accel`], and then +//! call [`DynamicAccel::update()`] with the updated build inputs when you want +//! to update the acceleration structure. +//! +//! Note that the inputs to [`DynamicAccel::update`] must have the same structure, +//! i.e. the number of motion keys, aabbs, triangle topology etc must be the same, +//! although the underlying data (including the data pointers) can be different. +//! If the data have a different structure, then behaviour is undefined. +//! [`DynamicAccel`] checks this by hashing the inputs and returns an error if +//! the data do not match. +//! +//! ### Unsafe API +//! +//! To allow for future updates of an acceleration structure, set +//! [`BuildFlags::ALLOW_UPDATE`] in the build flags when building the acceleration +//! structure initially. +//! +//! To update the previously built acceleration structure, set the operation to +//! [`BuildOperation::Update`] and then call [`accel_build()`] on the same output +//! data. All other options are required to be identical to the original build. +//! The update is done in-place on the output data. +//! +//! Updating an acceleration structure usually requires a different amount of temporary memory than the original build. +//! +//! When updating an existing acceleration structure, only the device pointers and/or +//! their buffer content may be changed. You cannot change the number of build inputs, +//! the build input types, build flags, traversable handles for instances (for an +//! instance-AS), or the number of vertices, indices, AABBs, instances, SBT records +//! or motion keys. Changes to any of these things may result in undefined behavior, +//! including GPU faults. +//! +//! Note the following: +//! +//! * When using indices, changing the connectivity or, in general, using shuffled +//! vertex positions will work, but the quality of the acceleration structure will +//! likely degrade substantially. +//! * During an animation operation, geometry that should be invisible to the camera +//! should not be “removed” from the scene, either by moving it very far away or +//! by converting it into a degenerate form. Such changes to the geometry will also +//! degrade the acceleration structure. +//! * In these cases, it is more efficient to re-build the geometry-AS and/or the +//! instance-AS, or to use the respective masking and flags. +//! +//! Updating an acceleration structure requires that any other acceleration structure that is using this acceleration structure as a child directly or indirectly also needs to be updated or rebuild. + use crate::{context::DeviceContext, error::Error, optix_call, sys}; use cust::{ memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer, DeviceSlice}, From 8663bed5c3bc8900e7167368d9d7056d5dbccdff Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 4 Nov 2021 22:10:07 +1300 Subject: [PATCH 060/100] Update to latest library changes --- crates/optix/examples/ex04_mesh/src/renderer.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 93728fed..952697eb 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -47,7 +47,7 @@ impl Renderer { CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; - let mut ctx = DeviceContext::new(&cuda_context)?; + let mut ctx = DeviceContext::new(&cuda_context, true)?; ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module @@ -114,11 +114,8 @@ impl Renderer { let buf_indices = DeviceBuffer::from_slice(&indices)?; let geometry_flags = GeometryFlags::None; - let triangle_input = IndexedTriangleArray::new( - &[&buf_vertex], - &buf_indices, - std::slice::from_ref(&geometry_flags), - ); + let triangle_input = + IndexedTriangleArray::new(&[&buf_vertex], &buf_indices, &[geometry_flags]); let accel_options = AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); From a1e6598719d814db9eb93a1cd260938c0ba21c0c Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 5 Nov 2021 09:12:26 +1300 Subject: [PATCH 061/100] Add more docs --- crates/optix/src/acceleration.rs | 437 ++++++++++++++++++++++++++++++- 1 file changed, 430 insertions(+), 7 deletions(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index b3c47038..33f2946f 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -152,6 +152,67 @@ //! continue to refer to other instance-AS and geometry-AS instances and transform //! nodes. //! +//! ```no_run +//! use cust::prelude as cu; +//! use optix::prelude as ox; +//! # fn doit() -> Result<(), Box> { +//! # cust::init(cu::CudaFlags::empty())?; +//! # ox::init()?; +//! # let device = cu::Device::get_device(0)?; +//! # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +//! # cu::ContextFlags::MAP_HOST, device)?; +//! # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +//! # let vertices: Vec<[f32; 3]> = Vec::new(); +//! # let indices: Vec<[u32; 3]> = Vec::new(); +//! # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; +//! +//! let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +//! let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; +//! +//! let geometry_flags = ox::GeometryFlags::None; +//! +//! let build_inputs = +//! [ox::IndexedTriangleArray::new( +//! &[&buf_vertex], +//! &buf_indices, +//! &[geometry_flags] +//! )]; +//! +//! let accel_options = +//! ox::AccelBuildOptions::new( +//! ox::BuildFlags::ALLOW_COMPACTION, +//! ox::BuildOperation::Build +//! ); +//! +//! // Get the storage requirements for temporary and output buffers +//! let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; +//! +//! // Allocate temporary and output buffers +//! let mut output_buffer = +//! unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; +//! let mut temp_buffer = +//! unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; +//! +//! // Build the accel +//! let hnd = unsafe { +//! accel_build( +//! ctx, +//! stream, +//! accel_options, +//! build_inputs, +//! &mut temp_buffer, +//! &mut output_buffer, +//! &mut properties, +//! )? +//! }; +//! +//! // The accel build is asynchronous +//! stream.synchronize()?; +//! +//! # Ok(()) +//! # } +//! ``` +//! //! ## Primitive Build Inputs //! The [`accel_build`] function accepts multiple build inputs per call, but they //! must be all triangle inputs, all curve inputs, or all AABB inputs. Mixing build @@ -266,7 +327,9 @@ //! * In these cases, it is more efficient to re-build the geometry-AS and/or the //! instance-AS, or to use the respective masking and flags. //! -//! Updating an acceleration structure requires that any other acceleration structure that is using this acceleration structure as a child directly or indirectly also needs to be updated or rebuild. +//! Updating an acceleration structure requires that any other acceleration structure +//! that is using this acceleration structure as a child directly or indirectly +//! also needs to be updated or rebuild. use crate::{context::DeviceContext, error::Error, optix_call, sys}; use cust::{ @@ -285,6 +348,58 @@ pub trait BuildInput: std::hash::Hash { fn to_sys(&self) -> sys::OptixBuildInput; } +/// Wrapper struct containing the storage and handle for a static acceleration +/// structure. +/// +/// An Accel can be built by providing a slice of [`BuildInput`]s over which to +/// build the acceleration structure, together with a matching slice of +/// [`AccelBuildOptions`]. +/// +/// ```no_run +/// use cust::prelude as cu; +/// use optix::prelude as ox; +/// # fn doit() -> Result<(), Box> { +/// # cust::init(cu::CudaFlags::empty())?; +/// # ox::init()?; +/// # let device = cu::Device::get_device(0)?; +/// # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +/// # cu::ContextFlags::MAP_HOST, device)?; +/// # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +/// # let vertices: Vec<[f32; 3]> = Vec::new(); +/// # let indices: Vec<[u32; 3]> = Vec::new(); +/// # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; +/// +/// let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +/// let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; +/// +/// let geometry_flags = ox::GeometryFlags::None; +/// let triangle_input = +/// ox::IndexedTriangleArray::new( +/// &[&buf_vertex], +/// &buf_indices, +/// &[geometry_flags] +/// ); +/// +/// let accel_options = +/// ox::AccelBuildOptions::new( +/// ox::BuildFlags::ALLOW_COMPACTION, +/// ox::BuildOperation::Build +/// ); +/// +/// let build_inputs = vec![triangle_input]; +/// +/// let gas = ox::Accel::build( +/// &ctx, +/// &stream, +/// &[accel_options], +/// &build_inputs, +/// true +/// )?; +/// +/// stream.synchronize()?; +/// # Ok(()) +/// # } +/// ``` pub struct Accel { buf: DeviceBuffer, hnd: TraversableHandle, @@ -298,6 +413,58 @@ impl Accel { /// Build and (optionally) compact the acceleration structure for the given /// `build_inputs`. + /// + /// This will handle all necessary memory allocation internally, synchronizing + /// all internal steps, but NOT the final build or compaction. + /// + /// If you want to re-use buffers between builds and line up multiple builds + /// at once for more performance/efficiency, you should use the unsafe api. + /// + /// ```no_run + /// use cust::prelude as cu; + /// use optix::prelude as ox; + /// # fn doit() -> Result<(), Box> { + /// # cust::init(cu::CudaFlags::empty())?; + /// # ox::init()?; + /// # let device = cu::Device::get_device(0)?; + /// # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | + /// # cu::ContextFlags::MAP_HOST, device)?; + /// # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; + /// # let vertices: Vec<[f32; 3]> = Vec::new(); + /// # let indices: Vec<[u32; 3]> = Vec::new(); + /// # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; + /// + /// let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; + /// let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; + /// + /// let geometry_flags = ox::GeometryFlags::None; + /// let triangle_input = + /// ox::IndexedTriangleArray::new( + /// &[&buf_vertex], + /// &buf_indices, + /// &[geometry_flags] + /// ); + /// + /// let accel_options = + /// ox::AccelBuildOptions::new( + /// ox::BuildFlags::ALLOW_COMPACTION, + /// ox::BuildOperation::Build + /// ); + /// + /// let build_inputs = vec![triangle_input]; + /// + /// let gas = ox::Accel::build( + /// &ctx, + /// &stream, + /// &[accel_options], + /// &build_inputs, + /// true + /// )?; + /// + /// stream.synchronize()?; + /// # Ok(()) + /// # } + /// ``` pub fn build( ctx: &DeviceContext, stream: &cust::stream::Stream, @@ -349,6 +516,7 @@ impl Accel { } } + /// Construct a new Accel from a handle and buffer. pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Accel { Accel { buf, hnd } } @@ -378,6 +546,8 @@ impl Accel { } } +/// Acceleration structure supporting dynamic updates. +/// /// Building an acceleration structure can be computationally costly. Applications /// may choose to update an existing acceleration structure using modified vertex /// data or bounding boxes. Updating an existing acceleration structure is generally @@ -404,7 +574,7 @@ impl DynamicAccel { /// Build and compact the acceleration structure for the given inputs. /// /// This forces the ALLOW_UPDATE flag for the build flags to make sure the - /// resulting accel can be updated + /// resulting accel can be updated. pub fn build( ctx: &DeviceContext, stream: &cust::stream::Stream, @@ -531,10 +701,6 @@ impl DynamicAccel { Ok(()) } - - pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Accel { - Accel { buf, hnd } - } } /// Opaque handle to a traversable acceleration structure. @@ -552,7 +718,62 @@ pub struct TraversableHandle { /// Computes the device memory required for temporary and output buffers /// when building the acceleration structure. Use the returned sizes to -/// allocate enough memory to pass to `accel_build()` +/// allocate enough memory to pass to [`accel_build()`]. +/// +/// # Examples +/// ```no_run +/// use cust::prelude as cu; +/// use optix::prelude as ox; +/// # fn doit() -> Result<(), Box> { +/// # cust::init(cu::CudaFlags::empty())?; +/// # ox::init()?; +/// # let device = cu::Device::get_device(0)?; +/// # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +/// # cu::ContextFlags::MAP_HOST, device)?; +/// # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +/// # let vertices: Vec<[f32; 3]> = Vec::new(); +/// # let indices: Vec<[u32; 3]> = Vec::new(); +/// # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; +/// let buf_vertex = DeviceBuffer::from_slice(&vertices)?; +/// let buf_indices = DeviceBuffer::from_slice(&indices)?; +/// +/// let geometry_flags = GeometryFlags::None; +/// let build_inputs = [IndexedTriangleArray::new( +/// &[&buf_vertex], +/// &buf_indices, +/// &[geometry_flags], +/// )]; +/// let accel_options = +/// AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); +/// +/// let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; +/// let mut output_buffer = +/// unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; +/// +/// let mut temp_buffer = +/// unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; +/// +/// let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; +/// +/// let mut properties = vec![AccelEmitDesc::CompactedSize( +/// compacted_size_buffer.as_device_ptr(), +/// )]; +/// +/// let hnd = unsafe { +/// accel_build( +/// ctx, +/// stream, +/// accel_options, +/// build_inputs, +/// &mut temp_buffer, +/// &mut output_buffer, +/// &mut properties, +/// )? +/// }; +/// +/// # Ok(()) +/// # } +/// ``` pub fn accel_compute_memory_usage( ctx: &DeviceContext, accel_options: &[AccelBuildOptions], @@ -576,6 +797,61 @@ pub fn accel_compute_memory_usage( /// Builds the acceleration structure. /// `temp_buffer` and `output_buffer` must be at least as large as the sizes /// returned by `accel_compute_memory_usage()` +/// +/// # Examples +/// ```no_run +/// use cust::prelude as cu; +/// use optix::prelude as ox; +/// # fn doit() -> Result<(), Box> { +/// # cust::init(cu::CudaFlags::empty())?; +/// # ox::init()?; +/// # let device = cu::Device::get_device(0)?; +/// # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +/// # cu::ContextFlags::MAP_HOST, device)?; +/// # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +/// # let vertices: Vec<[f32; 3]> = Vec::new(); +/// # let indices: Vec<[u32; 3]> = Vec::new(); +/// # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; +/// let buf_vertex = DeviceBuffer::from_slice(&vertices)?; +/// let buf_indices = DeviceBuffer::from_slice(&indices)?; +/// +/// let geometry_flags = GeometryFlags::None; +/// let build_inputs = [IndexedTriangleArray::new( +/// &[&buf_vertex], +/// &buf_indices, +/// &[geometry_flags], +/// )]; +/// let accel_options = +/// AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); +/// +/// let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; +/// let mut output_buffer = +/// unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; +/// +/// let mut temp_buffer = +/// unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; +/// +/// let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; +/// +/// let mut properties = vec![AccelEmitDesc::CompactedSize( +/// compacted_size_buffer.as_device_ptr(), +/// )]; +/// +/// let hnd = unsafe { +/// accel_build( +/// ctx, +/// stream, +/// accel_options, +/// build_inputs, +/// &mut temp_buffer, +/// &mut output_buffer, +/// &mut properties, +/// )? +/// }; +/// +/// # Ok(()) +/// # } +/// ``` pub unsafe fn accel_build( ctx: &DeviceContext, stream: &cust::stream::Stream, @@ -611,6 +887,77 @@ pub unsafe fn accel_build( /// Compacts the acceleration structure referenced by `input_handle`, /// storing the result in `output_buffer` and returning a handle to the /// newly compacted structure +/// +/// # Examples +/// ```no_run +/// use cust::prelude as cu; +/// use optix::prelude as ox; +/// # fn doit() -> Result<(), Box> { +/// # cust::init(cu::CudaFlags::empty())?; +/// # ox::init()?; +/// # let device = cu::Device::get_device(0)?; +/// # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +/// # cu::ContextFlags::MAP_HOST, device)?; +/// # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +/// # let vertices: Vec<[f32; 3]> = Vec::new(); +/// # let indices: Vec<[u32; 3]> = Vec::new(); +/// # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; +/// let buf_vertex = DeviceBuffer::from_slice(&vertices)?; +/// let buf_indices = DeviceBuffer::from_slice(&indices)?; +/// +/// let geometry_flags = GeometryFlags::None; +/// let build_inputs = [IndexedTriangleArray::new( +/// &[&buf_vertex], +/// &buf_indices, +/// &[geometry_flags], +/// )]; +/// let accel_options = +/// AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); +/// +/// let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; +/// let mut output_buffer = +/// unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; +/// +/// let mut temp_buffer = +/// unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; +/// +/// // Storage for the size of the compacted buffer +/// let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; +/// +/// // Tell OptiX that we want to know how big the compacted buffer needs to be +/// let mut properties = vec![AccelEmitDesc::CompactedSize( +/// compacted_size_buffer.as_device_ptr(), +/// )]; +/// +/// let hnd = unsafe { +/// accel_build( +/// ctx, +/// stream, +/// accel_options, +/// build_inputs, +/// &mut temp_buffer, +/// &mut output_buffer, +/// &mut properties, +/// )? +/// }; +/// +/// // The build is asynchronous, so we need to block on the stream before +/// // reading back the emitted compacted size +/// stream.synchronize()?; +/// +/// // Copy the returned size needed for the compacted buffer and allocate +/// // storage +/// let mut compacted_size = 0usize; +/// compacted_size_buffer.copy_to(&mut compacted_size)?; +/// +/// let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; +/// +/// // Compact the accel structure. +/// let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; +/// +/// # Ok(()) +/// # } +/// ``` pub unsafe fn accel_compact( ctx: &DeviceContext, stream: &cust::stream::Stream, @@ -630,6 +977,16 @@ pub unsafe fn accel_compact( } bitflags::bitflags! { + /// Flags providing configuration options to acceleration structure build. + /// + /// * `ALLOW_UPDATE` - Must be provided if the accel is to support dynamic updates. + /// * `ALLOW_COMPACTION` - Must be provided to enable memory compaction for the accel. + /// * `PREFER_FAST_TRACE` - Accel build is slower, but tracing against it will be faster. + /// * `PREFER_FAST_BUILD` - Accel build is faster, but tracing against it will be slower. + /// * `ALLOW_RANDOM_VERTEX_ACCESS` - Must be provided to be able to get at vertex data from CH + /// an AH programs on the device. May affect the performance of the accel (seems to be larger). + /// + /// Note that `PREFER_FAST_TRACE` and `PREFER_FAST_BUILD` are mutually exclusive. pub struct BuildFlags: u32 { const NONE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_NONE; const ALLOW_UPDATE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_UPDATE; @@ -640,6 +997,7 @@ bitflags::bitflags! { } } +/// Select which operation to perform with [`accel_build()`]. #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq)] pub enum BuildOperation { @@ -648,6 +1006,15 @@ pub enum BuildOperation { } bitflags::bitflags! { + /// Configure how to handle ray times that are outside of the provided motion keys. + /// + /// By default, the object will appear static (clamped) to the nearest motion + /// key for rays outside of the range of key times. + /// + /// * `START_VANISH` - The object will be invisible to rays with a time less + /// than the first provided motion key + /// * `END_VANISH` - The object will be invisible to rays with a time less + /// than the first provided motion key pub struct MotionFlags: u16 { const NONE = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_NONE as u16; const START_VANISH = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH as u16; @@ -655,6 +1022,20 @@ bitflags::bitflags! { } } +/// Provide an accel build with motion keys for motion blur. +/// +/// The motion options are always specified per traversable (acceleration structure +/// or motion transform). There is no dependency between the motion options of +/// traversables; given an instance referencing a geometry acceleration structure +/// with motion, it is not required to build an instance acceleration structure +/// with motion. The same goes for motion transforms. Even if an instance references +/// a motion transform as child traversable, the instance acceleration structure +/// itself may or may not have motion. +/// +/// Motion transforms must specify at least two motion keys. Acceleration structures, +/// however, also accept [`BuildOptions`] with field `motion_options` set +/// to zero. This effectively disables motion for the acceleration structure and +/// ignores the motion beginning and ending times, along with the motion flags. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] pub struct MotionOptions { @@ -664,6 +1045,7 @@ pub struct MotionOptions { pub time_end: f32, } +/// Options to configure the [`accel_build()`] #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] pub struct AccelBuildOptions { @@ -673,6 +1055,8 @@ pub struct AccelBuildOptions { } impl AccelBuildOptions { + /// Create a new AccelBuildOptions with the given flags and operation and + /// no motion blur. pub fn new(build_flags: BuildFlags, operation: BuildOperation) -> Self { AccelBuildOptions { build_flags, @@ -686,17 +1070,22 @@ impl AccelBuildOptions { } } + /// Set the number of motion keys. + /// + /// This must either be 0 for no motion blur, or >= 2. pub fn num_keys(mut self, num_keys: u16) -> Self { self.motion_options.num_keys = num_keys; self } + /// Set the start and end time that the first and last motion keys represent. pub fn time_interval(mut self, time_begin: f32, time_end: f32) -> Self { self.motion_options.time_begin = time_begin; self.motion_options.time_end = time_end; self } + /// Set the flags describing how to handle out-of-range time samples. pub fn motion_flags(mut self, flags: MotionFlags) -> Self { self.motion_options.flags = flags; self @@ -720,6 +1109,8 @@ pub struct AccelRelocationInfo { inner: sys::OptixAccelRelocationInfo, } +/// Struct used for OptiX to communicate the necessary buffer sizes for accel +/// temp and final outputs. #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct AccelBufferSizes { @@ -728,11 +1119,26 @@ pub struct AccelBufferSizes { pub temp_update_size_in_bytes: usize, } +/// Struct used for Optix to communicate the compacted size or list of bounding +/// boxes back from an accel build. +/// +/// # Examples +/// // Copy the returned size needed for the compacted buffer and allocate +/// // storage +/// let mut compacted_size = 0usize; +/// compacted_size_buffer.copy_to(&mut compacted_size)?; +/// +/// let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; +/// +/// // Compact the accel structure. +/// let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; + pub enum AccelEmitDesc { CompactedSize(DevicePointer), Aabbs(DevicePointer), } +/// Struct representing a bounding box. #[repr(C)] #[derive(DeviceCopy, Copy, Clone)] pub struct Aabb { @@ -759,6 +1165,23 @@ impl From<&mut AccelEmitDesc> for sys::OptixAccelEmitDesc { } } +/// Per-geometry tracing requirements used to allow potential optimizations. +/// +/// * `GeometryFlags::None` - Applies the default behavior when calling the +/// any-hit program, possibly multiple times, allowing the acceleration-structure +/// builder to apply all optimizations. +/// * `GeometryFlags::DisableAnyHit` - Disables some optimizations specific to +/// acceleration-structure builders. By default, traversal may call the any-hit +/// program more than once for each intersected primitive. Setting the flag +/// ensures that the any-hit program is called only once for a hit with a primitive. +/// However, setting this flag may change traversal performance. The usage of +/// this flag may be required for correctness of some rendering algorithms; +/// for example, in cases where opacity or transparency information is accumulated +/// in an any-hit program. +/// * `GeometryFlags::RequireSingleAnyHitCall` - Indicates that traversal should +/// not call the any-hit program for this primitive even if the corresponding SBT +/// record contains an any-hit program. Setting this flag usually improves +/// performance even if no any-hit program is present in the SBT. #[repr(u32)] #[derive(Copy, Clone, PartialEq, Hash)] pub enum GeometryFlags { From b3af8ae73cad93ec5078f05121ef185facb1dcc0 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sat, 6 Nov 2021 22:27:04 +1300 Subject: [PATCH 062/100] Remove mut requirement for getting pointer --- crates/cust/src/memory/device/device_box.rs | 2 +- crates/optix/src/curve_array.rs | 150 ----------- crates/optix/src/custom_primitive_array.rs | 119 -------- crates/optix/src/instance_array.rs | 191 ------------- crates/optix/src/module.rs | 285 -------------------- crates/optix/src/triangle_array.rs | 270 ------------------- 6 files changed, 1 insertion(+), 1016 deletions(-) delete mode 100644 crates/optix/src/curve_array.rs delete mode 100644 crates/optix/src/custom_primitive_array.rs delete mode 100644 crates/optix/src/instance_array.rs delete mode 100644 crates/optix/src/module.rs delete mode 100644 crates/optix/src/triangle_array.rs diff --git a/crates/cust/src/memory/device/device_box.rs b/crates/cust/src/memory/device/device_box.rs index db56d7d8..eae4c475 100644 --- a/crates/cust/src/memory/device/device_box.rs +++ b/crates/cust/src/memory/device/device_box.rs @@ -203,7 +203,7 @@ impl DeviceBox { /// let ptr = x.as_device_ptr(); /// println!("{:p}", ptr); /// ``` - pub fn as_device_ptr(&mut self) -> DevicePointer { + pub fn as_device_ptr(&self) -> DevicePointer { self.ptr } diff --git a/crates/optix/src/curve_array.rs b/crates/optix/src/curve_array.rs deleted file mode 100644 index 97efdf59..00000000 --- a/crates/optix/src/curve_array.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::{ - acceleration::{BuildInput, GeometryFlags}, - error::Error, - sys, -}; -use cust::memory::DeviceSlice; -use cust_raw::CUdeviceptr; -type Result = std::result::Result; - -use std::hash::Hash; -use std::marker::PhantomData; - -pub struct CurveArray<'v, 'w, 'i> { - curve_type: CurveType, - num_primitives: u32, - vertex_buffers: PhantomData<&'v f32>, - num_vertices: u32, - d_vertex_buffers: Vec, - vertex_stride_in_bytes: u32, - width_buffers: PhantomData<&'w f32>, - num_width_buffers: u32, - d_width_buffers: Vec, - width_stride_in_bytes: u32, - index_buffer: &'i DeviceSlice, - index_stride_in_bytes: u32, - flags: GeometryFlags, - primitive_index_offset: u32, -} - -impl<'v, 'w, 'i> Hash for CurveArray<'v, 'w, 'i> { - fn hash(&self, state: &mut H) { - self.curve_type.hash(state); - state.write_u32(self.num_primitives); - state.write_u32(self.num_vertices); - state.write_usize(self.d_vertex_buffers.len()); - state.write_u32(self.vertex_stride_in_bytes); - state.write_u32(self.num_vertices); - state.write_usize(self.d_width_buffers.len()); - state.write_u32(self.width_stride_in_bytes); - state.write_usize(self.index_buffer.len()); - state.write_u32(self.index_stride_in_bytes); - self.flags.hash(state); - state.write_u32(self.primitive_index_offset); - } -} - -impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { - pub fn new( - curve_type: CurveType, - num_primitives: u32, - vertex_buffers: &[&'v DeviceSlice], - width_buffers: &[&'w DeviceSlice], - index_buffer: &'i DeviceSlice, - ) -> Result> { - // TODO (AL): Do some sanity checking on the values here - let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); - - let num_width_buffers = width_buffers.len() as u32; - let d_width_buffers: Vec<_> = width_buffers.iter().map(|b| b.as_device_ptr()).collect(); - - Ok(CurveArray { - curve_type, - num_primitives, - vertex_buffers: PhantomData, - num_vertices, - d_vertex_buffers, - vertex_stride_in_bytes: 0, - width_buffers: PhantomData, - num_width_buffers, - d_width_buffers, - width_stride_in_bytes: 0, - index_buffer, - index_stride_in_bytes: 0, - flags: GeometryFlags::None, - primitive_index_offset: 0, - }) - } - - pub fn vertex_stride(mut self, stride_in_bytes: u32) -> Self { - self.vertex_stride_in_bytes = stride_in_bytes; - self - } - - pub fn width_stride(mut self, stride_in_bytes: u32) -> Self { - self.vertex_stride_in_bytes = stride_in_bytes; - self - } - - pub fn index_stride(mut self, stride_in_bytes: u32) -> Self { - self.vertex_stride_in_bytes = stride_in_bytes; - self - } - - pub fn flags(mut self, flags: GeometryFlags) -> Self { - self.flags = flags; - self - } - - pub fn primitive_index_offset(mut self, offset: u32) -> Self { - self.primitive_index_offset = offset; - self - } -} - -impl<'v, 'w, 'i> BuildInput for CurveArray<'v, 'w, 'i> { - fn to_sys(&self) -> sys::OptixBuildInput { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CURVES, - input: sys::OptixBuildInputUnion { - curve_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputCurveArray { - curveType: self.curve_type.into(), - numPrimitives: self.num_primitives, - vertexBuffers: self.d_vertex_buffers.as_ptr() as *const CUdeviceptr, - numVertices: self.num_vertices, - vertexStrideInBytes: self.vertex_stride_in_bytes, - widthBuffers: self.d_width_buffers.as_ptr() as *const CUdeviceptr, - widthStrideInBytes: self.width_stride_in_bytes, - normalBuffers: std::ptr::null(), - normalStrideInBytes: 0, - indexBuffer: self.index_buffer.as_device_ptr(), - indexStrideInBytes: self.index_stride_in_bytes, - flag: self.flags as u32, - primitiveIndexOffset: self.primitive_index_offset, - }), - }, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub enum CurveType { - RoundLinear, - RoundQuadraticBSpline, - RoundCubicBSpline, -} - -impl From for sys::OptixPrimitiveType { - fn from(c: CurveType) -> Self { - match c { - CurveType::RoundLinear => sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR, - CurveType::RoundQuadraticBSpline => { - sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE - } - CurveType::RoundCubicBSpline => { - sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE - } - } - } -} diff --git a/crates/optix/src/custom_primitive_array.rs b/crates/optix/src/custom_primitive_array.rs deleted file mode 100644 index da126b7b..00000000 --- a/crates/optix/src/custom_primitive_array.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::{ - acceleration::{Aabb, BuildInput, GeometryFlags}, - error::Error, - sys, -}; -use cust::memory::{DeviceSlice, GpuBox}; -use cust_raw::CUdeviceptr; -type Result = std::result::Result; - -use std::hash::Hash; -use std::marker::PhantomData; - -pub struct CustomPrimitiveArray<'a, 's> { - aabb_buffers: Vec, - aabb_buffers_marker: PhantomData<&'a Aabb>, - num_primitives: u32, - stride_in_bytes: u32, - flags: Vec, - num_sbt_records: u32, - sbt_index_offset_buffer: Option<&'s DeviceSlice>, - sbt_index_offset_stride_in_bytes: u32, - primitive_index_offset: u32, -} - -impl<'a, 'g, 's> Hash for CustomPrimitiveArray<'a, 's> { - fn hash(&self, state: &mut H) { - state.write_usize(self.aabb_buffers.len()); - state.write_u32(self.num_primitives); - state.write_u32(self.stride_in_bytes); - self.flags.hash(state); - state.write_u32(self.num_sbt_records); - if let Some(b) = self.sbt_index_offset_buffer { - state.write_usize(b.len()); - } else { - state.write_usize(0); - } - state.write_u32(self.sbt_index_offset_stride_in_bytes); - state.write_u32(self.primitive_index_offset); - } -} - -impl<'a, 's> CustomPrimitiveArray<'a, 's> { - pub fn new( - aabb_buffers: &[&'a DeviceSlice], - flags: &[GeometryFlags], - ) -> Result> { - let num_primitives = aabb_buffers.len() as u32; - let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr()).collect(); - - Ok(CustomPrimitiveArray { - aabb_buffers, - aabb_buffers_marker: PhantomData, - num_primitives, - stride_in_bytes: 0, - flags: flags.to_vec(), - num_sbt_records: 1, - sbt_index_offset_buffer: None, - sbt_index_offset_stride_in_bytes: 0, - primitive_index_offset: 0, - }) - } - - pub fn stride(mut self, stride_in_bytes: u32) -> Self { - self.stride_in_bytes = stride_in_bytes; - self - } - - pub fn primitive_index_offset(mut self, offset: u32) -> Self { - self.primitive_index_offset = offset; - self - } - - pub fn num_sbt_records(mut self, num_sbt_records: u32) -> Self { - self.num_sbt_records = num_sbt_records; - self - } - - pub fn sbt_index_offset_buffer( - mut self, - sbt_index_offset_buffer: &'s DeviceSlice, - ) -> Self { - self.sbt_index_offset_buffer = Some(sbt_index_offset_buffer); - self - } - - pub fn sbt_index_offset_buffer_stride(mut self, stride_in_bytes: u32) -> Self { - self.sbt_index_offset_stride_in_bytes = stride_in_bytes; - self - } -} - -impl<'a, 's> BuildInput for CustomPrimitiveArray<'a, 's> { - fn to_sys(&self) -> sys::OptixBuildInput { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES, - input: sys::OptixBuildInputUnion { - custom_primitive_array: std::mem::ManuallyDrop::new( - sys::OptixBuildInputCustomPrimitiveArray { - aabbBuffers: self.aabb_buffers.as_ptr(), - numPrimitives: self.num_primitives, - strideInBytes: self.stride_in_bytes, - flags: self.flags.as_ptr() as *const u32, - numSbtRecords: self.num_sbt_records, - sbtIndexOffsetBuffer: if let Some(sbt_index_offset_buffer) = - self.sbt_index_offset_buffer - { - sbt_index_offset_buffer.as_device_ptr() - } else { - 0 - }, - sbtIndexOffsetSizeInBytes: 4, - sbtIndexOffsetStrideInBytes: self.sbt_index_offset_stride_in_bytes, - primitiveIndexOffset: self.primitive_index_offset, - }, - ), - }, - } - } -} diff --git a/crates/optix/src/instance_array.rs b/crates/optix/src/instance_array.rs deleted file mode 100644 index 52bbadad..00000000 --- a/crates/optix/src/instance_array.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::marker::PhantomData; -use std::hash::Hash; - -use crate::{acceleration::{Accel, BuildInput, TraversableHandle}, const_assert, const_assert_eq, sys}; -use cust::{memory::DeviceSlice, DeviceCopy}; -use cust_raw::CUdeviceptr; -use mint::RowMatrix3x4; - -#[repr(C, align(16))] -#[derive(Debug, Copy, Clone, DeviceCopy)] -pub struct Instance<'a> { - transform: RowMatrix3x4, - instance_id: u32, - sbt_offset: u32, - visibility_mask: u32, - flags: InstanceFlags, - traversable_handle: TraversableHandle, - pad: [u32; 2], - accel: PhantomData<&'a ()>, -} - -const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); -const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - - -bitflags::bitflags! { - #[derive(DeviceCopy)] - pub struct InstanceFlags: u32 { - const NONE = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE; - const DISABLE_TRIANGLE_FACE_CULLING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING; - const FLIP_TRIANGLE_FACING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING; - const DISABLE_ANYHIT = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT; - const ENFORCE_ANYHIT = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT; - const DISABLE_TRANSFORM = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; - } -} - -impl<'a> Instance<'a> { - pub fn new(accel: &'a Accel) -> Instance<'a> { - #[cfg_attr(rustfmt, rustfmt_skip)] - Instance { - transform: [ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0].into(), - instance_id: 0, - sbt_offset: 0, - visibility_mask: 255, - flags: InstanceFlags::NONE, - traversable_handle: accel.handle(), - pad: [0; 2], - accel: PhantomData, - } - } - - pub unsafe fn from_handle(traversable_handle: TraversableHandle) -> Instance<'static> { - #[cfg_attr(rustfmt, rustfmt_skip)] - Instance { - transform: [ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0].into(), - instance_id: 0, - sbt_offset: 0, - visibility_mask: 255, - flags: InstanceFlags::NONE, - traversable_handle, - pad: [0; 2], - accel: PhantomData, - } - } - - pub fn transform>>(mut self, transform: T) -> Instance<'a> { - self.transform = transform.into(); - self - } - - pub fn instance_id(mut self, instance_id: u32) -> Instance<'a> { - self.instance_id = instance_id; - self - } - - pub fn sbt_offset(mut self, sbt_offset: u32) -> Instance<'a> { - self.sbt_offset = sbt_offset; - self - } - - pub fn visibility_mask(mut self, visibility_mask: u8) -> Instance<'a> { - self.visibility_mask = visibility_mask as u32; - self - } - - pub fn flags(mut self, flags: InstanceFlags) -> Instance<'a> { - self.flags = flags; - self - } -} - -pub struct InstanceArray<'i, 'a> { - instances: &'i DeviceSlice>, -} - -impl<'i, 'a> InstanceArray<'i, 'a> { - pub fn new(instances: &'i DeviceSlice>) -> InstanceArray<'i, 'a> { - InstanceArray { instances } - } -} - -impl<'i, 'a> Hash for InstanceArray<'i, 'a> { - fn hash(&self, state: &mut H) { - state.write_usize(self.instances.len()); - } -} - -impl<'i, 'a> BuildInput for InstanceArray<'i, 'a> { - fn to_sys(&self) -> sys::OptixBuildInput { - cfg_if::cfg_if! { - if #[cfg(any(feature="optix72", feature="optix73"))] { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, - input: sys::OptixBuildInputUnion { - instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), - numInstances: self.instances.len() as u32, - }) - } - } - } else { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, - input: sys::OptixBuildInputUnion { - instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), - numInstances: self.instances.len() as u32, - aabbs: 0, - numAabbs: 0, - }) - } - } - } - } - } -} - -pub struct InstancePointerArray<'i> { - instances: &'i DeviceSlice, -} - -impl<'i> InstancePointerArray<'i> { - pub fn new(instances: &'i DeviceSlice) -> InstancePointerArray { - InstancePointerArray { instances } - } -} - -impl<'i> Hash for InstancePointerArray<'i> { - fn hash(&self, state: &mut H) { - state.write_usize(self.instances.len()); - } -} - - -impl<'i> BuildInput for InstancePointerArray<'i> { - fn to_sys(&self) -> sys::OptixBuildInput { - cfg_if::cfg_if! { - if #[cfg(any(feature="optix72", feature="optix73"))] { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, - input: sys::OptixBuildInputUnion { - instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), - numInstances: self.instances.len() as u32, - }) - } - } - } else { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, - input: sys::OptixBuildInputUnion { - instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), - numInstances: self.instances.len() as u32, - aabbs: 0, - numAabbs: 0, - }) - } - } - } - } - } -} - diff --git a/crates/optix/src/module.rs b/crates/optix/src/module.rs deleted file mode 100644 index a1fe0cfb..00000000 --- a/crates/optix/src/module.rs +++ /dev/null @@ -1,285 +0,0 @@ -use crate::{context::DeviceContext, error::Error, optix_call, sys}; -type Result = std::result::Result; - -use std::ffi::{CStr, CString}; - -#[repr(transparent)] -pub struct Module { - pub(crate) raw: sys::OptixModule, -} - -/// Module compilation optimization level -#[repr(u32)] -#[derive(Debug, Hash, PartialEq, Copy, Clone)] -pub enum CompileOptimizationLevel { - Default = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_DEFAULT, - Level0 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_0, - Level1 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_1, - Level2 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_2, - Level3 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_3, -} - -/// Module compilation debug level -#[repr(u32)] -#[derive(Debug, Hash, PartialEq, Copy, Clone)] -pub enum CompileDebugLevel { - None = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_NONE, - LineInfo = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO, - Full = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_FULL, -} - -cfg_if::cfg_if! { - if #[cfg(any(feature="optix72", feature="optix73"))] { - #[repr(C)] - #[derive(Debug, Hash, PartialEq, Copy, Clone)] - pub struct ModuleCompileOptions { - pub max_register_count: i32, - pub opt_level: CompileOptimizationLevel, - pub debug_level: CompileDebugLevel, - } - - impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { - fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { - sys::OptixModuleCompileOptions { - maxRegisterCount: o.max_register_count, - optLevel: o.opt_level as u32, - debugLevel: o.debug_level as u32, - boundValues: std::ptr::null(), - numBoundValues: 0, - } - } - } - } else { - #[repr(C)] - #[derive(Debug, Hash, PartialEq, Copy, Clone)] - pub struct ModuleCompileOptions { - pub max_register_count: i32, - pub opt_level: CompileOptimizationLevel, - pub debug_level: CompileDebugLevel, - } - - impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { - fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { - sys::OptixModuleCompileOptions { - maxRegisterCount: o.max_register_count, - optLevel: o.opt_level as u32, - debugLevel: o.debug_level as u32, - } - } - } - } -} - -bitflags::bitflags! { - pub struct TraversableGraphFlags: u32 { - const ALLOW_ANY = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY; - const ALLOW_SINGLE_GAS = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS; - const ALLOW_SINGLE_LEVEL_INSTANCING = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; - } -} - -bitflags::bitflags! { - pub struct ExceptionFlags: u32 { - const NONE = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_NONE; - const STACK_OVERFLOW = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW; - const TRACE_DEPTH = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_TRACE_DEPTH; - const USER = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_USER; - const DEBUG = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_DEBUG; - } -} - -bitflags::bitflags! { - pub struct PrimitiveTypeFlags: i32 { - const DEFAULT = 0; - const CUSTOM = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM; - const ROUND_QUADRATIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE; - const ROUND_CUBIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE; - const ROUND_LINEAR = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR; - const TRIANGLE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; - } -} - -#[repr(u32)] -pub enum PrimitiveType { - RoundQuadraticBspline = - sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE as u32, - RoundCubicBspline = - sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE as u32, - RoundLinear = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR as u32, - Triangle = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE as u32, -} - -#[derive(Debug, Hash, PartialEq, Clone)] -pub struct PipelineCompileOptions { - uses_motion_blur: bool, - traversable_graph_flags: TraversableGraphFlags, - num_payload_values: i32, - num_attribute_values: i32, - exception_flags: ExceptionFlags, - pipeline_launch_params_variable_name: Option, - primitive_type_flags: PrimitiveTypeFlags, -} - -impl PipelineCompileOptions { - pub fn new() -> PipelineCompileOptions { - PipelineCompileOptions { - uses_motion_blur: false, - traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, - num_payload_values: 0, - num_attribute_values: 0, - exception_flags: ExceptionFlags::NONE, - pipeline_launch_params_variable_name: None, - primitive_type_flags: PrimitiveTypeFlags::DEFAULT, - } - } - - pub fn build(&self) -> sys::OptixPipelineCompileOptions { - cfg_if::cfg_if! { - if #[cfg(feature="optix73")] { - sys::OptixPipelineCompileOptions { - usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, - traversableGraphFlags: self.traversable_graph_flags.bits(), - numPayloadValues: self.num_payload_values, - numAttributeValues: self.num_attribute_values, - exceptionFlags: self.exception_flags.bits(), - pipelineLaunchParamsVariableName: if let Some(ref name) = self - .pipeline_launch_params_variable_name { - name.as_ptr() - } else { - std::ptr::null() - }, - usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, - reserved: 0, - reserved2: 0, - } - } else { - sys::OptixPipelineCompileOptions { - usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, - traversableGraphFlags: self.traversable_graph_flags.bits(), - numPayloadValues: self.num_payload_values, - numAttributeValues: self.num_attribute_values, - exceptionFlags: self.exception_flags.bits(), - pipelineLaunchParamsVariableName: if let Some(ref name) = self - .pipeline_launch_params_variable_name { - name.as_ptr() - } else { - std::ptr::null() - }, - usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, - } - } - } - } - - pub fn uses_motion_blur(mut self, umb: bool) -> Self { - self.uses_motion_blur = umb; - self - } - - pub fn traversable_graph_flags(mut self, tgf: TraversableGraphFlags) -> Self { - self.traversable_graph_flags = tgf; - self - } - - pub fn num_payload_values(mut self, npv: i32) -> Self { - self.num_payload_values = npv; - self - } - - pub fn num_attribute_values(mut self, nav: i32) -> Self { - self.num_attribute_values = nav; - self - } - - pub fn exception_flags(mut self, ef: ExceptionFlags) -> Self { - self.exception_flags = ef; - self - } - - pub fn pipeline_launch_params_variable_name(mut self, name: &str) -> Self { - self.pipeline_launch_params_variable_name = Some( - CString::new(name).expect("pipeline launch params variable name contains nul bytes"), - ); - self - } -} - -/// # Creating and destroying `Module`s -impl Module { - pub fn new( - ctx: &mut DeviceContext, - module_compile_options: &ModuleCompileOptions, - pipeline_compile_options: &PipelineCompileOptions, - ptx: &str, - ) -> Result<(Module, String)> { - let cptx = CString::new(ptx).unwrap(); - let mut log = [0u8; 4096]; - let mut log_len = log.len(); - - let mopt = module_compile_options.into(); - let popt = pipeline_compile_options.build(); - - let mut raw = std::ptr::null_mut(); - let res = unsafe { - optix_call!(optixModuleCreateFromPTX( - ctx.raw, - &mopt as *const _, - &popt, - cptx.as_ptr(), - cptx.as_bytes().len(), - log.as_mut_ptr() as *mut i8, - &mut log_len, - &mut raw, - )) - }; - - let log = CStr::from_bytes_with_nul(&log[0..log_len]) - .unwrap() - .to_string_lossy() - .into_owned(); - - match res { - Ok(()) => Ok((Module { raw }, log)), - Err(source) => Err(Error::ModuleCreation { source, log }), - } - } - - /// Returns a module containing the intersection program for the built-in - /// primitive type specified by the builtinISOptions. This module must be used - /// as the moduleIS for the OptixProgramGroupHitgroup in any SBT record for - /// that primitive type. - pub fn builtin_is_module_get( - ctx: &mut DeviceContext, - module_compile_options: &ModuleCompileOptions, - pipeline_compile_options: &PipelineCompileOptions, - builtin_is_module_type: PrimitiveType, - uses_motion_blur: bool, - ) -> Result { - let is_options = sys::OptixBuiltinISOptions { - builtinISModuleType: builtin_is_module_type as u32, - usesMotionBlur: if uses_motion_blur { 1 } else { 0 }, - }; - - let mut raw = std::ptr::null_mut(); - - unsafe { - optix_call!(optixBuiltinISModuleGet( - ctx.raw, - module_compile_options as *const _ as *const _, - pipeline_compile_options as *const _ as *const _, - &is_options as *const _, - &mut raw, - )) - .map(|_| Module { raw }) - .map_err(|e| Error::from(e)) - } - } -} - -impl Drop for Module { - fn drop(&mut self) { - unsafe { - sys::optixModuleDestroy(self.raw); - } - } -} diff --git a/crates/optix/src/triangle_array.rs b/crates/optix/src/triangle_array.rs deleted file mode 100644 index f12ee78b..00000000 --- a/crates/optix/src/triangle_array.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::hash::Hash; -use std::marker::PhantomData; - -use crate::{ - acceleration::{BuildInput, GeometryFlags}, - sys, -}; -use cust::memory::{DevicePointer, DeviceSlice}; -use cust_raw::CUdeviceptr; - -#[repr(u32)] -#[derive(Copy, Clone, PartialEq)] -pub enum VertexFormat { - None = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_NONE as u32, - Float3 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT3 as u32, - Float2 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT2 as u32, - Half3 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF3 as u32, - Half2 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF2 as u32, - SNorm16 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_3 as u32, - SNorm32 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_2 as u32, -} - -#[repr(u32)] -#[derive(Copy, Clone, PartialEq)] -pub enum IndicesFormat { - None = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE as u32, - Short3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3 as u32, - Int3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3 as u32, -} - -#[repr(u32)] -#[derive(Copy, Clone, PartialEq)] -pub enum TransformFormat { - None = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, - MatrixFloat12 = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12, -} - -pub trait Vertex: cust::memory::DeviceCopy { - const FORMAT: VertexFormat; - const STRIDE: u32 = 0; -} - -#[cfg(feature = "half")] -impl Vertex for [half::f16; 2] { - const FORMAT: VertexFormat = VertexFormat::Half2; -} - -#[cfg(feature = "half")] -impl Vertex for [half::f16; 3] { - const FORMAT: VertexFormat = VertexFormat::Half3; -} - -#[cfg(feature = "half")] -impl Vertex for mint::Vector2 { - const FORMAT: VertexFormat = VertexFormat::Half2; -} - -#[cfg(feature = "half")] -impl Vertex for mint::Vector3 { - const FORMAT: VertexFormat = VertexFormat::Half3; -} - -impl Vertex for [f32; 2] { - const FORMAT: VertexFormat = VertexFormat::Float2; -} - -impl Vertex for [f32; 3] { - const FORMAT: VertexFormat = VertexFormat::Float3; -} - -impl Vertex for [i16; 3] { - const FORMAT: VertexFormat = VertexFormat::SNorm16; -} - -impl Vertex for [i32; 3] { - const FORMAT: VertexFormat = VertexFormat::SNorm32; -} - -impl Vertex for mint::Vector2 { - const FORMAT: VertexFormat = VertexFormat::Float2; -} - -impl Vertex for mint::Vector3 { - const FORMAT: VertexFormat = VertexFormat::Float3; -} - -impl Vertex for mint::Vector3 { - const FORMAT: VertexFormat = VertexFormat::SNorm16; -} - -impl Vertex for mint::Vector3 { - const FORMAT: VertexFormat = VertexFormat::SNorm32; -} - -pub trait IndexTriple: cust::memory::DeviceCopy { - const FORMAT: IndicesFormat; - const STRIDE: u32 = 0; -} - -impl IndexTriple for [u16; 3] { - const FORMAT: IndicesFormat = IndicesFormat::Short3; -} - -impl IndexTriple for [u32; 3] { - const FORMAT: IndicesFormat = IndicesFormat::Int3; -} - -impl IndexTriple for mint::Vector3 { - const FORMAT: IndicesFormat = IndicesFormat::Short3; -} - -impl IndexTriple for mint::Vector3 { - const FORMAT: IndicesFormat = IndicesFormat::Int3; -} - -pub struct TriangleArray<'v, 'g, V: Vertex> { - // We hold slices here to make sure the referenced device memory remains - // valid for the lifetime of the build input - vertex_buffers: PhantomData<&'v V>, - num_vertices: u32, - d_vertex_buffers: Vec, - // per-sbt-record geometry flags - geometry_flags: &'g [GeometryFlags], - pre_transform: Option>, -} - -impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { - pub fn new(vertex_buffers: &[&'v DeviceSlice], geometry_flags: &'g [GeometryFlags]) -> Self { - // TODO (AL): do some sanity checking on the slice lengths here - let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); - TriangleArray { - vertex_buffers: PhantomData, - num_vertices, - d_vertex_buffers, - geometry_flags, - pre_transform: None, - } - } - - pub fn pre_transform(mut self, pre_transform: DevicePointer<[f32; 12]>) -> Self { - self.pre_transform = Some(pre_transform); - self - } -} - -impl<'v, 'g, V: Vertex> Hash for TriangleArray<'v, 'g, V> { - fn hash(&self, state: &mut H) { - state.write_u32(self.num_vertices); - state.write_usize(self.d_vertex_buffers.len()); - self.geometry_flags.hash(state); - } -} - -impl<'v, 'g, V: Vertex> BuildInput for TriangleArray<'v, 'g, V> { - fn to_sys(&self) -> sys::OptixBuildInput { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, - input: sys::OptixBuildInputUnion { - triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { - vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, - numVertices: self.num_vertices, - vertexFormat: V::FORMAT as u32, - vertexStrideInBytes: V::STRIDE, - indexBuffer: 0, - numIndexTriplets: 0, - indexFormat: 0, - indexStrideInBytes: 0, - flags: self.geometry_flags.as_ptr() as *const _, - numSbtRecords: 1, - sbtIndexOffsetBuffer: 0, - sbtIndexOffsetSizeInBytes: 0, - sbtIndexOffsetStrideInBytes: 0, - primitiveIndexOffset: 0, - preTransform: if let Some(t) = self.pre_transform { - t.as_raw() - } else { - 0 - }, - transformFormat: if self.pre_transform.is_some() { - sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 - } else { - sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE - }, - }), - }, - } - } -} - -pub struct IndexedTriangleArray<'v, 'i, V: Vertex, I: IndexTriple> { - // We hold slices here to make sure the referenced device memory remains - // valid for the lifetime of the build input - vertex_buffers: PhantomData<&'v V>, - num_vertices: u32, - d_vertex_buffers: Vec, - index_buffer: &'i DeviceSlice, - // per-object geometry flags - geometry_flags: Vec, - pre_transform: Option>, -} - -impl<'v, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'i, V, I> { - pub fn new( - vertex_buffers: &[&'v DeviceSlice], - index_buffer: &'i DeviceSlice, - geometry_flags: &[GeometryFlags], - ) -> Self { - let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); - IndexedTriangleArray { - vertex_buffers: PhantomData, - num_vertices, - d_vertex_buffers, - geometry_flags: geometry_flags.to_vec(), - index_buffer, - pre_transform: None, - } - } - - pub fn pre_transform(mut self, pre_transform: DevicePointer<[f32; 12]>) -> Self { - self.pre_transform = Some(pre_transform); - self - } -} - -impl<'v, 'i, V: Vertex, I: IndexTriple> Hash for IndexedTriangleArray<'v, 'i, V, I> { - fn hash(&self, state: &mut H) { - state.write_u32(self.num_vertices); - state.write_usize(self.d_vertex_buffers.len()); - self.geometry_flags.hash(state); - state.write_usize(self.index_buffer.len()); - } -} - -impl<'v, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, 'i, V, I> { - fn to_sys(&self) -> sys::OptixBuildInput { - sys::OptixBuildInput { - type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, - input: sys::OptixBuildInputUnion { - triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { - vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, - numVertices: self.num_vertices, - vertexFormat: V::FORMAT as u32, - vertexStrideInBytes: V::STRIDE, - indexBuffer: self.index_buffer.as_device_ptr(), - numIndexTriplets: self.index_buffer.len() as u32, - indexFormat: I::FORMAT as u32, - indexStrideInBytes: I::STRIDE, - flags: self.geometry_flags.as_ptr() as *const _, - numSbtRecords: 1, - sbtIndexOffsetBuffer: 0, - sbtIndexOffsetSizeInBytes: 0, - sbtIndexOffsetStrideInBytes: 0, - primitiveIndexOffset: 0, - preTransform: if let Some(t) = self.pre_transform { - t.as_raw() - } else { - 0 - }, - transformFormat: if self.pre_transform.is_some() { - sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 - } else { - sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE - }, - }), - }, - } - } -} From 6f2abe742ea0895df9f02e699709ca403bb12eca Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sat, 6 Nov 2021 22:27:55 +1300 Subject: [PATCH 063/100] Add a simple memcpy_htod wrapper --- crates/cust/src/memory/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index ee14ab21..a31e998e 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -88,8 +88,11 @@ pub use self::malloc::*; pub use self::pointer::*; pub use self::unified::*; +use crate::error::*; + use core::marker::PhantomData; use core::num::*; +use std::ffi::c_void; /// A trait describing a generic buffer that can be accessed from the GPU. This could be either a [`UnifiedBuffer`] /// or a regular [`DBuffer`]. @@ -331,3 +334,13 @@ impl_device_copy_glam! { mint::ColumnMatrix2, mint::ColumnMatrix3, mint::ColumnMatrix4, mint::ColumnMatrix3x4, mint::RowMatrix2, mint::RowMatrix3, mint::RowMatrix4, mint::RowMatrix3x4, } + +/// Simple wrapper over cuMemcpyHtoD_v2 +pub unsafe fn memcpy_htod( + d_ptr: cust_raw::CUdeviceptr, + src_ptr: *const c_void, + size: usize, +) -> CudaResult<()> { + crate::sys::cuMemcpyHtoD_v2(d_ptr, src_ptr, size).to_result()?; + Ok(()) +} From 7d4575713c7cf7b3b16fe664674685553f56b430 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sat, 6 Nov 2021 22:28:06 +1300 Subject: [PATCH 064/100] Add back pointer offset methods --- crates/cust/src/memory/pointer.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/cust/src/memory/pointer.rs b/crates/cust/src/memory/pointer.rs index 0de4fb40..72a2b288 100644 --- a/crates/cust/src/memory/pointer.rs +++ b/crates/cust/src/memory/pointer.rs @@ -8,6 +8,7 @@ use core::{ }; use std::ffi::c_void; use std::marker::PhantomData; +use std::mem::size_of; /// A pointer to device memory. /// @@ -79,7 +80,6 @@ impl DevicePointer { } } - /* TODO (AL): see if we need to reimplement these /// Calculates the offset from a device pointer. /// /// `count` is in units of T; eg. a `count` of 3 represents a pointer offset of @@ -117,7 +117,11 @@ impl DevicePointer { where T: Sized, { - Self::wrap(self.0.offset(count)) + let ptr = self.ptr + (count as usize * size_of::()) as u64; + Self { + ptr, + marker: PhantomData, + } } /// Calculates the offset from a device pointer using wrapping arithmetic. @@ -154,7 +158,13 @@ impl DevicePointer { where T: Sized, { - unsafe { Self::wrap(self.0.wrapping_offset(count)) } + let ptr = self + .ptr + .wrapping_add((count as usize * size_of::()) as u64); + Self { + ptr, + marker: PhantomData, + } } /// Calculates the offset from a pointer (convenience for `.offset(count as isize)`). @@ -302,7 +312,6 @@ impl DevicePointer { { self.wrapping_offset((count as isize).wrapping_neg()) } - */ } /// A pointer to unified memory. From b3425b33fd34943162c42b54816727bf404b595d Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sat, 6 Nov 2021 22:31:14 +1300 Subject: [PATCH 065/100] Big structure reorg and documentation push - Reorganized the module structure to something less fragmented. Modules are longer but more cohesive. - Integrated the optix programming guide inline into the module documentation. You need to build the docs with: RUSTDOCFLAGS="--html-in-header katex-header.html" cargo doc --no-deps To see the equations (this is done automatically on docs.rs) --- crates/optix/Cargo.toml | 3 + .../examples/ex02_pipeline/src/renderer.rs | 15 +- .../examples/ex03_window/src/renderer.rs | 13 +- .../optix/examples/ex04_mesh/src/renderer.rs | 25 +- crates/optix/images/example_sbt.jpg | Bin 0 -> 154783 bytes crates/optix/images/example_sbt.png | Bin 0 -> 45105 bytes crates/optix/images/optix_programs.jpg | Bin 0 -> 23032 bytes crates/optix/images/scene_graph.jpg | Bin 0 -> 40799 bytes crates/optix/images/scene_graph.png | Bin 0 -> 42422 bytes crates/optix/images/traversables_graph.jpg | Bin 0 -> 29885 bytes crates/optix/src/acceleration.md | 788 +++++++++ crates/optix/src/acceleration.rs | 1519 +++++++++++++---- crates/optix/src/context.md | 161 ++ crates/optix/src/context.rs | 162 -- crates/optix/src/denoiser.md | 14 + crates/optix/src/error.rs | 3 + crates/optix/src/impl_glam.rs | 2 +- crates/optix/src/introduction.md | 310 ++++ crates/optix/src/lib.rs | 68 +- crates/optix/src/pipeline.md | 286 ++++ crates/optix/src/pipeline.rs | 702 +++++++- crates/optix/src/prelude.rs | 15 +- crates/optix/src/program_group.rs | 394 ----- crates/optix/src/shader_binding_table.md | 240 +++ crates/optix/src/shader_binding_table.rs | 2 +- katex-header.html | 15 + 26 files changed, 3773 insertions(+), 964 deletions(-) create mode 100644 crates/optix/images/example_sbt.jpg create mode 100644 crates/optix/images/example_sbt.png create mode 100644 crates/optix/images/optix_programs.jpg create mode 100644 crates/optix/images/scene_graph.jpg create mode 100644 crates/optix/images/scene_graph.png create mode 100644 crates/optix/images/traversables_graph.jpg create mode 100644 crates/optix/src/acceleration.md create mode 100644 crates/optix/src/context.md create mode 100644 crates/optix/src/denoiser.md create mode 100644 crates/optix/src/introduction.md create mode 100644 crates/optix/src/pipeline.md delete mode 100644 crates/optix/src/program_group.rs create mode 100644 crates/optix/src/shader_binding_table.md create mode 100644 katex-header.html diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index f45eb4ea..e088fd14 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -18,9 +18,12 @@ glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", fe half = { version = "^1.8", optional = true } memoffset = "0.6.4" mint = "0.5.8" +embed-doc-image = {version = "0.1.4"} [build-dependencies] bindgen = "0.59" cc = "1.0.71" find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } +[package.metadata.docs.rs] +rustdoc-args = [ "--html-in-header", "katex-header.html" ] diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 15c80596..4baca2df 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -1,18 +1,17 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::{Device, DeviceAttribute}; -use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; use cust::DeviceCopy; use optix::{ context::DeviceContext, - module::{ + pipeline::{ CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, - PipelineCompileOptions, TraversableGraphFlags, + Pipeline, PipelineCompileOptions, PipelineLinkOptions, ProgramGroup, ProgramGroupDesc, + TraversableGraphFlags, }, - pipeline::{Pipeline, PipelineLinkOptions}, - program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; @@ -44,7 +43,7 @@ impl Renderer { CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; - let mut ctx = DeviceContext::new(&cuda_context)?; + let mut ctx = DeviceContext::new(&cuda_context, false)?; ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module @@ -144,7 +143,7 @@ impl Renderer { pipeline.set_stack_size(2 * 1024, 2 * 1024, 2 * 1024, 1)?; - let mut color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; + let color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; let launch_params = LaunchParams { frame_id: 0, @@ -192,7 +191,7 @@ impl Renderer { optix::launch( &self.pipeline, &self.stream, - &mut self.buf_launch_params, + self.buf_launch_params.as_device_ptr(), &self.sbt, self.launch_params.fb_size.x as u32, self.launch_params.fb_size.y as u32, diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 9bebc20e..ed324c88 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -1,17 +1,16 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::Device; -use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; use optix::{ context::DeviceContext, - module::{ + pipeline::{ CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, - PipelineCompileOptions, TraversableGraphFlags, + Pipeline, PipelineCompileOptions, PipelineLinkOptions, ProgramGroup, ProgramGroupDesc, + TraversableGraphFlags, }, - pipeline::{Pipeline, PipelineLinkOptions}, - program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; @@ -42,7 +41,7 @@ impl Renderer { CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; - let mut ctx = DeviceContext::new(&cuda_context)?; + let mut ctx = DeviceContext::new(&cuda_context, false)?; ctx.set_log_callback(|_level, tag, msg| println!("[{}]: {}", tag, msg), 4)?; // create module @@ -187,7 +186,7 @@ impl Renderer { optix::launch( &self.pipeline, &self.stream, - &mut self.buf_launch_params, + self.buf_launch_params.as_device_ptr(), &self.sbt, self.launch_params.fb_size.x as u32, self.launch_params.fb_size.y as u32, diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 952697eb..afc7dc6c 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -5,18 +5,18 @@ use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; use optix::{ + acceleration::IndexedTriangleArray, acceleration::{ - Accel, AccelBuildOptions, BuildFlags, BuildOperation, GeometryFlags, TraversableHandle, + Accel, AccelBuildOptions, BuildFlags, BuildOperation, GeometryFlags, Traversable, + TraversableHandle, }, context::DeviceContext, - module::{ + pipeline::{ CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, - PipelineCompileOptions, TraversableGraphFlags, + Pipeline, PipelineCompileOptions, PipelineLinkOptions, ProgramGroup, ProgramGroupDesc, + TraversableGraphFlags, }, - pipeline::{Pipeline, PipelineLinkOptions}, - program_group::{ProgramGroup, ProgramGroupDesc}, shader_binding_table::{SbtRecord, ShaderBindingTable}, - triangle_array::IndexedTriangleArray, }; use glam::{ivec2, vec3, IVec2, IVec3, Vec3, Vec4}; @@ -114,14 +114,15 @@ impl Renderer { let buf_indices = DeviceBuffer::from_slice(&indices)?; let geometry_flags = GeometryFlags::None; - let triangle_input = - IndexedTriangleArray::new(&[&buf_vertex], &buf_indices, &[geometry_flags]); - + let build_inputs = [IndexedTriangleArray::new( + &[&buf_vertex], + &buf_indices, + &[geometry_flags], + )]; let accel_options = AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); - let build_inputs = vec![triangle_input]; - + // build and compact the GAS let gas = Accel::build(&ctx, &stream, &[accel_options], &build_inputs, true)?; stream.synchronize()?; @@ -239,7 +240,7 @@ impl Renderer { optix::launch( &self.pipeline, &self.stream, - &mut self.buf_launch_params, + self.buf_launch_params.as_device_ptr(), &self.sbt, self.launch_params.frame.size.x as u32, self.launch_params.frame.size.y as u32, diff --git a/crates/optix/images/example_sbt.jpg b/crates/optix/images/example_sbt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1898e0a029f027b625ecaf0ef831199b7f7da34c GIT binary patch literal 154783 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

    HT_zo%H;r2NM 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 E0?>@B*8l(j literal 0 HcmV?d00001 diff --git a/crates/optix/images/optix_programs.jpg b/crates/optix/images/optix_programs.jpg new file mode 100644 index 0000000000000000000000000000000000000000..850fd7e884bb837758e95a33bd5fc1188ebe2b8e GIT binary patch literal 23032 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%0DVRtaGlVtkY;jA!u2cdgqp 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 idaFe2@`WeOmeP3VcJ8SNX~GbLL+`6)$rD literal 0 HcmV?d00001 diff --git a/crates/optix/images/scene_graph.png b/crates/optix/images/scene_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..360becf734429daf87f1709befe25fe07b6b09b4 GIT binary patch literal 42422 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%0Dg@sSHK)JDP&cUlT0{_4srlO-V)XgZCSTu;3ckVb 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

      ^vfaNpf%nyo36*Pund6T+WD_^54iIA~bS9 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 OL1ZKqBVDz2< literal 0 HcmV?d00001 diff --git a/crates/optix/images/traversables_graph.jpg b/crates/optix/images/traversables_graph.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7250201e42ca529682cfbd418755c66c2161128d GIT binary patch literal 29885 zcmc$`1yq~cwls@m_b3W^C@@^6E^*9^eHdQkQ-{lNo5z+F3l67T@<5bgdF|0-yY9-w19#JvAg z{R!X!`a=L38agg6IvNHh0PT;)=#L07p3)QXsgcOU4nHG)E~Ng^*ulxWYy^{EK=75O zb7WL>Nht$3qx^%T59142lgt)UCgIm|X1;!{LwC~vtUoR$4Y=Ze{_lhxpVc_kT2Ni! zK+fb2z}WC@AZWl2$WhTZj9>ZS@_QOHtA_>KZ)KWVH$K@FQPnrvCnMd#wB$YR9}w`= zx9`sf)Q96FoeoU`IvrDEO15&MpGm!lInE9@kciJXNY2UTbpO5DK@f_PWCKp9{*O=Q zz62rTG|$5>P$q^hMM93mv;=>W&A~06I+v=B7Yz~qm8cyMtkclm{P1iwGY!P(qG8W@ z(Eh}H-`O!7^cjZ-N=_B!1DO!5#r$~(coy5Fd7^wkZZ+f6XSC5su6ydcRIKT@%haLX zZrFuz&yE|04L})6+o}MH=4Z&1Cl9~VrXUvDI-*YvESH_$dVCugo(xGO;7DnBD7-#A z6+r){UqR0Y zU%d^Lsx>{EUL!VpZidNN2U5wXOQdu;=My>6P#U*D%-5D@C-Zf&a!TK-7g|{?m`D-Z zBqCHEW)Y%u(Jh?lDJK*x)e&&=;Ea%-eU99*VA6(RAteeVE%O#B4RC%ZwL#OBzY_pi z;k|}}cYBm)DkoTMO?RZ*P-|zpC=_Z!PBsW36NykJQ4eyc-i~%ujcNyL7*%#B2~ErP zwuNLkpxo-tq*LNVZ5OpeqLOQ0Qui&tm%4ISjm(?U5XO=d{0( zVEXmSZQw2FqT#EwnK{w(2#mG|fd@w_EPnNHmlJ7Um8STM&3UEz?eOU17y; z{=0GhAHT9o(O<8$-2uwOC*Kytl7NCZjo$^OeQh#WBZ<$tP&kjta<-`}c z(J+M-=8gVRD~YN5Ol!*j7*&W0xfK>&#qd!H2vVC_4rr15%xesv?jHO!MUYLIh3}m- zK7Y8YHjzFgu2a!IEN4!kQC!8f;0&2c%%`P%2O&zQS{D(HPx<;le7LY8F@z^}ab}b$ zLbJx)Q5Q1ojMLB?5NIzc@oL(OQWKbB1T3s0rcN_yV#E>Wy|sOJxEEh6vX!JeJ?%o& z?wV>y8$jy_MvqcmvcEx*;~9GdB%d`k2B9*AkCYVhaQcZjV0uF(x?RKE^~oav z`*RCb20YaY`U;H#!@ri=t{Ol8a}kLGeU}WB5=84lt34f6FaQjVw7mK$`lB2M^YD`i zUvae?gs~8LHb~^I=Gq!HBbs9BzQ@oQ&%R`|+-h;^DGO*4b0F9ux%u|y+8NK(rr5fI7oqI z+SaJI9h?@GZ=*?xIRvM~8KPKcpknbui;b+UD{=fOXd>BrOs z>?!ATF7Ifr;NHGboTjUz)@;5tatk3_+zC4v^+S~TyGHFKYlQmqYZ>N5he2J;y8

      $Z1ZBSIfX?d7xu2Q#<#D{R8(XFLJ#Qh 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 AR{#J2 literal 0 HcmV?d00001 diff --git a/crates/optix/src/acceleration.md b/crates/optix/src/acceleration.md new file mode 100644 index 00000000..1d5e5893 --- /dev/null +++ b/crates/optix/src/acceleration.md @@ -0,0 +1,788 @@ +# Acceleration Structures + +- [Building](#building) + - [Building Safe API](#building-safe-api) + - [Buliding Unsafe API](#building-unsafe-api) +- [Primitive Build Inputs](#primitive-build-inputs) +- [Build Flags](#build-flags) +- [Dynamic Updates](#dynamic-updates) + - [Dynamic Updates Safe API](#dynamic-updates-safe-api) + - [Dynamic Updates Unsafe API](#dynamic-updates-unsafe-api) +- [Compaction](#compaction) + - [Compaction Safe API](#compaction-safe-api) + - [Compaction Unsafe API](#compaction-unsafe-api) +- [Traversable Objects](#traversable-objects) + - [Traversable Objects Safe API](#traversable-objects-safe-api) + - [Traversable Objects Unsafe API](#traversable-objects-unsafe-api) +- [Motion Blur](#motion-blur) + - [Basics](#basics) + - [Motion Geometry Acceleration Structure](#motion-geometry-acceleration-structure) + - [Motion Instance Acceleration Structure](#motion-instance-acceleration-structure) + - [Motion Matrix Transform](#motion-matrix-transform) + - [Motion Scale Rotate Translate Transform](#motion-scale-rotate-translate-transform) + - [Transforms Trade-Offs](#transforms-trade-offs) + + +NVIDIA OptiX 7 provides acceleration structures to optimize the search for the +intersection of rays with the geometric data in the scene. Acceleration structures +can contain two types of data: geometric primitives (a geometry-AS) or instances +(an instance-AS). Acceleration structures are created on the device using a set +of functions. These functions enable overlapping and pipelining of acceleration +structure creation, called a build. The functions use one or more [`BuildInput`] +structs to specify the geometry plus a set of parameters to control the build. + +Acceleration structures have size limits, listed in “Limits”. For an instance +acceleration structure, the number of instances has an upper limit. For a geometry +acceleration structure, the number of geometric primitives is limited, +specifically the total number of primitives in its build inputs, multiplied by the +number of motion keys. + +The following acceleration structure types are supported: + +#### Instance acceleration structures +- [`InstanceArray`](crate::instance_array::InstanceArray) +- [`InstancePointerArray`](crate::instance_array::InstancePointerArray) + +#### Geometry acceleration structure containing built-in triangles +- [`TriangleArray`](crate::triangle_array::TriangleArray) +- [`IndexedTriangleArray`](crate::triangle_array::IndexedTriangleArray) + +#### Geometry acceleration structure containing built-in curves +- [`CurveArray`](crate::curve_array::CurveArray) + +#### Geometry acceleration structure containing custom primitives +- [`CustomPrimitiveArray`](crate::custom_primitive_array::CustomPrimitiveArray) + +## Building + +For geometry-AS builds, each build input can specify a set of triangles, a set +of curves, or a set of user-defined primitives bounded by specified axis-aligned +bounding boxes. Multiple build inputs can be passed as an array to [`accel_build`] +to combine different meshes into a single acceleration structure. All build +inputs for a single build must agree on the build input type. + +Instance acceleration structures have a single build input and specify an array +of instances. Each [`Instance`](crate::instance_array::Instance) includes a ray transformation and an +[`TraversableHandle`] that refers to a geometry-AS, a transform node, or another +instance acceleration structure. + +### Building Safe API + +The easiest way to build an acceleration structure is using [`Accel::build`] +to which you just pass a slice of [`BuildInput`]s and the function handles +memory allocation and synchronization for you. + +This is handy for getting something working with the minimum of fuss, but +means reallocating temporary storage each time. It also means synchronizing +after each build rather than potentially processing many builds on a stream +and synchronizing at the end. + +```no_run +use cust::prelude as cu; +use optix::prelude as ox; +# fn doit() -> Result<(), Box> { +# cust::init(cu::CudaFlags::empty())?; +# ox::init()?; +# let device = cu::Device::get_device(0)?; +# let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +# cu::ContextFlags::MAP_HOST, device)?; +# let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +# let vertices: Vec<[f32; 3]> = Vec::new(); +# let indices: Vec<[u32; 3]> = Vec::new(); +# let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; + +let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; + +let geometry_flags = ox::GeometryFlags::None; +let triangle_input = + ox::IndexedTriangleArray::new( + &[&buf_vertex], + &buf_indices, + &[geometry_flags] + ); + +let accel_options = + ox::AccelBuildOptions::new( + ox::BuildFlags::ALLOW_COMPACTION, + ox::BuildOperation::Build + ); + +let build_inputs = vec![triangle_input]; + +let gas = ox::Accel::build( + &ctx, + &stream, + &[accel_options], + &build_inputs, + true +)?; + +stream.synchronize()?; +# Ok(()) +# } +``` + +### Building Unsafe API + +As an alternative, you can also use the unsafe functions [`accel_build`], +[`accel_compact`], and [`Accel::from_raw_parts`] to handle the memory +allocation yourself, meaning you can reuse buffers between accel builds. + +To prepare for a build, the required memory sizes are queried by passing an +initial set of build inputs and parameters to [`accel_compute_memory_usage`]. +It returns three different sizes: + +* `output_size_in_bytes` - Size of the memory region where the resulting +acceleration structure is placed. This size is an upper bound and may be +substantially larger than the final acceleration structure. (See “Compacting acceleration structures”.) +* `temp_size_in_bytes` - Size of the memory region that is temporarily used during +the build. +* `temp_update_size_in_bytes` - Size of the memory region that is temporarily +required to update the acceleration structure. + +Using these sizes, the application allocates memory for the output and temporary +memory buffers on the device. The pointers to these buffers must be aligned to +a 128-byte boundary. These buffers are actively used for the duration of the +build. For this reason, they cannot be shared with other currently active build +requests. + +Note that [`accel_compute_memory_usage`] does not initiate any activity on the +device; pointers to device memory or contents of input buffers are not required to point to allocated memory. + +The function [`accel_build`] takes the same array of [`BuildInput`] structs as +[`accel_compute_memory_usage`] and builds a single acceleration structure from +these inputs. This acceleration structure can contain either geometry or +instances, depending on the inputs to the build. + +The build operation is executed on the device in the specified CUDA stream and +runs asynchronously on the device, similar to CUDA kernel launches. The +application may choose to block the host-side thread or synchronize with other +CUDA streams by using available CUDA synchronization functionality such as +[`Stream::synchronize()`](cust::stream::Stream::synchronize) or CUDA events. +The traversable handle returned is computed on the host and is returned from +the function immediately, without waiting for the build to finish. By producing +handles at acceleration time, custom handles can also be generated based on +input to the builder. + +The acceleration structure constructed by [`accel_build`] does not reference +any of the device buffers referenced in the build inputs. All relevant data +is copied from these buffers into the acceleration output buffer, possibly in +a different format. + +The application is free to release this memory after the build without +invalidating the acceleration structure. However, instance-AS builds will +continue to refer to other instance-AS and geometry-AS instances and transform +nodes. + +```no_run +use cust::prelude as cu; +use optix::prelude as ox; +# fn doit() -> Result<(), Box> { +# cust::init(cu::CudaFlags::empty())?; +# ox::init()?; +# let device = cu::Device::get_device(0)?; +# let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +# cu::ContextFlags::MAP_HOST, device)?; +# let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +# let vertices: Vec<[f32; 3]> = Vec::new(); +# let indices: Vec<[u32; 3]> = Vec::new(); +# let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; + +let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; + +let geometry_flags = ox::GeometryFlags::None; + +let build_inputs = + [ox::IndexedTriangleArray::new( + &[&buf_vertex], + &buf_indices, + &[geometry_flags] + )]; + +let accel_options = + ox::AccelBuildOptions::new( + ox::BuildFlags::ALLOW_COMPACTION, + ox::BuildOperation::Build + ); + +// Get the storage requirements for temporary and output buffers +let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; + +// Allocate temporary and output buffers +let mut output_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; +let mut temp_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; + +// Build the accel +let hnd = unsafe { + accel_build( + ctx, + stream, + accel_options, + build_inputs, + &mut temp_buffer, + &mut output_buffer, + &mut properties, + )? +}; + +// The accel build is asynchronous +stream.synchronize()?; + +# Ok(()) +# } +``` + +## Primitive Build Inputs +The [`accel_build`] function accepts multiple build inputs per call, but they +must be all triangle inputs, all curve inputs, or all AABB inputs. Mixing build +input types in a single geometry-AS is not allowed. + +Each build input maps to one or more consecutive records in the shader binding +table (SBT), which controls program dispatch. (See “Shader binding table”.) If +multiple records in the SBT are required, the application needs to provide a +device buffer with per-primitive SBT record indices for that build input. If +only a single SBT record is requested, all primitives reference this same unique +SBT record. Note that there is a limit to the number of referenced SBT records +per geometry-AS. (Limits are discussed in “Limits”.) + +Each build input also specifies an array of OptixGeometryFlags, one for each SBT +record. The flags for one record apply to all primitives mapped to this SBT record. + +The following flags are supported: + +* [`GeometryFlags::None`] - Applies the default behavior when calling the any-hit +program, possibly multiple times, allowing the acceleration-structure builder +to apply all optimizations. +* [`GeometryFlags::RequireSingleAnyHitCall`] - Disables some optimizations +specific to acceleration-structure builders. By default, traversal may call +the any-hit program more than once for each intersected primitive. Setting +the flag ensures that the any-hit program is called only once for a hit with a +primitive. However, setting this flag may change traversal performance. The +usage of this flag may be required for correctness of some rendering algorithms; +for example, in cases where opacity or transparency information is accumulated +in an any-hit program. +* [`GeometryFlags::DisableAnyHit`] - Indicates that traversal should not call +the any-hit program for this primitive even if the corresponding SBT record +contains an any-hit program. Setting this flag usually improves performance +even if no any-hit program is present in the SBT. + +Primitives inside a build input are indexed starting from zero. This primitive +index is accessible inside the intersection, any-hit, and closest-hit programs. +If the application chooses to offset this index for all primitives in a build +input, there is no overhead at runtime. This can be particularly useful when +data for consecutive build inputs is stored consecutively in device memory. +The `primitive_index_offset` value is only used when reporting the intersection +primitive. + +## Build Flags + +An acceleration structure build can be controlled using the values of the +[`BuildFlags`] enum. To enable random vertex access on an acceleration structure, +use [`BuildFlags::ALLOW_RANDOM_VERTEX_ACCESS`]. (See “Vertex random access”.) +To steer trade-offs between build performance, runtime traversal performance +and acceleration structure memory usage, use [`BuildFlags::PREFER_FAST_TRACE`] +and [`BuildFlags::PREFER_FAST_BUILD`]. For curve primitives in particular, +these flags control splitting; see “Splitting curve segments”. + +The flags [`BuildFlags::PREFER_FAST_TRACE`] and [`BuildFlags::PREFER_FAST_BUILD`] +are mutually exclusive. To combine multiple flags that are not mutually exclusive, +use the logical “or” operator. + +## Dynamic Updates + +Building an acceleration structure can be computationally costly. Applications +may choose to update an existing acceleration structure using modified vertex +data or bounding boxes. Updating an existing acceleration structure is generally +much faster than rebuilding. However, the quality of the acceleration structure +may degrade if the data changes too much with an update, for example, through +explosions or other chaotic transitions—even if for only parts of the mesh. +The degraded acceleration structure may result in slower traversal performance +as compared to an acceleration structure built from scratch from the modified +input data. + +### Dynamic Updates Safe API + +The simplest way to use dynamic updates is with the [`DynamicAccel`] structure. +Simply call [`DynamicAccel::new()`] as you would with [`Accel`], and then +call [`DynamicAccel::update()`] with the updated build inputs when you want +to update the acceleration structure. + +Note that the inputs to [`DynamicAccel::update`] must have the same structure, +i.e. the number of motion keys, aabbs, triangle topology etc must be the same, +although the underlying data (including the data pointers) can be different. +If the data have a different structure, then behaviour is undefined. +[`DynamicAccel`] checks this by hashing the inputs and returns an error if +the data do not match. + +### Dynamic Updates Unsafe API + +To allow for future updates of an acceleration structure, set +[`BuildFlags::ALLOW_UPDATE`] in the build flags when building the acceleration +structure initially. + +To update the previously built acceleration structure, set the operation to +[`BuildOperation::Update`] and then call [`accel_build()`] on the same output +data. All other options are required to be identical to the original build. +The update is done in-place on the output data. + +Updating an acceleration structure usually requires a different amount of temporary memory than the original build. + +When updating an existing acceleration structure, only the device pointers and/or +their buffer content may be changed. You cannot change the number of build inputs, +the build input types, build flags, traversable handles for instances (for an +instance-AS), or the number of vertices, indices, AABBs, instances, SBT records +or motion keys. Changes to any of these things may result in undefined behavior, +including GPU faults. + +Note the following: + +* When using indices, changing the connectivity or, in general, using shuffled +vertex positions will work, but the quality of the acceleration structure will +likely degrade substantially. +* During an animation operation, geometry that should be invisible to the camera +should not be “removed” from the scene, either by moving it very far away or +by converting it into a degenerate form. Such changes to the geometry will also +degrade the acceleration structure. +* In these cases, it is more efficient to re-build the geometry-AS and/or the +instance-AS, or to use the respective masking and flags. + +Updating an acceleration structure requires that any other acceleration structure +that is using this acceleration structure as a child directly or indirectly +also needs to be updated or rebuild. + +## Compaction +A post-process can compact an acceleration structure after construction. This +process can significantly reduce memory usage, but it requires an additional +pass. The build and compact operations are best performed in batches to ensure +that device synchronization does not degrade performance. The compacted size +depends on the acceleration structure type and its properties and on the device +architecture. + +### Compaction Safe API +To compact an [`Accel`] or [`DynamicAccel`] when building, simply pass `true` +for the `compact` parameter. + +### Compaction Unsafe API + +To compact the acceleration structure as a post-process, do the following: + +* Build flag [`BuildFlags::ALLOW_COMPACTION`] must be set in the + [`AccelBuildOptions`] passed to optixAccelBuild. +* The emit property [`AccelEmitDesc::CompactedSize`] must be passed to + [`accel_build()`]. This property is generated on the device and it must be + copied back to the host if it is required for allocating the new output + buffer. The application may then choose to compact the acceleration structure + using [`accel_compact()`]. + +The [`accel_compact()`] call should be guarded by an +`if compacted_size < output_size` (or similar) to avoid the compacting pass in +cases where it is not beneficial. Note that this check requires a copy of the +compacted size (as queried by [`accel_build()`]) from the device memory to host +memory. + +Just like an uncompacted acceleration structure, it is possible to traverse, +update, or relocate a compacted acceleration structure. + +For example: +```no_run +use cust::prelude as cu; +use optix::prelude as ox; +# fn doit() -> Result<(), Box> { +# cust::init(cu::CudaFlags::empty())?; +# ox::init()?; +# let device = cu::Device::get_device(0)?; +# let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +# cu::ContextFlags::MAP_HOST, device)?; +# let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +# let vertices: Vec<[f32; 3]> = Vec::new(); +# let indices: Vec<[u32; 3]> = Vec::new(); +# let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; + +let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; + +let geometry_flags = ox::GeometryFlags::None; + +let build_inputs = + [ox::IndexedTriangleArray::new( + &[&buf_vertex], + &buf_indices, + &[geometry_flags] + )]; + +let accel_options = + ox::AccelBuildOptions::new( + ox::BuildFlags::ALLOW_COMPACTION, + ox::BuildOperation::Build + ); + +// Get the storage requirements for temporary and output buffers +let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; + +// Allocate temporary and output buffers +let mut output_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; +let mut temp_buffer = + unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; + +// Build the accel +let hnd = unsafe { + accel_build( + ctx, + stream, + accel_options, + build_inputs, + &mut temp_buffer, + &mut output_buffer, + &mut properties, + )? +}; + +stream.synchronize()?; + +let mut compacted_size = 0usize; +compacted_size_buffer.copy_to(&mut compacted_size)?; + +let accel = if compacted_size < sizes.output_size_in_bytes { + let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; + let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; + + stream.synchronize()?; + Accel::from_raw_parts(buf, hnd); +else { + Accel::from_raw_parts(output_buffer, hnd) +}; + +# Ok(()) +# } +``` + +## Traversable Objects + +### Traversable Objects Safe API + +The transform traversable types, [`StaticTransform`](crate::transform::StaticTransform), +[`MatrixMotionTransform`](crate::transform::MatrixMotionTransform), and +[`SrtMotionTransform`](crate::transform::SrtMotionTransform) handle all +necessary memory allocation and pointer conversion for you in their `new()` +constructors. + +### Traversable Objects Unsafe API +The instances in an instance-AS may reference transform traversables, as well +as geometry-ASs. Transform traversables are fully managed by the application. +The application needs to create these traversables manually in device memory +in a specific form. The function [`convert_pointer_to_traversable_handle`] +converts a raw pointer into a traversable handle of the specified type. The +traversable handle can then be used to link traversables together. + +In device memory, all traversable objects need to be 64-byte aligned. Note that +moving a traversable to another location in memory invalidates the traversable +handle. The application is responsible for constructing a new traversable handle +and updating any other traversables referencing the invalidated traversable +handle. + +The traversable handle is considered opaque and the application should not rely +on any particular mapping of a pointer to the traversable handle. + +### Traversal of a Single Geometry Acceleration Structure +The traversable handle passed to `optixTrace` can be a traversable handle +created from a geometry-AS. This can be useful for scenes where single +geometry-AS objects represent the root of the scene graph. + +If the modules and pipeline only need to support single geometry-AS traversables, +it is beneficial to change the +[`PipelineCompileOptions::traversable_graph_flags`](crate::module::PipelineCompileOptions) from +[`TraversableGraphFlags::ALLOW_ANY`](crate::module::TraversableGraphFlags) to +[`TraversableGraphFlags::ALLOW_SINGLE_GAS`](crate::module::TraversableGraphFlags). + +This signals to NVIDIA OptiX 7 that no other traversable types require support +during traversal. + +## Motion Blur + +Motion support in OptiX targets the rendering of images with motion blur using a +stochastic sampling of time. OptiX supports two types of motion as part of the +scene: transform motion and vertex motion, often called deformation motion. When +setting up the scene traversal graph and building the acceleration structures, +motion options can be specified per acceleration structure as well as per motion +transform traversable. At run time, a time parameter is passed to the trace call +to perform the intersection of a ray against the scene at the selected point in +time. + +The general design of the motion feature in OptiX tries to strike a balance +between providing many parameters to offer a high degree of freedom combined +with a simple mapping of scene descriptions to these parameters but also +delivering high traversal performance at the same time. As such OptiX supports +the following key features: + +* Vertex and transformation motion +* Matrix as well as SRT (scale rotation translation) transformations +* Arbitrary time ranges (ranges not limited to [0,1]) and flags to specify behavior outside the time range +* Arbitrary concatenations of transformations (for example, a matrix transformation on top of a SRT transformation) +* Per-ray timestamps + +Scene descriptions with motion need to map easily to traversable objects and +their motion options as offered by OptiX. As such, the idea is that the motion +options are directly derived by the scene description, delivering high traversal +performance without the need for any performance-driven adjustments. However, due +to the complexity of the subject, there are a few exceptions that are discussed +in this section. + +This section details the usage of the motion options on the different traversable +types and how to map scene options best to avoid potential performance pitfalls. + +### Basics +Motion is supported by +[`MatrixMotionTransform`](crate::transform::MatrixMotionTransform), +[`SrtMotionTransform`](crate::transform::SrtMotionTransform) and +acceleration structure traversables. The general motion characteristics are +specified per traversable as motion options: the number of motion keys, flags, +and the beginning and ending motion times corresponding to the first and last +key. The remaining motion keys are evenly spaced between the beginning and +ending times. The motion keys are the data at specific points in time and the +data is interpolated in between neighboring keys. The motion options are +specified in the [`MotionOptions`] struct. + +The motion options are always specified per traversable (acceleration structure +or motion transform). There is no dependency between the motion options of +traversables; given an instance referencing a geometry acceleration structure +with motion, it is not required to build an instance acceleration structure +with motion. The same goes for motion transforms. Even if an instance references +a motion transform as child traversable, the instance acceleration structure +itself may or may not have motion. + +Motion transforms must specify at least two motion keys. Acceleration structures, +however, also accept [`AccelBuildOptions`] with field [`MotionOptions`] set to +[`default()`]. This effectively disables motion for the acceleration structure and +ignores the motion beginning and ending times, along with the motion flags. + +OptiX also supports static transform traversables in addition to the static +transform of an instance. Static transforms are intended for the case of motion +transforms in the scene. Without any motion transforms +([`MatrixMotionTransform`](crate::transform::MatrixMotionTransform) or +[`SrtMotionTransform](crate::transform::SrtMotionTransform)) in the traversable +graph, any static transformation should be baked into the instance transform. +However, if there is a motion transform, it may be required to apply a static +transformation on a traversable (for example, on a geometry-AS) first before +applying the motion transform. For example, a motion transform may be specified +in world coordinates, but the geometry it applies to needs to be placed into the +scene first (object-to-world transformation, which is usually done using the +instance transform). In this case, a static transform pointing at the geometry +acceleration structure can be used for the object-to-world transformation and +the instance transform pointing to the motion transform has an identity matrix +as transformation. + +Motion boundary conditions are specified by using flags. By default, the +behavior for any time outside the time range, is as if time was clamped to the +range, meaning it appears static and visible. Alternatively, to remove the +traversable before the beginning time, set [`MotionFlags::START_VANISH`]; to +remove it after the ending time, set [`MotionFlags::END_VANISH`]. + +For example: +``` +let motion_options = MotionFlags { + num_keys: 3, + time_begin: -1.0, + time_end: 1.5 + flags: MotionFlags::NONE, +}; +``` + +OptiX offers two types of motion transforms, SRTs (scale-rotation-translation) +as well as 3x4 affine matrices, each specifying one transform (SRT or matrix) +per motion key. The transformations are always specified as object-to-world +transformation just like the instance transformation. During traversal OptiX +performs a per-component linear interpolation of the two nearest keys. The +rotation component (expressed as a quaternion) of the SRT is an exception, +OptiX ensures that the interpolated quaternion of two SRTs is of unit length +by using nlerp interpolation for performance reasons. This results in a smooth, +scale-preserving rotation in Cartesian space though with non-constant velocity. + +For vertex motion, OptiX applies a linear interpolation between the vertex data +that are provided by the application. If intersection programs are used and +AABBs are supplied for the custom primitives, the AABBs are also linearly +interpolated for intersection. The AABBs at the motion keys must therefore be +big enough to contain any motion path of the underlying custom primitive. + +There are several device-side functions that take a time parameter such as +`optixTrace` and respect the motion options as set at the traversables. The +result of these device-side functions is always that of the specified point +in time, e.g, the intersection of the ray with the scene at the selected point +in time. Device-side functions are discussed in detail in “Device-side functions”. + +### Motion Geometry Acceleration Structure +Use [`Accel::build()`] to build a motion acceleration structure. The motion +options are part of the build options ([`AccelBuildOptions`]) and apply to all +build inputs. Build inputs must specify primitive vertex buffers (for +[`TriangleArray`] and [`CurveArray`]), radius buffers (for [`CurveArray`]), and +AABB buffers (for [`CustomPrimitiveArray`] and [`InstanceArray`]) for all motion +keys. These are interpolated during traversal to obtain the continuous motion vertices and AABBs between the begin and end time. + +The motion options are typically defined by the mesh data which should directly +map to the motion options on the geometry acceleration structure. For example, +if a triangle mesh has three per-vertex motion values, the geometry acceleration +structure needs to have three motion keys. Just as for non-motion meshes, it is +possible to combine meshes within a single geometry acceleration structure to +potentially increase traversal performance (this is generally recommended if +there is only a single instance of each mesh and the meshes overlap or are close +together). However, these meshes need to share the same motion options (as they +are specified per geometry acceleration structure). The usual trade-offs apply +in case meshes need to be updated from one frame to another as in an interactive +application. The entire geometry acceleration structure needs to be rebuilt or +refitted if the vertices of at least one mesh change. + +It is possible to use a custom intersection program to decouple the actual vertex +data and the motion options of the geometry acceleration structure. Intersection +programs allow any kind of intersection routine. For example, it is possible to +implement a three-motion-key-triangle intersection, but build a static geometry +acceleration structure over AABBs by passing AABBs to the geometry acceleration +structure build that enclose the full motion path of the triangles. However, this +is generally not recommended for two reasons: First, the AABBs tend to increase +in size very quickly even with very little motion. Second, it prevents the use +of hardware intersection routines. Both of these effects can have a tremendous +impact on performance. + +### Motion Instance Acceleration Structure + +Just as for a geometry acceleration structure, the motion options for an instance acceleration structure are specified as part of the build options. The notable difference to a geometry acceleration structure is that the motion options for an instance acceleration structure almost only impact performance. Hence, whether or not to build a motion instance acceleration structure has no impact on the correctness of the rendering (determining which instances can be intersected), but impacts memory usage as well as traversal performance. The only exception to that are the vanish flags as these force any instance of the instance acceleration structure to be non-intersectable for any ray time outside of the time range of the instance acceleration structure. + +In the following, guidelines are provided on setting the motion options to achieve good performance and avoid pitfalls. We will focus on the number of motion keys, usually the main discriminator for traversal performance and the only factor for memory usage. The optimal number of motion keys used for the instance acceleration structure build depends on the amount and linearity of the motion of the traversables referenced by the instances. The time beginning and ending range are usually defined by what is required to render the current frame. The recommendations given here may change in the future. + +The following advice should be considered a simplified heuristic. A more detailed derivation of whether or not to use motion is given below. For RTCores version 1.0 (Turing architecture), do not use motion for instance acceleration structure, but instead build a static instance acceleration structure that can leverage hardware-accelerated traversal. For any other device (devices without RTCores or RTCores version >= 2.0), build a motion instance acceleration structure if any of the instances references a motion transform or a motion acceleration structure as traversable child. + +If a motion instance acceleration structure is built, it is often sufficient to use a low number of motion keys (two or three) to avoid high memory costs. Also, it is not required to use a large number of motion keys just because one of the referenced motion transforms has many motion keys (such as the maximum motion keys of any referenced traversable by any of the instances). The motion options have no dependency between traversable objects and a high number of motion keys on the instance acceleration structure causes a high memory overhead. Clearly, motion should not be used for an instance acceleration structure if the instances only reference static traversables. + +Further considerations when using motion blur: + +#### Is motion enabled? +An instance acceleration structure should be built with motion on (the number of motion keys larger than one) if the overall amount of motion of the instanced traversables is non-minimal. For a single instance this can be quantified by the amount of change of its AABB over time. Hence, in case of a simple translation (for example, due to a matrix motion transform), the metric is the amount of the translation in comparison to the size of the AABB. In case of a scaling, it is the ratio of the size of the AABB at different points in times. If sufficiently many instanced traversables exhibit a non-minimal amount of change of their AABB over time, build a motion instance acceleration structure. Inversely, a static instance acceleration structure can yield higher traversal performance if many instanced traversables have no motion at all or only very little. The latter can happen for rotations. A rotation around the center of an object causes a rather small difference in the AABB of the object. However, if the rotational pivot point is not the center, it is likely to cause a big difference in the AABB of the object. + +As it is typically hard to actually quantify the amount of motion for the instances, switch to motion if sufficiently many instanced traversables have or are expected to have motion. Yet it is difficult to predict when exactly it pays off to use or not use motion on the instance acceleration structure. + +#### If motion is enabled, how many keys should be defined? + +A reasonable metric to determine the required number of motion keys for an instance acceleration structure is the linearity of the motion of the instanced traversables. If there are motion transforms with many motion keys, rotations, or a hierarchical set of motion transforms, more motion keys on the instance acceleration structure may increase traversal performance. Transformations like a simple translation, rotation around the center of an object, a small scale, or even all of those together are usually handles well by a two-motion-key instance acceleration structure. + +Finally, the quality of the instance acceleration structure is also affected by the number of motion keys of the referenced traversables of the instances. As such, it is desirable to have the motion options of the instance acceleration structure match the motion options of any referenced motion transform. For example, if all instances reference motion transforms with three keys, it is reasonable to also use three motion keys for the instance acceleration structure. Note that also in this case the statement from above still applies that using more motion keys only helps if the underlying transformation results in a non-linear motion. + +### Motion Matrix Transform + +The motion matrix transform traversable ([`MatrixMotionTransform`]) transforms the ray during traversal using a motion matrix. The traversable provides a 3x4 row-major object-to-world transformation matrix for each motion key. The final motion matrix is constructed during traversal by interpolating the elements of the matrices at the nearest motion keys. + +The [`MatrixMotionTransform`] can be created with an arbitrary number of keys +using its [`new()`](crate::transform::MatrixMotionTransform::new) constructor. + +### Motion Scale Rotate Translate Transform + +The behavior of the motion transform [`SrtMotionTransform`] is similar to the matrix motion transform [`MatrixMotionTransform`]. In [`SrtMotionTransform`] the object-to-world transforms per motion key are specified as a scale, rotation and translation (SRT) decomposition instead of a single 3x4 matrix. Each motion key is a struct of type [`SrtData`], which consists of 16 floats: + +``` +struct SrtData { + pub sx: f32, + pub a: f32, + pub b: f32, + pub pvx: f32, + pub sy: f32, + pub c: f32, + pub pvy: f32, + pub sz: f32, + pub pvz: f32, + pub qx: f32, + pub qy: f32, + pub qz: f32, + pub qw: f32, + pub tx: f32, + pub ty: f32, + pub tz: f32, +} +``` + +* The scaling matrix, +$$ +S=\begin{bmatrix} +sx & a & b & pvx \cr 0 & sy & c & pvy \cr 0 & 0 & sz & pvz +\end{bmatrix} +$$ + +defines an affine transformation that can include scale, shear, and a translation. +The translation allows to define the pivot point for the subsequent rotation. + +* The rotation quaternion +$$ +R = [qx, qy, qz, qw] +$$ +describes a rotation with angular +component $qw = \cos(\theta / 2)$ and other components +$$ +[qx, qy, qz] = \sin(\theta / 2) \cdot [ax, ay, az] +$$ where the axis $[ax, ay, az]$ is normalized. + +* The translation matrix, +$$ +T = \begin{bmatrix} 1 & 0 & 0 & tx \cr 0 & 1 & 0 & ty \cr 0 & 0 & 1 & tz \end{bmatrix} +$$ +defines another translation that is applied after the rotation. Typically, this +translation includes the inverse translation from the matrix $S$ to reverse the +translation for the pivot point for $R$. + +To obtain the effective transformation at time $t$, the elements of the components +of $S$, $R$, and $T$ will be interpolated linearly. The components are then +multiplied to obtain the combined transformation $C = T \times R \times S$. The +transformation $C$ is the effective object-to-world transformations at time $t$, +and $C^{-1}$ is the effective world-to-object transformation at time $t$. + +#### Example 1 - rotation about the origin: + +Use two motion keys. Set the first key to identity values. For the second key, define a quaternion from an axis and angle, for example, a 60-degree rotation about the z axis is given by: + +$$ Q = [ 0 0 \sin(\pi/6) \cos(\pi/6) ] $$ + +#### Example 2 - rotation about a pivot point: +Use two motion keys. Set the first key to identity values. Represent the pivot point as a translation $P$, and define the second key as follows: +$$ +S^{\prime} = P^{-1} \times S \newline +T^{\prime} = T \times P \newline +C = T^{\prime} \times R \times S^{\prime} +$$ + +#### Example 3 - scaling about a pivot point + +Use two motion keys. Set the first key to identity values. Represent the pivot as a translation $G = [G_x, G_y, f G_z]$ and modify the pivot point described above: + +$$ +P_x^{\prime} = P_x + (-S_x \times G_x + G_x) \newline +P_y^{\prime} = P_y + (-S_y \times G_y + G_y) \newline +P_z^{\prime} = P_z + (-S_z \times G_z + G_z) \newline +$$ + +### Transforms trade-offs +Several trade-offs must be considered when using transforms. + +#### SRTs compared to matrix motion transforms +Use SRTs for any transformations containing a rotation. Only SRTs produce a smooth rotation without distortion. They also avoid any oversampling of matrix transforms to approximate a rotation. However, note that the maximum angle of rotation due to two neighboring SRT keys needs to be less than 180 degrees, hence, the dot product of the quaternions needs to be positive. This way the rotations are interpolated using the shortest path. If a rotation of 180 degrees or more is required, additional keys need to be specified such that the rotation between two keys is less than 180 degrees. OptiX uses nlerp to interpolate quaternion at runtime. While nlerp produces the best traversal performance, it causes non-constant velocity in the rotation. The variation of rotational velocity is directly dependent on the amount of the rotation. If near constant rotation velocity is required, more SRT keys can be used. + +Due to the complexity of the rotation, instance acceleration structure builds with instances that reference SRT transforms can be relatively slow. For real-time or interactive applications, it can be advantageous to use matrix transforms to have fast rebuilds or refits of the instance acceleration structure. + +#### Motion options for motion transforms +The motion options for motion transforms should be derived by the scene setup and used as needed. The number of keys is defined by the number of transformations specified by the scene description. The beginning, ending times should be as needed for the frame or tighter if specified by the scene description. + +Avoid duplicating instances of motion transforms to achieve a motion behavior that can also be expressed by a single motion transform but many motion keys. An example is the handling of irregular keys, which is discussed in the following section. + +#### Dealing with irregular keys +OptiX only supports regular time intervals in its motion options. Irregular keys should be resampled to fit regular keys, potentially with a much higher number of keys if needed. + +A practical example for this is a motion matrix transform that performs a rotation. Since the matrix elements are linearly interpolated between keys, the rotation is not an actual rotation, but a scale/shear/translation. To avoid visual artifacts, the rotation needs to be sampled with potentially many matrix motion keys. Such a sampling bounds the maximum error in the approximation of the rotation by the linear interpolation of matrices. The sampling should not try to minimize the number of motion keys by outputting irregular motion keys, but rather oversample the rotation with many keys. + +Duplicate motion transforms should not be used as a workaround for irregular keys, where each key has varying motion beginning and ending times and vanish motion flags set. This duplication creates traversal overhead as all copies need to be intersected and their motion times compared to the ray's time. + + +[`TriangleArray`]: crate::triangle_array::TriangleArray +[`CurveArray`]: crate::curve_array::CurveArray +[`InstanceArray`]: crate::instance_array::InstanceArray +[`MatrixMotionTransform`]: crate::transform::MatrixMotionTransform +[`SrtMotionTransform`]: crate::transform::SrtMotionTransform + diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index 33f2946f..e1d9d8e6 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -1,339 +1,6 @@ -//! # Acceleration Structures -//! -//! NVIDIA OptiX 7 provides acceleration structures to optimize the search for the -//! intersection of rays with the geometric data in the scene. Acceleration structures -//! can contain two types of data: geometric primitives (a geometry-AS) or instances -//! (an instance-AS). Acceleration structures are created on the device using a set -//! of functions. These functions enable overlapping and pipelining of acceleration -//! structure creation, called a build. The functions use one or more [`BuildInput`] -//! structs to specify the geometry plus a set of parameters to control the build. -//! -//! Acceleration structures have size limits, listed in “Limits”. For an instance -//! acceleration structure, the number of instances has an upper limit. For a geometry -//! acceleration structure, the number of geometric primitives is limited, -//! specifically the total number of primitives in its build inputs, multiplied by the -//! number of motion keys. -//! -//! The following acceleration structure types are supported: -//! -//! #### Instance acceleration structures -//! - [`InstanceArray`](crate::instance_array::InstanceArray) -//! - [`InstancePointerArray`](crate::instance_array::InstancePointerArray) -//! -//! #### Geometry acceleration structure containing built-in triangles -//! - [`TriangleArray`](crate::triangle_array::TriangleArray) -//! - [`IndexedTriangleArray`](crate::triangle_array::IndexedTriangleArray) -//! -//! #### Geometry acceleration structure containing built-in curves -//! - [`CurveArray`](crate::curve_array::CurveArray) -//! -//! #### Geometry acceleration structure containing custom primitives -//! - [`CustomPrimitiveArray`](crate::custom_primitive_array::CustomPrimitiveArray) -//! -//! ## Building -//! -//! For geometry-AS builds, each build input can specify a set of triangles, a set -//! of curves, or a set of user-defined primitives bounded by specified axis-aligned -//! bounding boxes. Multiple build inputs can be passed as an array to [`accel_build`] -//! to combine different meshes into a single acceleration structure. All build -//! inputs for a single build must agree on the build input type. -//! -//! Instance acceleration structures have a single build input and specify an array -//! of instances. Each [`Instance`](crate::instance_array::Instance) includes a ray transformation and an -//! [`TraversableHandle`] that refers to a geometry-AS, a transform node, or another -//! instance acceleration structure. -//! -//! ### Safe API -//! -//! The easiest way to build an acceleration structure is using [`Accel::build`] -//! to which you just pass a slice of [`BuildInput`]s and the function handles -//! memory allocation and synchronization for you. -//! -//! This is handy for getting something working with the minimum of fuss, but -//! means reallocating temporary storage each time. It also means synchronizing -//! after each build rather than potentially processing many builds on a stream -//! and synchronizing at the end. -//! -//! ```no_run -//! use cust::prelude as cu; -//! use optix::prelude as ox; -//! # fn doit() -> Result<(), Box> { -//! # cust::init(cu::CudaFlags::empty())?; -//! # ox::init()?; -//! # let device = cu::Device::get_device(0)?; -//! # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | -//! # cu::ContextFlags::MAP_HOST, device)?; -//! # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; -//! # let vertices: Vec<[f32; 3]> = Vec::new(); -//! # let indices: Vec<[u32; 3]> = Vec::new(); -//! # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; -//! -//! let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; -//! let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; -//! -//! let geometry_flags = ox::GeometryFlags::None; -//! let triangle_input = -//! ox::IndexedTriangleArray::new( -//! &[&buf_vertex], -//! &buf_indices, -//! &[geometry_flags] -//! ); -//! -//! let accel_options = -//! ox::AccelBuildOptions::new( -//! ox::BuildFlags::ALLOW_COMPACTION, -//! ox::BuildOperation::Build -//! ); -//! -//! let build_inputs = vec![triangle_input]; -//! -//! let gas = ox::Accel::build( -//! &ctx, -//! &stream, -//! &[accel_options], -//! &build_inputs, -//! true -//! )?; -//! -//! stream.synchronize()?; -//! # Ok(()) -//! # } -//! ``` -//! -//! ### Unsafe API -//! -//! As an alternative, you can also use the unsafe functions [`accel_build`], -//! [`accel_compact`], and [`Accel::from_raw_parts`] to handle the memory -//! allocation yourself, meaning you can reuse buffers between accel builds. -//! -//! To prepare for a build, the required memory sizes are queried by passing an -//! initial set of build inputs and parameters to [`accel_compute_memory_usage`]. -//! It returns three different sizes: -//! -//! * `output_size_in_bytes` - Size of the memory region where the resulting -//! acceleration structure is placed. This size is an upper bound and may be -//! substantially larger than the final acceleration structure. (See “Compacting acceleration structures”.) -//! * `temp_size_in_bytes` - Size of the memory region that is temporarily used during -//! the build. -//! * `temp_update_size_in_bytes` - Size of the memory region that is temporarily -//! required to update the acceleration structure. -//! -//! Using these sizes, the application allocates memory for the output and temporary -//! memory buffers on the device. The pointers to these buffers must be aligned to -//! a 128-byte boundary. These buffers are actively used for the duration of the -//! build. For this reason, they cannot be shared with other currently active build -//! requests. -//! -//! Note that [`accel_compute_memory_usage`] does not initiate any activity on the -//! device; pointers to device memory or contents of input buffers are not required to point to allocated memory. -//! -//! The function [`accel_build`] takes the same array of [`BuildInput`] structs as -//! [`accel_compute_memory_usage`] and builds a single acceleration structure from -//! these inputs. This acceleration structure can contain either geometry or -//! instances, depending on the inputs to the build. -//! -//! The build operation is executed on the device in the specified CUDA stream and -//! runs asynchronously on the device, similar to CUDA kernel launches. The -//! application may choose to block the host-side thread or synchronize with other -//! CUDA streams by using available CUDA synchronization functionality such as -//! [`Stream::synchronize()`](cust::stream::Stream::synchronize) or CUDA events. -//! The traversable handle returned is computed on the host and is returned from -//! the function immediately, without waiting for the build to finish. By producing -//! handles at acceleration time, custom handles can also be generated based on -//! input to the builder. -//! -//! The acceleration structure constructed by [`accel_build`] does not reference -//! any of the device buffers referenced in the build inputs. All relevant data -//! is copied from these buffers into the acceleration output buffer, possibly in -//! a different format. -//! -//! The application is free to release this memory after the build without -//! invalidating the acceleration structure. However, instance-AS builds will -//! continue to refer to other instance-AS and geometry-AS instances and transform -//! nodes. -//! -//! ```no_run -//! use cust::prelude as cu; -//! use optix::prelude as ox; -//! # fn doit() -> Result<(), Box> { -//! # cust::init(cu::CudaFlags::empty())?; -//! # ox::init()?; -//! # let device = cu::Device::get_device(0)?; -//! # let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | -//! # cu::ContextFlags::MAP_HOST, device)?; -//! # let ctx = ox::DeviceContext::new(&cu_ctx, false)?; -//! # let vertices: Vec<[f32; 3]> = Vec::new(); -//! # let indices: Vec<[u32; 3]> = Vec::new(); -//! # let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; -//! -//! let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; -//! let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; -//! -//! let geometry_flags = ox::GeometryFlags::None; -//! -//! let build_inputs = -//! [ox::IndexedTriangleArray::new( -//! &[&buf_vertex], -//! &buf_indices, -//! &[geometry_flags] -//! )]; -//! -//! let accel_options = -//! ox::AccelBuildOptions::new( -//! ox::BuildFlags::ALLOW_COMPACTION, -//! ox::BuildOperation::Build -//! ); -//! -//! // Get the storage requirements for temporary and output buffers -//! let sizes = accel_compute_memory_usage(ctx, accel_options, build_inputs)?; -//! -//! // Allocate temporary and output buffers -//! let mut output_buffer = -//! unsafe { DeviceBuffer::::uninitialized(sizes.output_size_in_bytes)? }; -//! let mut temp_buffer = -//! unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; -//! -//! // Build the accel -//! let hnd = unsafe { -//! accel_build( -//! ctx, -//! stream, -//! accel_options, -//! build_inputs, -//! &mut temp_buffer, -//! &mut output_buffer, -//! &mut properties, -//! )? -//! }; -//! -//! // The accel build is asynchronous -//! stream.synchronize()?; -//! -//! # Ok(()) -//! # } -//! ``` -//! -//! ## Primitive Build Inputs -//! The [`accel_build`] function accepts multiple build inputs per call, but they -//! must be all triangle inputs, all curve inputs, or all AABB inputs. Mixing build -//! input types in a single geometry-AS is not allowed. -//! -//! Each build input maps to one or more consecutive records in the shader binding -//! table (SBT), which controls program dispatch. (See “Shader binding table”.) If -//! multiple records in the SBT are required, the application needs to provide a -//! device buffer with per-primitive SBT record indices for that build input. If -//! only a single SBT record is requested, all primitives reference this same unique -//! SBT record. Note that there is a limit to the number of referenced SBT records -//! per geometry-AS. (Limits are discussed in “Limits”.) -//! -//! Each build input also specifies an array of OptixGeometryFlags, one for each SBT -//! record. The flags for one record apply to all primitives mapped to this SBT record. -//! -//! The following flags are supported: -//! -//! * [`GeometryFlags::None`] - Applies the default behavior when calling the any-hit -//! program, possibly multiple times, allowing the acceleration-structure builder -//! to apply all optimizations. -//! * [`GeometryFlags::RequireSingleAnyHitCall`] - Disables some optimizations -//! specific to acceleration-structure builders. By default, traversal may call -//! the any-hit program more than once for each intersected primitive. Setting -//! the flag ensures that the any-hit program is called only once for a hit with a -//! primitive. However, setting this flag may change traversal performance. The -//! usage of this flag may be required for correctness of some rendering algorithms; -//! for example, in cases where opacity or transparency information is accumulated -//! in an any-hit program. -//! * [`GeometryFlags::DisableAnyHit`] - Indicates that traversal should not call -//! the any-hit program for this primitive even if the corresponding SBT record -//! contains an any-hit program. Setting this flag usually improves performance -//! even if no any-hit program is present in the SBT. -//! -//! Primitives inside a build input are indexed starting from zero. This primitive -//! index is accessible inside the intersection, any-hit, and closest-hit programs. -//! If the application chooses to offset this index for all primitives in a build -//! input, there is no overhead at runtime. This can be particularly useful when -//! data for consecutive build inputs is stored consecutively in device memory. -//! The `primitive_index_offset` value is only used when reporting the intersection -//! primitive. -//! -//! ## Build Flags -//! -//! An acceleration structure build can be controlled using the values of the -//! [`BuildFlags`] enum. To enable random vertex access on an acceleration structure, -//! use [`BuildFlags::ALLOW_RANDOM_VERTEX_ACCESS`]. (See “Vertex random access”.) -//! To steer trade-offs between build performance, runtime traversal performance -//! and acceleration structure memory usage, use [`BuildFlags::PREFER_FAST_TRACE`] -//! and [`BuildFlags::PREFER_FAST_BUILD`]. For curve primitives in particular, -//! these flags control splitting; see “Splitting curve segments”. -//! -//! The flags [`BuildFlags::PREFER_FAST_TRACE`] and [`BuildFlags::PREFER_FAST_BUILD`] -//! are mutually exclusive. To combine multiple flags that are not mutually exclusive, -//! use the logical “or” operator. -//! -//! ## Dynamic Updates -//! -//! Building an acceleration structure can be computationally costly. Applications -//! may choose to update an existing acceleration structure using modified vertex -//! data or bounding boxes. Updating an existing acceleration structure is generally -//! much faster than rebuilding. However, the quality of the acceleration structure -//! may degrade if the data changes too much with an update, for example, through -//! explosions or other chaotic transitions—even if for only parts of the mesh. -//! The degraded acceleration structure may result in slower traversal performance -//! as compared to an acceleration structure built from scratch from the modified -//! input data. -//! -//! ### Safe API -//! -//! The simplest way to use dynamic updates is with the [`DynamicAccel`] structure. -//! Simply call [`DynamicAccel::new()`] as you would with [`Accel`], and then -//! call [`DynamicAccel::update()`] with the updated build inputs when you want -//! to update the acceleration structure. -//! -//! Note that the inputs to [`DynamicAccel::update`] must have the same structure, -//! i.e. the number of motion keys, aabbs, triangle topology etc must be the same, -//! although the underlying data (including the data pointers) can be different. -//! If the data have a different structure, then behaviour is undefined. -//! [`DynamicAccel`] checks this by hashing the inputs and returns an error if -//! the data do not match. -//! -//! ### Unsafe API -//! -//! To allow for future updates of an acceleration structure, set -//! [`BuildFlags::ALLOW_UPDATE`] in the build flags when building the acceleration -//! structure initially. -//! -//! To update the previously built acceleration structure, set the operation to -//! [`BuildOperation::Update`] and then call [`accel_build()`] on the same output -//! data. All other options are required to be identical to the original build. -//! The update is done in-place on the output data. -//! -//! Updating an acceleration structure usually requires a different amount of temporary memory than the original build. -//! -//! When updating an existing acceleration structure, only the device pointers and/or -//! their buffer content may be changed. You cannot change the number of build inputs, -//! the build input types, build flags, traversable handles for instances (for an -//! instance-AS), or the number of vertices, indices, AABBs, instances, SBT records -//! or motion keys. Changes to any of these things may result in undefined behavior, -//! including GPU faults. -//! -//! Note the following: -//! -//! * When using indices, changing the connectivity or, in general, using shuffled -//! vertex positions will work, but the quality of the acceleration structure will -//! likely degrade substantially. -//! * During an animation operation, geometry that should be invisible to the camera -//! should not be “removed” from the scene, either by moving it very far away or -//! by converting it into a degenerate form. Such changes to the geometry will also -//! degrade the acceleration structure. -//! * In these cases, it is more efficient to re-build the geometry-AS and/or the -//! instance-AS, or to use the respective masking and flags. -//! -//! Updating an acceleration structure requires that any other acceleration structure -//! that is using this acceleration structure as a child directly or indirectly -//! also needs to be updated or rebuild. - -use crate::{context::DeviceContext, error::Error, optix_call, sys}; +use crate::{const_assert, const_assert_eq, context::DeviceContext, error::Error, optix_call, sys}; use cust::{ - memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer, DeviceSlice}, + memory::{CopyDestination, DeviceCopy, DeviceBox, DeviceBuffer, DevicePointer, DeviceSlice}, DeviceCopy, }; type Result = std::result::Result; @@ -342,12 +9,24 @@ use std::ops::Deref; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, + marker::PhantomData, }; +use memoffset::offset_of; +use std::ffi::c_void; +use std::mem::size_of; + +use cust_raw::CUdeviceptr; +use mint::{RowMatrix3x4, Vector3}; + pub trait BuildInput: std::hash::Hash { fn to_sys(&self) -> sys::OptixBuildInput; } +pub trait Traversable { + fn handle(&self) -> TraversableHandle; +} + /// Wrapper struct containing the storage and handle for a static acceleration /// structure. /// @@ -405,12 +84,14 @@ pub struct Accel { hnd: TraversableHandle, } -impl Accel { +impl Traversable for Accel { /// Get the [`TraversableHandle`] that represents this accel. - pub fn handle(&self) -> TraversableHandle { + fn handle(&self) -> TraversableHandle { self.hnd } +} +impl Accel { /// Build and (optionally) compact the acceleration structure for the given /// `build_inputs`. /// @@ -503,11 +184,16 @@ impl Accel { let mut compacted_size = 0usize; compacted_size_buffer.copy_to(&mut compacted_size)?; - let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; - - let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; - - Ok(Accel { buf, hnd }) + if compacted_size < sizes.output_size_in_bytes { + let mut buf = unsafe { DeviceBuffer::::uninitialized(compacted_size)? }; + let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; + Ok(Accel { buf, hnd }) + } else { + Ok(Accel { + buf: output_buffer, + hnd, + }) + } } else { Ok(Accel { buf: output_buffer, @@ -562,6 +248,13 @@ pub struct DynamicAccel { hash: u64, } +impl Traversable for DynamicAccel { + /// Get the [`TraversableHandle`] that represents this accel. + fn handle(&self) -> TraversableHandle { + self.accel.hnd + } +} + impl Deref for DynamicAccel { type Target = Accel; @@ -711,7 +404,7 @@ impl DynamicAccel { /// for ensuring that the device memory containing the acceleration structures /// this handle references are alive if you try to use this handle #[repr(transparent)] -#[derive(Copy, Clone, Debug, PartialEq, DeviceCopy)] +#[derive(Copy, Clone, Debug, PartialEq, DeviceCopy, Default)] pub struct TraversableHandle { pub(crate) inner: u64, } @@ -1015,6 +708,7 @@ bitflags::bitflags! { /// than the first provided motion key /// * `END_VANISH` - The object will be invisible to rays with a time less /// than the first provided motion key + #[derive(DeviceCopy)] pub struct MotionFlags: u16 { const NONE = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_NONE as u16; const START_VANISH = sys::OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH as u16; @@ -1037,7 +731,7 @@ bitflags::bitflags! { /// to zero. This effectively disables motion for the acceleration structure and /// ignores the motion beginning and ending times, along with the motion flags. #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, DeviceCopy)] pub struct MotionOptions { pub num_keys: u16, pub flags: MotionFlags, @@ -1045,6 +739,22 @@ pub struct MotionOptions { pub time_end: f32, } +impl Default for MotionOptions { + fn default() -> Self { + MotionOptions { + num_keys: 0, + flags: MotionFlags::NONE, + time_begin: 0.0, + time_end: 0.0, + } + } +} + +const_assert_eq!( + std::mem::size_of::(), + std::mem::size_of::(), +); + /// Options to configure the [`accel_build()`] #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] @@ -1132,22 +842,29 @@ pub struct AccelBufferSizes { /// /// // Compact the accel structure. /// let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; - pub enum AccelEmitDesc { CompactedSize(DevicePointer), Aabbs(DevicePointer), } -/// Struct representing a bounding box. +/// An axis-aligned bounding box. +/// +/// Used to communicate bounds info to and from OptiX for bounding custom primitives +/// and instances #[repr(C)] #[derive(DeviceCopy, Copy, Clone)] pub struct Aabb { - min_x: f32, - min_y: f32, - min_z: f32, - max_x: f32, - max_y: f32, - max_z: f32, + min: Vector3, + max: Vector3, +} + +impl Aabb { + /// Create a new Aabb by supplying the min and max points + pub fn new>>(min: V, max: V) -> Self { + let min = min.into(); + let max = max.into(); + Self { min, max } + } } impl From<&mut AccelEmitDesc> for sys::OptixAccelEmitDesc { @@ -1213,3 +930,1101 @@ impl From for u32 { } } } + +/// Specify acceleration structure build input data for a curves geometry +/// +/// A curve is a swept surface defined by a 3D spline curve and a varying width (radius). A curve (or "strand") of degree d (3=cubic, 2=quadratic, 1=linear) is represented by N > d vertices and N width values, and comprises N - d segments. Each segment is defined by d+1 consecutive vertices. Each curve may have a different number of vertices. +/// +/// OptiX describes the curve array as a list of curve segments. The primitive id is the segment number. It is the user's responsibility to maintain a mapping between curves and curve segments. Each index buffer entry i = indexBuffer[primid] specifies the start of a curve segment, represented by d+1 consecutive vertices in the vertex buffer, and d+1 consecutive widths in the width buffer. Width is interpolated the same way vertices are interpolated, that is, using the curve basis. +/// +/// Each curves build input has only one SBT record. To create curves with different materials in the same BVH, use multiple build inputs. +pub struct CurveArray<'v, 'w, 'i> { + curve_type: CurveType, + num_primitives: u32, + vertex_buffers: PhantomData<&'v f32>, + num_vertices: u32, + d_vertex_buffers: Vec, + vertex_stride_in_bytes: u32, + width_buffers: PhantomData<&'w f32>, + num_width_buffers: u32, + d_width_buffers: Vec, + width_stride_in_bytes: u32, + index_buffer: &'i DeviceSlice, + index_stride_in_bytes: u32, + flags: GeometryFlags, + primitive_index_offset: u32, +} + +impl<'v, 'w, 'i> Hash for CurveArray<'v, 'w, 'i> { + fn hash(&self, state: &mut H) { + self.curve_type.hash(state); + state.write_u32(self.num_primitives); + state.write_u32(self.num_vertices); + state.write_usize(self.d_vertex_buffers.len()); + state.write_u32(self.vertex_stride_in_bytes); + state.write_u32(self.num_vertices); + state.write_usize(self.d_width_buffers.len()); + state.write_u32(self.width_stride_in_bytes); + state.write_usize(self.index_buffer.len()); + state.write_u32(self.index_stride_in_bytes); + self.flags.hash(state); + state.write_u32(self.primitive_index_offset); + } +} + +impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { + /// Constructor + /// + /// # Parameters + /// * `curve_type` - Curve degree and basis + /// * `vertex_buffers` - A slice of device buffers, one per motion step. + /// The length of this slice must match the number of motion keys specified + /// in [`AccelBuildOptions::motion_options`] + /// * `width_buffers` - Parallel to `vertex_buffers` with matching lengths and + /// number of motion steps. One value per vertex specifying the width of + /// the curve + /// * `index_buffer` - An array of u32, one per curve segment. Each index is + /// the start of `degree+1` consecutive vertices in `vertex_buffers`, and + /// corresponding widths in `width_buffers`. These define a single segment. + /// The length of this array is therefore the number of curve segments + pub fn new( + curve_type: CurveType, + vertex_buffers: &[&'v DeviceSlice], + width_buffers: &[&'w DeviceSlice], + index_buffer: &'i DeviceSlice, + ) -> Result> { + // TODO (AL): Do some sanity checking on the values here + let num_vertices = vertex_buffers[0].len() as u32; + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + + let num_width_buffers = width_buffers.len() as u32; + let d_width_buffers: Vec<_> = width_buffers.iter().map(|b| b.as_device_ptr()).collect(); + + Ok(CurveArray { + curve_type, + num_primitives: index_buffer.len() as u32, + vertex_buffers: PhantomData, + num_vertices, + d_vertex_buffers, + vertex_stride_in_bytes: 0, + width_buffers: PhantomData, + num_width_buffers, + d_width_buffers, + width_stride_in_bytes: 0, + index_buffer, + index_stride_in_bytes: 0, + flags: GeometryFlags::None, + primitive_index_offset: 0, + }) + } + + /// Stride between vertices. If not specified, vertices are assumed to be + /// tightly packed. + pub fn vertex_stride(mut self, stride_in_bytes: u32) -> Self { + self.vertex_stride_in_bytes = stride_in_bytes; + self + } + + /// Stride between width values. If not specified, values are assumed to be + /// tightly packed. + pub fn width_stride(mut self, stride_in_bytes: u32) -> Self { + self.vertex_stride_in_bytes = stride_in_bytes; + self + } + + /// Stride between indices. If not specified, indices are assumed to be + /// tightly packed. + pub fn index_stride(mut self, stride_in_bytes: u32) -> Self { + self.vertex_stride_in_bytes = stride_in_bytes; + self + } + + /// Combination of [`GeometryFlags`] specifying the primitive behaviour + pub fn flags(mut self, flags: GeometryFlags) -> Self { + self.flags = flags; + self + } + + /// Primitive index bias, applied on the device in `optixGetPrimitiveIndex()`. + /// + /// Sum of primitiveIndexOffset and number of primitives must not overflow 32bits. + pub fn primitive_index_offset(mut self, offset: u32) -> Self { + self.primitive_index_offset = offset; + self + } +} + +impl<'v, 'w, 'i> BuildInput for CurveArray<'v, 'w, 'i> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CURVES, + input: sys::OptixBuildInputUnion { + curve_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputCurveArray { + curveType: self.curve_type.into(), + numPrimitives: self.num_primitives, + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const CUdeviceptr, + numVertices: self.num_vertices, + vertexStrideInBytes: self.vertex_stride_in_bytes, + widthBuffers: self.d_width_buffers.as_ptr() as *const CUdeviceptr, + widthStrideInBytes: self.width_stride_in_bytes, + normalBuffers: std::ptr::null(), + normalStrideInBytes: 0, + indexBuffer: self.index_buffer.as_device_ptr(), + indexStrideInBytes: self.index_stride_in_bytes, + flag: self.flags as u32, + primitiveIndexOffset: self.primitive_index_offset, + }), + }, + } + } +} + +/// Specifies the type of curves, either linear, quadratic or cubic b-splines. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum CurveType { + RoundLinear, + RoundQuadraticBSpline, + RoundCubicBSpline, +} + +impl From for sys::OptixPrimitiveType { + fn from(c: CurveType) -> Self { + match c { + CurveType::RoundLinear => sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR, + CurveType::RoundQuadraticBSpline => { + sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE + } + CurveType::RoundCubicBSpline => { + sys::OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE + } + } + } +} + +/// Specifies the type of vertex data +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum VertexFormat { + None = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_NONE as u32, + Float3 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT3 as u32, + Float2 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT2 as u32, + Half3 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF3 as u32, + Half2 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF2 as u32, + SNorm16 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_3 as u32, + SNorm32 = sys::OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_2 as u32, +} + +/// Specifies the type of index data +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum IndicesFormat { + None = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE as u32, + Short3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3 as u32, + Int3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3 as u32, +} + +/// Specifies the format of transform data +#[repr(u32)] +#[derive(Copy, Clone, PartialEq)] +pub enum TransformFormat { + None = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, + MatrixFloat12 = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12, +} + +/// Trait allowing the triangle builds to be generic over the input vertex data. +/// +/// For instance, if you had a custom vertex type: +/// ``` +/// struct MyVertex { +/// x: i16, +/// y: i16, +/// z: i16, +/// nx: f32, +/// ny: f32, +/// nz: f32, +/// } +/// +/// impl Vertex for MyVertex { +/// const FORMAT: VertexFormat = VertexFormat::SNorm16; +/// const STRIDE: u32 = 18; +/// } +/// ``` +pub trait Vertex: cust::memory::DeviceCopy { + const FORMAT: VertexFormat; + const STRIDE: u32 = 0; +} + +#[cfg(feature = "half")] +impl Vertex for [half::f16; 2] { + const FORMAT: VertexFormat = VertexFormat::Half2; +} + +#[cfg(feature = "half")] +impl Vertex for [half::f16; 3] { + const FORMAT: VertexFormat = VertexFormat::Half3; +} + +#[cfg(feature = "half")] +impl Vertex for mint::Vector2 { + const FORMAT: VertexFormat = VertexFormat::Half2; +} + +#[cfg(feature = "half")] +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::Half3; +} + +impl Vertex for [f32; 2] { + const FORMAT: VertexFormat = VertexFormat::Float2; +} + +impl Vertex for [f32; 3] { + const FORMAT: VertexFormat = VertexFormat::Float3; +} + +impl Vertex for [i16; 3] { + const FORMAT: VertexFormat = VertexFormat::SNorm16; +} + +impl Vertex for [i32; 3] { + const FORMAT: VertexFormat = VertexFormat::SNorm32; +} + +impl Vertex for mint::Vector2 { + const FORMAT: VertexFormat = VertexFormat::Float2; +} + +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::Float3; +} + +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::SNorm16; +} + +impl Vertex for mint::Vector3 { + const FORMAT: VertexFormat = VertexFormat::SNorm32; +} + +/// Trait allowing build inputs to be generic over the index type +pub trait IndexTriple: cust::memory::DeviceCopy { + const FORMAT: IndicesFormat; + const STRIDE: u32 = 0; +} + +impl IndexTriple for [u16; 3] { + const FORMAT: IndicesFormat = IndicesFormat::Short3; +} + +impl IndexTriple for [u32; 3] { + const FORMAT: IndicesFormat = IndicesFormat::Int3; +} + +impl IndexTriple for mint::Vector3 { + const FORMAT: IndicesFormat = IndicesFormat::Short3; +} + +impl IndexTriple for mint::Vector3 { + const FORMAT: IndicesFormat = IndicesFormat::Int3; +} + +/// Build input for specifying a (non-indexed) triangle geometry +pub struct TriangleArray<'v, 'g, V: Vertex> { + // We hold slices here to make sure the referenced device memory remains + // valid for the lifetime of the build input + vertex_buffers: PhantomData<&'v V>, + num_vertices: u32, + d_vertex_buffers: Vec, + // per-sbt-record geometry flags + geometry_flags: &'g [GeometryFlags], + pre_transform: Option>, +} + +impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { + pub fn new(vertex_buffers: &[&'v DeviceSlice], geometry_flags: &'g [GeometryFlags]) -> Self { + // TODO (AL): do some sanity checking on the slice lengths here + let num_vertices = vertex_buffers[0].len() as u32; + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + TriangleArray { + vertex_buffers: PhantomData, + num_vertices, + d_vertex_buffers, + geometry_flags, + pre_transform: None, + } + } + + pub fn pre_transform(mut self, pre_transform: DevicePointer<[f32; 12]>) -> Self { + self.pre_transform = Some(pre_transform); + self + } +} + +impl<'v, 'g, V: Vertex> Hash for TriangleArray<'v, 'g, V> { + fn hash(&self, state: &mut H) { + state.write_u32(self.num_vertices); + state.write_usize(self.d_vertex_buffers.len()); + self.geometry_flags.hash(state); + } +} + +impl<'v, 'g, V: Vertex> BuildInput for TriangleArray<'v, 'g, V> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: sys::OptixBuildInputUnion { + triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, + numVertices: self.num_vertices, + vertexFormat: V::FORMAT as u32, + vertexStrideInBytes: V::STRIDE, + indexBuffer: 0, + numIndexTriplets: 0, + indexFormat: 0, + indexStrideInBytes: 0, + flags: self.geometry_flags.as_ptr() as *const _, + numSbtRecords: 1, + sbtIndexOffsetBuffer: 0, + sbtIndexOffsetSizeInBytes: 0, + sbtIndexOffsetStrideInBytes: 0, + primitiveIndexOffset: 0, + preTransform: if let Some(t) = self.pre_transform { + t.as_raw() + } else { + 0 + }, + transformFormat: if self.pre_transform.is_some() { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 + } else { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE + }, + }), + }, + } + } +} + +pub struct IndexedTriangleArray<'v, 'i, V: Vertex, I: IndexTriple> { + // We hold slices here to make sure the referenced device memory remains + // valid for the lifetime of the build input + vertex_buffers: PhantomData<&'v V>, + num_vertices: u32, + d_vertex_buffers: Vec, + index_buffer: &'i DeviceSlice, + // per-object geometry flags + geometry_flags: Vec, + pre_transform: Option>, +} + +impl<'v, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'i, V, I> { + pub fn new( + vertex_buffers: &[&'v DeviceSlice], + index_buffer: &'i DeviceSlice, + geometry_flags: &[GeometryFlags], + ) -> Self { + let num_vertices = vertex_buffers[0].len() as u32; + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + IndexedTriangleArray { + vertex_buffers: PhantomData, + num_vertices, + d_vertex_buffers, + geometry_flags: geometry_flags.to_vec(), + index_buffer, + pre_transform: None, + } + } + + pub fn pre_transform(mut self, pre_transform: DevicePointer<[f32; 12]>) -> Self { + self.pre_transform = Some(pre_transform); + self + } +} + +impl<'v, 'i, V: Vertex, I: IndexTriple> Hash for IndexedTriangleArray<'v, 'i, V, I> { + fn hash(&self, state: &mut H) { + state.write_u32(self.num_vertices); + state.write_usize(self.d_vertex_buffers.len()); + self.geometry_flags.hash(state); + state.write_usize(self.index_buffer.len()); + } +} + +impl<'v, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, 'i, V, I> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_TRIANGLES, + input: sys::OptixBuildInputUnion { + triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { + vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, + numVertices: self.num_vertices, + vertexFormat: V::FORMAT as u32, + vertexStrideInBytes: V::STRIDE, + indexBuffer: self.index_buffer.as_device_ptr(), + numIndexTriplets: self.index_buffer.len() as u32, + indexFormat: I::FORMAT as u32, + indexStrideInBytes: I::STRIDE, + flags: self.geometry_flags.as_ptr() as *const _, + numSbtRecords: 1, + sbtIndexOffsetBuffer: 0, + sbtIndexOffsetSizeInBytes: 0, + sbtIndexOffsetStrideInBytes: 0, + primitiveIndexOffset: 0, + preTransform: if let Some(t) = self.pre_transform { + t.as_raw() + } else { + 0 + }, + transformFormat: if self.pre_transform.is_some() { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12 + } else { + sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE + }, + }), + }, + } + } +} + +pub struct CustomPrimitiveArray<'a, 's> { + aabb_buffers: Vec, + aabb_buffers_marker: PhantomData<&'a Aabb>, + num_primitives: u32, + stride_in_bytes: u32, + flags: Vec, + num_sbt_records: u32, + sbt_index_offset_buffer: Option<&'s DeviceSlice>, + sbt_index_offset_stride_in_bytes: u32, + primitive_index_offset: u32, +} + +impl<'a, 'g, 's> Hash for CustomPrimitiveArray<'a, 's> { + fn hash(&self, state: &mut H) { + state.write_usize(self.aabb_buffers.len()); + state.write_u32(self.num_primitives); + state.write_u32(self.stride_in_bytes); + self.flags.hash(state); + state.write_u32(self.num_sbt_records); + if let Some(b) = self.sbt_index_offset_buffer { + state.write_usize(b.len()); + } else { + state.write_usize(0); + } + state.write_u32(self.sbt_index_offset_stride_in_bytes); + state.write_u32(self.primitive_index_offset); + } +} + +impl<'a, 's> CustomPrimitiveArray<'a, 's> { + pub fn new( + aabb_buffers: &[&'a DeviceSlice], + flags: &[GeometryFlags], + ) -> Result> { + let num_primitives = aabb_buffers.len() as u32; + let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr()).collect(); + + Ok(CustomPrimitiveArray { + aabb_buffers, + aabb_buffers_marker: PhantomData, + num_primitives, + stride_in_bytes: 0, + flags: flags.to_vec(), + num_sbt_records: 1, + sbt_index_offset_buffer: None, + sbt_index_offset_stride_in_bytes: 0, + primitive_index_offset: 0, + }) + } + + pub fn stride(mut self, stride_in_bytes: u32) -> Self { + self.stride_in_bytes = stride_in_bytes; + self + } + + pub fn primitive_index_offset(mut self, offset: u32) -> Self { + self.primitive_index_offset = offset; + self + } + + pub fn num_sbt_records(mut self, num_sbt_records: u32) -> Self { + self.num_sbt_records = num_sbt_records; + self + } + + pub fn sbt_index_offset_buffer( + mut self, + sbt_index_offset_buffer: &'s DeviceSlice, + ) -> Self { + self.sbt_index_offset_buffer = Some(sbt_index_offset_buffer); + self + } + + pub fn sbt_index_offset_buffer_stride(mut self, stride_in_bytes: u32) -> Self { + self.sbt_index_offset_stride_in_bytes = stride_in_bytes; + self + } +} + +impl<'a, 's> BuildInput for CustomPrimitiveArray<'a, 's> { + fn to_sys(&self) -> sys::OptixBuildInput { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES, + input: sys::OptixBuildInputUnion { + custom_primitive_array: std::mem::ManuallyDrop::new( + sys::OptixBuildInputCustomPrimitiveArray { + aabbBuffers: self.aabb_buffers.as_ptr(), + numPrimitives: self.num_primitives, + strideInBytes: self.stride_in_bytes, + flags: self.flags.as_ptr() as *const u32, + numSbtRecords: self.num_sbt_records, + sbtIndexOffsetBuffer: if let Some(sbt_index_offset_buffer) = + self.sbt_index_offset_buffer + { + sbt_index_offset_buffer.as_device_ptr() + } else { + 0 + }, + sbtIndexOffsetSizeInBytes: 4, + sbtIndexOffsetStrideInBytes: self.sbt_index_offset_stride_in_bytes, + primitiveIndexOffset: self.primitive_index_offset, + }, + ), + }, + } + } +} + +#[repr(C, align(16))] +#[derive(Debug, Copy, Clone, DeviceCopy)] +pub struct Instance<'a> { + transform: RowMatrix3x4, + instance_id: u32, + sbt_offset: u32, + visibility_mask: u32, + flags: InstanceFlags, + traversable_handle: TraversableHandle, + pad: [u32; 2], + accel: PhantomData<&'a ()>, +} + +const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); +const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + + +bitflags::bitflags! { + #[derive(DeviceCopy)] + pub struct InstanceFlags: u32 { + const NONE = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE; + const DISABLE_TRIANGLE_FACE_CULLING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING; + const FLIP_TRIANGLE_FACING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING; + const DISABLE_ANYHIT = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT; + const ENFORCE_ANYHIT = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT; + const DISABLE_TRANSFORM = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; + } +} + +impl<'a> Instance<'a> { + pub fn new(accel: &'a T) -> Instance<'a> { + #[cfg_attr(rustfmt, rustfmt_skip)] + Instance { + transform: [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0].into(), + instance_id: 0, + sbt_offset: 0, + visibility_mask: 255, + flags: InstanceFlags::NONE, + traversable_handle: accel.handle(), + pad: [0; 2], + accel: PhantomData, + } + } + + pub unsafe fn from_handle(traversable_handle: TraversableHandle) -> Instance<'static> { + #[cfg_attr(rustfmt, rustfmt_skip)] + Instance { + transform: [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0].into(), + instance_id: 0, + sbt_offset: 0, + visibility_mask: 255, + flags: InstanceFlags::NONE, + traversable_handle, + pad: [0; 2], + accel: PhantomData, + } + } + + pub fn transform>>(mut self, transform: T) -> Instance<'a> { + self.transform = transform.into(); + self + } + + pub fn instance_id(mut self, instance_id: u32) -> Instance<'a> { + self.instance_id = instance_id; + self + } + + pub fn sbt_offset(mut self, sbt_offset: u32) -> Instance<'a> { + self.sbt_offset = sbt_offset; + self + } + + pub fn visibility_mask(mut self, visibility_mask: u8) -> Instance<'a> { + self.visibility_mask = visibility_mask as u32; + self + } + + pub fn flags(mut self, flags: InstanceFlags) -> Instance<'a> { + self.flags = flags; + self + } +} + +pub struct InstanceArray<'i, 'a> { + instances: &'i DeviceSlice>, +} + +impl<'i, 'a> InstanceArray<'i, 'a> { + pub fn new(instances: &'i DeviceSlice>) -> InstanceArray<'i, 'a> { + InstanceArray { instances } + } +} + +impl<'i, 'a> Hash for InstanceArray<'i, 'a> { + fn hash(&self, state: &mut H) { + state.write_usize(self.instances.len()); + } +} + +impl<'i, 'a> BuildInput for InstanceArray<'i, 'a> { + fn to_sys(&self) -> sys::OptixBuildInput { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + }) + } + } + } else { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + aabbs: 0, + numAabbs: 0, + }) + } + } + } + } + } +} + +pub struct InstancePointerArray<'i> { + instances: &'i DeviceSlice, +} + +impl<'i> InstancePointerArray<'i> { + pub fn new(instances: &'i DeviceSlice) -> InstancePointerArray { + InstancePointerArray { instances } + } +} + +impl<'i> Hash for InstancePointerArray<'i> { + fn hash(&self, state: &mut H) { + state.write_usize(self.instances.len()); + } +} + + +impl<'i> BuildInput for InstancePointerArray<'i> { + fn to_sys(&self) -> sys::OptixBuildInput { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + }) + } + } + } else { + sys::OptixBuildInput { + type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, + input: sys::OptixBuildInputUnion { + instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { + instances: self.instances.as_device_ptr(), + numInstances: self.instances.len() as u32, + aabbs: 0, + numAabbs: 0, + }) + } + } + } + } + } +} + +/// A scene graph node holding a child node with a transform to be applied during +/// ray traversal. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct StaticTransformWrapper(sys::OptixStaticTransform); + +unsafe impl DeviceCopy for StaticTransformWrapper {} + +const_assert_eq!( + std::mem::size_of::(), + std::mem::size_of::(), +); + +/// Stores the device memory and the [`TraversableHandle`] for a [`StaticTransform`] +pub struct DeviceStaticTransform { + buf: DeviceBox, + hnd: TraversableHandle, +} + +impl DeviceStaticTransform { + /// Create a new DeviceStaticTransform by copying the given [`StaticTransform`] + /// to the device and converting the resulting pointer to an OptiX [`Traversable`]; + pub fn new> + Clone>( + ctx: &DeviceContext, + child: &T, + transform: &M, + inv_transform: &M, + ) -> Result { + let transform = (*transform).clone().into(); + let inv_transform = (*inv_transform).clone().into(); + let buf = DeviceBox::new(&StaticTransformWrapper(sys::OptixStaticTransform { + child: child.handle().inner, + transform: transform.into(), + invTransform: inv_transform.into(), + ..Default::default() + }))?; + let hnd = unsafe { + convert_pointer_to_traversable_handle( + ctx, + buf.as_device_ptr().as_raw(), + TraversableType::StaticTransform, + )? + }; + + Ok(DeviceStaticTransform { buf, hnd }) + } + + /// Create a new DeviceStaticTransform from device memory and pre-converted + /// handle + pub unsafe fn from_raw_parts( + buf: DeviceBox, + hnd: TraversableHandle, + ) -> Self { + Self { buf, hnd } + } +} + +impl Traversable for DeviceStaticTransform { + fn handle(&self) -> TraversableHandle { + self.hnd + } +} + +/// A scene graph node holding a child node with a motion transform to be applied +/// during ray traversal, represented as SRT Data. +/// +/// Stores the device memory and the [`TraversableHandle`] for a [`sys::OptixMatrixMotionTransform`] +/// and an arbitrary number of motion keys +pub struct DeviceMatrixMotionTransform { + buf: DeviceBuffer, + hnd: TraversableHandle, +} + +impl DeviceMatrixMotionTransform { + /// Create a new MatrixMotionTransform with the given time range, flags and + /// motion keys. + /// + /// This method handles all memory allocation and copying the data to the + /// device. + /// + /// # Errors + /// * [`Error::TooFewMotionKeys`] - If `transforms.len() < 2` + /// * [`Error::OptixError`] - Any internal OptiX error + /// * [`Error::CudaError`] - Any internal OptiX error + pub fn new( + ctx: &DeviceContext, + child: &T, + time_begin: f32, + time_end: f32, + flags: MotionFlags, + transforms: &[RowMatrix3x4], + ) -> Result { + let num_keys = transforms.len(); + if num_keys < 2 { + return Err(Error::TooFewMotionKeys(num_keys)); + } + + let mmt = sys::OptixMatrixMotionTransform { + child: child.handle().inner, + motionOptions: sys::OptixMotionOptions { + numKeys: num_keys as u16, + timeBegin: time_begin, + timeEnd: time_end, + flags: flags.bits(), + }, + ..Default::default() + }; + + let size = + size_of::() + size_of::() * 12 * (num_keys - 2); + + // copy the transform data + unsafe { + // allocate memory for the transform struct and all the matrices + let buf = DeviceBuffer::::uninitialized(size)?; + + // get the offset of the matrix data from the base of the struct + let transform_ptr = buf + .as_ptr() + .add(offset_of!(sys::OptixMatrixMotionTransform, transform)); + + // copy the transform data. + // Note we're writing 24 bytes of data for the transform field that + // we'll just overwrite on the next line, but it's probably more + // efficient to do that than to write each field individually + cust::memory::memcpy_htod( + buf.as_device_ptr(), + &mmt as *const _ as *const c_void, + size_of::(), + )?; + + // copy the matrix data + cust::memory::memcpy_htod( + transform_ptr.as_raw(), + transforms.as_ptr() as *const c_void, + std::mem::size_of::>() * num_keys, + )?; + + let hnd = convert_pointer_to_traversable_handle( + ctx, + buf.as_device_ptr(), + TraversableType::MatrixMotionTransform, + )?; + + Ok(Self { buf, hnd }) + } + } + + /// Create a new MatrixMotionTransform from device memory and pre-converted + /// handle + pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Self { + Self { buf, hnd } + } +} + +impl Traversable for DeviceMatrixMotionTransform { + fn handle(&self) -> TraversableHandle { + self.hnd + } +} + +/// Represents an SRT transformation. +/// +/// An SRT transformation can represent a smooth rotation with fewer motion keys +/// than a matrix transformation. Each motion key is constructed from elements +/// taken from a matrix $S$, a quaternion $R$, and a translation $T$. +/// +/// The scaling matrix, +/// $$ +/// S=\begin{bmatrix} +/// sx & a & b & pvx \cr 0 & sy & c & pvy \cr 0 & 0 & sz & pvz +/// \end{bmatrix} +/// $$ +/// +/// defines an affine transformation that can include scale, shear, and a translation. +/// The translation allows to define the pivot point for the subsequent rotation. +/// +/// The rotation quaternion $R = [qx, qy, qz, qw]$ describes a rotation with angular +/// component $qw = \cos(\theta / 2)$ and other components +/// $[qx, qy, qz] = \sin(\theta / 2) \cdot [ax, ay, az]$ where the axis $[ax, ay, az]$ +/// is normalized. +/// +/// The translation matrix, +/// $$ +/// T = \begin{bmatrix} 1 & 0 & 0 & tx \cr 0 & 1 & 0 & ty \cr 0 & 0 & 1 & tz \end{bmatrix} +/// $$ +/// defines another translation that is applied after the rotation. Typically, this +/// translation includes the inverse translation from the matrix $S$ to reverse the +/// translation for the pivot point for $R$. +/// +/// To obtain the effective transformation at time $t$, the elements of the components +/// of $S$, $R$, and $T$ will be interpolated linearly. The components are then +/// multiplied to obtain the combined transformation $C = T \cdot R \cdot S$. The +/// transformation $C$ is the effective object-to-world transformations at time $t$, +/// and $C^{-1}$ is the effective world-to-object transformation at time $t$. +/// +#[repr(transparent)] +#[derive(Copy, Clone, Debug)] +pub struct SrtData(sys::OptixSRTData); + +unsafe impl DeviceCopy for SrtData {} + +impl Deref for SrtData { + type Target = sys::OptixSRTData; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A scene graph node holding a child node with a motion transform to be applied +/// during ray traversal, represented as SRT Data. +/// +/// Stores the device memory and the [`TraversableHandle`] for a [`sys::OptixSRTMotionTransform`] +/// and an arbitrary number of motion keys +/// +/// FIXME (AL): need to see about checking the limits on the number of keys +pub struct DeviceSrtMotionTransform { + buf: DeviceBuffer, + hnd: TraversableHandle, +} + +impl DeviceSrtMotionTransform { + /// Create a new SrtMotionTransform from the given child [`TraversableHandle`], + /// time range, flags and [`SrtData`] + /// + /// This method handles all memory allocation and copying the data to the + /// device. + /// + /// # Errors + /// * [`Error::TooFewMotionKeys`] - If `srt_data.len() < 2` + /// * [`Error::OptixError`] - Any internal OptiX error + /// * [`Error::CudaError`] - Any internal OptiX error + pub fn new( + ctx: &DeviceContext, + child: &T, + time_begin: f32, + time_end: f32, + flags: MotionFlags, + srt_data: &[SrtData], + ) -> Result { + let num_keys = srt_data.len(); + if num_keys < 2 { + return Err(Error::TooFewMotionKeys(num_keys)); + } + + let mmt = sys::OptixSRTMotionTransform { + child: child.handle().inner, + motionOptions: sys::OptixMotionOptions { + numKeys: num_keys as u16, + timeBegin: time_begin, + timeEnd: time_end, + flags: flags.bits(), + }, + ..Default::default() + }; + + let size = size_of::() + + size_of::() * size_of::() * (num_keys - 2); + + // copy the transform data + unsafe { + // allocate memory for the transform struct and all the matrices + let buf = DeviceBuffer::::uninitialized(size)?; + + // get the offset of the matrix data from the base of the struct + let transform_ptr = buf + .as_ptr() + .add(offset_of!(sys::OptixSRTMotionTransform, srtData)); + + // copy the transform data. + // Note we're writing 24 bytes of data for the transform field that + // we'll just overwrite on the next line, but it's probably more + // efficient to do that than to write each field individually + cust::memory::memcpy_htod( + buf.as_device_ptr(), + &mmt as *const _ as *const c_void, + size_of::(), + )?; + + // copy the matrix data + cust::memory::memcpy_htod( + transform_ptr.as_raw(), + srt_data.as_ptr() as *const c_void, + std::mem::size_of::() * num_keys, + )?; + + let hnd = convert_pointer_to_traversable_handle( + ctx, + buf.as_device_ptr(), + TraversableType::SrtMotionTransform, + )?; + + Ok(Self { buf, hnd }) + } + } + + /// Create a new SrtMotionTransform from device memory and pre-converted + /// handle + pub unsafe fn from_raw_parts(buf: DeviceBuffer, hnd: TraversableHandle) -> Self { + Self { buf, hnd } + } +} + +impl Traversable for DeviceSrtMotionTransform { + fn handle(&self) -> TraversableHandle { + self.hnd + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TraversableType { + StaticTransform, + MatrixMotionTransform, + SrtMotionTransform, +} + +impl From for sys::OptixTraversableType { + fn from(t: TraversableType) -> Self { + match t { + TraversableType::StaticTransform => { + sys::OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_STATIC_TRANSFORM + } + TraversableType::MatrixMotionTransform => { + sys::OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_MATRIX_MOTION_TRANSFORM + } + TraversableType::SrtMotionTransform => { + sys::OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_SRT_MOTION_TRANSFORM + } + } + } +} + +/// Convert a device pointer into a [`TraversableHandle`]. +/// +/// OptiX transform traversables are managed by the application. Once you have +/// created your transform and copied it to the device, use this to get a +/// [`TraversableHandle`] from it. +pub unsafe fn convert_pointer_to_traversable_handle( + ctx: &DeviceContext, + ptr: CUdeviceptr, + pointer_type: TraversableType, +) -> Result { + let mut inner = 0; + Ok(optix_call!(optixConvertPointerToTraversableHandle( + ctx.raw, + ptr, + pointer_type.into(), + &mut inner + )) + .map(|_| TraversableHandle { inner })?) +} diff --git a/crates/optix/src/context.md b/crates/optix/src/context.md new file mode 100644 index 00000000..0645dc4e --- /dev/null +++ b/crates/optix/src/context.md @@ -0,0 +1,161 @@ +OptiX Device Context handling. + +A context is created by [`DeviceContext::new()`] and is used to manage a single +GPU. The NVIDIA OptiX 7 device context is created by specifying the CUDA +context associated with the device. + +``` +# fn doit() -> Result<(), Box> { +use optix::prelude as ox; +use cust::prelude as cu; + +// Initialize cuda and optix +cust::init(cu::CudaFlags::empty())?; +ox::init()?; + +// Create a cuda context for the first device +let device = cu::Device::get_device(0)?; +let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +cu::ContextFlags::MAP_HOST, device)?; + +// Create optix device context +let ctx = ox::DeviceContext::new(&cu_ctx, false)?; + +# Ok(()) +# } +``` +A small set of context properties exist for determining sizes and limits. These +are queried using [`DeviceContext::get_property()`]. Such properties include +maximum trace depth, maximum traversable graph depth, maximum primitives per +build input, and maximum number of instances per acceleration structure. + +The context may retain ownership of any GPU resources necessary to launch the +ray tracing kernels. Some API objects will retain host memory. These are defined +with create/destroy patterns in the API. The context's `Drop` impl will clean +up any host or device resources associated with the context. If any other API +objects associated with this context still exist when the context is destroyed, +they are also destroyed. + +An application may combine any mixture of supported GPUs as long as the data +transfer and synchronization is handled appropriately. Some applications may +choose to simplify multi-GPU handling by restricting the variety of these blends, +for example, by mixing only GPUs of the same streaming multiprocessor version +to simplify data sharing. + +## Logging callbacks + +A logging callback closure can be specified using [`DeviceContext::set_log_callback`]. +The closure has the signiature: +`F: FnMut(u32, &str, &str) + 'static` + +The first argument is the log level and indicates the serverity of the message: + +* 0 - disable: Setting the callback level will disable all messages. The +callback function will not be called in this case. +* 1 - fatal: A non-recoverable error. The context and/or OptiX itself + might +no longer be in a usable state. +* 2 - error: A recoverable error, e.g., when passing invalid call +parameters. +* 3 - warning: Hints that OptiX might not behave exactly as requested by +the user or may perform slower than expected. +* 4 - print: Status or progress messages. +Higher levels might occur. +The second argument is a message category description (for example, "SCENE STAT") +The last argument is the message itself. + +## Compilation caching + +Compilation of input programs will be cached to disk when creating [`Module`](crate::module::Module), +[`ProgramGroup`](crate::program_group::ProgramGroup), and +[`Pipeline`](crate::pipeline::Pipeline) objects if caching has been enabled. + +Subsequent compilation can reuse the cached data to improve the time to create +these objects. The cache can be shared between multiple [`DeviceContext`] +objects, and NVIDIA OptiX 7 will take care of ensuring correct multi-threaded +access to the cache. If no sharing between [`DeviceContext`] objects is desired, +the path to the cache can be set differently for each [`DeviceContext`]. +Caching can be disabled entirely by setting the environment variable +`OPTIX_CACHE_MAXSIZE` to 0. Disabling the cache via the environment variable +will not affect existing cache files or their contents. + +The disk cache can be controlled with: + +### [`DeviceContext::set_cache_enabled()`] +The cache database is initialized when the device context is created and when +enabled through this function call. If the database cannot be initialized when +the device context is created, caching will be disabled; a message is reported +to the log callback if caching is enabled. In this case, the call to +[`DeviceContext::new()`] does not return an error. To ensure that cache +initialization succeeded on context creation, the status can be queried using +[`DeviceContext::get_cache_enabled`]. If caching is disabled, the cache can be +reconfigured and then enabled using [`DeviceContext::set_cache_enabled`]. If +the cache database cannot be initialized, an error is returned. Garbage +collection is performed on the next write to the cache database, not when the +cache is enabled. + +### [`DeviceContext::set_cache_location`] +The disk cache is created in the directory specified by location. The directory +is created if it does not exist. + +The cache database is created immediately if the cache is currently enabled. +Otherwise the cache database is created later when the cache is enabled. An +error is returned if it is not possible to create the cache database file at +the specified location for any reason (for example, if the path is invalid or +if the directory is not writable) and caching will be disabled. If the disk +cache is located on a network file share, behavior is undefined. + +The location of the disk cache can be overridden with the environment variable +`OPTIX_CACHE_PATH`. This environment variable takes precedence over the value +passed to this function when the disk cache is enabled. + +The default location of the cache depends on the operating system: +* Windows - `%LOCALAPPDATA%\NVIDIA\OptixCache` +* Linux - `/var/tmp/OptixCache_username`, or `/tmp/OptixCache_username` if the +first choice is not usable. The underscore and username suffix are omitted if +the username cannot be obtained. + +### [`DeviceContext::set_cache_database_sizes()`] +Parameters `low` and `high` set the low and high water marks for disk cache +garbage collection. Setting either limit to zero disables garbage collection. +Garbage collection only happens when the cache database is written. It is +triggered whenever the cache data size exceeds the high water mark and proceeding +until the size reaches the low water mark. Garbage collection always frees enough +space to allow the insertion of the new entry within the boundary of the low +water mark. An error is returned if either limit is nonzero and the high water +mark is lower than the low water mark. If more than one device context accesses +the same cache database with different high and low water mark values, the device +context uses its values when writing to the cache database. + +The high water mark can be overridden with the environment variable +`OPTIX_CACHE_MAXSIZE`. Setting `OPTIX_CACHE_MAXSIZE` to 0 will disable the cache. +Negative and non-integer values will be ignored. + +`OPTIX_CACHE_MAXSIZE` takes precedence over the `high` value passed to this +function. The low water mark will be set to half the value of +`OPTIX_CACHE_MAXSIZE`. + +Corresponding get* functions are supplied to retrieve the current value of these +cache properties. + +## Validation Mode +The NVIDIA OptiX 7 validation mode can help uncover errors which might otherwise +go undetected or which occur only intermittently and are difficult to locate. +Validation mode enables additional tests and settings during application +execution. This additional processing can reduce performance, so it should only +be used during debugging or in the final testing phase of a completed application. + +Validation mode can be enabled by passing `true` to the `enable_validation` +parameter of [`DeviceContext::new()`]. + +[`OptixError::ValidationFailure`](crate::error::OptixError::ValidationFailure) +will be signalled if an error is caught when validation mode is enabled. +[`launch()`](crate::launch) will synchronize after the launch and report errors, +if any. + +Among other effects, validation mode implicitly enables all OptiX debug +exceptions and provides an exception program if none is provided. The first +non-user exception caught inside an exception program will therefore be reported +and the launch terminated immediately. This will make exceptions more visible +that otherwise might be overlooked. + diff --git a/crates/optix/src/context.rs b/crates/optix/src/context.rs index 5f86ee09..0ead60f9 100644 --- a/crates/optix/src/context.rs +++ b/crates/optix/src/context.rs @@ -1,169 +1,7 @@ -//! OptiX Device Context handling. -//! -//! A context is created by [`DeviceContext::new()`] and is used to manage a single -//! GPU. The NVIDIA OptiX 7 device context is created by specifying the CUDA -//! context associated with the device. -//! -//! ``` -//! # fn doit() -> Result<(), Box> { -//! use optix::prelude as ox; -//! use cust::prelude as cu; -//! -//! // Initialize cuda and optix -//! cust::init(cu::CudaFlags::empty())?; -//! ox::init()?; -//! -//! // Create a cuda context for the first device -//! let device = cu::Device::get_device(0)?; -//! let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | -//! cu::ContextFlags::MAP_HOST, device)?; -//! -//! // Create optix device context -//! let ctx = ox::DeviceContext::new(&cu_ctx, false)?; -//! -//! # Ok(()) -//! # } -//! ``` -//! A small set of context properties exist for determining sizes and limits. These -//! are queried using [`DeviceContext::get_property()`]. Such properties include -//! maximum trace depth, maximum traversable graph depth, maximum primitives per -//! build input, and maximum number of instances per acceleration structure. -//! -//! The context may retain ownership of any GPU resources necessary to launch the -//! ray tracing kernels. Some API objects will retain host memory. These are defined -//! with create/destroy patterns in the API. The context's `Drop` impl will clean -//! up any host or device resources associated with the context. If any other API -//! objects associated with this context still exist when the context is destroyed, -//! they are also destroyed. -//! -//! An application may combine any mixture of supported GPUs as long as the data -//! transfer and synchronization is handled appropriately. Some applications may -//! choose to simplify multi-GPU handling by restricting the variety of these blends, -//! for example, by mixing only GPUs of the same streaming multiprocessor version -//! to simplify data sharing. -//! -//! ## Logging callbacks -//! -//! A logging callback closure can be specified using [`DeviceContext::set_log_callback`]. -//! The closure has the signiature: -//! `F: FnMut(u32, &str, &str) + 'static` -//! -//! The first argument is the log level and indicates the serverity of the message: -//! -//! * 0 - disable: Setting the callback level will disable all messages. The -//! callback function will not be called in this case. -//! * 1 - fatal: A non-recoverable error. The context and/or OptiX itself -//! might -//! no longer be in a usable state. -//! * 2 - error: A recoverable error, e.g., when passing invalid call -//! parameters. -//! * 3 - warning: Hints that OptiX might not behave exactly as requested by -//! the user or may perform slower than expected. -//! * 4 - print: Status or progress messages. -//! Higher levels might occur. -//! The second argument is a message category description (for example, "SCENE STAT") -//! The last argument is the message itself. -//! -//! ## Compilation caching -//! -//! Compilation of input programs will be cached to disk when creating [`Module`](crate::module::Module), -//! [`ProgramGroup`](crate::program_group::ProgramGroup), and -//! [`Pipeline`](crate::pipeline::Pipeline) objects if caching has been enabled. -//! -//! Subsequent compilation can reuse the cached data to improve the time to create -//! these objects. The cache can be shared between multiple [`DeviceContext`] -//! objects, and NVIDIA OptiX 7 will take care of ensuring correct multi-threaded -//! access to the cache. If no sharing between [`DeviceContext`] objects is desired, -//! the path to the cache can be set differently for each [`DeviceContext`]. -//! Caching can be disabled entirely by setting the environment variable -//! `OPTIX_CACHE_MAXSIZE` to 0. Disabling the cache via the environment variable -//! will not affect existing cache files or their contents. -//! -//! The disk cache can be controlled with: -//! -//! ### [`DeviceContext::set_cache_enabled()`] -//! The cache database is initialized when the device context is created and when -//! enabled through this function call. If the database cannot be initialized when -//! the device context is created, caching will be disabled; a message is reported -//! to the log callback if caching is enabled. In this case, the call to -//! [`DeviceContext::new()`] does not return an error. To ensure that cache -//! initialization succeeded on context creation, the status can be queried using -//! [`DeviceContext::get_cache_enabled`]. If caching is disabled, the cache can be -//! reconfigured and then enabled using [`DeviceContext::set_cache_enabled`]. If -//! the cache database cannot be initialized, an error is returned. Garbage -//! collection is performed on the next write to the cache database, not when the -//! cache is enabled. -//! -//! ### [`DeviceContext::set_cache_location`] -//! The disk cache is created in the directory specified by location. The directory -//! is created if it does not exist. -//! -//! The cache database is created immediately if the cache is currently enabled. -//! Otherwise the cache database is created later when the cache is enabled. An -//! error is returned if it is not possible to create the cache database file at -//! the specified location for any reason (for example, if the path is invalid or -//! if the directory is not writable) and caching will be disabled. If the disk -//! cache is located on a network file share, behavior is undefined. -//! -//! The location of the disk cache can be overridden with the environment variable -//! `OPTIX_CACHE_PATH`. This environment variable takes precedence over the value -//! passed to this function when the disk cache is enabled. -//! -//! The default location of the cache depends on the operating system: -//! * Windows - `%LOCALAPPDATA%\NVIDIA\OptixCache` -//! * Linux - `/var/tmp/OptixCache_username`, or `/tmp/OptixCache_username` if the -//! first choice is not usable. The underscore and username suffix are omitted if -//! the username cannot be obtained. -//! -//! ### [`DeviceContext::set_cache_database_sizes()`] -//! Parameters `low` and `high` set the low and high water marks for disk cache -//! garbage collection. Setting either limit to zero disables garbage collection. -//! Garbage collection only happens when the cache database is written. It is -//! triggered whenever the cache data size exceeds the high water mark and proceeding -//! until the size reaches the low water mark. Garbage collection always frees enough -//! space to allow the insertion of the new entry within the boundary of the low -//! water mark. An error is returned if either limit is nonzero and the high water -//! mark is lower than the low water mark. If more than one device context accesses -//! the same cache database with different high and low water mark values, the device -//! context uses its values when writing to the cache database. -//! -//! The high water mark can be overridden with the environment variable -//! `OPTIX_CACHE_MAXSIZE`. Setting `OPTIX_CACHE_MAXSIZE` to 0 will disable the cache. -//! Negative and non-integer values will be ignored. -//! -//! `OPTIX_CACHE_MAXSIZE` takes precedence over the `high` value passed to this -//! function. The low water mark will be set to half the value of -//! `OPTIX_CACHE_MAXSIZE`. -//! -//! Corresponding get* functions are supplied to retrieve the current value of these -//! cache properties. -//! -//! ## Validation Mode -//! The NVIDIA OptiX 7 validation mode can help uncover errors which might otherwise -//! go undetected or which occur only intermittently and are difficult to locate. -//! Validation mode enables additional tests and settings during application -//! execution. This additional processing can reduce performance, so it should only -//! be used during debugging or in the final testing phase of a completed application. -//! -//! Validation mode can be enabled by passing `true` to the `enable_validation` -//! parameter of [`DeviceContext::new()`]. -//! -//! [`OptixError::ValidationFailure`](crate::error::OptixError::ValidationFailure) -//! will be signalled if an error is caught when validation mode is enabled. -//! [`launch()`](crate::launch) will synchronize after the launch and report errors, -//! if any. -//! -//! Among other effects, validation mode implicitly enables all OptiX debug -//! exceptions and provides an exception program if none is provided. The first -//! non-user exception caught inside an exception program will therefore be reported -//! and the launch terminated immediately. This will make exceptions more visible -//! that otherwise might be overlooked. - use std::os::raw::{c_char, c_uint}; use std::{ ffi::{c_void, CStr, CString}, mem::MaybeUninit, - ptr, }; use cust::context::ContextHandle; diff --git a/crates/optix/src/denoiser.md b/crates/optix/src/denoiser.md new file mode 100644 index 00000000..46124e7b --- /dev/null +++ b/crates/optix/src/denoiser.md @@ -0,0 +1,14 @@ +# NVIDIA AI Denoiser + +Image areas that have not yet fully converged during rendering will often exhibit pixel-scale noise due to the insufficient amount of information gathered by the renderer. This grainy appearance in an image may be caused by low iteration counts, especially in scenes with complex lighting environments and material calculations. + +The NVIDIA AI Denoiser can estimate the converged image from a partially converged image. Instead of improving image quality through a larger number of path tracing iterations, the denoiser can produce images of acceptable quality with far fewer iterations by post-processing the image. + +The denoiser is based on statistical data sets that guide the denoising process. These data, represented by a binary blob called a training model, are produced from a large number of rendered images in different stages of convergence. The images are used as input to an underlying deep learning system. (See the NVIDIA Developer article “Deep Learning” for more information about deep-learning systems.) + +Because deep-learning training needs significant computational resources—even obtaining a sufficient number of partially converged images can be difficult—a general-purpose model is included with the OptiX software. This model is suitable for many renderers. However, the model may not yield optimal results when applied to images produced by renderers with very different noise characteristics compared to those used in the original training data. + +Post-processing rendered images includes image filters, such as blurring or sharpening, or reconstruction filters, such as box, triangle, or Gaussian filters. Custom post-processing performed on a noisy image can lead to unsatisfactory denoising results. During post-processing, the original high-frequency, per-pixel noise may become smeared across multiple pixels, making it more difficult to detect and be handled by the model. Therefore, post-processing operations should be done after the denoising process, while reconstruction filters should be implemented by using filter importance-sampling. + +In general, the pixel color space of an image that is used as input for the denoiser should match the color space of the images on which the denoiser was trained. However, slight variations, such as substituting sRGB with a simple gamma curve, should not have a noticeable impact. Images used for the training model included with the NVIDIA AI Denoiser distribution were output directly as HDR data. + diff --git a/crates/optix/src/error.rs b/crates/optix/src/error.rs index f29fd386..b7bc460d 100644 --- a/crates/optix/src/error.rs +++ b/crates/optix/src/error.rs @@ -197,6 +197,7 @@ pub enum Error { PipelineCreation { source: OptixError, log: String }, AccelUpdateMismatch, NulBytesInString, + TooFewMotionKeys(usize), } impl From for Error { @@ -221,6 +222,7 @@ impl std::error::Error for Error { Self::PipelineCreation { source, .. } => Some(source), Self::AccelUpdateMismatch => None, Self::NulBytesInString => None, + Self::TooFewMotionKeys(_) => None, } } } @@ -237,6 +239,7 @@ impl Display for Error { Self::PipelineCreation { log, .. } => write!(f, "Pipeline creation error: {}", log), Self::AccelUpdateMismatch => write!(f, "Build inputs passed to DynamicAccel::update do not match the structure of those used to build the accel"), Self::NulBytesInString => write!(f, "The provided string contained nul bytes"), + Self::TooFewMotionKeys(num) => write!(f, "Provided too few motion keys ({}) for transform. Must provide at least 2", num), } } } diff --git a/crates/optix/src/impl_glam.rs b/crates/optix/src/impl_glam.rs index 20a7084f..d6e5d683 100644 --- a/crates/optix/src/impl_glam.rs +++ b/crates/optix/src/impl_glam.rs @@ -1,4 +1,4 @@ -use crate::triangle_array::{IndexTriple, IndicesFormat, Vertex, VertexFormat}; +use crate::acceleration::{IndexTriple, IndicesFormat, Vertex, VertexFormat}; impl Vertex for glam::Vec3 { const FORMAT: VertexFormat = VertexFormat::Float3; diff --git a/crates/optix/src/introduction.md b/crates/optix/src/introduction.md new file mode 100644 index 00000000..9f7059e0 --- /dev/null +++ b/crates/optix/src/introduction.md @@ -0,0 +1,310 @@ +# Overview of OptiX + + NVIDIA OptiX 7 is intended for ray tracing applications that use NVIDIA® CUDA® + technology, such as: + +* Film and television visual effects +* Computer-aided design for engineering and manufacturing +* Light maps generated by path tracing +* High-performance computing +* LIDAR simulation + +NVIDIA OptiX 7 also includes support for motion blur and multi-level transforms, +features required by ray-tracing applications designed for production-quality +rendering. + +## Terms used in this documentation + +OptiX uses a shorthand to describe some common program components and data +structures that are worth memorizing as they crop up a lot. + +### Program Types +* **`RG`** - Ray generation - This is the entry point into the OptiX programming + model and is generally responsible for creating and tracing rays. +* **`IS`** - Intersection - Run to provide intersections with custom, + user-defined primitives (as opposed to built-in triangles). +* **`AH`** - Any-hit - Run during ray traversal for each potential intersection. + reports to OptiX whether the intersection should be considered valid and + whether to stop traversal. +* **`CH`** - Closest-hit - Run only for the closest hit found during ray + traversal. Can inspect and interpolating properties of the intersected + primitive. +* **`MS`** - Miss - Run whenever a ray ezits the scene without hitting anything. +* **`EX`** - Ezception - Run whenever an exception condition is found. +* **`DC`** - Direct callable - Can be called manually from another program. + May not itself continue ray traversal (i.e. may not call `optizTrace`). +* **`CC`** - Continuation callable - Can be called manually from another + program and may continue ray traversal. + +### Acceleration structures +* geometry-AS/GAS/BLAS - Geometry/Bottom-level acceleration structure. An + acceleration structure built over geometric primitives such as curves +* instance-AS/IAS/TLAS - Instance/Top-level acceleration structure built + over other acceleration structures and/or transform nodes in order to + compose more complez scenes and implement instancing and rigid + transformations. + +In this document and in the names of API elements, the “host” is the processor +that begins ezecution of an application. The “device” is the GPU with which +the host interacts. A “build” is the creation of an acceleration structure on +the device as initiated by the host. + +## Overview +The NVIDIA OptiX 7 API is a CUDA-centric API that is invoked by a CUDA-based +application. The API is designed to be stateless, multi-threaded and +asynchronous, providing ezplicit control over performance-sensitive operations +like memory management and shader compilation. + +It supports a lightweight representation for scenes that can represent +instancing, vertez- and transform-based motion blur, with built-in triangles, +built-in swept curves, and user-defined primitives. The API also includes +highly-tuned kernels and neural networks for machine-learning-based denoising. + +An NVIDIA OptiX 7 contezt controls a single GPU. The context does not hold bulk +CPU allocations, but like CUDA, may allocate resources on the device necessary +to invoke the launch. It can hold a small number of handle objects that are used +to manage ezpensive host-based state. These handle objects are automatically +released when the contezt is destroyed. Handle objects, where they do exist, +consume a small amount of host memory (typically less than 100 kilobytes) and +are independent of the size of the GPU resources being used. For ezceptions to +this rule, see “Program pipeline creation”. + +The application invokes the creation of acceleration structures (called builds), +compilation, and host-device memory transfers. All API functions employ CUDA +streams and invoke GPU functions asynchronously, where applicable. If more than +one stream is used, the application must ensure that required dependencies are +satisfied by using CUDA events to avoid race conditions on the GPU. + +Applications can specify multi-GPU capabilities with a few different recipes. +Multi-GPU features such as efficient load balancing or the sharing of GPU memory +via NVLINK must be handled by the application developer. + +For efficiency and coherence, the NVIDIA OptiX 7 runtime—unlike CUDA kernels— +allows the ezecution of one task, such as a single ray, to be moved at any point +in time to a different lane, warp or streaming multiprocessor (SM). +(See section “Kernel Focus” in the CUDA Toolkit Documentation.) Consequently, +applications cannot use shared memory, synchronization, barriers, or other +SM-thread-specific programming constructs in their programs supplied to OptiX. + +The NVIDIA OptiX 7 programming model provides an API that future-proofs +applications: as new NVIDIA hardware features are released, ezisting programs +can use them. For ezample, software-based ray tracing algorithms can be mapped +to hardware when support is added or mapped to software when the underlying +algorithms or hardware support such changes. + +## Basic concepts and definitions + +### Program +In NVIDIA OptiX 7, a program is a block of ezecutable code on the GPU that +represents a particular shading operation. This is called a shader in DXR and +Vulkan. For consistency with prior versions of NVIDIA OptiX 7, the term program +is used in the current documentation. This term also serves as a reminder that +these blocks of ezecutable code are programmable components in the system that +can do more than shading. See “Program input”. + +## Program and Data Model +NVIDIA OptiX 7 implements a single-ray programming model with ray generation, +any-hit, closest-hit, miss and intersection programs. + +The ray tracing pipeline provided by NVIDIA OptiX 7 is implemented by eight types +of programs: + +### Ray generation (RG) +The entry point into the ray tracing pipeline, invoked by the system in parallel +for each pizel, sample, or other user-defined work assignment. See +“Ray generation launches”. + +### Intersection (IS) +Implements a ray-primitive intersection test, invoked during traversal. See +“Traversing the scene graph” and “Ray information”. + +### Any-hit (AH) +Called when a traced ray finds a new, potentially closest, intersection point, +such as for shadow computation. See “Ray information”. + +### Closest-hit (CH) +Called when a traced ray finds the closest intersection point, such as for +material shading. See “Constructing a path tracer”. + +### Miss +Called when a traced ray misses all scene geometry. See “Constructing a path +tracer”. + +### Ezception +Ezception handler, invoked for conditions such as stack overflow and other errors. +See “Ezceptions”. + +### Direct callables +Similar to a regular CUDA function call, direct callables are called immediately. +See “Callables”. + +### Continuation callables +Unlike direct callables, continuation callables are ezecuted by the scheduler. +See “Callables”. + +The ray-tracing “pipeline” is based on the interconnected calling structure of +the eight programs and their relationship to the search through the geometric +data in the scene, called a traversal. Figure 2.1 is a diagram of these +relationships: + +![Figure 2.1 - Optix Progams][optix_programs] + +### Shader Binding Table +The shader binding table connects geometric data to programs and their +parameters. A record is a component of the shader binding table that is selected +during ezecution by using offsets specified when acceleration structures are +created and at runtime. A record contains two data regions, header and data. +SBT record packing is handled automatically by using the +[`SbtRecord`](shader_binding_table::SbtRecord) generic struct: + +```no_run +use cust::prelude as cu; +use optiz::prelude as ox; + +#[derive(Copy, Clone, Default, cu::DeviceCopy)] +struct HitgroupSbtData { + object_id: u32, +} + +type HitgroupRecord = oz::SbtRecord; +let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); +``` + +### Ray payload +The ray payload is used to pass data between `optizTrace` and the programs +invoked during ray traversal. Payload values are passed to and returned from +`optizTrace`, and follow a copy-in/copy-out semantic. There is a limited number +of payload values, but one or more of these values can also be a pointer to +stack-based local memory, or application-managed global memory. + +### Primitive attributes +Attributes are used to pass data from intersection programs to the any-hit +and closest-hit programs. Triangle intersection provides two predefined +attributes for the barycentric coordinates (U,V). User-defined intersections +can define a limited number of other attributes that are specific to those +primitives. + +### Buffer +NVIDIA OptiX 7 represents GPU information with a pointer to GPU memory. +References to the term “buffer” in this document refer to this GPU memory pointer +and the associated memory contents. Unlike NVIDIA OptiX 6, the allocation and +transfer of buffers is ezplicitly controlled by user code. + +## Acceleration Stutures +NVIDIA OptiX 7 acceleration structures are opaque data structures built on the +device. Typically, they are based on the bounding volume hierarchy model, but +implementations and the data layout of these structures may vary from one GPU +architecture to another. + +NVIDIA OptiX 7 provides two basic types of acceleration structures: + +* Geometry acceleration structures - Built over primitives (triangles, curves, + or user-defined primitives) +* Instance acceleration structures - Built over other objects such as + acceleration structures (either type) or motion transform nodes. Allow + for instancing with a per-instance static transform + +## Traversing the Scene Graph +To determine the intersection of geometric data by a ray, NVIDIA OptiX 7 searches +a graph of nodes composed of acceleration structures and transformations. This +search is called a traversal; the nodes in the graph are called traversable +objects or traversables. + +The following types of traversable objects ezist: + +* An instance acceleration structure +* A geometry acceleration structure (as a root for graph with a single geometry + acceleration structure (see “Traversal of a single geometry acceleration + structure”) +* Static transform +* Matriz motion transform +* Scaling, rotation, translation (SRT) motion transform + +For transformation traversables, the corresponding transformation applies to +all descendant child traversables (the sub graph spanned by the child of the +transformation traversable). The transformation traversables should only be +used in case of motion as applying transformations to geometry is order dependent +and motion transformations are time dependent. Static transformations are +available as they cannot be merged with any motion transformation due to +time-dependency, but should be merged with instance transformations (if desired +as the child of an instance) or any other static transformation (i.e., there +should be at most one static transformation following a motion transformation). +For ezample, Figure 2.2 combines both types: + +![Figure 2.2 - Traversables graph][traversables_graph] + +OptiX uses handles as references to traversable objects. These traversable +handles are 64-bit opaque values that are generated from device memory pointers +for the graph nodes. The handles identify the connectivity of these objects. +All calls to `optizTrace` begin at a traversable handle. + +## Ray tracing with NVIDIA OptiX 7 + +A functional ray tracing system is implemented by combining four components as +described in the following steps: + +1. Create one or more acceleration structures over one or many geometry meshes + and instances of these meshes in the scene. See + [Acceleration structures](crate::acceleration). +2. Create a pipeline of programs that contains all programs that will be invoked + during a ray tracing launch. See “Program pipeline creation”. +3. Create a shader binding table that includes references to these programs and + their parameters and choose a data layout that matches the implicit shader + binding table record selection of the instances and geometries in the + acceleration structures. See “Shader binding table”. +4. Launch a device-side kernel that will invoke a ray generation program with a + multitude of threads calling optizTrace to begin traversal and the execution + of the other programs. See “Ray generation launches”. Device-side + functionality is described in “Device-side functions”. + +Ray tracing work can be interleaved with other CUDA work to generate data, move +data to and from the device, and move data to other graphics APIs. It is the +application's responsibility to coordinate all work on the GPU. NVIDIA OptiX 7 +does not synchronize with any other work. + +# Implementation Principles + +## Error Handling +All OptiX functions return a return code, which is converted to a Rust +`Result`. You can also set a logging callback with +[`DeviceContext::set_log_callback`](crate::context::DeviceContext::set_log_callback) +to have OptiX report additional information. + +Functions that compile also return a `String` containing additional messages +for warnings and errors. + +## Stateless Model +Given the same input, the same output should be generated. GPU state is not held by NVIDIA OptiX 7 internally. + +In NVIDIA OptiX 7 functions, a [CUDA Stream](cust::stream::Stream) is associated +with the [CUDA Context](cust::context::Context) used to create the +[`DeviceContext`](context::DeviceContext). Some API functions take a +[Stream](cust::stream::Stream) as an argument. These functions incur work on +the device and require that the [CUDA Context](cust::context::Context) associated +with the [`DeviceContext`](context::DeviceContext)is the current context when +they are called. Applications can expect the +[CUDA Context](cust::context::Context) to remain the same after invoking NVIDIA +OptiX 7 functions. + +## Asynchronous Ezecution +Work performed on the device is issued on an application-supplied +[CUDA Stream](cust::stream::Stream) using asynchronous CUDA methods. The host +function blocks ezecution until all work has been issued on the stream, but does +not do any synchronization or blocking on the stream itself. + +## Function Table initialization + +You must call [`optix::init()`](crate::init) in order to load the function symbols from +the OptiX library in the driver before calling any other functions. + + diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 1a62bf56..9f9b8ea2 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -1,19 +1,55 @@ +//! # OptiX +//! +//!
      +//! ! +//!

      +//! You must call optix::init() before calling any of the functions +//! in this crate in order to load the necessary symbols from the driver. +//!

      +//!
      +//! +//! Rust bindings for NVIDIA's OptiX GPU raytracing library. +//! +//! For introductory documentation to the high-level concepts please see the +//! [introduction](crate::introduction) module documentation. Additional +//! high-level documentation is available in the individual modules: +//! +//! * [1. Introduction](introduction) +//! * [2. Context](context) +//! * [3. Acceleration Structures](acceleration) +//! * [4. Program Pipeline Creation](pipeline) +//! * [5. Shader Binding Table](shader_binding_table) +//! * [6. Ray Generation Launches](launch) + +#[doc = ::embed_doc_image::embed_image!("optix_programs", "images/optix_programs.jpg")] +#[doc = ::embed_doc_image::embed_image!("traversables_graph", "images/traversables_graph.jpg")] +#[doc = include_str!("introduction.md")] +pub mod introduction {} + +#[doc = include_str!("acceleration.md")] pub mod acceleration; + +#[doc = include_str!("context.md")] pub mod context; -pub mod curve_array; -pub mod custom_primitive_array; + +#[doc = include_str!("denoiser.md")] pub mod denoiser; + +/// Error handling pub mod error; -pub mod instance_array; -pub mod module; + +#[doc = include_str!("pipeline.md")] pub mod pipeline; pub mod prelude; -pub mod program_group; + +#[doc = ::embed_doc_image::embed_image!("example_sbt", "images/example_sbt.png")] +#[doc = ::embed_doc_image::embed_image!("scene_graph", "images/scene_graph.png")] +#[doc = include_str!("shader_binding_table.md")] pub mod shader_binding_table; pub mod sys; -pub mod triangle_array; pub use cust; +use cust::memory::DevicePointer; use error::{Error, ToResult}; type Result = std::result::Result; @@ -64,17 +100,31 @@ macro_rules! optix_call { /// Launch the given [`Pipeline`](pipeline::Pipeline) on the given [`Stream`](cust::stream::Stream). /// +/// A ray generation launch is the primary workhorse of the NVIDIA OptiX API. A launch invokes a 1D, 2D or 3D array of threads on the device and invokes ray generation programs for each thread. When the ray generation program invokes optixTrace, other programs are invoked to execute traversal, intersection, any-hit, closest-hit, miss and exception programs until the invocations are complete. +/// +/// A pipeline requires device-side memory for each launch. This space is allocated and managed by the API. Because launch resources may be shared between pipelines, they are only guaranteed to be freed when the [`DeviceContext`] is destroyed. +/// +/// All launches are asynchronous, using [`CUDA stream`]s. When it is necessary to implement synchronization, use the mechanisms provided by CUDA streams and events. +/// +/// In addition to the pipeline object, the CUDA stream, and the launch state, it is necessary to provide information about the SBT layout using the [`ShaderBindingTable`](crate::shader_binding_table::ShaderBindingTable) struct (see [Shader Binding Table](crate::shader_binding_table)). +/// +/// The value of the pipeline launch parameter is specified by the `pipeline_launch_params_variable_name` field of the [`PipelineCompileOptions`](crate::pipeline::PipelineCompileOptions) struct. It is determined at launch with a [`DevicePointer`](cust::memory::DevicePointer) parameter, named `pipeline_params`]. This must be the same size as that passed to the module compilation or an error will occur. +/// +/// The kernel creates a copy of `pipeline_params` before the launch, so the kernel is allowed to modify `pipeline_params` values during the launch. This means that subsequent launches can run with modified pipeline parameter values. Users cannot synchronize with this copy between the invocation of `launch()` and the start of the kernel. +/// /// # Safety /// You must ensure that: -/// - Any [`ProgramGroup`](program_group::ProgramGroup)s referenced by the [`Pipeline`](pipeline::Pipeline) are still alive /// - Any device memory referenced in `buf_launch_params` point to valid, /// correctly aligned memory /// - Any [`SbtRecord`](shader_binding_table::SbtRecord)s and associated data referenced by the /// [`ShaderBindingTable`](shader_binding_table::ShaderBindingTable) are alive and valid +/// +/// [`CUDA stream`]: cust::stream::Stream +/// [`DeviceContext`]: crate::context::DeviceContext pub unsafe fn launch( pipeline: &crate::pipeline::Pipeline, stream: &cust::stream::Stream, - buf_launch_params: &mut cust::memory::DeviceBox

      , + pipeline_params: DevicePointer

      , sbt: &sys::OptixShaderBindingTable, width: u32, height: u32, @@ -83,7 +133,7 @@ pub unsafe fn launch( Ok(optix_call!(optixLaunch( pipeline.raw, stream.as_inner(), - buf_launch_params.as_device_ptr().as_raw() as u64, + pipeline_params.as_raw() as u64, std::mem::size_of::

      (), sbt, width, diff --git a/crates/optix/src/pipeline.md b/crates/optix/src/pipeline.md new file mode 100644 index 00000000..fb45c4d0 --- /dev/null +++ b/crates/optix/src/pipeline.md @@ -0,0 +1,286 @@ + +# Program Pipeline Creation + +- [Program Input](#program-input) +- [Programming Model](#programming-model) +- [Module Creation](#module-creation) +- [Pipeline Launch Parameter](#pipeline-launch-parameter) + - [Parameter Specialization](#parameter-specialization) +- [Program Group Creation](#program-group-creation) +- [Pipeline Linking](#pipeline-linking) +- [Pipeline Stack Size](#pipeline-stack-size) + - [Constructing a Path Tracer](#constructing-a-path-tracer) +- [Compilation Cache](#compilation-cache) + +Programs are first compiled into modules of type [`Module`]. One or more modules are combined to create a program group of type [`ProgramGroup`]. Those program groups are then linked into an [`Pipeline`] on the GPU. This is similar to the compile and link process commonly found in software development. The program groups are also used to initialize the header of the SBT record associated with those programs. + +The constructors for [`Module`], [`ProgramGroup`], and [`Pipeline`] return a log string. This string is used to report information about any compilation that may have occurred, such as compile errors or verbose information about the compilation result. If an error occurred, the information that would be reported in the log string is also reported by the device context log callback (when provided) (see [`DeviceContext::set_log_callback()`](crate::context::DeviceContext::set_log_callback). + +Both mechanisms are provided for these create functions to allow a convenient mechanism for pulling out compilation errors from parallel creation operations without having to determine which output from the logger corresponds to which API invocation. + +Symbols in [`Module`] objects may be unresolved and contain extern references to variables and `__device__` functions. + +These symbols can be resolved during pipeline creation using the symbols defined in the pipeline modules. Duplicate symbols will trigger an error. + +A pipeline contains all programs that are required for a particular ray-tracing launch. An application may use a different pipeline for each launch, or may combine multiple ray-generation programs into a single pipeline. + +Most NVIDIA OptiX 7 API functions do not own any significant GPU state; Streaming Assembly (SASS) instructions, which define the executable binary programs in a pipeline, are an exception. The [`Pipeline`] owns the CUDA resource associated with the compiled SASS and it is held until the pipeline is destroyed. This allocation is proportional to the amount of compiled code in the pipeline, typically tens of kilobytes to a few megabytes. However, it is possible to create complex pipelines that require substantially more memory, especially if large static initializers are used. Wherever possible, exercise caution in the number and size of the pipelines. + +## Program Input +NVIDIA OptiX 7 programs are encoded in the parallel thread execution instruction set (PTX) language. To create PTX programs, compile CUDA source files using the NVIDIA `nvcc` offline compiler or `nvrtc` JIT compiler. The CUDA code includes PTX device headers used during compilation. + +See the `build.rs` files in the examples in this crate for code to compile PTX +as part of the cargo build. + +```bash +nvcc -ptx -Ipath-to-optix-sdk/include --use_fast_math myprogram.cu -o myprogram.ptx +``` + +The nvcc command-line options are explained in more detail as part of the usage description of the compiler options displayed with nvcc --help. +Note the following requirements for nvcc and nvrtc compilation: + +* The streaming multiprocessor (SM) target of the input PTX program must be less than or equal to the SM version of the GPU for which the module is compiled. +* To generate code for the minimum supported GPU (Maxwell), use architecture targets for SM 5.0, for example, --gpu-architecture=compute_50. Because OptiX rewrites the code internally, those targets will work on any newer GPU as well. +* CUDA Toolkits 10.2 and newer throw deprecation warnings for SM 5.0 targets. These can be suppressed with the compiler option -Wno-deprecated-gpu-targets. + If support for Maxwell GPUs is not required, you can use the next higher GPU architecture target SM 6.0 (Pascal) to suppress these warnings. + +* Use --machine=64 (-m64). Only 64-bit code is supported in OptiX. +* Define the output type with --ptx. Do not compile to obj or cubin. +* Do not use debug flags -g and -G. OptiX might not handle all debugging instrumentation. This is important when using the Microsoft Visual Studio CUDA integration, which sets these flags as default in the Debug target. +* Enable --relocatable-device-code=true (-rdc). Command nvcc can also use the option --keep-device-functions, which is not supported by nvrtc. These flags prevent the CUDA compiler from eliminating direct or continuation callables as dead code. +* To get smaller and faster code, enable --use_fast_math. This flag enables .approx instructions for trigonometric functions and reciprocals, avoiding inadvertent use of slow double-precision floats. For performance reasons, it is recommended that you set this flag; the only exception is use cases that require more precision. +* To profile your code with Nsight Compute, enable --generate-line-info and set `debug_level = CompileDebugLevel::LineInfo` in both the [`ModuleCompileOptions`] and [`PipelineLinkOptions`] in your application host code. + +## Programming Model +The NVIDIA OptiX 7 programming model supports the multiple instruction, multiple data (MIMD) subset of CUDA. Execution must be independent of other threads. For this reason, shared memory usage and warp-wide or block-wide synchronization—such as barriers—are not allowed in the input PTX code. All other GPU instructions are allowed, including math, texture, atomic operations, control flow, and loading data to memory. Special warp-wide instructions like vote and ballot are allowed, but can yield unexpected results as the locality of threads is not guaranteed and neighboring threads can change during execution, unlike in the full CUDA programming model. Still, warp-wide instructions can be used safely when the algorithm in question is independent of locality by, for example, implementing warp-aggregated atomic adds. + +The memory model is consistent only within the execution of a single launch index, which starts at the ray-generation invocation and only with subsequent programs reached from any `optixTrace` or callable program. This includes writes to stack allocated variables. Writes from other launch indices may not be available until after the launch is complete. If needed, atomic operations may be used to share data between launch indices, as long as an ordering between launch indices is not required. Memory fences are not supported. + +The input PTX should include one or more NVIDIA OptiX 7 programs. The type of program affects how the program can be used during the execution of the pipeline. These program types are specified by prefixing the program's name with the following: + + + + + + + + + + + +
      Program TypeFunction Name Prefix
      Ray Generation__raygen__
      Intersection__intersection__
      Any-Hit__anyhit__
      Closest-Hit__closesthit__
      Miss__miss__
      Direct Callable__direct_callable__
      Continuation Callable__continuation_callable__
      Exception__exception__
      + + If a particular function needs to be used with more than one type, then multiple copies with corresponding program prefixes should be generated. + +In addition, each program may call a specific set of device-side intrinsics that implement the actual ray-tracing-specific features. (See “Device-side functions”.) + +## Module Creation + +A module may include multiple programs of any program type. Two option structs control the parameters of the compilation process: + +* [`PipelineCompileOptions`] - Must be identical for all modules used to create program groups linked in a single pipeline. +* [`ModuleCompileOptions`] - May vary across the modules within the same pipeline. + +These options control general compilation settings, for example, the level of optimization. OptixPipelineCompileOptions controls features of the API such as the usage of custom any-hit programs, curve primitives, motion blur, exceptions, and the number of 32-bit values usable in ray payload and primitive attributes. For example: + +``` +let module_compile_options = ModuleCompileOptions { + opt_level: CompileOptimizationLevel::Default, + debug_level: CompileDebugLevel::LineInfo, + ..Default::default() +}; + +let pipeline_compile_options = PipelineCompileOptions::new() + .uses_motion_blur(false) + .num_attribute_values(2) + .num_payload_values(2) + .pipeline_launch_params_variable_name("PARAMS") + .exception_flags(ExceptionFlags::NONE) +} +.build(); + +let (module, log) = Module::new(&ctx, + &module_compile_options, + &pipeline_compile_options, + &ptx_string + )?; +``` +The `num_attribute_values` field of [`PipelineCompileOptions`] defines the number of 32-bit words that are reserved to store the attributes. This corresponds to the attribute definition in `optixReportIntersection`. See “Reporting intersections and attribute access”. + +

      +! +

      +For best performance when your scene contains nothing but triangles, set uses_primitive_type_flags to PrimitiveTypeFlags::TRIANGLE. +

      +
      + +## Pipeline Launch Parameter + +You specify launch-varying parameters or values that must be accessible from any module through a user-defined variable named in [`PipelineCompileOptions`]. In each module that needs access, declare this variable with `extern` or `extern "C"` linkage and the `__constant__` memory specifier. The size of the variable must match across all modules in a pipeline. Variables of equal size but differing types may trigger undefined behavior. + +For example, the following header file defines the variable to share, named PARAMS, as an instance of the Params struct: +```text +struct Params { + float* image; + unsigned int image_width; +}; + +extern "C" __constant__ Params PARAMS; +``` + +You must match the layout of this struct with an equivalent Rust struct. Take care that CUDA vector types have specific alignment requirements which you must match in the Rust struct or you will trigger invalid memory accesses or undefined behaviour. + +``` +struct Params { + image: f32, + image_width: u32, +} +``` + +You may also wish to use bindgen to automatically create the equivalent Rust struct from a C/C++ header to ensure they stay in sync. + +### Parameter Specialization + +Not current implemented + +## Program Group Creation +[`ProgramGroup`] objects are created from one to three [`Module`] objects and are used to fill the header of the SBT records. (See [Shader Binding Table](crate::shader_binding_table)) There are five types of program groups: Raygen, Miss, Exception, Hitgroup and Callable. + +Modules can contain more than one program. The program in the module is designated by its entry function name as part of the [`ProgramGroupDesc`] struct passed to [`ProgramGroup::new()`](crate::pipeline::ProgramGroup::new). Four program groups can contain only a single program; only the hitgroup program can designate up to three programs for the closest-hit, any-hit, and intersection programs. + +Programs from modules can be used in any number of [`ProgramGroup`] objects. The resulting program groups can be used to fill in any number of SBT records. Program groups can also be used across pipelines as long as the compilation options match. + +A hit group specifies the intersection program used to test whether a ray intersects a primitive, together with the hit shaders to be executed when a ray does intersect the primitive. For built-in primitive types, a built-in intersection program should be obtained from [`Module::builtin_is_module_get()`](crate::pipeline::Module::builtin_is_module_get) and used in the hit group. As a special case, the intersection program is not required – and is ignored – for triangle primitives. + +``` +let (module, _log) = Module::new( + &mut ctx, + &module_compile_options, + &pipeline_compile_options, + ptx, +)?; + +let pgdesc_hitgroup = ProgramGroupDesc::hitgroup( + Some((&module, "__closesthit__radiance")), + Some((&module, "__anyhit__radiance")), + None, +); + +let (pg_hitgroup, _log) = ProgramGroup::new(&mut ctx, &[pgdesc_hitgroup])?; +``` + +## Pipeline Linking + +After all program groups of a pipeline are defined, they must be linked into an [`Pipeline`]. The resulting [`Pipeline`] object is then used to invoke a ray-generation launch. + +When the [`Pipeline`] is linked, some fixed function components may be selected based on [`PipelineLinkOptions`] and [`PipelineCompileOptions`]. These options were previously used to compile the modules in the pipeline. The link options consist of the maximum recursion depth setting for recursive ray tracing, along with pipeline level settings for debugging. However, the value for the maximum recursion depth has an upper limit that overrides an limit set by the link options. (See “Limits”.) + +For example, the following code creates and links a [`Pipeline`]: +``` +let program_groups = [pg_raygen, pg_miss, pg_hitgroup]; + +let pipeline_link_options = PipelineLinkOptions { + max_trace_depth: 2, + debug_level: CompileDebugLevel::LineInfo, +}; + +let (pipeline, _log) = Pipeline::new( + &mut ctx, + &pipeline_compile_options, + pipeline_link_options, + &program_groups, +)?; +``` + +After [`Pipeline::new()`](crate::pipeline::Pipeline::new) completes, the fully linked module is loaded into the driver. + +NVIDIA OptiX 7 uses a small amount of GPU memory per pipeline. This memory is released when the pipeline or device context is destroyed. + +## Pipeline Stack Size + +The programs in a module may consume two types of stack structure : a direct stack and a continuation stack. The resulting stack needed for launching a pipeline depends on the resulting call graph, so the pipeline must be configured with the appropriate stack size. These sizes can be determined by the compiler for each program group. A pipeline may be reused for different call graphs as long as the set of programs is the same. For this reason, the pipeline stack size is configured separately from the pipeline compilation options. + +The direct stack requirements resulting from ray-generation, miss, exception, closest-hit, any-hit and intersection programs and the continuation stack requirements resulting from exception programs are calculated internally and do not need to be configured. The direct stack requirements resulting from direct-callable programs, as well as the continuation stack requirements resulting from ray-generation, miss, closest-hit, any-hit, intersection, and continuation-callable programs need to be configured. If these are not configured explicitly, an internal default implementation is used. When the maximum depth of call trees of continuation-callable and direct-callable programs is two or less, the default implementation is correct (but not necessarily optimal) Even in cases where the default implementation is correct, Users can always provide more precise stack requirements based on their knowledge of a particular call graph structure. + +To query individual program groups for their stack requirements, use [`ProgramGroup::get_stack_size`](crate::pipeline::ProgramGroup::get_stack_size). Use this information to calculate the total required stack sizes for a particular call graph of NVIDIA OptiX 7 programs. To set the stack sizes for a particular pipeline, use [`Pipeline::set_stack_size`](crate::pipeline::set_stack_size). For other parameters, helper functions are available to implement these calculations. The following is an explanation about how to compute the stack size for [`Pipeline::set_stack_size()`](crate::pipeline::Pipeline::set_stack_size), starting from a very conservative approach, and refining the estimates step by step. + +Let `css_rg` denote the maximum continuation stack size of all ray-generation programs; similarly for miss, closest-hit, any-hit, intersection, and continuation-callable programs. Let `dss_dc` denote the maximum direct stack size of all direct callable programs. Let `max_trace_depth` denote the maximum trace depth (as in [`PipelineLinkOptions::max_trace_depth`](crate::pipeline::PipelineLinkOptions)), and let `max_cc_depth` and `max_dc_depth` denote the maximum depth of call trees of continuation-callable and direct-callable programs, respectively. Then a simple, conservative approach to compute the three parameters of [`Pipeline::set_stack_size`](crate::pipeline::Pipeline::set_stack_size) is: + +``` +let direct_callable_stack_size_from_traversable = max_dc_depth * dss_dc; +let direct_callable_stack_size_from_state = max_dc_depth * dss_dc; + +// Upper bound on continuation stack used by call trees of continuation callables +let css_cc_tree = max_cc_depth * css_cc; + +// Upper bound on continuation stack used by closest-hit or miss programs, including +// the call tree of continuation-callable programs +let css_ch_or_ms_plus_cc_tree = css_ch.max(css_ms) + css_cc_tree; + +let continuation_stack_size = + css_rg + + css_cc_tree + + max_trace_depth * css_ch_or_ms_plus_cc_tree + + css_is + + css_ah; +``` + +This computation can be improved in several ways. For the computation of `continuation_stack_size`, the stack sizes `css_is` and `css_ah` are not used on top of the other summands, but can be offset against one level of `css_ch_or_ms_plus_cc_tree`. This gives a more complex but better estimate: + +``` +let continuation_stack_size = + css_rg + + css_cc_tree + + (max_trace_depth - 1).max(1) * css_ch_or_ms_plus_cc_tree + + max_trace_depth.min(1) * css_ch_or_ms_plus_cc_tree.max(css_is + css_ah); +``` + +The computation of the first two terms can be improved if the call trees of direct callable programs are analyzed separately based on the semantic type of their call site. In this context, call sites in any-hit and intersection programs count as traversal, whereas call sites in ray-generation, miss, and closest-hit programs count as state. + +``` +let direct_callable_stack_size_from_traversable = + max_dc_depth_from_traversal * dss_dc_from_traversal; +let direct_callable_stack_size_from_state + = max_dc_depth_from_state * dss_dc_from_state; +``` + +Depending on the scenario, these estimates can be improved further, sometimes substantially. For example, imagine there are two call trees of continuation-callable programs. One call tree is deep, but the involved continuation-callable programs need only a small continuation stack. The other call tree is shallow, but the involved continuation-callable programs needs a quite large continuation stack. The estimate of `css_cc_tree` can be improved as follows: + +``` +let css_cc_tree = max_cc_depth1 * css_cc1.max(max_cc_depth2 * css_cc2); +``` +Similar improvements might be possible for all expressions involving `max_trace_depth` if the ray types are considered separately, for example, camera rays and shadow rays. + +### Constructing a Path Tracer + +A simple path tracer can be constructed from two ray types: camera rays and shadow rays. The path tracer will consist only of ray-generation, miss, and closest-hit programs, and will not use any-hit, intersection, continuation-callable, or direct-callable programs. The camera rays will invoke only the miss and closest-hit programs `ms1` and `ch1`, respectively. `ch1` might trace shadow rays, which invoke only the miss and closest-hit programs `ms2` and `ch2`, respectively. That is, the maximum trace depth is two and the initial formulas simplify to: + +``` +let direct_callable_stack_size_from_traversable = max_dc_depth * dss_dc; +let direct_callable_stack_size_from_state = max_dc_depth * dss_dc; +let continuation_stack_size = css_rg + 2 * css_ch1.max(css_ch2).max(css_ms1).max(css_ms2); +``` + +However, from the call graph structure it is clear that ms2 or ch2 can only be invoked from ch1. This restriction allows for the following estimate: + +``` +let continuation_stack_size = css_rg + css_ms1.max(css_ch1 + css_ms2.max(css_ch2)); +``` + +This estimate is never worse than the previous one, but often better, for example, in the case where the closest-hit programs have different stack sizes (and the miss programs do not dominate the expression). + +## Compilation Cache + +Compilation work is triggered automatically when calling [`Module::new()`](crate::pipeline::Module::new) or [`ProgramGroup::new()`](crate::pipeline::ProgramGroup::new), and also potentially during [`Pipeline::new()`](crate::pipeline::Pipeline::new). This work is automatically cached on disk if enabled on the [`DeviceContext`]. Caching reduces compilation effort for recurring programs and program groups. While it is enabled by default, users can disable it through the use of [`DeviceContext::set_cache_enabled()`](crate::context::DeviceContext::set_cache_enabled). See [Context](crate::context) for other options regarding the compilation cache. + +Generally, cache entries are compatible with the same driver version and GPU type only. + + [`DeviceContext`]: crate::context::DeviceContext; + [`Module`]: crate::pipeline::Module + [`ProgramGroup`]: crate::pipeline::ProgramGroup + [`ProgramGroupDesc`]: crate::pipeline::ProgramGroupDesc + [`Pipeline`]: crate::pipeline::Pipeline + [`ModuleCompileOptions`]: crate::pipeline::ModuleCompileOptions + [`PipelineCompileOptions`]: crate::pipeline::PipelineCompileOptions + [`PipelineLinkOptions`]: crate::pipeline::PipelineLinkOptions + diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 848fca9b..14d4f243 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -1,14 +1,7 @@ -use crate::{ - context::DeviceContext, - error::Error, - module::{CompileDebugLevel, PipelineCompileOptions}, - optix_call, - program_group::ProgramGroup, - sys, -}; +use crate::{context::DeviceContext, error::Error, optix_call, sys}; type Result = std::result::Result; -use std::ffi::CStr; +use std::ffi::{CStr, CString}; #[repr(transparent)] pub struct Pipeline { @@ -16,7 +9,7 @@ pub struct Pipeline { } #[repr(C)] -#[derive(Debug, Hash, PartialEq, Copy, Clone)] +#[derive(Debug, Hash, PartialEq, Copy, Clone, Default)] pub struct PipelineLinkOptions { pub max_trace_depth: u32, pub debug_level: CompileDebugLevel, @@ -129,3 +122,692 @@ impl Pipeline { } } } + +#[repr(transparent)] +pub struct Module { + pub(crate) raw: sys::OptixModule, +} + +/// Module compilation optimization level +#[repr(u32)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub enum CompileOptimizationLevel { + Default = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_DEFAULT, + Level0 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_0, + Level1 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_1, + Level2 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_2, + Level3 = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_LEVEL_3, +} + +impl Default for CompileOptimizationLevel { + fn default() -> Self { + CompileOptimizationLevel::Default + } +} + +/// Module compilation debug level +#[repr(u32)] +#[derive(Debug, Hash, PartialEq, Copy, Clone)] +pub enum CompileDebugLevel { + None = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_NONE, + LineInfo = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO, + Full = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_FULL, +} + +impl Default for CompileDebugLevel { + fn default() -> Self { + CompileDebugLevel::None + } +} + +cfg_if::cfg_if! { + if #[cfg(any(feature="optix72", feature="optix73"))] { + #[repr(C)] + #[derive(Debug, Hash, PartialEq, Copy, Clone)] + pub struct ModuleCompileOptions { + pub max_register_count: i32, + pub opt_level: CompileOptimizationLevel, + pub debug_level: CompileDebugLevel, + } + + impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { + fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { + sys::OptixModuleCompileOptions { + maxRegisterCount: o.max_register_count, + optLevel: o.opt_level as u32, + debugLevel: o.debug_level as u32, + boundValues: std::ptr::null(), + numBoundValues: 0, + } + } + } + } else { + #[repr(C)] + #[derive(Debug, Hash, PartialEq, Copy, Clone)] + pub struct ModuleCompileOptions { + pub max_register_count: i32, + pub opt_level: CompileOptimizationLevel, + pub debug_level: CompileDebugLevel, + } + + impl From<&ModuleCompileOptions> for sys::OptixModuleCompileOptions { + fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { + sys::OptixModuleCompileOptions { + maxRegisterCount: o.max_register_count, + optLevel: o.opt_level as u32, + debugLevel: o.debug_level as u32, + } + } + } + } +} + +bitflags::bitflags! { + #[derive(Default)] + pub struct TraversableGraphFlags: u32 { + const ALLOW_ANY = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY; + const ALLOW_SINGLE_GAS = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS; + const ALLOW_SINGLE_LEVEL_INSTANCING = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; + } +} + +bitflags::bitflags! { + #[derive(Default)] + pub struct ExceptionFlags: u32 { + const NONE = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_NONE; + const STACK_OVERFLOW = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW; + const TRACE_DEPTH = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_TRACE_DEPTH; + const USER = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_USER; + const DEBUG = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_DEBUG; + } +} + +bitflags::bitflags! { + #[derive(Default)] + pub struct PrimitiveTypeFlags: i32 { + const DEFAULT = 0; + const CUSTOM = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM; + const ROUND_QUADRATIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE; + const ROUND_CUBIC_BSPLINE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE; + const ROUND_LINEAR = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR; + const TRIANGLE = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE; + } +} + +#[repr(u32)] +pub enum PrimitiveType { + RoundQuadraticBspline = + sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE as u32, + RoundCubicBspline = + sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_CUBIC_BSPLINE as u32, + RoundLinear = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_LINEAR as u32, + Triangle = sys::OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_TRIANGLE as u32, +} + +#[derive(Debug, Hash, PartialEq, Clone, Default)] +pub struct PipelineCompileOptions { + uses_motion_blur: bool, + traversable_graph_flags: TraversableGraphFlags, + num_payload_values: i32, + num_attribute_values: i32, + exception_flags: ExceptionFlags, + pipeline_launch_params_variable_name: Option, + primitive_type_flags: PrimitiveTypeFlags, +} + +impl PipelineCompileOptions { + pub fn new() -> PipelineCompileOptions { + PipelineCompileOptions { + uses_motion_blur: false, + traversable_graph_flags: TraversableGraphFlags::ALLOW_ANY, + num_payload_values: 0, + num_attribute_values: 0, + exception_flags: ExceptionFlags::NONE, + pipeline_launch_params_variable_name: None, + primitive_type_flags: PrimitiveTypeFlags::DEFAULT, + } + } + + pub fn build(&self) -> sys::OptixPipelineCompileOptions { + cfg_if::cfg_if! { + if #[cfg(feature="optix73")] { + sys::OptixPipelineCompileOptions { + usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, + traversableGraphFlags: self.traversable_graph_flags.bits(), + numPayloadValues: self.num_payload_values, + numAttributeValues: self.num_attribute_values, + exceptionFlags: self.exception_flags.bits(), + pipelineLaunchParamsVariableName: if let Some(ref name) = self + .pipeline_launch_params_variable_name { + name.as_ptr() + } else { + std::ptr::null() + }, + usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, + reserved: 0, + reserved2: 0, + } + } else { + sys::OptixPipelineCompileOptions { + usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, + traversableGraphFlags: self.traversable_graph_flags.bits(), + numPayloadValues: self.num_payload_values, + numAttributeValues: self.num_attribute_values, + exceptionFlags: self.exception_flags.bits(), + pipelineLaunchParamsVariableName: if let Some(ref name) = self + .pipeline_launch_params_variable_name { + name.as_ptr() + } else { + std::ptr::null() + }, + usesPrimitiveTypeFlags: self.primitive_type_flags.bits() as u32, + } + } + } + } + + pub fn uses_motion_blur(mut self, umb: bool) -> Self { + self.uses_motion_blur = umb; + self + } + + pub fn traversable_graph_flags(mut self, tgf: TraversableGraphFlags) -> Self { + self.traversable_graph_flags = tgf; + self + } + + pub fn num_payload_values(mut self, npv: i32) -> Self { + self.num_payload_values = npv; + self + } + + pub fn num_attribute_values(mut self, nav: i32) -> Self { + self.num_attribute_values = nav; + self + } + + pub fn exception_flags(mut self, ef: ExceptionFlags) -> Self { + self.exception_flags = ef; + self + } + + pub fn pipeline_launch_params_variable_name(mut self, name: &str) -> Self { + self.pipeline_launch_params_variable_name = Some( + CString::new(name).expect("pipeline launch params variable name contains nul bytes"), + ); + self + } +} + +/// # Creating and destroying `Module`s +impl Module { + pub fn new( + ctx: &mut DeviceContext, + module_compile_options: &ModuleCompileOptions, + pipeline_compile_options: &PipelineCompileOptions, + ptx: &str, + ) -> Result<(Module, String)> { + let cptx = CString::new(ptx).unwrap(); + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let mopt = module_compile_options.into(); + let popt = pipeline_compile_options.build(); + + let mut raw = std::ptr::null_mut(); + let res = unsafe { + optix_call!(optixModuleCreateFromPTX( + ctx.raw, + &mopt as *const _, + &popt, + cptx.as_ptr(), + cptx.as_bytes().len(), + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut raw, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((Module { raw }, log)), + Err(source) => Err(Error::ModuleCreation { source, log }), + } + } + + /// Returns a module containing the intersection program for the built-in + /// primitive type specified by the builtinISOptions. This module must be used + /// as the moduleIS for the OptixProgramGroupHitgroup in any SBT record for + /// that primitive type. + pub fn builtin_is_module_get( + ctx: &mut DeviceContext, + module_compile_options: &ModuleCompileOptions, + pipeline_compile_options: &PipelineCompileOptions, + builtin_is_module_type: PrimitiveType, + uses_motion_blur: bool, + ) -> Result { + let is_options = sys::OptixBuiltinISOptions { + builtinISModuleType: builtin_is_module_type as u32, + usesMotionBlur: if uses_motion_blur { 1 } else { 0 }, + }; + + let mut raw = std::ptr::null_mut(); + + unsafe { + optix_call!(optixBuiltinISModuleGet( + ctx.raw, + module_compile_options as *const _ as *const _, + pipeline_compile_options as *const _ as *const _, + &is_options as *const _, + &mut raw, + )) + .map(|_| Module { raw }) + .map_err(|e| Error::from(e)) + } + } +} + +impl Drop for Module { + fn drop(&mut self) { + unsafe { + sys::optixModuleDestroy(self.raw); + } + } +} + +#[derive(Clone)] +pub struct ProgramGroupModule<'m> { + pub module: &'m Module, + pub entry_function_name: CString, +} + +pub enum ProgramGroupDesc<'m> { + Raygen(ProgramGroupModule<'m>), + Miss(ProgramGroupModule<'m>), + Exception(ProgramGroupModule<'m>), + Hitgroup { + ch: Option>, + ah: Option>, + is: Option>, + }, + Callables { + dc: Option>, + cc: Option>, + }, +} + +impl<'m> ProgramGroupDesc<'m> { + pub fn raygen(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), + }) + } + + pub fn miss(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), + }) + } + + pub fn exception(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), + }) + } + + pub fn hitgroup( + ch: Option<(&'m Module, &str)>, + ah: Option<(&'m Module, &str)>, + is: Option<(&'m Module, &str)>, + ) -> ProgramGroupDesc<'m> { + ProgramGroupDesc::Hitgroup { + ch: ch.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), + }), + ah: ah.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), + }), + is: is.map(|(module, entry_function_name)| ProgramGroupModule { + module, + entry_function_name: CString::new(entry_function_name).expect("Invalid string"), + }), + } + } +} + +/// A group of programs to be associated with a SBT record. +/// +/// Modules can contain more than one program. The program in the module is +/// designated by its entry function name as part of the [ProgramGroupDesc] +/// struct passed to [`ProgramGroup::new()`] and +/// [`ProgramGroup::new_single()`], or specified directly in the +/// case of [`ProgramGroup::raygen()`], +/// [`ProgramGroup::miss()`] and +/// [ProgramGroup::hitgroup()`] +/// +/// Four program groups can contain only a single program; only hitgroups can +/// designate up to three programs for the closest-hit, any-hit, and +/// intersection programs. +/// +/// Programs from modules can be used in any number of [ProgramGroup] objects. +/// The resulting program groups can be used to fill in any number of +/// SBT records. Program groups can also be used across pipelines as long as the +/// compilation options match. +/// +/// A hit group specifies the intersection program used to test whether a ray +/// intersects a primitive, together with the hit shaders to be executed when a +/// ray does intersect the primitive. For built-in primitive types, a built-in +/// intersection program should be obtained from +/// [DeviceContext::builtin_is_module_get()] and used in the hit group. As a +/// special case, the intersection program is not required – and is ignored – +/// for triangle primitives. +/// +/// # Safety +/// The lifetime of a module must extend to the lifetime of any +/// ProgramGroup that references that module. +/// FIXME (AL): make this sound by storing module lifetimes here +#[repr(transparent)] +pub struct ProgramGroup { + pub(crate) raw: sys::OptixProgramGroup, +} + +impl ProgramGroup { + /// Use this information to calculate the total required stack sizes for a + /// particular call graph of NVIDIA OptiX programs. + /// + /// To set the stack sizes for a particular pipeline, use + /// [Pipeline::set_stack_size()](crate::Pipeline::set_stack_size()). + pub fn get_stack_size(&self) -> Result { + let mut stack_sizes = StackSizes::default(); + unsafe { + Ok(optix_call!(optixProgramGroupGetStackSize( + self.raw, + &mut stack_sizes as *mut _ as *mut _ + )) + .map(|_| stack_sizes)?) + } + } +} + +impl PartialEq for ProgramGroup { + fn eq(&self, rhs: &ProgramGroup) -> bool { + self.raw == rhs.raw + } +} + +/// # Creating and destroying `ProgramGroup`s +impl ProgramGroup { + /// Create a [ProgramGroup] for each of the [ProgramGroupDesc] objects in + /// `desc`. + pub fn new( + ctx: &mut DeviceContext, + desc: &[ProgramGroupDesc], + ) -> Result<(Vec, String)> { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix73"))] { + let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; + } else { + let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; + } + } + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let pg_desc: Vec = desc.iter().map(|d| d.into()).collect(); + + let mut raws = vec![std::ptr::null_mut(); pg_desc.len()]; + + let res = unsafe { + optix_call!(optixProgramGroupCreate( + ctx.raw, + pg_desc.as_ptr(), + pg_desc.len() as u32, + &pg_options, + log.as_mut_ptr() as *mut i8, + &mut log_len, + raws.as_mut_ptr(), + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok(( + raws.iter().map(|raw| ProgramGroup { raw: *raw }).collect(), + log, + )), + Err(source) => Err(Error::ProgramGroupCreation { source, log }), + } + } + + /// Create a single [ProgramGroup] specified by `desc`. + pub fn new_single( + ctx: &mut DeviceContext, + desc: &ProgramGroupDesc, + ) -> Result<(ProgramGroup, String)> { + cfg_if::cfg_if! { + if #[cfg(any(feature="optix73"))] { + let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; + } else { + let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; + } + } + + let mut log = [0u8; 4096]; + let mut log_len = log.len(); + + let pg_desc: sys::OptixProgramGroupDesc = desc.into(); + + let mut raw = std::ptr::null_mut(); + + let res = unsafe { + optix_call!(optixProgramGroupCreate( + ctx.raw, + &pg_desc, + 1, + &pg_options, + log.as_mut_ptr() as *mut i8, + &mut log_len, + &mut raw, + )) + }; + + let log = CStr::from_bytes_with_nul(&log[0..log_len]) + .unwrap() + .to_string_lossy() + .into_owned(); + + match res { + Ok(()) => Ok((ProgramGroup { raw }, log)), + Err(source) => Err(Error::ProgramGroupCreation { source, log }), + } + } + + /// Create a raygen [ProgramGroup] from `entry_function_name` in `module`. + pub fn raygen( + ctx: &mut DeviceContext, + module: &Module, + entry_function_name: &str, + ) -> Result { + let desc = ProgramGroupDesc::raygen(module, entry_function_name); + Ok(ProgramGroup::new_single(ctx, &desc)?.0) + } + + /// Create a miss [ProgramGroup] from `entry_function_name` in `module`. + pub fn miss( + ctx: &mut DeviceContext, + module: &Module, + entry_function_name: &str, + ) -> Result { + let desc = ProgramGroupDesc::miss(module, entry_function_name); + Ok(ProgramGroup::new_single(ctx, &desc)?.0) + } + + /// Create an exception [ProgramGroup] from `entry_function_name` in `module`. + pub fn exception( + ctx: &mut DeviceContext, + module: &Module, + entry_function_name: &str, + ) -> Result { + let desc = ProgramGroupDesc::exception(module, entry_function_name); + Ok(ProgramGroup::new_single(ctx, &desc)?.0) + } + + /// Create a hitgroup [ProgramGroup] from any combination of + /// `(module, entry_function_name)` pairs. + pub fn hitgroup( + ctx: &mut DeviceContext, + closest_hit: Option<(&Module, &str)>, + any_hit: Option<(&Module, &str)>, + intersection: Option<(&Module, &str)>, + ) -> Result { + let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); + Ok(ProgramGroup::new_single(ctx, &desc)?.0) + } +} + +impl Drop for ProgramGroup { + fn drop(&mut self) { + unsafe { + sys::optixProgramGroupDestroy(self.raw); + } + } +} + +impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { + fn from(desc: &ProgramGroupDesc<'m>) -> sys::OptixProgramGroupDesc { + match &desc { + ProgramGroupDesc::Raygen(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + raygen: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Miss(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Exception(ProgramGroupModule { + module, + entry_function_name, + }) => sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + miss: sys::OptixProgramGroupSingleModule { + module: module.raw, + entryFunctionName: entry_function_name.as_ptr(), + }, + }, + flags: 0, + }, + ProgramGroupDesc::Hitgroup { ch, ah, is } => { + let mut efn_ch_ptr = std::ptr::null(); + let mut efn_ah_ptr = std::ptr::null(); + let mut efn_is_ptr = std::ptr::null(); + + let module_ch = if let Some(pg_ch) = &ch { + efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); + pg_ch.module.raw + } else { + std::ptr::null_mut() + }; + + let module_ah = if let Some(pg_ah) = &ah { + efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); + pg_ah.module.raw + } else { + std::ptr::null_mut() + }; + + let module_is = if let Some(pg_is) = &is { + efn_is_ptr = pg_is.entry_function_name.as_ptr(); + pg_is.module.raw + } else { + std::ptr::null_mut() + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + hitgroup: sys::OptixProgramGroupHitgroup { + moduleCH: module_ch, + entryFunctionNameCH: efn_ch_ptr, + moduleAH: module_ah, + entryFunctionNameAH: efn_ah_ptr, + moduleIS: module_is, + entryFunctionNameIS: efn_is_ptr, + }, + }, + flags: 0, + } + } + ProgramGroupDesc::Callables { dc, cc } => { + let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { + (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { + (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) + } else { + (std::ptr::null_mut(), std::ptr::null()) + }; + + sys::OptixProgramGroupDesc { + kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, + __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { + callables: sys::OptixProgramGroupCallables { + moduleDC: module_dc, + entryFunctionNameDC: efn_dc, + moduleCC: module_cc, + entryFunctionNameCC: efn_cc, + }, + }, + flags: 0, + } + } + } + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct StackSizes { + pub css_rg: u32, + pub css_mg: u32, + pub css_ch: u32, + pub css_ah: u32, + pub css_is: u32, + pub css_cc: u32, + pub css_dc: u32, +} diff --git a/crates/optix/src/prelude.rs b/crates/optix/src/prelude.rs index d8cdf6f8..3d702f4d 100644 --- a/crates/optix/src/prelude.rs +++ b/crates/optix/src/prelude.rs @@ -1,19 +1,18 @@ pub use crate::{ + acceleration::CustomPrimitiveArray, acceleration::{ Aabb, Accel, AccelBufferSizes, AccelBuildOptions, AccelEmitDesc, AccelRelocationInfo, BuildFlags, BuildOperation, DynamicAccel, GeometryFlags, }, + acceleration::{CurveArray, CurveType}, + acceleration::{IndexTriple, IndexedTriangleArray, TriangleArray, Vertex}, + acceleration::{Instance, InstanceArray, InstanceFlags, InstancePointerArray}, context::{DeviceContext, DeviceProperty}, - curve_array::{CurveArray, CurveType}, - custom_primitive_array::CustomPrimitiveArray, - init, - instance_array::{Instance, InstanceArray, InstanceFlags, InstancePointerArray}, - launch, - module::{ + init, launch, + pipeline::{ Module, ModuleCompileOptions, PipelineCompileOptions, PrimitiveType, PrimitiveTypeFlags, }, pipeline::{Pipeline, PipelineLinkOptions}, - program_group::{ProgramGroup, ProgramGroupDesc, ProgramGroupModule, StackSizes}, + pipeline::{ProgramGroup, ProgramGroupDesc, ProgramGroupModule, StackSizes}, shader_binding_table::{SbtRecord, ShaderBindingTable}, - triangle_array::{IndexTriple, IndexedTriangleArray, TriangleArray, Vertex}, }; diff --git a/crates/optix/src/program_group.rs b/crates/optix/src/program_group.rs deleted file mode 100644 index a29600f6..00000000 --- a/crates/optix/src/program_group.rs +++ /dev/null @@ -1,394 +0,0 @@ -use crate::{context::DeviceContext, error::Error, module::Module, optix_call, sys}; -type Result = std::result::Result; - -use std::ffi::{CStr, CString}; - -#[derive(Clone)] -pub struct ProgramGroupModule<'m> { - pub module: &'m Module, - pub entry_function_name: CString, -} - -pub enum ProgramGroupDesc<'m> { - Raygen(ProgramGroupModule<'m>), - Miss(ProgramGroupModule<'m>), - Exception(ProgramGroupModule<'m>), - Hitgroup { - ch: Option>, - ah: Option>, - is: Option>, - }, - Callables { - dc: Option>, - cc: Option>, - }, -} - -impl<'m> ProgramGroupDesc<'m> { - pub fn raygen(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { - ProgramGroupDesc::Raygen(ProgramGroupModule { - module, - entry_function_name: CString::new(entry_function_name).expect("Invalid string"), - }) - } - - pub fn miss(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { - ProgramGroupDesc::Miss(ProgramGroupModule { - module, - entry_function_name: CString::new(entry_function_name).expect("Invalid string"), - }) - } - - pub fn exception(module: &'m Module, entry_function_name: &str) -> ProgramGroupDesc<'m> { - ProgramGroupDesc::Exception(ProgramGroupModule { - module, - entry_function_name: CString::new(entry_function_name).expect("Invalid string"), - }) - } - - pub fn hitgroup( - ch: Option<(&'m Module, &str)>, - ah: Option<(&'m Module, &str)>, - is: Option<(&'m Module, &str)>, - ) -> ProgramGroupDesc<'m> { - ProgramGroupDesc::Hitgroup { - ch: ch.map(|(module, entry_function_name)| ProgramGroupModule { - module, - entry_function_name: CString::new(entry_function_name).expect("Invalid string"), - }), - ah: ah.map(|(module, entry_function_name)| ProgramGroupModule { - module, - entry_function_name: CString::new(entry_function_name).expect("Invalid string"), - }), - is: is.map(|(module, entry_function_name)| ProgramGroupModule { - module, - entry_function_name: CString::new(entry_function_name).expect("Invalid string"), - }), - } - } -} - -/// Modules can contain more than one program. The program in the module is -/// designated by its entry function name as part of the [ProgramGroupDesc] -/// struct passed to [DeviceContext::program_group_create()] and -/// [DeviceContext::program_group_create_single()], or specified directly in the -/// case of [DeviceContext::program_group_raygen()], -/// [DeviceContext::program_group_miss()] and -/// [DeviceContext::program_group_hitgroup()] -/// -/// Four program groups can contain only a single program; only hitgroups can -/// designate up to three programs for the closest-hit, any-hit, and -/// intersection programs. -/// -/// Programs from modules can be used in any number of [ProgramGroup] objects. -/// The resulting program groups can be used to fill in any number of -/// SBT records. Program groups can also be used across pipelines as long as the -/// compilation options match. -/// -/// A hit group specifies the intersection program used to test whether a ray -/// intersects a primitive, together with the hit shaders to be executed when a -/// ray does intersect the primitive. For built-in primitive types, a built-in -/// intersection program should be obtained from -/// [DeviceContext::builtin_is_module_get()] and used in the hit group. As a -/// special case, the intersection program is not required – and is ignored – -/// for triangle primitives. -/// -/// # Safety -/// The lifetime of a module must extend to the lifetime of any -/// OptixProgramGroup that references that module. -#[repr(transparent)] -pub struct ProgramGroup { - pub(crate) raw: sys::OptixProgramGroup, -} - -impl ProgramGroup { - /// Use this information to calculate the total required stack sizes for a - /// particular call graph of NVIDIA OptiX programs. - /// - /// To set the stack sizes for a particular pipeline, use - /// [Pipeline::set_stack_size()](crate::Pipeline::set_stack_size()). - pub fn get_stack_size(&self) -> Result { - let mut stack_sizes = StackSizes::default(); - unsafe { - Ok(optix_call!(optixProgramGroupGetStackSize( - self.raw, - &mut stack_sizes as *mut _ as *mut _ - )) - .map(|_| stack_sizes)?) - } - } -} - -impl PartialEq for ProgramGroup { - fn eq(&self, rhs: &ProgramGroup) -> bool { - self.raw == rhs.raw - } -} - -/// # Creating and destroying `ProgramGroup`s -impl ProgramGroup { - /// Create a [ProgramGroup] for each of the [ProgramGroupDesc] objects in - /// `desc`. - pub fn new( - ctx: &mut DeviceContext, - desc: &[ProgramGroupDesc], - ) -> Result<(Vec, String)> { - cfg_if::cfg_if! { - if #[cfg(any(feature="optix73"))] { - let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; - } else { - let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; - } - } - - let mut log = [0u8; 4096]; - let mut log_len = log.len(); - - let pg_desc: Vec = desc.iter().map(|d| d.into()).collect(); - - let mut raws = vec![std::ptr::null_mut(); pg_desc.len()]; - - let res = unsafe { - optix_call!(optixProgramGroupCreate( - ctx.raw, - pg_desc.as_ptr(), - pg_desc.len() as u32, - &pg_options, - log.as_mut_ptr() as *mut i8, - &mut log_len, - raws.as_mut_ptr(), - )) - }; - - let log = CStr::from_bytes_with_nul(&log[0..log_len]) - .unwrap() - .to_string_lossy() - .into_owned(); - - match res { - Ok(()) => Ok(( - raws.iter().map(|raw| ProgramGroup { raw: *raw }).collect(), - log, - )), - Err(source) => Err(Error::ProgramGroupCreation { source, log }), - } - } - - /// Create a single [ProgramGroup] specified by `desc`. - pub fn new_single( - ctx: &mut DeviceContext, - desc: &ProgramGroupDesc, - ) -> Result<(ProgramGroup, String)> { - cfg_if::cfg_if! { - if #[cfg(any(feature="optix73"))] { - let pg_options = sys::OptixProgramGroupOptions { reserved: 0 }; - } else { - let pg_options = sys::OptixProgramGroupOptions { placeholder: 0 }; - } - } - - let mut log = [0u8; 4096]; - let mut log_len = log.len(); - - let pg_desc: sys::OptixProgramGroupDesc = desc.into(); - - let mut raw = std::ptr::null_mut(); - - let res = unsafe { - optix_call!(optixProgramGroupCreate( - ctx.raw, - &pg_desc, - 1, - &pg_options, - log.as_mut_ptr() as *mut i8, - &mut log_len, - &mut raw, - )) - }; - - let log = CStr::from_bytes_with_nul(&log[0..log_len]) - .unwrap() - .to_string_lossy() - .into_owned(); - - match res { - Ok(()) => Ok((ProgramGroup { raw }, log)), - Err(source) => Err(Error::ProgramGroupCreation { source, log }), - } - } - - /// Create a raygen [ProgramGroup] from `entry_function_name` in `module`. - pub fn raygen( - ctx: &mut DeviceContext, - module: &Module, - entry_function_name: &str, - ) -> Result { - let desc = ProgramGroupDesc::raygen(module, entry_function_name); - Ok(ProgramGroup::new_single(ctx, &desc)?.0) - } - - /// Create a miss [ProgramGroup] from `entry_function_name` in `module`. - pub fn miss( - ctx: &mut DeviceContext, - module: &Module, - entry_function_name: &str, - ) -> Result { - let desc = ProgramGroupDesc::miss(module, entry_function_name); - Ok(ProgramGroup::new_single(ctx, &desc)?.0) - } - - /// Create an exception [ProgramGroup] from `entry_function_name` in `module`. - pub fn exception( - ctx: &mut DeviceContext, - module: &Module, - entry_function_name: &str, - ) -> Result { - let desc = ProgramGroupDesc::exception(module, entry_function_name); - Ok(ProgramGroup::new_single(ctx, &desc)?.0) - } - - /// Create a hitgroup [ProgramGroup] from any combination of - /// `(module, entry_function_name)` pairs. - pub fn hitgroup( - ctx: &mut DeviceContext, - closest_hit: Option<(&Module, &str)>, - any_hit: Option<(&Module, &str)>, - intersection: Option<(&Module, &str)>, - ) -> Result { - let desc = ProgramGroupDesc::hitgroup(closest_hit, any_hit, intersection); - Ok(ProgramGroup::new_single(ctx, &desc)?.0) - } -} - -impl Drop for ProgramGroup { - fn drop(&mut self) { - unsafe { - sys::optixProgramGroupDestroy(self.raw); - } - } -} - -impl<'m> From<&ProgramGroupDesc<'m>> for sys::OptixProgramGroupDesc { - fn from(desc: &ProgramGroupDesc<'m>) -> sys::OptixProgramGroupDesc { - match &desc { - ProgramGroupDesc::Raygen(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_RAYGEN, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - raygen: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, - }, - flags: 0, - }, - ProgramGroupDesc::Miss(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_MISS, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - miss: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, - }, - flags: 0, - }, - ProgramGroupDesc::Exception(ProgramGroupModule { - module, - entry_function_name, - }) => sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_EXCEPTION, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - miss: sys::OptixProgramGroupSingleModule { - module: module.raw, - entryFunctionName: entry_function_name.as_ptr(), - }, - }, - flags: 0, - }, - ProgramGroupDesc::Hitgroup { ch, ah, is } => { - let mut efn_ch_ptr = std::ptr::null(); - let mut efn_ah_ptr = std::ptr::null(); - let mut efn_is_ptr = std::ptr::null(); - - let module_ch = if let Some(pg_ch) = &ch { - efn_ch_ptr = pg_ch.entry_function_name.as_ptr(); - pg_ch.module.raw - } else { - std::ptr::null_mut() - }; - - let module_ah = if let Some(pg_ah) = &ah { - efn_ah_ptr = pg_ah.entry_function_name.as_ptr(); - pg_ah.module.raw - } else { - std::ptr::null_mut() - }; - - let module_is = if let Some(pg_is) = &is { - efn_is_ptr = pg_is.entry_function_name.as_ptr(); - pg_is.module.raw - } else { - std::ptr::null_mut() - }; - - sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_HITGROUP, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - hitgroup: sys::OptixProgramGroupHitgroup { - moduleCH: module_ch, - entryFunctionNameCH: efn_ch_ptr, - moduleAH: module_ah, - entryFunctionNameAH: efn_ah_ptr, - moduleIS: module_is, - entryFunctionNameIS: efn_is_ptr, - }, - }, - flags: 0, - } - } - ProgramGroupDesc::Callables { dc, cc } => { - let (module_dc, efn_dc) = if let Some(pg_dc) = &dc { - (pg_dc.module.raw, pg_dc.entry_function_name.as_ptr()) - } else { - (std::ptr::null_mut(), std::ptr::null()) - }; - - let (module_cc, efn_cc) = if let Some(pg_cc) = &cc { - (pg_cc.module.raw, pg_cc.entry_function_name.as_ptr()) - } else { - (std::ptr::null_mut(), std::ptr::null()) - }; - - sys::OptixProgramGroupDesc { - kind: sys::OptixProgramGroupKind::OPTIX_PROGRAM_GROUP_KIND_CALLABLES, - __bindgen_anon_1: sys::OptixProgramGroupDesc__bindgen_ty_1 { - callables: sys::OptixProgramGroupCallables { - moduleDC: module_dc, - entryFunctionNameDC: efn_dc, - moduleCC: module_cc, - entryFunctionNameCC: efn_cc, - }, - }, - flags: 0, - } - } - } - } -} - -#[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] -pub struct StackSizes { - pub css_rg: u32, - pub css_mg: u32, - pub css_ch: u32, - pub css_ah: u32, - pub css_is: u32, - pub css_cc: u32, - pub css_dc: u32, -} diff --git a/crates/optix/src/shader_binding_table.md b/crates/optix/src/shader_binding_table.md new file mode 100644 index 00000000..bea007a8 --- /dev/null +++ b/crates/optix/src/shader_binding_table.md @@ -0,0 +1,240 @@ +# Shader Binding Table + +- [Records](#records) +- [Layout](#layout) +- [Acceleration Structures](#acceleration-structures) + - [SBT Instance Offset](#sbt-instance-offset) + - [SBT Geometry-AS Offset](#sbt-geometry-as-offset) + - [SBT Trace Offset](#sbt-trace-offset) + - [SBT Trace Stride](#sbt-trace-stride) + - [Example SBT For a Scene](#example-sbt-for-a-scene) +- [SBT Record Access on Device](#sbt-record-access-on-device) + +The shader binding table (SBT) is an array that contains information about the location of programs and their parameters. The SBT resides in device memory and is managed by the application. + +The shader binding table can be complex to get your head around. In addition to this documentation, you might also enjoy reading Will Usher's [*The RTX Shader Binding Table Three Ways*](https://www.willusher.io/graphics/2019/11/20/the-sbt-three-ways) + +## Records +A record is an array element of the SBT that consists of a header and a data block. The header content is opaque to the application, containing information accessed by traversal execution to identify and invoke programs. + +Rather than pack the records manually as in the C API, the Rust API instead gives you a generic [`SbtRecord`] type that you can specialize to supply your data to the SBT: + +``` +#[derive(Copy, Clone, Default, DeviceCopy)] +struct HitgroupSbtData { + object_id: u32, +} +type HitgroupRecord = SbtRecord; + +// Pack the object ids into the record for each object. In a real application +// you would supply pointers to device memory containing vertex attributes +// such as smoothed normals, texture coordinates etc. +let rec_hitgroup: Vec<_> = (0..num_objects) + .map(|i| { + let object_type = 0; + let rec = HitgroupRecord::pack( + HitgroupSbtData { object_id: i }, + &pg_hitgroup[object_type], + ) + .expect("failed to pack hitgroup record"); + rec + }) + .collect(); + + +``` + +The data section of an [`SbtRecord`] can be accessed on the device using the `optixGetSbtDataPointer()` device function. + +## Layout + +A shader binding table is split into five sections, where each section represents a unique program group type: + + + + + + + + +
      GroupProgram Types in Group
      Ray Generationray-generation
      Exceptionexception
      Missmiss
      Hitclosest-hit, any-hit, intersection
      Callabledirect-callable, continuation-callable
      + + See also [Program Group Creation](crate::pipeline) + + The [`ShaderBindingTable`] is created by passing [`DeviceBuffer`]s of [`SbtRecord`]s to the constructor: + + ``` +let mut buf_raygen = DeviceBuffer::from_slice(&rec_raygen)?; +let mut buf_miss = DeviceBuffer::from_slice(&rec_miss)?; +let mut buf_hitgroup = DeviceBuffer::from_slice(&rec_hitgroup)?; + +let sbt = ShaderBindingTable::new(&mut buf_raygen) + .miss(&mut buf_miss) + .hitgroup(&mut buf_hitgroup) + .build(); + ``` +The [`SbtRecord`]s buffers are assumed to be densely, packed and the [`SbtRecord`] itself is correctly aligned to 16 bytes. + +The index to records in the shader binding table is used in different ways for the miss, hit, and callables groups: + +* *Miss* - Miss programs are selected for every optixTrace call using the missSBTIndex parameter. +* *Callables* - Callables take the index as a parameter and call the direct-callable when invoking optixDirectCall and continuation-callable when invoking optixContinuationCall. +* *Any-hit, closest-hit, intersection* - The computation of the index for the hit group (intersection, any-hit, closest-hit) is done during traversal. See [Acceleration structures](#acceleration-structures) for more detail. + + +## Acceleration Structures + +The selection of the SBT hit group record for the instance is slightly more involved to allow for a number of use cases such as the implementation of different ray types. The SBT record index `sbt_index` is determined by the following index calculation during traversal: + +```text +sbt-index = + sbt-instance-offset + + (sbt-geometry-acceleration-structure-index * sbt-stride-from-trace-call) + + sbt-offset-from-trace-call +``` + +The index calculation depends upon the following SBT indices and offsets: + +* Instance offset +* Geometry acceleration structure index +* Trace offset +* Trace stride + +### SBT Instance Offset + +Instance acceleration structure instances (type [`Instance`]) store an SBT offset that is applied during traversal. This is zero for single geometry-AS traversable because there is no corresponding instance-AS to hold the value. (See “Traversal of a single geometry acceleration structure”.) This value is limited to 24 bits. + +### SBT Geometry-AS Index + +Each geometry acceleration structure build input references at least one SBT record. The first SBT geometry acceleration structure index for each geometry acceleration structure build input is the prefix sum of the number of SBT records. Therefore, the computed SBT geometry acceleration structure index is dependent on the order of the build inputs. + +The following example demonstrates a geometry acceleration structure with three build inputs. Each build input references one SBT record by specifying `num_sbt_records=1`. When intersecting geometry at trace time, the SBT geometry acceleration structure index used to compute the `sbt_index` to select the hit group record will be organized as follows: + + + + + + +
      SBT Geometry-AS Index012
      Geometry-AS build inputbuild_input[0]
      built_input[1]
      built_input[2]
      + +In this simple example, the index for the build input equals the SBT geometry acceleration structure index. Hence, whenever a primitive from “Build input [1]” is intersected, the SBT geometry acceleration structure index is one. + +When a single build input references multiple SBT records (for example, to support multiple materials per geometry), the mapping corresponds to the prefix sum over the number of referenced SBT records. + +For example, consider three build inputs where the first build input references four SBT records, the second references one SBT record, and the last references two SBT records: + + + + + + +
      SBT Geometry-AS Index0123456
      Geometry-AS build inputbuild_input[0] num=4
      build_input[1] num=1
      build_input[2] offset=2
      + +These three build inputs result in the following possible SBT geometry acceleration structure indices when intersecting the corresponding geometry acceleration structure build input: + +* One index in the range of [0,3] if a primitive from `build_input[0]` is intersected +* Four if a primitive from `build_input[1]` is intersected +* One index in the range of [5,6] if a primitive from `build_input[2]` is intersected + +The per-primitive SBT index offsets, as specified by using `sbt_index_offset_buffer`, are local to the build input. Hence, per-primitive offsets in the range [0,3] for the build input 0 and in the range [0,1] for the last build input, map to the SBT geometry acceleration structure index as follows: + + + + + + + + + + + + + + + + + + + + + + + + + + +
      SBT Geometry-AS Index0123456
      build_input[0].sbt_index_offset:[0]
      [1]
      [2]
      [3]
      build_input[1].sbt_index_offset=None
      build_input[1].sbt_index_offset:[0]
      [1]
      + +Because `build_input[1]` references a single SBT record, a `sbt_index_offset_buffer` does not need to be specified for the geometry acceleration structure build. See “Acceleration structures”. + +### SBT Trace Offset + +The `optixTrace` function takes the parameter `SBToffset`, allowing for an SBT access shift for this specific ray. It is required to implement different ray types, i.e. the offset is the index of the ray type. + +### SBT Trace Stride + +The parameter `SBTstride`, defined as an index offset, is multiplied by `optixTrace` with the SBT geometry acceleration structure index. It is required to implement different ray types, i.e. the stride is the number of ray types. + +### Example SBT For a Scene + +In this example, a shader binding table implements the program selection for a simple scene containing one instance acceleration structure and two instances of the same geometry acceleration structure, where the geometry acceleration structure has two build inputs: + +![Structure of a simple scene](scene_graph) + +The first build input references a single SBT record, while the second one references two SBT records. There are two ray types: one for forward path tracing and one for shadow rays (next event estimation). The two instances of the geometry acceleration structure have different transforms and SBT offsets to allow for material variation in each instance of the same geometry acceleration structure. Therefore, the SBT needs to hold two miss records and 12 hit group records (three for the geometry acceleration structure, ×2 for the ray types, ×2 for the two instances in the instance acceleration structure). + +![Example SBT](example_sbt) + +To trace a ray of type 0 (for example, for path tracing): + +``` +optixTrace(IAS_handle, + ray_org, ray_dir, + tmin, tmax, time, + visMask, rayFlags, + 0, // sbtOffset + 2, // sbtStride + 0, // missSBTIndex + rayPayload0, ...); +``` +Shadow rays need to pass in an adjusted `sbtOffset` as well as `missSBTIndex`: + +``` +optixTrace(IAS_handle, + ray_org, ray_dir, + tmin, tmax, time, + visMask, rayFlags, + 1, // sbtOffset + 2, // sbtStride + 1, // missSBTIndex + rayPayload0, ...); +``` + +Program groups of different types (ray generation, miss, intersection, and so on) do not need to be adjacent to each other as shown in the example. The pointer to the first SBT record of each program group type is passed to [`launch()`](crate::launch), as described previously, which allows for arbitrary spacing in the SBT between the records of different program group types. + +### SBT Record Access on Device + +To access the SBT data section of the currently running program, request its pointer by using an API function: +```text +CUdeviceptr optixGetSbtDataPointer(); +``` +Typically, this pointer is cast to a pointer that represents the layout of the data section. For example, for a closest hit program, the application gets access to the data associated with the SBT record that was used to invoke that closest hit program: + +```text +struct CHData { + int meshIdx; // Triangle mesh build input index + float3 base_color; +}; + +CHData* material_info = (CHData*)optixGetSbtDataPointer(); +``` +The program is encouraged to rely on the alignment constraints of the SBT data section to read this data efficiently. + + + [`Instance`]: crate::acceleration::Instance + [`SbtRecord`]: crate::shader_binding_table::SbtRecord + [`SbtRecord`]: crate::shader_binding_table::SbtRecord + [`ShaderBindingTable`]: crate::shader_binding_table::ShaderBindingTable + [`DeviceBuffer`]: cust::memory::DeviceBuffer + + diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 78a07506..eede1ee2 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -1,4 +1,4 @@ -use crate::{error::Error, optix_call, program_group::ProgramGroup, sys}; +use crate::{error::Error, optix_call, pipeline::ProgramGroup, sys}; use cust::memory::{DeviceCopy, DeviceSlice}; use cust_raw::CUdeviceptr; diff --git a/katex-header.html b/katex-header.html new file mode 100644 index 00000000..32ac35a4 --- /dev/null +++ b/katex-header.html @@ -0,0 +1,15 @@ + + + + From 25f731166f543b33e82ea04b317dd8eb6b82cafa Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 13:59:34 +1300 Subject: [PATCH 066/100] Wrap SBT properly --- .../examples/ex02_pipeline/src/renderer.rs | 5 +- .../examples/ex03_window/src/renderer.rs | 5 +- .../optix/examples/ex04_mesh/src/renderer.rs | 5 +- crates/optix/src/lib.rs | 6 +- crates/optix/src/shader_binding_table.rs | 94 +++++++------------ 5 files changed, 45 insertions(+), 70 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 4baca2df..d6781bc3 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -18,7 +18,7 @@ use optix::{ pub struct Renderer { launch_params: LaunchParams, buf_launch_params: DeviceBox, - sbt: optix::sys::OptixShaderBindingTable, + sbt: ShaderBindingTable, pipeline: Pipeline, buf_raygen: DeviceBuffer, buf_hitgroup: DeviceBuffer, @@ -120,8 +120,7 @@ impl Renderer { let sbt = ShaderBindingTable::new(&mut buf_raygen) .miss(&mut buf_miss) - .hitgroup(&mut buf_hitgroup) - .build(); + .hitgroup(&mut buf_hitgroup); // create pipeline let mut program_groups = Vec::new(); diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index ed324c88..248ffd88 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -19,7 +19,7 @@ use crate::vector::V4f32; pub struct Renderer { launch_params: LaunchParams, buf_launch_params: DeviceBox, - sbt: optix::sys::OptixShaderBindingTable, + sbt: ShaderBindingTable, buf_raygen: DeviceBuffer, buf_hitgroup: DeviceBuffer, buf_miss: DeviceBuffer, @@ -118,8 +118,7 @@ impl Renderer { let sbt = ShaderBindingTable::new(&mut buf_raygen) .miss(&mut buf_miss) - .hitgroup(&mut buf_hitgroup) - .build(); + .hitgroup(&mut buf_hitgroup); // create pipeline let mut program_groups = Vec::new(); diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index afc7dc6c..56d69f91 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -24,7 +24,7 @@ use glam::{ivec2, vec3, IVec2, IVec3, Vec3, Vec4}; pub struct Renderer { launch_params: LaunchParams, buf_launch_params: DeviceBox, - sbt: optix::sys::OptixShaderBindingTable, + sbt: ShaderBindingTable, gas: Accel, buf_raygen: DeviceBuffer, buf_hitgroup: DeviceBuffer, @@ -157,8 +157,7 @@ impl Renderer { let sbt = ShaderBindingTable::new(&mut buf_raygen) .miss(&mut buf_miss) - .hitgroup(&mut buf_hitgroup) - .build(); + .hitgroup(&mut buf_hitgroup); // create pipeline let mut program_groups = Vec::new(); diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 9f9b8ea2..dd1c16fd 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -46,6 +46,8 @@ pub mod prelude; #[doc = ::embed_doc_image::embed_image!("scene_graph", "images/scene_graph.png")] #[doc = include_str!("shader_binding_table.md")] pub mod shader_binding_table; +use shader_binding_table::ShaderBindingTable; + pub mod sys; pub use cust; @@ -125,7 +127,7 @@ pub unsafe fn launch( pipeline: &crate::pipeline::Pipeline, stream: &cust::stream::Stream, pipeline_params: DevicePointer

      , - sbt: &sys::OptixShaderBindingTable, + sbt: &ShaderBindingTable, width: u32, height: u32, depth: u32, @@ -135,7 +137,7 @@ pub unsafe fn launch( stream.as_inner(), pipeline_params.as_raw() as u64, std::mem::size_of::

      (), - sbt, + &sbt.0, width, height, depth, diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index eede1ee2..a1055ebb 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -3,6 +3,8 @@ use crate::{error::Error, optix_call, pipeline::ProgramGroup, sys}; use cust::memory::{DeviceCopy, DeviceSlice}; use cust_raw::CUdeviceptr; +use crate::{const_assert, const_assert_eq}; + type Result = std::result::Result; #[repr(C)] @@ -40,52 +42,25 @@ where unsafe impl DeviceCopy for SbtRecord {} -pub struct ShaderBindingTable { - raygen_record: CUdeviceptr, - exception_record: CUdeviceptr, - miss_record_base: CUdeviceptr, - miss_record_stride_in_bytes: u32, - miss_record_count: u32, - hitgroup_record_base: CUdeviceptr, - hitgroup_record_stride_in_bytes: u32, - hitgroup_record_count: u32, - callables_record_base: CUdeviceptr, - callables_record_stride_in_bytes: u32, - callables_record_count: u32, -} +#[repr(transparent)] +pub struct ShaderBindingTable(pub(crate) sys::OptixShaderBindingTable); impl ShaderBindingTable { pub fn new(buf_raygen_record: &DeviceSlice>) -> Self { let raygen_record = buf_raygen_record.as_device_ptr(); - ShaderBindingTable { - raygen_record, - exception_record: 0, - miss_record_base: 0, - miss_record_stride_in_bytes: 0, - miss_record_count: 0, - hitgroup_record_base: 0, - hitgroup_record_stride_in_bytes: 0, - hitgroup_record_count: 0, - callables_record_base: 0, - callables_record_stride_in_bytes: 0, - callables_record_count: 0, - } - } - - pub fn build(self) -> sys::OptixShaderBindingTable { - sys::OptixShaderBindingTable { - raygenRecord: self.raygen_record, - exceptionRecord: self.exception_record, - missRecordBase: self.miss_record_base, - missRecordStrideInBytes: self.miss_record_stride_in_bytes, - missRecordCount: self.miss_record_count, - hitgroupRecordBase: self.hitgroup_record_base, - hitgroupRecordStrideInBytes: self.hitgroup_record_stride_in_bytes, - hitgroupRecordCount: self.hitgroup_record_count, - callablesRecordBase: self.callables_record_base, - callablesRecordStrideInBytes: self.callables_record_stride_in_bytes, - callablesRecordCount: self.callables_record_count, - } + ShaderBindingTable(sys::OptixShaderBindingTable { + raygenRecord: raygen_record, + exceptionRecord: 0, + missRecordBase: 0, + missRecordStrideInBytes: 0, + missRecordCount: 0, + hitgroupRecordBase: 0, + hitgroupRecordStrideInBytes: 0, + hitgroupRecordCount: 0, + callablesRecordBase: 0, + callablesRecordStrideInBytes: 0, + callablesRecordCount: 0, + }) } pub fn exception( @@ -95,7 +70,7 @@ impl ShaderBindingTable { if buf_exception_record.len() != 1 { panic!("SBT not passed single exception record",); } - self.exception_record = buf_exception_record.as_device_ptr(); + self.0.exceptionRecord = buf_exception_record.as_device_ptr(); self } @@ -103,9 +78,9 @@ impl ShaderBindingTable { if buf_miss_records.len() == 0 { panic!("SBT passed empty miss records"); } - self.miss_record_base = buf_miss_records.as_device_ptr(); - self.miss_record_stride_in_bytes = std::mem::size_of::>() as u32; - self.miss_record_count = buf_miss_records.len() as u32; + self.0.missRecordBase = buf_miss_records.as_device_ptr(); + self.0.missRecordStrideInBytes = std::mem::size_of::>() as u32; + self.0.missRecordCount = buf_miss_records.len() as u32; self } @@ -116,9 +91,9 @@ impl ShaderBindingTable { if buf_hitgroup_records.len() == 0 { panic!("SBT passed empty hitgroup records"); } - self.hitgroup_record_base = buf_hitgroup_records.as_device_ptr(); - self.hitgroup_record_stride_in_bytes = std::mem::size_of::>() as u32; - self.hitgroup_record_count = buf_hitgroup_records.len() as u32; + self.0.hitgroupRecordBase = buf_hitgroup_records.as_device_ptr(); + self.0.hitgroupRecordStrideInBytes = std::mem::size_of::>() as u32; + self.0.hitgroupRecordCount = buf_hitgroup_records.len() as u32; self } @@ -129,17 +104,18 @@ impl ShaderBindingTable { if buf_callables_records.len() == 0 { panic!("SBT passed empty callables records"); } - self.callables_record_base = buf_callables_records.as_device_ptr(); - self.callables_record_stride_in_bytes = std::mem::size_of::>() as u32; - self.callables_record_count = buf_callables_records.len() as u32; + self.0.callablesRecordBase = buf_callables_records.as_device_ptr(); + self.0.callablesRecordStrideInBytes = std::mem::size_of::>() as u32; + self.0.callablesRecordCount = buf_callables_records.len() as u32; self } } -// Sanity check that the size of this union we're defining matches the one in -// optix header so we don't get any nasty surprises -fn _size_check() { - unsafe { - std::mem::transmute::(panic!()); - } -} +const_assert_eq!( + std::mem::align_of::(), + std::mem::align_of::(), +); +const_assert_eq!( + std::mem::size_of::(), + std::mem::size_of::() +); From 6b002ece79b010e52cd063c00c96be7d7f6c7a5c Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 14:20:14 +1300 Subject: [PATCH 067/100] Rename transform types --- crates/optix/src/acceleration.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index e1d9d8e6..e3b8afc4 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -1690,12 +1690,12 @@ const_assert_eq!( ); /// Stores the device memory and the [`TraversableHandle`] for a [`StaticTransform`] -pub struct DeviceStaticTransform { +pub struct StaticTransform { buf: DeviceBox, hnd: TraversableHandle, } -impl DeviceStaticTransform { +impl StaticTransform { /// Create a new DeviceStaticTransform by copying the given [`StaticTransform`] /// to the device and converting the resulting pointer to an OptiX [`Traversable`]; pub fn new> + Clone>( @@ -1703,7 +1703,7 @@ impl DeviceStaticTransform { child: &T, transform: &M, inv_transform: &M, - ) -> Result { + ) -> Result { let transform = (*transform).clone().into(); let inv_transform = (*inv_transform).clone().into(); let buf = DeviceBox::new(&StaticTransformWrapper(sys::OptixStaticTransform { @@ -1720,7 +1720,7 @@ impl DeviceStaticTransform { )? }; - Ok(DeviceStaticTransform { buf, hnd }) + Ok(StaticTransform { buf, hnd }) } /// Create a new DeviceStaticTransform from device memory and pre-converted @@ -1733,7 +1733,7 @@ impl DeviceStaticTransform { } } -impl Traversable for DeviceStaticTransform { +impl Traversable for StaticTransform { fn handle(&self) -> TraversableHandle { self.hnd } @@ -1744,12 +1744,12 @@ impl Traversable for DeviceStaticTransform { /// /// Stores the device memory and the [`TraversableHandle`] for a [`sys::OptixMatrixMotionTransform`] /// and an arbitrary number of motion keys -pub struct DeviceMatrixMotionTransform { +pub struct MatrixMotionTransform { buf: DeviceBuffer, hnd: TraversableHandle, } -impl DeviceMatrixMotionTransform { +impl MatrixMotionTransform { /// Create a new MatrixMotionTransform with the given time range, flags and /// motion keys. /// @@ -1767,7 +1767,7 @@ impl DeviceMatrixMotionTransform { time_end: f32, flags: MotionFlags, transforms: &[RowMatrix3x4], - ) -> Result { + ) -> Result { let num_keys = transforms.len(); if num_keys < 2 { return Err(Error::TooFewMotionKeys(num_keys)); @@ -1831,7 +1831,7 @@ impl DeviceMatrixMotionTransform { } } -impl Traversable for DeviceMatrixMotionTransform { +impl Traversable for MatrixMotionTransform { fn handle(&self) -> TraversableHandle { self.hnd } @@ -1891,14 +1891,12 @@ impl Deref for SrtData { /// /// Stores the device memory and the [`TraversableHandle`] for a [`sys::OptixSRTMotionTransform`] /// and an arbitrary number of motion keys -/// -/// FIXME (AL): need to see about checking the limits on the number of keys -pub struct DeviceSrtMotionTransform { +pub struct SrtMotionTransform { buf: DeviceBuffer, hnd: TraversableHandle, } -impl DeviceSrtMotionTransform { +impl SrtMotionTransform { /// Create a new SrtMotionTransform from the given child [`TraversableHandle`], /// time range, flags and [`SrtData`] /// @@ -1916,7 +1914,7 @@ impl DeviceSrtMotionTransform { time_end: f32, flags: MotionFlags, srt_data: &[SrtData], - ) -> Result { + ) -> Result { let num_keys = srt_data.len(); if num_keys < 2 { return Err(Error::TooFewMotionKeys(num_keys)); @@ -1980,7 +1978,7 @@ impl DeviceSrtMotionTransform { } } -impl Traversable for DeviceSrtMotionTransform { +impl Traversable for SrtMotionTransform { fn handle(&self) -> TraversableHandle { self.hnd } From 31e8e76d6213b21c46a145cb7e6fadda9932ef71 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 14:20:51 +1300 Subject: [PATCH 068/100] Simplify AccelBuildOptions creation Just take a build flags and move everything else to builders --- crates/optix/src/acceleration.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index e3b8afc4..0e303cef 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -680,6 +680,7 @@ bitflags::bitflags! { /// an AH programs on the device. May affect the performance of the accel (seems to be larger). /// /// Note that `PREFER_FAST_TRACE` and `PREFER_FAST_BUILD` are mutually exclusive. + #[derive(Default)] pub struct BuildFlags: u32 { const NONE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_NONE; const ALLOW_UPDATE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_UPDATE; @@ -698,6 +699,12 @@ pub enum BuildOperation { Update = sys::OptixBuildOperation_OPTIX_BUILD_OPERATION_UPDATE, } +impl Default for BuildOperation { + fn default() -> Self { + BuildOperation::Build + } +} + bitflags::bitflags! { /// Configure how to handle ray times that are outside of the provided motion keys. /// @@ -757,7 +764,7 @@ const_assert_eq!( /// Options to configure the [`accel_build()`] #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Default)] pub struct AccelBuildOptions { build_flags: BuildFlags, operation: BuildOperation, @@ -767,10 +774,10 @@ pub struct AccelBuildOptions { impl AccelBuildOptions { /// Create a new AccelBuildOptions with the given flags and operation and /// no motion blur. - pub fn new(build_flags: BuildFlags, operation: BuildOperation) -> Self { + pub fn new(build_flags: BuildFlags) -> Self { AccelBuildOptions { build_flags, - operation, + operation: BuildOperation::Build, motion_options: MotionOptions { num_keys: 1, flags: MotionFlags::NONE, @@ -780,6 +787,12 @@ impl AccelBuildOptions { } } + /// Set the build operation to either build or update + pub fn build_operation(mut self, op: BuildOperation) -> Self { + self.operation = op; + self + } + /// Set the number of motion keys. /// /// This must either be 0 for no motion blur, or >= 2. @@ -833,6 +846,7 @@ pub struct AccelBufferSizes { /// boxes back from an accel build. /// /// # Examples +/// ``` /// // Copy the returned size needed for the compacted buffer and allocate /// // storage /// let mut compacted_size = 0usize; @@ -842,6 +856,7 @@ pub struct AccelBufferSizes { /// /// // Compact the accel structure. /// let hnd = unsafe { accel_compact(ctx, stream, hnd, &mut buf)? }; +/// ``` pub enum AccelEmitDesc { CompactedSize(DevicePointer), Aabbs(DevicePointer), From 66f6aad42545d7461da8dd3d5c9d9d79796bacab Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 14:21:19 +1300 Subject: [PATCH 069/100] Hide programming guide in details tags --- crates/optix/src/acceleration.md | 141 +++++++++++++++++------ crates/optix/src/context.md | 10 +- crates/optix/src/introduction.md | 68 +++++------ crates/optix/src/lib.rs | 25 +++- crates/optix/src/pipeline.md | 7 ++ crates/optix/src/shader_binding_table.md | 8 ++ 6 files changed, 184 insertions(+), 75 deletions(-) diff --git a/crates/optix/src/acceleration.md b/crates/optix/src/acceleration.md index 1d5e5893..aefb0f61 100644 --- a/crates/optix/src/acceleration.md +++ b/crates/optix/src/acceleration.md @@ -1,5 +1,53 @@ # Acceleration Structures +```no_run +use cust::prelude as cu; +use optix::prelude as ox; +# fn doit() -> Result<(), Box> { +# cust::init(cu::CudaFlags::empty())?; +# ox::init()?; +# let device = cu::Device::get_device(0)?; +# let cu_ctx = cu::Context::create_and_push(cu::ContextFlags::SCHED_AUTO | +# cu::ContextFlags::MAP_HOST, device)?; +# let ctx = ox::DeviceContext::new(&cu_ctx, false)?; +# let vertices: Vec<[f32; 3]> = Vec::new(); +# let indices: Vec<[u32; 3]> = Vec::new(); +# let stream = cu::Stream::new(cu::StreamFlags::DEFAULT, None)?; + +// Allocate buffers and copy vertex and index data to device +let buf_vertex = cu::DeviceBuffer::from_slice(&vertices)?; +let buf_indices = cu::DeviceBuffer::from_slice(&indices)?; + +// Tell OptiX the structure of our triangle mesh +let geometry_flags = ox::GeometryFlags::None; +let triangle_input = + ox::IndexedTriangleArray::new( + &[&buf_vertex], + &buf_indices, + &[geometry_flags] + ); + +// Tell OptiX we'd prefer a faster traversal over a faster bvh build. +let accel_options = AccelBuildOptions::new(ox::BuildFlags::PREFER_FAST_TRACE); + +// Build the accel asynchronously +let gas = ox::Accel::build( + &ctx, + &stream, + &[accel_options], + &[triangle_input], + true +)?; +# Ok(()) +# } +``` + +# Programming Guide... +

      +Click here to expand programming guide + +## Contents + - [Building](#building) - [Building Safe API](#building-safe-api) - [Buliding Unsafe API](#building-unsafe-api) @@ -57,18 +105,18 @@ The following acceleration structure types are supported: For geometry-AS builds, each build input can specify a set of triangles, a set of curves, or a set of user-defined primitives bounded by specified axis-aligned -bounding boxes. Multiple build inputs can be passed as an array to [`accel_build`] +bounding boxes. Multiple build inputs can be passed as an array to [`Accel::build()`] to combine different meshes into a single acceleration structure. All build inputs for a single build must agree on the build input type. Instance acceleration structures have a single build input and specify an array -of instances. Each [`Instance`](crate::instance_array::Instance) includes a ray transformation and an +of instances. Each [`Instance`] includes a ray transformation and a [`TraversableHandle`] that refers to a geometry-AS, a transform node, or another instance acceleration structure. ### Building Safe API -The easiest way to build an acceleration structure is using [`Accel::build`] +The easiest way to build an acceleration structure is using [`Accel::build()`] to which you just pass a slice of [`BuildInput`]s and the function handles memory allocation and synchronization for you. @@ -125,12 +173,12 @@ stream.synchronize()?; ### Building Unsafe API -As an alternative, you can also use the unsafe functions [`accel_build`], -[`accel_compact`], and [`Accel::from_raw_parts`] to handle the memory +As an alternative, you can also use the unsafe functions [`accel_build()`], +[`accel_compact()`], and [`Accel::from_raw_parts()`] to handle the memory allocation yourself, meaning you can reuse buffers between accel builds. To prepare for a build, the required memory sizes are queried by passing an -initial set of build inputs and parameters to [`accel_compute_memory_usage`]. +initial set of build inputs and parameters to [`accel_compute_memory_usage()`]. It returns three different sizes: * `output_size_in_bytes` - Size of the memory region where the resulting @@ -147,11 +195,11 @@ a 128-byte boundary. These buffers are actively used for the duration of the build. For this reason, they cannot be shared with other currently active build requests. -Note that [`accel_compute_memory_usage`] does not initiate any activity on the +Note that [`accel_compute_memory_usage()`] does not initiate any activity on the device; pointers to device memory or contents of input buffers are not required to point to allocated memory. -The function [`accel_build`] takes the same array of [`BuildInput`] structs as -[`accel_compute_memory_usage`] and builds a single acceleration structure from +The function [`accel_build()`] takes the same array of [`BuildInput`] structs as +[`accel_compute_memory_usage()`] and builds a single acceleration structure from these inputs. This acceleration structure can contain either geometry or instances, depending on the inputs to the build. @@ -165,7 +213,7 @@ the function immediately, without waiting for the build to finish. By producing handles at acceleration time, custom handles can also be generated based on input to the builder. -The acceleration structure constructed by [`accel_build`] does not reference +The acceleration structure constructed by [`accel_build()`] does not reference any of the device buffers referenced in the build inputs. All relevant data is copied from these buffers into the acceleration output buffer, possibly in a different format. @@ -242,22 +290,22 @@ must be all triangle inputs, all curve inputs, or all AABB inputs. Mixing build input types in a single geometry-AS is not allowed. Each build input maps to one or more consecutive records in the shader binding -table (SBT), which controls program dispatch. (See “Shader binding table”.) If +table (SBT), which controls program dispatch. (See [Shader binding table](crate::shader_binding_table).) If multiple records in the SBT are required, the application needs to provide a device buffer with per-primitive SBT record indices for that build input. If only a single SBT record is requested, all primitives reference this same unique SBT record. Note that there is a limit to the number of referenced SBT records per geometry-AS. (Limits are discussed in “Limits”.) -Each build input also specifies an array of OptixGeometryFlags, one for each SBT +Each build input also specifies an array of [`GeometryFlags`], one for each SBT record. The flags for one record apply to all primitives mapped to this SBT record. The following flags are supported: -* [`GeometryFlags::None`] - Applies the default behavior when calling the any-hit +* [`GeometryFlags::None`](crate::acceleration::GeometryFlags) - Applies the default behavior when calling the any-hit program, possibly multiple times, allowing the acceleration-structure builder to apply all optimizations. -* [`GeometryFlags::RequireSingleAnyHitCall`] - Disables some optimizations +* [`GeometryFlags::RequireSingleAnyHitCall`](crate::acceleration::GeometryFlags) - Disables some optimizations specific to acceleration-structure builders. By default, traversal may call the any-hit program more than once for each intersected primitive. Setting the flag ensures that the any-hit program is called only once for a hit with a @@ -265,7 +313,7 @@ primitive. However, setting this flag may change traversal performance. The usage of this flag may be required for correctness of some rendering algorithms; for example, in cases where opacity or transparency information is accumulated in an any-hit program. -* [`GeometryFlags::DisableAnyHit`] - Indicates that traversal should not call +* [`GeometryFlags::DisableAnyHit`](crate::acceleration::GeometryFlags) - Indicates that traversal should not call the any-hit program for this primitive even if the corresponding SBT record contains an any-hit program. Setting this flag usually improves performance even if no any-hit program is present in the SBT. @@ -282,13 +330,13 @@ primitive. An acceleration structure build can be controlled using the values of the [`BuildFlags`] enum. To enable random vertex access on an acceleration structure, -use [`BuildFlags::ALLOW_RANDOM_VERTEX_ACCESS`]. (See “Vertex random access”.) +use [`BuildFlags::ALLOW_RANDOM_VERTEX_ACCESS`](crate::acceleration::BuildFlags). To steer trade-offs between build performance, runtime traversal performance -and acceleration structure memory usage, use [`BuildFlags::PREFER_FAST_TRACE`] -and [`BuildFlags::PREFER_FAST_BUILD`]. For curve primitives in particular, +and acceleration structure memory usage, use [`BuildFlags::PREFER_FAST_TRACE`](crate::acceleration::BuildFlags) +and [`BuildFlags::PREFER_FAST_BUILD`](crate::acceleration::BuildFlags). For curve primitives in particular, these flags control splitting; see “Splitting curve segments”. -The flags [`BuildFlags::PREFER_FAST_TRACE`] and [`BuildFlags::PREFER_FAST_BUILD`] +The flags [`BuildFlags::PREFER_FAST_TRACE`](crate::acceleration::BuildFlags) and [`BuildFlags::PREFER_FAST_BUILD`](crate::acceleration::BuildFlags) are mutually exclusive. To combine multiple flags that are not mutually exclusive, use the logical “or” operator. @@ -306,12 +354,13 @@ input data. ### Dynamic Updates Safe API -The simplest way to use dynamic updates is with the [`DynamicAccel`] structure. -Simply call [`DynamicAccel::new()`] as you would with [`Accel`], and then +The simplest way to use dynamic updates is with the [`DynamicAccel`] structure, which wraps an [`Accel`] and adds extra checks and functionality to support dyanmic updates to the acceleration structure. + +Simply call [`DynamicAccel::build()`] as you would with [`Accel`], and then call [`DynamicAccel::update()`] with the updated build inputs when you want to update the acceleration structure. -Note that the inputs to [`DynamicAccel::update`] must have the same structure, +Note that the inputs to [`DynamicAccel::update()`] must have the same structure, i.e. the number of motion keys, aabbs, triangle topology etc must be the same, although the underlying data (including the data pointers) can be different. If the data have a different structure, then behaviour is undefined. @@ -321,11 +370,11 @@ the data do not match. ### Dynamic Updates Unsafe API To allow for future updates of an acceleration structure, set -[`BuildFlags::ALLOW_UPDATE`] in the build flags when building the acceleration +[`BuildFlags::ALLOW_UPDATE`](crate::acceleration::BuildFlags) in the build flags when building the acceleration structure initially. To update the previously built acceleration structure, set the operation to -[`BuildOperation::Update`] and then call [`accel_build()`] on the same output +[`BuildOperation::Update`](crate::acceleration::BuildOperation) and then call [`accel_build()`] on the same output data. All other options are required to be identical to the original build. The update is done in-place on the output data. @@ -364,15 +413,17 @@ architecture. ### Compaction Safe API To compact an [`Accel`] or [`DynamicAccel`] when building, simply pass `true` -for the `compact` parameter. +for the `compact` parameter. This handles all buffer allocation and management +internally, providing safely and simplicity at the cost of not being able to re-use +temporary buffers. ### Compaction Unsafe API To compact the acceleration structure as a post-process, do the following: -* Build flag [`BuildFlags::ALLOW_COMPACTION`] must be set in the +* Build flag [`BuildFlags::ALLOW_COMPACTION`](crate::acceleration::BuildFlags) must be set in the [`AccelBuildOptions`] passed to optixAccelBuild. -* The emit property [`AccelEmitDesc::CompactedSize`] must be passed to +* The emit property [`AccelEmitDesc::CompactedSize`](crate::acceleration::AccelEmitDesc) must be passed to [`accel_build()`]. This property is generated on the device and it must be copied back to the host if it is required for allocating the new output buffer. The application may then choose to compact the acceleration structure @@ -537,8 +588,8 @@ types and how to map scene options best to avoid potential performance pitfalls. ### Basics Motion is supported by -[`MatrixMotionTransform`](crate::transform::MatrixMotionTransform), -[`SrtMotionTransform`](crate::transform::SrtMotionTransform) and +[`MatrixMotionTransform`], +[`SrtMotionTransform`] and acceleration structure traversables. The general motion characteristics are specified per traversable as motion options: the number of motion keys, flags, and the beginning and ending motion times corresponding to the first and last @@ -557,14 +608,14 @@ itself may or may not have motion. Motion transforms must specify at least two motion keys. Acceleration structures, however, also accept [`AccelBuildOptions`] with field [`MotionOptions`] set to -[`default()`]. This effectively disables motion for the acceleration structure and +`default()`. This effectively disables motion for the acceleration structure and ignores the motion beginning and ending times, along with the motion flags. OptiX also supports static transform traversables in addition to the static transform of an instance. Static transforms are intended for the case of motion transforms in the scene. Without any motion transforms -([`MatrixMotionTransform`](crate::transform::MatrixMotionTransform) or -[`SrtMotionTransform](crate::transform::SrtMotionTransform)) in the traversable +([`MatrixMotionTransform`] or +[`SrtMotionTransform`]) in the traversable graph, any static transformation should be baked into the instance transform. However, if there is a motion transform, it may be required to apply a static transformation on a traversable (for example, on a geometry-AS) first before @@ -579,8 +630,8 @@ as transformation. Motion boundary conditions are specified by using flags. By default, the behavior for any time outside the time range, is as if time was clamped to the range, meaning it appears static and visible. Alternatively, to remove the -traversable before the beginning time, set [`MotionFlags::START_VANISH`]; to -remove it after the ending time, set [`MotionFlags::END_VANISH`]. +traversable before the beginning time, set [`MotionFlags::START_VANISH`](crate::acceleration::MotionFlags); to +remove it after the ending time, set [`MotionFlags::END_VANISH`](crate::acceleration::MotionFlags). For example: ``` @@ -674,7 +725,7 @@ Finally, the quality of the instance acceleration structure is also affected by The motion matrix transform traversable ([`MatrixMotionTransform`]) transforms the ray during traversal using a motion matrix. The traversable provides a 3x4 row-major object-to-world transformation matrix for each motion key. The final motion matrix is constructed during traversal by interpolating the elements of the matrices at the nearest motion keys. The [`MatrixMotionTransform`] can be created with an arbitrary number of keys -using its [`new()`](crate::transform::MatrixMotionTransform::new) constructor. +using its [`new()`](crate::acceleration::MatrixMotionTransform::new) constructor. ### Motion Scale Rotate Translate Transform @@ -780,9 +831,27 @@ A practical example for this is a motion matrix transform that performs a rotati Duplicate motion transforms should not be used as a workaround for irregular keys, where each key has varying motion beginning and ending times and vanish motion flags set. This duplication creates traversal overhead as all copies need to be intersected and their motion times compared to the ray's time. +
      + +[`Accel::build()`]: crate::acceleration::Accel::build +[`Accel::from_raw_parts()`]: crate::acceleration::Accel::from_raw_parts +[`Accel]: crate::acceleration::Accel +[`Instance`]: crate::instance_array::Instance [`TriangleArray`]: crate::triangle_array::TriangleArray [`CurveArray`]: crate::curve_array::CurveArray [`InstanceArray`]: crate::instance_array::InstanceArray [`MatrixMotionTransform`]: crate::transform::MatrixMotionTransform [`SrtMotionTransform`]: crate::transform::SrtMotionTransform - +[`BuildInput`]: crate::acceleration::BuildInput +[`TraversableHandle`]: crate::acceleration::TraversableHandle +[`accel_build()`]: crate::acceleration::accel_build +[`accel_compute_memory_usage()`]: crate::acceleration::accel_compute_memory_usage +[`accel_compact()`]: crate::acceleration::accel_compact +[`GeometryFlags`]: crate::acceleration::GeometryFlags +[`BuildFlags`]: crate::acceleration::BuildFlags +[`DynamicAccel`]: crate::acceleration::DynamicAccel +[`DynamicAccel::build()`]: crate::acceleration::DynamicAccel::build +[`DynamicAccel::update()`]: crate::acceleration::DynamicAccel::update +[`AccelBuildOptions`]: crate::acceleration::AccelBuildOptions +[`convert_pointer_to_traversable_handle`]: crate::acceleration::convert_pointer_to_traversable_handle +[`MotionOptions`]: crate::acceleration::MotionOptions diff --git a/crates/optix/src/context.md b/crates/optix/src/context.md index 0645dc4e..01fbdb1d 100644 --- a/crates/optix/src/context.md +++ b/crates/optix/src/context.md @@ -1,4 +1,8 @@ -OptiX Device Context handling. +# OptiX Device Context handling. + +# Programming Guide... +
      +Click here to expand programming guide A context is created by [`DeviceContext::new()`] and is used to manage a single GPU. The NVIDIA OptiX 7 device context is created by specifying the CUDA @@ -135,7 +139,7 @@ Negative and non-integer values will be ignored. function. The low water mark will be set to half the value of `OPTIX_CACHE_MAXSIZE`. -Corresponding get* functions are supplied to retrieve the current value of these +Corresponding `get_xxx()` functions are supplied to retrieve the current value of these cache properties. ## Validation Mode @@ -159,3 +163,5 @@ non-user exception caught inside an exception program will therefore be reported and the launch terminated immediately. This will make exceptions more visible that otherwise might be overlooked. +
      + diff --git a/crates/optix/src/introduction.md b/crates/optix/src/introduction.md index 9f7059e0..8f3bff92 100644 --- a/crates/optix/src/introduction.md +++ b/crates/optix/src/introduction.md @@ -29,10 +29,10 @@ structures that are worth memorizing as they crop up a lot. * **`CH`** - Closest-hit - Run only for the closest hit found during ray traversal. Can inspect and interpolating properties of the intersected primitive. -* **`MS`** - Miss - Run whenever a ray ezits the scene without hitting anything. -* **`EX`** - Ezception - Run whenever an exception condition is found. +* **`MS`** - Miss - Run whenever a ray exits the scene without hitting anything. +* **`EX`** - Exception - Run whenever an exception condition is found. * **`DC`** - Direct callable - Can be called manually from another program. - May not itself continue ray traversal (i.e. may not call `optizTrace`). + May not itself continue ray traversal (i.e. may not call `optixTrace`). * **`CC`** - Continuation callable - Can be called manually from another program and may continue ray traversal. @@ -41,32 +41,32 @@ structures that are worth memorizing as they crop up a lot. acceleration structure built over geometric primitives such as curves * instance-AS/IAS/TLAS - Instance/Top-level acceleration structure built over other acceleration structures and/or transform nodes in order to - compose more complez scenes and implement instancing and rigid + compose more complex scenes and implement instancing and rigid transformations. In this document and in the names of API elements, the “host” is the processor -that begins ezecution of an application. The “device” is the GPU with which +that begins execution of an application. The “device” is the GPU with which the host interacts. A “build” is the creation of an acceleration structure on the device as initiated by the host. ## Overview The NVIDIA OptiX 7 API is a CUDA-centric API that is invoked by a CUDA-based application. The API is designed to be stateless, multi-threaded and -asynchronous, providing ezplicit control over performance-sensitive operations +asynchronous, providing explicit control over performance-sensitive operations like memory management and shader compilation. It supports a lightweight representation for scenes that can represent -instancing, vertez- and transform-based motion blur, with built-in triangles, +instancing, vertex- and transform-based motion blur, with built-in triangles, built-in swept curves, and user-defined primitives. The API also includes highly-tuned kernels and neural networks for machine-learning-based denoising. -An NVIDIA OptiX 7 contezt controls a single GPU. The context does not hold bulk +An NVIDIA OptiX 7 context controls a single GPU. The context does not hold bulk CPU allocations, but like CUDA, may allocate resources on the device necessary to invoke the launch. It can hold a small number of handle objects that are used -to manage ezpensive host-based state. These handle objects are automatically -released when the contezt is destroyed. Handle objects, where they do exist, +to manage expensive host-based state. These handle objects are automatically +released when the context is destroyed. Handle objects, where they do exist, consume a small amount of host memory (typically less than 100 kilobytes) and -are independent of the size of the GPU resources being used. For ezceptions to +are independent of the size of the GPU resources being used. For exceptions to this rule, see “Program pipeline creation”. The application invokes the creation of acceleration structures (called builds), @@ -80,26 +80,26 @@ Multi-GPU features such as efficient load balancing or the sharing of GPU memory via NVLINK must be handled by the application developer. For efficiency and coherence, the NVIDIA OptiX 7 runtime—unlike CUDA kernels— -allows the ezecution of one task, such as a single ray, to be moved at any point +allows the execution of one task, such as a single ray, to be moved at any point in time to a different lane, warp or streaming multiprocessor (SM). (See section “Kernel Focus” in the CUDA Toolkit Documentation.) Consequently, applications cannot use shared memory, synchronization, barriers, or other SM-thread-specific programming constructs in their programs supplied to OptiX. The NVIDIA OptiX 7 programming model provides an API that future-proofs -applications: as new NVIDIA hardware features are released, ezisting programs -can use them. For ezample, software-based ray tracing algorithms can be mapped +applications: as new NVIDIA hardware features are released, existing programs +can use them. For example, software-based ray tracing algorithms can be mapped to hardware when support is added or mapped to software when the underlying algorithms or hardware support such changes. ## Basic concepts and definitions ### Program -In NVIDIA OptiX 7, a program is a block of ezecutable code on the GPU that +In NVIDIA OptiX 7, a program is a block of executable code on the GPU that represents a particular shading operation. This is called a shader in DXR and Vulkan. For consistency with prior versions of NVIDIA OptiX 7, the term program is used in the current documentation. This term also serves as a reminder that -these blocks of ezecutable code are programmable components in the system that +these blocks of executable code are programmable components in the system that can do more than shading. See “Program input”. ## Program and Data Model @@ -111,7 +111,7 @@ of programs: ### Ray generation (RG) The entry point into the ray tracing pipeline, invoked by the system in parallel -for each pizel, sample, or other user-defined work assignment. See +for each pixel, sample, or other user-defined work assignment. See “Ray generation launches”. ### Intersection (IS) @@ -130,16 +130,16 @@ material shading. See “Constructing a path tracer”. Called when a traced ray misses all scene geometry. See “Constructing a path tracer”. -### Ezception -Ezception handler, invoked for conditions such as stack overflow and other errors. -See “Ezceptions”. +### Exception +Exception handler, invoked for conditions such as stack overflow and other errors. +See “Exceptions”. ### Direct callables Similar to a regular CUDA function call, direct callables are called immediately. See “Callables”. ### Continuation callables -Unlike direct callables, continuation callables are ezecuted by the scheduler. +Unlike direct callables, continuation callables are executed by the scheduler. See “Callables”. The ray-tracing “pipeline” is based on the interconnected calling structure of @@ -152,21 +152,21 @@ relationships: ### Shader Binding Table The shader binding table connects geometric data to programs and their parameters. A record is a component of the shader binding table that is selected -during ezecution by using offsets specified when acceleration structures are +during execution by using offsets specified when acceleration structures are created and at runtime. A record contains two data regions, header and data. SBT record packing is handled automatically by using the [`SbtRecord`](shader_binding_table::SbtRecord) generic struct: ```no_run use cust::prelude as cu; -use optiz::prelude as ox; +use optix::prelude as ox; #[derive(Copy, Clone, Default, cu::DeviceCopy)] struct HitgroupSbtData { object_id: u32, } -type HitgroupRecord = oz::SbtRecord; +type HitgroupRecord = ox::SbtRecord; let rec_hitgroup: Vec<_> = (0..num_objects) .map(|i| { let object_type = 0; @@ -181,9 +181,9 @@ let rec_hitgroup: Vec<_> = (0..num_objects) ``` ### Ray payload -The ray payload is used to pass data between `optizTrace` and the programs +The ray payload is used to pass data between `optixTrace` and the programs invoked during ray traversal. Payload values are passed to and returned from -`optizTrace`, and follow a copy-in/copy-out semantic. There is a limited number +`optixTrace`, and follow a copy-in/copy-out semantic. There is a limited number of payload values, but one or more of these values can also be a pointer to stack-based local memory, or application-managed global memory. @@ -198,7 +198,7 @@ primitives. NVIDIA OptiX 7 represents GPU information with a pointer to GPU memory. References to the term “buffer” in this document refer to this GPU memory pointer and the associated memory contents. Unlike NVIDIA OptiX 6, the allocation and -transfer of buffers is ezplicitly controlled by user code. +transfer of buffers is explicitly controlled by user code. ## Acceleration Stutures NVIDIA OptiX 7 acceleration structures are opaque data structures built on the @@ -220,14 +220,14 @@ a graph of nodes composed of acceleration structures and transformations. This search is called a traversal; the nodes in the graph are called traversable objects or traversables. -The following types of traversable objects ezist: +The following types of traversable objects exist: * An instance acceleration structure * A geometry acceleration structure (as a root for graph with a single geometry acceleration structure (see “Traversal of a single geometry acceleration structure”) * Static transform -* Matriz motion transform +* Matrix motion transform * Scaling, rotation, translation (SRT) motion transform For transformation traversables, the corresponding transformation applies to @@ -239,14 +239,14 @@ available as they cannot be merged with any motion transformation due to time-dependency, but should be merged with instance transformations (if desired as the child of an instance) or any other static transformation (i.e., there should be at most one static transformation following a motion transformation). -For ezample, Figure 2.2 combines both types: +For example, Figure 2.2 combines both types: ![Figure 2.2 - Traversables graph][traversables_graph] OptiX uses handles as references to traversable objects. These traversable handles are 64-bit opaque values that are generated from device memory pointers for the graph nodes. The handles identify the connectivity of these objects. -All calls to `optizTrace` begin at a traversable handle. +All calls to `optixTrace` begin at a traversable handle. ## Ray tracing with NVIDIA OptiX 7 @@ -263,7 +263,7 @@ described in the following steps: binding table record selection of the instances and geometries in the acceleration structures. See “Shader binding table”. 4. Launch a device-side kernel that will invoke a ray generation program with a - multitude of threads calling optizTrace to begin traversal and the execution + multitude of threads calling optixTrace to begin traversal and the execution of the other programs. See “Ray generation launches”. Device-side functionality is described in “Device-side functions”. @@ -296,10 +296,10 @@ they are called. Applications can expect the [CUDA Context](cust::context::Context) to remain the same after invoking NVIDIA OptiX 7 functions. -## Asynchronous Ezecution +## Asynchronous Execution Work performed on the device is issued on an application-supplied [CUDA Stream](cust::stream::Stream) using asynchronous CUDA methods. The host -function blocks ezecution until all work has been issued on the stream, but does +function blocks execution until all work has been issued on the stream, but does not do any synchronization or blocking on the stream itself. ## Function Table initialization diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index dd1c16fd..3967ead9 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -10,9 +10,25 @@ //! //! Rust bindings for NVIDIA's OptiX GPU raytracing library. //! -//! For introductory documentation to the high-level concepts please see the -//! [introduction](crate::introduction) module documentation. Additional -//! high-level documentation is available in the individual modules: +//! NVIDIA OptiX 7 is intended for ray tracing applications that use NVIDIA® CUDA® +//! technology, such as: +//! +//! * Film and television visual effects +//! * Computer-aided design for engineering and manufacturing +//! * Light maps generated by path tracing +//! * High-performance computing +//! * LIDAR simulation +//! +//! NVIDIA OptiX 7 also includes support for motion blur and multi-level transforms, +//! features required by ray-tracing applications designed for production-quality +//! rendering. +//! +//! # Programming Guide +//! +//! For high-level documentation please see the +//! [introduction](crate::introduction) module documentation and subsequent documentation in the +//! modules listed below. Each module has an expandable "Programming Guide" section that will +//! display the docs when clicked. //! //! * [1. Introduction](introduction) //! * [2. Context](context) @@ -20,6 +36,7 @@ //! * [4. Program Pipeline Creation](pipeline) //! * [5. Shader Binding Table](shader_binding_table) //! * [6. Ray Generation Launches](launch) +//! #[doc = ::embed_doc_image::embed_image!("optix_programs", "images/optix_programs.jpg")] #[doc = ::embed_doc_image::embed_image!("traversables_graph", "images/traversables_graph.jpg")] @@ -75,6 +92,7 @@ fn init_cold() -> Result<()> { /// Whether OptiX is initialized. If you are calling raw [`sys`] functions you must make sure /// this is true, otherwise OptiX will segfault. In the safe wrapper it is done automatically and optix not /// being initialized will return an error result. +#[doc(hidden)] pub fn optix_is_initialized() -> bool { // SAFETY: C globals are explicitly defined to be zero-initialized, and the sys version uses // Option for each field, and None is explicitly defined to be represented as a nullptr for Option, @@ -89,6 +107,7 @@ extern "C" { /// Call a raw OptiX sys function, making sure that OptiX is initialized. Returning /// an OptixNotInitialized error if it is not initialized. See [`optix_is_initialized`]. +#[doc(hidden)] #[macro_export] macro_rules! optix_call { ($name:ident($($param:expr),* $(,)?)) => {{ diff --git a/crates/optix/src/pipeline.md b/crates/optix/src/pipeline.md index fb45c4d0..8175bc65 100644 --- a/crates/optix/src/pipeline.md +++ b/crates/optix/src/pipeline.md @@ -1,6 +1,11 @@ # Program Pipeline Creation +# Programming Guide... +
      +Click here to expand programming guide + +# Contents - [Program Input](#program-input) - [Programming Model](#programming-model) - [Module Creation](#module-creation) @@ -275,6 +280,8 @@ Compilation work is triggered automatically when calling [`Module::new()`](crate Generally, cache entries are compatible with the same driver version and GPU type only. +
      + [`DeviceContext`]: crate::context::DeviceContext; [`Module`]: crate::pipeline::Module [`ProgramGroup`]: crate::pipeline::ProgramGroup diff --git a/crates/optix/src/shader_binding_table.md b/crates/optix/src/shader_binding_table.md index bea007a8..7818e5cb 100644 --- a/crates/optix/src/shader_binding_table.md +++ b/crates/optix/src/shader_binding_table.md @@ -1,5 +1,11 @@ # Shader Binding Table +# Programming Guide... +
      +Click here to expand programming guide + +# Contents + - [Records](#records) - [Layout](#layout) - [Acceleration Structures](#acceleration-structures) @@ -230,6 +236,8 @@ CHData* material_info = (CHData*)optixGetSbtDataPointer(); ``` The program is encouraged to rely on the alignment constraints of the SBT data section to read this data efficiently. +
      + [`Instance`]: crate::acceleration::Instance [`SbtRecord`]: crate::shader_binding_table::SbtRecord From 7ce2381c590016386bacd5823e0f6d35923ff865 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 14:21:27 +1300 Subject: [PATCH 070/100] Adapt to latest changes --- .../optix/examples/ex04_mesh/src/renderer.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 56d69f91..87843019 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -4,6 +4,7 @@ use cust::device::Device; use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; +use optix::acceleration::accel_compact; use optix::{ acceleration::IndexedTriangleArray, acceleration::{ @@ -114,16 +115,16 @@ impl Renderer { let buf_indices = DeviceBuffer::from_slice(&indices)?; let geometry_flags = GeometryFlags::None; - let build_inputs = [IndexedTriangleArray::new( - &[&buf_vertex], - &buf_indices, - &[geometry_flags], - )]; - let accel_options = - AccelBuildOptions::new(BuildFlags::ALLOW_COMPACTION, BuildOperation::Build); - + let build_inputs = + IndexedTriangleArray::new(&[&buf_vertex], &buf_indices, &[geometry_flags]); // build and compact the GAS - let gas = Accel::build(&ctx, &stream, &[accel_options], &build_inputs, true)?; + let gas = Accel::build( + &ctx, + &stream, + &[AccelBuildOptions::new(BuildFlags::PREFER_FAST_TRACE)], + &[build_inputs], + true, + )?; stream.synchronize()?; From 23fbe9a6583efc5a64b2c506b89e2bc466936de4 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 20:34:16 +1300 Subject: [PATCH 071/100] Fix toolchain version --- rust-toolchain | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rust-toolchain b/rust-toolchain index ad2673be..c5a8b847 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,5 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "nightly-2021-10-17" -components = ["rust-src", "rustc-dev"] \ No newline at end of file +channel = "nightly-2021-10-18" +components = ["rust-src", "rustc-dev"] + From 67106f419f9a617704c97e8ffec8faf6f12e7dfe Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 20:34:41 +1300 Subject: [PATCH 072/100] Fix name of DeviceContext --- examples/optix/denoiser/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/optix/denoiser/src/main.rs b/examples/optix/denoiser/src/main.rs index 23b75c9b..8db84227 100644 --- a/examples/optix/denoiser/src/main.rs +++ b/examples/optix/denoiser/src/main.rs @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { // set up CUDA and OptiX then make the needed structs/contexts. let cuda_ctx = cust::quick_init()?; optix::init()?; - let optix_ctx = DeviceContext::new(&cuda_ctx)?; + let optix_ctx = DeviceContext::new(&cuda_ctx, false)?; let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?; From be5fd90acb5c3d592ef318510f36499a65240d6f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 21:44:00 +1300 Subject: [PATCH 073/100] first optix rust test --- Cargo.toml | 2 ++ crates/cuda_builder/src/lib.rs | 2 +- .../optix/examples/ex02_pipeline/Cargo.toml | 1 + crates/optix/examples/ex02_pipeline/build.rs | 9 +++++ .../examples/ex02_pipeline/device/Cargo.toml | 13 +++++++ .../examples/ex02_pipeline/device/src/lib.rs | 35 +++++++++++++++++++ .../examples/ex02_pipeline/src/renderer.rs | 3 +- 7 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 crates/optix/examples/ex02_pipeline/device/Cargo.toml create mode 100644 crates/optix/examples/ex02_pipeline/device/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 8bfc12cb..90757d10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [workspace] members = [ "crates/*", + "crates/cuda_test/kernels", "crates/optix/examples/ex*", + "crates/optix/examples/ex*/device", "xtask", "examples/optix/*" ] diff --git a/crates/cuda_builder/src/lib.rs b/crates/cuda_builder/src/lib.rs index c0695141..3321551f 100644 --- a/crates/cuda_builder/src/lib.rs +++ b/crates/cuda_builder/src/lib.rs @@ -1,6 +1,6 @@ //! Utility crate for easily building CUDA crates using rustc_codegen_nvvm. Derived from rust-gpu's spirv_builder. -use nvvm::NvvmArch; +pub use nvvm::NvvmArch; use serde::Deserialize; use std::{ borrow::Borrow, diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml index 189cb991..f7f75beb 100644 --- a/crates/optix/examples/ex02_pipeline/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -12,3 +12,4 @@ anyhow = "1.0.44" [build-dependencies] find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } +cuda_builder = { version = "0.1", path = "../../../cuda_builder" } diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs index 64d4aca8..eed0d676 100644 --- a/crates/optix/examples/ex02_pipeline/build.rs +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -1,3 +1,4 @@ +use cuda_builder::CudaBuilder; use find_cuda_helper::{find_cuda_root, find_optix_root}; fn main() { @@ -22,6 +23,14 @@ fn main() { ]; compile_to_ptx("src/ex02_pipeline.cu", &args); + + let ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("device.ptx"); + + CudaBuilder::new("./device") + .copy_to(ptx_path) + .arch(cuda_builder::NvvmArch::Compute75) + .build() + .unwrap(); } fn compile_to_ptx(cu_path: &str, args: &[String]) { diff --git a/crates/optix/examples/ex02_pipeline/device/Cargo.toml b/crates/optix/examples/ex02_pipeline/device/Cargo.toml new file mode 100644 index 00000000..f23ec34e --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/device/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "device" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cuda_std = { version = "0.1", path = "../../../../cuda_std" } + +[lib] +crate-type = ["cdylib"] + diff --git a/crates/optix/examples/ex02_pipeline/device/src/lib.rs b/crates/optix/examples/ex02_pipeline/device/src/lib.rs new file mode 100644 index 00000000..c5635bac --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/device/src/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr( + target_arch = "nvptx64", + no_std, + feature(register_attr), + register_attr(nvvm_internal) +)] +#![deny(warnings)] + +use cuda_std::*; + +/* +#[repr(C)] +struct LaunchParams { + frame_id: i32, + color_buffer: *mut u32, + fb_size: [i32; 2], +} +*/ + +#[kernel] +pub unsafe fn __closesthit__radiance() {} + +#[kernel] +pub unsafe fn __anyhit__radiance() {} + +#[kernel] +pub unsafe fn __miss__radiance() {} + +#[kernel] +pub unsafe fn __raygen__renderFrame() { + //crate::println!("Hello from Rust kernel!"); +} + +// #[kernel] +// pub unsafe fn render(fb: *mut Vec3, view: &Viewport) {} diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index d6781bc3..23b37963 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -61,7 +61,8 @@ impl Renderer { .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) .exception_flags(ExceptionFlags::NONE); - let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); + // let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); + let ptx = include_str!(concat!(env!("OUT_DIR"), "/device.ptx")); let (module, _log) = Module::new( &mut ctx, From 0ef56c7cb7f35bc12a1e5e5389847c746935bb43 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Mon, 8 Nov 2021 13:47:34 +1300 Subject: [PATCH 074/100] Set ALLOW_COMPACTION in build options --- crates/optix/examples/ex04_mesh/src/renderer.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 87843019..648dbe84 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -117,11 +117,14 @@ impl Renderer { let geometry_flags = GeometryFlags::None; let build_inputs = IndexedTriangleArray::new(&[&buf_vertex], &buf_indices, &[geometry_flags]); + + let accel_options = AccelBuildOptions::new(BuildFlags::PREFER_FAST_TRACE | BuildFlags::ALLOW_COMPACTION); + // build and compact the GAS let gas = Accel::build( &ctx, &stream, - &[AccelBuildOptions::new(BuildFlags::PREFER_FAST_TRACE)], + &[accel_options], &[build_inputs], true, )?; From f1f29cb3323f18a781fb96fd11f15073d978052f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Mon, 8 Nov 2021 13:48:04 +1300 Subject: [PATCH 075/100] Use find_cuda_helper to get cuda path --- crates/nvvm/Cargo.toml | 3 +++ crates/nvvm/build.rs | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/nvvm/Cargo.toml b/crates/nvvm/Cargo.toml index 1a5bf5db..a222ed3e 100644 --- a/crates/nvvm/Cargo.toml +++ b/crates/nvvm/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" authors = ["Riccardo D'Ambrosio "] edition = "2018" license = "MIT OR Apache-2.0" + +[build-dependencies] +find_cuda_helper = { path = "../find_cuda_helper" } \ No newline at end of file diff --git a/crates/nvvm/build.rs b/crates/nvvm/build.rs index 9690211d..817fdd30 100644 --- a/crates/nvvm/build.rs +++ b/crates/nvvm/build.rs @@ -13,7 +13,16 @@ fn libnvvm_build() { // on windows, libnvvm should be in CUDA_PATH/nvvm/ // println!("cargo:rustc-link-lib=dylib=../../../Program Files/NVIDIA GPU // Computing Toolkit/CUDA/v11.3/nvvm/bin/nvvm64_40_0"); - println!("cargo:rustc-link-search={}", lib_search_path()); + + let cuda_root = find_cuda_helper::find_cuda_root().expect("Could not figure out cuda installation root"); + + #[cfg(windows)] + let lib_search_path = std::path::Path::new(&cuda_root).join("nvvm").join("lib").join("x64"); + + #[cfg(unix)] + let lib_search_path = std::path::Path::new(&cuda_root).join("nvvm").join("lib64"); + + println!("cargo:rustc-link-search={}", lib_search_path.display()); println!("cargo:rustc-link-lib=dylib=nvvm"); } From b70ef0a29def01ad0bc68019e27bc66ebb55f065 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Mon, 8 Nov 2021 13:49:28 +1300 Subject: [PATCH 076/100] Handle differering enum representation on windows and linux --- crates/optix/src/acceleration.rs | 34 +++++++++++++++++++++----------- crates/optix/src/pipeline.rs | 29 +++++++++++++++++---------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index 0e303cef..91365208 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -18,6 +18,14 @@ use std::mem::size_of; use cust_raw::CUdeviceptr; use mint::{RowMatrix3x4, Vector3}; +// Kinda nasty hack to work around the fact taht bindgen generates an i32 for enums on windows, +// but a u32 on linux +#[cfg(windows)] +type OptixEnumBaseType = i32; +#[cfg(unix)] +type OptixEnumBaseType = u32; + + pub trait BuildInput: std::hash::Hash { fn to_sys(&self) -> sys::OptixBuildInput; @@ -681,7 +689,7 @@ bitflags::bitflags! { /// /// Note that `PREFER_FAST_TRACE` and `PREFER_FAST_BUILD` are mutually exclusive. #[derive(Default)] - pub struct BuildFlags: u32 { + pub struct BuildFlags: OptixEnumBaseType { const NONE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_NONE; const ALLOW_UPDATE = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_UPDATE; const ALLOW_COMPACTION = sys::OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_COMPACTION; @@ -692,7 +700,8 @@ bitflags::bitflags! { } /// Select which operation to perform with [`accel_build()`]. -#[repr(u32)] +#[cfg_attr(windows, repr(i32))] +#[cfg_attr(unix, repr(u32))] #[derive(Debug, Copy, Clone, PartialEq)] pub enum BuildOperation { Build = sys::OptixBuildOperation_OPTIX_BUILD_OPERATION_BUILD, @@ -1130,16 +1139,18 @@ pub enum VertexFormat { } /// Specifies the type of index data -#[repr(u32)] +#[cfg_attr(windows, repr(i32))] +#[cfg_attr(unix, repr(u32))] #[derive(Copy, Clone, PartialEq)] pub enum IndicesFormat { - None = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE as u32, - Short3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3 as u32, - Int3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3 as u32, + None = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE, + Short3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3, + Int3 = sys::OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3, } /// Specifies the format of transform data -#[repr(u32)] +#[cfg_attr(windows, repr(i32))] +#[cfg_attr(unix, repr(u32))] #[derive(Copy, Clone, PartialEq)] pub enum TransformFormat { None = sys::OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE, @@ -1291,7 +1302,7 @@ impl<'v, 'g, V: Vertex> BuildInput for TriangleArray<'v, 'g, V> { triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, numVertices: self.num_vertices, - vertexFormat: V::FORMAT as u32, + vertexFormat: V::FORMAT as _, vertexStrideInBytes: V::STRIDE, indexBuffer: 0, numIndexTriplets: 0, @@ -1372,11 +1383,11 @@ impl<'v, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, triangle_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputTriangleArray { vertexBuffers: self.d_vertex_buffers.as_ptr() as *const u64, numVertices: self.num_vertices, - vertexFormat: V::FORMAT as u32, + vertexFormat: V::FORMAT as _, vertexStrideInBytes: V::STRIDE, indexBuffer: self.index_buffer.as_device_ptr(), numIndexTriplets: self.index_buffer.len() as u32, - indexFormat: I::FORMAT as u32, + indexFormat: I::FORMAT as _, indexStrideInBytes: I::STRIDE, flags: self.geometry_flags.as_ptr() as *const _, numSbtRecords: 1, @@ -1524,10 +1535,9 @@ pub struct Instance<'a> { const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - bitflags::bitflags! { #[derive(DeviceCopy)] - pub struct InstanceFlags: u32 { + pub struct InstanceFlags: OptixEnumBaseType { const NONE = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_NONE; const DISABLE_TRIANGLE_FACE_CULLING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRIANGLE_FACE_CULLING; const FLIP_TRIANGLE_FACING = sys::OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING; diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 14d4f243..6cfa64e3 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -3,6 +3,13 @@ type Result = std::result::Result; use std::ffi::{CStr, CString}; +// Kinda nasty hack to work around the fact taht bindgen generates an i32 for enums on windows, +// but a u32 on linux +#[cfg(windows)] +type OptixEnumBaseType = i32; +#[cfg(unix)] +type OptixEnumBaseType = u32; + #[repr(transparent)] pub struct Pipeline { pub(crate) raw: sys::OptixPipeline, @@ -19,7 +26,7 @@ impl From for sys::OptixPipelineLinkOptions { fn from(o: PipelineLinkOptions) -> Self { sys::OptixPipelineLinkOptions { maxTraceDepth: o.max_trace_depth, - debugLevel: o.debug_level as u32, + debugLevel: o.debug_level as _, } } } @@ -129,7 +136,8 @@ pub struct Module { } /// Module compilation optimization level -#[repr(u32)] +#[cfg_attr(windows, repr(i32))] +#[cfg_attr(unix, repr(u32))] #[derive(Debug, Hash, PartialEq, Copy, Clone)] pub enum CompileOptimizationLevel { Default = sys::OptixCompileOptimizationLevel::OPTIX_COMPILE_OPTIMIZATION_DEFAULT, @@ -146,7 +154,8 @@ impl Default for CompileOptimizationLevel { } /// Module compilation debug level -#[repr(u32)] +#[cfg_attr(windows, repr(i32))] +#[cfg_attr(unix, repr(u32))] #[derive(Debug, Hash, PartialEq, Copy, Clone)] pub enum CompileDebugLevel { None = sys::OptixCompileDebugLevel::OPTIX_COMPILE_DEBUG_LEVEL_NONE, @@ -174,8 +183,8 @@ cfg_if::cfg_if! { fn from(o: &ModuleCompileOptions) -> sys::OptixModuleCompileOptions { sys::OptixModuleCompileOptions { maxRegisterCount: o.max_register_count, - optLevel: o.opt_level as u32, - debugLevel: o.debug_level as u32, + optLevel: o.opt_level as _, + debugLevel: o.debug_level as _, boundValues: std::ptr::null(), numBoundValues: 0, } @@ -204,7 +213,7 @@ cfg_if::cfg_if! { bitflags::bitflags! { #[derive(Default)] - pub struct TraversableGraphFlags: u32 { + pub struct TraversableGraphFlags: OptixEnumBaseType { const ALLOW_ANY = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY; const ALLOW_SINGLE_GAS = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS; const ALLOW_SINGLE_LEVEL_INSTANCING = sys::OptixTraversableGraphFlags::OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING; @@ -213,7 +222,7 @@ bitflags::bitflags! { bitflags::bitflags! { #[derive(Default)] - pub struct ExceptionFlags: u32 { + pub struct ExceptionFlags: OptixEnumBaseType { const NONE = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_NONE; const STACK_OVERFLOW = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW; const TRACE_DEPTH = sys::OptixExceptionFlags::OPTIX_EXCEPTION_FLAG_TRACE_DEPTH; @@ -273,10 +282,10 @@ impl PipelineCompileOptions { if #[cfg(feature="optix73")] { sys::OptixPipelineCompileOptions { usesMotionBlur: if self.uses_motion_blur { 1 } else { 0 }, - traversableGraphFlags: self.traversable_graph_flags.bits(), + traversableGraphFlags: self.traversable_graph_flags.bits() as _, numPayloadValues: self.num_payload_values, numAttributeValues: self.num_attribute_values, - exceptionFlags: self.exception_flags.bits(), + exceptionFlags: self.exception_flags.bits() as _, pipelineLaunchParamsVariableName: if let Some(ref name) = self .pipeline_launch_params_variable_name { name.as_ptr() @@ -391,7 +400,7 @@ impl Module { uses_motion_blur: bool, ) -> Result { let is_options = sys::OptixBuiltinISOptions { - builtinISModuleType: builtin_is_module_type as u32, + builtinISModuleType: builtin_is_module_type as _, usesMotionBlur: if uses_motion_blur { 1 } else { 0 }, }; From bb9c61e03e171c793b4ce7753811ddf9be03342f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:37:05 +1300 Subject: [PATCH 077/100] Add DeviceVariable --- .../cust/src/memory/device/device_variable.rs | 51 +++++++++++++++++++ crates/cust/src/memory/device/mod.rs | 2 + 2 files changed, 53 insertions(+) create mode 100644 crates/cust/src/memory/device/device_variable.rs diff --git a/crates/cust/src/memory/device/device_variable.rs b/crates/cust/src/memory/device/device_variable.rs new file mode 100644 index 00000000..14eb52bf --- /dev/null +++ b/crates/cust/src/memory/device/device_variable.rs @@ -0,0 +1,51 @@ +use crate::error::CudaResult; +use crate::memory::device::CopyDestination; +use crate::memory::DeviceCopy; +use crate::memory::{DeviceBox, DevicePointer}; +use std::ops::{Deref, DerefMut}; + +/// Wrapper around a variable on the host and a [`DeviceBox`] holding the +/// variable on the device, allowing for easy synchronization and storage. +#[derive(Debug)] +pub struct DeviceVariable { + mem: DeviceBox, + var: T, +} + +impl DeviceVariable { + /// Create a new `DeviceVariable` wrapping `var`. + /// + /// Allocates storage on the device and copies `var` to the device. + pub fn new(var: T) -> CudaResult { + let mem = DeviceBox::new(&var)?; + Ok(Self { mem, var }) + } + + /// Copy the host copy of the variable to the device + pub fn copy_htod(&mut self) -> CudaResult<()> { + self.mem.copy_from(&self.var) + } + + /// Copy the device copy of the variable to the host + pub fn copy_dtoh(&mut self) -> CudaResult<()> { + self.mem.copy_to(&mut self.var) + } + + pub fn as_device_ptr(&self) -> DevicePointer { + self.mem.as_device_ptr() + } +} + +impl Deref for DeviceVariable { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.var + } +} + +impl DerefMut for DeviceVariable { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.var + } +} diff --git a/crates/cust/src/memory/device/mod.rs b/crates/cust/src/memory/device/mod.rs index fcb7b97e..0ffe2665 100644 --- a/crates/cust/src/memory/device/mod.rs +++ b/crates/cust/src/memory/device/mod.rs @@ -4,10 +4,12 @@ use crate::stream::Stream; mod device_box; mod device_buffer; mod device_slice; +mod device_variable; pub use self::device_box::*; pub use self::device_buffer::*; pub use self::device_slice::*; +pub use self::device_variable::*; /// Sealed trait implemented by types which can be the source or destination when copying data /// to/from the device or from one device allocation to another. From 4b855ee867d8a539322fd25b9e6989ff906fcf81 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:37:45 +1300 Subject: [PATCH 078/100] Add DeviceMemory trait Abstracts over different device storage representations --- crates/cust/src/memory/mod.rs | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index a31e998e..33f7ce70 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -163,6 +163,56 @@ impl GpuBox for UnifiedBox { } } +/// A trait describing a region of memory on the device with a base pointer and +/// a size, used to be generic over DeviceBox, DeviceBuffer, DeviceVariable etc. +pub trait DeviceMemory { + /// Get the raw cuda device pointer + fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr; + + /// Get the size of the memory region in bytes + fn size_in_bytes(&self) -> usize; +} + +impl DeviceMemory for DeviceBox { + fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr { + self.as_device_ptr().as_raw() + } + + fn size_in_bytes(&self) -> usize { + std::mem::size_of::() + } +} + +impl DeviceMemory for DeviceVariable { + fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr { + self.as_device_ptr().as_raw() + } + + fn size_in_bytes(&self) -> usize { + std::mem::size_of::() + } +} + +impl DeviceMemory for DeviceBuffer { + fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr { + unsafe { self.as_device_ptr().as_raw() } + } + + fn size_in_bytes(&self) -> usize { + std::mem::size_of::() * self.len() + } +} + +impl DeviceMemory for DeviceSlice { + fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr { + self.as_device_ptr() + } + + fn size_in_bytes(&self) -> usize { + std::mem::size_of::() * self.len() + } +} + mod private { use super::{DeviceBox, DeviceBuffer, DeviceCopy, UnifiedBox, UnifiedBuffer}; From fd68e3abc7c8873623939d3c55864b48fb24882b Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:37:56 +1300 Subject: [PATCH 079/100] Add mem_get_info --- crates/cust/src/memory/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 33f7ce70..da796d84 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -394,3 +394,18 @@ pub unsafe fn memcpy_htod( crate::sys::cuMemcpyHtoD_v2(d_ptr, src_ptr, size).to_result()?; Ok(()) } + +/// Get the current free and total memory. +/// +/// Returns in `.1` the total amount of memory available to the the current context. +/// Returns in `.0` the amount of memory on the device that is free according to +/// the OS. CUDA is not guaranteed to be able to allocate all of the memory that +/// the OS reports as free. +pub fn mem_get_info() -> CudaResult<(usize, usize)> { + let mut mem_free = 0; + let mut mem_total = 0; + unsafe { + crate::sys::cuMemGetInfo_v2(&mut mem_free, &mut mem_total).to_result()?; + } + Ok((mem_free, mem_total)) +} From 8edb09ebb9b2ace8972f069c3ec9f991e14fe762 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:38:42 +1300 Subject: [PATCH 080/100] Add external memory --- crates/cust/src/external.rs | 64 +++++++++++++++++++++++++++++++++++++ crates/cust/src/lib.rs | 1 + 2 files changed, 65 insertions(+) create mode 100644 crates/cust/src/external.rs diff --git a/crates/cust/src/external.rs b/crates/cust/src/external.rs new file mode 100644 index 00000000..2af93ce4 --- /dev/null +++ b/crates/cust/src/external.rs @@ -0,0 +1,64 @@ +//! External memory and synchronization resources + +use crate::error::{CudaResult, ToResult}; +use crate::memory::{DeviceCopy, DevicePointer}; + +use cust_raw as sys; + +#[repr(transparent)] +pub struct ExternalMemory(sys::CUexternalMemory); + +impl ExternalMemory { + // Import an external memory referenced by `fd` with `size` + pub unsafe fn import(fd: i32, size: usize) -> CudaResult { + let desc = sys::CUDA_EXTERNAL_MEMORY_HANDLE_DESC { + type_: sys::CUexternalMemoryHandleType_enum::CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD, + handle: sys::CUDA_EXTERNAL_MEMORY_HANDLE_DESC_st__bindgen_ty_1 { fd }, + size: size as u64, + flags: 0, + reserved: Default::default(), + }; + + let mut memory: sys::CUexternalMemory = std::ptr::null_mut(); + + sys::cuImportExternalMemory(&mut memory, &desc) + .to_result() + .map(|_| ExternalMemory(memory)) + } + + pub unsafe fn reimport(&mut self, fd: i32, size: usize) -> CudaResult<()> { + // import new memory - this will call drop to destroy the old one + *self = ExternalMemory::import(fd, size)?; + + Ok(()) + } + + // Map a buffer from this memory with `size` and `offset` + pub fn mapped_buffer( + &self, + size_in_bytes: usize, + offset_in_bytes: usize, + ) -> CudaResult> { + let buffer_desc = sys::CUDA_EXTERNAL_MEMORY_BUFFER_DESC { + flags: 0, + size: size_in_bytes as u64, + offset: offset_in_bytes as u64, + reserved: Default::default(), + }; + + let mut dptr = 0; + unsafe { + sys::cuExternalMemoryGetMappedBuffer(&mut dptr, self.0, &buffer_desc) + .to_result() + .map(|_| DevicePointer::from_raw(dptr)) + } + } +} + +impl Drop for ExternalMemory { + fn drop(&mut self) { + unsafe { + sys::cuDestroyExternalMemory(self.0).to_result().unwrap(); + } + } +} diff --git a/crates/cust/src/lib.rs b/crates/cust/src/lib.rs index 0556ddcc..4ff79e62 100644 --- a/crates/cust/src/lib.rs +++ b/crates/cust/src/lib.rs @@ -58,6 +58,7 @@ pub mod context; pub mod device; pub mod error; pub mod event; +pub mod external; pub mod function; pub mod graph; pub mod link; From b456b60e78f79e50f04662342235760dcf98d76a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:40:20 +1300 Subject: [PATCH 081/100] Add a few more types to prelude --- crates/cust/src/prelude.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/cust/src/prelude.rs b/crates/cust/src/prelude.rs index 83579de2..559a55ba 100644 --- a/crates/cust/src/prelude.rs +++ b/crates/cust/src/prelude.rs @@ -5,8 +5,13 @@ pub use crate::context::{Context, ContextFlags}; pub use crate::device::Device; +pub use crate::event::{Event, EventFlags, EventStatus}; +pub use crate::external::*; +pub use crate::function::Function; pub use crate::launch; -pub use crate::memory::{CopyDestination, DeviceBuffer, UnifiedBuffer}; +pub use crate::memory::{ + CopyDestination, DeviceBuffer, DevicePointer, DeviceSlice, DeviceVariable, UnifiedBuffer, +}; pub use crate::module::Module; pub use crate::stream::{Stream, StreamFlags}; pub use crate::util::*; From 3e7e7f4e91c19f8f473978c9beac5da6dbb22923 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:40:34 +1300 Subject: [PATCH 082/100] Add more types --- crates/optix/src/prelude.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/optix/src/prelude.rs b/crates/optix/src/prelude.rs index 3d702f4d..d51f977c 100644 --- a/crates/optix/src/prelude.rs +++ b/crates/optix/src/prelude.rs @@ -1,18 +1,16 @@ pub use crate::{ - acceleration::CustomPrimitiveArray, acceleration::{ Aabb, Accel, AccelBufferSizes, AccelBuildOptions, AccelEmitDesc, AccelRelocationInfo, - BuildFlags, BuildOperation, DynamicAccel, GeometryFlags, + BuildFlags, BuildOperation, CurveArray, CurveType, CustomPrimitiveArray, DynamicAccel, + GeometryFlags, IndexTriple, IndexedTriangleArray, Instance, InstanceArray, InstanceFlags, + InstancePointerArray, TraversableHandle, TriangleArray, Vertex, }, - acceleration::{CurveArray, CurveType}, - acceleration::{IndexTriple, IndexedTriangleArray, TriangleArray, Vertex}, - acceleration::{Instance, InstanceArray, InstanceFlags, InstancePointerArray}, context::{DeviceContext, DeviceProperty}, init, launch, pipeline::{ - Module, ModuleCompileOptions, PipelineCompileOptions, PrimitiveType, PrimitiveTypeFlags, + CompileDebugLevel, CompileOptimizationLevel, ExceptionFlags, Module, ModuleCompileOptions, + Pipeline, PipelineCompileOptions, PipelineLinkOptions, PrimitiveType, PrimitiveTypeFlags, + ProgramGroup, ProgramGroupDesc, ProgramGroupModule, StackSizes, TraversableGraphFlags, }, - pipeline::{Pipeline, PipelineLinkOptions}, - pipeline::{ProgramGroup, ProgramGroupDesc, ProgramGroupModule, StackSizes}, shader_binding_table::{SbtRecord, ShaderBindingTable}, }; From c31850a5ffa76c2762cb3fb91f3fe460cc9ba8fe Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Thu, 11 Nov 2021 14:40:51 +1300 Subject: [PATCH 083/100] Rework on top of new DeviceVariable --- .../optix/examples/ex04_mesh/src/renderer.rs | 32 +++++------- crates/optix/src/lib.rs | 49 ++++++++++++++----- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 648dbe84..073ee1ce 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -1,15 +1,14 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::Device; -use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer, DeviceVariable}; use cust::stream::{Stream, StreamFlags}; use cust::{CudaFlags, DeviceCopy}; -use optix::acceleration::accel_compact; + use optix::{ acceleration::IndexedTriangleArray, acceleration::{ - Accel, AccelBuildOptions, BuildFlags, BuildOperation, GeometryFlags, Traversable, - TraversableHandle, + Accel, AccelBuildOptions, BuildFlags, GeometryFlags, Traversable, TraversableHandle, }, context::DeviceContext, pipeline::{ @@ -23,8 +22,7 @@ use optix::{ use glam::{ivec2, vec3, IVec2, IVec3, Vec3, Vec4}; pub struct Renderer { - launch_params: LaunchParams, - buf_launch_params: DeviceBox, + launch_params: DeviceVariable, sbt: ShaderBindingTable, gas: Accel, buf_raygen: DeviceBuffer, @@ -118,16 +116,11 @@ impl Renderer { let build_inputs = IndexedTriangleArray::new(&[&buf_vertex], &buf_indices, &[geometry_flags]); - let accel_options = AccelBuildOptions::new(BuildFlags::PREFER_FAST_TRACE | BuildFlags::ALLOW_COMPACTION); + let accel_options = + AccelBuildOptions::new(BuildFlags::PREFER_FAST_TRACE | BuildFlags::ALLOW_COMPACTION); // build and compact the GAS - let gas = Accel::build( - &ctx, - &stream, - &[accel_options], - &[build_inputs], - true, - )?; + let gas = Accel::build(&ctx, &stream, &[accel_options], &[build_inputs], true)?; stream.synchronize()?; @@ -196,7 +189,7 @@ impl Renderer { let horizontal = cosfovy * aspect * direction.cross(up).normalize(); let vertical = cosfovy * horizontal.cross(direction).normalize(); - let launch_params = LaunchParams { + let launch_params = DeviceVariable::new(LaunchParams { frame: Frame { color_buffer: color_buffer.as_ptr(), size: ivec2(width as i32, height as i32), @@ -208,9 +201,7 @@ impl Renderer { vertical, }, traversable: gas.handle(), - }; - - let buf_launch_params = DeviceBox::new(&launch_params)?; + })?; Ok(Renderer { ctx, @@ -218,7 +209,6 @@ impl Renderer { stream, launch_params, gas, - buf_launch_params, buf_raygen, buf_hitgroup, buf_miss, @@ -237,13 +227,13 @@ impl Renderer { } pub fn render(&mut self) -> Result<(), Box> { - self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.copy_htod()?; unsafe { optix::launch( &self.pipeline, &self.stream, - self.buf_launch_params.as_device_ptr(), + &self.launch_params, &self.sbt, self.launch_params.frame.size.x as u32, self.launch_params.frame.size.y as u32, diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index 3967ead9..af1fffc0 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -68,7 +68,7 @@ use shader_binding_table::ShaderBindingTable; pub mod sys; pub use cust; -use cust::memory::DevicePointer; +use cust::memory::{DeviceMemory, DevicePointer}; use error::{Error, ToResult}; type Result = std::result::Result; @@ -121,31 +121,54 @@ macro_rules! optix_call { /// Launch the given [`Pipeline`](pipeline::Pipeline) on the given [`Stream`](cust::stream::Stream). /// -/// A ray generation launch is the primary workhorse of the NVIDIA OptiX API. A launch invokes a 1D, 2D or 3D array of threads on the device and invokes ray generation programs for each thread. When the ray generation program invokes optixTrace, other programs are invoked to execute traversal, intersection, any-hit, closest-hit, miss and exception programs until the invocations are complete. +/// A ray generation launch is the primary workhorse of the NVIDIA OptiX API. A +/// launch invokes a 1D, 2D or 3D array of threads on the device and invokes ray +/// generation programs for each thread. When the ray generation program invokes +/// `optixTrace`, other programs are invoked to execute traversal, intersection, +/// any-hit, closest-hit, miss and exception programs until the invocations are +/// complete. /// -/// A pipeline requires device-side memory for each launch. This space is allocated and managed by the API. Because launch resources may be shared between pipelines, they are only guaranteed to be freed when the [`DeviceContext`] is destroyed. +/// A pipeline requires device-side memory for each launch. This space is allocated +/// and managed by the API. Because launch resources may be shared between pipelines, +/// they are only guaranteed to be freed when the [`DeviceContext`] is destroyed. /// -/// All launches are asynchronous, using [`CUDA stream`]s. When it is necessary to implement synchronization, use the mechanisms provided by CUDA streams and events. +/// All launches are asynchronous, using [`CUDA stream`]s. When it is necessary +/// to implement synchronization, use the mechanisms provided by CUDA streams and +/// events. /// -/// In addition to the pipeline object, the CUDA stream, and the launch state, it is necessary to provide information about the SBT layout using the [`ShaderBindingTable`](crate::shader_binding_table::ShaderBindingTable) struct (see [Shader Binding Table](crate::shader_binding_table)). +/// In addition to the pipeline object, the CUDA stream, and the launch state, it +/// is necessary to provide information about the SBT layout using the +/// [`ShaderBindingTable`](crate::shader_binding_table::ShaderBindingTable) struct +/// (see [Shader Binding Table](crate::shader_binding_table)). /// -/// The value of the pipeline launch parameter is specified by the `pipeline_launch_params_variable_name` field of the [`PipelineCompileOptions`](crate::pipeline::PipelineCompileOptions) struct. It is determined at launch with a [`DevicePointer`](cust::memory::DevicePointer) parameter, named `pipeline_params`]. This must be the same size as that passed to the module compilation or an error will occur. +/// The value of the pipeline launch parameter is specified by the +/// `pipeline_launch_params_variable_name` field of the +/// [`PipelineCompileOptions`](crate::pipeline::PipelineCompileOptions) struct. +/// It is determined at launch with a [`DevicePointer`](cust::memory::DevicePointer) +/// parameter, named `pipeline_params`]. This must be the same size as that passed +/// to the module compilation or an error will occur. /// -/// The kernel creates a copy of `pipeline_params` before the launch, so the kernel is allowed to modify `pipeline_params` values during the launch. This means that subsequent launches can run with modified pipeline parameter values. Users cannot synchronize with this copy between the invocation of `launch()` and the start of the kernel. +/// The kernel creates a copy of `pipeline_params` before the launch, so the kernel +/// is allowed to modify `pipeline_params` values during the launch. This means +/// that subsequent launches can run with modified pipeline parameter values. Users +/// cannot synchronize with this copy between the invocation of `launch()` and +/// the start of the kernel. /// /// # Safety /// You must ensure that: /// - Any device memory referenced in `buf_launch_params` point to valid, /// correctly aligned memory -/// - Any [`SbtRecord`](shader_binding_table::SbtRecord)s and associated data referenced by the -/// [`ShaderBindingTable`](shader_binding_table::ShaderBindingTable) are alive and valid +/// - Any [`SbtRecord`](shader_binding_table::SbtRecord)s and associated data +/// referenced by the +/// [`ShaderBindingTable`](shader_binding_table::ShaderBindingTable) are alive +/// and valid /// /// [`CUDA stream`]: cust::stream::Stream /// [`DeviceContext`]: crate::context::DeviceContext -pub unsafe fn launch( +pub unsafe fn launch( pipeline: &crate::pipeline::Pipeline, stream: &cust::stream::Stream, - pipeline_params: DevicePointer

      , + pipeline_params: &M, sbt: &ShaderBindingTable, width: u32, height: u32, @@ -154,8 +177,8 @@ pub unsafe fn launch( Ok(optix_call!(optixLaunch( pipeline.raw, stream.as_inner(), - pipeline_params.as_raw() as u64, - std::mem::size_of::

      (), + pipeline_params.as_raw_ptr() as u64, + pipeline_params.size_in_bytes(), &sbt.0, width, height, From cb89463974068353e168e5541e8848b47311d21c Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Sun, 7 Nov 2021 21:44:00 +1300 Subject: [PATCH 084/100] first optix rust test --- Cargo.toml | 2 ++ crates/cuda_builder/src/lib.rs | 2 +- .../optix/examples/ex02_pipeline/Cargo.toml | 1 + crates/optix/examples/ex02_pipeline/build.rs | 9 +++++ .../examples/ex02_pipeline/device/Cargo.toml | 13 +++++++ .../examples/ex02_pipeline/device/src/lib.rs | 35 +++++++++++++++++++ .../examples/ex02_pipeline/src/renderer.rs | 3 +- 7 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 crates/optix/examples/ex02_pipeline/device/Cargo.toml create mode 100644 crates/optix/examples/ex02_pipeline/device/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 8bfc12cb..90757d10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [workspace] members = [ "crates/*", + "crates/cuda_test/kernels", "crates/optix/examples/ex*", + "crates/optix/examples/ex*/device", "xtask", "examples/optix/*" ] diff --git a/crates/cuda_builder/src/lib.rs b/crates/cuda_builder/src/lib.rs index c0695141..3321551f 100644 --- a/crates/cuda_builder/src/lib.rs +++ b/crates/cuda_builder/src/lib.rs @@ -1,6 +1,6 @@ //! Utility crate for easily building CUDA crates using rustc_codegen_nvvm. Derived from rust-gpu's spirv_builder. -use nvvm::NvvmArch; +pub use nvvm::NvvmArch; use serde::Deserialize; use std::{ borrow::Borrow, diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml index 189cb991..f7f75beb 100644 --- a/crates/optix/examples/ex02_pipeline/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -12,3 +12,4 @@ anyhow = "1.0.44" [build-dependencies] find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } +cuda_builder = { version = "0.1", path = "../../../cuda_builder" } diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs index 64d4aca8..eed0d676 100644 --- a/crates/optix/examples/ex02_pipeline/build.rs +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -1,3 +1,4 @@ +use cuda_builder::CudaBuilder; use find_cuda_helper::{find_cuda_root, find_optix_root}; fn main() { @@ -22,6 +23,14 @@ fn main() { ]; compile_to_ptx("src/ex02_pipeline.cu", &args); + + let ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("device.ptx"); + + CudaBuilder::new("./device") + .copy_to(ptx_path) + .arch(cuda_builder::NvvmArch::Compute75) + .build() + .unwrap(); } fn compile_to_ptx(cu_path: &str, args: &[String]) { diff --git a/crates/optix/examples/ex02_pipeline/device/Cargo.toml b/crates/optix/examples/ex02_pipeline/device/Cargo.toml new file mode 100644 index 00000000..f23ec34e --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/device/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "device" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cuda_std = { version = "0.1", path = "../../../../cuda_std" } + +[lib] +crate-type = ["cdylib"] + diff --git a/crates/optix/examples/ex02_pipeline/device/src/lib.rs b/crates/optix/examples/ex02_pipeline/device/src/lib.rs new file mode 100644 index 00000000..c5635bac --- /dev/null +++ b/crates/optix/examples/ex02_pipeline/device/src/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr( + target_arch = "nvptx64", + no_std, + feature(register_attr), + register_attr(nvvm_internal) +)] +#![deny(warnings)] + +use cuda_std::*; + +/* +#[repr(C)] +struct LaunchParams { + frame_id: i32, + color_buffer: *mut u32, + fb_size: [i32; 2], +} +*/ + +#[kernel] +pub unsafe fn __closesthit__radiance() {} + +#[kernel] +pub unsafe fn __anyhit__radiance() {} + +#[kernel] +pub unsafe fn __miss__radiance() {} + +#[kernel] +pub unsafe fn __raygen__renderFrame() { + //crate::println!("Hello from Rust kernel!"); +} + +// #[kernel] +// pub unsafe fn render(fb: *mut Vec3, view: &Viewport) {} diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index d6781bc3..23b37963 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -61,7 +61,8 @@ impl Renderer { .traversable_graph_flags(TraversableGraphFlags::ALLOW_SINGLE_GAS) .exception_flags(ExceptionFlags::NONE); - let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); + // let ptx = include_str!(concat!(env!("OUT_DIR"), "/src/ex02_pipeline.ptx")); + let ptx = include_str!(concat!(env!("OUT_DIR"), "/device.ptx")); let (module, _log) = Module::new( &mut ctx, From 69f2cead8f2a5572305153a23d04231fa7040f0a Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Mon, 8 Nov 2021 12:23:31 +1300 Subject: [PATCH 085/100] tweak build --- Cargo.toml | 1 - crates/optix/examples/ex02_pipeline/build.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 90757d10..13f57bcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "crates/*", - "crates/cuda_test/kernels", "crates/optix/examples/ex*", "crates/optix/examples/ex*/device", "xtask", diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs index eed0d676..57342b7a 100644 --- a/crates/optix/examples/ex02_pipeline/build.rs +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -29,6 +29,7 @@ fn main() { CudaBuilder::new("./device") .copy_to(ptx_path) .arch(cuda_builder::NvvmArch::Compute75) + .emit_llvm_ir(true) .build() .unwrap(); } From c6718c0a016fde753c193f5a974e876cabf40690 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 12 Nov 2021 08:15:13 +1300 Subject: [PATCH 086/100] update to latest optix changes --- .../optix/examples/ex02_pipeline/src/renderer.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 23b37963..046b3e06 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use cust::context::{Context as CuContext, ContextFlags}; use cust::device::{Device, DeviceAttribute}; -use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer}; +use cust::memory::{CopyDestination, DeviceBox, DeviceBuffer, DevicePointer, DeviceVariable}; use cust::stream::{Stream, StreamFlags}; use cust::CudaFlags; use cust::DeviceCopy; @@ -16,8 +16,7 @@ use optix::{ }; pub struct Renderer { - launch_params: LaunchParams, - buf_launch_params: DeviceBox, + launch_params: DeviceVariable, sbt: ShaderBindingTable, pipeline: Pipeline, buf_raygen: DeviceBuffer, @@ -145,23 +144,20 @@ impl Renderer { let color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; - let launch_params = LaunchParams { + let launch_params = DeviceVariable::new(LaunchParams { frame_id: 0, color_buffer: color_buffer.as_ptr(), fb_size: Point2i { x: width as i32, y: height as i32, }, - }; - - let buf_launch_params = DeviceBox::new(&launch_params)?; + })?; Ok(Renderer { ctx, cuda_context, stream, launch_params, - buf_launch_params, buf_raygen, buf_hitgroup, buf_miss, @@ -184,14 +180,14 @@ impl Renderer { } pub fn render(&mut self) -> Result<(), Box> { - self.buf_launch_params.copy_from(&self.launch_params)?; + self.launch_params.copy_htod()?; self.launch_params.frame_id += 1; unsafe { optix::launch( &self.pipeline, &self.stream, - self.buf_launch_params.as_device_ptr(), + &self.launch_params, &self.sbt, self.launch_params.fb_size.x as u32, self.launch_params.fb_size.y as u32, From 0772dd35b86b968ec4bdc4fb238b8f42743e679f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 12 Nov 2021 17:24:23 +1300 Subject: [PATCH 087/100] Split DeviceCopy into cust_core --- Cargo.toml | 3 +- crates/cust/Cargo.toml | 8 +- crates/cust/src/lib.rs | 3 - crates/cust/src/memory/mod.rs | 166 +----------------- crates/cust_core/Cargo.toml | 14 ++ crates/cust_core/src/lib.rs | 165 +++++++++++++++++ crates/optix/Cargo.toml | 8 +- .../optix/examples/ex02_pipeline/Cargo.toml | 1 + crates/optix/examples/ex02_pipeline/build.rs | 3 +- .../examples/ex02_pipeline/device/Cargo.toml | 4 +- .../examples/ex02_pipeline/device/src/lib.rs | 54 +++++- .../examples/ex02_pipeline/src/renderer.rs | 44 ++--- crates/optix_device/Cargo.toml | 10 ++ crates/optix_device/src/lib.rs | 47 +++++ examples/optix/denoiser/Cargo.toml | 3 +- examples/optix/denoiser/src/main.rs | 2 +- 16 files changed, 323 insertions(+), 212 deletions(-) create mode 100644 crates/cust_core/Cargo.toml create mode 100644 crates/cust_core/src/lib.rs create mode 100644 crates/optix_device/Cargo.toml create mode 100644 crates/optix_device/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 90757d10..023c372c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "crates/*", - "crates/cuda_test/kernels", "crates/optix/examples/ex*", "crates/optix/examples/ex*/device", "xtask", @@ -9,7 +8,7 @@ members = [ ] exclude = [ - "crates/optix/examples/*" + "crates/optix/examples/common" ] [profile.dev.package.rustc_codegen_nvvm] diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index 608f9470..6a6b8531 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] cust_raw = { path = "../cust_raw", version = "0.11.2"} +cust_core = { path = "../cust_core", version = "0.1.0"} bitflags = "1.2" cust_derive = { path = "../cust_derive", version = "0.1" } vek = { version = "0.15.1", optional = true } @@ -14,7 +15,12 @@ glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", fe mint = { version = "^0.5", optional = true } [features] -default-features = ["vek", "glam", "mint"] +default-features = ["vek", "impl_glam", "impl_mint"] +impl_glam = ["cust_core/glam", "glam"] +impl_mint = ["cust_core/mint", "mint"] +impl_vek = ["cust_core/vek", "vek"] + + [build-dependencies] find_cuda_helper = { path = "../find_cuda_helper", version = "0.1" } diff --git a/crates/cust/src/lib.rs b/crates/cust/src/lib.rs index 4ff79e62..63679085 100644 --- a/crates/cust/src/lib.rs +++ b/crates/cust/src/lib.rs @@ -80,9 +80,6 @@ use crate::error::{CudaResult, ToResult}; use bitflags::bitflags; use sys::{cuDriverGetVersion, cuInit}; -#[cfg(feature = "vek")] -pub use vek; - bitflags! { /// Bit flags for initializing the CUDA driver. Currently, no flags are defined, /// so `CudaFlags::empty()` is the only valid value. diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index da796d84..f24fcd62 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -90,8 +90,8 @@ pub use self::unified::*; use crate::error::*; -use core::marker::PhantomData; -use core::num::*; +pub use cust_core::DeviceCopy; + use std::ffi::c_void; /// A trait describing a generic buffer that can be accessed from the GPU. This could be either a [`UnifiedBuffer`] @@ -223,168 +223,6 @@ mod private { impl Sealed for DeviceBox {} } -/// Marker trait for types which can safely be copied to or from a CUDA device. -/// -/// A type can be safely copied if its value can be duplicated simply by copying bits and if it does -/// not contain a reference to memory which is not accessible to the device. Additionally, the -/// DeviceCopy trait does not imply copy semantics as the Copy trait does. -/// -/// ## How can I implement DeviceCopy? -/// -/// There are two ways to implement DeviceCopy on your type. The simplest is to use `derive`: -/// -/// ``` -/// use cust::DeviceCopy; -/// -/// #[derive(Clone, DeviceCopy)] -/// struct MyStruct(u64); -/// -/// # fn main () {} -/// ``` -/// -/// This is safe because the `DeviceCopy` derive macro will check that all fields of the struct, -/// enum or union implement `DeviceCopy`. For example, this fails to compile, because `Vec` cannot -/// be copied to the device: -/// -/// ```compile_fail -/// use cust::DeviceCopy; -/// -/// #[derive(Clone, DeviceCopy)] -/// struct MyStruct(Vec); -/// # fn main () {} -/// ``` -/// -/// You can also implement `DeviceCopy` unsafely: -/// -/// ``` -/// use cust::memory::DeviceCopy; -/// -/// #[derive(Clone)] -/// struct MyStruct(u64); -/// -/// unsafe impl DeviceCopy for MyStruct { } -/// # fn main () {} -/// ``` -/// -/// ## What is the difference between `DeviceCopy` and `Copy`? -/// -/// `DeviceCopy` is stricter than `Copy`. `DeviceCopy` must only be implemented for types which -/// do not contain references or raw pointers to non-device-accessible memory. `DeviceCopy` also -/// does not imply copy semantics - that is, `DeviceCopy` values are not implicitly copied on -/// assignment the way that `Copy` values are. This is helpful, as it may be desirable to implement -/// `DeviceCopy` for large structures that would be inefficient to copy for every assignment. -/// -/// ## When can't my type be `DeviceCopy`? -/// -/// Some types cannot be safely copied to the device. For example, copying `&T` would create an -/// invalid reference on the device which would segfault if dereferenced. Generalizing this, any -/// type implementing `Drop` cannot be `DeviceCopy` since it is responsible for some resource that -/// would not be available on the device. -pub unsafe trait DeviceCopy: Copy {} - -macro_rules! impl_device_copy { - ($($t:ty)*) => { - $( - unsafe impl DeviceCopy for $t {} - )* - } -} - -impl_device_copy!( - usize u8 u16 u32 u64 u128 - isize i8 i16 i32 i64 i128 - f32 f64 - bool char - - NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 -); -unsafe impl DeviceCopy for Option {} -unsafe impl DeviceCopy for Result {} -unsafe impl DeviceCopy for PhantomData {} -// Allow DeviceCopy for lifetime constraint markers -unsafe impl DeviceCopy for PhantomData<&()> {} -unsafe impl DeviceCopy for Wrapping {} -unsafe impl DeviceCopy for [T; N] {} -unsafe impl DeviceCopy for () {} -unsafe impl DeviceCopy for (A, B) {} -unsafe impl DeviceCopy for (A, B, C) {} -unsafe impl DeviceCopy - for (A, B, C, D) -{ -} -unsafe impl DeviceCopy - for (A, B, C, D, E) -{ -} -unsafe impl - DeviceCopy for (A, B, C, D, E, F) -{ -} -unsafe impl< - A: DeviceCopy, - B: DeviceCopy, - C: DeviceCopy, - D: DeviceCopy, - E: DeviceCopy, - F: DeviceCopy, - G: DeviceCopy, - > DeviceCopy for (A, B, C, D, E, F, G) -{ -} -unsafe impl< - A: DeviceCopy, - B: DeviceCopy, - C: DeviceCopy, - D: DeviceCopy, - E: DeviceCopy, - F: DeviceCopy, - G: DeviceCopy, - H: DeviceCopy, - > DeviceCopy for (A, B, C, D, E, F, G, H) -{ -} - -macro_rules! impl_device_copy_vek { - ($($strukt:ident),* $(,)?) => { - $( - unsafe impl DeviceCopy for $strukt {} - )* - } -} - -macro_rules! impl_device_copy_glam { - ($($strukt:ty),* $(,)?) => { - $( - unsafe impl DeviceCopy for $strukt {} - )* - } -} - -#[cfg(feature = "vek")] -use vek::*; - -#[cfg(feature = "vek")] -impl_device_copy_vek! { - Vec2, Vec3, Vec4, Extent2, Extent3, Rgb, Rgba, - Mat2, Mat3, Mat4, - CubicBezier2, CubicBezier3, - Quaternion, -} - -#[cfg(feature = "glam")] -impl_device_copy_glam! { - glam::Vec2, glam::Vec3, glam::Vec4, glam::IVec2, glam::IVec3, glam::IVec4, -} - -#[cfg(feature = "mint")] -impl_device_copy_glam! { - mint::Vector2, mint::Vector2, mint::Vector2, - mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, - mint::Vector4, mint::Vector4, mint::Vector4, - mint::ColumnMatrix2, mint::ColumnMatrix3, mint::ColumnMatrix4, mint::ColumnMatrix3x4, - mint::RowMatrix2, mint::RowMatrix3, mint::RowMatrix4, mint::RowMatrix3x4, -} - /// Simple wrapper over cuMemcpyHtoD_v2 pub unsafe fn memcpy_htod( d_ptr: cust_raw::CUdeviceptr, diff --git a/crates/cust_core/Cargo.toml b/crates/cust_core/Cargo.toml new file mode 100644 index 00000000..b6eb90a5 --- /dev/null +++ b/crates/cust_core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cust_core" +version = "0.1.0" +edition = "2021" + +[dependencies] +vek = { version = "0.15.1", default-features=false, features=["libm"], optional = true } +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false, optional=true } +mint = { version = "^0.5", optional = true } + +[features] +default-features = ["vek", "glam", "mint"] + + diff --git a/crates/cust_core/src/lib.rs b/crates/cust_core/src/lib.rs new file mode 100644 index 00000000..aaf04261 --- /dev/null +++ b/crates/cust_core/src/lib.rs @@ -0,0 +1,165 @@ +#![no_std] +use core::marker::PhantomData; +use core::num::*; + +/// Marker trait for types which can safely be copied to or from a CUDA device. +/// +/// A type can be safely copied if its value can be duplicated simply by copying bits and if it does +/// not contain a reference to memory which is not accessible to the device. Additionally, the +/// DeviceCopy trait does not imply copy semantics as the Copy trait does. +/// +/// ## How can I implement DeviceCopy? +/// +/// There are two ways to implement DeviceCopy on your type. The simplest is to use `derive`: +/// +/// ``` +/// use cust::DeviceCopy; +/// +/// #[derive(Clone, DeviceCopy)] +/// struct MyStruct(u64); +/// +/// # fn main () {} +/// ``` +/// +/// This is safe because the `DeviceCopy` derive macro will check that all fields of the struct, +/// enum or union implement `DeviceCopy`. For example, this fails to compile, because `Vec` cannot +/// be copied to the device: +/// +/// ```compile_fail +/// use cust::DeviceCopy; +/// +/// #[derive(Clone, DeviceCopy)] +/// struct MyStruct(Vec); +/// # fn main () {} +/// ``` +/// +/// You can also implement `DeviceCopy` unsafely: +/// +/// ``` +/// use cust::memory::DeviceCopy; +/// +/// #[derive(Clone)] +/// struct MyStruct(u64); +/// +/// unsafe impl DeviceCopy for MyStruct { } +/// # fn main () {} +/// ``` +/// +/// ## What is the difference between `DeviceCopy` and `Copy`? +/// +/// `DeviceCopy` is stricter than `Copy`. `DeviceCopy` must only be implemented for types which +/// do not contain references or raw pointers to non-device-accessible memory. `DeviceCopy` also +/// does not imply copy semantics - that is, `DeviceCopy` values are not implicitly copied on +/// assignment the way that `Copy` values are. This is helpful, as it may be desirable to implement +/// `DeviceCopy` for large structures that would be inefficient to copy for every assignment. +/// +/// ## When can't my type be `DeviceCopy`? +/// +/// Some types cannot be safely copied to the device. For example, copying `&T` would create an +/// invalid reference on the device which would segfault if dereferenced. Generalizing this, any +/// type implementing `Drop` cannot be `DeviceCopy` since it is responsible for some resource that +/// would not be available on the device. +pub unsafe trait DeviceCopy: Copy {} + +macro_rules! impl_device_copy { + ($($t:ty)*) => { + $( + unsafe impl DeviceCopy for $t {} + )* + } +} + +impl_device_copy!( + usize u8 u16 u32 u64 u128 + isize i8 i16 i32 i64 i128 + f32 f64 + bool char + + NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 +); +unsafe impl DeviceCopy for Option {} +unsafe impl DeviceCopy for Result {} +unsafe impl DeviceCopy for PhantomData {} +// Allow DeviceCopy for lifetime constraint markers +unsafe impl DeviceCopy for PhantomData<&()> {} +unsafe impl DeviceCopy for Wrapping {} +unsafe impl DeviceCopy for [T; N] {} +unsafe impl DeviceCopy for () {} +unsafe impl DeviceCopy for (A, B) {} +unsafe impl DeviceCopy for (A, B, C) {} +unsafe impl DeviceCopy + for (A, B, C, D) +{ +} +unsafe impl DeviceCopy + for (A, B, C, D, E) +{ +} +unsafe impl + DeviceCopy for (A, B, C, D, E, F) +{ +} +unsafe impl< + A: DeviceCopy, + B: DeviceCopy, + C: DeviceCopy, + D: DeviceCopy, + E: DeviceCopy, + F: DeviceCopy, + G: DeviceCopy, + > DeviceCopy for (A, B, C, D, E, F, G) +{ +} +unsafe impl< + A: DeviceCopy, + B: DeviceCopy, + C: DeviceCopy, + D: DeviceCopy, + E: DeviceCopy, + F: DeviceCopy, + G: DeviceCopy, + H: DeviceCopy, + > DeviceCopy for (A, B, C, D, E, F, G, H) +{ +} + +macro_rules! impl_device_copy_vek { + ($($strukt:ident),* $(,)?) => { + $( + unsafe impl DeviceCopy for $strukt {} + )* + } +} + +macro_rules! impl_device_copy_glam { + ($($strukt:ty),* $(,)?) => { + $( + unsafe impl DeviceCopy for $strukt {} + )* + } +} + +#[cfg(feature = "vek")] +use vek::*; + +#[cfg(feature = "vek")] +impl_device_copy_vek! { + Vec2, Vec3, Vec4, Extent2, Extent3, Rgb, Rgba, + Mat2, Mat3, Mat4, + CubicBezier2, CubicBezier3, + Quaternion, +} + +#[cfg(feature = "glam")] +impl_device_copy_glam! { + glam::Vec2, glam::Vec3, glam::Vec4, glam::IVec2, glam::IVec3, glam::IVec4, +} + +#[cfg(feature = "mint")] +impl_device_copy_glam! { + mint::Vector2, mint::Vector2, mint::Vector2, + mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, + mint::Vector4, mint::Vector4, mint::Vector4, + mint::ColumnMatrix2, mint::ColumnMatrix3, mint::ColumnMatrix4, mint::ColumnMatrix3x4, + mint::RowMatrix2, mint::RowMatrix3, mint::RowMatrix4, mint::RowMatrix3x4, +} diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index e088fd14..ce1adb39 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -2,19 +2,21 @@ name = "optix" version = "0.1.0" edition = "2021" +authors = ["Anders Langlands ", "Riccardo D'Ambrosio "] [features] optix71 = [] optix72 = [] optix73 = [] -default=["optix73", "glam"] +default=["optix73", "impl_glam"] +impl_glam=["cust/impl_glam", "glam"] [dependencies] -cust = { version = "0.1", path = "../cust", features=["glam", "mint"] } +cust = { version = "0.1", path = "../cust", features=["impl_mint"] } cust_raw = { version = "0.11.2", path = "../cust_raw" } cfg-if = "1.0.0" bitflags = "1.3.2" -glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false, optional=true } half = { version = "^1.8", optional = true } memoffset = "0.6.4" mint = "0.5.8" diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml index f7f75beb..1eb9c5cb 100644 --- a/crates/optix/examples/ex02_pipeline/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" optix = {path = "../../"} cust = {path = "../../../cust"} anyhow = "1.0.44" +device = { path = "./device" } [build-dependencies] find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs index eed0d676..cc228674 100644 --- a/crates/optix/examples/ex02_pipeline/build.rs +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -26,9 +26,10 @@ fn main() { let ptx_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("device.ptx"); - CudaBuilder::new("./device") + CudaBuilder::new("device") .copy_to(ptx_path) .arch(cuda_builder::NvvmArch::Compute75) + .optix(true) .build() .unwrap(); } diff --git a/crates/optix/examples/ex02_pipeline/device/Cargo.toml b/crates/optix/examples/ex02_pipeline/device/Cargo.toml index f23ec34e..f65f54e2 100644 --- a/crates/optix/examples/ex02_pipeline/device/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/device/Cargo.toml @@ -7,7 +7,9 @@ edition = "2021" [dependencies] cuda_std = { version = "0.1", path = "../../../../cuda_std" } +cust_core = { version = "0.1", path = "../../../../cust_core" } +optix_device = { path = "../../../../optix_device" } [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] diff --git a/crates/optix/examples/ex02_pipeline/device/src/lib.rs b/crates/optix/examples/ex02_pipeline/device/src/lib.rs index c5635bac..ca7ea687 100644 --- a/crates/optix/examples/ex02_pipeline/device/src/lib.rs +++ b/crates/optix/examples/ex02_pipeline/device/src/lib.rs @@ -1,21 +1,37 @@ +#![feature(asm)] #![cfg_attr( - target_arch = "nvptx64", + target_os = "cuda", no_std, feature(register_attr), register_attr(nvvm_internal) )] -#![deny(warnings)] +// #![deny(warnings)] + +use core::mem::MaybeUninit; use cuda_std::*; +use cust_core::DeviceCopy; + +use optix_device as optix; + +extern crate alloc; -/* #[repr(C)] -struct LaunchParams { - frame_id: i32, - color_buffer: *mut u32, - fb_size: [i32; 2], +#[derive(Copy, Clone)] +pub struct LaunchParams { + pub frame_id: i32, + pub fb_size: [u32; 2], + pub color_buffer: u64, +} + +unsafe impl DeviceCopy for LaunchParams {} + +#[no_mangle] +static PARAMS: MaybeUninit = MaybeUninit::uninit(); + +extern "C" { + pub fn vprintf(format: *const u8, valist: *const core::ffi::c_void) -> i32; } -*/ #[kernel] pub unsafe fn __closesthit__radiance() {} @@ -28,7 +44,27 @@ pub unsafe fn __miss__radiance() {} #[kernel] pub unsafe fn __raygen__renderFrame() { - //crate::println!("Hello from Rust kernel!"); + // let ix = _optix_get_launch_index_x(); + // let iy = _optix_get_launch_index_y(); + + let ix: u32; + let iy: u32; + + let idx = optix::get_launch_index(); + + let params = PARAMS.assume_init_ref(); + + if idx[0] == 3 && idx[1] == 4 { + vprintf( + b"Hello from Rust kernel!\n\0".as_ptr().cast(), + 0 as *const core::ffi::c_void, + ); + + vprintf( + b"frame id is %d\n\0".as_ptr().cast(), + ¶ms.frame_id as *const _ as *const core::ffi::c_void, + ); + } } // #[kernel] diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 046b3e06..8dd09d91 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -28,6 +28,16 @@ pub struct Renderer { cuda_context: CuContext, } +use device::LaunchParams; + +// #[repr(C)] +// #[derive(Copy, Clone, DeviceCopy)] +// struct LaunchParams { +// frame_id: i32, +// fb_size: [u32; 2], +// color_buffer: u64, +// } + impl Renderer { pub fn new(width: usize, height: usize) -> Result> { init_optix()?; @@ -145,12 +155,9 @@ impl Renderer { let color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; let launch_params = DeviceVariable::new(LaunchParams { - frame_id: 0, - color_buffer: color_buffer.as_ptr(), - fb_size: Point2i { - x: width as i32, - y: height as i32, - }, + frame_id: 17, + fb_size: [width as u32, height as u32], + color_buffer: color_buffer.as_device_ptr(), })?; Ok(Renderer { @@ -173,9 +180,9 @@ impl Renderer { height: usize, ) -> Result<(), Box> { self.color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; - self.launch_params.fb_size.x = width as i32; - self.launch_params.fb_size.y = height as i32; - self.launch_params.color_buffer = self.color_buffer.as_ptr(); + self.launch_params.fb_size[0] = width as u32; + self.launch_params.fb_size[1] = height as u32; + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); Ok(()) } @@ -189,8 +196,8 @@ impl Renderer { &self.stream, &self.launch_params, &self.sbt, - self.launch_params.fb_size.x as u32, - self.launch_params.fb_size.y as u32, + self.launch_params.fb_size[0], + self.launch_params.fb_size[1], 1, )?; } @@ -201,21 +208,6 @@ impl Renderer { } } -#[repr(C)] -#[derive(Copy, Clone, DeviceCopy)] -struct Point2i { - pub x: i32, - pub y: i32, -} - -#[repr(C)] -#[derive(Copy, Clone, DeviceCopy)] -struct LaunchParams { - pub frame_id: i32, - pub color_buffer: DevicePointer, - pub fb_size: Point2i, -} - type RaygenRecord = SbtRecord; type MissRecord = SbtRecord; diff --git a/crates/optix_device/Cargo.toml b/crates/optix_device/Cargo.toml new file mode 100644 index 00000000..957273c3 --- /dev/null +++ b/crates/optix_device/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "optix_device" +version = "0.1.0" +edition = "2021" +authors = ["Anders Langlands ", "Riccardo D'Ambrosio "] + +[dependencies] +cuda_std = { version = "0.1", path = "../cuda_std" } +glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false } + diff --git a/crates/optix_device/src/lib.rs b/crates/optix_device/src/lib.rs new file mode 100644 index 00000000..5036e5e5 --- /dev/null +++ b/crates/optix_device/src/lib.rs @@ -0,0 +1,47 @@ +#![cfg_attr( + target_arch = "nvptx64", + no_std, + feature(register_attr, asm), + register_attr(nvvm_internal) +)] + +extern crate alloc; + +use cuda_std::*; +use glam::{UVec2, UVec3}; + +extern "C" { + pub fn vprintf(format: *const u8, valist: *const core::ffi::c_void) -> i32; +} + +#[gpu_only] +#[inline(always)] +pub fn get_launch_index() -> UVec3 { + let x: u32; + let y: u32; + let z: u32; + + unsafe { + asm!("call ({0}), _optix_get_launch_index_x, ();", out(reg32) x); + asm!("call ({0}), _optix_get_launch_index_y, ();", out(reg32) y); + asm!("call ({0}), _optix_get_launch_index_z, ();", out(reg32) z); + } + + UVec3::new(x, y, z) +} + +#[gpu_only] +#[inline(always)] +pub fn get_launch_dimensions() -> UVec3 { + let x: u32; + let y: u32; + let z: u32; + + unsafe { + asm!("call ({0}), _optix_get_launch_dimension_x, ();", out(reg32) x); + asm!("call ({0}), _optix_get_launch_dimension_y, ();", out(reg32) y); + asm!("call ({0}), _optix_get_launch_dimension_z, ();", out(reg32) z); + } + + UVec3::new(x, y, z) +} diff --git a/examples/optix/denoiser/Cargo.toml b/examples/optix/denoiser/Cargo.toml index 1ee6e276..3624786d 100644 --- a/examples/optix/denoiser/Cargo.toml +++ b/examples/optix/denoiser/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" [dependencies] optix = { version = "0.1", path = "../../../crates/optix" } structopt = "0.3" -cust = { version = "0.1", path = "../../../crates/cust", features = ["vek"] } +cust = { version = "0.1", path = "../../../crates/cust", features = ["impl_vek"] } image = "0.23.14" +vek = { version = "0.15.1" } diff --git a/examples/optix/denoiser/src/main.rs b/examples/optix/denoiser/src/main.rs index 8db84227..b6ca9f5e 100644 --- a/examples/optix/denoiser/src/main.rs +++ b/examples/optix/denoiser/src/main.rs @@ -1,13 +1,13 @@ use cust::memory::DeviceBuffer; use cust::prelude::{Stream, StreamFlags}; use cust::util::SliceExt; -use cust::vek::{Clamp, Vec3}; use image::io::Reader; use optix::context::DeviceContext; use optix::denoiser::{Denoiser, DenoiserModelKind, DenoiserParams, Image, ImageFormat}; use std::error::Error; use std::path::PathBuf; use structopt::StructOpt; +use vek::{Clamp, Vec3}; #[derive(StructOpt)] #[structopt( From c318a0a12b5f7fe7a25af4fddd4088a4042ee530 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 12 Nov 2021 17:26:45 +1300 Subject: [PATCH 088/100] update to latest optix changes --- crates/optix/examples/ex02_pipeline/src/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index d6781bc3..58530487 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -190,7 +190,7 @@ impl Renderer { optix::launch( &self.pipeline, &self.stream, - self.buf_launch_params.as_device_ptr(), + &self.buf_launch_params, &self.sbt, self.launch_params.fb_size.x as u32, self.launch_params.fb_size.y as u32, From d270e80315218ede644b85100eeea425c1f39e56 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 12 Nov 2021 17:48:46 +1300 Subject: [PATCH 089/100] trying to get print working --- crates/optix/examples/ex02_pipeline/device/src/lib.rs | 5 ++++- crates/optix/examples/ex02_pipeline/src/renderer.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/device/src/lib.rs b/crates/optix/examples/ex02_pipeline/device/src/lib.rs index ca7ea687..99c73b8c 100644 --- a/crates/optix/examples/ex02_pipeline/device/src/lib.rs +++ b/crates/optix/examples/ex02_pipeline/device/src/lib.rs @@ -60,9 +60,12 @@ pub unsafe fn __raygen__renderFrame() { 0 as *const core::ffi::c_void, ); + #[repr(C)] + struct PrintArgs(i32); + vprintf( b"frame id is %d\n\0".as_ptr().cast(), - ¶ms.frame_id as *const _ as *const core::ffi::c_void, + core::mem::transmute(&PrintArgs(params.frame_id)), ); } } diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 8dd09d91..d6eaff3a 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -187,8 +187,9 @@ impl Renderer { } pub fn render(&mut self) -> Result<(), Box> { + self.launch_params.frame_id = 555; self.launch_params.copy_htod()?; - self.launch_params.frame_id += 1; + self.launch_params.frame_id = 777; unsafe { optix::launch( From 7a7a2c2b3d76017d7d9325113618f6838a5e045f Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 12 Nov 2021 18:05:35 +1300 Subject: [PATCH 090/100] tweak test kernel --- .../ex02_pipeline/src/ex02_pipeline.cu | 29 ++----------------- .../examples/ex02_pipeline/src/renderer.rs | 2 +- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu b/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu index 26e3c43b..7ff52b2b 100644 --- a/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu +++ b/crates/optix/examples/ex02_pipeline/src/ex02_pipeline.cu @@ -68,38 +68,15 @@ __miss__radiance() { /*! for this simple example, this will remain empty */ // ray gen program - the actual rendering happens in here //------------------------------------------------------------------------------ extern "C" __global__ void __raygen__renderFrame() { - if (PARAMS.frameID == 0 && optixGetLaunchIndex().x == 0 && + if (optixGetLaunchIndex().x == 0 && optixGetLaunchIndex().y == 0) { // we could of course also have used optixGetLaunchDims to query // the launch size, but accessing the PARAMS here // makes sure they're not getting optimized away (because // otherwise they'd not get used) - printf("############################################\n"); - printf("Hello world from OptiX 7 raygen program!\n(within a " - "%ix%i-sized launch)\n", - PARAMS.fbSize.x, PARAMS.fbSize.y); - printf("############################################\n"); + printf("Hello world from OptiX 7 c++!\n"); + printf("frameID is %d\n", PARAMS.frameID); } - - // ------------------------------------------------------------------ - // for this example, produce a simple test pattern: - // ------------------------------------------------------------------ - - // compute a test pattern based on pixel ID - const int ix = optixGetLaunchIndex().x; - const int iy = optixGetLaunchIndex().y; - - const int r = (ix % 256); - const int g = (iy % 256); - const int b = ((ix + iy) % 256); - - // convert to 32-bit rgba value (we explicitly set alpha to 0xff - // to make stb_image_write happy ... - const uint32_t rgba = 0xff000000 | (r << 0) | (g << 8) | (b << 16); - - // and write to frame buffer ... - const uint32_t fbIndex = ix + iy * PARAMS.fbSize.x; - PARAMS.colorBuffer[fbIndex] = rgba; } } // namespace osc diff --git a/crates/optix/examples/ex02_pipeline/src/renderer.rs b/crates/optix/examples/ex02_pipeline/src/renderer.rs index 58530487..37dbce3f 100644 --- a/crates/optix/examples/ex02_pipeline/src/renderer.rs +++ b/crates/optix/examples/ex02_pipeline/src/renderer.rs @@ -145,7 +145,7 @@ impl Renderer { let color_buffer = unsafe { DeviceBuffer::uninitialized(width * height)? }; let launch_params = LaunchParams { - frame_id: 0, + frame_id: 17, color_buffer: color_buffer.as_ptr(), fb_size: Point2i { x: width as i32, From 131c843444da7586e651be3ddc8327f301b8fff0 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 12 Nov 2021 20:34:04 +1300 Subject: [PATCH 091/100] stop llvm optimizing out LaunchParams --- crates/optix/examples/ex02_pipeline/device/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/device/src/lib.rs b/crates/optix/examples/ex02_pipeline/device/src/lib.rs index 99c73b8c..c6d2e057 100644 --- a/crates/optix/examples/ex02_pipeline/device/src/lib.rs +++ b/crates/optix/examples/ex02_pipeline/device/src/lib.rs @@ -27,7 +27,11 @@ pub struct LaunchParams { unsafe impl DeviceCopy for LaunchParams {} #[no_mangle] -static PARAMS: MaybeUninit = MaybeUninit::uninit(); +static PARAMS: LaunchParams = LaunchParams { + frame_id: 88, + fb_size: [1, 1], + color_buffer: 0, +}; extern "C" { pub fn vprintf(format: *const u8, valist: *const core::ffi::c_void) -> i32; @@ -52,8 +56,6 @@ pub unsafe fn __raygen__renderFrame() { let idx = optix::get_launch_index(); - let params = PARAMS.assume_init_ref(); - if idx[0] == 3 && idx[1] == 4 { vprintf( b"Hello from Rust kernel!\n\0".as_ptr().cast(), @@ -65,7 +67,7 @@ pub unsafe fn __raygen__renderFrame() { vprintf( b"frame id is %d\n\0".as_ptr().cast(), - core::mem::transmute(&PrintArgs(params.frame_id)), + core::mem::transmute(&PrintArgs(core::ptr::read_volatile(&PARAMS.frame_id))), ); } } From eae4261655b2ed07c7a320ca2f6305acd9349f30 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Sun, 12 Dec 2021 14:12:20 -0500 Subject: [PATCH 092/100] Chore: update cargo.toml dep versions --- crates/optix/Cargo.toml | 2 +- .../optix/examples/ex02_pipeline/Cargo.toml | 4 +- .../examples/ex02_pipeline/device/Cargo.toml | 2 +- crates/optix/examples/ex03_window/Cargo.toml | 2 +- crates/optix/examples/ex04_mesh/Cargo.toml | 2 +- crates/optix/optix_wrapper.rs | 58 +++++++++---------- crates/optix_device/Cargo.toml | 2 +- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index c86c9d47..06847705 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -28,7 +28,7 @@ embed-doc-image = {version = "0.1.4"} [build-dependencies] bindgen = "0.59" cc = "1.0.71" -find_cuda_helper = { version = "0.1", path = "../find_cuda_helper" } +find_cuda_helper = { version = "0.2", path = "../find_cuda_helper" } [package.metadata.docs.rs] rustdoc-args = [ "--html-in-header", "katex-header.html" ] diff --git a/crates/optix/examples/ex02_pipeline/Cargo.toml b/crates/optix/examples/ex02_pipeline/Cargo.toml index 1eb9c5cb..caeaf8b6 100644 --- a/crates/optix/examples/ex02_pipeline/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/Cargo.toml @@ -12,5 +12,5 @@ anyhow = "1.0.44" device = { path = "./device" } [build-dependencies] -find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } -cuda_builder = { version = "0.1", path = "../../../cuda_builder" } +find_cuda_helper = { version = "0.2", path = "../../../find_cuda_helper" } +cuda_builder = { version = "0.2", path = "../../../cuda_builder" } diff --git a/crates/optix/examples/ex02_pipeline/device/Cargo.toml b/crates/optix/examples/ex02_pipeline/device/Cargo.toml index f65f54e2..0b3dfa59 100644 --- a/crates/optix/examples/ex02_pipeline/device/Cargo.toml +++ b/crates/optix/examples/ex02_pipeline/device/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cuda_std = { version = "0.1", path = "../../../../cuda_std" } +cuda_std = { version = "0.2", path = "../../../../cuda_std" } cust_core = { version = "0.1", path = "../../../../cust_core" } optix_device = { path = "../../../../optix_device" } diff --git a/crates/optix/examples/ex03_window/Cargo.toml b/crates/optix/examples/ex03_window/Cargo.toml index 19a7646c..c4d1fdf7 100644 --- a/crates/optix/examples/ex03_window/Cargo.toml +++ b/crates/optix/examples/ex03_window/Cargo.toml @@ -14,4 +14,4 @@ gl = "0.14.0" num-traits = "0.2.14" [build-dependencies] -find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } +find_cuda_helper = { version = "0.2", path = "../../../find_cuda_helper" } diff --git a/crates/optix/examples/ex04_mesh/Cargo.toml b/crates/optix/examples/ex04_mesh/Cargo.toml index ab1d0cf2..30a73006 100644 --- a/crates/optix/examples/ex04_mesh/Cargo.toml +++ b/crates/optix/examples/ex04_mesh/Cargo.toml @@ -15,4 +15,4 @@ num-traits = "0.2.14" glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"] } [build-dependencies] -find_cuda_helper = { version = "0.1", path = "../../../find_cuda_helper" } +find_cuda_helper = { version = "0.2", path = "../../../find_cuda_helper" } diff --git a/crates/optix/optix_wrapper.rs b/crates/optix/optix_wrapper.rs index 45b3934e..24bad9e9 100644 --- a/crates/optix/optix_wrapper.rs +++ b/crates/optix/optix_wrapper.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.59.1 */ +/* automatically generated by rust-bindgen 0.59.2 */ #[repr(C)] pub struct __BindgenUnionField(::std::marker::PhantomData); @@ -188,9 +188,9 @@ impl OptixResult { } #[repr(transparent)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct OptixResult(pub ::std::os::raw::c_uint); +pub struct OptixResult(pub ::std::os::raw::c_int); pub mod OptixDeviceProperty { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRACE_DEPTH: Type = 8193; pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_TRAVERSABLE_GRAPH_DEPTH: Type = 8194; pub const OPTIX_DEVICE_PROPERTY_LIMIT_MAX_PRIMITIVES_PER_GAS: Type = 8195; @@ -212,8 +212,8 @@ pub type OptixLogCallback = ::std::option::Option< pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_OFF: OptixDeviceContextValidationMode = 0; pub const OptixDeviceContextValidationMode_OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL: - OptixDeviceContextValidationMode = 4294967295; -pub type OptixDeviceContextValidationMode = ::std::os::raw::c_uint; + OptixDeviceContextValidationMode = -1; +pub type OptixDeviceContextValidationMode = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixDeviceContextOptions { @@ -233,11 +233,11 @@ impl Default for OptixDeviceContextOptions { } pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE: OptixHitKind = 254; pub const OptixHitKind_OPTIX_HIT_KIND_TRIANGLE_BACK_FACE: OptixHitKind = 255; -pub type OptixHitKind = ::std::os::raw::c_uint; +pub type OptixHitKind = ::std::os::raw::c_int; pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_NONE: OptixIndicesFormat = 0; pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_SHORT3: OptixIndicesFormat = 8450; pub const OptixIndicesFormat_OPTIX_INDICES_FORMAT_UNSIGNED_INT3: OptixIndicesFormat = 8451; -pub type OptixIndicesFormat = ::std::os::raw::c_uint; +pub type OptixIndicesFormat = ::std::os::raw::c_int; pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_NONE: OptixVertexFormat = 0; pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT3: OptixVertexFormat = 8481; pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_FLOAT2: OptixVertexFormat = 8482; @@ -245,10 +245,10 @@ pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF3: OptixVertexFormat = 8483; pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_HALF2: OptixVertexFormat = 8484; pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_3: OptixVertexFormat = 8485; pub const OptixVertexFormat_OPTIX_VERTEX_FORMAT_SNORM16_2: OptixVertexFormat = 8486; -pub type OptixVertexFormat = ::std::os::raw::c_uint; +pub type OptixVertexFormat = ::std::os::raw::c_int; pub const OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_NONE: OptixTransformFormat = 0; pub const OptixTransformFormat_OPTIX_TRANSFORM_FORMAT_MATRIX_FLOAT12: OptixTransformFormat = 8673; -pub type OptixTransformFormat = ::std::os::raw::c_uint; +pub type OptixTransformFormat = ::std::os::raw::c_int; #[repr(C)] pub struct OptixBuildInputTriangleArray { pub vertexBuffers: *const CUdeviceptr, @@ -283,7 +283,7 @@ pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_QUADRATIC_BSPLINE: Optix pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE: OptixPrimitiveType = 9474; pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_ROUND_LINEAR: OptixPrimitiveType = 9475; pub const OptixPrimitiveType_OPTIX_PRIMITIVE_TYPE_TRIANGLE: OptixPrimitiveType = 9521; -pub type OptixPrimitiveType = ::std::os::raw::c_uint; +pub type OptixPrimitiveType = ::std::os::raw::c_int; pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_CUSTOM: OptixPrimitiveTypeFlags = 1; pub const OptixPrimitiveTypeFlags_OPTIX_PRIMITIVE_TYPE_FLAGS_ROUND_QUADRATIC_BSPLINE: OptixPrimitiveTypeFlags = 2; @@ -369,7 +369,7 @@ pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CUSTOM_PRIMITIVES: OptixBui pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES: OptixBuildInputType = 8515; pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS: OptixBuildInputType = 8516; pub const OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_CURVES: OptixBuildInputType = 8517; -pub type OptixBuildInputType = ::std::os::raw::c_uint; +pub type OptixBuildInputType = ::std::os::raw::c_int; #[repr(C)] pub struct OptixBuildInput__bindgen_ty_1 { pub triangleArray: __BindgenUnionField, @@ -395,7 +395,7 @@ pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_FLIP_TRIANGLE_FACING: OptixInst pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT: OptixInstanceFlags = 4; pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_ENFORCE_ANYHIT: OptixInstanceFlags = 8; pub const OptixInstanceFlags_OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM: OptixInstanceFlags = 64; -pub type OptixInstanceFlags = ::std::os::raw::c_uint; +pub type OptixInstanceFlags = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixInstance { @@ -414,14 +414,14 @@ pub const OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_TRACE: OptixBuildFlags = pub const OptixBuildFlags_OPTIX_BUILD_FLAG_PREFER_FAST_BUILD: OptixBuildFlags = 8; pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_VERTEX_ACCESS: OptixBuildFlags = 16; pub const OptixBuildFlags_OPTIX_BUILD_FLAG_ALLOW_RANDOM_INSTANCE_ACCESS: OptixBuildFlags = 32; -pub type OptixBuildFlags = ::std::os::raw::c_uint; +pub type OptixBuildFlags = ::std::os::raw::c_int; pub const OptixBuildOperation_OPTIX_BUILD_OPERATION_BUILD: OptixBuildOperation = 8545; pub const OptixBuildOperation_OPTIX_BUILD_OPERATION_UPDATE: OptixBuildOperation = 8546; -pub type OptixBuildOperation = ::std::os::raw::c_uint; +pub type OptixBuildOperation = ::std::os::raw::c_int; pub const OptixMotionFlags_OPTIX_MOTION_FLAG_NONE: OptixMotionFlags = 0; pub const OptixMotionFlags_OPTIX_MOTION_FLAG_START_VANISH: OptixMotionFlags = 1; pub const OptixMotionFlags_OPTIX_MOTION_FLAG_END_VANISH: OptixMotionFlags = 2; -pub type OptixMotionFlags = ::std::os::raw::c_uint; +pub type OptixMotionFlags = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct OptixMotionOptions { @@ -463,7 +463,7 @@ impl Default for OptixAccelBufferSizes { } pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_COMPACTED_SIZE: OptixAccelPropertyType = 8577; pub const OptixAccelPropertyType_OPTIX_PROPERTY_TYPE_AABBS: OptixAccelPropertyType = 8578; -pub type OptixAccelPropertyType = ::std::os::raw::c_uint; +pub type OptixAccelPropertyType = ::std::os::raw::c_int; #[repr(C)] pub struct OptixAccelEmitDesc { pub result: CUdeviceptr, @@ -532,9 +532,9 @@ pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_MATRIX_MOTION_TRANSFORM: OptixTraversableType = 8642; pub const OptixTraversableType_OPTIX_TRAVERSABLE_TYPE_SRT_MOTION_TRANSFORM: OptixTraversableType = 8643; -pub type OptixTraversableType = ::std::os::raw::c_uint; +pub type OptixTraversableType = ::std::os::raw::c_int; pub mod OptixPixelFormat { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_PIXEL_FORMAT_HALF2: Type = 8711; pub const OPTIX_PIXEL_FORMAT_HALF3: Type = 8705; pub const OPTIX_PIXEL_FORMAT_HALF4: Type = 8706; @@ -563,7 +563,7 @@ impl Default for OptixImage2D { } } pub mod OptixDenoiserModelKind { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_DENOISER_MODEL_KIND_LDR: Type = 8994; pub const OPTIX_DENOISER_MODEL_KIND_HDR: Type = 8995; pub const OPTIX_DENOISER_MODEL_KIND_AOV: Type = 8996; @@ -646,21 +646,21 @@ pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_BACK_FACING_TRIANGLES: OptixRayFlags pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_FRONT_FACING_TRIANGLES: OptixRayFlags = 32; pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_DISABLED_ANYHIT: OptixRayFlags = 64; pub const OptixRayFlags_OPTIX_RAY_FLAG_CULL_ENFORCED_ANYHIT: OptixRayFlags = 128; -pub type OptixRayFlags = ::std::os::raw::c_uint; +pub type OptixRayFlags = ::std::os::raw::c_int; pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_NONE: OptixTransformType = 0; pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_STATIC_TRANSFORM: OptixTransformType = 1; pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_MATRIX_MOTION_TRANSFORM: OptixTransformType = 2; pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_SRT_MOTION_TRANSFORM: OptixTransformType = 3; pub const OptixTransformType_OPTIX_TRANSFORM_TYPE_INSTANCE: OptixTransformType = 4; -pub type OptixTransformType = ::std::os::raw::c_uint; +pub type OptixTransformType = ::std::os::raw::c_int; pub mod OptixTraversableGraphFlags { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_ANY: Type = 0; pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_GAS: Type = 1; pub const OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING: Type = 2; } pub mod OptixCompileOptimizationLevel { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_COMPILE_OPTIMIZATION_DEFAULT: Type = 0; pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_0: Type = 9024; pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_1: Type = 9025; @@ -668,7 +668,7 @@ pub mod OptixCompileOptimizationLevel { pub const OPTIX_COMPILE_OPTIMIZATION_LEVEL_3: Type = 9027; } pub mod OptixCompileDebugLevel { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_COMPILE_DEBUG_LEVEL_DEFAULT: Type = 0; pub const OPTIX_COMPILE_DEBUG_LEVEL_NONE: Type = 9040; pub const OPTIX_COMPILE_DEBUG_LEVEL_LINEINFO: Type = 9041; @@ -709,7 +709,7 @@ impl Default for OptixModuleCompileOptions { } } pub mod OptixProgramGroupKind { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_PROGRAM_GROUP_KIND_RAYGEN: Type = 9249; pub const OPTIX_PROGRAM_GROUP_KIND_MISS: Type = 9250; pub const OPTIX_PROGRAM_GROUP_KIND_EXCEPTION: Type = 9251; @@ -717,7 +717,7 @@ pub mod OptixProgramGroupKind { pub const OPTIX_PROGRAM_GROUP_KIND_CALLABLES: Type = 9253; } pub const OptixProgramGroupFlags_OPTIX_PROGRAM_GROUP_FLAGS_NONE: OptixProgramGroupFlags = 0; -pub type OptixProgramGroupFlags = ::std::os::raw::c_uint; +pub type OptixProgramGroupFlags = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] pub struct OptixProgramGroupSingleModule { @@ -841,7 +841,7 @@ pub const OptixExceptionCodes_OPTIX_EXCEPTION_CODE_UNSUPPORTED_DATA_ACCESS: Opti -32; pub type OptixExceptionCodes = ::std::os::raw::c_int; pub mod OptixExceptionFlags { - pub type Type = ::std::os::raw::c_uint; + pub type Type = ::std::os::raw::c_int; pub const OPTIX_EXCEPTION_FLAG_NONE: Type = 0; pub const OPTIX_EXCEPTION_FLAG_STACK_OVERFLOW: Type = 1; pub const OPTIX_EXCEPTION_FLAG_TRACE_DEPTH: Type = 2; @@ -920,7 +920,7 @@ pub struct OptixStackSizes { } pub const OptixQueryFunctionTableOptions_OPTIX_QUERY_FUNCTION_TABLE_OPTION_DUMMY: OptixQueryFunctionTableOptions = 0; -pub type OptixQueryFunctionTableOptions = ::std::os::raw::c_uint; +pub type OptixQueryFunctionTableOptions = ::std::os::raw::c_int; pub type OptixQueryFunctionTable_t = ::std::option::Option< unsafe extern "C" fn( abiId: ::std::os::raw::c_int, @@ -1557,7 +1557,7 @@ pub const OptixTransformByteAlignment: size_t = 64; pub const OptixVersion: size_t = 70300; pub const OptixBuildInputSize: size_t = 1032; pub const OptixShaderBindingTableSize: size_t = 64; -#[repr(u32)] +#[repr(i32)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum OptixGeometryFlags { None = 0, diff --git a/crates/optix_device/Cargo.toml b/crates/optix_device/Cargo.toml index 957273c3..02e3131b 100644 --- a/crates/optix_device/Cargo.toml +++ b/crates/optix_device/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" authors = ["Anders Langlands ", "Riccardo D'Ambrosio "] [dependencies] -cuda_std = { version = "0.1", path = "../cuda_std" } +cuda_std = { version = "0.2", path = "../cuda_std" } glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false } From 799d79be1a3114af33c62da1084d8cea43ae53a6 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Mon, 13 Dec 2021 04:05:33 -0500 Subject: [PATCH 093/100] Feat: second pass for fixing conflicts --- crates/blastoff/Cargo.toml | 2 +- crates/blastoff/src/level1.rs | 8 ++--- crates/cust/Cargo.toml | 4 ++- crates/cust/src/memory/device/device_box.rs | 6 ++-- crates/cust/src/memory/mod.rs | 29 ++++------------- crates/cust/src/memory/pointer.rs | 12 +++++++ crates/cust/src/nvtx.rs | 1 + crates/cust_core/Cargo.toml | 6 ++-- crates/cust_core/src/lib.rs | 32 ++++++++++++------- crates/optix/Cargo.toml | 1 + crates/optix/src/acceleration.rs | 12 +++---- crates/optix/src/denoiser.rs | 2 +- examples/cuda/cpu/path_tracer/Cargo.toml | 3 +- examples/cuda/cpu/path_tracer/src/common.rs | 2 +- examples/cuda/cpu/path_tracer/src/cpu/mod.rs | 2 +- .../cuda/cpu/path_tracer/src/cuda/data.rs | 2 +- examples/cuda/cpu/path_tracer/src/cuda/mod.rs | 27 +++++++++------- examples/cuda/cpu/path_tracer/src/main.rs | 2 +- examples/cuda/cpu/path_tracer/src/renderer.rs | 2 +- examples/cuda/cpu/path_tracer/src/viewer.rs | 2 +- 20 files changed, 84 insertions(+), 73 deletions(-) diff --git a/crates/blastoff/Cargo.toml b/crates/blastoff/Cargo.toml index e7083efc..453f488b 100644 --- a/crates/blastoff/Cargo.toml +++ b/crates/blastoff/Cargo.toml @@ -8,5 +8,5 @@ repository = "https://github.com/Rust-GPU/Rust-CUDA" [dependencies] bitflags = "1.3.2" cublas_sys = { version = "0.1", path = "../cublas_sys" } -cust = { version = "0.2", path = "../cust", features = ["num-complex"] } +cust = { version = "0.2", path = "../cust", features = ["impl_num_complex"] } num-complex = "0.4.0" diff --git a/crates/blastoff/src/level1.rs b/crates/blastoff/src/level1.rs index 8d9cdf48..eec1c768 100644 --- a/crates/blastoff/src/level1.rs +++ b/crates/blastoff/src/level1.rs @@ -46,9 +46,9 @@ impl CublasContext { Ok(T::amin( ctx.raw, x.len() as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), stride.unwrap_or(1) as i32, - result.as_device_ptr().as_raw_mut(), + result.as_device_ptr().as_mut_ptr(), ) .to_result()?) }) @@ -108,9 +108,9 @@ impl CublasContext { Ok(T::amax( ctx.raw, x.len() as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), stride.unwrap_or(1) as i32, - result.as_device_ptr().as_raw_mut(), + result.as_device_ptr().as_mut_ptr(), ) .to_result()?) }) diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index 476bd863..94a5434e 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -20,10 +20,12 @@ num-complex = { version = "0.4", optional = true } vek = { version = "0.15.1", optional = true, default-features = false } [features] -default-features = ["vek", "impl_glam", "impl_mint"] +default= ["vek", "impl_glam", "impl_mint"] impl_glam = ["cust_core/glam", "glam"] impl_mint = ["cust_core/mint", "mint"] impl_vek = ["cust_core/vek", "vek"] +impl_half = ["cust_core/half"] +impl_num_complex = ["cust_core/num-complex", "num-complex"] [build-dependencies] find_cuda_helper = { path = "../find_cuda_helper", version = "0.2" } diff --git a/crates/cust/src/memory/device/device_box.rs b/crates/cust/src/memory/device/device_box.rs index f018c75c..9bb576c1 100644 --- a/crates/cust/src/memory/device/device_box.rs +++ b/crates/cust/src/memory/device/device_box.rs @@ -102,7 +102,7 @@ impl DeviceBox { /// assert_eq!(0, value); /// ``` pub unsafe fn zeroed() -> CudaResult { - let mut new_box = DeviceBox::uninitialized()?; + let new_box = DeviceBox::uninitialized()?; if mem::size_of::() != 0 { cuda::cuMemsetD8_v2(new_box.as_device_ptr().as_raw(), 0, mem::size_of::()) .to_result()?; @@ -406,8 +406,8 @@ mod test_device_box { #[test] fn test_device_pointer_implements_traits_safely() { let _context = crate::quick_init().unwrap(); - let mut x = DeviceBox::new(&5u64).unwrap(); - let mut y = DeviceBox::new(&0u64).unwrap(); + let x = DeviceBox::new(&5u64).unwrap(); + let y = DeviceBox::new(&0u64).unwrap(); // If the impls dereference the pointer, this should segfault. let _ = Ord::cmp(&x.as_device_ptr(), &y.as_device_ptr()); diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index 773213dd..f0e89dee 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -102,26 +102,17 @@ pub trait GpuBuffer: private::Sealed { } impl GpuBuffer for DeviceBuffer { - unsafe fn as_device_ptr(&self) -> DevicePointer { + fn as_device_ptr(&self) -> DevicePointer { self.as_ptr() } - fn as_device_ptr_mut(&mut self) -> DevicePointer { - self.as_mut_ptr() - } - fn len(&self) -> usize { (**self).len() } } impl GpuBuffer for UnifiedBuffer { - unsafe fn as_device_ptr(&self) -> DevicePointer { - DevicePointer::from_raw(self.as_ptr() as u64) - } - - fn as_device_ptr_mut(&mut self) -> DevicePointer { - // SAFETY: unified pointers can be dereferenced from the gpu. + fn as_device_ptr(&self) -> DevicePointer { DevicePointer::from_raw(self.as_ptr() as u64) } @@ -137,22 +128,13 @@ pub trait GpuBox: private::Sealed { } impl GpuBox for DeviceBox { - unsafe fn as_device_ptr(&self) -> DevicePointer { + fn as_device_ptr(&self) -> DevicePointer { self.ptr } - - fn as_device_ptr_mut(&mut self) -> DevicePointer { - DeviceBox::as_device_ptr(self) - } } impl GpuBox for UnifiedBox { - unsafe fn as_device_ptr(&self) -> DevicePointer { - DevicePointer::from_raw(self.ptr.as_raw() as u64) - } - - fn as_device_ptr_mut(&mut self) -> DevicePointer { - // SAFETY: unified pointers can be dereferenced from the gpu. + fn as_device_ptr(&self) -> DevicePointer { DevicePointer::from_raw(self.ptr.as_raw() as u64) } } @@ -189,7 +171,7 @@ impl DeviceMemory for DeviceVariable { impl DeviceMemory for DeviceBuffer { fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr { - unsafe { self.as_device_ptr().as_raw() } + self.as_device_ptr().as_raw() } fn size_in_bytes(&self) -> usize { @@ -218,6 +200,7 @@ mod private { } /// Simple wrapper over cuMemcpyHtoD_v2 +#[allow(clippy::missing_safety_doc)] pub unsafe fn memcpy_htod( d_ptr: cust_raw::CUdeviceptr, src_ptr: *const c_void, diff --git a/crates/cust/src/memory/pointer.rs b/crates/cust/src/memory/pointer.rs index 72a2b288..09e57464 100644 --- a/crates/cust/src/memory/pointer.rs +++ b/crates/cust/src/memory/pointer.rs @@ -38,6 +38,18 @@ impl Pointer for DevicePointer { } impl DevicePointer { + /// Returns a rust [`pointer`] created from this pointer, meant for FFI purposes. + /// **The pointer is not dereferenceable from the CPU!** + pub fn as_ptr(&self) -> *const T { + self.ptr as *const T + } + + /// Returns a rust [`pointer`] created from this pointer, meant for FFI purposes. + /// **The pointer is not dereferenceable from the CPU!** + pub fn as_mut_ptr(&self) -> *mut T { + self.ptr as *mut T + } + /// Returns the contained CUdeviceptr. pub fn as_raw(&self) -> CUdeviceptr { self.ptr diff --git a/crates/cust/src/nvtx.rs b/crates/cust/src/nvtx.rs index e69de29b..8b137891 100644 --- a/crates/cust/src/nvtx.rs +++ b/crates/cust/src/nvtx.rs @@ -0,0 +1 @@ + diff --git a/crates/cust_core/Cargo.toml b/crates/cust_core/Cargo.toml index b6eb90a5..7c40f1e5 100644 --- a/crates/cust_core/Cargo.toml +++ b/crates/cust_core/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" vek = { version = "0.15.1", default-features=false, features=["libm"], optional = true } glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false, optional=true } mint = { version = "^0.5", optional = true } +half = { version = "1.8", optional = true } +num-complex = { version = "0.4", optional = true } [features] -default-features = ["vek", "glam", "mint"] - - +default = ["vek", "glam", "mint"] diff --git a/crates/cust_core/src/lib.rs b/crates/cust_core/src/lib.rs index aaf04261..e24c5015 100644 --- a/crates/cust_core/src/lib.rs +++ b/crates/cust_core/src/lib.rs @@ -123,15 +123,15 @@ unsafe impl< { } -macro_rules! impl_device_copy_vek { - ($($strukt:ident),* $(,)?) => { +macro_rules! impl_device_copy_generic { + ($($($strukt:ident)::+),* $(,)?) => { $( - unsafe impl DeviceCopy for $strukt {} + unsafe impl DeviceCopy for $($strukt)::+ {} )* } } -macro_rules! impl_device_copy_glam { +macro_rules! impl_device_copy { ($($strukt:ty),* $(,)?) => { $( unsafe impl DeviceCopy for $strukt {} @@ -143,7 +143,7 @@ macro_rules! impl_device_copy_glam { use vek::*; #[cfg(feature = "vek")] -impl_device_copy_vek! { +impl_device_copy_generic! { Vec2, Vec3, Vec4, Extent2, Extent3, Rgb, Rgba, Mat2, Mat3, Mat4, CubicBezier2, CubicBezier3, @@ -151,15 +151,23 @@ impl_device_copy_vek! { } #[cfg(feature = "glam")] -impl_device_copy_glam! { +impl_device_copy! { glam::Vec2, glam::Vec3, glam::Vec4, glam::IVec2, glam::IVec3, glam::IVec4, } #[cfg(feature = "mint")] -impl_device_copy_glam! { - mint::Vector2, mint::Vector2, mint::Vector2, - mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, mint::Vector3, - mint::Vector4, mint::Vector4, mint::Vector4, - mint::ColumnMatrix2, mint::ColumnMatrix3, mint::ColumnMatrix4, mint::ColumnMatrix3x4, - mint::RowMatrix2, mint::RowMatrix3, mint::RowMatrix4, mint::RowMatrix3x4, +impl_device_copy_generic! { + mint::Vector2, mint::Vector3, mint::Vector4, + mint::ColumnMatrix2, mint::ColumnMatrix3, mint::ColumnMatrix4, mint::ColumnMatrix3x4, + mint::RowMatrix2, mint::RowMatrix3, mint::RowMatrix4, mint::RowMatrix3x4, +} + +#[cfg(feature = "half")] +unsafe impl DeviceCopy for half::f16 {} +#[cfg(feature = "half")] +unsafe impl DeviceCopy for half::bf16 {} + +#[cfg(feature = "num-complex")] +impl_device_copy_generic! { + num_complex::Complex } diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 06847705..82d1c75b 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -13,6 +13,7 @@ optix72 = [] optix73 = [] default=["optix73", "impl_glam"] impl_glam=["cust/impl_glam", "glam"] +impl_half=["cust/impl_half", "half"] [dependencies] cust = { version = "0.2", path = "../cust", features=["impl_mint"] } diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index 91365208..921745af 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -1180,23 +1180,23 @@ pub trait Vertex: cust::memory::DeviceCopy { const STRIDE: u32 = 0; } -#[cfg(feature = "half")] +#[cfg(feature = "impl_half")] impl Vertex for [half::f16; 2] { const FORMAT: VertexFormat = VertexFormat::Half2; } -#[cfg(feature = "half")] +#[cfg(feature = "impl_half")] impl Vertex for [half::f16; 3] { const FORMAT: VertexFormat = VertexFormat::Half3; } -#[cfg(feature = "half")] -impl Vertex for mint::Vector2 { +#[cfg(feature = "impl_half")] +impl Vertex for mint::Vector2 { const FORMAT: VertexFormat = VertexFormat::Half2; } -#[cfg(feature = "half")] -impl Vertex for mint::Vector3 { +#[cfg(feature = "impl_half")] +impl Vertex for mint::Vector3 { const FORMAT: VertexFormat = VertexFormat::Half3; } diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 28d87134..baf0176b 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -375,7 +375,7 @@ impl Denoiser { let raw_params = parameters.to_raw(); let mut out = input_image.to_raw(); - out.data = out_buffer.as_device_ptr().as_raw_mut() as u64; + out.data = out_buffer.as_device_ptr().as_raw() as u64; let layer = sys::OptixDenoiserLayer { input: input_image.to_raw(), diff --git a/examples/cuda/cpu/path_tracer/Cargo.toml b/examples/cuda/cpu/path_tracer/Cargo.toml index 5851fee3..d4aac270 100644 --- a/examples/cuda/cpu/path_tracer/Cargo.toml +++ b/examples/cuda/cpu/path_tracer/Cargo.toml @@ -4,8 +4,9 @@ version = "0.1.0" edition = "2018" [dependencies] +vek = "0.15" bytemuck = { version = "1.7.2", features = ["derive"] } -cust = { version = "0.2", path = "../../../../crates/cust", features = ["vek"] } +cust = { version = "0.2", path = "../../../../crates/cust", features = ["impl_vek"] } image = "0.23.14" path_tracer_gpu = { path = "../../gpu/path_tracer_gpu" } gpu_rand = { version = "0.1", path = "../../../../crates/gpu_rand" } diff --git a/examples/cuda/cpu/path_tracer/src/common.rs b/examples/cuda/cpu/path_tracer/src/common.rs index d875b9b7..7f342492 100644 --- a/examples/cuda/cpu/path_tracer/src/common.rs +++ b/examples/cuda/cpu/path_tracer/src/common.rs @@ -1,8 +1,8 @@ -use cust::vek::{Vec2, Vec3}; use glutin::event::{ ElementState, Event, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent, }; use path_tracer_gpu::Viewport; +use vek::{Vec2, Vec3}; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Camera { diff --git a/examples/cuda/cpu/path_tracer/src/cpu/mod.rs b/examples/cuda/cpu/path_tracer/src/cpu/mod.rs index ba7d982b..3b67efeb 100644 --- a/examples/cuda/cpu/path_tracer/src/cpu/mod.rs +++ b/examples/cuda/cpu/path_tracer/src/cpu/mod.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use cust::vek::{Clamp, Vec2, Vec3}; use gpu_rand::{DefaultRand, GpuRand}; use imgui::Ui; use path_tracer_gpu::{ @@ -8,6 +7,7 @@ use path_tracer_gpu::{ }; use rayon::prelude::*; use sysinfo::{ProcessorExt, System, SystemExt}; +use vek::{Clamp, Vec2, Vec3}; use crate::{common::Camera, cuda::SEED}; diff --git a/examples/cuda/cpu/path_tracer/src/cuda/data.rs b/examples/cuda/cpu/path_tracer/src/cuda/data.rs index 79353091..c487e6c2 100644 --- a/examples/cuda/cpu/path_tracer/src/cuda/data.rs +++ b/examples/cuda/cpu/path_tracer/src/cuda/data.rs @@ -3,8 +3,8 @@ use cust::{ error::CudaResult, memory::{DeviceBuffer, DeviceCopy, UnifiedBuffer}, util::SliceExt, - vek::{num_traits::Zero, Vec2, Vec3}, }; +use vek::{num_traits::Zero, Vec2, Vec3}; use gpu_rand::DefaultRand; use path_tracer_gpu::{material::MaterialKind, scene::Scene, Object, Viewport}; diff --git a/examples/cuda/cpu/path_tracer/src/cuda/mod.rs b/examples/cuda/cpu/path_tracer/src/cuda/mod.rs index 5d69f5b1..25e51590 100644 --- a/examples/cuda/cpu/path_tracer/src/cuda/mod.rs +++ b/examples/cuda/cpu/path_tracer/src/cuda/mod.rs @@ -11,13 +11,13 @@ use cust::{ event::{Event, EventFlags}, function::{BlockSize, GridSize}, prelude::*, - vek::{Vec2, Vec3}, }; use optix::{ - context::OptixContext, + context::DeviceContext, denoiser::{Denoiser, DenoiserModelKind, Image, ImageFormat}, }; use path_tracer_gpu::scene::Scene; +use vek::{Vec2, Vec3}; /// Seed for the random states pub const SEED: u64 = 932174513921034; @@ -33,7 +33,7 @@ pub struct CudaRenderer { stream: Stream, module: Module, denoiser: Denoiser, - _optix_context: OptixContext, + _optix_context: DeviceContext, _context: Context, buffers: CudaRendererBuffers, @@ -45,7 +45,7 @@ impl CudaRenderer { let context = cust::quick_init()?; optix::init().unwrap(); - let optix_context = OptixContext::new(&context).unwrap(); + let optix_context = DeviceContext::new(&context, false).unwrap(); let module = Module::from_str(PTX)?; let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?; @@ -93,7 +93,8 @@ impl CudaRenderer { Ok(self .denoiser - .setup_state(&self.stream, new_size.x as u32, new_size.y as u32, false)?) + .setup_state(&self.stream, new_size.x as u32, new_size.y as u32, false) + .unwrap()) } /// calculate an optimal launch configuration for an image kernel @@ -144,13 +145,15 @@ impl CudaRenderer { height, ); - self.denoiser.invoke( - stream, - Default::default(), - input_image, - Default::default(), - &mut self.buffers.denoised_buffer, - )?; + self.denoiser + .invoke( + stream, + Default::default(), + input_image, + Default::default(), + &mut self.buffers.denoised_buffer, + ) + .unwrap(); self.buffers.denoised_buffer.as_device_ptr() } else { diff --git a/examples/cuda/cpu/path_tracer/src/main.rs b/examples/cuda/cpu/path_tracer/src/main.rs index e7a6d3fd..58b2f7b9 100644 --- a/examples/cuda/cpu/path_tracer/src/main.rs +++ b/examples/cuda/cpu/path_tracer/src/main.rs @@ -5,7 +5,6 @@ pub mod renderer; pub mod viewer; use common::Camera; -use cust::vek::Vec3; use path_tracer_gpu::{ material::{DiffuseMaterial, MaterialKind, MetallicMaterial}, scene::Scene, @@ -13,6 +12,7 @@ use path_tracer_gpu::{ Object, }; use std::error::Error; +use vek::Vec3; pub const WIDTH: u32 = 1920; pub const HEIGHT: u32 = 1080; diff --git a/examples/cuda/cpu/path_tracer/src/renderer.rs b/examples/cuda/cpu/path_tracer/src/renderer.rs index 0b86c83e..0a5c631e 100644 --- a/examples/cuda/cpu/path_tracer/src/renderer.rs +++ b/examples/cuda/cpu/path_tracer/src/renderer.rs @@ -1,4 +1,4 @@ -use cust::vek::Vec2; +use vek::Vec2; use glutin::{event::Event, event_loop::ControlFlow}; use imgui::Ui; use path_tracer_gpu::scene::Scene; diff --git a/examples/cuda/cpu/path_tracer/src/viewer.rs b/examples/cuda/cpu/path_tracer/src/viewer.rs index 2c74f37c..4943fc55 100644 --- a/examples/cuda/cpu/path_tracer/src/viewer.rs +++ b/examples/cuda/cpu/path_tracer/src/viewer.rs @@ -1,4 +1,3 @@ -use cust::vek::Vec2; use glium::{ implement_vertex, index::{NoIndices, PrimitiveType}, @@ -16,6 +15,7 @@ use imgui::Condition; use imgui_winit_support::{HiDpiMode, WinitPlatform}; use path_tracer_gpu::scene::Scene; use std::time::Instant; +use vek::Vec2; use crate::{common::Camera, renderer::Renderer, HEIGHT, WIDTH}; From e858fdcaf37c8beb0df74cc84dfb79300eff7eb3 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Mon, 13 Dec 2021 04:33:09 -0500 Subject: [PATCH 094/100] Feat: delete as_ptr and as_mut_ptr on DeviceSlice --- crates/cust/src/memory/device/device_slice.rs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/crates/cust/src/memory/device/device_slice.rs b/crates/cust/src/memory/device/device_slice.rs index dc54f2ae..bc65b471 100644 --- a/crates/cust/src/memory/device/device_slice.rs +++ b/crates/cust/src/memory/device/device_slice.rs @@ -151,30 +151,6 @@ impl DeviceSlice { } */ - /// Returns a `DevicePointer` to the buffer. - /// - /// The caller must ensure that the buffer outlives the returned pointer, or it will end up - /// pointing to garbage. - /// - /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers - /// cannot be invalidated in that manner, but other types may be added in the future which can - /// reallocate. - pub fn as_ptr(&self) -> DevicePointer { - self.ptr - } - - /// Returns a `DevicePointer` to the buffer. - /// - /// The caller must ensure that the buffer outlives the returned pointer, or it will end up - /// pointing to garbage. - /// - /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers - /// cannot be invalidated in that manner, but other types may be added in the future which can - /// reallocate. - pub fn as_mut_ptr(&mut self) -> DevicePointer { - self.ptr - } - /// Forms a slice from a `DevicePointer` and a length. /// /// The `len` argument is the number of _elements_, not the number of bytes. From d0cdcbde8824623b7cbf2a665416a83c84a43b8c Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Mon, 13 Dec 2021 04:33:09 -0500 Subject: [PATCH 095/100] Revert "Feat: delete as_ptr and as_mut_ptr on DeviceSlice" This reverts commit e858fdcaf37c8beb0df74cc84dfb79300eff7eb3. --- crates/cust/src/memory/device/device_slice.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/cust/src/memory/device/device_slice.rs b/crates/cust/src/memory/device/device_slice.rs index bc65b471..dc54f2ae 100644 --- a/crates/cust/src/memory/device/device_slice.rs +++ b/crates/cust/src/memory/device/device_slice.rs @@ -151,6 +151,30 @@ impl DeviceSlice { } */ + /// Returns a `DevicePointer` to the buffer. + /// + /// The caller must ensure that the buffer outlives the returned pointer, or it will end up + /// pointing to garbage. + /// + /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers + /// cannot be invalidated in that manner, but other types may be added in the future which can + /// reallocate. + pub fn as_ptr(&self) -> DevicePointer { + self.ptr + } + + /// Returns a `DevicePointer` to the buffer. + /// + /// The caller must ensure that the buffer outlives the returned pointer, or it will end up + /// pointing to garbage. + /// + /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers + /// cannot be invalidated in that manner, but other types may be added in the future which can + /// reallocate. + pub fn as_mut_ptr(&mut self) -> DevicePointer { + self.ptr + } + /// Forms a slice from a `DevicePointer` and a length. /// /// The `len` argument is the number of _elements_, not the number of bytes. From c48d8ee8c3dc36a41e330f9cab603c18823ee62f Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Wed, 15 Dec 2021 12:54:01 -0500 Subject: [PATCH 096/100] Feat: experiment with deleting as_ptr and as_mut_ptr --- .../cust/src/memory/device/device_buffer.rs | 5 ++ crates/cust/src/memory/device/device_slice.rs | 59 +++++++------------ crates/cust/src/memory/mod.rs | 4 +- .../examples/ex03_window/src/renderer.rs | 4 +- .../optix/examples/ex04_mesh/src/renderer.rs | 4 +- crates/optix/src/acceleration.rs | 38 ++++++------ crates/optix/src/shader_binding_table.rs | 10 ++-- 7 files changed, 56 insertions(+), 68 deletions(-) diff --git a/crates/cust/src/memory/device/device_buffer.rs b/crates/cust/src/memory/device/device_buffer.rs index e5f7b239..20a021e4 100644 --- a/crates/cust/src/memory/device/device_buffer.rs +++ b/crates/cust/src/memory/device/device_buffer.rs @@ -226,6 +226,11 @@ impl DeviceBuffer { uninit.async_copy_from(slice, stream)?; Ok(uninit) } + + /// Explicitly creates a [`DeviceSlice`] from this buffer. + pub fn as_slice(&self) -> &DeviceSlice { + self + } } impl Deref for DeviceBuffer { diff --git a/crates/cust/src/memory/device/device_slice.rs b/crates/cust/src/memory/device/device_slice.rs index dc54f2ae..0e486156 100644 --- a/crates/cust/src/memory/device/device_slice.rs +++ b/crates/cust/src/memory/device/device_slice.rs @@ -1,5 +1,3 @@ -use cuda::CUdeviceptr; - use crate::error::{CudaResult, ToResult}; use crate::memory::device::AsyncCopyDestination; use crate::memory::device::{CopyDestination, DeviceBuffer}; @@ -74,8 +72,8 @@ impl DeviceSlice { /// let a = DeviceBuffer::from_slice(&[1, 2, 3]).unwrap(); /// println!("{:p}", a.as_ptr()); /// ``` - pub fn as_device_ptr(&self) -> CUdeviceptr { - self.ptr.as_raw() + pub fn as_device_ptr(&self) -> DevicePointer { + self.ptr } /* TODO (AL): keep these? @@ -151,30 +149,6 @@ impl DeviceSlice { } */ - /// Returns a `DevicePointer` to the buffer. - /// - /// The caller must ensure that the buffer outlives the returned pointer, or it will end up - /// pointing to garbage. - /// - /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers - /// cannot be invalidated in that manner, but other types may be added in the future which can - /// reallocate. - pub fn as_ptr(&self) -> DevicePointer { - self.ptr - } - - /// Returns a `DevicePointer` to the buffer. - /// - /// The caller must ensure that the buffer outlives the returned pointer, or it will end up - /// pointing to garbage. - /// - /// Modifying `DeviceBuffer` is guaranteed not to cause its buffer to be reallocated, so pointers - /// cannot be invalidated in that manner, but other types may be added in the future which can - /// reallocate. - pub fn as_mut_ptr(&mut self) -> DevicePointer { - self.ptr - } - /// Forms a slice from a `DevicePointer` and a length. /// /// The `len` argument is the number of _elements_, not the number of bytes. @@ -256,8 +230,12 @@ impl + AsMut<[T]> + ?Sized> CopyDestination for let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyDtoH_v2(val.as_mut_ptr() as *mut c_void, self.as_device_ptr(), size) - .to_result()? + cuda::cuMemcpyDtoH_v2( + val.as_mut_ptr() as *mut c_void, + self.as_device_ptr().as_raw(), + size, + ) + .to_result()? } } Ok(()) @@ -272,7 +250,8 @@ impl CopyDestination> for DeviceSlice { let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyDtoD_v2(self.ptr.as_raw(), val.as_device_ptr(), size).to_result()? + cuda::cuMemcpyDtoD_v2(self.ptr.as_raw(), val.as_device_ptr().as_raw(), size) + .to_result()? } } Ok(()) @@ -286,8 +265,12 @@ impl CopyDestination> for DeviceSlice { let size = mem::size_of::() * self.len(); if size != 0 { unsafe { - cuda::cuMemcpyDtoD_v2(val.as_device_ptr(), self.as_device_ptr(), size) - .to_result()? + cuda::cuMemcpyDtoD_v2( + val.as_device_ptr().as_raw(), + self.as_device_ptr().as_raw(), + size, + ) + .to_result()? } } Ok(()) @@ -334,7 +317,7 @@ impl + AsMut<[T]> + ?Sized> AsyncCopyDestination if size != 0 { cuda::cuMemcpyDtoHAsync_v2( val.as_mut_ptr() as *mut c_void, - self.as_device_ptr(), + self.as_device_ptr().as_raw(), size, stream.as_inner(), ) @@ -352,8 +335,8 @@ impl AsyncCopyDestination> for DeviceSlice { let size = mem::size_of::() * self.len(); if size != 0 { cuda::cuMemcpyDtoDAsync_v2( - self.as_device_ptr(), - val.as_device_ptr(), + self.as_device_ptr().as_raw(), + val.as_device_ptr().as_raw(), size, stream.as_inner(), ) @@ -370,8 +353,8 @@ impl AsyncCopyDestination> for DeviceSlice { let size = mem::size_of::() * self.len(); if size != 0 { cuda::cuMemcpyDtoDAsync_v2( - val.as_device_ptr(), - self.as_device_ptr(), + val.as_device_ptr().as_raw(), + self.as_device_ptr().as_raw(), size, stream.as_inner(), ) diff --git a/crates/cust/src/memory/mod.rs b/crates/cust/src/memory/mod.rs index f0e89dee..1caf4b56 100644 --- a/crates/cust/src/memory/mod.rs +++ b/crates/cust/src/memory/mod.rs @@ -103,7 +103,7 @@ pub trait GpuBuffer: private::Sealed { impl GpuBuffer for DeviceBuffer { fn as_device_ptr(&self) -> DevicePointer { - self.as_ptr() + self.as_slice().as_device_ptr() } fn len(&self) -> usize { @@ -181,7 +181,7 @@ impl DeviceMemory for DeviceBuffer { impl DeviceMemory for DeviceSlice { fn as_raw_ptr(&self) -> cust_raw::CUdeviceptr { - self.as_device_ptr() + self.as_device_ptr().as_raw() } fn size_in_bytes(&self) -> usize { diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 3973f1da..82997c9e 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -144,7 +144,7 @@ impl Renderer { let launch_params = DeviceVariable::new(LaunchParams { frame_id: 0, - color_buffer: color_buffer.as_ptr(), + color_buffer: color_buffer.as_device_ptr(), fb_size: Point2i { x: width as i32, y: height as i32, @@ -169,7 +169,7 @@ impl Renderer { self.color_buffer = unsafe { DeviceBuffer::uninitialized((width * height) as usize)? }; self.launch_params.fb_size.x = width as i32; self.launch_params.fb_size.y = height as i32; - self.launch_params.color_buffer = self.color_buffer.as_ptr(); + self.launch_params.color_buffer = self.color_buffer.as_device_ptr(); Ok(()) } diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 073ee1ce..2e4aaeed 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -191,7 +191,7 @@ impl Renderer { let launch_params = DeviceVariable::new(LaunchParams { frame: Frame { - color_buffer: color_buffer.as_ptr(), + color_buffer: color_buffer.as_device_ptr(), size: ivec2(width as i32, height as i32), }, camera: RenderCamera { @@ -222,7 +222,7 @@ impl Renderer { self.color_buffer = unsafe { DeviceBuffer::uninitialized((width * height) as usize)? }; self.launch_params.frame.size.x = width as i32; self.launch_params.frame.size.y = height as i32; - self.launch_params.frame.color_buffer = self.color_buffer.as_ptr(); + self.launch_params.frame.color_buffer = self.color_buffer.as_device_ptr(); Ok(()) } diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index 921745af..f65c5ba6 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -574,9 +574,9 @@ pub unsafe fn accel_build( accel_options.as_ptr() as *const _, build_sys.as_ptr(), build_sys.len() as u32, - temp_buffer.as_device_ptr(), + temp_buffer.as_device_ptr().as_raw(), temp_buffer.len(), - output_buffer.as_device_ptr(), + output_buffer.as_device_ptr().as_raw(), output_buffer.len(), &mut traversable_handle as *mut _ as *mut _, properties.as_ptr() as *const _, @@ -670,7 +670,7 @@ pub unsafe fn accel_compact( ctx.raw, stream.as_inner(), input_handle.inner, - output_buffer.as_device_ptr(), + output_buffer.as_device_ptr().as_raw(), output_buffer.len(), &mut traversable_handle as *mut _ as *mut _, )) @@ -1019,10 +1019,10 @@ impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { ) -> Result> { // TODO (AL): Do some sanity checking on the values here let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); let num_width_buffers = width_buffers.len() as u32; - let d_width_buffers: Vec<_> = width_buffers.iter().map(|b| b.as_device_ptr()).collect(); + let d_width_buffers: Vec<_> = width_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); Ok(CurveArray { curve_type, @@ -1093,7 +1093,7 @@ impl<'v, 'w, 'i> BuildInput for CurveArray<'v, 'w, 'i> { widthStrideInBytes: self.width_stride_in_bytes, normalBuffers: std::ptr::null(), normalStrideInBytes: 0, - indexBuffer: self.index_buffer.as_device_ptr(), + indexBuffer: self.index_buffer.as_device_ptr().as_raw(), indexStrideInBytes: self.index_stride_in_bytes, flag: self.flags as u32, primitiveIndexOffset: self.primitive_index_offset, @@ -1270,7 +1270,7 @@ impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { pub fn new(vertex_buffers: &[&'v DeviceSlice], geometry_flags: &'g [GeometryFlags]) -> Self { // TODO (AL): do some sanity checking on the slice lengths here let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); TriangleArray { vertex_buffers: PhantomData, num_vertices, @@ -1349,7 +1349,7 @@ impl<'v, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'i, V, I> { geometry_flags: &[GeometryFlags], ) -> Self { let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr()).collect(); + let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); IndexedTriangleArray { vertex_buffers: PhantomData, num_vertices, @@ -1385,7 +1385,7 @@ impl<'v, 'i, V: Vertex, I: IndexTriple> BuildInput for IndexedTriangleArray<'v, numVertices: self.num_vertices, vertexFormat: V::FORMAT as _, vertexStrideInBytes: V::STRIDE, - indexBuffer: self.index_buffer.as_device_ptr(), + indexBuffer: self.index_buffer.as_device_ptr().as_raw(), numIndexTriplets: self.index_buffer.len() as u32, indexFormat: I::FORMAT as _, indexStrideInBytes: I::STRIDE, @@ -1446,7 +1446,7 @@ impl<'a, 's> CustomPrimitiveArray<'a, 's> { flags: &[GeometryFlags], ) -> Result> { let num_primitives = aabb_buffers.len() as u32; - let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr()).collect(); + let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); Ok(CustomPrimitiveArray { aabb_buffers, @@ -1505,7 +1505,7 @@ impl<'a, 's> BuildInput for CustomPrimitiveArray<'a, 's> { sbtIndexOffsetBuffer: if let Some(sbt_index_offset_buffer) = self.sbt_index_offset_buffer { - sbt_index_offset_buffer.as_device_ptr() + sbt_index_offset_buffer.as_device_ptr().as_raw() } else { 0 }, @@ -1632,7 +1632,7 @@ impl<'i, 'a> BuildInput for InstanceArray<'i, 'a> { type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCES, input: sys::OptixBuildInputUnion { instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), + instances: self.instances.as_device_ptr().as_raw(), numInstances: self.instances.len() as u32, }) } @@ -1679,7 +1679,7 @@ impl<'i> BuildInput for InstancePointerArray<'i> { type_: sys::OptixBuildInputType_OPTIX_BUILD_INPUT_TYPE_INSTANCE_POINTERS, input: sys::OptixBuildInputUnion { instance_array: std::mem::ManuallyDrop::new(sys::OptixBuildInputInstanceArray { - instances: self.instances.as_device_ptr(), + instances: self.instances.as_device_ptr().as_raw(), numInstances: self.instances.len() as u32, }) } @@ -1819,7 +1819,7 @@ impl MatrixMotionTransform { // get the offset of the matrix data from the base of the struct let transform_ptr = buf - .as_ptr() + .as_device_ptr() .add(offset_of!(sys::OptixMatrixMotionTransform, transform)); // copy the transform data. @@ -1827,7 +1827,7 @@ impl MatrixMotionTransform { // we'll just overwrite on the next line, but it's probably more // efficient to do that than to write each field individually cust::memory::memcpy_htod( - buf.as_device_ptr(), + buf.as_device_ptr().as_raw(), &mmt as *const _ as *const c_void, size_of::(), )?; @@ -1841,7 +1841,7 @@ impl MatrixMotionTransform { let hnd = convert_pointer_to_traversable_handle( ctx, - buf.as_device_ptr(), + buf.as_device_ptr().as_raw(), TraversableType::MatrixMotionTransform, )?; @@ -1966,7 +1966,7 @@ impl SrtMotionTransform { // get the offset of the matrix data from the base of the struct let transform_ptr = buf - .as_ptr() + .as_device_ptr() .add(offset_of!(sys::OptixSRTMotionTransform, srtData)); // copy the transform data. @@ -1974,7 +1974,7 @@ impl SrtMotionTransform { // we'll just overwrite on the next line, but it's probably more // efficient to do that than to write each field individually cust::memory::memcpy_htod( - buf.as_device_ptr(), + buf.as_device_ptr().as_raw(), &mmt as *const _ as *const c_void, size_of::(), )?; @@ -1988,7 +1988,7 @@ impl SrtMotionTransform { let hnd = convert_pointer_to_traversable_handle( ctx, - buf.as_device_ptr(), + buf.as_device_ptr().as_raw(), TraversableType::SrtMotionTransform, )?; diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index a1055ebb..854111af 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -47,7 +47,7 @@ pub struct ShaderBindingTable(pub(crate) sys::OptixShaderBindingTable); impl ShaderBindingTable { pub fn new(buf_raygen_record: &DeviceSlice>) -> Self { - let raygen_record = buf_raygen_record.as_device_ptr(); + let raygen_record = buf_raygen_record.as_device_ptr().as_raw(); ShaderBindingTable(sys::OptixShaderBindingTable { raygenRecord: raygen_record, exceptionRecord: 0, @@ -70,7 +70,7 @@ impl ShaderBindingTable { if buf_exception_record.len() != 1 { panic!("SBT not passed single exception record",); } - self.0.exceptionRecord = buf_exception_record.as_device_ptr(); + self.0.exceptionRecord = buf_exception_record.as_device_ptr().as_raw(); self } @@ -78,7 +78,7 @@ impl ShaderBindingTable { if buf_miss_records.len() == 0 { panic!("SBT passed empty miss records"); } - self.0.missRecordBase = buf_miss_records.as_device_ptr(); + self.0.missRecordBase = buf_miss_records.as_device_ptr().as_raw(); self.0.missRecordStrideInBytes = std::mem::size_of::>() as u32; self.0.missRecordCount = buf_miss_records.len() as u32; self @@ -91,7 +91,7 @@ impl ShaderBindingTable { if buf_hitgroup_records.len() == 0 { panic!("SBT passed empty hitgroup records"); } - self.0.hitgroupRecordBase = buf_hitgroup_records.as_device_ptr(); + self.0.hitgroupRecordBase = buf_hitgroup_records.as_device_ptr().as_raw(); self.0.hitgroupRecordStrideInBytes = std::mem::size_of::>() as u32; self.0.hitgroupRecordCount = buf_hitgroup_records.len() as u32; self @@ -104,7 +104,7 @@ impl ShaderBindingTable { if buf_callables_records.len() == 0 { panic!("SBT passed empty callables records"); } - self.0.callablesRecordBase = buf_callables_records.as_device_ptr(); + self.0.callablesRecordBase = buf_callables_records.as_device_ptr().as_raw(); self.0.callablesRecordStrideInBytes = std::mem::size_of::>() as u32; self.0.callablesRecordCount = buf_callables_records.len() as u32; self From 766cb601bc1f2c6184974f7643e03ba7b36745a7 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Fri, 21 Jan 2022 01:36:21 -0500 Subject: [PATCH 097/100] Fix issues and warnings --- crates/blastoff/src/level1.rs | 44 ++++++------ crates/cudnn/src/context.rs | 70 +++++++++---------- crates/cust/CHANGELOG.md | 18 +++++ crates/cust/Cargo.toml | 2 +- crates/cust/src/external.rs | 2 + crates/cust/src/memory/device/device_box.rs | 22 +++--- crates/cust/src/memory/device/device_slice.rs | 4 +- crates/cust/src/memory/malloc.rs | 14 ++-- crates/cust_core/src/lib.rs | 4 ++ crates/optix/examples/ex02_pipeline/build.rs | 9 +-- .../examples/ex02_pipeline/device/src/lib.rs | 8 +-- crates/optix/examples/ex03_window/build.rs | 9 +-- crates/optix/examples/ex03_window/src/main.rs | 2 + .../examples/ex03_window/src/renderer.rs | 3 +- crates/optix/examples/ex04_mesh/build.rs | 9 +-- crates/optix/examples/ex04_mesh/src/main.rs | 2 + .../optix/examples/ex04_mesh/src/renderer.rs | 9 ++- crates/optix/src/acceleration.rs | 17 ++++- crates/optix/src/denoiser.rs | 7 +- crates/optix/src/error.rs | 2 +- crates/optix/src/lib.rs | 2 +- crates/optix/src/pipeline.rs | 2 +- crates/optix/src/shader_binding_table.rs | 11 ++- crates/optix_device/src/lib.rs | 2 +- examples/cuda/cpu/add/src/main.rs | 6 +- examples/cuda/cpu/path_tracer/src/cuda/mod.rs | 8 +-- 26 files changed, 149 insertions(+), 139 deletions(-) diff --git a/crates/blastoff/src/level1.rs b/crates/blastoff/src/level1.rs index bec4a3b0..5401d40c 100644 --- a/crates/blastoff/src/level1.rs +++ b/crates/blastoff/src/level1.rs @@ -46,7 +46,7 @@ impl CublasContext { Ok(T::amin( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), stride.unwrap_or(1) as i32, result.as_device_ptr().as_mut_ptr(), ) @@ -108,7 +108,7 @@ impl CublasContext { Ok(T::amax( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), stride.unwrap_or(1) as i32, result.as_device_ptr().as_mut_ptr(), ) @@ -172,10 +172,10 @@ impl CublasContext { Ok(T::axpy( ctx.raw, n as i32, - alpha.as_device_ptr().as_raw(), - x.as_device_ptr().as_raw(), + alpha.as_device_ptr().as_ptr(), + x.as_device_ptr().as_ptr(), x_stride.unwrap_or(1) as i32, - y.as_device_ptr().as_raw_mut(), + y.as_device_ptr().as_mut_ptr(), y_stride.unwrap_or(1) as i32, ) .to_result()?) @@ -245,9 +245,9 @@ impl CublasContext { Ok(T::copy( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), x_stride.unwrap_or(1) as i32, - y.as_device_ptr().as_raw_mut(), + y.as_device_ptr().as_mut_ptr(), y_stride.unwrap_or(1) as i32, ) .to_result()?) @@ -314,11 +314,11 @@ impl CublasContext { Ok(T::dot( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), x_stride.unwrap_or(1) as i32, - y.as_device_ptr().as_raw(), + y.as_device_ptr().as_ptr(), y_stride.unwrap_or(1) as i32, - result.as_device_ptr().as_raw_mut(), + result.as_device_ptr().as_mut_ptr(), ) .to_result()?) }) @@ -390,11 +390,11 @@ impl CublasContext { Ok(T::dotu( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), x_stride.unwrap_or(1) as i32, - y.as_device_ptr().as_raw(), + y.as_device_ptr().as_ptr(), y_stride.unwrap_or(1) as i32, - result.as_device_ptr().as_raw_mut(), + result.as_device_ptr().as_mut_ptr(), ) .to_result()?) }) @@ -438,11 +438,11 @@ impl CublasContext { Ok(T::dotc( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), x_stride.unwrap_or(1) as i32, - y.as_device_ptr().as_raw(), + y.as_device_ptr().as_ptr(), y_stride.unwrap_or(1) as i32, - result.as_device_ptr().as_raw_mut(), + result.as_device_ptr().as_mut_ptr(), ) .to_result()?) }) @@ -483,9 +483,9 @@ impl CublasContext { Ok(T::nrm2( ctx.raw, n as i32, - x.as_device_ptr().as_raw(), + x.as_device_ptr().as_ptr(), x_stride.unwrap_or(1) as i32, - result.as_device_ptr().as_raw_mut(), + result.as_device_ptr().as_mut_ptr(), ) .to_result()?) }) @@ -559,12 +559,12 @@ impl CublasContext { Ok(T::rot( ctx.raw, n as i32, - x.as_device_ptr().as_raw_mut(), + x.as_device_ptr().as_mut_ptr(), x_stride.unwrap_or(1) as i32, - y.as_device_ptr().as_raw_mut(), + y.as_device_ptr().as_mut_ptr(), y_stride.unwrap_or(1) as i32, - c.as_device_ptr().as_raw(), - s.as_device_ptr().as_raw(), + c.as_device_ptr().as_ptr(), + s.as_device_ptr().as_ptr(), ) .to_result()?) }) diff --git a/crates/cudnn/src/context.rs b/crates/cudnn/src/context.rs index 49a72bb7..b917fc9a 100644 --- a/crates/cudnn/src/context.rs +++ b/crates/cudnn/src/context.rs @@ -397,9 +397,9 @@ impl CudnnContext { let x_data = x.data().as_device_ptr().as_raw(); let y_desc = y.descriptor(); - let y_data = y.data().as_device_ptr().as_raw_mut(); + let y_data = y.data().as_device_ptr().as_ptr(); - let reserve_space_ptr = reserve_space.as_device_ptr().as_raw_mut(); + let reserve_space_ptr = reserve_space.as_device_ptr().as_ptr(); unsafe { sys::cudnnDropoutForward( @@ -454,9 +454,9 @@ impl CudnnContext { let dy_data = dy.data().as_device_ptr().as_raw(); let dx_desc = dx.descriptor(); - let dx_data = dx.data().as_device_ptr().as_raw_mut(); + let dx_data = dx.data().as_device_ptr().as_ptr(); - let reserve_space_ptr = reserve_space.as_device_ptr().as_raw_mut(); + let reserve_space_ptr = reserve_space.as_device_ptr().as_ptr(); unsafe { sys::cudnnDropoutBackward( @@ -528,7 +528,7 @@ impl CudnnContext { raw, self.raw, dropout, - states.as_device_ptr().as_raw_mut() as *mut std::ffi::c_void, + states.as_device_ptr().as_ptr() as *mut std::ffi::c_void, states.len(), seed, ) @@ -1185,14 +1185,14 @@ impl CudnnContext { let w_data = w.data().as_device_ptr().as_raw(); let w_desc = w.descriptor(); - let y_data = y.data().as_device_ptr().as_raw_mut(); + let y_data = y.data().as_device_ptr().as_ptr(); let y_desc = y.descriptor(); // If the _ size is 0 then the algorithm can work in-place and cuDNN expects a null // pointer. let (work_space_ptr, work_space_size): (*mut u8, usize) = { work_space.map_or((std::ptr::null_mut(), 0), |work_space| { - (work_space.as_device_ptr().as_raw_mut(), work_space.len()) + (work_space.as_device_ptr().as_mut_ptr(), work_space.len()) }) }; @@ -1287,12 +1287,12 @@ impl CudnnContext { let dy_data = dy.data().as_device_ptr().as_raw(); let dy_desc = dy.descriptor(); - let dx_data = dx.data().as_device_ptr().as_raw_mut(); + let dx_data = dx.data().as_device_ptr().as_ptr(); let dx_desc = dx.descriptor(); let (work_space_ptr, work_space_size): (*mut u8, usize) = { work_space.map_or((std::ptr::null_mut(), 0), |work_space| { - (work_space.as_device_ptr().as_raw_mut(), work_space.len()) + (work_space.as_device_ptr().as_mut_ptr(), work_space.len()) }) }; @@ -1388,12 +1388,12 @@ impl CudnnContext { let dy_data = dy.data().as_device_ptr().as_raw(); let dy_desc = dy.descriptor(); - let dw_data = dw.data().as_device_ptr().as_raw_mut(); + let dw_data = dw.data().as_device_ptr().as_ptr(); let dw_desc = dw.descriptor(); let (work_space_ptr, work_space_size): (*mut u8, usize) = { work_space.map_or((std::ptr::null_mut(), 0), |work_space| { - (work_space.as_device_ptr().as_raw_mut(), work_space.len()) + (work_space.as_device_ptr().as_mut_ptr(), work_space.len()) }) }; @@ -1615,28 +1615,28 @@ impl CudnnContext { L: RnnDataLayout, NCHW: SupportedType, { - let device_sequence_lengths_ptr = device_seq_lengths.as_device_ptr().as_raw(); + let device_sequence_lengths_ptr = device_seq_lengths.as_device_ptr().as_ptr(); let x_ptr = x.as_device_ptr().as_raw(); - let y_ptr = y.as_device_ptr().as_raw_mut(); + let y_ptr = y.as_device_ptr().as_ptr(); - let hx_ptr = hx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_raw()); + let hx_ptr = hx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_ptr()); let hy_ptr = hy.map_or(std::ptr::null_mut(), |buff| { - buff.as_device_ptr().as_raw_mut() + buff.as_device_ptr().as_mut_ptr() }); let c_desc = c_desc.map_or(std::ptr::null_mut(), |desc| desc.raw); - let cx_ptr = cx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_raw()); + let cx_ptr = cx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_ptr()); let cy_ptr = cy.map_or(std::ptr::null_mut(), |buff| { - buff.as_device_ptr().as_raw_mut() + buff.as_device_ptr().as_mut_ptr() }); - let weight_space_ptr = weight_space.as_device_ptr().as_raw_mut(); - let work_space_ptr = work_space.as_device_ptr().as_raw_mut(); + let weight_space_ptr = weight_space.as_device_ptr().as_ptr(); + let work_space_ptr = work_space.as_device_ptr().as_ptr(); let (reserve_space_ptr, reserve_space_size) = reserve_space .map_or((std::ptr::null_mut(), 0), |buff| { - (buff.as_device_ptr().as_raw_mut(), buff.len()) + (buff.as_device_ptr().as_mut_ptr(), buff.len()) }); unsafe { @@ -1814,32 +1814,32 @@ impl CudnnContext { L: RnnDataLayout, NCHW: SupportedType, { - let device_sequence_lengths_ptr = device_seq_lengths.as_device_ptr().as_raw(); + let device_sequence_lengths_ptr = device_seq_lengths.as_device_ptr().as_ptr(); let y_ptr = y.as_device_ptr().as_raw(); let dy_ptr = dy.as_device_ptr().as_raw(); - let dx_ptr = dx.as_device_ptr().as_raw_mut(); + let dx_ptr = dx.as_device_ptr().as_ptr(); let h_desc = h_desc.map_or(std::ptr::null_mut(), |desc| desc.raw); - let hx_ptr = hx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_raw()); - let dhy_ptr = dhy.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_raw()); + let hx_ptr = hx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_ptr()); + let dhy_ptr = dhy.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_ptr()); let dhx_ptr = dhx.map_or(std::ptr::null_mut(), |buff| { - buff.as_device_ptr().as_raw_mut() + buff.as_device_ptr().as_mut_ptr() }); let c_desc = c_desc.map_or(std::ptr::null_mut(), |desc| desc.raw); - let cx_ptr = cx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_raw()); - let dcy_ptr = dcy.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_raw()); + let cx_ptr = cx.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_ptr()); + let dcy_ptr = dcy.map_or(std::ptr::null(), |buff| buff.as_device_ptr().as_mut_ptr()); let dcx_ptr = dcx.map_or(std::ptr::null_mut(), |buff| { - buff.as_device_ptr().as_raw_mut() + buff.as_device_ptr().as_mut_ptr() }); - let weight_space_ptr = weight_space.as_device_ptr().as_raw_mut(); - let work_space_ptr = work_space.as_device_ptr().as_raw_mut(); - let reserve_space_ptr = reserve_space.as_device_ptr().as_raw_mut(); + let weight_space_ptr = weight_space.as_device_ptr().as_ptr(); + let work_space_ptr = work_space.as_device_ptr().as_ptr(); + let reserve_space_ptr = reserve_space.as_device_ptr().as_ptr(); unsafe { sys::cudnnRNNBackwardData_v8( @@ -1947,15 +1947,15 @@ impl CudnnContext { L: RnnDataLayout, NCHW: SupportedType, { - let device_sequence_lengths_ptr = device_seq_lengths.as_device_ptr().as_raw(); + let device_sequence_lengths_ptr = device_seq_lengths.as_device_ptr().as_mut_ptr(); let x_ptr = x.as_device_ptr().as_raw(); let hx_ptr = x.as_device_ptr().as_raw(); let y_ptr = y.as_device_ptr().as_raw(); - let dweight_space_ptr = dweight_space.as_device_ptr().as_raw_mut(); - let work_space_ptr = work_space.as_device_ptr().as_raw_mut(); - let reserve_space_ptr = reserve_space.as_device_ptr().as_raw_mut(); + let dweight_space_ptr = dweight_space.as_device_ptr().as_mut_ptr(); + let work_space_ptr = work_space.as_device_ptr().as_mut_ptr(); + let reserve_space_ptr = reserve_space.as_device_ptr().as_mut_ptr(); unsafe { sys::cudnnRNNBackwardWeights_v8( diff --git a/crates/cust/CHANGELOG.md b/crates/cust/CHANGELOG.md index 25432074..b15d1b8f 100644 --- a/crates/cust/CHANGELOG.md +++ b/crates/cust/CHANGELOG.md @@ -30,6 +30,24 @@ any breaking changes, the API is the same. - `Linker::complete` now only returns the built cubin, and not the cubin and a duration. - `Stream`, `Module`, `Linker`, `Function`, `Event`, `UnifiedBox`, `ArrayObject`, `LockedBuffer`, `LockedBox`, `DeviceSlice`, `DeviceBuffer`, and `DeviceBox` all now impl `Send` and `Sync`, this makes it much easier to write multigpu code. The CUDA API is fully thread-safe except for graph objects. +- Features such as `vek` for implementing DeviceCopy are now `impl_cratename`, e.g. `impl_vek`, `impl_half`, etc. +- `DevicePointer::as_raw` now returns a `CUdeviceptr` instead of a `*const T`. +- Added `DevicePointer::as_ptr` and `DevicePointer::as_mut_ptr` for returning `*const T` or `*mut T`. +- Added mint integration behind `impl_mint`. +- Added half integration behind `impl_half`. +- Added glam integration behind `impl_glam`. +- Added experimental linux external memory import APIs through `cust::external::ExternalMemory`. +- `vek` is no longer re-exported. +- `DeviceBox` now requires `T: DeviceCopy` (previously it didn't but almost all its methods did). +- `DeviceBox::from_raw` now takes a `CUdeviceptr` instead of a `*mut T`. +- `DeviceBox::as_device_ptr` now requires `&self` instead of `&mut self`. +- `DeviceBuffer` now requires `T: DeviceCopy`. +- `DeviceSlice` now requires `T: DeviceCopy`. +- `DeviceSlice` is now represented by a ptr and a len instead of `[T]` which was likely unsound. +- `DeviceSlice::as_ptr` and `DeviceSlice::as_ptr_mut` now both return a `DevicePointer`. +- Added `DeviceSlice::as_device_ptr`. +- `DeviceSlice::chunks` and `DeviceSlice::chunks_mut` have been deleted. +- `DeviceSlice::from_slice` and `DeviceSlice::from_slice_mut` have been deleted. ## 0.2.2 - 12/5/21 diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index f8537503..018eddb2 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -17,7 +17,7 @@ cust_core = { path = "../cust_core", version = "0.1.0"} cust_raw = { path = "../cust_raw", version = "0.11.2"} bitflags = "1.2" cust_derive = { path = "../cust_derive", version = "0.1" } -glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"], optional=true } +glam = { version = "0.20", features=["cuda"], optional = true } mint = { version = "^0.5", optional = true } num-complex = { version = "0.4", optional = true } vek = { version = "0.15.1", optional = true, default-features = false } diff --git a/crates/cust/src/external.rs b/crates/cust/src/external.rs index 2af93ce4..c735842a 100644 --- a/crates/cust/src/external.rs +++ b/crates/cust/src/external.rs @@ -10,6 +10,7 @@ pub struct ExternalMemory(sys::CUexternalMemory); impl ExternalMemory { // Import an external memory referenced by `fd` with `size` + #[allow(clippy::missing_safety_doc)] pub unsafe fn import(fd: i32, size: usize) -> CudaResult { let desc = sys::CUDA_EXTERNAL_MEMORY_HANDLE_DESC { type_: sys::CUexternalMemoryHandleType_enum::CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD, @@ -26,6 +27,7 @@ impl ExternalMemory { .map(|_| ExternalMemory(memory)) } + #[allow(clippy::missing_safety_doc)] pub unsafe fn reimport(&mut self, fd: i32, size: usize) -> CudaResult<()> { // import new memory - this will call drop to destroy the old one *self = ExternalMemory::import(fd, size)?; diff --git a/crates/cust/src/memory/device/device_box.rs b/crates/cust/src/memory/device/device_box.rs index 90693103..4d050a78 100644 --- a/crates/cust/src/memory/device/device_box.rs +++ b/crates/cust/src/memory/device/device_box.rs @@ -19,8 +19,8 @@ pub struct DeviceBox { pub(crate) ptr: DevicePointer, } -unsafe impl Send for DeviceBox {} -unsafe impl Sync for DeviceBox {} +unsafe impl Send for DeviceBox {} +unsafe impl Sync for DeviceBox {} impl DeviceBox { /// Allocate device memory and place val into it. @@ -156,14 +156,10 @@ impl DeviceBox { #[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))] pub fn zeroed() -> CudaResult { unsafe { - let mut new_box = DeviceBox::uninitialized()?; + let new_box = DeviceBox::uninitialized()?; if mem::size_of::() != 0 { - cuda::cuMemsetD8_v2( - new_box.as_device_ptr().as_raw_mut() as u64, - 0, - mem::size_of::(), - ) - .to_result()?; + cuda::cuMemsetD8_v2(new_box.as_device_ptr().as_raw(), 0, mem::size_of::()) + .to_result()?; } Ok(new_box) } @@ -200,10 +196,10 @@ impl DeviceBox { /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))] pub unsafe fn zeroed_async(stream: &Stream) -> CudaResult { - let mut new_box = DeviceBox::uninitialized_async(stream)?; + let new_box = DeviceBox::uninitialized_async(stream)?; if mem::size_of::() != 0 { cuda::cuMemsetD8Async( - new_box.as_device_ptr().as_raw_mut() as u64, + new_box.as_device_ptr().as_raw(), 0, mem::size_of::(), stream.as_inner(), @@ -214,7 +210,7 @@ impl DeviceBox { } } -impl DeviceBox { +impl DeviceBox { /// Allocate device memory, but do not initialize it. /// /// This doesn't actually allocate if `T` is zero-sized. @@ -470,7 +466,7 @@ impl AsyncCopyDestination for DeviceBox { let size = mem::size_of::(); if size != 0 { cuda::cuMemcpyHtoDAsync_v2( - self.ptr.as_raw_mut() as u64, + self.ptr.as_raw(), val as *const _ as *const c_void, size, stream.as_inner(), diff --git a/crates/cust/src/memory/device/device_slice.rs b/crates/cust/src/memory/device/device_slice.rs index 55073bb1..cfd05d51 100644 --- a/crates/cust/src/memory/device/device_slice.rs +++ b/crates/cust/src/memory/device/device_slice.rs @@ -17,8 +17,8 @@ pub struct DeviceSlice { len: usize, } -unsafe impl Send for DeviceSlice {} -unsafe impl Sync for DeviceSlice {} +unsafe impl Send for DeviceSlice {} +unsafe impl Sync for DeviceSlice {} impl DeviceSlice { pub fn as_host_vec(&self) -> CudaResult> { diff --git a/crates/cust/src/memory/malloc.rs b/crates/cust/src/memory/malloc.rs index 1f36ec68..fb86501a 100644 --- a/crates/cust/src/memory/malloc.rs +++ b/crates/cust/src/memory/malloc.rs @@ -59,7 +59,10 @@ pub unsafe fn cuda_malloc(count: usize) -> CudaResult(stream: &Stream, count: usize) -> CudaResult> { +pub unsafe fn cuda_malloc_async( + stream: &Stream, + count: usize, +) -> CudaResult> { let size = count.checked_mul(mem::size_of::()).unwrap_or(0); if size == 0 { return Err(CudaError::InvalidMemoryAllocation); @@ -73,7 +76,7 @@ pub unsafe fn cuda_malloc_async(stream: &Stream, count: usize) -> CudaResult< ) .to_result()?; let ptr = ptr as *mut T; - Ok(DevicePointer::wrap(ptr as *mut T)) + Ok(DevicePointer::from_raw(ptr as cuda::CUdeviceptr)) } /// Unsafe wrapper around `cuMemFreeAsync` which queues a memory allocation free operation on a stream. @@ -84,12 +87,15 @@ pub unsafe fn cuda_malloc_async(stream: &Stream, count: usize) -> CudaResult< /// # Safety /// /// The pointer must be valid. -pub unsafe fn cuda_free_async(stream: &Stream, mut p: DevicePointer) -> CudaResult<()> { +pub unsafe fn cuda_free_async( + stream: &Stream, + p: DevicePointer, +) -> CudaResult<()> { if mem::size_of::() == 0 { return Err(CudaError::InvalidMemoryAllocation); } - cuda::cuMemFreeAsync(p.as_raw_mut() as u64, stream.as_inner()).to_result() + cuda::cuMemFreeAsync(p.as_raw(), stream.as_inner()).to_result() } /// Unsafe wrapper around the `cuMemAllocManaged` function, which allocates some unified memory and diff --git a/crates/cust_core/src/lib.rs b/crates/cust_core/src/lib.rs index e24c5015..b6751250 100644 --- a/crates/cust_core/src/lib.rs +++ b/crates/cust_core/src/lib.rs @@ -59,6 +59,10 @@ use core::num::*; /// invalid reference on the device which would segfault if dereferenced. Generalizing this, any /// type implementing `Drop` cannot be `DeviceCopy` since it is responsible for some resource that /// would not be available on the device. +/// +/// # Safety +/// +/// The type being implemented must hold no references to CPU data. pub unsafe trait DeviceCopy: Copy {} macro_rules! impl_device_copy { diff --git a/crates/optix/examples/ex02_pipeline/build.rs b/crates/optix/examples/ex02_pipeline/build.rs index cc228674..b7274457 100644 --- a/crates/optix/examples/ex02_pipeline/build.rs +++ b/crates/optix/examples/ex02_pipeline/build.rs @@ -1,5 +1,5 @@ use cuda_builder::CudaBuilder; -use find_cuda_helper::{find_cuda_root, find_optix_root}; +use find_cuda_helper::find_optix_root; fn main() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -10,13 +10,6 @@ fn main() { ); optix_include = optix_include.join("include"); - let mut cuda_include = find_cuda_root().expect( - "Unable to find the CUDA Toolkit, make sure you installed it and - that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", - ); - - cuda_include = cuda_include.join("include"); - let args = vec![ format!("-I{}", optix_include.display()), format!("-I{}/../common/gdt", manifest_dir), diff --git a/crates/optix/examples/ex02_pipeline/device/src/lib.rs b/crates/optix/examples/ex02_pipeline/device/src/lib.rs index c6d2e057..45cabfee 100644 --- a/crates/optix/examples/ex02_pipeline/device/src/lib.rs +++ b/crates/optix/examples/ex02_pipeline/device/src/lib.rs @@ -6,8 +6,7 @@ register_attr(nvvm_internal) )] // #![deny(warnings)] - -use core::mem::MaybeUninit; +#![allow(clippy::missing_safety_doc)] use cuda_std::*; use cust_core::DeviceCopy; @@ -51,15 +50,12 @@ pub unsafe fn __raygen__renderFrame() { // let ix = _optix_get_launch_index_x(); // let iy = _optix_get_launch_index_y(); - let ix: u32; - let iy: u32; - let idx = optix::get_launch_index(); if idx[0] == 3 && idx[1] == 4 { vprintf( b"Hello from Rust kernel!\n\0".as_ptr().cast(), - 0 as *const core::ffi::c_void, + std::ptr::null::(), ); #[repr(C)] diff --git a/crates/optix/examples/ex03_window/build.rs b/crates/optix/examples/ex03_window/build.rs index 1dadfe69..7a7bdade 100644 --- a/crates/optix/examples/ex03_window/build.rs +++ b/crates/optix/examples/ex03_window/build.rs @@ -1,4 +1,4 @@ -use find_cuda_helper::{find_cuda_root, find_optix_root}; +use find_cuda_helper::find_optix_root; fn main() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -9,13 +9,6 @@ fn main() { ); optix_include = optix_include.join("include"); - let mut cuda_include = find_cuda_root().expect( - "Unable to find the CUDA Toolkit, make sure you installed it and - that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", - ); - - cuda_include = cuda_include.join("include"); - let args = vec![ format!("-I{}", optix_include.display()), format!("-I{}/../common/gdt", manifest_dir), diff --git a/crates/optix/examples/ex03_window/src/main.rs b/crates/optix/examples/ex03_window/src/main.rs index 39072e60..08bc998e 100644 --- a/crates/optix/examples/ex03_window/src/main.rs +++ b/crates/optix/examples/ex03_window/src/main.rs @@ -1,3 +1,5 @@ +#![allow(warnings)] + mod renderer; use renderer::Renderer; diff --git a/crates/optix/examples/ex03_window/src/renderer.rs b/crates/optix/examples/ex03_window/src/renderer.rs index 82997c9e..03acd884 100644 --- a/crates/optix/examples/ex03_window/src/renderer.rs +++ b/crates/optix/examples/ex03_window/src/renderer.rs @@ -36,8 +36,7 @@ impl Renderer { // create CUDA and OptiX contexts let device = Device::get_device(0)?; - let cuda_context = - CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let cuda_context = CuContext::new(device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context, false)?; diff --git a/crates/optix/examples/ex04_mesh/build.rs b/crates/optix/examples/ex04_mesh/build.rs index c0e8247b..b7c67358 100644 --- a/crates/optix/examples/ex04_mesh/build.rs +++ b/crates/optix/examples/ex04_mesh/build.rs @@ -1,4 +1,4 @@ -use find_cuda_helper::{find_cuda_root, find_optix_root}; +use find_cuda_helper::find_optix_root; fn main() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -9,13 +9,6 @@ fn main() { ); optix_include = optix_include.join("include"); - let mut cuda_include = find_cuda_root().expect( - "Unable to find the CUDA Toolkit, make sure you installed it and - that CUDA_ROOT, CUDA_PATH or CUDA_TOOLKIT_ROOT_DIR are set", - ); - - cuda_include = cuda_include.join("include"); - let args = vec![ format!("-I{}", optix_include.display()), format!("-I{}/../common/gdt", manifest_dir), diff --git a/crates/optix/examples/ex04_mesh/src/main.rs b/crates/optix/examples/ex04_mesh/src/main.rs index 76579f11..e10ffac7 100644 --- a/crates/optix/examples/ex04_mesh/src/main.rs +++ b/crates/optix/examples/ex04_mesh/src/main.rs @@ -1,3 +1,5 @@ +#![allow(warnings)] + mod renderer; use renderer::Renderer; diff --git a/crates/optix/examples/ex04_mesh/src/renderer.rs b/crates/optix/examples/ex04_mesh/src/renderer.rs index 2e4aaeed..e99cd4e1 100644 --- a/crates/optix/examples/ex04_mesh/src/renderer.rs +++ b/crates/optix/examples/ex04_mesh/src/renderer.rs @@ -42,8 +42,7 @@ impl Renderer { // create CUDA and OptiX contexts let device = Device::get_device(0)?; - let cuda_context = - CuContext::create_and_push(ContextFlags::SCHED_AUTO | ContextFlags::MAP_HOST, device)?; + let cuda_context = CuContext::new(device)?; let stream = Stream::new(StreamFlags::DEFAULT, None)?; let mut ctx = DeviceContext::new(&cuda_context, true)?; @@ -152,9 +151,9 @@ impl Renderer { let mut buf_miss = DeviceBuffer::from_slice(&rec_miss)?; let mut buf_hitgroup = DeviceBuffer::from_slice(&rec_hitgroup)?; - let sbt = ShaderBindingTable::new(&mut buf_raygen) - .miss(&mut buf_miss) - .hitgroup(&mut buf_hitgroup); + let sbt = ShaderBindingTable::new(&buf_raygen) + .miss(&buf_miss) + .hitgroup(&buf_hitgroup); // create pipeline let mut program_groups = Vec::new(); diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index f65c5ba6..de1e6b1c 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -1,3 +1,5 @@ +#![allow(clippy::missing_safety_doc)] + use crate::{const_assert, const_assert_eq, context::DeviceContext, error::Error, optix_call, sys}; use cust::{ memory::{CopyDestination, DeviceCopy, DeviceBox, DeviceBuffer, DevicePointer, DeviceSlice}, @@ -88,6 +90,7 @@ pub trait Traversable { /// # } /// ``` pub struct Accel { + #[allow(dead_code)] buf: DeviceBuffer, hnd: TraversableHandle, } @@ -168,7 +171,7 @@ impl Accel { let mut temp_buffer = unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; - let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; + let compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; let mut properties = vec![AccelEmitDesc::CompactedSize( compacted_size_buffer.as_device_ptr(), @@ -296,7 +299,7 @@ impl DynamicAccel { let mut temp_buffer = unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; - let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; + let compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; let mut properties = vec![AccelEmitDesc::CompactedSize( compacted_size_buffer.as_device_ptr(), @@ -377,7 +380,7 @@ impl DynamicAccel { let mut temp_buffer = unsafe { DeviceBuffer::::uninitialized(sizes.temp_size_in_bytes)? }; - let mut compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; + let compacted_size_buffer = unsafe { DeviceBox::::uninitialized()? }; let mut properties = vec![AccelEmitDesc::CompactedSize( compacted_size_buffer.as_device_ptr(), @@ -838,6 +841,7 @@ impl AccelBuildOptions { /// [`AccelRelocationInfo`] can also be used on all copies. #[repr(transparent)] pub struct AccelRelocationInfo { + #[allow(dead_code)] inner: sys::OptixAccelRelocationInfo, } @@ -970,6 +974,7 @@ pub struct CurveArray<'v, 'w, 'i> { d_vertex_buffers: Vec, vertex_stride_in_bytes: u32, width_buffers: PhantomData<&'w f32>, + #[allow(dead_code)] num_width_buffers: u32, d_width_buffers: Vec, width_stride_in_bytes: u32, @@ -1549,6 +1554,7 @@ bitflags::bitflags! { impl<'a> Instance<'a> { pub fn new(accel: &'a T) -> Instance<'a> { + #[allow(clippy::deprecated_cfg_attr)] #[cfg_attr(rustfmt, rustfmt_skip)] Instance { transform: [ @@ -1566,6 +1572,7 @@ impl<'a> Instance<'a> { } pub unsafe fn from_handle(traversable_handle: TraversableHandle) -> Instance<'static> { + #[allow(clippy::deprecated_cfg_attr)] #[cfg_attr(rustfmt, rustfmt_skip)] Instance { transform: [ @@ -1716,6 +1723,7 @@ const_assert_eq!( /// Stores the device memory and the [`TraversableHandle`] for a [`StaticTransform`] pub struct StaticTransform { + #[allow(dead_code)] buf: DeviceBox, hnd: TraversableHandle, } @@ -1770,6 +1778,7 @@ impl Traversable for StaticTransform { /// Stores the device memory and the [`TraversableHandle`] for a [`sys::OptixMatrixMotionTransform`] /// and an arbitrary number of motion keys pub struct MatrixMotionTransform { + #[allow(dead_code)] buf: DeviceBuffer, hnd: TraversableHandle, } @@ -1917,6 +1926,8 @@ impl Deref for SrtData { /// Stores the device memory and the [`TraversableHandle`] for a [`sys::OptixSRTMotionTransform`] /// and an arbitrary number of motion keys pub struct SrtMotionTransform { + // TODO(RDambrosio016): ask al what this is for :p + #[allow(dead_code)] buf: DeviceBuffer, hnd: TraversableHandle, } diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index baf0176b..4d14a211 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -9,7 +9,7 @@ use std::{ use cust::{ error::CudaResult, memory::{ - DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer, GpuBox, GpuBuffer, UnifiedBuffer, + DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer, GpuBuffer, UnifiedBuffer, }, prelude::Stream, }; @@ -561,11 +561,10 @@ impl<'a> Image<'a> { Self::validate_buf(buffer, format, width, height); Self { - buffer: unsafe { + buffer: // SAFETY: this buffer is never written to for the duration of this image being alive. // And we know the buffer is large enough to be reinterpreted as a buffer of bytes. - DevicePointer::from_raw(buffer.as_device_ptr().as_raw()) - }, + DevicePointer::from_raw(buffer.as_device_ptr().as_raw()), buffer_size: buffer.len() * std::mem::size_of::(), format, width, diff --git a/crates/optix/src/error.rs b/crates/optix/src/error.rs index b7bc460d..96121a33 100644 --- a/crates/optix/src/error.rs +++ b/crates/optix/src/error.rs @@ -183,7 +183,7 @@ impl ToResult for sys::OptixResult { sys::OptixResult::OPTIX_ERROR_CUDA_ERROR => CudaError, sys::OptixResult::OPTIX_ERROR_INTERNAL_ERROR => InternalError, sys::OptixResult::OPTIX_ERROR_UNKNOWN => Unknown, - value @ _ => panic!("Unhandled OptixResult value {:?}", value), + value => panic!("Unhandled OptixResult value {:?}", value), }) } } diff --git a/crates/optix/src/lib.rs b/crates/optix/src/lib.rs index af1fffc0..f497eaa1 100644 --- a/crates/optix/src/lib.rs +++ b/crates/optix/src/lib.rs @@ -68,7 +68,7 @@ use shader_binding_table::ShaderBindingTable; pub mod sys; pub use cust; -use cust::memory::{DeviceMemory, DevicePointer}; +use cust::memory::DeviceMemory; use error::{Error, ToResult}; type Result = std::result::Result; diff --git a/crates/optix/src/pipeline.rs b/crates/optix/src/pipeline.rs index 6cfa64e3..7b612527 100644 --- a/crates/optix/src/pipeline.rs +++ b/crates/optix/src/pipeline.rs @@ -415,7 +415,7 @@ impl Module { &mut raw, )) .map(|_| Module { raw }) - .map_err(|e| Error::from(e)) + .map_err(Error::from) } } } diff --git a/crates/optix/src/shader_binding_table.rs b/crates/optix/src/shader_binding_table.rs index 854111af..f0edd95a 100644 --- a/crates/optix/src/shader_binding_table.rs +++ b/crates/optix/src/shader_binding_table.rs @@ -1,9 +1,6 @@ +use crate::{const_assert, const_assert_eq}; use crate::{error::Error, optix_call, pipeline::ProgramGroup, sys}; - use cust::memory::{DeviceCopy, DeviceSlice}; -use cust_raw::CUdeviceptr; - -use crate::{const_assert, const_assert_eq}; type Result = std::result::Result; @@ -75,7 +72,7 @@ impl ShaderBindingTable { } pub fn miss(mut self, buf_miss_records: &DeviceSlice>) -> Self { - if buf_miss_records.len() == 0 { + if buf_miss_records.is_empty() { panic!("SBT passed empty miss records"); } self.0.missRecordBase = buf_miss_records.as_device_ptr().as_raw(); @@ -88,7 +85,7 @@ impl ShaderBindingTable { mut self, buf_hitgroup_records: &DeviceSlice>, ) -> Self { - if buf_hitgroup_records.len() == 0 { + if buf_hitgroup_records.is_empty() { panic!("SBT passed empty hitgroup records"); } self.0.hitgroupRecordBase = buf_hitgroup_records.as_device_ptr().as_raw(); @@ -101,7 +98,7 @@ impl ShaderBindingTable { mut self, buf_callables_records: &DeviceSlice>, ) -> Self { - if buf_callables_records.len() == 0 { + if buf_callables_records.is_empty() { panic!("SBT passed empty callables records"); } self.0.callablesRecordBase = buf_callables_records.as_device_ptr().as_raw(); diff --git a/crates/optix_device/src/lib.rs b/crates/optix_device/src/lib.rs index 5036e5e5..f8ce4778 100644 --- a/crates/optix_device/src/lib.rs +++ b/crates/optix_device/src/lib.rs @@ -8,7 +8,7 @@ extern crate alloc; use cuda_std::*; -use glam::{UVec2, UVec3}; +use glam::UVec3; extern "C" { pub fn vprintf(format: *const u8, valist: *const core::ffi::c_void) -> i32; diff --git a/examples/cuda/cpu/add/src/main.rs b/examples/cuda/cpu/add/src/main.rs index 5d5deb96..8ced6476 100644 --- a/examples/cuda/cpu/add/src/main.rs +++ b/examples/cuda/cpu/add/src/main.rs @@ -29,13 +29,13 @@ fn main() -> Result<(), Box> { let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?; // allocate the GPU memory needed to house our numbers and copy them over. - let mut lhs_gpu = lhs.as_slice().as_dbuf()?; - let mut rhs_gpu = rhs.as_slice().as_dbuf()?; + let lhs_gpu = lhs.as_slice().as_dbuf()?; + let rhs_gpu = rhs.as_slice().as_dbuf()?; // allocate our output buffer. You could also use DeviceBuffer::uninitialized() to avoid the // cost of the copy, but you need to be careful not to read from the buffer. let mut out = vec![0.0f32; NUMBERS_LEN]; - let mut out_buf = out.as_slice().as_dbuf()?; + let out_buf = out.as_slice().as_dbuf()?; // retrieve the add kernel from the module so we can calculate the right launch config. let func = module.get_function("add")?; diff --git a/examples/cuda/cpu/path_tracer/src/cuda/mod.rs b/examples/cuda/cpu/path_tracer/src/cuda/mod.rs index 1292b828..a80ee2a2 100644 --- a/examples/cuda/cpu/path_tracer/src/cuda/mod.rs +++ b/examples/cuda/cpu/path_tracer/src/cuda/mod.rs @@ -91,10 +91,10 @@ impl CudaRenderer { self.buffers.resize(new_size)?; self.cpu_image.resize(new_size.product(), Vec3::zero()); - Ok(self - .denoiser + self.denoiser .setup_state(&self.stream, new_size.x as u32, new_size.y as u32, false) - .unwrap()) + .unwrap(); + Ok(()) } /// calculate an optimal launch configuration for an image kernel @@ -190,7 +190,7 @@ impl CudaRenderer { let (blocks, threads) = self.launch_dimensions(); - let mut scene = Scene { + let scene = Scene { objects: &self.buffers.objects, materials: &self.buffers.materials, } From d9ef5f5ae4c0e86694667e4a730700004caff6cb Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Fri, 21 Jan 2022 01:43:23 -0500 Subject: [PATCH 098/100] Chore: run formatting --- .../optix/examples/ex02_pipeline/src/main.rs | 2 +- .../optix/examples/ex03_window/src/gl_util.rs | 37 ++--------- crates/optix/src/acceleration.rs | 66 ++++++++++++------- crates/optix/src/denoiser.rs | 6 +- .../cuda/cpu/path_tracer/src/cuda/data.rs | 2 +- examples/cuda/cpu/path_tracer/src/renderer.rs | 2 +- 6 files changed, 53 insertions(+), 62 deletions(-) diff --git a/crates/optix/examples/ex02_pipeline/src/main.rs b/crates/optix/examples/ex02_pipeline/src/main.rs index 00f2ba65..19c21f42 100644 --- a/crates/optix/examples/ex02_pipeline/src/main.rs +++ b/crates/optix/examples/ex02_pipeline/src/main.rs @@ -5,4 +5,4 @@ fn main() -> Result<(), Box> { let mut renderer = Renderer::new(256, 128)?; renderer.render()?; Ok(()) -} \ No newline at end of file +} diff --git a/crates/optix/examples/ex03_window/src/gl_util.rs b/crates/optix/examples/ex03_window/src/gl_util.rs index c8b39caa..9cbc48c9 100644 --- a/crates/optix/examples/ex03_window/src/gl_util.rs +++ b/crates/optix/examples/ex03_window/src/gl_util.rs @@ -9,10 +9,7 @@ pub struct Shader { } impl Shader { - pub fn from_source( - source: &CStr, - shader_type: GLenum, - ) -> Result { + pub fn from_source(source: &CStr, shader_type: GLenum) -> Result { let id = unsafe { gl::CreateShader(shader_type) }; unsafe { @@ -32,12 +29,7 @@ impl Shader { } let error = create_whitespace_cstring(len as usize); unsafe { - gl::GetShaderInfoLog( - id, - len, - std::ptr::null_mut(), - error.as_ptr() as *mut GLchar, - ); + gl::GetShaderInfoLog(id, len, std::ptr::null_mut(), error.as_ptr() as *mut GLchar); } Err(error.to_string_lossy().into_owned()) } else { @@ -90,12 +82,7 @@ impl Program { } let error = create_whitespace_cstring(len as usize); unsafe { - gl::GetProgramInfoLog( - id, - len, - std::ptr::null_mut(), - error.as_ptr() as *mut GLchar, - ); + gl::GetProgramInfoLog(id, len, std::ptr::null_mut(), error.as_ptr() as *mut GLchar); } return Err(error.to_string_lossy().into_owned()); } @@ -119,9 +106,7 @@ impl Program { pub fn get_location(&self, name: &str) -> Result { let cname = CString::new(name).unwrap(); - let loc = unsafe { - gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) - }; + let loc = unsafe { gl::GetUniformLocation(self.id, cname.as_ptr() as *mut GLchar) }; if loc != -1 { Ok(loc) @@ -359,12 +344,7 @@ impl Vertex { // and configure the vertex array unsafe { - Vertex::vertex_attrib_pointer( - f32x3::num_components(), - stride, - location, - offset, - ); + Vertex::vertex_attrib_pointer(f32x3::num_components(), stride, location, offset); } let location = location + 1; @@ -372,12 +352,7 @@ impl Vertex { // and configure the st array unsafe { - Vertex::vertex_attrib_pointer( - f32x2::num_components(), - stride, - location, - offset, - ); + Vertex::vertex_attrib_pointer(f32x2::num_components(), stride, location, offset); } } } diff --git a/crates/optix/src/acceleration.rs b/crates/optix/src/acceleration.rs index de1e6b1c..3b65aaa5 100644 --- a/crates/optix/src/acceleration.rs +++ b/crates/optix/src/acceleration.rs @@ -2,20 +2,20 @@ use crate::{const_assert, const_assert_eq, context::DeviceContext, error::Error, optix_call, sys}; use cust::{ - memory::{CopyDestination, DeviceCopy, DeviceBox, DeviceBuffer, DevicePointer, DeviceSlice}, + memory::{CopyDestination, DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer, DeviceSlice}, DeviceCopy, }; type Result = std::result::Result; +use memoffset::offset_of; +use std::ffi::c_void; +use std::mem::size_of; use std::ops::Deref; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, marker::PhantomData, }; -use memoffset::offset_of; -use std::ffi::c_void; -use std::mem::size_of; use cust_raw::CUdeviceptr; use mint::{RowMatrix3x4, Vector3}; @@ -27,8 +27,6 @@ type OptixEnumBaseType = i32; #[cfg(unix)] type OptixEnumBaseType = u32; - - pub trait BuildInput: std::hash::Hash { fn to_sys(&self) -> sys::OptixBuildInput; } @@ -962,9 +960,9 @@ impl From for u32 { /// Specify acceleration structure build input data for a curves geometry /// /// A curve is a swept surface defined by a 3D spline curve and a varying width (radius). A curve (or "strand") of degree d (3=cubic, 2=quadratic, 1=linear) is represented by N > d vertices and N width values, and comprises N - d segments. Each segment is defined by d+1 consecutive vertices. Each curve may have a different number of vertices. -/// +/// /// OptiX describes the curve array as a list of curve segments. The primitive id is the segment number. It is the user's responsibility to maintain a mapping between curves and curve segments. Each index buffer entry i = indexBuffer[primid] specifies the start of a curve segment, represented by d+1 consecutive vertices in the vertex buffer, and d+1 consecutive widths in the width buffer. Width is interpolated the same way vertices are interpolated, that is, using the curve basis. -/// +/// /// Each curves build input has only one SBT record. To create curves with different materials in the same BVH, use multiple build inputs. pub struct CurveArray<'v, 'w, 'i> { curve_type: CurveType, @@ -1006,11 +1004,11 @@ impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { /// /// # Parameters /// * `curve_type` - Curve degree and basis - /// * `vertex_buffers` - A slice of device buffers, one per motion step. + /// * `vertex_buffers` - A slice of device buffers, one per motion step. /// The length of this slice must match the number of motion keys specified /// in [`AccelBuildOptions::motion_options`] - /// * `width_buffers` - Parallel to `vertex_buffers` with matching lengths and - /// number of motion steps. One value per vertex specifying the width of + /// * `width_buffers` - Parallel to `vertex_buffers` with matching lengths and + /// number of motion steps. One value per vertex specifying the width of /// the curve /// * `index_buffer` - An array of u32, one per curve segment. Each index is /// the start of `degree+1` consecutive vertices in `vertex_buffers`, and @@ -1024,10 +1022,16 @@ impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { ) -> Result> { // TODO (AL): Do some sanity checking on the values here let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); + let d_vertex_buffers: Vec<_> = vertex_buffers + .iter() + .map(|b| b.as_device_ptr().as_raw()) + .collect(); let num_width_buffers = width_buffers.len() as u32; - let d_width_buffers: Vec<_> = width_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); + let d_width_buffers: Vec<_> = width_buffers + .iter() + .map(|b| b.as_device_ptr().as_raw()) + .collect(); Ok(CurveArray { curve_type, @@ -1074,7 +1078,7 @@ impl<'v, 'w, 'i> CurveArray<'v, 'w, 'i> { self } - /// Primitive index bias, applied on the device in `optixGetPrimitiveIndex()`. + /// Primitive index bias, applied on the device in `optixGetPrimitiveIndex()`. /// /// Sum of primitiveIndexOffset and number of primitives must not overflow 32bits. pub fn primitive_index_offset(mut self, offset: u32) -> Self { @@ -1275,7 +1279,10 @@ impl<'v, 'g, V: Vertex> TriangleArray<'v, 'g, V> { pub fn new(vertex_buffers: &[&'v DeviceSlice], geometry_flags: &'g [GeometryFlags]) -> Self { // TODO (AL): do some sanity checking on the slice lengths here let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); + let d_vertex_buffers: Vec<_> = vertex_buffers + .iter() + .map(|b| b.as_device_ptr().as_raw()) + .collect(); TriangleArray { vertex_buffers: PhantomData, num_vertices, @@ -1354,7 +1361,10 @@ impl<'v, 'i, V: Vertex, I: IndexTriple> IndexedTriangleArray<'v, 'i, V, I> { geometry_flags: &[GeometryFlags], ) -> Self { let num_vertices = vertex_buffers[0].len() as u32; - let d_vertex_buffers: Vec<_> = vertex_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); + let d_vertex_buffers: Vec<_> = vertex_buffers + .iter() + .map(|b| b.as_device_ptr().as_raw()) + .collect(); IndexedTriangleArray { vertex_buffers: PhantomData, num_vertices, @@ -1451,7 +1461,10 @@ impl<'a, 's> CustomPrimitiveArray<'a, 's> { flags: &[GeometryFlags], ) -> Result> { let num_primitives = aabb_buffers.len() as u32; - let aabb_buffers: Vec<_> = aabb_buffers.iter().map(|b| b.as_device_ptr().as_raw()).collect(); + let aabb_buffers: Vec<_> = aabb_buffers + .iter() + .map(|b| b.as_device_ptr().as_raw()) + .collect(); Ok(CustomPrimitiveArray { aabb_buffers, @@ -1537,8 +1550,14 @@ pub struct Instance<'a> { accel: PhantomData<&'a ()>, } -const_assert_eq!(std::mem::align_of::(), sys::OptixInstanceByteAlignment); -const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); +const_assert_eq!( + std::mem::align_of::(), + sys::OptixInstanceByteAlignment +); +const_assert_eq!( + std::mem::size_of::(), + std::mem::size_of::() +); bitflags::bitflags! { #[derive(DeviceCopy)] @@ -1558,8 +1577,8 @@ impl<'a> Instance<'a> { #[cfg_attr(rustfmt, rustfmt_skip)] Instance { transform: [ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0].into(), instance_id: 0, sbt_offset: 0, @@ -1576,8 +1595,8 @@ impl<'a> Instance<'a> { #[cfg_attr(rustfmt, rustfmt_skip)] Instance { transform: [ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0].into(), instance_id: 0, sbt_offset: 0, @@ -1677,7 +1696,6 @@ impl<'i> Hash for InstancePointerArray<'i> { } } - impl<'i> BuildInput for InstancePointerArray<'i> { fn to_sys(&self) -> sys::OptixBuildInput { cfg_if::cfg_if! { diff --git a/crates/optix/src/denoiser.rs b/crates/optix/src/denoiser.rs index 4d14a211..4d60281c 100644 --- a/crates/optix/src/denoiser.rs +++ b/crates/optix/src/denoiser.rs @@ -8,9 +8,7 @@ use std::{ use cust::{ error::CudaResult, - memory::{ - DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer, GpuBuffer, UnifiedBuffer, - }, + memory::{DeviceBox, DeviceBuffer, DeviceCopy, DevicePointer, GpuBuffer, UnifiedBuffer}, prelude::Stream, }; @@ -561,7 +559,7 @@ impl<'a> Image<'a> { Self::validate_buf(buffer, format, width, height); Self { - buffer: + buffer: // SAFETY: this buffer is never written to for the duration of this image being alive. // And we know the buffer is large enough to be reinterpreted as a buffer of bytes. DevicePointer::from_raw(buffer.as_device_ptr().as_raw()), diff --git a/examples/cuda/cpu/path_tracer/src/cuda/data.rs b/examples/cuda/cpu/path_tracer/src/cuda/data.rs index c487e6c2..d7698498 100644 --- a/examples/cuda/cpu/path_tracer/src/cuda/data.rs +++ b/examples/cuda/cpu/path_tracer/src/cuda/data.rs @@ -4,9 +4,9 @@ use cust::{ memory::{DeviceBuffer, DeviceCopy, UnifiedBuffer}, util::SliceExt, }; -use vek::{num_traits::Zero, Vec2, Vec3}; use gpu_rand::DefaultRand; use path_tracer_gpu::{material::MaterialKind, scene::Scene, Object, Viewport}; +use vek::{num_traits::Zero, Vec2, Vec3}; use super::SEED; diff --git a/examples/cuda/cpu/path_tracer/src/renderer.rs b/examples/cuda/cpu/path_tracer/src/renderer.rs index 0a5c631e..5ec9c512 100644 --- a/examples/cuda/cpu/path_tracer/src/renderer.rs +++ b/examples/cuda/cpu/path_tracer/src/renderer.rs @@ -1,8 +1,8 @@ -use vek::Vec2; use glutin::{event::Event, event_loop::ControlFlow}; use imgui::Ui; use path_tracer_gpu::scene::Scene; use sysinfo::{System, SystemExt}; +use vek::Vec2; use crate::{ common::{Camera, CameraController}, From 04c2059b9ac4e5d4d9b6b6c635aa94631500de14 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Fri, 21 Jan 2022 01:55:15 -0500 Subject: [PATCH 099/100] Chore: exclude examples from building in CI --- .github/workflows/rust.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0d5d1b7b..8f40b639 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -59,7 +59,7 @@ jobs: run: cargo fmt --all -- --check - name: Build - run: cargo build --workspace --exclude "optix" --exclude "optix_sys" --exclude "path_tracer" --exclude "denoiser" --exclude "add" + run: cargo build --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" # Don't currently test because many tests rely on the system having a CUDA GPU # - name: Test @@ -69,9 +69,9 @@ jobs: if: contains(matrix.os, 'ubuntu') env: RUSTFLAGS: -Dwarnings - run: cargo clippy --workspace --exclude "optix" --exclude "optix_sys" --exclude "path_tracer" --exclude "denoiser" --exclude "add" + run: cargo clippy --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" - name: Check documentation env: RUSTDOCFLAGS: -Dwarnings - run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "optix_sys" --exclude "path_tracer" --exclude "denoiser" --exclude "add" \ No newline at end of file + run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" \ No newline at end of file From 741264a292ae86b1afef26a2cee2ebb352ac0ec4 Mon Sep 17 00:00:00 2001 From: rdambrosio Date: Fri, 21 Jan 2022 17:57:50 -0500 Subject: [PATCH 100/100] Feat: update changelog with changes, misc changes before merge --- crates/cust/CHANGELOG.md | 34 ++++- crates/cust/Cargo.toml | 3 +- crates/cust/src/memory/device/device_slice.rs | 130 +++++++++++++++++- crates/cust/src/memory/pointer.rs | 2 +- crates/cust/src/util.rs | 2 +- crates/cust_core/Cargo.toml | 2 +- crates/cust_core/src/lib.rs | 2 +- crates/optix/Cargo.toml | 2 +- crates/optix/examples/ex04_mesh/Cargo.toml | 4 +- crates/optix_device/Cargo.toml | 3 +- 10 files changed, 169 insertions(+), 15 deletions(-) diff --git a/crates/cust/CHANGELOG.md b/crates/cust/CHANGELOG.md index b15d1b8f..437f53ff 100644 --- a/crates/cust/CHANGELOG.md +++ b/crates/cust/CHANGELOG.md @@ -36,18 +36,44 @@ it much easier to write multigpu code. The CUDA API is fully thread-safe except - Added mint integration behind `impl_mint`. - Added half integration behind `impl_half`. - Added glam integration behind `impl_glam`. +- `num-complex` integration is now behind `impl_num_complex`, not `num-complex`. - Added experimental linux external memory import APIs through `cust::external::ExternalMemory`. - `vek` is no longer re-exported. - `DeviceBox` now requires `T: DeviceCopy` (previously it didn't but almost all its methods did). - `DeviceBox::from_raw` now takes a `CUdeviceptr` instead of a `*mut T`. - `DeviceBox::as_device_ptr` now requires `&self` instead of `&mut self`. +- Deleted `DeviceBox::wrap`, use `DeviceBox::from_raw`. - `DeviceBuffer` now requires `T: DeviceCopy`. +- `DeviceBuffer` is now `repr(C)` and is represented by a `DevicePointer` and a `usize`. +- Added `DeviceBuffer::as_slice`. - `DeviceSlice` now requires `T: DeviceCopy`. -- `DeviceSlice` is now represented by a ptr and a len instead of `[T]` which was likely unsound. +- `DeviceSlice` is now represented as a `DevicePointer` and a `usize` (and is repr(C)) instead of `[T]` which was definitely unsound. - `DeviceSlice::as_ptr` and `DeviceSlice::as_ptr_mut` now both return a `DevicePointer`. -- Added `DeviceSlice::as_device_ptr`. -- `DeviceSlice::chunks` and `DeviceSlice::chunks_mut` have been deleted. -- `DeviceSlice::from_slice` and `DeviceSlice::from_slice_mut` have been deleted. +- Deleted `DeviceSlice::as_ptr` and `DeviceSlice::as_mut_ptr`. Use `DeviceSlice::as_device_ptr` then `DevicePointer::as_(mut)_ptr`. +- Deleted `DeviceSlice::chunks` and consequently `DeviceChunks`. +- Deleted `DeviceSlice::chunks_mut` and consequently `DeviceChunksMut`. +- Deleted `DeviceSlice::from_slice` and `DeviceSlice::from_slice_mut` because it was unsound. +- `DeviceSlice` no longer implements `Index` and `IndexMut`, switching away from `[T]` made this impossible to implement. +Instead you can now use `DeviceSlice::index` which behaves the same. +- `DeviceSlice` is now `Clone` and `Copy`. +- Added `DeviceVariable`, a simple wrapper around `DeviceBox` and `T` which allows easy management of a CPU and GPU version of a type. +- Added `DeviceMemory`, a trait describing any region of GPU memory that can be described with a pointer + a length. +- Added `memcpy_htod`, a wrapper around `cuMemcpyHtoD_v2`. +- Added `mem_get_info` to query the amount of free and total memory. +- `DevicePointer::as_raw` now returns a `CUdeviceptr`, not a `*const T` (use `DevicePointer::as_ptr`). +- Deleted `DevicePointer::as_raw_mut` (use `DevicePointer::as_mut_ptr`). +- Added `DevicePointer::as_ptr` and `DevicePointer::as_mut_ptr` for `*const T` and `*mut T`. +- Added `DevicePointer::from_raw` for `CUdeviceptr -> DevicePointer` with a safe function. +- Deleted `DevicePointer::wrap` (use `DevicePointer::from_raw`). +- Added dependency on `cust_core` for `DeviceCopy`. +- Added dependency on `goblin` for verifying cubins and fatbins (impossible to implement safe module loading without it). +- Deprecated `Module::from_str`, use `Module::from_ptx` and pass `&[]` for options. +- Added `ModuleJitOption`, `JitFallback`, `JitTarget`, and `OptLevel` for specifying options when loading a module. Note that +`ModuleJitOption::MaxRegisters` does not seem to work currently, but NVIDIA is looking into it. +- Added `Module::from_fatbin` and `Module::from_fatbin_unchecked`. +- Added `Module::from_cubin` and `Module::from_cubin_unchecked`. +- Added `Module::from_ptr` and `Module::from_ptx_cstr`. +- Deprecated `Module::load_from_string`, use `Module::from_ptx_cstr`. ## 0.2.2 - 12/5/21 diff --git a/crates/cust/Cargo.toml b/crates/cust/Cargo.toml index 018eddb2..30ea3dc2 100644 --- a/crates/cust/Cargo.toml +++ b/crates/cust/Cargo.toml @@ -25,12 +25,13 @@ bytemuck = { version = "1.7.3", optional = true } goblin = { version = "0.4.3", default-features = false, features = ["elf32", "elf64", "std", "endian_fd"] } [features] -default= ["vek", "impl_glam", "impl_mint", "bytemuck"] +default= ["bytemuck"] impl_glam = ["cust_core/glam", "glam"] impl_mint = ["cust_core/mint", "mint"] impl_vek = ["cust_core/vek", "vek"] impl_half = ["cust_core/half"] impl_num_complex = ["cust_core/num-complex", "num-complex"] + [build-dependencies] find_cuda_helper = { path = "../find_cuda_helper", version = "0.2" } diff --git a/crates/cust/src/memory/device/device_slice.rs b/crates/cust/src/memory/device/device_slice.rs index cfd05d51..3e3ee8f6 100644 --- a/crates/cust/src/memory/device/device_slice.rs +++ b/crates/cust/src/memory/device/device_slice.rs @@ -7,10 +7,11 @@ use crate::stream::Stream; use crate::sys as cuda; use std::mem; +use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; use std::os::raw::c_void; /// Fixed-size device-side slice. -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] #[repr(C)] pub struct DeviceSlice { ptr: DevicePointer, @@ -206,6 +207,133 @@ impl DeviceSlice { } } +pub trait DeviceSliceIndex { + /// Indexes into this slice without checking if it is in-bounds. + /// + /// # Safety + /// + /// The range must be in-bounds of the slice. + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice; + fn index(self, slice: &DeviceSlice) -> DeviceSlice; +} + +#[inline(never)] +#[cold] +#[track_caller] +fn slice_start_index_len_fail(index: usize, len: usize) -> ! { + panic!( + "range start index {} out of range for slice of length {}", + index, len + ); +} + +#[inline(never)] +#[cold] +#[track_caller] +fn slice_end_index_len_fail(index: usize, len: usize) -> ! { + panic!( + "range end index {} out of range for slice of length {}", + index, len + ); +} + +#[inline(never)] +#[cold] +#[track_caller] +fn slice_index_order_fail(index: usize, end: usize) -> ! { + panic!("slice index starts at {} but ends at {}", index, end); +} + +#[inline(never)] +#[cold] +#[track_caller] +fn slice_end_index_overflow_fail() -> ! { + panic!("attempted to index slice up to maximum usize"); +} + +impl DeviceSliceIndex for Range { + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice { + DeviceSlice::from_raw_parts(slice.as_device_ptr().add(self.start), self.end - self.start) + } + fn index(self, slice: &DeviceSlice) -> DeviceSlice { + if self.start > self.end { + slice_index_order_fail(self.start, self.end); + } else if self.end > slice.len() { + slice_end_index_len_fail(self.end, slice.len()); + } + // SAFETY: `self` is checked to be valid and in bounds above. + unsafe { self.get_unchecked(slice) } + } +} + +impl DeviceSliceIndex for RangeTo { + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice { + (0..self.end).get_unchecked(slice) + } + fn index(self, slice: &DeviceSlice) -> DeviceSlice { + (0..self.end).index(slice) + } +} + +impl DeviceSliceIndex for RangeFrom { + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice { + (self.start..slice.len()).get_unchecked(slice) + } + fn index(self, slice: &DeviceSlice) -> DeviceSlice { + if self.start > slice.len() { + slice_start_index_len_fail(self.start, slice.len()); + } + // SAFETY: `self` is checked to be valid and in bounds above. + unsafe { self.get_unchecked(slice) } + } +} + +impl DeviceSliceIndex for RangeFull { + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice { + *slice + } + fn index(self, slice: &DeviceSlice) -> DeviceSlice { + *slice + } +} + +fn into_slice_range(range: RangeInclusive) -> Range { + let exclusive_end = range.end() + 1; + let start = if range.is_empty() { + exclusive_end + } else { + *range.start() + }; + start..exclusive_end +} + +impl DeviceSliceIndex for RangeInclusive { + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice { + into_slice_range(self).get_unchecked(slice) + } + fn index(self, slice: &DeviceSlice) -> DeviceSlice { + if *self.end() == usize::MAX { + slice_end_index_overflow_fail(); + } + into_slice_range(self).index(slice) + } +} + +impl DeviceSliceIndex for RangeToInclusive { + unsafe fn get_unchecked(self, slice: &DeviceSlice) -> DeviceSlice { + (0..=self.end).get_unchecked(slice) + } + fn index(self, slice: &DeviceSlice) -> DeviceSlice { + (0..=self.end).index(slice) + } +} + +impl DeviceSlice { + pub fn index>(&self, idx: Idx) -> DeviceSlice { + idx.index(self) + } +} + impl crate::private::Sealed for DeviceSlice {} impl + AsMut<[T]> + ?Sized> CopyDestination for DeviceSlice { fn copy_from(&mut self, val: &I) -> CudaResult<()> { diff --git a/crates/cust/src/memory/pointer.rs b/crates/cust/src/memory/pointer.rs index 09e57464..9f14999b 100644 --- a/crates/cust/src/memory/pointer.rs +++ b/crates/cust/src/memory/pointer.rs @@ -22,7 +22,7 @@ use std::mem::size_of; /// the other side of that boundary does not attempt to dereference the pointer on the CPU. It is /// thus possible to pass a `DevicePointer` to a CUDA kernel written in C. #[repr(transparent)] -#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct DevicePointer { ptr: CUdeviceptr, marker: PhantomData<*mut T>, diff --git a/crates/cust/src/util.rs b/crates/cust/src/util.rs index 91e3f5fc..77baaa8f 100644 --- a/crates/cust/src/util.rs +++ b/crates/cust/src/util.rs @@ -10,7 +10,7 @@ use crate::{ }; pub trait DeviceCopyExt: DeviceCopy { - /// Makes a new [`DBox`] from this value. + /// Makes a new [`DeviceBox`] from this value. fn as_dbox(&self) -> CudaResult> { DeviceBox::new(self) } diff --git a/crates/cust_core/Cargo.toml b/crates/cust_core/Cargo.toml index 7c40f1e5..9de20e72 100644 --- a/crates/cust_core/Cargo.toml +++ b/crates/cust_core/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] vek = { version = "0.15.1", default-features=false, features=["libm"], optional = true } -glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false, optional=true } +glam = { version = "0.20", features=["cuda", "libm"], default-features=false, optional=true } mint = { version = "^0.5", optional = true } half = { version = "1.8", optional = true } num-complex = { version = "0.4", optional = true } diff --git a/crates/cust_core/src/lib.rs b/crates/cust_core/src/lib.rs index b6751250..c647c9c7 100644 --- a/crates/cust_core/src/lib.rs +++ b/crates/cust_core/src/lib.rs @@ -148,7 +148,7 @@ use vek::*; #[cfg(feature = "vek")] impl_device_copy_generic! { - Vec2, Vec3, Vec4, Extent2, Extent3, Rgb, Rgba, + Vec2, Vec3, Vec4, Extent2, Extent3, Mat2, Mat3, Mat4, CubicBezier2, CubicBezier3, Quaternion, diff --git a/crates/optix/Cargo.toml b/crates/optix/Cargo.toml index 82d1c75b..7c803c9e 100644 --- a/crates/optix/Cargo.toml +++ b/crates/optix/Cargo.toml @@ -20,7 +20,7 @@ cust = { version = "0.2", path = "../cust", features=["impl_mint"] } cust_raw = { version = "0.11.2", path = "../cust_raw" } cfg-if = "1.0.0" bitflags = "1.3.2" -glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false, optional=true } +glam = { version = "0.20", features=["cuda", "libm"], default-features=false, optional=true } half = { version = "^1.8", optional = true } memoffset = "0.6.4" mint = "0.5.8" diff --git a/crates/optix/examples/ex04_mesh/Cargo.toml b/crates/optix/examples/ex04_mesh/Cargo.toml index 30a73006..53725824 100644 --- a/crates/optix/examples/ex04_mesh/Cargo.toml +++ b/crates/optix/examples/ex04_mesh/Cargo.toml @@ -7,12 +7,12 @@ edition = "2021" [dependencies] optix = {path = "../../"} -cust = {path = "../../../cust", features=["glam"]} +cust = {path = "../../../cust", features=["impl_glam"]} anyhow = "1.0.44" glfw = "0.42.0" gl = "0.14.0" num-traits = "0.2.14" -glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda"] } +glam = { version = "0.20", features=["cuda"] } [build-dependencies] find_cuda_helper = { version = "0.2", path = "../../../find_cuda_helper" } diff --git a/crates/optix_device/Cargo.toml b/crates/optix_device/Cargo.toml index 02e3131b..c731edfd 100644 --- a/crates/optix_device/Cargo.toml +++ b/crates/optix_device/Cargo.toml @@ -6,5 +6,4 @@ authors = ["Anders Langlands ", "Riccardo D'Ambrosio [dependencies] cuda_std = { version = "0.2", path = "../cuda_std" } -glam = { git = "https://github.com/anderslanglands/glam-rs", branch = "cuda", features=["cuda", "libm"], default-features=false } - +glam = { version = "0.20", features=["cuda", "libm"], default-features=false }