Put Vulkan in your GTK with VkArea
at main 349 lines 11 kB view raw
1const std = @import("std"); 2const builtin = @import("builtin"); 3 4const gobject = @import("gobject"); 5const gio = @import("gio"); 6const gtk = @import("gtk"); 7const vk = @import("vulkan"); 8 9const VkArea = @import("vk_area.zig").VkArea; 10 11const triangle_spv = @embedFile("triangle_spv"); 12 13const Application = extern struct { 14 parent: Parent, 15 pub const Parent = gtk.Application; 16 17 pub const getGObjectType = gobject.ext.defineClass(Application, .{ 18 .name = "GtkVkTestApplication", 19 .classInit = &Class.init, 20 }); 21 22 pub fn new() *Application { 23 return gobject.ext.newInstance(Application, .{ 24 .application_id = "me.pluie.GtkVkTest", 25 }); 26 } 27 28 pub fn as(app: *Application, comptime T: type) *T { 29 return gobject.ext.as(T, app); 30 } 31 32 fn activateImpl(app: *Application) callconv(.c) void { 33 Window.new(app).as(gtk.Window).present(); 34 } 35 36 pub const Class = extern struct { 37 parent_class: Parent.Class, 38 39 pub const Instance = Application; 40 41 pub fn as(class: *Class, comptime T: type) *T { 42 return gobject.ext.as(T, class); 43 } 44 45 fn init(class: *Class) callconv(.c) void { 46 gio.Application.virtual_methods.activate.implement(class, &Application.activateImpl); 47 } 48 }; 49}; 50 51const Window = extern struct { 52 parent_instance: Parent, 53 pub const Parent = gtk.ApplicationWindow; 54 55 const Private = struct { 56 shaders: vk.ShaderModule, 57 pipeline_layout: vk.PipelineLayout, 58 pipelines: [1]vk.Pipeline, 59 60 var offset: c_int = 0; 61 }; 62 63 pub const getGObjectType = gobject.ext.defineClass(Window, .{ 64 .name = "GtkVkTestWindow", 65 .instanceInit = &init, 66 // .classInit = &Class.init, 67 .parent_class = &Class.parent, 68 .private = .{ .Type = Private, .offset = &Private.offset }, 69 }); 70 71 pub fn new(app: *Application) *Window { 72 return gobject.ext.newInstance(Window, .{ .application = app }); 73 } 74 75 fn init(self: *Window, _: *Class) callconv(.c) void { 76 const area = VkArea.new(); 77 78 // Enable validation layers if we're using debug mode 79 if (comptime builtin.mode == .Debug) { 80 area.addInstanceExtensions(&.{vk.extensions.ext_debug_utils.name}) catch {}; 81 area.addValidationLayers(&.{"VK_LAYER_KHRONOS_validation"}) catch {}; 82 } 83 84 _ = VkArea.signals.setup.connect(area, *Window, onSetup, self, .{}); 85 _ = VkArea.signals.teardown.connect(area, *Window, onTeardown, self, .{}); 86 _ = VkArea.signals.render.connect(area, *Window, onRender, self, .{}); 87 self.as(gtk.Window).setChild(area.as(gtk.Widget)); 88 } 89 90 fn onRender(area: *VkArea, ctx: *const VkArea.RenderContext, self: *Window) callconv(.c) void { 91 _ = area; 92 self.draw(ctx) catch |err| { 93 std.log.err("failed to draw={}", .{err}); 94 }; 95 } 96 fn onSetup(area: *VkArea, ctx: *const VkArea.RenderContext, self: *Window) callconv(.c) void { 97 _ = area; 98 self.setup(ctx) catch |err| { 99 std.log.err("failed to setup={}", .{err}); 100 }; 101 } 102 fn onTeardown(area: *VkArea, ctx: *const VkArea.RenderContext, self: *Window) callconv(.c) void { 103 _ = area; 104 self.teardown(ctx) catch |err| { 105 std.log.err("failed to teardown={}", .{err}); 106 }; 107 } 108 109 fn setup(self: *Window, ctx: *const VkArea.RenderContext) !void { 110 const priv = self.private(); 111 112 // Setup shaders 113 priv.shaders = try ctx.device.createShaderModule(&.{ 114 .code_size = triangle_spv.len, 115 .p_code = @ptrCast(@alignCast(triangle_spv)), 116 }, null); 117 errdefer ctx.device.destroyShaderModule( 118 priv.shaders, 119 null, 120 ); 121 122 const shader_stages = [_]vk.PipelineShaderStageCreateInfo{ 123 .{ 124 .module = priv.shaders, 125 .stage = .{ .vertex_bit = true }, 126 .p_name = "vertMain", 127 }, 128 .{ 129 .module = priv.shaders, 130 .stage = .{ .fragment_bit = true }, 131 .p_name = "fragMain", 132 }, 133 }; 134 135 // Setup pipeline layout 136 priv.pipeline_layout = try ctx.device.createPipelineLayout( 137 &.{}, 138 null, 139 ); 140 errdefer ctx.device.destroyPipelineLayout( 141 priv.pipeline_layout, 142 null, 143 ); 144 145 // Setup pipeline 146 const pipeline_rendering_info: vk.PipelineRenderingCreateInfo = .{ 147 .color_attachment_count = 1, 148 .p_color_attachment_formats = &.{.b8g8r8a8_srgb}, 149 .view_mask = 0, 150 .depth_attachment_format = .undefined, 151 .stencil_attachment_format = .undefined, 152 }; 153 const dynamic_states = [_]vk.DynamicState{ 154 .viewport, 155 .scissor, 156 }; 157 const attachment_states = [_]vk.PipelineColorBlendAttachmentState{.{ 158 .blend_enable = .true, 159 160 .src_color_blend_factor = .src_alpha, 161 .dst_color_blend_factor = .one_minus_src_alpha, 162 .color_blend_op = .add, 163 .src_alpha_blend_factor = .one, 164 .dst_alpha_blend_factor = .zero, 165 .alpha_blend_op = .add, 166 .color_write_mask = .{ 167 .r_bit = true, 168 .g_bit = true, 169 .b_bit = true, 170 .a_bit = true, 171 }, 172 }}; 173 174 const pipeline_info: vk.GraphicsPipelineCreateInfo = .{ 175 .layout = priv.pipeline_layout, 176 .stage_count = shader_stages.len, 177 .p_stages = &shader_stages, 178 .p_dynamic_state = &.{ 179 .dynamic_state_count = dynamic_states.len, 180 .p_dynamic_states = &dynamic_states, 181 }, 182 .p_color_blend_state = &.{ 183 .logic_op_enable = .false, 184 .logic_op = .copy, 185 .blend_constants = @splat(0), 186 .attachment_count = attachment_states.len, 187 .p_attachments = &attachment_states, 188 }, 189 .p_input_assembly_state = &.{ 190 .topology = .triangle_list, 191 .primitive_restart_enable = .false, 192 }, 193 .p_multisample_state = &.{ 194 .rasterization_samples = .{ 195 .@"1_bit" = true, 196 }, 197 .sample_shading_enable = .false, 198 .min_sample_shading = 0, 199 .alpha_to_coverage_enable = .false, 200 .alpha_to_one_enable = .false, 201 }, 202 .p_rasterization_state = &.{ 203 .depth_clamp_enable = .false, 204 .rasterizer_discard_enable = .false, 205 .polygon_mode = .fill, 206 .cull_mode = .{ .back_bit = true }, 207 .front_face = .clockwise, 208 .depth_bias_enable = .false, 209 .depth_bias_constant_factor = 0, 210 .depth_bias_clamp = 0, 211 .depth_bias_slope_factor = 0, 212 .line_width = 1, 213 }, 214 .p_viewport_state = &.{ 215 .viewport_count = 1, 216 .scissor_count = 1, 217 }, 218 .p_vertex_input_state = &.{}, 219 .subpass = 0, 220 .render_pass = .null_handle, 221 .base_pipeline_index = -1, 222 .p_next = @ptrCast(&pipeline_rendering_info), 223 }; 224 225 switch (try ctx.device.createGraphicsPipelines( 226 .null_handle, 227 priv.pipelines.len, 228 &.{pipeline_info}, 229 null, 230 &priv.pipelines, 231 )) { 232 .success => {}, 233 else => return error.FailedToGraphicsPipeline, 234 } 235 errdefer for (priv.pipelines) |p| ctx.device.destroyPipeline( 236 p, 237 null, 238 ); 239 } 240 241 fn teardown(self: *Window, ctx: *const VkArea.RenderContext) !void { 242 const priv = self.private(); 243 244 for (priv.pipelines) |p| ctx.device.destroyPipeline( 245 p, 246 null, 247 ); 248 ctx.device.destroyPipelineLayout( 249 priv.pipeline_layout, 250 null, 251 ); 252 ctx.device.destroyShaderModule( 253 priv.shaders, 254 null, 255 ); 256 } 257 258 fn draw(self: *Window, ctx: *const VkArea.RenderContext) !void { 259 const priv = self.private(); 260 261 var color = [4]f32{ 0.824, 0.216, 0.451, 1.0 }; 262 for (&color) |*ch| ch.* = srgb2Linear(ch.*); 263 264 const color_attachments = [_]vk.RenderingAttachmentInfo{.{ 265 .image_view = ctx.target.view, 266 .image_layout = .color_attachment_optimal, 267 .resolve_mode = .{}, 268 .resolve_image_layout = .color_attachment_optimal, 269 .load_op = .clear, 270 .store_op = .store, 271 .clear_value = .{ 272 .color = .{ .float_32 = color }, 273 }, 274 }}; 275 276 ctx.device.cmdBeginRendering(ctx.cmds, &.{ 277 .render_area = .{ 278 .offset = .{ .x = 0, .y = 0 }, 279 .extent = ctx.extent, 280 }, 281 .layer_count = 1, 282 .view_mask = 0, 283 .color_attachment_count = @intCast(color_attachments.len), 284 .p_color_attachments = &color_attachments, 285 }); 286 defer ctx.device.cmdEndRendering(ctx.cmds); 287 288 ctx.device.cmdBindPipeline( 289 ctx.cmds, 290 .graphics, 291 priv.pipelines[0], 292 ); 293 ctx.device.cmdSetViewport( 294 ctx.cmds, 295 0, 296 1, 297 &.{.{ 298 .x = 0, 299 .y = 0, 300 .width = @floatFromInt(ctx.extent.width / 2), 301 .height = @floatFromInt(ctx.extent.height / 2), 302 .min_depth = 0, 303 .max_depth = 1, 304 }}, 305 ); 306 ctx.device.cmdSetScissor( 307 ctx.cmds, 308 0, 309 1, 310 &.{.{ 311 .offset = .{ .x = 0, .y = 0 }, 312 .extent = ctx.extent, 313 }}, 314 ); 315 ctx.device.cmdDraw( 316 ctx.cmds, 317 3, 318 1, 319 0, 320 0, 321 ); 322 } 323 324 pub fn as(self: *Window, comptime T: type) *T { 325 return gobject.ext.as(T, self); 326 } 327 328 fn private(self: *Window) *Private { 329 return gobject.ext.impl_helpers.getPrivate(self, Private, Private.offset); 330 } 331 pub const Class = extern struct { 332 parent_class: Parent.Class, 333 var parent: *Parent.Class = undefined; 334 pub const Instance = Window; 335 }; 336}; 337 338pub fn main() void { 339 const app = Application.new(); 340 const status = app.as(gio.Application).run( 341 @intCast(std.os.argv.len), 342 std.os.argv.ptr, 343 ); 344 std.process.exit(@intCast(status)); 345} 346 347fn srgb2Linear(v: f32) f32 { 348 return if (v <= 0.04045) v / 12.92 else std.math.pow(f32, (v + 0.055) / 1.055, 2.4); 349}