Grafika hibakezelés és tipikus hibák

A VIK Wikiből
A lap korábbi változatát látod, amilyen Rohamcsiga (vitalap | szerkesztései) 2014. január 13., 20:51-kor történt szerkesztése után volt. (Hozzáadtam a "Nálam működik, a beadón viszont nem hibák" részt.)
Ugrás a navigációhoz Ugrás a kereséshez

FAQ (avagy a grafika listán a legtöbb levelet generáló "misztikus" hibák)

  • Fordításkor ilyen hibát kapok: error: stray '\357' in program... - UTF-8 BOM hiba
  • Az osztást tartalmazó számításom rossz eredményt ad - Egészosztási hiba
  • A 3D-s kép zajos, az élek cikk-cakkosak - Z-Fighting
  • Sugárkövetéskor a testen fura fekete részek jelennek meg - Önárnyékolás
  • Sugárkövetéskor az egész képem fekete/fehér - Tone Map használata
  • glQuad használatakor négyszög helyett két egymásba csúszott háromszög jelenik meg - QUAD

Tipikus C++ hibák

  • Ha stabil C++ tudással rendelkezel, ezt a részt nyugodtan ugord át
  • UTF8 BOM problémák:
    • Ha a kódodat UTF-8 kódolással mented, sok szerkesztő a fájl elejére biggyeszt három nem látható karaktert, az úgy nevezett UTF-8 Byte Order Mark-ot. Ez a Gordiuson fordítási hibát fog eredményezni, ezért a kódunkat feltöltés előtt mindenképpen konvertáljuk sima ASCII/ANSI szövegfájllá (Még a Jegyzettömb is képes erre, Mentés másként - ANSI)
  • Lokális változók scope-on kívüli használata:
    • Figyeljünk rá hogy a lokális változók csak addig léteznek, amíg a scope-on belül járunk
    • Ennek megfelelően ne adjuk át őket cím szerint, ha mégis referenciát akarunk készíteni ilyen változóról, használjunk const referenciát!
  • Egészosztás véletlen használata:
    • A C++-ban az operandusok típusától függ, hogy egészosztás (div) vagy rendes osztás történik
    • Ez a működés nehezen detektálható hibákat okozhat, ezért mindig figyeljünk rá! Például ha egész típusú változót osztunk egy számmal, az eredmény egész lesz, hiába akarjuk egy lebegőpontos változóban tárolni:
int a=5;
float b=a/10; //értéke nem 0.5 lesz, hanem 0!
  • A hiba elkerülése végett használjunk cast-olást vagy típusos konstansokat:
float b=(float)a/10;
float b=a/10.0f;
  • Hiányzó break a switch szerkezetben
    • A C++-ban a switch szerkezetének egyszerre több ága is lefuthat! Ha ezt nem szeretnénk, figyeljünk rá hogy minden ágat zárjunk le a break; paranccsal!
  • T&-át váró függvénynek nem lehet egy 'T' temporális változót odadni, de a VisualC++ fordító nem szól emiatt. Pl:
class Foo {
  Foo(Bar&) {}
};

Foo(Bar()) <- Ez nem szabályos, a beadón fordítási hibát ad!! A megoldás: a függvény várjon const Bar&-et, vagy Bar b; Foo(b);

OpenGL hibák

  • Az OpenGL sajnos nem túl típusos, így ha egy függvényt rossz paraméterekkel hívunk meg, akkor az általában nem generál fordítási hibát, hanem szimplán csak nem működik.
    • Az ilyen hibák megtalálása nagyon nehéz, mert csak annyit látsz, a program nem működik, de nem szól, hogy mivel van gondja.
    • Szerencsére az OpenGL készítői gondoltak erre, és az ilyen hibák beállítanak egy flaget amit le lehet kérdezni. Ezt a glGetError() függvénnyel lehet megtenni. A függvény ömagában csak egy számot ad vissza, amit nehéz értelmezni, de itt van egy wrapper, ami emberileg is fogyaszható formába írja ki, hogy mi történt. Ezt persze a beadón nem lehet használni, de az otthoni változatba debuggoláshoz sokat segíthet.
#include <iostream>
#include <cstring>

#define CheckError() __CheckError(__FILE__, __PRETTY_FUNCTION__, __LINE__)

