Sordie.co.uk

libsassy/libSassy.OpenGL.pas

Raw

{(
 )) libSassy.OpenGL
((    OpenGL library
 ))
((  Copyright  Sordie Aranka Solomon-Smith 2015
 ))
((  This work is made available under the terms of the Creative Commons
 )) Attribution-NonCommercial-ShareAlike 3.0 Unported license
((  http://creativecommons.org/licenses/by-nc-sa/3.0/
 )}

unit libSassy.OpenGL;

interface

uses
  Winapi.Windows,
  Winapi.Messages,

  libSassy.Strings,
  libSassy.FileSystem,
  libSassy.Streams,
  libSassy.Windows,
  libSassy.Errors,
  libSassy.SceneGraph,
  libSassy.Geometry,
  libSassy.Bitmaps,
  libSassy.Threads,

  dglOpenGL;

type
  TOpenGLError = class(TException);

{$REGION 'TOpenGLWindow'}
  TOpenGLWindow = class(TEngineWindow)
  private
    fDeviceContext: HDC;
    fRenderContext: HGLRC;

    fMultiSampleLevel:  Integer;
    fMultiSampleFormat: GLuint;

    fActiveScene: TSceneNode;
  public
    Projection: TMatrix4;
    Camera:     TMatrix4;

    constructor Create(const AWidth, AHeight: Integer; const AFullScreen: Boolean = False);

    procedure CreateWindow;           override;
    function  DestroyWindow: Boolean; override;

    function MakeCurrent: Boolean; inline;

    procedure Present; override;

    procedure SetDisplaySize(const AWidth, AHeight: Integer; const AFullScreen: Boolean = False); override;

    function InitializeAssets: Boolean; override;

    function Cycle (const Delta: Extended): Boolean; override;
    function Render(const Delta: Extended): Boolean; override;

    property DeviceContext: HDC   read fDeviceContext;
    property RenderContext: HGLRC read fRenderContext;

    property MultiSampleLevel: Integer read fMultiSampleLevel;

    property ActiveScene: TSceneNode read fActiveScene write fActiveScene;
  end;
{$ENDREGION}

{$REGION 'TOpenGLView'}
  TOpenGLView = class abstract(TSceneNode)
  private
    fWindow: TOpenGLWindow;
    fDepth:  Single;
  public
    constructor Create(const AWindow: TOpenGLWindow);

    function Process(const Delta: Extended): Boolean; override;

    property Window: TOpenGLWindow read fWindow write fWindow;
    property Depth:  Single        read fDepth  write fDepth;
  end;
{$ENDREGION}

{$REGION 'TOpenGLView2D'}
  TOpenGLView2D = class(TOpenGLView)
  public
    class procedure View(const AWindow: TOpenGLWindow; const Depth: Single = 100);

    function Process(const Delta: Extended): Boolean; override;
  end;
{$ENDREGION}

{$REGION 'TOpenGLView3D'}
  TOpenGLView3D = class(TOpenGLView)
  private
    fFOV: Single;
  public
    class procedure View(const AWindow: TOpenGLWindow; const FOV: Single = 45; const Depth: Single = 100);

    function Process(const Delta: Extended): Boolean; override;

    property FOV: Single read fFOV write fFOV;
  end;
{$ENDREGION}

{$REGION 'TOpenGLViewIsometric'}
  TOpenGLViewIsometric = class(TOpenGLView)
  public
    class procedure View(const AWindow: TOpenGLWindow; const Depth: Single = 100);

    function Process(const Delta: Extended): Boolean; override;
  end;
{$ENDREGION}

{$REGION 'TOpenGLCamera3D'}
  TOpenGLCamera3D = class(TOpenGLView3D)
  public
    Position: TVector4;
    Target:   TVector4;

    function Process(const Delta: Extended): Boolean; override;
  end;
{$ENDREGION}

{$REGION 'TOpenGLLight'}
  TOpenGLLight = class(TSceneNode)
  private
    fID: GLuint;
  public
    Ambient:  TVector4;
    Diffuse:  TVector4;
    Specular: TVector4;
    Position: TVector4;

    constructor Create(const AID: GLuint);

    function Process(const Delta: Extended): Boolean; override;

    property ID: GLuint read fID;
  end;
{$ENDREGION}

{$REGION 'TOpenGLMaterial'}
  TOpenGLMaterial = class(TSceneNode)
  public
    Ambient:   TVector4;
    Diffuse:   TVector4;
    Specular:  TVector4;
    Shininess: TVector4;
    Emission:  TVector4;

    function Process(const Delta: Extended): Boolean; override;
  end;
{$ENDREGION}

{$REGION 'TOpenGLQuadric'}
  TOpenGLQuadric = class
  private
    fQuadric: PGLUQuadric;

    fTextured: Boolean;
    fFlat:     Boolean;
    fOutside:  Boolean;

    class var fStock: TOpenGLQuadric;

    procedure SetTextured(Value: Boolean);
    procedure SetFlat    (Value: Boolean);
    procedure SetOutside (Value: Boolean);
  public
    constructor Create;
    destructor  Destroy; override;

    class constructor Create;
    class destructor  Destroy;

    class function Stock: TOpenGLQuadric;

    property Quadric: PGLUQuadric read fQuadric;

    property Textured: Boolean read fTextured write SetTextured;
    property Flat:     Boolean read fFlat     write SetFlat;
    property Outside:  Boolean read fOutside  write SetOutside;
  end;
{$ENDREGION}

{$REGION 'TOpenGLRender'}
  TOpenGLRender = class(TSceneNode)
  private
    fID: GLuint;
  public
    constructor Create;
    destructor  Destroy; override;

    class function CreateList(From: TProcedure): GLuint;

    class procedure DrawTriangle(a, b, c: TVector4);

    procedure DoRender; virtual;

    procedure Compile;
    procedure Uncompile;

    function Process(const Delta: Extended): Boolean; override;

    property ID: GLuint read fID write fID;
  end;
{$ENDREGION}

{$REGION 'TOpenGLPlain'}
  TOpenGLPlain = class(TOpenGLRender)
  public
    class procedure Draw;

    procedure DoRender; override;
  end;
{$ENDREGION}

{$REGION 'TOpenGLCube'}
  TOpenGLCube = class(TOpenGLRender)
  private
    fFaceTextures: Boolean;
  public
    class procedure Draw(const FaceTextures: Boolean = True);

    procedure DoRender; override;

    property FaceTextures: Boolean read fFaceTextures write fFaceTextures;
  end;
{$ENDREGION}

