GUI-Thread mit OpenGL/DirectX - überhaupt möglich?



  • Hi,

    vor ein paar Wochen gab es hier mal eine Art Diskussion über Background-Threads, bei der ich die Position vertreten habe, dass es durchaus sinnvoll ist einen "GUI-Thread" zu benutzen, anstatt alles in eine einzige große "Working-Queue" zu packen, die dann von mehreren Threads abgearbeitet wird. Aus einem einfachen Grund: Ein Thread ist genau das, was man von einer GUI erwartet: Er wird unabhängig vom Rest des Systems (so hofft man zumindest ;)) in festen Abständen aufgerufen.

    Ich habe damals auch geschrieben, dass das ja vielleicht mal eine Idee für Spiele wäre. Also quasi ein Thread, der für das Interface-Overlay zuständig ist. Und wenn man so darüber nachdenkt, scheint mir das auch gar keine schlechte Idee zu sein. Ich würde z.B. argumentieren, dass wenn man bei einem MMO in einer "belebten" Stadt ist, und hier nur 15-25 FPS hat, das Spiel trotzdem spielbar ist. Aber wenn auch die GUI zwischendurch 15 FPS erreicht, ist das schon argh nervig, dabei will man doch nur Items hin und her schieben. Ähnliche Szenarien gibt es auch bei anderen Genres, und sei es nur dass die GUI "flüssig" bleibt, auch wenn gerade das ganze Spiel am abnippeln ist.

    Allerdings frage ich mich gerade, ob das überhaupt möglich wäre. (Ohne zu große Performanceverluste.) Denn bei OpenGL und DirectX muss man ja an einer Stelle swap()en, und hier synchronisiert man ja sozusagen zwangsläufig. Und während die 3D-Szene gerade gerendert wird, kann man der ja schlecht den Puffer unter der Nase weg swap()en. Allerdings könnte man uU den 3D-Thread in eine Textur rendern lassen, die dann immer kopiert wird wenn der Thread fertig ist. Ich weiß allerdings nicht, wie performant man das machen könnte.

    Ich wüsste ganz gerne mal was ihr von der Idee haltet, ob ich hier etwas Entscheidenes übersehe und total auf dem Holzweg bin, ob es nur einfach nicht (gut) umsetzbar ist, oder ob es vielleicht eine bessere Möglichkeit gibt als in eine Textur zu rendern.



  • Rendern tust du immer nur in einem Thread. Die Logik kannst aufteilen.



  • Ehm ja.. könntest du das etwas weiter ausführen?^^



  • Je nach verwendeter API ist es durchaus möglich sowas zu machen. Die Frage ist nur ob es Sinn macht.
    Weil's halt Performance kostet.

    Ohne Trennung:
    * Szene direkt in den Back-Buffer rendern
    * Overlay drüber rendern
    * Present

    Mit Trennung müsstest du die Szene von einem eigenen Thread in eine extra Render-Texture rendern lassen. Nachdem ein neues Frame fertig ist wird diese Render-Texture dem Presenter-Thread übergeben (und der Szenen-Thread holt sich eine neue Render-Texture aus irgend einem Pool).
    Der Presenter-Thread muss dann erstmal die Render-Texture auf den Back-Buffer rendern, dann kann er sein Overlay drüberpappen, und dann presenten.

    Selbst wenn der Szenen-Thread und der Presenter-Thread exakt gleich viele Bilder machen, verbrätst du da Bandbreite. Auf modernen Systemen vermutlich zu vernachlässigen, aber OK.

    Dann kommt aber noch dazu dass du bei 12 FPS vs. 60 das ganze ja nicht 1:1 machst, sondern 1:5. Also pro 1 Szenen-Frame machst du 5 Frames im Presenter-Thread. Vermutlich wäre auch das auf modernen Systemen zu verschmerzen, aber pfuh, ich sag mal die Leute kaufen die dicken Garfikkarten eh...? Wird also kaum zu merkbar mehr verkäufen führen wenn man so ein Feature einbaut.

    Oder Kurzfassung: man muss dann aufpassen dass man sich mit dem Presenter-Thread nicht den Render-Thread zu stark ausbremst, und dann statt 12 FPS auf einmal nur mehr 5 FPS hat.

    *Abschweif*

    Mit dem Mauszeiger wurde das früher häufig so gemacht, dafür wurden dann aber Hardware-Sprites hergenommen. Keine Ahnung ob unter D3D9/10/11 noch Hardware-Sprites für den Mauszeiger unterstützt werden. (Die Karten werden es vermutlich noch können, aber auch da bin ich mir nichtmal sicher.)

    Und die andere früher häufig verwendete Variante, nämlich den "Mauszeiger-Bereich" sichern und beim Verschieben der Maus zu restoren hat auch deutliche Nachteile. Dann kann man nämlich nimmer "discard" als Present-Mode verwenden, was heisst man hat kein echtes Double-Buffering mehr, was heisst man muss mit Tearing rechnen (und wieder Performance-Einbussen).

    Oder wie hättest du dir die Sache vorgestellt?



  • hustbaer schrieb:

    Oder wie hättest du dir die Sache vorgestellt?

    Nun, erstmal hatte ich die Idee. Da ich denke dass das relativ viel Arbeit sein könnte, wollte ich erstmal nachfragen, vielleicht hat das ja schon mal jemand probiert. (Und festgestellt dass es aus Grund xy niemals funktionieren kann.)

    Eine denkbare Umsetzung wäre z.B. noch, dass man ein Textur-Handle hat das einfach vom 3D-Render-Thread ausgetauscht wird. Quasi ein doppelter double-buffer.

    Oder Kurzfassung: man muss dann aufpassen dass man sich mit dem Presenter-Thread nicht den Render-Thread zu stark ausbremst, und dann statt 12 FPS auf einmal nur mehr 5 FPS hat.

    Genau das ist eben die Frage. Sicher kostet es Performance, dessen bin ich mir bewusst. Aber wenn es ~2% kostet, denke ich überwiegen die Vorteile. (Ob man jetzt 25 oder 23 FPS hat, das merkt man nicht so.) Bei 10% wird es schon kritisch, da würde ich eher zu der üblichen Methode tendieren.



  • Mal eine rein prinzipielle Überlegung: Wenn du 15 fps hast und die Grafikkarte nicht voll ausgelastet ist, dann ist dein Code verbesserungswürdig. Wenn du 15 fps hast und deine Grafikkarte voll ausgelastet ist, wirst du es auch nicht schaffen, dein GUI schneller zu rendern...



  • cooky451 schrieb:

    Eine denkbare Umsetzung wäre z.B. noch, dass man ein Textur-Handle hat das einfach vom 3D-Render-Thread ausgetauscht wird. Quasi ein doppelter double-buffer.

    Ja klar so. Das "Textur-Handle" muss der Presenter-Thread aber trotzdem nochmal rendern.


  • Mod

    hustbaer schrieb:

    Je nach verwendeter API ist es durchaus möglich sowas zu machen. ...
    Mit Trennung müsstest du die Szene von einem eigenen Thread in eine extra Render-Texture rendern lassen.

    welche api kann das?
    opengl kann nur ein context haben der an den thread gebunden ist afaik, ein direct3d9 device kann eh nur von einem thread mit render calls versorgt werden, ist ja eine state machine die nichts von threads weiss und zwischen zwei devices kann man eigentlich keine resourcen teilen. d3d10/11 haben zwar ein deferred context, aber das zeichnet nur auf, es muss dennoch im main-thread abgespielt werden.

    ist das eine spezielle opengl extension die du meinst?



  • dot schrieb:

    Mal eine rein prinzipielle Überlegung: Wenn du 15 fps hast und die Grafikkarte nicht voll ausgelastet ist, dann ist dein Code verbesserungswürdig. Wenn du 15 fps hast und deine Grafikkarte voll ausgelastet ist, wirst du es auch nicht schaffen, dein GUI schneller zu rendern...

    Doch, klar. In dem man der GUI eine höhere Priorität zuweißt. Da diese wesentlich leichter zu zeichnen ist, könnte es sein dass man 60 GUI-FPS bekommt wenn man nur einen einzigen 3D-Frame/s opfert.



  • rapso schrieb:

    hustbaer schrieb:

    Je nach verwendeter API ist es durchaus möglich sowas zu machen. ...
    Mit Trennung müsstest du die Szene von einem eigenen Thread in eine extra Render-Texture rendern lassen.

    welche api kann das?

    Jede die Rendertargets zwischen Devices/Kontexten/... sharen kann sollte das können.

    Bei OpenGL sollte das mit wglShareLists gehen.

    Bei D3D9Ex/D3D10 über den pSharedHandle Parameter der diversen CreateXxx Funktionen: http://msdn.microsoft.com/en-us/library/bb219800(v=vs.85).aspx#Sharing_Resources

    Ich hab's selbst noch nie probiert, aber ich sehe keinen Grund warum es nicht funktionieren sollte.



  • hustbaer schrieb:

    rapso schrieb:

    hustbaer schrieb:

    Je nach verwendeter API ist es durchaus möglich sowas zu machen. ...
    Mit Trennung müsstest du die Szene von einem eigenen Thread in eine extra Render-Texture rendern lassen.

    welche api kann das?

    Jede die Rendertargets zwischen Devices/Kontexten/... sharen kann sollte das können.

    Bei OpenGL sollte das mit wglShareLists gehen.

    Bei D3D9Ex/D3D10 über den pSharedHandle Parameter der diversen CreateXxx Funktionen: http://msdn.microsoft.com/en-us/library/bb219800(v=vs.85).aspx#Sharing_Resources

    Ich hab's selbst noch nie probiert, aber ich sehe keinen Grund warum es nicht funktionieren sollte.

    diese entwicklung ist komplett an mir vorbeigegangen, danke, wuste ich echt nicht. dann waere es interesant zu sehen wie dein vorschlag in der wirklichkeit laeuft, ob D3D bzw der treiber das rendering von zwei devices wirklich interleaven wuerde, oder serialisiert.



  • Bei wglCreateContextAttribsARB kann man soweit ich weiß auch gleich nen zweiten Context angeben mit dem man Objekte shared.

    ob D3D bzw der treiber das rendering von zwei devices wirklich interleaven wuerde

    Jo, das wäre sicherlich ein Problem, wenn der erst mal in Ruhe alles zu Ende zeichnet und dann ruft "nächstes Bild bitte!". 😃



  • Ich hab' rapso was über 3D APIs erzählen können. 🕶
    Den Tag mal ich mir rot im Kalender an, und den Kalender lass ich mir in Epoxy eingiessen damit er ewig hält 😃

    Jo, wie das dann flutscht wäre interessant.
    Ich vermute aber mal ähnlich gut oder schlecht wie zwei Windowd D3D (bzw. OpenGL) Programme die gleichzeitig mit "presentation interval immediate" Dinge rendern.
    Und das zumindest könnte man ja halbwegs einfach ausprobieren.



  • Ehm, nur mal so, ein ganz anderes Problem, das mir gerade aufgefallen ist, als ich nur mal den Message-Loop vom Rest trennen wollte: Die HDCs und HGLRCs scheinen nicht Thread-Safe zu sein. Heißt für mich, dass man unter Windows nur im Window-Thread rendern kann. Was relativ bescheuert ist, aber da lässt sich wohl kaum etwas gegen unternehmen. Und mir scheint auch, dass es da einige Fallstricke gibt. Man kann z.B. nicht einfach mal einen memory-mapped Pointer an glTexImage2D geben, weil das dann blocken könnte. Vielleicht hat hier ja jemand eine Idee, wie man das lösen könnte. 😞



  • Was genau meinst du mit "nicht threadsafe", wie äußert sich das und woher kommen die betreffenden HDCs und HGRCs?



  • Na ja, dass man Get/Peek-Message nur in dem Thread benutzen kann, für den auch das Fenster erstellt wurde, sollte ja klar sein. Und das HWND kann man scheinbar auch nur in dem Thread benutzen, wenn man GetDC aufruft. Diesen DC braucht man ja wieder für wglCreateContext - dessen Rückgabewert man auch nicht einfach in einem anderen Thread benutzen darf. (Und so zieht sich das auch weiter wenn man einen modernen Kontext erstellt.) - Oder gibt es da irgendwo ein "Schlupfloch"?



  • cooky451 schrieb:

    Und das HWND kann man scheinbar auch nur in dem Thread benutzen, wenn man GetDC aufruft.

    Das wär mir neu.

    cooky451 schrieb:

    [...] dessen Rückgabewert man auch nicht einfach in einem anderen Thread benutzen darf.

    Das wär mir ebenfalls neu.

    Du kannst ein HDC nur in einem Thread gleichzeitig benutzen und ein GL Context kann auch zu jedem Zeitpunkt nur in einem Thread aktiv sein. Aber ansonsten...



  • Ah, sehr schön. Gerade probiert, und es funktioniert perfekt, das HWND kann man also auch in einem anderen Thread benutzen. Danke! 🙂

    Eine kleine Frage noch am Rande*: Vorher hat das Rendern einfach gestoppt, sobald ich das Fenster vergrößert/kleinert habe. Das ist jetzt nicht mehr so, was grundsätzlich positiv ist. Aber dafür flackert es, weil das beim verkleinern scheinbar von Windows einmal komplett mit weiß überschrieben wird. Kann man das irgendwie verhindern indem man WM_PAINT abfängt oder sowas?

    * Sollte wohl eher in's WinAPI-Forum, aber wo wir schon mal hier sind..



  • Evtl. einfach mal den Background Brush auf 0 setzen? Ansonten evtl. WM_ERASEBKGND abfangen und in WM_PAINT ValidateRect() aufrufen, sofern du das Fenster schon an anderer Stelle periodisch neu bemalst.



  • dot schrieb:

    Evtl. einfach mal den Background Brush auf 0 setzen? Ansonten evtl. WM_ERASEBKGND abfangen

    This.
    Hier noch was interessantes dazu:
    http://stackoverflow.com/a/1750301/1743172


Log in to reply