{
  Vampyre Imaging Library Demo
  D3D9 Demo (D3D9 extension)

  Demo that shows how to create Direct3D 9 textures from files
  and Imaging's images and vice versa. This sample uses SDL to create
  window and process messages. Background and sprite textures are loaded from
  files and rendered. Sprite is rendered in each corner of the window
  using various texture stage and blending settings.
  You can change sprite's texture format by pressing SPACE key
  (it cycles trough all TImageFormat values). Background texture
  can be saved to file by pressing S key and sprite texture
  can be saved by pressing D key.

}
unit DemoUnit;

{$I ImagingOptions.inc}
{$R ..\Common\MainIcon.res}

interface

procedure RunDemo;

implementation

uses
  Windows,
  SysUtils,
  ImagingTypes,
  Imaging,
  ImagingUtility,
  sdl,
  Direct3D9,
  ImagingDirect3D9,
  ImagingSdl,
  DemoUtils;

type
  TVector2 = record
    X, Y: Single;
  end;

  TVector4 = record
    X, Y, Z, W: Single;
  end;

  TVertex = record
    Position: TVector4;
    TexCoord1: TVector2;
    TexCoord2: TVector2;
  end;

  TRect = array[0..3] of TVertex;

const
  SWindowTitle = 'Vampyre Imaging Library (%s) - Direct3D9 Demo (format: %s)';
  SWindowIconTitle = 'Direct3D9 Demo';
  SBackImageFile = 'Tigers.jpg';
  SSpriteImageFile = 'Vezyr.png';
  SOutScreenFile = 'D3DScreen.png';
  SOutSpriteFile = 'D3DSprite.dds';
  SIconFile = 'Icon.png';
  DisplayWidth = 800;
  DisplayHeight = 600;
  SpriteWidth = 256.0;
  SpriteHeight = 192.0;
  FVF_VERTEX = D3DFVF_XYZRHW or D3DFVF_TEX2;

var
  WindowHandle: THandle;
  Direct3D: IDirect3D9 = nil;
  Device: IDirect3DDevice9 = nil;
  BackTex: IDirect3DTexture9 = nil;
  SpriteTex: IDirect3DTexture9 = nil;
  PresentParams: TD3DPresentParameters;
  DisplaySurface: PSDL_Surface = nil;
  SpriteImage: TImageData;
  SpriteFormat: TImageFormat = ifA8R8G8B8;
  BackRect: TRect;
  Rects: array[0..3] of TRect;
  Event : TSDL_Event;
  Running: Boolean = True;
  Frames: LongInt = 0;
  FPS: LongInt = 0;
  LastTime: LongInt = 0;
  TextureCaps: TD3DTextureCaps;

function Vector2(X, Y: Single): TVector2;
begin
  Result.X := X;
  Result.Y := Y;
end;

function Vector4(X, Y, Z, W: Single): TVector4;
begin
  Result.X := X;
  Result.Y := Y;
  Result.Z := Z;
  Result.W := W;
end;

procedure PlaceRect(Index: LongInt; X, Y: Single);
begin
  Rects[Index, 0].Position.X := X;
  Rects[Index, 0].Position.Y := Y;
  Rects[Index, 1].Position.X := X + SpriteWidth;
  Rects[Index, 1].Position.Y := Y;
  Rects[Index, 2].Position.X := X;
  Rects[Index, 2].Position.Y := Y + SpriteHeight;
  Rects[Index, 3].Position.X := X + SpriteWidth;
  Rects[Index, 3].Position.Y := Y + SpriteHeight;
end;

procedure MessageOut(Window: THandle; const Msg: string; const Args: array of const);
begin
  MessageBox(Window, PChar(Format(Msg, Args)), 'Message',
    MB_ICONINFORMATION or MB_OK);
end;

procedure MessageOutAndHalt(Window: THandle; const Msg: string; const Args: array of const);
begin
  MessageBox(Window, PChar(Format(Msg, Args)), 'Error',
    MB_ICONERROR or MB_OK);
  SDL_Quit;
  Halt(1);