{$REGION 'TOpenGLIcosahedron'}
  TOpenGLIcosahedron = class(TOpenGLRender)
  private
    fFlat: Boolean;
  public
    const X = 0.525731112119133606;
    const Z = 0.850650808352039932;

    const VertexData: array[0..11] of array[0..2] of Single = (
      (-X,	0,  Z),
      ( X,	0,  Z),
      (-X,	0, -Z),
      ( X,	0, -Z),
      ( 0,  Z,  X),
      ( 0,  Z, -X),
      ( 0, -Z,  X),
      ( 0, -Z, -X),
      ( Z,  X,  0),
      (-Z,  X,  0),
      ( Z, -X,  0),
      (-Z, -X,  0)
    );

    const VertexIndex: array[0..19, 0..2] of Integer = (
  		(0, 4, 1),	(0, 9, 4),
	  	(9, 5, 4),	(4, 5, 8),
		  (4, 8, 1),	(8, 10, 1),
		  (8, 3, 10),	(5, 3, 8),
		  (5, 2, 3),	(2, 7, 3),
		  (7, 10, 3),	(7, 6, 10),
		  (7, 11, 6),	(11, 0, 6),
		  (0, 1, 6),	(6, 1, 10),
		  (9, 0, 11),	(9, 11, 2),
		  (9, 2, 5),	(7, 2, 11)
    );

    const N1 = 0.356822103261948;
    const N2 = 0.934172332286835;
    const N3 = 0.577350258827209;

    const NormalData: array[0..19] of array[0..2] of Single = (
      (  0,  N1,  N2),
      (-N3,  N3,  N3),
      (-N1,  N2,   0),
      ( N1,  N2,   0),
      ( N3,  N3,  N3),
      ( N2,   0,  N1),
      ( N2,   0, -N1),
      ( N3,  N3, -N3),
      (  0,  N1, -N2),
      (  0, -N1, -N2),
      ( N3, -N3, -N3),
      ( N1, -N2,   0),
      (-N1, -N2,   0),
      (-N3, -N3,  N3),
      (  0, -N1,  N2),
      ( N3, -N3,  N3),
      (-N2,   0,  N1),
      (-N2,   0, -N1),
      (-N3,  N3, -N3),
      (-N3, -N3, -N3)
    );

    class procedure Draw(const Flat: Boolean = True);

    procedure DoRender; override;

    property Flat: Boolean read fFlat write fFlat;
  end;
{$ENDREGION}

{$REGION 'TOpenGLSphere'}
  TOpenGLSphere = class(TOpenGLRender)
  private
    fRadius:  Single;
    fSlices:  Integer;
    fStacks:  Integer;
    fQuadric: TOpenGLQuadric;
  public
    class procedure Draw(const Radius: Single = 1; const Slices: Integer = 8; const Stacks: Integer = -1; Quadric: TOpenGLQuadric = nil);

    procedure DoRender; override;

    property Radius:  Single         read fRadius  write fRadius;
    property Slices:  Integer        read fSlices  write fSlices;
    property Stacks:  Integer        read fStacks  write fStacks;
    property Quadric: TOpenGLQuadric read fQuadric write fQuadric;
  end;
{$ENDREGION}

{$REGION 'TOpenGLCylinder'}
  TOpenGLCylinder = class(TOpenGLRender)
  private
    fBaseRadius: Single;
    fTopRadius:  Single;
    fHeight:     Single;
    fSlices:     Integer;
    fStacks:     Integer;
    fQuadric:    TOpenGLQuadric;
  public
    class procedure Draw(const BaseRadius, TopRadius, Height: Single; const Slices: Integer = 8; const Stacks: Integer = 1; Quadric: TOpenGLQuadric = nil);

    procedure DoRender; override;

    property BaseRadius: Single         read fBaseRadius write fBaseRadius;
    property TopRadius:  Single         read fTopRadius  write fBaseRadius;
    property Height:     Single         read fHeight     write fHeight;
    property Slices:     Integer        read fSlices     write fSlices;
    property Stacks:     Integer        read fStacks     write fStacks;
    property Quadric:    TOpenGLQuadric read fQuadric    write fQuadric;
  end;
{$ENDREGION}

{$REGION 'TOpenGLCone'}
  TOpenGLCone = class(TOpenGLRender)
  private
    fRadius: Single;
    fHeight: Single;
    fSlices: Integer;
  public
    class procedure Draw(const Radius, Height: Single; const Slices: Integer = 6);

    procedure DoRender; override;

    property Radius: Single  read fRadius write fRadius;
    property Height: Single  read fHeight write fHeight;
    property Slices: Integer read fSlices write fSlices;
  end;
{$ENDREGION}

{$REGION 'TOpenGLShader'}
  TOpenGLShader = class
  private
    fID:         GLuint;
    fShaderType: GLenum;
  public
    constructor Create(const ASource: AnsiString; const AShaderType: GLenum);
    destructor  Destroy; override;

    property ID: GLuint read fID;

    property ShaderType: GLenum read fShaderType;
  end;
{$ENDREGION}

{$REGION 'TOpenGLShaderProgram'}
  TOpenGLShaderProgram = class(TSceneNode)
  private
    fID: GLuint;

    fVertexShader:   TOpenGLShader;
    fFragmentShader: TOpenGLShader;

    fUniformMap: array[0..13] of GLint;
  public
{$REGION 'Basic shader source'}
    const BasicVertexShader   = 'varying vec3 n,l,e;void main(){n=gl_NormalMatrix*gl_Normal;vec3 v=vec3(gl_ModelViewMatrix*gl_Vertex);l=vec3(gl_LightSource[0].position.xyz-v);e=-v;gl_Position=ftransform();gl_TexCoord[0]=gl_MultiTexCoord0;}';
    const BasicFragmentShader = 'varying vec3 n,l,e;uniform sampler2D tex0;void main(){vec4 c=texture2D(tex0,gl_TexCoord[0].xy);vec4 f=(gl_LightSource[0].ambient*gl_FrontMaterial.ambient)*c;vec3 N=normalize(n);vec3 L=normalize(l);float t=dot(N,L);if(t>0){f+=gl_LightSource[0].diffuse*'+
      'gl_FrontMaterial.diffuse*t*c;vec3 E=normalize(e);vec3 R=reflect(-L,N);float s=pow(max(dot(R,E),0),gl_FrontMaterial.shininess);f+=gl_LightSource[0].specular*gl_FrontMaterial.specular*s;}gl_FragColor=f;gl_FragColor.a=1;}';
{$ENDREGION}

    constructor Create(const AVertexSrc: AnsiString = BasicVertexShader; const AFragmentSrc: AnsiString = BasicFragmentShader);
    destructor  Destroy; override;

    class function CreateFromFiles(const AVertexFileName, AFragmentFileName: String): TOpenGLShaderProgram;

    function MapUniform(const Name: AnsiString; const UID: Integer): GLint;
    procedure Uniform(const UID:  Integer;    Value: array of GLfloat); overload;
    procedure Uniform(const Name: AnsiString; Value: array of GLfloat); overload;

    procedure Bind;   inline;
    procedure Unbind; inline;

    function Process(const Delta: Extended): Boolean; override;

    property ID: GLuint read fID;

    property VertexShader:   TOpenGLShader read fVertexShader;
    property FragmentShader: TOpenGLShader read fFragmentShader;
  end;
{$ENDREGION}

{$REGION 'TOpenGLTexture'}
  TOpenGLTexture = class(TSceneNode)
  private
    fID: GLuint;

    fWidth:  Integer;
    fHeight: Integer;

    fTexelXRatio: Single;
    fTexelYRatio: Single;

    fTexelWidth:  Integer;
    fTexelHeight: Integer;

    procedure SetTexelWidth (ATexelWidth:  Integer); inline;
    procedure SetTexelHeight(ATexelHeight: Integer); inline;
  public
    constructor Create(const AWidth, AHeight: Integer; const MinFilter: GLint = GL_NEAREST; const MagFilter: GLint = GL_NEAREST; const Depth: Boolean = False); overload;
    constructor Create(const ABitmap: TBitmap;         const MinFilter: GLint = GL_NEAREST; const MagFilter: GLint = GL_NEAREST); overload;
    constructor Create(const AFileName: String;        const MinFilter: GLint = GL_NEAREST; const MagFilter: GLint = GL_NEAREST); overload;

    destructor Destroy; override;

    procedure Bind;   inline;
    procedure Unbind; inline;

    function Process(const Delta: Extended): Boolean; override;

    procedure Draw2D(SrcX, SrcY, SrcW, SrcH, DestX, DestY, DestW, DestH: Integer; const DestZ: Integer = 0);

    property ID: GLuint read fID;

    property Width:  Integer read fWidth;
    property Height: Integer read fHeight;

    property TexelXRatio: Single read fTexelXRatio;
    property TexelYRatio: Single read fTexelYRatio;

    property TexelWidth:  Integer read fTexelWidth  write SetTexelWidth;
    property TexelHeight: Integer read fTexelHeight write SetTexelHeight;
  end;
{$ENDREGION}

{$REGION 'TOpenGLFrameBuffer'}
  TOpenGLFrameBuffer = class(TSceneNode)
  private
    fFrameBuffer: GLuint;
    fDepthBuffer: GLuint;

    fTexture: TOpenGLTexture;

    fWidth:  Integer;
    fHeight: Integer;
    fDepth:  Boolean;
  public
    constructor Create(const AWidth, AHeight: Integer; const ADepth: Boolean = False);
    destructor Destroy; override;

    procedure Resize(const AWidth, AHeight: Integer; const ADepth: Boolean = False);

    procedure CreateBuffer;
    procedure DestroyBuffer;

    procedure Present;

    procedure Bind;   inline;
    procedure Unbind; inline;

    function Process(const Delta: Extended): Boolean; override;

    property FrameBuffer: GLuint read fFrameBuffer;
    property DepthBuffer: GLuint read fDepthBuffer;

    property Texture: TOpenGLTexture read fTexture;

    property Width:  Integer read fWidth;
    property Height: Integer read fHeight;
    property Depth:  Boolean read fDepth;
  end;
{$ENDREGION}

{$REGION 'TOpenGLMatrix'}
  TOpenGLMatrix = record helper for TMatrix4
    procedure GetModelView;
    procedure GetProjection;
    procedure GetTexture;

    procedure Load;

    procedure Debug;
  end;
{$ENDREGION}

implementation

{$REGION 'TOpenGLWindow'}
constructor TOpenGLWindow.Create;
begin
  fDeviceContext := 0;
  fRenderContext := 0;

  fMultiSampleLevel  := 0;
  fMultiSampleFormat := 0;

  fActiveScene := nil;

  inherited Create(AWidth, AHeight, AFullScreen);
end;

procedure TOpenGLWindow.CreateWindow;
var
  PixelFormatDesc: PIXELFORMATDESCRIPTOR;
  PixelFormat:     GLint;

  function InitializeMultiSample: Boolean;
  var
    Formats: GLint;
    iAttrib: array[0..21] of Integer;
    fAttrib: array[0..1]  of Single;
  begin
    fAttrib[0] := 0;
    fAttrib[1] := 0;

    iAttrib[0]  := WGL_DRAW_TO_WINDOW_ARB;
    iAttrib[1]  := 1;
    iAttrib[2]  := WGL_SUPPORT_OPENGL_ARB;
    iAttrib[3]  := 1;
    iAttrib[4]  := WGL_ACCELERATION_ARB;
    iAttrib[5]  := WGL_FULL_ACCELERATION_ARB;
    iAttrib[6]  := WGL_COLOR_BITS_ARB;
    iAttrib[7]  := 24;
    iAttrib[8]  := WGL_ALPHA_BITS_ARB;
    iAttrib[9]  := 8;
    iAttrib[10] := WGL_DEPTH_BITS_ARB;
    iAttrib[11] := 16;
    iAttrib[12] := WGL_STENCIL_BITS_ARB;
    iAttrib[13] := 0;
    iAttrib[14] := WGL_DOUBLE_BUFFER_ARB;
    iAttrib[15] := 1;
    iAttrib[16] := WGL_SAMPLE_BUFFERS_ARB;
    iAttrib[17] := 1;
    iAttrib[18] := WGL_SAMPLES_ARB;
    iAttrib[19] := 8;
    iAttrib[20] := 0;
    iAttrib[21] := 0;

    fMultiSampleLevel := 8;

    repeat
      iAttrib[19] := fMultiSampleLevel;

      if wglChoosePixelFormatARB(fDeviceContext, @iAttrib[0], @fAttrib[0], 1, @fMultiSampleFormat, @Formats) then exit(True);

      fMultiSampleLevel := fMultiSampleLevel div 2;
    until fMultiSampleLevel = 1;

    fMultiSampleLevel  := 0;
    fMultiSampleFormat := 0;
    Result             := False;
  end;
