View Issue Details

IDProjectCategoryView StatusLast Update
0036448LazarusLCLpublic2020-01-08 12:48
ReporterRyan Joseph Assigned ToJuha Manninen  
PrioritynormalSeverityminorReproducibilityN/A
Status resolvedResolutionreopened 
Product Version2.1 (SVN) 
Summary0036448: OpenGL Control for GTK3
DescriptionProposed implementation of GtkGLArea for the OpenGL control (https://developer.gnome.org/gtk3/stable/GtkGLArea.html). A few minor details are missing because I wasn't sure how they should be implemented. We have complete demos which work with the new widget set which document some of the quirks required by GtkGLArea.

https://github.com/neurolabusc/OpenGLCoreTutorials
https://github.com/neurolabusc/Metal-Demos
TagsNo tags attached.
Fixed in Revisionr62412, r62417
LazTarget-
WidgetsetGTK 3
Attached Files

Activities

Ryan Joseph

2019-12-16 16:09

reporter  

patch.diff (14,231 bytes)   
From 7f66ef1039ad320c1b4fb45053728ddec03cd319 Mon Sep 17 00:00:00 2001
From: Ryan Joseph <genericptr@gmail.com>
Date: Fri, 13 Dec 2019 14:54:10 -0500
Subject: [PATCH] GLK3 GLArea

---
 components/opengl/glgtk3glxcontext.pas       | 139 +++++++++++++++++++
 components/opengl/openglcontext.pas          |  13 ++
 lcl/interfaces/gtk3/gtk3bindings/lazgdk3.pas |  10 +-
 lcl/interfaces/gtk3/gtk3bindings/lazgtk3.pas |  60 ++++++++
 lcl/interfaces/gtk3/gtk3widgets.pas          |  27 +++-
 5 files changed, 246 insertions(+), 3 deletions(-)
 create mode 100755 components/opengl/glgtk3glxcontext.pas
 mode change 100644 => 100755 components/opengl/openglcontext.pas
 mode change 100644 => 100755 lcl/interfaces/gtk3/gtk3bindings/lazgdk3.pas
 mode change 100644 => 100755 lcl/interfaces/gtk3/gtk3bindings/lazgtk3.pas
 mode change 100644 => 100755 lcl/interfaces/gtk3/gtk3widgets.pas

diff --git a/components/opengl/glgtk3glxcontext.pas b/components/opengl/glgtk3glxcontext.pas
new file mode 100755
index 0000000000..5d064b99a6
--- /dev/null
+++ b/components/opengl/glgtk3glxcontext.pas
@@ -0,0 +1,139 @@
+unit GLGtk3GlxContext;
+
+{$mode objfpc}
+{$LinkLib GL}
+
+interface
+
+uses
+  Classes, SysUtils, ctypes, LCLProc, LCLType, X, XUtil, XLib, gl, glext,
+  InterfaceBase,
+  glx,
+  WSLCLClasses, LCLMessageGlue,
+  LMessages, glib2, gtk3int, LazGdk3, LazGtk3, gtk3widgets,
+  Controls;
+
+function LBackingScaleFactor(Handle: HWND): single;
+procedure LOpenGLViewport({%H-}Handle: HWND; Left, Top, Width, Height: integer);
+procedure LOpenGLSwapBuffers(Handle: HWND);
+function LOpenGLMakeCurrent(Handle: HWND): boolean;
+function LOpenGLReleaseContext({%H-}Handle: HWND): boolean;
+function LOpenGLCreateContext(AWinControl: TWinControl;
+             WSPrivate: TWSPrivateClass; SharedControl: TWinControl;
+             DoubleBuffered, RGBA, DebugContext: boolean;
+             const RedBits, GreenBits, BlueBits, MajorVersion, MinorVersion,
+             MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: Cardinal;
+             const AParams: TCreateParams): HWND;
+procedure LOpenGLDestroyContextInfo(AWinControl: TWinControl);
+
+implementation
+
+{$assertions on}
+
+procedure on_render(widget: PGtkWidget; context: gpointer{Pcairo_t}; data: TGtk3Widget); cdecl;
+begin
+  data.LCLObject.Perform(LM_PAINT, WParam(data), 0);
+end;
+
+function gtkglarea_size_allocateCB(Widget: PGtkWidget; Size: pGtkAllocation; Data: gPointer): GBoolean; cdecl;
+var
+  SizeMsg: TLMSize;
+  GtkWidth, GtkHeight: integer;
+  LCLControl: TWinControl;
+begin
+  Result := true;
+  LCLControl:=TWinControl(Data);
+  if LCLControl=nil then exit;
+
+  gtk_widget_get_size_request(Widget, @GtkWidth, @GtkHeight);
+
+  SizeMsg.Msg:=0;
+  FillChar(SizeMsg,SizeOf(SizeMsg),0);
+  with SizeMsg do
+  begin
+    Result := 0;
+    Msg := LM_SIZE;
+    SizeType := Size_SourceIsInterface;
+    Width := SmallInt(GtkWidth);
+    Height := SmallInt(GtkHeight);
+  end;
+  LCLControl.WindowProc(TLMessage(SizeMsg));
+end;
+
+function gtk_gl_area_get_error (area: PGtkGLArea): PGError; cdecl; external;
+
+function LBackingScaleFactor(Handle: HWND): single;
+var
+  glarea: TGtk3GLArea absolute Handle;
+begin
+  // todo(ryan): get the correct screen for the handle!
+  result := TGdkScreen.get_default^.get_monitor_scale_factor(0);
+end;
+
+procedure LOpenGLViewport(Handle: HWND; Left, Top, Width, Height: integer);
+var
+  scaleFactor: integer;
+begin
+  scaleFactor := RoundToInt(LBackingScaleFactor(Handle));
+  glViewport(Left,Top,Width*scaleFactor,Height*scaleFactor);
+end;
+
+procedure LOpenGLSwapBuffers(Handle: HWND);
+var
+  glarea: TGtk3GLArea absolute Handle;
+begin
+  if Handle=0 then exit;
+  glFlush();
+end;
+
+function LOpenGLMakeCurrent(Handle: HWND): boolean;
+var
+  glarea: TGtk3GLArea absolute Handle;
+begin
+  glarea.Widget^.realize;
+  PGtkGLArea(glarea.Widget)^.make_current;
+  Assert(gtk_gl_area_get_error(PGtkGLArea(glarea.Widget)) = nil, 'LOpenGLMakeCurrent failed');
+  result := true;
+end;
+
+function LOpenGLReleaseContext(Handle: HWND): boolean;
+var
+  glarea: TGtk3GLArea absolute Handle;
+begin
+  // todo(ryan): is it possible to make no context current?
+  result:=true;
+end;
+
+function LOpenGLCreateContext(AWinControl: TWinControl;
+  WSPrivate: TWSPrivateClass; SharedControl: TWinControl;
+  DoubleBuffered, RGBA, DebugContext: boolean;
+  const RedBits, GreenBits, BlueBits, MajorVersion, MinorVersion,
+  MultiSampling, AlphaBits, DepthBits, StencilBits, AUXBuffers: Cardinal;
+  const AParams: TCreateParams): HWND;
+var
+  NewWidget: TGtk3GLArea;
+  glarea: PGtkGLArea;
+begin
+  NewWidget := TGtk3GLArea.Create(AWinControl, AParams);
+  result := TLCLIntfHandle(NewWidget);
+  glarea := PGtkGLArea(NewWidget.Widget);
+
+  g_signal_connect(glarea, 'render', TGCallback(@on_render), NewWidget);
+  // todo(ryan): do we need this?
+  g_signal_connect_after(glarea, 'size-allocate', TGCallback(@gtkglarea_size_allocateCB), AWinControl);
+
+  glarea^.set_auto_render(false);
+  glarea^.set_required_version(MajorVersion, MinorVersion);
+  glarea^.set_has_depth_buffer(DepthBits > 0);
+  glarea^.set_has_alpha(AlphaBits > 0);
+  glarea^.set_has_stencil_buffer(StencilBits > 0);
+end;
+
+procedure LOpenGLDestroyContextInfo(AWinControl: TWinControl);
+begin
+  if not AWinControl.HandleAllocated then exit;
+  // nothing to do
+end;
+
+end.
+
diff --git a/components/opengl/openglcontext.pas b/components/opengl/openglcontext.pas
old mode 100644
new mode 100755
index d3e933ff21..09da999c74
--- a/components/opengl/openglcontext.pas
+++ b/components/opengl/openglcontext.pas
@@ -42,6 +42,16 @@ unit OpenGLContext;
     {$DEFINE OpenGLTargetDefined}
   {$ENDIF}
 {$ENDIF}
+{$IFDEF LCLGTK3}
+  {$IF defined(Linux) or defined(FreeBSD)}
+    {$DEFINE UseGtk3GLX}
+    {$DEFINE UsesModernGL}
+    {$DEFINE HasRGBA}
+    {$DEFINE HasRGBBits}
+    {$DEFINE HasDebugContext}
+    {$DEFINE OpenGLTargetDefined}
+  {$ENDIF}
+{$ENDIF}
 {$IFDEF LCLCarbon}
   {$DEFINE UseCarbonAGL}
   {$DEFINE HasRGBA}
@@ -90,6 +100,9 @@ uses
 {$IFDEF UseGtk2GLX}
   GLGtkGlxContext;
 {$ENDIF}
+{$IFDEF UseGtk3GLX}
+  GLGtk3GlxContext;
+{$ENDIF}
 {$IFDEF UseCarbonAGL}
   GLCarbonAGLContext;
 {$ENDIF}
diff --git a/lcl/interfaces/gtk3/gtk3bindings/lazgdk3.pas b/lcl/interfaces/gtk3/gtk3bindings/lazgdk3.pas
old mode 100644
new mode 100755
index c269a63bd6..d0a167e40c
--- a/lcl/interfaces/gtk3/gtk3bindings/lazgdk3.pas
+++ b/lcl/interfaces/gtk3/gtk3bindings/lazgdk3.pas
@@ -2946,6 +2946,7 @@ type
     function get_monitor_width_mm(monitor_num: gint): gint; cdecl; inline;
     procedure get_monitor_workarea(monitor_num: gint; dest: PGdkRectangle); cdecl; inline;
     function get_n_monitors: gint; cdecl; inline;
+    function get_monitor_scale_factor(monitor_num: gint): gint; cdecl; inline;
     function get_number: gint; cdecl; inline;
     function get_primary_monitor: gint; cdecl; inline;
     function get_resolution: gdouble; cdecl; inline;
@@ -4083,6 +4084,7 @@ function gdk_screen_get_primary_monitor(screen: PGdkScreen): gint; cdecl; extern
 function gdk_screen_get_resolution(screen: PGdkScreen): gdouble; cdecl; external;
 function gdk_screen_get_rgba_visual(screen: PGdkScreen): PGdkVisual; cdecl; external;
 function gdk_screen_get_root_window(screen: PGdkScreen): PGdkWindow; cdecl; external;
+function gdk_screen_get_monitor_scale_factor(screen: PGdkScreen; monitor_num: gint): gint; cdecl; external;
 function gdk_screen_get_setting(screen: PGdkScreen; name: Pgchar; value: PGValue): gboolean; cdecl; external;
 function gdk_screen_get_system_visual(screen: PGdkScreen): PGdkVisual; cdecl; external;
 function gdk_screen_get_toplevel_windows(screen: PGdkScreen): PGList; cdecl; external;
@@ -4165,6 +4167,7 @@ function gdk_window_get_visible_region(window: PGdkWindow): Pcairo_region_t; cde
 function gdk_window_get_visual(window: PGdkWindow): PGdkVisual; cdecl; external;
 function gdk_window_get_width(window: PGdkWindow): gint; cdecl; external;
 function gdk_window_get_window_type(window: PGdkWindow): TGdkWindowType; cdecl; external;
+function gdk_window_get_scale_factor(window: PGdkWindow): gint; cdecl; external;
 function gdk_window_has_native(window: PGdkWindow): gboolean; cdecl; external;
 function gdk_window_is_destroyed(window: PGdkWindow): gboolean; cdecl; external;
 function gdk_window_is_input_only(window: PGdkWindow): gboolean; cdecl; external;
@@ -4659,6 +4662,11 @@ begin
   Result := LazGdk3.gdk_screen_get_n_monitors(@self);
 end;
 
+function TGdkScreen.get_monitor_scale_factor(monitor_num: gint): gint; cdecl;
+begin
+   Result := LazGdk3.gdk_screen_get_monitor_scale_factor(@self, monitor_num);
+end;
+
 function TGdkScreen.get_number: gint; cdecl;
 begin
   Result := LazGdk3.gdk_screen_get_number(@self);
@@ -5984,4 +5992,4 @@ begin
   Result := LazGdk3.gdk_visual_get_visual_type(@self);
 end;
 
-end.
\ No newline at end of file
+end.
diff --git a/lcl/interfaces/gtk3/gtk3bindings/lazgtk3.pas b/lcl/interfaces/gtk3/gtk3bindings/lazgtk3.pas
old mode 100644
new mode 100755
index a5fde35ded..ec8b6ee295
--- a/lcl/interfaces/gtk3/gtk3bindings/lazgtk3.pas
+++ b/lcl/interfaces/gtk3/gtk3bindings/lazgtk3.pas
@@ -1747,6 +1747,18 @@ type
     function get_child: PGtkWidget; cdecl; inline;
   end;
 
+  PGtkGLArea = ^TGtkGLArea;
+  TGtkGLArea = object(TGtkWidget)
+    function new: PGtkWidget; cdecl; inline; static;
+    procedure set_auto_render(auto_render: gboolean); cdecl; inline;
+    procedure make_current; cdecl; inline;
+    procedure queue_render; cdecl; inline;
+    procedure set_has_depth_buffer(has_depth_buffer: gboolean); cdecl; inline;
+    procedure set_has_stencil_buffer(has_stencil_buffer: gboolean); cdecl; inline;
+    procedure set_has_alpha(has_alpha: gboolean); cdecl; inline;
+    procedure set_required_version(major: gint; minor: gint); cdecl; inline;
+  end;
+
   PPGtkWindowType = ^PGtkWindowType;
   PGtkWindowType = ^TGtkWindowType;
 
@@ -11791,6 +11803,14 @@ function gtk_grid_get_row_homogeneous(grid: PGtkGrid): gboolean; cdecl; external
 function gtk_grid_get_row_spacing(grid: PGtkGrid): guint; cdecl; external;
 function gtk_grid_get_type: TGType; cdecl; external;
 function gtk_grid_new: PGtkGrid; cdecl; external;
+function gtk_gl_area_new: PGtkWidget; cdecl; external;
+procedure gtk_gl_area_make_current(area: PGtkGLArea); cdecl; external;
+procedure gtk_gl_area_queue_render(area: PGtkGLArea); cdecl; external;
+procedure gtk_gl_area_set_auto_render(area: PGtkGLArea; auto_render: gboolean); cdecl; external;
+procedure gtk_gl_area_set_has_depth_buffer(area: PGtkGLArea; has_depth_buffer: gboolean); cdecl; external;
+procedure gtk_gl_area_set_has_stencil_buffer(area: PGtkGLArea; has_stencil_buffer: gboolean); cdecl; external;
+procedure gtk_gl_area_set_has_alpha(area: PGtkGLArea; has_alpha: gboolean); cdecl; external;
+procedure gtk_gl_area_set_required_version(area: PGtkGLArea; major: gint; minor: gint); cdecl; external;
 function gtk_handle_box_get_type: TGType; cdecl; external;
 function gtk_hbox_get_type: TGType; cdecl; external;
 function gtk_hbutton_box_get_type: TGType; cdecl; external;
@@ -16528,6 +16548,46 @@ begin
   LazGtk3.gtk_window_unstick(@self);
 end;
 
+function TGtkGLArea.new: PGtkWidget; cdecl;
+begin
+  Result := LazGtk3.gtk_gl_area_new();
+end;
+
+procedure TGtkGLArea.set_auto_render(auto_render: gboolean); cdecl;
+begin
+  LazGtk3.gtk_gl_area_set_auto_render(@self, auto_render);
+end;
+
+procedure TGtkGLArea.make_current; cdecl;
+begin
+  gtk_gl_area_make_current(@self);
+end;
+
+procedure TGtkGLArea.queue_render; cdecl;
+begin
+  gtk_gl_area_queue_render(@self);
+end;
+
+procedure TGtkGLArea.set_has_depth_buffer(has_depth_buffer: gboolean); cdecl;
+begin
+  gtk_gl_area_set_has_depth_buffer(@self, has_depth_buffer);
+end;
+
+procedure TGtkGLArea.set_has_stencil_buffer(has_stencil_buffer: gboolean); cdecl;
+begin
+  gtk_gl_area_set_has_stencil_buffer(@self, has_stencil_buffer);
+end;
+
+procedure TGtkGLArea.set_has_alpha(has_alpha: gboolean); cdecl;
+begin
+  gtk_gl_area_set_has_alpha(@self, has_alpha);
+end;
+
+procedure TGtkGLArea.set_required_version(major: gint; minor: gint); cdecl;
+begin
+  gtk_gl_area_set_required_version(@self, major, minor);
+end;
+
 function TGtkDialog.new: PGtkDialog; cdecl;
 begin
   Result := LazGtk3.gtk_dialog_new();
diff --git a/lcl/interfaces/gtk3/gtk3widgets.pas b/lcl/interfaces/gtk3/gtk3widgets.pas
old mode 100644
new mode 100755
index 89e055f9a3..b2a40ab881
--- a/lcl/interfaces/gtk3/gtk3widgets.pas
+++ b/lcl/interfaces/gtk3/gtk3widgets.pas
@@ -54,7 +54,7 @@ type
     wtGroupBox, wtCalendar, wtTrackBar, wtScrollBar,
     wtScrollingWin, wtListBox, wtListView, wtCheckListBox, wtMemo, wtTreeModel,
     wtCustomControl, wtScrollingWinControl,
-    wtWindow, wtDialog, wtHintWindow);
+    wtWindow, wtDialog, wtHintWindow, wtGLArea);
   TGtk3WidgetTypes = set of TGtk3WidgetType;
 
   { TGtk3Widget }
