odin-lang / Odin

Odin Programming Language
https://odin-lang.org
BSD 3-Clause "New" or "Revised" License
6.95k stars 615 forks source link

Using the 'vendor:vulkan' package segfaults on the first call #3046

Closed gaultier closed 10 months ago

gaultier commented 10 months ago

Context

$ odin report
Where to find more information and get into contact when you encounter a bug:

    Website: https://odin-lang.org
    GitHub:  https://github.com/odin-lang/Odin/issues

Useful information to add to a bug report:

    Odin: dev-2023-12-nightly:e52cc73d
    OS:   Ubuntu 23.04, Linux 6.2.0-39-generic
    CPU:  11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz

Using Wayland (swaywm).

Expected Behavior

I am creating a window with the vendor:sdl2 package (which works fine) and a vulkan instance with the vendor:vulkan package. I expect it to run fine but it segfaults.

Current Behavior

It segfaults on the first vulkan call, whatever it is.

Failure Information (for bugs)

GDB only shows that the segfault happens within the vulkan call:

(gdb) r
Starting program: /home/pg/my-code/odin-vulkan/odin-vulkan
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff6df46c0 (LWP 156301)]
[New Thread 0x7ffff25f36c0 (LWP 156302)]

Thread 1 "odin-vulkan" received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000040478b in main.main () at main.odin:28

Steps to Reproduce

package main

import "core:os"
import "vendor:sdl2"
import "vendor:vulkan"

main :: proc() {
    if sdl2.Init(sdl2.INIT_EVERYTHING) < 0 {
        os.exit(1)
    }
    window := sdl2.CreateWindow("Hello", 0, 0, 800, 600, {sdl2.WindowFlags.VULKAN})
    if window == nil {
        os.exit(1)
    }
    app_info: vulkan.ApplicationInfo = {
        sType              = .APPLICATION_INFO,
        applicationVersion = vulkan.MAKE_VERSION(0, 0, 1),
        engineVersion      = vulkan.MAKE_VERSION(0, 0, 1),
        apiVersion         = vulkan.MAKE_VERSION(1, 0, 0),
    }
    create_info: vulkan.InstanceCreateInfo = {
        sType            = .INSTANCE_CREATE_INFO,
        pApplicationInfo = &app_info,
    }

    instance: vulkan.Instance = {}
    if r := vulkan.CreateInstance(&create_info, nil, &instance); r != .SUCCESS {
        os.exit(1)
    }
}

This segfaults. Interestingly, any vulkan call will segfault, not just CreateInstance.

E.g.:

    layer_count: u32 = 0
    if r := vulkan.EnumerateInstanceLayerProperties(&layer_count, nil); r != .SUCCESS {
        os.exit(1)
    }

Will also segfault.

The equivalent C code runs fine (same machine, same SDL/Vulkan version) and prints instance created as expected:

// cc vulkan-sdl.c -lSDL2 -lvulkan

#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>
#include <vulkan/vulkan.h>

int main() {
  SDL_Init(SDL_INIT_EVERYTHING);

  SDL_Window *window =
      SDL_CreateWindow("Hello", 0, 0, 800, 600, SDL_WINDOW_VULKAN);

  VkApplicationInfo app_info = {0};
  app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
  app_info.applicationVersion = VK_MAKE_VERSION(0, 0, 1);
  app_info.engineVersion = VK_MAKE_VERSION(0, 0, 1);
  app_info.apiVersion = VK_API_VERSION_1_0;

  VkInstanceCreateInfo inst_cre_info = {0};
  inst_cre_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
  inst_cre_info.pApplicationInfo = &app_info;

  VkInstance inst = {0};

  VkResult r = 0;
  if ((r = (vkCreateInstance(&inst_cre_info, NULL, &inst))) != VK_SUCCESS) {
    exit(1);
  };
  printf("instance created.\n");
}

I tried to:

Failure Logs

$ apt info libsdl2-dev
[...]
Package: libsdl2-dev
Version: 2.26.3+dfsg-1
[...]
$ vulkaninfo --summary
==========
VULKANINFO
==========

Vulkan Instance Version: 1.3.239

