Poslao: 06 Avg 2010 10:08
|
offline
- Srki_82
- Moderator foruma
- 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 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
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.
|
|
|
Poslao: 09 Avg 2010 00:10
|
offline
- Srki_82
- Moderator foruma
- 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
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 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
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 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
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
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
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 )... sutra cemo napisati klase za prozore i renderer, i konacno cemo dobiti aplikaciju koja ce koristiti OpenGL da obrise prozor bojom koju odredimo
|
|
|
|
Poslao: 13 Avg 2010 22:57
|
offline
- Srki_82
- Moderator foruma
- 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 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
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
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
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
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
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 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.
|
|
|
|
Poslao: 18 Avg 2010 21:45
|
offline
- Srki_82
- Moderator foruma
- 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
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
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 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.
|
|
|
|
Poslao: 19 Avg 2010 15:07
|
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
|
|
|
|
Poslao: 19 Avg 2010 17:18
|
offline
- zmmaj
- Građanin
- Pridružio: 03 Okt 2009
- Poruke: 246
|
ma sve je vrlo prosto i jednostavno...
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...
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..?
|
|
|
|
Poslao: 20 Avg 2010 17:28
|
offline
- Srki_82
- Moderator foruma
- 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" 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.
|
|
|
|
Poslao: 20 Avg 2010 19:44
|
offline
- zmmaj
- Građanin
- 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...
|
|
|
|
Poslao: 22 Avg 2010 21:18
|
offline
- Srki_82
- Moderator foruma
- 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
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!
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 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 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
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 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 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.
|
|
|
|