Jos malo OpenGL-a i DirectX-a

1

Jos malo OpenGL-a i DirectX-a

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Napisano: 05 Avg 2010 22:59

Ovih dana sam pomagao jednom prijatelju oko pisanja male igrice koju je pravio na osnovu tutorijala za DirectX iz ove teme. Nazalost, taj tutorijal i ceo framework nisu bas najlaksi za koriscenje. Zato mi pade na pamet da napisem nov tutorijal koji ce mozda biti malo komplikovaniji, ali ce koriscenje koda koji budemo napisali biti mnogo lakse. U sustini, ako sve bude kako treba, konacan proizvod tutorijala bi trebalo da bude mali 3D engine koji ce biti relativno jednostavan za koriscenje, pa ce moci da ga koriste i oni koji nisu pratili sve od pocetka. Jos jedna stvar... za razliku od tutorijala za DirectX, u ovom novom cemo napraviti klase koje ce znati da rade i sa DirectX-om i sa OpenGL-om, a znace ceo engine ce moci da radi na Windows-u i na Linux-u.

Kodove cu pisati u Delphi-ju 2010 i u Lazarus-u 0.9.28, ali ce biti tako napisani da ce raditi i na starijim verzijama (starije verzije Delphi-ja nece moci da otvore projekat koji je kreiran u Delphi-ju 2010, ali ce i dalje moci da iskompajliraju kod, dok sa Lazarus-om ne bi trebalo da bude nikakvih problema).

Razvojna okruzenja mozete skinuti sa ovih sajtova:
Probna verzija Delphi-ja (30 dana): https://downloads.embarcadero.com/free/delphi
Lazarus: http://sourceforge.net/projects/lazarus/files/

Projekat cemo nazvati Simple Graphics Engine, ili skraceno SGE. Pre nego sto pocnemo, trebalo bi da napisemo malo koda koji ce nam omoguciti da prilikom kompajliranja znamo da li smo na Windows-u ili Linux-u. Include fajlovi su ko stvoreni za to Smile Njih mozemo da ukljucimo na pocetku svakog fajla i uz pomoc njih mozemo znati na kom operativnom sistemu se program kompajlira, koje specijalne funkcije smo ukljucili, da li hocemo da napravimo konacan program za korisnika ili program koji cemo da testiramo, i slicno...

Za pocetka ce nam trebati informacija o operativnom sistemu i neke osnovne opcije, pa ce nas include fajl izgledati ovako:

SGE.inc
{$IFDEF MSWINDOWS}   {$DEFINE SGE_Windows}   {$DEFINE SGE_Supported} {$ENDIF} {$IFDEF LINUX}   {$DEFINE SGE_Linux}   {$DEFINE SGE_Supported} {$ENDIF} {$IFNDEF SGE_Supported}   {$MESSAGE FATAL 'SGE can be compiled only on Windows and Linux!'} {$ENDIF} {$IFDEF FPC}   {$DEFINE SGE_FPC}   {$MODE DELPHI} {$ENDIF} {$H+} {$MINENUMSIZE 4}

Kratko objasnjenje za one koji ne razumeju sta se desava u ovom fajlu. Proveravamo da li je kompajler definisao vrednost MSWINDOWS. Ta vrednost je definisana samo kada se program kompajlira na Windows-u. Ako je vrednost definisana, postavljamo nase vrednosti SGE_Windows i SGE_Supported (postavljamo nasu vrednost za detekciju operativnog sistema da bi posle lako na jednom mestu popravili kod za detekciju ako bude bilo potrebe). Isto radimo i za detekciju Linux-a. Ako smo detektovali Windows ili Linuk, postavili smo vrednost SGE_Supported, zato proveravamo da li ona postoji i ako je nema ispisujemo poruku o gresci. Na kraju u slucaju koriscenja Lazarus-a (FPC) postavljamo neke osnovne opcije da bi se ponasao kao Delphi, i time smo u mogucnosti da pisemo jedan kod koji ce raditi i u Delphi-ju i u Lazarus-u.

Sledece na redu je pokazivanje kako se prozor kreira u Windows-u, a kako u Linux-u. To cu napisati taman dok svi zainteresovani poskidaju Delphi ili Lazarus Smile

Dopuna: 06 Avg 2010 10:08

Da pocnemo s kreiranjem projekta… moramo imati na umu da kod koji pisemo mora raditi i na Windows-u i na Linux-u. Posto Windows i Linux imaju drugacije biblioteke, moramo pisati kod koji ce znati kad koje biblioteke da koristi. Za sad cemo napraviti jednostavan program koji bukvalno nista nece raditi, ali cete iz njega videti kako je moguce napisati kod koji kompajleru kaze sta treba da se iskompajlira na kojoj platformi:

CreateWindow.dpr/CreateWindow.lpr
program CreateWindow; {$I SGE.inc} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils; begin   try     // TODO   except     on E: Exception do     begin       {$IFDEF SGE_Windows}       MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);       {$ELSE}       WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);       {$ENDIF}     end;   end; end.

Vidite da smo na pocetku fajla importovali nas include fajl koji definise SGE_Windows vrednost ako se kompajlira na Windows-u, i SGE_Linux ako se kompajlira na Linux-u, a to ce nam pomoci da kazemo kompajleru sta da kompajlira od koda.

Da pogledamo prvo uses sekciju. Svi znamo da na Linux-u ne postoje Windows biblioteke (ko ne zna, sad je naucio) i ako bi hteli na Linux-u da iskompajliramo kod koji zahteva Windows biblioteke, dobili bi gresku. Zato smo Windows biblioteku stavili izmedju {$IFDEF SGE_Windows} i {$ENDIF}, sto znaci da ce se taj deo koda iskompajlirati samo u slucaju da se program kompajlira na Windows-u. Ako bi hteli da dodamo neki kod koji bi se kompajlirao samo na Linux-u stavili bi ga izmedju {$IFDEF SGE_Linux} i {$ENDIF}.

Drugi deo koda koji je specifican je nacin na koji cemo prikazati da je u programu doslo do greske zbog koje program ne moze da se izvrsava dalje (recimo ako program iz nekog razloga ne moze da kreira prozor). Na Windows-ima je najcesci nacin prikaza greske mali prozorcic, dok je na Linux-u obicaj da se greska ispise u konzoli. Ako pogledate kod videcete da se Windows deo nalazi izmedju {$IFDEF SGE_Windows} i {$ELSE}, a Linux kod izmedju {$ELSE} i {$ENDIF}. U sustini, kompajleru smo rekli da prvi deo koda iskompajlira ako se kompajlira na Windows-u, a drugi deo ako se ne kompajlira na Windows-u.

Na isti nacin cemo pisati i ostale stvari koje su specificne za Windows ili Linux. Sad kada znamo kako objasniti kompajleru sta da radi, mozemo poceti sa pisanjem koda za kreiranje prozora.



Registruj se da bi učestvovao u diskusiji. Registrovanim korisnicima se NE prikazuju reklame unutar poruka.
offline
  • Pridružio: 30 Dec 2007
  • Poruke: 4759
  • Gde živiš: Niš

spremni smo za časove Very Happy

Ziveli



offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Napisano: 06 Avg 2010 19:00

Objektno orijentisan pristup je kao stvoren za multiplatformsko programiranje. Mozemo da napravimo osnovnu klasu koja ne implementira metode, ali ih deklarise, zatim da napravimo konkretne klase koje je nasledjuju i implementiraju metode za svaku platformu, i na kraju funkciju koja ce kreirati instancu prave klase kada nam zatreba. Na taj nacin u glavnom programu nemamo potrebe da se brinemo o detaljima implementacije Smile

Za pocetak cemo napraviti jednostavnu klasu koja ce deklarisati sve metode koje su nam potrebne i recicemo da su sve apstranktne:

type   TSGEWindow = class   protected     function GetLeft: Integer; virtual; abstract;     procedure SetLeft(ALeft: Integer); virtual; abstract;     function GetTop: Integer; virtual; abstract;     procedure SetTop(ATop: Integer); virtual; abstract;     function GetWidth: Integer; virtual; abstract;     procedure SetWidth(AWidth: Integer); virtual; abstract;     function GetHeight: Integer; virtual; abstract;     procedure SetHeight(AHeight: Integer); virtual; abstract;     function GetCaption: String; virtual; abstract;     procedure SetCaption(ACaption: String); virtual; abstract;   public     property Left: Integer read GetLeft write SetLeft;     property Top: Integer read GetTop write SetTop;     property Width: Integer read GetWidth write SetWidth;     property Height: Integer read GetHeight write SetHeight;     property Caption: String read GetCaption write SetCaption;   end;

Definisali smo klasu koja ce omoguciti menjanje imena prozora, njegove pozicije i velicine. Sada da se bacimo na pisanje klase koja ce implementirati te funkcije za Windows:

type   TSGEWin32Window = class(TSGEWindow)   private     FWindow: HWND;     FWindowClass: TWndClassEx;   protected     function GetLeft: Integer; override;     procedure SetLeft(ALeft: Integer); override;     function GetTop: Integer; override;     procedure SetTop(ATop: Integer); override;     function GetWidth: Integer; override;     procedure SetWidth(AWidth: Integer); override;     function GetHeight: Integer; override;     procedure SetHeight(AHeight: Integer); override;     function GetCaption: String; override;     procedure SetCaption(ACaption: String); override;   public     constructor Create(ACaption: String; ALeft, ATop, AWidth, AHeight: Integer);     destructor Destroy; override;   end; function SGEWin32Proc(Wnd: HWnd; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; begin   case Msg of     WM_DESTROY:     begin       PostQuitMessage(0);       Result:= 0;     end   else     Result:= DefWindowProc(Wnd, Msg, wParam, lParam);   end; end; constructor TSGEWin32Window.Create(ACaption: String; ALeft, ATop, AWidth,   AHeight: Integer); begin   FillChar(FWindowClass, SizeOf(FWindowClass), 0);   FWindowClass.cbSize := SizeOf(FWindowClass);   FWindowClass.style := CS_HREDRAW or CS_VREDRAW;   FWindowClass.lpfnWndProc := @SGEWin32Proc;   FWindowClass.hInstance := HInstance;   FWindowClass.hIcon := LoadIcon(0, IDI_APPLICATION);   FWindowClass.hCursor := LoadCursor(0, IDC_ARROW);   FWindowClass.hbrBackground := GetStockObject(BLACK_BRUSH);   FWindowClass.lpszClassName := 'SGEWin32Window';   RegisterClassEx(FWindowClass);   FWindow := CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, FWindowClass.lpszClassName,     PChar(ACaption), WS_OVERLAPPEDWINDOW, ALeft, ATop, AWidth, AHeight, 0, 0,     HInstance, nil);   if FWindow = 0 then     raise Exception.Create('Cann''t create window');   ShowWindow(FWindow, SW_SHOWDEFAULT);   UpdateWindow(FWindow); end; destructor TSGEWin32Window.Destroy; begin   DestroyWindow(FWindow);   UnregisterClass(FWindowClass.lpszClassName, HInstance);   inherited; end; function TSGEWin32Window.GetCaption: String; begin   SetLength(Result, GetWindowTextLength(FWindow));   GetWindowText(FWindow, PChar(Result), Length(Result)); end; function TSGEWin32Window.GetHeight: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Bottom - Rect.Top; end; function TSGEWin32Window.GetLeft: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Left; end; function TSGEWin32Window.GetTop: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Top; end; function TSGEWin32Window.GetWidth: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Right - Rect.Left; end; procedure TSGEWin32Window.SetCaption(ACaption: String); begin   SetWindowText(FWindow, PChar(ACaption)); end; procedure TSGEWin32Window.SetHeight(AHeight: Integer); begin   MoveWindow(FWindow, Left, Top, Width, AHeight, True); end; procedure TSGEWin32Window.SetLeft(ALeft: Integer); begin   MoveWindow(FWindow, ALeft, Top, Width, Height, True); end; procedure TSGEWin32Window.SetTop(ATop: Integer); begin   MoveWindow(FWindow, Left, ATop, Width, Height, True); end; procedure TSGEWin32Window.SetWidth(AWidth: Integer); begin   MoveWindow(FWindow, Left, Top, AWidth, Height, True); end;

U konstruktoru registrujemo klasu za kreiranje prozora, a zatim i sam prozor na osnovu parametara koje smo prosledili. U slucaju da se desi greska prilikom kreiranja prozora, konstruktor se prekida uz gresku Cann''t create window koju cemo uhvatiti u glavnom programu. Posle kreiranja, prikazujemo nas prozor.

Destruktor je zaduzen za oslobadjane resursa koje smo zauzeli pri kreiranju.

Ostatak funkcija citaja/postavljaja osobine prozora.

Sledece na redu je implementacija klase koja ce se brinuti o prozoru za Linux:

type   TSGEX11Window = class(TSGEWindow)   private     FDisplay: PDisplay;     FWindow: TWindow;   protected     function GetLeft: Integer; override;     procedure SetLeft(ALeft: Integer); override;     function GetTop: Integer; override;     procedure SetTop(ATop: Integer); override;     function GetWidth: Integer; override;     procedure SetWidth(AWidth: Integer); override;     function GetHeight: Integer; override;     procedure SetHeight(AHeight: Integer); override;     function GetCaption: String; override;     procedure SetCaption(ACaption: String); override;   public     constructor Create(ADisplay: PDisplay; ACaption: String; ALeft, ATop, AWidth, AHeight: Integer);     destructor Destroy; override;   end; function TSGEX11Window.GetLeft: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.x; end; procedure TSGEX11Window.SetLeft(ALeft: Integer); begin   XMoveWindow(FDisplay, FWindow, ALeft, Top); end; function TSGEX11Window.GetTop: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.y; end; procedure TSGEX11Window.SetTop(ATop: Integer); begin   XMoveWindow(FDisplay, FWindow, Left, ATop); end; function TSGEX11Window.GetWidth: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.width; end; procedure TSGEX11Window.SetWidth(AWidth: Integer); begin   XResizeWindow(FDisplay, FWindow, AWidth, Height); end; function TSGEX11Window.GetHeight: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.height; end; procedure TSGEX11Window.SetHeight(AHeight: Integer); begin   XResizeWindow(FDisplay, FWindow, Width, AHeight); end; function TSGEX11Window.GetCaption: String; var   Caption: PChar; begin   XFetchName(FDisplay, FWindow, @Caption);   Result := Caption;   XFree(Caption); end; procedure TSGEX11Window.SetCaption(ACaption: String); begin   XStoreName(FDisplay, FWindow, PChar(ACaption)); end; constructor TSGEX11Window.Create(ADisplay: PDisplay; ACaption: String;   ALeft, ATop, AWidth, AHeight: Integer); var   WMDelete: TAtom; begin   FDisplay := ADisplay;   if FDisplay = nil then     raise Exception.Create('Cann''t open display');   FWindow := XCreateSimpleWindow(FDisplay, DefaultRootWindow(FDisplay), ALeft,     ATop, AWidth, AHeight, 0, 0, 0);   XStoreName(FDisplay, FWindow, PChar(ACaption));   WMDelete := XInternAtom(FDisplay, 'WM_DELETE_WINDOW', True);   XSetWMProtocols(FDisplay, FWindow, @WMDelete, 1);   XMapWindow(FDisplay, FWindow); end; destructor TSGEX11Window.Destroy; begin   XDestroyWindow(FDisplay, FWindow);   inherited; end;

Slicne stvari se dogadjaju i ovde. U konstruktoru proveravamo da li je display otvoren i kreiramo prozor. Zatim mu postavljamo ime i zahtevamo da dobijemo obavesetenje kad window manager hoce da zatvori prozor. Na kraju prikazujemo prozor.

U destruktoru unistavamo prozor koji smo kreirali.

Ostale funkcije, kao i kod Windows verzije, citaju/postavljaju osobine prozora.

Sada imamo implementacije za obe platforme. Napravicemo funkciju koja ce znati koju instancu mora kreirati u odnosu na platformu na kojoj se program kompajlira, i funkciju koja ce umeti da obradjuje poruke vezane za prozor koji kreiramo:

{$IFDEF SGE_Windows} uses   Windows, Messages, SGEWin32Window; function CreateWindow(ACaption: String; ALeft, ATop, AWidth, AHeight: Integer): TSGEWindow; begin   Result := TSGEWin32Window.Create(ACaption, ALeft, ATop, AWidth, AHeight); end; function ProcessMessages: Boolean; var   Msg: TMsg; begin   Result := True;   while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do   begin     TranslateMessage(Msg);     DispatchMessage(Msg);     if Msg.message = WM_QUIT then     begin       Result := False;       Exit;     end;   end; end; {$ELSE} uses   x, xlib, ctypes, SGEX11Window; var   Display: PDisplay; function CreateWindow(ACaption: String; ALeft, ATop, AWidth, AHeight: Integer): TSGEWindow; begin   Result := TSGEX11Window.Create(Display, ACaption, ALeft, ATop, AWidth, AHeight); end; function ProcessMessages: Boolean; var   Event: TXEvent;   ClientMsg: String; begin   Result := True;   while (XPending(Display) > 0) do   begin     XNextEvent(Display, @Event);     if (Event._type = ClientMessage) or (Event._type = DestroyNotify) then     begin       Result := False;       Exit;     end;   end; end; initialization   Display := XOpenDisplay(nil); finalization   XCloseDisplay(Display); {$ENDIF}

Kao sto vidite, opet smo koristili {$IFDEF} da bismo odredili sta treba kad da se iskompajlira. Za Windows kreiramo instancu TSGEWin32Window klase i koristimo Windows nacin za citanje poruka, dok za Linux kreiramo instancu TSGEX11Window klase i citamo poruke na Linux nacin.

Za one koji zele da znaju malo vise o tome kako se radi sa prozorima informacije mozete naci ovde
Windows: http://msdn.microsoft.com/en-us/library/ff468925(v=VS.85).aspx
Linux: http://tronche.com/gui/x/xlib/window/

Sad kad imamo sve sto nam treba, mozemo u glavnom programu da napravimo nase prozorce i da ne mislimo o tome da li program radi na Windows-u ili Linux-u:

program CreateWindow; {$I SGE.inc} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGEWindow in 'SGEWindow.pas'; var   Window: TSGEWindow; begin   try     Window := SGEWindow.CreateWindow('Test', 10, 10, 400, 300);     while ProcessMessages do       Sleep(1);     Window.Free;   except     on E: Exception do     begin       {$IFDEF SGE_Windows}       MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);       {$ELSE}       WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);       {$ENDIF}     end;   end; end.

Kao sto vidite, koristimo apstraktnu klasu, a CreateWindow funkcija nam vraca pravu instancu. Sad kad odprilike imamo sliku onog sto nas ceka, mogli bi da grubo osmislimo engine. Za pocetak bi mogli da kazemo da zelimo tako da organizujemo sve da je u glavnom programu dovoljno dodati samo jedan zabis u unit listi... ne zelimo da moramo posebno da dodajemo zapise za engine, za prozor, za podrsku za OpenGL, za DirectX, za teksture, itd, itd... Lepo bi bilo da samo dodamo recimo SGE i to je to... tako cemo i uraditi Smile Zatim, bilo bi lepo da imamo jednu klasu koja ce se brinuti o svim instancama objekata... dakle jednu glavnu klasu za engine. Trebala bi nam i klasa koja ce znati da zapisuje sta se dogadja u engine-u, neki jednostavan logger. I konacno, trebace nam klasa koja ce umeti da kreira prozor i da nesto renderuje na njega. Da bi stvari bile sto jednostavnije, klasa za renderovanje ce na pocetku biti u sustini samo wrapper za OpenGL/DirectX, kasnije cemo dodati i fine funkcijice i klase za rad sa teksturama, 3d objektima i slicno.

https://www.mycity.rs/must-login.png

Dopuna: 06 Avg 2010 21:02

Da bismo se lakse snalazili po fajlovima, bilo bi lepo da nekako organizujemo sve po foldere. Nacin koji cemo koristiti mi se cini kao prilicno dobar. Projekat se lako cisti, i kod za engine je odvojen od primera. Struktura ce za pocetak izgledati ovako:

Bin Bulid Common Samples     HelloWorld Source

U folderu Bin ce se nalaziti iskompajlirani fajlovi za Windows i Linux. Folder Build ce imati fajlove koji nastaju prilikom kompajliranja, Common ce imati fajlove koji ce biti zajednicki za engine i glavni program (recimo SGE.ini), Samples ce imati izvorne kodove primera (za sada samo jedan koji cemo koristiti za testiranje engine-a), i Source ce imati izvorni kod samog engine-a.

Za sada cu poslati samo osnovne fajlove i strukturu foldera, pa posle krecemo sa pisanjem engine-a.

https://www.mycity.rs/must-login.png

Dopuna: 07 Avg 2010 2:35

Za lakse debug-ovanje engine-a je dobro imati bar neki osnovni log. Zato cemo se prvo uhvatiti klase za pisanje logova. Posto hocemo da u jednom unit-u imamo sve sto ce nam trebati u glavnom programu, moramo u njemu definisati sve klase, zato cemo tu napraviti apstraktne klase i sve tipove koje ce korisnik engine-a moci da koristi. Za sada ce to biti engine i logger:

type   TSGEEngine = class;   TSGE_LogLevel = (     ESGE_LL_INFO,     ESGE_LL_WARNING,     ESGE_LL_ERROR,     ESGE_LL_ENGINE   );   TSGELogger = class;   TSGELoggerOnLogEvent = procedure(AMessage: String; ALogLevel: TSGE_LogLevel; var AWriteMessage: Boolean) of object;   { TSGEEngine }   TSGEEngine = class   protected     function GetLogger: TSGELogger; virtual; abstract;   public     property Logger: TSGELogger read GetLogger;   end;   { TSGELogger }   TSGELogger = class   protected     function GetLogLevel: TSGE_LogLevel; virtual; abstract;     procedure SetLogLevel(ALogLevel: TSGE_LogLevel); virtual; abstract;     function GetOnLog: TSGELoggerOnLogEvent; virtual; abstract;     procedure SetOnLog(AOnLog: TSGELoggerOnLogEvent); virtual; abstract;   public     procedure Log(AMessage: String; ALogLevel: TSGE_LogLevel = ESGE_LL_INFO); virtual; abstract;     property LogLevel: TSGE_LogLevel read GetLogLevel write SetLogLevel;     property OnLog: TSGELoggerOnLogEvent read GetOnLog write SetOnLog;   end;

Kao sto se vidi iz koda, nas engine ce za sada jedino omoguciti dostup do logger-a pa ce korisnik moci da pise u log.

Logger ce omoguciti pisanje poruke u log, filtriranje poruka na osnovu nivoa i presretavanje pisanja. Na taj nacin cemo u engine-u logovati skoro svaku sitnicu, a korisnik ce odluciti da li zeli sve da zapise ili samo upozorenja, greske, ili najosnovnije podatke. Uz pomoc presretavanja poruka ce moci sam da filtrira poruke ili da napravi svoj sistem pisanja loga.

Posto smo napravili apstraktnu klasu za logger, na redu je implementacija:

unit SGELogger; {$I SGE.inc} interface uses   SysUtils, SGETypes; type   { TLogger }   TLogger = class(TSGELogger)   private     FLogLevel: TSGE_LogLevel;     FOnLog: TSGELoggerOnLogEvent;     FLogFile: TextFile;     FOpened: Boolean;   protected     function GetLogLevel: TSGE_LogLevel; override;     procedure SetLogLevel(ALogLevel: TSGE_LogLevel); override;     function GetOnLog: TSGELoggerOnLogEvent; override;     procedure SetOnLog(AOnLog: TSGELoggerOnLogEvent); override;   public     constructor Create(AFileName: String);     destructor Destroy; override;     procedure Log(AMessage: String; ALogLevel: TSGE_LogLevel = ESGE_LL_INFO); override;     property LogLevel: TSGE_LogLevel read GetLogLevel write SetLogLevel;     property OnLog: TSGELoggerOnLogEvent read GetOnLog write SetOnLog;   end; implementation { TLogger } function TLogger.GetLogLevel: TSGE_LogLevel; begin   Result := FLogLevel; end; procedure TLogger.SetLogLevel(ALogLevel: TSGE_LogLevel); begin   FLogLevel := ALogLevel; end; function TLogger.GetOnLog: TSGELoggerOnLogEvent; begin   Result := FOnLog; end; procedure TLogger.SetOnLog(AOnLog: TSGELoggerOnLogEvent); begin   FOnLog := AOnLog; end; constructor TLogger.Create(AFileName: String); begin   FOpened := False;   FLogLevel := ESGE_LL_ERROR;   if AFileName <> '' then   begin     AssignFile(FLogFile, AFileName);     ReWrite(FLogFile);     FOpened := True;   end; end; destructor TLogger.Destroy; begin   if FOpened then     CloseFile(FLogFile);   inherited; end; procedure TLogger.Log(AMessage: String; ALogLevel: TSGE_LogLevel); var   WriteMessage: Boolean; begin   WriteMessage := ALogLevel >= FLogLevel;   if Assigned(FOnLog) then     FOnLog(AMessage, ALogLevel, WriteMessage);   if FOpened and WriteMessage then     WriteLn(FLogFile, AMessage); end; end.

Logger u konstruktoru proverava da li mu je zadato ime fajla i, ako jeste, otvara fajl za pisanje. U destruktoru se zadani fajl zatvara. Cela logika se nalazi u funkciji Log koja proverava da li poruku treba zapisati, zatim je salje korisniku na obradu i na kraju je zapisuje u fajl ako je prosla filter i ako je fajl otvoren.

Sada mozemo da napisemo i implementaciju engine-a koja ce kreirati instancu nase logger klase:

unit SGEEngine; {$I SGE.inc} interface uses   SysUtils, SGETypes; type   { TDevice }   TEngine = class(TSGEEngine)   private     FLogger: TSGELogger;   protected     function GetLogger: TSGELogger; override;   public     constructor Create(ALogFileName: String; AOnLog: TSGELoggerOnLogEvent);     destructor Destroy; override;     property Logger: TSGELogger read GetLogger;   end; implementation uses   SGELogger; { TEngine } function TEngine.GetLogger: TSGELogger; begin   Result := FLogger; end; constructor TEngine.Create(ALogFileName: String; AOnLog: TSGELoggerOnLogEvent); begin   FLogger := TLogger.Create(ALogFileName);   FLogger.OnLog := AOnLog;   FLogger.Log('SGE v' + SGE_VERSION, ESGE_LL_ENGINE); end; destructor TEngine.Destroy; begin   FLogger.Log('Engine destroyed', ESGE_LL_INFO);   FLogger.Free;   inherited; end; end.

Engine za sada samo kreira logger i u log zapisuje svoje ime i verziju prilikom kreiranja (obratite paznju da se kao nivo koristi ESGE_LL_ENGINE sto znaci da ce poruka uvek biti ispisana, osim ako se prilikom presretavanja ne iskljuci), i poruku o unistavanju na kraju (ova poruka ima ESGE_LL_INFO nivo i u konacnom programu nema potrebe da se ispisuje, ali cemo je mi koristiti za debug-ovanje tako sto cemo u programu reci da hocemo da logujemo sve poruke koje imaju bar nivo ESGE_LL_INFO).

Da bi mogli da koristimo engine koji smo upravo kreirali, napisacemo malu funckiju u SGETypes unit-u:

function CreateSGEEngine(ALogFileName: String = 'SGE.log'; AOnLog: TSGELoggerOnLogEvent = nil): TSGEEngine; begin   Result := TEngine.Create(ALogFileName, AOnLog); end;

Sada jos samo da popravimo glavni program tako da koristi taj engine:

program HelloWorld; {$I SGE.inc} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas'; var   Engine: TSGEEngine = nil; begin   try     try       Engine := CreateSGEEngine;       Engine.Logger.LogLevel := ESGE_LL_INFO;       Engine.Logger.Log('Testing...');     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     Engine.Free;   end; end.

Kreiramo nas engine, kazemo da hocemo da se loguju sve poruke (postavimo log level na ESGE_LL_INFO), zapisemo poruku u log i na kraju zatvorimo engine. Sad smo lepo pokazali kako glavni program uopste ne mora da zna koje klase se koriste za izvrsavanje komandi, dovoljno mu je da zna koje su apstraktne klase i da ih koristi. Jedino sto mu treba je funkcija koja ce kreirati pravi engine, a dalje moze sam Smile

U sledecem tutorijalu cemo pokazati kako mozemo da presretnemo poruku i da je umesto u standardan log zapisemo u html fajl.

https://www.mycity.rs/must-login.png

Dopuna: 07 Avg 2010 3:36

Jos jedan tutorijal za danas pa u krevet Smile Ovaj ce biti prilicno kratak. Napravicemo klasu koja ce presretati log poruke i pisati ih u html:

