EBGeometry
Compact, header-only C++ library for fast evaluation of signed distance functions
Loading...
Searching...
No Matches
Typedefs | Functions | Variables
EBGeometry::GPU Namespace Reference

Backend-neutral GPU runtime API surface (CUDA or HIP). More...

Typedefs

using Error = int
 Backend runtime error code (cudaError_t under CUDA, hipError_t under HIP).
 
using MemcpyKind = int
 Memory-copy direction (cudaMemcpyKind under CUDA, hipMemcpyKind under HIP).
 
template<class Query , class In >
using TransformResult = std::decay_t< std::invoke_result_t< const Query &, const In & > >
 The result type of a query on an input.
 

Functions

Error memAlloc (void **a_devicePtr, size_t a_numBytes) noexcept
 Allocate device memory (cudaMalloc / hipMalloc).
 
Error memFree (void *a_devicePtr) noexcept
 Free device memory allocated with memAlloc (cudaFree / hipFree).
 
Error memAllocManaged (void **a_ptr, size_t a_numBytes) noexcept
 Allocate managed (unified) memory accessible from host and device (cudaMallocManaged / hipMallocManaged).
 
Error memAllocHost (void **a_ptr, size_t a_numBytes) noexcept
 Allocate page-locked (pinned) host memory (cudaHostAlloc / hipHostMalloc).
 
Error memAllocHostMapped (void **a_ptr, size_t a_numBytes) noexcept
 Allocate page-locked (pinned) host memory that is additionally mapped into the device address space for zero-copy access (cudaHostAlloc with cudaHostAllocMapped / hipHostMalloc with hipHostMallocMapped). Under unified virtual addressing the returned host pointer is directly valid on the device. Free with memFreeHost.
 
Error memFreeHost (void *a_ptr) noexcept
 Free page-locked host memory allocated with memAllocHost (cudaFreeHost / hipHostFree).
 
Error memcpy (void *a_dst, const void *a_src, size_t a_numBytes, MemcpyKind a_kind) noexcept
 Copy memory between host and device (cudaMemcpy / hipMemcpy). Synchronous.
 
Error getDeviceCount (int *a_count) noexcept
 Get the number of available GPU devices (cudaGetDeviceCount / hipGetDeviceCount).
 
Error getLastError () noexcept
 Get and clear the last runtime error (cudaGetLastError / hipGetLastError).
 
Error deviceSynchronize () noexcept
 Block until the device has finished all preceding work (cudaDeviceSynchronize / hipDeviceSynchronize).
 
const char * getErrorString (Error a_error) noexcept
 Get the human-readable description of an error code (cudaGetErrorString / hipGetErrorString).
 
bool isShutdownError (Error a_error) noexcept
 True for the errors a runtime call returns once the runtime has shut down at program exit (cudaErrorCudartUnloading or cudaErrorContextIsDestroyed under CUDA, hipErrorDeinitialized or hipErrorContextIsDestroyed under HIP).
 
const char * getErrorHint (Error a_error) noexcept
 What to do about an error that means the binary has no code this GPU can run: the binary was built for another architecture, or carries only PTX that the driver, older than the toolkit, cannot compile (cudaErrorUnsupportedPtxVersion or cudaErrorNoKernelImageForDevice under CUDA, hipErrorNoBinaryForGpu under HIP).
 
bool deviceAvailable () noexcept
 Whether a GPU is visible to the runtime.
 
MemoryResource & memoryResource () noexcept
 The memory a query's data must live in for parallelFor() and transform() to read it.
 
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class F >
void parallelFor (std::size_t a_n, const F &a_f)
 Run a functor on every index: a_f(i) for i = 0, ..., a_n - 1, in no particular order.
 
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
void transform (const Query &a_query, PODSpan< In > a_in, PODSpan< Out > a_out)
 Run a query on every input in place: a_out[i] = a_query(a_in[i]), with no allocation and no copies.
 
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
void transform (const Query &a_query, const Pool &a_pool, const PODVector< In > &a_in, const PODVector< Out > &a_out)
 Run a query on every input of a pool-resident array, writing the results into another array in the same pool: out[i] = a_query(in[i]), with no allocation and no copies.
 
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
void transform (const Query &a_query, const std::vector< In > &a_in, std::vector< Out > &a_out)
 Run a query on every input, from host memory to host memory: a_out[i] = a_query(a_in[i]).
 

Variables

constexpr Error Success = 0
 Success value of Error (cudaSuccess / hipSuccess).
 
constexpr MemcpyKind MemcpyHostToDevice = 1
 Host-to-device copy direction (cudaMemcpyHostToDevice / hipMemcpyHostToDevice).
 
constexpr MemcpyKind MemcpyDeviceToHost = 2
 Device-to-host copy direction (cudaMemcpyDeviceToHost / hipMemcpyDeviceToHost).
 
constexpr bool enabled = false
 Whether this translation unit is compiled as CUDA or HIP, so that parallelFor() and transform() run a kernel.
 
constexpr const char * backendName = "host"
 Where parallelFor() and transform() run in this translation unit: "HIP", "CUDA" or "host".
 
constexpr unsigned int DefaultBlockSize = 128
 The number of threads per block a launch uses unless it is given another.
 
constexpr unsigned int DefaultMinResidentThreads = 384
 The threads a launch asks the CUDA compiler to keep resident on each multiprocessor, at least, unless it is given another: 384, three blocks of 128, which holds a thread to at most 168 registers.
 

Detailed Description

Backend-neutral GPU runtime API surface (CUDA or HIP).

Thin aliases and inline forwarders over the backend runtime (cuda* under CUDA, hip* under HIP) covering exactly the surface EBGeometry uses: memory allocation/copy/free, device enumeration, error retrieval, and synchronization. The two runtimes mirror each other's signatures 1:1 for this surface, so every wrapper is a one-line forwarder.

Typedef Documentation

◆ TransformResult

template<class Query , class In >
using EBGeometry::GPU::TransformResult = typedef std::decay_t<std::invoke_result_t<const Query&, const In&> >

The result type of a query on an input.

Template Parameters
QueryQuery functor type.
InInput type.

Function Documentation

◆ deviceAvailable()

bool EBGeometry::GPU::deviceAvailable ( )
inlinenoexcept

Whether a GPU is visible to the runtime.

Always false in a host build. In a CUDA or HIP build, false also when the runtime itself reports an error (no driver, for instance) rather than a device count.

Returns
True if at least one device is visible.

◆ deviceSynchronize()

Error EBGeometry::GPU::deviceSynchronize ( )
inlinenoexcept

Block until the device has finished all preceding work (cudaDeviceSynchronize / hipDeviceSynchronize).

Returns
Backend error code (Success on success).

◆ getDeviceCount()

Error EBGeometry::GPU::getDeviceCount ( int *  a_count)
inlinenoexcept

Get the number of available GPU devices (cudaGetDeviceCount / hipGetDeviceCount).

Parameters
[out]a_countReceives the device count.
Returns
Backend error code (Success on success).

◆ getErrorHint()

const char * EBGeometry::GPU::getErrorHint ( Error  a_error)
inlinenoexcept

What to do about an error that means the binary has no code this GPU can run: the binary was built for another architecture, or carries only PTX that the driver, older than the toolkit, cannot compile (cudaErrorUnsupportedPtxVersion or cudaErrorNoKernelImageForDevice under CUDA, hipErrorNoBinaryForGpu under HIP).

Parameters
[in]a_errorBackend error code.
Returns
A hint, starting with a line break, for those errors; an empty string otherwise.

◆ getErrorString()

const char * EBGeometry::GPU::getErrorString ( Error  a_error)
inlinenoexcept

Get the human-readable description of an error code (cudaGetErrorString / hipGetErrorString).

