{
    This file is part of Chentrah,
    Copyright (C) 2004-2008 Anton Rzheshevski (chebmaster@mail.ru).

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses/

 **********************************************************************
 
    This file contains the OpenGL initialization and function loading
    routines

 **********************************************************************}



var
  LibGL: {$ifdef use_dynlibs} THandle {$else} Pointer {$endif} ;
  LibGLName: string;

  function GetGLProcAddress2(ProcName, Suffix1, Suffix2: ansistring): Pointer;
  begin
    //Result:=_GetGLProcAddress(ProcName);
    //if not Assigned(Result)
    //  then
    Result:=_GetGLProcAddress(ProcName + Suffix1);
    if not Assigned(Result)
      then Result:=_GetGLProcAddress(ProcName + Suffix2);
    if not Assigned(Result)
      then Die(MI_ERROR_GL_FUNCTION_NOT_FOUND,
        [ProcName + Suffix1 + '() | ' + ProcName + Suffix2 + '()',
         utf8Decode(LibGlName), MessageContainer[MI_HINT_OPENGLTOOLOW]]);
  end;


  function GetGLProcAddress(ProcName, Suffix: ansistring): Pointer;
  var fn: string;
  begin
    Result:=_GetGLProcAddress(ProcName);
    if not Assigned(Result)
      then Result:=_GetGLProcAddress(ProcName + Suffix);
    if not Assigned(Result) then begin
      fn:= ProcName + '()';
      if Suffix <>'' then fn:=fn + ' | ' + ProcName + Suffix + '()';
      Die(MI_ERROR_GL_FUNCTION_NOT_FOUND,
        [fn, utf8Decode(LibGlName), MessageContainer[MI_HINT_OPENGLTOOLOW]]);
    end;
  end;


  function exGetGLProcAddress(ProcName, Suffix: PAnsiChar): Pointer; cdecl;
  begin
   try
    Result:=GetGlProcAddress(PCharToString(ProcName), PCharToString(Suffix));
   except
    ProcessGuardedException();
   end;
  end;

  function exGetGLProcAddress2(ProcName, Suffix1, Suffix2: PAnsiChar): Pointer; cdecl;
  begin
   try
    Result:=GetGlProcAddress2(PCharToString(ProcName), PCharToString(Suffix1), PCharToString(Suffix2));
   except
    ProcessGuardedException();
   end;
  end;



  function _GetGLProcAddress(ProcName: ansistring): Pointer;
  var
    baseaddr: pointer;
    exename: string;
  begin
    {$ifndef use_dynlibs}
      Result:=dlsym(LibGL, PChar(ProcName));
    {$else}
      Result:=GetProcAddress(LibGL, PChar(ProcName));
      // in Windows, the extension functions are loaded differently from the
      // "common" functions. What a pain.
      {$ifdef windows}
        if not Assigned(Result) and Assigned(wglGetProcAddress)
          then Result:=wglGetProcAddress(PChar(ProcName));
      {$endif}
    {$endif}
    if Assigned(Result) and MotherState.DebugMode then begin
      {$ifdef cpu64}
      AddLog('  %0() at %1', [ProcName, Result]);
      {$else}
      GetModuleByAddr(Result, baseaddr, exename);
      if (LibGlName <> exename) and (LibGlName <> '')
        then AddLog('  %0() at %1 in %2 (wrapper %3)', [ProcName, Result, exename, LibGlName])
        else AddLog('  %0() at %1 in %2', [ProcName, Result, exename]);
      {$endif}
    end;
  end;

  Procedure CloseOpenGL;
  begin
   {$ifndef use_dynlibs}
     If Assigned(LibGL) then dlClose(LibGL);
   {$else}
     If LibGL <> 0 then FreeLibrary(LibGL);
   {$endif}
  end;


  procedure CheckGLVersion;
  var
    i: integer;
    report: TIniFile;
  begin
    with MotherState do begin
      OGLRenderer:= String(PChar(glGetString(GL_RENDERER)));
      OGLVersionString:= String(PChar(glGetString(GL_VERSION)));
      OGLVendor:= String(PChar(glGetString(GL_VENDOR)));
      OGLSummary:= OGLVersionString + ' | ' + OGLRenderer + ' | ' + OGLVendor;
      RendererAlias:= SanateStringForFileName(OGLVendor + '_' + OGLRenderer + '_opengl' + OGLVersionString);
      for i:=1 to length(OGLRenderer) do
        if OGLRenderer[i] in ['/', '\', '|', ',', '.'] then begin
          OGLRenderer:=Copy(OGLRenderer, 1, i - 1);
          break;
        end;
//      cge.RenderName:= OGLRenderer;
      AddLog(MI_LOG_OGLINITIALIZED, [OGLVersionString,
                                     OGLRenderer,
                                     OGLVendor]);
      GetOpenGLVersion(OGLVersionHi, OGLVersionMed);
      OGLExtensions:= PChar(glGetString(GL_EXTENSIONS));
      OGLNpotSupported:= Pos('GL_ARB_texture_non_power_of_two', OGLExtensions) > 0;
      glGetIntegerv(GL_MAX_TEXTURE_SIZE, @OGLTextureMax);
      glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, @OGL3dTextureMax);
      glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, @OGLFboMax);
      glGetIntegerv(GL_MAX_TEXTURE_COORDS_ARB, @OGLMaxTextureCoords);
      glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, @OGLMaxTextureUnits);
      glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, @OGLMaxAnisotropy);

      if HomePath <> '' then begin
        //if the program starts normally, write a profile file for reporting purposes.
        //*** Implement sending it to the master server!
        if VerboseLog then AddLog('Saving opengl renderer profile');

        ValidateWritePath(HomePath + LogsDir);
        report:= TIniFile.Create(HomePath + LogsDir + 'opengl_'
          + SanateStringForFileName(
            String(PChar(glGetString(GL_RENDERER))) + '_' + OGLVersionString
          )
          + '.ini');
        report.WriteString('opengl', 'version', OGLVersionString);

        report.WriteInteger('opengl', 'max_texture_size', OGLTextureMax);
        report.WriteInteger('opengl', 'max_3d_texture_size', OGL3dTextureMax);
        report.WriteInteger('opengl', 'max_FBO_size', OGLFboMax);
        report.WriteBool('opengl', 'non-power-of-2_textures_supported', OGLNpotSupported);
        report.WriteInteger('opengl', 'fprg_max_texture_coords', OGLMaxTextureCoords);
        report.WriteInteger('opengl', 'fprg_max_texture_units', OGLMaxTextureUnits);
        report.WriteInteger('opengl', 'max_anisotropy', round(OGLMaxAnisotropy));

        report.WriteString('opengl_extensions', 'string', OGLExtensions);

        report.UpdateFile;
        report.Free;
      end;


      if (OGLVersionHi < OGL_REQ_HI)
      or ((OGLVersionHi = OGL_REQ_HI) and (OGLVersionMed < OGL_REQ_MED)) then begin
       {$ifdef windows}
        if lowercase (OGLRenderer) = 'gdi generic'
          then
            AddStopMessage(MI_MICROSOFT_GDI,[])
          else
       {$endif}
            AddStopMessage(MI_OPENGL_VERSION_TOO_LOW,
                            [OGL_REQ_HI,
                             OGL_REQ_MED,
                             MessageContainer[MI_HINT_OPENGLTOOLOW],
                             OGLVersionString,
                             OGLRenderer,
                             OGLVendor]);
      end;
    end;
  end;

  Procedure InitOpenGL;
  var
    cw: word;
    i: integer;
    gl_name,
    gl_name0: string;
    function LoadGLLibrary: boolean;
    begin
      if MotherState.DebugMode then AddLog('  Loading %0...', [gl_name]);
     {$ifdef use_dynlibs}
      LibGL:= LoadLibrary(PChar(gl_name)); //Windows and MacOS X
      Result:= LibGL <> 0;
     {$else}
      LibGL := dlopen(PChar(gl_name), RTLD_NOW); //Linux
      Result:= Assigned(LibGL);
     {$endif}
     if result then LibGlName:= GetDLLFileName(LibGL);
     if MotherState.DebugMode then
       if not result then AddLogComment('FAILED.')
                     else AddLogComment('Ok, %0', [LibGlName]);

    end;
  begin
    if MotherState.DebugMode then AddLog('Init OpenGL...');

    gl_name0:= Config.Str['opengl', 'GL_' + SystemSuffix + '_0'];
    i:=0;
    repeat
      gl_name:= Config.Str['opengl', 'GL_' + SystemSuffix + '_' + IntToStr(i)];
      if gl_name='' then Die(MI_ERROR_OPENGLDLLNOTFOUND, [
        {$ifdef unix} utf8Decode {$else} AnsiToWide {$endif}(gl_name0)]);
      if LoadGlLibrary() then break;
      inc(i);
    until false;

    //Disable FPU exceptions (it will ignore divisions by zero and such)
    //Without this, the program crashes in a most unexpected places!
    cw:= $133F;
    asm
      fldcw [cw]
    end;
    SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide,exOverflow, exUnderflow, exPrecision]);

   {$ifdef unix}
     {$ifdef darwin}
       //AGL code here...? Probably no need to, as everything should be hard linked.
     {$else}
      pointer(glXQueryExtensionsString):= GetGLProcAddress('glXQueryExtensionsString','');
      pointer(glXChooseVisual):= GetGLProcAddress('glXChooseVisual','');
      pointer(glXGetConfig):= GetGLProcAddress('glXGetConfig','');
      pointer(glXCreateContext):= GetGLProcAddress('glXCreateContext','');
      pointer(glXDestroyContext):= GetGLProcAddress('glXDestroyContext','');
      pointer(glXMakeCurrent):=  GetGLProcAddress('glXMakeCurrent','');
      pointer(glXSwapBuffers):= GetGLProcAddress('glXSwapBuffers','');
      pointer(glXSwapIntervalSGI):= _GetGLProcAddress('glXSwapIntervalSGI');
     {$endif}
   {$else}
      LoadFiveQuestionableWglFunctions;

      pointer(wglGetProcAddress) := GetGLProcAddress ('wglGetProcAddress','');
      pointer(wglCreateContext):= GetGLProcAddress ('wglCreateContext','');
      pointer(wglDeleteContext):= GetGLProcAddress ('wglDeleteContext','');
      pointer(wglMakeCurrent):= GetGLProcAddress ('wglMakeCurrent','');
   {$endif}

    if MotherState.DebugMode then AddLog('  Loading the procedure addresses from the DLL...');
    InitGlProcAddresses;

  end;


  procedure GetOpenGLVersion(var hi, med: integer);
  var
    s: String[3];
    p: PChar;
  begin
    try
      p:=glGetString(GL_VERSION);
      s:='';
      while p^ in ['0'..'9'] do begin
        s:=s + p^;
        inc (p);
      end;
      hi:=StrToInt(s);
      inc(p);
      s:='';
      while p^ in ['0'..'9'] do begin
        s:=s + p^;
        inc (p);
      end;
      med:=StrToInt(s);
    except
      hi:=OGL_REQ_HI;
      med:=OGL_REQ_MED;
    end;
  end;


