{$reference System.Windows.Forms.dll}
{$reference System.Drawing.dll}

// Данный пример демонстрирует запуск простейшей программы с модулем OpenGL
// Примите во внимание, что методы из gl_gdi. - могут служить только временной заменой в серьёзной программе
// (По крайней мере в данной версии. В будущем, возможно, gl_gdi будет улучшено)
// Они использованы тут, чтоб пример был проще

uses System.Windows.Forms;
uses System.Drawing;
uses System;
uses OpenGL;

{$apptype windows} // убирает консоль

var gl: OpenGL.gl<PlWin>;

{$region Shader}

function InitShader(fname: string; st: ShaderType): gl_shader;
begin
  Result := gl.CreateShader(st);
  
  var source := ReadAllText(fname);
  
  gl.ShaderSource(Result, 1,
    new string[](source),
    new integer[](source.Length)
  );
  
  gl.CompileShader(Result);
  // получаем состояние успешности компиляции
  // 1=успешно
  // 0=ошибка
  var comp_ok: integer;
  gl.GetShaderiv(Result, ShaderParameterName.COMPILE_STATUS, comp_ok);
  if comp_ok = 0 then
  begin
    
    // узнаём нужную длинную строки
    var l: integer;
    gl.GetShaderiv(Result, ShaderParameterName.INFO_LOG_LENGTH, l);
    
    // выделяем достаточно памяти чтоб сохранить строку
    var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
    
    // получаем строку логов
    gl.GetShaderInfoLog(Result, l, IntPtr.Zero, ptr);
    
    // преобразовываем в управляемую строку
    var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
    Writeln(log);
    
    // и в конце обязательно освобождаем памяти, чтобы не было утечек памяти
    System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
  end;
  
end;

{$endregion Shader}

{$region Program}

function InitProgram(vertex_shader, fragment_shader: gl_shader): gl_program;
begin
  Result := gl.CreateProgram;
  
  gl.AttachShader(Result, vertex_shader);
  if fragment_shader<>gl_shader.Zero then gl.AttachShader(Result, fragment_shader);
  
  gl.LinkProgram(Result);
  // всё то же самое что и у шейдеров
  var link_ok: integer;
  gl.GetProgramiv(Result, ProgramPropertyARB.LINK_STATUS, link_ok);
  if link_ok = 0 then
  begin
    
    var l: integer;
    gl.GetProgramiv(Result, ProgramPropertyARB.INFO_LOG_LENGTH, l);
    var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
    
    gl.GetProgramInfoLog(Result, l, IntPtr.Zero, ptr);
    var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
    Writeln(log);
    
    System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
  end;
  
end;

{$endregion Program}

const dy = -Sin(Pi / 6) / 2;

begin
  
  // Создаёт и настраиваем окно
  var f := new Form;
  f.StartPosition := FormStartPosition.CenterScreen;
  f.ClientSize := new Size(500, 500);
  f.FormBorderStyle := FormBorderStyle.Fixed3D;
  // Если окно закрылось - надо сразу завершить программу
  f.Closed += (o,e)->Halt();
  
  // Настраиваем поверхность рисования
  var hdc := gl_gdi.InitControl(f);
  
  // Настраиваем перерисовку
  gl_gdi.SetupControlRedrawing(f, hdc, EndFrame->
  begin
    
    {$region Настройка глобальных параметров OpenGL}
    
    // При создании экземпляра OpenGL.gl инициализируются некоторые функции
    // Это необходимо для всех функций из OpenGL1.2 и выше, потому что они локальны для контекста OpenGL
    gl := new OpenGL.gl<PlWin>;
    
    {$endregion Настройка глобальных параметров OpenGL}
    
    {$region Инициализация переменных}
    
    var vertex_pos_buffer: gl_buffer;
    gl.CreateBuffers(1, vertex_pos_buffer);
    gl.NamedBufferData(
      vertex_pos_buffer,
      new IntPtr(3*sizeof(Vec2f)),
      ArrGen(3, i->
      begin
        var rot := i * Pi * 2 / 3;
        
        Result := new Vec2f(
          Sin(rot),
          Cos(rot) + dy
        );
        
      end),
      VertexBufferObjectUsage.STATIC_DRAW
    );
    
    var vertex_clr_buffer: gl_buffer;
    gl.CreateBuffers(1, vertex_clr_buffer);
    gl.NamedBufferData(
      vertex_clr_buffer,
      new IntPtr(3*sizeof(Vec3f)),
      new Vec3f[](
        new Vec3f(1,0,0),
        new Vec3f(0,1,0),
        new Vec3f(0,0,1)
      ),
      VertexBufferObjectUsage.STATIC_DRAW
    );
    
    var element_buffer: gl_buffer;
    gl.CreateBuffers(1, element_buffer);
    gl.NamedBufferData(
      element_buffer,
      new IntPtr(3*sizeof(byte)),
      new byte[](
        0,1,2
      ),
      VertexBufferObjectUsage.STATIC_DRAW
    );
    
    var vertex_shader := InitShader('Rot Triangle 1.vertex.glsl', ShaderType.VERTEX_SHADER);
//    var fragment_shader := InitShader('fragment.glsl', ShaderType.FRAGMENT_SHADER);
    
    var sprog := InitProgram(vertex_shader, gl_shader.Zero {fragment_shader});
    
    var uniform_rot_k :=     gl.GetUniformLocation(sprog, 'rot_k');
    
    var attribute_position := gl.GetAttribLocation(sprog, 'position');
    var attribute_color :=    gl.GetAttribLocation(sprog, 'color');
    
    var t := new System.Diagnostics.Stopwatch;
    t.Start;
    
    {$endregion Инициализация переменных}
    
    while true do
    begin
      // очищаем окно в начале перерисовки
      gl.Clear(ClearBufferMask.COLOR_BUFFER_BIT);
      
      
      
      gl.UseProgram(sprog);
      
      gl.Uniform1f(uniform_rot_k, Cos( t.Elapsed.Ticks * 0.0000002 ) );
      
      gl.BindBuffer(BufferTargetARB.ARRAY_BUFFER, vertex_pos_buffer);
      gl.VertexAttribPointer(
        attribute_position,
        2,
        VertexAttribPointerType.FLOAT,
        false,
        8,
        IntPtr.Zero
      );
      gl.EnableVertexAttribArray(attribute_position);
      
      gl.BindBuffer(BufferTargetARB.ARRAY_BUFFER, vertex_clr_buffer);
      gl.VertexAttribPointer(
        attribute_color,
        3,
        VertexAttribPointerType.FLOAT,
        false,
        12,
        IntPtr.Zero
      );
      gl.EnableVertexAttribArray(attribute_color);
      
      gl.BindBuffer(BufferTargetARB.ELEMENT_ARRAY_BUFFER, element_buffer);
      gl.DrawElements(
        PrimitiveType.TRIANGLES,
        3,
        DrawElementsType.UNSIGNED_BYTE,
        IntPtr.Zero
      );
      
      gl.DisableVertexAttribArray(attribute_position);
      gl.DisableVertexAttribArray(attribute_color);
      
      
      
      // получаем тип последней ошибки
      var err := gl.GetError;
      // и если ошибка есть - выводим её
      if err<>ErrorCode.NO_ERROR then Writeln(err);
      
      gl.Finish;
      // EndFrame меняет местами буферы и ждёт vsync
      EndFrame;
    end;
    
  end);
  
  Application.Run(f);
end.