Mac OS drawing lag

61 views
Skip to first unread message

Arnis Gustins

unread,
Nov 29, 2022, 2:46:11 AM11/29/22
to fltk.general
Hi.

I have this problem on Mac OS 12 and 13 where my drawing functions are very "sluggish". For example dragging feels very rough. For some time I though that I just wrote the thing in a very inefficient way and tried to optimize it but without success. The code simply listens on drag events and calls window to redraw. And in the draw function there is just fl_rect function call.

Then very interesting thing happened. I just moved the window to the secondary screen (external monitor) and everything became extremely smooth. Moving it back to main laptop screen makes it lag again.

I can create a demo project that illustrates this problem later if necessary but I don't think there is need for that since it's super basic. Currently I am using version 1.3.8.

Have anybody experiences this problem before? I feel like this could be solved very easily but I just can't find the reason. 

Thanks.

Matthias Melcher

unread,
Nov 29, 2022, 2:56:42 AM11/29/22
to fltk.general
Please create a demo project that illustrates this problem. I don't have that issue here, so I wonder what's happening. Do you call "draw()" or "redraw()" on your widget inside the drag event?

Ian MacArthur

unread,
Nov 29, 2022, 4:36:31 AM11/29/22
to fltk.general
On Tuesday, 29 November 2022 at 07:46:11 UTC Arnis wrote:

I have this problem on Mac OS 12 and 13 where my drawing functions are very "sluggish". For example dragging feels very rough. For some time I though that I just wrote the thing in a very inefficient way and tried to optimize it but without success. The code simply listens on drag events and calls window to redraw. And in the draw function there is just fl_rect function call.

So... I don't have an answer for this, but some observations (that probably are not relevant!)

A while back I had a similar-sounding issue on macOS (well, it was OSX then!) but in my case the drawing was GL based. On the main monitor the GL context was sync-locked to the monitor and so only redrew each frame at 60Hz. And it was trying to draw *a lot* of frames (way too many...)
On the external monitor the GL context wasn't sync-locked, so it was running free, and could redraw all the same frames in the blink of an eye.
Which sounds like a similar problem - but may be entirely unrelated!

On a related note, triggering a lot of redraws from within the handle method is unlikely to be "efficient" anyway, I suspect. But, as Matt says we might need to see a worked example of what you are actually doing here, to better illuminate the issues.

Matthias Melcher

unread,
Nov 29, 2022, 6:23:57 AM11/29/22
to fltk.general
tl;dr call "redraw()' in event handling and callback routines. Never call "draw()' directly. FLTK will call "draw()" for you when the time is right.

macOS can send a *lot* of DRAG and MOVE events. More than 60 events per second. It makes no sense to actually draw a widget every time an event occurs, and if the screen is sync locked, it generates a huge lag, because while the drawing code is waiting for the screen "flyback", more DRAG events are piling up. Then taking only the top DRAG event and waiting for the screen once more will increase the pile even more.

All MOVE and DRAG event, actually *all* events should be handled quickly. So for a DRAG event, all you do is fetch the coordinates and save them in the widget, and call "redraw()" on the widget, so the widget knows how to draw itself without referencing any events. "redraw()" is very fast. It tells FLTK, that some area wants to be redrawn, but doesn't draw anything at all. It pretty much just sets a flag.

So it doesn't really matter how many DRAG events you have piled up, "redraw()" only sets a flag over and over.

Now once the event queue is empty (simplified), FLTK checks which widgets want to be redrawn, and draws them all at once. Even though you may have handled 20 DRAG events during a single screen refresh, only the most recent event has an effect that will be drawn. That way, you can handle thousands of events per second without ever waiting for the screen, and without getting a lag.

Just for completeness, if you want to animate something, you do exactly the same. In the timer, all you do is call "redraw()". The widget must be able to draw itself based on the current time, not based on the timer event, because you never know how the slow the user's machine is. Events always run completely asynchronously from screen drawing. 

Arnis Gustins

unread,
Nov 29, 2022, 7:22:55 AM11/29/22
to fltk.general
Thank you for the responses.
I am using "redraw".

Here is an example app:
#include <FL/Fl.H>
#include <FL/fl_draw.H>

class MainWindow: public Fl_Window {
  public:
    MainWindow(int width, int height, const char* title) : Fl_Window(width, height, title) {};
    ~MainWindow() {};

  private:
    int rectX = 0;
    int rectY = 0;
    int rectW = 1;
    int rectH = 1;

  protected:
    void draw() {
      // Clean screen
      fl_rectf(0, 0, this->w(), this->h(), FL_BLACK);
      // Draw box
      fl_rectf(this->x() + this->rectX, this->y() + this->rectY, this->rectW, this->rectH, FL_WHITE);
    }

    int handle(int event) {
      int x, y;

      switch (event) {
        case FL_PUSH:
          x = Fl::event_x() - this->x();
          y = Fl::event_y() - this->y();

          this->rectX = x;
          this->rectY = y;
          this->rectW = 1;
          this->rectH = 1;
          this->redraw();
          return 1;

        case FL_DRAG:
          x = Fl::event_x() - this->x();
          y = Fl::event_y() - this->y();

          this->rectW = x - this->rectX;
          this->rectH = y - this->rectY;
          this->redraw();
          return 1;

        default:
            break;
      }

      return 0;
    }
};


