Flimmern



  • Hi

    Ich versuche mithilfe der MFC ein Spiel zu programmieren.
    Allerdings muss ich dazu das Bild relativ häufig neuzeichnen
    (bzw. teile des Bildes)
    Das Flimmert sehr vermutlich weil nicht schnell genug gezeichnet
    wurde.
    Ich verwende zum zeichnen pDC->StretchBlt(..)
    Weiss wer ne Methode damit das Flimmern aufhöhrt?

    by



  • Wenn du nicht gerade ein Action-Spiel codes oder eines das flüssige animationen benötigst, reicht auch die GDI. Obwohl für Spiele eigentlich eher andere APIs geeignet sind (OpenGL kann auch 2D, DirectDraw und die sehr einfach zu erlernende SDL).

    Dein Flimmern hat aber zwei Gründe, die sich lösen lassen:

    1. Benutze einen Backbuffer (also einen zweiten Grafikbereich, der nicht angezeigt wird) und wenn dieser fertig gezeichnet ist, blittest du ihn.

    2. Bei einer Update-Message wird das Fenster gelöscht und dann erst der Buffer gezeichnet (schlimmeres Ergebnis als Punkt 1). D.h. du mußt die erase-Eigenschaft des Fensters disablen. (hab gerade keine MSDN zur Hand, mußte mal selbst schauen)

    Der 2. Punkt muß auf jeden Fall befolgt werden, dann wirst du eine besserung feststellen. Und Punkt 1 ist das Sahnehäunschen. 😉



  • Hi

    Also 2 hab ich gemacht das war nicht das problem flimmert jetzt aber immer noch ein bisschen.
    Bei 2 hab ich probleme ich erstelle ein CDC mit CreateCombatibleCD()
    Und blitte dort hinein.
    Wenn ich dann allerdings auf den Bildschirm (CPaintDC) blitten will passiert nichts alles bleibt grau.
    Was mach ich da falsch?

    by



  • Beim erstellen des compatiblen DC hast du bestimmt vergessen eine leere Bitmap mit rein zu zeichnen. Wenn man den CompatibleDC generiert hat er immer die Größe 0x0 Pixel, da hilft die Größenangabe nichts (da muß noch ne Bitmap mit der gewünschten Größe). Ich müsste heute abend nochmal in mein altes Projekt schauen, da hab ich es auch drin und den Fehler auch gemacht.



  • Als ich mal das gleiche Problem hatte, hat mir ein freundlicher Mensch diesen Code zugeschickt (MemDC.h):

    #ifndef _MEMDC_H_INCLUDED_
    #define _MEMDC_H_INCLUDED_
    
    #include "stdafx.h"
    
    // CMemDC stellt einen Gerätekontext im Speicher dar
    
    class CMemDC : public CDC
    {
    // Variablen
    private:
        CBitmap     m_bmp;          // Speicherbitmap
        CBitmap*    m_pOldBmp;      // Zeiger auf altes Bitmap
        CDC*        m_pDC;          // Vertretener Gerätekontext
        CRect       m_rcClip;       // Ungültiger Bereich
    
    // Konstruktoren/Destruktoren
    public:
        CMemDC( CDC* pDC );
        ~CMemDC( );
    };
    
    inline CMemDC::CMemDC( CDC* pDC ) : CDC( )
    {
        // Übergebenen Kontext absichern
        ASSERT( pDC != NULL );
        ASSERT( ( pDC -> GetDeviceCaps( RASTERCAPS ) ) & RC_BITBLT );
        ASSERT( !pDC -> IsPrinting( ) );
        m_pDC = pDC;    
    
        // Speichergerätekontext erzeugen
        CreateCompatibleDC( pDC );
    
        // Ungültigen Bereich ermitteln
        pDC -> GetClipBox( &m_rcClip );
        pDC -> LPtoDP( &m_rcClip );
    
        // Speicherbitmap erzeugen
        m_bmp.CreateCompatibleBitmap( pDC, m_rcClip.Width( ),
            m_rcClip.Height( ) );
        m_pOldBmp = SelectObject( &m_bmp );
    
        // Mapmode übernehmen 
        SetMapMode( pDC -> GetMapMode( ) );
        pDC -> DPtoLP( &m_rcClip );
    
        // Fensterausrichtung neu setzen
        SetWindowOrg( m_rcClip.left, m_rcClip.top );
    
        // Hintergrund weiss ausfüllen
        PatBlt( m_rcClip.left, m_rcClip.top, m_rcClip.Width( ),
            m_rcClip.Height( ), WHITENESS );
    }
    
    // Destruktor
    inline CMemDC::~CMemDC() 
    {   
        // Bits transferieren
        m_pDC -> BitBlt( m_rcClip.left, m_rcClip.top,
            m_rcClip.Width( ), m_rcClip.Height( ),
            this, m_rcClip.left, m_rcClip.top, SRCCOPY );
    
        // Altes Bitmap auswählen
        if( m_pOldBmp != NULL )
            SelectObject( m_pOldBmp );
    }
    
    #endif // _MEMDC_H_INCLUDED_
    

    Jetzt musst Du nur noch ein include von MemDc.h machen und in der zeichnenden Routine CMemDC dc(pDC); deklarieren.
    Wenn Du jetzt alle Funktionen zum Zeichnen / Grafik darstellen mit dc statt mit pDC hat es sich ausgeflackert 🙂

    z.B.:

    dc.Rectangle (&MyRect.m_rect);
    dc.SetTextColor (RGB(75,75,75));
    

    Ich hoffe das hilft Dir

    Bernd



  • Ich habe ein ähnliches Problem. Ich habe 1) schon befolgt, aber wie genau funktioniert Punkt 2?

    BTW: Warum bezeichnet man eigentlich die Verwendung von BitBlt als "blitten"?

    MfG, phreaking

    [ Dieser Beitrag wurde am 03.11.2002 um 17:22 Uhr von phreaking editiert. ]



  • Also auch wenn das eigentlich so jetzt nicht hierhergehört: Ich würde mir die Mühe machen das ganze einfach mit OpenGL umzusetzen. Im Endeffekt gehst du damit vielen Problemen der GDI einfach aus dem Weg. Z.B. das Umrechnen von irgendwelchen realen Koordinaten in Screenkoordinaten.
    Der Ansatz mit Doublebuffering in GDI ist trotzdem ganz interessant.

    Beim erstellen des compatiblen DC hast du bestimmt vergessen eine leere Bitmap mit rein zu zeichnen. Wenn man den CompatibleDC generiert hat er immer die Größe 0x0 Pixel, da hilft die Größenangabe nichts (da muß noch ne Bitmap mit der gewünschten Größe).

    Jetzt weiß ich endlich, was ich da immer falsch gemacht habe :). Danke



  • Kann mir keiner sagen, wie ich den 2. Punkt umsetzte?



  • Um das löschen des Fensters zu verhindern, wenn man ein Update macht, muß man beim Aufruf der CWnd::RedrawWindow() Methode folgende Flags übergeben:

    RDW_INVALIDATE | RDW_UPDATENOW
    

    z.B. in der CDialog-Klasse:

    this->RedrawWindow(NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW);
    

    Weitere Infos gibts in der MSDN.

    Ich hab nochmal in ein älteres Projekt von mir rein geschaut, dort hab nämlich in das ganze Fenster gezeichnet und auch einen Backbuffer benutzt und flimmert auch nicht.

    Erstmal einen globalen Backbuffer (kann man auch in eine Klasse kapseln) angelegt:

    CDC *backbuffer = NULL;
    

    Einmalig wird dann der Backbuffer initialisiert, mit einem Dummy-Bitmap das die gewünschte Größe hat (kann man im Resource-Editor erstellen):

    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP1);
    BITMAP bm;
    bitmap.GetObject(sizeof(bm), &bm);
    
    CDC *dc = new CClientDC(cDialog);
    backbuffer = new CDC();
    backbuffer->CreateCompatibleDC(dc);
    backbuffer->SelectObject(&bitmap);
    

    In diesen backbuffer blittet man dann immer rein und wenn man fertig ist, kopiert man den Backbuffer in das Fenster:

    CDC *dc = cDialog->GetWindowDC();
    dc->BitBlt(0, 0, 640, 480, backbuffer, 0, 0, SRCCOPY);
    


  • Blitten ist eigentlich auf dem PC falsch. Damals gab es auf dem Amiga, dem Sega Mega Drive und später dem Atari STE (der ST hatte noch keinen) einen Chip, der Speicherbereiche kopieren konnte, ohne Performanceverlust natürlich. Dieser Chip hieß Blitter. Vorzugsweise wurde dieser Blitter dafür benutzt Bitmaps auf den Screen bzw. Backbuffer zu kopieren. Die CPUs hätten das damals in dem Tempo nicht geschafft.

    Der Blitter ist Teil des Agnus-Grafikchips vom Amiga und eine Abkürzung für "Block-Image-Transferer", was soviel heißt wie Grafikblockverschieber. Der Name "Blitter" hat schon eine recht lange Tradition und beruht auf der Prozedur "BitBlt" (bit block transfer), die es auf einem älteren Computer der Firma XEROX gab. Diese Prozedur war für das schnelle Verschieben "rechteckiger" Bereiche in einer Bitmap zuständig. Und da die für den Amiga entwickelte Hardware-Einheit innerhalb von Agnus genau dasselbe tut, lag es nahe, diesen Namen zu verwenden. Jay Miner, der Designer der drei Amiga-Chips, ist in dieser Hinsicht anderer Meinung. Der Blitter des Amiga kann auch wirklich einiges mehr, als nur Daten hin- und herzuschieben. Jay nennt ihn deshalb Bimmer für Bit Image Manipulator.

    Aus historischen Gründen sagt heute jeder Blitten, auch wenn niemand wirklich weiß, wer die Grafiken kopiert (macht unter Windows die CPU, bei OpenGL und DirectGraphics wird der 3D-Chip benutzt, und die haben keinen Blitter).



  • @TheBigW
    Alles mit OpenGL machen??? Warum einfach wenn es auch kompliziert geht. In OpenGL braucht man im 2D-Bereich um einiges mehr Code als wenn man einfach einen MemDC verwendet... Bei komplexen Spielen im 3D-Bereich ist OpenGL sicher ratsam aber für 2D-Spiele würde ich in jedem Fall DirectDraw etc. verwenden. Naja, weniger Code als in OpenGL ist DirectDraw auch nicht. Schon gut, ich hör ja schon auf... Muss jeder selbst entscheiden. Für Brett- oder Kartenspiele die GDI, für Jump&Run Direct Draw und für EgoShooter OpenGL...würde ich sagen. Hab mit OpenGL noch nicht soviel Erfahrung im Spiele-Bereich...noch nicht 😃
    Boah, ich bin ja am senden... SCHRECKLICH 🙄



  • OPenGL ist einfacher als die GDI, einfacher als DirectX, einfacher als alles andere! 😃 Nein, im ernst. OpenGL ist wirklich einfach, da es sehr logisch und sehr "lesbar" ist. Und im 2D-Bereich braucht man nicht mehr Code als für DX oder GDI. Mit ein paar Funkrtionen schaltet man in den 2D-Modus bzw. man schaltet die Perspektive aus. Und schon kann man Vierecke mit Texturen zeichnen, ganz easy. Man kann sogar Layer benutzen, da es weiterhin den Z-Buffer gibt, jedoch ohne das die Perspektive in Kraft tritt. Soll heißen, was man in der Z-Kooridnate 10.0 zeichnet, ist genauso groß wie auf Z-Koordinate 0.0.

    Ich code auch ein Jump&Run komplett in MESA (freier OpenGL-Clone), funzt wunderbar. 🙂



  • CDC *backbuffer = NULL;

    Einmalig wird dann der Backbuffer initialisiert, mit einem Dummy-Bitmap das die gewünschte Größe hat (kann man im Resource-Editor erstellen):
    C/C++ Code:
    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP1);
    BITMAP bm;
    bitmap.GetObject(sizeof(bm), &bm);

    CDC *dc = new CClientDC(cDialog);
    backbuffer = new CDC();
    backbuffer->CreateCompatibleDC(dc);
    backbuffer->SelectObject(&bitmap);
    C/C++ Code:
    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP1);
    BITMAP bm;
    bitmap.GetObject(sizeof(bm), &bm);

    CDC *dc = new CClientDC(cDialog);
    backbuffer = new CDC();
    backbuffer->CreateCompatibleDC(dc);
    backbuffer->SelectObject(&bitmap);
    C/C++ Code:
    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP1);
    BITMAP bm;
    bitmap.GetObject(sizeof(bm), &bm);

    CDC *dc = new CClientDC(cDialog); 
    backbuffer = new CDC(); 
    backbuffer->CreateCompatibleDC(dc); 
    backbuffer->SelectObject(&bitmap);  
    
    In diesen backbuffer blittet man dann immer rein und wenn man fertig ist, kopiert man den Backbuffer in das Fenster: 
    C/C++ Code: 
    CDC *dc = cDialog->GetWindowDC(); 
    dc->BitBlt(0, 0, 640, 480, backbuffer, 0, 0, SRCCOPY);  
    C/C++ Code: 
    CDC *dc = cDialog->GetWindowDC(); 
    dc->BitBlt(0, 0, 640, 480, backbuffer, 0, 0, SRCCOPY);  
    C/C++ Code: 
    CDC *dc = cDialog->GetWindowDC(); 
    dc->BitBlt(0, 0, 640, 480, backbuffer, 0, 0, SRCCOPY);
    

    Was ist hier mit cDialog gemeint? Help danke



  • Ist eine Variable vom Typ CDialog, würde ich mal behaupten.



  • und wie erstellt man diese?



  • Mit dieser Zeile :

    cDialog->GetWindowDC();
    

    Holst du den Device Context des Windows cDialog, welcher nichts anderes ist als z.B. dein MainWindow oder ein SubWindow....

    Cheers



  • Falls es noch nicht klar ist.
    Bsp:

    Du hast eine Klasse A (CA) und eine Klasse B(CB);
    Du erstellst in Klasse B ein Objekt der Klasse A mit

    CA classA;    //classA ist nun das Objekt, genau wie cDialog!!!
    

    Nun kannst du wie oben auch dir das DC holen. 😉


Anmelden zum Antworten