Skip to main content

oxigdal_gpu/
compute.rs

1//! GPU compute pipeline for chaining operations.
2//!
3//! This module provides a high-level pipeline API for chaining GPU operations
4//! efficiently without intermediate CPU transfers.
5
6use crate::buffer::{GpuBuffer, GpuRasterBuffer};
7use crate::context::GpuContext;
8use crate::error::{GpuError, GpuResult};
9use crate::kernels::{
10    convolution::gaussian_blur,
11    raster::{ElementWiseOp, RasterKernel, ScalarKernel, ScalarOp, UnaryKernel, UnaryOp},
12    resampling::{ResamplingMethod, resize},
13    statistics::{
14        HistogramKernel, HistogramParams, ReductionKernel, ReductionOp, Statistics,
15        compute_statistics,
16    },
17};
18use crate::shaders::{
19    ComputePipelineBuilder, WgslShader, create_compute_bind_group_layout, storage_buffer_layout,
20    uniform_buffer_layout,
21};
22use bytemuck::{Pod, Zeroable};
23use std::marker::PhantomData;
24use tracing::debug;
25use wgpu::{
26    BindGroupDescriptor, BindGroupEntry, BufferUsages, CommandEncoderDescriptor,
27    ComputePassDescriptor, ComputePipeline as WgpuComputePipeline,
28};
29
30// =============================================================================
31// Data Type Conversion Module
32// =============================================================================
33
34/// Supported GPU data types for conversion operations.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum GpuDataType {
37    /// 8-bit unsigned integer (0-255).
38    U8,
39    /// 16-bit unsigned integer (0-65535).
40    U16,
41    /// 32-bit unsigned integer.
42    U32,
43    /// 8-bit signed integer (-128 to 127).
44    I8,
45    /// 16-bit signed integer.
46    I16,
47    /// 32-bit signed integer.
48    I32,
49    /// 32-bit floating point.
50    F32,
51    /// 64-bit floating point (emulated on GPU as two f32).
52    F64Emulated,
53}
54
55impl GpuDataType {
56    /// Get the size in bytes of this data type.
57    pub fn size_bytes(&self) -> usize {
58        match self {
59            Self::U8 | Self::I8 => 1,
60            Self::U16 | Self::I16 => 2,
61            Self::U32 | Self::I32 | Self::F32 => 4,
62            Self::F64Emulated => 8,
63        }
64    }
65
66    /// Get the minimum value for this type.
67    pub fn min_value(&self) -> f64 {
68        match self {
69            Self::U8 => 0.0,
70            Self::U16 => 0.0,
71            Self::U32 => 0.0,
72            Self::I8 => -128.0,
73            Self::I16 => -32768.0,
74            Self::I32 => -2147483648.0,
75            Self::F32 => f32::MIN as f64,
76            Self::F64Emulated => f64::MIN,
77        }
78    }
79
80    /// Get the maximum value for this type.
81    pub fn max_value(&self) -> f64 {
82        match self {
83            Self::U8 => 255.0,
84            Self::U16 => 65535.0,
85            Self::U32 => 4294967295.0,
86            Self::I8 => 127.0,
87            Self::I16 => 32767.0,
88            Self::I32 => 2147483647.0,
89            Self::F32 => f32::MAX as f64,
90            Self::F64Emulated => f64::MAX,
91        }
92    }
93
94    /// Check if this is a signed type.
95    pub fn is_signed(&self) -> bool {
96        matches!(
97            self,
98            Self::I8 | Self::I16 | Self::I32 | Self::F32 | Self::F64Emulated
99        )
100    }
101
102    /// Check if this is a floating point type.
103    pub fn is_float(&self) -> bool {
104        matches!(self, Self::F32 | Self::F64Emulated)
105    }
106
107    /// Get the WGSL type name for reading as u32 array.
108    fn wgsl_storage_type(&self) -> &'static str {
109        match self {
110            Self::U8 | Self::I8 | Self::U16 | Self::I16 | Self::U32 | Self::I32 => "u32",
111            Self::F32 => "f32",
112            Self::F64Emulated => "vec2<f32>",
113        }
114    }
115}
116
117/// Parameters for data type conversion with scaling and offset.
118#[derive(Debug, Clone, Copy, Pod, Zeroable)]
119#[repr(C)]
120pub struct ConversionParams {
121    /// Scale factor applied to input values.
122    pub scale: f32,
123    /// Offset added after scaling.
124    pub offset: f32,
125    /// Minimum output value (clamp).
126    pub out_min: f32,
127    /// Maximum output value (clamp).
128    pub out_max: f32,
129    /// NoData input value (if any).
130    pub nodata_in: f32,
131    /// NoData output value.
132    pub nodata_out: f32,
133    /// Whether to use nodata handling.
134    pub use_nodata: u32,
135    /// Padding for alignment.
136    _padding: u32,
137}
138
139impl Default for ConversionParams {
140    fn default() -> Self {
141        Self {
142            scale: 1.0,
143            offset: 0.0,
144            out_min: f32::MIN,
145            out_max: f32::MAX,
146            nodata_in: 0.0,
147            nodata_out: 0.0,
148            use_nodata: 0,
149            _padding: 0,
150        }
151    }
152}
153
154impl ConversionParams {
155    /// Create new conversion parameters with scale and offset.
156    pub fn new(scale: f32, offset: f32) -> Self {
157        Self {
158            scale,
159            offset,
160            ..Default::default()
161        }
162    }
163
164    /// Create parameters for converting between specific data types.
165    pub fn for_type_conversion(src: GpuDataType, dst: GpuDataType) -> Self {
166        // Calculate scale to map source range to destination range
167        let src_range = src.max_value() - src.min_value();
168        let dst_range = dst.max_value() - dst.min_value();
169
170        let scale = if src_range > 0.0 && dst_range > 0.0 {
171            (dst_range / src_range) as f32
172        } else {
173            1.0
174        };
175
176        let offset = if src.min_value() != dst.min_value() {
177            (dst.min_value() - src.min_value() * scale as f64) as f32
178        } else {
179            0.0
180        };
181
182        Self {
183            scale,
184            offset,
185            out_min: dst.min_value() as f32,
186            out_max: dst.max_value() as f32,
187            ..Default::default()
188        }
189    }
190
191    /// Set output clamp range.
192    pub fn with_clamp(mut self, min: f32, max: f32) -> Self {
193        self.out_min = min;
194        self.out_max = max;
195        self
196    }
197
198    /// Set nodata handling.
199    pub fn with_nodata(mut self, input_nodata: f32, output_nodata: f32) -> Self {
200        self.nodata_in = input_nodata;
201        self.nodata_out = output_nodata;
202        self.use_nodata = 1;
203        self
204    }
205
206    /// Create parameters for normalizing u8 to [0, 1] range.
207    pub fn u8_to_normalized() -> Self {
208        Self {
209            scale: 1.0 / 255.0,
210            offset: 0.0,
211            out_min: 0.0,
212            out_max: 1.0,
213            ..Default::default()
214        }
215    }
216
217    /// Create parameters for denormalizing [0, 1] to u8.
218    pub fn normalized_to_u8() -> Self {
219        Self {
220            scale: 255.0,
221            offset: 0.0,
222            out_min: 0.0,
223            out_max: 255.0,
224            ..Default::default()
225        }
226    }
227
228    /// Create parameters for normalizing u16 to [0, 1] range.
229    pub fn u16_to_normalized() -> Self {
230        Self {
231            scale: 1.0 / 65535.0,
232            offset: 0.0,
233            out_min: 0.0,
234            out_max: 1.0,
235            ..Default::default()
236        }
237    }
238}
239
240/// GPU kernel for data type conversion operations.
241pub struct DataTypeConversionKernel {
242    context: GpuContext,
243    pipeline: WgpuComputePipeline,
244    bind_group_layout: wgpu::BindGroupLayout,
245    workgroup_size: u32,
246}
247
248impl DataTypeConversionKernel {
249    /// Create a new data type conversion kernel.
250    ///
251    /// This kernel converts data from any supported type to f32 with optional
252    /// scaling and offset.
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if shader compilation or pipeline creation fails.
257    pub fn new(context: &GpuContext, src_type: GpuDataType) -> GpuResult<Self> {
258        debug!(
259            "Creating data type conversion kernel for {:?} -> f32",
260            src_type
261        );
262
263        let shader_source = Self::conversion_shader(src_type);
264        let mut shader = WgslShader::new(shader_source, "convert_type");
265        let shader_module = shader.compile(context.device())?;
266
267        let bind_group_layout = create_compute_bind_group_layout(
268            context.device(),
269            &[
270                storage_buffer_layout(0, true),  // input
271                uniform_buffer_layout(1),        // params
272                storage_buffer_layout(2, false), // output
273            ],
274            Some("DataTypeConversionKernel BindGroupLayout"),
275        )?;
276
277        let pipeline = ComputePipelineBuilder::new(context.device(), shader_module, "convert_type")
278            .bind_group_layout(&bind_group_layout)
279            .label(format!(
280                "DataTypeConversion Pipeline: {:?} -> f32",
281                src_type
282            ))
283            .build()?;
284
285        Ok(Self {
286            context: context.clone(),
287            pipeline,
288            bind_group_layout,
289            workgroup_size: 256,
290        })
291    }
292
293    /// Generate WGSL shader for type conversion.
294    fn conversion_shader(src_type: GpuDataType) -> String {
295        let (input_type, unpack_code) = match src_type {
296            GpuDataType::U8 => (
297                "u32",
298                r#"
299    // Unpack 4 u8 values from one u32
300    let packed = input[idx / 4u];
301    let byte_idx = idx % 4u;
302    var value: f32;
303    switch (byte_idx) {
304        case 0u: { value = f32(packed & 0xFFu); }
305        case 1u: { value = f32((packed >> 8u) & 0xFFu); }
306        case 2u: { value = f32((packed >> 16u) & 0xFFu); }
307        case 3u: { value = f32((packed >> 24u) & 0xFFu); }
308        default: { value = 0.0; }
309    }"#,
310            ),
311            GpuDataType::I8 => (
312                "u32",
313                r#"
314    // Unpack 4 i8 values from one u32
315    let packed = input[idx / 4u];
316    let byte_idx = idx % 4u;
317    var raw: u32;
318    switch (byte_idx) {
319        case 0u: { raw = packed & 0xFFu; }
320        case 1u: { raw = (packed >> 8u) & 0xFFu; }
321        case 2u: { raw = (packed >> 16u) & 0xFFu; }
322        case 3u: { raw = (packed >> 24u) & 0xFFu; }
323        default: { raw = 0u; }
324    }
325    // Sign extend from 8 bits
326    var value: f32;
327    if (raw >= 128u) {
328        value = f32(i32(raw) - 256);
329    } else {
330        value = f32(raw);
331    }"#,
332            ),
333            GpuDataType::U16 => (
334                "u32",
335                r#"
336    // Unpack 2 u16 values from one u32
337    let packed = input[idx / 2u];
338    let half_idx = idx % 2u;
339    var value: f32;
340    if (half_idx == 0u) {
341        value = f32(packed & 0xFFFFu);
342    } else {
343        value = f32((packed >> 16u) & 0xFFFFu);
344    }"#,
345            ),
346            GpuDataType::I16 => (
347                "u32",
348                r#"
349    // Unpack 2 i16 values from one u32
350    let packed = input[idx / 2u];
351    let half_idx = idx % 2u;
352    var raw: u32;
353    if (half_idx == 0u) {
354        raw = packed & 0xFFFFu;
355    } else {
356        raw = (packed >> 16u) & 0xFFFFu;
357    }
358    // Sign extend from 16 bits
359    var value: f32;
360    if (raw >= 32768u) {
361        value = f32(i32(raw) - 65536);
362    } else {
363        value = f32(raw);
364    }"#,
365            ),
366            GpuDataType::U32 => (
367                "u32",
368                r#"
369    let value = f32(input[idx]);"#,
370            ),
371            GpuDataType::I32 => (
372                "u32",
373                r#"
374    let value = f32(bitcast<i32>(input[idx]));"#,
375            ),
376            GpuDataType::F32 => (
377                "f32",
378                r#"
379    let value = input[idx];"#,
380            ),
381            GpuDataType::F64Emulated => (
382                "vec2<f32>",
383                r#"
384    // Emulate f64 using two f32s (high and low parts)
385    let packed = input[idx];
386    // This is a simplified conversion - full f64 support would need more complex handling
387    let value = packed.x + packed.y;"#,
388            ),
389        };
390
391        format!(
392            r#"
393struct ConversionParams {{
394    scale: f32,
395    offset: f32,
396    out_min: f32,
397    out_max: f32,
398    nodata_in: f32,
399    nodata_out: f32,
400    use_nodata: u32,
401    _padding: u32,
402}}
403
404@group(0) @binding(0) var<storage, read> input: array<{input_type}>;
405@group(0) @binding(1) var<uniform> params: ConversionParams;
406@group(0) @binding(2) var<storage, read_write> output: array<f32>;
407
408@compute @workgroup_size(256)
409fn convert_type(@builtin(global_invocation_id) global_id: vec3<u32>) {{
410    let idx = global_id.x;
411    let output_len = arrayLength(&output);
412
413    if (idx >= output_len) {{
414        return;
415    }}
416
417{unpack_code}
418
419    // Check for nodata
420    if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {{
421        output[idx] = params.nodata_out;
422        return;
423    }}
424
425    // Apply scale and offset
426    var result = value * params.scale + params.offset;
427
428    // Clamp to output range
429    result = clamp(result, params.out_min, params.out_max);
430
431    output[idx] = result;
432}}
433"#,
434            input_type = input_type,
435            unpack_code = unpack_code
436        )
437    }
438
439    /// Execute conversion from source type to f32.
440    ///
441    /// # Errors
442    ///
443    /// Returns an error if buffer sizes don't match or execution fails.
444    pub fn execute<T: Pod>(
445        &self,
446        input: &GpuBuffer<T>,
447        output: &mut GpuBuffer<f32>,
448        params: &ConversionParams,
449    ) -> GpuResult<()> {
450        // Create params uniform buffer
451        let params_buffer = GpuBuffer::from_data(
452            &self.context,
453            &[*params],
454            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
455        )?;
456
457        let bind_group = self
458            .context
459            .device()
460            .create_bind_group(&BindGroupDescriptor {
461                label: Some("DataTypeConversionKernel BindGroup"),
462                layout: &self.bind_group_layout,
463                entries: &[
464                    BindGroupEntry {
465                        binding: 0,
466                        resource: input.buffer().as_entire_binding(),
467                    },
468                    BindGroupEntry {
469                        binding: 1,
470                        resource: params_buffer.buffer().as_entire_binding(),
471                    },
472                    BindGroupEntry {
473                        binding: 2,
474                        resource: output.buffer().as_entire_binding(),
475                    },
476                ],
477            });
478
479        let mut encoder = self
480            .context
481            .device()
482            .create_command_encoder(&CommandEncoderDescriptor {
483                label: Some("DataTypeConversionKernel Encoder"),
484            });
485
486        {
487            let mut compute_pass = encoder.begin_compute_pass(&ComputePassDescriptor {
488                label: Some("DataTypeConversionKernel Pass"),
489                timestamp_writes: None,
490            });
491
492            compute_pass.set_pipeline(&self.pipeline);
493            compute_pass.set_bind_group(0, &bind_group, &[]);
494
495            let num_workgroups =
496                (output.len() as u32 + self.workgroup_size - 1) / self.workgroup_size;
497            compute_pass.dispatch_workgroups(num_workgroups, 1, 1);
498        }
499
500        self.context.check_device_lost()?;
501        self.context.queue().submit(Some(encoder.finish()));
502
503        debug!(
504            "Executed type conversion kernel on {} elements",
505            output.len()
506        );
507        Ok(())
508    }
509}
510
511/// GPU kernel for converting f32 back to other data types.
512pub struct F32ToTypeKernel {
513    context: GpuContext,
514    pipeline: WgpuComputePipeline,
515    bind_group_layout: wgpu::BindGroupLayout,
516    workgroup_size: u32,
517    dst_type: GpuDataType,
518}
519
520impl F32ToTypeKernel {
521    /// Create a new kernel for converting f32 to another type.
522    ///
523    /// # Errors
524    ///
525    /// Returns an error if shader compilation or pipeline creation fails.
526    pub fn new(context: &GpuContext, dst_type: GpuDataType) -> GpuResult<Self> {
527        debug!(
528            "Creating data type conversion kernel for f32 -> {:?}",
529            dst_type
530        );
531
532        let shader_source = Self::conversion_shader(dst_type);
533        let mut shader = WgslShader::new(shader_source, "convert_to_type");
534        let shader_module = shader.compile(context.device())?;
535
536        let bind_group_layout = create_compute_bind_group_layout(
537            context.device(),
538            &[
539                storage_buffer_layout(0, true),  // input (f32)
540                uniform_buffer_layout(1),        // params
541                storage_buffer_layout(2, false), // output
542            ],
543            Some("F32ToTypeKernel BindGroupLayout"),
544        )?;
545
546        let pipeline =
547            ComputePipelineBuilder::new(context.device(), shader_module, "convert_to_type")
548                .bind_group_layout(&bind_group_layout)
549                .label(format!("F32ToType Pipeline: f32 -> {:?}", dst_type))
550                .build()?;
551
552        Ok(Self {
553            context: context.clone(),
554            pipeline,
555            bind_group_layout,
556            workgroup_size: 256,
557            dst_type,
558        })
559    }
560
561    /// Generate WGSL shader for f32 to type conversion.
562    fn conversion_shader(dst_type: GpuDataType) -> String {
563        let (output_type, pack_code) = match dst_type {
564            GpuDataType::U8 => (
565                "u32",
566                r#"
567    // Pack 4 u8 values into one u32
568    let base_idx = idx * 4u;
569    var packed = 0u;
570
571    for (var i = 0u; i < 4u; i = i + 1u) {
572        let src_idx = base_idx + i;
573        if (src_idx < arrayLength(&input)) {
574            var value = input[src_idx];
575
576            // Check nodata
577            if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
578                value = params.nodata_out;
579            }
580
581            // Apply scale and offset, then clamp
582            value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
583            let byte_val = u32(value) & 0xFFu;
584            packed = packed | (byte_val << (i * 8u));
585        }
586    }
587
588    output[idx] = packed;"#,
589            ),
590            GpuDataType::U16 => (
591                "u32",
592                r#"
593    // Pack 2 u16 values into one u32
594    let base_idx = idx * 2u;
595    var packed = 0u;
596
597    for (var i = 0u; i < 2u; i = i + 1u) {
598        let src_idx = base_idx + i;
599        if (src_idx < arrayLength(&input)) {
600            var value = input[src_idx];
601
602            if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
603                value = params.nodata_out;
604            }
605
606            value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
607            let half_val = u32(value) & 0xFFFFu;
608            packed = packed | (half_val << (i * 16u));
609        }
610    }
611
612    output[idx] = packed;"#,
613            ),
614            GpuDataType::U32 => (
615                "u32",
616                r#"
617    var value = input[idx];
618
619    if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
620        value = params.nodata_out;
621    }
622
623    value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
624    output[idx] = u32(value);"#,
625            ),
626            GpuDataType::I8 => (
627                "u32",
628                r#"
629    // Pack 4 i8 values into one u32
630    let base_idx = idx * 4u;
631    var packed = 0u;
632
633    for (var i = 0u; i < 4u; i = i + 1u) {
634        let src_idx = base_idx + i;
635        if (src_idx < arrayLength(&input)) {
636            var value = input[src_idx];
637
638            if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
639                value = params.nodata_out;
640            }
641
642            value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
643            var byte_val: u32;
644            if (value < 0.0) {
645                byte_val = u32(i32(value) + 256) & 0xFFu;
646            } else {
647                byte_val = u32(value) & 0xFFu;
648            }
649            packed = packed | (byte_val << (i * 8u));
650        }
651    }
652
653    output[idx] = packed;"#,
654            ),
655            GpuDataType::I16 => (
656                "u32",
657                r#"
658    // Pack 2 i16 values into one u32
659    let base_idx = idx * 2u;
660    var packed = 0u;
661
662    for (var i = 0u; i < 2u; i = i + 1u) {
663        let src_idx = base_idx + i;
664        if (src_idx < arrayLength(&input)) {
665            var value = input[src_idx];
666
667            if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
668                value = params.nodata_out;
669            }
670
671            value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
672            var half_val: u32;
673            if (value < 0.0) {
674                half_val = u32(i32(value) + 65536) & 0xFFFFu;
675            } else {
676                half_val = u32(value) & 0xFFFFu;
677            }
678            packed = packed | (half_val << (i * 16u));
679        }
680    }
681
682    output[idx] = packed;"#,
683            ),
684            GpuDataType::I32 => (
685                "u32",
686                r#"
687    var value = input[idx];
688
689    if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
690        value = params.nodata_out;
691    }
692
693    value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
694    output[idx] = bitcast<u32>(i32(value));"#,
695            ),
696            GpuDataType::F32 => (
697                "f32",
698                r#"
699    var value = input[idx];
700
701    if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
702        value = params.nodata_out;
703    }
704
705    output[idx] = clamp(value * params.scale + params.offset, params.out_min, params.out_max);"#,
706            ),
707            GpuDataType::F64Emulated => (
708                "vec2<f32>",
709                r#"
710    var value = input[idx];
711
712    if (params.use_nodata != 0u && abs(value - params.nodata_in) < 1e-6) {
713        value = params.nodata_out;
714    }
715
716    value = clamp(value * params.scale + params.offset, params.out_min, params.out_max);
717    // Split into high and low parts for f64 emulation
718    output[idx] = vec2<f32>(value, 0.0);"#,
719            ),
720        };
721
722        format!(
723            r#"
724struct ConversionParams {{
725    scale: f32,
726    offset: f32,
727    out_min: f32,
728    out_max: f32,
729    nodata_in: f32,
730    nodata_out: f32,
731    use_nodata: u32,
732    _padding: u32,
733}}
734
735@group(0) @binding(0) var<storage, read> input: array<f32>;
736@group(0) @binding(1) var<uniform> params: ConversionParams;
737@group(0) @binding(2) var<storage, read_write> output: array<{output_type}>;
738
739@compute @workgroup_size(256)
740fn convert_to_type(@builtin(global_invocation_id) global_id: vec3<u32>) {{
741    let idx = global_id.x;
742    let output_len = arrayLength(&output);
743
744    if (idx >= output_len) {{
745        return;
746    }}
747
748{pack_code}
749}}
750"#,
751            output_type = output_type,
752            pack_code = pack_code
753        )
754    }
755
756    /// Execute conversion from f32 to destination type.
757    ///
758    /// # Errors
759    ///
760    /// Returns an error if execution fails.
761    pub fn execute<T: Pod>(
762        &self,
763        input: &GpuBuffer<f32>,
764        output: &mut GpuBuffer<T>,
765        params: &ConversionParams,
766    ) -> GpuResult<()> {
767        let params_buffer = GpuBuffer::from_data(
768            &self.context,
769            &[*params],
770            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
771        )?;
772
773        let bind_group = self
774            .context
775            .device()
776            .create_bind_group(&BindGroupDescriptor {
777                label: Some("F32ToTypeKernel BindGroup"),
778                layout: &self.bind_group_layout,
779                entries: &[
780                    BindGroupEntry {
781                        binding: 0,
782                        resource: input.buffer().as_entire_binding(),
783                    },
784                    BindGroupEntry {
785                        binding: 1,
786                        resource: params_buffer.buffer().as_entire_binding(),
787                    },
788                    BindGroupEntry {
789                        binding: 2,
790                        resource: output.buffer().as_entire_binding(),
791                    },
792                ],
793            });
794
795        let mut encoder = self
796            .context
797            .device()
798            .create_command_encoder(&CommandEncoderDescriptor {
799                label: Some("F32ToTypeKernel Encoder"),
800            });
801
802        {
803            let mut compute_pass = encoder.begin_compute_pass(&ComputePassDescriptor {
804                label: Some("F32ToTypeKernel Pass"),
805                timestamp_writes: None,
806            });
807
808            compute_pass.set_pipeline(&self.pipeline);
809            compute_pass.set_bind_group(0, &bind_group, &[]);
810
811            let num_workgroups =
812                (output.len() as u32 + self.workgroup_size - 1) / self.workgroup_size;
813            compute_pass.dispatch_workgroups(num_workgroups, 1, 1);
814        }
815
816        self.context.check_device_lost()?;
817        self.context.queue().submit(Some(encoder.finish()));
818
819        debug!(
820            "Executed f32 -> {:?} conversion on {} elements",
821            self.dst_type,
822            input.len()
823        );
824        Ok(())
825    }
826}
827
828/// Batch data type converter for efficient bulk conversions.
829///
830/// This struct caches conversion kernels for repeated use and optimizes
831/// memory bandwidth by processing data in tiles.
832pub struct BatchTypeConverter {
833    context: GpuContext,
834    tile_size: usize,
835}
836
837impl BatchTypeConverter {
838    /// Create a new batch type converter.
839    pub fn new(context: &GpuContext) -> Self {
840        Self {
841            context: context.clone(),
842            tile_size: 1024 * 1024, // 1M elements per tile
843        }
844    }
845
846    /// Set the tile size for batch processing.
847    pub fn with_tile_size(mut self, size: usize) -> Self {
848        self.tile_size = size;
849        self
850    }
851
852    /// Convert a buffer from one type to f32.
853    ///
854    /// This method handles memory-efficient tiled processing for large buffers.
855    ///
856    /// # Errors
857    ///
858    /// Returns an error if conversion fails.
859    pub fn convert_to_f32<T: Pod>(
860        &self,
861        input: &GpuBuffer<T>,
862        src_type: GpuDataType,
863        params: &ConversionParams,
864    ) -> GpuResult<GpuBuffer<f32>> {
865        let kernel = DataTypeConversionKernel::new(&self.context, src_type)?;
866
867        // Calculate output size based on source type packing
868        let output_len = match src_type {
869            GpuDataType::U8 | GpuDataType::I8 => input.len() * 4,
870            GpuDataType::U16 | GpuDataType::I16 => input.len() * 2,
871            _ => input.len(),
872        };
873
874        let mut output = GpuBuffer::new(
875            &self.context,
876            output_len,
877            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
878        )?;
879
880        kernel.execute(input, &mut output, params)?;
881
882        Ok(output)
883    }
884
885    /// Convert an f32 buffer to another type.
886    ///
887    /// # Errors
888    ///
889    /// Returns an error if conversion fails.
890    pub fn convert_from_f32<T: Pod>(
891        &self,
892        input: &GpuBuffer<f32>,
893        dst_type: GpuDataType,
894        params: &ConversionParams,
895    ) -> GpuResult<GpuBuffer<T>> {
896        let kernel = F32ToTypeKernel::new(&self.context, dst_type)?;
897
898        // Calculate output size based on destination type packing
899        let output_len = match dst_type {
900            GpuDataType::U8 | GpuDataType::I8 => (input.len() + 3) / 4,
901            GpuDataType::U16 | GpuDataType::I16 => (input.len() + 1) / 2,
902            _ => input.len(),
903        };
904
905        let mut output = GpuBuffer::new(
906            &self.context,
907            output_len,
908            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
909        )?;
910
911        kernel.execute(input, &mut output, params)?;
912
913        Ok(output)
914    }
915}
916
917// =============================================================================
918// End Data Type Conversion Module
919// =============================================================================
920
921/// GPU compute pipeline for chaining operations.
922///
923/// This struct provides a high-level API for building and executing
924/// GPU compute pipelines that chain multiple operations together
925/// without intermediate CPU transfers.
926pub struct ComputePipeline<T: Pod> {
927    context: GpuContext,
928    current_buffer: GpuBuffer<T>,
929    width: u32,
930    height: u32,
931    _phantom: PhantomData<T>,
932}
933
934impl<T: Pod + Zeroable> ComputePipeline<T> {
935    /// Create a new compute pipeline from a GPU buffer.
936    pub fn new(
937        context: &GpuContext,
938        input: GpuBuffer<T>,
939        width: u32,
940        height: u32,
941    ) -> GpuResult<Self> {
942        let expected_size = (width as usize) * (height as usize);
943        if input.len() != expected_size {
944            return Err(GpuError::invalid_kernel_params(format!(
945                "Buffer size mismatch: expected {}, got {}",
946                expected_size,
947                input.len()
948            )));
949        }
950
951        Ok(Self {
952            context: context.clone(),
953            current_buffer: input,
954            width,
955            height,
956            _phantom: PhantomData,
957        })
958    }
959
960    /// Create a pipeline from data.
961    pub fn from_data(context: &GpuContext, data: &[T], width: u32, height: u32) -> GpuResult<Self> {
962        let buffer = GpuBuffer::from_data(
963            context,
964            data,
965            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
966        )?;
967
968        Self::new(context, buffer, width, height)
969    }
970
971    /// Get the current buffer.
972    pub fn buffer(&self) -> &GpuBuffer<T> {
973        &self.current_buffer
974    }
975
976    /// Get the current dimensions.
977    pub fn dimensions(&self) -> (u32, u32) {
978        (self.width, self.height)
979    }
980
981    /// Apply element-wise operation with another buffer.
982    pub fn element_wise(mut self, op: ElementWiseOp, other: &GpuBuffer<T>) -> GpuResult<Self> {
983        debug!("Pipeline: applying {:?}", op);
984
985        let kernel = RasterKernel::new(&self.context, op)?;
986        let mut output = GpuBuffer::new(
987            &self.context,
988            self.current_buffer.len(),
989            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
990        )?;
991
992        kernel.execute(&self.current_buffer, other, &mut output)?;
993        self.current_buffer = output;
994
995        Ok(self)
996    }
997
998    /// Apply unary operation.
999    pub fn unary(mut self, op: UnaryOp) -> GpuResult<Self> {
1000        debug!("Pipeline: applying unary {:?}", op);
1001
1002        let kernel = UnaryKernel::new(&self.context, op)?;
1003        let mut output = GpuBuffer::new(
1004            &self.context,
1005            self.current_buffer.len(),
1006            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1007        )?;
1008
1009        kernel.execute(&self.current_buffer, &mut output)?;
1010        self.current_buffer = output;
1011
1012        Ok(self)
1013    }
1014
1015    /// Apply scalar operation.
1016    pub fn scalar(mut self, op: ScalarOp) -> GpuResult<Self> {
1017        debug!("Pipeline: applying scalar {:?}", op);
1018
1019        let kernel = ScalarKernel::new(&self.context, op)?;
1020        let mut output = GpuBuffer::new(
1021            &self.context,
1022            self.current_buffer.len(),
1023            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1024        )?;
1025
1026        kernel.execute(&self.current_buffer, &mut output)?;
1027        self.current_buffer = output;
1028
1029        Ok(self)
1030    }
1031
1032    /// Apply Gaussian blur.
1033    pub fn gaussian_blur(mut self, sigma: f32) -> GpuResult<Self> {
1034        debug!("Pipeline: applying Gaussian blur (sigma={})", sigma);
1035
1036        let output = gaussian_blur(
1037            &self.context,
1038            &self.current_buffer,
1039            self.width,
1040            self.height,
1041            sigma,
1042        )?;
1043        self.current_buffer = output;
1044
1045        Ok(self)
1046    }
1047
1048    /// Resize the raster.
1049    pub fn resize(
1050        mut self,
1051        new_width: u32,
1052        new_height: u32,
1053        method: ResamplingMethod,
1054    ) -> GpuResult<Self> {
1055        debug!(
1056            "Pipeline: resizing {}x{} -> {}x{} ({:?})",
1057            self.width, self.height, new_width, new_height, method
1058        );
1059
1060        let output = resize(
1061            &self.context,
1062            &self.current_buffer,
1063            self.width,
1064            self.height,
1065            new_width,
1066            new_height,
1067            method,
1068        )?;
1069
1070        self.width = new_width;
1071        self.height = new_height;
1072        self.current_buffer = output;
1073
1074        Ok(self)
1075    }
1076
1077    /// Add a constant value.
1078    pub fn add(self, value: f32) -> GpuResult<Self> {
1079        self.scalar(ScalarOp::Add(value))
1080    }
1081
1082    /// Multiply by a constant value.
1083    pub fn multiply(self, value: f32) -> GpuResult<Self> {
1084        self.scalar(ScalarOp::Multiply(value))
1085    }
1086
1087    /// Clamp values to a range.
1088    pub fn clamp(self, min: f32, max: f32) -> GpuResult<Self> {
1089        self.scalar(ScalarOp::Clamp { min, max })
1090    }
1091
1092    /// Apply threshold.
1093    pub fn threshold(self, threshold: f32, above: f32, below: f32) -> GpuResult<Self> {
1094        self.scalar(ScalarOp::Threshold {
1095            threshold,
1096            above,
1097            below,
1098        })
1099    }
1100
1101    /// Apply absolute value.
1102    pub fn abs(self) -> GpuResult<Self> {
1103        self.unary(UnaryOp::Abs)
1104    }
1105
1106    /// Apply square root.
1107    pub fn sqrt(self) -> GpuResult<Self> {
1108        self.unary(UnaryOp::Sqrt)
1109    }
1110
1111    /// Apply natural logarithm.
1112    pub fn log(self) -> GpuResult<Self> {
1113        self.unary(UnaryOp::Log)
1114    }
1115
1116    /// Apply exponential.
1117    pub fn exp(self) -> GpuResult<Self> {
1118        self.unary(UnaryOp::Exp)
1119    }
1120
1121    /// Compute statistics on current buffer.
1122    ///
1123    /// This method converts the buffer to f32 internally for statistics computation,
1124    /// supporting all GPU data types.
1125    pub async fn statistics(&self) -> GpuResult<Statistics> {
1126        // Create a temporary buffer to hold the reinterpreted data
1127        // We use the raw buffer data directly since T is Pod
1128        let staging = GpuBuffer::staging(&self.context, self.current_buffer.len())?;
1129        let mut staging_mut = staging.clone();
1130        staging_mut.copy_from(&self.current_buffer)?;
1131
1132        // Read to CPU, convert, and upload back as f32
1133        let data = staging.read().await?;
1134        let f32_data: Vec<f32> = data
1135            .into_iter()
1136            .map(|v: T| {
1137                // Safe conversion through bytemuck for Pod types
1138                let bytes = bytemuck::bytes_of(&v);
1139                if bytes.len() == 4 {
1140                    // Assume f32 layout for 4-byte types
1141                    f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
1142                } else {
1143                    // For non-4-byte types, use a simple cast
1144                    0.0f32
1145                }
1146            })
1147            .collect();
1148
1149        let input_buffer = GpuBuffer::from_data(
1150            &self.context,
1151            &f32_data,
1152            BufferUsages::STORAGE | BufferUsages::COPY_SRC,
1153        )?;
1154
1155        // Now compute statistics on the f32 buffer
1156        compute_statistics(&self.context, &input_buffer).await
1157    }
1158
1159    /// Compute statistics on current buffer with explicit type conversion.
1160    ///
1161    /// Use this method when you know the source data type for optimal conversion.
1162    pub async fn statistics_with_conversion(
1163        &self,
1164        src_type: GpuDataType,
1165        params: &ConversionParams,
1166    ) -> GpuResult<Statistics> {
1167        let converter = BatchTypeConverter::new(&self.context);
1168        let f32_buffer = converter.convert_to_f32(&self.current_buffer, src_type, params)?;
1169        compute_statistics(&self.context, &f32_buffer).await
1170    }
1171
1172    /// Compute histogram on current buffer.
1173    pub async fn histogram(
1174        &self,
1175        num_bins: u32,
1176        min_value: f32,
1177        max_value: f32,
1178    ) -> GpuResult<Vec<u32>> {
1179        let kernel = HistogramKernel::new(&self.context)?;
1180        let params = HistogramParams::new(num_bins, min_value, max_value);
1181        kernel.execute(&self.current_buffer, params).await
1182    }
1183
1184    /// Compute reduction (sum, min, max, etc.).
1185    pub async fn reduce(&self, op: ReductionOp) -> GpuResult<T>
1186    where
1187        T: Copy,
1188    {
1189        let kernel = ReductionKernel::new(&self.context, op)?;
1190        kernel.execute(&self.current_buffer, op).await
1191    }
1192
1193    /// Get the result buffer (consumes the pipeline).
1194    pub fn finish(self) -> GpuBuffer<T> {
1195        self.current_buffer
1196    }
1197
1198    /// Read the result to CPU memory asynchronously.
1199    pub async fn read(self) -> GpuResult<Vec<T>> {
1200        let staging = GpuBuffer::staging(&self.context, self.current_buffer.len())?;
1201        let mut staging_mut = staging.clone();
1202        staging_mut.copy_from(&self.current_buffer)?;
1203        staging.read().await
1204    }
1205
1206    /// Read the result to CPU memory asynchronously — explicit async entry-point.
1207    ///
1208    /// Delegates to [`ComputePipeline::read`] and surfaces the result through
1209    /// the standard `Future` interface. Downstream services that cannot park a
1210    /// thread should `await` this method instead of calling `read_blocking`.
1211    ///
1212    /// # Errors
1213    ///
1214    /// Returns an error if staging-buffer creation, the GPU→CPU copy, or the
1215    /// `MAP_READ` mapping fails.
1216    pub async fn read_async(self) -> GpuResult<Vec<T>> {
1217        self.read().await
1218    }
1219
1220    /// Read the result to CPU memory synchronously.
1221    pub fn read_blocking(self) -> GpuResult<Vec<T>> {
1222        pollster::block_on(self.read())
1223    }
1224
1225    /// Convert the current buffer to f32 with specified conversion parameters.
1226    ///
1227    /// This creates a new pipeline with f32 data type.
1228    ///
1229    /// # Errors
1230    ///
1231    /// Returns an error if conversion fails.
1232    pub fn convert_to_f32(
1233        self,
1234        src_type: GpuDataType,
1235        params: &ConversionParams,
1236    ) -> GpuResult<ComputePipeline<f32>> {
1237        let converter = BatchTypeConverter::new(&self.context);
1238        let f32_buffer = converter.convert_to_f32(&self.current_buffer, src_type, params)?;
1239
1240        Ok(ComputePipeline {
1241            context: self.context,
1242            current_buffer: f32_buffer,
1243            width: self.width,
1244            height: self.height,
1245            _phantom: PhantomData,
1246        })
1247    }
1248
1249    /// Apply linear transformation: output = input * scale + offset.
1250    ///
1251    /// This is a convenience method for common scaling operations.
1252    pub fn linear_transform(self, scale: f32, offset: f32) -> GpuResult<Self> {
1253        self.scalar(ScalarOp::Multiply(scale))?
1254            .scalar(ScalarOp::Add(offset))
1255    }
1256
1257    /// Normalize values to a specific range.
1258    ///
1259    /// Maps the current value range to [new_min, new_max].
1260    pub fn normalize_range(
1261        self,
1262        current_min: f32,
1263        current_max: f32,
1264        new_min: f32,
1265        new_max: f32,
1266    ) -> GpuResult<Self> {
1267        let current_range = current_max - current_min;
1268        let new_range = new_max - new_min;
1269
1270        if current_range.abs() < 1e-10 {
1271            return Err(GpuError::invalid_kernel_params(
1272                "Current range is too small for normalization",
1273            ));
1274        }
1275
1276        let scale = new_range / current_range;
1277        let offset = new_min - current_min * scale;
1278
1279        self.linear_transform(scale, offset)
1280    }
1281}
1282
1283/// Specialized implementation for f32 pipelines with full conversion support.
1284impl ComputePipeline<f32> {
1285    /// Convert f32 buffer to another data type.
1286    ///
1287    /// # Errors
1288    ///
1289    /// Returns an error if conversion fails.
1290    pub fn convert_to_type<U: Pod + Zeroable>(
1291        self,
1292        dst_type: GpuDataType,
1293        params: &ConversionParams,
1294    ) -> GpuResult<ComputePipeline<U>> {
1295        let converter = BatchTypeConverter::new(&self.context);
1296        let output_buffer: GpuBuffer<U> =
1297            converter.convert_from_f32(&self.current_buffer, dst_type, params)?;
1298
1299        // Adjust dimensions based on packing
1300        let (new_width, new_height) = match dst_type {
1301            GpuDataType::U8 | GpuDataType::I8 => {
1302                // u8 data is packed 4 per u32
1303                let total_elements = (self.width * self.height) as usize;
1304                let packed_len = (total_elements + 3) / 4;
1305                (packed_len as u32, 1)
1306            }
1307            GpuDataType::U16 | GpuDataType::I16 => {
1308                // u16 data is packed 2 per u32
1309                let total_elements = (self.width * self.height) as usize;
1310                let packed_len = (total_elements + 1) / 2;
1311                (packed_len as u32, 1)
1312            }
1313            _ => (self.width, self.height),
1314        };
1315
1316        Ok(ComputePipeline {
1317            context: self.context,
1318            current_buffer: output_buffer,
1319            width: new_width,
1320            height: new_height,
1321            _phantom: PhantomData,
1322        })
1323    }
1324
1325    /// Create a pipeline from u8 data with automatic normalization to [0, 1].
1326    ///
1327    /// # Errors
1328    ///
1329    /// Returns an error if buffer creation fails.
1330    pub fn from_u8_normalized(
1331        context: &GpuContext,
1332        data: &[u8],
1333        width: u32,
1334        height: u32,
1335    ) -> GpuResult<Self> {
1336        // Convert u8 data to f32 normalized
1337        let f32_data: Vec<f32> = data.iter().map(|&v| v as f32 / 255.0).collect();
1338        Self::from_data(context, &f32_data, width, height)
1339    }
1340
1341    /// Create a pipeline from u16 data with automatic normalization to [0, 1].
1342    ///
1343    /// # Errors
1344    ///
1345    /// Returns an error if buffer creation fails.
1346    pub fn from_u16_normalized(
1347        context: &GpuContext,
1348        data: &[u16],
1349        width: u32,
1350        height: u32,
1351    ) -> GpuResult<Self> {
1352        // Convert u16 data to f32 normalized
1353        let f32_data: Vec<f32> = data.iter().map(|&v| v as f32 / 65535.0).collect();
1354        Self::from_data(context, &f32_data, width, height)
1355    }
1356
1357    /// Apply scale and offset transformation optimized for type conversion.
1358    ///
1359    /// This uses GPU compute for efficient transformation.
1360    pub fn scale_offset(self, scale: f32, offset: f32) -> GpuResult<Self> {
1361        if (scale - 1.0).abs() < 1e-10 && offset.abs() < 1e-10 {
1362            // No-op if scale=1 and offset=0
1363            return Ok(self);
1364        }
1365
1366        self.linear_transform(scale, offset)
1367    }
1368}
1369
1370/// Multi-band raster compute pipeline.
1371pub struct MultibandPipeline<T: Pod> {
1372    context: GpuContext,
1373    bands: Vec<ComputePipeline<T>>,
1374}
1375
1376impl<T: Pod + Zeroable> MultibandPipeline<T> {
1377    /// Create a new multiband pipeline.
1378    pub fn new(context: &GpuContext, raster: &GpuRasterBuffer<T>) -> GpuResult<Self> {
1379        let (width, height) = raster.dimensions();
1380        let bands = raster
1381            .bands()
1382            .iter()
1383            .map(|band| ComputePipeline::new(context, band.clone(), width, height))
1384            .collect::<GpuResult<Vec<_>>>()?;
1385
1386        Ok(Self {
1387            context: context.clone(),
1388            bands,
1389        })
1390    }
1391
1392    /// Get the number of bands.
1393    pub fn num_bands(&self) -> usize {
1394        self.bands.len()
1395    }
1396
1397    /// Get a specific band pipeline.
1398    pub fn band(&self, index: usize) -> Option<&ComputePipeline<T>> {
1399        self.bands.get(index)
1400    }
1401
1402    /// Apply operation to all bands.
1403    pub fn map<F>(mut self, mut f: F) -> GpuResult<Self>
1404    where
1405        F: FnMut(ComputePipeline<T>) -> GpuResult<ComputePipeline<T>>,
1406    {
1407        self.bands = self
1408            .bands
1409            .into_iter()
1410            .map(|band| f(band))
1411            .collect::<GpuResult<Vec<_>>>()?;
1412
1413        Ok(self)
1414    }
1415
1416    /// Compute NDVI (Normalized Difference Vegetation Index).
1417    ///
1418    /// NDVI = (NIR - Red) / (NIR + Red)
1419    ///
1420    /// # Errors
1421    ///
1422    /// Returns an error if the raster doesn't have at least 4 bands (R,G,B,NIR).
1423    pub fn ndvi(self) -> GpuResult<ComputePipeline<T>> {
1424        if self.bands.len() < 4 {
1425            return Err(GpuError::invalid_kernel_params(
1426                "NDVI requires at least 4 bands (R,G,B,NIR)",
1427            ));
1428        }
1429
1430        // Assume band order: R(0), G(1), B(2), NIR(3)
1431        let nir = self
1432            .bands
1433            .get(3)
1434            .ok_or_else(|| GpuError::internal("Missing NIR band"))?;
1435        let red = self
1436            .bands
1437            .get(0)
1438            .ok_or_else(|| GpuError::internal("Missing Red band"))?;
1439
1440        // NDVI = (NIR - Red) / (NIR + Red)
1441        // This is a simplified version; full implementation would use custom kernel
1442        let nir_buffer = nir.buffer().clone();
1443        let red_buffer = red.buffer().clone();
1444
1445        let width = nir.width;
1446        let height = nir.height;
1447
1448        // Compute NIR - Red
1449        let diff_kernel = RasterKernel::new(&self.context, ElementWiseOp::Subtract)?;
1450        let mut diff_buffer = GpuBuffer::new(
1451            &self.context,
1452            nir_buffer.len(),
1453            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1454        )?;
1455        diff_kernel.execute(&nir_buffer, &red_buffer, &mut diff_buffer)?;
1456
1457        // Compute NIR + Red
1458        let sum_kernel = RasterKernel::new(&self.context, ElementWiseOp::Add)?;
1459        let mut sum_buffer = GpuBuffer::new(
1460            &self.context,
1461            nir_buffer.len(),
1462            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1463        )?;
1464        sum_kernel.execute(&nir_buffer, &red_buffer, &mut sum_buffer)?;
1465
1466        // Compute (NIR - Red) / (NIR + Red)
1467        let div_kernel = RasterKernel::new(&self.context, ElementWiseOp::Divide)?;
1468        let mut ndvi_buffer = GpuBuffer::new(
1469            &self.context,
1470            nir_buffer.len(),
1471            BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1472        )?;
1473        div_kernel.execute(&diff_buffer, &sum_buffer, &mut ndvi_buffer)?;
1474
1475        ComputePipeline::new(&self.context, ndvi_buffer, width, height)
1476    }
1477
1478    /// Finish and get all band buffers.
1479    pub fn finish(self) -> Vec<GpuBuffer<T>> {
1480        self.bands.into_iter().map(|b| b.finish()).collect()
1481    }
1482
1483    /// Read all bands to CPU memory.
1484    pub async fn read_all(self) -> GpuResult<Vec<Vec<T>>> {
1485        let mut results = Vec::with_capacity(self.bands.len());
1486
1487        for band in self.bands {
1488            results.push(band.read().await?);
1489        }
1490
1491        Ok(results)
1492    }
1493}
1494
1495#[cfg(test)]
1496mod tests {
1497    use super::*;
1498
1499    #[tokio::test]
1500    async fn test_compute_pipeline() {
1501        if let Ok(context) = GpuContext::new().await {
1502            let data: Vec<f32> = (0..100).map(|i| i as f32).collect();
1503
1504            if let Ok(pipeline) = ComputePipeline::from_data(&context, &data, 10, 10) {
1505                if let Ok(result) = pipeline.add(5.0).and_then(|p| p.multiply(2.0)) {
1506                    // Pipeline executed successfully
1507                    let _ = result.finish();
1508                }
1509            }
1510        }
1511    }
1512
1513    #[tokio::test]
1514    #[ignore]
1515    async fn test_pipeline_chaining() {
1516        if let Ok(context) = GpuContext::new().await {
1517            let data: Vec<f32> = vec![1.0; 64 * 64];
1518
1519            if let Ok(pipeline) = ComputePipeline::from_data(&context, &data, 64, 64) {
1520                if let Ok(result) = pipeline
1521                    .add(10.0)
1522                    .and_then(|p| p.multiply(2.0))
1523                    .and_then(|p| p.clamp(0.0, 100.0))
1524                {
1525                    let stats = result.statistics().await;
1526                    if let Ok(stats) = stats {
1527                        println!("Mean: {}", stats.mean());
1528                    }
1529                }
1530            }
1531        }
1532    }
1533
1534    // ==========================================================================
1535    // Data Type Conversion Tests
1536    // ==========================================================================
1537
1538    #[test]
1539    fn test_gpu_data_type_properties() {
1540        // Test size_bytes
1541        assert_eq!(GpuDataType::U8.size_bytes(), 1);
1542        assert_eq!(GpuDataType::U16.size_bytes(), 2);
1543        assert_eq!(GpuDataType::U32.size_bytes(), 4);
1544        assert_eq!(GpuDataType::F32.size_bytes(), 4);
1545        assert_eq!(GpuDataType::F64Emulated.size_bytes(), 8);
1546
1547        // Test min/max values
1548        assert_eq!(GpuDataType::U8.min_value(), 0.0);
1549        assert_eq!(GpuDataType::U8.max_value(), 255.0);
1550        assert_eq!(GpuDataType::I8.min_value(), -128.0);
1551        assert_eq!(GpuDataType::I8.max_value(), 127.0);
1552        assert_eq!(GpuDataType::U16.max_value(), 65535.0);
1553
1554        // Test is_signed
1555        assert!(!GpuDataType::U8.is_signed());
1556        assert!(GpuDataType::I8.is_signed());
1557        assert!(GpuDataType::F32.is_signed());
1558
1559        // Test is_float
1560        assert!(!GpuDataType::U8.is_float());
1561        assert!(GpuDataType::F32.is_float());
1562        assert!(GpuDataType::F64Emulated.is_float());
1563    }
1564
1565    #[test]
1566    fn test_conversion_params_default() {
1567        let params = ConversionParams::default();
1568        assert_eq!(params.scale, 1.0);
1569        assert_eq!(params.offset, 0.0);
1570        assert_eq!(params.use_nodata, 0);
1571    }
1572
1573    #[test]
1574    fn test_conversion_params_u8_to_normalized() {
1575        let params = ConversionParams::u8_to_normalized();
1576        assert!((params.scale - (1.0 / 255.0)).abs() < 1e-6);
1577        assert_eq!(params.offset, 0.0);
1578        assert_eq!(params.out_min, 0.0);
1579        assert_eq!(params.out_max, 1.0);
1580    }
1581
1582    #[test]
1583    fn test_conversion_params_normalized_to_u8() {
1584        let params = ConversionParams::normalized_to_u8();
1585        assert_eq!(params.scale, 255.0);
1586        assert_eq!(params.offset, 0.0);
1587        assert_eq!(params.out_min, 0.0);
1588        assert_eq!(params.out_max, 255.0);
1589    }
1590
1591    #[test]
1592    fn test_conversion_params_with_clamp() {
1593        let params = ConversionParams::new(2.0, 10.0).with_clamp(0.0, 100.0);
1594        assert_eq!(params.scale, 2.0);
1595        assert_eq!(params.offset, 10.0);
1596        assert_eq!(params.out_min, 0.0);
1597        assert_eq!(params.out_max, 100.0);
1598    }
1599
1600    #[test]
1601    fn test_conversion_params_with_nodata() {
1602        let params = ConversionParams::default().with_nodata(-9999.0, f32::NAN);
1603        assert_eq!(params.nodata_in, -9999.0);
1604        assert_eq!(params.use_nodata, 1);
1605    }
1606
1607    #[test]
1608    fn test_conversion_params_for_type_conversion() {
1609        // u8 to u16 should have scale ~257 (65535/255)
1610        let params = ConversionParams::for_type_conversion(GpuDataType::U8, GpuDataType::U16);
1611        let expected_scale = 65535.0 / 255.0;
1612        assert!((params.scale - expected_scale as f32).abs() < 0.01);
1613    }
1614
1615    #[tokio::test]
1616    async fn test_data_type_conversion_kernel_creation() {
1617        if let Ok(context) = GpuContext::new().await {
1618            // Test kernel creation for various types
1619            for dtype in &[
1620                GpuDataType::U8,
1621                GpuDataType::U16,
1622                GpuDataType::U32,
1623                GpuDataType::I8,
1624                GpuDataType::I16,
1625                GpuDataType::I32,
1626                GpuDataType::F32,
1627            ] {
1628                let result = DataTypeConversionKernel::new(&context, *dtype);
1629                assert!(result.is_ok(), "Failed to create kernel for {:?}", dtype);
1630            }
1631        }
1632    }
1633
1634    #[tokio::test]
1635    async fn test_f32_to_type_kernel_creation() {
1636        if let Ok(context) = GpuContext::new().await {
1637            for dtype in &[
1638                GpuDataType::U8,
1639                GpuDataType::U16,
1640                GpuDataType::U32,
1641                GpuDataType::F32,
1642            ] {
1643                let result = F32ToTypeKernel::new(&context, *dtype);
1644                assert!(
1645                    result.is_ok(),
1646                    "Failed to create F32ToType kernel for {:?}",
1647                    dtype
1648                );
1649            }
1650        }
1651    }
1652
1653    #[tokio::test]
1654    async fn test_batch_type_converter() {
1655        if let Ok(context) = GpuContext::new().await {
1656            let converter = BatchTypeConverter::new(&context);
1657
1658            // Test f32 identity conversion
1659            let f32_data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
1660            if let Ok(buffer) = GpuBuffer::from_data(
1661                &context,
1662                &f32_data,
1663                BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1664            ) {
1665                let params = ConversionParams::default();
1666                let result = converter.convert_to_f32(&buffer, GpuDataType::F32, &params);
1667                assert!(result.is_ok());
1668            }
1669        }
1670    }
1671
1672    #[tokio::test]
1673    #[ignore]
1674    async fn test_pipeline_with_u8_normalized() {
1675        if let Ok(context) = GpuContext::new().await {
1676            let u8_data: Vec<u8> = (0..100).collect();
1677
1678            if let Ok(pipeline) =
1679                ComputePipeline::<f32>::from_u8_normalized(&context, &u8_data, 10, 10)
1680            {
1681                // Verify normalization worked
1682                if let Ok(data) = pipeline.read_blocking() {
1683                    // First value should be 0/255 = 0
1684                    assert!(data[0].abs() < 1e-6);
1685                    // Value 255 would be 1.0, value 99 should be 99/255
1686                    let expected = 99.0 / 255.0;
1687                    assert!((data[99] - expected).abs() < 1e-4);
1688                }
1689            }
1690        }
1691    }
1692
1693    #[tokio::test]
1694    #[ignore]
1695    async fn test_pipeline_linear_transform() {
1696        if let Ok(context) = GpuContext::new().await {
1697            let data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
1698
1699            if let Ok(pipeline) = ComputePipeline::from_data(&context, &data, 2, 2) {
1700                // Apply y = 2x + 10
1701                if let Ok(result) = pipeline.linear_transform(2.0, 10.0) {
1702                    if let Ok(output) = result.read_blocking() {
1703                        assert!((output[0] - 12.0).abs() < 1e-4); // 2*1 + 10
1704                        assert!((output[1] - 14.0).abs() < 1e-4); // 2*2 + 10
1705                        assert!((output[2] - 16.0).abs() < 1e-4); // 2*3 + 10
1706                        assert!((output[3] - 18.0).abs() < 1e-4); // 2*4 + 10
1707                    }
1708                }
1709            }
1710        }
1711    }
1712
1713    #[tokio::test]
1714    #[ignore]
1715    async fn test_pipeline_normalize_range() {
1716        if let Ok(context) = GpuContext::new().await {
1717            // Data in range [0, 100]
1718            let data: Vec<f32> = vec![0.0, 50.0, 100.0, 25.0];
1719
1720            if let Ok(pipeline) = ComputePipeline::from_data(&context, &data, 2, 2) {
1721                // Normalize to [0, 1]
1722                if let Ok(result) = pipeline.normalize_range(0.0, 100.0, 0.0, 1.0) {
1723                    if let Ok(output) = result.read_blocking() {
1724                        assert!(output[0].abs() < 1e-4); // 0 -> 0
1725                        assert!((output[1] - 0.5).abs() < 1e-4); // 50 -> 0.5
1726                        assert!((output[2] - 1.0).abs() < 1e-4); // 100 -> 1.0
1727                        assert!((output[3] - 0.25).abs() < 1e-4); // 25 -> 0.25
1728                    }
1729                }
1730            }
1731        }
1732    }
1733
1734    #[tokio::test]
1735    #[ignore]
1736    async fn test_pipeline_scale_offset_noop() {
1737        if let Ok(context) = GpuContext::new().await {
1738            let data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
1739
1740            if let Ok(pipeline) = ComputePipeline::from_data(&context, &data, 2, 2) {
1741                // Identity transform should be a no-op
1742                if let Ok(result) = pipeline.scale_offset(1.0, 0.0) {
1743                    if let Ok(output) = result.read_blocking() {
1744                        for (i, &v) in output.iter().enumerate() {
1745                            assert!((v - data[i]).abs() < 1e-6);
1746                        }
1747                    }
1748                }
1749            }
1750        }
1751    }
1752
1753    #[test]
1754    fn test_gpu_data_type_wgsl_storage_type() {
1755        // Internal method test
1756        assert_eq!(GpuDataType::U8.wgsl_storage_type(), "u32");
1757        assert_eq!(GpuDataType::F32.wgsl_storage_type(), "f32");
1758        assert_eq!(GpuDataType::F64Emulated.wgsl_storage_type(), "vec2<f32>");
1759    }
1760}