1use std::{
8 ffi::CStr,
9 marker::PhantomData,
10 mem::MaybeUninit,
11 ops::{Deref, DerefMut},
12 ptr::{NonNull, null_mut},
13};
14
15use bon::bon;
16use core_builder::State;
17
18use crate::{
19 AudioInfo, ColorFamily, SampleType, VideoInfo,
20 api::Api,
21 ffi,
22 frame::{
23 AudioFormat, AudioFrame, FormatName, Frame, VideoFormat, VideoFrame, internal::FrameFromPtr,
24 },
25 function::Function,
26 map::{Map, MapRef},
27 node::{Dependencies, Filter, internal::FilterExtern},
28 plugin::{Plugin, Plugins},
29};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub struct CoreRef<'c> {
33 handle: *const ffi::VSCore,
34 api: Api,
35 marker: PhantomData<&'c ()>,
36}
37
38impl CoreRef<'_> {
39 #[must_use]
40 pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSCore, api: Api) -> Self {
41 Self {
42 handle: ptr.cast_mut(),
43 api,
44 marker: PhantomData,
45 }
46 }
47}
48
49impl AsRef<Core> for CoreRef<'_> {
50 fn as_ref(&self) -> &Core {
51 unsafe { &*std::ptr::from_ref(self).cast() }
52 }
53}
54
55impl Deref for CoreRef<'_> {
56 type Target = Core;
57
58 fn deref(&self) -> &Self::Target {
59 unsafe { &*std::ptr::from_ref(self).cast() }
60 }
61}
62
63impl DerefMut for CoreRef<'_> {
64 fn deref_mut(&mut self) -> &mut Self::Target {
65 unsafe { &mut *std::ptr::from_mut(self).cast() }
66 }
67}
68
69#[derive(Debug, PartialEq, Eq, Hash)]
70pub struct Core {
71 handle: *const ffi::VSCore,
72 api: Api,
73}
74
75impl Core {
76 #[must_use]
77 pub fn as_ptr(&self) -> *mut ffi::VSCore {
78 self.handle.cast_mut()
79 }
80
81 pub fn set_max_cache_size(&mut self, size: i64) {
82 unsafe {
83 (self.api.setMaxCacheSize)(size, self.as_ptr());
84 }
85 }
86
87 pub fn set_thread_count(&mut self, count: i32) {
88 unsafe {
89 (self.api.setThreadCount)(count, self.as_ptr());
90 }
91 }
92
93 #[must_use]
94 pub fn get_info(&self) -> ffi::VSCoreInfo {
95 unsafe {
96 let mut info = MaybeUninit::uninit();
97 (self.api.getCoreInfo)(self.as_ptr(), info.as_mut_ptr());
98 info.assume_init()
99 }
100 }
101
102 pub fn create_video_filter<F: Filter>(
106 &mut self,
107 out: MapRef,
108 name: &CStr,
109 info: &VideoInfo,
110 filter: Box<F>,
111 dependencies: &Dependencies,
112 ) {
113 debug_assert!(!out.as_ptr().is_null());
114 unsafe {
115 (self.api.createVideoFilter)(
116 out.as_ptr(),
117 name.as_ptr(),
118 info,
119 F::filter_get_frame,
120 Some(F::filter_free),
121 F::FILTER_MODE,
122 dependencies.as_ptr(),
123 dependencies.len().try_into().unwrap(),
124 Box::into_raw(filter).cast(),
125 self.as_ptr(),
126 );
127 }
128 }
129
130 pub fn create_audio_filter<F: Filter>(
134 &mut self,
135 out: &mut MapRef,
136 name: &CStr,
137 info: &AudioInfo,
138 filter: F,
139 dependencies: &Dependencies,
140 ) {
141 let filter = Box::new(filter);
142 unsafe {
143 (self.api.createAudioFilter)(
144 out.as_ptr(),
145 name.as_ptr(),
146 info,
147 F::filter_get_frame,
148 Some(F::filter_free),
149 F::FILTER_MODE,
150 dependencies.as_ptr(),
151 dependencies.len().try_into().unwrap(),
152 Box::into_raw(filter).cast(),
153 self.as_ptr(),
154 );
155 }
156 }
157
158 #[must_use]
159 pub fn new_video_frame(
160 &self,
161 format: &VideoFormat,
162 width: i32,
163 height: i32,
164 prop_src: Option<&VideoFrame>,
165 ) -> VideoFrame {
166 unsafe {
167 let ptr = (self.api.newVideoFrame)(
168 format,
169 width,
170 height,
171 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
172 self.as_ptr(),
173 );
174 VideoFrame::from_ptr(ptr, self.api)
175 }
176 }
177
178 #[must_use]
179 pub fn new_video_frame2(
180 &self,
181 format: &VideoFormat,
182 width: i32,
183 height: i32,
184 plane_src: &[*const ffi::VSFrame],
185 planes: &[i32],
186 prop_src: Option<&VideoFrame>,
187 ) -> VideoFrame {
188 unsafe {
189 let ptr = (self.api.newVideoFrame2)(
190 format,
191 width,
192 height,
193 plane_src.as_ptr(),
194 planes.as_ptr(),
195 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
196 self.as_ptr(),
197 );
198 VideoFrame::from_ptr(ptr, self.api)
199 }
200 }
201
202 #[must_use]
203 pub fn new_audio_frame(
204 &self,
205 format: &AudioFormat,
206 num_samples: i32,
207 prop_src: Option<&AudioFrame>,
208 ) -> AudioFrame {
209 unsafe {
210 let ptr = (self.api.newAudioFrame)(
211 format,
212 num_samples,
213 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
214 self.as_ptr(),
215 );
216 AudioFrame::from_ptr(ptr, self.api)
217 }
218 }
219
220 #[must_use]
221 pub fn new_audio_frame2(
222 &self,
223 format: &AudioFormat,
224 num_samples: i32,
225 channel_src: &[*const ffi::VSFrame],
226 channels: &[i32],
227 prop_src: Option<&AudioFrame>,
228 ) -> AudioFrame {
229 unsafe {
230 let ptr = (self.api.newAudioFrame2)(
231 format,
232 num_samples,
233 channel_src.as_ptr(),
234 channels.as_ptr(),
235 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
236 self.as_ptr(),
237 );
238 AudioFrame::from_ptr(ptr, self.api)
239 }
240 }
241
242 #[must_use]
243 pub fn copy_frame<F: Frame>(&self, frame: &F) -> F {
244 unsafe {
245 F::from_ptr(
246 (self.api.copyFrame)(frame.as_ptr(), self.as_ptr()),
247 self.api,
248 )
249 }
250 }
251
252 #[must_use]
253 pub fn query_video_format(
254 &self,
255 color_family: ColorFamily,
256 sample_type: SampleType,
257 bits_per_sample: i32,
258 subsampling_w: i32,
259 subsampling_h: i32,
260 ) -> VideoFormat {
261 unsafe {
262 let mut format = MaybeUninit::uninit();
263 (self.api.queryVideoFormat)(
264 format.as_mut_ptr(),
265 color_family,
266 sample_type,
267 bits_per_sample,
268 subsampling_w,
269 subsampling_h,
270 self.as_ptr(),
271 );
272 format.assume_init()
273 }
274 }
275
276 #[must_use]
277 pub fn get_video_format_name(&self, format: &VideoFormat) -> Option<String> {
278 let mut buffer = FormatName::new();
279 if 0 == unsafe { (self.api.getVideoFormatName)(format, buffer.as_mut_ptr().cast()) } {
280 None
281 } else {
282 Some(buffer.to_string())
283 }
284 }
285
286 #[must_use]
287 pub fn query_audio_format(
288 &self,
289 sample_type: SampleType,
290 bits_per_sample: i32,
291 channel_layout: u64,
292 ) -> AudioFormat {
293 unsafe {
294 let mut format = MaybeUninit::uninit();
295 (self.api.queryAudioFormat)(
296 format.as_mut_ptr(),
297 sample_type,
298 bits_per_sample,
299 channel_layout,
300 self.as_ptr(),
301 );
302 format.assume_init()
303 }
304 }
305
306 #[must_use]
307 pub fn get_audio_format_name(&self, format: &AudioFormat) -> Option<String> {
308 let mut buffer = FormatName::new();
309 if 0 == unsafe { (self.api.getAudioFormatName)(format, buffer.as_mut_ptr().cast()) } {
310 None
311 } else {
312 Some(buffer.to_string())
313 }
314 }
315
316 #[must_use]
317 pub fn query_video_format_id(
318 &self,
319 color_family: ColorFamily,
320 sample_type: SampleType,
321 bits_per_sample: i32,
322 subsampling_w: i32,
323 subsampling_h: i32,
324 ) -> u32 {
325 unsafe {
326 (self.api.queryVideoFormatID)(
327 color_family,
328 sample_type,
329 bits_per_sample,
330 subsampling_w,
331 subsampling_h,
332 self.as_ptr(),
333 )
334 }
335 }
336
337 #[must_use]
338 pub fn get_video_format_by_id(&self, id: u32) -> VideoFormat {
339 unsafe {
340 let mut format = MaybeUninit::uninit();
341 (self.api.getVideoFormatByID)(format.as_mut_ptr(), id, self.as_ptr());
342 format.assume_init()
343 }
344 }
345
346 pub fn create_function<T>(
347 &mut self,
348 func: ffi::VSPublicFunction,
349 data: Box<T>,
350 free: ffi::VSFreeFunctionData,
351 ) -> Function {
352 unsafe {
353 Function::from_ptr(
354 (self.api.createFunction)(func, Box::into_raw(data).cast(), free, self.as_ptr()),
355 self.api,
356 )
357 }
358 }
359
360 #[must_use]
361 pub fn get_plugin_by_id(&self, id: &CStr) -> Option<Plugin> {
362 unsafe {
363 NonNull::new((self.api.getPluginByID)(id.as_ptr(), self.as_ptr()))
364 .map(|p| Plugin::new(p, self.api))
365 }
366 }
367
368 #[must_use]
369 pub fn get_plugin_by_namespace(&self, ns: &CStr) -> Option<Plugin> {
370 unsafe {
371 NonNull::new((self.api.getPluginByNamespace)(ns.as_ptr(), self.as_ptr()))
372 .map(|p| Plugin::new(p, self.api))
373 }
374 }
375
376 #[must_use]
377 pub fn plugins(&self) -> Plugins<'_> {
378 Plugins::new(self)
379 }
380
381 pub fn log(&mut self, level: ffi::VSMessageType, msg: &CStr) {
382 unsafe {
383 (self.api.logMessage)(level, msg.as_ptr(), self.as_ptr());
384 }
385 }
386}
387
388impl Drop for Core {
389 fn drop(&mut self) {
390 unsafe {
391 (self.api.freeCore)(self.handle.cast_mut());
392 }
393 }
394}
395
396impl Core {
399 unsafe fn new_with(flags: i32, api: Api) -> Self {
400 let core = unsafe { (api.createCore)(flags) };
401 Self { handle: core, api }
402 }
403
404 #[must_use]
405 pub fn api(&self) -> Api {
406 self.api
407 }
408
409 #[must_use]
410 pub fn create_map(&self) -> Map {
411 unsafe {
412 let ptr = (self.api.createMap)();
413 Map::from_ptr(ptr, self.api)
414 }
415 }
416}
417
418#[bon]
421impl Core {
422 #[builder]
423 pub fn new(
424 #[builder(field)] flags: i32,
425 max_cache_size: Option<i64>,
426 thread_count: Option<i32>,
427 #[cfg(feature = "link-library")]
428 #[builder(default)]
429 api: Api,
430 #[cfg(not(feature = "link-library"))] api: Api,
431 ) -> Self {
432 let mut core = unsafe { Core::new_with(flags, api) };
433 if let Some(size) = max_cache_size {
434 core.set_max_cache_size(size);
435 }
436 if let Some(count) = thread_count {
437 core.set_thread_count(count);
438 }
439
440 core
441 }
442}
443
444impl<S: State> CoreBuilder<S> {
445 pub fn enable_graph_inspection(mut self) -> Self {
446 self.flags |= ffi::VSCoreCreationFlags::EnableGraphInspection as i32;
447 self
448 }
449
450 pub fn disable_auto_loading(mut self) -> Self {
451 self.flags |= ffi::VSCoreCreationFlags::DisableAutoLoading as i32;
452 self
453 }
454
455 pub fn disable_library_unloading(mut self) -> Self {
456 self.flags |= ffi::VSCoreCreationFlags::DisableLibraryUnloading as i32;
457 self
458 }
459}
460
461#[cfg(test)]
462#[cfg(feature = "link-library")]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn builder() {
468 let api = Api::default();
469 let core = Core::builder()
470 .api(api)
471 .enable_graph_inspection()
472 .disable_auto_loading()
473 .disable_library_unloading()
474 .max_cache_size(1024)
475 .thread_count(4)
476 .build();
477 assert_eq!(core.get_info().max_framebuffer_size, 1024);
478 assert_eq!(core.get_info().num_threads, 4);
479 }
480}