{
 *****************************************************************************
  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

  Author: Mattias Gaertner

  ToDo: MultiSampling, AuxBuffers
}
unit GLCarbonAGLContext;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LCLProc, LCLType, gl, Forms,
  MacOSAll,
  AGL, CarbonProc, CarbonDef, CarbonPrivate,
  WSLCLClasses, CarbonUtils,
  Controls;

procedure LOpenGLViewport(Handle: HWND; Left, Top, Width, Height: integer);
procedure LOpenGLSwapBuffers(Handle: HWND);
function LOpenGLMakeCurrent(Handle: HWND): boolean;
function LOpenGLReleaseContext(Handle: HWND): boolean;
procedure LOpenGLClip(Handle: HWND);
function LOpenGLCreateContext(AWinControl: TWinControl;
              {%H-}WSPrivate: TWSPrivateClass; SharedControl: TWinControl;
              DoubleBuffered, RGBA: boolean;
              const RedBits, GreenBits, BlueBits: Cardinal;
              const MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: Cardinal;
              const {%H-}AParams: TCreateParams): HWND;
procedure LOpenGLDestroyContextInfo(AWinControl: TWinControl);
function CreateOpenGLContextAttrList(DoubleBuffered: boolean; RGBA: boolean;
              RedBits, GreenBits, BlueBits, MultiSampling, AlphaBits, DepthBits,
              StencilBits, AUXBuffers: cardinal): PInteger;


type
  TAGLControlInfo = record
    Control: ControlRef;
    WinControl: TWinControl;
    AGLContext: TAGLContext;
  end;
  PAGLControlInfo = ^TAGLControlInfo;

var
  AGLControlInfo_FOURCC: FourCharCode;

function CreateAGLControlInfo(Control: ControlRef; AGLContext: TAGLContext;
  WinControl: TWinControl): PAGLControlInfo;
function GetAGLControlInfo(Control: ControlRef): PAGLControlInfo;
procedure FreeAGLControlInfo(Control: ControlRef);
function GetAGLContext(Control: ControlRef): TAGLContext;

implementation

procedure LOpenGLViewport(Handle: HWND; Left, Top, Width, Height: integer);
begin
  glViewport(Left,Top,Width,Height);
end;

procedure LOpenGLSwapBuffers(Handle: HWND);
var
  AGLContext: TAGLContext;
begin
  AGLContext:=GetAGLContext({%H-}ControlRef(Handle));
  aglSwapBuffers(AGLContext);
end;

function LOpenGLMakeCurrent(Handle: HWND): boolean;
var
  AGLContext: TAGLContext;
  Info: PAGLControlInfo;
  Control: TCarbonCustomControl;
begin
  Control:=TCarbonCustomControl(Handle);
  Info:=GetAGLControlInfo(Control.Widget);
  if Info=nil then exit;
  AGLContext:=Info^.AGLContext;
  Result:=aglSetCurrentContext(AGLContext)<>0;
end;

function LOpenGLReleaseContext(Handle: HWND): boolean;
begin
  Result := aglSetCurrentContext(nil)<>GL_FALSE;
end;

procedure LOpenGLClip(Handle: HWND);
var
  AGLContext: TAGLContext;
  Info: PAGLControlInfo;
  Form: TCustomForm;
  Win: WindowRef;
  //b: Rect;
  clipRgn: RgnHandle;
  Control: TCarbonCustomControl;
begin
  Control:=TCarbonCustomControl(Handle);
  Info:=GetAGLControlInfo(Control.Widget);
  debugln(['LOpenGLMakeCurrent got ingo...']);
  if Info=nil then exit;
  AGLContext:=Info^.AGLContext;
  aglSetCurrentContext(AGLContext);
  debugln(['LOpenGLMakeCurrent set current ',dbgs(Handle)]);
  Form:=GetParentForm(Info^.WinControl);
  debugln(['LOpenGLMakeCurrent got parent form: ',DbgSName(Form)]);
  Win:=TCarbonWindow(Form.Handle).Window;

  debugln(['LOpenGLMakeCurrent set clipping ...']);
  //GetWindowPortBounds(Win,b);
  clipRgn:=NewRgn;
  SetRectRgn(clipRgn,10,10,100,100);
  aglSetInteger({%H-}TAGLContext(GetWRefCon(Win)),AGL_CLIP_REGION,clipRgn);
  aglEnable({%H-}TAGLContext(GetWRefCon(Win)),AGL_CLIP_REGION);
  DisposeRgn(clipRgn);
