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:
+162
-95
@@ -40,9 +40,9 @@ use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use crate::PixelFormat;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::frame::VideoParamsPod;
|
||||
use crate::texture::{Frame, Texture};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
/// Backend selection preference (mapped onto wgpu backends).
|
||||
///
|
||||
@@ -183,9 +183,8 @@ impl DisplayBitDepth {
|
||||
/// Read the user's persisted choice through the oak_core config C ABI
|
||||
/// ("DisplayBitDepth").
|
||||
pub fn from_user_config() -> DisplayBitDepth {
|
||||
let configured =
|
||||
crate::commonutil::config_get_string(None, CONFIG_KEY_DISPLAY_BIT_DEPTH)
|
||||
.unwrap_or_default();
|
||||
let configured = crate::commonutil::config_get_string(None, CONFIG_KEY_DISPLAY_BIT_DEPTH)
|
||||
.unwrap_or_default();
|
||||
DisplayBitDepth::from_config_string(&configured)
|
||||
}
|
||||
|
||||
@@ -243,10 +242,10 @@ pub trait GpuContextLike: Send + Sync {
|
||||
fn download(&self, token: u64) -> Result<Frame>;
|
||||
/// Blit texture → texture (plain copy; color-managed deferred).
|
||||
fn blit(
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Concrete-context downcast hook (M2). The present path uses it to
|
||||
@@ -432,7 +431,9 @@ impl GpuContext {
|
||||
// (widely available on desktop GPUs); without it effect
|
||||
// shaders sample nearest — a quality degradation, not a
|
||||
// failure (logged once by the shaderfx runner).
|
||||
let filterable = adapter.features().contains(wgpu::Features::FLOAT32_FILTERABLE);
|
||||
let filterable = adapter
|
||||
.features()
|
||||
.contains(wgpu::Features::FLOAT32_FILTERABLE);
|
||||
let mut required_features = wgpu::Features::empty();
|
||||
if filterable {
|
||||
required_features |= wgpu::Features::FLOAT32_FILTERABLE;
|
||||
@@ -789,12 +790,7 @@ impl GpuContext {
|
||||
/// nodes (`ColorTransformJob`, M2). The LUT texture is uploaded once
|
||||
/// per key (the caller passes a stable processor cache id); applying
|
||||
/// it is GPU→GPU.
|
||||
pub fn apply_color_lut(
|
||||
&self,
|
||||
src: u64,
|
||||
key: &str,
|
||||
lut: &crate::lut::Lut3d,
|
||||
) -> Result<u64> {
|
||||
pub fn apply_color_lut(&self, src: u64, key: &str, lut: &crate::lut::Lut3d) -> Result<u64> {
|
||||
let token = {
|
||||
let mut cache = lock(&self.color_luts);
|
||||
if let Some((_, token)) = cache.iter().find(|(k, _)| k == key) {
|
||||
@@ -873,7 +869,8 @@ impl GpuContext {
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
self.queue.write_buffer(&uniform, 0, &f32_uniform_bytes(¶ms));
|
||||
self.queue
|
||||
.write_buffer(&uniform, 0, &f32_uniform_bytes(¶ms));
|
||||
let src_view = src_tex
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
@@ -1023,12 +1020,11 @@ impl GpuContext {
|
||||
cache: None,
|
||||
});
|
||||
if let Some(err) = pollster_block_on(scope.pop()) {
|
||||
return Err(Error::Failed(format!("present pipeline validation failed: {err}")));
|
||||
return Err(Error::Failed(format!(
|
||||
"present pipeline validation failed: {err}"
|
||||
)));
|
||||
}
|
||||
let program = PresentPipeline {
|
||||
pipeline,
|
||||
layout,
|
||||
};
|
||||
let program = PresentPipeline { pipeline, layout };
|
||||
cache.push((format, program.clone()));
|
||||
Ok(program)
|
||||
}
|
||||
@@ -1090,7 +1086,8 @@ impl GpuContext {
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
self.queue.write_buffer(&uniform, 0, &f32_uniform_bytes(¶ms));
|
||||
self.queue
|
||||
.write_buffer(&uniform, 0, &f32_uniform_bytes(¶ms));
|
||||
let y_view = y_tex
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
@@ -1239,12 +1236,11 @@ impl GpuContext {
|
||||
cache: None,
|
||||
});
|
||||
if let Some(err) = pollster_block_on(scope.pop()) {
|
||||
return Err(Error::Failed(format!("YUV pipeline validation failed: {err}")));
|
||||
return Err(Error::Failed(format!(
|
||||
"YUV pipeline validation failed: {err}"
|
||||
)));
|
||||
}
|
||||
let program = PresentPipeline {
|
||||
pipeline,
|
||||
layout,
|
||||
};
|
||||
let program = PresentPipeline { pipeline, layout };
|
||||
*cache = Some(program.clone());
|
||||
Ok(program)
|
||||
}
|
||||
@@ -1438,10 +1434,10 @@ impl GpuContext {
|
||||
/// of this pass (see README §4), so a `Some` processor returns
|
||||
/// `Error::Failed` and the plain-copy WGSL pipeline is used for `None`.
|
||||
pub fn blit(
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()> {
|
||||
if processor.is_some() {
|
||||
return Err(Error::Failed(
|
||||
@@ -1767,7 +1763,9 @@ impl GpuContext {
|
||||
cache: None,
|
||||
});
|
||||
if let Some(err) = pollster_block_on(scope.pop()) {
|
||||
return Err(Error::Failed(format!("effect pipeline validation failed: {err}")));
|
||||
return Err(Error::Failed(format!(
|
||||
"effect pipeline validation failed: {err}"
|
||||
)));
|
||||
}
|
||||
|
||||
let program = Arc::new(ShaderProgram {
|
||||
@@ -2121,7 +2119,6 @@ fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut {
|
||||
}
|
||||
"#;
|
||||
|
||||
|
||||
impl GpuContextLike for GpuContext {
|
||||
fn kind(&self) -> BackendKind {
|
||||
self.kind()
|
||||
@@ -2140,10 +2137,10 @@ impl GpuContextLike for GpuContext {
|
||||
}
|
||||
|
||||
fn blit(
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: u64,
|
||||
dst: u64,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()> {
|
||||
self.blit(src, dst, processor)
|
||||
}
|
||||
@@ -2197,11 +2194,11 @@ fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
// Minimal futures executor (wgpu brings futures-core transitively; a tiny
|
||||
// block_on is enough for the immediately-ready adapter/device futures).
|
||||
mod futures_executor {
|
||||
use std::future::Future;
|
||||
use std::pin::pin;
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
use std::future::Future;
|
||||
use std::pin::pin;
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
fn no_op(_: *const ()) {}
|
||||
fn clone(_: *const ()) -> RawWaker {
|
||||
noop_raw_waker()
|
||||
@@ -2353,7 +2350,13 @@ impl DisplayRenderer {
|
||||
}
|
||||
|
||||
/// Upload pixels into a texture (GPU: backend upload; CPU: buffer copy).
|
||||
pub fn upload_texture(
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `pixels` must point to at least `linesize * height` readable bytes
|
||||
/// of the texture's pixel format, and `linesize` must equal the
|
||||
/// frame's line size.
|
||||
pub unsafe fn upload_texture(
|
||||
&self,
|
||||
texture: &mut Texture,
|
||||
pixels: *const u8,
|
||||
@@ -2362,7 +2365,7 @@ impl DisplayRenderer {
|
||||
let size = texture.size();
|
||||
match texture {
|
||||
Texture::Gpu { token, ctx, .. } => {
|
||||
let frame = frame_from_pixels_for_upload(size, pixels, linesize)?;
|
||||
let frame = unsafe { frame_from_pixels_for_upload(size, pixels, linesize) }?;
|
||||
ctx.upload(*token, &frame)
|
||||
}
|
||||
Texture::Cpu(frame) => {
|
||||
@@ -2380,7 +2383,17 @@ impl DisplayRenderer {
|
||||
}
|
||||
|
||||
/// Download a texture's pixels into `dst` (with `linesize` stride).
|
||||
pub fn download_texture(&self, texture: &Texture, dst: *mut u8, linesize: usize) -> Result<()> {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `dst` must point to at least `linesize * height` writable bytes,
|
||||
/// and `linesize` must equal the frame's line size.
|
||||
pub unsafe fn download_texture(
|
||||
&self,
|
||||
texture: &Texture,
|
||||
dst: *mut u8,
|
||||
linesize: usize,
|
||||
) -> Result<()> {
|
||||
let frame = texture.to_frame()?;
|
||||
let stride = frame.linesize_bytes();
|
||||
if linesize != stride {
|
||||
@@ -2397,10 +2410,10 @@ impl DisplayRenderer {
|
||||
/// GPU path: plain-copy WGSL blit; a color processor on the GPU path is
|
||||
/// deferred (`Error::Failed`, see [`GpuContext::blit`]).
|
||||
pub fn blit_color_managed(
|
||||
&self,
|
||||
src: Option<&Texture>,
|
||||
dst: &mut Texture,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
&self,
|
||||
src: Option<&Texture>,
|
||||
dst: &mut Texture,
|
||||
processor: Option<&crate::color::ColorProcessor>,
|
||||
) -> Result<()> {
|
||||
match (src, dst) {
|
||||
(
|
||||
@@ -2430,7 +2443,12 @@ impl DisplayRenderer {
|
||||
|
||||
/// Cross-backend texture download by id (GPU registry only; CPU
|
||||
/// textures have no id registry — documented).
|
||||
pub fn download_from_texture(
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `dst` must point to at least `linesize * height` writable bytes,
|
||||
/// and `linesize` must equal the F32 line size.
|
||||
pub unsafe fn download_from_texture(
|
||||
&self,
|
||||
texture_id: i32,
|
||||
params: &VideoParamsPod,
|
||||
@@ -2464,7 +2482,12 @@ pub fn texture_id_of(t: &Texture) -> i32 {
|
||||
}
|
||||
|
||||
/// Build an F32 frame from raw pixels (for GPU upload).
|
||||
pub fn frame_from_pixels_for_upload(
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `pixels` must point to `linesize * height` readable bytes of F32 RGBA
|
||||
/// data (a null pointer is rejected with `Error::Invalid`).
|
||||
pub unsafe fn frame_from_pixels_for_upload(
|
||||
size: (i32, i32),
|
||||
pixels: *const u8,
|
||||
linesize: usize,
|
||||
@@ -2489,14 +2512,14 @@ pub fn frame_from_pixels_for_upload(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
/// Serializes the tests that assert on the process-global GPU
|
||||
/// transfer counters (and install a display LUT): parallel tests would
|
||||
/// otherwise see each other's transfers.
|
||||
static GPU_COUNTER_LOCK: Mutex<()> = Mutex::new(());
|
||||
/// Serializes the tests that assert on the process-global GPU
|
||||
/// transfer counters (and install a display LUT): parallel tests would
|
||||
/// otherwise see each other's transfers.
|
||||
static GPU_COUNTER_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn backend_string_roundtrip() {
|
||||
for (s, kind) in [
|
||||
("auto", BackendKind::Auto),
|
||||
@@ -2534,13 +2557,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn display_bit_depth_string_roundtrip() {
|
||||
assert_eq!(DisplayBitDepth::from_config_string("8"), DisplayBitDepth::Bit8);
|
||||
assert_eq!(
|
||||
DisplayBitDepth::from_config_string("8"),
|
||||
DisplayBitDepth::Bit8
|
||||
);
|
||||
assert_eq!(
|
||||
DisplayBitDepth::from_config_string("10"),
|
||||
DisplayBitDepth::Bit10
|
||||
);
|
||||
// Unknown / missing → the 10-bit default.
|
||||
assert_eq!(DisplayBitDepth::from_config_string(""), DisplayBitDepth::Bit10);
|
||||
assert_eq!(
|
||||
DisplayBitDepth::from_config_string(""),
|
||||
DisplayBitDepth::Bit10
|
||||
);
|
||||
assert_eq!(
|
||||
DisplayBitDepth::from_config_string("bogus"),
|
||||
DisplayBitDepth::Bit10
|
||||
@@ -2560,12 +2589,18 @@ mod tests {
|
||||
// 10-bit → the 10-bit (RGB10A2) swapchain format, HDR float fallback.
|
||||
assert_eq!(
|
||||
DisplayBitDepth::Bit10.present_formats(),
|
||||
&[wgpu::TextureFormat::Rgb10a2Unorm, wgpu::TextureFormat::Rgba16Float]
|
||||
&[
|
||||
wgpu::TextureFormat::Rgb10a2Unorm,
|
||||
wgpu::TextureFormat::Rgba16Float
|
||||
]
|
||||
);
|
||||
// 8-bit → the engine's current default preference list.
|
||||
assert_eq!(
|
||||
DisplayBitDepth::Bit8.present_formats(),
|
||||
&[wgpu::TextureFormat::Bgra8Unorm, wgpu::TextureFormat::Rgba8Unorm]
|
||||
&[
|
||||
wgpu::TextureFormat::Bgra8Unorm,
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2590,8 +2625,7 @@ mod tests {
|
||||
assert!(!GpuContext::shared().unwrap().is_adopted());
|
||||
|
||||
let (device, queue) = base.device_queue();
|
||||
let adopted =
|
||||
GpuContext::adopt(device, queue, BackendKind::Auto);
|
||||
let adopted = GpuContext::adopt(device, queue, BackendKind::Auto);
|
||||
assert!(
|
||||
GpuContext::install_shared(Some(adopted.clone())),
|
||||
"an unused engine context is replaceable by the UI device"
|
||||
@@ -2605,8 +2639,7 @@ mod tests {
|
||||
// pulled out from under in-flight work.
|
||||
let _texture = adopted.create_texture(2, 2).unwrap();
|
||||
let (device, queue) = adopted.device_queue();
|
||||
let second =
|
||||
GpuContext::adopt(device, queue, BackendKind::Auto);
|
||||
let second = GpuContext::adopt(device, queue, BackendKind::Auto);
|
||||
assert!(
|
||||
!GpuContext::install_shared(Some(second)),
|
||||
"a used context is not replaceable"
|
||||
@@ -2728,8 +2761,7 @@ mod tests {
|
||||
for i in 0..(w * h) as usize {
|
||||
let px = sample(i);
|
||||
for (c, v) in px.iter().enumerate() {
|
||||
frame.data[i * 16 + c * 4..i * 16 + c * 4 + 4]
|
||||
.copy_from_slice(&v.to_le_bytes());
|
||||
frame.data[i * 16 + c * 4..i * 16 + c * 4 + 4].copy_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
}
|
||||
ctx.upload(src, &frame).unwrap();
|
||||
@@ -2859,10 +2891,10 @@ mod tests {
|
||||
|
||||
let mut expected = vec![0.0f32; w * h * 4];
|
||||
let mut compare = |ctx: &GpuContext,
|
||||
tag: &str,
|
||||
matrix: crate::colormath::YuvMatrix,
|
||||
transform: YuvTransform,
|
||||
full_range: bool| {
|
||||
tag: &str,
|
||||
matrix: crate::colormath::YuvMatrix,
|
||||
transform: YuvTransform,
|
||||
full_range: bool| {
|
||||
crate::colormath::yuv444p16_to_rgb_f32(
|
||||
&y_code_plane,
|
||||
w * 2,
|
||||
@@ -2893,12 +2925,48 @@ mod tests {
|
||||
}
|
||||
};
|
||||
use crate::colormath::YuvMatrix;
|
||||
compare(&ctx, "bt601 limited", YuvMatrix::Bt601, YuvTransform::bt601_limited(), false);
|
||||
compare(&ctx, "bt601 full", YuvMatrix::Bt601, YuvTransform::bt601_full(), true);
|
||||
compare(&ctx, "bt709 limited", YuvMatrix::Bt709, YuvTransform::bt709_limited(), false);
|
||||
compare(&ctx, "bt709 full", YuvMatrix::Bt709, YuvTransform::bt709_full(), true);
|
||||
compare(&ctx, "bt2020 limited", YuvMatrix::Bt2020, YuvTransform::bt2020_limited(), false);
|
||||
compare(&ctx, "bt2020 full", YuvMatrix::Bt2020, YuvTransform::bt2020_full(), true);
|
||||
compare(
|
||||
&ctx,
|
||||
"bt601 limited",
|
||||
YuvMatrix::Bt601,
|
||||
YuvTransform::bt601_limited(),
|
||||
false,
|
||||
);
|
||||
compare(
|
||||
&ctx,
|
||||
"bt601 full",
|
||||
YuvMatrix::Bt601,
|
||||
YuvTransform::bt601_full(),
|
||||
true,
|
||||
);
|
||||
compare(
|
||||
&ctx,
|
||||
"bt709 limited",
|
||||
YuvMatrix::Bt709,
|
||||
YuvTransform::bt709_limited(),
|
||||
false,
|
||||
);
|
||||
compare(
|
||||
&ctx,
|
||||
"bt709 full",
|
||||
YuvMatrix::Bt709,
|
||||
YuvTransform::bt709_full(),
|
||||
true,
|
||||
);
|
||||
compare(
|
||||
&ctx,
|
||||
"bt2020 limited",
|
||||
YuvMatrix::Bt2020,
|
||||
YuvTransform::bt2020_limited(),
|
||||
false,
|
||||
);
|
||||
compare(
|
||||
&ctx,
|
||||
"bt2020 full",
|
||||
YuvMatrix::Bt2020,
|
||||
YuvTransform::bt2020_full(),
|
||||
true,
|
||||
);
|
||||
|
||||
ctx.destroy_texture(y);
|
||||
ctx.destroy_texture(u);
|
||||
@@ -2914,9 +2982,7 @@ mod tests {
|
||||
let Some(ctx) = any_gpu() else {
|
||||
return;
|
||||
};
|
||||
let lut = crate::lut::Lut3d::build(9, [0.0; 3], [1.0; 3], |c| {
|
||||
[c[1], c[2], c[0]]
|
||||
});
|
||||
let lut = crate::lut::Lut3d::build(9, [0.0; 3], [1.0; 3], |c| [c[1], c[2], c[0]]);
|
||||
let (w, h) = (4, 2);
|
||||
let src = ctx.create_texture(w, h).unwrap();
|
||||
let mut frame = Frame::new();
|
||||
@@ -2933,8 +2999,7 @@ mod tests {
|
||||
1.0,
|
||||
];
|
||||
for (c, v) in px.iter().enumerate() {
|
||||
frame.data[i * 16 + c * 4..i * 16 + c * 4 + 4]
|
||||
.copy_from_slice(&v.to_le_bytes());
|
||||
frame.data[i * 16 + c * 4..i * 16 + c * 4 + 4].copy_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
}
|
||||
ctx.upload(src, &frame).unwrap();
|
||||
@@ -2960,7 +3025,9 @@ mod tests {
|
||||
let want = lut.eval(src_px);
|
||||
for c in 0..3 {
|
||||
let g = f32::from_le_bytes(
|
||||
got.data[i * 16 + c * 4..i * 16 + c * 4 + 4].try_into().unwrap(),
|
||||
got.data[i * 16 + c * 4..i * 16 + c * 4 + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
assert!(
|
||||
(g - want[c]).abs() < 1e-4,
|
||||
@@ -3008,7 +3075,7 @@ mod tests {
|
||||
#[test]
|
||||
fn display_renderer_rejects_foreign_gl_context() {
|
||||
let mut r = DisplayRenderer::new(BackendKind::Gl);
|
||||
let fake = 0x1 as *mut std::ffi::c_void;
|
||||
let fake = std::ptr::dangling_mut::<std::ffi::c_void>();
|
||||
assert_eq!(r.init(fake).unwrap_err().code(), Error::Invalid.code());
|
||||
}
|
||||
|
||||
@@ -3039,20 +3106,20 @@ mod tests {
|
||||
frame.allocate();
|
||||
frame.data[0] = 0x77;
|
||||
let mut upload_target = r3.create_texture(&pod, None).unwrap();
|
||||
r3.upload_texture(
|
||||
&mut upload_target,
|
||||
frame.data.as_ptr(),
|
||||
frame.linesize_bytes(),
|
||||
)
|
||||
unsafe {
|
||||
r3.upload_texture(
|
||||
&mut upload_target,
|
||||
frame.data.as_ptr(),
|
||||
frame.linesize_bytes(),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
let mut buf = vec![0u8; frame.linesize_bytes() * 4];
|
||||
r3.download_texture(&upload_target, buf.as_mut_ptr(), frame.linesize_bytes())
|
||||
unsafe { r3.download_texture(&upload_target, buf.as_mut_ptr(), frame.linesize_bytes()) }
|
||||
.unwrap();
|
||||
assert_eq!(buf[0], 0x77);
|
||||
// Stride mismatch rejected.
|
||||
assert!(r3
|
||||
.upload_texture(&mut upload_target, frame.data.as_ptr(), 1)
|
||||
.is_err());
|
||||
assert!(unsafe { r3.upload_texture(&mut upload_target, frame.data.as_ptr(), 1) }.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3077,9 +3144,9 @@ mod tests {
|
||||
assert_eq!(df.data[4], 0x22);
|
||||
// Pass-through processor is a no-op.
|
||||
r.blit_color_managed(
|
||||
Some(&src),
|
||||
&mut dst,
|
||||
Some(&crate::color::ColorProcessor::pass_through()),
|
||||
Some(&src),
|
||||
&mut dst,
|
||||
Some(&crate::color::ColorProcessor::pass_through()),
|
||||
)
|
||||
.unwrap();
|
||||
// Size mismatch rejected.
|
||||
|
||||
@@ -318,7 +318,8 @@ impl ColorProcessor {
|
||||
}
|
||||
|
||||
/// Convert a whole F32 frame in place (row-major RGBA).
|
||||
pub fn convert_frame(&self, frame: &mut Frame) -> Result<()> { let Some(cpu) = &self.cpu else {
|
||||
pub fn convert_frame(&self, frame: &mut Frame) -> Result<()> {
|
||||
let Some(cpu) = &self.cpu else {
|
||||
return Ok(()); // pass-through
|
||||
};
|
||||
if frame.format != PixelFormat::F32 {
|
||||
@@ -366,8 +367,7 @@ impl ColorProcessor {
|
||||
if data.len() < count {
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
let mut f32s: Vec<f32> =
|
||||
data[..count].iter().map(|&v| v as f32 / 255.0).collect();
|
||||
let mut f32s: Vec<f32> = data[..count].iter().map(|&v| v as f32 / 255.0).collect();
|
||||
cpu.try_apply_rgba_pixels(&mut f32s, pixels, 4)
|
||||
.map_err(|e| Error::Failed(format!("OCIO f32 apply (bgra8 detour): {e}")))?;
|
||||
for (dst, v) in data[..count].iter_mut().zip(f32s) {
|
||||
@@ -409,7 +409,7 @@ impl GradingStyle {
|
||||
/// Reinterpret a byte buffer as f32 when its length is a multiple of 4
|
||||
/// (the pipeline guarantees F32 RGBA frames, so alignment is exact).
|
||||
fn bytemuck_f32_slice(data: &mut [u8]) -> Option<&mut [f32]> {
|
||||
if data.len() % 4 != 0 {
|
||||
if !data.len().is_multiple_of(4) {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: length is a multiple of 4 and `data` is byte-aligned; the
|
||||
@@ -427,11 +427,17 @@ fn bytemuck_f32_slice(data: &mut [u8]) -> Option<&mut [f32]> {
|
||||
/// project-properties commit). Render and export paths read it — a single
|
||||
/// project is open at a time, so a process global is the same shape as the
|
||||
/// OCIO default config above.
|
||||
static PIPELINE_COLOR: LazyLock<Mutex<(crate::colormath::WorkingColorSpace, crate::colormath::OutputColorSpec)>> =
|
||||
LazyLock::new(|| Mutex::new((
|
||||
static PIPELINE_COLOR: LazyLock<
|
||||
Mutex<(
|
||||
crate::colormath::WorkingColorSpace,
|
||||
crate::colormath::OutputColorSpec,
|
||||
)>,
|
||||
> = LazyLock::new(|| {
|
||||
Mutex::new((
|
||||
crate::colormath::WorkingColorSpace::default(),
|
||||
crate::colormath::OutputColorSpec::default(),
|
||||
)));
|
||||
))
|
||||
});
|
||||
|
||||
/// Set the pipeline color settings (working space + output spec).
|
||||
pub fn set_pipeline_color_settings(
|
||||
@@ -594,9 +600,7 @@ pub fn config_path() -> Option<String> {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
if default_config().is_none() {
|
||||
return None;
|
||||
}
|
||||
default_config()?;
|
||||
Some(format!(
|
||||
"{}/ocioconf/config.ocio",
|
||||
crate::commonutil::configuration_location()
|
||||
@@ -836,8 +840,8 @@ mod tests {
|
||||
let _lock = config_lock();
|
||||
if set_up_default_config().is_err() {
|
||||
return;
|
||||
} // Create via the built-in config; a valid processor must exist for
|
||||
// the ACES scene→display-encoded pairing and must preserve alpha.
|
||||
} // Create via the built-in config; a valid processor must exist for
|
||||
// the ACES scene→display-encoded pairing and must preserve alpha.
|
||||
let p = ColorProcessor::create("ACEScg", "sRGB Encoded Rec.709 (sRGB)", Direction::Normal);
|
||||
let p = p.expect("processor handle always returned");
|
||||
assert!(
|
||||
@@ -879,7 +883,8 @@ mod tests {
|
||||
return;
|
||||
}
|
||||
let display = config.display_all(0).unwrap();
|
||||
let n = config.num_views_by_reference_space(ocio_rs::SearchReferenceSpaceType::Scene, &display);
|
||||
let n =
|
||||
config.num_views_by_reference_space(ocio_rs::SearchReferenceSpaceType::Scene, &display);
|
||||
assert!(n >= 0);
|
||||
if n > 0 {
|
||||
let view = config
|
||||
@@ -913,7 +918,7 @@ mod tests {
|
||||
let f32s: &mut [f32] = unsafe {
|
||||
std::slice::from_raw_parts_mut(f.data.as_mut_ptr() as *mut f32, f.pixel_count() * 4)
|
||||
};
|
||||
for px in f32s.chunks_exact_mut(4) {
|
||||
for px in f32s.as_chunks_mut::<4>().0 {
|
||||
px[0] = 0.18;
|
||||
px[1] = 0.18;
|
||||
px[2] = 0.18;
|
||||
@@ -960,8 +965,10 @@ mod tests {
|
||||
"0.18 linear grey should land near 0.5 sRGB (got {})",
|
||||
out[0]
|
||||
);
|
||||
assert!((out[0] - out[1]).abs() < 1e-3 && (out[1] - out[2]).abs() < 1e-3,
|
||||
"grey stays grey: {out:?}");
|
||||
assert!(
|
||||
(out[0] - out[1]).abs() < 1e-3 && (out[1] - out[2]).abs() < 1e-3,
|
||||
"grey stays grey: {out:?}"
|
||||
);
|
||||
assert!((out[3] - 1.0).abs() < 1e-5, "alpha preserved");
|
||||
}
|
||||
|
||||
@@ -1073,7 +1080,7 @@ mod tests {
|
||||
let f32_result = p32.convert_f32_rgba(&mut f32s, 4);
|
||||
eprintln!("f32 convert: {f32_result:?} -> {f32s:?}");
|
||||
bgra_result.expect("convert");
|
||||
for (i, px) in data.chunks_exact(4).enumerate() {
|
||||
for (i, px) in data.as_chunks::<4>().0.iter().enumerate() {
|
||||
assert_eq!(px[3], 255, "pixel {i}: alpha preserved");
|
||||
let rgb: u32 = px[0] as u32 + px[1] as u32 + px[2] as u32;
|
||||
assert!(rgb > 0, "pixel {i} must not be crushed to black: {px:?}");
|
||||
@@ -1085,7 +1092,7 @@ mod tests {
|
||||
"grey stays grey through the display ICC: {b} {g} {r}"
|
||||
);
|
||||
f32_result.expect("convert f32");
|
||||
for (i, px) in f32s.chunks_exact(4).enumerate() {
|
||||
for (i, px) in f32s.as_chunks::<4>().0.iter().enumerate() {
|
||||
assert!((px[3] - 1.0).abs() < 1e-3, "pixel {i}: alpha preserved");
|
||||
let rgb = px[0] + px[1] + px[2];
|
||||
assert!(rgb > 0.0, "pixel {i} must not be crushed to black: {px:?}");
|
||||
@@ -1171,7 +1178,10 @@ mod tests {
|
||||
stub.contains("ove_grading_primary"),
|
||||
"function name present"
|
||||
);
|
||||
assert!(!stub.contains("sampler"), "no LUT upload expected for grading");
|
||||
assert!(
|
||||
!stub.contains("sampler"),
|
||||
"no LUT upload expected for grading"
|
||||
);
|
||||
// Cache hit: a repeated call returns the same text.
|
||||
let again = grading_primary_function_shader(style).unwrap();
|
||||
assert_eq!(stub, again);
|
||||
@@ -1200,8 +1210,14 @@ mod tests {
|
||||
"cie_xyz_d65_interchange",
|
||||
)
|
||||
.expect("default config generates an analytic shader");
|
||||
assert!(stub.contains("SceneLinearToCIEXYZ_d65"), "function name present");
|
||||
assert!(!stub.contains("sampler"), "no LUT upload expected in the default config");
|
||||
assert!(
|
||||
stub.contains("SceneLinearToCIEXYZ_d65"),
|
||||
"function name present"
|
||||
);
|
||||
assert!(
|
||||
!stub.contains("sampler"),
|
||||
"no LUT upload expected in the default config"
|
||||
);
|
||||
// Cache hit: a repeated call returns the same text.
|
||||
let again = ocio_function_shader(
|
||||
"SceneLinearToCIEXYZ_d65",
|
||||
@@ -1217,9 +1233,9 @@ mod tests {
|
||||
let _lock = config_lock();
|
||||
// studio-config's Rec.709 display is a CLF LUT chain (unlike the
|
||||
// analytic sRGB one); without a LUT upload path it must be refused.
|
||||
let Ok(cfg) = ocio_rs::Config::create_from_builtin_config(
|
||||
"studio-config-v2.1.0_aces-v1.3_ocio-v2.3",
|
||||
) else {
|
||||
let Ok(cfg) =
|
||||
ocio_rs::Config::create_from_builtin_config("studio-config-v2.1.0_aces-v1.3_ocio-v2.3")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let cfg = SafeConfig(cfg);
|
||||
@@ -1234,4 +1250,3 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -495,15 +495,14 @@ const LUT_N: usize = 4096;
|
||||
/// Declare one lazily-built transfer LUT over [0, 1].
|
||||
macro_rules! tf_lut {
|
||||
($name:ident, $f:expr) => {
|
||||
static $name: std::sync::LazyLock<[f32; LUT_N + 1]> =
|
||||
std::sync::LazyLock::new(|| {
|
||||
let f: fn(f32) -> f32 = $f;
|
||||
let mut t = [0.0f32; LUT_N + 1];
|
||||
for (i, e) in t.iter_mut().enumerate() {
|
||||
*e = f(i as f32 / LUT_N as f32);
|
||||
}
|
||||
t
|
||||
});
|
||||
static $name: std::sync::LazyLock<[f32; LUT_N + 1]> = std::sync::LazyLock::new(|| {
|
||||
let f: fn(f32) -> f32 = $f;
|
||||
let mut t = [0.0f32; LUT_N + 1];
|
||||
for (i, e) in t.iter_mut().enumerate() {
|
||||
*e = f(i as f32 / LUT_N as f32);
|
||||
}
|
||||
t
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -663,8 +662,8 @@ pub enum SourceTransfer {
|
||||
/// (AVCOL_PRI_* numbering, H.273 ISO codes.)
|
||||
pub fn source_primaries_from_av(color_primaries: i32) -> SourcePrimaries {
|
||||
match color_primaries {
|
||||
1 => SourcePrimaries::Bt709, // BT.709
|
||||
9 => SourcePrimaries::Bt2020, // BT.2020
|
||||
1 => SourcePrimaries::Bt709, // BT.709
|
||||
9 => SourcePrimaries::Bt2020, // BT.2020
|
||||
11 => SourcePrimaries::DisplayP3, // SMPTE RP 431-2 (DCI-P3)
|
||||
12 => SourcePrimaries::DisplayP3, // SMPTE EG 432-1 (Display P3)
|
||||
_ => SourcePrimaries::Unknown,
|
||||
@@ -675,16 +674,16 @@ pub fn source_primaries_from_av(color_primaries: i32) -> SourcePrimaries {
|
||||
/// (AVCOL_TRC_* numbering, H.273 ISO codes.)
|
||||
pub fn source_transfer_from_av(color_trc: i32) -> SourceTransfer {
|
||||
match color_trc {
|
||||
1 => SourceTransfer::SdrGamma, // BT.709 (see the enum docs: OETF inverse ≈ 2.2)
|
||||
4 => SourceTransfer::Gamma22, // gamma 2.2
|
||||
5 => SourceTransfer::Gamma28, // gamma 2.8
|
||||
6 => SourceTransfer::SdrGamma, // SMPTE 170M
|
||||
1 => SourceTransfer::SdrGamma, // BT.709 (see the enum docs: OETF inverse ≈ 2.2)
|
||||
4 => SourceTransfer::Gamma22, // gamma 2.2
|
||||
5 => SourceTransfer::Gamma28, // gamma 2.8
|
||||
6 => SourceTransfer::SdrGamma, // SMPTE 170M
|
||||
13 => SourceTransfer::SdrGamma, // sRGB (its EOTF is applied at decode)
|
||||
14 => SourceTransfer::SdrGamma, // BT.2020 10-bit
|
||||
15 => SourceTransfer::SdrGamma, // BT.2020 12-bit
|
||||
16 => SourceTransfer::Pq, // SMPTE ST 2084
|
||||
18 => SourceTransfer::Hlg, // ARIB STD-B67
|
||||
8 => SourceTransfer::Linear, // linear
|
||||
16 => SourceTransfer::Pq, // SMPTE ST 2084
|
||||
18 => SourceTransfer::Hlg, // ARIB STD-B67
|
||||
8 => SourceTransfer::Linear, // linear
|
||||
_ => SourceTransfer::Unknown,
|
||||
}
|
||||
}
|
||||
@@ -708,15 +707,11 @@ impl SourcePrimaries {
|
||||
/// own primaries) → ACEScg linear. `samples` is an F32 RGBA buffer,
|
||||
/// transformed in place. Linearize and the primaries matrix are fused
|
||||
/// into a single pass (LUT'd transfer, row-band parallel).
|
||||
pub fn decode_to_acescg(
|
||||
samples: &mut [f32],
|
||||
primaries: SourcePrimaries,
|
||||
transfer: SourceTransfer,
|
||||
) {
|
||||
pub fn decode_to_acescg(samples: &mut [f32], primaries: SourcePrimaries, transfer: SourceTransfer) {
|
||||
let linearize = decode_transfer_fn(transfer);
|
||||
let matrix = rgb_to_rgb_matrix(primaries.primaries(), PRIMARIES_AP1);
|
||||
par_pixels_f32(samples, move |band| {
|
||||
for px in band.chunks_exact_mut(4) {
|
||||
for px in band.as_chunks_mut::<4>().0 {
|
||||
for c in 0..3 {
|
||||
px[c] = linearize(px[c]);
|
||||
}
|
||||
@@ -740,7 +735,7 @@ pub fn decode_to_acescg_bytes(
|
||||
let linearize = decode_transfer_fn(transfer);
|
||||
let matrix = rgb_to_rgb_matrix(primaries.primaries(), PRIMARIES_AP1);
|
||||
par_pixels_bytes(bytes, move |band| {
|
||||
for px in band.chunks_exact_mut(16) {
|
||||
for px in band.as_chunks_mut::<16>().0 {
|
||||
let mut v = [
|
||||
f32::from_le_bytes(px[0..4].try_into().unwrap()),
|
||||
f32::from_le_bytes(px[4..8].try_into().unwrap()),
|
||||
@@ -772,7 +767,7 @@ pub fn acescg_to_output(samples: &mut [f32], spec: OutputColorSpec) {
|
||||
let matrix = rgb_to_rgb_matrix(PRIMARIES_AP1, spec.gamut.primaries());
|
||||
let encode = output_transfer_fn(spec.transfer);
|
||||
par_pixels_f32(samples, move |band| {
|
||||
for px in band.chunks_exact_mut(4) {
|
||||
for px in band.as_chunks_mut::<4>().0 {
|
||||
apply_mat(matrix, px);
|
||||
for c in 0..3 {
|
||||
px[c] = encode(px[c].clamp(0.0, 1.0));
|
||||
@@ -792,7 +787,7 @@ pub fn acescg_to_output_bytes(bytes: &mut [u8], pixels: usize, spec: OutputColor
|
||||
let matrix = rgb_to_rgb_matrix(PRIMARIES_AP1, spec.gamut.primaries());
|
||||
let encode = output_transfer_fn(spec.transfer);
|
||||
par_pixels_bytes(bytes, move |band| {
|
||||
for px in band.chunks_exact_mut(16) {
|
||||
for px in band.as_chunks_mut::<16>().0 {
|
||||
let mut v = [
|
||||
f32::from_le_bytes(px[0..4].try_into().unwrap()),
|
||||
f32::from_le_bytes(px[4..8].try_into().unwrap()),
|
||||
@@ -816,7 +811,7 @@ pub fn acescg_to_output_bytes(bytes: &mut [u8], pixels: usize, spec: OutputColor
|
||||
pub fn apply_transfer_oetf(samples: &mut [f32], transfer: OutputTransfer) {
|
||||
let encode = output_transfer_fn(transfer);
|
||||
par_pixels_f32(samples, move |band| {
|
||||
for px in band.chunks_exact_mut(4) {
|
||||
for px in band.as_chunks_mut::<4>().0 {
|
||||
for c in 0..3 {
|
||||
px[c] = encode(px[c]);
|
||||
}
|
||||
@@ -860,7 +855,7 @@ pub fn output_spec_to_xyz_d65(samples: &mut [f32], spec: OutputColorSpec) {
|
||||
});
|
||||
let matrix = rgb_to_xyz_matrix(spec.gamut.primaries());
|
||||
par_pixels_f32(samples, move |band| {
|
||||
for px in band.chunks_exact_mut(4) {
|
||||
for px in band.as_chunks_mut::<4>().0 {
|
||||
for c in 0..3 {
|
||||
px[c] = linearize(px[c]);
|
||||
}
|
||||
@@ -931,9 +926,18 @@ pub fn yuv444p16_to_rgb_f32(
|
||||
// `width` samples.
|
||||
let row_bytes = width.saturating_mul(2);
|
||||
let rows_after = height.saturating_sub(1);
|
||||
if y_plane.len() < rows_after.saturating_mul(y_stride).saturating_add(row_bytes)
|
||||
|| u_plane.len() < rows_after.saturating_mul(u_stride).saturating_add(row_bytes)
|
||||
|| v_plane.len() < rows_after.saturating_mul(v_stride).saturating_add(row_bytes)
|
||||
if y_plane.len()
|
||||
< rows_after
|
||||
.saturating_mul(y_stride)
|
||||
.saturating_add(row_bytes)
|
||||
|| u_plane.len()
|
||||
< rows_after
|
||||
.saturating_mul(u_stride)
|
||||
.saturating_add(row_bytes)
|
||||
|| v_plane.len()
|
||||
< rows_after
|
||||
.saturating_mul(v_stride)
|
||||
.saturating_add(row_bytes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -946,7 +950,11 @@ pub fn yuv444p16_to_rgb_f32(
|
||||
} else {
|
||||
(4096.0f32, 32768.0f32, 1.0 / 57344.0)
|
||||
};
|
||||
let y_scale = if full_range { 1.0 / 65535.0 } else { 1.0 / 56064.0 };
|
||||
let y_scale = if full_range {
|
||||
1.0 / 65535.0
|
||||
} else {
|
||||
1.0 / 56064.0
|
||||
};
|
||||
for row in 0..height {
|
||||
let y_row = row * y_stride;
|
||||
let u_row = row * u_stride;
|
||||
@@ -1002,9 +1010,9 @@ mod tests {
|
||||
// 0.0206157712 0.1095697056 0.8698145232
|
||||
let m = rgb_to_rgb_matrix(PRIMARIES_SRGB, PRIMARIES_AP1);
|
||||
let expected: Mat3 = [
|
||||
[0.6131324224, 0.3395230762, 0.0473445014],
|
||||
[0.0701922769, 0.9163536767, 0.0134540464],
|
||||
[0.0206157712, 0.1095697056, 0.8698145232],
|
||||
[0.613_132_4, 0.339_523_08, 0.047_344_502],
|
||||
[0.070_192_28, 0.916_353_7, 0.013_454_046],
|
||||
[0.020_615_771, 0.109_569_706, 0.869_814_5],
|
||||
];
|
||||
for r in 0..3 {
|
||||
for c in 0..3 {
|
||||
@@ -1034,7 +1042,11 @@ mod tests {
|
||||
// → sRGB linear → (OETF) → the original codes.
|
||||
let original = [0.25f32, 0.5, 0.75, 1.0];
|
||||
let mut samples = original;
|
||||
decode_to_acescg(&mut samples, SourcePrimaries::Bt709, SourceTransfer::SdrGamma);
|
||||
decode_to_acescg(
|
||||
&mut samples,
|
||||
SourcePrimaries::Bt709,
|
||||
SourceTransfer::SdrGamma,
|
||||
);
|
||||
acescg_to_output(
|
||||
&mut samples,
|
||||
OutputColorSpec {
|
||||
@@ -1056,7 +1068,11 @@ mod tests {
|
||||
fn round_trip_bt2020_acescg_bt2020() {
|
||||
let original = [0.2f32, 0.6, 0.9, 1.0];
|
||||
let mut samples = original;
|
||||
decode_to_acescg(&mut samples, SourcePrimaries::Bt2020, SourceTransfer::SdrGamma);
|
||||
decode_to_acescg(
|
||||
&mut samples,
|
||||
SourcePrimaries::Bt2020,
|
||||
SourceTransfer::SdrGamma,
|
||||
);
|
||||
acescg_to_output(
|
||||
&mut samples,
|
||||
OutputColorSpec {
|
||||
@@ -1081,7 +1097,11 @@ mod tests {
|
||||
let mut samples = [1.0f32, 1.0, 1.0, 1.0];
|
||||
decode_to_acescg(&mut samples, SourcePrimaries::Bt709, SourceTransfer::Linear);
|
||||
for i in 0..3 {
|
||||
assert!(approx(samples[i], 1.0, 1e-3), "white channel {i} = {}", samples[i]);
|
||||
assert!(
|
||||
approx(samples[i], 1.0, 1e-3),
|
||||
"white channel {i} = {}",
|
||||
samples[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1093,7 +1113,11 @@ mod tests {
|
||||
assert!(approx(srgb_eotf(0.0), 0.0, 1e-6), "eotf(0)");
|
||||
assert!(approx(srgb_eotf(1.0), 1.0, 1e-6), "eotf(1)");
|
||||
// Mid-grey: linear 0.18 → ~0.461 sRGB code (the classic check).
|
||||
assert!(approx(srgb_oetf(0.18), 0.4613, 1e-3), "oetf(0.18) = {}", srgb_oetf(0.18));
|
||||
assert!(
|
||||
approx(srgb_oetf(0.18), 0.4613, 1e-3),
|
||||
"oetf(0.18) = {}",
|
||||
srgb_oetf(0.18)
|
||||
);
|
||||
// Round trip.
|
||||
for v in [0.0f32, 0.01, 0.18, 0.5, 0.99, 1.0] {
|
||||
assert!(approx(srgb_eotf(srgb_oetf(v)), v, 1e-4), "round trip {v}");
|
||||
@@ -1103,9 +1127,17 @@ mod tests {
|
||||
#[test]
|
||||
fn pq_transfer_anchors() {
|
||||
// PQ code 1.0 = 10 000 nits = normalized 1.0.
|
||||
assert!(approx(pq_eotf(1.0), 1.0, 1e-4), "pq eotf(1) = {}", pq_eotf(1.0));
|
||||
assert!(
|
||||
approx(pq_eotf(1.0), 1.0, 1e-4),
|
||||
"pq eotf(1) = {}",
|
||||
pq_eotf(1.0)
|
||||
);
|
||||
// PQ code 0.5 ≈ 100 nits (normalized 0.01).
|
||||
assert!(approx(pq_eotf(0.5), 0.01008, 2e-3), "pq eotf(0.5) = {}", pq_eotf(0.5));
|
||||
assert!(
|
||||
approx(pq_eotf(0.5), 0.01008, 2e-3),
|
||||
"pq eotf(0.5) = {}",
|
||||
pq_eotf(0.5)
|
||||
);
|
||||
// Round trip.
|
||||
for v in [0.0f32, 0.25, 0.5, 0.75, 1.0] {
|
||||
assert!(approx(pq_eotf(pq_oetf(v)), v, 1e-3), "pq round trip {v}");
|
||||
@@ -1115,9 +1147,17 @@ mod tests {
|
||||
#[test]
|
||||
fn hlg_transfer_anchors() {
|
||||
assert!(approx(hlg_eotf(0.0), 0.0, 1e-6), "hlg eotf(0)");
|
||||
assert!(approx(hlg_eotf(1.0), 1.0, 1e-3), "hlg eotf(1) = {}", hlg_eotf(1.0));
|
||||
assert!(
|
||||
approx(hlg_eotf(1.0), 1.0, 1e-3),
|
||||
"hlg eotf(1) = {}",
|
||||
hlg_eotf(1.0)
|
||||
);
|
||||
// The piecewise join: code 0.5 ↔ linear 1/12.
|
||||
assert!(approx(hlg_eotf(0.5), 1.0 / 12.0, 1e-3), "hlg eotf(0.5) = {}", hlg_eotf(0.5));
|
||||
assert!(
|
||||
approx(hlg_eotf(0.5), 1.0 / 12.0, 1e-3),
|
||||
"hlg eotf(0.5) = {}",
|
||||
hlg_eotf(0.5)
|
||||
);
|
||||
// Round trip.
|
||||
for v in [0.0f32, 0.1, 0.5, 0.9, 1.0] {
|
||||
assert!(approx(hlg_eotf(hlg_oetf(v)), v, 1e-3), "hlg round trip {v}");
|
||||
@@ -1146,18 +1186,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn setting_round_trips() {
|
||||
assert_eq!(WorkingColorSpace::from_setting("acescg"), WorkingColorSpace::AcesCg);
|
||||
assert_eq!(
|
||||
WorkingColorSpace::from_setting("acescg"),
|
||||
WorkingColorSpace::AcesCg
|
||||
);
|
||||
assert_eq!(
|
||||
WorkingColorSpace::from_setting("srgb_legacy"),
|
||||
WorkingColorSpace::SrgbLegacy
|
||||
);
|
||||
assert_eq!(WorkingColorSpace::from_setting("bogus"), WorkingColorSpace::AcesCg);
|
||||
assert_eq!(OutputGamut::from_setting("displayp3"), OutputGamut::DisplayP3);
|
||||
assert_eq!(
|
||||
WorkingColorSpace::from_setting("bogus"),
|
||||
WorkingColorSpace::AcesCg
|
||||
);
|
||||
assert_eq!(
|
||||
OutputGamut::from_setting("displayp3"),
|
||||
OutputGamut::DisplayP3
|
||||
);
|
||||
assert_eq!(OutputGamut::from_setting("bt2020"), OutputGamut::Bt2020);
|
||||
assert_eq!(OutputGamut::from_setting(""), OutputGamut::Srgb);
|
||||
assert_eq!(OutputTransfer::from_setting("pq"), OutputTransfer::Pq);
|
||||
assert_eq!(OutputTransfer::from_setting("hlg"), OutputTransfer::Hlg);
|
||||
assert_eq!(OutputTransfer::from_setting("gamma22"), OutputTransfer::Gamma22);
|
||||
assert_eq!(
|
||||
OutputTransfer::from_setting("gamma22"),
|
||||
OutputTransfer::Gamma22
|
||||
);
|
||||
assert_eq!(OutputTransfer::from_setting(""), OutputTransfer::Srgb);
|
||||
assert_eq!(
|
||||
OutputColorSpec::from_settings("displayp3", "pq"),
|
||||
@@ -1184,13 +1236,31 @@ mod tests {
|
||||
fn gamma_transfer_anchors() {
|
||||
// Pure-power EOTFs at code 0.5. The 2.4 case is the BT.1886
|
||||
// mid-grey link: 0.5²·⁴ ≈ 0.189 (≈ the classic 18 % grey).
|
||||
assert!(approx(gamma_eotf(0.5, 2.4), 0.1894646, 1e-5), "eotf(0.5, 2.4) = {}", gamma_eotf(0.5, 2.4));
|
||||
assert!(approx(gamma_eotf(0.5, 2.2), 0.2176376, 1e-5), "eotf(0.5, 2.2) = {}", gamma_eotf(0.5, 2.2));
|
||||
assert!(approx(gamma_eotf(0.5, 2.8), 0.1435894, 1e-5), "eotf(0.5, 2.8) = {}", gamma_eotf(0.5, 2.8));
|
||||
assert!(
|
||||
approx(gamma_eotf(0.5, 2.4), 0.1894646, 1e-5),
|
||||
"eotf(0.5, 2.4) = {}",
|
||||
gamma_eotf(0.5, 2.4)
|
||||
);
|
||||
assert!(
|
||||
approx(gamma_eotf(0.5, 2.2), 0.2176376, 1e-5),
|
||||
"eotf(0.5, 2.2) = {}",
|
||||
gamma_eotf(0.5, 2.2)
|
||||
);
|
||||
assert!(
|
||||
approx(gamma_eotf(0.5, 2.8), 0.1435894, 1e-5),
|
||||
"eotf(0.5, 2.8) = {}",
|
||||
gamma_eotf(0.5, 2.8)
|
||||
);
|
||||
// Endpoints and round trips.
|
||||
for gamma in [2.2f32, 2.4, 2.8] {
|
||||
assert!(approx(gamma_eotf(0.0, gamma), 0.0, 1e-6), "eotf(0) g{gamma}");
|
||||
assert!(approx(gamma_eotf(1.0, gamma), 1.0, 1e-6), "eotf(1) g{gamma}");
|
||||
assert!(
|
||||
approx(gamma_eotf(0.0, gamma), 0.0, 1e-6),
|
||||
"eotf(0) g{gamma}"
|
||||
);
|
||||
assert!(
|
||||
approx(gamma_eotf(1.0, gamma), 1.0, 1e-6),
|
||||
"eotf(1) g{gamma}"
|
||||
);
|
||||
for v in [0.0f32, 0.18, 0.5, 0.9, 1.0] {
|
||||
assert!(
|
||||
approx(gamma_eotf(gamma_oetf(v, gamma), gamma), v, 1e-4),
|
||||
@@ -1244,14 +1314,20 @@ mod tests {
|
||||
.map(|i: usize| ((i.wrapping_mul(2654435761)) % 1000) as f32 / 999.0)
|
||||
.collect();
|
||||
let reference: Vec<f32> = samples
|
||||
.chunks_exact(4)
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
.flat_map(|px| {
|
||||
let mut v = [srgb_eotf(px[0]), srgb_eotf(px[1]), srgb_eotf(px[2]), px[3]];
|
||||
apply_mat(rgb_to_rgb_matrix(PRIMARIES_SRGB, PRIMARIES_AP1), &mut v);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
decode_to_acescg(&mut samples, SourcePrimaries::Bt709, SourceTransfer::SdrGamma);
|
||||
decode_to_acescg(
|
||||
&mut samples,
|
||||
SourcePrimaries::Bt709,
|
||||
SourceTransfer::SdrGamma,
|
||||
);
|
||||
for (i, (&got, &want)) in samples.iter().zip(reference.iter()).enumerate() {
|
||||
assert!(approx(got, want, 1e-4), "pixel {i}: {got} vs {want}");
|
||||
}
|
||||
@@ -1267,7 +1343,11 @@ mod tests {
|
||||
// (the ~1.1 system gamma belongs at final display only).
|
||||
for code in [0.05f32, 0.18, 0.5, 0.75, 1.0] {
|
||||
let mut samples = [code, code, code, 1.0];
|
||||
decode_to_acescg(&mut samples, SourcePrimaries::Bt709, SourceTransfer::SdrGamma);
|
||||
decode_to_acescg(
|
||||
&mut samples,
|
||||
SourcePrimaries::Bt709,
|
||||
SourceTransfer::SdrGamma,
|
||||
);
|
||||
acescg_to_output(&mut samples, OutputColorSpec::default());
|
||||
for i in 0..3 {
|
||||
assert!(
|
||||
@@ -1283,7 +1363,12 @@ mod tests {
|
||||
bytes[0..4].copy_from_slice(&0.5f32.to_le_bytes());
|
||||
bytes[4..8].copy_from_slice(&0.5f32.to_le_bytes());
|
||||
bytes[8..12].copy_from_slice(&0.5f32.to_le_bytes());
|
||||
decode_to_acescg_bytes(&mut bytes, 1, SourcePrimaries::Bt709, SourceTransfer::SdrGamma);
|
||||
decode_to_acescg_bytes(
|
||||
&mut bytes,
|
||||
1,
|
||||
SourcePrimaries::Bt709,
|
||||
SourceTransfer::SdrGamma,
|
||||
);
|
||||
let r = f32::from_le_bytes(bytes[0..4].try_into().unwrap());
|
||||
let m = rgb_to_rgb_matrix(PRIMARIES_SRGB, PRIMARIES_AP1);
|
||||
let expected = mat_vec(m, [srgb_eotf(0.5); 3]);
|
||||
@@ -1321,7 +1406,11 @@ mod tests {
|
||||
samples[c],
|
||||
expected[c]
|
||||
);
|
||||
assert!(samples[c] >= 0.0 && samples[c] <= 1.0, "channel {c} escaped [0, 1]: {}", samples[c]);
|
||||
assert!(
|
||||
samples[c] >= 0.0 && samples[c] <= 1.0,
|
||||
"channel {c} escaped [0, 1]: {}",
|
||||
samples[c]
|
||||
);
|
||||
}
|
||||
assert_eq!(samples[3], 0.5, "alpha must be untouched");
|
||||
}
|
||||
@@ -1373,10 +1462,26 @@ mod tests {
|
||||
let u = yuv_plane16(&[&[128 << 8], &[128 << 8]], width, stride);
|
||||
let v = u.clone();
|
||||
let mut out = [0.0f32; 8];
|
||||
yuv444p16_to_rgb_f32(&y, stride, &u, stride, &v, stride, width, height, YuvMatrix::Bt709, false, &mut out);
|
||||
yuv444p16_to_rgb_f32(
|
||||
&y,
|
||||
stride,
|
||||
&u,
|
||||
stride,
|
||||
&v,
|
||||
stride,
|
||||
width,
|
||||
height,
|
||||
YuvMatrix::Bt709,
|
||||
false,
|
||||
&mut out,
|
||||
);
|
||||
for c in 0..3 {
|
||||
assert!(approx(out[c], 1.0, 1e-4), "white channel {c}: {}", out[c]);
|
||||
assert!(approx(out[4 + c], 0.0, 1e-4), "black channel {c}: {}", out[4 + c]);
|
||||
assert!(
|
||||
approx(out[4 + c], 0.0, 1e-4),
|
||||
"black channel {c}: {}",
|
||||
out[4 + c]
|
||||
);
|
||||
}
|
||||
assert_eq!(out[3], 1.0, "alpha");
|
||||
assert_eq!(out[7], 1.0, "alpha");
|
||||
@@ -1394,7 +1499,19 @@ mod tests {
|
||||
let u = yuv_plane16(&[&[11059]], width, stride);
|
||||
let v = yuv_plane16(&[&[5329]], width, stride);
|
||||
let mut out = [0.0f32; 4];
|
||||
yuv444p16_to_rgb_f32(&y, stride, &u, stride, &v, stride, width, height, YuvMatrix::Bt601, true, &mut out);
|
||||
yuv444p16_to_rgb_f32(
|
||||
&y,
|
||||
stride,
|
||||
&u,
|
||||
stride,
|
||||
&v,
|
||||
stride,
|
||||
width,
|
||||
height,
|
||||
YuvMatrix::Bt601,
|
||||
true,
|
||||
&mut out,
|
||||
);
|
||||
assert!(approx(out[0], 0.0, 1e-3), "R = {}", out[0]);
|
||||
assert!(approx(out[1], 1.0, 1e-3), "G = {}", out[1]);
|
||||
assert!(approx(out[2], 0.0, 1e-3), "B = {}", out[2]);
|
||||
@@ -1412,7 +1529,19 @@ mod tests {
|
||||
let u = yuv_plane16(&[&[10666]], width, stride);
|
||||
let v = yuv_plane16(&[&[6725]], width, stride);
|
||||
let mut out = [0.0f32; 4];
|
||||
yuv444p16_to_rgb_f32(&y, stride, &u, stride, &v, stride, width, height, YuvMatrix::Bt709, false, &mut out);
|
||||
yuv444p16_to_rgb_f32(
|
||||
&y,
|
||||
stride,
|
||||
&u,
|
||||
stride,
|
||||
&v,
|
||||
stride,
|
||||
width,
|
||||
height,
|
||||
YuvMatrix::Bt709,
|
||||
false,
|
||||
&mut out,
|
||||
);
|
||||
assert!(approx(out[0], 0.0, 1e-3), "R = {}", out[0]);
|
||||
assert!(approx(out[1], 1.0, 1e-3), "G = {}", out[1]);
|
||||
assert!(approx(out[2], 0.0, 1e-3), "B = {}", out[2]);
|
||||
@@ -1423,11 +1552,35 @@ mod tests {
|
||||
// A 2×2 frame at stride 4 needs (2−1)·4 + 2·2 = 8 bytes per plane;
|
||||
// 4 bytes is too short → early return, `out` untouched.
|
||||
let mut out = [7.0f32; 4];
|
||||
yuv444p16_to_rgb_f32(&[0u8; 4], 4, &[0u8; 4], 4, &[0u8; 4], 4, 2, 2, YuvMatrix::Bt709, false, &mut out);
|
||||
yuv444p16_to_rgb_f32(
|
||||
&[0u8; 4],
|
||||
4,
|
||||
&[0u8; 4],
|
||||
4,
|
||||
&[0u8; 4],
|
||||
4,
|
||||
2,
|
||||
2,
|
||||
YuvMatrix::Bt709,
|
||||
false,
|
||||
&mut out,
|
||||
);
|
||||
assert_eq!(out, [7.0; 4]);
|
||||
// `out` too short for the frame → early return.
|
||||
let mut out2 = [7.0f32; 3];
|
||||
yuv444p16_to_rgb_f32(&[0u8; 8], 4, &[0u8; 8], 4, &[0u8; 8], 4, 2, 2, YuvMatrix::Bt709, false, &mut out2);
|
||||
yuv444p16_to_rgb_f32(
|
||||
&[0u8; 8],
|
||||
4,
|
||||
&[0u8; 8],
|
||||
4,
|
||||
&[0u8; 8],
|
||||
4,
|
||||
2,
|
||||
2,
|
||||
YuvMatrix::Bt709,
|
||||
false,
|
||||
&mut out2,
|
||||
);
|
||||
assert_eq!(out2, [7.0; 3]);
|
||||
}
|
||||
|
||||
|
||||
@@ -308,10 +308,7 @@ impl ConfigStore {
|
||||
Some(slash) => {
|
||||
let group = key[..slash].to_string();
|
||||
let sub = key[slash + 1..].to_string();
|
||||
sections
|
||||
.entry(group)
|
||||
.or_default()
|
||||
.insert(sub, toml_value);
|
||||
sections.entry(group).or_default().insert(sub, toml_value);
|
||||
}
|
||||
None => {
|
||||
sections
|
||||
@@ -823,7 +820,7 @@ fn format_g(v: f64) -> String {
|
||||
let sci = format!("{:.*e}", (PRECISION - 1) as usize, a); // "d.ddddd e±N"
|
||||
let exp = sci.split('e').nth(1).unwrap().parse::<i32>().unwrap();
|
||||
let xr = exp;
|
||||
let body = if xr < -4 || xr >= PRECISION {
|
||||
let body = if !(-4..PRECISION).contains(&xr) {
|
||||
// Scientific notation: mantissa is already rounded; strip trailing zeros.
|
||||
let mant = sci.split('e').next().unwrap();
|
||||
let m = trim_mantissa(mant);
|
||||
@@ -1164,7 +1161,11 @@ mod tests {
|
||||
let content = std::fs::read_to_string(dir.join("config.toml")).unwrap();
|
||||
// Native TOML types: ints/bools/floats unquoted, strings quoted.
|
||||
assert!(content.contains("FlatKey = 1"), "content: {}", content);
|
||||
assert!(content.contains("StrKey = \"hello\""), "content: {}", content);
|
||||
assert!(
|
||||
content.contains("StrKey = \"hello\""),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
assert!(content.contains("DblKey = 2.5"), "content: {}", content);
|
||||
assert!(content.contains("BoolKey = true"), "content: {}", content);
|
||||
// Grouped keys land in [group] tables.
|
||||
@@ -1262,9 +1263,21 @@ UnknownTypedThing=hello
|
||||
assert_eq!(s.get_double(Some("render"), "gain", -1.0), -1.0);
|
||||
// The file holds native TOML types, not stringified values.
|
||||
let content = std::fs::read_to_string(dir.join("config.toml")).unwrap();
|
||||
assert!(content.contains("DefaultSequenceWidth = 640"), "content: {}", content);
|
||||
assert!(content.contains("SplitClipsCopyNodes = false"), "content: {}", content);
|
||||
assert!(content.contains("ProxyPreset = \"medium\""), "content: {}", content);
|
||||
assert!(
|
||||
content.contains("DefaultSequenceWidth = 640"),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
assert!(
|
||||
content.contains("SplitClipsCopyNodes = false"),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
assert!(
|
||||
content.contains("ProxyPreset = \"medium\""),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
assert!(content.contains("gain = 1.5"), "content: {}", content);
|
||||
});
|
||||
}
|
||||
@@ -1293,10 +1306,22 @@ CustomKey=hello
|
||||
assert!(dir.join("config.toml").is_file());
|
||||
assert!(dir.join("config.ini").is_file());
|
||||
let content = std::fs::read_to_string(dir.join("config.toml")).unwrap();
|
||||
assert!(content.contains("DefaultSequenceWidth = 640"), "content: {}", content);
|
||||
assert!(content.contains("UseProxyMedia = false"), "content: {}", content);
|
||||
assert!(
|
||||
content.contains("DefaultSequenceWidth = 640"),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
assert!(
|
||||
content.contains("UseProxyMedia = false"),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
assert!(content.contains("[section]"), "content: {}", content);
|
||||
assert!(content.contains("CustomKey = \"hello\""), "content: {}", content);
|
||||
assert!(
|
||||
content.contains("CustomKey = \"hello\""),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
|
||||
// A second load reads the migrated TOML, not the INI.
|
||||
s.reset_defaults().unwrap();
|
||||
@@ -1615,7 +1640,11 @@ CustomKey=hello
|
||||
// No group defaults exist; every top-level key is a plain value.
|
||||
assert!(table.values().all(|v| !v.is_table()));
|
||||
// Serialized output must not contain INI-style "k=v" lines.
|
||||
assert!(!content.contains("DefaultSequenceWidth=1920"), "content: {}", content);
|
||||
assert!(
|
||||
!content.contains("DefaultSequenceWidth=1920"),
|
||||
"content: {}",
|
||||
content
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1698,7 +1727,7 @@ FlatAfterEmptySection=ok
|
||||
assert_eq!(s.get(Some("g"), "KeyWithEquals").unwrap(), "a=b");
|
||||
// "[]" empties the group, so the key is flat.
|
||||
assert_eq!(s.get(None, "FlatAfterEmptySection").unwrap(), "ok");
|
||||
assert!(matches!(s.get(Some(""), "FlatAfterEmptySection"), Ok(_)));
|
||||
assert!(s.get(Some(""), "FlatAfterEmptySection").is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -229,9 +229,7 @@ fn parse_xprop_icc_hex(output: &str) -> Option<Vec<u8>> {
|
||||
n_digits += 1;
|
||||
}
|
||||
if n_digits > 0 {
|
||||
if let Ok(byte) =
|
||||
u8::from_str_radix(&rest[digits_start..digits_start + n_digits], 16)
|
||||
{
|
||||
if let Ok(byte) = u8::from_str_radix(&rest[digits_start..digits_start + n_digits], 16) {
|
||||
bytes.push(byte);
|
||||
}
|
||||
}
|
||||
@@ -563,9 +561,8 @@ mod windows {
|
||||
let sub_key = wide_nul(r"Software\Microsoft\Windows\CurrentVersion\VideoSettings");
|
||||
let value_name = wide_nul("EnableAutoColorManagement");
|
||||
let mut key: *mut c_void = std::ptr::null_mut();
|
||||
let status = unsafe {
|
||||
RegOpenKeyExW(HKEY_CURRENT_USER, sub_key.as_ptr(), 0, KEY_READ, &mut key)
|
||||
};
|
||||
let status =
|
||||
unsafe { RegOpenKeyExW(HKEY_CURRENT_USER, sub_key.as_ptr(), 0, KEY_READ, &mut key) };
|
||||
if status != ERROR_SUCCESS {
|
||||
return None;
|
||||
}
|
||||
@@ -628,7 +625,12 @@ mod windows {
|
||||
pub(super) fn display_icc_for(device: &str) -> Option<String> {
|
||||
let name = wide_nul(device);
|
||||
let hdc = unsafe {
|
||||
CreateDCW(name.as_ptr(), name.as_ptr(), std::ptr::null(), std::ptr::null())
|
||||
CreateDCW(
|
||||
name.as_ptr(),
|
||||
name.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if hdc.is_null() {
|
||||
return None;
|
||||
@@ -660,7 +662,10 @@ mod windows {
|
||||
sz_device: [0; 32],
|
||||
};
|
||||
let ok = unsafe {
|
||||
GetMonitorInfoW(hmonitor as *const c_void, &mut info as *mut _ as *mut c_void)
|
||||
GetMonitorInfoW(
|
||||
hmonitor as *const c_void,
|
||||
&mut info as *mut _ as *mut c_void,
|
||||
)
|
||||
};
|
||||
if ok == 0 {
|
||||
return None;
|
||||
@@ -864,7 +869,7 @@ mod linux {
|
||||
.lines()
|
||||
.map(str::trim_start)
|
||||
.find(|l| l.starts_with("Filename:"))?;
|
||||
let name = line.splitn(2, ':').nth(1)?.trim();
|
||||
let name = line.split_once(':')?.1.trim();
|
||||
if name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -1066,7 +1071,10 @@ Monitors: 1
|
||||
#[test]
|
||||
fn parse_xrandr_monitors_malformed() {
|
||||
assert_eq!(parse_xrandr_monitors(""), Vec::<RandRMonitor>::new());
|
||||
assert_eq!(parse_xrandr_monitors("Monitors: 0\n"), Vec::<RandRMonitor>::new());
|
||||
assert_eq!(
|
||||
parse_xrandr_monitors("Monitors: 0\n"),
|
||||
Vec::<RandRMonitor>::new()
|
||||
);
|
||||
// No `:` separator, a missing geometry, and a geometry with no
|
||||
// outputs are all skipped without panicking.
|
||||
let out = "\
|
||||
|
||||
@@ -51,7 +51,7 @@ fn ends_with_case_insensitive(s: &str, suffix: &str) -> bool {
|
||||
s.as_bytes()[offset..]
|
||||
.iter()
|
||||
.zip(suffix.as_bytes().iter())
|
||||
.all(|(a, b)| a.to_ascii_lowercase() == b.to_ascii_lowercase())
|
||||
.all(|(a, b)| a.eq_ignore_ascii_case(b))
|
||||
}
|
||||
|
||||
/// The filefunctions family (stateless; the handle only exists for C ABI
|
||||
@@ -424,6 +424,33 @@ pub(crate) fn config_location_path() -> Result<PathBuf> {
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
/// The default disk cache directory (C++ `DiskManager::
|
||||
/// get_default_disk_cache_path`): `<configuration location>/mediacache`.
|
||||
///
|
||||
/// The `DiskCachePath` config key overrides the location when set (the
|
||||
/// preferences dialog's cache-directory setting); an empty/absent value
|
||||
/// keeps the default.
|
||||
///
|
||||
/// Single-lib unification: this used to live in the oakrender crate's
|
||||
/// `bridge::common` fallback (see `docs/zh/plans/riir/single-lib.md`);
|
||||
/// oaknode and oakrender both call it directly now.
|
||||
pub fn default_disk_cache_path() -> String {
|
||||
// A configured override wins (whitespace-only counts as absent).
|
||||
if let Ok(custom) = crate::configstore::ConfigStore::instance().get(None, "DiskCachePath") {
|
||||
if !custom.trim().is_empty() {
|
||||
return custom;
|
||||
}
|
||||
}
|
||||
Path::new(
|
||||
&FileFunctions::new()
|
||||
.get_configuration_location()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.join("mediacache")
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -927,30 +954,3 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&b);
|
||||
}
|
||||
}
|
||||
|
||||
/// The default disk cache directory (C++ `DiskManager::
|
||||
/// get_default_disk_cache_path`): `<configuration location>/mediacache`.
|
||||
///
|
||||
/// The `DiskCachePath` config key overrides the location when set (the
|
||||
/// preferences dialog's cache-directory setting); an empty/absent value
|
||||
/// keeps the default.
|
||||
///
|
||||
/// Single-lib unification: this used to live in the oakrender crate's
|
||||
/// `bridge::common` fallback (see `docs/zh/plans/riir/single-lib.md`);
|
||||
/// oaknode and oakrender both call it directly now.
|
||||
pub fn default_disk_cache_path() -> String {
|
||||
// A configured override wins (whitespace-only counts as absent).
|
||||
if let Ok(custom) = crate::configstore::ConfigStore::instance().get(None, "DiskCachePath") {
|
||||
if !custom.trim().is_empty() {
|
||||
return custom;
|
||||
}
|
||||
}
|
||||
Path::new(
|
||||
&FileFunctions::new()
|
||||
.get_configuration_location()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.join("mediacache")
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ mod tests {
|
||||
assert!(cur.get_video_params().unwrap().is_null());
|
||||
|
||||
// Store with a destructor.
|
||||
let p1 = 0x1 as *mut c_void;
|
||||
let p1 = std::ptr::dangling_mut::<c_void>();
|
||||
assert!(cur.set_video_params(p1, Some(count_destroy)).is_ok());
|
||||
assert_eq!(cur.get_video_params().unwrap(), p1);
|
||||
|
||||
|
||||
@@ -271,10 +271,10 @@ impl SubtitleParams {
|
||||
out.push_str("<subtitle");
|
||||
out.push_str(" in=\"");
|
||||
out.push_str(&escape_attribute(&rational_to_string(sub.in_time)));
|
||||
out.push_str("\"");
|
||||
out.push('"');
|
||||
out.push_str(" out=\"");
|
||||
out.push_str(&escape_attribute(&rational_to_string(sub.out_time)));
|
||||
out.push_str("\"");
|
||||
out.push('"');
|
||||
out.push('>');
|
||||
out.push_str(&escape_text(&sub.text));
|
||||
out.push_str("</subtitle>");
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
//! Textures and CPU frames.
|
||||
|
||||
use crate::Rational;
|
||||
use crate::PixelFormat;
|
||||
use crate::Rational;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::backend::{BackendKind, GpuContextLike};
|
||||
@@ -385,7 +385,7 @@ mod tests {
|
||||
let pod = f.video_params();
|
||||
assert_eq!(pod.width, 3);
|
||||
assert_eq!(pod.pixel_aspect_num, 2);
|
||||
assert_eq!(f.is_float(), true);
|
||||
assert!(f.is_float());
|
||||
assert_eq!(f.pixel_count(), 6);
|
||||
f.allocate();
|
||||
assert!(!f.data().is_null());
|
||||
|
||||
@@ -703,9 +703,9 @@ impl VideoParams {
|
||||
let divider = squared_divider.sqrt();
|
||||
|
||||
if divider <= DIVIDERS[0] as f64 {
|
||||
return DIVIDERS[0];
|
||||
DIVIDERS[0]
|
||||
} else if divider >= DIVIDERS[7] as f64 {
|
||||
return DIVIDERS[7];
|
||||
DIVIDERS[7]
|
||||
} else {
|
||||
for i in 1..DIVIDERS.len() {
|
||||
let prev = DIVIDERS[i - 1];
|
||||
@@ -716,7 +716,7 @@ impl VideoParams {
|
||||
return if prev_diff < next_diff { prev } else { next };
|
||||
}
|
||||
}
|
||||
return 1; // fallback, unreachable
|
||||
1 // fallback, unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1075,7 +1075,7 @@ fn format_g(value: f64) -> String {
|
||||
e += 1;
|
||||
}
|
||||
|
||||
let body = if PREC > e && e >= -4 {
|
||||
let body = if (-4..PREC).contains(&e) {
|
||||
let decimals = (PREC - 1 - e).max(0) as usize;
|
||||
trim_frac_trailing(format!("{:.*}", decimals, v))
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user