begin
  if not InitOpenGL then
    TOpenGLError.RaiseException('Failed to initialize OpenGL libraries');

  FillChar(PixelFormatDesc, sizeof(PixelFormatDesc), 0);
  with PixelFormatDesc do
  begin
    nSize        := sizeof(PIXELFORMATDESCRIPTOR);
    nVersion     := 1;
    dwFlags      := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
    iPixelType   := PFD_TYPE_RGBA;
    cColorBits   := 32;
    cDepthBits   := 16;
    iLayerType   := PFD_MAIN_PLANE;
  end;

  inherited;

  fDeviceContext := GetDC(Handle);
  if fDeviceContext = 0 then
  begin
    DestroyWindow;
    TOpenGLError.RaiseException('Failed to get window device context');
  end;

  if fMultiSampleFormat = 0 then
  begin
    PixelFormat := ChoosePixelFormat(fDeviceContext, @PixelFormatDesc);

    if PixelFormat = 0 then
    begin
      DestroyWindow;
      TOpenGLError.RaiseException('Failed to select pixel format');
    end;
  end
  else
    PixelFormat := fMultiSampleFormat;

  if not SetPixelFormat(fDeviceContext, PixelFormat, @PixelFormatDesc) then
  begin
    DestroyWindow;
    TOpenGLError.RaiseException('Failed to set pixel format');
  end;

  fRenderContext := wglCreateContext(fDeviceContext);
  if fRenderContext = 0 then
  begin
    DestroyWindow;
    TOpenGLError.RaiseException('Failed to create render context');
  end;

  if not MakeCurrent then
  begin
    DestroyWindow;
    TOpenGLError.RaiseException('Failed to select render context');
  end;

  ReadExtensions;
  ReadImplementationProperties;

  if fMultiSampleFormat = 0 then
    if InitializeMultiSample then
    begin
      DestroyWindow;
      CreateWindow;
    end;

  wglSwapIntervalEXT(0);

  SetDisplaySize(DisplayWidth, DisplayHeight, DisplayFullScreen);

  Show;
end;

function TOpenGLWindow.DestroyWindow;
begin
  if fDeviceContext <> 0 then
    wglMakeCurrent(fDeviceContext, 0);

  if fRenderContext <> 0 then
    wglDeleteContext(fRenderContext);

  if (Handle <> 0) and (fDeviceContext <> 0) then
    ReleaseDC(Handle, fDeviceContext);

  fDeviceContext := 0;
  fRenderContext := 0;

  Result := inherited;
end;

function TOpenGLWindow.MakeCurrent;
begin
  Result := wglMakeCurrent(fDeviceContext, fRenderContext);
end;

procedure TOpenGLWindow.Present;
begin
  Winapi.Windows.SwapBuffers(fDeviceContext);
end;

procedure TOpenGLWindow.SetDisplaySize;
begin
  inherited;

  Projection := TMatrix4.CreatePerspective(45, DisplayAspect, 1, 100);

  glViewport(0, 0, DisplayWidth, DisplayHeight);
end;

function TOpenGLWindow.InitializeAssets;
begin
  glEnable(GL_COLOR_MATERIAL);
  glShadeModel(GL_FLAT);

  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);

  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);

  glEnable(GL_TEXTURE_2D);

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

  if fMultiSampleLevel > 0 then
    glEnable(GL_MULTISAMPLE_ARB);

  glClearColor(0.4, 0.6, 1, 1);
  glClearDepth(1);

  Projection := TMatrix4.CreatePerspective(45, DisplayAspect, 1, 100);

  Camera.LoadIdentity;
  Camera.LookAt(TVector4.Create(0, 0, 10), TVector4.Create(0, 0, 0), TVector4.Create(0, 1, 0));

  Result := True;
end;

function TOpenGLWindow.Cycle;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_PROJECTION);
  Projection.Load;

  glMatrixMode(GL_MODELVIEW);
  Camera.Load;

  Result := inherited;
end;

function TOpenGLWindow.Render;
begin
  if fActiveScene <> nil then
    Result := fActiveScene.Process(Delta)
  else
    Result := inherited;
end;
{$ENDREGION}

{$REGION 'TOpenGLView'}
constructor TOpenGLView.Create;
begin
  inherited Create;

  fWindow := AWindow;
end;

function TOpenGLView.Process;
begin
  glViewport(0, 0, fWindow.DisplayWidth, fWindow.DisplayHeight);
  Result := True;
end;
{$ENDREGION}

{$REGION 'TOpenGLView2D'}
class procedure TOpenGLView2D.View;
begin
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  glOrtho(0, AWindow.DisplayWidth, AWindow.DisplayHeight, 0, 0, Depth);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

function TOpenGLView2D.Process;
begin
  Result := inherited;

  View(fWindow, Depth);
end;
{$ENDREGION}

{$REGION 'TOpenGLView3D'}
class procedure TOpenGLView3D.View;
//var
  //M: TMatrix4;
begin
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(FOV, AWindow.DisplayAspect, 1, Depth);
  //M := TMatrix4.CreatePerspectiveFieldOfView(PI / 4, AWindow.fDisplayAspect, 100, -100);
  //glLoadMatrixf(@M.Data2D[0, 0]);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

function TOpenGLView3D.Process;
begin
  Result := inherited;

  View(fWindow, fFOV, Depth);
end;
{$ENDREGION}

{$REGION 'TOpenGLViewIsometric'}
class procedure TOpenGLViewIsometric.View;
begin
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  glOrtho(-AWindow.DisplayWidth, AWindow.DisplayWidth, -AWindow.DisplayHeight, AWindow.DisplayHeight, -Depth, Depth);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;

  glRotatef(30, 1.0, 0.0, 0.0);
  glRotatef(-135.0, 0.0, 1.0, 0.0);
end;

function TOpenGLViewIsometric.Process;
begin
  Result := inherited;

  View(fWindow, Depth);
end;
{$ENDREGION}

