waterui-ffi 0.3.0

FFI bindings for the WaterUI cross-platform UI framework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
//! FFI bindings for the `ViewEffect` raw view.
//!
//! This module provides the FFI interface for capturing view content and applying
//! GPU effects using wgpu.
//!
//! The native backend is responsible for:
//! 1. Creating a capture layer for the child view (`CAMetalLayer` on Apple, `TextureView` on Android)
//! 2. Creating an output layer for the effect result
//! 3. Calling `waterui_view_effect_create` immediately to consume the semantic renderer
//! 4. Attaching/detaching presentation targets independently of renderer lifetime
//! 5. Installing `waterui_view_effect_set_redraw_callback`
//! 6. Rendering the child view to the capture layer
//! 7. Calling `waterui_view_effect_render` for each scheduled render with the captured texture
//! 8. Calling `waterui_view_effect_drop` when the semantic view is destroyed

use core::ffi::c_void;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;

use alloc::boxed::Box;
use alloc::vec;
// Only the Apple Metal input-import path drives asynchronous effect setup.
#[cfg(any(target_os = "macos", target_os = "ios"))]
use executor_core::spawn_local;

#[cfg(any(target_os = "macos", target_os = "ios"))]
use {
    objc2::{rc::Retained, runtime::ProtocolObject},
    objc2_metal::{MTLPixelFormat, MTLTexture, MTLTextureType},
    wgpu_hal::api::Metal as MetalApi,
};

use waterui_graphics::RedrawHandle;
use waterui_graphics::shared_context::GpuRuntime;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use waterui_graphics::view_effect::ViewEffectContext;
use waterui_graphics::view_effect::{
    OutputSize, ViewEffectErased, ViewEffectInput, ViewEffectOutput,
};

use crate::{IntoFFI, WuiAnyView};

/// Native callback invoked when an idle view-effect surface becomes dirty.
pub type WuiViewEffectRedrawCallback = unsafe extern "C" fn(context: *mut c_void);

struct ForeignRedrawTarget {
    context: usize,
    wake: WuiViewEffectRedrawCallback,
    drop: WuiViewEffectRedrawCallback,
}

// SAFETY: native installs callbacks whose context is explicitly documented as
// callable and releasable from any thread. The target owns that context until
// its `drop` callback runs.
unsafe impl Send for ForeignRedrawTarget {}
// SAFETY: see the `Send` implementation. The wake callback must be thread-safe.
unsafe impl Sync for ForeignRedrawTarget {}

impl ForeignRedrawTarget {
    fn wake(&self) {
        // SAFETY: `wake` and `context` were registered together by the backend, and
        // the context outlives this waker.
        unsafe {
            (self.wake)(self.context as *mut c_void);
        }
    }
}

impl Drop for ForeignRedrawTarget {
    fn drop(&mut self) {
        // SAFETY: `drop` and `context` are one registration, and `Drop` runs once.
        unsafe {
            (self.drop)(self.context as *mut c_void);
        }
    }
}

/// FFI representation of output size.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum WuiOutputSize {
    /// Match the input view's size.
    MatchInput,
    /// Fixed pixel dimensions.
    Fixed {
        /// Output width in pixels.
        width: u32,
        /// Output height in pixels.
        height: u32,
    },
    /// Scale factor relative to input.
    Scale {
        /// Multiplier applied to the input's width and height.
        factor: f32,
    },
}

impl From<OutputSize> for WuiOutputSize {
    fn from(size: OutputSize) -> Self {
        match size {
            OutputSize::MatchInput => Self::MatchInput,
            OutputSize::Fixed { width, height } => Self::Fixed { width, height },
            OutputSize::Scale(factor) => Self::Scale { factor },
        }
    }
}

impl From<WuiOutputSize> for OutputSize {
    fn from(size: WuiOutputSize) -> Self {
        match size {
            WuiOutputSize::MatchInput => Self::MatchInput,
            WuiOutputSize::Fixed { width, height } => Self::Fixed { width, height },
            WuiOutputSize::Scale { factor } => Self::Scale(factor),
        }
    }
}

