Skip to content

Commit

Permalink
README.md, expr2/exprfilter.cpp, reactor/LLVMReactor.hpp, Reactor.cpp…
Browse files Browse the repository at this point in the history
…: add support for relative pixel access with mirrored boundary condition

Signed-off-by: akarin <i@akarin.info>
  • Loading branch information
AkarinVS committed Jul 31, 2021
1 parent 25a9c02 commit 9a31bd9
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 35 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Plugin akarin
Expr
----

`akarin.Expr(clip[] clips, string[] expr[, int format, int opt=1])`
`akarin.Expr(clip[] clips, string[] expr[, int format, int opt=1, int boundary=0])`

This works just like [`std.Expr`](http://www.vapoursynth.com/doc/functions/expr.html) (esp. with the same SIMD JIT support on x86 hosts), with the following additions:
- use `x.PlaneStatsAverage` to load the `PlaneStatsAverage` frame property of the current frame in the given clip `x`.
Expand All @@ -21,7 +21,11 @@ This works just like [`std.Expr`](http://www.vapoursynth.com/doc/functions/expr.
- Pop a value and store to variable `var`: `var!`
- Read a variable `var` and push onto stack: `var@`
- (\*) Static relative pixel access (modeled after [AVS+ Expr](http://avisynth.nl/index.php/Expr#Pixel_addressing))
- Use `x[relX,relY]` to access the pixel (relX, relY) relative to current coordinate, where -width < relX < width and -height < relY < height. Off screen pixels will be cloned from the respective edge. Both relX and relY should be constant.
- Use `x[relX,relY]` to access the pixel (relX, relY) relative to current coordinate, where -width < relX < width and -height < relY < height. Off screen pixels will be either cloned from the respective edge (clamped) or use the pixel mirror from the respective edge (mirrored). Both relX and relY should be constant.
- Optionally, use `:m` or `:c` suffixes to specify mirrored and clamped boundary conditions, respectively.
- The `boundary` argument specifies the default boundary condition for all relative pixel accesses without explicit specification:
- 0 means clamped
- 1 means mirroed
- Support more bases for constants
- hexadecimals: 0x123 or 0x123.4p5
- octals: 023 (however, invalid octal numbers will be parsed as floating points, so "09" will be parsed the same as "9.0")
Expand Down
113 changes: 84 additions & 29 deletions expr2/exprfilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ std::vector<std::string> features = {
"N", "X", "Y", "pi", "width", "height",
"trunc", "round", "floor",
"@", "!",
"x[x,y]",
"x[x,y]", "x[x,y]:m",
};

enum class ComparisonType {
Expand All @@ -104,6 +104,12 @@ enum class LoadConstIndex {
LAST = 1,
};

enum class BoundaryCondition {
Unspecified = 0,
Clamped,
Mirrored,
};

union ExprUnion {
int32_t i;
uint32_t u;
Expand All @@ -121,9 +127,10 @@ struct ExprOp {
ExprUnion imm;
std::string name;
int x, y;
BoundaryCondition bc;

ExprOp(ExprOpType type, ExprUnion param = {}, std::string name = {}, int x = 0, int y = 0)
: type(type), imm(param), name(name), x(x), y(y) {}
ExprOp(ExprOpType type, ExprUnion param = {}, std::string name = {}, int x = 0, int y = 0, BoundaryCondition bc = BoundaryCondition::Unspecified)
: type(type), imm(param), name(name), x(x), y(y), bc(bc) {}
};

bool operator==(const ExprOp &lhs, const ExprOp &rhs) {
Expand Down Expand Up @@ -218,7 +225,7 @@ ExprOp decodeToken(const std::string &token)
{ "width",{ ExprOpType::CONST_LOAD, static_cast<int>(LoadConstType::Width) } },
{"height",{ ExprOpType::CONST_LOAD, static_cast<int>(LoadConstType::Height) } },
};
static const std::regex relpixelRe { "^([a-z])\\[(-?[0-9]+),(-?[0-9]+)\\]$" };
static const std::regex relpixelRe { "^([a-z])\\[(-?[0-9]+),(-?[0-9]+)\\](:[cm])?$" };
std::smatch match;

auto it = simple.find(token);
Expand Down Expand Up @@ -247,9 +254,11 @@ ExprOp decodeToken(const std::string &token)
// frame property access
return{ ExprOpType::CONST_LOAD, static_cast<int>(LoadConstType::LAST) + (token[0] >= 'x' ? token[0] - 'x' : token[0] - 'a' + 3), token.substr(2) };
} else if (std::regex_match(token, match, relpixelRe)) {
ASSERT(match.size() == 4);
auto clip = match[1].str(), sx = match[2].str(), sy = match[3].str();
return{ ExprOpType::MEM_LOAD, clip[0] >= 'x' ? clip[0] - 'x' : clip[0] - 'a' + 3, "", atoi(sx.c_str()), atoi(sy.c_str()) };
ASSERT(match.size() == 5);
auto clip = match[1].str(), sx = match[2].str(), sy = match[3].str(), flag = match[4].str();
BoundaryCondition bc = flag.size() == 0 ? BoundaryCondition::Unspecified :
(flag[1] == 'm' ? BoundaryCondition::Mirrored : BoundaryCondition::Clamped);
return{ ExprOpType::MEM_LOAD, clip[0] >= 'x' ? clip[0] - 'x' : clip[0] - 'a' + 3, "", atoi(sx.c_str()), atoi(sy.c_str()), bc };
} else {
size_t pos = 0;
long long l = 0;
Expand Down Expand Up @@ -316,11 +325,16 @@ class Compiler {
const VSVideoInfo * const *vi;
int numInputs;
int optMask;
Context(const std::string &expr, const VSVideoInfo *vo, const VSVideoInfo *const *vi, int numInputs, int opt):
expr(expr), vo(vo), vi(vi), numInputs(numInputs), optMask(opt) {
tokens = tokenize(expr);
for (const auto &tok: tokens)
ops.push_back(decodeToken(tok));
bool mirror;
Context(const std::string &expr, const VSVideoInfo *vo, const VSVideoInfo *const *vi, int numInputs, int opt, int mirror):
expr(expr), vo(vo), vi(vi), numInputs(numInputs), optMask(opt), mirror(!!mirror) {
tokens = tokenize(expr);
for (const auto &tok: tokens) {
auto op = decodeToken(tok);
if (op.bc == BoundaryCondition::Unspecified)
op.bc = mirror ? BoundaryCondition::Mirrored : BoundaryCondition::Clamped;
ops.push_back(op);
}
}
enum {
flagUseInteger = 1<<0,
Expand Down Expand Up @@ -391,8 +405,8 @@ class Compiler {
void buildOneIter(const Helper &helpers, State &state);

public:
Compiler(const std::string &expr, const VSVideoInfo *vo, const VSVideoInfo * const *vi, int numInputs, int opt = 0) :
ctx(expr, vo, vi, numInputs, opt) {}
Compiler(const std::string &expr, const VSVideoInfo *vo, const VSVideoInfo * const *vi, int numInputs, int opt = 0, int mirror = 0) :
ctx(expr, vo, vi, numInputs, opt, mirror) {}

Compiled compile();
};
Expand Down Expand Up @@ -544,7 +558,9 @@ static VType relativeAccessAdjust(rr::Int &x, rr::Int &alignedx, rr::Int &width,
using namespace rr;
if (op.x == 0)
return v;
else {
else if (op.bc == BoundaryCondition::Mirrored)
return v;
else if (op.bc == BoundaryCondition::Clamped) {
BasicBlock *contBb = Nucleus::createBasicBlock();
if (op.x < 0) {
int absx = std::abs(op.x);
Expand Down Expand Up @@ -661,19 +677,51 @@ void Compiler<lanes>::buildOneIter(const Helper &helpers, State &state)
const VSFormat *format = ctx.vi[op.imm.i]->format;
const bool unaligned = op.x != 0;
Int y = state.y, x = state.x;
if (op.y != 0)
y = Clamp(state.y + op.y, 0, state.height-1);
if (op.x != 0)
x = Clamp(state.x + op.x, 0, state.width-1);
IntV offsets = 0;
if (op.bc == BoundaryCondition::Clamped) {
if (op.y != 0)
y = Clamp(state.y + op.y, 0, state.height-1);
if (op.x != 0)
x = Clamp(state.x + op.x, 0, state.width-1);
} else { // Mirrored
if (op.y != 0) {
Int sy = state.y + Clamp(op.y, -state.height, state.height);
y = IfThenElse(sy < 0, -1 - sy,
IfThenElse(sy >= state.height, 2*state.height-1 - sy, sy));
}
if (op.x != 0) {
Int cx = Clamp(op.x, -state.width, state.width);
Int w2m1 = 2 * state.width - 1;
for (int i = 0; i < lanes; i++) {
Int sx = x + i + cx;
Int xi = IfThenElse(sx < 0, -1 - sx,
IfThenElse(sx >= state.width, w2m1 - sx, sx));
offsets = Insert(offsets, xi, i);
}
offsets = offsets * IntV(format->bytesPerSample);
x = 0;
}
}
p += y * state.strides[op.imm.i + 1] + x * format->bytesPerSample;
const bool regularLoad = op.bc != BoundaryCondition::Mirrored || op.x == 0;
if (format->sampleType == stInteger) {
IntV v;
if (format->bytesPerSample == 1)
v = IntV(*Pointer<ByteV>(p, (unaligned ? 1:lanes)*sizeof(uint8_t)));
else if (format->bytesPerSample == 2)
v = IntV(*Pointer<UShortV>(p, (unaligned ? 1:lanes)*sizeof(uint16_t)));
else if (format->bytesPerSample == 4)
v = IntV(*Pointer<IntV>(p, (unaligned ? 1:lanes)*sizeof(uint32_t)));
if (format->bytesPerSample == 1) {
if (regularLoad)
v = IntV(*Pointer<ByteV>(p, (unaligned ? 1:lanes)*sizeof(uint8_t)));
else
v = IntV(Gather(Pointer<Byte>(p), offsets, IntV(~0), sizeof(uint8_t)));
} else if (format->bytesPerSample == 2) {
if (regularLoad)
v = IntV(*Pointer<UShortV>(p, (unaligned ? 1:lanes)*sizeof(uint16_t)));
else
v = IntV(Gather(Pointer<UShort>(p), offsets, IntV(~0), sizeof(uint16_t)));
} else if (format->bytesPerSample == 4) {
if (regularLoad)
v = IntV(*Pointer<IntV>(p, (unaligned ? 1:lanes)*sizeof(uint32_t)));
else
v = IntV(Gather(Pointer<Int>(p), offsets, IntV(~0), sizeof(uint16_t)));
}
v = relativeAccessAdjust<lanes>(x, state.x, state.width, op, v);
if (ctx.forceFloat())
OUT(FloatV(v));
Expand All @@ -683,8 +731,12 @@ void Compiler<lanes>::buildOneIter(const Helper &helpers, State &state)
FloatV v;
if (format->bytesPerSample == 2)
abort(); // XXX: f16 not supported
else if (format->bytesPerSample == 4)
v = *Pointer<FloatV>(p, (unaligned ? 1:lanes)*sizeof(float));
else if (format->bytesPerSample == 4) {
if (regularLoad)
v = *Pointer<FloatV>(p, (unaligned ? 1:lanes)*sizeof(float));
else
v = Gather(Pointer<Float>(p), offsets, IntV(~0), sizeof(float));
}
v = relativeAccessAdjust<lanes>(x, state.x, state.width, op, v);
OUT(v);
}
Expand Down Expand Up @@ -1180,6 +1232,9 @@ static void VS_CC exprCreate(const VSMap *in, VSMap *out, void *userData, VSCore
int optMask = int64ToIntS(vsapi->propGetInt(in, "opt", 0, &err));
if (err) optMask = 1;

int mirror = int64ToIntS(vsapi->propGetInt(in, "boundary", 0, &err));
if (err) mirror = 0;

for (int i = 0; i < d->vi.format->numPlanes; i++) {
if (!expr[i].empty()) {
d->plane[i] = poProcess;
Expand All @@ -1193,7 +1248,7 @@ static void VS_CC exprCreate(const VSMap *in, VSMap *out, void *userData, VSCore
if (d->plane[i] != poProcess)
continue;

Compiler<LANES> comp(expr[i], &d->vi, vi, d->numInputs, optMask);
Compiler<LANES> comp(expr[i], &d->vi, vi, d->numInputs, optMask, mirror);
d->compiled[i] = comp.compile();
d->proc[i] = reinterpret_cast<ExprData::ProcessProc>(const_cast<void *>(d->compiled[i].routine->getEntry()));
}
Expand Down Expand Up @@ -1244,7 +1299,7 @@ void VS_CC versionCreate(const VSMap *in, VSMap *out, void *user_data, VSCore *c

void VS_CC exprInitialize(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) {
//configFunc("com.vapoursynth.expr", "expr", "VapourSynth Expr Filter", VAPOURSYNTH_API_VERSION, 1, plugin);
registerFunc("Expr", "clips:clip[];expr:data[];format:int:opt;opt:int:opt;", exprCreate, nullptr, plugin);
registerFunc("Expr", "clips:clip[];expr:data[];format:int:opt;opt:int:opt;boundary:int:opt;", exprCreate, nullptr, plugin);
registerFunc("Version", "", versionCreate, nullptr, plugin);
initExpr();
}
21 changes: 17 additions & 4 deletions expr2/reactor/LLVMReactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1198,14 +1198,27 @@ static llvm::Value *createGather(llvm::Value *base, llvm::Type *elTy, llvm::Valu
}
}

RValue<Float4> Gather(RValue<Pointer<Float>> base, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment, bool zeroMaskedLanes /* = false */)
RValue<Float8> Gather(RValue<Pointer<Float>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes /* = false */)
{
return As<Float4>(V(createGather(V(base.value()), T(Float::type()), V(offsets.value()), V(mask.value()), alignment, zeroMaskedLanes)));
return As<Float8>(V(createGather(V(base.value()), T(Float::type()), V(offsets.value()), V(mask.value()), alignment, zeroMaskedLanes)));
}

RValue<Int4> Gather(RValue<Pointer<Int>> base, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment, bool zeroMaskedLanes /* = false */)
RValue<Byte8> Gather(RValue<Pointer<Byte>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes /* = false */)
{
return As<Int4>(V(createGather(V(base.value()), T(Int::type()), V(offsets.value()), V(mask.value()), alignment, zeroMaskedLanes)));
llvm::Value *x = createGather(V(base.value()), T(Byte::type()), V(offsets.value()), V(mask.value()), alignment, zeroMaskedLanes);
llvm::Value *zero = llvm::Constant::getNullValue(x->getType());
const llvm::SmallVector<CreateShuffleVectorIndexType, 16> vec = {0,1,2,3,4,5,6,7,8,8,8,8,8,8,8,8};
return As<Byte8>(V(jit->builder->CreateShuffleVector(x, zero, vec)));
}

RValue<UShort8> Gather(RValue<Pointer<UShort>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes /* = false */)
{
return As<UShort8>(V(createGather(V(base.value()), T(UShort::type()), V(offsets.value()), V(mask.value()), alignment, zeroMaskedLanes)));
}

RValue<Int8> Gather(RValue<Pointer<Int>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes /* = false */)
{
return As<Int8>(V(createGather(V(base.value()), T(Int::type()), V(offsets.value()), V(mask.value()), alignment, zeroMaskedLanes)));
}

static void createScatter(llvm::Value *base, llvm::Value *val, llvm::Value *offsets, llvm::Value *mask, unsigned int alignment)
Expand Down
6 changes: 6 additions & 0 deletions expr2/reactor/Reactor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2899,7 +2899,13 @@ void MaskedStore(RValue<Pointer<Float4>> base, RValue<Float4> val, RValue<Int4>
void MaskedStore(RValue<Pointer<Int4>> base, RValue<Int4> val, RValue<Int4> mask, unsigned int alignment);

RValue<Float4> Gather(RValue<Pointer<Float>> base, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<Float8> Gather(RValue<Pointer<Float>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<Byte4> Gather(RValue<Pointer<Byte>> base, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<Byte8> Gather(RValue<Pointer<Byte>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<UShort4> Gather(RValue<Pointer<UShort>> base, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<UShort8> Gather(RValue<Pointer<UShort>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<Int4> Gather(RValue<Pointer<Int>> base, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment, bool zeroMaskedLanes = false);
RValue<Int8> Gather(RValue<Pointer<Int>> base, RValue<Int8> offsets, RValue<Int8> mask, unsigned int alignment, bool zeroMaskedLanes = false);
void Scatter(RValue<Pointer<Float>> base, RValue<Float4> val, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment);
void Scatter(RValue<Pointer<Int>> base, RValue<Int4> val, RValue<Int4> offsets, RValue<Int4> mask, unsigned int alignment);

Expand Down

0 comments on commit 9a31bd9

Please sign in to comment.