unit HTMLLogger; {$I SGE.inc} interface uses   SysUtils, SGETypes; type   THTMLLogger = class   private     FHTML: TextFile;   public     constructor Create(AHTMLLogFileName: String);     destructor Destroy; override;     procedure Log(AMessage: String; ALogLevel: TSGE_LogLevel; var AWriteMessage: Boolean);   end; implementation { THTMLLogger } constructor THTMLLogger.Create(AHTMLLogFileName: String); begin   AssignFile(FHTML, AHTMLLogFileName);   ReWrite(FHTML);   WriteLn(FHTML, '<html>');   WriteLn(FHTML, '<head>');   WriteLn(FHTML, '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />');   WriteLn(FHTML, '<title>' + ChangeFileExt(ExtractFileName(ParamStr(0)), '') + ' Log</title>');   WriteLn(FHTML, '<style type="text/css">');   WriteLn(FHTML, 'body, html {');   WriteLn(FHTML, 'background: #000000;');   WriteLn(FHTML, 'width: 1000px;');   WriteLn(FHTML, 'font-family: Arial;');   WriteLn(FHTML, 'font-size: 16px;');   WriteLn(FHTML, 'color: #C0C0C0;');   WriteLn(FHTML, '}');   WriteLn(FHTML, 'h1 {');   WriteLn(FHTML, 'color : #FFFFFF;');   WriteLn(FHTML, 'border-bottom : 1px dotted #888888;');   WriteLn(FHTML, '}');   WriteLn(FHTML, 'pre {');   WriteLn(FHTML, 'font-family : arial;');   WriteLn(FHTML, 'margin : 0;');   WriteLn(FHTML, '}');   WriteLn(FHTML, '.box {');   WriteLn(FHTML, 'border : 1px dotted #818286;');   WriteLn(FHTML, 'padding : 5px;');   WriteLn(FHTML, 'margin: 5px;');   WriteLn(FHTML, 'width: 950px;');   WriteLn(FHTML, 'background-color : #292929;');   WriteLn(FHTML, '}');   WriteLn(FHTML, '.err {');   WriteLn(FHTML, 'color: #EE1100;');   WriteLn(FHTML, 'font-weight: bold');   WriteLn(FHTML, '}');   WriteLn(FHTML, '.warn {');   WriteLn(FHTML, 'color: #FFCC00;');   WriteLn(FHTML, 'font-weight: bold');   WriteLn(FHTML, '}');   WriteLn(FHTML, '.info {');   WriteLn(FHTML, 'color: #C0C0C0;');   WriteLn(FHTML, '}');   WriteLn(FHTML, '.engine {');   WriteLn(FHTML, 'color: #CCA0A0;');   WriteLn(FHTML, '}');   WriteLn(FHTML, '</style>');   WriteLn(FHTML, '</head>');   WriteLn(FHTML, '<body>');   WriteLn(FHTML, '<h1>' + ChangeFileExt(ExtractFileName(ParamStr(0)), '') + '</h1>');   WriteLn(FHTML, '<div class="box">');   WriteLn(FHTML, '<table>'); end; destructor THTMLLogger.Destroy; begin   WriteLn(FHTML, '</table>');   WriteLn(FHTML, '</div>');   WriteLn(FHTML, '</body>');   WriteLn(FHTML, '</html>');   CloseFile(FHTML);   inherited; end; procedure THTMLLogger.Log(AMessage: String; ALogLevel: TSGE_LogLevel;   var AWriteMessage: Boolean); begin   if AWriteMessage then   begin     WriteLn(FHTML, '<tr>');     Write(FHTML, '<td class="');     case ALogLevel of       ESGE_LL_INFO: Write(FHTML, 'info');       ESGE_LL_WARNING: Write(FHTML, 'warn');       ESGE_LL_ERROR: Write(FHTML, 'err');       ESGE_LL_ENGINE: Write(FHTML, 'engine');     end;     WriteLn(FHTML, '"><pre>');     WriteLn(FHTML, AMessage);     WriteLn(FHTML, '</pre></td>');     WriteLn(FHTML, '</tr>');   end; end; end.

Kao sto se vidi iz koda, klasa u konstruktoru kreira fajl i upise zaglavlje html-a, a u destruktoru upise kraj html-a i zatvori datoteku. Logika se nalazi u Log proceduri koja u tabelu upisuje poruke. Da li ce poruka biti zapisana ili ne i dalje zavisi od logike u osnovnoj logger klasi (AWriteMessage je postavljena na vrednost koju je osnovni logger odredio na osnovu log level-a).

Sada nam ostaje jos samo da u glavnom programu kreiramo instancu HTML logger-a i da je iskoristi:

program HelloWorld; {$I SGE.inc} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas',   HTMLLogger in 'HTMLLogger.pas'; var   Engine: TSGEEngine = nil;   HTML: THTMLLogger = nil; begin   try     try       HTML := THTMLLogger.Create('SGE.html');       Engine := CreateSGEEngine('', HTML.Log);       Engine.Logger.LogLevel := ESGE_LL_INFO;       Engine.Logger.Log('Testing...');     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     Engine.Free;     HTML.Free;   end; end.

Prilikom kreiranja engine-a, prvi parametar postavimo na prazan string i zato se osnovni log fajl nece kreirati, a kao drugi parametar postavljamo funkciju za logovanje iz HTML loggera... i to je to Smile

https://www.mycity.rs/must-login.png

Dopuna: 07 Avg 2010 16:59

Pre nego sto se bacimo na pisanje klase za pravljenje prozora i crtanje, nedostaje nam jos jedna klasica, a to je klasa za merenje vremena. Klasica ce biti prilicno jednostavna i vracace nam koliko milisekundi je proslo od kad smo je resetovali (imace samo 2 funkcije... Reset i GetTime). Engine ce na pocetku kreirati jedan tajmer za sebe i ostale klase iz engine-a (za sada ce ga koristiti samo logger da ispise kad je poruka zapisana), ali ce i omoguciti kreiranje dodatnih tajlera za aplikaciju. Svaki tajmer ce imati svoje ime pa cemo u programu moci lako da nadjemo bas onaj tajmer koji nam treba. Posto cemo imena davati i ostalim klasama, bilo bi logicno da napravimo jos jednu malu klasicu koja ce sluziti za cuvanje imena, a svaka klasa koja ce imati ime ce je naslediti. Krecemo, kao i uvek, od apstraktne klase:

type   TSGENamedObject = class   protected     function GetName: String; virtual; abstract;   public     property Name: String read GetName;   end;

Ok... sad kad imamo klasu za rad sa imenom (implementacija ce sa nalaziti u klasama koje ce hteti da imaju ime... videcete zasto kad zavrsimo engine), mozemo da radimo na tajmeru. Prvo moramo dodati apstraktnu klasu u SGETypes.pas:

type   TSGETimer = class(TSGENamedObject)   protected     function GetTime: Cardinal; virtual; abstract;   public     procedure Reset; virtual; abstract;     property Time: Cardinal read GetTime;   end;

I odmah na implementaciju... koristicemo vec gotove funkcije koje postoje za Windows i Linux (opet cemo morati da koristimo {$IFDEF}... sad ste vec naucili sta se time postize pa vise necu skretati paznju na to):

unit SGETimer; {$I SGE.inc} interface uses   SysUtils, SGETypes; type   { TTimer }   TTimer = class(TSGETimer)   private     FName: String;     FStartTime: Cardinal;     function GetMS: Cardinal;   protected     function GetName: String; override;     function GetTime: Cardinal; override;   public     constructor Create(AName: String);     procedure Reset; override;     property Name: String read GetName;     property Time: Cardinal read GetTime;   end; implementation uses   {$IFDEF SGE_Windows}   Windows;   {$ELSE}   dos;   {$ENDIF} { TTimer } function TTimer.GetMS: Cardinal; begin   {$IFDEF SGE_Windows}   Result := GetTickCount;   {$ELSE}   Result := Cardinal(GetMsCount);   {$ENDIF} end; function TTimer.GetName: String; begin   Result := FName; end; function TTimer.GetTime: Cardinal; begin   Result := GetMS - FStartTime; end; constructor TTimer.Create(AName: String); begin   FName := AName;   Reset; end; procedure TTimer.Reset; begin   FStartTime := GetMS; end; end.

Napravili smo konstruktor koji upisuje ime tajmera i resetuje ga za pocetnu uportebu. Od ostalih funkcija je interesantna jos GetMS funkcija. Ona vraca broj milisekundi od kad je sistem pokrenut. Na Windows-u koristimo Win API funkciju za vracanje tog podatka, dok na Linux-u koristimo pomocnu funkciju GetMsCount koja je definisana u Dos unit-u FPC-a. Prilikom resetovanja uzimamo trenutan broj milisekundi tako da mozemo da izracunamo koliko je vremena proslo od tada do zvanja GetTime funkcije. I to je to sto se tajmera tice.

Sada je na red doslo prosirivanje engine klase. Moramo dodati glavni tajmer za engine, i mogucnost dodavanja novih imenovanih tajmera. Napisacemo kod tako da omogucava dodavanje tajmera samo ako tajmer sa istim imenom nije vec dodan. Ako korisnik hoce da doda tajmer sa vec postojecim imenom, trebalo bi da mu vratimo neku gresku koja nije bas toliko opasna da bi prekinula program pa cemo zbog toga napraviti tako da ako do greske dodje vratimo nil, a ako je program iskompajliran u debug modu izbacicemo i gresku.

Pa... prvo moramo da prosirimo nas SGE.inc da ume da detektuje debug mod. To cemo uraditi tako sto cemo proveriti da li je postavljena vrednost DEBUG (delphi sam definise tu vrednost kada se koristi debug profil za kompajliranje, a u Lazarusu cemo morati sami da je dodamo):

{$IFDEF DEBUG}   {$DEFINE SGE_Debug} {$ENDIF}

Za ukljucivanje DEBUG vrednosti u Lazarus-u se koristi prozor za podesavanje opcija kompajlera (Compiler Options), na jezicku Other. Treba dodati sledeci tekst u Custom options:

-dDEBUG

Dobro... spremni smo za prosirivanje engine klase... dodacemo potrebne funkcije u apstraktnu klasu:

type   TSGEEngine = class   protected     function GetLogger: TSGELogger; virtual; abstract;     function GetTimer: TSGETimer; virtual; abstract;     function GetTimers(AName: String): TSGETimer; virtual; abstract;   public     function CreateTimer(AName: String): TSGETimer; virtual; abstract;     procedure DestroyTimer(AName: String); overload; virtual; abstract;     procedure DestroyTimer(ATimer: TSGETimer); overload; virtual; abstract;     procedure DestroyAllTimers; virtual; abstract;     property Logger: TSGELogger read GetLogger;     property Timer: TSGETimer read GetTimer;     property Timers[AName: String]: TSGETimer read GetTimers;   end;

Dodali smo osnovni tajmer kojem se dostupa preko Timer property-ja i ostale tajmere do kojih se dostupa preko Timers property-ja i imena tajmera. Omogucili smo i kreiranje/unistavanje dodatnih tajmera preko funkcija CreateTimer i DestroyTimer/DestroyAllTimers. Idemo sad na implementaciju.

Dodacemo nove privatne promenljive za cuvanje kreiranih tajmera. Posto ce svaki tajmer imati ime i moracemo da ih trazimo po imenu, iskoristicemo klasu TStringList za cuvanje tajmera (klasa nije bas najbrza, ali je za nas trenutno dovoljno dobra).:

FTimer: TSGETimer; FTimers: TStringList;

Konstruktor cemo popraviti da kreira tajmer za engine i da pripremi listu za nove tajmere:

constructor TEngine.Create(ALogFileName: String; AOnLog: TSGELoggerOnLogEvent); begin   FTimer := TTimer.Create('');   FTimers := TStringList.Create;   FLogger := TLogger.Create(ALogFileName);   FLogger.OnLog := AOnLog;   FTimer.Reset;   FLogger.Log('SGE v' + SGE_VERSION, ESGE_LL_ENGINE); end;

Kao sto vidite, pre prvog pisanja u log smo pozvali Reset tajmera tako da ce logger moci da ga koristi za ispis vremena poruke (logger cemo kasnije popraviti da koristi tu mogucnost).

Destruktor moramo popraviti da unisti kreirane tajmere:

destructor TEngine.Destroy; begin   FLogger.Log('Engine destroyed', ESGE_LL_INFO);   FLogger.Free;   DestroyAllTimers;   FTimers.Free;   FTimer.Destroy;   inherited; end;

Unistavamo tajmere koje je korisnik kreirao, i tajmer koji je kreirao engine.

Sada cemo dodati kod za nove funkcije... da krenemo od CreateTimer:

function TEngine.CreateTimer(AName: String): TSGETimer; var   I: Integer; begin   I := FTimers.IndexOf(AName);   {$IFDEF SGE_Debug}   Assert(I = -1, 'Timer with the same name already exists');   {$ENDIF}   if I = -1 then   begin     Result := TTimer.Create(AName);     FTimers.AddObject(AName, Result);   end   else     Result := nil; end;

Funkcija prvo proverava da li u listi vec postoji zadato ime. Ako je rezultat (promenljiva I) -1 znaci da ime ne postoji u listi. Zatim, u slucaju debug-ovanja proveravamo da li je I zaista -1 i ispisujemo poruku o gresci ako nije. Ta provera se nece izvrsiti kad budemo iskompajlirali konacnu verziju programa (iskljucicemo DEBUG). Na kraju, ako ime postoji, vracamo prazan objekat, a ako ne postoji kreiramo nov tajmer i dodajemo ga u listu.

Sad na brisanje:

procedure TEngine.DestroyTimer(AName: String); var   I: Integer; begin   I := FTimers.IndexOf(AName);   {$IFDEF SGE_Debug}   Assert(I <> -1, 'Timer with given name does not exists');   {$ENDIF}   if I <> -1 then   begin     FTimers.Objects[I].Free;     FTimers.Delete(I);   end; end; procedure TEngine.DestroyTimer(ATimer: TSGETimer); var   I: Integer; begin   I := FTimers.IndexOfObject(ATimer);   {$IFDEF SGE_Debug}   Assert(I <> -1, 'Timer does not exists');   {$ENDIF}   if I <> -1 then   begin     FTimers.Objects[I].Free;     FTimers.Delete(I);   end; end;

Ovde se radimo slicnu stvar kao i kod kreiranja. Proveravamo da li tajmer postoji (u jednom slucaju po imenu, u drugom slucaju trazimo dati tajmer), ako ne postoji izbacujemo gresku, a ako postoji, unistavamo tajmer i brisemo ga iz liste.

Funkcija za brisanje svih tajmera ce samo protrcati kroz listu i obrisati sve sto nadje:

procedure TEngine.DestroyAllTimers; var   I: Integer; begin   for I := 0 to FTimers.Count - 1 do     FTimers.Objects[I].Free;   FTimers.Clear; end;

Za kraj ostaje funkcija koja ce vracati tajmer na osnovu imena... u sustini ce raditi isto kao i funkcija za brisanje, ali ce umesto brisanja, vratiti tajmer koji je nasla:

function TEngine.GetTimers(AName: String): TSGETimer; var   I: Integer; begin   I := FTimers.IndexOf(AName);   {$IFDEF SGE_Debug}   Assert(I <> -1, 'Timer with given name does not exists');   {$ENDIF}   if I <> -1 then     Result := TSGETimer(FTimers.Objects[I])   else     Result := nil; end;

Imamo spreman engine, sledece na redu je popravka logger klase. Prvo sto cemo dodati jos jednu promenljivu u funkciju koja presrece log zapise:

TSGELoggerOnLogEvent = procedure(ATime: Cardinal; AMessage: String; ALogLevel: TSGE_LogLevel; var AWriteMessage: Boolean) of object;

Da bi logger mogao da dostupa do tajmera iz engine-a, moramo mu nekako proslediti engine objekat. To cemo uraditi u konstruktoru.

constructor TLogger.Create(AEngine: TSGEEngine; AFileName: String); begin   FEngine := AEngine;   FOpened := False;   FLogLevel := ESGE_LL_ERROR;   if AFileName <> '' then   begin     AssignFile(FLogFile, AFileName);     ReWrite(FLogFile);     FOpened := True;   end; end;

Engine smo sacuvali u privatnoj promenljivoj i sada cemo moci da ga koristimo pri zapisivanju:

procedure TLogger.Log(AMessage: String; ALogLevel: TSGE_LogLevel); var   WriteMessage: Boolean;   Time: Cardinal; begin   Time := FEngine.Timer.Time;   WriteMessage := ALogLevel >= FLogLevel;   if Assigned(FOnLog) then     FOnLog(Time, AMessage, ALogLevel, WriteMessage);   if FOpened and WriteMessage then     WriteLn(FLogFile, Time, #9, AMessage); end;

Tako, sada logger zna u kom trenutku je poruka poslana u log i taj podatak zapisuje. Kad smo vec kod logiranja, popravicemo i nasu klasu za HTML log:

procedure THTMLLogger.Log(ATime: Cardinal; AMessage: String;   ALogLevel: TSGE_LogLevel; var AWriteMessage: Boolean); var   Min, Sec, MS: Cardinal; begin   if AWriteMessage then   begin     Min := ATime div 60000;     ATime := ATime mod 60000;     Sec := ATime div 1000;     ATime := ATime mod 1000;     MS := ATime;     WriteLn(FHTML, '<tr>');     WriteLn(FHTML, '<td width="100">');     WriteLn(FHTML, Format('%.2d:%.2d.%.3d', [Min, Sec, MS]));     WriteLn(FHTML, '</td>');     Write(FHTML, '<td class="');     case ALogLevel of       ESGE_LL_INFO: Write(FHTML, 'info');       ESGE_LL_WARNING: Write(FHTML, 'warn');       ESGE_LL_ERROR: Write(FHTML, 'err');       ESGE_LL_ENGINE: Write(FHTML, 'engine');     end;     WriteLn(FHTML, '"><pre>');     WriteLn(FHTML, AMessage);     WriteLn(FHTML, '</pre></td>');     WriteLn(FHTML, '</tr>');   end; end;

Sada ce HTML imati jos i podatak o vremenu u formatu Minuti:Sekunde.Milisekunde.

To je to sto se tice osnovnih klasa... sad mozemo polako da krenemo ka crtanju Smile

https://www.mycity.rs/must-login.png

BTW kako vam se cini ovo do sada?

Dopuna: 09 Avg 2010 0:10

Da iskoristimo znanje koje smo dobili u prvom tutorijalu... vreme je da kreiramo prozore Very Happy

Dodacemo nove klase koje cemo koristiti za prikaz prozora i renderovanje:

type   TSGE_FrameBufferType = (     ESGE_FBT_COLOR,     ESGE_FBT_DEPTH,     ESGE_FBT_STENCIL   );   TSGE_FrameBufferTypeSet = set of TSGE_FrameBufferType;   TSGEColor = packed record   case Integer of     1: (Red: Single;     Green: Single;     Blue: Single;     Alpha: Single;);     2: (Color: array [0..3] of Single;);   end;   TSGERenderer = class(TSGENamedObject)   protected     function GetRenderWindow: TSGERenderWindow; virtual; abstract;   public     procedure Initialize; virtual; abstract;     procedure Finalize; virtual; abstract;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); virtual; abstract;     procedure BeginFrame; virtual; abstract;     procedure EndFrame; virtual; abstract;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); virtual; abstract;     procedure SwapBuffers; virtual; abstract;     property RenderWindow: TSGERenderWindow read GetRenderWindow;   end;   TSGERenderWindow = class   protected     function GetCaption: String; virtual; abstract;     procedure SetCaption(ACaption: String); virtual; abstract;     function GetLeft: Integer; virtual; abstract;     procedure SetLeft(ALeft: Integer); virtual; abstract;     function GetTop: Integer; virtual; abstract;     procedure SetTop(ATop: Integer); virtual; abstract;     function GetWidth: Integer; virtual; abstract;     procedure SetWidth(AWidth: Integer); virtual; abstract;     function GetHeight: Integer; virtual; abstract;     procedure SetHeight(AHeight: Integer); virtual; abstract;     function GetAttribute(AName: String): String; virtual; abstract;   public     procedure Resize(AWidth, AHeight: Integer); virtual; abstract;     procedure Move(ALeft, ATop: Integer); virtual; abstract;     procedure SetMetrics(ALeft, ATop, AWidth, AHeight: Integer); virtual; abstract;     procedure GetMetrics(var ALeft, ATop, AWidth, AHeight: Integer); virtual; abstract;     procedure SwapBuffers; virtual; abstract;     property Caption: String read GetCaption write SetCaption;     property Left: Integer read GetLeft write SetLeft;     property Top: Integer read GetTop write SetTop;     property Width: Integer read GetWidth write SetWidth;     property Height: Integer read GetHeight write SetHeight;     property Attribute[AName: String]: String read GetAttribute;   end;

TSGE_FrameBufferType i TSGE_FrameBufferTypeSet tipovi nam omogucavaju da izaberemo na kojim buffer-ima zelimo da radimo. OpenGL i DirectX imaju nekoliko vrsta buffer-a. Color buffer se koristi za prikaz slike... tu se nalazi ono sto cemo videti na ekranu. Depth buffer se koristi za odredjivanje daljine objekata i dozvoljava nam da crtamo objekte jedne preko drugih bez brige da ce objekat u pozadini obrisati objekat koji je ispred njega. Stencil buffer se koristi za neke napredne nacine crtanja... svaki put kad nesto nacrtamo, mozemo na tom mestu da povecamo ili smanjimo vrednost buffer-a i time uticemo na sledece crtanje.

TSGEColor je tip koji cemo koristiti za odredjivanje boje brisanja i crtanja... vrednosti pojedinacnih komponenti boje su u rasponu od 0 do 1.

TSGERenderer je klasa koju cemo koristiti za renderovanje. Ona ce nam za sada omoguciti da kreiramo prozor, da pocnemo crtanje, obrisemo prozor bojom kojom zelimo,zavrsimo crtanje i konacno prikazemo to sto je nacrtano. Za sada jos nece biti komandi za crtanje, ali cemo ih ubrzo dodati.

TSGERenderWindow klasu necemo direktno koristiti. Nju cemo kreirati iz klase TSGERenderer i koristiti je za manipulaciju prozorom. Jedino kada cemo morati direktno da radimo sa tom klasom je kad budemo hteli da prozor programski pomerimo, promenimo velicinu ili naziv. U toj klasi se nalazi funkcija SwapBuffers koja ce prikazati sve ono sto je do tada iscrtano na prozoru i prozor pripremiti za sledecu sliku.

Posto planiramo da pravimo razlicite renderere (bar 2... jedan za OpenGL, drugi za DirectX), moracemo da napravimo jos nekoliko funkcija u klasi za engine. Takodje cemo morati da dodamo i funkciju koja ce da proverava poruke za nas prozor... pa... da dodamo sve sto nam nedostaje:

type   TSGEEngine = class   protected     function GetLogger: TSGELogger; virtual; abstract;     function GetTimer: TSGETimer; virtual; abstract;     function GetTimers(AName: String): TSGETimer; virtual; abstract;     function GetRenderer: TSGERenderer; virtual; abstract;     function GetRendererCount: Integer; virtual;abstract;     function GetRenderers(AIndex: Integer): TSGERenderer; virtual; abstract;   public     function CreateTimer(AName: String): TSGETimer; virtual; abstract;     procedure DestroyTimer(AName: String); overload; virtual; abstract;     procedure DestroyTimer(ATimer: TSGETimer); overload; virtual; abstract;     procedure DestroyAllTimers; virtual; abstract;     procedure RegisterRenderer(ARenderer: TSGERenderer); virtual; abstract;     procedure UnregisterRenderer(AName: String); overload; virtual; abstract;     procedure UnregisterRenderer(ARenderer: TSGERenderer); overload; virtual; abstract;     procedure UnregisterAllRenderers; virtual; abstract;     procedure Initialize(ARenderer: TSGERenderer); virtual; abstract;     procedure Finalize; virtual; abstract;     function ProcessMessages: Boolean; virtual; abstract;     property Logger: TSGELogger read GetLogger;     property Timer: TSGETimer read GetTimer;     property Timers[AName: String]: TSGETimer read GetTimers;     property Renderer: TSGERenderer read GetRenderer;     property RendererCount: Integer read GetRendererCount;     property Renderers[AIndex: Integer]: TSGERenderer read GetRenderers;   end;

Korisnik ce prvo morati da registruje renderer uz pomoc funkcije RegisterRenderer. Ako iz nekog razloga bude hteo da izbaci renderer iz engine-a, tada ce morati da pozove funkciju UnregisterRenderer. GetRendererCount i GetRenderers ce omoguciti korisniku da pogleda koji su sve rendereri registrovani. Initialize i Finalize su funkcije koje se moraju pozvati pre i posle glavne petlje u kojoj ce se program odvijati. Na taj nacin ce renderer moci da se pripremi (da kreira osnovne teksture, da proveri da li je graficka kartica dovoljno dobra, i slicno), i na kraju da oslobodi zauzete resurse ako ih je bilo. ProcessMessages je funkcija koju cemo u glavnom programu zvati pre svakog crtanja, a ona ce proveravati poruke za prozore i vracati nam True sve dok se glavni prozor ne zatvori i tako cemo znati da treba da prekinemo program.

Dodatne promenljive koje ce nam trebati su FInitialized koja ce oznacavati da li su engine i renderer inicijalizovani, FRenderer koja ce pokazivati na inicijalizovani renderer i FRenderers koja ce sadrzati sve registrovane renderere.

Ok... sad na implementaciju novih i promenu postojecih funkcija:

function TEngine.GetRenderer: TSGERenderer; begin   Result := FRenderer; end; function TEngine.GetRendererCount: Integer; begin   Result := FRenderers.Count; end; function TEngine.GetRenderers(AIndex: Integer): TSGERenderer; begin   Result := TSGERenderer(FRenderers.Objects[AIndex]); end; constructor TEngine.Create(ALogFileName: String; AOnLog: TSGELoggerOnLogEvent); begin   FInitialized := False;   FTimer := TTimer.Create('');   FTimers := TStringList.Create;   FRenderer := nil;   FRenderers := TStringList.Create;   FLogger := TLogger.Create(Self, ALogFileName);   FLogger.OnLog := AOnLog;   FTimer.Reset;   FLogger.Log('SGE v' + SGE_VERSION, ESGE_LL_ENGINE); end; destructor TEngine.Destroy; begin   Finalize;   UnregisterAllRenderers;   FRenderers.Free;   DestroyAllTimers;   FTimers.Free;   FLogger.Log('Engine destroyed', ESGE_LL_INFO);   FLogger.Free;   FTimer.Destroy;   inherited; end; procedure TEngine.RegisterRenderer(ARenderer: TSGERenderer); var   I: Integer; begin   I := FRenderers.IndexOfObject(ARenderer);   {$IFDEF SGE_Debug}   Assert(I = -1, 'Renderer is already registered');   {$ENDIF}   if I = -1 then   begin     FRenderers.AddObject(ARenderer.Name, ARenderer);     FLogger.Log(ARenderer.Name + ' registered', ESGE_LL_INFO);   end; end; procedure TEngine.UnregisterRenderer(AName: String); var   I: Integer; begin   I := FRenderers.IndexOf(AName);   {$IFDEF SGE_Debug}   Assert(I <> -1, 'Renderer with given name is not registered');   {$ENDIF}   if I <> -1 then   begin     FRenderers.Delete(I);     FLogger.Log(AName + ' unregistered', ESGE_LL_INFO);   end; end; procedure TEngine.UnregisterRenderer(ARenderer: TSGERenderer); var   I: Integer; begin   I := FRenderers.IndexOfObject(ARenderer);   {$IFDEF SGE_Debug}   Assert(I <> -1, 'Renderer is not registered');   {$ENDIF}   if I <> -1 then   begin     FRenderers.Delete(I);     FLogger.Log(ARenderer.Name + ' unregistered', ESGE_LL_INFO);   end; end; procedure TEngine.UnregisterAllRenderers; begin   FRenderers.Clear;   FLogger.Log('All renderers unregistered', ESGE_LL_INFO); end; procedure TEngine.Initialize(ARenderer: TSGERenderer); begin   if FInitialized then     Exit;   FRenderer := ARenderer;   FRenderer.Initialize;   FInitialized := True;   FLogger.Log('Engine initialized', ESGE_LL_INFO); end; procedure TEngine.Finalize; begin   if not FInitialized then     Exit;   FRenderer.Finalize;   FRenderer := nil;   FInitialized := False;   FLogger.Log('Engine finalized', ESGE_LL_INFO); end; function TEngine.ProcessMessages: Boolean; var   {$IFDEF SGE_Windows}   Msg: TMsg;   {$ELSE}   Event: TXEvent;   Display: PDisplay;   {$ENDIF} begin   Result := True;   {$IFDEF SGE_Debug}   Assert(FInitialized, 'Engine not initialized');   {$ENDIF}   if not FInitialized then     Exit;   Sleep(1);   {$IFDEF SGE_Windows}   while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do   begin     TranslateMessage(Msg);     DispatchMessage(Msg);     if Msg.message = WM_QUIT then     begin       Result := False;       Exit;     end;   end;   {$ELSE}   Display := Pointer(StrToInt(FRenderer.RenderWindow.Attribute['Display']));   while (XPending(Display) > 0) do   begin     XNextEvent(Display, @Event);     if (Event._type = ClientMessage) or (Event._type = DestroyNotify) then     begin       Result := False;       Exit;     end;   end;   {$ENDIF} end;

Kao sto se vidi iz koda, nema niceg posebno novog... renderere registrujemo na slican nacin na koji kreiramo tajmere, na slican nacin ih i brisemo iz liste registrovanih, jedino sto je malo drugacije od onog sto smo radili do sada je funkcija ProcessMessages. Windows deo je isti kao sto je bio i pre, ali u Linux delu od prozora uzimamo informaciju o displeju na kojem je registrovan. U ovom trenutku smo mogli uraditi isto kao i u prvom tutorijalu, ali logicnije je da se informacije o kreiranom prozoru nalaze u klasi za prozor. Time bi kasnije omogucili lakse dodavanje novih prozora.

Ovog puta nece biti koda (jer sam vec napravio i klase za renderer i prozore Very Happy)... sutra cemo napisati klase za prozore i renderer, i konacno cemo dobiti aplikaciju koja ce koristiti OpenGL da obrise prozor bojom koju odredimo Smile

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Napisano: 09 Avg 2010 21:56

