1use crate::context::{GpuContext, Kernel, PipelineKey};
4use crate::image::{GpuImage, GpuPixelFormat, GpuTile};
5use crate::kernels::{self, Params};
6use flashraw_core::{Error, FrameInfo, Orientation, PackOrder, PackedPayload, Rect, Result};
7use std::sync::Arc;
8
9#[derive(Debug, Clone, Copy)]
11pub enum GpuInput<'a> {
12 Packed {
14 data: &'a [u8],
15 bits: u32,
16 order: PackOrder,
17 row_stride: usize,
19 },
20 U16(&'a [u16]),
22 F32(&'a [f32]),
24}
25
26#[derive(Debug, Clone, Copy)]
28pub struct GpuFrame<'a> {
29 pub input: GpuInput<'a>,
30 pub raster_width: u32,
32 pub raster_height: u32,
33 pub cpp: u32,
34 pub window: Rect,
36 pub origin: (u32, u32),
38 pub linearization: Option<&'a [u16]>,
40}
41
42impl<'a> GpuFrame<'a> {
43 pub fn from_packed(p: &PackedPayload<'a>) -> Self {
44 GpuFrame {
45 input: GpuInput::Packed { data: p.data, bits: p.bits, order: p.order, row_stride: p.row_stride },
46 raster_width: p.width,
47 raster_height: p.height,
48 cpp: 1,
49 window: Rect::full(p.width, p.height),
50 origin: (0, 0),
51 linearization: None,
52 }
53 }
54 pub fn from_u16(data: &'a [u16], width: u32, height: u32, cpp: u32) -> Self {
55 Self::plain(GpuInput::U16(data), width, height, cpp)
56 }
57 pub fn from_f32(data: &'a [f32], width: u32, height: u32, cpp: u32) -> Self {
58 Self::plain(GpuInput::F32(data), width, height, cpp)
59 }
60 fn plain(input: GpuInput<'a>, width: u32, height: u32, cpp: u32) -> Self {
61 GpuFrame {
62 input,
63 raster_width: width,
64 raster_height: height,
65 cpp,
66 window: Rect::full(width, height),
67 origin: (0, 0),
68 linearization: None,
69 }
70 }
71 pub fn with_window(mut self, r: Rect) -> Self {
73 self.origin = (self.origin.0 + r.x - self.window.x, self.origin.1 + r.y - self.window.y);
74 self.window = r;
75 self
76 }
77 pub fn with_origin(mut self, origin: (u32, u32)) -> Self {
78 self.origin = origin;
79 self
80 }
81 pub fn with_linearization(mut self, lut: Option<&'a [u16]>) -> Self {
82 self.linearization = lut.filter(|l| !l.is_empty());
83 self
84 }
85 pub fn upload_bytes(&self) -> usize {
87 match self.input {
88 GpuInput::Packed { data, .. } => data.len(),
89 GpuInput::U16(d) => d.len() * 2,
90 GpuInput::F32(d) => d.len() * 4,
91 }
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum DevelopOutput {
98 Rgba8,
100 LinearRgbF32,
102 Rgb16,
105 LinearRgb16,
108 LinearRgbF16,
111}
112
113#[derive(Debug, Clone, Copy)]
115pub struct DevelopSpec {
116 pub crop: Rect,
118 pub half_size: bool,
120 pub wb: [f32; 4],
122 pub cam_to_rgb: [[f32; 3]; 3],
123 pub orientation: Orientation,
124 pub output: DevelopOutput,
125 pub levels: Option<[f32; 2]>,
128 pub curve: Option<GpuCurve>,
130}
131
132#[derive(Debug, Clone, Copy)]
134pub struct GpuCurve {
135 pub lut8: &'static [u8],
137 pub lut16: &'static [f32],
139}
140
141#[derive(Debug, Clone, Copy)]
143pub enum GpuOp {
144 Unpack,
146 Normalize,
148 Develop(DevelopSpec),
149}
150
151#[derive(Debug, Clone, Copy, Default, PartialEq)]
153pub struct GpuTimings {
154 pub upload_ms: f64,
157 pub compute_ms: f64,
159 pub kernel_ms: Option<f64>,
161 pub bands: u32,
163}
164
165#[derive(Debug)]
167pub struct GpuRun {
168 pub image: GpuImage,
169 pub timings: GpuTimings,
170}
171
172pub(crate) fn buffer_init(device: &wgpu::Device, label: &str, data: &[u8], usage: wgpu::BufferUsages) -> wgpu::Buffer {
173 let size = (data.len() as u64).div_ceil(4).max(1) * 4;
174 let buf =
175 device.create_buffer(&wgpu::BufferDescriptor { label: Some(label), size, usage, mapped_at_creation: true });
176 {
177 let mut view = buf.get_mapped_range_mut(..).expect("buffer mapped at creation");
178 let mut padded = data.to_vec();
179 padded.resize(size as usize, 0);
180 view.copy_from_slice(&padded);
181 }
182 buf.unmap();
183 buf
184}
185
186fn write_padded(queue: &wgpu::Queue, buf: &wgpu::Buffer, data: &[u8]) {
189 let bulk = data.len() & !3;
190 if bulk > 0 {
191 queue.write_buffer(buf, 0, &data[..bulk]);
192 }
193 if bulk < data.len() {
194 let mut tail = [0u8; 4];
195 tail[..data.len() - bulk].copy_from_slice(&data[bulk..]);
196 queue.write_buffer(buf, bulk as u64, &tail);
197 }
198}
199
200fn grid_1d(n: u64) -> (u32, u32) {
202 let groups = n.div_ceil(256).max(1);
203 let x = groups.min(32768);
204 (x as u32, groups.div_ceil(x) as u32)
205}
206
207fn u32_checked(v: u64, what: &str) -> Result<u32> {
208 u32::try_from(v).map_err(|_| Error::LimitExceeded(format!("{what} too large for GPU addressing")))
209}
210
211fn round4(v: u64) -> u64 {
212 v.div_ceil(4).max(1) * 4
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217enum OutLayout {
218 Single,
220 SubRange,
222 Tiled,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub(crate) struct Band {
230 pub y0: u32,
231 pub y1: u32,
232 pub in0: u32,
233 pub in1: u32,
234}
235
236#[derive(Debug, Clone, Copy)]
238pub(crate) struct BandGeometry {
239 pub rows: u32,
241 pub in_row_bytes: u64,
243 pub out_row_bytes: u64,
245 pub kind: BandKind,
247 pub frame_h: u32,
249 pub row_align: u32,
251 pub tile_rows: u32,
253 pub max_rows: u32,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub(crate) enum BandKind {
258 Direct,
260 Bilinear { crop_y: u32 },
262 Half { crop_y: u32 },
264}
265
266impl BandGeometry {
267 fn input_rows(&self, y0: u32, y1: u32) -> (u32, u32) {
268 let h = self.frame_h;
269 match self.kind {
270 BandKind::Direct => (y0, y1.min(h)),
271 BandKind::Bilinear { crop_y } => ((crop_y + y0).saturating_sub(1), (crop_y + y1 + 1).min(h)),
272 BandKind::Half { crop_y } => ((crop_y + 2 * y0).min(h), (crop_y + 2 * y1).min(h)),
273 }
274 }
275
276 pub fn plan(&self, budget: u64, in_slack: u64, out_whole: bool, out_slack: u64) -> Result<Vec<Band>> {
279 let rows_in = budget.saturating_sub(in_slack) / self.in_row_bytes.max(1);
280 let n_in = match self.kind {
281 BandKind::Direct => rows_in,
282 BandKind::Bilinear { .. } => rows_in.saturating_sub(2),
283 BandKind::Half { .. } => rows_in / 2,
284 };
285 let n_out = if out_whole { u64::MAX } else { budget.saturating_sub(out_slack) / self.out_row_bytes.max(1) };
286 let mut n = n_in.min(n_out).min(self.max_rows as u64).min(self.rows as u64) as u32;
287 if n < self.rows {
288 if n >= self.tile_rows {
289 n -= n % self.tile_rows;
290 }
291 n -= n % self.row_align;
292 }
293 if n == 0 {
294 return Err(Error::LimitExceeded(format!(
295 "one band of {} rows exceeds the GPU buffer limit ({:.1} MB)",
296 self.row_align,
297 budget as f64 / 1e6
298 )));
299 }
300 let mut bands = Vec::with_capacity(self.rows.div_ceil(n) as usize);
301 let mut y0 = 0;
302 while y0 < self.rows {
303 let y1 = (y0 + n).min(self.rows);
304 let (in0, in1) = self.input_rows(y0, y1);
305 bands.push(Band { y0, y1, in0, in1 });
306 y0 = y1;
307 }
308 Ok(bands)
309 }
310}
311
312pub(crate) fn band_rect(y0: u32, y1: u32, dw: u32, dh: u32, orientation: u32) -> Rect {
315 let n = y1 - y0;
316 match orientation {
317 3 | 4 => Rect::new(0, dh - y1, dw, n),
318 5 | 8 => Rect::new(y0, 0, n, dw),
319 6 | 7 => Rect::new(dh - y1, 0, n, dw),
320 _ => Rect::new(0, y0, dw, n),
321 }
322}
323
324struct InputSlice<'a> {
326 bytes: &'a [u8],
327 offset: u32,
328 len: u32,
329}
330
331struct Slot {
333 src: wgpu::Buffer,
334 staging: Option<wgpu::Buffer>,
335 last: Option<wgpu::SubmissionIndex>,
336}
337
338impl GpuContext {
339 pub async fn run(self: &Arc<Self>, frame: &GpuFrame<'_>, info: &FrameInfo, op: &GpuOp) -> Result<GpuRun> {
347 self.run_banded(frame, info, op, None).await
348 }
349
350 pub(crate) async fn run_banded(
353 self: &Arc<Self>,
354 frame: &GpuFrame<'_>,
355 info: &FrameInfo,
356 op: &GpuOp,
357 budget: Option<u64>,
358 ) -> Result<GpuRun> {
359 let (w, h, cpp) = (frame.window.width, frame.window.height, frame.cpp.max(1));
360 let win = frame.window;
361 if w == 0 || h == 0 || win.right() > frame.raster_width || win.bottom() > frame.raster_height {
362 return Err(Error::InvalidArgument(format!(
363 "window {win} outside {}x{} raster",
364 frame.raster_width, frame.raster_height
365 )));
366 }
367 let mut curve: Option<GpuCurve> = None;
368 let mut p = Params {
369 srgb_n: 4096,
370 width: w,
371 height: h,
372 cpp,
373 src_x: win.x,
374 origin_x: frame.origin.0,
375 origin_y: frame.origin.1,
376 orientation: 1,
377 wb: [1.0; 4],
378 ..Default::default()
379 };
380 let raster_samples = frame.raster_width as u64 * frame.raster_height as u64 * cpp as u64;
382 let raster_row = frame.raster_width as u64 * cpp as u64;
383 let (input_mode, order, in_row_bytes): (u32, u32, u64) = match frame.input {
384 GpuInput::Packed { bits, order, row_stride, .. } => {
385 if !(1..=16).contains(&bits) {
386 return Err(Error::InvalidArgument(format!("unsupported packed bit depth {bits}")));
387 }
388 let row_bytes = (raster_row * bits as u64).div_ceil(8);
389 p.bits = bits;
390 p.src_stride = u32_checked(row_stride as u64, "row stride")?;
391 p.row_bytes = u32_checked(row_bytes, "row size")?;
392 (2, kernels::order_index(order), (row_stride as u64).max(row_bytes))
393 }
394 GpuInput::U16(d) => {
395 if (d.len() as u64) < raster_samples {
396 return Err(Error::InvalidArgument("u16 raster shorter than its dimensions".into()));
397 }
398 p.src_stride = u32_checked(raster_row, "row")?;
399 (0, 0, raster_row * 2)
400 }
401 GpuInput::F32(d) => {
402 if (d.len() as u64) < raster_samples {
403 return Err(Error::InvalidArgument("f32 raster shorter than its dimensions".into()));
404 }
405 p.src_stride = u32_checked(raster_row, "row")?;
406 (1, 0, raster_row * 4)
407 }
408 };
409 let use_lut = frame.linearization.is_some() && input_mode != 1;
410 let mut calib = kernels::calib_table(info, cpp as usize);
411 p.pat_w = calib.pat_w;
412 p.pat_h = calib.pat_h;
413 p.off_scale = calib.off_scale;
414 p.off_rowd = calib.off_rowd;
415 p.off_cold = calib.off_cold;
416 p.n_rowd = calib.n_rowd;
417 p.n_cold = calib.n_cold;
418 let (cfa_w, cfa_h, sites) = kernels::cfa_sites(info);
419 p.cfa_w = cfa_w;
420 p.cfa_h = cfa_h;
421
422 let row_samples = w as u64 * cpp as u64;
424 let mut geo = BandGeometry {
425 rows: h,
426 in_row_bytes,
427 out_row_bytes: 0,
428 kind: BandKind::Direct,
429 frame_h: h,
430 row_align: 1,
431 tile_rows: 1,
432 max_rows: u32::MAX,
433 };
434 let (key, out_w, out_h, channels, format) = match op {
435 GpuOp::Unpack => {
436 if input_mode == 1 {
437 return Err(Error::InvalidArgument("unpack needs integer input".into()));
438 }
439 geo.out_row_bytes = row_samples * 2;
440 geo.row_align = if row_samples % 2 == 1 { 2 } else { 1 };
442 let key = PipelineKey { kernel: Kernel::Unpack, input_mode, order, use_lut, layout: 0, out_mode: 0 };
443 (key, w, h, cpp, GpuPixelFormat::U16)
444 }
445 GpuOp::Normalize => {
446 geo.out_row_bytes = row_samples * 4;
447 let key = PipelineKey { kernel: Kernel::Normalize, input_mode, order, use_lut, layout: 0, out_mode: 0 };
448 (key, w, h, cpp, GpuPixelFormat::F32)
449 }
450 GpuOp::Develop(d) => {
451 let crop = d.crop.clamp_to(w, h);
452 let (dw, dh) = if d.half_size { (crop.width / 2, crop.height / 2) } else { (crop.width, crop.height) };
453 if dw == 0 || dh == 0 {
454 return Err(Error::InvalidArgument("develop region too small".into()));
455 }
456 p.crop_x = crop.x;
457 p.crop_y = crop.y;
458 p.dev_w = dw;
459 p.dev_h = dh;
460 p.wb = d.wb;
461 curve = d.curve;
462 let m = d.cam_to_rgb;
463 p.m0 = [m[0][0], m[0][1], m[0][2], 0.0];
464 p.m1 = [m[1][0], m[1][1], m[1][2], 0.0];
465 p.m2 = [m[2][0], m[2][1], m[2][2], 0.0];
466 if let Some([scale, offset]) = d.levels {
467 p.levels = [scale, offset, 1.0, 0.0];
468 }
469 let layout = if cpp == 1 && info.cfa.is_some_and(|c| c.is_bayer()) {
470 3
471 } else if cpp == 1 && info.cfa.is_some() {
472 1
473 } else if cpp >= 3 {
474 2
475 } else {
476 0
477 };
478 let (out_mode, ow, oh, ch, fmt) = match d.output {
479 DevelopOutput::Rgba8 => {
480 p.orientation = d.orientation.exif();
481 let (ow, oh) = if d.orientation.swaps_dimensions() { (dh, dw) } else { (dw, dh) };
482 (0, ow, oh, 4, GpuPixelFormat::Rgba8)
483 }
484 DevelopOutput::LinearRgbF32 => (1, dw, dh, 3, GpuPixelFormat::F32),
485 DevelopOutput::Rgb16 => {
486 p.orientation = d.orientation.exif();
487 let (ow, oh) = if d.orientation.swaps_dimensions() { (dh, dw) } else { (dw, dh) };
488 (2, ow, oh, 4, GpuPixelFormat::U16)
489 }
490 DevelopOutput::LinearRgb16 => (3, dw, dh, 4, GpuPixelFormat::U16),
491 DevelopOutput::LinearRgbF16 => (4, dw, dh, 4, GpuPixelFormat::U16),
492 };
493 p.out_w = ow;
494 p.out_h = oh;
495 geo.rows = dh;
496 geo.out_row_bytes = dw as u64 * ch as u64 * fmt.bytes_per_sample() as u64;
497 geo.kind =
498 if d.half_size { BandKind::Half { crop_y: crop.y } } else { BandKind::Bilinear { crop_y: crop.y } };
499 geo.tile_rows = 16;
500 geo.max_rows = 16 * self.device.limits().max_compute_workgroups_per_dimension;
501 let key = PipelineKey {
502 kernel: if d.half_size { Kernel::DevelopHalf } else { Kernel::Develop },
503 input_mode,
504 order,
505 use_lut,
506 layout,
507 out_mode,
508 };
509 (key, ow, oh, ch, fmt)
510 }
511 };
512 if key.out_mode == 2 {
513 p.off_srgb16 = calib.tab.len() as u32;
514 match curve {
515 Some(c) if c.lut16.len() == kernels::srgb16_table().len() => calib.tab.extend_from_slice(c.lut16),
516 _ => calib.tab.extend_from_slice(kernels::srgb16_table()),
517 }
518 }
519 let bps = format.bytes_per_sample() as u64;
520 let out_bytes = out_w as u64 * out_h as u64 * channels as u64 * bps;
521 let columns = p.orientation >= 5;
523
524 let limits = self.device.limits();
526 let max_buffer = self.max_buffer_bytes();
527 let binding = budget.unwrap_or_else(|| self.max_binding_bytes()).min(self.max_binding_bytes()) & !3;
528 let align = limits.min_storage_buffer_offset_alignment.max(4) as u64;
529 let layout = if round4(out_bytes) <= binding {
530 OutLayout::Single
531 } else if out_bytes <= max_buffer && !columns {
532 OutLayout::SubRange
533 } else {
534 OutLayout::Tiled
535 };
536 let bands = geo.plan(binding, 8, layout == OutLayout::Single, align + 4)?;
537
538 let mut lut: Vec<u32> = Vec::new();
540 if let Some(l) = frame.linearization.filter(|_| use_lut) {
541 lut.extend(l.iter().map(|&v| v as u32));
542 p.lut_len = l.len() as u32;
543 }
544 p.off_srgb = lut.len() as u32;
545 match curve {
546 Some(c) if key.out_mode == 0 && c.lut8.len() > 1 => {
547 p.srgb_n = (c.lut8.len() - 1) as u32;
548 lut.extend(c.lut8.iter().map(|&v| v as u32));
549 }
550 _ => lut.extend_from_slice(&self.srgb),
551 }
552 p.off_cfa = lut.len() as u32;
553 lut.extend_from_slice(&sites);
554
555 let slice_of = |b: &Band| -> InputSlice<'_> {
557 let (r0, r1) = ((win.y + b.in0) as u64, (win.y + b.in1) as u64);
558 match frame.input {
559 GpuInput::Packed { data, row_stride, .. } => {
560 let len = data.len() as u64;
561 let start = (r0 * row_stride as u64).min(len);
562 let end = if r1 > r0 { ((r1 - 1) * row_stride as u64 + in_row_bytes).min(len) } else { start };
563 let al = start & !3;
564 InputSlice {
565 bytes: &data[al as usize..end as usize],
566 offset: (start - al) as u32,
567 len: (end - start) as u32,
568 }
569 }
570 GpuInput::U16(d) => {
571 let s = &d[(r0 * raster_row) as usize..(r1 * raster_row) as usize];
572 InputSlice { bytes: bytemuck::cast_slice(s), offset: 0, len: 0 }
573 }
574 GpuInput::F32(d) => {
575 let s = &d[(r0 * raster_row) as usize..(r1 * raster_row) as usize];
576 InputSlice { bytes: bytemuck::cast_slice(s), offset: 0, len: 0 }
577 }
578 }
579 };
580 let slices: Vec<InputSlice<'_>> = bands.iter().map(slice_of).collect();
581 let in_max = slices.iter().map(|s| round4(s.bytes.len() as u64)).max().unwrap_or(4);
582 let develop = matches!(op, GpuOp::Develop(_));
583 let mut params = Vec::with_capacity(bands.len());
586 let mut out_ranges = Vec::with_capacity(bands.len());
587 for (b, s) in bands.iter().zip(&slices) {
588 let mut q = p;
589 q.src_y = b.in0;
590 q.src_offset = s.offset;
591 q.src_len = s.len;
592 q.band_y = b.y0;
593 q.band_end = b.y1;
594 q.total = u32_checked((b.y1 - b.y0) as u64 * row_samples, "band")?;
595 let rect = if develop {
596 band_rect(b.y0, b.y1, p.dev_w, p.dev_h, p.orientation)
597 } else {
598 Rect::new(0, b.y0, w, b.y1 - b.y0)
599 };
600 let row_out = out_w as u64 * channels as u64 * bps;
601 let (offset, size) = match layout {
602 OutLayout::Single => {
603 q.dst_stride = out_w;
604 q.dst_off = if develop { 0 } else { u32_checked(rect.y as u64 * row_out / 4, "output")? };
605 (0, round4(out_bytes))
606 }
607 OutLayout::SubRange => {
608 let start = rect.y as u64 * row_out;
609 let a = start / align * align;
610 let end = round4(rect.bottom() as u64 * row_out);
611 q.dst_y = rect.y;
612 q.dst_stride = out_w;
613 q.dst_off = ((start - a) / 4) as u32;
614 (a, end - a)
615 }
616 OutLayout::Tiled => {
617 q.dst_x = rect.x;
618 q.dst_y = rect.y;
619 q.dst_stride = rect.width;
620 (0, round4(rect.width as u64 * rect.height as u64 * channels as u64 * bps))
621 }
622 };
623 params.push(q);
624 out_ranges.push((rect, offset, size));
625 }
626
627 let dev = &self.device;
628 let validation = dev.push_error_scope(wgpu::ErrorFilter::Validation);
629 let oom = dev.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
630
631 let ustride = (std::mem::size_of::<Params>() as u64)
633 .div_ceil(limits.min_uniform_buffer_offset_alignment as u64)
634 * limits.min_uniform_buffer_offset_alignment as u64;
635 let mut ubytes = vec![0u8; ustride as usize * params.len()];
636 for (i, q) in params.iter().enumerate() {
637 let o = i * ustride as usize;
638 ubytes[o..o + std::mem::size_of::<Params>()].copy_from_slice(bytemuck::bytes_of(q));
639 }
640 let ubuf = buffer_init(dev, "flashraw.params", &ubytes, wgpu::BufferUsages::UNIFORM);
641 let tab = buffer_init(dev, "flashraw.calib", bytemuck::cast_slice(&calib.tab), wgpu::BufferUsages::STORAGE);
642 let lutb = buffer_init(dev, "flashraw.lut", bytemuck::cast_slice(&lut), wgpu::BufferUsages::STORAGE);
643 let mut dst_usage = wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC;
644 if self.uma {
645 dst_usage |= wgpu::BufferUsages::MAP_READ;
646 }
647 let outputs: Vec<wgpu::Buffer> = match layout {
648 OutLayout::Single | OutLayout::SubRange => {
649 vec![self.acquire("flashraw.output", round4(out_bytes), dst_usage)]
650 }
651 OutLayout::Tiled => {
652 out_ranges.iter().map(|&(_, _, size)| self.acquire("flashraw.output", size, dst_usage)).collect()
653 }
654 };
655 let staged = !self.uma && in_max >= 1 << 20;
658 let nslots = bands.len().min(2);
659 let mut slots: Vec<Slot> = (0..nslots)
660 .map(|_| {
661 let src_usage = if self.uma {
662 wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::MAP_WRITE
663 } else {
664 wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
665 };
666 Slot {
667 src: self.acquire("flashraw.src", in_max, src_usage),
668 staging: staged.then(|| {
669 self.acquire(
670 "flashraw.upload",
671 in_max,
672 wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
673 )
674 }),
675 last: None,
676 }
677 })
678 .collect();
679 let ts = (self.info.timestamps && bands.len() * 2 <= wgpu::QUERY_SET_MAX_QUERIES as usize).then(|| {
680 let n = bands.len() as u32 * 2;
681 let qs = dev.create_query_set(&wgpu::QuerySetDescriptor {
682 label: Some("flashraw.timestamps"),
683 ty: wgpu::QueryType::Timestamp,
684 count: n,
685 });
686 let resolve = dev.create_buffer(&wgpu::BufferDescriptor {
687 label: Some("flashraw.ts_resolve"),
688 size: n as u64 * 8,
689 usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
690 mapped_at_creation: false,
691 });
692 let read = dev.create_buffer(&wgpu::BufferDescriptor {
693 label: Some("flashraw.ts_read"),
694 size: n as u64 * 8,
695 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
696 mapped_at_creation: false,
697 });
698 (qs, resolve, read)
699 });
700 let pipeline = self.pipeline(key);
701
702 let t0 = flashraw_core::profile::now_ms();
704 let mut upload_ms = 0.0;
705 for (i, (b, s)) in bands.iter().zip(&slices).enumerate() {
706 let slot = &mut slots[i % nslots];
707 let in_size = round4(s.bytes.len() as u64);
708 upload_ms += if self.uma {
711 self.write_mapped(&slot.src, s.bytes, slot.last.take()).await?
712 } else if let Some(st) = &slot.staging {
713 self.write_mapped(st, s.bytes, slot.last.take()).await?
714 } else {
715 let tu = flashraw_core::profile::now_ms();
716 write_padded(&self.queue, &slot.src, s.bytes);
717 flashraw_core::profile::now_ms() - tu
718 };
719 let (_, out_offset, out_size) = out_ranges[i];
720 let out_buf = if layout == OutLayout::Tiled { &outputs[i] } else { &outputs[0] };
721 let bg = dev.create_bind_group(&wgpu::BindGroupDescriptor {
722 label: Some("flashraw.kernel"),
723 layout: &self.layout,
724 entries: &[
725 wgpu::BindGroupEntry {
726 binding: 0,
727 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
728 buffer: &ubuf,
729 offset: i as u64 * ustride,
730 size: wgpu::BufferSize::new(std::mem::size_of::<Params>() as u64),
731 }),
732 },
733 wgpu::BindGroupEntry {
734 binding: 1,
735 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
736 buffer: &slot.src,
737 offset: 0,
738 size: wgpu::BufferSize::new(in_size),
739 }),
740 },
741 wgpu::BindGroupEntry { binding: 2, resource: tab.as_entire_binding() },
742 wgpu::BindGroupEntry { binding: 3, resource: lutb.as_entire_binding() },
743 wgpu::BindGroupEntry {
744 binding: 4,
745 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
746 buffer: out_buf,
747 offset: out_offset,
748 size: wgpu::BufferSize::new(out_size),
749 }),
750 },
751 ],
752 });
753 let groups = match op {
754 GpuOp::Unpack => grid_1d((params[i].total as u64).div_ceil(2)),
755 GpuOp::Normalize => grid_1d(params[i].total as u64),
756 GpuOp::Develop(_) => (p.dev_w.div_ceil(16), (b.y1 - b.y0).div_ceil(16)),
757 };
758 let mut enc = dev.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("flashraw.band") });
759 if let Some(st) = &slot.staging {
760 enc.copy_buffer_to_buffer(st, 0, &slot.src, 0, in_size);
761 }
762 {
763 let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
764 label: Some("flashraw.kernel"),
765 timestamp_writes: ts.as_ref().map(|(qs, _, _)| wgpu::ComputePassTimestampWrites {
766 query_set: qs,
767 beginning_of_pass_write_index: Some(2 * i as u32),
768 end_of_pass_write_index: Some(2 * i as u32 + 1),
769 }),
770 });
771 pass.set_pipeline(&pipeline);
772 pass.set_bind_group(0, &bg, &[]);
773 pass.dispatch_workgroups(groups.0, groups.1, 1);
774 }
775 if let Some((qs, resolve, read)) = ts.as_ref().filter(|_| i + 1 == bands.len()) {
776 let n = bands.len() as u32 * 2;
777 enc.resolve_query_set(qs, 0..n, resolve, 0);
778 enc.copy_buffer_to_buffer(resolve, 0, read, 0, n as u64 * 8);
779 }
780 slot.last = Some(self.queue.submit([enc.finish()]));
781 }
782 self.wait_idle().await?;
783 let compute_ms = (flashraw_core::profile::now_ms() - t0 - upload_ms).max(0.0);
784 let kernel_ms = match &ts {
785 Some((_, _, read)) => {
786 let n = bands.len() * 2;
787 let ticks = self
788 .read_mapped(read, n as u64 * 8, |b| {
789 b.chunks_exact(16)
790 .map(|c| {
791 let t0 = bytemuck::pod_read_unaligned::<u64>(&c[0..8]);
792 let t1 = bytemuck::pod_read_unaligned::<u64>(&c[8..16]);
793 t1.saturating_sub(t0)
794 })
795 .sum::<u64>()
796 })
797 .await?;
798 let ns = ticks as f64 * self.queue.get_timestamp_period() as f64;
799 (ns > 0.0).then_some(ns / 1e6)
801 }
802 None => None,
803 };
804 for s in slots {
805 self.release(s.src);
806 if let Some(st) = s.staging {
807 self.release(st);
808 }
809 }
810 let e2 = oom.pop().await;
811 let e1 = validation.pop().await;
812 if let Some(e) = e2.or(e1) {
813 return Err(Error::GpuUnavailable(format!("GPU error: {e}")));
814 }
815 if let Some(e) = self.take_error() {
816 return Err(Error::GpuUnavailable(format!("GPU error: {e}")));
817 }
818 let tiled = layout == OutLayout::Tiled;
819 let parts: Vec<GpuTile> = if tiled {
820 outputs.into_iter().zip(&out_ranges).map(|(buffer, &(rect, _, _))| GpuTile { rect, buffer }).collect()
821 } else {
822 outputs.into_iter().map(|buffer| GpuTile { rect: Rect::full(out_w, out_h), buffer }).collect()
823 };
824 Ok(GpuRun {
825 image: GpuImage { ctx: self.clone(), parts, tiled, width: out_w, height: out_h, channels, format },
826 timings: GpuTimings { upload_ms, compute_ms, kernel_ms, bands: bands.len() as u32 },
827 })
828 }
829
830 #[cfg(not(target_arch = "wasm32"))]
832 pub fn run_blocking(self: &Arc<Self>, frame: &GpuFrame<'_>, info: &FrameInfo, op: &GpuOp) -> Result<GpuRun> {
833 pollster::block_on(self.run(frame, info, op))
834 }
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 fn geo(rows: u32, kind: BandKind, frame_h: u32) -> BandGeometry {
842 BandGeometry {
843 rows,
844 in_row_bytes: 100,
845 out_row_bytes: 400,
846 kind,
847 frame_h,
848 row_align: 1,
849 tile_rows: 16,
850 max_rows: u32::MAX,
851 }
852 }
853
854 fn check_cover(bands: &[Band], rows: u32) {
855 assert_eq!(bands.first().unwrap().y0, 0);
856 assert_eq!(bands.last().unwrap().y1, rows);
857 assert!(bands.windows(2).all(|w| w[0].y1 == w[1].y0));
858 }
859
860 #[test]
861 fn plan_untiled_when_it_fits() {
862 let g = geo(100, BandKind::Bilinear { crop_y: 0 }, 100);
863 let b = g.plan(1 << 20, 8, true, 0).unwrap();
864 assert_eq!(b, vec![Band { y0: 0, y1: 100, in0: 0, in1: 100 }]);
865 }
866
867 #[test]
868 fn plan_bilinear_halo_and_tile_rounding() {
869 let g = geo(100, BandKind::Bilinear { crop_y: 3 }, 110);
871 let b = g.plan(4008, 8, true, 0).unwrap();
872 check_cover(&b, 100);
873 assert_eq!(b[0], Band { y0: 0, y1: 32, in0: 2, in1: 36 });
874 assert_eq!(b[1], Band { y0: 32, y1: 64, in0: 34, in1: 68 });
875 assert_eq!(b.last().unwrap().in1, 104);
876 let b = g.plan(4000 + 8, 8, false, 8).unwrap();
878 assert!(b.iter().all(|b| b.y1 - b.y0 <= 10));
879 check_cover(&b, 100);
880 }
881
882 #[test]
883 fn plan_half_and_alignment() {
884 let g = geo(50, BandKind::Half { crop_y: 1 }, 101);
885 let b = g.plan(2008, 8, true, 0).unwrap();
886 check_cover(&b, 50);
887 assert_eq!(b[0], Band { y0: 0, y1: 10, in0: 1, in1: 21 });
888 let mut g = geo(9, BandKind::Direct, 9);
889 g.row_align = 2;
890 g.tile_rows = 1;
891 let b = g.plan(308, 8, true, 0).unwrap();
892 assert!(b.iter().all(|b| b.y0 % 2 == 0));
893 check_cover(&b, 9);
894 assert!(g.plan(108, 8, true, 0).is_err());
895 }
896
897 #[test]
898 fn band_rects_cover_every_orientation() {
899 let (dw, dh) = (7u32, 10u32);
900 for o in 1..=8u32 {
901 let (ow, oh) = if o >= 5 { (dh, dw) } else { (dw, dh) };
902 let mut seen = vec![0u8; (ow * oh) as usize];
903 for (y0, y1) in [(0, 3), (3, 4), (4, 10)] {
904 let r = band_rect(y0, y1, dw, dh, o);
905 assert!(r.right() <= ow && r.bottom() <= oh, "o {o} {r}");
906 for dy in y0..y1 {
908 for dx in 0..dw {
909 let (ox, oy) = match o {
910 2 => (dw - 1 - dx, dy),
911 3 => (dw - 1 - dx, dh - 1 - dy),
912 4 => (dx, dh - 1 - dy),
913 5 => (dy, dx),
914 6 => (dh - 1 - dy, dx),
915 7 => (dh - 1 - dy, dw - 1 - dx),
916 8 => (dy, dw - 1 - dx),
917 _ => (dx, dy),
918 };
919 assert!(r.contains_rect(&Rect::new(ox, oy, 1, 1)), "o {o} ({ox},{oy}) outside {r}");
920 seen[(oy * ow + ox) as usize] += 1;
921 }
922 }
923 }
924 assert!(seen.iter().all(|&s| s == 1), "o {o}");
925 }
926 }
927}