diff --git a/assets/shaders/gui_box.frag b/assets/shaders/gui_box.frag new file mode 100644 index 0000000..7217a25 --- /dev/null +++ b/assets/shaders/gui_box.frag @@ -0,0 +1,39 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require + +in Varyings { + layout(location = 0) flat uint instance; + layout(location = 1) vec2 positionSSPX; +} var; + +#include "includes/gui_box_common.glsl" + +layout(location = 0) out vec4 fragColor; + +float boxSDF(vec2 p, vec2 center, vec2 halfExtents, float borderRadius) { + vec2 q = abs(p - center) - halfExtents + borderRadius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - borderRadius; +} + +#define BOX _Boxes[var.instance] + +void main() { + vec2 halfExtentsPX = 0.5 * BOX.sizePX; + vec2 centerSSPX = BOX.positionSSPX + halfExtentsPX; + float borderHalfWidthPX = 0.5 * BOX.borderWidthPX; + + float interiorSDF = boxSDF(var.positionSSPX, centerSSPX, halfExtentsPX, BOX.borderRadiusPX) + BOX.borderWidthPX; + float borderSDF = abs(interiorSDF - borderHalfWidthPX) - borderHalfWidthPX; + + float interiorCoverage = clamp(-interiorSDF + 0.5, 0.0, 1.0) * BOX.backgroundColor.a; + float borderCoverage = clamp(-borderSDF + 0.5, 0.0, 1.0) * BOX.borderColor.a; + float totalCoverage = interiorCoverage + borderCoverage; + + fragColor = vec4( + interiorCoverage * BOX.backgroundColor.rgb + + borderCoverage * BOX.borderColor.rgb, + totalCoverage + ); +} diff --git a/assets/shaders/gui_box.vert b/assets/shaders/gui_box.vert new file mode 100644 index 0000000..5a6ff46 --- /dev/null +++ b/assets/shaders/gui_box.vert @@ -0,0 +1,30 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require + +out Varyings { + layout(location = 0) flat uint instance; + layout(location = 1) vec2 positionSSPX; +} var; + +#include "includes/gui_box_common.glsl" + +const vec2 VERTICES[4] = vec2[]( + vec2(0, 1), + vec2(1, 1), + vec2(0, 0), + vec2(1, 0) +); + +#define BOX _Boxes[gl_InstanceIndex] +#define VERTEX VERTICES[gl_VertexIndex] + +void main() { + vec2 positionSSPX = VERTEX * BOX.sizePX + BOX.positionSSPX; + vec4 positionCS = vec4(_Global.matrixSSPXtoCS * vec3(positionSSPX, 1.0), 0.0, 1.0); + + gl_Position = positionCS; + var.instance = gl_InstanceIndex; + var.positionSSPX = positionSSPX; +} diff --git a/assets/shaders/gui_image.frag b/assets/shaders/gui_image.frag new file mode 100644 index 0000000..1cc108a --- /dev/null +++ b/assets/shaders/gui_image.frag @@ -0,0 +1,20 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require + +in Varyings { + layout(location = 0) flat uint instance; + layout(location = 1) vec2 texCoord; +} var; + +#include "includes/gui_image_common.glsl" + +layout(location = 0) out vec4 fragColor; + +#define IMAGE _Images[var.instance] + +void main() { + vec4 texel = texture(sampler2D(_Textures[uint(IMAGE.textureId)], _Sampler), var.texCoord); + fragColor = texel * IMAGE.tint; +} diff --git a/assets/shaders/gui_image.vert b/assets/shaders/gui_image.vert new file mode 100644 index 0000000..3e4329f --- /dev/null +++ b/assets/shaders/gui_image.vert @@ -0,0 +1,32 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require + +out Varyings { + layout(location = 0) flat uint instance; + layout(location = 1) vec2 texCoord; +} var; + +#include "includes/gui_image_common.glsl" + +const vec2 VERTICES[4] = vec2[]( + vec2(0, 1), + vec2(1, 1), + vec2(0, 0), + vec2(1, 0) +); + +#define IMAGE _Images[gl_InstanceIndex] +#define VERTEX VERTICES[gl_VertexIndex] + +void main() { + vec2 positionSSPX = VERTEX * IMAGE.sizePX + IMAGE.positionSSPX; + vec4 positionCS = vec4(_Global.matrixSSPXtoCS * vec3(positionSSPX, 1.0), 0.0, 1.0); + + vec2 texCoord = VERTEX * (IMAGE.uvMax - IMAGE.uvMin) + IMAGE.uvMin; + + gl_Position = positionCS; + var.instance = gl_InstanceIndex; + var.texCoord = texCoord; +} diff --git a/assets/shaders/gui_text.frag b/assets/shaders/gui_text.frag new file mode 100644 index 0000000..710dd1d --- /dev/null +++ b/assets/shaders/gui_text.frag @@ -0,0 +1,15 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require + +uint calcRootCode(float y1, float y2, float y3) { + uint i1 = floatBitsToUint(y1) >> 31U; + uint i2 = floatBitsToUint(y2) >> 30U; + uint i3 = floatBitsToUint(y3) >> 29U; + + uint shift = (i2 & 2U) | (i1 & ~2U); + shift = (i3 & 4U) | (shift & ~4U); + + return ((0x2E74U >> shift) & 0x0101U); +} diff --git a/assets/shaders/gui_text.vert b/assets/shaders/gui_text.vert new file mode 100644 index 0000000..92bcc45 --- /dev/null +++ b/assets/shaders/gui_text.vert @@ -0,0 +1,10 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require + +layout(location = 0) in vec4 positionOS_normalOS; +layout(location = 1) in vec4 texCoordEM_band_flags; +layout(location = 2) in vec4 jacobian; +layout(location = 3) in vec4 scale_offset; +layout(location = 4) in vec4 color; diff --git a/assets/shaders/includes/global_uniforms.glsl b/assets/shaders/includes/global_uniforms.glsl new file mode 100644 index 0000000..c73f940 --- /dev/null +++ b/assets/shaders/includes/global_uniforms.glsl @@ -0,0 +1,6 @@ +layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { + mat4 matrixWStoVS; + mat4 matrixVStoCS; + mat3x2 matrixSSPXtoCS; + vec3 ambientLight; +} _Global; diff --git a/assets/shaders/includes/gui_box_common.glsl b/assets/shaders/includes/gui_box_common.glsl new file mode 100644 index 0000000..1becd0b --- /dev/null +++ b/assets/shaders/includes/gui_box_common.glsl @@ -0,0 +1,14 @@ +struct Box { + vec4 backgroundColor; + vec4 borderColor; + vec2 positionSSPX; + vec2 sizePX; + float borderWidthPX; + float borderRadiusPX; +}; + +#include "global_uniforms.glsl" + +layout(set = 0, binding = 1, scalar) readonly buffer Boxes { + Box _Boxes[]; +}; diff --git a/assets/shaders/includes/gui_image_common.glsl b/assets/shaders/includes/gui_image_common.glsl new file mode 100644 index 0000000..7842858 --- /dev/null +++ b/assets/shaders/includes/gui_image_common.glsl @@ -0,0 +1,17 @@ +struct Image { + vec4 tint; + vec2 positionSSPX; + vec2 sizePX; + vec2 uvMin; + vec2 uvMax; + uint16_t textureId; +}; + +#include "global_uniforms.glsl" + +layout(set = 0, binding = 1, scalar) readonly buffer Images { + Image _Images[]; +}; + +layout(set = 0, binding = 2) uniform sampler _Sampler; +layout(set = 0, binding = 3) uniform texture2D _Textures[]; diff --git a/assets/shaders/main_common.glsl b/assets/shaders/includes/main_common.glsl similarity index 90% rename from assets/shaders/main_common.glsl rename to assets/shaders/includes/main_common.glsl index 767f674..2042f2c 100644 --- a/assets/shaders/main_common.glsl +++ b/assets/shaders/includes/main_common.glsl @@ -32,11 +32,7 @@ struct ObjectUniforms { uint16_t material; }; -layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { - mat4 matrixWStoVS; - mat4 matrixVStoCS; - vec3 ambientLight; -} _Global; +#include "global_uniforms.glsl" layout(set = 0, binding = 1, scalar) readonly buffer PointLights { uint count; diff --git a/assets/shaders/includes/tone_mapping.glsl b/assets/shaders/includes/tone_mapping.glsl new file mode 100644 index 0000000..43e9f55 --- /dev/null +++ b/assets/shaders/includes/tone_mapping.glsl @@ -0,0 +1,8 @@ +vec3 toneMapAcesNarkowicz(vec3 color) { + const float A = 2.51; + const float B = 0.03; + const float C = 2.43; + const float D = 0.59; + const float E = 0.14; + return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0); +} diff --git a/assets/shaders/main.frag b/assets/shaders/main.frag index 800e554..b6303e9 100644 --- a/assets/shaders/main.frag +++ b/assets/shaders/main.frag @@ -12,7 +12,8 @@ in Varyings { layout(location = 5) vec3 bitangentVS; } var; -#include "main_common.glsl" +#include "includes/main_common.glsl" +#include "includes/tone_mapping.glsl" layout(location = 0) out vec4 fragColor; @@ -39,15 +40,6 @@ float distributionGGX(float dotNH, float alpha) { return alphaSquared * INV_PI / (tmp * tmp); } -vec3 toneMapAcesNarkowicz(vec3 color) { - const float A = 2.51; - const float B = 0.03; - const float C = 2.43; - const float D = 0.59; - const float E = 0.14; - return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0); -} - vec3 lightOutgoingRadiance( vec3 viewDirectionVS, vec3 normalVS, float dotNV, vec3 baseColor, float alpha, float metallic, vec3 f0, diff --git a/assets/shaders/main.vert b/assets/shaders/main.vert index 918e8e1..f041cb7 100644 --- a/assets/shaders/main.vert +++ b/assets/shaders/main.vert @@ -17,7 +17,7 @@ out Varyings { layout(location = 5) vec3 bitangentVS; } var; -#include "main_common.glsl" +#include "includes/main_common.glsl" #define OBJECT _Object[gl_InstanceIndex] diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag index bd9bf02..d306458 100644 --- a/assets/shaders/skybox.frag +++ b/assets/shaders/skybox.frag @@ -9,14 +9,7 @@ layout(set = 0, binding = 2) uniform textureCube _Texture; layout(location = 0) out vec4 fragColor; -vec3 toneMapAcesNarkowicz(vec3 color) { - const float A = 2.51; - const float B = 0.03; - const float C = 2.43; - const float D = 0.59; - const float E = 0.14; - return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0); -} +#include "includes/tone_mapping.glsl" void main() { vec4 texel = texture(samplerCube(_Texture, _Sampler), var.texCoord); diff --git a/assets/shaders/skybox.vert b/assets/shaders/skybox.vert index 87635f8..b829a65 100644 --- a/assets/shaders/skybox.vert +++ b/assets/shaders/skybox.vert @@ -7,11 +7,7 @@ out Varyings { layout(location = 0) vec3 texCoord; } var; -layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { - mat4 matrixWStoVS; - mat4 matrixVStoCS; - vec3 ambientLight; -} _Global; +#include "includes/global_uniforms.glsl" void main() { vec3 directionVS = (_Global.matrixWStoVS * vec4(directionWS, 0.0)).xyz; diff --git a/build.zig b/build.zig index 817a4db..e8d6229 100644 --- a/build.zig +++ b/build.zig @@ -10,11 +10,22 @@ pub fn build(b: *std.Build) !void { const media_dep = b.dependency("media", .{ .target = target, }); + const vecmath_dep = b.dependency("vecmath", .{ .target = target, }); - const vulkan_dep = b.dependency("vulkan_zig", .{ .registry = b.path("vendor/vk.xml") }); - const zglfw_dep = b.dependency("zglfw", .{ .import_vulkan = true }); + + const vulkan_dep = b.dependency("vulkan_zig", .{ + .target = target, + .optimize = optimize, + .registry = b.path("vendor/vk.xml"), + }); + + const zglfw_dep = b.dependency("zglfw", .{ + .target = target, + .optimize = optimize, + .import_vulkan = true, + }); const media_mod = media_dep.module("media"); const vecmath_mod = vecmath_dep.module("vecmath"); diff --git a/compile_shaders.sh b/compile_shaders.sh index 6f0dd06..e66d597 100644 --- a/compile_shaders.sh +++ b/compile_shaders.sh @@ -1,7 +1,9 @@ #!/bin/sh -glslc --target-env=vulkan1.2 assets/shaders/equirect_to_cube.comp -o src/shaders/equirect_to_cube_comp.spv -glslc --target-env=vulkan1.2 assets/shaders/main.frag -o src/shaders/main_frag.spv -glslc --target-env=vulkan1.2 assets/shaders/main.vert -o src/shaders/main_vert.spv -glslc --target-env=vulkan1.2 assets/shaders/skybox.frag -o src/shaders/skybox_frag.spv -glslc --target-env=vulkan1.2 assets/shaders/skybox.vert -o src/shaders/skybox_vert.spv +glslc -g --target-env=vulkan1.2 assets/shaders/equirect_to_cube.comp -o src/shaders/equirect_to_cube_comp.spv +glslc -g --target-env=vulkan1.2 assets/shaders/gui_box.frag -o src/shaders/gui_box_frag.spv +glslc -g --target-env=vulkan1.2 assets/shaders/gui_box.vert -o src/shaders/gui_box_vert.spv +glslc -g --target-env=vulkan1.2 assets/shaders/main.frag -o src/shaders/main_frag.spv +glslc -g --target-env=vulkan1.2 assets/shaders/main.vert -o src/shaders/main_vert.spv +glslc -g --target-env=vulkan1.2 assets/shaders/skybox.frag -o src/shaders/skybox_frag.spv +glslc -g --target-env=vulkan1.2 assets/shaders/skybox.vert -o src/shaders/skybox_vert.spv diff --git a/src/Game.zig b/src/Game.zig index 0ce709e..ac5a684 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -15,6 +15,7 @@ const Chunk = @import("assets/Chunk.zig"); const Chunks = @import("Chunks.zig"); const CommandBuffer = @import("engine/CommandBuffer.zig"); const Engine = @import("engine/Engine.zig"); +const Gui = @import("Gui.zig"); const Iterator2 = math.Iterator2; const Materials = @import("assets/Materials.zig"); const Player = @import("Player.zig"); @@ -51,6 +52,7 @@ materials: Materials, textures: Textures, chunks: Chunks, skybox: Skybox, +gui: Gui, player: Player, @@ -164,7 +166,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain }, }); errdefer engine.destroyPipelineLayout(pipeline_layout); - engine.setObjectName(pipeline_layout, "PL", .{}); + engine.setObjectName(pipeline_layout, "PL Main", .{}); const vertex_shader = try engine.createShaderModule(.{ .code = &shaders.main_vert_spv }); defer engine.destroyShaderModule(vertex_shader); @@ -403,7 +405,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain .subpass = 0, }); errdefer engine.destroyPipeline(pipeline); - engine.setObjectName(pipeline, "P", .{}); + engine.setObjectName(pipeline, "P Main", .{}); const descriptor_pool = try engine.createDescriptorPool(.{ .flags = .{ @@ -430,7 +432,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain }, }); errdefer engine.destroyDescriptorPool(descriptor_pool); - engine.setObjectName(descriptor_pool, "DP", .{}); + engine.setObjectName(descriptor_pool, "DP Main", .{}); const global_descriptor_set = try engine.allocateDescriptorSet(.{ .descriptor_pool = descriptor_pool, @@ -645,6 +647,14 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain ); errdefer skybox.deinit(engine); + var gui = try Gui.init( + allocator, + engine, + global_uniforms.buffer, + swapchain.render_pass, + ); + errdefer gui.deinit(engine, allocator); + return .{ .allocator = allocator, .io = io, @@ -674,6 +684,7 @@ pub fn init(allocator: std.mem.Allocator, io: std.Io, engine: *Engine, swapchain .textures = textures, .chunks = .{ .chunks = chunks }, .skybox = skybox, + .gui = gui, .player = .init(player_position_sv, 0, 0), }; @@ -692,6 +703,7 @@ pub fn deinit(self: *Game) void { self.chunks.deinit(self.engine, self.descriptor_pool, self.allocator); self.skybox.deinit(self.engine); + self.gui.deinit(self.engine, self.allocator); self.global_uniforms.deinit(self.engine); self.global_uniforms_staging_buffer.deinit(self.engine); @@ -718,8 +730,36 @@ pub fn deinit(self: *Game) void { } pub fn update(self: *Game, dt: f32) void { + self.gui.beginFrame(); self.player.update(dt, &self.chunks); + const extent = self.swapchain.extent; + const framebuffer_size = vm.Vector2.init( + @floatFromInt(extent.width), + @floatFromInt(extent.height), + ); + + const crosshair_half_extent_px = 8; + const crosshair_half_width_px = 1; + + self.gui.pushBox(.{ + .background_color = .init(1, 1, 1, 0.5), + .border_color = .init(0, 0, 0, 1), + .position_sspx = framebuffer_size.mulScalar(0.5).add(.init(-crosshair_half_extent_px, -crosshair_half_width_px)), + .size_px = .init(2.0 * crosshair_half_extent_px, 2.0 * crosshair_half_width_px), + .border_width_px = 0, + .border_radius_px = 0, + }); + + self.gui.pushBox(.{ + .background_color = .init(1, 1, 1, 0.5), + .border_color = .init(0, 0, 0, 1), + .position_sspx = framebuffer_size.mulScalar(0.5).add(.init(-crosshair_half_width_px, -crosshair_half_extent_px)), + .size_px = .init(2.0 * crosshair_half_width_px, 2.0 * crosshair_half_extent_px), + .border_width_px = 0, + .border_radius_px = 0, + }); + self.render() catch |err| { std.log.err("Failed to render: {s}", .{@errorName(err)}); @panic("Frame update failed"); @@ -807,11 +847,20 @@ fn render(self: *Game) !void { ); // zig fmt: on + // zig fmt: off + const matrix_sspx_to_cs = vm.Matrix3x2.init( + 2.0 / framebuffer_size.x, 0, + 0, 2.0 / framebuffer_size.y, + -1, -1, + ); + // zig fmt: on + const ambient_light = vm.Vector3.init(0.01, 0.01, 0.01); const global_uniforms_data: shaders.GlobalUniforms = .{ .matrixWStoVS = matrix_ws_to_vs, .matrixVStoCS = matrix_vs_to_cs, + .matrixSSPXtoCS = matrix_sspx_to_cs, .ambientLight = ambient_light, }; @@ -887,6 +936,8 @@ fn render(self: *Game) !void { }, }); + // --- SUBPASS 0 (MAIN) --- + command_buffer.bindPipeline(.graphics, self.pipeline); try command_buffer.bindVertexBuffers(0, &.{ .{ @@ -902,8 +953,13 @@ fn render(self: *Game) !void { chunk.draw(self.pipeline_layout, command_buffer); } - try self.skybox.bind(command_buffer, extent); - self.skybox.draw(command_buffer); + try self.skybox.draw(command_buffer); + + // --- SUBPASS 1 (GUI) --- + + command_buffer.nextSubpass(.@"inline"); + + try self.gui.draw(self.engine, command_buffer); } try command_buffer.endCommandBuffer(); diff --git a/src/Gui.zig b/src/Gui.zig new file mode 100644 index 0000000..30743e6 --- /dev/null +++ b/src/Gui.zig @@ -0,0 +1,427 @@ +const Gui = @This(); +const std = @import("std"); + +const shaders = @import("shaders.zig"); +const vk = @import("vulkan"); +const vm = @import("vecmath"); + +const CommandBuffer = @import("engine/CommandBuffer.zig"); +const Engine = @import("engine/Engine.zig"); +const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer; +const Swapchain = @import("engine/Swapchain.zig"); +const Textures = @import("assets/Textures.zig"); + +pub const Draw = struct { + pub const Box = extern struct { + background_color: vm.Vector4, + border_color: vm.Vector4, + position_sspx: vm.Vector2, + size_px: vm.Vector2, + border_width_px: f32, + border_radius_px: f32, + }; + + pub const Text = extern struct { + color: vm.Vector4, + pos_min: vm.Vector2, + pos_max: vm.Vector2, + uv_min: vm.Vector2, + uv_max: vm.Vector2, + atlas_id: Textures.Id, + }; + + pub const Image = extern struct { + tint: vm.Vector4, + position_sspx: vm.Vector2, + size_px: vm.Vector2, + uv_min: vm.Vector2, + uv_max: vm.Vector2, + texture_id: Textures.Id, + }; +}; + +pub const Batch = struct { + draw_type: std.meta.DeclEnum(Draw), + first_instance: u32, + instance_count: u32, +}; + +box_gpu_buffer: GenericBuffer(void, Draw.Box), +text_gpu_buffer: GenericBuffer(void, Draw.Text), +image_gpu_buffer: GenericBuffer(void, Draw.Image), + +box_cpu_buffer: std.ArrayList(Draw.Box), +text_cpu_buffer: std.ArrayList(Draw.Text), +image_cpu_buffer: std.ArrayList(Draw.Image), + +batches: std.ArrayList(Batch), + +sampler: vk.Sampler, +index_buffer: shaders.IndexBuffer, + +box_descriptor_set_layout: vk.DescriptorSetLayout, +box_pipeline_layout: vk.PipelineLayout, +box_pipeline: vk.Pipeline, + +descriptor_pool: vk.DescriptorPool, +box_descriptor_set: vk.DescriptorSet, + +pub const max_box_draws = 1024; +pub const max_image_draws = 1024; +pub const max_text_draws = 4096; + +pub const max_batches = 1024; + +pub fn init( + allocator: std.mem.Allocator, + engine: *Engine, + global_uniforms_buffer: vk.Buffer, + render_pass: vk.RenderPass, +) !Gui { + var box_gpu_buffer: GenericBuffer(void, Draw.Box) = try .init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = max_box_draws, + .name = "GUI box draws", + }); + errdefer box_gpu_buffer.deinit(engine); + + var text_gpu_buffer: GenericBuffer(void, Draw.Text) = try .init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = max_text_draws, + .name = "GUI text draws", + }); + errdefer text_gpu_buffer.deinit(engine); + + var image_gpu_buffer: GenericBuffer(void, Draw.Image) = try .init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = max_image_draws, + .name = "GUI image draws", + }); + errdefer image_gpu_buffer.deinit(engine); + + var box_cpu_buffer: std.ArrayList(Draw.Box) = try .initCapacity(allocator, max_box_draws); + errdefer box_cpu_buffer.deinit(allocator); + + var text_cpu_buffer: std.ArrayList(Draw.Text) = try .initCapacity(allocator, max_text_draws); + errdefer text_cpu_buffer.deinit(allocator); + + var image_cpu_buffer: std.ArrayList(Draw.Image) = try .initCapacity(allocator, max_image_draws); + errdefer image_cpu_buffer.deinit(allocator); + + var batches: std.ArrayList(Batch) = try .initCapacity(allocator, max_batches); + errdefer batches.deinit(allocator); + + const sampler = try engine.createSampler(.{ + .mag_filter = .linear, + .min_filter = .linear, + .mipmap_mode = .linear, + .address_mode_u = .repeat, + .address_mode_v = .repeat, + .address_mode_w = .repeat, + .mip_lod_bias = 0, + .anisotropy_enable = .false, + .max_anisotropy = 0, + .compare_enable = .false, + .compare_op = .always, + .min_lod = 0, + .max_lod = vk.LOD_CLAMP_NONE, + .border_color = .float_transparent_black, + .unnormalized_coordinates = .false, + }); + errdefer engine.destroySampler(sampler); + + const box_descriptor_set_layout = try engine.createDescriptorSetLayout(.{ + .bindings = &.{ + .{ + .binding = 0, + .descriptor_type = .uniform_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, + .{ + .binding = 1, + .descriptor_type = .storage_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, + }, + }); + errdefer engine.destroyDescriptorSetLayout(box_descriptor_set_layout); + engine.setObjectName(box_descriptor_set_layout, "DSL GUI Box", .{}); + + const box_pipeline_layout = try engine.createPipelineLayout(.{ + .set_layouts = &.{ + box_descriptor_set_layout, + }, + }); + errdefer engine.destroyPipelineLayout(box_pipeline_layout); + engine.setObjectName(box_pipeline_layout, "PL GUI Box", .{}); + + const box_vertex_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_vert_spv }); + defer engine.destroyShaderModule(box_vertex_shader); + engine.setObjectName(box_vertex_shader, "SM gui_box_vert", .{}); + + const box_fragment_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_frag_spv }); + defer engine.destroyShaderModule(box_fragment_shader); + engine.setObjectName(box_fragment_shader, "SM gui_box_frag", .{}); + + var index_buffer = try shaders.IndexBuffer.init(engine, .{ + .usage = .index, + .target_queue = .graphics, + .array_capacity = 6, + .name = "QuadIB", + }); + errdefer index_buffer.deinit(engine); + try index_buffer.write(engine, .{ + .elements = &.{ 0, 1, 2, 2, 1, 3 }, + }); + + const box_pipeline = try engine.createGraphicsPipeline(.{ + .stages = &.{ + .{ + .stage = .{ .vertex_bit = true }, + .module = box_vertex_shader, + .name = "main", + }, + .{ + .stage = .{ .fragment_bit = true }, + .module = box_fragment_shader, + .name = "main", + }, + }, + .vertex_input_state = .{}, + .input_assembly_state = .{ + .topology = .triangle_list, + .primitive_restart_enable = .false, + }, + .viewport_state = .{ + .viewports = &.{undefined}, // dynamic + .scissors = &.{undefined}, // dynamic + }, + .rasterization_state = .{ + .depth_clamp_enable = .false, + .rasterizer_discard_enable = .false, + .polygon_mode = .fill, + .cull_mode = .{}, + .front_face = .counter_clockwise, + .depth_bias_enable = .false, + .depth_bias_constant_factor = 0, + .depth_bias_clamp = 0, + .depth_bias_slope_factor = 0, + .line_width = 1, + }, + .multisample_state = .{ + .rasterization_samples = .{ .@"1_bit" = true }, + .sample_shading_enable = .false, + .min_sample_shading = 1, + .alpha_to_coverage_enable = .false, + .alpha_to_one_enable = .false, + }, + .color_blend_state = .{ + .logic_op_enable = .false, + .logic_op = .copy, + .attachments = &.{ + .{ + .blend_enable = .true, + .src_color_blend_factor = .one, + .dst_color_blend_factor = .one_minus_src_alpha, + .color_blend_op = .add, + .src_alpha_blend_factor = .one, + .dst_alpha_blend_factor = .one_minus_src_alpha, + .alpha_blend_op = .add, + .color_write_mask = .{ + .r_bit = true, + .g_bit = true, + .b_bit = true, + .a_bit = true, + }, + }, + }, + .blend_constants = .{ 0, 0, 0, 0 }, + }, + .dynamic_state = .{ + .dynamic_states = &.{ .viewport, .scissor }, + }, + .layout = box_pipeline_layout, + .render_pass = render_pass, + .subpass = 1, + }); + errdefer engine.destroyPipeline(box_pipeline); + engine.setObjectName(box_pipeline, "P GUI Box", .{}); + + const descriptor_pool = try engine.createDescriptorPool(.{ + .max_sets = 1, + .pool_sizes = &.{ + .{ + .type = .uniform_buffer, + .descriptor_count = 1, + }, + .{ + .type = .storage_buffer, + .descriptor_count = 1, + }, + }, + }); + errdefer engine.destroyDescriptorPool(descriptor_pool); + engine.setObjectName(descriptor_pool, "DP GUI", .{}); + + const box_descriptor_set = try engine.allocateDescriptorSet(.{ + .descriptor_pool = descriptor_pool, + .set_layout = box_descriptor_set_layout, + }); + engine.setObjectName(box_descriptor_set, "DS GUI Box", .{}); + + try engine.updateDescriptorSets(.{ + .writes = &.{ + .{ + .dst_set = box_descriptor_set, + .dst_binding = 0, + .dst_array_element = 0, + .descriptor_type = .uniform_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = global_uniforms_buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + .{ + .dst_set = box_descriptor_set, + .dst_binding = 1, + .dst_array_element = 0, + .descriptor_type = .storage_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = box_gpu_buffer.buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + }, + }); + + return .{ + .box_gpu_buffer = box_gpu_buffer, + .text_gpu_buffer = text_gpu_buffer, + .image_gpu_buffer = image_gpu_buffer, + + .box_cpu_buffer = box_cpu_buffer, + .text_cpu_buffer = text_cpu_buffer, + .image_cpu_buffer = image_cpu_buffer, + + .batches = batches, + + .sampler = sampler, + .index_buffer = index_buffer, + + .box_descriptor_set_layout = box_descriptor_set_layout, + .box_pipeline_layout = box_pipeline_layout, + .box_pipeline = box_pipeline, + + .descriptor_pool = descriptor_pool, + .box_descriptor_set = box_descriptor_set, + }; +} + +pub fn deinit(self: *Gui, engine: *Engine, allocator: std.mem.Allocator) void { + std.log.scoped(.deinit).debug("Deinitializing {*} with {*} and Allocator{{{*},{*}}}", .{ self, engine, allocator.ptr, allocator.vtable }); + + engine.destroyDescriptorPool(self.descriptor_pool); + engine.destroyPipeline(self.box_pipeline); + self.index_buffer.deinit(engine); + engine.destroyPipelineLayout(self.box_pipeline_layout); + engine.destroyDescriptorSetLayout(self.box_descriptor_set_layout); + engine.destroySampler(self.sampler); + + self.batches.deinit(allocator); + + self.image_cpu_buffer.deinit(allocator); + self.text_cpu_buffer.deinit(allocator); + self.box_cpu_buffer.deinit(allocator); + + self.image_gpu_buffer.deinit(engine); + self.text_gpu_buffer.deinit(engine); + self.box_gpu_buffer.deinit(engine); + + self.* = undefined; +} + +pub fn beginFrame(self: *Gui) void { + self.box_cpu_buffer.clearRetainingCapacity(); + self.text_cpu_buffer.clearRetainingCapacity(); + self.image_cpu_buffer.clearRetainingCapacity(); + + self.batches.clearRetainingCapacity(); +} + +pub fn pushBox(self: *Gui, box: Draw.Box) void { + const instance: u32 = @intCast(self.box_cpu_buffer.items.len); + self.box_cpu_buffer.appendBounded(box) catch return; + self.extendOrAddBatch(.Box, instance); +} + +pub fn pushText(self: *Gui, text: Draw.Text) void { + const instance: u32 = @intCast(self.text_cpu_buffer.items.len); + self.text_cpu_buffer.appendBounded(text) catch return; + self.extendOrAddBatch(.Text, instance); +} + +pub fn pushImage(self: *Gui, image: Draw.Image) void { + const instance: u32 = @intCast(self.image_cpu_buffer.items.len); + self.image_cpu_buffer.appendBounded(image) catch return; + self.extendOrAddBatch(.Image, instance); +} + +fn extendOrAddBatch(self: *Gui, draw_type: std.meta.DeclEnum(Draw), instance: u32) void { + if (self.batches.items.len == 0 or self.batches.items[self.batches.items.len - 1].draw_type != draw_type) { + self.batches.appendBounded(.{ + .draw_type = draw_type, + .first_instance = instance, + .instance_count = 1, + }) catch return; + } else { + const batch = &self.batches.items[self.batches.items.len - 1]; + std.debug.assert(batch.first_instance + batch.instance_count == instance); + batch.instance_count += 1; + } +} + +pub fn draw(self: *const Gui, engine: *Engine, command_buffer: CommandBuffer) !void { + command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); + + try self.box_gpu_buffer.write(engine, .{ .elements = self.box_cpu_buffer.items }); + try self.text_gpu_buffer.write(engine, .{ .elements = self.text_cpu_buffer.items }); + try self.image_gpu_buffer.write(engine, .{ .elements = self.image_cpu_buffer.items }); + + for (self.batches.items) |batch| { + switch (batch.draw_type) { + .Box => { + command_buffer.bindPipeline(.graphics, self.box_pipeline); + command_buffer.bindDescriptorSet(.graphics, self.box_pipeline_layout, 0, self.box_descriptor_set, null); + }, + .Text => { + std.log.warn("GUI Text not implemented; skipping batch", .{}); + continue; + }, + .Image => { + std.log.warn("GUI Image not implemented; skipping batch", .{}); + continue; + }, + } + + command_buffer.drawIndexed(.{ + .index_count = 6, + .first_instance = batch.first_instance, + .instance_count = batch.instance_count, + }); + } +} diff --git a/src/engine/CommandBuffer.zig b/src/engine/CommandBuffer.zig index a1df051..8b5c09c 100644 --- a/src/engine/CommandBuffer.zig +++ b/src/engine/CommandBuffer.zig @@ -186,6 +186,10 @@ pub fn copyBufferToImage( self.proxy.copyBufferToImage(src_buffer, dst_image, dst_image_layout, regions); } +pub fn nextSubpass(self: CommandBuffer, contents: vk.SubpassContents) void { + self.proxy.nextSubpass(contents); +} + pub fn pipelineBarrier(self: CommandBuffer, barrier: PipelineBarrier) void { self.proxy.pipelineBarrier( barrier.src_stage_mask, diff --git a/src/engine/Skybox.zig b/src/engine/Skybox.zig index dc994db..f8e6bec 100644 --- a/src/engine/Skybox.zig +++ b/src/engine/Skybox.zig @@ -771,24 +771,7 @@ pub fn deinit(self: *Skybox, engine: *Engine) void { engine.destroyImage(self.image); } -pub fn bind(self: *const Skybox, command_buffer: CommandBuffer, extent: vk.Extent2D) !void { - command_buffer.setViewport(0, &.{ - .{ - .x = 0, - .y = 0, - .width = @floatFromInt(extent.width), - .height = @floatFromInt(extent.height), - .min_depth = 0, - .max_depth = 1, - }, - }); - command_buffer.setScissor(0, &.{ - .{ - .offset = .{ .x = 0, .y = 0 }, - .extent = extent, - }, - }); - command_buffer.bindPipeline(.graphics, self.pipeline); +pub fn draw(self: *const Skybox, command_buffer: CommandBuffer) !void { try command_buffer.bindVertexBuffers(0, &.{ .{ .buffer = self.vertex_buffer.buffer, @@ -796,9 +779,8 @@ pub fn bind(self: *const Skybox, command_buffer: CommandBuffer, extent: vk.Exten }, }); command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); -} -pub fn draw(self: *const Skybox, command_buffer: CommandBuffer) void { + command_buffer.bindPipeline(.graphics, self.pipeline); command_buffer.bindDescriptorSet(.graphics, self.pipeline_layout, 0, self.descriptor_set, null); command_buffer.drawIndexed(.{ .index_count = 36 }); } diff --git a/src/engine/Swapchain.zig b/src/engine/Swapchain.zig index 3e6d098..112de43 100644 --- a/src/engine/Swapchain.zig +++ b/src/engine/Swapchain.zig @@ -52,6 +52,7 @@ pub fn init(engine: *Engine) !Swapchain { }, }, .subpasses = &.{ + // Main .{ .pipeline_bind_point = .graphics, .color_attachments = &.{ @@ -65,6 +66,16 @@ pub fn init(engine: *Engine) !Swapchain { .layout = .depth_stencil_attachment_optimal, }, }, + // GUI + .{ + .pipeline_bind_point = .graphics, + .color_attachments = &.{ + .{ + .attachment = 0, + .layout = .color_attachment_optimal, + }, + }, + }, }, .dependencies = &.{ .{ @@ -89,6 +100,25 @@ pub fn init(engine: *Engine) !Swapchain { .by_region_bit = true, }, }, + .{ + .src_subpass = 0, + .dst_subpass = 1, + .src_stage_mask = .{ + .color_attachment_output_bit = true, + }, + .dst_stage_mask = .{ + .color_attachment_output_bit = true, + }, + .src_access_mask = .{ + .color_attachment_write_bit = true, + }, + .dst_access_mask = .{ + .color_attachment_write_bit = true, + }, + .dependency_flags = .{ + .by_region_bit = true, + }, + }, }, }); errdefer engine.destroyRenderPass(render_pass); @@ -129,6 +159,8 @@ pub fn deinit(self: *Swapchain, engine: *Engine) void { } pub fn recreate(self: *Swapchain, engine: *Engine) !void { + try engine.deviceWaitIdle(); // TODO LMAO + const mode = &engine.mode.surface; const allocator = engine.vk_allocator.allocator; diff --git a/src/engine/VkAllocator.zig b/src/engine/VkAllocator.zig index d8bedab..eefa1dc 100644 --- a/src/engine/VkAllocator.zig +++ b/src/engine/VkAllocator.zig @@ -132,7 +132,7 @@ fn freeFunction( const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?)); if (maybe_p_memory) |p_memory| { - self.mutex.lockUncancelable(); + self.mutex.lockUncancelable(self.io); defer self.mutex.unlock(self.io); const size = self.allocations.fetchRemove(p_memory).?.value; diff --git a/src/shaders.zig b/src/shaders.zig index a55e3db..c0fb894 100644 --- a/src/shaders.zig +++ b/src/shaders.zig @@ -46,6 +46,7 @@ pub const Index = u16; pub const GlobalUniforms = extern struct { matrixWStoVS: vm.Matrix4x4, matrixVStoCS: vm.Matrix4x4, + matrixSSPXtoCS: vm.Matrix3x2, ambientLight: vm.Vector3, }; @@ -96,6 +97,8 @@ pub const ObjectUniforms = extern struct { }; pub const equirect_to_cube_comp_spv align(4) = @embedFile("shaders/equirect_to_cube_comp.spv").*; +pub const gui_box_vert_spv align(4) = @embedFile("shaders/gui_box_vert.spv").*; +pub const gui_box_frag_spv align(4) = @embedFile("shaders/gui_box_frag.spv").*; pub const main_vert_spv align(4) = @embedFile("shaders/main_vert.spv").*; pub const main_frag_spv align(4) = @embedFile("shaders/main_frag.spv").*; pub const skybox_vert_spv align(4) = @embedFile("shaders/skybox_vert.spv").*;