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 #[cfg(feature = "vs-42")]
105 #[must_use]
106 pub fn get_info2(&self) -> ffi::VSCoreInfo2 {
107 unsafe {
108 let mut info = MaybeUninit::uninit();
109 (self.api.getCoreInfo2)(self.as_ptr(), info.as_mut_ptr());
110 info.assume_init()
111 }
112 }
113
114 pub fn create_video_filter<F: Filter>(
118 &mut self,
119 out: MapRef,
120 name: &CStr,
121 info: &VideoInfo,
122 filter: Box<F>,
123 dependencies: &Dependencies,
124 ) {
125 debug_assert!(!out.as_ptr().is_null());
126 unsafe {
127 (self.api.createVideoFilter)(
128 out.as_ptr(),
129 name.as_ptr(),
130 info,
131 F::filter_get_frame,
132 Some(F::filter_free),
133 F::FILTER_MODE,
134 dependencies.as_ptr(),
135 dependencies.len().try_into().unwrap(),
136 Box::into_raw(filter).cast(),
137 self.as_ptr(),
138 );
139 }
140 }
141
142 pub fn create_audio_filter<F: Filter>(
146 &mut self,
147 out: &mut MapRef,
148 name: &CStr,
149 info: &AudioInfo,
150 filter: F,
151 dependencies: &Dependencies,
152 ) {
153 let filter = Box::new(filter);
154 unsafe {
155 (self.api.createAudioFilter)(
156 out.as_ptr(),
157 name.as_ptr(),
158 info,
159 F::filter_get_frame,
160 Some(F::filter_free),
161 F::FILTER_MODE,
162 dependencies.as_ptr(),
163 dependencies.len().try_into().unwrap(),
164 Box::into_raw(filter).cast(),
165 self.as_ptr(),
166 );
167 }
168 }
169
170 #[must_use]
171 pub fn new_video_frame(
172 &self,
173 format: &VideoFormat,
174 width: i32,
175 height: i32,
176 prop_src: Option<&VideoFrame>,
177 ) -> VideoFrame {
178 unsafe {
179 let ptr = (self.api.newVideoFrame)(
180 format,
181 width,
182 height,
183 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
184 self.as_ptr(),
185 );
186 VideoFrame::from_ptr(ptr, self.api)
187 }
188 }
189
190 #[must_use]
191 pub fn new_video_frame2(
192 &self,
193 format: &VideoFormat,
194 width: i32,
195 height: i32,
196 plane_src: &[*const ffi::VSFrame],
197 planes: &[i32],
198 prop_src: Option<&VideoFrame>,
199 ) -> VideoFrame {
200 unsafe {
201 let ptr = (self.api.newVideoFrame2)(
202 format,
203 width,
204 height,
205 plane_src.as_ptr(),
206 planes.as_ptr(),
207 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
208 self.as_ptr(),
209 );
210 VideoFrame::from_ptr(ptr, self.api)
211 }
212 }
213
214 #[must_use]
215 pub fn new_audio_frame(
216 &self,
217 format: &AudioFormat,
218 num_samples: i32,
219 prop_src: Option<&AudioFrame>,
220 ) -> AudioFrame {
221 unsafe {
222 let ptr = (self.api.newAudioFrame)(
223 format,
224 num_samples,
225 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
226 self.as_ptr(),
227 );
228 AudioFrame::from_ptr(ptr, self.api)
229 }
230 }
231
232 #[must_use]
233 pub fn new_audio_frame2(
234 &self,
235 format: &AudioFormat,
236 num_samples: i32,
237 channel_src: &[*const ffi::VSFrame],
238 channels: &[i32],
239 prop_src: Option<&AudioFrame>,
240 ) -> AudioFrame {
241 unsafe {
242 let ptr = (self.api.newAudioFrame2)(
243 format,
244 num_samples,
245 channel_src.as_ptr(),
246 channels.as_ptr(),
247 prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
248 self.as_ptr(),
249 );
250 AudioFrame::from_ptr(ptr, self.api)
251 }
252 }
253
254 #[must_use]
255 pub fn copy_frame<F: Frame>(&self, frame: &F) -> F {
256 unsafe {
257 F::from_ptr(
258 (self.api.copyFrame)(frame.as_ptr(), self.as_ptr()),
259 self.api,
260 )
261 }
262 }
263
264 #[must_use]
265 pub fn query_video_format(
266 &self,
267 color_family: ColorFamily,
268 sample_type: SampleType,
269 bits_per_sample: i32,
270 subsampling_w: i32,
271 subsampling_h: i32,
272 ) -> VideoFormat {
273 unsafe {
274 let mut format = MaybeUninit::uninit();
275 (self.api.queryVideoFormat)(
276 format.as_mut_ptr(),
277 color_family,
278 sample_type,
279 bits_per_sample,
280 subsampling_w,
281 subsampling_h,
282 self.as_ptr(),
283 );
284 format.assume_init()
285 }
286 }
287
288 #[must_use]
289 pub fn get_video_format_name(&self, format: &VideoFormat) -> Option<String> {
290 let mut buffer = FormatName::new();
291 if 0 == unsafe { (self.api.getVideoFormatName)(format, buffer.as_mut_ptr().cast()) } {
292 None
293 } else {
294 Some(buffer.to_string())
295 }
296 }
297
298 #[must_use]
299 pub fn query_audio_format(
300 &self,
301 sample_type: SampleType,
302 bits_per_sample: i32,
303 channel_layout: u64,
304 ) -> AudioFormat {
305 unsafe {
306 let mut format = MaybeUninit::uninit();
307 (self.api.queryAudioFormat)(
308 format.as_mut_ptr(),
309 sample_type,
310 bits_per_sample,
311 channel_layout,
312 self.as_ptr(),
313 );
314 format.assume_init()
315 }
316 }
317
318 #[must_use]
319 pub fn get_audio_format_name(&self, format: &AudioFormat) -> Option<String> {
320 let mut buffer = FormatName::new();
321 if 0 == unsafe { (self.api.getAudioFormatName)(format, buffer.as_mut_ptr().cast()) } {
322 None
323 } else {
324 Some(buffer.to_string())
325 }
326 }
327
328 #[must_use]
329 pub fn query_video_format_id(
330 &self,
331 color_family: ColorFamily,
332 sample_type: SampleType,
333 bits_per_sample: i32,
334 subsampling_w: i32,
335 subsampling_h: i32,
336 ) -> u32 {
337 unsafe {
338 (self.api.queryVideoFormatID)(
339 color_family,
340 sample_type,
341 bits_per_sample,
342 subsampling_w,
343 subsampling_h,
344 self.as_ptr(),
345 )
346 }
347 }
348
349 #[must_use]
350 pub fn get_video_format_by_id(&self, id: u32) -> VideoFormat {
351 unsafe {
352 let mut format = MaybeUninit::uninit();
353 (self.api.getVideoFormatByID)(format.as_mut_ptr(), id, self.as_ptr());
354 format.assume_init()
355 }
356 }
357
358 pub fn create_function<T>(
359 &mut self,
360 func: ffi::VSPublicFunction,
361 data: Box<T>,
362 free: ffi::VSFreeFunctionData,
363 ) -> Function {
364 unsafe {
365 Function::from_ptr(
366 (self.api.createFunction)(func, Box::into_raw(data).cast(), free, self.as_ptr()),
367 self.api,
368 )
369 }
370 }
371
372 #[must_use]
373 pub fn get_plugin_by_id(&self, id: &CStr) -> Option<Plugin> {
374 unsafe {
375 NonNull::new((self.api.getPluginByID)(id.as_ptr(), self.as_ptr()))
376 .map(|p| Plugin::new(p, self.api))
377 }
378 }
379
380 #[must_use]
381 pub fn get_plugin_by_namespace(&self, ns: &CStr) -> Option<Plugin> {
382 unsafe {
383 NonNull::new((self.api.getPluginByNamespace)(ns.as_ptr(), self.as_ptr()))
384 .map(|p| Plugin::new(p, self.api))
385 }
386 }
387
388 #[must_use]
389 pub fn plugins(&self) -> Plugins<'_> {
390 Plugins::new(self)
391 }
392
393 pub fn log(&mut self, level: ffi::VSMessageType, msg: &CStr) {
394 unsafe {
395 (self.api.logMessage)(level, msg.as_ptr(), self.as_ptr());
396 }
397 }
398}
399
400impl Drop for Core {
401 fn drop(&mut self) {
402 unsafe {
403 (self.api.freeCore)(self.handle.cast_mut());
404 }
405 }
406}
407
408impl Core {
411 unsafe fn new_with(flags: i32, api: Api) -> Self {
412 let core = unsafe { (api.createCore)(flags) };
413 Self { handle: core, api }
414 }
415
416 #[must_use]
417 pub fn api(&self) -> Api {
418 self.api
419 }
420
421 #[must_use]
422 pub fn create_map(&self) -> Map {
423 unsafe {
424 let ptr = (self.api.createMap)();
425 Map::from_ptr(ptr, self.api)
426 }
427 }
428}
429
430#[bon]
433impl Core {
434 #[builder]
435 pub fn new(
436 #[builder(field)] flags: i32,
437 max_cache_size: Option<i64>,
438 thread_count: Option<i32>,
439 #[cfg(feature = "link-vs")]
440 #[builder(default)]
441 api: Api,
442 #[cfg(not(feature = "link-vs"))] api: Api,
443 ) -> Self {
444 let mut core = unsafe { Core::new_with(flags, api) };
445 if let Some(size) = max_cache_size {
446 core.set_max_cache_size(size);
447 }
448 if let Some(count) = thread_count {
449 core.set_thread_count(count);
450 }
451
452 core
453 }
454}
455
456impl<S: State> CoreBuilder<S> {
457 pub fn enable_graph_inspection(mut self) -> Self {
458 self.flags |= ffi::VSCoreCreationFlags::EnableGraphInspection as i32;
459 self
460 }
461
462 pub fn disable_auto_loading(mut self) -> Self {
463 self.flags |= ffi::VSCoreCreationFlags::DisableAutoLoading as i32;
464 self
465 }
466
467 pub fn disable_library_unloading(mut self) -> Self {
468 self.flags |= ffi::VSCoreCreationFlags::DisableLibraryUnloading as i32;
469 self
470 }
471
472 #[cfg(feature = "vs-42")]
474 pub fn enable_frame_ref_debug(mut self) -> Self {
475 self.flags |= ffi::VSCoreCreationFlags::EnableFrameRefDebug as i32;
476 self
477 }
478}
479
480#[cfg(test)]
481#[cfg(feature = "link-vs")]
482mod tests {
483 use super::*;
484
485 #[test]
486 fn builder() {
487 let api = Api::default();
488 let core = Core::builder()
489 .api(api)
490 .enable_graph_inspection()
491 .disable_auto_loading()
492 .disable_library_unloading()
493 .max_cache_size(1024)
494 .thread_count(4)
495 .build();
496 assert_eq!(core.get_info().max_framebuffer_size, 1024);
497 assert_eq!(core.get_info().num_threads, 4);
498 }
499
500 #[cfg(feature = "vs-42")]
501 #[test]
502 fn info2_reports_creation_flags() {
503 let core = Core::builder()
504 .api(Api::default())
505 .enable_graph_inspection()
506 .disable_auto_loading()
507 .build();
508
509 let info = core.get_info2();
510 let expected = ffi::VSCoreCreationFlags::EnableGraphInspection
511 | ffi::VSCoreCreationFlags::DisableAutoLoading;
512 assert_eq!(info.creation_flags, expected);
513 assert_eq!(info.core, core.get_info().core);
514 }
515}