use crate::factory::{Submit3DError, SurfaceMode, WindowRenderState};
#[derive(Debug, Clone)]
pub struct Compose3DJob {
pub camera: uzor_urx_3d::PerspectiveCamera,
pub dst_x: u32,
pub dst_y: u32,
pub dst_w: u32,
pub dst_h: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct ComposedOutcome {
pub jobs_rendered: u32,
pub jobs_skipped: u32,
pub surface_lost: bool,
}
pub fn submit_urx_composed(
state: &mut WindowRenderState,
base_color: [f32; 4],
jobs: &[Compose3DJob],
) -> Result<ComposedOutcome, Submit3DError> {
let (surf_w, surf_h, surface_format) = match &state.surface {
SurfaceMode::Gpu { surface, .. } => (
surface.config.width,
surface.config.height,
surface.config.format,
),
_ => return Err(Submit3DError::NotGpuSurface),
};
if surf_w == 0 || surf_h == 0 { return Err(Submit3DError::ZeroSizedSurface); }
let (device, queue) = match &state.surface {
SurfaceMode::Gpu { gpu_pool, dev_id, .. } => (
gpu_pool.devices[*dev_id].device.clone(),
gpu_pool.devices[*dev_id].queue.clone(),
),
_ => return Err(Submit3DError::NotGpuSurface),
};
let urx_scene_opt = state.urx_ctx.as_mut().map(|c| c.take_scene());
let SurfaceMode::Gpu { surface, .. } = &mut state.surface else {
return Err(Submit3DError::NotGpuSurface);
};
let frame = match surface.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
surface.surface.configure(&device, &surface.config);
return Ok(ComposedOutcome { jobs_rendered: 0, jobs_skipped: jobs.len() as u32, surface_lost: true });
}
_ => return Ok(ComposedOutcome { jobs_rendered: 0, jobs_skipped: jobs.len() as u32, surface_lost: true }),
};
let swap_view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("uzor-render-hub:compose"),
});
let urx_backend = state.active_urx.unwrap_or(uzor::UrxBackend::Cpu);
let backend_resolved = match urx_backend {
uzor::UrxBackend::Auto => uzor::UrxBackend::Wgpu, b => b,
};
match backend_resolved {
uzor::UrxBackend::Cpu => {
compose_urx_cpu_into_swap(
state, &device, &queue, &mut encoder, &swap_view,
surf_w, surf_h, urx_scene_opt, base_color,
);
}
uzor::UrxBackend::Wgpu => {
compose_urx_wgpu_into_swap(
state, &device, &queue, &mut encoder, &swap_view,
surf_w, surf_h, surface_format, urx_scene_opt, base_color,
);
}
uzor::UrxBackend::Hybrid | uzor::UrxBackend::WgpuFull | uzor::UrxBackend::Auto => {
clear_swap_view(&mut encoder, &swap_view, base_color);
if let Some(ref mut c) = state.urx_ctx {
let _ = c;
}
}
}
let mut jobs_rendered = 0u32;
let mut jobs_skipped = 0u32;
for job in jobs {
let dx = job.dst_x.min(surf_w.saturating_sub(1));
let dy = job.dst_y.min(surf_h.saturating_sub(1));
let dw = job.dst_w.min(surf_w.saturating_sub(dx));
let dh = job.dst_h.min(surf_h.saturating_sub(dy));
if dw == 0 || dh == 0 {
jobs_skipped += 1;
continue;
}
if state.urx_renderer_3d.is_none() {
state.urx_renderer_3d = Some(uzor_urx_3d::Renderer3D::new(
&device, &queue, surface_format, (dw, dh), 1024,
));
}
if state.urx_scene_3d.is_none() {
state.urx_scene_3d = Some(uzor_urx_3d::Scene3D::new());
}
let need_new_target = match &state.urx_offscreen_3d {
None => true,
Some(o) => o.width != dw || o.height != dh || o.format != surface_format,
};
if need_new_target {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("urx-3d-offscreen-compose"),
size: wgpu::Extent3d { width: dw, height: dh, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: surface_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
state.urx_offscreen_3d = Some(crate::factory::UrxOffscreen3D {
texture, view, width: dw, height: dh, format: surface_format,
});
}
let r3d_ok = {
let r3d = match state.urx_renderer_3d.as_mut() { Some(r) => r, None => { jobs_skipped += 1; continue; } };
let scene = match state.urx_scene_3d.as_ref() { Some(s) => s, None => { jobs_skipped += 1; continue; } };
let off = match state.urx_offscreen_3d.as_ref() { Some(o) => o, None => { jobs_skipped += 1; continue; } };
r3d.render(&device, &queue, &mut encoder, &off.view, &job.camera, scene);
true
};
if !r3d_ok { jobs_skipped += 1; continue; }
let off = state.urx_offscreen_3d.as_ref().unwrap();
encoder.copy_texture_to_texture(
wgpu::TexelCopyTextureInfo {
texture: &off.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyTextureInfo {
texture: &frame.texture,
mip_level: 0,
origin: wgpu::Origin3d { x: dx, y: dy, z: 0 },
aspect: wgpu::TextureAspect::All,
},
wgpu::Extent3d { width: dw, height: dh, depth_or_array_layers: 1 },
);
jobs_rendered += 1;
}
queue.submit([encoder.finish()]);
frame.present();
Ok(ComposedOutcome { jobs_rendered, jobs_skipped, surface_lost: false })
}
fn clear_swap_view(
encoder: &mut wgpu::CommandEncoder,
swap_view: &wgpu::TextureView,
base_color: [f32; 4],
) {
let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("compose:clear"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: swap_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: base_color[0] as f64,
g: base_color[1] as f64,
b: base_color[2] as f64,
a: base_color[3] as f64,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
fn compose_urx_cpu_into_swap(
state: &mut WindowRenderState,
_device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
swap_view: &wgpu::TextureView,
surf_w: u32,
surf_h: u32,
scene_opt: Option<uzor_urx_core::Scene>,
base_color: [f32; 4],
) {
if state.urx_cpu_backend.is_none() {
state.urx_cpu_backend = Some(uzor_urx_cpu::CpuBackend::new());
}
let need_new_pix = match &state.urx_cpu_pixmap {
None => true,
Some(p) => p.width() != surf_w || p.height() != surf_h,
};
if need_new_pix {
state.urx_cpu_pixmap = Some(uzor_urx_cpu::Pixmap::new(surf_w, surf_h));
}
let bg_rgba = [
(base_color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
(base_color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
(base_color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
(base_color[3] * 255.0).round().clamp(0.0, 255.0) as u8,
];
{
let backend = state.urx_cpu_backend.as_ref().expect("inited above");
let pixmap = state.urx_cpu_pixmap.as_mut().expect("inited above");
pixmap.fill(bg_rgba);
if let Some(scene) = scene_opt {
if let Err(e) = backend.render(&scene, pixmap) {
eprintln!("[render-hub] compose urx-cpu render error: {:?}", e);
}
}
}
let SurfaceMode::Gpu { surface, .. } = &mut state.surface else { return };
let (cw, ch, pix_ptr, pix_len) = {
let pixmap = state.urx_cpu_pixmap.as_ref().expect("inited above");
(pixmap.width(), pixmap.height(), pixmap.pixels().as_ptr(), pixmap.pixels().len())
};
if cw == surf_w && ch == surf_h && pix_len > 0 {
let pix: &[u8] = unsafe { std::slice::from_raw_parts(pix_ptr, pix_len) };
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &surface.target_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
pix,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * cw),
rows_per_image: Some(ch),
},
wgpu::Extent3d { width: cw, height: ch, depth_or_array_layers: 1 },
);
}
surface.blitter.copy(_device, encoder, &surface.target_view, swap_view);
let _ = base_color;
}
fn compose_urx_wgpu_into_swap(
state: &mut WindowRenderState,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
swap_view: &wgpu::TextureView,
surf_w: u32,
surf_h: u32,
format: wgpu::TextureFormat,
scene_opt: Option<uzor_urx_core::Scene>,
base_color: [f32; 4],
) {
if state.instanced_renderer.is_none() {
state.instanced_renderer =
Some(uzor_render_wgpu_instanced::InstancedRenderer::new(device, queue, format));
}
if state.instanced_ctx.is_none() {
state.instanced_ctx = Some(uzor_render_wgpu_instanced::InstancedRenderContext::new(
surf_w as f32, surf_h as f32, 0.0, 0.0,
));
}
if let Some(ctx) = state.instanced_ctx.as_mut() {
ctx.clear();
if let Some(scene) = scene_opt {
uzor_urx_wgpu::adapt_scene_into(&scene, ctx);
}
}
let cmds: Vec<uzor_render_wgpu_instanced::DrawCmd> = state.instanced_ctx.as_mut()
.map(|c| std::mem::take(&mut c.draw_commands))
.unwrap_or_default();
let clear = wgpu::Color {
r: base_color[0] as f64,
g: base_color[1] as f64,
b: base_color[2] as f64,
a: base_color[3] as f64,
};
if let Some(ref mut inst) = state.instanced_renderer {
inst.render(device, queue, swap_view, surf_w, surf_h, &cmds, Some(clear), None);
}
if let Some(ctx) = state.instanced_ctx.as_mut() {
let mut taken = cmds;
taken.clear();
ctx.draw_commands = taken;
}
let _ = encoder; }