@@ -165,7 +165,7 @@ type
     procedure SetParent(AParent: TGtk3Widget; const ALeft, ATop: Integer); virtual;
     procedure Show; virtual;
     procedure ShowAll; virtual;
-    procedure Update(ARect: PRect);
+    procedure Update(ARect: PRect); virtual;
     property CairoContext: Pcairo_t read GetCairoContext;
     property Color: TColor read GetColor write SetColor;
     property Context: HDC read GetContext;
@@ -823,6 +823,16 @@ type
     constructor Create(const ACommonDialog: TCommonDialog); virtual; overload;
   end;
 
+  { TGtk3GLArea }
+  TGtk3GLArea = class(TGtk3Widget)
+  protected
+    function CreateWidget(const Params: TCreateParams): PGtkWidget; override;
+  public
+    procedure Update(ARect: PRect); override;
+  end;
+
+
+
 {main event filter for all widgets, also called from widgetset main eventfilter}
 function Gtk3WidgetEvent(widget: PGtkWidget; event: PGdkEvent; data: GPointer): gboolean; cdecl;
 
@@ -7189,6 +7199,19 @@ begin
   CommonDialog := ACommonDialog;
 end;
 
+{ TGtk3GLArea }
+
+procedure TGtk3GLArea.Update(ARect: PRect);
+begin
+  if IsWidgetOK then
+    PGtkGLArea(Widget)^.queue_render;
+end;
+
+function TGtk3GLArea.CreateWidget(const Params: TCreateParams): PGtkWidget;
+begin
+  FWidgetType := [wtWidget, wtGLArea];
+  Result := TGtkGLArea.new;
+end;
 
 end.
 
