1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum GpuDataType {
37 U8,
39 U16,
41 U32,
43 I8,
45 I16,
47 I32,
49 F32,
51 F64Emulated,
53}
54
55impl GpuDataType {
56 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 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 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 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 pub fn is_float(&self) -> bool {
104 matches!(self, Self::F32 | Self::F64Emulated)
105 }
106
107 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#[derive(Debug, Clone, Copy, Pod, Zeroable)]
119#[repr(C)]
120pub struct ConversionParams {
121 pub scale: f32,
123 pub offset: f32,
125 pub out_min: f32,
127 pub out_max: f32,
129 pub nodata_in: f32,
131 pub nodata_out: f32,
133 pub use_nodata: u32,
135 _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 pub fn new(scale: f32, offset: f32) -> Self {
157 Self {
158 scale,
159 offset,
160 ..Default::default()
161 }
162 }
163
164 pub fn for_type_conversion(src: GpuDataType, dst: GpuDataType) -> Self {
166 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 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 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 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 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 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
240pub struct DataTypeConversionKernel {
242 context: GpuContext,
243 pipeline: WgpuComputePipeline,
244 bind_group_layout: wgpu::BindGroupLayout,
245 workgroup_size: u32,
246}
247
248impl DataTypeConversionKernel {
249 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), uniform_buffer_layout(1), storage_buffer_layout(2, false), ],
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 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 pub fn execute<T: Pod>(
445 &self,
446 input: &GpuBuffer<T>,
447 output: &mut GpuBuffer<f32>,
448 params: &ConversionParams,
449 ) -> GpuResult<()> {
450 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
511pub 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 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), uniform_buffer_layout(1), storage_buffer_layout(2, false), ],
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 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 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
828pub struct BatchTypeConverter {
833 context: GpuContext,
834 tile_size: usize,
835}
836
837impl BatchTypeConverter {
838 pub fn new(context: &GpuContext) -> Self {
840 Self {
841 context: context.clone(),
842 tile_size: 1024 * 1024, }
844 }
845
846 pub fn with_tile_size(mut self, size: usize) -> Self {
848 self.tile_size = size;
849 self
850 }
851
852 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 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 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 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
917pub 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 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 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 pub fn buffer(&self) -> &GpuBuffer<T> {
973 &self.current_buffer
974 }
975
976 pub fn dimensions(&self) -> (u32, u32) {
978 (self.width, self.height)
979 }
980
981 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 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 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 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 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 pub fn add(self, value: f32) -> GpuResult<Self> {
1079 self.scalar(ScalarOp::Add(value))
1080 }
1081
1082 pub fn multiply(self, value: f32) -> GpuResult<Self> {
1084 self.scalar(ScalarOp::Multiply(value))
1085 }
1086
1087 pub fn clamp(self, min: f32, max: f32) -> GpuResult<Self> {
1089 self.scalar(ScalarOp::Clamp { min, max })
1090 }
1091
1092 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 pub fn abs(self) -> GpuResult<Self> {
1103 self.unary(UnaryOp::Abs)
1104 }
1105
1106 pub fn sqrt(self) -> GpuResult<Self> {
1108 self.unary(UnaryOp::Sqrt)
1109 }
1110
1111 pub fn log(self) -> GpuResult<Self> {
1113 self.unary(UnaryOp::Log)
1114 }
1115
1116 pub fn exp(self) -> GpuResult<Self> {
1118 self.unary(UnaryOp::Exp)
1119 }
1120
1121 pub async fn statistics(&self) -> GpuResult<Statistics> {
1126 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 let data = staging.read().await?;
1134 let f32_data: Vec<f32> = data
1135 .into_iter()
1136 .map(|v: T| {
1137 let bytes = bytemuck::bytes_of(&v);
1139 if bytes.len() == 4 {
1140 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
1142 } else {
1143 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 compute_statistics(&self.context, &input_buffer).await
1157 }
1158
1159 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 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 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 pub fn finish(self) -> GpuBuffer<T> {
1195 self.current_buffer
1196 }
1197
1198 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 pub async fn read_async(self) -> GpuResult<Vec<T>> {
1217 self.read().await
1218 }
1219
1220 pub fn read_blocking(self) -> GpuResult<Vec<T>> {
1222 pollster::block_on(self.read())
1223 }
1224
1225 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 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 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
1283impl ComputePipeline<f32> {
1285 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 let (new_width, new_height) = match dst_type {
1301 GpuDataType::U8 | GpuDataType::I8 => {
1302 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 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 pub fn from_u8_normalized(
1331 context: &GpuContext,
1332 data: &[u8],
1333 width: u32,
1334 height: u32,
1335 ) -> GpuResult<Self> {
1336 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 pub fn from_u16_normalized(
1347 context: &GpuContext,
1348 data: &[u16],
1349 width: u32,
1350 height: u32,
1351 ) -> GpuResult<Self> {
1352 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 pub fn scale_offset(self, scale: f32, offset: f32) -> GpuResult<Self> {
1361 if (scale - 1.0).abs() < 1e-10 && offset.abs() < 1e-10 {
1362 return Ok(self);
1364 }
1365
1366 self.linear_transform(scale, offset)
1367 }
1368}
1369
1370pub struct MultibandPipeline<T: Pod> {
1372 context: GpuContext,
1373 bands: Vec<ComputePipeline<T>>,
1374}
1375
1376impl<T: Pod + Zeroable> MultibandPipeline<T> {
1377 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 pub fn num_bands(&self) -> usize {
1394 self.bands.len()
1395 }
1396
1397 pub fn band(&self, index: usize) -> Option<&ComputePipeline<T>> {
1399 self.bands.get(index)
1400 }
1401
1402 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 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 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 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 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 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 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 pub fn finish(self) -> Vec<GpuBuffer<T>> {
1480 self.bands.into_iter().map(|b| b.finish()).collect()
1481 }
1482
1483 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 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 #[test]
1539 fn test_gpu_data_type_properties() {
1540 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 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 assert!(!GpuDataType::U8.is_signed());
1556 assert!(GpuDataType::I8.is_signed());
1557 assert!(GpuDataType::F32.is_signed());
1558
1559 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 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 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 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, ¶ms);
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 if let Ok(data) = pipeline.read_blocking() {
1683 assert!(data[0].abs() < 1e-6);
1685 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 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); assert!((output[1] - 14.0).abs() < 1e-4); assert!((output[2] - 16.0).abs() < 1e-4); assert!((output[3] - 18.0).abs() < 1e-4); }
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 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 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); assert!((output[1] - 0.5).abs() < 1e-4); assert!((output[2] - 1.0).abs() < 1e-4); assert!((output[3] - 0.25).abs() < 1e-4); }
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 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 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}