end;

procedure UpdateCaption;
begin
  SDL_WM_SetCaption(PAnsiChar(AnsiString(Format(SWindowTitle + ' FPS: %d',
    [Imaging.GetVersionStr, GetFormatName(SpriteFormat), FPS]))),
    SWindowIconTitle);
end;

procedure CreateSpriteTexture(const Device: IDirect3DDevice9; Format: TImageFormat);
var
  D3DFormat: TD3DFormat;
  ConvTo: TImageFormat;
  ConvImage: TImageData;
begin
  // Find D3D format that matches given TImageFormat
  D3DFormat := ImagingDirect3D9.ImageFormatToD3DFormat(Format, ConvTo);
  if D3DFormat <> D3DFMT_UNKNOWN then
  begin
    // Free old texture and create new one in the different format
    SpriteTex := nil;
    Imaging.InitImage(ConvImage);
    Imaging.CloneImage(SpriteImage, ConvImage);
    // Create texture from image
    ImagingDirect3D9.CreateD3DTextureFromImage(ConvImage, Device, SpriteTex,
      SpriteImage.Width, SpriteImage.Height, 0, 0, D3DFormat, D3DPOOL_MANAGED);
    Imaging.FreeImage(ConvImage);
  end;
end;

procedure Initialize;
var
  Caption, Icon: PAnsiChar;
  Mode: TD3DDisplayMode;
  I: LongInt;
begin
  // Get SDL app window
  SDL_WM_GetCaption(Caption, Icon);
  WindowHandle := FindWindowA('SDL_app', Caption);
  if WindowHandle = 0 then
    MessageOutAndHalt(GetActiveWindow, 'Cannot get SDL window handle', []);

  // Place window to the center of the screen
  SetWindowPos(WindowHandle, 0, (GetSystemMetrics(SM_CXSCREEN) - DisplayWidth) div 2,
    (GetSystemMetrics(SM_CYSCREEN) - DisplayHeight - 20) div 2, 0, 0, SWP_NOSIZE or SWP_NOZORDER);

  // Create IDirect3D interface
  Direct3D := Direct3DCreate9(D3D_SDK_VERSION);
  if Direct3D = nil then
    MessageOutAndHalt(WindowHandle, 'Cannot create Direct3D interface', []);

  // Get the current display mode and fill presentation parameters
  Direct3D.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, Mode);
  FillChar(PresentParams, SizeOf(PresentParams), 0);
  PresentParams.hDeviceWindow := WindowHandle;
  PresentParams.Windowed := True;
  PresentParams.BackBufferCount := 1;
  PresentParams.BackBufferFormat := Mode.Format;
  PresentParams.SwapEffect := D3DSWAPEFFECT_DISCARD;
  PresentParams.PresentationInterval := D3DPRESENT_INTERVAL_IMMEDIATE;
  // Create Direct3D device
  if Failed(Direct3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, PresentParams.hDeviceWindow,
    D3DCREATE_SOFTWARE_VERTEXPROCESSING, @PresentParams, Device)) then
    MessageOutAndHalt(WindowHandle, 'Cannot create Direct3D device', []);

  // Get texture caps
  ImagingDirect3D9.GetDeviceTextureCaps(Device, TextureCaps);

  // Load background texture from file
  ImagingDirect3D9.LoadD3DTextureFromFile(GetDataDir + PathDelim + SBackImageFile, Device, BackTex);

  Imaging.InitImage(SpriteImage);
  // Load sprite image from file
  Imaging.LoadImageFromFile(GetDataDir + PathDelim + SSpriteImageFile, SpriteImage);
  // Create sprite texture from image
  CreateSpriteTexture(Device, SpriteFormat);

  // Set render states
  Device.SetRenderState(D3DRS_LIGHTING, 0);
  Device.SetRenderState(D3DRS_ALPHABLENDENABLE, 1);
  Device.SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA);
  Device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
  // Set texture stage states
  Device.SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
  Device.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
  Device.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
  Device.SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE);
  Device.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
  Device.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
  Device.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
  Device.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
  Device.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
  Device.SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);

  Device.SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
  Device.SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
  Device.SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

  // Set size of the background
  BackRect[0].Position := Vector4(0.0, 0.0, 0.0, 1.0);
  BackRect[1].Position := Vector4(DisplayWidth, 0.0, 0.0, 1.0);
  BackRect[2].Position := Vector4(0.0, DisplayHeight, 0.0, 1.0);
  BackRect[3].Position := Vector4(DisplayWidth, DisplayHeight, 0.0, 1.0);
  // Set background's tex coords
  BackRect[0].TexCoord1 := Vector2(0.0, 0.0);
  BackRect[1].TexCoord1 := Vector2(1.0, 0.0);
  BackRect[2].TexCoord1 := Vector2(0.0, 1.0);
  BackRect[3].TexCoord1 := Vector2(1.0, 1.0);

  // Set sprites' tex coords and default position
  for I := 0 to 3 do
  begin
    Rects[I, 0].Position := Vector4(0.0, 0.0, 0.0, 1.0);
    Rects[I, 1].Position := Vector4(0.0, 0.0, 0.0, 1.0);
    Rects[I, 2].Position := Vector4(0.0, 0.0, 0.0, 1.0);
    Rects[I, 3].Position := Vector4(0.0, 0.0, 0.0, 1.0);

    Rects[I, 0].TexCoord1 := Vector2(0.0, 0.0);
    Rects[I, 1].TexCoord1 := Vector2(1.0, 0.0);
    Rects[I, 2].TexCoord1 := Vector2(0.0, 1.0);
    Rects[I, 3].TexCoord1 := Vector2(1.0, 1.0);

    Rects[I, 0].TexCoord2 := Vector2(0.0, 1.0);
    Rects[I, 1].TexCoord2 := Vector2(1.0, 1.0);
    Rects[I, 2].TexCoord2 := Vector2(0.0, 0.0);
    Rects[I, 3].TexCoord2 := Vector2(1.0, 0.0);
  end;
  // Place sprites
  PlaceRect(0, 0, 0);
  PlaceRect(1, DisplayWidth - SpriteWidth, 0);
  PlaceRect(2, 0, DisplayHeight - SpriteHeight);
  PlaceRect(3, DisplayWidth - SpriteWidth, DisplayHeight - SpriteHeight);
