Skip to content

Fix segfault on window close: let GLFW outlive the Vulkan objects - #443

Open
ozanyasindogan wants to merge 5 commits into
KhronosGroup:mainfrom
ozanyasindogan:fix/glfw-vulkan-destruction-order
Open

ozanyasindogan wants to merge 5 commits into
KhronosGroup:mainfrom
ozanyasindogan:fix/glfw-vulkan-destruction-order

Conversation

@ozanyasindogan

@ozanyasindogan ozanyasindogan commented Aug 8, 2026 •

Copy link
Copy Markdown

Fixes the crash on window close reported in #138.

Problem

On the tested Arch Linux/Wayland setup, the affected desktop chapters crashed during shutdown because GLFW was terminated before the Vulkan objects were destroyed.

run() ends with cleanup(), which called glfwDestroyWindow() and glfwTerminate(). The Vulkan objects are vk::raii class members, so they're only destroyed after run() returns, by which time GLFW has already closed the connection to the window system. Destroying the swapchain then crashes inside libwayland-client (SIGSEGV from ~SwapchainKHR). Before the move to vk::raii, cleanup() destroyed the Vulkan objects by hand and called glfwTerminate() last; the conversion removed those calls but left the GLFW ones, which reversed the order.

Fix

A new shared header, attachments/glfw_guard.h, defines GlfwGuard. Its constructor calls glfwInit() and throws if that fails; its destructor calls glfwTerminate(). Each chapter declares the guard as its first member:

#include "glfw_guard.h"   // after the chapter's own GLFW include
...
  private:
	// Declared first, so GLFW outlives every Vulkan member below. See glfw_guard.h.
	GlfwGuard glfwGuard;

Members are destroyed in reverse declaration order, so GLFW is now terminated after every Vulkan object, however many members later chapters add. glfwTerminate() also destroys any windows that are still open, so the separate glfwDestroyWindow() call is gone.

The header is included after the chapter's own #include <GLFW/glfw3.h>, because it includes GLFW itself and would otherwise pull it in before GLFW_INCLUDE_VULKAN is defined. A failed glfwInit(), which was previously ignored, is now reported at startup through the existing try/catch in main().

Changes

  • attachments/glfw_guard.h: new. No CMake changes are needed.
  • 36 chapter sources that own a window (00_base_code.cpp to 38_ray_tracing.cpp): include the header and declare the guard first. glfwInit() is removed from initWindow(), and the GLFW teardown is removed from cleanup(). In 34_android.cpp, 35_gltf_ktx.cpp and 36_multiple_objects.cpp, all of this is inside the existing desktop-only branch.
  • Docs:
    • 00_Base_code.adoc explains the header, the include order, and why the guard is declared first.
    • The cleanup() snippets in 04_Swap_chain_recreation.adoc and 16_Multiple_Objects.adoc no longer call glfwDestroyWindow()/glfwTerminate().
    • Every page that links a chapter's .cpp also links glfw_guard.h, so a chapter downloaded on its own still compiles. The ray tracing course overview lists the header with its other files.

Testing

The current tests compare this PR with 54b886e, the upstream commit merged into this branch. Linux tests ran on the published head e5c9901. Windows and Android tests ran on 1e639b8, whose Git tree is identical to e5c9901; those tests were not rerun after the commit messages changed. All window-close tests were scripted. Earlier results are listed separately below.

  • Linux: Arch Linux, KDE Plasma 6.7.5 on Wayland, RTX 5080 with NVIDIA 615.71.09, Vulkan 1.4.363, GLFW 3.5.1, GCC 16.2.1, Clang 23.1.1.
  • Windows: Windows 11, MSVC 14.51, clang-cl 22.1.8, Vulkan SDK 1.4.357, RTX 5080.
  • Android: built on Windows with the repository's Gradle wrapper and the pinned NDK 28.1.13356709.