end;

procedure ResizeOGLControl(AWidget: TCarbonWidget);
var
  info  : PAGLControlInfo;
  r     : array [0..3] of Integer;
  bnd   : HIRect;
  str   : MacOSAll.Rect;
  win   : WindowRef;
begin
  info:=GetAGLControlInfo(AWidget.Widget);
  if not Assigned(info) then Exit;
  win:=HIViewGetWindow(AWidget.Widget);
  if not Assigned(win) then Exit;
  GetWindowBounds(win, kWindowStructureRgn, str{%H-});
  HIViewGetBounds(AWidget.Widget, bnd{%H-});
  HIViewConvertPoint(bnd.origin, AWidget.Widget, nil);

  r[0]:=Round(bnd.origin.x);
  r[1]:=Round((str.bottom-str.top)- bnd.origin.y-bnd.size.height);
  r[2]:=Round(bnd.size.width);
  r[3]:=Round(bnd.size.height);

  aglEnable(info^.aglContext, AGL_BUFFER_RECT);
  aglSetInteger(info^.aglContext, AGL_BUFFER_RECT, @r[0]);
end;

function CarbonGLControl_Resize(ANextHandler: EventHandlerCallRef;
  AEvent: EventRef;
  AWidget: TCarbonWidget): OSStatus; {$IFDEF darwin}mwpascal;{$ENDIF}
begin
  Result:=CallNextEventHandler(ANextHandler, AEvent);
  ResizeOGLControl(AWidget);
end;

function CarbonGLControl_WindowChange(ANextHandler: EventHandlerCallRef;
  AEvent: EventRef;
  AWidget: TCarbonWidget): OSStatus; {$IFDEF darwin}mwpascal;{$ENDIF}
var
  win : WindowRef;
begin
  Result:=CallNextEventHandler(ANextHandler, AEvent);
  if GetEventParameter(AEvent, kEventParamControlCurrentOwningWindow, typeWindowRef,
      nil, sizeof(win), nil, @win) = noErr then
    if Assigned(win) then ResizeOGLControl(AWidget);
end;

function LOpenGLCreateContext(AWinControl: TWinControl;
  WSPrivate: TWSPrivateClass; SharedControl: TWinControl;
  DoubleBuffered, RGBA: boolean;
  const RedBits, GreenBits, BlueBits: Cardinal;
  const MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: Cardinal;
  const AParams: TCreateParams): HWND;
var
  disp: GDHandle;
  aglPixFmt: TAGLPixelFormat;
  aglContext: TAGLContext;
  Control: TCarbonCustomControl;
  AttrList: PInteger;
  C: TCreateParams;
  AGLInfo: PAGLControlInfo;
  TempSpec: EventTypeSpec;
begin
  Result:=0;
  if AWinControl.Parent=nil then
    RaiseGDBException('GLCarbonAGLContext.LOpenGLCreateContext no parent');

  C.X := AWinControl.Left;
  C.Y := AWinControl.Top;
  C.Width := AWinControl.Width;
  C.Height := AWinControl.Height;
  // create a custom control
  Control := TCarbonCustomControl.Create(AWinControl, C);
  //debugln(['LOpenGLCreateContext ',dbgsName(AWinControl)]);

  // create the AGL context
  disp := GetMainDevice ();
  AttrList:=CreateOpenGLContextAttrList(DoubleBuffered,RGBA,
    RedBits,GreenBits,BlueBits,
    MultiSampling,AlphaBits,DepthBits,StencilBits,AUXBuffers);
  aglPixFmt := aglChoosePixelFormat (@disp, 1, AttrList);
  System.FreeMem(AttrList);
  aglContext := aglCreateContext (aglPixFmt, NIL);
  aglDestroyPixelFormat(aglPixFmt);

  aglSetDrawable(aglContext,
    GetWindowPort(TCarbonWindow(GetParentForm(AWinControl).Handle).Window));
  AGLControlInfo_FOURCC := MakeFourCC('ACI ');

  AGLInfo:=CreateAGLControlInfo(Control.Widget, AGLContext, AWinControl);
  if AGLInfo<>GetAGLControlInfo(Control.Widget) then
    RaiseGDBException('GLCarbonAGLContext.LOpenGLCreateContext inconsistency');

  ResizeOGLControl(Control);
  TempSpec:=MakeEventSpec(kEventClassControl, kEventControlBoundsChanged);
  InstallControlEventHandler(Control.Widget, RegisterEventHandler(@CarbonGLControl_Resize),
    1, @TempSpec, Control, nil);
  TempSpec:=MakeEventSpec(kEventClassControl, kEventControlOwningWindowChanged);
  // The control might be embeded into a window after its creation.
  // See example for this in bug report #17244
  InstallControlEventHandler(Control.Widget, RegisterEventHandler(@CarbonGLControl_WindowChange),
    1, @TempSpec, Control, nil);

  Result:=HWnd(Control);
  //debugln(['LOpenGLCreateContext ',dbgs(Result)]);
