CaptureMoment 0.1.0
Free and open-source non-destructive photo editor
Loading...
Searching...
No Matches
working_image_gpu_halide.cpp
Go to the documentation of this file.
1
10#include "config/app_config.h"
11#include <spdlog/spdlog.h>
12
13#include "HalideRuntime.h"
14
16
18{
19
21 return false;
22 }
23
24 // Initialize the Halide buffer to reference the GPU data (zero-copy)
25 initDataForHalide();
26
27 if (!isHalideBufferValid()) {
28 spdlog::error("[WorkingImageGPU_Halide::bindView]: Failed to init working buffer.");
29 return false;
30 }
31 m_original_halide_buffer = Halide::Buffer<float>::make_interleaved(
32 const_cast<float*>(m_view_data_image.original_data.data()),
36 );
37 m_original_halide_buffer.set_name("original_cpu_buffer");
38
39 m_original_halide_buffer.set_host_dirty();
40 m_halide_buffer.set_host_dirty();
41
42 auto vram_result { transferToVRAM() };
43 if (!vram_result.has_value()) {
44 spdlog::error("[WorkingImageGPU_Halide::bindView]: Failed to transfer to VRAM.");
45 return false;
46 }
47 spdlog::debug("[WorkingImageGPU_Halide::bindView]: Bound and initialized GPU Halide buffer ({}x{}).",
49 return true;
50}
51
52std::expected<void, ErrorHandling::CoreError>
54{
55 if (m_pipelines_initialized) return {};
56
57 try {
58 Halide::Target target{Config::AppConfig::getHalideTarget()};
59 Halide::Var x, y, c;
60
61 // Pipeline Reset
62 Halide::Func reset_func("gpu_reset");
63
64 reset_func(x, y, c) = m_original_halide_buffer(x, y, c);
65 reset_func.output_buffer().dim(0).set_stride(4);
66 reset_func.output_buffer().dim(2).set_stride(1);
67 reset_func.compile_jit(target);
68 m_reset_pipeline = Halide::Pipeline(reset_func);
69
70 if (!m_downsample_built) {
71 buildDownsamplePipeline();
72 }
73
74 m_pipelines_initialized = true;
75 spdlog::info("[WorkingImageGPU_Halide::transferToVRAM]: Pipelines compiled successfully.");
76 return {};
77 } catch (const std::exception& e) {
78 spdlog::critical("[WorkingImageGPU_Halide::transferToVRAM]: {}", e.what());
79 return std::unexpected(ErrorHandling::CoreError::Unexpected);
80 }
81}
82
84{
85 if (!m_pipelines_initialized) return false;
86
87 int ret { m_halide_buffer.copy_to_host() };
88 if (ret != 0) return false;
89
90 m_halide_buffer.device_sync();
91 return true;
92}
93
94void WorkingImageGPU_Halide::initDataForHalide()
95{
100}
101
105
107{
108 if (!m_pipelines_initialized) return;
109
110 Halide::Target target{Config::AppConfig::getHalideTarget()};
111 m_reset_pipeline.realize(m_halide_buffer, target);
112}
114{
115 return m_halide_buffer;
116}
117
118// ==============================================================================
119// DOWNSAMPLE PIPELINE
120// ==============================================================================
121
122static Halide::Expr kernel_cubic(Halide::Expr x){
123 Halide::Expr xx { Halide::abs(x) };
124 Halide::Expr xx2 { xx * xx };
125 Halide::Expr xx3 { xx2 * xx };
126 Halide::Expr a { -0.5f };
127
128 return Halide::select(xx < 1.0f,
129 (a + 2.0f) * xx3 - (a + 3.0f) * xx2 + 1.0f,
130 Halide::select(xx < 2.0f,
131 a * xx3 - 5.0f * a * xx2 + 8.0f * a * xx - 4.0f * a,
132 0.0f));
133}
134
135std::expected<std::unique_ptr<Common::ImageRegion>, ErrorHandling::CoreError>
137{
138 if (!isValid()) {
139 return std::unexpected(ErrorHandling::CoreError::InvalidWorkingImage);
140 }
141
142 if (target_width == 0 || target_height == 0) {
143 return std::unexpected(ErrorHandling::CoreError::InvalidImageRegion);
144 }
145
146 try {
147 if (m_view_data_image.width == target_width && m_view_data_image.height == target_height) {
148 return getFullResImage();
149 }
150
151 if (!m_downsample_built) {
152 buildDownsamplePipeline();
153 }
154
155 spdlog::debug("[WorkingImageGPU_Halide::downsample]: Starting downsample from {}x{} to {}x{}.",
156 m_view_data_image.width, m_view_data_image.height, target_width, target_height);
157
158 m_downsample_input.set(m_halide_buffer);
159
160 std::vector<float> result_data(target_width * target_height * m_view_data_image.channels);
161 Halide::Buffer<float> dst_buffer { Halide::Buffer<float>::make_interleaved(
162 result_data.data(),
163 static_cast<int>(target_width),
164 static_cast<int>(target_height),
165 static_cast<int>(m_view_data_image.channels)
166 )};
167
168 m_downsample_scale_x.set(static_cast<float>(target_width) / m_view_data_image.width);
169 m_downsample_scale_y.set(static_cast<float>(target_height) / m_view_data_image.height);
170
171 Halide::Target target{Config::AppConfig::getHalideTarget()};
172
173 m_downsample_pipeline.realize(dst_buffer, target);
174 dst_buffer.copy_to_host();
175
176 spdlog::debug("[WorkingImageGPU_Halide::downsample]: Successful downsample.");
177
178 return std::make_unique<Common::ImageRegion>(
179 std::move(result_data),
180 static_cast<int>(target_width),
181 static_cast<int>(target_height),
182 static_cast<int>(m_view_data_image.channels)
183 );
184
185 } catch (const std::bad_alloc&) {
186 return std::unexpected(ErrorHandling::CoreError::AllocationFailed);
187 } catch (const std::exception& e) {
188 spdlog::critical("[WorkingImageGPU_Halide::downsample]: {}", e.what());
189 return std::unexpected(ErrorHandling::CoreError::Unexpected);
190 }
191}
192
193void WorkingImageGPU_Halide::buildDownsamplePipeline()
194{
195 if (m_downsample_built) return;
196
197 m_downsample_input.dim(0).set_stride(4);
198 m_downsample_input.dim(2).set_stride(1);
199
200 Halide::Var x, y, c;
201 Halide::Func clamped{Halide::BoundaryConditions::repeat_edge(m_downsample_input)};
202
203 Halide::Expr inv_scale_x{1.0f / m_downsample_scale_x};
204 Halide::Expr inv_scale_y{1.0f / m_downsample_scale_y};
205
206 Halide::Expr sourcex{(Halide::cast<float>(x) + 0.5f) * inv_scale_x - 0.5f};
207 Halide::Expr sourcey{(Halide::cast<float>(y) + 0.5f) * inv_scale_y - 0.5f};
208
209 Halide::Expr fx{Halide::floor(sourcex)};
210 Halide::Expr fy{Halide::floor(sourcey)};
211
212 Halide::Expr ix = Halide::cast<int>(fx);
213 Halide::Expr iy = Halide::cast<int>(fy);
214
215 Halide::Func resized_y{"resized_y"};
216 Halide::Expr wy0 = kernel_cubic(-1 + fy - sourcey);
217 Halide::Expr wy1 = kernel_cubic( 0 + fy - sourcey);
218 Halide::Expr wy2 = kernel_cubic( 1 + fy - sourcey);
219 Halide::Expr wy3 = kernel_cubic( 2 + fy - sourcey);
220
221 resized_y(x, y, c) = wy0 * clamped(x, iy - 1, c) +
222 wy1 * clamped(x, iy , c) +
223 wy2 * clamped(x, iy + 1, c) +
224 wy3 * clamped(x, iy + 2, c);
225
226 Halide::Func resized_x{"resized_x"};
227 Halide::Expr wx0 = kernel_cubic(-1 + fx - sourcex);
228 Halide::Expr wx1 = kernel_cubic( 0 + fx - sourcex);
229 Halide::Expr wx2 = kernel_cubic( 1 + fx - sourcex);
230 Halide::Expr wx3 = kernel_cubic( 2 + fx - sourcex);
231
232 resized_x(x, y, c) = wx0 * resized_y(ix - 1, y, c) +
233 wx1 * resized_y(ix , y, c) +
234 wx2 * resized_y(ix + 1, y, c) +
235 wx3 * resized_y(ix + 2, y, c);
236
237 Halide::Func final_output{"final_output"};
238 final_output(x, y, c) = Halide::clamp(resized_x(x, y, c), 0.0f, 1.0f);
239
240 final_output.output_buffer().dim(0).set_stride(4);
241 final_output.output_buffer().dim(2).set_stride(1);
242
243 Halide::Target target{Config::AppConfig::getHalideTarget()};
244
245 if (target.has_gpu_feature())
246 {
247 Halide::Var xi, yi;
248 final_output.compute_root().gpu_tile(x, y, xi, yi, 16, 16);
249 }
250
251 m_downsample_pipeline = Halide::Pipeline(final_output);
252 final_output.compile_jit(target);
253 m_downsample_built = true;
254}
255
256} // namespace CaptureMoment::Core::ImageProcessing
Application-wide configuration singleton.
static const Halide::Target & getHalideTarget()
Gets the configured Halide Target object.
virtual bool bindView(const ImageView &view)
Binds a view of the image data to this hardware worker.
ImageView m_view_data_image
The image view bound to this hardware worker.
bool isValid() const override
Checks if the GPU view AND the Halide buffer are valid.
std::expected< void, ErrorHandling::CoreError > transferToVRAM() override
Transfers the current image data to GPU VRAM With Halide.
Halide::Buffer< float > & getExecutionBuffer()
Provides access to the Halide buffer used for GPU execution.
void resetExecutionBuffer()
Resets the execution buffer on the GPU to prepare for a new Halide pipeline execution.
bool downloadDeviceToHost() override
A pure method that specific backends (Halide, Vulkan, etc.) must implement.
std::expected< std::unique_ptr< Common::ImageRegion >, ErrorHandling::CoreError > downsample(Common::ImageDim target_width, Common::ImageDim target_height) override
Downsamples the current image to the target dimensions using Halide on GPU.
bool bindView(const ImageView &view) override
Binds the view and initializes the Halide buffer on the device.
bool isValid() const override
Checks if the GPU buffer is valid and allocated.
std::expected< std::unique_ptr< Common::ImageRegion >, ErrorHandling::CoreError > getFullResImage() override
Exports current internal image data by downloading from the GPU device to Host memory.
void initializeHalide(std::span< float > data, Common::ImageDim width, Common::ImageDim height, Common::ImageChan channels)
Creates a Halide buffer view over existing pixel data.
Halide::Buffer< float > m_halide_buffer
Internal Halide buffer holding the image data. Managed directly by this object. Halide::Buffer handle...
bool isHalideBufferValid() const noexcept
Checks if the Halide buffer is defined.
std::size_t ImageDim
Type for image dimensions (width, height).
Definition image_types.h:20
CoreError
Enumeration of all error codes for CaptureMoment Core.
Definition core_error.h:36
@ InvalidImageRegion
The provided ImageRegion dimensions or data are invalid.
@ InvalidWorkingImage
The working image state is invalid or corrupted.
@ AllocationFailed
Memory allocation failed.
@ Unexpected
An unexpected error occurred.
static Halide::Expr kernel_cubic(Halide::Expr x)
Represents a zero-copy window over contiguous image memory.
Definition image_view.h:26
std::span< float > working_data
Mutable span for processing.
Definition image_view.h:27
std::span< const float > original_data
Const span for non-destructive reset.
Definition image_view.h:28
Concrete implementation of image working buffer on GPU using Halide for processing.
CaptureMoment — footer