end;

procedure Present;
begin
  Device.Clear(0, nil, D3DCLEAR_TARGET, $FFCCFFFF, 1.0, 0);
  if Succeeded(Device.BeginScene) then
  begin
    Device.SetFVF(FVF_VERTEX);
    // First render background
    Device.SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
    Device.SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE);
    Device.SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_DISABLE);
    Device.SetTexture(0, BackTex);
    Device.DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, BackRect, SizeOf(TVertex));

    Device.SetTexture(0, SpriteTex);
    Device.SetTexture(1, SpriteTex);
    // Render first sprite
    Device.DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, Rects[0], SizeOf(TVertex));
    // Render second sprite
    Device.SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_ADDSIGNED);
    Device.SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_ADDSIGNED);
    Device.SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_MODULATE);
    Device.DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, Rects[1], SizeOf(TVertex));
    // Render third sprite
    Device.SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_ONE);
    Device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    Device.SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
    Device.SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
    Device.SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_SUBTRACT);
    Device.SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
    Device.DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, Rects[2], SizeOf(TVertex));
    Device.SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA);
    Device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    // Render last sprite
    Device.SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_DOTPRODUCT3);
    Device.SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_DOTPRODUCT3);
    Device.DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, Rects[3], SizeOf(TVertex));

    Device.SetTexture(0, nil);
    Device.SetTexture(1, nil);

    Device.EndScene;
  end;
  // Copy backbuffer to window
  Device.Present(nil, nil, 0, nil);
end;

