I was upgrading my sdl3 playground from 0.15.0-dev.1254+c9ce1debe to 0.15.1, and now everything works, but I SOMETIMES get Illegal instruction error. The thing is - it’s feels like it’s comepletly random - modifying completly unrelated code seems to either get rid of it or cause it to appear. I can move reading bmp and converting it to surface (loadTextureToSurface) file closer to texture creation in my zig code and error disappears. I can move calling convention of a frag function to a constant in my SHADER and error appears again. I change the order of frag and vert function (shader entry points) and it disappears. What is going on? Can I even trust this stack trace that’s telling me it’s happening in device.createTexture (most of the time stack trace is the same)?
[system:info] App name: SDL Application
[system:info] App version: <unspecified>
[system:info] App ID: <unspecified>
[system:info] SDL revision: SDL3-3.2.20 (https://github.com/castholm/SDL 0.2.6)
[gpu:info] SDL_GPU Driver: Vulkan
[gpu:info] Vulkan Device: NVIDIA GeForce RTX 4080
[gpu:info] Vulkan Driver: NVIDIA 570.153.02
[gpu:info] Vulkan Conformance: 1.4.0
Illegal instruction at address 0x1d4d493
/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/vulkan/SDL_gpu_vulkan.c:10556:67: 0x1d4d493 in VULKAN_Submit (/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/vulkan/SDL_gpu_vulkan.c)
(renderer->claimedWindowCount > 0 && vulkanCommandBuffer->swapchainRequested) ||
^
/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/vulkan/SDL_gpu_vulkan.c:5895:9: 0x1d65c05 in VULKAN_INTERNAL_CreateTexture (/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/vulkan/SDL_gpu_vulkan.c)
VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer);
^
/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/vulkan/SDL_gpu_vulkan.c:6835:15: 0x1d31d24 in VULKAN_CreateTexture (/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/vulkan/SDL_gpu_vulkan.c)
texture = VULKAN_INTERNAL_CreateTexture(
^
/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/SDL_gpu.c:1300:12: 0x1429913 in SDL_CreateGPUTexture_REAL (/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/gpu/SDL_gpu.c)
return device->CreateTexture(
^
/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/dynapi/SDL_dynapi_procs.h:138:1: 0x1383889 in SDL_CreateGPUTexture (/home/maxcross/.cache/zig/p/sdl-0.2.6+3.2.20-7uIn9NgjfwHH5a6HhyLHat2nHU3OP5B05QHhKJKuxEex/src/dynapi/SDL_dynapi.c)
SDL_DYNAPI_PROC(SDL_GPUTexture*,SDL_CreateGPUTexture,(SDL_GPUDevice *a, const SDL_GPUTextureCreateInfo *b),(a,b),return)
^
/home/maxcross/.cache/zig/p/sdl3-0.1.1-NmT1QwTgIADixpCS2g5pBBON5AEfnjOox94dU0dtT-tq/src/gpu.zig:2114:87: 0x12b2419 in createTexture (sdl3.zig)
.value = try errors.wrapCallNull(*c.SDL_GPUTexture, c.SDL_CreateGPUTexture(
^
/home/maxcross/projects/zig/zig-sdl3/src/main2.zig:89:45: 0x12b6539 in main (main2.zig)
const texture = try device.createTexture(.{
^
/home/maxcross/.cache/zig/p/N-V-__8AAN5NhBR0oTsvnwjPdeNiiDLtEsfXRHd1fv-R3TOv/lib/std/start.zig:627:37: 0x12bce71 in main (std.zig)
const result = root.main() catch |err| {
^
???:?:?: 0x7f006142a47d in ??? (libc.so.6)
Unwind information for `libc.so.6:0x7f006142a47d` was not available, trace may be incomplete
zig code:
const std = @import("std");
const sdl = @import("sdl3");
const zm = @import("zmath");
const ext = @import("ext.zig");
pub fn main() !void {
sdl.errors.error_callback = &ext.sdlErr;
sdl.log.setAllPriorities(.info);
sdl.log.setLogOutputFunction(anyopaque, ext.sdlLog, null);
// const shader = @import("shaders/textured_quad.shader.zig");
// std.debug.print("hasDescl: {}\n", .{@hasDecl(shader, "vertex")});
defer sdl.shutdown();
const init_flags: sdl.InitFlags = .{ .video = true, .gamepad = true };
try sdl.init(init_flags);
defer sdl.quit(init_flags);
const device = try sdl.gpu.Device.init(.{ .spirv = true }, false, null);
defer device.deinit();
const window = try sdl.video.Window.init("Hello, there!", 640, 480, .{});
defer window.deinit();
try device.claimWindow(window);
defer device.releaseWindow(window);
const vertex_shader = try ext.createShader(device, "textured_quad.shader", "vert", .vertex, 0, 1, 0, 0);
defer device.releaseShader(vertex_shader);
const fragment_shader = try ext.createShader(device, "textured_quad.shader", "frag", .fragment, 1, 0, 0, 0);
defer device.releaseShader(fragment_shader);
// const vertex_shader = try ext.createShader(device, "textured_quad.vert", "main", .vertex, 0, 1, 0, 0);
// defer device.releaseShader(vertex_shader);
// const fragment_shader = try ext.createShader(device, "textured_quad.frag", "main", .fragment, 1, 0, 0, 0);
// defer device.releaseShader(fragment_shader);
const sampler = try device.createSampler(.{
.min_filter = .nearest,
.mag_filter = .nearest,
.mipmap_mode = .nearest,
.address_mode_u = .clamp_to_edge,
.address_mode_v = .clamp_to_edge,
.address_mode_w = .clamp_to_edge,
});
defer device.releaseSampler(sampler);
const VertexBufferData = packed struct {
position: @Vector(3, f32),
uv: @Vector(2, f32),
};
const pipeline = try device.createGraphicsPipeline(.{
.target_info = .{ .color_target_descriptions = &.{.{ .format = device.getSwapchainTextureFormat(window) }} },
.vertex_input_state = .{
.vertex_buffer_descriptions = &ext.vertexBufferDescriptionsForVertexBufferObjects(&.{VertexBufferData}, 0),
.vertex_attributes = &ext.attributesForVertextBufferObjects(&.{VertexBufferData}, 0, 0),
},
.vertex_shader = vertex_shader,
.fragment_shader = fragment_shader,
});
defer device.releaseGraphicsPipeline(pipeline);
const vertex_data = [_]VertexBufferData{
.{ .position = .{ -1, 1, 0 }, .uv = .{ 0, 0 } },
.{ .position = .{ 1, 1, 0 }, .uv = .{ 1, 0 } },
.{ .position = .{ 1, -1, 0 }, .uv = .{ 1, 1 } },
.{ .position = .{ -1, -1, 0 }, .uv = .{ 0, 1 } },
};
const vertex_data_size: u32 = @intCast(@sizeOf(@TypeOf(vertex_data)));
const vertex_buffer = try device.createBuffer(.{
.size = vertex_data_size,
.usage = .{ .vertex = true },
.props = .{ .name = "Raviolli buffer" },
});
defer device.releaseBuffer(vertex_buffer);
const index_data = [_]u16{ 0, 1, 2, 0, 2, 3 };
const index_data_size: u32 = @intCast(@sizeOf(@TypeOf(index_data)));
const index_buffer = try device.createBuffer(.{
.size = index_data_size,
.usage = .{ .index = true },
.props = .{ .name = "Raviolli index buffer" },
});
defer device.releaseBuffer(index_buffer);
const surface = try ext.loadTextureToSurface("src/images/ravioli.bmp");
defer surface.deinit();
const texture = try device.createTexture(.{
.texture_type = .two_dimensional,
.format = .r8g8b8a8_unorm,
.width = @intCast(surface.getWidth()),
.height = @intCast(surface.getHeight()),
.layer_count_or_depth = 1,
.num_levels = 1,
.usage = .{ .sampler = true },
.props = .{ .name = "Raviolli texture" },
});
defer device.releaseTexture(texture);
const transfer_buffer = try device.createTransferBuffer(.{ .size = vertex_data_size + index_data_size, .usage = .upload });
defer device.releaseTransferBuffer(transfer_buffer);
{
defer device.unmapTransferBuffer(transfer_buffer);
const transfer_buffer_mapped = try device.mapTransferBuffer(transfer_buffer, false);
const transfer_buffer_vertex_data: *[vertex_data.len]VertexBufferData = @ptrFromInt(@intFromPtr(transfer_buffer_mapped));
transfer_buffer_vertex_data.* = vertex_data;
const transfer_buffer_index_data: *[index_data.len]u16 = @ptrFromInt(@intFromPtr(transfer_buffer_mapped) + vertex_data_size);
transfer_buffer_index_data.* = index_data;
}
const pixels = surface.getPixels() orelse return error.NoPixels;
const texture_transfer_buffer = try device.createTransferBuffer(.{ .usage = .upload, .size = @intCast(pixels.len) });
defer device.releaseTransferBuffer(texture_transfer_buffer);
{
defer device.unmapTransferBuffer(texture_transfer_buffer);
const texture_transfer_buffer_mapped = try device.mapTransferBuffer(texture_transfer_buffer, false);
const pixels_mapped: [*]u8 = @ptrCast(@alignCast(texture_transfer_buffer_mapped));
@memcpy(pixels_mapped, pixels);
}
const upload_command_buffer = try device.acquireCommandBuffer();
{
const copy_pass = upload_command_buffer.beginCopyPass();
defer copy_pass.end();
copy_pass.uploadToBuffer(
.{ .transfer_buffer = transfer_buffer, .offset = 0 },
.{ .buffer = vertex_buffer, .offset = 0, .size = vertex_data_size },
false,
);
copy_pass.uploadToBuffer(
.{ .transfer_buffer = transfer_buffer, .offset = vertex_data_size },
.{ .buffer = index_buffer, .offset = 0, .size = index_data_size },
false,
);
copy_pass.uploadToTexture(
.{ .transfer_buffer = texture_transfer_buffer, .offset = 0 },
.{ .texture = texture, .width = @intCast(surface.getWidth()), .height = @intCast(surface.getHeight()), .depth = 1 },
false,
);
}
try upload_command_buffer.submit();
// std.debug.print("111111111\n", .{});
var ns: u64 = 0;
var pos = zm.f32x4(0, 0, 1, 0);
var rot: f32 = 0;
const keyboard = ext.KeyboardState.init();
main_loop: while (true) {
const ns_current = sdl.timer.getNanosecondsSinceInit();
defer ns = ns_current;
const dt_ns: f32 = @floatFromInt(ns_current - ns);
const dt = dt_ns / std.time.ns_per_s;
while (sdl.events.poll()) |event| {
switch (event) {
.quit, .terminating => break :main_loop,
.key_down => |key_down| if (key_down.key) |key| switch (key) {
.escape => break :main_loop,
else => {},
},
else => {},
}
}
if (keyboard.isKeyDown(.w)) pos += zm.f32x4(0, 0, dt, 0);
if (keyboard.isKeyDown(.s)) pos += zm.f32x4(0, 0, -dt, 0);
if (keyboard.isKeyDown(.a)) pos += zm.f32x4(-dt, 0, 0, 0);
if (keyboard.isKeyDown(.d)) pos += zm.f32x4(dt, 0, 0, 0);
if (keyboard.isKeyDown(.left_shift)) pos += zm.f32x4(0, dt, 0, 0);
if (keyboard.isKeyDown(.left_ctrl)) pos += zm.f32x4(0, -dt, 0, 0);
rot += dt;
const object_to_world = zm.mul(zm.rotationY(rot), zm.translationV(pos));
var world_to_clip = zm.perspectiveFovRh(90.0 * std.math.rad_per_deg, 640.0 / 480.0, 0.1, 10);
world_to_clip[2][3] = 1; // invert z axis, so +z becomes away from screen
const object_to_clip = zm.mul(object_to_world, world_to_clip);
const projection = object_to_clip;
const UBO = extern struct {
// offset: @Vector(3, f32),
offset: zm.F32x4,
projection: zm.Mat,
};
const ubo: UBO = .{
.offset = .{ 0.5, -0.25, 0, 0 },
.projection = projection,
};
//draw
const draw_command_buffer = try device.acquireCommandBuffer();
const arr: [@bitSizeOf(UBO) / 8]u8 = @bitCast(ubo);
draw_command_buffer.pushVertexUniformData(0, &arr);
const maybe_swapchain_texture = try draw_command_buffer.waitAndAcquireSwapchainTexture(window);
if (maybe_swapchain_texture.texture) |swapchain_texture| {
const render_pass = draw_command_buffer.beginRenderPass(&.{
.{
.texture = swapchain_texture,
.load = .clear,
.clear_color = .{ .a = 1 },
},
}, null);
defer render_pass.end();
render_pass.bindGraphicsPipeline(pipeline);
render_pass.bindVertexBuffers(0, &.{.{ .buffer = vertex_buffer, .offset = 0 }});
render_pass.bindIndexBuffer(.{ .buffer = index_buffer, .offset = 0 }, .indices_16bit);
render_pass.bindFragmentSamplers(0, &.{.{ .texture = texture, .sampler = sampler }});
render_pass.drawIndexedPrimitives(6, 1, 0, 0, 0);
}
try draw_command_buffer.submit();
}
}
//ext.zig
const std = @import("std");
const sdl = @import("sdl3");
pub fn sdlErr(
err: ?[]const u8,
) void {
if (err) |val| {
std.debug.print("******* [Error! {s}] *******\n", .{val});
} else {
std.debug.print("******* [Unknown Error!] *******\n", .{});
}
}
pub fn sdlLog(
user_data: ?*anyopaque,
category: ?sdl.log.Category,
priority: ?sdl.log.Priority,
message: [:0]const u8,
) void {
_ = user_data;
const category_str: ?[]const u8 = if (category) |val| @tagName(val) else null;
const priority_str: [:0]const u8 = if (priority) |val| @tagName(val) else "unknown";
if (category_str) |val| {
std.debug.print("[{s}:{s}] {s}\n", .{ val, priority_str, message });
} else {
std.debug.print("[Custom_{?}:{s}] {s}\n", .{ category, priority_str, message });
}
}
pub inline fn createShader(
device: sdl.gpu.Device,
comptime name: [:0]const u8,
comptime entry_point: [:0]const u8,
stage: sdl.gpu.ShaderStage,
num_samplers: u32,
num_uniform_buffers: u32,
num_storage_buffers: u32,
num_storage_textures: u32,
) !sdl.gpu.Shader {
return try device.createShader(.{
.code = @embedFile(name),
.stage = stage,
// .entry_point = switch (stage) {
// .vertex => "vert",
// .fragment => "frag",
// },
// .entry_point = "main",
.entry_point = entry_point,
.format = .{ .spirv = true },
.num_samplers = num_samplers,
.num_uniform_buffers = num_uniform_buffers,
.num_storage_buffers = num_storage_buffers,
.num_storage_textures = num_storage_textures,
.props = .{ .name = name },
});
}
pub inline fn loadTextureToSurface(path: [:0]const u8) !sdl.surface.Surface {
const image_surface_raw = try sdl.surface.Surface.initFromBmpFile(path);
defer image_surface_raw.deinit();
const image_surface = try image_surface_raw.convertFormat(.packed_abgr_8_8_8_8);
errdefer image_surface.deinit();
return image_surface;
}
pub inline fn vertexBufferDescriptionsForVertexBufferObjects(
comptime vertex_buffer_objects: []const type,
slots_start: u32,
) [vertex_buffer_objects.len]sdl.gpu.VertexBufferDescription {
var result: [vertex_buffer_objects.len]sdl.gpu.VertexBufferDescription = undefined;
inline for (vertex_buffer_objects, slots_start..) |VBO, slot| {
result[slot] = .{
.pitch = @sizeOf(VBO),
.input_rate = if (@hasDecl(VBO, "input_rate")) VBO.input_rate else .vertex,
.slot = slot,
.instance_step_rate = 0,
};
}
return result;
}
pub inline fn attributesForVertextBufferObjects(
comptime vertex_buffer_objects: []const type,
buffer_slot_start: u32,
location_start: u32,
) [attributesLen(vertex_buffer_objects)]sdl.gpu.VertexAttribute {
var result: [attributesLen(vertex_buffer_objects)]sdl.gpu.VertexAttribute = undefined;
var location = location_start;
inline for (vertex_buffer_objects, buffer_slot_start..) |VBO, buffer_slot| {
const attributes = attributesForVertextBufferObject(VBO, buffer_slot, location);
result[location..][0..attributes.len].* = attributes;
location += attributes.len;
}
return result;
}
fn attributesLen(comptime vertex_buffer_objects: []const type) usize {
var result: usize = 0;
inline for (vertex_buffer_objects) |VBO| {
result += @typeInfo(VBO).@"struct".fields.len;
}
return result;
}
pub inline fn attributesForVertextBufferObject(
comptime VertexBufferObject: type,
buffer_slot: u32,
location_start: u32,
) [@typeInfo(VertexBufferObject).@"struct".fields.len]sdl.gpu.VertexAttribute {
const fields = @typeInfo(VertexBufferObject).@"struct".fields;
var result: [@typeInfo(VertexBufferObject).@"struct".fields.len]sdl.gpu.VertexAttribute = undefined;
inline for (fields, 0..) |field, i| {
result[i] = .{
.buffer_slot = buffer_slot,
.location = location_start + @as(u32, i),
.offset = @offsetOf(VertexBufferObject, field.name),
.format = switch (field.type) {
u32 => .u32x1,
@Vector(1, u32) => .u32x1,
@Vector(2, u32) => .u32x2,
@Vector(3, u32) => .u32x3,
@Vector(4, u32) => .u32x4,
i32 => .i32x1,
@Vector(1, i32) => .i32x1,
@Vector(2, i32) => .i32x2,
@Vector(3, i32) => .i32x3,
@Vector(4, i32) => .i32x4,
f32 => .f32x1,
@Vector(1, f32) => .f32x1,
@Vector(2, f32) => .f32x2,
@Vector(3, f32) => .f32x3,
@Vector(4, f32) => .f32x4,
@Vector(2, i8) => .i8x2,
@Vector(4, i8) => .i8x4,
@Vector(2, u8) => .u8x2,
@Vector(4, u8) => .u8x4,
@Vector(2, i16) => .i16x2,
@Vector(4, i16) => .i16x4,
@Vector(2, u16) => .u16x2,
@Vector(4, u16) => .u16x4,
@Vector(2, f16) => .f16x2,
@Vector(4, f16) => .f16x4,
else => @compileError("Unsupported type"),
},
};
}
return result;
}
pub const KeyboardState = struct {
state: []const bool,
pub fn init() @This() {
return .{ .state = sdl.keyboard.getState() };
}
pub inline fn isKeyDown(self: @This(), scancode: sdl.Scancode) bool {
return self.state[@intFromEnum(scancode)];
}
};