unit MainFm;

//---------------------------------------------------------------------------
interface

//---------------------------------------------------------------------------
uses
  Messages, SysUtils, LResources, Classes, Controls, Forms, Dialogs, ExtCtrls;

//---------------------------------------------------------------------------
type
  TMainForm = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    GameTicks: Integer;

    DrawIndex: Integer;
    MixIndex : Integer;

    procedure OnDeviceCreate(Sender: TObject; Param: Pointer;
     var Handled: Boolean);

    procedure TimerEvent(Sender: TObject);
    procedure ProcessEvent(Sender: TObject);

    procedure RenderPrimary(Sender: TObject);
    procedure RenderSecondary(Sender: TObject);

    procedure RenderMotion(Sender: TObject);
    procedure RenderBlur(Sender: TObject);

    procedure WMDisplayChange(var message:TMessage); message WM_DISPLAYCHANGE;
  public
    { Public declarations }
  end;

//---------------------------------------------------------------------------
var
  MainForm: TMainForm;

//---------------------------------------------------------------------------
implementation
uses
 Vectors2, Vectors2px, AsphyreTimer, AsphyreFactory, AsphyreTypes,
 AsphyreDb, AbstractDevices, AsphyreImages, AsphyreFonts, OGLProviders,
 GameTypes, AbstractCanvas, AsphyreRenderTargets;

//---------------------------------------------------------------------------
const
 OrigPx: TPoint4px = (
  (x:   0 + 4; y: 0 + 4),
  (x: 256 - 1; y: 0 + 3),
  (x: 256 - 3; y: 256 - 1),
  (x:   0 + 1; y: 256 - 4));

//---------------------------------------------------------------------------
procedure TMainForm.FormCreate(Sender: TObject);
begin
 // Set the display size
 DisplaySize:= Point2px(256, 256);

 // Indicate that we're using OpenGL for rendering.
 Factory.UseProvider(idOpenGL);

 // Create Asphyre components in run-time.
 GameDevice := Factory.CreateDevice();
 GameCanvas := Factory.CreateCanvas();
 GameImages := TAsphyreImages.Create();
 GameTargets:= TAsphyreRenderTargets.Create();

 GameFonts:= TAsphyreFonts.Create();
 GameFonts.Images:= GameImages;
 GameFonts.Canvas:= GameCanvas;

 MediaASDb:= TASDb.Create();
 MediaASDb.FileName:= ExtractFilePath(ParamStr(0)) + 'media.asdb';
 MediaASDb.OpenMode:= opReadOnly;

 GameDevice.WindowHandle:= Self.Handle;
 GameDevice.Size    := DisplaySize;
 GameDevice.Windowed:= True;
 GameDevice.VSync   := False;

 EventDeviceCreate.Subscribe(@OnDeviceCreate, 0);

 // Attempt to initialize Asphyre device.
 if (not GameDevice.Initialize()) then
  begin
   ShowMessage('Failed to initialize Asphyre device.');
   Application.Terminate();
   Exit;
  end;

 // Create rendering timer.
 Timer.OnTimer  := @TimerEvent;
 Timer.OnProcess:= @ProcessEvent;
 Timer.Speed    := 60.0;
 Timer.MaxFPS   := 4000;
 Timer.Enabled  := True;

 DrawIndex:= 0;
 MixIndex := 0;
end;

//---------------------------------------------------------------------------
procedure TMainForm.FormDestroy(Sender: TObject);
begin
 Timer.Enabled:= False;

 // Release all Asphyre components.
 FreeAndNil(GameFonts);
 FreeAndNil(GameTargets);
 FreeAndNil(GameImages);
 FreeAndNil(MediaASDb);
 FreeAndNil(GameCanvas);
 FreeAndNil(GameDevice);
end;

//---------------------------------------------------------------------------
procedure TMainForm.OnDeviceCreate(Sender: TObject; Param: Pointer;
 var Handled: Boolean);
var
 Success: Boolean;
begin
 // This variable returns "Success" to Device initialization, so if you
 // set it to False, device creation will fail.
 Success:= PBoolean(Param)^;

 GameImages.RemoveAll();      
 GameFonts.RemoveAll();

 // This image is used by our bitmap font.
 GameImages.AddFromASDb('tahoma9b.image', MediaASDb);
 GameImages.AddFromASDb('calibri.image', MediaASDb);

 fontTahoma:= GameFonts.Insert('/media.asdb | tahoma9b.xml', 'tahoma9b.image');
 fontCalibri:= GameFonts.Insert('/media.asdb | calibri.xml', 'calibri.image');

 // -> Create 4 render targets
 swapDraw:= GameTargets.Add(2, 256, 256, apf_A8R8G8B8);
 swapMix := GameTargets.Add(2, 256, 256, apf_A8R8G8B8);

 Success:=
  Success and
  (swapDraw <> -1)and
  (swapMix <> -1)and
  (fontCalibri <> -1)and
  (fontTahoma <> -1);

 PBoolean(Param)^:= Success;
