diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-24 13:09:24 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | a4f5e5b8fc5aed894f13ffe2b96cbd9c5a11770d (patch) | |
| tree | 9b0a3dc1e15cce2d62adc8d2aa24d3b4fd68a60c /src/gui | |
| parent | b4fc9fed0c2d10cd8c1b3b99246b39c262ac5ade (diff) | |
| download | pardes-a4f5e5b8fc5aed894f13ffe2b96cbd9c5a11770d.tar.gz pardes-a4f5e5b8fc5aed894f13ffe2b96cbd9c5a11770d.zip | |
Allocate SDL's memory from the gui's Zig allocator, and give it all back on the way out
SDL allocated with libc malloc. runNative now installs the shared C heap over
the gui's gpa with SDL_SetMemoryFunctions before SDL's first call: SDL frees
with whichever functions are current, so none of its memory may predate
them. SDL allocates from its own threads too, and the gpa is thread-safe in
every build (a DebugAllocator in Debug, libc's malloc in release).
That put SDL in a Debug build's leak report, and the report filled up: 1769
blocks at exit, because runNative never released its GPU objects, its window
or SDL itself. The process was about to exit, but a report that long hides
any leak that matters. runNative now releases every pipeline, texture,
sampler and buffer the Gui holds, closes the gamepad, destroys the GPU device
and the window, and calls SDL_Quit, all through defers. Eight 32-byte blocks
are left, and they are SDL's: 3.4.4's VULKAN_INTERNAL_DestroyCommandPool
never frees two arrays per command buffer (buffersUsedInPendingTransfers and
texturesUsedInPendingTransfers), which SDL's main branch now frees. They go
away with the next SDL; a comment at the SDL_Quit says so.
The frame-cost harness and a software-present run (PARDES_SOFT_PRESENT) both
run and exit cleanly in Debug.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Diffstat (limited to 'src/gui')
| -rw-r--r-- | src/gui/gui.zig | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 73f17a22..9d785b96 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -180,6 +180,12 @@ test "FreeType and HarfBuzz allocate from the gui allocator and give it all back try std.testing.expectEqual(@as(u8, 2), shaped[4].span); } +/// What SDL allocates from, on its own threads as well as the render thread: +/// the gui's gpa, through the shared C heap, which is thread-safe in every +/// build. runNative hands everything back and calls SDL_Quit on the way out. +var sdl_allocator: std.mem.Allocator = undefined; +const sdl_heap = c_heap.Heap(&sdl_allocator); + const max_fallback_fonts = 1 + fonts.fallback_names.len; const LoadedFallback = struct { face: *c.UIFont, @@ -2152,12 +2158,26 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u else |_| {} }; + // SDL frees with whichever functions are current, so they go in before + // its first call, while nothing of SDL's exists yet. + sdl_allocator = gpa; + if (!c.SDL_SetMemoryFunctions(sdl_heap.malloc, sdl_heap.calloc, sdl_heap.realloc, sdl_heap.free)) { + log.err("SDL_SetMemoryFunctions: {s}", .{c.SDL_GetError()}); + return error.SdlInit; + } _ = c.SDL_SetHint("SDL_JOYSTICK_HIDAPI", "1"); // SDL_HINT_JOYSTICK_HIDAPI _ = c.SDL_SetHint("SDL_JOYSTICK_HIDAPI_STEAMDECK", "1"); // ..._STEAMDECK if (!c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_GAMEPAD)) { log.err("SDL_Init: {s}", .{c.SDL_GetError()}); return error.SdlInit; } + // SDL allocates from gpa (above), so what it still holds at exit is in a + // Debug build's leak report. The window, the GPU device and everything + // made on it go back first (the defers below run before this one). What + // is left is SDL 3.4.4's: its Vulkan backend never frees two 32-byte + // arrays per command buffer (VULKAN_INTERNAL_DestroyCommandPool; SDL's + // main branch frees them). + defer c.SDL_Quit(); var win_flags: c.SDL_WindowFlags = c.SDL_WINDOW_RESIZABLE; if (!test_mode) win_flags |= c.SDL_WINDOW_HIGH_PIXEL_DENSITY; // A test run renders to the capture texture; keep it off the desktop. @@ -2171,6 +2191,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u log.err("SDL_CreateWindow: {s}", .{c.SDL_GetError()}); return error.SdlInit; }; + defer c.SDL_DestroyWindow(window); if (native_wayland) log.info("Wayland opacity: background-only transparent surface", .{}); const arrow_cursor = c.SDL_CreateCursor(&p9_arrow_set, &p9_arrow_mask, 16, 16, 1, 1); defer if (arrow_cursor) |cursor| c.SDL_DestroyCursor(cursor); @@ -2183,6 +2204,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u log.err("SDL_CreateGPUDevice: {s}", .{c.SDL_GetError()}); return error.SdlInit; }; + defer c.SDL_DestroyGPUDevice(device); var soft_present = false; var soft_renderer: ?*c.SDL_Renderer = null; const force_soft = if (env.get("PARDES_SOFT_PRESENT")) |raw| @@ -2346,6 +2368,25 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u .surface_opacity = surface_opacity, .soft_renderer = soft_renderer, }; + // The pipelines, textures and buffers the Gui holds, whatever they were + // regrown to; the image ones are released below. + defer { + c.SDL_ReleaseGPUGraphicsPipeline(device, g.pipeline); + c.SDL_ReleaseGPUGraphicsPipeline(device, g.overlay_pipeline); + c.SDL_ReleaseGPUGraphicsPipeline(device, g.crt_pipeline); + c.SDL_ReleaseGPUTexture(device, g.atlas_tex); + c.SDL_ReleaseGPUSampler(device, g.atlas_sampler); + c.SDL_ReleaseGPUSampler(device, g.linear_sampler); + c.SDL_ReleaseGPUTransferBuffer(device, g.atlas_xfer); + c.SDL_ReleaseGPUBuffer(device, g.overlay_vbuf); + c.SDL_ReleaseGPUTransferBuffer(device, g.overlay_vxfer); + if (g.vbuf) |buffer| c.SDL_ReleaseGPUBuffer(device, buffer); + if (g.vxfer) |transfer| c.SDL_ReleaseGPUTransferBuffer(device, transfer); + if (g.scene_tex) |texture| c.SDL_ReleaseGPUTexture(device, texture); + if (g.capture_tex) |texture| c.SDL_ReleaseGPUTexture(device, texture); + if (g.capture_xfer) |transfer| c.SDL_ReleaseGPUTransferBuffer(device, transfer); + if (g.gamepad) |pad| c.SDL_CloseGamepad(pad); + } setGuiFontName(&g, "Adwaita Mono"); loadFallbackFonts(&g, gpa); defer deinitFallbackFonts(&g, gpa); |
