diff --git a/Makefile.am b/Makefile.am index 1229eda11..8749ad2a1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ warningflags = -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-ignored-attributes commoncflags = -O3 -fvisibility=hidden $(warningflags) -I$(srcdir)/include $(DEBUGCFLAGS) $(MFLAGS) $(UNICODECFLAGS) $(STACKREALIGN) -AM_CXXFLAGS = -std=c++14 $(commoncflags) +AM_CXXFLAGS = -std=c++17 $(commoncflags) AM_CFLAGS = -std=c99 $(commoncflags) ACLOCAL_AMFLAGS = -I m4 diff --git a/include/VapourSynthC.h b/include/VapourSynthC.h new file mode 100644 index 000000000..5faff3dbc --- /dev/null +++ b/include/VapourSynthC.h @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2012-2022 Fredrik Mellbin +* +* This file is part of VapourSynth Classic. +* +* VS-C is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* VapourSynth is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with VapourSynth; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This API is for VS-C internal use only. + +#ifndef VAPOURSYNTHC_H +#define VAPOURSYNTHC_H + +#include + +#define VAPOURSYNTHC_API_VERSION 0x76732d63 // 'vs-c' + +typedef struct VSCAPI { + int (VS_CC *getPluginAPIVersion)(const VSPlugin *); // major version only + int (VS_CC *pluginSetRO)(VSPlugin *, int readonly); // returns old status + int (VS_CC *pluginRenameFunc)(VSPlugin *, const char *oldname, const char *newname); + VSPlugin *(VS_CC *createPlugin)(const char *id, const char *ns, int version, VSCore *core); +} VSCAPI; + +#endif /* VAPOURSYNTHC_H */ diff --git a/msvc_project/Core/Core.vcxproj b/msvc_project/Core/Core.vcxproj index 1f92ca554..5123ab042 100644 --- a/msvc_project/Core/Core.vcxproj +++ b/msvc_project/Core/Core.vcxproj @@ -89,7 +89,7 @@ true NOMINMAX;VS_CORE_EXPORTS;VS_TARGET_OS_WINDOWS;VS_TARGET_CPU_X86;VS_USE_MIMALLOC;VS_GRAPH_API;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true - stdcpp14 + stdcpp17 true @@ -108,7 +108,7 @@ true NOMINMAX;VS_CORE_EXPORTS;VS_TARGET_OS_WINDOWS;VS_TARGET_CPU_X86;VS_USE_MIMALLOC;VS_GRAPH_API;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true - stdcpp14 + stdcpp17 true @@ -130,7 +130,7 @@ StreamingSIMDExtensions2 false true - stdcpp14 + stdcpp17 true @@ -154,7 +154,7 @@ NOMINMAX;VS_CORE_EXPORTS;VS_TARGET_OS_WINDOWS;VS_TARGET_CPU_X86;VS_USE_MIMALLOC;VS_GRAPH_API;_CRT_SECURE_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions) false true - stdcpp14 + stdcpp17 true diff --git a/src/core/vsapi.cpp b/src/core/vsapi.cpp index 2d51a730e..e4a000b11 100644 --- a/src/core/vsapi.cpp +++ b/src/core/vsapi.cpp @@ -22,6 +22,7 @@ #include "cpufeatures.h" #include "vslog.h" #include "VSHelper4.h" +#include "VapourSynthC.h" #include #include #include @@ -1278,6 +1279,60 @@ const vs3::VSAPI3 vs_internal_vsapi3 = { &getCoreInfo2 }; +/////////////////////////////// +static int VS_CC getPluginAPIVersion(const VSPlugin *plugin) VS_NOEXCEPT { + assert(plugin); + if (!plugin) + return -1; + return plugin->getAPIVersion(); +} +static int VS_CC pluginSetRO(VSPlugin *plugin, int readonly) VS_NOEXCEPT { + assert(plugin); + const bool old = plugin->isLocked(); + if (readonly) + plugin->lock(); + else + plugin->unlock(); + return old; +} +bool VSPluginFunction::rename(const std::string &newname) { + if (name == newname) + return false; + + std::lock_guard lock(plugin->functionLock); + + auto &funcs = plugin->funcs; + auto node = funcs.extract(name); + node.key() = newname; + funcs.insert(std::move(node)); + + name = newname; + return true; +} +static int VS_CC pluginRenameFunc(VSPlugin *plugin, const char *oldname, const char *newname) VS_NOEXCEPT { + assert(plugin && oldname && newname); + VSPluginFunction *func = plugin->getFunctionByName(oldname); + if (!func) return false; + return func->rename(newname); +} +void VSCore::addPlugin(const std::string &name, VSPlugin *plugin) { + std::lock_guard lock(pluginLock); + plugins.emplace(name, plugin); +} +static VSPlugin *VS_CC createPlugin(const char *id, const char *ns, int version, VSCore *core) { + assert(id && ns && core); + VSPlugin *p = new VSPlugin(core); + vs_internal_vspapi.configPlugin(id, ns, "python plugin", version, VAPOURSYNTH_API_VERSION, 0, p); + p->lock(); + core->addPlugin(ns, p); + return p; +} +static const VSCAPI vsc_internal_api = { + &getPluginAPIVersion, + &pluginSetRO, + &pluginRenameFunc, + &createPlugin, +}; /////////////////////////////// const VSAPI *getVSAPIInternal(int apiMajor) { @@ -1305,6 +1360,8 @@ const VSAPI *VS_CC getVapourSynthAPI(int version) VS_NOEXCEPT { return &vs_internal_vsapi; } else if (apiMajor == VAPOURSYNTH3_API_MAJOR && apiMinor <= VAPOURSYNTH3_API_MINOR) { return reinterpret_cast(&vs_internal_vsapi3); + } else if (version == VAPOURSYNTHC_API_VERSION) { + return reinterpret_cast(&vsc_internal_api); } else { return nullptr; } diff --git a/src/core/vscore.cpp b/src/core/vscore.cpp index 9bf37379f..e57d55838 100644 --- a/src/core/vscore.cpp +++ b/src/core/vscore.cpp @@ -32,6 +32,10 @@ #include #include +#ifdef VS_PROFILE_CREATE +#include +#endif + #ifdef VS_TARGET_CPU_X86 #include "x86utils.h" #endif @@ -813,7 +817,16 @@ VSMap *VSPluginFunction::invoke(const VSMap &args) { std::string fullName = plugin->getNamespace() + "." + name; plugin->core->functionFrame = std::make_shared(fullName, new VSMap(&args), plugin->core->functionFrame); } + +#ifdef VS_PROFILE_CREATE + std::chrono::time_point startTime = std::chrono::high_resolution_clock::now(); +#endif func(&args, v, functionData, plugin->core, getVSAPIInternal(plugin->apiMajor)); +#ifdef VS_PROFILE_CREATE + std::chrono::nanoseconds duration = std::chrono::high_resolution_clock::now() - startTime; + std::cerr << this->plugin->fnamespace << "." << this->name << " uses " << 0.001 * (double)duration.count() << " us" << std::endl; +#endif + if (enableGraphInspection) { assert(plugin->core->functionFrame); plugin->core->functionFrame = plugin->core->functionFrame->next; diff --git a/src/core/vscore.h b/src/core/vscore.h index c419850fb..28ed2dde0 100644 --- a/src/core/vscore.h +++ b/src/core/vscore.h @@ -1006,6 +1006,7 @@ struct VSPluginFunction { bool isV3Compatible() const; std::string getV4ArgString() const; std::string getV3ArgString() const; + bool rename(const std::string &newname); // 'vs-c' }; @@ -1035,6 +1036,8 @@ struct VSPlugin { VSPlugin(const std::string &relFilename, const std::string &forcedNamespace, const std::string &forcedId, bool altSearchPath, VSCore *core); ~VSPlugin(); void lock() { readOnly = true; } + void unlock() { readOnly = false; } // 'vs-c' + bool isLocked() const { return readOnly; } // 'vs-c' bool configPlugin(const std::string &identifier, const std::string &pluginsNamespace, const std::string &fullname, int pluginVersion, int apiVersion, int flags); bool registerFunction(const std::string &name, const std::string &args, const std::string &returnType, VSPublicFunction argsFunc, void *functionData); VSMap *invoke(const std::string &funcName, const VSMap &args); @@ -1045,6 +1048,7 @@ struct VSPlugin { const std::string &getNamespace() const { return fnamespace; } const std::string &getFilename() const { return filename; } int getPluginVersion() const { return pluginVersion; } + int getAPIVersion() const { return apiMajor; } // 'vs-c' void getFunctions3(VSMap *out) const; }; @@ -1163,6 +1167,8 @@ struct VSCore { explicit VSCore(int flags); void freeCore(); + + void addPlugin(const std::string &name, VSPlugin *plugin); // 'vs-c' }; #endif // VSCORE_H diff --git a/src/cython/vapoursynth.pxd b/src/cython/vapoursynth.pxd index ec75a4bca..ba36340fe 100644 --- a/src/cython/vapoursynth.pxd +++ b/src/cython/vapoursynth.pxd @@ -388,3 +388,12 @@ cdef extern from "include/VapourSynth4.h" nogil: bint removeLogHandler(VSLogHandle *handle, VSCore *core) nogil const VSAPI *getVapourSynthAPI(int version) nogil + +cdef extern from "include/VapourSynthC.h" nogil: + enum: + VAPOURSYNTHC_API_VERSION + ctypedef struct VSCAPI: + int getPluginAPIVersion(VSPlugin *) nogil + int pluginSetRO(VSPlugin *, int) nogil + int pluginRenameFunc(VSPlugin *, const char *, const char *) nogil + VSPlugin *createPlugin(const char *id, const char *ns, int version, VSCore *core) nogil diff --git a/src/cython/vapoursynth.pyx b/src/cython/vapoursynth.pyx index 7acfd57c0..fbb2efe35 100644 --- a/src/cython/vapoursynth.pyx +++ b/src/cython/vapoursynth.pyx @@ -171,6 +171,7 @@ cdef class StandaloneEnvironmentPolicy: cdef object _policy = None cdef const VSAPI *_vsapi = NULL +cdef const VSCAPI *_vscapi = NULL cdef void _set_logger(EnvironmentData env, VSLogHandler handler, VSLogHandlerFree free, void *userData): @@ -2337,6 +2338,18 @@ cdef class Core(object): s += '\tNumber of Threads: ' + str(self.num_threads) + '\n' return s + def _create_plugin(self, id, ns, int version=0): # 'vs-c' + if os.getenv('VSC_DEBUG_REGISTER_FUNC'): + print('[VS-C]: creating plugin %s: id=%s, version=%d' % (ns, id, version), file=sys.stderr) + if _vscapi == NULL: + getVSAPIInternal() + tid = id.encode('utf-8') + cdef const char *cid = tid + tns = ns.encode('utf-8') + cdef const char *cns = tns + cdef VSPlugin *p = _vscapi.createPlugin(cid, cns, version, self.core) + return createPlugin(p, self.funcs, self) + cdef object createNode(VSNode *node, const VSAPI *funcs, Core core): if funcs.getNodeType(node) == VIDEO: return createVideoNode(node, funcs, core) @@ -2491,6 +2504,106 @@ cdef class Plugin(object): return True return False + def _register_func(self, name, args, returnType, func, *, override=False): # 'vs-c' + if os.getenv('VSC_DEBUG_REGISTER_FUNC'): + dbgname = '%s.%s' % (self.namespace, name) + print('[VS-C]: registering %s(%s) -> %s, override=%r' % (dbgname, args, returnType, override), file=sys.stderr) + traceback.print_stack() + func_ = func + def wrapped(**args): + print('[VS-C] registered Python filter %s called with %r' % (dbgname, args), file=sys.stderr) + return func_(**args) + func = wrapped + myfuncs = self.__dir__() + if not override and name in myfuncs: + raise Error('cannot override existing filter "' + name + '" without explicitly setting override.') + if override: + if isinstance(override, str): + newname = override + else: + newname = '_' + name + if newname in myfuncs: + raise Error('renamed function "' + newname + '" already exists') + + if _vscapi == NULL: + getVSAPIInternal() + + tname = name.encode('utf-8') + cdef const char *cname = tname + + cdef int apiver = _vscapi.getPluginAPIVersion(self.plugin) + if apiver < VAPOURSYNTH_API_MAJOR: + args = args.replace(':vnode', ':clip').replace(':vframe', ':frame') + returnType = 'any' + + cdef const char *cargs = NULL + cdef const char *crett = NULL + + cdef VSPluginFunction *mfunc = self.funcs.getPluginFunctionByName(cname, self.plugin) + if (args is None or returnType is None) and mfunc == NULL: + raise Error('args or return type cannot be None unless the function already exists') + if args is None: + cargs = self.funcs.getPluginFunctionArguments(mfunc) + else: + targs = args.encode('utf-8') + cargs = targs + if returnType is None: + crett = self.funcs.getPluginFunctionReturnType(mfunc) + else: + trett = returnType.encode('utf-8') + crett = trett + + cdef bint ro = _vscapi.pluginSetRO(self.plugin, 0) + cdef const char *cnewname = NULL + if override: + tnewname = newname.encode('utf-8') + cnewname = tnewname + if not _vscapi.pluginRenameFunc(self.plugin, cname, cnewname): + raise Error('failed to rename existing filter') + + fdata = createFilterFuncData(func, self.core.core) + Py_INCREF(fdata) + + cdef bint ret = self.funcs.registerFunction(cname, cargs, crett, publicFilterFunction, fdata, self.plugin) + if not ret: + if cnewname != NULL: + assert _vscapi.pluginRenameFunc(self.plugin, cnewname, cname), 'failed to restore old filter name' + Py_DECREF(fdata) + if ro: + _vscapi.pluginSetRO(self.plugin, 1) + return ret + +cdef class FilterFuncData(object): + cdef object func + cdef VSCore *core + + def __init__(self): + raise Error('Class cannot be instantiated directly') + + def __call__(self, **kwargs): + return self.func(**kwargs) + +cdef FuncData createFilterFuncData(object func, VSCore *core): + cdef FuncData instance = FuncData.__new__(FuncData) + instance.func = func + instance.core = core + return instance + +cdef void __stdcall publicFilterFunction(const VSMap *inm, VSMap *outm, void *userData, VSCore *core, const VSAPI *vsapi_do_not_use) nogil: + with gil: + d = userData + try: + m = mapToDict(inm, False) + ret = d(**m) + if not isinstance(ret, dict): + if ret is None: + ret = 0 + ret = {'clip':ret} + dictToMap(ret, outm, core, _vsapi) + except BaseException, e: + emsg = str(e).encode('utf-8') + _vsapi.mapSetError(outm, emsg) + cdef Plugin createPlugin(VSPlugin *plugin, const VSAPI *funcs, Core core): cdef Plugin instance = Plugin.__new__(Plugin) instance.core = core @@ -3074,9 +3187,11 @@ cdef public api const VSAPI *vpy4_getVSAPI(int version) nogil: return getVapourSynthAPI(version) cdef const VSAPI *getVSAPIInternal() nogil: - global _vsapi + global _vsapi, _vscapi if _vsapi == NULL: _vsapi = getVapourSynthAPI(VAPOURSYNTH_API_VERSION) + if _vscapi == NULL: + _vscapi = getVapourSynthAPI(VAPOURSYNTHC_API_VERSION) return _vsapi cdef public api int vpy4_getVariable(VSScript *se, const char *name, VSMap *dst) nogil: