在OSX上如何获得高分辨率和高帧率的鼠标坐标?(或其他解决方案?)

7
我希望在OSX上获得高分辨率和高帧率的鼠标移动。
"高帧率" = 60 fps或更高(最好> 120)
"高分辨率" = 子像素值
问题是,鼠标事件以太低的帧率到来,并且值被捕捉为整数(整个像素)。这会导致“不连续”的查看体验。以下是随时间变化的鼠标增量值的可视化:
    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。这对我来说很可怕。这是疯帽子。苹果文档变得奇怪,并似乎在说“如果你深入到这个级别,你只需要头文件”。

所以我一直在读取头文件。我发现了一些有趣的细节。

<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;

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

#define kIOHIDPointerResolutionKey      "HIDPointerResolution"

嗯。

总的来说,我感觉OSX在深层次上了解子像素鼠标坐标,只是必须有一种方法可以每帧读取原始鼠标移动值,但我不知道如何获取这些值。

问题
那么,我在问什么?

  • 是否有一种在OSX中获取高帧率鼠标事件的方法?(示例代码?)
  • 是否有一种在OSX中获取子像素鼠标坐标的方法?(示例代码?)
  • 是否有一种每帧读取“原始”鼠标增量的方法?(即不依赖于事件。)
  • 或者,如何获取NXEvents或设置HIDParameters?示例代码?(这样我就可以自己深入研究了...)

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

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的历史缓冲区大小,然后只需调整权重修饰符直到满意。


2

如果您正在使用IOHIDDevice回调来处理鼠标,您可以使用以下方法获取双倍值:

double doubleValue = IOHIDValueGetScaledValue(inIOHIDValueRef, kIOHIDTransactionDirectionTypeOutput);

1

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

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


1
如果您需要在比事件分派系统提供的更低级别上访问指针设备增量信息,则可能需要使用user-space USB APIs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接