use core::ffi::c_void;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use alloc::boxed::Box;
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
use alloc::vec;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
use executor_core::spawn_local;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
use futures::FutureExt;
#[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", target_os = "android"))]
use waterui_graphics::view_effect::ViewEffectContext;
use waterui_graphics::view_effect::{
OutputSize, ViewEffectErased, ViewEffectInput, ViewEffectOutput,
};
use crate::{IntoFFI, WuiAnyView};
pub type WuiViewEffectRedrawCallback = unsafe extern "C" fn(context: *mut c_void);
struct ForeignRedrawTarget {
context: usize,
wake: WuiViewEffectRedrawCallback,
drop: WuiViewEffectRedrawCallback,
}
unsafe impl Send for ForeignRedrawTarget {}
unsafe impl Sync for ForeignRedrawTarget {}
impl ForeignRedrawTarget {
fn wake(&self) {
unsafe {
(self.wake)(self.context as *mut c_void);
}
}
}
impl Drop for ForeignRedrawTarget {
fn drop(&mut self) {
unsafe {
(self.drop)(self.context as *mut c_void);
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum WuiOutputSize {
MatchInput,
Fixed {
width: u32,
height: u32,
},
Scale {
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),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiViewEffect {
pub content: *mut WuiAnyView,
pub effect: *mut c_void,
pub output_size: WuiOutputSize,
}
impl IntoFFI for ViewEffectErased {
type FFI = WuiViewEffect;
fn into_ffi(mut self) -> Self::FFI {
let output_size: WuiOutputSize = self.output_size().into();
let content = self.take_content().into_ffi();
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,
}
}
}
struct ViewEffectRendererWrapper {
erased: ViewEffectErased,
}
ffi_view!(ViewEffectErased, WuiViewEffect, view_effect);
enum ViewEffectTarget {
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
Surface {
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
},
#[cfg(any(target_os = "macos", target_os = "ios"))]
HostTexture { format: wgpu::TextureFormat },
}
impl ViewEffectTarget {
const fn format(&self) -> wgpu::TextureFormat {
match self {
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
Self::Surface { config, .. } => config.format,
#[cfg(any(target_os = "macos", target_os = "ios"))]
Self::HostTexture { format } => *format,
}
}
#[cfg(target_os = "android")]
fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
let Self::Surface { surface, config } = self;
config.width = width;
config.height = height;
surface.configure(device, config);
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct WuiViewEffectOutputSize {
pub width: u32,
pub height: u32,
}
pub struct WuiViewEffectState {
runtime: GpuRuntime,
output: Option<ViewEffectTarget>,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
attached_layer: *mut c_void,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
attached_prefers_hdr: bool,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
context_generation: u64,
imported_texture: Option<wgpu::Texture>,
imported_format: Option<wgpu::TextureFormat>,
effect_wrapper: Rc<RefCell<Option<ViewEffectRendererWrapper>>>,
redraw_handle: RedrawHandle,
setup_formats: Cell<Option<(wgpu::TextureFormat, wgpu::TextureFormat)>>,
setup_ready: Rc<Cell<bool>>,
input_width: u32,
input_height: u32,
output_width: u32,
output_height: u32,
output_size: OutputSize,
#[cfg(target_os = "android")]
hardware_buffer_imports: super::hardware_buffer::HardwareBufferImports,
#[cfg(target_os = "android")]
capture_compositor: super::capture_composite::CaptureCompositor,
}
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()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_create(
effect: *mut WuiViewEffect,
env: *const crate::WuiEnv,
) -> *mut WuiViewEffectState {
let wui_effect = unsafe { &mut *effect };
assert!(
!wui_effect.effect.is_null(),
"waterui_view_effect_create: descriptor was already consumed"
);
let effect_wrapper: ViewEffectRendererWrapper =
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();
let runtime = super::gpu_runtime::gpu_runtime(&unsafe { &*env }.0);
#[cfg(target_os = "android")]
let hardware_buffer_imports =
super::hardware_buffer::HardwareBufferImports::new(runtime.clone());
let redraw_handle = effect_wrapper.erased.redraw_handle();
Box::into_raw(Box::new(WuiViewEffectState {
runtime,
output: None,
imported_texture: None,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
attached_layer: core::ptr::null_mut(),
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
attached_prefers_hdr: false,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
context_generation: 0,
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,
#[cfg(target_os = "android")]
hardware_buffer_imports,
#[cfg(target_os = "android")]
capture_compositor: super::capture_composite::CaptureCompositor::default(),
}))
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
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)
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
#[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,
) {
let state = unsafe { crate::borrow_ffi_mut(state) };
assert!(
state.output.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);
let (output_width, output_height) = (config.width, config.height);
state.attached_layer = layer;
state.attached_prefers_hdr = prefers_hdr;
state.context_generation = state.runtime.context().generation();
finish_attach(
state,
ViewEffectTarget::Surface { surface, config },
input_width,
input_height,
output_width,
output_height,
);
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
fn ensure_current_context(
state: &mut WuiViewEffectState,
) -> Arc<waterui_graphics::shared_context::SharedGpuContext> {
let gpu = state.runtime.context();
if gpu.generation() == state.context_generation {
return gpu;
}
tracing::warn!(
old = state.context_generation,
new = gpu.generation(),
"GPU device was replaced; recreating the effect's device-bound state"
);
state.context_generation = gpu.generation();
state.imported_texture = None;
state.imported_format = None;
#[cfg(target_os = "android")]
{
state.hardware_buffer_imports.clear_after_device_loss();
state.capture_compositor = super::capture_composite::CaptureCompositor::default();
}
if state.output.is_some() {
drop(state.output.take());
let (surface, config) = create_configured_surface(
state,
state.attached_layer,
state.input_width,
state.input_height,
state.attached_prefers_hdr,
);
let (output_width, output_height) = (config.width, config.height);
let (input_width, input_height) = (state.input_width, state.input_height);
finish_attach(
state,
ViewEffectTarget::Surface { surface, config },
input_width,
input_height,
output_width,
output_height,
);
}
if state.setup_formats.get().is_some() {
restart_view_effect_setup(state);
}
gpu
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[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,
) {
panic!(
"waterui_view_effect_attach: Apple hosts attach with waterui_view_effect_attach_host_textures"
);
}
fn finish_attach(
state: &mut WuiViewEffectState,
output: ViewEffectTarget,
input_width: u32,
input_height: u32,
output_width: u32,
output_height: u32,
) {
if let Some((_, setup_output_format)) = state.setup_formats.get() {
assert_eq!(
setup_output_format,
output.format(),
"ViewEffect output format changed after setup"
);
}
state.input_width = input_width;
state.input_height = input_height;
state.output_width = output_width;
state.output_height = output_height;
state.output = Some(output);
let _ = state.redraw_handle.take_dirty();
if state.setup_ready.get() {
state.redraw_handle.request_redraw();
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_detach(state: *mut WuiViewEffectState) {
let state = unsafe { crate::borrow_ffi_mut(state) };
assert!(
state.output.is_some(),
"waterui_view_effect_detach: output surface is already detached"
);
#[cfg(target_os = "android")]
state.hardware_buffer_imports.clear();
state.imported_texture = None;
state.imported_format = None;
state.input_width = 0;
state.input_height = 0;
state.output_width = 0;
state.output_height = 0;
state.output = None;
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
const fn apple_presentation_format(prefers_hdr: bool) -> wgpu::TextureFormat {
if prefers_hdr {
wgpu::TextureFormat::Rgba16Float
} else {
wgpu::TextureFormat::Bgra8UnormSrgb
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_attach_host_textures(
state: *mut WuiViewEffectState,
input_width: u32,
input_height: u32,
prefers_hdr: bool,
) {
let state = unsafe { crate::borrow_ffi_mut(state) };
assert!(
state.output.is_none(),
"waterui_view_effect_attach_host_textures: output surface is already attached"
);
assert!(
input_width > 0 && input_height > 0,
"waterui_view_effect_attach_host_textures: 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_host_textures: output size must be non-zero, got {output_width}x{output_height}"
);
let format = apple_presentation_format(prefers_hdr);
finish_attach(
state,
ViewEffectTarget::HostTexture { format },
input_width,
input_height,
output_width,
output_height,
);
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_output_metal_pixel_format(
state: *const WuiViewEffectState,
) -> u32 {
let state = unsafe { crate::borrow_ffi(state) };
let format = state
.output
.as_ref()
.expect("waterui_view_effect_output_metal_pixel_format: presentation target is detached")
.format();
let metal_format = match format {
wgpu::TextureFormat::Bgra8Unorm => MTLPixelFormat::BGRA8Unorm,
wgpu::TextureFormat::Bgra8UnormSrgb => MTLPixelFormat::BGRA8Unorm_sRGB,
wgpu::TextureFormat::Rgba16Float => MTLPixelFormat::RGBA16Float,
other => panic!(
"waterui_view_effect_output_metal_pixel_format: {other:?} has no Metal equivalent"
),
};
u32::try_from(metal_format.0).expect("MTLPixelFormat values fit in a u32")
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_resolve_output_size(
state: *const WuiViewEffectState,
input_width: u32,
input_height: u32,
) -> WuiViewEffectOutputSize {
let state = unsafe { crate::borrow_ffi(state) };
let (width, height) = state.output_size.compute(input_width, input_height);
assert!(
width > 0 && height > 0,
"waterui_view_effect_resolve_output_size: output size must be non-zero, got {width}x{height} for input {input_width}x{input_height}"
);
WuiViewEffectOutputSize { width, height }
}
#[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,
) {
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())));
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
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;
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
state
.output
.as_mut()
.expect("ViewEffect resize requires an attached output surface")
.resize(&state.runtime.context().device, output_width, output_height);
}
}
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"
);
}
}
#[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,
) {
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);
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_set_input_hardware_buffer(
state: *mut WuiViewEffectState,
buffer: *mut c_void,
) -> *mut super::gpu_surface::WuiGpuCaptureFence {
use super::capture_format::{create_effect_input_texture, effect_input_texture_format};
use super::hardware_buffer::{copy_hardware_buffer_into_texture, describe_hardware_buffer};
let state = unsafe { crate::borrow_ffi_mut(state) };
let gpu = ensure_current_context(state);
super::run_gpu_frame(
&gpu,
"waterui_view_effect_set_input_hardware_buffer",
|| {
let buffer = buffer.cast();
let description = unsafe { describe_hardware_buffer(buffer) };
ensure_dimensions(state, description.width, description.height);
let input_format = effect_input_texture_format(description.format);
assert_setup_input_format(state, input_format);
let input_texture = match state.imported_texture.take() {
Some(texture)
if texture.width() == description.width
&& texture.height() == description.height
&& texture.format() == input_format =>
{
texture
}
_ => create_effect_input_texture(
&gpu.device,
&gpu.queue,
input_format,
description.width,
description.height,
),
};
let fence = unsafe {
copy_hardware_buffer_into_texture(
&mut state.hardware_buffer_imports,
buffer,
&input_texture,
"waterui_view_effect_set_input_hardware_buffer",
)
};
state.imported_texture = Some(input_texture);
state.imported_format = Some(input_format);
start_view_effect_setup(state, input_format);
fence
},
)
.unwrap_or(core::ptr::null_mut())
}
#[cfg(not(target_os = "android"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_set_input_hardware_buffer(
_state: *mut WuiViewEffectState,
_buffer: *mut c_void,
) -> *mut super::gpu_surface::WuiGpuCaptureFence {
panic!("waterui_view_effect_set_input_hardware_buffer: only supported on Android");
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_composite_gpu_surface(
effect: *mut WuiViewEffectState,
surface: *mut super::gpu_surface::WuiGpuSurfaceState,
x: i32,
y: i32,
width: u32,
height: u32,
scale: f64,
) {
let effect = unsafe { crate::borrow_ffi_mut(effect) };
let surface = unsafe { crate::borrow_ffi_mut(surface) };
let gpu = ensure_current_context(effect);
super::gpu_surface::ensure_current_context(surface);
super::run_gpu_frame(&gpu, "waterui_view_effect_composite_gpu_surface", || {
let input_texture = effect.imported_texture.take().expect(
"waterui_view_effect_composite_gpu_surface: no captured input has been handed over yet",
);
super::capture_composite::composite_gpu_surface(
&mut effect.capture_compositor,
surface,
&input_texture,
super::capture_composite::CompositePlacement {
x,
y,
width,
height,
scale,
},
"waterui_view_effect_composite_gpu_surface",
);
effect.imported_texture = Some(input_texture);
});
}
#[cfg(not(target_os = "android"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_composite_gpu_surface(
_effect: *mut WuiViewEffectState,
_surface: *mut super::gpu_surface::WuiGpuSurfaceState,
_x: i32,
_y: i32,
_width: u32,
_height: u32,
_scale: f64,
) {
panic!("waterui_view_effect_composite_gpu_surface: only supported on Android");
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_is_ready(state: *const WuiViewEffectState) -> bool {
let state = unsafe { crate::borrow_ffi(state) };
state.setup_ready.get()
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_render(state: *mut WuiViewEffectState) -> bool {
let state = unsafe { crate::borrow_ffi_mut(state) };
let gpu = ensure_current_context(state);
super::run_gpu_frame(&gpu, "waterui_view_effect_render", || {
let Some(input_format) = state.imported_format else {
return true;
};
if !state.setup_ready.get() {
return true;
}
assert_setup_input_format(state, input_format);
let Some(ViewEffectTarget::Surface {
surface: output_surface,
config: output_config,
}) = state.output.as_ref()
else {
panic!("waterui_view_effect_render: presentation target is detached");
};
let Some(output) = super::acquire_surface_texture(
output_surface,
&gpu,
output_config,
"waterui_view_effect_render",
) else {
return true;
};
let output_format = output_config.format;
let needs_redraw = render_effect_into(
state,
&gpu,
&output.texture,
output_format,
"waterui_view_effect_render",
);
output.present();
gpu.note_presented_submission(gpu.queue.submit([]));
needs_redraw
})
.unwrap_or(true)
}
fn render_effect_into(
state: &WuiViewEffectState,
gpu: &waterui_graphics::shared_context::SharedGpuContext,
output_texture: &wgpu::Texture,
output_format: wgpu::TextureFormat,
caller: &str,
) -> bool {
let input_format = state
.imported_format
.unwrap_or_else(|| panic!("{caller}: input texture was not provided"));
let input_texture = state
.imported_texture
.as_ref()
.unwrap_or_else(|| panic!("{caller}: 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_format),
..Default::default()
});
let input = ViewEffectInput {
device: &gpu.device,
queue: &gpu.queue,
texture: input_texture,
view: input_view,
format: input_format,
width: state.input_width,
height: state.input_height,
};
let effect_output = ViewEffectOutput {
device: &gpu.device,
queue: &gpu.queue,
texture: output_texture,
view: output_view,
format: output_format,
width: state.output_width,
height: state.output_height,
};
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");
effect_wrapper.erased.render(&input, &effect_output)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_render_to_metal_texture(
state: *mut WuiViewEffectState,
texture: *mut c_void,
width: u32,
height: u32,
out_needs_redraw: *mut bool,
) -> *mut super::gpu_surface::WuiGpuCaptureFence {
let state = unsafe { crate::borrow_ffi_mut(state) };
let metal_texture = unsafe {
Retained::<ProtocolObject<dyn MTLTexture>>::retain(texture.cast())
.expect("waterui_view_effect_render_to_metal_texture received a null texture")
};
assert!(
state.setup_ready.get(),
"waterui_view_effect_render_to_metal_texture called before asynchronous setup completed"
);
ensure_dimensions(state, width, height);
let output_format = state
.output
.as_ref()
.expect("waterui_view_effect_render_to_metal_texture: presentation target is detached")
.format();
let texture_format = match metal_texture.pixelFormat() {
MTLPixelFormat::BGRA8Unorm => wgpu::TextureFormat::Bgra8Unorm,
MTLPixelFormat::BGRA8Unorm_sRGB => wgpu::TextureFormat::Bgra8UnormSrgb,
MTLPixelFormat::RGBA16Float => wgpu::TextureFormat::Rgba16Float,
other => panic!(
"waterui_view_effect_render_to_metal_texture: unsupported Metal format {other:?}"
),
};
assert_eq!(
texture_format, output_format,
"waterui_view_effect_render_to_metal_texture: host texture format does not match the attached output format"
);
let (output_width, output_height) = (state.output_width, state.output_height);
let hal_texture = unsafe {
<MetalApi as wgpu_hal::Api>::Device::texture_from_raw(
metal_texture,
output_format,
MTLTextureType::Type2D,
1,
1,
wgpu_hal::CopyExtent {
width: output_width,
height: output_height,
depth: 1,
},
)
};
let wgpu_texture = unsafe {
state
.runtime
.context()
.device
.create_texture_from_hal::<MetalApi>(
hal_texture,
&wgpu::TextureDescriptor {
label: Some("ViewEffect Host Presentation Texture"),
size: wgpu::Extent3d {
width: output_width,
height: output_height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: output_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
},
)
};
let gpu = state.runtime.context();
let needs_redraw = render_effect_into(
state,
&gpu,
&wgpu_texture,
output_format,
"waterui_view_effect_render_to_metal_texture",
);
unsafe { out_needs_redraw.write(needs_redraw) };
let submission = gpu.queue.submit([]);
let fence =
super::gpu_surface::WuiGpuCaptureFence::new(gpu.submission_completion_driver(), submission);
Box::into_raw(Box::new(fence))
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_render(_state: *mut WuiViewEffectState) -> bool {
panic!(
"waterui_view_effect_render: Apple hosts render with waterui_view_effect_render_to_metal_texture"
);
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
fn start_view_effect_setup(state: &WuiViewEffectState, input_format: wgpu::TextureFormat) {
let output_format = state
.output
.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)));
spawn_view_effect_setup(state, input_format, output_format);
}
#[cfg(target_os = "android")]
fn restart_view_effect_setup(state: &WuiViewEffectState) {
let (input_format, output_format) = state
.setup_formats
.get()
.expect("ViewEffect recovery requires a completed earlier setup");
if state.effect_wrapper.borrow().is_none() {
return;
}
state.setup_ready.set(false);
spawn_view_effect_setup(state, input_format, output_format);
}
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
const fn restart_view_effect_setup(_state: &WuiViewEffectState) {}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
fn spawn_view_effect_setup(
state: &WuiViewEffectState,
input_format: wgpu::TextureFormat,
output_format: wgpu::TextureFormat,
) {
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 {
loop {
let gpu = runtime.context();
let outcome = {
let ctx = ViewEffectContext {
device: &gpu.device,
queue: &gpu.queue,
input_format,
output_format,
};
std::panic::AssertUnwindSafe(effect_wrapper.erased.setup(&ctx))
.catch_unwind()
.await
};
if let Err(payload) = outcome {
if gpu.device_lost_reason().is_none() {
std::panic::resume_unwind(payload);
}
continue;
}
if gpu.device_lost_reason().is_none()
&& runtime.context().generation() == gpu.generation()
{
break;
}
}
effect_slot.replace(Some(effect_wrapper));
setup_ready.set(true);
redraw_handle.request_redraw();
})
.detach();
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_effect_drop(state: *mut WuiViewEffectState) {
unsafe {
let _ = Box::from_raw(state);
}
}
#[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;
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);
let hal_texture = unsafe {
<MetalApi as Api>::Device::texture_from_raw(
metal_texture,
wgpu_format,
MTLTextureType::Type2D,
1, 1, wgpu_hal::CopyExtent {
width,
height,
depth: 1,
},
)
};
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: &[],
};
let wgpu_texture = unsafe {
state
.runtime
.context()
.device
.create_texture_from_hal::<MetalApi>(hal_texture, &texture_desc)
};
state.imported_texture = Some(wgpu_texture);
state.imported_format = Some(wgpu_format);
}