clippy: clear the workspace errors and apply the machine fixes

- Mark the raw-pointer interop entry points unsafe with # Safety docs
  (oak-core upload/download/frame-from-pixels, oak-audio convert) and
  satisfy the existing callers (tests).
- mut_from_ref: allow with the ABI contract documented (the handle
  get_mut helpers in oak-timeline/oak-render/oak-task take the shared
  reference the C ABI passes; exclusivity is the caller's unsafe
  contract).
- Fix the eq_op in the white-balance normalization (green / green).
- Apply cargo clippy --fix across the workspace (redundant closures and
  field names, field reassignment, items after test modules, ...).
- Revert the replace_box fix in image_effect's clip_define: a
  redefinition must allocate a new box, otherwise the old clip handle
  stays valid and the HS-map replace contract (clip != clip2) breaks.
- 283 warnings remain; they are all non-machine-applicable
  (chunks_exact -> as_chunks needs a manual iter_mut, too_many_arguments,
  complex types, missing Safety docs, ...) and are tracked as the
  follow-up.
This commit is contained in:
2026-09-15 19:29:32 +08:00
parent 7e87ec135e
commit ee7ea18d94
146 changed files with 5235 additions and 4046 deletions
+34 -27
View File
@@ -42,8 +42,8 @@ use std::sync::Arc;
use oak_codec::encoder::{create_from_params, Encoder};
use oak_codec::encodingparams::EncodingParams as CodecEncodingParams;
use oak_core::videoparams::VideoParams as CommonVideoParams;
use oak_core::texture::Texture;
use oak_core::videoparams::VideoParams as CommonVideoParams;
use crate::error::{Error, Result};
use crate::nodeops::{self, NodeRef};
@@ -127,7 +127,6 @@ pub struct EncodingParams {
pub color_trc: i32,
/// AVColorSpace (SDR 709 = 1, HDR BT.2020 = 9).
pub color_space: i32,
}
impl ExportTask {
@@ -147,7 +146,13 @@ impl ExportTask {
// `it_export` run).
let video_params = nodeops::sequence_video_params(&viewer.0, viewer.1, 0)
.or_else(|| nodeops::footage_video_params(&viewer.0, viewer.1, 0));
let render = RenderTask::new(base, video_params, viewer.clone(), ForceParams::default(), None);
let render = RenderTask::new(
base,
video_params,
viewer.clone(),
ForceParams::default(),
None,
);
ExportTask {
render,
viewer_node: viewer,
@@ -231,15 +236,13 @@ impl ExportTask {
/// The project's pipeline color settings (working colorspace + the
/// delivery output spec) read off the exported node's project — the
/// export renders to the project's delivery target, not to the display.
fn delivery_color(&self) -> (
oak_core::colormath::WorkingColorSpace,
oak_core::colormath::OutputColorSpec,
fn delivery_color(
&self,
) -> (
oak_core::colormath::WorkingColorSpace,
oak_core::colormath::OutputColorSpec,
) {
let guard = self
.viewer_node
.0
.lock()
.unwrap_or_else(|e| e.into_inner());
let guard = self.viewer_node.0.lock().unwrap_or_else(|e| e.into_inner());
(guard.working_color_space(), guard.output_color_spec())
}
@@ -288,25 +291,26 @@ impl ExportTask {
let frame = match texture {
Texture::Cpu(frame) => frame.clone(),
Texture::Gpu { .. } => texture.to_frame().map_err(|e| {
Error::Failed(format!("Render frame readback for the encoder failed: {e:?}"))
Error::Failed(format!(
"Render frame readback for the encoder failed: {e:?}"
))
})?,
};
let frame = &frame;
let params = CommonVideoParams::new_basic(
frame.width,
frame.height,
oak_core::ocioutils::PixelFormat::from_code(frame.format as i32),
4,
1,
1,
0,
1,
frame.width,
frame.height,
oak_core::ocioutils::PixelFormat::from_code(frame.format as i32),
4,
1,
1,
0,
1,
);
let mut out = oak_codec::frame::Frame::with_params(params);
out.set_timestamp(frame.timestamp);
out.allocate().map_err(|e| {
Error::Failed(format!("Failed to allocate encoder frame: {e:?}"))
})?;
out.allocate()
.map_err(|e| Error::Failed(format!("Failed to allocate encoder frame: {e:?}")))?;
let dst_stride = out.linesize_bytes() as usize;
let Some(dst) = out.data_mut() else {
return Err(Error::Failed(
@@ -386,7 +390,7 @@ impl TaskBehavior for ExportTask {
result?;
// Flush the encoder and surface any trailing error.
if let Err(_) = encoder.flush() {
if encoder.flush().is_err() {
// Fall through to the error read below (the flush error is
// surfaced through `get_error()` like the C ABI did).
}
@@ -411,7 +415,7 @@ impl RenderTaskBehavior for ExportTask {
// project's delivery colorspace before encoding. (No-op in the
// legacy sRGB working space.)
self.apply_output_node(&mut codec_frame);
if let Err(_) = encoder.write_video(&codec_frame) {
if encoder.write_video(&codec_frame).is_err() {
let err = encoder.get_error();
task.set_error(&err);
return Err(Error::Failed("Failed to write frame".to_string()));
@@ -433,7 +437,10 @@ impl RenderTaskBehavior for ExportTask {
} else {
0
};
if let Err(_) = encoder.write_audio(&samples.samples, frame_count as i32) {
if encoder
.write_audio(&samples.samples, frame_count as i32)
.is_err()
{
let err = encoder.get_error();
task.set_error(&err);
return Err(Error::Failed("Failed to write audio".to_string()));
@@ -450,7 +457,7 @@ impl RenderTaskBehavior for ExportTask {
};
// The simplified path does not carry the subtitle block's in/out
// times; write with 0.0/0.0 (the encoder default interval).
if let Err(_) = encoder.write_subtitle(text, 0.0, 0.0) {
if encoder.write_subtitle(text, 0.0, 0.0).is_err() {
let err = encoder.get_error();
task.set_error(&err);
return Err(Error::Failed("Failed to write subtitle".to_string()));
+5
View File
@@ -101,6 +101,11 @@ pub unsafe fn get<T: 'static>(h: &CHandle) -> Option<&T> {
/// # Safety
/// `T` must be the boxed type, and the handle must not be concurrently
/// shared mutably.
// The shared reference is the ABI input; exclusivity is the caller's
// `unsafe` contract above, so the lint's usual aliasing concern is
// discharged by the caller, not by `&mut CHandle` (which the C ABI does
// not pass).
#[allow(clippy::mut_from_ref)]
pub unsafe fn get_mut<T: 'static>(h: &CHandle) -> Option<&mut T> {
if h.ctx.is_null() {
return None;
+1 -3
View File
@@ -194,9 +194,7 @@ impl TaskManager {
/// Index of the task with the given address.
pub fn find_index(&self, ptr: *const Task) -> Option<usize> {
self.tasks
.iter()
.position(|t| t.task() as *const Task == ptr)
self.tasks.iter().position(|t| std::ptr::eq(t.task(), ptr))
}
/// Cancel the task with the given address (no-op when absent).
+41 -14
View File
@@ -199,8 +199,9 @@ impl ProxyTask {
/// 探测本机可用的硬件 H.264 编码器(每进程每 ffmpeg 路径缓存一次:
/// `ffmpeg -hide_banner -encoders` 的输出里按平台优先级找)。
pub fn probe_hw_encoder(ffmpeg_path: &str) -> HwEncoder {
static CACHE: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<String, HwEncoder>>> =
std::sync::OnceLock::new();
static CACHE: std::sync::OnceLock<
std::sync::Mutex<std::collections::HashMap<String, HwEncoder>>,
> = std::sync::OnceLock::new();
let cache = CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
let mut cache = cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(enc) = cache.get(ffmpeg_path) {
@@ -424,13 +425,14 @@ impl TaskBehavior for ProxyTask {
// Create the output directory if needed.
if let Some(parent) = std::path::Path::new(&self.output_filename).parent() {
if !parent.as_os_str().is_empty() && !parent.exists() {
if std::fs::create_dir_all(parent).is_err() {
task.set_error("Failed to create proxy output directory");
return Err(Error::Failed(
"Failed to create proxy output directory".to_string(),
));
}
if !parent.as_os_str().is_empty()
&& !parent.exists()
&& std::fs::create_dir_all(parent).is_err()
{
task.set_error("Failed to create proxy output directory");
return Err(Error::Failed(
"Failed to create proxy output directory".to_string(),
));
}
}
@@ -583,7 +585,12 @@ mod tests {
#[test]
fn transcode_arguments_software_keeps_parity_body() {
let args = ProxyTask::build_transcode_arguments(
"/src.mov", 0, &params(), "/dst.mp4", HwEncoder::Software, 4,
"/src.mov",
0,
&params(),
"/dst.mp4",
HwEncoder::Software,
4,
);
assert!(args.windows(2).any(|w| w == ["-hwaccel", "auto"]));
assert!(args.windows(2).any(|w| w == ["-threads", "4"]));
@@ -600,27 +607,47 @@ mod tests {
#[test]
fn transcode_arguments_hw_swaps_encoder() {
let vt = ProxyTask::build_transcode_arguments(
"/src.mov", 0, &params(), "/dst.mp4", HwEncoder::VideoToolbox, 4,
"/src.mov",
0,
&params(),
"/dst.mp4",
HwEncoder::VideoToolbox,
4,
);
assert!(vt.windows(2).any(|w| w == ["-c:v", "h264_videotoolbox"]));
assert!(vt.windows(2).any(|w| w == ["-q:v", "23"]));
assert!(!vt.iter().any(|a| a == "libx264" || a == "-crf"));
let nv = ProxyTask::build_transcode_arguments(
"/src.mov", 0, &params(), "/dst.mp4", HwEncoder::Nvenc, 4,
"/src.mov",
0,
&params(),
"/dst.mp4",
HwEncoder::Nvenc,
4,
);
assert!(nv.windows(2).any(|w| w == ["-c:v", "h264_nvenc"]));
assert!(nv.windows(2).any(|w| w == ["-preset", "p3"]));
assert!(nv.windows(2).any(|w| w == ["-cq", "23"]));
let qsv = ProxyTask::build_transcode_arguments(
"/src.mov", 0, &params(), "/dst.mp4", HwEncoder::Qsv, 4,
"/src.mov",
0,
&params(),
"/dst.mp4",
HwEncoder::Qsv,
4,
);
assert!(qsv.windows(2).any(|w| w == ["-c:v", "h264_qsv"]));
assert!(qsv.windows(2).any(|w| w == ["-global_quality", "23"]));
let amf = ProxyTask::build_transcode_arguments(
"/src.mov", 0, &params(), "/dst.mp4", HwEncoder::Amf, 4,
"/src.mov",
0,
&params(),
"/dst.mp4",
HwEncoder::Amf,
4,
);
assert!(amf.windows(2).any(|w| w == ["-c:v", "h264_amf"]));
assert!(amf.windows(2).any(|w| w == ["-quality", "speed"]));
+60 -48
View File
@@ -57,19 +57,15 @@ use oak_core::videoparams::VideoParams;
use oak_node::footage::FootageBehavior;
use oak_node::sequence::SequenceBehavior;
use oak_node::track::{TrackBehavior, TrackListBehavior, TrackType};
use oak_render::procpool::{
bgra8_to_f32_rgba, DispatcherConfig, ProcessDispatcher, ShmFrameRef,
};
use oak_render::procpool::{bgra8_to_f32_rgba, DispatcherConfig, ProcessDispatcher, ShmFrameRef};
use oak_render::ticket::{
ticket_kind, AudioTicketParams, MontageClip, MontageEffect, TicketArena, TicketId, TicketPayload,
TicketResult, VideoTicketParams,
ticket_kind, AudioTicketParams, MontageClip, MontageEffect, TicketArena, TicketId,
TicketPayload, TicketResult, VideoTicketParams,
};
use oak_render::worker::JobDispatch;
use crate::error::{Error, Result};
use crate::nodeops::{
find_input_footage, pixel_format_from_code, NodeRef, ProjectRef,
};
use crate::nodeops::{find_input_footage, pixel_format_from_code, NodeRef, ProjectRef};
use crate::task::Task;
use oak_core::{Rational, TimeRange};
@@ -112,12 +108,16 @@ pub struct ForceParams {
pub trait RenderTaskBehavior {
/// Called for each rendered video frame.
fn frame_downloaded(
&mut self,
task: &mut Task,
frame: &oak_core::texture::Texture,
&mut self,
task: &mut Task,
frame: &oak_core::texture::Texture,
) -> Result<()>;
/// Called for each rendered audio buffer.
fn audio_downloaded(&mut self, task: &mut Task, samples: &oak_render::ticket::AudioSamples) -> Result<()>;
fn audio_downloaded(
&mut self,
task: &mut Task,
samples: &oak_render::ticket::AudioSamples,
) -> Result<()>;
/// Called to encode a subtitle.
fn encode_subtitle(&mut self, task: &mut Task, text: &str) -> Result<()>;
}
@@ -230,7 +230,10 @@ impl RenderTask {
/// The forced output size from [`ForceParams`], or `None`.
fn force_size(&self) -> Option<(i32, i32)> {
if self.force_params.force_width > 0 && self.force_params.force_height > 0 {
Some((self.force_params.force_width, self.force_params.force_height))
Some((
self.force_params.force_width,
self.force_params.force_height,
))
} else {
None
}
@@ -253,7 +256,10 @@ impl RenderTask {
/// traverser's bypass pushes the effect input through unchanged).
/// Parameters are the non-hidden, non-connection data inputs at
/// their standard (non-keyframed) values.
fn clip_effects(graph: &oak_node::graph::Graph, host: oak_node::id::NodeId) -> Vec<MontageEffect> {
fn clip_effects(
graph: &oak_node::graph::Graph,
host: oak_node::id::NodeId,
) -> Vec<MontageEffect> {
// Walk the chain: from the host's effect input upstream until an
// unconnected input or a node without an effect input; a `seen`
// guard protects against malformed cycles.
@@ -333,10 +339,12 @@ impl RenderTask {
/// (bottom-most track first so the topmost track — the highest-numbered
/// one, the list's last — composites last;
/// `// CPP-PARITY: M12 P0 montage contract`).
fn video_montage(project: &ProjectRef, sequence: oak_node::id::NodeId, time: Rational) -> Vec<MontageClip> {
let guard = project
.lock()
.unwrap_or_else(|e| e.into_inner());
fn video_montage(
project: &ProjectRef,
sequence: oak_node::id::NodeId,
time: Rational,
) -> Vec<MontageClip> {
let guard = project.lock().unwrap_or_else(|e| e.into_inner());
let Some(entry) = guard.graph.get(sequence) else {
return Vec::new();
};
@@ -374,8 +382,7 @@ impl RenderTask {
continue;
};
for block_id in &track.blocks {
let Some(core) = crate::nodeops::block_core_of(&guard.graph, *block_id)
else {
let Some(core) = crate::nodeops::block_core_of(&guard.graph, *block_id) else {
continue;
};
if time < core.in_() || time >= core.out() {
@@ -411,10 +418,12 @@ impl RenderTask {
/// Flatten the audio tracks of `sequence` into an audio montage
/// (track order is irrelevant — the mixer accumulates gains).
fn audio_montage(project: &ProjectRef, sequence: oak_node::id::NodeId, time: Rational) -> Vec<MontageClip> {
let guard = project
.lock()
.unwrap_or_else(|e| e.into_inner());
fn audio_montage(
project: &ProjectRef,
sequence: oak_node::id::NodeId,
time: Rational,
) -> Vec<MontageClip> {
let guard = project.lock().unwrap_or_else(|e| e.into_inner());
let Some(entry) = guard.graph.get(sequence) else {
return Vec::new();
};
@@ -452,8 +461,7 @@ impl RenderTask {
continue;
};
for block_id in &track.blocks {
let Some(core) = crate::nodeops::block_core_of(&guard.graph, *block_id)
else {
let Some(core) = crate::nodeops::block_core_of(&guard.graph, *block_id) else {
continue;
};
if time < core.in_() || time >= core.out() {
@@ -569,7 +577,8 @@ impl RenderTask {
/// single range; the C++ submits one ticket per audio range).
fn build_audio_ticket(&self, range: TimeRange) -> Result<AudioTicketParams> {
let (project, viewer_id) = &self.viewer;
let (sample_rate, channel_layout) = crate::nodeops::sequence_audio_params(project, *viewer_id);
let (sample_rate, channel_layout) =
crate::nodeops::sequence_audio_params(project, *viewer_id);
let montage = Self::audio_montage(project, *viewer_id, range.in_());
Ok(AudioTicketParams {
viewer: viewer_id.identity(),
@@ -595,9 +604,13 @@ impl RenderTask {
let params = self.build_video_ticket(time)?;
let id = arena.next_id();
let dispatch_ptr = DispatchPtr(dispatch);
arena.submit_video_background_with_id(id, params, Box::new(move |result| {
push_finished(id, result, dispatch_ptr);
}));
arena.submit_video_background_with_id(
id,
params,
Box::new(move |result| {
push_finished(id, result, dispatch_ptr);
}),
);
Ok(id)
}
@@ -612,9 +625,13 @@ impl RenderTask {
let params = self.build_audio_ticket(range)?;
let id = arena.next_id();
let dispatch_ptr = DispatchPtr(dispatch);
arena.submit_audio_with_id(id, params, Box::new(move |result| {
push_finished(id, result, dispatch_ptr);
}));
arena.submit_audio_with_id(
id,
params,
Box::new(move |result| {
push_finished(id, result, dispatch_ptr);
}),
);
Ok(id)
}
@@ -638,10 +655,7 @@ impl RenderTask {
match self.submit_video_ticket(arena, time, dispatch) {
Ok(id) => {
in_flight.push(id);
ticket_keys.insert(
id,
(TICKET_VIDEO, time.numerator(), time.denominator()),
);
ticket_keys.insert(id, (TICKET_VIDEO, time.numerator(), time.denominator()));
Ok(())
}
Err(e) => {
@@ -772,8 +786,7 @@ impl RenderTask {
.start()
.map_err(|e| Error::Failed(format!("render worker pool start: {e}")))?;
let producer: oak_render::ticket::Producer = Arc::new(|time, params| {
oak_render::eval::render_produced_frame(time, params)
.map(TicketPayload::Video)
oak_render::eval::render_produced_frame(time, params).map(TicketPayload::Video)
});
// M15 S3: the private dispatcher routes audio through the
// worker pool too; the inline dispatcher is the fallback
@@ -910,9 +923,8 @@ impl RenderTask {
break;
}
Err(e) => {
result = Err(Error::Failed(format!(
"Audio render ticket failed: {e:?}"
)));
result =
Err(Error::Failed(format!("Audio render ticket failed: {e:?}")));
break;
}
}
@@ -956,9 +968,8 @@ impl RenderTask {
break;
}
Err(e) => {
result = Err(Error::Failed(format!(
"Frame render ticket failed: {e:?}"
)));
result =
Err(Error::Failed(format!("Frame render ticket failed: {e:?}")));
break;
}
}
@@ -1020,8 +1031,7 @@ impl RenderTask {
.finished
.lock()
.unwrap_or_else(|e| e.into_inner())
.is_empty()
&& dispatch_ref.running.load(Ordering::SeqCst) == 0
.is_empty() && dispatch_ref.running.load(Ordering::SeqCst) == 0
{
// Every ticket finished and its queue copy was consumed.
break;
@@ -1202,7 +1212,9 @@ fn shm_frame_to_texture(frame: &ShmFrameRef) -> oak_core::texture::Texture {
f.channels = 4;
f.timestamp = oak_core::Rational::new(meta.time_num, meta.time_den);
f.data = samples
.chunks_exact(4)
.as_chunks::<4>()
.0
.iter()
.flat_map(|px| {
let mut bytes = [0u8; 16];
for (i, v) in px.iter().enumerate() {