Skip to content

Commit 65c447b

Browse files
committed
rstdoc: add change log, add v12 global lock, extend Allocate and Free
1 parent 0b63378 commit 65c447b

3 files changed

Lines changed: 315 additions & 11 deletions

File tree

distrib/docs/english/source/avisynthdoc/FilterSDK/Cplusplus_api.rst

Lines changed: 278 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ SaveString, Sprintf, VSprintf, Invoke, BitBlt, AtExit, AddFunction,
425425
MakeWritable, FunctionExists, GetVar, GetVarDef, SetVar, SetGlobalVar,
426426
PushContext, PopContext, NewVideoFrame, CheckVersion, Subframe,
427427
SubframePlanar, SetMemoryMax, SetWorkingDir, DeleteScriptEnvironment
428-
and ApplyMessage. They are described in the following subsections.
428+
and ApplyMessage and many others They are described in the following
429+
subsections.
429430

430431

431432
.. _cplusplus_throwerror:
@@ -1710,7 +1711,8 @@ CPP interface (through avisynth.h).
17101711
has_at_least_v8 = avisynth_if_ver >= 8; // frame properties, NewVideoFrameP, other V8 environment functions
17111712
has_at_least_v8_1 = avisynth_if_ver > 8 || (avisynth_if_ver == 8 && avisynth_bugfix_ver >= 1);
17121713
// 8.1: C interface frameprop access fixed, IsPropertyWritable/MakePropertyWritable support, extended GetEnvProperty queries
1713-
has_at_least_v9 = avisynth_if_ver >= 9; // future
1714+
has_at_least_v9 = avisynth_if_ver >= 9;
1715+
has_at_least_v12 = avisynth_if_ver >= 12; // global locks
17141716

17151717
C interface (through avisynth_c.h)
17161718

@@ -1734,7 +1736,8 @@ C interface (through avisynth_c.h)
17341736
has_at_least_v8 = avisynth_if_ver >= 8; // frame properties, NewVideoFrameP, other V8 environment functions
17351737
has_at_least_v8_1 = avisynth_if_ver > 8 || (avisynth_if_ver == 8 && avisynth_bugfix_ver >= 1);
17361738
// 8.1: C interface frameprop access fixed, IsPropertyWritable/MakePropertyWritable support, extended GetEnvProperty queries
1737-
has_at_least_v9 = avisynth_if_ver >= 9; // future
1739+
has_at_least_v9 = avisynth_if_ver >= 9;
1740+
has_at_least_v12 = avisynth_if_ver >= 12; // global locks
17381741

17391742

17401743
AEP_INTERFACE_BUGFIX (c++) AVS_AEP_INTERFACE_BUGFIX (c)
@@ -1768,6 +1771,24 @@ Allocate, v8
17681771

17691772
buffer pool allocate.
17701773

1774+
Primary goal of ``AVS_NORMAL_ALLOC`` was to have the Avisynth-reserved memory
1775+
counter up-to-date and to take into account this memory area as well.
1776+
Thus when reaching the MemoryMax limit the core would free up memory from
1777+
this resource as well (along with frame registry and cache entries).
1778+
Works like a normal _aligned_alloc.
1779+
1780+
But ``AVS_POOLED_ALLOC`` has (or had) a lot more important benefit. Pooled allocations
1781+
are meant for filters that don't need the buffers between frames and can free
1782+
them between calls to ``GetFrame()``. Then multiple filters can use the same buffers
1783+
instead of each filter unnecessarily clinging onto them even while a different filter
1784+
is executing. This resulted in tons of memory savings, which was really useful, as most
1785+
of the complex scripts on HD material used to be memory-bound in that era (around 2015).
1786+
The main reason ``env->Allocate`` and ``env->Free`` were created at all was to support these
1787+
pooled allocations and memory re-use between filters. Adding ``AVS_NORMAL_ALLOC`` was just a bonus.
1788+
1789+
The memory savings might not be that important today (as of 2025) since PCs now have a lot
1790+
more RAM, but the savings are still there.
1791+
17711792
::
17721793

17731794
// IScriptEnvironment Allocate
@@ -1787,7 +1808,7 @@ Free, v8
17871808

17881809
virtual void __stdcall Free(void* ptr) = 0;
17891810

1790-
buffer pool free.
1811+
buffer pool free. Pair of ``Allocate``.
17911812

17921813

17931814
.. _cplusplus_getvartry:
@@ -1883,6 +1904,258 @@ See also
18831904
- :ref:`getFramePropsRW <cplusplus_getframepropsrw>`.
18841905

18851906