inline void __CheckError(const char *file, const char *func, int line) {
  GLenum error = glGetError();
  if(error != GL_NO_ERROR) {

    std::cout << "\n---------========={[ ";

    int err_len;

    switch(error) {
      case GL_INVALID_ENUM:
        std::cout << "GL_INVALID_ENUM";
        err_len = strlen("GL_INVALID_ENUM");
        break;
      case GL_INVALID_VALUE:
        std::cout << "GL_INVALID_VALUE";
        err_len = strlen("GL_INVALID_VALUE");
        break;
      case GL_INVALID_OPERATION:
        std::cout << "GL_INVALID_OPERATION";
        err_len = strlen("GL_INVALID_OPERATION");
        break;
      case GL_STACK_OVERFLOW:
        std::cout << "GL_STACK_OVERFLOW";
        err_len = strlen("GL_STACK_OVERFLOW");
        break;
      case GL_STACK_UNDERFLOW:
        std::cout << "GL_STACK_UNDERFLOW";
        err_len = strlen("GL_STACK_UNDERFLOW");
        break;
      case GL_OUT_OF_MEMORY:
        std::cout << "GL_OUT_OF_MEMORY";
        err_len = strlen("GL_OUT_OF_MEMORY");
        break;
      case GL_INVALID_FRAMEBUFFER_OPERATION:
        std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION";
        err_len = strlen("GL_INVALID_FRAMEBUFFER_OPERATION");
        break;
    };

    std::cout << " ]}=========---------\n" << std::endl;

    std::cout << "A '" << func << "' függvenyben" << std::endl;
    std::cout << "A '" << file << "' fajl " << line << ". soraban\n" << std::endl;
    
    int ending_line_length = err_len + 42;
    for(int i = 0; i < ending_line_length; i++) {
      std::cout << "=";
    }
    std::cout << std::endl;
  }
}
  • A használata:
    • A kódba ha leírod, hogy CheckError(); akkor az kiirja, hogy addig volt-e hiba.
    • Példa:
1    void SetProjectionMatrix() {
2      CheckError();
3      glMatrixMode(GL_PROJECTION);
4      glLoadIdentity();
5      gluPerspective(60, 1, 1, 50);
6      glMatrixMode(GL_MODELVIEW_MATRIX);
7      CheckError();
8    }
  • A futtatás eredménye:
---------========={[ GL_INVALID_ENUM ]}=========---------

A 'void SetProjectionMatrix()' függvenyben
A 'main.cpp' fajl 7. soraban

=========================================================
  • Ebből megtudod, hogy a hiba a 2. sorban még nem lépett fel, de a 7. már igen. Tehát a 4 OpenGL függvényhívás egyike a rossz, és az a gondja, hogy rossz enumot használsz (GL_INVALID_ENUM). A 4 függvényből csak kettőben van enum, a két glMatrixMode() hívásban. A kettő közül a 6. sor a rossz, az helyesen glMatrixMode(GL_MODELVIEW) lenne, mert a GL_MODELVIEW és a GL_MODELVIEW_MATRIX enumok nem ugyan arra valók. Ezt a hibát ennek a trükknek a használata nélkül nagyon nehéz lenne megtalálni, hiszen a kód jónak látszik, viszont a képernyőn nem látszódna semmi, vagy ha látszódna is, akkor annak semmi köze nem lenne ahhoz, amit rajzolni akartál. Egy ilyen hiba megtalálása még a legrutinosabbaknak is valószínűleg több órába telne. Ez az egyszerű függvény viszont szinte pontosan megmondta, hogy hol és mi a hiba.

Nálam működik, a beadón viszont nem hibák

  • Szinte biztos, hogy NEM a beadó rossz, csak a kódodba van valamilyen rejtett hiba, ami nem definiált, implementáció függő viselkedést okoz, ami a te gépeden véletlenül pont azt csinálja, amit akartál, hogy csináljon, de egy másik gépen - mint pl. a beadón - már nem.
  • Ilyen hibát c++-ba is tudsz véteni, pl : pow() negatív számokra:
    pow(-2, 2) == 4; // A kitevő egész, ez ok
De: pow(-2, 2.1f) = UNDEFINED // A kitevő valós, nem ok. A (-2)^2.1 egy komplex szám.
  • Az utóbbi kódra előfordulhat, hogy a pow függvény nálad a komplex eredmény valós részét adja vissza, a beadón viszont NAN-t, mondva, hogy komplex számot nem tud valóssá konvertálni helyesen.
  • Az OpenGL-be rengeteg ilyen van. És sajnos nincs ezek ellen nincs más ellenszer, mint hogy az általad használt függvények [dokumentációját] elolvasod, ahol leírják, hogy milyen esetben okoz implementáció függő viselkedést. Szerencsére az esetek több mint 90%-ába ilyenkor a függvény generál egy hibát, amit az előbb bemutatott CheckError()-al el lehet kapni, de nem mindig.