end;

//---------------------------------------------------------------------------
procedure TMainForm.TimerEvent(Sender: TObject);
begin
 GameDevice.RenderTo(@RenderMotion, 0, True,
  GameTargets[swapDraw + DrawIndex xor 1]);
 GameDevice.RenderTo(@RenderBlur, 0, False,
  GameTargets[swapMix + MixIndex xor 1]);

 GameDevice.Render(Panel1.Handle, @RenderPrimary, $000000);
 GameDevice.Render(Panel2.Handle, @RenderSecondary, $000000);

 Timer.Process();

 DrawIndex:= DrawIndex xor 1;
 MixIndex := MixIndex xor 1;
end;

//---------------------------------------------------------------------------
procedure TMainForm.ProcessEvent(Sender: TObject);
begin
 Inc(GameTicks);
end;

//---------------------------------------------------------------------------
procedure TMainForm.RenderMotion(Sender: TObject);
var
 Theta, RibbonLength: Single;
begin
 Theta:= (GameTicks mod 200) * Pi / 100;
 RibbonLength:= (1.0 + Sin(GameTicks / 50.0)) * Pi * 2 / 3 + (Pi / 3);

 GameCanvas.FillRibbon(Point2(128, 128 - 32), Point2(16.0, 24.0),
  Point2(48.0, 32.0), Theta, Theta + RibbonLength, 24,
  cColor4($FF7E00FF, $FF75D3FF, $FFD1FF75, $FFFFC042));

 GameFonts[fontCalibri].TextOut(
  Point2(-128 + GameTicks mod 384, 160.0),
  'Motion Blur!',
  cColor2($FFFFE000, $FFFF0000), 1.0);
end;

//---------------------------------------------------------------------------
procedure TMainForm.RenderBlur(Sender: TObject);
begin
 GameCanvas.Antialias := True;
 GameCanvas.MipMapping:= False;

 // Copy previous scene, englarged and slightly rotated.
 GameCanvas.UseTexturePx(GameTargets[swapMix + MixIndex], OrigPx);
 GameCanvas.TexMap(pBounds4(0.0, 0.0, 256.0, 256.0), clWhite4);

 // Darken the area slightly, to avoid color mess :)
 // Replace color parameter to $FFF0F0F0 to reduce the effect.
 GameCanvas.FillRect(0, 0, 256, 256, $FFF8F8F8, deMultiply);

 // Add the "motion scene" on our working surface.
 GameCanvas.UseTexture(GameTargets[swapDraw + DrawIndex], TexFull4);
 GameCanvas.TexMap(pBounds4(0.0, 0.0, 256.0, 256.0), cAlpha4(224));
end;

//---------------------------------------------------------------------------
procedure TMainForm.RenderPrimary(Sender: TObject);
var
 Theta, Length: Single;
begin
 GameCanvas.FillQuad(pBounds4(2, 2, 50, 50),
  cColor4($FF00FF00, $FFFF0000, $FF0000FF, $FFFFFFFF));

 GameCanvas.FillQuad(pBounds4(54, 2, 50, 50),
  cColor4($FF000000, $FFFF00FF, $FFFFFF00, $FF00FFFF));

 GameCanvas.FillQuad(pBounds4(2, 54, 50, 50),
  cColor4($FF95E792, $FFBD7700, $FF000000, $FFB3ECFF));

 GameCanvas.FillQuad(pBounds4(54, 54, 50, 50),
  cColor4($FF7E00FF, $FF75D3FF, $FFD1FF75, $FFFFC042));

 Theta := (GameTicks mod 300) * Pi / 150;
 Length:= (1.0 + Sin(GameTicks / 50.0)) * Pi * 2 / 3 + (Pi / 3);

 GameCanvas.FillArc(Point2(150, 150), Point2(80, 70), Theta, Theta + Length, 24,
  cColor4($FF6703FF, $FFAFFF03, $FFFFA703, $FFFFFFFF));

 GameFonts[fontTahoma].TextOut(
  Point2(4.0, 240.0),
  'FPS: ' + IntToStr(Timer.FrameRate),
  cColor2($FFFFE887, $FFFF0000), 1.0);
end;

//---------------------------------------------------------------------------
procedure TMainForm.RenderSecondary(Sender: TObject);
begin
 // Just render the "mixed" scene on the second panel.
 GameCanvas.UseTexture(GameTargets[swapMix + MixIndex], TexFull4);
 GameCanvas.TexMap(pBounds4(0.0, 0.0, 256.0, 256.0), clWhite4);
end;

//---------------------------------------------------------------------------
procedure TMainForm.WMDisplayChange(var message: TMessage);
begin
 if (GameDevice <> nil)and(GameDevice.Active) then GameDevice.Reset();
end;

//---------------------------------------------------------------------------
initialization
  {$I MainFm.lrs}

//---------------------------------------------------------------------------
end.
