Note: This README was generated by an AI from the project headers and source.
A C image processing library with heterogeneous compute support. Devices and operations are both registered into runtime dispatch tables at load time, so adding a new backend or a new operation no longer means touching a hand-written switch statement — you add an entry to a .def file and drop in an implementation.
- GPU-accelerated image processing via CUDA and HIP (AMD ROCm)
- OpenMP CPU fallback when no GPU is available
- Device-agnostic dispatch — devices (
CPU,CUDA,HIP, ...) and operations (GRAYSCALE,INVERT,BRIGHTNESS, ...) are both driven by X-macro.deflists, so new ones can be added without touching the scheduler or pipeline code - Self-registering backends — each backend registers its device availability and its per-op implementations via load-time constructors (
IF_CONSTRUCTOR), no manual wiring or init calls required - Pipeline API — build a sequence of operations, run them against an image in one call
- Automatic device management — host↔device transfers are handled generically at device-boundary crossings inside the scheduler via a per-device load/retrieve/free dispatch table; callers don't touch device memory directly
- Pipeline reordering optimizer — reorders operations before execution to minimize host↔device transfers, generalized to any number of devices, with four aggression levels (safe → stencil → reduction → morph)
- C11 core with C++ support for GPU backends
- CMake ≥ 3.20
- A C11 compiler
- OpenMP (optional but recommended)
- CUDA toolkit (optional)
- ROCm / HIP (optional)
At least one of CUDA or HIP is needed for GPU acceleration. If neither is found, the build falls back to OpenMP only. Even when built in, CUDA/HIP devices are only actually used if a GPU is detected at runtime — each backend probes its own availability on startup and enables or disables itself accordingly.
mkdir build && cd build
cmake ..
make -j$(nproc)CMake will automatically detect CUDA and HIP and enable them if found. No flags needed.
#include <ImageFlow/io/image.h>
IF_image_t img;
IF_loadImage(&img, "photo.jpg");
// ... process ...
IF_storeImage(&img, "out.jpg");
IF_freeImage(&img);IF_image_t holds pixel data as a normalized float* buffer (values in [0.0, 1.0]) with width, height, and channel count. Supported formats: PNG, BMP, JPG/JPEG.
Operations are queued into an IF_Flow_t pipeline and executed in one call against an image. Builder functions are generated automatically from operations/operations.def, one per registered operation:
#include <ImageFlow/pipeline.h>
#include <ImageFlow/scheduler/scheduler.h>
IF_image_t img, img_out;
IF_CHECK(IF_loadImage(&img, "img.jpg"));
IF_Flow_t flow;
IF_flow_init(&flow);
IF_flow_Invert(flow);
IF_flow_Grayscale(flow);
IF_flow_Brightness(flow, 0.5f);
IF_CHECK(IF_flow_run(flow, &img, &img_out));
IF_CHECK(IF_storeImage(&img_out, "out.jpg"));
IF_flow_free(flow);
IF_freeImage(&img);
IF_freeImage(&img_out);IF_flow_run hands the pipeline off to a scheduler. The scheduler is responsible for:
- checking, per operation, whether its preferred device is enabled and has a registered implementation, falling back to CPU otherwise
- managing host↔device transfers generically at device-boundary crossings, via the per-device load/retrieve/free dispatch table
- optionally reordering operations to minimize those transfers
- executing each operation via the dispatch layer
A specific scheduler can be selected explicitly:
IF_CHECK(IF_flow_run_sched(flow, &img, IF_SCHEDULER_REORDER_O0, &img_out));| Scheduler | Description |
|---|---|
IF_SCHEDULER_CPU |
CPU-only execution via OpenMP; skips GPU dispatch entirely |
IF_SCHEDULER_LINEAR |
Naive sequential scheduler; runs operations in pipeline order and transfers data at device boundaries |
IF_SCHEDULER_REORDER_O0 |
Reorders pipeline at safe aggression (POINT and METADATA ops only), then runs linearly. Default. |
IF_SCHEDULER_REORDER_O1 |
Reorders allowing STENCIL ops to cross barriers, then runs linearly |
IF_SCHEDULER_REORDER_O2 |
Reorders allowing STENCIL and REDUCTION ops to cross barriers, then runs linearly |
IF_SCHEDULER_REORDER_O3 |
Reorders allowing all op types (including MORPH) to cross barriers, then runs linearly |
The reorder optimizer groups operations by device affinity within each segment, minimizing host↔device transfers without changing observable output. Its sort key is now device-count agnostic: instead of a single GPU/CPU polarity flip, the device ordering itself is reversed on odd-priority tiers, so the device at the boundary between two tiers always matches on both sides — this generalizes cleanly to any number of registered devices. The aggression level controls which operation types are allowed to cross segment barriers:
- O0 (safe): only POINT and METADATA ops are reordered — these are trivially commutative
- O1: additionally allows STENCIL ops to cross barriers
- O2: additionally allows REDUCTION ops to cross barriers
- O3: additionally allows MORPH ops to cross barriers — use only if you understand the commutativity implications
IF_SCHEDULER_LINEAR is the reference implementation and the baseline against which the reorder schedulers are validated. IF_SCHEDULER_REORDER_O0 is the default used by IF_flow_run.
Defined in include/ImageFlow/operations/operations.def as IF_OP_DEF(id, name, traversal_type, args):
| Builder function | Description |
|---|---|
IF_flow_Grayscale(flow) |
Rec. 601 luminance grayscale |
IF_flow_Invert(flow) |
Per-channel inversion (v' = 1 - v) |
IF_flow_Brightness(flow, factor) |
Multiply channels by factor, clamped to 1.0 |
Defined in include/ImageFlow/devices/devices.def. Currently CPU, CUDA, and HIP. Each backend registers its own availability at load time (e.g. the CUDA backend probes cudaGetDeviceCount in a constructor and enables/disables IF_DEV_CUDA accordingly); the CPU backend is always enabled. IF_enabled_gpu() returns the first enabled GPU device, falling back to IF_DEV_CPU if none is available.
Adding a new device or a new operation is table-driven — no scheduler, pipeline, or dispatch code needs to change. See CONTRIBUTING.md for the walkthrough.
All functions return IF_error_t. The IF_CHECK macro propagates errors with file and line information:
IF_CHECK(IF_loadImage(&img, "photo.jpg"));On error it prints to stderr and returns from the calling function. For manual inspection:
const char *msg = IF_strerror(err);
IF_logError(stderr, err);include/ImageFlow/
constructor.h — portable "run before main" constructor macro (GCC/Clang/MSVC)
error.h — error codes, IF_CHECK, allocation macros
io/image.h — IF_image_t and I/O functions
devices/
devices.def — X-macro list of supported devices
devices.h — device enum/names, enable/disable + query, IF_enabled_gpu()
img_loader.h — per-device host<->device load/retrieve/free dispatch tables
operations/
operations.def — X-macro list of supported operations (id, name, traversal type, args)
op_args.h — tagged union of operation-specific arguments
op_constructor.h — IF_OP_IMPL macro: declares + self-registers a (dev, op) implementation
operations.h — op descriptors, dispatch table, IF_op_execute
pipeline.h — IF_Flow_t pipeline builder (flow builder fns generated from operations.def)
scheduler/
scheduler.h — IF_flow_run / IF_flow_run_sched
linear.h — sequential scheduler implementation
reorder.h — pipeline reordering optimizer
cpu.h — CPU-only scheduler
backends/
cpu.h — OpenMP CPU backend
cuda.h — CUDA backend
hip.h — HIP/ROCm backend (low maintenance)
src/
error.c
io/image.c
operations.c
pipeline.c
devices/devices.c
scheduler/
scheduler.c
linear.c
reorder.c
cpu.c
backends/
cpu.c
cuda.cu
hip.cpp
- Multi-GPU / cross-accelerator parallelism (e.g. NCCL-style same-vendor multi-GPU) is not planned. Dispatch is no longer hardcoded to "one GPU" though — the scheduler can route each operation to whichever registered device it prefers, and the goal is to extend this toward single-wafer-scale chips, not to fan work out across multiple same-vendor accelerators in parallel.
- HIP/ROCm support is low-maintenance — the backend exists but is not actively tested due to lack of AMD hardware.
- Intel GPU / oneAPI / SYCL support is not planned.
- MPI / distributed execution is not planned. Adding it would require reworking
IF_OpArgs_tand the dispatch model, since it currently assumes pointer arguments stay local to one process (see the warning inop_args.h).
MIT License. Copyright (c) 2026 Daniele Savino. Do whatever you want with it.