Test 54b886e This PR
Linux, Wayland: 22 presenting chapters closed through KWin, two full runs 44/44 SIGSEGV, each with a core dump 44/44 exit 0, no core dumps
Linux, Wayland: bounded main loop, 14_command_buffers ×3 and 30_multisampling ×5 8/8 SIGSEGV 8/8 exit 0
Linux, Wayland: bounded main loop, the 13 chapter sources from 00 to 14 06–10, 12 and 14 SIGSEGV; 00–05 exit 0 13/13 exit 0
Linux: GLFW's X11 backend on XWayland, the same 22 chapters 22/22 exit 0 22/22 exit 0
Linux: no display, so glfwInit() fails exit status 1, "failed to create window surface!" exit status 1, "failed to initialize GLFW!"
Windows: 36 chapters closed via WM_CLOSE 36/36 exit 0 36/36 exit 0
Builds: all 36 chapters with GCC and Clang (Linux) and MSVC and clang-cl (Windows) all build all build, with the same warning sets as 54b886e (also with -Wall -Wextra and /W4); none from glfw_guard.h
Android APKs: chapters 34, 35, 36 × x86_64 and arm64-v8a 6/6 built 6/6 built
C++20 module build (-DENABLE_CPP20_MODULE=ON): Linux GCC and Clang, Windows MSVC fails fails the same way
Local run of CI's clang-format check on the diff n/a no violations