Instance Extensions: count = 21
-------------------------------
VK_EXT_acquire_drm_display             : extension revision 1
VK_EXT_acquire_xlib_display            : extension revision 1
VK_EXT_debug_report                    : extension revision 10
VK_EXT_debug_utils                     : extension revision 2
VK_EXT_direct_mode_display             : extension revision 1
VK_EXT_display_surface_counter         : extension revision 1
VK_EXT_swapchain_colorspace            : extension revision 4
VK_KHR_device_group_creation           : extension revision 1
VK_KHR_display                         : extension revision 23
VK_KHR_external_fence_capabilities     : extension revision 1
VK_KHR_external_memory_capabilities    : extension revision 1
VK_KHR_external_semaphore_capabilities : extension revision 1
VK_KHR_get_display_properties2         : extension revision 1
VK_KHR_get_physical_device_properties2 : extension revision 2
VK_KHR_get_surface_capabilities2       : extension revision 1
VK_KHR_portability_enumeration         : extension revision 1
VK_KHR_surface                         : extension revision 25
VK_KHR_surface_protected_capabilities  : extension revision 1
VK_KHR_wayland_surface                 : extension revision 6
VK_KHR_xcb_surface                     : extension revision 6
VK_KHR_xlib_surface                    : extension revision 6

Instance Layers: count = 4
--------------------------
VK_LAYER_INTEL_nullhw       INTEL NULL HW                1.1.73   version 1
VK_LAYER_KHRONOS_validation Khronos Validation Layer     1.3.239  version 1
VK_LAYER_MESA_device_select Linux device selection layer 1.3.211  version 1
VK_LAYER_MESA_overlay       Mesa Overlay layer           1.3.211  version 1

Devices:
========
GPU0:
    apiVersion         = 1.3.238
    driverVersion      = 23.0.4
    vendorID           = 0x8086
    deviceID           = 0x9a49
    deviceType         = PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU
    deviceName         = Intel(R) Xe Graphics (TGL GT2)
    driverID           = DRIVER_ID_INTEL_OPEN_SOURCE_MESA
    driverName         = Intel open-source Mesa driver
    driverInfo         = Mesa 23.0.4-0ubuntu1~23.04.1
    conformanceVersion = 1.3.0.0
    deviceUUID         = ff258cf4-4865-a82b-e58f-77bffa8e3040
    driverUUID         = 14a9b327-5c23-edd1-a4cc-05540c6d05b5
GPU1:
    apiVersion         = 1.3.238
    driverVersion      = 0.0.1
    vendorID           = 0x10005
    deviceID           = 0x0000
    deviceType         = PHYSICAL_DEVICE_TYPE_CPU
    deviceName         = llvmpipe (LLVM 15.0.7, 256 bits)
    driverID           = DRIVER_ID_MESA_LLVMPIPE
    driverName         = llvmpipe
    driverInfo         = Mesa 23.0.4-0ubuntu1~23.04.1 (LLVM 15.0.7)
    conformanceVersion = 1.3.1.1
    deviceUUID         = 6d657361-3233-2e30-2e34-2d3075627500
    driverUUID         = 6c6c766d-7069-7065-5555-494400000000

UPDATE: Tried the same code on a different machine (Ubuntu 23.04 as well, but with a Nvidia GPU): same outcome. The Odin code segfaults, the C code works.

flysand7 commented 10 months ago

Looks like the procedure pointers aren't being loaded, therefore on the first call to vk.CreateInstance it tries to call a null-pointer which results in a SIGSEGV.

You should try passing the pointer returned by SDL_Vulkan_GetVkGetInstanceProcAddr to vk.load_proc_address_global. Read the documentation on the SDL proc though, seems like there's one more set up step you need to do before you call that function.

My guess is that the difference comes from the fact that C headers contained the function pointers, which we're not calling into and somehow the wrong function pointers are being initialized. This is not a bug with the vulkan vendor package.

gaultier commented 10 months ago

Thank you, that fixed it!

diff --git a/main.odin b/main.odin
index a30dcb4..c413dcd 100644
--- a/main.odin
+++ b/main.odin
@@ -1,5 +1,6 @@
 package main

+import "core:fmt"
 import "core:os"
 import "vendor:sdl2"
 import "vendor:vulkan"
@@ -13,6 +14,13 @@ main :: proc() {
    if window == nil {
        os.exit(1)
    }
+
+   getInstanceProcAddr := sdl2.Vulkan_GetVkGetInstanceProcAddr()
+   assert(getInstanceProcAddr != nil)
+
+   vulkan.load_proc_addresses_global(getInstanceProcAddr)
+   assert(vulkan.CreateInstance != nil)
+
    app_info: vulkan.ApplicationInfo = {
        sType              = .APPLICATION_INFO,
        applicationVersion = vulkan.MAKE_VERSION(0, 0, 1),
@@ -28,4 +36,5 @@ main :: proc() {
    if r := vulkan.CreateInstance(&create_info, nil, &instance); r != .SUCCESS {
        os.exit(1)
    }
+   fmt.println("instance created")
 }

So the function pointers need to be loaded manually. Not sure if that should be mentioned in the docs?

flysand7 commented 10 months ago

By the way you can use ```diff to highlight diffs in color

Regarding docs, maybe yeah, they kinda suck currently