{ ***************************************************************************** See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** Author: Mattias Gaertner ToDo: use custom pixelformat attributes: doublebufferd, version, ... It should work with initWithFrame_pixelFormat, but this paints nothing SwapBuffers - there is no function like aglSwapBuffers in CGL/NS Mouse: the TLCLCommonCallback mouse handlers check Owner.isEnabled, which for a NSView always returns false. } unit GLCocoaNSContext; {$mode objfpc}{$H+} {$ModeSwitch objectivec1} interface uses Classes, SysUtils, types, CocoaWSCommon, CocoaPrivate, CocoaUtils, LCLType, Cocoa_Extra, LMessages, LCLMessageGlue, Controls, LazLoggerBase, WSLCLClasses, MacOSAll, CocoaAll; function LBackingScaleFactor(Handle: HWND): single; procedure LSetWantsBestResolutionOpenGLSurface(const AValue: boolean; Handle: HWND); 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, AMacRetinaMode: boolean; MajorVersion, MinorVersion: Cardinal; MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: Cardinal; const {%H-}AParams: TCreateParams): HWND; procedure LOpenGLDestroyContextInfo(AWinControl: TWinControl); function CreateOpenGLContextAttrList(DoubleBuffered: boolean; MajorVersion, MinorVersion: Cardinal; MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: cardinal): NSOpenGLPixelFormatAttributePtr; const // missing constants in FPC 3.1.1 rev 31197 and below NSOpenGLPFAOpenGLProfile = 99; //cr: name changed to match https://developer.apple.com/library/mac/documentation//Cocoa/Reference/ApplicationKit/Classes/NSOpenGLPixelFormat_Class/index.html NSOpenGLProfileLegacy = $1000; NSOpenGLProfileVersion3_2Core = $3200; NSOpenGLProfileVersion4_1Core = $4100; //requires OSX SDK 10.10 or later, https://github.com/google/gxui/issues/98 type NSOpenGLViewFix = objccategory external (NSOpenGLView) procedure setWantsBestResolutionOpenGLSurface(bool: NSInteger); message 'setWantsBestResolutionOpenGLSurface:'; end; NSScreenFix = objccategory external (NSScreen) function backingScaleFactor: CGFloat ; message 'backingScaleFactor'; end; { TCocoaOpenGLView } TCocoaOpenGLView = objcclass(NSOpenGLView) public Owner: TWinControl; callback: TLCLCommonCallback; backingScaleFactor: Single; function acceptsFirstResponder: LCLObjCBoolean; override; function becomeFirstResponder: LCLObjCBoolean; override; function resignFirstResponder: LCLObjCBoolean; override; procedure drawRect(dirtyRect: NSRect); override; procedure dealloc; override; function lclGetCallback: ICommonCallback; override; procedure lclClearCallback; override; function lclIsEnabled: Boolean; override; // mouse procedure mouseDown(event: NSEvent); override; procedure mouseUp(event: NSEvent); override; procedure rightMouseDown(event: NSEvent); override; procedure rightMouseUp(event: NSEvent); override; procedure rightMouseDragged(event: NSEvent); override; procedure otherMouseDown(event: NSEvent); override; procedure otherMouseUp(event: NSEvent); override; procedure otherMouseDragged(event: NSEvent); override; procedure mouseDragged(event: NSEvent); override; procedure mouseEntered(event: NSEvent); override; procedure mouseExited(event: NSEvent); override; procedure mouseMoved(event: NSEvent); override; procedure scrollWheel(event: NSEvent); override; end; function GetCGLContextObj(OpenGLControlHandle: HWND): CGLContextObj; (*function CreateCGLContextAttrList(DoubleBuffered: boolean; {$IFDEF UsesModernGL} MajorVersion, MinorVersion: Cardinal; {$ENDIF} MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: cardinal): PInteger; function IsCGLPixelFormatAvailable(Attribs: PInteger): boolean;*) implementation //value > 1 if screen is scaled, e.g. default for MOST retina displays is 2 function LBackingScaleFactor(Handle: HWND): single; begin result := TCocoaOpenGLView(Handle).backingScaleFactor; end; procedure LSetWantsBestResolutionOpenGLSurface(const AValue: boolean; Handle: HWND); var View: TCocoaOpenGLView; begin if Handle=0 then exit; View:=TCocoaOpenGLView(Handle); if not View.respondsToSelector(objcselector('setWantsBestResolutionOpenGLSurface:')) then exit; if AValue then View.setWantsBestResolutionOpenGLSurface(1) else View.setWantsBestResolutionOpenGLSurface(0); if (AValue) and (NSScreen.mainScreen.respondsToSelector(objcselector('backingScaleFactor'))) then //MacOS >=10.7 View.backingScaleFactor := NSScreen.mainScreen.backingScaleFactor else View.backingScaleFactor := 1; end; procedure LOpenGLViewport(Handle: HWND; Left, Top, Width, Height: integer); var View: NSOpenGLView absolute Handle; lFinalWidth, lFinalHeight: Integer; begin lFinalWidth := Width; lFinalHeight := Height; if View <> nil then begin lFinalWidth := Round(Width * LBackingScaleFactor(Handle)); lFinalHeight := Round(Height * LBackingScaleFactor(Handle)); end; glViewport(Left,Top,lFinalWidth,lFinalHeight); end; procedure LOpenGLSwapBuffers(Handle: HWND); //var // View: TCocoaOpenGLView; //TCocoaOpenGLView begin if Handle=0 then exit; glFlush(); // View:=TCocoaOpenGLView(Handle); // View.nsGL.flushBuffer; end; function LOpenGLMakeCurrent(Handle: HWND): boolean; var CGLContext: CGLContextObj; begin if Handle=0 then exit(false); CGLContext:=GetCGLContextObj(Handle); Result:=CGLSetCurrentContext(CGLContext)=kCGLNoError; end; function LOpenGLReleaseContext(Handle: HWND): boolean; begin if Handle=0 then exit(false); Result:=CGLSetCurrentContext(nil)=kCGLNoError; //Result:=true; end; procedure LOpenGLClip(Handle: HWND); begin if Handle=0 then exit; // ToDo end; function LOpenGLCreateContext(AWinControl: TWinControl; WSPrivate: TWSPrivateClass; SharedControl: TWinControl; DoubleBuffered, AMacRetinaMode: boolean; MajorVersion, MinorVersion: Cardinal; MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: Cardinal; const AParams: TCreateParams): HWND; var View: TCocoaOpenGLView; Attrs: NSOpenGLPixelFormatAttributePtr; PixFmt: NSOpenGLPixelFormat; p: NSView; ns: NSRect; aNSOpenGLContext, SharedContext: NSOpenGLContext; begin Result:=0; p := nil; if (AParams.WndParent <> 0) then p := NSObject(AParams.WndParent).lclContentView; if Assigned(p) then LCLToNSRect(types.Bounds(AParams.X, AParams.Y, AParams.Width, AParams.Height), p.frame.size.height, ns) else ns := GetNSRect(AParams.X, AParams.Y, AParams.Width, AParams.Height); Attrs:=CreateOpenGLContextAttrList(DoubleBuffered,MajorVersion,MinorVersion, MultiSampling,AlphaBits,DepthBits,StencilBits,AUXBuffers); try PixFmt:=NSOpenGLPixelFormat(NSOpenGLPixelFormat.alloc).initWithAttributes(Attrs); { Use SharedControl to share OpenGL resources with another TOpenGLControl instance } if SharedControl <> nil then SharedContext := TCocoaOpenGLView(SharedControl.Handle).openGLContext else SharedContext := nil; aNSOpenGLContext:=NSOpenGLContext(NSOpenGLContext.alloc).initWithFormat_shareContext(PixFmt,SharedContext); if aNSOpenGLContext = nil then debugln(['LOpenGLCreateContext Error']); View := TCocoaOpenGLView(TCocoaOpenGLView.alloc).initWithFrame_pixelFormat(ns,PixFmt); if not Assigned(View) then Exit; finally FreeMem(Attrs); end; View.setHidden(AParams.Style and WS_VISIBLE = 0); if Assigned(p) then p.addSubview(View); SetViewDefaults(View); View.Owner:=AWinControl; { If we wouldn't set View.openGLContext, it would get automatically created. But then aNSOpenGLContext is ignored, and so SharedContext and SharedControl don't work. } View.setOpenGLContext(aNSOpenGLContext); View.callback:=TLCLCommonCallback.Create(View, AWinControl); LSetWantsBestResolutionOpenGLSurface(AMacRetinaMode, HWND(View)); //View.setPixelFormat(PixFmt); Result:=TLCLHandle(View); end; procedure LOpenGLDestroyContextInfo(AWinControl: TWinControl); begin // no special needed, simply release handle if AWinControl=nil then raise Exception.Create(''); end; function CreateOpenGLContextAttrList(DoubleBuffered: boolean; MajorVersion, MinorVersion: Cardinal; MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: cardinal): NSOpenGLPixelFormatAttributePtr; var p: integer; procedure AddUInt32(i: NSOpenGLPixelFormatAttribute); begin if Result<>nil then Result[p]:=i; inc(p); end; procedure CreateList; begin //see https://developer.apple.com/library/mac/documentation//Cocoa/Reference/ApplicationKit/Classes/NSOpenGLPixelFormat_Class/index.html //AddUInt32(NSOpenGLPFAAccelerated); // <- comment out: we can run in software if hardware is not available //AddUInt32(NSOpenGLPFAOpenGLProfile); //Versions beyond 'Legacy' appear to break CULL_FACE and DEPTH_BUFFER, legacy seems to be default, so comment out whole instruction //if (MajorVersion>=4) and (MinorVersion>=1) // AddUInt32(NSOpenGLProfileVersion4_1Core); //else if (MajorVersion>=3) and (MinorVersion>=2) then // AddUInt32(NSOpenGLProfileVersion3_2Core); //else //AddUInt32(NSOpenGLProfileLegacy); // NSOpenGLProfileLegacy is default and sufficient, later versions depend on SDK we are building against AddUInt32(NSOpenGLPFAOpenGLProfile); if (MajorVersion>=4) and (MinorVersion>=1) then AddUInt32(NSOpenGLProfileVersion4_1Core) //OpenGL 4.1, GLSL 4.1 else if (MajorVersion>=3) and (MinorVersion>=2) then AddUInt32(NSOpenGLProfileVersion3_2Core) else AddUInt32(NSOpenGLProfileLegacy); //OpenGL 2.1, GLSL 1.2 AddUInt32(NSOpenGLPFAColorSize); AddUInt32(24); if DepthBits > 0 then begin AddUInt32(NSOpenGLPFADepthSize); AddUInt32(32); end; if AlphaBits>0 then begin AddUInt32(NSOpenGLPFAAlphaSize); AddUInt32(AlphaBits); end; AddUInt32(NSOpenGLPFAAccelerated); if MultiSampling > 1 then begin AddUInt32(NSOpenGLPFAMultisample); AddUInt32(NSOpenGLPFASampleBuffers); AddUInt32(1); AddUInt32(NSOpenGLPFASamples); AddUInt32(MultiSampling); end; if StencilBits>0 then begin AddUInt32(NSOpenGLPFAStencilSize); AddUInt32(StencilBits); end; if AUXBuffers>0 then begin AddUInt32(NSOpenGLPFAAuxBuffers); AddUInt32(AUXBuffers); end; //if DoubleBuffered then //requires fix for nsGL // AddUInt32(NSOpenGLPFADoubleBuffer); //this doen't work with Lazarus AddUInt32(NSOpenGLPFAMaximumPolicy); //allows future changes to make attributes more demanding, e.g. add multisampling AddUInt32(NSOpenGLPFANoRecovery); //see apple web page: "not generally useful" but might help with multisample AddUInt32(0); // end of list end; begin Result:=nil; p:=0; CreateList; GetMem(Result,SizeOf(NSOpenGLPixelFormatAttribute)*(p+1)); p:=0; CreateList; end; function GetCGLContextObj(OpenGLControlHandle: HWND): CGLContextObj; var View: NSOpenGLView; begin Result:=nil; if OpenGLControlHandle=0 then exit; View:=TCocoaOpenGLView(OpenGLControlHandle); Result:=CGLContextObj(View.openGLContext.CGLContextObj); NSScreen.mainScreen.colorSpace; end; (* //these functions are commented out: this was an attempt to use CGL, porting NSOpenGLView instead was more successful function CreateCGLContextAttrList(DoubleBuffered: boolean; 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(kCGLPFAWindow); deprecated since 10.9 Add(kCGLPFAAccelerated); if DoubleBuffered then Add(kCGLPFADoubleBuffer); //if (MajorVersion>=3) and (MinorVersion>=2) then // Add(kCGLOGLPVersion); Add(kCGLPFANoRecovery); Add(kCGLPFAMaximumPolicy); Add(kCGLPFASingleRenderer); if AlphaBits>0 then begin Add(kCGLPFAAlphaSize); Add(AlphaBits); end; if DepthBits>0 then begin Add(kCGLPFADepthSize); Add(DepthBits); end; if StencilBits>0 then begin Add(kCGLPFAStencilSize); Add(StencilBits); end; if AUXBuffers>0 then begin //Add(kCGLPFAAuxBuffers); Add(AUXBuffers); ToDo end; if MultiSampling > 1 then begin Add(kCGLPFASampleBuffers); Add(1); Add(kCGLPFASamples); Add(MultiSampling); end; Add(0); // end of list end; begin Result:=nil; p:=0; CreateList; GetMem(Result,SizeOf(integer)*p); p:=0; CreateList; end; function IsCGLPixelFormatAvailable(Attribs: PInteger): boolean; var //display: CGDirectDisplayID; aPixFormatObj: CGLPixelFormatObj; aPixObjCountAttrList: GLint; begin //display := CGMainDisplayID(); if CGLChoosePixelFormat(Attribs, @aPixFormatObj, @aPixObjCountAttrList)<>kCGLNoError then exit(false); if aPixFormatObj=nil then exit(false); Result:=true; // ToDo: free aPixFormatObj end; *) { TCocoaOpenGLView } function TCocoaOpenGLView.acceptsFirstResponder: LCLObjCBoolean; begin Result := True; end; function TCocoaOpenGLView.becomeFirstResponder: LCLObjCBoolean; begin Result:=inherited becomeFirstResponder; callback.BecomeFirstResponder; end; function TCocoaOpenGLView.resignFirstResponder: LCLObjCBoolean; begin Result:=inherited resignFirstResponder; callback.ResignFirstResponder; end; procedure TCocoaOpenGLView.dealloc; begin inherited dealloc; end; function TCocoaOpenGLView.lclGetCallback: ICommonCallback; begin Result := callback; end; procedure TCocoaOpenGLView.lclClearCallback; begin callback := nil; end; function TCocoaOpenGLView.lclIsEnabled: Boolean; begin Result := Owner.Enabled; end; procedure TCocoaOpenGLView.mouseDown(event: NSEvent); begin if not Assigned(callback) or not callback.MouseUpDownEvent(event) then begin // do not pass mouseDown below or it will pass it to the parent control // causing double events //inherited mouseDown(event); end; end; procedure TCocoaOpenGLView.mouseUp(event: NSEvent); begin if not Assigned(callback) or not callback.MouseUpDownEvent(event) then inherited mouseUp(event); end; procedure TCocoaOpenGLView.rightMouseDown(event: NSEvent); begin if not Assigned(callback) or not callback.MouseUpDownEvent(event) then inherited rightMouseDown(event); end; procedure TCocoaOpenGLView.rightMouseUp(event: NSEvent); begin if not Assigned(callback) or not callback.MouseUpDownEvent(event) then inherited rightMouseUp(event); end; procedure TCocoaOpenGLView.rightMouseDragged(event: NSEvent); begin if not Assigned(callback) or not callback.MouseMove(event) then inherited rightMouseDragged(event); end; procedure TCocoaOpenGLView.otherMouseDown(event: NSEvent); begin if not Assigned(callback) or not callback.MouseUpDownEvent(event) then inherited otherMouseDown(event); end; procedure TCocoaOpenGLView.otherMouseUp(event: NSEvent); begin if not Assigned(callback) or not callback.MouseUpDownEvent(event) then inherited otherMouseUp(event); end; procedure TCocoaOpenGLView.otherMouseDragged(event: NSEvent); begin if not Assigned(callback) or not callback.MouseMove(event) then inherited otherMouseDragged(event); end; procedure TCocoaOpenGLView.mouseDragged(event: NSEvent); begin if Assigned(callback) then callback.MouseMove(event) else inherited mouseDragged(event); end; procedure TCocoaOpenGLView.mouseEntered(event: NSEvent); begin inherited mouseEntered(event); end; procedure TCocoaOpenGLView.mouseExited(event: NSEvent); begin inherited mouseExited(event); end; procedure TCocoaOpenGLView.mouseMoved(event: NSEvent); begin if not Assigned(callback) or not callback.MouseMove(event) then inherited mouseMoved(event); end; procedure TCocoaOpenGLView.scrollWheel(event: NSEvent); begin if Assigned(callback) then callback.scrollWheel(event) else inherited scrollWheel(event); end; procedure TCocoaOpenGLView.drawRect(dirtyRect: NSRect); var ctx : NSGraphicsContext; PS : TPaintStruct; r : NSRect; begin ctx := NSGraphicsContext.currentContext; inherited drawRect(dirtyRect); if CheckMainThread and Assigned(callback) then begin if ctx = nil then begin // In macOS 10.14 (mojave) current context is nil // we still can paint anything related to OpenGL! // todo: consider creating a dummy context (for a bitmap) FillChar(PS, SizeOf(TPaintStruct), 0); r := frame; r.origin.x:=0; r.origin.y:=0; PS.hdc := HDC(0); PS.rcPaint := NSRectToRect(r); LCLSendPaintMsg(Owner, HDC(0), @PS); end else callback.Draw(ctx, bounds, dirtyRect); end; end; end.