Grafika hibakezelés és tipikus hibák

A VIK Wikiből

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 (nyugodtan formázd át a hibaüzenetet, olyanra, hogy neked tetszen).


#include <iostream>
 
#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 = sizeof("GL_INVALID_ENUM") - 1;
        break;
      case GL_INVALID_VALUE:
        std::cout << "GL_INVALID_VALUE";
        err_len = sizeof("GL_INVALID_VALUE") - 1;
        break;
      case GL_INVALID_OPERATION:
        std::cout << "GL_INVALID_OPERATION";
        err_len = sizeof("GL_INVALID_OPERATION") - 1;
        break;
      case GL_STACK_OVERFLOW:
        std::cout << "GL_STACK_OVERFLOW";
        err_len = sizeof("GL_STACK_OVERFLOW") - 1;
        break;
      case GL_STACK_UNDERFLOW:
        std::cout << "GL_STACK_UNDERFLOW";
        err_len = sizeof("GL_STACK_UNDERFLOW") - 1;
        break;
      case GL_OUT_OF_MEMORY:
        std::cout << "GL_OUT_OF_MEMORY";
        err_len = sizeof("GL_OUT_OF_MEMORY") - 1;
        break;
      case GL_INVALID_FRAMEBUFFER_OPERATION:
        std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION";
        err_len = sizeof("GL_INVALID_FRAMEBUFFER_OPERATION") - 1;
        break;
    };
 
    std::cout << " ]}=========---------\n" << std::endl;
 
    std::cout << "A '" << func << "' fuggvenyben" << 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()' fuggvenyben
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
    pow(-2, 2.1f) = UNDEFINED // A kitevő valós, nem ok. A (-2)^2.1 egy komplex szám (4,0773 + 1,3248i).


  • 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 más ellenszer, mint hogy az általad használt függvények dokumentációját elolvasod, ahol leírják, hogy az 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.