{$REGION 'TOpenGLCamera3D'}
function TOpenGLCamera3D.Process;
begin
  Result := inherited;

  gluLookAt(Position.X, Position.Y, Position.Z, Target.X, Target.Y, Target.Z, 0, 1, 0);
end;
{$ENDREGION}

{$REGION 'TOpenGLLight'}
constructor TOpenGLLight.Create;
begin
  inherited Create;

  fID := AID;
end;

function TOpenGLLight.Process;
begin
  if Ambient.Used  then glLightfv(fID, GL_AMBIENT,  @Ambient.Data[0]);
  if Diffuse.Used  then glLightfv(fID, GL_DIFFUSE,  @Diffuse.Data[0]);
  if Specular.Used then glLightfv(fID, GL_SPECULAR, @Specular.Data[0]);
  if Position.Used then glLightfv(fID, GL_POSITION, @Position.Data[0]);
  Result := True;
end;
{$ENDREGION}

{$REGION 'TOpenGLMaterial'}
function TOpenGLMaterial.Process;
begin
  if Ambient.Used   then glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   @Ambient.Data[0]);
  if Diffuse.Used   then glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   @Diffuse.Data[0]);
  if Specular.Used  then glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  @Specular.Data[0]);
  if Shininess.Used then glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, Shininess.Data[0]);
  if Emission.Used  then glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  @Emission.Data[0]);
  Result := True;
end;
{$ENDREGION}

{$REGION 'TOpenGLQuadric'}
procedure TOpenGLQuadric.SetTextured;
begin
  fTextured := Value;

  gluQuadricTexture(fQuadric, fTextured);
end;

procedure TOpenGLQuadric.SetFlat;
begin
  fFlat := Value;

  if fFlat then
    gluQuadricNormals(fQuadric, GLU_FLAT)
  else
    gluQuadricNormals(fQuadric, GLU_SMOOTH);
end;

procedure TOpenGLQuadric.SetOutside;
begin
  fOutside := Value;

  if fOutside then
    gluQuadricOrientation(fQuadric, GLU_OUTSIDE)
  else
    gluQuadricOrientation(fQuadric, GLU_INSIDE);
end;

constructor TOpenGLQuadric.Create;
begin
  inherited;

  fQuadric := gluNewQuadric;

  Textured := True;
  Flat     := False;
  Outside  := True;
end;

destructor TOpenGLQuadric.Destroy;
begin
  gluDeleteQuadric(fQuadric);

  inherited;
end;

class constructor TOpenGLQuadric.Create;
begin
  fStock := nil;
end;

class destructor TOpenGLQuadric.Destroy;
begin
  if fStock <> nil then
    fStock.Free;

  fStock := nil;
end;

class function TOpenGLQuadric.Stock;
begin
  if fStock = nil then
    fStock := TOpenGLQuadric.Create;

  Result := fStock;
end;
{$ENDREGION}

{$REGION 'TOpenGLRender'}
constructor TOpenGLRender.Create;
begin
  inherited;

  fID := 0;
end;

destructor TOpenGLRender.Destroy;
begin
  Uncompile;

  inherited;
end;

class function TOpenGLRender.CreateList;
begin
  Result := glGenLists(1);

  glNewList(Result, GL_COMPILE);
  try
    From;
  finally
    glEndList;
  end;
end;

class procedure TOpenGLRender.DrawTriangle;
var
  u, v: TVector4;
  n:    TVector4;
  d:    Single;
begin
  u := b - c;
  v := a - b;

  n.x := u.y * v.z - u.z * v.y;
  n.y := u.z * v.x - u.x * v.z;
  n.z := u.x * v.y - u.y * v.x;

  d := sqrt(n.x * n.x + n.y * n.y + n.z * n.z);

  n := n / d;

  glNormal3fv(@n.Data[0]);

  glTexCoord2f(0, 0); glVertex3fv(@a.Data[0]);
  glTexCoord2f(1, 0); glVertex3fv(@b.Data[0]);
  glTexCoord2f(1, 1); glVertex3fv(@c.Data[0]);
end;

procedure TOpenGLRender.DoRender;
begin
  {}
end;

procedure TOpenGLRender.Compile;
begin
  Uncompile;

  fID := CreateList(DoRender);
end;

procedure TOpenGLRender.Uncompile;
begin
  if fID = 0 then exit;

  glDeleteLists(fID, 1);
  fID := 0;
end;

function TOpenGLRender.Process;
begin
  Result := True;

  if fID <> 0 then
    glCallList(fID)
  else
    DoRender;
end;
{$ENDREGION}

{$REGION 'TOpenGLPlain'}
class procedure TOpenGLPlain.Draw;
begin
  glBegin(GL_QUADS);

  glNormal3f(0, 1, 0);

  glTexCoord2f(0, 0); glVertex3f(-1, 0, -1);
  glTexCoord2f(0, 1); glVertex3f(-1, 0,  1);
  glTexCoord2f(1, 1); glVertex3f( 1, 0,  1);
  glTexCoord2f(1, 0); glVertex3f( 1, 0, -1);

  glEnd;
end;

procedure TOpenGLPlain.DoRender;
begin
  Draw;
end;
{$ENDREGION}

