Vulkan errors when using SDL3

Hello !
I don’t know if it’s coming from a problem with the vendor SDL3 bindings, if I’m doing something wrong or if I should ask the question directly in the SDL forums, but I’m getting errors when running a minimal program. I just followed step by step this tutorial and have almost exactly the same code, I’ve checked it over and over and don’t see any major difference : https://www.youtube.com/watch?v=tfc3vschDVw&t=1961s&pp=ygUJc2RsMyBvZGlu

I’ve tried to google the error that gets spammed in the console, without success, it seems to be at the sdl implementation level, but no one has reported it yet so I must be doing something wrong:

Validation Error: [ VUID-vkQueueSubmit-pSignalSemaphores-00067 ] | MessageID = 0x539277af
vkQueueSubmit(): pSubmits[0].pSignalSemaphores[0] (VkSemaphore 0x370000000037) is being signaled by VkQueue 0x1a84d4d31f0, but it may still be in use by VkSwapchainKHR 0x2f000000002f.
Here are the most recently acquired image indices: 0, 0, 1, 0, 0, [1], 0, 0.
(brackets mark the last use of VkSemaphore 0x370000000037 in a presentation operation)
Swapchain image 1 was presented but was not re-acquired, so VkSemaphore 0x370000000037 may still be in use and cannot be safely reused with image index 0.
Vulkan insight: One solution is to assign each image its own semaphore. This also handles the case where vkAcquireNextImageKHR returns the same index twice in a row. Here are some common methods to ensure that a semaphore passed to vkQueuePresentKHR is not in use and can be safely reused:
        a) Use a separate semaphore per swapchain image. Index these semaphores using the index of the acquired image.
        b) Consider the VK_EXT_swapchain_maintenance1 extension. It allows using a VkFence with the presentation operation.
The Vulkan spec states: Each binary semaphore element of the pSignalSemaphores member of any element of pSubmits must be unsignaled when the semaphore signal operation it defines is executed on the device (https://vulkan.lunarg.com/doc/view/1.4.313.2/windows/antora/spec/latest/chapters/cmdbuffers.html#VUID-vkQueueSubmit-pSignalSemaphores-00067)
Objects: 2
    [0] VkSemaphore 0x370000000037
    [1] VkQueue 0x1a84d4d31f0

Here are a few things I’ve noticed:

  • it seems to appear after a few frames, I suppose something is not cleaned up properly at the end of a frame
  • If I reduce the framerate with SDL_Delay, the problem seems to disappear, until I resize the window, at which point it fires once
  • Despite the errors, the program works as expected

And here is the full code :

package main

import sdl "vendor:sdl3"
import "core:log"
import "base:runtime"

main_context: runtime.Context
v_shader_src := #load("../../shaders/test_shader.spv.vert")
f_shader_src := #load("../../shaders/test_shader.spv.frag")

main :: proc() {
	context.logger = log.create_console_logger()

	main_context = context
	//sdl.SetLogPriorities(.VERBOSE)
	sdl.SetLogOutputFunction(sdl_log, nil)
	sdl_init_ok := sdl.Init(sdl.INIT_VIDEO)
	assert(sdl_init_ok)
	window := sdl.CreateWindow("Ether", 800, 600, sdl.WINDOW_RESIZABLE); assert(window != nil)

	gpu := sdl.CreateGPUDevice({.SPIRV}, true, nil); assert(gpu != nil)

	gpu_bound := sdl.ClaimWindowForGPUDevice(gpu, window); assert(gpu_bound)
	swapchain_params_ok := sdl.SetGPUSwapchainParameters(gpu, window, .SDR_LINEAR, .VSYNC); assert(swapchain_params_ok)

	v_shader := sdl.CreateGPUShader(gpu, {
		code_size = len(v_shader_src),
		code = raw_data(v_shader_src),
		entrypoint = "main",
		format = {.SPIRV},
		stage = .VERTEX,
	});

	f_shader := sdl.CreateGPUShader(gpu, {
		code_size = len(f_shader_src),
		code = raw_data(f_shader_src),
		entrypoint = "main",
		format = {.SPIRV},
		stage = .FRAGMENT,
	});

	pipeline := sdl.CreateGPUGraphicsPipeline(gpu, {
		vertex_shader = v_shader,
		fragment_shader = f_shader,
		primitive_type = .TRIANGLELIST,
		target_info = sdl.GPUGraphicsPipelineTargetInfo {
			num_color_targets = 1,
			color_target_descriptions = &(sdl.GPUColorTargetDescription {
				format = sdl.GetGPUSwapchainTextureFormat(gpu, window),
			})
		},
	})

	ok: bool

	sdl.ReleaseGPUShader(gpu, f_shader)
	sdl.ReleaseGPUShader(gpu, v_shader)

	last_time: u64 = sdl.GetTicks()
	current_time: u64 = sdl.GetTicks()
	main_loop: for {
		event: sdl.Event
		for sdl.PollEvent(&event) {
			#partial switch event.type {
			case .QUIT:
				break main_loop
			case .KEY_DOWN:
				if event.key.scancode == .ESCAPE do break main_loop
			}
		}
		
		current_time = sdl.GetTicks()
		{
			command_buffer := sdl.AcquireGPUCommandBuffer(gpu)
			gpu_tex: ^sdl.GPUTexture = nil
			ok = sdl.WaitAndAcquireGPUSwapchainTexture(command_buffer, window, &gpu_tex, nil, nil); assert(ok)

			if gpu_tex != nil {

				color_target := sdl.GPUColorTargetInfo {
					texture = gpu_tex,
					load_op = .CLEAR,
					clear_color = {0, 0.2, 0.4, 1},
					store_op = .STORE,
				}
				render_pass := sdl.BeginGPURenderPass(command_buffer, &color_target, 1, nil)
				sdl.BindGPUGraphicsPipeline(render_pass, pipeline)
				sdl.DrawGPUPrimitives(render_pass, 3, 1, 0, 0)

				sdl.EndGPURenderPass(render_pass)
			}
			else do log.info("NOT RENDERING")
			ok = sdl.SubmitGPUCommandBuffer(command_buffer); assert(ok)
		}
	}
}

sdl_log :: proc "c" (userdata: rawptr, category: sdl.LogCategory, priority: sdl.LogPriority, message: cstring) {
	context = main_context
}

Any idea why it happens? Thanks in advance for your help!

would you be able to try and render like so:

fence := sdl.SubmitGPUCommandBufferAndAcquireFence(cmd_buffer)
_ = sdl.WaitForGPUFences(ctx.device, true, &fence, 1)
sdl.ReleaseGPUFence(ctx.device, fence)

instead of just submitting the command buffer? this will force the cpu to wait until the current frame is done rendering before rendering another one. i should note, this is not ideal for performance and a proper solution like triple buffering should probably be implemented instead but this should solve the immediate issue