feat(oakrender): render-process isolation S3 - audio over shm, per-ticket slot formats, tuning
- Audio tickets join the process backend: render_audio_batch wire message, workers mix straight into shm slots (SLOT_FORMAT_AUDIO_F32), ShmAudio payload with release semantics, crash isolation covers audio renders; playback audio uses an async 4-chunk prefetch drained on the UI tick (also fixes the sub-60fps chunk truncation bug); oversized ranges and dispatcher outages fall back to in-process inline. - Per-ticket slot formats: force_format is honored (exports request F32 slots, dropping the BGRA8 round-trip and its 8-bit quantization); segments grow on demand via worker-idle rebuild with generation handoff; the scheduler filters over-capacity tickets. - Adaptive defaults: 128-256MB/worker segment budgets drive slots per worker, batch size follows workers/slots; bench_process example measures throughput and adjacent-frame completion deltas (e.g. 4 workers: 841 fps, 4.6ms mean delta).
This commit is contained in:
@@ -57,7 +57,9 @@ use oakcommon::videoparams::VideoParams;
|
||||
use oaknode::footage::FootageBehavior;
|
||||
use oaknode::sequence::SequenceBehavior;
|
||||
use oaknode::track::{TrackBehavior, TrackListBehavior, TrackType};
|
||||
use oakrender::procpool::{bgra8_to_f32_rgba, DispatcherConfig, ProcessDispatcher, ShmFrameRef};
|
||||
use oakrender::procpool::{
|
||||
bgra8_to_f32_rgba, DispatcherConfig, ProcessDispatcher, ShmAudioRef, ShmFrameRef,
|
||||
};
|
||||
use oakrender::ticket::{
|
||||
ticket_kind, AudioTicketParams, MontageClip, TicketArena, TicketId, TicketPayload,
|
||||
TicketResult, VideoTicketParams,
|
||||
@@ -668,7 +670,17 @@ impl RenderTask {
|
||||
oakrender::eval::render_produced_frame(time, params)
|
||||
.map(TicketPayload::Video)
|
||||
});
|
||||
let arena = Arc::new(TicketArena::new(dispatcher.clone(), producer));
|
||||
// M15 S3: the private dispatcher routes audio through the
|
||||
// worker pool too; the inline dispatcher is the fallback
|
||||
// when the dispatcher refuses a job (oversized audio ranges
|
||||
// / teardown).
|
||||
let inline = oakrender::worker::InlineDispatcher::sync();
|
||||
let arena = Arc::new(TicketArena::new_with_audio_fallback(
|
||||
dispatcher.clone(),
|
||||
dispatcher.clone(),
|
||||
Some(inline),
|
||||
producer,
|
||||
));
|
||||
(arena, Some(dispatcher))
|
||||
}
|
||||
};
|
||||
@@ -762,6 +774,22 @@ impl RenderTask {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(TicketPayload::ShmAudio(audio)) => {
|
||||
// M15 S3 process backend: the audio lives in a
|
||||
// worker shm slot. Copy the samples out (the
|
||||
// encoder needs an owned f32 buffer), hand them
|
||||
// to the behavior, then release the slot.
|
||||
let samples = audio.to_audio_samples();
|
||||
if let Err(e) = behavior.audio_downloaded(task, &samples) {
|
||||
result = Err(e);
|
||||
break;
|
||||
}
|
||||
if let Some(m) = oakrender::manager::RenderManager::global() {
|
||||
m.release_audio_frame(&audio);
|
||||
} else if let Some(d) = &private_dispatch {
|
||||
d.release_audio_frame(&audio);
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
result = Err(Error::Failed(
|
||||
"Audio render ticket delivered a non-audio payload".to_string(),
|
||||
@@ -1029,11 +1057,11 @@ fn push_finished(id: TicketId, result: TicketResult, dispatch: DispatchPtr) {
|
||||
dispatch.cv.notify_all();
|
||||
}
|
||||
|
||||
/// Copy a process-backend shm frame (BGRA8 slot) out into an F32 CPU
|
||||
/// texture the encoder consumes (M15 S2). The worker converted its F32
|
||||
/// pipeline output to BGRA8 for the slot (design §3.1); the encoder
|
||||
/// declares F32 input, so the export converts back — a necessary
|
||||
/// conversion at the encoder boundary with 8-bit quantization (S2).
|
||||
/// Copy a process-backend shm frame out into an F32 CPU texture the
|
||||
/// encoder consumes (M15 S2/S3). F32 slots (a forced F32 export ticket)
|
||||
/// are read straight out — their bytes are already f32 RGBA little-endian,
|
||||
/// no conversion; BGRA8 slots (the default preview path) convert once with
|
||||
/// [`bgra8_to_f32_rgba`].
|
||||
fn shm_frame_to_texture(frame: &ShmFrameRef) -> oakrender::texture::Texture {
|
||||
let meta = &frame.meta;
|
||||
let pixels = frame
|
||||
@@ -1041,6 +1069,17 @@ fn shm_frame_to_texture(frame: &ShmFrameRef) -> oakrender::texture::Texture {
|
||||
.slot_bytes(frame.slot)
|
||||
.get(..meta.data_size.max(0) as usize)
|
||||
.unwrap_or_default();
|
||||
if meta.format == oakcore_rs::PixelFormat::F32 as i32 {
|
||||
// F32 slot: the encoder gets the pipeline samples with no round trip.
|
||||
let mut f = oakrender::texture::Frame::new();
|
||||
f.width = meta.width;
|
||||
f.height = meta.height;
|
||||
f.format = oakcore_rs::PixelFormat::F32;
|
||||
f.channels = 4;
|
||||
f.timestamp = oakcore_rs::Rational::new(meta.time_num, meta.time_den);
|
||||
f.data = pixels.to_vec();
|
||||
return oakrender::texture::Texture::wrap_frame(f);
|
||||
}
|
||||
let samples = bgra8_to_f32_rgba(pixels);
|
||||
let mut f = oakrender::texture::Frame::new();
|
||||
f.width = meta.width;
|
||||
|
||||
Reference in New Issue
Block a user