Notes on these results:

  • The Wayland crash: every crash on 54b886e has the same stack: ~SwapchainKHR in the application's destructor, then the NVIDIA driver and wl_proxy_marshal_flags, then the crash in libwayland-client. The Linux close script starts each chapter, waits 6 seconds, then has KWin call closeWindow() on that process's window, which sends the same close request as the title-bar X.
  • Bounded runs: chapters before 15 never present, so on Wayland there is no window to close. For those, and for the repeated runs, I used a temporary local patch (not part of this PR) that ends the main loop after 300 iterations. The seven baseline crashes are the tested early chapters that create a swapchain.
  • Validation: 17 of the 22 presenting chapters load the validation layer themselves and printed nothing. I ran the other five again with the layer forced on. Only 33_vulkan_profiles reported anything: VUID-vkCmdPipelineBarrier2-synchronization2-03848, while recording frames, identically on both revisions. It's an existing issue in that chapter and not related to shutdown.
  • Windows never crashed with the old order, so there this is only a check that nothing regressed.
  • Android: the guard and its include are inside the desktop-only branch, and the Android build doesn't include glfw_guard.h or GLFW at all. I couldn't test Vulkan teardown on Android. On an emulator, every APK from both revisions crashes in GameActivity.onCreate because the activity's theme isn't an AppCompat theme. With the theme changed locally, the x86_64 APKs then fail with an UnsatisfiedLinkError for GameActivity.initializeNativeCode. Both problems exist upstream and are unrelated to this PR.
  • X11: on Linux, X11 was covered only by GLFW's X11 backend on XWayland, not by a native Xorg session.
  • Module build: with current toolchains, vulkan.cppm imports the std module, which the build doesn't provide, and the module is now named vulkan while the chapters import vulkan_hpp. CI doesn't build this configuration. To check the include order for it anyway, I preprocessed every chapter with USE_CPP20_MODULES=1: glfwCreateWindowSurface is declared in the same chapters on both revisions.
  • Docs:
    • make setup in antora/ runs without errors on both revisions. On this branch it copies glfw_guard.h, and 39 generated pages link to it.
    • A check of all attachment links in the generated pages found no broken link added by this PR. 11 simple_engine/*.cpp targets are missing on both revisions; that's an existing upstream problem.
    • I didn't run a full Antora site build.
  • Not tested: macOS/MoltenVK, and AMD or Intel drivers.

Open question

glfw_guard.h currently inherits Apache-2.0 from the attachments/** rule in REUSE.toml, while many of the early chapter sources it's taken from are listed as CC0-1.0. Would you prefer an explicit CC0 annotation for the header, so those examples stay consistent? I've left REUSE.toml unchanged for now.

Earlier results (before the shared header, against e8c3ba2)

These are from earlier revisions of this PR, when the guard was still defined inside each chapter. The baseline is e8c3ba2, the upstream commit the branch was based on at the time.

  • 55de73a, Linux/Wayland: Arch Linux, KWin 6.7.3, NVIDIA 610.43.03, Vulkan 1.4.357, GLFW 3.5.1, GCC 16.1.1. The same 22 chapters were closed through KWin by a script. On e8c3ba2 all 22 crashed with SIGSEGV; on 55de73a all 22 exited with 0. 14_command_buffers (3 runs) and 30_multisampling (5 runs) were run with the main loop ended after a fixed number of iterations: they crashed every time on e8c3ba2 and exited cleanly every time on 55de73a. As a control, removing only the glfwTerminate() call from e8c3ba2 also stopped the crash (5/5). That control leaks the connection, though, while this PR destroys it in the right order.
  • 8d7abf5, Windows: all 36 chapters closed via WM_CLOSE exited with 0 on both e8c3ba2 and 8d7abf5.

`cleanup()` called `glfwDestroyWindow()` and `glfwTerminate()` while every
`vk::raii` member was still alive. Those members belong to the application
class, so they are destroyed only after `run()` returns - that is, after GLFW
has already torn down the window system connection they still reference.
`vkDestroySwapchainKHR` then marshals a request on freed Wayland proxies and
the process dies during exit:

    KhronosGroup#12 HelloTriangleApplication::~HelloTriangleApplication()
    KhronosGroup#11 vk::raii::SwapchainKHR::~SwapchainKHR()
    KhronosGroup#10 vk::raii::SwapchainKHR::clear()
    KhronosGroup#4  libnvidia-glcore.so
    KhronosGroup#3  wl_proxy_marshal_flags
    #0  libwayland-client.so.0            <- SEGV_MAPERR

The pre-RAII tutorial destroyed every Vulkan object by hand at the end of
`cleanup()`, so `glfwTerminate()` genuinely ran last. Converting to `vk::raii`
removed those explicit calls but left the two GLFW calls behind, silently
inverting the order.

Ownership of the GLFW lifetime now belongs to a `GlfwGuard` member declared
first in the class. Members are destroyed in reverse declaration order, so
being first means it is destroyed last - after every Vulkan object, however
many later chapters add. `glfwTerminate()` also destroys any windows still
open, so the separate `glfwDestroyWindow()` call is no longer needed.

Applied to all 34 affected chapters, plus 00_base_code so the pattern is
correct from the very first chapter and never has to change again - which is
what the Base Code chapter already promises the reader.

Verified on Arch Linux / Wayland / KWin, NVIDIA 610.43.03, Vulkan 1.4.357:

  - all 304 build targets compile
  - 22 chapters that present a frame: launched and closed with a real
    compositor close request, all exit 0 (each exited 139 before)
  - 14_command_buffers (never presents, so its surface is never mapped and
    it cannot be closed by the compositor) checked by breaking out of the
    main loop instead: 3/3 SIGSEGV before, 3/3 clean after
  - no validation layer errors, and no coredumps from any fixed binary

Refs: KhronosGroup#138
@asuessenbach

asuessenbach commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

On windows, I don't have any issues with window closing, here.
But your approach is working as well, and it clearly is the better one.

But shouldn't the GlfwGuard have a constructor like this:

GlfwGuard()
{
	if (!glfwInit())
		throw std::runtime_error("Failed to initialize GLFW");
}

And even though implicit destruction of the GLFWwindow works fine, maybe should then introduce some wrapper for it as well, like

	struct GLFWWindowWrapper
	{
		GLFWWindowWrapper() = delete;

		GLFWWindowWrapper(int width, int height, const char *title)
		{
			glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
			glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
			window = glfwCreateWindow(width, height, title, nullptr, nullptr);
		}
		~GLFWWindowWrapper()
		{
			if (window)
				glfwDestroyWindow(window);
		}
		GLFWwindow *get() const
		{
			return window;
		}

	  private:
		GLFWwindow *window = nullptr;
	};

Or, as already mentioned in #138, switch to vkfw?

Besides that, I would not keep an empty cleanup function.

ozanyasindogan added a commit to ozanyasindogan/Vulkan-Tutorial that referenced this pull request Aug 10, 2026
Follow-up to review feedback on KhronosGroup#443.

`glfwInit()` was called at the top of `initWindow()` and its return value
discarded in every chapter, so a failed initialization surfaced later as a
null window rather than as an error. Moving it into `GlfwGuard` gives the
guard both ends of the GLFW lifetime and gives the failure somewhere to go:

    GlfwGuard()
    {
        if (!glfwInit())
        {
            throw std::runtime_error("failed to initialize GLFW!");
        }
    }

The constructor runs during member initialization, before `run()` is entered,
and every chapter already constructs the application object inside the `try`
in `main()`, so the error is reported through the same path as every other
`std::runtime_error`. If it throws, `~GlfwGuard()` never runs, so
`glfwTerminate()` is correctly skipped after a failed init.

Also in this change:

  - 34_android.cpp gains the same guard. Its PLATFORM_DESKTOP path creates a
    GLFW window but never called `glfwTerminate()` or `glfwDestroyWindow()`,
    so it leaked GLFW rather than destroying it out of order. Every chapter
    that owns a window now uses the guard.
  - `cleanup()` was left as bare empty braces in 31_compute_shader,
    32_ecosystem_utilities, 35_gltf_ktx and 38_ray_tracing, while 28 other
    chapters carried a comment explaining where GLFW teardown had moved to.
    Those four now match.
  - The guard in 32_ecosystem_utilities moved above `AppInfo appInfo` so that
    "declared first" is literally true there.
  - 00_Base_code.adoc introduces the guard where GLFW initialization is
    taught, instead of reintroducing it in the cleanup section, and
    04_Swap_chain_recreation.adoc drops `glfwInit()` from its initWindow
    snippet.

Verified on Windows 11, MSVC 14.51, Vulkan 1.4.357, RTX 5080:

  - all 144 build targets compile with no new warnings
  - clang-format-diff.py over the diff against main reports no violations
  - every chapter built from a pristine main and from this branch, then
    closed with a real WM_CLOSE and its exit code recorded: 36/36 exit 0 on
    both sides. Windows does not fault on the original ordering, so this
    measures no regression rather than a fix on that platform.

Refs: KhronosGroup#138

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ozanyasindogan

Copy link
Copy Markdown
Author

Hi,

I compiled and ran the tutorial files on a Linux/Wayland desktop and the crash happens on every desktop chapter there. On Windows it quits without crashing, like you say. But the ordering still looks wrong to me on both, it just doesn't blow up on Win32.

I checked Windows here as well before answering, so it's not only guesswork. I built every chapter from a clean main and from this branch, ran them all and closed each window the normal way. Nothing crashed on either side and I couldn't see any regression from the change.

I'm still learning Vulkan, so I don't want to overstate the Wayland part. What I could follow from the backtrace is that glfwTerminate() shuts down the connection to the compositor, and the swapchain is destroyed after that while it still needs it. Whether that actually faults seems to depend on the platform. The order itself being wrong is the part I'm confident about, since that one is just member destruction order.

What I wanted with this PR was to fix that without pulling in a new dependency or rewriting too much of the tutorial code.

About glfwInit() in the constructor, you're right and I've added it. The return value was being ignored everywhere anyway:

struct GlfwGuard
{
    GlfwGuard()
    {
        if (!glfwInit())
        {
            throw std::runtime_error("failed to initialize GLFW!");
        }
    }

    ~GlfwGuard()
    {
        glfwTerminate();
    }
} glfwGuard;

initWindow() loses its glfwInit() line. The throw happens while the object is still being constructed, and every chapter already creates app inside a try in main(), so it gets reported like any other runtime_error. And if the constructor throws then the destructor doesn't run, so glfwTerminate() isn't called after a failed init.

The GLFWWindowWrapper is nicer than a raw handle, no argument there, and the ordering in your sketch looks right to me. My hesitation is just how much it pulls in. GLFWWindowWrapper() = delete; makes it non-default-constructible, so it has to be built in a member-init list, and that moves window creation out of initWindow() and into a constructor in every chapter. initWindow() also sets the user pointer and the framebuffer callback, which both need this, so the setup would end up split in two places. The window hints aren't the same everywhere either, many chapters disable resizing, many enable it, and at least one sets no hint at all, so those would have to be passed in somehow. initWindow() is walked through step by step in 00_Base_code.adoc too, so that text would have to change with it.

None of that is an objection in principle. It just makes this a refactor of the tutorial's window setup rather than a crash fix, and it doesn't change teardown behaviour, since glfwTerminate() already destroys any windows still open.

Same for vkfw, only more so, since that is a new dependency in every chapter plus all the prose around it. I'd have thought that one belongs in #138 rather than here.

I also ran a code review with Claude Code over the rest of the chapters. Its notes are below.


Claude Code review notes

34_android.cpp was leaking GLFW. Fixed in this push. The PR description states the file was left untouched because it contains no GLFW. That is incorrect: it carries a full PLATFORM_DESKTOP path that calls glfwInit() and glfwCreateWindow(). It avoided this crash for an unrelated reason, namely that it never called glfwTerminate() or glfwDestroyWindow() at all, so it leaked the library rather than destroying it out of order. It now carries the same guard, which brings every GLFW-owning chapter into line. The description text will be corrected.

Inconsistent cleanup() bodies. Fixed in this push. The previous push left cleanup() as bare empty braces in 31_compute_shader.cpp, 32_ecosystem_utilities.cpp, 35_gltf_ktx.cpp and 38_ray_tracing.cpp, against 28 chapters that carried a comment explaining where GLFW teardown had moved to. Those four now match.

22_descriptor_layout emits validation errors when run. Pre-existing on main, reproducible without this branch, and outside the scope of this fix. Not addressed here. It can be raised separately if that is useful.

Verification. All 144 targets build clean under MSVC 14.51 with no new warnings. The repository's own clang-format-diff.py check over the diff against main reports no violations. Both a pristine main build and this branch were swept on Windows by issuing a real close request to each chapter window and recording process exit codes, with no crashes and no behavioural difference between the two.

@ozanyasindogan

Copy link
Copy Markdown
Author

The Android Build failure looks unrelated to this PR. It's a Gradle/AGP incompatibility in CI rather than anything in the tutorial code.

The job fails while applying the Android Gradle plugin, before any C++ gets compiled:

Plugin 'com.android.internal.application' relies on 'org.gradle.api.problems.internal.InternalProblems',
a Gradle internal API that was removed in Gradle 9.6.0.
Update the plugin to a version that no longer uses Gradle internal APIs, or use Gradle 9.5.

attachments/android/ has no checked-in gradlew, so the workflow runs gradle wrapper (workflow.yml L572) with whatever Gradle the runner image ships, currently 9.6.1. attachments/android/build.gradle pins AGP 8.11.0, which relies on the API that was removed in 9.6.0. There is a gradle/wrapper/gradle-wrapper.properties pinning gradle-9.0-milestone-1, but with no gradlew present it gets regenerated and never takes effect.

This PR seems to be the first one to actually run the job. Android Build is gated on check-android-changes, whose ANDROID_PATTERN includes attachments/35_gltf_ktx.cpp (workflow.yml L72), which this PR touches. Across the last 25 CMake CI runs the job is skipped everywhere else, including every push to main, so the breakage has been sitting there since the runner image moved to Gradle 9.6.x.

For completeness, the change can't affect the Android build. In all three Android-supported chapters it touches (34_android.cpp, 35_gltf_ktx.cpp, 36_multiple_objects.cpp) the guard sits inside the #else of #if PLATFORM_ANDROID, and the glfwInit() move is inside #if PLATFORM_DESKTOP, so none of it is compiled for Android.

Two ways to get the job green again, either as a one-liner here or as a separate PR:

  • pin the wrapper: gradle wrapper --gradle-version 9.5
  • or bump AGP to a version that supports Gradle 9.6

Follow-up to review feedback on KhronosGroup#443.

`glfwInit()` was called at the top of `initWindow()` and its return value
discarded in every chapter, so a failed initialization surfaced later as a
null window rather than as an error. Moving it into `GlfwGuard` gives the
guard both ends of the GLFW lifetime and gives the failure somewhere to go:

    GlfwGuard()
    {
        if (!glfwInit())
        {
            throw std::runtime_error("failed to initialize GLFW!");
        }
    }

The constructor runs during member initialization, before `run()` is entered,
and every chapter already constructs the application object inside the `try`
in `main()`, so the error is reported through the same path as every other
`std::runtime_error`. If it throws, `~GlfwGuard()` never runs, so
`glfwTerminate()` is correctly skipped after a failed init.

Also in this change:

  - 34_android.cpp gains the same guard. Its PLATFORM_DESKTOP path creates a
    GLFW window but never called `glfwTerminate()` or `glfwDestroyWindow()`,
    so it leaked GLFW rather than destroying it out of order. Every chapter
    that owns a window now uses the guard.
  - `cleanup()` was left as bare empty braces in 31_compute_shader,
    32_ecosystem_utilities, 35_gltf_ktx and 38_ray_tracing, while 28 other
    chapters carried a comment explaining where GLFW teardown had moved to.
    Those four now match.
  - The guard in 32_ecosystem_utilities moved above `AppInfo appInfo` so that
    "declared first" is literally true there.
  - 00_Base_code.adoc introduces the guard where GLFW initialization is
    taught, instead of reintroducing it in the cleanup section, and
    04_Swap_chain_recreation.adoc drops `glfwInit()` from its initWindow
    snippet.

Verified on Windows 11, MSVC 14.51, Vulkan 1.4.357, RTX 5080:

  - all 144 build targets compile with no new warnings
  - clang-format-diff.py over the diff against main reports no violations
  - every chapter built from a pristine main and from this branch, then
    closed with a real WM_CLOSE and its exit code recorded: 36/36 exit 0 on
    both sides. Windows does not fault on the original ordering, so this
    measures no regression rather than a fix on that platform.

Refs: KhronosGroup#138
@ozanyasindogan
ozanyasindogan force-pushed the fix/glfw-vulkan-destruction-order branch from 207324c to 8d7abf5 Compare August 10, 2026 13:35
@SaschaWillems

Copy link
Copy Markdown
Collaborator

We are aware of the Android issue, it also occurs in one of our own PRs.

@gpx1000

gpx1000 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

It's a pretty elegant solution. I tested it on Wayland, and Win, works for me. And thanks for updating the prose in the tutorial. I'm happy with this.

@gpx1000 gpx1000 linked an issue Aug 19, 2026 that may be closed by this pull request
@ozanyasindogan

Copy link
Copy Markdown
Author

@gpx1000 Thanks a lot, I’m really glad it works well on both Wayland and Windows. I appreciate you taking the time to test it and review the tutorial updates.

@gpx1000

gpx1000 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

FYI, Android CI now has a PR fix available: #458 When it merges to main, merge to the latest and we will take this PR. Thanks for the work.

@ozanyasindogan

Copy link
Copy Markdown
Author

@gpx1000 Just a quick follow-up: #458 has landed on main, and I've merged it into this branch (40dea36). All checks are green now, including Android Build and both build (ubuntu-latest) and build (windows-latest).

No conflicts, and nothing outstanding from the review — this should be ready whenever you have a moment. Thanks again to both of you for the reviews.

@SaschaWillems

Copy link
Copy Markdown
Collaborator

We discussed this on a recent meeting and while I'm fine with how this works, having this fix duplicated almost 40 times is something I'm not to keen on merging. I'd prefer having the GlfwGuard in a separate header file that's included in the chapters. That would remove code-duplication, something the tutorial already suffers from and something we want to address in the future anyway.

@marty-johnson59

Copy link
Copy Markdown
Contributor

Hi @ozanyasindogan, can you reply to Sascha's comment above? Any way to adjust this to accommodate his request? Thanks.

@ozanyasindogan

Copy link
Copy Markdown
Author

We discussed this on a recent meeting and while I'm fine with how this works, having this fix duplicated almost 40 times is something I'm not to keen on merging. I'd prefer having the GlfwGuard in a separate header file that's included in the chapters. That would remove code-duplication, something the tutorial already suffers from and something we want to address in the future anyway.

Hello everyone,

Thank you for following up, and thank you, Sascha, for the suggestion. Sorry for the delayed response.

I initially misunderstood your comment. I thought you meant that this fix would be handled as part of a broader effort to consolidate and reorganize the tutorial code, and that you didn’t intend to proceed with this PR. That’s why I hadn’t followed up further.

My original intention was to fix the crashes I encountered on Arch Linux with Wayland while keeping the changes small and preserving the tutorial’s existing structure. Moving GlfwGuard into a shared header makes sense and still fits that goal.

Since some time has passed, I’ve also checked the latest main. The teardown ordering addressed by this PR appears to remain unchanged.

Would you like me to bring this PR up to date with main, move GlfwGuard into a shared header as suggested, and update the tutorial text accordingly? I’d be happy to do that if it still fits your plans.

Thank you again for reviewing and testing the fix.

@SaschaWillems

Copy link
Copy Markdown
Collaborator

Would you like me to bring this PR up to date with main, move GlfwGuard into a shared header as suggested, and update the tutorial text accordingly? I’d be happy to do that if it still fits your plans.

Yes, that would be ideal and reduce code duplications.

The guard was repeated in all 36 chapters that own a window. It now lives
once in attachments/glfw_guard.h, together with the explanation of why it
has to be declared first.

Each chapter includes the header right after its own GLFW include, so
GLFW_INCLUDE_VULKAN is still defined when GLFW is first included, and
declares `GlfwGuard glfwGuard;` as its first member with a one-line
comment, since that ordering is what keeps GLFW alive until every
vk::raii member is gone. In 34_android, 35_gltf_ktx and 36_multiple_objects
both stay inside the existing desktop-only branch.

Docs:
- Base code teaches the header, the include order and the member.
- Every page that links a chapter's source now also links glfw_guard.h, so
  a chapter downloaded on its own still compiles.
- The ray tracing course overview lists the header with the other files.
- The Multiple Objects cleanup() snippet no longer calls
  glfwDestroyWindow()/glfwTerminate(). It was added after this PR was
  opened and showed exactly the ordering this PR fixes; the chapter's code
  already doesn't do it.
@ozanyasindogan

ozanyasindogan commented Oct 20, 2026 •

Copy link
Copy Markdown
Author

@SaschaWillems I've updated the PR as you suggested. GlfwGuard now lives in a single header, attachments/glfw_guard.h, which each chapter includes, and the branch is merged with upstream 54b886e. I've rewritten the description so the final change and all the test results are in one place.

On Arch Linux with KDE Plasma on Wayland, 54b886e crashed on window close in all 44 scripted runs, while e5c9901 exited cleanly in all 44. The scripted close tests also passed on both revisions on Windows and XWayland. All six Android chapter/ABI combinations built successfully on both revisions, but startup failures prevented Android Vulkan runtime testing. The description includes the full results and remaining limitations.

There's one open question for you at the end of the description, about the license annotation for the new header. Also, the four workflow runs are waiting for maintainer approval. Could someone approve them when convenient?

@gpx1000 @asuessenbach the behaviour is the same as the version you approved, but the structure has changed, so I'd appreciate another look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Segmentation fault on program termination with RAII Vulkan headers

5 participants