{$REGION 'TOpenGLCube'}
class procedure TOpenGLCube.Draw;
{$REGION 'Face texture coords'}
const
  T = 1 / 6;

  FT: array[Boolean] of array[0..5] of array[0..3] of array[0..1] of Single = (
    (
      ((0, 0), (1, 0), (1, 1), (0, 1)),
      ((1, 0), (1, 1), (0, 1), (0, 0)),
      ((0, 1), (0, 0), (1, 0), (1, 1)),
      ((1, 1), (0, 1), (0, 0), (1, 0)),
      ((1, 0), (1, 1), (0, 1), (0, 0)),
      ((0, 0), (1, 0), (1, 1), (0, 1))
    ),
    (
      ((T * 0, 0), (T * 0 + T, 0), (T * 0 + T, 1), (T * 0, 1)),
      ((T * 1, 0), (T * 1 + T, 0), (T * 1 + T, 1), (T * 1, 1)),
      ((T * 2, 0), (T * 2 + T, 0), (T * 2 + T, 1), (T * 2, 1)),
      ((T * 3, 0), (T * 3 + T, 0), (T * 3 + T, 1), (T * 3, 1)),
      ((T * 4, 0), (T * 4 + T, 0), (T * 4 + T, 1), (T * 4, 1)),
      ((T * 5, 0), (T * 5 + T, 0), (T * 5 + T, 1), (T * 5, 1))
    )
  );
{$ENDREGION}
begin
  glBegin(GL_QUADS);

  glNormal3f(0, 0, 1);

  glTexCoord2f(FT[FaceTextures][0][3][0], FT[FaceTextures][0][3][1]); glVertex3f(-1, -1,  1);
  glTexCoord2f(FT[FaceTextures][0][2][0], FT[FaceTextures][0][2][1]); glVertex3f( 1, -1,  1);
  glTexCoord2f(FT[FaceTextures][0][1][0], FT[FaceTextures][0][1][1]); glVertex3f( 1,  1,  1);
  glTexCoord2f(FT[FaceTextures][0][0][0], FT[FaceTextures][0][0][1]); glVertex3f(-1,  1,  1);

  glNormal3f(1, 0, 0);
  glTexCoord2f(FT[FaceTextures][1][3][0], FT[FaceTextures][1][3][1]); glVertex3f( 1, -1,  1);
  glTexCoord2f(FT[FaceTextures][1][2][0], FT[FaceTextures][1][2][1]); glVertex3f( 1, -1, -1);
  glTexCoord2f(FT[FaceTextures][1][1][0], FT[FaceTextures][1][1][1]); glVertex3f( 1,  1, -1);
  glTexCoord2f(FT[FaceTextures][1][0][0], FT[FaceTextures][1][0][1]); glVertex3f( 1,  1,  1);

  glNormal3f(0, 0, -1);
  glTexCoord2f(FT[FaceTextures][2][3][0], FT[FaceTextures][2][3][1]); glVertex3f( 1, -1, -1);
  glTexCoord2f(FT[FaceTextures][2][2][0], FT[FaceTextures][2][2][1]); glVertex3f(-1, -1, -1);
  glTexCoord2f(FT[FaceTextures][2][1][0], FT[FaceTextures][2][1][1]); glVertex3f(-1,  1, -1);
  glTexCoord2f(FT[FaceTextures][2][0][0], FT[FaceTextures][2][0][1]); glVertex3f( 1,  1, -1);

  glNormal3f(-1, 0, 0);
  glTexCoord2f(FT[FaceTextures][3][3][0], FT[FaceTextures][3][3][1]); glVertex3f(-1, -1, -1);
  glTexCoord2f(FT[FaceTextures][3][2][0], FT[FaceTextures][3][2][1]); glVertex3f(-1, -1,  1);
  glTexCoord2f(FT[FaceTextures][3][1][0], FT[FaceTextures][3][1][1]); glVertex3f(-1,  1,  1);
  glTexCoord2f(FT[FaceTextures][3][0][0], FT[FaceTextures][3][0][1]); glVertex3f(-1,  1, -1);

  glNormal3f(0, 1, 0);
  glTexCoord2f(FT[FaceTextures][4][3][0], FT[FaceTextures][4][3][1]); glVertex3f( 1,  1, -1);
  glTexCoord2f(FT[FaceTextures][4][2][0], FT[FaceTextures][4][2][1]); glVertex3f(-1,  1, -1);
  glTexCoord2f(FT[FaceTextures][4][1][0], FT[FaceTextures][4][1][1]); glVertex3f(-1,  1,  1);
  glTexCoord2f(FT[FaceTextures][4][0][0], FT[FaceTextures][4][0][1]); glVertex3f( 1,  1,  1);

  glNormal3f(0, -1, 0);
  glTexCoord2f(FT[FaceTextures][5][3][0], FT[FaceTextures][5][3][1]); glVertex3f(-1, -1, -1);
  glTexCoord2f(FT[FaceTextures][5][2][0], FT[FaceTextures][5][2][1]); glVertex3f( 1, -1, -1);
  glTexCoord2f(FT[FaceTextures][5][1][0], FT[FaceTextures][5][1][1]); glVertex3f( 1, -1,  1);
  glTexCoord2f(FT[FaceTextures][5][0][0], FT[FaceTextures][5][0][1]); glVertex3f(-1, -1,  1);

  glEnd;
end;

procedure TOpenGLCube.DoRender;
begin
  Draw;
end;
{$ENDREGION}

{$REGION 'TOpenGLIcosahedron'}
class procedure TOpenGLIcosahedron.Draw;
var
  i, j: Integer;
begin
  for i := 0 to 19 do
  begin
    glBegin(GL_POLYGON);

    if Flat then glNormal3fv(@NormalData[i][0]);

    for j := 2 downto 0 do
    begin
      case j of
        0: glTexCoord2f(0, 0);
        1: glTexCoord2f(0, 1);
        2: glTexCoord2f(1, 1);
      end;

      if not Flat then glNormal3fv(@VertexData[VertexIndex[i, j]]);

      glVertex3fv(@VertexData[VertexIndex[i, j]]);
    end;
    glEnd;
  end;
end;

procedure TOpenGLIcosahedron.DoRender;
begin
  Draw(fFlat);
end;
{$ENDREGION}

{$REGION 'TOpenGLSphere'}
class procedure TOpenGLSphere.Draw;
var
  s: Integer;
begin
  if Quadric = nil then Quadric := TOpenGLQuadric.Stock;
  if Stacks = -1 then s := Slices else s := Stacks;
  gluSphere(Quadric.Quadric, Radius, Slices, s);
end;

procedure TOpenGLSphere.DoRender;
begin
  Draw(fRadius, fSlices, fStacks, fQuadric);
end;
{$ENDREGION}

{$REGION 'TOpenGLCylinder'}
class procedure TOpenGLCylinder.Draw;
begin
  if Quadric = nil then Quadric := TOpenGLQuadric.Stock;

  gluCylinder(Quadric.Quadric, BaseRadius, TopRadius, Height, Slices, Stacks);
end;

procedure TOpenGLCylinder.DoRender;
begin
  Draw(fBaseRadius, fTopRadius, fHeight, fSlices, fStacks, fQuadric);
end;
{$ENDREGION}

{$REGION 'TOpenGLCone'}
class procedure TOpenGLCone.Draw;
var
  i:          Integer;
  p1, p2, p3: TVector4;
  Theta:      Single;
