OpenGLSurface and HiDPI

On Mac with HiDPI ON OpenGLSurface stays at 1 pixel = 1 point.

A bit of research finds the NSView property wantsBestResolutionOpenGLSurface needs to be set True.
However OpenGLSurface.Handle returns an NSOpenGLContext and I have to call ‘view’ on that to get the NSView of the OpenGLSurface.

That’s working OK but I’m uncertain about this from the doc for NSOpenGLContext.view

Is ‘full-screen mode’ a possibility with OpenGLSurface? I’ve made the Window fullscreen and still get a valid view so is this ‘full-screen mode’ something else?

The code is run in an Open event but initially the OpenGLSurface is still in Low-Res mode. It’s not until resizing that the HiDPI res takes effect. Calling update on the NSOpenGLContext fixes that but the conversations I’ve come across don’t mention having to call update. Is this the correct thing to do?

[code]declare function getView lib “AppKit” selector “view” (id As Ptr) As Ptr
declare sub setBestRes lib “AppKit” selector “setWantsBestResolutionOpenGLSurface:” (id As Ptr, b As boolean)
declare sub update lib “AppKit” selector “update” (id As Ptr)

dim oglContext As Ptr = Ptr(OpenGLSurface1.Handle)
dim view As Ptr = getView(oglContext)
if view <> nil then
setBestRes(view, true)

Also, what’s the situation on Windows or Linux? I don’t have those systems to test but would like to know of any complications there.

This is how I got the NSViewID from the OpenGLSurface.

Function NSViewID() As Ptr return ptr( rectControl( me ).handle ) End Function
Also when working with Retina and OpenGL, it uses pixels not points, so drawing may need to updated to match, along with handling of mouse location and such.

Apple recommend the use of the geometry conversion functions as opposed to using the scaling factor.

Dim p as NSSize = convertSizeToBacking( ptr( me.handle ), NSMakeSize( x, y ) )

I don’t know if it’s the correct thing to do, but it’s what I also had to do to get it work correctly.

Sub NSOpenGLContextUpdate() #if TargetCocoa then declare sub doThing lib AppKit selector "update" ( NSOpenGLContext as ptr ) doThing( NSOpenGLContext ) #endif End Sub

Ah, so Xojo is shadowing properties :stuck_out_tongue:

Thanks Sam, I don’t know how I’dve come across that. And I’ll start using all the conversion functions, found why its preferred and it simplifies some things.

Now what about Windows. Is a special call necessary or is it hi-res out of the box? I can make a simple experiment if someone would like to test.


I use Windows normally, but I don’t have any HiDPI. But if you prepare a test I can try to find one.

I don’t recall how I found it either.

Yeah sorry, I can’t help with that…

This should read “Also when working with OpenGL, it uses pixels not points”, it’s not just when in Retina. Although when not in Retina one pixel = one point.

Thanks Ramon, that’s a big help.

Here’s the project.

There’s 2 scaling options. If the Checkbox ‘scale by scalefactor’ is ON then the transform is scaled by the HiDPI ScaleFactor of Xojo. If it draws correctly with this ON then the OpenGLSurface is using the full HiDPI resolution.

The other scaling option is a TextField where you can type in a manual amount to scale. This extra scaling is always applied, even when ScaleFactor is on. The reason for this is the Windows option in Control Panel > Appearance And Personalization > Display > Make Text And Other Items Larger Or Smaller. If this is set to something other than 100% than I’m pretty sure it modifies the pixel size of the OpenGLSurface but I’m not sure if it’s independent from ScaleFactor or not. So if the Control Panel setting is 125% you might have to enter 1.25 in the TextField to draw correctly.
Here’s where I learned of that Windows option

There’s also a draw lines CheckBox which overlays lines that should be 1 pixel wide if the OpenGLSurface has full HiDPI resolution. A Canvas above is drawing 1 pixel wide lines for comparison.

If it’s not drawing correctly (ie without full HiDPI resolution) then it might look something like this. Only 1 quadrant block is visible and the lines are too thick.

If It’s drawing correctly it looks like this. The 4 quadrants fill the area and the lines are as thin as the Canvas above.

You might have to open these pictures in another tab to see them at full size. I’ve since added a label reporting the OpenGLSurface.Width, ScaleFactor, extra scale and resulting width that was used to draw.

Thanks again, I hope this experiment is enough to deduce what’s going on :slight_smile:

Yeah, it’s unfortunate and why the ContextHandle property was added and the Handle method deprecated.

I think all you need to do is modify the viewport if you want to have 1 unit = 1 pixel.

Good to know. ContextHandle isn’t in the docs yet (and context-clicking it lists “Help for Integer”) but it does autocomplete :slight_smile:

By just ScaleFactor? It’s sounding like Windows OpenGL is always full-res and that that Control Panel setting is the only dpi scaling which comes through in ScaleFactor. This’ll make Windows easy to support.

Thanks for clarifying things Joe, I’ll write up a summary of results soon.

Thanks again to Sam and Joe for the answer. This is the code I came up with which does cache the scale since I don’t want to run the declare every frame plus the scale value is needed in places without the necessary references. It’s all in method HiDPIupdate which is called from a OpenGLSurface subclasses Open and should be called from Window.ScaleFactorChanged.

I want to support Xojos before CGFloat so there’s an extra Target32Bit switch.

[code]Sub HiDPIupdate()

#if XojoVersion >= 2016 then
if App.SupportsHiDPI then

  HiDPIisOn = true
  #if TargetMacOS then
    dim view As Ptr = Ptr( RectControl(self).Handle )
    if not HiDPIMacBestResIsOn then
      declare sub setWantsBestRes lib "AppKit" selector "setWantsBestResolutionOpenGLSurface:" _
          (id As Ptr, b As boolean)
      declare sub update lib "AppKit" selector "update" (id As Ptr)
      setWantsBestRes( view, HiDPIisOn )
      update( Ptr(self.ContextHandle) )
      HiDPIMacBestResIsOn = true
    #if Target32Bit then
      declare function convertSize lib "AppKit" selector "convertSizeToBacking:" _
          (id As Ptr, p As NSSize32) As NSSize32
      dim sizein As NSSize32
      sizein.width = 1
      dim sizeout As NSSize32 = convertSize( view, sizein )
      HiDPIScale = sizeout.width
      declare function convertSize lib "AppKit" selector "convertSizeToBacking:" _
          (id As Ptr, p As NSSize64) As NSSize64
      dim sizein As NSSize64
      sizein.width = 1
      dim sizeout As NSSize64 = convertSize( view, sizein )
      HiDPIScale = sizeout.width
  #else //assume Windows and Linux are always full-res
    HiDPIScale = self.Window.ScaleFactor


End Sub[/code]

Then in the subclasses Render event is

if HiDPIisOn then OpenGL.glViewport( 0, 0, Width*HiDPIScale, Height*HiDPIScale ) else OpenGL.glViewport( 0, 0, Width, Height ) end

The projection matrix doesn’t have to be updated but a handful of things need need to be scaled too, like line width, point size, line stipple, scissor box, maybe more.

Also, maybe update(context) should be called for any ScaleFactorChanged, will need to experiment more :slight_smile: