Best HiDPI Picture Drawing Practises

I was just doing a little research into the fasted way to render a custom generated picture (not an embedded resource) into a HiDPI enabled project to share over OSX and Windows. My test was to create 2 pictures at 200x200, one generated using the window.BitmapForCaching(200,200) function and one generated with a 400x400 pixel image to be scaled at render.

Code for generating my two images are

[code]p1 = BitmapForCaching(200,200)

p2 = new Picture(400,400,32)

Where the source picture is 400x400 in size.

In a canvas’s paint event i did the following

[code]dim t as Integer = Ticks
dim d1,d2 as integer
dim i as integer
for i = 1 to 20000
next i

d1 = Ticks - t

t = Ticks

for i = 1 to 20000
next i

d2 = Ticks - t

g.ForeColor = &cFFFFFF
g.DrawString(str(d1) + " " + str(d2), 5, 25)

This basically draws the pictures on top of each other 20000 times and looks how long it takes in ticks, rendering this result to the screen.

The output results are rather confusing to me.

OSX 64 Non Rentina screen
d1 = 23
d2 = 34

OSX 64 Retina Screen
d1 = 900 (40x slower)
d2 = 416 (12.5x slower)

Win 32 Surface Pro 4 HiDPI
d1 = 577
d2 = 562

The confusing part is the huge difference in time to render on a retina screen, 4 times dpi, but with 40x slower rendering.
The other odd part is that on a retina screen it is slower to use a pre cached bitmap.

I tested this on Xojo 2018r1.1 as well as 2017r2.1. (Mac is a iMac (Retina 5K, 27-inch, Late 2015))

Does anyone have any idea what is going on here and how i can get better rendering speeds on a retina screen.
I would of expected 4 -5 times slower rendering on retina but factors of 40 seems a little odd to me.

write externally in picture on canvas size
p= BitmapForCaching(canvas1.Width,canvas1.height)
and draw in paint only the background image p
the time difference is elsewhere


under hi-DPI OSX 64 Retina Screen
I have in Paint 101/103 and externally 57/67 ticks

OSX 64 Non Rentina screen
without me at 35/44 externally 20/30 ticks

Writing to an external buffered image would be faster, but I still need to get the content to the screen. Think of dragging an image around in a screen, double buffering would not help there. I am using the invalidate region logic to gain huge speed improvements.

What are the specs of your machine to get you 10x faster rendering ?

Another test I tried was scaling up the image to fill the canvas using a timer at period of 1, to see how many times a second I could draw it. It was around 1200fps when same size and down to 10fps at full screen (5k). On the non retina it was around 100fps (1080p) which sort of makes sense.

I tried also rending using core graphics objects from MBS. With exact same speeds as graphics rendering.

One thing I did notice was a once off lag on first render using the scaling method to cache the image somewhere. The cache xojo image didn’t not Seem to have this lag, but rendered slower.

All I want to get out of these tests is the best way to store my internally generate images for fastest rendering to screen. Either bitmapforcaching or double the sized image scaled on render. So far it appears that scaling on render is best. But your results show otherwise.

One area I know I will have problems is support for zoom. And panning into a image. Eg I have a 4k sized image and I am zooming into it and panning it arround, updating it live to the screen. At full screen I would get around 10fps. I wonder if I can somehow render lower res version while panning and sharpen it up after a delay once panning has stopped.

hello Graham,
i have i Mac 5K with 32 GB Ram
in my image editing in XOJO I need up to 8000x 5300 pixels
which I can edit directly Zoom factor 0 to 600%
I use 4 image layers and 99 pixmapshapes
All image data are drawn externally at image level (0)
and scaled under Display.paint are also displayed texts and
further pixmapshape, lines, polygons and more scaled to the image size
under Mousemove, Mousedrag is called permanently with Display.invalidat
Timekeeping normally gives values <2 ticks
until full utilization
10 ticks that are still to endure
and all without any system calls
only with normal XOJO functions

XOJO is a good programming tool

How are you rendering to the display at fast enough fps for panning?
I can only get 10fps at full screen rendering a 2000x2000 picture to a canvas in the paint event

