builtin-programs/recognition/optical-flow.folk

# (Most people don't need this program to be working unless you know
# you are using optical flow for some specific camera recognition
# task.)

# NVIDIA hardware optical flow through the Optical Flow SDK CUDA interface.
# No CUDA Toolkit is needed at build time: the driver entry points are loaded
# from libcuda and libnvidia-opticalflow at runtime.
#
# Returns an opaque OpticalFlow pointer. Sample it with sampleOpticalFlow and
# release it with freeOpticalFlow. Vectors point from f0 to f1 in source-image
# pixel units.

fn opticalFlowInit {imageLib} {
    set cc [C]
    $cc extend $imageLib
    $cc include <dlfcn.h>
    $cc include <pthread.h>
    $cc include <stdlib.h>
    $cc include <string.h>

    $cc code {
        /* Minimal CUDA Driver API ABI.  Keeping this here makes the module
         * compile on machines which have an NVIDIA runtime but no Toolkit. */
        typedef int CUresult;
        typedef int CUdevice;
        typedef struct CUctx_st *CUcontext;
        typedef unsigned long long CUdeviceptr;
        typedef int CUmemorytype;
        #define CUDA_SUCCESS 0
        #define CU_MEMORYTYPE_HOST 1
        #define CU_MEMORYTYPE_DEVICE 2

        typedef struct CUDA_MEMCPY2D {
            size_t srcXInBytes;
            size_t srcY;
            CUmemorytype srcMemoryType;
            const void *srcHost;
            CUdeviceptr srcDevice;
            void *srcArray;
            size_t srcPitch;
            size_t dstXInBytes;
            size_t dstY;
            CUmemorytype dstMemoryType;
            void *dstHost;
            CUdeviceptr dstDevice;
            void *dstArray;
            size_t dstPitch;
            size_t WidthInBytes;
            size_t Height;
        } CUDA_MEMCPY2D;

        typedef CUresult (*PFN_cuInit)(unsigned int);
        typedef CUresult (*PFN_cuDeviceGetCount)(int *);
        typedef CUresult (*PFN_cuDeviceGet)(CUdevice *, int);
        typedef CUresult (*PFN_cuCtxCreate)(CUcontext *, unsigned int, CUdevice);
        typedef CUresult (*PFN_cuCtxDestroy)(CUcontext);
        typedef CUresult (*PFN_cuCtxGetCurrent)(CUcontext *);
        typedef CUresult (*PFN_cuCtxSetCurrent)(CUcontext);
        typedef CUresult (*PFN_cuCtxSynchronize)(void);
        typedef CUresult (*PFN_cuMemcpy2D)(const CUDA_MEMCPY2D *);
        typedef CUresult (*PFN_cuGetErrorName)(CUresult, const char **);
        typedef CUresult (*PFN_cuGetErrorString)(CUresult, const char **);

        typedef struct CUDAFunctions {
            void *library;
            PFN_cuInit init;
            PFN_cuDeviceGetCount deviceGetCount;
            PFN_cuDeviceGet deviceGet;
            PFN_cuCtxCreate contextCreate;
            PFN_cuCtxDestroy contextDestroy;
            PFN_cuCtxGetCurrent contextGetCurrent;
            PFN_cuCtxSetCurrent contextSetCurrent;
            PFN_cuCtxSynchronize contextSynchronize;
            PFN_cuMemcpy2D memcpy2D;
            PFN_cuGetErrorName getErrorName;
            PFN_cuGetErrorString getErrorString;
        } CUDAFunctions;

        /* NVIDIA Optical Flow SDK 2.x ABI. */
        typedef struct NvOFHandle_st *NvOFHandle;
        typedef struct NvOFGPUBufferHandle_st *NvOFGPUBufferHandle;
        typedef struct NVOFPrivDataHandle_st *NvOFPrivDataHandle;
        typedef int NV_OF_STATUS;
        typedef int NV_OF_BOOL;
        typedef int NV_OF_CAPS;
        typedef int NV_OF_OUTPUT_VECTOR_GRID_SIZE;
        typedef int NV_OF_HINT_VECTOR_GRID_SIZE;
        typedef int NV_OF_MODE;
        typedef int NV_OF_PERF_LEVEL;
        typedef int NV_OF_STEREO_DISPARITY_RANGE;
        typedef int NV_OF_BUFFER_USAGE;
        typedef int NV_OF_BUFFER_FORMAT;
        typedef int NV_OF_CUDA_BUFFER_TYPE;

        #define NV_OF_API_VERSION ((uint16_t)(2u << 4))
        #define NV_OF_SUCCESS 0
        #define NV_OF_TRUE 1
        #define NV_OF_CAPS_SUPPORTED_OUTPUT_GRID_SIZES 0
        #define NV_OF_MODE_OPTICALFLOW 1
        #define NV_OF_PERF_LEVEL_MEDIUM 10
        #define NV_OF_BUFFER_USAGE_INPUT 1
        #define NV_OF_BUFFER_USAGE_OUTPUT 2
        #define NV_OF_BUFFER_FORMAT_GRAYSCALE8 1
        #define NV_OF_BUFFER_FORMAT_SHORT2 5
        #define NV_OF_CUDA_BUFFER_TYPE_CUDEVICEPTR 2

        typedef struct NV_OF_FLOW_VECTOR {
            int16_t flowx;
            int16_t flowy;
        } NV_OF_FLOW_VECTOR;

        typedef struct OpticalFlow {
            uint32_t width;
            uint32_t height;
            uint32_t gridSize;
            NV_OF_FLOW_VECTOR *vectors;
        } OpticalFlow;

        typedef struct NV_OF_INIT_PARAMS {
            uint32_t width;
            uint32_t height;
            NV_OF_OUTPUT_VECTOR_GRID_SIZE outGridSize;
            NV_OF_HINT_VECTOR_GRID_SIZE hintGridSize;
            NV_OF_MODE mode;
            NV_OF_PERF_LEVEL perfLevel;
            NV_OF_BOOL enableExternalHints;
            NV_OF_BOOL enableOutputCost;
            NvOFPrivDataHandle hPrivData;
            NV_OF_STEREO_DISPARITY_RANGE disparityRange;
            NV_OF_BOOL enableRoi;
        } NV_OF_INIT_PARAMS;

        typedef struct NV_OF_BUFFER_DESCRIPTOR {
            uint32_t width;
            uint32_t height;
            NV_OF_BUFFER_USAGE bufferUsage;
            NV_OF_BUFFER_FORMAT bufferFormat;
        } NV_OF_BUFFER_DESCRIPTOR;

        typedef struct NV_OF_ROI_RECT {
            uint32_t start_x, start_y, width, height;
        } NV_OF_ROI_RECT;

        typedef struct NV_OF_EXECUTE_INPUT_PARAMS {
            NvOFGPUBufferHandle inputFrame;
            NvOFGPUBufferHandle referenceFrame;
            NvOFGPUBufferHandle externalHints;
            NV_OF_BOOL disableTemporalHints;
            uint32_t padding;
            NvOFPrivDataHandle hPrivData;
            uint32_t padding2;
            uint32_t numRois;
            NV_OF_ROI_RECT *roiData;
        } NV_OF_EXECUTE_INPUT_PARAMS;

        typedef struct NV_OF_EXECUTE_OUTPUT_PARAMS {
            NvOFGPUBufferHandle outputBuffer;
            NvOFGPUBufferHandle outputCostBuffer;
            NvOFPrivDataHandle hPrivData;
        } NV_OF_EXECUTE_OUTPUT_PARAMS;

        typedef struct NV_OF_BUFFER_STRIDE {
            uint32_t strideXInBytes;
            uint32_t strideYInBytes;
        } NV_OF_BUFFER_STRIDE;

        typedef struct NV_OF_CUDA_BUFFER_STRIDE_INFO {
            NV_OF_BUFFER_STRIDE strideInfo[3];
            uint32_t numPlanes;
        } NV_OF_CUDA_BUFFER_STRIDE_INFO;

        typedef NV_OF_STATUS (*PFN_nvCreateOpticalFlowCuda)(
            CUcontext, NvOFHandle *);
        typedef NV_OF_STATUS (*PFN_nvOFInit)(
            NvOFHandle, const NV_OF_INIT_PARAMS *);
        typedef NV_OF_STATUS (*PFN_nvOFCreateGPUBufferCuda)(
            NvOFHandle, const NV_OF_BUFFER_DESCRIPTOR *,
            NV_OF_CUDA_BUFFER_TYPE, NvOFGPUBufferHandle *);
        typedef void *(*PFN_nvOFGPUBufferGetCUarray)(NvOFGPUBufferHandle);
        typedef CUdeviceptr (*PFN_nvOFGPUBufferGetCUdeviceptr)(
            NvOFGPUBufferHandle);
        typedef NV_OF_STATUS (*PFN_nvOFGPUBufferGetStrideInfo)(
            NvOFGPUBufferHandle, NV_OF_CUDA_BUFFER_STRIDE_INFO *);
        typedef NV_OF_STATUS (*PFN_nvOFSetIOCudaStreams)(
            NvOFHandle, void *, void *);
        typedef NV_OF_STATUS (*PFN_nvOFExecute)(
            NvOFHandle, const NV_OF_EXECUTE_INPUT_PARAMS *,
            NV_OF_EXECUTE_OUTPUT_PARAMS *);
        typedef NV_OF_STATUS (*PFN_nvOFDestroyGPUBufferCuda)(
            NvOFGPUBufferHandle);
        typedef NV_OF_STATUS (*PFN_nvOFDestroy)(NvOFHandle);
        typedef NV_OF_STATUS (*PFN_nvOFGetLastError)(
            NvOFHandle, char *, uint32_t *);
        typedef NV_OF_STATUS (*PFN_nvOFGetCaps)(
            NvOFHandle, NV_OF_CAPS, uint32_t *, uint32_t *);

        typedef struct NV_OF_CUDA_API_FUNCTION_LIST {
            PFN_nvCreateOpticalFlowCuda nvCreateOpticalFlowCuda;
            PFN_nvOFInit nvOFInit;
            PFN_nvOFCreateGPUBufferCuda nvOFCreateGPUBufferCuda;
            PFN_nvOFGPUBufferGetCUarray nvOFGPUBufferGetCUarray;
            PFN_nvOFGPUBufferGetCUdeviceptr nvOFGPUBufferGetCUdeviceptr;
            PFN_nvOFGPUBufferGetStrideInfo nvOFGPUBufferGetStrideInfo;
            PFN_nvOFSetIOCudaStreams nvOFSetIOCudaStreams;
            PFN_nvOFExecute nvOFExecute;
            PFN_nvOFDestroyGPUBufferCuda nvOFDestroyGPUBufferCuda;
            PFN_nvOFDestroy nvOFDestroy;
            PFN_nvOFGetLastError nvOFGetLastError;
            PFN_nvOFGetCaps nvOFGetCaps;
        } NV_OF_CUDA_API_FUNCTION_LIST;

        typedef NV_OF_STATUS (*PFN_NvOFAPICreateInstanceCuda)(
            uint32_t, NV_OF_CUDA_API_FUNCTION_LIST *);

        typedef struct OpticalFlowRuntime {
            CUDAFunctions cuda;
            void *opticalFlowLibrary;
            NV_OF_CUDA_API_FUNCTION_LIST of;
            int attempted;
            int ready;
            char error[512];
        } OpticalFlowRuntime;

        static OpticalFlowRuntime runtime;

        typedef struct OpticalFlowSession {
            pthread_mutex_t mutex;
            int initialized;
            CUcontext context;
            NvOFHandle handle;
            uint32_t gridSize;
            uint32_t width;
            uint32_t height;
            uint32_t flowWidth;
            uint32_t flowHeight;
            NvOFGPUBufferHandle input;
            NvOFGPUBufferHandle reference;
            NvOFGPUBufferHandle output;
            NV_OF_CUDA_BUFFER_STRIDE_INFO inputStride;
            NV_OF_CUDA_BUFFER_STRIDE_INFO referenceStride;
            NV_OF_CUDA_BUFFER_STRIDE_INFO outputStride;
            CUdeviceptr inputPointer;
            CUdeviceptr referencePointer;
            CUdeviceptr outputPointer;
            uint8_t *grayFrame;
            size_t grayFrameSize;
            uint64_t referenceUniq;
            int referenceValid;
        } OpticalFlowSession;

        static OpticalFlowSession session = {
            .mutex = PTHREAD_MUTEX_INITIALIZER,
        };

        static void *ofOpenLibrary(const char *first, const char *second)
        {
            void *library = dlopen(first, RTLD_NOW | RTLD_LOCAL);
            if (!library && second)
                library = dlopen(second, RTLD_NOW | RTLD_LOCAL);
            return library;
        }

        static int ofLoadSymbol(void *library, const char *name, void **result)
        {
            *result = dlsym(library, name);
            if (*result) return 1;
            snprintf(runtime.error, sizeof(runtime.error),
                     "opticalFlow: missing driver symbol %s", name);
            return 0;
        }

        #define OF_LOAD(LIBRARY, FIELD, NAME) \
            do { if (!ofLoadSymbol((LIBRARY), (NAME), \
                                   (void **)&(FIELD))) return 0; } while (0)

        static int ofLoadRuntime(void)
        {
            if (runtime.ready) return 1;
            if (runtime.attempted) return 0;
            runtime.attempted = 1;

        #if defined(__APPLE__)
            snprintf(runtime.error, sizeof(runtime.error),
                     "opticalFlow: NVIDIA CUDA optical flow is unavailable "
                     "on macOS");
            return 0;
        #else
            runtime.cuda.library = ofOpenLibrary("libcuda.so.1", "libcuda.so");
            if (!runtime.cuda.library) {
                snprintf(runtime.error, sizeof(runtime.error),
                         "opticalFlow: could not load libcuda.so.1: %s",
                         dlerror());
                return 0;
            }
            OF_LOAD(runtime.cuda.library, runtime.cuda.init, "cuInit");
            OF_LOAD(runtime.cuda.library, runtime.cuda.deviceGetCount,
                    "cuDeviceGetCount");
            OF_LOAD(runtime.cuda.library, runtime.cuda.deviceGet, "cuDeviceGet");
            OF_LOAD(runtime.cuda.library, runtime.cuda.contextCreate,
                    "cuCtxCreate_v2");
            OF_LOAD(runtime.cuda.library, runtime.cuda.contextDestroy,
                    "cuCtxDestroy_v2");
            OF_LOAD(runtime.cuda.library, runtime.cuda.contextGetCurrent,
                    "cuCtxGetCurrent");
            OF_LOAD(runtime.cuda.library, runtime.cuda.contextSetCurrent,
                    "cuCtxSetCurrent");
            OF_LOAD(runtime.cuda.library, runtime.cuda.contextSynchronize,
                    "cuCtxSynchronize");
            OF_LOAD(runtime.cuda.library, runtime.cuda.memcpy2D,
                    "cuMemcpy2D_v2");
            /* These are diagnostic-only and may be absent on very old drivers. */
            runtime.cuda.getErrorName =
                (PFN_cuGetErrorName)dlsym(runtime.cuda.library, "cuGetErrorName");
            runtime.cuda.getErrorString =
                (PFN_cuGetErrorString)dlsym(runtime.cuda.library,
                                            "cuGetErrorString");

            runtime.opticalFlowLibrary = ofOpenLibrary(
                "libnvidia-opticalflow.so.1", "libnvidia-opticalflow.so");
            if (!runtime.opticalFlowLibrary) {
                snprintf(runtime.error, sizeof(runtime.error),
                         "opticalFlow: could not load "
                         "libnvidia-opticalflow.so.1: %s", dlerror());
                return 0;
            }
            PFN_NvOFAPICreateInstanceCuda createInstance = NULL;
            OF_LOAD(runtime.opticalFlowLibrary, createInstance,
                    "NvOFAPICreateInstanceCuda");
            NV_OF_STATUS status = createInstance(NV_OF_API_VERSION, &runtime.of);
            if (status != NV_OF_SUCCESS) {
                snprintf(runtime.error, sizeof(runtime.error),
                         "opticalFlow: NvOFAPICreateInstanceCuda failed (%d)",
                         status);
                return 0;
            }
            runtime.ready = 1;
            return 1;
        #endif
        }

        static void ofCUDAError(char *error, size_t size, const char *operation,
                                CUresult result)
        {
            const char *name = NULL, *description = NULL;
            if (runtime.cuda.getErrorName)
                runtime.cuda.getErrorName(result, &name);
            if (runtime.cuda.getErrorString)
                runtime.cuda.getErrorString(result, &description);
            snprintf(error, size, "opticalFlow: %s failed: %s%s%s (%d)",
                     operation, name ? name : "CUDA error",
                     description ? ": " : "", description ? description : "",
                     result);
        }

        static void ofSDKError(char *error, size_t size, const char *operation,
                               NV_OF_STATUS status, NvOFHandle handle)
        {
            char detail[256] = {0};
            uint32_t detailSize = sizeof(detail);
            if (handle && runtime.of.nvOFGetLastError)
                runtime.of.nvOFGetLastError(handle, detail, &detailSize);
            snprintf(error, size, "opticalFlow: %s failed (%d)%s%s",
                     operation, status, detail[0] ? ": " : "", detail);
        }

        static void ofCopyGrayRow(Image image, uint32_t y, uint8_t *output)
        {
            const uint8_t *row = image.data + (size_t)y * image.bytesPerRow;
            if (image.components == 1) {
                memcpy(output, row, image.width);
                return;
            }
            for (uint32_t x = 0; x < image.width; x++) {
                const uint8_t *pixel = row + (size_t)x * image.components;
                output[x] = image.components >= 3
                    ? (uint8_t)((77u * pixel[0] + 150u * pixel[1] +
                                29u * pixel[2] + 128u) >> 8)
                    : pixel[0];
            }
        }

        static void ofCopyGrayImage(Image image, uint8_t *output,
                                    size_t outputPitch)
        {
            if (image.components == 1 && image.bytesPerRow == image.width &&
                outputPitch == image.width) {
                memcpy(output, image.data, (size_t)image.width * image.height);
                return;
            }
            for (uint32_t y = 0; y < image.height; y++)
                ofCopyGrayRow(image, y,
                              output + (size_t)y * outputPitch);
        }

        static CUresult ofUploadGray(Image image, CUdeviceptr destination,
                                     size_t destinationPitch)
        {
            ofCopyGrayImage(image, session.grayFrame, image.width);
            CUDA_MEMCPY2D copy = {0};
            copy.srcMemoryType = CU_MEMORYTYPE_HOST;
            copy.srcHost = session.grayFrame;
            copy.srcPitch = image.width;
            copy.dstMemoryType = CU_MEMORYTYPE_DEVICE;
            copy.dstDevice = destination;
            copy.dstPitch = destinationPitch;
            copy.WidthInBytes = image.width;
            copy.Height = image.height;
            return runtime.cuda.memcpy2D(&copy);
        }

        static void ofDestroyBuffers(void)
        {
            if (session.output)
                runtime.of.nvOFDestroyGPUBufferCuda(session.output);
            if (session.reference)
                runtime.of.nvOFDestroyGPUBufferCuda(session.reference);
            if (session.input)
                runtime.of.nvOFDestroyGPUBufferCuda(session.input);
            session.input = NULL;
            session.reference = NULL;
            session.output = NULL;
            session.inputPointer = 0;
            session.referencePointer = 0;
            session.outputPointer = 0;
            session.referenceUniq = 0;
            session.referenceValid = 0;
            memset(&session.inputStride, 0, sizeof(session.inputStride));
            memset(&session.referenceStride, 0, sizeof(session.referenceStride));
            memset(&session.outputStride, 0, sizeof(session.outputStride));
            free(session.grayFrame);
            session.grayFrame = NULL;
            session.grayFrameSize = 0;
            session.width = 0;
            session.height = 0;
            session.flowWidth = 0;
            session.flowHeight = 0;
        }

        static void ofDestroyHandle(void)
        {
            ofDestroyBuffers();
            if (session.handle) runtime.of.nvOFDestroy(session.handle);
            session.handle = NULL;
            session.gridSize = 0;
        }

        static int ofCreateHandle(char *error, size_t errorSize)
        {
            NV_OF_STATUS status = runtime.of.nvCreateOpticalFlowCuda(
                session.context, &session.handle);
            if (status != NV_OF_SUCCESS) {
                ofSDKError(error, errorSize, "nvCreateOpticalFlowCuda", status,
                           session.handle);
                session.handle = NULL;
                return 0;
            }

            uint32_t capCount = 0;
            status = runtime.of.nvOFGetCaps(
                session.handle, NV_OF_CAPS_SUPPORTED_OUTPUT_GRID_SIZES,
                NULL, &capCount);
            if (status != NV_OF_SUCCESS || !capCount) {
                ofSDKError(error, errorSize, "nvOFGetCaps", status,
                           session.handle);
                ofDestroyHandle();
                return 0;
            }
            uint32_t *gridSizes = malloc(sizeof(*gridSizes) * capCount);
            if (!gridSizes) {
                snprintf(error, errorSize, "opticalFlow: out of host memory");
                ofDestroyHandle();
                return 0;
            }
            status = runtime.of.nvOFGetCaps(
                session.handle, NV_OF_CAPS_SUPPORTED_OUTPUT_GRID_SIZES,
                gridSizes, &capCount);
            if (status == NV_OF_SUCCESS) {
                for (uint32_t i = 0; i < capCount; i++) {
                    uint32_t candidate = gridSizes[i];
                    if ((candidate == 1 || candidate == 2 || candidate == 4) &&
                        (!session.gridSize || candidate < session.gridSize))
                        session.gridSize = candidate;
                }
            }
            free(gridSizes);
            if (status != NV_OF_SUCCESS || !session.gridSize) {
                ofSDKError(error, errorSize, "output-grid query", status,
                           session.handle);
                ofDestroyHandle();
                return 0;
            }
            return 1;
        }

        static int ofConfigure(uint32_t width, uint32_t height,
                               char *error, size_t errorSize)
        {
            if (session.width == width && session.height == height &&
                session.input && session.reference && session.output)
                return 1;

            if (session.width || session.height) {
                ofDestroyHandle();
                if (!ofCreateHandle(error, errorSize)) return 0;
            } else {
                ofDestroyBuffers();
                if (!session.handle && !ofCreateHandle(error, errorSize))
                    return 0;
            }

            NV_OF_INIT_PARAMS init = {0};
            init.width = width;
            init.height = height;
            init.outGridSize = session.gridSize;
            init.mode = NV_OF_MODE_OPTICALFLOW;
            init.perfLevel = NV_OF_PERF_LEVEL_MEDIUM;
            NV_OF_STATUS status = runtime.of.nvOFInit(session.handle, &init);
            if (status != NV_OF_SUCCESS) {
                ofSDKError(error, errorSize, "nvOFInit", status,
                           session.handle);
                goto fail;
            }

            uint32_t flowWidth =
                (width + session.gridSize - 1) / session.gridSize;
            uint32_t flowHeight =
                (height + session.gridSize - 1) / session.gridSize;
            NV_OF_BUFFER_DESCRIPTOR inputDescription = {
                width, height, NV_OF_BUFFER_USAGE_INPUT,
                NV_OF_BUFFER_FORMAT_GRAYSCALE8
            };
            NV_OF_BUFFER_DESCRIPTOR outputDescription = {
                flowWidth, flowHeight, NV_OF_BUFFER_USAGE_OUTPUT,
                NV_OF_BUFFER_FORMAT_SHORT2
            };
            status = runtime.of.nvOFCreateGPUBufferCuda(
                session.handle, &inputDescription,
                NV_OF_CUDA_BUFFER_TYPE_CUDEVICEPTR, &session.input);
            if (status != NV_OF_SUCCESS) {
                ofSDKError(error, errorSize, "create input buffer", status,
                           session.handle);
                goto fail;
            }
            status = runtime.of.nvOFCreateGPUBufferCuda(
                session.handle, &inputDescription,
                NV_OF_CUDA_BUFFER_TYPE_CUDEVICEPTR, &session.reference);
            if (status != NV_OF_SUCCESS) {
                ofSDKError(error, errorSize, "create reference buffer", status,
                           session.handle);
                goto fail;
            }
            status = runtime.of.nvOFCreateGPUBufferCuda(
                session.handle, &outputDescription,
                NV_OF_CUDA_BUFFER_TYPE_CUDEVICEPTR, &session.output);
            if (status != NV_OF_SUCCESS) {
                ofSDKError(error, errorSize, "create output buffer", status,
                           session.handle);
                goto fail;
            }

            if (runtime.of.nvOFGPUBufferGetStrideInfo(
                    session.input, &session.inputStride) != NV_OF_SUCCESS ||
                runtime.of.nvOFGPUBufferGetStrideInfo(
                    session.reference, &session.referenceStride) !=
                    NV_OF_SUCCESS ||
                runtime.of.nvOFGPUBufferGetStrideInfo(
                    session.output, &session.outputStride) != NV_OF_SUCCESS ||
                !session.inputStride.strideInfo[0].strideXInBytes ||
                !session.referenceStride.strideInfo[0].strideXInBytes ||
                !session.outputStride.strideInfo[0].strideXInBytes) {
                snprintf(error, errorSize,
                         "opticalFlow: NVIDIA returned invalid buffer strides");
                goto fail;
            }
            session.inputPointer =
                runtime.of.nvOFGPUBufferGetCUdeviceptr(session.input);
            session.referencePointer =
                runtime.of.nvOFGPUBufferGetCUdeviceptr(session.reference);
            session.outputPointer =
                runtime.of.nvOFGPUBufferGetCUdeviceptr(session.output);
            if (!session.inputPointer || !session.referencePointer ||
                !session.outputPointer) {
                snprintf(error, errorSize,
                         "opticalFlow: NVIDIA returned a null CUDA buffer");
                goto fail;
            }
            session.grayFrameSize = (size_t)width * height;
            session.grayFrame = malloc(session.grayFrameSize);
            if (!session.grayFrame) {
                snprintf(error, errorSize, "opticalFlow: out of host memory");
                goto fail;
            }

            session.width = width;
            session.height = height;
            session.flowWidth = flowWidth;
            session.flowHeight = flowHeight;
            return 1;

        fail:
            ofDestroyHandle();
            return 0;
        }
    }

    $cc proc initOpticalFlow {} void {
        if (!ofLoadRuntime()) FOLK_ERROR("%s", runtime.error);

        char error[512] = {0};
        int success = 0;
        pthread_mutex_lock(&session.mutex);
        if (session.initialized) {
            pthread_mutex_unlock(&session.mutex);
            return;
        }

        CUresult cudaResult = runtime.cuda.init(0);
        if (cudaResult != CUDA_SUCCESS) {
            ofCUDAError(error, sizeof(error), "cuInit", cudaResult);
            goto done;
        }
        CUcontext previousContext = NULL;
        runtime.cuda.contextGetCurrent(&previousContext);
        int deviceCount = 0;
        cudaResult = runtime.cuda.deviceGetCount(&deviceCount);
        if (cudaResult != CUDA_SUCCESS) {
            ofCUDAError(error, sizeof(error), "cuDeviceGetCount", cudaResult);
            goto restore;
        }
        for (int i = 0; i < deviceCount; i++) {
            CUdevice device;
            if (runtime.cuda.deviceGet(&device, i) != CUDA_SUCCESS) continue;
            if (runtime.cuda.contextCreate(&session.context, 0, device) !=
                CUDA_SUCCESS)
                continue;
            if (ofCreateHandle(error, sizeof(error))) {
                success = 1;
                break;
            }
            runtime.cuda.contextDestroy(session.context);
            session.context = NULL;
        }
        if (!success)
            snprintf(error, sizeof(error),
                     "opticalFlow: no CUDA device supports NVIDIA Optical Flow");

    restore:
        runtime.cuda.contextSetCurrent(previousContext);
        if (success) session.initialized = 1;
    done:
        pthread_mutex_unlock(&session.mutex);
        if (!success) FOLK_ERROR("%s", error);
    }

    $cc proc shutdownOpticalFlow {} void {
        pthread_mutex_lock(&session.mutex);
        if (session.initialized) {
            CUcontext previousContext = NULL;
            CUcontext destroyedContext = session.context;
            runtime.cuda.contextGetCurrent(&previousContext);
            runtime.cuda.contextSetCurrent(destroyedContext);
            ofDestroyHandle();
            runtime.cuda.contextDestroy(destroyedContext);
            session.context = NULL;
            session.initialized = 0;
            runtime.cuda.contextSetCurrent(
                previousContext == destroyedContext ? NULL : previousContext);
        }
        pthread_mutex_unlock(&session.mutex);
    }

    $cc proc runOpticalFlow {Image f0 Image f1} OpticalFlow* {
        if (!f0.data || !f1.data || !f0.width || !f0.height)
            FOLK_ERROR("opticalFlow: frames must be non-empty images");
        if (f0.width != f1.width || f0.height != f1.height)
            FOLK_ERROR("opticalFlow: frame dimensions differ (%ux%u and %ux%u)",
                       f0.width, f0.height, f1.width, f1.height);
        if (f0.components < 1 || f1.components < 1)
            FOLK_ERROR("opticalFlow: frames have no image components");

        char error[512] = {0};
        CUresult cudaResult;
        NV_OF_STATUS status;
        NV_OF_FLOW_VECTOR *hostFlow = NULL;
        OpticalFlow *answer = NULL;
        CUcontext previousContext = NULL;

        pthread_mutex_lock(&session.mutex);
        if (!session.initialized) {
            snprintf(error, sizeof(error),
                     "opticalFlow: library was not initialized");
            goto cleanup;
        }
        runtime.cuda.contextGetCurrent(&previousContext);
        cudaResult = runtime.cuda.contextSetCurrent(session.context);
        if (cudaResult != CUDA_SUCCESS) {
            ofCUDAError(error, sizeof(error), "cuCtxSetCurrent", cudaResult);
            goto cleanup;
        }
        if (!ofConfigure(f0.width, f0.height, error, sizeof(error)))
            goto cleanup;

        int reusePreviousReference =
            session.referenceValid && f0.uniq &&
            f0.uniq == session.referenceUniq;
        session.referenceValid = 0;
        if (reusePreviousReference) {
            NvOFGPUBufferHandle buffer = session.input;
            session.input = session.reference;
            session.reference = buffer;
            NV_OF_CUDA_BUFFER_STRIDE_INFO stride = session.inputStride;
            session.inputStride = session.referenceStride;
            session.referenceStride = stride;
            CUdeviceptr pointer = session.inputPointer;
            session.inputPointer = session.referencePointer;
            session.referencePointer = pointer;
        } else {
            cudaResult = ofUploadGray(
                f0, session.inputPointer,
                session.inputStride.strideInfo[0].strideXInBytes);
            if (cudaResult != CUDA_SUCCESS) {
                ofCUDAError(error, sizeof(error), "input upload", cudaResult);
                goto cleanup;
            }
        }
        cudaResult = ofUploadGray(
            f1, session.referencePointer,
            session.referenceStride.strideInfo[0].strideXInBytes);
        if (cudaResult != CUDA_SUCCESS) {
            ofCUDAError(error, sizeof(error), "reference upload", cudaResult);
            goto cleanup;
        }

        NV_OF_EXECUTE_INPUT_PARAMS executeInput = {0};
        executeInput.inputFrame = session.input;
        executeInput.referenceFrame = session.reference;
        executeInput.disableTemporalHints = NV_OF_TRUE;
        NV_OF_EXECUTE_OUTPUT_PARAMS executeOutput = {0};
        executeOutput.outputBuffer = session.output;
        status = runtime.of.nvOFExecute(
            session.handle, &executeInput, &executeOutput);
        if (status != NV_OF_SUCCESS) {
            ofSDKError(error, sizeof(error), "nvOFExecute", status,
                       session.handle);
            goto cleanup;
        }
        cudaResult = runtime.cuda.contextSynchronize();
        if (cudaResult != CUDA_SUCCESS) {
            ofCUDAError(error, sizeof(error), "cuCtxSynchronize", cudaResult);
            goto cleanup;
        }
        session.referenceUniq = f1.uniq;
        session.referenceValid = f1.uniq != 0;

        hostFlow = malloc(sizeof(*hostFlow) *
                          (size_t)session.flowWidth * session.flowHeight);
        if (!hostFlow) {
            snprintf(error, sizeof(error), "opticalFlow: out of host memory");
            goto cleanup;
        }
        size_t outputPitch =
            session.outputStride.strideInfo[0].strideXInBytes;
        size_t outputRowBytes =
            (size_t)session.flowWidth * sizeof(*hostFlow);
        CUDA_MEMCPY2D download = {0};
        download.srcMemoryType = CU_MEMORYTYPE_DEVICE;
        download.srcDevice = session.outputPointer;
        download.srcPitch = outputPitch;
        download.dstMemoryType = CU_MEMORYTYPE_HOST;
        download.dstHost = hostFlow;
        download.dstPitch = outputRowBytes;
        download.WidthInBytes = outputRowBytes;
        download.Height = session.flowHeight;
        cudaResult = runtime.cuda.memcpy2D(&download);
        if (cudaResult != CUDA_SUCCESS) {
            ofCUDAError(error, sizeof(error), "flow download", cudaResult);
            goto cleanup;
        }
        answer = malloc(sizeof(*answer));
        if (!answer) {
            snprintf(error, sizeof(error), "opticalFlow: out of host memory");
            goto cleanup;
        }
        *answer = (OpticalFlow) {
            .width = session.flowWidth,
            .height = session.flowHeight,
            .gridSize = session.gridSize,
            .vectors = hostFlow,
        };
        hostFlow = NULL;

    cleanup:
        free(hostFlow);
        if (session.initialized)
            runtime.cuda.contextSetCurrent(previousContext);
        pthread_mutex_unlock(&session.mutex);
        if (!answer) FOLK_ERROR("%s", error);
        return answer;
    }

    $cc proc sampleOpticalFlow {OpticalFlow* flow double pixelX double pixelY} Jim_Obj* {
        FOLK_ENSURE(flow && flow->vectors && flow->width && flow->height &&
                    flow->gridSize);

        int gridX = (int)(pixelX / flow->gridSize);
        int gridY = (int)(pixelY / flow->gridSize);
        if (gridX < 0) gridX = 0;
        if (gridY < 0) gridY = 0;
        if (gridX >= (int)flow->width) gridX = (int)flow->width - 1;
        if (gridY >= (int)flow->height) gridY = (int)flow->height - 1;

        NV_OF_FLOW_VECTOR vector =
            flow->vectors[(size_t)gridY * flow->width + gridX];
        Jim_Obj *xy[2] = {
            Jim_NewDoubleObj(interp, vector.flowx / 32.0),
            Jim_NewDoubleObj(interp, vector.flowy / 32.0),
        };
        return Jim_NewListObj(interp, xy, 2);
    }

    $cc proc opticalFlowTransferInfo {} Jim_Obj* {
        pthread_mutex_lock(&session.mutex);
        Jim_Obj *items[16] = {
            Jim_NewStringObj(interp, "width", -1),
            Jim_NewIntObj(interp, session.width),
            Jim_NewStringObj(interp, "height", -1),
            Jim_NewIntObj(interp, session.height),
            Jim_NewStringObj(interp, "inputStrideX", -1),
            Jim_NewIntObj(interp,
                session.inputStride.strideInfo[0].strideXInBytes),
            Jim_NewStringObj(interp, "inputStrideY", -1),
            Jim_NewIntObj(interp,
                session.inputStride.strideInfo[0].strideYInBytes),
            Jim_NewStringObj(interp, "flowWidth", -1),
            Jim_NewIntObj(interp, session.flowWidth),
            Jim_NewStringObj(interp, "flowHeight", -1),
            Jim_NewIntObj(interp, session.flowHeight),
            Jim_NewStringObj(interp, "outputStrideX", -1),
            Jim_NewIntObj(interp,
                session.outputStride.strideInfo[0].strideXInBytes),
            Jim_NewStringObj(interp, "outputStrideY", -1),
            Jim_NewIntObj(interp,
                session.outputStride.strideInfo[0].strideYInBytes),
        };
        Jim_Obj *answer = Jim_NewListObj(interp, items, 16);
        pthread_mutex_unlock(&session.mutex);
        return answer;
    }

    $cc proc freeOpticalFlow {OpticalFlow* flow} void {
        if (!flow) return;
        free(flow->vectors);
        free(flow);
    }

    set opticalFlowLib [$cc compile]
    $opticalFlowLib initOpticalFlow
    return $opticalFlowLib
}

When the image library is /imageLib/ {
    set opticalFlowLib [opticalFlowInit $imageLib]
    Claim the optical flow library is $opticalFlowLib \
        -destructor [list $opticalFlowLib shutdownOpticalFlow]
}