MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
defender_flame.cpp
Go to the documentation of this file.
1#include "defender_window.hpp"
2
4
5#include <algorithm>
6#include <array>
7#include <cstddef>
8#include <cstdint>
9#include <cstring>
10#include <string>
11#include <vector>
12
13namespace defender {
14
15 void DefenderWindow::create_flame_resources() {
16 create_flame_mesh();
17 create_flame_swapchain_resources();
18 }
19
20 void DefenderWindow::cleanup_flame_resources() {
21 cleanup_flame_swapchain_resources();
22 if (flame_vertex_buffer != VK_NULL_HANDLE) {
23 vkDestroyBuffer(device, flame_vertex_buffer, nullptr);
24 flame_vertex_buffer = VK_NULL_HANDLE;
25 }
26 if (flame_vertex_buffer_memory != VK_NULL_HANDLE) {
27 vkFreeMemory(device, flame_vertex_buffer_memory, nullptr);
28 flame_vertex_buffer_memory = VK_NULL_HANDLE;
29 }
30 flame_vertex_count = 0;
31 }
32
33 void DefenderWindow::cleanup_flame_swapchain_resources() {
34 if (flame_pipeline != VK_NULL_HANDLE) {
35 vkDestroyPipeline(device, flame_pipeline, nullptr);
36 flame_pipeline = VK_NULL_HANDLE;
37 }
38 if (flame_pipeline_layout != VK_NULL_HANDLE) {
39 vkDestroyPipelineLayout(device, flame_pipeline_layout, nullptr);
40 flame_pipeline_layout = VK_NULL_HANDLE;
41 }
42 }
43
44 void DefenderWindow::create_flame_swapchain_resources() {
45 if (flame_vertex_count == 0 || device == VK_NULL_HANDLE) {
46 return;
47 }
48 create_flame_pipeline();
49 }
50
51 void DefenderWindow::create_flame_mesh() {
52 constexpr int segments = 40;
53 constexpr float base_z = 0.555f;
54 constexpr float tip_z = 1.18f;
55 constexpr float base_y = 0.040f;
56 constexpr float outer_radius = 0.072f;
57 constexpr float inner_radius = 0.034f;
58
59 std::vector<space::FlameVertex> vertices{};
60 vertices.reserve(static_cast<std::size_t>(segments) * 6U);
61
62 const glm::vec4 outer_base_color{1.0f, 0.42f, 0.08f, 0.62f};
63 const glm::vec4 outer_tip_color{0.7f, 0.08f, 0.0f, 0.0f};
64 const glm::vec4 inner_base_color{1.0f, 0.94f, 0.52f, 0.86f};
65 const glm::vec4 inner_tip_color{1.0f, 0.32f, 0.04f, 0.0f};
66
67 auto add_cone = [&](float radius, const glm::vec4 &base_color, const glm::vec4 &tip_color) {
68 const glm::vec3 tip{0.0f, base_y, tip_z};
69 for (int i = 0; i < segments; ++i) {
70 const float a0 = (static_cast<float>(i) / static_cast<float>(segments)) * 2.0f * space::PI;
71 const float a1 = (static_cast<float>(i + 1) / static_cast<float>(segments)) * 2.0f * space::PI;
72 const glm::vec3 p0{std::cos(a0) * radius, base_y + std::sin(a0) * radius, base_z};
73 const glm::vec3 p1{std::cos(a1) * radius, base_y + std::sin(a1) * radius, base_z};
74 vertices.push_back({p0, base_color});
75 vertices.push_back({p1, base_color});
76 vertices.push_back({tip, tip_color});
77 }
78 };
79
80 add_cone(outer_radius, outer_base_color, outer_tip_color);
81 add_cone(inner_radius, inner_base_color, inner_tip_color);
82
83 flame_vertex_count = static_cast<uint32_t>(vertices.size());
84 const VkDeviceSize buffer_size = sizeof(space::FlameVertex) * static_cast<VkDeviceSize>(vertices.size());
85 create_buffer(buffer_size,
86 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
87 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
88 flame_vertex_buffer,
89 flame_vertex_buffer_memory);
90
91 void *data = nullptr;
92 if (vkMapMemory(device, flame_vertex_buffer_memory, 0, buffer_size, 0, &data) != VK_SUCCESS || data == nullptr) {
93 throw mxvk::Exception("Failed to map defender flame vertex buffer");
94 }
95 std::memcpy(data, vertices.data(), static_cast<std::size_t>(buffer_size));
96 vkUnmapMemory(device, flame_vertex_buffer_memory);
97 }
98
99 void DefenderWindow::create_flame_pipeline() {
100 cleanup_flame_swapchain_resources();
101
102 const std::vector<char> vert_shader_code = loadSpv(asset_root + "/data/flame.vert.spv");
103 const std::vector<char> frag_shader_code = loadSpv(asset_root + "/data/flame.frag.spv");
104
105 VkShaderModule vert_shader_module = createShaderModule(device, vert_shader_code);
106 VkShaderModule frag_shader_module = VK_NULL_HANDLE;
107
108 try {
109 frag_shader_module = createShaderModule(device, frag_shader_code);
110
111 VkPipelineShaderStageCreateInfo vert_stage{};
112 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
113 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
114 vert_stage.module = vert_shader_module;
115 vert_stage.pName = "main";
116
117 VkPipelineShaderStageCreateInfo frag_stage{};
118 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
119 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
120 frag_stage.module = frag_shader_module;
121 frag_stage.pName = "main";
122
123 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {vert_stage, frag_stage};
124
125 VkVertexInputBindingDescription binding_description{};
126 binding_description.binding = 0;
127 binding_description.stride = sizeof(space::FlameVertex);
128 binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
129
130 std::array<VkVertexInputAttributeDescription, 2> attributes{};
131 attributes[0].binding = 0;
132 attributes[0].location = 0;
133 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
134 attributes[0].offset = offsetof(space::FlameVertex, pos);
135 attributes[1].binding = 0;
136 attributes[1].location = 1;
137 attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
138 attributes[1].offset = offsetof(space::FlameVertex, color);
139
140 VkPipelineVertexInputStateCreateInfo vertex_input{};
141 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
142 vertex_input.vertexBindingDescriptionCount = 1;
143 vertex_input.pVertexBindingDescriptions = &binding_description;
144 vertex_input.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
145 vertex_input.pVertexAttributeDescriptions = attributes.data();
146
147 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
148 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
149 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
150 input_assembly.primitiveRestartEnable = VK_FALSE;
151
152 VkPipelineViewportStateCreateInfo viewport_state{};
153 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
154 viewport_state.viewportCount = 1;
155 viewport_state.scissorCount = 1;
156
157 const std::array<VkDynamicState, 2> dynamic_states = {
158 VK_DYNAMIC_STATE_VIEWPORT,
159 VK_DYNAMIC_STATE_SCISSOR,
160 };
161 VkPipelineDynamicStateCreateInfo dynamic_info{};
162 dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
163 dynamic_info.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
164 dynamic_info.pDynamicStates = dynamic_states.data();
165
166 VkPipelineRasterizationStateCreateInfo rasterizer{};
167 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
168 rasterizer.depthClampEnable = VK_FALSE;
169 rasterizer.rasterizerDiscardEnable = VK_FALSE;
170 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
171 rasterizer.lineWidth = 1.0f;
172 rasterizer.cullMode = VK_CULL_MODE_NONE;
173 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
174 rasterizer.depthBiasEnable = VK_FALSE;
175
176 VkPipelineMultisampleStateCreateInfo multisampling{};
177 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
178 multisampling.sampleShadingEnable = VK_FALSE;
179 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
180
181 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
182 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
183 depth_stencil.depthTestEnable = VK_TRUE;
184 depth_stencil.depthWriteEnable = VK_FALSE;
185 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
186 depth_stencil.depthBoundsTestEnable = VK_FALSE;
187 depth_stencil.stencilTestEnable = VK_FALSE;
188
189 VkPipelineColorBlendAttachmentState color_blend_attachment{};
190 color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
191 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
192 color_blend_attachment.blendEnable = VK_TRUE;
193 color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
194 color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
195 color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
196 color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
197 color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
198 color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
199
200 VkPipelineColorBlendStateCreateInfo color_blending{};
201 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
202 color_blending.logicOpEnable = VK_FALSE;
203 color_blending.attachmentCount = 1;
204 color_blending.pAttachments = &color_blend_attachment;
205
206 VkPushConstantRange push_range{};
207 push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
208 push_range.offset = 0;
209 push_range.size = sizeof(space::FlamePushConstants);
210
211 VkPipelineLayoutCreateInfo layout_info{};
212 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
213 layout_info.pushConstantRangeCount = 1;
214 layout_info.pPushConstantRanges = &push_range;
215
216 if (vkCreatePipelineLayout(device, &layout_info, nullptr, &flame_pipeline_layout) != VK_SUCCESS) {
217 throw mxvk::Exception("Failed to create defender flame pipeline layout");
218 }
219
220 const VkFormat color_format = getSwapchainFormat();
221 const VkFormat depth_format = getDepthFormat();
222
223 VkPipelineRenderingCreateInfo rendering_info{};
224 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
225 rendering_info.colorAttachmentCount = 1;
226 rendering_info.pColorAttachmentFormats = &color_format;
227 if (depth_format != VK_FORMAT_UNDEFINED) {
228 rendering_info.depthAttachmentFormat = depth_format;
229 }
230
231 VkGraphicsPipelineCreateInfo pipeline_info{};
232 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
233 pipeline_info.pNext = &rendering_info;
234 pipeline_info.stageCount = static_cast<uint32_t>(shader_stages.size());
235 pipeline_info.pStages = shader_stages.data();
236 pipeline_info.pVertexInputState = &vertex_input;
237 pipeline_info.pInputAssemblyState = &input_assembly;
238 pipeline_info.pViewportState = &viewport_state;
239 pipeline_info.pRasterizationState = &rasterizer;
240 pipeline_info.pMultisampleState = &multisampling;
241 pipeline_info.pDepthStencilState = &depth_stencil;
242 pipeline_info.pColorBlendState = &color_blending;
243 pipeline_info.pDynamicState = &dynamic_info;
244 pipeline_info.layout = flame_pipeline_layout;
245 pipeline_info.renderPass = VK_NULL_HANDLE;
246 pipeline_info.subpass = 0;
247
248 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &flame_pipeline) != VK_SUCCESS) {
249 throw mxvk::Exception("Failed to create defender flame pipeline");
250 }
251 } catch (...) {
252 if (frag_shader_module != VK_NULL_HANDLE) {
253 vkDestroyShaderModule(device, frag_shader_module, nullptr);
254 }
255 vkDestroyShaderModule(device, vert_shader_module, nullptr);
256 cleanup_flame_swapchain_resources();
257 throw;
258 }
259
260 vkDestroyShaderModule(device, frag_shader_module, nullptr);
261 vkDestroyShaderModule(device, vert_shader_module, nullptr);
262 }
263
264 void DefenderWindow::draw_engine_flame(VkCommandBuffer cmd, const VkExtent2D &extent, const glm::mat4 &view, const glm::mat4 &projection) {
265 const bool boost_active = boost_pressed || controller_boost_pressed;
266 const bool propulsion_active = propulsion_pressed || controller_propulsion_pressed || boost_active;
267 if (!propulsion_active || ship.current_speed <= 1.0f || flame_pipeline == VK_NULL_HANDLE || flame_vertex_buffer == VK_NULL_HANDLE || flame_vertex_count == 0) {
268 return;
269 }
270
271 VkViewport viewport{};
272 viewport.x = 0.0f;
273 viewport.y = 0.0f;
274 viewport.width = static_cast<float>(extent.width);
275 viewport.height = static_cast<float>(extent.height);
276 viewport.minDepth = 0.0f;
277 viewport.maxDepth = 1.0f;
278 vkCmdSetViewport(cmd, 0, 1, &viewport);
279
280 VkRect2D scissor{};
281 scissor.offset = {0, 0};
282 scissor.extent = extent;
283 vkCmdSetScissor(cmd, 0, 1, &scissor);
284
285 space::FlamePushConstants pc{};
286 pc.mvp = projection * view * last_ship_model_matrix;
287 pc.params = glm::vec4(elapsed_seconds, std::clamp(ship.current_speed / ship.max_speed, 0.0f, 2.0f), boost_active ? 1.0f : 0.0f, 0.0f);
288
289 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, flame_pipeline);
290 vkCmdPushConstants(cmd,
291 flame_pipeline_layout,
292 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
293 0,
294 sizeof(pc),
295 &pc);
296
297 VkBuffer vertex_buffers[] = {flame_vertex_buffer};
298 VkDeviceSize offsets[] = {0};
299 vkCmdBindVertexBuffers(cmd, 0, 1, vertex_buffers, offsets);
300 vkCmdDraw(cmd, flame_vertex_count, 1, 0, 0);
301 }
302
303 void DefenderWindow::create_buffer(VkDeviceSize size,
304 VkBufferUsageFlags usage,
305 VkMemoryPropertyFlags properties,
306 VkBuffer &buffer,
307 VkDeviceMemory &buffer_memory) const {
308 VkBufferCreateInfo buffer_info{};
309 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
310 buffer_info.size = size;
311 buffer_info.usage = usage;
312 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
313
314 if (vkCreateBuffer(device, &buffer_info, nullptr, &buffer) != VK_SUCCESS) {
315 throw mxvk::Exception("Failed to create defender buffer");
316 }
317
318 VkMemoryRequirements mem_requirements{};
319 vkGetBufferMemoryRequirements(device, buffer, &mem_requirements);
320
321 VkMemoryAllocateInfo alloc_info{};
322 alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
323 alloc_info.allocationSize = mem_requirements.size;
324
325 try {
326 alloc_info.memoryTypeIndex = find_memory_type(mem_requirements.memoryTypeBits, properties);
327 if (vkAllocateMemory(device, &alloc_info, nullptr, &buffer_memory) != VK_SUCCESS) {
328 throw mxvk::Exception("Failed to allocate defender buffer memory");
329 }
330 if (vkBindBufferMemory(device, buffer, buffer_memory, 0) != VK_SUCCESS) {
331 throw mxvk::Exception("Failed to bind defender buffer memory");
332 }
333 } catch (...) {
334 if (buffer_memory != VK_NULL_HANDLE) {
335 vkFreeMemory(device, buffer_memory, nullptr);
336 buffer_memory = VK_NULL_HANDLE;
337 }
338 if (buffer != VK_NULL_HANDLE) {
339 vkDestroyBuffer(device, buffer, nullptr);
340 buffer = VK_NULL_HANDLE;
341 }
342 throw;
343 }
344 }
345
346 [[nodiscard]] uint32_t DefenderWindow::find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags properties) const {
347 VkPhysicalDeviceMemoryProperties mem_properties{};
348 vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
349
350 for (uint32_t i = 0; i < mem_properties.memoryTypeCount; ++i) {
351 if ((type_filter & (1U << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) {
352 return i;
353 }
354 }
355
356 throw mxvk::Exception("Failed to find defender memory type");
357 }
358
359} // namespace defender
VkDevice device
Definition mxvk.hpp:485
VkFormat depth_format
Definition mxvk.hpp:494
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
Definition mxvk.cpp:145
VkPhysicalDevice physical_device
Definition mxvk.hpp:484
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
Definition mxvk.hpp:189
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
Definition mxvk.cpp:141
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:183
constexpr float PI
Single-precision value of pi.
glm::vec4 params
Shader-specific animation parameters.
glm::mat4 mvp
Model-view-projection matrix.