/// FFI representation of a `ViewEffect` view.
///
/// This struct is passed to the native backend when rendering the view tree.
/// The native backend should:
/// 1. Create capture and output layers
/// 2. Call `waterui_view_effect_create` immediately
/// 3. Attach the output layer once it exists
/// 4. Render the child view to the capture layer
/// 5. Call `waterui_view_effect_render` when rendering is scheduled
#[repr(C)]
#[derive(Debug)]
pub struct WuiViewEffect {
    /// The child view to capture (pointer to `WuiAnyView`).
    pub content: *mut WuiAnyView,
    /// Opaque pointer to the boxed effect renderer.
    /// This is consumed during init and should not be used after.
    pub effect: *mut c_void,
    /// Output size configuration.
    pub output_size: WuiOutputSize,
}

impl IntoFFI for ViewEffectErased {
    type FFI = WuiViewEffect;

    fn into_ffi(mut self) -> Self::FFI {
        // Capture output_size before moving self
        let output_size: WuiOutputSize = self.output_size().into();

        // Take the child view and convert to FFI
        let content = self.take_content().into_ffi();

        // Box the ViewEffectErased for FFI transfer
        // The effect renderer remains inside the erased wrapper
        let effect_wrapper = Box::new(ViewEffectRendererWrapper { erased: self });
        let effect_ptr = Box::into_raw(effect_wrapper).cast::<c_void>();

        WuiViewEffect {
            content,
            effect: effect_ptr,
            output_size,
        }
    }
}

/// Wrapper to hold `ViewEffectErased` for FFI calls.
struct ViewEffectRendererWrapper {
    erased: ViewEffectErased,
}

// Generate waterui_view_effect_id() and waterui_force_as_view_effect()
ffi_view!(ViewEffectErased, WuiViewEffect, view_effect, all(), any());

/// Opaque state held by the native backend after initialization.
pub struct WuiViewEffectState {
    /// Explicit environment-owned GPU runtime used by this semantic effect.
    runtime: GpuRuntime,
    /// Native presentation surface attached to this semantic effect.
    output_surface: Option<wgpu::Surface<'static>>,
    /// Configuration for the currently attached surface.
    output_config: Option<wgpu::SurfaceConfiguration>,
    /// Imported native capture texture.
    imported_texture: Option<wgpu::Texture>,
    /// Format of the imported texture.
    imported_format: Option<wgpu::TextureFormat>,
    /// The effect renderer. Setup temporarily moves it into its local future.
    effect_wrapper: Rc<RefCell<Option<ViewEffectRendererWrapper>>>,
    /// Handle shared with renderer-driven redraw callbacks.
    redraw_handle: RedrawHandle,
    /// Immutable input/output formats captured by the one-time setup.
    setup_formats: Cell<Option<(wgpu::TextureFormat, wgpu::TextureFormat)>>,
    /// Becomes true only after asynchronous setup completes.
    setup_ready: Rc<Cell<bool>>,
    /// Current input dimensions (from child view)
    input_width: u32,
    input_height: u32,
    /// Current output dimensions
    output_width: u32,
    output_height: u32,
    /// Output size configuration
    output_size: OutputSize,
}

impl core::fmt::Debug for WuiViewEffectState {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("WuiViewEffectState")
            .field("input_width", &self.input_width)
            .field("input_height", &self.input_height)
            .field("output_width", &self.output_width)
            .field("output_height", &self.output_height)
            .finish_non_exhaustive()
    }
}

/// Creates persistent state and immediately consumes the semantic effect renderer.
///
/// Presentation targets are attached later with [`waterui_view_effect_attach`],
/// so a view destroyed before layout still has one clear Rust owner.
///
/// # Safety
///
/// `effect` must be a valid, unconsumed descriptor returned by
/// `waterui_force_as_view_effect`.
/// `env` must contain an installed GPU runtime.
///
/// # Panics
///
/// Panics if `effect`'s inner effect pointer is null, meaning the descriptor
/// was already consumed by a previous call to this function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_create(
    effect: *mut WuiViewEffect,
    env: *const crate::WuiEnv,
) -> *mut WuiViewEffectState {
    // SAFETY: the caller contract requires `effect` to be a valid descriptor that no
    // one else is borrowing for this call.
    let wui_effect = unsafe { &mut *effect };
    assert!(
        !wui_effect.effect.is_null(),
        "waterui_view_effect_create: descriptor was already consumed"
    );
    let effect_wrapper: ViewEffectRendererWrapper =
        // SAFETY: the assert above proves the descriptor still owns its renderer, and
        // the field is nulled immediately after, so it is reclaimed once.
        unsafe { *Box::from_raw(wui_effect.effect.cast::<ViewEffectRendererWrapper>()) };
    wui_effect.effect = core::ptr::null_mut();
    let output_size: OutputSize = wui_effect.output_size.into();

    // SAFETY: the caller contract requires `env` to be a valid handle alive for this
    // call; it is only borrowed.
    let runtime = super::gpu_runtime::gpu_runtime(&unsafe { &*env }.0);
    let redraw_handle = effect_wrapper.erased.redraw_handle();
    Box::into_raw(Box::new(WuiViewEffectState {
        runtime,
        output_surface: None,
        output_config: None,
        imported_texture: None,
        imported_format: None,
        effect_wrapper: Rc::new(RefCell::new(Some(effect_wrapper))),
        redraw_handle,
        setup_formats: Cell::new(None),
        setup_ready: Rc::new(Cell::new(false)),
        input_width: 0,
        input_height: 0,
        output_width: 0,
        output_height: 0,
        output_size,
    }))
}