Parameters
[in]a_errorBackend error code.
Returns
Static string describing the error.

◆ getLastError()

Error EBGeometry::GPU::getLastError ( )
inlinenoexcept

Get and clear the last runtime error (cudaGetLastError / hipGetLastError).

Returns
Backend error code (Success if no error is pending).

◆ isShutdownError()

bool EBGeometry::GPU::isShutdownError ( Error  a_error)
inlinenoexcept

True for the errors a runtime call returns once the runtime has shut down at program exit (cudaErrorCudartUnloading or cudaErrorContextIsDestroyed under CUDA, hipErrorDeinitialized or hipErrorContextIsDestroyed under HIP).

Parameters
[in]a_errorBackend error code.
Returns
True if a_error is one of those errors.

◆ memAlloc()

Error EBGeometry::GPU::memAlloc ( void **  a_devicePtr,
size_t  a_numBytes 
)
inlinenoexcept

Allocate device memory (cudaMalloc / hipMalloc).

Parameters
[out]a_devicePtrReceives the device pointer to the allocation.
[in]a_numBytesNumber of bytes to allocate.
Returns
Backend error code (Success on success).

◆ memAllocHost()

Error EBGeometry::GPU::memAllocHost ( void **  a_ptr,
size_t  a_numBytes 
)
inlinenoexcept

Allocate page-locked (pinned) host memory (cudaHostAlloc / hipHostMalloc).

Parameters
[out]a_ptrReceives the host pointer to the pinned allocation.
[in]a_numBytesNumber of bytes to allocate.
Returns
Backend error code (Success on success).

◆ memAllocHostMapped()

Error EBGeometry::GPU::memAllocHostMapped ( void **  a_ptr,
size_t  a_numBytes 
)
inlinenoexcept

Allocate page-locked (pinned) host memory that is additionally mapped into the device address space for zero-copy access (cudaHostAlloc with cudaHostAllocMapped / hipHostMalloc with hipHostMallocMapped). Under unified virtual addressing the returned host pointer is directly valid on the device. Free with memFreeHost.

Parameters
[out]a_ptrReceives the host pointer to the mapped pinned allocation.
[in]a_numBytesNumber of bytes to allocate.
Returns
Backend error code (Success on success).

◆ memAllocManaged()

Error EBGeometry::GPU::memAllocManaged ( void **  a_ptr,
size_t  a_numBytes 
)
inlinenoexcept

Allocate managed (unified) memory accessible from host and device (cudaMallocManaged / hipMallocManaged).

Parameters
[out]a_ptrReceives the managed pointer to the allocation.
[in]a_numBytesNumber of bytes to allocate.
Returns
Backend error code (Success on success).

◆ memcpy()

Error EBGeometry::GPU::memcpy ( void *  a_dst,
const void *  a_src,
size_t  a_numBytes,
MemcpyKind  a_kind 
)
inlinenoexcept

Copy memory between host and device (cudaMemcpy / hipMemcpy). Synchronous.

Parameters
[out]a_dstDestination pointer (host or device, per a_kind).
[in]a_srcSource pointer (host or device, per a_kind).
[in]a_numBytesNumber of bytes to copy.
[in]a_kindCopy direction.
Returns
Backend error code (Success on success).

◆ memFree()

Error EBGeometry::GPU::memFree ( void *  a_devicePtr)
inlinenoexcept

Free device memory allocated with memAlloc (cudaFree / hipFree).

Parameters
[in]a_devicePtrDevice pointer to free (null is a no-op).
Returns
Backend error code (Success on success).

◆ memFreeHost()

Error EBGeometry::GPU::memFreeHost ( void *  a_ptr)
inlinenoexcept

Free page-locked host memory allocated with memAllocHost (cudaFreeHost / hipHostFree).

Parameters
[in]a_ptrHost pointer to free (null is a no-op).
Returns
Backend error code (Success on success).

◆ memoryResource()