Klase za rad sa prozorima su prilicno slicne kao one iz prvog tutorijala... uz jednu bitnu razliku, kreiraju OpenGL context koji nam omogucava da na prozoru crtamo uz pomoc OpenGL komandi.

Kreiranje OpenGL context-a se razlikuje na Windows-u i Linux-u. Prvo cemo obraditi Windows klasu:

unit OGLWin32RenderWindow; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, Windows, Messages, gl; type   { TOGLWin32RenderWindow }   TOGLWin32RenderWindow = class(TSGERenderWindow)   private     FEngine: TSGEEngine;     FRenderer: TSGERenderer;     FWindow: HWND;     FDC: HDC;     FGLRC: HGLRC;     FWindowClass: TWndClassEx;     FAttributes: TStringList;   protected     function GetCaption: String; override;     procedure SetCaption(ACaption: String); override;     function GetLeft: Integer; override;     procedure SetLeft(ALeft: Integer); override;     function GetTop: Integer; override;     procedure SetTop(ATop: Integer); override;     function GetWidth: Integer; override;     procedure SetWidth(AWidth: Integer); override;     function GetHeight: Integer; override;     procedure SetHeight(AHeight: Integer); override;     function GetAttribute(AName: String): String; override;   public     constructor Create(AEngine: TSGEEngine; ARenderer: TSGERenderer;       ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24);     destructor Destroy; override;     procedure Resize(AWidth, AHeight: Integer); override;     procedure Move(ALeft, ATop: Integer); override;     procedure SetMetrics(ALeft, ATop, AWidth, AHeight: Integer); override;     procedure GetMetrics(var ALeft, ATop, AWidth, AHeight: Integer); override;     procedure SwapBuffers; override;     property Caption: String read GetCaption write SetCaption;     property Left: Integer read GetLeft write SetLeft;     property Top: Integer read GetTop write SetTop;     property Width: Integer read GetWidth write SetWidth;     property Height: Integer read GetHeight write SetHeight;     property Attribute[AName: String]: String read GetAttribute;   end; implementation function SGEWin32Proc(Wnd: HWnd; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; begin   case Msg of     WM_DESTROY:     begin       PostQuitMessage(0);       Result:= 0;     end   else     Result:= DefWindowProc(Wnd, Msg, wParam, lParam);   end; end; { TOGLWin32RenderWindow } function TOGLWin32RenderWindow.GetCaption: String; begin   SetLength(Result, GetWindowTextLength(FWindow));   GetWindowText(FWindow, PChar(Result), Length(Result)); end; procedure TOGLWin32RenderWindow.SetCaption(ACaption: String); begin   SetWindowText(FWindow, PChar(ACaption)); end; function TOGLWin32RenderWindow.GetLeft: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Left; end; procedure TOGLWin32RenderWindow.SetLeft(ALeft: Integer); begin   MoveWindow(FWindow, ALeft, Top, Width, Height, True); end; function TOGLWin32RenderWindow.GetTop: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Top; end; procedure TOGLWin32RenderWindow.SetTop(ATop: Integer); begin   MoveWindow(FWindow, Left, ATop, Width, Height, True); end; function TOGLWin32RenderWindow.GetWidth: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Right - Rect.Left; end; procedure TOGLWin32RenderWindow.SetWidth(AWidth: Integer); begin   MoveWindow(FWindow, Left, Top, AWidth, Height, True); end; function TOGLWin32RenderWindow.GetHeight: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Bottom - Rect.Top; end; procedure TOGLWin32RenderWindow.SetHeight(AHeight: Integer); begin   MoveWindow(FWindow, Left, Top, Width, AHeight, True); end; function TOGLWin32RenderWindow.GetAttribute(AName: String): String; begin   Result := FAttributes.Values[AName]; end; constructor TOGLWin32RenderWindow.Create(AEngine: TSGEEngine;   ARenderer: TSGERenderer;ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); var   Style: Cardinal;   Rect: TRect;   ScreenWidth, ScreenHeight, Width, Height: Integer;   PF: TPixelFormatDescriptor;   PFIndex: Integer; begin   FEngine := AEngine;   FRenderer := ARenderer;   FAttributes := TStringList.Create;   FillChar(FWindowClass, SizeOf(FWindowClass), 0);   FWindowClass.cbSize := SizeOf(FWindowClass);   FWindowClass.style := CS_HREDRAW or CS_VREDRAW or CS_OWNDC;   FWindowClass.lpfnWndProc := @SGEWin32Proc;   FWindowClass.hInstance := HInstance;   FWindowClass.hIcon := LoadIcon(0, IDI_APPLICATION);   FWindowClass.hCursor := LoadCursor(0, IDC_ARROW);   FWindowClass.hbrBackground := GetStockObject(NULL_BRUSH);   FWindowClass.lpszClassName := 'SGEWin32Window';   RegisterClassEx(FWindowClass);   Style := WS_VISIBLE or WS_CLIPCHILDREN or WS_OVERLAPPED or WS_BORDER or     WS_CAPTION or WS_SYSMENU or WS_MINIMIZEBOX or WS_MAXIMIZEBOX or WS_THICKFRAME;   Rect.Left := 0;   Rect.Top := 0;   Rect.Right := AWidth;   Rect.Bottom := AHeight;   AdjustWindowRectEx(Rect, Style, False, 0);   Width := Rect.Right - Rect.Left;   Height := Rect.Bottom - Rect.Top;   ScreenWidth := GetSystemMetrics(SM_CXSCREEN);   ScreenHeight := GetSystemMetrics(SM_CYSCREEN);   Rect.Left := (ScreenWidth - (Rect.Right - Rect.Left)) div 2;   Rect.Top := (ScreenHeight - (Rect.Bottom - Rect.Top)) div 2;   FWindow := CreateWindowEx(0, FWindowClass.lpszClassName, PChar(ACaption),     Style, Rect.Left, Rect.Top, Width, Height, 0, 0, HInstance, nil);   if FWindow = 0 then     raise Exception.Create('Can not create window');   FDC := GetDC(FWindow);   FillChar(PF, SizeOf(PF), 0);   PF.nSize := SizeOf(PF);   PF.nVersion := 1;   PF.dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;   PF.iPixelType := PFD_TYPE_RGBA;   PF.cColorBits := AColorDepth;   case AColorDepth of     16:     begin       PF.cRedBits := 5;       PF.cGreenBits := 5;       PF.cBlueBits := 5;     end;     24:     begin       PF.cRedBits := 8;       PF.cGreenBits := 8;       PF.cBlueBits := 8;     end;     32:     begin       PF.cRedBits := 8;       PF.cGreenBits := 8;       PF.cBlueBits := 8;       PF.cAlphaBits := 8;     end   end;   PF.iLayerType := PFD_MAIN_PLANE;   PFIndex := ChoosePixelFormat(FDC, @PF);   if PFIndex = 0 then     raise Exception.Create('Invalid pixel format');   if not SetPixelFormat(FDC, PFIndex, @PF) then     raise Exception.Create('Can not set pixel format');   FGLRC := wglCreateContext(FDC);   wglMakeCurrent(FDC, FGLRC);   glViewport(0, 0, AWidth, AHeight);   ShowWindow(FWindow, SW_SHOWDEFAULT);   UpdateWindow(FWindow);   FAttributes.Values['Window'] := IntToStr(FWindow);   FEngine.Logger.Log('Window created', ESGE_LL_INFO); end; destructor TOGLWin32RenderWindow.Destroy; begin   wglMakeCurrent(0, 0);   wglDeleteContext(FGLRC);   ReleaseDC(FWindow, FDC);   DestroyWindow(FWindow);   UnregisterClass(FWindowClass.lpszClassName, HInstance);   FEngine.Logger.Log('Window destroyed', ESGE_LL_INFO);   FAttributes.Free;   inherited; end; procedure TOGLWin32RenderWindow.Resize(AWidth, AHeight: Integer); begin   MoveWindow(FWindow, Left, Top, AWidth, AHeight, True); end; procedure TOGLWin32RenderWindow.Move(ALeft, ATop: Integer); begin   MoveWindow(FWindow, ALeft, ATop, Width, Height, True); end; procedure TOGLWin32RenderWindow.SetMetrics(ALeft, ATop, AWidth, AHeight: Integer   ); begin   MoveWindow(FWindow, ALeft, ATop, AWidth, AHeight, True); end; procedure TOGLWin32RenderWindow.GetMetrics(var ALeft, ATop, AWidth,   AHeight: Integer); var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   ALeft := Rect.Left;   ATop := Rect.Top;   AWidth := Rect.Right - Rect.Left;   AHeight := Rect.Bottom - Rect.Top; end; procedure TOGLWin32RenderWindow.SwapBuffers; begin   Windows.SwapBuffers(FDC); end; end.

Razlike u odnosu na klasu iz prvog tutorijala pocinju u konstruktoru. Za razliko od prethodne verzije, ne koristimo isti stil neko nekoliko stilova koji kreiraju prozorce koje lepse radi sa OpenGL-om. Jos jedna razlika je koriscenje funckije AdjustWindowRectEx. Kad kreiramo prozor velicine 800x600, u tu velicinu su uracunate i ivice prozora i meni i title bar... zbog toga ne dobijemo prostor manji od 800x600 za crtanje. Funckija AdjustWindowRectEx popravi dimenzije na osnovu stilova tako da dobijemo prostor za crtanje koji smo trazili. Uz pomoc GetSystemMetrics funkcije dobijemo velicinu ekrana i na taj nacin mozemo prozor postaviti na sredinu ekrana.

Prozor je sada kreiran... vreme je za OpenGL context Smile Da bi mogli da ga kreiramo, moramo prvo dobiti Device context prozora. Device context se koristi za crtanje po prozoru uz pomoc GDI komandi koje se uglavnom koriste za crtanje aplikacija koje ste do sada pravili (prozori, dugmici, edit boxi i slicno). Za dobijanje device context-a se koristi funckija GetDC. Za kreiranje OpenGL context-a treba jos i da odlucimo koji format pixel-a cemo koristiti za crtanje po prozoru. Za to se koristi TPixelFormatDescriptor tip podatka. Preko tog podatka zatrazimo mogucnost da u prozoru mozmo da crtamo OpenGL komandama i da prozor ima 2 buffer-a za crtanje, jedan koji se vidi i jedan po kojem crtamo. Kad zavrsimo sa crtanje tada ta dva buffer-a zamenimo, na taj nacin ce se na ekranu prikazati ono sto smo nacrtali, a mi cemo moci ponovo da krenemo sa crtanjem. Sledece sto postavljamo je kolicina bitova rezervisanih za svaku boju. Na taj nacin kontrolisemo koliko boja cemo moci da prikazemo. Funkcijom ChoosePixelFormat dobijamo indeks formata koji najvise odgovara onome koji smo zatrazili ili 0 ako takav nije pronadjen. SetPixelFormat funkcijom postavljamo zeljeni format na prozor i konacno smo spremni za kreiranje OpenGL contexta preko funkcije wglCreateContext. Pre nego sto pocnemo sa koriscenjem OpenGL komandi, moramo jos i da povezemo OpenGL context sa Device context-om prozora uz pomoc funkcije wglMakeCurrent. I sad smo spremni za crtanje.

Kada zatvaramo prozor, moramo osloboditi i OpenGL context. To radimo u destruktoru uz pomoc funkcije wglMakeCurrent koju koristimo da prekinemo vezu izmedju OpenGL context-a i Device contexta, i funkcije wglDeleteContext koje unistava OpenGL context.

Za razliku od verzije klasi iz prvog tutorijala, ova ima jos i funkciju SwapBuffers koja sluzi za prikaz buffer-a na kojem smo crtali. Implementacija je prilicno jednostavna... koristimo Win API funkciju SwapBuffers koja zameni buffer-e za zadati Device context.

To je to za Windows, sledeci put cemo napraviti Linux prozor.

Dopuna: 09 Avg 2010 22:49

Na Linux-u je rad sa OpenGL-om malo laksi, bar kad je kreiranje context-a u pitanju:

unit OGLX11RenderWindow; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, x, xlib, xutil, gl, glx; type   { TOGLX11RenderWindow }   TOGLX11RenderWindow = class(TSGERenderWindow)   private     FEngine: TSGEEngine;     FRenderer: TSGERenderer;     FDisplay: PDisplay;     FWindow: TWindow;     FGLC: GLXContext;     FAttributes: TStringList;   protected     function GetCaption: String; override;     procedure SetCaption(ACaption: String); override;     function GetLeft: Integer; override;     procedure SetLeft(ALeft: Integer); override;     function GetTop: Integer; override;     procedure SetTop(ATop: Integer); override;     function GetWidth: Integer; override;     procedure SetWidth(AWidth: Integer); override;     function GetHeight: Integer; override;     procedure SetHeight(AHeight: Integer); override;     function GetAttribute(AName: String): String; override;   public     constructor Create(AEngine: TSGEEngine; ARenderer: TSGERenderer;       ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24);     destructor Destroy; override;     procedure Resize(AWidth, AHeight: Integer); override;     procedure Move(ALeft, ATop: Integer); override;     procedure SetMetrics(ALeft, ATop, AWidth, AHeight: Integer); override;     procedure GetMetrics(var ALeft, ATop, AWidth, AHeight: Integer); override;     procedure SwapBuffers; override;     property Caption: String read GetCaption write SetCaption;     property Left: Integer read GetLeft write SetLeft;     property Top: Integer read GetTop write SetTop;     property Width: Integer read GetWidth write SetWidth;     property Height: Integer read GetHeight write SetHeight;     property Attribute[AName: String]: String read GetAttribute;   end; implementation { TOGLX11RenderWindow } function TOGLX11RenderWindow.GetCaption: String; var   Caption: PChar; begin   XFetchName(FDisplay, FWindow, @Caption);   Result := Caption;   XFree(Caption); end; procedure TOGLX11RenderWindow.SetCaption(ACaption: String); begin   XStoreName(FDisplay, FWindow, PChar(ACaption)); end; function TOGLX11RenderWindow.GetLeft: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.x; end; procedure TOGLX11RenderWindow.SetLeft(ALeft: Integer); begin   XMoveWindow(FDisplay, FWindow, ALeft, Top); end; function TOGLX11RenderWindow.GetTop: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.y; end; procedure TOGLX11RenderWindow.SetTop(ATop: Integer); begin   XMoveWindow(FDisplay, FWindow, Left, ATop); end; function TOGLX11RenderWindow.GetWidth: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.width; end; procedure TOGLX11RenderWindow.SetWidth(AWidth: Integer); begin   XResizeWindow(FDisplay, FWindow, AWidth, Height); end; function TOGLX11RenderWindow.GetHeight: Integer; var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   Result := WinAttr.height; end; procedure TOGLX11RenderWindow.SetHeight(AHeight: Integer); begin   XResizeWindow(FDisplay, FWindow, Width, AHeight); end; function TOGLX11RenderWindow.GetAttribute(AName: String): String; begin   Result := FAttributes.Values[AName]; end; constructor TOGLX11RenderWindow.Create(AEngine: TSGEEngine;   ARenderer: TSGERenderer; ACaption: String; AWidth: Integer; AHeight: Integer;   AColorDepth: Integer); const   Attrs: array[0..10] of Integer = (GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8,     GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, None); var   Root: TWindow;   VI: PXVisualInfo;   CMap: TColormap;   WinAttr: TXSetWindowAttributes;   WMDelete: TAtom; begin   FEngine := AEngine;   FRenderer := ARenderer;   FAttributes := TStringList.Create;   FDisplay := XOpenDisplay(nil);   if FDisplay = nil then     raise Exception.Create('Can not open display');   Root := DefaultRootWindow(FDisplay);   case AColorDepth of     16:     begin       Attrs[3] := 5;       Attrs[5] := 5;       Attrs[7] := 5;       Attrs[9] := 0;     end;     24:     begin       Attrs[3] := 8;       Attrs[5] := 8;       Attrs[7] := 8;       Attrs[9] := 0;     end;     32:     begin       Attrs[3] := 8;       Attrs[5] := 8;       Attrs[7] := 8;       Attrs[9] := 8;     end;   end;   VI := glXChooseVisual(FDisplay, 0, @Attrs[0]);   if VI = nil then     raise Exception.Create('No appropriate visual found');   CMap := XCreateColormap(FDisplay, Root, VI^.visual, AllocNone);   FillChar(WinAttr, SizeOf(WinAttr), 0);   WinAttr.colormap := CMap;   FWindow := XCreateWindow(FDisplay, Root, 0, 0, AWidth, AHeight, 0, VI^.depth, InputOutput, VI^.visual, CWColormap, @WinAttr);   XStoreName(FDisplay, FWindow, PChar(ACaption));   WMDelete := XInternAtom(FDisplay, 'WM_DELETE_WINDOW', True);   XSetWMProtocols(FDisplay, FWindow, @WMDelete, 1);   FGLC := glXCreateContext(FDisplay, VI, nil, True);   glXMakeCurrent(FDisplay, FWindow, FGLC);   glViewport(0, 0, AWidth, AHeight);   XMapWindow(FDisplay, FWindow);   FAttributes.Values['Display'] := IntToStr(Cardinal(FDisplay));   FAttributes.Values['Window'] := IntToStr(FWindow);   FEngine.Logger.Log('Window created', ESGE_LL_INFO); end; destructor TOGLX11RenderWindow.Destroy; begin   glXMakeCurrent(FDisplay, None, nil);   glXDestroyContext(FDisplay, FGLC);   XDestroyWindow(FDisplay, FWindow);   XCloseDisplay(FDisplay);   FEngine.Logger.Log('Window destroyed', ESGE_LL_INFO);   FAttributes.Free;   inherited; end; procedure TOGLX11RenderWindow.Resize(AWidth, AHeight: Integer); begin   XResizeWindow(FDisplay, FWindow, AWidth, AHeight); end; procedure TOGLX11RenderWindow.Move(ALeft, ATop: Integer); begin   XMoveWindow(FDisplay, FWindow, ALeft, ATop); end; procedure TOGLX11RenderWindow.SetMetrics(ALeft, ATop, AWidth, AHeight: Integer   ); begin   XMoveResizeWindow(FDisplay, FWindow, ALeft, ATop, AWidth, AHeight); end; procedure TOGLX11RenderWindow.GetMetrics(var ALeft, ATop, AWidth,   AHeight: Integer); var   WinAttr: TXWindowAttributes; begin   XGetWindowAttributes(FDisplay, FWindow, @WinAttr);   ALeft := WinAttr.x;   ATop := WinAttr.y;   AWidth := WinAttr.width;   AHeight := WinAttr.height; end; procedure TOGLX11RenderWindow.SwapBuffers; begin   glXSwapBuffers(FDisplay, FWindow); end; end.

Razlika je opet u konstruktoru, destruktori i novoj funkciji SwapBuffers. U konstruktoru radimo nesto slicno kao i u Windows verziji. Niz Attrs sadrzi nekoliko konstanti i par vrednosti koje se menjaju. U sustini, prve dve konstante kazu da zelimo da koristimo RGBA boje, a ne paletu, i da zelimo da imamo 2 buffer-a. Slede konstante koje oznacavaju posebne boje i koliko bitova zelimo da rezervisemo za njih, zadnji parametar mora biti None. Taj niz smo kreirali da bi mogli da pozovemo funkciju glXChooseVisual koja nam vrati najblizi format onom kojeg smo zahtevali preko Attrs niza. Na osnovu formata kreiramo Colormap koji definise kojim bojama mozemo crtati po prozoru. Za razliku od verzije iz prvog tutorijala, ovde za kreiranje prozora koristimo funkciju XCreateWindow koja ima malo vise parametara i omogucava nam finiju kontrolu nad kreiranjem prozora. Preko parametara oderdjujemo na kom displeju se prozor mora kreirati, koji prozor mu je parent, koordinate i velicinu (za koordinate stavljamo 0, 0 i pustamo WM da odredi gde ce se prozor kreirati) i postavljamo format koji cemo koristiti za crtanje. Posle kreiranja prozora, mozemo kreirati i OpenGL context funkcijom glXCreateContext, i zatim ga povezati s prozorom uz pomoc funkcije glXMakeCurrent... i mozemo da crtamo Very Happy

Destruktor je jednostavan kao kod Windows klase. Funkcijom glXMakeCurrent prekidamo vezu izmedju OpenGL context-a i prozora, i zatim brisemo context funkcijom glXDestroyContext.

Za zamenu buffer-a se koristi funkcija glXSwapBuffers... hmmm... tu nema vise sta da se kaze Very Happy

Sad imamo obe klase za prozor, sledece je renderer klasa koja ce kreirati prozor i omoguciti dostup do funkcija za crtanje.

Dopuna: 11 Avg 2010 21:27

Trenutno renderer klasa nece imati Bog zna sta... kreirace prozor, moci ce da zapocne crtanje, da obrise prozor i da zavrsi crtanje... u sustini, jedino sto ce imati veze sa OpenGL-om je samo brisanje prozora:

unit OGLRenderer; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, gl; type   { TOGLRenderer }   TOGLRenderer = class(TSGERenderer)   private     FEngine: TSGEEngine;     FName: String;     FInitialized: Boolean;     FRenderWindow: TSGERenderWindow;   protected     function GetName: String; override;     function GetRenderWindow: TSGERenderWindow; override;   public     constructor Create(AEngine: TSGEEngine);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     property Name: String read GetName;     property RenderWindow: TSGERenderWindow read GetRenderWindow;   end; implementation uses   {$IFDEF SGE_Windows}   OGLWin32RenderWindow;   {$ELSE}   OGLX11RenderWindow;   {$ENDIF} { TOGLRenderer } function TOGLRenderer.GetName: String; begin   Result := FName; end; function TOGLRenderer.GetRenderWindow: TSGERenderWindow; begin   Result := FRenderWindow; end; constructor TOGLRenderer.Create(AEngine: TSGEEngine); begin   FEngine := AEngine;   FName := 'OpenGL Renderer';   FInitialized := False;   FEngine.Logger.Log(FName + ' created', ESGE_LL_ENGINE); end; destructor TOGLRenderer.Destroy; begin   Finalize;   FEngine.UnregisterRenderer(Self);   FEngine.Logger.Log(FName + ' destroyed', ESGE_LL_ENGINE);   inherited; end; procedure TOGLRenderer.Initialize; begin   if FInitialized then     Exit;   FInitialized := True;   FRenderWindow := nil;   FEngine.Logger.Log(FName + ' initialized', ESGE_LL_INFO); end; procedure TOGLRenderer.Finalize; begin   if not FInitialized then     Exit;   FRenderWindow.Free;   FInitialized := False;   FEngine.Logger.Log(FName + ' finalized', ESGE_LL_INFO); end; procedure TOGLRenderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   {$IFDEF SGE_Debug}   Assert(FRenderWindow = nil, 'Render window already exists');   {$ENDIF}   if FRenderWindow = nil then   begin     {$IFDEF SGE_Windows}     FRenderWindow := TOGLWin32RenderWindow.Create(FEngine, Self, ACaption, AWidth, AHeight);     {$ELSE}     FRenderWindow := TOGLX11RenderWindow.Create(FEngine, Self, ACaption, AWidth, AHeight);     {$ENDIF}   end; end; procedure TOGLRenderer.BeginFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit; end; procedure TOGLRenderer.EndFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit; end; procedure TOGLRenderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); var   Mask: Cardinal; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   Mask := 0;   if ESGE_FBT_COLOR in AFrameBuffers then   begin     glClearColor(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha);     Mask := Mask or GL_COLOR_BUFFER_BIT;   end;   if ESGE_FBT_DEPTH in AFrameBuffers then   begin     glClearDepth(ADepth);     Mask := Mask or GL_DEPTH_BUFFER_BIT;   end;   if ESGE_FBT_STENCIL in AFrameBuffers then   begin     glClearStencil(AStencil);     Mask := Mask or GL_STENCIL_BUFFER_BIT;   end;   glClear(Mask); end; procedure TOGLRenderer.SwapBuffers; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   FRenderWindow.SwapBuffers; end; end.

U konstruktoru i destruktoru se ne dogadja nista posebno. Konstruktor priprema podatke za pocetak, dok se u destruktoru sve oslobadja i renderer se odregistruje iz engine-a. Funkcije Initialize i Finalize poziva engine kada se engine sprema za pocetak ili kraj koriscenja renderer-a. CreateWindow funkcija na osnovu OS-a odredjuje da li treba da kreira Windows ili Linux prozor i tada je sve spremno za crtanje.

BeginFrame i EndFrame za sada ne rade nista jer OpenGL ne zahteva nikakve specijalne postupke u slucaju pocetka i zavrsavanja crtanja. SwapBuffers samo zove istu funkciju koja je implementirana u klasi za prozor. U Clear funkciji cemo koristiti par OpenGL funkcija. Kao parametre za funkciju dajemo tipove buffer-a koje zelimo da obrisemo (color, depth i/ili stencil), i vrednosti kojim zelimo da izbrisemo te buffer-e. Na osnovu nasih tipova kreiramo OpenGL vrednosti i preko glClear* funkcija postavljamo vrednosti za brisanje. Na kraju zovemo glClear i brisemo buffer-e.

Imamo engine, renderer i prozor, sad mozemo malo da prepravimo glavni program i da prikazemo prozor ciji ce sadrzaj biti izbrisan OpenGL funkcijama:

program HelloWorld; {$I SGE.inc} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas',   HTMLLogger in 'HTMLLogger.pas',   OGLRenderer in '..\..\Source\Renderers\OGLRenderer\OGLRenderer.pas'; var   Engine: TSGEEngine = nil;   HTML: THTMLLogger = nil;   OGLRendr: TOGLRenderer = nil; begin   try     try       HTML := THTMLLogger.Create('SGE.html');       Engine := CreateSGEEngine('', HTML.Log);       Engine.Logger.LogLevel := ESGE_LL_INFO;       OGLRendr := TOGLRenderer.Create(Engine);       Engine.RegisterRenderer(OGLRendr);       Engine.Initialize(Engine.Renderers[0]);       Engine.Renderer.CreateWindow;       while Engine.ProcessMessages do       begin         Engine.Renderer.BeginFrame;         Engine.Renderer.Clear([ESGE_FBT_COLOR], SGEColor(0.4, 0.4, 1, 0));         Engine.Renderer.EndFrame;         Engine.Renderer.SwapBuffers;       end;       Engine.Finalize;     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     OGLRendr.Free;     Engine.Free;     HTML.Free;   end; end.

Kao sto se vidi iz koda, kreiramo OpenGL renderer, registrujemo ga i zatim inicijalizujemo engine. Sad smo spremni da kreiramo prozor i udjemo u glavnu petlju u kojoj brisemo prozor plavicastom bojom.

Oni koji su pogledali kod, videli su da se u Renderers folderu nalazi i DX9Renderer... to je sledece sto cemo da uradimo... implementiracemo renderer i prozor za Direct3D9, a njihovo koriscenje ce biti potpuno isto kao sto je koriscenje renderera i prozora za OpenGL Smile

https://www.mycity.rs/must-login.png

Dopuna: 12 Avg 2010 18:32

DirectX renderer izgleda skoro isto kao i OpenGL renderer... u celom kodu je samo par promena koje cemo prokomentarisati:

unit DX9Renderer; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, Direct3D9; type   { TDX9Renderer }   TDX9Renderer = class(TSGERenderer)   private     FEngine: TSGEEngine;     FName: String;     FInitialized: Boolean;     FD3D: IDirect3D9;     FRenderWindow: TSGERenderWindow;   protected     function GetName: String; override;     function GetRenderWindow: TSGERenderWindow; override;   public     constructor Create(AEngine: TSGEEngine);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     function SGEColorToDXColor(AColor: TSGEColor): TD3DColor;     property Name: String read GetName;     property RenderWindow: TSGERenderWindow read GetRenderWindow;   end; implementation uses   DX9Win32RenderWindow; { TDX9Renderer } function TDX9Renderer.GetName: String; begin   Result := FName; end; function TDX9Renderer.GetRenderWindow: TSGERenderWindow; begin   Result := FRenderWindow; end; constructor TDX9Renderer.Create(AEngine: TSGEEngine); begin   FEngine := AEngine;   FName := 'Direct3D9 Renderer';   FInitialized := False;   FEngine.Logger.Log(FName + ' created', ESGE_LL_ENGINE); end; destructor TDX9Renderer.Destroy; begin   Finalize;   FEngine.UnregisterRenderer(Self);   FEngine.Logger.Log(FName + ' destroyed', ESGE_LL_ENGINE);   inherited; end; procedure TDX9Renderer.Initialize; begin   if FInitialized then     Exit;   FD3D := Direct3DCreate9(D3D_SDK_VERSION);   if FD3D = nil then     raise Exception.Create('Can not create Direct3D');   FInitialized := True;   FRenderWindow := nil;   FEngine.Logger.Log(FName + ' initialized', ESGE_LL_ENGINE); end; procedure TDX9Renderer.Finalize; begin   if not FInitialized then     Exit;   FRenderWindow.Free;   FD3D := nil;   FInitialized := False;   FEngine.Logger.Log(FName + ' finalized', ESGE_LL_ENGINE); end; procedure TDX9Renderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   {$IFDEF SGE_Debug}   Assert(FRenderWindow = nil, 'Render window already exists');   {$ENDIF}   if FRenderWindow = nil then     FRenderWindow := TDX9Win32RenderWindow.Create(FEngine, Self, FD3D, ACaption, AWidth, AHeight); end; procedure TDX9Renderer.BeginFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   GetDevice.BeginScene; end; procedure TDX9Renderer.EndFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   GetDevice.EndScene; end; procedure TDX9Renderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); var   Flags: Cardinal; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   Flags := 0;   if ESGE_FBT_COLOR in AFrameBuffers then     Flags := Flags or D3DCLEAR_TARGET;   if ESGE_FBT_DEPTH in AFrameBuffers then     Flags := Flags or D3DCLEAR_ZBUFFER;   if ESGE_FBT_STENCIL in AFrameBuffers then     Flags := Flags or D3DCLEAR_STENCIL;   GetDevice.Clear(0, nil, Flags, SGEColorToDXColor(AColor), ADepth, AStencil); end; procedure TDX9Renderer.SwapBuffers; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   FRenderWindow.SwapBuffers; end; function TDX9Renderer.SGEColorToDXColor(AColor: TSGEColor): TD3DColor; begin   Result := D3DCOLOR_COLORVALUE(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha); end; end.

U Initialize funkciji kreiramo Direct3D objekat koji cemo koristiti za inicijalizaciju prozora. Ta klasa nam omogucava da kreiramo sve ostale Direct3D objekte. U Finalize funkciji, taj isti objekat oslobadjamo (posto koristimo interfejs za cuvanje tog objekta, dovoljno je da promenljivu postavimo na nil, a Delphi/FPC ce sam pozvati destruktor).

CreateWindow kreira prozor, iz kojeg cemo u funkcijama BeginFrame, EndFrame i Clear uzimati Direct3D device objekat, koji sluzi za crtanje po prozoru. Direct3D device objekat se kreira uz pomoc Direct3D objekta i zato ga prosledjujemo prozoru prilikom kreiranja.

BeginFrame i EndFrame za razliku od OpenGL verzije koja ne radi nista, moraju da pozovu BeginScene i EndScene da bi nam omogucili crtanje. Clear funkcija radi potpuno isto kao i u OpenGL verziji, jedino ovde koristi DirectX konstante i funkcije. Kod SwapBuffers funkcije nema sta da se kaze jer je potpuno ista kao i u OpenGL-u.

Sad na klasu za prozor koja ce takodje liciti na klasu iz OpenGL-a:

unit DX9Win32RenderWindow; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, Windows, Messages, Direct3D9; type   { TDX9Win32RenderWindow }   TDX9Win32RenderWindow = class(TSGERenderWindow)   private     FEngine: TSGEEngine;     FRenderer: TSGERenderer;     FWindow: HWND;     FWindowClass: TWndClassEx;     FD3DDevice: IDirect3DDevice9;     FD3DPresentParams: TD3DPresentParameters;     FAttributes: TStringList;   protected     function GetCaption: String; override;     procedure SetCaption(ACaption: String); override;     function GetLeft: Integer; override;     procedure SetLeft(ALeft: Integer); override;     function GetTop: Integer; override;     procedure SetTop(ATop: Integer); override;     function GetWidth: Integer; override;     procedure SetWidth(AWidth: Integer); override;     function GetHeight: Integer; override;     procedure SetHeight(AHeight: Integer); override;     function GetAttribute(AName: String): String; override;   public     constructor Create(AEngine: TSGEEngine; ARenderer: TSGERenderer;       AD3D: IDirect3D9; ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24);     destructor Destroy; override;     procedure Resize(AWidth, AHeight: Integer); override;     procedure Move(ALeft, ATop: Integer); override;     procedure SetMetrics(ALeft, ATop, AWidth, AHeight: Integer); override;     procedure GetMetrics(var ALeft, ATop, AWidth, AHeight: Integer); override;     procedure SwapBuffers; override;     property Caption: String read GetCaption write SetCaption;     property Left: Integer read GetLeft write SetLeft;     property Top: Integer read GetTop write SetTop;     property Width: Integer read GetWidth write SetWidth;     property Height: Integer read GetHeight write SetHeight;     property Attribute[AName: String]: String read GetAttribute;   end; implementation function SGEWin32Proc(Wnd: HWnd; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; begin   case Msg of     WM_DESTROY:     begin       PostQuitMessage(0);       Result:= 0;     end   else     Result:= DefWindowProc(Wnd, Msg, wParam, lParam);   end; end; { TDX9Win32RenderWindow } function TDX9Win32RenderWindow.GetCaption: String; begin   SetLength(Result, GetWindowTextLength(FWindow));   GetWindowText(FWindow, PChar(Result), Length(Result)); end; procedure TDX9Win32RenderWindow.SetCaption(ACaption: String); begin   SetWindowText(FWindow, PChar(ACaption)); end; function TDX9Win32RenderWindow.GetLeft: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Left; end; procedure TDX9Win32RenderWindow.SetLeft(ALeft: Integer); begin   MoveWindow(FWindow, ALeft, Top, Width, Height, True); end; function TDX9Win32RenderWindow.GetTop: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Top; end; procedure TDX9Win32RenderWindow.SetTop(ATop: Integer); begin   MoveWindow(FWindow, Left, ATop, Width, Height, True); end; function TDX9Win32RenderWindow.GetWidth: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Right - Rect.Left; end; procedure TDX9Win32RenderWindow.SetWidth(AWidth: Integer); begin   MoveWindow(FWindow, Left, Top, AWidth, Height, True); end; function TDX9Win32RenderWindow.GetHeight: Integer; var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   Result := Rect.Bottom - Rect.Top; end; procedure TDX9Win32RenderWindow.SetHeight(AHeight: Integer); begin   MoveWindow(FWindow, Left, Top, Width, AHeight, True); end; function TDX9Win32RenderWindow.GetAttribute(AName: String): String; begin   Result := FAttributes.Values[AName]; end; constructor TDX9Win32RenderWindow.Create(AEngine: TSGEEngine;   ARenderer: TSGERenderer; AD3D: IDirect3D9; ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); var   Style: Cardinal;   Rect: TRect;   ScreenWidth, ScreenHeight, Width, Height: Integer; begin   FEngine := AEngine;   FRenderer := ARenderer;   FAttributes := TStringList.Create;   FillChar(FWindowClass, SizeOf(FWindowClass), 0);   FWindowClass.cbSize := SizeOf(FWindowClass);   FWindowClass.style := CS_HREDRAW or CS_VREDRAW or CS_OWNDC;   FWindowClass.lpfnWndProc := @SGEWin32Proc;   FWindowClass.hInstance := HInstance;   FWindowClass.hIcon := LoadIcon(0, IDI_APPLICATION);   FWindowClass.hCursor := LoadCursor(0, IDC_ARROW);   FWindowClass.hbrBackground := GetStockObject(NULL_BRUSH);   FWindowClass.lpszClassName := 'SGEWin32Window';   RegisterClassEx(FWindowClass);   Style := WS_VISIBLE or WS_CLIPCHILDREN or WS_OVERLAPPED or WS_BORDER or     WS_CAPTION or WS_SYSMENU or WS_MINIMIZEBOX or WS_MAXIMIZEBOX or WS_THICKFRAME;   Rect.Left := 0;   Rect.Top := 0;   Rect.Right := AWidth;   Rect.Bottom := AHeight;   AdjustWindowRectEx(Rect, Style, False, 0);   Width := Rect.Right - Rect.Left;   Height := Rect.Bottom - Rect.Top;   ScreenWidth := GetSystemMetrics(SM_CXSCREEN);   ScreenHeight := GetSystemMetrics(SM_CYSCREEN);   Rect.Left := (ScreenWidth - (Rect.Right - Rect.Left)) div 2;   Rect.Top := (ScreenHeight - (Rect.Bottom - Rect.Top)) div 2;   FWindow := CreateWindowEx(0, FWindowClass.lpszClassName, PChar(ACaption),     Style, Rect.Left, Rect.Top, Width, Height, 0, 0, HInstance, nil);   if FWindow = 0 then     raise Exception.Create('Can not create window');   FillChar(FD3DPresentParams, SizeOf(FD3DPresentParams), 0);   case AColorDepth of     16: FD3DPresentParams.BackBufferFormat := D3DFMT_X1R5G5B5;     24: FD3DPresentParams.BackBufferFormat := D3DFMT_X8R8G8B8;     32: FD3DPresentParams.BackBufferFormat := D3DFMT_A8R8G8B8;   end;   FD3DPresentParams.SwapEffect := D3DSWAPEFFECT_DISCARD;   FD3DPresentParams.hDeviceWindow := FWindow;   FD3DPresentParams.Windowed := True;   if AD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, FWindow,     D3DCREATE_HARDWARE_VERTEXPROCESSING, @FD3DPresentParams, FD3DDevice) <> D3D_OK then     raise Exception.Create('Can not create Direct3D device');   ShowWindow(FWindow, SW_SHOWDEFAULT);   UpdateWindow(FWindow);   FAttributes.Values['Window'] := IntToStr(FWindow);   FAttributes.Values['D3DDevice'] := IntToStr(Integer(FD3DDevice));   FEngine.Logger.Log('Window created', ESGE_LL_INFO); end; destructor TDX9Win32RenderWindow.Destroy; begin   FD3DDevice := nil;   DestroyWindow(FWindow);   UnregisterClass(FWindowClass.lpszClassName, HInstance);   FEngine.Logger.Log('Window destroyed', ESGE_LL_INFO);   FAttributes.Free;   inherited; end; procedure TDX9Win32RenderWindow.Resize(AWidth, AHeight: Integer); begin   MoveWindow(FWindow, Left, Top, AWidth, AHeight, True); end; procedure TDX9Win32RenderWindow.Move(ALeft, ATop: Integer); begin   MoveWindow(FWindow, ALeft, ATop, Width, Height, True); end; procedure TDX9Win32RenderWindow.SetMetrics(ALeft, ATop, AWidth, AHeight: Integer   ); begin   MoveWindow(FWindow, ALeft, ATop, AWidth, AHeight, True); end; procedure TDX9Win32RenderWindow.GetMetrics(var ALeft, ATop, AWidth,   AHeight: Integer); var   Rect: TRect; begin   GetWindowRect(FWindow, Rect);   ALeft := Rect.Left;   ATop := Rect.Top;   AWidth := Rect.Right - Rect.Left;   AHeight := Rect.Bottom - Rect.Top; end; procedure TDX9Win32RenderWindow.SwapBuffers; begin   if FD3DDevice.Present(nil, nil, 0, nil) = D3DERR_DEVICELOST then     if FD3DDevice.TestCooperativeLevel = D3DERR_DEVICENOTRESET then       FD3DDevice.Reset(FD3DPresentParams); end; end.

Jedine razlike su u konstruktoru, destruktoru i SwapBuffers funkcijama.

Deo konstruktora do kreiranja prozora je isti kao i u OpenGL-u. Posle toga popunjavamo promenljivu tipa TD3DPresentParameters podacima o tome kako zelimo da se Direct3D device ponasa. Vecinu podataka ostavimo na standardnoj vrednost, definisemo format buffer-a po kojem cemo crtati, nacin na koji se buffer-i menjaju i kazemo da ne zelimo full screen prozor (full screen cemo kasnije obraditi). Posle toga zovemo funkciju CreateDevice kojoj kazemo da zelimo da dobijemo device koji ima hardversko ubrzanje, i koji vrsi hardversku obradu pixela. Ako je funkcija uspela, spremni smo za crtanje.

Destruktor je prilicno jednostavan... pre nego sto unistimo prozor, moramo samo osloboditi device koji smo kreirali i nas posao je time zavrsen Smile

SwapBuffers je malo komplikovaniji u odnosu na OpenGL verziju. Direct3D device u odredjenim slucajevima moze da izgubi pristup do memorije koju je rezervisao na grafickoj kartici i tada ga vise ne mozemo koristiti za crtanje sve dok ne pozovemo njegovu funkciju Reset. Prilikom prikazivanja buffer-a u kojeg smo nacrtali sta smo zeleli mozemo dobiti gresku D3DERR_DEVICELOST koja znaci da moramo resetovati device. Reset nece uspeti uvek i zato koristimo funkciju TestCooperativeLevel i ako je njen rezultat D3DERR_DEVICENOTRESET, znaci da mozemo da pozovemo Reset. Najcesci razlog koji zahteva resetovanje je kada full screen prozor izgubi fokus, i tada je resetovanje moguce tek kada prozor ponovo dobije fokus.

Sada cemo popraviti i glavnu aplikaciju da na Windows-u iskoristi DirectX renderer:

program HelloWorld; {$I SGE.inc} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas',   HTMLLogger in 'HTMLLogger.pas',   {$IFDEF SGE_Windows}   DX9Renderer in '..\..\Source\Renderers\DX9Renderer\DX9Renderer.pas',   {$ENDIF}   OGLRenderer in '..\..\Source\Renderers\OGLRenderer\OGLRenderer.pas'; var   Engine: TSGEEngine = nil;   HTML: THTMLLogger = nil;   {$IFDEF SGE_Windows}   DX9Rendr: TDX9Renderer = nil;   {$ENDIF}   OGLRendr: TOGLRenderer = nil; begin   try     try       HTML := THTMLLogger.Create('SGE.html');       Engine := CreateSGEEngine('', HTML.Log);       Engine.Logger.LogLevel := ESGE_LL_INFO;       {$IFDEF SGE_Windows}       DX9Rendr := TDX9Renderer.Create(Engine);       Engine.RegisterRenderer(DX9Rendr);       {$ENDIF}       OGLRendr := TOGLRenderer.Create(Engine);       Engine.RegisterRenderer(OGLRendr);       Engine.Initialize(Engine.Renderers[0]);       Engine.Renderer.CreateWindow;       while Engine.ProcessMessages do       begin         Engine.Renderer.BeginFrame;         Engine.Renderer.Clear([ESGE_FBT_COLOR], SGEColor(0.4, 0.4, 1, 0));         Engine.Renderer.EndFrame;         Engine.Renderer.SwapBuffers;       end;       Engine.Finalize;     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     {$IFDEF SGE_Windows}     DX9Rendr.Free;     {$ENDIF}     OGLRendr.Free;     Engine.Free;     HTML.Free;   end; end.

Posto se DirectX renderer registruje prvi na Windows-u, inicijalizacija engine-a ce uzeti njega kao parametar.

I, eto, u glavnom programu koristimo DirectX na isti nacin na koji koristimo OpenGL zahvaljujuci engine-u kojeg smo napisali. Jeste da za sada ne radi bog zna sta, ali u sustini vidimo da je moguce napisati tako nesto. Sledece sto cemo obraditi su jos neki tipovi i funkcije koje ce nam omoguciti da crtamo jednostavne oblike.

Dok cekate da stigne sledeci tutorijal, neko bi mogao da popravi programce tako da u konzoli ispise nazive renderera, da omoguci izbor i da u imenu prozora ispise i ime aktivnog renderera... recimo da izgleda nekako ovako:

Registered renderers:   1. Ime prvog renderera   2. Ime drugog renderera   ...   N. Ime n-tog renderera Select renderer:
Tu bi korisnik ukucao broj renderera koji zeli da iskoristi i ako je broj pravilno unesen, inicijalizuje se engine, i pri kreiranju prozora bi ime dali recimo ovako:

SGE using + Ime izabranog renderera

Da vidimo da li ste nesto naucili do sada Very Happy

Hint: da bi ukljucili konzolni mod u Delphi-ju treba dodati {$APPTYPE CONSOLE}, dok u Lazarusu u compiler Options treba iskljuciti Win32 gui application.

https://www.mycity.rs/must-login.png

Dopuna: 13 Avg 2010 22:57

Niko ne napravi tu aplikacijicu Smile Sta da se radi... koriste se osnovne komande za prikaz i citanje podatak pa nema neceg posebnog da se objasnjava... evo koda:

program HelloWorld; {$I SGE.inc} {$IFDEF SGE_Delphi} {$APPTYPE CONSOLE} {$ENDIF} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas',   HTMLLogger in 'HTMLLogger.pas',   {$IFDEF SGE_Windows}   DX9Renderer in '..\..\Source\Renderers\DX9Renderer\DX9Renderer.pas',   {$ENDIF}   OGLRenderer in '..\..\Source\Renderers\OGLRenderer\OGLRenderer.pas'; var   Engine: TSGEEngine = nil;   HTML: THTMLLogger = nil;   {$IFDEF SGE_Windows}   DX9Rendr: TDX9Renderer = nil;   {$ENDIF}   OGLRendr: TOGLRenderer = nil;   RendrIndex: String;   I: Integer; begin   try     try       HTML := THTMLLogger.Create('SGE.html');       Engine := CreateSGEEngine('', HTML.Log);       Engine.Logger.LogLevel := ESGE_LL_INFO;       {$IFDEF SGE_Windows}       DX9Rendr := TDX9Renderer.Create(Engine);       Engine.RegisterRenderer(DX9Rendr);       {$ENDIF}       OGLRendr := TOGLRenderer.Create(Engine);       Engine.RegisterRenderer(OGLRendr);       WriteLn('Registered renderers:');       for I := 0 to Engine.RendererCount - 1 do         WriteLn(Format('  %d. %s', [I + 1, Engine.Renderers[I].Name]));       WriteLn;       Write('Select renderer: ');       ReadLn(RendrIndex);       I := StrToIntDef(RendrIndex, 0) - 1;       if (I < 0) or (I >= Engine.RendererCount) then         Exit;       Engine.Initialize(Engine.Renderers[I]);       Engine.Renderer.CreateWindow('SGE using ' + Engine.Renderers[I].Name);       while Engine.ProcessMessages do       begin         Engine.Renderer.BeginFrame;         Engine.Renderer.Clear([ESGE_FBT_COLOR], SGEColor(0.4, 0.4, 1, 0));         Engine.Renderer.EndFrame;         Engine.Renderer.SwapBuffers;       end;       Engine.Finalize;     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     {$IFDEF SGE_Windows}     DX9Rendr.Free;     {$ENDIF}     OGLRendr.Free;     Engine.Free;     HTML.Free;   end; end.

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Napisano: 15 Avg 2010 2:47

Za sada od pomocnih tipova cemo napraviti matrice i vektore. Matrice i vektori se koriste za skoro sve u vezi sa crtanjem u OpenGL-u i DirectX-u. Necu objasnjavati sta su matrice i vektori, i kako se koriste jer se to uci u skoli, ali ako nekog zanima, moze procictati nesto o njima ovde:
Matrice: http://en.wikipedia.org/wiki/Matrix_(mathematics)
Vektori: http://en.wikipedia.org/wiki/Vector_(mathematics_and_physics)

Za pocetak cemo napisati samo par funkcija... par za matrice (projekcije, transofrmacije, mnozenje) i par za vektore (racunanje duzine, normalizacija, mnozenje). Kako budemo napredovali, dodavacemo nove ili menjati postojece funckije. Ove funkcije su cista matematika tako da bi trebalo da ih razumeju skoro svi koji su u srednjoj skoli dobro naucili matematiku Smile

type   TSGEMatrix = packed record   case Integer of     1: (M11, M12, M13, M14: Single;         M21, M22, M23, M24: Single;         M31, M32, M33, M34: Single;         M41, M42, M43, M44: Single;);     2: (M: array [0..15] of Single;);   end;   TSGEVector4 = packed record   case Integer of     1: (X, Y, Z, W: Single;);     2: (V: array [0..3] of Single;);   end;   TSGEVector3 = packed record   case Integer of     1: (X, Y, Z: Single;);     2: (V: array [0..2] of Single;);   end;   TSGEVector2 = packed record   case Integer of     1: (X, Y: Single;);     2: (V: array [0..1] of Single;);   end; function SGEMatrixIdentity: TSGEMatrix; begin   Result.M11 := 1;   Result.M12 := 0;   Result.M13 := 0;   Result.M14 := 0;   Result.M21 := 0;   Result.M22 := 1;   Result.M23 := 0;   Result.M24 := 0;   Result.M31 := 0;   Result.M32 := 0;   Result.M33 := 1;   Result.M34 := 0;   Result.M41 := 0;   Result.M42 := 0;   Result.M43 := 0;   Result.M44 := 1; end; function SGEMatrixFrustum(ALeft, ARight, ABottom, ATop, ANear, AFar: Single   ): TSGEMatrix; var   InvW, InvH, InvD: Single; begin   InvW := 1 / (ARight - ALeft);   InvH := 1 / (ATop - ABottom);   InvD := 1 / (AFar - ANear);   Result.M11 := (2 * ANear) * InvW;   Result.M12 := 0;   Result.M13 := 0;   Result.M14 := 0;   Result.M21 := 0;   Result.M22 := (2 * ANear) * InvH;   Result.M23 := 0;   Result.M24 := 0;   Result.M31 := (ARight + ALeft) * InvW;   Result.M32 := (ATop + ABottom) * InvH;   Result.M33 := -((AFar + ANear) * InvD);   Result.M34 := -1;   Result.M41 := 0;   Result.M42 := 0;   Result.M43 := -((2 * AFar * ANear) * InvD);   Result.M44 := 0; end; function SGEMatrixPerspective(AFOV, AAspect, ANear, AFar: Single): TSGEMatrix; var   Left, Right, Bottom, Top: Single; begin   Top := ANear * Tan(AFOV * PI / 360);   Bottom := -Top;   Left := Bottom * AAspect;   Right := -Left;   Result := SGEMatrixFrustum(Left, Right, Bottom, Top, ANear, AFar); end; function SGEMatrixOrthographic(ALeft, ARight, ABottom, ATop, ANear, AFar: Single   ): TSGEMatrix; var   InvW, InvH, InvD: Single; begin   InvW := 1 / (ARight - ALeft);   InvH := 1 / (ATop - ABottom);   InvD := 1 / (AFar - ANear);   Result.M11 := 2 * InvW;   Result.M12 := 0;   Result.M13 := 0;   Result.M14 := 0;   Result.M21 := 0;   Result.M22 := 2 * InvH;   Result.M23 := 0;   Result.M24 := 0;   Result.M31 := 0;   Result.M32 := 0;   Result.M33 := -(2 * InvD);   Result.M34 := 0;   Result.M41 := -((ARight + ALeft) * InvW);   Result.M42 := -((ATop + ABottom) * InvH);   Result.M43 := -((AFar + ANear) * InvD);   Result.M44 := 1; end; function SGEMatrixLookAt(APosition, ATarget, AUp: TSGEVector3): TSGEMatrix; var   S, U, F: TSGEVector3; begin   F := SGEVector3Normalize(SGEVector3(ATarget.X - APosition.X,     ATarget.Y - APosition.Y, ATarget.Z - APosition.Z));   S := SGEVector3Normalize(SGEVector3CrossProduct(F, AUp));   U := SGEVector3CrossProduct(S, F);   Result.M11 := S.X;   Result.M12 := U.X;   Result.M13 := -F.X;   Result.M14 := 0;   Result.M21 := S.Y;   Result.M22 := U.Y;   Result.M23 := -F.Y;   Result.M24 := 0;   Result.M31 := S.Z;   Result.M32 := U.Z;   Result.M33 := -F.Z;   Result.M34 := 0;   Result.M41 := 0;   Result.M42 := 0;   Result.M43 := 0;   Result.M44 := 1;   Result := SGEMatrixMultiply(SGEMatrixTranslate(-APosition.X, -APosition.Y,     -APosition.Z), Result); end; function SGEMatrixTranslate(AX, AY, AZ: Single): TSGEMatrix; begin   Result.M11 := 1;   Result.M12 := 0;   Result.M13 := 0;   Result.M14 := 0;   Result.M21 := 0;   Result.M22 := 1;   Result.M23 := 0;   Result.M24 := 0;   Result.M31 := 0;   Result.M32 := 0;   Result.M33 := 1;   Result.M34 := 0;   Result.M41 := AX;   Result.M42 := AY;   Result.M43 := AZ;   Result.M44 := 1; end; function SGEMatrixScale(AX, AY, AZ: Single): TSGEMatrix; begin   Result.M11 := AX;   Result.M12 := 0;   Result.M13 := 0;   Result.M14 := 0;   Result.M21 := 0;   Result.M22 := AY;   Result.M23 := 0;   Result.M24 := 0;   Result.M31 := 0;   Result.M32 := 0;   Result.M33 := AZ;   Result.M34 := 0;   Result.M41 := 0;   Result.M42 := 0;   Result.M43 := 0;   Result.M44 := 1; end; function SGEMatrixRotate(AAngle, AX, AY, AZ: Single): TSGEMatrix; var   S, C, Rad: Single;   V: TSGEVector3; begin   Rad := PI * AAngle / 180;   S := Sin(Rad);   C := Cos(Rad);   V := SGEVector3Normalize(SGEVector3(AX, AY, AZ));   Result.M11 := V.X * V.X * (1 - C) + C;   Result.M12 := V.Y * V.X * (1 - C) + V.Z * S;   Result.M13 := V.Z * V.X * (1 - C) - V.Y * S;   Result.M14 := 0;   Result.M21 := V.X * V.Y * (1 - C) - V.Z * S;   Result.M22 := V.Y * V.Y * (1 - C) + C;   Result.M23 := V.Z * V.Y * (1 - C) + V.X * S;   Result.M24 := 0;   Result.M31 := V.X * V.Z * (1 - C) + V.Y * S;   Result.M32 := V.Y * V.Z * (1 - C) - V.X * S;   Result.M33 := V.Z * V.Z * (1 - C) + C;   Result.M34 := 0;   Result.M41 := 0;   Result.M42 := 0;   Result.M43 := 0;   Result.M44 := 1; end; function SGEMatrixMultiply(AM1, AM2: TSGEMatrix): TSGEMatrix; begin   Result.M11 := AM1.M11 * AM2.M11 + AM1.M12 * AM2.M21 + AM1.M13 * AM2.M31 + AM1.M14 * AM2.M41;   Result.M12 := AM1.M11 * AM2.M12 + AM1.M12 * AM2.M22 + AM1.M13 * AM2.M32 + AM1.M14 * AM2.M42;   Result.M13 := AM1.M11 * AM2.M13 + AM1.M12 * AM2.M23 + AM1.M13 * AM2.M33 + AM1.M14 * AM2.M43;   Result.M14 := AM1.M11 * AM2.M14 + AM1.M12 * AM2.M24 + AM1.M13 * AM2.M34 + AM1.M14 * AM2.M44;   Result.M21 := AM1.M21 * AM2.M11 + AM1.M22 * AM2.M21 + AM1.M23 * AM2.M31 + AM1.M24 * AM2.M41;   Result.M22 := AM1.M21 * AM2.M12 + AM1.M22 * AM2.M22 + AM1.M23 * AM2.M32 + AM1.M24 * AM2.M42;   Result.M23 := AM1.M21 * AM2.M13 + AM1.M22 * AM2.M23 + AM1.M23 * AM2.M33 + AM1.M24 * AM2.M43;   Result.M24 := AM1.M21 * AM2.M14 + AM1.M22 * AM2.M24 + AM1.M23 * AM2.M34 + AM1.M24 * AM2.M44;   Result.M31 := AM1.M31 * AM2.M11 + AM1.M32 * AM2.M21 + AM1.M33 * AM2.M31 + AM1.M34 * AM2.M41;   Result.M32 := AM1.M31 * AM2.M12 + AM1.M32 * AM2.M22 + AM1.M33 * AM2.M32 + AM1.M34 * AM2.M42;   Result.M33 := AM1.M31 * AM2.M13 + AM1.M32 * AM2.M23 + AM1.M33 * AM2.M33 + AM1.M34 * AM2.M43;   Result.M34 := AM1.M31 * AM2.M14 + AM1.M32 * AM2.M24 + AM1.M33 * AM2.M34 + AM1.M34 * AM2.M44;   Result.M41 := AM1.M41 * AM2.M11 + AM1.M42 * AM2.M21 + AM1.M43 * AM2.M31 + AM1.M44 * AM2.M41;   Result.M42 := AM1.M41 * AM2.M12 + AM1.M42 * AM2.M22 + AM1.M43 * AM2.M32 + AM1.M44 * AM2.M42;   Result.M43 := AM1.M41 * AM2.M13 + AM1.M42 * AM2.M23 + AM1.M43 * AM2.M33 + AM1.M44 * AM2.M43;   Result.M44 := AM1.M41 * AM2.M14 + AM1.M42 * AM2.M24 + AM1.M43 * AM2.M34 + AM1.M44 * AM2.M44; end; function SGEMatrixTranspose(AM: TSGEMatrix): TSGEMatrix; begin   Result.M11 := AM.M11;   Result.M12 := AM.M21;   Result.M13 := AM.M31;   Result.M14 := AM.M41;   Result.M21 := AM.M12;   Result.M22 := AM.M22;   Result.M23 := AM.M32;   Result.M24 := AM.M42;   Result.M31 := AM.M13;   Result.M32 := AM.M23;   Result.M33 := AM.M33;   Result.M34 := AM.M43;   Result.M41 := AM.M14;   Result.M42 := AM.M24;   Result.M43 := AM.M34;   Result.M44 := AM.M44; end; function SGEVector4(AX, AY, AZ, AW: Single): TSGEVector4; begin   Result.X := AX;   Result.Y := AY;   Result.Z := AZ;   Result.W := AW; end; function SGEVector4Length(AV: TSGEVector4): Single; begin   Result := Sqrt(AV.X * AV.X + AV.Y * AV.Y + AV.Z * AV.Z + AV.W * AV.W); end; function SGEVector4SqrLength(AV: TSGEVector4): Single; begin   Result := AV.X * AV.X + AV.Y * AV.Y + AV.Z * AV.Z + AV.W * AV.W; end; function SGEVector4Multiply(AV: TSGEVector4; AValue: Single): TSGEVector4; begin   Result.X := AV.X * AValue;   Result.Y := AV.Y * AValue;   Result.Z := AV.Z * AValue;   Result.W := AV.W * AValue; end; function SGEVector4Normalize(AV: TSGEVector4): TSGEVector4; begin   Result := SGEVector4Multiply(AV, 1 / SGEVector4Length(AV)); end; function SGEVector4DotProduct(AV1, AV2: TSGEVector4): Single; begin   Result := AV1.X * AV2.X + AV1.Y * AV2.Y + AV1.Z * AV2.Z + AV1.W * AV2.W; end; function SGEVector3(AX, AY, AZ: Single): TSGEVector3; begin   Result.X := AX;   Result.Y := AY;   Result.Z := AZ; end; function SGEVector3Length(AV: TSGEVector3): Single; begin   Result := Sqrt(AV.X * AV.X + AV.Y * AV.Y + AV.Z * AV.Z); end; function SGEVector3SqrLength(AV: TSGEVector3): Single; begin   Result := AV.X * AV.X + AV.Y * AV.Y + AV.Z * AV.Z; end; function SGEVector3Multiply(AV: TSGEVector3; AValue: Single): TSGEVector3; begin   Result.X := AV.X * AValue;   Result.Y := AV.Y * AValue;   Result.Z := AV.Z * AValue; end; function SGEVector3Normalize(AV: TSGEVector3): TSGEVector3; begin   Result := SGEVector3Multiply(AV, 1 / SGEVector3Length(AV)); end; function SGEVector3DotProduct(AV1, AV2: TSGEVector3): Single; begin   Result := AV1.X * AV2.X + AV1.Y * AV2.Y + AV1.Z * AV2.Z; end; function SGEVector3CrossProduct(AV1, AV2: TSGEVector3): TSGEVector3; begin   Result := SGEVector3(AV1.Y * AV2.Z - AV1.Z * AV2.Y,     AV1.Z * AV2.X - AV1.X * AV2.Z, AV1.X * AV2.Y - AV1.Y * AV2.X); end; function SGEVector2(AX, AY: Single): TSGEVector2; begin   Result.X := AX;   Result.Y := AY; end; function SGEVector2Length(AV: TSGEVector2): Single; begin   Result := Sqrt(AV.X * AV.X + AV.Y * AV.Y); end; function SGEVector2SqrLength(AV: TSGEVector2): Single; begin   Result := AV.X * AV.X + AV.Y * AV.Y; end; function SGEVector2Multiply(AV: TSGEVector2; AValue: Single): TSGEVector2; begin   Result.X := AV.X * AValue;   Result.Y := AV.Y * AValue; end; function SGEVector2Normalize(AV: TSGEVector2): TSGEVector2; begin   Result := SGEVector2Multiply(AV, 1 / SGEVector2Length(AV)); end; function SGEVector2DotProduct(AV1, AV2: TSGEVector2): Single; begin   Result := AV1.X * AV2.X + AV1.Y * AV2.Y; end; function SGEVector2CrossProduct(AV1, AV2: TSGEVector2): Single; begin   Result := AV1.X * AV2.Y - AV1.Y * AV2.X; end;

Bino je jos napomenuti da OpenGL i DirectX ne koriste iste matrice... OpenGL koristi pravilo desne ruke za racunanje, dok DirectX koristi pravilo leve ruke. Jos jedna razlika je u tome sto su vektori u OpenGL-u matricama rasporedjeni po kolonama, a u DirectX-u po redovima. Mi cemo za nas engine uzeti OpenGL-ov nacin, a renderer-e cemo kasnije prosiriti tako da umeju pravilno da protumace matrice koje im prosledimo. Na taj nacin cemo u glavnom programu morati da znamo samo jedan nacin rada sa matricama i to samo u slucaju ako zelimo da radimo neke operacije koje zahtevaju nestandardne matrice, odnosno one za koje nismo predvideli funkcije za kreiranje. Trudicemo se da ubacimo sve sto nam treba tako da u sustini nikad necemo morati direktno da radimo sa matricama.

Dopuna: 15 Avg 2010 21:08

Matrice koje cemo na pocetku koristiti ce objekte koje crtamo postaviti na pravo mesto u sceni, postaviti kameru gde zelimo da bude i sve to preneti na 2D povrsinu naseg ekrana. Matrica koja postavlja objekat na pravo mesto se zove world matrica, ona koja postavlja kameru se zove view, a ona koja sve to transformise u 2D sliku se zove perspective matrica. Kod koji smo napisali za matrice nam omogucava kreiranje world matrice za rotiranje, skaliranje i translaciju, kreiranje view matrice na osnovu tacke na kojoj se kamera nalazi, u koju tacku gleda i u kom smeru se nalazi "gore", i kreiranje ortogonalne ili matrice projekcije za perspective matricu. Jos jedna od matrica koju cemo kasnije koristiti je matrica za transformaciju koordinata tekstura. Engine cemo napisati tako da podrzava i tu vrstu matrica, a objasnicemo ih kad budemo stigli do crtanja tekstura.

Ok... sad da prosirimo klasu za renderer:

type   TSGE_MatrixType = (     ESGE_MT_PROJECTION,     ESGE_MT_VIEW,     ESGE_MT_WORLD,     ESGE_MT_TEXTURE   );   { TSGERenderer }   TSGERenderer = class(TSGENamedObject)   protected     function GetRenderWindow: TSGERenderWindow; virtual; abstract;     function GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; virtual; abstract;     procedure SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); virtual; abstract;   public     procedure Initialize; virtual; abstract;     procedure Finalize; virtual; abstract;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); virtual; abstract;     procedure BeginFrame; virtual; abstract;     procedure EndFrame; virtual; abstract;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); virtual; abstract;     procedure SwapBuffers; virtual; abstract;     procedure SetWorldViewMatrix(AWorld, AView: TSGEMatrix); virtual; abstract;     // Testing     procedure DrawSimpleTriangle; virtual; abstract;     // Testing     property RenderWindow: TSGERenderWindow read GetRenderWindow;     property Matrix[AType: TSGE_MatrixType]: TSGEMatrix read GetMatrix write SetMatrix;   end;

DrawSimpleTriangle funkcija ce nam sluziti samo da testiramo da li kod za matrice radi pravilno. Cim budemo napisali kod koji ce omoguciti crtanje objekata, obrisacemo ovu funkciju.

Za matrice cemo koristiti funckije GetMatrix, SetMatrix i SetWorldViewMatrix. GetMatrix ce vratiti vrednost trazene matrice, SetMatrix ce postaviti zeljenu matricu dok ce SetWorldViewMatrix postaviti i world i view matrice. SetWorldViewMatrix smo dodali jer OpenGL nema odvojene world i view matrice, nego ima jednu koja sa zove model i koja u sebi sadrzi i world i view. Da ne bi morali 2 puta da razunamo model matricu preko SetMatrix funkcije, koristicemo SetWorldViewMatrix u slucaju da moramo da promenimo i world i view matricu.

Sad na implementaciju renderera:

unit OGLRenderer; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, gl; type   { TOGLRenderer }   TOGLRenderer = class(TSGERenderer)   private     FEngine: TSGEEngine;     FName: String;     FInitialized: Boolean;     FRenderWindow: TSGERenderWindow;     FMatrix: array[TSGE_MatrixType] of TSGEMatrix;   protected     function GetName: String; override;     function GetRenderWindow: TSGERenderWindow; override;     function GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; override;     procedure SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); override;   public     constructor Create(AEngine: TSGEEngine);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     procedure SetWorldViewMatrix(AWorld, AView: TSGEMatrix); override;     // Testing     procedure DrawSimpleTriangle; override;     // Testing     property Name: String read GetName;     property RenderWindow: TSGERenderWindow read GetRenderWindow;     property Matrix[AType: TSGE_MatrixType]: TSGEMatrix read GetMatrix write SetMatrix;   end; implementation uses   {$IFDEF SGE_Windows}   OGLWin32RenderWindow;   {$ELSE}   OGLX11RenderWindow;   {$ENDIF} { TOGLRenderer } function TOGLRenderer.GetName: String; begin   Result := FName; end; function TOGLRenderer.GetRenderWindow: TSGERenderWindow; begin   Result := FRenderWindow; end; function TOGLRenderer.GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; begin   Result := FMatrix[AType]; end; procedure TOGLRenderer.SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); var   OGLMatrixType: Cardinal;   WorldView: TSGEMatrix; begin   FMatrix[AType] := AMatrix;   case AType of     ESGE_MT_PROJECTION: OGLMatrixType := GL_PROJECTION;     ESGE_MT_VIEW: OGLMatrixType := GL_MODELVIEW;     ESGE_MT_WORLD: OGLMatrixType := GL_MODELVIEW;     ESGE_MT_TEXTURE: OGLMatrixType := GL_TEXTURE;   end;   glMatrixMode(OGLMatrixType);   if OGLMatrixType <> GL_MODELVIEW then     glLoadMatrixf(@AMatrix.M[0])   else   begin     WorldView := SGEMatrixMultiply(FMatrix[ESGE_MT_WORLD], FMatrix[ESGE_MT_VIEW]);     glLoadMatrixf(@WorldView.M[0]);   end; end; constructor TOGLRenderer.Create(AEngine: TSGEEngine); begin   FEngine := AEngine;   FName := 'OpenGL Renderer';   FInitialized := False;   FEngine.Logger.Log(FName + ' created', ESGE_LL_ENGINE); end; destructor TOGLRenderer.Destroy; begin   Finalize;   FEngine.UnregisterRenderer(Self);   FEngine.Logger.Log(FName + ' destroyed', ESGE_LL_ENGINE);   inherited; end; procedure TOGLRenderer.Initialize; begin   if FInitialized then     Exit;   FRenderWindow := nil;   FInitialized := True;   FEngine.Logger.Log(FName + ' initialized', ESGE_LL_ENGINE); end; procedure TOGLRenderer.Finalize; begin   if not FInitialized then     Exit;   FRenderWindow.Free;   FInitialized := False;   FEngine.Logger.Log(FName + ' finalized', ESGE_LL_ENGINE); end; procedure TOGLRenderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); var   MatrixType: TSGE_MatrixType; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   {$IFDEF SGE_Debug}   Assert(FRenderWindow = nil, 'Render window already exists');   {$ENDIF}   if FRenderWindow = nil then   begin     {$IFDEF SGE_Windows}     FRenderWindow := TOGLWin32RenderWindow.Create(FEngine, Self, ACaption, AWidth, AHeight);     {$ELSE}     FRenderWindow := TOGLX11RenderWindow.Create(FEngine, Self, ACaption, AWidth, AHeight);     {$ENDIF}     for MatrixType := Low(TSGE_MatrixType) to High(TSGE_MatrixType) do       Matrix[MatrixType] := SGEMatrixIdentity;   end; end; procedure TOGLRenderer.BeginFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit; end; procedure TOGLRenderer.EndFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit; end; procedure TOGLRenderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); var   Mask: Cardinal; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   Mask := 0;   if ESGE_FBT_COLOR in AFrameBuffers then   begin     glClearColor(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha);     Mask := Mask or GL_COLOR_BUFFER_BIT;   end;   if ESGE_FBT_DEPTH in AFrameBuffers then   begin     glClearDepth(ADepth);     Mask := Mask or GL_DEPTH_BUFFER_BIT;   end;   if ESGE_FBT_STENCIL in AFrameBuffers then   begin     glClearStencil(AStencil);     Mask := Mask or GL_STENCIL_BUFFER_BIT;   end;   glClear(Mask); end; procedure TOGLRenderer.SwapBuffers; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   FRenderWindow.SwapBuffers; end; procedure TOGLRenderer.SetWorldViewMatrix(AWorld, AView: TSGEMatrix); var   WorldView: TSGEMatrix; begin   FMatrix[ESGE_MT_WORLD] := AWorld;   FMatrix[ESGE_MT_VIEW] := AView;   glMatrixMode(GL_MODELVIEW);   WorldView := SGEMatrixMultiply(AWorld, AView);   glLoadMatrixf(@WorldView.M[0]); end; procedure TOGLRenderer.DrawSimpleTriangle; begin   glBegin(GL_TRIANGLES);   glVertex3f(0, 1, 0);   glVertex3f(1, -1, 0);   glVertex3f(-1, -1, 0);   glEnd; end; end.

GetMatrix ne radi nista posebno... samo vraca kesiranu vrednost trazene matrice. SetMatrix postavlja vrednost u kes, i zatim poziva OpenGL funkciju za postavljanje matrice. Izuzetak su world i view matrice za koje moramo da izracunamo model matricu koju prosledjujemo OpenGL-u. SetWorldViewMatrix je samo precica za postavljanje model matrice. DrawSimpleTriangle za sada ne treba da vas brine... sve sto treba da se zna je da ta funkcija crta trougao i to je to Smile

Sada sve to isto, ali za DirectX:

unit DX9Renderer; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, Direct3D9; type   { TDX9Renderer }   TDX9Renderer = class(TSGERenderer)   private     FEngine: TSGEEngine;     FName: String;     FInitialized: Boolean;     FD3D: IDirect3D9;     FRenderWindow: TSGERenderWindow;     FMatrix: array[TSGE_MatrixType] of TSGEMatrix;     function GetDevice: IDirect3DDevice9;   protected     function GetName: String; override;     function GetRenderWindow: TSGERenderWindow; override;     function GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; override;     procedure SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); override;   public     constructor Create(AEngine: TSGEEngine);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     procedure SetWorldViewMatrix(AWorld, AView: TSGEMatrix); override;     // Testing     procedure DrawSimpleTriangle; override;     // Testing     function SGEColorToDXColor(AColor: TSGEColor): TD3DColor;     function SGEMatrixToDXMatrix(AMatrix: TSGEMatrix): TD3DMatrix;     property Name: String read GetName;     property RenderWindow: TSGERenderWindow read GetRenderWindow;   end; implementation uses   DX9Win32RenderWindow; { TDX9Renderer } function TDX9Renderer.GetDevice: IDirect3DDevice9; begin   Result := IDirect3DDevice9(StrToInt(FRenderWindow.Attribute['D3DDevice'])); end; function TDX9Renderer.GetName: String; begin   Result := FName; end; function TDX9Renderer.GetRenderWindow: TSGERenderWindow; begin   Result := FRenderWindow; end; function TDX9Renderer.GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; begin   Result := FMatrix[AType]; end; procedure TDX9Renderer.SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); var   DXMatrixType: TD3DTransformStateType;   View: TSGEMatrix; begin   FMatrix[AType] := AMatrix;   case AType of     ESGE_MT_PROJECTION: DXMatrixType := D3DTS_PROJECTION;     ESGE_MT_VIEW: DXMatrixType := D3DTS_VIEW;     ESGE_MT_WORLD: DXMatrixType := D3DTS_WORLD;     ESGE_MT_TEXTURE: DXMatrixType := D3DTS_TEXTURE0;   end;   if DXMatrixType <> D3DTS_VIEW then     GetDevice.SetTransform(DXMatrixType, SGEMatrixToDXMatrix(AMatrix))   else   begin     View := SGEMatrixMultiply(SGEMatrixScale(1, 1, -1), AMatrix);     GetDevice.SetTransform(DXMatrixType, SGEMatrixToDXMatrix(View));   end; end; constructor TDX9Renderer.Create(AEngine: TSGEEngine); begin   FEngine := AEngine;   FName := 'Direct3D9 Renderer';   FInitialized := False;   FEngine.Logger.Log(FName + ' created', ESGE_LL_ENGINE); end; destructor TDX9Renderer.Destroy; begin   Finalize;   FEngine.UnregisterRenderer(Self);   FEngine.Logger.Log(FName + ' destroyed', ESGE_LL_ENGINE);   inherited; end; procedure TDX9Renderer.Initialize; begin   if FInitialized then     Exit;   FD3D := Direct3DCreate9(D3D_SDK_VERSION);   if FD3D = nil then     raise Exception.Create('Can not create Direct3D');   FRenderWindow := nil;   FInitialized := True;   FEngine.Logger.Log(FName + ' initialized', ESGE_LL_ENGINE); end; procedure TDX9Renderer.Finalize; begin   if not FInitialized then     Exit;   FRenderWindow.Free;   FD3D := nil;   FInitialized := False;   FEngine.Logger.Log(FName + ' finalized', ESGE_LL_ENGINE); end; procedure TDX9Renderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); var   MatrixType: TSGE_MatrixType; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   {$IFDEF SGE_Debug}   Assert(FRenderWindow = nil, 'Render window already exists');   {$ENDIF}   if FRenderWindow = nil then   begin     FRenderWindow := TDX9Win32RenderWindow.Create(FEngine, Self, FD3D, ACaption, AWidth, AHeight);     for MatrixType := Low(TSGE_MatrixType) to High(TSGE_MatrixType) do       Matrix[MatrixType] := SGEMatrixIdentity;   end; end; procedure TDX9Renderer.BeginFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   GetDevice.BeginScene; end; procedure TDX9Renderer.EndFrame; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   GetDevice.EndScene; end; procedure TDX9Renderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); var   Flags: Cardinal; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   Flags := 0;   if ESGE_FBT_COLOR in AFrameBuffers then     Flags := Flags or D3DCLEAR_TARGET;   if ESGE_FBT_DEPTH in AFrameBuffers then     Flags := Flags or D3DCLEAR_ZBUFFER;   if ESGE_FBT_STENCIL in AFrameBuffers then     Flags := Flags or D3DCLEAR_STENCIL;   GetDevice.Clear(0, nil, Flags, SGEColorToDXColor(AColor), ADepth, AStencil); end; procedure TDX9Renderer.SwapBuffers; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, FName + ' not initialized');   {$ENDIF}   if not FInitialized then     Exit;   FRenderWindow.SwapBuffers; end; procedure TDX9Renderer.SetWorldViewMatrix(AWorld, AView: TSGEMatrix); begin   Matrix[ESGE_MT_WORLD] := AWorld;   Matrix[ESGE_MT_VIEW] := AView; end; procedure TDX9Renderer.DrawSimpleTriangle; var   VB: IDirect3DVertexBuffer9;   Data: Pointer; const   D3DFVF_TESTVERTEX = D3DFVF_XYZ;   Vertices: array[0..2] of TSGEVector3 = (     (X: 0; Y: 1; Z: 0),     (X: 1; Y: -1; Z: 0),     (X: -1; Y: -1; Z: 0)); begin   GetDevice.CreateVertexBuffer(3 * SizeOf(TSGEVector3), 0, D3DFVF_TESTVERTEX, D3DPOOL_DEFAULT, VB, nil);   VB.Lock(0, SizeOf(Vertices), Data, 0);   Move(Vertices, Data^, SizeOf(Vertices));   VB.Unlock;   GetDevice.SetStreamSource(0, VB, 0, SizeOf(TSGEVector3));   GetDevice.SetFVF(D3DFVF_TESTVERTEX);   GetDevice.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);   VB := nil; end; function TDX9Renderer.SGEColorToDXColor(AColor: TSGEColor): TD3DColor; begin   Result := D3DCOLOR_COLORVALUE(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha); end; function TDX9Renderer.SGEMatrixToDXMatrix(AMatrix: TSGEMatrix): TD3DMatrix; begin   Result := TD3DMatrix(AMatrix); end; end.

GetMatrix funkcija se nije promenila. SetMatrix u DirectX verziji mora da izvrsi jednu malu promenu prilikom postavljanja view matrice. Posto DirectX koristi pravilo leve ruke, a nase matrice koriste pravilo desne, view matrici moramo da invertujemo Z osu, a to cemo uraditi tako sto cemo view matricu pomnoziti matricom za skaliranje koja ima negativnu vrednost za Z osu. SetWorldViewMatrix samo postavlja world i view matrice kao da smo dva puta pozvali SetMatrix. DrawSimpleTriangle kreira potrebne objekte za crtanje u DirectX-u i crta trouglic.

Sad jos samo da iskoristimo to sve u glavnom programu:

program HelloWorld; {$I SGE.inc} {$IFDEF SGE_Delphi} {$APPTYPE CONSOLE} {$ENDIF} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas',   HTMLLogger in 'HTMLLogger.pas',   {$IFDEF SGE_Windows}   DX9Renderer in '..\..\Source\Renderers\DX9Renderer\DX9Renderer.pas',   {$ENDIF}   OGLRenderer in '..\..\Source\Renderers\OGLRenderer\OGLRenderer.pas'; var   Engine: TSGEEngine = nil;   HTML: THTMLLogger = nil;   {$IFDEF SGE_Windows}   DX9Rendr: TDX9Renderer = nil;   {$ENDIF}   OGLRendr: TOGLRenderer = nil;   RendrIndex: String;   I: Integer; begin   try     try       HTML := THTMLLogger.Create('SGE.html');       Engine := CreateSGEEngine('', HTML.Log);       Engine.Logger.LogLevel := ESGE_LL_INFO;       {$IFDEF SGE_Windows}       DX9Rendr := TDX9Renderer.Create(Engine);       Engine.RegisterRenderer(DX9Rendr);       {$ENDIF}       OGLRendr := TOGLRenderer.Create(Engine);       Engine.RegisterRenderer(OGLRendr);       WriteLn('Registered renderers:');       for I := 0 to Engine.RendererCount - 1 do         WriteLn(Format('  %d. %s', [I + 1, Engine.Renderers[I].Name]));       WriteLn;       Write('Select renderer: ');       ReadLn(RendrIndex);       I := StrToIntDef(RendrIndex, 0) - 1;       if (I < 0) or (I >= Engine.RendererCount) then         Exit;       Engine.Initialize(Engine.Renderers[I]);       Engine.Renderer.CreateWindow('SGE using ' + Engine.Renderers[I].Name);       Engine.Renderer.Matrix[ESGE_MT_PROJECTION] := SGEMatrixPerspective(45,         Engine.Renderer.RenderWindow.Width / Engine.Renderer.RenderWindow.Height, 1, 1000);       Engine.Renderer.Matrix[ESGE_MT_VIEW] := SGEMatrixLookAt(         SGEVector3(0, 0, 5), SGEVector3(0, 0, 0), SGEVector3(0, 1, 0));       while Engine.ProcessMessages do       begin         Engine.Renderer.BeginFrame;         Engine.Renderer.Clear([ESGE_FBT_COLOR], SGEColor(0.4, 0.4, 1, 0));         Engine.Renderer.Matrix[ESGE_MT_WORLD] := SGEMatrixRotate(Engine.Timer.Time / 20, 0, 0, 1);         Engine.Renderer.DrawSimpleTriangle;         Engine.Renderer.EndFrame;         Engine.Renderer.SwapBuffers;       end;       Engine.Finalize;     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     {$IFDEF SGE_Windows}     DX9Rendr.Free;     {$ENDIF}     OGLRendr.Free;     Engine.Free;     HTML.Free;   end; end.

Na osnovu izbora kreiramo renderer i prozor, zatim postavljamo matrice za projekciju i kameru, i zatim u glavnoj petlji kreiramo world matricu koja ce rotirati trouglic koji crtamo funkcijom DrawSimpleTriangle.

Konacno imamo nesto nacrtano u prozorcetu, a kod za crtanje u glavnom programu je potpuno isti bez obzira na to da li koristimo OpenGL ili DirectX, i bez obzira na to da li program radi na Windows-u ili Linux-u.

https://www.mycity.rs/must-login.png

Dopuna: 15 Avg 2010 21:23

Samo jedna kratka informacija... u buduce cu pisati samo implementacije dodanih/promenjenih funkcija. Nema potrebe da konstantno postavljam celu klasu zbog par dodanih funkcija.

I jedno kratko pitanje... da li da postavljam screen shot-ove i izvrsne fajlove?

Dopuna: 17 Avg 2010 22:17

Do sad je sve bilo lako ko pasulj Very Happy Sad polako krece glavni deo... ako nesto nije bilo jasno do sad, sad je pravo vreme za pitanja. Na redu su klase i tipovi koji ce nam omoguciti kreiranje buffer-a za crtanje. Crtanje se svodi na kreiranje specijalnog niza koji sadrzi podatke o koordinatama za crtanje, bojama, koordinatama tekstura i slicno. Taj buffer se zove vertex buffer jer sadrzi podatke o vertex-ima. Jos jedan tip buffer-a koji se cesto koristi je index buffer. U njemu su zapisani redni brojevi vertex-a. OpenGL i DirectX na osnovu tog buffer-a mogu da citaju podatke iz vertex buffer-a preko reda ili vise puta i na taj nacin se smanjuje kolicina podataka koji moraju da se prenesu iz memoriju u graficku karticu. Razlika izmedju obicnih nizova i vertex i index buffer-a je u tome sto se vertex i index buffer-i uglavnom kreiraju direktno u memoriji graficke kartice i time se ne gubi vreme na prenos podataka iz sistemske u graficku memoriju pa zbog toga dobijamo brze crtanje. Zbog toga sto se podaci cuvaju dirktno u grafickoj memoriji, postavlja se pitanje kako onda mozemo da citamo i pisemo u te buffer-e. Fora je u tome da se, kad god zahtevamo pristup do podataka za pisanje ili citanje, deo graficke memorije u kojem se nalazi taj buffer kopira u sistemsku memoriju, a kad zavrsimo sa njim, nazad u graficku. Taj proces traje relativno dugo i zbog toga prilikom kreiranja buffer-a mozemo grafickoj kartici da kazemo na kakav nacin planiramo da koristimo taj buffer... da li zelimo samo jednom da ga napunimo i da ga vise ne diramo, da li zelimo da citamo iz njega, da li zelimo cesto da pisemo, da li ne zelimo nikad da citamo, ali planiramo vrlo cesto ga ga menjamo, i slicno. Na taj nacin, drajver moze da izabere najbolje mesto u memoriji i da time smanji vreme koje je potrebno za prenos podataka. Osim toga prilikom uzimanja buffer-a za citanje/pisanje mozemo da kazemo drajveru na kakav nacin cemo menjati podatke... da li cemo samo citamo, da li cemo ponovo da napinomo ceo buffer, i slicno. Na taj nacin, drajver moze dodatno da optimizuje prenos podataka.

Sad kad znate odprilike sta su vertex i index buffer, mozete da zamislite kako ce izgledati crtanje... kreiracemo buffer, u njega ubaciti podatke, onda ce jedan objekat da koristi taj buffer za crtanje, pa drugi, pa treci... na kraju cemo doci do problema jer necemo znati kad da oslobodimo buffer, odnosno, necemo znati da li nekom taj buffer jos uvek treba. Zato cemo napraviti klasicu koja ce samo da broji koliko puta je buffer (ili neki drugi objekt) uzet, i automatski ce osloboditi buffer kad vise nikom ne bude bio potreban. Ta klasa ce imati samo 2 funkcije... jednu da joj kazemo da zelimo da je koristimo, i drugu da joj kazemo da smo zavrsili:

type   TSGEReferenceCounted = class   private     FRefCount: Integer;   public     constructor Create;     destructor Destroy; override;     procedure Grab;     function Drop: Boolean;     property RefCount: Integer read FRefCount;   end; constructor TSGEReferenceCounted.Create; begin   FRefCount := 1; end; destructor TSGEReferenceCounted.Destroy; begin   {$IFDEF SGE_Debug}   Assert(FRefCount = 0, ClassName + ' ref. count is ' + IntToStr(FRefCount));   {$ENDIF}   inherited; end; procedure TSGEReferenceCounted.Grab; begin   Inc(FRefCount); end; function TSGEReferenceCounted.Drop: Boolean; begin   Dec(FRefCount);   Result := FRefCount = 0;   if Result then     Free; end;

U konstruktoru FRefCount postavimo na 1 da bi oznacili da jedan objekat zeli da koristi instancu koju je upravo kreirao. U destruktoru proveravamo da li je FRefCount 0 (u momentu unistavanja, FRefCount bi morao biti 0) i prikazujemo gresku ako nije 0 i ako smo u DEBUG nacinu rada. Grab jednostavno kaze da jos neki objekat zeli da koristi ovu instancu tako sto FRefCount poveca za jedan. Drop radi obrnuto od Grab, smanjuje FRefCount, i ako je FRefCount 0, tada i obrise objekat. Preko rezultata obavestava objekat o tome da li je instanca unistena ili ne. Jednom kad je instanca unistena vise nije moguce zvati njene funkcije (doci ce do greske u programu).

Uz pomoc ove klase, mozemo da napravimo vertex i index buffer-e koji ce umeti sami da se uniste kad vise nisu potrebni. Vertex i index buffer-i ce izgledati prilicno slicno:

type   TSGEVertexBuffer = class(TSGEReferenceCounted)   protected     function GetBufferUsage: TSGE_BufferUsage; virtual; abstract;     function GetBufferSize: Integer; virtual; abstract;     function GetVertexSize: Integer; virtual; abstract;     function GetLocked: Boolean; virtual; abstract;   public     constructor Create(ARenderer: TSGERenderer; AVertexSize,       AVertexCount: Integer; ABufferUsage: TSGE_BufferUsage); virtual; abstract;     function Lock(AOffset, ASize: Integer; ALockType: TSGE_LockType): Pointer; virtual; abstract;     procedure Unlock; virtual; abstract;     property BufferUsage: TSGE_BufferUsage read GetBufferUsage;     property BufferSize: Integer read GetBufferSize;     property VertexSize: Integer read GetVertexSize;     property Locked: Boolean read GetLocked;   end;   TSGEIndexBuffer = class(TSGEReferenceCounted)   protected     function GetBufferUsage: TSGE_BufferUsage; virtual; abstract;     function GetBufferSize: Integer; virtual; abstract;     function GetIndexType: TSGE_IndexType; virtual; abstract;     function GetLocked: Boolean; virtual; abstract;   public     constructor Create(ARenderer: TSGERenderer; AIndexType: TSGE_IndexType;       AIndexCount: Integer; ABufferUsage: TSGE_BufferUsage); virtual; abstract;     function Lock(AOffset, ASize: Integer; ALockType: TSGE_LockType): Pointer; virtual; abstract;     procedure Unlock; virtual; abstract;     property BufferUsage: TSGE_BufferUsage read GetBufferUsage;     property BufferSize: Integer read GetBufferSize;     property IndexType: TSGE_IndexType read GetIndexType;     property Locked: Boolean read GetLocked;   end;

Za sada jedina vidljiva razlika je u tome sto u konstruktoru vertex buffer kao drugi parametar uzima velicinu vertex-a, a index buffer tip (tip utice na velicinu pa mu to nekako dodje na isto) index-a. Tip index-a samo oderdjuje da li se za index koristi 16-bitna ili 32-bitna vrednost:

type   TSGE_IndexType = (     ESGE_IT_16,     ESGE_IT_32   );

Na osnovu objasnjenja sa pocetka ovog tutorijala vidimo da u konstruktoru kao 4 parametar imamo ABufferUsage koji drajveru govori na koji nacin cemo koristiti buffer:

type   TSGE_BufferUsage = (     ESGE_BU_STATIC,     ESGE_BU_DYNAMIC,     ESGE_BU_WRITE_ONLY,     ESGE_BU_DISCARDABLE,     ESGE_BU_DYNAMIC_WRITE_ONLY,     ESGE_BU_DYNAMIC_WRITE_ONLY_DISCARDABLE   );

Static znaci da cemo jednom napuniti buffer i da ga vise necemo menjati, dynamic je suprotno (menjacemo ga cesto), write only... samo za pisanje, discardable znaci da planiramo svaki put da popunjavamo ceo buffer.

Kod Lock funkcije kazemo drajveru od kog bajta i koliko veliko parce buffer-a zelimo da dobijemo, dok zadnjim parametrom kazemo kakve operacije zelimo da izvrsimo nad podacima:

type   TSGE_LockType = (     ESGE_LT_NORMAL,     ESGE_LT_DISCARD,     ESGE_LT_READ_ONLY,     ESGE_LT_NO_OVERWRITE   );

Normal znaci da ne planiramo nista posebno... mozda cemo citati, mozda pisati, discard znaci da nemamo nameru da citamo i da jednostavno taj deo memorije zameni onim sto mu napunimo, read only... samo cemo citati podatke, no overwrite je slicno kao normal, s tim da smo obavezni da necemo prepisati ni jedan deo buffer-a koji smo vec koristili za crtanje trenutnog frejma.

Sledece sto cemo obraditi su pomocne klase kojima cemo drajveru moci da kazemo koji podatak se nalazi na kojoj poziciji u kom buffer-u jer vertex buffer-u mogu biti samo pozicije, mogu biti i boje ili koordinate textura, ili razni drugi podaci... zato moramo drajveru objasniti sta se sve nalazi u buffer-u.

Dopuna: 18 Avg 2010 21:45

Opis svakog elementa u buffer-u mora sadrzati 3 osnovna podatka... koja je velicina elementa, koliko bajtova je udaljen od pocetka elementa i za sta se taj element koristi. Recimo koordinata vertex-a se sastoji od 3 broja i sluzi za racunanje pozicije vertex-a, i ako je prvi podatak u elementu nalazi se 0 bajtova od pocetka, boja se sastoji iz R, G, B i A komponente i sluzi za odredjivanje boje crtanja, i ako ide posle koordinate vertex-a bice udaljena od pocetka elementa za velicinu 3 broja, i slicno. Osim velicine i nacina koriscenja, koordinate tekstura zahtevaju jos jedan parametar, a to je redni broj teksture na koju uticu (OpenGL i directX podrzavaju crtanje objekata sa vise tekstura).

Sad nesto malo o organizaciji podataka u buffer-u. Nama niko ne brani da kreiramo buffer u kojem cemo upisati elemente koji imaju poziciju (3 broja), zatim recimo 10 bajtova nekih podataka koji nemaju veze sa crtanjem, zatim boju, pa onda opet neke bezvezne podatke, pa onda koordinate za teksture. Ako pravilno postavimo velicine i poziciju pocetka podatka, drajver ce znati pravilno da procita te podatke, ali cemo bezveze trositi memoriju graficke kartice podacima koji nisu bitni za crtanje. Zbog toga je dobro da su u buffer-u samo podaci potrebni za crtanje i da izmedju njih nema praznog prostora. Na drugom primeru cemo pokazati jos jednu stvar koja je bitna kod organizacije podataka... zamislite da imamo buffer u kojem imamo 1000 elemenata koji sadrze koordinatu vertex-a i boju crtanja. Recimo da sad hocemo boju da promenimo... znaci morali bi da pozovemo Lock funkciju za ceo buffer, koja ce nam vratiti boje, ali i koordinate. Za svaki element cemo dobiti 3 broja (koordinatu) koja nam u sustini nisu potrebna, jer nas zanima samo boja. Taj problem bi se resio tako sto bi kreirali 2 buffer-a, jedan koji sadrzi samo koordinate, i drugi koji sadrzi samo boje. Na taj nacin mozemo menjati samo boje, ali prilikom crtanja ne smemo zaboraviti da moramo reci drajveru da koristi oba buffer-a za crtanje.

Da bi olaksali koriscenje buffer-a kreiracemo 2 klase koje ce drajveru reci koje sve buffer-e treba da koristi i sta se u kojem buffer-u na kom mestu nalazi. Klasa koja ce se brinuti o tome koji sve buffer-i treba da se koriste izgleda ovako:

type   TSGEVertexBufferBinding = class(TSGEReferenceCounted)   protected     function GetVertexBuffers(AIndex: Integer): TSGEVertexBuffer; virtual; abstract;     procedure SetVertexBuffers(AIndex: Integer; AVertexBuffer: TSGEVertexBuffer); virtual; abstract;     function GetCount: Integer; virtual; abstract;   public     procedure AddVertexBuffer(AVertexBuffer: TSGEVertexBuffer; AIndex: Integer = -1); virtual; abstract;     procedure DeleteVertexBuffer(AIndex: Integer); virtual; abstract;     procedure DeleteAllVertexBuffers; virtual; abstract;     procedure RemoveVertexBuffer(AVertexBuffer: TSGEVertexBuffer); virtual; abstract;     property VertexBuffers[AIndex: Integer]: TSGEVertexBuffer read GetVertexBuffers write SetVertexBuffers;     property Count: Integer read GetCount;   end;

U sustini, klasa ce biti implementirana kao klasicna lista koja dozvoljava dodavanje i brisanje vertex buffer-a.

Klasa koja ce se brinuti o tome da drajveru objasni sta se u kom buffer-u nalazi izgleda ovako:

type   TSGEVertexDeclaration = class(TSGEReferenceCounted)   protected     function GetCount: Integer; virtual; abstract;     function GetElements(AIndex: Integer): TSGEVertexElement; virtual; abstract;     procedure SetElements(AIndex: Integer; AElement: TSGEVertexElement); virtual; abstract;   public     procedure AddElement(ASource, AOffset: Integer;       AType: TSGE_VertexElementType; AUsage: TSGE_VertexElementUsage;       AIndex: Integer); virtual; abstract;     procedure DeleteElement(AIndex: Integer); virtual; abstract;     procedure DeleteAllElements; virtual; abstract;     property Count: Integer read GetCount;     property Elements[AIndex: Integer]: TSGEVertexElement read GetElements write SetElements;   end;

Ova klasa ce se takodje ponasati kao lista, ali ce umesto buffer-a imati elemente vertex-a. Iz AddElement funkcije se vidi da svaki element ima 5 parametara. Prvi parametar je redni broj buffer-a iz TSGEVertexBufferBinding-a na koji se element odnosi, drugi je broj bajtova od pocetka vertex-a do tog elementa, treci je tip odnosno velicina, cetvrti je nacin koriscenja, i zadnji se koristi za redni broj teksture na koju ce element uticati ako se koristi za definisanje koordinata teksture. Za laksu obradu elemenata cemo napraviti tip TSGEVertexElement koji ce sadrzati sve te podatke:

type   TSGEVertexElement = record     ElementSource: Integer;     ElementOffset: Integer;     ElementType: TSGE_VertexElementType;     ElementUsage: TSGE_VertexElementUsage;     ElementIndex: Integer;   end;   PSGEVertexElement = ^TSGEVertexElement;

Treba jos i da definisemo tipove elemenata i nacine koriscenja koje je moguce postaviti:

type   TSGE_VertexElementUsage = (     ESGE_VEU_POSITION,     ESGE_VEU_NORMAL,     ESGE_VEU_DIFFUSE,     ESGE_VEU_TEXTURE_COORDINATES   );   TSGE_VertexElementType = (     ESGE_VET_FLOAT1,     ESGE_VET_FLOAT2,     ESGE_VET_FLOAT3,     ESGE_VET_FLOAT4,     ESGE_VET_COLOUR,     ESGE_VET_SHORT1,     ESGE_VET_SHORT2,     ESGE_VET_SHORT3,     ESGE_VET_SHORT4   );

Za sada cemo podrzati koordinate vertex-a, normale (normale se koriste za odredjivanje orijentacije objekta odnosno pod kojim uglom reflektuje svetlost), boju i koordinate tekstura. Od tipova cemo podrzati realne brojeve, cele brojeve i boje. Uglavnom cemo koristiti realne brojeve i boje... recimo za koordinatu vertex-a nam trebaju X, Y i Z koordinate pa cemo zato koristiti tip ESGE_VET_FLOAT3, koordinata texture ima samo 2 broja pa cemo koristiti ESGE_VET_FLOAT2, i slicno.

Na ovaj nacin imamo odvojene deklaracije elemenata od samih podataka pa bez problema mozemo da recimo kreiramo jednu deklaraciju i vise buffer-a pa da ih posle samo menjamo po potrebi (recimo ako svi objekti koje crtamo imaju koordinatu vertex-a, boju i koordinatu za teksturu, dovoljno je da napravimo samo jednu deklaraciju koji cemo koristiti za crtanje svih objekata jer je organizacija podataka u buffer-u ista).

Sve sto smo do sad rekli i napravili je fino i lepo, ali smo zaboravili na nesto... index buffer-i. U ovim klasama nigde nema mesta za index buffer jer u sustini index buffer-i ne uticu na organizaciju podataka u vertex buffer-u. Treba nam jos jedna klasa koja ce da sadrzi deklaraciju elemenata, vertex buffere i index buffer (koji nije obavezan) koji ce se koristiti za crtanje. Osim toga bilo bi lepo da mozemo da kreiramo jedan veliki vertex buffer i index buffer za staticne objekte koji se nece menjati i time ubrzamo crtanje. Zato bi bilo dobro da mozemo da kazemo od kog elementa u buffer-ima drajver treba da crta i koliko vertex-a da nacrta. E, ti podaci su dovoljni da drajver tacno zna sta i kako da renderuje pa cemo tu klasu da nazovemo TSGERenderOperation:

type   TSGERenderOperation = class   protected     function GetVertexDeclaration: TSGEVertexDeclaration; virtual; abstract;     procedure SetVertexDeclaration(AVertexDeclaration: TSGEVertexDeclaration); virtual; abstract;     function GetVertexBufferBinding: TSGEVertexBufferBinding; virtual; abstract;     procedure SetVertexBufferBinding(AVertexBufferBinding: TSGEVertexBufferBinding); virtual; abstract;     function GetIndexBuffer: TSGEIndexBuffer; virtual; abstract;     procedure SetIndexBuffer(AIndexBuffer: TSGEIndexBuffer); virtual; abstract;     function GetVertexOffset: Integer; virtual; abstract;     procedure SetVertexOffset(AVertexOffset: Integer); virtual; abstract;     function GetIndexOffset: Integer; virtual; abstract;     procedure SetIndexOffset(AIndexOffset: Integer); virtual; abstract;     function GetVertexCount: Integer; virtual; abstract;     procedure SetVertexCount(AVertexCount: Integer); virtual; abstract;   public     property VertexDeclaration: TSGEVertexDeclaration read GetVertexDeclaration write SetVertexDeclaration;     property VertexBufferBinding: TSGEVertexBufferBinding read GetVertexBufferBinding write SetVertexBufferBinding;     property IndexBuffer: TSGEIndexBuffer read GetIndexBuffer write SetIndexBuffer;     property VertexOffset: Integer read GetVertexOffset write SetVertexOffset;     property IndexOffset: Integer read GetIndexOffset write SetIndexOffset;     property VertexCount: Integer read GetIndexOffset write SetIndexOffset;   end;

TSGERenderOperation je klasa koju cemo prosledjivati rendereru kad god budemo hteli da nam nesto nacrta, a renderer ce znati kako da protumaci sve te podatke i da ih u pravom redosledu prosledi grafickoj kartici.

Sledece sto cemo odraditi su implementacije klasa koje smo definisali i funkcije u rendereru koje ce omoguciti njihovo kreiranje. Usput cemo malo reorganizovati OpenGL i DirectX implementacije tako da malo smanjimo dupliranje koda.

offline
  • Pridružio: 28 Okt 2009
  • Poruke: 212
  • Gde živiš: Kanjiza

Iako ne citam nista (ne zanima me trenutno ovo ) ... video sam dosta napisanog ... svaka cast Srki ... ja nebi mogao ovoliko pisati xD

offline
  • Pridružio: 03 Okt 2009
  • Poruke: 246

ma sve je vrlo prosto i jednostavno... Smile
Sve za apsolutne pocetnike...

narocito deo, koji je u celosti objasnjen, a ide odprilike ovako :

function SGEMatrixMultiply(AM1, AM2: TSGEMatrix): TSGEMatrix;
begin
Result.M11 := AM1.M11 * AM2.M11 + AM1.M12 * AM2.M21 + AM1.M13 * AM2.M31 + AM1.M14 * AM2.M41;
Result.M12 := AM1.M11 * AM2.M12 + AM1.M12 * AM2.M22 + AM1.M13 * AM2.M32 + AM1.M14 * AM2.M42;
Result.M13 := AM1.M11 * AM2.M13 + AM1.M12 * AM2.M23 + AM1.M13 * AM2.M33 + AM1.M14 * AM2.M43;
Result.M14 := AM1.M11 * AM2.M14 + AM1.M12 * AM2.M24 + AM1.M13 * AM2.M34 + AM1.M14 * AM2.M44;

Result.M21 := AM1.M21 * AM2.M11 + AM1.M22 * AM2.M21 + AM1.M23 * AM2.M31 + AM1.M24 * AM2.M41;
Result.M22 := AM1.M21 * AM2.M12 + AM1.M22 * AM2.M22 + AM1.M23 * AM2.M32 + AM1.M24 * AM2.M42;
Result.M23 := AM1.M21 * AM2.M13 + AM1.M22 * AM2.M23 + AM1.M23 * AM2.M33 + AM1.M24 * AM2.M43;
Result.M24 := AM1.M21 * AM2.M14 + AM1.M22 * AM2.M24 + AM1.M23 * AM2.M34 + AM1.M24 * AM2.M44;

Result.M31 := AM1.M31 * AM2.M11 + AM1.M32 * AM2.M21 + AM1.M33 * AM2.M31 + AM1.M34 * AM2.M41;
Result.M32 := AM1.M31 * AM2.M12 + AM1.M32 * AM2.M22 + AM1.M33 * AM2.M32 + AM1.M34 * AM2.M42;
Result.M33 := AM1.M31 * AM2.M13 + AM1.M32 * AM2.M23 + AM1.M33 * AM2.M33 + AM1.M34 * AM2.M43;
Result.M34 := AM1.M31 * AM2.M14 + AM1.M32 * AM2.M24 + AM1.M33 * AM2.M34 + AM1.M34 * AM2.M44;

Result.M41 := AM1.M41 * AM2.M11 + AM1.M42 * AM2.M21 + AM1.M43 * AM2.M31 + AM1.M44 * AM2.M41;
Result.M42 := AM1.M41 * AM2.M12 + AM1.M42 * AM2.M22 + AM1.M43 * AM2.M32 + AM1.M44 * AM2.M42;
Result.M43 := AM1.M41 * AM2.M13 + AM1.M42 * AM2.M23 + AM1.M43 * AM2.M33 + AM1.M44 * AM2.M43;
Result.M44 := AM1.M41 * AM2.M14 + AM1.M42 * AM2.M24 + AM1.M43 * AM2.M34 + AM1.M44 * AM2.M44;
end;

function SGEMatrixTranspose(AM: TSGEMatrix): TSGEMatrix;
begin
Result.M11 := AM.M11;
Result.M12 := AM.M21;
Result.M13 := AM.M31;
Result.M14 := AM.M41;

Result.M21 := AM.M12;
Result.M22 := AM.M22;
Result.M23 := AM.M32;
Result.M24 := AM.M42;

Result.M31 := AM.M13;
Result.M32 := AM.M23;
Result.M33 := AM.M33;
Result.M34 := AM.M43;

Result.M41 := AM.M14;
Result.M42 := AM.M24;
Result.M43 := AM.M34;
Result.M44 := AM.M44;
end;
Ko nije razumeo sta pise...?
aj da vas vidim...

stvarno svaka cast... Smile

El moze pitanje?
A sto lepo ne napravis DLL ( koji inace postoji) koji bi sve ovo odradio umesto tebe, a ti se lepo koristis samo pozivima..?

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Napisano: 19 Avg 2010 19:21

Ovo sto pisem je tutorijal o tome kako se pravi taj "DLL" Smile Tutorijal nije za pocetnike, sto i pise u prvoj poruci, ali ce konacan proizvod biti biblioteka koja ce biti laka za upotrebu (i to pise u prvoj poruci).

Rad sa matricama i vektorima se uci u srednjoj skoli... uzevsi u obzir da je vecina ljudi koji citaju ovaj tutorijal vec presla to gradivo, ne vidim razlog zbog kojeg im jednostavne funkcije za rad sa matricama i vektorima ne bi bile jasne.

Dopuna: 20 Avg 2010 17:28

Idemo na implementaciju klasa o kojima smo pricali predhodnog puta. Da krenemo od TVertexDeclaration klase:

unit SGEVertexDeclaration; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes; type   { TVertexDeclaration }   TVertexDeclaration = class(TSGEVertexDeclaration)   protected     FElements: TList;   protected     function GetCount: Integer; override;     function GetElements(AIndex: Integer): TSGEVertexElement; override;     procedure SetElements(AIndex: Integer; AElement: TSGEVertexElement); override;   public     constructor Create;     destructor Destroy; override;     procedure AddElement(ASource, AOffset: Integer;       AType: TSGE_VertexElementType; AUsage: TSGE_VertexElementUsage;       AIndex: Integer); override;     procedure DeleteElement(AIndex: Integer); override;     procedure DeleteAllElements; override;     property Count: Integer read GetCount;     property Elements[AIndex: Integer]: TSGEVertexElement read GetElements write SetElements;   end; implementation { TVertexDeclaration } function TVertexDeclaration.GetCount: Integer; begin   Result := FElements.Count; end; function TVertexDeclaration.GetElements(AIndex: Integer): TSGEVertexElement; begin   Result := PSGEVertexElement(FElements[AIndex])^; end; procedure TVertexDeclaration.SetElements(AIndex: Integer;   AElement: TSGEVertexElement); begin   PSGEVertexElement(FElements[AIndex])^ := AElement; end; constructor TVertexDeclaration.Create; begin   FElements := TList.Create; end; destructor TVertexDeclaration.Destroy; begin   DeleteAllElements;   FElements.Free;   inherited; end; procedure TVertexDeclaration.AddElement(ASource, AOffset: Integer;   AType: TSGE_VertexElementType; AUsage: TSGE_VertexElementUsage;   AIndex: Integer); var   Element: PSGEVertexElement; begin   New(Element);   Element^.ElementSource := ASource;   Element^.ElementOffset := AOffset;   Element^.ElementType := AType;   Element^.ElementUsage := AUsage;   Element^.ElementIndex := AIndex;   FElements.Add(Element); end; procedure TVertexDeclaration.DeleteElement(AIndex: Integer); begin   Dispose(PSGEVertexElement(FElements[AIndex]));   FElements.Delete(AIndex); end; procedure TVertexDeclaration.DeleteAllElements; var   I: Integer; begin   for I := 0 to FElements.Count - 1 do     Dispose(PSGEVertexElement(FElements[I]));   FElements.Clear; end; end.

Kao sto se vidi iz koda, ceo posao za nas vrsi TList klasa. Na nama je samo da kreiramo i oslobadjamo vertex element. Jedino na sta treba obratiti pasnju su funkcije za dobijanje i postavljanje vertex elementa. Funkcija za citanje vraca kopiju elementa, sto znaci da promene na rezultatu nece uticati na element u nizu. Potrebno je eksplicitno postaviti element, cime se vrednost parametra kopira nazad u niz.

Klasa TVertexBufferBinding ce biti prva koja ce koristiti funkcije TSGEReferenceCounted klase:

unit SGEVertexBufferBinding; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes; type   { TVertexBufferBinding }   TVertexBufferBinding = class(TSGEVertexBufferBinding)   private     FVertexBuffers: TList;   protected     function GetVertexBuffers(AIndex: Integer): TSGEVertexBuffer; override;     procedure SetVertexBuffers(AIndex: Integer; AVertexBuffer: TSGEVertexBuffer); override;     function GetCount: Integer; override;   public     constructor Create;     destructor Destroy; override;     procedure AddVertexBuffer(AVertexBuffer: TSGEVertexBuffer; AIndex: Integer = -1); override;     procedure DeleteVertexBuffer(AIndex: Integer); override;     procedure DeleteAllVertexBuffers; override;     procedure RemoveVertexBuffer(AVertexBuffer: TSGEVertexBuffer); override;     property VertexBuffers[AIndex: Integer]: TSGEVertexBuffer read GetVertexBuffers write SetVertexBuffers;     property Count: Integer read GetCount;   end; implementation { TVertexBufferBinding } function TVertexBufferBinding.GetVertexBuffers(AIndex: Integer   ): TSGEVertexBuffer; begin   {$IFDEF SGE_Debug}   Assert(AIndex < FVertexBuffers.Count, 'Invalid index');   {$ENDIF}   if AIndex >= FVertexBuffers.Count then     Result := nil   else     Result := TSGEVertexBuffer(FVertexBuffers[AIndex]); end; procedure TVertexBufferBinding.SetVertexBuffers(AIndex: Integer;   AVertexBuffer: TSGEVertexBuffer); begin   {$IFDEF SGE_Debug}   Assert(AIndex < FVertexBuffers.Count, 'Invalid index');   {$ENDIF}   if AIndex >= FVertexBuffers.Count then     AddVertexBuffer(AVertexBuffer, AIndex)   else   begin     if FVertexBuffers[AIndex] <> nil then       TSGEVertexBuffer(FVertexBuffers[AIndex]).Drop;     FVertexBuffers[AIndex] := AVertexBuffer;     if FVertexBuffers[AIndex] <> nil then       TSGEVertexBuffer(FVertexBuffers[AIndex]).Grab;   end; end; function TVertexBufferBinding.GetCount: Integer; begin   Result := FVertexBuffers.Count; end; constructor TVertexBufferBinding.Create; begin   FVertexBuffers := TList.Create; end; destructor TVertexBufferBinding.Destroy; begin   DeleteAllVertexBuffers;   FVertexBuffers.Free;   inherited; end; procedure TVertexBufferBinding.AddVertexBuffer(AVertexBuffer: TSGEVertexBuffer; AIndex: Integer); var   I: Integer; begin   {$IFDEF SGE_Debug}   Assert(AIndex >= FVertexBuffers.Count, 'Invalid index');   {$ENDIF}   if AIndex < FVertexBuffers.Count then     SetVertexBuffers(AIndex, AVertexBuffer)   else   begin     for I := 0 to (FVertexBuffers.Count - AIndex - 1) do       FVertexBuffers.Add(nil);     FVertexBuffers.Add(AVertexBuffer);     if FVertexBuffers[AIndex] <> nil then       TSGEVertexBuffer(FVertexBuffers[AIndex]).Grab;   end; end; procedure TVertexBufferBinding.DeleteVertexBuffer(AIndex: Integer); begin   if TSGEVertexBuffer(FVertexBuffers[AIndex]) <> nil then     TSGEVertexBuffer(FVertexBuffers[AIndex]).Drop;   FVertexBuffers.Delete(AIndex); end; procedure TVertexBufferBinding.DeleteAllVertexBuffers; var   I: Integer; begin   for I := 0 to FVertexBuffers.Count - 1 do     if TSGEVertexBuffer(FVertexBuffers[I]) <> nil then       TSGEVertexBuffer(FVertexBuffers[I]).Drop;   FVertexBuffers.Clear; end; procedure TVertexBufferBinding.RemoveVertexBuffer(   AVertexBuffer: TSGEVertexBuffer); var   I: Integer; begin   I := FVertexBuffers.IndexOf(AVertexBuffer);   {$IFDEF SGE_Debug}   Assert(I > -1, 'Invalid vertex buffer');   {$ENDIF}   DeleteVertexBuffer(I); end; end.

Kao i prethodna klasa, i ova za cuvanje podataka koristi TList klasu. Razlika je u tome sto su elementi ove liste ovjekti koji nasledjuju klasu TSGEReferenceCounted, a za njihov pravilan rad je bitno da svaki put kad uzmemo njihovu referencu pozovemo Grab, a kad nam vise ne treba Drop. Na taj nacin ce se TSGEReferenceCounted klase same brinuti o svom oslobadjanju. Osim toga, dodane su i neke provere za debug mod tako da cemo znati ako greskom pokusamo da pristupimo do nepostojeceg buffer-a.

Za sad cemo dodati jos implementaciju klase TRenderOperation koja je u sustini samo prakticniji nacin za prosledjivanje TVertexDeclaration, TVertexBufferBinding, index buffer-a i parametara koji odredjuju koji vertex-i ce se iscrtati. Osim toga ce umesto nas zvati Grab i Drop funkcije za TVertexDeclaration, TVertexBufferBinding i index buffer kad god ih budemo postavljali ili uklanjali:

unit SGERenderOperation; {$I SGE.inc} interface uses   SysUtils, SGETypes; type   { TRenderOperation }   TRenderOperation = class(TSGERenderOperation)   private     FVertexDeclaration: TSGEVertexDeclaration;     FVertexBufferBinding: TSGEVertexBufferBinding;     FIndexBuffer: TSGEIndexBuffer;     FVertexOffset: Integer;     FIndexOffset: Integer;     FVertexCount: Integer;   protected     function GetVertexDeclaration: TSGEVertexDeclaration; override;     procedure SetVertexDeclaration(AVertexDeclaration: TSGEVertexDeclaration); override;     function GetVertexBufferBinding: TSGEVertexBufferBinding; override;     procedure SetVertexBufferBinding(AVertexBufferBinding: TSGEVertexBufferBinding); override;     function GetIndexBuffer: TSGEIndexBuffer; override;     procedure SetIndexBuffer(AIndexBuffer: TSGEIndexBuffer); override;     function GetVertexOffset: Integer; override;     procedure SetVertexOffset(AVertexOffset: Integer); override;     function GetIndexOffset: Integer; override;     procedure SetIndexOffset(AIndexOffset: Integer); override;     function GetVertexCount: Integer; override;     procedure SetVertexCount(AVertexCount: Integer); override;   public     constructor Create;     destructor Destroy; override;     property VertexDeclaration: TSGEVertexDeclaration read GetVertexDeclaration write SetVertexDeclaration;     property VertexBufferBinding: TSGEVertexBufferBinding read GetVertexBufferBinding write SetVertexBufferBinding;     property IndexBuffer: TSGEIndexBuffer read GetIndexBuffer write SetIndexBuffer;     property VertexOffset: Integer read GetVertexOffset write SetVertexOffset;     property IndexOffset: Integer read GetIndexOffset write SetIndexOffset;     property VertexCount: Integer read GetIndexOffset write SetIndexOffset;   end; implementation { TRenderOperation } function TRenderOperation.GetVertexDeclaration: TSGEVertexDeclaration; begin   Result := FVertexDeclaration; end; procedure TRenderOperation.SetVertexDeclaration(   AVertexDeclaration: TSGEVertexDeclaration); begin   if FVertexDeclaration <> nil then     FVertexDeclaration.Drop;   FVertexDeclaration := AVertexDeclaration;   if FVertexDeclaration <> nil then     FVertexDeclaration.Grab; end; function TRenderOperation.GetVertexBufferBinding: TSGEVertexBufferBinding; begin   Result := FVertexBufferBinding; end; procedure TRenderOperation.SetVertexBufferBinding(   AVertexBufferBinding: TSGEVertexBufferBinding); begin   if FVertexBufferBinding <> nil then     FVertexBufferBinding.Drop;   FVertexBufferBinding := AVertexBufferBinding;   if FVertexBufferBinding <> nil then     FVertexBufferBinding.Grab; end; function TRenderOperation.GetIndexBuffer: TSGEIndexBuffer; begin   Result := FIndexBuffer; end; procedure TRenderOperation.SetIndexBuffer(AIndexBuffer: TSGEIndexBuffer); begin   if FIndexBuffer <> nil then     FIndexBuffer.Drop;   FIndexBuffer := AIndexBuffer;   if FIndexBuffer <> nil then     FIndexBuffer.Grab; end; function TRenderOperation.GetVertexOffset: Integer; begin   Result := FVertexOffset; end; procedure TRenderOperation.SetVertexOffset(AVertexOffset: Integer); begin   FVertexOffset := AVertexOffset; end; function TRenderOperation.GetIndexOffset: Integer; begin   Result := FIndexOffset; end; procedure TRenderOperation.SetIndexOffset(AIndexOffset: Integer); begin   FIndexOffset := AIndexOffset; end; function TRenderOperation.GetVertexCount: Integer; begin   Result := FVertexCount; end; procedure TRenderOperation.SetVertexCount(AVertexCount: Integer); begin   FVertexCount := AVertexCount; end; constructor TRenderOperation.Create; begin   FVertexDeclaration := nil;   FVertexBufferBinding := nil;   FIndexBuffer := nil;   FVertexOffset := 0;   FIndexOffset := 0;   FVertexCount := 0; end; destructor TRenderOperation.Destroy; begin   VertexDeclaration := nil;   VertexBufferBinding := nil;   IndexBuffer := nil; end; end.

To je to za sada... sledece na redu je mala promena u organizaciji renderera (dodacemo baznu klasu koju ce svi rendereri nasledjivati), i dodavanje funkcija koje ce kreirati ove klase koje smo sad obradili.

offline
  • Pridružio: 03 Okt 2009
  • Poruke: 246

ali... taj dll vec postoji...

Ovo je kao kad bi nekog ko hoce da kupi automobil, ucish kako se proizvodi taj automobil...

Uostalom... ja ne znam ni jednu srednju skolu koja je matrice onako opisivala...

Uostalom onima koji nisu pocetnici, ovo i ne treba, imaju sve to u uputsvu davno napisanih klasa koje pozivas

gde je poenta?

E da... gledao sam , misim tvoj program, 3D , gde vam je problem kako nanznaciti kada koja nota ide da bi je player odsvirao...
Ubij me, ne mog da se setim kako se zvase, a mrzi me da trazim...

Mislim da vam je resenje format za gitar-Pro...
iz vise razloga,
1 imaju na tone obaradjenih muzika...Jedan zaista impozantan spisak od nekoliko hiljada pesama ( ima i domacih)
2 svaki je instrument obradjen posebno... vama treba samo gitara koliko sam skontao... paa, izvucite samo nju...

Nisam se mnogo zezao oko formata, ali sam koristio gitar pto , pa znam o cemu pricam...

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Napisano: 20 Avg 2010 20:05

Sta znam... moja srednja skola mora da je bila neka mnogo opasna cim smo matrice tako obradjivali Smile

zmmaj ::ali... taj dll vec postoji...

Ovo je kao kad bi nekog ko hoce da kupi automobil, ucish kako se proizvodi taj automobil...


Ni ja ne bih mogao bolje to da srocim... ovaj tutorijal je upravo to! Smile

Hmmm... koliko znam, nemam nikakvih problema sa notama... koristim GH i RB formate, tj standardne formate za igre tog tipa i sve radi savrseno. Tema u kojoj pisemo o tome je http://www.mycity.rs/3D-programiranje/Vasi-projekti.html pa tamo pisi odgovore koji su vezi sa tim. Ovde pisi samo ako ima veze sa ovim tutorijalom.

Dopuna: 20 Avg 2010 20:06

Evo, posle brzog dodavanja osnovne klase za renderer koju svi ostali rendereri nasledjuju, dobijamo kod koji izgleda ovako:

unit SGERenderer; {$I SGE.inc} interface uses   SysUtils, SGETypes; type   { TRenderer }   TRenderer = class(TSGERenderer)   protected     FEngine: TSGEEngine;     FInitialized: Boolean;     FRenderWindow: TSGERenderWindow;     FMatrix: array[TSGE_MatrixType] of TSGEMatrix;     procedure CheckInitialized;     function GetRenderWindow: TSGERenderWindow; override;     function GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; override;     procedure SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); override;   public     constructor Create(AEngine: TSGEEngine; AName: String);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     procedure SetWorldViewMatrix(AWorld, AView: TSGEMatrix); override;     function CreateVertexDeclaration: TSGEVertexDeclaration; override;     function CreateVertexBufferBinding: TSGEVertexBufferBinding; override;     function CreateRenderOperation: TSGERenderOperation; override;   end; implementation uses   SGEVertexDeclaration, SGEVertexBufferBinding, SGERenderOperation; { TRenderer } procedure TRenderer.CheckInitialized; begin   {$IFDEF SGE_Debug}   Assert(FInitialized, Name + ' not initialized');   {$ENDIF} end; function TRenderer.GetRenderWindow: TSGERenderWindow; begin   CheckInitialized;   Result := FRenderWindow; end; function TRenderer.GetMatrix(AType: TSGE_MatrixType): TSGEMatrix; begin   CheckInitialized;   Result := FMatrix[AType]; end; procedure TRenderer.SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); begin   CheckInitialized;   FMatrix[AType] := AMatrix; end; constructor TRenderer.Create(AEngine: TSGEEngine; AName: String); begin   inherited Create(AName);   FEngine := AEngine;   FInitialized := False;   FEngine.Logger.Log(Name + ' created', ESGE_LL_ENGINE); end; destructor TRenderer.Destroy; begin   Finalize;   FEngine.UnregisterRenderer(Self);   FEngine.Logger.Log(Name + ' destroyed', ESGE_LL_ENGINE);   inherited Destroy; end; procedure TRenderer.Initialize; begin   FRenderWindow := nil;   FInitialized := True;   FEngine.Logger.Log(Name + ' initialized', ESGE_LL_ENGINE); end; procedure TRenderer.Finalize; begin   FRenderWindow.Free;   FInitialized := False;   FEngine.Logger.Log(Name + ' finalized', ESGE_LL_ENGINE); end; procedure TRenderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); begin   CheckInitialized;   {$IFDEF SGE_Debug}   Assert(FRenderWindow = nil, 'Render window already exists');   {$ENDIF} end; procedure TRenderer.BeginFrame; begin   CheckInitialized; end; procedure TRenderer.EndFrame; begin   CheckInitialized; end; procedure TRenderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); begin   CheckInitialized; end; procedure TRenderer.SwapBuffers; begin   CheckInitialized; end; procedure TRenderer.SetWorldViewMatrix(AWorld, AView: TSGEMatrix); begin   CheckInitialized; end; function TRenderer.CreateVertexDeclaration: TSGEVertexDeclaration; begin   Result := TVertexDeclaration.Create; end; function TRenderer.CreateVertexBufferBinding: TSGEVertexBufferBinding; begin   Result := TVertexBufferBinding.Create; end; function TRenderer.CreateRenderOperation: TSGERenderOperation; begin   Result := TRenderOperation.Create; end; end.

Sva osnovna funkcionalnost (ona koju bi trebao svaki renderer trebalo da implementira na isti nacin) je implementirana u ovoj klasi. Iz koda se vidi da nema niceg sto je specificno na Windows ili Linux, odnosno OpenGL ili DirectX. Od funkcija koje pre nisu postojale, dodali smo funkcije koje kreiraju TSGEVertexDeclaration, TSGEVertexBufferBinding, TSGERenderOperation jer i njih treba da ima svaki renderer. Kad smo dodali ovu klasu, mozemo malo pojednostaviti klase za OpenGL i DirectX:

unit DX9Renderer; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, SGERenderer, Direct3D9; type   { TDX9Renderer }   TDX9Renderer = class(TRenderer)   private     FD3D: IDirect3D9;     function GetDevice: IDirect3DDevice9;   protected     procedure SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); override;   public     constructor Create(AEngine: TSGEEngine);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     procedure SetWorldViewMatrix(AWorld, AView: TSGEMatrix); override;     // Testing     procedure DrawSimpleTriangle; override;     // Testing     function SGEColorToDXColor(AColor: TSGEColor): TD3DColor;     function SGEMatrixToDXMatrix(AMatrix: TSGEMatrix): TD3DMatrix;   end; implementation uses   DX9Win32RenderWindow; { TDX9Renderer } function TDX9Renderer.GetDevice: IDirect3DDevice9; begin   Result := IDirect3DDevice9(StrToInt(FRenderWindow.Attribute['D3DDevice'])); end; procedure TDX9Renderer.SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); var   DXMatrixType: TD3DTransformStateType;   View: TSGEMatrix; begin   inherited SetMatrix(AType, AMatrix);   case AType of     ESGE_MT_PROJECTION: DXMatrixType := D3DTS_PROJECTION;     ESGE_MT_VIEW: DXMatrixType := D3DTS_VIEW;     ESGE_MT_WORLD: DXMatrixType := D3DTS_WORLD;     ESGE_MT_TEXTURE: DXMatrixType := D3DTS_TEXTURE0;   end;   if DXMatrixType <> D3DTS_VIEW then     GetDevice.SetTransform(DXMatrixType, SGEMatrixToDXMatrix(AMatrix))   else   begin     View := SGEMatrixMultiply(SGEMatrixScale(1, 1, -1), AMatrix);     GetDevice.SetTransform(DXMatrixType, SGEMatrixToDXMatrix(View));   end; end; constructor TDX9Renderer.Create(AEngine: TSGEEngine); begin   inherited Create(AEngine, 'Direct3D9 Renderer'); end; destructor TDX9Renderer.Destroy; begin   inherited Destroy; end; procedure TDX9Renderer.Initialize; begin   if FInitialized then     Exit;   FD3D := Direct3DCreate9(D3D_SDK_VERSION);   if FD3D = nil then     raise Exception.Create('Can not create Direct3D');   inherited Initialize; end; procedure TDX9Renderer.Finalize; begin   if not FInitialized then     Exit;   inherited Finalize;   FD3D := nil; end; procedure TDX9Renderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); var   MatrixType: TSGE_MatrixType; begin   inherited CreateWindow(ACaption, AWidth, AHeight, AColorDepth);   if not FInitialized then     Exit;   if FRenderWindow = nil then   begin     FRenderWindow := TDX9Win32RenderWindow.Create(FEngine, Self, FD3D, ACaption, AWidth, AHeight);     for MatrixType := Low(TSGE_MatrixType) to High(TSGE_MatrixType) do       Matrix[MatrixType] := SGEMatrixIdentity;   end; end; procedure TDX9Renderer.BeginFrame; begin   inherited BeginFrame;   if not FInitialized then     Exit;   GetDevice.BeginScene; end; procedure TDX9Renderer.EndFrame; begin   inherited EndFrame;   if not FInitialized then     Exit;   GetDevice.EndScene; end; procedure TDX9Renderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); var   Flags: Cardinal; begin   inherited Clear(AFrameBuffers, AColor, ADepth, AStencil);   if not FInitialized then     Exit;   Flags := 0;   if ESGE_FBT_COLOR in AFrameBuffers then     Flags := Flags or D3DCLEAR_TARGET;   if ESGE_FBT_DEPTH in AFrameBuffers then     Flags := Flags or D3DCLEAR_ZBUFFER;   if ESGE_FBT_STENCIL in AFrameBuffers then     Flags := Flags or D3DCLEAR_STENCIL;   GetDevice.Clear(0, nil, Flags, SGEColorToDXColor(AColor), ADepth, AStencil); end; procedure TDX9Renderer.SwapBuffers; begin   inherited SwapBuffers;   if not FInitialized then     Exit;   FRenderWindow.SwapBuffers; end; procedure TDX9Renderer.SetWorldViewMatrix(AWorld, AView: TSGEMatrix); begin   inherited SetWorldViewMatrix(AWorld, AView);   Matrix[ESGE_MT_WORLD] := AWorld;   Matrix[ESGE_MT_VIEW] := AView; end; procedure TDX9Renderer.DrawSimpleTriangle; var   VB: IDirect3DVertexBuffer9;   Data: Pointer; const   D3DFVF_TESTVERTEX = D3DFVF_XYZ;   Vertices: array[0..2] of TSGEVector3 = (     (X: 0; Y: 1; Z: 0),     (X: 1; Y: -1; Z: 0),     (X: -1; Y: -1; Z: 0)); begin   GetDevice.CreateVertexBuffer(3 * SizeOf(TSGEVector3), 0, D3DFVF_TESTVERTEX, D3DPOOL_DEFAULT, VB, nil);   VB.Lock(0, SizeOf(Vertices), Data, 0);   Move(Vertices, Data^, SizeOf(Vertices));   VB.Unlock;   GetDevice.SetStreamSource(0, VB, 0, SizeOf(TSGEVector3));   GetDevice.SetFVF(D3DFVF_TESTVERTEX);   GetDevice.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);   VB := nil; end; function TDX9Renderer.SGEColorToDXColor(AColor: TSGEColor): TD3DColor; begin   Result := D3DCOLOR_COLORVALUE(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha); end; function TDX9Renderer.SGEMatrixToDXMatrix(AMatrix: TSGEMatrix): TD3DMatrix; begin   Result := TD3DMatrix(AMatrix); end; end.

unit OGLRenderer; {$I SGE.inc} interface uses   SysUtils, Classes, SGETypes, SGERenderer, gl; type   { TOGLRenderer }   TOGLRenderer = class(TRenderer)   protected     procedure SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); override;   public     constructor Create(AEngine: TSGEEngine);     destructor Destroy; override;     procedure Initialize; override;     procedure Finalize; override;     procedure CreateWindow(ACaption: String = 'SGE'; AWidth: Integer = 800;       AHeight: Integer = 600; AColorDepth: Integer = 24); override;     procedure BeginFrame; override;     procedure EndFrame; override;     procedure Clear(AFrameBuffers: TSGE_FrameBufferTypeSet; AColor: TSGEColor;       ADepth: Single = 1; AStencil: Integer = 0); override;     procedure SwapBuffers; override;     procedure SetWorldViewMatrix(AWorld, AView: TSGEMatrix); override;     // Testing     procedure DrawSimpleTriangle; override;     // Testing   end; implementation uses   {$IFDEF SGE_Windows}   OGLWin32RenderWindow;   {$ELSE}   OGLX11RenderWindow;   {$ENDIF} { TOGLRenderer } procedure TOGLRenderer.SetMatrix(AType: TSGE_MatrixType; AMatrix: TSGEMatrix); var   OGLMatrixType: Cardinal;   WorldView: TSGEMatrix; begin   inherited SetMatrix(AType, AMatrix);   case AType of     ESGE_MT_PROJECTION: OGLMatrixType := GL_PROJECTION;     ESGE_MT_VIEW: OGLMatrixType := GL_MODELVIEW;     ESGE_MT_WORLD: OGLMatrixType := GL_MODELVIEW;     ESGE_MT_TEXTURE: OGLMatrixType := GL_TEXTURE;   end;   glMatrixMode(OGLMatrixType);   if OGLMatrixType <> GL_MODELVIEW then     glLoadMatrixf(@AMatrix.M[0])   else   begin     WorldView := SGEMatrixMultiply(FMatrix[ESGE_MT_WORLD], FMatrix[ESGE_MT_VIEW]);     glLoadMatrixf(@WorldView.M[0]);   end; end; constructor TOGLRenderer.Create(AEngine: TSGEEngine); begin   inherited Create(AEngine, 'OpenGL Renderer'); end; destructor TOGLRenderer.Destroy; begin   inherited Destroy; end; procedure TOGLRenderer.Initialize; begin   if FInitialized then     Exit;   inherited Initialize; end; procedure TOGLRenderer.Finalize; begin   if not FInitialized then     Exit;   inherited Finalize; end; procedure TOGLRenderer.CreateWindow(ACaption: String; AWidth: Integer;   AHeight: Integer; AColorDepth: Integer); var   MatrixType: TSGE_MatrixType; begin   inherited CreateWindow(ACaption, AWidth, AHeight, AColorDepth);   if not FInitialized then     Exit;   if FRenderWindow = nil then   begin     {$IFDEF SGE_Windows}     FRenderWindow := TOGLWin32RenderWindow.Create(FEngine, Self, ACaption, AWidth, AHeight);     {$ELSE}     FRenderWindow := TOGLX11RenderWindow.Create(FEngine, Self, ACaption, AWidth, AHeight);     {$ENDIF}     for MatrixType := Low(TSGE_MatrixType) to High(TSGE_MatrixType) do       Matrix[MatrixType] := SGEMatrixIdentity;   end; end; procedure TOGLRenderer.BeginFrame; begin   inherited BeginFrame;   if not FInitialized then     Exit; end; procedure TOGLRenderer.EndFrame; begin   inherited EndFrame;   if not FInitialized then     Exit; end; procedure TOGLRenderer.Clear(AFrameBuffers: TSGE_FrameBufferTypeSet;   AColor: TSGEColor; ADepth: Single; AStencil: Integer); var   Mask: Cardinal; begin   inherited Clear(AFrameBuffers, AColor, ADepth, AStencil);   if not FInitialized then     Exit;   Mask := 0;   if ESGE_FBT_COLOR in AFrameBuffers then   begin     glClearColor(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha);     Mask := Mask or GL_COLOR_BUFFER_BIT;   end;   if ESGE_FBT_DEPTH in AFrameBuffers then   begin     glClearDepth(ADepth);     Mask := Mask or GL_DEPTH_BUFFER_BIT;   end;   if ESGE_FBT_STENCIL in AFrameBuffers then   begin     glClearStencil(AStencil);     Mask := Mask or GL_STENCIL_BUFFER_BIT;   end;   glClear(Mask); end; procedure TOGLRenderer.SwapBuffers; begin   inherited SwapBuffers;   if not FInitialized then     Exit;   FRenderWindow.SwapBuffers; end; procedure TOGLRenderer.SetWorldViewMatrix(AWorld, AView: TSGEMatrix); var   WorldView: TSGEMatrix; begin   inherited SetWorldViewMatrix(AWorld, AView);   FMatrix[ESGE_MT_WORLD] := AWorld;   FMatrix[ESGE_MT_VIEW] := AView;   glMatrixMode(GL_MODELVIEW);   WorldView := SGEMatrixMultiply(AWorld, AView);   glLoadMatrixf(@WorldView.M[0]); end; procedure TOGLRenderer.DrawSimpleTriangle; begin   glBegin(GL_TRIANGLES);   glVertex3f(0, 1, 0);   glVertex3f(1, -1, 0);   glVertex3f(-1, -1, 0);   glEnd; end; end.

Ovo je ujedno i savrsena prilika da postavim ceo kod koji imamo do sada. Mozete primetiti da se glavni program uopste nije primenio iako smo kod u pozadini napisali drugacije... to je zato sto smo na pocetku tacno definisali sta svaka klasa mora da omoguci glavnom programu i sve dok taj deo koda ostaje isti, u glavnom programu ne moramo nista da menjamo bez obzira na to kako smo isprogramirali low level klase.

https://www.mycity.rs/must-login.png

Dopuna: 22 Avg 2010 19:40

Konacno sam stigao do dela gde mogu malo da protestiram sve ovo sto je do sad napisano i nasao sam jednu greskicu zbog kojeg se Z osa ne ponasa jednako u OpenGL i DirectX implementaciji. Greska je u delu kad sam rekao da View matrica mora da se invertuje po Z osi zbog razlike u matricama u OpenGL-u i DirectX-u, ali to vazi samo ako se koriste funkcije za kreiranje matrica koje dolaze uz njih, ali posto mi kreiramo nase matrice to onda nije potrebno.

Sad na crtanje Smile Ovog puta cemo napraviti klasu za vertex buffer, pomocnu klasicu za konverziju internih tipova u tipove potrebne za OpenGL i DirectX, i funkciju koja ce renderovati to sto smo napunili u buffer. Ovog puta cemo, za promenu, krenuti od DirectX implementacije jer je ona laksa. Kreiracemo pomocnu klasu za konverziju podataka:

unit DX9Support; {$I SGE.inc} interface uses   SysUtils, SGETypes, Direct3D9; type   { TDX9Support }   TDX9Support = class   private     FEngine: TSGEEngine;   public     constructor Create(AEngine: TSGEEngine);     procedure Initialize(AD3DDevice: IDirect3DDevice9);     function GetColor(AColor: TSGEColor): TD3DColor;     function GetMatrix(AMatrix: TSGEMatrix): TD3DMatrix;     function GetBufferUsage(ABufferUsage: TSGE_BufferUsage): Cardinal;     function GetLockType(ALockType: TSGE_LockType; ABufferUsage: TSGE_BufferUsage): Cardinal;     function GetElementType(AElementType: TSGE_VertexElementType): TD3DDeclType;     function GetElementUsage(AElementUsage: TSGE_VertexElementUsage): TD3DDeclUsage;     function GetVertexDeclaration(ADevice: IDirect3DDevice9;       AVertexDeclaration: TSGEVertexDeclaration): IDirect3DVertexDeclaration9;   end; procedure CreateSupport(AEngine: TSGEEngine); function GetSupport: TDX9Support; implementation var   Support: TDX9Support; procedure CreateSupport(AEngine: TSGEEngine); begin   Support := TDX9Support.Create(AEngine); end; function GetSupport: TDX9Support; begin   Result := Support; end; { TDX9Support } constructor TDX9Support.Create(AEngine: TSGEEngine); begin   FEngine := AEngine; end; procedure TDX9Support.Initialize(AD3DDevice: IDirect3DDevice9); var   D3D: IDirect3D9;   AdapterIdentifier: TD3DAdapterIdentifier9; begin   AD3DDevice.GetDirect3D(D3D);   D3D.GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, AdapterIdentifier);   FEngine.Logger.Log(Format('DirectX 9 on %s', [AdapterIdentifier.Description]), ESGE_LL_ENGINE); end; function TDX9Support.GetColor(AColor: TSGEColor): TD3DColor; begin   Result := D3DCOLOR_COLORVALUE(AColor.Red, AColor.Green, AColor.Blue, AColor.Alpha); end; function TDX9Support.GetMatrix(AMatrix: TSGEMatrix): TD3DMatrix; begin   Result := TD3DMatrix(AMatrix); end; function TDX9Support.GetBufferUsage(ABufferUsage: TSGE_BufferUsage): Cardinal; begin   Result := 0;   if ABufferUsage in [ESGE_BU_DYNAMIC, ESGE_BU_DYNAMIC_WRITE_ONLY, ESGE_BU_DYNAMIC_WRITE_ONLY_DISCARDABLE] then     Result := Result or D3DUSAGE_DYNAMIC;   if ABufferUsage in [ESGE_BU_WRITE_ONLY, ESGE_BU_DYNAMIC_WRITE_ONLY, ESGE_BU_DYNAMIC_WRITE_ONLY_DISCARDABLE] then     Result := Result or D3DUSAGE_WRITEONLY; end; function TDX9Support.GetLockType(ALockType: TSGE_LockType; ABufferUsage: TSGE_BufferUsage): Cardinal; begin   Result := 0;   case ALockType of     ESGE_LT_DISCARD:       if (GetBufferUsage(ABufferUsage) and D3DUSAGE_DYNAMIC) <> 0 then         Result := D3DLOCK_DISCARD;     ESGE_LT_READ_ONLY:       if (GetBufferUsage(ABufferUsage) and D3DUSAGE_WRITEONLY) = 0 then       Result := D3DLOCK_READONLY;     ESGE_LT_NO_OVERWRITE:       if (GetBufferUsage(ABufferUsage) and D3DUSAGE_DYNAMIC) <> 0 then         Result := D3DLOCK_NOOVERWRITE;   end; end; function TDX9Support.GetElementType(AElementType: TSGE_VertexElementType   ): TD3DDeclType; begin   case AElementType of     ESGE_VET_FLOAT1: Result := D3DDECLTYPE_FLOAT1;     ESGE_VET_FLOAT2: Result := D3DDECLTYPE_FLOAT2;     ESGE_VET_FLOAT3: Result := D3DDECLTYPE_FLOAT3;     ESGE_VET_FLOAT4: Result := D3DDECLTYPE_FLOAT4;     ESGE_VET_COLOUR: Result := D3DDECLTYPE_FLOAT4;     ESGE_VET_SHORT2: Result := D3DDECLTYPE_SHORT2;     ESGE_VET_SHORT4: Result := D3DDECLTYPE_SHORT4;   else     Result := D3DDECLTYPE_FLOAT3;   end; end; function TDX9Support.GetElementUsage(AElementUsage: TSGE_VertexElementUsage   ): TD3DDeclUsage; begin   case AElementUsage of     ESGE_VEU_POSITION: Result := D3DDECLUSAGE_POSITION;     ESGE_VEU_NORMAL: Result := D3DDECLUSAGE_NORMAL;     ESGE_VEU_DIFFUSE: Result := D3DDECLUSAGE_COLOR;     ESGE_VEU_TEXTURE_COORDINATES: Result := D3DDECLUSAGE_TEXCOORD;   else     Result := D3DDECLUSAGE_POSITION;   end; end; function TDX9Support.GetVertexDeclaration(ADevice: IDirect3DDevice9;   AVertexDeclaration: TSGEVertexDeclaration): IDirect3DVertexDeclaration9; var   I: Integer;   VertexElements: array of TD3DVertexElement9; begin   SetLength(VertexElements, AVertexDeclaration.Count + 1);   for I := 0 to AVertexDeclaration.Count - 1 do   begin     VertexElements[I].Stream := AVertexDeclaration.Elements[I].ElementSource;     VertexElements[I].Offset := AVertexDeclaration.Elements[I].ElementOffset;     VertexElements[I]._Type := GetElementType(AVertexDeclaration.Elements[I].ElementType);     VertexElements[I].Usage := GetElementUsage(AVertexDeclaration.Elements[I].ElementUsage);     VertexElements[I].UsageIndex := AVertexDeclaration.Elements[I].ElementIndex;     VertexElements[I].Method := D3DDECLMETHOD_DEFAULT;   end;   VertexElements[AVertexDeclaration.Count] := D3DDECL_END;   if ADevice.CreateVertexDeclaration(@VertexElements[0], Result) <> D3D_OK then     raise Exception.Create('Can not create vertex declaration');   SetLength(VertexElements, 0); end; end.

Prilikom inicijalizacije nema niceg posebnog, jedino sto radimo je upis poruke u log (verzija DirectX-a i ime graficke kartice).

Funkcije za konverziju boje i matrica su iz renderera premestene u ovu klasu.

GetBufferUsage funkcija nas tip za koriscenje buffer-a pretvara u DirectX tip sto je preciznije moguce. GetLockType radi isto, ali za tip nacin zakljucavanja buffer-a. Za vise informacija o nacinu koriscenja i zakljucavanja pogledajte dokumentaciju:
http://msdn.microsoft.com/en-us/library/bb172534(v=VS.85).aspx
http://msdn.microsoft.com/en-us/library/bb172568(v=VS.85).aspx

GetElementType konvertuje nas tip elementa u DirectX tip elementa koji su u sustini mapirani 1 na 1, isti slucaj je i sa GetElementUsage funkcijom koja daje nacin na koji zelimo da koristimo element.

Zadnja funkcija kreira DirectX verziju VertexDeclaration klase. Nasa klasa je u sustini napravljena na osnovu DirectX verzije (da bi laksali rad u OpenGL-u), i sada samo iz nase klase prebacujemo podatke u DirectX klasu.

Sada na implementaciju vertex buffer-a:

unit DX9VertexBuffer; {$I SGE.inc} interface uses   SysUtils, SGETypes, DX9Support, Direct3D9; type   { TDX9VertexBuffer }   TDX9VertexBuffer = class(TSGEVertexBuffer)   private     FVertexSize: Integer;     FVertexCount: Integer;     FBufferUsage: TSGE_BufferUsage;     FLocked: Boolean;     FDevice: IDirect3DDevice9;     FVertexBuffer: IDirect3DVertexBuffer9;   protected     function GetBufferUsage: TSGE_BufferUsage; override;     function GetBufferSize: Integer; override;     function GetVertexSize: Integer; override;     function GetLocked: Boolean; override;   public     constructor Create(ADevice: IDirect3DDevice9; AVertexSize, AVertexCount: Integer;       ABufferUsage: TSGE_BufferUsage);     destructor Destroy; override;     function Lock(AOffset, ASize: Integer; ALockType: TSGE_LockType): Pointer; override;     procedure Unlock; override;     property D3DVertexBuffer: IDirect3DVertexBuffer9 read FVertexBuffer;   end; implementation { TDX9VertexBuffer } function TDX9VertexBuffer.GetBufferUsage: TSGE_BufferUsage; begin   Result := FBufferUsage; end; function TDX9VertexBuffer.GetBufferSize: Integer; begin   Result := FVertexSize * FVertexCount; end; function TDX9VertexBuffer.GetVertexSize: Integer; begin   Result := FVertexSize; end; function TDX9VertexBuffer.GetLocked: Boolean; begin   Result := FLocked; end; constructor TDX9VertexBuffer.Create(ADevice: IDirect3DDevice9; AVertexSize,   AVertexCount: Integer; ABufferUsage: TSGE_BufferUsage); begin   inherited Create;   FVertexSize := AVertexSize;   FVertexCount := AVertexCount;   FBufferUsage := ABufferUsage;   FLocked := False;   FDevice := ADevice;   if ADevice.CreateVertexBuffer(FVertexSize * FVertexCount, GetSupport.GetBufferUsage(ABufferUsage), 0, D3DPOOL_MANAGED, FVertexBuffer, nil) <> D3D_OK then     raise Exception.Create('Can not create vertex buffer'); end; destructor TDX9VertexBuffer.Destroy; begin   if FLocked then     Unlock;   FVertexBuffer := nil;   inherited Destroy; end; function TDX9VertexBuffer.Lock(AOffset, ASize: Integer; ALockType: TSGE_LockType   ): Pointer; begin   {$IFDEF SGE_Debug}   Assert(not FLocked, 'Vertex buffer is already locked');   {$ENDIF}   if FVertexBuffer.Lock(AOffset, ASize, Result, GetSupport.GetLockType(ALockType, FBufferUsage)) <> D3D_OK then     raise Exception.Create('Can not lock vertex buffer');   FLocked := True; end; procedure TDX9VertexBuffer.Unlock; begin   {$IFDEF SGE_Debug}   Assert(FLocked, 'Vertex buffer is not locked');   {$ENDIF}   if FVertexBuffer.Unlock <> D3D_OK then     raise Exception.Create('Can not unlock vertex buffer');   FLocked := False; end; end.

