OSX上的高分辨率和高帧率鼠标坐标?(或者其他解决方案?)



我想在OSX上获得高分辨率和高帧率的鼠标移动。

"High framerate" = 60fps或更高(最好> 120)
"高分辨率"=亚像素值


我有一个opengl视图以显示器刷新率运行,所以它是~60 fps。我用鼠标四处看,所以我隐藏了鼠标光标,我依靠鼠标的增量值。

问题是鼠标事件的帧率太低,值被捕捉到整数(整像素)。这会导致"起伏不定"的观看体验。下面是鼠标增量值随时间变化的可视化图:

    mouse delta X
    ^                xx
  2 |      x    x x     x xx
    | x x x   x             xx x  x x
  0 |x-x-x--xx-x-x-xx--x-x----x-xx-x-----> frame
    |
-2  |
    v

这是一个典型的(缩短的)曲线,由用户将鼠标向右移动一点创建。每个x表示每个帧的deltaX值,由于deltaX值被四舍五入为整数,因此此图实际上非常准确。正如我们所看到的,一帧的deltaX值是0.000,下一帧是1.000,然后再次是0.000,然后是2.000,然后是0.000,然后是3.000,0.000,以此类推。

这意味着视图将在一帧旋转2000个单位,然后在下一帧旋转0.000个单位,然后旋转3000个单位。当鼠标以或多或少恒定的速度被拖动时,就会发生这种情况。不用说,这看起来像垃圾。

那么,我如何1)增加鼠标的事件帧率?2)得到亚像素值?

到目前为止


我尝试了以下方法:

- (void)mouseMoved:(NSEvent *)theEvent {
    CGFloat dx, dy;
    dx = [theEvent deltaX];
    dy = [theEvent deltaY];
    // ...
    actOnMouse(dx,dy);
}

这个很明显。dx在这里是浮点数,但值总是四舍五入(0.000,1.000等)。这就创建了上面的图表。

所以我想,下一步是在鼠标事件进入WindowServer之前尝试点击它们。所以我创建了一个CGEventTrap:

eventMask = (1 << kCGEventMouseMoved);
eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap,
            0, eventMask, myCGEventCallback, NULL);
//...
myCGEventCallback(...){
    double dx = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
    double dy = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
}

仍然值是n.000,尽管我认为事件触发的速率稍微高一些。但它仍然不是60帧/秒。我还是能看到上面的图表。

我也试过把鼠标的灵敏度设置得很高,然后把我这边的值调小。但OSX似乎增加了某种加速之类的东西——数值变得非常"不稳定",因此无法使用,而且射击速度仍然太低。

没有运气,我已经开始跟随老鼠事件进入兔子洞,我已经到达了IOKit。这对我来说很可怕。是疯帽子匠。苹果的文档变得很奇怪,似乎在说"如果你这么深入,你真正需要的只是头文件"。

所以我一直在读头文件。我还发现了一些有趣的花边新闻。

在第377行<IOKit/hidsystem/IOLLEvent.h>中有这个结构体:

struct {    /* For mouse-down and mouse-up events */
    UInt8   subx;       /* sub-pixel position for x */
    UInt8   suby;       /* sub-pixel position for y */
    // ...
} mouse;

看,它说亚像素位置!好的。然后在<IOKit/hidsystem/IOLLParameter.h>

第73行
#define kIOHIDPointerResolutionKey      "HIDPointerResolution"

嗯。

总而言之,我觉得OSX了解亚像素鼠标坐标,并且必须有一种方法来读取每一帧的原始鼠标移动,但我只是不知道如何获得这些值。


那我到底想要什么?

    在OSX中有一种获得高帧率鼠标事件的方法吗?(示例代码?)
  • 在OSX中是否有获得亚像素鼠标坐标的方法?(示例代码?)
  • 是否有一种方法来读取"原始"鼠标增量每帧?(即而不是依赖于事件)
  • 或者,如何获取NXEvents或设置HIDParameters?示例代码?(所以我可以自己更深入地研究这个…)