int main(int argc, char **argv) {
  Fl::scheme("gtk+");

  // Views
  MainWindow *mainWindow = new MainWindow(Fl::w(), Fl::h(), "Test");
  mainWindow->end();
  mainWindow->show(argc, argv);
  mainWindow->make_current();

  return Fl::run();
}

Ian MacArthur

unread,
Nov 29, 2022, 8:40:22 AM11/29/22
to fltk.general
On Tuesday, 29 November 2022 at 12:22:55 UTC Arnis wrote:
Thank you for the responses.
I am using "redraw".

Here is an example app:

Hmm, OK.
Looks straightforward enough; can't see any obvious reason that would not Just Work.
I don't have a Mac here, but FWIW it seems fine under Win10.

A few thoughts, but I do not think they are going to make much difference:

1. I'd derive from Fl_Double_Window rather than Fl_Window - that will smooth things and reduce flickering on non-composited displays; though unlikely to make much difference on macOS, which does have a composited display...

2. Always call the base class handle from a derived class, particularly if you do not use the event yourself, to make sure the event loop is propagated fully. Particularly with container widgets like group or window, this can make a difference. 

3. Not sure what the call to make_current(); is meant to be doing? It should have no effect there.

Here's the code tweaked as I suggest, but I doubt it will make much difference!


#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>

class MainWindow: public Fl_Double_Window {
  public:
    MainWindow(int width, int height, const char* title) : Fl_Double_Window(width, height, title) {};
      return Fl_Double_Window::handle(event);

    }
};


int main(int argc, char **argv) {
  Fl::scheme("gtk+");

  // Views
  MainWindow *mainWindow = new MainWindow(Fl::w(), Fl::h(), "Test");
  mainWindow->end();
  mainWindow->show(argc, argv);
  // mainWindow->make_current();

  return Fl::run();
}

// end of file //




Matthias Melcher

unread,
Nov 29, 2022, 1:53:08 PM11/29/22
to fltk.general
I did a little performance analysis on this. It's not representative because I use an M1 laptop, but at least with Monterey 12.6, I see a little bit of this.

I can see how a fullscreen window has quite some impact on rendering times on my Retina display. I don't have the effect that using the secondary display is slower, but I was surprised to see that I get a frame rate of only 16fps on either display. Drawing a fullscreen rectangle takes about 1ms, so that's not the issue. But diving deeper, copying the back buffer to the front buffer (macOS always has builtin double buffering) takes most of the time. 

In the comments I saw that Manolo has added some code that special-handles `Fl_Window::make_current()`, so it is definitely a bad idea to call that anywhere. But even after deleting that, the UI is still at 16fps. Maybe Manolo has an idea? Maybe removing make_current() is a good-enough fix?

Arnis Gustins

unread,
Nov 29, 2022, 2:45:41 PM11/29/22
to fltk.general
It stated in the docs than OSX the Fl_Window and Fl_Double_Window is the same thing.

But still, tried all of the above and the issue remains.

Also tried running the same binary on other Macbook Pro. Same result.

Let me know what kind of debug information I can gather to help you understand this better.

Not sure if it's relevant but I am building using CMake. And this is how it looks like.
include(FetchContent)

FetchContent_Declare(FLTK
  GIT_REPOSITORY   https://github.com/fltk/fltk.git
  GIT_TAG          release-1.3.8
)
FetchContent_MakeAvailable(FLTK)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

add_executable(test
  main.cpp
)

target_include_directories(test PRIVATE ${fltk_SOURCE_DIR} ${fltk_BINARY_DIR})
target_link_libraries(test PRIVATE "-framework cocoa")
target_link_libraries(test PRIVATE
  fltk
)

asd


Ian MacArthur

unread,
Nov 30, 2022, 5:40:16 AM11/30/22
to fltk.general
On Tuesday, 29 November 2022 at 18:53:08 UTC Matthias Melcher wrote:
I can see how a fullscreen window has quite some impact on rendering times on my Retina display. I don't have the effect that using the secondary display is slower,

Just "for the record", in the GL drawing case I told about earlier (and which I think we have established is *not* relevant here...) the secondary display was notably FASTER, not slower, than the "internal" display. (This was on al older iMac model.)
Also, neither display was "retina" spec.

Ian MacArthur

unread,
Nov 30, 2022, 5:44:04 AM11/30/22
to fltk.general
On Tuesday, 29 November 2022 at 19:45:41 UTC Arnis wrote:
It stated in the docs than OSX the Fl_Window and Fl_Double_Window is the same thing.

Indeed; which is why I suggest to you to always use Fl_Double_Window on macOS- it (should) make no difference at all on macOS, but when you port your code to another platform it will make a difference, so if you use the Fl_Double_Window here, then your code will behave "the same" when ported to another host that does not have composited window rendering by default.

Reply all
Reply to author
Forward
0 new messages