MeteorRain
30th August 2026, 03:00
In current AviSynth+ plugin development, video frame processing predominantly relies on raw pointer arithmetic and manual pitch tracking (GetReadPtr, GetWritePtr, GetPitch, and ptr += pitch). While this low-level model grants direct hardware-level control, developers must continuously manage manual byte-to-element conversions (pitch / sizeof(T)), repetitive offset calculations for sub-regions (ROI/cropping), and potential boundary errors across different bit depths.
To modernize the developer experience, AviSynth+ could introduce a compact, header-only view template that abstracts 2D strided frame buffers. Downstream filters could query ready-to-use plane view objects directly from video frames instead of querying detached pointers and byte strides. By wrapping pointer arithmetic and element scaling inside an inline value-type wrapper, filter logic can remain clean, expressive, and less susceptible to pointer misuse.
Proposed Conceptual Structure
The core architecture separates horizontal scanlines and vertical stepping into orthogonal components:
Row<T>: Represents a single horizontal scanline. By enforcing unit stride (S_x = 1) in the type system, it guarantees that compilers can auto-vectorize horizontal pixel loops without dynamic stride checks.
RowCursor<T>: Replaces manual pointer incrementation (ptr += pitch) with a type-safe line iterator that compiles to a single add instruction.
Plane<T>: A 24-byte non-owning 2D view (Pointer, Width, Height, Element Pitch) designed to be passed directly via CPU registers.
RestrictPlane<T> / as_restrict: Safe by default for in-place buffers (MakeWritable), with an explicit opt-in conversion path to inject __restrict semantics when source and destination are known to be disjoint.
namespace your_namespace_here {
// ============================================================================
// 1. Row<T> - 1D Contiguous Scanline View (Guarantees unit stride for SIMD)
// ============================================================================
template <typename T>
class Row {
public:
T* data(); // Pointer to first element in current line
size_t size(); // Number of elements in current line
bool empty(); // Check if line width is 0
T& operator[](size_t x); // Fast indexed element access
T* begin(); // Contiguous forward iterator (for range-for)
T* end(); // End iterator
Row subspan(size_t off, size_t n); // Sub-line slice (e.g. for horizontal margins)
};
// ============================================================================
// 2. RowCursor<T> - Vertical Line Stepper (Compiles directly to: ptr += pitch)
// ============================================================================
template <typename T>
class RowCursor {
public:
T* ptr(); // Raw pointer to the start of current row
Row<T> row(); // Obtain contiguous Row<T> view for current line
Row<T> operator*(); // Dereference shorthand to obtain Row<T>
Row<T> operator[](int dy); // Relative vertical peek (e.g. cur[-1] for previous line)
RowCursor& operator++(); // Advance down by 1 stride line
RowCursor& operator--(); // Step up by 1 stride line
RowCursor& operator+=(int dy); // Step by arbitrary vertical offset
};
// ============================================================================
// 3. Plane<T> - 2D Strided Memory View (Register-friendly 24-byte layout)
// ============================================================================
template <typename T>
class Plane {
public:
int32_t width(); // Plane width in elements
int32_t height(); // Plane height in elements
int32_t stride(); // Horizontal pitch in elements (pitch / sizeof(T))
ptrdiff_t stride_bytes(); // Raw byte stride
T* data(); // Base plane buffer pointer
T& operator()(int y, int x); // 2D coordinate random access
T* row_ptr(int y); // Direct pointer to start of row y
Row<T> row(int y); // Contiguous 1D view of row y
RowCursor<T> cursor(int y = 0); // Create row cursor starting at row y
RowCursor<T> begin(); // Cursor at row 0 (enables range-for over rows)
RowCursor<T> end(); // Cursor at row height
Plane subplane(int x, int y, int w, int h); // Zero-copy ROI cropping / sub-window
};
// ============================================================================
// 4. Opt-in Restrict (No-Alias) Converters
// ============================================================================
// Explicit wrappers for independent buffers (bypasses compiler alias analysis)
template <typename T> using RestrictPlane = /* Plane with __restrict data pointer */;
template <typename T> using RestrictRow = /* Row with __restrict data pointer */;
// Converters (analogous to std::as_const)
template <typename T> RestrictPlane<T> as_restrict(Plane<T> plane);
template <typename T> Plane<T> as_unrestricted(RestrictPlane<T> plane);
} // namespace your_namespace_here
Usage
Obtaining Plane Views from Video Frames
Downstream filters can obtain view objects either via a free helper function or via a frame method:
// Option A: Via free factory helper
auto src_plane = ns::make_plane<const uint8_t>(src, PLANAR_Y);
auto dst_plane = ns::make_plane<uint8_t>(dst, PLANAR_Y);
// Option B: Directly from PVideoFrame (if integrated into core interface)
auto src_plane = src->GetPlane<const uint8_t>(PLANAR_Y);
auto dst_plane = dst->GetPlane<uint8_t>(PLANAR_Y);
Pure C++ non-restrict pointers
template <typename T>
void InvertFilter(ns::Plane<const T> src, ns::Plane<T> dst) {
for (int y = 0; y < src.height(); ++y) {
auto in_row = src.row(y); // Contiguous 1D view
auto out_row = dst.row(y); // Contiguous 1D view
for (size_t x = 0; x < in_row.size(); ++x) {
out_row[x] = 255 - in_row[x];
}
}
}
SIMD non-restrict pointers
void ProcessAVX2(ns::Plane<const uint8_t> src, ns::Plane<uint8_t> dst) {
const __m256i v_max = _mm256_set1_epi8(255);
const int width = src.width();
for (int y = 0; y < src.height(); ++y) {
const uint8_t* s = src.row_ptr(y);
uint8_t* d = dst.row_ptr(y);
int x = 0;
for (; x + 31 < width; x += 32) {
__m256i v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(s + x));
_mm256_storeu_si256(reinterpret_cast<__m256i*>(d + x), _mm256_sub_epi8(v_max, v));
}
for (; x < width; ++x) {
d[x] = 255 - s[x];
}
}
}
Highway non-restrict pointers
#include <hwy/highway.h>
namespace hn = hwy::HWY_NAMESPACE;
template <class D>
void ProcessHighway(ns::Plane<const uint8_t> src, ns::Plane<uint8_t> dst, D d) {
const auto v_max = hn::Set(d, 255);
const size_t N = hn::Lanes(d);
for (int y = 0; y < src.height(); ++y) {
auto in_row = src.row(y);
auto out_row = dst.row(y);
size_t x = 0;
for (; x + N <= in_row.size(); x += N) {
const auto v = hn::LoadU(d, in_row.data() + x);
hn::StoreU(hn::Sub(v_max, v), d, out_row.data() + x);
}
for (; x < in_row.size(); ++x) {
out_row[x] = 255 - in_row[x];
}
}
}
Restrict pointers
template <typename T>
void DiffKernel(ns::RestrictPlane<const T> src1,
ns::RestrictPlane<const T> src2,
ns::RestrictPlane<T> dst)
{
for (int y = 0; y < dst.height(); ++y) {
auto in1 = src1.row(y); // Type is RestrictRow<const T>
auto in2 = src2.row(y); // Type is RestrictRow<const T>
auto out = dst.row(y); // Type is RestrictRow<T>
for (size_t x = 0; x < out.size(); ++x) {
out[x] = std::abs(static_cast<int>(in1[x]) - static_cast<int>(in2[x]));
}
}
}
// Filter entry point: direct retrieval of restrict objects
PVideoFrame MyDiffFilter::GetFrame(int n, IScriptEnvironment* env) {
PVideoFrame src1 = child1->GetFrame(n, env);
PVideoFrame src2 = child2->GetFrame(n, env);
PVideoFrame dst = env->NewVideoFrame(vi);
// Option A: Via make_restrict_plane helper
auto plane1 = ns::make_restrict_plane<const uint8_t>(src1, PLANAR_Y);
auto plane2 = ns::make_restrict_plane<const uint8_t>(src2, PLANAR_Y);
auto planeD = ns::make_restrict_plane<uint8_t>(dst, PLANAR_Y);
// Option B: Directly via frame method
// auto plane1 = src1->GetRestrictPlane<const uint8_t>(PLANAR_Y);
// auto plane2 = src2->GetRestrictPlane<const uint8_t>(PLANAR_Y);
// auto planeD = dst->GetRestrictPlane<uint8_t>(PLANAR_Y);
DiffKernel(plane1, plane2, planeD);
return dst;
}
Performance verification benchmark
To verify whether these view abstractions introduce any compiler optimization penalties, a benchmark was conducted using a composite video processing kernel (3-tap blur + difference enhancement + clamping) on a 1080p sequence (1920x1080, stride 1984, Release /O2 /arch:AVX2, single-threaded):
[Benchmark Results: 1080p, Single-threaded, Release /O2 /arch:AVX2]
Clang-CL 19:
- C++ Scalar (Raw Pointer + Restrict) : 2,005 FPS ( 3.87 GB/s) -> 100.0% (base)
- C++ Scalar (Plane + RestrictRow) : 2,290 FPS ( 4.42 GB/s) -> 114.2% (+14.2%)
- AVX2 Hand-written (Raw Pointer) : 4,636 FPS ( 8.95 GB/s) -> 100.0% (base)
- AVX2 Hand-written (Plane/RowPtr) : 4,637 FPS ( 8.95 GB/s) -> 100.0% ( 0.00% delta)
MSVC (VS 2026):
- C++ Scalar (Raw Pointer + Restrict) : 1,449 FPS ( 2.80 GB/s) -> 100.0% (base)
- C++ Scalar (Plane/RowPtr + Restrict) : 1,460 FPS ( 2.82 GB/s) -> 100.7% (+ 0.7%)
- AVX2 Hand-written (Raw Pointer) : 4,482 FPS ( 8.65 GB/s) -> 100.0% (base)
- AVX2 Hand-written (Plane/RowPtr) : 4,458 FPS ( 8.61 GB/s) -> 99.5% (- 0.5% noise)
I would be very interested to hear your thoughts on this modernization concept.
While this is not meant for the immediate 3.7.6 milestone, it seems like a promising direction to simplify filter writing in the near future while preserving 100% of raw pointer performance. Looking forward to any advice, critique, or design feedback.
Disclaimer: This post was crafted by me and proofread by AI.
To modernize the developer experience, AviSynth+ could introduce a compact, header-only view template that abstracts 2D strided frame buffers. Downstream filters could query ready-to-use plane view objects directly from video frames instead of querying detached pointers and byte strides. By wrapping pointer arithmetic and element scaling inside an inline value-type wrapper, filter logic can remain clean, expressive, and less susceptible to pointer misuse.
Proposed Conceptual Structure
The core architecture separates horizontal scanlines and vertical stepping into orthogonal components:
Row<T>: Represents a single horizontal scanline. By enforcing unit stride (S_x = 1) in the type system, it guarantees that compilers can auto-vectorize horizontal pixel loops without dynamic stride checks.
RowCursor<T>: Replaces manual pointer incrementation (ptr += pitch) with a type-safe line iterator that compiles to a single add instruction.
Plane<T>: A 24-byte non-owning 2D view (Pointer, Width, Height, Element Pitch) designed to be passed directly via CPU registers.
RestrictPlane<T> / as_restrict: Safe by default for in-place buffers (MakeWritable), with an explicit opt-in conversion path to inject __restrict semantics when source and destination are known to be disjoint.
namespace your_namespace_here {
// ============================================================================
// 1. Row<T> - 1D Contiguous Scanline View (Guarantees unit stride for SIMD)
// ============================================================================
template <typename T>
class Row {
public:
T* data(); // Pointer to first element in current line
size_t size(); // Number of elements in current line
bool empty(); // Check if line width is 0
T& operator[](size_t x); // Fast indexed element access
T* begin(); // Contiguous forward iterator (for range-for)
T* end(); // End iterator
Row subspan(size_t off, size_t n); // Sub-line slice (e.g. for horizontal margins)
};
// ============================================================================
// 2. RowCursor<T> - Vertical Line Stepper (Compiles directly to: ptr += pitch)
// ============================================================================
template <typename T>
class RowCursor {
public:
T* ptr(); // Raw pointer to the start of current row
Row<T> row(); // Obtain contiguous Row<T> view for current line
Row<T> operator*(); // Dereference shorthand to obtain Row<T>
Row<T> operator[](int dy); // Relative vertical peek (e.g. cur[-1] for previous line)
RowCursor& operator++(); // Advance down by 1 stride line
RowCursor& operator--(); // Step up by 1 stride line
RowCursor& operator+=(int dy); // Step by arbitrary vertical offset
};
// ============================================================================
// 3. Plane<T> - 2D Strided Memory View (Register-friendly 24-byte layout)
// ============================================================================
template <typename T>
class Plane {
public:
int32_t width(); // Plane width in elements
int32_t height(); // Plane height in elements
int32_t stride(); // Horizontal pitch in elements (pitch / sizeof(T))
ptrdiff_t stride_bytes(); // Raw byte stride
T* data(); // Base plane buffer pointer
T& operator()(int y, int x); // 2D coordinate random access
T* row_ptr(int y); // Direct pointer to start of row y
Row<T> row(int y); // Contiguous 1D view of row y
RowCursor<T> cursor(int y = 0); // Create row cursor starting at row y
RowCursor<T> begin(); // Cursor at row 0 (enables range-for over rows)
RowCursor<T> end(); // Cursor at row height
Plane subplane(int x, int y, int w, int h); // Zero-copy ROI cropping / sub-window
};
// ============================================================================
// 4. Opt-in Restrict (No-Alias) Converters
// ============================================================================
// Explicit wrappers for independent buffers (bypasses compiler alias analysis)
template <typename T> using RestrictPlane = /* Plane with __restrict data pointer */;
template <typename T> using RestrictRow = /* Row with __restrict data pointer */;
// Converters (analogous to std::as_const)
template <typename T> RestrictPlane<T> as_restrict(Plane<T> plane);
template <typename T> Plane<T> as_unrestricted(RestrictPlane<T> plane);
} // namespace your_namespace_here
Usage
Obtaining Plane Views from Video Frames
Downstream filters can obtain view objects either via a free helper function or via a frame method:
// Option A: Via free factory helper
auto src_plane = ns::make_plane<const uint8_t>(src, PLANAR_Y);
auto dst_plane = ns::make_plane<uint8_t>(dst, PLANAR_Y);
// Option B: Directly from PVideoFrame (if integrated into core interface)
auto src_plane = src->GetPlane<const uint8_t>(PLANAR_Y);
auto dst_plane = dst->GetPlane<uint8_t>(PLANAR_Y);
Pure C++ non-restrict pointers
template <typename T>
void InvertFilter(ns::Plane<const T> src, ns::Plane<T> dst) {
for (int y = 0; y < src.height(); ++y) {
auto in_row = src.row(y); // Contiguous 1D view
auto out_row = dst.row(y); // Contiguous 1D view
for (size_t x = 0; x < in_row.size(); ++x) {
out_row[x] = 255 - in_row[x];
}
}
}
SIMD non-restrict pointers
void ProcessAVX2(ns::Plane<const uint8_t> src, ns::Plane<uint8_t> dst) {
const __m256i v_max = _mm256_set1_epi8(255);
const int width = src.width();
for (int y = 0; y < src.height(); ++y) {
const uint8_t* s = src.row_ptr(y);
uint8_t* d = dst.row_ptr(y);
int x = 0;
for (; x + 31 < width; x += 32) {
__m256i v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(s + x));
_mm256_storeu_si256(reinterpret_cast<__m256i*>(d + x), _mm256_sub_epi8(v_max, v));
}
for (; x < width; ++x) {
d[x] = 255 - s[x];
}
}
}
Highway non-restrict pointers
#include <hwy/highway.h>
namespace hn = hwy::HWY_NAMESPACE;
template <class D>
void ProcessHighway(ns::Plane<const uint8_t> src, ns::Plane<uint8_t> dst, D d) {
const auto v_max = hn::Set(d, 255);
const size_t N = hn::Lanes(d);
for (int y = 0; y < src.height(); ++y) {
auto in_row = src.row(y);
auto out_row = dst.row(y);
size_t x = 0;
for (; x + N <= in_row.size(); x += N) {
const auto v = hn::LoadU(d, in_row.data() + x);
hn::StoreU(hn::Sub(v_max, v), d, out_row.data() + x);
}
for (; x < in_row.size(); ++x) {
out_row[x] = 255 - in_row[x];
}
}
}
Restrict pointers
template <typename T>
void DiffKernel(ns::RestrictPlane<const T> src1,
ns::RestrictPlane<const T> src2,
ns::RestrictPlane<T> dst)
{
for (int y = 0; y < dst.height(); ++y) {
auto in1 = src1.row(y); // Type is RestrictRow<const T>
auto in2 = src2.row(y); // Type is RestrictRow<const T>
auto out = dst.row(y); // Type is RestrictRow<T>
for (size_t x = 0; x < out.size(); ++x) {
out[x] = std::abs(static_cast<int>(in1[x]) - static_cast<int>(in2[x]));
}
}
}
// Filter entry point: direct retrieval of restrict objects
PVideoFrame MyDiffFilter::GetFrame(int n, IScriptEnvironment* env) {
PVideoFrame src1 = child1->GetFrame(n, env);
PVideoFrame src2 = child2->GetFrame(n, env);
PVideoFrame dst = env->NewVideoFrame(vi);
// Option A: Via make_restrict_plane helper
auto plane1 = ns::make_restrict_plane<const uint8_t>(src1, PLANAR_Y);
auto plane2 = ns::make_restrict_plane<const uint8_t>(src2, PLANAR_Y);
auto planeD = ns::make_restrict_plane<uint8_t>(dst, PLANAR_Y);
// Option B: Directly via frame method
// auto plane1 = src1->GetRestrictPlane<const uint8_t>(PLANAR_Y);
// auto plane2 = src2->GetRestrictPlane<const uint8_t>(PLANAR_Y);
// auto planeD = dst->GetRestrictPlane<uint8_t>(PLANAR_Y);
DiffKernel(plane1, plane2, planeD);
return dst;
}
Performance verification benchmark
To verify whether these view abstractions introduce any compiler optimization penalties, a benchmark was conducted using a composite video processing kernel (3-tap blur + difference enhancement + clamping) on a 1080p sequence (1920x1080, stride 1984, Release /O2 /arch:AVX2, single-threaded):
[Benchmark Results: 1080p, Single-threaded, Release /O2 /arch:AVX2]
Clang-CL 19:
- C++ Scalar (Raw Pointer + Restrict) : 2,005 FPS ( 3.87 GB/s) -> 100.0% (base)
- C++ Scalar (Plane + RestrictRow) : 2,290 FPS ( 4.42 GB/s) -> 114.2% (+14.2%)
- AVX2 Hand-written (Raw Pointer) : 4,636 FPS ( 8.95 GB/s) -> 100.0% (base)
- AVX2 Hand-written (Plane/RowPtr) : 4,637 FPS ( 8.95 GB/s) -> 100.0% ( 0.00% delta)
MSVC (VS 2026):
- C++ Scalar (Raw Pointer + Restrict) : 1,449 FPS ( 2.80 GB/s) -> 100.0% (base)
- C++ Scalar (Plane/RowPtr + Restrict) : 1,460 FPS ( 2.82 GB/s) -> 100.7% (+ 0.7%)
- AVX2 Hand-written (Raw Pointer) : 4,482 FPS ( 8.65 GB/s) -> 100.0% (base)
- AVX2 Hand-written (Plane/RowPtr) : 4,458 FPS ( 8.61 GB/s) -> 99.5% (- 0.5% noise)
I would be very interested to hear your thoughts on this modernization concept.
While this is not meant for the immediate 3.7.6 milestone, it seems like a promising direction to simplify filter writing in the near future while preserving 100% of raw pointer performance. Looking forward to any advice, critique, or design feedback.
Disclaimer: This post was crafted by me and proofread by AI.