fn create_configured_surface(
    state: &WuiViewEffectState,
    layer: *mut c_void,
    input_width: u32,
    input_height: u32,
    prefers_hdr: bool,
) -> (wgpu::Surface<'static>, wgpu::SurfaceConfiguration) {
    assert!(
        input_width > 0 && input_height > 0,
        "waterui_view_effect_attach: dimensions must be non-zero, got {input_width}x{input_height}"
    );
    let (output_width, output_height) = state.output_size.compute(input_width, input_height);
    assert!(
        output_width > 0 && output_height > 0,
        "waterui_view_effect_attach: output size must be non-zero, got {output_width}x{output_height}"
    );

    let gpu = state.runtime.context();
    let surface = crate::components::gpu_surface::create_surface_from_layer(&gpu.instance, layer);
    let capabilities = surface.get_capabilities(&gpu.adapter);
    let format = waterui_graphics::gpu_surface::preferred_surface_format_with_preference(
        &capabilities,
        prefers_hdr,
    );
    assert!(
        capabilities
            .present_modes
            .contains(&wgpu::PresentMode::Fifo),
        "waterui_view_effect_attach: output surface does not support FIFO presentation"
    );
    let alpha_mode = [
        wgpu::CompositeAlphaMode::PreMultiplied,
        wgpu::CompositeAlphaMode::PostMultiplied,
        wgpu::CompositeAlphaMode::Inherit,
        wgpu::CompositeAlphaMode::Opaque,
    ]
    .into_iter()
    .find(|mode| capabilities.alpha_modes.contains(mode))
    .expect("waterui_view_effect_attach: output surface reports no composite alpha mode");
    let config = wgpu::SurfaceConfiguration {
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
        format,
        width: output_width,
        height: output_height,
        present_mode: wgpu::PresentMode::Fifo,
        alpha_mode,
        view_formats: vec![],
        desired_maximum_frame_latency: 2,
    };
    surface.configure(&gpu.device, &config);
    (surface, config)
}

/// Attaches a native presentation target while preserving the effect renderer.
///
/// # Safety
///
/// - `state` must come from [`waterui_view_effect_create`].
/// - `layer` must remain valid until [`waterui_view_effect_detach`].
/// - The state must currently be detached.
///
/// # Panics
///
/// Panics if `state` already has an output surface attached, if
/// `input_width`/`input_height` is zero, or if the computed output size is zero.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_attach(
    state: *mut WuiViewEffectState,
    layer: *mut c_void,
    input_width: u32,
    input_height: u32,
    prefers_hdr: bool,
) {
    // SAFETY: the caller contract requires `state` to be a valid handle, alive and
    // not otherwise borrowed for this call; the exclusive borrow ends here.
    let state = unsafe { crate::borrow_ffi_mut(state) };
    assert!(
        state.output_surface.is_none(),
        "waterui_view_effect_attach: output surface is already attached"
    );
    let (surface, config) =
        create_configured_surface(state, layer, input_width, input_height, prefers_hdr);
    if let Some((_, setup_output_format)) = state.setup_formats.get() {
        assert_eq!(
            setup_output_format, config.format,
            "ViewEffect output format changed after setup"
        );
    }
    state.input_width = input_width;
    state.input_height = input_height;
    state.output_width = config.width;
    state.output_height = config.height;
    state.output_config = Some(config);
    state.output_surface = Some(surface);
    let _ = state.redraw_handle.take_dirty();
    if state.setup_ready.get() {
        state.redraw_handle.request_redraw();
    }
}