1907+
.. _cplusplus_acquiregloballock:
1908+
.. _cplusplus_releasegloballock:
1909+
1910+
AcquireGlobalLock, v12
1911+
^^^^^^^^^^^^^^^^^^^^^^
1912+
ReleaseGlobalLock, v12
1913+
^^^^^^^^^^^^^^^^^^^^^^
1914+
1915+
::
1916+
1917+
// C++ interface (IScriptEnvironment virtual methods)
1918+
virtual bool __stdcall AcquireGlobalLock(const char* name) = 0;
1919+
virtual void __stdcall ReleaseGlobalLock(const char* name) = 0;
1920+
1921+
// C interface (global avs_ functions)
1922+
AVS_EXPORT int __stdcall avs_acquire_global_lock(AVS_ScriptEnvironment* env, const char* name);
1923+
AVS_EXPORT void __stdcall avs_release_global_lock(AVS_ScriptEnvironment* env, const char* name);
1924+
1925+
These functions provide a global, named mutex mechanism to synchronize access to shared
1926+
resources across different plugins within the same Avisynth process. This is essential for
1927+
libraries like fftw3 that have non-thread-safe global state (e.g., their planner functions).
1928+
1929+
When AcquireGlobalLock (or avs_acquire_global_lock) is called, the calling thread will block
1930+
until the named lock is available. The lock is then exclusively held by that thread until
1931+
ReleaseGlobalLock (or avs_release_global_lock) is called.
1932+
1933+
The name parameter allows for different independent global locks.
1934+
For FFTW, the recommended name is "fftw".
1935+
1936+
For safe use, set and check ``has_at_least_v12``, see interface version check methods above.
1937+
1938+
**Example#1 RAII for C++ interface plugins**
1939+
1940+
C++ plugins, which operate with the IScriptEnvironment* interface, should use a
1941+
Resource Acquisition Is Initialization (RAII) wrapper. This GlobalLockGuard class example
1942+
ensures the lock is automatically released when the guarding object goes out of scope,
1943+
even if exceptions occur.
1944+
1945+
The constructor of this GlobalLockGuard expects an IScriptEnvironment* directly,
1946+
as C++ plugins will have access to this pointer.
1947+
::
1948+
1949+
// FFTW is not thread-safe, need to guard around its functions (except fftw_execute).
1950+
// http://www.fftw.org/fftw3_doc/Thread-safety.html#Thread-safety
1951+
// Pre V12, not 100%, does not guard locks from multiple plugins using FFTW at the same time.
1952+
static std::mutex fftw_legacy_mutex; // defined as static
1953+
1954+
// Since Avisynth IF v12 use global lock which handles fftw locks for different plugins which use the same FFTW library.
1955+
class GlobalLockGuard
1956+
{
1957+
public:
1958+
// env_ptr should be the IScriptEnvironment* received by the plugin's function.
1959+
// lock_name is the name of the lock (e.g., "fftw").
1960+
// use_v12_if_available: If true, tries V12. If false or V12 not available, falls back to legacy mutex.
1961+
GlobalLockGuard(IScriptEnvironment* env_ptr, const char* lock_name, bool use_v12_global_lock)
1962+
: m_env_ptr(env_ptr), m_lockName(lock_name), m_acquired(false), m_is_legacy_lock(false)
1963+
{
1964+
if (!m_env_ptr || !m_lockName) {
1965+
// Invalid parameters, cannot acquire lock.
1966+
return;
1967+
}
1968+
1969+
// Attempt to acquire V12 global lock if requested and available.
1970+
// This assumes IScriptEnvironment provides a way to check its version.
1971+
if (use_v12_global_lock)
1972+
{
1973+
// We must use a try-catch block if AcquireGlobalLock can throw,
1974+
// or just assume it blocks and returns success/failure.
1975+
// Assuming it blocks and returns true/false for success.
1976+
m_acquired = m_env_ptr->AcquireGlobalLock(m_lockName);
1977+
if (m_acquired) {
1978+
m_is_legacy_lock = false; // Successfully acquired V12 lock
1979+
return; // Lock acquired, exit constructor
1980+
}
1981+
}
1982+
1983+
// If we reach here, V12 lock wasn't used/acquired, fall back to legacy mutex.
1984+
if (strcmp(m_lockName, "fftw") == 0) {
1985+
fftw_legacy_mutex.lock(); // Acquire the legacy mutex
1986+
m_acquired = true;
1987+
m_is_legacy_lock = true; // Acquired legacy lock
1988+
}
1989+
// else { // Handle unrecognized lock_name for legacy fallback if needed }
1990+
}
1991+
1992+
// Destructor releases the lock.
1993+
~GlobalLockGuard()
1994+
{
1995+
if (m_acquired) // Only attempt to release if successfully acquired
1996+
{
1997+
if (m_is_legacy_lock) {
1998+
fftw_legacy_mutex.unlock(); // Release legacy mutex
1999+
}
2000+
else {
2001+
// Release V12 global lock
2002+
if (m_env_ptr) { // Safety check
2003+
m_env_ptr->ReleaseGlobalLock(m_lockName);
2004+
}
2005+
}
2006+
}
2007+
}
2008+
2009+
bool is_acquired() const { return m_acquired; }
2010+
2011+
// Disallow copying and assignment to prevent common errors with mutexes.
2012+
GlobalLockGuard(const GlobalLockGuard&) = delete;
2013+
GlobalLockGuard& operator=(const GlobalLockGuard&) = delete;
2014+
2015+
private:
2016+
IScriptEnvironment* m_env_ptr; // Store the C++ interface pointer directly
2017+
const char* m_lockName;
2018+
bool m_acquired;
2019+
bool m_is_legacy_lock; // true if legacy mutex was used, false if V12 global lock was used
2020+
};
2021+
2022+
**Example for lock, C++ plugin using RAII**
2023+
2024+
::
2025+
2026+
// In a C++ plugin's source file (e.g., fft3dfilter, dfttest.cpp)
2027+
#include "AvsLockGuard.h" // Assuming GlobalLockGuard is in this header
2028+
2029+
// ... plugin setup ...
2030+
2031+
// Use a scope to define the critical section for FFTW planning.
2032+
{
2033+
// Acquire the global "fftw" lock. It's automatically released when this scope exits.
2034+
GlobalLockGuard fftw_lock(env, "fftw", has_at_least_v12);
2035+
2036+
// --- CRITICAL SECTION START ---
2037+
// Code here is protected by the global "fftw" lock.
2038+
// Only one thread across all dynamically linked FFTW plugins
2039+
// within this process can execute this section concurrently.
2040+
fftwf_plan my_plan = fftwf_plan_dft_r2c_3d(...); // Perform FFTW planning
2041+
// --- CRITICAL SECTION END ---
2042+
2043+
} // `fftw_lock` goes out of scope here, automatically releasing the lock.
2044+
2045+
2046+
Note that when the lock is used in plan destroying, in a class destructror, we don't have
2047+
``env`` as a parameter, so we must use an an ``env_saved`` pointer stored earlier.
2048+
2049+
**Example#2 RAII for C++ plugins using C-Compatible interface**
2050+
2051+
C-compatible plugins, which receive an ``AVS_ScriptEnvironment*``, should also use an
2052+
RAII wrapper for safe lock management. This ``GlobalLockGuardC`` will internally call
2053+
the ``avs_`` C functions for acquiring and releasing the lock.
2054+
2055+
::
2056+
2057+
// Note: no pre-V12 fallback is shown here
2058+
2059+
// It operates on the AVS_ScriptEnvironment* handle and calls the C-interface functions.
2060+
class GlobalLockGuardC
2061+
{
2062+
public:
2063+
// env_handle should be the AVS_ScriptEnvironment* received by the plugin's function.
2064+
GlobalLockGuardC(AVS_ScriptEnvironment* env_handle, const char* lock_name)
2065+
: m_env_handle(env_handle), m_lockName(lock_name), m_acquired(false)
2066+
{
2067+
if (m_env_handle && m_lockName)
2068+
{
2069+
m_acquired = (avs_acquire_global_lock(m_env_handle, m_lockName) == 1);
2070+
}
2071+
}
2072+
2073+
// Destructor releases the lock using the C-interface functions.
2074+
~GlobalLockGuardC()
2075+
{
2076+
if (m_acquired && m_env_handle && m_lockName)
2077+
{
2078+
avs_release_global_lock(m_env_handle, m_lockName);
2079+
}
2080+
}
2081+
2082+
bool is_acquired() const { return m_acquired; }
2083+
2084+
// Disallow copying and assignment to prevent common errors with mutexes.
2085+
GlobalLockGuardC(const GlobalLockGuardC&) = delete;
2086+
GlobalLockGuardC& operator=(const GlobalLockGuardC&) = delete;
2087+
2088+
private:
2089+
AVS_ScriptEnvironment* m_env_handle; // Store the C-compatible handle
2090+
const char* m_lockName;
2091+
bool m_acquired;
2092+
};
2093+
2094+
2095+
2096+
**Example for lock, C++ interface, with above RAII**
2097+
::
2098+
2099+
// In a C-compatible plugin's source file (e.g., myfilter.cpp that uses C++ features)
2100+
// You'd typically also include the C-compatible RAII wrapper
2101+
2102+
// Plugin function signature for C-compatible plugins (receives AVS_ScriptEnvironment*)
2103+
static AVS_Value AVSC_CC Create_xxxx(AVS_ScriptEnvironment* env, AVS_Value args, void* param)
2104+
{
2105+
// ... plugin setup ...
2106+
2107+
// Use a scope to define the critical section for FFTW planning.
2108+
{
2109+
// Acquire the global "fftw" lock using the C-compatible RAII wrapper.
2110+
GlobalLockGuardC fftw_lock(env, "fftw");
2111+
2112+
// You might want to check if the lock was acquired, though avs_acquire_global_lock
2113+
// will typically block until successful.
2114+
// If you need to handle acquisition failure, you would check fftw_lock.is_acquired().
2115+
// In a pure C plugin, you would return an error AVS_Value.
2116+
2117+
// --- CRITICAL SECTION START ---
2118+
// Code here is protected by the global "fftw" lock.
2119+
fftwf_plan my_plan = fftwf_plan_dft_r2c_3d(...); // Perform FFTW planning
2120+
// --- CRITICAL SECTION END ---
2121+
2122+
} // `fftw_lock` goes out of scope here, automatically releasing the lock.
2123+
2124+
// ... rest of the plugin logic ...
2125+
}
2126+
2127+
2128+
**Example for use from pure C-compatible**
2129+
2130+
Pure C plugins do not have access to C++ RAII. They must manually call ``avs_acquire_global_lock``
2131+
and ``avs_release_global_lock``, ensuring that every acquisition has a corresponding release.
2132+
2133+
::
2134+
2135+
fftwf_plan my_plan = NULL;
2136+
int lock_acquired = 0; // Flag to track if lock was acquired
2137+
2138+
// Acquire the lock
2139+
lock_acquired = avs_acquire_global_lock(env, "fftw");
2140+
if (!lock_acquired) {
2141+
// Handle error: Return an error AVS_Value.
2142+
return avs_new_error(clip, "MyCFilter: Failed to acquire global FFTW planner lock!");
2143+
}
2144+
2145+
// --- CRITICAL SECTION START ---
2146+
// Code here is protected by the global "fftw" lock.
2147+
my_plan = fftwf_plan_dft_r2c_3d(...); // Perform FFTW planning
2148+
// --- CRITICAL SECTION END ---
2149+
2150+
// Release the lock manually
2151+
avs_release_global_lock(env, "fftw");
2152+
2153+
2154+
2155+
See also https://github.com/AviSynth/AviSynthPlus/issues/444
2156+
and https://www.fftw.org/doc/Thread-safety.html
2157+
2158+
18862159
.. _cplusplus_pvideoframe:
18872160

