最简单的延迟形式之一是定期采样(轮询),并将它们插入某种机器中,该机器将计算最后一次的平均值N
样品。这有效地产生了从任何位置到任何其他位置的线性插值,延迟为N / SampleRate
.
为了有效地做到这一点,您可以使用环形缓冲区。如果您在其中存储整数数据,则可以使用记忆来避免每次对整个缓冲区进行平均。相反,您只需保留一个运行总和,直到缓冲区已满,然后每次添加新值时,您首先减去以前的值(您将要替换的值)。
在 C++ 中,可能如下所示:
template<typename ValueType, size_t BufferSize>
struct SummedRingBuffer
{
public:
void Append(const ValueType& value)
{
if (count < BufferSize) [[unlikely]]
{
buffer[tail] = value;
sum += value;
tail = ++count;
}
else [[likely]]
{
sum -= buffer[tail];
buffer[tail] = value;
sum += value;
tail = (tail + 1) % BufferSize;
}
}
const ValueType& GetSum() const { return sum; }
size_t GetCount() const { return count; }
private:
std::array<ValueType, BufferSize> buffer;
ValueType sum = {};
size_t count = 0;
size_t tail = 0;
};
现在,对于您的鼠标位置,只需使用以下两个缓冲区:
template<typename ValueType, size_t BufferSize>
struct DelayedMousePosition
{
public:
void SetPos(const ValueType& x, const ValueType& y)
{
xvalues.Append(x);
yvalues.Append(y);
}
void GetPos(ValueType& x, ValueType& y)
{
if (xvalues.GetCount() > 0)
{
x = xvalues.GetSum() / xvalues.GetCount();
y = yvalues.GetSum() / yvalues.GetCount();
}
}
private:
SummedRingBuffer<ValueType, BufferSize> xvalues;
SummedRingBuffer<ValueType, BufferSize> yvalues;
};
您必须确保您的数据类型足够大,以便您推入其中的任何数据都不会溢出总和。所以,为了安全起见,你可以使用int64_t
作为类型。您应该避免在此方法中使用浮点类型,因为它们可能会因精度误差而导致总和漂移。
无论如何,假设您每秒接收 60 次更新,并且您需要 500 毫秒的平滑窗口。嗯,这需要 30 个样本。所以你可以像这样设置延迟缓冲区:
DelayedMousePosition<int64_t, 30> delayedMousePos;
每次收到样本时,都将其推入缓冲区SetPos
,然后用拉出平滑位置GetPos
。就这么简单!没有动态分配或任何其他事情发生,并且它执行最少的处理,这正是您在驱动程序中想要的。也许对于内核的东西你需要切换出去std::array
对于 C 风格的数组。我不知道。但这就好了,不需要其他修改。
现在,您可能会发现我没有做任何内核模式的事情,并且我怀疑现在大多数驱动程序中通常不进行轮询。至少对于 HID 设备来说不是。因此,您可能需要采取一些额外的技巧才能完成这项工作。但鉴于您的问题缺乏具体细节,我认为这是一个合理的起点。