/// Detaches the native presentation target without destroying the effect renderer.
///
/// # Safety
///
/// `state` must be valid and currently attached.
///
/// # Panics
///
/// Panics if `state` does not currently have an output surface attached.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_detach(state: *mut WuiViewEffectState) {
    // SAFETY: the caller contract requires `state` to be a valid handle, alive and
    // not otherwise borrowed for this call; the exclusive borrow ends here.
    let state = unsafe { crate::borrow_ffi_mut(state) };
    let surface = state
        .output_surface
        .take()
        .expect("waterui_view_effect_detach: output surface is already detached");
    state.output_config = None;
    state.imported_texture = None;
    state.imported_format = None;
    state.input_width = 0;
    state.input_height = 0;
    state.output_width = 0;
    state.output_height = 0;
    drop(surface);
}

/// Installs the native wake target for renderer-driven redraw requests.
///
/// # Safety
///
/// - `state` and `context` must be valid.
/// - Both callbacks must be thread-safe and use `context` only until
///   `drop_callback` releases it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_set_redraw_callback(
    state: *mut WuiViewEffectState,
    context: *mut c_void,
    wake: WuiViewEffectRedrawCallback,
    drop_callback: WuiViewEffectRedrawCallback,
) {
    // SAFETY: the caller contract requires `state` to be a valid handle, alive and
    // not otherwise borrowed for this call; the exclusive borrow ends here.
    let state = unsafe { crate::borrow_ffi_mut(state) };
    let target = ForeignRedrawTarget {
        context: context as usize,
        wake,
        drop: drop_callback,
    };
    state
        .redraw_handle
        .set_waker(Some(Arc::new(move || target.wake())));
}

/// Validates and applies new input dimensions, resizing owned textures.
///
/// Only reachable from the Apple Metal input-import entry point; every other
/// platform feeds `ViewEffect` through the Rust-side filter pipeline.
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn ensure_dimensions(state: &mut WuiViewEffectState, width: u32, height: u32) {
    assert!(
        width > 0 && height > 0,
        "ViewEffect input dimensions must be non-zero, got {width}x{height}"
    );
    let (output_width, output_height) = state.output_size.compute(width, height);
    assert!(
        output_width > 0 && output_height > 0,
        "ViewEffect output dimensions must be non-zero, got {output_width}x{output_height}"
    );
    let input_resized = width != state.input_width || height != state.input_height;
    let output_resized = output_width != state.output_width || output_height != state.output_height;

    if input_resized {
        state.input_width = width;
        state.input_height = height;
    }
    if output_resized {
        state.output_width = output_width;
        state.output_height = output_height;
        let config = state
            .output_config
            .as_mut()
            .expect("ViewEffect resize requires an attached output configuration");
        config.width = output_width;
        config.height = output_height;
        let surface = state
            .output_surface
            .as_ref()
            .expect("ViewEffect resize requires an attached output surface");
        surface.configure(&state.runtime.context().device, config);
    }
}

fn assert_setup_input_format(state: &WuiViewEffectState, input_format: wgpu::TextureFormat) {
    if let Some((setup_input_format, _)) = state.setup_formats.get() {
        assert_eq!(
            setup_input_format, input_format,
            "ViewEffect input format changed after setup"
        );
    }
}

/// Imports the Metal texture containing the captured child view.
///
/// # Safety
///
/// - state must be a valid pointer from `waterui_view_effect_create`.
/// - texture must point to a live `MTLTexture` for the duration of this call.
///
/// # Panics
///
/// Panics if the imported Metal texture did not resolve to a supported
/// texture format.
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_set_input_metal_texture(
    state: *mut WuiViewEffectState,
    texture: *mut c_void,
    width: u32,
    height: u32,
) {
    // SAFETY: the caller contract requires `state` to be a valid handle, alive and
    // not otherwise borrowed for this call; the exclusive borrow ends here.
    let state = unsafe { crate::borrow_ffi_mut(state) };
    ensure_dimensions(state, width, height);
    import_metal_texture(state, texture, width, height);
    let input_format = state
        .imported_format
        .expect("ViewEffect Metal input import did not provide a texture format");
    start_view_effect_setup(state, input_format);
}