end;

procedure LOpenGLDestroyContextInfo(AWinControl: TWinControl);
var
  Ref: ControlRef;
  Info: PAGLControlInfo;
begin
  if not AWinControl.HandleAllocated then Exit;
  if csDesigning in AWinControl.ComponentState then Exit;
  Ref := ControlRef(TCarbonControl(AWinControl.Handle).Widget);
  Info := GetAGLControlInfo(Ref);
  if Info=nil then exit;
  aglDestroyContext(Info^.AGLContext);
  Info^.AGLContext := nil;
  FreeAGLControlInfo(Ref);
end;

function CreateOpenGLContextAttrList(DoubleBuffered: boolean; RGBA: boolean;
  RedBits, GreenBits, BlueBits, MultiSampling, AlphaBits, DepthBits,
  StencilBits, AUXBuffers: cardinal): PInteger;
var
  p: integer;

  procedure Add(i: integer);
  begin
    if Result<>nil then
      Result[p]:=i;
    inc(p);
  end;

  procedure CreateList;
  begin
    Add(AGL_WINDOW);
    if DoubleBuffered then
      Add(AGL_DOUBLEBUFFER);
    if RGBA then
      Add(AGL_RGBA);
    Add(AGL_NO_RECOVERY);
    Add(AGL_MAXIMUM_POLICY);
    Add(AGL_SINGLE_RENDERER);
    Add(AGL_RED_SIZE); Add(RedBits);
    Add(AGL_GREEN_SIZE); Add(GreenBits);
    Add(AGL_BLUE_SIZE); Add(BlueBits);
    if AlphaBits>0 then
    begin
      Add(AGL_ALPHA_SIZE);  Add(AlphaBits);
    end;
    if DepthBits>0 then
    begin
      Add(AGL_DEPTH_SIZE);  Add(DepthBits);
    end;
    if StencilBits>0 then
    begin
      Add(AGL_STENCIL_SIZE);  Add(StencilBits);
    end;
    if AUXBuffers>0 then
    begin
      Add(AGL_AUX_BUFFERS);  Add(AUXBuffers);
    end;
    if MultiSampling > 1 then
    begin
      Add(AGL_SAMPLE_BUFFERS_ARB); Add(1);
      Add(AGL_SAMPLES_ARB); Add(MultiSampling);
    end;

    Add(AGL_NONE);
  end;

begin
  Result:=nil;
  p:=0;
  CreateList;
  GetMem(Result,SizeOf(integer)*p);
  p:=0;
  CreateList;
end;

function CreateAGLControlInfo(Control: ControlRef; AGLContext: TAGLContext;
  WinControl: TWinControl): PAGLControlInfo;
begin
  New(Result);
  FillByte(Result^, SizeOf(Result^), 0);
  Result^.Control:=Control;
  Result^.WinControl:=WinControl;
  Result^.AGLContext:=AGLContext;

  SetControlProperty(Control, LAZARUS_FOURCC, AGLControlInfo_FOURCC,
                     SizeOf(Result), @Result);
end;

function GetAGLControlInfo(Control: ControlRef): PAGLControlInfo;
var
  m: LongWord;
begin
  GetControlProperty(Control, LAZARUS_FOURCC, AGLControlInfo_FOURCC,
                     SizeOf(Result), @m, @Result);
end;

procedure FreeAGLControlInfo(Control: ControlRef);
var
  Info: PAGLControlInfo;
begin
  Info:=GetAGLControlInfo(Control);
  if Info=nil then exit;
  RemoveControlProperty(Control, LAZARUS_FOURCC, AGLControlInfo_FOURCC);
  System.FreeMem(Info);
end;

function GetAGLContext(Control: ControlRef): TAGLContext;
begin
  Result:=GetAGLControlInfo(TCarbonCustomControl(Control).Widget)^.AGLContext;
end;

initialization

finalization

end.