(这是一个很晚的答案,但我认为对于其他偶然发现这个问题的人来说仍然很有用。)

你试过过滤鼠标输入吗?这可能很棘手,因为过滤往往是滞后和精度之间的权衡。然而,几年前我写了一篇文章,解释了我是如何过滤鼠标移动的,并为一个游戏开发网站写了一篇文章。链接:http://www.flipcode.com/archives/Smooth_Mouse_Filtering.shtml

由于该网站不再处于积极的开发中(并且可能会消失),这里是相关的摘录:


在几乎所有情况下,过滤意味着平均。然而,如果我们只是简单地计算鼠标移动随时间的平均值,我们就会引入延迟。那么,我们如何在不产生任何副作用的情况下进行过滤呢?好吧,我们还是用平均,但我们会用一些智能来做。同时,我们会给用户对过滤的精细控制,这样他们就可以自己调整了。

我们将使用随时间推移的平均鼠标输入的非线性滤波器,其中旧值对过滤结果的影响较小。

工作原理

每一帧,无论你是否移动鼠标,我们都将当前的鼠标移动放入历史缓冲区,并删除最古老的历史值。所以我们的历史记录总是包含X个样本,其中X是"历史缓冲区大小",代表最近一次采样的鼠标移动。

如果我们使用历史缓冲区大小为10,并使用整个缓冲区的标准平均值,则过滤器将引入大量延迟。快速鼠标移动在60FPS的机器上会落后1/6秒。在一款快速动作游戏中,这可能会非常流畅,但实际上是不可用的。在同样的情况下,历史缓冲大小为2会给我们带来很少的延迟,但过滤效果很差(玩家的反应很粗糙)。

非线性过滤器旨在对抗这种互斥的情况。这个想法很简单。我们不是盲目地平均历史缓冲区中的所有值,而是用一个权重平均它们。我们从1.0的权重开始。因此,历史缓冲区中的第一个值(当前帧的鼠标输入)具有完整的权重。然后我们将这个权重乘以一个"权重修饰符"(比如……)0.2),并移动到历史缓冲区中的下一个值。随着时间的推移(通过我们的历史缓冲区),这些值对最终结果的权重(影响)越来越小。

为了详细说明,使用0.5的权重修饰符,当前框架的样本将具有100%的权重,前一个样本将具有50%的权重,下一个最老的样本将具有25%的权重,下一个将具有12.5%的权重,以此类推。如果你把它画出来,它看起来像一条曲线。因此,权重修正器背后的想法是控制随着历史上样本变老,曲线下降的幅度。

减少延迟意味着减少权重修饰符。将权重修饰符减小到0将为用户提供原始的、未经过滤的反馈。将其增加到1.0将导致结果是历史缓冲区中所有值的简单平均值。

我们将为用户提供两个变量进行精细控制:历史缓冲区大小和权重修饰符。我倾向于使用历史缓冲区大小为10,并且只是使用权重修改器,直到我满意为止。

如果你正在为鼠标使用IOHIDDevice回调,你可以使用这个来获得一个双值:

double doubleValue = IOHIDValueGetScaledValue(inIOHIDValueRef, kIOHIDTransactionDirectionTypeOutput);

存在亚像素坐标的可能性,因为Mac OS X被设计为与分辨率无关。屏幕上2x2硬件像素的正方形可以代表软件中的单个虚拟像素,允许光标放置在(x + 0.5, y + 0.5)

在任何使用正常1x缩放的实际Mac上,你永远不会看到亚像素坐标,因为鼠标光标不能移动到屏幕上的分数像素位置——鼠标移动的量子正是1像素。

如果您需要访问比事件调度系统提供的更低级别的指针设备增量信息,那么您可能需要使用用户空间USB api。

最新更新