/// Returns whether asynchronous effect setup has completed.
///
/// Completion also triggers the installed redraw callback.
///
/// # Safety
///
/// `state` must be a valid pointer returned by [`waterui_view_effect_create`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_is_ready(state: *const WuiViewEffectState) -> bool {
    // SAFETY: the caller contract requires `state` to be a valid handle that stays
    // alive for this call; it is only borrowed.
    let state = unsafe { crate::borrow_ffi(state) };
    state.setup_ready.get()
}

/// Render the effect.
///
/// This function applies the effect to the captured input and renders to the output.
///
/// # Arguments
///
/// * `state` - Pointer to attached persistent state
///
/// # Returns
///
/// Whether another frame should be scheduled immediately.
///
/// # Safety
///
/// `state` must be a valid pointer from `waterui_view_effect_create` with an attached target.
///
/// # Panics
///
/// Panics if no input texture was imported before this call.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_render(state: *mut WuiViewEffectState) -> bool {
    // SAFETY: the caller contract requires `state` to be a valid handle, alive and
    // not otherwise borrowed for this call; the exclusive borrow ends here.
    let state = unsafe { crate::borrow_ffi_mut(state) };
    let input_format = state
        .imported_format
        .expect("waterui_view_effect_render: input texture was not provided");

    assert!(
        state.setup_ready.get(),
        "waterui_view_effect_render called before asynchronous setup completed"
    );
    assert_setup_input_format(state, input_format);
    let output_surface = state
        .output_surface
        .as_ref()
        .expect("waterui_view_effect_render: presentation target is detached");
    let output_config = state
        .output_config
        .as_ref()
        .expect("waterui_view_effect_render: output configuration is detached");

    // Get output texture
    let Some(output) = super::acquire_surface_texture(
        output_surface,
        &state.runtime.context().device,
        output_config,
        "waterui_view_effect_render",
    ) else {
        return false;
    };

    let input_texture = state
        .imported_texture
        .as_ref()
        .expect("waterui_view_effect_render: input texture was not provided");

    let input_view = input_texture.create_view(&wgpu::TextureViewDescriptor {
        label: Some("ViewEffect Input View"),
        ..Default::default()
    });

    let output_view = output.texture.create_view(&wgpu::TextureViewDescriptor {
        label: Some("ViewEffect Output View"),
        format: Some(output_config.format),
        ..Default::default()
    });

    // Create input/output structs
    let input = ViewEffectInput {
        device: &state.runtime.context().device,
        queue: &state.runtime.context().queue,
        texture: input_texture,
        view: input_view,
        format: input_format,
        width: state.input_width,
        height: state.input_height,
    };

    let effect_output = ViewEffectOutput {
        device: &state.runtime.context().device,
        queue: &state.runtime.context().queue,
        texture: &output.texture,
        view: output_view,
        format: output_config.format,
        width: state.output_width,
        height: state.output_height,
    };

    // Call effect render
    let mut effect_wrapper = state.effect_wrapper.borrow_mut();
    let effect_wrapper = effect_wrapper
        .as_mut()
        .expect("ViewEffect ready state is missing its semantic renderer");
    let needs_redraw = effect_wrapper.erased.render(&input, &effect_output);

    // Present
    output.present();

    needs_redraw
}

/// Kicks off asynchronous effect setup for the given input format.
///
/// Only reachable from the Apple Metal input-import entry point; every other
/// platform feeds `ViewEffect` through the Rust-side filter pipeline.
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn start_view_effect_setup(state: &WuiViewEffectState, input_format: wgpu::TextureFormat) {
    let output_format = state
        .output_config
        .as_ref()
        .expect("ViewEffect setup requires an attached presentation target")
        .format;
    if let Some((setup_input_format, setup_output_format)) = state.setup_formats.get() {
        assert_eq!(
            setup_input_format, input_format,
            "ViewEffect input format changed after setup"
        );
        assert_eq!(
            setup_output_format, output_format,
            "ViewEffect output format changed after setup"
        );
        return;
    }

    state.setup_formats.set(Some((input_format, output_format)));
    let mut effect_wrapper = state
        .effect_wrapper
        .borrow_mut()
        .take()
        .expect("ViewEffect semantic renderer is unavailable before setup starts");
    let effect_slot = Rc::clone(&state.effect_wrapper);
    let setup_ready = Rc::clone(&state.setup_ready);
    let runtime = state.runtime.clone();
    let redraw_handle = state.redraw_handle.clone();
    spawn_local(async move {
        let gpu = runtime.context();
        let ctx = ViewEffectContext {
            device: &gpu.device,
            queue: &gpu.queue,
            input_format,
            output_format,
        };
        effect_wrapper.erased.setup(&ctx).await;
        effect_slot.replace(Some(effect_wrapper));
        setup_ready.set(true);
        redraw_handle.request_redraw();
    })
    .detach();
}

