[MacOS] Resize flicker
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)
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?
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
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.
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?
He's talking about the fact that the left side of the gray box moves even though he's resizing from the right side.
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.
Nice work! I'll add this for the 3.6 milestone, thanks!