I have a couple of suggestions, mainly for macOS.

  1. Make sure you’re using canvas.invalidate and not canvas.refresh.
  2. Modify the graphics context to reduce the quality of the scaled image, the declares are in this forum, you can also switch off anti-aliasing.
  3. When panning, only update the canvas when the location has actually changed.
  4. Keep all math outside of the paint event, so do the math when needed and store the results in variables. You need the Paint event to do as little as possible.
  5. When you’re zoomed into the picture, make sure you use the “SourceRect” rather than drawing the picture bigger than the window.
  6. Use a Core Animation layer (CALayer).
  7. Use an ImageWell (with some simply declares you can hide the border and make the image scale to fit), then move & resize the imageWell.
  8. Use Core Image & Metal, this one will get you the best performance, however it’s also the hardest to implement. I can reach 240 fps on a 2012 rMBP using Core Image and OpenGL, however OpenGL is now deprecated, so we have to use Metal. Thankfully Metal 2 is actually faster than the OpenGL that Apple used (for 8 years).

hello Graham,

Hi-DPI Test

Dim t As Integer = Ticks
Dim d1,d2 As Double
Dim i As Integer

t =Ticks
rem public Variable extern by Slider1
zoom = slider1.value/slider1.maximum // 0-600
fx= gw / canvas1.width zoom
fy= gh / canvas1.Height
Rem p(0) = merged image to display
g.drawpicture p(0),0,0,canvas1.Width,canvas1.height,scrollbar2.value,scrollbar1.value,p(0).Widthzoom,p(0).heightzoom

d1 = Ticks - t

rem open
rem p(4) as picture - global Properties / p1-p2-px PixmapShape global

Dim r As New random
Dim x,y,i,br,pos(2) As Integer
Dim buffer As picture
gw = Val(breite.Text)
If gw < 100 Then gw=100
If gh< 100 Then gh=100

zoom = slider1.value/slider1.Maximum // als regler
fx= gw / canvas1.width zoom
fy= gh / canvas1.Height

For i = 0 To 4
p(i)= New Picture(gw,gh,32)

// 100 Oval Plane 1
For i = 0 To 100
p(1).Graphics.forecolor = RGB(r.InRange(30,200),r.InRange(30,200),r.InRange(30,200))
// 100 Rect Plane 2
buffer = p(2).mask
buffer.Graphics.ForeColor = HSV(0,0,0)

For i = 0 To 100
p(2).Graphics.PenWidth = br
p(2).Graphics.penheight = br
p(2).Graphics.forecolor = RGB(r.InRange(30,200),r.InRange(30,200),r.InRange(30,200))
buffer.Graphics.PenWidth = br
buffer.Graphics.penheight = br
// Big Oval Plane 3
buffer = p(3).mask
p(3).Graphics.PenWidth = 100
p(3).Graphics.penheight = 100
p(3).Graphics.forecolor = RGB(r.InRange(30,200),r.InRange(30,200),r.InRange(30,200))
buffer.Graphics.ForeColor = HSV(0,0,0)
buffer.Graphics.PenWidth = 100
buffer.Graphics.penheight = 100
// Text Plane 4
buffer = p(4).mask
p(4).Graphics.forecolor = RGB(r.InRange(30,200),r.InRange(30,200),r.InRange(30,200))
p(4).Graphics.TextSize = gw/10
p(4).Graphics.drawstring("Hello ",gw/3,gh/3)
buffer.Graphics.forecolor = RGB(0,0,0)
buffer.Graphics.TextSize = gw/10
buffer.Graphics.drawstring("Hello ",gw/3,gh/3)

// View
p(0).Graphics.drawpicture p(1),0,0
p(0).Graphics.drawpicture p(2),0,0
p(0).Graphics.drawpicture p(3),0,0
p(0).Graphics.drawpicture p(4),0,0

ScrollBar1.Maximum = p(0).height-canvas1.height
ScrollBar2.Maximum = p(0).width-canvas1.Width

I was able to get the fast rendering I needed using

dim c as CGContextMBS = GetCurrentCGContextMBS() c.InterpolationQuality = CGContextMBS.kCGInterpolationNone

Then generating a smaller image and scaling that to full size to drop the quality.

Windows rendering is nice and fast.