-- 
2.21.0 (Apple Git-122)

patch.diff (14,231 bytes)   

Chris Rorden

2019-12-16 16:20

reporter   ~0119887

Ryan
  This is excellent work. One note to developers: GTK3 requires OpenGL 3.3 core specification or later. The 'modern' core specification drops support for legacy OpenGL functions like glBegin/glEnd, glMatrix, and the fixed processing pipeline. Hence Ryan links to demos that use the core specification. The examples in the folder /Lazarus/Components/OpenGL/examples use legacy OpenGL and therefore do not work with GTK3.

For completeness, Lazarus OpenGL support is limited by the widgetset used:
 MacOS Carbon: Legacy OpenGL only (2.1 or earlier)
 MacOS Cocoa: A context can target OpenGL legacy (2.1 or earlier) or Modern OpenGL Core (3.2 and later), but must choose one of these, one can not mix and match modern and legacy features.
 GTK3: Modern OpenGL core specification only, one can not use legacy features.
 GTK2, QT5, Windows: OpenGL compatibility specification. Users can mix and match legacy and modern features. By aware that performance of legacy features is often slower and less efficient in terms of GPU resources (memory) than using modern methods.

Anton Kavalenka

2019-12-18 20:50

reporter   ~0119923

in r42412 forgotten GLGtk3GlxContext

Juha Manninen

2019-12-18 21:37

developer   ~0119927

I applied the patch in r62412 after just minimal testing.
Thanks!

About Legacy OpenGL versus Modern OpenGL, the modern version sounds better.
How much work would be to port the Lazarus OpenGL examples for the modern OpenGL? Any volunteers?

Ryan Joseph

2019-12-18 21:50

reporter   ~0119928

Thanks. It's not 100% complete but I will continue to submit patches. We're also looking into a way to include multisampling as this is not supported natively by the context as currently does not exist.

Lots of work to port the examples actually. :(

Juha Manninen

2019-12-18 22:09

developer   ~0119931

> Lots of work to port the examples actually. :(

Oh, OK.
Should I keep this open for the multisampling stuff etc. patches?

Ryan Joseph

2019-12-18 22:21

reporter   ~0119933

I just assumed I would open new issues for new features and take them piece by piece. The only other thing we have planned for now is multisampling.

Juha Manninen

2019-12-18 23:15

developer   ~0119937

Resolving this one. Thanks.

Anton Kavalenka

2019-12-19 07:14

reporter   ~0119938

/projects/lazarus/components/opengl/openglcontext.pas(104,3) Fatal: Cannot find GLGtk3GlxContext used by OpenGLContext of package LazOpenGLContext.

There is NO GLGtk3GlxContext.pas in commit 62412, it is just forgotten

Juha Manninen

2019-12-19 08:39

developer   ~0119939

Oops, sorry.
I added the file. Please test.

Chris Rorden

2019-12-30 19:07

reporter   ~0120159

Juha:
 wrt "About Legacy OpenGL versus Modern OpenGL, the modern version sounds better. How much work would be to port the Lazarus OpenGL examples for the modern OpenGL? Any volunteers?" Please find the attached minimal demo. I would also be happy for Lazarus to include the demos at
  https://github.com/neurolabusc/OpenGLCoreTutorials
However, those include additional assets (e.g. images) so while they demonstrate advanced features they require more disk space.

The only issue is that on MacOS you must use glcorearb instead of gl/glext to enable modern OpenGL. I really think that the file glcorearb.pas should be included with FPC, or if it is included with Lazarus it should be in the /components/OpenGL folder rather than a /components/OpenGL/CoreDemo folder. However, there has been no movement on this for the last 5 years... I leave it to your judgement how to include this with Lazarus. I do think this is important: including modern OpenGL with Lazarus will help developers explore these features and use the modern widget sets.
  https://bugs.freepascal.org/view.php?id=31301
opengl_core_demo.zip (36,629 bytes)

Issue History

Date Modified Username Field Change
2019-12-16 16:09 Ryan Joseph New Issue
2019-12-16 16:09 Ryan Joseph File Added: patch.diff
2019-12-16 16:20 Chris Rorden Note Added: 0119887
2019-12-18 12:34 Juha Manninen Assigned To => Juha Manninen
2019-12-18 12:34 Juha Manninen Status new => assigned
2019-12-18 20:50 Anton Kavalenka Note Added: 0119923
2019-12-18 21:37 Juha Manninen Note Added: 0119927
2019-12-18 21:38 Juha Manninen Fixed in Revision => r62412
2019-12-18 21:38 Juha Manninen LazTarget => -
2019-12-18 21:38 Juha Manninen Widgetset GTK 3 => GTK 3
2019-12-18 21:50 Ryan Joseph Note Added: 0119928
2019-12-18 22:09 Juha Manninen Note Added: 0119931
2019-12-18 22:21 Ryan Joseph Note Added: 0119933
2019-12-18 23:15 Juha Manninen Status assigned => resolved
2019-12-18 23:15 Juha Manninen Resolution open => fixed
2019-12-18 23:15 Juha Manninen Widgetset GTK 3 => GTK 3
2019-12-18 23:15 Juha Manninen Note Added: 0119937
2019-12-19 07:14 Anton Kavalenka Note Added: 0119938
2019-12-19 07:30 Juha Manninen Status resolved => assigned
2019-12-19 07:30 Juha Manninen Resolution fixed => reopened
2019-12-19 08:39 Juha Manninen Status assigned => resolved
2019-12-19 08:39 Juha Manninen Fixed in Revision r62412 => r62412, r62417
2019-12-19 08:39 Juha Manninen Widgetset GTK 3 => GTK 3
2019-12-19 08:39 Juha Manninen Note Added: 0119939
2019-12-30 19:07 Chris Rorden File Added: opengl_core_demo.zip
2019-12-30 19:07 Chris Rorden Note Added: 0120159