unit DEng_glsl;
//
//  Low level unit for making work with OpenGL GLSL shaders easier in DEng.
//  Please do not use it in high level code, use DEngShader instead if possible.
//

{$MODE FPC}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

uses
  dsbo,
  dansiview,
  {$IFDEF ANDROID}GLES,{$ELSE}GL, GLext,{$ENDIF}
  {$IFDEF MSWINDOWS}windows,{$ENDIF}
  DEng;

function deng_glCompileShader(Id: GLuint;
                              Name: TAnsiView;
                              Source: TAnsiView): Boolean;
      //  Compiles shader in the specified Source to the specified Id.
      //  If it fails, prints error message to log and returns False.
      //  If succeded, returns True.
      //  The specified Name is used in log message to identify the shader.
      //
      //  Essentially the function is glShaderSource+glCompileShader calls.

function deng_glLinkShaders(const Name: TAnsiView;
                            ShaderProgram: GLuint;
                            VertexShader: GLuint;
                            FragmentShader: GLuint): Boolean;
      //  Links the specified vertex and fragment shaders to the program.
      //  Returns True if succeded. Returns False and prints error to log
      //  otherwise.
      //
      //  Essentially the function is glAttachShader+glLinkProgram calls.

procedure deng_glDeleteShaderProgram(ShaderProgram: GLuint;
                                     VertexShader: GLuint;
                                     FragmentShader: GLuint);
      // Detachs the shaders from the program, then deletes all the shaders
      // and the program.

implementation

function deng_glCompileShader(Id: GLuint;
                              Name: TAnsiView;
                              Source: TAnsiView): Boolean;
var
  S: PAnsiChar;
  Len: GLint;
  CompileStatus: GLint;
  InfoLog: PAnsiChar;
  SBO: array[0 .. 512 - 1] of Byte;
  // MaxLength and InfoLogLength have different types
  MaxLength: GLint;
  InfoLogLength: GLsizei;
  ActualLength: SizeInt;
begin
  // Actual work
  S := Source.Data;
  Len := Source.Length;
  glShaderSource(Id, 1, @S, @Len);
  glCompileShader(Id);

  // Get status to be returned
  glGetShaderiv(Id, GL_COMPILE_STATUS, @CompileStatus);
  Result := CompileStatus = GL_TRUE;

  // Get InfoLog if presented
  MaxLength := 0;
  InfoLogLength := 0;
  glGetShaderiv(Id, GL_INFO_LOG_LENGTH, @MaxLength);
  if MaxLength > 0 then begin
    InfoLog := SBOGetMem(SBO, MaxLength);
    InfoLogLength := MaxLength;
    glGetShaderInfoLog(Id, MaxLength, @InfoLogLength, InfoLog);
    ActualLength := IndexByte(InfoLog^, InfoLogLength, 0);
    if ActualLength <> -1 then
      InfoLogLength := ActualLength;
  end else
    InfoLog := '';

  // Print info to log if needed
  if not Result then begin
    LogTagged('DD', 'FAILED ' + Name.ToString + ': ' + AnsiView(InfoLog, InfoLogLength).ToString);
  end else begin
    if InfoLogLength > 20 then begin
      LogTagged('DD', Name.ToString + ': ' + AnsiView(InfoLog, InfoLogLength).ToString);
    end;
  end;

  // Free memory of the InfoLog
  if MaxLength > 0 then
    SBOFreeMem(SBO, InfoLog);
end;

function deng_glLinkShaders(const Name: TAnsiView;
                            ShaderProgram: GLuint;
                            VertexShader: GLuint;
                            FragmentShader: GLuint): Boolean;
var
  LinkStatus: GLint;
  InfoLog: PAnsiChar;
  SBO: array[0 .. 512 - 1] of Byte;
  // MaxLength and InfoLogLength have different types
  MaxLength: GLint;
  InfoLogLength: GLsizei;
  ActualLength: SizeInt;
begin
  // Do the actual work
  glAttachShader(ShaderProgram, VertexShader);
  glAttachShader(ShaderProgram, FragmentShader);
  glLinkProgram(ShaderProgram);

  // Check for an error
  glGetProgramiv(ShaderProgram, GL_LINK_STATUS, @LinkStatus);
  Result := LinkStatus = GL_TRUE;

  // Get InfoLog if presented
  MaxLength := 0;
  InfoLogLength := 0;
  glGetProgramiv(ShaderProgram, GL_INFO_LOG_LENGTH, @MaxLength);
  if MaxLength > 0 then begin
    InfoLog := SBOGetMem(SBO, MaxLength);
    InfoLogLength := MaxLength;
    glGetProgramInfoLog(ShaderProgram, MaxLength, @InfoLogLength, InfoLog);
    ActualLength := IndexByte(InfoLog^, InfoLogLength, 0);
    if ActualLength <> -1 then
      InfoLogLength := ActualLength;
  end else
    InfoLog := '';

  // Print info to log if needed
  if not Result then begin
    LogTagged('DD', 'FAILED ' + Name.ToString + ': ' + AnsiView(InfoLog, InfoLogLength).ToString);
  end else begin
    //if InfoLogLength > 20 then begin
    //  LogTagged('DD', Name.ToString + ': ' + AnsiView(InfoLog, InfoLogLength).ToString);
    //end;
  end;

  // Free memory of the InfoLog
  if MaxLength > 0 then
    SBOFreeMem(SBO, InfoLog);
end;

procedure deng_glDeleteShaderProgram(ShaderProgram: GLuint;
                                     VertexShader: GLuint;
                                     FragmentShader: GLuint);
begin
  glDetachShader(ShaderProgram, VertexShader);
  glDetachShader(ShaderProgram, FragmentShader);
  glDeleteShader(VertexShader);
  glDeleteShader(FragmentShader);
  glDeleteProgram(ShaderProgram);
end;

end.
