const std = @import("std"); const sdl = @import("sdl"); const GameError = @import("game.zig").GameError; window: *sdl.Window, renderer: *sdl.Renderer, device: *sdl.GPUDevice, /// Only available while drawing command_buffer: ?*sdl.GPUCommandBuffer, shader_vert: *sdl.GPUShader, shader_frag: *sdl.GPUShader, vertex_buffer: *sdl.GPUBuffer, depth_texture: *sdl.GPUTexture, msaa_resolve: *sdl.GPUTexture, pipeline: *sdl.GPUGraphicsPipeline, to_resize: ?struct { u32, u32 } = null, const Self = @This(); pub fn create() GameError!Self { // Init if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) return GameError.SdlError; // Window and Renderer var renderer: ?*sdl.Renderer = null; var window: ?*sdl.Window = null; if (!sdl.CreateWindowAndRenderer( "Spacefarer", 1600, 900, sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE, &window, &renderer, )) return GameError.SdlError; errdefer sdl.DestroyRenderer(renderer); errdefer sdl.DestroyWindow(window); if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) return GameError.SdlError; // Device const device = sdl.CreateGPUDevice( sdl.GPU_SHADERFORMAT_SPIRV, @import("builtin").mode == .Debug, null, ) orelse return GameError.SdlError; errdefer sdl.DestroyGPUDevice(device); // Claim if (!sdl.ClaimWindowForGPUDevice(device, window)) return GameError.SdlError; errdefer sdl.ReleaseWindowFromGPUDevice(device, window); const shader_vert = try load_shader( device, "data/shaders/basic.vert", sdl.GPU_SHADERSTAGE_VERTEX, ); errdefer sdl.ReleaseGPUShader(device, shader_vert); const shader_frag = try load_shader( device, "data/shaders/basic.frag", sdl.GPU_SHADERSTAGE_FRAGMENT, ); errdefer sdl.ReleaseGPUShader(device, shader_frag); const vertex_buffer = sdl.CreateGPUBuffer(device, &.{ .usage = sdl.GPU_BUFFERUSAGE_VERTEX, // 6 Vertices * 3 Coordinates * 4 Bytes .size = 6 * 3 * 4, }) orelse return GameError.SdlError; errdefer sdl.ReleaseGPUBuffer(device, vertex_buffer); const transfer_buffer = sdl.CreateGPUTransferBuffer(device, &.{ .size = 6 * 3 * 4, .usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD, }) orelse return GameError.SdlError; defer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer); { // Filling up transfer buffer const mapped_buffer: [*c]f32 = @alignCast(@ptrCast(sdl.MapGPUTransferBuffer(device, transfer_buffer, false) orelse return GameError.SdlError)); defer sdl.UnmapGPUTransferBuffer(device, transfer_buffer); std.mem.copyForwards(f32, mapped_buffer[0 .. 6 * 3], &[6 * 3]f32{ // Triangle 1 -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, -1.0, 1.0, // Triangle 2 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, -1.0, 1.0, 1.0, }); } { // Copying data over from transfer buffer to vertex buffer const command_buffer = sdl.AcquireGPUCommandBuffer(device) orelse return GameError.SdlError; const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError; sdl.UploadToGPUBuffer(copy_pass, &.{ .transfer_buffer = transfer_buffer }, &.{ .size = 6 * 3 * 4, .buffer = vertex_buffer, }, false); sdl.EndGPUCopyPass(copy_pass); if (!sdl.SubmitGPUCommandBuffer(command_buffer)) return GameError.SdlError; } const target_format = sdl.GetGPUSwapchainTextureFormat(device, window); if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) return GameError.SdlError; // TODO: Clean var window_width: c_int = 1; var window_height: c_int = 1; if (!sdl.GetWindowSizeInPixels(window, &window_width, &window_height)) return GameError.SdlError; const depth_texture = try create_depth_texture(device, @intCast(window_width), @intCast(window_height)); errdefer sdl.ReleaseGPUTexture(device, depth_texture); const msaa_resolve = try create_texture(device, @intCast(window_width), @intCast(window_height), target_format); errdefer sdl.ReleaseGPUTexture(device, msaa_resolve); const pipeline = sdl.CreateGPUGraphicsPipeline(device, &.{ .vertex_shader = shader_vert, .fragment_shader = shader_frag, .vertex_input_state = .{ .vertex_buffer_descriptions = &.{ .slot = 0, // 3 Coordinates * 4 Bytes .pitch = 3 * 4, .input_rate = sdl.GPU_VERTEXINPUTRATE_VERTEX, }, .num_vertex_buffers = 1, .vertex_attributes = &sdl.GPUVertexAttribute{ .offset = 0, .location = 0, .format = sdl.GPU_VERTEXELEMENTFORMAT_FLOAT3, .buffer_slot = 0, }, .num_vertex_attributes = 1, }, .primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST, .rasterizer_state = .{ .cull_mode = sdl.GPU_CULLMODE_BACK, .fill_mode = sdl.GPU_FILLMODE_FILL, .front_face = sdl.GPU_FRONTFACE_CLOCKWISE, }, .multisample_state = .{ .sample_count = sdl.GPU_SAMPLECOUNT_4, }, .depth_stencil_state = .{ .compare_op = sdl.GPU_COMPAREOP_LESS, .enable_depth_test = true, .enable_depth_write = true, }, .target_info = .{ .depth_stencil_format = sdl.GPU_TEXTUREFORMAT_D16_UNORM, .color_target_descriptions = &sdl.GPUColorTargetDescription{ .format = target_format, .blend_state = .{ .enable_blend = true, .alpha_blend_op = sdl.GPU_BLENDOP_ADD, .color_blend_op = sdl.GPU_BLENDOP_ADD, .color_write_mask = sdl.GPU_COLORCOMPONENT_R | sdl.GPU_COLORCOMPONENT_G | sdl.GPU_COLORCOMPONENT_B | sdl.GPU_COLORCOMPONENT_A, .src_alpha_blendfactor = sdl.GPU_BLENDFACTOR_ONE, .src_color_blendfactor = sdl.GPU_BLENDFACTOR_SRC_ALPHA, .dst_alpha_blendfactor = sdl.GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, .dst_color_blendfactor = sdl.GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, }, }, .num_color_targets = 1, .has_depth_stencil_target = true, }, }) orelse return GameError.SdlError; errdefer sdl.ReleaseGPUGraphicsPipeline(pipeline); return .{ .window = window.?, .renderer = renderer.?, .device = device, .command_buffer = null, .shader_vert = shader_vert, .shader_frag = shader_frag, .vertex_buffer = vertex_buffer, .depth_texture = depth_texture, .msaa_resolve = msaa_resolve, .pipeline = pipeline, }; } pub fn destroy(self: *Self) void { sdl.ReleaseWindowFromGPUDevice(self.device, self.window); sdl.DestroyRenderer(self.renderer); sdl.DestroyWindow(self.window); sdl.ReleaseGPUGraphicsPipeline(self.device, self.pipeline); sdl.ReleaseGPUTexture(self.device, self.msaa_resolve); sdl.ReleaseGPUTexture(self.device, self.depth_texture); sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer); sdl.ReleaseGPUShader(self.device, self.shader_vert); sdl.ReleaseGPUShader(self.device, self.shader_frag); if (self.command_buffer != null) { _ = sdl.CancelGPUCommandBuffer(self.command_buffer); self.command_buffer = null; } sdl.DestroyGPUDevice(self.device); } pub fn begin_draw(self: *Self) GameError!void { self.command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; if (self.to_resize) |new_size| { try self.reset_textures(new_size[0], new_size[1]); self.to_resize = null; } } pub fn draw_debug(self: *Self) GameError!void { var render_target: ?*sdl.GPUTexture = null; var width: u32 = 0; var height: u32 = 0; if (!sdl.WaitAndAcquireGPUSwapchainTexture(self.command_buffer, self.window, &render_target, &width, &height)) return GameError.SdlError; // Hidden if (render_target == null) return; const render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{ .clear_color = .{ .r = 0.0, .g = 0.0, .b = 1.0, .a = 1.0 }, .cycle = false, .load_op = sdl.GPU_LOADOP_CLEAR, .store_op = sdl.GPU_STOREOP_RESOLVE, // .store_op = sdl.GPU_STOREOP_STORE, .resolve_texture = render_target, .mip_level = 0, .texture = self.msaa_resolve, }, 1, &.{ .clear_depth = 1.0, .load_op = sdl.GPU_LOADOP_CLEAR, .store_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_store_op = sdl.GPU_STOREOP_DONT_CARE, .texture = self.depth_texture, }) orelse return GameError.SdlError; sdl.BindGPUGraphicsPipeline(render_pass, self.pipeline); sdl.BindGPUVertexBuffers(render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1); sdl.DrawGPUPrimitives(render_pass, 6, 1, 0, 0); sdl.EndGPURenderPass(render_pass); } pub fn end_draw(self: *Self) GameError!void { defer self.command_buffer = null; if (!sdl.SubmitGPUCommandBuffer(self.command_buffer)) return GameError.SdlError; } fn load_shader(device: *sdl.GPUDevice, path: []const u8, stage: c_uint) GameError!*sdl.GPUShader { const file = std.fs.cwd().openFile(path, .{}) catch return GameError.OSError; defer file.close(); const code = file.readToEndAllocOptions(std.heap.c_allocator, 1024 * 1024 * 1024, null, @alignOf(u8), 0) catch return GameError.OSError; defer std.heap.c_allocator.free(code); return sdl.CreateGPUShader(device, &.{ .code = code, .code_size = code.len, .entrypoint = "main", .format = sdl.GPU_SHADERFORMAT_SPIRV, .stage = stage, }) orelse return GameError.SdlError; } fn create_depth_texture(device: *sdl.GPUDevice, width: u32, height: u32) GameError!*sdl.GPUTexture { return sdl.CreateGPUTexture(device, &.{ .format = sdl.GPU_TEXTUREFORMAT_D16_UNORM, .layer_count_or_depth = 1, .width = width, .height = height, .num_levels = 1, .sample_count = sdl.GPU_SAMPLECOUNT_4, .usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, }) orelse return GameError.SdlError; } fn create_texture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint) GameError!*sdl.GPUTexture { return sdl.CreateGPUTexture(device, &.{ .format = format, .layer_count_or_depth = 1, .width = width, .height = height, .num_levels = 1, .sample_count = sdl.GPU_SAMPLECOUNT_4, .usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, }) orelse return GameError.SdlError; } fn reset_textures(self: *Self, width: u32, height: u32) GameError!void { sdl.ReleaseGPUTexture(self.device, self.depth_texture); self.depth_texture = try create_depth_texture(self.device, width, height); const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window); sdl.ReleaseGPUTexture(self.device, self.msaa_resolve); self.msaa_resolve = try create_texture(self.device, width, height, target_format); } pub fn resize(self: *Self, width: u32, height: u32) void { self.to_resize = .{ width, height }; }