SDL icon indicating copy to clipboard operation
SDL copied to clipboard

[MacOS] Resize flicker

Open filipkrw opened this issue 11 months ago • 7 comments

Hi, I'm experiencing a flicker when resizing the window on MacOS. See:

https://github.com/user-attachments/assets/d833ce30-b4ab-46b1-9f84-8ea431357f05

It looks as if SDL3 can't keep up with rendering frames on time, but the program is dead simple -- a single rectangle. It also doesn't happen on Windows.

Source code:

#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;

static int window_width = 640;
static int window_height = 480;

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    SDL_SetAppMetadata("Example Renderer Rectangles", "1.0", "com.example.renderer-rectangles");

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_CreateWindowAndRenderer("SDL3", window_width, window_height, SDL_WINDOW_RESIZABLE, &window, &renderer))
    {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    if (event->type == SDL_EVENT_QUIT)
    {
        return SDL_APP_SUCCESS;
    }
    else if (event->type == SDL_EVENT_WINDOW_RESIZED)
    {
        window_width = event->window.data1;
        window_height = event->window.data2;
    }
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appstate)
{
    SDL_SetWindowMinimumSize(window, 320, 240);

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);

    SDL_FRect rounded_rect = {
        .x = 30.0f,
        .y = 30.0f,
        .w = window_width - 60.0f,
        .h = window_height - 60.0f};
    SDL_SetRenderDrawColor(renderer, 180, 180, 180, SDL_ALPHA_OPAQUE);
    SDL_RenderFillRect(renderer, &rounded_rect);

    SDL_RenderPresent(renderer);

    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
}

And build config:

-- Checking for module 'libusb-1.0>=1.0.16'
--   No package 'libusb-1.0' found
-- Could NOT find LibUSB (missing: LibUSB_LIBRARY LibUSB_INCLUDE_PATH) (found version "LibUSB_VERSION-NOTFOUND")
-- 
-- SDL3 was configured with the following options:
-- 
-- Platform: Darwin-24.0.0
-- 64-bit:   TRUE
-- Compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Revision: SDL3-3.2.1-release-3.2.0-30-g17c4bdd75
-- Vendor:   
-- 
-- Subsystems:
--   Audio:    ON
--   Video:    ON
--   GPU:      ON
--   Render:   ON
--   Camera:   ON
--   Joystick: ON
--   Haptic:   ON
--   Hidapi:   ON
--   Power:    ON
--   Sensor:   ON
--   Dialog:   ON
-- 
-- Options:
--   SDL_ALSA                    (Wanted: OFF): OFF
--   SDL_ALSA_SHARED             (Wanted: OFF): OFF
--   SDL_ALTIVEC                 (Wanted: OFF): OFF
--   SDL_ARMNEON                 (Wanted: ON): ON
--   SDL_ASAN                    (Wanted: OFF): OFF
--   SDL_ASSEMBLY                (Wanted: ON): ON
--   SDL_ASSERTIONS              (Wanted: auto): auto
--   SDL_AVX                     (Wanted: OFF): OFF
--   SDL_AVX2                    (Wanted: OFF): OFF
--   SDL_AVX512F                 (Wanted: OFF): OFF
--   SDL_BACKGROUNDING_SIGNAL    (Wanted: OFF): OFF
--   SDL_CCACHE                  (Wanted: OFF): OFF
--   SDL_CLANG_TIDY              (Wanted: OFF): OFF
--   SDL_CLOCK_GETTIME           (Wanted: OFF): OFF
--   SDL_COCOA                   (Wanted: ON): ON
--   SDL_DBUS                    (Wanted: OFF): OFF
--   SDL_DIRECTX                 (Wanted: OFF): OFF
--   SDL_DISKAUDIO               (Wanted: ON): ON
--   SDL_DUMMYAUDIO              (Wanted: ON): ON
--   SDL_DUMMYCAMERA             (Wanted: ON): ON
--   SDL_DUMMYVIDEO              (Wanted: ON): ON
--   SDL_EXAMPLES                (Wanted: OFF): OFF
--   SDL_EXAMPLES_LINK_SHARED    (Wanted: ON): OFF
--   SDL_FOREGROUNDING_SIGNAL    (Wanted: OFF): OFF
--   SDL_GCC_ATOMICS             (Wanted: ON): ON
--   SDL_GPU_DXVK                (Wanted: OFF): OFF
--   SDL_HIDAPI                  (Wanted: ON): ON
--   SDL_HIDAPI_JOYSTICK         (Wanted: ON): ON
--   SDL_HIDAPI_LIBUSB           (Wanted: ON): OFF
--   SDL_HIDAPI_LIBUSB_SHARED    (Wanted: ON): OFF
--   SDL_IBUS                    (Wanted: OFF): OFF
--   SDL_INSTALL                 (Wanted: OFF): OFF
--   SDL_INSTALL_TESTS           (Wanted: OFF): OFF
--   SDL_JACK                    (Wanted: OFF): OFF
--   SDL_JACK_SHARED             (Wanted: OFF): OFF
--   SDL_KMSDRM                  (Wanted: OFF): OFF
--   SDL_KMSDRM_SHARED           (Wanted: OFF): OFF
--   SDL_LASX                    (Wanted: OFF): OFF
--   SDL_LIBC                    (Wanted: ON): ON
--   SDL_LIBICONV                (Wanted: OFF): OFF
--   SDL_LIBUDEV                 (Wanted: ON): OFF
--   SDL_LIBURING                (Wanted: OFF): OFF
--   SDL_LSX                     (Wanted: OFF): OFF
--   SDL_METAL                   (Wanted: ON): ON
--   SDL_MMX                     (Wanted: OFF): OFF
--   SDL_OFFSCREEN               (Wanted: ON): ON
--   SDL_OPENGL                  (Wanted: ON): ON
--   SDL_OPENGLES                (Wanted: ON): ON
--   SDL_OPENVR                  (Wanted: OFF): OFF
--   SDL_OSS                     (Wanted: OFF): OFF
--   SDL_PIPEWIRE                (Wanted: OFF): OFF
--   SDL_PIPEWIRE_SHARED         (Wanted: OFF): OFF
--   SDL_PTHREADS                (Wanted: ON): ON
--   SDL_PTHREADS_SEM            (Wanted: ON): ON
--   SDL_PULSEAUDIO              (Wanted: OFF): OFF
--   SDL_PULSEAUDIO_SHARED       (Wanted: OFF): OFF
--   SDL_RENDER_D3D              (Wanted: OFF): OFF
--   SDL_RENDER_D3D11            (Wanted: OFF): OFF
--   SDL_RENDER_D3D12            (Wanted: OFF): OFF
--   SDL_RENDER_GPU              (Wanted: ON): ON
--   SDL_RENDER_METAL            (Wanted: ON): ON
--   SDL_RENDER_VULKAN           (Wanted: ON): ON
--   SDL_ROCKCHIP                (Wanted: OFF): OFF
--   SDL_RPATH                   (Wanted: ON): ON
--   SDL_RPI                     (Wanted: OFF): OFF
--   SDL_SNDIO                   (Wanted: OFF): OFF
--   SDL_SNDIO_SHARED            (Wanted: OFF): OFF
--   SDL_SSE                     (Wanted: OFF): OFF
--   SDL_SSE2                    (Wanted: OFF): OFF
--   SDL_SSE3                    (Wanted: OFF): OFF
--   SDL_SSE4_1                  (Wanted: OFF): OFF
--   SDL_SSE4_2                  (Wanted: OFF): OFF
--   SDL_SYSTEM_ICONV            (Wanted: OFF): OFF
--   SDL_TESTS                   (Wanted: OFF): OFF
--   SDL_TESTS_LINK_SHARED       (Wanted: ON): OFF
--   SDL_UNINSTALL               (Wanted: OFF): OFF
--   SDL_VIRTUAL_JOYSTICK        (Wanted: ON): ON
--   SDL_VIVANTE                 (Wanted: OFF): OFF
--   SDL_VULKAN                  (Wanted: ON): ON
--   SDL_WASAPI                  (Wanted: OFF): OFF
--   SDL_WAYLAND                 (Wanted: OFF): OFF
--   SDL_WAYLAND_LIBDECOR        (Wanted: OFF): OFF
--   SDL_WAYLAND_LIBDECOR_SHARED (Wanted: OFF): OFF
--   SDL_WAYLAND_SHARED          (Wanted: OFF): OFF
--   SDL_X11                     (Wanted: OFF): OFF
--   SDL_X11_SHARED              (Wanted: OFF): OFF
--   SDL_X11_XCURSOR             (Wanted: OFF): OFF
--   SDL_X11_XDBE                (Wanted: OFF): OFF
--   SDL_X11_XFIXES              (Wanted: OFF): OFF
--   SDL_X11_XINPUT              (Wanted: OFF): OFF
--   SDL_X11_XRANDR              (Wanted: OFF): OFF
--   SDL_X11_XSCRNSAVER          (Wanted: OFF): OFF
--   SDL_X11_XSHAPE              (Wanted: OFF): OFF
--   SDL_X11_XSYNC               (Wanted: OFF): OFF
--   SDL_XINPUT                  (Wanted: OFF): OFF
-- 
--  Build Shared Library: ON
--  Build Static Library: OFF
--  Build libraries as Apple Framework: OFF
-- 
-- If something was not detected, although the libraries
-- were installed, then make sure you have set the
-- CMAKE_C_FLAGS and CMAKE_PREFIX_PATH CMake variables correctly.
-- 
-- Configuring done (0.3s)
-- Generating done (0.1s)

