2015-10-15 15 views
5

Sto scrivendo un'app di ebook reader per Windows Store. Uso le catene di scambio Direct2D + DXGI per rendere le pagine dei libri sullo schermo.Multithreading Direct2D efficiente

Il contenuto del mio libro a volte è piuttosto complesso (geometria, bitmap, maschere, ecc.), Quindi può essere necessario fino a 100 ms per renderlo. Quindi sto cercando di eseguire un rendering off-screen su una bitmap in un thread separato, quindi mostrerò questa bitmap nel thread principale.

Tuttavia, non riesco a capire come farlo in modo efficiente.

Finora ho provato due approcci:

  1. Utilizzare un unico ID2D1Factory con la bandiera D2D1_FACTORY_TYPE_MULTI_THREADED, creare ID2D1BitmapRenderTarget e utilizzarlo in thread in background per il rendering off-screen. (Questo richiede inoltre ID2D1Multithread::Enter/Leave sulle operazioni IDXGISwapChain::Present). Il problema è che l'operazione ID2D1RenderTarget::EndDraw nel thread in background richiede talvolta fino a 100 ms e il rendering del thread principale è bloccato per questo periodo a causa del blocco interno di Direct2D.

  2. Utilizzare un codice ID2D1Factory separato nella discussione in background (come descritto in http://www.sdknews.com/ios/using-direct2d-for-server-side-rendering) e disattivare la sincronizzazione interna di Direct2D. In questo caso non c'è cross-locking tra due thread. Sfortunatamente, in questo caso non posso usare direttamente la bitmap risultante nel principale ID2D1Factory, perché appartiene a una fabbrica diversa. Devo spostare i dati bitmap nella memoria della CPU, quindi copiarli nella memoria GPU del principale ID2D1Factory. Questa operazione introduce anche ritardi significativi (credo che sia dovuto ad ampi accessi di memoria, ma non ne sono sicuro).

C'è un modo per farlo in modo efficiente?

P.S. Tutto il tempo qui è dato per tablet Acer Switch 10. Sul normale PC Core i7 entrambi gli approcci funzionano senza alcun ritardo visibile.

risposta

4

Ok, ho trovato una soluzione.

Fondamentalmente, tutto ciò di cui avevo bisogno è di modificare l'approccio 2 per utilizzare la condivisione delle risorse DXGI tra due set di fabbrica DirectX. Salterò tutti i dettagli scabrosi (possono essere trovate qui: http://xboxforums.create.msdn.com/forums/t/66208.aspx), ma passaggi fondamentali sono:

  1. Creare due insiemi di risorse DirectX: principale (che verrà utilizzato per il rendering sullo schermo), e secondaria (per il rendering fuori schermo).
  2. Utilizzando ID3D11Device2 dal set principale risorsa, creare consistenza D3D 2D da CreateTexture2DD3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_RESOURCE_MISC_SHARED_NTHANDLE e D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX bandiere.
  3. Ottenere l'handle condiviso da esso lanciandolo a IDXGIResource1 e chiamando CreateSharedHandle da esso con XGI_SHARED_RESOURCE_READ e DXGI_SHARED_RESOURCE_WRITE.
  4. Aprire questa trama condivisa in set di risorse secondarie nella thread in background chiamando ID3D11Device2::OpenSharedResource1.
  5. Acquisire il mutex con chiave di questa texture (IDXGIKeyedMutex::AcquireSync), creare il target di rendering da esso (ID2D1Factory2::CreateDxgiSurfaceRenderTarget), disegnare su di esso e rilasciare mutex (IDXGIKeyedMutex::ReleaseSync).
  6. Sul thread principale, nel set di risorse principale, acquisire mutex e creare bitmap condivisa da texture creata nel passaggio 2, disegnare questa bitmap, quindi rilasciare mutex.

Si noti che è necessario il roaming mutex. Non farlo risulta in alcuni messaggi di errore di debug di DirectX criptici e operazioni errate o addirittura in crash.

0

tl; dr: rendering in bitmap sul thread in background in modalità software. Disegna da bitmap per renderizzare il target sul thread dell'interfaccia utente in modalità hardware.

L'approccio migliore che ho potuto trovare finora è quello di utilizzare thread in background con software di rendering (IWICImagingFactory::CreateBitmap e ID2D1Factory::CreateWicBitmapRenderTarget) e poi copiarlo in una bitmap di hardware di nuovo sul filo con l'hardware rendering di destinazione tramite ID2D1RenderTarget::CreateBitmapFromWicBitmap . E poi blitarlo usando ID2D1RenderTarget::DrawBitmap.

Ecco come paint.net 4.0 esegue il rendering delle selezioni. Quando si disegna una selezione con lo strumento Lazo, verrà utilizzato un thread in background per disegnare il contorno della selezione in modo asincrono (il thread dell'interfaccia utente non attende il completamento). Si può finire con un poligono molto complicato a causa dello stile del tratto e delle animazioni. Lo renderò 4 volte, in cui ogni frame di animazione ha un offset leggermente diverso per lo stile di tratto tratteggiato.

Ovviamente questo rendering può richiedere un po 'di tempo in quanto il poligono diventa più complesso (ovvero se continui a scrivere per un po'). Ho alcune altre ottimizzazioni speciali per quando usi lo strumento Sposta selezione che ti permette di fare trasformazioni (ruotare, tradurre, ridimensionare): se il thread in background non ha ancora ridisegnato il poligono corrente con la nuova trasformazione, allora renderà la vecchia bitmap (con il poligono corrente e la vecchia trasformazione) con la nuova trasformazione applicata. Lo schema di selezione può essere distorto (ridimensionamento) o troncato (tradotto all'esterno dell'area visualizzabile) mentre il thread in background si rialza, ma è un piccolo prezzo da pagare per la reattività a 60 fps.Questa ottimizzazione funziona molto bene perché non è possibile modificare la trasformazione del poligono e di una selezione contemporaneamente.