2011-07-22 15 views
5

Mi piacerebbe ottenere movimenti del mouse in alta risoluzione e frame rate elevato su OSX.Coordinate del mouse framerate ad alta risoluzione e alta su OSX? (O altra soluzione?)

"High framerate" = 60 fps o superiore (preferibilmente> 120)
"ad alta risoluzione" Valori = subpixel

Problema
Ho una vista OpenGL in esecuzione a circa la frequenza di aggiornamento del monitor , quindi è ~ 60 fps. Io uso il mouse per guardarmi intorno, quindi ho nascosto il cursore del mouse e sto facendo affidamento sui valori delta del mouse.

Il problema è che gli eventi del mouse arrivano a un framerate troppo basso e i valori vengono spezzati all'intero (pixel interi). Ciò provoca un'esperienza di visione "scomoda". Ecco una visualizzazione di valori delta topo nel tempo:

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 

Questo è un tipico (abbreviato) curva creata dall'utente spostando il mouse un po 'a destra. Ogni x rappresenta il valore deltaX per ciascun fotogramma e poiché i valori deltaX sono arrotondati ai numeri interi, questo grafico è in realtà abbastanza preciso. Come possiamo vedere, il valore deltaX sarà 0.000 un fotogramma, quindi 1.000 il successivo, ma poi sarà nuovamente 0.000, quindi 2.000, quindi nuovamente 0.000, quindi 3.000, 0.000 e così via.

Ciò significa che la vista ruoterà 2.000 unità per fotogramma, quindi ruoterà 0.000 unità il successivo e quindi ruoterà 3.000 unità. Questo accade mentre il mouse viene trascinato con una velocità più o meno costante. Nedless per dire, questo sembra merda.

Quindi, come posso 1) aumentato il framerate dell'evento del mouse? e 2) ottenere valori subpixel?

Finora
ho provato la seguente:

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

Beh, questo era evidente. dx qui è float, ma i valori sono sempre arrotondati (0.000, 1.000 ecc.). Questo crea il grafico sopra.

Quindi il passo successivo era cercare di toccare gli eventi del mouse prima che entrassero nel WindowServer, ho pensato. Così ho creato un CGEventTrap:

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

Ancora valori sono n.000, anche se credo che il tasso di sparare evento è un po 'più alto. Ma non è ancora a 60 fps. Ho ancora il grafico sopra.

Ho anche provato ad impostare la sensibilità del mouse molto alta, quindi ridimensionare i valori sul mio lato. Ma sembra che OSX aggiunga una sorta di accelerazione o qualcosa - i valori diventano veramente "instabili" e di conseguenza inutilizzabili, e la velocità di fuoco è ancora troppo bassa.

Senza fortuna, ho iniziato a seguire gli eventi del mouse nella tana del coniglio, e sono arrivato a IOKit. Questo è spaventoso per me. È il cappellaio matto. La documentazione di Apple diventa strana e sembra dire "se sei così in fondo, tutto ciò di cui hai veramente bisogno sono i file di intestazione".

Quindi ho letto i file di intestazione. E ho trovato alcune curiosità interessanti.

In <IOKit/hidsystem/IOLLEvent.h> on line 377 c'è questa struct:

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

Sede, si dice posizione sub-pixel! Ok. Poi sulla linea 73 in <IOKit/hidsystem/IOLLParameter.h>

#define kIOHIDPointerResolutionKey  "HIDPointerResolution" 

Hmm.

Tutto sommato, ho la sensazione che OSX conosca le coordinate del mouse sub-pixel in profondità, e ci deve essere solo un modo per leggere i movimenti grezzi del mouse ogni fotogramma, ma non ho idea di come ottenere quelli valori.

Domande
Erh, quindi, che cosa sto chiedendo?

  • C'è un modo per ottenere eventi del mouse framerate elevati in OSX? (Esempio di codice?)
  • C'è un modo per ottenere coordinate del mouse sub-pixel in OSX? (Esempio di codice?)
  • C'è un modo di leggere il deltato "raw" delta ogni frame? (Ie non si basano su un evento.)
  • Oppure, come ottengo NXEvents o come impostato HIDParameters? Codice di esempio? (Così posso scavare più in profondità per conto mio ...)

(Ci scusiamo per lungo post)

risposta

1

La possibilità di coordinate subpixel esiste perché Mac OS X è progettato per essere indipendente dalla risoluzione. Un quadrato di pixel hardware 2x2 su uno schermo potrebbe rappresentare un singolo pixel virtuale nel software, consentendo al cursore di essere posizionato su (x + 0.5, y + 0.5).