/// Clean up `ViewEffect` resources.
///
/// # Safety
///
/// `state` must be a valid pointer from `waterui_view_effect_create`,
/// and must not be used after this call.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_drop(state: *mut WuiViewEffectState) {
    // SAFETY: the caller contract makes `state` an owning handle from the matching
    // constructor that has not been dropped.
    unsafe {
        let _ = Box::from_raw(state);
    }
}

/// Import a Metal texture as a wgpu texture (Apple zero-copy path).
///
/// This function creates a wgpu texture that directly references the Metal texture,
/// enabling zero-copy texture sharing between the native view and the GPU effect pipeline.
///
/// The native side (Swift) is responsible for:
/// 1. Creating an `IOSurface`
/// 2. Creating a Metal texture backed by that `IOSurface`
/// 3. Passing the `MTLTexture` pointer to this function
///
/// # Arguments
///
/// * `state` - The `ViewEffect` state
/// * `mtl_texture_ptr` - Pointer to an `MTLTexture`
/// * `width` - Width in pixels
/// * `height` - Height in pixels
///
/// # Safety
///
/// The `MTLTexture` must remain valid for the lifetime of the imported texture.
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn import_metal_texture(
    state: &mut WuiViewEffectState,
    mtl_texture_ptr: *mut c_void,
    width: u32,
    height: u32,
) {
    use wgpu_hal::Api;

    // SAFETY: the caller contract requires the pointer to be a live `MTLTexture`;
    // `retain` takes its own reference, so it stays alive for the work below.
    let metal_texture = unsafe {
        Retained::<ProtocolObject<dyn MTLTexture>>::retain(mtl_texture_ptr.cast())
            .expect("view_effect::import_metal_texture received a null texture")
    };

    tracing::debug!(
        "[ViewEffect] Importing Metal texture: {}x{} {:?}",
        width,
        height,
        metal_texture.pixelFormat()
    );

    let wgpu_format = match metal_texture.pixelFormat() {
        MTLPixelFormat::BGRA8Unorm => wgpu::TextureFormat::Bgra8Unorm,
        MTLPixelFormat::BGRA8Unorm_sRGB => wgpu::TextureFormat::Bgra8UnormSrgb,
        MTLPixelFormat::RGBA16Float => wgpu::TextureFormat::Rgba16Float,
        other => {
            panic!("view_effect::import_metal_texture: unsupported Metal format {other:?}");
        }
    };
    assert_setup_input_format(state, wgpu_format);

    // Create HAL texture from the Metal texture
    // SAFETY: `metal_texture` is the retained texture above, described with the format
    // and size read from that same texture.
    let hal_texture = unsafe {
        <MetalApi as Api>::Device::texture_from_raw(
            metal_texture,
            wgpu_format,
            MTLTextureType::Type2D,
            1, // array_layers
            1, // mip_levels
            wgpu_hal::CopyExtent {
                width,
                height,
                depth: 1,
            },
        )
    };

    // Create wgpu texture descriptor
    let texture_desc = wgpu::TextureDescriptor {
        label: Some("ViewEffect Imported Metal Texture"),
        size: wgpu::Extent3d {
            width,
            height,
            depth_or_array_layers: 1,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu_format,
        usage: wgpu::TextureUsages::TEXTURE_BINDING,
        view_formats: &[],
    };

    // Create wgpu texture from HAL texture
    // SAFETY: the HAL texture above came from this runtime's device, which is the
    // device the wgpu texture is created on.
    let wgpu_texture = unsafe {
        state
            .runtime
            .context()
            .device
            .create_texture_from_hal::<MetalApi>(hal_texture, &texture_desc)
    };

    // Store the imported texture
    state.imported_texture = Some(wgpu_texture);
    state.imported_format = Some(wgpu_format);
}