🖼️ IMAGE PROCESSING ARCHITECTURE¶
Overview¶
The image processing architecture in CaptureMoment is designed to be hardware-agnostic, high-performance, and non-destructive. It abstracts the underlying hardware (CPU or GPU) behind clean interfaces, allowing the same processing pipeline to run efficiently on different platforms without code changes.
The core innovation revolves around the IWorkingImageHardware interface and the WorkingImageContext lifecycle manager. Data ownership is centralized in WorkingImageData, while hardware workers operate on zero-copy ImageView spans. This separation enables explicit memory control, pipeline pre-compilation, and instant non-destructive resets without re-reading source files.
Key Components¶
1. IWorkingImageHardware (Abstract Interface)¶
This is the central abstraction that enables hardware-agnostic processing. It defines the contract for interacting with an image buffer, regardless of its physical location.
Key Methods:
- virtual std::expected<std::unique_ptr<Common::ImageRegion>, ErrorHandling::CoreError> getFullResImage() const = 0;
- Creates a deep CPU copy of the full-resolution processed image data for display, saving, or committing to source.
- virtual bool bindView(const ImageView& view) = 0;
- Attaches a non-owning view of image data (working span, original span, and dimensions) to the hardware worker. Enables zero-copy processing.
- virtual bool isValid() const = 0;
- Checks if the buffer/view is valid and ready for processing.
- virtual std::expected<std::unique_ptr<Common::ImageRegion>, ErrorHandling::CoreError> downsample(Common::ImageDim target_width, Common::ImageDim target_height) = 0;
- Exports a downscaled version of the image. GPU implementations perform this directly on the device before transferring the small result, minimizing PCIe bandwidth.
2. ImageView (Zero-Copy View)¶
A lightweight, non-owning structure that bridges WorkingImageContext (data owner) and hardware workers.
struct ImageView {
std::span<float> working_data; ///< Mutable span for processing
std::span<const float> original_data; ///< Const span for non-destructive reset
Common::ImageDim width{0};
Common::ImageDim height{0};
Common::ImageChan channels{0};
};
- Purpose: Eliminates data duplication. Hardware backends bind to these spans once during initialization, and all subsequent operations read/write directly to the shared memory.
3. Base Classes¶
WorkingImageData¶
-
Purpose: Owns the CPU RAM buffers and manages image metadata.
-
Responsibilities:
- Maintains
std::vector<float> m_data(working buffer) andstd::vector<float> m_original_data(source cache). - Provides
initializeData(Common::ImageRegion&&)for setup andrestoreOriginalData()(viastd::ranges::copy) for instant non-destructive resets. - Exposes accessors:
getWorkingDataSpan(),getOriginalDataSpan(),getWidth(),getHeight(),getChannels(),isValid()
WorkingImageHalide (Shared Halide Logic)¶
- Purpose:Provides common Halide buffer functionality for both CPU and GPU implementations.
- Responsibilities:
- Holds
Halide::Buffer<float>. initializeHalide(std::span<float>, ...)creates a zero-copy Halide view over external memory.isHalideBufferValid()checks buffer definition state.
4. Concrete Implementations¶
WorkingImageCPU & WorkingImageCPU_Halide¶
- Inherits From:
WorkingImageCPU_Halide→WorkingImageCPU→IWorkingImageHardware+WorkingImageData+WorkingImageHalide - Purpose: Default fallback backend. Data is bound directly to CPU RAM via
bindView().
Key Additions:
- getOriginalHalideBuffer() & isOriginalHalideBufferValid(): Expose the original data buffer for non-destructive pipeline execution.
- CPU scheduling uses optimized split/parallel/vectorize directives.
WorkingImageCPU_Halide & WorkingImageGPU_Halide¶
- Inherits From:
WorkingImageGPU_Halide→WorkingImageGPU→IWorkingImageHardware+WorkingImageData+WorkingImageHalide - Purpose: High-performance GPU backend with explicit VRAM management. Key Additions:
transferToVRAM(): Pre-compiles and caches Halide pipelines (m_reset_pipeline,m_downsample_pipeline) for GPU execution.resetExecutionBuffer(): Restores GPU working buffer from original buffer via cached pipeline.getExecutionBuffer(): Returns reference to GPU buffer for direct pipeline realization.syncToHostRAM(): Forces explicit GPU→CPU synchronization.downloadDeviceToHost(): Protected method for backend-specific transfer logic.downsample: Uses pre-compiled Catmull-Rom cubic resampling pipeline with GPU tiling.
5. Lifecycle & Factory Management¶
WorkingImageFactory (Registry Pattern)¶
- Responsibility: Creates empty hardware abstractions based on
AppConfigbackend settings. - Signature Change: Creator functions are now parameterless:
std::function<std::unique_ptr<IWorkingImageHardware>()>. - Impact: Decouples object instantiation from data management. The factory returns an uninitialized worker; data binding happens later via
bindView().
WorkingImageContext (Context Pattern)¶
- Responsibility: Central coordinator for the image processing lifecycle.
- Workflow in
prepare(): - Creates
WorkingImageData(owns CPU RAM). - Creates hardware worker via
`WorkingImageFactory. - Constructs
ImageViewfrom owned data. - Calls
worker->bindView(view)to attach zero-copy spans. - Non-Destructive Reset:
resetToOriginal()callsWorkingImageData::restoreOriginalData(). Changes are instantly visible to the bound hardware worker via sharedstd::span.
6. Pipeline Execution Architecture¶
OperationPipelineExecutor (Abstract Base)¶
- Role: Defines contract for fused Halide pipeline execution.
- Key Methods:
- init(operations, factory): Builds and compiles the fused graph.
- updateRuntimeParams(operations): Updates
Halide::Param<float>values without recompilation. - executeOnHalideBuffer(input, output): Executes pipeline with separate input and output buffers, enabling non-destructive processing and dimension-changing operations.
Backend-Specific Executors¶
- OperationPipelineExecutorCPU: Applies CPU scheduling (
split(y, yo, yi, 32).parallel(yo).vectorize(x, 8)). Retrieves original/working buffers fromWorkingImageCPU_Halide. - OperationPipelineExecutorGPU: Applies GPU scheduling via
gpu_tile()whentarget.has_gpu_feature()is true. CallsresetExecutionBuffer()before execution. - Selection:
PipelineRegistry::registerHalideExecutors()queriesAppConfig::getProcessingBackend()at startup and registers the appropriate executor with PipelineBuilder.
7. Backend Selection System¶
AppConfig (Singleton)¶
Stores the selected backend globally: CPU_RAM or GPU_MEMORY.
BenchmarkingBackendDecider¶
Performs runtime benchmarking at application startup to determine optimal backend:
- Tests supported backends against CPU baseline.
- Applies a performance margin to avoid GPU overhead for negligible gains.
- Stores result in AppConfig for the entire lifecycle.
8. UI Integration & Performance Considerations¶
The UI layer remains completely unaware of hardware specifics:
- PhotoEngine::getWorkingImageAsRegion(): Calls getFullResImage() on the active hardware worker to export processed data to CPU.
- DisplayManager: Receives ImageRegion objects, handles zoom/pan, and preferentially requests downscaled previews via IWorkingImageHardware::downsample().
- Rendering Items: Work exclusively with CPU ImageRegion data.
Performance Benefits (UI)¶
GPU downsample() executes entirely on the device, transferring only the small result buffer to RAM. This drastically reduces PCIe traffic and enables smooth, real-time zoom/pan interactions even with 50MP+ source files. CPU downsample() uses OIIO::ImageBufAlgo::resample with optimized tiling for quality and speed.
9. Extensibility Guide¶
Adding a New Backend¶
- Create a new class inheriting from
IWorkingImageHardware(and optionallyWorkingImageData/WorkingImageHalide). - Implement
bindView(),getFullResImage(),isValid(), anddownsample()on GPU. - Register a creator lambda in
WorkingImageRegistration::registerDefaultBackends(). - Update
BenchmarkingBackendDeciderto include it in priority order.
Adding a New Operation¶
- Implement
IOperationFusionLogicand defineappendToFusedPipeline(). - Register in
OperationRegistry::registerAll(). - The fused pipeline system automatically integrates it without modifying executor logic.
GPU Backend Requirements¶
| Backend | Platform | Requirements | Runtime |
|---|---|---|---|
| CUDA | NVIDIA GPUs | CUDA Toolkit installed, NVIDIA drivers | No |
| OpenCL | AMD/NVIDIA/Intel | OpenCL runtime (usually included with GPU drivers) | Yes |
| Vulkan | Cross-platform | Vulkan SDK and compatible driver | Yes |
| DirectX12 | Windows only | Windows 10/11, compatible GPU | No |
| Metal | Apple platforms only | macOS 10.11+/iOS 8.0+, compatible GPU | No |
| OpenGL | Cross-platform (Desktop) | OpenGL 4.3+ compatible driver | Yes |