procedure Finalize;
begin
  BackTex := nil;
  SpriteTex := nil;
  Device := nil;
  Direct3D := nil;
  Imaging.FreeImage(SpriteImage);
end;

procedure TakeScreenShot;
var
  RenderTarget, OldRenderTarget: IDirect3DSurface9;
  ScreenImg: TImageData;
begin
  // Create new render target and activate it
  Device.CreateRenderTarget(DisplayWidth, DisplayHeight, D3DFMT_A8R8G8B8,
    D3DMULTISAMPLE_NONE, 0, True, RenderTarget, nil);
  Device.GetRenderTarget(0, OldRenderTarget);
  Device.SetRenderTarget(0, RenderTarget);
  // Render to new target
  Present;
  // Activate old target
  Device.SetRenderTarget(0, OldRenderTarget);
  // Convert reder target surface to Imaging image and save it to file
  ImagingDirect3D9.CreateImageFromD3DSurface(RenderTarget, ScreenImg);
  Imaging.SaveImageToFile(SOutScreenFile, ScreenImg);
  // Free all
  Imaging.FreeImage(ScreenImg);
  RenderTarget := nil;
  OldRenderTarget := nil;
end;

procedure RunDemo;
begin
  // Initialize SDL
  if (SDL_Init(SDL_INIT_VIDEO) < 0) then
    MessageOutAndHalt(GetActiveWindow, 'SDL initialization failed: %s', [SDL_GetError]);

  SDL_WM_SetCaption(PAnsiChar(AnsiString(Format(SWindowTitle, [Imaging.GetVersionStr,
    GetFormatName(SpriteFormat)]))), SWindowIconTitle);
  SDL_WM_SetIcon(LoadSDLSurfaceFromFile(GetDataDir + PathDelim + SIconFile), 0);

  // Initialize video mode
  DisplaySurface := SDL_SetVideoMode(DisplayWidth, DisplayHeight, 32, 0);
  if DisplaySurface = nil then
    MessageOutAndHalt(GetActiveWindow, 'SDL SetVideoMode failed: %s', [SDL_GetError]);

  // Initialize surfaces and enter main loop
  Initialize;
  LastTime := SDL_GetTicks;
  while Running do
  begin
    while SDL_PollEvent(@Event) = 1 do
    begin
      case Event.type_ of
        SDL_QUITEV:
          begin
            Running := False;
          end;
        SDL_KEYDOWN:
          begin
            with Event.key.keysym do
              if ((sym = SDLK_F4) and ((modifier and KMOD_ALT) <> 0)) or
                (Event.key.keysym.sym = SDLK_ESCAPE) then
                Running := False;

            // Using S and D keys you can take screen shots and texture
            // shots easily
            // SPACE key can be used to cycle sprite image formats
            case Event.key.keysym.sym of
              SDLK_S: TakeScreenShot;
              SDLK_D: ImagingDirect3D9.SaveD3DTextureToFile(SOutSpriteFile, SpriteTex);
              SDLK_SPACE:
                begin
                  SpriteFormat := NextFormat(SpriteFormat);
                  CreateSpriteTexture(Device, SpriteFormat);
                  UpdateCaption;
                end;
            end;
          end;
      end;
    end;

    // Calculate FPS
    if LongInt(SDL_GetTicks) - LastTime > 1000 then
    begin
      FPS := Frames;
      UpdateCaption;
      Frames := 0;
      LastTime := SDL_GetTicks;
    end;
    Inc(Frames);

    // Renders background and sprites to the window
    Present;
  end;
  // Frees all textures, images, and D3D objects
  Finalize;
  SDL_Quit;
end;

{
  File Notes:

  -- 0.77.1 ---------------------------------------------------
    - Refactored the demo (moved stuff to unit from dpr) and
      added Lazarus project files.

  -- 0.26.1 Changes/Bug Fixes ---------------------------------
    - Delphi 2009 compatibility pchar/string changes.

  -- 0.17 Changes/Bug Fixes -----------------------------------
    - S key now saves screenshot to file
}


end.