MemoryResource & EBGeometry::GPU::memoryResource ( )
inlinenoexcept

The memory a query's data must live in for parallelFor() and transform() to read it.

deviceMemoryResource() in a CUDA or HIP build, and emulatedDeviceMemoryResource() (host memory that reports itself device-accessible) otherwise. Mirroring a frozen pool into it and taking a rebasedView() onto the mirror is then the same code in both builds: a device copy that a kernel reads, or a host copy that the host loop reads.

Returns
The memory resource.

◆ parallelFor()

template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class F >
void EBGeometry::GPU::parallelFor ( std::size_t  a_n,
const F &  a_f 
)
inline

Run a functor on every index: a_f(i) for i = 0, ..., a_n - 1, in no particular order.

In a CUDA or HIP build, a kernel runs the functor once per index (one thread each, in blocks of BlockSize threads) on the current device, and the call returns once it has finished. Every runtime call is checked with EBGEOMETRY_GPU_CHECK, which aborts with the failing call on an error, and a visible device is required (see deviceAvailable()). The functor reaches the kernel by value, so anything it reads or writes must be memory the device can access.

In a host build the functor is copied once, as a kernel launch would copy it, and run in a loop. The functor must be trivially copyable in both builds, which is checked at compile time. A grid holds at most 2^31 - 1 blocks, so a_n may not exceed (2^31 - 1) * BlockSize; a larger count aborts with a message, in both builds.

Template Parameters
BlockSizeThreads per block: a multiple of 64 (an AMD wavefront, and two NVIDIA warps), at most 1024. The kernel carries it as launch bounds, so the compiler can give each thread the registers that block size allows.
MinResidentThreadsIn a CUDA build, the threads to keep resident on a multiprocessor, at least, which fixes the register budget (see DefaultMinResidentThreads); at most 2048, 0 for no minimum.
FTrivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a std::size_t.
Parameters
[in]a_nNumber of indices.
[in]a_fThe functor.

◆ transform() [1/3]

template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
void EBGeometry::GPU::transform ( const Query &  a_query,
const Pool &  a_pool,
const PODVector< In > &  a_in,
const PODVector< Out > &  a_out 
)
inline

Run a query on every input of a pool-resident array, writing the results into another array in the same pool: out[i] = a_query(in[i]), with no allocation and no copies.

The span overload, with the memory checked first. A PODVector is an offset into a pool, so this overload takes the pool both arrays live in and binds them against its base. It aborts with a message, in every build, unless the pool is frozen (as rebasedView() requires of a device-accessible pool) and its resource is device-accessible: a mirror into memoryResource(), or a pool built in managed memory. A pool in hostMemoryResource() is therefore rejected in a host build too, where memoryResource() is the emulated device resource. A PODVector holds an offset, not its pool, so membership can be checked only as far as both arrays ending inside the pool's used bytes, which also aborts otherwise; that an array really belongs to this pool (or a mirror of its pool) is the caller's responsibility. The inputs are bound read-only. Freezing forbids reservations, not writes, so the results can be written into a frozen pool, in place over the inputs or into a disjoint array as for the span overload. Arrays in two different pools, or not in a pool at all, go through the span overload.

Template Parameters
BlockSizeThreads per block; see parallelFor().
MinResidentThreadsThreads to keep resident, in a CUDA build; see parallelFor().
QueryTrivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a const In&.
InInput type.
OutResult type: the query's result type.
Parameters
[in]a_queryThe query.
[in]a_poolThe frozen, device-accessible pool holding both arrays.
[in]a_inThe inputs.
[in]a_outThe results' array, as many elements as a_in; its elements are written.

◆ transform() [2/3]

template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
void EBGeometry::GPU::transform ( const Query &  a_query,
const std::vector< In > &  a_in,
std::vector< Out > &  a_out 
)
inline

Run a query on every input, from host memory to host memory: a_out[i] = a_query(a_in[i]).

