7

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

“高帧率”= 60 fps 或更高(最好 > 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,以此类推。

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

那么,我怎样才能 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 fps。我仍然得到上面的图表。

我还尝试将鼠标灵敏度设置得非常高,然后在我这边按比例缩小值。但似乎 OSX 增加了某种加速或其他东西——这些值变得非常“不稳定”,因此无法使用,而且射速仍然太低。

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

所以我一直在阅读头文件。我发现了一些有趣的花絮。

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

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

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

#define kIOHIDPointerResolutionKey      "HIDPointerResolution"

唔。

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

问题
呃,那么,我在问什么?

  • 有没有办法在 OSX 中获得高帧率鼠标事件?(示例代码?)
  • 有没有办法在 OSX 中获取亚像素鼠标坐标?(示例代码?)
  • 有没有办法每帧读取“原始”鼠标增量?(即依赖事件。)
  • 或者,我如何获取 NXEvents 或设置 HIDParameters?示例代码?(所以我可以自己更深入地研究这个......)

(抱歉发了这么长的帖子)

4

4 回答 4

7

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

您是否尝试过过滤鼠标输入?这可能很棘手,因为过滤往往是滞后和精度之间的权衡。然而,几年前我写了一篇文章,解释了我如何过滤我的鼠标移动,并为一个游戏开发网站写了一篇文章。该链接是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 的历史缓冲区,并且只使用权重修改器,直到我满意为止。

于 2014-02-26T17:27:43.727 回答
2

如果您使用鼠标的 IOHIDDevice 回调,您可以使用它来获取双精度值:

double doubleValue = IOHIDValueGetScaledValue(inIOHIDValueRef, kIOHIDTransactionDirectionTypeOutput);
于 2013-10-25T09:34:37.893 回答
1

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

于 2011-07-22T14:53:24.320 回答
1

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

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

于 2011-07-22T14:11:54.570 回答