summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-24 13:09:24 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commita4f5e5b8fc5aed894f13ffe2b96cbd9c5a11770d (patch)
tree9b0a3dc1e15cce2d62adc8d2aa24d3b4fd68a60c /src/gui/gui.zig
parentb4fc9fed0c2d10cd8c1b3b99246b39c262ac5ade (diff)
downloadpardes-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/gui.zig')
-rw-r--r--src/gui/gui.zig41
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);