A convenience over the span overload. In a CUDA or HIP build it allocates device memory for the inputs and the results, copies the inputs in, runs the query in a kernel, copies the results into a_out and frees, so timing it includes two allocations and two copies; time the span or pool overload to measure the query itself. In a host build the query runs in a loop directly on the vectors. a_out is not resized: it must already hold as many elements as a_in, which aborts with a message otherwise, and can be reused across calls. At most 2^32 - 1 inputs, which also aborts with a message otherwise. Neither the input nor the result may be bool (std::vector<bool> has no contiguous storage to copy), which is checked at compile time in both builds.

Template Parameters
BlockSizeThreads per block; see parallelFor().
MinResidentThreadsThreads to keep resident, in a CUDA build; see parallelFor().
QueryTrivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a const In&.
InTrivially copyable input type.
OutResult type: the query's result type.
Parameters
[in]a_queryThe query.
[in]a_inThe inputs.
[out]a_outThe results, in input order; as many elements as a_in.

◆ transform() [3/3]

template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
void EBGeometry::GPU::transform ( const Query &  a_query,
PODSpan< In >  a_in,
PODSpan< Out >  a_out 
)
inline

Run a query on every input in place: a_out[i] = a_query(a_in[i]), with no allocation and no copies.

The inputs and the results stay wherever the caller put them, so this is the call to time when a query's cost is what is being measured, and the one to use for repeated queries over the same points. A span is a pointer and a count: bind a PODVector against the base of a pool mirrored into memoryResource() (inputs) or of a pool reserved there (results), or wrap managed memory or a buffer the application already owns on the device. In a host build the spans are host memory. The results may overwrite the inputs in place (the same array, when In and Out are the same type), but the two must otherwise not overlap, which aborts with a message. The query runs through parallelFor(), with its requirements.

Template Parameters
BlockSizeThreads per block; see parallelFor().
MinResidentThreadsThreads to keep resident, in a CUDA build; see parallelFor().
QueryTrivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a const In&.
InInput type, const or not.
OutResult type: the query's result type, not const.
Parameters
[in]a_queryThe query.
[in]a_inThe inputs, in memory the device can read.
[out]a_outThe results, in memory the device can write; as many as there are inputs.

Variable Documentation

◆ DefaultBlockSize

constexpr unsigned int EBGeometry::GPU::DefaultBlockSize = 128
inlineconstexpr

The number of threads per block a launch uses unless it is given another.

Not tuned: the best block size depends on the device and on the query (a BVH traversal uses many registers per thread). Pass another as the first template argument to measure it, e.g. GPU::transform<256>(query, points, results).

◆ DefaultMinResidentThreads

constexpr unsigned int EBGeometry::GPU::DefaultMinResidentThreads = 384
inlineconstexpr

The threads a launch asks the CUDA compiler to keep resident on each multiprocessor, at least, unless it is given another: 384, three blocks of 128, which holds a thread to at most 168 registers.

The kernel carries it as launch bounds (a minimum number of resident blocks, rounded up). Without a minimum, the compiler picks one register budget for the whole kernel, every function it calls included, and the pick jumps with small changes to code the kernel may not even run (a tape's ray kernel once went from 255 registers to 40, spilling twice as much); given one, it uses the budget the minimum allows. Not tuned for every query: the budget is a ceiling the compiler tends to fill, so a kernel that runs best with few registers may be slower under it (rays traced through a fused composition went from 56 registers to 161 and ran up to a third slower, and the renderer's frame of a mesh from 128 to 161, 10% slower). The minimum still pays where registers matter: on one desktop GPU, ray tracing through a tape took 2.5 times as long with no minimum as at 384, and 1.45 times as long at 768, while a mesh's signed distance took the same time at every setting; DeviceTuning's mesh-minus-ball tape distance in float was a fifth to a quarter faster at 768 than at 384, whatever the block size. 0 asks for no minimum. HIP reads a second launch bound differently, and a HIP build ignores it.