filipkrw avatar Jan 24 '25 21:01 filipkrw

In your reddit post, you mentioned this working alright in sokol on Mac, can you provide a sample of that so that it can be compared?

playmer avatar Jan 26 '25 22:01 playmer

Hey, thanks for reaching out!

I'm not doing anything special, simply drawing some text using sokol_fontstash and a rectangle using sokol_gl. At some point I also used sokol_gp and similarly had no issues with resizing on MacOS (M1 Max chip).

See the code here: https://github.com/filipkrw/hello-sokol. It should build on Windows and MacOS, but let me know in case you try it out and encounter some issues.

https://github.com/user-attachments/assets/b1c8e75e-2c75-4f63-a320-c0ab78c66a63

filipkrw avatar Jan 27 '25 23:01 filipkrw

I'd like to add that with this exact example, this does happen on Windows (10), if you resize the window from the left or top instead of the right or bottom. Actually in all cases the rendering seems to be consistently a single frame behind. It's just that it's less noticeable when resizing from the right/bottom because your pointer is also moving. Given how finnicky/random Windows is regarding window resizing i'm not sure if this can even be fixed.

LeonardoTemperanza avatar Jan 30 '25 16:01 LeonardoTemperanza

In the video above, it does look like the frame of the window lags behind the mouse cursor, but that's also happening in the sokol example. If I go frame by frame and look at the rendering of the square, it does match the width of the title bar (minus padding), so the application rendering appears to be keeping up with the OS's view of the window frame. The hardware mouse cursor will always be faster than the window following it, so what are you seeing or expecting that I'm missing?

slouken avatar Mar 24 '25 20:03 slouken

He's talking about the fact that the left side of the gray box moves even though he's resizing from the right side.

LeonardoTemperanza avatar Mar 27 '25 10:03 LeonardoTemperanza

I was trying to find a solution for the same issue. I then applied the changes described here to SDL_cocoametalview.m

 - (CALayer *)makeBackingLayer
 {
    CAMetalLayer *metalLayer = [self.class.layerClass layer];

    // Allow CA to resize the layer properly
    metalLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;

    // Important for live-resize: redraw instead of stretching old frames
    metalLayer.needsDisplayOnBoundsChange = YES;

    return metalLayer;
 }

- (instancetype)initWithFrame:(NSRect)frame
                      highDPI:(BOOL)highDPI
                     windowID:(Uint32)windowID
                       opaque:(BOOL)opaque
{
    self = [super initWithFrame:frame];
    if (self != nil) {
        self.highDPI = highDPI;
        self.sdlWindowID = windowID;
        self.wantsLayer = YES;

        // Allow resize.
        self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;

        // We want to redraw the layer during live window resize.
        self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;

        // Not strictly necessary, but in case something goes wrong with live window
        // resize, this layer placement makes it more obvious what's going wrong.
        self.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft;
        
        
        self.layer.opaque = opaque;

        SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_EARLY, SDL_MetalViewEventWatch, (__bridge void *)(self));

        [self updateDrawableSize];
    }

    return self;
}

...and it does seem to fix the issue.

Here a comparison.

  • Without the fix https://github.com/user-attachments/assets/91a04ac0-6f1e-4bcc-adee-8f8d624fc61a

  • With the fix https://github.com/user-attachments/assets/b57c496d-950c-4a42-9f0e-3a7e1d743659

In all honesty I'm not super experienced with gpu programming, so I'm not sure whether this needs further refinement or if this behavior should be opt-in. Some may prefer to use the default "clickToResize/ dragAndScaleContent / drawOnRelease". But as I do prefer to draw on resize, this works great for me

P.S. I also found a related blog post describing additional techniques to improve Metal live-resizing: Glitchless Metal Window Resizing I didn't apply the full solution described there, because the author mentions that his approach can introduce some frame drops. But it may still be useful background information if you plan to refine the macOS backend further.

BluEgo avatar Dec 05 '25 14:12 BluEgo

Nice work! I'll add this for the 3.6 milestone, thanks!

slouken avatar Dec 05 '25 16:12 slouken