OpenGL-Bild im Speicher erzeugen
-
Das schon gelesen?
http://stackoverflow.com/questions/12482166/creating-opengl-context-without-window
-
Du brauchst den HDC eines Fensters...
-
Hallo hustbaer,
hustbaer schrieb:
Das schon gelesen?
http://stackoverflow.com/questions/12482166/creating-opengl-context-without-windowDanke für den Link (ich hatte bisher immer nach "OpenGl in memory" gesucht); leider hilft er nicht weiter. Dort wird empfohlen GetDesktopWindow als Fenster zu verwenden. Dann kommt bei ChoosePixelFormat eine 50 zurück und der Fehler bei SetPixelFormat ist dann ERROR_INVALID_FLAGS.
In ähnlichen Beiträgen auf Stackverflow wird empfohlen ein leeres und unsichtbares Fenster anzulegen. Da komme ich aber nicht über CreateWindow hinweg - es führt in allen Fällen zu einem ERROR_RESOURCE_TYPE_NOT_FOUND. Auch das Anlegen einer Resource brachte keinen Erfolg. In einer Konsolenanwendung scheint ihm irgendwas zu fehlen ??
dot schrieb:
Du brauchst den HDC eines Fensters...
.. das habe ich schon begriffen. Nur wie mache ich ein Fenster in einer Konsolenanwendung oder DLL?
und wie komme ich nachher an das Bild?
und was macht eigentlich dieses komische SelectObject?Gruß
Werner
-
Werner Salomon schrieb:
dot schrieb:
Du brauchst den HDC eines Fensters...
.. das habe ich schon begriffen. Nur wie mache ich ein Fenster in einer Konsolenanwendung oder DLL?
Gleich wie in jeder anderen Windowsanwendung...
Werner Salomon schrieb:
und wie komme ich nachher an das Bild?
Das hängt davon ab welche OpenGL Version du genau verwenden kannst. Am einfachsten malst du direkt in den default Framebuffer und holst dir dann den Inhalt per glReadPixels(). Die moderne Lösung wären Framebuffer Objects.
Werner Salomon schrieb:
und was macht eigentlich dieses komische SelectObject?
Das setzt rein prinzipiell ein gegebenes Objekt in den jeweiligen DC ein. Aber diesen ganzen CreateCompatibleDC() und SelectObject() Gedöns kannst du vergessen, weil das funktioniert mit OpenGL sowieso nicht so.
Was genau ist das eigentlich, das du da rendern willst? Der ganze OpenGL Kram ist leider übelst kaputt; wenn du eine ordentliche Lösung für Windows haben willst, würde ich persönlich zu Direct3D raten. Potentieller Vorteil von OpenGL ist, dass einige simple Dinge mit legacy OpenGL wesentlich weniger aufwändig umzusetzen sind weil die API in vielen Belangen mehr high-level ist – bzw. so tut als ob und in ernsthaften Anwendungen ist man dann darauf angewiesen, seinen Code so zu schreiben, dass der Driver mit seinen 100000000 Heuristiken aus dem Usage-Pattern erratet, was man eigentlich gemeint hat und dann das richtige tut, obwohl es nirgendwo explizit dasteht weil die API es einen nicht explizit hinschreiben lässt – während mit Direct3D und modernem OpenGL ein relativ hoher Grundaufwand betrieben werden muss, bis man selbst die einfachsten Dinge machen kann (wenn das mal steht is es dafür dann aber selbstverständlich wesentlich mächtiger). Nachteil ist, dass OpenGL ein riesiger Misthaufen ist...
-
Boh - das Fenster ist geschafft. Ich hatte eine leere WindProc-Function angelegt, die nur 0 zurück lieferte. Mann muss dort auch noch DefWindowProc aufrufen, dann bekommt man auch ein Fenster.
Aber der Fehler bei SetPixelFormat bleibt bei ERROR_INVALID_FLAGS.
Mein aktueller Stand:
int main() { auto const hinstance = GetModuleHandle(nullptr); const char* const myclass = "TheHiddenWindow" ; // Register the main window class. WNDCLASS wc; wc.style = CS_OWNDC; // CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; // MainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hinstance; wc.hIcon = nullptr; // LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = nullptr; // LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = nullptr; // GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "egal"; wc.lpszClassName = myclass; if( !RegisterClass(&wc) ) { auto const ec = ::GetLastError(); return -9; } auto hWnd = CreateWindow( myclass, "Title-egal2", 0, // dwStyle 0,0, // Pos x,y 320, 200, // Breite, Höhe nullptr, // hWndParent nullptr, // hMenu hinstance, // hInstance -> GetModuleHandle nullptr // lpParam ); // !WS_VISIBLE if( hWnd == nullptr ) { auto const ec = ::GetLastError(); // 1813 := ERROR_RESOURCE_TYPE_NOT_FOUND return -9; } // auto hdc = make_unique_ptr< HDC, &::DeleteDC >( ::GetDC( hWnd ) ); auto hdc = std::unique_ptr< std::remove_pointer< HDC >::type, DcDeleter >( ::GetDC( hWnd ) ); if( !hdc ) { auto const ec = GetLastError(); return -9; } auto memBM = std::unique_ptr< std::remove_pointer< HBITMAP >::type, ObjDeleter >( ::CreateBitmap( 320, 200, 1, 8, nullptr ) ); if( !memBM ) { auto const ec = GetLastError(); return -9; } /*auto hGdiObj =*/ SelectObject( hdc.get(), memBM.get() ); PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd 1, // version number PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL , // support OpenGL PFD_TYPE_COLORINDEX, // Specifies the type of pixel data e{PFD_TYPE_RGBA, PFD_TYPE_COLORINDEX} 8, // ..-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accum bits ignored 32, // 32-bit z-buffer 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer (ignored!) 0, // reserved 0, 0, 0 // layer masks ignored }; // get the best available match of pixel format for the device context auto iPixelFormat = ChoosePixelFormat( hdc.get(), &pfd); // make that the pixel format of the device context auto rc = SetPixelFormat( hdc.get(), iPixelFormat, &pfd ); if( !rc ) { auto ec = GetLastError(); // <=========== ec == 1004 := ERROR_INVALID_FLAGS return -9; } auto hGlRc = std::unique_ptr< std::remove_pointer< HGLRC >::type, ContextDeleter >( wglCreateContext( hdc.get() ) ); // make it the calling thread's current rendering context rc = wglMakeCurrent( hdc.get(), hGlRc.get() ); if( !rc ) { auto ec = GetLastError(); return -9; } ////////// --- OpenGL // make the rendering context not current wglMakeCurrent( NULL, NULL ); return 0; }
Gruß
Werner
-
Hallo dot,
dot schrieb:
Werner Salomon schrieb:
und was macht eigentlich dieses komische SelectObject?
Das setzt rein prinzipiell ein gegebenes Objekt in den jeweiligen DC ein. Aber diesen ganzen CreateCompatibleDC() und SelectObject() Gedöns kannst du vergessen, weil das funktioniert mit OpenGL sowieso nicht so.
das mit CreateCompatibleDC hat sich schon erledigt (s.o.). Bei dem anderen Kram fehlt mir völlig die Metaebene. Ich bin auch nicht der Windows-Programmierer, wenn es um Fenster geht. Wir benutzen Qt und das machen auch noch meine Kollegen und nicht ich.
dot schrieb:
Was genau ist das eigentlich, das du da rendern willst? Der ganze OpenGL Kram ist leider übelst kaputt; wenn du eine ordentliche Lösung für Windows haben willst, würde ich persönlich zu Direct3D raten. Potentieller Vorteil von OpenGL ist, dass einige simple Dinge mit legacy OpenGL wesentlich weniger aufwändig umzusetzen sind weil die API in vielen Belangen mehr high-level ist – bzw. so tut als ob und in ernsthaften Anwendungen ist man dann darauf angewiesen, seinen Code so zu schreiben, dass der Driver mit seinen 100000000 Heuristiken aus dem Usage-Pattern erratet, was man eigentlich gemeint hat und dann das richtige tut, obwohl es nirgendwo explizit dasteht weil die API es einen nicht explizit hinschreiben lässt – während mit Direct3D und modernem OpenGL ein relativ hoher Grundaufwand betrieben werden muss, bis man selbst die einfachsten Dinge machen kann (wenn das mal steht is es dafür dann aber selbstverständlich wesentlich mächtiger). Nachteil ist, dass OpenGL ein riesiger Misthaufen ist...
Wir benutzen bereits OpenGl in unserer Applikation und mit dem 'reinen' OpenGl (wenn man denn endlich sein HDC hat) habe ich nur beste Erfahrungen gemacht. Zumal das ganze wirklich einfach zu codieren ist - finde ich jedenfalls.
Rendern will ich in diesem speziellen Fall im Grunde sehr wenig. Das sind z.Zt. nur eine Hanvoll einfarbige Flächen in 3D. Vielleicht kommen später noch BitMaps dazu - ist aber Zukunftsmusik.
Ok - Du rätst mir zu Direct3D. Ich hatte mir das vor Jahren schon mal angesehen, und bin durch die Beispiele nur abgeschreckt worden. Ich habe das Gefühl der Code wurde von Praktikanten entworfen, die ihre ersten Programmiererfahrungen machen. Jedwede Fehlerbehandlung war eine Katastrophe. Zudem wimmelte es in jedem der Beispiele von globalen Variablen. Wenn man mehr als ein Dreieck zeichnen wollte, so war schon nicht mehr klar, wo man die Daten für das zweite Dreieck unter bringt. Außer man erzeugt eine zweite globale Variable.
Kannst Du mir einen Link auf ein lauffähiges Minimalbeispiel Direct3D geben? Oder ein Minimalbeispiel hier einstellen.
Gruß
Werner
-
Werner Salomon schrieb:
Aber der Fehler bei SetPixelFormat bleibt bei ERROR_INVALID_FLAGS.
Auf den ersten Blick würd ich sagen dass das am PFD_DRAW_TO_BITMAP liegt. Du willst PFD_DRAW_TO_WINDOW...
Werner Salomon schrieb:
Rendern will ich in diesem speziellen Fall im Grunde sehr wenig. Das sind z.Zt. nur eine Hanvoll einfarbige Flächen in 3D. Vielleicht kommen später noch BitMaps dazu - ist aber Zukunftsmusik.
Nun, da sollte legacy OpenGL ausreichend sein. Wenn es dir nur um Ergebnisse geht, dann wirst du damit sicherlich schneller am Ziel sein. Was du dir imo allerdings auch überlegen solltest, ist, ob du nicht einfach eine Library verwenden willst. Ich könnte mir vorstellen, dass beispielsweise das hier geeignet sein könnte: https://libcinder.org/
Nur sehr bedingt sinnvoll, aber ich will auch mal die Möglichkeit ansprechen, die Flächen einfach selbst zu malen (i.e. in Software). Wenn es wirklich nur eine Handvoll einfacher Polygone ohne jegliches Shading sein soll und es keine komplexen Sichtbarkeitsangelegenheiten gibt (konvexe Formen, gegenseitige Verdeckung), könnte man beispielsweise auch einfach die Projektion der Polygone ausrechnen und mit herkömmlichem GDI malen. Das soll keine Empfehlung sein, aber je nach Natur der konkreten Anwendung könnte das auch eine valide Lösung sein (ein potentieller Vorteil dabei könnte auch die Möglichkeit das Ganze als Vektorgrafik zu exportieren sein)...wobei du mittlerweile wohl schon so weit bist, dass du besser einfach mit OpenGL weitermachst...
Werner Salomon schrieb:
Ok - Du rätst mir zu Direct3D. Ich hatte mir das vor Jahren schon mal angesehen, und bin durch die Beispiele nur abgeschreckt worden. Ich habe das Gefühl der Code wurde von Praktikanten entworfen, die ihre ersten Programmiererfahrungen machen. Jedwede Fehlerbehandlung war eine Katastrophe. Zudem wimmelte es in jedem der Beispiele von globalen Variablen. Wenn man mehr als ein Dreieck zeichnen wollte, so war schon nicht mehr klar, wo man die Daten für das zweite Dreieck unter bringt. Außer man erzeugt eine zweite globale Variable.
Die offiziellen Beispiele sind leider teilweise tatsächlich ziemlich grottig; sei versichert dass das nur am Beispielcode liegt (wer auch immer den verbrochen hat), die API an sich ist ziemlich solide und wesentlich sauberer als OpenGL es je sein wird. "Wesentlich" ist dabei sogar noch hoffnungslos untertrieben, du kannst dir gar nicht vorstellen wie fundamental kaputt das Design von OpenGL ist...
Der Fairness halber muss natürlich gesagt werden, dass das Design von OpenGL halt auch aus den 80ern stammt und ursprünglich nur für die Grafikworkstations eines ganz bestimmten Herstellers ausgelegt war, was mit modernen multicore PCs, Betriebssystemen und der Art und Weise, wie man heutzutage gerne darauf Grafikprogrammierung betreiben würde dementsprechend schlecht vereinbar ist...nevertheless würde ich argumentieren dass das Design selbst für 80er-Jahre-Standards teilweise außerst...merkwürdig...ist...
Werner Salomon schrieb:
Kannst Du mir einen Link auf ein lauffähiges Minimalbeispiel Direct3D geben? Oder ein Minimalbeispiel hier einstellen.
Ist schon zu lange her bei mir, über aktuelle Ressourcen zu dem Thema bin ich leider absolut nicht am Laufenden, ich verwend einfach nur die MSDN zum Nachschlagen. Wenn du wirklich willst kann ich schauen, ob ich irgendwo in meinem Bitbucket was rumliegen hab oder so. Wenn es dir rein nur um schnelle Ergebnisse geht, wirst du mit legacy OpenGL aber definitiv glücklicher sein, das will schon ehrlich gesagt sein...Direct3D (gleich wie "modernes" OpenGL) braucht doch vergleichsweise sehr viel Code bis man mal zu dem Punkt kommt wo man irgendwas machen kann, die Einstiegshürde ist da leider definitiv ziemlich hoch, das will ich nicht schönreden...
-
Hallo dot,
dot schrieb:
Werner Salomon schrieb:
Aber der Fehler bei SetPixelFormat bleibt bei ERROR_INVALID_FLAGS.
Auf den ersten Blick würd ich sagen dass das am PFD_DRAW_TO_BITMAP liegt. Du willst PFD_DRAW_TO_WINDOW...
Du bist mein Held des Tages
zu meiner Schande muss ich gestehen, dass ich den ganzen Code schon mal geschrieben hatte, mich aber nicht mehr erinnert habe. Das ist es eben, wenn einem die Metaebene fehlt und man das nur alle Jahre einmal macht.
Aber jetzt komme ich zumindest biswglMakeCurrent
ohne Fehler durch.Zum Thema OpenGl und Windows werde ich einen neuen Thread aufmachen.
Gruß
Werner
-
nice!
-
Ich bin auch nicht der Windows-Programmierer, wenn es um Fenster geht. Wir benutzen Qt und das machen auch noch meine Kollegen und nicht ich.
Dann bleib doch dabei
Wie schon gesagt, für OpenGL für den Context brauchst irgendwie ein Dummy Window handle. Leider muss das gültig (aka erzeugt) sein, muss aber nicht sichtbar sein.
Das geht mit Qt auch Wobei QT sauberer trennt zwischen GUI und Console handling. Da könnte Terminal App wirklich problematisch sein, weil da kein QOpengl angezogen bekommst.
Aber ne QT GUI Anwendung, die nur die Konsole bedient und ansonsten nen unsichtbares Mainwindow hat oder sich das Konsole window schnappt ... ich hab da schon schlimmeres gesehen.
Wirst halt dafür in der Abhängikeits-Hölle schmorenAuch "modernes" OpenGl (aktuell 4.5) geht super mit QT.
Schau dir mal QOpenGLFunctions_4_5_Core z.b. an.
Ich vermute aber, das wirst mit einer QCoreApplication nicht instantiert bekommen, da wirst eine QGuiApplication mit aktiver windows queue brauchen.
Wie sicher schon gelesen hasst, ist es kein Hexenwerk mehr, wenn erst mal nen funktionierenden Context erzeugt bekommen hasst. Dann kannst dich mit den FBO's austobenGetConsoleWindow liefert dir das Window Handle von der Console ... welches sich super eignet um nen unsichtbares Child-window zu erzeugen.
Und selbst das gänge mit Qt sehr komfortabel, schau dir QWindow::fromWinId(WId id) an.
Ciao ...
-
nach dem oben beschriebenen Weg bin ich zwar fehlerlos durch das Programm gekommen, konnte dann aber am Ende nicht die Pixeldaten lesen, die ich erwartet hätte. Darauf bin ich dann nochmal in mich gegangen, habe einiges nachgelesen, und habe dann mit diesen Erfahrungen mir meinen ersten Versuch mit dem Memory Device Context wieder angeschaut.
Und schlußendlich hat es dann auch geklappt. Wichtige Infos dabei waren:
GetDC(nullptr)
liefert den DC (Device Context) des Bildschirms. Und beiCreateCompatibleBitmap
darf nicht derHDC
ausCreateDC
sondern muss der ausGetDC
benutzt werden! Aus diesem Missverständnis heraus resultierte auch meine Frage nach dem SelectObject (s.o.).
Weiter war wichtig, dass man zwar beim Pixelformat eine 4-Byte-Farbtiefe angegeben muss (=32Bit), wenn man den DC des Screens verwendet, aber beim Auslesen mitglReadPixels
komme ich nur an die 3-Byte-Farbinformation (RGB) ran. Letzteres ist für mich aber auch völlig ausreichend.Hier mein lauffähiger Stand:
#include "Deleter.h" // make_unique_ptr<> #include <Windows.h> #include <gl/GL.h> #include <algorithm> // -> ReadImage #include <map> // -> ReadImage #include <vector> // -> ReadImage #include <iostream> // std:::cerr u.a. #include <string> #include <system_error> void glCheck() { auto const err = glGetError(); if( err != GL_NO_ERROR ) { assert(( "glGetError() != GL_NO_ERROR", 0)); throw std::runtime_error( "glGetError=" + std::to_string(err) ); } } void display() { // simple sample glClear(GL_COLOR_BUFFER_BIT); glCheck(); glBegin(GL_TRIANGLES); glColor3f( 1.0f, 0.5f, 0.2f ); glVertex2i(0, 1); glColor3f( .8f, 0.0f, 0.0f ); glVertex2i(-1, -1); glColor3f( 1.0f, 0.0f, 0.0f ); glVertex2i(1, -1); glEnd(); glCheck(); glFlush(); glCheck(); } void ReadImage( int WIDTH, int HEIGHT ) { auto const bytesPerPixel = 3; // RGB sind 3 Byte std::vector< unsigned char > pixels( WIDTH * HEIGHT * bytesPerPixel ); GLenum format = GL_RGB; // oder GL_COLOR_INDEX?; glReadPixels( 0, 0, WIDTH, HEIGHT, format, GL_UNSIGNED_BYTE, &pixels[0] ); glCheck(); std::map< unsigned char, char > chars; const char* CHARS = ".+*#O/!abcdefghijklm"; // Zeichen, die die verschiedene Farben darstellen auto pCh = CHARS; for( auto line=0; line < HEIGHT; ++line, std::cout << std::endl ) { auto px = &pixels[0] + 0 + line * WIDTH * bytesPerPixel; // 0:=rot; 1:=grün; 2:=blau for( auto column = 0; column < std::min(78, WIDTH); ++column, px += bytesPerPixel ) { auto c = chars.find( *px ); if( c != end(chars) ) std::cout << c->second; else { std::cout << *pCh; chars.emplace( *px, *pCh++ ); if( pCh >= CHARS + 20 ) pCh = CHARS; } } } } template< typename P > void setPixelFormat( const P& hdc, BYTE type, DWORD flags, BYTE cColorBits ) { using namespace std; PIXELFORMATDESCRIPTOR pfd; pfd = {0}; pfd.nSize = sizeof(pfd); pfd.nVersion = 1; pfd.iPixelType = type; pfd.dwFlags = flags; pfd.cColorBits = cColorBits; auto const pf = ::ChoosePixelFormat( hdc.get(), &pfd ); if( pf == 0 ) { auto const ec = ::GetLastError(); throw std::system_error( ec, std::system_category(), "ChoosePixelFormat failed" ); } if( ::SetPixelFormat( hdc.get(), pf, &pfd ) == FALSE ) { auto const ec = ::GetLastError(); throw std::system_error( ec, std::system_category(), "SetPixelFormat failed" ); } } class GlContext { public: template< typename P > explicit GlContext( const P& hDc ) : m_hDc( hDc.get() ) , m_hGlRc( ::wglCreateContext( m_hDc ) ) { if( !m_hGlRc ) { auto const ec = ::GetLastError(); throw std::system_error( ec, std::system_category(), "wglCreateContext failed" ); } if( !::wglMakeCurrent( m_hDc, m_hGlRc ) ) { auto const ec = ::GetLastError(); throw std::system_error( ec, std::system_category(), "wglMakeCurrent failed" ); } } ~GlContext() { ::wglMakeCurrent( nullptr, nullptr ); ::wglDeleteContext( m_hGlRc ); } GlContext( const GlContext& ) = delete; GlContext& operator=( const GlContext& ) = delete; private: HDC m_hDc; HGLRC m_hGlRc; }; int do_main() { using namespace std; auto const WIDTH = 20; auto const HEIGHT = 20; auto hdcSreen = GetDC( nullptr ); // If this parameter is nullptr, GetDC retrieves the DC for the entire screen. { auto hdcMem = make_unique_ptr< HDC, ::DeleteDC >( ::CreateCompatibleDC( hdcSreen ) ); auto hBM = make_unique_ptr< HGDIOBJ, ::DeleteObject >( ::CreateCompatibleBitmap( hdcSreen, WIDTH, HEIGHT ) ); // hier NICHT den HDC 'hdcMem' mitgeben! ::SelectObject( hdcMem.get(), hBM.get() ); setPixelFormat( hdcMem, PFD_TYPE_RGBA, PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL, 32 ); // ColorBits MUSS ==32 sein GlContext glContext( hdcMem ); // wglCreateContext und wglMakeCurrent display(); // OpenGL calls ReadImage( WIDTH, HEIGHT ); // .. pixel access with glReadPixels } return 0; } int main() { try { return do_main(); } catch( const std::exception& ex ) { std::cerr << "Exception: " << ex.what() << std::endl; } }
und der Vollständigkeit halber auch noch der Inhalt von "Deleter.h" - hat aber nichts mit dem eigentlichen Problem zu tun:
#pragma once #include <Windows.h> #include <memory> #include <system_error> #include <type_traits> template< typename Hnd, BOOL( __stdcall *DelType )( Hnd ) > struct Deleter { void operator()( Hnd hnd ) const { if( hnd ) DelType( hnd ); } }; template< typename Hnd, BOOL( __stdcall *DelType )( Hnd ) > std::unique_ptr< typename std::remove_pointer< Hnd >::type, Deleter< Hnd, DelType > > make_unique_ptr( Hnd hnd ) { if( !hnd ) { auto ec = ::GetLastError(); throw std::system_error( ec, std::system_category(), "kreieren eines Objekts schlug fehl" ); } return std::unique_ptr< typename std::remove_pointer< Hnd >::type, Deleter< Hnd, DelType > >( hnd ); }
Gruß
Werner