18882161
PVideoFrame
@@ -2446,4 +2719,4 @@ ____
24462719

24472720
Back to :doc:`FilterSDK`
24482721

2449-
$Date: 2025/02/24 13:53:00 $
2722+
$Date: 2025/06/03 08:36:00 $

distrib/docs/english/source/avisynthdoc/FilterSDK/FilterSDK.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,18 @@ What's new in the API V11
424424

425425
- Add missing ``AVS_MT_xxxx mode`` constants to ``avisynth_c.h`` (similar to c++ ``avisynth.h`` header ``enum MtMode``)
426426

427+
.. _api_v12_whats_new:
428+
429+
What's new in the API V12
430+
-------------------------
431+
432+
- C and C++ API (AVISYNTH_INTERFACE_VERSION = 12):
433+
- Global Lock support:
434+
435+
* ``env->AcquireGlobalLock``, ``env->ReleaseGlobalLock`` (C++),
436+
* ``avs_acquire_global_lock``, ``avs_release_global_lock`` (C)
437+
438+
See :ref:`global lock support<cplusplus_acquiregloballock>`
427439

428440
Some history
429441
------------
@@ -452,7 +464,7 @@ License terms
452464

453465
Note: Avisynth Filter SDK parts are under specific :doc:`SDK license <SDKLicense>` terms.
454466

455-
$Date: 2025/02/03 11:11:11 $
467+
$Date: 2025/06/03 08:30:00 $
456468

457469
Latest online Avisynth+ version is at https://avisynthplus.readthedocs.io/en/latest/avisynthdoc/FilterSDK/FilterSDK.html
458470
This one is maintained properly.

0 commit comments

Comments
 (0)