Su qualsiasi Mac con normale ridimensionamento 1x, non vedrete mai le coordinate subpixel perché il cursore del mouse non può essere spostato su una posizione pixel frazionaria sullo schermo - il movimento del mouse è esattamente di 1 pixel.

1

Se è necessario accedere alle informazioni delta del dispositivo puntatore a un livello inferiore rispetto a quello fornito dal sistema di distribuzione degli eventi, sarà probabilmente necessario utilizzare lo user-space USB APIs.

1

Se si utilizza i callback IOHIDDevice per il mouse è possibile utilizzare questo per ottenere un valore doppio:

double doubleValue = IOHIDValueGetScaledValue(inIOHIDValueRef, kIOHIDTransactionDirectionTypeOutput); 
5

(Questa è una risposta molto tardi, ma che secondo me è ancora utile per gli altri che inciampare su questo.)

Hai provato a filtrare l'input del mouse? Questo può essere difficile perché il filtraggio tende ad essere un compromesso tra ritardo e precisione. Tuttavia, anni fa ho scritto un articolo che spiegava come ho filtrato i miei movimenti del mouse e ho scritto un articolo per un sito di sviluppo di giochi. Il collegamento è http://www.flipcode.com/archives/Smooth_Mouse_Filtering.shtml.

Dal momento che il sito non è più in fase di sviluppo attivo (e può andare via) qui è l'estratto rilevante:


In quasi tutti i casi, il filtraggio significa media. Tuttavia, se modifichiamo semplicemente il movimento del mouse nel tempo, introdurremo il lag.Come, quindi, filtriamo senza introdurre effetti collaterali? Bene, useremo ancora la media, ma lo faremo con un po 'di intelligenza. E allo stesso tempo, daremo all'utente un controllo preciso sul filtraggio in modo che possano regolarlo autonomamente.

Utilizzeremo un filtro non lineare di input medio del mouse nel tempo, in cui i valori meno recenti hanno una minore influenza sul risultato filtrato.

Come funziona

Ogni fotogramma, se si sposta il mouse o no, abbiamo messo il movimento attuale del mouse in un buffer storia e rimuovere il valore storia più antica. Quindi la nostra cronologia contiene sempre X campioni, dove X è la "dimensione del buffer della cronologia", che rappresenta i movimenti del mouse campionati più recenti nel tempo.

Se si utilizza una dimensione del buffer della cronologia di 10 e una media standard dell'intero buffer, il filtro introdurrebbe molto ritardo. I movimenti veloci del mouse sarebbero in ritardo di 1/6 di secondo su una macchina da 60FPS. In un gioco d'azione veloce, questo sarebbe molto semplice, ma praticamente inutilizzabile. Nello stesso scenario, una dimensione del buffer della cronologia di 2 ci darebbe un ritardo molto piccolo, ma un filtraggio molto scadente (reazioni brusche e a scatti)

Il filtro non lineare ha lo scopo di combattere questo scenario mutuamente esclusivo. L'idea è molto semplice. Piuttosto che mediare ciecamente tutti i valori nel buffer della cronologia allo stesso modo, li calcoliamo con un peso medio. Iniziamo con un peso di 1,0. Quindi il primo valore nel buffer della cronologia (l'input del mouse del frame corrente) ha tutto il peso. Quindi moltiplichiamo questo peso con un "modificatore di peso" (diciamo ... 0.2) e passiamo al valore successivo nel buffer della cronologia. Più indietro nel tempo (attraverso il nostro buffer storico) andiamo, i valori hanno sempre meno peso (influenza) sul risultato finale.

Per elaborare, con un modificatore di peso di 0,5, il campione del frame corrente avrebbe il 100% di peso, il campione precedente avrebbe il 50% di peso, il campione successivo più vecchio avrebbe il 25% di peso, il successivo avrebbe il 12,5% di peso e così via. Se si traccia questo grafico, sembra una curva. Quindi l'idea alla base del modificatore di peso è di controllare quanto diminuisce la curva man mano che i campioni nella storia invecchiano.

Ridurre il ritardo significa diminuire il modificatore di peso. Ridurre il modificatore di peso a 0 fornirà all'utente un feedback crudo e non filtrato. Aumentandolo a 1.0, il risultato sarà una media semplice di tutti i valori nel buffer della cronologia.

Offriremo all'utente due variabili per il controllo fine: la dimensione del buffer della cronologia e il modificatore del peso. Tendo ad usare una dimensione del buffer della cronologia di 10, e gioco con il modificatore di peso fino a quando non sono felice.

Problemi correlati