MXVK Vulkan Framework 0.35.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, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, flame_vertex_buffer, flame_vertex_buffer_memory);
86
87 void *data = nullptr;
88 if (vkMapMemory(device, flame_vertex_buffer_memory, 0, buffer_size, 0, &data) != VK_SUCCESS || data == nullptr) {
89 throw mxvk::Exception("Failed to map defender flame vertex buffer");
90 }
91 std::memcpy(data, vertices.data(), static_cast<std::size_t>(buffer_size));
92 vkUnmapMemory(device, flame_vertex_buffer_memory);
93 }
94
95 void DefenderWindow::create_flame_pipeline() {
96 cleanup_flame_swapchain_resources();
97
98 const std::vector<char> vert_shader_code = loadSpv(asset_root + "/data/flame.vert.spv");
99 const std::vector<char> frag_shader_code = loadSpv(asset_root + "/data/flame.frag.spv");
100
101 VkShaderModule vert_shader_module = createShaderModule(device, vert_shader_code);
102 VkShaderModule frag_shader_module = VK_NULL_HANDLE;
103
104 try {
105 frag_shader_module = createShaderModule(device, frag_shader_code);
106
107 VkPipelineShaderStageCreateInfo vert_stage{};
108 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
109 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
110 vert_stage.module = vert_shader_module;
111 vert_stage.pName = "main";
112
113 VkPipelineShaderStageCreateInfo frag_stage{};
114 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
115 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
116 frag_stage.module = frag_shader_module;
117 frag_stage.pName = "main";
118
119 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {vert_stage, frag_stage};
120
121 VkVertexInputBindingDescription binding_description{};
122 binding_description.binding = 0;
123 binding_description.stride = sizeof(space::FlameVertex);
124 binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
125
126 std::array<VkVertexInputAttributeDescription, 2> attributes{};
127 attributes[0].binding = 0;
128 attributes[0].location = 0;
129 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
130 attributes[0].offset = offsetof(space::FlameVertex, pos);
131 attributes[1].binding = 0;
132 attributes[1].location = 1;
133 attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
134 attributes[1].offset = offsetof(space::FlameVertex, color);
135
136 VkPipelineVertexInputStateCreateInfo vertex_input{};
137 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
138 vertex_input.vertexBindingDescriptionCount = 1;
139 vertex_input.pVertexBindingDescriptions = &binding_description;
140 vertex_input.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
141 vertex_input.pVertexAttributeDescriptions = attributes.data();
142
143 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
144 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
145 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
146 input_assembly.primitiveRestartEnable = VK_FALSE;
147
148 VkPipelineViewportStateCreateInfo viewport_state{};
149 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
150 viewport_state.viewportCount = 1;
151 viewport_state.scissorCount = 1;
152
153 const std::array<VkDynamicState, 2> dynamic_states = {
154 VK_DYNAMIC_STATE_VIEWPORT,
155 VK_DYNAMIC_STATE_SCISSOR,
156 };
157 VkPipelineDynamicStateCreateInfo dynamic_info{};
158 dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
159 dynamic_info.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
160 dynamic_info.pDynamicStates = dynamic_states.data();
161
162 VkPipelineRasterizationStateCreateInfo rasterizer{};
163 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
164 rasterizer.depthClampEnable = VK_FALSE;
165 rasterizer.rasterizerDiscardEnable = VK_FALSE;
166 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
167 rasterizer.lineWidth = 1.0f;
168 rasterizer.cullMode = VK_CULL_MODE_NONE;
169 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
170 rasterizer.depthBiasEnable = VK_FALSE;
171
172 VkPipelineMultisampleStateCreateInfo multisampling{};
173 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
174 multisampling.sampleShadingEnable = VK_FALSE;
175 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
176
177 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
178 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
179 depth_stencil.depthTestEnable = VK_TRUE;
180 depth_stencil.depthWriteEnable = VK_FALSE;
181 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
182 depth_stencil.depthBoundsTestEnable = VK_FALSE;
183 depth_stencil.stencilTestEnable = VK_FALSE;
184
185 VkPipelineColorBlendAttachmentState color_blend_attachment{};
186 color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
187 color_blend_attachment.blendEnable = VK_TRUE;
188 color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
189 color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
190 color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
191 color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
192 color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
193 color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
194
195 VkPipelineColorBlendStateCreateInfo color_blending{};
196 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
197 color_blending.logicOpEnable = VK_FALSE;
198 color_blending.attachmentCount = 1;
199 color_blending.pAttachments = &color_blend_attachment;
200
201 VkPushConstantRange push_range{};
202 push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
203 push_range.offset = 0;
204 push_range.size = sizeof(space::FlamePushConstants);
205
206 VkPipelineLayoutCreateInfo layout_info{};
207 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
208 layout_info.pushConstantRangeCount = 1;
209 layout_info.pPushConstantRanges = &push_range;
210
211 if (vkCreatePipelineLayout(device, &layout_info, nullptr, &flame_pipeline_layout) != VK_SUCCESS) {
212 throw mxvk::Exception("Failed to create defender flame pipeline layout");
213 }
214
215 const VkFormat color_format = getSwapchainFormat();
216 const VkFormat depth_format = getDepthFormat();
217
218 VkPipelineRenderingCreateInfo rendering_info{};
219 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
220 rendering_info.colorAttachmentCount = 1;
221 rendering_info.pColorAttachmentFormats = &color_format;
222 if (depth_format != VK_FORMAT_UNDEFINED) {
223 rendering_info.depthAttachmentFormat = depth_format;
224 }
225
226 VkGraphicsPipelineCreateInfo pipeline_info{};
227 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
228 pipeline_info.pNext = &rendering_info;
229 pipeline_info.stageCount = static_cast<uint32_t>(shader_stages.size());
230 pipeline_info.pStages = shader_stages.data();
231 pipeline_info.pVertexInputState = &vertex_input;
232 pipeline_info.pInputAssemblyState = &input_assembly;
233 pipeline_info.pViewportState = &viewport_state;
234 pipeline_info.pRasterizationState = &rasterizer;
235 pipeline_info.pMultisampleState = &multisampling;
236 pipeline_info.pDepthStencilState = &depth_stencil;
237 pipeline_info.pColorBlendState = &color_blending;
238 pipeline_info.pDynamicState = &dynamic_info;
239 pipeline_info.layout = flame_pipeline_layout;
240 pipeline_info.renderPass = VK_NULL_HANDLE;
241 pipeline_info.subpass = 0;
242
243 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &flame_pipeline) != VK_SUCCESS) {
244 throw mxvk::Exception("Failed to create defender flame pipeline");
245 }
246 } catch (...) {
247 if (frag_shader_module != VK_NULL_HANDLE) {
248 vkDestroyShaderModule(device, frag_shader_module, nullptr);
249 }
250 vkDestroyShaderModule(device, vert_shader_module, nullptr);
251 cleanup_flame_swapchain_resources();
252 throw;
253 }
254
255 vkDestroyShaderModule(device, frag_shader_module, nullptr);
256 vkDestroyShaderModule(device, vert_shader_module, nullptr);
257 }
258
259 void DefenderWindow::draw_engine_flame(VkCommandBuffer cmd, const VkExtent2D &extent, const glm::mat4 &view, const glm::mat4 &projection) {
260 const bool boost_active = boost_pressed || controller_boost_pressed;
261 const bool propulsion_active = propulsion_pressed || controller_propulsion_pressed || boost_active;
262 if (!propulsion_active || ship.current_speed <= 1.0f || flame_pipeline == VK_NULL_HANDLE || flame_vertex_buffer == VK_NULL_HANDLE || flame_vertex_count == 0) {
263 return;
264 }
265
266 VkViewport viewport{};
267 viewport.x = 0.0f;
268 viewport.y = 0.0f;
269 viewport.width = static_cast<float>(extent.width);
270 viewport.height = static_cast<float>(extent.height);
271 viewport.minDepth = 0.0f;
272 viewport.maxDepth = 1.0f;
273 vkCmdSetViewport(cmd, 0, 1, &viewport);
274
275 VkRect2D scissor{};
276 scissor.offset = {0, 0};
277 scissor.extent = extent;
278 vkCmdSetScissor(cmd, 0, 1, &scissor);
279
280 space::FlamePushConstants pc{};
281 pc.mvp = projection * view * last_ship_model_matrix;
282 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);
283
284 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, flame_pipeline);
285 vkCmdPushConstants(cmd, flame_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
286
287 VkBuffer vertex_buffers[] = {flame_vertex_buffer};
288 VkDeviceSize offsets[] = {0};
289 vkCmdBindVertexBuffers(cmd, 0, 1, vertex_buffers, offsets);
290 vkCmdDraw(cmd, flame_vertex_count, 1, 0, 0);
291 }
292
293 void DefenderWindow::create_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &buffer_memory) const {
294 VkBufferCreateInfo buffer_info{};
295 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
296 buffer_info.size = size;
297 buffer_info.usage = usage;
298 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
299
300 if (vkCreateBuffer(device, &buffer_info, nullptr, &buffer) != VK_SUCCESS) {
301 throw mxvk::Exception("Failed to create defender buffer");
302 }
303
304 VkMemoryRequirements mem_requirements{};
305 vkGetBufferMemoryRequirements(device, buffer, &mem_requirements);
306
307 VkMemoryAllocateInfo alloc_info{};
308 alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
309 alloc_info.allocationSize = mem_requirements.size;
310
311 try {
312 alloc_info.memoryTypeIndex = find_memory_type(mem_requirements.memoryTypeBits, properties);
313 if (vkAllocateMemory(device, &alloc_info, nullptr, &buffer_memory) != VK_SUCCESS) {
314 throw mxvk::Exception("Failed to allocate defender buffer memory");
315 }
316 if (vkBindBufferMemory(device, buffer, buffer_memory, 0) != VK_SUCCESS) {
317 throw mxvk::Exception("Failed to bind defender buffer memory");
318 }
319 } catch (...) {
320 if (buffer_memory != VK_NULL_HANDLE) {
321 vkFreeMemory(device, buffer_memory, nullptr);
322 buffer_memory = VK_NULL_HANDLE;
323 }
324 if (buffer != VK_NULL_HANDLE) {
325 vkDestroyBuffer(device, buffer, nullptr);
326 buffer = VK_NULL_HANDLE;
327 }
328 throw;
329 }
330 }
331
332 [[nodiscard]] uint32_t DefenderWindow::find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags properties) const {
333 VkPhysicalDeviceMemoryProperties mem_properties{};
334 vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
335
336 for (uint32_t i = 0; i < mem_properties.memoryTypeCount; ++i) {
337 if ((type_filter & (1U << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) {
338 return i;
339 }
340 }
341
342 throw mxvk::Exception("Failed to find defender memory type");
343 }
344
345} // namespace defender
VkDevice device
Definition mxvk.hpp:606
VkFormat depth_format
Definition mxvk.hpp:615
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
Definition mxvk.cpp:154
VkPhysicalDevice physical_device
Definition mxvk.hpp:605
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
Definition mxvk.hpp:236
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
Definition mxvk.cpp:152
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:213
constexpr float PI
Single-precision value of pi.
glm::vec4 params
Shader-specific animation parameters.
glm::mat4 mvp
Model-view-projection matrix.