begin
  Theta := (PI * 2) / Slices;

  p3.SetValue(0, Height, 0);

  for i := 0 to Slices - 1 do
  begin
    p1.SetValue(sin(Theta *  i)      * Radius, 0, cos(Theta *  i)      * Radius);
    p2.SetValue(sin(Theta * (i + 1)) * Radius, 0, cos(Theta * (i + 1)) * Radius);

    DrawTriangle(p1, p2, p3);
  end;
end;

procedure TOpenGLCone.DoRender;
begin
  Draw(fRadius, fHeight, fSlices);
end;
{$ENDREGION}

{$REGION 'TOpenGLShader'}
constructor TOpenGLShader.Create;
var
  SrcPtr:   PAnsiChar;
  SrcLen:   GLint;

  Compiled: GLint;

  LogStr:   AnsiString;
  LogLen:   GLint;
begin
  inherited Create;

  fShaderType := AShaderType;
  fID := glCreateShader(AShaderType);

  SrcPtr := PAnsiChar(ASource);
  SrcLen := Length(ASource);

  glShaderSource(fID, 1, @SrcPtr, @SrcLen);
  glCompileShader(fID);

  glGetShaderiv(fID, GL_COMPILE_STATUS, @Compiled);

  if Compiled <> GL_TRUE then
  begin
    glGetShaderiv(fID, GL_INFO_LOG_LENGTH, @LogLen);
    SetLength(LogStr, LogLen);

    glGetShaderInfoLog(fID, LogLen, LogLen, PAnsiChar(LogStr));

    TOpenGLError.RaiseException('Failed to compile shader:'#13#10 + String(LogStr));
  end;
end;

destructor TOpenGLShader.Destroy;
begin
  glDeleteShader(fID);

  inherited;
end;
{$ENDREGION}

{$REGION 'TOpenGLShaderProgram'}
constructor TOpenGLShaderProgram.Create;
begin
  inherited Create;

  fID := glCreateProgram;

  fVertexShader   := TOpenGLShader.Create(AVertexSrc,   GL_VERTEX_SHADER);
  fFragmentShader := TOpenGLShader.Create(AFragmentSrc, GL_FRAGMENT_SHADER);

  glAttachShader(fID, fVertexShader.ID);
  glAttachShader(fID, fFragmentShader.ID);

  glLinkProgram(fID);
end;

destructor TOpenGLShaderProgram.Destroy;
begin
  fVertexShader.Free;
  fFragmentShader.Free;

  glDeleteProgram(fID);

  inherited;
end;

class function TOpenGLShaderProgram.CreateFromFiles;
  function LoadString(FileName: String): AnsiString;
  var
    s: String;
  begin
    s.Load(FileStream(FileName));

    Result := AnsiString(s);
  end;
var
  VertexSrc:   AnsiString;
  FragmentSrc: AnsiString;
begin
  VertexSrc   := LoadString(AVertexFileName);
  FragmentSrc := LoadString(AFragmentFileName);

  Result := TOpenGLShaderProgram.Create(VertexSrc, FragmentSrc);
end;

function TOpenGLShaderProgram.MapUniform;
begin
  if (UID < 0) or (UID > high(fUniformMap)) then TOpenGLError.RaiseException('Invalid uniform ID');

  Result := glGetUniformLocation(fID, PAnsiChar(Name));
  if Result = 0 then TOpenGLError.RaiseException('Unknown uniform name "' + String(Name) + '"');

  fUniformMap[UID] := Result;
end;

procedure TOpenGLShaderProgram.Uniform(const UID: Integer; Value: array of GLfloat);
begin
  case Length(Value) of
    1: glUniform1f(fUniformMap[UID], Value[0]);
    2: glUniform2f(fUniformMap[UID], Value[0], Value[1]);
    3: glUniform3f(fUniformMap[UID], Value[0], Value[1], Value[2]);
    4: glUniform4f(fUniformMap[UID], Value[0], Value[1], Value[2], Value[3]);
  else
    TOpenGLError.RaiseException('Invalid uniform data size');
  end;
end;

procedure TOpenGLShaderProgram.Uniform(const Name: AnsiString; Value: array of GLfloat);
var
  fLocation: GLint;
begin
  fLocation := glGetUniformLocation(fID, PAnsiChar(Name));
  if fLocation = 0 then TOpenGLError.RaiseException('Unknown uniform name "' + String(Name) + '"');

  case Length(Value) of
    1: glUniform1f(fLocation, Value[0]);
    2: glUniform2f(fLocation, Value[0], Value[1]);
    3: glUniform3f(fLocation, Value[0], Value[1], Value[2]);
    4: glUniform4f(fLocation, Value[0], Value[1], Value[2], Value[3]);
  else
    TOpenGLError.RaiseException('Invalid uniform data size');
  end;
end;

procedure TOpenGLShaderProgram.Bind;
begin
  glUseProgram(fID);
end;

procedure TOpenGLShaderProgram.Unbind;
begin
  glUseProgram(0);
end;

function TOpenGLShaderProgram.Process;
begin
  Result := True;
  Bind;
end;
{$ENDREGION}

{$REGION 'TOpenGLTexture'}
procedure TOpenGLTexture.SetTexelWidth;
begin
  fTexelWidth := ATexelWidth;
  fTexelXRatio := fTexelWidth / fWidth;
end;

procedure TOpenGLTexture.SetTexelHeight;
begin
  fTexelHeight := ATexelHeight;
  fTexelYRatio := fTexelHeight / fHeight;
end;

constructor TOpenGLTexture.Create(const AWidth, AHeight: Integer; const MinFilter: GLint = GL_NEAREST; const MagFilter: GLint = GL_NEAREST; const Depth: Boolean = False);
begin
  inherited Create;

  fWidth  := AWidth;
  fHeight := AHeight;

  glGenTextures(1, @fID);

  Bind;

  if Depth then
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, fWidth, fHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nil)
  else
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, fWidth, fHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MinFilter);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MagFilter);

  TexelWidth  := 1;
  TexelHeight := 1;

  Unbind;
end;

