16 void VK_PointSpriteBatch::load(
VK_Window *window,
const std::string &texture_path_value,
const std::string &vertex_shader_path_value,
const std::string &fragment_shader_path_value,
size_t max_vertex_count) {
17 if (window ==
nullptr) {
18 throw mxvk::Exception(
"VK_PointSpriteBatch::load called with null window");
20 if (max_vertex_count == 0) {
21 throw mxvk::Exception(
"VK_PointSpriteBatch::load requires non-zero vertex capacity");
35 texture_path = texture_path_value;
36 vertex_shader_path = vertex_shader_path_value;
37 fragment_shader_path = fragment_shader_path_value;
38 max_vertices = max_vertex_count;
40 if (context.device == VK_NULL_HANDLE || context.physical_device == VK_NULL_HANDLE || context.graphics_queue == VK_NULL_HANDLE || context.command_pool == VK_NULL_HANDLE) {
41 throw mxvk::Exception(
"Cannot create point-sprite batch before Vulkan render resources are available");
43 if (color_attachment_format == VK_FORMAT_UNDEFINED || image_count == 0) {
44 throw mxvk::Exception(
"Cannot create point-sprite batch before swapchain resources are available");
48 create_vertex_buffer();
49 create_swapchain_resources();
54 if (!batch_loaded || window ==
nullptr) {
67 cleanup_swapchain_resources();
68 create_swapchain_resources();
72 cleanup_swapchain_resources();
73 destroy_vertex_buffer();
76 pipeline_cache = VK_NULL_HANDLE;
77 color_attachment_format = VK_FORMAT_UNDEFINED;
78 depth_attachment_format = VK_FORMAT_UNDEFINED;
81 vertex_shader_path.clear();
82 fragment_shader_path.clear();
89 if (!batch_loaded || vertex_buffer.mapped ==
nullptr || vertices ==
nullptr || count == 0) {
93 if (count > max_vertices) {
94 throw mxvk::Exception(
"VK_PointSpriteBatch::upload_vertices exceeds batch capacity");
98 active_vertices = count;
102 if (image_index >= uniform_buffers.size() || uniform_buffers[image_index].mapped ==
nullptr) {
106 UniformBufferObject ubo{};
108 std::memcpy(uniform_buffers[image_index].mapped, &ubo,
sizeof(ubo));
112 if (!batch_loaded || active_vertices == 0 || pipeline == VK_NULL_HANDLE || image_index >= descriptor_sets.size()) {
116 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
117 VkBuffer buffers[] = {vertex_buffer.buffer};
118 VkDeviceSize offsets[] = {0};
119 vkCmdBindVertexBuffers(cmd, 0, 1, buffers, offsets);
120 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_sets[image_index], 0,
nullptr);
121 vkCmdDraw(cmd,
static_cast<uint32_t
>(active_vertices), 1, 0, 0);
125 if (additive_blending == enabled) {
128 additive_blending = enabled;
136 if (depth_test_enabled == enabled) {
139 depth_test_enabled = enabled;
147 if (depth_write_enabled == enabled) {
150 depth_write_enabled = enabled;
157 void VK_PointSpriteBatch::create_vertex_buffer() {
158 create_buffer(context,
sizeof(
PointSpriteVertex) *
static_cast<VkDeviceSize
>(max_vertices), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertex_buffer);
162 void VK_PointSpriteBatch::destroy_vertex_buffer() {
destroy_buffer(context.
device, vertex_buffer); }
164 void VK_PointSpriteBatch::create_swapchain_resources() {
165 create_descriptor_set_layout();
166 create_uniform_buffers();
167 create_descriptor_pool();
168 create_descriptor_sets();
172 void VK_PointSpriteBatch::cleanup_swapchain_resources() {
173 if (context.device == VK_NULL_HANDLE) {
174 descriptor_sets.clear();
175 uniform_buffers.clear();
179 if (descriptor_pool != VK_NULL_HANDLE) {
180 vkDestroyDescriptorPool(context.device, descriptor_pool,
nullptr);
181 descriptor_pool = VK_NULL_HANDLE;
183 if (descriptor_set_layout != VK_NULL_HANDLE) {
184 vkDestroyDescriptorSetLayout(context.device, descriptor_set_layout,
nullptr);
185 descriptor_set_layout = VK_NULL_HANDLE;
187 destroy_uniform_buffers();
188 descriptor_sets.clear();
191 void VK_PointSpriteBatch::create_descriptor_set_layout() {
192 VkDescriptorSetLayoutBinding sampler_binding{};
193 sampler_binding.binding = 0;
194 sampler_binding.descriptorCount = 1;
195 sampler_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
196 sampler_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
198 VkDescriptorSetLayoutBinding ubo_binding{};
199 ubo_binding.binding = 1;
200 ubo_binding.descriptorCount = 1;
201 ubo_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
202 ubo_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
204 std::array<VkDescriptorSetLayoutBinding, 2> bindings{sampler_binding, ubo_binding};
205 VkDescriptorSetLayoutCreateInfo layout_info{};
206 layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
207 layout_info.bindingCount =
static_cast<uint32_t
>(bindings.size());
208 layout_info.pBindings = bindings.data();
209 if (vkCreateDescriptorSetLayout(context.device, &layout_info,
nullptr, &descriptor_set_layout) != VK_SUCCESS) {
210 throw mxvk::Exception(
"failed to create point-sprite descriptor set layout");
214 void VK_PointSpriteBatch::create_uniform_buffers() {
215 uniform_buffers.resize(image_count);
216 for (
auto &buffer : uniform_buffers) {
217 create_buffer(context,
sizeof(UniformBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buffer);
222 void VK_PointSpriteBatch::destroy_uniform_buffers() {
223 for (
auto &buffer : uniform_buffers) {
226 uniform_buffers.clear();
229 void VK_PointSpriteBatch::create_descriptor_pool() {
230 const uint32_t count =
static_cast<uint32_t
>(image_count);
231 std::array<VkDescriptorPoolSize, 2> pool_sizes{};
232 pool_sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
233 pool_sizes[0].descriptorCount = count;
234 pool_sizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
235 pool_sizes[1].descriptorCount = count;
237 VkDescriptorPoolCreateInfo pool_info{};
238 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
239 pool_info.poolSizeCount =
static_cast<uint32_t
>(pool_sizes.size());
240 pool_info.pPoolSizes = pool_sizes.data();
241 pool_info.maxSets = count;
242 if (vkCreateDescriptorPool(context.device, &pool_info,
nullptr, &descriptor_pool) != VK_SUCCESS) {
243 throw mxvk::Exception(
"failed to create point-sprite descriptor pool");
247 void VK_PointSpriteBatch::create_descriptor_sets() {
248 std::vector<VkDescriptorSetLayout> layouts(image_count, descriptor_set_layout);
249 VkDescriptorSetAllocateInfo alloc_info{};
250 alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
251 alloc_info.descriptorPool = descriptor_pool;
252 alloc_info.descriptorSetCount =
static_cast<uint32_t
>(image_count);
253 alloc_info.pSetLayouts = layouts.data();
255 descriptor_sets.resize(image_count, VK_NULL_HANDLE);
256 if (vkAllocateDescriptorSets(context.device, &alloc_info, descriptor_sets.data()) != VK_SUCCESS) {
257 throw mxvk::Exception(
"failed to allocate point-sprite descriptor sets");
260 for (
size_t i = 0; i < image_count; ++i) {
261 VkDescriptorImageInfo image_info{};
262 image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
263 image_info.imageView = texture.view;
264 image_info.sampler = texture.sampler;
266 VkDescriptorBufferInfo buffer_info{};
267 buffer_info.buffer = uniform_buffers[i].buffer;
268 buffer_info.offset = 0;
269 buffer_info.range =
sizeof(UniformBufferObject);
271 std::array<VkWriteDescriptorSet, 2> writes{};
272 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
273 writes[0].dstSet = descriptor_sets[i];
274 writes[0].dstBinding = 0;
275 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
276 writes[0].descriptorCount = 1;
277 writes[0].pImageInfo = &image_info;
278 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
279 writes[1].dstSet = descriptor_sets[i];
280 writes[1].dstBinding = 1;
281 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
282 writes[1].descriptorCount = 1;
283 writes[1].pBufferInfo = &buffer_info;
285 vkUpdateDescriptorSets(context.device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
289 void VK_PointSpriteBatch::create_pipeline() {
292 const std::vector<char> vert_code = read_shader_file(vertex_shader_path);
293 const std::vector<char> frag_code = read_shader_file(fragment_shader_path);
295 VkShaderModule frag_module = VK_NULL_HANDLE;
300 VkPipelineShaderStageCreateInfo vert_stage{};
301 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
302 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
303 vert_stage.module = vert_module;
304 vert_stage.pName =
"main";
306 VkPipelineShaderStageCreateInfo frag_stage{};
307 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
308 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
309 frag_stage.module = frag_module;
310 frag_stage.pName =
"main";
311 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {vert_stage, frag_stage};
313 VkVertexInputBindingDescription binding{};
315 binding.stride =
sizeof(PointSpriteVertex);
316 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
318 std::array<VkVertexInputAttributeDescription, 3> attributes{};
319 attributes[0].binding = 0;
320 attributes[0].location = 0;
321 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
322 attributes[0].offset = offsetof(PointSpriteVertex, position);
323 attributes[1].binding = 0;
324 attributes[1].location = 1;
325 attributes[1].format = VK_FORMAT_R32_SFLOAT;
326 attributes[1].offset = offsetof(PointSpriteVertex, size);
327 attributes[2].binding = 0;
328 attributes[2].location = 2;
329 attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
330 attributes[2].offset = offsetof(PointSpriteVertex, color);
332 VkPipelineVertexInputStateCreateInfo vertex_input{};
333 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
334 vertex_input.vertexBindingDescriptionCount = 1;
335 vertex_input.pVertexBindingDescriptions = &binding;
336 vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
337 vertex_input.pVertexAttributeDescriptions = attributes.data();
339 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
340 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
341 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
342 input_assembly.primitiveRestartEnable = VK_FALSE;
344 std::array<VkDynamicState, 2> dynamic_states = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
345 VkPipelineDynamicStateCreateInfo dynamic_state{};
346 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
347 dynamic_state.dynamicStateCount =
static_cast<uint32_t
>(dynamic_states.size());
348 dynamic_state.pDynamicStates = dynamic_states.data();
350 VkPipelineViewportStateCreateInfo viewport_state{};
351 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
352 viewport_state.viewportCount = 1;
353 viewport_state.scissorCount = 1;
355 VkPipelineRasterizationStateCreateInfo rasterizer{};
356 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
357 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
358 rasterizer.cullMode = VK_CULL_MODE_NONE;
359 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
360 rasterizer.lineWidth = 1.0f;
362 VkPipelineMultisampleStateCreateInfo multisampling{};
363 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
364 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
366 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
367 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
368 depth_stencil.depthTestEnable = depth_test_enabled ? VK_TRUE : VK_FALSE;
369 depth_stencil.depthWriteEnable = depth_write_enabled ? VK_TRUE : VK_FALSE;
370 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
372 VkPipelineColorBlendAttachmentState blend{};
373 blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
374 blend.blendEnable = VK_TRUE;
375 blend.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
376 blend.dstColorBlendFactor = additive_blending ? VK_BLEND_FACTOR_ONE : VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
377 blend.colorBlendOp = VK_BLEND_OP_ADD;
378 blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
379 blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
380 blend.alphaBlendOp = VK_BLEND_OP_ADD;
382 VkPipelineColorBlendStateCreateInfo color_blending{};
383 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
384 color_blending.attachmentCount = 1;
385 color_blending.pAttachments = &blend;
387 VkPipelineLayoutCreateInfo layout_info{};
388 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
389 layout_info.setLayoutCount = 1;
390 layout_info.pSetLayouts = &descriptor_set_layout;
391 if (vkCreatePipelineLayout(context.device, &layout_info,
nullptr, &pipeline_layout) != VK_SUCCESS) {
392 throw mxvk::Exception(
"failed to create point-sprite pipeline layout");
395 VkPipelineRenderingCreateInfo rendering_info{};
396 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
397 rendering_info.colorAttachmentCount = 1;
398 rendering_info.pColorAttachmentFormats = &color_attachment_format;
399 rendering_info.depthAttachmentFormat = depth_attachment_format;
400 rendering_info.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
402 VkGraphicsPipelineCreateInfo pipeline_info{};
403 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
404 pipeline_info.pNext = &rendering_info;
405 pipeline_info.stageCount =
static_cast<uint32_t
>(shader_stages.size());
406 pipeline_info.pStages = shader_stages.data();
407 pipeline_info.pVertexInputState = &vertex_input;
408 pipeline_info.pInputAssemblyState = &input_assembly;
409 pipeline_info.pViewportState = &viewport_state;
410 pipeline_info.pRasterizationState = &rasterizer;
411 pipeline_info.pMultisampleState = &multisampling;
412 pipeline_info.pDepthStencilState = &depth_stencil;
413 pipeline_info.pColorBlendState = &color_blending;
414 pipeline_info.pDynamicState = &dynamic_state;
415 pipeline_info.layout = pipeline_layout;
416 pipeline_info.renderPass = VK_NULL_HANDLE;
417 if (vkCreateGraphicsPipelines(context.device, pipeline_cache, 1, &pipeline_info,
nullptr, &pipeline) != VK_SUCCESS) {
418 throw mxvk::Exception(
"failed to create point-sprite graphics pipeline");
421 if (frag_module != VK_NULL_HANDLE) {
422 vkDestroyShaderModule(context.device, frag_module,
nullptr);
424 vkDestroyShaderModule(context.device, vert_module,
nullptr);
428 vkDestroyShaderModule(context.device, frag_module,
nullptr);
429 vkDestroyShaderModule(context.device, vert_module,
nullptr);
432 void VK_PointSpriteBatch::destroy_pipeline() {
433 if (context.device == VK_NULL_HANDLE) {
434 pipeline = VK_NULL_HANDLE;
435 pipeline_layout = VK_NULL_HANDLE;
438 if (pipeline != VK_NULL_HANDLE) {
439 vkDestroyPipeline(context.device, pipeline,
nullptr);
440 pipeline = VK_NULL_HANDLE;
442 if (pipeline_layout != VK_NULL_HANDLE) {
443 vkDestroyPipelineLayout(context.device, pipeline_layout,
nullptr);
444 pipeline_layout = VK_NULL_HANDLE;
448 std::vector<char> VK_PointSpriteBatch::read_shader_file(
const std::string &path)
const {
return mxvk::load_spv(path); }
~VK_PointSpriteBatch()
Destroy all owned Vulkan resources.
void load(VK_Window *window, const std::string &texture_path, const std::string &vertex_shader_path, const std::string &fragment_shader_path, size_t max_vertices)
Create texture, vertex buffer, descriptors, and point-list pipeline.
void set_depth_write_enabled(bool enabled)
Enable or disable depth writes in the point-sprite pipeline.
void render(VkCommandBuffer cmd, uint32_t image_index)
Record point-sprite draw commands into an active rendering scope.
void update_mvp(uint32_t image_index, const glm::mat4 &mvp)
Update the MVP uniform for one swapchain image.
void resize(VK_Window *window)
Recreate swapchain-dependent resources after a window resize.
void cleanup()
Destroy all owned resources and reset the batch to an unloaded state.
void set_additive_blending(bool enabled)
Select additive or alpha-over color blending.
void upload_vertices(const PointSpriteVertex *vertices, size_t count)
Copy point vertices into the persistent mapped vertex buffer.
void set_depth_test_enabled(bool enabled)
Enable or disable depth testing in the point-sprite pipeline.
Main Vulkan window wrapper for MXVK.
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
VkQueue getGraphicsQueue() const noexcept
Get the graphics queue handle.
VkCommandPool getCommandPool() const noexcept
Get the command pool used for graphics/upload work.
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
VkPipelineCache getPipelineCache() const noexcept
Get the persistent Vulkan pipeline cache used by framework pipelines.
VkPhysicalDevice getPhysicalDevice() const noexcept
Get the Vulkan physical device handle.
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Reusable point-sprite renderer for particle and starfield effects.
Utilities for loading and saving PNG images.
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
void create_texture_from_png(const VulkanContext &context, const std::string &path, TextureResource &texture, VkFormat format=VK_FORMAT_R8G8B8A8_UNORM)
Load a PNG and upload it into a sampled 2D texture.
void map_buffer(VkDevice device, BufferResource &buffer)
Persistently map a host-visible buffer allocation.
void create_buffer(const VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, BufferResource &buffer)
Create and bind a Vulkan buffer allocation.
void destroy_buffer(VkDevice device, BufferResource &buffer)
Unmap and destroy a BufferResource.
std::vector< char > load_spv(const std::string &path)
Load a SPIR-V file from disk.
void destroy_texture(VkDevice device, TextureResource &texture)
Destroy every Vulkan handle owned by a TextureResource.
Vertex layout consumed by VK_PointSpriteBatch.
VkDevice device
Logical device used to create and destroy resources.