unit DDrawFrameBuffer;

// TODO ensure FBO and others extenstions are supported

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

interface

uses
  dstrnum,
  dansiview,
  {$IFDEF ANDROID}GLES,{$ELSE}GL, GLext,{$ENDIF} // TODO use OpenGL api only in opengl backend
  DEng_glsl,
  DEng;

const
  USE_DEPTH_TEXTURE: Boolean = True;

type
TDrawFrameBuffer = object
private
  FWidth: PtrUInt;
  FHeight: PtrUInt;
  FMultisampling: PtrUInt;

  FFBOTexture: GLuint;
  FFBODepth: GLuint;
  FRBODepth: GLuint;
  FFBO: GLuint;

  FDepthTest: Boolean;
public
  procedure Init(Width, Height, Multisampling: PtrUInt);
  procedure Done;

  procedure ReInit(Width, Height, Multisampling: PtrUInt);

  procedure BeginRender; inline;
  procedure EndRender; inline;

  procedure Bind; inline;
  procedure Unbind; inline;

  procedure BindDepth; inline;
  procedure UnbindDepth; inline;

  procedure Start2D; // FIXME temporary

  property DepthTest: Boolean read FDepthTest write FDepthTest;
end;

implementation

procedure TDrawFrameBuffer.Init(Width, Height, Multisampling: PtrUInt);
var
  Status: GLenum;
begin
  FDepthTest := True;

  FMultisampling := Multisampling;
  if Multisampling < 4 then
    Multisampling := 4;
  FWidth := Width * (Multisampling div 4);
  FHeight := Height * (Multisampling div 4);
  Log(IntToStr(FWidth) + 'x' + IntToStr(FHeight) + ' ' + IntToStr(FMultisampling));

  glActiveTexture(GL_TEXTURE0);
  glGenTextures(1, @FFBOTexture);
  glBindTexture(GL_TEXTURE_2D, FFBOTexture);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FWidth, FHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
  glBindTexture(GL_TEXTURE_2D, 0);

  if USE_DEPTH_TEXTURE and FDepthTest then begin
    glActiveTexture(GL_TEXTURE1);
    glGenTextures(1, @FFBODepth);
    glBindTexture(GL_TEXTURE_2D, FFBODepth);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    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_DEPTH_TEXTURE_MODE, GL_INTENSITY);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_ALWAYS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, FWidth, FHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nil);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0);
  end else begin
    glGenRenderbuffersEXT(1, @FRBODepth);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, FRBODepth);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, FWidth, FHeight);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
  end;

  glGenFramebuffersEXT(1, @FFBO);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FFBO);
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, FFBOTexture, 0);
  if USE_DEPTH_TEXTURE and FDepthTest then begin
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, FFBODepth, 0);
  end else begin
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, FRBODepth);
  end;
  Status := glCheckFrameBufferStatusEXT(GL_FRAMEBUFFER_EXT);
  if Status <> GL_FRAMEBUFFER_COMPLETE_EXT then begin
    Log('ERROR glCheckFrameBufferStatus: $' + HexStr(Status, SizeOf(Status) * 2) + ' (' + IntToStr(Status) + ')');
    Halt(1);
  end;
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
end;

procedure TDrawFrameBuffer.Done;
begin
  glDeleteTextures(1, @FFBOTexture);
  if USE_DEPTH_TEXTURE and FDepthTest then begin
    glDeleteTextures(1, @FFBODepth);
  end else begin
    glDeleteRenderbuffersEXT(1, @FRBODepth);
  end;
  glDeleteFramebuffersEXT(1, @FFBO);
end;

procedure TDrawFrameBuffer.ReInit(Width, Height, Multisampling: PtrUInt);
begin
  FWidth := Width * (Multisampling div 4);
  FHeight := Height * (Multisampling div 4);
  FMultisampling := Multisampling;

  glBindTexture(GL_TEXTURE_2D, FFBOTexture);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FWidth, FHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
  glBindTexture(GL_TEXTURE_2D, 0);

  if USE_DEPTH_TEXTURE and FDepthTest then begin
    glBindTexture(GL_TEXTURE_2D, FFBODepth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, FWidth, FHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nil);
    glBindTexture(GL_TEXTURE_2D, 0);
  end else begin
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, FRBODepth);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, FWidth, FHeight);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
  end;
end;

procedure TDrawFrameBuffer.BeginRender;
begin
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FFBO); Eng.Draw^.LogErrors('glBindFramebufferEXT');
  glViewPort(0, 0, FWidth, FHeight); Eng.Draw^.LogErrors('glViewPort');
  if FDepthTest then begin
    glEnable(GL_DEPTH_TEST); Eng.Draw^.LogErrors('glEnable(GL_DEPTH_TEST)');
    glDepthFunc(GL_ALWAYS); Eng.Draw^.LogErrors('glDepthFunc(GL_ALWAYS)');
    glDepthMask(GL_TRUE); Eng.Draw^.LogErrors('glDepthMask(GL_TRUE)');
  end else begin
    glDisable(GL_DEPTH_TEST);
  end;
end;

procedure TDrawFrameBuffer.EndRender;
begin
  // glDisable(GL_DEPTH_TEST); Eng.Draw^.LogErrors('glDisable(GL_DEPTH_TEST)');
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); Eng.Draw^.LogErrors('glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)');
  glViewPort(0, 0, Eng.Width, Eng.Height); Eng.Draw^.LogErrors('glViewPort(0, 0, Eng.Width, Eng.Height)');
end;

procedure TDrawFrameBuffer.Bind;
begin
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, FFBOTexture);
end;

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

procedure TDrawFrameBuffer.BindDepth;
begin
  if USE_DEPTH_TEXTURE and FDepthTest then begin
    glActiveTexture(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, FFBODepth);
    glActiveTexture(GL_TEXTURE0);
  end else begin
    Log('TDrawFrameBuffer.BindDepth: depth texture binding is disabled');
  end;
end;

procedure TDrawFrameBuffer.UnbindDepth;
begin
  if USE_DEPTH_TEXTURE and FDepthTest then begin
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, 0);
    glDisable(GL_TEXTURE_2D);
    glActiveTexture(GL_TEXTURE0);
  end else begin
    Log('TDrawFrameBuffer.BindDepth: depth texture binding is disabled');
  end;
end;

procedure TDrawFrameBuffer.Start2D;
begin
  glViewPort(0, 0, FWidth, FHeight);
end;

end.