constructor TOpenGLTexture.Create(const ABitmap: TBitmap; const MinFilter: GLint = GL_NEAREST; const MagFilter: GLint = GL_NEAREST);
begin
  inherited Create;

  fWidth  := ABitmap.Width;
  fHeight := ABitmap.Height;

  glGenTextures(1, @fID);

  Bind;

  if ((MinFilter >= GL_NEAREST_MIPMAP_NEAREST) and (MinFilter <= GL_LINEAR_MIPMAP_LINEAR))
  or ((MagFilter >= GL_NEAREST_MIPMAP_NEAREST) and (MagFilter <= GL_LINEAR_MIPMAP_LINEAR)) then
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, fWidth, fHeight, GL_RGBA, GL_UNSIGNED_BYTE, ABitmap.BitmapData.Scan0)
  else
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fWidth, fHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, ABitmap.BitmapData.Scan0);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MinFilter);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MagFilter);

  TexelWidth  := 1;
  TexelHeight := 1;

  Unbind;
end;

constructor TOpenGLTexture.Create(const AFileName: String; const MinFilter: GLint = GL_NEAREST; const MagFilter: GLint = GL_NEAREST);
var
  B: TBitmap;
begin
  B := TBitmap.Create;

  try
    if not B.LoadFromFile(AFileName) then TOpenGLError.RaiseException('Failed to load bitmap ' + AFileName);

    B.SwapRB;

    Create(B, MinFilter, MagFilter);
  finally
    B.Free;
  end;
end;

destructor TOpenGLTexture.Destroy;
begin
  if fID <> 0 then
    glDeleteTextures(1, @fID);

  inherited;
end;

procedure TOpenGLTexture.Bind;
begin
  glBindTexture(GL_TEXTURE_2D, fID);
end;

procedure TOpenGLTexture.Unbind;
begin
  glBindTexture(GL_TEXTURE_2D, 0);
end;

function TOpenGLTexture.Process;
begin
  Result := True;
  glEnable(GL_TEXTURE_2D);
  Bind;
end;

procedure TOpenGLTexture.Draw2D;
var
  tx1, tx2: Single;
  ty1, ty2: Single;
begin
  tx1 := SrcX * fTexelXRatio; tx2 := (SrcX + SrcW) * fTexelXRatio;
  ty1 := SrcY * fTexelYRatio; ty2 := (SrcY + SrcH) * fTexelYRatio;

  glTexCoord2f(tx1, ty1); glVertex3i(DestX,         DestY,         DestZ);
  glTexCoord2f(tx2, ty1); glVertex3i(DestX + DestW, DestY,         DestZ);
  glTexCoord2f(tx2, ty2); glVertex3i(DestX + DestW, DestY + DestH, DestZ);
  glTexCoord2f(tx1, ty2); glVertex3i(DestX,         DestY + DestH, DestZ);
end;
{$ENDREGION}

{$REGION 'TOpenGLFrameBuffer'}
constructor TOpenGLFrameBuffer.Create;
begin
  inherited Create;

  fFrameBuffer := 0;
  fDepthBuffer := 0;

  fTexture := nil;

  Resize(AWidth, AHeight, ADepth);
end;

destructor TOpenGLFrameBuffer.Destroy;
begin
  DestroyBuffer;

  inherited;
end;

procedure TOpenGLFrameBuffer.Resize;
begin
  DestroyBuffer;

  fWidth  := AWidth;
  fHeight := AHeight;
  fDepth  := ADepth;

  CreateBuffer;
end;

procedure TOpenGLFrameBuffer.CreateBuffer;
begin
  fTexture := TOpenGLTexture.Create(fWidth, fHeight, GL_NEAREST, GL_NEAREST, fDepth);
  fTexture.Bind;

  glGenFramebuffers(1, @fFrameBuffer);
  glBindFramebuffer(GL_FRAMEBUFFER, fFrameBuffer);

  if fDepth then
  begin
  	glDrawBuffer(GL_NONE);
	  glReadBuffer(GL_NONE);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fTexture.fID, 0);
  end
  else
  begin
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fTexture.fID, 0);

    glGenRenderbuffers(1, @fDepthBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, fDepthBuffer);

    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fWidth, fHeight);

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fDepthBuffer);
  end;

  if glCheckFramebufferStatus(GL_FRAMEBUFFER) <> GL_FRAMEBUFFER_COMPLETE then
    TOpenGLError.RaiseException('Failed to create frame buffer');

  Unbind;
end;

procedure TOpenGLFrameBuffer.DestroyBuffer;
begin
  if fFrameBuffer <> 0 then
    glDeleteFramebuffers(1, @fFrameBuffer);

  if fDepthBuffer <> 0 then
    glDeleteRenderbuffers(1, @fDepthBuffer);

  if fTexture <> nil then
    fTexture.Free;

  fFrameBuffer := 0;
  fDepthBuffer := 0;

  fTexture := nil;
end;

procedure TOpenGLFrameBuffer.Present;
begin
  Unbind;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;

  glOrtho(0, fWidth, 0, fHeight, -1, 1);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;

  fTexture.Bind;

  glDisable(GL_DEPTH_TEST);

  glBegin(GL_QUADS);
    glTexCoord2f(0, 0); glVertex2i(0,      0);
    glTexCoord2f(1, 0); glVertex2i(fWidth, 0);
    glTexCoord2f(1, 1); glVertex2i(fWidth, fHeight);
    glTexCoord2f(0, 1); glVertex2i(0,      fHeight);
  glEnd;
end;

procedure TOpenGLFrameBuffer.Bind;
begin
  glBindFramebuffer(GL_FRAMEBUFFER, fFrameBuffer);
  glViewport(0, 0, fWidth, fHeight);
end;

procedure TOpenGLFrameBuffer.Unbind;
begin
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
end;

function TOpenGLFrameBuffer.Process;
begin
  Result := True;
  Bind;
end;
{$ENDREGION}

{$REGION 'TOpenGLMatrix'}
procedure TOpenGLMatrix.GetModelView;
begin
  glGetFloatv(GL_MODELVIEW_MATRIX, @Data2D[0, 0]);
end;

procedure TOpenGLMatrix.GetProjection;
begin
  glGetFloatv(GL_PROJECTION_MATRIX, @Data2D[0, 0]);
end;

procedure TOpenGLMatrix.GetTexture;
begin
  glGetFloatv(GL_TEXTURE_MATRIX, @Data2D[0, 0]);
end;

procedure TOpenGLMatrix.Load;
begin
  glLoadMatrixf(@Data2D[0, 0]);
end;

procedure TOpenGLMatrix.Debug;
var
  x, y: Integer;
begin
  for x := 0 to 3 do
  begin
    for y := 0 to 3 do
      Write(String.Float(Data2D[x, y], 4) + ', ');

    Writeln;
  end;
end;
{$ENDREGION}

end.