1 /*
2 * Copyright (c) 2012-2017 Fredrik Mellbin
3 *
4 * This file is part of VapourSynth.
5 *
6 * VapourSynth is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * VapourSynth is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with VapourSynth; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 
21 #include "vscore.h"
22 #include "VSHelper.h"
23 #include "version.h"
24 #include "cpufeatures.h"
25 #ifndef VS_TARGET_OS_WINDOWS
26 #include <dirent.h>
27 #include <cstddef>
28 #include <unistd.h>
29 #include "settings.h"
30 #endif
31 #include <cassert>
32 #include <queue>
33 
34 #ifdef VS_TARGET_CPU_X86
35 #include "x86utils.h"
36 #endif
37 
38 #ifdef VS_TARGET_OS_WINDOWS
39 #include <shlobj.h>
40 #include <locale>
41 #include "../common/vsutf16.h"
42 #endif
43 
44 // Internal filter headers
45 #include "internalfilters.h"
46 #include "cachefilter.h"
47 
48 #ifdef VS_TARGET_OS_DARWIN
49 #define thread_local __thread
50 #endif
51 
isAlpha(char c)52 static inline bool isAlpha(char c) {
53     return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
54 }
55 
isAlphaNumUnderscore(char c)56 static inline bool isAlphaNumUnderscore(char c) {
57     return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
58 }
59 
isValidIdentifier(const std::string & s)60 static bool isValidIdentifier(const std::string &s) {
61     size_t len = s.length();
62     if (!len)
63         return false;
64 
65     if (!isAlpha(s[0]))
66         return false;
67     for (size_t i = 1; i < len; i++)
68         if (!isAlphaNumUnderscore(s[i]))
69             return false;
70     return true;
71 }
72 
73 #ifdef VS_TARGET_OS_WINDOWS
readRegistryValue(const wchar_t * keyName,const wchar_t * valueName)74 static std::wstring readRegistryValue(const wchar_t *keyName, const wchar_t *valueName) {
75     HKEY hKey;
76     LONG lRes = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &hKey);
77     if (lRes != ERROR_SUCCESS) {
78         lRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &hKey);
79         if (lRes != ERROR_SUCCESS)
80             return std::wstring();
81     }
82     WCHAR szBuffer[512];
83     DWORD dwBufferSize = sizeof(szBuffer);
84     ULONG nError;
85     nError = RegQueryValueEx(hKey, valueName, 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
86     RegCloseKey(hKey);
87     if (ERROR_SUCCESS == nError)
88         return szBuffer;
89     return std::wstring();
90 }
91 #endif
92 
FrameContext(int n,int index,VSNode * clip,const PFrameContext & upstreamContext)93 FrameContext::FrameContext(int n, int index, VSNode *clip, const PFrameContext &upstreamContext) :
94     reqOrder(upstreamContext->reqOrder), numFrameRequests(0), n(n), clip(clip), upstreamContext(upstreamContext), userData(nullptr), frameDone(nullptr), error(false), lockOnOutput(true), node(nullptr), lastCompletedN(-1), index(index), lastCompletedNode(nullptr), frameContext(nullptr) {
95 }
96 
FrameContext(int n,int index,VSNodeRef * node,VSFrameDoneCallback frameDone,void * userData,bool lockOnOutput)97 FrameContext::FrameContext(int n, int index, VSNodeRef *node, VSFrameDoneCallback frameDone, void *userData, bool lockOnOutput) :
98     reqOrder(0), numFrameRequests(0), n(n), clip(node->clip.get()), userData(userData), frameDone(frameDone), error(false), lockOnOutput(lockOnOutput), node(node), lastCompletedN(-1), index(index), lastCompletedNode(nullptr), frameContext(nullptr) {
99 }
100 
setError(const std::string & errorMsg)101 bool FrameContext::setError(const std::string &errorMsg) {
102     bool prevState = error;
103     error = true;
104     if (!prevState)
105         errorMessage = errorMsg;
106     return prevState;
107 }
108 
109 ///////////////
110 
ExtFunction(VSPublicFunction func,void * userData,VSFreeFuncData free,VSCore * core,const VSAPI * vsapi)111 ExtFunction::ExtFunction(VSPublicFunction func, void *userData, VSFreeFuncData free, VSCore *core, const VSAPI *vsapi) : func(func), userData(userData), free(free), core(core), vsapi(vsapi) {
112     core->functionInstanceCreated();
113 }
114 
~ExtFunction()115 ExtFunction::~ExtFunction() {
116     if (free)
117         free(userData);
118     core->functionInstanceDestroyed();
119 }
120 
call(const VSMap * in,VSMap * out)121 void ExtFunction::call(const VSMap *in, VSMap *out) {
122     func(in, out, userData, core, vsapi);
123 }
124 
125 ///////////////
126 
VSVariant(VSVType vtype)127 VSVariant::VSVariant(VSVType vtype) : vtype(vtype), internalSize(0), storage(nullptr) {
128 }
129 
VSVariant(const VSVariant & v)130 VSVariant::VSVariant(const VSVariant &v) : vtype(v.vtype), internalSize(v.internalSize), storage(nullptr) {
131     if (internalSize) {
132         switch (vtype) {
133         case VSVariant::vInt:
134             storage = new IntList(*reinterpret_cast<IntList *>(v.storage)); break;
135         case VSVariant::vFloat:
136             storage = new FloatList(*reinterpret_cast<FloatList *>(v.storage)); break;
137         case VSVariant::vData:
138             storage = new DataList(*reinterpret_cast<DataList *>(v.storage)); break;
139         case VSVariant::vNode:
140             storage = new NodeList(*reinterpret_cast<NodeList *>(v.storage)); break;
141         case VSVariant::vFrame:
142             storage = new FrameList(*reinterpret_cast<FrameList *>(v.storage)); break;
143         case VSVariant::vMethod:
144             storage = new FuncList(*reinterpret_cast<FuncList *>(v.storage)); break;
145         default:;
146         }
147     }
148 }
149 
VSVariant(VSVariant && v)150 VSVariant::VSVariant(VSVariant &&v) : vtype(v.vtype), internalSize(v.internalSize), storage(v.storage) {
151     v.vtype = vUnset;
152     v.storage = nullptr;
153     v.internalSize = 0;
154 }
155 
~VSVariant()156 VSVariant::~VSVariant() {
157     if (storage) {
158         switch (vtype) {
159         case VSVariant::vInt:
160             delete reinterpret_cast<IntList *>(storage); break;
161         case VSVariant::vFloat:
162             delete reinterpret_cast<FloatList *>(storage); break;
163         case VSVariant::vData:
164             delete reinterpret_cast<DataList *>(storage); break;
165         case VSVariant::vNode:
166             delete reinterpret_cast<NodeList *>(storage); break;
167         case VSVariant::vFrame:
168             delete reinterpret_cast<FrameList *>(storage); break;
169         case VSVariant::vMethod:
170             delete reinterpret_cast<FuncList *>(storage); break;
171         default:;
172         }
173     }
174 }
175 
size() const176 size_t VSVariant::size() const {
177     return internalSize;
178 }
179 
getType() const180 VSVariant::VSVType VSVariant::getType() const {
181     return vtype;
182 }
183 
append(int64_t val)184 void VSVariant::append(int64_t val) {
185     initStorage(vInt);
186     reinterpret_cast<IntList *>(storage)->push_back(val);
187     internalSize++;
188 }
189 
append(double val)190 void VSVariant::append(double val) {
191     initStorage(vFloat);
192     reinterpret_cast<FloatList *>(storage)->push_back(val);
193     internalSize++;
194 }
195 
append(const std::string & val)196 void VSVariant::append(const std::string &val) {
197     initStorage(vData);
198     reinterpret_cast<DataList *>(storage)->push_back(std::make_shared<std::string>(val));
199     internalSize++;
200 }
201 
append(const VSNodeRef & val)202 void VSVariant::append(const VSNodeRef &val) {
203     initStorage(vNode);
204     reinterpret_cast<NodeList *>(storage)->push_back(val);
205     internalSize++;
206 }
207 
append(const PVideoFrame & val)208 void VSVariant::append(const PVideoFrame &val) {
209     initStorage(vFrame);
210     reinterpret_cast<FrameList *>(storage)->push_back(val);
211     internalSize++;
212 }
213 
append(const PExtFunction & val)214 void VSVariant::append(const PExtFunction &val) {
215     initStorage(vMethod);
216     reinterpret_cast<FuncList *>(storage)->push_back(val);
217     internalSize++;
218 }
219 
initStorage(VSVType t)220 void VSVariant::initStorage(VSVType t) {
221     assert(vtype == vUnset || vtype == t);
222     vtype = t;
223     if (!storage) {
224         switch (t) {
225         case VSVariant::vInt:
226             storage = new IntList(); break;
227         case VSVariant::vFloat:
228             storage = new FloatList(); break;
229         case VSVariant::vData:
230             storage = new DataList(); break;
231         case VSVariant::vNode:
232             storage = new NodeList(); break;
233         case VSVariant::vFrame:
234             storage = new FrameList(); break;
235         case VSVariant::vMethod:
236             storage = new FuncList(); break;
237         default:;
238         }
239     }
240 }
241 
242 ///////////////
243 
isWindowsLargePageBroken()244 static bool isWindowsLargePageBroken() {
245     // A Windows bug exists where a VirtualAlloc call immediately after VirtualFree
246     // yields a page that has not been zeroed. The returned page is asynchronously
247     // zeroed a few milliseconds later, resulting in memory corruption. The same bug
248     // allows VirtualFree to return before the page has been unmapped.
249     static const bool broken = []() -> bool {
250 #ifdef VS_TARGET_OS_WINDOWS
251         size_t size = GetLargePageMinimum();
252 
253         for (int i = 0; i < 100; ++i) {
254             void *ptr = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
255             if (!ptr)
256                 return true;
257 
258             for (size_t n = 0; n < 64; ++n) {
259                 if (static_cast<uint8_t *>(ptr)[n]) {
260                     vsWarning("Windows 10 VirtualAlloc bug detected: update to version 1803+");
261                     return true;
262                 }
263             }
264             memset(ptr, 0xFF, 64);
265 
266             if (VirtualFree(ptr, 0, MEM_RELEASE) != TRUE)
267                 return true;
268             if (!IsBadReadPtr(ptr, 1)) {
269                 vsWarning("Windows 10 VirtualAlloc bug detected: update to version 1803+");
270                 return true;
271             }
272         }
273 #endif // VS_TARGET_OS_WINDOWS
274         return false;
275     }();
276     return broken;
277 }
278 
largePageSupported()279 /* static */ bool MemoryUse::largePageSupported() {
280     // Disable large pages on 32-bit to avoid memory fragmentation.
281     if (sizeof(void *) < 8)
282         return false;
283 
284     static const bool supported = []() -> bool {
285 #ifdef VS_TARGET_OS_WINDOWS
286         HANDLE token = INVALID_HANDLE_VALUE;
287         TOKEN_PRIVILEGES priv = {};
288 
289         if (!(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)))
290             return false;
291 
292         if (!(LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &priv.Privileges[0].Luid))) {
293             CloseHandle(token);
294             return false;
295         }
296 
297         priv.PrivilegeCount = 1;
298         priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
299 
300         if (!(AdjustTokenPrivileges(token, FALSE, &priv, 0, nullptr, 0))) {
301             CloseHandle(token);
302             return false;
303         }
304 
305         CloseHandle(token);
306         return true;
307 #else
308         return false;
309 #endif // VS_TARGET_OS_WINDOWS
310     }();
311     return supported;
312 }
313 
largePageSize()314 /* static */ size_t MemoryUse::largePageSize() {
315     static const size_t size = []() -> size_t {
316 #ifdef VS_TARGET_OS_WINDOWS
317         return GetLargePageMinimum();
318 #else
319         return 2 * (1UL << 20);
320 #endif
321     }();
322     return size;
323 }
324 
allocateLargePage(size_t bytes) const325 void *MemoryUse::allocateLargePage(size_t bytes) const {
326     if (!largePageEnabled)
327         return nullptr;
328 
329     size_t granularity = largePageSize();
330     size_t allocBytes = VSFrame::alignment + bytes;
331     allocBytes = (allocBytes + (granularity - 1)) & ~(granularity - 1);
332     assert(allocBytes % granularity == 0);
333 
334     // Don't allocate a large page if it would conflict with the buffer recycling logic.
335     if (!isGoodFit(bytes, allocBytes - VSFrame::alignment))
336         return nullptr;
337 
338     void *ptr = nullptr;
339 #ifdef VS_TARGET_OS_WINDOWS
340     ptr = VirtualAlloc(nullptr, allocBytes, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
341 #else
342     ptr = vs_aligned_malloc(allocBytes, VSFrame::alignment);
343 #endif
344     if (!ptr)
345         return nullptr;
346 
347     BlockHeader *header = new (ptr) BlockHeader;
348     header->size = allocBytes - VSFrame::alignment;
349     header->large = true;
350     return ptr;
351 }
352 
freeLargePage(void * ptr) const353 void MemoryUse::freeLargePage(void *ptr) const {
354 #ifdef VS_TARGET_OS_WINDOWS
355     VirtualFree(ptr, 0, MEM_RELEASE);
356 #else
357     vs_aligned_free(ptr);
358 #endif
359 }
360 
allocateMemory(size_t bytes) const361 void *MemoryUse::allocateMemory(size_t bytes) const {
362     void *ptr = allocateLargePage(bytes);
363     if (ptr)
364         return ptr;
365 
366     ptr = vs_aligned_malloc(VSFrame::alignment + bytes, VSFrame::alignment);
367     if (!ptr)
368         vsFatal("out of memory: %zu", bytes);
369 
370     BlockHeader *header = new (ptr) BlockHeader;
371     header->size = bytes;
372     header->large = false;
373     return ptr;
374 }
375 
freeMemory(void * ptr) const376 void MemoryUse::freeMemory(void *ptr) const {
377     const BlockHeader *header = static_cast<const BlockHeader *>(ptr);
378     if (header->large)
379         freeLargePage(ptr);
380     else
381         vs_aligned_free(ptr);
382 }
383 
isGoodFit(size_t requested,size_t actual) const384 bool MemoryUse::isGoodFit(size_t requested, size_t actual) const {
385     return actual <= requested + requested / 8;
386 }
387 
add(size_t bytes)388 void MemoryUse::add(size_t bytes) {
389     used.fetch_add(bytes);
390 }
391 
subtract(size_t bytes)392 void MemoryUse::subtract(size_t bytes) {
393     used.fetch_sub(bytes);
394     if (freeOnZero && !used)
395         delete this;
396 }
397 
allocBuffer(size_t bytes)398 uint8_t *MemoryUse::allocBuffer(size_t bytes) {
399     std::lock_guard<std::mutex> lock(mutex);
400     auto iter = buffers.lower_bound(bytes);
401     if (iter != buffers.end()) {
402         if (isGoodFit(bytes, iter->first)) {
403             unusedBufferSize -= iter->first;
404             uint8_t *buf = iter->second;
405             buffers.erase(iter);
406             return buf + VSFrame::alignment;
407         }
408     }
409 
410     uint8_t *buf = static_cast<uint8_t *>(allocateMemory(bytes));
411     return buf + VSFrame::alignment;
412 }
413 
freeBuffer(uint8_t * buf)414 void MemoryUse::freeBuffer(uint8_t *buf) {
415     assert(buf);
416 
417     std::lock_guard<std::mutex> lock(mutex);
418     buf -= VSFrame::alignment;
419 
420     const BlockHeader *header = reinterpret_cast<const BlockHeader *>(buf);
421     if (!header->size)
422         vsFatal("Memory corruption detected. Windows bug?");
423 
424     buffers.emplace(std::make_pair(header->size, buf));
425     unusedBufferSize += header->size;
426 
427     size_t memoryUsed = used;
428     while (memoryUsed + unusedBufferSize > maxMemoryUse && !buffers.empty()) {
429         if (!memoryWarningIssued) {
430             vsWarning("Script exceeded memory limit. Consider raising cache size.");
431             memoryWarningIssued = true;
432         }
433         std::uniform_int_distribution<size_t> randSrc(0, buffers.size() - 1);
434         auto iter = buffers.begin();
435         std::advance(iter, randSrc(generator));
436         assert(unusedBufferSize >= iter->first);
437         unusedBufferSize -= iter->first;
438         freeMemory(iter->second);
439         buffers.erase(iter);
440     }
441 }
442 
memoryUse()443 size_t MemoryUse::memoryUse() {
444     return used;
445 }
446 
getLimit()447 size_t MemoryUse::getLimit() {
448     std::lock_guard<std::mutex> lock(mutex);
449     return maxMemoryUse;
450 }
451 
setMaxMemoryUse(int64_t bytes)452 int64_t MemoryUse::setMaxMemoryUse(int64_t bytes) {
453     std::lock_guard<std::mutex> lock(mutex);
454     if (bytes > 0 && static_cast<uint64_t>(bytes) <= SIZE_MAX)
455         maxMemoryUse = static_cast<size_t>(bytes);
456     return maxMemoryUse;
457 }
458 
isOverLimit()459 bool MemoryUse::isOverLimit() {
460     return used > maxMemoryUse;
461 }
462 
signalFree()463 void MemoryUse::signalFree() {
464     freeOnZero = true;
465     if (!used)
466         delete this;
467 }
468 
MemoryUse()469 MemoryUse::MemoryUse() : used(0), freeOnZero(false), largePageEnabled(largePageSupported()), memoryWarningIssued(false), unusedBufferSize(0) {
470     assert(VSFrame::alignment >= sizeof(BlockHeader));
471 
472     // If the Windows VirtualAlloc bug is present, it is not safe to use large pages by default,
473     // because another application could trigger the bug.
474     //if (isWindowsLargePageBroken())
475     //    largePageEnabled = false;
476 
477     // Always disable large pages at the moment
478     largePageEnabled = false;
479 
480     // 1GB
481     setMaxMemoryUse(1024 * 1024 * 1024);
482 
483     // set 4GB as default on systems with (probably) 64bit address space
484     if (sizeof(void *) >= 8)
485         setMaxMemoryUse(static_cast<int64_t>(4) * 1024 * 1024 * 1024);
486 }
487 
~MemoryUse()488 MemoryUse::~MemoryUse() {
489     for (auto &iter : buffers)
490         freeMemory(iter.second);
491 }
492 
493 ///////////////
494 
VSPlaneData(size_t dataSize,MemoryUse & mem)495 VSPlaneData::VSPlaneData(size_t dataSize, MemoryUse &mem) : refCount(1), mem(mem), size(dataSize + 2 * VSFrame::guardSpace) {
496 #ifdef VS_FRAME_POOL
497     data = mem.allocBuffer(size + 2 * VSFrame::guardSpace);
498 #else
499     data = vs_aligned_malloc<uint8_t>(size + 2 * VSFrame::guardSpace, VSFrame::alignment);
500 #endif
501     assert(data);
502     if (!data)
503         vsFatal("Failed to allocate memory for planes. Out of memory.");
504     mem.add(size);
505 #ifdef VS_FRAME_GUARD
506     for (size_t i = 0; i < VSFrame::guardSpace / sizeof(VS_FRAME_GUARD_PATTERN); i++) {
507         reinterpret_cast<uint32_t *>(data)[i] = VS_FRAME_GUARD_PATTERN;
508         reinterpret_cast<uint32_t *>(data + size - VSFrame::guardSpace)[i] = VS_FRAME_GUARD_PATTERN;
509     }
510 #endif
511 }
512 
VSPlaneData(const VSPlaneData & d)513 VSPlaneData::VSPlaneData(const VSPlaneData &d) : refCount(1), mem(d.mem), size(d.size) {
514 #ifdef VS_FRAME_POOL
515     data = mem.allocBuffer(size);
516 #else
517     data = vs_aligned_malloc<uint8_t>(size, VSFrame::alignment);
518 #endif
519     assert(data);
520     if (!data)
521         vsFatal("Failed to allocate memory for plane in copy constructor. Out of memory.");
522     mem.add(size);
523     memcpy(data, d.data, size);
524 }
525 
~VSPlaneData()526 VSPlaneData::~VSPlaneData() {
527 #ifdef VS_FRAME_POOL
528     mem.freeBuffer(data);
529 #else
530     vs_aligned_free(data);
531 #endif
532     mem.subtract(size);
533 }
534 
unique()535 bool VSPlaneData::unique() {
536     return (refCount == 1);
537 }
538 
addRef()539 void VSPlaneData::addRef() {
540     ++refCount;
541 }
542 
release()543 void VSPlaneData::release() {
544     if (!--refCount)
545         delete this;
546 }
547 
548 ///////////////
549 
VSFrame(const VSFormat * f,int width,int height,const VSFrame * propSrc,VSCore * core)550 VSFrame::VSFrame(const VSFormat *f, int width, int height, const VSFrame *propSrc, VSCore *core) : format(f), data(), width(width), height(height) {
551     if (!f)
552         vsFatal("Error in frame creation: null format");
553 
554     if (width <= 0 || height <= 0)
555         vsFatal("Error in frame creation: dimensions are negative (%dx%d)", width, height);
556 
557     if (propSrc)
558         properties = propSrc->properties;
559 
560     stride[0] = (width * (f->bytesPerSample) + (alignment - 1)) & ~(alignment - 1);
561 
562     if (f->numPlanes == 3) {
563         int plane23 = ((width >> f->subSamplingW) * (f->bytesPerSample) + (alignment - 1)) & ~(alignment - 1);
564         stride[1] = plane23;
565         stride[2] = plane23;
566     } else {
567         stride[1] = 0;
568         stride[2] = 0;
569     }
570 
571     data[0] = new VSPlaneData(stride[0] * height, *core->memory);
572     if (f->numPlanes == 3) {
573         int size23 = stride[1] * (height >> f->subSamplingH);
574         data[1] = new VSPlaneData(size23, *core->memory);
575         data[2] = new VSPlaneData(size23, *core->memory);
576     }
577 }
578 
VSFrame(const VSFormat * f,int width,int height,const VSFrame * const * planeSrc,const int * plane,const VSFrame * propSrc,VSCore * core)579 VSFrame::VSFrame(const VSFormat *f, int width, int height, const VSFrame * const *planeSrc, const int *plane, const VSFrame *propSrc, VSCore *core) : format(f), data(), width(width), height(height) {
580     if (!f)
581         vsFatal("Error in frame creation: null format");
582 
583     if (width <= 0 || height <= 0)
584         vsFatal("Error in frame creation: dimensions are negative (%dx%d)", width, height);
585 
586     if (propSrc)
587         properties = propSrc->properties;
588 
589     stride[0] = (width * (f->bytesPerSample) + (alignment - 1)) & ~(alignment - 1);
590 
591     if (f->numPlanes == 3) {
592         int plane23 = ((width >> f->subSamplingW) * (f->bytesPerSample) + (alignment - 1)) & ~(alignment - 1);
593         stride[1] = plane23;
594         stride[2] = plane23;
595     } else {
596         stride[1] = 0;
597         stride[2] = 0;
598     }
599 
600     for (int i = 0; i < format->numPlanes; i++) {
601         if (planeSrc[i]) {
602             if (plane[i] < 0 || plane[i] >= planeSrc[i]->format->numPlanes)
603                 vsFatal("Error in frame creation: plane %d does not exist in the source frame", plane[i]);
604             if (planeSrc[i]->getHeight(plane[i]) != getHeight(i) || planeSrc[i]->getWidth(plane[i]) != getWidth(i))
605                 vsFatal("Error in frame creation: dimensions of plane %d do not match. Source: %dx%d; destination: %dx%d", plane[i], planeSrc[i]->getWidth(plane[i]), planeSrc[i]->getHeight(plane[i]), getWidth(i), getHeight(i));
606             data[i] = planeSrc[i]->data[plane[i]];
607             data[i]->addRef();
608         } else {
609             if (i == 0) {
610                 data[i] = new VSPlaneData(stride[i] * height, *core->memory);
611             } else {
612                 data[i] = new VSPlaneData(stride[i] * (height >> f->subSamplingH), *core->memory);
613             }
614         }
615     }
616 }
617 
VSFrame(const VSFrame & f)618 VSFrame::VSFrame(const VSFrame &f) {
619     data[0] = f.data[0];
620     data[1] = f.data[1];
621     data[2] = f.data[2];
622     data[0]->addRef();
623     if (data[1]) {
624         data[1]->addRef();
625         data[2]->addRef();
626     }
627     format = f.format;
628     width = f.width;
629     height = f.height;
630     stride[0] = f.stride[0];
631     stride[1] = f.stride[1];
632     stride[2] = f.stride[2];
633     properties = f.properties;
634 }
635 
~VSFrame()636 VSFrame::~VSFrame() {
637     data[0]->release();
638     if (data[1]) {
639         data[1]->release();
640         data[2]->release();
641     }
642 }
643 
getStride(int plane) const644 int VSFrame::getStride(int plane) const {
645     assert(plane >= 0 && plane < 3);
646     if (plane < 0 || plane >= format->numPlanes)
647         vsFatal("Requested stride of nonexistent plane %d", plane);
648     return stride[plane];
649 }
650 
getReadPtr(int plane) const651 const uint8_t *VSFrame::getReadPtr(int plane) const {
652     if (plane < 0 || plane >= format->numPlanes)
653         vsFatal("Requested read pointer for nonexistent plane %d", plane);
654 
655     return data[plane]->data + guardSpace;
656 }
657 
getWritePtr(int plane)658 uint8_t *VSFrame::getWritePtr(int plane) {
659     if (plane < 0 || plane >= format->numPlanes)
660         vsFatal("Requested write pointer for nonexistent plane %d", plane);
661 
662     // copy the plane data if this isn't the only reference
663     if (!data[plane]->unique()) {
664         VSPlaneData *old = data[plane];
665         data[plane] = new VSPlaneData(*data[plane]);
666         old->release();
667     }
668 
669     return data[plane]->data + guardSpace;
670 }
671 
672 #ifdef VS_FRAME_GUARD
verifyGuardPattern()673 bool VSFrame::verifyGuardPattern() {
674     for (int p = 0; p < format->numPlanes; p++) {
675         for (size_t i = 0; i < guardSpace / sizeof(VS_FRAME_GUARD_PATTERN); i++) {
676             uint32_t p1 = reinterpret_cast<uint32_t *>(data[p]->data)[i];
677             uint32_t p2 = reinterpret_cast<uint32_t *>(data[p]->data + data[p]->size - guardSpace)[i];
678             if (reinterpret_cast<uint32_t *>(data[p]->data)[i] != VS_FRAME_GUARD_PATTERN ||
679                 reinterpret_cast<uint32_t *>(data[p]->data + data[p]->size - guardSpace)[i] != VS_FRAME_GUARD_PATTERN)
680                 return false;
681         }
682     }
683 
684     return true;
685 }
686 #endif
687 
688 struct split1 {
689     enum empties_t { empties_ok, no_empties };
690 };
691 
692 template <typename Container>
split(Container & result,const typename Container::value_type & s,const typename Container::value_type & delimiters,split1::empties_t empties=split1::empties_ok)693 Container& split(
694     Container& result,
695     const typename Container::value_type& s,
696     const typename Container::value_type& delimiters,
697     split1::empties_t empties = split1::empties_ok) {
698     result.clear();
699     size_t current;
700     size_t next = -1;
701     do {
702         if (empties == split1::no_empties) {
703             next = s.find_first_not_of(delimiters, next + 1);
704             if (next == Container::value_type::npos) break;
705             next -= 1;
706         }
707         current = next + 1;
708         next = s.find_first_of(delimiters, current);
709         result.push_back(s.substr(current, next - current));
710     } while (next != Container::value_type::npos);
711     return result;
712 }
713 
VSFunction(const std::string & argString,VSPublicFunction func,void * functionData)714 VSFunction::VSFunction(const std::string &argString, VSPublicFunction func, void *functionData)
715     : argString(argString), functionData(functionData), func(func) {
716     std::vector<std::string> argList;
717     split(argList, argString, std::string(";"), split1::no_empties);
718     for(const std::string &arg : argList) {
719         std::vector<std::string> argParts;
720         split(argParts, arg, std::string(":"), split1::no_empties);
721 
722         if (argParts.size() < 2)
723             vsFatal("Invalid argument specifier '%s'. It appears to be incomplete.", arg.c_str());
724 
725         bool arr = false;
726         enum FilterArgumentType type = faNone;
727         const std::string &argName = argParts[0];
728         const std::string &typeName = argParts[1];
729 
730         if (typeName == "int")
731             type = faInt;
732         else if (typeName == "float")
733             type = faFloat;
734         else if (typeName == "data")
735             type = faData;
736         else if (typeName == "clip")
737             type = faClip;
738         else if (typeName == "frame")
739             type = faFrame;
740         else if (typeName == "func")
741             type = faFunc;
742         else {
743             arr = true;
744 
745             if (typeName == "int[]")
746                 type = faInt;
747             else if (typeName == "float[]")
748                 type = faFloat;
749             else if (typeName == "data[]")
750                 type = faData;
751             else if (typeName == "clip[]")
752                 type = faClip;
753             else if (typeName == "frame[]")
754                 type = faFrame;
755             else if (typeName == "func[]")
756                 type = faFunc;
757             else
758                 vsFatal("Argument '%s' has invalid type '%s'.", argName.c_str(), typeName.c_str());
759         }
760 
761         bool opt = false;
762         bool empty = false;
763 
764         for (size_t i = 2; i < argParts.size(); i++) {
765             if (argParts[i] == "opt") {
766                 if (opt)
767                     vsFatal("Argument '%s' has duplicate argument specifier '%s'", argName.c_str(), argParts[i].c_str());
768                 opt = true;
769             } else if (argParts[i] == "empty") {
770                 if (empty)
771                     vsFatal("Argument '%s' has duplicate argument specifier '%s'", argName.c_str(), argParts[i].c_str());
772                 empty = true;
773             }  else {
774                 vsFatal("Argument '%s' has unknown argument modifier '%s'", argName.c_str(), argParts[i].c_str());
775             }
776         }
777 
778         if (!isValidIdentifier(argName))
779             vsFatal("Argument name '%s' contains illegal characters.", argName.c_str());
780 
781         if (empty && !arr)
782             vsFatal("Argument '%s' is not an array. Only array arguments can have the empty flag set.", argName.c_str());
783 
784         args.push_back(FilterArgument(argName, type, arr, empty, opt));
785     }
786 }
787 
VSNode(const VSMap * in,VSMap * out,const std::string & name,VSFilterInit init,VSFilterGetFrame getFrame,VSFilterFree free,VSFilterMode filterMode,int flags,void * instanceData,int apiMajor,VSCore * core)788 VSNode::VSNode(const VSMap *in, VSMap *out, const std::string &name, VSFilterInit init, VSFilterGetFrame getFrame, VSFilterFree free, VSFilterMode filterMode, int flags, void *instanceData, int apiMajor, VSCore *core) :
789 instanceData(instanceData), name(name), init(init), filterGetFrame(getFrame), free(free), filterMode(filterMode), apiMajor(apiMajor), core(core), flags(flags), hasVi(false), serialFrame(-1) {
790 
791     if (flags & ~(nfNoCache | nfIsCache | nfMakeLinear))
792         throw VSException("Filter " + name  + " specified unknown flags");
793 
794     if ((flags & nfIsCache) && !(flags & nfNoCache))
795         throw VSException("Filter " + name + " specified an illegal combination of flags (nfNoCache must always be set with nfIsCache)");
796 
797     core->filterInstanceCreated();
798     VSMap inval(*in);
799     init(&inval, out, &this->instanceData, this, core, getVSAPIInternal(apiMajor));
800 
801     if (out->hasError()) {
802         core->filterInstanceDestroyed();
803         throw VSException(vs_internal_vsapi.getError(out));
804     }
805 
806     if (!hasVi) {
807         core->filterInstanceDestroyed();
808         throw VSException("Filter " + name + " didn't set vi");
809     }
810 
811     for (const auto &iter : vi) {
812         if (iter.numFrames <= 0) {
813             core->filterInstanceDestroyed();
814             throw VSException("Filter " + name + " returned zero or negative frame count");
815         }
816     }
817 }
818 
~VSNode()819 VSNode::~VSNode() {
820     core->destroyFilterInstance(this);
821 }
822 
getFrame(const PFrameContext & ct)823 void VSNode::getFrame(const PFrameContext &ct) {
824     core->threadPool->start(ct);
825 }
826 
getVideoInfo(int index)827 const VSVideoInfo &VSNode::getVideoInfo(int index) {
828     if (index < 0 || index >= static_cast<int>(vi.size()))
829         vsFatal("getVideoInfo: Out of bounds videoinfo index %d. Valid range: [0,%d].", index, static_cast<int>(vi.size() - 1));
830     return vi[index];
831 }
832 
setVideoInfo(const VSVideoInfo * vi,int numOutputs)833 void VSNode::setVideoInfo(const VSVideoInfo *vi, int numOutputs) {
834     if (numOutputs < 1)
835         vsFatal("setVideoInfo: Video filter %s needs to have at least one output (%d were given).", name.c_str(), numOutputs);
836     for (int i = 0; i < numOutputs; i++) {
837         if ((!!vi[i].height) ^ (!!vi[i].width))
838             vsFatal("setVideoInfo: Variable dimension clips must have both width and height set to 0. Dimensions given by filter %s: %dx%d.", name.c_str(), vi[i].width, vi[i].height);
839         if (vi[i].format && !core->isValidFormatPointer(vi[i].format))
840             vsFatal("setVideoInfo: The VSFormat pointer passed by %s was not obtained from registerFormat() or getFormatPreset().", name.c_str());
841         int64_t num = vi[i].fpsNum;
842         int64_t den = vi[i].fpsDen;
843         vs_normalizeRational(&num, &den);
844         if (num != vi[i].fpsNum || den != vi[i].fpsDen)
845             vsFatal(("setVideoInfo: The frame rate specified by " + name + " must be a reduced fraction. (Instead, it is " + std::to_string(vi[i].fpsNum) + "/" + std::to_string(vi[i].fpsDen) + ".)").c_str());
846 
847         this->vi.push_back(vi[i]);
848         this->vi[i].flags = flags;
849     }
850     hasVi = true;
851 }
852 
getFrameInternal(int n,int activationReason,VSFrameContext & frameCtx)853 PVideoFrame VSNode::getFrameInternal(int n, int activationReason, VSFrameContext &frameCtx) {
854     const VSFrameRef *r = filterGetFrame(n, activationReason, &instanceData, &frameCtx.ctx->frameContext, &frameCtx, core, &vs_internal_vsapi);
855 
856 #ifdef VS_TARGET_OS_WINDOWS
857     if (!vs_isSSEStateOk())
858         vsFatal("Bad SSE state detected after return from %s", name.c_str());
859 #endif
860 
861     if (r) {
862         PVideoFrame p(std::move(r->frame));
863         delete r;
864         const VSFormat *fi = p->getFormat();
865         const VSVideoInfo &lvi = vi[frameCtx.ctx->index];
866 
867         if (!lvi.format && fi->colorFamily == cmCompat)
868             vsFatal("Illegal compat frame returned by %s.", name.c_str());
869         else if (lvi.format && lvi.format != fi)
870             vsFatal("Filter %s declared the format %s (id %d), but it returned a frame with the format %s (id %d).", name.c_str(), lvi.format->name, lvi.format->id, fi->name, fi->id);
871         else if ((lvi.width || lvi.height) && (p->getWidth(0) != lvi.width || p->getHeight(0) != lvi.height))
872             vsFatal("Filter %s declared the size %dx%d, but it returned a frame with the size %dx%d.", name.c_str(), lvi.width, lvi.height, p->getWidth(0), p->getHeight(0));
873 
874 #ifdef VS_FRAME_GUARD
875         if (!p->verifyGuardPattern())
876             vsFatal("Guard memory corrupted in frame %d returned from %s", n, name.c_str());
877 #endif
878 
879         return p;
880     }
881 
882     PVideoFrame p;
883     return p;
884 }
885 
reserveThread()886 void VSNode::reserveThread() {
887     core->threadPool->reserveThread();
888 }
889 
releaseThread()890 void VSNode::releaseThread() {
891     core->threadPool->releaseThread();
892 }
893 
isWorkerThread()894 bool VSNode::isWorkerThread() {
895     return core->threadPool->isWorkerThread();
896 }
897 
notifyCache(bool needMemory)898 void VSNode::notifyCache(bool needMemory) {
899     std::lock_guard<std::mutex> lock(serialMutex);
900     CacheInstance *cache = (CacheInstance *)instanceData;
901     cache->cache.adjustSize(needMemory);
902 }
903 
newVideoFrame(const VSFormat * f,int width,int height,const VSFrame * propSrc)904 PVideoFrame VSCore::newVideoFrame(const VSFormat *f, int width, int height, const VSFrame *propSrc) {
905     return std::make_shared<VSFrame>(f, width, height, propSrc, this);
906 }
907 
newVideoFrame(const VSFormat * f,int width,int height,const VSFrame * const * planeSrc,const int * planes,const VSFrame * propSrc)908 PVideoFrame VSCore::newVideoFrame(const VSFormat *f, int width, int height, const VSFrame * const *planeSrc, const int *planes, const VSFrame *propSrc) {
909     return std::make_shared<VSFrame>(f, width, height, planeSrc, planes, propSrc, this);
910 }
911 
copyFrame(const PVideoFrame & srcf)912 PVideoFrame VSCore::copyFrame(const PVideoFrame &srcf) {
913     return std::make_shared<VSFrame>(*srcf.get());
914 }
915 
copyFrameProps(const PVideoFrame & src,PVideoFrame & dst)916 void VSCore::copyFrameProps(const PVideoFrame &src, PVideoFrame &dst) {
917     dst->setProperties(src->getProperties());
918 }
919 
getFormatPreset(int id)920 const VSFormat *VSCore::getFormatPreset(int id) {
921     std::lock_guard<std::mutex> lock(formatLock);
922 
923     auto f = formats.find(id);
924     if (f != formats.end())
925         return f->second;
926     return nullptr;
927 }
928 
registerFormat(VSColorFamily colorFamily,VSSampleType sampleType,int bitsPerSample,int subSamplingW,int subSamplingH,const char * name,int id)929 const VSFormat *VSCore::registerFormat(VSColorFamily colorFamily, VSSampleType sampleType, int bitsPerSample, int subSamplingW, int subSamplingH, const char *name, int id) {
930     // this is to make exact format comparisons easy by simply allowing pointer comparison
931 
932     // block nonsense formats
933     if (subSamplingH < 0 || subSamplingW < 0 || subSamplingH > 4 || subSamplingW > 4)
934         return nullptr;
935 
936     if (sampleType < 0 || sampleType > 1)
937         return nullptr;
938 
939     if (colorFamily == cmRGB && (subSamplingH != 0 || subSamplingW != 0))
940         return nullptr;
941 
942     if (sampleType == stFloat && (bitsPerSample != 16 && bitsPerSample != 32))
943         return nullptr;
944 
945     if (bitsPerSample < 8 || bitsPerSample > 32)
946         return nullptr;
947 
948     if (colorFamily == cmCompat && !name)
949         return nullptr;
950 
951     std::lock_guard<std::mutex> lock(formatLock);
952 
953     for (const auto &iter : formats) {
954         const VSFormat *f = iter.second;
955 
956         if (f->colorFamily == colorFamily && f->sampleType == sampleType
957                 && f->subSamplingW == subSamplingW && f->subSamplingH == subSamplingH && f->bitsPerSample == bitsPerSample)
958             return f;
959     }
960 
961     VSFormat *f = new VSFormat();
962     memset(f->name, 0, sizeof(f->name));
963 
964     if (name) {
965         strcpy(f->name, name);
966     } else {
967         const char *sampleTypeStr = "";
968         if (sampleType == stFloat)
969             sampleTypeStr = (bitsPerSample == 32) ? "S" : "H";
970 
971         const char *yuvName = nullptr;
972 
973         switch (colorFamily) {
974         case cmGray:
975             snprintf(f->name, sizeof(f->name), "Gray%s%d", sampleTypeStr, bitsPerSample);
976             break;
977         case cmRGB:
978             snprintf(f->name, sizeof(f->name), "RGB%s%d", sampleTypeStr, bitsPerSample * 3);
979             break;
980         case cmYUV:
981             if (subSamplingW == 1 && subSamplingH == 1)
982                 yuvName = "420";
983             else if (subSamplingW == 1 && subSamplingH == 0)
984                 yuvName = "422";
985             else if (subSamplingW == 0 && subSamplingH == 0)
986                 yuvName = "444";
987             else if (subSamplingW == 2 && subSamplingH == 2)
988                 yuvName = "410";
989             else if (subSamplingW == 2 && subSamplingH == 0)
990                 yuvName = "411";
991             else if (subSamplingW == 0 && subSamplingH == 1)
992                 yuvName = "440";
993             if (yuvName)
994                 snprintf(f->name, sizeof(f->name), "YUV%sP%s%d", yuvName, sampleTypeStr, bitsPerSample);
995             else
996                 snprintf(f->name, sizeof(f->name), "YUVssw%dssh%dP%s%d", subSamplingW, subSamplingH, sampleTypeStr, bitsPerSample);
997             break;
998         case cmYCoCg:
999             snprintf(f->name, sizeof(f->name), "YCoCgssw%dssh%dP%s%d", subSamplingW, subSamplingH, sampleTypeStr, bitsPerSample);
1000             break;
1001         default:;
1002         }
1003     }
1004 
1005     if (id != pfNone)
1006         f->id = id;
1007     else
1008         f->id = colorFamily + formatIdOffset++;
1009 
1010     f->colorFamily = colorFamily;
1011     f->sampleType = sampleType;
1012     f->bitsPerSample = bitsPerSample;
1013     f->bytesPerSample = 1;
1014 
1015     while (f->bytesPerSample * 8 < bitsPerSample)
1016         f->bytesPerSample *= 2;
1017 
1018     f->subSamplingW = subSamplingW;
1019     f->subSamplingH = subSamplingH;
1020     f->numPlanes = (colorFamily == cmGray || colorFamily == cmCompat) ? 1 : 3;
1021 
1022     formats.insert(std::make_pair(f->id, f));
1023     return f;
1024 }
1025 
isValidFormatPointer(const VSFormat * f)1026 bool VSCore::isValidFormatPointer(const VSFormat *f) {
1027     std::lock_guard<std::mutex> lock(formatLock);
1028 
1029     for (const auto &iter : formats) {
1030         if (iter.second == f)
1031             return true;
1032     }
1033     return false;
1034 }
1035 
getCoreInfo()1036 const VSCoreInfo &VSCore::getCoreInfo() {
1037     getCoreInfo2(coreInfo);
1038     return coreInfo;
1039 }
1040 
getCoreInfo2(VSCoreInfo & info)1041 void VSCore::getCoreInfo2(VSCoreInfo &info) {
1042     info.versionString = VAPOURSYNTH_VERSION_STRING;
1043     info.core = VAPOURSYNTH_CORE_VERSION;
1044     info.api = VAPOURSYNTH_API_VERSION;
1045     info.numThreads = threadPool->threadCount();
1046     info.maxFramebufferSize = memory->getLimit();
1047     info.usedFramebufferSize = memory->memoryUse();
1048 }
1049 
1050 void VS_CC vs_internal_configPlugin(const char *identifier, const char *defaultNamespace, const char *name, int apiVersion, int readOnly, VSPlugin *plugin);
1051 void VS_CC vs_internal_registerFunction(const char *name, const char *args, VSPublicFunction argsFunc, void *functionData, VSPlugin *plugin);
1052 
loadPlugin(const VSMap * in,VSMap * out,void * userData,VSCore * core,const VSAPI * vsapi)1053 static void VS_CC loadPlugin(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi) {
1054     try {
1055         int err;
1056         const char *forcens = vsapi->propGetData(in, "forcens", 0, &err);
1057         if (!forcens)
1058             forcens = "";
1059         const char *forceid = vsapi->propGetData(in, "forceid", 0, &err);
1060         if (!forceid)
1061             forceid = "";
1062         bool altSearchPath = !!vsapi->propGetInt(in, "altsearchpath", 0, &err);
1063         core->loadPlugin(vsapi->propGetData(in, "path", 0, nullptr), forcens, forceid, altSearchPath);
1064     } catch (VSException &e) {
1065         vsapi->setError(out, e.what());
1066     }
1067 }
1068 
loadPluginInitialize(VSConfigPlugin configFunc,VSRegisterFunction registerFunc,VSPlugin * plugin)1069 void VS_CC loadPluginInitialize(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) {
1070     registerFunc("LoadPlugin", "path:data;altsearchpath:int:opt;forcens:data:opt;forceid:data:opt;", &loadPlugin, nullptr, plugin);
1071 }
1072 
registerFormats()1073 void VSCore::registerFormats() {
1074     // Register known formats with informational names
1075     registerFormat(cmGray, stInteger,  8, 0, 0, "Gray8", pfGray8);
1076     registerFormat(cmGray, stInteger, 16, 0, 0, "Gray16", pfGray16);
1077 
1078     registerFormat(cmGray, stFloat,   16, 0, 0, "GrayH", pfGrayH);
1079     registerFormat(cmGray, stFloat,   32, 0, 0, "GrayS", pfGrayS);
1080 
1081     registerFormat(cmYUV,  stInteger, 8, 1, 1, "YUV420P8", pfYUV420P8);
1082     registerFormat(cmYUV,  stInteger, 8, 1, 0, "YUV422P8", pfYUV422P8);
1083     registerFormat(cmYUV,  stInteger, 8, 0, 0, "YUV444P8", pfYUV444P8);
1084     registerFormat(cmYUV,  stInteger, 8, 2, 2, "YUV410P8", pfYUV410P8);
1085     registerFormat(cmYUV,  stInteger, 8, 2, 0, "YUV411P8", pfYUV411P8);
1086     registerFormat(cmYUV,  stInteger, 8, 0, 1, "YUV440P8", pfYUV440P8);
1087 
1088     registerFormat(cmYUV,  stInteger, 9, 1, 1, "YUV420P9", pfYUV420P9);
1089     registerFormat(cmYUV,  stInteger, 9, 1, 0, "YUV422P9", pfYUV422P9);
1090     registerFormat(cmYUV,  stInteger, 9, 0, 0, "YUV444P9", pfYUV444P9);
1091 
1092     registerFormat(cmYUV,  stInteger, 10, 1, 1, "YUV420P10", pfYUV420P10);
1093     registerFormat(cmYUV,  stInteger, 10, 1, 0, "YUV422P10", pfYUV422P10);
1094     registerFormat(cmYUV,  stInteger, 10, 0, 0, "YUV444P10", pfYUV444P10);
1095 
1096     registerFormat(cmYUV,  stInteger, 12, 1, 1, "YUV420P12", pfYUV420P12);
1097     registerFormat(cmYUV,  stInteger, 12, 1, 0, "YUV422P12", pfYUV422P12);
1098     registerFormat(cmYUV,  stInteger, 12, 0, 0, "YUV444P12", pfYUV444P12);
1099 
1100     registerFormat(cmYUV,  stInteger, 14, 1, 1, "YUV420P14", pfYUV420P14);
1101     registerFormat(cmYUV,  stInteger, 14, 1, 0, "YUV422P14", pfYUV422P14);
1102     registerFormat(cmYUV,  stInteger, 14, 0, 0, "YUV444P14", pfYUV444P14);
1103 
1104     registerFormat(cmYUV,  stInteger, 16, 1, 1, "YUV420P16", pfYUV420P16);
1105     registerFormat(cmYUV,  stInteger, 16, 1, 0, "YUV422P16", pfYUV422P16);
1106     registerFormat(cmYUV,  stInteger, 16, 0, 0, "YUV444P16", pfYUV444P16);
1107 
1108     registerFormat(cmYUV,  stFloat,   16, 0, 0, "YUV444PH", pfYUV444PH);
1109     registerFormat(cmYUV,  stFloat,   32, 0, 0, "YUV444PS", pfYUV444PS);
1110 
1111     registerFormat(cmRGB,  stInteger, 8, 0, 0, "RGB24", pfRGB24);
1112     registerFormat(cmRGB,  stInteger, 9, 0, 0, "RGB27", pfRGB27);
1113     registerFormat(cmRGB,  stInteger, 10, 0, 0, "RGB30", pfRGB30);
1114     registerFormat(cmRGB,  stInteger, 16, 0, 0, "RGB48", pfRGB48);
1115 
1116     registerFormat(cmRGB,  stFloat,   16, 0, 0, "RGBH", pfRGBH);
1117     registerFormat(cmRGB,  stFloat,   32, 0, 0, "RGBS", pfRGBS);
1118 
1119     registerFormat(cmCompat, stInteger, 32, 0, 0, "CompatBGR32", pfCompatBGR32);
1120     registerFormat(cmCompat, stInteger, 16, 1, 0, "CompatYUY2", pfCompatYUY2);
1121 }
1122 
1123 
1124 #ifdef VS_TARGET_OS_WINDOWS
loadAllPluginsInPath(const std::wstring & path,const std::wstring & filter)1125 bool VSCore::loadAllPluginsInPath(const std::wstring &path, const std::wstring &filter) {
1126 #else
1127 bool VSCore::loadAllPluginsInPath(const std::string &path, const std::string &filter) {
1128 #endif
1129     if (path.empty())
1130         return false;
1131 
1132 #ifdef VS_TARGET_OS_WINDOWS
1133     std::wstring wPath = path + L"\\" + filter;
1134     WIN32_FIND_DATA findData;
1135     HANDLE findHandle = FindFirstFile(wPath.c_str(), &findData);
1136     if (findHandle == INVALID_HANDLE_VALUE)
1137         return false;
1138     do {
1139         try {
1140             loadPlugin(utf16_to_utf8(path + L"\\" + findData.cFileName));
1141         } catch (VSException &) {
1142             // Ignore any errors
1143         }
1144     } while (FindNextFile(findHandle, &findData));
1145     FindClose(findHandle);
1146 #else
1147     DIR *dir = opendir(path.c_str());
1148     if (!dir)
1149         return false;
1150 
1151     int name_max = pathconf(path.c_str(), _PC_NAME_MAX);
1152     if (name_max == -1)
1153         name_max = 255;
1154 
1155     while (true) {
1156         struct dirent *result = readdir(dir);
1157         if (!result) {
1158             break;
1159         }
1160 
1161         std::string name(result->d_name);
1162         // If name ends with filter
1163         if (name.size() >= filter.size() && name.compare(name.size() - filter.size(), filter.size(), filter) == 0) {
1164             try {
1165                 std::string fullname;
1166                 fullname.append(path).append("/").append(name);
1167                 loadPlugin(fullname);
1168             } catch (VSException &) {
1169                 // Ignore any errors
1170             }
1171         }
1172     }
1173 
1174     if (closedir(dir)) {
1175         // Shouldn't happen
1176     }
1177 #endif
1178 
1179     return true;
1180 }
1181 
1182 void VSCore::functionInstanceCreated() {
1183     ++numFunctionInstances;
1184 }
1185 
1186 void VSCore::functionInstanceDestroyed() {
1187     --numFunctionInstances;
1188 }
1189 
1190 void VSCore::filterInstanceCreated() {
1191     ++numFilterInstances;
1192 }
1193 
1194 void VSCore::filterInstanceDestroyed() {
1195     if (!--numFilterInstances) {
1196         assert(coreFreed);
1197         delete this;
1198     }
1199 }
1200 
1201 struct VSCoreShittyList {
1202     VSFilterFree free;
1203     void *instanceData;
1204     VSCoreShittyList *next;
1205     // add stuff like vsapi here if multiple versions end up floating around for compatibility
1206 };
1207 
1208 void VSCore::destroyFilterInstance(VSNode *node) {
1209     static thread_local int freeDepth = 0;
1210     static thread_local VSCoreShittyList *nodeFreeList = nullptr;
1211     freeDepth++;
1212 
1213     if (node->free) {
1214         nodeFreeList = new VSCoreShittyList({ node->free, node->instanceData, nodeFreeList });
1215     } else {
1216         filterInstanceDestroyed();
1217     }
1218 
1219     if (freeDepth == 1) {
1220         while (nodeFreeList) {
1221             VSCoreShittyList *current = nodeFreeList;
1222             nodeFreeList = current->next;
1223             current->free(current->instanceData, this, &vs_internal_vsapi);
1224             delete current;
1225             filterInstanceDestroyed();
1226         }
1227     }
1228 
1229     freeDepth--;
1230 }
1231 
1232 VSCore::VSCore(int threads) :
1233     coreFreed(false),
1234     numFilterInstances(1),
1235     numFunctionInstances(0),
1236     formatIdOffset(1000),
1237     cpuLevel(INT_MAX),
1238     memory(new MemoryUse()) {
1239 #ifdef VS_TARGET_OS_WINDOWS
1240     if (!vs_isSSEStateOk())
1241         vsFatal("Bad SSE state detected when creating new core");
1242 #endif
1243 
1244     threadPool = new VSThreadPool(this, threads);
1245 
1246     registerFormats();
1247 
1248     // The internal plugin units, the loading is a bit special so they can get special flags
1249     VSPlugin *p;
1250 
1251     // Initialize internal plugins
1252     p = new VSPlugin(this);
1253     ::vs_internal_configPlugin("com.vapoursynth.std", "std", "VapourSynth Core Functions", VAPOURSYNTH_API_VERSION, 0, p);
1254     loadPluginInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1255     cacheInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1256     exprInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1257     genericInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1258     lutInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1259     boxBlurInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1260     mergeInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1261     reorderInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1262     stdlibInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1263     p->enableCompat();
1264     p->lock();
1265 
1266     plugins.insert(std::make_pair(p->id, p));
1267     p = new VSPlugin(this);
1268     resizeInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1269     plugins.insert(std::make_pair(p->id, p));
1270     p->enableCompat();
1271 
1272     plugins.insert(std::make_pair(p->id, p));
1273     p = new VSPlugin(this);
1274     textInitialize(::vs_internal_configPlugin, ::vs_internal_registerFunction, p);
1275     plugins.insert(std::make_pair(p->id, p));
1276     p->enableCompat();
1277 
1278 #ifdef VS_TARGET_OS_WINDOWS
1279 
1280     const std::wstring filter = L"*.dll";
1281 
1282 #ifdef _WIN64
1283     #define VS_INSTALL_REGKEY L"Software\\VapourSynth"
1284     std::wstring bits(L"64");
1285 #else
1286     #define VS_INSTALL_REGKEY L"Software\\VapourSynth-32"
1287     std::wstring bits(L"32");
1288 #endif
1289 
1290     HMODULE module;
1291     GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)&vs_internal_configPlugin, &module);
1292     std::vector<wchar_t> pathBuf(65536);
1293     GetModuleFileName(module, pathBuf.data(), (DWORD)pathBuf.size());
1294     std::wstring dllPath = pathBuf.data();
1295     dllPath.resize(dllPath.find_last_of('\\') + 1);
1296     std::wstring portableFilePath = dllPath + L"portable.vs";
1297     FILE *portableFile = _wfopen(portableFilePath.c_str(), L"rb");
1298     bool isPortable = !!portableFile;
1299     if (portableFile)
1300         fclose(portableFile);
1301 
1302     if (isPortable) {
1303         // Use alternative search strategy relative to dll path
1304 
1305         // Autoload bundled plugins
1306         std::wstring corePluginPath = dllPath + L"vapoursynth" + bits + L"\\coreplugins";
1307         if (!loadAllPluginsInPath(corePluginPath, filter))
1308             vsCritical("Core plugin autoloading failed. Installation is broken?");
1309 
1310         // Autoload global plugins last, this is so the bundled plugins cannot be overridden easily
1311         // and accidentally block updated bundled versions
1312         std::wstring globalPluginPath = dllPath + L"vapoursynth" + bits + L"\\plugins";
1313         loadAllPluginsInPath(globalPluginPath, filter);
1314     } else {
1315         // Autoload user specific plugins first so a user can always override
1316         std::vector<wchar_t> appDataBuffer(MAX_PATH + 1);
1317         if (SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, appDataBuffer.data()) != S_OK)
1318             SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_DEFAULT, appDataBuffer.data());
1319 
1320         std::wstring appDataPath = std::wstring(appDataBuffer.data()) + L"\\VapourSynth\\plugins" + bits;
1321 
1322         // Autoload per user plugins
1323         loadAllPluginsInPath(appDataPath, filter);
1324 
1325         // Autoload bundled plugins
1326         std::wstring corePluginPath = readRegistryValue(VS_INSTALL_REGKEY, L"CorePlugins");
1327         if (!loadAllPluginsInPath(corePluginPath, filter))
1328             vsCritical("Core plugin autoloading failed. Installation is broken?");
1329 
1330         // Autoload global plugins last, this is so the bundled plugins cannot be overridden easily
1331         // and accidentally block updated bundled versions
1332         std::wstring globalPluginPath = readRegistryValue(VS_INSTALL_REGKEY, L"Plugins");
1333         loadAllPluginsInPath(globalPluginPath, filter);
1334     }
1335 
1336 #else
1337     std::string configFile;
1338     const char *home = getenv("HOME");
1339 #ifdef VS_TARGET_OS_DARWIN
1340     std::string filter = ".dylib";
1341     if (home) {
1342         configFile.append(home).append("/Library/Application Support/VapourSynth/vapoursynth.conf");
1343     }
1344 #else
1345     std::string filter = ".so";
1346     const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
1347     if (xdg_config_home) {
1348         configFile.append(xdg_config_home).append("/vapoursynth/vapoursynth.conf");
1349     } else if (home) {
1350         configFile.append(home).append("/.config/vapoursynth/vapoursynth.conf");
1351     } // If neither exists, an empty string will do.
1352 #endif
1353 
1354     VSMap *settings = readSettings(configFile);
1355     const char *error = vs_internal_vsapi.getError(settings);
1356     if (error) {
1357         vsWarning("%s\n", error);
1358     } else {
1359         int err;
1360         const char *tmp;
1361 
1362         tmp = vs_internal_vsapi.propGetData(settings, "UserPluginDir", 0, &err);
1363         std::string userPluginDir(tmp ? tmp : "");
1364 
1365         tmp = vs_internal_vsapi.propGetData(settings, "SystemPluginDir", 0, &err);
1366         std::string systemPluginDir(tmp ? tmp : VS_PATH_PLUGINDIR);
1367 
1368         tmp = vs_internal_vsapi.propGetData(settings, "AutoloadUserPluginDir", 0, &err);
1369         bool autoloadUserPluginDir = tmp ? std::string(tmp) == "true" : true;
1370 
1371         tmp = vs_internal_vsapi.propGetData(settings, "AutoloadSystemPluginDir", 0, &err);
1372         bool autoloadSystemPluginDir = tmp ? std::string(tmp) == "true" : true;
1373 
1374         if (autoloadUserPluginDir && !userPluginDir.empty()) {
1375             if (!loadAllPluginsInPath(userPluginDir, filter)) {
1376                 vsWarning("Autoloading the user plugin dir '%s' failed. Directory doesn't exist?", userPluginDir.c_str());
1377             }
1378         }
1379 
1380         if (autoloadSystemPluginDir) {
1381             if (!loadAllPluginsInPath(systemPluginDir, filter)) {
1382                 vsCritical("Autoloading the system plugin dir '%s' failed. Directory doesn't exist?", systemPluginDir.c_str());
1383             }
1384         }
1385     }
1386 
1387     vs_internal_vsapi.freeMap(settings);
1388 #endif
1389 }
1390 
1391 void VSCore::freeCore() {
1392     if (coreFreed)
1393         vsFatal("Double free of core");
1394     coreFreed = true;
1395     threadPool->waitForDone();
1396     if (numFilterInstances > 1)
1397         vsWarning("Core freed but %d filter instance(s) still exist", numFilterInstances.load() - 1);
1398     if (memory->memoryUse() > 0)
1399         vsWarning("Core freed but %llu bytes still allocated in framebuffers", static_cast<unsigned long long>(memory->memoryUse()));
1400     if (numFunctionInstances > 0)
1401         vsWarning("Core freed but %d function instance(s) still exist", numFunctionInstances.load());
1402     // Release the extra filter instance that always keeps the core alive
1403     filterInstanceDestroyed();
1404 }
1405 
1406 VSCore::~VSCore() {
1407     memory->signalFree();
1408     delete threadPool;
1409     for(const auto &iter : plugins)
1410         delete iter.second;
1411     plugins.clear();
1412     for (const auto &iter : formats)
1413         delete iter.second;
1414     formats.clear();
1415 }
1416 
1417 VSMap VSCore::getPlugins() {
1418     VSMap m;
1419     std::lock_guard<std::recursive_mutex> lock(pluginLock);
1420     int num = 0;
1421     for (const auto &iter : plugins) {
1422         std::string b = iter.second->fnamespace + ";" + iter.second->id + ";" + iter.second->fullname;
1423         vs_internal_vsapi.propSetData(&m, ("Plugin" + std::to_string(++num)).c_str(), b.c_str(), static_cast<int>(b.size()), paReplace);
1424     }
1425     return m;
1426 }
1427 
1428 VSPlugin *VSCore::getPluginById(const std::string &identifier) {
1429     std::lock_guard<std::recursive_mutex> lock(pluginLock);
1430     auto p = plugins.find(identifier);
1431     if (p != plugins.end())
1432         return p->second;
1433     return nullptr;
1434 }
1435 
1436 VSPlugin *VSCore::getPluginByNs(const std::string &ns) {
1437     std::lock_guard<std::recursive_mutex> lock(pluginLock);
1438     for (const auto &iter : plugins) {
1439         if (iter.second->fnamespace == ns)
1440             return iter.second;
1441     }
1442     return nullptr;
1443 }
1444 
1445 void VSCore::loadPlugin(const std::string &filename, const std::string &forcedNamespace, const std::string &forcedId, bool altSearchPath) {
1446     VSPlugin *p = new VSPlugin(filename, forcedNamespace, forcedId, altSearchPath, this);
1447 
1448     std::lock_guard<std::recursive_mutex> lock(pluginLock);
1449 
1450     VSPlugin *already_loaded_plugin = getPluginById(p->id);
1451     if (already_loaded_plugin) {
1452         std::string error = "Plugin " + filename + " already loaded (" + p->id + ")";
1453         if (already_loaded_plugin->filename.size())
1454             error += " from " + already_loaded_plugin->filename;
1455         delete p;
1456         throw VSException(error);
1457     }
1458 
1459     already_loaded_plugin = getPluginByNs(p->fnamespace);
1460     if (already_loaded_plugin) {
1461         std::string error = "Plugin load of " + filename + " failed, namespace " + p->fnamespace + " already populated";
1462         if (already_loaded_plugin->filename.size())
1463             error += " by " + already_loaded_plugin->filename;
1464         delete p;
1465         throw VSException(error);
1466     }
1467 
1468     plugins.insert(std::make_pair(p->id, p));
1469 
1470     // allow avisynth plugins to accept legacy avisynth formats
1471     if (p->fnamespace == "avs" && p->id == "com.vapoursynth.avisynth")
1472         p->enableCompat();
1473 }
1474 
1475 void VSCore::createFilter(const VSMap *in, VSMap *out, const std::string &name, VSFilterInit init, VSFilterGetFrame getFrame, VSFilterFree free, VSFilterMode filterMode, int flags, void *instanceData, int apiMajor) {
1476     try {
1477         PVideoNode node(std::make_shared<VSNode>(in, out, name, init, getFrame, free, filterMode, flags, instanceData, apiMajor, this));
1478         for (size_t i = 0; i < node->getNumOutputs(); i++) {
1479             // fixme, not that elegant but saves more variant poking code
1480             VSNodeRef *ref = new VSNodeRef(node, static_cast<int>(i));
1481             vs_internal_vsapi.propSetNode(out, "clip", ref, paAppend);
1482             delete ref;
1483         }
1484     } catch (VSException &e) {
1485         vs_internal_vsapi.setError(out, e.what());
1486     }
1487 }
1488 
1489 int VSCore::getCpuLevel() const {
1490     return cpuLevel;
1491 }
1492 
1493 int VSCore::setCpuLevel(int cpu) {
1494     return cpuLevel.exchange(cpu);
1495 }
1496 
1497 VSPlugin::VSPlugin(VSCore *core)
1498     : apiMajor(0), apiMinor(0), hasConfig(false), readOnly(false), compat(false), libHandle(0), core(core) {
1499 }
1500 
1501 VSPlugin::VSPlugin(const std::string &relFilename, const std::string &forcedNamespace, const std::string &forcedId, bool altSearchPath, VSCore *core)
1502     : apiMajor(0), apiMinor(0), hasConfig(false), readOnly(false), compat(false), libHandle(0), core(core), fnamespace(forcedNamespace), id(forcedId) {
1503 #ifdef VS_TARGET_OS_WINDOWS
1504     std::wstring wPath = utf16_from_utf8(relFilename);
1505     std::vector<wchar_t> fullPathBuffer(32767 + 1); // add 1 since msdn sucks at mentioning whether or not it includes the final null
1506     if (wPath.substr(0, 4) != L"\\\\?\\")
1507         wPath = L"\\\\?\\" + wPath;
1508     GetFullPathName(wPath.c_str(), static_cast<DWORD>(fullPathBuffer.size()), fullPathBuffer.data(), nullptr);
1509     wPath = fullPathBuffer.data();
1510     if (wPath.substr(0, 4) == L"\\\\?\\")
1511         wPath = wPath.substr(4);
1512     filename = utf16_to_utf8(wPath);
1513     for (auto &iter : filename)
1514         if (iter == '\\')
1515             iter = '/';
1516 
1517     libHandle = LoadLibraryEx(wPath.c_str(), nullptr, altSearchPath ? 0 : (LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR));
1518 
1519     if (!libHandle) {
1520         DWORD lastError = GetLastError();
1521 
1522         if (lastError == 126)
1523             throw VSException("Failed to load " + relFilename + ". GetLastError() returned " + std::to_string(lastError) + ". The file you tried to load or one of its dependencies is probably missing.");
1524         throw VSException("Failed to load " + relFilename + ". GetLastError() returned " + std::to_string(lastError) + ".");
1525     }
1526 
1527     VSInitPlugin pluginInit = (VSInitPlugin)GetProcAddress(libHandle, "VapourSynthPluginInit");
1528 
1529     if (!pluginInit)
1530         pluginInit = (VSInitPlugin)GetProcAddress(libHandle, "_VapourSynthPluginInit@12");
1531 
1532     if (!pluginInit) {
1533         FreeLibrary(libHandle);
1534         throw VSException("No entry point found in " + relFilename);
1535     }
1536 #else
1537     std::vector<char> fullPathBuffer(PATH_MAX + 1);
1538     if (realpath(relFilename.c_str(), fullPathBuffer.data()))
1539         filename = fullPathBuffer.data();
1540     else
1541         filename = relFilename;
1542 
1543     libHandle = dlopen(filename.c_str(), RTLD_LAZY);
1544 
1545     if (!libHandle) {
1546         const char *dlError = dlerror();
1547         if (dlError)
1548             throw VSException("Failed to load " + relFilename + ". Error given: " + dlError);
1549         else
1550             throw VSException("Failed to load " + relFilename);
1551     }
1552 
1553     VSInitPlugin pluginInit = (VSInitPlugin)dlsym(libHandle, "VapourSynthPluginInit");
1554 
1555     if (!pluginInit) {
1556         dlclose(libHandle);
1557         throw VSException("No entry point found in " + relFilename);
1558     }
1559 
1560 
1561 #endif
1562     pluginInit(::vs_internal_configPlugin, ::vs_internal_registerFunction, this);
1563 
1564 #ifdef VS_TARGET_OS_WINDOWS
1565     if (!vs_isSSEStateOk())
1566         vsFatal("Bad SSE state detected after loading %s", filename.c_str());
1567 #endif
1568 
1569     if (readOnlySet)
1570         readOnly = true;
1571 
1572     if (apiMajor != VAPOURSYNTH_API_MAJOR || apiMinor > VAPOURSYNTH_API_MINOR) {
1573 #ifdef VS_TARGET_OS_WINDOWS
1574         FreeLibrary(libHandle);
1575 #else
1576         dlclose(libHandle);
1577 #endif
1578         throw VSException("Core only supports API R" + std::to_string(VAPOURSYNTH_API_MAJOR) + "." + std::to_string(VAPOURSYNTH_API_MINOR) + " but the loaded plugin requires API R" + std::to_string(apiMajor) + "." + std::to_string(apiMinor) + "; Filename: " + relFilename + "; Name: " + fullname);
1579     }
1580 }
1581 
1582 VSPlugin::~VSPlugin() {
1583 #ifdef VS_TARGET_OS_WINDOWS
1584     if (libHandle)
1585         FreeLibrary(libHandle);
1586 #else
1587     if (libHandle)
1588         dlclose(libHandle);
1589 #endif
1590 }
1591 
1592 void VSPlugin::configPlugin(const std::string &identifier, const std::string &defaultNamespace, const std::string &fullname, int apiVersion, bool readOnly) {
1593     if (hasConfig)
1594         vsFatal("Attempted to configure plugin %s twice", identifier.c_str());
1595 
1596     if (id.empty())
1597         id = identifier;
1598 
1599     if (fnamespace.empty())
1600         fnamespace = defaultNamespace;
1601 
1602     this->fullname = fullname;
1603 
1604     apiMajor = apiVersion;
1605     if (apiMajor >= 0x10000) {
1606         apiMinor = (apiMajor & 0xFFFF);
1607         apiMajor >>= 16;
1608     }
1609 
1610     readOnlySet = readOnly;
1611     hasConfig = true;
1612 }
1613 
1614 void VSPlugin::registerFunction(const std::string &name, const std::string &args, VSPublicFunction argsFunc, void *functionData) {
1615     if (readOnly)
1616         vsFatal("Plugin %s tried to modify read only namespace.", filename.c_str());
1617 
1618     if (!isValidIdentifier(name))
1619         vsFatal("Plugin %s tried to register '%s', an illegal identifier.", filename.c_str(), name.c_str());
1620 
1621     std::lock_guard<std::mutex> lock(registerFunctionLock);
1622 
1623     if (funcs.count(name)) {
1624         vsWarning("Plugin %s tried to register '%s' more than once. Second registration ignored.", filename.c_str(), name.c_str());
1625         return;
1626     }
1627 
1628     funcs.insert(std::make_pair(name, VSFunction(args, argsFunc, functionData)));
1629 }
1630 
1631 static bool hasCompatNodes(const VSMap &m) {
1632     for (const auto &vsv : m.getStorage()) {
1633         if (vsv.second.getType() == VSVariant::vNode) {
1634             for (size_t i = 0; i < vsv.second.size(); i++) {
1635                 for (size_t j = 0; j < vsv.second.getValue<VSNodeRef>(i).clip->getNumOutputs(); j++) {
1636                     const VSNodeRef &ref = vsv.second.getValue<VSNodeRef>(i);
1637                     const VSVideoInfo &vi = ref.clip->getVideoInfo(static_cast<int>(j));
1638                     if (vi.format && vi.format->colorFamily == cmCompat)
1639                         return true;
1640                 }
1641             }
1642         }
1643     }
1644     return false;
1645 }
1646 
1647 static bool hasForeignNodes(const VSMap &m, const VSCore *core) {
1648     for (const auto &vsv : m.getStorage()) {
1649         if (vsv.second.getType() == VSVariant::vNode) {
1650             for (size_t i = 0; i < vsv.second.size(); i++) {
1651                 for (size_t j = 0; j < vsv.second.getValue<VSNodeRef>(i).clip->getNumOutputs(); j++) {
1652                     const VSNodeRef &ref = vsv.second.getValue<VSNodeRef>(i);
1653                     if (!ref.clip->isRightCore(core))
1654                         return true;
1655                 }
1656             }
1657         }
1658     }
1659     return false;
1660 }
1661 
1662 VSMap VSPlugin::invoke(const std::string &funcName, const VSMap &args) {
1663     const char lookup[] = { 'i', 'f', 's', 'c', 'v', 'm' };
1664     VSMap v;
1665 
1666     try {
1667         if (funcs.count(funcName)) {
1668             const VSFunction &f = funcs[funcName];
1669             if (!compat && hasCompatNodes(args))
1670                 throw VSException(funcName + ": only special filters may accept compat input");
1671             if (hasForeignNodes(args, core))
1672                 throw VSException(funcName + ": nodes foreign to this core passed as input, improper api usage detected");
1673 
1674             std::set<std::string> remainingArgs;
1675             for (const auto &key : args.getStorage())
1676                 remainingArgs.insert(key.first);
1677 
1678             for (const FilterArgument &fa : f.args) {
1679                 char c = vs_internal_vsapi.propGetType(&args, fa.name.c_str());
1680 
1681                 if (c != 'u') {
1682                     remainingArgs.erase(fa.name);
1683 
1684                     if (lookup[static_cast<int>(fa.type)] != c)
1685                         throw VSException(funcName + ": argument " + fa.name + " is not of the correct type");
1686 
1687                     if (!fa.arr && args[fa.name.c_str()].size() > 1)
1688                         throw VSException(funcName + ": argument " + fa.name + " is not of array type but more than one value was supplied");
1689 
1690                     if (!fa.empty && args[fa.name.c_str()].size() < 1)
1691                         throw VSException(funcName + ": argument " + fa.name + " does not accept empty arrays");
1692 
1693                 } else if (!fa.opt) {
1694                     throw VSException(funcName + ": argument " + fa.name + " is required");
1695                 }
1696             }
1697 
1698             if (!remainingArgs.empty()) {
1699                 auto iter = remainingArgs.cbegin();
1700                 std::string s = *iter;
1701                 ++iter;
1702                 for (; iter != remainingArgs.cend(); ++iter)
1703                     s += ", " + *iter;
1704                 throw VSException(funcName + ": no argument(s) named " + s);
1705             }
1706 
1707             f.func(&args, &v, f.functionData, core, getVSAPIInternal(apiMajor));
1708 
1709             if (!compat && hasCompatNodes(v))
1710                 vsFatal("%s: illegal filter node returning a compat format detected, DO NOT USE THE COMPAT FORMATS IN NEW FILTERS", funcName.c_str());
1711 
1712             return v;
1713         }
1714     } catch (VSException &e) {
1715         vs_internal_vsapi.setError(&v, e.what());
1716         return v;
1717     }
1718 
1719     vs_internal_vsapi.setError(&v, ("Function '" + funcName + "' not found in " + id).c_str());
1720     return v;
1721 }
1722 
1723 VSMap VSPlugin::getFunctions() {
1724     VSMap m;
1725     for (const auto & f : funcs) {
1726         std::string b = f.first + ";" + f.second.argString;
1727         vs_internal_vsapi.propSetData(&m, f.first.c_str(), b.c_str(), static_cast<int>(b.size()), paReplace);
1728     }
1729     return m;
1730 }
1731 
1732 #ifdef VS_TARGET_CPU_X86
1733 static int alignmentHelper() {
1734     return getCPUFeatures()->avx512_f ? 64 : 32;
1735 }
1736 
1737 int VSFrame::alignment = alignmentHelper();
1738 #else
1739 int VSFrame::alignment = 32;
1740 #endif
1741