use super::{
core::render_accumulation,
types::{Accumulation, AccumulationCarry, AccumulationOutcome},
};
use crate::{
bridge::{
export_thread::{
ExportProgress,
params::{ExportParams, RemoteSelection},
remote::{
self, FinalPictureFollowUp, TransferPlan, final_picture_follow_up,
final_picture_refused, plan_export_transfer, remember_final_picture_refused,
run_final_image_request,
},
scene_snapshot::SceneSnapshot,
tonemap_png::tonemap_accumulation,
},
remote::remote_can_render,
},
settings::{LocalComputeTarget, WorkerSettings},
};
use indicatrix::{color::ColorSpace, renderer::gpu_backend::GpuBackend};
use std::sync::atomic::AtomicBool;
pub enum RenderedImage {
Rgba(Vec<u8>),
Cancelled,
Failed(String),
}
#[expect(
clippy::too_many_arguments,
reason = "the same per-image identity `render_accumulation` takes (scene, pose, \
params, remote selection, GPU backend, local compute, carry, cancel, \
progress) plus the output colour space; bundling would only move the count"
)]
pub fn render_image_rgba(
scene: &SceneSnapshot,
cam_yaw: f32,
cam_pitch: f32,
params: ExportParams,
color_space: ColorSpace,
remote: &RemoteSelection,
gpu: &GpuBackend,
local_compute: LocalComputeTarget,
carry: &mut AccumulationCarry,
cancel: &AtomicBool,
mut report_progress: impl FnMut(ExportProgress),
) -> RenderedImage {
let worker = remote.worker.as_ref();
let plan = plan_export_transfer(
remote.transfer,
remote.compute_target,
worker.is_some(),
remote_can_render(scene.env_map.as_ref(), true).is_err(),
carry.final_picture_declined || worker.is_some_and(final_picture_refused),
);
match (plan, worker) {
(TransferPlan::FinalPicture, Some(worker)) => {
let outcome = final_picture(
scene,
(cam_yaw, cam_pitch),
params,
color_space,
worker,
cancel,
&mut report_progress,
);
match final_picture_follow_up(outcome, remote.compute_target) {
FinalPictureFollowUp::Use(rgba) => return RenderedImage::Rgba(rgba),
FinalPictureFollowUp::Cancelled => return RenderedImage::Cancelled,
FinalPictureFollowUp::Fail(message) => return RenderedImage::Failed(message),
FinalPictureFollowUp::FallBackToFullData { note, remember } => {
if remember {
remember_final_picture_refused(worker);
}
carry.final_picture_declined = true;
carry.transfer_noted = true;
report_note(&mut report_progress, params.samples_per_pixel, note);
}
}
}
(TransferPlan::FullDataRefusedBefore, _) if !carry.transfer_noted => {
carry.transfer_noted = true;
report_note(
&mut report_progress,
params.samples_per_pixel,
"The remote does not support final-picture transfer; using full data.".to_string(),
);
}
_ => {}
}
full_data(
scene,
(cam_yaw, cam_pitch),
params,
color_space,
remote,
gpu,
local_compute,
carry,
cancel,
report_progress,
)
}
#[expect(
clippy::too_many_arguments,
reason = "forwards `render_image_rgba`'s own arguments unchanged"
)]
fn full_data(
scene: &SceneSnapshot,
(cam_yaw, cam_pitch): (f32, f32),
params: ExportParams,
color_space: ColorSpace,
remote: &RemoteSelection,
gpu: &GpuBackend,
local_compute: LocalComputeTarget,
carry: &mut AccumulationCarry,
cancel: &AtomicBool,
report_progress: impl FnMut(ExportProgress),
) -> RenderedImage {
match render_accumulation(
scene,
cam_yaw,
cam_pitch,
params,
remote.compute_target,
remote.worker.as_ref(),
gpu,
local_compute,
carry,
cancel,
report_progress,
) {
AccumulationOutcome::Cancelled => RenderedImage::Cancelled,
AccumulationOutcome::Failed(message) => RenderedImage::Failed(message),
AccumulationOutcome::Completed(Accumulation {
accum,
samples_per_pixel,
}) => RenderedImage::Rgba(tonemap_accumulation(
params.width,
params.height,
samples_per_pixel,
&accum,
color_space,
)),
}
}
fn final_picture(
scene: &SceneSnapshot,
(cam_yaw, cam_pitch): (f32, f32),
params: ExportParams,
color_space: ColorSpace,
worker: &WorkerSettings,
cancel: &AtomicBool,
report_progress: &mut impl FnMut(ExportProgress),
) -> remote::FinalPictureOutcome {
let ExportParams {
width,
height,
samples_per_pixel,
..
} = params;
let state = remote::scene_state_from_snapshot(scene, width, height, cam_yaw, cam_pitch);
run_final_image_request(
worker,
state,
samples_per_pixel,
color_space,
cancel,
|samples_done| {
let done = samples_done.min(samples_per_pixel);
report_progress(ExportProgress {
fraction: done as f32 / samples_per_pixel.max(1) as f32,
samples_done: done,
samples_total: samples_per_pixel,
preview: None,
note: None,
});
},
)
}
fn report_note(report_progress: &mut impl FnMut(ExportProgress), total: u32, note: String) {
report_progress(ExportProgress {
fraction: 0.0,
samples_done: 0,
samples_total: total,
preview: None,
note: Some(note),
});
}