I ova klasa je jedna od jednostavnijih, za razliku od OpenGL implementacije koja nas ceka Smile Prva zanimljiva funkcija je konstruktor u kojoj kreiramo vertex buffer koristeci Direct3D device klasu. Kao parametre uzima velicinu buffer-a u bajtovima, nacin na koji zelimo da koristimo buffer, format koji cemo koristiti (ako je taj parametar 0, onda format definisemo uz pomoc vertex declaration-a), memoriju u koju zelimo da upisemo buffer (managed znaci da ce DirectX sam da se brine o prenosu buffer-a u i iz graficke kartice), i zadnji parametar je nas rezultat u kojem ce biti upisan vertex buffer.

Destruktor jednostavno otkljuca buffer ako je bio zakljucan i oslobodi ga.

Lock i unlock funkcije zakljucavaju i otkljucavaju tj. omogucavaju nam da pisemo/citamo podatke. Lock kao parametre uzima koliko bajtova od pocetka zelimo da preskocimo, koliko bajtova zelimo da uzmemo i tip zakljucavanja. Otkljucavanje ne zahteva nikakve parametre.

To je klasa za vertex buffer. Pre nego sto napisemo funkciju za renderovanje, napisacemo jos i implementaciju vertex declaration klase za DirectX koja ce imati zapamcenu vertex declaration klasu koju kreira sam DirectX za lakse i brze crtanje:

unit DX9VertexDeclaration; {$I SGE.inc} interface uses   SysUtils, SGETypes, SGEVertexDeclaration, DX9Support, Direct3D9; type   { TVertexDeclaration }   { TDX9VertexDeclaration }   TDX9VertexDeclaration = class(TVertexDeclaration)   private     FChanged: Boolean;     FDevice: IDirect3DDevice9;     FVertexDeclaration: IDirect3DVertexDeclaration9;     function GetD3DVertexDeclaration: IDirect3DVertexDeclaration9;   protected     procedure SetElements(AIndex: Integer; AElement: TSGEVertexElement); override;   public     constructor Create(ADevice: IDirect3DDevice9);     destructor Destroy; override;     procedure AddElement(ASource, AOffset: Integer;       AType: TSGE_VertexElementType; AUsage: TSGE_VertexElementUsage;       AIndex: Integer = 0); override;     procedure DeleteElement(AIndex: Integer); override;     procedure DeleteAllElements; override;     property D3DVertexDeclaration: IDirect3DVertexDeclaration9 read GetD3DVertexDeclaration;   end; implementation { TDX9VertexDeclaration } function TDX9VertexDeclaration.GetD3DVertexDeclaration: IDirect3DVertexDeclaration9; begin   if FChanged then   begin     FVertexDeclaration := GetSupport.GetVertexDeclaration(FDevice, Self);     FChanged := False;   end;   Result := FVertexDeclaration; end; procedure TDX9VertexDeclaration.SetElements(AIndex: Integer;   AElement: TSGEVertexElement); begin   inherited SetElements(AIndex, AElement);   FChanged := True; end; constructor TDX9VertexDeclaration.Create(ADevice: IDirect3DDevice9); begin   inherited Create;   FDevice := ADevice;   FVertexDeclaration := nil;   FChanged := True; end; destructor TDX9VertexDeclaration.Destroy; begin   FVertexDeclaration := nil;   inherited Destroy; end; procedure TDX9VertexDeclaration.AddElement(ASource, AOffset: Integer;   AType: TSGE_VertexElementType; AUsage: TSGE_VertexElementUsage;   AIndex: Integer); begin   inherited AddElement(ASource, AOffset, AType, AUsage, AIndex);   FChanged := True; end; procedure TDX9VertexDeclaration.DeleteElement(AIndex: Integer); begin   inherited DeleteElement(AIndex);   FChanged := True; end; procedure TDX9VertexDeclaration.DeleteAllElements; begin   inherited DeleteAllElements;   FChanged := True; end; end.

Jedino sto ova klasa radi je kreiranje DirectX vertex declaration klase svaki put kad nam zatreba ako su se elementi u medjuvremnu promenili. U suprotnom na vraca zapamcenu vrednost.

Sada na funkciju za crtanje:

procedure TDX9Renderer.Render(ARenderOperation: TSGERenderOperation); var   Device: IDirect3DDevice9;   VertexBufferBinding: TSGEVertexBufferBinding;   I, LastVertexBufferIndex: Integer; begin   inherited Render(ARenderOperation);   Device := GetDevice;   Device.SetVertexDeclaration(TDX9VertexDeclaration(ARenderOperation.VertexDeclaration).D3DVertexDeclaration);   LastVertexBufferIndex := -1;   VertexBufferBinding := ARenderOperation.VertexBufferBinding;   for I := 0 to VertexBufferBinding.Count - 1 do     if VertexBufferBinding.VertexBuffers[I] <> nil then     begin       Device.SetStreamSource(I, TDX9VertexBuffer(VertexBufferBinding.VertexBuffers[I]).D3DVertexBuffer,         0, VertexBufferBinding.VertexBuffers[I].VertexSize);       LastVertexBufferIndex := I;     end     else       Device.SetStreamSource(I, nil, 0, 0);   for I := VertexBufferBinding.Count to FLastVertexBufferIndex do     Device.SetStreamSource(I, nil, 0, 0);   FLastVertexBufferIndex := LastVertexBufferIndex;   Device.DrawPrimitive(D3DPT_TRIANGLELIST, ARenderOperation.VertexOffset, ARenderOperation.VertexCount div 3); end;

Ova funkcija je relativno kratka jer smo ostale klase napisali da nam olaksaju ovaj posao. Na pocetku postavljemo vertex declaration za DirectX. Sledeci korak je ukljucivanje svih buffer-a koji ce se koristiti za crtanje, i iskljucivanje svih koji su nepotrebni ili su bili ranije ukljuceni. Na kraju crtamo to sto je u buffer-u preko funkcije DrawPrimitive. Nju cemo objasniti kasnije kad budemo obradili i OpenGL verziju.

To je to za sada... sad na veceru Smile

Dopuna: 22 Avg 2010 20:55

OpenGL verziju cemo poceti na isti nacin kao i DirectX verziju... od pomocne klase za konverziju podataka:

unit OGLSupport; {$I SGE.inc} interface uses   SysUtils, SGETypes, gl, glext; type   TOGLVersion = record     Major: Integer;     Minor: Integer;     Release: Integer;   end;   { TOGLSupport }   TOGLSupport = class   private     FEngine: TSGEEngine;     FHardwareBuffers: Boolean;     FMultitexture: Boolean;   public     constructor Create(AEngine: TSGEEngine);     procedure Initialize;     function StrToVer(Ver: String): TOGLVersion;     function GetBufferUsage(ABufferUsage: TSGE_BufferUsage): Cardinal;     function GetElementType(AElementType: TSGE_VertexElementType): Cardinal;     function GetElementTypeSize(AElementType: TSGE_VertexElementType): Cardinal;     property HardwareBuffers: Boolean read FHardwareBuffers;     property Multitexture: Boolean read FMultitexture;   end; procedure CreateSupport(AEngine: TSGEEngine); function GetSupport: TOGLSupport; implementation var   Support: TOGLSupport; procedure CreateSupport(AEngine: TSGEEngine); begin   Support := TOGLSupport.Create(AEngine); end; function GetSupport: TOGLSupport; begin   Result := Support; end; { TOGLSupport } constructor TOGLSupport.Create(AEngine: TSGEEngine); begin   FEngine := AEngine; end; procedure TOGLSupport.Initialize; var   Version: TOGLVersion; begin   Version := StrToVer(glGetString(GL_VERSION));   FHardwareBuffers := Load_GL_ARB_vertex_buffer_object;   FMultitexture := Load_GL_ARB_multitexture;   FEngine.Logger.Log(Format('OpenGL %s on %s', [glGetString(GL_VERSION), glGetString(GL_RENDERER)]), ESGE_LL_ENGINE);   if not FHardwareBuffers then     FEngine.Logger.Log('Hardware buffers not supported', ESGE_LL_WARNING);   if not FMultitexture then     FEngine.Logger.Log('Multitexture not supported', ESGE_LL_WARNING); end; function TOGLSupport.StrToVer(Ver: String): TOGLVersion; begin   Result.Major := StrToIntDef(Copy(Ver, 1, Pos('.', Ver) - 1), 0);   Delete(Ver, 1, Pos('.', Ver) + 1);   Result.Minor := StrToIntDef(Copy(Ver, 1, Pos('.', Ver) - 1), 0);   Delete(Ver, 1, Pos('.', Ver) + 1);   Result.Release := StrToIntDef(Ver, 0); end; function TOGLSupport.GetBufferUsage(ABufferUsage: TSGE_BufferUsage): Cardinal; begin   case ABufferUsage of     ESGE_BU_STATIC, ESGE_BU_WRITE_ONLY: Result := GL_STATIC_DRAW;     ESGE_BU_DYNAMIC, ESGE_BU_DYNAMIC_WRITE_ONLY: Result := GL_DYNAMIC_DRAW;     ESGE_BU_DYNAMIC_WRITE_ONLY_DISCARDABLE: Result := GL_STREAM_DRAW;   else     Result := GL_DYNAMIC_DRAW;   end; end; function TOGLSupport.GetElementType(AElementType: TSGE_VertexElementType   ): Cardinal; begin   case AElementType of     ESGE_VET_FLOAT1, ESGE_VET_FLOAT2, ESGE_VET_FLOAT3, ESGE_VET_FLOAT4:       Result := GL_FLOAT;     ESGE_VET_COLOUR:       Result := GL_FLOAT;     ESGE_VET_SHORT1, ESGE_VET_SHORT2, ESGE_VET_SHORT3, ESGE_VET_SHORT4:       Result := GL_SHORT;   else     Result := GL_FLOAT;   end; end; function TOGLSupport.GetElementTypeSize(AElementType: TSGE_VertexElementType   ): Cardinal; begin   case AElementType of     ESGE_VET_FLOAT1, ESGE_VET_SHORT1:       Result := 1;     ESGE_VET_FLOAT2, ESGE_VET_SHORT2:       Result := 2;     ESGE_VET_FLOAT3, ESGE_VET_SHORT3:       Result := 3;     ESGE_VET_FLOAT4, ESGE_VET_SHORT4, ESGE_VET_COLOUR:       Result := 4;   else     Result := 3;   end; end; end.

Prilikom inicijalizacije proveravamo da li drajver podrzava kreiranje buffer-a u memoriji graficke kartice, i da li podrzava vise tekstura odjednom. U slucaju da neka od te dve funkcionalnosti nije dostupna, ispisujemo upozorenje, ali se izvrsavanje nastavlja uz odredjena ogranicenja.

OpenGL ima jednu funkciju koja nije bila potrebna u DirectX verziji, a to je koliko elemenata ima odredjeni tip... DirectX prilikom dodeljivanja tipa elementa vertex-a upisuje vrednost koja obelezava i tip i kolicinu, recimo FLOAT3, OpenGL ima 2 podatka za to, tip (FLOAT) i kolicina (3).

Ok... sad na implementaciju vertex buffer-a:

unit OGLVertexBuffer; {$I SGE.inc} interface uses   SysUtils, SGETypes, OGLSupport, gl, glext; type   { TOGLVertexBuffer }   TOGLVertexBuffer = class(TSGEVertexBuffer)   private     FVertexSize: Integer;     FVertexCount: Integer;     FBufferUsage: TSGE_BufferUsage;     FLocked: Boolean;     FHWVertexBuffer: Cardinal;     FSWVertexBuffer: PByte;   protected     function GetBufferUsage: TSGE_BufferUsage; override;     function GetBufferSize: Integer; override;     function GetVertexSize: Integer; override;     function GetLocked: Boolean; override;   public     constructor Create(AVertexSize, AVertexCount: Integer; ABufferUsage: TSGE_BufferUsage);     destructor Destroy; override;     function Lock(AOffset, ASize: Integer; ALockType: TSGE_LockType): Pointer; override;     procedure Unlock; override;     property HWVertexBuffer: Cardinal read FHWVertexBuffer;     property SWVertexBuffer: PByte read FSWVertexBuffer;   end; implementation { TOGLVertexBuffer } function TOGLVertexBuffer.GetBufferUsage: TSGE_BufferUsage; begin   Result := FBufferUsage; end; function TOGLVertexBuffer.GetBufferSize: Integer; begin   Result := FVertexSize * FVertexCount; end; function TOGLVertexBuffer.GetVertexSize: Integer; begin   Result := FVertexSize; end; function TOGLVertexBuffer.GetLocked: Boolean; begin   Result := FLocked; end; constructor TOGLVertexBuffer.Create(AVertexSize, AVertexCount: Integer;   ABufferUsage: TSGE_BufferUsage); begin   inherited Create;   FVertexSize := AVertexSize;   FVertexCount := AVertexCount;   FBufferUsage := ABufferUsage;   FLocked := False;   if GetSupport.HardwareBuffers then   begin     glGenBuffersARB(1, @FHWVertexBuffer);     if FHWVertexBuffer = 0 then       raise Exception.Create('Can not create vertex buffer');     glBindBufferARB(GL_ARRAY_BUFFER_ARB, FHWVertexBuffer);     glBufferDataARB(GL_ARRAY_BUFFER_ARB, FVertexSize * FVertexCount,       nil, GetSupport.GetBufferUsage(ABufferUsage));   end   else     GetMem(FSWVertexBuffer, FVertexSize * FVertexCount); end; destructor TOGLVertexBuffer.Destroy; begin   if FLocked then     Unlock;   if GetSupport.HardwareBuffers then     glDeleteBuffersARB(1, @FHWVertexBuffer)   else     FreeMem(FSWVertexBuffer, FVertexSize * FVertexCount);   inherited Destroy; end; function TOGLVertexBuffer.Lock(AOffset, ASize: Integer; ALockType: TSGE_LockType   ): Pointer; var   Access: Cardinal;   Res: PByte; begin   {$IFDEF SGE_Debug}   Assert(not FLocked, 'Vertex buffer is already locked');   {$ENDIF}   if GetSupport.HardwareBuffers then   begin     glBindBufferARB(GL_ARRAY_BUFFER_ARB, FHWVertexBuffer);     if ALockType = ESGE_LT_DISCARD then       glBufferDataARB(GL_ARRAY_BUFFER_ARB, FVertexSize * FVertexCount,         nil, GetSupport.GetBufferUsage(FBufferUsage));     if FBufferUsage in [ESGE_BU_WRITE_ONLY, ESGE_BU_DYNAMIC_WRITE_ONLY, ESGE_BU_DYNAMIC_WRITE_ONLY_DISCARDABLE] then       Access := GL_WRITE_ONLY     else if ALockType = ESGE_LT_READ_ONLY then       Access := GL_READ_ONLY     else       Access := GL_READ_WRITE;     Res := glMapBufferARB(GL_ARRAY_BUFFER_ARB, Access);     if Res = nil then       raise Exception.Create('Can not lock vertex buffer');     Result := Res + AOffset;   end   else     Result := FSWVertexBuffer + AOffset;   FLocked := True; end; procedure TOGLVertexBuffer.Unlock; begin   {$IFDEF SGE_Debug}   Assert(FLocked, 'Vertex buffer is not locked');   {$ENDIF}   if GetSupport.HardwareBuffers then   begin     glBindBufferARB(GL_ARRAY_BUFFER_ARB, FHWVertexBuffer);     if glUnmapBufferARB(GL_ARRAY_BUFFER_ARB) = 0 then       raise Exception.Create('Can not unlock vertex buffer');   end;   FLocked := False; end; end.

Za razliku od DirectX verzije, ovde moramo podrzati 2 mogucnosti... jedna je da OpenGL podrzava funkcije za kreiranje buffer-a na grafickoj kartici, druga je da ne podrzava. Na srecu, implementacija se ne razlikuje toliko pa je sve stavljeno u jednu klasu.

U konstruktoru proveravamo da li hardversku buffer-i mogu da se krieraju i ako mogu kreiramo ga uz pomoc funkcije glGenBuffersARB, i zatim postavljamo nacin na koji zelimo da ga koristimo funkcijom glBufferDataARB. U slucaju da ne mozemo kreirati hardverski buffer, jednostavno uzimamo deo sistemske meorije za buffer.

Destruktor radi slicnu stvar... proverava da li su podrzani hardverski buffer-i i na osnovu toga odlucuje da li brise preko glDeleteBuffersARB funkcije ili samo oslobadja zauzetu memoriju.

Lock na osnovu tipa koriscenja i zakljucavanja odredjuje kako ce dobiti podatke iz buffer-a, ili jednostavno vraca memorijsku lokaciju iz sistemske memorije ako hardverski buffer-i nisu podrzani. Unlock je bitan samo za hardverske buffer-e... ako ih nema, nista nije potrebno. Za vise informacija o buffer-ima za OpenGL pogledajte sledeci link: http://www.opengl.org/wiki/Vertex_Buffer_Object

Sada na funkciju za crtanje... malo je duza od directX verzije, ali u sustini radi istu stvar:

procedure TOGLRenderer.Render(ARenderOperation: TSGERenderOperation); var   I: Integer;   VertexDeclaration: TSGEVertexDeclaration;   VertexBufferBinding: TSGEVertexBufferBinding;   VertexBuffer: TOGLVertexBuffer;   Data: PByte; begin   inherited Render(ARenderOperation);   VertexDeclaration := ARenderOperation.VertexDeclaration;   VertexBufferBinding := ARenderOperation.VertexBufferBinding;   for I := 0 to VertexDeclaration.Count - 1 do   begin     VertexBuffer := TOGLVertexBuffer(VertexBufferBinding.VertexBuffers[VertexDeclaration.Elements[I].ElementSource]);     if VertexBuffer <> nil then     begin       if GetSupport.HardwareBuffers then       begin         glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer.HWVertexBuffer);         Data := PByte(VertexDeclaration.Elements[I].ElementOffset);       end       else         Data := VertexBuffer.SWVertexBuffer + VertexDeclaration.Elements[I].ElementOffset;       if ARenderOperation.VertexOffset <> 0 then         Data := Data + (VertexBuffer.VertexSize * ARenderOperation.VertexOffset);       case VertexDeclaration.Elements[I].ElementUsage of         ESGE_VEU_POSITION:         begin           glVertexPointer(GetSupport.GetElementTypeSize(VertexDeclaration.Elements[I].ElementType),             GetSupport.GetElementType(VertexDeclaration.Elements[I].ElementType),             VertexBuffer.VertexSize, Data);           glEnableClientState(GL_VERTEX_ARRAY);         end;         ESGE_VEU_NORMAL:         begin           glNormalPointer(GetSupport.GetElementType(VertexDeclaration.Elements[I].ElementType),             VertexBuffer.VertexSize, Data);           glEnableClientState(GL_NORMAL_ARRAY);         end;         ESGE_VEU_DIFFUSE:         begin           glColorPointer(GetSupport.GetElementTypeSize(VertexDeclaration.Elements[I].ElementType),             GetSupport.GetElementType(VertexDeclaration.Elements[I].ElementType),             VertexBuffer.VertexSize, Data);           glEnableClientState(GL_COLOR_ARRAY);         end;         ESGE_VEU_TEXTURE_COORDINATES:         begin           if GetSupport.Multitexture then             glClientActiveTextureARB(GL_TEXTURE0_ARB + VertexDeclaration.Elements[I].ElementIndex)           else             if VertexDeclaration.Elements[I].ElementIndex <> 0 then               Continue;           glTexCoordPointer(GetSupport.GetElementTypeSize(VertexDeclaration.Elements[I].ElementType),             GetSupport.GetElementType(VertexDeclaration.Elements[I].ElementType),             VertexBuffer.VertexSize, Data);           glEnableClientState(GL_TEXTURE_COORD_ARRAY);         end;       end;     end;   end;   glDrawArrays(GL_TRIANGLES, 0, ARenderOperation.VertexCount);   glDisableClientState(GL_VERTEX_ARRAY);   glDisableClientState(GL_NORMAL_ARRAY);   glDisableClientState(GL_COLOR_ARRAY);   if GetSupport.Multitexture then   begin     for I := 0 to VertexDeclaration.Count - 1 do       if VertexDeclaration.Elements[I].ElementUsage = ESGE_VEU_TEXTURE_COORDINATES then       begin         glClientActiveTextureARB(GL_TEXTURE0_ARB + VertexDeclaration.Elements[I].ElementIndex);         glDisableClientState(GL_TEXTURE_COORD_ARRAY);       end;   end   else     glDisableClientState(GL_TEXTURE_COORD_ARRAY); end;

U OpenGL-u ne postoji vertex declaration klasa i zato moramo rucno da koristimo nasu verziju... na osnovu elemenata iz vertex declaration-a odredjujemo koji buffer treba da ukljucimo, a na osnovu tipa i nacina koriscenja odredjujemok ako taj buffer treba da iskoristimo. Posle toga zovemo funckiju glDrawArrays koja na osnovu podataka iz buffer-a crta objekte. Na kraju oslobadjamo sve buffer-e koje smo koristili.

Sad mozemo da se okrenemo i pogledamo funkcije za crtanje u OpenGL-u i DirectX-u... u njima postoji parametar koji kaze kako vertex-i moraju da se interpretiraju... za sada je u kodu zapisano da su vertex-i tacke trougla... svaka 3 vertex-a kreiraju jedan trougao... ali postoji jos nacina crtanja. Mozemo da crtamo tacke, linije, pojedinacne trouglove, liste trouglova u kojima se uzimaju 2 tacke od prethodnog trougla i dodaje se nova, i jos par nacina. Nas sledeci zadatak ce biti prosirivanje RenderOperation klase tako da mozemo i taj podatak da prosledimo rendereru, ali pre nego sto to uradimo, mogli bi da napravimo mali test ovoga do sada Smile Izbrisacemo testnu funkciju za crtanje trougla, i napraviti svoj trougao u glavnom programu koristeci klase koje smo do sada napravili. Kod nece biti kratak i jednostavan, ali za sada je bitno da na isti nacin mozemo da crtamo bez obzira na to koji renderer se koristi:

program HelloWorld; {$I SGE.inc} {$IFDEF SGE_Delphi} {$APPTYPE CONSOLE} {$ENDIF} uses   {$IFDEF SGE_Windows}   Windows,   {$ENDIF}   SysUtils,   SGETypes in '..\..\Common\SGETypes.pas',   HTMLLogger in 'HTMLLogger.pas',   {$IFDEF SGE_Windows}   DX9Renderer in '..\..\Source\Renderers\DX9Renderer\DX9Renderer.pas',   {$ENDIF}   OGLRenderer in '..\..\Source\Renderers\OGLRenderer\OGLRenderer.pas'; type   TMyVertex = packed record     X, Y, Z: Single;     R, G, B, A: Single;   end; const   Vertices: array[0..2] of TMyVertex = (     (X: 0; Y: 1; Z: 0; R: 1; G: 0; B: 0; A: 1),     (X: 1; Y: -1; Z: 0; R: 0; G: 1; B: 0; A: 1),     (X: -1; Y: -1; Z: 0; R: 0; G: 0; B: 1; A: 1)); var   Engine: TSGEEngine = nil;   HTML: THTMLLogger = nil;   {$IFDEF SGE_Windows}   DX9Rendr: TDX9Renderer = nil;   {$ENDIF}   OGLRendr: TOGLRenderer = nil;   RendrIndex: String;   I: Integer;   VertexBuffer: TSGEVertexBuffer;   VertexBufferData: Pointer;   VertexDeclaration: TSGEVertexDeclaration;   VertexBufferBinding: TSGEVertexBufferBinding;   RenderOperation: TSGERenderOperation; begin   try     try       HTML := THTMLLogger.Create('SGE.html');       Engine := CreateSGEEngine('', HTML.Log);       Engine.Logger.LogLevel := ESGE_LL_INFO;       {$IFDEF SGE_Windows}       DX9Rendr := TDX9Renderer.Create(Engine);       Engine.RegisterRenderer(DX9Rendr);       {$ENDIF}       OGLRendr := TOGLRenderer.Create(Engine);       Engine.RegisterRenderer(OGLRendr);       WriteLn('Registered renderers:');       for I := 0 to Engine.RendererCount - 1 do         WriteLn(Format('  %d. %s', [I + 1, Engine.Renderers[I].Name]));       WriteLn;       Write('Select renderer: ');       ReadLn(RendrIndex);       I := StrToIntDef(RendrIndex, 0) - 1;       if (I < 0) or (I >= Engine.RendererCount) then         Exit;       Engine.Initialize(Engine.Renderers[I]);       Engine.Renderer.CreateWindow('SGE using ' + Engine.Renderers[I].Name);       Engine.Renderer.Matrix[ESGE_MT_PROJECTION] := SGEMatrixPerspective(45,         Engine.Renderer.RenderWindow.Width / Engine.Renderer.RenderWindow.Height, 1, 1000);       Engine.Renderer.Matrix[ESGE_MT_VIEW] := SGEMatrixLookAt(         SGEVector3(0, 3, 5), SGEVector3(0, 0, 0), SGEVector3(0, 1, 0));       VertexBuffer := Engine.Renderer.CreateVertexBuffer(7 * SizeOf(Single), 3, ESGE_BU_STATIC);       VertexBufferData := VertexBuffer.Lock(0, 0, ESGE_LT_NORMAL);       Move(Vertices, VertexBufferData^, VertexBuffer.BufferSize);       VertexBuffer.Unlock;       VertexDeclaration := Engine.Renderer.CreateVertexDeclaration;       VertexDeclaration.AddElement(0, 0, ESGE_VET_FLOAT3, ESGE_VEU_POSITION);       VertexDeclaration.AddElement(0, 3 * SizeOf(Single), ESGE_VET_COLOUR, ESGE_VEU_DIFFUSE);       VertexBufferBinding := Engine.Renderer.CreateVertexBufferBinding;       VertexBufferBinding.AddVertexBuffer(VertexBuffer);       RenderOperation := Engine.Renderer.CreateRenderOperation;       RenderOperation.VertexDeclaration := VertexDeclaration;       RenderOperation.VertexBufferBinding := VertexBufferBinding;       RenderOperation.VertexCount := 3;       while Engine.ProcessMessages do       begin         Engine.Renderer.BeginFrame;         Engine.Renderer.Clear([ESGE_FBT_COLOR], SGEColor(0.4, 0.4, 1, 0));         Engine.Renderer.Matrix[ESGE_MT_WORLD] := SGEMatrixRotate(Engine.Timer.Time / 20, 0, 1, 0);         Engine.Renderer.Render(RenderOperation);         Engine.Renderer.EndFrame;         Engine.Renderer.SwapBuffers;       end;       RenderOperation.Drop;       VertexBufferBinding.Drop;       VertexDeclaration.Drop;       VertexBuffer.Drop;       Engine.Finalize;     except       on E: Exception do       begin         {$IFDEF SGE_Windows}         MessageBox(0, PChar(E.Message), PChar(ExtractFileName(ParamStr(0))), 0);         {$ELSE}         WriteLn(ExtractFileName(ParamStr(0)) + ': ' + E.Message);         {$ENDIF}       end;     end;   finally     {$IFDEF SGE_Windows}     DX9Rendr.Free;     {$ENDIF}     OGLRendr.Free;     Engine.Free;     HTML.Free;   end; end.

Kreiramo vertex buffer i u njega upisemo podatke (koordinate i boju), zatim popunimo vertex declaration i vertex buffer binding, i iskoristimo ih u render operation klasi. Render operation prosledjujemo funkciji render i dobijamo sliku na ekranu Smile Na kraju oslobadjamo zauzete resurse (zovemo Drop funkciju jer te klase nasledjuju TSGEReferenceCounted klasu).



https://www.mycity.rs/must-login.png

P.S. u ovom zip-u se nalaze i izvrsni fajlovi za Windows i Linux.

Dopuna: 22 Avg 2010 21:18

BTW upravo sam nasao jednu gresku u glext.pas unit-u zbog koje extenzije ne rade kako treba u Delphi 2009+. Izvrsna datoteka za Windows je napravljena s tom greskom i zbog toga ce u log-u pisati neka bezvezna verzija OpenGL-a i program nece moci da koristi hardverske buffer-e i multitexturing. Gresku sam popravio, a popravljeni fajlovi ce biti poslati sa sledecim tutorijalom.

Ko je trenutno na forumu
 

Ukupno su 1058 korisnika na forumu :: 42 registrovanih, 9 sakrivenih i 1007 gosta   ::   [ Administrator ] [ Supermoderator ] [ Moderator ] :: Detaljnije

Najviše korisnika na forumu ikad bilo je 3466 - dana 01 Jun 2021 17:07

Korisnici koji su trenutno na forumu:
Korisnici trenutno na forumu: A.R.Chafee.Jr., Apok, babaroga, bestguarder, bigfoot, bojank, CikaKURE, dane007, darkangel, Dimitrise93, FOX, Georgius, Goran 0000, ILGromovnik, indja, Još malo pa deda, Karla, Kibice, Lošmi, Luka Blažević, Marko Marković, mercedesamg, Mixelotti, mnn2, mrav pesadinac, Neutral-M, nuke92, Panter, Parker, procesor, Rogan33, shone34, slonic_tonic, sombrero, stegonosa, Trpe Grozni, uruk, vladulns, wizzardone, wulfy, Zoca, 1107